Skip to content

Commit 743d179

Browse files
authored
Make CertificateRequest et al work with ML-DSA
* Add ctors to CertificateRequest * Enlighten CertificateRequest that future signing algorithms might not require a HashAlgorithmName * Add support to CertificateRequestListBuilder * Add cert.GetMLDsaPublicKey/GetMLDsaPrivateKey/CopyWithPrivateKey to power the above.
1 parent 7ac6188 commit 743d179

32 files changed

+964
-127
lines changed

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

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,17 +113,48 @@ internal static int HashOidToByteLength(string hashOid)
113113
};
114114
}
115115

116+
internal static bool HashAlgorithmRequired(string? keyAlgorithm)
117+
{
118+
// This list could either be written as "ML-DSA and friends return false",
119+
// or "RSA and friends return true".
120+
//
121+
// The consequences of returning true is that the hashAlgorithm parameter
122+
// gets pre-validated to not be null or empty, which means false positives
123+
// impact new ML-DSA-like algorithms.
124+
//
125+
// The consequences of returning false is that the hashAlgorithm parameter
126+
// is not pre-validated. That just means that in a false negative the user
127+
// gets probably the same exception, but from a different callstack.
128+
//
129+
// False positives or negatives are not possible with the simple Build that takes
130+
// only an X509Certificate2, as we control the destiny there entirely, it's only
131+
// for the power user scenario of the X509SignatureGenerator that this is a concern.
132+
//
133+
// Since the false-positive is worse than the false-negative, the list is written
134+
// as explicit-true, implicit-false.
135+
return keyAlgorithm switch
136+
{
137+
Oids.Rsa or
138+
Oids.RsaPss or
139+
Oids.EcPublicKey or
140+
Oids.Dsa => true,
141+
_ => false,
142+
};
143+
}
144+
116145
internal static CryptographicException CreateAlgorithmUnknownException(AsnWriter encodedId)
117146
{
118147
#if NET10_0_OR_GREATER
119-
return encodedId.Encode(static encoded =>
120-
new CryptographicException(
121-
SR.Format(SR.Cryptography_UnknownAlgorithmIdentifier, Convert.ToHexString(encoded))));
148+
return encodedId.Encode(static encoded => CreateAlgorithmUnknownException(Convert.ToHexString(encoded)));
122149
#else
123-
return new CryptographicException(
124-
SR.Format(SR.Cryptography_UnknownAlgorithmIdentifier,
125-
HexConverter.ToString(encodedId.Encode(), HexConverter.Casing.Upper)));
150+
return CreateAlgorithmUnknownException(HexConverter.ToString(encodedId.Encode(), HexConverter.Casing.Upper));
126151
#endif
127152
}
153+
154+
internal static CryptographicException CreateAlgorithmUnknownException(string algorithmId)
155+
{
156+
throw new CryptographicException(
157+
SR.Format(SR.Cryptography_UnknownAlgorithmIdentifier, algorithmId));
158+
}
128159
}
129160
}

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -753,7 +753,12 @@ public static MLDsa ImportSubjectPublicKeyInfo(ReadOnlySpan<byte> source)
753753
AsnValueReader reader = new AsnValueReader(source, AsnEncodingRules.DER);
754754
SubjectPublicKeyInfoAsn.Decode(ref reader, manager.Memory, out SubjectPublicKeyInfoAsn spki);
755755

756-
MLDsaAlgorithm algorithm = MLDsaAlgorithm.GetMLDsaAlgorithmFromOid(spki.Algorithm.Algorithm);
756+
MLDsaAlgorithm? algorithm = MLDsaAlgorithm.GetMLDsaAlgorithmFromOid(spki.Algorithm.Algorithm);
757+
758+
if (algorithm is null)
759+
{
760+
throw Helpers.CreateAlgorithmUnknownException(spki.Algorithm.Algorithm);
761+
}
757762

758763
if (spki.Algorithm.Parameters.HasValue)
759764
{
@@ -803,7 +808,12 @@ public static MLDsa ImportPkcs8PrivateKey(ReadOnlySpan<byte> source)
803808
AsnValueReader reader = new AsnValueReader(source, AsnEncodingRules.DER);
804809
PrivateKeyInfoAsn.Decode(ref reader, manager.Memory, out PrivateKeyInfoAsn pki);
805810

806-
MLDsaAlgorithm algorithm = MLDsaAlgorithm.GetMLDsaAlgorithmFromOid(pki.PrivateKeyAlgorithm.Algorithm);
811+
MLDsaAlgorithm? algorithm = MLDsaAlgorithm.GetMLDsaAlgorithmFromOid(pki.PrivateKeyAlgorithm.Algorithm);
812+
813+
if (algorithm is null)
814+
{
815+
throw Helpers.CreateAlgorithmUnknownException(pki.PrivateKeyAlgorithm.Algorithm);
816+
}
807817

808818
if (pki.PrivateKeyAlgorithm.Parameters.HasValue)
809819
{

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

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -107,22 +107,15 @@ private MLDsaAlgorithm(string name, int secretKeySizeInBytes, int publicKeySizeI
107107
/// </value>
108108
public static MLDsaAlgorithm MLDsa87 { get; } = new MLDsaAlgorithm("ML-DSA-87", 4896, 2592, 4627, Oids.MLDsa87);
109109

110-
internal static MLDsaAlgorithm GetMLDsaAlgorithmFromOid(string oid)
110+
internal static MLDsaAlgorithm? GetMLDsaAlgorithmFromOid(string? oid)
111111
{
112112
return oid switch
113113
{
114114
Oids.MLDsa44 => MLDsa44,
115115
Oids.MLDsa65 => MLDsa65,
116116
Oids.MLDsa87 => MLDsa87,
117-
_ => ThrowAlgorithmUnknown(oid),
117+
_ => null,
118118
};
119119
}
120-
121-
[DoesNotReturn]
122-
private static MLDsaAlgorithm ThrowAlgorithmUnknown(string algorithmId)
123-
{
124-
throw new CryptographicException(
125-
SR.Format(SR.Cryptography_UnknownAlgorithmIdentifier, algorithmId));
126-
}
127120
}
128121
}

src/libraries/Common/src/System/Security/Cryptography/MLDsaImplementation.NotSupported.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,19 @@ protected override void ExportMLDsaSecretKeyCore(Span<byte> destination) =>
2626
protected override void ExportMLDsaPrivateSeedCore(Span<byte> destination) =>
2727
throw new PlatformNotSupportedException();
2828

29-
internal static partial MLDsa GenerateKeyImpl(MLDsaAlgorithm algorithm) =>
29+
internal static partial MLDsaImplementation GenerateKeyImpl(MLDsaAlgorithm algorithm) =>
3030
throw new PlatformNotSupportedException();
3131

32-
internal static partial MLDsa ImportPublicKey(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source) =>
32+
internal static partial MLDsaImplementation ImportPublicKey(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source) =>
3333
throw new PlatformNotSupportedException();
3434

35-
internal static partial MLDsa ImportPkcs8PrivateKeyValue(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source) =>
35+
internal static partial MLDsaImplementation ImportPkcs8PrivateKeyValue(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source) =>
3636
throw new PlatformNotSupportedException();
3737

38-
internal static partial MLDsa ImportSecretKey(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source) =>
38+
internal static partial MLDsaImplementation ImportSecretKey(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source) =>
3939
throw new PlatformNotSupportedException();
4040

41-
internal static partial MLDsa ImportSeed(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source) =>
41+
internal static partial MLDsaImplementation ImportSeed(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source) =>
4242
throw new PlatformNotSupportedException();
4343
}
4444
}

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,19 @@ protected override void ExportMLDsaSecretKeyCore(Span<byte> destination) =>
2727
protected override void ExportMLDsaPrivateSeedCore(Span<byte> destination) =>
2828
throw new PlatformNotSupportedException();
2929

30-
internal static partial MLDsa GenerateKeyImpl(MLDsaAlgorithm algorithm) =>
30+
internal static partial MLDsaImplementation GenerateKeyImpl(MLDsaAlgorithm algorithm) =>
3131
throw new PlatformNotSupportedException();
3232

33-
internal static partial MLDsa ImportPublicKey(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source) =>
33+
internal static partial MLDsaImplementation ImportPublicKey(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source) =>
3434
throw new PlatformNotSupportedException();
3535

36-
internal static partial MLDsa ImportPkcs8PrivateKeyValue(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source) =>
36+
internal static partial MLDsaImplementation ImportPkcs8PrivateKeyValue(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source) =>
3737
throw new PlatformNotSupportedException();
3838

39-
internal static partial MLDsa ImportSecretKey(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source) =>
39+
internal static partial MLDsaImplementation ImportSecretKey(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source) =>
4040
throw new PlatformNotSupportedException();
4141

42-
internal static partial MLDsa ImportSeed(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source) =>
42+
internal static partial MLDsaImplementation ImportSeed(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source) =>
4343
throw new PlatformNotSupportedException();
4444
}
4545
}

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

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,41 @@ private MLDsaImplementation(MLDsaAlgorithm algorithm)
1717

1818
internal static partial bool SupportsAny();
1919

20-
internal static partial MLDsa GenerateKeyImpl(MLDsaAlgorithm algorithm);
21-
internal static partial MLDsa ImportPublicKey(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source);
22-
internal static partial MLDsa ImportPkcs8PrivateKeyValue(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source);
23-
internal static partial MLDsa ImportSecretKey(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source);
24-
internal static partial MLDsa ImportSeed(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source);
20+
internal static partial MLDsaImplementation GenerateKeyImpl(MLDsaAlgorithm algorithm);
21+
internal static partial MLDsaImplementation ImportPublicKey(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source);
22+
internal static partial MLDsaImplementation ImportPkcs8PrivateKeyValue(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source);
23+
internal static partial MLDsaImplementation ImportSecretKey(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source);
24+
internal static partial MLDsaImplementation ImportSeed(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source);
25+
26+
/// <summary>
27+
/// Duplicates an ML-DSA private key by export/import.
28+
/// Only intended to be used when the key type is unknown.
29+
/// </summary>
30+
internal static MLDsaImplementation DuplicatePrivateKey(MLDsa key)
31+
{
32+
// The implementation type and any platform types (e.g. MLDsaOpenSsl)
33+
// should inherently know how to clone themselves without the crudeness
34+
// of export/import.
35+
Debug.Assert(key is not MLDsaImplementation);
36+
37+
MLDsaAlgorithm alg = key.Algorithm;
38+
byte[] rented = CryptoPool.Rent(alg.SecretKeySizeInBytes);
39+
int written = 0;
40+
41+
try
42+
{
43+
written = key.ExportMLDsaPrivateSeed(rented);
44+
return ImportSeed(alg, new ReadOnlySpan<byte>(rented, 0, written));
45+
}
46+
catch (CryptographicException)
47+
{
48+
written = key.ExportMLDsaSecretKey(rented);
49+
return ImportSecretKey(alg, new ReadOnlySpan<byte>(rented, 0, written));
50+
}
51+
finally
52+
{
53+
CryptoPool.Return(rented, written);
54+
}
55+
}
2556
}
2657
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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 Xunit;
5+
6+
namespace System.Security.Cryptography.Tests
7+
{
8+
internal sealed class MLDsaTestImplementation : MLDsa
9+
{
10+
internal delegate void ExportAction(Span<byte> destination);
11+
internal delegate void SignAction(ReadOnlySpan<byte> data, ReadOnlySpan<byte> context, Span<byte> destination);
12+
internal delegate bool VerifyFunc(ReadOnlySpan<byte> data, ReadOnlySpan<byte> context, ReadOnlySpan<byte> signature);
13+
14+
internal ExportAction ExportMLDsaPrivateSeedHook { get; set; }
15+
internal ExportAction ExportMLDsaPublicKeyHook { get; set; }
16+
internal ExportAction ExportMLDsaSecretKeyHook { get; set; }
17+
internal SignAction SignDataHook { get; set; }
18+
internal VerifyFunc VerifyDataHook { get; set; }
19+
internal Action<bool> DisposeHook { get; set; } = _ => { };
20+
21+
private MLDsaTestImplementation(MLDsaAlgorithm algorithm) : base(algorithm)
22+
{
23+
}
24+
25+
protected override void Dispose(bool disposing) => DisposeHook(disposing);
26+
27+
protected override void ExportMLDsaPrivateSeedCore(Span<byte> destination) => ExportMLDsaPrivateSeedHook(destination);
28+
protected override void ExportMLDsaPublicKeyCore(Span<byte> destination) => ExportMLDsaPublicKeyHook(destination);
29+
protected override void ExportMLDsaSecretKeyCore(Span<byte> destination) => ExportMLDsaSecretKeyHook(destination);
30+
31+
protected override void SignDataCore(ReadOnlySpan<byte> data, ReadOnlySpan<byte> context, Span<byte> destination) =>
32+
SignDataHook(data, context, destination);
33+
34+
protected override bool VerifyDataCore(ReadOnlySpan<byte> data, ReadOnlySpan<byte> context, ReadOnlySpan<byte> signature) =>
35+
VerifyDataHook(data, context, signature);
36+
37+
internal static MLDsaTestImplementation CreateOverriddenCoreMethodsFail(MLDsaAlgorithm algorithm)
38+
{
39+
return new MLDsaTestImplementation(algorithm)
40+
{
41+
ExportMLDsaPrivateSeedHook = _ => Assert.Fail(),
42+
ExportMLDsaPublicKeyHook = _ => Assert.Fail(),
43+
ExportMLDsaSecretKeyHook = _ => Assert.Fail(),
44+
SignDataHook = (_, _, _) => Assert.Fail(),
45+
VerifyDataHook = (_, _, _) => { Assert.Fail(); return false; },
46+
};
47+
}
48+
49+
internal static MLDsaTestImplementation CreateNoOp(MLDsaAlgorithm algorithm)
50+
{
51+
return new MLDsaTestImplementation(algorithm)
52+
{
53+
ExportMLDsaPrivateSeedHook = d => d.Clear(),
54+
ExportMLDsaPublicKeyHook = d => d.Clear(),
55+
ExportMLDsaSecretKeyHook = d => d.Clear(),
56+
SignDataHook = (data, context, destination) => destination.Clear(),
57+
VerifyDataHook = (data, context, signature) => signature.IndexOfAnyExcept((byte)0) == -1,
58+
};
59+
}
60+
61+
internal static MLDsaTestImplementation Wrap(MLDsa other)
62+
{
63+
return new MLDsaTestImplementation(other.Algorithm)
64+
{
65+
ExportMLDsaPrivateSeedHook = d => other.ExportMLDsaPrivateSeed(d),
66+
ExportMLDsaPublicKeyHook = d => other.ExportMLDsaPublicKey(d),
67+
ExportMLDsaSecretKeyHook = d => other.ExportMLDsaSecretKey(d),
68+
SignDataHook = (data, context, destination) => other.SignData(data, destination, context),
69+
VerifyDataHook = (data, context, signature) => other.VerifyData(data, signature, context),
70+
};
71+
}
72+
}
73+
}

src/libraries/Common/tests/System/Security/Cryptography/X509Certificates/CertificateAuthority.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
using System.Runtime.InteropServices;
99
using Xunit;
1010

11+
// PQC types are used throughout, but only when the caller requests them.
12+
#pragma warning disable SYSLIB5006
13+
1114
namespace System.Security.Cryptography.X509Certificates.Tests.Common
1215
{
1316
// This class represents only a portion of what is required to be a proper Certificate Authority.
@@ -994,6 +997,7 @@ internal static X509Certificate2 CloneWithPrivateKey(X509Certificate2 cert, obje
994997
{
995998
RSA rsa => cert.CopyWithPrivateKey(rsa),
996999
ECDsa ecdsa => cert.CopyWithPrivateKey(ecdsa),
1000+
MLDsa mldsa => cert.CopyWithPrivateKey(mldsa),
9971001
DSA dsa => cert.CopyWithPrivateKey(dsa),
9981002
_ => throw new InvalidOperationException(
9991003
$"Had no handler for key of type {key?.GetType().FullName ?? "null"}")
@@ -1008,6 +1012,9 @@ internal sealed class KeyFactory
10081012
internal static KeyFactory ECDsa { get; } =
10091013
new(() => Cryptography.ECDsa.Create(ECCurve.NamedCurves.nistP384));
10101014

1015+
internal static KeyFactory MLDsa { get; } =
1016+
new(() => Cryptography.MLDsa.GenerateKey(MLDsaAlgorithm.MLDsa65));
1017+
10111018
private Func<IDisposable> _factory;
10121019

10131020
private KeyFactory(Func<IDisposable> factory)
@@ -1047,6 +1054,7 @@ internal KeyHolder(X509Certificate2 cert)
10471054
_key =
10481055
cert.GetRSAPrivateKey() ??
10491056
cert.GetECDsaPrivateKey() ??
1057+
cert.GetMLDsaPrivateKey() ??
10501058
(IDisposable)cert.GetDSAPrivateKey() ??
10511059
throw new NotSupportedException();
10521060
}
@@ -1067,6 +1075,7 @@ internal CertificateRequest CreateRequest(string subject)
10671075
{
10681076
RSA rsa => new CertificateRequest(subject, rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1),
10691077
ECDsa ecdsa => new CertificateRequest(subject, ecdsa, HashAlgorithmName.SHA256),
1078+
MLDsa mldsa => new CertificateRequest(subject, mldsa),
10701079
_ => throw new NotSupportedException(),
10711080
};
10721081
}
@@ -1077,6 +1086,7 @@ internal X509SignatureGenerator GetGenerator()
10771086
{
10781087
RSA rsa => X509SignatureGenerator.CreateForRSA(rsa, RSASignaturePadding.Pkcs1),
10791088
ECDsa ecdsa => X509SignatureGenerator.CreateForECDsa(ecdsa),
1089+
MLDsa mldsa => X509SignatureGenerator.CreateForMLDsa(mldsa),
10801090
_ => throw new NotSupportedException(),
10811091
};
10821092
}

0 commit comments

Comments
 (0)