File: System\Security\Cryptography\DSACryptoServiceProvider.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.Diagnostics;
using System.IO;
using System.Runtime.Versioning;

namespace System.Security.Cryptography
{
    public sealed class DSACryptoServiceProvider : DSA, ICspAsymmetricAlgorithm
    {
        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;

        /// <summary>
        /// Initializes a new instance of the DSACryptoServiceProvider class.
        /// </summary>
        [UnsupportedOSPlatform("ios")]
        [UnsupportedOSPlatform("tvos")]
        public DSACryptoServiceProvider()
            : this(
                  new CspParameters(CapiHelper.DefaultDssProviderType,
                      null,
                      null,
                      s_useMachineKeyStore))
        {
        }

        /// <summary>
        /// Initializes a new instance of the DSACryptoServiceProvider class with the specified key size.
        /// </summary>
        /// <param name="dwKeySize">The size of the key for the asymmetric algorithm in bits.</param>
        [UnsupportedOSPlatform("ios")]
        [UnsupportedOSPlatform("tvos")]
        public DSACryptoServiceProvider(int dwKeySize)
            : this(dwKeySize,
                  new CspParameters(CapiHelper.DefaultDssProviderType,
                      null,
                      null,
                      s_useMachineKeyStore))
        {
        }

        /// <summary>
        /// Initializes a new instance of the DSACryptoServiceProvider class with the specified parameters
        /// for the cryptographic service provider (CSP).
        /// </summary>
        /// <param name="parameters">The parameters for the CSP.</param>
        [SupportedOSPlatform("windows")]
        public DSACryptoServiceProvider(CspParameters? parameters)
            : this(0, parameters)
        {
        }

        /// <summary>
        /// Initializes a new instance of the DSACryptoServiceProvider class with the specified key size and parameters
        /// for the cryptographic service provider (CSP).
        /// </summary>
        /// <param name="dwKeySize">The size of the key for the cryptographic algorithm in bits.</param>
        /// <param name="parameters">The parameters for the CSP.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA5350", Justification = "SHA1 is required by the FIPS 186-2 DSA spec.")]
        [SupportedOSPlatform("windows")]
        public DSACryptoServiceProvider(int dwKeySize, CspParameters? parameters)
        {
            ArgumentOutOfRangeException.ThrowIfNegative(dwKeySize);

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

            _keySize = dwKeySize;

            // 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.Dss,
                                _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>
        /// Gets a CspKeyContainerInfo object that describes additional information about a cryptographic key pair.
        /// </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);
            }
        }

        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(512, 1024, 64) }; // per FIPS 186-2
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the key should be persisted in the cryptographic
        /// service provider (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 a value that indicates whether the DSACryptoServiceProvider object contains
        /// only a public key.
        /// </summary>
        public bool PublicOnly
        {
            get
            {
                byte[] publicKey = CapiHelper.GetKeyParameter(SafeKeyHandle, CapiHelper.ClrPropertyId.CLR_PUBLICKEYONLY);
                return (publicKey[0] == 1);
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the key should be persisted in the computer's
        /// key store instead of the user profile store.
        /// </summary>
        public static bool UseMachineKeyStore
        {
            get
            {
                return (s_useMachineKeyStore == CspProviderFlags.UseMachineKeyStore);
            }
            set
            {
                s_useMachineKeyStore = (value ? CspProviderFlags.UseMachineKeyStore : 0);
            }
        }

        public override string? KeyExchangeAlgorithm => null;
        public override string SignatureAlgorithm => "http://www.w3.org/2000/09/xmldsig#dsa-sha1";

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (_safeKeyHandle != null && !_safeKeyHandle.IsClosed)
                {
                    _safeKeyHandle.Dispose();
                }

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

                _disposed = true;
            }

            base.Dispose(disposing);
        }

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

        public override DSAParameters ExportParameters(bool includePrivateParameters)
        {
            byte[] cspBlob = ExportCspBlob(includePrivateParameters);
            byte[]? cspPublicBlob = null;

            if (includePrivateParameters)
            {
                byte bVersion = CapiHelper.GetKeyBlobHeaderVersion(cspBlob);
                if (bVersion <= 2)
                {
                    // Since DSSPUBKEY is used for either public or private key, we got X
                    // but not Y. To get Y, do another export and ask for public key blob.
                    cspPublicBlob = ExportCspBlob(false);
                }
            }

            return cspBlob.ToDSAParameters(includePrivateParameters, cspPublicBlob);
        }

        /// <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.DefaultDssProviderType), out safeProvHandle);
            return safeProvHandle;
        }

        /// <summary>
        /// Imports a blob that represents DSA key information.
        /// </summary>
        /// <param name="keyBlob">A byte array that represents a DSA key blob.</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;
        }

        public override void ImportParameters(DSAParameters 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 the specified input stream and signs the resulting hash value.
        /// </summary>
        /// <param name="inputStream">The input data for which to compute the hash.</param>
        /// <returns>The DSA signature for the specified data.</returns>
        public byte[] SignData(Stream inputStream)
        {
            byte[] hashVal = SHA1.HashData(inputStream);
            return SignHash(hashVal, null);
        }

        /// <summary>
        /// Computes the hash value of the specified input stream and signs the resulting hash value.
        /// </summary>
        /// <param name="buffer">The input data for which to compute the hash.</param>
        /// <returns>The DSA signature for the specified data.</returns>
        public byte[] SignData(byte[] buffer)
        {
            byte[] hashVal = SHA1.HashData(buffer);
            return SignHash(hashVal, null);
        }

        /// <summary>
        /// Signs a byte array from the specified start point to the specified end point.
        /// </summary>
        /// <param name="buffer">The input data to sign.</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>
        /// <returns>The DSA signature for the specified data.</returns>
        public byte[] SignData(byte[] buffer, int offset, int count)
        {
            byte[] hashVal = SHA1.HashData(new ReadOnlySpan<byte>(buffer, offset, count));
            return SignHash(hashVal, null);
        }

        /// <summary>
        /// Verifies the specified signature data by comparing it to the signature computed for the specified data.
        /// </summary>
        /// <param name="rgbData">The data that was signed.</param>
        /// <param name="rgbSignature">The signature data to be verified.</param>
        /// <returns>true if the signature verifies as valid; otherwise, false.</returns>
        public bool VerifyData(byte[] rgbData, byte[] rgbSignature)
        {
            byte[] hashVal = SHA1.HashData(rgbData);
            return VerifyHash(hashVal, null, rgbSignature);
        }

        /// <summary>
        /// Creates the DSA signature for the specified data.
        /// </summary>
        /// <param name="rgbHash">The data to be signed.</param>
        /// <returns>The digital signature for the specified data.</returns>
        public override byte[] CreateSignature(byte[] rgbHash)
        {
            return SignHash(rgbHash, null);
        }

        public override bool VerifySignature(byte[] rgbHash, byte[] rgbSignature)
        {
            return VerifyHash(rgbHash, null, rgbSignature);
        }

        protected override byte[] HashData(byte[] data, int offset, int count, HashAlgorithmName hashAlgorithm)
        {
            // we're sealed and the base should have checked this before calling us
            Debug.Assert(data != null);
            Debug.Assert(offset >= 0 && offset <= data.Length);
            Debug.Assert(count >= 0 && count <= data.Length - offset);
            Debug.Assert(!string.IsNullOrEmpty(hashAlgorithm.Name));

            if (hashAlgorithm != HashAlgorithmName.SHA1)
            {
                throw new CryptographicException(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithm.Name);
            }

            return SHA1.HashData(new ReadOnlySpan<byte>(data, offset, count));
        }

        protected override byte[] HashData(Stream data, HashAlgorithmName hashAlgorithm)
        {
            // we're sealed and the base should have checked this before calling us
            Debug.Assert(data != null);
            Debug.Assert(!string.IsNullOrEmpty(hashAlgorithm.Name));

            if (hashAlgorithm != HashAlgorithmName.SHA1)
            {
                throw new CryptographicException(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithm.Name);
            }

            return SHA1.HashData(data);
        }

        /// <summary>
        /// Computes the signature for the specified hash value by encrypting it with the private key.
        /// </summary>
        /// <param name="rgbHash">The hash value of the data to be signed.</param>
        /// <param name="str">The name of the hash algorithm used to create the hash value of the data.</param>
        /// <returns>The DSA signature for the specified hash value.</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);

            if (rgbHash.Length != SHA1.HashSizeInBytes)
                throw new CryptographicException(SR.Format(SR.Cryptography_InvalidHashSize, "SHA1", SHA1.HashSizeInBytes));

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

        /// <summary>
        /// Verifies the specified signature data by comparing it to the signature computed for the specified hash value.
        /// </summary>
        /// <param name="rgbHash">The hash value of the data to be signed.</param>
        /// <param name="str">The name of the hash algorithm used to create the hash value of the data.</param>
        /// <param name="rgbSignature">The signature data to be verified.</param>
        /// <returns>true if the signature verifies as valid; otherwise, false.</returns>
        public bool VerifyHash(byte[] rgbHash, string? str, byte[] rgbSignature)
        {
            ArgumentNullException.ThrowIfNull(rgbHash);
            ArgumentNullException.ThrowIfNull(rgbSignature);

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

            return CapiHelper.VerifySign(
                SafeProvHandle,
                SafeKeyHandle,
                CapiHelper.CALG_DSS_SIGN,
                calgHash,
                rgbHash,
                rgbSignature);
        }

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

            // The CAPI DSS public key representation consists of the following sequence:
            //  - BLOBHEADER (the first byte is bType)
            //  - DSSPUBKEY or DSSPUBKEY_VER3 (the first field is the magic field)

            // The first byte should be PUBLICKEYBLOB
            if (keyBlob[0] != CapiHelper.PUBLICKEYBLOB)
            {
                return false;
            }

            // Magic should be DSS_MAGIC or DSS_PUB_MAGIC_VER3
            if ((keyBlob[11] != 0x31 && keyBlob[11] != 0x33) || keyBlob[10] != 0x53 || keyBlob[9] != 0x53 || keyBlob[8] != 0x44)
            {
                return false;
            }

            return true;
        }
    }
}