在不同的机器上生成和签署证书 C#

Generate and sign certificate in different machines C#

我需要生成证书以用于代理之间的安全通信。每个代理生成一个证书,并且必须将其发送到另一台机器上的系统 CA,以进行签名(并被其他代理信任)。我正在使用 C# 为代理执行此操作,代码如下:

 //generate certificate
        ECDsa elipticCurveNistP256Key = ECDsa.Create(ECCurve.CreateFromValue("1.2.840.10045.3.1.7")); // nistP256 curve
        CertificateRequest certificateRequest = new CertificateRequest("CN=" + agentId, elipticCurveNistP256Key, HashAlgorithmName.SHA256);
        certificateRequest.CertificateExtensions.Add(
            new X509BasicConstraintsExtension(false, false, 0, false));
        certificateRequest.CertificateExtensions.Add(
            new X509KeyUsageExtension(
                X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.NonRepudiation,
                false));

        // Add the SubjectAlternativeName extension
        var sanBuilder = new SubjectAlternativeNameBuilder();
        sanBuilder.AddIpAddress(IPAddress.Parse(agentIpAddress));
        certificateRequest.CertificateExtensions.Add(sanBuilder.Build());

        certificateRequest.CertificateExtensions.Add(
            new X509EnhancedKeyUsageExtension(
                new OidCollection
                {
                    new Oid("1.3.6.1.5.5.7.3.8")
                },
                true));

        certificateRequest.CertificateExtensions.Add(
            new X509SubjectKeyIdentifierExtension(certificateRequest.PublicKey, false));

CA系统的代码如下:

  X509Certificate2 signedCertificate = certificateRequest.Create(
            caCertificatePFX,
            DateTimeOffset.UtcNow.AddDays(-1),
            DateTimeOffset.UtcNow.AddDays(30),
            new byte[] {1, 2, 3, 4});

当然,我也使用代码进行机器之间的通信,我没有在这里显示。但我至少有两个问题:

  1. 我希望将证书生成和签名完全分开,但即使尝试了很多次,这也是我能够开始工作的唯一代码。如果我没记错的话,这段代码在 CA 系统上创建了证书,这不是理想的场景(CA 可以访问代理私钥),但如果我没有找到更好的,我可以接受。

  2. 第二个问题是,即使我接受了第一个问题,我仍然需要将 CertificateRequest 对象从一台机器发送到另一台机器,而 CertificateRequest 是不可序列化的。我找到了 "Creates an ASN.1 DER-encoded PKCS#10 CertificationRequest value representing the state of the current object." 的方法 CreateSigningRequest() 但是我还没有找到一种方法来使它再次成为 CertificateRequest 对象,这样我就可以 运行 CA 系统代码。

有人知道我该怎么做吗?希望将证书生成和证书签名完全分开,但如果不可能,至少要重新创建 CertificateRequest 对象。

我正在 运行ning .Net Framework 4.7.2,我需要维护它才能使用以前开发的 Windows 表单。

谢谢

如您所述,无法回读 PKCS#10 请求。这主要是因为 "OK" 证书颁发机构缺少太多东西,因此拥有 reader 只会产生很多 "bad" 证书颁发机构。 (由于您的 CA 不支持吊销,它也是一个 "bad" CA,但您正在使用短期证书来缓解这种情况。)

PKCS#10 请求包含:

  • 一个数据格式版本
  • 一个名字(大概是请求者想要的名字)
  • 一个public键
  • 属性
    • 请求的扩展在这里(EKU、主题备用名称等)
  • 签名,证明请求者有私钥。

如果您不使用数据格式,则数据格式版本无关紧要,并且签名对于 "closed" 颁发者(仅向直接已知方颁发证书的 CA)来说并不重要。因此,您只需要传输 public 密钥和请求所需的任何其他数据(查看您当前的代码、代理 ID 和 IP 地址)。

唯一棘手的部分是发送 public 密钥...但是使用 .NET Core 3.0+,您可以将所有密钥规范化为它们的 SubjectPublicKeyInfo 格式:

byte[] spki = elipticCurveNistP256Key.ExportSubjectPublicKeyInfo();

虽然 PublicKey 类型拥有 ImportSubjectPublicKeyInfo 方法会非常聪明,但这还没有发生。对于通用解析,您可能想尝试所有主要的密钥类型,但由于您在另一端是一个封闭的 CA,您可以先验地知道它是 ECDSA:

using (ECDsa clientPub = ECDsa.Create())
{
    clientPub.ImportSubjectPublicKeyInfo(transmittedSpki, out _);

    // the rest of your code goes here.
}

我强烈建议使用 CA 软件来签署证书请求。期间.

任何尝试拥有您的 CA 代码的尝试都会使解决方案在许多方面变得不可靠、脆弱且容易出错。有几个选项,首先是 Microsoft ADCS (Windows) 和 EJBCA (Windows/Linux)。任何其他设计都会很糟糕。