File: System\Security\Cryptography\ECCng.ImportExport.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 static Interop.BCrypt;

namespace System.Security.Cryptography
{
    internal static partial class ECCng
    {
        internal static CngKey ImportKeyBlob(byte[] ecBlob, string curveName, bool includePrivateParameters)
        {
            CngKeyBlobFormat blobFormat = includePrivateParameters ? CngKeyBlobFormat.EccPrivateBlob : CngKeyBlobFormat.EccPublicBlob;
            CngKey newKey = CngKey.Import(ecBlob, curveName, blobFormat);
            newKey.ExportPolicy |= CngExportPolicies.AllowPlaintextExport;

            return newKey;
        }

        internal static CngKey ImportFullKeyBlob(byte[] ecBlob, bool includePrivateParameters)
        {
            CngKeyBlobFormat blobFormat = includePrivateParameters ? CngKeyBlobFormat.EccFullPrivateBlob : CngKeyBlobFormat.EccFullPublicBlob;
            CngKey newKey = CngKey.Import(ecBlob, blobFormat);
            newKey.ExportPolicy |= CngExportPolicies.AllowPlaintextExport;

            return newKey;
        }

        internal static byte[] ExportKeyBlob(CngKey key, bool includePrivateParameters)
        {
            CngKeyBlobFormat blobFormat = includePrivateParameters ? CngKeyBlobFormat.EccPrivateBlob : CngKeyBlobFormat.EccPublicBlob;
            return key.Export(blobFormat);
        }

        internal static byte[] ExportFullKeyBlob(CngKey key, bool includePrivateParameters)
        {
            CngKeyBlobFormat blobFormat = includePrivateParameters ? CngKeyBlobFormat.EccFullPrivateBlob : CngKeyBlobFormat.EccFullPublicBlob;
            return key.Export(blobFormat);
        }

        internal static byte[] ExportKeyBlob(
            CngKey key,
            bool includePrivateParameters,
            out CngKeyBlobFormat format,
            out string? curveName)
        {
            curveName = key.GetCurveName(out _);
            bool forceGenericBlob = false;

            if (string.IsNullOrEmpty(curveName))
            {
                // Normalize curveName to null.
                curveName = null;

                forceGenericBlob = true;
                format = includePrivateParameters ?
                    CngKeyBlobFormat.EccFullPrivateBlob :
                    CngKeyBlobFormat.EccFullPublicBlob;
            }
            else
            {
                format = includePrivateParameters ?
                    CngKeyBlobFormat.EccPrivateBlob :
                    CngKeyBlobFormat.EccPublicBlob;
            }

            byte[] blob = key.Export(format);

            // Importing a known NIST curve as explicit parameters NCryptExportKey may
            // cause it to export with the dwMagic of the known curve and a generic blob body.
            // This combination can't be re-imported. So correct the dwMagic value to allow it
            // to import.
            if (forceGenericBlob)
            {
                FixupGenericBlob(blob);
            }

            return blob;
        }

        internal static ECParameters ExportExplicitParameters(CngKey key, bool includePrivateParameters)
        {
            if (includePrivateParameters)
            {
                return ExportPrivateExplicitParameters(key);
            }
            else
            {
                byte[] blob = ExportFullKeyBlob(key, includePrivateParameters: false);
                ECParameters ecparams = default;
                ExportPrimeCurveParameters(ref ecparams, blob, includePrivateParameters: false);
                return ecparams;
            }
        }

        internal static ECParameters ExportParameters(CngKey key, bool includePrivateParameters)
        {
            ECParameters ecparams = default;

            const string TemporaryExportPassword = "DotnetExportPhrase";
            string? curveName = key.GetCurveName(out string? oidValue);

            if (string.IsNullOrEmpty(curveName))
            {
                if (includePrivateParameters)
                {
                    ecparams = ExportPrivateExplicitParameters(key);
                }
                else
                {
                    byte[] fullKeyBlob = ExportFullKeyBlob(key, includePrivateParameters: false);
                    ECCng.ExportPrimeCurveParameters(ref ecparams, fullKeyBlob, includePrivateParameters: false);
                }
            }
            else
            {
                bool encryptedOnlyExport = CngPkcs8.AllowsOnlyEncryptedExport(key);

                if (includePrivateParameters && encryptedOnlyExport)
                {
                    byte[] exported = key.ExportPkcs8KeyBlob(TemporaryExportPassword, 1);
                    EccKeyFormatHelper.ReadEncryptedPkcs8(
                        exported,
                        TemporaryExportPassword,
                        out _,
                        out ecparams);
                }
                else
                {
                    byte[] keyBlob = ExportKeyBlob(key, includePrivateParameters);
                    ECCng.ExportNamedCurveParameters(ref ecparams, keyBlob, includePrivateParameters);
                    ecparams.Curve = ECCurve.CreateFromOid(new Oid(oidValue, curveName));
                }
            }

            return ecparams;
        }

        private static ECParameters ExportPrivateExplicitParameters(CngKey key)
        {
            bool encryptedOnlyExport = CngPkcs8.AllowsOnlyEncryptedExport(key);

            ECParameters ecparams = default;

            if (encryptedOnlyExport)
            {
                // We can't ask CNG for the explicit parameters when performing a PKCS#8 export. Instead,
                // we ask CNG for the explicit parameters for the public part only, since the parameters are public.
                // Then we ask CNG by encrypted PKCS#8 for the private parameters (D) and combine the explicit public
                // key along with the private key.
                const string TemporaryExportPassword = "DotnetExportPhrase";
                byte[] publicKeyBlob = ExportFullKeyBlob(key, includePrivateParameters: false);
                ExportPrimeCurveParameters(ref ecparams, publicKeyBlob, includePrivateParameters: false);

                byte[] exported = key.ExportPkcs8KeyBlob(TemporaryExportPassword, 1);
                EccKeyFormatHelper.ReadEncryptedPkcs8(
                    exported,
                    TemporaryExportPassword,
                    out _,
                    out ECParameters localParameters);

                Debug.Assert(ecparams.Q.X.AsSpan().SequenceEqual(localParameters.Q.X));
                Debug.Assert(ecparams.Q.Y.AsSpan().SequenceEqual(localParameters.Q.Y));
                ecparams.D = localParameters.D;
            }
            else
            {
                byte[] blob = ExportFullKeyBlob(key, includePrivateParameters: true);
                ExportPrimeCurveParameters(ref ecparams, blob, includePrivateParameters: true);
            }

            return ecparams;
        }

        private static unsafe void FixupGenericBlob(byte[] blob)
        {
            if (blob.Length > sizeof(BCRYPT_ECCKEY_BLOB))
            {
                fixed (byte* pBlob = blob)
                {
                    BCRYPT_ECCKEY_BLOB* pBcryptBlob = (BCRYPT_ECCKEY_BLOB*)pBlob;

                    switch ((KeyBlobMagicNumber)pBcryptBlob->Magic)
                    {
                        case KeyBlobMagicNumber.BCRYPT_ECDH_PUBLIC_P256_MAGIC:
                        case KeyBlobMagicNumber.BCRYPT_ECDH_PUBLIC_P384_MAGIC:
                        case KeyBlobMagicNumber.BCRYPT_ECDH_PUBLIC_P521_MAGIC:
                            pBcryptBlob->Magic = KeyBlobMagicNumber.BCRYPT_ECDH_PUBLIC_GENERIC_MAGIC;
                            break;
                        case KeyBlobMagicNumber.BCRYPT_ECDH_PRIVATE_P256_MAGIC:
                        case KeyBlobMagicNumber.BCRYPT_ECDH_PRIVATE_P384_MAGIC:
                        case KeyBlobMagicNumber.BCRYPT_ECDH_PRIVATE_P521_MAGIC:
                            pBcryptBlob->Magic = KeyBlobMagicNumber.BCRYPT_ECDH_PRIVATE_GENERIC_MAGIC;
                            break;
                        case KeyBlobMagicNumber.BCRYPT_ECDSA_PUBLIC_P256_MAGIC:
                        case KeyBlobMagicNumber.BCRYPT_ECDSA_PUBLIC_P384_MAGIC:
                        case KeyBlobMagicNumber.BCRYPT_ECDSA_PUBLIC_P521_MAGIC:
                            pBcryptBlob->Magic = KeyBlobMagicNumber.BCRYPT_ECDSA_PUBLIC_GENERIC_MAGIC;
                            break;
                        case KeyBlobMagicNumber.BCRYPT_ECDSA_PRIVATE_P256_MAGIC:
                        case KeyBlobMagicNumber.BCRYPT_ECDSA_PRIVATE_P384_MAGIC:
                        case KeyBlobMagicNumber.BCRYPT_ECDSA_PRIVATE_P521_MAGIC:
                            pBcryptBlob->Magic = KeyBlobMagicNumber.BCRYPT_ECDSA_PRIVATE_GENERIC_MAGIC;
                            break;
                    }
                }
            }
        }
    }
}