File: src\runtime\src\libraries\Common\src\System\Security\Cryptography\ECCng.ImportExport.NamedCurve.cs
Web Access
Project: 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.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
using BCRYPT_ECCKEY_BLOB = Interop.BCrypt.BCRYPT_ECCKEY_BLOB;
using ErrorCode = Interop.NCrypt.ErrorCode;
using KeyBlobMagicNumber = Interop.BCrypt.KeyBlobMagicNumber;

namespace System.Security.Cryptography
{
    internal static partial class ECCng
    {
        internal delegate TResult EncodeBlobFunc<TResult>(byte[] blob);

        // When possible, operate on the blob inside the callback since the array will be pinned during its execution.
        internal static T EncodeEccKeyBlob<T>(KeyBlobMagicNumber magic, byte[] x, byte[] y, byte[]? d, Func<byte[], T> encodeCallback, bool clearBlob = true)
        {
            bool includePrivateParameters = d is not null;
            byte[] blob;
            unsafe
            {
                // We need to build a key blob structured as follows:
                //     BCRYPT_ECCKEY_BLOB   header
                //     byte[cbKey]          Q.X
                //     byte[cbKey]          Q.Y
                //     -- Only if "includePrivateParameters" is true --
                //     byte[cbKey]          D

                Debug.Assert(x.Length == y.Length);

                int blobSize;

                checked
                {
                    blobSize = sizeof(BCRYPT_ECCKEY_BLOB) +
                        x.Length +
                        y.Length;
                    if (includePrivateParameters)
                    {
                        blobSize += d!.Length;
                    }
                }

                blob = new byte[blobSize];
                fixed (byte* pBlob = &blob[0])
                {
                    try
                    {
                        // Build the header
                        BCRYPT_ECCKEY_BLOB* pBcryptBlob = (BCRYPT_ECCKEY_BLOB*)pBlob;
                        pBcryptBlob->Magic = magic;
                        pBcryptBlob->cbKey = x.Length;

                        // Emit the blob
                        int offset = sizeof(BCRYPT_ECCKEY_BLOB);
                        Interop.BCrypt.Emit(blob, ref offset, x);
                        Interop.BCrypt.Emit(blob, ref offset, y);

                        if (includePrivateParameters)
                        {
                            Debug.Assert(x.Length == d?.Length);
                            Interop.BCrypt.Emit(blob, ref offset, d!);
                        }

                        // We better have computed the right allocation size above!
                        Debug.Assert(offset == blobSize);
                        return encodeCallback(blob);
                    }
                    finally
                    {
                        if (clearBlob)
                        {
                            CryptographicOperations.ZeroMemory(blob);
                        }
                    }
                }
            }
        }

        internal delegate T DecodeBlobFunc<T>(KeyBlobMagicNumber magic, byte[] x, byte[] y, byte[]? d);

        // When possible, operate on the private key inside the callback since the array will be pinned during its execution.
        internal static T DecodeEccKeyBlob<T>(ReadOnlySpan<byte> ecBlob, DecodeBlobFunc<T> decodeCallback, bool clearPrivateKey = true)
        {
            // We now have a buffer laid out as follows:
            //     BCRYPT_ECCKEY_BLOB   header
            //     byte[cbKey]          Q.X
            //     byte[cbKey]          Q.Y
            //     -- Private only --
            //     byte[cbKey]          D

            KeyBlobMagicNumber magic = (KeyBlobMagicNumber)MemoryMarshal.Cast<byte, int>(ecBlob)[0];

            unsafe
            {
                // Fail-fast if a rogue provider gave us a blob that isn't even the size of the blob header.
                if (ecBlob.Length < sizeof(BCRYPT_ECCKEY_BLOB))
                    throw ErrorCode.E_FAIL.ToCryptographicException();

                fixed (byte* pEcBlob = &ecBlob[0])
                {
                    BCRYPT_ECCKEY_BLOB* pBcryptBlob = (BCRYPT_ECCKEY_BLOB*)pEcBlob;

                    int offset = sizeof(BCRYPT_ECCKEY_BLOB);

                    byte[] x = Interop.BCrypt.Consume(ecBlob, ref offset, pBcryptBlob->cbKey);
                    byte[] y = Interop.BCrypt.Consume(ecBlob, ref offset, pBcryptBlob->cbKey);

                    if (offset < ecBlob.Length)
                    {
                        byte[] d = new byte[pBcryptBlob->cbKey];

                        fixed (byte* pinnedD = d)
                        {
                            try
                            {
                                Interop.BCrypt.Consume(ecBlob, ref offset, d.Length, d);

                                Debug.Assert(offset == ecBlob.Length);

                                return decodeCallback(magic, x, y, d);
                            }
                            finally
                            {
                                if (clearPrivateKey)
                                {
                                    CryptographicOperations.ZeroMemory(d);
                                }
                            }
                        }
                    }
                    else
                    {
                        Debug.Assert(offset == ecBlob.Length);

                        return decodeCallback(magic, x, y, null);
                    }
                }
            }
        }

        internal static SafeNCryptKeyHandle ImportKeyBlob(
            string blobType,
            ReadOnlySpan<byte> keyBlob,
            string curveName,
            SafeNCryptProviderHandle provider,
            int flags = 0)
        {
            ErrorCode errorCode;
            SafeNCryptKeyHandle keyHandle;

            using (SafeUnicodeStringHandle safeCurveName = new SafeUnicodeStringHandle(curveName))
            {
                unsafe
                {
                    Interop.BCrypt.BCryptBufferDesc desc = default;
                    Interop.BCrypt.BCryptBuffer buff = default;

                    buff.cbBuffer = (curveName.Length + 1) * 2; // Add 1 for null terminator
                    buff.BufferType = Interop.BCrypt.CngBufferDescriptors.NCRYPTBUFFER_ECC_CURVE_NAME;
                    buff.pvBuffer = safeCurveName.DangerousGetHandle();

                    desc.cBuffers = 1;
                    desc.pBuffers = (IntPtr)(&buff);
                    desc.ulVersion = Interop.BCrypt.BCRYPTBUFFER_VERSION;

                    errorCode = Interop.NCrypt.NCryptImportKey(
                        provider,
                        IntPtr.Zero,
                        blobType,
                        (IntPtr)(&desc),
                        out keyHandle,
                        ref MemoryMarshal.GetReference(keyBlob),
                        keyBlob.Length,
                        flags);
                }
            }

            if (errorCode != ErrorCode.ERROR_SUCCESS)
            {
                Exception e = errorCode.ToCryptographicException();
                keyHandle.Dispose();
                if (errorCode == ErrorCode.NTE_INVALID_PARAMETER)
                {
                    throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_CurveNotSupported, curveName), e);
                }
                throw e;
            }

            return keyHandle;
        }
    }
}