From aaed7d957f97311a1d4f49f82ef688335c6a833a Mon Sep 17 00:00:00 2001 From: Krzysztof Wicher Date: Tue, 16 Jul 2024 14:25:04 +0200 Subject: [PATCH 01/17] OpenSSL providers support --- .../Interop.EcDsa.cs | 61 ---- .../Interop.EvpPkey.EcKey.cs | 17 +- .../Interop.EvpPkey.Ecdh.cs | 94 +++++- .../Interop.EvpPkey.Rsa.cs | 83 ++--- .../Interop.EvpPkey.cs | 61 ++-- .../SafeHandles/SafeEvpPkeyCtxHandle.Unix.cs | 47 +++ .../ECDiffieHellmanOpenSsl.Derive.cs | 45 ++- .../Cryptography/ECDiffieHellmanOpenSsl.cs | 55 ++-- .../ECDiffieHellmanOpenSslPublicKey.cs | 20 +- .../Security/Cryptography/ECDsaOpenSsl.cs | 215 +++++++----- .../System/Security/Cryptography/ECOpenSsl.cs | 34 +- .../Security/Cryptography/RSAOpenSsl.cs | 158 +++++---- .../ref/System.Security.Cryptography.cs | 6 + .../src/System.Security.Cryptography.csproj | 4 - .../Cryptography/ECDiffieHellmanOpenSsl.cs | 41 +-- .../Security/Cryptography/ECDsaOpenSsl.cs | 45 +-- .../Cryptography/OpenSsl.NotSupported.cs | 8 + .../Security/Cryptography/RSAOpenSsl.cs | 6 +- .../Cryptography/SafeEvpPKeyHandle.OpenSsl.cs | 96 +++++- .../X509Certificates/OpenSslExportProvider.cs | 42 +-- .../tests/OpenSslNamedKeysTests.manual.cs | 264 +++++++++++++-- .../tests/osslplugins/README.md | 69 +++- .../tests/osslplugins/test.sh | 55 +++- .../CMakeLists.txt | 1 - .../entrypoints.c | 18 +- .../opensslshim.h | 33 +- .../osslcompat_30.h | 26 +- .../pal_ecdsa.c | 34 -- .../pal_ecdsa.h | 29 -- .../pal_evp_pkey.c | 307 +++++++++++++++--- .../pal_evp_pkey.h | 71 +++- .../pal_evp_pkey_ecdh.c | 5 +- .../pal_evp_pkey_ecdh.h | 2 +- .../pal_evp_pkey_rsa.c | 170 +--------- .../pal_evp_pkey_rsa.h | 31 +- .../pal_ssl.c | 2 +- 36 files changed, 1442 insertions(+), 813 deletions(-) delete mode 100644 src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.cs delete mode 100644 src/native/libs/System.Security.Cryptography.Native/pal_ecdsa.c delete mode 100644 src/native/libs/System.Security.Cryptography.Native/pal_ecdsa.h diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.cs deleted file mode 100644 index 7d994d96402dd3..00000000000000 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.cs +++ /dev/null @@ -1,61 +0,0 @@ -// 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 Microsoft.Win32.SafeHandles; - -internal static partial class Interop -{ - internal static partial class Crypto - { - internal static bool EcDsaSign(ReadOnlySpan dgst, Span sig, out int siglen, SafeEcKeyHandle ecKey) => - EcDsaSign(ref MemoryMarshal.GetReference(dgst), dgst.Length, ref MemoryMarshal.GetReference(sig), out siglen, ecKey); - - [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EcDsaSign")] - [return: MarshalAs(UnmanagedType.Bool)] - private static partial bool EcDsaSign(ref byte dgst, int dlen, ref byte sig, out int siglen, SafeEcKeyHandle ecKey); - - internal static int EcDsaVerify(ReadOnlySpan dgst, ReadOnlySpan sigbuf, SafeEcKeyHandle ecKey) - { - int ret = EcDsaVerify( - ref MemoryMarshal.GetReference(dgst), - dgst.Length, - ref MemoryMarshal.GetReference(sigbuf), - sigbuf.Length, - ecKey); - - if (ret < 0) - { - ErrClearError(); - } - - return ret; - } - - /*- - * returns - * 1: correct signature - * 0: incorrect signature - * -1: error - */ - [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EcDsaVerify")] - private static partial int EcDsaVerify(ref byte dgst, int dgst_len, ref byte sigbuf, int sig_len, SafeEcKeyHandle ecKey); - - // returns the maximum length of a DER encoded ECDSA signature created with this key. - [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EcDsaSize")] - private static partial int CryptoNative_EcDsaSize(SafeEcKeyHandle ecKey); - - internal static int EcDsaSize(SafeEcKeyHandle ecKey) - { - int ret = CryptoNative_EcDsaSize(ecKey); - - if (ret == 0) - { - throw CreateOpenSslCryptographicException(); - } - - return ret; - } - } -} diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.EcKey.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.EcKey.cs index 6ef629c8e0d6e7..bd89f4e3bee21a 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.EcKey.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.EcKey.cs @@ -12,8 +12,21 @@ internal static partial class Crypto [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPkeyGetEcKey")] internal static partial SafeEcKeyHandle EvpPkeyGetEcKey(SafeEvpPKeyHandle pkey); - [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPkeySetEcKey")] + [LibraryImport(Libraries.CryptoNative)] [return: MarshalAs(UnmanagedType.Bool)] - internal static partial bool EvpPkeySetEcKey(SafeEvpPKeyHandle pkey, SafeEcKeyHandle key); + private static partial bool CryptoNative_EvpPkeySetEcKey(SafeEvpPKeyHandle pkey, SafeEcKeyHandle key); + + // Calls EVP_PKEY_set1_EC_KEY therefore the key will be duplicated + internal static SafeEvpPKeyHandle CreateEvpPkeyFromEcKey(SafeEcKeyHandle key) + { + SafeEvpPKeyHandle pkey = Interop.Crypto.EvpPkeyCreate(); + if (!CryptoNative_EvpPkeySetEcKey(pkey, key)) + { + pkey.Dispose(); + throw Interop.Crypto.CreateOpenSslCryptographicException(); + } + + return pkey; + } } } diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Ecdh.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Ecdh.cs index 5308043c8e80ef..1abcdaf65f8343 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Ecdh.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Ecdh.cs @@ -11,8 +11,100 @@ internal static partial class Interop { internal static partial class Crypto { + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyCtxCreateFromPKey")] + private static partial SafeEvpPKeyCtxHandle EvpPKeyCtxCreate(SafeEvpPKeyHandle pkey, IntPtr extraHandle); + + internal static SafeEvpPKeyCtxHandle EvpPKeyCtxCreate(SafeEvpPKeyHandle pkey) + => EvpPKeyCtxCreate(pkey, pkey.ExtraHandle); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyCtxCreate")] - internal static partial SafeEvpPKeyCtxHandle EvpPKeyCtxCreate(SafeEvpPKeyHandle pkey, SafeEvpPKeyHandle peerkey, out uint secretLength); + private static partial SafeEvpPKeyCtxHandle EvpPKeyCtxCreate(SafeEvpPKeyHandle pkey, IntPtr extraHandle, SafeEvpPKeyHandle peerkey, out uint secretLength); + + internal static SafeEvpPKeyCtxHandle EvpPKeyCtxCreate(SafeEvpPKeyHandle pkey, SafeEvpPKeyHandle peerkey, out uint secretLength) + => EvpPKeyCtxCreate(pkey, pkey.ExtraHandle, peerkey, out secretLength); + + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyCtxConfigureForECDSASign")] + private static partial int EvpPKeyCtxConfigureForECDSASignCore(SafeEvpPKeyCtxHandle ctx); + + internal static void EvpPKeyCtxConfigureForECDSASign(SafeEvpPKeyCtxHandle ctx) + { + Debug.Assert(ctx != null); + Debug.Assert(!ctx.IsInvalid); + + if (EvpPKeyCtxConfigureForECDSASignCore(ctx) != 1) + { + throw CreateOpenSslCryptographicException(); + } + } + + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyCtxConfigureForECDSAVerify")] + private static partial int EvpPKeyCtxConfigureForECDSAVerifyCore(SafeEvpPKeyCtxHandle ctx); + + internal static void EvpPKeyCtxConfigureForECDSAVerify(SafeEvpPKeyCtxHandle ctx) + { + Debug.Assert(ctx != null); + Debug.Assert(!ctx.IsInvalid); + + if (EvpPKeyCtxConfigureForECDSAVerifyCore(ctx) != 1) + { + throw CreateOpenSslCryptographicException(); + } + } + + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyCtxSignHash")] + private static unsafe partial int EvpPKeyCtxSignHash(SafeEvpPKeyCtxHandle ctx, byte* hash, int hashLen, byte* destination, ref int destinationLen); + + internal static unsafe bool TryEvpPKeyCtxSignHash(SafeEvpPKeyCtxHandle ctx, ReadOnlySpan hash, Span destination, out int bytesWritten) + { + Debug.Assert(ctx != null); + Debug.Assert(!ctx.IsInvalid); + + if (hash.Length == 0 || destination.Length == 0) + { + bytesWritten = 0; + return false; + } + + bytesWritten = destination.Length; + ref byte hashRef = ref MemoryMarshal.GetReference(hash); + ref byte destRef = ref MemoryMarshal.GetReference(destination); + fixed (byte* hashPtr = &hashRef) + fixed (byte* destPtr = &destRef) + { + return EvpPKeyCtxSignHash(ctx, hashPtr, hash.Length, destPtr, ref bytesWritten) == 1; + } + } + + internal static unsafe bool TryEvpPKeyCtxSignatureSize(SafeEvpPKeyCtxHandle ctx, ReadOnlySpan hash, out int bytesWritten) + { + Debug.Assert(ctx != null); + Debug.Assert(!ctx.IsInvalid); + + bytesWritten = 0; + + if (hash.Length == 0) + { + return false; + } + + ref byte hashRef = ref MemoryMarshal.GetReference(hash); + fixed (byte* hashPtr = &hashRef) + { + byte* destPtr = null; + return EvpPKeyCtxSignHash(ctx, hashPtr, hash.Length, destPtr, ref bytesWritten) == 1; + } + } + + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyCtxVerifyHash")] + private static partial int EvpPKeyCtxVerifyHash(SafeEvpPKeyCtxHandle ctx, ref byte hash, int hashLen, ref byte signature, int signatureLen); + + internal static bool EvpPKeyCtxVerifyHash(SafeEvpPKeyCtxHandle ctx, ReadOnlySpan hash, ReadOnlySpan signature) + { + Debug.Assert(ctx != null); + Debug.Assert(!ctx.IsInvalid); + + return EvpPKeyCtxVerifyHash(ctx, ref MemoryMarshal.GetReference(hash), hash.Length, ref MemoryMarshal.GetReference(signature), signature.Length) == 1; + } [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyDeriveSecretAgreement")] private static partial int EvpPKeyDeriveSecretAgreement( 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 9fe32f23e42b02..1fec6d14bfca61 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 @@ -48,6 +48,7 @@ internal static SafeEvpPKeyHandle RsaGenerateKey(int keySize) [LibraryImport(Libraries.CryptoNative)] private static partial int CryptoNative_RsaDecrypt( SafeEvpPKeyHandle pkey, + IntPtr extraHandle, ref byte source, int sourceLength, RSAEncryptionPaddingMode paddingMode, @@ -64,6 +65,7 @@ internal static int RsaDecrypt( { int written = CryptoNative_RsaDecrypt( pkey, + pkey.ExtraHandle, ref MemoryMarshal.GetReference(source), source.Length, paddingMode, @@ -83,6 +85,7 @@ ref MemoryMarshal.GetReference(destination), [LibraryImport(Libraries.CryptoNative)] private static partial int CryptoNative_RsaEncrypt( SafeEvpPKeyHandle pkey, + IntPtr extraHandle, ref byte source, int sourceLength, RSAEncryptionPaddingMode paddingMode, @@ -99,6 +102,7 @@ internal static int RsaEncrypt( { int written = CryptoNative_RsaEncrypt( pkey, + pkey.ExtraHandle, ref MemoryMarshal.GetReference(source), source.Length, paddingMode, @@ -116,78 +120,53 @@ ref MemoryMarshal.GetReference(destination), } [LibraryImport(Libraries.CryptoNative)] - private static partial int CryptoNative_RsaSignHash( - SafeEvpPKeyHandle pkey, + private static partial int CryptoNative_EvpPKeyCtxConfigureForRsaSign( + SafeEvpPKeyCtxHandle ctx, RSASignaturePaddingMode paddingMode, - IntPtr digestAlgorithm, - ref byte hash, - int hashLength, - ref byte destination, - int destinationLength); + IntPtr digestAlgorithm); - internal static int RsaSignHash( - SafeEvpPKeyHandle pkey, + internal static void CryptoNative_ConfigureForRsaSign( + SafeEvpPKeyCtxHandle ctx, RSASignaturePaddingMode paddingMode, - IntPtr digestAlgorithm, - ReadOnlySpan hash, - Span destination) + HashAlgorithmName digestAlgorithm) { - int written = CryptoNative_RsaSignHash( - pkey, - paddingMode, - digestAlgorithm, - ref MemoryMarshal.GetReference(hash), - hash.Length, - ref MemoryMarshal.GetReference(destination), - destination.Length); + if (digestAlgorithm.Name == null) + { + throw new ArgumentNullException(nameof(digestAlgorithm)); + } - if (written < 0) + IntPtr digestAlgorithmPtr = Interop.Crypto.HashAlgorithmToEvp(digestAlgorithm.Name); + int ret = CryptoNative_EvpPKeyCtxConfigureForRsaSign(ctx, paddingMode, digestAlgorithmPtr); + + if (ret != 1) { - Debug.Assert(written == -1); throw CreateOpenSslCryptographicException(); } - - return written; } [LibraryImport(Libraries.CryptoNative)] - private static partial int CryptoNative_RsaVerifyHash( - SafeEvpPKeyHandle pkey, + private static partial int CryptoNative_EvpPKeyCtxConfigureForRsaVerify( + SafeEvpPKeyCtxHandle ctx, RSASignaturePaddingMode paddingMode, - IntPtr digestAlgorithm, - ref byte hash, - int hashLength, - ref byte signature, - int signatureLength); + IntPtr digestAlgorithm); - internal static bool RsaVerifyHash( - SafeEvpPKeyHandle pkey, + internal static void CryptoNative_ConfigureForRsaVerify( + SafeEvpPKeyCtxHandle ctx, RSASignaturePaddingMode paddingMode, - IntPtr digestAlgorithm, - ReadOnlySpan hash, - ReadOnlySpan signature) + HashAlgorithmName digestAlgorithm) { - int ret = CryptoNative_RsaVerifyHash( - pkey, - paddingMode, - digestAlgorithm, - ref MemoryMarshal.GetReference(hash), - hash.Length, - ref MemoryMarshal.GetReference(signature), - signature.Length); - - if (ret == 1) + if (digestAlgorithm.Name == null) { - return true; + throw new ArgumentNullException(nameof(digestAlgorithm)); } - if (ret == 0) + IntPtr digestAlgorithmPtr = Interop.Crypto.HashAlgorithmToEvp(digestAlgorithm.Name); + int ret = CryptoNative_EvpPKeyCtxConfigureForRsaVerify(ctx, paddingMode, digestAlgorithmPtr); + + if (ret != 1) { - return false; + throw CreateOpenSslCryptographicException(); } - - Debug.Assert(ret == -1); - throw CreateOpenSslCryptographicException(); } } } diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs index 4e8659b5653b78..99922f9d4f884c 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs @@ -13,36 +13,22 @@ internal static partial class Crypto [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPkeyCreate")] internal static partial SafeEvpPKeyHandle EvpPkeyCreate(); - [LibraryImport(Libraries.CryptoNative)] - private static partial SafeEvpPKeyHandle CryptoNative_EvpPKeyDuplicate( - SafeEvpPKeyHandle currentKey, - EvpAlgorithmId algorithmId); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPkeyDestroy")] + internal static partial void EvpPkeyDestroy(IntPtr pkey, IntPtr extraHandle); - internal static SafeEvpPKeyHandle EvpPKeyDuplicate( - SafeEvpPKeyHandle currentKey, - EvpAlgorithmId algorithmId) - { - SafeEvpPKeyHandle pkey = CryptoNative_EvpPKeyDuplicate( - currentKey, - algorithmId); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyBits")] + internal static partial int EvpPKeyBits(SafeEvpPKeyHandle pkey); - if (pkey.IsInvalid) - { - pkey.Dispose(); - throw CreateOpenSslCryptographicException(); - } + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_UpRefEvpPkey")] + private static partial int UpRefEvpPkey(SafeEvpPKeyHandle handle, IntPtr extraHandle); - return pkey; + internal static int UpRefEvpPkey(SafeEvpPKeyHandle handle) + { + return UpRefEvpPkey(handle, handle.ExtraHandle); } - [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPkeyDestroy")] - internal static partial void EvpPkeyDestroy(IntPtr pkey); - - [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeySize")] - internal static partial int EvpPKeySize(SafeEvpPKeyHandle pkey); - - [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_UpRefEvpPkey")] - internal static partial int UpRefEvpPkey(SafeEvpPKeyHandle handle); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyType")] + internal static partial EvpAlgorithmId EvpPKeyType(SafeEvpPKeyHandle handle); [LibraryImport(Libraries.CryptoNative)] private static unsafe partial SafeEvpPKeyHandle CryptoNative_DecodeSubjectPublicKeyInfo( @@ -274,6 +260,31 @@ internal static SafeEvpPKeyHandle LoadPublicKeyFromEngine( return pkey; } + [LibraryImport(Libraries.CryptoNative, StringMarshalling = StringMarshalling.Utf8)] + private static partial IntPtr CryptoNative_LoadKeyFromProvider( + string providerName, + string keyUri, + ref IntPtr extraHandle); + + internal static SafeEvpPKeyHandle LoadKeyFromProvider( + string providerName, + string keyUri) + { + IntPtr extraHandle = IntPtr.Zero; + IntPtr evpPKeyHandle = CryptoNative_LoadKeyFromProvider(providerName, keyUri, ref extraHandle); + + if (IntPtr.Zero == evpPKeyHandle) + { + throw CreateOpenSslCryptographicException(); + } + + Debug.Assert(extraHandle != IntPtr.Zero); + return new SafeEvpPKeyHandle(evpPKeyHandle, extraHandle: extraHandle); + } + + [LibraryImport(Libraries.CryptoNative, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void CryptoNative_EvpPkeyExtraHandleDestroy(IntPtr extraHandle); + internal enum EvpAlgorithmId { Unknown = 0, diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpPkeyCtxHandle.Unix.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpPkeyCtxHandle.Unix.cs index d64f135ca0f720..9a76ddab26143d 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpPkeyCtxHandle.Unix.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpPkeyCtxHandle.Unix.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.InteropServices; +using System.Security.Cryptography; namespace Microsoft.Win32.SafeHandles { @@ -26,5 +27,51 @@ protected override bool ReleaseHandle() } public override bool IsInvalid => handle == IntPtr.Zero; + + internal static SafeEvpPKeyCtxHandle CreateFromEvpPkey(SafeEvpPKeyHandle evpPkey) + { + return Interop.Crypto.EvpPKeyCtxCreate(evpPkey); + } + + internal void ConfigureForRSASign(HashAlgorithmName hashAlgorithm, RSASignaturePaddingMode padding) + { + Interop.Crypto.CryptoNative_ConfigureForRsaSign(this, padding, hashAlgorithm); + } + + internal void ConfigureForRSAVerify(HashAlgorithmName hashAlgorithm, RSASignaturePaddingMode padding) + { + Interop.Crypto.CryptoNative_ConfigureForRsaVerify(this, padding, hashAlgorithm); + } + + internal void ConfigureForECDSASign() + { + Interop.Crypto.EvpPKeyCtxConfigureForECDSASign(this); + } + + internal void ConfigureForECDSAVerify() + { + Interop.Crypto.EvpPKeyCtxConfigureForECDSAVerify(this); + } + + internal bool TryGetSufficientSignatureSizeInBytesCore( + ReadOnlySpan hash, out int bytesWritten) + { + return Interop.Crypto.TryEvpPKeyCtxSignatureSize(this, hash, out bytesWritten); + } + + internal bool TrySignHashCore( + ReadOnlySpan hash, + Span outputSignature, + out int bytesWritten) + { + return Interop.Crypto.TryEvpPKeyCtxSignHash(this, hash, outputSignature, out bytesWritten); + } + + internal bool VerifyHashCore( + ReadOnlySpan hash, + ReadOnlySpan signature) + { + return Interop.Crypto.EvpPKeyCtxVerifyHash(this, hash, signature); + } } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs index f6fc78762e043c..c4e0147296e782 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs @@ -88,10 +88,13 @@ public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPa Debug.Assert(otherPartyPublicKey != null); Debug.Assert(_key is not null); // Callers should validate prior. - // Ensure that this ECDH object contains a private key by attempting a parameter export - // which will throw an OpenSslCryptoException if no private key is available - ECParameters thisKeyExplicit = ExportExplicitParameters(true); - bool thisIsNamed = Interop.Crypto.EcKeyHasCurveName(_key.Value); + bool thisIsNamed; + + using (SafeEcKeyHandle ecKey = Interop.Crypto.EvpPkeyGetEcKey(_key)) + { + thisIsNamed = Interop.Crypto.EcKeyHasCurveName(ecKey); + } + ECDiffieHellmanOpenSslPublicKey? otherKey = otherPartyPublicKey as ECDiffieHellmanOpenSslPublicKey; bool disposeOtherKey = false; @@ -109,7 +112,11 @@ public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPa bool otherIsNamed = otherKey.HasCurveName; - SafeEvpPKeyHandle? ourKey = null; + // The only case when we will not directly use our key is when peer key is an explicit curve + // but we're using named in which case we'll recreate our key with explicit parameters. + SafeEvpPKeyHandle ourKey = _key; + bool disposeOurKey = false; + SafeEvpPKeyHandle? theirKey = null; byte[]? rented = null; int secretLength = 0; @@ -123,23 +130,33 @@ public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPa if (otherIsNamed == thisIsNamed) { - ourKey = _key.UpRefKeyHandle(); theirKey = otherKey.DuplicateKeyHandle(); } else if (otherIsNamed) { - ourKey = _key.UpRefKeyHandle(); - using (ECOpenSsl tmp = new ECOpenSsl(otherKey.ExportExplicitParameters())) { - theirKey = tmp.UpRefKeyHandle(); + theirKey = tmp.CreateKeyHandle(); } } else { - using (ECOpenSsl tmp = new ECOpenSsl(thisKeyExplicit)) + try { - ourKey = tmp.UpRefKeyHandle(); + // This is generally not expected to fail except: + // - when key can't be accessed but is available (i.e. TPM) + // - private key is actually missing + + using (ECOpenSsl tmp = new ECOpenSsl(ExportExplicitParameters(true))) + { + ourKey = tmp.CreateKeyHandle(); + disposeOurKey = true; + } + } + catch (CryptographicException) + { + // In both cases of failure we'll report lack of private key + throw new CryptographicException(SR.Cryptography_CSP_NoPrivateKey); } theirKey = otherKey.DuplicateKeyHandle(); @@ -186,13 +203,17 @@ public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPa finally { theirKey?.Dispose(); - ourKey?.Dispose(); if (disposeOtherKey) { otherKey.Dispose(); } + if (disposeOurKey) + { + ourKey.Dispose(); + } + if (rented != null) { CryptoPool.Return(rented, secretLength); diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs index c394d1fbf0b542..3015a7e1fc07aa 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs @@ -9,7 +9,7 @@ namespace System.Security.Cryptography { public sealed partial class ECDiffieHellmanOpenSsl : ECDiffieHellman { - private ECOpenSsl? _key; + private SafeEvpPKeyHandle _key; [UnsupportedOSPlatform("android")] [UnsupportedOSPlatform("browser")] @@ -19,8 +19,8 @@ public sealed partial class ECDiffieHellmanOpenSsl : ECDiffieHellman public ECDiffieHellmanOpenSsl(ECCurve curve) { ThrowIfNotSupported(); - _key = new ECOpenSsl(curve); - KeySizeValue = _key.KeySize; + _key = SafeEvpPKeyHandle.GenerateECKey(curve, out int keySize); + KeySizeValue = keySize; } [UnsupportedOSPlatform("android")] @@ -42,7 +42,7 @@ public ECDiffieHellmanOpenSsl(int keySize) { ThrowIfNotSupported(); base.KeySize = keySize; - _key = new ECOpenSsl(this); + _key = SafeEvpPKeyHandle.GenerateECKey(keySize); } public override KeySizes[] LegalKeySizes => s_defaultKeySizes.CloneKeySizesArray(); @@ -52,7 +52,7 @@ protected override void Dispose(bool disposing) if (disposing) { _key?.Dispose(); - _key = null; + _key = null!; } base.Dispose(disposing); @@ -76,14 +76,17 @@ public override int KeySize ThrowIfDisposed(); _key.Dispose(); - _key = new ECOpenSsl(this); + _key = SafeEvpPKeyHandle.GenerateECKey(value); } } public override void GenerateKey(ECCurve curve) { ThrowIfDisposed(); - KeySizeValue = _key.GenerateKey(curve); + + _key.Dispose(); + _key = SafeEvpPKeyHandle.GenerateECKey(curve, out int keySizeValue); + KeySizeValue = keySizeValue; } public override ECDiffieHellmanPublicKey PublicKey @@ -91,25 +94,37 @@ public override ECDiffieHellmanPublicKey PublicKey get { ThrowIfDisposed(); - - using (SafeEvpPKeyHandle handle = _key.UpRefKeyHandle()) - { - return new ECDiffieHellmanOpenSslPublicKey(handle); - } + return new ECDiffieHellmanOpenSslPublicKey(_key); } } public override void ImportParameters(ECParameters parameters) { ThrowIfDisposed(); - KeySizeValue = _key.ImportParameters(parameters); + _key.Dispose(); + _key = SafeEvpPKeyHandle.GenerateECKey(parameters, out int keySize); + KeySizeValue = keySize; + } + + public override ECParameters ExportExplicitParameters(bool includePrivateParameters) + { + ThrowIfDisposed(); + + using (SafeEcKeyHandle ecKey = Interop.Crypto.EvpPkeyGetEcKey(_key)) + { + return ECOpenSsl.ExportExplicitParameters(ecKey, includePrivateParameters); + } } - public override ECParameters ExportExplicitParameters(bool includePrivateParameters) => - ECOpenSsl.ExportExplicitParameters(GetKey(), includePrivateParameters); + public override ECParameters ExportParameters(bool includePrivateParameters) + { + ThrowIfDisposed(); - public override ECParameters ExportParameters(bool includePrivateParameters) => - ECOpenSsl.ExportParameters(GetKey(), includePrivateParameters); + using (SafeEcKeyHandle ecKey = Interop.Crypto.EvpPkeyGetEcKey(_key)) + { + return ECOpenSsl.ExportParameters(ecKey, includePrivateParameters); + } + } public override void ImportEncryptedPkcs8PrivateKey( ReadOnlySpan passwordBytes, @@ -135,12 +150,6 @@ private void ThrowIfDisposed() ObjectDisposedException.ThrowIf(_key is null, this); } - private SafeEcKeyHandle GetKey() - { - ThrowIfDisposed(); - return _key.Value; - } - static partial void ThrowIfNotSupported(); } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSslPublicKey.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSslPublicKey.cs index ee3a517b692aba..2dfbaa92a030ca 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSslPublicKey.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSslPublicKey.cs @@ -80,25 +80,7 @@ protected override void Dispose(bool disposing) internal SafeEvpPKeyHandle DuplicateKeyHandle() { SafeEcKeyHandle 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.EvpPkeySetEcKey(pkeyHandle, currentKey)) - { - throw Interop.Crypto.CreateOpenSslCryptographicException(); - } - - return pkeyHandle; - } - catch - { - pkeyHandle.Dispose(); - throw; - } + return Interop.Crypto.CreateEvpPkeyFromEcKey(currentKey); } [MemberNotNull(nameof(_key))] diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDsaOpenSsl.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDsaOpenSsl.cs index 68a61758b670cf..ffa1ccdb191d4c 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDsaOpenSsl.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDsaOpenSsl.cs @@ -15,7 +15,7 @@ public sealed partial class ECDsaOpenSsl : ECDsa, IRuntimeAlgorithm // secp521r1 maxes out at 139 bytes, so 256 should always be enough private const int SignatureStackBufSize = 256; - private ECOpenSsl? _key; + private Lazy? _key; /// /// Create an ECDsaOpenSsl algorithm with a named curve. @@ -30,8 +30,8 @@ public sealed partial class ECDsaOpenSsl : ECDsa, IRuntimeAlgorithm public ECDsaOpenSsl(ECCurve curve) { ThrowIfNotSupported(); - _key = new ECOpenSsl(curve); - ForceSetKeySize(_key.KeySize); + _key = new Lazy(SafeEvpPKeyHandle.GenerateECKey(curve, out int keySize)); + ForceSetKeySize(keySize); } /// @@ -59,10 +59,8 @@ public ECDsaOpenSsl() public ECDsaOpenSsl(int keySize) { ThrowIfNotSupported(); - // Use the base setter to get the validation and field assignment without the - // side effect of dereferencing _key. base.KeySize = keySize; - _key = new ECOpenSsl(this); + _key = new Lazy(GenerateKeyFromSize); } /// @@ -84,16 +82,34 @@ private void ForceSetKeySize(int newKeySize) public override byte[] SignHash(byte[] hash) { ArgumentNullException.ThrowIfNull(hash); - ThrowIfDisposed(); - SafeEcKeyHandle key = _key.Value; - int signatureLength = Interop.Crypto.EcDsaSize(key); - Span signDestination = stackalloc byte[SignatureStackBufSize]; - ReadOnlySpan derSignature = SignHash(hash, signDestination, signatureLength, key); + using (SafeEvpPKeyCtxHandle ctx = CreateCtx()) + { + ctx.ConfigureForECDSASign(); + + if (!ctx.TryGetSufficientSignatureSizeInBytesCore(hash, out int sufficientDerSignatureSize)) + { + throw new CryptographicException(); + } + + Span derSignature = sufficientDerSignatureSize <= SignatureStackBufSize ? stackalloc byte[sufficientDerSignatureSize] : new byte[sufficientDerSignatureSize]; + if (!ctx.TrySignHashCore(hash, derSignature, out int bytesWritten)) + { + throw new CryptographicException(); + } + + if (bytesWritten > derSignature.Length) + { + Debug.Fail("TrySignHashCore wrote more bytes than it claimed it would write"); + throw new CryptographicException(); + } + + derSignature = derSignature.Slice(0, bytesWritten); - byte[] converted = AsymmetricAlgorithmHelpers.ConvertDerToIeee1363(derSignature, KeySize); - return converted; + byte[] converted = AsymmetricAlgorithmHelpers.ConvertDerToIeee1363(derSignature, KeySize); + return converted; + } } public override bool TrySignHash(ReadOnlySpan hash, Span destination, out int bytesWritten) @@ -105,6 +121,14 @@ public override bool TrySignHash(ReadOnlySpan hash, Span destination out bytesWritten); } + // SafeEvpPKeyCtxHandle doesn't touch ref counts of SafeEvpPKeyHandle so we need to keep it alive during it's lifetime. + // We only use CreateCtx from within single `using` statement so we can guarantee no lifetime issues. + private SafeEvpPKeyCtxHandle CreateCtx() + { + ThrowIfDisposed(); + return SafeEvpPKeyCtxHandle.CreateFromEvpPkey(_key.Value); + } + protected override bool TrySignHashCore( ReadOnlySpan hash, Span destination, @@ -112,10 +136,6 @@ protected override bool TrySignHashCore( out int bytesWritten) { ThrowIfDisposed(); - SafeEcKeyHandle key = _key.Value; - - int signatureLength = Interop.Crypto.EcDsaSize(key); - Span signDestination = stackalloc byte[SignatureStackBufSize]; if (signatureFormat == DSASignatureFormat.IeeeP1363FixedFieldConcatenation) { @@ -127,33 +147,71 @@ protected override bool TrySignHashCore( return false; } - ReadOnlySpan derSignature = SignHash(hash, signDestination, signatureLength, key); - bytesWritten = AsymmetricAlgorithmHelpers.ConvertDerToIeee1363(derSignature, KeySize, destination); - Debug.Assert(bytesWritten == encodedSize); + using (SafeEvpPKeyCtxHandle ctx = CreateCtx()) + { + ctx.ConfigureForECDSASign(); + + if (!ctx.TryGetSufficientSignatureSizeInBytesCore(hash, out int sufficientSignatureSizeInBytes)) + { + throw Interop.Crypto.CreateOpenSslCryptographicException(); + } + + Span derSignatureDestination = sufficientSignatureSizeInBytes <= SignatureStackBufSize ? stackalloc byte[sufficientSignatureSizeInBytes] : new byte[sufficientSignatureSizeInBytes]; + if (!ctx.TrySignHashCore(hash, derSignatureDestination, out int derSignatureBytesWritten)) + { + // this is unrelated to sufficient size reason + throw Interop.Crypto.CreateOpenSslCryptographicException(); + } + + derSignatureDestination = derSignatureDestination.Slice(0, derSignatureBytesWritten); + bytesWritten = AsymmetricAlgorithmHelpers.ConvertDerToIeee1363(derSignatureDestination, KeySize, destination); + Debug.Assert(bytesWritten == encodedSize); + } + return true; } else if (signatureFormat == DSASignatureFormat.Rfc3279DerSequence) { - if (destination.Length >= signatureLength) - { - signDestination = destination; - } - else if (signatureLength > signDestination.Length) - { - Debug.Fail($"Stack-based signDestination is insufficient ({signatureLength} needed)"); - bytesWritten = 0; - return false; - } - - ReadOnlySpan derSignature = SignHash(hash, signDestination, signatureLength, key); - - if (destination == signDestination) + using (SafeEvpPKeyCtxHandle ctx = CreateCtx()) { - bytesWritten = derSignature.Length; + ctx.ConfigureForECDSASign(); + + // We could theoretically pass this through but we need to distinguish between "not enough space" and "failed" + // We could check for presence of private key but that won't work when it's an external key. + if (!ctx.TryGetSufficientSignatureSizeInBytesCore(hash, out int sufficientSignatureSizeInBytes)) + { + throw Interop.Crypto.CreateOpenSslCryptographicException(); + } + + if (destination.Length >= sufficientSignatureSizeInBytes) + { + // The only reason this could fail won't be related to buffer size + if (!ctx.TrySignHashCore(hash, destination, out bytesWritten)) + { + throw Interop.Crypto.CreateOpenSslCryptographicException(); + } + + return true; + } + + // Since sufficientSignatureSizeInBytes can be more than what's actually needed + // we need temporary buffer of sufficient size and see if operation can succeed with that + Span derSignatureDestination = sufficientSignatureSizeInBytes <= SignatureStackBufSize ? stackalloc byte[sufficientSignatureSizeInBytes] : new byte[sufficientSignatureSizeInBytes]; + if (!ctx.TrySignHashCore(hash, destination, out int bytesWrittenToTemporaryBuffer)) + { + throw Interop.Crypto.CreateOpenSslCryptographicException(); + } + + if (bytesWrittenToTemporaryBuffer > destination.Length) + { + bytesWritten = 0; + return false; + } + + derSignatureDestination.CopyTo(destination); + bytesWritten = bytesWrittenToTemporaryBuffer; return true; } - - return Helpers.TryCopyToDestination(derSignature, destination, out bytesWritten); } else { @@ -161,33 +219,6 @@ protected override bool TrySignHashCore( } } - private static ReadOnlySpan SignHash( - ReadOnlySpan hash, - Span destination, - int signatureLength, - SafeEcKeyHandle key) - { - if (signatureLength > destination.Length) - { - Debug.Fail($"Stack-based signDestination is insufficient ({signatureLength} needed)"); - destination = new byte[signatureLength]; - } - - if (!Interop.Crypto.EcDsaSign(hash, destination, out int actualLength, key)) - { - throw Interop.Crypto.CreateOpenSslCryptographicException(); - } - - Debug.Assert( - actualLength <= signatureLength, - "ECDSA_sign reported an unexpected signature size", - "ECDSA_sign reported signatureSize was {0}, when <= {1} was expected", - actualLength, - signatureLength); - - return destination.Slice(0, actualLength); - } - public override bool VerifyHash(byte[] hash, byte[] signature) { ArgumentNullException.ThrowIfNull(hash); @@ -242,16 +273,18 @@ protected override bool VerifyHashCore( signatureFormat.ToString()); } - SafeEcKeyHandle key = _key.Value; - int verifyResult = Interop.Crypto.EcDsaVerify(hash, toVerify, key); - return verifyResult == 1; + using (SafeEvpPKeyCtxHandle ctx = CreateCtx()) + { + ctx.ConfigureForECDSAVerify(); + return ctx.VerifyHashCore(hash, toVerify); + } } protected override void Dispose(bool disposing) { if (disposing) { - _key?.Dispose(); + FreeKey(); _key = null; } @@ -273,38 +306,59 @@ public override int KeySize base.KeySize = value; ThrowIfDisposed(); - _key.Dispose(); - _key = new ECOpenSsl(this); + + FreeKey(); + _key = new Lazy(GenerateKeyFromSize); } } public override void GenerateKey(ECCurve curve) { ThrowIfDisposed(); - _key.GenerateKey(curve); + + FreeKey(); + _key = new Lazy(SafeEvpPKeyHandle.GenerateECKey(curve, out int keySize)); // Use ForceSet instead of the property setter to ensure that LegalKeySizes doesn't interfere // with the already loaded key. - ForceSetKeySize(_key.KeySize); + ForceSetKeySize(keySize); } public override void ImportParameters(ECParameters parameters) { ThrowIfDisposed(); - _key.ImportParameters(parameters); - ForceSetKeySize(_key.KeySize); + + FreeKey(); + _key = new Lazy(SafeEvpPKeyHandle.GenerateECKey(parameters, out int keySize)); + + // Use ForceSet instead of the property setter to ensure that LegalKeySizes doesn't interfere + // with the already loaded key. + ForceSetKeySize(keySize); + } + + private SafeEvpPKeyHandle GenerateKeyFromSize() + { + return SafeEvpPKeyHandle.GenerateECKey(KeySize); } public override ECParameters ExportExplicitParameters(bool includePrivateParameters) { ThrowIfDisposed(); - return ECOpenSsl.ExportExplicitParameters(_key.Value, includePrivateParameters); + + using (SafeEcKeyHandle ecKey = Interop.Crypto.EvpPkeyGetEcKey(_key.Value)) + { + return ECOpenSsl.ExportExplicitParameters(ecKey, includePrivateParameters); + } } public override ECParameters ExportParameters(bool includePrivateParameters) { ThrowIfDisposed(); - return ECOpenSsl.ExportParameters(_key.Value, includePrivateParameters); + + using (SafeEcKeyHandle ecKey = Interop.Crypto.EvpPkeyGetEcKey(_key.Value)) + { + return ECOpenSsl.ExportParameters(ecKey, includePrivateParameters); + } } public override void ImportEncryptedPkcs8PrivateKey( @@ -325,6 +379,15 @@ public override void ImportEncryptedPkcs8PrivateKey( base.ImportEncryptedPkcs8PrivateKey(password, source, out bytesRead); } + private void FreeKey() + { + if (_key != null && _key.IsValueCreated) + { + SafeEvpPKeyHandle handle = _key.Value; + handle?.Dispose(); + } + } + [MemberNotNull(nameof(_key))] private void ThrowIfDisposed() { diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs b/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs index 29c5d8c944a24d..0c95d5bf1fb5d8 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs @@ -15,9 +15,9 @@ public ECOpenSsl(ECCurve curve) GenerateKey(curve); } - public ECOpenSsl(AsymmetricAlgorithm owner) + public ECOpenSsl(int keySizeBits) { - _key = new Lazy(() => GenerateKeyLazy(owner)); + _key = new Lazy(() => GenerateKeyByKeySize(keySizeBits)); } public ECOpenSsl(ECParameters ecParameters) @@ -25,6 +25,7 @@ public ECOpenSsl(ECParameters ecParameters) ImportParameters(ecParameters); } + // Takes ownership of the key public ECOpenSsl(SafeEcKeyHandle key) { _key = new Lazy(key); @@ -32,9 +33,6 @@ public ECOpenSsl(SafeEcKeyHandle key) internal SafeEcKeyHandle Value => _key.Value; - private static SafeEcKeyHandle GenerateKeyLazy(AsymmetricAlgorithm owner) => - GenerateKeyByKeySize(owner.KeySize); - public void Dispose() { FreeKey(); @@ -42,33 +40,15 @@ public void Dispose() internal int KeySize => Interop.Crypto.EcKeyGetSize(_key.Value); - internal SafeEvpPKeyHandle UpRefKeyHandle() + internal SafeEvpPKeyHandle CreateKeyHandle() { SafeEcKeyHandle currentKey = _key.Value; - Debug.Assert(currentKey != null, "null TODO"); - - 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.EvpPkeySetEcKey(pkeyHandle, currentKey)) - { - throw Interop.Crypto.CreateOpenSslCryptographicException(); - } + Debug.Assert(currentKey != null, "key is null"); - return pkeyHandle; - } - catch - { - pkeyHandle.Dispose(); - throw; - } + return Interop.Crypto.CreateEvpPkeyFromEcKey(currentKey); } - internal void SetKey(SafeEcKeyHandle key) + private void SetKey(SafeEcKeyHandle key) { Debug.Assert(key != null, "key != null"); Debug.Assert(!key.IsInvalid, "!key.IsInvalid"); diff --git a/src/libraries/Common/src/System/Security/Cryptography/RSAOpenSsl.cs b/src/libraries/Common/src/System/Security/Cryptography/RSAOpenSsl.cs index 7e6efbb2da57ab..6b0b32d7420ab3 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/RSAOpenSsl.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/RSAOpenSsl.cs @@ -15,8 +15,6 @@ namespace System.Security.Cryptography { public sealed partial class RSAOpenSsl : RSA, IRuntimeAlgorithm { - private const int BitsPerByte = 8; - private Lazy? _key; [UnsupportedOSPlatform("android")] @@ -88,7 +86,7 @@ public override byte[] Decrypt(byte[] data, RSAEncryptionPadding padding) ValidatePadding(padding); SafeEvpPKeyHandle key = GetKey(); - int rsaSize = Interop.Crypto.EvpPKeySize(key); + int rsaSize = key.GetKeySizeBytes(); Span destination = default; byte[] buf = CryptoPool.Rent(rsaSize); @@ -116,11 +114,10 @@ public override bool TryDecrypt( ValidatePadding(padding); SafeEvpPKeyHandle key = GetKey(); - int keySizeBytes = Interop.Crypto.EvpPKeySize(key); - // 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. + // For RSA decryption buffer is equal to key size in bytes + int keySizeBytes = key.GetKeySizeBytes(); + if (destination.Length < keySizeBytes) { // RSA up through 4096 bits use a stackalloc @@ -174,7 +171,7 @@ private static int Decrypt( // Caller should have already checked this. Debug.Assert(!key.IsInvalid); - int rsaSize = Interop.Crypto.EvpPKeySize(key); + int rsaSize = key.GetKeySizeBytes(); if (data.Length != rsaSize) { @@ -183,7 +180,7 @@ private static int Decrypt( if (destination.Length < rsaSize) { - Debug.Fail("Caller is responsible for temporary decryption buffer creation"); + Debug.Fail($"Caller is responsible for temporary decryption buffer creation destination. {nameof(destination)}.{nameof(destination.Length)} == {destination.Length}, {nameof(rsaSize)} = {rsaSize}"); throw new CryptographicException(); } @@ -211,7 +208,7 @@ public override byte[] Encrypt(byte[] data, RSAEncryptionPadding padding) ValidatePadding(padding); SafeEvpPKeyHandle key = GetKey(); - byte[] buf = new byte[Interop.Crypto.EvpPKeySize(key)]; + byte[] buf = new byte[key.GetKeySizeBytes()]; bool encrypted = TryEncrypt( key, @@ -246,7 +243,7 @@ private static bool TryEncrypt( RSAEncryptionPadding padding, out int bytesWritten) { - int rsaSize = Interop.Crypto.EvpPKeySize(key); + int rsaSize = key.GetKeySizeBytes(); if (destination.Length < rsaSize) { @@ -661,7 +658,7 @@ private void SetKey(SafeEvpPKeyHandle newKey) // Use ForceSet instead of the property setter to ensure that LegalKeySizes doesn't interfere // with the already loaded key. - ForceSetKeySize(BitsPerByte * Interop.Crypto.EvpPKeySize(newKey)); + ForceSetKeySize(newKey.GetKeySizeBits()); } private static void ValidateParameters(ref RSAParameters parameters) @@ -723,7 +720,7 @@ private SafeEvpPKeyHandle GetKey() private SafeEvpPKeyHandle GenerateKey() { - return Interop.Crypto.RsaGenerateKey(KeySize); + return SafeEvpPKeyHandle.GenerateRSAKey(KeySize); } public override byte[] SignHash(byte[] hash, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding) @@ -731,21 +728,40 @@ public override byte[] SignHash(byte[] hash, HashAlgorithmName hashAlgorithm, RS ArgumentNullException.ThrowIfNull(hash); ArgumentException.ThrowIfNullOrEmpty(hashAlgorithm.Name, nameof(hashAlgorithm)); ArgumentNullException.ThrowIfNull(padding); + ThrowIfDisposed(); - if (!TrySignHash( - hash, - Span.Empty, - hashAlgorithm, padding, - true, - out _, - out byte[]? signature)) + using (SafeEvpPKeyCtxHandle ctx = CreateCtx()) { - Debug.Fail("TrySignHash should not return false in allocation mode"); - throw new CryptographicException(); - } + ctx.ConfigureForRSASign(hashAlgorithm, padding.Mode); + + if (!ctx.TryGetSufficientSignatureSizeInBytesCore(hash, out int sufficientDerSignatureSize)) + { + throw new CryptographicException(); + } + + byte[] signature = new byte[sufficientDerSignatureSize]; + if (!ctx.TrySignHashCore(hash, signature, out int bytesWritten)) + { + throw new CryptographicException(); + } + + if (bytesWritten > signature.Length) + { + Debug.Fail("TrySignHashCore wrote more bytes than it claimed it would write"); + throw new CryptographicException(); + } - Debug.Assert(signature != null); - return signature; + if (bytesWritten == signature.Length) + { + return signature; + } + else + { + byte[] ret = new byte[bytesWritten]; + new ReadOnlySpan(signature).Slice(0, bytesWritten).CopyTo(ret); + return ret; + } + } } public override bool TrySignHash( @@ -758,55 +774,75 @@ public override bool TrySignHash( ArgumentException.ThrowIfNullOrEmpty(hashAlgorithm.Name, nameof(hashAlgorithm)); ArgumentNullException.ThrowIfNull(padding); - bool ret = TrySignHash( + return TrySignHashCore( hash, destination, hashAlgorithm, padding, - false, - out bytesWritten, - out byte[]? alloced); + out bytesWritten); + } - Debug.Assert(alloced == null); - return ret; + // SafeEvpPKeyCtxHandle doesn't touch ref counts of SafeEvpPKeyHandle so we need to keep it alive during it's lifetime. + // We only use CreateCtx from within single `using` statement so we can guarantee no lifetime issues. + private SafeEvpPKeyCtxHandle CreateCtx() + { + ThrowIfDisposed(); + return SafeEvpPKeyCtxHandle.CreateFromEvpPkey(GetKey()); } - private bool TrySignHash( + private bool TrySignHashCore( ReadOnlySpan hash, Span destination, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding, - bool allocateSignature, - out int bytesWritten, - out byte[]? signature) + out int bytesWritten) { Debug.Assert(!string.IsNullOrEmpty(hashAlgorithm.Name)); Debug.Assert(padding != null); ValidatePadding(padding); + ThrowIfDisposed(); - signature = null; + using (SafeEvpPKeyCtxHandle ctx = CreateCtx()) + { + ctx.ConfigureForRSASign(hashAlgorithm, padding.Mode); - IntPtr digestAlgorithm = Interop.Crypto.HashAlgorithmToEvp(hashAlgorithm.Name); - SafeEvpPKeyHandle key = GetKey(); - int bytesRequired = Interop.Crypto.EvpPKeySize(key); + // We could theoretically pass this through but we need to distinguish between "not enough space" and "failed" + // We could check for presence of private key but that won't work when it's an external key. + if (!ctx.TryGetSufficientSignatureSizeInBytesCore(hash, out int sufficientSignatureSizeInBytes)) + { + throw Interop.Crypto.CreateOpenSslCryptographicException(); + } - if (allocateSignature) - { - Debug.Assert(destination.Length == 0); - signature = new byte[bytesRequired]; - destination = signature; - } - else if (destination.Length < bytesRequired) - { - bytesWritten = 0; - return false; - } + if (destination.Length >= sufficientSignatureSizeInBytes) + { + if (!ctx.TrySignHashCore(hash, destination, out bytesWritten)) + { + // The only reason this could fail won't be related to buffer size so we throw rather returning false + throw Interop.Crypto.CreateOpenSslCryptographicException(); + } - int written = Interop.Crypto.RsaSignHash(key, padding.Mode, digestAlgorithm, hash, destination); - Debug.Assert(written == bytesRequired); - bytesWritten = written; + return true; + } - return true; + // Since sufficientSignatureSizeInBytes can be more than what's actually needed + // we use temporary buffer of sufficient size and see if operation can succeed with that + byte[] signatureDestination = new byte[sufficientSignatureSizeInBytes]; + if (!ctx.TrySignHashCore(hash, signatureDestination, out int bytesWrittenToTemporaryBuffer)) + { + // There is really no reason for this to fail since we already allocated enough + throw Interop.Crypto.CreateOpenSslCryptographicException(); + } + + if (bytesWrittenToTemporaryBuffer > destination.Length) + { + bytesWritten = 0; + return false; + } + + signatureDestination.CopyTo(destination); + bytesWritten = bytesWrittenToTemporaryBuffer; + return true; + } } public override bool VerifyHash( @@ -826,15 +862,11 @@ public override bool VerifyHash(ReadOnlySpan hash, ReadOnlySpan sign ArgumentException.ThrowIfNullOrEmpty(hashAlgorithm.Name, nameof(hashAlgorithm)); ValidatePadding(padding); - IntPtr digestAlgorithm = Interop.Crypto.HashAlgorithmToEvp(hashAlgorithm.Name); - SafeEvpPKeyHandle key = GetKey(); - - return Interop.Crypto.RsaVerifyHash( - key, - padding.Mode, - digestAlgorithm, - hash, - signature); + using (SafeEvpPKeyCtxHandle ctx = CreateCtx()) + { + ctx.ConfigureForRSAVerify(hashAlgorithm, padding.Mode); + return ctx.VerifyHashCore(hash, signature); + } } private static ReadOnlyMemory VerifyPkcs8(ReadOnlyMemory pkcs8) 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 11409ac9857c14..9913bf2a3ee49e 100644 --- a/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs +++ b/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs @@ -2364,6 +2364,12 @@ public sealed partial class SafeEvpPKeyHandle : System.Runtime.InteropServices.S [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")] [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")] [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")] + public static System.Security.Cryptography.SafeEvpPKeyHandle OpenKeyFromProvider(string providerName, string keyUri) { throw null; } + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")] + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")] + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")] + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")] public static System.Security.Cryptography.SafeEvpPKeyHandle OpenPrivateKeyFromEngine(string engineName, string keyId) { throw null; } [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")] [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] 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 b9f0ce5317f7f9..574e3b3065e754 100644 --- a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj +++ b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj @@ -729,8 +729,6 @@ Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.Crypto.cs" /> - - @@ -63,38 +56,20 @@ public ECDiffieHellmanOpenSsl(IntPtr handle) throw new ArgumentException(SR.Cryptography_OpenInvalidHandle, nameof(handle)); ThrowIfNotSupported(); + SafeEcKeyHandle ecKeyHandle = SafeEcKeyHandle.DuplicateHandle(handle); - _key = new ECOpenSsl(ecKeyHandle); - KeySizeValue = _key.KeySize; + _key = Interop.Crypto.CreateEvpPkeyFromEcKey(ecKeyHandle); + KeySizeValue = _key.GetKeySizeBits(); } /// - /// Obtain a SafeHandle version of an EVP_PKEY* which wraps an EC_KEY* equivalent + /// Obtain a SafeHandle version of an EVP_PKEY* /// to the current key for this instance. /// - /// A SafeHandle for the EC_KEY key in OpenSSL + /// A SafeHandle for the EVP_PKEY key in OpenSSL public SafeEvpPKeyHandle DuplicateKeyHandle() { - SafeEcKeyHandle 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.EvpPkeySetEcKey(pkeyHandle, currentKey)) - { - throw Interop.Crypto.CreateOpenSslCryptographicException(); - } - - return pkeyHandle; - } - catch - { - pkeyHandle.Dispose(); - throw; - } + return _key.DuplicateHandle(); } static partial void ThrowIfNotSupported() diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDsaOpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDsaOpenSsl.cs index 0887f0a9fb8a63..c32049b634bafc 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDsaOpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDsaOpenSsl.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.Diagnostics; using System.Runtime.InteropServices; using System.Runtime.Versioning; using Microsoft.Win32.SafeHandles; @@ -30,16 +31,9 @@ public ECDsaOpenSsl(SafeEvpPKeyHandle pkeyHandle) throw new ArgumentException(SR.Cryptography_OpenInvalidHandle, nameof(pkeyHandle)); ThrowIfNotSupported(); - // If ecKey is valid it has already been up-ref'd, so we can just use this handle as-is. - SafeEcKeyHandle key = Interop.Crypto.EvpPkeyGetEcKey(pkeyHandle); - if (key.IsInvalid) - { - key.Dispose(); - throw Interop.Crypto.CreateOpenSslCryptographicException(); - } - _key = new ECOpenSsl(key); - KeySizeValue = _key.KeySize; + _key = new Lazy(pkeyHandle.DuplicateHandle()); + ForceSetKeySize(pkeyHandle.GetKeySizeBits()); } /// @@ -63,39 +57,24 @@ public ECDsaOpenSsl(IntPtr handle) throw new ArgumentException(SR.Cryptography_OpenInvalidHandle, nameof(handle)); ThrowIfNotSupported(); + + // This handle is meant for ECOpenSsl which will take ownership of this handle and will dispose it. SafeEcKeyHandle ecKeyHandle = SafeEcKeyHandle.DuplicateHandle(handle); - _key = new ECOpenSsl(ecKeyHandle); - KeySizeValue = _key.KeySize; + + // CreateEvpPkeyFromEcKey already uprefs so nothing else to do + _key = new Lazy(Interop.Crypto.CreateEvpPkeyFromEcKey(ecKeyHandle)); + ForceSetKeySize(_key.Value.GetKeySizeBits()); } /// - /// Obtain a SafeHandle version of an EVP_PKEY* which wraps an EC_KEY* equivalent + /// Obtain a SafeHandle version of an EVP_PKEY* equivalent /// to the current key for this instance. /// - /// A SafeHandle for the EC_KEY key in OpenSSL + /// A SafeHandle for the EVP_PKEY key in OpenSSL public SafeEvpPKeyHandle DuplicateKeyHandle() { ThrowIfDisposed(); - SafeEcKeyHandle currentKey = _key.Value; - 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.EvpPkeySetEcKey(pkeyHandle, currentKey)) - { - throw Interop.Crypto.CreateOpenSslCryptographicException(); - } - - return pkeyHandle; - } - catch - { - pkeyHandle.Dispose(); - throw; - } + return _key.Value.DuplicateHandle(); } static partial void ThrowIfNotSupported() diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/OpenSsl.NotSupported.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/OpenSsl.NotSupported.cs index cbbfc9912d2e19..19d633684aa4f8 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/OpenSsl.NotSupported.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/OpenSsl.NotSupported.cs @@ -279,6 +279,14 @@ public static SafeEvpPKeyHandle OpenPrivateKeyFromEngine(string engineName, stri public static SafeEvpPKeyHandle OpenPublicKeyFromEngine(string engineName, string keyId) => throw new PlatformNotSupportedException(SR.PlatformNotSupported_CryptographyOpenSSL); + [UnsupportedOSPlatform("android")] + [UnsupportedOSPlatform("browser")] + [UnsupportedOSPlatform("ios")] + [UnsupportedOSPlatform("tvos")] + [UnsupportedOSPlatform("windows")] + public static SafeEvpPKeyHandle OpenKeyFromProvider(string providerName, string keyUri) => + throw new PlatformNotSupportedException(SR.PlatformNotSupported_CryptographyOpenSSL); + public SafeEvpPKeyHandle DuplicateHandle() => null!; public override bool IsInvalid => true; protected override bool ReleaseHandle() => false; diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSAOpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSAOpenSsl.cs index 60fdd0ab1d61ee..c7e2cf3beb8af0 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSAOpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSAOpenSsl.cs @@ -73,10 +73,8 @@ public RSAOpenSsl(SafeEvpPKeyHandle pkeyHandle) throw new ArgumentException(SR.Cryptography_OpenInvalidHandle, nameof(pkeyHandle)); ThrowIfNotSupported(); - SafeEvpPKeyHandle newKey = Interop.Crypto.EvpPKeyDuplicate( - pkeyHandle, - Interop.Crypto.EvpAlgorithmId.RSA); + SafeEvpPKeyHandle newKey = pkeyHandle.DuplicateHandle(); SetKey(newKey); } @@ -87,7 +85,7 @@ public RSAOpenSsl(SafeEvpPKeyHandle pkeyHandle) /// A SafeHandle for the RSA key in OpenSSL public SafeEvpPKeyHandle DuplicateKeyHandle() { - return Interop.Crypto.EvpPKeyDuplicate(GetKey(), Interop.Crypto.EvpAlgorithmId.RSA); + return GetKey().DuplicateHandle(); } static partial void ThrowIfNotSupported() diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SafeEvpPKeyHandle.OpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SafeEvpPKeyHandle.OpenSsl.cs index 5cebb67ab9d71b..776251b0149870 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SafeEvpPKeyHandle.OpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SafeEvpPKeyHandle.OpenSsl.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Runtime.InteropServices; using System.Runtime.Versioning; +using Microsoft.Win32.SafeHandles; namespace System.Security.Cryptography { @@ -11,6 +12,12 @@ public sealed partial class SafeEvpPKeyHandle : SafeHandle { internal static readonly SafeEvpPKeyHandle InvalidHandle = new SafeEvpPKeyHandle(); + /// + /// In some cases like when a key is loaded from a provided, the key may have an associated data + /// we need to keep alive for the lifetime of the key. This field is used to track that data. + /// + internal IntPtr ExtraHandle { get; private set; } = IntPtr.Zero; + [UnsupportedOSPlatform("android")] [UnsupportedOSPlatform("browser")] [UnsupportedOSPlatform("ios")] @@ -31,13 +38,53 @@ public SafeEvpPKeyHandle(IntPtr handle, bool ownsHandle) { } + internal SafeEvpPKeyHandle(IntPtr handle, IntPtr extraHandle) + : base(handle, ownsHandle: true) + { + ExtraHandle = extraHandle; + } + protected override bool ReleaseHandle() { - Interop.Crypto.EvpPkeyDestroy(handle); + Interop.Crypto.EvpPkeyDestroy(handle, ExtraHandle); + ExtraHandle = IntPtr.Zero; + SetHandle(IntPtr.Zero); return true; } + internal static SafeEvpPKeyHandle GenerateRSAKey(int keySize) + { + return Interop.Crypto.RsaGenerateKey(keySize); + } + + internal static SafeEvpPKeyHandle GenerateECKey(int keySize) + { + SafeEvpPKeyHandle ret = GenerateECKeyCore(new ECOpenSsl(keySize), out int createdKeySize); + Debug.Assert(keySize == createdKeySize); + return ret; + } + + internal static SafeEvpPKeyHandle GenerateECKey(ECCurve curve, out int keySize) + { + return GenerateECKeyCore(new ECOpenSsl(curve), out keySize); + } + + internal static SafeEvpPKeyHandle GenerateECKey(ECParameters parameters, out int keySize) + { + return GenerateECKeyCore(new ECOpenSsl(parameters), out keySize); + } + + private static SafeEvpPKeyHandle GenerateECKeyCore(ECOpenSsl ecOpenSsl, out int keySize) + { + using (ECOpenSsl ec = ecOpenSsl) + { + SafeEvpPKeyHandle handle = Interop.Crypto.CreateEvpPkeyFromEcKey(ec.Value); + keySize = ec.KeySize; + return handle; + } + } + public override bool IsInvalid { get { return handle == IntPtr.Zero; } @@ -70,6 +117,8 @@ public SafeEvpPKeyHandle DuplicateHandle() // Since we didn't actually create a new handle, copy the handle // to the new SafeHandle. safeHandle.SetHandle(handle); + // ExtraHandle is upref'd by UpRefEvpPkey + safeHandle.ExtraHandle = ExtraHandle; return safeHandle; } @@ -177,5 +226,50 @@ public static SafeEvpPKeyHandle OpenPublicKeyFromEngine(string engineName, strin return Interop.Crypto.LoadPublicKeyFromEngine(engineName, keyId); } + + [UnsupportedOSPlatform("android")] + [UnsupportedOSPlatform("browser")] + [UnsupportedOSPlatform("ios")] + [UnsupportedOSPlatform("tvos")] + [UnsupportedOSPlatform("windows")] + public static SafeEvpPKeyHandle OpenKeyFromProvider(string providerName, string keyUri) + { + ArgumentException.ThrowIfNullOrEmpty(providerName); + ArgumentException.ThrowIfNullOrEmpty(keyUri); + + if (!Interop.OpenSslNoInit.OpenSslIsAvailable) + { + throw new PlatformNotSupportedException(SR.PlatformNotSupported_CryptographyOpenSSL); + } + + return Interop.Crypto.LoadKeyFromProvider(providerName, keyUri); + } + + internal int GetKeySizeBits() + { + return Interop.Crypto.EvpPKeyBits(this); + } + + internal int GetKeySizeBytes() + { + // EVP_PKEY_size returns the maximum suitable size for the output buffers for almost all operations that can be done with the key. + // For most of the OpenSSL 'default' provider keys it will return the same size as this method, + // but other providers such as 'tpm2' it may return larger size. + // Instead we will round up EVP_PKEY_bits result. + int keySizeBits = GetKeySizeBits(); + + if (keySizeBits <= 0) + { + Debug.Fail($"EVP_PKEY_bits returned non-positive value: {keySizeBits}"); + throw new CryptographicException(); + } + + return (GetKeySizeBits() + 7) / 8; + } + + internal Interop.Crypto.EvpAlgorithmId GetKeyType() + { + return Interop.Crypto.EvpPKeyType(this); + } } } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslExportProvider.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslExportProvider.cs index 355dbc303865d5..8e673efd607880 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslExportProvider.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslExportProvider.cs @@ -22,40 +22,24 @@ protected override byte[] ExportPkcs8( ICertificatePalCore certificatePal, ReadOnlySpan password) { - AsymmetricAlgorithm? alg = null; + AsymmetricAlgorithm alg; SafeEvpPKeyHandle? privateKey = ((OpenSslX509CertificateReader)certificatePal).PrivateKeyHandle; - try + switch (privateKey?.GetKeyType()) { - alg = new RSAOpenSsl(privateKey!); - } - catch (CryptographicException) - { - } - - if (alg == null) - { - try - { - alg = new ECDsaOpenSsl(privateKey!); - } - catch (CryptographicException) - { - } - } - - if (alg == null) - { - try - { - alg = new DSAOpenSsl(privateKey!); - } - catch (CryptographicException) - { - } + case Interop.Crypto.EvpAlgorithmId.RSA: + alg = new RSAOpenSsl(privateKey); + break; + case Interop.Crypto.EvpAlgorithmId.ECC: + alg = new ECDsaOpenSsl(privateKey); + break; + case Interop.Crypto.EvpAlgorithmId.DSA: + alg = new DSAOpenSsl(privateKey); + break; + default: + throw new CryptographicException(SR.Cryptography_InvalidHandle); } - Debug.Assert(alg != null); return alg.ExportEncryptedPkcs8PrivateKey(password, s_windowsPbe); } diff --git a/src/libraries/System.Security.Cryptography/tests/OpenSslNamedKeysTests.manual.cs b/src/libraries/System.Security.Cryptography/tests/OpenSslNamedKeysTests.manual.cs index 06cd2ed2721a11..af073eaf9699b8 100644 --- a/src/libraries/System.Security.Cryptography/tests/OpenSslNamedKeysTests.manual.cs +++ b/src/libraries/System.Security.Cryptography/tests/OpenSslNamedKeysTests.manual.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.Linq; using Test.Cryptography; using Xunit; @@ -9,26 +10,58 @@ namespace System.Security.Cryptography.Tests // See osslplugins/README.md for instructions on how to build and install the test engine and setup for TPM tests. public class OpenSslNamedKeysTests { - private const string EnvVarPrefix = "DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_"; - private const string TestEngineEnabledEnvVarName = EnvVarPrefix + "ENABLE"; - private const string TestEngineEnsureFailingEnvVarName = EnvVarPrefix + "ENSURE_FAILING"; - private const string TpmTssEngineEcDsaKeyHandleEnvVarName = EnvVarPrefix + "TPM_ECDSA_KEY_HANDLE"; + private const string EnvVarPrefix = "DOTNET_CRYPTOGRAPHY_TESTS_"; + + private const string TestEnsureFailingEnvVarName = EnvVarPrefix + "ENSURE_FAILING"; + + private const string EngineEnvVarPrefix = EnvVarPrefix + "ENGINE_"; + private const string TestEngineEnabledEnvVarName = EngineEnvVarPrefix + "ENABLE"; + + private const string TpmEnvVarPrefix = EnvVarPrefix + "TPM_"; + private const string TpmEcDsaKeyHandleEnvVarName = TpmEnvVarPrefix + "ECDSA_KEY_HANDLE"; + private const string TpmEcDhKeyHandleEnvVarName = TpmEnvVarPrefix + "ECDH_KEY_HANDLE"; + private const string TpmRsaSignKeyHandleEnvVarName = TpmEnvVarPrefix + "RSA_SIGN_KEY_HANDLE"; + private const string TpmRsaDecryptKeyHandleEnvVarName = TpmEnvVarPrefix + "RSA_DECRYPT_KEY_HANDLE"; private const string NonExistingEngineName = "dntestnonexisting"; - private const string NonExistingEngineKeyName = "nonexisting"; + private const string NonExistingEngineOrProviderKeyName = "nonexisting"; private const string TestEngineName = "dntest"; private const string TestEngineKeyId = "first"; private const string TpmTssEngineName = "tpm2tss"; - public static string TpmTssEngineEcDsaKeyHandle { get; } = Environment.GetEnvironmentVariable(TpmTssEngineEcDsaKeyHandleEnvVarName); - public static bool ShouldRunEngineTests { get; } = PlatformDetection.OpenSslPresentOnSystem && StringToBool(Environment.GetEnvironmentVariable(TestEngineEnabledEnvVarName)); - public static bool ShouldFailTests { get; } = StringToBool(Environment.GetEnvironmentVariable(TestEngineEnsureFailingEnvVarName)); - public static bool ShouldRunTpmTssTests => PlatformDetection.OpenSslPresentOnSystem && !string.IsNullOrEmpty(TpmTssEngineEcDsaKeyHandle); + private const string Tpm2ProviderName = "tpm2"; + + private static string TpmEcDsaKeyHandle { get; } = Environment.GetEnvironmentVariable(TpmEcDsaKeyHandleEnvVarName); + private static string TpmEcDsaKeyHandleUri { get; } = GetHandleKeyUri(TpmEcDsaKeyHandle); + + private static string TpmEcDhKeyHandle { get; } = Environment.GetEnvironmentVariable(TpmEcDhKeyHandleEnvVarName); + private static string TpmEcDhKeyHandleUri { get; } = GetHandleKeyUri(TpmEcDhKeyHandle); + + private static string TpmRsaSignKeyHandle { get; } = Environment.GetEnvironmentVariable(TpmRsaSignKeyHandleEnvVarName); + private static string TpmRsaSignKeyHandleUri { get; } = GetHandleKeyUri(TpmRsaSignKeyHandle); + + private static string TpmRsaDecryptKeyHandle { get; } = Environment.GetEnvironmentVariable(TpmRsaDecryptKeyHandleEnvVarName); + private static string TpmRsaDecryptKeyHandleUri { get; } = GetHandleKeyUri(TpmRsaDecryptKeyHandle); + + public static bool ShouldRunEngineTests { get; } = PlatformDetection.OpenSslPresentOnSystem && StringToBool(Environment.GetEnvironmentVariable(TestEngineEnabledEnvVarName)); + public static bool ShouldRunProviderEcDsaTests { get; } = PlatformDetection.OpenSslPresentOnSystem && !string.IsNullOrEmpty(TpmEcDsaKeyHandleUri); + public static bool ShouldRunProviderEcDhTests { get; } = PlatformDetection.OpenSslPresentOnSystem && !string.IsNullOrEmpty(TpmEcDhKeyHandleUri); + public static bool ShouldRunProviderRsaSignTests { get; } = PlatformDetection.OpenSslPresentOnSystem && !string.IsNullOrEmpty(TpmRsaSignKeyHandleUri); + public static bool ShouldRunProviderRsaDecryptTests { get; } = PlatformDetection.OpenSslPresentOnSystem && !string.IsNullOrEmpty(TpmRsaDecryptKeyHandleUri); + public static bool ShouldRunAnyProviderTests => ShouldRunProviderEcDsaTests || ShouldRunProviderEcDhTests || ShouldRunProviderRsaSignTests || ShouldRunProviderRsaDecryptTests; + + public static bool ShouldFailTests { get; } = StringToBool(Environment.GetEnvironmentVariable(TestEnsureFailingEnvVarName)); + public static bool ShouldRunTpmTssTests => ShouldRunEngineTests && !string.IsNullOrEmpty(TpmEcDsaKeyHandle); + + private static readonly string AnyProviderKeyUri = TpmEcDsaKeyHandleUri ?? TpmEcDhKeyHandleUri ?? TpmRsaSignKeyHandleUri ?? TpmRsaDecryptKeyHandleUri ?? "test"; private static bool StringToBool(string? value) => "true".Equals(value, StringComparison.OrdinalIgnoreCase) || value == "1"; + private static string GetHandleKeyUri(string handle) + => handle != null ? $"handle:{handle}" : null; + // PKCS#1 format private static readonly byte[] s_rsaPrivateKey = ( "3082025C02010002818100BF67168485215A6AB89BCAB9331F6F5F360F4300BE5CF282F77042957E" + @@ -60,6 +93,7 @@ public static void NotSupported() { Assert.Throws(() => SafeEvpPKeyHandle.OpenPublicKeyFromEngine(TestEngineName, TestEngineKeyId)); Assert.Throws(() => SafeEvpPKeyHandle.OpenPrivateKeyFromEngine(TestEngineName, TestEngineKeyId)); + Assert.Throws(() => SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, AnyProviderKeyUri)); } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.OpenSslPresentOnSystem))] @@ -70,32 +104,35 @@ public static void NullArguments() Assert.Throws("engineName", () => SafeEvpPKeyHandle.OpenPublicKeyFromEngine(null, TestEngineKeyId)); Assert.Throws("keyId", () => SafeEvpPKeyHandle.OpenPublicKeyFromEngine(TestEngineName, null)); + + Assert.Throws(() => SafeEvpPKeyHandle.OpenKeyFromProvider(null, AnyProviderKeyUri)); + Assert.Throws(() => SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, null)); } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.OpenSslPresentOnSystem))] - public static void NonExistingEngine() + public static void Engine_NonExisting() { - Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPrivateKeyFromEngine(NonExistingEngineName, TestEngineKeyId)); - Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPublicKeyFromEngine(NonExistingEngineName, TestEngineKeyId)); + Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPrivateKeyFromEngine(NonExistingEngineOrProviderKeyName, TestEngineKeyId)); + Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPublicKeyFromEngine(NonExistingEngineOrProviderKeyName, TestEngineKeyId)); } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.OpenSslPresentOnSystem))] - public static void NonExistingKey() + public static void Provider_NonExisting() { - Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPrivateKeyFromEngine(TestEngineName, NonExistingEngineKeyName)); - Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPublicKeyFromEngine(TestEngineName, NonExistingEngineKeyName)); + Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenKeyFromProvider(NonExistingEngineOrProviderKeyName, AnyProviderKeyUri)); } [ConditionalFact(nameof(ShouldRunEngineTests))] - public static void Engine_SanityTest() + public static void Engine_NonExistingKey() { - Assert.False(ShouldFailTests, "This test is supposed to fail"); + Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPrivateKeyFromEngine(TestEngineName, NonExistingEngineOrProviderKeyName)); + Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPublicKeyFromEngine(TestEngineName, NonExistingEngineOrProviderKeyName)); } - [ConditionalFact(nameof(ShouldRunTpmTssTests))] - public static void Tpm_SanityTest() + [ConditionalFact(nameof(ShouldRunAnyProviderTests))] + public static void Provider_NonExistingKey() { - Assert.False(ShouldFailTests, "This test is supposed to fail"); + Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, NonExistingEngineOrProviderKeyName)); } [ConditionalFact(nameof(ShouldRunEngineTests))] @@ -174,7 +211,7 @@ public static void Engine_UsePublicKey() [ConditionalFact(nameof(ShouldRunTpmTssTests))] public static void Engine_OpenExistingTPMPrivateKey() { - using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenPrivateKeyFromEngine(TpmTssEngineName, TpmTssEngineEcDsaKeyHandle); + using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenPrivateKeyFromEngine(TpmTssEngineName, TpmEcDsaKeyHandle); using ECDsa ecdsaPri = new ECDsaOpenSsl(priKeyHandle); using ECDsa ecdsaBad = ECDsa.Create(); ecdsaBad.KeySize = ecdsaPri.KeySize; @@ -182,9 +219,194 @@ public static void Engine_OpenExistingTPMPrivateKey() byte[] data = new byte[] { 1, 2, 3, 1, 1, 2, 3 }; byte[] signature = ecdsaPri.SignData(data, HashAlgorithmName.SHA256); byte[] badSignature = ecdsaBad.SignData(data, HashAlgorithmName.SHA256); + Assert.Equal(signature.Length, badSignature.Length); Assert.NotEqual(data, signature); Assert.True(ecdsaPri.VerifyData(data, signature, HashAlgorithmName.SHA256)); Assert.False(ecdsaPri.VerifyData(data, badSignature, HashAlgorithmName.SHA256)); } + + [ConditionalFact(nameof(ShouldRunProviderEcDsaTests))] + public static void Provider_TPM2ECDSA() + { + using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, TpmEcDsaKeyHandleUri); + using ECDsa ecdsaPri = new ECDsaOpenSsl(priKeyHandle); + + byte[] data = new byte[] { 1, 2, 3, 1, 1, 2, 3 }; + + byte[] ecdsaPubBytes = ecdsaPri.ExportSubjectPublicKeyInfo(); + ECDsa ecdsaPub = ECDsa.Create(); + ecdsaPub.ImportSubjectPublicKeyInfo(ecdsaPubBytes, out int bytesRead); + Assert.Equal(ecdsaPubBytes.Length, bytesRead); + + using ECDsa ecdsaBad = ECDsa.Create(); + ecdsaBad.KeySize = ecdsaPri.KeySize; + + // Verify can sign/verify multiple times + for (int i = 0; i < 10; i++) + { + data[0] = (byte)i; + byte[] signature = ecdsaPri.SignData(data, HashAlgorithmName.SHA256); + byte[] badSignature = ecdsaBad.SignData(data, HashAlgorithmName.SHA256); + Assert.NotEqual(data, signature); + Assert.NotEqual(data, badSignature); + Assert.NotEqual(badSignature, signature); + Assert.True(ecdsaPub.VerifyData(data, signature, HashAlgorithmName.SHA256)); + Assert.False(ecdsaPub.VerifyData(data, badSignature, HashAlgorithmName.SHA256)); + Assert.False(ecdsaBad.VerifyData(data, signature, HashAlgorithmName.SHA256)); + + // TPM key is intended for sign/decrypt only, we could theoretically make verify work without needing to export/import by forcing 'default' provider + // for this operation but it's most likely misusage on user part and tpm2 provider intentionally didn't allow it so we will follow this logic. + Assert.ThrowsAny(() => ecdsaPri.VerifyData(data, signature, HashAlgorithmName.SHA256)); + } + + // It's TPM so it should not be possible to export parameters + Assert.ThrowsAny(() => ecdsaPri.ExportParameters(includePrivateParameters: true)); + } + + [ConditionalFact(nameof(ShouldRunProviderEcDhTests))] + public static void Provider_TPM2ECDH() + { + using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, TpmEcDhKeyHandleUri); + using ECDiffieHellman alicePri = new ECDiffieHellmanOpenSsl(priKeyHandle); + using ECDiffieHellman alicePub = ECDiffieHellman.Create(); + alicePub.ImportParameters(alicePri.ExportParameters(includePrivateParameters: false)); + + using ECDiffieHellman bobPri = ECDiffieHellman.Create(); + bobPri.KeySize = alicePri.KeySize; + + using ECDiffieHellman bobPub = ECDiffieHellman.Create(); + bobPub.ImportParameters(bobPri.ExportParameters(includePrivateParameters: false)); + + byte[] sharedKeyFromAlice; + using (ECDiffieHellmanPublicKey bobPublic = bobPub.PublicKey) + { + sharedKeyFromAlice = alicePri.DeriveKeyMaterial(bobPublic); + + Assert.NotEmpty(sharedKeyFromAlice); + + byte firstByte = sharedKeyFromAlice[0]; + bool allSame = sharedKeyFromAlice.All((x) => x == firstByte); + Assert.False(allSame, "all bytes of shared key are the same"); + } + + using (ECDiffieHellmanPublicKey alicePublic = alicePub.PublicKey) + { + byte[] sharedKeyFromBob = bobPri.DeriveKeyMaterial(alicePublic); + Assert.Equal(sharedKeyFromAlice, sharedKeyFromBob); + } + } + + [ConditionalFact(nameof(ShouldRunProviderRsaSignTests))] + [ActiveIssue("https://github.com/tpm2-software/tpm2-openssl/issues/115")] + // or a workaround API proposal + [ActiveIssue("https://github.com/dotnet/runtime/issues/104080")] + public static void Provider_TPM2SignRsa() + { + using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, TpmRsaSignKeyHandleUri); + using RSA rsaPri = new RSAOpenSsl(priKeyHandle); + byte[] rsaPubBytes = rsaPri.ExportSubjectPublicKeyInfo(); + RSA rsaPub = RSA.Create(); + rsaPub.ImportSubjectPublicKeyInfo(rsaPubBytes, out int bytesRead); + Assert.Equal(rsaPubBytes.Length, bytesRead); + + using RSA rsaBad = RSA.Create(); + rsaBad.KeySize = rsaPri.KeySize; + + byte[] data = new byte[] { 1, 2, 3, 1, 1, 2, 3 }; + byte[] badSignature = rsaBad.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pss); + + // can use same key more than once + for (int i = 0; i < 10; i++) + { + data[0] = (byte)i; + byte[] signature = rsaPri.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pss); + Assert.True(rsaPub.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss), "signature does not verify with the right key"); + Assert.False(rsaPub.VerifyData(data, badSignature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss), "signature should not verify with the wrong key"); + + signature[12] ^= 1; + Assert.False(rsaPub.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss), "tampered signature should not verify"); + + // TPM key is intended for sign only, we could theoretically make verify work without needing to export/import by forcing 'default' provider + // for this operation it's most likely misusage on user part and tpm2 provider intentionally didn't allow it so we will follow this logic. + Assert.ThrowsAny(() => rsaPri.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss)); + } + } + + [ConditionalFact(nameof(ShouldRunProviderRsaDecryptTests))] + public static void Provider_TPM2DecryptRsa() + { + // TPM2 OAEP support was added in the second half of 2023 therefore we only test Pkcs1 padding + // See: https://github.com/tpm2-software/tpm2-openssl/issues/89 + RSAEncryptionPadding padding = RSAEncryptionPadding.Pkcs1; + using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, TpmRsaDecryptKeyHandleUri); + using RSA rsaPri = new RSAOpenSsl(priKeyHandle); + byte[] rsaPubBytes = rsaPri.ExportSubjectPublicKeyInfo(); + RSA rsaPub = RSA.Create(); + rsaPub.ImportSubjectPublicKeyInfo(rsaPubBytes, out int bytesRead); + Assert.Equal(rsaPubBytes.Length, bytesRead); + + using RSA rsaBad = RSA.Create(); + rsaBad.KeySize = rsaPri.KeySize; + + byte[] data = new byte[] { 1, 2, 3, 1, 1, 2, 3 }; + byte[] encryptedWithDifferentKey = rsaBad.Encrypt(data, padding); + Assert.ThrowsAny(() => rsaPri.Decrypt(encryptedWithDifferentKey, padding)); + + // TPM private key is intended only for decrypt + Assert.ThrowsAny(() => rsaPri.Encrypt(data, padding)); + + // can use same key more than once + for (int i = 0; i < 10; i++) + { + data[0] = (byte)i; + byte[] encrypted = rsaPub.Encrypt(data, padding); + Assert.NotEqual(encrypted, data); + + Assert.Equal(data, rsaPri.Decrypt(encrypted, padding)); + } + } + + // Sanity tests for flags + [ConditionalFact(nameof(ShouldRunEngineTests))] + public static void SanityTest_EngineAny() + { + Assert.False(ShouldFailTests, "This test is supposed to fail"); + } + + [ConditionalFact(nameof(ShouldRunTpmTssTests))] + public static void SanityTest_EngineTpmTss() + { + Assert.False(ShouldFailTests, "This test is supposed to fail"); + } + + [ConditionalFact(nameof(ShouldRunAnyProviderTests))] + public static void SanityTest_AnyTpm2Provider() + { + Assert.False(ShouldFailTests, "This test is supposed to fail"); + } + + [ConditionalFact(nameof(ShouldRunProviderEcDsaTests))] + public static void SanityTest_Tpm2ProviderEcDsa() + { + Assert.False(ShouldFailTests, "This test is supposed to fail"); + } + + [ConditionalFact(nameof(ShouldRunProviderEcDhTests))] + public static void SanityTest_Tpm2ProviderEcDh() + { + Assert.False(ShouldFailTests, "This test is supposed to fail"); + } + + [ConditionalFact(nameof(ShouldRunProviderRsaSignTests))] + public static void SanityTest_Tpm2ProviderRsaSign() + { + Assert.False(ShouldFailTests, "This test is supposed to fail"); + } + + [ConditionalFact(nameof(ShouldRunProviderRsaDecryptTests))] + public static void SanityTest_Tpm2ProviderRsaDecrypt() + { + Assert.False(ShouldFailTests, "This test is supposed to fail"); + } } } diff --git a/src/libraries/System.Security.Cryptography/tests/osslplugins/README.md b/src/libraries/System.Security.Cryptography/tests/osslplugins/README.md index 04d3cdae5fd995..fd7bc9f37a99f5 100644 --- a/src/libraries/System.Security.Cryptography/tests/osslplugins/README.md +++ b/src/libraries/System.Security.Cryptography/tests/osslplugins/README.md @@ -1,8 +1,14 @@ +TODO: update names of variables and review +TODO: add section with logging https://github.com/tpm2-software/tpm2-tss/blob/master/doc/logging.md +TODO: export TSS2_LOG=all+NONE + + # Testing instructions for OpenSSL ENGINE Once everything is setup tests related to TPM and our engine can be run using: ```bash +export DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_ENABLE=true ./test.sh ``` @@ -12,7 +18,7 @@ If TPM environmental variable similar to following is defined: ```bash # 0x81000007 is just an example, read further how to get it -export DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_TPM_ECDSA_KEY_HANDLE=0x81000007 +export DOTNET_CRYPTOGRAPHY_TESTS_TPM_ECDSA_KEY_HANDLE=0x81000007 ``` then tests using TPM will be run as well and they will use `0x81000007` handle. @@ -114,7 +120,33 @@ export TSS2_LOG=all+TRACE Most of the time this should not be needed but it might be useful if you're seeing issues when interacting with the ENGINE. -### Getting TPM handle +# Testing instructions for OpenSSL Provider + +## Installation + +To install TPM2 provider refer to https://github.com/tpm2-software/tpm2-openssl - on Ubuntu following step can be used: + +```bash +sudo apt install tpm2-openssl tpm2-tools tpm2-abrmd libtss2-tcti-tabrmd0 +``` + +## Running provider tests + +In order to run provider tests you need to have TPM handles and set one or more of the following environmental variables: + +```csharp +# Handle values are just an example - refer to 'Getting TPM handle' section for instructions on how to create or get them +export DOTNET_CRYPTOGRAPHY_TESTS_TPM_ECDSA_KEY_HANDLE=0x81000007 +export DOTNET_CRYPTOGRAPHY_TESTS_TPM_ECDH_KEY_HANDLE=0x8100000d +export DOTNET_CRYPTOGRAPHY_TESTS_TPM_RSA_DECRYPT_KEY_HANDLE=0x8100000c + +# RSA-PSS tests will always fail due to following issues but they can be run still +# https://github.com/dotnet/runtime/issues/104080 +# https://github.com/tpm2-software/tpm2-openssl/issues/115 +export DOTNET_CRYPTOGRAPHY_TESTS_TPM_RSA_SIGN_KEY_HANDLE=0x8100000a +``` + +# Getting TPM handle First, we will need `tpm2-tools`` installed: @@ -122,7 +154,7 @@ First, we will need `tpm2-tools`` installed: sudo apt install tpm2-tools ``` -#### Getting TPM handles +## Getting TPM handles If you already have a handle but you forgot what it is you can list all available handles using following command: @@ -145,11 +177,11 @@ You can also extract public key like this if needed: tpm2_readpublic -c 0x81000007 -o /tmp/key.pub ``` -#### Testing handle with OpenSSL CLI +### Testing handle with OpenSSL CLI In case you find issues with your handle you can test it using OpenSSL CLI, for example `0x81000004` can be tested like following: -##### RSA key +#### RSA key ```bash # create testdata file with some content @@ -168,9 +200,9 @@ openssl pkey -engine tpm2tss -inform engine -in '0x81000007' -pubout -out testke openssl pkeyutl -verify -in testdata.dgst -sigfile testdata.sig -inkey testkey.pub -pubin -pkeyopt digest:sha256 ``` -#### Creating keys and handles +## Creating keys and handles -##### ECDSA key +### ECDSA key ```bash tpm2_createprimary -C o -g sha256 -G ecc256:ecdsa-sha256:null -c primary.ctx -a 'fixedtpm|fixedparent|sensitivedataorigin|userwithauth|noda|sign' @@ -179,7 +211,16 @@ tpm2_createprimary -C o -g sha256 -G ecc256:ecdsa-sha256:null -c primary.ctx -a tpm2_evictcontrol -C o -c primary.ctx ``` -##### RSA key (RSAPSS + SHA256) +### ECDH key + +```bash +tpm2_createprimary -C o -g sha256 -G ecc256 -c primary.ctx -a 'fixedtpm|fixedparent|sensitivedataorigin|userwithauth|noda|decrypt' + +# To create permenent handle and print it: +tpm2_evictcontrol -C o -c primary.ctx +``` + +### RSA key (RSAPSS + SHA256) This is not used by tests but if needed for further testing: @@ -190,3 +231,15 @@ tpm2_createprimary -C o -g sha256 -G rsa2048:rsapss:null -c primary.ctx -a 'fixe # To create permenent handle and print it: tpm2_evictcontrol -C o -c primary.ctx ``` + +### RSA key (decryption) + +This is not used by tests but if needed for further testing: + +```bash +# To create key +tpm2_createprimary -C o -g sha256 -G rsa2048 -c primary.ctx -a 'fixedtpm|fixedparent|sensitivedataorigin|userwithauth|noda|decrypt' + +# To create permenent handle and print it: +tpm2_evictcontrol -C o -c primary.ctx +``` diff --git a/src/libraries/System.Security.Cryptography/tests/osslplugins/test.sh b/src/libraries/System.Security.Cryptography/tests/osslplugins/test.sh index c13dd079319a58..9d61736d6c24d6 100755 --- a/src/libraries/System.Security.Cryptography/tests/osslplugins/test.sh +++ b/src/libraries/System.Security.Cryptography/tests/osslplugins/test.sh @@ -17,23 +17,63 @@ dotnet="$repo_root_path/dotnet.sh" nativelibs_path="$src_path/native/libs" -export DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_ENABLE=true +if [ -z "$DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_ENABLE" ]; then + echo "WARNING: Engine tests will not be run" + echo "WARNING: Use following variable to enable them:" + echo "WARNING: export DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_ENABLE=true" + echo "WARNING: Refer to README.md for more information." + echo +fi -if [ -z "$DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_TPM_ECDSA_KEY_HANDLE" ]; then - echo "WARNING: TPM tests will not be run" +if [ -z "$DOTNET_CRYPTOGRAPHY_TESTS_TPM_ECDSA_KEY_HANDLE" ]; then + echo "WARNING: TPM ECDSA tests will not be run" echo "WARNING: Use following environmental variable to enable them:" - echo "WARNING: export DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_TPM_ECDSA_KEY_HANDLE=YourHandleHere" + echo "WARNING: export DOTNET_CRYPTOGRAPHY_TESTS_TPM_ECDSA_KEY_HANDLE=YourHandleHere" echo "WARNING: For example:" - echo "WARNING: export DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_TPM_ECDSA_KEY_HANDLE=0x81000007" + echo "WARNING: export DOTNET_CRYPTOGRAPHY_TESTS_TPM_ECDSA_KEY_HANDLE=0x81000007" echo "WARNING: Refer to README.md for more information on how to get handle." + echo +fi + +if [ -z "$DOTNET_CRYPTOGRAPHY_TESTS_TPM_ECDH_KEY_HANDLE" ]; then + echo "WARNING: TPM ECDH tests will not be run" + echo "WARNING: Use following environmental variable to enable them:" + echo "WARNING: export DOTNET_CRYPTOGRAPHY_TESTS_TPM_ECDH_KEY_HANDLE=YourHandleHere" + echo "WARNING: For example:" + echo "WARNING: export DOTNET_CRYPTOGRAPHY_TESTS_TPM_ECDH_KEY_HANDLE=0x8100000d" + echo "WARNING: Refer to README.md for more information on how to get handle." + echo +fi + +if [ -z "$DOTNET_CRYPTOGRAPHY_TESTS_TPM_RSA_SIGN_KEY_HANDLE" ]; then + echo 'WARNING: [ActiveIssue("https://github.com/tpm2-software/tpm2-openssl/issues/115")]' + echo 'WARNING: [ActiveIssue("https://github.com/dotnet/runtime/issues/104080")]' + echo "WARNING: TPM RSA sign tests will not be run" + echo "WARNING: Use following environmental variable to enable them:" + echo "WARNING: export DOTNET_CRYPTOGRAPHY_TESTS_TPM_RSA_SIGN_KEY_HANDLE=YourHandleHere" + echo "WARNING: For example:" + echo "WARNING: export DOTNET_CRYPTOGRAPHY_TESTS_TPM_RSA_SIGN_KEY_HANDLE=0x8100000a" + echo "WARNING: Refer to README.md for more information on how to get handle." + echo +fi + +if [ -z "$DOTNET_CRYPTOGRAPHY_TESTS_TPM_RSA_DECRYPT_KEY_HANDLE" ]; then + echo "WARNING: TPM RSA decrypt tests will not be run" + echo "WARNING: Use following environmental variable to enable them:" + echo "WARNING: export DOTNET_CRYPTOGRAPHY_TESTS_TPM_RSA_DECRYPT_KEY_HANDLE=YourHandleHere" + echo "WARNING: For example:" + echo "WARNING: export DOTNET_CRYPTOGRAPHY_TESTS_TPM_RSA_DECRYPT_KEY_HANDLE=0x8100000c" + echo "WARNING: Refer to README.md for more information on how to get handle." + echo fi if [ "$1" == "--self-check" ]; then - export DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_ENSURE_FAILING=true + export DOTNET_CRYPTOGRAPHY_TESTS_ENSURE_FAILING=true else echo "INFO: To run self-check use:" echo "INFO: ./test.sh --self-check" - echo "INFO: Expect two test failures." + echo "INFO: Expect test failures one per each category of tests (custom ENGINE, ECDSA, ECDH, RSA sign, RSA decrypt)." + echo fi set -e @@ -45,4 +85,5 @@ cd "$ssc_src_path" $dotnet build cd "$ssc_tests_path" + $dotnet test --filter "FullyQualifiedName~System.Security.Cryptography.Tests.OpenSslNamedKeysTests." diff --git a/src/native/libs/System.Security.Cryptography.Native/CMakeLists.txt b/src/native/libs/System.Security.Cryptography.Native/CMakeLists.txt index c2808d3fd7646d..de817d0834ac42 100644 --- a/src/native/libs/System.Security.Cryptography.Native/CMakeLists.txt +++ b/src/native/libs/System.Security.Cryptography.Native/CMakeLists.txt @@ -26,7 +26,6 @@ set(NATIVECRYPTO_SOURCES pal_bignum.c pal_bio.c pal_dsa.c - pal_ecdsa.c pal_ecc_import_export.c pal_eckey.c pal_err.c diff --git a/src/native/libs/System.Security.Cryptography.Native/entrypoints.c b/src/native/libs/System.Security.Cryptography.Native/entrypoints.c index fd9c808597db2e..f1f159ff0d2a78 100644 --- a/src/native/libs/System.Security.Cryptography.Native/entrypoints.c +++ b/src/native/libs/System.Security.Cryptography.Native/entrypoints.c @@ -10,7 +10,6 @@ #include "pal_bio.h" #include "pal_dsa.h" #include "pal_ecc_import_export.h" -#include "pal_ecdsa.h" #include "pal_eckey.h" #include "pal_err.h" #include "pal_evp.h" @@ -70,9 +69,6 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_DsaSizeSignature) DllImportEntry(CryptoNative_DsaUpRef) DllImportEntry(CryptoNative_DsaVerify) - DllImportEntry(CryptoNative_EcDsaSign) - DllImportEntry(CryptoNative_EcDsaSize) - DllImportEntry(CryptoNative_EcDsaVerify) DllImportEntry(CryptoNative_EcKeyCreateByExplicitParameters) DllImportEntry(CryptoNative_EcKeyCreateByKeyParameters) DllImportEntry(CryptoNative_EcKeyCreateByOid) @@ -164,15 +160,19 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_EvpPkeyCreate) DllImportEntry(CryptoNative_EvpPKeyCreateRsa) DllImportEntry(CryptoNative_EvpPKeyCtxCreate) + DllImportEntry(CryptoNative_EvpPKeyCtxCreateFromPKey) + DllImportEntry(CryptoNative_EvpPKeyCtxConfigureForECDSASign) + DllImportEntry(CryptoNative_EvpPKeyCtxConfigureForECDSAVerify) + DllImportEntry(CryptoNative_EvpPKeyCtxSignHash) + DllImportEntry(CryptoNative_EvpPKeyCtxVerifyHash) DllImportEntry(CryptoNative_EvpPKeyCtxDestroy) DllImportEntry(CryptoNative_EvpPKeyDeriveSecretAgreement) DllImportEntry(CryptoNative_EvpPkeyDestroy) - DllImportEntry(CryptoNative_EvpPKeyDuplicate) DllImportEntry(CryptoNative_EvpPkeyGetDsa) DllImportEntry(CryptoNative_EvpPkeyGetEcKey) DllImportEntry(CryptoNative_EvpPkeySetDsa) DllImportEntry(CryptoNative_EvpPkeySetEcKey) - DllImportEntry(CryptoNative_EvpPKeySize) + DllImportEntry(CryptoNative_EvpPKeyBits) DllImportEntry(CryptoNative_EvpRC2Cbc) DllImportEntry(CryptoNative_EvpRC2Ecb) DllImportEntry(CryptoNative_EvpSha1) @@ -230,6 +230,7 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_HmacOneShot) DllImportEntry(CryptoNative_HmacReset) DllImportEntry(CryptoNative_HmacUpdate) + DllImportEntry(CryptoNative_LoadKeyFromProvider) DllImportEntry(CryptoNative_LoadPrivateKeyFromEngine) DllImportEntry(CryptoNative_LoadPublicKeyFromEngine) DllImportEntry(CryptoNative_LookupFriendlyNameByOid) @@ -256,9 +257,10 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_RsaDecrypt) DllImportEntry(CryptoNative_RsaEncrypt) DllImportEntry(CryptoNative_RsaGenerateKey) - DllImportEntry(CryptoNative_RsaSignHash) - DllImportEntry(CryptoNative_RsaVerifyHash) + DllImportEntry(CryptoNative_EvpPKeyCtxConfigureForRsaSign) + DllImportEntry(CryptoNative_EvpPKeyCtxConfigureForRsaVerify) DllImportEntry(CryptoNative_UpRefEvpPkey) + DllImportEntry(CryptoNative_EvpPKeyType) DllImportEntry(CryptoNative_X509BuildOcspRequest) DllImportEntry(CryptoNative_X509ChainBuildOcspRequest) DllImportEntry(CryptoNative_X509ChainGetCachedOcspStatus) diff --git a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h index 3541c7558484ea..1562cec98ec0cf 100644 --- a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h +++ b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h @@ -43,6 +43,7 @@ #if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_3_0_RTM #include +#include #include #include #endif @@ -430,6 +431,7 @@ extern bool g_libSslUses32BitTime; REQUIRED_FUNCTION(EVP_PKEY_CTX_get0_pkey) \ REQUIRED_FUNCTION(EVP_PKEY_CTX_new) \ REQUIRED_FUNCTION(EVP_PKEY_CTX_new_id) \ + LIGHTUP_FUNCTION(EVP_PKEY_CTX_new_from_pkey) \ FALLBACK_FUNCTION(EVP_PKEY_CTX_set_rsa_keygen_bits) \ FALLBACK_FUNCTION(EVP_PKEY_CTX_set_rsa_oaep_md) \ FALLBACK_FUNCTION(EVP_PKEY_CTX_set_rsa_padding) \ @@ -446,6 +448,7 @@ extern bool g_libSslUses32BitTime; REQUIRED_FUNCTION(EVP_PKEY_free) \ RENAMED_FUNCTION(EVP_PKEY_get_base_id, EVP_PKEY_base_id) \ RENAMED_FUNCTION(EVP_PKEY_get_size, EVP_PKEY_size) \ + RENAMED_FUNCTION(EVP_PKEY_get_bits, EVP_PKEY_bits) \ FALLBACK_FUNCTION(EVP_PKEY_get0_RSA) \ REQUIRED_FUNCTION(EVP_PKEY_get1_DSA) \ REQUIRED_FUNCTION(EVP_PKEY_get1_EC_KEY) \ @@ -525,7 +528,20 @@ extern bool g_libSslUses32BitTime; RENAMED_FUNCTION(OPENSSL_sk_push, sk_push) \ RENAMED_FUNCTION(OPENSSL_sk_value, sk_value) \ FALLBACK_FUNCTION(OpenSSL_version_num) \ + LIGHTUP_FUNCTION(OSSL_LIB_CTX_free) \ + LIGHTUP_FUNCTION(OSSL_LIB_CTX_new) \ + LIGHTUP_FUNCTION(OSSL_PROVIDER_load) \ LIGHTUP_FUNCTION(OSSL_PROVIDER_try_load) \ + LIGHTUP_FUNCTION(OSSL_PROVIDER_unload) \ + LIGHTUP_FUNCTION(OSSL_STORE_close) \ + LIGHTUP_FUNCTION(OSSL_STORE_eof) \ + LIGHTUP_FUNCTION(OSSL_STORE_INFO_free) \ + LIGHTUP_FUNCTION(OSSL_STORE_INFO_get_type) \ + LIGHTUP_FUNCTION(OSSL_STORE_INFO_get1_PKEY) \ + LIGHTUP_FUNCTION(OSSL_STORE_INFO_get1_PUBKEY) \ + LIGHTUP_FUNCTION(OSSL_STORE_load) \ + LIGHTUP_FUNCTION(OSSL_STORE_open) \ + LIGHTUP_FUNCTION(OSSL_STORE_open_ex) \ LIGHTUP_FUNCTION(OSSL_PARAM_construct_octet_string) \ LIGHTUP_FUNCTION(OSSL_PARAM_construct_int32) \ LIGHTUP_FUNCTION(OSSL_PARAM_construct_end) \ @@ -978,6 +994,7 @@ extern TYPEOF(OPENSSL_gmtime)* OPENSSL_gmtime_ptr; #define EVP_PKEY_free EVP_PKEY_free_ptr #define EVP_PKEY_get_base_id EVP_PKEY_get_base_id_ptr #define EVP_PKEY_get_size EVP_PKEY_get_size_ptr +#define EVP_PKEY_get_bits EVP_PKEY_get_bits_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 @@ -985,6 +1002,7 @@ extern TYPEOF(OPENSSL_gmtime)* OPENSSL_gmtime_ptr; #define EVP_PKEY_keygen EVP_PKEY_keygen_ptr #define EVP_PKEY_keygen_init EVP_PKEY_keygen_init_ptr #define EVP_PKEY_new EVP_PKEY_new_ptr +#define EVP_PKEY_CTX_new_from_pkey EVP_PKEY_CTX_new_from_pkey_ptr #define EVP_PKEY_public_check EVP_PKEY_public_check_ptr #define EVP_PKEY_set1_DSA EVP_PKEY_set1_DSA_ptr #define EVP_PKEY_set1_EC_KEY EVP_PKEY_set1_EC_KEY_ptr @@ -1058,7 +1076,20 @@ extern TYPEOF(OPENSSL_gmtime)* OPENSSL_gmtime_ptr; #define OPENSSL_sk_push OPENSSL_sk_push_ptr #define OPENSSL_sk_value OPENSSL_sk_value_ptr #define OpenSSL_version_num OpenSSL_version_num_ptr +#define OSSL_LIB_CTX_free OSSL_LIB_CTX_free_ptr +#define OSSL_LIB_CTX_new OSSL_LIB_CTX_new_ptr +#define OSSL_PROVIDER_load OSSL_PROVIDER_load_ptr #define OSSL_PROVIDER_try_load OSSL_PROVIDER_try_load_ptr +#define OSSL_PROVIDER_unload OSSL_PROVIDER_unload_ptr +#define OSSL_STORE_close OSSL_STORE_close_ptr +#define OSSL_STORE_eof OSSL_STORE_eof_ptr +#define OSSL_STORE_INFO_free OSSL_STORE_INFO_free_ptr +#define OSSL_STORE_INFO_get_type OSSL_STORE_INFO_get_type_ptr +#define OSSL_STORE_INFO_get1_PKEY OSSL_STORE_INFO_get1_PKEY_ptr +#define OSSL_STORE_INFO_get1_PUBKEY OSSL_STORE_INFO_get1_PUBKEY_ptr +#define OSSL_STORE_load OSSL_STORE_load_ptr +#define OSSL_STORE_open OSSL_STORE_open_ptr +#define OSSL_STORE_open_ex OSSL_STORE_open_ex_ptr #define OSSL_PARAM_construct_octet_string OSSL_PARAM_construct_octet_string_ptr #define OSSL_PARAM_construct_int32 OSSL_PARAM_construct_int32_ptr #define OSSL_PARAM_construct_end OSSL_PARAM_construct_end_ptr @@ -1333,9 +1364,9 @@ extern TYPEOF(OPENSSL_gmtime)* OPENSSL_gmtime_ptr; #define EVP_MD_get_size EVP_MD_size #define EVP_PKEY_get_base_id EVP_PKEY_base_id #define EVP_PKEY_get_size EVP_PKEY_size +#define EVP_PKEY_get_bits EVP_PKEY_bits #define SSL_get1_peer_certificate SSL_get_peer_certificate #define EVP_CIPHER_get_nid EVP_CIPHER_nid - #endif #if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_3_0_RTM diff --git a/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h b/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h index 78b626b1c3809e..930b4e78fc3c60 100644 --- a/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h +++ b/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h @@ -17,9 +17,15 @@ #define OSSL_MAC_PARAM_XOF "xof" #define OSSL_MAC_PARAM_SIZE "size" -typedef struct ossl_provider_st OSSL_PROVIDER; +#define OSSL_STORE_INFO_PKEY 4 +#define OSSL_STORE_INFO_PUBKEY 3 + typedef struct ossl_lib_ctx_st OSSL_LIB_CTX; typedef struct ossl_param_st OSSL_PARAM; +typedef struct ossl_provider_st OSSL_PROVIDER; +typedef struct ossl_store_ctx_st OSSL_STORE_CTX; +typedef struct ossl_store_info_st OSSL_STORE_INFO; +typedef OSSL_STORE_INFO* (*OSSL_STORE_post_process_info_fn)(OSSL_STORE_INFO*, void*); struct ossl_param_st { @@ -54,10 +60,28 @@ int EVP_PKEY_CTX_set_rsa_pss_saltlen(EVP_PKEY_CTX* ctx, int saltlen); int EVP_PKEY_CTX_set_signature_md(EVP_PKEY_CTX* ctx, const EVP_MD* md); int EVP_PKEY_get_base_id(const EVP_PKEY* pkey); int EVP_PKEY_get_size(const EVP_PKEY* pkey); +int EVP_PKEY_get_bits(const EVP_PKEY* pkey); OSSL_PARAM OSSL_PARAM_construct_end(void); OSSL_PARAM OSSL_PARAM_construct_int32(const char *key, int32_t *buf); OSSL_PARAM OSSL_PARAM_construct_octet_string(const char *key, void *buf, size_t bsize); OSSL_PROVIDER* OSSL_PROVIDER_try_load(OSSL_LIB_CTX* , const char* name, int retain_fallbacks); +void OSSL_LIB_CTX_free(OSSL_LIB_CTX*); +OSSL_LIB_CTX* OSSL_LIB_CTX_new(void); +OSSL_PROVIDER* OSSL_PROVIDER_load(OSSL_LIB_CTX*, const char* name); +OSSL_PROVIDER* OSSL_PROVIDER_try_load(OSSL_LIB_CTX*, const char* name, int retain_fallbacks); +int OSSL_PROVIDER_unload(OSSL_PROVIDER* prov); +int OSSL_STORE_close(OSSL_STORE_CTX* ctx); +int OSSL_STORE_eof(OSSL_STORE_CTX* ctx); +OSSL_STORE_INFO* OSSL_STORE_load(OSSL_STORE_CTX* ctx); +void OSSL_STORE_INFO_free(OSSL_STORE_INFO* info); +int OSSL_STORE_INFO_get_type(const OSSL_STORE_INFO* info); +EVP_PKEY* OSSL_STORE_INFO_get1_PKEY(const OSSL_STORE_INFO* info); +EVP_PKEY* OSSL_STORE_INFO_get1_PUBKEY(const OSSL_STORE_INFO* info); +OSSL_STORE_CTX* OSSL_STORE_open( + const char*, const UI_METHOD*, void*, OSSL_STORE_post_process_info_fn post_process, void*); +OSSL_STORE_CTX* OSSL_STORE_open_ex( + const char*, OSSL_LIB_CTX*, const char*, const UI_METHOD*, void*, const OSSL_PARAM*, OSSL_STORE_post_process_info_fn post_process, void*); + X509* SSL_get1_peer_certificate(const SSL* ssl); diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_ecdsa.c b/src/native/libs/System.Security.Cryptography.Native/pal_ecdsa.c deleted file mode 100644 index 43792d35b6ea53..00000000000000 --- a/src/native/libs/System.Security.Cryptography.Native/pal_ecdsa.c +++ /dev/null @@ -1,34 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#include "pal_ecdsa.h" -#include "pal_utilities.h" - -int32_t -CryptoNative_EcDsaSign(const uint8_t* dgst, int32_t dgstlen, uint8_t* sig, int32_t* siglen, EC_KEY* key) -{ - ERR_clear_error(); - - if (!siglen) - { - return 0; - } - - unsigned int unsignedSigLength = 0; - int ret = ECDSA_sign(0, dgst, dgstlen, sig, &unsignedSigLength, key); - *siglen = Uint32ToInt32(unsignedSigLength); - return ret; -} - -int32_t -CryptoNative_EcDsaVerify(const uint8_t* dgst, int32_t dgstlen, const uint8_t* sig, int32_t siglen, EC_KEY* key) -{ - ERR_clear_error(); - return ECDSA_verify(0, dgst, dgstlen, sig, siglen, key); -} - -int32_t CryptoNative_EcDsaSize(const EC_KEY* key) -{ - // No error queue impact. - return ECDSA_size(key); -} diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_ecdsa.h b/src/native/libs/System.Security.Cryptography.Native/pal_ecdsa.h deleted file mode 100644 index 02cc7a587463e6..00000000000000 --- a/src/native/libs/System.Security.Cryptography.Native/pal_ecdsa.h +++ /dev/null @@ -1,29 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#include "pal_types.h" -#include "pal_compiler.h" -#include "opensslshim.h" - -/* -Shims the ECDSA_sign method. - -Returns 1 on success, otherwise 0. -*/ -PALEXPORT int32_t -CryptoNative_EcDsaSign(const uint8_t* dgst, int32_t dgstlen, uint8_t* sig, int32_t* siglen, EC_KEY* key); - -/* -Shims the ECDSA_verify method. - -Returns 1 for a correct signature, 0 for an incorrect signature, -1 on error. -*/ -PALEXPORT int32_t -CryptoNative_EcDsaVerify(const uint8_t* dgst, int32_t dgstlen, const uint8_t* sig, int32_t siglen, EC_KEY* key); - -/* -Shims the ECDSA_size method. - -Returns the maximum length of a DER encoded ECDSA signature created with this key. -*/ -PALEXPORT int32_t CryptoNative_EcDsaSize(const EC_KEY* key); diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c index dea4f277b8969d..352512a1bb695e 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c @@ -3,6 +3,21 @@ #include #include "pal_evp_pkey.h" +#include "pal_utilities.h" + +#ifdef NEED_OPENSSL_3_0 +c_static_assert(OSSL_STORE_INFO_PKEY == 4); +c_static_assert(OSSL_STORE_INFO_PUBKEY == 3); +#endif + +struct EvpPKeyExtraHandle_st +{ + int references; + OSSL_LIB_CTX* libCtx; + OSSL_PROVIDER* prov; +}; + +typedef struct EvpPKeyExtraHandle_st EvpPKeyExtraHandle; EVP_PKEY* CryptoNative_EvpPkeyCreate(void) { @@ -10,85 +25,82 @@ EVP_PKEY* CryptoNative_EvpPkeyCreate(void) return EVP_PKEY_new(); } -EVP_PKEY* CryptoNative_EvpPKeyDuplicate(EVP_PKEY* currentKey, int32_t algId) +static void CryptoNative_EvpPkeyExtraHandleDestroy(EvpPKeyExtraHandle* handle) { - assert(currentKey != NULL); + assert(handle->references >= 1); + assert(handle->prov != NULL); + assert(handle->libCtx != NULL); - ERR_clear_error(); - - int currentAlgId = EVP_PKEY_get_base_id(currentKey); + handle->references--; - if (algId != NID_undef && algId != currentAlgId) + if (handle->references == 0) { - ERR_put_error(ERR_LIB_EVP, 0, EVP_R_DIFFERENT_KEY_TYPES, __FILE__, __LINE__); - return NULL; + OSSL_PROVIDER_unload(handle->prov); + OSSL_LIB_CTX_free(handle->libCtx); + free(handle); } - - EVP_PKEY* newKey = EVP_PKEY_new(); - - if (newKey == NULL) - { - return NULL; - } - - bool success = true; - - if (currentAlgId == EVP_PKEY_RSA) - { - const RSA* rsa = EVP_PKEY_get0_RSA(currentKey); - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wcast-qual" - if (rsa == NULL || !EVP_PKEY_set1_RSA(newKey, (RSA*)rsa)) -#pragma clang diagnostic pop - { - success = false; - } - } - else - { - ERR_put_error(ERR_LIB_EVP, 0, EVP_R_UNSUPPORTED_ALGORITHM, __FILE__, __LINE__); - success = false; - } - - if (!success) - { - EVP_PKEY_free(newKey); - newKey = NULL; - } - - return newKey; } -void CryptoNative_EvpPkeyDestroy(EVP_PKEY* pkey) +void CryptoNative_EvpPkeyDestroy(EVP_PKEY* pkey, void* extraHandle) { if (pkey != NULL) { EVP_PKEY_free(pkey); } + + if (extraHandle != NULL) + { + EvpPKeyExtraHandle* extra = (EvpPKeyExtraHandle*)extraHandle; + CryptoNative_EvpPkeyExtraHandleDestroy(extra); + } } -int32_t CryptoNative_EvpPKeySize(EVP_PKEY* pkey) +int32_t CryptoNative_EvpPKeyBits(EVP_PKEY* pkey) { // This function is not expected to populate the error queue with // any errors, but it's technically possible that an external // ENGINE or OSSL_PROVIDER populate the queue in their implementation, // but the calling code does not check for one. assert(pkey != NULL); - return EVP_PKEY_get_size(pkey); + return EVP_PKEY_get_bits(pkey); } -int32_t CryptoNative_UpRefEvpPkey(EVP_PKEY* pkey) +int32_t CryptoNative_UpRefEvpPkey(EVP_PKEY* pkey, void* extraHandle) { + EvpPKeyExtraHandle* extra = (EvpPKeyExtraHandle*)extraHandle; + if (!pkey) { return 0; } + if (extra != NULL) + { + extra->references++; + } + // No error queue impact. return EVP_PKEY_up_ref(pkey); } +int32_t CryptoNative_EvpPKeyType(EVP_PKEY* key) +{ + int32_t base_id = EVP_PKEY_get_base_id(key); + switch (base_id) { + case EVP_PKEY_RSA: + case EVP_PKEY_EC: + case EVP_PKEY_DSA: + return base_id; + case EVP_PKEY_RSA_PSS: + return EVP_PKEY_RSA; + case EVP_PKEY_ED448: + case EVP_PKEY_ED25519: + return EVP_PKEY_EC; + default: + return 0; + } +} + static bool Lcm(const BIGNUM* num1, const BIGNUM* num2, BN_CTX* ctx, BIGNUM* result) { assert(result); @@ -580,3 +592,204 @@ EVP_PKEY* CryptoNative_LoadPublicKeyFromEngine(const char* engineName, const cha *haveEngine = 0; return NULL; } + +EVP_PKEY* CryptoNative_LoadKeyFromProvider(const char* providerName, const char* keyUri, void** extraHandle) +{ + ERR_clear_error(); + +#ifdef FEATURE_DISTRO_AGNOSTIC_SSL + if (!API_EXISTS(OSSL_PROVIDER_load)) + { + ERR_put_error(ERR_LIB_NONE, 0, ERR_R_DISABLED, __FILE__, __LINE__); + return NULL; + } +#endif + +#ifdef NEED_OPENSSL_3_0 + EVP_PKEY* ret = NULL; + OSSL_LIB_CTX* libCtx = OSSL_LIB_CTX_new(); + OSSL_PROVIDER* prov = NULL; + OSSL_STORE_CTX* store = NULL; + OSSL_STORE_INFO* firstPubKey = NULL; + + if (libCtx == NULL) + { + goto end; + } + + prov = OSSL_PROVIDER_load(libCtx, providerName); + + if (prov == NULL) + { + goto end; + } + + store = OSSL_STORE_open_ex(keyUri, libCtx, NULL, NULL, NULL, NULL, NULL, NULL); + if (store == NULL) + { + goto end; + } + + // Quite similar to loading a single certificate from a PFX, if we find a private key that wins. + // Otherwise, the first public key wins. + // Otherwise, we'll push a keyload error + while (ret == NULL && !OSSL_STORE_eof(store)) + { + OSSL_STORE_INFO* info = OSSL_STORE_load(store); + + if (info == NULL) + { + continue; + } + + int type = OSSL_STORE_INFO_get_type(info); + + if (type == OSSL_STORE_INFO_PKEY) + { + ret = OSSL_STORE_INFO_get1_PKEY(info); + break; + } + else if (type == OSSL_STORE_INFO_PUBKEY && firstPubKey == NULL) + { + firstPubKey = info; + // skip the free + continue; + } + + OSSL_STORE_INFO_free(info); + } + + if (ret == NULL && firstPubKey != NULL) + { + ret = OSSL_STORE_INFO_get1_PUBKEY(firstPubKey); + } + + if (ret == NULL) + { + ERR_clear_error(); + ERR_put_error(ERR_LIB_NONE, 0, EVP_R_NO_KEY_SET, __FILE__, __LINE__); + } + +end: + if (firstPubKey != NULL) + { + OSSL_STORE_INFO_free(firstPubKey); + } + + if (store != NULL) + { + OSSL_STORE_close(store); + } + + if (ret == NULL) + { + if (prov != NULL) + { + assert(libCtx != NULL); + // we still want a separate check for libCtx as only prov could be NULL + OSSL_PROVIDER_unload(prov); + } + + if (libCtx != NULL) + { + OSSL_LIB_CTX_free(libCtx); + } + + *extraHandle = NULL; + } + else + { + EvpPKeyExtraHandle* extra = (EvpPKeyExtraHandle*)malloc(sizeof(EvpPKeyExtraHandle)); + extra->prov = prov; + extra->libCtx = libCtx; + extra->references = 1; + *extraHandle = extra; + } + + return ret; +#else + (void)providerName; + (void)keyUri; + ERR_put_error(ERR_LIB_NONE, 0, ERR_R_DISABLED, __FILE__, __LINE__); + *extraHandle = NULL; + return NULL; +#endif +} + +EVP_PKEY_CTX* CryptoNative_EvpPKeyCtxCreateFromPKey(EVP_PKEY* pkey, void* extraHandle) +{ + assert(pkey != NULL); + +#ifdef NEED_OPENSSL_3_0 + EvpPKeyExtraHandle* handle = (EvpPKeyExtraHandle*)extraHandle; + OSSL_LIB_CTX* libCtx = (handle != NULL) ? handle->libCtx : NULL; + return EVP_PKEY_CTX_new_from_pkey(libCtx, pkey, NULL); +#else + assert(libCtx == NULL); + return EVP_PKEY_CTX_new(pkey, NULL); +#endif +} + +int32_t CryptoNative_EvpPKeyCtxConfigureForECDSASign(EVP_PKEY_CTX* ctx) +{ + if (ctx == NULL) + { + return 0; + } + + if (EVP_PKEY_sign_init(ctx) <= 0) + { + return 0; + } + + return 1; +} + +int32_t CryptoNative_EvpPKeyCtxConfigureForECDSAVerify(EVP_PKEY_CTX* ctx) +{ + if (ctx == NULL) + { + return 0; + } + + if (EVP_PKEY_verify_init(ctx) <= 0) + { + return 0; + } + + return 1; +} + +int32_t CryptoNative_EvpPKeyCtxSignHash(EVP_PKEY_CTX* ctx, + const uint8_t* hash, + int32_t hashLen, + uint8_t* destination, + int32_t* destinationLen) +{ + assert(ctx != NULL); + assert(hash != NULL); + assert(hashLen > 0); + assert(destinationLen != NULL); + assert(destination != NULL || *destinationLen == 0); + + ERR_clear_error(); + size_t written = Int32ToSizeT(*destinationLen); + + if (EVP_PKEY_sign(ctx, destination, &written, hash, Int32ToSizeT(hashLen)) > 0) + { + *destinationLen = SizeTToInt32(written); + return 1; + } + + return 0; +} + +int32_t CryptoNative_EvpPKeyCtxVerifyHash(EVP_PKEY_CTX* ctx, + const uint8_t* hash, + int32_t hashLen, + uint8_t* signature, + int32_t signatureLen) +{ + ERR_clear_error(); + return EVP_PKEY_verify(ctx, signature, Int32ToSizeT(signatureLen), hash, Int32ToSizeT(hashLen)) > 0; +} diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.h b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.h index e4d5f85d4b9ec3..8d7714a508ce40 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.h +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.h @@ -12,12 +12,6 @@ Returns the new EVP_PKEY instance. */ PALEXPORT EVP_PKEY* CryptoNative_EvpPkeyCreate(void); -/* -Create a new EVP_PKEY that has the same interior key as currentKey, -optionally verifying that the key has the correct algorithm. -*/ -PALEXPORT EVP_PKEY* CryptoNative_EvpPKeyDuplicate(EVP_PKEY* currentKey, int32_t algId); - /* Cleans up and deletes a EVP_PKEY instance. @@ -27,12 +21,12 @@ No-op if pkey is null. The given EVP_PKEY pointer is invalid after this call. Always succeeds. */ -PALEXPORT void CryptoNative_EvpPkeyDestroy(EVP_PKEY* pkey); +PALEXPORT void CryptoNative_EvpPkeyDestroy(EVP_PKEY* pkey, void* extraHandle); /* -Returns the maximum size, in bytes, of an operation with the provided key. +Returns the cryptographic length of the cryptosystem to which the key belongs, in bits. */ -PALEXPORT int32_t CryptoNative_EvpPKeySize(EVP_PKEY* pkey); +PALEXPORT int32_t CryptoNative_EvpPKeyBits(EVP_PKEY* pkey); /* Used by System.Security.Cryptography.X509Certificates' OpenSslX509CertificateReader when @@ -41,7 +35,16 @@ duplicating a private key context as part of duplicating the Pal object. Returns the number (as of this call) of references to the EVP_PKEY. Anything less than 2 is an error, because the key is already in the process of being freed. */ -PALEXPORT int32_t CryptoNative_UpRefEvpPkey(EVP_PKEY* pkey); +PALEXPORT int32_t CryptoNative_UpRefEvpPkey(EVP_PKEY* pkey, void* extraHandle); + +/* +Returns one of the following 4 values for the given EVP_PKEY: + 0 - unknown + EVP_PKEY_RSA - RSA + EVP_PKEY_EC - EC + EVP_PKEY_DSA - DSA +*/ +PALEXPORT int32_t CryptoNative_EvpPKeyType(EVP_PKEY* key); /* Decodes an X.509 SubjectPublicKeyInfo into an EVP_PKEY*, verifying the interpreted algorithm type. @@ -104,3 +107,51 @@ Returns a valid EVP_PKEY* on success, NULL on failure. haveEngine is 1 if OpenSSL ENGINE's are supported, otherwise 0. */ PALEXPORT EVP_PKEY* CryptoNative_LoadPublicKeyFromEngine(const char* engineName, const char* keyName, int32_t* haveEngine); + +/* +Load a key by URI from a specified OSSL_PROVIDER. + +Returns a valid EVP_PKEY* on success, NULL on failure. +On success extraHandle may be non-null value which we need to keep alive +until the EVP_PKEY is destroyed. +*/ +PALEXPORT EVP_PKEY* CryptoNative_LoadKeyFromProvider(const char* providerName, const char* keyUri, void** extraHandle); + +/* +It's a wrapper for EVP_PKEY_CTX_new_from_pkey and EVP_PKEY_CTX_new +which handles extraHandle. +*/ +PALEXPORT EVP_PKEY_CTX* CryptoNative_EvpPKeyCtxCreateFromPKey(EVP_PKEY* pkey, void* extraHandle); + + +/* +Configures the EVP_PKEY_CTX for use in an ECDSA sign operation. +Returns 1 on success, 0 on failure. +*/ +PALEXPORT int32_t CryptoNative_EvpPKeyCtxConfigureForECDSASign(EVP_PKEY_CTX* ctx); + +/* +Configures the EVP_PKEY_CTX for use in an ECDSA verify operation. +Returns 1 on success, 0 on failure. +*/ +PALEXPORT int32_t CryptoNative_EvpPKeyCtxConfigureForECDSAVerify(EVP_PKEY_CTX* ctx); + +/* +Signs hash using EVP_PKEY_CTX. +Returns 1 on success, 0 on failure. +*/ +PALEXPORT int32_t CryptoNative_EvpPKeyCtxSignHash(EVP_PKEY_CTX* ctx, + const uint8_t* hash, + int32_t hashLen, + uint8_t* destination, + int32_t* destinationLen); + +/* +Verifies hash using EVP_PKEY_CTX. +Returns 1 on success, 0 on failure. +*/ +PALEXPORT int32_t CryptoNative_EvpPKeyCtxVerifyHash(EVP_PKEY_CTX* ctx, + const uint8_t* hash, + int32_t hashLen, + uint8_t* signature, + int32_t signatureLen); diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdh.c b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdh.c index b6a9daf715998d..c1126e5206fcb3 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdh.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdh.c @@ -2,8 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. #include "pal_evp_pkey_ecdh.h" +#include "pal_evp_pkey.h" -EVP_PKEY_CTX* CryptoNative_EvpPKeyCtxCreate(EVP_PKEY* pkey, EVP_PKEY* peerkey, uint32_t* secretLength) +EVP_PKEY_CTX* CryptoNative_EvpPKeyCtxCreate(EVP_PKEY* pkey, void* extraHandle, EVP_PKEY* peerkey, uint32_t* secretLength) { if (secretLength != NULL) *secretLength = 0; @@ -16,7 +17,7 @@ EVP_PKEY_CTX* CryptoNative_EvpPKeyCtxCreate(EVP_PKEY* pkey, EVP_PKEY* peerkey, u } /* Create the context for the shared secret derivation */ - EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey, NULL); + EVP_PKEY_CTX* ctx = CryptoNative_EvpPKeyCtxCreateFromPKey(pkey, extraHandle); if (ctx == NULL) { diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdh.h b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdh.h index 2319669fdeae84..b852b5995ca608 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdh.h +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdh.h @@ -5,7 +5,7 @@ #include "pal_compiler.h" #include "opensslshim.h" -PALEXPORT EVP_PKEY_CTX* CryptoNative_EvpPKeyCtxCreate(EVP_PKEY* pkey, EVP_PKEY* peerkey, uint32_t* secretLength); +PALEXPORT EVP_PKEY_CTX* CryptoNative_EvpPKeyCtxCreate(EVP_PKEY* pkey, void* extraHandle, EVP_PKEY* peerkey, uint32_t* secretLength); PALEXPORT int32_t CryptoNative_EvpPKeyDeriveSecretAgreement(uint8_t* secret, uint32_t secretLength, EVP_PKEY_CTX* ctx); 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 043bf9f9d1edab..9f14667e8981c1 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 @@ -1,12 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#include "pal_evp_pkey.h" #include "pal_evp_pkey_rsa.h" #include "pal_utilities.h" #include -static int HasNoPrivateKey(const RSA* rsa); - EVP_PKEY* CryptoNative_EvpPKeyCreateRsa(RSA* currentKey) { assert(currentKey != NULL); @@ -103,6 +102,7 @@ static bool ConfigureEncryption(EVP_PKEY_CTX* ctx, RsaPaddingMode padding, const } int32_t CryptoNative_RsaDecrypt(EVP_PKEY* pkey, + void* extraHandle, const uint8_t* source, int32_t sourceLen, RsaPaddingMode padding, @@ -118,7 +118,7 @@ int32_t CryptoNative_RsaDecrypt(EVP_PKEY* pkey, ERR_clear_error(); - EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey, NULL); + EVP_PKEY_CTX* ctx = CryptoNative_EvpPKeyCtxCreateFromPKey(pkey, extraHandle); int ret = -1; @@ -132,17 +132,6 @@ int32_t CryptoNative_RsaDecrypt(EVP_PKEY* pkey, goto done; } - // This check may no longer be needed on OpenSSL 3.0 - { - const 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__); - goto done; - } - } - size_t written = Int32ToSizeT(destinationLen); if (EVP_PKEY_decrypt(ctx, destination, &written, source, Int32ToSizeT(sourceLen)) > 0) @@ -160,6 +149,7 @@ int32_t CryptoNative_RsaDecrypt(EVP_PKEY* pkey, } int32_t CryptoNative_RsaEncrypt(EVP_PKEY* pkey, + void* extraHandle, const uint8_t* source, int32_t sourceLen, RsaPaddingMode padding, @@ -174,7 +164,7 @@ int32_t CryptoNative_RsaEncrypt(EVP_PKEY* pkey, ERR_clear_error(); - EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey, NULL); + EVP_PKEY_CTX* ctx = CryptoNative_EvpPKeyCtxCreateFromPKey(pkey, extraHandle); int ret = -1; @@ -204,7 +194,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 EvpPKeyCtxConfigureForRsaSignVerify(EVP_PKEY_CTX* ctx, RsaPaddingMode padding, const EVP_MD* digest) { if (padding == RsaPaddingPkcs1) { @@ -235,160 +225,32 @@ static bool ConfigureSignature(EVP_PKEY_CTX* ctx, RsaPaddingMode padding, const return true; } -int32_t CryptoNative_RsaSignHash(EVP_PKEY* pkey, - RsaPaddingMode padding, - const EVP_MD* digest, - const uint8_t* hash, - int32_t hashLen, - uint8_t* destination, - int32_t destinationLen) -{ - assert(pkey != NULL); - assert(destination != NULL); - assert(padding >= RsaPaddingPkcs1 && padding <= RsaPaddingOaepOrPss); - assert(digest != NULL || padding == RsaPaddingPkcs1); - - ERR_clear_error(); - - EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey, NULL); - - int ret = -1; - - if (ctx == NULL || EVP_PKEY_sign_init(ctx) <= 0) - { - goto done; - } - - if (!ConfigureSignature(ctx, padding, digest)) - { - goto done; - } - - // This check may no longer be needed on OpenSSL 3.0 - { - const 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__); - goto done; - } - } - - size_t written = Int32ToSizeT(destinationLen); - - if (EVP_PKEY_sign(ctx, destination, &written, hash, Int32ToSizeT(hashLen)) > 0) - { - ret = SizeTToInt32(written); - } - -done: - if (ctx != NULL) - { - EVP_PKEY_CTX_free(ctx); - } - - return ret; -} - -int32_t CryptoNative_RsaVerifyHash(EVP_PKEY* pkey, - RsaPaddingMode padding, - const EVP_MD* digest, - const uint8_t* hash, - int32_t hashLen, - const uint8_t* signature, - int32_t signatureLen) +int32_t CryptoNative_EvpPKeyCtxConfigureForRsaSign(EVP_PKEY_CTX* ctx, RsaPaddingMode padding, const EVP_MD* digest) { - assert(pkey != NULL); - assert(signature != NULL); - assert(padding >= RsaPaddingPkcs1 && padding <= RsaPaddingOaepOrPss); - assert(digest != NULL || padding == RsaPaddingPkcs1); - - ERR_clear_error(); - - EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey, NULL); - - int ret = -1; - - if (ctx == NULL || EVP_PKEY_verify_init(ctx) <= 0) - { - goto done; - } - - if (!ConfigureSignature(ctx, padding, digest)) + if (EVP_PKEY_sign_init(ctx) <= 0) { - goto done; - } - - // EVP_PKEY_verify is not consistent on whether a missized hash is an error or just a mismatch. - // Normalize to mismatch. - if (hashLen != EVP_MD_get_size(digest)) - { - ret = 0; - goto done; + return 0; } - ret = EVP_PKEY_verify(ctx, signature, Int32ToSizeT(signatureLen), hash, Int32ToSizeT(hashLen)); - -done: - if (ctx != NULL) + if (!EvpPKeyCtxConfigureForRsaSignVerify(ctx, padding, digest)) { - EVP_PKEY_CTX_free(ctx); + return 0; } - return ret; + return 1; } -static int HasNoPrivateKey(const RSA* rsa) +int32_t CryptoNative_EvpPKeyCtxConfigureForRsaVerify(EVP_PKEY_CTX* ctx, RsaPaddingMode padding, const EVP_MD* digest) { - if (rsa == NULL) - return 1; - - // Shared pointer, don't free. - const RSA_METHOD* meth = RSA_get_method(rsa); - - // The method has described 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_test_flags(rsa, RSA_FLAG_EXT_PKEY) || RSA_meth_get_flags((RSA_METHOD*)meth) & RSA_FLAG_EXT_PKEY) -#pragma clang diagnostic pop + if (EVP_PKEY_verify_init(ctx) <= 0) { 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) + if (!EvpPKeyCtxConfigureForRsaSignVerify(ctx, padding, digest)) { 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; + return 1; } 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 2d3c58787650a8..94424464c109b3 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 @@ -31,6 +31,7 @@ Decrypt source into destination using the specified RSA key (wrapped in an EVP_P Returns the number of bytes written to destination, -1 on error. */ PALEXPORT int32_t CryptoNative_RsaDecrypt(EVP_PKEY* pkey, + void* extraHandle, const uint8_t* source, int32_t sourceLen, RsaPaddingMode padding, @@ -44,6 +45,7 @@ Encrypt source into destination using the specified RSA key (wrapped in an EVP_P Returns the number of bytes written to destination, -1 on error. */ PALEXPORT int32_t CryptoNative_RsaEncrypt(EVP_PKEY* pkey, + void* extraHandle, const uint8_t* source, int32_t sourceLen, RsaPaddingMode padding, @@ -52,30 +54,13 @@ PALEXPORT int32_t CryptoNative_RsaEncrypt(EVP_PKEY* pkey, int32_t destinationLen); /* -Complete the RSA signature generation for the specified hash using the provided RSA key -(wrapped in an EVP_PKEY) and padding/digest options. - -Returns the number of bytes written to destination, -1 on error. +Configures the EVP_PKEY_CTX for use in an RSA sign operation. +Returns 1 on success, 0 on failure. */ -PALEXPORT int32_t CryptoNative_RsaSignHash(EVP_PKEY* pkey, - RsaPaddingMode padding, - const EVP_MD* digest, - const uint8_t* hash, - int32_t hashLen, - uint8_t* destination, - int32_t destinationLen); +PALEXPORT int32_t CryptoNative_EvpPKeyCtxConfigureForRsaSign(EVP_PKEY_CTX* ctx, RsaPaddingMode padding, const EVP_MD* digest); /* -Verify an RSA signature for the specified hash using the provided RSA key (wrapped in an EVP_PKEY) -and padding/digest options. - -Returns 1 on a verified signature, 0 on a mismatched signature, -1 on error. +Configures the EVP_PKEY_CTX for use in an RSA verify operation. +Returns 1 on success, 0 on failure. */ -PALEXPORT int32_t CryptoNative_RsaVerifyHash(EVP_PKEY* pkey, - RsaPaddingMode padding, - const EVP_MD* digest, - const uint8_t* hash, - int32_t hashLen, - const uint8_t* signature, - int32_t signatureLen); - +PALEXPORT int32_t CryptoNative_EvpPKeyCtxConfigureForRsaVerify(EVP_PKEY_CTX* ctx, RsaPaddingMode padding, const EVP_MD* digest); diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_ssl.c b/src/native/libs/System.Security.Cryptography.Native/pal_ssl.c index dc0478ef2a230e..93d352702748f6 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_ssl.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_ssl.c @@ -1261,7 +1261,7 @@ int32_t CryptoNative_OpenSslGetProtocolSupport(SslProtocols protocol) if (evp != NULL) { - CryptoNative_EvpPkeyDestroy(evp); + CryptoNative_EvpPkeyDestroy(evp, NULL); } if (bio1) From 9733e32e30af234f8059e7e0a8b8c435872ee638 Mon Sep 17 00:00:00 2001 From: Krzysztof Wicher Date: Tue, 16 Jul 2024 15:27:28 +0200 Subject: [PATCH 02/17] Address self feedback (Lazy+leak) --- .../ECDiffieHellmanOpenSsl.Derive.cs | 4 +- .../Cryptography/ECDiffieHellmanOpenSsl.cs | 39 ++++++++++++------- .../Cryptography/ECDiffieHellmanOpenSsl.cs | 17 +++++--- .../Security/Cryptography/ECDsaOpenSsl.cs | 9 +++-- 4 files changed, 43 insertions(+), 26 deletions(-) diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs index c4e0147296e782..9ee46e02e5bc07 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs @@ -90,7 +90,7 @@ public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPa bool thisIsNamed; - using (SafeEcKeyHandle ecKey = Interop.Crypto.EvpPkeyGetEcKey(_key)) + using (SafeEcKeyHandle ecKey = Interop.Crypto.EvpPkeyGetEcKey(_key.Value)) { thisIsNamed = Interop.Crypto.EcKeyHasCurveName(ecKey); } @@ -114,7 +114,7 @@ public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPa // The only case when we will not directly use our key is when peer key is an explicit curve // but we're using named in which case we'll recreate our key with explicit parameters. - SafeEvpPKeyHandle ourKey = _key; + SafeEvpPKeyHandle ourKey = _key.Value; bool disposeOurKey = false; SafeEvpPKeyHandle? theirKey = null; diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs index 3015a7e1fc07aa..f83258ca30eaea 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs @@ -9,7 +9,7 @@ namespace System.Security.Cryptography { public sealed partial class ECDiffieHellmanOpenSsl : ECDiffieHellman { - private SafeEvpPKeyHandle _key; + private Lazy? _key; [UnsupportedOSPlatform("android")] [UnsupportedOSPlatform("browser")] @@ -19,7 +19,7 @@ public sealed partial class ECDiffieHellmanOpenSsl : ECDiffieHellman public ECDiffieHellmanOpenSsl(ECCurve curve) { ThrowIfNotSupported(); - _key = SafeEvpPKeyHandle.GenerateECKey(curve, out int keySize); + _key = new Lazy(SafeEvpPKeyHandle.GenerateECKey(curve, out int keySize)); KeySizeValue = keySize; } @@ -42,7 +42,7 @@ public ECDiffieHellmanOpenSsl(int keySize) { ThrowIfNotSupported(); base.KeySize = keySize; - _key = SafeEvpPKeyHandle.GenerateECKey(keySize); + _key = new Lazy(() => SafeEvpPKeyHandle.GenerateECKey(keySize)); } public override KeySizes[] LegalKeySizes => s_defaultKeySizes.CloneKeySizesArray(); @@ -51,8 +51,8 @@ protected override void Dispose(bool disposing) { if (disposing) { - _key?.Dispose(); - _key = null!; + FreeKey(); + _key = null; } base.Dispose(disposing); @@ -75,8 +75,8 @@ public override int KeySize base.KeySize = value; ThrowIfDisposed(); - _key.Dispose(); - _key = SafeEvpPKeyHandle.GenerateECKey(value); + FreeKey(); + _key = new Lazy(SafeEvpPKeyHandle.GenerateECKey(value)); } } @@ -84,8 +84,8 @@ public override void GenerateKey(ECCurve curve) { ThrowIfDisposed(); - _key.Dispose(); - _key = SafeEvpPKeyHandle.GenerateECKey(curve, out int keySizeValue); + FreeKey(); + _key = new Lazy(SafeEvpPKeyHandle.GenerateECKey(curve, out int keySizeValue)); KeySizeValue = keySizeValue; } @@ -94,15 +94,17 @@ public override ECDiffieHellmanPublicKey PublicKey get { ThrowIfDisposed(); - return new ECDiffieHellmanOpenSslPublicKey(_key); + + // This may generate the key + return new ECDiffieHellmanOpenSslPublicKey(_key.Value); } } public override void ImportParameters(ECParameters parameters) { ThrowIfDisposed(); - _key.Dispose(); - _key = SafeEvpPKeyHandle.GenerateECKey(parameters, out int keySize); + FreeKey(); + _key = new Lazy(SafeEvpPKeyHandle.GenerateECKey(parameters, out int keySize)); KeySizeValue = keySize; } @@ -110,7 +112,7 @@ public override ECParameters ExportExplicitParameters(bool includePrivateParamet { ThrowIfDisposed(); - using (SafeEcKeyHandle ecKey = Interop.Crypto.EvpPkeyGetEcKey(_key)) + using (SafeEcKeyHandle ecKey = Interop.Crypto.EvpPkeyGetEcKey(_key.Value)) { return ECOpenSsl.ExportExplicitParameters(ecKey, includePrivateParameters); } @@ -120,7 +122,7 @@ public override ECParameters ExportParameters(bool includePrivateParameters) { ThrowIfDisposed(); - using (SafeEcKeyHandle ecKey = Interop.Crypto.EvpPkeyGetEcKey(_key)) + using (SafeEcKeyHandle ecKey = Interop.Crypto.EvpPkeyGetEcKey(_key.Value)) { return ECOpenSsl.ExportParameters(ecKey, includePrivateParameters); } @@ -144,6 +146,15 @@ public override void ImportEncryptedPkcs8PrivateKey( base.ImportEncryptedPkcs8PrivateKey(password, source, out bytesRead); } + private void FreeKey() + { + if (_key != null && _key.IsValueCreated) + { + SafeEvpPKeyHandle handle = _key.Value; + handle?.Dispose(); + } + } + [MemberNotNull(nameof(_key))] private void ThrowIfDisposed() { diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs index 2c07c555cbab06..454e3e66bc528c 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs @@ -31,8 +31,8 @@ public ECDiffieHellmanOpenSsl(SafeEvpPKeyHandle pkeyHandle) ThrowIfNotSupported(); - _key = pkeyHandle.DuplicateHandle(); - KeySizeValue = _key.GetKeySizeBits(); + _key = new Lazy(pkeyHandle.DuplicateHandle()); + KeySizeValue = _key.Value.GetKeySizeBits(); } /// @@ -57,9 +57,13 @@ public ECDiffieHellmanOpenSsl(IntPtr handle) ThrowIfNotSupported(); - SafeEcKeyHandle ecKeyHandle = SafeEcKeyHandle.DuplicateHandle(handle); - _key = Interop.Crypto.CreateEvpPkeyFromEcKey(ecKeyHandle); - KeySizeValue = _key.GetKeySizeBits(); + using (SafeEcKeyHandle ecKeyHandle = SafeEcKeyHandle.DuplicateHandle(handle)) + { + // CreateEvpPkeyFromEcKey already uprefs so nothing else to do + _key = new Lazy(Interop.Crypto.CreateEvpPkeyFromEcKey(ecKeyHandle)); + } + + KeySizeValue = _key.Value.GetKeySizeBits(); } /// @@ -69,7 +73,8 @@ public ECDiffieHellmanOpenSsl(IntPtr handle) /// A SafeHandle for the EVP_PKEY key in OpenSSL public SafeEvpPKeyHandle DuplicateKeyHandle() { - return _key.DuplicateHandle(); + ThrowIfDisposed(); + return _key.Value.DuplicateHandle(); } static partial void ThrowIfNotSupported() diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDsaOpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDsaOpenSsl.cs index c32049b634bafc..c6aa25676e1351 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDsaOpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDsaOpenSsl.cs @@ -58,11 +58,12 @@ public ECDsaOpenSsl(IntPtr handle) ThrowIfNotSupported(); - // This handle is meant for ECOpenSsl which will take ownership of this handle and will dispose it. - SafeEcKeyHandle ecKeyHandle = SafeEcKeyHandle.DuplicateHandle(handle); + using (SafeEcKeyHandle ecKeyHandle = SafeEcKeyHandle.DuplicateHandle(handle)) + { + // CreateEvpPkeyFromEcKey already uprefs so nothing else to do + _key = new Lazy(Interop.Crypto.CreateEvpPkeyFromEcKey(ecKeyHandle)); + } - // CreateEvpPkeyFromEcKey already uprefs so nothing else to do - _key = new Lazy(Interop.Crypto.CreateEvpPkeyFromEcKey(ecKeyHandle)); ForceSetKeySize(_key.Value.GetKeySizeBits()); } From 80f399623990ee3e57d1c90abd46fad2ef9de1c0 Mon Sep 17 00:00:00 2001 From: Krzysztof Wicher Date: Tue, 16 Jul 2024 19:35:57 +0200 Subject: [PATCH 03/17] Attempt to fix EVP_PKEY_CTX_new_from_pkey errors --- .../opensslshim.h | 3 --- .../osslcompat_30.h | 3 ++- .../pal_evp_pkey.c | 16 ++++++++++------ 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h index 1562cec98ec0cf..e76d310e528774 100644 --- a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h +++ b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h @@ -447,7 +447,6 @@ extern bool g_libSslUses32BitTime; REQUIRED_FUNCTION(EVP_PKEY_encrypt_init) \ REQUIRED_FUNCTION(EVP_PKEY_free) \ RENAMED_FUNCTION(EVP_PKEY_get_base_id, EVP_PKEY_base_id) \ - RENAMED_FUNCTION(EVP_PKEY_get_size, EVP_PKEY_size) \ RENAMED_FUNCTION(EVP_PKEY_get_bits, EVP_PKEY_bits) \ FALLBACK_FUNCTION(EVP_PKEY_get0_RSA) \ REQUIRED_FUNCTION(EVP_PKEY_get1_DSA) \ @@ -993,7 +992,6 @@ extern TYPEOF(OPENSSL_gmtime)* OPENSSL_gmtime_ptr; #define EVP_PKEY_encrypt EVP_PKEY_encrypt_ptr #define EVP_PKEY_free EVP_PKEY_free_ptr #define EVP_PKEY_get_base_id EVP_PKEY_get_base_id_ptr -#define EVP_PKEY_get_size EVP_PKEY_get_size_ptr #define EVP_PKEY_get_bits EVP_PKEY_get_bits_ptr #define EVP_PKEY_get0_RSA EVP_PKEY_get0_RSA_ptr #define EVP_PKEY_get1_DSA EVP_PKEY_get1_DSA_ptr @@ -1363,7 +1361,6 @@ extern TYPEOF(OPENSSL_gmtime)* OPENSSL_gmtime_ptr; // Undo renames for renamed-in-3.0 #define EVP_MD_get_size EVP_MD_size #define EVP_PKEY_get_base_id EVP_PKEY_base_id -#define EVP_PKEY_get_size EVP_PKEY_size #define EVP_PKEY_get_bits EVP_PKEY_bits #define SSL_get1_peer_certificate SSL_get_peer_certificate #define EVP_CIPHER_get_nid EVP_CIPHER_nid diff --git a/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h b/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h index 930b4e78fc3c60..a114785cb7acba 100644 --- a/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h +++ b/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h @@ -59,8 +59,9 @@ int EVP_PKEY_CTX_set_rsa_padding(EVP_PKEY_CTX* ctx, int pad_mode); int EVP_PKEY_CTX_set_rsa_pss_saltlen(EVP_PKEY_CTX* ctx, int saltlen); int EVP_PKEY_CTX_set_signature_md(EVP_PKEY_CTX* ctx, const EVP_MD* md); int EVP_PKEY_get_base_id(const EVP_PKEY* pkey); -int EVP_PKEY_get_size(const EVP_PKEY* pkey); int EVP_PKEY_get_bits(const EVP_PKEY* pkey); +EVP_PKEY_CTX *EVP_PKEY_CTX_new_from_pkey( + OSSL_LIB_CTX *libctx, EVP_PKEY *pkey, const char *propquery); OSSL_PARAM OSSL_PARAM_construct_end(void); OSSL_PARAM OSSL_PARAM_construct_int32(const char *key, int32_t *buf); diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c index 352512a1bb695e..c89200e40675ef 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c @@ -721,13 +721,17 @@ EVP_PKEY_CTX* CryptoNative_EvpPKeyCtxCreateFromPKey(EVP_PKEY* pkey, void* extraH assert(pkey != NULL); #ifdef NEED_OPENSSL_3_0 - EvpPKeyExtraHandle* handle = (EvpPKeyExtraHandle*)extraHandle; - OSSL_LIB_CTX* libCtx = (handle != NULL) ? handle->libCtx : NULL; - return EVP_PKEY_CTX_new_from_pkey(libCtx, pkey, NULL); -#else - assert(libCtx == NULL); - return EVP_PKEY_CTX_new(pkey, NULL); + if (API_EXISTS(EVP_PKEY_CTX_new_from_pkey)) + { + EvpPKeyExtraHandle* handle = (EvpPKeyExtraHandle*)extraHandle; + OSSL_LIB_CTX* libCtx = (handle != NULL) ? handle->libCtx : NULL; + return EVP_PKEY_CTX_new_from_pkey(libCtx, pkey, NULL); + } + else #endif + { + return EVP_PKEY_CTX_new(pkey, NULL); + } } int32_t CryptoNative_EvpPKeyCtxConfigureForECDSASign(EVP_PKEY_CTX* ctx) From 3c5dd0d979df93aae594055ac9266f30c5ee11a9 Mon Sep 17 00:00:00 2001 From: Krzysztof Wicher Date: Tue, 16 Jul 2024 21:50:27 +0200 Subject: [PATCH 04/17] update osslcompat_30.h with EVP_PKEY types --- .../libs/System.Security.Cryptography.Native/osslcompat_30.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h b/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h index a114785cb7acba..a8a09811e6a504 100644 --- a/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h +++ b/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h @@ -20,6 +20,10 @@ #define OSSL_STORE_INFO_PKEY 4 #define OSSL_STORE_INFO_PUBKEY 3 +#define EVP_PKEY_RSA_PSS 912 +#define EVP_PKEY_ED448 1088 +#define EVP_PKEY_ED25519 1087 + typedef struct ossl_lib_ctx_st OSSL_LIB_CTX; typedef struct ossl_param_st OSSL_PARAM; typedef struct ossl_provider_st OSSL_PROVIDER; From ee8cb2986c76a1b2ffa08ada7ec82cc0859687b4 Mon Sep 17 00:00:00 2001 From: Krzysztof Wicher Date: Tue, 16 Jul 2024 23:16:23 +0200 Subject: [PATCH 05/17] properly ifdef extraHandle code --- .../pal_evp_pkey.c | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c index c89200e40675ef..95da0b97adfab2 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c @@ -8,7 +8,6 @@ #ifdef NEED_OPENSSL_3_0 c_static_assert(OSSL_STORE_INFO_PKEY == 4); c_static_assert(OSSL_STORE_INFO_PUBKEY == 3); -#endif struct EvpPKeyExtraHandle_st { @@ -19,12 +18,6 @@ struct EvpPKeyExtraHandle_st typedef struct EvpPKeyExtraHandle_st EvpPKeyExtraHandle; -EVP_PKEY* CryptoNative_EvpPkeyCreate(void) -{ - ERR_clear_error(); - return EVP_PKEY_new(); -} - static void CryptoNative_EvpPkeyExtraHandleDestroy(EvpPKeyExtraHandle* handle) { assert(handle->references >= 1); @@ -40,6 +33,13 @@ static void CryptoNative_EvpPkeyExtraHandleDestroy(EvpPKeyExtraHandle* handle) free(handle); } } +#endif + +EVP_PKEY* CryptoNative_EvpPkeyCreate(void) +{ + ERR_clear_error(); + return EVP_PKEY_new(); +} void CryptoNative_EvpPkeyDestroy(EVP_PKEY* pkey, void* extraHandle) { @@ -48,11 +48,15 @@ void CryptoNative_EvpPkeyDestroy(EVP_PKEY* pkey, void* extraHandle) EVP_PKEY_free(pkey); } +#ifdef NEED_OPENSSL_3_0 if (extraHandle != NULL) { EvpPKeyExtraHandle* extra = (EvpPKeyExtraHandle*)extraHandle; CryptoNative_EvpPkeyExtraHandleDestroy(extra); } +#else + assert(extraHandle == NULL); +#endif } int32_t CryptoNative_EvpPKeyBits(EVP_PKEY* pkey) @@ -67,17 +71,21 @@ int32_t CryptoNative_EvpPKeyBits(EVP_PKEY* pkey) int32_t CryptoNative_UpRefEvpPkey(EVP_PKEY* pkey, void* extraHandle) { - EvpPKeyExtraHandle* extra = (EvpPKeyExtraHandle*)extraHandle; - if (!pkey) { return 0; } +#ifdef NEED_OPENSSL_3_0 + EvpPKeyExtraHandle* extra = (EvpPKeyExtraHandle*)extraHandle; + if (extra != NULL) { extra->references++; } +#else + assert(extraHandle == NULL); +#endif // No error queue impact. return EVP_PKEY_up_ref(pkey); @@ -730,6 +738,9 @@ EVP_PKEY_CTX* CryptoNative_EvpPKeyCtxCreateFromPKey(EVP_PKEY* pkey, void* extraH else #endif { +#ifndef NEED_OPENSSL_3_0 + (void)extraHandle; +#endif return EVP_PKEY_CTX_new(pkey, NULL); } } From fc20bb128fdb50a0ec57060bdf21090f2126def0 Mon Sep 17 00:00:00 2001 From: Krzysztof Wicher Date: Wed, 17 Jul 2024 00:12:43 +0200 Subject: [PATCH 06/17] fix: unused parameter extraHandle when OSSL 3 not available --- .../libs/System.Security.Cryptography.Native/pal_evp_pkey.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c index 95da0b97adfab2..bbc5bcca7f1780 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c @@ -55,6 +55,7 @@ void CryptoNative_EvpPkeyDestroy(EVP_PKEY* pkey, void* extraHandle) CryptoNative_EvpPkeyExtraHandleDestroy(extra); } #else + (void)extraHandle; assert(extraHandle == NULL); #endif } @@ -84,6 +85,7 @@ int32_t CryptoNative_UpRefEvpPkey(EVP_PKEY* pkey, void* extraHandle) extra->references++; } #else + (void)extraHandle; assert(extraHandle == NULL); #endif From a9ab139c1bac66683619f732e91f242ebb64d04d Mon Sep 17 00:00:00 2001 From: Krzysztof Wicher Date: Wed, 17 Jul 2024 15:24:46 +0200 Subject: [PATCH 07/17] bugfixes, feedback --- .../SafeHandles/SafeEvpPkeyCtxHandle.Unix.cs | 46 ----------------- .../ECDiffieHellmanOpenSsl.Derive.cs | 17 +++---- .../Security/Cryptography/ECDsaOpenSsl.cs | 50 +++++++++---------- .../Security/Cryptography/RSAOpenSsl.cs | 43 ++++++++-------- .../Cryptography/ECDiffieHellmanOpenSsl.cs | 5 ++ .../Security/Cryptography/ECDsaOpenSsl.cs | 10 ++-- .../Security/Cryptography/RSAOpenSsl.cs | 5 ++ .../Cryptography/SafeEvpPKeyHandle.OpenSsl.cs | 2 +- .../tests/osslplugins/README.md | 5 -- .../pal_evp_pkey.c | 28 +++++++---- 10 files changed, 90 insertions(+), 121 deletions(-) diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpPkeyCtxHandle.Unix.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpPkeyCtxHandle.Unix.cs index 9a76ddab26143d..0cd026c3b233a6 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpPkeyCtxHandle.Unix.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpPkeyCtxHandle.Unix.cs @@ -27,51 +27,5 @@ protected override bool ReleaseHandle() } public override bool IsInvalid => handle == IntPtr.Zero; - - internal static SafeEvpPKeyCtxHandle CreateFromEvpPkey(SafeEvpPKeyHandle evpPkey) - { - return Interop.Crypto.EvpPKeyCtxCreate(evpPkey); - } - - internal void ConfigureForRSASign(HashAlgorithmName hashAlgorithm, RSASignaturePaddingMode padding) - { - Interop.Crypto.CryptoNative_ConfigureForRsaSign(this, padding, hashAlgorithm); - } - - internal void ConfigureForRSAVerify(HashAlgorithmName hashAlgorithm, RSASignaturePaddingMode padding) - { - Interop.Crypto.CryptoNative_ConfigureForRsaVerify(this, padding, hashAlgorithm); - } - - internal void ConfigureForECDSASign() - { - Interop.Crypto.EvpPKeyCtxConfigureForECDSASign(this); - } - - internal void ConfigureForECDSAVerify() - { - Interop.Crypto.EvpPKeyCtxConfigureForECDSAVerify(this); - } - - internal bool TryGetSufficientSignatureSizeInBytesCore( - ReadOnlySpan hash, out int bytesWritten) - { - return Interop.Crypto.TryEvpPKeyCtxSignatureSize(this, hash, out bytesWritten); - } - - internal bool TrySignHashCore( - ReadOnlySpan hash, - Span outputSignature, - out int bytesWritten) - { - return Interop.Crypto.TryEvpPKeyCtxSignHash(this, hash, outputSignature, out bytesWritten); - } - - internal bool VerifyHashCore( - ReadOnlySpan hash, - ReadOnlySpan signature) - { - return Interop.Crypto.EvpPKeyCtxVerifyHash(this, hash, signature); - } } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs index 9ee46e02e5bc07..04f3dd2037787b 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs @@ -112,11 +112,8 @@ public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPa bool otherIsNamed = otherKey.HasCurveName; - // The only case when we will not directly use our key is when peer key is an explicit curve - // but we're using named in which case we'll recreate our key with explicit parameters. - SafeEvpPKeyHandle ourKey = _key.Value; - bool disposeOurKey = false; - + // We need to always duplicate handle in case this operation is done by multiple threads and one of them disposes the handle + SafeEvpPKeyHandle? ourKey = null; SafeEvpPKeyHandle? theirKey = null; byte[]? rented = null; int secretLength = 0; @@ -130,10 +127,13 @@ public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPa if (otherIsNamed == thisIsNamed) { + ourKey = _key.Value.DuplicateHandle(); theirKey = otherKey.DuplicateKeyHandle(); } else if (otherIsNamed) { + ourKey = _key.Value.DuplicateHandle(); + using (ECOpenSsl tmp = new ECOpenSsl(otherKey.ExportExplicitParameters())) { theirKey = tmp.CreateKeyHandle(); @@ -146,11 +146,9 @@ public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPa // This is generally not expected to fail except: // - when key can't be accessed but is available (i.e. TPM) // - private key is actually missing - using (ECOpenSsl tmp = new ECOpenSsl(ExportExplicitParameters(true))) { ourKey = tmp.CreateKeyHandle(); - disposeOurKey = true; } } catch (CryptographicException) @@ -209,10 +207,7 @@ public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPa otherKey.Dispose(); } - if (disposeOurKey) - { - ourKey.Dispose(); - } + ourKey?.Dispose(); if (rented != null) { diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDsaOpenSsl.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDsaOpenSsl.cs index ffa1ccdb191d4c..2800c53e482200 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDsaOpenSsl.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDsaOpenSsl.cs @@ -84,17 +84,19 @@ public override byte[] SignHash(byte[] hash) ArgumentNullException.ThrowIfNull(hash); ThrowIfDisposed(); - using (SafeEvpPKeyCtxHandle ctx = CreateCtx()) + // We need to duplicate key handle in case it's being used by multiple threads and one of them disposes it + using (SafeEvpPKeyHandle key = _key.Value.DuplicateHandle()) + using (SafeEvpPKeyCtxHandle ctx = Interop.Crypto.EvpPKeyCtxCreate(key)) { - ctx.ConfigureForECDSASign(); + Interop.Crypto.EvpPKeyCtxConfigureForECDSASign(ctx); - if (!ctx.TryGetSufficientSignatureSizeInBytesCore(hash, out int sufficientDerSignatureSize)) + if (!Interop.Crypto.TryEvpPKeyCtxSignatureSize(ctx, hash, out int sufficientDerSignatureSize)) { throw new CryptographicException(); } Span derSignature = sufficientDerSignatureSize <= SignatureStackBufSize ? stackalloc byte[sufficientDerSignatureSize] : new byte[sufficientDerSignatureSize]; - if (!ctx.TrySignHashCore(hash, derSignature, out int bytesWritten)) + if (!Interop.Crypto.TryEvpPKeyCtxSignHash(ctx, hash, derSignature, out int bytesWritten)) { throw new CryptographicException(); } @@ -121,14 +123,6 @@ public override bool TrySignHash(ReadOnlySpan hash, Span destination out bytesWritten); } - // SafeEvpPKeyCtxHandle doesn't touch ref counts of SafeEvpPKeyHandle so we need to keep it alive during it's lifetime. - // We only use CreateCtx from within single `using` statement so we can guarantee no lifetime issues. - private SafeEvpPKeyCtxHandle CreateCtx() - { - ThrowIfDisposed(); - return SafeEvpPKeyCtxHandle.CreateFromEvpPkey(_key.Value); - } - protected override bool TrySignHashCore( ReadOnlySpan hash, Span destination, @@ -147,17 +141,19 @@ protected override bool TrySignHashCore( return false; } - using (SafeEvpPKeyCtxHandle ctx = CreateCtx()) + // We need to duplicate key handle in case it's being used by multiple threads and one of them disposes it + using (SafeEvpPKeyHandle key = _key.Value.DuplicateHandle()) + using (SafeEvpPKeyCtxHandle ctx = Interop.Crypto.EvpPKeyCtxCreate(key)) { - ctx.ConfigureForECDSASign(); + Interop.Crypto.EvpPKeyCtxConfigureForECDSASign(ctx); - if (!ctx.TryGetSufficientSignatureSizeInBytesCore(hash, out int sufficientSignatureSizeInBytes)) + if (!Interop.Crypto.TryEvpPKeyCtxSignatureSize(ctx, hash, out int sufficientSignatureSizeInBytes)) { throw Interop.Crypto.CreateOpenSslCryptographicException(); } Span derSignatureDestination = sufficientSignatureSizeInBytes <= SignatureStackBufSize ? stackalloc byte[sufficientSignatureSizeInBytes] : new byte[sufficientSignatureSizeInBytes]; - if (!ctx.TrySignHashCore(hash, derSignatureDestination, out int derSignatureBytesWritten)) + if (!Interop.Crypto.TryEvpPKeyCtxSignHash(ctx, hash, derSignatureDestination, out int derSignatureBytesWritten)) { // this is unrelated to sufficient size reason throw Interop.Crypto.CreateOpenSslCryptographicException(); @@ -172,13 +168,15 @@ protected override bool TrySignHashCore( } else if (signatureFormat == DSASignatureFormat.Rfc3279DerSequence) { - using (SafeEvpPKeyCtxHandle ctx = CreateCtx()) + // We need to duplicate key handle in case it's being used by multiple threads and one of them disposes it + using (SafeEvpPKeyHandle key = _key.Value.DuplicateHandle()) + using (SafeEvpPKeyCtxHandle ctx = Interop.Crypto.EvpPKeyCtxCreate(key)) { - ctx.ConfigureForECDSASign(); + Interop.Crypto.EvpPKeyCtxConfigureForECDSASign(ctx); // We could theoretically pass this through but we need to distinguish between "not enough space" and "failed" // We could check for presence of private key but that won't work when it's an external key. - if (!ctx.TryGetSufficientSignatureSizeInBytesCore(hash, out int sufficientSignatureSizeInBytes)) + if (!Interop.Crypto.TryEvpPKeyCtxSignatureSize(ctx, hash, out int sufficientSignatureSizeInBytes)) { throw Interop.Crypto.CreateOpenSslCryptographicException(); } @@ -186,7 +184,7 @@ protected override bool TrySignHashCore( if (destination.Length >= sufficientSignatureSizeInBytes) { // The only reason this could fail won't be related to buffer size - if (!ctx.TrySignHashCore(hash, destination, out bytesWritten)) + if (!Interop.Crypto.TryEvpPKeyCtxSignHash(ctx, hash, destination, out bytesWritten)) { throw Interop.Crypto.CreateOpenSslCryptographicException(); } @@ -197,7 +195,7 @@ protected override bool TrySignHashCore( // Since sufficientSignatureSizeInBytes can be more than what's actually needed // we need temporary buffer of sufficient size and see if operation can succeed with that Span derSignatureDestination = sufficientSignatureSizeInBytes <= SignatureStackBufSize ? stackalloc byte[sufficientSignatureSizeInBytes] : new byte[sufficientSignatureSizeInBytes]; - if (!ctx.TrySignHashCore(hash, destination, out int bytesWrittenToTemporaryBuffer)) + if (!Interop.Crypto.TryEvpPKeyCtxSignHash(ctx, hash, derSignatureDestination, out int bytesWrittenToTemporaryBuffer)) { throw Interop.Crypto.CreateOpenSslCryptographicException(); } @@ -208,7 +206,7 @@ protected override bool TrySignHashCore( return false; } - derSignatureDestination.CopyTo(destination); + derSignatureDestination.Slice(0, bytesWrittenToTemporaryBuffer).CopyTo(destination); bytesWritten = bytesWrittenToTemporaryBuffer; return true; } @@ -273,10 +271,12 @@ protected override bool VerifyHashCore( signatureFormat.ToString()); } - using (SafeEvpPKeyCtxHandle ctx = CreateCtx()) + // We need to duplicate key handle in case it's being used by multiple threads and one of them disposes it + using (SafeEvpPKeyHandle key = _key.Value.DuplicateHandle()) + using (SafeEvpPKeyCtxHandle ctx = Interop.Crypto.EvpPKeyCtxCreate(key)) { - ctx.ConfigureForECDSAVerify(); - return ctx.VerifyHashCore(hash, toVerify); + Interop.Crypto.EvpPKeyCtxConfigureForECDSAVerify(ctx); + return Interop.Crypto.EvpPKeyCtxVerifyHash(ctx, hash, toVerify); } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/RSAOpenSsl.cs b/src/libraries/Common/src/System/Security/Cryptography/RSAOpenSsl.cs index 6b0b32d7420ab3..f0eec972432d6b 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/RSAOpenSsl.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/RSAOpenSsl.cs @@ -115,7 +115,9 @@ public override bool TryDecrypt( ValidatePadding(padding); SafeEvpPKeyHandle key = GetKey(); - // For RSA decryption buffer is equal to key size in bytes + // OpenSSL requires that the decryption buffer be at least as large as key size in bytes. + // So if the destination is too small, use a temporary buffer so we can match + // Windows behavior of succeeding as long as the buffer can hold the final output. int keySizeBytes = key.GetKeySizeBytes(); if (destination.Length < keySizeBytes) @@ -730,17 +732,19 @@ public override byte[] SignHash(byte[] hash, HashAlgorithmName hashAlgorithm, RS ArgumentNullException.ThrowIfNull(padding); ThrowIfDisposed(); - using (SafeEvpPKeyCtxHandle ctx = CreateCtx()) + // We need to duplicate key handle in case it's being used by multiple threads and one of them disposes it + using (SafeEvpPKeyHandle key = _key.Value.DuplicateHandle()) + using (SafeEvpPKeyCtxHandle ctx = Interop.Crypto.EvpPKeyCtxCreate(key)) { - ctx.ConfigureForRSASign(hashAlgorithm, padding.Mode); + Interop.Crypto.CryptoNative_ConfigureForRsaSign(ctx, padding.Mode, hashAlgorithm); - if (!ctx.TryGetSufficientSignatureSizeInBytesCore(hash, out int sufficientDerSignatureSize)) + if (!Interop.Crypto.TryEvpPKeyCtxSignatureSize(ctx, hash, out int sufficientDerSignatureSize)) { throw new CryptographicException(); } byte[] signature = new byte[sufficientDerSignatureSize]; - if (!ctx.TrySignHashCore(hash, signature, out int bytesWritten)) + if (!Interop.Crypto.TryEvpPKeyCtxSignHash(ctx, hash, signature, out int bytesWritten)) { throw new CryptographicException(); } @@ -782,14 +786,6 @@ public override bool TrySignHash( out bytesWritten); } - // SafeEvpPKeyCtxHandle doesn't touch ref counts of SafeEvpPKeyHandle so we need to keep it alive during it's lifetime. - // We only use CreateCtx from within single `using` statement so we can guarantee no lifetime issues. - private SafeEvpPKeyCtxHandle CreateCtx() - { - ThrowIfDisposed(); - return SafeEvpPKeyCtxHandle.CreateFromEvpPkey(GetKey()); - } - private bool TrySignHashCore( ReadOnlySpan hash, Span destination, @@ -802,20 +798,22 @@ private bool TrySignHashCore( ValidatePadding(padding); ThrowIfDisposed(); - using (SafeEvpPKeyCtxHandle ctx = CreateCtx()) + // We need to duplicate key handle in case it's being used by multiple threads and one of them disposes it + using (SafeEvpPKeyHandle key = _key.Value.DuplicateHandle()) + using (SafeEvpPKeyCtxHandle ctx = Interop.Crypto.EvpPKeyCtxCreate(key)) { - ctx.ConfigureForRSASign(hashAlgorithm, padding.Mode); + Interop.Crypto.CryptoNative_ConfigureForRsaSign(ctx, padding.Mode, hashAlgorithm); // We could theoretically pass this through but we need to distinguish between "not enough space" and "failed" // We could check for presence of private key but that won't work when it's an external key. - if (!ctx.TryGetSufficientSignatureSizeInBytesCore(hash, out int sufficientSignatureSizeInBytes)) + if (!Interop.Crypto.TryEvpPKeyCtxSignatureSize(ctx, hash, out int sufficientSignatureSizeInBytes)) { throw Interop.Crypto.CreateOpenSslCryptographicException(); } if (destination.Length >= sufficientSignatureSizeInBytes) { - if (!ctx.TrySignHashCore(hash, destination, out bytesWritten)) + if (!Interop.Crypto.TryEvpPKeyCtxSignHash(ctx, hash, destination, out bytesWritten)) { // The only reason this could fail won't be related to buffer size so we throw rather returning false throw Interop.Crypto.CreateOpenSslCryptographicException(); @@ -827,7 +825,7 @@ private bool TrySignHashCore( // Since sufficientSignatureSizeInBytes can be more than what's actually needed // we use temporary buffer of sufficient size and see if operation can succeed with that byte[] signatureDestination = new byte[sufficientSignatureSizeInBytes]; - if (!ctx.TrySignHashCore(hash, signatureDestination, out int bytesWrittenToTemporaryBuffer)) + if (!Interop.Crypto.TryEvpPKeyCtxSignHash(ctx, hash, signatureDestination, out int bytesWrittenToTemporaryBuffer)) { // There is really no reason for this to fail since we already allocated enough throw Interop.Crypto.CreateOpenSslCryptographicException(); @@ -861,11 +859,14 @@ public override bool VerifyHash(ReadOnlySpan hash, ReadOnlySpan sign { ArgumentException.ThrowIfNullOrEmpty(hashAlgorithm.Name, nameof(hashAlgorithm)); ValidatePadding(padding); + ThrowIfDisposed(); - using (SafeEvpPKeyCtxHandle ctx = CreateCtx()) + // We need to duplicate key handle in case it's being used by multiple threads and one of them disposes it + using (SafeEvpPKeyHandle key = _key.Value.DuplicateHandle()) + using (SafeEvpPKeyCtxHandle ctx = Interop.Crypto.EvpPKeyCtxCreate(key)) { - ctx.ConfigureForRSAVerify(hashAlgorithm, padding.Mode); - return ctx.VerifyHashCore(hash, signature); + Interop.Crypto.CryptoNative_ConfigureForRsaVerify(ctx, padding.Mode, hashAlgorithm); + return Interop.Crypto.EvpPKeyCtxVerifyHash(ctx, hash, signature); } } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs index 454e3e66bc528c..bc5daa009f4dfd 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs @@ -31,6 +31,11 @@ public ECDiffieHellmanOpenSsl(SafeEvpPKeyHandle pkeyHandle) ThrowIfNotSupported(); + if (pkeyHandle.GetKeyType() != Interop.Crypto.EvpAlgorithmId.ECC) + { + throw new CryptographicException(SR.Cryptography_OpenInvalidHandle); + } + _key = new Lazy(pkeyHandle.DuplicateHandle()); KeySizeValue = _key.Value.GetKeySizeBits(); } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDsaOpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDsaOpenSsl.cs index c6aa25676e1351..9f653667a7d5c9 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDsaOpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDsaOpenSsl.cs @@ -1,7 +1,6 @@ // 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; using System.Runtime.InteropServices; using System.Runtime.Versioning; using Microsoft.Win32.SafeHandles; @@ -32,6 +31,11 @@ public ECDsaOpenSsl(SafeEvpPKeyHandle pkeyHandle) ThrowIfNotSupported(); + if (pkeyHandle.GetKeyType() != Interop.Crypto.EvpAlgorithmId.ECC) + { + throw new CryptographicException(SR.Cryptography_OpenInvalidHandle); + } + _key = new Lazy(pkeyHandle.DuplicateHandle()); ForceSetKeySize(pkeyHandle.GetKeySizeBits()); } @@ -68,10 +72,10 @@ public ECDsaOpenSsl(IntPtr handle) } /// - /// Obtain a SafeHandle version of an EVP_PKEY* equivalent + /// Obtain a SafeHandle version of an EVP_PKEY* which wraps an EC_KEY* equivalent /// to the current key for this instance. /// - /// A SafeHandle for the EVP_PKEY key in OpenSSL + /// A SafeHandle for the EC_KEY key in OpenSSL public SafeEvpPKeyHandle DuplicateKeyHandle() { ThrowIfDisposed(); diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSAOpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSAOpenSsl.cs index c7e2cf3beb8af0..67bc07e1cfa44f 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSAOpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSAOpenSsl.cs @@ -74,6 +74,11 @@ public RSAOpenSsl(SafeEvpPKeyHandle pkeyHandle) ThrowIfNotSupported(); + if (pkeyHandle.GetKeyType() != Interop.Crypto.EvpAlgorithmId.RSA) + { + throw new CryptographicException(SR.Cryptography_OpenInvalidHandle); + } + SafeEvpPKeyHandle newKey = pkeyHandle.DuplicateHandle(); SetKey(newKey); } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SafeEvpPKeyHandle.OpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SafeEvpPKeyHandle.OpenSsl.cs index 776251b0149870..cdf0fa30ebb32b 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SafeEvpPKeyHandle.OpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SafeEvpPKeyHandle.OpenSsl.cs @@ -13,7 +13,7 @@ public sealed partial class SafeEvpPKeyHandle : SafeHandle internal static readonly SafeEvpPKeyHandle InvalidHandle = new SafeEvpPKeyHandle(); /// - /// In some cases like when a key is loaded from a provided, the key may have an associated data + /// In some cases like when a key is loaded from a provider, the key may have an associated data /// we need to keep alive for the lifetime of the key. This field is used to track that data. /// internal IntPtr ExtraHandle { get; private set; } = IntPtr.Zero; diff --git a/src/libraries/System.Security.Cryptography/tests/osslplugins/README.md b/src/libraries/System.Security.Cryptography/tests/osslplugins/README.md index fd7bc9f37a99f5..37f27a7b17ee99 100644 --- a/src/libraries/System.Security.Cryptography/tests/osslplugins/README.md +++ b/src/libraries/System.Security.Cryptography/tests/osslplugins/README.md @@ -1,8 +1,3 @@ -TODO: update names of variables and review -TODO: add section with logging https://github.com/tpm2-software/tpm2-tss/blob/master/doc/logging.md -TODO: export TSS2_LOG=all+NONE - - # Testing instructions for OpenSSL ENGINE Once everything is setup tests related to TPM and our engine can be run using: diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c index bbc5bcca7f1780..8efeb877aaeb29 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c @@ -4,6 +4,7 @@ #include #include "pal_evp_pkey.h" #include "pal_utilities.h" +#include "pal_atomic.h" #ifdef NEED_OPENSSL_3_0 c_static_assert(OSSL_STORE_INFO_PKEY == 4); @@ -11,28 +12,33 @@ c_static_assert(OSSL_STORE_INFO_PUBKEY == 3); struct EvpPKeyExtraHandle_st { - int references; + atomic_int refCount; OSSL_LIB_CTX* libCtx; OSSL_PROVIDER* prov; }; typedef struct EvpPKeyExtraHandle_st EvpPKeyExtraHandle; +#pragma clang diagnostic push +// There's no way to specify explicit memory ordering for increment/decrement with C atomics. +#pragma clang diagnostic ignored "-Watomic-implicit-seq-cst" static void CryptoNative_EvpPkeyExtraHandleDestroy(EvpPKeyExtraHandle* handle) { - assert(handle->references >= 1); - assert(handle->prov != NULL); - assert(handle->libCtx != NULL); + int count = --handle->refCount; + assert(count >= 0); - handle->references--; - - if (handle->references == 0) + if (count == 0) { + assert(handle->prov != NULL); + assert(handle->libCtx != NULL); + OSSL_PROVIDER_unload(handle->prov); OSSL_LIB_CTX_free(handle->libCtx); free(handle); } } +#pragma clang diagnostic pop + #endif EVP_PKEY* CryptoNative_EvpPkeyCreate(void) @@ -70,6 +76,9 @@ int32_t CryptoNative_EvpPKeyBits(EVP_PKEY* pkey) return EVP_PKEY_get_bits(pkey); } +#pragma clang diagnostic push +// There's no way to specify explicit memory ordering for increment/decrement with C atomics. +#pragma clang diagnostic ignored "-Watomic-implicit-seq-cst" int32_t CryptoNative_UpRefEvpPkey(EVP_PKEY* pkey, void* extraHandle) { if (!pkey) @@ -82,7 +91,7 @@ int32_t CryptoNative_UpRefEvpPkey(EVP_PKEY* pkey, void* extraHandle) if (extra != NULL) { - extra->references++; + extra->refCount++; } #else (void)extraHandle; @@ -92,6 +101,7 @@ int32_t CryptoNative_UpRefEvpPkey(EVP_PKEY* pkey, void* extraHandle) // No error queue impact. return EVP_PKEY_up_ref(pkey); } +#pragma clang diagnostic pop int32_t CryptoNative_EvpPKeyType(EVP_PKEY* key) { @@ -712,7 +722,7 @@ EVP_PKEY* CryptoNative_LoadKeyFromProvider(const char* providerName, const char* EvpPKeyExtraHandle* extra = (EvpPKeyExtraHandle*)malloc(sizeof(EvpPKeyExtraHandle)); extra->prov = prov; extra->libCtx = libCtx; - extra->references = 1; + atomic_init(&extra->refCount, 1); *extraHandle = extra; } From 95dacb4e73bbef7952a7d0cfc2ba03ec89919eb1 Mon Sep 17 00:00:00 2001 From: Krzysztof Wicher Date: Wed, 17 Jul 2024 16:10:57 +0200 Subject: [PATCH 08/17] ifndef some defines in compat layer, remove CryptoNative_EvpPkeyExtraHandleDestroy --- .../Interop.EvpPkey.cs | 3 --- .../System.Security.Cryptography.Native/osslcompat_30.h | 8 ++++++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs index 99922f9d4f884c..afb7ebd0e88338 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs @@ -282,9 +282,6 @@ internal static SafeEvpPKeyHandle LoadKeyFromProvider( return new SafeEvpPKeyHandle(evpPKeyHandle, extraHandle: extraHandle); } - [LibraryImport(Libraries.CryptoNative, StringMarshalling = StringMarshalling.Utf8)] - internal static partial void CryptoNative_EvpPkeyExtraHandleDestroy(IntPtr extraHandle); - internal enum EvpAlgorithmId { Unknown = 0, diff --git a/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h b/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h index a8a09811e6a504..b0574d1a7705bf 100644 --- a/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h +++ b/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h @@ -20,9 +20,17 @@ #define OSSL_STORE_INFO_PKEY 4 #define OSSL_STORE_INFO_PUBKEY 3 +#ifndef EVP_PKEY_RSA_PSS #define EVP_PKEY_RSA_PSS 912 +#endif + +#ifndef EVP_PKEY_ED448 #define EVP_PKEY_ED448 1088 +#endif + +#ifndef EVP_PKEY_ED25519 #define EVP_PKEY_ED25519 1087 +#endif typedef struct ossl_lib_ctx_st OSSL_LIB_CTX; typedef struct ossl_param_st OSSL_PARAM; From 05c74061644d46f1856656c45e2387184ace617e Mon Sep 17 00:00:00 2001 From: Krzysztof Wicher Date: Thu, 18 Jul 2024 16:38:32 +0200 Subject: [PATCH 09/17] change style to match old RsaSignHash --- .../Interop.EvpPkey.EcDsa.cs | 81 ++++++++++ .../Interop.EvpPkey.Ecdh.cs | 127 +++------------ .../Interop.EvpPkey.Rsa.cs | 77 +++++++-- .../SafeHandles/SafeEvpPkeyCtxHandle.Unix.cs | 31 ---- .../ECDiffieHellmanOpenSsl.Derive.cs | 67 +++----- .../Cryptography/ECDiffieHellmanOpenSsl.cs | 10 +- .../Security/Cryptography/ECDsaOpenSsl.cs | 152 ++++++------------ .../System/Security/Cryptography/ECOpenSsl.cs | 27 ++++ .../Security/Cryptography/RSAOpenSsl.cs | 132 ++++----------- .../src/System.Security.Cryptography.csproj | 8 +- .../Cryptography/SafeEvpPKeyHandle.OpenSsl.cs | 32 ---- .../CMakeLists.txt | 1 + .../entrypoints.c | 14 +- .../pal_evp_pkey.c | 66 +------- .../pal_evp_pkey.h | 35 +--- .../pal_evp_pkey_ecdh.c | 54 +++---- .../pal_evp_pkey_ecdh.h | 6 +- .../pal_evp_pkey_ecdsa.c | 77 +++++++++ .../pal_evp_pkey_ecdsa.h | 32 ++++ .../pal_evp_pkey_rsa.c | 95 +++++++++-- .../pal_evp_pkey_rsa.h | 30 +++- 21 files changed, 546 insertions(+), 608 deletions(-) create mode 100644 src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.EcDsa.cs delete mode 100644 src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpPkeyCtxHandle.Unix.cs create mode 100644 src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdsa.c create mode 100644 src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdsa.h diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.EcDsa.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.EcDsa.cs new file mode 100644 index 00000000000000..2fbbca528530b6 --- /dev/null +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.EcDsa.cs @@ -0,0 +1,81 @@ +// 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.Diagnostics; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + internal static partial class Crypto + { + [LibraryImport(Libraries.CryptoNative)] + private static partial int CryptoNative_EcDsaSignHash( + SafeEvpPKeyHandle pkey, + IntPtr extraHandle, + ref byte hash, + int hashLength, + ref byte destination, + int destinationLength); + + internal static int EcDsaSignHash( + SafeEvpPKeyHandle pkey, + ReadOnlySpan hash, + Span destination) + { + int written = CryptoNative_EcDsaSignHash( + pkey, + pkey.ExtraHandle, + ref MemoryMarshal.GetReference(hash), + hash.Length, + ref MemoryMarshal.GetReference(destination), + destination.Length); + + if (written < 0) + { + Debug.Assert(written == -1); + throw CreateOpenSslCryptographicException(); + } + + return written; + } + + [LibraryImport(Libraries.CryptoNative)] + private static partial int CryptoNative_EcDsaVerifyHash( + SafeEvpPKeyHandle pkey, + IntPtr extraHandle, + ref byte hash, + int hashLength, + ref byte signature, + int signatureLength); + + internal static bool EcDsaVerifyHash( + SafeEvpPKeyHandle pkey, + ReadOnlySpan hash, + ReadOnlySpan signature) + { + int ret = CryptoNative_EcDsaVerifyHash( + pkey, + pkey.ExtraHandle, + ref MemoryMarshal.GetReference(hash), + hash.Length, + ref MemoryMarshal.GetReference(signature), + signature.Length); + + if (ret == 1) + { + return true; + } + + if (ret == 0) + { + return false; + } + + Debug.Assert(ret == -1); + throw CreateOpenSslCryptographicException(); + } + } +} diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Ecdh.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Ecdh.cs index 1abcdaf65f8343..e85795bde5126e 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Ecdh.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Ecdh.cs @@ -11,124 +11,35 @@ internal static partial class Interop { internal static partial class Crypto { - [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyCtxCreateFromPKey")] - private static partial SafeEvpPKeyCtxHandle EvpPKeyCtxCreate(SafeEvpPKeyHandle pkey, IntPtr extraHandle); - - internal static SafeEvpPKeyCtxHandle EvpPKeyCtxCreate(SafeEvpPKeyHandle pkey) - => EvpPKeyCtxCreate(pkey, pkey.ExtraHandle); - - [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyCtxCreate")] - private static partial SafeEvpPKeyCtxHandle EvpPKeyCtxCreate(SafeEvpPKeyHandle pkey, IntPtr extraHandle, SafeEvpPKeyHandle peerkey, out uint secretLength); - - internal static SafeEvpPKeyCtxHandle EvpPKeyCtxCreate(SafeEvpPKeyHandle pkey, SafeEvpPKeyHandle peerkey, out uint secretLength) - => EvpPKeyCtxCreate(pkey, pkey.ExtraHandle, peerkey, out secretLength); - - [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyCtxConfigureForECDSASign")] - private static partial int EvpPKeyCtxConfigureForECDSASignCore(SafeEvpPKeyCtxHandle ctx); - - internal static void EvpPKeyCtxConfigureForECDSASign(SafeEvpPKeyCtxHandle ctx) - { - Debug.Assert(ctx != null); - Debug.Assert(!ctx.IsInvalid); - - if (EvpPKeyCtxConfigureForECDSASignCore(ctx) != 1) - { - throw CreateOpenSslCryptographicException(); - } - } - - [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyCtxConfigureForECDSAVerify")] - private static partial int EvpPKeyCtxConfigureForECDSAVerifyCore(SafeEvpPKeyCtxHandle ctx); - - internal static void EvpPKeyCtxConfigureForECDSAVerify(SafeEvpPKeyCtxHandle ctx) - { - Debug.Assert(ctx != null); - Debug.Assert(!ctx.IsInvalid); - - if (EvpPKeyCtxConfigureForECDSAVerifyCore(ctx) != 1) - { - throw CreateOpenSslCryptographicException(); - } - } - - [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyCtxSignHash")] - private static unsafe partial int EvpPKeyCtxSignHash(SafeEvpPKeyCtxHandle ctx, byte* hash, int hashLen, byte* destination, ref int destinationLen); - - internal static unsafe bool TryEvpPKeyCtxSignHash(SafeEvpPKeyCtxHandle ctx, ReadOnlySpan hash, Span destination, out int bytesWritten) - { - Debug.Assert(ctx != null); - Debug.Assert(!ctx.IsInvalid); - - if (hash.Length == 0 || destination.Length == 0) - { - bytesWritten = 0; - return false; - } - - bytesWritten = destination.Length; - ref byte hashRef = ref MemoryMarshal.GetReference(hash); - ref byte destRef = ref MemoryMarshal.GetReference(destination); - fixed (byte* hashPtr = &hashRef) - fixed (byte* destPtr = &destRef) - { - return EvpPKeyCtxSignHash(ctx, hashPtr, hash.Length, destPtr, ref bytesWritten) == 1; - } - } - - internal static unsafe bool TryEvpPKeyCtxSignatureSize(SafeEvpPKeyCtxHandle ctx, ReadOnlySpan hash, out int bytesWritten) - { - Debug.Assert(ctx != null); - Debug.Assert(!ctx.IsInvalid); - - bytesWritten = 0; - - if (hash.Length == 0) - { - return false; - } - - ref byte hashRef = ref MemoryMarshal.GetReference(hash); - fixed (byte* hashPtr = &hashRef) - { - byte* destPtr = null; - return EvpPKeyCtxSignHash(ctx, hashPtr, hash.Length, destPtr, ref bytesWritten) == 1; - } - } - - [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyCtxVerifyHash")] - private static partial int EvpPKeyCtxVerifyHash(SafeEvpPKeyCtxHandle ctx, ref byte hash, int hashLen, ref byte signature, int signatureLen); - - internal static bool EvpPKeyCtxVerifyHash(SafeEvpPKeyCtxHandle ctx, ReadOnlySpan hash, ReadOnlySpan signature) - { - Debug.Assert(ctx != null); - Debug.Assert(!ctx.IsInvalid); - - return EvpPKeyCtxVerifyHash(ctx, ref MemoryMarshal.GetReference(hash), hash.Length, ref MemoryMarshal.GetReference(signature), signature.Length) == 1; - } - [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyDeriveSecretAgreement")] private static partial int EvpPKeyDeriveSecretAgreement( + SafeEvpPKeyHandle pkey, + IntPtr extraHandle, + SafeEvpPKeyHandle peerKey, ref byte secret, - uint secretLength, - SafeEvpPKeyCtxHandle ctx); + uint secretLength); - [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyCtxDestroy")] - internal static partial void EvpPKeyCtxDestroy(IntPtr ctx); - - internal static void EvpPKeyDeriveSecretAgreement(SafeEvpPKeyCtxHandle ctx, Span destination) + internal static int EvpPKeyDeriveSecretAgreement(SafeEvpPKeyHandle pkey, SafeEvpPKeyHandle peerKey, Span destination) { - Debug.Assert(ctx != null); - Debug.Assert(!ctx.IsInvalid); - - int ret = EvpPKeyDeriveSecretAgreement( + Debug.Assert(pkey != null); + Debug.Assert(!pkey.IsInvalid); + Debug.Assert(peerKey != null); + Debug.Assert(!peerKey.IsInvalid); + + int written = EvpPKeyDeriveSecretAgreement( + pkey, + pkey.ExtraHandle, + peerKey, ref MemoryMarshal.GetReference(destination), - (uint)destination.Length, - ctx); + (uint)destination.Length); - if (ret != 1) + if (written <= 0) { + Debug.Assert(written == 0); throw CreateOpenSslCryptographicException(); } + + return written; } } } 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 1fec6d14bfca61..e27e75ac851304 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 @@ -120,15 +120,22 @@ ref MemoryMarshal.GetReference(destination), } [LibraryImport(Libraries.CryptoNative)] - private static partial int CryptoNative_EvpPKeyCtxConfigureForRsaSign( - SafeEvpPKeyCtxHandle ctx, + private static partial int CryptoNative_RsaSignHash( + SafeEvpPKeyHandle pkey, + IntPtr extraHandle, RSASignaturePaddingMode paddingMode, - IntPtr digestAlgorithm); + IntPtr digestAlgorithm, + ref byte hash, + int hashLength, + ref byte destination, + int destinationLength); - internal static void CryptoNative_ConfigureForRsaSign( - SafeEvpPKeyCtxHandle ctx, + internal static int RsaSignHash( + SafeEvpPKeyHandle pkey, RSASignaturePaddingMode paddingMode, - HashAlgorithmName digestAlgorithm) + HashAlgorithmName digestAlgorithm, + ReadOnlySpan hash, + Span destination) { if (digestAlgorithm.Name == null) { @@ -136,24 +143,43 @@ internal static void CryptoNative_ConfigureForRsaSign( } IntPtr digestAlgorithmPtr = Interop.Crypto.HashAlgorithmToEvp(digestAlgorithm.Name); - int ret = CryptoNative_EvpPKeyCtxConfigureForRsaSign(ctx, paddingMode, digestAlgorithmPtr); - if (ret != 1) + int written = CryptoNative_RsaSignHash( + pkey, + pkey.ExtraHandle, + paddingMode, + digestAlgorithmPtr, + ref MemoryMarshal.GetReference(hash), + hash.Length, + ref MemoryMarshal.GetReference(destination), + destination.Length); + + if (written < 0) { + Debug.Assert(written == -1); throw CreateOpenSslCryptographicException(); } + + return written; } [LibraryImport(Libraries.CryptoNative)] - private static partial int CryptoNative_EvpPKeyCtxConfigureForRsaVerify( - SafeEvpPKeyCtxHandle ctx, + private static partial int CryptoNative_RsaVerifyHash( + SafeEvpPKeyHandle pkey, + IntPtr extraHandle, RSASignaturePaddingMode paddingMode, - IntPtr digestAlgorithm); + IntPtr digestAlgorithm, + ref byte hash, + int hashLength, + ref byte signature, + int signatureLength); - internal static void CryptoNative_ConfigureForRsaVerify( - SafeEvpPKeyCtxHandle ctx, + internal static bool RsaVerifyHash( + SafeEvpPKeyHandle pkey, RSASignaturePaddingMode paddingMode, - HashAlgorithmName digestAlgorithm) + HashAlgorithmName digestAlgorithm, + ReadOnlySpan hash, + ReadOnlySpan signature) { if (digestAlgorithm.Name == null) { @@ -161,12 +187,29 @@ internal static void CryptoNative_ConfigureForRsaVerify( } IntPtr digestAlgorithmPtr = Interop.Crypto.HashAlgorithmToEvp(digestAlgorithm.Name); - int ret = CryptoNative_EvpPKeyCtxConfigureForRsaVerify(ctx, paddingMode, digestAlgorithmPtr); - if (ret != 1) + int ret = CryptoNative_RsaVerifyHash( + pkey, + pkey.ExtraHandle, + paddingMode, + digestAlgorithmPtr, + ref MemoryMarshal.GetReference(hash), + hash.Length, + ref MemoryMarshal.GetReference(signature), + signature.Length); + + if (ret == 1) { - throw CreateOpenSslCryptographicException(); + return true; } + + if (ret == 0) + { + return false; + } + + Debug.Assert(ret == -1); + throw CreateOpenSslCryptographicException(); } } } diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpPkeyCtxHandle.Unix.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpPkeyCtxHandle.Unix.cs deleted file mode 100644 index 0cd026c3b233a6..00000000000000 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpPkeyCtxHandle.Unix.cs +++ /dev/null @@ -1,31 +0,0 @@ -// 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; - -namespace Microsoft.Win32.SafeHandles -{ - internal sealed class SafeEvpPKeyCtxHandle : SafeHandle - { - public SafeEvpPKeyCtxHandle() - : base(IntPtr.Zero, ownsHandle: true) - { - } - - public SafeEvpPKeyCtxHandle(IntPtr handle, bool ownsHandle) - : base(handle, ownsHandle) - { - } - - protected override bool ReleaseHandle() - { - Interop.Crypto.EvpPKeyCtxDestroy(handle); - SetHandle(IntPtr.Zero); - return true; - } - - public override bool IsInvalid => handle == IntPtr.Zero; - } -} diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs index 04f3dd2037787b..5def04d316c61e 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs @@ -113,10 +113,14 @@ public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPa bool otherIsNamed = otherKey.HasCurveName; // We need to always duplicate handle in case this operation is done by multiple threads and one of them disposes the handle - SafeEvpPKeyHandle? ourKey = null; + SafeEvpPKeyHandle? ourKey = _key.Value; + bool disposeOurKey = false; + SafeEvpPKeyHandle? theirKey = null; - byte[]? rented = null; - int secretLength = 0; + + // secp521r1 which is the biggest common case maxes out at 66 bytes so 128 should always be enough. + const int StackAllocMax = 128; + Span secret = stackalloc byte[StackAllocMax]; try { @@ -127,13 +131,10 @@ public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPa if (otherIsNamed == thisIsNamed) { - ourKey = _key.Value.DuplicateHandle(); theirKey = otherKey.DuplicateKeyHandle(); } else if (otherIsNamed) { - ourKey = _key.Value.DuplicateHandle(); - using (ECOpenSsl tmp = new ECOpenSsl(otherKey.ExportExplicitParameters())) { theirKey = tmp.CreateKeyHandle(); @@ -149,6 +150,7 @@ public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPa using (ECOpenSsl tmp = new ECOpenSsl(ExportExplicitParameters(true))) { ourKey = tmp.CreateKeyHandle(); + disposeOurKey = true; } } catch (CryptographicException) @@ -160,46 +162,23 @@ public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPa theirKey = otherKey.DuplicateKeyHandle(); } - using (SafeEvpPKeyCtxHandle ctx = Interop.Crypto.EvpPKeyCtxCreate(ourKey, theirKey, out uint secretLengthU)) - { - if (ctx == null || ctx.IsInvalid || secretLengthU == 0 || secretLengthU > int.MaxValue) - { - throw Interop.Crypto.CreateOpenSslCryptographicException(); - } - - secretLength = (int)secretLengthU; + int written = Interop.Crypto.EvpPKeyDeriveSecretAgreement(ourKey, theirKey, secret); + secret = secret.Slice(0, written); - // Indicate that secret can hold stackallocs from nested scopes - scoped Span secret; - - // Arbitrary limit. But it covers secp521r1, which is the biggest common case. - const int StackAllocMax = 66; - - if (secretLength > StackAllocMax) - { - rented = CryptoPool.Rent(secretLength); - secret = new Span(rented, 0, secretLength); - } - else - { - secret = stackalloc byte[secretLength]; - } - - Interop.Crypto.EvpPKeyDeriveSecretAgreement(ctx, secret); - - if (hasher == null) - { - return secret.ToArray(); - } - else - { - hasher.AppendData(secret); - return null; - } + if (hasher == null) + { + return secret.ToArray(); + } + else + { + hasher.AppendData(secret); + return null; } } finally { + CryptographicOperations.ZeroMemory(secret); + theirKey?.Dispose(); if (disposeOtherKey) @@ -207,11 +186,9 @@ public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPa otherKey.Dispose(); } - ourKey?.Dispose(); - - if (rented != null) + if (disposeOurKey) { - CryptoPool.Return(rented, secretLength); + ourKey.Dispose(); } } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs index f83258ca30eaea..9cec339025b667 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs @@ -19,7 +19,7 @@ public sealed partial class ECDiffieHellmanOpenSsl : ECDiffieHellman public ECDiffieHellmanOpenSsl(ECCurve curve) { ThrowIfNotSupported(); - _key = new Lazy(SafeEvpPKeyHandle.GenerateECKey(curve, out int keySize)); + _key = new Lazy(ECOpenSsl.GenerateECKey(curve, out int keySize)); KeySizeValue = keySize; } @@ -42,7 +42,7 @@ public ECDiffieHellmanOpenSsl(int keySize) { ThrowIfNotSupported(); base.KeySize = keySize; - _key = new Lazy(() => SafeEvpPKeyHandle.GenerateECKey(keySize)); + _key = new Lazy(() => ECOpenSsl.GenerateECKey(keySize)); } public override KeySizes[] LegalKeySizes => s_defaultKeySizes.CloneKeySizesArray(); @@ -76,7 +76,7 @@ public override int KeySize ThrowIfDisposed(); FreeKey(); - _key = new Lazy(SafeEvpPKeyHandle.GenerateECKey(value)); + _key = new Lazy(ECOpenSsl.GenerateECKey(value)); } } @@ -85,7 +85,7 @@ public override void GenerateKey(ECCurve curve) ThrowIfDisposed(); FreeKey(); - _key = new Lazy(SafeEvpPKeyHandle.GenerateECKey(curve, out int keySizeValue)); + _key = new Lazy(ECOpenSsl.GenerateECKey(curve, out int keySizeValue)); KeySizeValue = keySizeValue; } @@ -104,7 +104,7 @@ public override void ImportParameters(ECParameters parameters) { ThrowIfDisposed(); FreeKey(); - _key = new Lazy(SafeEvpPKeyHandle.GenerateECKey(parameters, out int keySize)); + _key = new Lazy(ECOpenSsl.GenerateECKey(parameters, out int keySize)); KeySizeValue = keySize; } diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDsaOpenSsl.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDsaOpenSsl.cs index 2800c53e482200..f87780d95e5d99 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDsaOpenSsl.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDsaOpenSsl.cs @@ -30,7 +30,7 @@ public sealed partial class ECDsaOpenSsl : ECDsa, IRuntimeAlgorithm public ECDsaOpenSsl(ECCurve curve) { ThrowIfNotSupported(); - _key = new Lazy(SafeEvpPKeyHandle.GenerateECKey(curve, out int keySize)); + _key = new Lazy(ECOpenSsl.GenerateECKey(curve, out int keySize)); ForceSetKeySize(keySize); } @@ -84,34 +84,14 @@ public override byte[] SignHash(byte[] hash) ArgumentNullException.ThrowIfNull(hash); ThrowIfDisposed(); - // We need to duplicate key handle in case it's being used by multiple threads and one of them disposes it - using (SafeEvpPKeyHandle key = _key.Value.DuplicateHandle()) - using (SafeEvpPKeyCtxHandle ctx = Interop.Crypto.EvpPKeyCtxCreate(key)) - { - Interop.Crypto.EvpPKeyCtxConfigureForECDSASign(ctx); + SafeEvpPKeyHandle key = GetKey(); - if (!Interop.Crypto.TryEvpPKeyCtxSignatureSize(ctx, hash, out int sufficientDerSignatureSize)) - { - throw new CryptographicException(); - } - - Span derSignature = sufficientDerSignatureSize <= SignatureStackBufSize ? stackalloc byte[sufficientDerSignatureSize] : new byte[sufficientDerSignatureSize]; - if (!Interop.Crypto.TryEvpPKeyCtxSignHash(ctx, hash, derSignature, out int bytesWritten)) - { - throw new CryptographicException(); - } - - if (bytesWritten > derSignature.Length) - { - Debug.Fail("TrySignHashCore wrote more bytes than it claimed it would write"); - throw new CryptographicException(); - } - - derSignature = derSignature.Slice(0, bytesWritten); + Span derSignature = stackalloc byte[SignatureStackBufSize]; + int written = Interop.Crypto.EcDsaSignHash(key, hash, derSignature); + derSignature = derSignature.Slice(0, written); - byte[] converted = AsymmetricAlgorithmHelpers.ConvertDerToIeee1363(derSignature, KeySize); - return converted; - } + byte[] converted = AsymmetricAlgorithmHelpers.ConvertDerToIeee1363(derSignature, KeySize); + return converted; } public override bool TrySignHash(ReadOnlySpan hash, Span destination, out int bytesWritten) @@ -133,83 +113,38 @@ protected override bool TrySignHashCore( if (signatureFormat == DSASignatureFormat.IeeeP1363FixedFieldConcatenation) { - int encodedSize = 2 * AsymmetricAlgorithmHelpers.BitsToBytes(KeySize); + int encodedSize = GetMaxSignatureSize(DSASignatureFormat.IeeeP1363FixedFieldConcatenation); + // IeeeP1363FixedFieldConcatenation has a constant signature size therefore we can shortcut here if (destination.Length < encodedSize) { bytesWritten = 0; return false; } - // We need to duplicate key handle in case it's being used by multiple threads and one of them disposes it - using (SafeEvpPKeyHandle key = _key.Value.DuplicateHandle()) - using (SafeEvpPKeyCtxHandle ctx = Interop.Crypto.EvpPKeyCtxCreate(key)) - { - Interop.Crypto.EvpPKeyCtxConfigureForECDSASign(ctx); - - if (!Interop.Crypto.TryEvpPKeyCtxSignatureSize(ctx, hash, out int sufficientSignatureSizeInBytes)) - { - throw Interop.Crypto.CreateOpenSslCryptographicException(); - } - - Span derSignatureDestination = sufficientSignatureSizeInBytes <= SignatureStackBufSize ? stackalloc byte[sufficientSignatureSizeInBytes] : new byte[sufficientSignatureSizeInBytes]; - if (!Interop.Crypto.TryEvpPKeyCtxSignHash(ctx, hash, derSignatureDestination, out int derSignatureBytesWritten)) - { - // this is unrelated to sufficient size reason - throw Interop.Crypto.CreateOpenSslCryptographicException(); - } - - derSignatureDestination = derSignatureDestination.Slice(0, derSignatureBytesWritten); - bytesWritten = AsymmetricAlgorithmHelpers.ConvertDerToIeee1363(derSignatureDestination, KeySize, destination); - Debug.Assert(bytesWritten == encodedSize); - } + SafeEvpPKeyHandle key = GetKey(); + + Span derSignature = stackalloc byte[SignatureStackBufSize]; + int written = Interop.Crypto.EcDsaSignHash(key, hash, derSignature); + derSignature = derSignature.Slice(0, written); + + bytesWritten = AsymmetricAlgorithmHelpers.ConvertDerToIeee1363(derSignature, KeySize, destination); + Debug.Assert(bytesWritten == encodedSize); return true; } else if (signatureFormat == DSASignatureFormat.Rfc3279DerSequence) { - // We need to duplicate key handle in case it's being used by multiple threads and one of them disposes it - using (SafeEvpPKeyHandle key = _key.Value.DuplicateHandle()) - using (SafeEvpPKeyCtxHandle ctx = Interop.Crypto.EvpPKeyCtxCreate(key)) - { - Interop.Crypto.EvpPKeyCtxConfigureForECDSASign(ctx); - - // We could theoretically pass this through but we need to distinguish between "not enough space" and "failed" - // We could check for presence of private key but that won't work when it's an external key. - if (!Interop.Crypto.TryEvpPKeyCtxSignatureSize(ctx, hash, out int sufficientSignatureSizeInBytes)) - { - throw Interop.Crypto.CreateOpenSslCryptographicException(); - } - - if (destination.Length >= sufficientSignatureSizeInBytes) - { - // The only reason this could fail won't be related to buffer size - if (!Interop.Crypto.TryEvpPKeyCtxSignHash(ctx, hash, destination, out bytesWritten)) - { - throw Interop.Crypto.CreateOpenSslCryptographicException(); - } - - return true; - } - - // Since sufficientSignatureSizeInBytes can be more than what's actually needed - // we need temporary buffer of sufficient size and see if operation can succeed with that - Span derSignatureDestination = sufficientSignatureSizeInBytes <= SignatureStackBufSize ? stackalloc byte[sufficientSignatureSizeInBytes] : new byte[sufficientSignatureSizeInBytes]; - if (!Interop.Crypto.TryEvpPKeyCtxSignHash(ctx, hash, derSignatureDestination, out int bytesWrittenToTemporaryBuffer)) - { - throw Interop.Crypto.CreateOpenSslCryptographicException(); - } - - if (bytesWrittenToTemporaryBuffer > destination.Length) - { - bytesWritten = 0; - return false; - } - - derSignatureDestination.Slice(0, bytesWrittenToTemporaryBuffer).CopyTo(destination); - bytesWritten = bytesWrittenToTemporaryBuffer; - return true; - } + SafeEvpPKeyHandle key = GetKey(); + + // We need to distinguish between the case where the destination buffer is too small and the case where something else went wrong + // Since Rfc3279DerSequence signature size is not constant (DER is not constant size) we will use temporary buffer with max size. + // If that succeeds we can copy to destination buffer if it's large enough. + Span tmpDerSignature = stackalloc byte[SignatureStackBufSize]; + bytesWritten = Interop.Crypto.EcDsaSignHash(key, hash, tmpDerSignature); + tmpDerSignature = tmpDerSignature.Slice(0, bytesWritten); + + return Helpers.TryCopyToDestination(tmpDerSignature, destination, out bytesWritten); } else { @@ -271,13 +206,12 @@ protected override bool VerifyHashCore( signatureFormat.ToString()); } - // We need to duplicate key handle in case it's being used by multiple threads and one of them disposes it - using (SafeEvpPKeyHandle key = _key.Value.DuplicateHandle()) - using (SafeEvpPKeyCtxHandle ctx = Interop.Crypto.EvpPKeyCtxCreate(key)) - { - Interop.Crypto.EvpPKeyCtxConfigureForECDSAVerify(ctx); - return Interop.Crypto.EvpPKeyCtxVerifyHash(ctx, hash, toVerify); - } + SafeEvpPKeyHandle key = GetKey(); + + return Interop.Crypto.EcDsaVerifyHash( + key, + hash, + toVerify); } protected override void Dispose(bool disposing) @@ -317,7 +251,7 @@ public override void GenerateKey(ECCurve curve) ThrowIfDisposed(); FreeKey(); - _key = new Lazy(SafeEvpPKeyHandle.GenerateECKey(curve, out int keySize)); + _key = new Lazy(ECOpenSsl.GenerateECKey(curve, out int keySize)); // Use ForceSet instead of the property setter to ensure that LegalKeySizes doesn't interfere // with the already loaded key. @@ -329,16 +263,30 @@ public override void ImportParameters(ECParameters parameters) ThrowIfDisposed(); FreeKey(); - _key = new Lazy(SafeEvpPKeyHandle.GenerateECKey(parameters, out int keySize)); + _key = new Lazy(ECOpenSsl.GenerateECKey(parameters, out int keySize)); // Use ForceSet instead of the property setter to ensure that LegalKeySizes doesn't interfere // with the already loaded key. ForceSetKeySize(keySize); } + private SafeEvpPKeyHandle GetKey() + { + ThrowIfDisposed(); + + SafeEvpPKeyHandle key = _key.Value; + + if (key == null || key.IsInvalid) + { + throw new CryptographicException(SR.Cryptography_OpenInvalidHandle); + } + + return key; + } + private SafeEvpPKeyHandle GenerateKeyFromSize() { - return SafeEvpPKeyHandle.GenerateECKey(KeySize); + return ECOpenSsl.GenerateECKey(KeySize); } public override ECParameters ExportExplicitParameters(bool includePrivateParameters) diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs b/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs index 0c95d5bf1fb5d8..9f028ac4c90469 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs @@ -114,5 +114,32 @@ private void FreeKey() _key = null!; } } + + internal static SafeEvpPKeyHandle GenerateECKey(int keySize) + { + SafeEvpPKeyHandle ret = GenerateECKeyCore(new ECOpenSsl(keySize), out int createdKeySize); + Debug.Assert(keySize == createdKeySize); + return ret; + } + + internal static SafeEvpPKeyHandle GenerateECKey(ECCurve curve, out int keySize) + { + return GenerateECKeyCore(new ECOpenSsl(curve), out keySize); + } + + internal static SafeEvpPKeyHandle GenerateECKey(ECParameters parameters, out int keySize) + { + return GenerateECKeyCore(new ECOpenSsl(parameters), out keySize); + } + + private static SafeEvpPKeyHandle GenerateECKeyCore(ECOpenSsl ecOpenSsl, out int keySize) + { + using (ECOpenSsl ec = ecOpenSsl) + { + SafeEvpPKeyHandle handle = Interop.Crypto.CreateEvpPkeyFromEcKey(ec.Value); + keySize = ec.KeySize; + return handle; + } + } } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/RSAOpenSsl.cs b/src/libraries/Common/src/System/Security/Cryptography/RSAOpenSsl.cs index f0eec972432d6b..7469affbce28e3 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/RSAOpenSsl.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/RSAOpenSsl.cs @@ -231,9 +231,9 @@ public override byte[] Encrypt(byte[] data, RSAEncryptionPadding padding) public override bool TryEncrypt(ReadOnlySpan data, Span destination, RSAEncryptionPadding padding, out int bytesWritten) { ArgumentNullException.ThrowIfNull(padding); - ValidatePadding(padding); - SafeEvpPKeyHandle? key = GetKey(); + + SafeEvpPKeyHandle key = GetKey(); return TryEncrypt(key, data, destination, padding, out bytesWritten); } @@ -722,7 +722,7 @@ private SafeEvpPKeyHandle GetKey() private SafeEvpPKeyHandle GenerateKey() { - return SafeEvpPKeyHandle.GenerateRSAKey(KeySize); + return Interop.Crypto.RsaGenerateKey(KeySize); } public override byte[] SignHash(byte[] hash, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding) @@ -732,40 +732,19 @@ public override byte[] SignHash(byte[] hash, HashAlgorithmName hashAlgorithm, RS ArgumentNullException.ThrowIfNull(padding); ThrowIfDisposed(); - // We need to duplicate key handle in case it's being used by multiple threads and one of them disposes it - using (SafeEvpPKeyHandle key = _key.Value.DuplicateHandle()) - using (SafeEvpPKeyCtxHandle ctx = Interop.Crypto.EvpPKeyCtxCreate(key)) - { - Interop.Crypto.CryptoNative_ConfigureForRsaSign(ctx, padding.Mode, hashAlgorithm); - - if (!Interop.Crypto.TryEvpPKeyCtxSignatureSize(ctx, hash, out int sufficientDerSignatureSize)) - { - throw new CryptographicException(); - } - - byte[] signature = new byte[sufficientDerSignatureSize]; - if (!Interop.Crypto.TryEvpPKeyCtxSignHash(ctx, hash, signature, out int bytesWritten)) - { - throw new CryptographicException(); - } + SafeEvpPKeyHandle key = GetKey(); + int bytesRequired = key.GetKeySizeBytes(); + byte[] signature = new byte[bytesRequired]; - if (bytesWritten > signature.Length) - { - Debug.Fail("TrySignHashCore wrote more bytes than it claimed it would write"); - throw new CryptographicException(); - } + int written = Interop.Crypto.RsaSignHash(key, padding.Mode, hashAlgorithm, hash, signature); - if (bytesWritten == signature.Length) - { - return signature; - } - else - { - byte[] ret = new byte[bytesWritten]; - new ReadOnlySpan(signature).Slice(0, bytesWritten).CopyTo(ret); - return ret; - } + if (written != signature.Length) + { + Debug.Fail($"RsaSignHash behaved unexpectedly: {nameof(written)}=={written}, {nameof(signature.Length)}=={signature.Length}"); + throw new CryptographicException(); } + + return signature; } public override bool TrySignHash( @@ -777,70 +756,20 @@ public override bool TrySignHash( { ArgumentException.ThrowIfNullOrEmpty(hashAlgorithm.Name, nameof(hashAlgorithm)); ArgumentNullException.ThrowIfNull(padding); - - return TrySignHashCore( - hash, - destination, - hashAlgorithm, - padding, - out bytesWritten); - } - - private bool TrySignHashCore( - ReadOnlySpan hash, - Span destination, - HashAlgorithmName hashAlgorithm, - RSASignaturePadding padding, - out int bytesWritten) - { - Debug.Assert(!string.IsNullOrEmpty(hashAlgorithm.Name)); - Debug.Assert(padding != null); - ValidatePadding(padding); ThrowIfDisposed(); - // We need to duplicate key handle in case it's being used by multiple threads and one of them disposes it - using (SafeEvpPKeyHandle key = _key.Value.DuplicateHandle()) - using (SafeEvpPKeyCtxHandle ctx = Interop.Crypto.EvpPKeyCtxCreate(key)) - { - Interop.Crypto.CryptoNative_ConfigureForRsaSign(ctx, padding.Mode, hashAlgorithm); - - // We could theoretically pass this through but we need to distinguish between "not enough space" and "failed" - // We could check for presence of private key but that won't work when it's an external key. - if (!Interop.Crypto.TryEvpPKeyCtxSignatureSize(ctx, hash, out int sufficientSignatureSizeInBytes)) - { - throw Interop.Crypto.CreateOpenSslCryptographicException(); - } - - if (destination.Length >= sufficientSignatureSizeInBytes) - { - if (!Interop.Crypto.TryEvpPKeyCtxSignHash(ctx, hash, destination, out bytesWritten)) - { - // The only reason this could fail won't be related to buffer size so we throw rather returning false - throw Interop.Crypto.CreateOpenSslCryptographicException(); - } - - return true; - } - - // Since sufficientSignatureSizeInBytes can be more than what's actually needed - // we use temporary buffer of sufficient size and see if operation can succeed with that - byte[] signatureDestination = new byte[sufficientSignatureSizeInBytes]; - if (!Interop.Crypto.TryEvpPKeyCtxSignHash(ctx, hash, signatureDestination, out int bytesWrittenToTemporaryBuffer)) - { - // There is really no reason for this to fail since we already allocated enough - throw Interop.Crypto.CreateOpenSslCryptographicException(); - } - - if (bytesWrittenToTemporaryBuffer > destination.Length) - { - bytesWritten = 0; - return false; - } + SafeEvpPKeyHandle key = GetKey(); + int bytesRequired = key.GetKeySizeBytes(); - signatureDestination.CopyTo(destination); - bytesWritten = bytesWrittenToTemporaryBuffer; - return true; + if (destination.Length < bytesRequired) + { + bytesWritten = 0; + return false; } + + bytesWritten = Interop.Crypto.RsaSignHash(key, padding.Mode, hashAlgorithm, hash, destination); + Debug.Assert(bytesWritten == bytesRequired); + return true; } public override bool VerifyHash( @@ -861,13 +790,14 @@ public override bool VerifyHash(ReadOnlySpan hash, ReadOnlySpan sign ValidatePadding(padding); ThrowIfDisposed(); - // We need to duplicate key handle in case it's being used by multiple threads and one of them disposes it - using (SafeEvpPKeyHandle key = _key.Value.DuplicateHandle()) - using (SafeEvpPKeyCtxHandle ctx = Interop.Crypto.EvpPKeyCtxCreate(key)) - { - Interop.Crypto.CryptoNative_ConfigureForRsaVerify(ctx, padding.Mode, hashAlgorithm); - return Interop.Crypto.EvpPKeyCtxVerifyHash(ctx, hash, signature); - } + SafeEvpPKeyHandle key = GetKey(); + + return Interop.Crypto.RsaVerifyHash( + key, + padding.Mode, + hashAlgorithm, + hash, + signature); } private static ReadOnlyMemory VerifyPkcs8(ReadOnlyMemory pkcs8) 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 574e3b3065e754..adf842fe363e00 100644 --- a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj +++ b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj @@ -749,6 +749,8 @@ Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.EvpPkey.Dsa.cs" /> + - - + 0); - assert(destinationLen != NULL); - assert(destination != NULL || *destinationLen == 0); - - ERR_clear_error(); - size_t written = Int32ToSizeT(*destinationLen); - - if (EVP_PKEY_sign(ctx, destination, &written, hash, Int32ToSizeT(hashLen)) > 0) - { - *destinationLen = SizeTToInt32(written); - return 1; - } - - return 0; -} - -int32_t CryptoNative_EvpPKeyCtxVerifyHash(EVP_PKEY_CTX* ctx, - const uint8_t* hash, - int32_t hashLen, - uint8_t* signature, - int32_t signatureLen) -{ - ERR_clear_error(); - return EVP_PKEY_verify(ctx, signature, Int32ToSizeT(signatureLen), hash, Int32ToSizeT(hashLen)) > 0; -} diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.h b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.h index 8d7714a508ce40..084ae28d77cd43 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.h +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.h @@ -121,37 +121,4 @@ PALEXPORT EVP_PKEY* CryptoNative_LoadKeyFromProvider(const char* providerName, c It's a wrapper for EVP_PKEY_CTX_new_from_pkey and EVP_PKEY_CTX_new which handles extraHandle. */ -PALEXPORT EVP_PKEY_CTX* CryptoNative_EvpPKeyCtxCreateFromPKey(EVP_PKEY* pkey, void* extraHandle); - - -/* -Configures the EVP_PKEY_CTX for use in an ECDSA sign operation. -Returns 1 on success, 0 on failure. -*/ -PALEXPORT int32_t CryptoNative_EvpPKeyCtxConfigureForECDSASign(EVP_PKEY_CTX* ctx); - -/* -Configures the EVP_PKEY_CTX for use in an ECDSA verify operation. -Returns 1 on success, 0 on failure. -*/ -PALEXPORT int32_t CryptoNative_EvpPKeyCtxConfigureForECDSAVerify(EVP_PKEY_CTX* ctx); - -/* -Signs hash using EVP_PKEY_CTX. -Returns 1 on success, 0 on failure. -*/ -PALEXPORT int32_t CryptoNative_EvpPKeyCtxSignHash(EVP_PKEY_CTX* ctx, - const uint8_t* hash, - int32_t hashLen, - uint8_t* destination, - int32_t* destinationLen); - -/* -Verifies hash using EVP_PKEY_CTX. -Returns 1 on success, 0 on failure. -*/ -PALEXPORT int32_t CryptoNative_EvpPKeyCtxVerifyHash(EVP_PKEY_CTX* ctx, - const uint8_t* hash, - int32_t hashLen, - uint8_t* signature, - int32_t signatureLen); +EVP_PKEY_CTX* EvpPKeyCtxCreateFromPKey(EVP_PKEY* pkey, void* extraHandle); diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdh.c b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdh.c index c1126e5206fcb3..a64fc326b24bcc 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdh.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdh.c @@ -3,21 +3,18 @@ #include "pal_evp_pkey_ecdh.h" #include "pal_evp_pkey.h" +#include "pal_utilities.h" +#include -EVP_PKEY_CTX* CryptoNative_EvpPKeyCtxCreate(EVP_PKEY* pkey, void* extraHandle, EVP_PKEY* peerkey, uint32_t* secretLength) +static EVP_PKEY_CTX* EvpPKeyCtxCreate(EVP_PKEY* pkey, void* extraHandle, EVP_PKEY* peerkey) { - if (secretLength != NULL) - *secretLength = 0; - - ERR_clear_error(); - - if (pkey == NULL || peerkey == NULL || secretLength == NULL) + if (pkey == NULL || peerkey == NULL) { return NULL; } /* Create the context for the shared secret derivation */ - EVP_PKEY_CTX* ctx = CryptoNative_EvpPKeyCtxCreateFromPKey(pkey, extraHandle); + EVP_PKEY_CTX* ctx = EvpPKeyCtxCreateFromPKey(pkey, extraHandle); if (ctx == NULL) { @@ -26,43 +23,42 @@ EVP_PKEY_CTX* CryptoNative_EvpPKeyCtxCreate(EVP_PKEY* pkey, void* extraHandle, E size_t tmpLength = 0; - /* Initialize, provide the peer public key, and determine the buffer size */ - if (1 != EVP_PKEY_derive_init(ctx) || 1 != EVP_PKEY_derive_set_peer(ctx, peerkey) || - 1 != EVP_PKEY_derive(ctx, NULL, &tmpLength)) + /* Initialize, provide the peer public key */ + if (1 != EVP_PKEY_derive_init(ctx) || 1 != EVP_PKEY_derive_set_peer(ctx, peerkey)) { EVP_PKEY_CTX_free(ctx); return NULL; } - *secretLength = (uint32_t)tmpLength; return ctx; } -int32_t CryptoNative_EvpPKeyDeriveSecretAgreement(uint8_t* secret, uint32_t secretLength, EVP_PKEY_CTX* ctx) +int32_t CryptoNative_EvpPKeyDeriveSecretAgreement(EVP_PKEY* pkey, void* extraHandle, EVP_PKEY* peerKey, uint8_t* secret, uint32_t secretLength) { - size_t tmpSize = (size_t)secretLength; - int ret = 0; + if (pkey == NULL || peerKey == NULL || secretLength == 0 || secret == NULL) + { + return 0; + } ERR_clear_error(); - if (secret != NULL && ctx != NULL) - { - ret = EVP_PKEY_derive(ctx, secret, &tmpSize); + size_t tmpSize = (size_t)secretLength; + EVP_PKEY_CTX* ctx = EvpPKeyCtxCreate(pkey, extraHandle, peerKey); - if (ret == 1 && tmpSize != (size_t)secretLength) - { - OPENSSL_cleanse(secret, secretLength); - ret = 0; - } + if (ctx == NULL) + { + return 0; } - return ret; -} + int ret = EVP_PKEY_derive(ctx, secret, &tmpSize); -void CryptoNative_EvpPKeyCtxDestroy(EVP_PKEY_CTX* ctx) -{ - if (ctx != NULL) + EVP_PKEY_CTX_free(ctx); + + if (ret != 1) { - EVP_PKEY_CTX_free(ctx); + return 0; } + + assert(tmpSize > 0 && tmpSize <= secretLength); + return SizeTToInt32(tmpSize); } diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdh.h b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdh.h index b852b5995ca608..6cdb6063c1c577 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdh.h +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdh.h @@ -5,8 +5,4 @@ #include "pal_compiler.h" #include "opensslshim.h" -PALEXPORT EVP_PKEY_CTX* CryptoNative_EvpPKeyCtxCreate(EVP_PKEY* pkey, void* extraHandle, EVP_PKEY* peerkey, uint32_t* secretLength); - -PALEXPORT int32_t CryptoNative_EvpPKeyDeriveSecretAgreement(uint8_t* secret, uint32_t secretLength, EVP_PKEY_CTX* ctx); - -PALEXPORT void CryptoNative_EvpPKeyCtxDestroy(EVP_PKEY_CTX* ctx); +PALEXPORT int32_t CryptoNative_EvpPKeyDeriveSecretAgreement(EVP_PKEY* pkey, void* extraHandle, EVP_PKEY* peerKey, uint8_t* secret, uint32_t secretLength); diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdsa.c b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdsa.c new file mode 100644 index 00000000000000..86f6916297c3db --- /dev/null +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdsa.c @@ -0,0 +1,77 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "pal_evp_pkey.h" +#include "pal_evp_pkey_ecdsa.h" +#include "pal_utilities.h" +#include + +int32_t CryptoNative_EcDsaSignHash(EVP_PKEY* pkey, + void* extraHandle, + const uint8_t* hash, + int32_t hashLen, + uint8_t* destination, + int32_t destinationLen) +{ + assert(pkey != NULL); + assert(destination != NULL); + + ERR_clear_error(); + + EVP_PKEY_CTX* ctx = EvpPKeyCtxCreateFromPKey(pkey, extraHandle); + + int ret = -1; + + if (ctx == NULL || EVP_PKEY_sign_init(ctx) <= 0) + { + goto done; + } + + size_t written = Int32ToSizeT(destinationLen); + + if (EVP_PKEY_sign(ctx, destination, &written, hash, Int32ToSizeT(hashLen)) > 0) + { + ret = SizeTToInt32(written); + } + +done: + if (ctx != NULL) + { + EVP_PKEY_CTX_free(ctx); + } + + return ret; +} + +int32_t CryptoNative_EcDsaVerifyHash(EVP_PKEY* pkey, + void* extraHandle, + const uint8_t* hash, + int32_t hashLen, + const uint8_t* signature, + int32_t signatureLen) +{ + assert(pkey != NULL); + assert(signature != NULL); + + ERR_clear_error(); + + EVP_PKEY_CTX* ctx = EvpPKeyCtxCreateFromPKey(pkey, extraHandle); + + int ret = -1; + + if (ctx == NULL || EVP_PKEY_verify_init(ctx) <= 0) + { + goto done; + } + + // We normalize all error codes to 1 or 0 because we cannot distinguish between missized hash and other errors + ret = EVP_PKEY_verify(ctx, signature, Int32ToSizeT(signatureLen), hash, Int32ToSizeT(hashLen)) == 1; + +done: + if (ctx != NULL) + { + EVP_PKEY_CTX_free(ctx); + } + + return ret; +} diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdsa.h b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdsa.h new file mode 100644 index 00000000000000..adc74db89cb57b --- /dev/null +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdsa.h @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "opensslshim.h" +#include "pal_compiler.h" +#include "pal_types.h" + +/* +Complete the ECDSA signature generation for the specified hash using the provided ECDSA key +(wrapped in an EVP_PKEY) and padding/digest options. + +Returns the number of bytes written to destination, -1 on error. +*/ +PALEXPORT int32_t CryptoNative_EcDsaSignHash(EVP_PKEY* pkey, + void* extraHandle, + const uint8_t* hash, + int32_t hashLen, + uint8_t* destination, + int32_t destinationLen); + +/* +Verify an ECDSA signature for the specified hash using the provided ECDSA key (wrapped in an EVP_PKEY) +and padding/digest options. + +Returns 1 on a verified signature, 0 on a mismatched signature, -1 on error. +*/ +PALEXPORT int32_t CryptoNative_EcDsaVerifyHash(EVP_PKEY* pkey, + void* extraHandle, + const uint8_t* hash, + int32_t hashLen, + const uint8_t* signature, + int32_t signatureLen); 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 9f14667e8981c1..1554ab935d890c 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 @@ -118,7 +118,7 @@ int32_t CryptoNative_RsaDecrypt(EVP_PKEY* pkey, ERR_clear_error(); - EVP_PKEY_CTX* ctx = CryptoNative_EvpPKeyCtxCreateFromPKey(pkey, extraHandle); + EVP_PKEY_CTX* ctx = EvpPKeyCtxCreateFromPKey(pkey, extraHandle); int ret = -1; @@ -164,7 +164,7 @@ int32_t CryptoNative_RsaEncrypt(EVP_PKEY* pkey, ERR_clear_error(); - EVP_PKEY_CTX* ctx = CryptoNative_EvpPKeyCtxCreateFromPKey(pkey, extraHandle); + EVP_PKEY_CTX* ctx = EvpPKeyCtxCreateFromPKey(pkey, extraHandle); int ret = -1; @@ -194,7 +194,7 @@ int32_t CryptoNative_RsaEncrypt(EVP_PKEY* pkey, return ret; } -static bool EvpPKeyCtxConfigureForRsaSignVerify(EVP_PKEY_CTX* ctx, RsaPaddingMode padding, const EVP_MD* digest) +static bool ConfigureSignature(EVP_PKEY_CTX* ctx, RsaPaddingMode padding, const EVP_MD* digest) { if (padding == RsaPaddingPkcs1) { @@ -225,32 +225,97 @@ static bool EvpPKeyCtxConfigureForRsaSignVerify(EVP_PKEY_CTX* ctx, RsaPaddingMod return true; } -int32_t CryptoNative_EvpPKeyCtxConfigureForRsaSign(EVP_PKEY_CTX* ctx, RsaPaddingMode padding, const EVP_MD* digest) +int32_t CryptoNative_RsaSignHash(EVP_PKEY* pkey, + void* extraHandle, + RsaPaddingMode padding, + const EVP_MD* digest, + const uint8_t* hash, + int32_t hashLen, + uint8_t* destination, + int32_t destinationLen) { - if (EVP_PKEY_sign_init(ctx) <= 0) + assert(pkey != NULL); + assert(destination != NULL); + assert(padding >= RsaPaddingPkcs1 && padding <= RsaPaddingOaepOrPss); + assert(digest != NULL || padding == RsaPaddingPkcs1); + + ERR_clear_error(); + + EVP_PKEY_CTX* ctx = EvpPKeyCtxCreateFromPKey(pkey, extraHandle); + + int ret = -1; + + if (ctx == NULL || EVP_PKEY_sign_init(ctx) <= 0) + { + goto done; + } + + if (!ConfigureSignature(ctx, padding, digest)) + { + goto done; + } + + size_t written = Int32ToSizeT(destinationLen); + + if (EVP_PKEY_sign(ctx, destination, &written, hash, Int32ToSizeT(hashLen)) > 0) { - return 0; + ret = SizeTToInt32(written); } - if (!EvpPKeyCtxConfigureForRsaSignVerify(ctx, padding, digest)) +done: + if (ctx != NULL) { - return 0; + EVP_PKEY_CTX_free(ctx); } - return 1; + return ret; } -int32_t CryptoNative_EvpPKeyCtxConfigureForRsaVerify(EVP_PKEY_CTX* ctx, RsaPaddingMode padding, const EVP_MD* digest) +int32_t CryptoNative_RsaVerifyHash(EVP_PKEY* pkey, + void* extraHandle, + RsaPaddingMode padding, + const EVP_MD* digest, + const uint8_t* hash, + int32_t hashLen, + const uint8_t* signature, + int32_t signatureLen) { - if (EVP_PKEY_verify_init(ctx) <= 0) + assert(pkey != NULL); + assert(signature != NULL); + assert(padding >= RsaPaddingPkcs1 && padding <= RsaPaddingOaepOrPss); + assert(digest != NULL || padding == RsaPaddingPkcs1); + + ERR_clear_error(); + + EVP_PKEY_CTX* ctx = EvpPKeyCtxCreateFromPKey(pkey, extraHandle); + + int ret = -1; + + if (ctx == NULL || EVP_PKEY_verify_init(ctx) <= 0) + { + goto done; + } + + if (!ConfigureSignature(ctx, padding, digest)) + { + goto done; + } + + // EVP_PKEY_verify is not consistent on whether a missized hash is an error or just a mismatch. + // Normalize to mismatch. + if (hashLen != EVP_MD_get_size(digest)) { - return 0; + ret = 0; + goto done; } - if (!EvpPKeyCtxConfigureForRsaSignVerify(ctx, padding, digest)) + ret = EVP_PKEY_verify(ctx, signature, Int32ToSizeT(signatureLen), hash, Int32ToSizeT(hashLen)); + +done: + if (ctx != NULL) { - return 0; + EVP_PKEY_CTX_free(ctx); } - return 1; + return ret; } 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 94424464c109b3..7ae93de6cf3553 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 @@ -54,13 +54,31 @@ PALEXPORT int32_t CryptoNative_RsaEncrypt(EVP_PKEY* pkey, int32_t destinationLen); /* -Configures the EVP_PKEY_CTX for use in an RSA sign operation. -Returns 1 on success, 0 on failure. +Complete the RSA signature generation for the specified hash using the provided RSA key +(wrapped in an EVP_PKEY) and padding/digest options. + +Returns the number of bytes written to destination, -1 on error. */ -PALEXPORT int32_t CryptoNative_EvpPKeyCtxConfigureForRsaSign(EVP_PKEY_CTX* ctx, RsaPaddingMode padding, const EVP_MD* digest); +PALEXPORT int32_t CryptoNative_RsaSignHash(EVP_PKEY* pkey, + void* extraHandle, + RsaPaddingMode padding, + const EVP_MD* digest, + const uint8_t* hash, + int32_t hashLen, + uint8_t* destination, + int32_t destinationLen); /* -Configures the EVP_PKEY_CTX for use in an RSA verify operation. -Returns 1 on success, 0 on failure. +Verify an RSA signature for the specified hash using the provided RSA key (wrapped in an EVP_PKEY) +and padding/digest options. + +Returns 1 on a verified signature, 0 on a mismatched signature, -1 on error. */ -PALEXPORT int32_t CryptoNative_EvpPKeyCtxConfigureForRsaVerify(EVP_PKEY_CTX* ctx, RsaPaddingMode padding, const EVP_MD* digest); +PALEXPORT int32_t CryptoNative_RsaVerifyHash(EVP_PKEY* pkey, + void* extraHandle, + RsaPaddingMode padding, + const EVP_MD* digest, + const uint8_t* hash, + int32_t hashLen, + const uint8_t* signature, + int32_t signatureLen); From 04b4b4b713d6bd71f74a628479c1b7a02fdf3d3f Mon Sep 17 00:00:00 2001 From: Krzysztof Wicher Date: Thu, 18 Jul 2024 17:07:57 +0200 Subject: [PATCH 10/17] XML doc + extra test case --- .../Cryptography/SafeEvpPKeyHandle.OpenSsl.cs | 34 +++++++++++++++++++ .../tests/OpenSslNamedKeysTests.manual.cs | 14 ++++++++ 2 files changed, 48 insertions(+) diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SafeEvpPKeyHandle.OpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SafeEvpPKeyHandle.OpenSsl.cs index 451037fd375c6a..746af5d3962d9a 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SafeEvpPKeyHandle.OpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SafeEvpPKeyHandle.OpenSsl.cs @@ -195,6 +195,40 @@ public static SafeEvpPKeyHandle OpenPublicKeyFromEngine(string engineName, strin return Interop.Crypto.LoadPublicKeyFromEngine(engineName, keyId); } + /// + /// Open a named public key using a named OpenSSL provider. + /// + /// + /// The name of the provider to process the key open request. + /// + /// + /// The URI of the key to open. + /// + /// + /// The opened key. + /// + /// + /// or is . + /// + /// + /// or is the empty string. + /// + /// + /// the key could not be opened via the specified provider. + /// + /// + /// + /// Both provider name and key URI must be trusted inputs. + /// + /// + /// This operation will fail if OpenSSL cannot successfully load the named provider, + /// or if the named provider cannot load the named key. + /// + /// + /// The syntax for is determined by each individual + /// provider. + /// + /// [UnsupportedOSPlatform("android")] [UnsupportedOSPlatform("browser")] [UnsupportedOSPlatform("ios")] diff --git a/src/libraries/System.Security.Cryptography/tests/OpenSslNamedKeysTests.manual.cs b/src/libraries/System.Security.Cryptography/tests/OpenSslNamedKeysTests.manual.cs index af073eaf9699b8..1a3e2146e0ebe1 100644 --- a/src/libraries/System.Security.Cryptography/tests/OpenSslNamedKeysTests.manual.cs +++ b/src/libraries/System.Security.Cryptography/tests/OpenSslNamedKeysTests.manual.cs @@ -109,6 +109,20 @@ public static void NullArguments() Assert.Throws(() => SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, null)); } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.OpenSslPresentOnSystem))] + public static void EmptyNameThroughNullCharacter() + { + Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPrivateKeyFromEngine("\0", "foo")); + Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPublicKeyFromEngine("\0", "foo")); + Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenKeyFromProvider("\0", "foo")); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.OpenSslPresentOnSystem))] + public static void EmptyUriThroughNullCharacter() + { + Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenKeyFromProvider("default", "\0")); + } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.OpenSslPresentOnSystem))] public static void Engine_NonExisting() { From 20c0fcbbc792ce19a6e38fde33af12ffe5d1b7d5 Mon Sep 17 00:00:00 2001 From: Krzysztof Wicher Date: Thu, 18 Jul 2024 17:32:35 +0200 Subject: [PATCH 11/17] remote OSSL_STORE_open usage and revert comment on the DuplicateKeyHandle --- .../System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs | 4 ++-- .../System/Security/Cryptography/SafeEvpPKeyHandle.OpenSsl.cs | 1 - .../libs/System.Security.Cryptography.Native/opensslshim.h | 2 -- .../libs/System.Security.Cryptography.Native/osslcompat_30.h | 2 -- 4 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs index bc5daa009f4dfd..e56af24ad4f177 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs @@ -72,10 +72,10 @@ public ECDiffieHellmanOpenSsl(IntPtr handle) } /// - /// Obtain a SafeHandle version of an EVP_PKEY* + /// Obtain a SafeHandle version of an EVP_PKEY* which wraps an EC_KEY* equivalent /// to the current key for this instance. /// - /// A SafeHandle for the EVP_PKEY key in OpenSSL + /// A SafeHandle for the EC_KEY key in OpenSSL public SafeEvpPKeyHandle DuplicateKeyHandle() { ThrowIfDisposed(); diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SafeEvpPKeyHandle.OpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SafeEvpPKeyHandle.OpenSsl.cs index 746af5d3962d9a..8db542fd8f6a47 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SafeEvpPKeyHandle.OpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SafeEvpPKeyHandle.OpenSsl.cs @@ -4,7 +4,6 @@ using System.Diagnostics; using System.Runtime.InteropServices; using System.Runtime.Versioning; -using Microsoft.Win32.SafeHandles; namespace System.Security.Cryptography { diff --git a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h index e76d310e528774..01497272682cd8 100644 --- a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h +++ b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h @@ -539,7 +539,6 @@ extern bool g_libSslUses32BitTime; LIGHTUP_FUNCTION(OSSL_STORE_INFO_get1_PKEY) \ LIGHTUP_FUNCTION(OSSL_STORE_INFO_get1_PUBKEY) \ LIGHTUP_FUNCTION(OSSL_STORE_load) \ - LIGHTUP_FUNCTION(OSSL_STORE_open) \ LIGHTUP_FUNCTION(OSSL_STORE_open_ex) \ LIGHTUP_FUNCTION(OSSL_PARAM_construct_octet_string) \ LIGHTUP_FUNCTION(OSSL_PARAM_construct_int32) \ @@ -1086,7 +1085,6 @@ extern TYPEOF(OPENSSL_gmtime)* OPENSSL_gmtime_ptr; #define OSSL_STORE_INFO_get1_PKEY OSSL_STORE_INFO_get1_PKEY_ptr #define OSSL_STORE_INFO_get1_PUBKEY OSSL_STORE_INFO_get1_PUBKEY_ptr #define OSSL_STORE_load OSSL_STORE_load_ptr -#define OSSL_STORE_open OSSL_STORE_open_ptr #define OSSL_STORE_open_ex OSSL_STORE_open_ex_ptr #define OSSL_PARAM_construct_octet_string OSSL_PARAM_construct_octet_string_ptr #define OSSL_PARAM_construct_int32 OSSL_PARAM_construct_int32_ptr diff --git a/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h b/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h index b0574d1a7705bf..24baa4f68c7c89 100644 --- a/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h +++ b/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h @@ -92,8 +92,6 @@ void OSSL_STORE_INFO_free(OSSL_STORE_INFO* info); int OSSL_STORE_INFO_get_type(const OSSL_STORE_INFO* info); EVP_PKEY* OSSL_STORE_INFO_get1_PKEY(const OSSL_STORE_INFO* info); EVP_PKEY* OSSL_STORE_INFO_get1_PUBKEY(const OSSL_STORE_INFO* info); -OSSL_STORE_CTX* OSSL_STORE_open( - const char*, const UI_METHOD*, void*, OSSL_STORE_post_process_info_fn post_process, void*); OSSL_STORE_CTX* OSSL_STORE_open_ex( const char*, OSSL_LIB_CTX*, const char*, const UI_METHOD*, void*, const OSSL_PARAM*, OSSL_STORE_post_process_info_fn post_process, void*); From 356acb248800902f36e9cc575839771409bd3d26 Mon Sep 17 00:00:00 2001 From: Krzysztof Wicher Date: Fri, 19 Jul 2024 10:54:43 +0200 Subject: [PATCH 12/17] Address feedback --- .../Interop.EvpPkey.cs | 41 +++++- .../ECDiffieHellmanOpenSsl.Derive.cs | 4 +- .../Cryptography/ECDiffieHellmanOpenSsl.cs | 2 +- .../Security/Cryptography/ECDsaOpenSsl.cs | 2 +- .../System/Security/Cryptography/ECOpenSsl.cs | 13 +- .../Security/Cryptography/RSAOpenSsl.cs | 18 +-- .../Cryptography/ECDiffieHellmanOpenSsl.cs | 6 +- .../Security/Cryptography/ECDsaOpenSsl.cs | 6 +- .../Security/Cryptography/RSAOpenSsl.cs | 2 +- .../Cryptography/SafeEvpPKeyHandle.OpenSsl.cs | 35 +---- .../X509Certificates/OpenSslExportProvider.cs | 9 +- .../tests/OpenSslNamedKeysTests.manual.cs | 137 +++++++++++------- .../tests/osslplugins/test.sh | 9 -- .../osslcompat_30.h | 9 -- .../pal_evp_pkey.c | 22 ++- .../pal_evp_pkey_ecdh.c | 5 + 16 files changed, 173 insertions(+), 147 deletions(-) diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs index afb7ebd0e88338..68f4f8cb6433f0 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs @@ -19,6 +19,23 @@ internal static partial class Crypto [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyBits")] internal static partial int EvpPKeyBits(SafeEvpPKeyHandle pkey); + internal static int GetEvpPKeySizeBytes(SafeEvpPKeyHandle pkey) + { + // EVP_PKEY_size returns the maximum suitable size for the output buffers for almost all operations that can be done with the key. + // For most of the OpenSSL 'default' provider keys it will return the same size as this method, + // but other providers such as 'tpm2' it may return larger size. + // Instead we will round up EVP_PKEY_bits result. + int keySizeBits = Interop.Crypto.EvpPKeyBits(pkey); + + if (keySizeBits <= 0) + { + Debug.Fail($"EVP_PKEY_bits returned non-positive value: {keySizeBits}"); + throw new CryptographicException(); + } + + return (keySizeBits + 7) / 8; + } + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_UpRefEvpPkey")] private static partial int UpRefEvpPkey(SafeEvpPKeyHandle handle, IntPtr extraHandle); @@ -271,15 +288,29 @@ internal static SafeEvpPKeyHandle LoadKeyFromProvider( string keyUri) { IntPtr extraHandle = IntPtr.Zero; - IntPtr evpPKeyHandle = CryptoNative_LoadKeyFromProvider(providerName, keyUri, ref extraHandle); + IntPtr evpPKeyHandle = IntPtr.Zero; - if (IntPtr.Zero == evpPKeyHandle) + try { - throw CreateOpenSslCryptographicException(); + evpPKeyHandle = CryptoNative_LoadKeyFromProvider(providerName, keyUri, ref extraHandle); + + if (evpPKeyHandle == IntPtr.Zero || extraHandle == IntPtr.Zero) + { + Debug.Assert(evpPKeyHandle == IntPtr.Zero, "extraHandle should not be null if evpPKeyHandle is not null"); + throw CreateOpenSslCryptographicException(); + } + + return new SafeEvpPKeyHandle(evpPKeyHandle, extraHandle: extraHandle); } + catch + { + if (evpPKeyHandle != IntPtr.Zero || extraHandle != IntPtr.Zero) + { + EvpPkeyDestroy(evpPKeyHandle, extraHandle); + } - Debug.Assert(extraHandle != IntPtr.Zero); - return new SafeEvpPKeyHandle(evpPKeyHandle, extraHandle: extraHandle); + throw; + } } internal enum EvpAlgorithmId diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs index 5def04d316c61e..32a463bd31caf1 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs @@ -137,7 +137,7 @@ public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPa { using (ECOpenSsl tmp = new ECOpenSsl(otherKey.ExportExplicitParameters())) { - theirKey = tmp.CreateKeyHandle(); + theirKey = tmp.CreateEvpPKeyHandle(); } } else @@ -149,7 +149,7 @@ public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPa // - private key is actually missing using (ECOpenSsl tmp = new ECOpenSsl(ExportExplicitParameters(true))) { - ourKey = tmp.CreateKeyHandle(); + ourKey = tmp.CreateEvpPKeyHandle(); disposeOurKey = true; } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs index 9cec339025b667..eff1ba80a346b4 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs @@ -104,7 +104,7 @@ public override void ImportParameters(ECParameters parameters) { ThrowIfDisposed(); FreeKey(); - _key = new Lazy(ECOpenSsl.GenerateECKey(parameters, out int keySize)); + _key = new Lazy(ECOpenSsl.ImportECKey(parameters, out int keySize)); KeySizeValue = keySize; } diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDsaOpenSsl.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDsaOpenSsl.cs index f87780d95e5d99..b683d88442c2c4 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDsaOpenSsl.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDsaOpenSsl.cs @@ -263,7 +263,7 @@ public override void ImportParameters(ECParameters parameters) ThrowIfDisposed(); FreeKey(); - _key = new Lazy(ECOpenSsl.GenerateECKey(parameters, out int keySize)); + _key = new Lazy(ECOpenSsl.ImportECKey(parameters, out int keySize)); // Use ForceSet instead of the property setter to ensure that LegalKeySizes doesn't interfere // with the already loaded key. diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs b/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs index 9f028ac4c90469..c3464d0f30c101 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs @@ -40,7 +40,7 @@ public void Dispose() internal int KeySize => Interop.Crypto.EcKeyGetSize(_key.Value); - internal SafeEvpPKeyHandle CreateKeyHandle() + internal SafeEvpPKeyHandle CreateEvpPKeyHandle() { SafeEcKeyHandle currentKey = _key.Value; Debug.Assert(currentKey != null, "key is null"); @@ -117,22 +117,23 @@ private void FreeKey() internal static SafeEvpPKeyHandle GenerateECKey(int keySize) { - SafeEvpPKeyHandle ret = GenerateECKeyCore(new ECOpenSsl(keySize), out int createdKeySize); + SafeEvpPKeyHandle ret = ImportECKeyCore(new ECOpenSsl(keySize), out int createdKeySize); Debug.Assert(keySize == createdKeySize); return ret; } internal static SafeEvpPKeyHandle GenerateECKey(ECCurve curve, out int keySize) { - return GenerateECKeyCore(new ECOpenSsl(curve), out keySize); + return ImportECKeyCore(new ECOpenSsl(curve), out keySize); } - internal static SafeEvpPKeyHandle GenerateECKey(ECParameters parameters, out int keySize) + internal static SafeEvpPKeyHandle ImportECKey(ECParameters parameters, out int keySize) { - return GenerateECKeyCore(new ECOpenSsl(parameters), out keySize); + return ImportECKeyCore(new ECOpenSsl(parameters), out keySize); } - private static SafeEvpPKeyHandle GenerateECKeyCore(ECOpenSsl ecOpenSsl, out int keySize) + // Note: This method takes ownership of ecOpenSsl and disposes it + private static SafeEvpPKeyHandle ImportECKeyCore(ECOpenSsl ecOpenSsl, out int keySize) { using (ECOpenSsl ec = ecOpenSsl) { diff --git a/src/libraries/Common/src/System/Security/Cryptography/RSAOpenSsl.cs b/src/libraries/Common/src/System/Security/Cryptography/RSAOpenSsl.cs index 7469affbce28e3..96867e0f5e9d9b 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/RSAOpenSsl.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/RSAOpenSsl.cs @@ -86,7 +86,7 @@ public override byte[] Decrypt(byte[] data, RSAEncryptionPadding padding) ValidatePadding(padding); SafeEvpPKeyHandle key = GetKey(); - int rsaSize = key.GetKeySizeBytes(); + int rsaSize = Interop.Crypto.GetEvpPKeySizeBytes(key); Span destination = default; byte[] buf = CryptoPool.Rent(rsaSize); @@ -118,7 +118,7 @@ public override bool TryDecrypt( // OpenSSL requires that the decryption buffer be at least as large as key size in bytes. // So if the destination is too small, use a temporary buffer so we can match // Windows behavior of succeeding as long as the buffer can hold the final output. - int keySizeBytes = key.GetKeySizeBytes(); + int keySizeBytes = Interop.Crypto.GetEvpPKeySizeBytes(key); if (destination.Length < keySizeBytes) { @@ -173,7 +173,7 @@ private static int Decrypt( // Caller should have already checked this. Debug.Assert(!key.IsInvalid); - int rsaSize = key.GetKeySizeBytes(); + int rsaSize = Interop.Crypto.GetEvpPKeySizeBytes(key); if (data.Length != rsaSize) { @@ -182,7 +182,7 @@ private static int Decrypt( if (destination.Length < rsaSize) { - Debug.Fail($"Caller is responsible for temporary decryption buffer creation destination. {nameof(destination)}.{nameof(destination.Length)} == {destination.Length}, {nameof(rsaSize)} = {rsaSize}"); + Debug.Fail($"Caller is responsible for temporary decryption buffer creation destination. destination.Length: {destination.Length}, needed: {rsaSize}"); throw new CryptographicException(); } @@ -210,7 +210,7 @@ public override byte[] Encrypt(byte[] data, RSAEncryptionPadding padding) ValidatePadding(padding); SafeEvpPKeyHandle key = GetKey(); - byte[] buf = new byte[key.GetKeySizeBytes()]; + byte[] buf = new byte[Interop.Crypto.GetEvpPKeySizeBytes(key)]; bool encrypted = TryEncrypt( key, @@ -245,7 +245,7 @@ private static bool TryEncrypt( RSAEncryptionPadding padding, out int bytesWritten) { - int rsaSize = key.GetKeySizeBytes(); + int rsaSize = Interop.Crypto.GetEvpPKeySizeBytes(key); if (destination.Length < rsaSize) { @@ -660,7 +660,7 @@ private void SetKey(SafeEvpPKeyHandle newKey) // Use ForceSet instead of the property setter to ensure that LegalKeySizes doesn't interfere // with the already loaded key. - ForceSetKeySize(newKey.GetKeySizeBits()); + ForceSetKeySize(Interop.Crypto.EvpPKeyBits(newKey)); } private static void ValidateParameters(ref RSAParameters parameters) @@ -733,7 +733,7 @@ public override byte[] SignHash(byte[] hash, HashAlgorithmName hashAlgorithm, RS ThrowIfDisposed(); SafeEvpPKeyHandle key = GetKey(); - int bytesRequired = key.GetKeySizeBytes(); + int bytesRequired = Interop.Crypto.GetEvpPKeySizeBytes(key); byte[] signature = new byte[bytesRequired]; int written = Interop.Crypto.RsaSignHash(key, padding.Mode, hashAlgorithm, hash, signature); @@ -759,7 +759,7 @@ public override bool TrySignHash( ThrowIfDisposed(); SafeEvpPKeyHandle key = GetKey(); - int bytesRequired = key.GetKeySizeBytes(); + int bytesRequired = Interop.Crypto.GetEvpPKeySizeBytes(key); if (destination.Length < bytesRequired) { diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs index e56af24ad4f177..20f6d539f708fc 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs @@ -31,13 +31,13 @@ public ECDiffieHellmanOpenSsl(SafeEvpPKeyHandle pkeyHandle) ThrowIfNotSupported(); - if (pkeyHandle.GetKeyType() != Interop.Crypto.EvpAlgorithmId.ECC) + if (Interop.Crypto.EvpPKeyType(pkeyHandle) != Interop.Crypto.EvpAlgorithmId.ECC) { throw new CryptographicException(SR.Cryptography_OpenInvalidHandle); } _key = new Lazy(pkeyHandle.DuplicateHandle()); - KeySizeValue = _key.Value.GetKeySizeBits(); + KeySizeValue = Interop.Crypto.EvpPKeyBits(_key.Value); } /// @@ -68,7 +68,7 @@ public ECDiffieHellmanOpenSsl(IntPtr handle) _key = new Lazy(Interop.Crypto.CreateEvpPkeyFromEcKey(ecKeyHandle)); } - KeySizeValue = _key.Value.GetKeySizeBits(); + KeySizeValue = Interop.Crypto.EvpPKeyBits(_key.Value); } /// diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDsaOpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDsaOpenSsl.cs index 9f653667a7d5c9..b8318f56d2ff5e 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDsaOpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDsaOpenSsl.cs @@ -31,13 +31,13 @@ public ECDsaOpenSsl(SafeEvpPKeyHandle pkeyHandle) ThrowIfNotSupported(); - if (pkeyHandle.GetKeyType() != Interop.Crypto.EvpAlgorithmId.ECC) + if (Interop.Crypto.EvpPKeyType(pkeyHandle) != Interop.Crypto.EvpAlgorithmId.ECC) { throw new CryptographicException(SR.Cryptography_OpenInvalidHandle); } _key = new Lazy(pkeyHandle.DuplicateHandle()); - ForceSetKeySize(pkeyHandle.GetKeySizeBits()); + ForceSetKeySize(Interop.Crypto.EvpPKeyBits(pkeyHandle)); } /// @@ -68,7 +68,7 @@ public ECDsaOpenSsl(IntPtr handle) _key = new Lazy(Interop.Crypto.CreateEvpPkeyFromEcKey(ecKeyHandle)); } - ForceSetKeySize(_key.Value.GetKeySizeBits()); + ForceSetKeySize(Interop.Crypto.EvpPKeyBits(_key.Value)); } /// diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSAOpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSAOpenSsl.cs index 67bc07e1cfa44f..c180bd98c36f08 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSAOpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSAOpenSsl.cs @@ -74,7 +74,7 @@ public RSAOpenSsl(SafeEvpPKeyHandle pkeyHandle) ThrowIfNotSupported(); - if (pkeyHandle.GetKeyType() != Interop.Crypto.EvpAlgorithmId.RSA) + if (Interop.Crypto.EvpPKeyType(pkeyHandle) != Interop.Crypto.EvpAlgorithmId.RSA) { throw new CryptographicException(SR.Cryptography_OpenInvalidHandle); } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SafeEvpPKeyHandle.OpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SafeEvpPKeyHandle.OpenSsl.cs index 8db542fd8f6a47..64600e976a1a10 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SafeEvpPKeyHandle.OpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SafeEvpPKeyHandle.OpenSsl.cs @@ -15,7 +15,7 @@ public sealed partial class SafeEvpPKeyHandle : SafeHandle /// In some cases like when a key is loaded from a provider, the key may have an associated data /// we need to keep alive for the lifetime of the key. This field is used to track that data. /// - internal IntPtr ExtraHandle { get; private set; } = IntPtr.Zero; + internal IntPtr ExtraHandle { get; private set; } [UnsupportedOSPlatform("android")] [UnsupportedOSPlatform("browser")] @@ -195,13 +195,13 @@ public static SafeEvpPKeyHandle OpenPublicKeyFromEngine(string engineName, strin } /// - /// Open a named public key using a named OpenSSL provider. + /// Open a named public key using a named OSSL_PROVIDER. /// /// /// The name of the provider to process the key open request. /// /// - /// The URI of the key to open. + /// The provider-assigned URI of the key to open. /// /// /// The opened key. @@ -217,7 +217,7 @@ public static SafeEvpPKeyHandle OpenPublicKeyFromEngine(string engineName, strin /// /// /// - /// Both provider name and key URI must be trusted inputs. + /// Both and must be trusted inputs. /// /// /// This operation will fail if OpenSSL cannot successfully load the named provider, @@ -245,32 +245,5 @@ public static SafeEvpPKeyHandle OpenKeyFromProvider(string providerName, string return Interop.Crypto.LoadKeyFromProvider(providerName, keyUri); } - - internal int GetKeySizeBits() - { - return Interop.Crypto.EvpPKeyBits(this); - } - - internal int GetKeySizeBytes() - { - // EVP_PKEY_size returns the maximum suitable size for the output buffers for almost all operations that can be done with the key. - // For most of the OpenSSL 'default' provider keys it will return the same size as this method, - // but other providers such as 'tpm2' it may return larger size. - // Instead we will round up EVP_PKEY_bits result. - int keySizeBits = GetKeySizeBits(); - - if (keySizeBits <= 0) - { - Debug.Fail($"EVP_PKEY_bits returned non-positive value: {keySizeBits}"); - throw new CryptographicException(); - } - - return (GetKeySizeBits() + 7) / 8; - } - - internal Interop.Crypto.EvpAlgorithmId GetKeyType() - { - return Interop.Crypto.EvpPKeyType(this); - } } } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslExportProvider.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslExportProvider.cs index 8e673efd607880..0119bb474438a2 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslExportProvider.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslExportProvider.cs @@ -25,7 +25,14 @@ protected override byte[] ExportPkcs8( AsymmetricAlgorithm alg; SafeEvpPKeyHandle? privateKey = ((OpenSslX509CertificateReader)certificatePal).PrivateKeyHandle; - switch (privateKey?.GetKeyType()) + if (privateKey == null) + { + throw new CryptographicException(SR.Cryptography_OpenInvalidHandle); + } + + Interop.Crypto.EvpAlgorithmId evpAlgId = Interop.Crypto.EvpPKeyType(privateKey); + + switch (evpAlgId) { case Interop.Crypto.EvpAlgorithmId.RSA: alg = new RSAOpenSsl(privateKey); diff --git a/src/libraries/System.Security.Cryptography/tests/OpenSslNamedKeysTests.manual.cs b/src/libraries/System.Security.Cryptography/tests/OpenSslNamedKeysTests.manual.cs index 1a3e2146e0ebe1..b5aab83a1aa3b6 100644 --- a/src/libraries/System.Security.Cryptography/tests/OpenSslNamedKeysTests.manual.cs +++ b/src/libraries/System.Security.Cryptography/tests/OpenSslNamedKeysTests.manual.cs @@ -12,8 +12,6 @@ public class OpenSslNamedKeysTests { private const string EnvVarPrefix = "DOTNET_CRYPTOGRAPHY_TESTS_"; - private const string TestEnsureFailingEnvVarName = EnvVarPrefix + "ENSURE_FAILING"; - private const string EngineEnvVarPrefix = EnvVarPrefix + "ENGINE_"; private const string TestEngineEnabledEnvVarName = EngineEnvVarPrefix + "ENABLE"; @@ -51,7 +49,6 @@ public class OpenSslNamedKeysTests public static bool ShouldRunProviderRsaDecryptTests { get; } = PlatformDetection.OpenSslPresentOnSystem && !string.IsNullOrEmpty(TpmRsaDecryptKeyHandleUri); public static bool ShouldRunAnyProviderTests => ShouldRunProviderEcDsaTests || ShouldRunProviderEcDhTests || ShouldRunProviderRsaSignTests || ShouldRunProviderRsaDecryptTests; - public static bool ShouldFailTests { get; } = StringToBool(Environment.GetEnvironmentVariable(TestEnsureFailingEnvVarName)); public static bool ShouldRunTpmTssTests => ShouldRunEngineTests && !string.IsNullOrEmpty(TpmEcDsaKeyHandle); private static readonly string AnyProviderKeyUri = TpmEcDsaKeyHandleUri ?? TpmEcDhKeyHandleUri ?? TpmRsaSignKeyHandleUri ?? TpmRsaDecryptKeyHandleUri ?? "test"; @@ -277,24 +274,52 @@ public static void Provider_TPM2ECDSA() Assert.ThrowsAny(() => ecdsaPri.ExportParameters(includePrivateParameters: true)); } + [ConditionalFact(nameof(ShouldRunProviderEcDsaTests))] + public static void Provider_TPM2ECDSA_ExportParameters() + { + using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, TpmEcDsaKeyHandleUri); + using ECDsa ecdsaPri = new ECDsaOpenSsl(priKeyHandle); + + ECDsa ecdsaPub = ECDsa.Create(); + ecdsaPub.ImportParameters(ecdsaPri.ExportParameters(false)); + Assert.ThrowsAny(() => ecdsaPri.ExportParameters(true)); + + byte[] data = new byte[] { 1, 2, 3, 1, 1, 2, 3 }; + byte[] signature = ecdsaPri.SignData(data, HashAlgorithmName.SHA256); + Assert.True(ecdsaPub.VerifyData(data, signature, HashAlgorithmName.SHA256)); + } + + [ConditionalFact(nameof(ShouldRunProviderEcDsaTests))] + public static void Provider_TPM2ECDSA_ExportExplicitParameters() + { + using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, TpmEcDsaKeyHandleUri); + using ECDsa ecdsaPri = new ECDsaOpenSsl(priKeyHandle); + + ECDsa ecdsaPub = ECDsa.Create(); + ecdsaPub.ImportParameters(ecdsaPri.ExportExplicitParameters(false)); + Assert.ThrowsAny(() => ecdsaPri.ExportExplicitParameters(true)); + + byte[] data = new byte[] { 1, 2, 3, 1, 1, 2, 3 }; + byte[] signature = ecdsaPri.SignData(data, HashAlgorithmName.SHA256); + Assert.True(ecdsaPub.VerifyData(data, signature, HashAlgorithmName.SHA256)); + } + [ConditionalFact(nameof(ShouldRunProviderEcDhTests))] public static void Provider_TPM2ECDH() { using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, TpmEcDhKeyHandleUri); using ECDiffieHellman alicePri = new ECDiffieHellmanOpenSsl(priKeyHandle); using ECDiffieHellman alicePub = ECDiffieHellman.Create(); - alicePub.ImportParameters(alicePri.ExportParameters(includePrivateParameters: false)); - using ECDiffieHellman bobPri = ECDiffieHellman.Create(); - bobPri.KeySize = alicePri.KeySize; + ECParameters aliceECParams = alicePri.ExportParameters(includePrivateParameters: false); + alicePub.ImportParameters(aliceECParams); - using ECDiffieHellman bobPub = ECDiffieHellman.Create(); - bobPub.ImportParameters(bobPri.ExportParameters(includePrivateParameters: false)); + using ECDiffieHellman bobPri = ECDiffieHellman.Create(aliceECParams.Curve); byte[] sharedKeyFromAlice; - using (ECDiffieHellmanPublicKey bobPublic = bobPub.PublicKey) + using (ECDiffieHellmanPublicKey bobPublic = bobPri.PublicKey) { - sharedKeyFromAlice = alicePri.DeriveKeyMaterial(bobPublic); + sharedKeyFromAlice = alicePri.DeriveRawSecretAgreement(bobPublic); Assert.NotEmpty(sharedKeyFromAlice); @@ -305,9 +330,15 @@ public static void Provider_TPM2ECDH() using (ECDiffieHellmanPublicKey alicePublic = alicePub.PublicKey) { - byte[] sharedKeyFromBob = bobPri.DeriveKeyMaterial(alicePublic); + byte[] sharedKeyFromBob = bobPri.DeriveRawSecretAgreement(alicePublic); Assert.Equal(sharedKeyFromAlice, sharedKeyFromBob); } + + // Now we derive it again but using directly PublicKey on the instance directly wrapping our TPM handle + using (ECDiffieHellmanPublicKey alicePublic = alicePri.PublicKey) + { + Assert.Equal(sharedKeyFromAlice, bobPri.DeriveRawSecretAgreement(alicePublic)); + } } [ConditionalFact(nameof(ShouldRunProviderRsaSignTests))] @@ -346,12 +377,25 @@ public static void Provider_TPM2SignRsa() } } - [ConditionalFact(nameof(ShouldRunProviderRsaDecryptTests))] - public static void Provider_TPM2DecryptRsa() + [ConditionalTheory(nameof(ShouldRunProviderRsaDecryptTests))] + [InlineData(RSAEncryptionPaddingMode.Pkcs1)] + [InlineData(RSAEncryptionPaddingMode.Oaep)] + public static void Provider_TPM2DecryptRsa(RSAEncryptionPaddingMode mode) { - // TPM2 OAEP support was added in the second half of 2023 therefore we only test Pkcs1 padding - // See: https://github.com/tpm2-software/tpm2-openssl/issues/89 - RSAEncryptionPadding padding = RSAEncryptionPadding.Pkcs1; + RSAEncryptionPadding padding; + + switch (mode) + { + case RSAEncryptionPaddingMode.Pkcs1: + padding = RSAEncryptionPadding.Pkcs1; + break; + case RSAEncryptionPaddingMode.Oaep: + padding = RSAEncryptionPadding.OaepSHA256; + break; + default: + throw new ArgumentOutOfRangeException(nameof(mode)); + } + using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, TpmRsaDecryptKeyHandleUri); using RSA rsaPri = new RSAOpenSsl(priKeyHandle); byte[] rsaPubBytes = rsaPri.ExportSubjectPublicKeyInfo(); @@ -376,51 +420,36 @@ public static void Provider_TPM2DecryptRsa() byte[] encrypted = rsaPub.Encrypt(data, padding); Assert.NotEqual(encrypted, data); - Assert.Equal(data, rsaPri.Decrypt(encrypted, padding)); + try + { + Assert.Equal(data, rsaPri.Decrypt(encrypted, padding)); + } + catch (CryptographicException) when (mode == RSAEncryptionPaddingMode.Oaep) + { + // TPM2 OAEP support was added in the second half of 2023 therefore we allow for OAEP to throw for the time being + // See: https://github.com/tpm2-software/tpm2-openssl/issues/89 + } } } - // Sanity tests for flags - [ConditionalFact(nameof(ShouldRunEngineTests))] - public static void SanityTest_EngineAny() - { - Assert.False(ShouldFailTests, "This test is supposed to fail"); - } - - [ConditionalFact(nameof(ShouldRunTpmTssTests))] - public static void SanityTest_EngineTpmTss() - { - Assert.False(ShouldFailTests, "This test is supposed to fail"); - } - - [ConditionalFact(nameof(ShouldRunAnyProviderTests))] - public static void SanityTest_AnyTpm2Provider() - { - Assert.False(ShouldFailTests, "This test is supposed to fail"); - } - - [ConditionalFact(nameof(ShouldRunProviderEcDsaTests))] - public static void SanityTest_Tpm2ProviderEcDsa() + [ConditionalFact(nameof(ShouldRunProviderRsaDecryptTests))] + public static void Provider_TPM2DecryptRsa_ExportParameters() { - Assert.False(ShouldFailTests, "This test is supposed to fail"); - } + // TPM2 OAEP support was added in the second half of 2023 therefore we only test Pkcs1 padding + // See: https://github.com/tpm2-software/tpm2-openssl/issues/89 + RSAEncryptionPadding padding = RSAEncryptionPadding.Pkcs1; + using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, TpmRsaDecryptKeyHandleUri); + using RSA rsaPri = new RSAOpenSsl(priKeyHandle); - [ConditionalFact(nameof(ShouldRunProviderEcDhTests))] - public static void SanityTest_Tpm2ProviderEcDh() - { - Assert.False(ShouldFailTests, "This test is supposed to fail"); - } + RSA rsaPub = RSA.Create(); + rsaPub.ImportParameters(rsaPri.ExportParameters(false)); - [ConditionalFact(nameof(ShouldRunProviderRsaSignTests))] - public static void SanityTest_Tpm2ProviderRsaSign() - { - Assert.False(ShouldFailTests, "This test is supposed to fail"); - } + Assert.ThrowsAny(() => rsaPri.ExportParameters(true)); - [ConditionalFact(nameof(ShouldRunProviderRsaDecryptTests))] - public static void SanityTest_Tpm2ProviderRsaDecrypt() - { - Assert.False(ShouldFailTests, "This test is supposed to fail"); + byte[] data = new byte[] { 1, 2, 3, 1, 1, 2, 3 }; + byte[] encrypted = rsaPub.Encrypt(data, padding); + Assert.NotEqual(encrypted, data); + Assert.Equal(data, rsaPri.Decrypt(encrypted, padding)); } } } diff --git a/src/libraries/System.Security.Cryptography/tests/osslplugins/test.sh b/src/libraries/System.Security.Cryptography/tests/osslplugins/test.sh index 9d61736d6c24d6..7c8cd686c31bbe 100755 --- a/src/libraries/System.Security.Cryptography/tests/osslplugins/test.sh +++ b/src/libraries/System.Security.Cryptography/tests/osslplugins/test.sh @@ -67,15 +67,6 @@ if [ -z "$DOTNET_CRYPTOGRAPHY_TESTS_TPM_RSA_DECRYPT_KEY_HANDLE" ]; then echo fi -if [ "$1" == "--self-check" ]; then - export DOTNET_CRYPTOGRAPHY_TESTS_ENSURE_FAILING=true -else - echo "INFO: To run self-check use:" - echo "INFO: ./test.sh --self-check" - echo "INFO: Expect test failures one per each category of tests (custom ENGINE, ECDSA, ECDH, RSA sign, RSA decrypt)." - echo -fi - set -e cd "$nativelibs_path" diff --git a/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h b/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h index 24baa4f68c7c89..79bbf56273bbe7 100644 --- a/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h +++ b/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h @@ -24,14 +24,6 @@ #define EVP_PKEY_RSA_PSS 912 #endif -#ifndef EVP_PKEY_ED448 -#define EVP_PKEY_ED448 1088 -#endif - -#ifndef EVP_PKEY_ED25519 -#define EVP_PKEY_ED25519 1087 -#endif - typedef struct ossl_lib_ctx_st OSSL_LIB_CTX; typedef struct ossl_param_st OSSL_PARAM; typedef struct ossl_provider_st OSSL_PROVIDER; @@ -79,7 +71,6 @@ OSSL_PARAM OSSL_PARAM_construct_end(void); OSSL_PARAM OSSL_PARAM_construct_int32(const char *key, int32_t *buf); OSSL_PARAM OSSL_PARAM_construct_octet_string(const char *key, void *buf, size_t bsize); -OSSL_PROVIDER* OSSL_PROVIDER_try_load(OSSL_LIB_CTX* , const char* name, int retain_fallbacks); void OSSL_LIB_CTX_free(OSSL_LIB_CTX*); OSSL_LIB_CTX* OSSL_LIB_CTX_new(void); OSSL_PROVIDER* OSSL_PROVIDER_load(OSSL_LIB_CTX*, const char* name); diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c index dda58f24b22b70..2e0e8efee114e2 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c @@ -106,18 +106,16 @@ int32_t CryptoNative_UpRefEvpPkey(EVP_PKEY* pkey, void* extraHandle) int32_t CryptoNative_EvpPKeyType(EVP_PKEY* key) { int32_t base_id = EVP_PKEY_get_base_id(key); - switch (base_id) { - case EVP_PKEY_RSA: - case EVP_PKEY_EC: - case EVP_PKEY_DSA: - return base_id; - case EVP_PKEY_RSA_PSS: - return EVP_PKEY_RSA; - case EVP_PKEY_ED448: - case EVP_PKEY_ED25519: - return EVP_PKEY_EC; - default: - return 0; + switch (base_id) + { + case EVP_PKEY_RSA: + case EVP_PKEY_EC: + case EVP_PKEY_DSA: + return base_id; + case EVP_PKEY_RSA_PSS: + return EVP_PKEY_RSA; + default: + return 0; } } diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdh.c b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdh.c index a64fc326b24bcc..e057afb6d00614 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdh.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdh.c @@ -50,6 +50,11 @@ int32_t CryptoNative_EvpPKeyDeriveSecretAgreement(EVP_PKEY* pkey, void* extraHan return 0; } + // When secret is non-null (which is always the case here) + // tmpSize is both in and out. + // As input it must contain the size of the secret buffer. + // If buffer is too small, the function will fail by returning 0. + // When succeeds it will store number of bytes written. int ret = EVP_PKEY_derive(ctx, secret, &tmpSize); EVP_PKEY_CTX_free(ctx); From 98d886571199bca7030832e9516513734143c8dd Mon Sep 17 00:00:00 2001 From: Krzysztof Wicher Date: Fri, 19 Jul 2024 17:26:58 +0200 Subject: [PATCH 13/17] Add back HasNoPrivateKey check on OSSL ver LT 3 --- .../pal_evp_pkey_rsa.c | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) 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 1554ab935d890c..8e0c80e6907312 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 @@ -4,8 +4,11 @@ #include "pal_evp_pkey.h" #include "pal_evp_pkey_rsa.h" #include "pal_utilities.h" +#include "openssl.h" #include +static int HasNoPrivateKey(const RSA* rsa); + EVP_PKEY* CryptoNative_EvpPKeyCreateRsa(RSA* currentKey) { assert(currentKey != NULL); @@ -132,6 +135,22 @@ int32_t CryptoNative_RsaDecrypt(EVP_PKEY* pkey, goto done; } + // This check will not work with hardware keys coming from OpenSSL providers + // because providers don't seem to set RSA_FLAG_EXT_PKEY (the tpm2 most notably) + // ENGINE-s may or may not set it. + // This is needed only on OpenSSL < 3.0, + // see: https://github.com/dotnet/runtime/issues/53345 + if (CryptoNative_OpenSslVersionNumber() < OPENSSL_VERSION_3_0_RTM) + { + const 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__); + goto done; + } + } + size_t written = Int32ToSizeT(destinationLen); if (EVP_PKEY_decrypt(ctx, destination, &written, source, Int32ToSizeT(sourceLen)) > 0) @@ -301,6 +320,22 @@ int32_t CryptoNative_RsaVerifyHash(EVP_PKEY* pkey, goto done; } + // This check will not work with hardware keys coming from OpenSSL providers + // because providers don't seem to set RSA_FLAG_EXT_PKEY (the tpm2 most notably) + // ENGINE-s may or may not set it. + // This is needed only on OpenSSL < 3.0, + // see: https://github.com/dotnet/runtime/issues/53345 + if (CryptoNative_OpenSslVersionNumber() < OPENSSL_VERSION_3_0_RTM) + { + const 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__); + goto done; + } + } + // EVP_PKEY_verify is not consistent on whether a missized hash is an error or just a mismatch. // Normalize to mismatch. if (hashLen != EVP_MD_get_size(digest)) @@ -319,3 +354,57 @@ int32_t CryptoNative_RsaVerifyHash(EVP_PKEY* pkey, return ret; } + +static int HasNoPrivateKey(const RSA* rsa) +{ + if (rsa == NULL) + return 1; + + // Shared pointer, don't free. + const RSA_METHOD* meth = RSA_get_method(rsa); + + // The method has described 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_test_flags(rsa, RSA_FLAG_EXT_PKEY) || 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; +} From 09a083408951a58e037b82b5fee3f20289c19584 Mon Sep 17 00:00:00 2001 From: Krzysztof Wicher Date: Fri, 19 Jul 2024 17:39:51 +0200 Subject: [PATCH 14/17] move check to SignHash --- .../pal_evp_pkey_rsa.c | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) 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 8e0c80e6907312..e444d8c0c57b12 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 @@ -274,6 +274,22 @@ int32_t CryptoNative_RsaSignHash(EVP_PKEY* pkey, goto done; } + // This check will not work with hardware keys coming from OpenSSL providers + // because providers don't seem to set RSA_FLAG_EXT_PKEY (the tpm2 most notably) + // ENGINE-s may or may not set it. + // This is needed only on OpenSSL < 3.0, + // see: https://github.com/dotnet/runtime/issues/53345 + if (CryptoNative_OpenSslVersionNumber() < OPENSSL_VERSION_3_0_RTM) + { + const 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__); + goto done; + } + } + size_t written = Int32ToSizeT(destinationLen); if (EVP_PKEY_sign(ctx, destination, &written, hash, Int32ToSizeT(hashLen)) > 0) @@ -320,22 +336,6 @@ int32_t CryptoNative_RsaVerifyHash(EVP_PKEY* pkey, goto done; } - // This check will not work with hardware keys coming from OpenSSL providers - // because providers don't seem to set RSA_FLAG_EXT_PKEY (the tpm2 most notably) - // ENGINE-s may or may not set it. - // This is needed only on OpenSSL < 3.0, - // see: https://github.com/dotnet/runtime/issues/53345 - if (CryptoNative_OpenSslVersionNumber() < OPENSSL_VERSION_3_0_RTM) - { - const 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__); - goto done; - } - } - // EVP_PKEY_verify is not consistent on whether a missized hash is an error or just a mismatch. // Normalize to mismatch. if (hashLen != EVP_MD_get_size(digest)) From e2f007da1729215c3ce1b681982dd372f34857c7 Mon Sep 17 00:00:00 2001 From: Krzysztof Wicher Date: Fri, 19 Jul 2024 18:56:20 +0200 Subject: [PATCH 15/17] address feedback (ThrowIfNull + switch expression) --- .../Interop.EvpPkey.Rsa.cs | 10 ++-------- .../X509Certificates/OpenSslExportProvider.cs | 20 ++++++------------- 2 files changed, 8 insertions(+), 22 deletions(-) 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 e27e75ac851304..8f56e1fa62f67c 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 @@ -137,10 +137,7 @@ internal static int RsaSignHash( ReadOnlySpan hash, Span destination) { - if (digestAlgorithm.Name == null) - { - throw new ArgumentNullException(nameof(digestAlgorithm)); - } + ArgumentNullException.ThrowIfNull(digestAlgorithm.Name, nameof(digestAlgorithm)); IntPtr digestAlgorithmPtr = Interop.Crypto.HashAlgorithmToEvp(digestAlgorithm.Name); @@ -181,10 +178,7 @@ internal static bool RsaVerifyHash( ReadOnlySpan hash, ReadOnlySpan signature) { - if (digestAlgorithm.Name == null) - { - throw new ArgumentNullException(nameof(digestAlgorithm)); - } + ArgumentNullException.ThrowIfNull(digestAlgorithm.Name, nameof(digestAlgorithm)); IntPtr digestAlgorithmPtr = Interop.Crypto.HashAlgorithmToEvp(digestAlgorithm.Name); diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslExportProvider.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslExportProvider.cs index 0119bb474438a2..b25933246132f7 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslExportProvider.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslExportProvider.cs @@ -22,7 +22,6 @@ protected override byte[] ExportPkcs8( ICertificatePalCore certificatePal, ReadOnlySpan password) { - AsymmetricAlgorithm alg; SafeEvpPKeyHandle? privateKey = ((OpenSslX509CertificateReader)certificatePal).PrivateKeyHandle; if (privateKey == null) @@ -32,20 +31,13 @@ protected override byte[] ExportPkcs8( Interop.Crypto.EvpAlgorithmId evpAlgId = Interop.Crypto.EvpPKeyType(privateKey); - switch (evpAlgId) + AsymmetricAlgorithm alg = evpAlgId switch { - case Interop.Crypto.EvpAlgorithmId.RSA: - alg = new RSAOpenSsl(privateKey); - break; - case Interop.Crypto.EvpAlgorithmId.ECC: - alg = new ECDsaOpenSsl(privateKey); - break; - case Interop.Crypto.EvpAlgorithmId.DSA: - alg = new DSAOpenSsl(privateKey); - break; - default: - throw new CryptographicException(SR.Cryptography_InvalidHandle); - } + Interop.Crypto.EvpAlgorithmId.RSA => new RSAOpenSsl(privateKey), + Interop.Crypto.EvpAlgorithmId.ECC => new ECDsaOpenSsl(privateKey), + Interop.Crypto.EvpAlgorithmId.DSA => new DSAOpenSsl(privateKey), + _ => throw new CryptographicException(SR.Cryptography_InvalidHandle), + }; return alg.ExportEncryptedPkcs8PrivateKey(password, s_windowsPbe); } From 6498754ca4f497017c6f7f937edb75b3224699dc Mon Sep 17 00:00:00 2001 From: Krzysztof Wicher Date: Fri, 19 Jul 2024 21:28:38 +0200 Subject: [PATCH 16/17] update XML doc --- .../Cryptography/SafeEvpPKeyHandle.OpenSsl.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SafeEvpPKeyHandle.OpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SafeEvpPKeyHandle.OpenSsl.cs index 64600e976a1a10..073e6b0fec5dda 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SafeEvpPKeyHandle.OpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SafeEvpPKeyHandle.OpenSsl.cs @@ -198,10 +198,10 @@ public static SafeEvpPKeyHandle OpenPublicKeyFromEngine(string engineName, strin /// Open a named public key using a named OSSL_PROVIDER. /// /// - /// The name of the provider to process the key open request. + /// The name of the OSSL_PROVIDER to process the key open request. /// /// - /// The provider-assigned URI of the key to open. + /// The URI assigned by the OSSL_PROVIDER of the key to open. /// /// /// The opened key. @@ -213,19 +213,19 @@ public static SafeEvpPKeyHandle OpenPublicKeyFromEngine(string engineName, strin /// or is the empty string. /// /// - /// the key could not be opened via the specified provider. + /// the key could not be opened via the specified named OSSL_PROVIDER. /// /// /// /// Both and must be trusted inputs. /// /// - /// This operation will fail if OpenSSL cannot successfully load the named provider, - /// or if the named provider cannot load the named key. + /// This operation will fail if OpenSSL cannot successfully load the named OSSL_PROVIDER, + /// or if the named OSSL_PROVIDER cannot load the named key. /// /// /// The syntax for is determined by each individual - /// provider. + /// named OSSL_PROVIDER. /// /// [UnsupportedOSPlatform("android")] From a925f20cae352c555efb8321bd0def6ffcbaaddc Mon Sep 17 00:00:00 2001 From: Krzysztof Wicher Date: Fri, 19 Jul 2024 23:19:56 +0200 Subject: [PATCH 17/17] attempt to fix ossl 1.0.2 build by moving ifndef to opensslshim.h --- .../libs/System.Security.Cryptography.Native/opensslshim.h | 4 ++++ .../libs/System.Security.Cryptography.Native/osslcompat_30.h | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h index 01497272682cd8..c3f11cdb9e2162 100644 --- a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h +++ b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h @@ -91,6 +91,10 @@ void ERR_put_error(int32_t lib, int32_t func, int32_t reason, const char* file, #define RSA_PSS_SALTLEN_DIGEST -1 #endif +#ifndef EVP_PKEY_RSA_PSS +#define EVP_PKEY_RSA_PSS 912 +#endif + // ERR_R_UNSUPPORTED was introduced in OpenSSL 3. We need it for building with older OpenSSLs. // Add a static assert so we know if OpenSSL changes the value. #ifndef ERR_R_UNSUPPORTED diff --git a/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h b/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h index 79bbf56273bbe7..3fbe98235dff6d 100644 --- a/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h +++ b/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h @@ -20,10 +20,6 @@ #define OSSL_STORE_INFO_PKEY 4 #define OSSL_STORE_INFO_PUBKEY 3 -#ifndef EVP_PKEY_RSA_PSS -#define EVP_PKEY_RSA_PSS 912 -#endif - typedef struct ossl_lib_ctx_st OSSL_LIB_CTX; typedef struct ossl_param_st OSSL_PARAM; typedef struct ossl_provider_st OSSL_PROVIDER;