Skip to content

Commit 52453ff

Browse files
Add Composite ML-DSA support for HTTPS (#63280)
1 parent 1abbddc commit 52453ff

File tree

2 files changed

+140
-60
lines changed

2 files changed

+140
-60
lines changed

src/Servers/Kestrel/Core/src/Internal/Certificates/CertificateConfigLoader.cs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,25 @@ private static X509Certificate2 LoadCertificateKey(X509Certificate2 certificate,
114114
const string SlhDsaShake_256sOid = "2.16.840.1.101.3.4.3.30";
115115
const string SlhDsaShake_256fOid = "2.16.840.1.101.3.4.3.31";
116116

117+
const string MLDsa44WithRSA2048PssPreHashSha256Oid = "2.16.840.1.114027.80.9.1.0";
118+
const string MLDsa44WithRSA2048Pkcs15PreHashSha256Oid = "2.16.840.1.114027.80.9.1.1";
119+
const string MLDsa44WithEd25519PreHashSha512Oid = "2.16.840.1.114027.80.9.1.2";
120+
const string MLDsa44WithECDsaP256PreHashSha256Oid = "2.16.840.1.114027.80.9.1.3";
121+
const string MLDsa65WithRSA3072PssPreHashSha512Oid = "2.16.840.1.114027.80.9.1.4";
122+
const string MLDsa65WithRSA3072Pkcs15PreHashSha512Oid = "2.16.840.1.114027.80.9.1.5";
123+
const string MLDsa65WithRSA4096PssPreHashSha512Oid = "2.16.840.1.114027.80.9.1.6";
124+
const string MLDsa65WithRSA4096Pkcs15PreHashSha512Oid = "2.16.840.1.114027.80.9.1.7";
125+
const string MLDsa65WithECDsaP256PreHashSha512Oid = "2.16.840.1.114027.80.9.1.8";
126+
const string MLDsa65WithECDsaP384PreHashSha512Oid = "2.16.840.1.114027.80.9.1.9";
127+
const string MLDsa65WithECDsaBrainpoolP256r1PreHashSha512Oid = "2.16.840.1.114027.80.9.1.10";
128+
const string MLDsa65WithEd25519PreHashSha512Oid = "2.16.840.1.114027.80.9.1.11";
129+
const string MLDsa87WithECDsaP384PreHashSha512Oid = "2.16.840.1.114027.80.9.1.12";
130+
const string MLDsa87WithECDsaBrainpoolP384r1PreHashSha512Oid = "2.16.840.1.114027.80.9.1.13";
131+
const string MLDsa87WithEd448PreHashShake256_512Oid = "2.16.840.1.114027.80.9.1.14";
132+
const string MLDsa87WithRSA3072PssPreHashSha512Oid = "2.16.840.1.114027.80.9.1.15";
133+
const string MLDsa87WithRSA4096PssPreHashSha512Oid = "2.16.840.1.114027.80.9.1.16";
134+
const string MLDsa87WithECDsaP521PreHashSha512Oid = "2.16.840.1.114027.80.9.1.17";
135+
117136
// Duplication is required here because there are separate CopyWithPrivateKey methods for each algorithm.
118137
var keyText = File.ReadAllText(keyPath);
119138
switch (certificate.PublicKey.Oid.Value)
@@ -200,6 +219,36 @@ private static X509Certificate2 LoadCertificateKey(X509Certificate2 certificate,
200219
throw CreateErrorGettingPrivateKeyException(keyPath, ex);
201220
}
202221
}
222+
case MLDsa44WithRSA2048PssPreHashSha256Oid:
223+
case MLDsa44WithRSA2048Pkcs15PreHashSha256Oid:
224+
case MLDsa44WithEd25519PreHashSha512Oid:
225+
case MLDsa44WithECDsaP256PreHashSha256Oid:
226+
case MLDsa65WithRSA3072PssPreHashSha512Oid:
227+
case MLDsa65WithRSA3072Pkcs15PreHashSha512Oid:
228+
case MLDsa65WithRSA4096PssPreHashSha512Oid:
229+
case MLDsa65WithRSA4096Pkcs15PreHashSha512Oid:
230+
case MLDsa65WithECDsaP256PreHashSha512Oid:
231+
case MLDsa65WithECDsaP384PreHashSha512Oid:
232+
case MLDsa65WithECDsaBrainpoolP256r1PreHashSha512Oid:
233+
case MLDsa65WithEd25519PreHashSha512Oid:
234+
case MLDsa87WithECDsaP384PreHashSha512Oid:
235+
case MLDsa87WithECDsaBrainpoolP384r1PreHashSha512Oid:
236+
case MLDsa87WithEd448PreHashShake256_512Oid:
237+
case MLDsa87WithRSA3072PssPreHashSha512Oid:
238+
case MLDsa87WithRSA4096PssPreHashSha512Oid:
239+
case MLDsa87WithECDsaP521PreHashSha512Oid:
240+
{
241+
using var compositeMLDsa = ImportCompositeMLDsaKeyFromFile(keyText, password);
242+
243+
try
244+
{
245+
return certificate.CopyWithPrivateKey(compositeMLDsa);
246+
}
247+
catch (Exception ex)
248+
{
249+
throw CreateErrorGettingPrivateKeyException(keyPath, ex);
250+
}
251+
}
203252
#pragma warning restore SYSLIB5006 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
204253
default:
205254
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, CoreStrings.UnrecognizedCertificateKeyOid, certificate.PublicKey.Oid.Value));
@@ -259,6 +308,19 @@ private static SlhDsa ImportSlhDsaKeyFromFile(string keyText, string? password)
259308
}
260309
}
261310

311+
[Experimental("SYSLIB5006")]
312+
private static CompositeMLDsa ImportCompositeMLDsaKeyFromFile(string keyText, string? password)
313+
{
314+
if (password == null)
315+
{
316+
return CompositeMLDsa.ImportFromPem(keyText);
317+
}
318+
else
319+
{
320+
return CompositeMLDsa.ImportFromEncryptedPem(keyText, password);
321+
}
322+
}
323+
262324
private static X509Certificate2 LoadFromStoreCert(CertificateConfig certInfo)
263325
{
264326
var subject = certInfo.Subject!;

src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs

Lines changed: 78 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,50 @@ public void ConfigureEndpoint_ThrowsWhen_The_KeyIsPublic()
683683
Assert.IsAssignableFrom<CryptographicException>(ex.InnerException);
684684
}
685685

686+
#pragma warning disable SYSLIB5006
687+
private static readonly Dictionary<string, MLDsaAlgorithm> _mlDsaAlgorithms = ((IEnumerable<MLDsaAlgorithm>)[
688+
MLDsaAlgorithm.MLDsa44,
689+
MLDsaAlgorithm.MLDsa65,
690+
MLDsaAlgorithm.MLDsa87,
691+
]).ToDictionary(a => a.Name);
692+
693+
private static readonly Dictionary<string, SlhDsaAlgorithm> _slhDsaAlgorithms = ((IEnumerable<SlhDsaAlgorithm>)[
694+
SlhDsaAlgorithm.SlhDsaSha2_128s,
695+
SlhDsaAlgorithm.SlhDsaSha2_128f,
696+
SlhDsaAlgorithm.SlhDsaSha2_192s,
697+
SlhDsaAlgorithm.SlhDsaSha2_192f,
698+
SlhDsaAlgorithm.SlhDsaSha2_256s,
699+
SlhDsaAlgorithm.SlhDsaSha2_256f,
700+
SlhDsaAlgorithm.SlhDsaShake128s,
701+
SlhDsaAlgorithm.SlhDsaShake128f,
702+
SlhDsaAlgorithm.SlhDsaShake192s,
703+
SlhDsaAlgorithm.SlhDsaShake192f,
704+
SlhDsaAlgorithm.SlhDsaShake256s,
705+
SlhDsaAlgorithm.SlhDsaShake256f,
706+
]).ToDictionary(a => a.Name);
707+
708+
private static readonly Dictionary<string, CompositeMLDsaAlgorithm> _compositeMLDsaAlgorithms = ((IEnumerable<CompositeMLDsaAlgorithm>)[
709+
CompositeMLDsaAlgorithm.MLDsa65WithECDsaP256,
710+
CompositeMLDsaAlgorithm.MLDsa65WithRSA3072Pkcs15,
711+
CompositeMLDsaAlgorithm.MLDsa65WithEd25519,
712+
CompositeMLDsaAlgorithm.MLDsa65WithECDsaP384,
713+
CompositeMLDsaAlgorithm.MLDsa87WithRSA4096Pss,
714+
CompositeMLDsaAlgorithm.MLDsa65WithECDsaBrainpoolP256r1,
715+
CompositeMLDsaAlgorithm.MLDsa44WithRSA2048Pss,
716+
CompositeMLDsaAlgorithm.MLDsa44WithRSA2048Pkcs15,
717+
CompositeMLDsaAlgorithm.MLDsa44WithEd25519,
718+
CompositeMLDsaAlgorithm.MLDsa44WithECDsaP256,
719+
CompositeMLDsaAlgorithm.MLDsa65WithRSA4096Pss,
720+
CompositeMLDsaAlgorithm.MLDsa87WithECDsaBrainpoolP384r1,
721+
CompositeMLDsaAlgorithm.MLDsa87WithECDsaP384,
722+
CompositeMLDsaAlgorithm.MLDsa87WithECDsaP521,
723+
CompositeMLDsaAlgorithm.MLDsa87WithEd448,
724+
CompositeMLDsaAlgorithm.MLDsa87WithRSA3072Pss,
725+
CompositeMLDsaAlgorithm.MLDsa65WithRSA3072Pss,
726+
CompositeMLDsaAlgorithm.MLDsa65WithRSA4096Pkcs15,
727+
]).ToDictionary(a => a.Name);
728+
#pragma warning restore SYSLIB5006
729+
686730
public static TheoryData<string, string, string> GetPemCertificateTestData()
687731
{
688732
var data = new TheoryData<string, string, string>();
@@ -695,29 +739,24 @@ public static TheoryData<string, string, string> GetPemCertificateTestData()
695739
#pragma warning disable SYSLIB5006
696740
if (MLDsa.IsSupported)
697741
{
698-
algorithms.AddRange([
699-
"MLDsa44",
700-
"MLDsa65",
701-
"MLDsa87",
702-
]);
742+
algorithms.AddRange(_mlDsaAlgorithms.Keys);
703743
}
704744

705745
if (SlhDsa.IsSupported)
706746
{
707-
algorithms.AddRange([
708-
"SlhDsaSha2_128s",
709-
"SlhDsaSha2_128f",
710-
"SlhDsaSha2_192s",
711-
"SlhDsaSha2_192f",
712-
"SlhDsaSha2_256s",
713-
"SlhDsaSha2_256f",
714-
"SlhDsaShake_128s",
715-
"SlhDsaShake_128f",
716-
"SlhDsaShake_192s",
717-
"SlhDsaShake_192f",
718-
"SlhDsaShake_256s",
719-
"SlhDsaShake_256f"
720-
]);
747+
algorithms.AddRange(_slhDsaAlgorithms.Keys);
748+
}
749+
750+
// Composite ML-DSA certificate generation is not supported at the time
751+
// of writing, so we skip it.
752+
// When it gets implemented in the future, simply remove the SkipCompositeMLDsa
753+
// condition to include it in the tests.
754+
const bool SkipCompositeMLDsa = true;
755+
if (CompositeMLDsa.IsSupported && !SkipCompositeMLDsa)
756+
{
757+
algorithms.AddRange(_compositeMLDsaAlgorithms
758+
.Where(kvp => CompositeMLDsa.IsAlgorithmSupported(kvp.Value))
759+
.Select(kvp => kvp.Key));
721760
}
722761
#pragma warning restore SYSLIB5006
723762

@@ -840,64 +879,35 @@ private static X509Certificate2 GenerateTestCertificateWithAlgorithm(string algo
840879
}
841880
break;
842881

843-
case "MLDsa44":
844-
case "MLDsa65":
845-
case "MLDsa87":
846882
#pragma warning disable SYSLIB5006
847-
var mlDsaAlgorithm = algorithmType switch
848-
{
849-
"MLDsa44" => MLDsaAlgorithm.MLDsa44,
850-
"MLDsa65" => MLDsaAlgorithm.MLDsa65,
851-
"MLDsa87" => MLDsaAlgorithm.MLDsa87,
852-
_ => throw new ArgumentException($"Unknown ML-DSA variant: {algorithmType}")
853-
};
883+
case var x when _mlDsaAlgorithms.TryGetValue(x, out var mlDsaAlgorithm):
854884
using (var mlDsa = MLDsa.GenerateKey(mlDsaAlgorithm))
855885
{
856886
var request = new CertificateRequest(distinguishedName, mlDsa);
857887
certificate = CreateTestCertificate(request, sanBuilder);
858888
keyPem = ExportMLDsaKeyToPem(mlDsa, keyPassword);
859889
}
860-
#pragma warning restore SYSLIB5006
861890
break;
862891

863-
case "SlhDsaSha2_128s":
864-
case "SlhDsaSha2_128f":
865-
case "SlhDsaSha2_192s":
866-
case "SlhDsaSha2_192f":
867-
case "SlhDsaSha2_256s":
868-
case "SlhDsaSha2_256f":
869-
case "SlhDsaShake_128s":
870-
case "SlhDsaShake_128f":
871-
case "SlhDsaShake_192s":
872-
case "SlhDsaShake_192f":
873-
case "SlhDsaShake_256s":
874-
case "SlhDsaShake_256f":
875-
#pragma warning disable SYSLIB5006
876-
var slhDsaAlgorithm = algorithmType switch
877-
{
878-
"SlhDsaSha2_128s" => SlhDsaAlgorithm.SlhDsaSha2_128s,
879-
"SlhDsaSha2_128f" => SlhDsaAlgorithm.SlhDsaSha2_128f,
880-
"SlhDsaSha2_192s" => SlhDsaAlgorithm.SlhDsaSha2_192s,
881-
"SlhDsaSha2_192f" => SlhDsaAlgorithm.SlhDsaSha2_192f,
882-
"SlhDsaSha2_256s" => SlhDsaAlgorithm.SlhDsaSha2_256s,
883-
"SlhDsaSha2_256f" => SlhDsaAlgorithm.SlhDsaSha2_256f,
884-
"SlhDsaShake_128s" => SlhDsaAlgorithm.SlhDsaShake128s,
885-
"SlhDsaShake_128f" => SlhDsaAlgorithm.SlhDsaShake128f,
886-
"SlhDsaShake_192s" => SlhDsaAlgorithm.SlhDsaShake192s,
887-
"SlhDsaShake_192f" => SlhDsaAlgorithm.SlhDsaShake192f,
888-
"SlhDsaShake_256s" => SlhDsaAlgorithm.SlhDsaShake256s,
889-
"SlhDsaShake_256f" => SlhDsaAlgorithm.SlhDsaShake256f,
890-
_ => throw new ArgumentException($"Unknown SLH-DSA variant: {algorithmType}")
891-
};
892+
case var x when _slhDsaAlgorithms.TryGetValue(x, out var slhDsaAlgorithm):
892893
using (var slhDsa = SlhDsa.GenerateKey(slhDsaAlgorithm))
893894
{
894895
var request = new CertificateRequest(distinguishedName, slhDsa);
895896
certificate = CreateTestCertificate(request, sanBuilder);
896897
keyPem = ExportSlhDsaKeyToPem(slhDsa, keyPassword);
897898
}
898-
#pragma warning restore SYSLIB5006
899899
break;
900900

901+
case var x when _compositeMLDsaAlgorithms.TryGetValue(x, out var compositeMLDsaAlgorithm):
902+
using (var compositeMLDsa = CompositeMLDsa.GenerateKey(compositeMLDsaAlgorithm))
903+
{
904+
var request = new CertificateRequest(distinguishedName, compositeMLDsa);
905+
certificate = CreateTestCertificate(request, sanBuilder);
906+
keyPem = ExportCompositeMLDsaKeyToPem(compositeMLDsa, keyPassword);
907+
}
908+
break;
909+
#pragma warning restore SYSLIB5006
910+
901911
default:
902912
throw new ArgumentException($"Unknown algorithm type: {algorithmType}");
903913
}
@@ -951,6 +961,14 @@ private static string ExportSlhDsaKeyToPem(SlhDsa slhDsa, string password)
951961
? slhDsa.ExportPkcs8PrivateKeyPem()
952962
: slhDsa.ExportEncryptedPkcs8PrivateKeyPem(password.AsSpan(), new PbeParameters(PbeEncryptionAlgorithm.Aes256Cbc, HashAlgorithmName.SHA256, 100_000));
953963
}
964+
965+
private static string ExportCompositeMLDsaKeyToPem(CompositeMLDsa compositeMLDsa, string password)
966+
{
967+
return password is null
968+
? compositeMLDsa.ExportPkcs8PrivateKeyPem()
969+
: compositeMLDsa.ExportEncryptedPkcs8PrivateKeyPem(password.AsSpan(), new PbeParameters(PbeEncryptionAlgorithm.Aes256Cbc, HashAlgorithmName.SHA256, 100_000));
970+
}
971+
954972
#pragma warning restore SYSLIB5006
955973

956974
[Fact]

0 commit comments

Comments
 (0)