File: System\Security\Cryptography\CngAlgorithmCore.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 Microsoft.Win32.SafeHandles;
using static Interop.NCrypt;

namespace System.Security.Cryptography
{
    //
    // Common infrastructure for crypto algorithms that accept CngKeys.
    //
    internal struct CngAlgorithmCore
    {
        private readonly Type _disposedType;
#pragma warning disable CS0649
        public CngAlgorithm? DefaultKeyType;
#pragma warning restore CS0649
        private CngKey? _lazyKey;
        private bool _disposed;

        public CngAlgorithmCore(Type disposedType) : this()
        {
            _disposedType = disposedType;
        }

        public static CngKey Duplicate(CngKey key)
        {
            return CngKey.Open(key.HandleNoDuplicate, key.IsEphemeral ? CngKeyHandleOpenOptions.EphemeralKey : CngKeyHandleOpenOptions.None);
        }

        public bool IsKeyGeneratedNamedCurve()
        {
            ThrowIfDisposed();
            return (_lazyKey != null && _lazyKey.IsECNamedCurve());
        }

        public void DisposeKey()
        {
            if (_lazyKey != null)
            {
                _lazyKey.Dispose();
                _lazyKey = null;
            }
        }

        public unsafe CngKey GetOrGenerateKey(int keySize, CngAlgorithm algorithm)
        {
            ThrowIfDisposed();

            // If our key size was changed, we need to generate a new key.
            if (_lazyKey != null)
            {
                if (_lazyKey.KeySize != keySize)
                    DisposeKey();
            }

            // If we don't have a key yet, we need to generate one now.
            if (_lazyKey == null)
            {
                CngKeyCreationParameters creationParameters = new CngKeyCreationParameters()
                {
                    ExportPolicy = CngExportPolicies.AllowPlaintextExport,
                };

                Span<byte> keySizeBuffer = stackalloc byte[sizeof(int)];
                bool success = BitConverter.TryWriteBytes(keySizeBuffer, keySize);
                Debug.Assert(success);

                CngProperty keySizeProperty = new CngProperty(KeyPropertyName.Length, keySizeBuffer, CngPropertyOptions.None);
                creationParameters.Parameters.Add(keySizeProperty);

                _lazyKey = CngKey.Create(algorithm, null, creationParameters);
            }

            return _lazyKey;
        }

        public CngKey GetOrGenerateKey(ECCurve? curve)
        {
            ThrowIfDisposed();

            if (_lazyKey != null)
            {
                return _lazyKey;
            }

            // We don't have a key yet so generate
            Debug.Assert(curve.HasValue);

            CngKeyCreationParameters creationParameters = new CngKeyCreationParameters()
            {
                ExportPolicy = CngExportPolicies.AllowPlaintextExport,
            };

            if (curve.Value.IsNamed)
            {
                creationParameters.Parameters.Add(CngKey.GetPropertyFromNamedCurve(curve.Value));
            }
            else if (curve.Value.IsPrime)
            {
                ECCurve eccurve = curve.Value;
                byte[] parametersBlob = ECCng.GetPrimeCurveParameterBlob(ref eccurve);
                CngProperty prop = new CngProperty(
                    Interop.BCrypt.BCryptPropertyStrings.BCRYPT_ECC_PARAMETERS,
                    parametersBlob,
                    CngPropertyOptions.None);
                creationParameters.Parameters.Add(prop);
            }
            else
            {
                throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_CurveNotSupported, curve.Value.CurveType.ToString()));
            }

            try
            {
                _lazyKey = CngKey.Create(DefaultKeyType ?? CngAlgorithm.ECDsa, null, creationParameters);
            }
            catch (CryptographicException e)
            {
                // Map to PlatformNotSupportedException if appropriate
                ErrorCode errorCode = (ErrorCode)e.HResult;

                if (curve.Value.IsNamed &&
                    errorCode == ErrorCode.NTE_INVALID_PARAMETER || errorCode == ErrorCode.NTE_NOT_SUPPORTED)
                {
                    throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_CurveNotSupported, curve.Value.Oid.FriendlyName), e);
                }
                throw;
            }

            return _lazyKey;
        }

        public void SetKey(CngKey key)
        {
            Debug.Assert(key != null);
            ThrowIfDisposed();

            // If we already have a key, clear it out.
            DisposeKey();

            _lazyKey = key;
        }

        public void Dispose()
        {
            DisposeKey();
            _disposed = true;
        }

        internal void ThrowIfDisposed()
        {
            ObjectDisposedException.ThrowIf(_disposed, _disposedType);
        }
    }
}