From 041b71c1e7fa1f82fc505d67577d87c6d59d8754 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Jul 2026 14:29:37 +0000 Subject: [PATCH 1/4] Initial plan From fd27f32d83915d3be36939a9b5ae04515b9589a3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Jul 2026 14:54:55 +0000 Subject: [PATCH 2/4] Validate COSE algorithm headers in async verification Co-authored-by: vcsjones <361677+vcsjones@users.noreply.github.com> --- .../Cryptography/Cose/CoseSign1Message.cs | 24 +++++++++---------- .../Cryptography/Cose/CoseSignature.cs | 13 ++++++++++ ...CoseMultiSignMessageTests.Verify.Stream.cs | 20 ++++++++++++++++ .../CoseSign1MessageTests.Verify.Stream.cs | 20 ++++++++++++++++ 4 files changed, 65 insertions(+), 12 deletions(-) diff --git a/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseSign1Message.cs b/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseSign1Message.cs index da3b1e0c4df954..9c08628c8e4fda 100644 --- a/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseSign1Message.cs +++ b/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseSign1Message.cs @@ -771,18 +771,7 @@ public bool VerifyDetached(CoseKey key, Stream detachedContent, ReadOnlySpan contentBytes, Stream? contentStream, ReadOnlySpan associatedData) { Debug.Assert(contentStream == null || contentBytes.Length == 0); - ReadOnlyMemory encodedAlg = CoseHelpers.GetCoseAlgorithmFromProtectedHeaders(ProtectedHeaders); - - CoseAlgorithm? nullableAlg = CoseHelpers.DecodeCoseAlgorithmHeader(encodedAlg); - if (nullableAlg == null) - { - throw new CryptographicException(SR.Sign1VerifyAlgHeaderWasIncorrect); - } - - if (nullableAlg.Value != key.Algorithm) - { - throw new CryptographicException(SR.Format(SR.Sign1UnknownCoseAlgorithm, nullableAlg)); - } + ValidateAlgorithm(key); using (ToBeSignedBuilder toBeSignedBuilder = key.CreateToBeSignedBuilder()) { @@ -932,6 +921,7 @@ public Task VerifyDetachedAsync(CoseKey key, Stream detachedContent, ReadO throw new InvalidOperationException(SR.ContentWasEmbedded); } + ValidateAlgorithm(key); return VerifyAsyncCore(key, detachedContent, associatedData, cancellationToken); } @@ -1048,5 +1038,15 @@ private CoseAlgorithm GetCoseAlgorithmFromProtectedHeaders() return nullableAlg.Value; } + + private void ValidateAlgorithm(CoseKey key) + { + CoseAlgorithm algorithm = GetCoseAlgorithmFromProtectedHeaders(); + + if (algorithm != key.Algorithm) + { + throw new CryptographicException(SR.Format(SR.Sign1UnknownCoseAlgorithm, algorithm)); + } + } } } diff --git a/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseSignature.cs b/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseSignature.cs index a6c9d0e88268d8..370d9f11f13f3a 100644 --- a/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseSignature.cs +++ b/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseSignature.cs @@ -608,6 +608,7 @@ public Task VerifyDetachedAsync(CoseKey key, Stream detachedContent, ReadO throw new InvalidOperationException(SR.ContentWasEmbedded); } + ValidateAlgorithm(key); return VerifyAsyncCore(key, detachedContent, associatedData, cancellationToken); } @@ -644,6 +645,8 @@ private async Task VerifyAsyncCore(CoseKey key, Stream content, ReadOnlyMe private bool VerifyCore(CoseKey key, ReadOnlySpan contentBytes, Stream? contentStream, ReadOnlySpan associatedData) { + ValidateAlgorithm(key); + using (ToBeSignedBuilder toBeSignedBuilder = key.CreateToBeSignedBuilder()) { int bufferLength = CoseMessage.ComputeToBeSignedEncodedSize( @@ -685,5 +688,15 @@ private CoseAlgorithm GetCoseAlgorithmFromProtectedHeaders() return nullableAlg.Value; } + + private void ValidateAlgorithm(CoseKey key) + { + CoseAlgorithm algorithm = GetCoseAlgorithmFromProtectedHeaders(); + + if (algorithm != key.Algorithm) + { + throw new CryptographicException(SR.Format(SR.Sign1UnknownCoseAlgorithm, algorithm)); + } + } } } diff --git a/src/libraries/System.Security.Cryptography.Cose/tests/CoseMultiSignMessageTests.Verify.Stream.cs b/src/libraries/System.Security.Cryptography.Cose/tests/CoseMultiSignMessageTests.Verify.Stream.cs index 35a008b409bf31..e659346feccdaf 100644 --- a/src/libraries/System.Security.Cryptography.Cose/tests/CoseMultiSignMessageTests.Verify.Stream.cs +++ b/src/libraries/System.Security.Cryptography.Cose/tests/CoseMultiSignMessageTests.Verify.Stream.cs @@ -4,6 +4,7 @@ using System.Collections.ObjectModel; using System.IO; using System.Threading.Tasks; +using Test.Cryptography; using Xunit; using static System.Security.Cryptography.Cose.Tests.CoseTestHelpers; @@ -63,6 +64,25 @@ public async Task VerifyAsyncWithUnreadableStream() using Stream unseekableStream = GetTestStream(s_sampleContent, StreamKind.Unreadable); await Assert.ThrowsAsync("detachedContent", () => msg.Signatures[0].VerifyDetachedAsync(DefaultKey, unseekableStream)); } + + [Theory] + [InlineData("44A1013822")] + [InlineData("45A1182A182A")] + public async Task VerifyWithCoseKeyThrowsForInvalidAlgorithmHeader(string replacementHeaders) + { + CoseSigner signer = GetCoseSigner(DefaultKey, DefaultHash); + signer.ProtectedHeaders.Add(new CoseHeaderLabel(42), 42); + string encodedMessage = CoseMultiSignMessage.SignDetached(s_sampleContent, signer).ByteArrayToHex(); + encodedMessage = ReplaceFirst(encodedMessage, "47A20126182A182A", replacementHeaders); + + CoseMultiSignMessage message = CoseMessage.DecodeMultiSign(ByteUtils.HexToByteArray(encodedMessage)); + CoseKey key = new CoseKey(DefaultKey, DefaultHash); + using Stream syncStream = GetTestStream(s_sampleContent); + using Stream asyncStream = GetTestStream(s_sampleContent); + + Assert.Throws(() => message.Signatures[0].VerifyDetached(key, syncStream)); + await Assert.ThrowsAsync(() => message.Signatures[0].VerifyDetachedAsync(key, asyncStream)); + } } public class CoseMultiSignMessageTests_VerifyStream_Sync : CoseMultiSignMessageTests_VerifyStream diff --git a/src/libraries/System.Security.Cryptography.Cose/tests/CoseSign1MessageTests.Verify.Stream.cs b/src/libraries/System.Security.Cryptography.Cose/tests/CoseSign1MessageTests.Verify.Stream.cs index 09a10710db416e..2e8850eecc7ee2 100644 --- a/src/libraries/System.Security.Cryptography.Cose/tests/CoseSign1MessageTests.Verify.Stream.cs +++ b/src/libraries/System.Security.Cryptography.Cose/tests/CoseSign1MessageTests.Verify.Stream.cs @@ -3,6 +3,7 @@ using System.IO; using System.Threading.Tasks; +using Test.Cryptography; using Xunit; using static System.Security.Cryptography.Cose.Tests.CoseTestHelpers; @@ -59,6 +60,25 @@ public async Task VerifyAsyncWithUnreadableStream() using Stream unseekableStream = GetTestStream(s_sampleContent, StreamKind.Unreadable); await Assert.ThrowsAsync("detachedContent", () => msg.VerifyDetachedAsync(DefaultKey, unseekableStream)); } + + [Theory] + [InlineData("44A1013822")] + [InlineData("45A1182A182A")] + public async Task VerifyWithCoseKeyThrowsForInvalidAlgorithmHeader(string replacementHeaders) + { + CoseSigner signer = GetCoseSigner(DefaultKey, DefaultHash); + signer.ProtectedHeaders.Add(new CoseHeaderLabel(42), 42); + string encodedMessage = CoseSign1Message.SignDetached(s_sampleContent, signer).ByteArrayToHex(); + encodedMessage = ReplaceFirst(encodedMessage, "47A20126182A182A", replacementHeaders); + + CoseSign1Message message = CoseMessage.DecodeSign1(ByteUtils.HexToByteArray(encodedMessage)); + CoseKey key = new CoseKey(DefaultKey, DefaultHash); + using Stream syncStream = GetTestStream(s_sampleContent); + using Stream asyncStream = GetTestStream(s_sampleContent); + + Assert.Throws(() => message.VerifyDetached(key, syncStream)); + await Assert.ThrowsAsync(() => message.VerifyDetachedAsync(key, asyncStream)); + } } public class CoseSign1MessageTests_VerifyStream_Sync : CoseSign1MessageTests_VerifyStream From 315e5d4b25390686d192ae2c9c98a6343fca0508 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Fri, 17 Jul 2026 16:07:04 -0400 Subject: [PATCH 3/4] Fix exception messages --- .../src/Resources/Strings.resx | 3 ++ .../Cryptography/Cose/CoseSign1Message.cs | 2 +- .../Cryptography/Cose/CoseSignature.cs | 2 +- ...CoseMultiSignMessageTests.Verify.Stream.cs | 38 +++++++++++++++---- .../CoseSign1MessageTests.Verify.Stream.cs | 32 +++++++++++++--- 5 files changed, 63 insertions(+), 14 deletions(-) diff --git a/src/libraries/System.Security.Cryptography.Cose/src/Resources/Strings.resx b/src/libraries/System.Security.Cryptography.Cose/src/Resources/Strings.resx index 0691df3b1e48b5..806b24f6b1425b 100644 --- a/src/libraries/System.Security.Cryptography.Cose/src/Resources/Strings.resx +++ b/src/libraries/System.Security.Cryptography.Cose/src/Resources/Strings.resx @@ -234,6 +234,9 @@ Algorithm header CBOR type was incorrect, expected int or tstr. + + COSE algorithm '{0}' does not match the algorithm '{1}' of the specified key. + Algorithm (alg) header is required and it must be a protected header. diff --git a/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseSign1Message.cs b/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseSign1Message.cs index 9c08628c8e4fda..3cc43dfd2e3af8 100644 --- a/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseSign1Message.cs +++ b/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseSign1Message.cs @@ -1045,7 +1045,7 @@ private void ValidateAlgorithm(CoseKey key) if (algorithm != key.Algorithm) { - throw new CryptographicException(SR.Format(SR.Sign1UnknownCoseAlgorithm, algorithm)); + throw new CryptographicException(SR.Format(SR.Sign1VerifyAlgDoesNotMatchKeyAlgorithm, algorithm, key.Algorithm)); } } } diff --git a/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseSignature.cs b/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseSignature.cs index 370d9f11f13f3a..2b65f4b7d1a1d0 100644 --- a/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseSignature.cs +++ b/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseSignature.cs @@ -695,7 +695,7 @@ private void ValidateAlgorithm(CoseKey key) if (algorithm != key.Algorithm) { - throw new CryptographicException(SR.Format(SR.Sign1UnknownCoseAlgorithm, algorithm)); + throw new CryptographicException(SR.Format(SR.Sign1VerifyAlgDoesNotMatchKeyAlgorithm, algorithm, key.Algorithm)); } } } diff --git a/src/libraries/System.Security.Cryptography.Cose/tests/CoseMultiSignMessageTests.Verify.Stream.cs b/src/libraries/System.Security.Cryptography.Cose/tests/CoseMultiSignMessageTests.Verify.Stream.cs index e659346feccdaf..50d41a0568217d 100644 --- a/src/libraries/System.Security.Cryptography.Cose/tests/CoseMultiSignMessageTests.Verify.Stream.cs +++ b/src/libraries/System.Security.Cryptography.Cose/tests/CoseMultiSignMessageTests.Verify.Stream.cs @@ -65,23 +65,47 @@ public async Task VerifyAsyncWithUnreadableStream() await Assert.ThrowsAsync("detachedContent", () => msg.Signatures[0].VerifyDetachedAsync(DefaultKey, unseekableStream)); } - [Theory] - [InlineData("44A1013822")] - [InlineData("45A1182A182A")] - public async Task VerifyWithCoseKeyThrowsForInvalidAlgorithmHeader(string replacementHeaders) + [Fact] + public async Task VerifyWithCoseKeyThrowsForMismatchedAlgorithmHeader() + { + CoseSigner signer = GetCoseSigner(DefaultKey, DefaultHash); + signer.ProtectedHeaders.Add(new CoseHeaderLabel(42), 42); + string encodedMessage = CoseMultiSignMessage.SignDetached(s_sampleContent, signer).ByteArrayToHex(); + encodedMessage = ReplaceFirst(encodedMessage, "47A20126182A182A", "44A1013822"); + + CoseMultiSignMessage message = CoseMessage.DecodeMultiSign(ByteUtils.HexToByteArray(encodedMessage)); + CoseSignature signature = Assert.Single(message.Signatures); + CoseKey key = new CoseKey(DefaultKey, DefaultHash); + using Stream syncStream = GetTestStream(s_sampleContent); + using Stream asyncStream = GetTestStream(s_sampleContent); + + CryptographicException syncException = Assert.Throws( + () => signature.VerifyDetached(key, syncStream)); + CryptographicException asyncException = await Assert.ThrowsAsync( + () => signature.VerifyDetachedAsync(key, asyncStream)); + + Assert.Contains(nameof(CoseAlgorithm.ES384), syncException.Message); + Assert.Contains(nameof(CoseAlgorithm.ES256), syncException.Message); + Assert.Contains(nameof(CoseAlgorithm.ES384), asyncException.Message); + Assert.Contains(nameof(CoseAlgorithm.ES256), asyncException.Message); + } + + [Fact] + public async Task VerifyWithCoseKeyThrowsForMissingAlgorithmHeader() { CoseSigner signer = GetCoseSigner(DefaultKey, DefaultHash); signer.ProtectedHeaders.Add(new CoseHeaderLabel(42), 42); string encodedMessage = CoseMultiSignMessage.SignDetached(s_sampleContent, signer).ByteArrayToHex(); - encodedMessage = ReplaceFirst(encodedMessage, "47A20126182A182A", replacementHeaders); + encodedMessage = ReplaceFirst(encodedMessage, "47A20126182A182A", "45A1182A182A"); CoseMultiSignMessage message = CoseMessage.DecodeMultiSign(ByteUtils.HexToByteArray(encodedMessage)); + CoseSignature signature = Assert.Single(message.Signatures); CoseKey key = new CoseKey(DefaultKey, DefaultHash); using Stream syncStream = GetTestStream(s_sampleContent); using Stream asyncStream = GetTestStream(s_sampleContent); - Assert.Throws(() => message.Signatures[0].VerifyDetached(key, syncStream)); - await Assert.ThrowsAsync(() => message.Signatures[0].VerifyDetachedAsync(key, asyncStream)); + Assert.Throws(() => signature.VerifyDetached(key, syncStream)); + await Assert.ThrowsAsync(() => signature.VerifyDetachedAsync(key, asyncStream)); } } diff --git a/src/libraries/System.Security.Cryptography.Cose/tests/CoseSign1MessageTests.Verify.Stream.cs b/src/libraries/System.Security.Cryptography.Cose/tests/CoseSign1MessageTests.Verify.Stream.cs index 2e8850eecc7ee2..68649b5ce208ea 100644 --- a/src/libraries/System.Security.Cryptography.Cose/tests/CoseSign1MessageTests.Verify.Stream.cs +++ b/src/libraries/System.Security.Cryptography.Cose/tests/CoseSign1MessageTests.Verify.Stream.cs @@ -61,15 +61,37 @@ public async Task VerifyAsyncWithUnreadableStream() await Assert.ThrowsAsync("detachedContent", () => msg.VerifyDetachedAsync(DefaultKey, unseekableStream)); } - [Theory] - [InlineData("44A1013822")] - [InlineData("45A1182A182A")] - public async Task VerifyWithCoseKeyThrowsForInvalidAlgorithmHeader(string replacementHeaders) + [Fact] + public async Task VerifyWithCoseKeyThrowsForMismatchedAlgorithmHeader() + { + CoseSigner signer = GetCoseSigner(DefaultKey, DefaultHash); + signer.ProtectedHeaders.Add(new CoseHeaderLabel(42), 42); + string encodedMessage = CoseSign1Message.SignDetached(s_sampleContent, signer).ByteArrayToHex(); + encodedMessage = ReplaceFirst(encodedMessage, "47A20126182A182A", "44A1013822"); + + CoseSign1Message message = CoseMessage.DecodeSign1(ByteUtils.HexToByteArray(encodedMessage)); + CoseKey key = new CoseKey(DefaultKey, DefaultHash); + using Stream syncStream = GetTestStream(s_sampleContent); + using Stream asyncStream = GetTestStream(s_sampleContent); + + CryptographicException syncException = Assert.Throws( + () => message.VerifyDetached(key, syncStream)); + CryptographicException asyncException = await Assert.ThrowsAsync( + () => message.VerifyDetachedAsync(key, asyncStream)); + + Assert.Contains(nameof(CoseAlgorithm.ES384), syncException.Message); + Assert.Contains(nameof(CoseAlgorithm.ES256), syncException.Message); + Assert.Contains(nameof(CoseAlgorithm.ES384), asyncException.Message); + Assert.Contains(nameof(CoseAlgorithm.ES256), asyncException.Message); + } + + [Fact] + public async Task VerifyWithCoseKeyThrowsForMissingAlgorithmHeader() { CoseSigner signer = GetCoseSigner(DefaultKey, DefaultHash); signer.ProtectedHeaders.Add(new CoseHeaderLabel(42), 42); string encodedMessage = CoseSign1Message.SignDetached(s_sampleContent, signer).ByteArrayToHex(); - encodedMessage = ReplaceFirst(encodedMessage, "47A20126182A182A", replacementHeaders); + encodedMessage = ReplaceFirst(encodedMessage, "47A20126182A182A", "45A1182A182A"); CoseSign1Message message = CoseMessage.DecodeSign1(ByteUtils.HexToByteArray(encodedMessage)); CoseKey key = new CoseKey(DefaultKey, DefaultHash); From 2f56a462afc3fbb4c924db02bd634510903afa9f Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Fri, 17 Jul 2026 16:32:21 -0400 Subject: [PATCH 4/4] Fix alpha sort Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../src/Resources/Strings.resx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Security.Cryptography.Cose/src/Resources/Strings.resx b/src/libraries/System.Security.Cryptography.Cose/src/Resources/Strings.resx index 806b24f6b1425b..c175c410cb8505 100644 --- a/src/libraries/System.Security.Cryptography.Cose/src/Resources/Strings.resx +++ b/src/libraries/System.Security.Cryptography.Cose/src/Resources/Strings.resx @@ -231,12 +231,12 @@ Unsupported key '{0}'. - - Algorithm header CBOR type was incorrect, expected int or tstr. - COSE algorithm '{0}' does not match the algorithm '{1}' of the specified key. + + Algorithm header CBOR type was incorrect, expected int or tstr. + Algorithm (alg) header is required and it must be a protected header.