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 68f4f8cb6433f..f5129fe32d643 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 @@ -281,7 +281,8 @@ internal static SafeEvpPKeyHandle LoadPublicKeyFromEngine( private static partial IntPtr CryptoNative_LoadKeyFromProvider( string providerName, string keyUri, - ref IntPtr extraHandle); + ref IntPtr extraHandle, + [MarshalAs(UnmanagedType.Bool)] out bool haveProvider); internal static SafeEvpPKeyHandle LoadKeyFromProvider( string providerName, @@ -292,7 +293,13 @@ internal static SafeEvpPKeyHandle LoadKeyFromProvider( try { - evpPKeyHandle = CryptoNative_LoadKeyFromProvider(providerName, keyUri, ref extraHandle); + evpPKeyHandle = CryptoNative_LoadKeyFromProvider(providerName, keyUri, ref extraHandle, out bool haveProvider); + + if (!haveProvider) + { + Debug.Assert(evpPKeyHandle == IntPtr.Zero && extraHandle == IntPtr.Zero, "both handles should be null if provider is not supported"); + throw new PlatformNotSupportedException(SR.PlatformNotSupported_CryptographyOpenSSLProvidersNotSupported); + } if (evpPKeyHandle == IntPtr.Zero || extraHandle == IntPtr.Zero) { diff --git a/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx b/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx index 630f2f89bc6ff..412cfbf03a8ac 100644 --- a/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx +++ b/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx @@ -834,6 +834,9 @@ OpenSSL is required for algorithm '{0}' but could not be found or loaded. + + OpenSSL providers are not supported on this platform. + Access is denied. diff --git a/src/libraries/System.Security.Cryptography/tests/OpenSslNamedKeysTests.manual.cs b/src/libraries/System.Security.Cryptography/tests/OpenSslNamedKeysTests.manual.cs index b5aab83a1aa3b..e2396634c79a0 100644 --- a/src/libraries/System.Security.Cryptography/tests/OpenSslNamedKeysTests.manual.cs +++ b/src/libraries/System.Security.Cryptography/tests/OpenSslNamedKeysTests.manual.cs @@ -2,8 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Linq; +using System.Text; using Test.Cryptography; using Xunit; +using TempFileHolder = System.Security.Cryptography.X509Certificates.Tests.TempFileHolder; namespace System.Security.Cryptography.Tests { @@ -43,10 +45,13 @@ public class OpenSslNamedKeysTests 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 ProvidersSupported { get; } = PlatformDetection.IsOpenSsl3; + public static bool ProvidersNotSupported => !ProvidersSupported; + public static bool ShouldRunProviderEcDsaTests { get; } = ProvidersSupported && !string.IsNullOrEmpty(TpmEcDsaKeyHandleUri); + public static bool ShouldRunProviderEcDhTests { get; } = ProvidersSupported && !string.IsNullOrEmpty(TpmEcDhKeyHandleUri); + public static bool ShouldRunProviderRsaSignTests { get; } = ProvidersSupported && !string.IsNullOrEmpty(TpmRsaSignKeyHandleUri); + public static bool ShouldRunProviderRsaDecryptTests { get; } = ProvidersSupported && !string.IsNullOrEmpty(TpmRsaDecryptKeyHandleUri); public static bool ShouldRunAnyProviderTests => ShouldRunProviderEcDsaTests || ShouldRunProviderEcDhTests || ShouldRunProviderRsaSignTests || ShouldRunProviderRsaDecryptTests; public static bool ShouldRunTpmTssTests => ShouldRunEngineTests && !string.IsNullOrEmpty(TpmEcDsaKeyHandle); @@ -86,11 +91,30 @@ private static string GetHandleKeyUri(string handle) "B27434FA544BDAC679E1E16581D0E90203010001").HexToByteArray(); [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.OpenSslNotPresentOnSystem))] - public static void NotSupported() + public static void EngineNotSupported_ThrowsPlatformNotSupported() { Assert.Throws(() => SafeEvpPKeyHandle.OpenPublicKeyFromEngine(TestEngineName, TestEngineKeyId)); Assert.Throws(() => SafeEvpPKeyHandle.OpenPrivateKeyFromEngine(TestEngineName, TestEngineKeyId)); - Assert.Throws(() => SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, AnyProviderKeyUri)); + } + + [ConditionalFact(nameof(ProvidersNotSupported))] + public static void ProvidersNotSupported_ThrowsPlatformNotSupported() + { + try + { + using SafeEvpPKeyHandle key = SafeEvpPKeyHandle.OpenKeyFromProvider("default", NonExistingEngineOrProviderKeyName); + Assert.Fail("We expected an exception to be thrown"); + } + catch (PlatformNotSupportedException) + { + // Expected + } + catch (CryptographicException) when (PlatformDetection.IsApplePlatform) + { + // Our tests detect providers using PlatformDetection.IsOpenSsl3 which is always false for Apple platforms. + // Product on the other hand does feature detection and that might end up working + // in which case we should still throw any CryptographicException because the keyUri does not exist. + } } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.OpenSslPresentOnSystem))] @@ -111,10 +135,14 @@ public static void EmptyNameThroughNullCharacter() { Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPrivateKeyFromEngine("\0", "foo")); Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPublicKeyFromEngine("\0", "foo")); - Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenKeyFromProvider("\0", "foo")); + + if (ProvidersSupported) + { + Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenKeyFromProvider("\0", "foo")); + } } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.OpenSslPresentOnSystem))] + [ConditionalFact(nameof(ProvidersSupported))] public static void EmptyUriThroughNullCharacter() { Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenKeyFromProvider("default", "\0")); @@ -127,7 +155,7 @@ public static void Engine_NonExisting() Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenPublicKeyFromEngine(NonExistingEngineOrProviderKeyName, TestEngineKeyId)); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.OpenSslPresentOnSystem))] + [ConditionalFact(nameof(ProvidersSupported))] public static void Provider_NonExisting() { Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenKeyFromProvider(NonExistingEngineOrProviderKeyName, AnyProviderKeyUri)); @@ -146,6 +174,63 @@ public static void Provider_NonExistingKey() Assert.ThrowsAny(() => SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, NonExistingEngineOrProviderKeyName)); } + [ConditionalFact(nameof(ProvidersSupported))] + public static void Provider_Default_RSASignAndDecrypt() + { + using RSA originalKey = RSA.Create(); + string pem = originalKey.ExportRSAPrivateKeyPem(); + + using TempFileHolder pemFile = new TempFileHolder(Encoding.UTF8.GetBytes(pem)); + Uri fileUri = new Uri(pemFile.FilePath); + string keyUri = fileUri.AbsoluteUri; + using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenKeyFromProvider("default", keyUri); + using RSA rsaPri = new RSAOpenSsl(priKeyHandle); + byte[] data = new byte[] { 1, 2, 3, 1, 1, 2, 3 }; + byte[] signature = rsaPri.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pss); + Assert.True(originalKey.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss), "signature does not verify with the right key"); + + byte[] encrypted = originalKey.Encrypt(data, RSAEncryptionPadding.OaepSHA256); + byte[] decrypted = rsaPri.Decrypt(encrypted, RSAEncryptionPadding.OaepSHA256); + Assert.Equal(data, decrypted); + } + + [ConditionalFact(nameof(ProvidersSupported))] + public static void Provider_Default_ECDsaSignAndVerify() + { + using ECDsa originalKey = ECDsa.Create(); + string pem = originalKey.ExportECPrivateKeyPem(); + + using TempFileHolder pemFile = new TempFileHolder(Encoding.UTF8.GetBytes(pem)); + Uri fileUri = new Uri(pemFile.FilePath); + string keyUri = fileUri.AbsoluteUri; + using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenKeyFromProvider("default", keyUri); + using ECDsa ecdsaPri = new ECDsaOpenSsl(priKeyHandle); + byte[] data = new byte[] { 1, 2, 3, 1, 1, 2, 3 }; + byte[] signature = ecdsaPri.SignData(data, HashAlgorithmName.SHA256); + Assert.True(originalKey.VerifyData(data, signature, HashAlgorithmName.SHA256), "signature does not verify with the right key"); + } + + [ConditionalFact(nameof(ProvidersSupported))] + public static void Provider_Default_ECDHKeyExchange() + { + using ECDiffieHellman originalAliceKey = ECDiffieHellman.Create(); + string pem = originalAliceKey.ExportECPrivateKeyPem(); + + using TempFileHolder pemFile = new TempFileHolder(Encoding.UTF8.GetBytes(pem)); + Uri fileUri = new Uri(pemFile.FilePath); + string keyUri = fileUri.AbsoluteUri; + using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenKeyFromProvider("default", keyUri); + using ECDiffieHellman alicePri = new ECDiffieHellmanOpenSsl(priKeyHandle); + using ECDiffieHellman bobPri = ECDiffieHellman.Create(alicePri.ExportParameters(false).Curve); + + byte[] sharedSecret1 = originalAliceKey.DeriveRawSecretAgreement(bobPri.PublicKey); + byte[] sharedSecret2 = alicePri.DeriveRawSecretAgreement(bobPri.PublicKey); + byte[] sharedSecret3 = bobPri.DeriveRawSecretAgreement(alicePri.PublicKey); + + Assert.Equal(sharedSecret1, sharedSecret2); + Assert.Equal(sharedSecret1, sharedSecret3); + } + [ConditionalFact(nameof(ShouldRunEngineTests))] public static void Engine_OpenExistingPrivateKey() { 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 2e0e8efee114e..f80ef99c02515 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 @@ -611,19 +611,20 @@ EVP_PKEY* CryptoNative_LoadPublicKeyFromEngine(const char* engineName, const cha return NULL; } -EVP_PKEY* CryptoNative_LoadKeyFromProvider(const char* providerName, const char* keyUri, void** extraHandle) +EVP_PKEY* CryptoNative_LoadKeyFromProvider(const char* providerName, const char* keyUri, void** extraHandle, int32_t* haveProvider) { 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__); + *haveProvider = 0; return NULL; } #endif #ifdef NEED_OPENSSL_3_0 + *haveProvider = 1; EVP_PKEY* ret = NULL; OSSL_LIB_CTX* libCtx = OSSL_LIB_CTX_new(); OSSL_PROVIDER* prov = NULL; @@ -730,6 +731,7 @@ EVP_PKEY* CryptoNative_LoadKeyFromProvider(const char* providerName, const char* (void)keyUri; ERR_put_error(ERR_LIB_NONE, 0, ERR_R_DISABLED, __FILE__, __LINE__); *extraHandle = NULL; + *haveProvider = 0; return NULL; #endif } 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 084ae28d77cd4..f71b64cb07c09 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 @@ -104,7 +104,7 @@ PALEXPORT EVP_PKEY* CryptoNative_LoadPrivateKeyFromEngine(const char* engineName Load a named key, via ENGINE_load_public_key, from the named engine. Returns a valid EVP_PKEY* on success, NULL on failure. -haveEngine is 1 if OpenSSL ENGINE's are supported, otherwise 0. +*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); @@ -114,8 +114,10 @@ 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. + +*haveProvider is 1 if OpenSSL providers are supported, otherwise 0. */ -PALEXPORT EVP_PKEY* CryptoNative_LoadKeyFromProvider(const char* providerName, const char* keyUri, void** extraHandle); +PALEXPORT EVP_PKEY* CryptoNative_LoadKeyFromProvider(const char* providerName, const char* keyUri, void** extraHandle, int32_t* haveProvider); /* It's a wrapper for EVP_PKEY_CTX_new_from_pkey and EVP_PKEY_CTX_new