2016-11-07 1 views
3

Je souhaite passer du certificat auto-signé par appareil à la paire de certificats, dont l'un est généré précédemment, placé dans le magasin des autorités de certification racines de confiance, est identique pour tous les périphériques, et fonctionne en tant que CA racine pour le second certificat, qui est généré par périphérique et placé dans le magasin personnel.Exception lors de la tentative de création de certificat non auto-signé

Je ne souhaite pas utiliser makecert, car la création d'un certificat signé affiche une interface utilisateur que je souhaite éviter. En outre, OpenSSL ne peut pas être utilisé en raison de certaines choses liées à la licence (bien que j'ai une solution de travail avec elle). Donc, pour l'instant je travaille avec un petit outil C#, basé sur CertEnroll lib.

Voici comment je crée pfx pour le premier certificat CA racine.

makecert -n "CN=Root CA" -cy authority -r -a sha256 -len 2048 -sv root.pvk root.cer 
pvk2pfx -pvk root.pvk -spc root.cer -pfx root.pfx -pi 123 -po 123 

Pour créer un certificat à partir du code C#, j'ai des questions How to create self-signed certificate programmatically for WCF service? et référencé C# Generate a non self signed client CX509Certificate Request without a CA using the certenroll.dll.

Jusqu'ici, j'ai le code suivant. Méthode de génération de certificat:

/// <summary> 
/// Generates self-signed certificate with specified subject, which will expire after specified timespan. 
/// </summary> 
public X509Certificate2 CreateCertificate(string subjectName, TimeSpan expirationLength, X509Certificate2 issuer = null) 
{ 
    // create DN for subject and issuer 
    var dn = new CX500DistinguishedName(); 
    dn.Encode("CN=" + subjectName); 

    var issuerName = new CX500DistinguishedName(); 
    if(issuer != null) 
    { 
     issuerName.Encode(issuer.Subject); 
    } 

    var privateKey = new CX509PrivateKey 
    { 
     ProviderName = "Microsoft Strong Cryptographic Provider", 
     Length = 2048, 
     KeySpec = X509KeySpec.XCN_AT_KEYEXCHANGE, 
     KeyUsage = X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_DECRYPT_FLAG | 
         X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_KEY_AGREEMENT_FLAG, 
     MachineContext = true, 
     ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG 
    }; 

    privateKey.Create(); 

    // Use the stronger SHA512 hashing algorithm 
    var hashobj = new CObjectId(); 
    hashobj.InitializeFromAlgorithmName(ObjectIdGroupId.XCN_CRYPT_HASH_ALG_OID_GROUP_ID, 
      ObjectIdPublicKeyFlags.XCN_CRYPT_OID_INFO_PUBKEY_ANY, 
      AlgorithmFlags.AlgorithmFlagsNone, "SHA512"); 

    var cert = new CX509CertificateRequestCertificate(); 
    cert.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextMachine, privateKey, ""); 
    cert.Subject = dn; 
    if (issuer != null) 
     cert.Issuer = issuerName; 
    else 
     cert.Issuer = dn; 
    cert.NotBefore = DateTime.Now.Date; 
    cert.NotAfter = cert.NotBefore + expirationLength; 
    cert.HashAlgorithm = hashobj; // Specify the hashing algorithm 

    if(issuer != null) 
    { 
     var signerCertificate = new CSignerCertificate(); 
     signerCertificate.Initialize(true, X509PrivateKeyVerify.VerifyAllowUI, EncodingType.XCN_CRYPT_STRING_HEX, issuer.GetRawCertDataString()); 
     cert.SignerCertificate = signerCertificate; 
    } 
    cert.Encode(); 

    // Do the final enrollment process 
    var enroll = new CX509Enrollment(); 
    enroll.InitializeFromRequest(cert); // load the certificate 
    enroll.CertificateFriendlyName = subjectName; // Optional: add a friendly name 

    var csr = enroll.CreateRequest(); // Output the request in base64 
    // and install it back as the response 
    enroll.InstallResponse(InstallResponseRestrictionFlags.AllowUntrustedCertificate, 
      csr, EncodingType.XCN_CRYPT_STRING_BASE64, ""); // no password 
    // output a base64 encoded PKCS#12 so we can import it back to the .Net security classes 
    var base64encoded = enroll.CreatePFX("", // no password, this is for internal consumption 
      PFXExportOptions.PFXExportChainWithRoot); 

    // instantiate the target class with the PKCS#12 data (and the empty password) 
    return new X509Certificate2(Convert.FromBase64String(base64encoded), "", 
     X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet); 
} 

application simple pour trouver le certificat existant et en créer un nouveau sur cette base:

static void Main(string[] args) 
{ 
    var certificateGenerator = new CertificateGenerator(); 

    X509Certificate2 rootCA; 

    using (var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine)) 
    { 
     store.Open(OpenFlags.ReadWrite); 

     rootCA = store.Certificates.OfType<X509Certificate2>() 
      .FirstOrDefault(c => c.Subject.StartsWith("CN=Root CA", StringComparison.Ordinal)); 

     store.Close(); 
    } 

    if (rootCA == null) 
     throw new Exception("Can't find root CA certificate"); 

    var testCert = certificateGenerator.CreateCertificate("Test", new TimeSpan(3650, 0, 0, 0), rootCA); 
    using (var store = new X509Store(StoreName.My, StoreLocation.LocalMachine)) 
    { 
     store.Open(OpenFlags.ReadWrite); 
     store.Add(testCert); 
     store.Close(); 
    } 
} 

La chose est que cela fonctionne très bien, si je tente de référence du certificat non Autorités de certification racines de confiance, mais en Personal (même si j'ai un mot de passe sur le certificat). Mais si je tente de créer un certificat basé sur le certificat CA de Autorités de certification racine de confiance, je reçois exception sur signerCertificate.Initialize, en disant

Cannot find object or property. 0x80092004 (-2146885628 CRYPT_E_NOT_FOUND) 

Alors, qu'est-ce que je manque?

Répondre

2

ISignerCertificate::Initialize exige que la clé privée soit lié par les demandes ou mon magasin:

https://msdn.microsoft.com/en-us/library/windows/desktop/aa376832(v=vs.85).aspx:

Si une clé privée est nécessaire, seuls les magasins personnels et demande sont recherchés .

Si une clé privée n'est pas nécessaire, les magasins racine et intermédiaire CA sont également recherchés.

de Windows attend que vous ne mettez la partie publique de l'autorité de certification dans le CA (intermédiaire) ou magasins racine/de ThirdPartyRoot, et que si vous êtes l'émetteur vous aurez également installé (avec la clé privée maintenant) dans CurrentUser \ My ou LocalMachine \ My.

+0

Quel est le point dans le certificat (même juste sa partie publique) étant dans le magasin CA, si sa version avec clé privée est déjà dans Mon magasin? Je doute que je puisse l'enlever, car le certificat généré référencera le certificat de l'AC à partir de Mon magasin, pas du magasin racine/AC. – lentinant

+0

@lentinant Ce n'est pas tout à fait comme ça que ça fonctionne. Le magasin "Mon" est utilisé lorsqu'il cherche la clé privée pour générer la signature, mais les informations qu'il écrit pour l'émetteur sont juste des identifiants, pas "il était dans le magasin". Le magasin de CA est juste pour accélérer la recherche dans la construction de la chaîne, et Root/TrustedRoot sont pour les décisions de confiance dans la construction de la chaîne. "Un certificat" n'a pas de magasin, ou ne sait pas ce qu'est un magasin ... les magasins ne servent que des fonctions pendant certains chemins d'exécution de code. – bartonjs

+0

l'a fonctionné.Le certificat CA doit également être placé dans le magasin personnel, comme vous l'avez dit, l'application ne doit pas attribuer elle-même la propriété 'Issuer' - elle est définie automatiquement si la propriété' IssuerCertificate' est assignée avant Encode. Si vous le définissez manuellement, vous obtiendrez très probablement «Windows n'a pas assez d'informations pour vérifier ce certificat» affiché sur les informations du certificat. – lentinant