From af6315197a1c7c727d3ffc9ee5aceab2089ed004 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Sat, 20 Jul 2024 12:11:58 +0200 Subject: [PATCH 1/2] JIT: Switch `StaysWithinManagedObject` to peel offsets from VNs (#105169) The SCEV analysis does not care about the value of something once it is seen to be invariant inside the loop we are currently analyzing. This was problematic for this logic that tries to peel additions away from offsets; for arm64, we may have hoisted `array + 0x10` outside the loop, which would cause us to fail to get back to the base array. Switch the reasoning to use VNs and peel the offsets from the VNs instead. No x64 diffs are expected as we do not hoist the `array + 0x10` out of the loop there. Improvements expected on arm64 where we can now prove that a "full" strength reduction is allowable more often. --- src/coreclr/jit/inductionvariableopts.cpp | 50 +++++++++++++---------- src/coreclr/jit/valuenum.cpp | 36 ++++++++++++++++ src/coreclr/jit/valuenum.h | 2 + 3 files changed, 67 insertions(+), 21 deletions(-) diff --git a/src/coreclr/jit/inductionvariableopts.cpp b/src/coreclr/jit/inductionvariableopts.cpp index 32c5a1967b6b05..fa3d627feadcc8 100644 --- a/src/coreclr/jit/inductionvariableopts.cpp +++ b/src/coreclr/jit/inductionvariableopts.cpp @@ -1845,16 +1845,32 @@ bool StrengthReductionContext::CheckAdvancedCursors(ArrayStack* curs // bool StrengthReductionContext::StaysWithinManagedObject(ArrayStack* cursors, ScevAddRec* addRec) { - int64_t offset; - Scev* baseScev = addRec->Start->PeelAdditions(&offset); - offset = static_cast(offset); + ValueNumPair addRecStartVNP = m_scevContext.MaterializeVN(addRec->Start); + if (!addRecStartVNP.BothDefined()) + { + return false; + } + + ValueNumPair addRecStartBase = addRecStartVNP; + target_ssize_t offsetLiberal = 0; + target_ssize_t offsetConservative = 0; + m_comp->vnStore->PeelOffsets(addRecStartBase.GetLiberalAddr(), &offsetLiberal); + m_comp->vnStore->PeelOffsets(addRecStartBase.GetConservativeAddr(), &offsetConservative); + + if (offsetLiberal != offsetConservative) + { + return false; + } + + target_ssize_t offset = offsetLiberal; - // We only support arrays and strings here. To strength reduce Span - // accesses we need additional properies on the range designated by a - // Span that we currently do not specify, or we need to prove that the - // byref we may form in the IV update would have been formed anyway by the - // loop. - if (!baseScev->OperIs(ScevOper::Local) || !baseScev->TypeIs(TYP_REF)) + // We only support objects here (targeting array/strings). To strength + // reduce Span accesses we need additional properties on the range + // designated by a Span that we currently do not specify, or we need to + // prove that the byref we may form in the IV update would have been formed + // anyway by the loop. + if ((m_comp->vnStore->TypeOfVN(addRecStartBase.GetConservative()) != TYP_REF) || + (m_comp->vnStore->TypeOfVN(addRecStartBase.GetLiberal()) != TYP_REF)) { return false; } @@ -1888,22 +1904,14 @@ bool StrengthReductionContext::StaysWithinManagedObject(ArrayStack* return false; } - ScevLocal* local = (ScevLocal*)baseScev; - - ValueNumPair vnp = m_scevContext.MaterializeVN(baseScev); - if (!vnp.BothDefined()) - { - return false; - } - BasicBlock* preheader = m_loop->EntryEdge(0)->getSourceBlock(); - if (!m_comp->optAssertionVNIsNonNull(vnp.GetConservative(), preheader->bbAssertionOut)) + if (!m_comp->optAssertionVNIsNonNull(addRecStartBase.GetConservative(), preheader->bbAssertionOut)) { return false; } - // We have a non-null array/string. Check that the 'start' offset looks - // fine. TODO: We could also use assertions on the length of the + // We have a non-null object as the base. Check that the 'start' offset + // looks fine. TODO: We could also use assertions on the length of the // array/string. E.g. if we know the length of the array is > 3, then we // can allow the add rec to have a later start. Maybe range check can be // used? @@ -1914,7 +1922,7 @@ bool StrengthReductionContext::StaysWithinManagedObject(ArrayStack* // Now see if we have a bound that guarantees that we iterate fewer times // than the array/string's length. - ValueNum arrLengthVN = m_comp->vnStore->VNForFunc(TYP_INT, VNF_ARR_LENGTH, vnp.GetLiberal()); + ValueNum arrLengthVN = m_comp->vnStore->VNForFunc(TYP_INT, VNF_ARR_LENGTH, addRecStartBase.GetLiberal()); for (int i = 0; i < m_backEdgeBounds.Height(); i++) { diff --git a/src/coreclr/jit/valuenum.cpp b/src/coreclr/jit/valuenum.cpp index 4d5ad347744a03..7e00b61aabb25b 100644 --- a/src/coreclr/jit/valuenum.cpp +++ b/src/coreclr/jit/valuenum.cpp @@ -14998,3 +14998,39 @@ CORINFO_CLASS_HANDLE ValueNumStore::GetObjectType(ValueNum vn, bool* pIsExact, b return NO_CLASS_HANDLE; } + +//-------------------------------------------------------------------------------- +// PeelOffsets: Peel all additions with a constant offset away from the +// specified VN. +// +// Arguments: +// vn - [in, out] The VN. Will be modified to the base VN that the offsets are added to. +// offset - [out] The offsets peeled out of the VNF_ADD funcs. +// +void ValueNumStore::PeelOffsets(ValueNum* vn, target_ssize_t* offset) +{ +#ifdef DEBUG + var_types vnType = TypeOfVN(*vn); + assert((vnType == TYP_I_IMPL) || (vnType == TYP_REF) || (vnType == TYP_BYREF)); +#endif + + *offset = 0; + VNFuncApp app; + while (GetVNFunc(*vn, &app) && (app.m_func == VNF_ADD)) + { + if (IsVNConstantNonHandle(app.m_args[0])) + { + *offset += ConstantValue(app.m_args[0]); + *vn = app.m_args[1]; + } + else if (IsVNConstantNonHandle(app.m_args[1])) + { + *offset += ConstantValue(app.m_args[1]); + *vn = app.m_args[0]; + } + else + { + break; + } + } +} diff --git a/src/coreclr/jit/valuenum.h b/src/coreclr/jit/valuenum.h index 0138e6e5aecf7c..b9592d8c8cae27 100644 --- a/src/coreclr/jit/valuenum.h +++ b/src/coreclr/jit/valuenum.h @@ -512,6 +512,8 @@ class ValueNumStore CORINFO_CLASS_HANDLE GetObjectType(ValueNum vn, bool* pIsExact, bool* pIsNonNull); + void PeelOffsets(ValueNum* vn, target_ssize_t* offset); + // And the single constant for an object reference type. static ValueNum VNForNull() { From f26dbf0e4d7258c1b8cd6920d93dd291a86e6f1f Mon Sep 17 00:00:00 2001 From: Krzysztof Wicher Date: Sat, 20 Jul 2024 12:16:53 +0200 Subject: [PATCH 2/2] OpenSSL providers support (#104961) * OpenSSL providers support * Address self feedback (Lazy+leak) * Attempt to fix EVP_PKEY_CTX_new_from_pkey errors * update osslcompat_30.h with EVP_PKEY types * properly ifdef extraHandle code * fix: unused parameter extraHandle when OSSL 3 not available * bugfixes, feedback * ifndef some defines in compat layer, remove CryptoNative_EvpPkeyExtraHandleDestroy * change style to match old RsaSignHash * XML doc + extra test case * remote OSSL_STORE_open usage and revert comment on the DuplicateKeyHandle * Address feedback * Add back HasNoPrivateKey check on OSSL ver LT 3 * move check to SignHash * address feedback (ThrowIfNull + switch expression) * update XML doc * attempt to fix ossl 1.0.2 build by moving ifndef to opensslshim.h --- .../Interop.EcDsa.cs | 61 ---- .../Interop.EvpPkey.EcDsa.cs | 81 +++++ .../Interop.EvpPkey.EcKey.cs | 17 +- .../Interop.EvpPkey.Ecdh.cs | 33 +- .../Interop.EvpPkey.Rsa.cs | 24 +- .../Interop.EvpPkey.cs | 79 +++-- .../SafeHandles/SafeEvpPkeyCtxHandle.Unix.cs | 30 -- .../ECDiffieHellmanOpenSsl.Derive.cs | 95 +++--- .../Cryptography/ECDiffieHellmanOpenSsl.cs | 66 ++-- .../ECDiffieHellmanOpenSslPublicKey.cs | 20 +- .../Security/Cryptography/ECDsaOpenSsl.cs | 159 ++++----- .../System/Security/Cryptography/ECOpenSsl.cs | 62 ++-- .../Security/Cryptography/RSAOpenSsl.cs | 93 ++---- .../ref/System.Security.Cryptography.cs | 6 + .../src/System.Security.Cryptography.csproj | 12 +- .../Cryptography/ECDiffieHellmanOpenSsl.cs | 45 +-- .../Security/Cryptography/ECDsaOpenSsl.cs | 44 +-- .../Cryptography/OpenSsl.NotSupported.cs | 8 + .../Security/Cryptography/RSAOpenSsl.cs | 11 +- .../Cryptography/SafeEvpPKeyHandle.OpenSsl.cs | 70 +++- .../X509Certificates/OpenSslExportProvider.cs | 35 +- .../tests/OpenSslNamedKeysTests.manual.cs | 307 ++++++++++++++++-- .../tests/osslplugins/README.md | 64 +++- .../tests/osslplugins/test.sh | 54 ++- .../CMakeLists.txt | 2 +- .../entrypoints.c | 14 +- .../opensslshim.h | 38 ++- .../osslcompat_30.h | 28 +- .../pal_ecdsa.c | 34 -- .../pal_ecdsa.h | 29 -- .../pal_evp_pkey.c | 268 ++++++++++++--- .../pal_evp_pkey.h | 38 ++- .../pal_evp_pkey_ecdh.c | 60 ++-- .../pal_evp_pkey_ecdh.h | 6 +- .../pal_evp_pkey_ecdsa.c | 77 +++++ .../pal_evp_pkey_ecdsa.h | 32 ++ .../pal_evp_pkey_rsa.c | 28 +- .../pal_evp_pkey_rsa.h | 5 +- .../pal_ssl.c | 2 +- 39 files changed, 1429 insertions(+), 708 deletions(-) delete mode 100644 src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.cs create mode 100644 src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.EcDsa.cs delete mode 100644 src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpPkeyCtxHandle.Unix.cs delete mode 100644 src/native/libs/System.Security.Cryptography.Native/pal_ecdsa.c delete mode 100644 src/native/libs/System.Security.Cryptography.Native/pal_ecdsa.h create mode 100644 src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdsa.c create mode 100644 src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdsa.h diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.cs deleted file mode 100644 index 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.EcDsa.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.EcDsa.cs new file mode 100644 index 00000000000000..2fbbca528530b6 --- /dev/null +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.EcDsa.cs @@ -0,0 +1,81 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + internal static partial class Crypto + { + [LibraryImport(Libraries.CryptoNative)] + private static partial int CryptoNative_EcDsaSignHash( + SafeEvpPKeyHandle pkey, + IntPtr extraHandle, + ref byte hash, + int hashLength, + ref byte destination, + int destinationLength); + + internal static int EcDsaSignHash( + SafeEvpPKeyHandle pkey, + ReadOnlySpan hash, + Span destination) + { + int written = CryptoNative_EcDsaSignHash( + pkey, + pkey.ExtraHandle, + ref MemoryMarshal.GetReference(hash), + hash.Length, + ref MemoryMarshal.GetReference(destination), + destination.Length); + + if (written < 0) + { + Debug.Assert(written == -1); + throw CreateOpenSslCryptographicException(); + } + + return written; + } + + [LibraryImport(Libraries.CryptoNative)] + private static partial int CryptoNative_EcDsaVerifyHash( + SafeEvpPKeyHandle pkey, + IntPtr extraHandle, + ref byte hash, + int hashLength, + ref byte signature, + int signatureLength); + + internal static bool EcDsaVerifyHash( + SafeEvpPKeyHandle pkey, + ReadOnlySpan hash, + ReadOnlySpan signature) + { + int ret = CryptoNative_EcDsaVerifyHash( + pkey, + pkey.ExtraHandle, + ref MemoryMarshal.GetReference(hash), + hash.Length, + ref MemoryMarshal.GetReference(signature), + signature.Length); + + if (ret == 1) + { + return true; + } + + if (ret == 0) + { + return false; + } + + Debug.Assert(ret == -1); + throw CreateOpenSslCryptographicException(); + } + } +} diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.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..e85795bde5126e 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Ecdh.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Ecdh.cs @@ -11,32 +11,35 @@ internal static partial class Interop { internal static partial class Crypto { - [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyCtxCreate")] - internal static partial SafeEvpPKeyCtxHandle EvpPKeyCtxCreate(SafeEvpPKeyHandle pkey, SafeEvpPKeyHandle peerkey, out uint secretLength); - [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyDeriveSecretAgreement")] private static partial int EvpPKeyDeriveSecretAgreement( + SafeEvpPKeyHandle pkey, + IntPtr extraHandle, + SafeEvpPKeyHandle peerKey, ref byte secret, - uint secretLength, - SafeEvpPKeyCtxHandle ctx); - - [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyCtxDestroy")] - internal static partial void EvpPKeyCtxDestroy(IntPtr ctx); + uint secretLength); - internal static void EvpPKeyDeriveSecretAgreement(SafeEvpPKeyCtxHandle ctx, Span destination) + internal static int EvpPKeyDeriveSecretAgreement(SafeEvpPKeyHandle pkey, SafeEvpPKeyHandle peerKey, Span destination) { - Debug.Assert(ctx != null); - Debug.Assert(!ctx.IsInvalid); + Debug.Assert(pkey != null); + Debug.Assert(!pkey.IsInvalid); + Debug.Assert(peerKey != null); + Debug.Assert(!peerKey.IsInvalid); - int ret = EvpPKeyDeriveSecretAgreement( + int written = EvpPKeyDeriveSecretAgreement( + pkey, + pkey.ExtraHandle, + peerKey, ref MemoryMarshal.GetReference(destination), - (uint)destination.Length, - ctx); + (uint)destination.Length); - if (ret != 1) + if (written <= 0) { + Debug.Assert(written == 0); throw CreateOpenSslCryptographicException(); } + + return written; } } } diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Rsa.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Rsa.cs index 9fe32f23e42b02..8f56e1fa62f67c 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Rsa.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Rsa.cs @@ -48,6 +48,7 @@ internal static SafeEvpPKeyHandle RsaGenerateKey(int keySize) [LibraryImport(Libraries.CryptoNative)] private static partial int CryptoNative_RsaDecrypt( SafeEvpPKeyHandle pkey, + IntPtr extraHandle, ref byte source, int sourceLength, RSAEncryptionPaddingMode paddingMode, @@ -64,6 +65,7 @@ internal static int RsaDecrypt( { int written = CryptoNative_RsaDecrypt( pkey, + pkey.ExtraHandle, ref MemoryMarshal.GetReference(source), source.Length, paddingMode, @@ -83,6 +85,7 @@ ref MemoryMarshal.GetReference(destination), [LibraryImport(Libraries.CryptoNative)] private static partial int CryptoNative_RsaEncrypt( SafeEvpPKeyHandle pkey, + IntPtr extraHandle, ref byte source, int sourceLength, RSAEncryptionPaddingMode paddingMode, @@ -99,6 +102,7 @@ internal static int RsaEncrypt( { int written = CryptoNative_RsaEncrypt( pkey, + pkey.ExtraHandle, ref MemoryMarshal.GetReference(source), source.Length, paddingMode, @@ -118,6 +122,7 @@ ref MemoryMarshal.GetReference(destination), [LibraryImport(Libraries.CryptoNative)] private static partial int CryptoNative_RsaSignHash( SafeEvpPKeyHandle pkey, + IntPtr extraHandle, RSASignaturePaddingMode paddingMode, IntPtr digestAlgorithm, ref byte hash, @@ -128,14 +133,19 @@ private static partial int CryptoNative_RsaSignHash( internal static int RsaSignHash( SafeEvpPKeyHandle pkey, RSASignaturePaddingMode paddingMode, - IntPtr digestAlgorithm, + HashAlgorithmName digestAlgorithm, ReadOnlySpan hash, Span destination) { + ArgumentNullException.ThrowIfNull(digestAlgorithm.Name, nameof(digestAlgorithm)); + + IntPtr digestAlgorithmPtr = Interop.Crypto.HashAlgorithmToEvp(digestAlgorithm.Name); + int written = CryptoNative_RsaSignHash( pkey, + pkey.ExtraHandle, paddingMode, - digestAlgorithm, + digestAlgorithmPtr, ref MemoryMarshal.GetReference(hash), hash.Length, ref MemoryMarshal.GetReference(destination), @@ -153,6 +163,7 @@ ref MemoryMarshal.GetReference(destination), [LibraryImport(Libraries.CryptoNative)] private static partial int CryptoNative_RsaVerifyHash( SafeEvpPKeyHandle pkey, + IntPtr extraHandle, RSASignaturePaddingMode paddingMode, IntPtr digestAlgorithm, ref byte hash, @@ -163,14 +174,19 @@ private static partial int CryptoNative_RsaVerifyHash( internal static bool RsaVerifyHash( SafeEvpPKeyHandle pkey, RSASignaturePaddingMode paddingMode, - IntPtr digestAlgorithm, + HashAlgorithmName digestAlgorithm, ReadOnlySpan hash, ReadOnlySpan signature) { + ArgumentNullException.ThrowIfNull(digestAlgorithm.Name, nameof(digestAlgorithm)); + + IntPtr digestAlgorithmPtr = Interop.Crypto.HashAlgorithmToEvp(digestAlgorithm.Name); + int ret = CryptoNative_RsaVerifyHash( pkey, + pkey.ExtraHandle, paddingMode, - digestAlgorithm, + digestAlgorithmPtr, ref MemoryMarshal.GetReference(hash), hash.Length, ref MemoryMarshal.GetReference(signature), diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs index 4e8659b5653b78..68f4f8cb6433f0 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs @@ -13,36 +13,39 @@ internal static partial class Crypto [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPkeyCreate")] internal static partial SafeEvpPKeyHandle EvpPkeyCreate(); - [LibraryImport(Libraries.CryptoNative)] - private static partial SafeEvpPKeyHandle CryptoNative_EvpPKeyDuplicate( - SafeEvpPKeyHandle currentKey, - EvpAlgorithmId algorithmId); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPkeyDestroy")] + internal static partial void EvpPkeyDestroy(IntPtr pkey, IntPtr extraHandle); - internal static SafeEvpPKeyHandle EvpPKeyDuplicate( - SafeEvpPKeyHandle currentKey, - EvpAlgorithmId algorithmId) + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyBits")] + internal static partial int EvpPKeyBits(SafeEvpPKeyHandle pkey); + + internal static int GetEvpPKeySizeBytes(SafeEvpPKeyHandle pkey) { - SafeEvpPKeyHandle pkey = CryptoNative_EvpPKeyDuplicate( - currentKey, - algorithmId); + // EVP_PKEY_size returns the maximum suitable size for the output buffers for almost all operations that can be done with the key. + // For most of the OpenSSL 'default' provider keys it will return the same size as this method, + // but other providers such as 'tpm2' it may return larger size. + // Instead we will round up EVP_PKEY_bits result. + int keySizeBits = Interop.Crypto.EvpPKeyBits(pkey); - if (pkey.IsInvalid) + if (keySizeBits <= 0) { - pkey.Dispose(); - throw CreateOpenSslCryptographicException(); + Debug.Fail($"EVP_PKEY_bits returned non-positive value: {keySizeBits}"); + throw new CryptographicException(); } - return pkey; + return (keySizeBits + 7) / 8; } - [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPkeyDestroy")] - internal static partial void EvpPkeyDestroy(IntPtr pkey); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_UpRefEvpPkey")] + private static partial int UpRefEvpPkey(SafeEvpPKeyHandle handle, IntPtr extraHandle); - [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeySize")] - internal static partial int EvpPKeySize(SafeEvpPKeyHandle pkey); + internal static int UpRefEvpPkey(SafeEvpPKeyHandle handle) + { + return UpRefEvpPkey(handle, handle.ExtraHandle); + } - [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_UpRefEvpPkey")] - internal static partial int UpRefEvpPkey(SafeEvpPKeyHandle handle); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyType")] + internal static partial EvpAlgorithmId EvpPKeyType(SafeEvpPKeyHandle handle); [LibraryImport(Libraries.CryptoNative)] private static unsafe partial SafeEvpPKeyHandle CryptoNative_DecodeSubjectPublicKeyInfo( @@ -274,6 +277,42 @@ internal static SafeEvpPKeyHandle LoadPublicKeyFromEngine( return pkey; } + [LibraryImport(Libraries.CryptoNative, StringMarshalling = StringMarshalling.Utf8)] + private static partial IntPtr CryptoNative_LoadKeyFromProvider( + string providerName, + string keyUri, + ref IntPtr extraHandle); + + internal static SafeEvpPKeyHandle LoadKeyFromProvider( + string providerName, + string keyUri) + { + IntPtr extraHandle = IntPtr.Zero; + IntPtr evpPKeyHandle = IntPtr.Zero; + + try + { + evpPKeyHandle = CryptoNative_LoadKeyFromProvider(providerName, keyUri, ref extraHandle); + + if (evpPKeyHandle == IntPtr.Zero || extraHandle == IntPtr.Zero) + { + Debug.Assert(evpPKeyHandle == IntPtr.Zero, "extraHandle should not be null if evpPKeyHandle is not null"); + throw CreateOpenSslCryptographicException(); + } + + return new SafeEvpPKeyHandle(evpPKeyHandle, extraHandle: extraHandle); + } + catch + { + if (evpPKeyHandle != IntPtr.Zero || extraHandle != IntPtr.Zero) + { + EvpPkeyDestroy(evpPKeyHandle, extraHandle); + } + + throw; + } + } + internal enum EvpAlgorithmId { Unknown = 0, diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpPkeyCtxHandle.Unix.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpPkeyCtxHandle.Unix.cs deleted file mode 100644 index d64f135ca0f720..00000000000000 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpPkeyCtxHandle.Unix.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Runtime.InteropServices; - -namespace Microsoft.Win32.SafeHandles -{ - internal sealed class SafeEvpPKeyCtxHandle : SafeHandle - { - public SafeEvpPKeyCtxHandle() - : base(IntPtr.Zero, ownsHandle: true) - { - } - - public SafeEvpPKeyCtxHandle(IntPtr handle, bool ownsHandle) - : base(handle, ownsHandle) - { - } - - protected override bool ReleaseHandle() - { - Interop.Crypto.EvpPKeyCtxDestroy(handle); - SetHandle(IntPtr.Zero); - return true; - } - - public override bool IsInvalid => handle == IntPtr.Zero; - } -} diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs index f6fc78762e043c..32a463bd31caf1 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs @@ -88,10 +88,13 @@ public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPa Debug.Assert(otherPartyPublicKey != null); Debug.Assert(_key is not null); // Callers should validate prior. - // Ensure that this ECDH object contains a private key by attempting a parameter export - // which will throw an OpenSslCryptoException if no private key is available - ECParameters thisKeyExplicit = ExportExplicitParameters(true); - bool thisIsNamed = Interop.Crypto.EcKeyHasCurveName(_key.Value); + bool thisIsNamed; + + using (SafeEcKeyHandle ecKey = Interop.Crypto.EvpPkeyGetEcKey(_key.Value)) + { + thisIsNamed = Interop.Crypto.EcKeyHasCurveName(ecKey); + } + ECDiffieHellmanOpenSslPublicKey? otherKey = otherPartyPublicKey as ECDiffieHellmanOpenSslPublicKey; bool disposeOtherKey = false; @@ -109,10 +112,15 @@ public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPa bool otherIsNamed = otherKey.HasCurveName; - SafeEvpPKeyHandle? ourKey = null; + // We need to always duplicate handle in case this operation is done by multiple threads and one of them disposes the handle + SafeEvpPKeyHandle? ourKey = _key.Value; + bool disposeOurKey = false; + SafeEvpPKeyHandle? theirKey = null; - byte[]? rented = null; - int secretLength = 0; + + // secp521r1 which is the biggest common case maxes out at 66 bytes so 128 should always be enough. + const int StackAllocMax = 128; + Span secret = stackalloc byte[StackAllocMax]; try { @@ -123,79 +131,64 @@ public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPa if (otherIsNamed == thisIsNamed) { - ourKey = _key.UpRefKeyHandle(); theirKey = otherKey.DuplicateKeyHandle(); } else if (otherIsNamed) { - ourKey = _key.UpRefKeyHandle(); - using (ECOpenSsl tmp = new ECOpenSsl(otherKey.ExportExplicitParameters())) { - theirKey = tmp.UpRefKeyHandle(); + theirKey = tmp.CreateEvpPKeyHandle(); } } else { - using (ECOpenSsl tmp = new ECOpenSsl(thisKeyExplicit)) + try { - ourKey = tmp.UpRefKeyHandle(); + // This is generally not expected to fail except: + // - when key can't be accessed but is available (i.e. TPM) + // - private key is actually missing + using (ECOpenSsl tmp = new ECOpenSsl(ExportExplicitParameters(true))) + { + ourKey = tmp.CreateEvpPKeyHandle(); + disposeOurKey = true; + } } - - theirKey = otherKey.DuplicateKeyHandle(); - } - - using (SafeEvpPKeyCtxHandle ctx = Interop.Crypto.EvpPKeyCtxCreate(ourKey, theirKey, out uint secretLengthU)) - { - if (ctx == null || ctx.IsInvalid || secretLengthU == 0 || secretLengthU > int.MaxValue) + catch (CryptographicException) { - throw Interop.Crypto.CreateOpenSslCryptographicException(); + // In both cases of failure we'll report lack of private key + throw new CryptographicException(SR.Cryptography_CSP_NoPrivateKey); } - secretLength = (int)secretLengthU; - - // Indicate that secret can hold stackallocs from nested scopes - scoped Span secret; - - // Arbitrary limit. But it covers secp521r1, which is the biggest common case. - const int StackAllocMax = 66; - - if (secretLength > StackAllocMax) - { - rented = CryptoPool.Rent(secretLength); - secret = new Span(rented, 0, secretLength); - } - else - { - secret = stackalloc byte[secretLength]; - } + theirKey = otherKey.DuplicateKeyHandle(); + } - Interop.Crypto.EvpPKeyDeriveSecretAgreement(ctx, secret); + int written = Interop.Crypto.EvpPKeyDeriveSecretAgreement(ourKey, theirKey, secret); + secret = secret.Slice(0, written); - if (hasher == null) - { - return secret.ToArray(); - } - else - { - hasher.AppendData(secret); - return null; - } + if (hasher == null) + { + return secret.ToArray(); + } + else + { + hasher.AppendData(secret); + return null; } } finally { + CryptographicOperations.ZeroMemory(secret); + theirKey?.Dispose(); - ourKey?.Dispose(); if (disposeOtherKey) { otherKey.Dispose(); } - if (rented != null) + if (disposeOurKey) { - CryptoPool.Return(rented, secretLength); + ourKey.Dispose(); } } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs index c394d1fbf0b542..eff1ba80a346b4 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.cs @@ -9,7 +9,7 @@ namespace System.Security.Cryptography { public sealed partial class ECDiffieHellmanOpenSsl : ECDiffieHellman { - private ECOpenSsl? _key; + private Lazy? _key; [UnsupportedOSPlatform("android")] [UnsupportedOSPlatform("browser")] @@ -19,8 +19,8 @@ public sealed partial class ECDiffieHellmanOpenSsl : ECDiffieHellman public ECDiffieHellmanOpenSsl(ECCurve curve) { ThrowIfNotSupported(); - _key = new ECOpenSsl(curve); - KeySizeValue = _key.KeySize; + _key = new Lazy(ECOpenSsl.GenerateECKey(curve, out int keySize)); + KeySizeValue = keySize; } [UnsupportedOSPlatform("android")] @@ -42,7 +42,7 @@ public ECDiffieHellmanOpenSsl(int keySize) { ThrowIfNotSupported(); base.KeySize = keySize; - _key = new ECOpenSsl(this); + _key = new Lazy(() => ECOpenSsl.GenerateECKey(keySize)); } public override KeySizes[] LegalKeySizes => s_defaultKeySizes.CloneKeySizesArray(); @@ -51,7 +51,7 @@ protected override void Dispose(bool disposing) { if (disposing) { - _key?.Dispose(); + FreeKey(); _key = null; } @@ -75,15 +75,18 @@ public override int KeySize base.KeySize = value; ThrowIfDisposed(); - _key.Dispose(); - _key = new ECOpenSsl(this); + FreeKey(); + _key = new Lazy(ECOpenSsl.GenerateECKey(value)); } } public override void GenerateKey(ECCurve curve) { ThrowIfDisposed(); - KeySizeValue = _key.GenerateKey(curve); + + FreeKey(); + _key = new Lazy(ECOpenSsl.GenerateECKey(curve, out int keySizeValue)); + KeySizeValue = keySizeValue; } public override ECDiffieHellmanPublicKey PublicKey @@ -92,24 +95,38 @@ public override ECDiffieHellmanPublicKey PublicKey { ThrowIfDisposed(); - using (SafeEvpPKeyHandle handle = _key.UpRefKeyHandle()) - { - return new ECDiffieHellmanOpenSslPublicKey(handle); - } + // This may generate the key + return new ECDiffieHellmanOpenSslPublicKey(_key.Value); } } public override void ImportParameters(ECParameters parameters) { ThrowIfDisposed(); - KeySizeValue = _key.ImportParameters(parameters); + FreeKey(); + _key = new Lazy(ECOpenSsl.ImportECKey(parameters, out int keySize)); + KeySizeValue = keySize; + } + + public override ECParameters ExportExplicitParameters(bool includePrivateParameters) + { + ThrowIfDisposed(); + + using (SafeEcKeyHandle ecKey = Interop.Crypto.EvpPkeyGetEcKey(_key.Value)) + { + return ECOpenSsl.ExportExplicitParameters(ecKey, includePrivateParameters); + } } - public override ECParameters ExportExplicitParameters(bool includePrivateParameters) => - ECOpenSsl.ExportExplicitParameters(GetKey(), includePrivateParameters); + public override ECParameters ExportParameters(bool includePrivateParameters) + { + ThrowIfDisposed(); - public override ECParameters ExportParameters(bool includePrivateParameters) => - ECOpenSsl.ExportParameters(GetKey(), includePrivateParameters); + using (SafeEcKeyHandle ecKey = Interop.Crypto.EvpPkeyGetEcKey(_key.Value)) + { + return ECOpenSsl.ExportParameters(ecKey, includePrivateParameters); + } + } public override void ImportEncryptedPkcs8PrivateKey( ReadOnlySpan passwordBytes, @@ -129,16 +146,19 @@ public override void ImportEncryptedPkcs8PrivateKey( base.ImportEncryptedPkcs8PrivateKey(password, source, out bytesRead); } - [MemberNotNull(nameof(_key))] - private void ThrowIfDisposed() + private void FreeKey() { - ObjectDisposedException.ThrowIf(_key is null, this); + if (_key != null && _key.IsValueCreated) + { + SafeEvpPKeyHandle handle = _key.Value; + handle?.Dispose(); + } } - private SafeEcKeyHandle GetKey() + [MemberNotNull(nameof(_key))] + private void ThrowIfDisposed() { - ThrowIfDisposed(); - return _key.Value; + ObjectDisposedException.ThrowIf(_key is null, this); } static partial void ThrowIfNotSupported(); diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSslPublicKey.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSslPublicKey.cs index 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..b683d88442c2c4 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDsaOpenSsl.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDsaOpenSsl.cs @@ -15,7 +15,7 @@ public sealed partial class ECDsaOpenSsl : ECDsa, IRuntimeAlgorithm // secp521r1 maxes out at 139 bytes, so 256 should always be enough private const int SignatureStackBufSize = 256; - private ECOpenSsl? _key; + private Lazy? _key; /// /// Create an ECDsaOpenSsl algorithm with a named curve. @@ -30,8 +30,8 @@ public sealed partial class ECDsaOpenSsl : ECDsa, IRuntimeAlgorithm public ECDsaOpenSsl(ECCurve curve) { ThrowIfNotSupported(); - _key = new ECOpenSsl(curve); - ForceSetKeySize(_key.KeySize); + _key = new Lazy(ECOpenSsl.GenerateECKey(curve, out int keySize)); + ForceSetKeySize(keySize); } /// @@ -59,10 +59,8 @@ public ECDsaOpenSsl() public ECDsaOpenSsl(int keySize) { ThrowIfNotSupported(); - // Use the base setter to get the validation and field assignment without the - // side effect of dereferencing _key. base.KeySize = keySize; - _key = new ECOpenSsl(this); + _key = new Lazy(GenerateKeyFromSize); } /// @@ -84,13 +82,13 @@ private void ForceSetKeySize(int newKeySize) public override byte[] SignHash(byte[] hash) { ArgumentNullException.ThrowIfNull(hash); - ThrowIfDisposed(); - SafeEcKeyHandle key = _key.Value; - int signatureLength = Interop.Crypto.EcDsaSize(key); - Span signDestination = stackalloc byte[SignatureStackBufSize]; - ReadOnlySpan derSignature = SignHash(hash, signDestination, signatureLength, key); + SafeEvpPKeyHandle key = GetKey(); + + Span derSignature = stackalloc byte[SignatureStackBufSize]; + int written = Interop.Crypto.EcDsaSignHash(key, hash, derSignature); + derSignature = derSignature.Slice(0, written); byte[] converted = AsymmetricAlgorithmHelpers.ConvertDerToIeee1363(derSignature, KeySize); return converted; @@ -112,48 +110,41 @@ protected override bool TrySignHashCore( out int bytesWritten) { ThrowIfDisposed(); - SafeEcKeyHandle key = _key.Value; - - int signatureLength = Interop.Crypto.EcDsaSize(key); - Span signDestination = stackalloc byte[SignatureStackBufSize]; if (signatureFormat == DSASignatureFormat.IeeeP1363FixedFieldConcatenation) { - int encodedSize = 2 * AsymmetricAlgorithmHelpers.BitsToBytes(KeySize); + int encodedSize = GetMaxSignatureSize(DSASignatureFormat.IeeeP1363FixedFieldConcatenation); + // IeeeP1363FixedFieldConcatenation has a constant signature size therefore we can shortcut here if (destination.Length < encodedSize) { bytesWritten = 0; return false; } - ReadOnlySpan derSignature = SignHash(hash, signDestination, signatureLength, key); + SafeEvpPKeyHandle key = GetKey(); + + Span derSignature = stackalloc byte[SignatureStackBufSize]; + int written = Interop.Crypto.EcDsaSignHash(key, hash, derSignature); + derSignature = derSignature.Slice(0, written); + bytesWritten = AsymmetricAlgorithmHelpers.ConvertDerToIeee1363(derSignature, KeySize, destination); Debug.Assert(bytesWritten == encodedSize); + return true; } else if (signatureFormat == DSASignatureFormat.Rfc3279DerSequence) { - if (destination.Length >= signatureLength) - { - signDestination = destination; - } - else if (signatureLength > signDestination.Length) - { - Debug.Fail($"Stack-based signDestination is insufficient ({signatureLength} needed)"); - bytesWritten = 0; - return false; - } - - ReadOnlySpan derSignature = SignHash(hash, signDestination, signatureLength, key); + SafeEvpPKeyHandle key = GetKey(); - if (destination == signDestination) - { - bytesWritten = derSignature.Length; - return true; - } + // We need to distinguish between the case where the destination buffer is too small and the case where something else went wrong + // Since Rfc3279DerSequence signature size is not constant (DER is not constant size) we will use temporary buffer with max size. + // If that succeeds we can copy to destination buffer if it's large enough. + Span tmpDerSignature = stackalloc byte[SignatureStackBufSize]; + bytesWritten = Interop.Crypto.EcDsaSignHash(key, hash, tmpDerSignature); + tmpDerSignature = tmpDerSignature.Slice(0, bytesWritten); - return Helpers.TryCopyToDestination(derSignature, destination, out bytesWritten); + return Helpers.TryCopyToDestination(tmpDerSignature, destination, out bytesWritten); } else { @@ -161,33 +152,6 @@ protected override bool TrySignHashCore( } } - private static ReadOnlySpan SignHash( - ReadOnlySpan hash, - Span destination, - int signatureLength, - SafeEcKeyHandle key) - { - if (signatureLength > destination.Length) - { - Debug.Fail($"Stack-based signDestination is insufficient ({signatureLength} needed)"); - destination = new byte[signatureLength]; - } - - if (!Interop.Crypto.EcDsaSign(hash, destination, out int actualLength, key)) - { - throw Interop.Crypto.CreateOpenSslCryptographicException(); - } - - Debug.Assert( - actualLength <= signatureLength, - "ECDSA_sign reported an unexpected signature size", - "ECDSA_sign reported signatureSize was {0}, when <= {1} was expected", - actualLength, - signatureLength); - - return destination.Slice(0, actualLength); - } - public override bool VerifyHash(byte[] hash, byte[] signature) { ArgumentNullException.ThrowIfNull(hash); @@ -242,16 +206,19 @@ protected override bool VerifyHashCore( signatureFormat.ToString()); } - SafeEcKeyHandle key = _key.Value; - int verifyResult = Interop.Crypto.EcDsaVerify(hash, toVerify, key); - return verifyResult == 1; + SafeEvpPKeyHandle key = GetKey(); + + return Interop.Crypto.EcDsaVerifyHash( + key, + hash, + toVerify); } protected override void Dispose(bool disposing) { if (disposing) { - _key?.Dispose(); + FreeKey(); _key = null; } @@ -273,38 +240,73 @@ public override int KeySize base.KeySize = value; ThrowIfDisposed(); - _key.Dispose(); - _key = new ECOpenSsl(this); + + FreeKey(); + _key = new Lazy(GenerateKeyFromSize); } } public override void GenerateKey(ECCurve curve) { ThrowIfDisposed(); - _key.GenerateKey(curve); + + FreeKey(); + _key = new Lazy(ECOpenSsl.GenerateECKey(curve, out int keySize)); // Use ForceSet instead of the property setter to ensure that LegalKeySizes doesn't interfere // with the already loaded key. - ForceSetKeySize(_key.KeySize); + ForceSetKeySize(keySize); } public override void ImportParameters(ECParameters parameters) { ThrowIfDisposed(); - _key.ImportParameters(parameters); - ForceSetKeySize(_key.KeySize); + + FreeKey(); + _key = new Lazy(ECOpenSsl.ImportECKey(parameters, out int keySize)); + + // Use ForceSet instead of the property setter to ensure that LegalKeySizes doesn't interfere + // with the already loaded key. + ForceSetKeySize(keySize); + } + + private SafeEvpPKeyHandle GetKey() + { + ThrowIfDisposed(); + + SafeEvpPKeyHandle key = _key.Value; + + if (key == null || key.IsInvalid) + { + throw new CryptographicException(SR.Cryptography_OpenInvalidHandle); + } + + return key; + } + + private SafeEvpPKeyHandle GenerateKeyFromSize() + { + return ECOpenSsl.GenerateECKey(KeySize); } public override ECParameters ExportExplicitParameters(bool includePrivateParameters) { ThrowIfDisposed(); - return ECOpenSsl.ExportExplicitParameters(_key.Value, includePrivateParameters); + + using (SafeEcKeyHandle ecKey = Interop.Crypto.EvpPkeyGetEcKey(_key.Value)) + { + return ECOpenSsl.ExportExplicitParameters(ecKey, includePrivateParameters); + } } public override ECParameters ExportParameters(bool includePrivateParameters) { ThrowIfDisposed(); - return ECOpenSsl.ExportParameters(_key.Value, includePrivateParameters); + + using (SafeEcKeyHandle ecKey = Interop.Crypto.EvpPkeyGetEcKey(_key.Value)) + { + return ECOpenSsl.ExportParameters(ecKey, includePrivateParameters); + } } public override void ImportEncryptedPkcs8PrivateKey( @@ -325,6 +327,15 @@ public override void ImportEncryptedPkcs8PrivateKey( base.ImportEncryptedPkcs8PrivateKey(password, source, out bytesRead); } + private void FreeKey() + { + if (_key != null && _key.IsValueCreated) + { + SafeEvpPKeyHandle handle = _key.Value; + handle?.Dispose(); + } + } + [MemberNotNull(nameof(_key))] private void ThrowIfDisposed() { diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs b/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs index 29c5d8c944a24d..c3464d0f30c101 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECOpenSsl.cs @@ -15,9 +15,9 @@ public ECOpenSsl(ECCurve curve) GenerateKey(curve); } - public ECOpenSsl(AsymmetricAlgorithm owner) + public ECOpenSsl(int keySizeBits) { - _key = new Lazy(() => GenerateKeyLazy(owner)); + _key = new Lazy(() => GenerateKeyByKeySize(keySizeBits)); } public ECOpenSsl(ECParameters ecParameters) @@ -25,6 +25,7 @@ public ECOpenSsl(ECParameters ecParameters) ImportParameters(ecParameters); } + // Takes ownership of the key public ECOpenSsl(SafeEcKeyHandle key) { _key = new Lazy(key); @@ -32,9 +33,6 @@ public ECOpenSsl(SafeEcKeyHandle key) internal SafeEcKeyHandle Value => _key.Value; - private static SafeEcKeyHandle GenerateKeyLazy(AsymmetricAlgorithm owner) => - GenerateKeyByKeySize(owner.KeySize); - public void Dispose() { FreeKey(); @@ -42,33 +40,15 @@ public void Dispose() internal int KeySize => Interop.Crypto.EcKeyGetSize(_key.Value); - internal SafeEvpPKeyHandle UpRefKeyHandle() + internal SafeEvpPKeyHandle CreateEvpPKeyHandle() { SafeEcKeyHandle currentKey = _key.Value; - Debug.Assert(currentKey != null, "null TODO"); - - SafeEvpPKeyHandle pkeyHandle = Interop.Crypto.EvpPkeyCreate(); - - try - { - // Wrapping our key in an EVP_PKEY will up_ref our key. - // When the EVP_PKEY is Disposed it will down_ref the key. - // So everything should be copacetic. - if (!Interop.Crypto.EvpPkeySetEcKey(pkeyHandle, currentKey)) - { - throw Interop.Crypto.CreateOpenSslCryptographicException(); - } + Debug.Assert(currentKey != null, "key is null"); - return pkeyHandle; - } - catch - { - pkeyHandle.Dispose(); - throw; - } + return Interop.Crypto.CreateEvpPkeyFromEcKey(currentKey); } - internal void SetKey(SafeEcKeyHandle key) + private void SetKey(SafeEcKeyHandle key) { Debug.Assert(key != null, "key != null"); Debug.Assert(!key.IsInvalid, "!key.IsInvalid"); @@ -134,5 +114,33 @@ private void FreeKey() _key = null!; } } + + internal static SafeEvpPKeyHandle GenerateECKey(int keySize) + { + SafeEvpPKeyHandle ret = ImportECKeyCore(new ECOpenSsl(keySize), out int createdKeySize); + Debug.Assert(keySize == createdKeySize); + return ret; + } + + internal static SafeEvpPKeyHandle GenerateECKey(ECCurve curve, out int keySize) + { + return ImportECKeyCore(new ECOpenSsl(curve), out keySize); + } + + internal static SafeEvpPKeyHandle ImportECKey(ECParameters parameters, out int keySize) + { + return ImportECKeyCore(new ECOpenSsl(parameters), out keySize); + } + + // Note: This method takes ownership of ecOpenSsl and disposes it + private static SafeEvpPKeyHandle ImportECKeyCore(ECOpenSsl ecOpenSsl, out int keySize) + { + using (ECOpenSsl ec = ecOpenSsl) + { + SafeEvpPKeyHandle handle = Interop.Crypto.CreateEvpPkeyFromEcKey(ec.Value); + keySize = ec.KeySize; + return handle; + } + } } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/RSAOpenSsl.cs b/src/libraries/Common/src/System/Security/Cryptography/RSAOpenSsl.cs index 7e6efbb2da57ab..96867e0f5e9d9b 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/RSAOpenSsl.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/RSAOpenSsl.cs @@ -15,8 +15,6 @@ namespace System.Security.Cryptography { public sealed partial class RSAOpenSsl : RSA, IRuntimeAlgorithm { - private const int BitsPerByte = 8; - private Lazy? _key; [UnsupportedOSPlatform("android")] @@ -88,7 +86,7 @@ public override byte[] Decrypt(byte[] data, RSAEncryptionPadding padding) ValidatePadding(padding); SafeEvpPKeyHandle key = GetKey(); - int rsaSize = Interop.Crypto.EvpPKeySize(key); + int rsaSize = Interop.Crypto.GetEvpPKeySizeBytes(key); Span destination = default; byte[] buf = CryptoPool.Rent(rsaSize); @@ -116,11 +114,12 @@ public override bool TryDecrypt( ValidatePadding(padding); SafeEvpPKeyHandle key = GetKey(); - int keySizeBytes = Interop.Crypto.EvpPKeySize(key); - // OpenSSL requires that the decryption buffer be at least as large as EVP_PKEY_size. + // OpenSSL requires that the decryption buffer be at least as large as key size in bytes. // So if the destination is too small, use a temporary buffer so we can match - // Windows behavior of succeeding so long as the buffer can hold the final output. + // Windows behavior of succeeding as long as the buffer can hold the final output. + int keySizeBytes = Interop.Crypto.GetEvpPKeySizeBytes(key); + if (destination.Length < keySizeBytes) { // RSA up through 4096 bits use a stackalloc @@ -174,7 +173,7 @@ private static int Decrypt( // Caller should have already checked this. Debug.Assert(!key.IsInvalid); - int rsaSize = Interop.Crypto.EvpPKeySize(key); + int rsaSize = Interop.Crypto.GetEvpPKeySizeBytes(key); if (data.Length != rsaSize) { @@ -183,7 +182,7 @@ private static int Decrypt( if (destination.Length < rsaSize) { - Debug.Fail("Caller is responsible for temporary decryption buffer creation"); + Debug.Fail($"Caller is responsible for temporary decryption buffer creation destination. destination.Length: {destination.Length}, needed: {rsaSize}"); throw new CryptographicException(); } @@ -211,7 +210,7 @@ public override byte[] Encrypt(byte[] data, RSAEncryptionPadding padding) ValidatePadding(padding); SafeEvpPKeyHandle key = GetKey(); - byte[] buf = new byte[Interop.Crypto.EvpPKeySize(key)]; + byte[] buf = new byte[Interop.Crypto.GetEvpPKeySizeBytes(key)]; bool encrypted = TryEncrypt( key, @@ -232,9 +231,9 @@ public override byte[] Encrypt(byte[] data, RSAEncryptionPadding padding) public override bool TryEncrypt(ReadOnlySpan data, Span destination, RSAEncryptionPadding padding, out int bytesWritten) { ArgumentNullException.ThrowIfNull(padding); - ValidatePadding(padding); - SafeEvpPKeyHandle? key = GetKey(); + + SafeEvpPKeyHandle key = GetKey(); return TryEncrypt(key, data, destination, padding, out bytesWritten); } @@ -246,7 +245,7 @@ private static bool TryEncrypt( RSAEncryptionPadding padding, out int bytesWritten) { - int rsaSize = Interop.Crypto.EvpPKeySize(key); + int rsaSize = Interop.Crypto.GetEvpPKeySizeBytes(key); if (destination.Length < rsaSize) { @@ -661,7 +660,7 @@ private void SetKey(SafeEvpPKeyHandle newKey) // Use ForceSet instead of the property setter to ensure that LegalKeySizes doesn't interfere // with the already loaded key. - ForceSetKeySize(BitsPerByte * Interop.Crypto.EvpPKeySize(newKey)); + ForceSetKeySize(Interop.Crypto.EvpPKeyBits(newKey)); } private static void ValidateParameters(ref RSAParameters parameters) @@ -731,20 +730,20 @@ public override byte[] SignHash(byte[] hash, HashAlgorithmName hashAlgorithm, RS ArgumentNullException.ThrowIfNull(hash); ArgumentException.ThrowIfNullOrEmpty(hashAlgorithm.Name, nameof(hashAlgorithm)); ArgumentNullException.ThrowIfNull(padding); + ThrowIfDisposed(); - if (!TrySignHash( - hash, - Span.Empty, - hashAlgorithm, padding, - true, - out _, - out byte[]? signature)) + SafeEvpPKeyHandle key = GetKey(); + int bytesRequired = Interop.Crypto.GetEvpPKeySizeBytes(key); + byte[] signature = new byte[bytesRequired]; + + int written = Interop.Crypto.RsaSignHash(key, padding.Mode, hashAlgorithm, hash, signature); + + if (written != signature.Length) { - Debug.Fail("TrySignHash should not return false in allocation mode"); + Debug.Fail($"RsaSignHash behaved unexpectedly: {nameof(written)}=={written}, {nameof(signature.Length)}=={signature.Length}"); throw new CryptographicException(); } - Debug.Assert(signature != null); return signature; } @@ -757,55 +756,19 @@ public override bool TrySignHash( { ArgumentException.ThrowIfNullOrEmpty(hashAlgorithm.Name, nameof(hashAlgorithm)); ArgumentNullException.ThrowIfNull(padding); + ThrowIfDisposed(); - bool ret = TrySignHash( - hash, - destination, - hashAlgorithm, - padding, - false, - out bytesWritten, - out byte[]? alloced); - - Debug.Assert(alloced == null); - return ret; - } - - private bool TrySignHash( - ReadOnlySpan hash, - Span destination, - HashAlgorithmName hashAlgorithm, - RSASignaturePadding padding, - bool allocateSignature, - out int bytesWritten, - out byte[]? signature) - { - Debug.Assert(!string.IsNullOrEmpty(hashAlgorithm.Name)); - Debug.Assert(padding != null); - ValidatePadding(padding); - - signature = null; - - IntPtr digestAlgorithm = Interop.Crypto.HashAlgorithmToEvp(hashAlgorithm.Name); SafeEvpPKeyHandle key = GetKey(); - int bytesRequired = Interop.Crypto.EvpPKeySize(key); + int bytesRequired = Interop.Crypto.GetEvpPKeySizeBytes(key); - if (allocateSignature) - { - Debug.Assert(destination.Length == 0); - signature = new byte[bytesRequired]; - destination = signature; - } - else if (destination.Length < bytesRequired) + if (destination.Length < bytesRequired) { bytesWritten = 0; return false; } - int written = Interop.Crypto.RsaSignHash(key, padding.Mode, digestAlgorithm, hash, destination); - Debug.Assert(written == bytesRequired); - bytesWritten = written; - + bytesWritten = Interop.Crypto.RsaSignHash(key, padding.Mode, hashAlgorithm, hash, destination); + Debug.Assert(bytesWritten == bytesRequired); return true; } @@ -825,14 +788,14 @@ public override bool VerifyHash(ReadOnlySpan hash, ReadOnlySpan sign { ArgumentException.ThrowIfNullOrEmpty(hashAlgorithm.Name, nameof(hashAlgorithm)); ValidatePadding(padding); + ThrowIfDisposed(); - IntPtr digestAlgorithm = Interop.Crypto.HashAlgorithmToEvp(hashAlgorithm.Name); SafeEvpPKeyHandle key = GetKey(); return Interop.Crypto.RsaVerifyHash( key, padding.Mode, - digestAlgorithm, + hashAlgorithm, hash, signature); } diff --git a/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs b/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs index 11409ac9857c14..9913bf2a3ee49e 100644 --- a/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs +++ b/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs @@ -2364,6 +2364,12 @@ public sealed partial class SafeEvpPKeyHandle : System.Runtime.InteropServices.S [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")] [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")] [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")] + public static System.Security.Cryptography.SafeEvpPKeyHandle OpenKeyFromProvider(string providerName, string keyUri) { throw null; } + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")] + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")] + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")] + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")] public static System.Security.Cryptography.SafeEvpPKeyHandle OpenPrivateKeyFromEngine(string engineName, string keyId) { throw null; } [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")] [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] diff --git a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj index b9f0ce5317f7f9..adf842fe363e00 100644 --- a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj +++ b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj @@ -729,8 +729,6 @@ Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.Crypto.cs" /> - + - - - + (pkeyHandle.DuplicateHandle()); + KeySizeValue = Interop.Crypto.EvpPKeyBits(_key.Value); } /// @@ -63,9 +61,14 @@ public ECDiffieHellmanOpenSsl(IntPtr handle) throw new ArgumentException(SR.Cryptography_OpenInvalidHandle, nameof(handle)); ThrowIfNotSupported(); - SafeEcKeyHandle ecKeyHandle = SafeEcKeyHandle.DuplicateHandle(handle); - _key = new ECOpenSsl(ecKeyHandle); - KeySizeValue = _key.KeySize; + + using (SafeEcKeyHandle ecKeyHandle = SafeEcKeyHandle.DuplicateHandle(handle)) + { + // CreateEvpPkeyFromEcKey already uprefs so nothing else to do + _key = new Lazy(Interop.Crypto.CreateEvpPkeyFromEcKey(ecKeyHandle)); + } + + KeySizeValue = Interop.Crypto.EvpPKeyBits(_key.Value); } /// @@ -75,26 +78,8 @@ public ECDiffieHellmanOpenSsl(IntPtr handle) /// A SafeHandle for the EC_KEY key in OpenSSL public SafeEvpPKeyHandle DuplicateKeyHandle() { - SafeEcKeyHandle currentKey = GetKey(); - SafeEvpPKeyHandle pkeyHandle = Interop.Crypto.EvpPkeyCreate(); - - try - { - // Wrapping our key in an EVP_PKEY will up_ref our key. - // When the EVP_PKEY is Disposed it will down_ref the key. - // So everything should be copacetic. - if (!Interop.Crypto.EvpPkeySetEcKey(pkeyHandle, currentKey)) - { - throw Interop.Crypto.CreateOpenSslCryptographicException(); - } - - return pkeyHandle; - } - catch - { - pkeyHandle.Dispose(); - throw; - } + ThrowIfDisposed(); + return _key.Value.DuplicateHandle(); } static partial void ThrowIfNotSupported() diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDsaOpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDsaOpenSsl.cs index 0887f0a9fb8a63..b8318f56d2ff5e 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDsaOpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDsaOpenSsl.cs @@ -30,16 +30,14 @@ public ECDsaOpenSsl(SafeEvpPKeyHandle pkeyHandle) throw new ArgumentException(SR.Cryptography_OpenInvalidHandle, nameof(pkeyHandle)); ThrowIfNotSupported(); - // If ecKey is valid it has already been up-ref'd, so we can just use this handle as-is. - SafeEcKeyHandle key = Interop.Crypto.EvpPkeyGetEcKey(pkeyHandle); - if (key.IsInvalid) + + if (Interop.Crypto.EvpPKeyType(pkeyHandle) != Interop.Crypto.EvpAlgorithmId.ECC) { - key.Dispose(); - throw Interop.Crypto.CreateOpenSslCryptographicException(); + throw new CryptographicException(SR.Cryptography_OpenInvalidHandle); } - _key = new ECOpenSsl(key); - KeySizeValue = _key.KeySize; + _key = new Lazy(pkeyHandle.DuplicateHandle()); + ForceSetKeySize(Interop.Crypto.EvpPKeyBits(pkeyHandle)); } /// @@ -63,9 +61,14 @@ public ECDsaOpenSsl(IntPtr handle) throw new ArgumentException(SR.Cryptography_OpenInvalidHandle, nameof(handle)); ThrowIfNotSupported(); - SafeEcKeyHandle ecKeyHandle = SafeEcKeyHandle.DuplicateHandle(handle); - _key = new ECOpenSsl(ecKeyHandle); - KeySizeValue = _key.KeySize; + + using (SafeEcKeyHandle ecKeyHandle = SafeEcKeyHandle.DuplicateHandle(handle)) + { + // CreateEvpPkeyFromEcKey already uprefs so nothing else to do + _key = new Lazy(Interop.Crypto.CreateEvpPkeyFromEcKey(ecKeyHandle)); + } + + ForceSetKeySize(Interop.Crypto.EvpPKeyBits(_key.Value)); } /// @@ -76,26 +79,7 @@ public ECDsaOpenSsl(IntPtr handle) public SafeEvpPKeyHandle DuplicateKeyHandle() { ThrowIfDisposed(); - SafeEcKeyHandle currentKey = _key.Value; - SafeEvpPKeyHandle pkeyHandle = Interop.Crypto.EvpPkeyCreate(); - - try - { - // Wrapping our key in an EVP_PKEY will up_ref our key. - // When the EVP_PKEY is Disposed it will down_ref the key. - // So everything should be copacetic. - if (!Interop.Crypto.EvpPkeySetEcKey(pkeyHandle, currentKey)) - { - throw Interop.Crypto.CreateOpenSslCryptographicException(); - } - - return pkeyHandle; - } - catch - { - pkeyHandle.Dispose(); - throw; - } + return _key.Value.DuplicateHandle(); } static partial void ThrowIfNotSupported() diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/OpenSsl.NotSupported.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/OpenSsl.NotSupported.cs index 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..c180bd98c36f08 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSAOpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSAOpenSsl.cs @@ -73,10 +73,13 @@ public RSAOpenSsl(SafeEvpPKeyHandle pkeyHandle) throw new ArgumentException(SR.Cryptography_OpenInvalidHandle, nameof(pkeyHandle)); ThrowIfNotSupported(); - SafeEvpPKeyHandle newKey = Interop.Crypto.EvpPKeyDuplicate( - pkeyHandle, - Interop.Crypto.EvpAlgorithmId.RSA); + if (Interop.Crypto.EvpPKeyType(pkeyHandle) != Interop.Crypto.EvpAlgorithmId.RSA) + { + throw new CryptographicException(SR.Cryptography_OpenInvalidHandle); + } + + SafeEvpPKeyHandle newKey = pkeyHandle.DuplicateHandle(); SetKey(newKey); } @@ -87,7 +90,7 @@ public RSAOpenSsl(SafeEvpPKeyHandle pkeyHandle) /// A SafeHandle for the RSA key in OpenSSL public SafeEvpPKeyHandle DuplicateKeyHandle() { - return Interop.Crypto.EvpPKeyDuplicate(GetKey(), Interop.Crypto.EvpAlgorithmId.RSA); + return GetKey().DuplicateHandle(); } static partial void ThrowIfNotSupported() diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SafeEvpPKeyHandle.OpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SafeEvpPKeyHandle.OpenSsl.cs index 5cebb67ab9d71b..073e6b0fec5dda 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SafeEvpPKeyHandle.OpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SafeEvpPKeyHandle.OpenSsl.cs @@ -11,6 +11,12 @@ public sealed partial class SafeEvpPKeyHandle : SafeHandle { internal static readonly SafeEvpPKeyHandle InvalidHandle = new SafeEvpPKeyHandle(); + /// + /// In some cases like when a key is loaded from a provider, the key may have an associated data + /// we need to keep alive for the lifetime of the key. This field is used to track that data. + /// + internal IntPtr ExtraHandle { get; private set; } + [UnsupportedOSPlatform("android")] [UnsupportedOSPlatform("browser")] [UnsupportedOSPlatform("ios")] @@ -31,9 +37,17 @@ public SafeEvpPKeyHandle(IntPtr handle, bool ownsHandle) { } + internal SafeEvpPKeyHandle(IntPtr handle, IntPtr extraHandle) + : base(handle, ownsHandle: true) + { + ExtraHandle = extraHandle; + } + protected override bool ReleaseHandle() { - Interop.Crypto.EvpPkeyDestroy(handle); + Interop.Crypto.EvpPkeyDestroy(handle, ExtraHandle); + ExtraHandle = IntPtr.Zero; + SetHandle(IntPtr.Zero); return true; } @@ -70,6 +84,8 @@ public SafeEvpPKeyHandle DuplicateHandle() // Since we didn't actually create a new handle, copy the handle // to the new SafeHandle. safeHandle.SetHandle(handle); + // ExtraHandle is upref'd by UpRefEvpPkey + safeHandle.ExtraHandle = ExtraHandle; return safeHandle; } @@ -177,5 +193,57 @@ public static SafeEvpPKeyHandle OpenPublicKeyFromEngine(string engineName, strin return Interop.Crypto.LoadPublicKeyFromEngine(engineName, keyId); } + + /// + /// Open a named public key using a named OSSL_PROVIDER. + /// + /// + /// The name of the OSSL_PROVIDER to process the key open request. + /// + /// + /// The URI assigned by the OSSL_PROVIDER of the key to open. + /// + /// + /// The opened key. + /// + /// + /// or is . + /// + /// + /// or is the empty string. + /// + /// + /// the key could not be opened via the specified named OSSL_PROVIDER. + /// + /// + /// + /// Both and must be trusted inputs. + /// + /// + /// This operation will fail if OpenSSL cannot successfully load the named OSSL_PROVIDER, + /// or if the named OSSL_PROVIDER cannot load the named key. + /// + /// + /// The syntax for is determined by each individual + /// named OSSL_PROVIDER. + /// + /// + [UnsupportedOSPlatform("android")] + [UnsupportedOSPlatform("browser")] + [UnsupportedOSPlatform("ios")] + [UnsupportedOSPlatform("tvos")] + [UnsupportedOSPlatform("windows")] + public static SafeEvpPKeyHandle OpenKeyFromProvider(string providerName, string keyUri) + { + ArgumentException.ThrowIfNullOrEmpty(providerName); + ArgumentException.ThrowIfNullOrEmpty(keyUri); + + if (!Interop.OpenSslNoInit.OpenSslIsAvailable) + { + throw new PlatformNotSupportedException(SR.PlatformNotSupported_CryptographyOpenSSL); + } + + return Interop.Crypto.LoadKeyFromProvider(providerName, keyUri); + } } } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslExportProvider.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslExportProvider.cs index 355dbc303865d5..b25933246132f7 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslExportProvider.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslExportProvider.cs @@ -22,40 +22,23 @@ protected override byte[] ExportPkcs8( ICertificatePalCore certificatePal, ReadOnlySpan password) { - AsymmetricAlgorithm? alg = null; SafeEvpPKeyHandle? privateKey = ((OpenSslX509CertificateReader)certificatePal).PrivateKeyHandle; - try - { - alg = new RSAOpenSsl(privateKey!); - } - catch (CryptographicException) + if (privateKey == null) { + throw new CryptographicException(SR.Cryptography_OpenInvalidHandle); } - if (alg == null) - { - try - { - alg = new ECDsaOpenSsl(privateKey!); - } - catch (CryptographicException) - { - } - } + Interop.Crypto.EvpAlgorithmId evpAlgId = Interop.Crypto.EvpPKeyType(privateKey); - if (alg == null) + AsymmetricAlgorithm alg = evpAlgId switch { - try - { - alg = new DSAOpenSsl(privateKey!); - } - catch (CryptographicException) - { - } - } + Interop.Crypto.EvpAlgorithmId.RSA => new RSAOpenSsl(privateKey), + Interop.Crypto.EvpAlgorithmId.ECC => new ECDsaOpenSsl(privateKey), + Interop.Crypto.EvpAlgorithmId.DSA => new DSAOpenSsl(privateKey), + _ => throw new CryptographicException(SR.Cryptography_InvalidHandle), + }; - Debug.Assert(alg != null); return alg.ExportEncryptedPkcs8PrivateKey(password, s_windowsPbe); } diff --git a/src/libraries/System.Security.Cryptography/tests/OpenSslNamedKeysTests.manual.cs b/src/libraries/System.Security.Cryptography/tests/OpenSslNamedKeysTests.manual.cs index 06cd2ed2721a11..b5aab83a1aa3b6 100644 --- a/src/libraries/System.Security.Cryptography/tests/OpenSslNamedKeysTests.manual.cs +++ b/src/libraries/System.Security.Cryptography/tests/OpenSslNamedKeysTests.manual.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Linq; using Test.Cryptography; using Xunit; @@ -9,26 +10,55 @@ namespace System.Security.Cryptography.Tests // See osslplugins/README.md for instructions on how to build and install the test engine and setup for TPM tests. public class OpenSslNamedKeysTests { - private const string EnvVarPrefix = "DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_"; - private const string TestEngineEnabledEnvVarName = EnvVarPrefix + "ENABLE"; - private const string TestEngineEnsureFailingEnvVarName = EnvVarPrefix + "ENSURE_FAILING"; - private const string TpmTssEngineEcDsaKeyHandleEnvVarName = EnvVarPrefix + "TPM_ECDSA_KEY_HANDLE"; + private const string EnvVarPrefix = "DOTNET_CRYPTOGRAPHY_TESTS_"; + + private const string EngineEnvVarPrefix = EnvVarPrefix + "ENGINE_"; + private const string TestEngineEnabledEnvVarName = EngineEnvVarPrefix + "ENABLE"; + + private const string TpmEnvVarPrefix = EnvVarPrefix + "TPM_"; + private const string TpmEcDsaKeyHandleEnvVarName = TpmEnvVarPrefix + "ECDSA_KEY_HANDLE"; + private const string TpmEcDhKeyHandleEnvVarName = TpmEnvVarPrefix + "ECDH_KEY_HANDLE"; + private const string TpmRsaSignKeyHandleEnvVarName = TpmEnvVarPrefix + "RSA_SIGN_KEY_HANDLE"; + private const string TpmRsaDecryptKeyHandleEnvVarName = TpmEnvVarPrefix + "RSA_DECRYPT_KEY_HANDLE"; private const string NonExistingEngineName = "dntestnonexisting"; - private const string NonExistingEngineKeyName = "nonexisting"; + private const string NonExistingEngineOrProviderKeyName = "nonexisting"; private const string TestEngineName = "dntest"; private const string TestEngineKeyId = "first"; private const string TpmTssEngineName = "tpm2tss"; - public static string TpmTssEngineEcDsaKeyHandle { get; } = Environment.GetEnvironmentVariable(TpmTssEngineEcDsaKeyHandleEnvVarName); - public static bool ShouldRunEngineTests { get; } = PlatformDetection.OpenSslPresentOnSystem && StringToBool(Environment.GetEnvironmentVariable(TestEngineEnabledEnvVarName)); - public static bool ShouldFailTests { get; } = StringToBool(Environment.GetEnvironmentVariable(TestEngineEnsureFailingEnvVarName)); - public static bool ShouldRunTpmTssTests => PlatformDetection.OpenSslPresentOnSystem && !string.IsNullOrEmpty(TpmTssEngineEcDsaKeyHandle); + private const string Tpm2ProviderName = "tpm2"; + + private static string TpmEcDsaKeyHandle { get; } = Environment.GetEnvironmentVariable(TpmEcDsaKeyHandleEnvVarName); + private static string TpmEcDsaKeyHandleUri { get; } = GetHandleKeyUri(TpmEcDsaKeyHandle); + + private static string TpmEcDhKeyHandle { get; } = Environment.GetEnvironmentVariable(TpmEcDhKeyHandleEnvVarName); + private static string TpmEcDhKeyHandleUri { get; } = GetHandleKeyUri(TpmEcDhKeyHandle); + + private static string TpmRsaSignKeyHandle { get; } = Environment.GetEnvironmentVariable(TpmRsaSignKeyHandleEnvVarName); + private static string TpmRsaSignKeyHandleUri { get; } = GetHandleKeyUri(TpmRsaSignKeyHandle); + + private static string TpmRsaDecryptKeyHandle { get; } = Environment.GetEnvironmentVariable(TpmRsaDecryptKeyHandleEnvVarName); + private static string TpmRsaDecryptKeyHandleUri { get; } = GetHandleKeyUri(TpmRsaDecryptKeyHandle); + + public static bool ShouldRunEngineTests { get; } = PlatformDetection.OpenSslPresentOnSystem && StringToBool(Environment.GetEnvironmentVariable(TestEngineEnabledEnvVarName)); + public static bool ShouldRunProviderEcDsaTests { get; } = PlatformDetection.OpenSslPresentOnSystem && !string.IsNullOrEmpty(TpmEcDsaKeyHandleUri); + public static bool ShouldRunProviderEcDhTests { get; } = PlatformDetection.OpenSslPresentOnSystem && !string.IsNullOrEmpty(TpmEcDhKeyHandleUri); + public static bool ShouldRunProviderRsaSignTests { get; } = PlatformDetection.OpenSslPresentOnSystem && !string.IsNullOrEmpty(TpmRsaSignKeyHandleUri); + public static bool ShouldRunProviderRsaDecryptTests { get; } = PlatformDetection.OpenSslPresentOnSystem && !string.IsNullOrEmpty(TpmRsaDecryptKeyHandleUri); + public static bool ShouldRunAnyProviderTests => ShouldRunProviderEcDsaTests || ShouldRunProviderEcDhTests || ShouldRunProviderRsaSignTests || ShouldRunProviderRsaDecryptTests; + + public static bool ShouldRunTpmTssTests => ShouldRunEngineTests && !string.IsNullOrEmpty(TpmEcDsaKeyHandle); + + private static readonly string AnyProviderKeyUri = TpmEcDsaKeyHandleUri ?? TpmEcDhKeyHandleUri ?? TpmRsaSignKeyHandleUri ?? TpmRsaDecryptKeyHandleUri ?? "test"; private static bool StringToBool(string? value) => "true".Equals(value, StringComparison.OrdinalIgnoreCase) || value == "1"; + private static string GetHandleKeyUri(string handle) + => handle != null ? $"handle:{handle}" : null; + // PKCS#1 format private static readonly byte[] s_rsaPrivateKey = ( "3082025C02010002818100BF67168485215A6AB89BCAB9331F6F5F360F4300BE5CF282F77042957E" + @@ -60,6 +90,7 @@ public static void NotSupported() { Assert.Throws(() => SafeEvpPKeyHandle.OpenPublicKeyFromEngine(TestEngineName, TestEngineKeyId)); Assert.Throws(() => SafeEvpPKeyHandle.OpenPrivateKeyFromEngine(TestEngineName, TestEngineKeyId)); + Assert.Throws(() => SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, AnyProviderKeyUri)); } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.OpenSslPresentOnSystem))] @@ -70,32 +101,49 @@ public static void NullArguments() Assert.Throws("engineName", () => SafeEvpPKeyHandle.OpenPublicKeyFromEngine(null, TestEngineKeyId)); Assert.Throws("keyId", () => SafeEvpPKeyHandle.OpenPublicKeyFromEngine(TestEngineName, null)); + + Assert.Throws(() => SafeEvpPKeyHandle.OpenKeyFromProvider(null, AnyProviderKeyUri)); + Assert.Throws(() => SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, null)); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.OpenSslPresentOnSystem))] + public static void EmptyNameThroughNullCharacter() + { + Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPrivateKeyFromEngine("\0", "foo")); + Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPublicKeyFromEngine("\0", "foo")); + Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenKeyFromProvider("\0", "foo")); } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.OpenSslPresentOnSystem))] - public static void NonExistingEngine() + public static void EmptyUriThroughNullCharacter() { - Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPrivateKeyFromEngine(NonExistingEngineName, TestEngineKeyId)); - Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPublicKeyFromEngine(NonExistingEngineName, TestEngineKeyId)); + Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenKeyFromProvider("default", "\0")); } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.OpenSslPresentOnSystem))] - public static void NonExistingKey() + public static void Engine_NonExisting() { - Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPrivateKeyFromEngine(TestEngineName, NonExistingEngineKeyName)); - Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPublicKeyFromEngine(TestEngineName, NonExistingEngineKeyName)); + Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPrivateKeyFromEngine(NonExistingEngineOrProviderKeyName, TestEngineKeyId)); + Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPublicKeyFromEngine(NonExistingEngineOrProviderKeyName, TestEngineKeyId)); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.OpenSslPresentOnSystem))] + public static void Provider_NonExisting() + { + Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenKeyFromProvider(NonExistingEngineOrProviderKeyName, AnyProviderKeyUri)); } [ConditionalFact(nameof(ShouldRunEngineTests))] - public static void Engine_SanityTest() + public static void Engine_NonExistingKey() { - Assert.False(ShouldFailTests, "This test is supposed to fail"); + Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPrivateKeyFromEngine(TestEngineName, NonExistingEngineOrProviderKeyName)); + Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPublicKeyFromEngine(TestEngineName, NonExistingEngineOrProviderKeyName)); } - [ConditionalFact(nameof(ShouldRunTpmTssTests))] - public static void Tpm_SanityTest() + [ConditionalFact(nameof(ShouldRunAnyProviderTests))] + public static void Provider_NonExistingKey() { - Assert.False(ShouldFailTests, "This test is supposed to fail"); + Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, NonExistingEngineOrProviderKeyName)); } [ConditionalFact(nameof(ShouldRunEngineTests))] @@ -174,7 +222,7 @@ public static void Engine_UsePublicKey() [ConditionalFact(nameof(ShouldRunTpmTssTests))] public static void Engine_OpenExistingTPMPrivateKey() { - using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenPrivateKeyFromEngine(TpmTssEngineName, TpmTssEngineEcDsaKeyHandle); + using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenPrivateKeyFromEngine(TpmTssEngineName, TpmEcDsaKeyHandle); using ECDsa ecdsaPri = new ECDsaOpenSsl(priKeyHandle); using ECDsa ecdsaBad = ECDsa.Create(); ecdsaBad.KeySize = ecdsaPri.KeySize; @@ -182,9 +230,226 @@ public static void Engine_OpenExistingTPMPrivateKey() byte[] data = new byte[] { 1, 2, 3, 1, 1, 2, 3 }; byte[] signature = ecdsaPri.SignData(data, HashAlgorithmName.SHA256); byte[] badSignature = ecdsaBad.SignData(data, HashAlgorithmName.SHA256); + Assert.Equal(signature.Length, badSignature.Length); Assert.NotEqual(data, signature); Assert.True(ecdsaPri.VerifyData(data, signature, HashAlgorithmName.SHA256)); Assert.False(ecdsaPri.VerifyData(data, badSignature, HashAlgorithmName.SHA256)); } + + [ConditionalFact(nameof(ShouldRunProviderEcDsaTests))] + public static void Provider_TPM2ECDSA() + { + using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, TpmEcDsaKeyHandleUri); + using ECDsa ecdsaPri = new ECDsaOpenSsl(priKeyHandle); + + byte[] data = new byte[] { 1, 2, 3, 1, 1, 2, 3 }; + + byte[] ecdsaPubBytes = ecdsaPri.ExportSubjectPublicKeyInfo(); + ECDsa ecdsaPub = ECDsa.Create(); + ecdsaPub.ImportSubjectPublicKeyInfo(ecdsaPubBytes, out int bytesRead); + Assert.Equal(ecdsaPubBytes.Length, bytesRead); + + using ECDsa ecdsaBad = ECDsa.Create(); + ecdsaBad.KeySize = ecdsaPri.KeySize; + + // Verify can sign/verify multiple times + for (int i = 0; i < 10; i++) + { + data[0] = (byte)i; + byte[] signature = ecdsaPri.SignData(data, HashAlgorithmName.SHA256); + byte[] badSignature = ecdsaBad.SignData(data, HashAlgorithmName.SHA256); + Assert.NotEqual(data, signature); + Assert.NotEqual(data, badSignature); + Assert.NotEqual(badSignature, signature); + Assert.True(ecdsaPub.VerifyData(data, signature, HashAlgorithmName.SHA256)); + Assert.False(ecdsaPub.VerifyData(data, badSignature, HashAlgorithmName.SHA256)); + Assert.False(ecdsaBad.VerifyData(data, signature, HashAlgorithmName.SHA256)); + + // TPM key is intended for sign/decrypt only, we could theoretically make verify work without needing to export/import by forcing 'default' provider + // for this operation but it's most likely misusage on user part and tpm2 provider intentionally didn't allow it so we will follow this logic. + Assert.ThrowsAny(() => ecdsaPri.VerifyData(data, signature, HashAlgorithmName.SHA256)); + } + + // It's TPM so it should not be possible to export parameters + Assert.ThrowsAny(() => ecdsaPri.ExportParameters(includePrivateParameters: true)); + } + + [ConditionalFact(nameof(ShouldRunProviderEcDsaTests))] + public static void Provider_TPM2ECDSA_ExportParameters() + { + using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, TpmEcDsaKeyHandleUri); + using ECDsa ecdsaPri = new ECDsaOpenSsl(priKeyHandle); + + ECDsa ecdsaPub = ECDsa.Create(); + ecdsaPub.ImportParameters(ecdsaPri.ExportParameters(false)); + Assert.ThrowsAny(() => ecdsaPri.ExportParameters(true)); + + byte[] data = new byte[] { 1, 2, 3, 1, 1, 2, 3 }; + byte[] signature = ecdsaPri.SignData(data, HashAlgorithmName.SHA256); + Assert.True(ecdsaPub.VerifyData(data, signature, HashAlgorithmName.SHA256)); + } + + [ConditionalFact(nameof(ShouldRunProviderEcDsaTests))] + public static void Provider_TPM2ECDSA_ExportExplicitParameters() + { + using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, TpmEcDsaKeyHandleUri); + using ECDsa ecdsaPri = new ECDsaOpenSsl(priKeyHandle); + + ECDsa ecdsaPub = ECDsa.Create(); + ecdsaPub.ImportParameters(ecdsaPri.ExportExplicitParameters(false)); + Assert.ThrowsAny(() => ecdsaPri.ExportExplicitParameters(true)); + + byte[] data = new byte[] { 1, 2, 3, 1, 1, 2, 3 }; + byte[] signature = ecdsaPri.SignData(data, HashAlgorithmName.SHA256); + Assert.True(ecdsaPub.VerifyData(data, signature, HashAlgorithmName.SHA256)); + } + + [ConditionalFact(nameof(ShouldRunProviderEcDhTests))] + public static void Provider_TPM2ECDH() + { + using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, TpmEcDhKeyHandleUri); + using ECDiffieHellman alicePri = new ECDiffieHellmanOpenSsl(priKeyHandle); + using ECDiffieHellman alicePub = ECDiffieHellman.Create(); + + ECParameters aliceECParams = alicePri.ExportParameters(includePrivateParameters: false); + alicePub.ImportParameters(aliceECParams); + + using ECDiffieHellman bobPri = ECDiffieHellman.Create(aliceECParams.Curve); + + byte[] sharedKeyFromAlice; + using (ECDiffieHellmanPublicKey bobPublic = bobPri.PublicKey) + { + sharedKeyFromAlice = alicePri.DeriveRawSecretAgreement(bobPublic); + + Assert.NotEmpty(sharedKeyFromAlice); + + byte firstByte = sharedKeyFromAlice[0]; + bool allSame = sharedKeyFromAlice.All((x) => x == firstByte); + Assert.False(allSame, "all bytes of shared key are the same"); + } + + using (ECDiffieHellmanPublicKey alicePublic = alicePub.PublicKey) + { + byte[] sharedKeyFromBob = bobPri.DeriveRawSecretAgreement(alicePublic); + Assert.Equal(sharedKeyFromAlice, sharedKeyFromBob); + } + + // Now we derive it again but using directly PublicKey on the instance directly wrapping our TPM handle + using (ECDiffieHellmanPublicKey alicePublic = alicePri.PublicKey) + { + Assert.Equal(sharedKeyFromAlice, bobPri.DeriveRawSecretAgreement(alicePublic)); + } + } + + [ConditionalFact(nameof(ShouldRunProviderRsaSignTests))] + [ActiveIssue("https://github.com/tpm2-software/tpm2-openssl/issues/115")] + // or a workaround API proposal + [ActiveIssue("https://github.com/dotnet/runtime/issues/104080")] + public static void Provider_TPM2SignRsa() + { + using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, TpmRsaSignKeyHandleUri); + using RSA rsaPri = new RSAOpenSsl(priKeyHandle); + byte[] rsaPubBytes = rsaPri.ExportSubjectPublicKeyInfo(); + RSA rsaPub = RSA.Create(); + rsaPub.ImportSubjectPublicKeyInfo(rsaPubBytes, out int bytesRead); + Assert.Equal(rsaPubBytes.Length, bytesRead); + + using RSA rsaBad = RSA.Create(); + rsaBad.KeySize = rsaPri.KeySize; + + byte[] data = new byte[] { 1, 2, 3, 1, 1, 2, 3 }; + byte[] badSignature = rsaBad.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pss); + + // can use same key more than once + for (int i = 0; i < 10; i++) + { + data[0] = (byte)i; + byte[] signature = rsaPri.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pss); + Assert.True(rsaPub.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss), "signature does not verify with the right key"); + Assert.False(rsaPub.VerifyData(data, badSignature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss), "signature should not verify with the wrong key"); + + signature[12] ^= 1; + Assert.False(rsaPub.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss), "tampered signature should not verify"); + + // TPM key is intended for sign only, we could theoretically make verify work without needing to export/import by forcing 'default' provider + // for this operation it's most likely misusage on user part and tpm2 provider intentionally didn't allow it so we will follow this logic. + Assert.ThrowsAny(() => rsaPri.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss)); + } + } + + [ConditionalTheory(nameof(ShouldRunProviderRsaDecryptTests))] + [InlineData(RSAEncryptionPaddingMode.Pkcs1)] + [InlineData(RSAEncryptionPaddingMode.Oaep)] + public static void Provider_TPM2DecryptRsa(RSAEncryptionPaddingMode mode) + { + RSAEncryptionPadding padding; + + switch (mode) + { + case RSAEncryptionPaddingMode.Pkcs1: + padding = RSAEncryptionPadding.Pkcs1; + break; + case RSAEncryptionPaddingMode.Oaep: + padding = RSAEncryptionPadding.OaepSHA256; + break; + default: + throw new ArgumentOutOfRangeException(nameof(mode)); + } + + using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, TpmRsaDecryptKeyHandleUri); + using RSA rsaPri = new RSAOpenSsl(priKeyHandle); + byte[] rsaPubBytes = rsaPri.ExportSubjectPublicKeyInfo(); + RSA rsaPub = RSA.Create(); + rsaPub.ImportSubjectPublicKeyInfo(rsaPubBytes, out int bytesRead); + Assert.Equal(rsaPubBytes.Length, bytesRead); + + using RSA rsaBad = RSA.Create(); + rsaBad.KeySize = rsaPri.KeySize; + + byte[] data = new byte[] { 1, 2, 3, 1, 1, 2, 3 }; + byte[] encryptedWithDifferentKey = rsaBad.Encrypt(data, padding); + Assert.ThrowsAny(() => rsaPri.Decrypt(encryptedWithDifferentKey, padding)); + + // TPM private key is intended only for decrypt + Assert.ThrowsAny(() => rsaPri.Encrypt(data, padding)); + + // can use same key more than once + for (int i = 0; i < 10; i++) + { + data[0] = (byte)i; + byte[] encrypted = rsaPub.Encrypt(data, padding); + Assert.NotEqual(encrypted, data); + + try + { + Assert.Equal(data, rsaPri.Decrypt(encrypted, padding)); + } + catch (CryptographicException) when (mode == RSAEncryptionPaddingMode.Oaep) + { + // TPM2 OAEP support was added in the second half of 2023 therefore we allow for OAEP to throw for the time being + // See: https://github.com/tpm2-software/tpm2-openssl/issues/89 + } + } + } + + [ConditionalFact(nameof(ShouldRunProviderRsaDecryptTests))] + public static void Provider_TPM2DecryptRsa_ExportParameters() + { + // TPM2 OAEP support was added in the second half of 2023 therefore we only test Pkcs1 padding + // See: https://github.com/tpm2-software/tpm2-openssl/issues/89 + RSAEncryptionPadding padding = RSAEncryptionPadding.Pkcs1; + using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, TpmRsaDecryptKeyHandleUri); + using RSA rsaPri = new RSAOpenSsl(priKeyHandle); + + RSA rsaPub = RSA.Create(); + rsaPub.ImportParameters(rsaPri.ExportParameters(false)); + + Assert.ThrowsAny(() => rsaPri.ExportParameters(true)); + + byte[] data = new byte[] { 1, 2, 3, 1, 1, 2, 3 }; + byte[] encrypted = rsaPub.Encrypt(data, padding); + Assert.NotEqual(encrypted, data); + Assert.Equal(data, rsaPri.Decrypt(encrypted, padding)); + } } } diff --git a/src/libraries/System.Security.Cryptography/tests/osslplugins/README.md b/src/libraries/System.Security.Cryptography/tests/osslplugins/README.md index 04d3cdae5fd995..37f27a7b17ee99 100644 --- a/src/libraries/System.Security.Cryptography/tests/osslplugins/README.md +++ b/src/libraries/System.Security.Cryptography/tests/osslplugins/README.md @@ -3,6 +3,7 @@ Once everything is setup tests related to TPM and our engine can be run using: ```bash +export DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_ENABLE=true ./test.sh ``` @@ -12,7 +13,7 @@ If TPM environmental variable similar to following is defined: ```bash # 0x81000007 is just an example, read further how to get it -export DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_TPM_ECDSA_KEY_HANDLE=0x81000007 +export DOTNET_CRYPTOGRAPHY_TESTS_TPM_ECDSA_KEY_HANDLE=0x81000007 ``` then tests using TPM will be run as well and they will use `0x81000007` handle. @@ -114,7 +115,33 @@ export TSS2_LOG=all+TRACE Most of the time this should not be needed but it might be useful if you're seeing issues when interacting with the ENGINE. -### Getting TPM handle +# Testing instructions for OpenSSL Provider + +## Installation + +To install TPM2 provider refer to https://github.com/tpm2-software/tpm2-openssl - on Ubuntu following step can be used: + +```bash +sudo apt install tpm2-openssl tpm2-tools tpm2-abrmd libtss2-tcti-tabrmd0 +``` + +## Running provider tests + +In order to run provider tests you need to have TPM handles and set one or more of the following environmental variables: + +```csharp +# Handle values are just an example - refer to 'Getting TPM handle' section for instructions on how to create or get them +export DOTNET_CRYPTOGRAPHY_TESTS_TPM_ECDSA_KEY_HANDLE=0x81000007 +export DOTNET_CRYPTOGRAPHY_TESTS_TPM_ECDH_KEY_HANDLE=0x8100000d +export DOTNET_CRYPTOGRAPHY_TESTS_TPM_RSA_DECRYPT_KEY_HANDLE=0x8100000c + +# RSA-PSS tests will always fail due to following issues but they can be run still +# https://github.com/dotnet/runtime/issues/104080 +# https://github.com/tpm2-software/tpm2-openssl/issues/115 +export DOTNET_CRYPTOGRAPHY_TESTS_TPM_RSA_SIGN_KEY_HANDLE=0x8100000a +``` + +# Getting TPM handle First, we will need `tpm2-tools`` installed: @@ -122,7 +149,7 @@ First, we will need `tpm2-tools`` installed: sudo apt install tpm2-tools ``` -#### Getting TPM handles +## Getting TPM handles If you already have a handle but you forgot what it is you can list all available handles using following command: @@ -145,11 +172,11 @@ You can also extract public key like this if needed: tpm2_readpublic -c 0x81000007 -o /tmp/key.pub ``` -#### Testing handle with OpenSSL CLI +### Testing handle with OpenSSL CLI In case you find issues with your handle you can test it using OpenSSL CLI, for example `0x81000004` can be tested like following: -##### RSA key +#### RSA key ```bash # create testdata file with some content @@ -168,9 +195,9 @@ openssl pkey -engine tpm2tss -inform engine -in '0x81000007' -pubout -out testke openssl pkeyutl -verify -in testdata.dgst -sigfile testdata.sig -inkey testkey.pub -pubin -pkeyopt digest:sha256 ``` -#### Creating keys and handles +## Creating keys and handles -##### ECDSA key +### ECDSA key ```bash tpm2_createprimary -C o -g sha256 -G ecc256:ecdsa-sha256:null -c primary.ctx -a 'fixedtpm|fixedparent|sensitivedataorigin|userwithauth|noda|sign' @@ -179,7 +206,16 @@ tpm2_createprimary -C o -g sha256 -G ecc256:ecdsa-sha256:null -c primary.ctx -a tpm2_evictcontrol -C o -c primary.ctx ``` -##### RSA key (RSAPSS + SHA256) +### ECDH key + +```bash +tpm2_createprimary -C o -g sha256 -G ecc256 -c primary.ctx -a 'fixedtpm|fixedparent|sensitivedataorigin|userwithauth|noda|decrypt' + +# To create permenent handle and print it: +tpm2_evictcontrol -C o -c primary.ctx +``` + +### RSA key (RSAPSS + SHA256) This is not used by tests but if needed for further testing: @@ -190,3 +226,15 @@ tpm2_createprimary -C o -g sha256 -G rsa2048:rsapss:null -c primary.ctx -a 'fixe # To create permenent handle and print it: tpm2_evictcontrol -C o -c primary.ctx ``` + +### RSA key (decryption) + +This is not used by tests but if needed for further testing: + +```bash +# To create key +tpm2_createprimary -C o -g sha256 -G rsa2048 -c primary.ctx -a 'fixedtpm|fixedparent|sensitivedataorigin|userwithauth|noda|decrypt' + +# To create permenent handle and print it: +tpm2_evictcontrol -C o -c primary.ctx +``` diff --git a/src/libraries/System.Security.Cryptography/tests/osslplugins/test.sh b/src/libraries/System.Security.Cryptography/tests/osslplugins/test.sh index c13dd079319a58..7c8cd686c31bbe 100755 --- a/src/libraries/System.Security.Cryptography/tests/osslplugins/test.sh +++ b/src/libraries/System.Security.Cryptography/tests/osslplugins/test.sh @@ -17,23 +17,54 @@ dotnet="$repo_root_path/dotnet.sh" nativelibs_path="$src_path/native/libs" -export DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_ENABLE=true +if [ -z "$DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_ENABLE" ]; then + echo "WARNING: Engine tests will not be run" + echo "WARNING: Use following variable to enable them:" + echo "WARNING: export DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_ENABLE=true" + echo "WARNING: Refer to README.md for more information." + echo +fi -if [ -z "$DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_TPM_ECDSA_KEY_HANDLE" ]; then - echo "WARNING: TPM tests will not be run" +if [ -z "$DOTNET_CRYPTOGRAPHY_TESTS_TPM_ECDSA_KEY_HANDLE" ]; then + echo "WARNING: TPM ECDSA tests will not be run" echo "WARNING: Use following environmental variable to enable them:" - echo "WARNING: export DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_TPM_ECDSA_KEY_HANDLE=YourHandleHere" + echo "WARNING: export DOTNET_CRYPTOGRAPHY_TESTS_TPM_ECDSA_KEY_HANDLE=YourHandleHere" echo "WARNING: For example:" - echo "WARNING: export DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_TPM_ECDSA_KEY_HANDLE=0x81000007" + echo "WARNING: export DOTNET_CRYPTOGRAPHY_TESTS_TPM_ECDSA_KEY_HANDLE=0x81000007" echo "WARNING: Refer to README.md for more information on how to get handle." + echo fi -if [ "$1" == "--self-check" ]; then - export DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_ENSURE_FAILING=true -else - echo "INFO: To run self-check use:" - echo "INFO: ./test.sh --self-check" - echo "INFO: Expect two test failures." +if [ -z "$DOTNET_CRYPTOGRAPHY_TESTS_TPM_ECDH_KEY_HANDLE" ]; then + echo "WARNING: TPM ECDH tests will not be run" + echo "WARNING: Use following environmental variable to enable them:" + echo "WARNING: export DOTNET_CRYPTOGRAPHY_TESTS_TPM_ECDH_KEY_HANDLE=YourHandleHere" + echo "WARNING: For example:" + echo "WARNING: export DOTNET_CRYPTOGRAPHY_TESTS_TPM_ECDH_KEY_HANDLE=0x8100000d" + echo "WARNING: Refer to README.md for more information on how to get handle." + echo +fi + +if [ -z "$DOTNET_CRYPTOGRAPHY_TESTS_TPM_RSA_SIGN_KEY_HANDLE" ]; then + echo 'WARNING: [ActiveIssue("https://github.com/tpm2-software/tpm2-openssl/issues/115")]' + echo 'WARNING: [ActiveIssue("https://github.com/dotnet/runtime/issues/104080")]' + echo "WARNING: TPM RSA sign tests will not be run" + echo "WARNING: Use following environmental variable to enable them:" + echo "WARNING: export DOTNET_CRYPTOGRAPHY_TESTS_TPM_RSA_SIGN_KEY_HANDLE=YourHandleHere" + echo "WARNING: For example:" + echo "WARNING: export DOTNET_CRYPTOGRAPHY_TESTS_TPM_RSA_SIGN_KEY_HANDLE=0x8100000a" + echo "WARNING: Refer to README.md for more information on how to get handle." + echo +fi + +if [ -z "$DOTNET_CRYPTOGRAPHY_TESTS_TPM_RSA_DECRYPT_KEY_HANDLE" ]; then + echo "WARNING: TPM RSA decrypt tests will not be run" + echo "WARNING: Use following environmental variable to enable them:" + echo "WARNING: export DOTNET_CRYPTOGRAPHY_TESTS_TPM_RSA_DECRYPT_KEY_HANDLE=YourHandleHere" + echo "WARNING: For example:" + echo "WARNING: export DOTNET_CRYPTOGRAPHY_TESTS_TPM_RSA_DECRYPT_KEY_HANDLE=0x8100000c" + echo "WARNING: Refer to README.md for more information on how to get handle." + echo fi set -e @@ -45,4 +76,5 @@ cd "$ssc_src_path" $dotnet build cd "$ssc_tests_path" + $dotnet test --filter "FullyQualifiedName~System.Security.Cryptography.Tests.OpenSslNamedKeysTests." diff --git a/src/native/libs/System.Security.Cryptography.Native/CMakeLists.txt b/src/native/libs/System.Security.Cryptography.Native/CMakeLists.txt index c2808d3fd7646d..8dee7f91e90541 100644 --- a/src/native/libs/System.Security.Cryptography.Native/CMakeLists.txt +++ b/src/native/libs/System.Security.Cryptography.Native/CMakeLists.txt @@ -26,7 +26,6 @@ set(NATIVECRYPTO_SOURCES pal_bignum.c pal_bio.c pal_dsa.c - pal_ecdsa.c pal_ecc_import_export.c pal_eckey.c pal_err.c @@ -36,6 +35,7 @@ set(NATIVECRYPTO_SOURCES pal_evp_pkey.c pal_evp_pkey_dsa.c pal_evp_pkey_ecdh.c + pal_evp_pkey_ecdsa.c pal_evp_pkey_eckey.c pal_evp_pkey_rsa.c pal_hmac.c diff --git a/src/native/libs/System.Security.Cryptography.Native/entrypoints.c b/src/native/libs/System.Security.Cryptography.Native/entrypoints.c index fd9c808597db2e..480fba6e0cc982 100644 --- a/src/native/libs/System.Security.Cryptography.Native/entrypoints.c +++ b/src/native/libs/System.Security.Cryptography.Native/entrypoints.c @@ -10,7 +10,6 @@ #include "pal_bio.h" #include "pal_dsa.h" #include "pal_ecc_import_export.h" -#include "pal_ecdsa.h" #include "pal_eckey.h" #include "pal_err.h" #include "pal_evp.h" @@ -19,6 +18,7 @@ #include "pal_evp_pkey.h" #include "pal_evp_pkey_dsa.h" #include "pal_evp_pkey_ecdh.h" +#include "pal_evp_pkey_ecdsa.h" #include "pal_evp_pkey_eckey.h" #include "pal_evp_pkey_rsa.h" #include "pal_hmac.h" @@ -70,9 +70,8 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_DsaSizeSignature) DllImportEntry(CryptoNative_DsaUpRef) DllImportEntry(CryptoNative_DsaVerify) - DllImportEntry(CryptoNative_EcDsaSign) - DllImportEntry(CryptoNative_EcDsaSize) - DllImportEntry(CryptoNative_EcDsaVerify) + DllImportEntry(CryptoNative_EcDsaSignHash) + DllImportEntry(CryptoNative_EcDsaVerifyHash) DllImportEntry(CryptoNative_EcKeyCreateByExplicitParameters) DllImportEntry(CryptoNative_EcKeyCreateByKeyParameters) DllImportEntry(CryptoNative_EcKeyCreateByOid) @@ -163,16 +162,13 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_EvpMdSize) DllImportEntry(CryptoNative_EvpPkeyCreate) DllImportEntry(CryptoNative_EvpPKeyCreateRsa) - DllImportEntry(CryptoNative_EvpPKeyCtxCreate) - DllImportEntry(CryptoNative_EvpPKeyCtxDestroy) DllImportEntry(CryptoNative_EvpPKeyDeriveSecretAgreement) DllImportEntry(CryptoNative_EvpPkeyDestroy) - DllImportEntry(CryptoNative_EvpPKeyDuplicate) DllImportEntry(CryptoNative_EvpPkeyGetDsa) DllImportEntry(CryptoNative_EvpPkeyGetEcKey) DllImportEntry(CryptoNative_EvpPkeySetDsa) DllImportEntry(CryptoNative_EvpPkeySetEcKey) - DllImportEntry(CryptoNative_EvpPKeySize) + DllImportEntry(CryptoNative_EvpPKeyBits) DllImportEntry(CryptoNative_EvpRC2Cbc) DllImportEntry(CryptoNative_EvpRC2Ecb) DllImportEntry(CryptoNative_EvpSha1) @@ -230,6 +226,7 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_HmacOneShot) DllImportEntry(CryptoNative_HmacReset) DllImportEntry(CryptoNative_HmacUpdate) + DllImportEntry(CryptoNative_LoadKeyFromProvider) DllImportEntry(CryptoNative_LoadPrivateKeyFromEngine) DllImportEntry(CryptoNative_LoadPublicKeyFromEngine) DllImportEntry(CryptoNative_LookupFriendlyNameByOid) @@ -259,6 +256,7 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_RsaSignHash) DllImportEntry(CryptoNative_RsaVerifyHash) DllImportEntry(CryptoNative_UpRefEvpPkey) + DllImportEntry(CryptoNative_EvpPKeyType) DllImportEntry(CryptoNative_X509BuildOcspRequest) DllImportEntry(CryptoNative_X509ChainBuildOcspRequest) DllImportEntry(CryptoNative_X509ChainGetCachedOcspStatus) diff --git a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h index 3541c7558484ea..c3f11cdb9e2162 100644 --- a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h +++ b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h @@ -43,6 +43,7 @@ #if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_3_0_RTM #include +#include #include #include #endif @@ -90,6 +91,10 @@ void ERR_put_error(int32_t lib, int32_t func, int32_t reason, const char* file, #define RSA_PSS_SALTLEN_DIGEST -1 #endif +#ifndef EVP_PKEY_RSA_PSS +#define EVP_PKEY_RSA_PSS 912 +#endif + // ERR_R_UNSUPPORTED was introduced in OpenSSL 3. We need it for building with older OpenSSLs. // Add a static assert so we know if OpenSSL changes the value. #ifndef ERR_R_UNSUPPORTED @@ -430,6 +435,7 @@ extern bool g_libSslUses32BitTime; REQUIRED_FUNCTION(EVP_PKEY_CTX_get0_pkey) \ REQUIRED_FUNCTION(EVP_PKEY_CTX_new) \ REQUIRED_FUNCTION(EVP_PKEY_CTX_new_id) \ + LIGHTUP_FUNCTION(EVP_PKEY_CTX_new_from_pkey) \ FALLBACK_FUNCTION(EVP_PKEY_CTX_set_rsa_keygen_bits) \ FALLBACK_FUNCTION(EVP_PKEY_CTX_set_rsa_oaep_md) \ FALLBACK_FUNCTION(EVP_PKEY_CTX_set_rsa_padding) \ @@ -445,7 +451,7 @@ extern bool g_libSslUses32BitTime; REQUIRED_FUNCTION(EVP_PKEY_encrypt_init) \ REQUIRED_FUNCTION(EVP_PKEY_free) \ RENAMED_FUNCTION(EVP_PKEY_get_base_id, EVP_PKEY_base_id) \ - RENAMED_FUNCTION(EVP_PKEY_get_size, EVP_PKEY_size) \ + RENAMED_FUNCTION(EVP_PKEY_get_bits, EVP_PKEY_bits) \ FALLBACK_FUNCTION(EVP_PKEY_get0_RSA) \ REQUIRED_FUNCTION(EVP_PKEY_get1_DSA) \ REQUIRED_FUNCTION(EVP_PKEY_get1_EC_KEY) \ @@ -525,7 +531,19 @@ extern bool g_libSslUses32BitTime; RENAMED_FUNCTION(OPENSSL_sk_push, sk_push) \ RENAMED_FUNCTION(OPENSSL_sk_value, sk_value) \ FALLBACK_FUNCTION(OpenSSL_version_num) \ + LIGHTUP_FUNCTION(OSSL_LIB_CTX_free) \ + LIGHTUP_FUNCTION(OSSL_LIB_CTX_new) \ + LIGHTUP_FUNCTION(OSSL_PROVIDER_load) \ LIGHTUP_FUNCTION(OSSL_PROVIDER_try_load) \ + LIGHTUP_FUNCTION(OSSL_PROVIDER_unload) \ + LIGHTUP_FUNCTION(OSSL_STORE_close) \ + LIGHTUP_FUNCTION(OSSL_STORE_eof) \ + LIGHTUP_FUNCTION(OSSL_STORE_INFO_free) \ + LIGHTUP_FUNCTION(OSSL_STORE_INFO_get_type) \ + LIGHTUP_FUNCTION(OSSL_STORE_INFO_get1_PKEY) \ + LIGHTUP_FUNCTION(OSSL_STORE_INFO_get1_PUBKEY) \ + LIGHTUP_FUNCTION(OSSL_STORE_load) \ + LIGHTUP_FUNCTION(OSSL_STORE_open_ex) \ LIGHTUP_FUNCTION(OSSL_PARAM_construct_octet_string) \ LIGHTUP_FUNCTION(OSSL_PARAM_construct_int32) \ LIGHTUP_FUNCTION(OSSL_PARAM_construct_end) \ @@ -977,7 +995,7 @@ extern TYPEOF(OPENSSL_gmtime)* OPENSSL_gmtime_ptr; #define EVP_PKEY_encrypt EVP_PKEY_encrypt_ptr #define EVP_PKEY_free EVP_PKEY_free_ptr #define EVP_PKEY_get_base_id EVP_PKEY_get_base_id_ptr -#define EVP_PKEY_get_size EVP_PKEY_get_size_ptr +#define EVP_PKEY_get_bits EVP_PKEY_get_bits_ptr #define EVP_PKEY_get0_RSA EVP_PKEY_get0_RSA_ptr #define EVP_PKEY_get1_DSA EVP_PKEY_get1_DSA_ptr #define EVP_PKEY_get1_EC_KEY EVP_PKEY_get1_EC_KEY_ptr @@ -985,6 +1003,7 @@ extern TYPEOF(OPENSSL_gmtime)* OPENSSL_gmtime_ptr; #define EVP_PKEY_keygen EVP_PKEY_keygen_ptr #define EVP_PKEY_keygen_init EVP_PKEY_keygen_init_ptr #define EVP_PKEY_new EVP_PKEY_new_ptr +#define EVP_PKEY_CTX_new_from_pkey EVP_PKEY_CTX_new_from_pkey_ptr #define EVP_PKEY_public_check EVP_PKEY_public_check_ptr #define EVP_PKEY_set1_DSA EVP_PKEY_set1_DSA_ptr #define EVP_PKEY_set1_EC_KEY EVP_PKEY_set1_EC_KEY_ptr @@ -1058,7 +1077,19 @@ extern TYPEOF(OPENSSL_gmtime)* OPENSSL_gmtime_ptr; #define OPENSSL_sk_push OPENSSL_sk_push_ptr #define OPENSSL_sk_value OPENSSL_sk_value_ptr #define OpenSSL_version_num OpenSSL_version_num_ptr +#define OSSL_LIB_CTX_free OSSL_LIB_CTX_free_ptr +#define OSSL_LIB_CTX_new OSSL_LIB_CTX_new_ptr +#define OSSL_PROVIDER_load OSSL_PROVIDER_load_ptr #define OSSL_PROVIDER_try_load OSSL_PROVIDER_try_load_ptr +#define OSSL_PROVIDER_unload OSSL_PROVIDER_unload_ptr +#define OSSL_STORE_close OSSL_STORE_close_ptr +#define OSSL_STORE_eof OSSL_STORE_eof_ptr +#define OSSL_STORE_INFO_free OSSL_STORE_INFO_free_ptr +#define OSSL_STORE_INFO_get_type OSSL_STORE_INFO_get_type_ptr +#define OSSL_STORE_INFO_get1_PKEY OSSL_STORE_INFO_get1_PKEY_ptr +#define OSSL_STORE_INFO_get1_PUBKEY OSSL_STORE_INFO_get1_PUBKEY_ptr +#define OSSL_STORE_load OSSL_STORE_load_ptr +#define OSSL_STORE_open_ex OSSL_STORE_open_ex_ptr #define OSSL_PARAM_construct_octet_string OSSL_PARAM_construct_octet_string_ptr #define OSSL_PARAM_construct_int32 OSSL_PARAM_construct_int32_ptr #define OSSL_PARAM_construct_end OSSL_PARAM_construct_end_ptr @@ -1332,10 +1363,9 @@ extern TYPEOF(OPENSSL_gmtime)* OPENSSL_gmtime_ptr; // Undo renames for renamed-in-3.0 #define EVP_MD_get_size EVP_MD_size #define EVP_PKEY_get_base_id EVP_PKEY_base_id -#define EVP_PKEY_get_size EVP_PKEY_size +#define EVP_PKEY_get_bits EVP_PKEY_bits #define SSL_get1_peer_certificate SSL_get_peer_certificate #define EVP_CIPHER_get_nid EVP_CIPHER_nid - #endif #if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_3_0_RTM diff --git a/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h b/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h index 78b626b1c3809e..3fbe98235dff6d 100644 --- a/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h +++ b/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h @@ -17,9 +17,15 @@ #define OSSL_MAC_PARAM_XOF "xof" #define OSSL_MAC_PARAM_SIZE "size" -typedef struct ossl_provider_st OSSL_PROVIDER; +#define OSSL_STORE_INFO_PKEY 4 +#define OSSL_STORE_INFO_PUBKEY 3 + typedef struct ossl_lib_ctx_st OSSL_LIB_CTX; typedef struct ossl_param_st OSSL_PARAM; +typedef struct ossl_provider_st OSSL_PROVIDER; +typedef struct ossl_store_ctx_st OSSL_STORE_CTX; +typedef struct ossl_store_info_st OSSL_STORE_INFO; +typedef OSSL_STORE_INFO* (*OSSL_STORE_post_process_info_fn)(OSSL_STORE_INFO*, void*); struct ossl_param_st { @@ -53,11 +59,27 @@ int EVP_PKEY_CTX_set_rsa_padding(EVP_PKEY_CTX* ctx, int pad_mode); int EVP_PKEY_CTX_set_rsa_pss_saltlen(EVP_PKEY_CTX* ctx, int saltlen); int EVP_PKEY_CTX_set_signature_md(EVP_PKEY_CTX* ctx, const EVP_MD* md); int EVP_PKEY_get_base_id(const EVP_PKEY* pkey); -int EVP_PKEY_get_size(const EVP_PKEY* pkey); +int EVP_PKEY_get_bits(const EVP_PKEY* pkey); +EVP_PKEY_CTX *EVP_PKEY_CTX_new_from_pkey( + OSSL_LIB_CTX *libctx, EVP_PKEY *pkey, const char *propquery); OSSL_PARAM OSSL_PARAM_construct_end(void); OSSL_PARAM OSSL_PARAM_construct_int32(const char *key, int32_t *buf); OSSL_PARAM OSSL_PARAM_construct_octet_string(const char *key, void *buf, size_t bsize); -OSSL_PROVIDER* OSSL_PROVIDER_try_load(OSSL_LIB_CTX* , const char* name, int retain_fallbacks); +void OSSL_LIB_CTX_free(OSSL_LIB_CTX*); +OSSL_LIB_CTX* OSSL_LIB_CTX_new(void); +OSSL_PROVIDER* OSSL_PROVIDER_load(OSSL_LIB_CTX*, const char* name); +OSSL_PROVIDER* OSSL_PROVIDER_try_load(OSSL_LIB_CTX*, const char* name, int retain_fallbacks); +int OSSL_PROVIDER_unload(OSSL_PROVIDER* prov); +int OSSL_STORE_close(OSSL_STORE_CTX* ctx); +int OSSL_STORE_eof(OSSL_STORE_CTX* ctx); +OSSL_STORE_INFO* OSSL_STORE_load(OSSL_STORE_CTX* ctx); +void OSSL_STORE_INFO_free(OSSL_STORE_INFO* info); +int OSSL_STORE_INFO_get_type(const OSSL_STORE_INFO* info); +EVP_PKEY* OSSL_STORE_INFO_get1_PKEY(const OSSL_STORE_INFO* info); +EVP_PKEY* OSSL_STORE_INFO_get1_PUBKEY(const OSSL_STORE_INFO* info); +OSSL_STORE_CTX* OSSL_STORE_open_ex( + const char*, OSSL_LIB_CTX*, const char*, const UI_METHOD*, void*, const OSSL_PARAM*, OSSL_STORE_post_process_info_fn post_process, void*); + X509* SSL_get1_peer_certificate(const SSL* ssl); diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_ecdsa.c b/src/native/libs/System.Security.Cryptography.Native/pal_ecdsa.c deleted file mode 100644 index 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..2e0e8efee114e2 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c @@ -3,91 +3,121 @@ #include #include "pal_evp_pkey.h" +#include "pal_utilities.h" +#include "pal_atomic.h" -EVP_PKEY* CryptoNative_EvpPkeyCreate(void) -{ - ERR_clear_error(); - return EVP_PKEY_new(); -} +#ifdef NEED_OPENSSL_3_0 +c_static_assert(OSSL_STORE_INFO_PKEY == 4); +c_static_assert(OSSL_STORE_INFO_PUBKEY == 3); -EVP_PKEY* CryptoNative_EvpPKeyDuplicate(EVP_PKEY* currentKey, int32_t algId) +struct EvpPKeyExtraHandle_st { - assert(currentKey != NULL); + atomic_int refCount; + OSSL_LIB_CTX* libCtx; + OSSL_PROVIDER* prov; +}; - ERR_clear_error(); +typedef struct EvpPKeyExtraHandle_st EvpPKeyExtraHandle; - int currentAlgId = EVP_PKEY_get_base_id(currentKey); +#pragma clang diagnostic push +// There's no way to specify explicit memory ordering for increment/decrement with C atomics. +#pragma clang diagnostic ignored "-Watomic-implicit-seq-cst" +static void CryptoNative_EvpPkeyExtraHandleDestroy(EvpPKeyExtraHandle* handle) +{ + int count = --handle->refCount; + assert(count >= 0); - if (algId != NID_undef && algId != currentAlgId) + if (count == 0) { - ERR_put_error(ERR_LIB_EVP, 0, EVP_R_DIFFERENT_KEY_TYPES, __FILE__, __LINE__); - return NULL; - } + assert(handle->prov != NULL); + assert(handle->libCtx != NULL); - EVP_PKEY* newKey = EVP_PKEY_new(); - - if (newKey == NULL) - { - return NULL; + OSSL_PROVIDER_unload(handle->prov); + OSSL_LIB_CTX_free(handle->libCtx); + free(handle); } - - bool success = true; - - if (currentAlgId == EVP_PKEY_RSA) - { - const RSA* rsa = EVP_PKEY_get0_RSA(currentKey); - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wcast-qual" - if (rsa == NULL || !EVP_PKEY_set1_RSA(newKey, (RSA*)rsa)) +} #pragma clang diagnostic pop - { - success = false; - } - } - else - { - ERR_put_error(ERR_LIB_EVP, 0, EVP_R_UNSUPPORTED_ALGORITHM, __FILE__, __LINE__); - success = false; - } - if (!success) - { - EVP_PKEY_free(newKey); - newKey = NULL; - } +#endif - return newKey; +EVP_PKEY* CryptoNative_EvpPkeyCreate(void) +{ + ERR_clear_error(); + return EVP_PKEY_new(); } -void CryptoNative_EvpPkeyDestroy(EVP_PKEY* pkey) +void CryptoNative_EvpPkeyDestroy(EVP_PKEY* pkey, void* extraHandle) { if (pkey != NULL) { EVP_PKEY_free(pkey); } + +#ifdef NEED_OPENSSL_3_0 + if (extraHandle != NULL) + { + EvpPKeyExtraHandle* extra = (EvpPKeyExtraHandle*)extraHandle; + CryptoNative_EvpPkeyExtraHandleDestroy(extra); + } +#else + (void)extraHandle; + assert(extraHandle == NULL); +#endif } -int32_t CryptoNative_EvpPKeySize(EVP_PKEY* pkey) +int32_t CryptoNative_EvpPKeyBits(EVP_PKEY* pkey) { // This function is not expected to populate the error queue with // any errors, but it's technically possible that an external // ENGINE or OSSL_PROVIDER populate the queue in their implementation, // but the calling code does not check for one. assert(pkey != NULL); - return EVP_PKEY_get_size(pkey); + return EVP_PKEY_get_bits(pkey); } -int32_t CryptoNative_UpRefEvpPkey(EVP_PKEY* pkey) +#pragma clang diagnostic push +// There's no way to specify explicit memory ordering for increment/decrement with C atomics. +#pragma clang diagnostic ignored "-Watomic-implicit-seq-cst" +int32_t CryptoNative_UpRefEvpPkey(EVP_PKEY* pkey, void* extraHandle) { if (!pkey) { return 0; } +#ifdef NEED_OPENSSL_3_0 + EvpPKeyExtraHandle* extra = (EvpPKeyExtraHandle*)extraHandle; + + if (extra != NULL) + { + extra->refCount++; + } +#else + (void)extraHandle; + assert(extraHandle == NULL); +#endif + // No error queue impact. return EVP_PKEY_up_ref(pkey); } +#pragma clang diagnostic pop + +int32_t CryptoNative_EvpPKeyType(EVP_PKEY* key) +{ + int32_t base_id = EVP_PKEY_get_base_id(key); + switch (base_id) + { + case EVP_PKEY_RSA: + case EVP_PKEY_EC: + case EVP_PKEY_DSA: + return base_id; + case EVP_PKEY_RSA_PSS: + return EVP_PKEY_RSA; + default: + return 0; + } +} static bool Lcm(const BIGNUM* num1, const BIGNUM* num2, BN_CTX* ctx, BIGNUM* result) { @@ -580,3 +610,147 @@ EVP_PKEY* CryptoNative_LoadPublicKeyFromEngine(const char* engineName, const cha *haveEngine = 0; return NULL; } + +EVP_PKEY* CryptoNative_LoadKeyFromProvider(const char* providerName, const char* keyUri, void** extraHandle) +{ + ERR_clear_error(); + +#ifdef FEATURE_DISTRO_AGNOSTIC_SSL + if (!API_EXISTS(OSSL_PROVIDER_load)) + { + ERR_put_error(ERR_LIB_NONE, 0, ERR_R_DISABLED, __FILE__, __LINE__); + return NULL; + } +#endif + +#ifdef NEED_OPENSSL_3_0 + EVP_PKEY* ret = NULL; + OSSL_LIB_CTX* libCtx = OSSL_LIB_CTX_new(); + OSSL_PROVIDER* prov = NULL; + OSSL_STORE_CTX* store = NULL; + OSSL_STORE_INFO* firstPubKey = NULL; + + if (libCtx == NULL) + { + goto end; + } + + prov = OSSL_PROVIDER_load(libCtx, providerName); + + if (prov == NULL) + { + goto end; + } + + store = OSSL_STORE_open_ex(keyUri, libCtx, NULL, NULL, NULL, NULL, NULL, NULL); + if (store == NULL) + { + goto end; + } + + // Quite similar to loading a single certificate from a PFX, if we find a private key that wins. + // Otherwise, the first public key wins. + // Otherwise, we'll push a keyload error + while (ret == NULL && !OSSL_STORE_eof(store)) + { + OSSL_STORE_INFO* info = OSSL_STORE_load(store); + + if (info == NULL) + { + continue; + } + + int type = OSSL_STORE_INFO_get_type(info); + + if (type == OSSL_STORE_INFO_PKEY) + { + ret = OSSL_STORE_INFO_get1_PKEY(info); + break; + } + else if (type == OSSL_STORE_INFO_PUBKEY && firstPubKey == NULL) + { + firstPubKey = info; + // skip the free + continue; + } + + OSSL_STORE_INFO_free(info); + } + + if (ret == NULL && firstPubKey != NULL) + { + ret = OSSL_STORE_INFO_get1_PUBKEY(firstPubKey); + } + + if (ret == NULL) + { + ERR_clear_error(); + ERR_put_error(ERR_LIB_NONE, 0, EVP_R_NO_KEY_SET, __FILE__, __LINE__); + } + +end: + if (firstPubKey != NULL) + { + OSSL_STORE_INFO_free(firstPubKey); + } + + if (store != NULL) + { + OSSL_STORE_close(store); + } + + if (ret == NULL) + { + if (prov != NULL) + { + assert(libCtx != NULL); + // we still want a separate check for libCtx as only prov could be NULL + OSSL_PROVIDER_unload(prov); + } + + if (libCtx != NULL) + { + OSSL_LIB_CTX_free(libCtx); + } + + *extraHandle = NULL; + } + else + { + EvpPKeyExtraHandle* extra = (EvpPKeyExtraHandle*)malloc(sizeof(EvpPKeyExtraHandle)); + extra->prov = prov; + extra->libCtx = libCtx; + atomic_init(&extra->refCount, 1); + *extraHandle = extra; + } + + return ret; +#else + (void)providerName; + (void)keyUri; + ERR_put_error(ERR_LIB_NONE, 0, ERR_R_DISABLED, __FILE__, __LINE__); + *extraHandle = NULL; + return NULL; +#endif +} + +EVP_PKEY_CTX* EvpPKeyCtxCreateFromPKey(EVP_PKEY* pkey, void* extraHandle) +{ + assert(pkey != NULL); + +#ifdef NEED_OPENSSL_3_0 + if (API_EXISTS(EVP_PKEY_CTX_new_from_pkey)) + { + EvpPKeyExtraHandle* handle = (EvpPKeyExtraHandle*)extraHandle; + OSSL_LIB_CTX* libCtx = (handle != NULL) ? handle->libCtx : NULL; + return EVP_PKEY_CTX_new_from_pkey(libCtx, pkey, NULL); + } + else +#endif + { +#ifndef NEED_OPENSSL_3_0 + (void)extraHandle; +#endif + return EVP_PKEY_CTX_new(pkey, NULL); + } +} diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.h b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.h index e4d5f85d4b9ec3..084ae28d77cd43 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.h +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.h @@ -12,12 +12,6 @@ Returns the new EVP_PKEY instance. */ PALEXPORT EVP_PKEY* CryptoNative_EvpPkeyCreate(void); -/* -Create a new EVP_PKEY that has the same interior key as currentKey, -optionally verifying that the key has the correct algorithm. -*/ -PALEXPORT EVP_PKEY* CryptoNative_EvpPKeyDuplicate(EVP_PKEY* currentKey, int32_t algId); - /* Cleans up and deletes a EVP_PKEY instance. @@ -27,12 +21,12 @@ No-op if pkey is null. The given EVP_PKEY pointer is invalid after this call. Always succeeds. */ -PALEXPORT void CryptoNative_EvpPkeyDestroy(EVP_PKEY* pkey); +PALEXPORT void CryptoNative_EvpPkeyDestroy(EVP_PKEY* pkey, void* extraHandle); /* -Returns the maximum size, in bytes, of an operation with the provided key. +Returns the cryptographic length of the cryptosystem to which the key belongs, in bits. */ -PALEXPORT int32_t CryptoNative_EvpPKeySize(EVP_PKEY* pkey); +PALEXPORT int32_t CryptoNative_EvpPKeyBits(EVP_PKEY* pkey); /* Used by System.Security.Cryptography.X509Certificates' OpenSslX509CertificateReader when @@ -41,7 +35,16 @@ duplicating a private key context as part of duplicating the Pal object. Returns the number (as of this call) of references to the EVP_PKEY. Anything less than 2 is an error, because the key is already in the process of being freed. */ -PALEXPORT int32_t CryptoNative_UpRefEvpPkey(EVP_PKEY* pkey); +PALEXPORT int32_t CryptoNative_UpRefEvpPkey(EVP_PKEY* pkey, void* extraHandle); + +/* +Returns one of the following 4 values for the given EVP_PKEY: + 0 - unknown + EVP_PKEY_RSA - RSA + EVP_PKEY_EC - EC + EVP_PKEY_DSA - DSA +*/ +PALEXPORT int32_t CryptoNative_EvpPKeyType(EVP_PKEY* key); /* Decodes an X.509 SubjectPublicKeyInfo into an EVP_PKEY*, verifying the interpreted algorithm type. @@ -104,3 +107,18 @@ Returns a valid EVP_PKEY* on success, NULL on failure. haveEngine is 1 if OpenSSL ENGINE's are supported, otherwise 0. */ PALEXPORT EVP_PKEY* CryptoNative_LoadPublicKeyFromEngine(const char* engineName, const char* keyName, int32_t* haveEngine); + +/* +Load a key by URI from a specified OSSL_PROVIDER. + +Returns a valid EVP_PKEY* on success, NULL on failure. +On success extraHandle may be non-null value which we need to keep alive +until the EVP_PKEY is destroyed. +*/ +PALEXPORT EVP_PKEY* CryptoNative_LoadKeyFromProvider(const char* providerName, const char* keyUri, void** extraHandle); + +/* +It's a wrapper for EVP_PKEY_CTX_new_from_pkey and EVP_PKEY_CTX_new +which handles extraHandle. +*/ +EVP_PKEY_CTX* EvpPKeyCtxCreateFromPKey(EVP_PKEY* pkey, void* extraHandle); diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdh.c b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdh.c index b6a9daf715998d..e057afb6d00614 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdh.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdh.c @@ -2,21 +2,19 @@ // The .NET Foundation licenses this file to you under the MIT license. #include "pal_evp_pkey_ecdh.h" +#include "pal_evp_pkey.h" +#include "pal_utilities.h" +#include -EVP_PKEY_CTX* CryptoNative_EvpPKeyCtxCreate(EVP_PKEY* pkey, EVP_PKEY* peerkey, uint32_t* secretLength) +static EVP_PKEY_CTX* EvpPKeyCtxCreate(EVP_PKEY* pkey, void* extraHandle, EVP_PKEY* peerkey) { - if (secretLength != NULL) - *secretLength = 0; - - ERR_clear_error(); - - if (pkey == NULL || peerkey == NULL || secretLength == NULL) + if (pkey == NULL || peerkey == NULL) { return NULL; } /* Create the context for the shared secret derivation */ - EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey, NULL); + EVP_PKEY_CTX* ctx = EvpPKeyCtxCreateFromPKey(pkey, extraHandle); if (ctx == NULL) { @@ -25,43 +23,47 @@ EVP_PKEY_CTX* CryptoNative_EvpPKeyCtxCreate(EVP_PKEY* pkey, EVP_PKEY* peerkey, u size_t tmpLength = 0; - /* Initialize, provide the peer public key, and determine the buffer size */ - if (1 != EVP_PKEY_derive_init(ctx) || 1 != EVP_PKEY_derive_set_peer(ctx, peerkey) || - 1 != EVP_PKEY_derive(ctx, NULL, &tmpLength)) + /* Initialize, provide the peer public key */ + if (1 != EVP_PKEY_derive_init(ctx) || 1 != EVP_PKEY_derive_set_peer(ctx, peerkey)) { EVP_PKEY_CTX_free(ctx); return NULL; } - *secretLength = (uint32_t)tmpLength; return ctx; } -int32_t CryptoNative_EvpPKeyDeriveSecretAgreement(uint8_t* secret, uint32_t secretLength, EVP_PKEY_CTX* ctx) +int32_t CryptoNative_EvpPKeyDeriveSecretAgreement(EVP_PKEY* pkey, void* extraHandle, EVP_PKEY* peerKey, uint8_t* secret, uint32_t secretLength) { - size_t tmpSize = (size_t)secretLength; - int ret = 0; + if (pkey == NULL || peerKey == NULL || secretLength == 0 || secret == NULL) + { + return 0; + } ERR_clear_error(); - if (secret != NULL && ctx != NULL) - { - ret = EVP_PKEY_derive(ctx, secret, &tmpSize); + size_t tmpSize = (size_t)secretLength; + EVP_PKEY_CTX* ctx = EvpPKeyCtxCreate(pkey, extraHandle, peerKey); - if (ret == 1 && tmpSize != (size_t)secretLength) - { - OPENSSL_cleanse(secret, secretLength); - ret = 0; - } + if (ctx == NULL) + { + return 0; } - return ret; -} + // When secret is non-null (which is always the case here) + // tmpSize is both in and out. + // As input it must contain the size of the secret buffer. + // If buffer is too small, the function will fail by returning 0. + // When succeeds it will store number of bytes written. + int ret = EVP_PKEY_derive(ctx, secret, &tmpSize); -void CryptoNative_EvpPKeyCtxDestroy(EVP_PKEY_CTX* ctx) -{ - if (ctx != NULL) + EVP_PKEY_CTX_free(ctx); + + if (ret != 1) { - EVP_PKEY_CTX_free(ctx); + return 0; } + + assert(tmpSize > 0 && tmpSize <= secretLength); + return SizeTToInt32(tmpSize); } diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdh.h b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdh.h index 2319669fdeae84..6cdb6063c1c577 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdh.h +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdh.h @@ -5,8 +5,4 @@ #include "pal_compiler.h" #include "opensslshim.h" -PALEXPORT EVP_PKEY_CTX* CryptoNative_EvpPKeyCtxCreate(EVP_PKEY* pkey, EVP_PKEY* peerkey, uint32_t* secretLength); - -PALEXPORT int32_t CryptoNative_EvpPKeyDeriveSecretAgreement(uint8_t* secret, uint32_t secretLength, EVP_PKEY_CTX* ctx); - -PALEXPORT void CryptoNative_EvpPKeyCtxDestroy(EVP_PKEY_CTX* ctx); +PALEXPORT int32_t CryptoNative_EvpPKeyDeriveSecretAgreement(EVP_PKEY* pkey, void* extraHandle, EVP_PKEY* peerKey, uint8_t* secret, uint32_t secretLength); diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdsa.c b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdsa.c new file mode 100644 index 00000000000000..86f6916297c3db --- /dev/null +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdsa.c @@ -0,0 +1,77 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "pal_evp_pkey.h" +#include "pal_evp_pkey_ecdsa.h" +#include "pal_utilities.h" +#include + +int32_t CryptoNative_EcDsaSignHash(EVP_PKEY* pkey, + void* extraHandle, + const uint8_t* hash, + int32_t hashLen, + uint8_t* destination, + int32_t destinationLen) +{ + assert(pkey != NULL); + assert(destination != NULL); + + ERR_clear_error(); + + EVP_PKEY_CTX* ctx = EvpPKeyCtxCreateFromPKey(pkey, extraHandle); + + int ret = -1; + + if (ctx == NULL || EVP_PKEY_sign_init(ctx) <= 0) + { + goto done; + } + + size_t written = Int32ToSizeT(destinationLen); + + if (EVP_PKEY_sign(ctx, destination, &written, hash, Int32ToSizeT(hashLen)) > 0) + { + ret = SizeTToInt32(written); + } + +done: + if (ctx != NULL) + { + EVP_PKEY_CTX_free(ctx); + } + + return ret; +} + +int32_t CryptoNative_EcDsaVerifyHash(EVP_PKEY* pkey, + void* extraHandle, + const uint8_t* hash, + int32_t hashLen, + const uint8_t* signature, + int32_t signatureLen) +{ + assert(pkey != NULL); + assert(signature != NULL); + + ERR_clear_error(); + + EVP_PKEY_CTX* ctx = EvpPKeyCtxCreateFromPKey(pkey, extraHandle); + + int ret = -1; + + if (ctx == NULL || EVP_PKEY_verify_init(ctx) <= 0) + { + goto done; + } + + // We normalize all error codes to 1 or 0 because we cannot distinguish between missized hash and other errors + ret = EVP_PKEY_verify(ctx, signature, Int32ToSizeT(signatureLen), hash, Int32ToSizeT(hashLen)) == 1; + +done: + if (ctx != NULL) + { + EVP_PKEY_CTX_free(ctx); + } + + return ret; +} diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdsa.h b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdsa.h new file mode 100644 index 00000000000000..adc74db89cb57b --- /dev/null +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ecdsa.h @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "opensslshim.h" +#include "pal_compiler.h" +#include "pal_types.h" + +/* +Complete the ECDSA signature generation for the specified hash using the provided ECDSA key +(wrapped in an EVP_PKEY) and padding/digest options. + +Returns the number of bytes written to destination, -1 on error. +*/ +PALEXPORT int32_t CryptoNative_EcDsaSignHash(EVP_PKEY* pkey, + void* extraHandle, + const uint8_t* hash, + int32_t hashLen, + uint8_t* destination, + int32_t destinationLen); + +/* +Verify an ECDSA signature for the specified hash using the provided ECDSA key (wrapped in an EVP_PKEY) +and padding/digest options. + +Returns 1 on a verified signature, 0 on a mismatched signature, -1 on error. +*/ +PALEXPORT int32_t CryptoNative_EcDsaVerifyHash(EVP_PKEY* pkey, + void* extraHandle, + const uint8_t* hash, + int32_t hashLen, + const uint8_t* signature, + int32_t signatureLen); diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_rsa.c b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_rsa.c index 043bf9f9d1edab..e444d8c0c57b12 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_rsa.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_rsa.c @@ -1,8 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#include "pal_evp_pkey.h" #include "pal_evp_pkey_rsa.h" #include "pal_utilities.h" +#include "openssl.h" #include static int HasNoPrivateKey(const RSA* rsa); @@ -103,6 +105,7 @@ static bool ConfigureEncryption(EVP_PKEY_CTX* ctx, RsaPaddingMode padding, const } int32_t CryptoNative_RsaDecrypt(EVP_PKEY* pkey, + void* extraHandle, const uint8_t* source, int32_t sourceLen, RsaPaddingMode padding, @@ -118,7 +121,7 @@ int32_t CryptoNative_RsaDecrypt(EVP_PKEY* pkey, ERR_clear_error(); - EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey, NULL); + EVP_PKEY_CTX* ctx = EvpPKeyCtxCreateFromPKey(pkey, extraHandle); int ret = -1; @@ -132,7 +135,12 @@ int32_t CryptoNative_RsaDecrypt(EVP_PKEY* pkey, goto done; } - // This check may no longer be needed on OpenSSL 3.0 + // This check will not work with hardware keys coming from OpenSSL providers + // because providers don't seem to set RSA_FLAG_EXT_PKEY (the tpm2 most notably) + // ENGINE-s may or may not set it. + // This is needed only on OpenSSL < 3.0, + // see: https://github.com/dotnet/runtime/issues/53345 + if (CryptoNative_OpenSslVersionNumber() < OPENSSL_VERSION_3_0_RTM) { const RSA* rsa = EVP_PKEY_get0_RSA(pkey); @@ -160,6 +168,7 @@ int32_t CryptoNative_RsaDecrypt(EVP_PKEY* pkey, } int32_t CryptoNative_RsaEncrypt(EVP_PKEY* pkey, + void* extraHandle, const uint8_t* source, int32_t sourceLen, RsaPaddingMode padding, @@ -174,7 +183,7 @@ int32_t CryptoNative_RsaEncrypt(EVP_PKEY* pkey, ERR_clear_error(); - EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey, NULL); + EVP_PKEY_CTX* ctx = EvpPKeyCtxCreateFromPKey(pkey, extraHandle); int ret = -1; @@ -236,6 +245,7 @@ static bool ConfigureSignature(EVP_PKEY_CTX* ctx, RsaPaddingMode padding, const } int32_t CryptoNative_RsaSignHash(EVP_PKEY* pkey, + void* extraHandle, RsaPaddingMode padding, const EVP_MD* digest, const uint8_t* hash, @@ -250,7 +260,7 @@ int32_t CryptoNative_RsaSignHash(EVP_PKEY* pkey, ERR_clear_error(); - EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey, NULL); + EVP_PKEY_CTX* ctx = EvpPKeyCtxCreateFromPKey(pkey, extraHandle); int ret = -1; @@ -264,7 +274,12 @@ int32_t CryptoNative_RsaSignHash(EVP_PKEY* pkey, goto done; } - // This check may no longer be needed on OpenSSL 3.0 + // This check will not work with hardware keys coming from OpenSSL providers + // because providers don't seem to set RSA_FLAG_EXT_PKEY (the tpm2 most notably) + // ENGINE-s may or may not set it. + // This is needed only on OpenSSL < 3.0, + // see: https://github.com/dotnet/runtime/issues/53345 + if (CryptoNative_OpenSslVersionNumber() < OPENSSL_VERSION_3_0_RTM) { const RSA* rsa = EVP_PKEY_get0_RSA(pkey); @@ -292,6 +307,7 @@ int32_t CryptoNative_RsaSignHash(EVP_PKEY* pkey, } int32_t CryptoNative_RsaVerifyHash(EVP_PKEY* pkey, + void* extraHandle, RsaPaddingMode padding, const EVP_MD* digest, const uint8_t* hash, @@ -306,7 +322,7 @@ int32_t CryptoNative_RsaVerifyHash(EVP_PKEY* pkey, ERR_clear_error(); - EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey, NULL); + EVP_PKEY_CTX* ctx = EvpPKeyCtxCreateFromPKey(pkey, extraHandle); int ret = -1; diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_rsa.h b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_rsa.h index 2d3c58787650a8..7ae93de6cf3553 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_rsa.h +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_rsa.h @@ -31,6 +31,7 @@ Decrypt source into destination using the specified RSA key (wrapped in an EVP_P Returns the number of bytes written to destination, -1 on error. */ PALEXPORT int32_t CryptoNative_RsaDecrypt(EVP_PKEY* pkey, + void* extraHandle, const uint8_t* source, int32_t sourceLen, RsaPaddingMode padding, @@ -44,6 +45,7 @@ Encrypt source into destination using the specified RSA key (wrapped in an EVP_P Returns the number of bytes written to destination, -1 on error. */ PALEXPORT int32_t CryptoNative_RsaEncrypt(EVP_PKEY* pkey, + void* extraHandle, const uint8_t* source, int32_t sourceLen, RsaPaddingMode padding, @@ -58,6 +60,7 @@ Complete the RSA signature generation for the specified hash using the provided Returns the number of bytes written to destination, -1 on error. */ PALEXPORT int32_t CryptoNative_RsaSignHash(EVP_PKEY* pkey, + void* extraHandle, RsaPaddingMode padding, const EVP_MD* digest, const uint8_t* hash, @@ -72,10 +75,10 @@ and padding/digest options. Returns 1 on a verified signature, 0 on a mismatched signature, -1 on error. */ PALEXPORT int32_t CryptoNative_RsaVerifyHash(EVP_PKEY* pkey, + void* extraHandle, RsaPaddingMode padding, const EVP_MD* digest, const uint8_t* hash, int32_t hashLen, const uint8_t* signature, int32_t signatureLen); - diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_ssl.c b/src/native/libs/System.Security.Cryptography.Native/pal_ssl.c index 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)