Skip to content

Commit 779baa6

Browse files
authored
Make SafeEvpPKeyHandle.OpenKeyFromProvider throw PNSE on OSSL less than 3.0 (#106397)
* Make SafeEvpPKeyHandle.OpenKeyFromProvider throw PNSE on OSSL less than 3.0 * address feedback * Address recent feedback
1 parent 67993d0 commit 779baa6

File tree

5 files changed

+99
-14
lines changed

5 files changed

+99
-14
lines changed

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,8 @@ internal static SafeEvpPKeyHandle LoadPublicKeyFromEngine(
281281
private static partial IntPtr CryptoNative_LoadKeyFromProvider(
282282
string providerName,
283283
string keyUri,
284-
ref IntPtr extraHandle);
284+
ref IntPtr extraHandle,
285+
[MarshalAs(UnmanagedType.Bool)] out bool haveProvider);
285286

286287
internal static SafeEvpPKeyHandle LoadKeyFromProvider(
287288
string providerName,
@@ -292,7 +293,13 @@ internal static SafeEvpPKeyHandle LoadKeyFromProvider(
292293

293294
try
294295
{
295-
evpPKeyHandle = CryptoNative_LoadKeyFromProvider(providerName, keyUri, ref extraHandle);
296+
evpPKeyHandle = CryptoNative_LoadKeyFromProvider(providerName, keyUri, ref extraHandle, out bool haveProvider);
297+
298+
if (!haveProvider)
299+
{
300+
Debug.Assert(evpPKeyHandle == IntPtr.Zero && extraHandle == IntPtr.Zero, "both handles should be null if provider is not supported");
301+
throw new PlatformNotSupportedException(SR.PlatformNotSupported_CryptographyOpenSSLProvidersNotSupported);
302+
}
296303

297304
if (evpPKeyHandle == IntPtr.Zero || extraHandle == IntPtr.Zero)
298305
{

src/libraries/System.Security.Cryptography/src/Resources/Strings.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -834,6 +834,9 @@
834834
<data name="PlatformNotSupported_CryptographyOpenSSLNotFound" xml:space="preserve">
835835
<value>OpenSSL is required for algorithm '{0}' but could not be found or loaded.</value>
836836
</data>
837+
<data name="PlatformNotSupported_CryptographyOpenSSLProvidersNotSupported" xml:space="preserve">
838+
<value>OpenSSL providers are not supported on this platform.</value>
839+
</data>
837840
<data name="Security_AccessDenied" xml:space="preserve">
838841
<value>Access is denied.</value>
839842
</data>

src/libraries/System.Security.Cryptography/tests/OpenSslNamedKeysTests.manual.cs

Lines changed: 79 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Linq;
5+
using System.Text;
56
using Test.Cryptography;
67
using Xunit;
8+
using TempFileHolder = System.Security.Cryptography.X509Certificates.Tests.TempFileHolder;
79

810
namespace System.Security.Cryptography.Tests
911
{
@@ -43,10 +45,13 @@ public class OpenSslNamedKeysTests
4345
private static string TpmRsaDecryptKeyHandleUri { get; } = GetHandleKeyUri(TpmRsaDecryptKeyHandle);
4446

4547
public static bool ShouldRunEngineTests { get; } = PlatformDetection.OpenSslPresentOnSystem && StringToBool(Environment.GetEnvironmentVariable(TestEngineEnabledEnvVarName));
46-
public static bool ShouldRunProviderEcDsaTests { get; } = PlatformDetection.OpenSslPresentOnSystem && !string.IsNullOrEmpty(TpmEcDsaKeyHandleUri);
47-
public static bool ShouldRunProviderEcDhTests { get; } = PlatformDetection.OpenSslPresentOnSystem && !string.IsNullOrEmpty(TpmEcDhKeyHandleUri);
48-
public static bool ShouldRunProviderRsaSignTests { get; } = PlatformDetection.OpenSslPresentOnSystem && !string.IsNullOrEmpty(TpmRsaSignKeyHandleUri);
49-
public static bool ShouldRunProviderRsaDecryptTests { get; } = PlatformDetection.OpenSslPresentOnSystem && !string.IsNullOrEmpty(TpmRsaDecryptKeyHandleUri);
48+
49+
public static bool ProvidersSupported { get; } = PlatformDetection.IsOpenSsl3;
50+
public static bool ProvidersNotSupported => !ProvidersSupported;
51+
public static bool ShouldRunProviderEcDsaTests { get; } = ProvidersSupported && !string.IsNullOrEmpty(TpmEcDsaKeyHandleUri);
52+
public static bool ShouldRunProviderEcDhTests { get; } = ProvidersSupported && !string.IsNullOrEmpty(TpmEcDhKeyHandleUri);
53+
public static bool ShouldRunProviderRsaSignTests { get; } = ProvidersSupported && !string.IsNullOrEmpty(TpmRsaSignKeyHandleUri);
54+
public static bool ShouldRunProviderRsaDecryptTests { get; } = ProvidersSupported && !string.IsNullOrEmpty(TpmRsaDecryptKeyHandleUri);
5055
public static bool ShouldRunAnyProviderTests => ShouldRunProviderEcDsaTests || ShouldRunProviderEcDhTests || ShouldRunProviderRsaSignTests || ShouldRunProviderRsaDecryptTests;
5156

5257
public static bool ShouldRunTpmTssTests => ShouldRunEngineTests && !string.IsNullOrEmpty(TpmEcDsaKeyHandle);
@@ -86,10 +91,15 @@ private static string GetHandleKeyUri(string handle)
8691
"B27434FA544BDAC679E1E16581D0E90203010001").HexToByteArray();
8792

8893
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.OpenSslNotPresentOnSystem))]
89-
public static void NotSupported()
94+
public static void EngineNotSupported_ThrowsPlatformNotSupported()
9095
{
9196
Assert.Throws<PlatformNotSupportedException>(() => SafeEvpPKeyHandle.OpenPublicKeyFromEngine(TestEngineName, TestEngineKeyId));
9297
Assert.Throws<PlatformNotSupportedException>(() => SafeEvpPKeyHandle.OpenPrivateKeyFromEngine(TestEngineName, TestEngineKeyId));
98+
}
99+
100+
[ConditionalFact(nameof(ProvidersNotSupported))]
101+
public static void ProvidersNotSupported_ThrowsPlatformNotSupported()
102+
{
93103
Assert.Throws<PlatformNotSupportedException>(() => SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, AnyProviderKeyUri));
94104
}
95105

@@ -111,10 +121,14 @@ public static void EmptyNameThroughNullCharacter()
111121
{
112122
Assert.ThrowsAny<CryptographicException>(() => SafeEvpPKeyHandle.OpenPrivateKeyFromEngine("\0", "foo"));
113123
Assert.ThrowsAny<CryptographicException>(() => SafeEvpPKeyHandle.OpenPublicKeyFromEngine("\0", "foo"));
114-
Assert.ThrowsAny<CryptographicException>(() => SafeEvpPKeyHandle.OpenKeyFromProvider("\0", "foo"));
124+
125+
if (ProvidersSupported)
126+
{
127+
Assert.ThrowsAny<CryptographicException>(() => SafeEvpPKeyHandle.OpenKeyFromProvider("\0", "foo"));
128+
}
115129
}
116130

117-
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.OpenSslPresentOnSystem))]
131+
[ConditionalFact(nameof(ProvidersSupported))]
118132
public static void EmptyUriThroughNullCharacter()
119133
{
120134
Assert.ThrowsAny<CryptographicException>(() => SafeEvpPKeyHandle.OpenKeyFromProvider("default", "\0"));
@@ -127,7 +141,7 @@ public static void Engine_NonExisting()
127141
Assert.ThrowsAny<CryptographicException>(() => SafeEvpPKeyHandle.OpenPublicKeyFromEngine(NonExistingEngineOrProviderKeyName, TestEngineKeyId));
128142
}
129143

130-
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.OpenSslPresentOnSystem))]
144+
[ConditionalFact(nameof(ProvidersSupported))]
131145
public static void Provider_NonExisting()
132146
{
133147
Assert.ThrowsAny<CryptographicException>(() => SafeEvpPKeyHandle.OpenKeyFromProvider(NonExistingEngineOrProviderKeyName, AnyProviderKeyUri));
@@ -146,6 +160,63 @@ public static void Provider_NonExistingKey()
146160
Assert.ThrowsAny<CryptographicException>(() => SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, NonExistingEngineOrProviderKeyName));
147161
}
148162

163+
[ConditionalFact(nameof(ProvidersSupported))]
164+
public static void Provider_Default_RSASignAndDecrypt()
165+
{
166+
using RSA originalKey = RSA.Create();
167+
string pem = originalKey.ExportRSAPrivateKeyPem();
168+
169+
using TempFileHolder pemFile = new TempFileHolder(Encoding.UTF8.GetBytes(pem));
170+
Uri fileUri = new Uri(pemFile.FilePath);
171+
string keyUri = fileUri.AbsoluteUri;
172+
using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenKeyFromProvider("default", keyUri);
173+
using RSA rsaPri = new RSAOpenSsl(priKeyHandle);
174+
byte[] data = new byte[] { 1, 2, 3, 1, 1, 2, 3 };
175+
byte[] signature = rsaPri.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);
176+
Assert.True(originalKey.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss), "signature does not verify with the right key");
177+
178+
byte[] encrypted = originalKey.Encrypt(data, RSAEncryptionPadding.OaepSHA256);
179+
byte[] decrypted = rsaPri.Decrypt(encrypted, RSAEncryptionPadding.OaepSHA256);
180+
Assert.Equal(data, decrypted);
181+
}
182+
183+
[ConditionalFact(nameof(ProvidersSupported))]
184+
public static void Provider_Default_ECDsaSignAndVerify()
185+
{
186+
using ECDsa originalKey = ECDsa.Create();
187+
string pem = originalKey.ExportECPrivateKeyPem();
188+
189+
using TempFileHolder pemFile = new TempFileHolder(Encoding.UTF8.GetBytes(pem));
190+
Uri fileUri = new Uri(pemFile.FilePath);
191+
string keyUri = fileUri.AbsoluteUri;
192+
using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenKeyFromProvider("default", keyUri);
193+
using ECDsa ecdsaPri = new ECDsaOpenSsl(priKeyHandle);
194+
byte[] data = new byte[] { 1, 2, 3, 1, 1, 2, 3 };
195+
byte[] signature = ecdsaPri.SignData(data, HashAlgorithmName.SHA256);
196+
Assert.True(originalKey.VerifyData(data, signature, HashAlgorithmName.SHA256), "signature does not verify with the right key");
197+
}
198+
199+
[ConditionalFact(nameof(ProvidersSupported))]
200+
public static void Provider_Default_ECDHKeyExchange()
201+
{
202+
using ECDiffieHellman originalAliceKey = ECDiffieHellman.Create();
203+
string pem = originalAliceKey.ExportECPrivateKeyPem();
204+
205+
using TempFileHolder pemFile = new TempFileHolder(Encoding.UTF8.GetBytes(pem));
206+
Uri fileUri = new Uri(pemFile.FilePath);
207+
string keyUri = fileUri.AbsoluteUri;
208+
using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenKeyFromProvider("default", keyUri);
209+
using ECDiffieHellman alicePri = new ECDiffieHellmanOpenSsl(priKeyHandle);
210+
using ECDiffieHellman bobPri = ECDiffieHellman.Create(alicePri.ExportParameters(false).Curve);
211+
212+
byte[] sharedSecret1 = originalAliceKey.DeriveRawSecretAgreement(bobPri.PublicKey);
213+
byte[] sharedSecret2 = alicePri.DeriveRawSecretAgreement(bobPri.PublicKey);
214+
byte[] sharedSecret3 = bobPri.DeriveRawSecretAgreement(alicePri.PublicKey);
215+
216+
Assert.Equal(sharedSecret1, sharedSecret2);
217+
Assert.Equal(sharedSecret1, sharedSecret3);
218+
}
219+
149220
[ConditionalFact(nameof(ShouldRunEngineTests))]
150221
public static void Engine_OpenExistingPrivateKey()
151222
{

src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -611,19 +611,20 @@ EVP_PKEY* CryptoNative_LoadPublicKeyFromEngine(const char* engineName, const cha
611611
return NULL;
612612
}
613613

614-
EVP_PKEY* CryptoNative_LoadKeyFromProvider(const char* providerName, const char* keyUri, void** extraHandle)
614+
EVP_PKEY* CryptoNative_LoadKeyFromProvider(const char* providerName, const char* keyUri, void** extraHandle, int32_t* haveProvider)
615615
{
616616
ERR_clear_error();
617617

618618
#ifdef FEATURE_DISTRO_AGNOSTIC_SSL
619619
if (!API_EXISTS(OSSL_PROVIDER_load))
620620
{
621-
ERR_put_error(ERR_LIB_NONE, 0, ERR_R_DISABLED, __FILE__, __LINE__);
621+
*haveProvider = 0;
622622
return NULL;
623623
}
624624
#endif
625625

626626
#ifdef NEED_OPENSSL_3_0
627+
*haveProvider = 1;
627628
EVP_PKEY* ret = NULL;
628629
OSSL_LIB_CTX* libCtx = OSSL_LIB_CTX_new();
629630
OSSL_PROVIDER* prov = NULL;
@@ -730,6 +731,7 @@ EVP_PKEY* CryptoNative_LoadKeyFromProvider(const char* providerName, const char*
730731
(void)keyUri;
731732
ERR_put_error(ERR_LIB_NONE, 0, ERR_R_DISABLED, __FILE__, __LINE__);
732733
*extraHandle = NULL;
734+
*haveProvider = 0;
733735
return NULL;
734736
#endif
735737
}

src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ PALEXPORT EVP_PKEY* CryptoNative_LoadPrivateKeyFromEngine(const char* engineName
104104
Load a named key, via ENGINE_load_public_key, from the named engine.
105105
106106
Returns a valid EVP_PKEY* on success, NULL on failure.
107-
haveEngine is 1 if OpenSSL ENGINE's are supported, otherwise 0.
107+
*haveEngine is 1 if OpenSSL ENGINE's are supported, otherwise 0.
108108
*/
109109
PALEXPORT EVP_PKEY* CryptoNative_LoadPublicKeyFromEngine(const char* engineName, const char* keyName, int32_t* haveEngine);
110110

@@ -114,8 +114,10 @@ Load a key by URI from a specified OSSL_PROVIDER.
114114
Returns a valid EVP_PKEY* on success, NULL on failure.
115115
On success extraHandle may be non-null value which we need to keep alive
116116
until the EVP_PKEY is destroyed.
117+
118+
*haveProvider is 1 if OpenSSL providers are supported, otherwise 0.
117119
*/
118-
PALEXPORT EVP_PKEY* CryptoNative_LoadKeyFromProvider(const char* providerName, const char* keyUri, void** extraHandle);
120+
PALEXPORT EVP_PKEY* CryptoNative_LoadKeyFromProvider(const char* providerName, const char* keyUri, void** extraHandle, int32_t* haveProvider);
119121

120122
/*
121123
It's a wrapper for EVP_PKEY_CTX_new_from_pkey and EVP_PKEY_CTX_new

0 commit comments

Comments
 (0)