Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
295 changes: 295 additions & 0 deletions CoseSign1.Certificates.Tests/CoseSign1MessageExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,295 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace CoseSign1.Certificates.Tests;

using System;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.Cose;
using System.Security.Cryptography.X509Certificates;
using CoseSign1.Certificates.Extensions;

/// <summary>
/// Unit tests for the CoseSign1MessageExtensions methods that verify COSE_Sign1 messages with embedded certificates.
/// Covers all overloads and edge cases.
/// </summary>
public class CoseSign1MessageExtensionsTests
{
/// <summary>
/// Setup method
/// </summary>
[SetUp]
public void Setup()
{
}

private static CoseSign1MessageFactory CoseSign1MessageFactory = new CoseSign1MessageFactory();
Comment thread Dismissed
// Helper: Create a valid COSE_Sign1 message with embedded certificate and content
private static CoseSign1Message CreateValidCoseSign1Message(byte[] content, X509Certificate2 cert, out byte[] coseBytes, bool detached = false)
{
X509Certificate2CoseSigningKeyProvider testObjRsa = new(cert);
coseBytes = CoseSign1MessageFactory.CreateCoseSign1MessageBytes(content, testObjRsa, !detached).ToArray();
return CoseMessage.DecodeSign1(coseBytes);
}

/// <summary>
/// Helper to create a COSE_Sign1 message with NO embedded certificate (no x5c/x5t headers).
/// </summary>
private static CoseSign1Message CreateCoseSign1MessageWithoutCert(byte[] content)
{
using var rsa = RSA.Create(2048);

var coseSigner = new CoseSigner(rsa, RSASignaturePadding.Pkcs1, HashAlgorithmName.SHA256, new CoseHeaderMap(), new CoseHeaderMap());
var coseBytes = CoseSign1Message.SignEmbedded(content, coseSigner);
return CoseMessage.DecodeSign1(coseBytes);
}

/// <summary>
/// Test that VerifyEmbeddedWithCertificate returns true for a valid message with embedded certificate.
/// </summary>
[Test]
public void VerifyEmbeddedWithCertificate_ValidMessage_ReturnsTrue()
{
// Arrange
var cert = TestCertificateUtils.CreateCertificate();
var content = new byte[] { 1, 2, 3, 4 };
var msg = CreateValidCoseSign1Message(content, cert, out _);

// Act
bool result = msg.VerifyEmbeddedWithCertificate();

// Assert
Assert.That(result, Is.True, "Expected verification to succeed for valid message.");
}

/// <summary>
/// Test that VerifyEmbeddedWithCertificate returns false if message is null.
/// </summary>
[Test]
public void VerifyEmbeddedWithCertificate_NullMessage_ReturnsFalse()
{
// Arrange
CoseSign1Message? msg = null!;

// Act & Assert
Assert.That(msg.VerifyEmbeddedWithCertificate(), Is.False, "Expected false when message is null.");
}

/// <summary>
/// Test that VerifyEmbeddedWithCertificate returns false if no embedded certificate is present.
/// </summary>
[Test]
public void VerifyEmbeddedWithCertificate_NoCertificate_ReturnsFalse()
{
// Arrange
var content = new byte[] { 1, 2, 3, 4 };
var msg = CreateCoseSign1MessageWithoutCert(content);
// Act
bool result = msg.VerifyEmbeddedWithCertificate();
// Assert
Assert.That(result, Is.False, "Expected verification to fail when no certificate is present.");
}

/// <summary>
/// Test that VerifyDetachedWithCertificate (byte[]) returns true for valid message and content.
/// </summary>
[Test]
public void VerifyDetachedWithCertificate_ByteArray_Valid_ReturnsTrue()
{
// Arrange
var cert = TestCertificateUtils.CreateCertificate();
var content = new byte[] { 10, 20, 30 };
var msg = CreateValidCoseSign1Message(content, cert, out _, detached: true); // Detached
// Act
bool result = msg.VerifyDetachedWithCertificate(content);
// Assert
Assert.That(result, Is.True, "Expected verification to succeed for valid detached message.");
}

/// <summary>
/// Test that VerifyDetachedWithCertificate (byte[]) returns false if message is null.
/// </summary>
[Test]
public void VerifyDetachedWithCertificate_ByteArray_NullMessage_ReturnsFalse()
{
// Arrange
CoseSign1Message msg = null!;
var content = new byte[] { 1, 2 };
// Act & Assert
Assert.That(msg.VerifyDetachedWithCertificate(content), Is.False, "Expected false when message is null.");
}

/// <summary>
/// Test that VerifyDetachedWithCertificate (byte[]) returns false if content is null.
/// </summary>
[Test]
public void VerifyDetachedWithCertificate_ByteArray_NullContent_ReturnsFalse()
{
// Arrange
var cert = TestCertificateUtils.CreateCertificate();
var content = new byte[] { 1, 2 };
var msg = CreateValidCoseSign1Message(content, cert, out _, detached: true); // Detached
// Act & Assert
Assert.That(msg.VerifyDetachedWithCertificate((byte[])null!), Is.False, "Expected false when detached content is null.");
}

/// <summary>
/// Test that VerifyDetachedWithCertificate (ReadOnlySpan) returns true for valid message and content.
/// </summary>
[Test]
public void VerifyDetachedWithCertificate_ReadOnlySpan_Valid_ReturnsTrue()
{
// Arrange
var cert = TestCertificateUtils.CreateCertificate();
var content = new byte[] { 42, 43, 44 };
var msg = CreateValidCoseSign1Message(content, cert, out _, detached: true); // Detached
// Act
bool result = msg.VerifyDetachedWithCertificate(content.AsSpan());
// Assert
Assert.That(result, Is.True, "Expected verification to succeed for valid detached message.");
}

/// <summary>
/// Test that VerifyDetachedWithCertificate (ReadOnlySpan) returns false if content is empty.
/// </summary>
[Test]
public void VerifyDetachedWithCertificate_ReadOnlySpan_EmptyContent_ReturnsFalse()
{
// Arrange
var cert = TestCertificateUtils.CreateCertificate();
var contentCreate = new byte[] { 99, 100, 101 };
var contentVerify = Array.Empty<byte>();
var msg = CreateValidCoseSign1Message(contentCreate, cert, out _, detached: true); // Detached
// Act
bool result = msg.VerifyDetachedWithCertificate(contentVerify.AsSpan());
// Assert
Assert.That(result, Is.False, "Expected verification to fail for empty detached content.");
}

/// <summary>
/// Test that VerifyDetachedWithCertificate (Stream) returns true for valid message and content.
/// </summary>
[Test]
public void VerifyDetachedWithCertificate_Stream_Valid_ReturnsTrue()
{
// Arrange
var cert = TestCertificateUtils.CreateCertificate();
var content = new byte[] { 99, 100, 101 };
using var stream = new MemoryStream(content);
var msg = CreateValidCoseSign1Message(content, cert, out _, detached: true); // Detached
// Act
bool result = msg.VerifyDetachedWithCertificate(stream);
// Assert
Assert.That(result, Is.True, "Expected verification to succeed for valid detached message.");
}

/// <summary>
/// Test that VerifyDetachedWithCertificate (Stream) returns false if message is null.
/// </summary>
[Test]
public void VerifyDetachedWithCertificate_Stream_NullMessage_ReturnsFalse()
{
// Arrange
CoseSign1Message msg = null!;
using var stream = new MemoryStream(new byte[] { 1 });
// Act & Assert
Assert.That(msg.VerifyDetachedWithCertificate(stream), Is.False, "Expected false when message is null.");
}

/// <summary>
/// Test that VerifyDetachedWithCertificate (Stream) returns false if stream is null.
/// </summary>
[Test]
public void VerifyDetachedWithCertificate_Stream_NullStream_ReturnsFalse()
{
// Arrange
var cert = TestCertificateUtils.CreateCertificate();
var content = new byte[] { 99, 100, 101 };
var msg = CreateValidCoseSign1Message(content, cert, out _, detached: true); // Detached
// Act & Assert
Assert.That(msg.VerifyDetachedWithCertificate((Stream)null!), Is.False, "Expected false when detached stream is null.");
}

/// <summary>
/// Test that VerifyEmbeddedWithCertificate returns false for a detached message (Content is null).
/// </summary>
[Test]
public void VerifyEmbeddedWithCertificate_DetachedMessage_ReturnsFalse()
{
// Arrange
var cert = TestCertificateUtils.CreateCertificate();
var content = new byte[] { 1, 2, 3, 4 };
byte[] coseBytes;
// Create a detached message: pass null as content and detached=true
var msg = CreateValidCoseSign1Message(content, cert, out coseBytes, detached: true);

// Act
bool result = msg.VerifyEmbeddedWithCertificate();

// Assert
Assert.That(result, Is.False, "Expected verification to fail for detached message (no content).");
}

/// <summary>
/// Test that VerifyDetachedWithCertificate (byte[]) returns false if no signing certificate is present.
/// </summary>
[Test]
public void VerifyDetachedWithCertificate_ByteArray_NoCertificate_ReturnsFalse()
{
// Arrange
var content = new byte[] { 1, 2, 3 };
var msg = CreateCoseSign1MessageWithoutCert(content); // No cert in headers
// Act
bool result = msg.VerifyDetachedWithCertificate(content);
// Assert
Assert.That(result, Is.False, "Expected verification to fail when no signing certificate is present.");
}

/// <summary>
/// Test that VerifyDetachedWithCertificate (ReadOnlySpan) returns false if no signing certificate is present.
/// </summary>
[Test]
public void VerifyDetachedWithCertificate_ReadOnlySpan_NoCertificate_ReturnsFalse()
{
// Arrange
var content = new byte[] { 1, 2, 3 };
var msg = CreateCoseSign1MessageWithoutCert(content); // No cert in headers
// Act
bool result = msg.VerifyDetachedWithCertificate(content.AsSpan());
// Assert
Assert.That(result, Is.False, "Expected verification to fail when no signing certificate is present.");
}

/// <summary>
/// Test that VerifyDetachedWithCertificate (Stream) returns false if no signing certificate is present.
/// </summary>
[Test]
public void VerifyDetachedWithCertificate_Stream_NoCertificate_ReturnsFalse()
{
// Arrange
var content = new byte[] { 1, 2, 3 };
using var stream = new MemoryStream(content);
var msg = CreateCoseSign1MessageWithoutCert(content); // No cert in headers
// Act
bool result = msg.VerifyDetachedWithCertificate(stream);
// Assert
Assert.That(result, Is.False, "Expected verification to fail when no signing certificate is present.");
}

/// <summary>
/// Test that VerifyDetachedWithCertificate (Stream) returns false if no signing certificate is present or public key is null.
/// </summary>
[Test]
public void VerifyDetachedWithCertificate_Stream_NoCertificateOrPublicKey_ReturnsFalse()
{
// Arrange
var content = new byte[] { 1, 2, 3 };
using var stream = new MemoryStream(content);
var msg = CreateCoseSign1MessageWithoutCert(content); // No cert in headers
// Act
bool result = msg.VerifyDetachedWithCertificate(stream);
// Assert
Assert.That(result, Is.False, "Expected verification to fail when no signing certificate or public key is present.");
}
}
10 changes: 6 additions & 4 deletions CoseSign1.Certificates/CertificateCoseHeaderLabels.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,21 @@ namespace CoseSign1.Certificates;
/// <summary>
/// <see cref="CoseHeaderLabel"/> objects which are specific to certificate signed <see cref="CoseSign1Message"/> objects.
/// </summary>
internal class CertificateCoseHeaderLabels
public static class CertificateCoseHeaderLabels
{
// Taken from https://www.iana.org/assignments/cose/cose.xhtml
/// <summary>
/// Represents an unordered list of certificates.
/// </summary>
internal static readonly CoseHeaderLabel X5Bag = new(32);
public static readonly CoseHeaderLabel X5Bag = new(32);

/// <summary>
/// Represents an ordered list (leaf first) of the certificate chain for the certificate used to sign the <see cref="CoseSign1Message"/> object.
/// </summary>
internal static readonly CoseHeaderLabel X5Chain = new(33);
public static readonly CoseHeaderLabel X5Chain = new(33);

/// <summary>
/// Represents the thumbprint for the certificate used to sign the <see cref="CoseSign1Message"/> object.
/// </summary>
internal static readonly CoseHeaderLabel X5T = new(34);
public static readonly CoseHeaderLabel X5T = new(34);
}
Loading
Loading