Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[API Proposal]: Expose signature and protected headers raw bytes #72611

Closed
jozkee opened this issue Jul 21, 2022 · 2 comments · Fixed by #73796
Closed

[API Proposal]: Expose signature and protected headers raw bytes #72611

jozkee opened this issue Jul 21, 2022 · 2 comments · Fixed by #73796
Assignees
Labels
api-approved API was approved in API review, it can be implemented area-System.Security blocking Marks issues that we want to fast track in order to unblock other important work
Milestone

Comments

@jozkee
Copy link
Member

jozkee commented Jul 21, 2022

Background and motivation

We need to add APIs to expose the raw bytes of encoded protected headers and signatures in order to enable counter signature scenarios.
We don't directly support countersign as the spec defining them is still on draft, therefore, we want to allow them to at least be built without reflection.

API Proposal

Proposal:

namespace System.Security.Cryptography.Cose
{
    public abstract partial class CoseMessage
    {
+        public ReadOnlyMemory<byte> EncodedProtectedHeaders { get { throw null; } }
    }
    
    public sealed partial class CoseSign1Message : CoseMessage
    {
+        public ReadOnlyMemory<byte> Signature { get { throw null; } }
    }
    
    public sealed partial class CoseSignature
    {
+        public ReadOnlyMemory<byte> EncodedProtectedHeaders { get { throw null; } }
+        public ReadOnlyMemory<byte> Signature { get { throw null; } }
    }
}

API Usage

Here’s an example, it is quite cumbersome because countersigns are cumbersome, but please see GetToBeSignedForCounterSign which requires the aforementioned APIs to build the object that will be signed.

byte[] CounterSign(byte[] encodedMsg)
{
    CoseMultiSignMessage multiSignMsg = CoseMessage.DecodeMultiSign(encodedMsg);

    CoseSignature signatureToCounterSign = multiSignMsg.Signatures[0];
    byte[] encodedCounterSignature = GetCounterSign(multiSignMsg, signatureToCounterSign);
    
    CoseHeaderLabel counterSignLabel = new(7);
    CoseHeaderValue counterSignValue = CoseHeaderValue.FromEncodedValue(encodedCounterSignature);
    signatureToCounterSign.UnprotectedHeaders.Add(counterSignLabel, counterSignValue);

    return multiSignMsg.Encode();
}

private byte[] GetCounterSign(CoseMultiSignMessage msg, CoseSignature signature)
{
    Assert.True(msg.Signatures.Contains(signature));

    var writer = new CborWriter();
    writer.WriteStartArray(3);
    // encoded protected
    byte[] encodedProtectedHeaders = GetCounterSignProtectedHeaders(-7 /*ES256*/);
    writer.WriteByteString(encodedProtectedHeaders);
    // empty unprotected headers
    writer.WriteStartMap(0);
    writer.WriteEndMap();
    // signature
    byte[] signatureBytes = GetSignature(MyEcdsaKey, MyHashAlgorithm, GetToBeSignedForCounterSign(msg, signature, encodedProtectedHeaders));
    writer.WriteByteString(signatureBytes);
    writer.WriteEndArray();

    return writer.Encode();
}

private byte[] GetToBeSignedForCounterSign(CoseMultiSignMessage msg, CoseSignature signature, byte[] signProtected)
{
    var writer = new CborWriter();
    writer.WriteStartArray(5);
    writer.WriteTextString("CounterSignature");
    writer.WriteByteString(msg.EncodedProtectedHeaders.Span); // body_protected
    writer.WriteByteString(signProtected); // sign_protected
    writer.WriteByteString(default(Span<byte>)); // external_aad
    writer.WriteByteString(signature.Signature.Span);
    writer.WriteEndArray();

    return writer.Encode();
}


private static byte[] GetCounterSignProtectedHeaders(int algorithm)
{
    var writer = new CborWriter();
    writer.WriteStartMap(1);
    writer.WriteInt32(1); // alg
    writer.WriteInt32(algorithm);
    writer.WriteEndMap();

    return writer.Encode();
}

private static byte[] GetSignature(AsymmetricAlgorithm key, HashAlgorithmName hash, byte[] toBeSigned)
{
    if (key is ECDsa ecdsa)
    {
        return ecdsa.SignData(toBeSigned, hash);
    }
    else if (key is RSA rsa)
    {
        return rsa.SignData(toBeSigned, hash, RSASignaturePadding.Pss);
    }

    throw new ArgumentException("Key must be ECDsa or RSA", nameof(key));
}

Alternative Designs

No response

Risks

None. As per the RFC, the raw bytes are part of the COSE_Sign* structure definition.
Also, this change only applies to COSE_Sign*; COSE_Encrypt and other structures planned for the future won't be affected.

@jozkee jozkee added api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Security blocking Marks issues that we want to fast track in order to unblock other important work labels Jul 21, 2022
@jozkee jozkee added this to the 7.0.0 milestone Jul 21, 2022
@jozkee jozkee self-assigned this Jul 21, 2022
@ghost
Copy link

ghost commented Jul 21, 2022

Tagging subscribers to this area: @dotnet/area-system-security, @vcsjones
See info in area-owners.md if you want to be subscribed.

Issue Details

Background and motivation

We need to add APIs to expose the raw bytes of encoded protected headers and signatures in order to enable counter signature scenarios.
We don't directly support countersign as the spec defining them is still on draft, therefore, we want to allow them to at least be built without reflection.

API Proposal

Proposal:

namespace System.Security.Cryptography.Cose
{
    public abstract partial class CoseMessage
    {
+        public ReadOnlyMemory<byte> EncodedProtectedHeaders { get { throw null; } }
    }
    
    public sealed partial class CoseSign1Message : CoseMessage
    {
+        public ReadOnlyMemory<byte> Signature { get { throw null; } }
    }
    
    public sealed partial class CoseSignature
    {
+        public ReadOnlyMemory<byte> EncodedProtectedHeaders { get { throw null; } }
+        public ReadOnlyMemory<byte> Signature { get { throw null; } }
    }
}

API Usage

Here’s an example, it is quite cumbersome because countersigns are cumbersome, but please see GetToBeSignedForCounterSign which requires the aforementioned APIs to build the object that will be signed.

byte[] CounterSign(byte[] encodedMsg)
{
    CoseMultiSignMessage multiSignMsg = CoseMessage.DecodeMultiSign(encodedMsg);

    CoseSignature signatureToCounterSign = multiSignMsg.Signatures[0];
    byte[] encodedCounterSignature = GetCounterSign(multiSignMsg, signatureToCounterSign);
    
    CoseHeaderLabel counterSignLabel = new(7);
    CoseHeaderValue counterSignValue = CoseHeaderValue.FromEncodedValue(encodedCounterSignature);
    signatureToCounterSign.UnprotectedHeaders.Add(counterSignLabel, counterSignValue);

    return multiSignMsg.Encode();
}

private byte[] GetCounterSign(CoseMultiSignMessage msg, CoseSignature signature)
{
    Assert.True(msg.Signatures.Contains(signature));

    var writer = new CborWriter();
    writer.WriteStartArray(3);
    // encoded protected
    byte[] encodedProtectedHeaders = GetCounterSignProtectedHeaders(-7 /*ES256*/);
    writer.WriteByteString(encodedProtectedHeaders);
    // empty unprotected headers
    writer.WriteStartMap(0);
    writer.WriteEndMap();
    // signature
    byte[] signatureBytes = GetSignature(MyEcdsaKey, MyHashAlgorithm, GetToBeSignedForCounterSign(msg, signature, encodedProtectedHeaders));
    writer.WriteByteString(signatureBytes);
    writer.WriteEndArray();

    return writer.Encode();
}

private byte[] GetToBeSignedForCounterSign(CoseMultiSignMessage msg, CoseSignature signature, byte[] signProtected)
{
    var writer = new CborWriter();
    writer.WriteStartArray(5);
    writer.WriteTextString("CounterSignature");
    writer.WriteByteString(msg.EncodedProtectedHeaders.Span); // body_protected
    writer.WriteByteString(signProtected); // sign_protected
    writer.WriteByteString(default(Span<byte>)); // external_aad
    writer.WriteByteString(signature.Signature.Span);
    writer.WriteEndArray();

    return writer.Encode();
}


private static byte[] GetCounterSignProtectedHeaders(int algorithm)
{
    var writer = new CborWriter();
    writer.WriteStartMap(1);
    writer.WriteInt32(1); // alg
    writer.WriteInt32(algorithm);
    writer.WriteEndMap();

    return writer.Encode();
}

private static byte[] GetSignature(AsymmetricAlgorithm key, HashAlgorithmName hash, byte[] toBeSigned)
{
    if (key is ECDsa ecdsa)
    {
        return ecdsa.SignData(toBeSigned, hash);
    }
    else if (key is RSA rsa)
    {
        return rsa.SignData(toBeSigned, hash, RSASignaturePadding.Pss);
    }

    throw new ArgumentException("Key must be ECDsa or RSA", nameof(key));
}

Alternative Designs

No response

Risks

None. As per the RFC, the raw bytes are part of the COSE_Sign* structure definition.
Also, this change only applies to COSE_Sign*; COSE_Encrypt and other structures planned for the future won't be affected.

Author: Jozkee
Assignees: Jozkee
Labels:

api-suggestion, area-System.Security, blocking

Milestone: 7.0.0

@jozkee jozkee added api-ready-for-review API is ready for review, it is NOT ready for implementation and removed api-suggestion Early API idea and discussion, it is NOT ready for implementation labels Jul 21, 2022
@terrajobst
Copy link
Contributor

terrajobst commented Jul 26, 2022

Video

  • Let's rename EncodedProtectedHeaders to RawProtectedHeadersByte
 namespace System.Security.Cryptography.Cose;
 
 public partial class CoseMessage
 {
+    public ReadOnlyMemory<byte> RawProtectedHeaders { get; }
 }
 
 public partial class CoseSign1Message : CoseMessage
 {
+    public ReadOnlyMemory<byte> Signature { get; }
 }
 
 public partial class CoseSignature
 {
+    public ReadOnlyMemory<byte> RawProtectedHeaders { get; }
+    public ReadOnlyMemory<byte> Signature { get; }
 }

@terrajobst terrajobst added api-approved API was approved in API review, it can be implemented and removed api-ready-for-review API is ready for review, it is NOT ready for implementation labels Jul 26, 2022
@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Aug 11, 2022
@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Aug 12, 2022
@ghost ghost locked as resolved and limited conversation to collaborators Sep 11, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-approved API was approved in API review, it can be implemented area-System.Security blocking Marks issues that we want to fast track in order to unblock other important work
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants