Skip to content

Commit 46c3e85

Browse files
authored
MLDsaOpenSsl + tests (#114485)
* MLDsaOpenSsl + tests * Address feedback * Changes w.r.t. ML-DSA CertificateRequest PR * Address feedback: Test & doc improvements
1 parent 99a0bd2 commit 46c3e85

File tree

20 files changed

+2982
-170
lines changed

20 files changed

+2982
-170
lines changed

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPKey.MLDsa.cs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,40 @@ internal static partial class Interop
1212
{
1313
internal static partial class Crypto
1414
{
15+
// Must be kept in sync with PalMLDsaId in native shim.
16+
internal enum PalMLDsaAlgorithmId
17+
{
18+
Unknown = 0,
19+
MLDsa44 = 1,
20+
MLDsa65 = 2,
21+
MLDsa87 = 3,
22+
}
23+
24+
[LibraryImport(Libraries.CryptoNative)]
25+
private static partial int CryptoNative_MLDsaGetPalId(
26+
SafeEvpPKeyHandle mldsa,
27+
out PalMLDsaAlgorithmId mldsaId);
28+
29+
internal static PalMLDsaAlgorithmId MLDsaGetPalId(SafeEvpPKeyHandle key)
30+
{
31+
const int Success = 1;
32+
const int Fail = 0;
33+
int result = CryptoNative_MLDsaGetPalId(key, out PalMLDsaAlgorithmId mldsaId);
34+
35+
return result switch
36+
{
37+
Success => mldsaId,
38+
Fail => throw CreateOpenSslCryptographicException(),
39+
int other => throw FailThrow(other),
40+
};
41+
42+
static Exception FailThrow(int result)
43+
{
44+
Debug.Fail($"Unexpected return value {result} from {nameof(CryptoNative_MLDsaGetPalId)}.");
45+
return new CryptographicException();
46+
}
47+
}
48+
1549
[LibraryImport(Libraries.CryptoNative, StringMarshalling = StringMarshalling.Utf8)]
1650
private static partial SafeEvpPKeyHandle CryptoNative_MLDsaGenerateKey(string keyType, ReadOnlySpan<byte> seed, int seedLength);
1751

@@ -80,7 +114,7 @@ internal static void MLDsaSignPure(
80114
Span<byte> destination)
81115
{
82116
int ret = CryptoNative_MLDsaSignPure(
83-
pkey, pkey.ExtraHandle,
117+
pkey, GetExtraHandle(pkey),
84118
msg, msg.Length,
85119
context, context.Length,
86120
destination, destination.Length);
@@ -105,7 +139,7 @@ internal static bool MLDsaVerifyPure(
105139
ReadOnlySpan<byte> signature)
106140
{
107141
int ret = CryptoNative_MLDsaVerifyPure(
108-
pkey, pkey.ExtraHandle,
142+
pkey, GetExtraHandle(pkey),
109143
msg, msg.Length,
110144
context, context.Length,
111145
signature, signature.Length);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections.Generic;
5+
using System.Security.Cryptography.Dsa.Tests;
6+
using Microsoft.DotNet.RemoteExecutor;
7+
using Microsoft.DotNet.XUnitExtensions;
8+
using Test.Cryptography;
9+
using Xunit;
10+
11+
namespace System.Security.Cryptography.Tests
12+
{
13+
[ConditionalClass(typeof(MLDsa), nameof(MLDsa.IsSupported))]
14+
public class MLDsaImplementationTests : MLDsaTestsBase
15+
{
16+
protected override MLDsa GenerateKey(MLDsaAlgorithm algorithm) => MLDsa.GenerateKey(algorithm);
17+
protected override MLDsa ImportPrivateSeed(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> seed) => MLDsa.ImportMLDsaPrivateSeed(algorithm, seed);
18+
protected override MLDsa ImportSecretKey(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source) => MLDsa.ImportMLDsaSecretKey(algorithm, source);
19+
protected override MLDsa ImportPublicKey(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source) => MLDsa.ImportMLDsaPublicKey(algorithm, source);
20+
21+
[Fact]
22+
public static void GenerateImport_NullAlgorithm()
23+
{
24+
AssertExtensions.Throws<ArgumentNullException>("algorithm", static () => MLDsa.GenerateKey(null));
25+
AssertExtensions.Throws<ArgumentNullException>("algorithm", static () => MLDsa.ImportMLDsaPrivateSeed(null, default));
26+
AssertExtensions.Throws<ArgumentNullException>("algorithm", static () => MLDsa.ImportMLDsaPublicKey(null, default));
27+
AssertExtensions.Throws<ArgumentNullException>("algorithm", static () => MLDsa.ImportMLDsaSecretKey(null, default));
28+
}
29+
30+
[Theory]
31+
[MemberData(nameof(MLDsaTestsData.AllMLDsaAlgorithms), MemberType = typeof(MLDsaTestsData))]
32+
public static void ImportMLDsaSecretKey_WrongSize(MLDsaAlgorithm algorithm)
33+
{
34+
AssertExtensions.Throws<ArgumentException>("source", () => MLDsa.ImportMLDsaSecretKey(algorithm, new byte[algorithm.SecretKeySizeInBytes - 1]));
35+
AssertExtensions.Throws<ArgumentException>("source", () => MLDsa.ImportMLDsaSecretKey(algorithm, new byte[algorithm.SecretKeySizeInBytes + 1]));
36+
AssertExtensions.Throws<ArgumentException>("source", () => MLDsa.ImportMLDsaSecretKey(algorithm, default));
37+
}
38+
39+
[Theory]
40+
[MemberData(nameof(MLDsaTestsData.AllMLDsaAlgorithms), MemberType = typeof(MLDsaTestsData))]
41+
public static void ImportMLDsaPrivateSeed_WrongSize(MLDsaAlgorithm algorithm)
42+
{
43+
AssertExtensions.Throws<ArgumentException>("source", () => MLDsa.ImportMLDsaPrivateSeed(algorithm, new byte[algorithm.PrivateSeedSizeInBytes - 1]));
44+
AssertExtensions.Throws<ArgumentException>("source", () => MLDsa.ImportMLDsaPrivateSeed(algorithm, new byte[algorithm.PrivateSeedSizeInBytes + 1]));
45+
AssertExtensions.Throws<ArgumentException>("source", () => MLDsa.ImportMLDsaPrivateSeed(algorithm, default));
46+
}
47+
48+
[Theory]
49+
[MemberData(nameof(MLDsaTestsData.AllMLDsaAlgorithms), MemberType = typeof(MLDsaTestsData))]
50+
public static void ImportMLDsaPublicKey_WrongSize(MLDsaAlgorithm algorithm)
51+
{
52+
AssertExtensions.Throws<ArgumentException>("source", () => MLDsa.ImportMLDsaPublicKey(algorithm, new byte[algorithm.PublicKeySizeInBytes - 1]));
53+
AssertExtensions.Throws<ArgumentException>("source", () => MLDsa.ImportMLDsaPublicKey(algorithm, new byte[algorithm.PublicKeySizeInBytes + 1]));
54+
AssertExtensions.Throws<ArgumentException>("source", () => MLDsa.ImportMLDsaPublicKey(algorithm, default));
55+
}
56+
57+
[Fact]
58+
public static void UseAfterDispose()
59+
{
60+
MLDsa mldsa = MLDsa.GenerateKey(MLDsaAlgorithm.MLDsa44);
61+
mldsa.Dispose();
62+
mldsa.Dispose(); // no throw
63+
64+
VerifyDisposed(mldsa);
65+
}
66+
}
67+
}

src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/MLDsa/MLDsaTests.cs

Lines changed: 31 additions & 144 deletions
Large diffs are not rendered by default.
Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections.Generic;
5+
using Microsoft.DotNet.XUnitExtensions;
6+
using Test.Cryptography;
7+
using Xunit;
8+
9+
namespace System.Security.Cryptography.Tests
10+
{
11+
[ConditionalClass(typeof(MLDsa), nameof(MLDsa.IsSupported))]
12+
public abstract class MLDsaTestsBase
13+
{
14+
protected abstract MLDsa GenerateKey(MLDsaAlgorithm algorithm);
15+
protected abstract MLDsa ImportPrivateSeed(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> seed);
16+
protected abstract MLDsa ImportSecretKey(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source);
17+
protected abstract MLDsa ImportPublicKey(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source);
18+
19+
[Theory]
20+
[MemberData(nameof(MLDsaTestsData.AllMLDsaAlgorithms), MemberType = typeof(MLDsaTestsData))]
21+
public void AlgorithmIsAssigned(MLDsaAlgorithm algorithm)
22+
{
23+
using MLDsa mldsa = GenerateKey(algorithm);
24+
Assert.Same(algorithm, mldsa.Algorithm);
25+
}
26+
27+
[Theory]
28+
[MemberData(nameof(MLDsaTestsData.AllMLDsaAlgorithms), MemberType = typeof(MLDsaTestsData))]
29+
public void GenerateSignVerifyNoContext(MLDsaAlgorithm algorithm)
30+
{
31+
using MLDsa mldsa = GenerateKey(algorithm);
32+
byte[] data = [ 1, 2, 3, 4, 5 ];
33+
byte[] signature = new byte[mldsa.Algorithm.SignatureSizeInBytes];
34+
Assert.Equal(signature.Length, mldsa.SignData(data, signature));
35+
36+
ExerciseSuccessfulVerify(mldsa, data, signature, []);
37+
}
38+
39+
[Theory]
40+
[MemberData(nameof(MLDsaTestsData.AllMLDsaAlgorithms), MemberType = typeof(MLDsaTestsData))]
41+
public void GenerateSignVerifyWithContext(MLDsaAlgorithm algorithm)
42+
{
43+
using MLDsa mldsa = GenerateKey(algorithm);
44+
byte[] context = [ 1, 1, 3, 5, 6 ];
45+
byte[] data = [ 1, 2, 3, 4, 5 ];
46+
byte[] signature = new byte[mldsa.Algorithm.SignatureSizeInBytes];
47+
Assert.Equal(signature.Length, mldsa.SignData(data, signature, context));
48+
49+
ExerciseSuccessfulVerify(mldsa, data, signature, context);
50+
}
51+
52+
[Theory]
53+
[MemberData(nameof(MLDsaTestsData.AllMLDsaAlgorithms), MemberType = typeof(MLDsaTestsData))]
54+
public void GenerateSignExportPublicVerifyWithPublicOnly(MLDsaAlgorithm algorithm)
55+
{
56+
byte[] publicKey;
57+
byte[] data = [ 1, 2, 3, 4, 5 ];
58+
byte[] signature;
59+
60+
using (MLDsa mldsa = GenerateKey(algorithm))
61+
{
62+
signature = new byte[algorithm.SignatureSizeInBytes];
63+
Assert.Equal(signature.Length, mldsa.SignData(data, signature));
64+
AssertExtensions.TrueExpression(mldsa.VerifyData(data, signature));
65+
66+
publicKey = new byte[algorithm.PublicKeySizeInBytes];
67+
Assert.Equal(publicKey.Length, mldsa.ExportMLDsaPublicKey(publicKey));
68+
}
69+
70+
using (MLDsa mldsaPub = ImportPublicKey(algorithm, publicKey))
71+
{
72+
ExerciseSuccessfulVerify(mldsaPub, data, signature, []);
73+
}
74+
}
75+
76+
[Theory]
77+
[MemberData(nameof(MLDsaTestsData.AllMLDsaAlgorithms), MemberType = typeof(MLDsaTestsData))]
78+
public void GenerateExportSecretKeySignAndVerify(MLDsaAlgorithm algorithm)
79+
{
80+
byte[] secretKey;
81+
byte[] data = [ 1, 2, 3, 4, 5 ];
82+
byte[] signature;
83+
84+
using (MLDsa mldsaTmp = GenerateKey(algorithm))
85+
{
86+
signature = new byte[algorithm.SignatureSizeInBytes];
87+
Assert.Equal(signature.Length, mldsaTmp.SignData(data, signature));
88+
89+
secretKey = new byte[algorithm.SecretKeySizeInBytes];
90+
Assert.Equal(secretKey.Length, mldsaTmp.ExportMLDsaSecretKey(secretKey));
91+
}
92+
93+
using (MLDsa mldsa = ImportSecretKey(algorithm, secretKey))
94+
{
95+
AssertExtensions.TrueExpression(mldsa.VerifyData(data, signature));
96+
97+
signature.AsSpan().Fill(0);
98+
Assert.Equal(signature.Length, mldsa.SignData(data, signature));
99+
100+
AssertExtensions.TrueExpression(mldsa.VerifyData(data, signature));
101+
data[0] ^= 1;
102+
AssertExtensions.FalseExpression(mldsa.VerifyData(data, signature));
103+
}
104+
}
105+
106+
[Theory]
107+
[MemberData(nameof(MLDsaTestsData.AllMLDsaAlgorithms), MemberType = typeof(MLDsaTestsData))]
108+
public void GenerateExportPrivateSeedSignAndVerify(MLDsaAlgorithm algorithm)
109+
{
110+
byte[] privateSeed;
111+
byte[] data = [ 1, 2, 3, 4, 5 ];
112+
byte[] signature;
113+
114+
using (MLDsa mldsaTmp = GenerateKey(algorithm))
115+
{
116+
signature = new byte[algorithm.SignatureSizeInBytes];
117+
Assert.Equal(signature.Length, mldsaTmp.SignData(data, signature));
118+
119+
privateSeed = new byte[algorithm.PrivateSeedSizeInBytes];
120+
Assert.Equal(privateSeed.Length, mldsaTmp.ExportMLDsaPrivateSeed(privateSeed));
121+
}
122+
123+
using (MLDsa mldsa = ImportPrivateSeed(algorithm, privateSeed))
124+
{
125+
AssertExtensions.TrueExpression(mldsa.VerifyData(data, signature));
126+
127+
signature.AsSpan().Fill(0);
128+
Assert.Equal(signature.Length, mldsa.SignData(data, signature));
129+
130+
ExerciseSuccessfulVerify(mldsa, data, signature, []);
131+
}
132+
}
133+
134+
[Fact]
135+
public void ImportSecretKey_CannotReconstructSeed()
136+
{
137+
byte[] secretKey = new byte[MLDsaAlgorithm.MLDsa44.SecretKeySizeInBytes];
138+
using (MLDsa mldsaOriginal = GenerateKey(MLDsaAlgorithm.MLDsa44))
139+
{
140+
Assert.Equal(secretKey.Length, mldsaOriginal.ExportMLDsaSecretKey(secretKey));
141+
}
142+
143+
using (MLDsa mldsa = ImportSecretKey(MLDsaAlgorithm.MLDsa44, secretKey))
144+
{
145+
Assert.Throws<CryptographicException>(() => mldsa.ExportMLDsaPrivateSeed(new byte[MLDsaAlgorithm.MLDsa44.PrivateSeedSizeInBytes]));
146+
}
147+
}
148+
149+
[Fact]
150+
public void ImportSeed_CanReconstructSecretKey()
151+
{
152+
byte[] secretKey = new byte[MLDsaAlgorithm.MLDsa44.SecretKeySizeInBytes];
153+
byte[] seed = new byte[MLDsaAlgorithm.MLDsa44.PrivateSeedSizeInBytes];
154+
using (MLDsa mldsaOriginal = GenerateKey(MLDsaAlgorithm.MLDsa44))
155+
{
156+
Assert.Equal(secretKey.Length, mldsaOriginal.ExportMLDsaSecretKey(secretKey));
157+
Assert.Equal(seed.Length, mldsaOriginal.ExportMLDsaPrivateSeed(seed));
158+
}
159+
160+
using (MLDsa mldsa = ImportPrivateSeed(MLDsaAlgorithm.MLDsa44, seed))
161+
{
162+
byte[] secretKey2 = new byte[MLDsaAlgorithm.MLDsa44.SecretKeySizeInBytes];
163+
byte[] seed2 = new byte[MLDsaAlgorithm.MLDsa44.PrivateSeedSizeInBytes];
164+
165+
Assert.Equal(secretKey2.Length, mldsa.ExportMLDsaSecretKey(secretKey2));
166+
Assert.Equal(seed2.Length, mldsa.ExportMLDsaPrivateSeed(seed2));
167+
168+
AssertExtensions.SequenceEqual(secretKey, secretKey2);
169+
AssertExtensions.SequenceEqual(seed, seed2);
170+
}
171+
}
172+
173+
[Theory]
174+
[MemberData(nameof(MLDsaTestsData.AllNistTestCases), MemberType = typeof(MLDsaTestsData))]
175+
public void NistImportPublicKeyVerify(MLDsaNistTestCase testCase)
176+
{
177+
using MLDsa mldsa = ImportPublicKey(testCase.Algorithm, testCase.PublicKey);
178+
Assert.Equal(testCase.ShouldPass, mldsa.VerifyData(testCase.Message, testCase.Signature, testCase.Context));
179+
}
180+
181+
[Theory]
182+
[MemberData(nameof(MLDsaTestsData.AllNistTestCases), MemberType = typeof(MLDsaTestsData))]
183+
public void NistImportSecretKeyVerifyExportsAndSignature(MLDsaNistTestCase testCase)
184+
{
185+
using MLDsa mldsa = ImportSecretKey(testCase.Algorithm, testCase.SecretKey);
186+
187+
byte[] pubKey = new byte[testCase.Algorithm.PublicKeySizeInBytes];
188+
Assert.Equal(pubKey.Length, mldsa.ExportMLDsaPublicKey(pubKey));
189+
AssertExtensions.SequenceEqual(testCase.PublicKey, pubKey);
190+
191+
byte[] secretKey = new byte[testCase.Algorithm.SecretKeySizeInBytes];
192+
Assert.Equal(secretKey.Length, mldsa.ExportMLDsaSecretKey(secretKey));
193+
194+
byte[] seed = new byte[testCase.Algorithm.PrivateSeedSizeInBytes];
195+
Assert.Throws<CryptographicException>(() => mldsa.ExportMLDsaPrivateSeed(seed));
196+
197+
Assert.Equal(testCase.ShouldPass, mldsa.VerifyData(testCase.Message, testCase.Signature, testCase.Context));
198+
}
199+
200+
protected static void ExerciseSuccessfulVerify(MLDsa mldsa, byte[] data, byte[] signature, byte[] context)
201+
{
202+
AssertExtensions.TrueExpression(mldsa.VerifyData(data, signature, context));
203+
data[0] ^= 1;
204+
AssertExtensions.FalseExpression(mldsa.VerifyData(data, signature, context));
205+
data[0] ^= 1;
206+
207+
signature[0] ^= 1;
208+
AssertExtensions.FalseExpression(mldsa.VerifyData(data, signature, context));
209+
signature[0] ^= 1;
210+
211+
if (context.Length > 0)
212+
{
213+
AssertExtensions.FalseExpression(mldsa.VerifyData(data, signature, []));
214+
215+
context[0] ^= 1;
216+
AssertExtensions.FalseExpression(mldsa.VerifyData(data, signature, context));
217+
context[0] ^= 1;
218+
}
219+
else
220+
{
221+
AssertExtensions.FalseExpression(mldsa.VerifyData(data, signature, [0]));
222+
AssertExtensions.FalseExpression(mldsa.VerifyData(data, signature, [1, 2, 3]));
223+
}
224+
225+
AssertExtensions.TrueExpression(mldsa.VerifyData(data, signature, context));
226+
}
227+
228+
protected static void VerifyDisposed(MLDsa mldsa)
229+
{
230+
PbeParameters pbeParams = new PbeParameters(PbeEncryptionAlgorithm.Aes128Cbc, HashAlgorithmName.SHA256, 10);
231+
232+
Assert.Throws<ObjectDisposedException>(() => mldsa.SignData([], new byte[mldsa.Algorithm.SignatureSizeInBytes]));
233+
Assert.Throws<ObjectDisposedException>(() => mldsa.VerifyData([], new byte[mldsa.Algorithm.SignatureSizeInBytes]));
234+
235+
Assert.Throws<ObjectDisposedException>(() => mldsa.ExportMLDsaPrivateSeed(new byte[mldsa.Algorithm.PrivateSeedSizeInBytes]));
236+
Assert.Throws<ObjectDisposedException>(() => mldsa.ExportMLDsaPublicKey(new byte[mldsa.Algorithm.PublicKeySizeInBytes]));
237+
Assert.Throws<ObjectDisposedException>(() => mldsa.ExportMLDsaSecretKey(new byte[mldsa.Algorithm.SecretKeySizeInBytes]));
238+
239+
Assert.Throws<ObjectDisposedException>(() => mldsa.ExportPkcs8PrivateKey());
240+
Assert.Throws<ObjectDisposedException>(() => mldsa.TryExportPkcs8PrivateKey(new byte[10000], out _));
241+
Assert.Throws<ObjectDisposedException>(() => mldsa.ExportPkcs8PrivateKeyPem());
242+
243+
Assert.Throws<ObjectDisposedException>(() => mldsa.ExportEncryptedPkcs8PrivateKey([1, 2, 3], pbeParams));
244+
Assert.Throws<ObjectDisposedException>(() => mldsa.ExportEncryptedPkcs8PrivateKey("123", pbeParams));
245+
Assert.Throws<ObjectDisposedException>(() => mldsa.TryExportEncryptedPkcs8PrivateKey([1, 2, 3], pbeParams, new byte[10000], out _));
246+
Assert.Throws<ObjectDisposedException>(() => mldsa.TryExportEncryptedPkcs8PrivateKey("123", pbeParams, new byte[10000], out _));
247+
248+
Assert.Throws<ObjectDisposedException>(() => mldsa.ExportEncryptedPkcs8PrivateKeyPem([1, 2, 3], pbeParams));
249+
Assert.Throws<ObjectDisposedException>(() => mldsa.ExportEncryptedPkcs8PrivateKeyPem("123", pbeParams));
250+
251+
Assert.Throws<ObjectDisposedException>(() => mldsa.ExportSubjectPublicKeyInfo());
252+
Assert.Throws<ObjectDisposedException>(() => mldsa.TryExportSubjectPublicKeyInfo(new byte[10000], out _));
253+
Assert.Throws<ObjectDisposedException>(() => mldsa.ExportSubjectPublicKeyInfoPem());
254+
}
255+
}
256+
}

0 commit comments

Comments
 (0)