Skip to content

Commit 91ae788

Browse files
[release/9.0] Make SafeEvpPKeyHandle.OpenKeyFromProvider throw PNSE on OSSL less than 3.0 (#106753)
* Make SafeEvpPKeyHandle.OpenKeyFromProvider throw PNSE on OSSL less than 3.0 * address feedback * Address recent feedback * Relax PNSE expectation for OSSL providers on Apple platforms (#106804) --------- Co-authored-by: Krzysztof Wicher <[email protected]> Co-authored-by: Krzysztof Wicher <[email protected]>
1 parent 2937bf3 commit 91ae788

File tree

5 files changed

+114
-15
lines changed

5 files changed

+114
-15
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: 94 additions & 9 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,11 +91,30 @@ 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));
93-
Assert.Throws<PlatformNotSupportedException>(() => SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, AnyProviderKeyUri));
98+
}
99+
100+
[ConditionalFact(nameof(ProvidersNotSupported))]
101+
public static void ProvidersNotSupported_ThrowsPlatformNotSupported()
102+
{
103+
try
104+
{
105+
using SafeEvpPKeyHandle key = SafeEvpPKeyHandle.OpenKeyFromProvider("default", NonExistingEngineOrProviderKeyName);
106+
Assert.Fail("We expected an exception to be thrown");
107+
}
108+
catch (PlatformNotSupportedException)
109+
{
110+
// Expected
111+
}
112+
catch (CryptographicException) when (PlatformDetection.IsApplePlatform)
113+
{
114+
// Our tests detect providers using PlatformDetection.IsOpenSsl3 which is always false for Apple platforms.
115+
// Product on the other hand does feature detection and that might end up working
116+
// in which case we should still throw any CryptographicException because the keyUri does not exist.
117+
}
94118
}
95119

96120
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.OpenSslPresentOnSystem))]
@@ -111,10 +135,14 @@ public static void EmptyNameThroughNullCharacter()
111135
{
112136
Assert.ThrowsAny<CryptographicException>(() => SafeEvpPKeyHandle.OpenPrivateKeyFromEngine("\0", "foo"));
113137
Assert.ThrowsAny<CryptographicException>(() => SafeEvpPKeyHandle.OpenPublicKeyFromEngine("\0", "foo"));
114-
Assert.ThrowsAny<CryptographicException>(() => SafeEvpPKeyHandle.OpenKeyFromProvider("\0", "foo"));
138+
139+
if (ProvidersSupported)
140+
{
141+
Assert.ThrowsAny<CryptographicException>(() => SafeEvpPKeyHandle.OpenKeyFromProvider("\0", "foo"));
142+
}
115143
}
116144

117-
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.OpenSslPresentOnSystem))]
145+
[ConditionalFact(nameof(ProvidersSupported))]
118146
public static void EmptyUriThroughNullCharacter()
119147
{
120148
Assert.ThrowsAny<CryptographicException>(() => SafeEvpPKeyHandle.OpenKeyFromProvider("default", "\0"));
@@ -127,7 +155,7 @@ public static void Engine_NonExisting()
127155
Assert.ThrowsAny<CryptographicException>(() => SafeEvpPKeyHandle.OpenPublicKeyFromEngine(NonExistingEngineOrProviderKeyName, TestEngineKeyId));
128156
}
129157

130-
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.OpenSslPresentOnSystem))]
158+
[ConditionalFact(nameof(ProvidersSupported))]
131159
public static void Provider_NonExisting()
132160
{
133161
Assert.ThrowsAny<CryptographicException>(() => SafeEvpPKeyHandle.OpenKeyFromProvider(NonExistingEngineOrProviderKeyName, AnyProviderKeyUri));
@@ -146,6 +174,63 @@ public static void Provider_NonExistingKey()
146174
Assert.ThrowsAny<CryptographicException>(() => SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, NonExistingEngineOrProviderKeyName));
147175
}
148176

177+
[ConditionalFact(nameof(ProvidersSupported))]
178+
public static void Provider_Default_RSASignAndDecrypt()
179+
{
180+
using RSA originalKey = RSA.Create();
181+
string pem = originalKey.ExportRSAPrivateKeyPem();
182+
183+
using TempFileHolder pemFile = new TempFileHolder(Encoding.UTF8.GetBytes(pem));
184+
Uri fileUri = new Uri(pemFile.FilePath);
185+
string keyUri = fileUri.AbsoluteUri;
186+
using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenKeyFromProvider("default", keyUri);
187+
using RSA rsaPri = new RSAOpenSsl(priKeyHandle);
188+
byte[] data = new byte[] { 1, 2, 3, 1, 1, 2, 3 };
189+
byte[] signature = rsaPri.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);
190+
Assert.True(originalKey.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss), "signature does not verify with the right key");
191+
192+
byte[] encrypted = originalKey.Encrypt(data, RSAEncryptionPadding.OaepSHA256);
193+
byte[] decrypted = rsaPri.Decrypt(encrypted, RSAEncryptionPadding.OaepSHA256);
194+
Assert.Equal(data, decrypted);
195+
}
196+
197+
[ConditionalFact(nameof(ProvidersSupported))]
198+
public static void Provider_Default_ECDsaSignAndVerify()
199+
{
200+
using ECDsa originalKey = ECDsa.Create();
201+
string pem = originalKey.ExportECPrivateKeyPem();
202+
203+
using TempFileHolder pemFile = new TempFileHolder(Encoding.UTF8.GetBytes(pem));
204+
Uri fileUri = new Uri(pemFile.FilePath);
205+
string keyUri = fileUri.AbsoluteUri;
206+
using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenKeyFromProvider("default", keyUri);
207+
using ECDsa ecdsaPri = new ECDsaOpenSsl(priKeyHandle);
208+
byte[] data = new byte[] { 1, 2, 3, 1, 1, 2, 3 };
209+
byte[] signature = ecdsaPri.SignData(data, HashAlgorithmName.SHA256);
210+
Assert.True(originalKey.VerifyData(data, signature, HashAlgorithmName.SHA256), "signature does not verify with the right key");
211+
}
212+
213+
[ConditionalFact(nameof(ProvidersSupported))]
214+
public static void Provider_Default_ECDHKeyExchange()
215+
{
216+
using ECDiffieHellman originalAliceKey = ECDiffieHellman.Create();
217+
string pem = originalAliceKey.ExportECPrivateKeyPem();
218+
219+
using TempFileHolder pemFile = new TempFileHolder(Encoding.UTF8.GetBytes(pem));
220+
Uri fileUri = new Uri(pemFile.FilePath);
221+
string keyUri = fileUri.AbsoluteUri;
222+
using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenKeyFromProvider("default", keyUri);
223+
using ECDiffieHellman alicePri = new ECDiffieHellmanOpenSsl(priKeyHandle);
224+
using ECDiffieHellman bobPri = ECDiffieHellman.Create(alicePri.ExportParameters(false).Curve);
225+
226+
byte[] sharedSecret1 = originalAliceKey.DeriveRawSecretAgreement(bobPri.PublicKey);
227+
byte[] sharedSecret2 = alicePri.DeriveRawSecretAgreement(bobPri.PublicKey);
228+
byte[] sharedSecret3 = bobPri.DeriveRawSecretAgreement(alicePri.PublicKey);
229+
230+
Assert.Equal(sharedSecret1, sharedSecret2);
231+
Assert.Equal(sharedSecret1, sharedSecret3);
232+
}
233+
149234
[ConditionalFact(nameof(ShouldRunEngineTests))]
150235
public static void Engine_OpenExistingPrivateKey()
151236
{

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)