Skip to content

Commit 2907a39

Browse files
ML-DSA SignedCMS (#118088)
1 parent 9713fda commit 2907a39

File tree

15 files changed

+1047
-411
lines changed

15 files changed

+1047
-411
lines changed

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,14 @@ public MLDsaKeyInfo(
7878
public byte[] EncryptionPasswordBytes => Encoding.UTF8.GetBytes(EncryptionPassword); // Assuming UTF-8 encoding
7979
public byte[] Certificate => Convert.FromBase64String(CertificateBase64);
8080
#if !EXCLUDE_PEM_ENCODING_FROM_TEST_DATA
81-
public string EncryptedPem_Seed => PemEncoding.WriteString("ENCRYPTED PRIVATE KEY", Pkcs8EncryptedPrivateKey_Seed);
82-
public string EncryptedPem_Expanded => PemEncoding.WriteString("ENCRYPTED PRIVATE KEY", Pkcs8EncryptedPrivateKey_Expanded);
83-
public string EncryptedPem_Both => PemEncoding.WriteString("ENCRYPTED PRIVATE KEY", Pkcs8EncryptedPrivateKey_Both);
84-
public string PrivateKeyPem_Seed => PemEncoding.WriteString("PRIVATE KEY", Pkcs8PrivateKey_Seed);
85-
public string PrivateKeyPem_Expanded => PemEncoding.WriteString("PRIVATE KEY", Pkcs8PrivateKey_Expanded);
86-
public string PrivateKeyPem_Both => PemEncoding.WriteString("PRIVATE KEY", Pkcs8PrivateKey_Both);
87-
public string PublicKeyPem => PemEncoding.WriteString("PUBLIC KEY", Pkcs8PublicKey);
88-
public string CertificatePem => PemEncoding.WriteString("CERTIFICATE", Certificate);
81+
public string EncryptedPem_Seed => ByteUtils.PemEncode("ENCRYPTED PRIVATE KEY", Pkcs8EncryptedPrivateKey_Seed);
82+
public string EncryptedPem_Expanded => ByteUtils.PemEncode("ENCRYPTED PRIVATE KEY", Pkcs8EncryptedPrivateKey_Expanded);
83+
public string EncryptedPem_Both => ByteUtils.PemEncode("ENCRYPTED PRIVATE KEY", Pkcs8EncryptedPrivateKey_Both);
84+
public string PrivateKeyPem_Seed => ByteUtils.PemEncode("PRIVATE KEY", Pkcs8PrivateKey_Seed);
85+
public string PrivateKeyPem_Expanded => ByteUtils.PemEncode("PRIVATE KEY", Pkcs8PrivateKey_Expanded);
86+
public string PrivateKeyPem_Both => ByteUtils.PemEncode("PRIVATE KEY", Pkcs8PrivateKey_Both);
87+
public string PublicKeyPem => ByteUtils.PemEncode("PUBLIC KEY", Pkcs8PublicKey);
88+
public string CertificatePem => ByteUtils.PemEncode("CERTIFICATE", Certificate);
8989
#endif
9090
public byte[] Pfx_Seed => Convert.FromBase64String(Pfx_Seed_Base64);
9191
public byte[] Pfx_Expanded => Convert.FromBase64String(Pfx_Expanded_Base64);

src/libraries/System.Security.Cryptography.Pkcs/ref/System.Security.Cryptography.Pkcs.netcoreapp.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ public sealed partial class CmsSigner
1616
{
1717
public CmsSigner(System.Security.Cryptography.Pkcs.SubjectIdentifierType signerIdentifierType, System.Security.Cryptography.X509Certificates.X509Certificate2? certificate, System.Security.Cryptography.AsymmetricAlgorithm? privateKey) { }
1818
[System.Diagnostics.CodeAnalysis.ExperimentalAttribute("SYSLIB5006", UrlFormat="https://aka.ms/dotnet-warnings/{0}")]
19-
public CmsSigner(System.Security.Cryptography.Pkcs.SubjectIdentifierType signerIdentifierType, System.Security.Cryptography.X509Certificates.X509Certificate2? certificate, System.Security.Cryptography.SlhDsa? privateKey) { }
19+
public CmsSigner(System.Security.Cryptography.Pkcs.SubjectIdentifierType signerIdentifierType, System.Security.Cryptography.X509Certificates.X509Certificate2? certificate, System.Security.Cryptography.MLDsa? privateKey) { }
2020
public CmsSigner(System.Security.Cryptography.Pkcs.SubjectIdentifierType signerIdentifierType, System.Security.Cryptography.X509Certificates.X509Certificate2? certificate, System.Security.Cryptography.RSA? privateKey, System.Security.Cryptography.RSASignaturePadding? signaturePadding) { }
21+
[System.Diagnostics.CodeAnalysis.ExperimentalAttribute("SYSLIB5006", UrlFormat="https://aka.ms/dotnet-warnings/{0}")]
22+
public CmsSigner(System.Security.Cryptography.Pkcs.SubjectIdentifierType signerIdentifierType, System.Security.Cryptography.X509Certificates.X509Certificate2? certificate, System.Security.Cryptography.SlhDsa? privateKey) { }
2123
public System.Security.Cryptography.AsymmetricAlgorithm? PrivateKey { get { throw null; } set { } }
2224
public System.Security.Cryptography.RSASignaturePadding? SignaturePadding { get { throw null; } set { } }
2325
}

src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ public override byte[] GetSubjectKeyIdentifier(X509Certificate2 certificate)
8484
if (typeof(T) == typeof(DSA) && Internal.Cryptography.Helpers.IsDSASupported)
8585
return (T?)(object?)certificate.GetDSAPrivateKey();
8686
#endif
87+
if (typeof(T) == typeof(MLDsa) && MLDsa.IsSupported)
88+
return (T?)(object?)certificate.GetMLDsaPrivateKey();
8789
if (typeof(T) == typeof(SlhDsa) && SlhDsa.IsSupported)
8890
return (T?)(object?)certificate.GetSlhDsaPrivateKey();
8991

src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/Windows/PkcsPalWindows.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ public sealed override byte[] GetSubjectKeyIdentifier(X509Certificate2 certifica
150150
return (T)(object)new ECDsaCng(cngKey);
151151
if (typeof(T) == typeof(DSA))
152152
return (T)(object)new DSACng(cngKey);
153+
if (typeof(T) == typeof(MLDsa))
154+
return (T)(object)new MLDsaCng(cngKey);
153155
if (typeof(T) == typeof(SlhDsa))
154156
throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(SlhDsa)));
155157

src/libraries/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,7 @@ System.Security.Cryptography.Pkcs.EnvelopedCms</PackageDescription>
589589
<Compile Include="System\Security\Cryptography\Pkcs\CmsSignature.cs" />
590590
<Compile Include="System\Security\Cryptography\Pkcs\CmsSignature.ECDsa.cs" />
591591
<Compile Include="System\Security\Cryptography\Pkcs\CmsSignature.RSA.cs" />
592+
<Compile Include="System\Security\Cryptography\Pkcs\CmsSignature.MLDsa.cs" />
592593
<Compile Include="System\Security\Cryptography\Pkcs\CmsSignature.SlhDsa.cs" />
593594
<Compile Include="System\Security\Cryptography\Pkcs\CmsSigner.cs" />
594595
<Compile Include="System\Security\Cryptography\Pkcs\SignedCms.cs" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
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.Diagnostics.CodeAnalysis;
6+
using System.Security.Cryptography.X509Certificates;
7+
8+
namespace System.Security.Cryptography.Pkcs
9+
{
10+
internal partial class CmsSignature
11+
{
12+
static partial void PrepareRegistrationMLDsa(Dictionary<string, CmsSignature> lookup)
13+
{
14+
lookup.Add(Oids.MLDsa44, new MLDsaCmsSignature(Oids.MLDsa44));
15+
lookup.Add(Oids.MLDsa65, new MLDsaCmsSignature(Oids.MLDsa65));
16+
lookup.Add(Oids.MLDsa87, new MLDsaCmsSignature(Oids.MLDsa87));
17+
}
18+
19+
private sealed class MLDsaCmsSignature : CmsSignature
20+
{
21+
private string _signatureAlgorithm;
22+
23+
internal MLDsaCmsSignature(string signatureAlgorithm)
24+
{
25+
_signatureAlgorithm = signatureAlgorithm;
26+
}
27+
28+
protected override bool VerifyKeyType(object key) => key is MLDsa;
29+
internal override bool NeedsHashedMessage => false;
30+
31+
internal override RSASignaturePadding? SignaturePadding => null;
32+
33+
internal override bool VerifySignature(
34+
#if NET || NETSTANDARD2_1
35+
ReadOnlySpan<byte> valueHash,
36+
ReadOnlyMemory<byte> signature,
37+
#else
38+
byte[] valueHash,
39+
byte[] signature,
40+
#endif
41+
string? digestAlgorithmOid,
42+
ReadOnlyMemory<byte>? signatureParameters,
43+
X509Certificate2 certificate)
44+
{
45+
if (signatureParameters.HasValue)
46+
{
47+
throw new CryptographicException(
48+
SR.Format(SR.Cryptography_UnknownAlgorithmIdentifier, _signatureAlgorithm));
49+
}
50+
51+
MLDsa? publicKey = certificate.GetMLDsaPublicKey();
52+
53+
if (publicKey is null)
54+
{
55+
return false;
56+
}
57+
58+
using (publicKey)
59+
{
60+
return publicKey.VerifyData(
61+
valueHash,
62+
#if NET || NETSTANDARD2_1
63+
signature.Span
64+
#else
65+
signature
66+
#endif
67+
);
68+
}
69+
}
70+
71+
protected override bool Sign(
72+
#if NET || NETSTANDARD2_1
73+
ReadOnlySpan<byte> dataHash,
74+
#else
75+
byte[] dataHash,
76+
#endif
77+
string? hashAlgorithmOid,
78+
X509Certificate2 certificate,
79+
object? key,
80+
bool silent,
81+
[NotNullWhen(true)] out string? signatureAlgorithm,
82+
[NotNullWhen(true)] out byte[]? signatureValue,
83+
out byte[]? signatureParameters)
84+
{
85+
signatureParameters = null;
86+
signatureAlgorithm = _signatureAlgorithm;
87+
88+
using (GetSigningKey(key, certificate, silent, static cert => cert.GetMLDsaPublicKey(), out MLDsa? signingKey))
89+
{
90+
if (signingKey is null)
91+
{
92+
signatureValue = null;
93+
return false;
94+
}
95+
96+
// Don't pool because we will likely return this buffer to the caller.
97+
byte[] signature = new byte[signingKey.Algorithm.SignatureSizeInBytes];
98+
signingKey.SignData(dataHash, signature);
99+
100+
if (key != null)
101+
{
102+
using (MLDsa certKey = certificate.GetMLDsaPublicKey()!)
103+
{
104+
if (!certKey.VerifyData(dataHash, signature))
105+
{
106+
signatureValue = null;
107+
return false;
108+
}
109+
}
110+
}
111+
112+
signatureValue = signature;
113+
return true;
114+
}
115+
}
116+
}
117+
}
118+
}

src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@ static CmsSignature()
2121
PrepareRegistrationRsa(s_lookup);
2222
PrepareRegistrationDsa(s_lookup);
2323
PrepareRegistrationECDsa(s_lookup);
24+
PrepareRegistrationMLDsa(s_lookup);
2425
PrepareRegistrationSlhDsa(s_lookup);
2526
}
2627

2728
static partial void PrepareRegistrationRsa(Dictionary<string, CmsSignature> lookup);
2829
static partial void PrepareRegistrationDsa(Dictionary<string, CmsSignature> lookup);
2930
static partial void PrepareRegistrationECDsa(Dictionary<string, CmsSignature> lookup);
31+
static partial void PrepareRegistrationMLDsa(Dictionary<string, CmsSignature> lookup);
3032
static partial void PrepareRegistrationSlhDsa(Dictionary<string, CmsSignature> lookup);
3133

3234
internal abstract RSASignaturePadding? SignaturePadding { get; }

src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSigner.cs

Lines changed: 12 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,16 @@ public CmsSigner(SubjectIdentifierType signerIdentifierType, X509Certificate2? c
111111
{
112112
}
113113

114+
#if NET || NETSTANDARD2_1
115+
[Experimental(Experimentals.PostQuantumCryptographyDiagId, UrlFormat = Experimentals.SharedUrlFormat)]
116+
public
117+
#else
118+
private
119+
#endif
120+
CmsSigner(SubjectIdentifierType signerIdentifierType, X509Certificate2? certificate, MLDsa? privateKey)
121+
: this(signerIdentifierType, certificate, privateKey, signaturePadding: null)
122+
{
123+
}
114124

115125
#if NET || NETSTANDARD2_1
116126
[Experimental(Experimentals.PostQuantumCryptographyDiagId, UrlFormat = Experimentals.SharedUrlFormat)]
@@ -193,7 +203,7 @@ private CmsSigner(
193203
Certificate = certificate;
194204
DigestAlgorithm = s_defaultAlgorithm.CopyOid();
195205

196-
Debug.Assert(privateKey is null or AsymmetricAlgorithm or SlhDsa);
206+
Debug.Assert(privateKey is null or AsymmetricAlgorithm or MLDsa or SlhDsa);
197207
_privateKey = (IDisposable?)privateKey;
198208

199209
_signaturePadding = signaturePadding;
@@ -371,36 +381,8 @@ internal SignerInfoAsn Sign(
371381

372382
if (SignerIdentifierType == SubjectIdentifierType.NoSignature)
373383
{
374-
// The behavior of this scenario should match Windows which currently does not
375-
// implement PQC. So we do a best effort determination of whether the algorithm
376-
// is a pure algorithm and throw if so. This is subject to change once Windows
377-
// implements PQC.
378-
string? keyAlgorithm = null;
379-
if (Certificate != null)
380-
{
381-
try
382-
{
383-
keyAlgorithm = Certificate.GetKeyAlgorithm();
384-
}
385-
catch (CryptographicException)
386-
{
387-
}
388-
}
389-
390-
if (keyAlgorithm != null)
391-
{
392-
CmsSignature? processor = CmsSignature.ResolveAndVerifyKeyType(keyAlgorithm, _privateKey, SignaturePadding);
393-
if (processor?.NeedsHashedMessage == false)
394-
{
395-
throw new CryptographicException(SR.Cryptography_Cms_CertificateDoesNotSupportNoSignature);
396-
}
397-
}
398-
399-
ReadOnlyMemory<byte> messageToSign =
400-
GetMessageToSign(shouldHash: true, data, contentTypeOid, out newSignerInfo.SignedAttributes);
401-
402384
signatureAlgorithm = Oids.NoSignature;
403-
signatureValue = messageToSign;
385+
signatureValue = GetMessageToSign(shouldHash: true, data, contentTypeOid, out newSignerInfo.SignedAttributes);
404386
signed = true;
405387
}
406388
else

src/libraries/System.Security.Cryptography.Pkcs/tests/Certificates.cs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Collections.Generic;
45
using System.Linq;
56
using System.Security.Cryptography.SLHDsa.Tests;
7+
using System.Security.Cryptography.Tests;
68
using Test.Cryptography;
79

810
namespace System.Security.Cryptography.Pkcs.Tests
@@ -35,12 +37,14 @@ internal static class Certificates
3537
public static readonly CertLoader RsaOaep2048_Sha256Parameters = new CertLoaderFromRawData(RawData.RsaOaep2048_Sha256ParametersCert, RawData.RsaOaep2048_Sha256ParametersPfx, "1111");
3638
public static readonly CertLoader RsaOaep2048_NoParameters = new CertLoaderFromRawData(RawData.RsaOaep2048_NoParametersCert, RawData.RsaOaep2048_NoParametersPfx, "1111");
3739
public static readonly CertLoader SlhDsaSha2_128s_Ietf = new CertLoaderFromRawData(SlhDsaTestData.IetfSlhDsaSha2_128sCertificate, SlhDsaTestData.IetfSlhDsaSha2_128sCertificatePfx, "PLACEHOLDER");
38-
public static readonly CertLoader[] SlhDsaGeneratedCerts = LoadSlhDsaCerts();
40+
public static readonly CertLoader[] SlhDsaGeneratedCerts = [..SlhDsaTestData.GeneratedKeyInfosRaw.Select(info => new CertLoaderFromRawData(info.Certificate, info.SelfSignedCertificatePfx, info.EncryptionPassword))];
3941

40-
private static CertLoader[] LoadSlhDsaCerts() =>
41-
SlhDsaTestData.GeneratedKeyInfosRaw
42-
.Select(info => new CertLoaderFromRawData(info.Certificate, info.SelfSignedCertificatePfx, info.EncryptionPassword))
43-
.ToArray();
42+
public static readonly Dictionary<MLDsaAlgorithm, CertLoader> MLDsaIetf = new()
43+
{
44+
{ MLDsaAlgorithm.MLDsa44, new CertLoaderFromRawData(MLDsaTestsData.IetfMLDsa44.Certificate, MLDsaTestsData.IetfMLDsa44.Pfx_Seed, "PLACEHOLDER") },
45+
{ MLDsaAlgorithm.MLDsa65, new CertLoaderFromRawData(MLDsaTestsData.IetfMLDsa65.Certificate, MLDsaTestsData.IetfMLDsa65.Pfx_Seed, "PLACEHOLDER") },
46+
{ MLDsaAlgorithm.MLDsa87, new CertLoaderFromRawData(MLDsaTestsData.IetfMLDsa87.Certificate, MLDsaTestsData.IetfMLDsa87.Pfx_Seed, "PLACEHOLDER") },
47+
};
4448

4549
// Note: the raw data is its own (nested) class to avoid problems with static field initialization ordering.
4650
private static class RawData

0 commit comments

Comments
 (0)