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 3a0cd9655052aa..8f64f766bc0734 100644 --- a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptSignHash.cs +++ b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptSignHash.cs @@ -55,6 +55,7 @@ internal static unsafe NTSTATUS BCryptSignHashPss( ReadOnlySpan hash, Span destination, string hashAlgorithmName, + int saltLength, out int bytesWritten) { fixed (char* pHashAlgorithmName = hashAlgorithmName) @@ -63,7 +64,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 7df6ddce3e4ee7..ae82b107b4d7a9 100644 --- a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptVerifySignature.cs +++ b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptVerifySignature.cs @@ -62,7 +62,8 @@ internal static unsafe bool BCryptVerifySignaturePss( SafeBCryptKeyHandle key, ReadOnlySpan hash, ReadOnlySpan signature, - string hashAlgorithmName) + string hashAlgorithmName, + int saltLength) { NTSTATUS status; @@ -73,7 +74,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 ddd484e0caf9f6..8820a4085faba8 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 @@ -48,6 +48,11 @@ internal RSASignaturePadding GetSignaturePadding(int? digestValueLength = null) saltSize = Helpers.HashOidToByteLength(HashAlgorithm.Algorithm); } +#if NET11_0_OR_GREATER + return SaltLength == saltSize ? + RSASignaturePadding.Pss : + RSASignaturePadding.CreatePss(SaltLength); +#else if (SaltLength != saltSize) { throw new CryptographicException( @@ -57,8 +62,8 @@ internal RSASignaturePadding GetSignaturePadding(int? digestValueLength = null) HashAlgorithm.Algorithm)); } - // 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 98cf22419b7dd2..6343e023cf1d1b 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/RSAAndroid.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/RSAAndroid.cs @@ -652,7 +652,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(); } @@ -683,7 +683,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 { @@ -721,7 +721,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(); } @@ -767,9 +767,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 426c9f73dcf5d5..339e196b8ed2a4 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/RSAAppleCrypto.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/RSAAppleCrypto.cs @@ -391,6 +391,8 @@ public override bool TrySignHash(ReadOnlySpan hash, Span destination ArgumentException.ThrowIfNullOrEmpty(hashAlgorithm.Name, nameof(hashAlgorithm)); ArgumentNullException.ThrowIfNull(padding); + ValidatePaddingParameters(hashAlgorithm, padding); + ThrowIfDisposed(); Interop.AppleCrypto.PAL_SignatureAlgorithm signatureAlgorithm = padding.Mode switch @@ -448,7 +450,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 { @@ -461,6 +463,17 @@ public override bool TrySignHash(ReadOnlySpan hash, Span destination } } + 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); + } + } + public override bool VerifyHash( byte[] hash, byte[] signature, @@ -478,6 +491,8 @@ public override bool VerifyHash(ReadOnlySpan hash, ReadOnlySpan sign ArgumentException.ThrowIfNullOrEmpty(hashAlgorithm.Name, nameof(hashAlgorithm)); ArgumentNullException.ThrowIfNull(padding); + ValidatePaddingParameters(hashAlgorithm, padding); + ThrowIfDisposed(); Interop.AppleCrypto.PAL_HashAlgorithm palAlgId = PalAlgorithmFromAlgorithmName(hashAlgorithm, out _); @@ -533,7 +548,8 @@ public override bool VerifyHash(ReadOnlySpan hash, ReadOnlySpan sign } 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); @@ -830,20 +840,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..7801d143aaf644 --- /dev/null +++ b/src/libraries/Common/src/System/Security/Cryptography/RsaPaddingProcessor.DigestInfo.cs @@ -0,0 +1,147 @@ +// 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) + { + // Using rsaKeySizeInBits to align with EncodePss and DecodePss + int emLen = BytesRequiredForBitCount(rsaKeySizeInBits - 1); + int hLen = HashLength(hashAlgorithm); + return pssSaltLength switch + { + PssSaltLengthMax => Math.Max(0, emLen - hLen - 2), + PssSaltLengthIsHashLength => hLen, + _ => pssSaltLength + }; + } + + internal static int BytesRequiredForBitCount(int keySizeInBits) + { + return (int)(((uint)keySizeInBits + 7) >>> 3); + } + + 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 e40fcf54ac1c7c..1e2af9388be21a 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/RsaPaddingProcessor.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/RsaPaddingProcessor.cs @@ -8,113 +8,10 @@ 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 +277,11 @@ internal static unsafe void PadOaep( } } - internal static unsafe void EncodePss(HashAlgorithmName hashAlgorithmName, ReadOnlySpan mHash, Span destination, int keySize) + internal static unsafe void EncodePss(HashAlgorithmName hashAlgorithmName, ReadOnlySpan mHash, Span destination, int keySize, int saltLength) { + const int MaxStackSaltLength = 128; + + int sLen = saltLength; int hLen = HashLength(hashAlgorithmName); // https://tools.ietf.org/html/rfc3447#section-9.1.1 @@ -393,9 +293,6 @@ internal static unsafe void EncodePss(HashAlgorithmName hashAlgorithmName, ReadO 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. @@ -426,7 +323,10 @@ internal static unsafe void EncodePss(HashAlgorithmName hashAlgorithmName, ReadO 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 = sLen > MaxStackSaltLength + ? new byte[sLen] + : stackalloc byte[sLen]; RandomNumberGenerator.Fill(salt); // 5. Let M' = an octet string of 8 zeros concat mHash concat salt @@ -469,8 +369,9 @@ internal static unsafe void EncodePss(HashAlgorithmName hashAlgorithmName, ReadO CryptoPool.Return(dbMaskRented, clearSize: 0); } - internal static unsafe bool VerifyPss(HashAlgorithmName hashAlgorithmName, ReadOnlySpan mHash, ReadOnlySpan em, int keySize) + internal static unsafe bool VerifyPss(HashAlgorithmName hashAlgorithmName, ReadOnlySpan mHash, ReadOnlySpan em, int keySize, int saltLength) { + int sLen = saltLength; int hLen = HashLength(hashAlgorithmName); // https://tools.ietf.org/html/rfc3447#section-9.1.2 @@ -485,9 +386,6 @@ internal static unsafe bool VerifyPss(HashAlgorithmName hashAlgorithmName, ReadO 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) { 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 00bd7cf87ba497..8358ddb55e1b94 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 @@ -46,6 +46,7 @@ public abstract class SignVerify protected abstract RSAProvider RSAFactory { get; } public bool SupportsPss => RSAFactory.SupportsPss; + public bool AreCustomSaltLengthsSupportedWithPss => SupportsPss && PlatformSupport.AreCustomSaltLengthsSupportedWithPss; protected abstract byte[] SignData(RSA rsa, byte[] data, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding); protected abstract byte[] SignHash(RSA rsa, byte[] hash, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding); @@ -603,6 +604,158 @@ public void VerifySignature_SHA3_512_RSA2048() VerifySignature(signature, TestData.HelloBytes, HashAlgorithmName.SHA3_512.Name, TestData.RSA2048Params); } + [ConditionalTheory(nameof(AreCustomSaltLengthsSupportedWithPss))] + [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(AreCustomSaltLengthsSupportedWithPss))] + [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)) + { + bool signatureIsValid = VerifyData(rsa, data, signature, HashAlgorithmName.SHA256, padding); + if (validateWithCorrectSaltLength) + { + Assert.True(signatureIsValid); + } + else + { + Assert.False(signatureIsValid); + } + } + } + + [ConditionalTheory(nameof(SupportsPss))] + [InlineData("SHA256", 256 / 8)] + [InlineData("SHA384", 384 / 8)] + public void PssWithImplicitAndExplicitHashLength(string hashName, int hashLength) + { + byte[] data = TestData.HelloBytes; + HashAlgorithmName hashAlgorithm = new HashAlgorithmName(hashName); + RSASignaturePadding implicitPadding = RSASignaturePadding.Pss; + RSASignaturePadding explicitPadding = RSASignaturePadding.CreatePss(hashLength); + + using (RSA rsa = RSAFactory.Create(TestData.RSA2048Params)) + { + byte[] implicitSignature = SignData(rsa, data, hashAlgorithm, implicitPadding); + byte[] explicitSignature = SignData(rsa, data, hashAlgorithm, explicitPadding); + + AssertExtensions.TrueExpression(VerifyData(rsa, data, implicitSignature, hashAlgorithm, explicitPadding)); + AssertExtensions.TrueExpression(VerifyData(rsa, data, explicitSignature, hashAlgorithm, implicitPadding)); + + if (AreCustomSaltLengthsSupportedWithPss) + { + RSASignaturePadding minusOne = RSASignaturePadding.CreatePss(hashLength - 1); + RSASignaturePadding plusOne = RSASignaturePadding.CreatePss(hashLength + 1); + + AssertExtensions.FalseExpression(VerifyData(rsa, data, implicitSignature, hashAlgorithm, minusOne)); + AssertExtensions.FalseExpression(VerifyData(rsa, data, implicitSignature, hashAlgorithm, plusOne)); + } + } + } + + [ConditionalTheory(nameof(AreCustomSaltLengthsSupportedWithPss))] + [InlineData("SHA256", 222)] + [InlineData("SHA384", 206)] + public void PssWithMaxAndExplicitSaltLength(string hashName, int maxSaltLength) + { + byte[] data = TestData.HelloBytes; + HashAlgorithmName hashAlgorithm = new HashAlgorithmName(hashName); + RSASignaturePadding maxPadding = RSASignaturePadding.CreatePss(RSASignaturePadding.PssSaltLengthMax); + RSASignaturePadding explicitPadding = RSASignaturePadding.CreatePss(maxSaltLength); + + using (RSA rsa = RSAFactory.Create(TestData.RSA2048Params)) + { + byte[] maxSignature = SignData(rsa, data, hashAlgorithm, maxPadding); + byte[] explicitSignature = SignData(rsa, data, hashAlgorithm, explicitPadding); + + AssertExtensions.TrueExpression(VerifyData(rsa, data, maxSignature, hashAlgorithm, explicitPadding)); + AssertExtensions.TrueExpression(VerifyData(rsa, data, explicitSignature, hashAlgorithm, maxPadding)); + } + } + + [ConditionalTheory(nameof(AreCustomSaltLengthsSupportedWithPss))] + [InlineData("SHA256", 222)] + [InlineData("SHA384", 206)] + public void PssSaltLengthTooLarge(string hashName, int maxSaltLength) + { + byte[] data = TestData.HelloBytes; + HashAlgorithmName hashAlgorithm = new HashAlgorithmName(hashName); + RSASignaturePadding maxPadding = RSASignaturePadding.CreatePss(RSASignaturePadding.PssSaltLengthMax); + RSASignaturePadding tooLargePadding = RSASignaturePadding.CreatePss(maxSaltLength + 1); + + using (RSA rsa = RSAFactory.Create(TestData.RSA2048Params)) + { + byte[] maxSignature = SignData(rsa, data, hashAlgorithm, maxPadding); + + Assert.ThrowsAny(() => SignData(rsa, data, hashAlgorithm, tooLargePadding)); + AssertExtensions.FalseExpression(VerifyData(rsa, data, maxSignature, hashAlgorithm, tooLargePadding)); + } + } + [Theory] [MemberData(nameof(RoundTripTheories))] public void SignAndVerify_Roundtrip( @@ -987,8 +1140,8 @@ public void VerifyHashSignature_SHA256_2048() } [Theory] - [MemberData(nameof(HashAlgorithmNames))] - public void PssRoundtrip(string hashAlgorithmName, Func isSupported) + [MemberData(nameof(PssRoundTripParameters))] + public void PssRoundtrip(string hashAlgorithmName, Func isSupported, int? saltLength) { if (!isSupported(RSAFactory)) { @@ -1010,7 +1163,9 @@ public void PssRoundtrip(string hashAlgorithmName, Func isSup byte[] data = TestData.RsaBigExponentParams.Modulus; HashAlgorithmName hashAlgorithm = new HashAlgorithmName(hashAlgorithmName); - RSASignaturePadding padding = RSASignaturePadding.Pss; + RSASignaturePadding padding = saltLength is null ? + RSASignaturePadding.Pss : + RSASignaturePadding.CreatePss(saltLength.Value); if (RSAFactory.SupportsPss) { @@ -1299,6 +1454,24 @@ public void Pkcs1UnsupportedHashAlgorithm(string hashAlgorithm) } } + [ConditionalTheory] + [InlineData(0)] + [InlineData(5)] + [InlineData(RSASignaturePadding.PssSaltLengthMax)] + public void PssUnsupportedPssSaltLengthOnApple(int saltLength) + { + SkipTestException.ThrowUnless(PlatformDetection.IsApplePlatform); + + using (RSA rsa = RSAFactory.Create()) + { + Assert.ThrowsAny(() => + SignData(rsa, new byte[] { 1 }, HashAlgorithmName.SHA256, RSASignaturePadding.CreatePss(saltLength))); + + Assert.ThrowsAny(() => + VerifyData(rsa, new byte[] { 1 }, new byte[] { 1 }, HashAlgorithmName.SHA256, RSASignaturePadding.CreatePss(saltLength))); + } + } + [ConditionalTheory] [InlineData("SHA3-256")] [InlineData("SHA3-384")] @@ -1642,21 +1815,34 @@ private void SignAndVerify(byte[] data, string hashAlgorithmName, RSAParameters } } - public static IEnumerable HashAlgorithmNames + public static IEnumerable PssRoundTripParameters { get { - yield return new object[] { HashAlgorithmName.SHA256.Name, (RSAProvider _) => true }; - yield return new object[] { HashAlgorithmName.SHA384.Name, (RSAProvider _) => true }; - yield return new object[] { HashAlgorithmName.SHA512.Name, (RSAProvider _) => true }; - - yield return new object[] { HashAlgorithmName.MD5.Name, (RSAProvider p) => p.SupportsMd5Signatures }; + int?[] saltLengths = PlatformSupport.AreCustomSaltLengthsSupportedWithPss + ? [null, RSASignaturePadding.PssSaltLengthMax, RSASignaturePadding.PssSaltLengthIsHashLength, 0, 1, 4] + : [null]; - yield return new object[] { HashAlgorithmName.SHA1.Name, (RSAProvider p) => p.SupportsSha1Signatures }; + foreach (int? saltLength in saltLengths) + { + yield return new object[] { HashAlgorithmName.SHA256.Name, (RSAProvider _) => true, saltLength }; + yield return new object[] { HashAlgorithmName.SHA384.Name, (RSAProvider _) => true, saltLength }; + yield return new object[] { HashAlgorithmName.SHA512.Name, (RSAProvider _) => true, saltLength }; + yield return new object[] { HashAlgorithmName.MD5.Name, (RSAProvider p) => p.SupportsMd5Signatures, saltLength }; + yield return new object[] { HashAlgorithmName.SHA1.Name, (RSAProvider p) => p.SupportsSha1Signatures, saltLength }; + yield return new object[] { HashAlgorithmName.SHA3_256.Name, (RSAProvider p) => p.SupportsSha3, saltLength }; + yield return new object[] { HashAlgorithmName.SHA3_384.Name, (RSAProvider p) => p.SupportsSha3, saltLength }; + yield return new object[] { HashAlgorithmName.SHA3_512.Name, (RSAProvider p) => p.SupportsSha3, saltLength }; + } - yield return new object[] { HashAlgorithmName.SHA3_256.Name, (RSAProvider p) => p.SupportsSha3 }; - yield return new object[] { HashAlgorithmName.SHA3_384.Name, (RSAProvider p) => p.SupportsSha3 }; - yield return new object[] { HashAlgorithmName.SHA3_512.Name, (RSAProvider p) => p.SupportsSha3 }; + yield return new object[] { HashAlgorithmName.SHA256.Name, (RSAProvider _) => true, 32 }; + yield return new object[] { HashAlgorithmName.SHA384.Name, (RSAProvider _) => true, 48 }; + yield return new object[] { HashAlgorithmName.SHA512.Name, (RSAProvider _) => true, 64 }; + yield return new object[] { HashAlgorithmName.MD5.Name, (RSAProvider p) => p.SupportsMd5Signatures, 16 }; + yield return new object[] { HashAlgorithmName.SHA1.Name, (RSAProvider p) => p.SupportsSha1Signatures, 20 }; + yield return new object[] { HashAlgorithmName.SHA3_256.Name, (RSAProvider p) => p.SupportsSha3, 32 }; + yield return new object[] { HashAlgorithmName.SHA3_384.Name, (RSAProvider p) => p.SupportsSha3, 48 }; + yield return new object[] { HashAlgorithmName.SHA3_512.Name, (RSAProvider p) => p.SupportsSha3, 64 }; } } } diff --git a/src/libraries/Common/tests/System/Security/Cryptography/PlatformSupport.cs b/src/libraries/Common/tests/System/Security/Cryptography/PlatformSupport.cs index b93baf7587d9a8..9f51c0247a7be5 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/PlatformSupport.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/PlatformSupport.cs @@ -247,6 +247,12 @@ private static bool CheckIfRsaPssSupported() /// internal static bool IsRsaPssSupported => s_isRsaPssSupported ??= CheckIfRsaPssSupported(); +#if NET11_0_OR_GREATER + internal static bool AreCustomSaltLengthsSupportedWithPss => IsRsaPssSupported && !PlatformDetection.IsApplePlatform; +#else + internal static bool AreCustomSaltLengthsSupportedWithPss => false; +#endif + internal static bool IsPqcMLKemX509Supported { get 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 c175c410cb8505..088d17a8af8dcc 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. @@ -240,4 +243,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..e48022423d1057 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,23 @@ private static CoseAlgorithm GetRSAAlgorithm(RSASignaturePadding signaturePaddin { Debug.Assert(signaturePadding != null); - if (signaturePadding == RSASignaturePadding.Pss) + if (signaturePadding.Mode == RSASignaturePaddingMode.Pss) { +#if NET11_0_OR_GREATER + int hashLength = hashAlgorithm.Name switch + { + nameof(HashAlgorithmName.SHA256) => SHA256.HashSizeInBytes, + nameof(HashAlgorithmName.SHA384) => SHA384.HashSizeInBytes, + nameof(HashAlgorithmName.SHA512) => SHA512.HashSizeInBytes, + _ => 0, + }; + + if (signaturePadding.PssSaltLength != RSASignaturePadding.PssSaltLengthIsHashLength && + signaturePadding.PssSaltLength != hashLength) + { + throw new ArgumentException(SR.CoseSignerPssSaltLengthMustBeHashLength, nameof(signaturePadding)); + } +#endif return hashAlgorithm.Name switch { nameof(HashAlgorithmName.SHA256) => CoseAlgorithm.PS256, diff --git a/src/libraries/System.Security.Cryptography.Cose/tests/CoseKeyTests.ArgumentValidation.cs b/src/libraries/System.Security.Cryptography.Cose/tests/CoseKeyTests.ArgumentValidation.cs index b54320dd6104cd..a9275bb6ea127d 100644 --- a/src/libraries/System.Security.Cryptography.Cose/tests/CoseKeyTests.ArgumentValidation.cs +++ b/src/libraries/System.Security.Cryptography.Cose/tests/CoseKeyTests.ArgumentValidation.cs @@ -27,6 +27,37 @@ public void CreateCoseKeyWithNullArg_Throws() AssertExtensions.Throws("hashAlgorithm.Name", () => new CoseKey(CoseTestHelpers.ES256, default)); } +#if NET11_0_OR_GREATER + [Theory] + [InlineData("SHA256", 32)] + [InlineData("SHA384", 48)] + [InlineData("SHA512", 64)] + public void CreateCoseKeyWithExplicitPssHashLength(string hashAlgorithmName, int saltLength) + { + CoseKey key = new CoseKey( + CoseTestHelpers.RSAKey, + RSASignaturePadding.CreatePss(saltLength), + new HashAlgorithmName(hashAlgorithmName)); + + Assert.NotNull(key); + } + + [Theory] + [InlineData("SHA256", 31)] + [InlineData("SHA256", 33)] + [InlineData("SHA384", 32)] + [InlineData("SHA512", RSASignaturePadding.PssSaltLengthMax)] + public void CreateCoseKeyWithInvalidPssSaltLength(string hashAlgorithmName, int saltLength) + { + AssertExtensions.Throws( + "signaturePadding", + () => new CoseKey( + CoseTestHelpers.RSAKey, + RSASignaturePadding.CreatePss(saltLength), + new HashAlgorithmName(hashAlgorithmName))); + } +#endif + [Fact] public void CreateCoseSignerWithNullCoseKey_Throws() { diff --git a/src/libraries/System.Security.Cryptography.Cose/tests/CoseSignerTests.cs b/src/libraries/System.Security.Cryptography.Cose/tests/CoseSignerTests.cs index 977a316031a13a..1b5f9b9d77f8a0 100644 --- a/src/libraries/System.Security.Cryptography.Cose/tests/CoseSignerTests.cs +++ b/src/libraries/System.Security.Cryptography.Cose/tests/CoseSignerTests.cs @@ -77,5 +77,39 @@ public void CoseSigner_NullSignaturePadding() { Assert.Throws("signaturePadding", () => new CoseSigner(RSA.Create(), null!, HashAlgorithmName.SHA256)); } + +#if NET11_0_OR_GREATER + [Theory] + [InlineData("SHA256", 32)] + [InlineData("SHA384", 48)] + [InlineData("SHA512", 64)] + public void CoseSigner_PssPaddingWithExplicitHashLength(string hashAlgorithmName, int saltLength) + { + CoseSigner signer = new CoseSigner( + RSA.Create(), + RSASignaturePadding.CreatePss(saltLength), + new HashAlgorithmName(hashAlgorithmName)); + + RSASignaturePadding padding = Assert.IsType(signer.RSASignaturePadding); + Assert.Equal(saltLength, padding.PssSaltLength); + } + + [Theory] + [InlineData("SHA256", 0)] + [InlineData("SHA256", 17)] + [InlineData("SHA256", 31)] + [InlineData("SHA256", 33)] + [InlineData("SHA256", RSASignaturePadding.PssSaltLengthMax)] + [InlineData("SHA384", 32)] + public void CoseSigner_PssPaddingWithInvalidSaltLength(string hashAlgorithmName, int saltLength) + { + Assert.Throws( + "signaturePadding", + () => new CoseSigner( + RSA.Create(), + RSASignaturePadding.CreatePss(saltLength), + new HashAlgorithmName(hashAlgorithmName))); + } +#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 a6d789e4ff0fc3..87ccfbb91c2e22 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 @@ -724,6 +724,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 e6788d5e22cd5c..0a1e9efd1108ec 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 @@ -98,7 +98,7 @@ protected abstract RSASignaturePadding GetSignaturePadding( HashAlgorithmName digestAlgorithmName, int digestValueLength); - private protected static bool SignCore( + private protected bool SignCore( #if NET || NETSTANDARD2_1 ReadOnlySpan dataHash, #else @@ -109,13 +109,15 @@ private protected static bool SignCore( object? key, bool silent, RSASignaturePadding signaturePadding, - [NotNullWhen(true)] out byte[]? signatureValue) + [NotNullWhen(true)] out byte[]? signatureValue, + out byte[]? signatureParameters) { using (GetSigningKey(key, certificate, silent, RSACertificateExtensions.GetRSAPublicKey, out RSA? privateKey)) { if (privateKey is null) { signatureValue = null; + signatureParameters = null; return false; } @@ -141,12 +143,20 @@ private protected static bool SignCore( { // key did not match certificate signatureValue = null; + signatureParameters = null; return false; } } } - return true; + if (GetSignatureParameters(privateKey, hashAlgorithmName, out signatureParameters)) + { + return true; + } + + signatureValue = null; + signatureParameters = null; + return false; } #endif signatureValue = privateKey.SignHash( @@ -166,14 +176,24 @@ private protected static bool SignCore( { // key did not match certificate signatureValue = null; + signatureParameters = null; return false; } } } - return true; + if (GetSignatureParameters(privateKey, hashAlgorithmName, out signatureParameters)) + { + return true; + } + + signatureValue = null; + signatureParameters = null; + return false; } } + + protected abstract bool GetSignatureParameters(RSA publicKey, HashAlgorithmName hashAlgorithmName, out byte[]? signatureParameters); } private sealed class RSAPkcs1CmsSignature : RSACmsSignature @@ -206,6 +226,14 @@ protected override RSASignaturePadding GetSignaturePadding( throw new CryptographicException(SR.Cryptography_Pkcs_InvalidSignatureParameters); } + protected override bool GetSignatureParameters(RSA publicKey, + HashAlgorithmName hashAlgorithmName, + out byte[]? signatureParameters) + { + signatureParameters = null; + return true; + } + protected override bool Sign( #if NET || NETSTANDARD2_1 ReadOnlySpan dataHash, @@ -227,10 +255,10 @@ protected override bool Sign( key, silent, RSASignaturePadding.Pkcs1, - out signatureValue); + out signatureValue, + out signatureParameters); signatureAlgorithm = result ? Oids.Rsa : null; - signatureParameters = null; return result; } } @@ -294,12 +322,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 NET11_0_OR_GREATER + public RSAPssCmsSignature(int saltLength) : base(null, null) + { + SignaturePadding = RSASignaturePadding.CreatePss(saltLength); + } +#endif protected override RSASignaturePadding GetSignaturePadding( ReadOnlyMemory? signatureParameters, string? digestAlgorithmOid, @@ -351,38 +386,13 @@ protected override bool Sign( certificate, key, silent, - RSASignaturePadding.Pss, - out signatureValue); + SignaturePadding, + out signatureValue, + out signatureParameters); if (result) { signatureAlgorithm = Oids.RsaPss; - - if (hashAlgorithmName == HashAlgorithmName.SHA1) - { - signatureParameters = s_rsaPssSha1Parameters; - } - else if (hashAlgorithmName == HashAlgorithmName.SHA256) - { - signatureParameters = s_rsaPssSha256Parameters; - } - else if (hashAlgorithmName == HashAlgorithmName.SHA384) - { - signatureParameters = s_rsaPssSha384Parameters; - } - else if (hashAlgorithmName == HashAlgorithmName.SHA512) - { - signatureParameters = s_rsaPssSha512Parameters; - } - else - { - // The only hash algorithm we don't support is MD5. - // We shouldn't get here with anything other than MD5. - Debug.Assert(hashAlgorithmName == HashAlgorithmName.MD5, $"Unsupported digest algorithm '{hashAlgorithmName.Name}'"); - signatureAlgorithm = null; - signatureParameters = null; - return false; - } } else { @@ -392,6 +402,81 @@ protected override bool Sign( return result; } + + protected override bool GetSignatureParameters(RSA publicKey, + HashAlgorithmName hashAlgorithmName, + out byte[]? signatureParameters) + { + if (hashAlgorithmName == HashAlgorithmName.MD5) + { + signatureParameters = null; + return false; + } + +#if NET11_0_OR_GREATER + if (SignaturePadding.PssSaltLength != RSASignaturePadding.PssSaltLengthIsHashLength) + { + signatureParameters = GetSignaturePaddingForCustomPssSaltLength(publicKey, hashAlgorithmName); + return true; + } +#endif + if (hashAlgorithmName == HashAlgorithmName.SHA1) + { + signatureParameters = s_rsaPssSha1Parameters; + } + else if (hashAlgorithmName == HashAlgorithmName.SHA256) + { + signatureParameters = s_rsaPssSha256Parameters; + } + else if (hashAlgorithmName == HashAlgorithmName.SHA384) + { + signatureParameters = s_rsaPssSha384Parameters; + } + else if (hashAlgorithmName == HashAlgorithmName.SHA512) + { + signatureParameters = s_rsaPssSha512Parameters; + } + else + { + // The only hash algorithm we don't support is MD5. + // We shouldn't get here with anything other than MD5. + Debug.Assert(hashAlgorithmName == HashAlgorithmName.MD5, $"Unsupported digest algorithm '{hashAlgorithmName.Name}'"); + signatureParameters = null; + return false; + } + + return true; + } + +#if NET11_0_OR_GREATER + private byte[] GetSignaturePaddingForCustomPssSaltLength(RSA publicKey, HashAlgorithmName hashAlgorithmName) + { + string digestOid = PkcsHelpers.GetOidFromHashAlgorithm(hashAlgorithmName); + Debug.Assert(publicKey != null, "Expected a public key to be present for PSS parameters."); + + ValuePssParamsAsn parameters = new ValuePssParamsAsn + { + HashAlgorithm = new ValueAlgorithmIdentifierAsn { Algorithm = digestOid }, + MaskGenAlgorithm = new ValueAlgorithmIdentifierAsn { 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 78f2da64235fad..2f94fa162555fc 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 NET11_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 947e3698f4d739..eb1d8d797ea9e2 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 ce691f3db0725c..f72a943f28bd36 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 @@ -14,6 +14,8 @@ namespace System.Security.Cryptography.Pkcs.Tests { public static partial class SignedCmsTests { + private static bool AreCustomPssSaltLengthsSupported => PlatformSupport.AreCustomSaltLengthsSupportedWithPss; + [Fact] public static void CmsSignerKeyIsNullByDefault() { @@ -684,15 +686,8 @@ 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) + [MemberData(nameof(CreateSignature_RsaPssTests))] + 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 +699,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); @@ -733,6 +728,14 @@ public static void CreateSignature_RsaPss(string digestOid, bool assignByConstru } } + public static IEnumerable CreateSignature_RsaPssTests() => + from oid in (string[])([Oids.Sha256, Oids.Sha384, Oids.Sha512, Oids.Sha1]) + from byCtor in new[] { true, false } + from saltLength in AreCustomPssSaltLengthsSupported + ? new[] { 0, 63, RSASignaturePadding.PssSaltLengthMax, RSASignaturePadding.PssSaltLengthIsHashLength } + : new[] { RSASignaturePadding.PssSaltLengthIsHashLength } + select new object[] { oid, byCtor, saltLength }; + [Fact] public static void CreateSignature_RsaPss_SeparateKey() { 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 b4c8e396e4ae1d..557843071931d5 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,28 @@ 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)); + if (PlatformSupport.AreCustomSaltLengthsSupportedWithPss) + { + signer.CheckSignature(true); + } + else + { + Assert.Throws(() => 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)); + if (PlatformSupport.AreCustomSaltLengthsSupportedWithPss) + { + cms.CheckSignature(true); + } + else + { + // Since at least one signer fails, the document signature will fail + Assert.Throws(() => cms.CheckSignature(true)); + } } [Fact] 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 0c3f2471e07ca7..8d21c57f450bbf 100644 --- a/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs +++ b/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs @@ -2934,10 +2934,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 5594937b25238c..c4f31d1d67947b 100644 --- a/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx +++ b/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx @@ -387,6 +387,9 @@ The specified curve '{0}' or its parameters are not valid for this platform. + + Custom PSS salt lengths are not supported on this platform. + Custom trust certificates were provided while in System trust mode. @@ -549,6 +552,9 @@ Specified padding mode is not valid for this algorithm. + + Specified PSS 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 cb2e45e1488afc..08374f34d6119e 100644 --- a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj +++ b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj @@ -458,8 +458,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..5ef82f8b2d7eda 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 { + /// + /// A constant value indicating that the PSS 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; + + /// + /// A constant value indicating that the PSS salt length should be the maximum allowable + /// + /// 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 and the size of the RSA key used. + 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 non-negative 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 has a negative value other than 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 d34d4d248915e1..ae86b745a01723 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,36 +18,27 @@ 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; } 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) - { - throw new CryptographicException(SR.Cryptography_InvalidPaddingMode); - } + Debug.Assert(_padding.Mode == RSASignaturePaddingMode.Pss); int cbSalt; string digestOid; 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 +49,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). ValuePssParamsAsn parameters = new ValuePssParamsAsn { diff --git a/src/libraries/System.Security.Cryptography/tests/RSATests.cs b/src/libraries/System.Security.Cryptography/tests/RSATests.cs index 404a94a77da9ee..0f251b1abc05f1 100644 --- a/src/libraries/System.Security.Cryptography/tests/RSATests.cs +++ b/src/libraries/System.Security.Cryptography/tests/RSATests.cs @@ -226,6 +226,32 @@ public static 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(0)] + [InlineData(2)] + [InlineData(RSASignaturePadding.PssSaltLengthIsHashLength)] + [InlineData(RSASignaturePadding.PssSaltLengthMax)] + public static 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)] + public static 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/RsaCngTests.cs b/src/libraries/System.Security.Cryptography/tests/RsaCngTests.cs index 1b3d4f244e047e..56455bd5ce2672 100644 --- a/src/libraries/System.Security.Cryptography/tests/RsaCngTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/RsaCngTests.cs @@ -16,12 +16,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/tests/X509Certificates/CertificateCreation/CertificateRequestChainTests.cs b/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/CertificateRequestChainTests.cs index d5587ae54ca040..ae9114c5f81e47 100644 --- a/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/CertificateRequestChainTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/CertificateRequestChainTests.cs @@ -13,6 +13,7 @@ public static class CertificateRequestChainTests // Android supports PSS at the algorithms layer, but does not support it // being used in cert chains. public static bool PlatformSupportsPss { get; } = !PlatformDetection.IsAndroid && PlatformSupport.IsRsaPssSupported; + public static bool AreCustomSaltLengthsSupportedWithPss { get; } = PlatformSupportsPss && PlatformSupport.AreCustomSaltLengthsSupportedWithPss; [Fact] public static void CreateChain_ECC() @@ -489,8 +490,22 @@ private static void CreateAndTestChain( } } - [ConditionalFact(typeof(CertificateRequestChainTests), nameof(PlatformSupportsPss))] - public static void CreateChain_RSAPSS() + [ConditionalTheory(nameof(AreCustomSaltLengthsSupportedWithPss))] + [InlineData(1)] + [InlineData(RSASignaturePadding.PssSaltLengthMax)] + [InlineData(RSASignaturePadding.PssSaltLengthIsHashLength)] + public static void CreateChain_RSAPSS(int saltLength) + { + CreateChain_RSAPSSCore(saltLength); + } + + [ConditionalFact(nameof(PlatformSupportsPss))] + public static void CreateChain_RSAPSS_SaltLengthIsHashLength() + { + CreateChain_RSAPSSCore(RSASignaturePadding.PssSaltLengthIsHashLength); + } + + private static void CreateChain_RSAPSSCore(int saltLength) { using (RSA rootKey = RSA.Create()) using (RSA intermedKey = RSA.Create()) @@ -501,7 +516,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 241746e316099b..5b7a14f3c659d6 100644 --- a/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/CertificateRequestLoadTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/CertificateRequestLoadTests.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.Collections.Generic; using System.Formats.Asn1; using System.Net; using Test.Cryptography; @@ -374,22 +375,46 @@ public static void VerifySignature_RSA_PKCS1(string hashAlgorithm) } } + public static IEnumerable AllRsaPssCustomSaltLength() + { + foreach (string hashAlgorithm in new[] { "SHA256", "SHA384", "SHA512", "SHA1" }) + { + foreach (int saltLength in new[] { 0, 1, RSASignaturePadding.PssSaltLengthMax, RSASignaturePadding.PssSaltLengthIsHashLength }) + { + yield return new object[] { hashAlgorithm, saltLength }; + } + } + } + + [ConditionalTheory(typeof(PlatformSupport), nameof(PlatformSupport.AreCustomSaltLengthsSupportedWithPss))] + [MemberData(nameof(AllRsaPssCustomSaltLength))] + public static void VerifySignature_RSA_PSS_CustomSaltLength(string hashAlgorithm, int saltLength) + { + VerifySignature_RSA_PSSCore(hashAlgorithm, saltLength); + } + [Theory] [InlineData("SHA256")] [InlineData("SHA384")] [InlineData("SHA512")] [InlineData("SHA1")] - public static void VerifySignature_RSA_PSS(string hashAlgorithm) + public static void VerifySignature_RSA_PSS_SaltLengthIsHashLength(string hashAlgorithm) + { + VerifySignature_RSA_PSSCore(hashAlgorithm, RSASignaturePadding.PssSaltLengthIsHashLength); + } + + private static void VerifySignature_RSA_PSSCore(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 +422,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; } } @@ -824,8 +849,22 @@ public static void LoadCreate_MatchesCreate_RSAPkcs1() } } + [ConditionalTheory(typeof(PlatformSupport), nameof(PlatformSupport.AreCustomSaltLengthsSupportedWithPss))] + [InlineData(0)] + [InlineData(4)] + [InlineData(RSASignaturePadding.PssSaltLengthMax)] + public static void LoadCreate_MatchesCreate_RSAPss_CustomSaltLength(int saltLength) + { + LoadCreate_MatchesCreate_RSAPssCore(saltLength); + } + [Fact] - public static void LoadCreate_MatchesCreate_RSAPss() + public static void LoadCreate_MatchesCreate_RSAPss_SaltLengthIsHashLength() + { + LoadCreate_MatchesCreate_RSAPssCore(RSASignaturePadding.PssSaltLengthIsHashLength); + } + + private static void LoadCreate_MatchesCreate_RSAPssCore(int saltLength) { using (RSA key = RSA.Create(2048)) { @@ -834,9 +873,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..6872ec203d83ec 100644 --- a/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/CertificateRequestUsageTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/CertificateRequestUsageTests.cs @@ -1,11 +1,13 @@ // 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 System.Globalization; -using RsaTestData = System.Security.Cryptography.Rsa.Tests.TestData; using Test.Cryptography; using Xunit; +using RsaTestData = System.Security.Cryptography.Rsa.Tests.TestData; + namespace System.Security.Cryptography.X509Certificates.Tests.CertificateCreation { [SkipOnPlatform(TestPlatforms.Browser, "Browser doesn't support X.509 certificates")] @@ -306,6 +308,34 @@ public static void SelfSign_ECC_DiminishedPoint_UseCertKeys() } } + [ConditionalTheory(typeof(PlatformSupport), nameof(PlatformSupport.AreCustomSaltLengthsSupportedWithPss))] + [InlineData(0)] + [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 _); + + 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 f6299dfe02004a..806a74aa2e49cd 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, } @@ -37,6 +39,12 @@ public static IEnumerable SupportedCertKinds() yield return new object[] { CertKind.RsaPkcs1 }; yield return new object[] { CertKind.RsaPss }; + if (PlatformSupport.AreCustomSaltLengthsSupportedWithPss) + { + yield return new object[] { CertKind.RsaPssWithCustomSaltLength }; + yield return new object[] { CertKind.RsaPssWithMaxSaltLength }; + } + if (SlhDsa.IsSupported) { yield return new object[] { CertKind.SlhDsa }; @@ -1557,6 +1565,18 @@ private static void BuildCertificateAndRun( key = rsa; req = new CertificateRequest(subjectName, rsa, HashAlgorithmName.SHA384, GetRsaPadding(certKind)); } + else if (certKind == CertKind.RsaPssWithMaxSaltLength) + { + RSA rsa = RSA.Create(); + key = rsa; + req = new CertificateRequest(subjectName, rsa, HashAlgorithmName.SHA384, RSASignaturePadding.CreatePss(RSASignaturePadding.PssSaltLengthMax)); + } + else if (certKind == CertKind.RsaPssWithCustomSaltLength) + { + RSA 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); @@ -1715,7 +1735,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; @@ -1754,7 +1774,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)); @@ -1789,7 +1809,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()) }; @@ -1801,6 +1821,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..c96f5300ea39dd 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; @@ -9,6 +10,12 @@ namespace System.Security.Cryptography.X509Certificates.Tests.CertificateCreatio [SkipOnPlatform(TestPlatforms.Browser, "Browser doesn't support asymmetric cryptography")] public static class RSAPssX509SignatureGeneratorTests { + private const string Mgf1Oid = "1.2.840.113549.1.1.8"; + private const string RsaPssOid = "1.2.840.113549.1.1.10"; + private const string Sha256Oid = "2.16.840.1.101.3.4.2.1"; + private const string Sha384Oid = "2.16.840.1.101.3.4.2.2"; + private const string Sha512Oid = "2.16.840.1.101.3.4.2.3"; + [Fact] public static void RsaPssSignatureGeneratorCtor_Exceptions() { @@ -56,6 +63,73 @@ public static void PublicKeyEncoding() } } + [ConditionalTheory(typeof(PlatformSupport), nameof(PlatformSupport.AreCustomSaltLengthsSupportedWithPss))] + [InlineData("SHA256", Sha256Oid, 32, 222, 0)] + [InlineData("SHA256", Sha256Oid, 32, 222, 1)] + [InlineData("SHA256", Sha256Oid, 32, 222, RSASignaturePadding.PssSaltLengthIsHashLength)] + [InlineData("SHA256", Sha256Oid, 32, 222, RSASignaturePadding.PssSaltLengthMax)] + [InlineData("SHA384", Sha384Oid, 48, 206, RSASignaturePadding.PssSaltLengthIsHashLength)] + [InlineData("SHA384", Sha384Oid, 48, 206, RSASignaturePadding.PssSaltLengthMax)] + [InlineData("SHA512", Sha512Oid, 64, 190, RSASignaturePadding.PssSaltLengthIsHashLength)] + [InlineData("SHA512", Sha512Oid, 64, 190, RSASignaturePadding.PssSaltLengthMax)] + public static void PssPaddingSaltLengths( + string hashAlgorithmName, + string hashAlgorithmOid, + int hashLength, + int maxSaltLength, + int saltLengthToTest) + { + using (RSA rsa = RSA.Create()) + { + rsa.ImportFromPem(TestData.RsaPkcs8Key); + + HashAlgorithmName hashAlgorithm = new HashAlgorithmName(hashAlgorithmName); + 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, hashAlgorithm); + byte[] signatureAlgorithm = signatureGenerator.GetSignatureAlgorithmIdentifier(hashAlgorithm); + + AsnReader asnReader = new AsnReader(signatureAlgorithm, AsnEncodingRules.DER); + AsnReader rootSequence = asnReader.ReadSequence(); + Assert.Equal(RsaPssOid, rootSequence.ReadObjectIdentifier()); + AsnReader pssStructure = rootSequence.ReadSequence(); + + AsnReader hashAlgorithmWrapper = pssStructure.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, 0, true)); + AsnReader hashAlgorithmIdentifier = hashAlgorithmWrapper.ReadSequence(); + Assert.Equal(hashAlgorithmOid, hashAlgorithmIdentifier.ReadObjectIdentifier()); + hashAlgorithmIdentifier.ThrowIfNotEmpty(); + hashAlgorithmWrapper.ThrowIfNotEmpty(); + + AsnReader mgfWrapper = pssStructure.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, 1, true)); + AsnReader mgfIdentifier = mgfWrapper.ReadSequence(); + Assert.Equal(Mgf1Oid, mgfIdentifier.ReadObjectIdentifier()); + AsnReader mgfHashIdentifier = mgfIdentifier.ReadSequence(); + Assert.Equal(hashAlgorithmOid, mgfHashIdentifier.ReadObjectIdentifier()); + mgfHashIdentifier.ThrowIfNotEmpty(); + mgfIdentifier.ThrowIfNotEmpty(); + mgfWrapper.ThrowIfNotEmpty(); + + Asn1Tag saltTag = new Asn1Tag(TagClass.ContextSpecific, 2, true); + AsnReader saltEntry = pssStructure.ReadSequence(saltTag); + Numerics.BigInteger actualSaltLength = saltEntry.ReadInteger(); + int expectedSaltLength = saltLengthToTest switch + { + RSASignaturePadding.PssSaltLengthIsHashLength => hashLength, + RSASignaturePadding.PssSaltLengthMax => maxSaltLength, + _ => saltLengthToTest, + }; + Assert.Equal(expectedSaltLength, actualSaltLength); + saltEntry.ThrowIfNotEmpty(); + pssStructure.ThrowIfNotEmpty(); + rootSequence.ThrowIfNotEmpty(); + asnReader.ThrowIfNotEmpty(); + + Assert.True(rsa.VerifyData(data, signature, hashAlgorithm, 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..f49cfc6dc760c3 100644 --- a/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/X509Sha1SignatureGenerators.cs +++ b/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/X509Sha1SignatureGenerators.cs @@ -53,11 +53,13 @@ public override byte[] SignData(byte[] data, HashAlgorithmName hashAlgorithm) => internal sealed class RSASha1PssSignatureGenerator : X509SignatureGenerator { + private readonly RSASignaturePadding _signaturePadding; private readonly X509SignatureGenerator _realRsaGenerator; - internal RSASha1PssSignatureGenerator(RSA rsa) + internal RSASha1PssSignatureGenerator(RSA rsa, RSASignaturePadding signaturePadding) { - _realRsaGenerator = CreateForRSA(rsa, RSASignaturePadding.Pss); + _signaturePadding = signaturePadding; + _realRsaGenerator = CreateForRSA(rsa, signaturePadding); } protected override PublicKey BuildPublicKey() => _realRsaGenerator.PublicKey; @@ -65,8 +67,27 @@ internal RSASha1PssSignatureGenerator(RSA rsa) public override byte[] GetSignatureAlgorithmIdentifier(HashAlgorithmName hashAlgorithm) { 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(); + } + + } throw new InvalidOperationException(); } 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 a1fd694a9683ff..cc96964adb5bc9 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 @@ -245,7 +245,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 @@ -268,7 +268,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; } @@ -280,6 +280,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, @@ -304,7 +305,7 @@ int32_t CryptoNative_RsaSignHash(EVP_PKEY* pkey, goto done; } - if (!ConfigureSignature(ctx, padding, digest)) + if (!ConfigureSignature(ctx, padding, pssSaltLength, digest)) { goto done; } @@ -343,6 +344,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, @@ -365,7 +367,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,