-
Notifications
You must be signed in to change notification settings - Fork 5k
Signed CMS for SLH-DSA #115310
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Signed CMS for SLH-DSA #115310
Conversation
Note regarding the
|
1 similar comment
Note regarding the
|
Tagging subscribers to this area: @dotnet/area-system-security, @bartonjs, @vcsjones |
Line 33 in 86949c2
|
I am curious if we need to implement any of this down level, since .NET Framework will have its own implementation. I... think... it probably could make sense to implement all of the new public API surface as
|
...raries/System.Security.Cryptography.Pkcs/ref/System.Security.Cryptography.Pkcs.netcoreapp.cs
Outdated
Show resolved
Hide resolved
...stem.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.SlhDsa.cs
Outdated
Show resolved
Hide resolved
...ries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.cs
Outdated
Show resolved
Hide resolved
...braries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSigner.cs
Outdated
Show resolved
Hide resolved
...braries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSigner.cs
Outdated
Show resolved
Hide resolved
...braries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSigner.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/SignedCmsTests.netcoreapp.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/SignedCmsTests.netcoreapp.cs
Show resolved
Hide resolved
src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/SignedCmsTests.netcoreapp.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/SignedCmsTests.netcoreapp.cs
Outdated
Show resolved
Hide resolved
...braries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSigner.cs
Outdated
Show resolved
Hide resolved
For Unix, downlevel won't work because we don't have the key accessors on certs for openssl in Microsoft.Bcl.Cryptography. However, I think we can still support Windows downlevel (net8.0 and net9.0) since the accessors will be available for them.
After talking to @bartonjs about this: netstandard2.0 should have the same API as NetFx so that means no public API changes. I think |
...ryptography/src/System/Security/Cryptography/X509Certificates/X509CertificateKeyAccessors.cs
Outdated
Show resolved
Hide resolved
.../System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.DSA.cs
Outdated
Show resolved
Hide resolved
...libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsHash.cs
Outdated
Show resolved
Hide resolved
/// <exception cref="CryptographicException"> | ||
/// The public key was invalid, or otherwise could not be imported. | ||
/// </exception> | ||
[ExperimentalAttribute("SYSLIB5006")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should use the const (and UrlFormat, as being introduced in #115412)
[Experimental(Experimentals.PostQuantumCryptographyDiagId)]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@barotnjs I don't know where we landed on this - but shouldn't the experimental be on the class? Like, if we ship the class it would, technically, be an unguarded breaking change for us to remove it, if we needed to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we put it on the class, but then mark ML-DSA (or whatever) as "not Experimental" while one of the rest still is, we have to be diligent at moving the Experimentals down to each remaining member.
We could also put it on the class until there's one stable member so that we don't end up with "well, we deleted everything, because all of PQC got abandoned by the industry before the specs all went final-stable.... so now we have this empty static class"... but I don't really see that outcome, so I don't feel like the class-level one is warranted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could also put it on the class until there's one stable member so that we don't end up with "well, we deleted everything, because all of PQC got abandoned by the industry before the specs all went final-stable.
More like, "It turns out that extensions are not the right shape for [whatever reason]" but, okay.
/// <summary> | ||
/// Helper methods to access keys on <see cref="X509Certificate2"/>. | ||
/// </summary> | ||
public static class X509CertificateKeyAccessors |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should EB-Never this for .NET 10+?
public static class X509CertificateKeyAccessorsTests | ||
{ | ||
[ConditionalFact(typeof(SlhDsa), nameof(SlhDsa.IsSupported))] | ||
public static void GetPublicKey() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we are using a single class for all of the algorithm names, the tests here should have SLH-DSA somewhere in the test name (repeat feedback for all tests in fixture)
#else | ||
// Unix: OpenSsl accessors not supported downlevel | ||
// Windows: SlhDsa currently not supported so test won't execute | ||
Assert.Throws<PlatformNotSupportedException>(() => X509CertificateKeyAccessors.GetSlhDsaPrivateKey(null)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will this assert even be hit because it's a ConditionalFact
?
- Windows returns
false
right now.... - OOB Linux should return
false
right now...
So I am failing to see when this #else
will be hit.
X509Certificate2 copied = X509CertificateKeyAccessors.CopyWithPrivateKey(cert, privateKey); | ||
AssertExtensions.TrueExpression(copied.HasPrivateKey); | ||
|
||
SlhDsa? copiedKey = copied.GetSlhDsaPrivateKey(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should dispose of the cert and the key.
@@ -177,6 +179,8 @@ public sealed override byte[] GetSubjectKeyIdentifier(X509Certificate2 certifica | |||
return (T)(object)new RSACryptoServiceProvider(cspParams); | |||
if (typeof(T) == typeof(DSA)) | |||
return (T)(object)new DSACryptoServiceProvider(cspParams); | |||
if (typeof(T) == typeof(SlhDsa)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this if
is ever going to be hit because this is the CAPI path and I would be... surprised... if and PQC types worked with CAPI. ECDsa
doesn't, either, which is why ECDsa is not here.
@@ -1,4 +1,4 @@ | |||
<Project Sdk="Microsoft.NET.Sdk"> | |||
<Project Sdk="Microsoft.NET.Sdk"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is adding a BOM. We try to keep BOMs out of this repo, if I remember correctly.
// RFC 8702 specifies SHAKE256 in CMS must use 512 bits of output. | ||
private const int OutputSizeBytes = 512 / 8; | ||
|
||
private readonly Shake256 _shake256; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
YESSS. Finally some use of SHAKE!
Note that we did not OOB any of the SHA-3 APIs. So, if you think this is going to be needed to make CMS work for .NET Standard or .NET 9, then the Shake API's won't be available.
// The spec (as of May 5, 2025) has strength requirements on the hash, but we will | ||
// not enforce them here. If the callers wants to enforce them, they can do so by themselves. | ||
|
||
SlhDsa? publicKey = certificate.GetSlhDsaPublicKey(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We didn't get set a good example in the other algorithms, but this should be disposed (The classic-crypto algs can be cleaned up in a separate PR. Maybe we should make an issue for that?)
public static class X509CertificateKeyAccessorsTests | ||
{ | ||
[ConditionalFact(typeof(SlhDsa), nameof(SlhDsa.IsSupported))] | ||
public static void GetPublicKey() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public static void GetPublicKey() | |
public static void GetSlhDsaPublicKey() |
{ | ||
Assert.NotNull(certKey); | ||
AssertExtensions.SequenceEqual(SlhDsaTestData.IetfSlhDsaSha2_128sPublicKeyValue, certKey.ExportSlhDsaPublicKey()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This shows that it exports the correct public key, but not that it's not a private key.
It should look for a CryptographicException from signing something. (Looking for an exception from exporting the private key isn't quite right, since that would also happen from a non-exportable private key... though X509Certificate2.CreateFromPem shouldn't make one of those).
#else | ||
// Unix: OpenSsl accessors not supported downlevel | ||
// Windows: SlhDsa currently not supported so test won't execute | ||
Assert.Throws<PlatformNotSupportedException>(() => X509CertificateKeyAccessors.GetSlhDsaPrivateKey(null)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test is already conditional on SlhDsa.IsSupported. So why is it using #if, and why would it expect failure?
{ | ||
#if NET10_0_OR_GREATER | ||
string certPem = PemEncoding.WriteString("CERTIFICATE", SlhDsaTestData.IetfSlhDsaSha2_128sCertificate); | ||
using (X509Certificate2 cert = X509Certificate2.CreateFromPem(certPem)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test should also check that GetSlhDsaPublicKey is returning a public-only key when a private key is available.
} | ||
|
||
[ConditionalFact(typeof(SlhDsa), nameof(SlhDsa.IsSupported))] | ||
public static void GetPrivateKey() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public static void GetPrivateKey() | |
public static void GetSlhDsaPrivateKey() |
/// <exception cref="CryptographicException"> | ||
/// The public key was invalid, or otherwise could not be imported. | ||
/// </exception> | ||
[ExperimentalAttribute("SYSLIB5006")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we put it on the class, but then mark ML-DSA (or whatever) as "not Experimental" while one of the rest still is, we have to be diligent at moving the Experimentals down to each remaining member.
We could also put it on the class until there's one stable member so that we don't end up with "well, we deleted everything, because all of PQC got abandoned by the industry before the specs all went final-stable.... so now we have this empty static class"... but I don't really see that outcome, so I don't feel like the class-level one is warranted.
signatureParameters = null; | ||
|
||
// If there's no private key, fall back to the public key for a "no private key" exception. | ||
SlhDsa? signingKey = key as SlhDsa ?? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Likewise, more algorithms need to be disposed (and cleanup for the others can happen separately)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just have to be careful here to not dispose of the key if it was passed in externally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// If there's no private key, fall back to the public key for a "no private key" exception.
Seems like a roundabout way of doing it... can we just throw here ourselves or do we prefer the exception coming from the cert?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this has a type forward to .NET Framework, we try to be exception compatible with .NET Framework, and this is what .NET Framework does.
This PR forks Sign and Verify into a Pure and Hash mode with the Hash mode being the same as the existing implementation. Additionally it adds support for SHAKE128 and SHAKE256 as digest algorithms in CMS.
Some design decisions:
SubjectIdentifierType.NoSignature
is specified, we will check whether the signing algorithm is SLH-DSA and throw if it is. Otherwise we will just use the hash as the signature as before. If Windows has a different behavior for NetFx, we will just adopt it since we don't have an opinion here.CheckHash
.CMS for SLH-DSA successfully roundtrips with openssl (message created with SignedCms can be verified with OpenSsl and vice versa).