Skip to content

Commit 1832158

Browse files
JeromyStJstatia
andauthored
Add unit tests for CoseSign1MessageExtensions and enhance CertificateCoseHeaderLabels accessibility (#133)
Co-authored-by: Jeromy Statia (from Dev Box) <jstatia@microsoft.com>
1 parent 50a10e4 commit 1832158

File tree

3 files changed

+480
-7
lines changed

3 files changed

+480
-7
lines changed
Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
namespace CoseSign1.Certificates.Tests;
5+
6+
using System;
7+
using System.IO;
8+
using System.Security.Cryptography;
9+
using System.Security.Cryptography.Cose;
10+
using System.Security.Cryptography.X509Certificates;
11+
using CoseSign1.Certificates.Extensions;
12+
13+
/// <summary>
14+
/// Unit tests for the CoseSign1MessageExtensions methods that verify COSE_Sign1 messages with embedded certificates.
15+
/// Covers all overloads and edge cases.
16+
/// </summary>
17+
public class CoseSign1MessageExtensionsTests
18+
{
19+
/// <summary>
20+
/// Setup method
21+
/// </summary>
22+
[SetUp]
23+
public void Setup()
24+
{
25+
}
26+
27+
private static CoseSign1MessageFactory CoseSign1MessageFactory = new CoseSign1MessageFactory();
28+
// Helper: Create a valid COSE_Sign1 message with embedded certificate and content
29+
private static CoseSign1Message CreateValidCoseSign1Message(byte[] content, X509Certificate2 cert, out byte[] coseBytes, bool detached = false)
30+
{
31+
X509Certificate2CoseSigningKeyProvider testObjRsa = new(cert);
32+
coseBytes = CoseSign1MessageFactory.CreateCoseSign1MessageBytes(content, testObjRsa, !detached).ToArray();
33+
return CoseMessage.DecodeSign1(coseBytes);
34+
}
35+
36+
/// <summary>
37+
/// Helper to create a COSE_Sign1 message with NO embedded certificate (no x5c/x5t headers).
38+
/// </summary>
39+
private static CoseSign1Message CreateCoseSign1MessageWithoutCert(byte[] content)
40+
{
41+
using var rsa = RSA.Create(2048);
42+
43+
var coseSigner = new CoseSigner(rsa, RSASignaturePadding.Pkcs1, HashAlgorithmName.SHA256, new CoseHeaderMap(), new CoseHeaderMap());
44+
var coseBytes = CoseSign1Message.SignEmbedded(content, coseSigner);
45+
return CoseMessage.DecodeSign1(coseBytes);
46+
}
47+
48+
/// <summary>
49+
/// Test that VerifyEmbeddedWithCertificate returns true for a valid message with embedded certificate.
50+
/// </summary>
51+
[Test]
52+
public void VerifyEmbeddedWithCertificate_ValidMessage_ReturnsTrue()
53+
{
54+
// Arrange
55+
var cert = TestCertificateUtils.CreateCertificate();
56+
var content = new byte[] { 1, 2, 3, 4 };
57+
var msg = CreateValidCoseSign1Message(content, cert, out _);
58+
59+
// Act
60+
bool result = msg.VerifyEmbeddedWithCertificate();
61+
62+
// Assert
63+
Assert.That(result, Is.True, "Expected verification to succeed for valid message.");
64+
}
65+
66+
/// <summary>
67+
/// Test that VerifyEmbeddedWithCertificate returns false if message is null.
68+
/// </summary>
69+
[Test]
70+
public void VerifyEmbeddedWithCertificate_NullMessage_ReturnsFalse()
71+
{
72+
// Arrange
73+
CoseSign1Message? msg = null!;
74+
75+
// Act & Assert
76+
Assert.That(msg.VerifyEmbeddedWithCertificate(), Is.False, "Expected false when message is null.");
77+
}
78+
79+
/// <summary>
80+
/// Test that VerifyEmbeddedWithCertificate returns false if no embedded certificate is present.
81+
/// </summary>
82+
[Test]
83+
public void VerifyEmbeddedWithCertificate_NoCertificate_ReturnsFalse()
84+
{
85+
// Arrange
86+
var content = new byte[] { 1, 2, 3, 4 };
87+
var msg = CreateCoseSign1MessageWithoutCert(content);
88+
// Act
89+
bool result = msg.VerifyEmbeddedWithCertificate();
90+
// Assert
91+
Assert.That(result, Is.False, "Expected verification to fail when no certificate is present.");
92+
}
93+
94+
/// <summary>
95+
/// Test that VerifyDetachedWithCertificate (byte[]) returns true for valid message and content.
96+
/// </summary>
97+
[Test]
98+
public void VerifyDetachedWithCertificate_ByteArray_Valid_ReturnsTrue()
99+
{
100+
// Arrange
101+
var cert = TestCertificateUtils.CreateCertificate();
102+
var content = new byte[] { 10, 20, 30 };
103+
var msg = CreateValidCoseSign1Message(content, cert, out _, detached: true); // Detached
104+
// Act
105+
bool result = msg.VerifyDetachedWithCertificate(content);
106+
// Assert
107+
Assert.That(result, Is.True, "Expected verification to succeed for valid detached message.");
108+
}
109+
110+
/// <summary>
111+
/// Test that VerifyDetachedWithCertificate (byte[]) returns false if message is null.
112+
/// </summary>
113+
[Test]
114+
public void VerifyDetachedWithCertificate_ByteArray_NullMessage_ReturnsFalse()
115+
{
116+
// Arrange
117+
CoseSign1Message msg = null!;
118+
var content = new byte[] { 1, 2 };
119+
// Act & Assert
120+
Assert.That(msg.VerifyDetachedWithCertificate(content), Is.False, "Expected false when message is null.");
121+
}
122+
123+
/// <summary>
124+
/// Test that VerifyDetachedWithCertificate (byte[]) returns false if content is null.
125+
/// </summary>
126+
[Test]
127+
public void VerifyDetachedWithCertificate_ByteArray_NullContent_ReturnsFalse()
128+
{
129+
// Arrange
130+
var cert = TestCertificateUtils.CreateCertificate();
131+
var content = new byte[] { 1, 2 };
132+
var msg = CreateValidCoseSign1Message(content, cert, out _, detached: true); // Detached
133+
// Act & Assert
134+
Assert.That(msg.VerifyDetachedWithCertificate((byte[])null!), Is.False, "Expected false when detached content is null.");
135+
}
136+
137+
/// <summary>
138+
/// Test that VerifyDetachedWithCertificate (ReadOnlySpan) returns true for valid message and content.
139+
/// </summary>
140+
[Test]
141+
public void VerifyDetachedWithCertificate_ReadOnlySpan_Valid_ReturnsTrue()
142+
{
143+
// Arrange
144+
var cert = TestCertificateUtils.CreateCertificate();
145+
var content = new byte[] { 42, 43, 44 };
146+
var msg = CreateValidCoseSign1Message(content, cert, out _, detached: true); // Detached
147+
// Act
148+
bool result = msg.VerifyDetachedWithCertificate(content.AsSpan());
149+
// Assert
150+
Assert.That(result, Is.True, "Expected verification to succeed for valid detached message.");
151+
}
152+
153+
/// <summary>
154+
/// Test that VerifyDetachedWithCertificate (ReadOnlySpan) returns false if content is empty.
155+
/// </summary>
156+
[Test]
157+
public void VerifyDetachedWithCertificate_ReadOnlySpan_EmptyContent_ReturnsFalse()
158+
{
159+
// Arrange
160+
var cert = TestCertificateUtils.CreateCertificate();
161+
var contentCreate = new byte[] { 99, 100, 101 };
162+
var contentVerify = Array.Empty<byte>();
163+
var msg = CreateValidCoseSign1Message(contentCreate, cert, out _, detached: true); // Detached
164+
// Act
165+
bool result = msg.VerifyDetachedWithCertificate(contentVerify.AsSpan());
166+
// Assert
167+
Assert.That(result, Is.False, "Expected verification to fail for empty detached content.");
168+
}
169+
170+
/// <summary>
171+
/// Test that VerifyDetachedWithCertificate (Stream) returns true for valid message and content.
172+
/// </summary>
173+
[Test]
174+
public void VerifyDetachedWithCertificate_Stream_Valid_ReturnsTrue()
175+
{
176+
// Arrange
177+
var cert = TestCertificateUtils.CreateCertificate();
178+
var content = new byte[] { 99, 100, 101 };
179+
using var stream = new MemoryStream(content);
180+
var msg = CreateValidCoseSign1Message(content, cert, out _, detached: true); // Detached
181+
// Act
182+
bool result = msg.VerifyDetachedWithCertificate(stream);
183+
// Assert
184+
Assert.That(result, Is.True, "Expected verification to succeed for valid detached message.");
185+
}
186+
187+
/// <summary>
188+
/// Test that VerifyDetachedWithCertificate (Stream) returns false if message is null.
189+
/// </summary>
190+
[Test]
191+
public void VerifyDetachedWithCertificate_Stream_NullMessage_ReturnsFalse()
192+
{
193+
// Arrange
194+
CoseSign1Message msg = null!;
195+
using var stream = new MemoryStream(new byte[] { 1 });
196+
// Act & Assert
197+
Assert.That(msg.VerifyDetachedWithCertificate(stream), Is.False, "Expected false when message is null.");
198+
}
199+
200+
/// <summary>
201+
/// Test that VerifyDetachedWithCertificate (Stream) returns false if stream is null.
202+
/// </summary>
203+
[Test]
204+
public void VerifyDetachedWithCertificate_Stream_NullStream_ReturnsFalse()
205+
{
206+
// Arrange
207+
var cert = TestCertificateUtils.CreateCertificate();
208+
var content = new byte[] { 99, 100, 101 };
209+
var msg = CreateValidCoseSign1Message(content, cert, out _, detached: true); // Detached
210+
// Act & Assert
211+
Assert.That(msg.VerifyDetachedWithCertificate((Stream)null!), Is.False, "Expected false when detached stream is null.");
212+
}
213+
214+
/// <summary>
215+
/// Test that VerifyEmbeddedWithCertificate returns false for a detached message (Content is null).
216+
/// </summary>
217+
[Test]
218+
public void VerifyEmbeddedWithCertificate_DetachedMessage_ReturnsFalse()
219+
{
220+
// Arrange
221+
var cert = TestCertificateUtils.CreateCertificate();
222+
var content = new byte[] { 1, 2, 3, 4 };
223+
byte[] coseBytes;
224+
// Create a detached message: pass null as content and detached=true
225+
var msg = CreateValidCoseSign1Message(content, cert, out coseBytes, detached: true);
226+
227+
// Act
228+
bool result = msg.VerifyEmbeddedWithCertificate();
229+
230+
// Assert
231+
Assert.That(result, Is.False, "Expected verification to fail for detached message (no content).");
232+
}
233+
234+
/// <summary>
235+
/// Test that VerifyDetachedWithCertificate (byte[]) returns false if no signing certificate is present.
236+
/// </summary>
237+
[Test]
238+
public void VerifyDetachedWithCertificate_ByteArray_NoCertificate_ReturnsFalse()
239+
{
240+
// Arrange
241+
var content = new byte[] { 1, 2, 3 };
242+
var msg = CreateCoseSign1MessageWithoutCert(content); // No cert in headers
243+
// Act
244+
bool result = msg.VerifyDetachedWithCertificate(content);
245+
// Assert
246+
Assert.That(result, Is.False, "Expected verification to fail when no signing certificate is present.");
247+
}
248+
249+
/// <summary>
250+
/// Test that VerifyDetachedWithCertificate (ReadOnlySpan) returns false if no signing certificate is present.
251+
/// </summary>
252+
[Test]
253+
public void VerifyDetachedWithCertificate_ReadOnlySpan_NoCertificate_ReturnsFalse()
254+
{
255+
// Arrange
256+
var content = new byte[] { 1, 2, 3 };
257+
var msg = CreateCoseSign1MessageWithoutCert(content); // No cert in headers
258+
// Act
259+
bool result = msg.VerifyDetachedWithCertificate(content.AsSpan());
260+
// Assert
261+
Assert.That(result, Is.False, "Expected verification to fail when no signing certificate is present.");
262+
}
263+
264+
/// <summary>
265+
/// Test that VerifyDetachedWithCertificate (Stream) returns false if no signing certificate is present.
266+
/// </summary>
267+
[Test]
268+
public void VerifyDetachedWithCertificate_Stream_NoCertificate_ReturnsFalse()
269+
{
270+
// Arrange
271+
var content = new byte[] { 1, 2, 3 };
272+
using var stream = new MemoryStream(content);
273+
var msg = CreateCoseSign1MessageWithoutCert(content); // No cert in headers
274+
// Act
275+
bool result = msg.VerifyDetachedWithCertificate(stream);
276+
// Assert
277+
Assert.That(result, Is.False, "Expected verification to fail when no signing certificate is present.");
278+
}
279+
280+
/// <summary>
281+
/// Test that VerifyDetachedWithCertificate (Stream) returns false if no signing certificate is present or public key is null.
282+
/// </summary>
283+
[Test]
284+
public void VerifyDetachedWithCertificate_Stream_NoCertificateOrPublicKey_ReturnsFalse()
285+
{
286+
// Arrange
287+
var content = new byte[] { 1, 2, 3 };
288+
using var stream = new MemoryStream(content);
289+
var msg = CreateCoseSign1MessageWithoutCert(content); // No cert in headers
290+
// Act
291+
bool result = msg.VerifyDetachedWithCertificate(stream);
292+
// Assert
293+
Assert.That(result, Is.False, "Expected verification to fail when no signing certificate or public key is present.");
294+
}
295+
}

CoseSign1.Certificates/CertificateCoseHeaderLabels.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,21 @@ namespace CoseSign1.Certificates;
66
/// <summary>
77
/// <see cref="CoseHeaderLabel"/> objects which are specific to certificate signed <see cref="CoseSign1Message"/> objects.
88
/// </summary>
9-
internal class CertificateCoseHeaderLabels
9+
public static class CertificateCoseHeaderLabels
1010
{
1111
// Taken from https://www.iana.org/assignments/cose/cose.xhtml
1212
/// <summary>
1313
/// Represents an unordered list of certificates.
1414
/// </summary>
15-
internal static readonly CoseHeaderLabel X5Bag = new(32);
15+
public static readonly CoseHeaderLabel X5Bag = new(32);
16+
1617
/// <summary>
1718
/// Represents an ordered list (leaf first) of the certificate chain for the certificate used to sign the <see cref="CoseSign1Message"/> object.
1819
/// </summary>
19-
internal static readonly CoseHeaderLabel X5Chain = new(33);
20+
public static readonly CoseHeaderLabel X5Chain = new(33);
21+
2022
/// <summary>
2123
/// Represents the thumbprint for the certificate used to sign the <see cref="CoseSign1Message"/> object.
2224
/// </summary>
23-
internal static readonly CoseHeaderLabel X5T = new(34);
25+
public static readonly CoseHeaderLabel X5T = new(34);
2426
}

0 commit comments

Comments
 (0)