diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Rsa.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Rsa.cs index 8f56e1fa62f67c..40558f63923f2f 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Rsa.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Rsa.cs @@ -124,6 +124,7 @@ private static partial int CryptoNative_RsaSignHash( SafeEvpPKeyHandle pkey, IntPtr extraHandle, RSASignaturePaddingMode paddingMode, + int pssSaltLength, IntPtr digestAlgorithm, ref byte hash, int hashLength, @@ -133,6 +134,7 @@ private static partial int CryptoNative_RsaSignHash( internal static int RsaSignHash( SafeEvpPKeyHandle pkey, RSASignaturePaddingMode paddingMode, + int pssSaltLength, HashAlgorithmName digestAlgorithm, ReadOnlySpan hash, Span destination) @@ -145,6 +147,7 @@ internal static int RsaSignHash( pkey, pkey.ExtraHandle, paddingMode, + pssSaltLength, digestAlgorithmPtr, ref MemoryMarshal.GetReference(hash), hash.Length, @@ -165,6 +168,7 @@ private static partial int CryptoNative_RsaVerifyHash( SafeEvpPKeyHandle pkey, IntPtr extraHandle, RSASignaturePaddingMode paddingMode, + int pssSaltLength, IntPtr digestAlgorithm, ref byte hash, int hashLength, @@ -174,6 +178,7 @@ private static partial int CryptoNative_RsaVerifyHash( internal static bool RsaVerifyHash( SafeEvpPKeyHandle pkey, RSASignaturePaddingMode paddingMode, + int pssSaltLength, HashAlgorithmName digestAlgorithm, ReadOnlySpan hash, ReadOnlySpan signature) @@ -186,6 +191,7 @@ internal static bool RsaVerifyHash( pkey, pkey.ExtraHandle, paddingMode, + pssSaltLength, digestAlgorithmPtr, ref MemoryMarshal.GetReference(hash), hash.Length, diff --git a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptSignHash.cs b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptSignHash.cs index 376a133244b17a..2c33b59dbc1c28 100644 --- a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptSignHash.cs +++ b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptSignHash.cs @@ -54,6 +54,7 @@ internal static unsafe NTSTATUS BCryptSignHashPss( ReadOnlySpan hash, Span destination, string hashAlgorithmName, + int saltLength, out int bytesWritten) { fixed (char* pHashAlgorithmName = hashAlgorithmName) @@ -62,7 +63,7 @@ internal static unsafe NTSTATUS BCryptSignHashPss( { BCRYPT_PSS_PADDING_INFO paddingInfo = default; paddingInfo.pszAlgId = (IntPtr)pHashAlgorithmName; - paddingInfo.cbSalt = hash.Length; + paddingInfo.cbSalt = saltLength; return BCryptSignHash( key, diff --git a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptVerifySignature.cs b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptVerifySignature.cs index da39ca0dcdd4a2..852660e6dde604 100644 --- a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptVerifySignature.cs +++ b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptVerifySignature.cs @@ -61,7 +61,8 @@ internal static unsafe bool BCryptVerifySignaturePss( SafeBCryptKeyHandle key, ReadOnlySpan hash, ReadOnlySpan signature, - string hashAlgorithmName) + string hashAlgorithmName, + int saltLength) { NTSTATUS status; @@ -72,7 +73,7 @@ internal static unsafe bool BCryptVerifySignaturePss( { BCRYPT_PSS_PADDING_INFO paddingInfo = default; paddingInfo.pszAlgId = (IntPtr)pHashAlgorithmName; - paddingInfo.cbSalt = hash.Length; + paddingInfo.cbSalt = saltLength; status = BCryptVerifySignature( key, diff --git a/src/libraries/Common/src/System/Security/Cryptography/Asn1/PssParamsAsn.manual.cs b/src/libraries/Common/src/System/Security/Cryptography/Asn1/PssParamsAsn.manual.cs index b80004f5efa50c..5014c5896cb63c 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/Asn1/PssParamsAsn.manual.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/Asn1/PssParamsAsn.manual.cs @@ -41,6 +41,9 @@ internal RSASignaturePadding GetSignaturePadding( HashAlgorithm.Algorithm)); } +#if NET10_0_OR_GREATER + return RSASignaturePadding.CreatePss(SaltLength); +#else int saltSize = digestValueLength.GetValueOrDefault(); if (!digestValueLength.HasValue) @@ -59,6 +62,7 @@ internal RSASignaturePadding GetSignaturePadding( // When RSASignaturePadding supports custom salt sizes this return will look different. return RSASignaturePadding.Pss; +#endif } } } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashAlgorithmNames.cs b/src/libraries/Common/src/System/Security/Cryptography/HashAlgorithmNames.cs similarity index 100% rename from src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashAlgorithmNames.cs rename to src/libraries/Common/src/System/Security/Cryptography/HashAlgorithmNames.cs diff --git a/src/libraries/Common/src/System/Security/Cryptography/RSAAndroid.cs b/src/libraries/Common/src/System/Security/Cryptography/RSAAndroid.cs index 1d1c742609d93f..d97cef8a38c3e7 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/RSAAndroid.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/RSAAndroid.cs @@ -657,7 +657,7 @@ private bool TrySignHash( Debug.Assert(padding != null); signature = null; - if (padding != RSASignaturePadding.Pkcs1 && padding != RSASignaturePadding.Pss) + if (padding != RSASignaturePadding.Pkcs1 && padding.Mode != RSASignaturePaddingMode.Pss) { throw PaddingModeNotSupported(); } @@ -688,7 +688,7 @@ private bool TrySignHash( } else if (padding.Mode == RSASignaturePaddingMode.Pss) { - RsaPaddingProcessor.EncodePss(hashAlgorithm, hash, encodedBytes, KeySize); + RsaPaddingProcessor.EncodePss(hashAlgorithm, hash, encodedBytes, KeySize, RsaPaddingProcessor.CalculatePssSaltLength(padding.PssSaltLength, KeySize, hashAlgorithm)); } else { @@ -726,7 +726,7 @@ public override bool VerifyHash(ReadOnlySpan hash, ReadOnlySpan sign { ArgumentException.ThrowIfNullOrEmpty(hashAlgorithm.Name, nameof(hashAlgorithm)); ArgumentNullException.ThrowIfNull(padding); - if (padding != RSASignaturePadding.Pkcs1 && padding != RSASignaturePadding.Pss) + if (padding != RSASignaturePadding.Pkcs1 && padding.Mode != RSASignaturePaddingMode.Pss) { throw PaddingModeNotSupported(); } @@ -772,9 +772,10 @@ public override bool VerifyHash(ReadOnlySpan hash, ReadOnlySpan sign CryptoPool.Return(repadRent, requiredBytes); return valid; } - else if (padding == RSASignaturePadding.Pss) + else if (padding.Mode == RSASignaturePaddingMode.Pss) { - return RsaPaddingProcessor.VerifyPss(hashAlgorithm, hash, unwrapped, KeySize); + int saltLength = RsaPaddingProcessor.CalculatePssSaltLength(padding.PssSaltLength, KeySize, hashAlgorithm); + return RsaPaddingProcessor.VerifyPss(hashAlgorithm, hash, unwrapped, KeySize, saltLength); } else { diff --git a/src/libraries/Common/src/System/Security/Cryptography/RSAAppleCrypto.cs b/src/libraries/Common/src/System/Security/Cryptography/RSAAppleCrypto.cs index 4842ab98bcce17..b012e3fb22818b 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/RSAAppleCrypto.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/RSAAppleCrypto.cs @@ -397,6 +397,14 @@ public override bool TrySignHash(ReadOnlySpan hash, Span destination ArgumentException.ThrowIfNullOrEmpty(hashAlgorithm.Name, nameof(hashAlgorithm)); ArgumentNullException.ThrowIfNull(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); + } + ThrowIfDisposed(); Interop.AppleCrypto.PAL_SignatureAlgorithm signatureAlgorithm = padding.Mode switch @@ -454,7 +462,7 @@ public override bool TrySignHash(ReadOnlySpan hash, Span destination byte[] rented = CryptoPool.Rent(rsaSize); Span buf = new Span(rented, 0, rsaSize); - RsaPaddingProcessor.EncodePss(hashAlgorithm, hash, buf, keySize); + RsaPaddingProcessor.EncodePss(hashAlgorithm, hash, buf, keySize, hash.Length); try { @@ -537,9 +545,9 @@ public override bool VerifyHash(ReadOnlySpan hash, ReadOnlySpan sign Debug.Fail($"TryRsaVerificationPrimitive with a pre-allocated buffer"); throw new CryptographicException(); } - Debug.Assert(bytesWritten == rsaSize); - return RsaPaddingProcessor.VerifyPss(hashAlgorithm, hash, unwrapped, keySize); + int saltLength = RsaPaddingProcessor.CalculatePssSaltLength(padding.PssSaltLength, KeySize, hashAlgorithm); + return RsaPaddingProcessor.VerifyPss(hashAlgorithm, hash, unwrapped, keySize, saltLength); } finally { diff --git a/src/libraries/Common/src/System/Security/Cryptography/RSACng.SignVerify.cs b/src/libraries/Common/src/System/Security/Cryptography/RSACng.SignVerify.cs index 8e1b7910d3ca4c..bcdcf1224c7c88 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/RSACng.SignVerify.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/RSACng.SignVerify.cs @@ -66,7 +66,8 @@ public override byte[] SignHash(byte[] hash, HashAlgorithmName hashAlgorithm, RS return keyHandle.SignHash(hash, AsymmetricPaddingMode.NCRYPT_PAD_PKCS1_FLAG, &pkcsPaddingInfo, estimatedSize); case RSASignaturePaddingMode.Pss: - var pssPaddingInfo = new BCRYPT_PSS_PADDING_INFO() { pszAlgId = namePtr, cbSalt = hash.Length }; + int saltLength = RsaPaddingProcessor.CalculatePssSaltLength(padding.PssSaltLength, KeySize, hashAlgorithm); + var pssPaddingInfo = new BCRYPT_PSS_PADDING_INFO() { pszAlgId = namePtr, cbSalt = saltLength }; return keyHandle.SignHash(hash, AsymmetricPaddingMode.NCRYPT_PAD_PSS_FLAG, &pssPaddingInfo, estimatedSize); default: @@ -104,7 +105,11 @@ public override unsafe bool TrySignHash(ReadOnlySpan hash, Span dest return keyHandle.TrySignHash(hash, destination, AsymmetricPaddingMode.NCRYPT_PAD_PKCS1_FLAG, &pkcs1PaddingInfo, out bytesWritten); case RSASignaturePaddingMode.Pss: - var pssPaddingInfo = new BCRYPT_PSS_PADDING_INFO() { pszAlgId = namePtr, cbSalt = hash.Length }; + var pssPaddingInfo = new BCRYPT_PSS_PADDING_INFO() + { + pszAlgId = namePtr, + cbSalt = RsaPaddingProcessor.CalculatePssSaltLength(padding.PssSaltLength, KeySize, hashAlgorithm) + }; return keyHandle.TrySignHash(hash, destination, AsymmetricPaddingMode.NCRYPT_PAD_PSS_FLAG, &pssPaddingInfo, out bytesWritten); default: @@ -152,7 +157,11 @@ public override unsafe bool VerifyHash(ReadOnlySpan hash, ReadOnlySpan hash, ReadOnlySpan sign SafeEvpPKeyHandle key = GetKey(); + int pssSaltLength = padding.Mode == RSASignaturePaddingMode.Pss + ? RsaPaddingProcessor.CalculatePssSaltLength(padding.PssSaltLength, KeySize, hashAlgorithm) + : 0; return Interop.Crypto.RsaVerifyHash( key, padding.Mode, + pssSaltLength, hashAlgorithm, hash, signature); @@ -853,20 +863,15 @@ private static void ValidatePadding(RSASignaturePadding padding) { ArgumentNullException.ThrowIfNull(padding); - // RSASignaturePadding currently only has the mode property, so - // there's no need for a runtime check that PKCS#1 doesn't use - // nonsensical options like with RSAEncryptionPadding. - // - // This would change if we supported PSS with an MGF other than MGF-1, - // or with a custom salt size, or with a different MGF digest algorithm - // than the data digest algorithm. + // PKCS#1 does not currently have anything to validate. if (padding.Mode == RSASignaturePaddingMode.Pkcs1) { Debug.Assert(padding == RSASignaturePadding.Pkcs1); } else if (padding.Mode == RSASignaturePaddingMode.Pss) { - Debug.Assert(padding == RSASignaturePadding.Pss); + // PSS salt length is validated in the RsaSignaturePaddingMode constructor. + Debug.Assert(padding.PssSaltLength < RSASignaturePadding.PssSaltLengthMax); } else { diff --git a/src/libraries/Common/src/System/Security/Cryptography/RsaPaddingProcessor.DigestInfo.cs b/src/libraries/Common/src/System/Security/Cryptography/RsaPaddingProcessor.DigestInfo.cs new file mode 100644 index 00000000000000..34a0b6877b1fc9 --- /dev/null +++ b/src/libraries/Common/src/System/Security/Cryptography/RsaPaddingProcessor.DigestInfo.cs @@ -0,0 +1,143 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; + +namespace System.Security.Cryptography +{ + internal static partial class RsaPaddingProcessor + { + // DigestInfo header values taken from https://tools.ietf.org/html/rfc3447#section-9.2, Note 1. + private static ReadOnlySpan DigestInfoMD5 => + [ + 0x30, 0x20, 0x30, 0x0C, 0x06, 0x08, 0x2A, 0x86, + 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05, 0x05, 0x00, + 0x04, 0x10, + ]; + + private static ReadOnlySpan DigestInfoSha1 => + [ + 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B, 0x0E, 0x03, + 0x02, 0x1A, 0x05, 0x00, 0x04, 0x14, + ]; + + private static ReadOnlySpan DigestInfoSha256 => + [ + 0x30, 0x31, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86, 0x48, + 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, + 0x20, + ]; + + private static ReadOnlySpan DigestInfoSha384 => + [ + 0x30, 0x41, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86, 0x48, + 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, + 0x30, + ]; + + private static ReadOnlySpan DigestInfoSha512 => + [ + 0x30, 0x51, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86, 0x48, + 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, + 0x40, + ]; + + private static ReadOnlySpan DigestInfoSha3_256 => + [ + 0x30, 0x31, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86, 0x48, + 0x01, 0x65, 0x03, 0x04, 0x02, 0x08, 0x05, 0x00, 0x04, + 0x20, + ]; + + private static ReadOnlySpan DigestInfoSha3_384 => + [ + 0x30, 0x41, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86, 0x48, + 0x01, 0x65, 0x03, 0x04, 0x02, 0x09, 0x05, 0x00, 0x04, + 0x30, + ]; + + private static ReadOnlySpan DigestInfoSha3_512 => + [ + 0x30, 0x51, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86, 0x48, + 0x01, 0x65, 0x03, 0x04, 0x02, 0x0A, 0x05, 0x00, 0x04, + 0x40, + ]; + + + /// + /// Represents a constant value indicating that the salt length should match the hash length. + /// + /// This value is typically used in cryptographic operations where the salt length is required to + /// be the same as the hash length. + internal const int PssSaltLengthIsHashLength = -1; + + /// + /// Represents the maximum allowable length, in bytes, for a PSS (Probabilistic Signature Scheme) salt. + /// + /// This 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. + internal const int PssSaltLengthMax = -2; + + /// + /// Calculates the length of the salt for PSS signatures based on the RSA key size and hash algorithm. + /// + /// The salt length used for the padding. + /// The key size of the RSA key used. + /// The hash algorithm used. + /// + internal static int CalculatePssSaltLength(int pssSaltLength, int rsaKeySizeInBits, HashAlgorithmName hashAlgorithm) + { + int emLen = (rsaKeySizeInBits + 7) >>> 3; + int hLen = HashLength(hashAlgorithm); + return pssSaltLength switch + { + PssSaltLengthMax => Math.Max(0, emLen - hLen - 2), + PssSaltLengthIsHashLength => hLen, + _ => pssSaltLength + }; + } + + private static ReadOnlySpan GetDigestInfoForAlgorithm( + HashAlgorithmName hashAlgorithmName, + out int digestLengthInBytes) + { + switch (hashAlgorithmName.Name) + { + case HashAlgorithmNames.MD5: + digestLengthInBytes = MD5.HashSizeInBytes; + return DigestInfoMD5; + case HashAlgorithmNames.SHA1: + digestLengthInBytes = SHA1.HashSizeInBytes; + return DigestInfoSha1; + case HashAlgorithmNames.SHA256: + digestLengthInBytes = SHA256.HashSizeInBytes; + return DigestInfoSha256; + case HashAlgorithmNames.SHA384: + digestLengthInBytes = SHA384.HashSizeInBytes; + return DigestInfoSha384; + case HashAlgorithmNames.SHA512: + digestLengthInBytes = SHA512.HashSizeInBytes; + return DigestInfoSha512; + case HashAlgorithmNames.SHA3_256: + digestLengthInBytes = SHA3_256.HashSizeInBytes; + return DigestInfoSha3_256; + case HashAlgorithmNames.SHA3_384: + digestLengthInBytes = SHA3_384.HashSizeInBytes; + return DigestInfoSha3_384; + case HashAlgorithmNames.SHA3_512: + digestLengthInBytes = SHA3_512.HashSizeInBytes; + return DigestInfoSha3_512; + default: + Debug.Fail("Unknown digest algorithm"); + throw new CryptographicException(); + } + } + + internal static int HashLength(HashAlgorithmName hashAlgorithmName) + { + GetDigestInfoForAlgorithm(hashAlgorithmName, out int hLen); + return hLen; + } + + } +} diff --git a/src/libraries/Common/src/System/Security/Cryptography/RsaPaddingProcessor.cs b/src/libraries/Common/src/System/Security/Cryptography/RsaPaddingProcessor.cs index d4d493f3daca54..4a10fd97a431c1 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/RsaPaddingProcessor.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/RsaPaddingProcessor.cs @@ -8,113 +8,15 @@ namespace System.Security.Cryptography { - internal static class RsaPaddingProcessor + internal static partial class RsaPaddingProcessor { - // DigestInfo header values taken from https://tools.ietf.org/html/rfc3447#section-9.2, Note 1. - private static ReadOnlySpan DigestInfoMD5 => - [ - 0x30, 0x20, 0x30, 0x0C, 0x06, 0x08, 0x2A, 0x86, - 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05, 0x05, 0x00, - 0x04, 0x10, - ]; - - private static ReadOnlySpan DigestInfoSha1 => - [ - 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B, 0x0E, 0x03, - 0x02, 0x1A, 0x05, 0x00, 0x04, 0x14, - ]; - - private static ReadOnlySpan DigestInfoSha256 => - [ - 0x30, 0x31, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86, 0x48, - 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, - 0x20, - ]; - - private static ReadOnlySpan DigestInfoSha384 => - [ - 0x30, 0x41, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86, 0x48, - 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, - 0x30, - ]; - - private static ReadOnlySpan DigestInfoSha512 => - [ - 0x30, 0x51, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86, 0x48, - 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, - 0x40, - ]; - - private static ReadOnlySpan DigestInfoSha3_256 => - [ - 0x30, 0x31, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86, 0x48, - 0x01, 0x65, 0x03, 0x04, 0x02, 0x08, 0x05, 0x00, 0x04, - 0x20, - ]; - - private static ReadOnlySpan DigestInfoSha3_384 => - [ - 0x30, 0x41, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86, 0x48, - 0x01, 0x65, 0x03, 0x04, 0x02, 0x09, 0x05, 0x00, 0x04, - 0x30, - ]; - - private static ReadOnlySpan DigestInfoSha3_512 => - [ - 0x30, 0x51, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86, 0x48, - 0x01, 0x65, 0x03, 0x04, 0x02, 0x0A, 0x05, 0x00, 0x04, - 0x40, - ]; - private static ReadOnlySpan EightZeros => [0, 0, 0, 0, 0, 0, 0, 0]; - private static ReadOnlySpan GetDigestInfoForAlgorithm( - HashAlgorithmName hashAlgorithmName, - out int digestLengthInBytes) - { - switch (hashAlgorithmName.Name) - { - case HashAlgorithmNames.MD5: - digestLengthInBytes = MD5.HashSizeInBytes; - return DigestInfoMD5; - case HashAlgorithmNames.SHA1: - digestLengthInBytes = SHA1.HashSizeInBytes; - return DigestInfoSha1; - case HashAlgorithmNames.SHA256: - digestLengthInBytes = SHA256.HashSizeInBytes; - return DigestInfoSha256; - case HashAlgorithmNames.SHA384: - digestLengthInBytes = SHA384.HashSizeInBytes; - return DigestInfoSha384; - case HashAlgorithmNames.SHA512: - digestLengthInBytes = SHA512.HashSizeInBytes; - return DigestInfoSha512; - case HashAlgorithmNames.SHA3_256: - digestLengthInBytes = SHA3_256.HashSizeInBytes; - return DigestInfoSha3_256; - case HashAlgorithmNames.SHA3_384: - digestLengthInBytes = SHA3_384.HashSizeInBytes; - return DigestInfoSha3_384; - case HashAlgorithmNames.SHA3_512: - digestLengthInBytes = SHA3_512.HashSizeInBytes; - return DigestInfoSha3_512; - default: - Debug.Fail("Unknown digest algorithm"); - throw new CryptographicException(); - } - } - internal static int BytesRequiredForBitCount(int keySizeInBits) { return (int)(((uint)keySizeInBits + 7) / 8); } - internal static int HashLength(HashAlgorithmName hashAlgorithmName) - { - GetDigestInfoForAlgorithm(hashAlgorithmName, out int hLen); - return hLen; - } - internal static void PadPkcs1Encryption( ReadOnlySpan source, Span destination) @@ -380,8 +282,10 @@ internal static void PadOaep( } } - internal static void EncodePss(HashAlgorithmName hashAlgorithmName, ReadOnlySpan mHash, Span destination, int keySize) + internal static void EncodePss(HashAlgorithmName hashAlgorithmName, ReadOnlySpan mHash, Span destination, int keySize, int saltLength) { + const int MaxStackSaltLength = 128; + int hLen = HashLength(hashAlgorithmName); // https://tools.ietf.org/html/rfc3447#section-9.1.1 @@ -393,14 +297,11 @@ internal static void EncodePss(HashAlgorithmName hashAlgorithmName, ReadOnlySpan throw new CryptographicException(SR.Cryptography_SignHash_WrongSize); } - // In this implementation, sLen is restricted to the length of the input hash. - int sLen = hLen; - // 3. if emLen < hLen + sLen + 2, encoding error. // // sLen = hLen in this implementation. - if (emLen < 2 + hLen + sLen) + if (emLen < 2 + hLen + saltLength) { throw new CryptographicException(SR.Cryptography_KeyTooSmall); } @@ -426,7 +327,10 @@ internal static void EncodePss(HashAlgorithmName hashAlgorithmName, ReadOnlySpan Debug.Assert(hasher.HashLengthInBytes == hLen); // 4. Generate a random salt of length sLen Debug.Assert(hLen is >= 0 and <= 64); - Span salt = stackalloc byte[sLen]; + + Span salt = saltLength > MaxStackSaltLength + ? new byte[saltLength] + : stackalloc byte[saltLength]; RandomNumberGenerator.Fill(salt); // 5. Let M' = an octet string of 8 zeros concat mHash concat salt @@ -444,7 +348,7 @@ internal static void EncodePss(HashAlgorithmName hashAlgorithmName, ReadOnlySpan // 7. Generate PS as zero-valued bytes of length emLen - sLen - hLen - 2. // 8. Let DB = PS || 0x01 || salt - int psLen = emLen - sLen - hLen - 2; + int psLen = emLen - saltLength - hLen - 2; db.Slice(0, psLen).Clear(); db[psLen] = 0x01; salt.CopyTo(db.Slice(psLen + 1)); @@ -469,7 +373,7 @@ internal static void EncodePss(HashAlgorithmName hashAlgorithmName, ReadOnlySpan CryptoPool.Return(dbMaskRented, clearSize: 0); } - internal static bool VerifyPss(HashAlgorithmName hashAlgorithmName, ReadOnlySpan mHash, ReadOnlySpan em, int keySize) + internal static bool VerifyPss(HashAlgorithmName hashAlgorithmName, ReadOnlySpan mHash, ReadOnlySpan em, int keySize, int saltLength) { int hLen = HashLength(hashAlgorithmName); @@ -485,11 +389,8 @@ internal static bool VerifyPss(HashAlgorithmName hashAlgorithmName, ReadOnlySpan Debug.Assert(em.Length >= emLen); - // In this implementation, sLen is restricted to hLen. - int sLen = hLen; - // 3. If emLen < hLen + sLen + 2, output "inconsistent" and stop. - if (emLen < hLen + sLen + 2) + if (emLen < hLen + saltLength + 2) { return false; } @@ -538,7 +439,7 @@ internal static bool VerifyPss(HashAlgorithmName hashAlgorithmName, ReadOnlySpan // // Since signature verification is a public key operation there's no need to // use fixed time equality checking here. - for (int i = emLen - hLen - sLen - 2 - 1; i >= 0; --i) + for (int i = emLen - hLen - saltLength - 2 - 1; i >= 0; --i) { if (dbMask[i] != 0) { @@ -548,13 +449,13 @@ internal static bool VerifyPss(HashAlgorithmName hashAlgorithmName, ReadOnlySpan // 10 ("b") If the octet at position emLen - hLen - sLen - 1 (under a 1-indexed scheme) // is not 0x01, output "inconsistent" and stop. - if (dbMask[emLen - hLen - sLen - 2] != 0x01) + if (dbMask[emLen - hLen - saltLength - 2] != 0x01) { return false; } // 11. Let salt be the last sLen octets of DB. - ReadOnlySpan salt = dbMask.Slice(dbMask.Length - sLen); + ReadOnlySpan salt = dbMask.Slice(dbMask.Length - saltLength); // 12/13. Let H' = Hash(eight zeros || mHash || salt) hasher.AppendData(EightZeros); diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/RSA/SignVerify.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/RSA/SignVerify.cs index cc4d396d01ffb7..80877da36d1aa4 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/RSA/SignVerify.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/RSA/SignVerify.cs @@ -582,6 +582,90 @@ public void VerifySignature_SHA3_512_RSA2048() VerifySignature(signature, TestData.HelloBytes, HashAlgorithmName.SHA3_512.Name, TestData.RSA2048Params); } + [ConditionalTheory(nameof(SupportsPss))] + [InlineData(true)] + [InlineData(false)] + public void PssSignature_Sha_256_RSA2048_PSS_SaltLength_24(bool validateWithCorrectSaltLength) + { + // echo -n Hello | openssl dgst -sha-256 -binary | openssl pkeyutl -sign -inkey rsa.pem -pkeyopt rsa_padding_mode:pss -pkeyopt digest:sha-256 -pkeyopt rsa_pss_saltlen:24 | xxd -i -c 16 + byte[] signature = new byte[] { + 0xad, 0x2e, 0x10, 0x1a, 0xd0, 0xd7, 0x8b, 0x13, 0x88, 0xae, 0x18, 0x93, 0x3a, 0x62, 0x49, 0x3f, + 0xdc, 0x60, 0x64, 0xe3, 0xd8, 0x6f, 0xfa, 0x0c, 0xcb, 0xba, 0x6a, 0x5f, 0x46, 0x16, 0x56, 0x15, + 0x5b, 0x53, 0x80, 0x19, 0xa0, 0x37, 0x8f, 0xd9, 0xe1, 0xb2, 0x83, 0x94, 0x40, 0x77, 0x44, 0x74, + 0x23, 0xd0, 0x28, 0x96, 0x90, 0xa1, 0x90, 0x02, 0x95, 0x2e, 0x0e, 0xae, 0x44, 0x55, 0x94, 0x0a, + 0x58, 0x84, 0x98, 0xec, 0x71, 0xd0, 0xce, 0xaa, 0x35, 0xb0, 0x2d, 0xf2, 0xa5, 0x9f, 0xd0, 0x9a, + 0x9d, 0x23, 0x38, 0xfa, 0xc7, 0xdb, 0xe3, 0xb4, 0x4d, 0x71, 0x12, 0xd7, 0xc1, 0x35, 0x70, 0x41, + 0x01, 0x14, 0xbf, 0x3f, 0x17, 0xad, 0x4d, 0x40, 0x52, 0x03, 0x46, 0x52, 0xee, 0xcb, 0x2f, 0xcc, + 0x79, 0xd3, 0x32, 0xed, 0x64, 0x13, 0x67, 0xd4, 0xb2, 0x6c, 0x12, 0xd8, 0x1d, 0xf5, 0xd5, 0x33, + 0xb9, 0x50, 0x46, 0xa7, 0xf6, 0xe5, 0xc1, 0x65, 0xbd, 0xff, 0x90, 0x69, 0xb1, 0x37, 0xd6, 0x26, + 0xb1, 0xad, 0x77, 0x01, 0x5d, 0xf4, 0xd7, 0xf3, 0x4c, 0xf2, 0xf8, 0x5f, 0x67, 0x5b, 0x0e, 0xb6, + 0x17, 0xcd, 0xc8, 0x61, 0x92, 0xb2, 0xf3, 0xa0, 0xca, 0xbc, 0x7b, 0xc2, 0xac, 0xeb, 0x46, 0xd0, + 0x2f, 0x7c, 0xd8, 0x7d, 0xef, 0x43, 0x8a, 0x64, 0x93, 0x1e, 0xe0, 0xbf, 0x5a, 0x20, 0x6b, 0xb8, + 0xe7, 0x61, 0x7b, 0x67, 0xca, 0xb7, 0xe1, 0x78, 0x48, 0xab, 0xa2, 0xa6, 0x21, 0x3e, 0x50, 0x7b, + 0x49, 0xe0, 0x78, 0x74, 0x46, 0x4a, 0x65, 0xf4, 0x97, 0x52, 0xc7, 0x38, 0xeb, 0x00, 0x05, 0xff, + 0xb5, 0x37, 0xdb, 0x6a, 0xf3, 0x7b, 0x4d, 0x3b, 0x20, 0xa6, 0x7b, 0x6f, 0xcd, 0x34, 0x7c, 0xd3, + 0x83, 0x61, 0x51, 0xcf, 0x67, 0x55, 0x43, 0x2e, 0xd0, 0x73, 0x00, 0xfb, 0xd0, 0x61, 0x36, 0xad + }; + + RSASignaturePadding padding = RSASignaturePadding.CreatePss(validateWithCorrectSaltLength ? 24 : 12); + byte[] data = TestData.HelloBytes; + + using (RSA rsa = RSAFactory.Create(TestData.RSA2048Params)) + { + bool signatureIsValid = VerifyData(rsa, data, signature, HashAlgorithmName.SHA256, padding); + if (validateWithCorrectSaltLength) + { + Assert.True(signatureIsValid); + } + else + { + Assert.False(signatureIsValid); + } + } + } + + [ConditionalTheory(nameof(SupportsPss))] + [InlineData(true)] + [InlineData(false)] + public void PssSignature_Sha_256_RSA2048_PSS_SaltLength_Max(bool validateWithCorrectSaltLength) + { + // echo -n Hello | openssl dgst -sha-256 -binary | openssl pkeyutl -sign -inkey rsa.pem -pkeyopt rsa_padding_mode:pss -pkeyopt digest:sha-256 -pkeyopt rsa_pss_saltlen:max | xxd -i -c 16 + byte[] signature = new byte[] { + 0x46, 0x31, 0x80, 0x84, 0x97, 0x26, 0x47, 0x7a, 0xb3, 0x61, 0x13, 0x52, 0x51, 0x03, 0x6a, 0x71, + 0x41, 0x89, 0x66, 0xf3, 0xd5, 0xd4, 0xa3, 0x9c, 0x67, 0x87, 0x25, 0x00, 0x6e, 0x93, 0xcc, 0xc0, + 0xd0, 0xe6, 0x34, 0x7c, 0xe4, 0x1a, 0xb9, 0x3b, 0x48, 0xd1, 0xf7, 0x7e, 0x1b, 0x09, 0xa9, 0xd5, + 0x92, 0x74, 0x57, 0x08, 0xcb, 0xfd, 0xc2, 0x75, 0xe8, 0x8c, 0xb5, 0x9c, 0x28, 0xc4, 0x68, 0x40, + 0xe5, 0xa3, 0x1d, 0x29, 0x10, 0x7c, 0x60, 0x82, 0x50, 0xfb, 0xc1, 0x8c, 0x05, 0x23, 0xfb, 0xd5, + 0x3e, 0xf0, 0x6c, 0x36, 0x31, 0xdc, 0x1f, 0xfa, 0xd3, 0xba, 0x99, 0x24, 0x13, 0x06, 0x0b, 0x50, + 0x18, 0xe0, 0x43, 0x8b, 0xed, 0x6c, 0xa5, 0x7b, 0x0b, 0x94, 0xd9, 0x2a, 0x21, 0xd0, 0xe0, 0x58, + 0x31, 0x8b, 0x60, 0x55, 0xa2, 0x10, 0x95, 0xc4, 0xb1, 0x2d, 0x6c, 0x96, 0x0a, 0x61, 0xf2, 0xe7, + 0xd4, 0x5c, 0x5a, 0x8d, 0x4d, 0xd2, 0xe9, 0x2a, 0x34, 0xcb, 0x32, 0xe5, 0xd1, 0x17, 0xb2, 0xd2, + 0x02, 0x47, 0x90, 0xba, 0x69, 0xd5, 0xa3, 0xfe, 0x35, 0xba, 0x0a, 0xb7, 0x35, 0xe2, 0xae, 0xb6, + 0x82, 0xf1, 0xee, 0x72, 0x8e, 0x1e, 0xf9, 0x06, 0xed, 0xd7, 0x09, 0x6d, 0xf2, 0x49, 0x00, 0x3d, + 0x11, 0x8a, 0x1b, 0xb1, 0x9d, 0x6e, 0xd2, 0x49, 0xca, 0xab, 0x6d, 0x6e, 0x13, 0xca, 0xa9, 0x9f, + 0xdf, 0xfb, 0x6b, 0x29, 0xaa, 0x16, 0x54, 0x23, 0x52, 0xa7, 0x69, 0xa2, 0x72, 0xba, 0xc3, 0x07, + 0x3d, 0x60, 0x36, 0xdf, 0xfd, 0x9b, 0x1f, 0x4e, 0x58, 0xd9, 0x65, 0xf0, 0x10, 0xa7, 0x8d, 0x08, + 0xc0, 0x3c, 0xcf, 0x8a, 0x92, 0x2d, 0x9d, 0x75, 0x9b, 0xae, 0x68, 0xd0, 0xda, 0xaa, 0xe2, 0xd2, + 0x11, 0xf2, 0x10, 0xbd, 0x19, 0x47, 0x75, 0x3c, 0x30, 0xc4, 0x0f, 0xd0, 0x7b, 0xc2, 0x6b, 0x4b + }; + + RSASignaturePadding padding = RSASignaturePadding.CreatePss(validateWithCorrectSaltLength ? RSASignaturePadding.PssSaltLengthMax : 2); + byte[] data = TestData.HelloBytes; + + using (RSA rsa = RSAFactory.Create(TestData.RSA2048Params)) + { + var signatureIsValid = VerifyData(rsa, data, signature, HashAlgorithmName.SHA256, padding); + if (validateWithCorrectSaltLength) + { + Assert.True(signatureIsValid); + } + else + { + Assert.False(signatureIsValid); + } + } + } + [Theory] [MemberData(nameof(RoundTripTheories))] public void SignAndVerify_Roundtrip(string hashAlgorithm, RSAParameters rsaParameters) @@ -960,8 +1044,8 @@ public void VerifyHashSignature_SHA256_2048() } [Theory] - [MemberData(nameof(HashAlgorithmNames))] - public void PssRoundtrip(string hashAlgorithmName) + [MemberData(nameof(PssRoundTripParameters))] + public void PssRoundtrip(string hashAlgorithmName, RSASignaturePadding padding) { RSAParameters privateParameters = TestData.RSA2048Params; RSAParameters publicParameters = new RSAParameters @@ -978,7 +1062,6 @@ public void PssRoundtrip(string hashAlgorithmName) byte[] data = TestData.RsaBigExponentParams.Modulus; HashAlgorithmName hashAlgorithm = new HashAlgorithmName(hashAlgorithmName); - RSASignaturePadding padding = RSASignaturePadding.Pss; if (RSAFactory.SupportsPss) { @@ -1586,29 +1669,35 @@ private void SignAndVerify(byte[] data, string hashAlgorithmName, RSAParameters } } - public static IEnumerable HashAlgorithmNames + public static IEnumerable PssRoundTripParameters { get { - yield return new object[] { HashAlgorithmName.SHA256.Name }; - yield return new object[] { HashAlgorithmName.SHA384.Name }; - yield return new object[] { HashAlgorithmName.SHA512.Name }; - - if (RSAFactory.SupportsMd5Signatures) + int?[] saltLengths = [null, RSASignaturePadding.PssSaltLengthMax, RSASignaturePadding.PssSaltLengthIsHashLength, 1, 4]; + foreach (var saltLength in saltLengths) { - yield return new object[] { HashAlgorithmName.MD5.Name }; - } + var padding = saltLength is null ? RSASignaturePadding.Pss : RSASignaturePadding.CreatePss(saltLength.Value); - if (RSAFactory.SupportsSha1Signatures) - { - yield return new object[] { HashAlgorithmName.SHA1.Name }; - } + yield return new object[] { HashAlgorithmName.SHA256.Name, padding }; + yield return new object[] { HashAlgorithmName.SHA384.Name, padding }; + yield return new object[] { HashAlgorithmName.SHA512.Name, padding }; - if (RSAFactory.SupportsSha3) - { - yield return new object[] { HashAlgorithmName.SHA3_256.Name }; - yield return new object[] { HashAlgorithmName.SHA3_384.Name }; - yield return new object[] { HashAlgorithmName.SHA3_512.Name }; + if (RSAFactory.SupportsMd5Signatures) + { + yield return new object[] { HashAlgorithmName.MD5.Name, padding }; + } + + if (RSAFactory.SupportsSha1Signatures) + { + yield return new object[] { HashAlgorithmName.SHA1.Name, padding }; + } + + if (RSAFactory.SupportsSha3) + { + yield return new object[] { HashAlgorithmName.SHA3_256.Name, padding }; + yield return new object[] { HashAlgorithmName.SHA3_384.Name, padding }; + yield return new object[] { HashAlgorithmName.SHA3_512.Name, padding }; + } } } } diff --git a/src/libraries/Microsoft.Bcl.Cryptography/src/Microsoft.Bcl.Cryptography.csproj b/src/libraries/Microsoft.Bcl.Cryptography/src/Microsoft.Bcl.Cryptography.csproj index f34fbaa03c6f13..01b2b281f2dd45 100644 --- a/src/libraries/Microsoft.Bcl.Cryptography/src/Microsoft.Bcl.Cryptography.csproj +++ b/src/libraries/Microsoft.Bcl.Cryptography/src/Microsoft.Bcl.Cryptography.csproj @@ -7,6 +7,7 @@ Provides support for some cryptographic primitives for .NET Framework and .NET Standard. $(NoWarn);SYSLIB5006 true + false diff --git a/src/libraries/System.Security.Cryptography.Cng/tests/RsaCngTests.cs b/src/libraries/System.Security.Cryptography.Cng/tests/RsaCngTests.cs index 1d6310797ab28b..d08fdf2100e601 100644 --- a/src/libraries/System.Security.Cryptography.Cng/tests/RsaCngTests.cs +++ b/src/libraries/System.Security.Cryptography.Cng/tests/RsaCngTests.cs @@ -15,12 +15,14 @@ namespace System.Security.Cryptography.Cng.Tests { public static class RsaCngTests { - [Fact] - public static void SignVerifyHashRoundTrip() + [Theory] + [InlineData(32)] + [InlineData(64)] + public static void SignVerifyHashRoundTrip(int saltLength) { byte[] message = "781021abcd982139a8bc91387870ac01".HexToByteArray(); byte[] hash = SHA1.Create().ComputeHash(message); - TestSignVerifyHashRoundTrip(hash, HashAlgorithmName.SHA1, RSASignaturePadding.Pss, 0x100); + TestSignVerifyHashRoundTrip(hash, HashAlgorithmName.SHA1, RSASignaturePadding.CreatePss(saltLength), 0x100); } private static void TestSignVerifyHashRoundTrip(byte[] hash, HashAlgorithmName hashAlgorithm, RSASignaturePadding paddingMode, int expectedSignatureLength) diff --git a/src/libraries/System.Security.Cryptography.Cose/src/Resources/Strings.resx b/src/libraries/System.Security.Cryptography.Cose/src/Resources/Strings.resx index 8a49f05e5e00e0..a1438ee67031be 100644 --- a/src/libraries/System.Security.Cryptography.Cose/src/Resources/Strings.resx +++ b/src/libraries/System.Security.Cryptography.Cose/src/Resources/Strings.resx @@ -150,6 +150,9 @@ Error while decoding CBOR-encoded value, see inner exception for details. + + COSE does not support custom salt length for PSS signatures. + RSA key needs a signature padding. @@ -237,4 +240,4 @@ Algorithm (alg) header is required and it must be a protected header. - \ No newline at end of file + diff --git a/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseKey.cs b/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseKey.cs index ee7b02f00d1415..5b30569d85a85a 100644 --- a/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseKey.cs +++ b/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseKey.cs @@ -269,8 +269,11 @@ private static CoseAlgorithm GetRSAAlgorithm(RSASignaturePadding signaturePaddin { Debug.Assert(signaturePadding != null); - if (signaturePadding == RSASignaturePadding.Pss) + if (signaturePadding.Mode == RSASignaturePaddingMode.Pss) { +#if NET10_0_OR_GREATER + Debug.Assert(signaturePadding.PssSaltLength == RSASignaturePadding.PssSaltLengthIsHashLength); +#endif return hashAlgorithm.Name switch { nameof(HashAlgorithmName.SHA256) => CoseAlgorithm.PS256, diff --git a/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseSigner.cs b/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseSigner.cs index b572bf74cb3b02..90c1de42f1b489 100644 --- a/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseSigner.cs +++ b/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseSigner.cs @@ -112,6 +112,15 @@ public CoseSigner(RSA key, RSASignaturePadding signaturePadding, HashAlgorithmNa ArgumentNullException.ThrowIfNull(key); ArgumentNullException.ThrowIfNull(signaturePadding); +#if NET10_0_OR_GREATER + if (signaturePadding.Mode == RSASignaturePaddingMode.Pss) + { + if (signaturePadding.PssSaltLength != RSASignaturePadding.PssSaltLengthIsHashLength) + { + throw new ArgumentException(SR.CoseSignerPssSaltLengthMustBeHashLength, nameof(signaturePadding)); + } + } +#endif CoseKey = new CoseKey(key, signaturePadding, hashAlgorithm); _protectedHeaders = protectedHeaders; diff --git a/src/libraries/System.Security.Cryptography.Cose/tests/CoseSignerTests.cs b/src/libraries/System.Security.Cryptography.Cose/tests/CoseSignerTests.cs index 977a316031a13a..0aade42cec867c 100644 --- a/src/libraries/System.Security.Cryptography.Cose/tests/CoseSignerTests.cs +++ b/src/libraries/System.Security.Cryptography.Cose/tests/CoseSignerTests.cs @@ -77,5 +77,17 @@ public void CoseSigner_NullSignaturePadding() { Assert.Throws("signaturePadding", () => new CoseSigner(RSA.Create(), null!, HashAlgorithmName.SHA256)); } + +#if NET10_0_OR_GREATER + [Theory] + [InlineData(0)] + [InlineData(17)] + [InlineData(32)] + [InlineData(RSASignaturePadding.PssSaltLengthMax)] + public void CoseSigner_PssPaddingWithInvalidSaltLength(int saltLength) + { + Assert.Throws("signaturePadding", () => new CoseSigner(RSA.Create(), RSASignaturePadding.CreatePss(saltLength), HashAlgorithmName.SHA256)); + } +#endif } } diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj b/src/libraries/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj index 59e67ad0997f38..954e44ff188415 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj @@ -720,6 +720,13 @@ System.Security.Cryptography.Pkcs.EnvelopedCms + + + + + diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.RSA.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.RSA.cs index 557f7c94e4e4f8..23ee7d6821c994 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.RSA.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.RSA.cs @@ -294,12 +294,19 @@ private sealed class RSAPssCmsSignature : RSACmsSignature 0x01, 0x40, }; - internal override RSASignaturePadding? SignaturePadding => RSASignaturePadding.Pss; + internal override RSASignaturePadding SignaturePadding { get; } public RSAPssCmsSignature() : base(null, null) { + SignaturePadding = RSASignaturePadding.Pss; } +#if NET10_0_OR_GREATER + public RSAPssCmsSignature(int saltLength) : base(null, null) + { + SignaturePadding = RSASignaturePadding.CreatePss(saltLength); + } +#endif protected override RSASignaturePadding GetSignaturePadding( ReadOnlyMemory? signatureParameters, string? digestAlgorithmOid, @@ -348,13 +355,20 @@ protected override bool Sign( certificate, key, silent, - RSASignaturePadding.Pss, + SignaturePadding, out signatureValue); if (result) { signatureAlgorithm = Oids.RsaPss; +#if NET10_0_OR_GREATER + if (SignaturePadding.PssSaltLength != RSASignaturePadding.PssSaltLengthIsHashLength) + { + signatureParameters = GetSignaturePaddingForCustomPssSaltLength(certificate, hashAlgorithmName); + return result; + } +#endif if (hashAlgorithmName == HashAlgorithmName.SHA1) { signatureParameters = s_rsaPssSha1Parameters; @@ -389,6 +403,37 @@ protected override bool Sign( return result; } + +#if NET10_0_OR_GREATER + private byte[] GetSignaturePaddingForCustomPssSaltLength(X509Certificate2 certificate, HashAlgorithmName hashAlgorithmName) + { + string digestOid = PkcsHelpers.GetOidFromHashAlgorithm(hashAlgorithmName); + using RSA? publicKey = certificate.GetRSAPublicKey(); + Debug.Assert(publicKey != null, "Expected a public key to be present for PSS parameters."); + + PssParamsAsn parameters = new PssParamsAsn + { + HashAlgorithm = new AlgorithmIdentifierAsn { Algorithm = digestOid }, + MaskGenAlgorithm = new AlgorithmIdentifierAsn { Algorithm = Oids.Mgf1 }, + SaltLength = RsaPaddingProcessor.CalculatePssSaltLength(SignaturePadding.PssSaltLength, publicKey.KeySize, hashAlgorithmName), + TrailerField = 1 + }; + + AsnWriter writer = new AsnWriter(AsnEncodingRules.DER); + + using (writer.PushSequence()) + { + writer.WriteObjectIdentifierForCrypto(digestOid); + } + + parameters.MaskGenAlgorithm.Parameters = writer.Encode(); + writer.Reset(); + + parameters.Encode(writer); + + return writer.Encode(); + } +#endif } } } diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.cs index fec172dab43ecb..9adad32c95b878 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.cs @@ -87,13 +87,19 @@ protected abstract bool Sign( // The processor is RSA, but does not agree with the specified signature padding, so override. if (processor.SignaturePadding != rsaSignaturePadding) { - if (rsaSignaturePadding == RSASignaturePadding.Pkcs1) + if (rsaSignaturePadding.Mode == RSASignaturePaddingMode.Pkcs1) { processor = s_lookup[Oids.Rsa]; Debug.Assert(processor is not null); } - else if (rsaSignaturePadding == RSASignaturePadding.Pss) + else if (rsaSignaturePadding.Mode == RSASignaturePaddingMode.Pss) { +#if NET10_0_OR_GREATER + if (rsaSignaturePadding.PssSaltLength != RSASignaturePadding.PssSaltLengthIsHashLength) + { + return new RSAPssCmsSignature(rsaSignaturePadding.PssSaltLength); + } +#endif processor = s_lookup[Oids.RsaPss]; Debug.Assert(processor is not null); } diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSigner.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSigner.cs index 312e25dc1affc5..f75cda8856f656 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSigner.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSigner.cs @@ -55,7 +55,7 @@ private AsymmetricAlgorithm? PrivateKey set { if (value is not null && - value != RSASignaturePadding.Pkcs1 && value != RSASignaturePadding.Pss) + value != RSASignaturePadding.Pkcs1 && value.Mode != RSASignaturePaddingMode.Pss) { throw new ArgumentException(SR.Argument_InvalidRsaSignaturePadding, nameof(value)); } @@ -183,7 +183,7 @@ private CmsSigner( RSASignaturePadding? signaturePadding) { if (signaturePadding is not null && - signaturePadding != RSASignaturePadding.Pkcs1 && signaturePadding != RSASignaturePadding.Pss) + signaturePadding != RSASignaturePadding.Pkcs1 && signaturePadding.Mode != RSASignaturePaddingMode.Pss) { throw new ArgumentException(SR.Argument_InvalidRsaSignaturePadding, nameof(signaturePadding)); } diff --git a/src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/SignedCmsTests.netcoreapp.cs b/src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/SignedCmsTests.netcoreapp.cs index 2be391dfe1ff09..d2268bdf724213 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/SignedCmsTests.netcoreapp.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/SignedCmsTests.netcoreapp.cs @@ -684,15 +684,39 @@ public static void CreateSignature_DigestAlgorithmWithSignatureOid_Prohibited() } [Theory] - [InlineData(Oids.Sha256, true)] - [InlineData(Oids.Sha384, true)] - [InlineData(Oids.Sha512, true)] - [InlineData(Oids.Sha1, true)] - [InlineData(Oids.Sha256, false)] - [InlineData(Oids.Sha384, false)] - [InlineData(Oids.Sha512, false)] - [InlineData(Oids.Sha1, false)] - public static void CreateSignature_RsaPss(string digestOid, bool assignByConstructor) + [InlineData(Oids.Sha256, true, 0)] + [InlineData(Oids.Sha384, true, 0)] + [InlineData(Oids.Sha512, true, 0)] + [InlineData(Oids.Sha1, true, 0)] + [InlineData(Oids.Sha256, false, 0)] + [InlineData(Oids.Sha384, false, 0)] + [InlineData(Oids.Sha512, false, 0)] + [InlineData(Oids.Sha1, false, 0)] + [InlineData(Oids.Sha256, true, 1)] + [InlineData(Oids.Sha384, true, 1)] + [InlineData(Oids.Sha512, true, 1)] + [InlineData(Oids.Sha1, true, 1)] + [InlineData(Oids.Sha256, false, 1)] + [InlineData(Oids.Sha384, false, 1)] + [InlineData(Oids.Sha512, false, 1)] + [InlineData(Oids.Sha1, false, 1)] + [InlineData(Oids.Sha256, true, RSASignaturePadding.PssSaltLengthMax)] + [InlineData(Oids.Sha384, true, RSASignaturePadding.PssSaltLengthMax)] + [InlineData(Oids.Sha512, true, RSASignaturePadding.PssSaltLengthMax)] + [InlineData(Oids.Sha1, true, RSASignaturePadding.PssSaltLengthMax)] + [InlineData(Oids.Sha256, false, RSASignaturePadding.PssSaltLengthMax)] + [InlineData(Oids.Sha384, false, RSASignaturePadding.PssSaltLengthMax)] + [InlineData(Oids.Sha512, false, RSASignaturePadding.PssSaltLengthMax)] + [InlineData(Oids.Sha1, false, RSASignaturePadding.PssSaltLengthMax)] + [InlineData(Oids.Sha256, true, RSASignaturePadding.PssSaltLengthIsHashLength)] + [InlineData(Oids.Sha384, true, RSASignaturePadding.PssSaltLengthIsHashLength)] + [InlineData(Oids.Sha512, true, RSASignaturePadding.PssSaltLengthIsHashLength)] + [InlineData(Oids.Sha1, true, RSASignaturePadding.PssSaltLengthIsHashLength)] + [InlineData(Oids.Sha256, false, RSASignaturePadding.PssSaltLengthIsHashLength)] + [InlineData(Oids.Sha384, false, RSASignaturePadding.PssSaltLengthIsHashLength)] + [InlineData(Oids.Sha512, false, RSASignaturePadding.PssSaltLengthIsHashLength)] + [InlineData(Oids.Sha1, false, RSASignaturePadding.PssSaltLengthIsHashLength)] + public static void CreateSignature_RsaPss(string digestOid, bool assignByConstructor, int saltLength) { ContentInfo content = new ContentInfo(new byte[] { 1, 2, 3 }); SignedCms cms = new SignedCms(content); @@ -704,12 +728,12 @@ public static void CreateSignature_RsaPss(string digestOid, bool assignByConstru if (assignByConstructor) { - signer = new CmsSigner(SubjectIdentifierType.IssuerAndSerialNumber, cert, null, RSASignaturePadding.Pss); + signer = new CmsSigner(SubjectIdentifierType.IssuerAndSerialNumber, cert, null, RSASignaturePadding.CreatePss(saltLength)); } else { signer = new CmsSigner(SubjectIdentifierType.IssuerAndSerialNumber, cert); - signer.SignaturePadding = RSASignaturePadding.Pss; + signer.SignaturePadding = RSASignaturePadding.CreatePss(saltLength); } signer.DigestAlgorithm = new Oid(digestOid, null); diff --git a/src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/SignedCmsWholeDocumentTests.cs b/src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/SignedCmsWholeDocumentTests.cs index f3adf2c9af4cf7..eed726ea3c0195 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/SignedCmsWholeDocumentTests.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/SignedCmsWholeDocumentTests.cs @@ -126,15 +126,14 @@ public static void ReadRsaPssDocument(bool fromSpan) // CheckHash always throws for certificate-based signers. Assert.Throws(() => signer.CheckHash()); - // At this time we cannot support the PSS parameters for this document. - Assert.Throws(() => 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 - Assert.Throws(() => cms.CheckSignature(true)); + cms.CheckSignature(true); } [ConditionalFact(typeof(SignatureSupport), nameof(SignatureSupport.SupportsRsaSha1Signatures))] diff --git a/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs b/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs index 9ce2245d0d13e5..0a1b02fd3f431f 100644 --- a/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs +++ b/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs @@ -2736,10 +2736,14 @@ public override void SetKey(System.Security.Cryptography.AsymmetricAlgorithm key } public sealed partial class RSASignaturePadding : System.IEquatable { + public const int PssSaltLengthIsHashLength = -1; + public const int PssSaltLengthMax = -2; internal RSASignaturePadding() { } public System.Security.Cryptography.RSASignaturePaddingMode Mode { get { throw null; } } + public int PssSaltLength { get { throw null; } } public static System.Security.Cryptography.RSASignaturePadding Pkcs1 { get { throw null; } } public static System.Security.Cryptography.RSASignaturePadding Pss { get { throw null; } } + public static RSASignaturePadding CreatePss(int saltLength) { throw null; } public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } public bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] System.Security.Cryptography.RSASignaturePadding? other) { throw null; } public override int GetHashCode() { throw null; } diff --git a/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx b/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx index c62f5dac36b581..e0b2acef70c70d 100644 --- a/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx +++ b/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx @@ -366,6 +366,9 @@ The specified curve '{0}' or its parameters are not valid for this platform. + + Custom salt lengths for PSS are not supported on this platform. + Custom trust certificates were provided while in System trust mode. @@ -522,6 +525,9 @@ Specified padding mode is not valid for this algorithm. + + Specified salt length is invalid. + The store handle is invalid. diff --git a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj index c55a7454e0d140..94e4569fe27811 100644 --- a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj +++ b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj @@ -452,8 +452,12 @@ Link="Common\System\Security\Cryptography\RSAKeyFormatHelper.Encrypted.cs" /> + + - diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSABCrypt.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSABCrypt.cs index 5795d2840c7e08..b52d593668b95c 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSABCrypt.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSABCrypt.cs @@ -294,6 +294,7 @@ public override bool TrySignHash( hash, destination, hashAlgorithmName, + RsaPaddingProcessor.CalculatePssSaltLength(padding.PssSaltLength, KeySize, hashAlgorithm), out written); break; @@ -346,7 +347,8 @@ public override bool VerifyHash( key, hash, signature, - hashAlgorithmName); + hashAlgorithmName, + RsaPaddingProcessor.CalculatePssSaltLength(padding.PssSaltLength, KeySize, hashAlgorithm)); default: throw new CryptographicException(SR.Cryptography_UnsupportedPaddingMode); } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSASignaturePadding.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSASignaturePadding.cs index 60ee325a9fccbc..a630ac971d6fb2 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSASignaturePadding.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSASignaturePadding.cs @@ -6,17 +6,53 @@ namespace System.Security.Cryptography { - // NOTE: This is *currently* 1:1 with the enum, but it exists to reserve room for more options - // such as custom # of PSS salt bytes without having to modify other parts of the API - // surface. - /// /// Specifies the padding mode and parameters to use with RSA signature creation or verification operations. /// public sealed class RSASignaturePadding : IEquatable { + /// + /// Represents a constant value indicating that the salt length should match the hash length. + /// + /// This value is typically used in cryptographic operations where the salt length is required to + /// be the same as the hash length. + public const int PssSaltLengthIsHashLength = RsaPaddingProcessor.PssSaltLengthIsHashLength; + + /// + /// Represents the maximum allowable length, in bytes, for a PSS (Probabilistic Signature Scheme) salt. + /// + /// This 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. + public const int PssSaltLengthMax = RsaPaddingProcessor.PssSaltLengthMax; + + /// + /// Specifies the salt length to use for PSS padding. This property is only relevant when the is . + /// + /// + /// This value must either be a positive number or one of the special constants or . + /// + public int PssSaltLength { get; } + + /// + /// Creates a new instance of for PSS padding with a specific salt length. + /// + /// The length of the salt in bytes, or one of the constants or . + /// A new instance of configured for PSS padding with the specified salt length. + /// The is negative or not one of the special constants. + public static RSASignaturePadding CreatePss(int saltLength) + { + switch (saltLength) + { + case PssSaltLengthIsHashLength or PssSaltLengthMax: + case >= 0: + return new RSASignaturePadding(saltLength); + default: + throw new ArgumentOutOfRangeException(nameof(saltLength), SR.Cryptography_InvalidSaltLengthForPss); + } + } + private static readonly RSASignaturePadding s_pkcs1 = new RSASignaturePadding(RSASignaturePaddingMode.Pkcs1); - private static readonly RSASignaturePadding s_pss = new RSASignaturePadding(RSASignaturePaddingMode.Pss); + private static readonly RSASignaturePadding s_pss = CreatePss(PssSaltLengthIsHashLength); private readonly RSASignaturePaddingMode _mode; @@ -25,6 +61,12 @@ private RSASignaturePadding(RSASignaturePaddingMode mode) _mode = mode; } + private RSASignaturePadding(int pssSaltLength) + { + _mode = RSASignaturePaddingMode.Pss; + PssSaltLength = pssSaltLength; + } + /// /// mode. /// @@ -51,7 +93,7 @@ public RSASignaturePaddingMode Mode public override int GetHashCode() { - return _mode.GetHashCode(); + return HashCode.Combine(_mode, PssSaltLength); } public override bool Equals([NotNullWhen(true)] object? obj) @@ -61,7 +103,7 @@ public override bool Equals([NotNullWhen(true)] object? obj) public bool Equals([NotNullWhen(true)] RSASignaturePadding? other) { - return other is not null && _mode == other._mode; + return other is not null && _mode == other._mode && PssSaltLength == other.PssSaltLength; } public static bool operator ==(RSASignaturePadding? left, RSASignaturePadding? right) diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/RSAPssX509SignatureGenerator.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/RSAPssX509SignatureGenerator.cs index c1daad2b756e25..abd7b7def535f3 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/RSAPssX509SignatureGenerator.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/RSAPssX509SignatureGenerator.cs @@ -18,7 +18,6 @@ internal RSAPssX509SignatureGenerator(RSA key, RSASignaturePadding padding) Debug.Assert(padding != null); Debug.Assert(padding.Mode == RSASignaturePaddingMode.Pss); - // Currently we don't accept options in PSS mode, but we could, so store the padding here. _key = key; _padding = padding; } @@ -26,8 +25,7 @@ internal RSAPssX509SignatureGenerator(RSA key, RSASignaturePadding padding) public override byte[] GetSignatureAlgorithmIdentifier(HashAlgorithmName hashAlgorithm) { // If we ever support options in PSS (like MGF-2, if such an MGF is ever invented) - // Or, more reasonably, supporting a custom value for the salt size. - if (_padding != RSASignaturePadding.Pss) + if (_padding.Mode != RSASignaturePaddingMode.Pss) { throw new CryptographicException(SR.Cryptography_InvalidPaddingMode); } @@ -37,17 +35,14 @@ public override byte[] GetSignatureAlgorithmIdentifier(HashAlgorithmName hashAlg if (hashAlgorithm == HashAlgorithmName.SHA256) { - cbSalt = SHA256.HashSizeInBytes; digestOid = Oids.Sha256; } else if (hashAlgorithm == HashAlgorithmName.SHA384) { - cbSalt = SHA384.HashSizeInBytes; digestOid = Oids.Sha384; } else if (hashAlgorithm == HashAlgorithmName.SHA512) { - cbSalt = SHA512.HashSizeInBytes; digestOid = Oids.Sha512; } else @@ -58,17 +53,11 @@ public override byte[] GetSignatureAlgorithmIdentifier(HashAlgorithmName hashAlg SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithm.Name)); } + cbSalt = RsaPaddingProcessor.CalculatePssSaltLength(_padding.PssSaltLength, _key.KeySize, hashAlgorithm); + // RFC 5754 says that the NULL for SHA2 (256/384/512) MUST be omitted // (https://tools.ietf.org/html/rfc5754#section-2) (and that you MUST // be able to read it even if someone wrote it down) - // - // Since we - // * don't support SHA-1 in this class - // * only support MGF-1 - // * don't support the MGF PRF being different than hashAlgorithm - // * use saltLength==hashLength - // * don't allow custom trailer - // we don't have to worry about any of the DEFAULTs. (specify, specify, specify, omit). PssParamsAsn parameters = new PssParamsAsn { diff --git a/src/libraries/System.Security.Cryptography/tests/RSATests.cs b/src/libraries/System.Security.Cryptography/tests/RSATests.cs index 7b55391916c587..bdb812b88ea8eb 100644 --- a/src/libraries/System.Security.Cryptography/tests/RSATests.cs +++ b/src/libraries/System.Security.Cryptography/tests/RSATests.cs @@ -226,6 +226,32 @@ public void RSASignaturePadding_Equality() Assert.False(RSASignaturePadding.Pkcs1.Equals((object)null)); Assert.False(RSASignaturePadding.Pkcs1 == null); Assert.True(RSASignaturePadding.Pkcs1 != null); + + Assert.Equal(RSASignaturePadding.CreatePss(RSASignaturePadding.PssSaltLengthIsHashLength), RSASignaturePadding.CreatePss(RSASignaturePadding.PssSaltLengthIsHashLength)); + Assert.Equal(RSASignaturePadding.CreatePss(RSASignaturePadding.PssSaltLengthMax), RSASignaturePadding.CreatePss(RSASignaturePadding.PssSaltLengthMax)); + Assert.Equal(RSASignaturePadding.CreatePss(15), RSASignaturePadding.CreatePss(15)); + Assert.NotEqual(RSASignaturePadding.CreatePss(15), RSASignaturePadding.CreatePss(16)); + Assert.NotEqual(RSASignaturePadding.Pkcs1, RSASignaturePadding.CreatePss(16)); + } + + [Theory] + [InlineData(2)] + [InlineData(RSASignaturePadding.PssSaltLengthIsHashLength)] + [InlineData(RSASignaturePadding.PssSaltLengthMax)] + public void RSASignaturePadding_Constructor_ValidParameters(int saltLength) + { + RSASignaturePadding padding = RSASignaturePadding.CreatePss(saltLength); + Assert.Equal(RSASignaturePaddingMode.Pss, padding.Mode); + Assert.Equal(saltLength, padding.PssSaltLength); + } + + [Theory] + [InlineData(-3)] + [InlineData(0)] + public void RSASignaturePadding_Constructor_InvalidParameters(int saltLength) + { + ArgumentOutOfRangeException exception = Assert.Throws(() => RSASignaturePadding.CreatePss(saltLength)); + Assert.Equal("saltLength", exception.ParamName); } [Fact] diff --git a/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/CertificateRequestChainTests.cs b/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/CertificateRequestChainTests.cs index ee6624bdd7e88b..e43596501a4ace 100644 --- a/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/CertificateRequestChainTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/CertificateRequestChainTests.cs @@ -489,8 +489,11 @@ private static void CreateAndTestChain( } } - [ConditionalFact(nameof(PlatformSupportsPss))] - public static void CreateChain_RSAPSS() + [ConditionalTheory(nameof(PlatformSupportsPss))] + [InlineData(1)] + [InlineData(RSASignaturePadding.PssSaltLengthMax)] + [InlineData(RSASignaturePadding.PssSaltLengthIsHashLength)] + public static void CreateChain_RSAPSS(int saltLength) { using (RSA rootKey = RSA.Create()) using (RSA intermedKey = RSA.Create()) @@ -501,7 +504,7 @@ public static void CreateChain_RSAPSS() X509Certificate2 leafCert = null; CertificateRequest request; - RSASignaturePadding padding = RSASignaturePadding.Pss; + RSASignaturePadding padding = RSASignaturePadding.CreatePss(saltLength); DateTimeOffset notBefore = DateTimeOffset.UtcNow; DateTimeOffset notAfter = notBefore.AddHours(1); diff --git a/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/CertificateRequestLoadTests.cs b/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/CertificateRequestLoadTests.cs index 4d4903f5b6fcda..ae1ab4ae46ab5c 100644 --- a/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/CertificateRequestLoadTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/CertificateRequestLoadTests.cs @@ -375,21 +375,30 @@ public static void VerifySignature_RSA_PKCS1(string hashAlgorithm) } [Theory] - [InlineData("SHA256")] - [InlineData("SHA384")] - [InlineData("SHA512")] - [InlineData("SHA1")] - public static void VerifySignature_RSA_PSS(string hashAlgorithm) + [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. + [InlineData("SHA256", RSASignaturePadding.PssSaltLengthMax)] + [InlineData("SHA384", RSASignaturePadding.PssSaltLengthMax)] + [InlineData("SHA512", RSASignaturePadding.PssSaltLengthMax)] + //[InlineData("SHA1", RSASignaturePadding.PssSaltLengthMax)] // The current implementation for CertificateRequest does not support SHA-1 with PSS. If this is required, the RSASha1PssSignatureGenerator and the RSAPssX509SignatureGenerator class needs updates. + [InlineData("SHA256", RSASignaturePadding.PssSaltLengthIsHashLength)] + [InlineData("SHA384", RSASignaturePadding.PssSaltLengthIsHashLength)] + [InlineData("SHA512", RSASignaturePadding.PssSaltLengthIsHashLength)] + [InlineData("SHA1", RSASignaturePadding.PssSaltLengthIsHashLength)] + public static void VerifySignature_RSA_PSS(string hashAlgorithm, int saltLength) { HashAlgorithmName hashAlgorithmName = new HashAlgorithmName(hashAlgorithm); using (RSA key = RSA.Create()) { + RSASignaturePadding padding = RSASignaturePadding.CreatePss(saltLength); CertificateRequest first = new CertificateRequest( "CN=Test", key, hashAlgorithmName, - RSASignaturePadding.Pss); + padding); byte[] pkcs10; @@ -397,11 +406,11 @@ public static void VerifySignature_RSA_PSS(string hashAlgorithm) { if (SignatureSupport.SupportsX509Sha1Signatures) { - pkcs10 = first.CreateSigningRequest(new RSASha1PssSignatureGenerator(key)); + pkcs10 = first.CreateSigningRequest(new RSASha1PssSignatureGenerator(key, padding)); } else { - Assert.ThrowsAny(() => first.CreateSigningRequest(new RSASha1PssSignatureGenerator(key))); + Assert.ThrowsAny(() => first.CreateSigningRequest(new RSASha1PssSignatureGenerator(key, padding))); return; } } @@ -411,19 +420,19 @@ public static void VerifySignature_RSA_PSS(string hashAlgorithm) } // Assert.NoThrow - CertificateRequest.LoadSigningRequest(pkcs10, hashAlgorithmName, out _); + CertificateRequest.LoadSigningRequest(pkcs10, hashAlgorithmName, out _, signerSignaturePadding: padding); pkcs10[^1] ^= 0xFF; Assert.Throws( - () => CertificateRequest.LoadSigningRequest(pkcs10, hashAlgorithmName, out _)); + () => CertificateRequest.LoadSigningRequest(pkcs10, hashAlgorithmName, out _, signerSignaturePadding: padding)); // Assert.NoThrow CertificateRequest.LoadSigningRequest( pkcs10, hashAlgorithmName, out _, - CertificateRequestLoadOptions.SkipSignatureValidation); + CertificateRequestLoadOptions.SkipSignatureValidation, signerSignaturePadding: padding); } } @@ -824,8 +833,12 @@ public static void LoadCreate_MatchesCreate_RSAPkcs1() } } - [Fact] - public static void LoadCreate_MatchesCreate_RSAPss() + [Theory] + [InlineData(0)] + [InlineData(4)] + [InlineData(RSASignaturePadding.PssSaltLengthMax)] + [InlineData(RSASignaturePadding.PssSaltLengthIsHashLength)] + public static void LoadCreate_MatchesCreate_RSAPss(int saltLength) { using (RSA key = RSA.Create(2048)) { @@ -834,9 +847,9 @@ public static void LoadCreate_MatchesCreate_RSAPss() "CN=Roundtrip, O=RSA, OU=PSS", key, HashAlgorithmName.SHA256, - RSASignaturePadding.Pss), - X509SignatureGenerator.CreateForRSA(key, RSASignaturePadding.Pss), - deterministicSignature: false); + RSASignaturePadding.CreatePss(saltLength)), + X509SignatureGenerator.CreateForRSA(key, RSASignaturePadding.CreatePss(saltLength)), + deterministicSignature: saltLength == 0); } } diff --git a/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/CertificateRequestUsageTests.cs b/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/CertificateRequestUsageTests.cs index 4824610078bb04..737da5410ab688 100644 --- a/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/CertificateRequestUsageTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/CertificateRequestUsageTests.cs @@ -5,6 +5,7 @@ using RsaTestData = System.Security.Cryptography.Rsa.Tests.TestData; using Test.Cryptography; using Xunit; +using System.Formats.Asn1; namespace System.Security.Cryptography.X509Certificates.Tests.CertificateCreation { @@ -306,6 +307,33 @@ public static void SelfSign_ECC_DiminishedPoint_UseCertKeys() } } + [Theory] + [InlineData(1)] + [InlineData(RSASignaturePadding.PssSaltLengthMax)] + [InlineData(RSASignaturePadding.PssSaltLengthIsHashLength)] + public static void SelfSign_RSA_PssPadding_CustomSaltLength(int customSaltLength) + { + 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 tbsCertificate = sequence.ReadEncodedValue(); + ReadOnlyMemory signatureAlgorithm = sequence.ReadEncodedValue(); + byte[] signature = sequence.ReadBitString(out var _); + + int testSaltLength = customSaltLength switch + { + RSASignaturePadding.PssSaltLengthMax => 222, + RSASignaturePadding.PssSaltLengthIsHashLength => 32, + _ => customSaltLength + }; + Assert.True(rsa.VerifyData(tbsCertificate.Span, signature, HashAlgorithmName.SHA256, RSASignaturePadding.CreatePss(testSaltLength))); + } + } + [Theory] [InlineData("80", "0080")] [InlineData("0080", "0080")] diff --git a/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/CrlBuilderTests.cs b/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/CrlBuilderTests.cs index 2a5a2bcd7ba161..895f623ec5591d 100644 --- a/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/CrlBuilderTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/CrlBuilderTests.cs @@ -22,6 +22,8 @@ public enum CertKind MLDsa, RsaPkcs1, RsaPss, + RsaPssWithCustomSaltLength, + RsaPssWithMaxSaltLength, SlhDsa, } @@ -36,6 +38,8 @@ public static IEnumerable SupportedCertKinds() yield return new object[] { CertKind.RsaPkcs1 }; yield return new object[] { CertKind.RsaPss }; + yield return new object[] { CertKind.RsaPssWithCustomSaltLength }; + yield return new object[] { CertKind.RsaPssWithMaxSaltLength }; if (SlhDsa.IsSupported) { @@ -1538,6 +1542,18 @@ private static void BuildCertificateAndRun( key = rsa; req = new CertificateRequest(subjectName, rsa, HashAlgorithmName.SHA384, GetRsaPadding(certKind)); } + else if (certKind == CertKind.RsaPssWithMaxSaltLength) + { + var rsa = RSA.Create(); + key = rsa; + req = new CertificateRequest(subjectName, rsa, HashAlgorithmName.SHA384, RSASignaturePadding.CreatePss(RSASignaturePadding.PssSaltLengthMax)); + } + else if (certKind == CertKind.RsaPssWithCustomSaltLength) + { + var rsa = RSA.Create(); + key = rsa; + req = new CertificateRequest(subjectName, rsa, HashAlgorithmName.SHA384, RSASignaturePadding.CreatePss(200)); + } else if (certKind == CertKind.MLDsa) { MLDsa mldsa = MLDsa.GenerateKey(MLDsaAlgorithm.MLDsa44); @@ -1696,7 +1712,7 @@ private static X509SignatureGenerator GetSignatureGenerator( X509Certificate2 cert, out IDisposable key) { - if (certKind == CertKind.RsaPkcs1 || certKind == CertKind.RsaPss) + if (certKind == CertKind.RsaPkcs1 || certKind == CertKind.RsaPss || certKind == CertKind.RsaPssWithCustomSaltLength || certKind == CertKind.RsaPssWithMaxSaltLength) { RSA rsa = cert.GetRSAPrivateKey(); key = rsa; @@ -1735,7 +1751,7 @@ private static void VerifySignature( { bool signatureValid; - if (certKind == CertKind.RsaPkcs1 || certKind == CertKind.RsaPss) + if (certKind == CertKind.RsaPkcs1 || certKind == CertKind.RsaPss ||certKind == CertKind.RsaPssWithCustomSaltLength || certKind == CertKind.RsaPssWithMaxSaltLength) { using RSA rsa = cert.GetRSAPublicKey(); signatureValid = rsa.VerifyData(data, signature, hashAlgorithm, GetRsaPadding(certKind)); @@ -1770,7 +1786,7 @@ private static bool RequiresHashAlgorithm(CertKind certKind) { return certKind switch { - CertKind.ECDsa or CertKind.RsaPkcs1 or CertKind.RsaPss => true, + CertKind.ECDsa or CertKind.RsaPkcs1 or CertKind.RsaPss or CertKind.RsaPssWithCustomSaltLength or CertKind.RsaPssWithMaxSaltLength => true, CertKind.MLDsa or CertKind.SlhDsa => false, _ => throw new NotSupportedException(certKind.ToString()) }; @@ -1782,6 +1798,8 @@ private static RSASignaturePadding GetRsaPadding(CertKind certKind) { CertKind.RsaPkcs1 => RSASignaturePadding.Pkcs1, CertKind.RsaPss => RSASignaturePadding.Pss, + CertKind.RsaPssWithCustomSaltLength => RSASignaturePadding.CreatePss(200), + CertKind.RsaPssWithMaxSaltLength => RSASignaturePadding.CreatePss(RSASignaturePadding.PssSaltLengthMax), _ => null, }; } diff --git a/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/RSAPssX509SignatureGeneratorTests.cs b/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/RSAPssX509SignatureGeneratorTests.cs index 216bb1ca84cbec..a0645d016cf3e4 100644 --- a/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/RSAPssX509SignatureGeneratorTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/RSAPssX509SignatureGeneratorTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Formats.Asn1; using Test.Cryptography; using Xunit; @@ -56,6 +57,48 @@ public static void PublicKeyEncoding() } } + [Theory] + [InlineData(1)] + [InlineData(RSASignaturePadding.PssSaltLengthIsHashLength)] + [InlineData(RSASignaturePadding.PssSaltLengthMax)] + public static void PssPaddingSaltLengths(int saltLengthToTest) + { + using (RSA rsa = RSA.Create()) + { + rsa.ImportParameters(TestData.RsaBigExponentParams); + + RSASignaturePadding signaturePadding = RSASignaturePadding.CreatePss(saltLengthToTest); + X509SignatureGenerator signatureGenerator = X509SignatureGenerator.CreateForRSA(rsa, signaturePadding); + + byte[] data = new byte[] { 1, 2, 3, 4, 5 }; + byte[] signature = signatureGenerator.SignData(data, HashAlgorithmName.SHA256); + byte[] signatureAlgorithm = signatureGenerator.GetSignatureAlgorithmIdentifier(HashAlgorithmName.SHA256); + + AsnReader asnReader = new AsnReader(signatureAlgorithm, AsnEncodingRules.DER); + AsnReader rootSequence = asnReader.ReadSequence(); + Assert.Equal("1.2.840.113549.1.1.10", rootSequence.ReadObjectIdentifier()); // Make sure it's RSASSA-PSS + AsnReader pssStructure = rootSequence.ReadSequence(); + pssStructure.ReadEncodedValue(); // Ignore the hash algorithm OID + pssStructure.ReadEncodedValue(); // Ignore the mask generation function OID + Asn1Tag saltTag = new Asn1Tag(TagClass.ContextSpecific, 2, true); + + if (pssStructure.HasData && pssStructure.PeekTag().HasSameClassAndValue(saltTag)) + { + var saltEntry = pssStructure.ReadSequence(saltTag); + var actualSaltLength = saltEntry.ReadInteger(); + var expectedSaltLength = saltLengthToTest switch + { + RSASignaturePadding.PssSaltLengthIsHashLength => 32, + RSASignaturePadding.PssSaltLengthMax => 222, + _ => saltLengthToTest + }; + Assert.Equal(expectedSaltLength, actualSaltLength); + } + + Assert.True(rsa.VerifyData(data, signature, HashAlgorithmName.SHA256, signaturePadding)); + } + } + [Theory] [InlineData("SHA256")] [InlineData("SHA384")] diff --git a/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/X509Sha1SignatureGenerators.cs b/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/X509Sha1SignatureGenerators.cs index 0ed6da88215eb7..71904695a6ce5e 100644 --- a/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/X509Sha1SignatureGenerators.cs +++ b/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/X509Sha1SignatureGenerators.cs @@ -55,9 +55,9 @@ internal sealed class RSASha1PssSignatureGenerator : X509SignatureGenerator { private readonly X509SignatureGenerator _realRsaGenerator; - internal RSASha1PssSignatureGenerator(RSA rsa) + internal RSASha1PssSignatureGenerator(RSA rsa, RSASignaturePadding signaturePadding) { - _realRsaGenerator = CreateForRSA(rsa, RSASignaturePadding.Pss); + _realRsaGenerator = CreateForRSA(rsa, signaturePadding); } protected override PublicKey BuildPublicKey() => _realRsaGenerator.PublicKey; diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_rsa.c b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_rsa.c index f4a79df5fa1498..cfc2531efb2fd7 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_rsa.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_rsa.c @@ -213,7 +213,7 @@ int32_t CryptoNative_RsaEncrypt(EVP_PKEY* pkey, return ret; } -static bool ConfigureSignature(EVP_PKEY_CTX* ctx, RsaPaddingMode padding, const EVP_MD* digest) +static bool ConfigureSignature(EVP_PKEY_CTX* ctx, RsaPaddingMode padding, int pssSaltLength, const EVP_MD* digest) { #pragma clang diagnostic push @@ -236,7 +236,7 @@ static bool ConfigureSignature(EVP_PKEY_CTX* ctx, RsaPaddingMode padding, const assert(padding == RsaPaddingOaepOrPss); if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PSS_PADDING) <= 0 || - EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, RSA_PSS_SALTLEN_DIGEST) <= 0) + EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, pssSaltLength) <= 0) { return false; } @@ -248,6 +248,7 @@ static bool ConfigureSignature(EVP_PKEY_CTX* ctx, RsaPaddingMode padding, const int32_t CryptoNative_RsaSignHash(EVP_PKEY* pkey, void* extraHandle, RsaPaddingMode padding, + int pssSaltLength, const EVP_MD* digest, const uint8_t* hash, int32_t hashLen, @@ -270,7 +271,7 @@ int32_t CryptoNative_RsaSignHash(EVP_PKEY* pkey, goto done; } - if (!ConfigureSignature(ctx, padding, digest)) + if (!ConfigureSignature(ctx, padding, pssSaltLength, digest)) { goto done; } @@ -310,6 +311,7 @@ int32_t CryptoNative_RsaSignHash(EVP_PKEY* pkey, int32_t CryptoNative_RsaVerifyHash(EVP_PKEY* pkey, void* extraHandle, RsaPaddingMode padding, + int pssSaltLength, const EVP_MD* digest, const uint8_t* hash, int32_t hashLen, @@ -332,7 +334,7 @@ int32_t CryptoNative_RsaVerifyHash(EVP_PKEY* pkey, goto done; } - if (!ConfigureSignature(ctx, padding, digest)) + if (!ConfigureSignature(ctx, padding, pssSaltLength, digest)) { goto done; } diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_rsa.h b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_rsa.h index 7ae93de6cf3553..1ae796b1fecda9 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_rsa.h +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_rsa.h @@ -62,6 +62,7 @@ Returns the number of bytes written to destination, -1 on error. PALEXPORT int32_t CryptoNative_RsaSignHash(EVP_PKEY* pkey, void* extraHandle, RsaPaddingMode padding, + int pssSaltLength, const EVP_MD* digest, const uint8_t* hash, int32_t hashLen, @@ -77,6 +78,7 @@ Returns 1 on a verified signature, 0 on a mismatched signature, -1 on error. PALEXPORT int32_t CryptoNative_RsaVerifyHash(EVP_PKEY* pkey, void* extraHandle, RsaPaddingMode padding, + int pssSaltLength, const EVP_MD* digest, const uint8_t* hash, int32_t hashLen,