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

Add support for indefinite length arrays #74215

Merged
merged 5 commits into from
Aug 29, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema

<!--
Microsoft ResX Schema
Version 2.0

The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.

Example:

... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
Expand All @@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>

There are any number of "resheader" rows that contain simple
There are any number of "resheader" rows that contain simple
name/value pairs.

Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.

The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:

Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.

mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.

mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.

mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
Expand Down Expand Up @@ -154,10 +154,10 @@
<value>Label in Critical Headers array was incorrect.</value>
</data>
<data name="CriticalHeadersMustBeArrayOfAtLeastOne" xml:space="preserve">
<value>Critical Headers must be a definite-length CBOR array of at least one element.</value>
<value>Critical Headers must be a CBOR array of at least one element.</value>
</data>
<data name="DecodeCoseSignatureMustBeArrayOfThree" xml:space="preserve">
<value>COSE Signature must be a definite-length array of 3 elements.</value>
<value>COSE Signature must be an array of three elements.</value>
</data>
<data name="DecodeErrorWhileDecoding" xml:space="preserve">
<value>Error while decoding COSE message. {0}</value>
Expand All @@ -168,11 +168,14 @@
<data name="DecodeMessageContainedTrailingData" xml:space="preserve">
<value>CBOR payload contained trailing data after message was complete.</value>
</data>
<data name="DecodeMultiSignArrayLengthMustBeFour" xml:space="preserve">
<value>COSE_Sign must be an array of four elements.</value>
</data>
<data name="DecodeMultiSignIncorrectTag" xml:space="preserve">
<value>Incorrect tag. Expected Sign(98) or Untagged, Actual '{0}'.</value>
</data>
<data name="DecodeSign1ArrayLengthMustBeFour" xml:space="preserve">
<value>Array length for COSE_Sign1 must be four.</value>
<value>COSE_Sign1 must be an array of four elements.</value>
</data>
<data name="DecodeSign1EncodedProtectedMapIncorrect" xml:space="preserve">
<value>Protected map was incorrect.</value>
Expand Down Expand Up @@ -225,4 +228,4 @@
<data name="Sign1VerifyAlgIsRequired" xml:space="preserve">
<value>Algorithm (alg) header is required and it must be a protected header.</value>
</data>
</root>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -266,16 +266,18 @@ private static void ValidateInsertion(CoseHeaderLabel label, CoseHeaderValue val
reader.SkipValue();
break;
case KnownHeaders.Crit:
int length = reader.ReadStartArray().GetValueOrDefault();
if (length < 1)
{
throw new ArgumentException(SR.CriticalHeadersMustBeArrayOfAtLeastOne, nameof(value));
}
reader.ReadStartArray();
bool isEmpty = true;

for (int i = 0; i < length; i++)
while (true)
{
CborReaderState state = reader.PeekState();
if (state == CborReaderState.UnsignedInteger || state == CborReaderState.NegativeInteger)
if (state == CborReaderState.EndArray)
{
reader.ReadEndArray();
break;
}
else if (state == CborReaderState.UnsignedInteger || state == CborReaderState.NegativeInteger)
{
reader.ReadInt32();
}
Expand All @@ -287,8 +289,13 @@ private static void ValidateInsertion(CoseHeaderLabel label, CoseHeaderValue val
{
throw new ArgumentException(SR.Format(SR.CoseHeaderMapHeaderDoesNotAcceptSpecifiedValue, label.LabelName), nameof(value));
}
isEmpty = false;
}

if (isEmpty)
{
throw new ArgumentException(SR.CriticalHeadersMustBeArrayOfAtLeastOne, nameof(value));
}
reader.SkipToParent();
break;
case KnownHeaders.ContentType:
if (initialState != CborReaderState.TextString &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,18 @@ private static CoseSign1Message DecodeCoseSign1Core(CborReader reader)
throw new CryptographicException(SR.Format(SR.DecodeSign1IncorrectTag, tag));
}

ReadOnlyMemory<byte> coseSignArray = reader.ReadEncodedValue();

if (reader.BytesRemaining != 0)
{
throw new CryptographicException(SR.Format(SR.DecodeErrorWhileDecoding, SR.DecodeMessageContainedTrailingData));
}

reader = new CborReader(coseSignArray);

int? arrayLength = reader.ReadStartArray();
if (arrayLength != 4)
if (arrayLength.HasValue ? arrayLength != CoseSign1Message.Sign1ArrayLength :
HasIndefiniteLengthArrayIncorrectLength(coseSignArray, CoseSign1Message.Sign1ArrayLength))
{
throw new CryptographicException(SR.Format(SR.DecodeErrorWhileDecoding, SR.DecodeSign1ArrayLengthMustBeFour));
}
Expand All @@ -149,10 +159,7 @@ private static CoseSign1Message DecodeCoseSign1Core(CborReader reader)
byte[] signature = DecodeSignature(reader);
reader.ReadEndArray();

if (reader.BytesRemaining != 0)
{
throw new CryptographicException(SR.Format(SR.DecodeErrorWhileDecoding, SR.DecodeMessageContainedTrailingData));
}
Debug.Assert(reader.BytesRemaining == 0);

return new CoseSign1Message(protectedHeader, unprotectedHeader, payload, signature, protectedHeaderAsBstr, tag.HasValue);
}
Expand Down Expand Up @@ -207,10 +214,20 @@ private static CoseMultiSignMessage DecodeCoseMultiSignCore(CborReader reader)
throw new CryptographicException(SR.Format(SR.DecodeMultiSignIncorrectTag, tag));
}

ReadOnlyMemory<byte> coseSignArray = reader.ReadEncodedValue();

if (reader.BytesRemaining != 0)
{
throw new CryptographicException(SR.Format(SR.DecodeErrorWhileDecoding, SR.DecodeMessageContainedTrailingData));
}

reader = new CborReader(coseSignArray);

int? arrayLength = reader.ReadStartArray();
if (arrayLength != 4)
if (arrayLength.HasValue ? arrayLength != CoseMultiSignMessage.MultiSignArrayLength :
HasIndefiniteLengthArrayIncorrectLength(coseSignArray, CoseMultiSignMessage.MultiSignArrayLength))
{
throw new CryptographicException(SR.Format(SR.DecodeErrorWhileDecoding, SR.DecodeSign1ArrayLengthMustBeFour));
throw new CryptographicException(SR.Format(SR.DecodeErrorWhileDecoding, SR.DecodeMultiSignArrayLengthMustBeFour));
}

var protectedHeaders = new CoseHeaderMap();
Expand All @@ -225,14 +242,10 @@ private static CoseMultiSignMessage DecodeCoseMultiSignCore(CborReader reader)
}

byte[]? payload = DecodePayload(reader);
List<CoseSignature> signatures = DecodeCoseSignaturesArray(reader, encodedProtectedHeaders);
List<CoseSignature> signatures = DecodeCoseSignaturesArray(reader);

reader.ReadEndArray();

if (reader.BytesRemaining != 0)
{
throw new CryptographicException(SR.Format(SR.DecodeErrorWhileDecoding, SR.DecodeMessageContainedTrailingData));
}
Debug.Assert(reader.BytesRemaining == 0);

return new CoseMultiSignMessage(protectedHeaders, unprotectedHeaders, payload, signatures, encodedProtectedHeaders, tag.HasValue);
}
Expand Down Expand Up @@ -319,46 +332,75 @@ private static byte[] DecodeSignature(CborReader reader)
return reader.ReadByteString();
}

private static List<CoseSignature> DecodeCoseSignaturesArray(CborReader reader, byte[] bodyProtected)
private static List<CoseSignature> DecodeCoseSignaturesArray(CborReader reader)
{
int? signaturesLength = reader.ReadStartArray();
List<CoseSignature> signatures = new List<CoseSignature>(signaturesLength.GetValueOrDefault());

while (reader.PeekState() == CborReaderState.StartArray)
{
CoseSignature signature = DecodeCoseSignature(reader.ReadEncodedValue());
signatures.Add(signature);
}

if (signaturesLength.GetValueOrDefault() < 1)
reader.ReadEndArray();

if (signatures.Count < 1)
{
throw new CryptographicException(SR.Format(SR.DecodeErrorWhileDecoding, SR.MultiSignMessageMustCarryAtLeastOneSignature));
}

List<CoseSignature> signatures = new List<CoseSignature>(signaturesLength!.Value);
return signatures;
}

for (int i = 0; i < signaturesLength; i++)
private static CoseSignature DecodeCoseSignature(ReadOnlyMemory<byte> coseSignature)
{
var reader = new CborReader(coseSignature);
int? length = reader.ReadStartArray();

if (length.HasValue ? length != CoseMultiSignMessage.CoseSignatureArrayLength :
HasIndefiniteLengthArrayIncorrectLength(coseSignature, CoseMultiSignMessage.CoseSignatureArrayLength))
{
int? length = reader.ReadStartArray();
throw new CryptographicException(SR.Format(SR.DecodeErrorWhileDecoding, SR.DecodeCoseSignatureMustBeArrayOfThree));
}

if (length != CoseMultiSignMessage.CoseSignatureArrayLength)
{
throw new CryptographicException(SR.Format(SR.DecodeErrorWhileDecoding, SR.DecodeCoseSignatureMustBeArrayOfThree));
}
var protectedHeaders = new CoseHeaderMap();
DecodeProtectedBucket(reader, protectedHeaders, out byte[] signProtected);

var protectedHeaders = new CoseHeaderMap();
DecodeProtectedBucket(reader, protectedHeaders, out byte[] signProtected);
var unprotectedHeaders = new CoseHeaderMap();
DecodeUnprotectedBucket(reader, unprotectedHeaders);

var unprotectedHeaders = new CoseHeaderMap();
DecodeUnprotectedBucket(reader, unprotectedHeaders);
if (ContainDuplicateLabels(protectedHeaders, unprotectedHeaders))
{
throw new CryptographicException(SR.Sign1SignHeaderDuplicateLabels);
}

if (ContainDuplicateLabels(protectedHeaders, unprotectedHeaders))
{
throw new CryptographicException(SR.Sign1SignHeaderDuplicateLabels);
}
byte[] signatureBytes = DecodeSignature(reader);
reader.ReadEndArray();

byte[] signatureBytes = DecodeSignature(reader);
return new CoseSignature(protectedHeaders, unprotectedHeaders, signProtected, signatureBytes);
}

signatures.Add(new CoseSignature(protectedHeaders, unprotectedHeaders, bodyProtected, signProtected, signatureBytes));
private static bool HasIndefiniteLengthArrayIncorrectLength(ReadOnlyMemory<byte> encodedArray, int expectedLength)
{
var reader = new CborReader(encodedArray);
reader.ReadStartArray();
int count = 0;

reader.ReadEndArray();
while (reader.PeekState() != CborReaderState.EndArray)
{
reader.SkipValue();
count++;

if (count > expectedLength)
return true;
jozkee marked this conversation as resolved.
Show resolved Hide resolved
}

bool retVal = count != expectedLength;
reader.ReadEndArray();
Debug.Assert(reader.BytesRemaining == 0);

return signatures;
return retVal;
}

internal static void AppendToBeSigned(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace System.Security.Cryptography.Cose
/// </summary>
public sealed class CoseMultiSignMessage : CoseMessage
{
private const int MultiSignArrayLength = 4;
internal const int MultiSignArrayLength = 4;
private const int MultiSignSizeOfCborTag = 2;
internal const int CoseSignatureArrayLength = 3;

Expand Down Expand Up @@ -804,7 +804,7 @@ private void AddSignatureCore(ReadOnlySpan<byte> contentBytes, Stream? contentSt
bytesWritten = CoseHelpers.SignHash(signer, hasher, buffer);

byte[] signature = bufferSpan.Slice(0, bytesWritten).ToArray();
_signatures.Add(new CoseSignature(this, signProtectedHeaders, signer.UnprotectedHeaders, _protectedHeaderAsBstr, encodedSignProtected, signature));
_signatures.Add(new CoseSignature(this, signProtectedHeaders, signer.UnprotectedHeaders, encodedSignProtected, signature));
}
}
finally
Expand Down Expand Up @@ -877,7 +877,7 @@ private async Task AddSignatureCoreAsync(Stream content, CoseSigner signer, Read
bytesWritten = CoseHelpers.SignHash(signer, hasher, buffer);

byte[] signature = buffer.AsSpan(0, bytesWritten).ToArray();
_signatures.Add(new CoseSignature(this, signProtectedHeaders, signer.UnprotectedHeaders, _protectedHeaderAsBstr, encodedSignProtected, signature));
_signatures.Add(new CoseSignature(this, signProtectedHeaders, signer.UnprotectedHeaders, encodedSignProtected, signature));
}

ArrayPool<byte>.Shared.Return(buffer, clearArray: true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace System.Security.Cryptography.Cose
/// </summary>
public sealed class CoseSign1Message : CoseMessage
{
private const int Sign1ArrayLength = 4;
internal const int Sign1ArrayLength = 4;
private const int Sign1SizeOfCborTag = 1;
private readonly byte[] _signature;

Expand Down
Loading