From f26dbf0e4d7258c1b8cd6920d93dd291a86e6f1f Mon Sep 17 00:00:00 2001 From: Krzysztof Wicher Date: Sat, 20 Jul 2024 12:16:53 +0200 Subject: [PATCH] OpenSSL providers support (#104961) * OpenSSL providers support * Address self feedback (Lazy+leak) * Attempt to fix EVP_PKEY_CTX_new_from_pkey errors * update osslcompat_30.h with EVP_PKEY types * properly ifdef extraHandle code * fix: unused parameter extraHandle when OSSL 3 not available * bugfixes, feedback * ifndef some defines in compat layer, remove CryptoNative_EvpPkeyExtraHandleDestroy * change style to match old RsaSignHash * XML doc + extra test case * remote OSSL_STORE_open usage and revert comment on the DuplicateKeyHandle * Address feedback * Add back HasNoPrivateKey check on OSSL ver LT 3 * move check to SignHash * address feedback (ThrowIfNull + switch expression) * update XML doc * attempt to fix ossl 1.0.2 build by moving ifndef to opensslshim.h --- .../Interop.EcDsa.cs | 61 ---- .../Interop.EvpPkey.EcDsa.cs | 81 +++++ .../Interop.EvpPkey.EcKey.cs | 17 +- .../Interop.EvpPkey.Ecdh.cs | 33 +- .../Interop.EvpPkey.Rsa.cs | 24 +- .../Interop.EvpPkey.cs | 79 +++-- .../SafeHandles/SafeEvpPkeyCtxHandle.Unix.cs | 30 -- .../ECDiffieHellmanOpenSsl.Derive.cs | 95 +++--- .../Cryptography/ECDiffieHellmanOpenSsl.cs | 66 ++-- .../ECDiffieHellmanOpenSslPublicKey.cs | 20 +- .../Security/Cryptography/ECDsaOpenSsl.cs | 159 ++++----- .../System/Security/Cryptography/ECOpenSsl.cs | 62 ++-- .../Security/Cryptography/RSAOpenSsl.cs | 93 ++---- .../ref/System.Security.Cryptography.cs | 6 + .../src/System.Security.Cryptography.csproj | 12 +- .../Cryptography/ECDiffieHellmanOpenSsl.cs | 45 +-- .../Security/Cryptography/ECDsaOpenSsl.cs | 44 +-- .../Cryptography/OpenSsl.NotSupported.cs | 8 + .../Security/Cryptography/RSAOpenSsl.cs | 11 +- .../Cryptography/SafeEvpPKeyHandle.OpenSsl.cs | 70 +++- .../X509Certificates/OpenSslExportProvider.cs | 35 +- .../tests/OpenSslNamedKeysTests.manual.cs | 307 ++++++++++++++++-- .../tests/osslplugins/README.md | 64 +++- .../tests/osslplugins/test.sh | 54 ++- .../CMakeLists.txt | 2 +- .../entrypoints.c | 14 +- .../opensslshim.h | 38 ++- .../osslcompat_30.h | 28 +- .../pal_ecdsa.c | 34 -- .../pal_ecdsa.h | 29 -- .../pal_evp_pkey.c | 268 ++++++++++++--- .../pal_evp_pkey.h | 38 ++- .../pal_evp_pkey_ecdh.c | 60 ++-- .../pal_evp_pkey_ecdh.h | 6 +- .../pal_evp_pkey_ecdsa.c | 77 +++++ .../pal_evp_pkey_ecdsa.h | 32 ++ .../pal_evp_pkey_rsa.c | 28 +- .../pal_evp_pkey_rsa.h | 5 +- .../pal_ssl.c | 2 +- 39 files changed, 1429 insertions(+), 708 deletions(-) delete mode 100644 src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.cs 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 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 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.EcDsa.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.cs deleted file mode 100644 index 7d994d96402dd..0000000000000 --- 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.EcDsa.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.EcDsa.cs new file mode 100644 index 0000000000000..2fbbca528530b --- /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.EcKey.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.EcKey.cs index 6ef629c8e0d6e..bd89f4e3bee21 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 5308043c8e80e..e85795bde5126 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,32 +11,35 @@ internal static partial class Interop { internal static partial class Crypto { - [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyCtxCreate")] - internal static partial SafeEvpPKeyCtxHandle EvpPKeyCtxCreate(SafeEvpPKeyHandle pkey, SafeEvpPKeyHandle peerkey, out uint secretLength); - [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyDeriveSecretAgreement")] private static partial int EvpPKeyDeriveSecretAgreement( + SafeEvpPKeyHandle pkey, + IntPtr extraHandle, + SafeEvpPKeyHandle peerKey, ref byte secret, - uint secretLength, - SafeEvpPKeyCtxHandle ctx); - - [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyCtxDestroy")] - internal static partial void EvpPKeyCtxDestroy(IntPtr ctx); + uint secretLength); - 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); + Debug.Assert(pkey != null); + Debug.Assert(!pkey.IsInvalid); + Debug.Assert(peerKey != null); + Debug.Assert(!peerKey.IsInvalid); - int ret = EvpPKeyDeriveSecretAgreement( + 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 9fe32f23e42b0..8f56e1fa62f67 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, @@ -118,6 +122,7 @@ ref MemoryMarshal.GetReference(destination), [LibraryImport(Libraries.CryptoNative)] private static partial int CryptoNative_RsaSignHash( SafeEvpPKeyHandle pkey, + IntPtr extraHandle, RSASignaturePaddingMode paddingMode, IntPtr digestAlgorithm, ref byte hash, @@ -128,14 +133,19 @@ private static partial int CryptoNative_RsaSignHash( internal static int RsaSignHash( SafeEvpPKeyHandle pkey, RSASignaturePaddingMode paddingMode, - IntPtr digestAlgorithm, + HashAlgorithmName digestAlgorithm, ReadOnlySpan hash, Span destination) { + ArgumentNullException.ThrowIfNull(digestAlgorithm.Name, nameof(digestAlgorithm)); + + IntPtr digestAlgorithmPtr = Interop.Crypto.HashAlgorithmToEvp(digestAlgorithm.Name); + int written = CryptoNative_RsaSignHash( pkey, + pkey.ExtraHandle, paddingMode, - digestAlgorithm, + digestAlgorithmPtr, ref MemoryMarshal.GetReference(hash), hash.Length, ref MemoryMarshal.GetReference(destination), @@ -153,6 +163,7 @@ ref MemoryMarshal.GetReference(destination), [LibraryImport(Libraries.CryptoNative)] private static partial int CryptoNative_RsaVerifyHash( SafeEvpPKeyHandle pkey, + IntPtr extraHandle, RSASignaturePaddingMode paddingMode, IntPtr digestAlgorithm, ref byte hash, @@ -163,14 +174,19 @@ private static partial int CryptoNative_RsaVerifyHash( internal static bool RsaVerifyHash( SafeEvpPKeyHandle pkey, RSASignaturePaddingMode paddingMode, - IntPtr digestAlgorithm, + HashAlgorithmName digestAlgorithm, ReadOnlySpan hash, ReadOnlySpan signature) { + ArgumentNullException.ThrowIfNull(digestAlgorithm.Name, nameof(digestAlgorithm)); + + IntPtr digestAlgorithmPtr = Interop.Crypto.HashAlgorithmToEvp(digestAlgorithm.Name); + int ret = CryptoNative_RsaVerifyHash( pkey, + pkey.ExtraHandle, paddingMode, - digestAlgorithm, + digestAlgorithmPtr, ref MemoryMarshal.GetReference(hash), hash.Length, ref MemoryMarshal.GetReference(signature), 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 4e8659b5653b7..68f4f8cb6433f 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,39 @@ 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) + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyBits")] + internal static partial int EvpPKeyBits(SafeEvpPKeyHandle pkey); + + internal static int GetEvpPKeySizeBytes(SafeEvpPKeyHandle pkey) { - SafeEvpPKeyHandle pkey = CryptoNative_EvpPKeyDuplicate( - currentKey, - algorithmId); + // 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 (pkey.IsInvalid) + if (keySizeBits <= 0) { - pkey.Dispose(); - throw CreateOpenSslCryptographicException(); + Debug.Fail($"EVP_PKEY_bits returned non-positive value: {keySizeBits}"); + throw new CryptographicException(); } - return pkey; + return (keySizeBits + 7) / 8; } - [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPkeyDestroy")] - internal static partial void EvpPkeyDestroy(IntPtr pkey); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_UpRefEvpPkey")] + private static partial int UpRefEvpPkey(SafeEvpPKeyHandle handle, IntPtr extraHandle); - [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeySize")] - internal static partial int EvpPKeySize(SafeEvpPKeyHandle pkey); + internal static int UpRefEvpPkey(SafeEvpPKeyHandle handle) + { + return UpRefEvpPkey(handle, handle.ExtraHandle); + } - [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 +277,42 @@ 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 = IntPtr.Zero; + + try + { + 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); + } + + throw; + } + } + 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 deleted file mode 100644 index d64f135ca0f72..0000000000000 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpPkeyCtxHandle.Unix.cs +++ /dev/null @@ -1,30 +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; - -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 f6fc78762e043..32a463bd31caf 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.Value)) + { + thisIsNamed = Interop.Crypto.EcKeyHasCurveName(ecKey); + } + ECDiffieHellmanOpenSslPublicKey? otherKey = otherPartyPublicKey as ECDiffieHellmanOpenSslPublicKey; bool disposeOtherKey = false; @@ -109,10 +112,15 @@ public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPa bool otherIsNamed = otherKey.HasCurveName; - SafeEvpPKeyHandle? ourKey = null; + // We need to always duplicate handle in case this operation is done by multiple threads and one of them disposes the handle + 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 { @@ -123,79 +131,64 @@ 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.CreateEvpPKeyHandle(); } } 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.CreateEvpPKeyHandle(); + disposeOurKey = true; + } } - - theirKey = otherKey.DuplicateKeyHandle(); - } - - using (SafeEvpPKeyCtxHandle ctx = Interop.Crypto.EvpPKeyCtxCreate(ourKey, theirKey, out uint secretLengthU)) - { - if (ctx == null || ctx.IsInvalid || secretLengthU == 0 || secretLengthU > int.MaxValue) + catch (CryptographicException) { - throw Interop.Crypto.CreateOpenSslCryptographicException(); + // In both cases of failure we'll report lack of private key + throw new CryptographicException(SR.Cryptography_CSP_NoPrivateKey); } - secretLength = (int)secretLengthU; - - // 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]; - } + theirKey = otherKey.DuplicateKeyHandle(); + } - Interop.Crypto.EvpPKeyDeriveSecretAgreement(ctx, secret); + int written = Interop.Crypto.EvpPKeyDeriveSecretAgreement(ourKey, theirKey, secret); + secret = secret.Slice(0, written); - 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(); - ourKey?.Dispose(); if (disposeOtherKey) { otherKey.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 c394d1fbf0b54..eff1ba80a346b 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 Lazy? _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 = new Lazy(ECOpenSsl.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 = new Lazy(() => ECOpenSsl.GenerateECKey(keySize)); } public override KeySizes[] LegalKeySizes => s_defaultKeySizes.CloneKeySizesArray(); @@ -51,7 +51,7 @@ protected override void Dispose(bool disposing) { if (disposing) { - _key?.Dispose(); + FreeKey(); _key = null; } @@ -75,15 +75,18 @@ public override int KeySize base.KeySize = value; ThrowIfDisposed(); - _key.Dispose(); - _key = new ECOpenSsl(this); + FreeKey(); + _key = new Lazy(ECOpenSsl.GenerateECKey(value)); } } public override void GenerateKey(ECCurve curve) { ThrowIfDisposed(); - KeySizeValue = _key.GenerateKey(curve); + + FreeKey(); + _key = new Lazy(ECOpenSsl.GenerateECKey(curve, out int keySizeValue)); + KeySizeValue = keySizeValue; } public override ECDiffieHellmanPublicKey PublicKey @@ -92,24 +95,38 @@ public override ECDiffieHellmanPublicKey PublicKey { ThrowIfDisposed(); - using (SafeEvpPKeyHandle handle = _key.UpRefKeyHandle()) - { - return new ECDiffieHellmanOpenSslPublicKey(handle); - } + // This may generate the key + return new ECDiffieHellmanOpenSslPublicKey(_key.Value); } } public override void ImportParameters(ECParameters parameters) { ThrowIfDisposed(); - KeySizeValue = _key.ImportParameters(parameters); + FreeKey(); + _key = new Lazy(ECOpenSsl.ImportECKey(parameters, out int keySize)); + KeySizeValue = keySize; + } + + public override ECParameters ExportExplicitParameters(bool includePrivateParameters) + { + ThrowIfDisposed(); + + using (SafeEcKeyHandle ecKey = Interop.Crypto.EvpPkeyGetEcKey(_key.Value)) + { + 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.Value)) + { + return ECOpenSsl.ExportParameters(ecKey, includePrivateParameters); + } + } public override void ImportEncryptedPkcs8PrivateKey( ReadOnlySpan passwordBytes, @@ -129,16 +146,19 @@ public override void ImportEncryptedPkcs8PrivateKey( base.ImportEncryptedPkcs8PrivateKey(password, source, out bytesRead); } - [MemberNotNull(nameof(_key))] - private void ThrowIfDisposed() + private void FreeKey() { - ObjectDisposedException.ThrowIf(_key is null, this); + if (_key != null && _key.IsValueCreated) + { + SafeEvpPKeyHandle handle = _key.Value; + handle?.Dispose(); + } } - private SafeEcKeyHandle GetKey() + [MemberNotNull(nameof(_key))] + private void ThrowIfDisposed() { - ThrowIfDisposed(); - return _key.Value; + ObjectDisposedException.ThrowIf(_key is null, this); } 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 ee3a517b692ab..2dfbaa92a030c 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 68a61758b670c..b683d88442c2c 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(ECOpenSsl.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,13 +82,13 @@ 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); + SafeEvpPKeyHandle key = GetKey(); + + 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; @@ -112,48 +110,41 @@ 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) { - 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; } - ReadOnlySpan derSignature = SignHash(hash, signDestination, signatureLength, key); + 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) { - 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); + SafeEvpPKeyHandle key = GetKey(); - if (destination == signDestination) - { - bytesWritten = derSignature.Length; - return true; - } + // 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(derSignature, destination, out bytesWritten); + return Helpers.TryCopyToDestination(tmpDerSignature, destination, out bytesWritten); } else { @@ -161,33 +152,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 +206,19 @@ protected override bool VerifyHashCore( signatureFormat.ToString()); } - SafeEcKeyHandle key = _key.Value; - int verifyResult = Interop.Crypto.EcDsaVerify(hash, toVerify, key); - return verifyResult == 1; + SafeEvpPKeyHandle key = GetKey(); + + return Interop.Crypto.EcDsaVerifyHash( + key, + hash, + toVerify); } protected override void Dispose(bool disposing) { if (disposing) { - _key?.Dispose(); + FreeKey(); _key = null; } @@ -273,38 +240,73 @@ 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(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. - ForceSetKeySize(_key.KeySize); + ForceSetKeySize(keySize); } public override void ImportParameters(ECParameters parameters) { ThrowIfDisposed(); - _key.ImportParameters(parameters); - ForceSetKeySize(_key.KeySize); + + FreeKey(); + _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. + 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 ECOpenSsl.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 +327,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 29c5d8c944a24..c3464d0f30c10 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 CreateEvpPKeyHandle() { 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"); @@ -134,5 +114,33 @@ private void FreeKey() _key = null!; } } + + internal static SafeEvpPKeyHandle GenerateECKey(int keySize) + { + 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 ImportECKeyCore(new ECOpenSsl(curve), out keySize); + } + + internal static SafeEvpPKeyHandle ImportECKey(ECParameters parameters, out int keySize) + { + return ImportECKeyCore(new ECOpenSsl(parameters), out keySize); + } + + // Note: This method takes ownership of ecOpenSsl and disposes it + private static SafeEvpPKeyHandle ImportECKeyCore(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 7e6efbb2da57a..96867e0f5e9d9 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 = Interop.Crypto.GetEvpPKeySizeBytes(key); Span destination = default; byte[] buf = CryptoPool.Rent(rsaSize); @@ -116,11 +114,12 @@ 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. + // 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 so long as the buffer can hold the final output. + // Windows behavior of succeeding as long as the buffer can hold the final output. + int keySizeBytes = Interop.Crypto.GetEvpPKeySizeBytes(key); + if (destination.Length < keySizeBytes) { // RSA up through 4096 bits use a stackalloc @@ -174,7 +173,7 @@ private static int Decrypt( // Caller should have already checked this. Debug.Assert(!key.IsInvalid); - int rsaSize = Interop.Crypto.EvpPKeySize(key); + int rsaSize = Interop.Crypto.GetEvpPKeySizeBytes(key); if (data.Length != rsaSize) { @@ -183,7 +182,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. destination.Length: {destination.Length}, needed: {rsaSize}"); throw new CryptographicException(); } @@ -211,7 +210,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[Interop.Crypto.GetEvpPKeySizeBytes(key)]; bool encrypted = TryEncrypt( key, @@ -232,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); } @@ -246,7 +245,7 @@ private static bool TryEncrypt( RSAEncryptionPadding padding, out int bytesWritten) { - int rsaSize = Interop.Crypto.EvpPKeySize(key); + int rsaSize = Interop.Crypto.GetEvpPKeySizeBytes(key); if (destination.Length < rsaSize) { @@ -661,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(BitsPerByte * Interop.Crypto.EvpPKeySize(newKey)); + ForceSetKeySize(Interop.Crypto.EvpPKeyBits(newKey)); } private static void ValidateParameters(ref RSAParameters parameters) @@ -731,20 +730,20 @@ 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)) + SafeEvpPKeyHandle key = GetKey(); + int bytesRequired = Interop.Crypto.GetEvpPKeySizeBytes(key); + byte[] signature = new byte[bytesRequired]; + + int written = Interop.Crypto.RsaSignHash(key, padding.Mode, hashAlgorithm, hash, signature); + + if (written != signature.Length) { - Debug.Fail("TrySignHash should not return false in allocation mode"); + Debug.Fail($"RsaSignHash behaved unexpectedly: {nameof(written)}=={written}, {nameof(signature.Length)}=={signature.Length}"); throw new CryptographicException(); } - Debug.Assert(signature != null); return signature; } @@ -757,55 +756,19 @@ public override bool TrySignHash( { ArgumentException.ThrowIfNullOrEmpty(hashAlgorithm.Name, nameof(hashAlgorithm)); ArgumentNullException.ThrowIfNull(padding); + ThrowIfDisposed(); - bool ret = TrySignHash( - hash, - destination, - hashAlgorithm, - padding, - false, - out bytesWritten, - out byte[]? alloced); - - Debug.Assert(alloced == null); - return ret; - } - - private bool TrySignHash( - ReadOnlySpan hash, - Span destination, - HashAlgorithmName hashAlgorithm, - RSASignaturePadding padding, - bool allocateSignature, - out int bytesWritten, - out byte[]? signature) - { - Debug.Assert(!string.IsNullOrEmpty(hashAlgorithm.Name)); - Debug.Assert(padding != null); - ValidatePadding(padding); - - signature = null; - - IntPtr digestAlgorithm = Interop.Crypto.HashAlgorithmToEvp(hashAlgorithm.Name); SafeEvpPKeyHandle key = GetKey(); - int bytesRequired = Interop.Crypto.EvpPKeySize(key); + int bytesRequired = Interop.Crypto.GetEvpPKeySizeBytes(key); - if (allocateSignature) - { - Debug.Assert(destination.Length == 0); - signature = new byte[bytesRequired]; - destination = signature; - } - else if (destination.Length < bytesRequired) + if (destination.Length < bytesRequired) { bytesWritten = 0; return false; } - int written = Interop.Crypto.RsaSignHash(key, padding.Mode, digestAlgorithm, hash, destination); - Debug.Assert(written == bytesRequired); - bytesWritten = written; - + bytesWritten = Interop.Crypto.RsaSignHash(key, padding.Mode, hashAlgorithm, hash, destination); + Debug.Assert(bytesWritten == bytesRequired); return true; } @@ -825,14 +788,14 @@ public override bool VerifyHash(ReadOnlySpan hash, ReadOnlySpan sign { ArgumentException.ThrowIfNullOrEmpty(hashAlgorithm.Name, nameof(hashAlgorithm)); ValidatePadding(padding); + ThrowIfDisposed(); - IntPtr digestAlgorithm = Interop.Crypto.HashAlgorithmToEvp(hashAlgorithm.Name); SafeEvpPKeyHandle key = GetKey(); return Interop.Crypto.RsaVerifyHash( key, padding.Mode, - digestAlgorithm, + hashAlgorithm, hash, signature); } 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 11409ac9857c1..9913bf2a3ee49 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 b9f0ce5317f7f..adf842fe363e0 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" /> - + - - - + (pkeyHandle.DuplicateHandle()); + KeySizeValue = Interop.Crypto.EvpPKeyBits(_key.Value); } /// @@ -63,9 +61,14 @@ 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; + + using (SafeEcKeyHandle ecKeyHandle = SafeEcKeyHandle.DuplicateHandle(handle)) + { + // CreateEvpPkeyFromEcKey already uprefs so nothing else to do + _key = new Lazy(Interop.Crypto.CreateEvpPkeyFromEcKey(ecKeyHandle)); + } + + KeySizeValue = Interop.Crypto.EvpPKeyBits(_key.Value); } /// @@ -75,26 +78,8 @@ public ECDiffieHellmanOpenSsl(IntPtr handle) /// A SafeHandle for the EC_KEY 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; - } + 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 0887f0a9fb8a6..b8318f56d2ff5 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 @@ -30,16 +30,14 @@ 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) + + if (Interop.Crypto.EvpPKeyType(pkeyHandle) != Interop.Crypto.EvpAlgorithmId.ECC) { - key.Dispose(); - throw Interop.Crypto.CreateOpenSslCryptographicException(); + throw new CryptographicException(SR.Cryptography_OpenInvalidHandle); } - _key = new ECOpenSsl(key); - KeySizeValue = _key.KeySize; + _key = new Lazy(pkeyHandle.DuplicateHandle()); + ForceSetKeySize(Interop.Crypto.EvpPKeyBits(pkeyHandle)); } /// @@ -63,9 +61,14 @@ public ECDsaOpenSsl(IntPtr handle) throw new ArgumentException(SR.Cryptography_OpenInvalidHandle, nameof(handle)); ThrowIfNotSupported(); - SafeEcKeyHandle ecKeyHandle = SafeEcKeyHandle.DuplicateHandle(handle); - _key = new ECOpenSsl(ecKeyHandle); - KeySizeValue = _key.KeySize; + + using (SafeEcKeyHandle ecKeyHandle = SafeEcKeyHandle.DuplicateHandle(handle)) + { + // CreateEvpPkeyFromEcKey already uprefs so nothing else to do + _key = new Lazy(Interop.Crypto.CreateEvpPkeyFromEcKey(ecKeyHandle)); + } + + ForceSetKeySize(Interop.Crypto.EvpPKeyBits(_key.Value)); } /// @@ -76,26 +79,7 @@ public ECDsaOpenSsl(IntPtr handle) 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 cbbfc9912d2e1..19d633684aa4f 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 60fdd0ab1d61e..c180bd98c36f0 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,13 @@ public RSAOpenSsl(SafeEvpPKeyHandle pkeyHandle) throw new ArgumentException(SR.Cryptography_OpenInvalidHandle, nameof(pkeyHandle)); ThrowIfNotSupported(); - SafeEvpPKeyHandle newKey = Interop.Crypto.EvpPKeyDuplicate( - pkeyHandle, - Interop.Crypto.EvpAlgorithmId.RSA); + if (Interop.Crypto.EvpPKeyType(pkeyHandle) != Interop.Crypto.EvpAlgorithmId.RSA) + { + throw new CryptographicException(SR.Cryptography_OpenInvalidHandle); + } + + SafeEvpPKeyHandle newKey = pkeyHandle.DuplicateHandle(); SetKey(newKey); } @@ -87,7 +90,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 5cebb67ab9d71..073e6b0fec5dd 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 @@ -11,6 +11,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 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; } + [UnsupportedOSPlatform("android")] [UnsupportedOSPlatform("browser")] [UnsupportedOSPlatform("ios")] @@ -31,9 +37,17 @@ 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; } @@ -70,6 +84,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 +193,57 @@ public static SafeEvpPKeyHandle OpenPublicKeyFromEngine(string engineName, strin return Interop.Crypto.LoadPublicKeyFromEngine(engineName, keyId); } + + /// + /// Open a named public key using a named OSSL_PROVIDER. + /// + /// + /// The name of the OSSL_PROVIDER to process the key open request. + /// + /// + /// The URI assigned by the OSSL_PROVIDER of the key to open. + /// + /// + /// The opened key. + /// + /// + /// or is . + /// + /// + /// or is the empty string. + /// + /// + /// 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 OSSL_PROVIDER, + /// or if the named OSSL_PROVIDER cannot load the named key. + /// + /// + /// The syntax for is determined by each individual + /// named OSSL_PROVIDER. + /// + /// + [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); + } } } 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 355dbc303865d..b25933246132f 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,23 @@ protected override byte[] ExportPkcs8( ICertificatePalCore certificatePal, ReadOnlySpan password) { - AsymmetricAlgorithm? alg = null; SafeEvpPKeyHandle? privateKey = ((OpenSslX509CertificateReader)certificatePal).PrivateKeyHandle; - try - { - alg = new RSAOpenSsl(privateKey!); - } - catch (CryptographicException) + if (privateKey == null) { + throw new CryptographicException(SR.Cryptography_OpenInvalidHandle); } - if (alg == null) - { - try - { - alg = new ECDsaOpenSsl(privateKey!); - } - catch (CryptographicException) - { - } - } + Interop.Crypto.EvpAlgorithmId evpAlgId = Interop.Crypto.EvpPKeyType(privateKey); - if (alg == null) + AsymmetricAlgorithm alg = evpAlgId switch { - try - { - alg = new DSAOpenSsl(privateKey!); - } - catch (CryptographicException) - { - } - } + 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), + }; - 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 06cd2ed2721a1..b5aab83a1aa3b 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,55 @@ 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 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 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 +90,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 +101,49 @@ 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 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 NonExistingEngine() + public static void EmptyUriThroughNullCharacter() { - Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPrivateKeyFromEngine(NonExistingEngineName, TestEngineKeyId)); - Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPublicKeyFromEngine(NonExistingEngineName, TestEngineKeyId)); + Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenKeyFromProvider("default", "\0")); } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.OpenSslPresentOnSystem))] - public static void NonExistingKey() + public static void Engine_NonExisting() { - Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPrivateKeyFromEngine(TestEngineName, NonExistingEngineKeyName)); - Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPublicKeyFromEngine(TestEngineName, NonExistingEngineKeyName)); + Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPrivateKeyFromEngine(NonExistingEngineOrProviderKeyName, TestEngineKeyId)); + Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPublicKeyFromEngine(NonExistingEngineOrProviderKeyName, TestEngineKeyId)); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.OpenSslPresentOnSystem))] + public static void Provider_NonExisting() + { + 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 +222,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 +230,226 @@ 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(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(); + + ECParameters aliceECParams = alicePri.ExportParameters(includePrivateParameters: false); + alicePub.ImportParameters(aliceECParams); + + using ECDiffieHellman bobPri = ECDiffieHellman.Create(aliceECParams.Curve); + + byte[] sharedKeyFromAlice; + using (ECDiffieHellmanPublicKey bobPublic = bobPri.PublicKey) + { + sharedKeyFromAlice = alicePri.DeriveRawSecretAgreement(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.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))] + [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)); + } + } + + [ConditionalTheory(nameof(ShouldRunProviderRsaDecryptTests))] + [InlineData(RSAEncryptionPaddingMode.Pkcs1)] + [InlineData(RSAEncryptionPaddingMode.Oaep)] + public static void Provider_TPM2DecryptRsa(RSAEncryptionPaddingMode mode) + { + 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(); + 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); + + 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 + } + } + } + + [ConditionalFact(nameof(ShouldRunProviderRsaDecryptTests))] + public static void Provider_TPM2DecryptRsa_ExportParameters() + { + // 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); + + RSA rsaPub = RSA.Create(); + rsaPub.ImportParameters(rsaPri.ExportParameters(false)); + + Assert.ThrowsAny(() => rsaPri.ExportParameters(true)); + + 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/README.md b/src/libraries/System.Security.Cryptography/tests/osslplugins/README.md index 04d3cdae5fd99..37f27a7b17ee9 100644 --- a/src/libraries/System.Security.Cryptography/tests/osslplugins/README.md +++ b/src/libraries/System.Security.Cryptography/tests/osslplugins/README.md @@ -3,6 +3,7 @@ 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 +13,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 +115,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 +149,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 +172,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 +195,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 +206,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 +226,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 c13dd079319a5..7c8cd686c31bb 100755 --- a/src/libraries/System.Security.Cryptography/tests/osslplugins/test.sh +++ b/src/libraries/System.Security.Cryptography/tests/osslplugins/test.sh @@ -17,23 +17,54 @@ 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 [ "$1" == "--self-check" ]; then - export DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_ENSURE_FAILING=true -else - echo "INFO: To run self-check use:" - echo "INFO: ./test.sh --self-check" - echo "INFO: Expect two test failures." +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 set -e @@ -45,4 +76,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 c2808d3fd7646..8dee7f91e9054 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 @@ -36,6 +35,7 @@ set(NATIVECRYPTO_SOURCES pal_evp_pkey.c pal_evp_pkey_dsa.c pal_evp_pkey_ecdh.c + pal_evp_pkey_ecdsa.c pal_evp_pkey_eckey.c pal_evp_pkey_rsa.c pal_hmac.c diff --git a/src/native/libs/System.Security.Cryptography.Native/entrypoints.c b/src/native/libs/System.Security.Cryptography.Native/entrypoints.c index fd9c808597db2..480fba6e0cc98 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" @@ -19,6 +18,7 @@ #include "pal_evp_pkey.h" #include "pal_evp_pkey_dsa.h" #include "pal_evp_pkey_ecdh.h" +#include "pal_evp_pkey_ecdsa.h" #include "pal_evp_pkey_eckey.h" #include "pal_evp_pkey_rsa.h" #include "pal_hmac.h" @@ -70,9 +70,8 @@ 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_EcDsaSignHash) + DllImportEntry(CryptoNative_EcDsaVerifyHash) DllImportEntry(CryptoNative_EcKeyCreateByExplicitParameters) DllImportEntry(CryptoNative_EcKeyCreateByKeyParameters) DllImportEntry(CryptoNative_EcKeyCreateByOid) @@ -163,16 +162,13 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_EvpMdSize) DllImportEntry(CryptoNative_EvpPkeyCreate) DllImportEntry(CryptoNative_EvpPKeyCreateRsa) - DllImportEntry(CryptoNative_EvpPKeyCtxCreate) - 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 +226,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) @@ -259,6 +256,7 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_RsaSignHash) DllImportEntry(CryptoNative_RsaVerifyHash) 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 3541c7558484e..c3f11cdb9e216 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 @@ -90,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 @@ -430,6 +435,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) \ @@ -445,7 +451,7 @@ 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) \ REQUIRED_FUNCTION(EVP_PKEY_get1_EC_KEY) \ @@ -525,7 +531,19 @@ 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_ex) \ LIGHTUP_FUNCTION(OSSL_PARAM_construct_octet_string) \ LIGHTUP_FUNCTION(OSSL_PARAM_construct_int32) \ LIGHTUP_FUNCTION(OSSL_PARAM_construct_end) \ @@ -977,7 +995,7 @@ 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 #define EVP_PKEY_get1_EC_KEY EVP_PKEY_get1_EC_KEY_ptr @@ -985,6 +1003,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 +1077,19 @@ 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_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 @@ -1332,10 +1363,9 @@ 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 - #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 78b626b1c3809..3fbe98235dff6 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 { @@ -53,11 +59,27 @@ 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); 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_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 43792d35b6ea5..0000000000000 --- 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 02cc7a587463e..0000000000000 --- 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 dea4f277b8969..2e0e8efee114e 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,91 +3,121 @@ #include #include "pal_evp_pkey.h" +#include "pal_utilities.h" +#include "pal_atomic.h" -EVP_PKEY* CryptoNative_EvpPkeyCreate(void) -{ - ERR_clear_error(); - return EVP_PKEY_new(); -} +#ifdef NEED_OPENSSL_3_0 +c_static_assert(OSSL_STORE_INFO_PKEY == 4); +c_static_assert(OSSL_STORE_INFO_PUBKEY == 3); -EVP_PKEY* CryptoNative_EvpPKeyDuplicate(EVP_PKEY* currentKey, int32_t algId) +struct EvpPKeyExtraHandle_st { - assert(currentKey != NULL); + atomic_int refCount; + OSSL_LIB_CTX* libCtx; + OSSL_PROVIDER* prov; +}; - ERR_clear_error(); +typedef struct EvpPKeyExtraHandle_st EvpPKeyExtraHandle; - int currentAlgId = EVP_PKEY_get_base_id(currentKey); +#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) +{ + int count = --handle->refCount; + assert(count >= 0); - if (algId != NID_undef && algId != currentAlgId) + if (count == 0) { - ERR_put_error(ERR_LIB_EVP, 0, EVP_R_DIFFERENT_KEY_TYPES, __FILE__, __LINE__); - return NULL; - } + assert(handle->prov != NULL); + assert(handle->libCtx != NULL); - EVP_PKEY* newKey = EVP_PKEY_new(); - - if (newKey == NULL) - { - return NULL; + OSSL_PROVIDER_unload(handle->prov); + OSSL_LIB_CTX_free(handle->libCtx); + free(handle); } - - 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; - } +#endif - return newKey; +EVP_PKEY* CryptoNative_EvpPkeyCreate(void) +{ + ERR_clear_error(); + return EVP_PKEY_new(); } -void CryptoNative_EvpPkeyDestroy(EVP_PKEY* pkey) +void CryptoNative_EvpPkeyDestroy(EVP_PKEY* pkey, void* extraHandle) { if (pkey != NULL) { EVP_PKEY_free(pkey); } + +#ifdef NEED_OPENSSL_3_0 + if (extraHandle != NULL) + { + EvpPKeyExtraHandle* extra = (EvpPKeyExtraHandle*)extraHandle; + CryptoNative_EvpPkeyExtraHandleDestroy(extra); + } +#else + (void)extraHandle; + assert(extraHandle == NULL); +#endif } -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) +#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) { return 0; } +#ifdef NEED_OPENSSL_3_0 + EvpPKeyExtraHandle* extra = (EvpPKeyExtraHandle*)extraHandle; + + if (extra != NULL) + { + extra->refCount++; + } +#else + (void)extraHandle; + assert(extraHandle == NULL); +#endif + // No error queue impact. return EVP_PKEY_up_ref(pkey); } +#pragma clang diagnostic pop + +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; + default: + return 0; + } +} static bool Lcm(const BIGNUM* num1, const BIGNUM* num2, BN_CTX* ctx, BIGNUM* result) { @@ -580,3 +610,147 @@ 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; + atomic_init(&extra->refCount, 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* EvpPKeyCtxCreateFromPKey(EVP_PKEY* pkey, void* extraHandle) +{ + assert(pkey != NULL); + +#ifdef NEED_OPENSSL_3_0 + 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 + { +#ifndef NEED_OPENSSL_3_0 + (void)extraHandle; +#endif + return EVP_PKEY_CTX_new(pkey, NULL); + } +} 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 e4d5f85d4b9ec..084ae28d77cd4 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,18 @@ 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. +*/ +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 b6a9daf715998..e057afb6d0061 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,21 +2,19 @@ // The .NET Foundation licenses this file to you under the MIT license. #include "pal_evp_pkey_ecdh.h" +#include "pal_evp_pkey.h" +#include "pal_utilities.h" +#include -EVP_PKEY_CTX* CryptoNative_EvpPKeyCtxCreate(EVP_PKEY* pkey, 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 = EVP_PKEY_CTX_new(pkey, NULL); + EVP_PKEY_CTX* ctx = EvpPKeyCtxCreateFromPKey(pkey, extraHandle); if (ctx == NULL) { @@ -25,43 +23,47 @@ EVP_PKEY_CTX* CryptoNative_EvpPKeyCtxCreate(EVP_PKEY* pkey, EVP_PKEY* peerkey, u 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; -} + // 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); -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 2319669fdeae8..6cdb6063c1c57 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, 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 0000000000000..86f6916297c3d --- /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 0000000000000..adc74db89cb57 --- /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 043bf9f9d1eda..e444d8c0c57b1 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,8 +1,10 @@ // 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 "openssl.h" #include static int HasNoPrivateKey(const RSA* rsa); @@ -103,6 +105,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 +121,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 = EvpPKeyCtxCreateFromPKey(pkey, extraHandle); int ret = -1; @@ -132,7 +135,12 @@ int32_t CryptoNative_RsaDecrypt(EVP_PKEY* pkey, goto done; } - // This check may no longer be needed on OpenSSL 3.0 + // 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); @@ -160,6 +168,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 +183,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 = EvpPKeyCtxCreateFromPKey(pkey, extraHandle); int ret = -1; @@ -236,6 +245,7 @@ static bool ConfigureSignature(EVP_PKEY_CTX* ctx, RsaPaddingMode padding, const } int32_t CryptoNative_RsaSignHash(EVP_PKEY* pkey, + void* extraHandle, RsaPaddingMode padding, const EVP_MD* digest, const uint8_t* hash, @@ -250,7 +260,7 @@ int32_t CryptoNative_RsaSignHash(EVP_PKEY* pkey, ERR_clear_error(); - EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey, NULL); + EVP_PKEY_CTX* ctx = EvpPKeyCtxCreateFromPKey(pkey, extraHandle); int ret = -1; @@ -264,7 +274,12 @@ int32_t CryptoNative_RsaSignHash(EVP_PKEY* pkey, goto done; } - // This check may no longer be needed on OpenSSL 3.0 + // 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); @@ -292,6 +307,7 @@ int32_t CryptoNative_RsaSignHash(EVP_PKEY* pkey, } int32_t CryptoNative_RsaVerifyHash(EVP_PKEY* pkey, + void* extraHandle, RsaPaddingMode padding, const EVP_MD* digest, const uint8_t* hash, @@ -306,7 +322,7 @@ int32_t CryptoNative_RsaVerifyHash(EVP_PKEY* pkey, ERR_clear_error(); - EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey, NULL); + EVP_PKEY_CTX* ctx = EvpPKeyCtxCreateFromPKey(pkey, extraHandle); int ret = -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 2d3c58787650a..7ae93de6cf355 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, @@ -58,6 +60,7 @@ Complete the RSA signature generation for the specified hash using the provided Returns the number of bytes written to destination, -1 on error. */ PALEXPORT int32_t CryptoNative_RsaSignHash(EVP_PKEY* pkey, + void* extraHandle, RsaPaddingMode padding, const EVP_MD* digest, const uint8_t* hash, @@ -72,10 +75,10 @@ and padding/digest options. Returns 1 on a verified signature, 0 on a mismatched signature, -1 on error. */ PALEXPORT int32_t CryptoNative_RsaVerifyHash(EVP_PKEY* pkey, + void* extraHandle, RsaPaddingMode padding, const EVP_MD* digest, 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_ssl.c b/src/native/libs/System.Security.Cryptography.Native/pal_ssl.c index dc0478ef2a230..93d352702748f 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)