104080 custom rsapss salt length#119255
Conversation
…ation/generation with RSA keys. dotnet#104080
…lt customizations
…pported there) Updated reference code
Cose does not support custom salt lengths with PSS.
There was a problem hiding this comment.
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
RSASignaturePaddingto 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
CmsSignerandCertificateRequestto 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 |
…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>
|
|
||
| // 3. If emLen < hLen + sLen + 2, output "inconsistent" and stop. | ||
| if (emLen < hLen + sLen + 2) | ||
| if (emLen < hLen + saltLength + 2) |
There was a problem hiding this comment.
Everything else in this function is named what it is in the spec. I'd prefer if you reintroduced the sLen variable a few lines up, but instead of int sLen = hLen; it would be int sLen = saltLength; (so the parameter has a good name for callers, but the function uses the name from the spec)
And, of course, after introducing sLen, undo all of the sLen => saltLength changes in this function.
There was a problem hiding this comment.
It looks like you undid the sLen to saltLength rename in EncodePss, but not in VerifyPss.
| 0x11, 0xf2, 0x10, 0xbd, 0x19, 0x47, 0x75, 0x3c, 0x30, 0xc4, 0x0f, 0xd0, 0x7b, 0xc2, 0x6b, 0x4b | ||
| }; | ||
|
|
||
| RSASignaturePadding padding = RSASignaturePadding.CreatePss(validateWithCorrectSaltLength ? RSASignaturePadding.PssSaltLengthMax : 2); |
There was a problem hiding this comment.
Since max ends up being a specific number when paired with the key, there should be a test that signs with max and verifies with the number, and vice versa.
Probably not this one, since it's not visibly calling sign.
| private byte[] GetSignaturePaddingForCustomPssSaltLength(X509Certificate2 certificate, HashAlgorithmName hashAlgorithmName) | ||
| { | ||
| string digestOid = PkcsHelpers.GetOidFromHashAlgorithm(hashAlgorithmName); | ||
| using RSA? publicKey = certificate.GetRSAPublicKey(); |
There was a problem hiding this comment.
In cryptography code, other than test code, we (almost?) always use braced using-Dispose statements so it's much clearer when objects are freed.
But, rather than re-opening a key handle here, could we maybe just save the key size in the call to SignCore?
There was a problem hiding this comment.
@bartonjs So SignCore would return the signatureParameters via an out variable?
| "07849DC26FCBB2F3BD5F57BDF214BAE374575F1BD4E6816482324799417CB379", | ||
| messageDigestAttr.MessageDigest.ByteArrayToHex()); | ||
|
|
||
|
|
There was a problem hiding this comment.
This looks like it turned a blank line into a "non-blank blank line" (only has spaces). That should be trimmed down to a non-diff.
| { | ||
| using (RSA rsa = RSA.Create()) | ||
| { | ||
| rsa.ImportParameters(TestData.RsaBigExponentParams); |
There was a problem hiding this comment.
@vcsjones when I saw this line I immediately thought "but didn't we (you) move us off of this key?", but it looks like no...
Am I entirely hallucinating, or what?
| pssStructure.ReadEncodedValue(); // Ignore the hash algorithm OID | ||
| pssStructure.ReadEncodedValue(); // Ignore the mask generation function OID |
There was a problem hiding this comment.
The pre-existing test that verifies these values doesn't take a custom salt size. We should verify them here to show that we didn't break that codepath with custom salt sizes.
While we're at it, we should also add hash algorithm variance.
(We can verify the algorithm/MGF OIDs in a different test than the custom salt size, but we do want to verify both pieces of data in some test)
…/henning-krause/runtime into 104080-custom-rsapss-salt-length
|
|
||
| // 3. If emLen < hLen + sLen + 2, output "inconsistent" and stop. | ||
| if (emLen < hLen + sLen + 2) | ||
| if (emLen < hLen + saltLength + 2) |
There was a problem hiding this comment.
It looks like you undid the sLen to saltLength rename in EncodePss, but not in VerifyPss.
| pssStructure.ReadEncodedValue(); // Ignore the hash algorithm OID | ||
| pssStructure.ReadEncodedValue(); // Ignore the mask generation function OID | ||
| ReadOnlyMemory<byte> hashAlgorithm = pssStructure.ReadEncodedValue(); // Ignore the hash algorithm OID | ||
| ReadOnlyMemory<byte> mgf = pssStructure.ReadEncodedValue(); // Ignore the mask generation function OID |
There was a problem hiding this comment.
The previous feedback was that the hashAlgorithm and mgf values needed to be verified. You have now captured them into locals, but I don't see you verifying that they're the expected values.
Again, the purpose is to make sure that we're writing down a confirming/correct block when a custom PSS size is specified, and I don't see any other test doing that.
There was a problem hiding this comment.
I was interrupted last time I worked on this, so I committed early. I'll fix this next time together with the remaining open issues.
| using System.Formats.Asn1; | ||
| using System.Security.Cryptography.Asn1; |
There was a problem hiding this comment.
These using directives appear to be unused in this file. Consider removing them to avoid IDE0005 / analyzer warnings (which may be treated as errors in some build configurations).
| /// A constant value indicating that the PSS salt length should be the maximum allowable | ||
| /// </summary> | ||
| /// <remarks>A constant is used to define the upper limit for the salt length in PSS-based | ||
| /// cryptographic operations. The maximum length is determined by the hash algorithm's output size.</remarks> |
There was a problem hiding this comment.
The remarks for PssSaltLengthMax say the maximum salt length is determined by the hash algorithm output size, but it also depends on the RSA key size (max is emLen - hLen - 2). Consider updating the docs to reflect the key-size dependency.
| /// A constant value indicating that the PSS salt length should be the maximum allowable | |
| /// </summary> | |
| /// <remarks>A constant is used to define the upper limit for the salt length in PSS-based | |
| /// cryptographic operations. The maximum length is determined by the hash algorithm's output size.</remarks> | |
| /// A constant value indicating that the PSS salt length should be the maximum allowable for the given RSA key and hash algorithm. | |
| /// </summary> | |
| /// <remarks> | |
| /// When this value is used, the effective salt length is chosen by the implementation so that it does not exceed | |
| /// the maximum permitted by RSASSA-PSS, which depends on both the RSA key size and the hash algorithm's output | |
| /// size (at most <c>emLen - hLen - 2</c> bytes, where <c>emLen</c> is the encoded message length in bytes and | |
| /// <c>hLen</c> is the hash output length in bytes). | |
| /// </remarks> |
| /// 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 positive number or one of the special constants <see cref="PssSaltLengthIsHashLength"/> or <see cref="PssSaltLengthMax"/>. |
There was a problem hiding this comment.
The PssSaltLength remarks say the value must be a "positive" number, but CreatePss allows 0 (and tests cover it). Consider updating the wording to "non-negative" to match the API behavior.
| /// This value must either be a positive number or one of the special constants <see cref="PssSaltLengthIsHashLength"/> or <see cref="PssSaltLengthMax"/>. | |
| /// This value must either be a non-negative number or one of the special constants <see cref="PssSaltLengthIsHashLength"/> or <see cref="PssSaltLengthMax"/>. |
| if (certKind == CertKind.RsaPkcs1 || certKind == CertKind.RsaPss ||certKind == CertKind.RsaPssWithCustomSaltLength || certKind == CertKind.RsaPssWithMaxSaltLength) | ||
| { |
There was a problem hiding this comment.
Minor formatting: missing space after "||" makes the condition harder to read and is inconsistent with typical formatting in this file.
| { | ||
| public static partial class SignedCmsTests | ||
| { | ||
| private static bool AreCustomPssSaltLengthsSupported => PlatformDetection.IsWindows || PlatformDetection.IsLinux; |
There was a problem hiding this comment.
AreCustomPssSaltLengthsSupported is hard-coded to Windows/Linux, but custom PSS salt lengths are supported on other OpenSSL-based Unix platforms too. Consider reusing PlatformSupport.AreCustomSaltLengthsSupportedWithPss here to avoid unnecessarily skipping coverage on supported platforms (e.g., FreeBSD).
| private static bool AreCustomPssSaltLengthsSupported => PlatformDetection.IsWindows || PlatformDetection.IsLinux; | |
| private static bool AreCustomPssSaltLengthsSupported => PlatformSupport.AreCustomSaltLengthsSupportedWithPss; |
| // PSS signature with parameters are only supported on .NET 11 or later | ||
| #if NET11_0_OR_GREATER | ||
| if (PlatformSupport.AreCustomSaltLengthsSupportedWithPss) | ||
| { | ||
| signer.CheckSignature(true); | ||
| } | ||
| else | ||
| { | ||
| Assert.Throws<CryptographicException>(() => signer.CheckSignature(true)); | ||
| } | ||
| #else | ||
| Assert.Throws<CryptographicException>(() => signer.CheckSignature(true)); | ||
| #endif |
There was a problem hiding this comment.
The comments/guards are inconsistent: this block says PSS parameters are only supported on .NET 11+, but the later cms.CheckSignature block mentions .NET 10+ without an equivalent compile-time guard. Consider aligning the comments (and/or the #if) so the minimum supported version is unambiguous.
| internal static int CalculatePssSaltLength(int pssSaltLength, int rsaKeySizeInBits, HashAlgorithmName hashAlgorithm) | ||
| { | ||
| int emLen = BytesRequiredForBitCount(rsaKeySizeInBits); | ||
| int hLen = HashLength(hashAlgorithm); | ||
| return pssSaltLength switch | ||
| { | ||
| PssSaltLengthMax => Math.Max(0, emLen - hLen - 2), | ||
| PssSaltLengthIsHashLength => hLen, | ||
| _ => pssSaltLength |
There was a problem hiding this comment.
CalculatePssSaltLength computes emLen from rsaKeySizeInBits, but PSS encoding/verification in this codebase uses emBits = keySize - 1. For RSA keys whose bit-length is not a multiple of 8, this will overestimate the maximum salt length by 1 byte and can cause CreatePss(PssSaltLengthMax) to fail with key-too-small for otherwise-valid keys. Consider basing emLen on (rsaKeySizeInBits - 1) to align with EncodePss/VerifyPss.
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.