Skip to content

Conversation

henning-krause
Copy link

@henning-krause henning-krause commented Sep 2, 2025

Added support for custom PSS salt length for RSA based signature operations. Resolves #104080.

With this PR, I have extended the RsaSignaturePadding class with a custom salt length as discussed in #104080. I've also updated the CmsSigner and the CertificateRequest class to support this.

CoseSigner did not need functionality updates, because the spec does not support custom salt length. I have added checks to prevent this to be configured on the CoseSigner.

Tests where either added or updated to test the new functionality.

All RSA implementations which support this (MAC doesn't seem to support this) were updated.

@Copilot Copilot AI review requested due to automatic review settings September 2, 2025 05:45
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Sep 2, 2025
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.

Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This pull request adds support for custom PSS salt length for RSA-based signature operations. The implementation extends the RSASignaturePadding class with a new CreatePss(int saltLength) method and updates related cryptographic components to utilize custom salt lengths.

Key changes include:

  • Extended RSASignaturePadding to support custom PSS salt lengths with new constants and factory method
  • Updated all RSA implementation backends (OpenSSL, CNG, BCrypt, etc.) to handle custom salt lengths
  • Added comprehensive test coverage for various salt length scenarios
  • Updated high-level APIs like CmsSigner and CertificateRequest to support the new functionality

Reviewed Changes

Copilot reviewed 40 out of 41 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_rsa.h Added pssSaltLength parameter to RSA sign/verify functions
src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_rsa.c Implemented custom salt length support in OpenSSL backend
src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSASignaturePadding.cs Added CreatePss method, PssSaltLength property, and related constants
src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/RSAPssX509SignatureGenerator.cs Updated to use custom salt lengths from padding configuration
src/libraries/Common/src/System/Security/Cryptography/RsaPaddingProcessor.cs Modified PSS encoding/verification to accept custom salt lengths
src/libraries/Common/src/System/Security/Cryptography/RsaPaddingProcessor.DigestInfo.cs Added salt length calculation logic and moved digest info constants
src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/RSAPssX509SignatureGeneratorTests.cs Added comprehensive tests for custom PSS salt lengths
src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSigner.cs Updated validation to accept any PSS mode padding
src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseSigner.cs Added validation to prevent custom salt lengths in COSE

henning-krause and others added 2 commits September 2, 2025 07:51
…tes/CertificateCreation/CertificateRequestChainTests.cs

Co-authored-by: Copilot <[email protected]>
@@ -41,6 +41,9 @@ internal RSASignaturePadding GetSignaturePadding(
HashAlgorithm.Algorithm));
}

#if NET10_0_OR_GREATER
Copy link
Member

Choose a reason for hiding this comment

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

This is tricky. Right now, .NET 10 is "done". Technically this work will go in for .NET 11. However, the runtime and SDK are not "bumped" to .NET 11 yet so #if NET11_0_OR_GREATER doesn't exist. We might need to wait for this work until #118583 is complete.

Copy link
Member

@bartonjs bartonjs left a comment

Choose a reason for hiding this comment

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

I've made it about halfway, but have to disappear for a bit, so posting what I've already seen.

Debug.Assert(padding == RSASignaturePadding.Pss);
if (padding.PssSaltLength < RSASignaturePadding.PssSaltLengthMax)
{
throw PaddingModeNotSupported();
Copy link
Member

Choose a reason for hiding this comment

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

The exception says the padding "mode" is invalid. Which isn't the case here. The "mode" is PSS, which is valid. So we probably want a new message for the salt size being gibberish (and yet a different one for "that doesn't work here")

Copy link
Author

Choose a reason for hiding this comment

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

@bartonjs Given that I followed the suggestion by @vcsjones and implemented validation logic on the RSASignaturePadding.CreatePss method, I think we can't run into this any more. Would it be sufficient to put in a Debug.Assert?

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, an assert here is fine given the throw there.

#if NET10_0_OR_GREATER
if (signaturePadding.Mode == RSASignaturePaddingMode.Pss)
{
if (signaturePadding.PssSaltLength != RSASignaturePadding.PssSaltLengthIsHashLength)
Copy link
Member

Choose a reason for hiding this comment

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

Should this also allow a custom value that happens to match?

[InlineData(0)]
[InlineData(17)]
[InlineData(RSASignaturePadding.PssSaltLengthMax)]
public void CoseSigner_PssPaddingWithInvalidSaltLength(int saltLength)
Copy link
Member

Choose a reason for hiding this comment

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

Whether we allow it, or not, there should be a test using 32 (matching SHA256).

[InlineData(Oids.Sha256, false, RSASignaturePadding.PssSaltLengthIsHashLength)]
[InlineData(Oids.Sha384, false, RSASignaturePadding.PssSaltLengthIsHashLength)]
[InlineData(Oids.Sha512, false, RSASignaturePadding.PssSaltLengthIsHashLength)]
[InlineData(Oids.Sha1, false, RSASignaturePadding.PssSaltLengthIsHashLength)]
Copy link
Member

Choose a reason for hiding this comment

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

This is a bit big for InlineData, probably should change to a MemberData using a couple of loops for enhanced legibility.

@@ -127,14 +127,14 @@ public static void ReadRsaPssDocument(bool fromSpan)
Assert.Throws<CryptographicException>(() => signer.CheckHash());

// At this time we cannot support the PSS parameters for this document.
Copy link
Member

Choose a reason for hiding this comment

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

Comment is out of date.

@@ -127,14 +127,14 @@ public static void ReadRsaPssDocument(bool fromSpan)
Assert.Throws<CryptographicException>(() => signer.CheckHash());

// At this time we cannot support the PSS parameters for this document.
Assert.Throws<CryptographicException>(() => signer.CheckSignature(true));
signer.CheckSignature(true);

// Since there are no NoSignature signers the document CheckHash will succeed.
// Assert.NotThrows
cms.CheckHash();

// Since at least one signer fails, the document signature will fail
Copy link
Member

Choose a reason for hiding this comment

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

Comment is out of date.

[InlineData("SHA256", 1)]
[InlineData("SHA384", 1)]
[InlineData("SHA512", 1)]
//[InlineData("SHA1", 1)] // The current implementation for CertificateRequest does not support SHA-1 with PSS. If this is required, the RSASha1PssSignatureGenerator and the RSAPssX509SignatureGenerator class needs updates.
Copy link
Member

Choose a reason for hiding this comment

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

SHA-1 used to be tested here. And the code doing it is still here. The coverage shouldn't be lost.

deterministicSignature: false);
RSASignaturePadding.CreatePss(saltLength)),
X509SignatureGenerator.CreateForRSA(key, RSASignaturePadding.CreatePss(saltLength)),
deterministicSignature: saltLength == 0);
Copy link
Member

Choose a reason for hiding this comment

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

So why isn't 0 included in the InlineData then?

Comment on lines 58 to +61
}


[Theory]
Copy link
Member

Choose a reason for hiding this comment

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

Nit: too many blank lines

Suggested change
}
[Theory]
}
[Theory]

Comment on lines +74 to +84
var data = new byte[] { 1, 2, 3, 4, 5 };
var signature = signatureGenerator.SignData(data, HashAlgorithmName.SHA256);
var signatureAlgorithm = signatureGenerator.GetSignatureAlgorithmIdentifier(HashAlgorithmName.SHA256);

var asnReader = new AsnReader(signatureAlgorithm, AsnEncodingRules.DER);
var rootSequence = asnReader.ReadSequence();
Assert.Equal("1.2.840.113549.1.1.10", rootSequence.ReadObjectIdentifier()); // Make sure it's RSASSA-PSS
var pssStructure = rootSequence.ReadSequence();
pssStructure.ReadEncodedValue(); // Ignore the hash algorithm OID
pssStructure.ReadEncodedValue(); // Ignore the mask generation function OID
var saltTag = new Asn1Tag(TagClass.ContextSpecific, 2, true);
Copy link
Member

Choose a reason for hiding this comment

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

Many uses of var here that are against style guidelines. If you want to keep them when it's permitted, you can, but I'd just change all of them to specify the type.

Comment on lines +84 to +85
var saltTag = new Asn1Tag(TagClass.ContextSpecific, 2, true);
if (pssStructure.HasData && pssStructure.PeekTag().HasSameClassAndValue(saltTag))
Copy link
Member

Choose a reason for hiding this comment

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

Blank lines before control flow is preferred

Suggested change
var saltTag = new Asn1Tag(TagClass.ContextSpecific, 2, true);
if (pssStructure.HasData && pssStructure.PeekTag().HasSameClassAndValue(saltTag))
var saltTag = new Asn1Tag(TagClass.ContextSpecific, 2, true);
if (pssStructure.HasData && pssStructure.PeekTag().HasSameClassAndValue(saltTag))

/// </remarks>
public int PssSaltLength { get; }

public static RSASignaturePadding CreatePss(int saltLength)
Copy link
Member

Choose a reason for hiding this comment

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

Public method requires documentation

@jeffhandley jeffhandley added this to the 11.0.0 milestone Sep 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-System.Security community-contribution Indicates that the PR has been added by a community member
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[API Proposal]: RSA PSS salt length
4 participants