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

时间:2016-11-30 15:47:16

标签: c# ssl iis bouncycastle x509certificate2

我遇到了以下情况,在感觉我已经用尽各种研究Google和Stack Overflow之后,我决定只问自己的问题。

我正在尝试根据我已拥有并拥有的CA证书生成个人证书(使用BouncyCastle)。生成证书后,将其放入“我的”商店,然后我尝试更新我的IIS网站的SSL绑定以使用此新证书。

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

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();
    }
}

最后一点,我已经尝试了一些我在各种网站上看到的关于该错误的事情(似乎不是很清楚问题是什么):

  • 这适用于不同的盒子(我的个人开发机器),但我在服务器机器上运行这些障碍(运行Windows Server 2012 R2)
  • “IIS帮助”对话框通知我机器正在运行IIS 8.5
  • 使用CertUtil.exe验证了有效性生成证书和CA证书
  • 验证了生成的证书,CA证书中有一个可以找到的私钥
  • 经过验证的管理员(最终甚至是我的登录帐户)都可以访问CA证书和生成的证书的私钥文件。

我的问题可能是什么想法?

更新

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

通过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对象中,添加到商店,最后设置绑定。

2 个答案:

答案 0 :(得分:1)

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

但实际上你不必将它写入文件。

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

还有其他细微之处,例如设置PrivateKey属性对于从商店加载的证书实例具有不同的行为,而从字节加载的版本具有不同的行为...... PFX / PKCS#12导出/导入可以解决所有这些。

答案 1 :(得分:0)

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

enter image description here

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