File: System\Security\Cryptography\RSACryptoServiceProvider.Windows.cs
Web Access
Project: src\src\runtime\src\libraries\System.Security.Cryptography\src\System.Security.Cryptography.csproj (System.Security.Cryptography)
// 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.Binary;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Runtime.Versioning;
using Internal.Cryptography;

namespace System.Security.Cryptography
{
    public sealed partial class RSACryptoServiceProvider : RSA, ICspAsymmetricAlgorithm, IRuntimeAlgorithm
    {
        private int _keySize;
        private readonly CspParameters _parameters;
        private readonly bool _randomKeyContainer;
        private SafeCapiKeyHandle? _safeKeyHandle;
        private SafeProvHandle? _safeProvHandle;
        private static volatile CspProviderFlags s_useMachineKeyStore;
        private bool _disposed;

        [UnsupportedOSPlatform("browser")]
        public RSACryptoServiceProvider()
            : this(0, new CspParameters(CapiHelper.DefaultRsaProviderType,
                                       null,
                                       null,
                                       s_useMachineKeyStore),
                                       true)
        {
        }

        [UnsupportedOSPlatform("browser")]
        public RSACryptoServiceProvider(int dwKeySize)
            : this(dwKeySize,
                  new CspParameters(CapiHelper.DefaultRsaProviderType,
                  null,
                  null,
                  s_useMachineKeyStore), false)
        {
        }

        [SupportedOSPlatform("windows")]
        public RSACryptoServiceProvider(int dwKeySize, CspParameters? parameters)
            : this(dwKeySize, parameters, false)
        {
        }

        [SupportedOSPlatform("windows")]
        public RSACryptoServiceProvider(CspParameters? parameters)
            : this(0, parameters, true)
        {
        }

        private RSACryptoServiceProvider(int keySize, CspParameters? parameters, bool useDefaultKeySize)
        {
            ArgumentOutOfRangeException.ThrowIfNegative(keySize);

            _parameters = CapiHelper.SaveCspParameters(
                CapiHelper.CspAlgorithmType.Rsa,
                parameters,
                s_useMachineKeyStore,
                out _randomKeyContainer);

            _keySize = useDefaultKeySize ? 1024 : keySize;

            // If this is not a random container we generate, create it eagerly
            // in the constructor so we can report any errors now.
            if (!_randomKeyContainer)
            {
                // Force-read the SafeKeyHandle property, which will summon it into existence.
                SafeCapiKeyHandle localHandle = SafeKeyHandle;
                Debug.Assert(localHandle != null);
            }
        }

        private SafeProvHandle SafeProvHandle
        {
            get
            {
                if (_safeProvHandle == null)
                {
                    lock (_parameters)
                    {
                        if (_safeProvHandle == null)
                        {
                            SafeProvHandle hProv = CapiHelper.CreateProvHandle(_parameters, _randomKeyContainer);

                            Debug.Assert(hProv != null);
                            Debug.Assert(!hProv.IsInvalid);
                            Debug.Assert(!hProv.IsClosed);

                            _safeProvHandle = hProv;
                        }
                    }

                    return _safeProvHandle;
                }

                return _safeProvHandle;
            }
            set
            {
                lock (_parameters)
                {
                    SafeProvHandle? current = _safeProvHandle;

                    if (ReferenceEquals(value, current))
                    {
                        return;
                    }

                    if (current != null)
                    {
                        SafeCapiKeyHandle? keyHandle = _safeKeyHandle;
                        _safeKeyHandle = null;
                        keyHandle?.Dispose();
                        current.Dispose();
                    }

                    _safeProvHandle = value;
                }
            }
        }

        private SafeCapiKeyHandle SafeKeyHandle
        {
            get
            {
                if (_safeKeyHandle == null)
                {
                    lock (_parameters)
                    {
                        if (_safeKeyHandle == null)
                        {
                            SafeCapiKeyHandle hKey = CapiHelper.GetKeyPairHelper(
                                CapiHelper.CspAlgorithmType.Rsa,
                                _parameters,
                                _keySize,
                                SafeProvHandle);

                            Debug.Assert(hKey != null);
                            Debug.Assert(!hKey.IsInvalid);
                            Debug.Assert(!hKey.IsClosed);

                            _safeKeyHandle = hKey;
                        }
                    }
                }

                return _safeKeyHandle;
            }

            set
            {
                lock (_parameters)
                {
                    SafeCapiKeyHandle? current = _safeKeyHandle;

                    if (ReferenceEquals(value, current))
                    {
                        return;
                    }

                    _safeKeyHandle = value;
                    current?.Dispose();
                }
            }
        }

        /// <summary>
        /// CspKeyContainerInfo property
        /// </summary>
        [SupportedOSPlatform("windows")]
        public CspKeyContainerInfo CspKeyContainerInfo
        {
            get
            {
                // .NET Framework compat: Read the SafeKeyHandle property to force the key to load,
                // because it might throw here.
                SafeCapiKeyHandle localHandle = SafeKeyHandle;
                Debug.Assert(localHandle != null);

                return new CspKeyContainerInfo(_parameters, _randomKeyContainer);
            }
        }

        /// <summary>
        /// _keySize property
        /// </summary>
        public override int KeySize
        {
            get
            {
                byte[] keySize = CapiHelper.GetKeyParameter(SafeKeyHandle, CapiHelper.ClrPropertyId.CLR_KEYLEN);
                _keySize = BinaryPrimitives.ReadInt32LittleEndian(keySize);
                return _keySize;
            }
        }

        public override KeySizes[] LegalKeySizes
        {
            get
            {
                return new[] { new KeySizes(384, 16384, 8) };
            }
        }

        /// <summary>
        /// get set Persisted key in CSP
        /// </summary>
        public bool PersistKeyInCsp
        {
            get
            {
                return CapiHelper.GetPersistKeyInCsp(SafeProvHandle);
            }
            set
            {
                bool oldPersistKeyInCsp = this.PersistKeyInCsp;
                if (value == oldPersistKeyInCsp)
                {
                    return; // Do nothing
                }
                CapiHelper.SetPersistKeyInCsp(SafeProvHandle, value);
            }
        }

        /// <summary>
        /// Gets the information of key if it is a public key
        /// </summary>
        public bool PublicOnly
        {
            get
            {
                byte[] publicKey = CapiHelper.GetKeyParameter(SafeKeyHandle, CapiHelper.ClrPropertyId.CLR_PUBLICKEYONLY);
                return (publicKey[0] == 1);
            }
        }

        /// <summary>
        /// MachineKey store properties
        /// </summary>
        public static bool UseMachineKeyStore
        {
            get
            {
                return (s_useMachineKeyStore == CspProviderFlags.UseMachineKeyStore);
            }
            set
            {
                s_useMachineKeyStore = (value ? CspProviderFlags.UseMachineKeyStore : 0);
            }
        }

        /// <summary>
        ///     Decrypt raw data, generally used for decrypting symmetric key material
        /// </summary>
        /// <param name="rgb">encrypted data</param>
        /// <param name="fOAEP">true to use OAEP padding (PKCS #1 v2), false to use PKCS #1 type 2 padding</param>
        /// <returns>decrypted data</returns>
        [Obsolete(Obsoletions.RSACspEncryptDecryptMessage, DiagnosticId = Obsoletions.RSACspEncryptDecryptDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
        public byte[] Decrypt(byte[] rgb, bool fOAEP)
        {
            ArgumentNullException.ThrowIfNull(rgb);
            return Decrypt(rgb, fOAEP ? RSAEncryptionPadding.OaepSHA1 : RSAEncryptionPadding.Pkcs1);
        }

        /// <summary>
        /// This method is not supported. Use Decrypt(byte[], RSAEncryptionPadding) instead.
        /// </summary>
        [Obsolete(Obsoletions.RsaEncryptDecryptValueMessage, DiagnosticId = Obsoletions.RsaEncryptDecryptDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public override byte[] DecryptValue(byte[] rgb) => base.DecryptValue(rgb);

        /// <summary>
        /// Dispose the key handles
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (_safeKeyHandle != null && !_safeKeyHandle.IsClosed)
                {
                    _safeKeyHandle.Dispose();
                }

                if (_safeProvHandle != null && !_safeProvHandle.IsClosed)
                {
                    _safeProvHandle.Dispose();
                }

                _disposed = true;
            }
        }

        /// <summary>
        ///     Encrypt raw data, generally used for encrypting symmetric key material.
        /// </summary>
        /// <remarks>
        ///     This method can only encrypt (keySize - 88 bits) of data, so should not be used for encrypting
        ///     arbitrary byte arrays. Instead, encrypt a symmetric key with this method, and use the symmetric
        ///     key to encrypt the sensitive data.
        /// </remarks>
        /// <param name="rgb">raw data to encrypt</param>
        /// <param name="fOAEP">true to use OAEP padding (PKCS #1 v2), false to use PKCS #1 type 2 padding</param>
        /// <returns>Encrypted key</returns>
        [Obsolete(Obsoletions.RSACspEncryptDecryptMessage, DiagnosticId = Obsoletions.RSACspEncryptDecryptDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
        public byte[] Encrypt(byte[] rgb, bool fOAEP)
        {
            ArgumentNullException.ThrowIfNull(rgb);
            return Encrypt(rgb, fOAEP ? RSAEncryptionPadding.OaepSHA1 : RSAEncryptionPadding.Pkcs1);
        }

        /// <summary>
        /// This method is not supported. Use Encrypt(byte[], RSAEncryptionPadding) instead.
        /// </summary>
        [Obsolete(Obsoletions.RsaEncryptDecryptValueMessage, DiagnosticId = Obsoletions.RsaEncryptDecryptDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public override byte[] EncryptValue(byte[] rgb) => base.EncryptValue(rgb);

        /// <summary>
        ///Exports a blob containing the key information associated with an RSACryptoServiceProvider object.
        /// </summary>
        public byte[] ExportCspBlob(bool includePrivateParameters)
        {
            return CapiHelper.ExportKeyBlob(includePrivateParameters, SafeKeyHandle);
        }

        /// <summary>
        /// Exports the RSAParameters
        /// </summary>
        public override RSAParameters ExportParameters(bool includePrivateParameters)
        {
            byte[] cspBlob = ExportCspBlob(includePrivateParameters);
            return cspBlob.ToRSAParameters(includePrivateParameters);
        }

        /// <summary>
        /// This method helps Acquire the default CSP and avoids the need for static SafeProvHandle
        /// in CapiHelper class
        /// </summary>
        private static SafeProvHandle AcquireSafeProviderHandle()
        {
            SafeProvHandle safeProvHandle;
            CapiHelper.AcquireCsp(new CspParameters(CapiHelper.DefaultRsaProviderType), out safeProvHandle);
            return safeProvHandle;
        }

        /// <summary>
        /// Imports a blob that represents RSA key information
        /// </summary>
        /// <param name="keyBlob"></param>
        public void ImportCspBlob(byte[] keyBlob)
        {
            ObjectDisposedException.ThrowIf(_disposed, this);
            SafeCapiKeyHandle safeKeyHandle;

            if (IsPublic(keyBlob))
            {
                SafeProvHandle safeProvHandleTemp = AcquireSafeProviderHandle();
                CapiHelper.ImportKeyBlob(safeProvHandleTemp, (CspProviderFlags)0, false, keyBlob, out safeKeyHandle);

                // The property set will take care of releasing any already-existing resources.
                SafeProvHandle = safeProvHandleTemp;
            }
            else
            {
                CapiHelper.ImportKeyBlob(SafeProvHandle, _parameters.Flags, false, keyBlob, out safeKeyHandle);
            }

            // The property set will take care of releasing any already-existing resources.
            SafeKeyHandle = safeKeyHandle;
        }

        /// <summary>
        /// Imports the specified RSAParameters
        /// </summary>
        public override void ImportParameters(RSAParameters parameters)
        {
            byte[] keyBlob = parameters.ToKeyBlob();
            ImportCspBlob(keyBlob);
        }

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

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

        /// <summary>
        /// Computes the hash value of a subset of the specified byte array using the
        /// specified hash algorithm, and signs the resulting hash value.
        /// </summary>
        /// <param name="buffer">The input data for which to compute the hash</param>
        /// <param name="offset">The offset into the array from which to begin using data</param>
        /// <param name="count">The number of bytes in the array to use as data. </param>
        /// <param name="halg">The hash algorithm to use to create the hash value. </param>
        /// <returns>The RSA signature for the specified data.</returns>
        public byte[] SignData(byte[] buffer, int offset, int count, object halg)
        {
            int calgHash = CapiHelper.ObjToHashAlgId(halg);
            HashAlgorithmName hashAlgorithmName = CapiHelper.AlgIdToHashAlgorithmName(calgHash);
            byte[] hashVal = CryptographicOperations.HashData(hashAlgorithmName, new ReadOnlySpan<byte>(buffer, offset, count));
            return SignHash(hashVal, calgHash);
        }

        /// <summary>
        /// Computes the hash value of a subset of the specified byte array using the
        /// specified hash algorithm, and signs the resulting hash value.
        /// </summary>
        /// <param name="buffer">The input data for which to compute the hash</param>
        /// <param name="halg">The hash algorithm to use to create the hash value. </param>
        /// <returns>The RSA signature for the specified data.</returns>
        public byte[] SignData(byte[] buffer, object halg)
        {
            int calgHash = CapiHelper.ObjToHashAlgId(halg);
            HashAlgorithmName hashAlgorithmName = CapiHelper.AlgIdToHashAlgorithmName(calgHash);
            byte[] hashVal = CryptographicOperations.HashData(hashAlgorithmName, buffer);
            return SignHash(hashVal, calgHash);
        }

        /// <summary>
        /// Computes the hash value of a subset of the specified byte array using the
        /// specified hash algorithm, and signs the resulting hash value.
        /// </summary>
        /// <param name="inputStream">The input data for which to compute the hash</param>
        /// <param name="halg">The hash algorithm to use to create the hash value. </param>
        /// <returns>The RSA signature for the specified data.</returns>
        public byte[] SignData(Stream inputStream, object halg)
        {
            int calgHash = CapiHelper.ObjToHashAlgId(halg);
            HashAlgorithmName hashAlgorithmName = CapiHelper.AlgIdToHashAlgorithmName(calgHash);
            byte[] hashVal = CryptographicOperations.HashData(hashAlgorithmName, inputStream);
            return SignHash(hashVal, calgHash);
        }

        /// <summary>
        /// Computes the hash value of a subset of the specified byte array using the
        /// specified hash algorithm, and signs the resulting hash value.
        /// </summary>
        /// <param name="rgbHash">The input data for which to compute the hash</param>
        /// <param name="str">The hash algorithm to use to create the hash value. </param>
        /// <returns>The RSA signature for the specified data.</returns>
        public byte[] SignHash(byte[] rgbHash, string? str)
        {
            ArgumentNullException.ThrowIfNull(rgbHash);

            if (PublicOnly)
                throw new CryptographicException(SR.Cryptography_CSP_NoPrivateKey);

            int calgHash = CapiHelper.NameOrOidToHashAlgId(str, OidGroup.HashAlgorithm);

            return SignHash(rgbHash, calgHash);
        }

        /// <summary>
        /// Computes the hash value of a subset of the specified byte array using the
        /// specified hash algorithm, and signs the resulting hash value.
        /// </summary>
        /// <param name="rgbHash">The input data for which to compute the hash</param>
        /// <param name="calgHash">The hash algorithm to use to create the hash value. </param>
        /// <returns>The RSA signature for the specified data.</returns>
        private byte[] SignHash(byte[] rgbHash, int calgHash)
        {
            Debug.Assert(rgbHash != null);

            // Read the SafeKeyHandle property to force the key to load
            SafeCapiKeyHandle localHandle = SafeKeyHandle;
            Debug.Assert(localHandle != null);

            return CapiHelper.SignValue(
                SafeProvHandle,
                _parameters.KeyNumber,
                CapiHelper.CALG_RSA_SIGN,
                calgHash,
                rgbHash);
        }

        /// <summary>
        /// Verifies the signature of a hash value.
        /// </summary>
        public bool VerifyData(byte[] buffer, object halg, byte[] signature)
        {
            int calgHash = CapiHelper.ObjToHashAlgId(halg);
            HashAlgorithmName hashAlgorithmName = CapiHelper.AlgIdToHashAlgorithmName(calgHash);
            byte[] hashVal = CryptographicOperations.HashData(hashAlgorithmName, buffer);
            return VerifyHash(hashVal, calgHash, signature);
        }

        /// <summary>
        /// Verifies the signature of a hash value.
        /// </summary>
        public bool VerifyHash(byte[] rgbHash, string str, byte[] rgbSignature)
        {
            ArgumentNullException.ThrowIfNull(rgbHash);
            ArgumentNullException.ThrowIfNull(rgbSignature);

            int calgHash = CapiHelper.NameOrOidToHashAlgId(str, OidGroup.HashAlgorithm);
            return VerifyHash(rgbHash, calgHash, rgbSignature);
        }

        /// <summary>
        /// Verifies the signature of a hash value.
        /// </summary>
        private bool VerifyHash(byte[] rgbHash, int calgHash, byte[] rgbSignature)
        {
            return CapiHelper.VerifySign(
                SafeProvHandle,
                SafeKeyHandle,
                CapiHelper.CALG_RSA_SIGN,
                calgHash,
                rgbHash,
                rgbSignature);
        }

        /// <summary>
        /// find whether an RSA key blob is public.
        /// </summary>
        private static bool IsPublic(byte[] keyBlob)
        {
            ArgumentNullException.ThrowIfNull(keyBlob);

            // The CAPI RSA public key representation consists of the following sequence:
            //  - BLOBHEADER
            //  - RSAPUBKEY

            // The first should be PUBLICKEYBLOB and magic should be RSA_PUB_MAGIC "RSA1"
            if (keyBlob[0] != CapiHelper.PUBLICKEYBLOB)
            {
                return false;
            }
            if (keyBlob[11] != 0x31 || keyBlob[10] != 0x41 || keyBlob[9] != 0x53 || keyBlob[8] != 0x52)
            {
                return false;
            }
            return true;
        }

        private static int GetAlgorithmId(HashAlgorithmName hashAlgorithm) =>
            hashAlgorithm.Name switch
            {
                "MD5" => CapiHelper.CALG_MD5,
                "SHA1" => CapiHelper.CALG_SHA1,
                "SHA256" => CapiHelper.CALG_SHA_256,
                "SHA384" => CapiHelper.CALG_SHA_384,
                "SHA512" => CapiHelper.CALG_SHA_512,
                _ => throw new CryptographicException(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithm.Name),
            };

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

            bool fOAEP;

            if (padding == RSAEncryptionPadding.Pkcs1)
            {
                fOAEP = false;
            }
            else if (padding == RSAEncryptionPadding.OaepSHA1)
            {
                fOAEP = true;

                int rsaSize = GetMaxOutputSize();
                const int OaepSha1Overhead = 20 + 20 + 2;

                // Normalize the Windows 7 and Windows 8.1+ exception
                if (rsaSize - OaepSha1Overhead < data.Length)
                {
                    const int NTE_BAD_LENGTH = unchecked((int)0x80090004);
                    throw NTE_BAD_LENGTH.ToCryptographicException();
                }
            }
            else
            {
                throw PaddingModeNotSupported();
            }

            byte[]? encryptedKey = null;
            CapiHelper.EncryptKey(SafeKeyHandle, data, data.Length, fOAEP, ref encryptedKey);
            return encryptedKey;
        }

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

            bool fOAEP;

            if (padding == RSAEncryptionPadding.Pkcs1)
            {
                fOAEP = false;
            }
            else if (padding == RSAEncryptionPadding.OaepSHA1)
            {
                fOAEP = true;
            }
            else
            {
                throw PaddingModeNotSupported();
            }

            // Save the KeySize value to a local because it has non-trivial cost.
            int keySize = KeySize;

            // size check -- must be exactly the modulus size
            if (data.Length != (keySize / 8))
            {
                throw new CryptographicException(SR.Cryptography_RSA_DecryptWrongSize);
            }

            byte[] decryptedKey;
            CapiHelper.DecryptKey(SafeKeyHandle, data, data.Length, fOAEP, out decryptedKey);
            return decryptedKey;
        }

        public override byte[] SignHash(
            byte[] hash,
            HashAlgorithmName hashAlgorithm,
            RSASignaturePadding padding)
        {
            ArgumentNullException.ThrowIfNull(hash);
            ArgumentException.ThrowIfNullOrEmpty(hashAlgorithm.Name, nameof(hashAlgorithm));
            ArgumentNullException.ThrowIfNull(padding);
            if (padding != RSASignaturePadding.Pkcs1)
                throw PaddingModeNotSupported();

            return SignHash(hash, GetAlgorithmId(hashAlgorithm));
        }

        public override bool VerifyHash(
            byte[] hash,
            byte[] signature,
            HashAlgorithmName hashAlgorithm,
            RSASignaturePadding padding)
        {
            ArgumentNullException.ThrowIfNull(hash);
            ArgumentNullException.ThrowIfNull(signature);
            ArgumentException.ThrowIfNullOrEmpty(hashAlgorithm.Name, nameof(hashAlgorithm));
            ArgumentNullException.ThrowIfNull(padding);
            if (padding != RSASignaturePadding.Pkcs1)
                throw PaddingModeNotSupported();

            return VerifyHash(hash, GetAlgorithmId(hashAlgorithm), signature);
        }

        public override string? KeyExchangeAlgorithm
        {
            get
            {
                if (_parameters.KeyNumber == (int)Interop.Advapi32.KeySpec.AT_KEYEXCHANGE)
                {
                    return "RSA-PKCS1-KeyEx";
                }
                return null;
            }
        }

        public override string SignatureAlgorithm
        {
            get
            {
                return "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
            }
        }

        private static CryptographicException PaddingModeNotSupported()
        {
            return new CryptographicException(SR.Cryptography_InvalidPaddingMode);
        }
    }
}