diff --git a/.gitignore b/.gitignore
index 311f5bfb..74c870b1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -363,3 +363,6 @@ MigrationBackup/
# Fody - auto-generated XML schema
FodyWeavers.xsd
+
+# Visual Studio live unit testing configuration files.
+*.lutconfig
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d6743918..6f61453e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,12 +1,20 @@
# Changelog
-## [v1.1.6-pre1](https://github.com/microsoft/CoseSignTool/tree/v1.1.6-pre1) (2024-02-07)
+## [v1.1.7-pre1](https://github.com/microsoft/CoseSignTool/tree/v1.1.7-pre1) (2024-02-14)
+
+[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v1.1.7...v1.1.7-pre1)
-[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v1.1.7...v1.1.6-pre1)
+**Merged pull requests:**
+
+- Command Line Validation of Indirect Signatures [\#78](https://github.com/microsoft/CoseSignTool/pull/78) ([elantiguamsft](https://github.com/elantiguamsft))
## [v1.1.7](https://github.com/microsoft/CoseSignTool/tree/v1.1.7) (2024-02-07)
-[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v1.1.6...v1.1.7)
+[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v1.1.6-pre1...v1.1.7)
+
+## [v1.1.6-pre1](https://github.com/microsoft/CoseSignTool/tree/v1.1.6-pre1) (2024-02-07)
+
+[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v1.1.6...v1.1.6-pre1)
**Merged pull requests:**
@@ -26,19 +34,19 @@
## [v1.1.4-pre1](https://github.com/microsoft/CoseSignTool/tree/v1.1.4-pre1) (2024-01-31)
-[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v1.1.4...v1.1.4-pre1)
+[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v1.1.3-pre1...v1.1.4-pre1)
**Merged pull requests:**
- write validation output to standard out [\#74](https://github.com/microsoft/CoseSignTool/pull/74) ([elantiguamsft](https://github.com/elantiguamsft))
-## [v1.1.4](https://github.com/microsoft/CoseSignTool/tree/v1.1.4) (2024-01-26)
+## [v1.1.3-pre1](https://github.com/microsoft/CoseSignTool/tree/v1.1.3-pre1) (2024-01-26)
-[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v1.1.3-pre1...v1.1.4)
+[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v1.1.4...v1.1.3-pre1)
-## [v1.1.3-pre1](https://github.com/microsoft/CoseSignTool/tree/v1.1.3-pre1) (2024-01-26)
+## [v1.1.4](https://github.com/microsoft/CoseSignTool/tree/v1.1.4) (2024-01-26)
-[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v1.1.3...v1.1.3-pre1)
+[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v1.1.3...v1.1.4)
**Merged pull requests:**
@@ -70,19 +78,19 @@
## [v1.1.1-pre1](https://github.com/microsoft/CoseSignTool/tree/v1.1.1-pre1) (2024-01-17)
-[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v1.1.0-pre7...v1.1.1-pre1)
+[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v1.1.1...v1.1.1-pre1)
**Merged pull requests:**
- Move CreateChangelog to after build in PR build [\#70](https://github.com/microsoft/CoseSignTool/pull/70) ([lemccomb](https://github.com/lemccomb))
-## [v1.1.0-pre7](https://github.com/microsoft/CoseSignTool/tree/v1.1.0-pre7) (2024-01-12)
+## [v1.1.1](https://github.com/microsoft/CoseSignTool/tree/v1.1.1) (2024-01-12)
-[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v1.1.1...v1.1.0-pre7)
+[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v1.1.0-pre7...v1.1.1)
-## [v1.1.1](https://github.com/microsoft/CoseSignTool/tree/v1.1.1) (2024-01-12)
+## [v1.1.0-pre7](https://github.com/microsoft/CoseSignTool/tree/v1.1.0-pre7) (2024-01-12)
-[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v1.1.0-pre6...v1.1.1)
+[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v1.1.0-pre6...v1.1.0-pre7)
**Closed issues:**
@@ -139,7 +147,7 @@
## [v1.1.0-pre1](https://github.com/microsoft/CoseSignTool/tree/v1.1.0-pre1) (2023-11-03)
-[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v1.1.0...v1.1.0-pre1)
+[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v0.3.1-pre.10...v1.1.0-pre1)
**Merged pull requests:**
@@ -149,13 +157,13 @@
- DetachedSignatureFactory accepts pre-hashed content as payload [\#53](https://github.com/microsoft/CoseSignTool/pull/53) ([elantiguamsft](https://github.com/elantiguamsft))
- Add password support for certificate files [\#52](https://github.com/microsoft/CoseSignTool/pull/52) ([lemccomb](https://github.com/lemccomb))
-## [v1.1.0](https://github.com/microsoft/CoseSignTool/tree/v1.1.0) (2023-10-10)
+## [v0.3.1-pre.10](https://github.com/microsoft/CoseSignTool/tree/v0.3.1-pre.10) (2023-10-10)
-[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v0.3.1-pre.10...v1.1.0)
+[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v1.1.0...v0.3.1-pre.10)
-## [v0.3.1-pre.10](https://github.com/microsoft/CoseSignTool/tree/v0.3.1-pre.10) (2023-10-10)
+## [v1.1.0](https://github.com/microsoft/CoseSignTool/tree/v1.1.0) (2023-10-10)
-[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v0.3.2...v0.3.1-pre.10)
+[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v0.3.2...v1.1.0)
**Merged pull requests:**
diff --git a/CoseDetachedSIgnature.Tests/CoseSign1MessageDetachedSignatureExtensionsTests.cs b/CoseDetachedSIgnature.Tests/CoseSign1MessageDetachedSignatureExtensionsTests.cs
deleted file mode 100644
index df4197c6..00000000
--- a/CoseDetachedSIgnature.Tests/CoseSign1MessageDetachedSignatureExtensionsTests.cs
+++ /dev/null
@@ -1,249 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-namespace CoseDetachedSignature.Tests;
-
-using System.IO;
-using CoseDetachedSignature;
-using CoseDetachedSignature.Extensions;
-
-///
-/// Class for Testing Methods of
-///
-public class CoseSign1MessageDetachedSignatureExtensionsTests
-{
- [SetUp]
- public void Setup()
- {
- }
-
- [Test]
- public void TestTryGetDetachedSignatureAlgorithmSuccess()
- {
- ICoseSigningKeyProvider coseSigningKeyProvider = SetupMockSigningKeyProvider(nameof(TestTryGetDetachedSignatureAlgorithmSuccess));
- DetachedSignatureFactory factory = new();
- byte[] randomBytes = new byte[50];
- new Random().NextBytes(randomBytes);
-
- CoseSign1Message detachedSignature = factory.CreateDetachedSignature(randomBytes, coseSigningKeyProvider, "application/test.payload");
- detachedSignature.TryGetDetachedSignatureAlgorithm(out HashAlgorithmName hashAlgorithmName).Should().BeTrue();
- hashAlgorithmName.Should().Be(HashAlgorithmName.SHA256);
- }
-
- [Test]
- public void TestTryGetDetachedSignatureAlgorithmFailure()
- {
- ICoseSigningKeyProvider coseSigningKeyProvider = SetupMockSigningKeyProvider(nameof(TestTryGetDetachedSignatureAlgorithmFailure));
-
- // no content type
- Mock removeContentTypeHeaderExtender = new Mock(MockBehavior.Strict);
- removeContentTypeHeaderExtender.Setup(m => m.ExtendProtectedHeaders(It.IsAny())).Returns((input) =>
- {
- // remove ContentType
- if (input.ContainsKey(CoseHeaderLabel.ContentType))
- {
- input.Remove(CoseHeaderLabel.ContentType);
- }
- return input;
- });
- removeContentTypeHeaderExtender.Setup(m => m.ExtendUnProtectedHeaders(It.IsAny())).Returns((input) => input);
-
- // empty content type
- Mock emptyContentTypeHeaderExtender = new Mock(MockBehavior.Strict);
- emptyContentTypeHeaderExtender.Setup(m => m.ExtendProtectedHeaders(It.IsAny())).Returns((input) =>
- {
- // remove content type
- input = removeContentTypeHeaderExtender.Object.ExtendProtectedHeaders(input);
-
- // add content type with an empty string value
- input.Add(CoseHeaderLabel.ContentType, string.Empty);
-
- return input;
- });
- emptyContentTypeHeaderExtender.Setup(m => m.ExtendUnProtectedHeaders(It.IsAny())).Returns((input) => input);
-
- DetachedSignatureFactory factory = new();
- byte[] randomBytes = new byte[50];
- new Random().NextBytes(randomBytes);
-
- // call TryGetDetachedSignatureAlgoithm on a null message object should fail
- CoseSign1Message? objectUnderTest = null;
- objectUnderTest.TryGetDetachedSignatureAlgorithm(out HashAlgorithmName hashAlgorithmName).Should().BeFalse();
-
- // call TryGetDetachedSignatureAlgorithm on a CoseSign1Message with no ContentType header should fail
- objectUnderTest = factory.MessageFactory.CreateCoseSign1Message(randomBytes, coseSigningKeyProvider, embedPayload: true, @"application\test.output", removeContentTypeHeaderExtender.Object);
- objectUnderTest.TryGetDetachedSignatureAlgorithm(out hashAlgorithmName).Should().BeFalse("missing content type should fail the test");
-
- // call TryGetDetachedSignatureAlgorithm on a CoseSign1Message with empty ContentType should fail
- objectUnderTest = factory.MessageFactory.CreateCoseSign1Message(randomBytes, coseSigningKeyProvider, embedPayload: true, @"application\test.output", emptyContentTypeHeaderExtender.Object);
- objectUnderTest.TryGetDetachedSignatureAlgorithm(out hashAlgorithmName).Should().BeFalse("empty content type should fail the test");
-
- // call TryGetDetachedSignatureAlgorithm on a CoseSign1Message with invalid mime type hash extension ContentType should fail
- objectUnderTest = factory.MessageFactory.CreateCoseSign1Message(randomBytes, coseSigningKeyProvider, embedPayload: true, @"application\test.output");
- objectUnderTest.TryGetDetachedSignatureAlgorithm(out hashAlgorithmName).Should().BeFalse("missing mime type hash extension in content type should fail the test");
-
- // call TryGetDetachedSignatureAlgorithm on a CoseSign1Message with invalid mime type hash extension ContentType should succeed
- objectUnderTest = factory.MessageFactory.CreateCoseSign1Message(randomBytes, coseSigningKeyProvider, embedPayload: true, @"application\test.output+hash-notavalidvalue");
- objectUnderTest.TryGetDetachedSignatureAlgorithm(out hashAlgorithmName).Should().BeTrue("invalid mime type hash extension in content type should not fail this call");
- }
-
- [Test]
- public void TestIsDetachedSignatureSuccess()
- {
- ICoseSigningKeyProvider coseSigningKeyProvider = SetupMockSigningKeyProvider(nameof(TestTryGetDetachedSignatureAlgorithmSuccess));
- DetachedSignatureFactory factory = new();
- byte[] randomBytes = new byte[50];
- new Random().NextBytes(randomBytes);
-
- CoseSign1Message detachedSignature = factory.CreateDetachedSignature(randomBytes, coseSigningKeyProvider, "application/test.payload");
- detachedSignature.IsDetachedSignature().Should().BeTrue();
- }
-
- [Test]
- public void TestIsDetachedSignatureFailure()
- {
- ICoseSigningKeyProvider coseSigningKeyProvider = SetupMockSigningKeyProvider(nameof(TestTryGetDetachedSignatureAlgorithmSuccess));
- DetachedSignatureFactory factory = new();
- byte[] randomBytes = new byte[50];
- new Random().NextBytes(randomBytes);
-
- CoseSign1Message detachedSignature = factory.MessageFactory.CreateCoseSign1Message(randomBytes, coseSigningKeyProvider, embedPayload: true, "application/test.payload");
- detachedSignature.IsDetachedSignature().Should().BeFalse();
- }
-
- [Test]
- public void TestSignatureMatchesStreamSuccess()
- {
- ICoseSigningKeyProvider coseSigningKeyProvider = SetupMockSigningKeyProvider(nameof(TestTryGetDetachedSignatureAlgorithmSuccess));
- DetachedSignatureFactory factory = new();
- byte[] randomBytes = new byte[50];
- new Random().NextBytes(randomBytes);
- using MemoryStream stream = new(randomBytes);
-
- CoseSign1Message detachedSignature = factory.CreateDetachedSignature(randomBytes, coseSigningKeyProvider, "application/test.payload");
- detachedSignature.SignatureMatches(stream).Should().BeTrue();
- }
-
- [Test]
- public void TestSignatureMatchesStreamFailure()
- {
- ICoseSigningKeyProvider coseSigningKeyProvider = SetupMockSigningKeyProvider(nameof(TestTryGetDetachedSignatureAlgorithmSuccess));
- DetachedSignatureFactory factory = new();
- byte[] randomBytes = new byte[50];
- byte[] randomBytes2 = new byte[50];
- new Random().NextBytes(randomBytes);
- new Random().NextBytes(randomBytes2);
- using MemoryStream stream = new(randomBytes2);
-
- // test mismatched signature
- CoseSign1Message? detachedSignature = factory.CreateDetachedSignature(randomBytes, coseSigningKeyProvider, "application/test.payload");
- detachedSignature.SignatureMatches(stream).Should().BeFalse();
- stream.Dispose();
- using MemoryStream stream2 = new(randomBytes);
-
- // test invalid hash extension case
- detachedSignature = factory.MessageFactory.CreateCoseSign1Message(randomBytes, coseSigningKeyProvider, embedPayload: true, "application/test.payload");
- detachedSignature.SignatureMatches(stream2).Should().BeFalse();
- stream2.Seek(stream2.Length, SeekOrigin.Begin);
-
- // test null object case
- detachedSignature = null;
- CoseSign1MessageDetachedSignatureExtensions.SignatureMatches(detachedSignature, stream2).Should().BeFalse();
- }
-
- [Test]
- public void TestSignatureMatchesBytesSuccess()
- {
- ICoseSigningKeyProvider coseSigningKeyProvider = SetupMockSigningKeyProvider(nameof(TestTryGetDetachedSignatureAlgorithmSuccess));
- DetachedSignatureFactory factory = new();
- byte[] randomBytes = new byte[50];
- new Random().NextBytes(randomBytes);
-
- CoseSign1Message detachedSignature = factory.CreateDetachedSignature(randomBytes, coseSigningKeyProvider, "application/test.payload");
- detachedSignature.SignatureMatches(randomBytes).Should().BeTrue();
- }
-
- [Test]
- public void TestSignatureMatchesBytesFailure()
- {
- ICoseSigningKeyProvider coseSigningKeyProvider = SetupMockSigningKeyProvider(nameof(TestTryGetDetachedSignatureAlgorithmSuccess));
- DetachedSignatureFactory factory = new();
- byte[] randomBytes = new byte[50];
- byte[] randomBytes2 = new byte[50];
- new Random().NextBytes(randomBytes);
- new Random().NextBytes(randomBytes2);
-
- // test mismatched signature
- CoseSign1Message? detachedSignature = factory.CreateDetachedSignature(randomBytes, coseSigningKeyProvider, "application/test.payload");
- detachedSignature.SignatureMatches(randomBytes2).Should().BeFalse();
-
- // test invalid hash extension case
- detachedSignature = factory.MessageFactory.CreateCoseSign1Message(randomBytes, coseSigningKeyProvider, embedPayload: true, "application/test.payload");
- detachedSignature.SignatureMatches(randomBytes).Should().BeFalse();
-
- // test null object case
- detachedSignature = null;
- CoseSign1MessageDetachedSignatureExtensions.SignatureMatches(detachedSignature, randomBytes).Should().BeFalse();
- }
-
- [Test]
- public void TestTryGetHashAlgorithmSuccess()
- {
- ICoseSigningKeyProvider coseSigningKeyProvider = SetupMockSigningKeyProvider(nameof(TestTryGetDetachedSignatureAlgorithmSuccess));
- DetachedSignatureFactory factory = new();
- byte[] randomBytes = new byte[50];
- new Random().NextBytes(randomBytes);
-
- CoseSign1Message detachedSignature = factory.CreateDetachedSignature(randomBytes, coseSigningKeyProvider, "application/test.payload");
- detachedSignature.TryGetHashAlgorithm(out HashAlgorithm? hashAlgorithm).Should().BeTrue();
- hashAlgorithm.Should().NotBeNull();
- hashAlgorithm.Should().BeAssignableTo();
- }
-
- [Test]
- public void TestTryGetHashAlgorithmFailure()
- {
- ICoseSigningKeyProvider coseSigningKeyProvider = SetupMockSigningKeyProvider(nameof(TestTryGetDetachedSignatureAlgorithmSuccess));
- DetachedSignatureFactory factory = new();
- byte[] randomBytes = new byte[50];
- byte[] randomBytes2 = new byte[50];
- new Random().NextBytes(randomBytes);
- new Random().NextBytes(randomBytes2);
-
- // Fail to extract a hash algorithm name
- CoseSign1Message? detachedSignature = factory.MessageFactory.CreateCoseSign1Message(randomBytes, coseSigningKeyProvider, embedPayload: true, "application/test.payload");
- detachedSignature.TryGetHashAlgorithm(out HashAlgorithm? hashAlgorithm).Should().BeFalse();
- hashAlgorithm.Should().BeNull();
-
- // COSE Sign1 Detached signature case with other things being valid
- // content should be null in this case.
- detachedSignature = factory.MessageFactory.CreateCoseSign1Message(randomBytes, coseSigningKeyProvider, embedPayload: false, "application/test.payload+hash-sha256");
- detachedSignature.TryGetHashAlgorithm(out hashAlgorithm).Should().BeFalse();
- hashAlgorithm.Should().BeNull();
-
- // Invalid hash definition case
- detachedSignature = factory.MessageFactory.CreateCoseSign1Message(randomBytes, coseSigningKeyProvider, embedPayload: true, "application/test.payload+hash-notavalidhashalgorithm");
- detachedSignature.TryGetHashAlgorithm(out hashAlgorithm).Should().BeFalse();
- hashAlgorithm.Should().BeNull();
-
- // test null object case
- detachedSignature = null;
- CoseSign1MessageDetachedSignatureExtensions.TryGetHashAlgorithm(detachedSignature, out hashAlgorithm).Should().BeFalse();
- hashAlgorithm.Should().BeNull();
- }
-
- private ICoseSigningKeyProvider SetupMockSigningKeyProvider(string testName)
- {
- Mock mockedSignerKeyProvider = new(MockBehavior.Strict);
- X509Certificate2 selfSignedCertWithRSA = TestCertificateUtils.CreateCertificate(testName);
-
- mockedSignerKeyProvider.Setup(x => x.GetProtectedHeaders()).Returns(null);
- mockedSignerKeyProvider.Setup(x => x.GetUnProtectedHeaders()).Returns(null);
- mockedSignerKeyProvider.Setup(x => x.HashAlgorithm).Returns(HashAlgorithmName.SHA256);
- mockedSignerKeyProvider.Setup(x => x.GetECDsaKey(It.IsAny())).Returns(null);
- mockedSignerKeyProvider.Setup(x => x.GetRSAKey(It.IsAny())).Returns(selfSignedCertWithRSA.GetRSAPrivateKey());
- mockedSignerKeyProvider.Setup(x => x.IsRSA).Returns(true);
-
- return mockedSignerKeyProvider.Object;
- }
-}
diff --git a/CoseDetachedSIgnature.Tests/DetachedSignatureFactoryTests.cs b/CoseDetachedSIgnature.Tests/DetachedSignatureFactoryTests.cs
deleted file mode 100644
index 3ac560a5..00000000
--- a/CoseDetachedSIgnature.Tests/DetachedSignatureFactoryTests.cs
+++ /dev/null
@@ -1,275 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-namespace CoseDetachedSignature.Tests;
-
-///
-/// Class for Testing Methods of
-///
-public class DetachedSignatureFactoryTests
-{
- [SetUp]
- public void Setup()
- {
- }
-
- [Test]
- public void TestConstructors()
- {
- Mock mockFactory = new(MockBehavior.Strict);
- using DetachedSignatureFactory factory = new();
- using DetachedSignatureFactory factory2 = new(HashAlgorithmName.SHA384);
- using DetachedSignatureFactory factory3 = new(HashAlgorithmName.SHA512, mockFactory.Object);
-
- factory.HashAlgorithm.Should().BeAssignableTo();
- factory.HashAlgorithmName.Should().Be(HashAlgorithmName.SHA256);
- factory.MessageFactory.Should().BeOfType();
-
- factory2.HashAlgorithm.Should().BeAssignableTo();
- factory2.HashAlgorithmName.Should().Be(HashAlgorithmName.SHA384);
- factory2.MessageFactory.Should().BeOfType();
-
- factory3.HashAlgorithm.Should().BeAssignableTo();
- factory3.HashAlgorithmName.Should().Be(HashAlgorithmName.SHA512);
- factory3.MessageFactory.Should().Be(mockFactory.Object);
- }
-
- [Test]
- public async Task TestCreateDetachedSignatureAsync()
- {
- ICoseSigningKeyProvider coseSigningKeyProvider = SetupMockSigningKeyProvider(nameof(TestCreateDetachedSignatureAsync));
- using DetachedSignatureFactory factory = new();
- byte[] randomBytes = new byte[50];
- new Random().NextBytes(randomBytes);
- using MemoryStream memStream = new(randomBytes);
-
- // test the sync method
- Assert.Throws(() => factory.CreateDetachedSignature(randomBytes, coseSigningKeyProvider, string.Empty));
- CoseSign1Message detachedSignature = factory.CreateDetachedSignature(randomBytes, coseSigningKeyProvider, "application/test.payload");
- detachedSignature.ProtectedHeaders.ContainsKey(CoseHeaderLabel.ContentType).Should().BeTrue();
- detachedSignature.ProtectedHeaders[CoseHeaderLabel.ContentType].GetValueAsString().Should().Be("application/test.payload+hash-sha256");
- detachedSignature.SignatureMatches(randomBytes).Should().BeTrue();
-
- Assert.Throws(() => factory.CreateDetachedSignature(memStream, coseSigningKeyProvider, string.Empty));
- memStream.Seek(0, SeekOrigin.Begin);
- CoseSign1Message detachedSignature2 = factory.CreateDetachedSignature(memStream, coseSigningKeyProvider, "application/test.payload");
- detachedSignature2.ProtectedHeaders.ContainsKey(CoseHeaderLabel.ContentType).Should().BeTrue();
- detachedSignature2.ProtectedHeaders[CoseHeaderLabel.ContentType].GetValueAsString().Should().Be("application/test.payload+hash-sha256");
- detachedSignature2.SignatureMatches(randomBytes).Should().BeTrue();
- memStream.Seek(0, SeekOrigin.Begin);
-
- // test the async methods
- Assert.ThrowsAsync(() => factory.CreateDetachedSignatureAsync(randomBytes, coseSigningKeyProvider, string.Empty));
- CoseSign1Message detachedSignature3 = await factory.CreateDetachedSignatureAsync(randomBytes, coseSigningKeyProvider, "application/test.payload");
- detachedSignature3.ProtectedHeaders.ContainsKey(CoseHeaderLabel.ContentType).Should().BeTrue();
- detachedSignature3.ProtectedHeaders[CoseHeaderLabel.ContentType].GetValueAsString().Should().Be("application/test.payload+hash-sha256");
- detachedSignature3.SignatureMatches(randomBytes).Should().BeTrue();
-
- Assert.ThrowsAsync(() => factory.CreateDetachedSignatureAsync(memStream, coseSigningKeyProvider, string.Empty));
- memStream.Seek(0, SeekOrigin.Begin);
- CoseSign1Message detachedSignature4 = await factory.CreateDetachedSignatureAsync(memStream, coseSigningKeyProvider, "application/test.payload");
- detachedSignature4.ProtectedHeaders.ContainsKey(CoseHeaderLabel.ContentType).Should().BeTrue();
- detachedSignature4.ProtectedHeaders[CoseHeaderLabel.ContentType].GetValueAsString().Should().Be("application/test.payload+hash-sha256");
- detachedSignature4.SignatureMatches(randomBytes).Should().BeTrue();
- memStream.Seek(0, SeekOrigin.Begin);
- }
-
- [Test]
- public async Task TestCreateDetachedSignatureHashProvidedAsync()
- {
- ICoseSigningKeyProvider coseSigningKeyProvider = SetupMockSigningKeyProvider(nameof(TestCreateDetachedSignatureHashProvidedAsync));
- using DetachedSignatureFactory factory = new();
- byte[] randomBytes = new byte[50];
- new Random().NextBytes(randomBytes);
- using HashAlgorithm hasher = CoseSign1MessageDetachedSignatureExtensions.CreateHashAlgorithmFromName(factory.HashAlgorithmName)
- ?? throw new Exception($"Failed to get hash algorithm from {nameof(CoseSign1MessageDetachedSignatureExtensions.CreateHashAlgorithmFromName)}");
- byte[] hash = hasher!.ComputeHash(randomBytes);
- using MemoryStream hashStream = new(hash);
-
- // test the sync method
- Assert.Throws(() => factory.CreateDetachedSignatureFromHash(hash, coseSigningKeyProvider, string.Empty));
- CoseSign1Message detachedSignature = factory.CreateDetachedSignatureFromHash(hash, coseSigningKeyProvider, "application/test.payload");
- detachedSignature.ProtectedHeaders.ContainsKey(CoseHeaderLabel.ContentType).Should().BeTrue();
- detachedSignature.ProtectedHeaders[CoseHeaderLabel.ContentType].GetValueAsString().Should().Be("application/test.payload+hash-sha256");
- detachedSignature.SignatureMatches(randomBytes).Should().BeTrue();
-
- Assert.Throws(() => factory.CreateDetachedSignature(hashStream, coseSigningKeyProvider, string.Empty));
- hashStream.Seek(0, SeekOrigin.Begin);
- CoseSign1Message detachedSignature2 = factory.CreateDetachedSignatureFromHash(hashStream, coseSigningKeyProvider, "application/test.payload");
- detachedSignature2.ProtectedHeaders.ContainsKey(CoseHeaderLabel.ContentType).Should().BeTrue();
- detachedSignature2.ProtectedHeaders[CoseHeaderLabel.ContentType].GetValueAsString().Should().Be("application/test.payload+hash-sha256");
- detachedSignature2.SignatureMatches(randomBytes).Should().BeTrue();
- hashStream.Seek(0, SeekOrigin.Begin);
-
- // test the async methods
- Assert.ThrowsAsync(() => factory.CreateDetachedSignatureFromHashAsync(hash, coseSigningKeyProvider, string.Empty));
- CoseSign1Message detachedSignature3 = await factory.CreateDetachedSignatureFromHashAsync(hash, coseSigningKeyProvider, "application/test.payload");
- detachedSignature3.ProtectedHeaders.ContainsKey(CoseHeaderLabel.ContentType).Should().BeTrue();
- detachedSignature3.ProtectedHeaders[CoseHeaderLabel.ContentType].GetValueAsString().Should().Be("application/test.payload+hash-sha256");
- detachedSignature3.SignatureMatches(randomBytes).Should().BeTrue();
-
- Assert.ThrowsAsync(() => factory.CreateDetachedSignatureFromHashAsync(hashStream, coseSigningKeyProvider, string.Empty));
- hashStream.Seek(0, SeekOrigin.Begin);
- CoseSign1Message detachedSignature4 = await factory.CreateDetachedSignatureFromHashAsync(hashStream, coseSigningKeyProvider, "application/test.payload");
- detachedSignature4.ProtectedHeaders.ContainsKey(CoseHeaderLabel.ContentType).Should().BeTrue();
- detachedSignature4.ProtectedHeaders[CoseHeaderLabel.ContentType].GetValueAsString().Should().Be("application/test.payload+hash-sha256");
- detachedSignature4.SignatureMatches(randomBytes).Should().BeTrue();
- hashStream.Seek(0, SeekOrigin.Begin);
- }
-
- [Test]
- public async Task TestCreateDetachedSignatureBytesAsync()
- {
- ICoseSigningKeyProvider coseSigningKeyProvider = SetupMockSigningKeyProvider(nameof(TestCreateDetachedSignatureBytesAsync));
- using DetachedSignatureFactory factory = new();
- byte[] randomBytes = new byte[50];
- new Random().NextBytes(randomBytes);
- using MemoryStream memStream = new(randomBytes);
-
- // test the sync method
- Assert.Throws(() => factory.CreateDetachedSignatureBytes(randomBytes, coseSigningKeyProvider, string.Empty));
- CoseSign1Message detachedSignature = CoseMessage.DecodeSign1(factory.CreateDetachedSignatureBytes(randomBytes, coseSigningKeyProvider, "application/test.payload").ToArray());
- detachedSignature.ProtectedHeaders.ContainsKey(CoseHeaderLabel.ContentType).Should().BeTrue();
- detachedSignature.ProtectedHeaders[CoseHeaderLabel.ContentType].GetValueAsString().Should().Be("application/test.payload+hash-sha256");
- detachedSignature.SignatureMatches(randomBytes).Should().BeTrue();
-
- Assert.Throws(() => factory.CreateDetachedSignatureBytes(memStream, coseSigningKeyProvider, string.Empty));
- memStream.Seek(0, SeekOrigin.Begin);
- CoseSign1Message detachedSignature2 = CoseMessage.DecodeSign1(factory.CreateDetachedSignatureBytes(memStream, coseSigningKeyProvider, "application/test.payload").ToArray());
- detachedSignature2.ProtectedHeaders.ContainsKey(CoseHeaderLabel.ContentType).Should().BeTrue();
- detachedSignature2.ProtectedHeaders[CoseHeaderLabel.ContentType].GetValueAsString().Should().Be("application/test.payload+hash-sha256");
- detachedSignature2.SignatureMatches(randomBytes).Should().BeTrue();
- memStream.Seek(0, SeekOrigin.Begin);
-
- // test the async methods
- Assert.ThrowsAsync(() => factory.CreateDetachedSignatureBytesAsync(randomBytes, coseSigningKeyProvider, string.Empty));
- CoseSign1Message detachedSignature3 = CoseMessage.DecodeSign1((await factory.CreateDetachedSignatureBytesAsync(randomBytes, coseSigningKeyProvider, "application/test.payload")).ToArray());
- detachedSignature3.ProtectedHeaders.ContainsKey(CoseHeaderLabel.ContentType).Should().BeTrue();
- detachedSignature3.ProtectedHeaders[CoseHeaderLabel.ContentType].GetValueAsString().Should().Be("application/test.payload+hash-sha256");
- detachedSignature3.SignatureMatches(randomBytes).Should().BeTrue();
-
- Assert.ThrowsAsync(() => factory.CreateDetachedSignatureBytesAsync(memStream, coseSigningKeyProvider, string.Empty));
- memStream.Seek(0, SeekOrigin.Begin);
- CoseSign1Message detachedSignature4 = CoseMessage.DecodeSign1((await factory.CreateDetachedSignatureBytesAsync(memStream, coseSigningKeyProvider, "application/test.payload")).ToArray());
- detachedSignature4.ProtectedHeaders.ContainsKey(CoseHeaderLabel.ContentType).Should().BeTrue();
- detachedSignature4.ProtectedHeaders[CoseHeaderLabel.ContentType].GetValueAsString().Should().Be("application/test.payload+hash-sha256");
- memStream.Seek(0, SeekOrigin.Begin);
- detachedSignature4.SignatureMatches(memStream).Should().BeTrue();
- }
-
- [Test]
- public async Task TestCreateDetachedSignatureBytesHashProvidedAsync()
- {
- ICoseSigningKeyProvider coseSigningKeyProvider = SetupMockSigningKeyProvider(nameof(TestCreateDetachedSignatureBytesHashProvidedAsync));
- using DetachedSignatureFactory factory = new();
- byte[] randomBytes = new byte[50];
- new Random().NextBytes(randomBytes);
- using HashAlgorithm hasher = CoseSign1MessageDetachedSignatureExtensions.CreateHashAlgorithmFromName(factory.HashAlgorithmName)
- ?? throw new Exception($"Failed to get hash algorithm from {nameof(CoseSign1MessageDetachedSignatureExtensions.CreateHashAlgorithmFromName)}");
- byte[] hash = hasher!.ComputeHash(randomBytes);
- using MemoryStream hashStream = new(hash);
-
- // test the sync method
- Assert.Throws(() => factory.CreateDetachedSignatureBytesFromHash(hash, coseSigningKeyProvider, string.Empty));
- CoseSign1Message detachedSignature = CoseMessage.DecodeSign1(factory.CreateDetachedSignatureBytesFromHash(hash, coseSigningKeyProvider, "application/test.payload").ToArray());
- detachedSignature.ProtectedHeaders.ContainsKey(CoseHeaderLabel.ContentType).Should().BeTrue();
- detachedSignature.ProtectedHeaders[CoseHeaderLabel.ContentType].GetValueAsString().Should().Be("application/test.payload+hash-sha256");
- detachedSignature.SignatureMatches(randomBytes).Should().BeTrue();
-
- Assert.Throws(() => factory.CreateDetachedSignatureBytesFromHash(hashStream, coseSigningKeyProvider, string.Empty));
- hashStream.Seek(0, SeekOrigin.Begin);
- CoseSign1Message detachedSignature2 = CoseMessage.DecodeSign1(factory.CreateDetachedSignatureBytesFromHash(hashStream, coseSigningKeyProvider, "application/test.payload").ToArray());
- detachedSignature2.ProtectedHeaders.ContainsKey(CoseHeaderLabel.ContentType).Should().BeTrue();
- detachedSignature2.ProtectedHeaders[CoseHeaderLabel.ContentType].GetValueAsString().Should().Be("application/test.payload+hash-sha256");
- detachedSignature2.SignatureMatches(randomBytes).Should().BeTrue();
- hashStream.Seek(0, SeekOrigin.Begin);
-
- // test the async methods
- Assert.ThrowsAsync(() => factory.CreateDetachedSignatureBytesFromHashAsync(hash, coseSigningKeyProvider, string.Empty));
- CoseSign1Message detachedSignature3 = CoseMessage.DecodeSign1((await factory.CreateDetachedSignatureBytesFromHashAsync(hash, coseSigningKeyProvider, "application/test.payload")).ToArray());
- detachedSignature3.ProtectedHeaders.ContainsKey(CoseHeaderLabel.ContentType).Should().BeTrue();
- detachedSignature3.ProtectedHeaders[CoseHeaderLabel.ContentType].GetValueAsString().Should().Be("application/test.payload+hash-sha256");
- detachedSignature3.SignatureMatches(randomBytes).Should().BeTrue();
-
- Assert.ThrowsAsync(() => factory.CreateDetachedSignatureBytesFromHashAsync(hashStream, coseSigningKeyProvider, string.Empty));
- hashStream.Seek(0, SeekOrigin.Begin);
- CoseSign1Message detachedSignature4 = CoseMessage.DecodeSign1((await factory.CreateDetachedSignatureBytesFromHashAsync(hashStream, coseSigningKeyProvider, "application/test.payload")).ToArray());
- detachedSignature4.ProtectedHeaders.ContainsKey(CoseHeaderLabel.ContentType).Should().BeTrue();
- detachedSignature4.ProtectedHeaders[CoseHeaderLabel.ContentType].GetValueAsString().Should().Be("application/test.payload+hash-sha256");
- hashStream.Seek(0, SeekOrigin.Begin);
- detachedSignature4.SignatureMatches(randomBytes).Should().BeTrue();
- }
-
- [Test]
- public void TestCreateDetachedSignatureMd5()
- {
- ICoseSigningKeyProvider coseSigningKeyProvider = SetupMockSigningKeyProvider(nameof(TestCreateDetachedSignatureMd5));
- using DetachedSignatureFactory factory = new(HashAlgorithmName.MD5);
- byte[] randomBytes = new byte[50];
- new Random().NextBytes(randomBytes);
-
- // test the sync method
- Assert.Throws(() => factory.CreateDetachedSignature(randomBytes, coseSigningKeyProvider, string.Empty));
- CoseSign1Message detachedSignature = CoseMessage.DecodeSign1(factory.CreateDetachedSignatureBytes(randomBytes, coseSigningKeyProvider, "application/test.payload").ToArray());
- detachedSignature.ProtectedHeaders.ContainsKey(CoseHeaderLabel.ContentType).Should().BeTrue();
- detachedSignature.ProtectedHeaders[CoseHeaderLabel.ContentType].GetValueAsString().Should().Be("application/test.payload+hash-md5");
- detachedSignature.SignatureMatches(randomBytes).Should().BeTrue();
- }
-
- [Test]
- public void TestCreateDetachedSignatureMd5HashProvided()
- {
- ICoseSigningKeyProvider coseSigningKeyProvider = SetupMockSigningKeyProvider(nameof(TestCreateDetachedSignatureMd5));
- using DetachedSignatureFactory factory = new();
- byte[] randomBytes = new byte[50];
- new Random().NextBytes(randomBytes);
- using HashAlgorithm hasher = CoseSign1MessageDetachedSignatureExtensions.CreateHashAlgorithmFromName(HashAlgorithmName.MD5)
- ?? throw new Exception($"Failed to get hash algorithm from {nameof(CoseSign1MessageDetachedSignatureExtensions.CreateHashAlgorithmFromName)}");
- byte[] hash = hasher!.ComputeHash(randomBytes);
-
- // test the sync method
- Assert.Throws(() => factory.CreateDetachedSignatureFromHash(hash, coseSigningKeyProvider, string.Empty));
- CoseSign1Message detachedSignature = CoseMessage.DecodeSign1(factory.CreateDetachedSignatureBytesFromHash(hash, coseSigningKeyProvider, "application/test.payload").ToArray());
- detachedSignature.ProtectedHeaders.ContainsKey(CoseHeaderLabel.ContentType).Should().BeTrue();
- detachedSignature.ProtectedHeaders[CoseHeaderLabel.ContentType].GetValueAsString().Should().Be("application/test.payload+hash-md5");
- detachedSignature.SignatureMatches(randomBytes).Should().BeTrue();
-
- // test unknown hash length
- // test the sync method
- Assert.Throws(() => factory.CreateDetachedSignatureFromHash(randomBytes, coseSigningKeyProvider, "application/test.payload"));
- }
-
- [Test]
- public void TestCreateDetachedSignatureAlreadyProvided()
- {
- ICoseSigningKeyProvider coseSigningKeyProvider = SetupMockSigningKeyProvider(nameof(TestCreateDetachedSignatureAlreadyProvided));
- using DetachedSignatureFactory factory = new();
- byte[] randomBytes = new byte[50];
- new Random().NextBytes(randomBytes);
- using HashAlgorithm hasher = CoseSign1MessageDetachedSignatureExtensions.CreateHashAlgorithmFromName(factory.HashAlgorithmName)
- ?? throw new Exception($"Failed to get hash algorithm from {nameof(CoseSign1MessageDetachedSignatureExtensions.CreateHashAlgorithmFromName)}");
- ReadOnlyMemory hash = hasher!.ComputeHash(randomBytes);
-
- // test the sync method
- Assert.Throws(() => factory.CreateDetachedSignature(hash, coseSigningKeyProvider, string.Empty));
- CoseSign1Message detachedSignature = CoseMessage.DecodeSign1(factory.CreateDetachedSignatureBytes(randomBytes, coseSigningKeyProvider, "application/test.payload+hash-sha256").ToArray());
- detachedSignature.ProtectedHeaders.ContainsKey(CoseHeaderLabel.ContentType).Should().BeTrue();
- detachedSignature.ProtectedHeaders[CoseHeaderLabel.ContentType].GetValueAsString().Should().Be("application/test.payload+hash-sha256");
- detachedSignature.SignatureMatches(randomBytes).Should().BeTrue();
- }
-
- private ICoseSigningKeyProvider SetupMockSigningKeyProvider(string testName)
- {
- Mock mockedSignerKeyProvider = new(MockBehavior.Strict);
- X509Certificate2 selfSignedCertWithRSA = TestCertificateUtils.CreateCertificate(testName);
-
- mockedSignerKeyProvider.Setup(x => x.GetProtectedHeaders()).Returns(null);
- mockedSignerKeyProvider.Setup(x => x.GetUnProtectedHeaders()).Returns(null);
- mockedSignerKeyProvider.Setup(x => x.HashAlgorithm).Returns(HashAlgorithmName.SHA256);
- mockedSignerKeyProvider.Setup(x => x.GetECDsaKey(It.IsAny())).Returns(null);
- mockedSignerKeyProvider.Setup(x => x.GetRSAKey(It.IsAny())).Returns(selfSignedCertWithRSA.GetRSAPrivateKey());
- mockedSignerKeyProvider.Setup(x => x.IsRSA).Returns(true);
-
- return mockedSignerKeyProvider.Object;
- }
-}
diff --git a/CoseHandler.Tests/CoseSignValidateTests.cs b/CoseHandler.Tests/CoseSignValidateTests.cs
index 9b642d07..822bada6 100644
--- a/CoseHandler.Tests/CoseSignValidateTests.cs
+++ b/CoseHandler.Tests/CoseSignValidateTests.cs
@@ -3,7 +3,7 @@
namespace CoseSignUnitTests;
-using CoseDetachedSignature;
+using CoseIndirectSignature;
using System.Net.Mime;
using System.Runtime.ConstrainedExecution;
@@ -340,8 +340,8 @@ public void DetachedValidateModifiedPayload()
[TestMethod]
public void IndirectSignatureValidation()
{
- var msgFac = new DetachedSignatureFactory();
- byte[] signedBytes = msgFac.CreateDetachedSignatureBytes(
+ var msgFac = new IndirectSignatureFactory();
+ byte[] signedBytes = msgFac.CreateIndirectSignatureBytes(
payload: Payload1Bytes,
contentType: "application/spdx+json",
signingKeyProvider: new X509Certificate2CoseSigningKeyProvider(Leaf1Priv)).ToArray();
@@ -358,8 +358,8 @@ public void IndirectSignatureValidation()
[TestMethod]
public void IndirectSignatureModifiedPayload()
{
- var msgFac = new DetachedSignatureFactory();
- byte[] signedBytes = msgFac.CreateDetachedSignatureBytes(
+ var msgFac = new IndirectSignatureFactory();
+ byte[] signedBytes = msgFac.CreateIndirectSignatureBytes(
payload: Payload1Bytes,
contentType: "application/spdx+json",
signingKeyProvider: new X509Certificate2CoseSigningKeyProvider(Leaf1Priv)).ToArray();
@@ -381,8 +381,8 @@ public void IndirectSignatureModifiedPayload()
[TestMethod]
public void IndirectSignatureUntrustedSignature()
{
- var msgFac = new DetachedSignatureFactory();
- byte[] signedBytes = msgFac.CreateDetachedSignatureBytes(
+ var msgFac = new IndirectSignatureFactory();
+ byte[] signedBytes = msgFac.CreateIndirectSignatureBytes(
payload: Payload1Bytes,
contentType: "application/spdx+json",
signingKeyProvider: new X509Certificate2CoseSigningKeyProvider(Leaf1Priv)).ToArray();
diff --git a/CoseHandler/CoseHandler.cs b/CoseHandler/CoseHandler.cs
index 96f7fcc6..0c469a27 100644
--- a/CoseHandler/CoseHandler.cs
+++ b/CoseHandler/CoseHandler.cs
@@ -21,7 +21,7 @@ namespace CoseX509;
using CoseSign1.Certificates.Local.Validators;
using CoseSign1.Extensions;
using CoseSign1.Interfaces;
-using CoseDetachedSignature.Extensions;
+using CoseIndirectSignature.Extensions;
///
/// Contains static methods to generate and validate Cose X509 signatures.
@@ -605,7 +605,7 @@ internal static ValidationResult ValidateInternal(
// Determine the type of content validation to perform.
// Check for an indirect signature, where the content header contains the hash of the payload, and the algorithm is stored in the message.
// If this is the case and external content is provided, we can validate an external payload hash against the hash stored in the cose message content.
- ContentValidationType cvt = msg.IsDetachedSignature() ? ContentValidationType.Indirect :
+ ContentValidationType cvt = msg.IsIndirectSignature() ? ContentValidationType.Indirect :
(hasBytes || hasStream) ? ContentValidationType.Detached : ContentValidationType.Embedded;
try
diff --git a/CoseHandler/CoseHandler.csproj b/CoseHandler/CoseHandler.csproj
index 619b4f22..c495a72e 100644
--- a/CoseHandler/CoseHandler.csproj
+++ b/CoseHandler/CoseHandler.csproj
@@ -19,7 +19,7 @@
-
+
diff --git a/CoseIndirectSignature.Tests/CoseHashVFuzzer.cs b/CoseIndirectSignature.Tests/CoseHashVFuzzer.cs
new file mode 100644
index 00000000..b49d2f75
--- /dev/null
+++ b/CoseIndirectSignature.Tests/CoseHashVFuzzer.cs
@@ -0,0 +1,30 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+// Ignore Spelling: Cose Fuzzer
+
+namespace CoseIndirectSignature.Tests;
+using System;
+using CoseIndirectSignature.Exceptions;
+
+///
+/// Class used to fuzz the parsing logic for CoseHashV and CborReader under the covers.
+///
+public static class CoseHashVFuzzer
+{
+ ///
+ /// Fuzz target method matching signatures expected for fuzzing.
+ ///
+ ///
+ public static void FuzzCoseHashVParser(ReadOnlySpan input)
+ {
+ try
+ {
+ CoseHashV objectUnderTest = CoseHashV.Deserialize(input);
+ }
+ // deserialize documents two exceptions to be thrown, so catch them as known "good" behavior.
+ catch(Exception ex) when (ex is InvalidCoseDataException || ex is ArgumentNullException)
+ {
+ }
+ }
+}
diff --git a/CoseIndirectSignature.Tests/CoseHashVTests.cs b/CoseIndirectSignature.Tests/CoseHashVTests.cs
new file mode 100644
index 00000000..d10c970c
--- /dev/null
+++ b/CoseIndirectSignature.Tests/CoseHashVTests.cs
@@ -0,0 +1,604 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+// Ignore Spelling: Cose Deserialization
+
+namespace CoseIndirectSignature.Tests;
+
+using System.Formats.Cbor;
+using System.Security.Cryptography;
+using CoseIndirectSignature.Exceptions;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using NUnit.Framework.Internal;
+
+///
+/// Class for Testing Methods of
+///
+public class CoseHashVTests
+{
+ [SetUp]
+ public void Setup()
+ {
+ }
+
+ [Test]
+ [TestCase(1, Description = "Default constructor.")]
+ [TestCase(2, Description = "Sha256 with valid byte data.")]
+ [TestCase(3, Description = "Sha256 with valid stream data.")]
+ [TestCase(4, Description = "Sha256 with valid byte data, and a location.")]
+ [TestCase(5, Description = "Sha256 with valid byte data, a location, and additionalData.")]
+ [TestCase(6, Description = "Sha256 with valid stream, and a location.")]
+ [TestCase(7, Description = "Sha256 with a valid stream, a location, and additionalData.")]
+ [TestCase(8, Description = "Sha256 with a valid readonly memory.")]
+ [TestCase(9, Description = "Sha256 with a valid readonly memory, and a location.")]
+ [TestCase(10, Description = "Copy constructor.")]
+ [TestCase(11, Description = "Bypass hash validation for explicit construction")]
+ [TestCase(12, Description = "Bypass hash validation for explicit construction with a bogus algorithm, should serialize")]
+ public void TestCoseHashVConstructorSuccess(int testCase)
+ {
+ // arrange
+ byte[] testData = new byte[] { 0x01, 0x02, 0x03, 0x04 };
+ using MemoryStream stream = new MemoryStream(testData);
+ ReadOnlyMemory rom = new ReadOnlyMemory(testData);
+ CoseHashV testObj = new CoseHashV();
+ switch (testCase)
+ {
+ case 1:
+ testObj.Algorithm.Should().Be(CoseHashAlgorithm.Reserved);
+ testObj.HashValue.Should().BeEmpty();
+ testObj.Location.Should().BeNullOrWhiteSpace();
+ testObj.AdditionalData.Should().BeNull();
+ break;
+ case 2:
+ testObj = new CoseHashV(CoseHashAlgorithm.SHA256, byteData: testData);
+ testObj.Algorithm.Should().Be(CoseHashAlgorithm.SHA256);
+ testObj.HashValue.Should().NotBeEmpty();
+ testObj.HashValue.Length.Should().Be(32);
+ testObj.Location.Should().BeNullOrWhiteSpace();
+ testObj.AdditionalData.Should().BeNull();
+ break;
+ case 3:
+ testObj = new CoseHashV(CoseHashAlgorithm.SHA256, stream);
+ stream.Seek(0, SeekOrigin.Begin);
+ testObj.Algorithm.Should().Be(CoseHashAlgorithm.SHA256);
+ testObj.HashValue.Should().NotBeEmpty();
+ testObj.HashValue.Length.Should().Be(32);
+ testObj.Location.Should().BeNullOrWhiteSpace();
+ testObj.AdditionalData.Should().BeNull();
+ break;
+ case 4:
+ testObj = new CoseHashV(CoseHashAlgorithm.SHA256, testData, "location");
+ testObj.Algorithm.Should().Be(CoseHashAlgorithm.SHA256);
+ testObj.HashValue.Should().NotBeEmpty();
+ testObj.HashValue.Length.Should().Be(32);
+ testObj.Location.Should().Be("location");
+ testObj.AdditionalData.Should().BeNull();
+ break;
+ case 5:
+ testObj = new CoseHashV(CoseHashAlgorithm.SHA256, testData, "location", testData);
+ testObj.Algorithm.Should().Be(CoseHashAlgorithm.SHA256);
+ testObj.HashValue.Should().NotBeEmpty();
+ testObj.HashValue.Length.Should().Be(32);
+ testObj.Location.Should().Be("location");
+ testObj.AdditionalData.Should().BeEquivalentTo(testData);
+ break;
+ case 6:
+ testObj = new CoseHashV(CoseHashAlgorithm.SHA256, stream, "location");
+ stream.Seek(0, SeekOrigin.Begin);
+ testObj.Algorithm.Should().Be(CoseHashAlgorithm.SHA256);
+ testObj.HashValue.Should().NotBeEmpty();
+ testObj.HashValue.Length.Should().Be(32);
+ testObj.Location.Should().Be("location");
+ testObj.AdditionalData.Should().BeNull();
+ break;
+ case 7:
+ testObj = new CoseHashV(CoseHashAlgorithm.SHA256, stream, "location", testData);
+ stream.Seek(0, SeekOrigin.Begin);
+ testObj.Algorithm.Should().Be(CoseHashAlgorithm.SHA256);
+ testObj.HashValue.Should().NotBeEmpty();
+ testObj.HashValue.Length.Should().Be(32);
+ testObj.Location.Should().Be("location");
+ testObj.AdditionalData.Should().BeEquivalentTo(testData);
+ break;
+ case 8:
+ testObj = new CoseHashV(CoseHashAlgorithm.SHA256, rom, "location");
+ testObj.Algorithm.Should().Be(CoseHashAlgorithm.SHA256);
+ testObj.HashValue.Should().NotBeEmpty();
+ testObj.HashValue.Length.Should().Be(32);
+ testObj.Location.Should().Be("location");
+ testObj.AdditionalData.Should().BeNull();
+ break;
+ case 9:
+ testObj = new CoseHashV(CoseHashAlgorithm.SHA256, rom, "location", rom);
+ testObj.Algorithm.Should().Be(CoseHashAlgorithm.SHA256);
+ testObj.HashValue.Should().NotBeEmpty();
+ testObj.HashValue.Length.Should().Be(32);
+ testObj.Location.Should().Be("location");
+ testObj.AdditionalData.Should().BeEquivalentTo(testData);
+ break;
+ case 10:
+ testObj.AdditionalData = [0x03, 0x02, 0x01];
+ CoseHashV other = new CoseHashV(testObj);
+ other.Algorithm.Should().Be(testObj.Algorithm);
+ other.HashValue.Should().BeEquivalentTo(testObj.HashValue);
+ other.Location.Should().Be(testObj.Location);
+ other.AdditionalData.Should().BeEquivalentTo(testObj.AdditionalData);
+ break;
+ case 11:
+ CoseHashV testObject11 = new CoseHashV(CoseHashAlgorithm.SHA256, hashValue: [0x1, 0x2, 0x3], disableValidation: true);
+ testObject11.Algorithm.Should().Be(CoseHashAlgorithm.SHA256);
+ testObject11.HashValue.Should().BeEquivalentTo(new byte[] { 0x1, 0x2, 0x3 });
+ break;
+ case 12:
+ CoseHashV testObject12 = new CoseHashV((CoseHashAlgorithm)(-100), hashValue: [0x1, 0x2, 0x3], disableValidation: true);
+ testObject12.Algorithm.Should().Be((CoseHashAlgorithm)(-100));
+ testObject12.HashValue.Should().BeEquivalentTo(new byte[] { 0x1, 0x2, 0x3 });
+ break;
+ default:
+ throw new InvalidDataException($"Test case {testCase} is not defined in {nameof(TestCoseHashVConstructorSuccess)}");
+ }
+ }
+
+ [Test]
+ [TestCase(1, Description = "Reserved algorithm case for byte ctor.")]
+ [TestCase(2, Description = "Hash value that is not the size of the expected hash algorithm.")]
+ [TestCase(3, Description = "Reserved algorithm case for stream ctor.")]
+ [TestCase(4, Description = "Reserved algorithm case for byte ctor. and location")]
+ [TestCase(5, Description = "Reserved algorithm case for byte ctor. and location and additionalData")]
+ [TestCase(6, Description = "Reserved algorithm case for stream ctor. and location")]
+ [TestCase(7, Description = "Reserved algorithm case for stream ctor. and location and additionalData")]
+ [TestCase(8, Description = "SHA256Trunc64 algorithm for readonly memory byte array, and location")]
+ [TestCase(9, Description = "SHAKE128 algorithm for readonly memory byte array and location and additionalData")]
+ [TestCase(10, Description = "Null stream.")]
+ [TestCase(11, Description = "Null byte data.")]
+ [TestCase(12, Description = "Null read only data.")]
+ [TestCase(13, Description = "Null hashValue")]
+ [TestCase(14, Description = "0 length hashValue")]
+ public void TestCoseHashVConstructorFailure(int testCase)
+ {
+ // arrange
+ byte[] testData = new byte[] { 0x01, 0x02, 0x03, 0x04 };
+ using MemoryStream stream = new MemoryStream(testData);
+ ReadOnlyMemory rom = new ReadOnlyMemory(testData);
+
+ // act and assert
+ Action act = () => new CoseHashV(CoseHashAlgorithm.Reserved, byteData: testData);
+ switch (testCase)
+ {
+ case 1:
+ act.Should().Throw();
+ break;
+ case 2:
+ act = () => new CoseHashV(CoseHashAlgorithm.SHA256, hashValue: testData);
+ act.Should().Throw();
+ break;
+ case 3:
+ act = () => new CoseHashV(CoseHashAlgorithm.Reserved, stream);
+ act.Should().Throw();
+ break;
+ case 4:
+ act = () => new CoseHashV(CoseHashAlgorithm.Reserved, testData, "location");
+ act.Should().Throw();
+ break;
+ case 5:
+ act = () => new CoseHashV(CoseHashAlgorithm.SHAKE256, testData, "location", testData);
+ act.Should().Throw();
+ break;
+ case 6:
+#pragma warning disable CS0618
+ act = () => new CoseHashV(CoseHashAlgorithm.SHA1, stream, "location");
+ act.Should().Throw();
+#pragma warning restore CS0618
+ break;
+ case 7:
+#pragma warning disable CS0618
+ act = () => new CoseHashV(CoseHashAlgorithm.SHA512Truc256, stream, "location", testData);
+ act.Should().Throw();
+#pragma warning restore CS0618
+ break;
+ case 8:
+#pragma warning disable CS0618
+ act = () => new CoseHashV(CoseHashAlgorithm.SHA256Trunc64, rom, "location");
+ act.Should().Throw();
+#pragma warning restore CS0618
+ break;
+ case 9:
+ act = () => new CoseHashV(CoseHashAlgorithm.SHAKE128, rom, "location", rom);
+ act.Should().Throw();
+ break;
+ case 10:
+#nullable disable
+ act = () => new CoseHashV(CoseHashAlgorithm.SHA256, streamData: null);
+ act.Should().Throw();
+#nullable restore
+ break;
+ case 11:
+#nullable disable
+ act = () => new CoseHashV(CoseHashAlgorithm.SHA256, byteData: null);
+ act.Should().Throw();
+#nullable restore
+ break;
+ case 12:
+ act = () => new CoseHashV(CoseHashAlgorithm.SHA256, readonlyData: null);
+ act.Should().Throw();
+ break;
+ case 13:
+ act = () => new CoseHashV(CoseHashAlgorithm.SHA256, hashValue: null);
+ act.Should().Throw();
+ break;
+ case 14:
+ act = () => new CoseHashV(CoseHashAlgorithm.SHA256, hashValue: []);
+ act.Should().Throw();
+ break;
+ default:
+ throw new InvalidDataException($"Test case {testCase} is not defined in {nameof(TestCoseHashVConstructorFailure)}");
+ }
+ }
+
+ [Test]
+ public void CoseHashVContentStreamMatchesTests()
+ {
+ // arrange
+ byte[] testData = new byte[] { 0x01, 0x02, 0x03, 0x04 };
+ using MemoryStream stream = new MemoryStream(testData);
+ ReadOnlyMemory rom = new ReadOnlyMemory(testData);
+ CoseHashV testObj = new CoseHashV(CoseHashAlgorithm.SHA256, byteData: testData);
+
+ // act
+ bool result = testObj.ContentMatches(testData);
+ bool resultStream = testObj.ContentMatches(stream);
+ bool resultRom = testObj.ContentMatches(rom);
+
+ // assert
+ result.Should().BeTrue();
+ resultStream.Should().BeTrue();
+ resultRom.Should().BeTrue();
+ }
+
+ [Test]
+ public void CoseHashVContentStreamMatchesAsyncTests()
+ {
+ // arrange
+ byte[] testData = new byte[] { 0x01, 0x02, 0x03, 0x04 };
+ using MemoryStream stream = new MemoryStream(testData);
+ ReadOnlyMemory rom = new ReadOnlyMemory(testData);
+ CoseHashV testObj = new CoseHashV(CoseHashAlgorithm.SHA256, byteData: testData);
+
+ // act
+ bool result = testObj.ContentMatchesAsync(testData).Result;
+ bool resultStream = testObj.ContentMatchesAsync(stream).Result;
+ bool resultRom = testObj.ContentMatchesAsync(rom).Result;
+
+ // assert
+ result.Should().BeTrue();
+ resultStream.Should().BeTrue();
+ resultRom.Should().BeTrue();
+ }
+
+ [Test]
+ public void ContentMatchesNullDataFailureTests()
+ {
+ // arrange
+ CoseHashV testObj = new CoseHashV(CoseHashAlgorithm.SHA256, byteData: new byte[] { 0x01, 0x02, 0x03, 0x04 });
+
+ // act and assert
+#nullable disable
+ Func act = async () => await testObj.ContentMatchesAsync(data: null).ConfigureAwait(false);
+ act.Should().ThrowAsync();
+
+ Action act2 = () => testObj.ContentMatches(stream: null);
+ act2.Should().Throw();
+#nullable restore
+ }
+
+ [Test]
+ public void TestSerialization()
+ {
+ // arrange
+ byte[] testData = new byte[] { 0x01, 0x02, 0x03, 0x04 };
+ CoseHashV testObj = new CoseHashV(CoseHashAlgorithm.SHA256, byteData: testData);
+
+ // act
+ byte[] encoding = testObj.Serialize();
+ CoseHashV newObj = CoseHashV.Deserialize(new CborReader(encoding));
+
+ // assert
+ encoding.Should().NotBeNull();
+ encoding.Length.Should().BeGreaterThan(0);
+ newObj.Algorithm.Should().Be(testObj.Algorithm);
+ newObj.HashValue.Should().BeEquivalentTo(testObj.HashValue);
+ newObj.Location.Should().Be(testObj.Location);
+ newObj.AdditionalData.Should().BeEquivalentTo(testObj.AdditionalData);
+ }
+
+ [Test]
+ public void TestSerializationWithOptionalFields()
+ {
+ // arrange
+ byte[] testData = new byte[] { 0x01, 0x02, 0x03, 0x04 };
+ CoseHashV testObj = new CoseHashV(CoseHashAlgorithm.SHA256, testData, location: "this is the location");
+
+ // act
+ byte[] encoding = testObj.Serialize();
+ CoseHashV newObj = CoseHashV.Deserialize(encoding);
+
+ // assert
+ encoding.Should().NotBeNull();
+ encoding.Length.Should().BeGreaterThan(0);
+ newObj.Algorithm.Should().Be(testObj.Algorithm);
+ newObj.HashValue.Should().BeEquivalentTo(testObj.HashValue);
+ newObj.Location.Should().Be(testObj.Location);
+ newObj.AdditionalData.Should().BeEquivalentTo(testObj.AdditionalData);
+
+ testObj = new CoseHashV(CoseHashAlgorithm.SHA256, testData, location: "this is the location", additionalData: [0x01, 0x03, 0x04]);
+
+ // act
+ encoding = testObj.Serialize();
+ newObj = CoseHashV.Deserialize(encoding);
+
+ // assert
+ encoding.Should().NotBeNull();
+ encoding.Length.Should().BeGreaterThan(0);
+ newObj.Algorithm.Should().Be(testObj.Algorithm);
+ newObj.HashValue.Should().BeEquivalentTo(testObj.HashValue);
+ newObj.Location.Should().Be(testObj.Location);
+ newObj.AdditionalData.Should().BeEquivalentTo(testObj.AdditionalData);
+ }
+
+ [Test]
+ [TestCase(1, Description = "Too few properties.")]
+ [TestCase(2, Description = "Too many properties.")]
+ [TestCase(3, Description = "Correct properties, but wrong type.")]
+ [TestCase(4, Description = "Correct algorithm and hash, but wrong location type.")]
+ [TestCase(5, Description = "Correct algorithm and hash, but wrong additionalData type.")]
+ [TestCase(6, Description = "Correct algorithm, but hash length does not match algorithm.")]
+ [TestCase(7, Description = "Not starting with start array")]
+ [TestCase(8, Description = "Null Cbor reader")]
+ [TestCase(9, Description = "Null byte array")]
+ [TestCase(10, Description = "Invalid tstr encoding")]
+ [TestCase(11, Description = "Valid tstr encoding")]
+ [TestCase(12, Description ="Invalid algorithm integer - negative.")]
+ [TestCase(13, Description = "Invalid algorithm integer - positive.")]
+ [TestCase(14, Description = "Invalid algorithm integer, random hash, should deserialize with flag.")]
+ [TestCase(15, Description = "Valid algorithm integer, random hash, should deserialize with flag.")]
+ [TestCase(16, Description = "0 bytes to ReadOnlySpan")]
+ [TestCase(17, Description = "Fuzz overflow")]
+ [TestCase(18, Description = "Fuzz enum overflow")]
+ public void TestObjectManualSerializationPaths(int testCase)
+ {
+ CborWriter? writer;
+ int propertyCount = 1;
+ byte[]? cborEcoding;
+ switch (testCase)
+ {
+ // handle too few properties
+ case 1:
+ writer = new(CborConformanceMode.Strict);
+ writer.WriteStartArray(propertyCount);
+ writer.WriteInt64((long)2);
+ writer.WriteEndArray();
+
+ cborEcoding = writer.Encode();
+ Assert.ThrowsException(() => CoseHashV.Deserialize(cborEcoding));
+ break;
+ // handle too many properties
+ case 2:
+ writer = new(CborConformanceMode.Strict);
+ propertyCount = 5;
+ writer.Reset();
+ writer.WriteStartArray(propertyCount);
+ writer.WriteInt64((long)CoseHashAlgorithm.SHA256);
+ writer.WriteByteString(new byte[] { 0x01, 0x02, 0x03, 0x04 });
+ writer.WriteTextString("location");
+ writer.WriteByteString(new byte[] { 0x01, 0x02, 0x03, 0x04 });
+ writer.WriteBoolean(true);
+ writer.WriteEndArray();
+ cborEcoding = writer.Encode();
+ Assert.ThrowsException(() => CoseHashV.Deserialize(cborEcoding));
+ break;
+ // handle the correct amount, but the wrong types
+ case 3:
+ writer = new(CborConformanceMode.Strict);
+ propertyCount = 2;
+ writer.Reset();
+ writer.WriteStartArray(propertyCount);
+ writer.WriteBoolean(true);
+ writer.WriteBoolean(false);
+ writer.WriteEndArray();
+ cborEcoding = writer.Encode();
+ Assert.ThrowsException(() => CoseHashV.Deserialize(cborEcoding));
+ break;
+ // handle the correct algorithm and hash, but wrong type for location
+ case 4:
+ writer = new(CborConformanceMode.Strict);
+ propertyCount = 3;
+ writer.Reset();
+ writer.WriteStartArray(propertyCount);
+ writer.WriteInt64((long)CoseHashAlgorithm.SHA256);
+ writer.WriteByteString(SHA256.HashData([0x01, 0x02, 0x03, 0x04]));
+ writer.WriteBoolean(true);
+ writer.WriteEndArray();
+ cborEcoding = writer.Encode();
+ Assert.ThrowsException(() => CoseHashV.Deserialize(cborEcoding));
+ break;
+ // handle the correct algorithm, hash and location, but wrong type for additional data
+ case 5:
+ writer = new(CborConformanceMode.Strict);
+ propertyCount = 4;
+ writer.Reset();
+ writer.WriteStartArray(propertyCount);
+ writer.WriteInt64((long)CoseHashAlgorithm.SHA256);
+ writer.WriteByteString(SHA256.HashData([0x01, 0x02, 0x03, 0x04]));
+ writer.WriteTextString("location");
+ writer.WriteBoolean(false);
+ writer.WriteEndArray();
+ cborEcoding = writer.Encode();
+ Assert.ThrowsException(() => CoseHashV.Deserialize(cborEcoding));
+ break;
+ // handle the correct algorithm but mismatched hash length.
+ case 6:
+ writer = new(CborConformanceMode.Strict);
+ propertyCount = 2;
+ writer.Reset();
+ writer.WriteStartArray(propertyCount);
+ writer.WriteInt64((long)CoseHashAlgorithm.SHA256);
+ writer.WriteByteString([0x01, 0x02, 0x03, 0x04]);
+ writer.WriteEndArray();
+ cborEcoding = writer.Encode();
+ Assert.ThrowsException(() => CoseHashV.Deserialize(cborEcoding));
+ break;
+ // handle not starting with start array.
+ case 7:
+ writer = new(CborConformanceMode.Strict);
+ writer.Reset();
+ writer.WriteInt64((long)CoseHashAlgorithm.SHA256);
+ cborEcoding = writer.Encode();
+ Assert.ThrowsException(() => CoseHashV.Deserialize(cborEcoding));
+ break;
+ // handle null reader
+ case 8:
+ Assert.ThrowsException(() => CoseHashV.Deserialize(reader: null));
+ break;
+ // handle null data
+ case 9:
+ Assert.ThrowsException(() => CoseHashV.Deserialize(data: null));
+ break;
+ // handle invalid tstr encoding of algorithm
+ case 10:
+ writer = new(CborConformanceMode.Strict);
+ propertyCount = 2;
+ writer.Reset();
+ writer.WriteStartArray(propertyCount);
+ writer.WriteTextString("broken");
+ writer.WriteByteString(SHA256.HashData([0x1, 0x2, 0x3, 0x4]));
+ writer.WriteEndArray();
+ cborEcoding = writer.Encode();
+ Assert.ThrowsException(() => CoseHashV.Deserialize(cborEcoding));
+ break;
+ // handle a valid tstr encoding of algorithm
+ case 11:
+ writer = new(CborConformanceMode.Strict);
+ propertyCount = 2;
+ writer.Reset();
+ writer.WriteStartArray(propertyCount);
+ writer.WriteTextString(CoseHashAlgorithm.SHA256.ToString());
+ writer.WriteByteString(SHA256.HashData([0x1, 0x2, 0x3, 0x4]));
+ writer.WriteEndArray();
+ cborEcoding = writer.Encode();
+ CoseHashV properDeserialization = CoseHashV.Deserialize(cborEcoding);
+ properDeserialization.Algorithm.Should().Be(CoseHashAlgorithm.SHA256);
+ properDeserialization.HashValue.Should().BeEquivalentTo(SHA256.HashData([0x1, 0x2, 0x3, 0x4]));
+ properDeserialization.Location.Should().BeNull();
+ properDeserialization.AdditionalData.Should().BeNull();
+ break;
+ // handle an invalid algorithm integer negative.
+ case 12:
+ writer = new(CborConformanceMode.Strict);
+ propertyCount = 2;
+ writer.Reset();
+ writer.WriteStartArray(propertyCount);
+ writer.WriteInt64(-100);
+ writer.WriteByteString(SHA256.HashData([0x1, 0x2, 0x3, 0x4]));
+ writer.WriteEndArray();
+ cborEcoding = writer.Encode();
+ Assert.ThrowsException(() => CoseHashV.Deserialize(cborEcoding));
+ break;
+ // handle an invalid algorithm integer positive.
+ case 13:
+ writer = new(CborConformanceMode.Strict);
+ propertyCount = 2;
+ writer.Reset();
+ writer.WriteStartArray(propertyCount);
+ writer.WriteInt64(9999);
+ writer.WriteByteString(SHA256.HashData([0x1, 0x2, 0x3, 0x4]));
+ writer.WriteEndArray();
+ cborEcoding = writer.Encode();
+ Assert.ThrowsException(() => CoseHashV.Deserialize(cborEcoding));
+ break;
+ // handle an invalid algorithm integer positive, a random hash and pass the ignore validation flag.
+ case 14:
+ writer = new(CborConformanceMode.Strict);
+ propertyCount = 2;
+ writer.Reset();
+ writer.WriteStartArray(propertyCount);
+ writer.WriteInt64(-123);
+ writer.WriteByteString([0x1, 0x2, 0x3, 0x4]);
+ writer.WriteEndArray();
+ cborEcoding = writer.Encode();
+ CoseHashV testObject14 = CoseHashV.Deserialize(cborEcoding, disableValidation: true);
+ testObject14.Algorithm.Should().Be((CoseHashAlgorithm)(-123));
+ testObject14.HashValue.Should().BeEquivalentTo([0x1, 0x2, 0x3, 0x4]);
+ break;
+ // handle an valid algorithm integer positive, a random hash and pass the ignore validation flag.
+ case 15:
+ writer = new(CborConformanceMode.Strict);
+ propertyCount = 2;
+ writer.Reset();
+ writer.WriteStartArray(propertyCount);
+ writer.WriteInt64((long)CoseHashAlgorithm.SHA256);
+ writer.WriteByteString([0x1, 0x2, 0x3, 0x4]);
+ writer.WriteEndArray();
+ cborEcoding = writer.Encode();
+ CoseHashV testObject15 = CoseHashV.Deserialize(cborEcoding, disableValidation: true);
+ testObject15.Algorithm.Should().Be(CoseHashAlgorithm.SHA256);
+ testObject15.HashValue.Should().BeEquivalentTo([0x1, 0x2, 0x3, 0x4]);
+ break;
+ // handle 0 bytes to ReadOnlySpan
+ case 16:
+ Action test16 = () => _ = CoseHashV.Deserialize((ReadOnlySpan)[]);
+ test16.Should().Throw();
+ break;
+ // handle fuzz overflow
+ case 17:
+ byte[] fuzzData17 = Convert.FromBase64String("gjv//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////w==");
+ Action test17 = () => _ = CoseHashV.Deserialize(fuzzData17);
+ test17.Should().Throw();
+ break;
+ // handle fuzz enum overflow
+ case 18:
+ byte[] fuzzData18 = Convert.FromBase64String("hHc0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0Cg==");
+ Action test18 = () => _ = CoseHashV.Deserialize(fuzzData18);
+ test18.Should().Throw();
+ break;
+
+ default:
+ throw new InvalidDataException($"Test case {testCase} is not defined in {nameof(TestObjectManualSerializationPaths)}");
+ }
+ }
+
+ [Test]
+ public void TestMismatchedHashAlgorithmAndHashSize()
+ {
+ // arrange
+ byte[] testData = new byte[] { 0x01, 0x02, 0x03, 0x04 };
+ byte[] hash = SHA512.HashData(testData);
+ CoseHashV testObj = new CoseHashV(CoseHashAlgorithm.SHA256, byteData: hash);
+ Assert.ThrowsException(() => testObj.HashValue = hash);
+ }
+
+ [Test]
+ [TestCase(1, Description = "Set invalid hash length through setter.")]
+ [TestCase(2, Description = "Set null through setter.")]
+ [TestCase(3, Description = "Set 0-length array through setter.")]
+ public void TestSetHashWithoutAlgorithm(int testCase)
+ {
+ // arrange
+ byte[] testData = new byte[] { 0x01, 0x02, 0x03, 0x04 };
+ byte[] hash = SHA256.HashData(testData);
+ CoseHashV testObj = new CoseHashV();
+ switch (testCase)
+ {
+ case 1:
+ Assert.ThrowsException(() => testObj.HashValue = hash);
+ break;
+ case 2:
+ Assert.ThrowsException(() => testObj.HashValue = null);
+ break;
+ case 3:
+ Assert.ThrowsException(() => testObj.HashValue = new byte[0]);
+ break;
+ default:
+ throw new InvalidDataException($"Test case {testCase} is not defined in {nameof(TestSetHashWithoutAlgorithm)}");
+ }
+ }
+}
diff --git a/CoseDetachedSIgnature.Tests/CoseDetachedSignature.Tests.csproj b/CoseIndirectSignature.Tests/CoseIndirectSignature.Tests.csproj
similarity index 92%
rename from CoseDetachedSIgnature.Tests/CoseDetachedSignature.Tests.csproj
rename to CoseIndirectSignature.Tests/CoseIndirectSignature.Tests.csproj
index 60e9a155..1d4e1c2d 100644
--- a/CoseDetachedSIgnature.Tests/CoseDetachedSignature.Tests.csproj
+++ b/CoseIndirectSignature.Tests/CoseIndirectSignature.Tests.csproj
@@ -22,7 +22,7 @@
-
+
@@ -36,7 +36,7 @@
-
+
diff --git a/CoseIndirectSignature.Tests/CoseSign1MessageIndirectSignatureExtensionsTests.cs b/CoseIndirectSignature.Tests/CoseSign1MessageIndirectSignatureExtensionsTests.cs
new file mode 100644
index 00000000..eaa7363e
--- /dev/null
+++ b/CoseIndirectSignature.Tests/CoseSign1MessageIndirectSignatureExtensionsTests.cs
@@ -0,0 +1,325 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+// Ignore Spelling: Cose
+
+namespace CoseIndirectSignature.Tests;
+
+using System.IO;
+using CoseIndirectSignature;
+using CoseIndirectSignature.Exceptions;
+using CoseIndirectSignature.Extensions;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+///
+/// Class for Testing Methods of
+///
+public class CoseSign1MessageIndirectSignatureExtensionsTests
+{
+ [SetUp]
+ public void Setup()
+ {
+ }
+
+ [Test]
+ public void TestTryGetIndirectSignatureAlgorithmSuccess()
+ {
+ ICoseSigningKeyProvider coseSigningKeyProvider = SetupMockSigningKeyProvider(nameof(TestTryGetIndirectSignatureAlgorithmSuccess));
+ IndirectSignatureFactory factory = new();
+ byte[] randomBytes = new byte[50];
+ new Random().NextBytes(randomBytes);
+
+ CoseSign1Message IndirectSignature = factory.CreateIndirectSignature(randomBytes, coseSigningKeyProvider, "application/test.payload", useOldFormat: true);
+ IndirectSignature.TryGetIndirectSignatureAlgorithm(out HashAlgorithmName hashAlgorithmName).Should().BeTrue();
+ hashAlgorithmName.Should().Be(HashAlgorithmName.SHA256);
+ }
+
+ [Test]
+ public void TestTryGetIndirectSignatureAlgorithmFailure()
+ {
+ ICoseSigningKeyProvider coseSigningKeyProvider = SetupMockSigningKeyProvider(nameof(TestTryGetIndirectSignatureAlgorithmFailure));
+
+ // no content type
+ Mock removeContentTypeHeaderExtender = new Mock(MockBehavior.Strict);
+ removeContentTypeHeaderExtender.Setup(m => m.ExtendProtectedHeaders(It.IsAny())).Returns((input) =>
+ {
+ // remove ContentType
+ if (input.ContainsKey(CoseHeaderLabel.ContentType))
+ {
+ input.Remove(CoseHeaderLabel.ContentType);
+ }
+ return input;
+ });
+ removeContentTypeHeaderExtender.Setup(m => m.ExtendUnProtectedHeaders(It.IsAny())).Returns((input) => input);
+
+ // empty content type
+ Mock emptyContentTypeHeaderExtender = new Mock(MockBehavior.Strict);
+ emptyContentTypeHeaderExtender.Setup(m => m.ExtendProtectedHeaders(It.IsAny())).Returns((input) =>
+ {
+ // remove content type
+ input = removeContentTypeHeaderExtender.Object.ExtendProtectedHeaders(input);
+
+ // add content type with an empty string value
+ input.Add(CoseHeaderLabel.ContentType, string.Empty);
+
+ return input;
+ });
+ emptyContentTypeHeaderExtender.Setup(m => m.ExtendUnProtectedHeaders(It.IsAny())).Returns((input) => input);
+
+ IndirectSignatureFactory factory = new();
+ byte[] randomBytes = new byte[50];
+ new Random().NextBytes(randomBytes);
+
+ // call TryGetIndirectSignatureAlgoithm on a null message object should fail
+ CoseSign1Message? objectUnderTest = null;
+ objectUnderTest.TryGetIndirectSignatureAlgorithm(out HashAlgorithmName hashAlgorithmName).Should().BeFalse();
+
+ // call TryGetIndirectSignatureAlgorithm on a CoseSign1Message with no ContentType header should fail
+ objectUnderTest = factory.MessageFactory.CreateCoseSign1Message(randomBytes, coseSigningKeyProvider, embedPayload: true, @"application\test.output", removeContentTypeHeaderExtender.Object);
+ objectUnderTest.TryGetIndirectSignatureAlgorithm(out hashAlgorithmName).Should().BeFalse("missing content type should fail the test");
+
+ // call TryGetIndirectSignatureAlgorithm on a CoseSign1Message with empty ContentType should fail
+ objectUnderTest = factory.MessageFactory.CreateCoseSign1Message(randomBytes, coseSigningKeyProvider, embedPayload: true, @"application\test.output", emptyContentTypeHeaderExtender.Object);
+ objectUnderTest.TryGetIndirectSignatureAlgorithm(out hashAlgorithmName).Should().BeFalse("empty content type should fail the test");
+
+ // call TryGetIndirectSignatureAlgorithm on a CoseSign1Message with invalid mime type hash extension ContentType should fail
+ objectUnderTest = factory.MessageFactory.CreateCoseSign1Message(randomBytes, coseSigningKeyProvider, embedPayload: true, @"application\test.output");
+ objectUnderTest.TryGetIndirectSignatureAlgorithm(out hashAlgorithmName).Should().BeFalse("missing mime type hash extension in content type should fail the test");
+
+ // call TryGetIndirectSignatureAlgorithm on a CoseSign1Message with invalid mime type hash extension ContentType should succeed
+ objectUnderTest = factory.MessageFactory.CreateCoseSign1Message(randomBytes, coseSigningKeyProvider, embedPayload: true, @"application\test.output+hash-notavalidvalue");
+ objectUnderTest.TryGetIndirectSignatureAlgorithm(out hashAlgorithmName).Should().BeTrue("invalid mime type hash extension in content type should not fail this call");
+ }
+
+ [Test]
+ public void TestIsIndirectSignatureSuccess()
+ {
+ ICoseSigningKeyProvider coseSigningKeyProvider = SetupMockSigningKeyProvider(nameof(TestIsIndirectSignatureSuccess));
+ IndirectSignatureFactory factory = new();
+ byte[] randomBytes = new byte[50];
+ new Random().NextBytes(randomBytes);
+
+ CoseSign1Message IndirectSignature = factory.CreateIndirectSignature(randomBytes, coseSigningKeyProvider, "application/test.payload");
+ IndirectSignature.IsIndirectSignature().Should().BeTrue();
+ }
+
+ [Test]
+ public void TestIsIndirectSignatureFailure()
+ {
+ ICoseSigningKeyProvider coseSigningKeyProvider = SetupMockSigningKeyProvider(nameof(TestIsIndirectSignatureFailure));
+ IndirectSignatureFactory factory = new();
+ byte[] randomBytes = new byte[50];
+ new Random().NextBytes(randomBytes);
+
+ CoseSign1Message IndirectSignature = factory.MessageFactory.CreateCoseSign1Message(randomBytes, coseSigningKeyProvider, embedPayload: true, "application/test.payload");
+ IndirectSignature.IsIndirectSignature().Should().BeFalse();
+ }
+
+ [Test]
+ public void TestSignatureMatchesStreamSuccess()
+ {
+ ICoseSigningKeyProvider coseSigningKeyProvider = SetupMockSigningKeyProvider(nameof(TestSignatureMatchesStreamSuccess));
+ IndirectSignatureFactory factory = new();
+ byte[] randomBytes = new byte[50];
+ new Random().NextBytes(randomBytes);
+ using MemoryStream stream = new(randomBytes);
+
+ CoseSign1Message IndirectSignature = factory.CreateIndirectSignature(randomBytes, coseSigningKeyProvider, "application/test.payload");
+ IndirectSignature.SignatureMatches(stream).Should().BeTrue();
+ }
+
+ [Test]
+ public void TestSignatureMatchesStreamFailure()
+ {
+ ICoseSigningKeyProvider coseSigningKeyProvider = SetupMockSigningKeyProvider(nameof(TestSignatureMatchesStreamFailure));
+ IndirectSignatureFactory factory = new();
+ byte[] randomBytes = new byte[50];
+ byte[] randomBytes2 = new byte[50];
+ new Random().NextBytes(randomBytes);
+ new Random().NextBytes(randomBytes2);
+ using MemoryStream stream = new(randomBytes2);
+
+ // test mismatched signature
+ CoseSign1Message? IndirectSignature = factory.CreateIndirectSignature(randomBytes, coseSigningKeyProvider, "application/test.payload");
+ IndirectSignature.SignatureMatches(stream).Should().BeFalse();
+ stream.Dispose();
+ using MemoryStream stream2 = new(randomBytes);
+
+ // test invalid hash extension case
+ IndirectSignature = factory.MessageFactory.CreateCoseSign1Message(randomBytes, coseSigningKeyProvider, embedPayload: true, "application/test.payload");
+ IndirectSignature.SignatureMatches(stream2).Should().BeFalse();
+ stream2.Seek(stream2.Length, SeekOrigin.Begin);
+
+ // test null object case
+ IndirectSignature = null;
+ CoseSign1MessageIndirectSignatureExtensions.SignatureMatches(IndirectSignature, stream2).Should().BeFalse();
+ }
+
+ [Test]
+ public void TestSignatureMatchesBytesSuccess()
+ {
+ ICoseSigningKeyProvider coseSigningKeyProvider = SetupMockSigningKeyProvider(nameof(TestSignatureMatchesBytesSuccess));
+ IndirectSignatureFactory factory = new();
+ byte[] randomBytes = new byte[50];
+ new Random().NextBytes(randomBytes);
+
+ CoseSign1Message IndirectSignature = factory.CreateIndirectSignature(randomBytes, coseSigningKeyProvider, "application/test.payload");
+ IndirectSignature.SignatureMatches(randomBytes).Should().BeTrue();
+ }
+
+ [Test]
+ public void TestSignatureMatchesBytesFailure()
+ {
+ ICoseSigningKeyProvider coseSigningKeyProvider = SetupMockSigningKeyProvider(nameof(TestSignatureMatchesBytesFailure));
+ IndirectSignatureFactory factory = new();
+ byte[] randomBytes = new byte[50];
+ byte[] randomBytes2 = new byte[50];
+ new Random().NextBytes(randomBytes);
+ new Random().NextBytes(randomBytes2);
+
+ // test mismatched signature
+ CoseSign1Message? IndirectSignature = factory.CreateIndirectSignature(randomBytes, coseSigningKeyProvider, "application/test.payload");
+ IndirectSignature.SignatureMatches(randomBytes2).Should().BeFalse();
+
+ // test invalid hash extension case
+ IndirectSignature = factory.MessageFactory.CreateCoseSign1Message(randomBytes, coseSigningKeyProvider, embedPayload: true, "application/test.payload");
+ IndirectSignature.SignatureMatches(randomBytes).Should().BeFalse();
+
+ // test null object case
+ IndirectSignature = null;
+ CoseSign1MessageIndirectSignatureExtensions.SignatureMatches(IndirectSignature, randomBytes).Should().BeFalse();
+ }
+
+ [Test]
+ public void TestTryGetHashAlgorithmSuccess()
+ {
+ ICoseSigningKeyProvider coseSigningKeyProvider = SetupMockSigningKeyProvider(nameof(TestTryGetHashAlgorithmSuccess));
+ IndirectSignatureFactory factory = new();
+ byte[] randomBytes = new byte[50];
+ new Random().NextBytes(randomBytes);
+
+ CoseSign1Message IndirectSignature = factory.CreateIndirectSignature(randomBytes, coseSigningKeyProvider, "application/test.payload", useOldFormat: true);
+ IndirectSignature.TryGetHashAlgorithm(out HashAlgorithm? hashAlgorithm).Should().BeTrue();
+ hashAlgorithm.Should().NotBeNull();
+ hashAlgorithm.Should().BeAssignableTo();
+ }
+
+ [Test]
+ [TestCase(1, Description = "Success return")]
+ [TestCase(2, Description = "Invalid CoseHashV - ContentType")]
+ [TestCase(3, Description = "Invalid CoseHashV - detached signature")]
+ [TestCase(4, Description = "Invalid CoseHshV - invalid content")]
+ [TestCase(5, Description = "Success TryGet")]
+ [TestCase(6, Description = "Failure TryGet")]
+ [TestCase(7, Description = "Get - Null")]
+ [TestCase(8, Description = "TryGet - Null")]
+ public void TestGetCoseHashVScenarios(int testCase)
+ {
+ ICoseSigningKeyProvider coseSigningKeyProvider = SetupMockSigningKeyProvider(nameof(TestSignatureMatchesBytesFailure));
+ IndirectSignatureFactory signaturefactory = new();
+ CoseSign1MessageFactory messageFactory = new();
+ byte[] randomBytes = new byte[50];
+ new Random().NextBytes(randomBytes);
+
+ switch (testCase)
+ {
+ // test the fetching case
+ case 1:
+ CoseSign1Message? testObj1 = signaturefactory.CreateIndirectSignature(randomBytes, coseSigningKeyProvider, "application/test.payload");
+ CoseHashV hashObject = testObj1.GetCoseHashV();
+ hashObject.ContentMatches(randomBytes).Should().BeTrue();
+ break;
+ // test the invalid content type case.
+ case 2:
+ CoseSign1Message? testObj2 = messageFactory.CreateCoseSign1Message(randomBytes, coseSigningKeyProvider, embedPayload: true, "application/test.payload+hash-sha256");
+ Action test2 = () => testObj2.GetCoseHashV();
+ test2.Should().Throw();
+ break;
+ // test detached signature.
+ case 3:
+ CoseSign1Message? testObj3 = messageFactory.CreateCoseSign1Message(randomBytes, coseSigningKeyProvider, embedPayload: false, "application/test.payload+hash-sha256");
+ Action test3 = () => testObj3.GetCoseHashV();
+ test3.Should().Throw();
+ break;
+ // test invalid content
+ case 4:
+ CoseSign1Message? testObj4 = messageFactory.CreateCoseSign1Message(randomBytes, coseSigningKeyProvider, embedPayload: true, "application/test.payload+cose-hash-v");
+ Action test4 = () => testObj4.GetCoseHashV();
+ test4.Should().Throw();
+ break;
+ // tryget success
+ case 5:
+ CoseSign1Message? testObj5 = signaturefactory.CreateIndirectSignature(randomBytes, coseSigningKeyProvider, "application/test.payload");
+ testObj5.TryGetCoseHashV(out CoseHashV? hashObject5).Should().BeTrue();
+ hashObject5.ContentMatches(randomBytes).Should().BeTrue();
+ break;
+ // tryget failure
+ case 6:
+ CoseSign1Message? testObj6 = messageFactory.CreateCoseSign1Message(randomBytes, coseSigningKeyProvider, embedPayload: false, "application/test.payload+hash-sha256");
+ testObj6.TryGetCoseHashV(out _).Should().BeFalse();
+ break;
+ // get null
+ case 7:
+#nullable disable
+ Action test7 = () => ((CoseSign1Message)null).GetCoseHashV();
+#nullable enable
+ test7.Should().Throw();
+ break;
+ // tryget null
+ case 8:
+#nullable disable
+ ((CoseSign1Message)null).TryGetCoseHashV(out _).Should().BeFalse();
+#nullable enable
+ break;
+ default:
+ throw new InvalidDataException($"TestCase {testCase} is not defined in {nameof(TestGetCoseHashVScenarios)}");
+ }
+ }
+
+ [Test]
+ public void TestTryGetHashAlgorithmFailure()
+ {
+ ICoseSigningKeyProvider coseSigningKeyProvider = SetupMockSigningKeyProvider(nameof(TestTryGetHashAlgorithmFailure));
+ IndirectSignatureFactory factory = new();
+ byte[] randomBytes = new byte[50];
+ byte[] randomBytes2 = new byte[50];
+ new Random().NextBytes(randomBytes);
+ new Random().NextBytes(randomBytes2);
+
+ // Fail to extract a hash algorithm name
+ CoseSign1Message? IndirectSignature = factory.MessageFactory.CreateCoseSign1Message(randomBytes, coseSigningKeyProvider, embedPayload: true, "application/test.payload");
+ IndirectSignature.TryGetHashAlgorithm(out HashAlgorithm? hashAlgorithm).Should().BeFalse();
+ hashAlgorithm.Should().BeNull();
+
+ // COSE Sign1 Indirect signature case with other things being valid
+ // content should be null in this case.
+ IndirectSignature = factory.MessageFactory.CreateCoseSign1Message(randomBytes, coseSigningKeyProvider, embedPayload: false, "application/test.payload+hash-sha256");
+ IndirectSignature.TryGetHashAlgorithm(out hashAlgorithm).Should().BeFalse();
+ hashAlgorithm.Should().BeNull();
+
+ // Invalid hash definition case
+ IndirectSignature = factory.MessageFactory.CreateCoseSign1Message(randomBytes, coseSigningKeyProvider, embedPayload: true, "application/test.payload+hash-notavalidhashalgorithm");
+ IndirectSignature.TryGetHashAlgorithm(out hashAlgorithm).Should().BeFalse();
+ hashAlgorithm.Should().BeNull();
+
+ // test null object case
+ IndirectSignature = null;
+ CoseSign1MessageIndirectSignatureExtensions.TryGetHashAlgorithm(IndirectSignature, out hashAlgorithm).Should().BeFalse();
+ hashAlgorithm.Should().BeNull();
+ }
+
+ private ICoseSigningKeyProvider SetupMockSigningKeyProvider(string testName)
+ {
+ Mock mockedSignerKeyProvider = new(MockBehavior.Strict);
+ X509Certificate2 selfSignedCertWithRSA = TestCertificateUtils.CreateCertificate(testName);
+
+ mockedSignerKeyProvider.Setup(x => x.GetProtectedHeaders()).Returns(null);
+ mockedSignerKeyProvider.Setup(x => x.GetUnProtectedHeaders()).Returns(null);
+ mockedSignerKeyProvider.Setup(x => x.HashAlgorithm).Returns(HashAlgorithmName.SHA256);
+ mockedSignerKeyProvider.Setup(x => x.GetECDsaKey(It.IsAny())).Returns(null);
+ mockedSignerKeyProvider.Setup(x => x.GetRSAKey(It.IsAny())).Returns(selfSignedCertWithRSA.GetRSAPrivateKey());
+ mockedSignerKeyProvider.Setup(x => x.IsRSA).Returns(true);
+
+ return mockedSignerKeyProvider.Object;
+ }
+}
diff --git a/CoseIndirectSignature.Tests/IndirectSignatureFactoryTests.cs b/CoseIndirectSignature.Tests/IndirectSignatureFactoryTests.cs
new file mode 100644
index 00000000..aaedbb51
--- /dev/null
+++ b/CoseIndirectSignature.Tests/IndirectSignatureFactoryTests.cs
@@ -0,0 +1,260 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace CoseIndirectSignature.Tests;
+
+///
+/// Class for Testing Methods of
+///
+public class IndirectSignatureFactoryTests
+{
+ [SetUp]
+ public void Setup()
+ {
+ }
+
+ [Test]
+ public void TestConstructors()
+ {
+ Mock mockFactory = new(MockBehavior.Strict);
+ using IndirectSignatureFactory factory = new();
+ using IndirectSignatureFactory factory2 = new(HashAlgorithmName.SHA384);
+ using IndirectSignatureFactory factory3 = new(HashAlgorithmName.SHA512, mockFactory.Object);
+
+ factory.HashAlgorithm.Should().BeAssignableTo();
+ factory.HashAlgorithmName.Should().Be(HashAlgorithmName.SHA256);
+ factory.MessageFactory.Should().BeOfType();
+
+ factory2.HashAlgorithm.Should().BeAssignableTo();
+ factory2.HashAlgorithmName.Should().Be(HashAlgorithmName.SHA384);
+ factory2.MessageFactory.Should().BeOfType();
+
+ factory3.HashAlgorithm.Should().BeAssignableTo();
+ factory3.HashAlgorithmName.Should().Be(HashAlgorithmName.SHA512);
+ factory3.MessageFactory.Should().Be(mockFactory.Object);
+ }
+
+ [Test]
+ public async Task TestCreateIndirectSignatureAsync()
+ {
+ ICoseSigningKeyProvider coseSigningKeyProvider = SetupMockSigningKeyProvider(nameof(TestCreateIndirectSignatureAsync));
+ using IndirectSignatureFactory factory = new();
+ byte[] randomBytes = new byte[50];
+ new Random().NextBytes(randomBytes);
+ using MemoryStream memStream = new(randomBytes);
+
+ // test the sync method
+ Assert.Throws(() => factory.CreateIndirectSignature(randomBytes, coseSigningKeyProvider, string.Empty));
+ CoseSign1Message IndirectSignature = factory.CreateIndirectSignature(randomBytes, coseSigningKeyProvider, "application/test.payload");
+ IndirectSignature.ProtectedHeaders.ContainsKey(CoseHeaderLabel.ContentType).Should().BeTrue();
+ IndirectSignature.ProtectedHeaders[CoseHeaderLabel.ContentType].GetValueAsString().Should().Be("application/test.payload+cose-hash-v");
+ IndirectSignature.SignatureMatches(randomBytes).Should().BeTrue();
+
+ Assert.Throws(() => factory.CreateIndirectSignature(memStream, coseSigningKeyProvider, string.Empty));
+ memStream.Seek(0, SeekOrigin.Begin);
+ CoseSign1Message IndirectSignature2 = factory.CreateIndirectSignature(memStream, coseSigningKeyProvider, "application/test.payload");
+ IndirectSignature2.ProtectedHeaders.ContainsKey(CoseHeaderLabel.ContentType).Should().BeTrue();
+ IndirectSignature2.ProtectedHeaders[CoseHeaderLabel.ContentType].GetValueAsString().Should().Be("application/test.payload+cose-hash-v");
+ IndirectSignature2.SignatureMatches(randomBytes).Should().BeTrue();
+ memStream.Seek(0, SeekOrigin.Begin);
+
+ // test the async methods
+ Assert.ThrowsAsync(() => factory.CreateIndirectSignatureAsync(randomBytes, coseSigningKeyProvider, string.Empty));
+ CoseSign1Message IndirectSignature3 = await factory.CreateIndirectSignatureAsync(randomBytes, coseSigningKeyProvider, "application/test.payload");
+ IndirectSignature3.ProtectedHeaders.ContainsKey(CoseHeaderLabel.ContentType).Should().BeTrue();
+ IndirectSignature3.ProtectedHeaders[CoseHeaderLabel.ContentType].GetValueAsString().Should().Be("application/test.payload+cose-hash-v");
+ IndirectSignature3.SignatureMatches(randomBytes).Should().BeTrue();
+
+ Assert.ThrowsAsync(() => factory.CreateIndirectSignatureAsync(memStream, coseSigningKeyProvider, string.Empty));
+ memStream.Seek(0, SeekOrigin.Begin);
+ CoseSign1Message IndirectSignature4 = await factory.CreateIndirectSignatureAsync(memStream, coseSigningKeyProvider, "application/test.payload");
+ IndirectSignature4.ProtectedHeaders.ContainsKey(CoseHeaderLabel.ContentType).Should().BeTrue();
+ IndirectSignature4.ProtectedHeaders[CoseHeaderLabel.ContentType].GetValueAsString().Should().Be("application/test.payload+cose-hash-v");
+ IndirectSignature4.SignatureMatches(randomBytes).Should().BeTrue();
+ memStream.Seek(0, SeekOrigin.Begin);
+ }
+
+ [Test]
+ public async Task TestCreateIndirectSignatureHashProvidedAsync()
+ {
+ ICoseSigningKeyProvider coseSigningKeyProvider = SetupMockSigningKeyProvider(nameof(TestCreateIndirectSignatureHashProvidedAsync));
+ using IndirectSignatureFactory factory = new();
+ byte[] randomBytes = new byte[50];
+ new Random().NextBytes(randomBytes);
+ using HashAlgorithm hasher = CoseSign1MessageIndirectSignatureExtensions.CreateHashAlgorithmFromName(factory.HashAlgorithmName)
+ ?? throw new Exception($"Failed to get hash algorithm from {nameof(CoseSign1MessageIndirectSignatureExtensions.CreateHashAlgorithmFromName)}");
+ byte[] hash = hasher!.ComputeHash(randomBytes);
+ using MemoryStream hashStream = new(hash);
+
+ // test the sync method
+ Assert.Throws(() => factory.CreateIndirectSignatureFromHash(hash, coseSigningKeyProvider, string.Empty));
+ CoseSign1Message IndirectSignature = factory.CreateIndirectSignatureFromHash(hash, coseSigningKeyProvider, "application/test.payload");
+ IndirectSignature.ProtectedHeaders.ContainsKey(CoseHeaderLabel.ContentType).Should().BeTrue();
+ IndirectSignature.ProtectedHeaders[CoseHeaderLabel.ContentType].GetValueAsString().Should().Be("application/test.payload+cose-hash-v");
+ IndirectSignature.SignatureMatches(randomBytes).Should().BeTrue();
+
+ Assert.Throws(() => factory.CreateIndirectSignature(hashStream, coseSigningKeyProvider, string.Empty));
+ hashStream.Seek(0, SeekOrigin.Begin);
+ CoseSign1Message IndirectSignature2 = factory.CreateIndirectSignatureFromHash(hashStream, coseSigningKeyProvider, "application/test.payload");
+ IndirectSignature2.ProtectedHeaders.ContainsKey(CoseHeaderLabel.ContentType).Should().BeTrue();
+ IndirectSignature2.ProtectedHeaders[CoseHeaderLabel.ContentType].GetValueAsString().Should().Be("application/test.payload+cose-hash-v");
+ IndirectSignature2.SignatureMatches(randomBytes).Should().BeTrue();
+ hashStream.Seek(0, SeekOrigin.Begin);
+
+ // test the async methods
+ Assert.ThrowsAsync(() => factory.CreateIndirectSignatureFromHashAsync(hash, coseSigningKeyProvider, string.Empty));
+ CoseSign1Message IndirectSignature3 = await factory.CreateIndirectSignatureFromHashAsync(hash, coseSigningKeyProvider, "application/test.payload");
+ IndirectSignature3.ProtectedHeaders.ContainsKey(CoseHeaderLabel.ContentType).Should().BeTrue();
+ IndirectSignature3.ProtectedHeaders[CoseHeaderLabel.ContentType].GetValueAsString().Should().Be("application/test.payload+cose-hash-v");
+ IndirectSignature3.SignatureMatches(randomBytes).Should().BeTrue();
+
+ Assert.ThrowsAsync(() => factory.CreateIndirectSignatureFromHashAsync(hashStream, coseSigningKeyProvider, string.Empty));
+ hashStream.Seek(0, SeekOrigin.Begin);
+ CoseSign1Message IndirectSignature4 = await factory.CreateIndirectSignatureFromHashAsync(hashStream, coseSigningKeyProvider, "application/test.payload");
+ IndirectSignature4.ProtectedHeaders.ContainsKey(CoseHeaderLabel.ContentType).Should().BeTrue();
+ IndirectSignature4.ProtectedHeaders[CoseHeaderLabel.ContentType].GetValueAsString().Should().Be("application/test.payload+cose-hash-v");
+ IndirectSignature4.SignatureMatches(randomBytes).Should().BeTrue();
+ hashStream.Seek(0, SeekOrigin.Begin);
+ }
+
+ [Test]
+ public async Task TestCreateIndirectSignatureBytesAsync()
+ {
+ ICoseSigningKeyProvider coseSigningKeyProvider = SetupMockSigningKeyProvider(nameof(TestCreateIndirectSignatureBytesAsync));
+ using IndirectSignatureFactory factory = new();
+ byte[] randomBytes = new byte[50];
+ new Random().NextBytes(randomBytes);
+ using MemoryStream memStream = new(randomBytes);
+
+ // test the sync method
+ Assert.Throws(() => factory.CreateIndirectSignatureBytes(randomBytes, coseSigningKeyProvider, string.Empty));
+ CoseSign1Message IndirectSignature = CoseMessage.DecodeSign1(factory.CreateIndirectSignatureBytes(randomBytes, coseSigningKeyProvider, "application/test.payload").ToArray());
+ IndirectSignature.ProtectedHeaders.ContainsKey(CoseHeaderLabel.ContentType).Should().BeTrue();
+ IndirectSignature.ProtectedHeaders[CoseHeaderLabel.ContentType].GetValueAsString().Should().Be("application/test.payload+cose-hash-v");
+ IndirectSignature.SignatureMatches(randomBytes).Should().BeTrue();
+
+ Assert.Throws(() => factory.CreateIndirectSignatureBytes(memStream, coseSigningKeyProvider, string.Empty));
+ memStream.Seek(0, SeekOrigin.Begin);
+ CoseSign1Message IndirectSignature2 = CoseMessage.DecodeSign1(factory.CreateIndirectSignatureBytes(memStream, coseSigningKeyProvider, "application/test.payload").ToArray());
+ IndirectSignature2.ProtectedHeaders.ContainsKey(CoseHeaderLabel.ContentType).Should().BeTrue();
+ IndirectSignature2.ProtectedHeaders[CoseHeaderLabel.ContentType].GetValueAsString().Should().Be("application/test.payload+cose-hash-v");
+ IndirectSignature2.SignatureMatches(randomBytes).Should().BeTrue();
+ memStream.Seek(0, SeekOrigin.Begin);
+
+ // test the async methods
+ Assert.ThrowsAsync(() => factory.CreateIndirectSignatureBytesAsync(randomBytes, coseSigningKeyProvider, string.Empty));
+ CoseSign1Message IndirectSignature3 = CoseMessage.DecodeSign1((await factory.CreateIndirectSignatureBytesAsync(randomBytes, coseSigningKeyProvider, "application/test.payload")).ToArray());
+ IndirectSignature3.ProtectedHeaders.ContainsKey(CoseHeaderLabel.ContentType).Should().BeTrue();
+ IndirectSignature3.ProtectedHeaders[CoseHeaderLabel.ContentType].GetValueAsString().Should().Be("application/test.payload+cose-hash-v");
+ IndirectSignature3.SignatureMatches(randomBytes).Should().BeTrue();
+
+ Assert.ThrowsAsync(() => factory.CreateIndirectSignatureBytesAsync(memStream, coseSigningKeyProvider, string.Empty));
+ memStream.Seek(0, SeekOrigin.Begin);
+ CoseSign1Message IndirectSignature4 = CoseMessage.DecodeSign1((await factory.CreateIndirectSignatureBytesAsync(memStream, coseSigningKeyProvider, "application/test.payload")).ToArray());
+ IndirectSignature4.ProtectedHeaders.ContainsKey(CoseHeaderLabel.ContentType).Should().BeTrue();
+ IndirectSignature4.ProtectedHeaders[CoseHeaderLabel.ContentType].GetValueAsString().Should().Be("application/test.payload+cose-hash-v");
+ memStream.Seek(0, SeekOrigin.Begin);
+ IndirectSignature4.SignatureMatches(memStream).Should().BeTrue();
+ }
+
+ [Test]
+ public async Task TestCreateIndirectSignatureBytesHashProvidedAsync()
+ {
+ ICoseSigningKeyProvider coseSigningKeyProvider = SetupMockSigningKeyProvider(nameof(TestCreateIndirectSignatureBytesHashProvidedAsync));
+ using IndirectSignatureFactory factory = new();
+ byte[] randomBytes = new byte[50];
+ new Random().NextBytes(randomBytes);
+ using HashAlgorithm hasher = CoseSign1MessageIndirectSignatureExtensions.CreateHashAlgorithmFromName(factory.HashAlgorithmName)
+ ?? throw new Exception($"Failed to get hash algorithm from {nameof(CoseSign1MessageIndirectSignatureExtensions.CreateHashAlgorithmFromName)}");
+ byte[] hash = hasher!.ComputeHash(randomBytes);
+ using MemoryStream hashStream = new(hash);
+
+ // test the sync method
+ Assert.Throws(() => factory.CreateIndirectSignatureBytesFromHash(hash, coseSigningKeyProvider, string.Empty));
+ CoseSign1Message IndirectSignature = CoseMessage.DecodeSign1(factory.CreateIndirectSignatureBytesFromHash(hash, coseSigningKeyProvider, "application/test.payload").ToArray());
+ IndirectSignature.ProtectedHeaders.ContainsKey(CoseHeaderLabel.ContentType).Should().BeTrue();
+ IndirectSignature.ProtectedHeaders[CoseHeaderLabel.ContentType].GetValueAsString().Should().Be("application/test.payload+cose-hash-v");
+ IndirectSignature.SignatureMatches(randomBytes).Should().BeTrue();
+
+ Assert.Throws(() => factory.CreateIndirectSignatureBytesFromHash(hashStream, coseSigningKeyProvider, string.Empty));
+ hashStream.Seek(0, SeekOrigin.Begin);
+ CoseSign1Message IndirectSignature2 = CoseMessage.DecodeSign1(factory.CreateIndirectSignatureBytesFromHash(hashStream, coseSigningKeyProvider, "application/test.payload").ToArray());
+ IndirectSignature2.ProtectedHeaders.ContainsKey(CoseHeaderLabel.ContentType).Should().BeTrue();
+ IndirectSignature2.ProtectedHeaders[CoseHeaderLabel.ContentType].GetValueAsString().Should().Be("application/test.payload+cose-hash-v");
+ IndirectSignature2.SignatureMatches(randomBytes).Should().BeTrue();
+ hashStream.Seek(0, SeekOrigin.Begin);
+
+ // test the async methods
+ Assert.ThrowsAsync(() => factory.CreateIndirectSignatureBytesFromHashAsync(hash, coseSigningKeyProvider, string.Empty));
+ CoseSign1Message IndirectSignature3 = CoseMessage.DecodeSign1((await factory.CreateIndirectSignatureBytesFromHashAsync(hash, coseSigningKeyProvider, "application/test.payload")).ToArray());
+ IndirectSignature3.ProtectedHeaders.ContainsKey(CoseHeaderLabel.ContentType).Should().BeTrue();
+ IndirectSignature3.ProtectedHeaders[CoseHeaderLabel.ContentType].GetValueAsString().Should().Be("application/test.payload+cose-hash-v");
+ IndirectSignature3.SignatureMatches(randomBytes).Should().BeTrue();
+
+ Assert.ThrowsAsync(() => factory.CreateIndirectSignatureBytesFromHashAsync(hashStream, coseSigningKeyProvider, string.Empty));
+ hashStream.Seek(0, SeekOrigin.Begin);
+ CoseSign1Message IndirectSignature4 = CoseMessage.DecodeSign1((await factory.CreateIndirectSignatureBytesFromHashAsync(hashStream, coseSigningKeyProvider, "application/test.payload")).ToArray());
+ IndirectSignature4.ProtectedHeaders.ContainsKey(CoseHeaderLabel.ContentType).Should().BeTrue();
+ IndirectSignature4.ProtectedHeaders[CoseHeaderLabel.ContentType].GetValueAsString().Should().Be("application/test.payload+cose-hash-v");
+ hashStream.Seek(0, SeekOrigin.Begin);
+ IndirectSignature4.SignatureMatches(randomBytes).Should().BeTrue();
+ }
+
+ [Test]
+ public void TestCreateIndirectSignatureMd5Failure()
+ {
+ ICoseSigningKeyProvider coseSigningKeyProvider = SetupMockSigningKeyProvider(nameof(TestCreateIndirectSignatureMd5Failure));
+ Action act = () => { IndirectSignatureFactory factory = new(HashAlgorithmName.MD5); };
+ act.Should().Throw();
+ }
+
+ [Test]
+ public void TestCreateIndirectSignatureMd5HashProvidedFailure()
+ {
+ ICoseSigningKeyProvider coseSigningKeyProvider = SetupMockSigningKeyProvider(nameof(TestCreateIndirectSignatureMd5HashProvidedFailure));
+ using IndirectSignatureFactory factory = new();
+ byte[] randomBytes = new byte[50];
+ new Random().NextBytes(randomBytes);
+ using HashAlgorithm hasher = CoseSign1MessageIndirectSignatureExtensions.CreateHashAlgorithmFromName(HashAlgorithmName.MD5)
+ ?? throw new Exception($"Failed to get hash algorithm from {nameof(CoseSign1MessageIndirectSignatureExtensions.CreateHashAlgorithmFromName)}");
+ byte[] hash = hasher!.ComputeHash(randomBytes);
+
+ // test the sync method
+ Assert.Throws(() => factory.CreateIndirectSignatureFromHash(hash, coseSigningKeyProvider, string.Empty));
+ Assert.Throws(() => factory.CreateIndirectSignatureBytesFromHash(hash, coseSigningKeyProvider, "application/test.payload"));
+ }
+
+ [Test]
+ public void TestCreateIndirectSignatureAlreadyProvided()
+ {
+ ICoseSigningKeyProvider coseSigningKeyProvider = SetupMockSigningKeyProvider(nameof(TestCreateIndirectSignatureAlreadyProvided));
+ using IndirectSignatureFactory factory = new();
+ byte[] randomBytes = new byte[50];
+ new Random().NextBytes(randomBytes);
+ using HashAlgorithm hasher = CoseSign1MessageIndirectSignatureExtensions.CreateHashAlgorithmFromName(factory.HashAlgorithmName)
+ ?? throw new Exception($"Failed to get hash algorithm from {nameof(CoseSign1MessageIndirectSignatureExtensions.CreateHashAlgorithmFromName)}");
+ ReadOnlyMemory hash = hasher!.ComputeHash(randomBytes);
+
+ // test the sync method
+ Assert.Throws(() => factory.CreateIndirectSignature(hash, coseSigningKeyProvider, string.Empty));
+ CoseSign1Message IndirectSignature = CoseMessage.DecodeSign1(factory.CreateIndirectSignatureBytes(randomBytes, coseSigningKeyProvider, "application/test.payload").ToArray());
+ IndirectSignature.ProtectedHeaders.ContainsKey(CoseHeaderLabel.ContentType).Should().BeTrue();
+ IndirectSignature.ProtectedHeaders[CoseHeaderLabel.ContentType].GetValueAsString().Should().Be("application/test.payload+cose-hash-v");
+ IndirectSignature.SignatureMatches(randomBytes).Should().BeTrue();
+ }
+
+ private ICoseSigningKeyProvider SetupMockSigningKeyProvider(string testName)
+ {
+ Mock mockedSignerKeyProvider = new(MockBehavior.Strict);
+ X509Certificate2 selfSignedCertWithRSA = TestCertificateUtils.CreateCertificate(testName);
+
+ mockedSignerKeyProvider.Setup(x => x.GetProtectedHeaders()).Returns(null);
+ mockedSignerKeyProvider.Setup(x => x.GetUnProtectedHeaders()).Returns(null);
+ mockedSignerKeyProvider.Setup(x => x.HashAlgorithm).Returns(HashAlgorithmName.SHA256);
+ mockedSignerKeyProvider.Setup(x => x.GetECDsaKey(It.IsAny())).Returns(null);
+ mockedSignerKeyProvider.Setup(x => x.GetRSAKey(It.IsAny())).Returns(selfSignedCertWithRSA.GetRSAPrivateKey());
+ mockedSignerKeyProvider.Setup(x => x.IsRSA).Returns(true);
+
+ return mockedSignerKeyProvider.Object;
+ }
+}
diff --git a/CoseDetachedSIgnature.Tests/Usings.cs b/CoseIndirectSignature.Tests/Usings.cs
similarity index 84%
rename from CoseDetachedSIgnature.Tests/Usings.cs
rename to CoseIndirectSignature.Tests/Usings.cs
index 9b3cc5ba..85677a11 100644
--- a/CoseDetachedSIgnature.Tests/Usings.cs
+++ b/CoseIndirectSignature.Tests/Usings.cs
@@ -4,8 +4,8 @@
global using System.Security.Cryptography;
global using System.Security.Cryptography.Cose;
global using System.Security.Cryptography.X509Certificates;
-global using CoseDetachedSignature;
-global using CoseDetachedSignature.Extensions;
+global using CoseIndirectSignature;
+global using CoseIndirectSignature.Extensions;
global using CoseSign1;
global using CoseSign1.Abstractions.Interfaces;
global using CoseSign1.Interfaces;
diff --git a/CoseDetachedSignature.md b/CoseIndirectSignature.md
similarity index 52%
rename from CoseDetachedSignature.md
rename to CoseIndirectSignature.md
index 90a2bc33..f2b76069 100644
--- a/CoseDetachedSignature.md
+++ b/CoseIndirectSignature.md
@@ -1,37 +1,37 @@
-# [CoseDetachedSignature](https://github.com/microsoft/CoseSignTool/tree/main/CoseDetachedSignature)
-**CoseDetachedSignature** is a .NET Standard 2.0 library containing a concrete implementation which embeds the hash of an object into the .Content of a CoseSign1Message object and updates the ContentType field to include a new content type extension of `+hash-{algorithm}` to indicate the content is a hash of the original content type. This functionality is exposed via a factory pattern in [**DetachedSignatureFactory**](https://github.com/microsoft/CoseSignTool/tree/main/CoseDetachedSignature/CoseSignatureFactory.cs) for use with Supply Chain Integrity Transparency and Trust [SCITT](https://scitt.io/).
+# [CoseIndirectSignature](https://github.com/microsoft/CoseSignTool/tree/main/CoseIndirectSignature)
+**CoseIndirectSignature** is a .NET Standard 2.0 library containing a concrete implementation which embeds the hash of an object into the .Content of a CoseSign1Message object and updates the ContentType field to include a new content type extension of `+cose-hash-v` to indicate the content is a cose_hash_v structure of the original content type. This functionality is exposed via a factory pattern in [**IndirectSignatureFactory**](https://github.com/microsoft/CoseSignTool/tree/main/CoseIndirectSignature/IndirectSignatureFactory.cs) for use with Supply Chain Integrity Transparency and Trust [SCITT](https://scitt.io/).
## Dependencies
-**CoseDetachedSignature** has the following package dependencies
+**CoseIndirecSignature** has the following package dependencies
* CoseSign1
## Creation
This library includes the following classes:
-### [**DetachedSignatureFactory**](https://github.com/microsoft/CoseSignTool/tree/main/CoseDetachedSignature/DetachedSignatureFactory.cs)
-This class implements the creation of a CoseSign1Message object leveraging CoseSign1 which conforms to an embedded DetachedSignature format for content which is needed to be submitted to SCITT for receipt generation.
+### [**IndirectSignatureFactory**](https://github.com/microsoft/CoseSignTool/tree/main/CoseIndirectSignature/IndirectSignatureFactory.cs)
+This class implements the creation of a CoseSign1Message object leveraging CoseSign1 which conforms to an embedded IndirectSignature format for content which is needed to be submitted to SCITT for receipt generation.
There are various `Create*` methods which support both synchronous and asynchronous operations.
#### Example
```
-using CoseDetachedSignature;
+using CoseIndirectSignature;
using CoseSign1;
using CoseSign1.Certificates.Local;
...
-using DetachedSignatureFactory factory = new();
+using IndirectSignatureFactory factory = new();
byte[] randomBytes = new byte[50];
new Random().NextBytes(randomBytes);
using MemoryStream memStream = new(randomBytes);
X509Certificate2CoseSigningKeyProvider coseSigningKeyProvider = new(...);
-CoseSign1Message detachedSignature = factory.CreateDetachedSignature(payload: randomBytes, signingKeyProvider: coseSigningKeyProvider, contentType: "application/test.payload");
+CoseSign1Message indirectSignature = factory.CreateIndirectSignature(payload: randomBytes, signingKeyProvider: coseSigningKeyProvider, contentType: "application/test.payload");
```
## Validation
-To help with validation of DetachedSignatures which are embedded within a CoseSign1Message object, [CoseSign1MessageDetachedSignatureExtensions](https://github.com/microsoft/CoseSignTool/tree/main/CoseDetachedSignature/Extensions/CoseSign1MessageDetachedSignatureExtensions.cs) C# extension class is provided to add a `SignatureMatches(...)` overload that accepts **Stream** or **Byte[]** content.
+To help with validation of IndirectSignatures which are embedded within a CoseSign1Message object, [CoseSign1MessageIndirectSignatureExtensions](https://github.com/microsoft/CoseSignTool/tree/main/CoseIndirectSignature/Extensions/CoseSign1MessageIndirectSignatureExtensions.cs) C# extension class is provided to add a `SignatureMatches(...)` overload that accepts **Stream** or **Byte[]** content.
#### Example:
```
-using CoseDetachedSignature.Extensions;
+using CoseIndirectSignature.Extensions;
using CoseSign1;
using CoseSign1.Certificates.Local;
using System.IO;
@@ -41,7 +41,7 @@ using System.IO;
Stream coseFileStream = File.OpenRead(...);
Stream originalContentStream = File.OpenRead(...);
CoseSign1Message message = CoseMessage.DecodeSign1(coseFileStream);
-if(message.IsDetachedSignature())
+if(message.IsIndirectSignature())
{
return message.SignatureMatches(originalContentStream);
}
diff --git a/CoseIndirectSignature/CoseAlgorithms.cs b/CoseIndirectSignature/CoseAlgorithms.cs
new file mode 100644
index 00000000..8f8dba36
--- /dev/null
+++ b/CoseIndirectSignature/CoseAlgorithms.cs
@@ -0,0 +1,56 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+// Ignore Spelling: Cose
+
+namespace CoseIndirectSignature;
+
+using System.ComponentModel;
+
+///
+/// COSE HashAlgorithm values from the IANA COSE Algorithms registry found at https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+///
+public enum CoseHashAlgorithm : long
+{
+ ///
+ /// Reserved
+ ///
+ [Browsable(false)]
+ Reserved = 0,
+ ///
+ /// SHA-1 Hash Algorithm
+ ///
+ /// SHA1 is not recommended for new data.
+ [Obsolete("Use CoseAlgorithm.SHA256 instead")]
+ SHA1 = -14,
+ ///
+ /// SHA-256 Truncated to 64 bits Hash Algorithm
+ ///
+ /// SHA256 truncated to 64 bits is not recommended for new data.
+ [Obsolete("Use CoseAlgorithm.SHA256 instead")]
+ SHA256Trunc64 = -15,
+ ///
+ /// SHA-256 Hash Algorithm
+ ///
+ SHA256 = -16,
+ ///
+ /// SHA-512 Truncated to 256 bits Hash Algorithm
+ ///
+ SHA512Truc256 = -17,
+ ///
+ /// SHAKE128 Hash Algorithm
+ ///
+ SHAKE128 = -18,
+ ///
+ /// SHA384 Hash Algorithm
+ ///
+ SHA384 = -43,
+ ///
+ /// SHA512 Hash Algorithm
+ ///
+ SHA512 = -44,
+ ///
+ /// SHAKE256 Hash Algorithm
+ ///
+ SHAKE256 = -45,
+}
diff --git a/CoseIndirectSignature/CoseHashV.cs b/CoseIndirectSignature/CoseHashV.cs
new file mode 100644
index 00000000..29d35dca
--- /dev/null
+++ b/CoseIndirectSignature/CoseHashV.cs
@@ -0,0 +1,562 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+// Ignore Spelling: Cose Deserialize
+
+namespace CoseIndirectSignature;
+using System;
+using System.Formats.Cbor;
+using System.IO;
+using System.Threading.Tasks;
+using CoseIndirectSignature.Exceptions;
+using CoseSign1.Abstractions.Exceptions;
+
+///
+/// Represents the COSE_Hash_V structure as suggested in https://tools.ietf.org/html/rfc9054#section-2.1
+///
+public record CoseHashV
+{
+ ///
+ /// The hash algorithm used to generate the hash value.
+ ///
+ public CoseHashAlgorithm Algorithm { get; set; }
+
+ private byte[]? InternalHashValue;
+ ///
+ /// The actual value from the hashing function
+ ///
+ public byte[] HashValue
+ {
+ get
+ {
+ return InternalHashValue ?? Array.Empty();
+ }
+ set
+ {
+ // validate the input
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+ if (value.Length == 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), "The hash value cannot be empty.");
+ }
+ if (Algorithm == CoseHashAlgorithm.Reserved)
+ {
+ throw new ArgumentException("The algorithm must be set before the hash can be stored.", nameof(value));
+ }
+
+ // sanity check the length of the hash against the specified algorithm to be sure we're not allowing a mismatch.
+ HashAlgorithm algo = GetHashAlgorithmFromCoseHashAlgorithm(Algorithm);
+ if (value.Length != (algo.HashSize / 8))
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), @$"The hash value length of {value.Length} did not match the CoseHashAlgorithm {Algorithm} required length of {algo.HashSize / 8}");
+ }
+ InternalHashValue = value;
+ }
+ }
+
+ ///
+ /// Optional location of object that was hashed
+ ///
+ public string? Location { get; set; }
+
+ ///
+ /// Optional array containing other details meaningful to the application.
+ ///
+ public byte[]? AdditionalData { get; set; }
+
+ ///
+ /// Default constructor for the CoseHashV class.
+ ///
+ public CoseHashV()
+ {
+ }
+
+ ///
+ /// Copy constructor for the CoseHashV class.
+ ///
+ /// The other to copy.
+ public CoseHashV(CoseHashV other)
+ {
+ Algorithm = other.Algorithm;
+ // deep copy the hash value over
+ InternalHashValue = new byte[other.HashValue.Length];
+ other.HashValue.CopyTo(InternalHashValue, 0);
+
+ // copy the location string
+ Location = other.Location;
+
+ // deep copy the additional data over if present.
+ if (other.AdditionalData != null)
+ {
+ AdditionalData = new byte[other.AdditionalData.Length];
+ other.AdditionalData.CopyTo(AdditionalData, 0);
+ }
+ }
+
+ ///
+ /// Constructor for the CoseHashV class which takes a hash algorithm and a hash value.
+ ///
+ /// The CoseHashAlgorithm to be used for CoseHashV.
+ /// The hash value to be present.
+ /// True to disable the checks which ensure the decoded algorithm expected hash length and the length of the decoded hash match, False (default) to leave them enabled.
+ public CoseHashV(
+ CoseHashAlgorithm algorithm,
+ byte[] hashValue,
+ bool disableValidation = false)
+ : this(algorithm, null, null)
+ {
+ _ = hashValue ?? throw new ArgumentNullException(nameof(hashValue));
+ if(hashValue.Length == 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(hashValue), "Hash value provided must contain > 0 elements.");
+ }
+
+ if (disableValidation)
+ {
+ // bypass the property setter to avoid validation against the algorithm.
+ InternalHashValue = hashValue;
+ }
+ else
+ {
+ // use the property setter to validate the hash value against the algorithm verses directly assigning InternalHashValue.
+ HashValue = hashValue;
+ }
+ }
+
+ ///
+ /// Creates a CoseHashV object from byte[] of data.
+ ///
+ /// The CoseHashAlgorithm to be used for CoseHashV.
+ /// The data to be hashed.
+ /// The optional location of the content represented by this hash.
+ /// The optional additional information.
+ public CoseHashV(
+ CoseHashAlgorithm algorithm,
+ byte[] byteData,
+ string? location = null,
+ byte[]? additionalData = null)
+ : this(algorithm, location, additionalData)
+ {
+ _= byteData ?? throw new ArgumentNullException(nameof(byteData));
+ if(byteData.Length == 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(byteData), "The data to be hashed cannot be empty.");
+ }
+ using HashAlgorithm hashAlgorightm = GetHashAlgorithmFromCoseHashAlgorithm(algorithm);
+ // bypass the property setter since we are computing the hash value based on the algorithm directly.
+ InternalHashValue = hashAlgorightm.ComputeHash(byteData);
+ }
+
+ ///
+ /// Creates a CoseHashV object from ReadOnlyMemory{byte} of data.
+ ///
+ /// The CoseHashAlgorithm to be used for CoseHashV.
+ /// The data to be hashed.
+ /// The optional location of the content represented by this hash.
+ /// The optional additional information.
+ public CoseHashV(
+ CoseHashAlgorithm algorithm,
+ ReadOnlyMemory readonlyData,
+ string? location = null,
+ ReadOnlyMemory? additionalData = null)
+ : this(algorithm, byteData: readonlyData.ToArray(), location, additionalData?.ToArray())
+ {
+ }
+
+ ///
+ /// Creates a CoseHashV object from a Stream of data.
+ ///
+ /// The CoseHashAlgorithm to be used for CoseHashV.
+ /// The data to be hashed.
+ /// The optional location of the content represented by this hash.
+ /// The optional additional information.
+ public CoseHashV(
+ CoseHashAlgorithm algorithm,
+ Stream streamData,
+ string? location = null,
+ byte[]? additionalData = null)
+ : this(algorithm, location, additionalData)
+ {
+ _= streamData ?? throw new ArgumentNullException(nameof(streamData));
+
+ using HashAlgorithm hashAlgorightm = GetHashAlgorithmFromCoseHashAlgorithm(algorithm);
+ // bypass the property setter since we are computing the hash value based on the algorithm directly.
+ InternalHashValue = hashAlgorightm.ComputeHash(streamData);
+ }
+
+ ///
+ /// Private constructor to share and consolidate initialization code.
+ ///
+ /// The CoseHashAlgorithm to be used for CoseHashV.
+ /// The optional location of the content represented by this hash.
+ /// The optional additional information.
+ private CoseHashV(
+ CoseHashAlgorithm algorithm,
+ string? location = null,
+ byte[]? additionalData = null)
+ {
+ Algorithm = algorithm;
+ Location = location;
+ AdditionalData = additionalData;
+ }
+
+ ///
+ /// Validates that the given data stored in the stream matches the hash value stored in this instance.
+ ///
+ /// The data bytes to check to match the hash.
+ /// True if the hash of matches HashBytes, False otherwise.
+ /// Thrown if data passed in is null or has a length of 0.
+ /// Thrown if the computed hash length and the stored hash length differ.
+ public Task ContentMatchesAsync(Stream stream)
+ => Task.FromResult(HashMatches(data: null, stream: stream));
+
+ ///
+ /// Validates that the given data stored in the stream matches the hash value stored in this instance.
+ ///
+ /// The data bytes to check to match the hash.
+ /// True if the hash of matches HashBytes, False otherwise.
+ /// Thrown if data passed in is null or has a length of 0.
+ /// Thrown if the computed hash length and the stored hash length differ.
+ public bool ContentMatches(Stream stream)
+ => HashMatches(data: null, stream: stream);
+
+ ///
+ /// Validates that the given data in bytes matches the hash value stored in this instance.
+ ///
+ /// The data bytes to check to match the hash.
+ /// True if the hash of matches HashBytes, False otherwise.
+ /// Thrown if data passed in is null or has a length of 0.
+ /// Thrown if the computed hash length and the stored hash length differ.
+ public Task ContentMatchesAsync(byte[] data)
+ => Task.FromResult(HashMatches(data: data, stream: null));
+
+ ///
+ /// Validates that the given data in bytes matches the hash value stored in this instance.
+ ///
+ /// The data bytes to check to match the hash.
+ /// True if the hash of matches HashBytes, False otherwise.
+ /// Thrown if data passed in is null or has a length of 0.
+ /// Thrown if the computed hash length and the stored hash length differ.
+ public bool ContentMatches(ReadOnlyMemory data)
+ => HashMatches(data: data.ToArray(), stream: null);
+
+ ///
+ /// Validates that the given data in bytes matches the hash value stored in this instance.
+ ///
+ /// The data bytes to check to match the hash.
+ /// True if the hash of matches HashBytes, False otherwise.
+ /// Thrown if data passed in is null or has a length of 0.
+ /// Thrown if the computed hash length and the stored hash length differ.
+ public Task ContentMatchesAsync(ReadOnlyMemory data)
+ => Task.FromResult(HashMatches(data: data.ToArray(), stream: null));
+
+ ///
+ /// Validates that the given data in bytes matches the hash value stored in this instance.
+ ///
+ /// The data bytes to check to match the hash.
+ /// True if the hash of matches HashBytes, False otherwise.
+ /// Thrown if data passed in is null or has a length of 0.
+ /// Thrown if the computed hash length and the stored hash length differ.
+ public bool ContentMatches(byte[] data)
+ => HashMatches(data: data, stream: null);
+
+ ///
+ /// Method for handling byte[] and stream for the same logic.
+ ///
+ /// if specified, then will compute a hash of this data and compare to internal hash value.
+ /// if data is null and stream is specified, then will compute a hash of this stream and compare to internal hash value.
+ /// True if the hashes match, False otherwise.
+ /// Thrown if data is null or data length is 0 and stream is null, or if data is null and stream is null.
+ /// Thrown if the length of the computed hash does not match the internal stored hash length, thus the wrong hash algorithm is being used.
+ private bool HashMatches(byte[]? data, Stream? stream)
+ {
+ // handle input validation
+ if (
+ (data == null || data.Length == 0) &&
+ (stream == null))
+ {
+ throw new ArgumentNullException(nameof(data));
+ }
+
+ // initialize and compute the hash
+ using HashAlgorithm hashAlgorithm = GetHashAlgorithmFromCoseHashAlgorithm(Algorithm);
+ byte[] hash = stream != null ? hashAlgorithm.ComputeHash(stream) : hashAlgorithm.ComputeHash(data);
+
+ // handle the case where the algorithm we derived did not match the algorithm that was used to populate the CoseHashV instance.
+ return hash.Length != HashValue.Length
+ ? throw new CoseSign1Exception($@"The computed hash length of {hash.Length} for hash type {hashAlgorithm.GetType().FullName} created a hash different than the length of {HashValue.Length} which is unexpected.")
+ : hash.SequenceEqual(HashValue);
+ }
+
+ ///
+ /// Writes the current CoseHashV instance to a cbor byte[].
+ ///
+ /// a byte[] cbor representation of the CoseHashV object.
+ public byte[] Serialize()
+ {
+ CborWriter writer = new(CborConformanceMode.Strict);
+
+ int propertyCount = 2;
+
+ if (Location != null)
+ {
+ propertyCount++;
+ }
+
+ if (AdditionalData != null)
+ {
+ propertyCount++;
+ }
+
+ writer.WriteStartArray(propertyCount);
+ writer.WriteInt64((long)Algorithm);
+ writer.WriteByteString(HashValue);
+ if (Location != null)
+ {
+ writer.WriteTextString(Location);
+ }
+ if (AdditionalData != null)
+ {
+ writer.WriteByteString(AdditionalData);
+ }
+ writer.WriteEndArray();
+
+ return writer.Encode();
+ }
+
+ ///
+ /// Reads a COSE_Hash_V structure from the .
+ ///
+ /// A ReadOnlyMemory{byte} which represents a CoseHashV object.
+ /// True to disable the checks which ensure the decoded algorithm expected hash length and the length of the decoded hash match, False (default) to leave them enabled.
+ /// A proper COSE_Hash_V structure if read from the reader.
+ /// Thrown if is null.
+ /// Thrown if an invalid object state or format is detected.
+ public static CoseHashV Deserialize(ReadOnlyMemory data, bool disableValidation = false)
+ => Deserialize(new CborReader(data), disableValidation);
+
+ ///
+ /// Reads a COSE_Hash_V structure from the .
+ ///
+ /// A ReadOnlySpan{byte} which represents a CoseHashV object.
+ /// True to disable the checks which ensure the decoded algorithm expected hash length and the length of the decoded hash match, False (default) to leave them enabled.
+ /// A proper COSE_Hash_V structure if read from the reader.
+ /// Thrown if is null.
+ /// Thrown if an invalid object state or format is detected.
+ public static CoseHashV Deserialize(ReadOnlySpan data, bool disableValidation = false)
+ => Deserialize(new CborReader(data.ToArray().AsMemory()), disableValidation);
+
+ ///
+ /// Reads a COSE_Hash_V structure from the .
+ ///
+ /// A byte[] which represents a CoseHashV object.
+ /// True to disable the checks which ensure the decoded algorithm expected hash length and the length of the decoded hash match, False (default) to leave them enabled.
+ /// A proper COSE_Hash_V structure if read from the reader.
+ /// Thrown if is null.
+ /// Thrown if an invalid object state or format is detected.
+ public static CoseHashV Deserialize(byte[] data, bool disableValidation = false)
+ => Deserialize(new CborReader(data ?? throw new ArgumentNullException(nameof(data), "Cannot deserialize null bytes into a CoseHashV")), disableValidation);
+
+ ///
+ /// Reads a COSE_Hash_V structure from the .
+ ///
+ /// The CBOR reader to be read from, it must have allowMultipleRootLevelValues set to true.
+ /// True to disable the checks which ensure the decoded algorithm expected hash length and the length of the decoded hash match, False (default) to leave them enabled.
+ /// A proper COSE_Hash_V structure if read from the reader.
+ /// Thrown if is null.
+ /// Thrown if an invalid object state or format is detected.
+ public static CoseHashV Deserialize(CborReader reader, bool disableValidation = false)
+ {
+ if (reader == null)
+ {
+ throw new ArgumentNullException(nameof(reader));
+ }
+ CoseHashV returnValue = new CoseHashV();
+
+ // tracking state for error purposes.
+ uint propertiesRead = 0;
+
+ try
+ {
+ if (PeekStateWithExceptionHandling(reader) != CborReaderState.StartArray)
+ {
+ throw new InvalidCoseDataException($"Invalid COSE_Hash_V structure, expected {nameof(CborReaderState.StartArray)} but got {reader.PeekState()} instead.");
+ }
+
+ int? propertiesToRead;
+ try
+ {
+ propertiesToRead = reader.ReadStartArray();
+ }
+ catch (Exception ex) when (ex is CborContentException)
+ {
+ throw new InvalidCoseDataException($"Invalid COSE_Hash_V structure, reading the state of the reader threw an exception: {ex.Message}", ex);
+ }
+ if (propertiesToRead < 2 || propertiesToRead > 4)
+ {
+ throw new InvalidCoseDataException($"Invalid COSE_Hash_V structure, expected 2 to 4 properties but got {propertiesToRead} instead.");
+ }
+
+ // read the hash algorithm
+ CborReaderState state = PeekStateWithExceptionHandling(reader);
+
+ if (state != CborReaderState.UnsignedInteger &&
+ state != CborReaderState.NegativeInteger &&
+ state != CborReaderState.TextString)
+ {
+ throw new InvalidCoseDataException($"Invalid COSE_Hash_V structure, expected {nameof(CborReaderState.UnsignedInteger)} or {nameof(CborReaderState.NegativeInteger)} or {nameof(CborReaderState.TextString)} but got {state} instead for \"hashAlg\" property.");
+ }
+ if (state == CborReaderState.TextString)
+ {
+ string? algorithmString;
+ try
+ {
+ algorithmString = reader.ReadTextString();
+ }
+ catch (Exception ex) when (ex is InvalidOperationException || ex is CborContentException)
+ {
+ throw new InvalidCoseDataException($"Invalid COSE_Hash_V structure, the hash algorithm provided threw an exception: {ex.Message}", ex);
+ }
+
+ try
+ {
+ returnValue.Algorithm = Enum.TryParse(algorithmString, ignoreCase: true, out CoseHashAlgorithm algorithm)
+ ? algorithm
+ : throw new InvalidCoseDataException($"Invalid COSE_Hash_V structure, the hash algorithm provided \"{algorithmString}\" could not be parsed into a valid {nameof(CoseHashAlgorithm)}.");
+ }
+ catch (ArgumentException ex)
+ {
+ throw new InvalidCoseDataException($"Invalid COSE_Hash_V structure, the hash algorithm provided \"{algorithmString}\" threw an exception: {ex.Message}", ex);
+ }
+ }
+ else
+ {
+ try
+ {
+ returnValue.Algorithm = (CoseHashAlgorithm)reader.ReadInt64();
+ }
+ catch (Exception ex) when (ex is InvalidOperationException || ex is OverflowException || ex is CborContentException)
+ {
+ throw new InvalidCoseDataException($"Invalid COSE_Hash_V structure, the hash algorithm provided threw an exception: {ex.Message}", ex);
+ }
+ }
+ ++propertiesRead;
+
+ state = PeekStateWithExceptionHandling(reader);
+ if (state != CborReaderState.ByteString)
+ {
+ throw new InvalidCoseDataException($"Invalid COSE_Hash_V structure, expected {nameof(CborReaderState.ByteString)} but got {state} instead for \"hashValue\" property.");
+ }
+ try
+ {
+ byte[]? value;
+ try
+ {
+ value = reader.ReadByteString();
+ }
+ catch(Exception ex) when (ex is InvalidOperationException || ex is CborContentException)
+ {
+ throw new InvalidCoseDataException($"Invalid COSE_Hash_V structure, reading the hash value provided threw an exception: {ex.Message}", ex);
+ }
+ if (disableValidation)
+ {
+ // directly assign to the internal hash value to bypass the property setter.
+ returnValue.InternalHashValue = value;
+ }
+ else
+ {
+ // use the property setter to validate the hash value against the algorithm.
+ returnValue.HashValue = value;
+ }
+ }
+ catch (Exception ex) when (ex is ArgumentException ||
+ ex is NotSupportedException)
+ {
+ throw new InvalidCoseDataException($"Invalid COSE_Hash_V structure, the hash value provided threw an exception: {ex.Message}", ex);
+ }
+ ++propertiesRead;
+
+ // check for and read location as a text string.
+ state = PeekStateWithExceptionHandling(reader);
+ if (state == CborReaderState.TextString)
+ {
+ try
+ {
+ returnValue.Location = reader.ReadTextString();
+ }
+ catch(Exception ex) when (ex is InvalidOperationException || ex is CborContentException)
+ {
+ throw new InvalidCoseDataException($"Invalid COSE_Hash_V structure, reading the location provided threw an exception: {ex.Message}", ex);
+ }
+ ++propertiesRead;
+ }
+
+ // check for and read additional data as a byte string
+ state = PeekStateWithExceptionHandling(reader);
+ if (state == CborReaderState.ByteString)
+ {
+ try
+ {
+ returnValue.AdditionalData = reader.ReadByteString();
+ }
+ catch(Exception ex) when (ex is InvalidOperationException || ex is CborContentException)
+ {
+ throw new InvalidCoseDataException($"Invalid COSE_Hash_V structure, reading the additional data provided threw an exception: {ex.Message}", ex);
+ }
+ ++propertiesRead;
+ }
+
+ // validate the end of the structure is present.
+ state = PeekStateWithExceptionHandling(reader);
+ if (state != CborReaderState.EndArray)
+ {
+ throw new InvalidCoseDataException($"Invalid COSE_Hash_V structure, expected {nameof(CborReaderState.EndArray)} but got {state} instead after reading {propertiesRead} elements of {propertiesToRead} detected elements.");
+ }
+ try
+ {
+ reader.ReadEndArray();
+ }
+ catch(Exception ex) when (ex is InvalidOperationException || ex is CborContentException)
+ {
+ throw new InvalidCoseDataException($"Invalid COSE_Hash_V structure, reading the end of the array threw an exception: {ex.Message}", ex);
+ }
+ }
+ catch(CborContentException ex)
+ {
+ throw new InvalidCoseDataException($"While processing content, a CborContentException was encountered: \"{ex.Message}\"", ex);
+ }
+ return returnValue;
+ }
+
+ private static CborReaderState PeekStateWithExceptionHandling(CborReader reader)
+ {
+ try
+ {
+ return reader.PeekState();
+ }
+ catch(Exception ex) when (ex is CborContentException)
+ {
+ throw new InvalidCoseDataException($"Invalid COSE_Hash_V structure, reading the state of the reader threw an exception: {ex.Message}", ex);
+ }
+ }
+
+ ///
+ /// Get the hash algorithm from the specified CoseHashAlgorithm.
+ ///
+ /// The CoseHashAlgorithm to get a hashing type from.
+ /// The type of the hash object to use.
+ /// The CoseHashAlgorithm specified is not yet supported.
+ private static HashAlgorithm GetHashAlgorithmFromCoseHashAlgorithm(CoseHashAlgorithm algorithm)
+ {
+ return algorithm switch
+ {
+ CoseHashAlgorithm.SHA256 => new SHA256Managed(),
+ CoseHashAlgorithm.SHA512 => new SHA512Managed(),
+ CoseHashAlgorithm.SHA384 => new SHA384Managed(),
+ _ => throw new NotSupportedException($"The algorithm {algorithm} is not supported by {nameof(CoseHashV)}.")
+ };
+ }
+}
diff --git a/CoseDetachedSignature/CoseDetachedSignature.csproj b/CoseIndirectSignature/CoseIndirectSignature.csproj
similarity index 87%
rename from CoseDetachedSignature/CoseDetachedSignature.csproj
rename to CoseIndirectSignature/CoseIndirectSignature.csproj
index 2b1465d8..6fcf8b69 100644
--- a/CoseDetachedSignature/CoseDetachedSignature.csproj
+++ b/CoseIndirectSignature/CoseIndirectSignature.csproj
@@ -15,6 +15,10 @@
true
+
+
+
+
diff --git a/CoseIndirectSignature/Exceptions/CoseIndirectSignatureException.cs b/CoseIndirectSignature/Exceptions/CoseIndirectSignatureException.cs
new file mode 100644
index 00000000..dc2ba23d
--- /dev/null
+++ b/CoseIndirectSignature/Exceptions/CoseIndirectSignatureException.cs
@@ -0,0 +1,53 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+// Ignore Spelling: Cose
+
+namespace CoseIndirectSignature.Exceptions;
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.Serialization;
+using CoseSign1.Abstractions.Exceptions;
+
+///
+/// Base exception class for the CoseIndirectSignature library.
+///
+[Serializable]
+[ExcludeFromCodeCoverage]
+public class CoseIndirectSignatureException : CoseSign1Exception
+{
+
+ ///
+ /// Default Constructor.
+ ///
+ public CoseIndirectSignatureException()
+ {
+ }
+
+ ///
+ /// Creates an instance of with a specified message.
+ ///
+ /// The message for the exception.
+ public CoseIndirectSignatureException(string message) : base(message)
+ {
+ }
+
+ ///
+ /// Creates an instance of with a specified message and inner exception.
+ ///
+ /// The message for the exception.
+ /// The inner exception for this exception.
+ public CoseIndirectSignatureException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+
+ ///
+ /// Creates an instance of with a specified message and inner context.
+ ///
+ /// The serialization info for this exception.
+ /// The streaming context for this exception.
+ protected CoseIndirectSignatureException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+}
\ No newline at end of file
diff --git a/CoseIndirectSignature/Exceptions/InvalidCoseDataException.cs b/CoseIndirectSignature/Exceptions/InvalidCoseDataException.cs
new file mode 100644
index 00000000..9b1763ad
--- /dev/null
+++ b/CoseIndirectSignature/Exceptions/InvalidCoseDataException.cs
@@ -0,0 +1,42 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+// Ignore Spelling: Cose
+
+namespace CoseIndirectSignature.Exceptions;
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.Serialization;
+
+///
+/// Exception thrown when the COSE data is invalid with Cose Indirect Signature library.
+///
+[Serializable]
+[ExcludeFromCodeCoverage]
+public sealed class InvalidCoseDataException : CoseIndirectSignatureException
+{
+ ///
+ /// The default constructor.
+ ///
+ public InvalidCoseDataException()
+ {
+ }
+
+ ///
+ /// Creates an instance of with a specified message.
+ ///
+ /// The message for the exception.
+ public InvalidCoseDataException(string message) : base(message)
+ {
+ }
+
+ ///
+ /// Creates an instance of with a specified message and inner exception.
+ ///
+ /// The message for the exception.
+ /// The inner exception for this exception.
+ public InvalidCoseDataException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+}
\ No newline at end of file
diff --git a/CoseDetachedSignature/Extensions/CoseSign1MessageDetachedSignatureExtensions.cs b/CoseIndirectSignature/Extensions/CoseSign1MessageIndirectSignatureExtensions.cs
similarity index 55%
rename from CoseDetachedSignature/Extensions/CoseSign1MessageDetachedSignatureExtensions.cs
rename to CoseIndirectSignature/Extensions/CoseSign1MessageIndirectSignatureExtensions.cs
index c2be7c54..7e5e44e7 100644
--- a/CoseDetachedSignature/Extensions/CoseSign1MessageDetachedSignatureExtensions.cs
+++ b/CoseIndirectSignature/Extensions/CoseSign1MessageIndirectSignatureExtensions.cs
@@ -1,20 +1,25 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-namespace CoseDetachedSignature.Extensions;
+// Ignore Spelling: Cose
+
+namespace CoseIndirectSignature.Extensions;
+
+using CoseIndirectSignature.Exceptions;
///
-/// Class which extends for detached signature use cases.
+/// Class which extends for indirect signature use cases.
///
///
/// Logging is done through Trace.TraceError and Debug.WriteLine.
///
-public static class CoseSign1MessageDetachedSignatureExtensions
+public static class CoseSign1MessageIndirectSignatureExtensions
{
private static readonly string AlgorithmGroupName = "algorithm";
// Regex looks for "+hash-sha256" and will parse it out as a named group of "extension" with value "+hash-sha256" an the algorithm group name of "sha256"
// Will also work with "+hash-sha3_256"
private static readonly Regex HashMimeTypeExtension = new(@$"(?\+hash-(?<{AlgorithmGroupName}>[\w_]+))", RegexOptions.Compiled);
+ private static readonly Regex CoseHashVMimeTypeExtension = new(@$"\+cose-hash-v", RegexOptions.Compiled);
///
/// Lazy populate all known hash algorithms from System.Security.Cryptography into a runtime cache
@@ -74,78 +79,130 @@ private static IEnumerable FindAllDerivedHashAlgorithms()
: null;
}
+ ///
+ /// Checks to see if a COSE Sign1 Message has the Content Type Protected Header set to include +cose-hash-v
+ ///
+ /// The CoseSign1Message to evaluate
+ /// True if +cose-hash-v is found, False otherwise.
+ public static bool TryGetIsCoseHashVContentType(this CoseSign1Message? @this)
+ {
+ if (@this == null)
+ {
+ Trace.TraceError($"{nameof(TryGetIsCoseHashVContentType)} was called on a null CoseSign1Message object");
+ return false;
+ }
+
+ if (@this.Content == null)
+ {
+ Trace.TraceWarning($"{nameof(TryGetIsCoseHashVContentType)} was called on a detached CoseSign1Message object, which is not valid.");
+ return false;
+ }
+
+ if (!@this.ProtectedHeaders.TryGetValue(CoseHeaderLabel.ContentType, out CoseHeaderValue contentTypeValue))
+ {
+ Trace.TraceWarning($"{nameof(TryGetIsCoseHashVContentType)} was called on a CoseSign1Message object({@this.GetHashCode()}) without the ContentType protected header present.");
+ return false;
+ }
+
+ string contentType = contentTypeValue.GetValueAsString();
+ if (string.IsNullOrEmpty(contentType))
+ {
+ Trace.TraceWarning($"{nameof(TryGetIsCoseHashVContentType)} was called on a CoseSign1Message object({@this.GetHashCode()}) without the ContentType protected header being a string value.");
+ return false;
+ }
+
+ Match mimeMatch = CoseHashVMimeTypeExtension.Match(contentType);
+ return mimeMatch.Success;
+ }
+
///
/// Extracts the hash algorithm name from the hash extension within the Content Type Protected Header if present.
///
/// The CoseSign1Message to evaluate
/// The discovered Hash Algorithm Name from the Content Type Protected Header value of the CoseSign1Message.
/// True if successful in extracting a HashAlgorithmName from the Content Type Protected Header; False otherwise.
- public static bool TryGetDetachedSignatureAlgorithm(this CoseSign1Message? @this, out HashAlgorithmName name)
+ public static bool TryGetIndirectSignatureAlgorithm(this CoseSign1Message? @this, out HashAlgorithmName name)
{
if (@this == null)
{
- Trace.TraceError($"{nameof(TryGetDetachedSignatureAlgorithm)} was called on a null CoseSign1Message object");
+ Trace.TraceError($"{nameof(TryGetIndirectSignatureAlgorithm)} was called on a null CoseSign1Message object");
return false;
}
if (!@this.ProtectedHeaders.TryGetValue(CoseHeaderLabel.ContentType, out CoseHeaderValue contentTypeValue))
{
- Trace.TraceWarning($"{nameof(TryGetDetachedSignatureAlgorithm)} was called on a CoseSign1Message object({@this.GetHashCode()}) without the ContentType protected header present.");
+ Trace.TraceWarning($"{nameof(TryGetIndirectSignatureAlgorithm)} was called on a CoseSign1Message object({@this.GetHashCode()}) without the ContentType protected header present.");
return false;
}
string contentType = contentTypeValue.GetValueAsString();
if (string.IsNullOrEmpty(contentType))
{
- Trace.TraceWarning($"{nameof(TryGetDetachedSignatureAlgorithm)} was called on a CoseSign1Message object({@this.GetHashCode()}) without the ContentType protected header being a string value.");
+ Trace.TraceWarning($"{nameof(TryGetIndirectSignatureAlgorithm)} was called on a CoseSign1Message object({@this.GetHashCode()}) without the ContentType protected header being a string value.");
return false;
}
Match mimeMatch = HashMimeTypeExtension.Match(contentType);
if (!mimeMatch.Success)
{
- Trace.TraceWarning($"{nameof(TryGetDetachedSignatureAlgorithm)} was called on a CoseSign1Message object({@this.GetHashCode()}) with the ContentType protected header being \"{contentType}\" however it did not match the regex pattern \"{HashMimeTypeExtension}\".");
+ Trace.TraceWarning($"{nameof(TryGetIndirectSignatureAlgorithm)} was called on a CoseSign1Message object({@this.GetHashCode()}) with the ContentType protected header being \"{contentType}\" however it did not match the regex pattern \"{HashMimeTypeExtension}\".");
return false;
}
name = new HashAlgorithmName(mimeMatch.Groups[AlgorithmGroupName].Value.ToUpperInvariant());
- Debug.WriteLine($"{nameof(TryGetDetachedSignatureAlgorithm)} extracted hash algorithm name: {name.Name}, returning true");
+ Debug.WriteLine($"{nameof(TryGetIndirectSignatureAlgorithm)} extracted hash algorithm name: {name.Name}, returning true");
return true;
}
///
- /// Determines whether the current CoseSign1Message object includes a detached signature.
+ /// Determines whether the current CoseSign1Message object includes a Indirect signature.
///
/// The CoseSign1Message to evaluate.
- /// True if the CoseSign1Message is a encoded detached signature; False otherwise.
- public static bool IsDetachedSignature(this CoseSign1Message? @this) => @this.TryGetDetachedSignatureAlgorithm(out _);
+ /// True if the CoseSign1Message is a encoded indirect signature; False otherwise.
+ public static bool IsIndirectSignature(this CoseSign1Message? @this) => @this.TryGetIsCoseHashVContentType() || @this.TryGetIndirectSignatureAlgorithm(out _);
///
- /// Computes if the encoded detached signature within the CoseSign1Message object matches the given artifact stream.
+ /// Computes if the encoded Indirect signature within the CoseSign1Message object matches the given artifact stream.
///
/// The CoseSign1Message to evaluate.
/// The artifact stream to evaluate.
- /// True if the detached signature in the CoseSign1Message matches the signature of the artifact stream; False otherwise.
+ /// True if the Indirect signature in the CoseSign1Message matches the signature of the artifact stream; False otherwise.
public static bool SignatureMatches(this CoseSign1Message? @this, Stream artifactStream)
=> SignatureMatchesInternal(@this, artifactStream: artifactStream);
///
- /// Computes if the encoded detached signature within the CoseSign1Message object matches the given artifact bytes.
+ /// Computes if the encoded Indirect signature within the CoseSign1Message object matches the given artifact bytes.
///
/// The CoseSign1Message to evaluate.
/// The artifact bytes to evaluate.
- /// True if the detached signature in the CoseSign1Message matches the signature of the artifact bytes; False otherwise.
+ /// True if the Indirect signature in the CoseSign1Message matches the signature of the artifact bytes; False otherwise.
public static bool SignatureMatches(this CoseSign1Message? @this, ReadOnlyMemory artifactBytes)
=> SignatureMatchesInternal(@this, artifactBytes: artifactBytes);
///
- /// Computes if the encoded detached signature within the CoseSign1Message object matches the given artifact bytes or artifact stream.
+ /// Computes if the encoded Indirect signature within the CoseSign1Message object matches the given artifact bytes or artifact stream.
///
/// The CoseSign1Message to evaluate.
/// The artifact bytes to evaluate.
/// The artifact stream to evaluate.
- /// True if the detached signature in the CoseSign1Message matches the signature of the artifact bytes; False otherwise.
+ /// True if the Indirect signature in the CoseSign1Message matches the signature of the artifact bytes; False otherwise.
private static bool SignatureMatchesInternal(this CoseSign1Message? @this, ReadOnlyMemory? artifactBytes = null, Stream? artifactStream = null)
+ {
+ if(@this.TryGetIsCoseHashVContentType())
+ {
+ return SignatureMatchesInternalCoseHashV(@this, artifactBytes, artifactStream);
+ }
+ return SignatureMatchesInternalDirect(@this, artifactBytes, artifactStream);
+ }
+
+ ///
+ /// Computes if the encoded Indirect signature within the CoseSign1Message object matches the given artifact bytes or artifact stream.
+ ///
+ /// The CoseSign1Message to evaluate.
+ /// The artifact bytes to evaluate.
+ /// The artifact stream to evaluate.
+ /// True if the Indirect signature in the CoseSign1Message matches the signature of the artifact bytes; False otherwise.
+ private static bool SignatureMatchesInternalDirect(this CoseSign1Message @this, ReadOnlyMemory? artifactBytes = null, Stream? artifactStream = null)
{
if (!@this.TryGetHashAlgorithm(out HashAlgorithm? hasher))
{
@@ -172,7 +229,7 @@ public static bool TryGetHashAlgorithm(this CoseSign1Message? @this, out HashAlg
{
hasher = null;
- if (!@this.TryGetDetachedSignatureAlgorithm(out HashAlgorithmName algorithmName))
+ if (!@this.TryGetIndirectSignatureAlgorithm(out HashAlgorithmName algorithmName))
{
Trace.TraceWarning($"{nameof(TryGetHashAlgorithm)} was called on a CoseSign1Message[{@this?.GetHashCode()}] object which did not have a valid hashing algorithm defined");
return false;
@@ -194,4 +251,57 @@ public static bool TryGetHashAlgorithm(this CoseSign1Message? @this, out HashAlg
Debug.WriteLine($"{nameof(TryGetHashAlgorithm)} created a HashAlgorithm from Hash Algorithm Name: {algorithmName.Name}");
return true;
}
+
+ ///
+ /// Returns the CoseHashV object contained within the .Content of the CoseSign1Message if it is a CoseHashV encoded message.
+ ///
+ /// The CoseSign1Message to evaluate.
+ /// True to disable the checks which ensure the decoded algorithm expected hash length and the length of the decoded hash match, False (default) to leave them enabled.
+ /// A deserialized CoseHashV object if no errors, an exception otherwise.
+ /// Thrown if the CoseSign1Message is not a CoseHashV capable object.
+ /// Thrown if the content of this CoseSign1Message cannot be deserialized into a CoseHashV object.
+ public static CoseHashV GetCoseHashV(this CoseSign1Message @this, bool disableValidation = false)
+ {
+ return !@this.TryGetIsCoseHashVContentType()
+ ? throw new InvalidDataException($"The CoseSign1Message[{@this?.GetHashCode()}] object is not a CoseHashV capable object.")
+ : CoseHashV.Deserialize(@this!.Content.Value, disableValidation);
+ }
+
+ ///
+ /// Returns true and populates indirectHash with the CoseHashV object contained within the .Content of the CoseSign1Message if it is a CoseHashV encoded message, false otherwise.
+ ///
+ /// The CoseSign1Message to evaluate.
+ /// True to disable the checks which ensure the decoded algorithm expected hash length and the length of the decoded hash match, False (default) to leave them enabled.
+ /// True if indirectHash is successfully populated, false otherwise.
+ public static bool TryGetCoseHashV(this CoseSign1Message @this, out CoseHashV? indirectHash, bool disableValidation = false)
+ {
+ indirectHash = null;
+
+ try
+ {
+ indirectHash = @this.GetCoseHashV(disableValidation: disableValidation);
+ }
+ catch (Exception ex) when (ex is InvalidDataException || ex is InvalidCoseDataException)
+ {
+ Trace.TraceWarning($"Attempting to get CoseHashV from CoseSign1Message[{@this?.GetHashCode()}] failed, returning false.");
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Leverages the CoseHashV structure path to validate content against the stored indirect hash of the content.
+ ///
+ /// The CoseSign1Message to evaluate the CoseHashV structure from .Content
+ /// The artifact bytes to evaluate.
+ /// The artifact stream to evaluate.
+ /// True if the Indirect signature in the CoseSign1Message matches the signature of the artifact bytes; False otherwise.
+ private static bool SignatureMatchesInternalCoseHashV(this CoseSign1Message @this, ReadOnlyMemory? artifactBytes = null, Stream? artifactStream = null)
+ {
+ CoseHashV hashStructure = CoseHashV.Deserialize(@this.Content.Value);
+ return artifactStream != null
+ ? hashStructure.ContentMatches(artifactStream)
+ : hashStructure.ContentMatches(artifactBytes!.Value);
+ }
}
diff --git a/CoseDetachedSignature/DetachedSignatureFactory.cs b/CoseIndirectSignature/IndirectSignatureFactory.cs
similarity index 59%
rename from CoseDetachedSignature/DetachedSignatureFactory.cs
rename to CoseIndirectSignature/IndirectSignatureFactory.cs
index d597800b..1c3ce387 100644
--- a/CoseDetachedSignature/DetachedSignatureFactory.cs
+++ b/CoseIndirectSignature/IndirectSignatureFactory.cs
@@ -1,19 +1,23 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-namespace CoseDetachedSignature;
+// Ignore Spelling: cose
+
+namespace CoseIndirectSignature;
///
-/// Class used to construct objects which contain a detached signature of a given payload.
+/// Class used to construct objects which contain a indirect signature of a given payload.
///
///
/// This class will append a "+hash-{hash-algorithm-name}" to the content type when storing it within the Content Type protected header of the using a (Defaulting to a ).
/// The field will contain the hash value of the specified payload.
/// The default hash algorithm used is .
///
-public sealed class DetachedSignatureFactory : IDisposable
+public sealed class IndirectSignatureFactory : IDisposable
{
private readonly HashAlgorithm InternalHashAlgorithm;
+ private readonly uint HashLength;
+ private readonly CoseHashAlgorithm InternalCoseHashAlgorithm;
private readonly HashAlgorithmName InternalHashAlgorithmName;
private readonly ICoseSign1MessageFactory InternalMessageFactory;
@@ -34,328 +38,397 @@ public sealed class DetachedSignatureFactory : IDisposable
///
- /// Creates a new instance of the class using the hash algorithm and a .
+ /// Creates a new instance of the class using the hash algorithm and a .
///
- public DetachedSignatureFactory() : this(HashAlgorithmName.SHA256)
+ public IndirectSignatureFactory() : this(HashAlgorithmName.SHA256)
{
}
///
- /// Creates a new instance of the class using the specified hash algorithm and a .
+ /// Creates a new instance of the class using the specified hash algorithm and a .
///
/// The hashing algorithm name to be used when performing hashing operations.
- public DetachedSignatureFactory(HashAlgorithmName hashAlgorithmName) : this(hashAlgorithmName, new CoseSign1MessageFactory())
+ public IndirectSignatureFactory(HashAlgorithmName hashAlgorithmName) : this(hashAlgorithmName, new CoseSign1MessageFactory())
{
}
///
- /// Creates a new instance of the class using the specified hash algorithm and the specified .
+ /// Creates a new instance of the class using the specified hash algorithm and the specified .
///
/// The hashing algorithm name to be used when performing hashing operations.
/// The CoseSign1MessageFactory to be used when creating CoseSign1Messages.
- public DetachedSignatureFactory(HashAlgorithmName hashAlgorithmName, ICoseSign1MessageFactory coseSign1MessageFactory)
+ public IndirectSignatureFactory(HashAlgorithmName hashAlgorithmName, ICoseSign1MessageFactory coseSign1MessageFactory)
{
InternalHashAlgorithmName = hashAlgorithmName;
- InternalHashAlgorithm = CoseSign1MessageDetachedSignatureExtensions.CreateHashAlgorithmFromName(hashAlgorithmName) ?? throw new ArgumentOutOfRangeException(nameof(hashAlgorithmName), $"hashAlgorithmName[{hashAlgorithmName}] could not be instantiated into a valid HashAlgorithm");
+ InternalHashAlgorithm = CoseSign1MessageIndirectSignatureExtensions.CreateHashAlgorithmFromName(hashAlgorithmName) ?? throw new ArgumentOutOfRangeException(nameof(hashAlgorithmName), $"hashAlgorithmName[{hashAlgorithmName}] could not be instantiated into a valid HashAlgorithm");
InternalMessageFactory = coseSign1MessageFactory;
+ HashLength = (uint)InternalHashAlgorithm.HashSize / 8;
+ InternalCoseHashAlgorithm = GetCoseHashAlgorithmFromHashAlgorithm(InternalHashAlgorithm);
+ }
+
+ private CoseHashAlgorithm GetCoseHashAlgorithmFromHashAlgorithm(HashAlgorithm algorithm)
+ {
+ if (algorithm is SHA256)
+ {
+ return CoseHashAlgorithm.SHA256;
+ }
+ else if (algorithm is SHA384)
+ {
+ return CoseHashAlgorithm.SHA384;
+ }
+ else if (algorithm is SHA512)
+ {
+ return CoseHashAlgorithm.SHA512;
+ }
+ else
+ {
+ throw new ArgumentException($@"No mapping for hash algorithm {algorithm.GetType().FullName} to any {nameof(CoseHashAlgorithm)}");
+ }
}
///
- /// Creates a detached signature of the specified payload returned as a following the rules in this class description.
+ /// Creates a Indirect signature of the specified payload returned as a following the rules in this class description.
///
- /// The payload to create a detached signature for.
+ /// The payload to create a Indirect signature for.
/// The COSE signing key provider to be used for the signing operation within the .
/// A media type string following https://datatracker.ietf.org/doc/html/rfc6838.
- /// A CoseSign1Message which can be used as a detached signature validation of the payload.
+ /// True to use the older format, False to use CoseHashV format (default).
+ /// A CoseSign1Message which can be used as a Indirect signature validation of the payload.
/// The contentType parameter was empty or null
- public CoseSign1Message CreateDetachedSignature(
+ public CoseSign1Message CreateIndirectSignature(
ReadOnlyMemory payload,
ICoseSigningKeyProvider signingKeyProvider,
- string contentType) => (CoseSign1Message)CreateDetachedSignatureWithChecksInternal(
+ string contentType,
+ bool useOldFormat = false) => (CoseSign1Message)CreateIndirectSignatureWithChecksInternal(
returnBytes: false,
signingKeyProvider: signingKeyProvider,
contentType: contentType,
- bytePayload: payload);
+ bytePayload: payload,
+ useOldFormat: useOldFormat);
///
- /// Creates a detached signature of the payload given a hash of the payload returned as a following the rules in this class description.
+ /// Creates a Indirect signature of the payload given a hash of the payload returned as a following the rules in this class description.
///
/// The raw hash of the payload
/// The COSE signing key provider to be used for the signing operation within the .
/// A media type string following https://datatracker.ietf.org/doc/html/rfc6838.
- /// A CoseSign1Message which can be used as a detached signature validation of the payload.
+ /// True to use the older format, False to use CoseHashV format (default).
+ /// A CoseSign1Message which can be used as a Indirect signature validation of the payload.
/// The contentType parameter was empty or null
/// Hash size does not correspond to any known hash algorithms
- public CoseSign1Message CreateDetachedSignatureFromHash(
+ public CoseSign1Message CreateIndirectSignatureFromHash(
ReadOnlyMemory rawHash,
ICoseSigningKeyProvider signingKeyProvider,
- string contentType) => (CoseSign1Message)CreateDetachedSignatureWithChecksInternal(
+ string contentType,
+ bool useOldFormat = false) => (CoseSign1Message)CreateIndirectSignatureWithChecksInternal(
returnBytes: false,
signingKeyProvider: signingKeyProvider,
contentType: contentType,
bytePayload: rawHash,
- payloadHashed: true);
+ payloadHashed: true,
+ useOldFormat: useOldFormat);
///
- /// Creates a detached signature of the specified payload returned as a following the rules in this class description.
+ /// Creates a Indirect signature of the specified payload returned as a following the rules in this class description.
///
- /// The payload to create a detached signature for.
+ /// The payload to create a Indirect signature for.
/// The COSE signing key provider to be used for the signing operation within the .
/// A media type string following https://datatracker.ietf.org/doc/html/rfc6838.
- /// A Task which can be awaited which will return a CoseSign1Message which can be used as a detached signature validation of the payload.
+ /// True to use the older format, False to use CoseHashV format (default).
+ /// A Task which can be awaited which will return a CoseSign1Message which can be used as a Indirect signature validation of the payload.
/// The contentType parameter was empty or null
- public Task CreateDetachedSignatureAsync(
+ public Task CreateIndirectSignatureAsync(
ReadOnlyMemory payload,
ICoseSigningKeyProvider signingKeyProvider,
- string contentType) => Task.FromResult(
- (CoseSign1Message)CreateDetachedSignatureWithChecksInternal(
+ string contentType,
+ bool useOldFormat = false) => Task.FromResult(
+ (CoseSign1Message)CreateIndirectSignatureWithChecksInternal(
returnBytes: false,
signingKeyProvider: signingKeyProvider,
contentType: contentType,
- bytePayload: payload));
+ bytePayload: payload,
+ useOldFormat: useOldFormat));
///
- /// Creates a detached signature of the payload given a hash of the payload returned as a following the rules in this class description.
+ /// Creates a Indirect signature of the payload given a hash of the payload returned as a following the rules in this class description.
///
/// The raw hash of the payload
/// The COSE signing key provider to be used for the signing operation within the .
/// A media type string following https://datatracker.ietf.org/doc/html/rfc6838.
- /// A CoseSign1Message which can be used as a detached signature validation of the payload.
+ /// True to use the older format, False to use CoseHashV format (default).
+ /// A CoseSign1Message which can be used as a Indirect signature validation of the payload.
/// The contentType parameter was empty or null
/// Hash size does not correspond to any known hash algorithms
- public Task CreateDetachedSignatureFromHashAsync(
+ public Task CreateIndirectSignatureFromHashAsync(
ReadOnlyMemory rawHash,
ICoseSigningKeyProvider signingKeyProvider,
- string contentType) => Task.FromResult(
- (CoseSign1Message)CreateDetachedSignatureWithChecksInternal(
+ string contentType,
+ bool useOldFormat = false) => Task.FromResult(
+ (CoseSign1Message)CreateIndirectSignatureWithChecksInternal(
returnBytes: false,
signingKeyProvider: signingKeyProvider,
contentType: contentType,
bytePayload: rawHash,
- payloadHashed: true));
+ payloadHashed: true,
+ useOldFormat: useOldFormat));
///
- /// Creates a detached signature of the specified payload returned as a following the rules in this class description.
+ /// Creates a Indirect signature of the specified payload returned as a following the rules in this class description.
///
- /// The payload to create a detached signature for.
+ /// The payload to create a Indirect signature for.
/// The COSE signing key provider to be used for the signing operation within the .
/// A media type string following https://datatracker.ietf.org/doc/html/rfc6838.
- /// A Task which can be awaited which will return a CoseSign1Message which can be used as a detached signature validation of the payload.
+ /// True to use the older format, False to use CoseHashV format (default).
+ /// A Task which can be awaited which will return a CoseSign1Message which can be used as a Indirect signature validation of the payload.
/// The contentType parameter was empty or null
- public CoseSign1Message CreateDetachedSignature(
+ public CoseSign1Message CreateIndirectSignature(
Stream payload,
ICoseSigningKeyProvider signingKeyProvider,
- string contentType) => (CoseSign1Message)CreateDetachedSignatureWithChecksInternal(
+ string contentType,
+ bool useOldFormat = false) => (CoseSign1Message)CreateIndirectSignatureWithChecksInternal(
returnBytes: false,
signingKeyProvider: signingKeyProvider,
contentType: contentType,
- streamPayload: payload);
+ streamPayload: payload,
+ useOldFormat: useOldFormat);
///
- /// Creates a detached signature of the payload given a hash of the payload returned as a following the rules in this class description.
+ /// Creates a Indirect signature of the payload given a hash of the payload returned as a following the rules in this class description.
///
/// The raw hash of the payload
/// The COSE signing key provider to be used for the signing operation within the .
/// A media type string following https://datatracker.ietf.org/doc/html/rfc6838.
- /// A CoseSign1Message which can be used as a detached signature validation of the payload.
+ /// True to use the older format, False to use CoseHashV format (default).
+ /// A CoseSign1Message which can be used as a Indirect signature validation of the payload.
/// The contentType parameter was empty or null
/// Hash size does not correspond to any known hash algorithms
- public CoseSign1Message CreateDetachedSignatureFromHash(
+ public CoseSign1Message CreateIndirectSignatureFromHash(
Stream rawHash,
ICoseSigningKeyProvider signingKeyProvider,
- string contentType) => (CoseSign1Message)CreateDetachedSignatureWithChecksInternal(
+ string contentType,
+ bool useOldFormat = false) => (CoseSign1Message)CreateIndirectSignatureWithChecksInternal(
returnBytes: false,
signingKeyProvider: signingKeyProvider,
contentType: contentType,
streamPayload: rawHash,
- payloadHashed: true);
+ payloadHashed: true,
+ useOldFormat: useOldFormat);
///
- /// Creates a detached signature of the specified payload returned as a following the rules in this class description.
+ /// Creates a Indirect signature of the specified payload returned as a following the rules in this class description.
///
- /// The payload to create a detached signature for.
+ /// The payload to create a Indirect signature for.
/// The COSE signing key provider to be used for the signing operation within the .
/// A media type string following https://datatracker.ietf.org/doc/html/rfc6838.
- /// A Task which can be awaited which will return a CoseSign1Message which can be used as a detached signature validation of the payload.
+ /// True to use the older format, False to use CoseHashV format (default).
+ /// A Task which can be awaited which will return a CoseSign1Message which can be used as a Indirect signature validation of the payload.
/// The contentType parameter was empty or null
- public Task CreateDetachedSignatureAsync(
+ public Task CreateIndirectSignatureAsync(
Stream payload,
ICoseSigningKeyProvider signingKeyProvider,
- string contentType) => Task.FromResult(
- (CoseSign1Message)CreateDetachedSignatureWithChecksInternal(
+ string contentType,
+ bool useOldFormat = false) => Task.FromResult(
+ (CoseSign1Message)CreateIndirectSignatureWithChecksInternal(
returnBytes: false,
signingKeyProvider: signingKeyProvider,
contentType: contentType,
- streamPayload: payload));
+ streamPayload: payload,
+ useOldFormat: useOldFormat));
///
- /// Creates a detached signature of the payload given a hash of the payload returned as a following the rules in this class description.
+ /// Creates a Indirect signature of the payload given a hash of the payload returned as a following the rules in this class description.
///
/// The raw hash of the payload
/// The COSE signing key provider to be used for the signing operation within the .
/// A media type string following https://datatracker.ietf.org/doc/html/rfc6838.
- /// A CoseSign1Message which can be used as a detached signature validation of the payload.
+ /// True to use the older format, False to use CoseHashV format (default).
+ /// A CoseSign1Message which can be used as a Indirect signature validation of the payload.
/// The contentType parameter was empty or null
/// Hash size does not correspond to any known hash algorithms
- public Task CreateDetachedSignatureFromHashAsync(
+ public Task CreateIndirectSignatureFromHashAsync(
Stream rawHash,
ICoseSigningKeyProvider signingKeyProvider,
- string contentType) => Task.FromResult(
- (CoseSign1Message)CreateDetachedSignatureWithChecksInternal(
+ string contentType,
+ bool useOldFormat = false) => Task.FromResult(
+ (CoseSign1Message)CreateIndirectSignatureWithChecksInternal(
returnBytes: false,
signingKeyProvider: signingKeyProvider,
contentType: contentType,
streamPayload: rawHash,
- payloadHashed: true));
+ payloadHashed: true,
+ useOldFormat: useOldFormat));
///
- /// Creates a detached signature of the specified payload returned as a following the rules in this class description.
+ /// Creates a Indirect signature of the specified payload returned as a following the rules in this class description.
///
- /// The payload to create a detached signature for.
+ /// The payload to create a Indirect signature for.
/// The COSE signing key provider to be used for the signing operation within the .
/// A media type string following https://datatracker.ietf.org/doc/html/rfc6838.
- /// A byte[] representation of a CoseSign1Message which can be used as a detached signature validation of the payload.
+ /// True to use the older format, False to use CoseHashV format (default).
+ /// A byte[] representation of a CoseSign1Message which can be used as a Indirect signature validation of the payload.
/// The contentType parameter was empty or null
- public ReadOnlyMemory CreateDetachedSignatureBytes(
+ public ReadOnlyMemory CreateIndirectSignatureBytes(
ReadOnlyMemory payload,
ICoseSigningKeyProvider signingKeyProvider,
- string contentType) => (ReadOnlyMemory)CreateDetachedSignatureWithChecksInternal(
+ string contentType,
+ bool useOldFormat = false) => (ReadOnlyMemory)CreateIndirectSignatureWithChecksInternal(
returnBytes: true,
signingKeyProvider: signingKeyProvider,
contentType: contentType,
- bytePayload: payload);
+ bytePayload: payload,
+ useOldFormat: useOldFormat);
///
- /// Creates a detached signature of the payload given a hash of the payload returned as a following the rules in this class description.
+ /// Creates a Indirect signature of the payload given a hash of the payload returned as a following the rules in this class description.
///
/// The raw hash of the payload
/// The COSE signing key provider to be used for the signing operation within the .
/// A media type string following https://datatracker.ietf.org/doc/html/rfc6838.
- /// A byte[] representation of a CoseSign1Message which can be used as a detached signature validation of the payload.
+ /// True to use the older format, False to use CoseHashV format (default).
+ /// A byte[] representation of a CoseSign1Message which can be used as a Indirect signature validation of the payload.
/// The contentType parameter was empty or null
/// Hash size does not correspond to any known hash algorithms
- public ReadOnlyMemory CreateDetachedSignatureBytesFromHash(
+ public ReadOnlyMemory CreateIndirectSignatureBytesFromHash(
ReadOnlyMemory rawHash,
ICoseSigningKeyProvider signingKeyProvider,
- string contentType) => (ReadOnlyMemory)CreateDetachedSignatureWithChecksInternal(
+ string contentType,
+ bool useOldFormat = false) => (ReadOnlyMemory)CreateIndirectSignatureWithChecksInternal(
returnBytes: true,
signingKeyProvider: signingKeyProvider,
contentType: contentType,
bytePayload: rawHash,
- payloadHashed: true);
+ payloadHashed: true,
+ useOldFormat: useOldFormat);
///
- /// Creates a detached signature of the specified payload returned as a following the rules in this class description.
+ /// Creates a Indirect signature of the specified payload returned as a following the rules in this class description.
///
- /// The payload to create a detached signature for.
+ /// The payload to create a Indirect signature for.
/// The COSE signing key provider to be used for the signing operation within the .
/// A media type string following https://datatracker.ietf.org/doc/html/rfc6838.
- /// A Task which when completed returns a byte[] representation of a CoseSign1Message which can be used as a detached signature validation of the payload.
+ /// True to use the older format, False to use CoseHashV format (default).
+ /// A Task which when completed returns a byte[] representation of a CoseSign1Message which can be used as a Indirect signature validation of the payload.
/// The contentType parameter was empty or null
- public Task> CreateDetachedSignatureBytesAsync(
+ public Task> CreateIndirectSignatureBytesAsync(
ReadOnlyMemory payload,
ICoseSigningKeyProvider signingKeyProvider,
- string contentType) => Task.FromResult(
- (ReadOnlyMemory)CreateDetachedSignatureWithChecksInternal(
+ string contentType,
+ bool useOldFormat = false) => Task.FromResult(
+ (ReadOnlyMemory)CreateIndirectSignatureWithChecksInternal(
returnBytes: true,
signingKeyProvider: signingKeyProvider,
contentType: contentType,
- bytePayload: payload));
+ bytePayload: payload,
+ useOldFormat: useOldFormat));
///
- /// Creates a detached signature of the payload given a hash of the payload returned as a following the rules in this class description.
+ /// Creates a Indirect signature of the payload given a hash of the payload returned as a following the rules in this class description.
///
/// The raw hash of the payload
/// The COSE signing key provider to be used for the signing operation within the .
/// A media type string following https://datatracker.ietf.org/doc/html/rfc6838.
- /// A Task which when completed returns a byte[] representation of a CoseSign1Message which can be used as a detached signature validation of the payload.
+ /// True to use the older format, False to use CoseHashV format (default).
+ /// A Task which when completed returns a byte[] representation of a CoseSign1Message which can be used as a Indirect signature validation of the payload.
/// The contentType parameter was empty or null
/// Hash size does not correspond to any known hash algorithms
- public Task> CreateDetachedSignatureBytesFromHashAsync(
+ public Task> CreateIndirectSignatureBytesFromHashAsync(
ReadOnlyMemory rawHash,
ICoseSigningKeyProvider signingKeyProvider,
- string contentType) => Task.FromResult(
- (ReadOnlyMemory)CreateDetachedSignatureWithChecksInternal(
+ string contentType,
+ bool useOldFormat = false) => Task.FromResult(
+ (ReadOnlyMemory)CreateIndirectSignatureWithChecksInternal(
returnBytes: true,
signingKeyProvider: signingKeyProvider,
contentType: contentType,
bytePayload: rawHash,
- payloadHashed: true));
+ payloadHashed: true,
+ useOldFormat: useOldFormat));
///
- /// Creates a detached signature of the specified payload returned as a following the rules in this class description.
+ /// Creates a Indirect signature of the specified payload returned as a following the rules in this class description.
///
- /// The payload to create a detached signature for.
+ /// The payload to create a Indirect signature for.
/// The COSE signing key provider to be used for the signing operation within the .
/// A media type string following https://datatracker.ietf.org/doc/html/rfc6838.
- /// A byte[] representation of a CoseSign1Message which can be used as a detached signature validation of the payload.
+ /// True to use the older format, False to use CoseHashV format (default).
+ /// A byte[] representation of a CoseSign1Message which can be used as a Indirect signature validation of the payload.
/// The contentType parameter was empty or null
- public ReadOnlyMemory CreateDetachedSignatureBytes(
+ public ReadOnlyMemory CreateIndirectSignatureBytes(
Stream payload,
ICoseSigningKeyProvider signingKeyProvider,
- string contentType) => (ReadOnlyMemory)CreateDetachedSignatureWithChecksInternal(
+ string contentType,
+ bool useOldFormat = false) => (ReadOnlyMemory)CreateIndirectSignatureWithChecksInternal(
returnBytes: true,
signingKeyProvider: signingKeyProvider,
contentType: contentType,
- streamPayload: payload);
+ streamPayload: payload,
+ useOldFormat: useOldFormat);
///
- /// Creates a detached signature of the payload given a hash of the payload returned as a following the rules in this class description.
+ /// Creates a Indirect signature of the payload given a hash of the payload returned as a following the rules in this class description.
///
/// The raw hash of the payload
/// The COSE signing key provider to be used for the signing operation within the .
/// A media type string following https://datatracker.ietf.org/doc/html/rfc6838.
- /// A byte[] representation of a CoseSign1Message which can be used as a detached signature validation of the payload.
+ /// True to use the older format, False to use CoseHashV format (default).
+ /// A byte[] representation of a CoseSign1Message which can be used as a Indirect signature validation of the payload.
/// The contentType parameter was empty or null
/// Hash size does not correspond to any known hash algorithms
- public ReadOnlyMemory CreateDetachedSignatureBytesFromHash(
+ public ReadOnlyMemory CreateIndirectSignatureBytesFromHash(
Stream rawHash,
ICoseSigningKeyProvider signingKeyProvider,
- string contentType) => (ReadOnlyMemory)CreateDetachedSignatureWithChecksInternal(
+ string contentType,
+ bool useOldFormat = false) => (ReadOnlyMemory)CreateIndirectSignatureWithChecksInternal(
returnBytes: true,
signingKeyProvider: signingKeyProvider,
contentType: contentType,
streamPayload: rawHash,
- payloadHashed: true);
+ payloadHashed: true,
+ useOldFormat: useOldFormat);
///
- /// Creates a detached signature of the specified payload returned as a following the rules in this class description.
+ /// Creates a Indirect signature of the specified payload returned as a following the rules in this class description.
///
- /// The payload to create a detached signature for.
+ /// The payload to create a Indirect signature for.
/// The COSE signing key provider to be used for the signing operation within the .
/// A media type string following https://datatracker.ietf.org/doc/html/rfc6838.
- /// A Task which when completed returns a byte[] representation of a CoseSign1Message which can be used as a detached signature validation of the payload.
+ /// True to use the older format, False to use CoseHashV format (default).
+ /// A Task which when completed returns a byte[] representation of a CoseSign1Message which can be used as a Indirect signature validation of the payload.
/// The contentType parameter was empty or null
- public Task> CreateDetachedSignatureBytesAsync(
+ public Task> CreateIndirectSignatureBytesAsync(
Stream payload,
ICoseSigningKeyProvider signingKeyProvider,
- string contentType) => Task.FromResult(
- (ReadOnlyMemory)CreateDetachedSignatureWithChecksInternal(
+ string contentType,
+ bool useOldFormat = false) => Task.FromResult(
+ (ReadOnlyMemory)CreateIndirectSignatureWithChecksInternal(
returnBytes: true,
signingKeyProvider: signingKeyProvider,
contentType: contentType,
- streamPayload: payload));
+ streamPayload: payload,
+ useOldFormat: useOldFormat));
///
- /// Creates a detached signature of the payload given a hash of the payload returned as a following the rules in this class description.
+ /// Creates a Indirect signature of the payload given a hash of the payload returned as a following the rules in this class description.
///
/// The raw hash of the payload
/// The COSE signing key provider to be used for the signing operation within the .
/// A media type string following https://datatracker.ietf.org/doc/html/rfc6838.
- /// A Task which when completed returns a byte[] representation of a CoseSign1Message which can be used as a detached signature validation of the payload.
+ /// True to use the older format, False to use CoseHashV format (default).
+ /// A Task which when completed returns a byte[] representation of a CoseSign1Message which can be used as a Indirect signature validation of the payload.
/// The contentType parameter was empty or null
/// Hash size does not correspond to any known hash algorithms
- public Task> CreateDetachedSignatureBytesFromHashAsync(
+ public Task> CreateIndirectSignatureBytesFromHashAsync(
Stream rawHash,
ICoseSigningKeyProvider signingKeyProvider,
- string contentType) => Task.FromResult(
- (ReadOnlyMemory)CreateDetachedSignatureWithChecksInternal(
+ string contentType,
+ bool useOldFormat = false) => Task.FromResult(
+ (ReadOnlyMemory)CreateIndirectSignatureWithChecksInternal(
returnBytes: true,
signingKeyProvider: signingKeyProvider,
contentType: contentType,
streamPayload: rawHash,
- payloadHashed: true));
-
+ payloadHashed: true,
+ useOldFormat: useOldFormat));
///
/// Does the heavy lifting for this class in computing the hash and creating the correct representation of the CoseSign1Message base on input.
///
@@ -365,17 +438,19 @@ public Task> CreateDetachedSignatureBytesFromHashAsync(
/// If not null, then Stream API's on the CoseSign1MessageFactory are used.
/// If streamPayload is null then this must be specified and must not be null and will use the Byte API's on the CoseSign1MesssageFactory
/// True if the payload represents the raw hash
+ /// True to use the older format, False to use CoseHashV format (default).
/// Either a CoseSign1Message or a ReadOnlyMemory{byte} representing the CoseSign1Message object.
/// The contentType parameter was empty or null
/// Either streamPayload or bytePayload must be specified, but not both at the same time, or both cannot be null
/// payloadHashed is set, but hash size does not correspond to any known hash algorithms
- private object CreateDetachedSignatureWithChecksInternal(
+ private object CreateIndirectSignatureWithChecksInternal(
bool returnBytes,
ICoseSigningKeyProvider signingKeyProvider,
string contentType,
Stream? streamPayload = null,
ReadOnlyMemory? bytePayload = null,
- bool payloadHashed = false)
+ bool payloadHashed = false,
+ bool useOldFormat = false)
{
if (string.IsNullOrWhiteSpace(contentType))
{
@@ -388,6 +463,105 @@ private object CreateDetachedSignatureWithChecksInternal(
throw new ArgumentNullException("payload", "Either streamPayload or bytePayload must be specified, but not both at the same time, or both cannot be null");
}
+ return useOldFormat
+ ? CreateIndirectSignatureWithChecksInternalOldFormat(
+ returnBytes,
+ signingKeyProvider,
+ contentType,
+ streamPayload,
+ bytePayload,
+ payloadHashed)
+ : CreateIndirectSignatureWithChecksInternalNewFormat(
+ returnBytes,
+ signingKeyProvider,
+ contentType,
+ streamPayload,
+ bytePayload,
+ payloadHashed);
+ }
+
+ ///
+ /// Does the heavy lifting for this class in computing the hash and creating the correct representation of the CoseSign1Message base on input.
+ ///
+ /// True if ReadOnlyMemory form of CoseSign1Message is to be returned, False for a proper CoseSign1Message
+ /// The signing key provider used for COSE signing operations.
+ /// The user specified content type.
+ /// If not null, then Stream API's on the CoseSign1MessageFactory are used.
+ /// If streamPayload is null then this must be specified and must not be null and will use the Byte API's on the CoseSign1MesssageFactory
+ /// True if the payload represents the raw hash
+ /// Either a CoseSign1Message or a ReadOnlyMemory{byte} representing the CoseSign1Message object.
+ /// The contentType parameter was empty or null
+ /// Either streamPayload or bytePayload must be specified, but not both at the same time, or both cannot be null
+ /// payloadHashed is set, but hash size does not correspond to any known hash algorithms
+ private object CreateIndirectSignatureWithChecksInternalNewFormat(
+ bool returnBytes,
+ ICoseSigningKeyProvider signingKeyProvider,
+ string contentType,
+ Stream? streamPayload = null,
+ ReadOnlyMemory? bytePayload = null,
+ bool payloadHashed = false)
+ {
+ CoseHashV hash;
+ string extendedContentType = ExtendContentType(contentType);
+ if (!payloadHashed)
+ {
+ hash = streamPayload != null
+ ? new CoseHashV(InternalCoseHashAlgorithm, streamPayload)
+ : new CoseHashV(InternalCoseHashAlgorithm, bytePayload!.Value);
+ }
+ else
+ {
+ byte[] rawHash = streamPayload != null
+ ? streamPayload.GetBytes()
+ : bytePayload!.Value.ToArray();
+
+ if (rawHash.Length != HashLength)
+ {
+ throw new ArgumentException($"{nameof(payloadHashed)} is set, but payload length {rawHash.Length} does not correspond to the hash size for {InternalHashAlgorithmName} of {HashLength}.");
+ }
+
+ hash = new CoseHashV();
+ hash.Algorithm = InternalCoseHashAlgorithm;
+ hash.HashValue = rawHash;
+ }
+
+
+ return returnBytes
+ // return the raw bytes if asked
+ ? InternalMessageFactory.CreateCoseSign1MessageBytes(
+ hash.Serialize(),
+ signingKeyProvider,
+ embedPayload: true,
+ contentType: extendedContentType)
+ // return the CoseSign1Message object
+ : InternalMessageFactory.CreateCoseSign1Message(
+ hash.Serialize(),
+ signingKeyProvider,
+ embedPayload: true,
+ contentType: extendedContentType);
+ }
+
+ ///
+ /// Does the heavy lifting for this class in computing the hash and creating the correct representation of the CoseSign1Message base on input.
+ ///
+ /// True if ReadOnlyMemory form of CoseSign1Message is to be returned, False for a proper CoseSign1Message
+ /// The signing key provider used for COSE signing operations.
+ /// The user specified content type.
+ /// If not null, then Stream API's on the CoseSign1MessageFactory are used.
+ /// If streamPayload is null then this must be specified and must not be null and will use the Byte API's on the CoseSign1MesssageFactory
+ /// True if the payload represents the raw hash
+ /// Either a CoseSign1Message or a ReadOnlyMemory{byte} representing the CoseSign1Message object.
+ /// The contentType parameter was empty or null
+ /// Either streamPayload or bytePayload must be specified, but not both at the same time, or both cannot be null
+ /// payloadHashed is set, but hash size does not correspond to any known hash algorithms
+ private object CreateIndirectSignatureWithChecksInternalOldFormat(
+ bool returnBytes,
+ ICoseSigningKeyProvider signingKeyProvider,
+ string contentType,
+ Stream? streamPayload = null,
+ ReadOnlyMemory? bytePayload = null,
+ bool payloadHashed = false)
+ {
ReadOnlyMemory hash;
string extendedContentType;
if (!payloadHashed)
@@ -395,7 +569,7 @@ private object CreateDetachedSignatureWithChecksInternal(
hash = streamPayload != null
? InternalHashAlgorithm.ComputeHash(streamPayload)
: InternalHashAlgorithm.ComputeHash(bytePayload!.Value.ToArray());
- extendedContentType = ExtendContentType(contentType);
+ extendedContentType = ExtendContentTypeOld(contentType);
}
else
{
@@ -405,7 +579,7 @@ private object CreateDetachedSignatureWithChecksInternal(
try
{
HashAlgorithmName algoName = SizeInBytesToAlgorithm[hash.Length];
- extendedContentType = ExtendContentType(contentType, algoName);
+ extendedContentType = ExtendContentTypeOld(contentType, algoName);
}
catch (KeyNotFoundException e)
{
@@ -445,20 +619,19 @@ private object CreateDetachedSignatureWithChecksInternal(
/// quick lookup map between algorithm name and mime extension
///
private static readonly ConcurrentDictionary MimeExtensionMap = new(
- new Dictionary()
+ new Dictionary()
{
{ HashAlgorithmName.SHA256.Name, "+hash-sha256" },
{ HashAlgorithmName.SHA384.Name, "+hash-sha384" },
{ HashAlgorithmName.SHA512.Name, "+hash-sha512" }
});
- private bool DisposedValue;
///
/// Method which produces a mime type extension based on the given content type and hash algorithm name.
///
/// The content type to append the hash value to if not already appended.
/// A string representing the content type with an appended hash algorithm
- private string ExtendContentType(string contentType) => ExtendContentType(contentType, InternalHashAlgorithmName);
+ private string ExtendContentTypeOld(string contentType) => ExtendContentTypeOld(contentType, InternalHashAlgorithmName);
///
/// Method which produces a mime type extension based on the given content type and hash algorithm name.
@@ -466,7 +639,7 @@ private object CreateDetachedSignatureWithChecksInternal(
/// The content type to append the hash value to if not already appended.
/// The "HashAlgorithmName" to append if not already appended.
/// A string representing the content type with an appended hash algorithm
- private static string ExtendContentType(string contentType, HashAlgorithmName algorithmName)
+ private static string ExtendContentTypeOld(string contentType, HashAlgorithmName algorithmName)
{
// extract from the string cache to keep string allocations down.
string extensionMapping = MimeExtensionMap.GetOrAdd(algorithmName.Name, (name) => $"+hash-{name.ToLowerInvariant()}");
@@ -479,6 +652,22 @@ private static string ExtendContentType(string contentType, HashAlgorithmName al
: $"{contentType}{extensionMapping}";
}
+ ///
+ /// Method which produces a mime type extension for cose_hash_v
+ ///
+ /// The content type to append the cose_hash_v extension to if not already appended.
+ /// A string representing the content type with an appended cose_hash_v extension
+ private static string ExtendContentType(string contentType)
+ {
+ // only add the extension mapping, if it's not already present within the contentType
+ bool alreadyPresent = contentType.IndexOf("+cose-hash-v", StringComparison.InvariantCultureIgnoreCase) != -1;
+
+ return alreadyPresent
+ ? contentType
+ : $"{contentType}+cose-hash-v";
+ }
+
+ private bool DisposedValue;
///
/// Dispose pattern implementation
///
diff --git a/CoseDetachedSignature/Usings.cs b/CoseIndirectSignature/Usings.cs
similarity index 92%
rename from CoseDetachedSignature/Usings.cs
rename to CoseIndirectSignature/Usings.cs
index b063cfd1..bd3812a5 100644
--- a/CoseDetachedSignature/Usings.cs
+++ b/CoseIndirectSignature/Usings.cs
@@ -12,7 +12,7 @@
global using System.Security.Cryptography.Cose;
global using System.Text.RegularExpressions;
global using System.Threading.Tasks;
-global using CoseDetachedSignature.Extensions;
+global using CoseIndirectSignature.Extensions;
global using CoseSign1.Abstractions.Interfaces;
global using CoseSign1.Extensions;
global using CoseSign1.Interfaces;
diff --git a/CoseSign1.Tests/CoseSign1.Tests.csproj b/CoseSign1.Tests/CoseSign1.Tests.csproj
index 0b3c2bbd..c2b41276 100644
--- a/CoseSign1.Tests/CoseSign1.Tests.csproj
+++ b/CoseSign1.Tests/CoseSign1.Tests.csproj
@@ -33,7 +33,7 @@
-
+
diff --git a/CoseSignTool.sln b/CoseSignTool.sln
index 33e8ee93..f92597c0 100644
--- a/CoseSignTool.sln
+++ b/CoseSignTool.sln
@@ -34,9 +34,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{36BAA2CE-A
CHANGELOG.md = CHANGELOG.md
docs\CODE_OF_CONDUCT.md = docs\CODE_OF_CONDUCT.md
docs\CONTRIBUTING.md = docs\CONTRIBUTING.md
- CoseDetachedSignature.md = CoseDetachedSignature.md
docs\CoseHandler.md = docs\CoseHandler.md
CoseSign1.Abstractions.md = CoseSign1.Abstractions.md
+ CoseIndirectSignature.md = CoseIndirectSignature.md
CoseSign1.Certificates.md = CoseSign1.Certificates.md
CoseSign1.md = CoseSign1.md
docs\CoseSignTool.md = docs\CoseSignTool.md
@@ -48,17 +48,17 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{36BAA2CE-A
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoseHandler.Tests", "CoseHandler.Tests\CoseHandler.Tests.csproj", "{DD155201-7EDA-4979-9E6D-768EB655C8A8}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoseDetachedSignature", "CoseDetachedSignature\CoseDetachedSignature.csproj", "{74F706B4-ED89-490D-8CD2-DE8E60D9CE31}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoseIndirectSignature", "CoseIndirectSignature\CoseIndirectSignature.csproj", "{74F706B4-ED89-490D-8CD2-DE8E60D9CE31}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Nuspecs", "Nuspecs", "{97AFB857-361D-4B32-856A-6190A6D1066C}"
ProjectSection(SolutionItems) = preProject
- Nuspec\CoseDetachedSignature.nuspec = Nuspec\CoseDetachedSignature.nuspec
Nuspec\CoseSign1.Abstractions.nuspec = Nuspec\CoseSign1.Abstractions.nuspec
+ Nuspec\CoseIndirectSignature.nuspec = Nuspec\CoseIndirectSignature.nuspec
Nuspec\CoseSign1.Certificates.nuspec = Nuspec\CoseSign1.Certificates.nuspec
Nuspec\CoseSign1.nuspec = Nuspec\CoseSign1.nuspec
EndProjectSection
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoseDetachedSignature.Tests", "CoseDetachedSIgnature.Tests\CoseDetachedSignature.Tests.csproj", "{58984C00-6EA6-47ED-AF9D-717B187A168B}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoseIndirectSignature.Tests", "CoseIndirectSignature.Tests\CoseIndirectSignature.Tests.csproj", "{58984C00-6EA6-47ED-AF9D-717B187A168B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Workflows", "Workflows", "{2E02B2A3-69D1-4460-9329-91540D7B56B5}"
ProjectSection(SolutionItems) = preProject
diff --git a/CoseSignTool.tests/ValidateCommandTests.cs b/CoseSignTool.tests/ValidateCommandTests.cs
index 857f4fd7..aecfdcc7 100644
--- a/CoseSignTool.tests/ValidateCommandTests.cs
+++ b/CoseSignTool.tests/ValidateCommandTests.cs
@@ -5,7 +5,7 @@ namespace CoseSignUnitTests;
using System;
using System.Linq;
-using CoseDetachedSignature;
+using CoseIndirectSignature;
using CoseSign1.Certificates.Local;
using CoseX509;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -165,8 +165,8 @@ public void ValidateUntrustedFails()
public void ValidateIndirectSucceedsWithRootPassedIn()
{
// sign indirectly
- var msgFac = new DetachedSignatureFactory();
- byte[] signedBytes = msgFac.CreateDetachedSignatureBytes(
+ var msgFac = new IndirectSignatureFactory();
+ byte[] signedBytes = msgFac.CreateIndirectSignatureBytes(
payload: File.ReadAllBytes(PayloadFile),
contentType: "application/spdx+json",
signingKeyProvider: new X509Certificate2CoseSigningKeyProvider(SelfSignedCert)).ToArray();
@@ -195,8 +195,8 @@ public void ValidateIndirectSucceedsWithRootPassedIn()
public void ValidateIndirectFailsWithoutPayloadPassedIn()
{
// sign indirectly
- var msgFac = new DetachedSignatureFactory();
- byte[] signedBytes = msgFac.CreateDetachedSignatureBytes(
+ var msgFac = new IndirectSignatureFactory();
+ byte[] signedBytes = msgFac.CreateIndirectSignatureBytes(
payload: File.ReadAllBytes(PayloadFile),
contentType: "application/spdx+json",
signingKeyProvider: new X509Certificate2CoseSigningKeyProvider(SelfSignedCert)).ToArray();
@@ -226,8 +226,8 @@ public void ValidateIndirectFailsWithoutPayloadPassedIn()
public void ValidateIndirectFailsWithModifiedPayload()
{
// sign indirectly
- var msgFac = new DetachedSignatureFactory();
- byte[] signedBytes = msgFac.CreateDetachedSignatureBytes(
+ var msgFac = new IndirectSignatureFactory();
+ byte[] signedBytes = msgFac.CreateIndirectSignatureBytes(
payload: File.ReadAllBytes(PayloadFile),
contentType: "application/spdx+json",
signingKeyProvider: new X509Certificate2CoseSigningKeyProvider(SelfSignedCert)).ToArray();
@@ -259,8 +259,8 @@ public void ValidateIndirectFailsWithModifiedPayload()
public void ValidateIndirectFailsWithUntrustedRoot()
{
// sign indirectly
- var msgFac = new DetachedSignatureFactory();
- byte[] signedBytes = msgFac.CreateDetachedSignatureBytes(
+ var msgFac = new IndirectSignatureFactory();
+ byte[] signedBytes = msgFac.CreateIndirectSignatureBytes(
payload: File.ReadAllBytes(PayloadFile),
contentType: "application/spdx+json",
signingKeyProvider: new X509Certificate2CoseSigningKeyProvider(SelfSignedCert)).ToArray();
diff --git a/Nuspec/CoseDetachedSignature.nuspec b/Nuspec/CoseIndirectSignature.nuspec
similarity index 85%
rename from Nuspec/CoseDetachedSignature.nuspec
rename to Nuspec/CoseIndirectSignature.nuspec
index e014f43a..c5cc2266 100644
--- a/Nuspec/CoseDetachedSignature.nuspec
+++ b/Nuspec/CoseIndirectSignature.nuspec
@@ -1,12 +1,12 @@
- CoseDetachedSignature
+ CoseIndirectSignature
$VersionNgt$
Microsoft
false
-Abstractions and classes required to manage detached signatures via COSE Sign1 message envelopes in a way that is compatible with Supply Chain Integrity Transparency and Trust (SCITT).
+Abstractions and classes required to manage indirect signatures via COSE Sign1 message envelopes in a way that is compatible with Supply Chain Integrity Transparency and Trust (SCITT).
@@ -15,8 +15,8 @@ Abstractions and classes required to manage detached signatures via COSE Sign1 m
-
-
+
+