无法从ECDSA证书导出私钥参数

问题描述 投票:0回答:1

我正在尝试生成ECDSA证书。证书本身看起来不错,但是私有密钥却是不可导出的(我无法导出其参数)。该证书是使用Bouncy Castle生成的,然后转换为.NET格式。由于我使用的是ECDsaCng密钥,因此无法直接将私钥分配给证书,因此必须使用如herehere所述的解决方法。应用解决方法后,私钥将丢失AllowPlaintextExport策略,并且我不知道如何强制使用它。我试图从this答案中应用一种解决方法,然后从pkcs8 blob中重新导入密钥,但这将我带到了将密钥分配给证书的最初问题。这将再次带有不可导出的密钥。如何将证书和EC私钥附加到证书,并使其可同时导出?这是代码(问题在Main方法的最后一行):

using Microsoft.Win32.SafeHandles;
using Org.BouncyCastle.Asn1.Sec;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Operators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.X509;
using System;
using System.Collections;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using BcX509Certificate = Org.BouncyCastle.X509.X509Certificate;

namespace TestEccConvertWithExportPolicies
{
    public class Program
    {
        public static void Main(string[] args)
        {
            // generate Bouncy Castle certificate:

            var bcCertWithKey = X509CertificateGenerator.Generate();

            // convert to .NET

            var msCertificate = new X509Certificate2(DotNetUtilities.ToX509Certificate(bcCertWithKey.Certificate));
            var bcPrivateKey = (ECPrivateKeyParameters)bcCertWithKey.PrivateKey;
            var q = bcPrivateKey.Parameters.G.Normalize().Multiply(bcPrivateKey.D);
            var bcPublicKey = new ECPublicKeyParameters(bcPrivateKey.AlgorithmName, q, bcPrivateKey.PublicKeyParamSet);
            var ecParameters = new ECParameters();
            ecParameters.Curve = ECCurve.NamedCurves.nistP256;
            ecParameters.D = bcPrivateKey.D.ToByteArrayUnsigned();
            ecParameters.Q.X = bcPublicKey.Q.XCoord.GetEncoded();
            ecParameters.Q.Y = bcPublicKey.Q.YCoord.GetEncoded();
            ecParameters.Validate();
            var ecDsaCng = (ECDsaCng)ECDsaCng.Create(ecParameters);

            // set .NET certificate private key

            var newCertificate = X509CertificateCngKeySetter.MateECDsaPrivateKey(msCertificate, ecDsaCng.Key);
            var newPrivateKey = newCertificate.GetECDsaPrivateKey();
            var newParameters = newPrivateKey.ExportParameters(true); // the requested operation is not supported
        }
    }

    public class X509CertWithKey
    {
        public BcX509Certificate Certificate { get; set; }
        public AsymmetricKeyParameter PrivateKey { get; set; }
    }

    public class X509CertificateGenerator
    {
        public static X509CertWithKey Generate()
        {
            var keyPair = GenerateKeyPair();
            var subject = GetSubject();
            var generator = new X509V3CertificateGenerator();
            generator.SetSerialNumber(GenerateSerial());
            generator.SetIssuerDN(subject);
            generator.SetNotBefore(DateTime.Now - TimeSpan.FromDays(5));
            generator.SetNotAfter(DateTime.Now + TimeSpan.FromDays(365));
            generator.SetSubjectDN(subject);
            generator.SetPublicKey(keyPair.Public);
            generator.AddExtension(X509Extensions.SubjectKeyIdentifier, false, new SubjectKeyIdentifier(SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(keyPair.Public)));
            generator.AddExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(true));
            generator.AddExtension(X509Extensions.KeyUsage, false, new KeyUsage(KeyUsage.DataEncipherment | KeyUsage.DigitalSignature | KeyUsage.KeyEncipherment | KeyUsage.KeyCertSign));
            generator.AddExtension(X509Extensions.ExtendedKeyUsage, false, new ExtendedKeyUsage(KeyPurposeID.IdKPServerAuth));
            var signatureFactory = new Asn1SignatureFactory("Sha256WithECDSA", keyPair.Private);
            var certificate = generator.Generate(signatureFactory);
            return new X509CertWithKey { Certificate = certificate, PrivateKey = keyPair.Private };
        }

        private static AsymmetricCipherKeyPair GenerateKeyPair()
        {
            var parameters = new ECKeyGenerationParameters(SecObjectIdentifiers.SecP256r1, new SecureRandom());
            var keyGenerator = new ECKeyPairGenerator();
            keyGenerator.Init(parameters);
            var keyPair = keyGenerator.GenerateKeyPair();
            return keyPair;
        }

        private static X509Name GetSubject()
        {
            IList ordering = new ArrayList();
            IDictionary attributes = new Hashtable();
            attributes[X509Name.CN] = "mycert";
            ordering.Add(X509Name.CN);
            var name = new X509Name(ordering, attributes);
            return name;
        }

        private static BigInteger GenerateSerial()
        {
            var serialRandomGen = new Random();
            var serialRandomBytes = new byte[17];
            serialRandomGen.NextBytes(serialRandomBytes);
            serialRandomBytes[0] = (byte)serialRandomGen.Next(0, 127);
            var serial = new BigInteger(serialRandomBytes);
            return serial;
        }
    }

    public class X509CertificateCngKeySetter
    {
        [DllImport("crypt32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern bool CertSetCertificateContextProperty(IntPtr pCertContext, CertContextPropId dwPropId, CertSetPropertyFlags dwFlags, SafeNCryptKeyHandle pvData);

        [DllImport("ncrypt.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        internal static extern int NCryptSetProperty(SafeNCryptHandle hObject, string pszProperty, byte[] pbInput, int cbInput, CngPropertyOptions dwFlags);

        internal enum CertContextPropId : int
        {
            CERT_NCRYPT_KEY_HANDLE_PROP_ID = 78,
        }

        [Flags]
        internal enum CertSetPropertyFlags : int
        {
            None = 0,
        }

        public static X509Certificate2 MateECDsaPrivateKey(X509Certificate2 cert, CngKey cngKey)
        {
            var exportPolicyBytes = BitConverter.GetBytes((int)(CngExportPolicies.AllowExport | CngExportPolicies.AllowPlaintextExport));
            NCryptSetProperty(cngKey.Handle, "Export Policy", exportPolicyBytes, exportPolicyBytes.Length, CngPropertyOptions.Persist);

            using (var tmpCert = new X509Certificate2(cert.RawData))
            {
                SafeNCryptKeyHandle keyHandle = cngKey.Handle;

                if (!CertSetCertificateContextProperty(tmpCert.Handle,
                    CertContextPropId.CERT_NCRYPT_KEY_HANDLE_PROP_ID,
                    CertSetPropertyFlags.None, keyHandle))
                    throw new CryptographicException(Marshal.GetLastWin32Error());

                byte[] pfxBytes = tmpCert.Export(X509ContentType.Pkcs12);

                keyHandle = new SafeNCryptKeyHandle();

                if (!CertSetCertificateContextProperty(tmpCert.Handle,
                    CertContextPropId.CERT_NCRYPT_KEY_HANDLE_PROP_ID,
                    CertSetPropertyFlags.None, keyHandle))
                    throw new CryptographicException(Marshal.GetLastWin32Error());

                var matedCert = new X509Certificate2(pfxBytes, (string)null, X509KeyStorageFlags.Exportable);
                return matedCert;
            }
        }
    }
}
c# .net private-key ecdsa cng
1个回答
0
投票

保留导出策略的一种方法是避免从pfx重新导入。我可以通过使用CopyWithPrivateKey类的ECDsaCertificateExtensions方法来做到这一点:

public static X509Certificate2 MateECDsaPrivateKey(X509Certificate2 cert, CngKey cngKey)
{
    var newCert = ECDsaCertificateExtensions.CopyWithPrivateKey(cert, new ECDsaCng(cngKey));
    return newCert;
}

source code,此方法将创建一个新的证书实例以及重新导入的私钥参数。

© www.soinside.com 2019 - 2024. All rights reserved.