将生成的证书添加到商店并更新 IIS 站点绑定

Add a generated certificate to the store and update an IIS site binding

我 运行 喜欢以下内容,在感觉我已经用尽了对 Google 和 Stack Overflow 的各种研究途径后,我决定只问我自己的问题。

我正在尝试根据我已经拥有的 CA 证书生成个人证书(使用 BouncyCastle)。生成证书后,将其放入 'My' 存储区,然后我尝试更新我的 IIS 网站的 SSL 绑定以使用这个新证书。

我注意到 IIS 网站的更新(使用 ServerManager)没有抛出异常,但是当我转到 IIS 管理器控制台时,我注意到该网站的绑定没有 SSL 证书 select编辑。当我尝试 select 我创建的证书时(显示为一个可行的选项)我收到以下错误消息:

A specified logon session does not exist. It may already have been terminated. (Exception from HRESULT: 0x80070520)

作为测试,我导出了我生成的证书(带有私钥)并通过向导重新安装它,然后再次尝试设置有效的绑定(通过 IIS 管理器)。

由于这种行为,我认为这是我生成证书或将证书添加到商店的方式的问题。我希望有人可能知道我遇到的问题可能是什么。以下是用于创建证书、将其添加到商店以及以编程方式更新网站绑定的相关函数(我相信):

生成获取CA证书私钥,生成个人自签名证书,更新站点绑定的主要功能:

public static bool GenerateServerCertificate(
    X509Certificate2 CACert, 
    bool addToStore,
    DateTime validUntil)
{
    try
    {
        if (CACert.PrivateKey == null)
        {
            throw new CryptoException("Authority certificate has no private key");
        }

        var key = DotNetUtilities.GetKeyPair(CACert.PrivateKey).Private;

        byte[] certHash = GenerateCertificateBasedOnCAPrivateKey(
            addToStore,
            key,
            validUntil);

        using (ServerManager manager = new ServerManager())
        {
            Site site = manager.Sites.Where(q => q.Name == "My Site").FirstOrDefault();

            if (site == null)
            {
                return false;
            }

            foreach (Binding binding in site.Bindings)
            {
                if (binding.Protocol == "https")
                {
                    binding.CertificateHash = certHash;
                    binding.CertificateStoreName = "MY";
                }
            }

            manager.CommitChanges();
        }
    }
    catch(Exception ex)
    {
        LOG.Error("Error generating certitifcate", ex);
        return false;
    }

    return true;
}

正在根据 CA 私钥生成证书:

public static byte[] GenerateCertificateBasedOnCAPrivateKey(
    bool addToStore,
    AsymmetricKeyParameter issuerPrivKey,
    DateTime validUntil,
    int keyStrength = 2048)
{
    string subjectName = $"CN={CertSubjectName}";

    // Generating Random Numbers
    CryptoApiRandomGenerator randomGenerator = new CryptoApiRandomGenerator();
    SecureRandom random = new SecureRandom(randomGenerator);
    ISignatureFactory signatureFactory = new Asn1SignatureFactory("SHA512WITHRSA", issuerPrivKey, random);

    // The Certificate Generator
    X509V3CertificateGenerator certificateGenerator = new X509V3CertificateGenerator();
    certificateGenerator.AddExtension(
        X509Extensions.ExtendedKeyUsage, 
        true, 
        new ExtendedKeyUsage((new List<DerObjectIdentifier> { new DerObjectIdentifier("1.3.6.1.5.5.7.3.1") })));

    // Serial Number
    BigInteger serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), random);
    certificateGenerator.SetSerialNumber(serialNumber);

    // Issuer and Subject Name            
    X509Name subjectDN = new X509Name(subjectName);
    X509Name issuerDN = new X509Name(CACertificateName);
    certificateGenerator.SetIssuerDN(issuerDN);
    certificateGenerator.SetSubjectDN(subjectDN);

    // Valid For
    DateTime notBefore = DateTime.UtcNow.Date;
    DateTime notAfter = validUntil > notBefore ? validUntil : notBefore.AddYears(1);

    certificateGenerator.SetNotBefore(notBefore);
    certificateGenerator.SetNotAfter(notAfter);

    // Subject Public Key
    AsymmetricCipherKeyPair subjectKeyPair;
    var keyGenerationParameters = new KeyGenerationParameters(random, keyStrength);
    var keyPairGenerator = new RsaKeyPairGenerator();
    keyPairGenerator.Init(keyGenerationParameters);
    subjectKeyPair = keyPairGenerator.GenerateKeyPair();

    certificateGenerator.SetPublicKey(subjectKeyPair.Public);

    // Generating the Certificate
    Org.BouncyCastle.X509.X509Certificate certificate = certificateGenerator.Generate(signatureFactory);

    // correcponding private key
    PrivateKeyInfo info = PrivateKeyInfoFactory.CreatePrivateKeyInfo(subjectKeyPair.Private);

    // merge into X509Certificate2
    X509Certificate2 x509 = new X509Certificate2(certificate.GetEncoded());

    Asn1Sequence seq = (Asn1Sequence)Asn1Object.FromByteArray(info.ParsePrivateKey().GetDerEncoded());
    if (seq.Count != 9)
    {
        throw new PemException("Malformed sequence in RSA private key");
    }

    RsaPrivateKeyStructure rsa = RsaPrivateKeyStructure.GetInstance(seq);
    RsaPrivateCrtKeyParameters rsaparams = new RsaPrivateCrtKeyParameters(
        rsa.Modulus, 
        rsa.PublicExponent, 
        rsa.PrivateExponent, 
        rsa.Prime1,
        rsa.Prime2, 
        rsa.Exponent1, 
        rsa.Exponent2,
        rsa.Coefficient);

    x509.PrivateKey = DotNetUtilities.ToRSA(rsaparams);

    if (addToStore)
    {
        // Add certificate to the Personal store
        AddCertToStore(x509, StoreName.My, StoreLocation.LocalMachine, "Certificate Friendly Name");
    }

    return x509.GetCertHash();
}

正在将证书添加到商店:

private static void AddCertToStore(X509Certificate2 cert, StoreName storeName, StoreLocation storeLocation, string friendlyName)
{
    X509Store store = new X509Store(storeName, storeLocation);

    try
    {
        store.Open(OpenFlags.ReadWrite);
        store.Add(cert);

        if (!string.IsNullOrWhiteSpace(friendlyName)) {
            var certs = store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, cert.Subject, true);
            if (certs.Count > 0)
            {
                certs[0].FriendlyName = friendlyName;
            }
        }
    }
    finally
    {
        store.Close();
    }
}

最后一点,关于这个错误,我已经尝试了一些我在各个网站上看到的东西(似乎不太清楚问题是什么):

知道我的问题是什么吗?


更新:

我通过执行以下操作获得了一些结果:

通过执行 File.WriteAllBytes(filePath, cert.Export(X509ContentType.Pkcs12, password));

以编程方式将我的证书导出到文件

然后我通过以下操作将此证书文件导入商店:

var cert = new X509Certificate2(certFilePath, certPassword, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);

// My original AddCertToStore function
AddCertToStore(cert, StoreName.My, StoreLocation.LocalMachine, "Friendly Name"); 

最后,我像之前一样设置了绑定:

using (ServerManager manager = new ServerManager())
{
    Site site = manager.Sites.Where(q => q.Name == "My Site").FirstOrDefault();

    if (site == null)
    {
        return false;
    }

    foreach (Binding binding in site.Bindings)
    {
        if (binding.Protocol == "https")
        {
            binding.CertificateHash = certHash;
            binding.CertificateStoreName = "MY";
        }
    }

    manager.CommitChanges();
 }

这样做可行,但我不明白为什么我要将证书导出到文件,然后将其加载到 X509Certificate2 对象中,添加到商店,最后设置绑定。

ToRSA 方法很可能会创建一个临时 RSA 密钥,因此当引用全部消失时,密钥将被删除。将临时结构导出到 PFX,然后使用 PersistKeySet 重新导入它是将其转换为持久密钥的一种方法。其他存在,但那个是不那么复杂的之一。

不过,您实际上不必将其写入文件。

byte[] pkcs12Blob = cert.Export(X509ContentType.Pkcs12, password);
ver certWithPersistedKey = new X509Certificate2(pkcs12Blob, password, allTheFlagsYouAlreadySet);

还有其他微妙之处,比如设置 PrivateKey 属性 对于从商店加载的证书实例和从字节加载的证书实例具有不同的行为......PFX/PKCS#12 export/import 解决所有这些问题。

对我们来说,它与无效的证书有关。我们转到 IIS >> 服务器证书并从那里导出证书。

此后证书已正确绑定到 IIS 站点。