dotnet3.1/SOURCES/corefx-openssl-0002-Use-EVP_PKEY-for-RSA-Decrypt.patch

1036 lines
43 KiB
Diff
Raw Normal View History

2021-11-03 21:54:43 +00:00
From 16f162f76cdbdd150487eb9824f9d8f8e39df5ca Mon Sep 17 00:00:00 2001
From: Jeremy Barton <jbarton@microsoft.com>
Date: Wed, 24 Mar 2021 10:27:42 -0700
Subject: [PATCH 02/11] Use EVP_PKEY for RSA Decrypt
---
.../Interop.EVP.DigestAlgs.cs | 58 +++++
.../Interop.EVP.cs | 18 +-
.../Interop.EvpPkey.Rsa.cs | 37 +++
.../Interop.EvpPkey.cs | 3 +
.../Interop.Rsa.cs | 16 --
.../Security/Cryptography/RSAOpenSsl.cs | 222 ++++++++++--------
.../apibridge.c | 17 ++
.../apibridge.h | 1 +
.../opensslshim.h | 17 +-
.../pal_evp_pkey.c | 7 +
.../pal_evp_pkey.h | 5 +
.../pal_evp_pkey_rsa.c | 137 ++++++++++-
.../pal_evp_pkey_rsa.h | 27 ++-
.../pal_rsa.c | 13 -
.../pal_rsa.h | 8 -
.../HashProviderDispenser.Unix.cs | 36 +--
...em.Security.Cryptography.Algorithms.csproj | 3 +
...ystem.Security.Cryptography.OpenSsl.csproj | 3 +
18 files changed, 444 insertions(+), 184 deletions(-)
create mode 100644 src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.DigestAlgs.cs
diff --git a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.DigestAlgs.cs b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.DigestAlgs.cs
new file mode 100644
index 0000000000..53ef644d84
--- /dev/null
+++ b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.DigestAlgs.cs
@@ -0,0 +1,58 @@
+// 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 System.Security.Cryptography;
+
+internal static partial class Interop
+{
+ internal static partial class Crypto
+ {
+ private static volatile IntPtr s_evpMd5;
+ private static volatile IntPtr s_evpSha1;
+ private static volatile IntPtr s_evpSha256;
+ private static volatile IntPtr s_evpSha384;
+ private static volatile IntPtr s_evpSha512;
+
+ [DllImport(Libraries.CryptoNative)]
+ private static extern IntPtr CryptoNative_EvpMd5();
+
+ internal static IntPtr EvpMd5() =>
+ s_evpMd5 != IntPtr.Zero ? s_evpMd5 : (s_evpMd5 = CryptoNative_EvpMd5());
+
+ [DllImport(Libraries.CryptoNative)]
+ internal static extern IntPtr CryptoNative_EvpSha1();
+
+ internal static IntPtr EvpSha1() =>
+ s_evpSha1 != IntPtr.Zero ? s_evpSha1 : (s_evpSha1 = CryptoNative_EvpSha1());
+
+ [DllImport(Libraries.CryptoNative)]
+ internal static extern IntPtr CryptoNative_EvpSha256();
+
+ internal static IntPtr EvpSha256() =>
+ s_evpSha256 != IntPtr.Zero ? s_evpSha256 : (s_evpSha256 = CryptoNative_EvpSha256());
+
+ [DllImport(Libraries.CryptoNative)]
+ internal static extern IntPtr CryptoNative_EvpSha384();
+
+ internal static IntPtr EvpSha384() =>
+ s_evpSha384 != IntPtr.Zero ? s_evpSha384 : (s_evpSha384 = CryptoNative_EvpSha384());
+
+ [DllImport(Libraries.CryptoNative)]
+ internal static extern IntPtr CryptoNative_EvpSha512();
+
+ internal static IntPtr EvpSha512() =>
+ s_evpSha512 != IntPtr.Zero ? s_evpSha512 : (s_evpSha512 = CryptoNative_EvpSha512());
+
+ internal static IntPtr HashAlgorithmToEvp(string hashAlgorithmId) => hashAlgorithmId switch
+ {
+ nameof(HashAlgorithmName.SHA1) => EvpSha1(),
+ nameof(HashAlgorithmName.SHA256) => EvpSha256(),
+ nameof(HashAlgorithmName.SHA384) => EvpSha384(),
+ nameof(HashAlgorithmName.SHA512) => EvpSha512(),
+ nameof(HashAlgorithmName.MD5) => EvpMd5(),
+ _ => throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithmId))
+ };
+ }
+}
diff --git a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.cs b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.cs
index 67a9b54230..ea9dc5186d 100644
--- a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.cs
+++ b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.cs
@@ -4,6 +4,7 @@
using System;
using System.Runtime.InteropServices;
+using System.Security.Cryptography;
using Microsoft.Win32.SafeHandles;
internal static partial class Interop
@@ -31,23 +32,6 @@ internal static partial class Crypto
[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpMdSize")]
internal extern static int EvpMdSize(IntPtr md);
-
- [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpMd5")]
- internal extern static IntPtr EvpMd5();
-
- [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpSha1")]
- internal extern static IntPtr EvpSha1();
-
- [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpSha256")]
- internal extern static IntPtr EvpSha256();
-
- [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpSha384")]
- internal extern static IntPtr EvpSha384();
-
- [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpSha512")]
- internal extern static IntPtr EvpSha512();
-
-
[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_GetMaxMdSize")]
private extern static int GetMaxMdSize();
diff --git a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Rsa.cs b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Rsa.cs
index c28522784b..f023ced7f9 100644
--- a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Rsa.cs
+++ b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.Rsa.cs
@@ -2,6 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System;
+using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using Microsoft.Win32.SafeHandles;
@@ -26,6 +28,41 @@ internal static SafeEvpPKeyHandle RsaGenerateKey(int keySize)
return pkey;
}
+ [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_RsaDecrypt")]
+ private static extern int CryptoNative_RsaDecrypt(
+ SafeEvpPKeyHandle pkey,
+ ref byte source,
+ int sourceLength,
+ RSAEncryptionPaddingMode paddingMode,
+ IntPtr digestAlgorithm,
+ ref byte destination,
+ int destinationLength);
+
+ internal static int RsaDecrypt(
+ SafeEvpPKeyHandle pkey,
+ ReadOnlySpan<byte> source,
+ RSAEncryptionPaddingMode paddingMode,
+ IntPtr digestAlgorithm,
+ Span<byte> destination)
+ {
+ int written = CryptoNative_RsaDecrypt(
+ pkey,
+ ref MemoryMarshal.GetReference(source),
+ source.Length,
+ paddingMode,
+ digestAlgorithm,
+ ref MemoryMarshal.GetReference(destination),
+ destination.Length);
+
+ if (written < 0)
+ {
+ Debug.Assert(written == -1);
+ throw CreateOpenSslCryptographicException();
+ }
+
+ return written;
+ }
+
[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPkeyGetRsa")]
internal static extern SafeRsaHandle EvpPkeyGetRsa(SafeEvpPKeyHandle pkey);
diff --git a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs
index 2eff6bfcd1..3ad4bc9d08 100644
--- a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs
+++ b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs
@@ -16,6 +16,9 @@ internal static partial class Crypto
[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPkeyDestroy")]
internal static extern void EvpPkeyDestroy(IntPtr pkey);
+ [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeySize")]
+ internal static extern int EvpPKeySize(SafeEvpPKeyHandle pkey);
+
[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_UpRefEvpPkey")]
internal static extern int UpRefEvpPkey(SafeEvpPKeyHandle handle);
}
diff --git a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Rsa.cs b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Rsa.cs
index a05f020ada..5ad534a8f2 100644
--- a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Rsa.cs
+++ b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Rsa.cs
@@ -44,22 +44,6 @@ internal static partial class Crypto
SafeRsaHandle rsa,
RsaPadding padding);
- internal static int RsaPrivateDecrypt(
- int flen,
- ReadOnlySpan<byte> from,
- Span<byte> to,
- SafeRsaHandle rsa,
- RsaPadding padding) =>
- RsaPrivateDecrypt(flen, ref MemoryMarshal.GetReference(from), ref MemoryMarshal.GetReference(to), rsa, padding);
-
- [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_RsaPrivateDecrypt")]
- private extern static int RsaPrivateDecrypt(
- int flen,
- ref byte from,
- ref byte to,
- SafeRsaHandle rsa,
- RsaPadding padding);
-
internal static int RsaSignPrimitive(
ReadOnlySpan<byte> from,
Span<byte> to,
diff --git a/src/Common/src/System/Security/Cryptography/RSAOpenSsl.cs b/src/Common/src/System/Security/Cryptography/RSAOpenSsl.cs
index 4d4a8414b3..87e31b9dde 100644
--- a/src/Common/src/System/Security/Cryptography/RSAOpenSsl.cs
+++ b/src/Common/src/System/Security/Cryptography/RSAOpenSsl.cs
@@ -85,10 +85,9 @@ public override byte[] Decrypt(byte[] data, RSAEncryptionPadding padding)
if (padding == null)
throw new ArgumentNullException(nameof(padding));
- Interop.Crypto.RsaPadding rsaPadding = GetInteropPadding(padding, out RsaPaddingProcessor oaepProcessor);
- SafeRsaHandle key = GetKey();
-
- int rsaSize = Interop.Crypto.RsaSize(key);
+ ValidatePadding(padding);
+ SafeEvpPKeyHandle key = GetPKey();
+ int rsaSize = Interop.Crypto.EvpPKeySize(key);
byte[] buf = null;
Span<byte> destination = default;
@@ -97,18 +96,15 @@ public override byte[] Decrypt(byte[] data, RSAEncryptionPadding padding)
buf = CryptoPool.Rent(rsaSize);
destination = new Span<byte>(buf, 0, rsaSize);
- if (!TryDecrypt(key, data, destination, rsaPadding, oaepProcessor, out int bytesWritten))
- {
- Debug.Fail($"{nameof(TryDecrypt)} should not return false for RSA_size buffer");
- throw new CryptographicException();
- }
-
+ int bytesWritten = Decrypt(key, data, destination, padding);
return destination.Slice(0, bytesWritten).ToArray();
}
finally
{
CryptographicOperations.ZeroMemory(destination);
CryptoPool.Return(buf, clearSize: 0);
+ // Until EVP_PKEY is what gets stored, free the temporary key handle.
+ key.Dispose();
}
}
@@ -119,82 +115,73 @@ public override byte[] Decrypt(byte[] data, RSAEncryptionPadding padding)
out int bytesWritten)
{
if (padding == null)
- {
throw new ArgumentNullException(nameof(padding));
- }
-
- Interop.Crypto.RsaPadding rsaPadding = GetInteropPadding(padding, out RsaPaddingProcessor oaepProcessor);
- SafeRsaHandle key = GetKey();
- int keySizeBytes = Interop.Crypto.RsaSize(key);
+ ValidatePadding(padding);
+ SafeEvpPKeyHandle key = GetPKey();
+ int keySizeBytes = Interop.Crypto.EvpPKeySize(key);
- // OpenSSL does not take a length value for the destination, so it can write out of bounds.
- // To prevent the OOB write, decrypt into a temporary buffer.
+ // OpenSSL requires that the decryption buffer be at least as large as EVP_PKEY_size.
+ // 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.
if (destination.Length < keySizeBytes)
{
- Span<byte> tmp = stackalloc byte[0];
+ // RSA up through 4096 bits use a stackalloc
+ Span<byte> tmp = stackalloc byte[512];
byte[] rent = null;
- // RSA up through 4096 stackalloc
- if (keySizeBytes <= 512)
+ if (keySizeBytes > tmp.Length)
{
- tmp = stackalloc byte[keySizeBytes];
- }
- else
- {
- rent = ArrayPool<byte>.Shared.Rent(keySizeBytes);
+ rent = CryptoPool.Rent(keySizeBytes);
tmp = rent;
}
- bool ret = TryDecrypt(key, data, tmp, rsaPadding, oaepProcessor, out bytesWritten);
+ int written = Decrypt(key, data, tmp, padding);
+ // Until EVP_PKEY is what gets stored, free the temporary key handle.
+ key.Dispose();
+ bool ret;
- if (ret)
+ if (destination.Length < written)
{
- tmp = tmp.Slice(0, bytesWritten);
-
- if (bytesWritten > destination.Length)
- {
- ret = false;
- bytesWritten = 0;
- }
- else
- {
- tmp.CopyTo(destination);
- }
-
- CryptographicOperations.ZeroMemory(tmp);
+ bytesWritten = 0;
+ ret = false;
+ }
+ else
+ {
+ tmp.Slice(0, written).CopyTo(destination);
+ bytesWritten = written;
+ ret = true;
}
+ // Whether a stackalloc or a rented array, clear our copy of
+ // the decrypted content.
+ CryptographicOperations.ZeroMemory(tmp.Slice(0, written));
+
if (rent != null)
{
- // Already cleared
- ArrayPool<byte>.Shared.Return(rent);
+ // Already cleared.
+ CryptoPool.Return(rent, clearSize: 0);
}
return ret;
}
- return TryDecrypt(key, data, destination, rsaPadding, oaepProcessor, out bytesWritten);
+ bytesWritten = Decrypt(key, data, destination, padding);
+ // Until EVP_PKEY is what gets stored, free the temporary key handle.
+ key.Dispose();
+ return true;
}
- private static bool TryDecrypt(
- SafeRsaHandle key,
+ private static int Decrypt(
+ SafeEvpPKeyHandle key,
ReadOnlySpan<byte> data,
Span<byte> destination,
- Interop.Crypto.RsaPadding rsaPadding,
- RsaPaddingProcessor rsaPaddingProcessor,
- out int bytesWritten)
+ RSAEncryptionPadding padding)
{
- // If rsaPadding is PKCS1 or OAEP-SHA1 then no depadding method should be present.
- // If rsaPadding is NoPadding then a depadding method should be present.
- Debug.Assert(
- (rsaPadding == Interop.Crypto.RsaPadding.NoPadding) ==
- (rsaPaddingProcessor != null));
-
// Caller should have already checked this.
Debug.Assert(!key.IsInvalid);
- int rsaSize = Interop.Crypto.RsaSize(key);
+ int rsaSize = Interop.Crypto.EvpPKeySize(key);
if (data.Length != rsaSize)
{
@@ -203,50 +190,24 @@ public override byte[] Decrypt(byte[] data, RSAEncryptionPadding padding)
if (destination.Length < rsaSize)
{
- bytesWritten = 0;
- return false;
+ Debug.Fail("Caller is responsible for temporary decryption buffer creation");
+ throw new CryptographicException();
}
- Span<byte> decryptBuf = destination;
- byte[] paddingBuf = null;
+ IntPtr hashAlgorithm = IntPtr.Zero;
- if (rsaPaddingProcessor != null)
+ if (padding.Mode == RSAEncryptionPaddingMode.Oaep)
{
- paddingBuf = CryptoPool.Rent(rsaSize);
- decryptBuf = paddingBuf;
+ Debug.Assert(padding.OaepHashAlgorithm.Name != null);
+ hashAlgorithm = Interop.Crypto.HashAlgorithmToEvp(padding.OaepHashAlgorithm.Name);
}
- try
- {
- int returnValue = Interop.Crypto.RsaPrivateDecrypt(data.Length, data, decryptBuf, key, rsaPadding);
- CheckReturn(returnValue);
-
- if (rsaPaddingProcessor != null)
- {
- return rsaPaddingProcessor.DepadOaep(paddingBuf, destination, out bytesWritten);
- }
- else
- {
- // If the padding mode is RSA_NO_PADDING then the size of the decrypted block
- // will be RSA_size. If any padding was used, then some amount (determined by the padding algorithm)
- // will have been reduced, and only returnValue bytes were part of the decrypted
- // body. Either way, we can just use returnValue, but some additional bytes may have been overwritten
- // in the destination span.
- bytesWritten = returnValue;
- }
-
- return true;
- }
- finally
- {
- if (paddingBuf != null)
- {
- // DecryptBuf is paddingBuf if paddingBuf is not null, erase it before returning it.
- // If paddingBuf IS null then decryptBuf was destination, and shouldn't be cleared.
- CryptographicOperations.ZeroMemory(decryptBuf);
- CryptoPool.Return(paddingBuf, clearSize: 0);
- }
- }
+ return Interop.Crypto.RsaDecrypt(
+ key,
+ data,
+ padding.Mode,
+ hashAlgorithm,
+ destination);
}
public override byte[] Encrypt(byte[] data, RSAEncryptionPadding padding)
@@ -550,6 +511,30 @@ private void ThrowIfDisposed()
}
}
+ private SafeEvpPKeyHandle GetPKey()
+ {
+ SafeRsaHandle 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.EvpPkeySetRsa(pkeyHandle, currentKey))
+ {
+ throw Interop.Crypto.CreateOpenSslCryptographicException();
+ }
+ }
+ catch
+ {
+ pkeyHandle.Dispose();
+ throw;
+ }
+
+ return pkeyHandle;
+ }
+
private SafeRsaHandle GetKey()
{
ThrowIfDisposed();
@@ -843,6 +828,55 @@ private static int GetAlgorithmNid(HashAlgorithmName hashAlgorithmName)
return nid;
}
+ private static void ValidatePadding(RSAEncryptionPadding padding)
+ {
+ if (padding == null)
+ {
+ throw new ArgumentNullException(nameof(padding));
+ }
+
+ // There are currently two defined padding modes:
+ // * Oaep has an option (the hash algorithm)
+ // * Pkcs1 has no options
+ //
+ // Anything other than those to modes is an error,
+ // and Pkcs1 having options set is an error, so compare it to
+ // the padding struct instead of the padding mode enum.
+ if (padding.Mode != RSAEncryptionPaddingMode.Oaep &&
+ padding != RSAEncryptionPadding.Pkcs1)
+ {
+ throw PaddingModeNotSupported();
+ }
+ }
+
+ private static void ValidatePadding(RSASignaturePadding padding)
+ {
+ if (padding == null)
+ {
+ throw new ArgumentNullException(nameof(padding));
+ }
+
+ // RSASignaturePadding currently only has the mode property, so
+ // there's no need for a runtime check that PKCS#1 doesn't use
+ // nonsensical options like with RSAEncryptionPadding.
+ //
+ // This would change if we supported PSS with an MGF other than MGF-1,
+ // or with a custom salt size, or with a different MGF digest algorithm
+ // than the data digest algorithm.
+ if (padding.Mode == RSASignaturePaddingMode.Pkcs1)
+ {
+ Debug.Assert(padding == RSASignaturePadding.Pkcs1);
+ }
+ else if (padding.Mode == RSASignaturePaddingMode.Pss)
+ {
+ Debug.Assert(padding == RSASignaturePadding.Pss);
+ }
+ else
+ {
+ throw PaddingModeNotSupported();
+ }
+ }
+
private static Exception PaddingModeNotSupported() =>
new CryptographicException(SR.Cryptography_InvalidPaddingMode);
diff --git a/src/Native/Unix/System.Security.Cryptography.Native/apibridge.c b/src/Native/Unix/System.Security.Cryptography.Native/apibridge.c
index def7198deb..ff71105837 100644
--- a/src/Native/Unix/System.Security.Cryptography.Native/apibridge.c
+++ b/src/Native/Unix/System.Security.Cryptography.Native/apibridge.c
@@ -258,6 +258,23 @@ int32_t local_DSA_set0_key(DSA* dsa, BIGNUM* bnY, BIGNUM* bnX)
return 1;
}
+RSA* local_EVP_PKEY_get0_RSA(EVP_PKEY* pkey)
+{
+ if (pkey == NULL)
+ {
+ return NULL;
+ }
+
+ RSA* rsa = EVP_PKEY_get1_RSA(pkey);
+
+ if (rsa != NULL)
+ {
+ RSA_free(rsa);
+ }
+
+ return rsa;
+}
+
int32_t local_EVP_PKEY_up_ref(EVP_PKEY* pkey)
{
if (!pkey)
diff --git a/src/Native/Unix/System.Security.Cryptography.Native/apibridge.h b/src/Native/Unix/System.Security.Cryptography.Native/apibridge.h
index b58611ae73..e1315499f3 100644
--- a/src/Native/Unix/System.Security.Cryptography.Native/apibridge.h
+++ b/src/Native/Unix/System.Security.Cryptography.Native/apibridge.h
@@ -15,6 +15,7 @@ int32_t local_DSA_set0_pqg(DSA* dsa, BIGNUM* bnP, BIGNUM* bnQ, BIGNUM* bnG);
void local_EVP_CIPHER_CTX_free(EVP_CIPHER_CTX* ctx);
EVP_CIPHER_CTX* local_EVP_CIPHER_CTX_new(void);
int32_t local_EVP_CIPHER_CTX_reset(EVP_CIPHER_CTX* ctx);
+RSA* local_EVP_PKEY_get0_RSA(EVP_PKEY* pkey);
int32_t local_EVP_PKEY_up_ref(EVP_PKEY* pkey);
void local_HMAC_CTX_free(HMAC_CTX* ctx);
HMAC_CTX* local_HMAC_CTX_new(void);
diff --git a/src/Native/Unix/System.Security.Cryptography.Native/opensslshim.h b/src/Native/Unix/System.Security.Cryptography.Native/opensslshim.h
index dff6091e9e..47cb142d25 100644
--- a/src/Native/Unix/System.Security.Cryptography.Native/opensslshim.h
+++ b/src/Native/Unix/System.Security.Cryptography.Native/opensslshim.h
@@ -128,6 +128,7 @@ EVP_CIPHER_CTX* EVP_CIPHER_CTX_new(void);
int32_t EVP_CIPHER_CTX_reset(EVP_CIPHER_CTX* ctx);
void EVP_MD_CTX_free(EVP_MD_CTX* ctx);
EVP_MD_CTX* EVP_MD_CTX_new(void);
+RSA* EVP_PKEY_get0_RSA(EVP_PKEY* pkey);
int32_t EVP_PKEY_up_ref(EVP_PKEY* pkey);
void HMAC_CTX_free(HMAC_CTX* ctx);
HMAC_CTX* HMAC_CTX_new(void);
@@ -178,6 +179,12 @@ int32_t X509_up_ref(X509* x509);
#define EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, bits) \
RSA_pkey_ctx_ctrl(ctx, EVP_PKEY_OP_KEYGEN, EVP_PKEY_CTRL_RSA_KEYGEN_BITS, bits, NULL)
+#undef EVP_PKEY_CTX_set_rsa_padding
+#define EVP_PKEY_CTX_set_rsa_padding(ctx, pad) \
+ RSA_pkey_ctx_ctrl(ctx, -1, EVP_PKEY_CTRL_RSA_PADDING, pad, NULL)
+
+// EVP_PKEY_CTX_set_rsa_oaep_md doesn't call RSA_pkey_ctx_ctrl in 1.1, so don't redefine it here.
+
#endif
#if OPENSSL_VERSION_NUMBER < OPENSSL_VERSION_1_0_2_RTM
@@ -355,10 +362,13 @@ void SSL_get0_alpn_selected(const SSL* ssl, const unsigned char** protocol, unsi
REQUIRED_FUNCTION(EVP_PKEY_CTX_new) \
REQUIRED_FUNCTION(EVP_PKEY_CTX_new_id) \
REQUIRED_FUNCTION(EVP_PKEY_base_id) \
+ REQUIRED_FUNCTION(EVP_PKEY_decrypt) \
+ REQUIRED_FUNCTION(EVP_PKEY_decrypt_init) \
REQUIRED_FUNCTION(EVP_PKEY_derive_set_peer) \
REQUIRED_FUNCTION(EVP_PKEY_derive_init) \
REQUIRED_FUNCTION(EVP_PKEY_derive) \
REQUIRED_FUNCTION(EVP_PKEY_free) \
+ FALLBACK_FUNCTION(EVP_PKEY_get0_RSA) \
REQUIRED_FUNCTION(EVP_PKEY_get1_DSA) \
REQUIRED_FUNCTION(EVP_PKEY_get1_EC_KEY) \
REQUIRED_FUNCTION(EVP_PKEY_get1_RSA) \
@@ -368,6 +378,7 @@ void SSL_get0_alpn_selected(const SSL* ssl, const unsigned char** protocol, unsi
REQUIRED_FUNCTION(EVP_PKEY_set1_DSA) \
REQUIRED_FUNCTION(EVP_PKEY_set1_EC_KEY) \
REQUIRED_FUNCTION(EVP_PKEY_set1_RSA) \
+ REQUIRED_FUNCTION(EVP_PKEY_size) \
FALLBACK_FUNCTION(EVP_PKEY_up_ref) \
REQUIRED_FUNCTION(EVP_rc2_cbc) \
REQUIRED_FUNCTION(EVP_rc2_ecb) \
@@ -448,7 +459,6 @@ void SSL_get0_alpn_selected(const SSL* ssl, const unsigned char** protocol, unsi
REQUIRED_FUNCTION(RSA_new) \
FALLBACK_FUNCTION(RSA_pkey_ctx_ctrl) \
RENAMED_FUNCTION(RSA_PKCS1_OpenSSL, RSA_PKCS1_SSLeay) \
- REQUIRED_FUNCTION(RSA_private_decrypt) \
REQUIRED_FUNCTION(RSA_private_encrypt) \
REQUIRED_FUNCTION(RSA_public_decrypt) \
REQUIRED_FUNCTION(RSA_public_encrypt) \
@@ -748,10 +758,13 @@ FOR_ALL_OPENSSL_FUNCTIONS
#define EVP_PKEY_CTX_new EVP_PKEY_CTX_new_ptr
#define EVP_PKEY_CTX_new_id EVP_PKEY_CTX_new_id_ptr
#define EVP_PKEY_base_id EVP_PKEY_base_id_ptr
+#define EVP_PKEY_decrypt_init EVP_PKEY_decrypt_init_ptr
+#define EVP_PKEY_decrypt EVP_PKEY_decrypt_ptr
#define EVP_PKEY_derive_set_peer EVP_PKEY_derive_set_peer_ptr
#define EVP_PKEY_derive_init EVP_PKEY_derive_init_ptr
#define EVP_PKEY_derive EVP_PKEY_derive_ptr
#define EVP_PKEY_free EVP_PKEY_free_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
#define EVP_PKEY_get1_RSA EVP_PKEY_get1_RSA_ptr
@@ -761,6 +774,7 @@ FOR_ALL_OPENSSL_FUNCTIONS
#define EVP_PKEY_set1_DSA EVP_PKEY_set1_DSA_ptr
#define EVP_PKEY_set1_EC_KEY EVP_PKEY_set1_EC_KEY_ptr
#define EVP_PKEY_set1_RSA EVP_PKEY_set1_RSA_ptr
+#define EVP_PKEY_size EVP_PKEY_size_ptr
#define EVP_PKEY_up_ref EVP_PKEY_up_ref_ptr
#define EVP_rc2_cbc EVP_rc2_cbc_ptr
#define EVP_rc2_ecb EVP_rc2_ecb_ptr
@@ -841,7 +855,6 @@ FOR_ALL_OPENSSL_FUNCTIONS
#define RSA_new RSA_new_ptr
#define RSA_pkey_ctx_ctrl RSA_pkey_ctx_ctrl_ptr
#define RSA_PKCS1_OpenSSL RSA_PKCS1_OpenSSL_ptr
-#define RSA_private_decrypt RSA_private_decrypt_ptr
#define RSA_private_encrypt RSA_private_encrypt_ptr
#define RSA_public_decrypt RSA_public_decrypt_ptr
#define RSA_public_encrypt RSA_public_encrypt_ptr
diff --git a/src/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey.c b/src/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey.c
index 4d479dde6c..f232b382ea 100644
--- a/src/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey.c
+++ b/src/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey.c
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+#include <assert.h>
#include "pal_evp_pkey.h"
EVP_PKEY* CryptoNative_EvpPkeyCreate()
@@ -17,6 +18,12 @@ void CryptoNative_EvpPkeyDestroy(EVP_PKEY* pkey)
}
}
+int32_t CryptoNative_EvpPKeySize(EVP_PKEY* pkey)
+{
+ assert(pkey != NULL);
+ return EVP_PKEY_size(pkey);
+}
+
int32_t CryptoNative_UpRefEvpPkey(EVP_PKEY* pkey)
{
if (!pkey)
diff --git a/src/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey.h b/src/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey.h
index 7baf997d8d..750282efdb 100644
--- a/src/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey.h
+++ b/src/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey.h
@@ -24,6 +24,11 @@ Always succeeds.
*/
DLLEXPORT void CryptoNative_EvpPkeyDestroy(EVP_PKEY* pkey);
+/*
+Returns the maximum size, in bytes, of an operation with the provided key.
+*/
+DLLEXPORT int32_t CryptoNative_EvpPKeySize(EVP_PKEY* pkey);
+
/*
Used by System.Security.Cryptography.X509Certificates' OpenSslX509CertificateReader when
duplicating a private key context as part of duplicating the Pal object.
diff --git a/src/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey_rsa.c b/src/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey_rsa.c
index 29f9238ce9..6235c905db 100644
--- a/src/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey_rsa.c
+++ b/src/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey_rsa.c
@@ -3,6 +3,10 @@
// See the LICENSE file in the project root for more information.
#include "pal_evp_pkey_rsa.h"
+#include "pal_utilities.h"
+#include <assert.h>
+
+static int HasNoPrivateKey(RSA* rsa);
EVP_PKEY* CryptoNative_RsaGenerateKey(int keySize)
{
@@ -16,8 +20,7 @@ EVP_PKEY* CryptoNative_RsaGenerateKey(int keySize)
EVP_PKEY* pkey = NULL;
EVP_PKEY* ret = NULL;
- if (EVP_PKEY_keygen_init(ctx) == 1 &&
- EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, keySize) == 1 &&
+ if (EVP_PKEY_keygen_init(ctx) == 1 && EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, keySize) == 1 &&
EVP_PKEY_keygen(ctx, &pkey) == 1)
{
ret = pkey;
@@ -33,6 +36,82 @@ EVP_PKEY* CryptoNative_RsaGenerateKey(int keySize)
return ret;
}
+int32_t CryptoNative_RsaDecrypt(EVP_PKEY* pkey,
+ const uint8_t* source,
+ int32_t sourceLen,
+ RsaPaddingMode padding,
+ const EVP_MD* digest,
+ uint8_t* destination,
+ int32_t destinationLen)
+{
+ assert(pkey != NULL);
+ assert(source != NULL);
+ assert(destination != NULL);
+ assert(padding >= RsaPaddingPkcs1 && padding <= RsaPaddingOaepOrPss);
+ assert(digest != NULL || padding == RsaPaddingPkcs1);
+
+ EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey, NULL);
+
+ int ret = -1;
+
+ if (ctx == NULL || EVP_PKEY_decrypt_init(ctx) <= 0)
+ {
+ goto done;
+ }
+
+ if (padding == RsaPaddingPkcs1)
+ {
+ if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0)
+ {
+ goto done;
+ }
+ }
+ else
+ {
+ assert(padding == RsaPaddingOaepOrPss);
+
+ if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0)
+ {
+ goto done;
+ }
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wcast-qual"
+ if (EVP_PKEY_CTX_set_rsa_oaep_md(ctx, digest) <= 0)
+#pragma clang diagnostic pop
+ {
+ goto done;
+ }
+ }
+
+ // This check may no longer be needed on OpenSSL 3.0
+ {
+ RSA* rsa = EVP_PKEY_get0_RSA(pkey);
+
+ if (rsa == NULL || HasNoPrivateKey(rsa))
+ {
+ ERR_PUT_error(ERR_LIB_RSA, RSA_F_RSA_NULL_PRIVATE_DECRYPT, RSA_R_VALUE_MISSING, __FILE__, __LINE__);
+ ret = -1;
+ goto done;
+ }
+ }
+
+ size_t written = Int32ToSizeT(destinationLen);
+
+ if (EVP_PKEY_decrypt(ctx, destination, &written, source, Int32ToSizeT(sourceLen)) > 0)
+ {
+ ret = SizeTToInt32(written);
+ }
+
+done:
+ if (ctx != NULL)
+ {
+ EVP_PKEY_CTX_free(ctx);
+ }
+
+ return ret;
+}
+
RSA* CryptoNative_EvpPkeyGetRsa(EVP_PKEY* pkey)
{
return EVP_PKEY_get1_RSA(pkey);
@@ -42,3 +121,57 @@ int32_t CryptoNative_EvpPkeySetRsa(EVP_PKEY* pkey, RSA* rsa)
{
return EVP_PKEY_set1_RSA(pkey, rsa);
}
+
+static int HasNoPrivateKey(RSA* rsa)
+{
+ if (rsa == NULL)
+ return 1;
+
+ // Shared pointer, don't free.
+ const RSA_METHOD* meth = RSA_get_method(rsa);
+
+ // The method has descibed itself as having the private key external to the structure.
+ // That doesn't mean it's actually present, but we can't tell.
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wcast-qual"
+ if (RSA_meth_get_flags((RSA_METHOD*)meth) & RSA_FLAG_EXT_PKEY)
+#pragma clang diagnostic pop
+ {
+ return 0;
+ }
+
+ // In the event that there's a middle-ground where we report failure when success is expected,
+ // one could do something like check if the RSA_METHOD intercepts all private key operations:
+ //
+ // * meth->rsa_priv_enc
+ // * meth->rsa_priv_dec
+ // * meth->rsa_sign (in 1.0.x this is only respected if the RSA_FLAG_SIGN_VER flag is asserted)
+ //
+ // But, for now, leave it at the EXT_PKEY flag test.
+
+ // The module is documented as accepting either d or the full set of CRT parameters (p, q, dp, dq, qInv)
+ // So if we see d, we're good. Otherwise, if any of the rest are missing, we're public-only.
+ const BIGNUM* d;
+ RSA_get0_key(rsa, NULL, NULL, &d);
+
+ if (d != NULL)
+ {
+ return 0;
+ }
+
+ const BIGNUM* p;
+ const BIGNUM* q;
+ const BIGNUM* dmp1;
+ const BIGNUM* dmq1;
+ const BIGNUM* iqmp;
+
+ RSA_get0_factors(rsa, &p, &q);
+ RSA_get0_crt_params(rsa, &dmp1, &dmq1, &iqmp);
+
+ if (p == NULL || q == NULL || dmp1 == NULL || dmq1 == NULL || iqmp == NULL)
+ {
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/src/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey_rsa.h b/src/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey_rsa.h
index 1fda149414..d220065adf 100644
--- a/src/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey_rsa.h
+++ b/src/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey_rsa.h
@@ -2,15 +2,38 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-#include "pal_types.h"
-#include "pal_compiler.h"
#include "opensslshim.h"
+#include "pal_compiler.h"
+#include "pal_types.h"
+
+/*
+Padding options for RSA.
+Matches RSAEncryptionPaddingMode / RSASignaturePaddingMode.
+*/
+typedef enum
+{
+ RsaPaddingPkcs1,
+ RsaPaddingOaepOrPss,
+} RsaPaddingMode;
/*
Creates an RSA key of the requested size.
*/
DLLEXPORT EVP_PKEY* CryptoNative_RsaGenerateKey(int32_t keySize);
+/*
+Decrypt source into destination using the specified RSA key (wrapped in an EVP_PKEY) and padding/digest options.
+
+Returns the number of bytes written to destination, -1 on error.
+*/
+DLLEXPORT int32_t CryptoNative_RsaDecrypt(EVP_PKEY* pkey,
+ const uint8_t* source,
+ int32_t sourceLen,
+ RsaPaddingMode padding,
+ const EVP_MD* digest,
+ uint8_t* destination,
+ int32_t destinationLen);
+
/*
Shims the EVP_PKEY_get1_RSA method.
diff --git a/src/Native/Unix/System.Security.Cryptography.Native/pal_rsa.c b/src/Native/Unix/System.Security.Cryptography.Native/pal_rsa.c
index 080027de0e..0c635dfca7 100644
--- a/src/Native/Unix/System.Security.Cryptography.Native/pal_rsa.c
+++ b/src/Native/Unix/System.Security.Cryptography.Native/pal_rsa.c
@@ -109,19 +109,6 @@ CryptoNative_RsaPublicEncrypt(int32_t flen, const uint8_t* from, uint8_t* to, RS
return RSA_public_encrypt(flen, from, to, rsa, openSslPadding);
}
-int32_t
-CryptoNative_RsaPrivateDecrypt(int32_t flen, const uint8_t* from, uint8_t* to, RSA* rsa, RsaPadding padding)
-{
- if (HasNoPrivateKey(rsa))
- {
- ERR_PUT_error(ERR_LIB_RSA, RSA_F_RSA_NULL_PRIVATE_DECRYPT, RSA_R_VALUE_MISSING, __FILE__, __LINE__);
- return -1;
- }
-
- int openSslPadding = GetOpenSslPadding(padding);
- return RSA_private_decrypt(flen, from, to, rsa, openSslPadding);
-}
-
int32_t CryptoNative_RsaSignPrimitive(int32_t flen, const uint8_t* from, uint8_t* to, RSA* rsa)
{
if (HasNoPrivateKey(rsa))
diff --git a/src/Native/Unix/System.Security.Cryptography.Native/pal_rsa.h b/src/Native/Unix/System.Security.Cryptography.Native/pal_rsa.h
index 1c0bc2df47..30d7d9fa59 100644
--- a/src/Native/Unix/System.Security.Cryptography.Native/pal_rsa.h
+++ b/src/Native/Unix/System.Security.Cryptography.Native/pal_rsa.h
@@ -55,14 +55,6 @@ Returns the size of the signature, or -1 on error.
DLLEXPORT int32_t
CryptoNative_RsaPublicEncrypt(int32_t flen, const uint8_t* from, uint8_t* to, RSA* rsa, RsaPadding padding);
-/*
-Shims the RSA_private_decrypt method.
-
-Returns the size of the signature, or -1 on error.
-*/
-DLLEXPORT int32_t
-CryptoNative_RsaPrivateDecrypt(int32_t flen, const uint8_t* from, uint8_t* to, RSA* rsa, RsaPadding padding);
-
/*
Shims RSA_private_encrypt with a fixed value of RSA_NO_PADDING.
diff --git a/src/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/HashProviderDispenser.Unix.cs b/src/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/HashProviderDispenser.Unix.cs
index ff8e91e7c9..589ff882bf 100644
--- a/src/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/HashProviderDispenser.Unix.cs
+++ b/src/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/HashProviderDispenser.Unix.cs
@@ -12,40 +12,16 @@ namespace Internal.Cryptography
{
internal static partial class HashProviderDispenser
{
- public static HashProvider CreateHashProvider(string hashAlgorithmId)
+ internal static HashProvider CreateHashProvider(string hashAlgorithmId)
{
- switch (hashAlgorithmId)
- {
- case HashAlgorithmNames.SHA1:
- return new EvpHashProvider(Interop.Crypto.EvpSha1());
- case HashAlgorithmNames.SHA256:
- return new EvpHashProvider(Interop.Crypto.EvpSha256());
- case HashAlgorithmNames.SHA384:
- return new EvpHashProvider(Interop.Crypto.EvpSha384());
- case HashAlgorithmNames.SHA512:
- return new EvpHashProvider(Interop.Crypto.EvpSha512());
- case HashAlgorithmNames.MD5:
- return new EvpHashProvider(Interop.Crypto.EvpMd5());
- }
- throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithmId));
+ IntPtr evpType = Interop.Crypto.HashAlgorithmToEvp(hashAlgorithmId);
+ return new EvpHashProvider(evpType);
}
- public static unsafe HashProvider CreateMacProvider(string hashAlgorithmId, byte[] key)
+ internal static unsafe HashProvider CreateMacProvider(string hashAlgorithmId, byte[] key)
{
- switch (hashAlgorithmId)
- {
- case HashAlgorithmNames.SHA1:
- return new HmacHashProvider(Interop.Crypto.EvpSha1(), key);
- case HashAlgorithmNames.SHA256:
- return new HmacHashProvider(Interop.Crypto.EvpSha256(), key);
- case HashAlgorithmNames.SHA384:
- return new HmacHashProvider(Interop.Crypto.EvpSha384(), key);
- case HashAlgorithmNames.SHA512:
- return new HmacHashProvider(Interop.Crypto.EvpSha512(), key);
- case HashAlgorithmNames.MD5:
- return new HmacHashProvider(Interop.Crypto.EvpMd5(), key);
- }
- throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithmId));
+ IntPtr evpType = Interop.Crypto.HashAlgorithmToEvp(hashAlgorithmId);
+ return new HmacHashProvider(evpType, key);
}
// -----------------------------
diff --git a/src/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj b/src/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj
index 6ad2b78e01..c6e8b5b69a 100644
--- a/src/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj
+++ b/src/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj
@@ -513,6 +513,9 @@
<Compile Include="$(CommonPath)\Interop\Unix\System.Security.Cryptography.Native\Interop.EVP.cs">
<Link>Common\Interop\Unix\System.Security.Cryptography.Native\Interop.EVP.cs</Link>
</Compile>
+ <Compile Include="$(CommonPath)\Interop\Unix\System.Security.Cryptography.Native\Interop.EVP.DigestAlgs.cs">
+ <Link>Common\Interop\Unix\System.Security.Cryptography.Native\Interop.EVP.DigestAlgs.cs</Link>
+ </Compile>
<Compile Include="$(CommonPath)\Interop\Unix\System.Security.Cryptography.Native\Interop.Hmac.cs">
<Link>Common\Interop\Unix\System.Security.Cryptography.Native\Interop.Hmac.cs</Link>
</Compile>
diff --git a/src/System.Security.Cryptography.OpenSsl/src/System.Security.Cryptography.OpenSsl.csproj b/src/System.Security.Cryptography.OpenSsl/src/System.Security.Cryptography.OpenSsl.csproj
index 17ab176ec2..dbbc4848e8 100644
--- a/src/System.Security.Cryptography.OpenSsl/src/System.Security.Cryptography.OpenSsl.csproj
+++ b/src/System.Security.Cryptography.OpenSsl/src/System.Security.Cryptography.OpenSsl.csproj
@@ -57,6 +57,9 @@
<Compile Include="$(CommonPath)\Interop\Unix\System.Security.Cryptography.Native\Interop.ERR.cs">
<Link>Common\Interop\Unix\System.Security.Cryptography.Native\Interop.ERR.cs</Link>
</Compile>
+ <Compile Include="$(CommonPath)\Interop\Unix\System.Security.Cryptography.Native\Interop.EVP.DigestAlgs.cs">
+ <Link>Common\Interop\Unix\System.Security.Cryptography.Native\Interop.EVP.DigestAlgs.cs</Link>
+ </Compile>
<Compile Include="$(CommonPath)\Interop\Unix\System.Security.Cryptography.Native\Interop.EvpPkey.cs">
<Link>Common\Interop\Unix\System.Security.Cryptography.Native\Interop.EvpPkey.cs</Link>
</Compile>
--
2.31.1