Skip to content

ML-DSA + COSE improve tests #116543

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 24, 2025
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

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ internal static CoseKey FromUntrustedAlgorithmAndKey(CoseAlgorithm untrustedAlgo
_ => throw new CryptographicException(SR.Format(SR.Sign1UnknownCoseAlgorithm, untrustedAlgorithm))
};

static CoseKey FromKeyWithExpectedAlgorithm(MLDsaAlgorithm expected, MLDsa key)
=> key.Algorithm.Name == expected.Name ? FromKey(key) : CoseKey.FromKey(key);
CoseKey FromKeyWithExpectedAlgorithm(MLDsaAlgorithm expected, MLDsa key)
=> key.Algorithm.Name == expected.Name ? FromKey(key) : throw new CryptographicException(SR.Format(SR.Sign1UnknownCoseAlgorithm, untrustedAlgorithm));
}
#pragma warning restore SYSLIB5006
else
Expand All @@ -133,18 +133,18 @@ internal static CoseAlgorithm CoseAlgorithmFromInt64(long alg)
private static void ThrowIfCoseAlgorithmNotSupported(CoseAlgorithm alg)
{
#pragma warning disable SYSLIB5006
if (alg != CoseAlgorithm.ES256 &&
alg != CoseAlgorithm.ES384 &&
alg != CoseAlgorithm.ES512 &&
alg != CoseAlgorithm.PS256 &&
alg != CoseAlgorithm.PS384 &&
alg != CoseAlgorithm.PS512 &&
alg != CoseAlgorithm.RS256 &&
alg != CoseAlgorithm.RS384 &&
alg != CoseAlgorithm.RS512 &&
alg != CoseAlgorithm.MLDsa44 &&
alg != CoseAlgorithm.MLDsa65 &&
alg != CoseAlgorithm.MLDsa87)
if (alg is not CoseAlgorithm.ES256 and
not CoseAlgorithm.ES384 and
not CoseAlgorithm.ES512 and
not CoseAlgorithm.PS256 and
not CoseAlgorithm.PS384 and
not CoseAlgorithm.PS512 and
not CoseAlgorithm.RS256 and
not CoseAlgorithm.RS384 and
not CoseAlgorithm.RS512 and
not CoseAlgorithm.MLDsa44 and
not CoseAlgorithm.MLDsa65 and
not CoseAlgorithm.MLDsa87)
{
throw new CryptographicException(SR.Format(SR.Sign1UnknownCoseAlgorithm, alg));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public sealed class CoseSigner
/// Gets the private key to use during signing.
/// </summary>
/// <value>The private key to use during signing.</value>
public AsymmetricAlgorithm? Key { get; }
public AsymmetricAlgorithm? Key => CoseKey.AsymmetricAlgorithm;

/// <summary>
/// Gets the private key to use during signing.
Expand Down Expand Up @@ -78,9 +78,6 @@ public CoseSigner(AsymmetricAlgorithm key, HashAlgorithmName hashAlgorithm, Cose
if (key is not ECDsa)
throw new ArgumentException(SR.Format(SR.Sign1UnsupportedKey, key.GetType().Name), nameof(key));

#pragma warning disable CS0618 // Type or member is obsolete
Key = key;
#pragma warning restore CS0618 // Type or member is obsolete
CoseKey = CoseKey.FromKey((ECDsa)key, hashAlgorithm);

_protectedHeaders = protectedHeaders;
Expand Down Expand Up @@ -115,9 +112,6 @@ public CoseSigner(RSA key, RSASignaturePadding signaturePadding, HashAlgorithmNa
ArgumentNullException.ThrowIfNull(key);
ArgumentNullException.ThrowIfNull(signaturePadding);

#pragma warning disable CS0618 // Type or member is obsolete
Key = key;
#pragma warning restore CS0618 // Type or member is obsolete
CoseKey = CoseKey.FromKey(key, signaturePadding, hashAlgorithm);

_protectedHeaders = protectedHeaders;
Expand All @@ -137,9 +131,6 @@ public CoseSigner(CoseKey key, CoseHeaderMap? protectedHeaders = null, CoseHeade
if (key is null)
throw new ArgumentNullException(nameof(key));

#pragma warning disable CS0618 // Type or member is obsolete
Key = null;
#pragma warning restore CS0618 // Type or member is obsolete
CoseKey = key;

_protectedHeaders = protectedHeaders;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class CoseHeaderMapTests
public void SetValue_GetValue_KnownCoseHeaderLabel(SetValueMethod setMethod, GetValueMethod getMethod)
{
var map = new CoseHeaderMap();
SetValue(map, CoseHeaderLabel.Algorithm, (int)ECDsaAlgorithm.ES256, setMethod);
SetValue(map, CoseHeaderLabel.Algorithm, (int)CoseAlgorithm.ES256, setMethod);

if (setMethod != SetValueMethod.AddShortcut)
{
Expand All @@ -26,7 +26,7 @@ public void SetValue_GetValue_KnownCoseHeaderLabel(SetValueMethod setMethod, Get
SetValue(map, CoseHeaderLabel.ContentType, ContentTypeDummyValue, setMethod);
SetValue(map, CoseHeaderLabel.KeyIdentifier, s_sampleContent, setMethod);

Assert.Equal((int)ECDsaAlgorithm.ES256, GetValue<int>(map, CoseHeaderLabel.Algorithm, getMethod));
Assert.Equal((int)CoseAlgorithm.ES256, GetValue<int>(map, CoseHeaderLabel.Algorithm, getMethod));

if (getMethod != GetValueMethod.GetValueShortcut)
{
Expand Down Expand Up @@ -122,7 +122,7 @@ public void SetValue_InvalidCoseHeaderValue()
public void Enumerate()
{
var map = new CoseHeaderMap();
SetValue(map, CoseHeaderLabel.Algorithm, (int)ECDsaAlgorithm.ES256, default(SetValueMethod));
SetValue(map, CoseHeaderLabel.Algorithm, (int)CoseAlgorithm.ES256, default(SetValueMethod));
SetEncodedValue(map, CoseHeaderLabel.CriticalHeaders, GetDummyCritHeaderValue(), default(SetValueMethod));
SetValue(map ,CoseHeaderLabel.ContentType, ContentTypeDummyValue, default(SetValueMethod));
SetValue(map, CoseHeaderLabel.KeyIdentifier, s_sampleContent, default(SetValueMethod));
Expand All @@ -137,7 +137,7 @@ public void Enumerate()
Assert.Equal(new CoseHeaderLabel(currentHeader), label);
ReadOnlyMemory<byte> expectedValue = currentHeader switch
{
KnownHeaderAlg => EncodeInt32((int)ECDsaAlgorithm.ES256, writer),
KnownHeaderAlg => EncodeInt32((int)CoseAlgorithm.ES256, writer),
KnownHeaderCrit => GetDummyCritHeaderValue(),
KnownHeaderContentType => EncodeString(ContentTypeDummyValue, writer),
KnownHeaderKid => EncodeBytes(s_sampleContent, writer),
Expand Down Expand Up @@ -194,7 +194,7 @@ public void GetValueFromReadOnlyProtectedMap(GetValueMethod getMethod)

Assert.True(protectedHeaders.IsReadOnly, "message.ProtectedHeaders.IsReadOnly");

int expectedAlgorithm = (int)ECDsaAlgorithm.ES256;
int expectedAlgorithm = (int)CoseAlgorithm.ES256;
int algorithm = GetValue<int>(protectedHeaders, CoseHeaderLabel.Algorithm, getMethod);
Assert.Equal(expectedAlgorithm, algorithm);

Expand All @@ -221,7 +221,7 @@ public void SetValueAndRemoveAndClearThrowIfProtectedMapIsReadOnly()
VerifyThrows(protectedHeaders, CoseHeaderLabel.Algorithm);

// Verify existing value was not overwritten even after throwing.
Assert.Equal((int)ECDsaAlgorithm.ES256, GetValue<int>(protectedHeaders, CoseHeaderLabel.Algorithm, default(GetValueMethod)));
Assert.Equal((int)CoseAlgorithm.ES256, GetValue<int>(protectedHeaders, CoseHeaderLabel.Algorithm, default(GetValueMethod)));

// Non-readonly header works correctly.
CoseHeaderMap unprotectedHeaders = message.UnprotectedHeaders;
Expand Down Expand Up @@ -525,7 +525,7 @@ public static IEnumerable<object[]> KnownHeadersEncodedValues_TestData()

foreach ((SetValueMethod setMethod, GetValueMethod getMethod) in setGetValuePairs)
{
writer.WriteInt32((int)ECDsaAlgorithm.ES256);
writer.WriteInt32((int)CoseAlgorithm.ES256);
yield return ReturnDataAndReset(KnownHeaderAlg, writer, setMethod, getMethod);

WriteDummyCritHeaderValue(writer, useIndefiniteLength: false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public void GetValueAsInt32Overflows(long value)
[InlineData(0)]
[InlineData(int.MaxValue)]
[InlineData(int.MinValue)]
[InlineData((int)ECDsaAlgorithm.ES256)]
[InlineData((int)CoseAlgorithm.ES256)]
public void GetValueAsInt32Succeeds(int value)
{
var writer = new CborWriter();
Expand Down Expand Up @@ -193,7 +193,7 @@ public enum GetValueAs
[InlineData(0)]
[InlineData(int.MaxValue)]
[InlineData(int.MinValue)]
[InlineData((int)ECDsaAlgorithm.ES256)]
[InlineData((int)CoseAlgorithm.ES256)]
public void FromInt32Succeeds(int value)
{
var writer = new CborWriter();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#pragma warning disable SYSLIB5006

using System;
using System.Collections.Generic;
using System.Formats.Cbor;
Expand All @@ -23,12 +21,10 @@ public void CreateCoseKeyWithNullArg_Throws()
AssertExtensions.Throws<ArgumentNullException>("key", static () => CoseKey.FromKey((ECDsa)null!, HashAlgorithmName.SHA256));
AssertExtensions.Throws<ArgumentNullException>("key", static () => CoseKey.FromKey((RSA)null!, RSASignaturePadding.Pkcs1, HashAlgorithmName.SHA256));

RSA rsaKey = (RSA)KeyManager.GetKey(CoseTestKeyManager.RSAPkcs1Identifier).Key;
AssertExtensions.Throws<ArgumentNullException>("signaturePadding", () => CoseKey.FromKey(rsaKey, null!, HashAlgorithmName.SHA256));
AssertExtensions.Throws<ArgumentNullException>("hashAlgorithm.Name", () => CoseKey.FromKey(rsaKey, RSASignaturePadding.Pkcs1, default));
AssertExtensions.Throws<ArgumentNullException>("signaturePadding", () => CoseKey.FromKey(CoseTestHelpers.RSAKey, null!, HashAlgorithmName.SHA256));
AssertExtensions.Throws<ArgumentNullException>("hashAlgorithm.Name", () => CoseKey.FromKey(CoseTestHelpers.RSAKey, RSASignaturePadding.Pkcs1, default));

ECDsa ecdsaKey = (ECDsa)KeyManager.GetKey(CoseTestKeyManager.ECDsaIdentifier).Key;
AssertExtensions.Throws<ArgumentNullException>("hashAlgorithm.Name", () => CoseKey.FromKey(ecdsaKey, default));
AssertExtensions.Throws<ArgumentNullException>("hashAlgorithm.Name", () => CoseKey.FromKey(CoseTestHelpers.ES256, default));
}

[Fact]
Expand All @@ -40,11 +36,12 @@ public void CreateCoseSignerWithNullCoseKey_Throws()
[Fact]
public void VerifySingleSignerWithNullCoseKey_Throws()
{
CoseTestKey key = KeyManager.GetKey(CoseTestKeyManager.ECDsaIdentifier);
CoseKey key = CoseKey.FromKey(CoseTestHelpers.ES256, HashAlgorithmName.SHA256);
CoseSigner signer = new CoseSigner(key);
byte[] payload = Encoding.UTF8.GetBytes(nameof(VerifySingleSignerWithNullCoseKey_Throws));
MemoryStream payloadStream = new(payload);
byte[] embeddedMessageBytes = CoseSign1Message.SignEmbedded(payload, key.Signer, Array.Empty<byte>());
byte[] detatchedMessageBytes = CoseSign1Message.SignDetached(payload, key.Signer, Array.Empty<byte>());
byte[] embeddedMessageBytes = CoseSign1Message.SignEmbedded(payload, signer, Array.Empty<byte>());
byte[] detatchedMessageBytes = CoseSign1Message.SignDetached(payload, signer, Array.Empty<byte>());

CoseSign1Message embeddedMessage = CoseSign1Message.DecodeSign1(embeddedMessageBytes);
CoseSign1Message detachedMessage = CoseSign1Message.DecodeSign1(detatchedMessageBytes);
Expand All @@ -58,11 +55,12 @@ public void VerifySingleSignerWithNullCoseKey_Throws()
[Fact]
public void VerifyMultiSignerWithNullCoseKey_Throws()
{
CoseTestKey key = KeyManager.GetKey(CoseTestKeyManager.ECDsaIdentifier);
CoseKey key = CoseKey.FromKey(CoseTestHelpers.ES256, HashAlgorithmName.SHA256);
CoseSigner signer = new CoseSigner(key);
byte[] payload = Encoding.UTF8.GetBytes(nameof(VerifySingleSignerWithNullCoseKey_Throws));
MemoryStream payloadStream = new(payload);
byte[] embeddedMessageBytes = CoseMultiSignMessage.SignEmbedded(payload, key.Signer);
byte[] detatchedMessageBytes = CoseMultiSignMessage.SignDetached(payload, key.Signer);
byte[] embeddedMessageBytes = CoseMultiSignMessage.SignEmbedded(payload, signer);
byte[] detatchedMessageBytes = CoseMultiSignMessage.SignDetached(payload, signer);

CoseMultiSignMessage embeddedMessage = CoseMultiSignMessage.DecodeMultiSign(embeddedMessageBytes);
CoseMultiSignMessage detachedMessage = CoseMultiSignMessage.DecodeMultiSign(detatchedMessageBytes);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#pragma warning disable SYSLIB5006

using System;
using System.Collections.Generic;
using System.Text;
Expand All @@ -14,17 +12,8 @@

namespace System.Security.Cryptography.Cose.Tests
{
public partial class CoseKeyTests : IClassFixture<CoseTestKeyManager.TestFixture>
public partial class CoseKeyTests
{
private CoseTestKeyManager.TestFixture _keyManagerFixture;
private CoseTestKeyManager KeyManager => _keyManagerFixture.KeyManager;
private CoseTestKeyManager BadKeyManager => _keyManagerFixture.BadKeyManager;

public CoseKeyTests(CoseTestKeyManager.TestFixture keyManagerFixture)
{
_keyManagerFixture = keyManagerFixture;
}

[ConditionalTheory(typeof(MLDsa), nameof(MLDsa.IsSupported))]
[MemberData(nameof(AllMLDsaCoseDraftExamples))]
public static void DecodeSign1MLDsaCoseDraftExamples(MLDsaAlgorithm algorithm, byte[] mldsaPk, byte[] sign1)
Expand Down Expand Up @@ -71,94 +60,6 @@ private static int GetExpectedMLDsaAlgorithm(MLDsaAlgorithm algorithm)
}
}

[Theory]
[MemberData(nameof(AllKeysAndSign1Implementations))]
public void TestSignVerifySingleSignerAllAlgorithms(string keyId, CoseTestSign1 signerImplementation)
{
byte[] payload = Encoding.UTF8.GetBytes("Hello World");
byte[] signature = signerImplementation.Sign(KeyManager, keyId, payload);
Assert.NotNull(signature);
Assert.True(signature.Length > 0);
Assert.True(signerImplementation.Verify(KeyManager, keyId, payload, signature));

// we try different key
CoseTestKey differentKey = KeyManager.GetDifferentKey(keyId);
Assert.False(signerImplementation.Verify(KeyManager, differentKey.Id, payload, signature));

// we try bad signature (or bad key with good signature, same thing)
Assert.False(signerImplementation.Verify(BadKeyManager, keyId, payload, signature));

// we try fake payload
if (!signerImplementation.IsEmbedded)
{
// embedded ignore payload arg
byte[] fakePayload = Encoding.UTF8.GetBytes("Hello World 2");
Assert.False(signerImplementation.Verify(KeyManager, keyId, fakePayload, signature));
}
}

[Theory]
[MemberData(nameof(AllKeysAndMultiSignImplementations))]
public void TestSignVerifyMultiSignerAllAlgorithms(string[] keyIds, CoseTestMultiSign signerImplementation)
{
byte[] payload = Encoding.UTF8.GetBytes("Hello World");
byte[] signature = signerImplementation.Sign(KeyManager, keyIds, payload);
Assert.NotNull(signature);
Assert.True(signature.Length > 0);
Assert.True(signerImplementation.Verify(KeyManager, keyIds, payload, signature));

// we try fake signature
Assert.False(signerImplementation.Verify(BadKeyManager, keyIds, payload, signature));

// we try fake payload
if (!signerImplementation.IsEmbedded)
{
byte[] fakePayload = Encoding.UTF8.GetBytes("Hello World 2");
Assert.False(signerImplementation.Verify(KeyManager, keyIds, fakePayload, signature));
}
}

public static IEnumerable<object[]> AllKeysAndSign1Implementations()
{
foreach (string keyId in CoseTestKeyManager.GetAllKeyIds())
{
foreach (CoseTestSign1 sign1 in CoseTestSign1.GetImplementations())
{
yield return [keyId, sign1];
}
}
}

public static IEnumerable<object[]> AllKeysAndMultiSignImplementations()
{
string[] keyIds = CoseTestKeyManager.GetAllKeyIds();
int[] nrOfKeys = [1, 2, 3, 3, 5];

for (int i = 0; i < keyIds.Length; i++)
{
string[] keysToTest = PickNKeys(nrOfKeys[i % nrOfKeys.Length], i, keyIds);
foreach (CoseTestMultiSign multiSign in CoseTestMultiSign.GetImplementations())
{
yield return [keysToTest, multiSign];
}
}

static string[] PickNKeys(int n, int atIndex, string[] keys)
{
Assert.True(keys.Length >= 1);

// If n is larger than the number of keys, we just wrap around and use same key multiple times.

string[] ret = new string[n];
for (int i = 0; i < n; i++)
{
ret[i] = keys[(atIndex + i) % keys.Length];
}

return ret;
}
}

public static IEnumerable<object[]> AllMLDsaCoseDraftExamples()
{
yield return new object[] { MLDsaAlgorithm.MLDsa44, MLDsa44PkHex.HexToByteArray(), MLDsa44Sign1Hex.HexToByteArray() };
Expand Down
Loading
Loading