Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make SafeEvpPKeyHandle.OpenKeyFromProvider throw PNSE on OSSL less than 3.0 #106397

Merged
merged 3 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -834,6 +834,9 @@
<data name="PlatformNotSupported_CryptographyOpenSSLNotFound" xml:space="preserve">
<value>OpenSSL is required for algorithm '{0}' but could not be found or loaded.</value>
</data>
<data name="PlatformNotSupported_CryptographyOpenSSLProvidersNotSupported" xml:space="preserve">
<value>OpenSSL providers are not supported on this platform.</value>
</data>
<data name="Security_AccessDenied" xml:space="preserve">
<value>Access is denied.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -86,10 +91,15 @@ 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<PlatformNotSupportedException>(() => SafeEvpPKeyHandle.OpenPublicKeyFromEngine(TestEngineName, TestEngineKeyId));
Assert.Throws<PlatformNotSupportedException>(() => SafeEvpPKeyHandle.OpenPrivateKeyFromEngine(TestEngineName, TestEngineKeyId));
}

[ConditionalFact(nameof(ProvidersNotSupported))]
public static void ProvidersNotSupported_ThrowsPlatformNotSupported()
{
Assert.Throws<PlatformNotSupportedException>(() => SafeEvpPKeyHandle.OpenKeyFromProvider(Tpm2ProviderName, AnyProviderKeyUri));
}

Expand All @@ -111,10 +121,14 @@ public static void EmptyNameThroughNullCharacter()
{
Assert.ThrowsAny<CryptographicException>(() => SafeEvpPKeyHandle.OpenPrivateKeyFromEngine("\0", "foo"));
Assert.ThrowsAny<CryptographicException>(() => SafeEvpPKeyHandle.OpenPublicKeyFromEngine("\0", "foo"));
Assert.ThrowsAny<CryptographicException>(() => SafeEvpPKeyHandle.OpenKeyFromProvider("\0", "foo"));

if (ProvidersSupported)
{
Assert.ThrowsAny<CryptographicException>(() => SafeEvpPKeyHandle.OpenKeyFromProvider("\0", "foo"));
}
}

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

[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.OpenSslPresentOnSystem))]
[ConditionalFact(nameof(ProvidersSupported))]
public static void Provider_NonExisting()
{
Assert.ThrowsAny<CryptographicException>(() => SafeEvpPKeyHandle.OpenKeyFromProvider(NonExistingEngineOrProviderKeyName, AnyProviderKeyUri));
Expand All @@ -146,6 +160,63 @@ public static void Provider_NonExistingKey()
Assert.ThrowsAny<CryptographicException>(() => 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()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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
Expand Down
Loading