Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Initial draft of 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

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

PranavSenthilnathan
Copy link
Member

CMS for SLH-DSA successfully roundtrips with openssl (message created with SignedCms can be verified with OpenSsl and vice versa). The new implementation forks Sign and Verify into a Pure and Hash mode with the Hash mode being the same as the existing implementation. All existing tests pass.

This is currently a draft mainly so some questions can be answered. These are TODOs in code and briefly enumerated here:

  • Countersigning
  • NoSignature
  • CheckHash
  • Hash strength
  • Are we going to support NET Framework?
  • Build (csproj) changes and questions about where to ifdef

Copy link

Note regarding the new-api-needs-documentation label:

This serves as a reminder for when your PR is modifying a ref *.cs file and adding/modifying public APIs, please make sure the API implementation in the src *.cs file is documented with triple slash comments, so the PR reviewers can sign off that change.

1 similar comment
Copy link

Note regarding the new-api-needs-documentation label:

This serves as a reminder for when your PR is modifying a ref *.cs file and adding/modifying public APIs, please make sure the API implementation in the src *.cs file is documented with triple slash comments, so the PR reviewers can sign off that change.

Copy link
Contributor

Tagging subscribers to this area: @dotnet/area-system-security, @bartonjs, @vcsjones
See info in area-owners.md if you want to be subscribed.

@vcsjones
Copy link
Member

vcsjones commented May 5, 2025

Are we going to support NET Framework?

SignedCms and all of its associated types type-forward to the .NET Framework implementation. We can't enable it on .NET Framework through System.Security.Cryptography.Pkcs. Whatever work that needs to be done (if any) will need to be done in .NET Framework itself.

[assembly: TypeForwardedTo(typeof(System.Security.Cryptography.Pkcs.SignedCms))]

@vcsjones
Copy link
Member

vcsjones commented May 5, 2025

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 #if NET10_0_OR_GREATER and don't implement it down level at all.

  1. >= net462 TFMs are going to get type-forwarded.
  2. <= net9.0 TFMs won't have the new public API. That's okay, they should just move to .NET 10. .NET (Core) users should expect to require updating to get new capabilities.
  3. >= netstandard2.0 this is where... maybe... there is value in doing the OOB. If someone is on netstandard and on Windows, but not .NET Framework and not .NET Core, then down level support might be justified. The one that has come up in the past is UWP.

public System.Security.Cryptography.AsymmetricAlgorithm? PrivateKey { get { throw null; } set { } }
public System.Security.Cryptography.RSASignaturePadding? SignaturePadding { get { throw null; } set { } }
[System.Diagnostics.CodeAnalysis.ExperimentalAttribute("SYSLIB5006")]
public System.Security.Cryptography.SlhDsa? SlhDsaPrivateKey { get { throw null; } set { } }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the AsymmetricAlgorithm version has already bitten us: Don't add a public property here until we have a compelling reason.

Comment on lines +66 to +67
// TODO Current 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So... what's the TODO? That's just a statement of fact.

using System.Security.Cryptography.X509Certificates;
using Internal.Cryptography;
using static System.Security.Cryptography.Pkcs.CmsSigner;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
using static System.Security.Cryptography.Pkcs.CmsSigner;

set => _privateKey = value;
}

// TODO not sure why PrivateKey is ifdef'd out...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These types are typeforwarded to .NET Framework for the netfx implementation, which means the .NET Standard or .NET Framework TFMs can't expose any new members.

And that's why for .NET Framework the only scenario is "passed a certificate with an attached private key"... those ctors are the only ones that exist there.

CmsSigner(SubjectIdentifierType signerIdentifierType, X509Certificate2? certificate, SlhDsa? privateKey)
: this(signerIdentifierType, certificate, privateKey, signaturePadding: null)
{
// TODO this ctor can break users if they had been passing in null for privateKey since it's now ambiguous
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again... what's the TODO? If it's a note for API Review, that doesn't belong in code.

@@ -311,21 +405,63 @@ internal SignerInfoAsn Sign(
ReadOnlyMemory<byte> signatureValue;
ReadOnlyMemory<byte> signatureParameters = default;


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

(SlhDsaAlgorithm.SlhDsaSha2_128s, Oids.Sha256),
(SlhDsaAlgorithm.SlhDsaShake128f, Oids.Sha256),
(SlhDsaAlgorithm.SlhDsaSha2_256f, Oids.Sha512),
(SlhDsaAlgorithm.SlhDsaShake256f, Oids.Sha512), // TODO SHAKE256 is not supported but it's the recommended algo here..
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// TODO SHAKE256 is not supported but it's the recommended algo here..

Sounds like you need to put a layer of indirection in front of IncrementalHash, then?

Comment on lines +1103 to +1112
private delegate CmsSigner CreateSignerFunc<TKey>(SubjectIdentifierType sit, X509Certificate2 cert, TKey key);
private static CreateSignerFunc<AsymmetricAlgorithm> CreateAsymmetricAlgorithmSigner = (sit, cert, key) =>
{
return new CmsSigner(sit, cert, key);
};

private static CreateSignerFunc<SlhDsa> CreateSlhDsaSigner = (sit, cert, key) =>
{
return new CmsSigner(sit, cert, key);
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think these delegates add value (but do add cost and complexity).

VerifyWithExplicitPrivateKey should just take object and type test to know what to call.

And then I don't have to be upset about the chopping style used where these made the lines too long 😄

CertLoader loader = Certificates.SlhDsaGeneratedCerts.Single(cert => cert.CerData.SequenceEqual(info.Certificate));
using (X509Certificate2 signerCert = loader.TryGetCertificateWithPrivateKey())
{
CmsSigner signer = new CmsSigner(identifierType, signerCert);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Testing with a CmsSigner using only a cert-attached private key should also be done for the netfx tests. A fair amount of the validation here uses "new" API, so the netfx-capable test will have to be watered down.

Perhaps it should just gain #if around the parts that can't be tested in netfx, and this moved out of the netcoreapp partial.

@@ -868,7 +1133,13 @@ private static void VerifyWithExplicitPrivateKey(X509Certificate2 cert, Asymmetr
}
}

private static void VerifyCounterSignatureWithExplicitPrivateKey(X509Certificate2 cert, AsymmetricAlgorithm key, X509Certificate2 counterSignerCert, AsymmetricAlgorithm counterSignerKey)
private static void VerifyCounterSignatureWithExplicitPrivateKey<TKey, TCounterSignerKey>(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

De-generic, just take object and type test.

byte[]? attributesToSign = null;
signedAttrsSet.SignedAttributes = PkcsHelpers.NormalizeAttributeSet(
signedAttrs.ToArray(),
encodedSignedAttrs => attributesToSign = encodedSignedAttrs);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is the only call to NormalizeAttributeSet now, just change it to out the value. Capturing via a lambda feels icky.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at current usage, everyone else passes null, which means making a proper overload, one that does the work and has an out, and the other that just sends the out to discard.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants