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

How to create X509Certificate2 with PrivateKey in .NET Standard 2.0/Core 2.1?

我们正在生成一些自签名证书以使用 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;
}

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

Operation is not supported on this platform.

显然 this is expected behavior in .NET Core. I am aware of the CopyWithPrivateKey method as mentioned in , which in theory is what I should be using. However, this method is not supported in .NET Standard 2.0 (note the error at the top of the page indicating the redirect). Furthermore, the .NET Framework app cannot be converted to .NET Core at the moment because of some other dependencies which are .NET Framework. According to this matrix,.NET Framework 根本不支持 .NET Standard 2.1,这意味着 我无法升级到 .NET Standard 2.1 并使用 CopyWithPrivateKey!

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

经过多次挖掘,我找到的唯一解决方案是将证书转换为 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);
    }

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


由于关于 Bouncy Castle 和 .NET 证书的文档充其量是稀疏的,这里是我在此过程中发现的一些怪癖:

  • PKCS12 容器中的证书条目和密钥条目的别名必须相同。否则,您将在尝试使用私钥时遇到 "Keyset does not exist" 异常。

  • 加载字节数组时必须使用X509KeyStorageFlags.Exportable,否则可能会出现"Key not valid for use in specified state"异常,具体取决于您使用证书的方式。这也意味着您必须提供密码,因为没有密码就不会过载,但您可以使用任何旧字符串,因为它是临时密码。