2016-11-30 1 views
2

Je suis en train de courir après et après avoir eu l'impression d'avoir épuisé diverses possibilités de recherche sur Google et Stack Overflow, j'ai décidé de poser ma propre question à ce sujet .Ajouter un certificat généré au magasin et mettre à jour une liaison de site IIS

J'essaie de générer un certificat personnel (en utilisant BouncyCastle) basé sur un certificat de CA que j'ai déjà et que je possède. Après avoir généré le certificat, en le plaçant dans le magasin 'Mon', je tente ensuite de mettre à jour la liaison SSL de mon site IIS pour utiliser ce nouveau certificat. Ce que je remarque est que les mises à jour du site Web IIS (en utilisant ServerManager) ne lient pas l'exception, mais quand je vais à la console du gestionnaire IIS, je remarque que la liaison du site n'a aucun certificat SSL sélectionné. Lorsque je tente de sélectionner le certificat que j'ai créé (apparaît bien comme une option viable) je reçois le message d'erreur suivant:

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

En tant que test, j'exporté mon certificat généré (avec la clé privée) et réinstallé par l'intermédiaire de l'assistant, puis une fois de plus essayé de mettre en place la liaison (via le gestionnaire IIS) qui a fonctionné.

En raison de ce comportement, j'ai supposé que c'était un problème avec la façon dont je générais ou ajoutais le certificat au magasin. J'espérais que quelqu'un pourrait avoir une idée de ce que le problème que je suis peut-être. Voici les fonctions pertinentes (je crois) utilisées pour créer le certificat, l'ajouter au magasin et mettre à jour la liaison du site par programme:

La fonction principale qui génère la clé privée du certificat de l'autorité de certification, génère l'auto-identification personnelle. certificat signé et met à jour les sites de liaison:

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

Génération du certificat en fonction de la clé privée 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(); 
} 

Ajout du certificat au magasin:

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

Juste une note finale, je l'ai essayé quelques petites choses de ce que j'ai vu sur des sites différents en ce qui concerne cette erreur (ne semble pas très clair ce que la question est):

  • Cela fonctionne sur une autre boîte (ma machine de développement personnel) mais je frappé ces accrocs sur une machine serveur (exécutant Windows server R2 2012)
  • la boîte de dialogue d'aide IIS me informe la machine exécute IIS 8.5
  • vérifié la validité générée certificat et le certificat CA avec CertUtil.exe
  • Vérifié le certificat généré et le certificat de CA avait une clé privée qui pouvait être trouvée
  • Les administrateurs vérifiés (et éventuellement même mon compte connecté) avaient accès à l'emplacement de la clé privée du certificat de l'autorité de certification et du certificat généré.

Des idées ce que mon problème pourrait être?


Mise à jour:

Je suis en mesure d'obtenir des résultats en procédant comme suit:

Export mon certificat dans un fichier programme en faisant File.WriteAllBytes(filePath, cert.Export(X509ContentType.Pkcs12, password));

Puis-je importer ce certificat classer au magasin en faisant:

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

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

Enfin, je mets la liaison que je faisais auparavant:

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

En procédant ainsi, fonctionne, mais je ne vois pas pourquoi je devrais exporter le certificat dans un fichier, charger ensuite dans un X509Certificate2 objet, ajouter au magasin, et enfin mettre en place la liaison.

+0

J'ai rencontré le même problème avec un code similaire, et l'ajout de 'X509KeyStorageFlags.MachineKeySet' résout définitivement le problème. –

Répondre

0

La méthode ToRSA crée très probablement une clé RSA éphémère. Ainsi, lorsque toutes les références ont disparu, la clé est supprimée. L'exportation de la structure éphémère dans un PFX puis son réimportation avec PersistKeySet est une façon de le transformer en une clé persistante. D'autres existent, mais celui-là est l'un des moins compliqués. Cependant, il n'est pas nécessaire de l'écrire dans un fichier.

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

Il y a aussi d'autres subtilités en cours, comme définissant la propriété PrivateKey a des comportements différents pour une instance cert qui a été chargé dans un magasin et un qui a été chargé d'octets ... PFX/exportation PKCS # 12/import fonctionne autour de tous ceux-là.

+0

Merci, c'est une meilleure solution. – Fizz