Add support for indefinite length arrays (#74215)
authorDavid Cantú <dacantu@microsoft.com>
Mon, 29 Aug 2022 15:44:13 +0000 (10:44 -0500)
committerGitHub <noreply@github.com>
Mon, 29 Aug 2022 15:44:13 +0000 (08:44 -0700)
* Add support for indefinite length arrays

* Update src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseHeaderMap.cs

Co-authored-by: campersau <buchholz.bastian@googlemail.com>
* Validate indefinite-length arrays length up-front for fixed-lengh arrays

* Update src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseMessage.cs

Co-authored-by: Jeremy Barton <jbarton@microsoft.com>
Co-authored-by: campersau <buchholz.bastian@googlemail.com>
Co-authored-by: Jeremy Barton <jbarton@microsoft.com>
src/libraries/System.Security.Cryptography.Cose/src/Resources/Strings.resx
src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseHeaderMap.cs
src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseMessage.cs
src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseMultiSignMessage.cs
src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseSign1Message.cs
src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseSignature.cs
src/libraries/System.Security.Cryptography.Cose/tests/CoseHeaderMapTests.cs
src/libraries/System.Security.Cryptography.Cose/tests/CoseMessageTests.DecodeMultiSign.cs
src/libraries/System.Security.Cryptography.Cose/tests/CoseMessageTests.DecodeSign1.cs
src/libraries/System.Security.Cryptography.Cose/tests/CoseTestHelpers.cs

index 4a371d8..c03febd 100644 (file)
@@ -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>
         <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.
     -->
     <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>
   <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>
   <data name="Sign1VerifyAlgIsRequired" xml:space="preserve">
     <value>Algorithm (alg) header is required and it must be a protected header.</value>
   </data>
-</root>
+</root>
\ No newline at end of file
index 5b9e30b..50d36b1 100644 (file)
@@ -266,16 +266,18 @@ namespace System.Security.Cryptography.Cose
                             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();
                                 }
@@ -287,8 +289,13 @@ namespace System.Security.Cryptography.Cose
                                 {
                                     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 &&
index 1f5abdc..1d74e26 100644 (file)
@@ -128,8 +128,18 @@ namespace System.Security.Cryptography.Cose
                     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));
                 }
@@ -149,10 +159,7 @@ namespace System.Security.Cryptography.Cose
                 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);
             }
@@ -207,10 +214,20 @@ namespace System.Security.Cryptography.Cose
                     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();
@@ -225,14 +242,10 @@ namespace System.Security.Cryptography.Cose
                 }
 
                 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);
             }
@@ -319,46 +332,77 @@ namespace System.Security.Cryptography.Cose
             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);
+            }
+
+            reader.ReadEndArray();
 
-            if (signaturesLength.GetValueOrDefault() < 1)
+            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;
+                }
             }
+
+            bool retVal = count != expectedLength;
             reader.ReadEndArray();
+            Debug.Assert(reader.BytesRemaining == 0);
 
-            return signatures;
+            return retVal;
         }
 
         internal static void AppendToBeSigned(
index 3b0eceb..351f987 100644 (file)
@@ -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;
 
@@ -804,7 +804,7 @@ namespace System.Security.Cryptography.Cose
                     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
@@ -877,7 +877,7 @@ namespace System.Security.Cryptography.Cose
                 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);
index a38a584..4a4bdc5 100644 (file)
@@ -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;
 
index c716e07..831c5c8 100644 (file)
@@ -14,7 +14,6 @@ namespace System.Security.Cryptography.Cose
     /// </summary>
     public sealed class CoseSignature
     {
-        private readonly byte[] _encodedBodyProtectedHeaders;
         internal readonly byte[] _encodedSignProtectedHeaders;
         internal readonly byte[] _signature;
         private CoseMultiSignMessage? _message;
@@ -43,17 +42,16 @@ namespace System.Security.Cryptography.Cose
         /// <value>A region of memory that contains the digital signature.</value>
         public ReadOnlyMemory<byte> Signature => _signature;
 
-        internal CoseSignature(CoseMultiSignMessage message, CoseHeaderMap protectedHeaders, CoseHeaderMap unprotectedHeaders, byte[] encodedBodyProtectedHeaders, byte[] encodedSignProtectedHeaders, byte[] signature)
-            : this(protectedHeaders, unprotectedHeaders, encodedBodyProtectedHeaders, encodedSignProtectedHeaders, signature)
+        internal CoseSignature(CoseMultiSignMessage message, CoseHeaderMap protectedHeaders, CoseHeaderMap unprotectedHeaders, byte[] encodedSignProtectedHeaders, byte[] signature)
+            : this(protectedHeaders, unprotectedHeaders, encodedSignProtectedHeaders, signature)
         {
             Message = message;
         }
 
-        internal CoseSignature(CoseHeaderMap protectedHeaders, CoseHeaderMap unprotectedHeaders, byte[] encodedBodyProtectedHeaders, byte[] encodedSignProtectedHeaders, byte[] signature)
+        internal CoseSignature(CoseHeaderMap protectedHeaders, CoseHeaderMap unprotectedHeaders, byte[] encodedSignProtectedHeaders, byte[] signature)
         {
             ProtectedHeaders = protectedHeaders;
             UnprotectedHeaders = unprotectedHeaders;
-            _encodedBodyProtectedHeaders = encodedBodyProtectedHeaders;
             _encodedSignProtectedHeaders = encodedSignProtectedHeaders;
             _signature = signature;
         }
@@ -400,7 +398,7 @@ namespace System.Security.Cryptography.Cose
             {
                 int bufferLength = CoseMessage.ComputeToBeSignedEncodedSize(
                     SigStructureContext.Signature,
-                    _encodedBodyProtectedHeaders.Length,
+                    Message.RawProtectedHeaders.Length,
                     _encodedSignProtectedHeaders.Length,
                     associatedData.Length,
                     contentLength: 0);
@@ -408,7 +406,7 @@ namespace System.Security.Cryptography.Cose
 
                 try
                 {
-                    await CoseMessage.AppendToBeSignedAsync(buffer, hasher, SigStructureContext.Signature, _encodedBodyProtectedHeaders, _encodedSignProtectedHeaders, associatedData, content, cancellationToken).ConfigureAwait(false);
+                    await CoseMessage.AppendToBeSignedAsync(buffer, hasher, SigStructureContext.Signature, Message.RawProtectedHeaders, _encodedSignProtectedHeaders, associatedData, content, cancellationToken).ConfigureAwait(false);
                     return VerifyHash(key, hasher, hashAlgorithm, keyType, padding);
                 }
                 finally
@@ -432,7 +430,7 @@ namespace System.Security.Cryptography.Cose
             {
                 int bufferLength = CoseMessage.ComputeToBeSignedEncodedSize(
                     SigStructureContext.Signature,
-                    _encodedBodyProtectedHeaders.Length,
+                    Message.RawProtectedHeaders.Length,
                     _encodedSignProtectedHeaders.Length,
                     associatedData.Length,
                     contentLength: 0);
@@ -440,7 +438,7 @@ namespace System.Security.Cryptography.Cose
 
                 try
                 {
-                    CoseMessage.AppendToBeSigned(buffer, hasher, SigStructureContext.Signature, _encodedBodyProtectedHeaders, _encodedSignProtectedHeaders, associatedData, contentBytes, contentStream);
+                    CoseMessage.AppendToBeSigned(buffer, hasher, SigStructureContext.Signature, Message.RawProtectedHeaders.Span, _encodedSignProtectedHeaders, associatedData, contentBytes, contentStream);
                     return VerifyHash(key, hasher, hashAlgorithm, keyType, padding);
                 }
                 finally
index c21562b..5af3af3 100644 (file)
@@ -295,6 +295,41 @@ namespace System.Security.Cryptography.Cose.Tests
             Assert.Equal(1, map.Count);
         }
 
+        [Fact]
+        public void SetEncodedValue_CriticalHeaders_ThrowIf_ArrayEmpty()
+        {
+            // definite length
+            var writer = new CborWriter();
+            writer.WriteStartArray(0);
+            writer.WriteEndArray();
+
+            Verify(writer.Encode());
+
+            // indefinite length
+            writer.Reset();
+            writer.WriteStartArray(null);
+            writer.WriteEndArray();
+
+            Verify(writer.Encode());
+
+            void Verify(byte[] encodedValue)
+            {
+                CoseHeaderMap map = new();
+                CoseHeaderValue value = CoseHeaderValue.FromEncodedValue(writer.Encode());
+                Assert.Throws<ArgumentException>(() => map[CoseHeaderLabel.CriticalHeaders] = value);
+            }
+        }
+
+        [Fact]
+        public void SetEncodedValue_CriticalHeaders_ThrowIf_IndefiniteLengthArrayMissingBreak()
+        {
+            byte[] encodedValue = GetDummyCritHeaderValue(useIndefiniteLength: true);
+
+            CoseHeaderMap map = new();
+            CoseHeaderValue value = CoseHeaderValue.FromEncodedValue(encodedValue.AsSpan(0, encodedValue.Length - 1));
+            Assert.Throws<ArgumentException>(() => map[CoseHeaderLabel.CriticalHeaders] = value);
+        }
+
         public enum SetValueMethod
         {
             ItemSet,
@@ -493,7 +528,11 @@ namespace System.Security.Cryptography.Cose.Tests
                 writer.WriteInt32((int)ECDsaAlgorithm.ES256);
                 yield return ReturnDataAndReset(KnownHeaderAlg, writer, setMethod, getMethod);
 
-                WriteDummyCritHeaderValue(writer);
+                WriteDummyCritHeaderValue(writer, useIndefiniteLength: false);
+                yield return ReturnDataAndReset(KnownHeaderCrit, writer, setMethod, getMethod);
+
+
+                WriteDummyCritHeaderValue(writer, useIndefiniteLength: true);
                 yield return ReturnDataAndReset(KnownHeaderCrit, writer, setMethod, getMethod);
 
                 writer.WriteTextString(ContentTypeDummyValue);
index 4ba9403..57fd753 100644 (file)
@@ -1,6 +1,7 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.Formats.Cbor;
 using Test.Cryptography;
@@ -82,5 +83,66 @@ namespace System.Security.Cryptography.Cose.Tests
             writer.WriteEndArray();
             Assert.Throws<CryptographicException>(() => CoseMessage.DecodeMultiSign(writer.Encode()));
         }
+
+        [Theory]
+        // COSE_Sign is an indefinite-length array
+        [InlineData("D8629F40A054546869732069732074686520636F6E74656E742E818343A10126A1044231315840E2AEAFD40D69D19DFE6E52077C5D7FF4E408282CBEFB5D06CBF414AF2E19D982AC45AC98B8544C908B4507DE1E90B717C3D34816FE926A2B98F53AFD2FA0F30AFF")]
+        // [+COSE_Signature]
+        [InlineData("D8628440A054546869732069732074686520636F6E74656E742E9F8343A10126A1044231315840E2AEAFD40D69D19DFE6E52077C5D7FF4E408282CBEFB5D06CBF414AF2E19D982AC45AC98B8544C908B4507DE1E90B717C3D34816FE926A2B98F53AFD2FA0F30AFF")]
+        // COSE_Signature
+        [InlineData("D8628440A054546869732069732074686520636F6E74656E742E819F43A10126A1044231315840E2AEAFD40D69D19DFE6E52077C5D7FF4E408282CBEFB5D06CBF414AF2E19D982AC45AC98B8544C908B4507DE1E90B717C3D34816FE926A2B98F53AFD2FA0F30AFF")]
+        // All of them
+        [InlineData("D8629F40A054546869732069732074686520636F6E74656E742E9F9F43A10126A1044231315840E2AEAFD40D69D19DFE6E52077C5D7FF4E408282CBEFB5D06CBF414AF2E19D982AC45AC98B8544C908B4507DE1E90B717C3D34816FE926A2B98F53AFD2FA0F30AFFFFFF")]
+        public void DecodeMultiSign_IndefiniteLengthArray(string hexCborPayload)
+        {
+            byte[] cborPayload = ByteUtils.HexToByteArray(hexCborPayload);
+            CoseMultiSignMessage msg = CoseMessage.DecodeMultiSign(cborPayload);
+
+            ReadOnlyCollection<CoseSignature> signatures = msg.Signatures;
+            Assert.Equal(1, signatures.Count);
+            Assert.True(signatures[0].VerifyEmbedded(DefaultKey));
+        }
+
+        [Theory]
+        // COSE_Sign
+        [InlineData("D8629F40A054546869732069732074686520636F6E74656E742E818343A10126A1044231315840E2AEAFD40D69D19DFE6E52077C5D7FF4E408282CBEFB5D06CBF414AF2E19D982AC45AC98B8544C908B4507DE1E90B717C3D34816FE926A2B98F53AFD2FA0F30A")]
+        // [+COSE_Signature]
+        [InlineData("D8628440A054546869732069732074686520636F6E74656E742E9F8343A10126A1044231315840E2AEAFD40D69D19DFE6E52077C5D7FF4E408282CBEFB5D06CBF414AF2E19D982AC45AC98B8544C908B4507DE1E90B717C3D34816FE926A2B98F53AFD2FA0F30A")]
+        // COSE_Signature
+        [InlineData("D8628440A054546869732069732074686520636F6E74656E742E819F43A10126A1044231315840E2AEAFD40D69D19DFE6E52077C5D7FF4E408282CBEFB5D06CBF414AF2E19D982AC45AC98B8544C908B4507DE1E90B717C3D34816FE926A2B98F53AFD2FA0F30A")]
+        public void DecodeMultiSign_IndefiniteLengthArray_MissingBreak(string hexCborPayload)
+        {
+            byte[] cborPayload = ByteUtils.HexToByteArray(hexCborPayload);
+            CryptographicException ex = Assert.Throws<CryptographicException>(() => CoseMessage.DecodeMultiSign(cborPayload));
+            Assert.IsType<CborContentException>(ex.InnerException);
+        }
+
+        // All these payloads contain one extra element of type byte string.
+        [Theory]
+        // COSE_Sign
+        [InlineData("D8629F40A054546869732069732074686520636F6E74656E742E818343A10126A1044231315840E2AEAFD40D69D19DFE6E52077C5D7FF4E408282CBEFB5D06CBF414AF2E19D982AC45AC98B8544C908B4507DE1E90B717C3D34816FE926A2B98F53AFD2FA0F30A40FF")]
+        // [+COSE_Signature] - this structure does not have a fixed length required, but the byte string is unexpected.
+        [InlineData("D8628440A054546869732069732074686520636F6E74656E742E9F8343A10126A1044231315840E2AEAFD40D69D19DFE6E52077C5D7FF4E408282CBEFB5D06CBF414AF2E19D982AC45AC98B8544C908B4507DE1E90B717C3D34816FE926A2B98F53AFD2FA0F30A40FF")]
+        // COSE_Signature
+        [InlineData("D8628440A054546869732069732074686520636F6E74656E742E819F43A10126A1044231315840E2AEAFD40D69D19DFE6E52077C5D7FF4E408282CBEFB5D06CBF414AF2E19D982AC45AC98B8544C908B4507DE1E90B717C3D34816FE926A2B98F53AFD2FA0F30A40FF")]
+        public void DecodeMultiSign_IndefiniteLengthArray_LargerByOne(string hexCborPayload)
+        {
+            byte[] cborPayload = ByteUtils.HexToByteArray(hexCborPayload);
+            CryptographicException ex = Assert.Throws<CryptographicException>(() => CoseMessage.DecodeMultiSign(cborPayload));
+        }
+
+        [Theory]
+        // COSE_Sign
+        [InlineData("D8629F40A054546869732069732074686520636F6E74656E742EFF")]
+        // [+COSE_Signature]
+        [InlineData("D8628440A054546869732069732074686520636F6E74656E742E9FFF")]
+        // COSE_Signature
+        [InlineData("D8628440A054546869732069732074686520636F6E74656E742E819F43A10126A104423131FF")]
+        public void DecodeMultiSign_IndefiniteLengthArray_ShorterByOne(string hexCborPayload)
+        {
+            byte[] cborPayload = ByteUtils.HexToByteArray(hexCborPayload);
+            CryptographicException ex = Assert.Throws<CryptographicException>(() => CoseMessage.DecodeMultiSign(cborPayload));
+            Assert.Null(ex.InnerException);
+        }
     }
 }
index 129eec8..8d3fdb2 100644 (file)
@@ -69,5 +69,38 @@ namespace System.Security.Cryptography.Cose.Tests
             writer.WriteEndArray();
             Assert.Throws<CryptographicException>(() => CoseMessage.DecodeSign1(writer.Encode()));
         }
+
+        [Fact]
+        public void DecodeSign1_IndefiniteLengthArray()
+        {
+            byte[] cborPayload = ByteUtils.HexToByteArray("D29F43A10126A10442313154546869732069732074686520636F6E74656E742E58408EB33E4CA31D1C465AB05AAC34CC6B23D58FEF5C083106C4D25A91AEF0B0117E2AF9A291AA32E14AB834DC56ED2A223444547E01F11D3B0916E5A4C345CACB36FF");
+            CoseSign1Message msg = CoseMessage.DecodeSign1(cborPayload);
+
+            Assert.True(msg.VerifyEmbedded(DefaultKey));
+        }
+
+        [Fact]
+        public void DecodeSign1_IndefiniteLengthArray_MissingBreak()
+        {
+            byte[] cborPayload = ByteUtils.HexToByteArray("D29F43A10126A10442313154546869732069732074686520636F6E74656E742E58408EB33E4CA31D1C465AB05AAC34CC6B23D58FEF5C083106C4D25A91AEF0B0117E2AF9A291AA32E14AB834DC56ED2A223444547E01F11D3B0916E5A4C345CACB36");
+            CryptographicException ex = Assert.Throws<CryptographicException>(() => CoseMessage.DecodeSign1(cborPayload));
+            Assert.IsType<CborContentException>(ex.InnerException);
+        }
+
+        [Fact]
+        public void DecodeSign1_IndefiniteLengthArray_LargerByOne()
+        {
+            byte[] cborPayload = ByteUtils.HexToByteArray("D29F43A10126A10442313154546869732069732074686520636F6E74656E742E58408EB33E4CA31D1C465AB05AAC34CC6B23D58FEF5C083106C4D25A91AEF0B0117E2AF9A291AA32E14AB834DC56ED2A223444547E01F11D3B0916E5A4C345CACB3640FF");
+            CryptographicException ex = Assert.Throws<CryptographicException>(() => CoseMessage.DecodeSign1(cborPayload));
+            Assert.Null(ex.InnerException);
+        }
+
+        [Fact]
+        public void DecodeSign1_IndefiniteLengthArray_ShorterByOne()
+        {
+            byte[] cborPayload = ByteUtils.HexToByteArray("D29F43A10126A10442313154546869732069732074686520636F6E74656E742EFF");
+            CryptographicException ex = Assert.Throws<CryptographicException>(() => CoseMessage.DecodeSign1(cborPayload));
+            Assert.Null(ex.InnerException);
+        }
     }
 }
index 8716f3c..4eb5d7d 100644 (file)
@@ -541,17 +541,17 @@ namespace System.Security.Cryptography.Cose.Tests
             MultiSign = 98
         }
 
-        internal static void WriteDummyCritHeaderValue(CborWriter writer)
+        internal static void WriteDummyCritHeaderValue(CborWriter writer, bool useIndefiniteLength = false)
         {
-            writer.WriteStartArray(1);
+            writer.WriteStartArray(useIndefiniteLength ? null : 1);
             writer.WriteInt32(42);
             writer.WriteEndArray();
         }
 
-        internal static byte[] GetDummyCritHeaderValue()
+        internal static byte[] GetDummyCritHeaderValue(bool useIndefiniteLength = false)
         {
             var writer = new CborWriter();
-            WriteDummyCritHeaderValue(writer);
+            WriteDummyCritHeaderValue(writer, useIndefiniteLength);
             return writer.Encode();
         }