Blob Blame History Raw
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