更改 X509 证书的颁发者名称

Change issuer name of X509 certificate

7 年前有 same question 但使用外部库解决了。现在可以在没有外部库的情况下更改 X509Certificate2 Issuer Name?

public static X509Certificate2 Create(string host)
{
    var ecdsa = ECDsa.Create();
    var request = new CertificateRequest($"CN={host}", ecdsa, HashAlgorithmName.SHA256);
    var validFrom = DateTimeOffset.UtcNow;
    var validUntil = DateTimeOffset.UtcNow.AddYears(5);
    var certificate = request.CreateSelfSigned(validFrom, validUntil);
    var certificateBytes = certificate.Export(X509ContentType.Pfx);
    // ??? set issuer ???
    return new X509Certificate2(certificateBytes);
}

对于自签名证书,更改颁发者名称只会生成不可验证的证书(除非您与颁发者​​共享私钥,这是一个坏主意)。

对于链签名证书,您可以使用 CertificateRequest.Create 方法之一来提供颁发者证书(其使用者名称将是新证书的颁发者名称),或者您可以提供颁发者名称和签名生成器(例如 X509SignatureGenerator.CreateForECDsa(key)),用于完全控制(包括制作上述不可验证的证书)。

@bartojs 答案是正确的我必须使用 CertificateRequest.Create.

我正在为我的 MITM 代理服务器应用程序创建自签名证书,所以这里是在 .Net Framework 4.7.2 中生成证书的工作代码,没有外部 .dll 库,如 Bouncy Castle 或 CERTENROLLIB

public static X509Certificate2 CreateMyCertificate(string host, bool isCA)
{
    var validFrom = DateTimeOffset.UtcNow;
    var validUntil = DateTimeOffset.UtcNow.AddYears(8);
    X509Certificate2 certificate;
    RSA rsaKey = Program.rootRSA;

    if (isCA)
    {
        // create exportable Root private key
        // will be used later as global variable Program.rootRSA
        CspParameters cspParams = new CspParameters
        {
            KeyContainerName = Guid.NewGuid().ToString()
        };

        rsaKey = new RSACryptoServiceProvider(2048, cspParams);
    }

    // Request a certificate with the common name as the host using the key pair.
    // Common Name (AKA CN) represents the server name protected by the SSL certificate.
    var request = new CertificateRequest(
        $"CN={host}",
        rsaKey,
        HashAlgorithmName.SHA256,
        RSASignaturePadding.Pkcs1
    );

    // Add Certificate Extensions
    request.CertificateExtensions.Add(
        new X509EnhancedKeyUsageExtension(
            new OidCollection
            {
                new Oid("1.3.6.1.5.5.7.3.1")
            }, true)
    );

    if (isCA)
    {
        request.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, true, 1, false));
        request.CertificateExtensions.Add(
            new X509SubjectKeyIdentifierExtension(request.PublicKey, false));
        request.CertificateExtensions.Add(
            new X509KeyUsageExtension(X509KeyUsageFlags.CrlSign | X509KeyUsageFlags.KeyCertSign, false));

        certificate = request.CreateSelfSigned(validFrom, validUntil);
    }
    else
    {
        SubjectAlternativeNameBuilder sanBuilder = new SubjectAlternativeNameBuilder();
        sanBuilder.AddDnsName(host.Replace("*.", ""));
        request.CertificateExtensions.Add(sanBuilder.Build());
        request.CertificateExtensions.Add(new X509Extension("2.5.29.35", Program.authorityKeyIdentifer, false));
        request.CertificateExtensions.Add(new X509KeyUsageExtension(
            X509KeyUsageFlags.KeyEncipherment, false));

        var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
        var unixTime = Convert.ToInt64((DateTime.UtcNow - epoch).TotalSeconds);
        var serial = BitConverter.GetBytes(unixTime);

        certificate = request.Create(Program.rootCert, validFrom, validUntil.AddDays(-5), serial);
        certificate.PrivateKey = Program.rootCert.PrivateKey;
    }

    var certificateBytes = certificate.Export(X509ContentType.Pfx, "");
    // return certificate with persisted private key
    return new X509Certificate2(certificateBytes, "", X509KeyStorageFlags.Exportable);
}

然后像这样使用它:

  1. 创建全局变量并将其命名为 rootCertrootRSA
  2. 在应用程序启动时检查计算机证书,如果未存储 CA,则使用
  3. 生成
rootCert = CreateMyCertificate("__MY_ROOT_CERTIFICATE", true);
  1. Export/Store rootCert 到受信任的根位置
  2. 稍后如果有 https connect 请求,我们可以使用
  3. 生成证书
CreateMyCertificate("whosebug.com", false);
  1. 当 chaining/creating host 证书确保 rootCert 已定义,因为它将在 CreateMyCertificate() 方法中使用,下面一行
if (isCA){...}
else
{
    certificate = request.Create(Program.rootCert, validFrom, validUntil.AddDays(-5), serial);
}

请注意为什么您不应该使用 CERTENROLLIB:

  • 慢,因为它的操作需要直接从计算机证书中读取
  • RSA 生成也比内置的 .net 4.7.2 慢
  • 要获取私钥,我们首先需要安装证书,我们可以通过对计算机证书进行大量安装来导出到 PFX,它可以向您的计算机证书发送垃圾邮件。