如何在 .NET Standard 2.0/Core 2.1 中使用 PrivateKey 创建 X509Certificate2?

wly*_*les 4 c# .net-core .net-standard-2.0

我们正在生成一些自签名证书以使用 BouncyCastle 进行测试,但是当我们尝试向证书添加私钥时,代码会引发异常。这是有问题的代码:

private static X509Certificate2 CreateCertificate(string subject, DateTimeOffset notBefore, DataTimeOffset notAfter, string issuer, AsymmetricKeyParamter issuerPrivateKey)
{
        // Setup
        X509V3CertificateGenerator certGenerator = new X509V3CertificateGenerator();
        SecureRandom random = new SecureRandom(new CryptoApiRandomGenerator());

        RsaKeyPairGenerator keyPairGenerator = new RsaKeyPairGenerator();
        keyPairGenerator.Init(new KeyGenerationParameters(random, KeyStrength));

        // Randomly generate a serial number
        BigInteger serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(long.MaxValue), random);
        certGenerator.SetSerialNumber(serialNumber);

        // Set the issuer and subject names
        X509Name issuerName = new X509Name(issuer);
        X509Name subjectName = new X509Name(subject);
        certGenerator.SetIssuerDN(issuerName);
        certGenerator.SetSubjectDN(subjectName);

        // Set the validity period
        certGenerator.SetNotBefore(notBefore.UtcDateTime);
        certGenerator.SetNotAfter(notAfter.UtcDateTime);

        // Randomly generate the public key
        AsymmetricCipherKeyPair subjectKeyPair = keyPairGenerator.GenerateKeyPair();
        certGenerator.SetPublicKey(subjectKeyPair.Public);

        // Generate the signed certificate
        ISignatureFactory signatureFactory = new Asn1SignatureFactory(SHA256RSASignatureAlgorithm, issuerPrivateKey ?? subjectKeyPair.Private, random);
        X509Certificate2 certificate = new X509Certificate2(certGenerator.Generate(signatureFactory).GetEncoded());

        // Include the private key with the response
        // ERROR HERE!
        certificate.PrivateKey = DotNetUtilities.ToRSA(subjectKeyPair.Private as RsaPrivateCrtKeyParameters);

        return certificate;
}
Run Code Online (Sandbox Code Playgroud)

此代码位于面向 .NET Standard 2.0 的库中,该库是两个不同应用程序的依赖项:一个面向 .NET Core 2.1,另一个面向 .NET Framework 4.7.2。我相信这在 .NET Framework 应用程序中工作正常,但在 .NET Core 应用程序中,我在上面指示的行中收到此消息的异常:

此平台不支持操作。

显然,这是 .NET Core 中的预期行为。我知道这个问题中CopyWithPrivateKey提到的方法,理论上这是我应该使用的。但是,.NET Standard 2.0 不支持此方法(请注意页面顶部指示重定向的错误)。此外,由于 .NET Framework 的其他一些依赖项,.NET Framework 应用程序目前无法转换为 .NET Core。根据此矩阵,.NET Framework 根本不支持 .NET Standard 2.1,这意味着我无法升级到 .NET Standard 2.1并使用!CopyWithPrivateKey

如何X509Certificate2以与 .NET Core 兼容的方式在 .NET Standard 2.0 中使用私钥创建一个?

wly*_*les 6

经过大量挖掘,我找到的唯一解决方案是将证书转换为 PKCS12 格式的字节数组,附加私钥,然后将其读回X509Certificate2对象。这很糟糕,但据我所知,在 .NET Core 中,获得私钥的唯一方法是调用CopyWithPrivateKey(在问题中提到的 .NET Standard 2.0 中不可用)或加载包含私有密钥的PKCS12 数据钥匙。下面的代码可以做到这一点:

    private static X509Certificate2 AddPrivateKeyPlatformIndependent(Org.BouncyCastle.X509.X509Certificate bouncyCastleCert, AsymmetricKeyParameter privateKey)
    {
        string alias = bouncyCastleCert.SubjectDN.ToString();
        Pkcs12Store store = new Pkcs12StoreBuilder().Build();

        X509CertificateEntry certEntry = new X509CertificateEntry(bouncyCastleCert);
        store.SetCertificateEntry(alias, certEntry);

        // TODO: This needs extra logic to support a certificate chain
        AsymmetricKeyEntry keyEntry = new AsymmetricKeyEntry(privateKey);
        store.SetKeyEntry(alias, keyEntry, new X509CertificateEntry[] { certEntry });

        byte[] certificateData;
        string password = GenerateRandomString();
        using (MemoryStream memoryStream = new MemoryStream())
        {
            store.Save(memoryStream, password.ToCharArray(), new SecureRandom());
            memoryStream.Flush();
            certificateData = memoryStream.ToArray();
        }

        return new X509Certificate2(certificateData, password, X509KeyStorageFlags.Exportable);
    }
Run Code Online (Sandbox Code Playgroud)

使用它代替问题代码中的最后两行,而不是创建X509Certificate2with certGenerator,使用它来创建等效的 Bouncy Castle Type 以传递给它:Org.BouncyCastle.X509.X509Certificate bouncyCastleCert = certGenerator.Generate(signatureFactory);


由于有关 Bouncy Castle 和 .NET 证书的文档充其量很少,因此我在此过程中发现了一些怪癖:

  • PKCS12 容器中证书条目和密钥条目的别名必须相同。否则,您将在尝试使用私钥时收到“密钥集不存在”异常。

  • X509KeyStorageFlags.Exportable加载字节数组时必须使用,否则根据您使用证书的方式,您可能会收到“密钥在指定状态下无效”异常。这也意味着您必须提供密码,因为没有它就不会过载,但您可以使用任何旧字符串,因为它是临时密码。