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

namespace System.Security.Cryptography
{
    public sealed partial class ECDsaCng : ECDsa
    {
        private CngAlgorithmCore _core = new CngAlgorithmCore(typeof(ECDsaCng));
        private CngAlgorithm _hashAlgorithm = CngAlgorithm.Sha256;

        /// <summary>
        ///     Hash algorithm to use when generating a signature over arbitrary data
        /// </summary>
        public CngAlgorithm HashAlgorithm
        {
            get
            {
                return _hashAlgorithm;
            }
            set
            {
                ArgumentNullException.ThrowIfNull(value);
                _hashAlgorithm = value;
            }
        }

        /// <summary>
        ///     Creates a new ECDsaCng object that will use the specified key. The key's
        ///     <see cref="CngKey.AlgorithmGroup" /> must be ECDsa. This constructor
        ///     creates a copy of the key. Hence, the caller can safely dispose of the
        ///     passed in key and continue using the ECDsaCng object.
        /// </summary>
        /// <param name="key">Key to use for ECDsa operations</param>
        /// <exception cref="ArgumentException">if <paramref name="key" /> is not an ECDsa key</exception>
        /// <exception cref="ArgumentNullException">if <paramref name="key" /> is null.</exception>
        [SupportedOSPlatform("windows")]
        public ECDsaCng(CngKey key)
        {
            ArgumentNullException.ThrowIfNull(key);

            if (!IsEccAlgorithmGroup(key.AlgorithmGroup))
                throw new ArgumentException(SR.Cryptography_ArgECDsaRequiresECDsaKey, nameof(key));

            Key = CngAlgorithmCore.Duplicate(key);
        }

        /// <summary>
        ///     Creates a new ECDsaCng object that will use the specified key. Unlike the public
        ///     constructor, this does not copy the key and ownership is transferred. The
        ///     <paramref name="transferOwnership"/> parameter must be true.
        /// </summary>
        /// <param name="key">Key to use for ECDsa operations</param>
        /// <param name="transferOwnership">
        /// Must be true. Signals that ownership of <paramref name="key"/> will be transferred to the new instance.
        /// </param>
        [SupportedOSPlatform("windows")]
        internal ECDsaCng(CngKey key, bool transferOwnership)
        {
            Debug.Assert(key is not null);
            Debug.Assert(IsEccAlgorithmGroup(key.AlgorithmGroup));
            Debug.Assert(transferOwnership);

            Key = key;
        }

        protected override void Dispose(bool disposing)
        {
            _core.Dispose();
        }

        private void ThrowIfDisposed()
        {
            _core.ThrowIfDisposed();
        }

        private void DisposeKey()
        {
            _core.DisposeKey();
        }

        private static bool IsEccAlgorithmGroup(CngAlgorithmGroup? algorithmGroup)
        {
            // Sometimes, when reading from certificates, ECDSA keys get identified as ECDH.
            // Windows allows the ECDH keys to perform both key exchange (ECDH) and signing (ECDSA),
            // so either value is acceptable for the ECDSA wrapper object.
            //
            // It is worth noting, however, that ECDSA-identified keys cannot be used for key exchange (ECDH) in CNG.
            return algorithmGroup == CngAlgorithmGroup.ECDsa || algorithmGroup == CngAlgorithmGroup.ECDiffieHellman;
        }

        internal string? GetCurveName(out string? oidValue)
        {
            return Key.GetCurveName(out oidValue);
        }

        private void ImportFullKeyBlob(byte[] ecfullKeyBlob, bool includePrivateParameters)
        {
            CngKey key = ECCng.ImportFullKeyBlob(ecfullKeyBlob, includePrivateParameters);
            try
            {
                Key = key;
            }
            catch
            {
                key.Dispose();
                throw;
            }
        }

        private void ImportKeyBlob(byte[] ecfullKeyBlob, string curveName, bool includePrivateParameters)
        {
            CngKey key = ECCng.ImportKeyBlob(ecfullKeyBlob, curveName, includePrivateParameters);
            try
            {
                Key = key;
            }
            catch
            {
                key.Dispose();
                throw;
            }
        }

        private byte[] ExportKeyBlob(bool includePrivateParameters)
        {
            return ECCng.ExportKeyBlob(Key, includePrivateParameters);
        }

        private byte[] ExportFullKeyBlob(bool includePrivateParameters)
        {
            return ECCng.ExportFullKeyBlob(Key, includePrivateParameters);
        }

        private void AcceptImport(CngPkcs8.Pkcs8Response response)
        {
            try
            {
                Key = response.Key;
            }
            catch
            {
                response.FreeKey();
                throw;
            }
        }

        public override bool TryExportPkcs8PrivateKey(Span<byte> destination, out int bytesWritten)
        {
            bool encryptedOnlyExport = CngPkcs8.AllowsOnlyEncryptedExport(Key);

            if (encryptedOnlyExport)
            {
                const string TemporaryExportPassword = "DotnetExportPhrase";
                byte[] exported = ExportEncryptedPkcs8(TemporaryExportPassword, 1);
                EccKeyFormatHelper.ReadEncryptedPkcs8(
                    exported,
                    TemporaryExportPassword,
                    out _,
                    out ECParameters ecParameters);
                return EccKeyFormatHelper.WritePkcs8PrivateKey(ecParameters).TryEncode(destination, out bytesWritten);
            }

            return Key.TryExportKeyBlob(
                Interop.NCrypt.NCRYPT_PKCS8_PRIVATE_KEY_BLOB,
                destination,
                out bytesWritten);
        }

        private byte[] ExportEncryptedPkcs8(ReadOnlySpan<char> pkcs8Password, int kdfCount)
        {
            return Key.ExportPkcs8KeyBlob(pkcs8Password, kdfCount);
        }

        private bool TryExportEncryptedPkcs8(
            ReadOnlySpan<char> pkcs8Password,
            int kdfCount,
            Span<byte> destination,
            out int bytesWritten)
        {
            return Key.TryExportPkcs8KeyBlob(
                pkcs8Password,
                kdfCount,
                destination,
                out bytesWritten);
        }

        [Obsolete(Obsoletions.EccXmlExportImportMessage, DiagnosticId = Obsoletions.EccXmlExportImportDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
        public void FromXmlString(string xml, ECKeyXmlFormat format)
            => throw new PlatformNotSupportedException();

        public byte[] SignData(byte[] data)
            => SignData(data, new HashAlgorithmName(HashAlgorithm.Algorithm));

        public byte[] SignData(byte[] data, int offset, int count) =>
            SignData(data, offset, count, new HashAlgorithmName(HashAlgorithm.Algorithm));

        public byte[] SignData(Stream data)
            => SignData(data, new HashAlgorithmName(HashAlgorithm.Algorithm));

        [Obsolete(Obsoletions.EccXmlExportImportMessage, DiagnosticId = Obsoletions.EccXmlExportImportDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
        public string ToXmlString(ECKeyXmlFormat format)
            => throw new PlatformNotSupportedException();

        public bool VerifyData(byte[] data, byte[] signature)
            => VerifyData(data, signature, new HashAlgorithmName(HashAlgorithm.Algorithm));

        public bool VerifyData(byte[] data, int offset, int count, byte[] signature)
            => VerifyData(data, offset, count, signature, new HashAlgorithmName(HashAlgorithm.Algorithm));

        public bool VerifyData(Stream data, byte[] signature)
            => VerifyData(data, signature, new HashAlgorithmName(HashAlgorithm.Algorithm));
    }
}