From 8134c625ef679a8fcfdff708f53f17b25792c502 Mon Sep 17 00:00:00 2001 From: Krzysztof Wicher Date: Tue, 16 Jul 2024 14:25:04 +0200 Subject: [PATCH] OpenSSL providers support --- .../Interop.EcDsa.cs | 61 ---- .../Interop.EvpPkey.EcKey.cs | 17 +- .../Interop.EvpPkey.Ecdh.cs | 94 +++++- .../Interop.EvpPkey.Rsa.cs | 83 ++--- .../Interop.EvpPkey.cs | 61 ++-- .../SafeHandles/SafeEvpPkeyCtxHandle.Unix.cs | 47 +++ .../ECDiffieHellmanOpenSsl.Derive.cs | 45 ++- .../Cryptography/ECDiffieHellmanOpenSsl.cs | 55 ++-- .../ECDiffieHellmanOpenSslPublicKey.cs | 20 +- .../Security/Cryptography/ECDsaOpenSsl.cs | 215 +++++++----- .../System/Security/Cryptography/ECOpenSsl.cs | 34 +- .../Security/Cryptography/RSAOpenSsl.cs | 158 +++++---- .../ref/System.Security.Cryptography.cs | 6 + .../src/System.Security.Cryptography.csproj | 4 - .../Cryptography/ECDiffieHellmanOpenSsl.cs | 41 +-- .../Security/Cryptography/ECDsaOpenSsl.cs | 45 +-- .../Cryptography/OpenSsl.NotSupported.cs | 8 + .../Security/Cryptography/RSAOpenSsl.cs | 6 +- .../Cryptography/SafeEvpPKeyHandle.OpenSsl.cs | 96 +++++- .../X509Certificates/OpenSslExportProvider.cs | 42 +-- .../tests/OpenSslNamedKeysTests.manual.cs | 264 +++++++++++++-- .../tests/osslplugins/README.md | 69 +++- .../tests/osslplugins/test.sh | 55 +++- .../CMakeLists.txt | 1 - .../entrypoints.c | 18 +- .../opensslshim.h | 33 +- .../osslcompat_30.h | 26 +- .../pal_ecdsa.c | 34 -- .../pal_ecdsa.h | 29 -- .../pal_evp_pkey.c | 307 +++++++++++++++--- .../pal_evp_pkey.h | 71 +++- .../pal_evp_pkey_ecdh.c | 5 +- .../pal_evp_pkey_ecdh.h | 2 +- .../pal_evp_pkey_rsa.c | 170 +--------- .../pal_evp_pkey_rsa.h | 31 +- .../pal_ssl.c | 2 +- 36 files changed, 1442 insertions(+), 813 deletions(-) delete mode 100644 src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.cs delete mode 100644 src/native/libs/System.Security.Cryptography.Native/pal_ecdsa.c delete mode 100644 src/native/libs/System.Security.Cryptography.Native/pal_ecdsa.h diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.cs deleted file mode 100644 index 7d994d96402dd3..00000000000000 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Runtime.InteropServices; -using Microsoft.Win32.SafeHandles; - -internal static partial class Interop -{ - internal static partial class Crypto - { - internal static bool EcDsaSign(ReadOnlySpan dgst, Span sig, out int siglen, SafeEcKeyHandle ecKey) => - EcDsaSign(ref MemoryMarshal.GetReference(dgst), dgst.Length, ref MemoryMarshal.GetReference(sig), out siglen, ecKey); - - [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EcDsaSign")] - [return: MarshalAs(UnmanagedType.Bool)] - private static partial bool EcDsaSign(ref byte dgst, int dlen, ref byte sig, out int siglen, SafeEcKeyHandle ecKey); - - internal static int EcDsaVerify(ReadOnlySpan dgst, ReadOnlySpan sigbuf, SafeEcKeyHandle ecKey) - { - int ret = EcDsaVerify( - ref MemoryMarshal.GetReference(dgst), - dgst.Length, - ref MemoryMarshal.GetReference(sigbuf), - sigbuf.Length, - ecKey); - - if (ret < 0) - { - ErrClearError(); - } - - return ret; - } - - /*- - * returns - * 1: correct signature - * 0: incorrect signature - * -1: error - */ - [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EcDsaVerify")] - private static partial int EcDsaVerify(ref byte dgst, int dgst_len, ref byte sigbuf, int sig_len, SafeEcKeyHandle ecKey); - - // returns the maximum length of a DER encoded ECDSA signature created with this key. - [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EcDsaSize")] - private static partial int CryptoNative_EcDsaSize(SafeEcKeyHandle ecKey); - - internal static int EcDsaSize(SafeEcKeyHandle ecKey) - { - int ret = CryptoNative_EcDsaSize(ecKey); - - if (ret == 0) - { - throw CreateOpenSslCryptographicException(); - } - - return ret; - } - } -} diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.EcKey.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.EcKey.cs index 6ef629c8e0d6e7..bd89f4e3bee21a 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.EcKey.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.EcKey.cs @@ -12,8 +12,21 @@ internal static partial class Crypto [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPkeyGetEcKey")] internal static partial SafeEcKeyHandle EvpPkeyGetEcKey(SafeEvpPKeyHandle pkey); - [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPkeySetEcKey")] + [LibraryImport(Libraries.CryptoNative)] [return: MarshalAs(UnmanagedType.Bool)] - internal static partial bool EvpPkeySetEcKey(SafeEvpPKeyHandle pkey, SafeEcKeyHandle key); + private static partial bool CryptoNative_EvpPkeySetEcKey(SafeEvpPKeyHandle pkey, SafeEcKeyHandle key); + + // Calls EVP_PKEY_set1_EC_KEY therefore the key will be duplicated + internal static SafeEvpPKeyHandle CreateEvpPkeyFromEcKey(SafeEcKeyHandle key) + { + SafeEvpPKeyHandle pkey = Interop.Crypto.EvpPkeyCreate(); + if (!CryptoNative_EvpPkeySetEcKey(pkey, key)) + { + pkey.Dispose(); + throw Interop.Crypto.CreateOpenSslCryptographicException(); + } + + return pkey; + } } } diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Ecdh.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Ecdh.cs index 5308043c8e80ef..1abcdaf65f8343 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Ecdh.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Ecdh.cs @@ -11,8 +11,100 @@ internal static partial class Interop { internal static partial class Crypto { + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyCtxCreateFromPKey")] + private static partial SafeEvpPKeyCtxHandle EvpPKeyCtxCreate(SafeEvpPKeyHandle pkey, IntPtr extraHandle); + + internal static SafeEvpPKeyCtxHandle EvpPKeyCtxCreate(SafeEvpPKeyHandle pkey) + => EvpPKeyCtxCreate(pkey, pkey.ExtraHandle); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyCtxCreate")] - internal static partial SafeEvpPKeyCtxHandle EvpPKeyCtxCreate(SafeEvpPKeyHandle pkey, SafeEvpPKeyHandle peerkey, out uint secretLength); + private static partial SafeEvpPKeyCtxHandle EvpPKeyCtxCreate(SafeEvpPKeyHandle pkey, IntPtr extraHandle, SafeEvpPKeyHandle peerkey, out uint secretLength); + + internal static SafeEvpPKeyCtxHandle EvpPKeyCtxCreate(SafeEvpPKeyHandle pkey, SafeEvpPKeyHandle peerkey, out uint secretLength) + => EvpPKeyCtxCreate(pkey, pkey.ExtraHandle, peerkey, out secretLength); + + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyCtxConfigureForECDSASign")] + private static partial int EvpPKeyCtxConfigureForECDSASignCore(SafeEvpPKeyCtxHandle ctx); + + internal static void EvpPKeyCtxConfigureForECDSASign(SafeEvpPKeyCtxHandle ctx) + { + Debug.Assert(ctx != null); + Debug.Assert(!ctx.IsInvalid); + + if (EvpPKeyCtxConfigureForECDSASignCore(ctx) != 1) + { + throw CreateOpenSslCryptographicException(); + } + } + + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyCtxConfigureForECDSAVerify")] + private static partial int EvpPKeyCtxConfigureForECDSAVerifyCore(SafeEvpPKeyCtxHandle ctx); + + internal static void EvpPKeyCtxConfigureForECDSAVerify(SafeEvpPKeyCtxHandle ctx) + { + Debug.Assert(ctx != null); + Debug.Assert(!ctx.IsInvalid); + + if (EvpPKeyCtxConfigureForECDSAVerifyCore(ctx) != 1) + { + throw CreateOpenSslCryptographicException(); + } + } + + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyCtxSignHash")] + private static unsafe partial int EvpPKeyCtxSignHash(SafeEvpPKeyCtxHandle ctx, byte* hash, int hashLen, byte* destination, ref int destinationLen); + + internal static unsafe bool TryEvpPKeyCtxSignHash(SafeEvpPKeyCtxHandle ctx, ReadOnlySpan hash, Span destination, out int bytesWritten) + { + Debug.Assert(ctx != null); + Debug.Assert(!ctx.IsInvalid); + + if (hash.Length == 0 || destination.Length == 0) + { + bytesWritten = 0; + return false; + } + + bytesWritten = destination.Length; + ref byte hashRef = ref MemoryMarshal.GetReference(hash); + ref byte destRef = ref MemoryMarshal.GetReference(destination); + fixed (byte* hashPtr = &hashRef) + fixed (byte* destPtr = &destRef) + { + return EvpPKeyCtxSignHash(ctx, hashPtr, hash.Length, destPtr, ref bytesWritten) == 1; + } + } + + internal static unsafe bool TryEvpPKeyCtxSignatureSize(SafeEvpPKeyCtxHandle ctx, ReadOnlySpan hash, out int bytesWritten) + { + Debug.Assert(ctx != null); + Debug.Assert(!ctx.IsInvalid); + + bytesWritten = 0; + + if (hash.Length == 0) + { + return false; + } + + ref byte hashRef = ref MemoryMarshal.GetReference(hash); + fixed (byte* hashPtr = &hashRef) + { + byte* destPtr = null; + return EvpPKeyCtxSignHash(ctx, hashPtr, hash.Length, destPtr, ref bytesWritten) == 1; + } + } + + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyCtxVerifyHash")] + private static partial int EvpPKeyCtxVerifyHash(SafeEvpPKeyCtxHandle ctx, ref byte hash, int hashLen, ref byte signature, int signatureLen); + + internal static bool EvpPKeyCtxVerifyHash(SafeEvpPKeyCtxHandle ctx, ReadOnlySpan hash, ReadOnlySpan signature) + { + Debug.Assert(ctx != null); + Debug.Assert(!ctx.IsInvalid); + + return EvpPKeyCtxVerifyHash(ctx, ref MemoryMarshal.GetReference(hash), hash.Length, ref MemoryMarshal.GetReference(signature), signature.Length) == 1; + } [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyDeriveSecretAgreement")] private static partial int EvpPKeyDeriveSecretAgreement( diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Rsa.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Rsa.cs index 9fe32f23e42b02..1fec6d14bfca61 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Rsa.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Rsa.cs @@ -48,6 +48,7 @@ internal static SafeEvpPKeyHandle RsaGenerateKey(int keySize) [LibraryImport(Libraries.CryptoNative)] private static partial int CryptoNative_RsaDecrypt( SafeEvpPKeyHandle pkey, + IntPtr extraHandle, ref byte source, int sourceLength, RSAEncryptionPaddingMode paddingMode, @@ -64,6 +65,7 @@ internal static int RsaDecrypt( { int written = CryptoNative_RsaDecrypt( pkey, + pkey.ExtraHandle, ref MemoryMarshal.GetReference(source), source.Length, paddingMode, @@ -83,6 +85,7 @@ ref MemoryMarshal.GetReference(destination), [LibraryImport(Libraries.CryptoNative)] private static partial int CryptoNative_RsaEncrypt( SafeEvpPKeyHandle pkey, + IntPtr extraHandle, ref byte source, int sourceLength, RSAEncryptionPaddingMode paddingMode, @@ -99,6 +102,7 @@ internal static int RsaEncrypt( { int written = CryptoNative_RsaEncrypt( pkey, + pkey.ExtraHandle, ref MemoryMarshal.GetReference(source), source.Length, paddingMode, @@ -116,78 +120,53 @@ ref MemoryMarshal.GetReference(destination), } [LibraryImport(Libraries.CryptoNative)] - private static partial int CryptoNative_RsaSignHash( - SafeEvpPKeyHandle pkey, + private static partial int CryptoNative_EvpPKeyCtxConfigureForRsaSign( + SafeEvpPKeyCtxHandle ctx, RSASignaturePaddingMode paddingMode, - IntPtr digestAlgorithm, - ref byte hash, - int hashLength, - ref byte destination, - int destinationLength); + IntPtr digestAlgorithm); - internal static int RsaSignHash( - SafeEvpPKeyHandle pkey, + internal static void CryptoNative_ConfigureForRsaSign( + SafeEvpPKeyCtxHandle ctx, RSASignaturePaddingMode paddingMode, - IntPtr digestAlgorithm, - ReadOnlySpan hash, - Span destination) + HashAlgorithmName digestAlgorithm) { - int written = CryptoNative_RsaSignHash( - pkey, - paddingMode, - digestAlgorithm, - ref MemoryMarshal.GetReference(hash), - hash.Length, - ref MemoryMarshal.GetReference(destination), - destination.Length); + if (digestAlgorithm.Name == null) + { + throw new ArgumentNullException(nameof(digestAlgorithm)); + } - if (written < 0) + IntPtr digestAlgorithmPtr = Interop.Crypto.HashAlgorithmToEvp(digestAlgorithm.Name); + int ret = CryptoNative_EvpPKeyCtxConfigureForRsaSign(ctx, paddingMode, digestAlgorithmPtr); + + if (ret != 1) { - Debug.Assert(written == -1); throw CreateOpenSslCryptographicException(); } - - return written; } [LibraryImport(Libraries.CryptoNative)] - private static partial int CryptoNative_RsaVerifyHash( - SafeEvpPKeyHandle pkey, + private static partial int CryptoNative_EvpPKeyCtxConfigureForRsaVerify( + SafeEvpPKeyCtxHandle ctx, RSASignaturePaddingMode paddingMode, - IntPtr digestAlgorithm, - ref byte hash, - int hashLength, - ref byte signature, - int signatureLength); + IntPtr digestAlgorithm); - internal static bool RsaVerifyHash( - SafeEvpPKeyHandle pkey, + internal static void CryptoNative_ConfigureForRsaVerify( + SafeEvpPKeyCtxHandle ctx, RSASignaturePaddingMode paddingMode, - IntPtr digestAlgorithm, - ReadOnlySpan hash, - ReadOnlySpan signature) + HashAlgorithmName digestAlgorithm) { - int ret = CryptoNative_RsaVerifyHash( - pkey, - paddingMode, - digestAlgorithm, - ref MemoryMarshal.GetReference(hash), - hash.Length, - ref MemoryMarshal.GetReference(signature), - signature.Length); - - if (ret == 1) + if (digestAlgorithm.Name == null) { - return true; + throw new ArgumentNullException(nameof(digestAlgorithm)); } - if (ret == 0) + IntPtr digestAlgorithmPtr = Interop.Crypto.HashAlgorithmToEvp(digestAlgorithm.Name); + int ret = CryptoNative_EvpPKeyCtxConfigureForRsaVerify(ctx, paddingMode, digestAlgorithmPtr); + + if (ret != 1) { - return false; + throw CreateOpenSslCryptographicException(); } - - Debug.Assert(ret == -1); - throw CreateOpenSslCryptographicException(); } } } diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs index 4e8659b5653b78..99922f9d4f884c 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs @@ -13,36 +13,22 @@ internal static partial class Crypto [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPkeyCreate")] internal static partial SafeEvpPKeyHandle EvpPkeyCreate(); - [LibraryImport(Libraries.CryptoNative)] - private static partial SafeEvpPKeyHandle CryptoNative_EvpPKeyDuplicate( - SafeEvpPKeyHandle currentKey, - EvpAlgorithmId algorithmId); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPkeyDestroy")] + internal static partial void EvpPkeyDestroy(IntPtr pkey, IntPtr extraHandle); - internal static SafeEvpPKeyHandle EvpPKeyDuplicate( - SafeEvpPKeyHandle currentKey, - EvpAlgorithmId algorithmId) - { - SafeEvpPKeyHandle pkey = CryptoNative_EvpPKeyDuplicate( - currentKey, - algorithmId); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyBits")] + internal static partial int EvpPKeyBits(SafeEvpPKeyHandle pkey); - if (pkey.IsInvalid) - { - pkey.Dispose(); - throw CreateOpenSslCryptographicException(); - } + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_UpRefEvpPkey")] + private static partial int UpRefEvpPkey(SafeEvpPKeyHandle handle, IntPtr extraHandle); - return pkey; + internal static int UpRefEvpPkey(SafeEvpPKeyHandle handle) + { + return UpRefEvpPkey(handle, handle.ExtraHandle); } - [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPkeyDestroy")] - internal static partial void EvpPkeyDestroy(IntPtr pkey); - - [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeySize")] - internal static partial int EvpPKeySize(SafeEvpPKeyHandle pkey); - - [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_UpRefEvpPkey")] - internal static partial int UpRefEvpPkey(SafeEvpPKeyHandle handle); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyType")] + internal static partial EvpAlgorithmId EvpPKeyType(SafeEvpPKeyHandle handle); [LibraryImport(Libraries.CryptoNative)] private static unsafe partial SafeEvpPKeyHandle CryptoNative_DecodeSubjectPublicKeyInfo( @@ -274,6 +260,31 @@ internal static SafeEvpPKeyHandle LoadPublicKeyFromEngine( return pkey; } + [LibraryImport(Libraries.CryptoNative, StringMarshalling = StringMarshalling.Utf8)] + private static partial IntPtr CryptoNative_LoadKeyFromProvider( + string providerName, + string keyUri, + ref IntPtr extraHandle); + + internal static SafeEvpPKeyHandle LoadKeyFromProvider( + string providerName, + string keyUri) + { + IntPtr extraHandle = IntPtr.Zero; + IntPtr evpPKeyHandle = CryptoNative_LoadKeyFromProvider(providerName, keyUri, ref extraHandle); + + if (IntPtr.Zero == evpPKeyHandle) + { + throw CreateOpenSslCryptographicException(); + } + + Debug.Assert(extraHandle != IntPtr.Zero); + return new SafeEvpPKeyHandle(evpPKeyHandle, extraHandle: extraHandle); + } + + [LibraryImport(Libraries.CryptoNative, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void CryptoNative_EvpPkeyExtraHandleDestroy(IntPtr extraHandle); + internal enum EvpAlgorithmId { Unknown = 0, diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpPkeyCtxHandle.Unix.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpPkeyCtxHandle.Unix.cs index d64f135ca0f720..9a76ddab26143d 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpPkeyCtxHandle.Unix.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpPkeyCtxHandle.Unix.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.InteropServices; +using System.Security.Cryptography; namespace Microsoft.Win32.SafeHandles { @@ -26,5 +27,51 @@ protected override bool ReleaseHandle() } public override bool IsInvalid => handle == IntPtr.Zero; + + internal static SafeEvpPKeyCtxHandle CreateFromEvpPkey(SafeEvpPKeyHandle evpPkey) + { + return Interop.Crypto.EvpPKeyCtxCreate(evpPkey); + } + + internal void ConfigureForRSASign(HashAlgorithmName hashAlgorithm, RSASignaturePaddingMode padding) + { + Interop.Crypto.CryptoNative_ConfigureForRsaSign(this, padding, hashAlgorithm); + } + + internal void ConfigureForRSAVerify(HashAlgorithmName hashAlgorithm, RSASignaturePaddingMode padding) + { + Interop.Crypto.CryptoNative_ConfigureForRsaVerify(this, padding, hashAlgorithm); + } + + internal void ConfigureForECDSASign() + { + Interop.Crypto.EvpPKeyCtxConfigureForECDSASign(this); + } + + internal void ConfigureForECDSAVerify() + { + Interop.Crypto.EvpPKeyCtxConfigureForECDSAVerify(this); + } + + internal bool TryGetSufficientSignatureSizeInBytesCore( + ReadOnlySpan hash, out int bytesWritten) + { + return Interop.Crypto.TryEvpPKeyCtxSignatureSize(this, hash, out bytesWritten); + } + + internal bool TrySignHashCore( + ReadOnlySpan hash, + Span outputSignature, + out int bytesWritten) + { + return Interop.Crypto.TryEvpPKeyCtxSignHash(this, hash, outputSignature, out bytesWritten); + } + + internal bool VerifyHashCore( + ReadOnlySpan hash, + ReadOnlySpan signature) + { + return Interop.Crypto.EvpPKeyCtxVerifyHash(this, hash, signature); + } } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs index f6fc78762e043c..c4e0147296e782 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs @@ -88,10 +88,13 @@ public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPa Debug.Assert(otherPartyPublicKey != null); Debug.Assert(_key is not null); // Callers should validate prior. - // Ensure that this ECDH object contains a private key by attempting a parameter export - // which will throw an OpenSslCryptoException if no private key is available - ECParameters thisKeyExplicit = ExportExplicitParameters(true); - bool thisIsNamed = Interop.Crypto.EcKeyHasCurveName(_key.Value); + bool thisIsNamed; + + using (SafeEcKeyHandle ecKey = Interop.Crypto.EvpPkeyGetEcKey(_key)) + { + thisIsNamed = Interop.Crypto.EcKeyHasCurveName(ecKey); + } + ECDiffieHellmanOpenSslPublicKey? otherKey = otherPartyPublicKey as ECDiffieHellmanOpenSslPublicKey; bool disposeOtherKey = false; @@ -109,7 +112,11 @@ public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPa bool otherIsNamed = otherKey.HasCurveName; - SafeEvpPKeyHandle? ourKey = null; + // The only case when we will not directly use our key is when peer key is an explicit curve + // but we're using named in which case we'll recreate our key with explicit parameters. + SafeEvpPKeyHandle ourKey = _key; + bool disposeOurKey = false; + SafeEvpPKeyHandle? theirKey = null; byte[]? rented = null; int secretLength = 0; @@ -123,23 +130,33 @@ public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPa if (otherIsNamed == thisIsNamed) { - ourKey = _key.UpRefKeyHandle(); theirKey = otherKey.DuplicateKeyHandle(); } else if (otherIsNamed) { - ourKey = _key.UpRefKeyHandle(); - using (ECOpenSsl tmp = new ECOpenSsl(otherKey.ExportExplicitParameters())) { - theirKey = tmp.UpRefKeyHandle(); + theirKey = tmp.CreateKeyHandle(); } } else { - using (ECOpenSsl tmp = new ECOpenSsl(thisKeyExplicit)) + try { - ourKey = tmp.UpRefKeyHandle(); + // This is generally not expected to fail except: + // - when key can't be accessed but is available (i.e. TPM) + // - private key is actually missing + + using (ECOpenSsl tmp = new ECOpenSsl(ExportExplicitParameters(true))) + { + ourKey = tmp.CreateKeyHandle(); + disposeOurKey = true; + } + } + catch (CryptographicException) + { + // In both cases of failure we'll report lack of private key + throw new CryptographicException(SR.Cryptography_CSP_NoPrivateKey); } theirKey = otherKey.DuplicateKeyHandle(); @@ -186,13 +203,17 @@ public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPa finally { theirKey?.Dispose(); - ourKey?.Dispose(); if (disposeOtherKey) { otherKey.Dispose(); } + if (disposeOurKey) + { + ourKey.Dispose(); + } + if (rented != null) { CryptoPool.Return(rented, secretLength); diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs index c394d1fbf0b542..3015a7e1fc07aa 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs @@ -9,7 +9,7 @@ namespace System.Security.Cryptography { public sealed partial class ECDiffieHellmanOpenSsl : ECDiffieHellman { - private ECOpenSsl? _key; + private SafeEvpPKeyHandle _key; [UnsupportedOSPlatform("android")] [UnsupportedOSPlatform("browser")] @@ -19,8 +19,8 @@ public sealed partial class ECDiffieHellmanOpenSsl : ECDiffieHellman public ECDiffieHellmanOpenSsl(ECCurve curve) { ThrowIfNotSupported(); - _key = new ECOpenSsl(curve); - KeySizeValue = _key.KeySize; + _key = SafeEvpPKeyHandle.GenerateECKey(curve, out int keySize); + KeySizeValue = keySize; } [UnsupportedOSPlatform("android")] @@ -42,7 +42,7 @@ public ECDiffieHellmanOpenSsl(int keySize) { ThrowIfNotSupported(); base.KeySize = keySize; - _key = new ECOpenSsl(this); + _key = SafeEvpPKeyHandle.GenerateECKey(keySize); } public override KeySizes[] LegalKeySizes => s_defaultKeySizes.CloneKeySizesArray(); @@ -52,7 +52,7 @@ protected override void Dispose(bool disposing) if (disposing) { _key?.Dispose(); - _key = null; + _key = null!; } base.Dispose(disposing); @@ -76,14 +76,17 @@ public override int KeySize ThrowIfDisposed(); _key.Dispose(); - _key = new ECOpenSsl(this); + _key = SafeEvpPKeyHandle.GenerateECKey(value); } } public override void GenerateKey(ECCurve curve) { ThrowIfDisposed(); - KeySizeValue = _key.GenerateKey(curve); + + _key.Dispose(); + _key = SafeEvpPKeyHandle.GenerateECKey(curve, out int keySizeValue); + KeySizeValue = keySizeValue; } public override ECDiffieHellmanPublicKey PublicKey @@ -91,25 +94,37 @@ public override ECDiffieHellmanPublicKey PublicKey get { ThrowIfDisposed(); - - using (SafeEvpPKeyHandle handle = _key.UpRefKeyHandle()) - { - return new ECDiffieHellmanOpenSslPublicKey(handle); - } + return new ECDiffieHellmanOpenSslPublicKey(_key); } } public override void ImportParameters(ECParameters parameters) { ThrowIfDisposed(); - KeySizeValue = _key.ImportParameters(parameters); + _key.Dispose(); + _key = SafeEvpPKeyHandle.GenerateECKey(parameters, out int keySize); + KeySizeValue = keySize; + } + + public override ECParameters ExportExplicitParameters(bool includePrivateParameters) + { + ThrowIfDisposed(); + + using (SafeEcKeyHandle ecKey = Interop.Crypto.EvpPkeyGetEcKey(_key)) + { + return ECOpenSsl.ExportExplicitParameters(ecKey, includePrivateParameters); + } } - public override ECParameters ExportExplicitParameters(bool includePrivateParameters) => - ECOpenSsl.ExportExplicitParameters(GetKey(), includePrivateParameters); + public override ECParameters ExportParameters(bool includePrivateParameters) + { + ThrowIfDisposed(); - public override ECParameters ExportParameters(bool includePrivateParameters) => - ECOpenSsl.ExportParameters(GetKey(), includePrivateParameters); + using (SafeEcKeyHandle ecKey = Interop.Crypto.EvpPkeyGetEcKey(_key)) + { + return ECOpenSsl.ExportParameters(ecKey, includePrivateParameters); + } + } public override void ImportEncryptedPkcs8PrivateKey( ReadOnlySpan passwordBytes, @@ -135,12 +150,6 @@ private void ThrowIfDisposed() ObjectDisposedException.ThrowIf(_key is null, this); } - private SafeEcKeyHandle GetKey() - { - ThrowIfDisposed(); - return _key.Value; - } - static partial void ThrowIfNotSupported(); } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSslPublicKey.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSslPublicKey.cs index ee3a517b692aba..2dfbaa92a030ca 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSslPublicKey.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSslPublicKey.cs @@ -80,25 +80,7 @@ protected override void Dispose(bool disposing) internal SafeEvpPKeyHandle DuplicateKeyHandle() { SafeEcKeyHandle currentKey = GetKey(); - SafeEvpPKeyHandle pkeyHandle = Interop.Crypto.EvpPkeyCreate(); - - try - { - // Wrapping our key in an EVP_PKEY will up_ref our key. - // When the EVP_PKEY is Disposed it will down_ref the key. - // So everything should be copacetic. - if (!Interop.Crypto.EvpPkeySetEcKey(pkeyHandle, currentKey)) - { - throw Interop.Crypto.CreateOpenSslCryptographicException(); - } - - return pkeyHandle; - } - catch - { - pkeyHandle.Dispose(); - throw; - } + return Interop.Crypto.CreateEvpPkeyFromEcKey(currentKey); } [MemberNotNull(nameof(_key))] diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDsaOpenSsl.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDsaOpenSsl.cs index 68a61758b670cf..ffa1ccdb191d4c 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDsaOpenSsl.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDsaOpenSsl.cs @@ -15,7 +15,7 @@ public sealed partial class ECDsaOpenSsl : ECDsa, IRuntimeAlgorithm // secp521r1 maxes out at 139 bytes, so 256 should always be enough private const int SignatureStackBufSize = 256; - private ECOpenSsl? _key; + private Lazy? _key; /// /// Create an ECDsaOpenSsl algorithm with a named curve. @@ -30,8 +30,8 @@ public sealed partial class ECDsaOpenSsl : ECDsa, IRuntimeAlgorithm public ECDsaOpenSsl(ECCurve curve) { ThrowIfNotSupported(); - _key = new ECOpenSsl(curve); - ForceSetKeySize(_key.KeySize); + _key = new Lazy(SafeEvpPKeyHandle.GenerateECKey(curve, out int keySize)); + ForceSetKeySize(keySize); } /// @@ -59,10 +59,8 @@ public ECDsaOpenSsl() public ECDsaOpenSsl(int keySize) { ThrowIfNotSupported(); - // Use the base setter to get the validation and field assignment without the - // side effect of dereferencing _key. base.KeySize = keySize; - _key = new ECOpenSsl(this); + _key = new Lazy(GenerateKeyFromSize); } /// @@ -84,16 +82,34 @@ private void ForceSetKeySize(int newKeySize) public override byte[] SignHash(byte[] hash) { ArgumentNullException.ThrowIfNull(hash); - ThrowIfDisposed(); - SafeEcKeyHandle key = _key.Value; - int signatureLength = Interop.Crypto.EcDsaSize(key); - Span signDestination = stackalloc byte[SignatureStackBufSize]; - ReadOnlySpan derSignature = SignHash(hash, signDestination, signatureLength, key); + using (SafeEvpPKeyCtxHandle ctx = CreateCtx()) + { + ctx.ConfigureForECDSASign(); + + if (!ctx.TryGetSufficientSignatureSizeInBytesCore(hash, out int sufficientDerSignatureSize)) + { + throw new CryptographicException(); + } + + Span derSignature = sufficientDerSignatureSize <= SignatureStackBufSize ? stackalloc byte[sufficientDerSignatureSize] : new byte[sufficientDerSignatureSize]; + if (!ctx.TrySignHashCore(hash, derSignature, out int bytesWritten)) + { + throw new CryptographicException(); + } + + if (bytesWritten > derSignature.Length) + { + Debug.Fail("TrySignHashCore wrote more bytes than it claimed it would write"); + throw new CryptographicException(); + } + + derSignature = derSignature.Slice(0, bytesWritten); - byte[] converted = AsymmetricAlgorithmHelpers.ConvertDerToIeee1363(derSignature, KeySize); - return converted; + byte[] converted = AsymmetricAlgorithmHelpers.ConvertDerToIeee1363(derSignature, KeySize); + return converted; + } } public override bool TrySignHash(ReadOnlySpan hash, Span destination, out int bytesWritten) @@ -105,6 +121,14 @@ public override bool TrySignHash(ReadOnlySpan hash, Span destination out bytesWritten); } + // SafeEvpPKeyCtxHandle doesn't touch ref counts of SafeEvpPKeyHandle so we need to keep it alive during it's lifetime. + // We only use CreateCtx from within single `using` statement so we can guarantee no lifetime issues. + private SafeEvpPKeyCtxHandle CreateCtx() + { + ThrowIfDisposed(); + return SafeEvpPKeyCtxHandle.CreateFromEvpPkey(_key.Value); + } + protected override bool TrySignHashCore( ReadOnlySpan hash, Span destination, @@ -112,10 +136,6 @@ protected override bool TrySignHashCore( out int bytesWritten) { ThrowIfDisposed(); - SafeEcKeyHandle key = _key.Value; - - int signatureLength = Interop.Crypto.EcDsaSize(key); - Span signDestination = stackalloc byte[SignatureStackBufSize]; if (signatureFormat == DSASignatureFormat.IeeeP1363FixedFieldConcatenation) { @@ -127,33 +147,71 @@ protected override bool TrySignHashCore( return false; } - ReadOnlySpan derSignature = SignHash(hash, signDestination, signatureLength, key); - bytesWritten = AsymmetricAlgorithmHelpers.ConvertDerToIeee1363(derSignature, KeySize, destination); - Debug.Assert(bytesWritten == encodedSize); + using (SafeEvpPKeyCtxHandle ctx = CreateCtx()) + { + ctx.ConfigureForECDSASign(); + + if (!ctx.TryGetSufficientSignatureSizeInBytesCore(hash, out int sufficientSignatureSizeInBytes)) + { + throw Interop.Crypto.CreateOpenSslCryptographicException(); + } + + Span derSignatureDestination = sufficientSignatureSizeInBytes <= SignatureStackBufSize ? stackalloc byte[sufficientSignatureSizeInBytes] : new byte[sufficientSignatureSizeInBytes]; + if (!ctx.TrySignHashCore(hash, derSignatureDestination, out int derSignatureBytesWritten)) + { + // this is unrelated to sufficient size reason + throw Interop.Crypto.CreateOpenSslCryptographicException(); + } + + derSignatureDestination = derSignatureDestination.Slice(0, derSignatureBytesWritten); + bytesWritten = AsymmetricAlgorithmHelpers.ConvertDerToIeee1363(derSignatureDestination, KeySize, destination); + Debug.Assert(bytesWritten == encodedSize); + } + return true; } else if (signatureFormat == DSASignatureFormat.Rfc3279DerSequence) { - if (destination.Length >= signatureLength) - { - signDestination = destination; - } - else if (signatureLength > signDestination.Length) - { - Debug.Fail($"Stack-based signDestination is insufficient ({signatureLength} needed)"); - bytesWritten = 0; - return false; - } - - ReadOnlySpan derSignature = SignHash(hash, signDestination, signatureLength, key); - - if (destination == signDestination) + using (SafeEvpPKeyCtxHandle ctx = CreateCtx()) { - bytesWritten = derSignature.Length; + ctx.ConfigureForECDSASign(); + + // We could theoretically pass this through but we need to distinguish between "not enough space" and "failed" + // We could check for presence of private key but that won't work when it's an external key. + if (!ctx.TryGetSufficientSignatureSizeInBytesCore(hash, out int sufficientSignatureSizeInBytes)) + { + throw Interop.Crypto.CreateOpenSslCryptographicException(); + } + + if (destination.Length >= sufficientSignatureSizeInBytes) + { + // The only reason this could fail won't be related to buffer size + if (!ctx.TrySignHashCore(hash, destination, out bytesWritten)) + { + throw Interop.Crypto.CreateOpenSslCryptographicException(); + } + + return true; + } + + // Since sufficientSignatureSizeInBytes can be more than what's actually needed + // we need temporary buffer of sufficient size and see if operation can succeed with that + Span derSignatureDestination = sufficientSignatureSizeInBytes <= SignatureStackBufSize ? stackalloc byte[sufficientSignatureSizeInBytes] : new byte[sufficientSignatureSizeInBytes]; + if (!ctx.TrySignHashCore(hash, destination, out int bytesWrittenToTemporaryBuffer)) + { + throw Interop.Crypto.CreateOpenSslCryptographicException(); + } + + if (bytesWrittenToTemporaryBuffer > destination.Length) + { + bytesWritten = 0; + return false; + } + + derSignatureDestination.CopyTo(destination); + bytesWritten = bytesWrittenToTemporaryBuffer; return true; } - - return Helpers.TryCopyToDestination(derSignature, destination, out bytesWritten); } else { @@ -161,33 +219,6 @@ protected override bool TrySignHashCore( } } - private static ReadOnlySpan SignHash( - ReadOnlySpan hash, - Span destination, - int signatureLength, - SafeEcKeyHandle key) - { - if (signatureLength > destination.Length) - { - Debug.Fail($"Stack-based signDestination is insufficient ({signatureLength} needed)"); - destination = new byte[signatureLength]; - } - - if (!Interop.Crypto.EcDsaSign(hash, destination, out int actualLength, key)) - { - throw Interop.Crypto.CreateOpenSslCryptographicException(); - } - - Debug.Assert( - actualLength <= signatureLength, - "ECDSA_sign reported an unexpected signature size", - "ECDSA_sign reported signatureSize was {0}, when <= {1} was expected", - actualLength, - signatureLength); - - return destination.Slice(0, actualLength); - } - public override bool VerifyHash(byte[] hash, byte[] signature) { ArgumentNullException.ThrowIfNull(hash); @@ -242,16 +273,18 @@ protected override bool VerifyHashCore( signatureFormat.ToString()); } - SafeEcKeyHandle key = _key.Value; - int verifyResult = Interop.Crypto.EcDsaVerify(hash, toVerify, key); - return verifyResult == 1; + using (SafeEvpPKeyCtxHandle ctx = CreateCtx()) + { + ctx.ConfigureForECDSAVerify(); + return ctx.VerifyHashCore(hash, toVerify); + } } protected override void Dispose(bool disposing) { if (disposing) { - _key?.Dispose(); + FreeKey(); _key = null; } @@ -273,38 +306,59 @@ public override int KeySize base.KeySize = value; ThrowIfDisposed(); - _key.Dispose(); - _key = new ECOpenSsl(this); + + FreeKey(); + _key = new Lazy(GenerateKeyFromSize); } } public override void GenerateKey(ECCurve curve) { ThrowIfDisposed(); - _key.GenerateKey(curve); + + FreeKey(); + _key = new Lazy(SafeEvpPKeyHandle.GenerateECKey(curve, out int keySize)); // Use ForceSet instead of the property setter to ensure that LegalKeySizes doesn't interfere // with the already loaded key. - ForceSetKeySize(_key.KeySize); + ForceSetKeySize(keySize); } public override void ImportParameters(ECParameters parameters) { ThrowIfDisposed(); - _key.ImportParameters(parameters); - ForceSetKeySize(_key.KeySize); + + FreeKey(); + _key = new Lazy(SafeEvpPKeyHandle.GenerateECKey(parameters, out int keySize)); + + // Use ForceSet instead of the property setter to ensure that LegalKeySizes doesn't interfere + // with the already loaded key. + ForceSetKeySize(keySize); + } + + private SafeEvpPKeyHandle GenerateKeyFromSize() + { + return SafeEvpPKeyHandle.GenerateECKey(KeySize); } public override ECParameters ExportExplicitParameters(bool includePrivateParameters) { ThrowIfDisposed(); - return ECOpenSsl.ExportExplicitParameters(_key.Value, includePrivateParameters); + + using (SafeEcKeyHandle ecKey = Interop.Crypto.EvpPkeyGetEcKey(_key.Value)) + { + return ECOpenSsl.ExportExplicitParameters(ecKey, includePrivateParameters); + } } public override ECParameters ExportParameters(bool includePrivateParameters) { ThrowIfDisposed(); - return ECOpenSsl.ExportParameters(_key.Value, includePrivateParameters); + + using (SafeEcKeyHandle ecKey = Interop.Crypto.EvpPkeyGetEcKey(_key.Value)) + { + return ECOpenSsl.ExportParameters(ecKey, includePrivateParameters); + } } public override void ImportEncryptedPkcs8PrivateKey( @@ -325,6 +379,15 @@ public override void ImportEncryptedPkcs8PrivateKey( base.ImportEncryptedPkcs8PrivateKey(password, source, out bytesRead); } + private void FreeKey() + { + if (_key != null && _key.IsValueCreated) + { + SafeEvpPKeyHandle handle = _key.Value; + handle?.Dispose(); + } + } + [MemberNotNull(nameof(_key))] private void ThrowIfDisposed() { diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs b/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs index 29c5d8c944a24d..0c95d5bf1fb5d8 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs @@ -15,9 +15,9 @@ public ECOpenSsl(ECCurve curve) GenerateKey(curve); } - public ECOpenSsl(AsymmetricAlgorithm owner) + public ECOpenSsl(int keySizeBits) { - _key = new Lazy(() => GenerateKeyLazy(owner)); + _key = new Lazy(() => GenerateKeyByKeySize(keySizeBits)); } public ECOpenSsl(ECParameters ecParameters) @@ -25,6 +25,7 @@ public ECOpenSsl(ECParameters ecParameters) ImportParameters(ecParameters); } + // Takes ownership of the key public ECOpenSsl(SafeEcKeyHandle key) { _key = new Lazy(key); @@ -32,9 +33,6 @@ public ECOpenSsl(SafeEcKeyHandle key) internal SafeEcKeyHandle Value => _key.Value; - private static SafeEcKeyHandle GenerateKeyLazy(AsymmetricAlgorithm owner) => - GenerateKeyByKeySize(owner.KeySize); - public void Dispose() { FreeKey(); @@ -42,33 +40,15 @@ public void Dispose() internal int KeySize => Interop.Crypto.EcKeyGetSize(_key.Value); - internal SafeEvpPKeyHandle UpRefKeyHandle() + internal SafeEvpPKeyHandle CreateKeyHandle() { SafeEcKeyHandle currentKey = _key.Value; - Debug.Assert(currentKey != null, "null TODO"); - - SafeEvpPKeyHandle pkeyHandle = Interop.Crypto.EvpPkeyCreate(); - - try - { - // Wrapping our key in an EVP_PKEY will up_ref our key. - // When the EVP_PKEY is Disposed it will down_ref the key. - // So everything should be copacetic. - if (!Interop.Crypto.EvpPkeySetEcKey(pkeyHandle, currentKey)) - { - throw Interop.Crypto.CreateOpenSslCryptographicException(); - } + Debug.Assert(currentKey != null, "key is null"); - return pkeyHandle; - } - catch - { - pkeyHandle.Dispose(); - throw; - } + return Interop.Crypto.CreateEvpPkeyFromEcKey(currentKey); } - internal void SetKey(SafeEcKeyHandle key) + private void SetKey(SafeEcKeyHandle key) { Debug.Assert(key != null, "key != null"); Debug.Assert(!key.IsInvalid, "!key.IsInvalid"); diff --git a/src/libraries/Common/src/System/Security/Cryptography/RSAOpenSsl.cs b/src/libraries/Common/src/System/Security/Cryptography/RSAOpenSsl.cs index 7e6efbb2da57ab..6b0b32d7420ab3 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/RSAOpenSsl.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/RSAOpenSsl.cs @@ -15,8 +15,6 @@ namespace System.Security.Cryptography { public sealed partial class RSAOpenSsl : RSA, IRuntimeAlgorithm { - private const int BitsPerByte = 8; - private Lazy? _key; [UnsupportedOSPlatform("android")] @@ -88,7 +86,7 @@ public override byte[] Decrypt(byte[] data, RSAEncryptionPadding padding) ValidatePadding(padding); SafeEvpPKeyHandle key = GetKey(); - int rsaSize = Interop.Crypto.EvpPKeySize(key); + int rsaSize = key.GetKeySizeBytes(); Span destination = default; byte[] buf = CryptoPool.Rent(rsaSize); @@ -116,11 +114,10 @@ public override bool TryDecrypt( ValidatePadding(padding); SafeEvpPKeyHandle key = GetKey(); - int keySizeBytes = Interop.Crypto.EvpPKeySize(key); - // OpenSSL requires that the decryption buffer be at least as large as EVP_PKEY_size. - // So if the destination is too small, use a temporary buffer so we can match - // Windows behavior of succeeding so long as the buffer can hold the final output. + // For RSA decryption buffer is equal to key size in bytes + int keySizeBytes = key.GetKeySizeBytes(); + if (destination.Length < keySizeBytes) { // RSA up through 4096 bits use a stackalloc @@ -174,7 +171,7 @@ private static int Decrypt( // Caller should have already checked this. Debug.Assert(!key.IsInvalid); - int rsaSize = Interop.Crypto.EvpPKeySize(key); + int rsaSize = key.GetKeySizeBytes(); if (data.Length != rsaSize) { @@ -183,7 +180,7 @@ private static int Decrypt( if (destination.Length < rsaSize) { - Debug.Fail("Caller is responsible for temporary decryption buffer creation"); + Debug.Fail($"Caller is responsible for temporary decryption buffer creation destination. {nameof(destination)}.{nameof(destination.Length)} == {destination.Length}, {nameof(rsaSize)} = {rsaSize}"); throw new CryptographicException(); } @@ -211,7 +208,7 @@ public override byte[] Encrypt(byte[] data, RSAEncryptionPadding padding) ValidatePadding(padding); SafeEvpPKeyHandle key = GetKey(); - byte[] buf = new byte[Interop.Crypto.EvpPKeySize(key)]; + byte[] buf = new byte[key.GetKeySizeBytes()]; bool encrypted = TryEncrypt( key, @@ -246,7 +243,7 @@ private static bool TryEncrypt( RSAEncryptionPadding padding, out int bytesWritten) { - int rsaSize = Interop.Crypto.EvpPKeySize(key); + int rsaSize = key.GetKeySizeBytes(); if (destination.Length < rsaSize) { @@ -661,7 +658,7 @@ private void SetKey(SafeEvpPKeyHandle newKey) // Use ForceSet instead of the property setter to ensure that LegalKeySizes doesn't interfere // with the already loaded key. - ForceSetKeySize(BitsPerByte * Interop.Crypto.EvpPKeySize(newKey)); + ForceSetKeySize(newKey.GetKeySizeBits()); } private static void ValidateParameters(ref RSAParameters parameters) @@ -723,7 +720,7 @@ private SafeEvpPKeyHandle GetKey() private SafeEvpPKeyHandle GenerateKey() { - return Interop.Crypto.RsaGenerateKey(KeySize); + return SafeEvpPKeyHandle.GenerateRSAKey(KeySize); } public override byte[] SignHash(byte[] hash, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding) @@ -731,21 +728,40 @@ public override byte[] SignHash(byte[] hash, HashAlgorithmName hashAlgorithm, RS ArgumentNullException.ThrowIfNull(hash); ArgumentException.ThrowIfNullOrEmpty(hashAlgorithm.Name, nameof(hashAlgorithm)); ArgumentNullException.ThrowIfNull(padding); + ThrowIfDisposed(); - if (!TrySignHash( - hash, - Span.Empty, - hashAlgorithm, padding, - true, - out _, - out byte[]? signature)) + using (SafeEvpPKeyCtxHandle ctx = CreateCtx()) { - Debug.Fail("TrySignHash should not return false in allocation mode"); - throw new CryptographicException(); - } + ctx.ConfigureForRSASign(hashAlgorithm, padding.Mode); + + if (!ctx.TryGetSufficientSignatureSizeInBytesCore(hash, out int sufficientDerSignatureSize)) + { + throw new CryptographicException(); + } + + byte[] signature = new byte[sufficientDerSignatureSize]; + if (!ctx.TrySignHashCore(hash, signature, out int bytesWritten)) + { + throw new CryptographicException(); + } + + if (bytesWritten > signature.Length) + { + Debug.Fail("TrySignHashCore wrote more bytes than it claimed it would write"); + throw new CryptographicException(); + } - Debug.Assert(signature != null); - return signature; + if (bytesWritten == signature.Length) + { + return signature; + } + else + { + byte[] ret = new byte[bytesWritten]; + new ReadOnlySpan(signature).Slice(0, bytesWritten).CopyTo(ret); + return ret; + } + } } public override bool TrySignHash( @@ -758,55 +774,75 @@ public override bool TrySignHash( ArgumentException.ThrowIfNullOrEmpty(hashAlgorithm.Name, nameof(hashAlgorithm)); ArgumentNullException.ThrowIfNull(padding); - bool ret = TrySignHash( + return TrySignHashCore( hash, destination, hashAlgorithm, padding, - false, - out bytesWritten, - out byte[]? alloced); + out bytesWritten); + } - Debug.Assert(alloced == null); - return ret; + // SafeEvpPKeyCtxHandle doesn't touch ref counts of SafeEvpPKeyHandle so we need to keep it alive during it's lifetime. + // We only use CreateCtx from within single `using` statement so we can guarantee no lifetime issues. + private SafeEvpPKeyCtxHandle CreateCtx() + { + ThrowIfDisposed(); + return SafeEvpPKeyCtxHandle.CreateFromEvpPkey(GetKey()); } - private bool TrySignHash( + private bool TrySignHashCore( ReadOnlySpan hash, Span destination, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding, - bool allocateSignature, - out int bytesWritten, - out byte[]? signature) + out int bytesWritten) { Debug.Assert(!string.IsNullOrEmpty(hashAlgorithm.Name)); Debug.Assert(padding != null); ValidatePadding(padding); + ThrowIfDisposed(); - signature = null; + using (SafeEvpPKeyCtxHandle ctx = CreateCtx()) + { + ctx.ConfigureForRSASign(hashAlgorithm, padding.Mode); - IntPtr digestAlgorithm = Interop.Crypto.HashAlgorithmToEvp(hashAlgorithm.Name); - SafeEvpPKeyHandle key = GetKey(); - int bytesRequired = Interop.Crypto.EvpPKeySize(key); + // We could theoretically pass this through but we need to distinguish between "not enough space" and "failed" + // We could check for presence of private key but that won't work when it's an external key. + if (!ctx.TryGetSufficientSignatureSizeInBytesCore(hash, out int sufficientSignatureSizeInBytes)) + { + throw Interop.Crypto.CreateOpenSslCryptographicException(); + } - if (allocateSignature) - { - Debug.Assert(destination.Length == 0); - signature = new byte[bytesRequired]; - destination = signature; - } - else if (destination.Length < bytesRequired) - { - bytesWritten = 0; - return false; - } + if (destination.Length >= sufficientSignatureSizeInBytes) + { + if (!ctx.TrySignHashCore(hash, destination, out bytesWritten)) + { + // The only reason this could fail won't be related to buffer size so we throw rather returning false + throw Interop.Crypto.CreateOpenSslCryptographicException(); + } - int written = Interop.Crypto.RsaSignHash(key, padding.Mode, digestAlgorithm, hash, destination); - Debug.Assert(written == bytesRequired); - bytesWritten = written; + return true; + } - return true; + // Since sufficientSignatureSizeInBytes can be more than what's actually needed + // we use temporary buffer of sufficient size and see if operation can succeed with that + byte[] signatureDestination = new byte[sufficientSignatureSizeInBytes]; + if (!ctx.TrySignHashCore(hash, signatureDestination, out int bytesWrittenToTemporaryBuffer)) + { + // There is really no reason for this to fail since we already allocated enough + throw Interop.Crypto.CreateOpenSslCryptographicException(); + } + + if (bytesWrittenToTemporaryBuffer > destination.Length) + { + bytesWritten = 0; + return false; + } + + signatureDestination.CopyTo(destination); + bytesWritten = bytesWrittenToTemporaryBuffer; + return true; + } } public override bool VerifyHash( @@ -826,15 +862,11 @@ public override bool VerifyHash(ReadOnlySpan hash, ReadOnlySpan sign ArgumentException.ThrowIfNullOrEmpty(hashAlgorithm.Name, nameof(hashAlgorithm)); ValidatePadding(padding); - IntPtr digestAlgorithm = Interop.Crypto.HashAlgorithmToEvp(hashAlgorithm.Name); - SafeEvpPKeyHandle key = GetKey(); - - return Interop.Crypto.RsaVerifyHash( - key, - padding.Mode, - digestAlgorithm, - hash, - signature); + using (SafeEvpPKeyCtxHandle ctx = CreateCtx()) + { + ctx.ConfigureForRSAVerify(hashAlgorithm, padding.Mode); + return ctx.VerifyHashCore(hash, signature); + } } private static ReadOnlyMemory VerifyPkcs8(ReadOnlyMemory pkcs8) diff --git a/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs b/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs index c19cedc9ff4519..859015550effb6 100644 --- a/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs +++ b/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs @@ -2361,6 +2361,12 @@ public sealed partial class SafeEvpPKeyHandle : System.Runtime.InteropServices.S [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")] [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")] [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")] + public static System.Security.Cryptography.SafeEvpPKeyHandle OpenKeyFromProvider(string providerName, string keyUri) { throw null; } + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")] + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")] + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")] + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")] public static System.Security.Cryptography.SafeEvpPKeyHandle OpenPrivateKeyFromEngine(string engineName, string keyId) { throw null; } [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")] [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] diff --git a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj index b9f0ce5317f7f9..574e3b3065e754 100644 --- a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj +++ b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj @@ -729,8 +729,6 @@ Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.Crypto.cs" /> - - @@ -63,38 +56,20 @@ public ECDiffieHellmanOpenSsl(IntPtr handle) throw new ArgumentException(SR.Cryptography_OpenInvalidHandle, nameof(handle)); ThrowIfNotSupported(); + SafeEcKeyHandle ecKeyHandle = SafeEcKeyHandle.DuplicateHandle(handle); - _key = new ECOpenSsl(ecKeyHandle); - KeySizeValue = _key.KeySize; + _key = Interop.Crypto.CreateEvpPkeyFromEcKey(ecKeyHandle); + KeySizeValue = _key.GetKeySizeBits(); } /// - /// Obtain a SafeHandle version of an EVP_PKEY* which wraps an EC_KEY* equivalent + /// Obtain a SafeHandle version of an EVP_PKEY* /// to the current key for this instance. /// - /// A SafeHandle for the EC_KEY key in OpenSSL + /// A SafeHandle for the EVP_PKEY key in OpenSSL public SafeEvpPKeyHandle DuplicateKeyHandle() { - SafeEcKeyHandle currentKey = GetKey(); - SafeEvpPKeyHandle pkeyHandle = Interop.Crypto.EvpPkeyCreate(); - - try - { - // Wrapping our key in an EVP_PKEY will up_ref our key. - // When the EVP_PKEY is Disposed it will down_ref the key. - // So everything should be copacetic. - if (!Interop.Crypto.EvpPkeySetEcKey(pkeyHandle, currentKey)) - { - throw Interop.Crypto.CreateOpenSslCryptographicException(); - } - - return pkeyHandle; - } - catch - { - pkeyHandle.Dispose(); - throw; - } + return _key.DuplicateHandle(); } static partial void ThrowIfNotSupported() diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDsaOpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDsaOpenSsl.cs index 0887f0a9fb8a63..c32049b634bafc 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDsaOpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDsaOpenSsl.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; using System.Runtime.InteropServices; using System.Runtime.Versioning; using Microsoft.Win32.SafeHandles; @@ -30,16 +31,9 @@ public ECDsaOpenSsl(SafeEvpPKeyHandle pkeyHandle) throw new ArgumentException(SR.Cryptography_OpenInvalidHandle, nameof(pkeyHandle)); ThrowIfNotSupported(); - // If ecKey is valid it has already been up-ref'd, so we can just use this handle as-is. - SafeEcKeyHandle key = Interop.Crypto.EvpPkeyGetEcKey(pkeyHandle); - if (key.IsInvalid) - { - key.Dispose(); - throw Interop.Crypto.CreateOpenSslCryptographicException(); - } - _key = new ECOpenSsl(key); - KeySizeValue = _key.KeySize; + _key = new Lazy(pkeyHandle.DuplicateHandle()); + ForceSetKeySize(pkeyHandle.GetKeySizeBits()); } /// @@ -63,39 +57,24 @@ public ECDsaOpenSsl(IntPtr handle) throw new ArgumentException(SR.Cryptography_OpenInvalidHandle, nameof(handle)); ThrowIfNotSupported(); + + // This handle is meant for ECOpenSsl which will take ownership of this handle and will dispose it. SafeEcKeyHandle ecKeyHandle = SafeEcKeyHandle.DuplicateHandle(handle); - _key = new ECOpenSsl(ecKeyHandle); - KeySizeValue = _key.KeySize; + + // CreateEvpPkeyFromEcKey already uprefs so nothing else to do + _key = new Lazy(Interop.Crypto.CreateEvpPkeyFromEcKey(ecKeyHandle)); + ForceSetKeySize(_key.Value.GetKeySizeBits()); } /// - /// Obtain a SafeHandle version of an EVP_PKEY* which wraps an EC_KEY* equivalent + /// Obtain a SafeHandle version of an EVP_PKEY* equivalent /// to the current key for this instance. /// - /// A SafeHandle for the EC_KEY key in OpenSSL + /// A SafeHandle for the EVP_PKEY key in OpenSSL public SafeEvpPKeyHandle DuplicateKeyHandle() { ThrowIfDisposed(); - SafeEcKeyHandle currentKey = _key.Value; - SafeEvpPKeyHandle pkeyHandle = Interop.Crypto.EvpPkeyCreate(); - - try - { - // Wrapping our key in an EVP_PKEY will up_ref our key. - // When the EVP_PKEY is Disposed it will down_ref the key. - // So everything should be copacetic. - if (!Interop.Crypto.EvpPkeySetEcKey(pkeyHandle, currentKey)) - { - throw Interop.Crypto.CreateOpenSslCryptographicException(); - } - - return pkeyHandle; - } - catch - { - pkeyHandle.Dispose(); - throw; - } + return _key.Value.DuplicateHandle(); } static partial void ThrowIfNotSupported() diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/OpenSsl.NotSupported.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/OpenSsl.NotSupported.cs index cbbfc9912d2e19..19d633684aa4f8 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/OpenSsl.NotSupported.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/OpenSsl.NotSupported.cs @@ -279,6 +279,14 @@ public static SafeEvpPKeyHandle OpenPrivateKeyFromEngine(string engineName, stri public static SafeEvpPKeyHandle OpenPublicKeyFromEngine(string engineName, string keyId) => throw new PlatformNotSupportedException(SR.PlatformNotSupported_CryptographyOpenSSL); + [UnsupportedOSPlatform("android")] + [UnsupportedOSPlatform("browser")] + [UnsupportedOSPlatform("ios")] + [UnsupportedOSPlatform("tvos")] + [UnsupportedOSPlatform("windows")] + public static SafeEvpPKeyHandle OpenKeyFromProvider(string providerName, string keyUri) => + throw new PlatformNotSupportedException(SR.PlatformNotSupported_CryptographyOpenSSL); + public SafeEvpPKeyHandle DuplicateHandle() => null!; public override bool IsInvalid => true; protected override bool ReleaseHandle() => false; diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSAOpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSAOpenSsl.cs index 60fdd0ab1d61ee..c7e2cf3beb8af0 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSAOpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSAOpenSsl.cs @@ -73,10 +73,8 @@ public RSAOpenSsl(SafeEvpPKeyHandle pkeyHandle) throw new ArgumentException(SR.Cryptography_OpenInvalidHandle, nameof(pkeyHandle)); ThrowIfNotSupported(); - SafeEvpPKeyHandle newKey = Interop.Crypto.EvpPKeyDuplicate( - pkeyHandle, - Interop.Crypto.EvpAlgorithmId.RSA); + SafeEvpPKeyHandle newKey = pkeyHandle.DuplicateHandle(); SetKey(newKey); } @@ -87,7 +85,7 @@ public RSAOpenSsl(SafeEvpPKeyHandle pkeyHandle) /// A SafeHandle for the RSA key in OpenSSL public SafeEvpPKeyHandle DuplicateKeyHandle() { - return Interop.Crypto.EvpPKeyDuplicate(GetKey(), Interop.Crypto.EvpAlgorithmId.RSA); + return GetKey().DuplicateHandle(); } static partial void ThrowIfNotSupported() diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SafeEvpPKeyHandle.OpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SafeEvpPKeyHandle.OpenSsl.cs index 5cebb67ab9d71b..776251b0149870 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SafeEvpPKeyHandle.OpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SafeEvpPKeyHandle.OpenSsl.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Runtime.InteropServices; using System.Runtime.Versioning; +using Microsoft.Win32.SafeHandles; namespace System.Security.Cryptography { @@ -11,6 +12,12 @@ public sealed partial class SafeEvpPKeyHandle : SafeHandle { internal static readonly SafeEvpPKeyHandle InvalidHandle = new SafeEvpPKeyHandle(); + /// + /// In some cases like when a key is loaded from a provided, the key may have an associated data + /// we need to keep alive for the lifetime of the key. This field is used to track that data. + /// + internal IntPtr ExtraHandle { get; private set; } = IntPtr.Zero; + [UnsupportedOSPlatform("android")] [UnsupportedOSPlatform("browser")] [UnsupportedOSPlatform("ios")] @@ -31,13 +38,53 @@ public SafeEvpPKeyHandle(IntPtr handle, bool ownsHandle) { } + internal SafeEvpPKeyHandle(IntPtr handle, IntPtr extraHandle) + : base(handle, ownsHandle: true) + { + ExtraHandle = extraHandle; + } + protected override bool ReleaseHandle() { - Interop.Crypto.EvpPkeyDestroy(handle); + Interop.Crypto.EvpPkeyDestroy(handle, ExtraHandle); + ExtraHandle = IntPtr.Zero; + SetHandle(IntPtr.Zero); return true; } + internal static SafeEvpPKeyHandle GenerateRSAKey(int keySize) + { + return Interop.Crypto.RsaGenerateKey(keySize); + } + + internal static SafeEvpPKeyHandle GenerateECKey(int keySize) + { + SafeEvpPKeyHandle ret = GenerateECKeyCore(new ECOpenSsl(keySize), out int createdKeySize); + Debug.Assert(keySize == createdKeySize); + return ret; + } + + internal static SafeEvpPKeyHandle GenerateECKey(ECCurve curve, out int keySize) + { + return GenerateECKeyCore(new ECOpenSsl(curve), out keySize); + } + + internal static SafeEvpPKeyHandle GenerateECKey(ECParameters parameters, out int keySize) + { + return GenerateECKeyCore(new ECOpenSsl(parameters), out keySize); + } + + private static SafeEvpPKeyHandle GenerateECKeyCore(ECOpenSsl ecOpenSsl, out int keySize) + { + using (ECOpenSsl ec = ecOpenSsl) + { + SafeEvpPKeyHandle handle = Interop.Crypto.CreateEvpPkeyFromEcKey(ec.Value); + keySize = ec.KeySize; + return handle; + } + } + public override bool IsInvalid { get { return handle == IntPtr.Zero; } @@ -70,6 +117,8 @@ public SafeEvpPKeyHandle DuplicateHandle() // Since we didn't actually create a new handle, copy the handle // to the new SafeHandle. safeHandle.SetHandle(handle); + // ExtraHandle is upref'd by UpRefEvpPkey + safeHandle.ExtraHandle = ExtraHandle; return safeHandle; } @@ -177,5 +226,50 @@ public static SafeEvpPKeyHandle OpenPublicKeyFromEngine(string engineName, strin return Interop.Crypto.LoadPublicKeyFromEngine(engineName, keyId); } + + [UnsupportedOSPlatform("android")] + [UnsupportedOSPlatform("browser")] + [UnsupportedOSPlatform("ios")] + [UnsupportedOSPlatform("tvos")] + [UnsupportedOSPlatform("windows")] + public static SafeEvpPKeyHandle OpenKeyFromProvider(string providerName, string keyUri) + { + ArgumentException.ThrowIfNullOrEmpty(providerName); + ArgumentException.ThrowIfNullOrEmpty(keyUri); + + if (!Interop.OpenSslNoInit.OpenSslIsAvailable) + { + throw new PlatformNotSupportedException(SR.PlatformNotSupported_CryptographyOpenSSL); + } + + return Interop.Crypto.LoadKeyFromProvider(providerName, keyUri); + } + + internal int GetKeySizeBits() + { + return Interop.Crypto.EvpPKeyBits(this); + } + + internal int GetKeySizeBytes() + { + // EVP_PKEY_size returns the maximum suitable size for the output buffers for almost all operations that can be done with the key. + // For most of the OpenSSL 'default' provider keys it will return the same size as this method, + // but other providers such as 'tpm2' it may return larger size. + // Instead we will round up EVP_PKEY_bits result. + int keySizeBits = GetKeySizeBits(); + + if (keySizeBits <= 0) + { + Debug.Fail($"EVP_PKEY_bits returned non-positive value: {keySizeBits}"); + throw new CryptographicException(); + } + + return (GetKeySizeBits() + 7) / 8; + } + + internal Interop.Crypto.EvpAlgorithmId GetKeyType() + { + return Interop.Crypto.EvpPKeyType(this); + } } } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslExportProvider.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslExportProvider.cs index 355dbc303865d5..8e673efd607880 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslExportProvider.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslExportProvider.cs @@ -22,40 +22,24 @@ protected override byte[] ExportPkcs8( ICertificatePalCore certificatePal, ReadOnlySpan password) { - AsymmetricAlgorithm? alg = null; + AsymmetricAlgorithm alg; SafeEvpPKeyHandle? privateKey = ((OpenSslX509CertificateReader)certificatePal).PrivateKeyHandle; - try + switch (privateKey?.GetKeyType()) { - alg = new RSAOpenSsl(privateKey!); - } - catch (CryptographicException) - { - } - - if (alg == null) - { - try - { - alg = new ECDsaOpenSsl(privateKey!); - } - catch (CryptographicException) - { - } - } - - if (alg == null) - { - try - { - alg = new DSAOpenSsl(privateKey!); - } - catch (CryptographicException) - { - } + case Interop.Crypto.EvpAlgorithmId.RSA: + alg = new RSAOpenSsl(privateKey); + break; + case Interop.Crypto.EvpAlgorithmId.ECC: + alg = new ECDsaOpenSsl(privateKey); + break; + case Interop.Crypto.EvpAlgorithmId.DSA: + alg = new DSAOpenSsl(privateKey); + break; + default: + throw new CryptographicException(SR.Cryptography_InvalidHandle); } - Debug.Assert(alg != null); return alg.ExportEncryptedPkcs8PrivateKey(password, s_windowsPbe); } diff --git a/src/libraries/System.Security.Cryptography/tests/OpenSslNamedKeysTests.manual.cs b/src/libraries/System.Security.Cryptography/tests/OpenSslNamedKeysTests.manual.cs index 06cd2ed2721a11..af073eaf9699b8 100644 --- a/src/libraries/System.Security.Cryptography/tests/OpenSslNamedKeysTests.manual.cs +++ b/src/libraries/System.Security.Cryptography/tests/OpenSslNamedKeysTests.manual.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Linq; using Test.Cryptography; using Xunit; @@ -9,26 +10,58 @@ namespace System.Security.Cryptography.Tests // See osslplugins/README.md for instructions on how to build and install the test engine and setup for TPM tests. public class OpenSslNamedKeysTests { - private const string EnvVarPrefix = "DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_"; - private const string TestEngineEnabledEnvVarName = EnvVarPrefix + "ENABLE"; - private const string TestEngineEnsureFailingEnvVarName = EnvVarPrefix + "ENSURE_FAILING"; - private const string TpmTssEngineEcDsaKeyHandleEnvVarName = EnvVarPrefix + "TPM_ECDSA_KEY_HANDLE"; + private const string EnvVarPrefix = "DOTNET_CRYPTOGRAPHY_TESTS_"; + + private const string TestEnsureFailingEnvVarName = EnvVarPrefix + "ENSURE_FAILING"; + + private const string EngineEnvVarPrefix = EnvVarPrefix + "ENGINE_"; + private const string TestEngineEnabledEnvVarName = EngineEnvVarPrefix + "ENABLE"; + + private const string TpmEnvVarPrefix = EnvVarPrefix + "TPM_"; + private const string TpmEcDsaKeyHandleEnvVarName = TpmEnvVarPrefix + "ECDSA_KEY_HANDLE"; + private const string TpmEcDhKeyHandleEnvVarName = TpmEnvVarPrefix + "ECDH_KEY_HANDLE"; + private const string TpmRsaSignKeyHandleEnvVarName = TpmEnvVarPrefix + "RSA_SIGN_KEY_HANDLE"; + private const string TpmRsaDecryptKeyHandleEnvVarName = TpmEnvVarPrefix + "RSA_DECRYPT_KEY_HANDLE"; private const string NonExistingEngineName = "dntestnonexisting"; - private const string NonExistingEngineKeyName = "nonexisting"; + private const string NonExistingEngineOrProviderKeyName = "nonexisting"; private const string TestEngineName = "dntest"; private const string TestEngineKeyId = "first"; private const string TpmTssEngineName = "tpm2tss"; - public static string TpmTssEngineEcDsaKeyHandle { get; } = Environment.GetEnvironmentVariable(TpmTssEngineEcDsaKeyHandleEnvVarName); - public static bool ShouldRunEngineTests { get; } = PlatformDetection.OpenSslPresentOnSystem && StringToBool(Environment.GetEnvironmentVariable(TestEngineEnabledEnvVarName)); - public static bool ShouldFailTests { get; } = StringToBool(Environment.GetEnvironmentVariable(TestEngineEnsureFailingEnvVarName)); - public static bool ShouldRunTpmTssTests => PlatformDetection.OpenSslPresentOnSystem && !string.IsNullOrEmpty(TpmTssEngineEcDsaKeyHandle); + private const string Tpm2ProviderName = "tpm2"; + + private static string TpmEcDsaKeyHandle { get; } = Environment.GetEnvironmentVariable(TpmEcDsaKeyHandleEnvVarName); + private static string TpmEcDsaKeyHandleUri { get; } = GetHandleKeyUri(TpmEcDsaKeyHandle); + + private static string TpmEcDhKeyHandle { get; } = Environment.GetEnvironmentVariable(TpmEcDhKeyHandleEnvVarName); + private static string TpmEcDhKeyHandleUri { get; } = GetHandleKeyUri(TpmEcDhKeyHandle); + + private static string TpmRsaSignKeyHandle { get; } = Environment.GetEnvironmentVariable(TpmRsaSignKeyHandleEnvVarName); + private static string TpmRsaSignKeyHandleUri { get; } = GetHandleKeyUri(TpmRsaSignKeyHandle); + + private static string TpmRsaDecryptKeyHandle { get; } = Environment.GetEnvironmentVariable(TpmRsaDecryptKeyHandleEnvVarName); + private static string TpmRsaDecryptKeyHandleUri { get; } = GetHandleKeyUri(TpmRsaDecryptKeyHandle); + + public static bool ShouldRunEngineTests { get; } = PlatformDetection.OpenSslPresentOnSystem && StringToBool(Environment.GetEnvironmentVariable(TestEngineEnabledEnvVarName)); + public static bool ShouldRunProviderEcDsaTests { get; } = PlatformDetection.OpenSslPresentOnSystem && !string.IsNullOrEmpty(TpmEcDsaKeyHandleUri); + public static bool ShouldRunProviderEcDhTests { get; } = PlatformDetection.OpenSslPresentOnSystem && !string.IsNullOrEmpty(TpmEcDhKeyHandleUri); + public static bool ShouldRunProviderRsaSignTests { get; } = PlatformDetection.OpenSslPresentOnSystem && !string.IsNullOrEmpty(TpmRsaSignKeyHandleUri); + public static bool ShouldRunProviderRsaDecryptTests { get; } = PlatformDetection.OpenSslPresentOnSystem && !string.IsNullOrEmpty(TpmRsaDecryptKeyHandleUri); + public static bool ShouldRunAnyProviderTests => ShouldRunProviderEcDsaTests || ShouldRunProviderEcDhTests || ShouldRunProviderRsaSignTests || ShouldRunProviderRsaDecryptTests; + + public static bool ShouldFailTests { get; } = StringToBool(Environment.GetEnvironmentVariable(TestEnsureFailingEnvVarName)); + public static bool ShouldRunTpmTssTests => ShouldRunEngineTests && !string.IsNullOrEmpty(TpmEcDsaKeyHandle); + + private static readonly string AnyProviderKeyUri = TpmEcDsaKeyHandleUri ?? TpmEcDhKeyHandleUri ?? TpmRsaSignKeyHandleUri ?? TpmRsaDecryptKeyHandleUri ?? "test"; private static bool StringToBool(string? value) => "true".Equals(value, StringComparison.OrdinalIgnoreCase) || value == "1"; + private static string GetHandleKeyUri(string handle) + => handle != null ? $"handle:{handle}" : null; + // PKCS#1 format private static readonly byte[] s_rsaPrivateKey = ( "3082025C02010002818100BF67168485215A6AB89BCAB9331F6F5F360F4300BE5CF282F77042957E" + @@ -60,6 +93,7 @@ public static void NotSupported() { Assert.Throws(() => SafeEvpPKeyHandle.OpenPublicKeyFromEngine(TestEngineName, TestEngineKeyId)); Assert.Throws(() => SafeEvpPKeyHandle.OpenPrivateKeyFromEngine(TestEngineName, TestEngineKeyId)); + Assert.Throws(() => SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, AnyProviderKeyUri)); } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.OpenSslPresentOnSystem))] @@ -70,32 +104,35 @@ public static void NullArguments() Assert.Throws("engineName", () => SafeEvpPKeyHandle.OpenPublicKeyFromEngine(null, TestEngineKeyId)); Assert.Throws("keyId", () => SafeEvpPKeyHandle.OpenPublicKeyFromEngine(TestEngineName, null)); + + Assert.Throws(() => SafeEvpPKeyHandle.OpenKeyFromProvider(null, AnyProviderKeyUri)); + Assert.Throws(() => SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, null)); } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.OpenSslPresentOnSystem))] - public static void NonExistingEngine() + public static void Engine_NonExisting() { - Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPrivateKeyFromEngine(NonExistingEngineName, TestEngineKeyId)); - Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPublicKeyFromEngine(NonExistingEngineName, TestEngineKeyId)); + Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPrivateKeyFromEngine(NonExistingEngineOrProviderKeyName, TestEngineKeyId)); + Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPublicKeyFromEngine(NonExistingEngineOrProviderKeyName, TestEngineKeyId)); } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.OpenSslPresentOnSystem))] - public static void NonExistingKey() + public static void Provider_NonExisting() { - Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPrivateKeyFromEngine(TestEngineName, NonExistingEngineKeyName)); - Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPublicKeyFromEngine(TestEngineName, NonExistingEngineKeyName)); + Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenKeyFromProvider(NonExistingEngineOrProviderKeyName, AnyProviderKeyUri)); } [ConditionalFact(nameof(ShouldRunEngineTests))] - public static void Engine_SanityTest() + public static void Engine_NonExistingKey() { - Assert.False(ShouldFailTests, "This test is supposed to fail"); + Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPrivateKeyFromEngine(TestEngineName, NonExistingEngineOrProviderKeyName)); + Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPublicKeyFromEngine(TestEngineName, NonExistingEngineOrProviderKeyName)); } - [ConditionalFact(nameof(ShouldRunTpmTssTests))] - public static void Tpm_SanityTest() + [ConditionalFact(nameof(ShouldRunAnyProviderTests))] + public static void Provider_NonExistingKey() { - Assert.False(ShouldFailTests, "This test is supposed to fail"); + Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, NonExistingEngineOrProviderKeyName)); } [ConditionalFact(nameof(ShouldRunEngineTests))] @@ -174,7 +211,7 @@ public static void Engine_UsePublicKey() [ConditionalFact(nameof(ShouldRunTpmTssTests))] public static void Engine_OpenExistingTPMPrivateKey() { - using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenPrivateKeyFromEngine(TpmTssEngineName, TpmTssEngineEcDsaKeyHandle); + using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenPrivateKeyFromEngine(TpmTssEngineName, TpmEcDsaKeyHandle); using ECDsa ecdsaPri = new ECDsaOpenSsl(priKeyHandle); using ECDsa ecdsaBad = ECDsa.Create(); ecdsaBad.KeySize = ecdsaPri.KeySize; @@ -182,9 +219,194 @@ public static void Engine_OpenExistingTPMPrivateKey() byte[] data = new byte[] { 1, 2, 3, 1, 1, 2, 3 }; byte[] signature = ecdsaPri.SignData(data, HashAlgorithmName.SHA256); byte[] badSignature = ecdsaBad.SignData(data, HashAlgorithmName.SHA256); + Assert.Equal(signature.Length, badSignature.Length); Assert.NotEqual(data, signature); Assert.True(ecdsaPri.VerifyData(data, signature, HashAlgorithmName.SHA256)); Assert.False(ecdsaPri.VerifyData(data, badSignature, HashAlgorithmName.SHA256)); } + + [ConditionalFact(nameof(ShouldRunProviderEcDsaTests))] + public static void Provider_TPM2ECDSA() + { + using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, TpmEcDsaKeyHandleUri); + using ECDsa ecdsaPri = new ECDsaOpenSsl(priKeyHandle); + + byte[] data = new byte[] { 1, 2, 3, 1, 1, 2, 3 }; + + byte[] ecdsaPubBytes = ecdsaPri.ExportSubjectPublicKeyInfo(); + ECDsa ecdsaPub = ECDsa.Create(); + ecdsaPub.ImportSubjectPublicKeyInfo(ecdsaPubBytes, out int bytesRead); + Assert.Equal(ecdsaPubBytes.Length, bytesRead); + + using ECDsa ecdsaBad = ECDsa.Create(); + ecdsaBad.KeySize = ecdsaPri.KeySize; + + // Verify can sign/verify multiple times + for (int i = 0; i < 10; i++) + { + data[0] = (byte)i; + byte[] signature = ecdsaPri.SignData(data, HashAlgorithmName.SHA256); + byte[] badSignature = ecdsaBad.SignData(data, HashAlgorithmName.SHA256); + Assert.NotEqual(data, signature); + Assert.NotEqual(data, badSignature); + Assert.NotEqual(badSignature, signature); + Assert.True(ecdsaPub.VerifyData(data, signature, HashAlgorithmName.SHA256)); + Assert.False(ecdsaPub.VerifyData(data, badSignature, HashAlgorithmName.SHA256)); + Assert.False(ecdsaBad.VerifyData(data, signature, HashAlgorithmName.SHA256)); + + // TPM key is intended for sign/decrypt only, we could theoretically make verify work without needing to export/import by forcing 'default' provider + // for this operation but it's most likely misusage on user part and tpm2 provider intentionally didn't allow it so we will follow this logic. + Assert.ThrowsAny(() => ecdsaPri.VerifyData(data, signature, HashAlgorithmName.SHA256)); + } + + // It's TPM so it should not be possible to export parameters + Assert.ThrowsAny(() => ecdsaPri.ExportParameters(includePrivateParameters: true)); + } + + [ConditionalFact(nameof(ShouldRunProviderEcDhTests))] + public static void Provider_TPM2ECDH() + { + using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, TpmEcDhKeyHandleUri); + using ECDiffieHellman alicePri = new ECDiffieHellmanOpenSsl(priKeyHandle); + using ECDiffieHellman alicePub = ECDiffieHellman.Create(); + alicePub.ImportParameters(alicePri.ExportParameters(includePrivateParameters: false)); + + using ECDiffieHellman bobPri = ECDiffieHellman.Create(); + bobPri.KeySize = alicePri.KeySize; + + using ECDiffieHellman bobPub = ECDiffieHellman.Create(); + bobPub.ImportParameters(bobPri.ExportParameters(includePrivateParameters: false)); + + byte[] sharedKeyFromAlice; + using (ECDiffieHellmanPublicKey bobPublic = bobPub.PublicKey) + { + sharedKeyFromAlice = alicePri.DeriveKeyMaterial(bobPublic); + + Assert.NotEmpty(sharedKeyFromAlice); + + byte firstByte = sharedKeyFromAlice[0]; + bool allSame = sharedKeyFromAlice.All((x) => x == firstByte); + Assert.False(allSame, "all bytes of shared key are the same"); + } + + using (ECDiffieHellmanPublicKey alicePublic = alicePub.PublicKey) + { + byte[] sharedKeyFromBob = bobPri.DeriveKeyMaterial(alicePublic); + Assert.Equal(sharedKeyFromAlice, sharedKeyFromBob); + } + } + + [ConditionalFact(nameof(ShouldRunProviderRsaSignTests))] + [ActiveIssue("https://github.com/tpm2-software/tpm2-openssl/issues/115")] + // or a workaround API proposal + [ActiveIssue("https://github.com/dotnet/runtime/issues/104080")] + public static void Provider_TPM2SignRsa() + { + using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, TpmRsaSignKeyHandleUri); + using RSA rsaPri = new RSAOpenSsl(priKeyHandle); + byte[] rsaPubBytes = rsaPri.ExportSubjectPublicKeyInfo(); + RSA rsaPub = RSA.Create(); + rsaPub.ImportSubjectPublicKeyInfo(rsaPubBytes, out int bytesRead); + Assert.Equal(rsaPubBytes.Length, bytesRead); + + using RSA rsaBad = RSA.Create(); + rsaBad.KeySize = rsaPri.KeySize; + + byte[] data = new byte[] { 1, 2, 3, 1, 1, 2, 3 }; + byte[] badSignature = rsaBad.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pss); + + // can use same key more than once + for (int i = 0; i < 10; i++) + { + data[0] = (byte)i; + byte[] signature = rsaPri.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pss); + Assert.True(rsaPub.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss), "signature does not verify with the right key"); + Assert.False(rsaPub.VerifyData(data, badSignature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss), "signature should not verify with the wrong key"); + + signature[12] ^= 1; + Assert.False(rsaPub.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss), "tampered signature should not verify"); + + // TPM key is intended for sign only, we could theoretically make verify work without needing to export/import by forcing 'default' provider + // for this operation it's most likely misusage on user part and tpm2 provider intentionally didn't allow it so we will follow this logic. + Assert.ThrowsAny(() => rsaPri.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss)); + } + } + + [ConditionalFact(nameof(ShouldRunProviderRsaDecryptTests))] + public static void Provider_TPM2DecryptRsa() + { + // TPM2 OAEP support was added in the second half of 2023 therefore we only test Pkcs1 padding + // See: https://github.com/tpm2-software/tpm2-openssl/issues/89 + RSAEncryptionPadding padding = RSAEncryptionPadding.Pkcs1; + using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, TpmRsaDecryptKeyHandleUri); + using RSA rsaPri = new RSAOpenSsl(priKeyHandle); + byte[] rsaPubBytes = rsaPri.ExportSubjectPublicKeyInfo(); + RSA rsaPub = RSA.Create(); + rsaPub.ImportSubjectPublicKeyInfo(rsaPubBytes, out int bytesRead); + Assert.Equal(rsaPubBytes.Length, bytesRead); + + using RSA rsaBad = RSA.Create(); + rsaBad.KeySize = rsaPri.KeySize; + + byte[] data = new byte[] { 1, 2, 3, 1, 1, 2, 3 }; + byte[] encryptedWithDifferentKey = rsaBad.Encrypt(data, padding); + Assert.ThrowsAny(() => rsaPri.Decrypt(encryptedWithDifferentKey, padding)); + + // TPM private key is intended only for decrypt + Assert.ThrowsAny(() => rsaPri.Encrypt(data, padding)); + + // can use same key more than once + for (int i = 0; i < 10; i++) + { + data[0] = (byte)i; + byte[] encrypted = rsaPub.Encrypt(data, padding); + Assert.NotEqual(encrypted, data); + + Assert.Equal(data, rsaPri.Decrypt(encrypted, padding)); + } + } + + // Sanity tests for flags + [ConditionalFact(nameof(ShouldRunEngineTests))] + public static void SanityTest_EngineAny() + { + Assert.False(ShouldFailTests, "This test is supposed to fail"); + } + + [ConditionalFact(nameof(ShouldRunTpmTssTests))] + public static void SanityTest_EngineTpmTss() + { + Assert.False(ShouldFailTests, "This test is supposed to fail"); + } + + [ConditionalFact(nameof(ShouldRunAnyProviderTests))] + public static void SanityTest_AnyTpm2Provider() + { + Assert.False(ShouldFailTests, "This test is supposed to fail"); + } + + [ConditionalFact(nameof(ShouldRunProviderEcDsaTests))] + public static void SanityTest_Tpm2ProviderEcDsa() + { + Assert.False(ShouldFailTests, "This test is supposed to fail"); + } + + [ConditionalFact(nameof(ShouldRunProviderEcDhTests))] + public static void SanityTest_Tpm2ProviderEcDh() + { + Assert.False(ShouldFailTests, "This test is supposed to fail"); + } + + [ConditionalFact(nameof(ShouldRunProviderRsaSignTests))] + public static void SanityTest_Tpm2ProviderRsaSign() + { + Assert.False(ShouldFailTests, "This test is supposed to fail"); + } + + [ConditionalFact(nameof(ShouldRunProviderRsaDecryptTests))] + public static void SanityTest_Tpm2ProviderRsaDecrypt() + { + Assert.False(ShouldFailTests, "This test is supposed to fail"); + } } } diff --git a/src/libraries/System.Security.Cryptography/tests/osslplugins/README.md b/src/libraries/System.Security.Cryptography/tests/osslplugins/README.md index 04d3cdae5fd995..fd7bc9f37a99f5 100644 --- a/src/libraries/System.Security.Cryptography/tests/osslplugins/README.md +++ b/src/libraries/System.Security.Cryptography/tests/osslplugins/README.md @@ -1,8 +1,14 @@ +TODO: update names of variables and review +TODO: add section with logging https://github.com/tpm2-software/tpm2-tss/blob/master/doc/logging.md +TODO: export TSS2_LOG=all+NONE + + # Testing instructions for OpenSSL ENGINE Once everything is setup tests related to TPM and our engine can be run using: ```bash +export DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_ENABLE=true ./test.sh ``` @@ -12,7 +18,7 @@ If TPM environmental variable similar to following is defined: ```bash # 0x81000007 is just an example, read further how to get it -export DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_TPM_ECDSA_KEY_HANDLE=0x81000007 +export DOTNET_CRYPTOGRAPHY_TESTS_TPM_ECDSA_KEY_HANDLE=0x81000007 ``` then tests using TPM will be run as well and they will use `0x81000007` handle. @@ -114,7 +120,33 @@ export TSS2_LOG=all+TRACE Most of the time this should not be needed but it might be useful if you're seeing issues when interacting with the ENGINE. -### Getting TPM handle +# Testing instructions for OpenSSL Provider + +## Installation + +To install TPM2 provider refer to https://github.com/tpm2-software/tpm2-openssl - on Ubuntu following step can be used: + +```bash +sudo apt install tpm2-openssl tpm2-tools tpm2-abrmd libtss2-tcti-tabrmd0 +``` + +## Running provider tests + +In order to run provider tests you need to have TPM handles and set one or more of the following environmental variables: + +```csharp +# Handle values are just an example - refer to 'Getting TPM handle' section for instructions on how to create or get them +export DOTNET_CRYPTOGRAPHY_TESTS_TPM_ECDSA_KEY_HANDLE=0x81000007 +export DOTNET_CRYPTOGRAPHY_TESTS_TPM_ECDH_KEY_HANDLE=0x8100000d +export DOTNET_CRYPTOGRAPHY_TESTS_TPM_RSA_DECRYPT_KEY_HANDLE=0x8100000c + +# RSA-PSS tests will always fail due to following issues but they can be run still +# https://github.com/dotnet/runtime/issues/104080 +# https://github.com/tpm2-software/tpm2-openssl/issues/115 +export DOTNET_CRYPTOGRAPHY_TESTS_TPM_RSA_SIGN_KEY_HANDLE=0x8100000a +``` + +# Getting TPM handle First, we will need `tpm2-tools`` installed: @@ -122,7 +154,7 @@ First, we will need `tpm2-tools`` installed: sudo apt install tpm2-tools ``` -#### Getting TPM handles +## Getting TPM handles If you already have a handle but you forgot what it is you can list all available handles using following command: @@ -145,11 +177,11 @@ You can also extract public key like this if needed: tpm2_readpublic -c 0x81000007 -o /tmp/key.pub ``` -#### Testing handle with OpenSSL CLI +### Testing handle with OpenSSL CLI In case you find issues with your handle you can test it using OpenSSL CLI, for example `0x81000004` can be tested like following: -##### RSA key +#### RSA key ```bash # create testdata file with some content @@ -168,9 +200,9 @@ openssl pkey -engine tpm2tss -inform engine -in '0x81000007' -pubout -out testke openssl pkeyutl -verify -in testdata.dgst -sigfile testdata.sig -inkey testkey.pub -pubin -pkeyopt digest:sha256 ``` -#### Creating keys and handles +## Creating keys and handles -##### ECDSA key +### ECDSA key ```bash tpm2_createprimary -C o -g sha256 -G ecc256:ecdsa-sha256:null -c primary.ctx -a 'fixedtpm|fixedparent|sensitivedataorigin|userwithauth|noda|sign' @@ -179,7 +211,16 @@ tpm2_createprimary -C o -g sha256 -G ecc256:ecdsa-sha256:null -c primary.ctx -a tpm2_evictcontrol -C o -c primary.ctx ``` -##### RSA key (RSAPSS + SHA256) +### ECDH key + +```bash +tpm2_createprimary -C o -g sha256 -G ecc256 -c primary.ctx -a 'fixedtpm|fixedparent|sensitivedataorigin|userwithauth|noda|decrypt' + +# To create permenent handle and print it: +tpm2_evictcontrol -C o -c primary.ctx +``` + +### RSA key (RSAPSS + SHA256) This is not used by tests but if needed for further testing: @@ -190,3 +231,15 @@ tpm2_createprimary -C o -g sha256 -G rsa2048:rsapss:null -c primary.ctx -a 'fixe # To create permenent handle and print it: tpm2_evictcontrol -C o -c primary.ctx ``` + +### RSA key (decryption) + +This is not used by tests but if needed for further testing: + +```bash +# To create key +tpm2_createprimary -C o -g sha256 -G rsa2048 -c primary.ctx -a 'fixedtpm|fixedparent|sensitivedataorigin|userwithauth|noda|decrypt' + +# To create permenent handle and print it: +tpm2_evictcontrol -C o -c primary.ctx +``` diff --git a/src/libraries/System.Security.Cryptography/tests/osslplugins/test.sh b/src/libraries/System.Security.Cryptography/tests/osslplugins/test.sh index c13dd079319a58..9d61736d6c24d6 100755 --- a/src/libraries/System.Security.Cryptography/tests/osslplugins/test.sh +++ b/src/libraries/System.Security.Cryptography/tests/osslplugins/test.sh @@ -17,23 +17,63 @@ dotnet="$repo_root_path/dotnet.sh" nativelibs_path="$src_path/native/libs" -export DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_ENABLE=true +if [ -z "$DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_ENABLE" ]; then + echo "WARNING: Engine tests will not be run" + echo "WARNING: Use following variable to enable them:" + echo "WARNING: export DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_ENABLE=true" + echo "WARNING: Refer to README.md for more information." + echo +fi -if [ -z "$DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_TPM_ECDSA_KEY_HANDLE" ]; then - echo "WARNING: TPM tests will not be run" +if [ -z "$DOTNET_CRYPTOGRAPHY_TESTS_TPM_ECDSA_KEY_HANDLE" ]; then + echo "WARNING: TPM ECDSA tests will not be run" echo "WARNING: Use following environmental variable to enable them:" - echo "WARNING: export DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_TPM_ECDSA_KEY_HANDLE=YourHandleHere" + echo "WARNING: export DOTNET_CRYPTOGRAPHY_TESTS_TPM_ECDSA_KEY_HANDLE=YourHandleHere" echo "WARNING: For example:" - echo "WARNING: export DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_TPM_ECDSA_KEY_HANDLE=0x81000007" + echo "WARNING: export DOTNET_CRYPTOGRAPHY_TESTS_TPM_ECDSA_KEY_HANDLE=0x81000007" echo "WARNING: Refer to README.md for more information on how to get handle." + echo +fi + +if [ -z "$DOTNET_CRYPTOGRAPHY_TESTS_TPM_ECDH_KEY_HANDLE" ]; then + echo "WARNING: TPM ECDH tests will not be run" + echo "WARNING: Use following environmental variable to enable them:" + echo "WARNING: export DOTNET_CRYPTOGRAPHY_TESTS_TPM_ECDH_KEY_HANDLE=YourHandleHere" + echo "WARNING: For example:" + echo "WARNING: export DOTNET_CRYPTOGRAPHY_TESTS_TPM_ECDH_KEY_HANDLE=0x8100000d" + echo "WARNING: Refer to README.md for more information on how to get handle." + echo +fi + +if [ -z "$DOTNET_CRYPTOGRAPHY_TESTS_TPM_RSA_SIGN_KEY_HANDLE" ]; then + echo 'WARNING: [ActiveIssue("https://github.com/tpm2-software/tpm2-openssl/issues/115")]' + echo 'WARNING: [ActiveIssue("https://github.com/dotnet/runtime/issues/104080")]' + echo "WARNING: TPM RSA sign tests will not be run" + echo "WARNING: Use following environmental variable to enable them:" + echo "WARNING: export DOTNET_CRYPTOGRAPHY_TESTS_TPM_RSA_SIGN_KEY_HANDLE=YourHandleHere" + echo "WARNING: For example:" + echo "WARNING: export DOTNET_CRYPTOGRAPHY_TESTS_TPM_RSA_SIGN_KEY_HANDLE=0x8100000a" + echo "WARNING: Refer to README.md for more information on how to get handle." + echo +fi + +if [ -z "$DOTNET_CRYPTOGRAPHY_TESTS_TPM_RSA_DECRYPT_KEY_HANDLE" ]; then + echo "WARNING: TPM RSA decrypt tests will not be run" + echo "WARNING: Use following environmental variable to enable them:" + echo "WARNING: export DOTNET_CRYPTOGRAPHY_TESTS_TPM_RSA_DECRYPT_KEY_HANDLE=YourHandleHere" + echo "WARNING: For example:" + echo "WARNING: export DOTNET_CRYPTOGRAPHY_TESTS_TPM_RSA_DECRYPT_KEY_HANDLE=0x8100000c" + echo "WARNING: Refer to README.md for more information on how to get handle." + echo fi if [ "$1" == "--self-check" ]; then - export DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_ENSURE_FAILING=true + export DOTNET_CRYPTOGRAPHY_TESTS_ENSURE_FAILING=true else echo "INFO: To run self-check use:" echo "INFO: ./test.sh --self-check" - echo "INFO: Expect two test failures." + echo "INFO: Expect test failures one per each category of tests (custom ENGINE, ECDSA, ECDH, RSA sign, RSA decrypt)." + echo fi set -e @@ -45,4 +85,5 @@ cd "$ssc_src_path" $dotnet build cd "$ssc_tests_path" + $dotnet test --filter "FullyQualifiedName~System.Security.Cryptography.Tests.OpenSslNamedKeysTests." diff --git a/src/native/libs/System.Security.Cryptography.Native/CMakeLists.txt b/src/native/libs/System.Security.Cryptography.Native/CMakeLists.txt index c2808d3fd7646d..de817d0834ac42 100644 --- a/src/native/libs/System.Security.Cryptography.Native/CMakeLists.txt +++ b/src/native/libs/System.Security.Cryptography.Native/CMakeLists.txt @@ -26,7 +26,6 @@ set(NATIVECRYPTO_SOURCES pal_bignum.c pal_bio.c pal_dsa.c - pal_ecdsa.c pal_ecc_import_export.c pal_eckey.c pal_err.c diff --git a/src/native/libs/System.Security.Cryptography.Native/entrypoints.c b/src/native/libs/System.Security.Cryptography.Native/entrypoints.c index fd9c808597db2e..f1f159ff0d2a78 100644 --- a/src/native/libs/System.Security.Cryptography.Native/entrypoints.c +++ b/src/native/libs/System.Security.Cryptography.Native/entrypoints.c @@ -10,7 +10,6 @@ #include "pal_bio.h" #include "pal_dsa.h" #include "pal_ecc_import_export.h" -#include "pal_ecdsa.h" #include "pal_eckey.h" #include "pal_err.h" #include "pal_evp.h" @@ -70,9 +69,6 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_DsaSizeSignature) DllImportEntry(CryptoNative_DsaUpRef) DllImportEntry(CryptoNative_DsaVerify) - DllImportEntry(CryptoNative_EcDsaSign) - DllImportEntry(CryptoNative_EcDsaSize) - DllImportEntry(CryptoNative_EcDsaVerify) DllImportEntry(CryptoNative_EcKeyCreateByExplicitParameters) DllImportEntry(CryptoNative_EcKeyCreateByKeyParameters) DllImportEntry(CryptoNative_EcKeyCreateByOid) @@ -164,15 +160,19 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_EvpPkeyCreate) DllImportEntry(CryptoNative_EvpPKeyCreateRsa) DllImportEntry(CryptoNative_EvpPKeyCtxCreate) + DllImportEntry(CryptoNative_EvpPKeyCtxCreateFromPKey) + DllImportEntry(CryptoNative_EvpPKeyCtxConfigureForECDSASign) + DllImportEntry(CryptoNative_EvpPKeyCtxConfigureForECDSAVerify) + DllImportEntry(CryptoNative_EvpPKeyCtxSignHash) + DllImportEntry(CryptoNative_EvpPKeyCtxVerifyHash) DllImportEntry(CryptoNative_EvpPKeyCtxDestroy) DllImportEntry(CryptoNative_EvpPKeyDeriveSecretAgreement) DllImportEntry(CryptoNative_EvpPkeyDestroy) - DllImportEntry(CryptoNative_EvpPKeyDuplicate) DllImportEntry(CryptoNative_EvpPkeyGetDsa) DllImportEntry(CryptoNative_EvpPkeyGetEcKey) DllImportEntry(CryptoNative_EvpPkeySetDsa) DllImportEntry(CryptoNative_EvpPkeySetEcKey) - DllImportEntry(CryptoNative_EvpPKeySize) + DllImportEntry(CryptoNative_EvpPKeyBits) DllImportEntry(CryptoNative_EvpRC2Cbc) DllImportEntry(CryptoNative_EvpRC2Ecb) DllImportEntry(CryptoNative_EvpSha1) @@ -230,6 +230,7 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_HmacOneShot) DllImportEntry(CryptoNative_HmacReset) DllImportEntry(CryptoNative_HmacUpdate) + DllImportEntry(CryptoNative_LoadKeyFromProvider) DllImportEntry(CryptoNative_LoadPrivateKeyFromEngine) DllImportEntry(CryptoNative_LoadPublicKeyFromEngine) DllImportEntry(CryptoNative_LookupFriendlyNameByOid) @@ -256,9 +257,10 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_RsaDecrypt) DllImportEntry(CryptoNative_RsaEncrypt) DllImportEntry(CryptoNative_RsaGenerateKey) - DllImportEntry(CryptoNative_RsaSignHash) - DllImportEntry(CryptoNative_RsaVerifyHash) + DllImportEntry(CryptoNative_EvpPKeyCtxConfigureForRsaSign) + DllImportEntry(CryptoNative_EvpPKeyCtxConfigureForRsaVerify) DllImportEntry(CryptoNative_UpRefEvpPkey) + DllImportEntry(CryptoNative_EvpPKeyType) DllImportEntry(CryptoNative_X509BuildOcspRequest) DllImportEntry(CryptoNative_X509ChainBuildOcspRequest) DllImportEntry(CryptoNative_X509ChainGetCachedOcspStatus) diff --git a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h index 3541c7558484ea..1562cec98ec0cf 100644 --- a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h +++ b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h @@ -43,6 +43,7 @@ #if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_3_0_RTM #include +#include #include #include #endif @@ -430,6 +431,7 @@ extern bool g_libSslUses32BitTime; REQUIRED_FUNCTION(EVP_PKEY_CTX_get0_pkey) \ REQUIRED_FUNCTION(EVP_PKEY_CTX_new) \ REQUIRED_FUNCTION(EVP_PKEY_CTX_new_id) \ + LIGHTUP_FUNCTION(EVP_PKEY_CTX_new_from_pkey) \ FALLBACK_FUNCTION(EVP_PKEY_CTX_set_rsa_keygen_bits) \ FALLBACK_FUNCTION(EVP_PKEY_CTX_set_rsa_oaep_md) \ FALLBACK_FUNCTION(EVP_PKEY_CTX_set_rsa_padding) \ @@ -446,6 +448,7 @@ extern bool g_libSslUses32BitTime; REQUIRED_FUNCTION(EVP_PKEY_free) \ RENAMED_FUNCTION(EVP_PKEY_get_base_id, EVP_PKEY_base_id) \ RENAMED_FUNCTION(EVP_PKEY_get_size, EVP_PKEY_size) \ + RENAMED_FUNCTION(EVP_PKEY_get_bits, EVP_PKEY_bits) \ FALLBACK_FUNCTION(EVP_PKEY_get0_RSA) \ REQUIRED_FUNCTION(EVP_PKEY_get1_DSA) \ REQUIRED_FUNCTION(EVP_PKEY_get1_EC_KEY) \ @@ -525,7 +528,20 @@ extern bool g_libSslUses32BitTime; RENAMED_FUNCTION(OPENSSL_sk_push, sk_push) \ RENAMED_FUNCTION(OPENSSL_sk_value, sk_value) \ FALLBACK_FUNCTION(OpenSSL_version_num) \ + LIGHTUP_FUNCTION(OSSL_LIB_CTX_free) \ + LIGHTUP_FUNCTION(OSSL_LIB_CTX_new) \ + LIGHTUP_FUNCTION(OSSL_PROVIDER_load) \ LIGHTUP_FUNCTION(OSSL_PROVIDER_try_load) \ + LIGHTUP_FUNCTION(OSSL_PROVIDER_unload) \ + LIGHTUP_FUNCTION(OSSL_STORE_close) \ + LIGHTUP_FUNCTION(OSSL_STORE_eof) \ + LIGHTUP_FUNCTION(OSSL_STORE_INFO_free) \ + LIGHTUP_FUNCTION(OSSL_STORE_INFO_get_type) \ + LIGHTUP_FUNCTION(OSSL_STORE_INFO_get1_PKEY) \ + LIGHTUP_FUNCTION(OSSL_STORE_INFO_get1_PUBKEY) \ + LIGHTUP_FUNCTION(OSSL_STORE_load) \ + LIGHTUP_FUNCTION(OSSL_STORE_open) \ + LIGHTUP_FUNCTION(OSSL_STORE_open_ex) \ LIGHTUP_FUNCTION(OSSL_PARAM_construct_octet_string) \ LIGHTUP_FUNCTION(OSSL_PARAM_construct_int32) \ LIGHTUP_FUNCTION(OSSL_PARAM_construct_end) \ @@ -978,6 +994,7 @@ extern TYPEOF(OPENSSL_gmtime)* OPENSSL_gmtime_ptr; #define EVP_PKEY_free EVP_PKEY_free_ptr #define EVP_PKEY_get_base_id EVP_PKEY_get_base_id_ptr #define EVP_PKEY_get_size EVP_PKEY_get_size_ptr +#define EVP_PKEY_get_bits EVP_PKEY_get_bits_ptr #define EVP_PKEY_get0_RSA EVP_PKEY_get0_RSA_ptr #define EVP_PKEY_get1_DSA EVP_PKEY_get1_DSA_ptr #define EVP_PKEY_get1_EC_KEY EVP_PKEY_get1_EC_KEY_ptr @@ -985,6 +1002,7 @@ extern TYPEOF(OPENSSL_gmtime)* OPENSSL_gmtime_ptr; #define EVP_PKEY_keygen EVP_PKEY_keygen_ptr #define EVP_PKEY_keygen_init EVP_PKEY_keygen_init_ptr #define EVP_PKEY_new EVP_PKEY_new_ptr +#define EVP_PKEY_CTX_new_from_pkey EVP_PKEY_CTX_new_from_pkey_ptr #define EVP_PKEY_public_check EVP_PKEY_public_check_ptr #define EVP_PKEY_set1_DSA EVP_PKEY_set1_DSA_ptr #define EVP_PKEY_set1_EC_KEY EVP_PKEY_set1_EC_KEY_ptr @@ -1058,7 +1076,20 @@ extern TYPEOF(OPENSSL_gmtime)* OPENSSL_gmtime_ptr; #define OPENSSL_sk_push OPENSSL_sk_push_ptr #define OPENSSL_sk_value OPENSSL_sk_value_ptr #define OpenSSL_version_num OpenSSL_version_num_ptr +#define OSSL_LIB_CTX_free OSSL_LIB_CTX_free_ptr +#define OSSL_LIB_CTX_new OSSL_LIB_CTX_new_ptr +#define OSSL_PROVIDER_load OSSL_PROVIDER_load_ptr #define OSSL_PROVIDER_try_load OSSL_PROVIDER_try_load_ptr +#define OSSL_PROVIDER_unload OSSL_PROVIDER_unload_ptr +#define OSSL_STORE_close OSSL_STORE_close_ptr +#define OSSL_STORE_eof OSSL_STORE_eof_ptr +#define OSSL_STORE_INFO_free OSSL_STORE_INFO_free_ptr +#define OSSL_STORE_INFO_get_type OSSL_STORE_INFO_get_type_ptr +#define OSSL_STORE_INFO_get1_PKEY OSSL_STORE_INFO_get1_PKEY_ptr +#define OSSL_STORE_INFO_get1_PUBKEY OSSL_STORE_INFO_get1_PUBKEY_ptr +#define OSSL_STORE_load OSSL_STORE_load_ptr +#define OSSL_STORE_open OSSL_STORE_open_ptr +#define OSSL_STORE_open_ex OSSL_STORE_open_ex_ptr #define OSSL_PARAM_construct_octet_string OSSL_PARAM_construct_octet_string_ptr #define OSSL_PARAM_construct_int32 OSSL_PARAM_construct_int32_ptr #define OSSL_PARAM_construct_end OSSL_PARAM_construct_end_ptr @@ -1333,9 +1364,9 @@ extern TYPEOF(OPENSSL_gmtime)* OPENSSL_gmtime_ptr; #define EVP_MD_get_size EVP_MD_size #define EVP_PKEY_get_base_id EVP_PKEY_base_id #define EVP_PKEY_get_size EVP_PKEY_size +#define EVP_PKEY_get_bits EVP_PKEY_bits #define SSL_get1_peer_certificate SSL_get_peer_certificate #define EVP_CIPHER_get_nid EVP_CIPHER_nid - #endif #if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_3_0_RTM diff --git a/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h b/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h index 78b626b1c3809e..930b4e78fc3c60 100644 --- a/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h +++ b/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h @@ -17,9 +17,15 @@ #define OSSL_MAC_PARAM_XOF "xof" #define OSSL_MAC_PARAM_SIZE "size" -typedef struct ossl_provider_st OSSL_PROVIDER; +#define OSSL_STORE_INFO_PKEY 4 +#define OSSL_STORE_INFO_PUBKEY 3 + typedef struct ossl_lib_ctx_st OSSL_LIB_CTX; typedef struct ossl_param_st OSSL_PARAM; +typedef struct ossl_provider_st OSSL_PROVIDER; +typedef struct ossl_store_ctx_st OSSL_STORE_CTX; +typedef struct ossl_store_info_st OSSL_STORE_INFO; +typedef OSSL_STORE_INFO* (*OSSL_STORE_post_process_info_fn)(OSSL_STORE_INFO*, void*); struct ossl_param_st { @@ -54,10 +60,28 @@ int EVP_PKEY_CTX_set_rsa_pss_saltlen(EVP_PKEY_CTX* ctx, int saltlen); int EVP_PKEY_CTX_set_signature_md(EVP_PKEY_CTX* ctx, const EVP_MD* md); int EVP_PKEY_get_base_id(const EVP_PKEY* pkey); int EVP_PKEY_get_size(const EVP_PKEY* pkey); +int EVP_PKEY_get_bits(const EVP_PKEY* pkey); OSSL_PARAM OSSL_PARAM_construct_end(void); OSSL_PARAM OSSL_PARAM_construct_int32(const char *key, int32_t *buf); OSSL_PARAM OSSL_PARAM_construct_octet_string(const char *key, void *buf, size_t bsize); OSSL_PROVIDER* OSSL_PROVIDER_try_load(OSSL_LIB_CTX* , const char* name, int retain_fallbacks); +void OSSL_LIB_CTX_free(OSSL_LIB_CTX*); +OSSL_LIB_CTX* OSSL_LIB_CTX_new(void); +OSSL_PROVIDER* OSSL_PROVIDER_load(OSSL_LIB_CTX*, const char* name); +OSSL_PROVIDER* OSSL_PROVIDER_try_load(OSSL_LIB_CTX*, const char* name, int retain_fallbacks); +int OSSL_PROVIDER_unload(OSSL_PROVIDER* prov); +int OSSL_STORE_close(OSSL_STORE_CTX* ctx); +int OSSL_STORE_eof(OSSL_STORE_CTX* ctx); +OSSL_STORE_INFO* OSSL_STORE_load(OSSL_STORE_CTX* ctx); +void OSSL_STORE_INFO_free(OSSL_STORE_INFO* info); +int OSSL_STORE_INFO_get_type(const OSSL_STORE_INFO* info); +EVP_PKEY* OSSL_STORE_INFO_get1_PKEY(const OSSL_STORE_INFO* info); +EVP_PKEY* OSSL_STORE_INFO_get1_PUBKEY(const OSSL_STORE_INFO* info); +OSSL_STORE_CTX* OSSL_STORE_open( + const char*, const UI_METHOD*, void*, OSSL_STORE_post_process_info_fn post_process, void*); +OSSL_STORE_CTX* OSSL_STORE_open_ex( + const char*, OSSL_LIB_CTX*, const char*, const UI_METHOD*, void*, const OSSL_PARAM*, OSSL_STORE_post_process_info_fn post_process, void*); + X509* SSL_get1_peer_certificate(const SSL* ssl); diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_ecdsa.c b/src/native/libs/System.Security.Cryptography.Native/pal_ecdsa.c deleted file mode 100644 index 43792d35b6ea53..00000000000000 --- a/src/native/libs/System.Security.Cryptography.Native/pal_ecdsa.c +++ /dev/null @@ -1,34 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#include "pal_ecdsa.h" -#include "pal_utilities.h" - -int32_t -CryptoNative_EcDsaSign(const uint8_t* dgst, int32_t dgstlen, uint8_t* sig, int32_t* siglen, EC_KEY* key) -{ - ERR_clear_error(); - - if (!siglen) - { - return 0; - } - - unsigned int unsignedSigLength = 0; - int ret = ECDSA_sign(0, dgst, dgstlen, sig, &unsignedSigLength, key); - *siglen = Uint32ToInt32(unsignedSigLength); - return ret; -} - -int32_t -CryptoNative_EcDsaVerify(const uint8_t* dgst, int32_t dgstlen, const uint8_t* sig, int32_t siglen, EC_KEY* key) -{ - ERR_clear_error(); - return ECDSA_verify(0, dgst, dgstlen, sig, siglen, key); -} - -int32_t CryptoNative_EcDsaSize(const EC_KEY* key) -{ - // No error queue impact. - return ECDSA_size(key); -} diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_ecdsa.h b/src/native/libs/System.Security.Cryptography.Native/pal_ecdsa.h deleted file mode 100644 index 02cc7a587463e6..00000000000000 --- a/src/native/libs/System.Security.Cryptography.Native/pal_ecdsa.h +++ /dev/null @@ -1,29 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#include "pal_types.h" -#include "pal_compiler.h" -#include "opensslshim.h" - -/* -Shims the ECDSA_sign method. - -Returns 1 on success, otherwise 0. -*/ -PALEXPORT int32_t -CryptoNative_EcDsaSign(const uint8_t* dgst, int32_t dgstlen, uint8_t* sig, int32_t* siglen, EC_KEY* key); - -/* -Shims the ECDSA_verify method. - -Returns 1 for a correct signature, 0 for an incorrect signature, -1 on error. -*/ -PALEXPORT int32_t -CryptoNative_EcDsaVerify(const uint8_t* dgst, int32_t dgstlen, const uint8_t* sig, int32_t siglen, EC_KEY* key); - -/* -Shims the ECDSA_size method. - -Returns the maximum length of a DER encoded ECDSA signature created with this key. -*/ -PALEXPORT int32_t CryptoNative_EcDsaSize(const EC_KEY* key); diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c index dea4f277b8969d..352512a1bb695e 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c @@ -3,6 +3,21 @@ #include #include "pal_evp_pkey.h" +#include "pal_utilities.h" + +#ifdef NEED_OPENSSL_3_0 +c_static_assert(OSSL_STORE_INFO_PKEY == 4); +c_static_assert(OSSL_STORE_INFO_PUBKEY == 3); +#endif + +struct EvpPKeyExtraHandle_st +{ + int references; + OSSL_LIB_CTX* libCtx; + OSSL_PROVIDER* prov; +}; + +typedef struct EvpPKeyExtraHandle_st EvpPKeyExtraHandle; EVP_PKEY* CryptoNative_EvpPkeyCreate(void) { @@ -10,85 +25,82 @@ EVP_PKEY* CryptoNative_EvpPkeyCreate(void) return EVP_PKEY_new(); } -EVP_PKEY* CryptoNative_EvpPKeyDuplicate(EVP_PKEY* currentKey, int32_t algId) +static void CryptoNative_EvpPkeyExtraHandleDestroy(EvpPKeyExtraHandle* handle) { - assert(currentKey != NULL); + assert(handle->references >= 1); + assert(handle->prov != NULL); + assert(handle->libCtx != NULL); - ERR_clear_error(); - - int currentAlgId = EVP_PKEY_get_base_id(currentKey); + handle->references--; - if (algId != NID_undef && algId != currentAlgId) + if (handle->references == 0) { - ERR_put_error(ERR_LIB_EVP, 0, EVP_R_DIFFERENT_KEY_TYPES, __FILE__, __LINE__); - return NULL; + OSSL_PROVIDER_unload(handle->prov); + OSSL_LIB_CTX_free(handle->libCtx); + free(handle); } - - EVP_PKEY* newKey = EVP_PKEY_new(); - - if (newKey == NULL) - { - return NULL; - } - - bool success = true; - - if (currentAlgId == EVP_PKEY_RSA) - { - const RSA* rsa = EVP_PKEY_get0_RSA(currentKey); - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wcast-qual" - if (rsa == NULL || !EVP_PKEY_set1_RSA(newKey, (RSA*)rsa)) -#pragma clang diagnostic pop - { - success = false; - } - } - else - { - ERR_put_error(ERR_LIB_EVP, 0, EVP_R_UNSUPPORTED_ALGORITHM, __FILE__, __LINE__); - success = false; - } - - if (!success) - { - EVP_PKEY_free(newKey); - newKey = NULL; - } - - return newKey; } -void CryptoNative_EvpPkeyDestroy(EVP_PKEY* pkey) +void CryptoNative_EvpPkeyDestroy(EVP_PKEY* pkey, void* extraHandle) { if (pkey != NULL) { EVP_PKEY_free(pkey); } + + if (extraHandle != NULL) + { + EvpPKeyExtraHandle* extra = (EvpPKeyExtraHandle*)extraHandle; + CryptoNative_EvpPkeyExtraHandleDestroy(extra); + } } -int32_t CryptoNative_EvpPKeySize(EVP_PKEY* pkey) +int32_t CryptoNative_EvpPKeyBits(EVP_PKEY* pkey) { // This function is not expected to populate the error queue with // any errors, but it's technically possible that an external // ENGINE or OSSL_PROVIDER populate the queue in their implementation, // but the calling code does not check for one. assert(pkey != NULL); - return EVP_PKEY_get_size(pkey); + return EVP_PKEY_get_bits(pkey); } -int32_t CryptoNative_UpRefEvpPkey(EVP_PKEY* pkey) +int32_t CryptoNative_UpRefEvpPkey(EVP_PKEY* pkey, void* extraHandle) { + EvpPKeyExtraHandle* extra = (EvpPKeyExtraHandle*)extraHandle; + if (!pkey) { return 0; } + if (extra != NULL) + { + extra->references++; + } + // No error queue impact. return EVP_PKEY_up_ref(pkey); } +int32_t CryptoNative_EvpPKeyType(EVP_PKEY* key) +{ + int32_t base_id = EVP_PKEY_get_base_id(key); + switch (base_id) { + case EVP_PKEY_RSA: + case EVP_PKEY_EC: + case EVP_PKEY_DSA: + return base_id; + case EVP_PKEY_RSA_PSS: + return EVP_PKEY_RSA; + case EVP_PKEY_ED448: + case EVP_PKEY_ED25519: + return EVP_PKEY_EC; + default: + return 0; + } +} + static bool Lcm(const BIGNUM* num1, const BIGNUM* num2, BN_CTX* ctx, BIGNUM* result) { assert(result); @@ -580,3 +592,204 @@ EVP_PKEY* CryptoNative_LoadPublicKeyFromEngine(const char* engineName, const cha *haveEngine = 0; return NULL; } + +EVP_PKEY* CryptoNative_LoadKeyFromProvider(const char* providerName, const char* keyUri, void** extraHandle) +{ + ERR_clear_error(); + +#ifdef FEATURE_DISTRO_AGNOSTIC_SSL + if (!API_EXISTS(OSSL_PROVIDER_load)) + { + ERR_put_error(ERR_LIB_NONE, 0, ERR_R_DISABLED, __FILE__, __LINE__); + return NULL; + } +#endif + +#ifdef NEED_OPENSSL_3_0 + EVP_PKEY* ret = NULL; + OSSL_LIB_CTX* libCtx = OSSL_LIB_CTX_new(); + OSSL_PROVIDER* prov = NULL; + OSSL_STORE_CTX* store = NULL; + OSSL_STORE_INFO* firstPubKey = NULL; + + if (libCtx == NULL) + { + goto end; + } + + prov = OSSL_PROVIDER_load(libCtx, providerName); + + if (prov == NULL) + { + goto end; + } + + store = OSSL_STORE_open_ex(keyUri, libCtx, NULL, NULL, NULL, NULL, NULL, NULL); + if (store == NULL) + { + goto end; + } + + // Quite similar to loading a single certificate from a PFX, if we find a private key that wins. + // Otherwise, the first public key wins. + // Otherwise, we'll push a keyload error + while (ret == NULL && !OSSL_STORE_eof(store)) + { + OSSL_STORE_INFO* info = OSSL_STORE_load(store); + + if (info == NULL) + { + continue; + } + + int type = OSSL_STORE_INFO_get_type(info); + + if (type == OSSL_STORE_INFO_PKEY) + { + ret = OSSL_STORE_INFO_get1_PKEY(info); + break; + } + else if (type == OSSL_STORE_INFO_PUBKEY && firstPubKey == NULL) + { + firstPubKey = info; + // skip the free + continue; + } + + OSSL_STORE_INFO_free(info); + } + + if (ret == NULL && firstPubKey != NULL) + { + ret = OSSL_STORE_INFO_get1_PUBKEY(firstPubKey); + } + + if (ret == NULL) + { + ERR_clear_error(); + ERR_put_error(ERR_LIB_NONE, 0, EVP_R_NO_KEY_SET, __FILE__, __LINE__); + } + +end: + if (firstPubKey != NULL) + { + OSSL_STORE_INFO_free(firstPubKey); + } + + if (store != NULL) + { + OSSL_STORE_close(store); + } + + if (ret == NULL) + { + if (prov != NULL) + { + assert(libCtx != NULL); + // we still want a separate check for libCtx as only prov could be NULL + OSSL_PROVIDER_unload(prov); + } + + if (libCtx != NULL) + { + OSSL_LIB_CTX_free(libCtx); + } + + *extraHandle = NULL; + } + else + { + EvpPKeyExtraHandle* extra = (EvpPKeyExtraHandle*)malloc(sizeof(EvpPKeyExtraHandle)); + extra->prov = prov; + extra->libCtx = libCtx; + extra->references = 1; + *extraHandle = extra; + } + + return ret; +#else + (void)providerName; + (void)keyUri; + ERR_put_error(ERR_LIB_NONE, 0, ERR_R_DISABLED, __FILE__, __LINE__); + *extraHandle = NULL; + return NULL; +#endif +} + +EVP_PKEY_CTX* CryptoNative_EvpPKeyCtxCreateFromPKey(EVP_PKEY* pkey, void* extraHandle) +{ + assert(pkey != NULL); + +#ifdef NEED_OPENSSL_3_0 + EvpPKeyExtraHandle* handle = (EvpPKeyExtraHandle*)extraHandle; + OSSL_LIB_CTX* libCtx = (handle != NULL) ? handle->libCtx : NULL; + return EVP_PKEY_CTX_new_from_pkey(libCtx, pkey, NULL); +#else + assert(libCtx == NULL); + return EVP_PKEY_CTX_new(pkey, NULL); +#endif +} + +int32_t CryptoNative_EvpPKeyCtxConfigureForECDSASign(EVP_PKEY_CTX* ctx) +{ + if (ctx == NULL) + { + return 0; + } + + if (EVP_PKEY_sign_init(ctx) <= 0) + { + return 0; + } + + return 1; +} + +int32_t CryptoNative_EvpPKeyCtxConfigureForECDSAVerify(EVP_PKEY_CTX* ctx) +{ + if (ctx == NULL) + { + return 0; + } + + if (EVP_PKEY_verify_init(ctx) <= 0) + { + return 0; + } + + return 1; +} + +int32_t CryptoNative_EvpPKeyCtxSignHash(EVP_PKEY_CTX* ctx, + const uint8_t* hash, + int32_t hashLen, + uint8_t* destination, + int32_t* destinationLen) +{ + assert(ctx != NULL); + assert(hash != NULL); + assert(hashLen > 0); + assert(destinationLen != NULL); + assert(destination != NULL || *destinationLen == 0); + + ERR_clear_error(); + size_t written = Int32ToSizeT(*destinationLen); + + if (EVP_PKEY_sign(ctx, destination, &written, hash, Int32ToSizeT(hashLen)) > 0) + { + *destinationLen = SizeTToInt32(written); + return 1; + } + + return 0; +} + +int32_t CryptoNative_EvpPKeyCtxVerifyHash(EVP_PKEY_CTX* ctx, + const uint8_t* hash, + int32_t hashLen, + uint8_t* signature, + int32_t signatureLen) +{ + ERR_clear_error(); + return EVP_PKEY_verify(ctx, signature, Int32ToSizeT(signatureLen), hash, Int32ToSizeT(hashLen)) > 0; +} diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.h b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.h index e4d5f85d4b9ec3..8d7714a508ce40 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.h +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.h @@ -12,12 +12,6 @@ Returns the new EVP_PKEY instance. */ PALEXPORT EVP_PKEY* CryptoNative_EvpPkeyCreate(void); -/* -Create a new EVP_PKEY that has the same interior key as currentKey, -optionally verifying that the key has the correct algorithm. -*/ -PALEXPORT EVP_PKEY* CryptoNative_EvpPKeyDuplicate(EVP_PKEY* currentKey, int32_t algId); - /* Cleans up and deletes a EVP_PKEY instance. @@ -27,12 +21,12 @@ No-op if pkey is null. The given EVP_PKEY pointer is invalid after this call. Always succeeds. */ -PALEXPORT void CryptoNative_EvpPkeyDestroy(EVP_PKEY* pkey); +PALEXPORT void CryptoNative_EvpPkeyDestroy(EVP_PKEY* pkey, void* extraHandle); /* -Returns the maximum size, in bytes, of an operation with the provided key. +Returns the cryptographic length of the cryptosystem to which the key belongs, in bits. */ -PALEXPORT int32_t CryptoNative_EvpPKeySize(EVP_PKEY* pkey); +PALEXPORT int32_t CryptoNative_EvpPKeyBits(EVP_PKEY* pkey); /* Used by System.Security.Cryptography.X509Certificates' OpenSslX509CertificateReader when @@ -41,7 +35,16 @@ duplicating a private key context as part of duplicating the Pal object. Returns the number (as of this call) of references to the EVP_PKEY. Anything less than 2 is an error, because the key is already in the process of being freed. */ -PALEXPORT int32_t CryptoNative_UpRefEvpPkey(EVP_PKEY* pkey); +PALEXPORT int32_t CryptoNative_UpRefEvpPkey(EVP_PKEY* pkey, void* extraHandle); + +/* +Returns one of the following 4 values for the given EVP_PKEY: + 0 - unknown + EVP_PKEY_RSA - RSA + EVP_PKEY_EC - EC + EVP_PKEY_DSA - DSA +*/ +PALEXPORT int32_t CryptoNative_EvpPKeyType(EVP_PKEY* key); /* Decodes an X.509 SubjectPublicKeyInfo into an EVP_PKEY*, verifying the interpreted algorithm type. @@ -104,3 +107,51 @@ Returns a valid EVP_PKEY* on success, NULL on failure. haveEngine is 1 if OpenSSL ENGINE's are supported, otherwise 0. */ PALEXPORT EVP_PKEY* CryptoNative_LoadPublicKeyFromEngine(const char* engineName, const char* keyName, int32_t* haveEngine); + +/* +Load a key by URI from a specified OSSL_PROVIDER. + +Returns a valid EVP_PKEY* on success, NULL on failure. +On success extraHandle may be non-null value which we need to keep alive +until the EVP_PKEY is destroyed. +*/ +PALEXPORT EVP_PKEY* CryptoNative_LoadKeyFromProvider(const char* providerName, const char* keyUri, void** extraHandle); + +/* +It's a wrapper for EVP_PKEY_CTX_new_from_pkey and EVP_PKEY_CTX_new +which handles extraHandle. +*/ +PALEXPORT EVP_PKEY_CTX* CryptoNative_EvpPKeyCtxCreateFromPKey(EVP_PKEY* pkey, void* extraHandle); + + +/* +Configures the EVP_PKEY_CTX for use in an ECDSA sign operation. +Returns 1 on success, 0 on failure. +*/ +PALEXPORT int32_t CryptoNative_EvpPKeyCtxConfigureForECDSASign(EVP_PKEY_CTX* ctx); + +/* +Configures the EVP_PKEY_CTX for use in an ECDSA verify operation. +Returns 1 on success, 0 on failure. +*/ +PALEXPORT int32_t CryptoNative_EvpPKeyCtxConfigureForECDSAVerify(EVP_PKEY_CTX* ctx); + +/* +Signs hash using EVP_PKEY_CTX. +Returns 1 on success, 0 on failure. +*/ +PALEXPORT int32_t CryptoNative_EvpPKeyCtxSignHash(EVP_PKEY_CTX* ctx, + const uint8_t* hash, + int32_t hashLen, + uint8_t* destination, + int32_t* destinationLen); + +/* +Verifies hash using EVP_PKEY_CTX. +Returns 1 on success, 0 on failure. +*/ +PALEXPORT int32_t CryptoNative_EvpPKeyCtxVerifyHash(EVP_PKEY_CTX* ctx, + const uint8_t* hash, + int32_t hashLen, + uint8_t* signature, + int32_t signatureLen); diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdh.c b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdh.c index b6a9daf715998d..c1126e5206fcb3 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdh.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdh.c @@ -2,8 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. #include "pal_evp_pkey_ecdh.h" +#include "pal_evp_pkey.h" -EVP_PKEY_CTX* CryptoNative_EvpPKeyCtxCreate(EVP_PKEY* pkey, EVP_PKEY* peerkey, uint32_t* secretLength) +EVP_PKEY_CTX* CryptoNative_EvpPKeyCtxCreate(EVP_PKEY* pkey, void* extraHandle, EVP_PKEY* peerkey, uint32_t* secretLength) { if (secretLength != NULL) *secretLength = 0; @@ -16,7 +17,7 @@ EVP_PKEY_CTX* CryptoNative_EvpPKeyCtxCreate(EVP_PKEY* pkey, EVP_PKEY* peerkey, u } /* Create the context for the shared secret derivation */ - EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey, NULL); + EVP_PKEY_CTX* ctx = CryptoNative_EvpPKeyCtxCreateFromPKey(pkey, extraHandle); if (ctx == NULL) { diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdh.h b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdh.h index 2319669fdeae84..b852b5995ca608 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdh.h +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdh.h @@ -5,7 +5,7 @@ #include "pal_compiler.h" #include "opensslshim.h" -PALEXPORT EVP_PKEY_CTX* CryptoNative_EvpPKeyCtxCreate(EVP_PKEY* pkey, EVP_PKEY* peerkey, uint32_t* secretLength); +PALEXPORT EVP_PKEY_CTX* CryptoNative_EvpPKeyCtxCreate(EVP_PKEY* pkey, void* extraHandle, EVP_PKEY* peerkey, uint32_t* secretLength); PALEXPORT int32_t CryptoNative_EvpPKeyDeriveSecretAgreement(uint8_t* secret, uint32_t secretLength, EVP_PKEY_CTX* ctx); diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_rsa.c b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_rsa.c index 043bf9f9d1edab..9f14667e8981c1 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_rsa.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_rsa.c @@ -1,12 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#include "pal_evp_pkey.h" #include "pal_evp_pkey_rsa.h" #include "pal_utilities.h" #include -static int HasNoPrivateKey(const RSA* rsa); - EVP_PKEY* CryptoNative_EvpPKeyCreateRsa(RSA* currentKey) { assert(currentKey != NULL); @@ -103,6 +102,7 @@ static bool ConfigureEncryption(EVP_PKEY_CTX* ctx, RsaPaddingMode padding, const } int32_t CryptoNative_RsaDecrypt(EVP_PKEY* pkey, + void* extraHandle, const uint8_t* source, int32_t sourceLen, RsaPaddingMode padding, @@ -118,7 +118,7 @@ int32_t CryptoNative_RsaDecrypt(EVP_PKEY* pkey, ERR_clear_error(); - EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey, NULL); + EVP_PKEY_CTX* ctx = CryptoNative_EvpPKeyCtxCreateFromPKey(pkey, extraHandle); int ret = -1; @@ -132,17 +132,6 @@ int32_t CryptoNative_RsaDecrypt(EVP_PKEY* pkey, goto done; } - // This check may no longer be needed on OpenSSL 3.0 - { - const RSA* rsa = EVP_PKEY_get0_RSA(pkey); - - if (rsa == NULL || HasNoPrivateKey(rsa)) - { - ERR_PUT_error(ERR_LIB_RSA, RSA_F_RSA_NULL_PRIVATE_DECRYPT, RSA_R_VALUE_MISSING, __FILE__, __LINE__); - goto done; - } - } - size_t written = Int32ToSizeT(destinationLen); if (EVP_PKEY_decrypt(ctx, destination, &written, source, Int32ToSizeT(sourceLen)) > 0) @@ -160,6 +149,7 @@ int32_t CryptoNative_RsaDecrypt(EVP_PKEY* pkey, } int32_t CryptoNative_RsaEncrypt(EVP_PKEY* pkey, + void* extraHandle, const uint8_t* source, int32_t sourceLen, RsaPaddingMode padding, @@ -174,7 +164,7 @@ int32_t CryptoNative_RsaEncrypt(EVP_PKEY* pkey, ERR_clear_error(); - EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey, NULL); + EVP_PKEY_CTX* ctx = CryptoNative_EvpPKeyCtxCreateFromPKey(pkey, extraHandle); int ret = -1; @@ -204,7 +194,7 @@ int32_t CryptoNative_RsaEncrypt(EVP_PKEY* pkey, return ret; } -static bool ConfigureSignature(EVP_PKEY_CTX* ctx, RsaPaddingMode padding, const EVP_MD* digest) +static bool EvpPKeyCtxConfigureForRsaSignVerify(EVP_PKEY_CTX* ctx, RsaPaddingMode padding, const EVP_MD* digest) { if (padding == RsaPaddingPkcs1) { @@ -235,160 +225,32 @@ static bool ConfigureSignature(EVP_PKEY_CTX* ctx, RsaPaddingMode padding, const return true; } -int32_t CryptoNative_RsaSignHash(EVP_PKEY* pkey, - RsaPaddingMode padding, - const EVP_MD* digest, - const uint8_t* hash, - int32_t hashLen, - uint8_t* destination, - int32_t destinationLen) -{ - assert(pkey != NULL); - assert(destination != NULL); - assert(padding >= RsaPaddingPkcs1 && padding <= RsaPaddingOaepOrPss); - assert(digest != NULL || padding == RsaPaddingPkcs1); - - ERR_clear_error(); - - EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey, NULL); - - int ret = -1; - - if (ctx == NULL || EVP_PKEY_sign_init(ctx) <= 0) - { - goto done; - } - - if (!ConfigureSignature(ctx, padding, digest)) - { - goto done; - } - - // This check may no longer be needed on OpenSSL 3.0 - { - const RSA* rsa = EVP_PKEY_get0_RSA(pkey); - - if (rsa == NULL || HasNoPrivateKey(rsa)) - { - ERR_PUT_error(ERR_LIB_RSA, RSA_F_RSA_NULL_PRIVATE_DECRYPT, RSA_R_VALUE_MISSING, __FILE__, __LINE__); - goto done; - } - } - - size_t written = Int32ToSizeT(destinationLen); - - if (EVP_PKEY_sign(ctx, destination, &written, hash, Int32ToSizeT(hashLen)) > 0) - { - ret = SizeTToInt32(written); - } - -done: - if (ctx != NULL) - { - EVP_PKEY_CTX_free(ctx); - } - - return ret; -} - -int32_t CryptoNative_RsaVerifyHash(EVP_PKEY* pkey, - RsaPaddingMode padding, - const EVP_MD* digest, - const uint8_t* hash, - int32_t hashLen, - const uint8_t* signature, - int32_t signatureLen) +int32_t CryptoNative_EvpPKeyCtxConfigureForRsaSign(EVP_PKEY_CTX* ctx, RsaPaddingMode padding, const EVP_MD* digest) { - assert(pkey != NULL); - assert(signature != NULL); - assert(padding >= RsaPaddingPkcs1 && padding <= RsaPaddingOaepOrPss); - assert(digest != NULL || padding == RsaPaddingPkcs1); - - ERR_clear_error(); - - EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey, NULL); - - int ret = -1; - - if (ctx == NULL || EVP_PKEY_verify_init(ctx) <= 0) - { - goto done; - } - - if (!ConfigureSignature(ctx, padding, digest)) + if (EVP_PKEY_sign_init(ctx) <= 0) { - goto done; - } - - // EVP_PKEY_verify is not consistent on whether a missized hash is an error or just a mismatch. - // Normalize to mismatch. - if (hashLen != EVP_MD_get_size(digest)) - { - ret = 0; - goto done; + return 0; } - ret = EVP_PKEY_verify(ctx, signature, Int32ToSizeT(signatureLen), hash, Int32ToSizeT(hashLen)); - -done: - if (ctx != NULL) + if (!EvpPKeyCtxConfigureForRsaSignVerify(ctx, padding, digest)) { - EVP_PKEY_CTX_free(ctx); + return 0; } - return ret; + return 1; } -static int HasNoPrivateKey(const RSA* rsa) +int32_t CryptoNative_EvpPKeyCtxConfigureForRsaVerify(EVP_PKEY_CTX* ctx, RsaPaddingMode padding, const EVP_MD* digest) { - if (rsa == NULL) - return 1; - - // Shared pointer, don't free. - const RSA_METHOD* meth = RSA_get_method(rsa); - - // The method has described itself as having the private key external to the structure. - // That doesn't mean it's actually present, but we can't tell. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wcast-qual" - if (RSA_test_flags(rsa, RSA_FLAG_EXT_PKEY) || RSA_meth_get_flags((RSA_METHOD*)meth) & RSA_FLAG_EXT_PKEY) -#pragma clang diagnostic pop + if (EVP_PKEY_verify_init(ctx) <= 0) { return 0; } - // In the event that there's a middle-ground where we report failure when success is expected, - // one could do something like check if the RSA_METHOD intercepts all private key operations: - // - // * meth->rsa_priv_enc - // * meth->rsa_priv_dec - // * meth->rsa_sign (in 1.0.x this is only respected if the RSA_FLAG_SIGN_VER flag is asserted) - // - // But, for now, leave it at the EXT_PKEY flag test. - - // The module is documented as accepting either d or the full set of CRT parameters (p, q, dp, dq, qInv) - // So if we see d, we're good. Otherwise, if any of the rest are missing, we're public-only. - const BIGNUM* d; - RSA_get0_key(rsa, NULL, NULL, &d); - - if (d != NULL) + if (!EvpPKeyCtxConfigureForRsaSignVerify(ctx, padding, digest)) { return 0; } - const BIGNUM* p; - const BIGNUM* q; - const BIGNUM* dmp1; - const BIGNUM* dmq1; - const BIGNUM* iqmp; - - RSA_get0_factors(rsa, &p, &q); - RSA_get0_crt_params(rsa, &dmp1, &dmq1, &iqmp); - - if (p == NULL || q == NULL || dmp1 == NULL || dmq1 == NULL || iqmp == NULL) - { - return 1; - } - - return 0; + return 1; } diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_rsa.h b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_rsa.h index 2d3c58787650a8..94424464c109b3 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_rsa.h +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_rsa.h @@ -31,6 +31,7 @@ Decrypt source into destination using the specified RSA key (wrapped in an EVP_P Returns the number of bytes written to destination, -1 on error. */ PALEXPORT int32_t CryptoNative_RsaDecrypt(EVP_PKEY* pkey, + void* extraHandle, const uint8_t* source, int32_t sourceLen, RsaPaddingMode padding, @@ -44,6 +45,7 @@ Encrypt source into destination using the specified RSA key (wrapped in an EVP_P Returns the number of bytes written to destination, -1 on error. */ PALEXPORT int32_t CryptoNative_RsaEncrypt(EVP_PKEY* pkey, + void* extraHandle, const uint8_t* source, int32_t sourceLen, RsaPaddingMode padding, @@ -52,30 +54,13 @@ PALEXPORT int32_t CryptoNative_RsaEncrypt(EVP_PKEY* pkey, int32_t destinationLen); /* -Complete the RSA signature generation for the specified hash using the provided RSA key -(wrapped in an EVP_PKEY) and padding/digest options. - -Returns the number of bytes written to destination, -1 on error. +Configures the EVP_PKEY_CTX for use in an RSA sign operation. +Returns 1 on success, 0 on failure. */ -PALEXPORT int32_t CryptoNative_RsaSignHash(EVP_PKEY* pkey, - RsaPaddingMode padding, - const EVP_MD* digest, - const uint8_t* hash, - int32_t hashLen, - uint8_t* destination, - int32_t destinationLen); +PALEXPORT int32_t CryptoNative_EvpPKeyCtxConfigureForRsaSign(EVP_PKEY_CTX* ctx, RsaPaddingMode padding, const EVP_MD* digest); /* -Verify an RSA signature for the specified hash using the provided RSA key (wrapped in an EVP_PKEY) -and padding/digest options. - -Returns 1 on a verified signature, 0 on a mismatched signature, -1 on error. +Configures the EVP_PKEY_CTX for use in an RSA verify operation. +Returns 1 on success, 0 on failure. */ -PALEXPORT int32_t CryptoNative_RsaVerifyHash(EVP_PKEY* pkey, - RsaPaddingMode padding, - const EVP_MD* digest, - const uint8_t* hash, - int32_t hashLen, - const uint8_t* signature, - int32_t signatureLen); - +PALEXPORT int32_t CryptoNative_EvpPKeyCtxConfigureForRsaVerify(EVP_PKEY_CTX* ctx, RsaPaddingMode padding, const EVP_MD* digest); diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_ssl.c b/src/native/libs/System.Security.Cryptography.Native/pal_ssl.c index dc0478ef2a230e..93d352702748f6 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_ssl.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_ssl.c @@ -1261,7 +1261,7 @@ int32_t CryptoNative_OpenSslGetProtocolSupport(SslProtocols protocol) if (evp != NULL) { - CryptoNative_EvpPkeyDestroy(evp); + CryptoNative_EvpPkeyDestroy(evp, NULL); } if (bio1)