我正在尝试生成ECDSA证书。证书本身看起来不错,但是私有密钥却是不可导出的(我无法导出其参数)。该证书是使用Bouncy Castle生成的,然后转换为.NET格式。由于我使用的是ECDsaCng
密钥,因此无法直接将私钥分配给证书,因此必须使用如here和here所述的解决方法。应用解决方法后,私钥将丢失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;
}
}
}
}
保留导出策略的一种方法是避免从pfx重新导入。我可以通过使用CopyWithPrivateKey
类的ECDsaCertificateExtensions
方法来做到这一点:
public static X509Certificate2 MateECDsaPrivateKey(X509Certificate2 cert, CngKey cngKey)
{
var newCert = ECDsaCertificateExtensions.CopyWithPrivateKey(cert, new ECDsaCng(cngKey));
return newCert;
}
从source code,此方法将创建一个新的证书实例以及重新导入的私钥参数。