From 569206c4e0d08f6f33c4bd3cb7a86224ca4015c4 Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Fri, 15 Aug 2025 14:37:13 -0400 Subject: [PATCH 1/2] Add Composite ML-DSA support --- .../Certificates/CertificateConfigLoader.cs | 62 ++++++++ .../test/KestrelConfigurationLoaderTests.cs | 138 ++++++++++-------- 2 files changed, 140 insertions(+), 60 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Certificates/CertificateConfigLoader.cs b/src/Servers/Kestrel/Core/src/Internal/Certificates/CertificateConfigLoader.cs index b74353fe8246..cb08b5503cec 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Certificates/CertificateConfigLoader.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Certificates/CertificateConfigLoader.cs @@ -114,6 +114,25 @@ private static X509Certificate2 LoadCertificateKey(X509Certificate2 certificate, const string SlhDsaShake_256sOid = "2.16.840.1.101.3.4.3.30"; const string SlhDsaShake_256fOid = "2.16.840.1.101.3.4.3.31"; + const string MLDsa44WithRSA2048PssPreHashSha256Oid = "2.16.840.1.114027.80.9.0"; + const string MLDsa44WithRSA2048Pkcs15PreHashSha256Oid = "2.16.840.1.114027.80.9.1"; + const string MLDsa44WithEd25519PreHashSha512Oid = "2.16.840.1.114027.80.9.2"; + const string MLDsa44WithECDsaP256PreHashSha256Oid = "2.16.840.1.114027.80.9.3"; + const string MLDsa65WithRSA3072PssPreHashSha512Oid = "2.16.840.1.114027.80.9.4"; + const string MLDsa65WithRSA3072Pkcs15PreHashSha512Oid = "2.16.840.1.114027.80.9.5"; + const string MLDsa65WithRSA4096PssPreHashSha512Oid = "2.16.840.1.114027.80.9.6"; + const string MLDsa65WithRSA4096Pkcs15PreHashSha512Oid = "2.16.840.1.114027.80.9.7"; + const string MLDsa65WithECDsaP256PreHashSha512Oid = "2.16.840.1.114027.80.9.8"; + const string MLDsa65WithECDsaP384PreHashSha512Oid = "2.16.840.1.114027.80.9.9"; + const string MLDsa65WithECDsaBrainpoolP256r1PreHashSha512Oid = "2.16.840.1.114027.80.9.10"; + const string MLDsa65WithEd25519PreHashSha512Oid = "2.16.840.1.114027.80.9.11"; + const string MLDsa87WithECDsaP384PreHashSha512Oid = "2.16.840.1.114027.80.9.12"; + const string MLDsa87WithECDsaBrainpoolP384r1PreHashSha512Oid = "2.16.840.1.114027.80.9.13"; + const string MLDsa87WithEd448PreHashShake256_512Oid = "2.16.840.1.114027.80.9.14"; + const string MLDsa87WithRSA3072PssPreHashSha512Oid = "2.16.840.1.114027.80.9.15"; + const string MLDsa87WithRSA4096PssPreHashSha512Oid = "2.16.840.1.114027.80.9.16"; + const string MLDsa87WithECDsaP521PreHashSha512Oid = "2.16.840.1.114027.80.9.17"; + // Duplication is required here because there are separate CopyWithPrivateKey methods for each algorithm. var keyText = File.ReadAllText(keyPath); switch (certificate.PublicKey.Oid.Value) @@ -200,6 +219,36 @@ private static X509Certificate2 LoadCertificateKey(X509Certificate2 certificate, throw CreateErrorGettingPrivateKeyException(keyPath, ex); } } + case MLDsa44WithRSA2048PssPreHashSha256Oid: + case MLDsa44WithRSA2048Pkcs15PreHashSha256Oid: + case MLDsa44WithEd25519PreHashSha512Oid: + case MLDsa44WithECDsaP256PreHashSha256Oid: + case MLDsa65WithRSA3072PssPreHashSha512Oid: + case MLDsa65WithRSA3072Pkcs15PreHashSha512Oid: + case MLDsa65WithRSA4096PssPreHashSha512Oid: + case MLDsa65WithRSA4096Pkcs15PreHashSha512Oid: + case MLDsa65WithECDsaP256PreHashSha512Oid: + case MLDsa65WithECDsaP384PreHashSha512Oid: + case MLDsa65WithECDsaBrainpoolP256r1PreHashSha512Oid: + case MLDsa65WithEd25519PreHashSha512Oid: + case MLDsa87WithECDsaP384PreHashSha512Oid: + case MLDsa87WithECDsaBrainpoolP384r1PreHashSha512Oid: + case MLDsa87WithEd448PreHashShake256_512Oid: + case MLDsa87WithRSA3072PssPreHashSha512Oid: + case MLDsa87WithRSA4096PssPreHashSha512Oid: + case MLDsa87WithECDsaP521PreHashSha512Oid: + { + using var compositeMLDsa = ImportCompositeMLDsaKeyFromFile(keyText, password); + + try + { + return certificate.CopyWithPrivateKey(compositeMLDsa); + } + catch (Exception ex) + { + throw CreateErrorGettingPrivateKeyException(keyPath, ex); + } + } #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. default: 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) } } + [Experimental("SYSLIB5006")] + private static CompositeMLDsa ImportCompositeMLDsaKeyFromFile(string keyText, string? password) + { + if (password == null) + { + return CompositeMLDsa.ImportFromPem(keyText); + } + else + { + return CompositeMLDsa.ImportFromEncryptedPem(keyText, password); + } + } + private static X509Certificate2 LoadFromStoreCert(CertificateConfig certInfo) { var subject = certInfo.Subject!; diff --git a/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs b/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs index 02f18df74672..d33ab36cd8c6 100644 --- a/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs +++ b/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs @@ -683,6 +683,50 @@ public void ConfigureEndpoint_ThrowsWhen_The_KeyIsPublic() Assert.IsAssignableFrom(ex.InnerException); } +#pragma warning disable SYSLIB5006 + private static readonly Dictionary _mlDsaAlgorithms = ((IEnumerable)[ + MLDsaAlgorithm.MLDsa44, + MLDsaAlgorithm.MLDsa65, + MLDsaAlgorithm.MLDsa87, + ]).ToDictionary(a => a.Name); + + private static readonly Dictionary _slhDsaAlgorithms = ((IEnumerable)[ + SlhDsaAlgorithm.SlhDsaSha2_128s, + SlhDsaAlgorithm.SlhDsaSha2_128f, + SlhDsaAlgorithm.SlhDsaSha2_192s, + SlhDsaAlgorithm.SlhDsaSha2_192f, + SlhDsaAlgorithm.SlhDsaSha2_256s, + SlhDsaAlgorithm.SlhDsaSha2_256f, + SlhDsaAlgorithm.SlhDsaShake128s, + SlhDsaAlgorithm.SlhDsaShake128f, + SlhDsaAlgorithm.SlhDsaShake192s, + SlhDsaAlgorithm.SlhDsaShake192f, + SlhDsaAlgorithm.SlhDsaShake256s, + SlhDsaAlgorithm.SlhDsaShake256f, + ]).ToDictionary(a => a.Name); + + private static readonly Dictionary _compositeMLDsaAlgorithms = ((IEnumerable)[ + CompositeMLDsaAlgorithm.MLDsa65WithECDsaP256, + CompositeMLDsaAlgorithm.MLDsa65WithRSA3072Pkcs15, + CompositeMLDsaAlgorithm.MLDsa65WithEd25519, + CompositeMLDsaAlgorithm.MLDsa65WithECDsaP384, + CompositeMLDsaAlgorithm.MLDsa87WithRSA4096Pss, + CompositeMLDsaAlgorithm.MLDsa65WithECDsaBrainpoolP256r1, + CompositeMLDsaAlgorithm.MLDsa44WithRSA2048Pss, + CompositeMLDsaAlgorithm.MLDsa44WithRSA2048Pkcs15, + CompositeMLDsaAlgorithm.MLDsa44WithEd25519, + CompositeMLDsaAlgorithm.MLDsa44WithECDsaP256, + CompositeMLDsaAlgorithm.MLDsa65WithRSA4096Pss, + CompositeMLDsaAlgorithm.MLDsa87WithECDsaBrainpoolP384r1, + CompositeMLDsaAlgorithm.MLDsa87WithECDsaP384, + CompositeMLDsaAlgorithm.MLDsa87WithECDsaP521, + CompositeMLDsaAlgorithm.MLDsa87WithEd448, + CompositeMLDsaAlgorithm.MLDsa87WithRSA3072Pss, + CompositeMLDsaAlgorithm.MLDsa65WithRSA3072Pss, + CompositeMLDsaAlgorithm.MLDsa65WithRSA4096Pkcs15, + ]).ToDictionary(a => a.Name); +#pragma warning restore SYSLIB5006 + public static TheoryData GetPemCertificateTestData() { var data = new TheoryData(); @@ -695,29 +739,24 @@ public static TheoryData GetPemCertificateTestData() #pragma warning disable SYSLIB5006 if (MLDsa.IsSupported) { - algorithms.AddRange([ - "MLDsa44", - "MLDsa65", - "MLDsa87", - ]); + algorithms.AddRange(_mlDsaAlgorithms.Keys); } if (SlhDsa.IsSupported) { - algorithms.AddRange([ - "SlhDsaSha2_128s", - "SlhDsaSha2_128f", - "SlhDsaSha2_192s", - "SlhDsaSha2_192f", - "SlhDsaSha2_256s", - "SlhDsaSha2_256f", - "SlhDsaShake_128s", - "SlhDsaShake_128f", - "SlhDsaShake_192s", - "SlhDsaShake_192f", - "SlhDsaShake_256s", - "SlhDsaShake_256f" - ]); + algorithms.AddRange(_slhDsaAlgorithms.Keys); + } + + // Composite ML-DSA certificate generation is not supported at the time + // of writing, so we skip it. + // When it gets implemented in the future, simply remove the SkipCompositeMLDsa + // condition to include it in the tests. + const bool SkipCompositeMLDsa = true; + if (CompositeMLDsa.IsSupported && !SkipCompositeMLDsa) + { + algorithms.AddRange(_compositeMLDsaAlgorithms + .Where(kvp => CompositeMLDsa.IsAlgorithmSupported(kvp.Value)) + .Select(kvp => kvp.Key)); } #pragma warning restore SYSLIB5006 @@ -840,64 +879,35 @@ private static X509Certificate2 GenerateTestCertificateWithAlgorithm(string algo } break; - case "MLDsa44": - case "MLDsa65": - case "MLDsa87": #pragma warning disable SYSLIB5006 - var mlDsaAlgorithm = algorithmType switch - { - "MLDsa44" => MLDsaAlgorithm.MLDsa44, - "MLDsa65" => MLDsaAlgorithm.MLDsa65, - "MLDsa87" => MLDsaAlgorithm.MLDsa87, - _ => throw new ArgumentException($"Unknown ML-DSA variant: {algorithmType}") - }; + case var x when _mlDsaAlgorithms.TryGetValue(x, out var mlDsaAlgorithm): using (var mlDsa = MLDsa.GenerateKey(mlDsaAlgorithm)) { var request = new CertificateRequest(distinguishedName, mlDsa); certificate = CreateTestCertificate(request, sanBuilder); keyPem = ExportMLDsaKeyToPem(mlDsa, keyPassword); } -#pragma warning restore SYSLIB5006 break; - case "SlhDsaSha2_128s": - case "SlhDsaSha2_128f": - case "SlhDsaSha2_192s": - case "SlhDsaSha2_192f": - case "SlhDsaSha2_256s": - case "SlhDsaSha2_256f": - case "SlhDsaShake_128s": - case "SlhDsaShake_128f": - case "SlhDsaShake_192s": - case "SlhDsaShake_192f": - case "SlhDsaShake_256s": - case "SlhDsaShake_256f": -#pragma warning disable SYSLIB5006 - var slhDsaAlgorithm = algorithmType switch - { - "SlhDsaSha2_128s" => SlhDsaAlgorithm.SlhDsaSha2_128s, - "SlhDsaSha2_128f" => SlhDsaAlgorithm.SlhDsaSha2_128f, - "SlhDsaSha2_192s" => SlhDsaAlgorithm.SlhDsaSha2_192s, - "SlhDsaSha2_192f" => SlhDsaAlgorithm.SlhDsaSha2_192f, - "SlhDsaSha2_256s" => SlhDsaAlgorithm.SlhDsaSha2_256s, - "SlhDsaSha2_256f" => SlhDsaAlgorithm.SlhDsaSha2_256f, - "SlhDsaShake_128s" => SlhDsaAlgorithm.SlhDsaShake128s, - "SlhDsaShake_128f" => SlhDsaAlgorithm.SlhDsaShake128f, - "SlhDsaShake_192s" => SlhDsaAlgorithm.SlhDsaShake192s, - "SlhDsaShake_192f" => SlhDsaAlgorithm.SlhDsaShake192f, - "SlhDsaShake_256s" => SlhDsaAlgorithm.SlhDsaShake256s, - "SlhDsaShake_256f" => SlhDsaAlgorithm.SlhDsaShake256f, - _ => throw new ArgumentException($"Unknown SLH-DSA variant: {algorithmType}") - }; + case var x when _slhDsaAlgorithms.TryGetValue(x, out var slhDsaAlgorithm): using (var slhDsa = SlhDsa.GenerateKey(slhDsaAlgorithm)) { var request = new CertificateRequest(distinguishedName, slhDsa); certificate = CreateTestCertificate(request, sanBuilder); keyPem = ExportSlhDsaKeyToPem(slhDsa, keyPassword); } -#pragma warning restore SYSLIB5006 break; + case var x when _compositeMLDsaAlgorithms.TryGetValue(x, out var compositeMLDsaAlgorithm): + using (var compositeMLDsa = CompositeMLDsa.GenerateKey(compositeMLDsaAlgorithm)) + { + var request = new CertificateRequest(distinguishedName, compositeMLDsa); + certificate = CreateTestCertificate(request, sanBuilder); + keyPem = ExportCompositeMLDsaKeyToPem(compositeMLDsa, keyPassword); + } + break; +#pragma warning restore SYSLIB5006 + default: throw new ArgumentException($"Unknown algorithm type: {algorithmType}"); } @@ -951,6 +961,14 @@ private static string ExportSlhDsaKeyToPem(SlhDsa slhDsa, string password) ? slhDsa.ExportPkcs8PrivateKeyPem() : slhDsa.ExportEncryptedPkcs8PrivateKeyPem(password.AsSpan(), new PbeParameters(PbeEncryptionAlgorithm.Aes256Cbc, HashAlgorithmName.SHA256, 100_000)); } + + private static string ExportCompositeMLDsaKeyToPem(CompositeMLDsa compositeMLDsa, string password) + { + return password is null + ? compositeMLDsa.ExportPkcs8PrivateKeyPem() + : compositeMLDsa.ExportEncryptedPkcs8PrivateKeyPem(password.AsSpan(), new PbeParameters(PbeEncryptionAlgorithm.Aes256Cbc, HashAlgorithmName.SHA256, 100_000)); + } + #pragma warning restore SYSLIB5006 [Fact] From b8cb1e810b196fb6449a377354aaa4e28af5c7a0 Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Fri, 15 Aug 2025 15:11:50 -0400 Subject: [PATCH 2/2] Fix OIDs --- .../Certificates/CertificateConfigLoader.cs | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Certificates/CertificateConfigLoader.cs b/src/Servers/Kestrel/Core/src/Internal/Certificates/CertificateConfigLoader.cs index cb08b5503cec..8d2f25a336e0 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Certificates/CertificateConfigLoader.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Certificates/CertificateConfigLoader.cs @@ -114,24 +114,24 @@ private static X509Certificate2 LoadCertificateKey(X509Certificate2 certificate, const string SlhDsaShake_256sOid = "2.16.840.1.101.3.4.3.30"; const string SlhDsaShake_256fOid = "2.16.840.1.101.3.4.3.31"; - const string MLDsa44WithRSA2048PssPreHashSha256Oid = "2.16.840.1.114027.80.9.0"; - const string MLDsa44WithRSA2048Pkcs15PreHashSha256Oid = "2.16.840.1.114027.80.9.1"; - const string MLDsa44WithEd25519PreHashSha512Oid = "2.16.840.1.114027.80.9.2"; - const string MLDsa44WithECDsaP256PreHashSha256Oid = "2.16.840.1.114027.80.9.3"; - const string MLDsa65WithRSA3072PssPreHashSha512Oid = "2.16.840.1.114027.80.9.4"; - const string MLDsa65WithRSA3072Pkcs15PreHashSha512Oid = "2.16.840.1.114027.80.9.5"; - const string MLDsa65WithRSA4096PssPreHashSha512Oid = "2.16.840.1.114027.80.9.6"; - const string MLDsa65WithRSA4096Pkcs15PreHashSha512Oid = "2.16.840.1.114027.80.9.7"; - const string MLDsa65WithECDsaP256PreHashSha512Oid = "2.16.840.1.114027.80.9.8"; - const string MLDsa65WithECDsaP384PreHashSha512Oid = "2.16.840.1.114027.80.9.9"; - const string MLDsa65WithECDsaBrainpoolP256r1PreHashSha512Oid = "2.16.840.1.114027.80.9.10"; - const string MLDsa65WithEd25519PreHashSha512Oid = "2.16.840.1.114027.80.9.11"; - const string MLDsa87WithECDsaP384PreHashSha512Oid = "2.16.840.1.114027.80.9.12"; - const string MLDsa87WithECDsaBrainpoolP384r1PreHashSha512Oid = "2.16.840.1.114027.80.9.13"; - const string MLDsa87WithEd448PreHashShake256_512Oid = "2.16.840.1.114027.80.9.14"; - const string MLDsa87WithRSA3072PssPreHashSha512Oid = "2.16.840.1.114027.80.9.15"; - const string MLDsa87WithRSA4096PssPreHashSha512Oid = "2.16.840.1.114027.80.9.16"; - const string MLDsa87WithECDsaP521PreHashSha512Oid = "2.16.840.1.114027.80.9.17"; + const string MLDsa44WithRSA2048PssPreHashSha256Oid = "2.16.840.1.114027.80.9.1.0"; + const string MLDsa44WithRSA2048Pkcs15PreHashSha256Oid = "2.16.840.1.114027.80.9.1.1"; + const string MLDsa44WithEd25519PreHashSha512Oid = "2.16.840.1.114027.80.9.1.2"; + const string MLDsa44WithECDsaP256PreHashSha256Oid = "2.16.840.1.114027.80.9.1.3"; + const string MLDsa65WithRSA3072PssPreHashSha512Oid = "2.16.840.1.114027.80.9.1.4"; + const string MLDsa65WithRSA3072Pkcs15PreHashSha512Oid = "2.16.840.1.114027.80.9.1.5"; + const string MLDsa65WithRSA4096PssPreHashSha512Oid = "2.16.840.1.114027.80.9.1.6"; + const string MLDsa65WithRSA4096Pkcs15PreHashSha512Oid = "2.16.840.1.114027.80.9.1.7"; + const string MLDsa65WithECDsaP256PreHashSha512Oid = "2.16.840.1.114027.80.9.1.8"; + const string MLDsa65WithECDsaP384PreHashSha512Oid = "2.16.840.1.114027.80.9.1.9"; + const string MLDsa65WithECDsaBrainpoolP256r1PreHashSha512Oid = "2.16.840.1.114027.80.9.1.10"; + const string MLDsa65WithEd25519PreHashSha512Oid = "2.16.840.1.114027.80.9.1.11"; + const string MLDsa87WithECDsaP384PreHashSha512Oid = "2.16.840.1.114027.80.9.1.12"; + const string MLDsa87WithECDsaBrainpoolP384r1PreHashSha512Oid = "2.16.840.1.114027.80.9.1.13"; + const string MLDsa87WithEd448PreHashShake256_512Oid = "2.16.840.1.114027.80.9.1.14"; + const string MLDsa87WithRSA3072PssPreHashSha512Oid = "2.16.840.1.114027.80.9.1.15"; + const string MLDsa87WithRSA4096PssPreHashSha512Oid = "2.16.840.1.114027.80.9.1.16"; + const string MLDsa87WithECDsaP521PreHashSha512Oid = "2.16.840.1.114027.80.9.1.17"; // Duplication is required here because there are separate CopyWithPrivateKey methods for each algorithm. var keyText = File.ReadAllText(keyPath);