Skip to content

Commit cf41052

Browse files
KeyGen and OSSL support for managed Composite ML-DSA + RSA (#117856)
1 parent 8433b62 commit cf41052

File tree

9 files changed

+415
-10
lines changed

9 files changed

+415
-10
lines changed

src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsaImplementation.Windows.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,17 @@ internal static partial bool IsAlgorithmSupportedImpl(CompositeMLDsaAlgorithm al
3737
return CompositeMLDsaManaged.IsAlgorithmSupportedImpl(algorithm);
3838
}
3939

40-
internal static partial CompositeMLDsa GenerateKeyImpl(CompositeMLDsaAlgorithm algorithm) =>
41-
throw new PlatformNotSupportedException();
40+
internal static partial CompositeMLDsa GenerateKeyImpl(CompositeMLDsaAlgorithm algorithm)
41+
{
42+
#if !NETFRAMEWORK
43+
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
44+
{
45+
throw new PlatformNotSupportedException();
46+
}
47+
#endif
48+
49+
return CompositeMLDsaManaged.GenerateKeyImpl(algorithm);
50+
}
4251

4352
internal static partial CompositeMLDsa ImportCompositeMLDsaPublicKeyImpl(CompositeMLDsaAlgorithm algorithm, ReadOnlySpan<byte> source)
4453
{

src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsaManaged.RSA.cs

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,28 @@ private RsaComponent(RSA rsa, HashAlgorithmName hashAlgorithmName, RSASignatureP
3535
#if NETFRAMEWORK
3636
// RSA-PSS requires RSACng on .NET Framework
3737
private static RSACng CreateRSA() => new RSACng();
38+
private static RSACng CreateRSA(int keySizeInBits) => new RSACng(keySizeInBits);
39+
#elif NETSTANDARD2_0
40+
private static RSA CreateRSA() => RSA.Create();
41+
42+
private static RSA CreateRSA(int keySizeInBits)
43+
{
44+
RSA rsa = RSA.Create();
45+
46+
try
47+
{
48+
rsa.KeySize = keySizeInBits;
49+
return rsa;
50+
}
51+
catch
52+
{
53+
rsa.Dispose();
54+
throw;
55+
}
56+
}
3857
#else
3958
private static RSA CreateRSA() => RSA.Create();
59+
private static RSA CreateRSA(int keySizeInBits) => RSA.Create(keySizeInBits);
4060
#endif
4161

4262
internal override int SignData(
@@ -80,8 +100,25 @@ internal override bool VerifyData(
80100
#endif
81101
}
82102

83-
public static RsaComponent GenerateKey(RsaAlgorithm algorithm) =>
84-
throw new NotImplementedException();
103+
public static RsaComponent GenerateKey(RsaAlgorithm algorithm)
104+
{
105+
RSA? rsa = null;
106+
107+
try
108+
{
109+
rsa = CreateRSA(algorithm.KeySizeInBits);
110+
111+
// RSA key generation is lazy, so we need to force it to happen eagerly.
112+
_ = rsa.ExportParameters(includePrivateParameters: false);
113+
114+
return new RsaComponent(rsa, algorithm.HashAlgorithmName, algorithm.Padding);
115+
}
116+
catch (CryptographicException)
117+
{
118+
rsa?.Dispose();
119+
throw;
120+
}
121+
}
85122

86123
public static RsaComponent ImportPrivateKey(RsaAlgorithm algorithm, ReadOnlySpan<byte> source)
87124
{

src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsaManaged.cs

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,82 @@ internal static bool IsAlgorithmSupportedImpl(CompositeMLDsaAlgorithm algorithm)
5252
});
5353
}
5454

55-
internal static CompositeMLDsa GenerateKeyImpl(CompositeMLDsaAlgorithm algorithm) =>
56-
throw new PlatformNotSupportedException();
55+
internal static CompositeMLDsa GenerateKeyImpl(CompositeMLDsaAlgorithm algorithm)
56+
{
57+
Debug.Assert(IsAlgorithmSupportedImpl(algorithm));
58+
59+
AlgorithmMetadata metadata = s_algorithmMetadata[algorithm];
60+
61+
// draft-ietf-lamps-pq-composite-sigs-latest (July 7, 2025), 4.1
62+
// 1. Generate component keys
63+
//
64+
// mldsaSeed = Random(32)
65+
// (mldsaPK, _) = ML-DSA.KeyGen(mldsaSeed)
66+
// (tradPK, tradSK) = Trad.KeyGen()
67+
68+
MLDsa? mldsaKey = null;
69+
ComponentAlgorithm? tradKey = null;
70+
71+
try
72+
{
73+
mldsaKey = MLDsaImplementation.GenerateKey(metadata.MLDsaAlgorithm);
74+
}
75+
catch (CryptographicException)
76+
{
77+
}
78+
79+
try
80+
{
81+
tradKey = metadata.TraditionalAlgorithm switch
82+
{
83+
RsaAlgorithm rsaAlgorithm => RsaComponent.GenerateKey(rsaAlgorithm),
84+
ECDsaAlgorithm ecdsaAlgorithm => ECDsaComponent.GenerateKey(ecdsaAlgorithm),
85+
_ => FailAndGetNull(),
86+
};
87+
88+
static ComponentAlgorithm? FailAndGetNull()
89+
{
90+
Debug.Fail("Only supported algorithms should reach here.");
91+
return null;
92+
}
93+
}
94+
catch (CryptographicException)
95+
{
96+
}
97+
98+
// 2. Check for component key gen failure
99+
//
100+
// if NOT (mldsaPK, mldsaSK) or NOT (tradPK, tradSK):
101+
// output "Key generation error"
102+
103+
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
104+
static bool KeyGenFailed([NotNullWhen(false)] MLDsa? mldsaKey, [NotNullWhen(false)] ComponentAlgorithm? tradKey) =>
105+
(mldsaKey is null) | (tradKey is null);
106+
107+
if (KeyGenFailed(mldsaKey, tradKey))
108+
{
109+
try
110+
{
111+
Debug.Assert(mldsaKey is null || tradKey is null);
112+
113+
mldsaKey?.Dispose();
114+
tradKey?.Dispose();
115+
}
116+
catch (CryptographicException)
117+
{
118+
}
119+
120+
throw new CryptographicException();
121+
}
122+
123+
// 3. Output the composite public and private keys
124+
//
125+
// pk = SerializePublicKey(mldsaPK, tradPK)
126+
// sk = SerializePrivateKey(mldsaSeed, tradSK)
127+
// return (pk, sk)
128+
129+
return new CompositeMLDsaManaged(algorithm, mldsaKey, tradKey);
130+
}
57131

58132
internal static CompositeMLDsa ImportCompositeMLDsaPublicKeyImpl(CompositeMLDsaAlgorithm algorithm, ReadOnlySpan<byte> source)
59133
{

src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/CompositeMLDsa/CompositeMLDsaFactoryTests.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public static class CompositeMLDsaFactoryTests
1313
[Fact]
1414
public static void NullArgumentValidation()
1515
{
16+
AssertExtensions.Throws<ArgumentNullException>("algorithm", static () => CompositeMLDsa.GenerateKey(null));
1617
AssertExtensions.Throws<ArgumentNullException>("algorithm", static () => CompositeMLDsa.IsAlgorithmSupported(null));
1718
AssertExtensions.Throws<ArgumentNullException>("algorithm", static () => CompositeMLDsa.ImportCompositeMLDsaPrivateKey(null, Array.Empty<byte>()));
1819
AssertExtensions.Throws<ArgumentNullException>("algorithm", static () => CompositeMLDsa.ImportCompositeMLDsaPrivateKey(null, ReadOnlySpan<byte>.Empty));
@@ -169,6 +170,17 @@ private static void AssertImportBadPublicKey(CompositeMLDsaAlgorithm algorithm,
169170
key);
170171
}
171172

173+
[Theory]
174+
[MemberData(nameof(CompositeMLDsaTestData.SupportedAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))]
175+
public static void AlgorithmMatches_GenerateKey(CompositeMLDsaAlgorithm algorithm)
176+
{
177+
AssertThrowIfNotSupported(() =>
178+
{
179+
using CompositeMLDsa dsa = CompositeMLDsa.GenerateKey(algorithm);
180+
Assert.Equal(algorithm, dsa.Algorithm);
181+
});
182+
}
183+
172184
[Theory]
173185
[MemberData(nameof(CompositeMLDsaTestData.SupportedAlgorithmIetfVectorsTestData), MemberType = typeof(CompositeMLDsaTestData))]
174186
public static void AlgorithmMatches_Import(CompositeMLDsaTestData.CompositeMLDsaTestVector vector)
@@ -186,7 +198,7 @@ public static void AlgorithmMatches_Import(CompositeMLDsaTestData.CompositeMLDsa
186198
public static void IsSupported_AgreesWithPlatform()
187199
{
188200
// Composites are supported everywhere MLDsa is supported
189-
Assert.Equal(MLDsa.IsSupported && !PlatformDetection.IsLinux, CompositeMLDsa.IsSupported);
201+
Assert.Equal(MLDsa.IsSupported, CompositeMLDsa.IsSupported);
190202
}
191203

192204
[Theory]
@@ -195,7 +207,7 @@ public static void IsAlgorithmSupported_AgreesWithPlatform(CompositeMLDsaAlgorit
195207
{
196208
bool supported = CompositeMLDsaTestHelpers.ExecuteComponentFunc(
197209
algorithm,
198-
_ => MLDsa.IsSupported && !PlatformDetection.IsLinux,
210+
_ => MLDsa.IsSupported,
199211
_ => false,
200212
_ => false);
201213

src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/CompositeMLDsa/CompositeMLDsaImplementationTests.cs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ namespace System.Security.Cryptography.Tests
88
[ConditionalClass(typeof(CompositeMLDsa), nameof(CompositeMLDsa.IsSupported))]
99
public sealed class CompositeMLDsaImplementationTests : CompositeMLDsaTestsBase
1010
{
11+
[Theory]
12+
[MemberData(nameof(CompositeMLDsaTestData.SupportedAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))]
13+
public static void CompositeMLDsaIsOnlyPublicAncestor_GenerateKey(CompositeMLDsaAlgorithm algorithm)
14+
{
15+
AssertCompositeMLDsaIsOnlyPublicAncestor(() => CompositeMLDsa.GenerateKey(algorithm));
16+
}
17+
1118
[Theory]
1219
[MemberData(nameof(CompositeMLDsaTestData.SupportedAlgorithmIetfVectorsTestData), MemberType = typeof(CompositeMLDsaTestData))]
1320
public static void CompositeMLDsaIsOnlyPublicAncestor_Import(CompositeMLDsaTestData.CompositeMLDsaTestVector info)
@@ -32,6 +39,66 @@ private static void AssertCompositeMLDsaIsOnlyPublicAncestor(Func<CompositeMLDsa
3239
Assert.Equal(typeof(CompositeMLDsa), keyType);
3340
}
3441

42+
#region Roundtrip by exporting then importing
43+
44+
[Theory]
45+
[MemberData(nameof(CompositeMLDsaTestData.SupportedAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))]
46+
public void RoundTrip_Export_Import_PublicKey(CompositeMLDsaAlgorithm algorithm)
47+
{
48+
// Generate new key
49+
using CompositeMLDsa dsa = GenerateKey(algorithm);
50+
51+
CompositeMLDsaTestHelpers.AssertExportPublicKey(
52+
export =>
53+
{
54+
// Roundtrip using public key. First export it.
55+
byte[] exportedPublicKey = export(dsa);
56+
CompositeMLDsaTestHelpers.AssertImportPublicKey(
57+
import =>
58+
{
59+
// Then import it.
60+
using CompositeMLDsa roundTrippedDsa = import();
61+
62+
// Verify the roundtripped object has the same key
63+
Assert.Equal(algorithm, roundTrippedDsa.Algorithm);
64+
AssertExtensions.SequenceEqual(dsa.ExportCompositeMLDsaPublicKey(), roundTrippedDsa.ExportCompositeMLDsaPublicKey());
65+
Assert.Throws<CryptographicException>(() => roundTrippedDsa.ExportCompositeMLDsaPrivateKey());
66+
},
67+
algorithm,
68+
exportedPublicKey);
69+
});
70+
}
71+
72+
[Theory]
73+
[MemberData(nameof(CompositeMLDsaTestData.SupportedAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))]
74+
public void RoundTrip_Export_Import_PrivateKey(CompositeMLDsaAlgorithm algorithm)
75+
{
76+
// Generate new key
77+
using CompositeMLDsa dsa = GenerateKey(algorithm);
78+
79+
CompositeMLDsaTestHelpers.AssertExportPrivateKey(
80+
export =>
81+
{
82+
// Roundtrip using secret key. First export it.
83+
byte[] exportedSecretKey = export(dsa);
84+
CompositeMLDsaTestHelpers.AssertImportPrivateKey(
85+
import =>
86+
{
87+
// Then import it.
88+
using CompositeMLDsa roundTrippedDsa = import();
89+
90+
// Verify the roundtripped object has the same key
91+
Assert.Equal(algorithm, roundTrippedDsa.Algorithm);
92+
AssertExtensions.SequenceEqual(dsa.ExportCompositeMLDsaPrivateKey(), roundTrippedDsa.ExportCompositeMLDsaPrivateKey());
93+
AssertExtensions.SequenceEqual(dsa.ExportCompositeMLDsaPublicKey(), roundTrippedDsa.ExportCompositeMLDsaPublicKey());
94+
},
95+
algorithm,
96+
exportedSecretKey);
97+
});
98+
}
99+
100+
#endregion Roundtrip by exporting then importing
101+
35102
#region Roundtrip by importing then exporting
36103

37104
[Theory]
@@ -60,6 +127,9 @@ public void RoundTrip_Import_Export_PrivateKey(CompositeMLDsaTestData.CompositeM
60127

61128
#endregion Roundtrip by importing then exporting
62129

130+
protected override CompositeMLDsa GenerateKey(CompositeMLDsaAlgorithm algorithm) =>
131+
CompositeMLDsa.GenerateKey(algorithm);
132+
63133
protected override CompositeMLDsa ImportPublicKey(CompositeMLDsaAlgorithm algorithm, ReadOnlySpan<byte> source) =>
64134
CompositeMLDsa.ImportCompositeMLDsaPublicKey(algorithm, source);
65135

src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/CompositeMLDsa/CompositeMLDsaTestData.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ internal CompositeMLDsaTestVector(string tcId, CompositeMLDsaAlgorithm algo, str
7171
public static IEnumerable<object[]> AllAlgorithmsTestData =>
7272
AllAlgorithms.Select(v => new object[] { v });
7373

74+
public static IEnumerable<object[]> SupportedAlgorithmsTestData =>
75+
AllAlgorithms.Where(CompositeMLDsa.IsAlgorithmSupported).Select(v => new object[] { v });
76+
7477
internal static MLDsaKeyInfo GetMLDsaIetfTestVector(CompositeMLDsaAlgorithm algorithm)
7578
{
7679
MLDsaAlgorithm mldsaAlgorithm = CompositeMLDsaTestHelpers.MLDsaAlgorithms[algorithm];

0 commit comments

Comments
 (0)