From 16f162f76cdbdd150487eb9824f9d8f8e39df5ca Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Wed, 24 Mar 2021 10:27:42 -0700 Subject: [PATCH 02/11] Use EVP_PKEY for RSA Decrypt --- .../Interop.EVP.DigestAlgs.cs | 58 +++++ .../Interop.EVP.cs | 18 +- .../Interop.EvpPkey.Rsa.cs | 37 +++ .../Interop.EvpPkey.cs | 3 + .../Interop.Rsa.cs | 16 -- .../Security/Cryptography/RSAOpenSsl.cs | 222 ++++++++++-------- .../apibridge.c | 17 ++ .../apibridge.h | 1 + .../opensslshim.h | 17 +- .../pal_evp_pkey.c | 7 + .../pal_evp_pkey.h | 5 + .../pal_evp_pkey_rsa.c | 137 ++++++++++- .../pal_evp_pkey_rsa.h | 27 ++- .../pal_rsa.c | 13 - .../pal_rsa.h | 8 - .../HashProviderDispenser.Unix.cs | 36 +-- ...em.Security.Cryptography.Algorithms.csproj | 3 + ...ystem.Security.Cryptography.OpenSsl.csproj | 3 + 18 files changed, 444 insertions(+), 184 deletions(-) create mode 100644 src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.DigestAlgs.cs diff --git a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.DigestAlgs.cs b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.DigestAlgs.cs new file mode 100644 index 0000000000..53ef644d84 --- /dev/null +++ b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.DigestAlgs.cs @@ -0,0 +1,58 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; +using System.Security.Cryptography; + +internal static partial class Interop +{ + internal static partial class Crypto + { + private static volatile IntPtr s_evpMd5; + private static volatile IntPtr s_evpSha1; + private static volatile IntPtr s_evpSha256; + private static volatile IntPtr s_evpSha384; + private static volatile IntPtr s_evpSha512; + + [DllImport(Libraries.CryptoNative)] + private static extern IntPtr CryptoNative_EvpMd5(); + + internal static IntPtr EvpMd5() => + s_evpMd5 != IntPtr.Zero ? s_evpMd5 : (s_evpMd5 = CryptoNative_EvpMd5()); + + [DllImport(Libraries.CryptoNative)] + internal static extern IntPtr CryptoNative_EvpSha1(); + + internal static IntPtr EvpSha1() => + s_evpSha1 != IntPtr.Zero ? s_evpSha1 : (s_evpSha1 = CryptoNative_EvpSha1()); + + [DllImport(Libraries.CryptoNative)] + internal static extern IntPtr CryptoNative_EvpSha256(); + + internal static IntPtr EvpSha256() => + s_evpSha256 != IntPtr.Zero ? s_evpSha256 : (s_evpSha256 = CryptoNative_EvpSha256()); + + [DllImport(Libraries.CryptoNative)] + internal static extern IntPtr CryptoNative_EvpSha384(); + + internal static IntPtr EvpSha384() => + s_evpSha384 != IntPtr.Zero ? s_evpSha384 : (s_evpSha384 = CryptoNative_EvpSha384()); + + [DllImport(Libraries.CryptoNative)] + internal static extern IntPtr CryptoNative_EvpSha512(); + + internal static IntPtr EvpSha512() => + s_evpSha512 != IntPtr.Zero ? s_evpSha512 : (s_evpSha512 = CryptoNative_EvpSha512()); + + internal static IntPtr HashAlgorithmToEvp(string hashAlgorithmId) => hashAlgorithmId switch + { + nameof(HashAlgorithmName.SHA1) => EvpSha1(), + nameof(HashAlgorithmName.SHA256) => EvpSha256(), + nameof(HashAlgorithmName.SHA384) => EvpSha384(), + nameof(HashAlgorithmName.SHA512) => EvpSha512(), + nameof(HashAlgorithmName.MD5) => EvpMd5(), + _ => throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithmId)) + }; + } +} diff --git a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.cs b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.cs index 67a9b54230..ea9dc5186d 100644 --- a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.cs +++ b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.cs @@ -4,6 +4,7 @@ using System; using System.Runtime.InteropServices; +using System.Security.Cryptography; using Microsoft.Win32.SafeHandles; internal static partial class Interop @@ -31,23 +32,6 @@ internal static partial class Crypto [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpMdSize")] internal extern static int EvpMdSize(IntPtr md); - - [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpMd5")] - internal extern static IntPtr EvpMd5(); - - [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpSha1")] - internal extern static IntPtr EvpSha1(); - - [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpSha256")] - internal extern static IntPtr EvpSha256(); - - [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpSha384")] - internal extern static IntPtr EvpSha384(); - - [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpSha512")] - internal extern static IntPtr EvpSha512(); - - [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_GetMaxMdSize")] private extern static int GetMaxMdSize(); diff --git a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Rsa.cs b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Rsa.cs index c28522784b..f023ced7f9 100644 --- a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Rsa.cs +++ b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Rsa.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; +using System.Diagnostics; using System.Runtime.InteropServices; using System.Security.Cryptography; using Microsoft.Win32.SafeHandles; @@ -26,6 +28,41 @@ internal static SafeEvpPKeyHandle RsaGenerateKey(int keySize) return pkey; } + [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_RsaDecrypt")] + private static extern int CryptoNative_RsaDecrypt( + SafeEvpPKeyHandle pkey, + ref byte source, + int sourceLength, + RSAEncryptionPaddingMode paddingMode, + IntPtr digestAlgorithm, + ref byte destination, + int destinationLength); + + internal static int RsaDecrypt( + SafeEvpPKeyHandle pkey, + ReadOnlySpan source, + RSAEncryptionPaddingMode paddingMode, + IntPtr digestAlgorithm, + Span destination) + { + int written = CryptoNative_RsaDecrypt( + pkey, + ref MemoryMarshal.GetReference(source), + source.Length, + paddingMode, + digestAlgorithm, + ref MemoryMarshal.GetReference(destination), + destination.Length); + + if (written < 0) + { + Debug.Assert(written == -1); + throw CreateOpenSslCryptographicException(); + } + + return written; + } + [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPkeyGetRsa")] internal static extern SafeRsaHandle EvpPkeyGetRsa(SafeEvpPKeyHandle pkey); diff --git a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs index 2eff6bfcd1..3ad4bc9d08 100644 --- a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs +++ b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs @@ -16,6 +16,9 @@ internal static partial class Crypto [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPkeyDestroy")] internal static extern void EvpPkeyDestroy(IntPtr pkey); + [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeySize")] + internal static extern int EvpPKeySize(SafeEvpPKeyHandle pkey); + [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_UpRefEvpPkey")] internal static extern int UpRefEvpPkey(SafeEvpPKeyHandle handle); } diff --git a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Rsa.cs b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Rsa.cs index a05f020ada..5ad534a8f2 100644 --- a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Rsa.cs +++ b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Rsa.cs @@ -44,22 +44,6 @@ internal static partial class Crypto SafeRsaHandle rsa, RsaPadding padding); - internal static int RsaPrivateDecrypt( - int flen, - ReadOnlySpan from, - Span to, - SafeRsaHandle rsa, - RsaPadding padding) => - RsaPrivateDecrypt(flen, ref MemoryMarshal.GetReference(from), ref MemoryMarshal.GetReference(to), rsa, padding); - - [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_RsaPrivateDecrypt")] - private extern static int RsaPrivateDecrypt( - int flen, - ref byte from, - ref byte to, - SafeRsaHandle rsa, - RsaPadding padding); - internal static int RsaSignPrimitive( ReadOnlySpan from, Span to, diff --git a/src/Common/src/System/Security/Cryptography/RSAOpenSsl.cs b/src/Common/src/System/Security/Cryptography/RSAOpenSsl.cs index 4d4a8414b3..87e31b9dde 100644 --- a/src/Common/src/System/Security/Cryptography/RSAOpenSsl.cs +++ b/src/Common/src/System/Security/Cryptography/RSAOpenSsl.cs @@ -85,10 +85,9 @@ public override byte[] Decrypt(byte[] data, RSAEncryptionPadding padding) if (padding == null) throw new ArgumentNullException(nameof(padding)); - Interop.Crypto.RsaPadding rsaPadding = GetInteropPadding(padding, out RsaPaddingProcessor oaepProcessor); - SafeRsaHandle key = GetKey(); - - int rsaSize = Interop.Crypto.RsaSize(key); + ValidatePadding(padding); + SafeEvpPKeyHandle key = GetPKey(); + int rsaSize = Interop.Crypto.EvpPKeySize(key); byte[] buf = null; Span destination = default; @@ -97,18 +96,15 @@ public override byte[] Decrypt(byte[] data, RSAEncryptionPadding padding) buf = CryptoPool.Rent(rsaSize); destination = new Span(buf, 0, rsaSize); - if (!TryDecrypt(key, data, destination, rsaPadding, oaepProcessor, out int bytesWritten)) - { - Debug.Fail($"{nameof(TryDecrypt)} should not return false for RSA_size buffer"); - throw new CryptographicException(); - } - + int bytesWritten = Decrypt(key, data, destination, padding); return destination.Slice(0, bytesWritten).ToArray(); } finally { CryptographicOperations.ZeroMemory(destination); CryptoPool.Return(buf, clearSize: 0); + // Until EVP_PKEY is what gets stored, free the temporary key handle. + key.Dispose(); } } @@ -119,82 +115,73 @@ public override byte[] Decrypt(byte[] data, RSAEncryptionPadding padding) out int bytesWritten) { if (padding == null) - { throw new ArgumentNullException(nameof(padding)); - } - - Interop.Crypto.RsaPadding rsaPadding = GetInteropPadding(padding, out RsaPaddingProcessor oaepProcessor); - SafeRsaHandle key = GetKey(); - int keySizeBytes = Interop.Crypto.RsaSize(key); + ValidatePadding(padding); + SafeEvpPKeyHandle key = GetPKey(); + int keySizeBytes = Interop.Crypto.EvpPKeySize(key); - // OpenSSL does not take a length value for the destination, so it can write out of bounds. - // To prevent the OOB write, decrypt into a temporary buffer. + // OpenSSL requires that the decryption buffer be at least as large as EVP_PKEY_size. + // So if the destination is too small, use a temporary buffer so we can match + // Windows behavior of succeeding so long as the buffer can hold the final output. if (destination.Length < keySizeBytes) { - Span tmp = stackalloc byte[0]; + // RSA up through 4096 bits use a stackalloc + Span tmp = stackalloc byte[512]; byte[] rent = null; - // RSA up through 4096 stackalloc - if (keySizeBytes <= 512) + if (keySizeBytes > tmp.Length) { - tmp = stackalloc byte[keySizeBytes]; - } - else - { - rent = ArrayPool.Shared.Rent(keySizeBytes); + rent = CryptoPool.Rent(keySizeBytes); tmp = rent; } - bool ret = TryDecrypt(key, data, tmp, rsaPadding, oaepProcessor, out bytesWritten); + int written = Decrypt(key, data, tmp, padding); + // Until EVP_PKEY is what gets stored, free the temporary key handle. + key.Dispose(); + bool ret; - if (ret) + if (destination.Length < written) { - tmp = tmp.Slice(0, bytesWritten); - - if (bytesWritten > destination.Length) - { - ret = false; - bytesWritten = 0; - } - else - { - tmp.CopyTo(destination); - } - - CryptographicOperations.ZeroMemory(tmp); + bytesWritten = 0; + ret = false; + } + else + { + tmp.Slice(0, written).CopyTo(destination); + bytesWritten = written; + ret = true; } + // Whether a stackalloc or a rented array, clear our copy of + // the decrypted content. + CryptographicOperations.ZeroMemory(tmp.Slice(0, written)); + if (rent != null) { - // Already cleared - ArrayPool.Shared.Return(rent); + // Already cleared. + CryptoPool.Return(rent, clearSize: 0); } return ret; } - return TryDecrypt(key, data, destination, rsaPadding, oaepProcessor, out bytesWritten); + bytesWritten = Decrypt(key, data, destination, padding); + // Until EVP_PKEY is what gets stored, free the temporary key handle. + key.Dispose(); + return true; } - private static bool TryDecrypt( - SafeRsaHandle key, + private static int Decrypt( + SafeEvpPKeyHandle key, ReadOnlySpan data, Span destination, - Interop.Crypto.RsaPadding rsaPadding, - RsaPaddingProcessor rsaPaddingProcessor, - out int bytesWritten) + RSAEncryptionPadding padding) { - // If rsaPadding is PKCS1 or OAEP-SHA1 then no depadding method should be present. - // If rsaPadding is NoPadding then a depadding method should be present. - Debug.Assert( - (rsaPadding == Interop.Crypto.RsaPadding.NoPadding) == - (rsaPaddingProcessor != null)); - // Caller should have already checked this. Debug.Assert(!key.IsInvalid); - int rsaSize = Interop.Crypto.RsaSize(key); + int rsaSize = Interop.Crypto.EvpPKeySize(key); if (data.Length != rsaSize) { @@ -203,50 +190,24 @@ public override byte[] Decrypt(byte[] data, RSAEncryptionPadding padding) if (destination.Length < rsaSize) { - bytesWritten = 0; - return false; + Debug.Fail("Caller is responsible for temporary decryption buffer creation"); + throw new CryptographicException(); } - Span decryptBuf = destination; - byte[] paddingBuf = null; + IntPtr hashAlgorithm = IntPtr.Zero; - if (rsaPaddingProcessor != null) + if (padding.Mode == RSAEncryptionPaddingMode.Oaep) { - paddingBuf = CryptoPool.Rent(rsaSize); - decryptBuf = paddingBuf; + Debug.Assert(padding.OaepHashAlgorithm.Name != null); + hashAlgorithm = Interop.Crypto.HashAlgorithmToEvp(padding.OaepHashAlgorithm.Name); } - try - { - int returnValue = Interop.Crypto.RsaPrivateDecrypt(data.Length, data, decryptBuf, key, rsaPadding); - CheckReturn(returnValue); - - if (rsaPaddingProcessor != null) - { - return rsaPaddingProcessor.DepadOaep(paddingBuf, destination, out bytesWritten); - } - else - { - // If the padding mode is RSA_NO_PADDING then the size of the decrypted block - // will be RSA_size. If any padding was used, then some amount (determined by the padding algorithm) - // will have been reduced, and only returnValue bytes were part of the decrypted - // body. Either way, we can just use returnValue, but some additional bytes may have been overwritten - // in the destination span. - bytesWritten = returnValue; - } - - return true; - } - finally - { - if (paddingBuf != null) - { - // DecryptBuf is paddingBuf if paddingBuf is not null, erase it before returning it. - // If paddingBuf IS null then decryptBuf was destination, and shouldn't be cleared. - CryptographicOperations.ZeroMemory(decryptBuf); - CryptoPool.Return(paddingBuf, clearSize: 0); - } - } + return Interop.Crypto.RsaDecrypt( + key, + data, + padding.Mode, + hashAlgorithm, + destination); } public override byte[] Encrypt(byte[] data, RSAEncryptionPadding padding) @@ -550,6 +511,30 @@ private void ThrowIfDisposed() } } + private SafeEvpPKeyHandle GetPKey() + { + SafeRsaHandle currentKey = GetKey(); + SafeEvpPKeyHandle pkeyHandle = Interop.Crypto.EvpPkeyCreate(); + + try + { + // Wrapping our key in an EVP_PKEY will up_ref our key. + // When the EVP_PKEY is Disposed it will down_ref the key. + // So everything should be copacetic. + if (!Interop.Crypto.EvpPkeySetRsa(pkeyHandle, currentKey)) + { + throw Interop.Crypto.CreateOpenSslCryptographicException(); + } + } + catch + { + pkeyHandle.Dispose(); + throw; + } + + return pkeyHandle; + } + private SafeRsaHandle GetKey() { ThrowIfDisposed(); @@ -843,6 +828,55 @@ private static int GetAlgorithmNid(HashAlgorithmName hashAlgorithmName) return nid; } + private static void ValidatePadding(RSAEncryptionPadding padding) + { + if (padding == null) + { + throw new ArgumentNullException(nameof(padding)); + } + + // There are currently two defined padding modes: + // * Oaep has an option (the hash algorithm) + // * Pkcs1 has no options + // + // Anything other than those to modes is an error, + // and Pkcs1 having options set is an error, so compare it to + // the padding struct instead of the padding mode enum. + if (padding.Mode != RSAEncryptionPaddingMode.Oaep && + padding != RSAEncryptionPadding.Pkcs1) + { + throw PaddingModeNotSupported(); + } + } + + private static void ValidatePadding(RSASignaturePadding padding) + { + if (padding == null) + { + throw new ArgumentNullException(nameof(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. + if (padding.Mode == RSASignaturePaddingMode.Pkcs1) + { + Debug.Assert(padding == RSASignaturePadding.Pkcs1); + } + else if (padding.Mode == RSASignaturePaddingMode.Pss) + { + Debug.Assert(padding == RSASignaturePadding.Pss); + } + else + { + throw PaddingModeNotSupported(); + } + } + private static Exception PaddingModeNotSupported() => new CryptographicException(SR.Cryptography_InvalidPaddingMode); diff --git a/src/Native/Unix/System.Security.Cryptography.Native/apibridge.c b/src/Native/Unix/System.Security.Cryptography.Native/apibridge.c index def7198deb..ff71105837 100644 --- a/src/Native/Unix/System.Security.Cryptography.Native/apibridge.c +++ b/src/Native/Unix/System.Security.Cryptography.Native/apibridge.c @@ -258,6 +258,23 @@ int32_t local_DSA_set0_key(DSA* dsa, BIGNUM* bnY, BIGNUM* bnX) return 1; } +RSA* local_EVP_PKEY_get0_RSA(EVP_PKEY* pkey) +{ + if (pkey == NULL) + { + return NULL; + } + + RSA* rsa = EVP_PKEY_get1_RSA(pkey); + + if (rsa != NULL) + { + RSA_free(rsa); + } + + return rsa; +} + int32_t local_EVP_PKEY_up_ref(EVP_PKEY* pkey) { if (!pkey) diff --git a/src/Native/Unix/System.Security.Cryptography.Native/apibridge.h b/src/Native/Unix/System.Security.Cryptography.Native/apibridge.h index b58611ae73..e1315499f3 100644 --- a/src/Native/Unix/System.Security.Cryptography.Native/apibridge.h +++ b/src/Native/Unix/System.Security.Cryptography.Native/apibridge.h @@ -15,6 +15,7 @@ int32_t local_DSA_set0_pqg(DSA* dsa, BIGNUM* bnP, BIGNUM* bnQ, BIGNUM* bnG); void local_EVP_CIPHER_CTX_free(EVP_CIPHER_CTX* ctx); EVP_CIPHER_CTX* local_EVP_CIPHER_CTX_new(void); int32_t local_EVP_CIPHER_CTX_reset(EVP_CIPHER_CTX* ctx); +RSA* local_EVP_PKEY_get0_RSA(EVP_PKEY* pkey); int32_t local_EVP_PKEY_up_ref(EVP_PKEY* pkey); void local_HMAC_CTX_free(HMAC_CTX* ctx); HMAC_CTX* local_HMAC_CTX_new(void); diff --git a/src/Native/Unix/System.Security.Cryptography.Native/opensslshim.h b/src/Native/Unix/System.Security.Cryptography.Native/opensslshim.h index dff6091e9e..47cb142d25 100644 --- a/src/Native/Unix/System.Security.Cryptography.Native/opensslshim.h +++ b/src/Native/Unix/System.Security.Cryptography.Native/opensslshim.h @@ -128,6 +128,7 @@ EVP_CIPHER_CTX* EVP_CIPHER_CTX_new(void); int32_t EVP_CIPHER_CTX_reset(EVP_CIPHER_CTX* ctx); void EVP_MD_CTX_free(EVP_MD_CTX* ctx); EVP_MD_CTX* EVP_MD_CTX_new(void); +RSA* EVP_PKEY_get0_RSA(EVP_PKEY* pkey); int32_t EVP_PKEY_up_ref(EVP_PKEY* pkey); void HMAC_CTX_free(HMAC_CTX* ctx); HMAC_CTX* HMAC_CTX_new(void); @@ -178,6 +179,12 @@ int32_t X509_up_ref(X509* x509); #define EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, bits) \ RSA_pkey_ctx_ctrl(ctx, EVP_PKEY_OP_KEYGEN, EVP_PKEY_CTRL_RSA_KEYGEN_BITS, bits, NULL) +#undef EVP_PKEY_CTX_set_rsa_padding +#define EVP_PKEY_CTX_set_rsa_padding(ctx, pad) \ + RSA_pkey_ctx_ctrl(ctx, -1, EVP_PKEY_CTRL_RSA_PADDING, pad, NULL) + +// EVP_PKEY_CTX_set_rsa_oaep_md doesn't call RSA_pkey_ctx_ctrl in 1.1, so don't redefine it here. + #endif #if OPENSSL_VERSION_NUMBER < OPENSSL_VERSION_1_0_2_RTM @@ -355,10 +362,13 @@ void SSL_get0_alpn_selected(const SSL* ssl, const unsigned char** protocol, unsi REQUIRED_FUNCTION(EVP_PKEY_CTX_new) \ REQUIRED_FUNCTION(EVP_PKEY_CTX_new_id) \ REQUIRED_FUNCTION(EVP_PKEY_base_id) \ + REQUIRED_FUNCTION(EVP_PKEY_decrypt) \ + REQUIRED_FUNCTION(EVP_PKEY_decrypt_init) \ REQUIRED_FUNCTION(EVP_PKEY_derive_set_peer) \ REQUIRED_FUNCTION(EVP_PKEY_derive_init) \ REQUIRED_FUNCTION(EVP_PKEY_derive) \ REQUIRED_FUNCTION(EVP_PKEY_free) \ + FALLBACK_FUNCTION(EVP_PKEY_get0_RSA) \ REQUIRED_FUNCTION(EVP_PKEY_get1_DSA) \ REQUIRED_FUNCTION(EVP_PKEY_get1_EC_KEY) \ REQUIRED_FUNCTION(EVP_PKEY_get1_RSA) \ @@ -368,6 +378,7 @@ void SSL_get0_alpn_selected(const SSL* ssl, const unsigned char** protocol, unsi REQUIRED_FUNCTION(EVP_PKEY_set1_DSA) \ REQUIRED_FUNCTION(EVP_PKEY_set1_EC_KEY) \ REQUIRED_FUNCTION(EVP_PKEY_set1_RSA) \ + REQUIRED_FUNCTION(EVP_PKEY_size) \ FALLBACK_FUNCTION(EVP_PKEY_up_ref) \ REQUIRED_FUNCTION(EVP_rc2_cbc) \ REQUIRED_FUNCTION(EVP_rc2_ecb) \ @@ -448,7 +459,6 @@ void SSL_get0_alpn_selected(const SSL* ssl, const unsigned char** protocol, unsi REQUIRED_FUNCTION(RSA_new) \ FALLBACK_FUNCTION(RSA_pkey_ctx_ctrl) \ RENAMED_FUNCTION(RSA_PKCS1_OpenSSL, RSA_PKCS1_SSLeay) \ - REQUIRED_FUNCTION(RSA_private_decrypt) \ REQUIRED_FUNCTION(RSA_private_encrypt) \ REQUIRED_FUNCTION(RSA_public_decrypt) \ REQUIRED_FUNCTION(RSA_public_encrypt) \ @@ -748,10 +758,13 @@ FOR_ALL_OPENSSL_FUNCTIONS #define EVP_PKEY_CTX_new EVP_PKEY_CTX_new_ptr #define EVP_PKEY_CTX_new_id EVP_PKEY_CTX_new_id_ptr #define EVP_PKEY_base_id EVP_PKEY_base_id_ptr +#define EVP_PKEY_decrypt_init EVP_PKEY_decrypt_init_ptr +#define EVP_PKEY_decrypt EVP_PKEY_decrypt_ptr #define EVP_PKEY_derive_set_peer EVP_PKEY_derive_set_peer_ptr #define EVP_PKEY_derive_init EVP_PKEY_derive_init_ptr #define EVP_PKEY_derive EVP_PKEY_derive_ptr #define EVP_PKEY_free EVP_PKEY_free_ptr +#define EVP_PKEY_get0_RSA EVP_PKEY_get0_RSA_ptr #define EVP_PKEY_get1_DSA EVP_PKEY_get1_DSA_ptr #define EVP_PKEY_get1_EC_KEY EVP_PKEY_get1_EC_KEY_ptr #define EVP_PKEY_get1_RSA EVP_PKEY_get1_RSA_ptr @@ -761,6 +774,7 @@ FOR_ALL_OPENSSL_FUNCTIONS #define EVP_PKEY_set1_DSA EVP_PKEY_set1_DSA_ptr #define EVP_PKEY_set1_EC_KEY EVP_PKEY_set1_EC_KEY_ptr #define EVP_PKEY_set1_RSA EVP_PKEY_set1_RSA_ptr +#define EVP_PKEY_size EVP_PKEY_size_ptr #define EVP_PKEY_up_ref EVP_PKEY_up_ref_ptr #define EVP_rc2_cbc EVP_rc2_cbc_ptr #define EVP_rc2_ecb EVP_rc2_ecb_ptr @@ -841,7 +855,6 @@ FOR_ALL_OPENSSL_FUNCTIONS #define RSA_new RSA_new_ptr #define RSA_pkey_ctx_ctrl RSA_pkey_ctx_ctrl_ptr #define RSA_PKCS1_OpenSSL RSA_PKCS1_OpenSSL_ptr -#define RSA_private_decrypt RSA_private_decrypt_ptr #define RSA_private_encrypt RSA_private_encrypt_ptr #define RSA_public_decrypt RSA_public_decrypt_ptr #define RSA_public_encrypt RSA_public_encrypt_ptr diff --git a/src/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey.c b/src/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey.c index 4d479dde6c..f232b382ea 100644 --- a/src/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey.c +++ b/src/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey.c @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#include #include "pal_evp_pkey.h" EVP_PKEY* CryptoNative_EvpPkeyCreate() @@ -17,6 +18,12 @@ void CryptoNative_EvpPkeyDestroy(EVP_PKEY* pkey) } } +int32_t CryptoNative_EvpPKeySize(EVP_PKEY* pkey) +{ + assert(pkey != NULL); + return EVP_PKEY_size(pkey); +} + int32_t CryptoNative_UpRefEvpPkey(EVP_PKEY* pkey) { if (!pkey) diff --git a/src/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey.h b/src/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey.h index 7baf997d8d..750282efdb 100644 --- a/src/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey.h +++ b/src/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey.h @@ -24,6 +24,11 @@ Always succeeds. */ DLLEXPORT void CryptoNative_EvpPkeyDestroy(EVP_PKEY* pkey); +/* +Returns the maximum size, in bytes, of an operation with the provided key. +*/ +DLLEXPORT int32_t CryptoNative_EvpPKeySize(EVP_PKEY* pkey); + /* Used by System.Security.Cryptography.X509Certificates' OpenSslX509CertificateReader when duplicating a private key context as part of duplicating the Pal object. diff --git a/src/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey_rsa.c b/src/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey_rsa.c index 29f9238ce9..6235c905db 100644 --- a/src/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey_rsa.c +++ b/src/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey_rsa.c @@ -3,6 +3,10 @@ // See the LICENSE file in the project root for more information. #include "pal_evp_pkey_rsa.h" +#include "pal_utilities.h" +#include + +static int HasNoPrivateKey(RSA* rsa); EVP_PKEY* CryptoNative_RsaGenerateKey(int keySize) { @@ -16,8 +20,7 @@ EVP_PKEY* CryptoNative_RsaGenerateKey(int keySize) EVP_PKEY* pkey = NULL; EVP_PKEY* ret = NULL; - if (EVP_PKEY_keygen_init(ctx) == 1 && - EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, keySize) == 1 && + if (EVP_PKEY_keygen_init(ctx) == 1 && EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, keySize) == 1 && EVP_PKEY_keygen(ctx, &pkey) == 1) { ret = pkey; @@ -33,6 +36,82 @@ EVP_PKEY* CryptoNative_RsaGenerateKey(int keySize) return ret; } +int32_t CryptoNative_RsaDecrypt(EVP_PKEY* pkey, + const uint8_t* source, + int32_t sourceLen, + RsaPaddingMode padding, + const EVP_MD* digest, + uint8_t* destination, + int32_t destinationLen) +{ + assert(pkey != NULL); + assert(source != NULL); + assert(destination != NULL); + assert(padding >= RsaPaddingPkcs1 && padding <= RsaPaddingOaepOrPss); + assert(digest != NULL || padding == RsaPaddingPkcs1); + + EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey, NULL); + + int ret = -1; + + if (ctx == NULL || EVP_PKEY_decrypt_init(ctx) <= 0) + { + goto done; + } + + if (padding == RsaPaddingPkcs1) + { + if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0) + { + goto done; + } + } + else + { + assert(padding == RsaPaddingOaepOrPss); + + if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0) + { + goto done; + } + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcast-qual" + if (EVP_PKEY_CTX_set_rsa_oaep_md(ctx, digest) <= 0) +#pragma clang diagnostic pop + { + goto done; + } + } + + // This check may no longer be needed on OpenSSL 3.0 + { + RSA* rsa = EVP_PKEY_get0_RSA(pkey); + + if (rsa == NULL || HasNoPrivateKey(rsa)) + { + ERR_PUT_error(ERR_LIB_RSA, RSA_F_RSA_NULL_PRIVATE_DECRYPT, RSA_R_VALUE_MISSING, __FILE__, __LINE__); + ret = -1; + goto done; + } + } + + size_t written = Int32ToSizeT(destinationLen); + + if (EVP_PKEY_decrypt(ctx, destination, &written, source, Int32ToSizeT(sourceLen)) > 0) + { + ret = SizeTToInt32(written); + } + +done: + if (ctx != NULL) + { + EVP_PKEY_CTX_free(ctx); + } + + return ret; +} + RSA* CryptoNative_EvpPkeyGetRsa(EVP_PKEY* pkey) { return EVP_PKEY_get1_RSA(pkey); @@ -42,3 +121,57 @@ int32_t CryptoNative_EvpPkeySetRsa(EVP_PKEY* pkey, RSA* rsa) { return EVP_PKEY_set1_RSA(pkey, rsa); } + +static int HasNoPrivateKey(RSA* rsa) +{ + if (rsa == NULL) + return 1; + + // Shared pointer, don't free. + const RSA_METHOD* meth = RSA_get_method(rsa); + + // The method has descibed itself as having the private key external to the structure. + // That doesn't mean it's actually present, but we can't tell. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcast-qual" + if (RSA_meth_get_flags((RSA_METHOD*)meth) & RSA_FLAG_EXT_PKEY) +#pragma clang diagnostic pop + { + return 0; + } + + // In the event that there's a middle-ground where we report failure when success is expected, + // one could do something like check if the RSA_METHOD intercepts all private key operations: + // + // * meth->rsa_priv_enc + // * meth->rsa_priv_dec + // * meth->rsa_sign (in 1.0.x this is only respected if the RSA_FLAG_SIGN_VER flag is asserted) + // + // But, for now, leave it at the EXT_PKEY flag test. + + // The module is documented as accepting either d or the full set of CRT parameters (p, q, dp, dq, qInv) + // So if we see d, we're good. Otherwise, if any of the rest are missing, we're public-only. + const BIGNUM* d; + RSA_get0_key(rsa, NULL, NULL, &d); + + if (d != NULL) + { + return 0; + } + + const BIGNUM* p; + const BIGNUM* q; + const BIGNUM* dmp1; + const BIGNUM* dmq1; + const BIGNUM* iqmp; + + RSA_get0_factors(rsa, &p, &q); + RSA_get0_crt_params(rsa, &dmp1, &dmq1, &iqmp); + + if (p == NULL || q == NULL || dmp1 == NULL || dmq1 == NULL || iqmp == NULL) + { + return 1; + } + + return 0; +} diff --git a/src/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey_rsa.h b/src/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey_rsa.h index 1fda149414..d220065adf 100644 --- a/src/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey_rsa.h +++ b/src/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey_rsa.h @@ -2,15 +2,38 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#include "pal_types.h" -#include "pal_compiler.h" #include "opensslshim.h" +#include "pal_compiler.h" +#include "pal_types.h" + +/* +Padding options for RSA. +Matches RSAEncryptionPaddingMode / RSASignaturePaddingMode. +*/ +typedef enum +{ + RsaPaddingPkcs1, + RsaPaddingOaepOrPss, +} RsaPaddingMode; /* Creates an RSA key of the requested size. */ DLLEXPORT EVP_PKEY* CryptoNative_RsaGenerateKey(int32_t keySize); +/* +Decrypt source into destination using the specified RSA key (wrapped in an EVP_PKEY) and padding/digest options. + +Returns the number of bytes written to destination, -1 on error. +*/ +DLLEXPORT int32_t CryptoNative_RsaDecrypt(EVP_PKEY* pkey, + const uint8_t* source, + int32_t sourceLen, + RsaPaddingMode padding, + const EVP_MD* digest, + uint8_t* destination, + int32_t destinationLen); + /* Shims the EVP_PKEY_get1_RSA method. diff --git a/src/Native/Unix/System.Security.Cryptography.Native/pal_rsa.c b/src/Native/Unix/System.Security.Cryptography.Native/pal_rsa.c index 080027de0e..0c635dfca7 100644 --- a/src/Native/Unix/System.Security.Cryptography.Native/pal_rsa.c +++ b/src/Native/Unix/System.Security.Cryptography.Native/pal_rsa.c @@ -109,19 +109,6 @@ CryptoNative_RsaPublicEncrypt(int32_t flen, const uint8_t* from, uint8_t* to, RS return RSA_public_encrypt(flen, from, to, rsa, openSslPadding); } -int32_t -CryptoNative_RsaPrivateDecrypt(int32_t flen, const uint8_t* from, uint8_t* to, RSA* rsa, RsaPadding padding) -{ - if (HasNoPrivateKey(rsa)) - { - ERR_PUT_error(ERR_LIB_RSA, RSA_F_RSA_NULL_PRIVATE_DECRYPT, RSA_R_VALUE_MISSING, __FILE__, __LINE__); - return -1; - } - - int openSslPadding = GetOpenSslPadding(padding); - return RSA_private_decrypt(flen, from, to, rsa, openSslPadding); -} - int32_t CryptoNative_RsaSignPrimitive(int32_t flen, const uint8_t* from, uint8_t* to, RSA* rsa) { if (HasNoPrivateKey(rsa)) diff --git a/src/Native/Unix/System.Security.Cryptography.Native/pal_rsa.h b/src/Native/Unix/System.Security.Cryptography.Native/pal_rsa.h index 1c0bc2df47..30d7d9fa59 100644 --- a/src/Native/Unix/System.Security.Cryptography.Native/pal_rsa.h +++ b/src/Native/Unix/System.Security.Cryptography.Native/pal_rsa.h @@ -55,14 +55,6 @@ Returns the size of the signature, or -1 on error. DLLEXPORT int32_t CryptoNative_RsaPublicEncrypt(int32_t flen, const uint8_t* from, uint8_t* to, RSA* rsa, RsaPadding padding); -/* -Shims the RSA_private_decrypt method. - -Returns the size of the signature, or -1 on error. -*/ -DLLEXPORT int32_t -CryptoNative_RsaPrivateDecrypt(int32_t flen, const uint8_t* from, uint8_t* to, RSA* rsa, RsaPadding padding); - /* Shims RSA_private_encrypt with a fixed value of RSA_NO_PADDING. diff --git a/src/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/HashProviderDispenser.Unix.cs b/src/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/HashProviderDispenser.Unix.cs index ff8e91e7c9..589ff882bf 100644 --- a/src/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/HashProviderDispenser.Unix.cs +++ b/src/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/HashProviderDispenser.Unix.cs @@ -12,40 +12,16 @@ namespace Internal.Cryptography { internal static partial class HashProviderDispenser { - public static HashProvider CreateHashProvider(string hashAlgorithmId) + internal static HashProvider CreateHashProvider(string hashAlgorithmId) { - switch (hashAlgorithmId) - { - case HashAlgorithmNames.SHA1: - return new EvpHashProvider(Interop.Crypto.EvpSha1()); - case HashAlgorithmNames.SHA256: - return new EvpHashProvider(Interop.Crypto.EvpSha256()); - case HashAlgorithmNames.SHA384: - return new EvpHashProvider(Interop.Crypto.EvpSha384()); - case HashAlgorithmNames.SHA512: - return new EvpHashProvider(Interop.Crypto.EvpSha512()); - case HashAlgorithmNames.MD5: - return new EvpHashProvider(Interop.Crypto.EvpMd5()); - } - throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithmId)); + IntPtr evpType = Interop.Crypto.HashAlgorithmToEvp(hashAlgorithmId); + return new EvpHashProvider(evpType); } - public static unsafe HashProvider CreateMacProvider(string hashAlgorithmId, byte[] key) + internal static unsafe HashProvider CreateMacProvider(string hashAlgorithmId, byte[] key) { - switch (hashAlgorithmId) - { - case HashAlgorithmNames.SHA1: - return new HmacHashProvider(Interop.Crypto.EvpSha1(), key); - case HashAlgorithmNames.SHA256: - return new HmacHashProvider(Interop.Crypto.EvpSha256(), key); - case HashAlgorithmNames.SHA384: - return new HmacHashProvider(Interop.Crypto.EvpSha384(), key); - case HashAlgorithmNames.SHA512: - return new HmacHashProvider(Interop.Crypto.EvpSha512(), key); - case HashAlgorithmNames.MD5: - return new HmacHashProvider(Interop.Crypto.EvpMd5(), key); - } - throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithmId)); + IntPtr evpType = Interop.Crypto.HashAlgorithmToEvp(hashAlgorithmId); + return new HmacHashProvider(evpType, key); } // ----------------------------- diff --git a/src/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj b/src/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj index 6ad2b78e01..c6e8b5b69a 100644 --- a/src/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj +++ b/src/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj @@ -513,6 +513,9 @@ Common\Interop\Unix\System.Security.Cryptography.Native\Interop.EVP.cs + + Common\Interop\Unix\System.Security.Cryptography.Native\Interop.EVP.DigestAlgs.cs + Common\Interop\Unix\System.Security.Cryptography.Native\Interop.Hmac.cs diff --git a/src/System.Security.Cryptography.OpenSsl/src/System.Security.Cryptography.OpenSsl.csproj b/src/System.Security.Cryptography.OpenSsl/src/System.Security.Cryptography.OpenSsl.csproj index 17ab176ec2..dbbc4848e8 100644 --- a/src/System.Security.Cryptography.OpenSsl/src/System.Security.Cryptography.OpenSsl.csproj +++ b/src/System.Security.Cryptography.OpenSsl/src/System.Security.Cryptography.OpenSsl.csproj @@ -57,6 +57,9 @@ Common\Interop\Unix\System.Security.Cryptography.Native\Interop.ERR.cs + + Common\Interop\Unix\System.Security.Cryptography.Native\Interop.EVP.DigestAlgs.cs + Common\Interop\Unix\System.Security.Cryptography.Native\Interop.EvpPkey.cs -- 2.31.1