Skip to content

104080 custom rsapss salt length - #119255

Open
henning-krause wants to merge 65 commits into
dotnet:mainfrom
henning-krause:104080-custom-rsapss-salt-length
Open

henning-krause wants to merge 65 commits into
dotnet:mainfrom
henning-krause:104080-custom-rsapss-salt-length

Conversation

@henning-krause

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

Copy link
Copy Markdown

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 AI lite 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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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

Comment thread src/libraries/Common/src/System/Security/Cryptography/RSASecurityTransforms.cs Outdated
Comment thread src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs Outdated
henning-krause and others added 3 commits September 2, 2025 07:51
…tes/CertificateCreation/CertificateRequestChainTests.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
….Cryptography.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…ngProcessor.DigestInfo.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 38 out of 39 changed files in this pull request and generated 4 comments.

Comment on lines +409 to +414
#if NET11_0_OR_GREATER
if (SignaturePadding.PssSaltLength != RSASignaturePadding.PssSaltLengthIsHashLength)
{
signatureParameters = GetSignaturePaddingForCustomPssSaltLength(publicKey, hashAlgorithmName);
}
#endif
Comment on lines +115 to +123
#if NET11_0_OR_GREATER
if (signaturePadding.Mode == RSASignaturePaddingMode.Pss)
{
if (signaturePadding.PssSaltLength != RSASignaturePadding.PssSaltLengthIsHashLength)
{
throw new ArgumentException(SR.CoseSignerPssSaltLengthMustBeHashLength, nameof(signaturePadding));
}
}
#endif
Comment on lines +274 to +276
#if NET11_0_OR_GREATER
Debug.Assert(signaturePadding.PssSaltLength == RSASignaturePadding.PssSaltLengthIsHashLength);
#endif
Comment on lines +44 to +46
#if NET11_0_OR_GREATER
return RSASignaturePadding.CreatePss(SaltLength);
#else
Copilot AI review requested due to automatic review settings July 15, 2026 07:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 38 out of 39 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (2)

src/libraries/Common/src/System/Security/Cryptography/RsaPaddingProcessor.cs:300

  • The size check if (emLen < 2 + hLen + sLen) can overflow when sLen is a large user-supplied value (via CreatePss), which can bypass the guard and lead to large allocations/invalid slicing later. Use subtraction (or long/checked arithmetic) to make the guard overflow-safe.
            // 3.  if emLen < hLen + sLen + 2, encoding error.
            //
            // sLen = hLen in this implementation.

            if (emLen < 2 + hLen + sLen)

src/libraries/Common/src/System/Security/Cryptography/RsaPaddingProcessor.cs:393

  • This guard uses hLen + sLen + 2, which can overflow for large sLen values and incorrectly skip returning false. That can result in negative indexes later in VerifyPss. Make the check overflow-safe (e.g., via subtraction) before proceeding.
            // 3. If emLen < hLen + sLen + 2, output "inconsistent" and stop.
            if (emLen < hLen + sLen + 2)
            {
                return false;
            }

Comment on lines 849 to 852
{
Debug.Assert(padding == RSASignaturePadding.Pss);
// PSS salt length is validated in the RsaSignaturePaddingMode constructor.
Debug.Assert(padding.PssSaltLength >= RSASignaturePadding.PssSaltLengthMax);
}
Copilot AI review requested due to automatic review settings July 15, 2026 11:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 38 out of 39 changed files in this pull request and generated 1 comment.

Comment on lines +116 to +123
Numerics.BigInteger actualSaltLength = saltEntry.ReadInteger();
int expectedSaltLength = saltLengthToTest switch
{
RSASignaturePadding.PssSaltLengthIsHashLength => hashLength,
RSASignaturePadding.PssSaltLengthMax => maxSaltLength,
_ => saltLengthToTest,
};
Assert.Equal(expectedSaltLength, actualSaltLength);
Copilot AI review requested due to automatic review settings July 15, 2026 12:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 38 out of 39 changed files in this pull request and generated 3 comments.

Comment on lines +466 to +475
private static void ValidatePaddingParameters(HashAlgorithmName hashAlgorithm, RSASignaturePadding padding)
{
// Apple does not support custom salt length for the PSS padding
if (padding.Mode == RSASignaturePaddingMode.Pss &&
(padding.PssSaltLength != RSASignaturePadding.PssSaltLengthIsHashLength &&
padding.PssSaltLength != RsaPaddingProcessor.HashLength(hashAlgorithm)))
{
throw new CryptographicException(SR.Cryptography_CustomPssSaltLengthNotSupported);
}
}
Comment on lines +318 to +336
using (RSA rsa = RSA.Create())
{
var requestBuilder = new CertificateRequest("CN=Test", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.CreatePss(customSaltLength));
X509Certificate2 cert = requestBuilder.CreateSelfSigned(DateTime.Now, DateTime.Now.AddYears(1));

var reader = new AsnReader(cert.RawData, AsnEncodingRules.DER);
AsnReader sequence = reader.ReadSequence();
ReadOnlyMemory<byte> tbsCertificate = sequence.ReadEncodedValue();
ReadOnlyMemory<byte> signatureAlgorithm = sequence.ReadEncodedValue();
byte[] signature = sequence.ReadBitString(out _);

int testSaltLength = customSaltLength switch
{
RSASignaturePadding.PssSaltLengthMax => 222,
RSASignaturePadding.PssSaltLengthIsHashLength => 32,
_ => customSaltLength
};
Assert.True(rsa.VerifyData(tbsCertificate.Span, signature, HashAlgorithmName.SHA256, RSASignaturePadding.CreatePss(testSaltLength)));
}
Comment on lines 69 to +88
if (hashAlgorithm == HashAlgorithmName.SHA1)
return "300D06092A864886F70D01010A3000".HexToByteArray();

{
if (_signaturePadding.PssSaltLength == RSASignaturePadding.PssSaltLengthIsHashLength)
{
// sha1WithRSAEncryption with RSASSA-PSS parameters
return "300D06092A864886F70D01010A3000".HexToByteArray();
}
else if (_signaturePadding.PssSaltLength == 0)
{
return "303506092a864886f70d01010a3028a009300706052b0e03021aa116301406092a864886f70d010108300706052b0e03021aa203020100".HexToByteArray();
}
else if (_signaturePadding.PssSaltLength == 1)
{
return "303506092a864886f70d01010a3028a009300706052b0e03021aa116301406092a864886f70d010108300706052b0e03021aa203020101".HexToByteArray();
}
else if (_signaturePadding.PssSaltLength == RSASignaturePadding.PssSaltLengthMax)
{
// Salt length is 234
return "303606092a864886f70d01010a3029a009300706052b0e03021aa116301406092a864886f70d010108300706052b0e03021aa204020200ea".HexToByteArray();
}
@henning-krause

Copy link
Copy Markdown
Author

@bartonjs All comments should now be fixed... Can you review my last changes? Thanks :-)

Copilot AI review requested due to automatic review settings July 15, 2026 17:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 38 out of 39 changed files in this pull request and generated 2 comments.

Comment on lines +28 to +34
/// <summary>
/// Specifies the salt length to use for PSS padding. This property is only relevant when the <see cref="Mode"/> is <see cref="RSASignaturePaddingMode.Pss"/>.
/// </summary>
/// <remarks>
/// This value must either be a non-negative number or one of the special constants <see cref="PssSaltLengthIsHashLength"/> or <see cref="PssSaltLengthMax"/>.
/// </remarks>
public int PssSaltLength { get; }
Comment on lines 849 to +851
{
Debug.Assert(padding == RSASignaturePadding.Pss);
// PSS salt length is validated in the RsaSignaturePaddingMode constructor.
Debug.Assert(padding.PssSaltLength >= RSASignaturePadding.PssSaltLengthMax);
@github-actions

github-actions Bot commented Jul 19, 2026 •

Copy link
Copy Markdown
Contributor

Workflow state for the Holistic Review Orchestrator.

{
  "version": 5,
  "last_dispatched_commit": "d0d88a24fb7edde8e3b0826d606d91309e18ef35",
  "last_dispatched_base_ref": "main",
  "last_dispatched_base_sha": "17bbb207a46d1b2ae3741baa62cb88a98e2e9847",
  "last_reviewed_commit": "d0d88a24fb7edde8e3b0826d606d91309e18ef35",
  "last_reviewed_base_ref": "main",
  "last_reviewed_base_sha": "17bbb207a46d1b2ae3741baa62cb88a98e2e9847",
  "last_recorded_worker_run_id": "29684886999",
  "review_attempt_commit": "",
  "review_attempt_base_ref": "",
  "review_attempt_count": 0,
  "max_review_attempts": 5,
  "review_history_format": "holistic-review-disclosure-v1",
  "review_history": [
    {
      "commit": "d0d88a24fb7edde8e3b0826d606d91309e18ef35",
      "review_id": 4730678743
    }
  ]
}

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Holistic Review

Motivation: Justified. Issue #104080 is api-approved and documents a real interoperability gap: .NET hard-codes the PSS salt length to the hash length, so it cannot produce or validate PSS signatures that use a different salt length (e.g. RSA_PSS_SALTLEN_MAX or an explicit value), which other stacks and standards require.

Approach: Sound and consistent with the codebase. RSASignaturePadding.CreatePss(int) plus the PssSaltLengthIsHashLength/PssSaltLengthMax sentinels are stored on the immutable padding object; every backend (OpenSSL, CNG, BCrypt, Android, Apple, plus the managed RsaPaddingProcessor) resolves the sentinels through a single new RsaPaddingProcessor.CalculatePssSaltLength helper, and platforms that cannot honor a custom length (Apple, COSE) throw rather than silently ignore it. This directly follows the implementation checklist bartonjs posted on the approved issue (encode/verify, certificate PssParams, PssParamsAsn rehydration, CMS/COSE, and tests).

Summary: ⚠️ Needs Human Review. I found no blocking defects and verified the public surface matches the approved API exactly, but this is a large, security-sensitive cryptographic change spanning five platform backends plus CMS/COSE/X.509 encoding, and the area owner (bartonjs) has an in-flight review (previously CHANGES_REQUESTED). A crypto-domain maintainer should confirm the salt-length math and per-platform semantics before merge.


Detailed Findings

✅ API Approval — Matches approved shape

The new public surface in ref/System.Security.Cryptography.cs (const int PssSaltLengthIsHashLength = -1, const int PssSaltLengthMax = -2, int PssSaltLength { get; }, static RSASignaturePadding CreatePss(int saltLength)) is byte-for-byte identical to the shape approved by bartonjs (who applied the api-approved label on 2025-06-03) in the issue comment. Namespaces, type, member names, and signatures all match; no extra or missing public API.

✅ Cross-platform completeness — sentinels resolved centrally, unsupported paths throw

Sentinel values (-1/-2) are never leaked to native layers or the managed encoder: each caller resolves them via RsaPaddingProcessor.CalculatePssSaltLength(padding.PssSaltLength, KeySize, hashAlgorithm) before use (RSAOpenSsl, RSACng.SignVerify, RSABCrypt, RSAAndroid, RSAAppleCrypto). Backends that cannot honor a non-hLen salt reject it: Apple's ValidatePaddingParameters (called from both TrySignHash and VerifyHash) throws Cryptography_CustomPssSaltLengthNotSupported, and COSE's GetRSAAlgorithm throws because the spec mandates sLen==hLen. The EncodePss/VerifyPss emLen < 2 + hLen + sLen guard plus the PssSaltLengthTooLarge test protect against over-large explicit salts, and EncodePss moves the salt buffer to the heap when sLen > 128 to avoid an unbounded stackalloc.

✅ Test coverage — thorough and interop-anchored

Tests exercise explicit lengths, PssSaltLengthMax, and hLen equivalence, use externally-generated OpenSSL signatures as known-answer vectors, assert cross-verification between implicit Pss and explicit CreatePss(hLen), verify wrong salt lengths fail, and cover CMS/COSE/CertificateRequest. Coverage is gated by AreCustomSaltLengthsSupportedWithPss (excludes Apple), matching the platform capabilities.

💡 Stale comment in RsaPaddingProcessor.EncodePss

The comment // sLen = hLen in this implementation. (RsaPaddingProcessor.cs, in EncodePss near the emLen < 2 + hLen + sLen check) is now inaccurate since sLen comes from the caller-supplied saltLength. It sits on an unchanged line so I did not attach an inline comment; consider removing it to avoid confusion. Non-blocking.

Note

This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.

Generated by Holistic Review · 275.2 AIC · ⌖ 10.7 AIC · ⊞ 10K

@henning-krause

Copy link
Copy Markdown
Author

@bartonjs Can you take a look at my last changes?

Copilot AI review requested due to automatic review settings September 14, 2026 05:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Four moderate correctness and resource-handling issues remain unresolved.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (4)

src/libraries/Common/src/System/Security/Cryptography/Asn1/PssParamsAsn.manual.cs:54

  • SaltLength is read from untrusted ASN.1, but this branch passes every non-default value directly to CreatePss. That accepts the internal -1/-2 sentinels, so a CMS or PKCS#10 PSS parameter with a negative salt-length INTEGER is interpreted as hash-length/max instead of rejected, even though the ASN.1 field is an actual non-negative salt length. Reject negative SaltLength before constructing the padding.
                RSASignaturePadding.CreatePss(SaltLength);

src/libraries/Common/src/System/Security/Cryptography/RSAAndroid.cs:686

  • EncodePss now rejects custom salt lengths, but this call is outside a try/finally. On Android, a too-large (or otherwise invalid) salt length throws after encodedRented is acquired, so the pool buffer is not returned; repeated invalid signing attempts can steadily increase allocation pressure. Keep the encoding and primitive call inside a try/finally that returns encodedRented.
                    RsaPaddingProcessor.EncodePss(hashAlgorithm, hash, encodedBytes, KeySize, RsaPaddingProcessor.CalculatePssSaltLength(padding.PssSaltLength, KeySize, hashAlgorithm));

src/libraries/Common/src/System/Security/Cryptography/RsaPaddingProcessor.cs:390

  • The same int overflow lets an oversized salt length pass this verification guard. For example, with sLen == int.MaxValue, the sum becomes negative and the method later indexes dbMask with a negative computed position, throwing instead of returning false for an invalid signature. Use a non-overflowing comparison such as sLen > emLen - hLen - 2.
            if (emLen < hLen + sLen + 2)

src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.RSA.cs:459

  • For SHA-1, the shared PSS ASN.1 definition uses AlgorithmIdentifier values whose parameters are an explicit NULL (PssParamsAsn.xml:22-27). This new custom-salt path constructs both the hash and MGF1 hash identifiers without parameters, so a non-default salt causes explicit [0]/[1] fields with absent SHA-1 parameters; RFC 4055 consumers can reject that encoding. Populate the SHA-1 NULL parameters, or omit the default hash and MGF fields when they are SHA-1.
                    HashAlgorithm = new ValueAlgorithmIdentifierAsn { Algorithm = digestOid },
  • Files reviewed: 38/39 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment on lines 297 to 298
//
// sLen = hLen in this implementation.

This branch has not been deployed

No deployments
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

5 participants