// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Buffers;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Formats.Asn1;
using System.IO;
using System.Runtime.Versioning;
using System.Security.Cryptography.Asn1;
using Microsoft.Win32.SafeHandles;
using Internal.Cryptography;

namespace System.Security.Cryptography
{
    public sealed partial class RSAOpenSsl : RSA, IRuntimeAlgorithm
    {
        private const int BitsPerByte = 8;

        private Lazy<SafeEvpPKeyHandle>? _key;

        [UnsupportedOSPlatform("android")]
        [UnsupportedOSPlatform("browser")]
        [UnsupportedOSPlatform("ios")]
        [UnsupportedOSPlatform("tvos")]
        [UnsupportedOSPlatform("windows")]
        public RSAOpenSsl()
            : this(2048)
        {
        }

        [UnsupportedOSPlatform("android")]
        [UnsupportedOSPlatform("browser")]
        [UnsupportedOSPlatform("ios")]
        [UnsupportedOSPlatform("tvos")]
        [UnsupportedOSPlatform("windows")]
        public RSAOpenSsl(int keySize)
        {
            ThrowIfNotSupported();
            base.KeySize = keySize;
            _key = new Lazy<SafeEvpPKeyHandle>(GenerateKey);
        }

        public override int KeySize
        {
            set
            {
                if (KeySize == value)
                {
                    return;
                }

                // Set the KeySize before FreeKey so that an invalid value doesn't throw away the key
                base.KeySize = value;

                ThrowIfDisposed();
                FreeKey();
                _key = new Lazy<SafeEvpPKeyHandle>(GenerateKey);
            }
        }

        private void ForceSetKeySize(int newKeySize)
        {
            // In the event that a key was loaded via ImportParameters or an IntPtr/SafeHandle
            // it could be outside of the bounds that we currently represent as "legal key sizes".
            // Since that is our view into the underlying component it can be detached from the
            // component's understanding.  If it said it has opened a key, and this is the size, trust it.
            KeySizeValue = newKeySize;
        }

        public override KeySizes[] LegalKeySizes
        {
            get
            {
                // While OpenSSL 1.0.x and 1.1.0 will generate RSA-384 keys,
                // OpenSSL 1.1.1 has lifted the minimum to RSA-512.
                //
                // Rather than make the matrix even more complicated,
                // the low limit now is 512 on all OpenSSL-based RSA.
                return new[] { new KeySizes(512, 16384, 8) };
            }
        }

        public override byte[] Decrypt(byte[] data, RSAEncryptionPadding padding)
        {
            ArgumentNullException.ThrowIfNull(data);
            ArgumentNullException.ThrowIfNull(padding);

            ValidatePadding(padding);
            SafeEvpPKeyHandle key = GetKey();
            int rsaSize = Interop.Crypto.EvpPKeySize(key);
            Span<byte> destination = default;
            byte[] buf = CryptoPool.Rent(rsaSize);

            try
            {
                destination = new Span<byte>(buf, 0, rsaSize);

                int bytesWritten = Decrypt(key, data, destination, padding);
                return destination.Slice(0, bytesWritten).ToArray();
            }
            finally
            {
                CryptographicOperations.ZeroMemory(destination);
                CryptoPool.Return(buf, clearSize: 0);
            }
        }

        public override bool TryDecrypt(
            ReadOnlySpan<byte> data,
            Span<byte> destination,
            RSAEncryptionPadding padding,
            out int bytesWritten)
        {
            ArgumentNullException.ThrowIfNull(padding);

            ValidatePadding(padding);
            SafeEvpPKeyHandle key = GetKey();
            int keySizeBytes = Interop.Crypto.EvpPKeySize(key);

            // 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)
            {
                // RSA up through 4096 bits use a stackalloc
                Span<byte> tmp = stackalloc byte[512];
                byte[]? rent = null;

                if (keySizeBytes > tmp.Length)
                {
                    rent = CryptoPool.Rent(keySizeBytes);
                    tmp = rent;
                }

                int written = Decrypt(key, data, tmp, padding);
                bool ret;

                if (destination.Length < written)
                {
                    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.
                    CryptoPool.Return(rent, clearSize: 0);
                }

                return ret;
            }

            bytesWritten = Decrypt(key, data, destination, padding);
            return true;
        }

        private static int Decrypt(
            SafeEvpPKeyHandle key,
            ReadOnlySpan<byte> data,
            Span<byte> destination,
            RSAEncryptionPadding padding)
        {
            // Caller should have already checked this.
            Debug.Assert(!key.IsInvalid);

            int rsaSize = Interop.Crypto.EvpPKeySize(key);

            if (data.Length != rsaSize)
            {
                throw new CryptographicException(SR.Cryptography_RSA_DecryptWrongSize);
            }

            if (destination.Length < rsaSize)
            {
                Debug.Fail("Caller is responsible for temporary decryption buffer creation");
                throw new CryptographicException();
            }

            IntPtr hashAlgorithm = IntPtr.Zero;

            if (padding.Mode == RSAEncryptionPaddingMode.Oaep)
            {
                Debug.Assert(padding.OaepHashAlgorithm.Name != null);
                hashAlgorithm = Interop.Crypto.HashAlgorithmToEvp(padding.OaepHashAlgorithm.Name);
            }

            return Interop.Crypto.RsaDecrypt(
                key,
                data,
                padding.Mode,
                hashAlgorithm,
                destination);
        }

        public override byte[] Encrypt(byte[] data, RSAEncryptionPadding padding)
        {
            ArgumentNullException.ThrowIfNull(data);
            ArgumentNullException.ThrowIfNull(padding);

            ValidatePadding(padding);
            SafeEvpPKeyHandle key = GetKey();

            byte[] buf = new byte[Interop.Crypto.EvpPKeySize(key)];

            bool encrypted = TryEncrypt(
                key,
                data,
                buf,
                padding,
                out int bytesWritten);

            if (!encrypted || bytesWritten != buf.Length)
            {
                Debug.Fail($"TryEncrypt behaved unexpectedly: {nameof(encrypted)}=={encrypted}, {nameof(bytesWritten)}=={bytesWritten}, {nameof(buf.Length)}=={buf.Length}");
                throw new CryptographicException();
            }

            return buf;
        }

        public override bool TryEncrypt(ReadOnlySpan<byte> data, Span<byte> destination, RSAEncryptionPadding padding, out int bytesWritten)
        {
            ArgumentNullException.ThrowIfNull(padding);

            ValidatePadding(padding);
            SafeEvpPKeyHandle? key = GetKey();

            return TryEncrypt(key, data, destination, padding, out bytesWritten);
        }

        private static bool TryEncrypt(
            SafeEvpPKeyHandle key,
            ReadOnlySpan<byte> data,
            Span<byte> destination,
            RSAEncryptionPadding padding,
            out int bytesWritten)
        {
            int rsaSize = Interop.Crypto.EvpPKeySize(key);

            if (destination.Length < rsaSize)
            {
                bytesWritten = 0;
                return false;
            }

            IntPtr hashAlgorithm = IntPtr.Zero;

            if (padding.Mode == RSAEncryptionPaddingMode.Oaep)
            {
                Debug.Assert(padding.OaepHashAlgorithm.Name != null);
                hashAlgorithm = Interop.Crypto.HashAlgorithmToEvp(padding.OaepHashAlgorithm.Name);
            }

            int written = Interop.Crypto.RsaEncrypt(
                key,
                data,
                padding.Mode,
                hashAlgorithm,
                destination);

            Debug.Assert(written == rsaSize);
            bytesWritten = written;
            return true;
        }

        private delegate T ExportPrivateKeyFunc<T>(ReadOnlyMemory<byte> pkcs8, ReadOnlyMemory<byte> pkcs1);

        private delegate ReadOnlyMemory<byte> TryExportPrivateKeySelector(
            ReadOnlyMemory<byte> pkcs8,
            ReadOnlyMemory<byte> pkcs1);

        private T ExportPrivateKey<T>(ExportPrivateKeyFunc<T> exporter)
        {
            // It's entirely possible that this line will cause the key to be generated in the first place.
            SafeEvpPKeyHandle key = GetKey();

            ArraySegment<byte> p8 = Interop.Crypto.RentEncodePkcs8PrivateKey(key);

            try
            {
                ReadOnlyMemory<byte> pkcs1 = VerifyPkcs8(p8);
                return exporter(p8, pkcs1);
            }
            finally
            {
                CryptoPool.Return(p8);
            }
        }

        private bool TryExportPrivateKey(TryExportPrivateKeySelector selector, Span<byte> destination, out int bytesWritten)
        {
            // It's entirely possible that this line will cause the key to be generated in the first place.
            SafeEvpPKeyHandle key = GetKey();

            ArraySegment<byte> p8 = Interop.Crypto.RentEncodePkcs8PrivateKey(key);

            try
            {
                ReadOnlyMemory<byte> pkcs1 = VerifyPkcs8(p8);
                ReadOnlyMemory<byte> selected = selector(p8, pkcs1);
                return selected.Span.TryCopyToDestination(destination, out bytesWritten);
            }
            finally
            {
                CryptoPool.Return(p8);
            }
        }

        private T ExportPublicKey<T>(Func<ReadOnlyMemory<byte>, T> exporter)
        {
            // It's entirely possible that this line will cause the key to be generated in the first place.
            SafeEvpPKeyHandle key = GetKey();

            ArraySegment<byte> spki = Interop.Crypto.RentEncodeSubjectPublicKeyInfo(key);

            try
            {
                return exporter(spki);
            }
            finally
            {
                CryptoPool.Return(spki);
            }
        }

        private bool TryExportPublicKey(
            Func<ReadOnlyMemory<byte>, ReadOnlyMemory<byte>>? transform,
            Span<byte> destination,
            out int bytesWritten)
        {
            // It's entirely possible that this line will cause the key to be generated in the first place.
            SafeEvpPKeyHandle key = GetKey();

            ArraySegment<byte> spki = Interop.Crypto.RentEncodeSubjectPublicKeyInfo(key);

            try
            {
                ReadOnlyMemory<byte> data = spki;

                if (transform != null)
                {
                    data = transform(data);
                }

                return data.Span.TryCopyToDestination(destination, out bytesWritten);
            }
            finally
            {
                CryptoPool.Return(spki);
            }
        }

        public override bool TryExportPkcs8PrivateKey(Span<byte> destination, out int bytesWritten)
        {
            return TryExportPrivateKey(static (pkcs8, pkcs1) => pkcs8, destination, out bytesWritten);
        }

        public override byte[] ExportPkcs8PrivateKey()
        {
            return ExportPrivateKey(static (pkcs8, pkcs1) => pkcs8.ToArray());
        }

        public override bool TryExportRSAPrivateKey(Span<byte> destination, out int bytesWritten)
        {
            return TryExportPrivateKey(static (pkcs8, pkcs1) => pkcs1, destination, out bytesWritten);
        }

        public override byte[] ExportRSAPrivateKey()
        {
            return ExportPrivateKey(static (pkcs8, pkcs1) => pkcs1.ToArray());
        }

        public override byte[] ExportRSAPublicKey()
        {
            return ExportPublicKey(
                static spki =>
                {
                    ReadOnlyMemory<byte> pkcs1 = RSAKeyFormatHelper.ReadSubjectPublicKeyInfo(spki, out int read);
                    Debug.Assert(read == spki.Length);
                    return pkcs1.ToArray();
                });
        }

        public override bool TryExportRSAPublicKey(Span<byte> destination, out int bytesWritten)
        {
            return TryExportPublicKey(
                spki =>
                {
                    ReadOnlyMemory<byte> pkcs1 = RSAKeyFormatHelper.ReadSubjectPublicKeyInfo(spki, out int read);
                    Debug.Assert(read == spki.Length);
                    return pkcs1;
                },
                destination,
                out bytesWritten);
        }

        public override byte[] ExportSubjectPublicKeyInfo()
        {
            return ExportPublicKey(static spki => spki.ToArray());
        }

        public override bool TryExportSubjectPublicKeyInfo(Span<byte> destination, out int bytesWritten)
        {
            return TryExportPublicKey(
                transform: null,
                destination,
                out bytesWritten);
        }

        public override RSAParameters ExportParameters(bool includePrivateParameters)
        {
            if (includePrivateParameters)
            {
                return ExportPrivateKey(
                    static (pkcs8, pkcs1) =>
                    {
                        AlgorithmIdentifierAsn algId = default;
                        RSAParameters ret;
                        RSAKeyFormatHelper.FromPkcs1PrivateKey(pkcs1, in algId, out ret);
                        return ret;
                    });
            }

            return ExportPublicKey(
                static spki =>
                {
                    RSAParameters ret;
                    RSAKeyFormatHelper.ReadSubjectPublicKeyInfo(
                        spki.Span,
                        out int read,
                        out ret);

                    Debug.Assert(read == spki.Length);
                    return ret;
                });
        }

        public override void ImportParameters(RSAParameters parameters)
        {
            ValidateParameters(ref parameters);
            ThrowIfDisposed();

            if (parameters.D != null)
            {
                AsnWriter writer = RSAKeyFormatHelper.WritePkcs8PrivateKey(parameters);
                ArraySegment<byte> pkcs8 = writer.RentAndEncode();

                try
                {
                    ImportPkcs8PrivateKey(pkcs8, checkAlgorithm: false, out _);
                }
                finally
                {
                    CryptoPool.Return(pkcs8);
                }
            }
            else
            {
                AsnWriter writer = RSAKeyFormatHelper.WriteSubjectPublicKeyInfo(parameters);
                ArraySegment<byte> spki = writer.RentAndEncode();

                try
                {
                    ImportSubjectPublicKeyInfo(spki, checkAlgorithm: false, out _);
                }
                finally
                {
                    CryptoPool.Return(spki);
                }
            }
        }

        public override void ImportRSAPublicKey(ReadOnlySpan<byte> source, out int bytesRead)
        {
            ThrowIfDisposed();

            int read;

            try
            {
                AsnDecoder.ReadEncodedValue(
                    source,
                    AsnEncodingRules.BER,
                    out _,
                    out _,
                    out read);
            }
            catch (AsnContentException e)
            {
                throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e);
            }

            AsnWriter writer = RSAKeyFormatHelper.WriteSubjectPublicKeyInfo(source.Slice(0, read));
            ArraySegment<byte> spki = writer.RentAndEncode();

            try
            {
                ImportSubjectPublicKeyInfo(spki, checkAlgorithm: false, out _);
            }
            finally
            {
                CryptoPool.Return(spki);
            }

            bytesRead = read;
        }

        public override void ImportSubjectPublicKeyInfo(
            ReadOnlySpan<byte> source,
            out int bytesRead)
        {
            ThrowIfDisposed();

            ImportSubjectPublicKeyInfo(source, checkAlgorithm: true, out bytesRead);
        }

        private void ImportSubjectPublicKeyInfo(
            ReadOnlySpan<byte> source,
            bool checkAlgorithm,
            out int bytesRead)
        {
            int read;

            if (checkAlgorithm)
            {
                read = RSAKeyFormatHelper.CheckSubjectPublicKeyInfo(source);
            }
            else
            {
                read = source.Length;
            }

            SafeEvpPKeyHandle newKey = Interop.Crypto.DecodeSubjectPublicKeyInfo(
                source.Slice(0, read),
                Interop.Crypto.EvpAlgorithmId.RSA);

            Debug.Assert(!newKey.IsInvalid);
            SetKey(newKey);
            bytesRead = read;
        }

        public override void ImportEncryptedPkcs8PrivateKey(
            ReadOnlySpan<byte> passwordBytes,
            ReadOnlySpan<byte> source,
            out int bytesRead)
        {
            ThrowIfDisposed();
            base.ImportEncryptedPkcs8PrivateKey(passwordBytes, source, out bytesRead);
        }

        public override void ImportEncryptedPkcs8PrivateKey(
            ReadOnlySpan<char> password,
            ReadOnlySpan<byte> source,
            out int bytesRead)
        {
            ThrowIfDisposed();
            base.ImportEncryptedPkcs8PrivateKey(password, source, out bytesRead);
        }

        public override void ImportPkcs8PrivateKey(ReadOnlySpan<byte> source, out int bytesRead)
        {
            ThrowIfDisposed();

            ImportPkcs8PrivateKey(source, checkAlgorithm: true, out bytesRead);
        }

        private void ImportPkcs8PrivateKey(ReadOnlySpan<byte> source, bool checkAlgorithm, out int bytesRead)
        {
            int read;

            if (checkAlgorithm)
            {
                read = RSAKeyFormatHelper.CheckPkcs8(source);
            }
            else
            {
                read = source.Length;
            }

            SafeEvpPKeyHandle newKey = Interop.Crypto.DecodePkcs8PrivateKey(
                source.Slice(0, read),
                Interop.Crypto.EvpAlgorithmId.RSA);

            Debug.Assert(!newKey.IsInvalid);
            SetKey(newKey);
            bytesRead = read;
        }

        public override void ImportRSAPrivateKey(ReadOnlySpan<byte> source, out int bytesRead)
        {
            ThrowIfDisposed();

            int read;

            try
            {
                AsnDecoder.ReadEncodedValue(
                    source,
                    AsnEncodingRules.BER,
                    out _,
                    out _,
                    out read);
            }
            catch (AsnContentException e)
            {
                throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e);
            }

            AsnWriter writer = RSAKeyFormatHelper.WritePkcs8PrivateKey(source.Slice(0, read));
            ArraySegment<byte> pkcs8 = writer.RentAndEncode();

            try
            {
                ImportPkcs8PrivateKey(pkcs8, checkAlgorithm: false, out _);
            }
            finally
            {
                CryptoPool.Return(pkcs8);
            }

            bytesRead = read;
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                FreeKey();
                _key = null;
            }

            base.Dispose(disposing);
        }

        private void FreeKey()
        {
            if (_key != null && _key.IsValueCreated)
            {
                SafeEvpPKeyHandle handle = _key.Value;
                handle?.Dispose();
            }
        }

        [MemberNotNull(nameof(_key))]
        private void SetKey(SafeEvpPKeyHandle newKey)
        {
            Debug.Assert(!newKey.IsInvalid);
            FreeKey();
            _key = new Lazy<SafeEvpPKeyHandle>(newKey);

            // Use ForceSet instead of the property setter to ensure that LegalKeySizes doesn't interfere
            // with the already loaded key.
            ForceSetKeySize(BitsPerByte * Interop.Crypto.EvpPKeySize(newKey));
        }

        private static void ValidateParameters(ref RSAParameters parameters)
        {
            if (parameters.Modulus == null || parameters.Exponent == null)
                throw new CryptographicException(SR.Argument_InvalidValue);

            if (!HasConsistentPrivateKey(ref parameters))
                throw new CryptographicException(SR.Argument_InvalidValue);
        }

        private static bool HasConsistentPrivateKey(ref RSAParameters parameters)
        {
            if (parameters.D == null)
            {
                if (parameters.P != null ||
                    parameters.DP != null ||
                    parameters.Q != null ||
                    parameters.DQ != null ||
                    parameters.InverseQ != null)
                {
                    return false;
                }
            }
            else
            {
                if (parameters.P == null ||
                    parameters.DP == null ||
                    parameters.Q == null ||
                    parameters.DQ == null ||
                    parameters.InverseQ == null)
                {
                    return false;
                }
            }

            return true;
        }

        [MemberNotNull(nameof(_key))]
        private void ThrowIfDisposed()
        {
            ObjectDisposedException.ThrowIf(_key is null, this);
        }

        private SafeEvpPKeyHandle GetKey()
        {
            ThrowIfDisposed();

            SafeEvpPKeyHandle key = _key.Value;

            if (key == null || key.IsInvalid)
            {
                throw new CryptographicException(SR.Cryptography_OpenInvalidHandle);
            }

            return key;
        }

        private SafeEvpPKeyHandle GenerateKey()
        {
            return Interop.Crypto.RsaGenerateKey(KeySize);
        }

        public override byte[] SignHash(byte[] hash, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding)
        {
            ArgumentNullException.ThrowIfNull(hash);
            ArgumentException.ThrowIfNullOrEmpty(hashAlgorithm.Name, nameof(hashAlgorithm));
            ArgumentNullException.ThrowIfNull(padding);

            if (!TrySignHash(
                hash,
                Span<byte>.Empty,
                hashAlgorithm, padding,
                true,
                out _,
                out byte[]? signature))
            {
                Debug.Fail("TrySignHash should not return false in allocation mode");
                throw new CryptographicException();
            }

            Debug.Assert(signature != null);
            return signature;
        }

        public override bool TrySignHash(
            ReadOnlySpan<byte> hash,
            Span<byte> destination,
            HashAlgorithmName hashAlgorithm,
            RSASignaturePadding padding,
            out int bytesWritten)
        {
            ArgumentException.ThrowIfNullOrEmpty(hashAlgorithm.Name, nameof(hashAlgorithm));
            ArgumentNullException.ThrowIfNull(padding);

            bool ret = TrySignHash(
                hash,
                destination,
                hashAlgorithm,
                padding,
                false,
                out bytesWritten,
                out byte[]? alloced);

            Debug.Assert(alloced == null);
            return ret;
        }

        private bool TrySignHash(
            ReadOnlySpan<byte> hash,
            Span<byte> destination,
            HashAlgorithmName hashAlgorithm,
            RSASignaturePadding padding,
            bool allocateSignature,
            out int bytesWritten,
            out byte[]? signature)
        {
            Debug.Assert(!string.IsNullOrEmpty(hashAlgorithm.Name));
            Debug.Assert(padding != null);
            ValidatePadding(padding);

            signature = null;

            IntPtr digestAlgorithm = Interop.Crypto.HashAlgorithmToEvp(hashAlgorithm.Name);
            SafeEvpPKeyHandle key = GetKey();
            int bytesRequired = Interop.Crypto.EvpPKeySize(key);

            if (allocateSignature)
            {
                Debug.Assert(destination.Length == 0);
                signature = new byte[bytesRequired];
                destination = signature;
            }
            else if (destination.Length < bytesRequired)
            {
                bytesWritten = 0;
                return false;
            }

            int written = Interop.Crypto.RsaSignHash(key, padding.Mode, digestAlgorithm, hash, destination);
            Debug.Assert(written == bytesRequired);
            bytesWritten = written;

            return true;
        }

        public override bool VerifyHash(
            byte[] hash,
            byte[] signature,
            HashAlgorithmName hashAlgorithm,
            RSASignaturePadding padding)
        {
            ArgumentNullException.ThrowIfNull(hash);
            ArgumentNullException.ThrowIfNull(signature);

            return VerifyHash(new ReadOnlySpan<byte>(hash), new ReadOnlySpan<byte>(signature), hashAlgorithm, padding);
        }

        public override bool VerifyHash(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> signature, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding)
        {
            ArgumentException.ThrowIfNullOrEmpty(hashAlgorithm.Name, nameof(hashAlgorithm));
            ValidatePadding(padding);

            IntPtr digestAlgorithm = Interop.Crypto.HashAlgorithmToEvp(hashAlgorithm.Name);
            SafeEvpPKeyHandle key = GetKey();

            return Interop.Crypto.RsaVerifyHash(
                key,
                padding.Mode,
                digestAlgorithm,
                hash,
                signature);
        }

        private static ReadOnlyMemory<byte> VerifyPkcs8(ReadOnlyMemory<byte> pkcs8)
        {
            // OpenSSL 1.1.1 will export RSA public keys as a PKCS#8, but this makes a broken structure.
            //
            // So, crack it back open.  If we can walk the payload it's valid, otherwise throw the
            // "there's no private key" exception.

            try
            {
                ReadOnlyMemory<byte> pkcs1Priv = RSAKeyFormatHelper.ReadPkcs8(pkcs8, out int read);
                Debug.Assert(read == pkcs8.Length);
                _ = RSAPrivateKeyAsn.Decode(pkcs1Priv, AsnEncodingRules.BER);
                return pkcs1Priv;
            }
            catch (CryptographicException)
            {
                throw new CryptographicException(SR.Cryptography_CSP_NoPrivateKey);
            }
        }

        private static void ValidatePadding(RSAEncryptionPadding padding)
        {
            ArgumentNullException.ThrowIfNull(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();
            }

            // If the hash algorithm is not supported by the platform, such as SHA3, then we don't support it for
            // RSAOpenSsl, even if OpenSSL itself might support OAEP-SHA3. We use the platform's hashing in some
            // places for RSA, regardless of what is implementing RSA. If RSAOpenSsl were used on macOS, then
            // there would be some incongruence between what hashes OpenSSL supports and what macOS support. Signing
            // for example, always uses the platform's implementation of hashing.
            if (padding.Mode == RSAEncryptionPaddingMode.Oaep &&
                padding.OaepHashAlgorithm.Name is string name &&
                !HashProviderDispenser.HashSupported(name))
            {
                throw new PlatformNotSupportedException();
            }
        }

        private static void ValidatePadding(RSASignaturePadding padding)
        {
            ArgumentNullException.ThrowIfNull(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();
            }
        }

        static partial void ThrowIfNotSupported();

        private static CryptographicException PaddingModeNotSupported() =>
            new CryptographicException(SR.Cryptography_InvalidPaddingMode);
    }
}
