2013-08-20 71 views
13

我有一個在本地系統帳戶下作爲Windows服務運行的自託管WCF服務器。我正在嘗試在c#中以編程方式創建自簽名證書,以便與使用消息級安全性的net.tcp端點一起使用。如何以編程方式爲WCF服務創建自簽名證書?

我正在使用以下代碼,它非常接近於How to create a self-signed certificate using C#?中接受的答案,並嘗試解決我的問題。

public static X509Certificate2 CreateSelfSignedCertificate(string subjectName, TimeSpan expirationLength) 
{ 
    // create DN for subject and issuer 
    var dn = new CX500DistinguishedName(); 
    dn.Encode("CN=" + subjectName, X500NameFlags.XCN_CERT_NAME_STR_NONE); 

    CX509PrivateKey privateKey = new CX509PrivateKey(); 
    privateKey.ProviderName = "Microsoft Strong Cryptographic Provider"; 
    privateKey.Length = 1024; 
    privateKey.KeySpec = X509KeySpec.XCN_AT_KEYEXCHANGE; 
    privateKey.KeyUsage = X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_DECRYPT_FLAG | X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_KEY_AGREEMENT_FLAG; 
    privateKey.MachineContext = true; 
    privateKey.ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_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, "SHA1"); 

    // Create the self signing request 
    var cert = new CX509CertificateRequestCertificate(); 
    cert.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextMachine, privateKey, ""); 
    cert.Subject = dn; 
    cert.Issuer = dn; // the issuer and the subject are the same 
    cert.NotBefore = DateTime.Now.Date; 
    // this cert expires immediately. Change to whatever makes sense for you 
    cert.NotAfter = cert.NotBefore + expirationLength; 
    //cert.X509Extensions.Add((CX509Extension)eku); // add the EKU 
    cert.HashAlgorithm = hashobj; // Specify the hashing algorithm 
    cert.Encode(); // encode the certificate 

    // Do the final enrollment process 
    var enroll = new CX509Enrollment(); 
    enroll.InitializeFromRequest(cert); // load the certificate 
    enroll.CertificateFriendlyName = subjectName; // Optional: add a friendly name 
    string 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 System.Security.Cryptography.X509Certificates.X509Certificate2(
     System.Convert.FromBase64String(base64encoded), "", 
     // mark the private key as exportable (this is usually what you want to do) 
     // mark private key to go into the Machine store instead of the current users store 
     X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet 
    ); 
} 

,我又把它存儲與此代碼:

X509Store store = new X509Store(storeName, StoreLocation.LocalMachine); 
store.Open(OpenFlags.ReadWrite); 
store.Add(newCert); 
store.Close(); 

這將創建證書,並把它在LOCALMACHINE證書存儲區。問題是,當我嘗試啓動WCF服務時,出現以下異常:

證書'CN = myCertificate'可能沒有可進行密鑰交換的私鑰,或者該進程可能沒有私鑰的訪問權限。詳情請參閱內部例外。 內部異常:鍵集不存在

我的證書FindPrivateKey樣品(http://msdn.microsoft.com/en-us/library/aa717039%28v=vs.100%29.aspx)的輸出是:

Private key directory: 
C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys 
Private key file name: 
f0d47c7826b8ef5148b6d412f1c40024_4a8a026f-58e4-40f7-b779-3ae9b6aae1a7 

我可以看到在資源管理器這個1.43KB文件。如果我查看屬性|安全性,我將看到SYSTEM和Administrators都完全控制。

在研究這個錯誤我見過許多關於私鑰丟失或權限不正確的答案。我看不出有什麼問題。

真奇怪的是,如果我使用mmc證書插件,請轉到證書並選擇所有任務|管理私鑰......我看到相同的安全設置。即使我彈出對話框並點擊「取消」按鈕,在查看完這些後,證書現在可以在WCF中正常工作。我可以簡單地重新啓動服務,一切都運行完美。

如果我使用MakeCert創建證書,它從一開始就工作得很好。我不知道它有什麼不同。

另一個可能不相關的信息是,該證書不僅被放到My Store中,我告訴它放進去,但它也被放入「中級認證機構」商店。我不知道爲什麼或者是否重要。

所以......任何想法我做錯了什麼?

更新:好吧,這不僅僅是一個WCF問題。當我嘗試使用HttpSetServiceConfiguration使用證書綁定到使用http.sys的端點時,我基本上遇到同樣的問題。該方法返回1312 - 「指定的登錄會話不存在,它可能已被終止」。這實際上不是真正的錯誤。我在安全事件日誌看到的審計失敗是這樣說:

Cryptographic Parameters: 
    Provider Name: Microsoft Software Key Storage Provider 
    Algorithm Name: Not Available. 
    Key Name: {A23712D0-9A7B-4377-89DB-B1B39E3DA8B5} 
    Key Type: Machine key. 

Cryptographic Operation: 
    Operation: Open Key. 
    Return Code: 0x80090011 

0x80090011是未找到對象。所以這似乎是同樣的問題。再次,我打開證書的管理私鑰對話框後,這也很完美。

我仍在尋找問題的原因。

更新#2:我能夠使用下面接受的答案得到這個工作。有趣的是,這段代碼現在似乎將證書放入機器存儲中,而不調用X509Store代碼。我仍然稱這些代碼是因爲我不確定,它不會傷害任何東西。這是我用來創建證書的最終代碼。

static public X509Certificate2 CreateSelfSignedCertificate(string subjectName, TimeSpan expirationLength) 
    { 
     // create DN for subject and issuer 
     var dn = new CX500DistinguishedName(); 
     dn.Encode("CN=" + subjectName, X500NameFlags.XCN_CERT_NAME_STR_NONE); 

     CX509PrivateKey privateKey = new CX509PrivateKey(); 
     privateKey.ProviderName = "Microsoft Strong Cryptographic Provider"; 
     privateKey.Length = 2048; 
     privateKey.KeySpec = X509KeySpec.XCN_AT_KEYEXCHANGE; 
     privateKey.KeyUsage = X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_DECRYPT_FLAG | X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_KEY_AGREEMENT_FLAG; 
     privateKey.MachineContext = true; 
     privateKey.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"); 

     // Create the self signing request 
     var cert = new CX509CertificateRequestCertificate(); 
     cert.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextMachine, privateKey, ""); 
     cert.Subject = dn; 
     cert.Issuer = dn; // the issuer and the subject are the same 
     cert.NotBefore = DateTime.Now.Date; 
     // this cert expires immediately. Change to whatever makes sense for you 
     cert.NotAfter = cert.NotBefore + expirationLength; 
     cert.HashAlgorithm = hashobj; // Specify the hashing algorithm 
     cert.Encode(); // encode the certificate 

     // Do the final enrollment process 
     var enroll = new CX509Enrollment(); 
     enroll.InitializeFromRequest(cert); // load the certificate 
     enroll.CertificateFriendlyName = subjectName; // Optional: add a friendly name 
     string 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 System.Security.Cryptography.X509Certificates.X509Certificate2(
      System.Convert.FromBase64String(base64encoded), "", 
      // mark the private key as exportable (this is usually what you want to do) 
      // mark private key to go into the Machine store instead of the current users store 
      X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet 
     ); 
    } 
+0

有誰知道如何通過友好名稱加載現有證書? –

+0

感謝您提供更新..我有一個類似的問題,無法確定我做錯了什麼,你幫助我出了很多這篇文章! :) – Spyral

回答

5

我在PowerShell中使用等效代碼時遇到了同樣的問題。看來私人密鑰有時會消失。我使用了進程監視器,您可以看到要刪除的密鑰文件。

我解決這個問題的方法是將X509KeyStorageFlags.PersistKeySet添加到X509Certificate2構造函數中。

+0

謝謝你的回答。我有一個工作解決方案,我不喜歡改變它。我現在沒有時間來測試這個答案,但是如果我這樣做,我會回來並將其標記爲公認的答案。 – MarkR

+0

一年後,我終於檢查出這個答案,它的工作原理!我將PersistKeySet標誌添加到構造函數中,並且鍵不會消失。我更新了我的原始帖子以顯示工作代碼。 – MarkR

5

我無法做這項工作,但我找到了一個替代解決方案。 (2014年12月更新:我現在已經使用已接受的答案開始工作。)

我能夠使用PluralSight.Crypto library來實現我所需的功能。我不得不稍微修改源代碼以獲取私鑰存儲在LocalMachine存儲中。我所做的更改是CryptContext.cs文件。我改變了CreateSelfSignedCertificate方法。以下是一段代碼,包括我所做的更改。實質上,如果CryptContext對象在其標誌中包含此值,我將CryptKeyProviderInformation結構的標誌成員設置爲0x20(CRYPT_MACHINE_KEYSET)。

 byte[] asnName = properties.Name.RawData; 
     GCHandle asnNameHandle = GCHandle.Alloc(asnName, GCHandleType.Pinned); 

     int flags = 0;     // New code 
     if ((this.Flags & 0x20) == 0x20) // New code 
      flags = 0x20;     // New code 

     var kpi = new Win32Native.CryptKeyProviderInformation 
     { 
      ContainerName = this.ContainerName, 
      KeySpec = (int)KeyType.Exchange, 
      ProviderType = 1, // default RSA Full provider 
      Flags = flags     // New code 
     }; 

然後,我使用的功能在我自己的代碼是這樣的:

 using (Pluralsight.Crypto.CryptContext ctx = new Pluralsight.Crypto.CryptContext()) { 

      ctx.Flags = 0x8 | 0x20; 
      ctx.Open(); 

      X509Certificate2 cert = ctx.CreateSelfSignedCertificate(
       new Pluralsight.Crypto.SelfSignedCertProperties 
       { 
        IsPrivateKeyExportable = true, 
        KeyBitLength = 4096, 
        Name = new X500DistinguishedName("CN=" + subjectName), 
        ValidFrom = DateTime.Today, 
        ValidTo = DateTime.Today + expirationLength, 
       }); 

      return cert; 
     } 

請注意,我設置的標誌爲CryptContext對象是0x8中| 0x20(CRYPT_NEWKEYSET | CRYPT_MACHINE_KEYSET)。

我希望我能弄清楚我原來的解決方案出了什麼問題。但我需要一些工作,並在測試中這個解決方案做我需要的。我希望它能幫助別人。

+0

我在哪裏可以找到這些CryptContext標誌?我找不到任何文檔。 –

+0

您可以在這裏找到Flags的文檔:http://msdn.microsoft.com/en-us/library/windows/desktop/aa379886%28v=vs.85%29.aspx和數字標誌值在這裏:http ://www.pinvoke.net/default.aspx/advapi32.cryptacquirecontext – MarkR

3

您還可以使用CodePlex上的CLR安全庫(https://clrsecurity.codeplex.com/)。以下是創建自簽名證書的示例代碼,並使用SSLStream進行測試。

 var machineName = Environment.MachineName; 
     var keyCreationParameters = new CngKeyCreationParameters(); 
     keyCreationParameters.KeyUsage = CngKeyUsages.AllUsages; 
     keyCreationParameters.KeyCreationOptions = CngKeyCreationOptions.OverwriteExistingKey; 
     keyCreationParameters.Parameters.Add(new CngProperty("Length", BitConverter.GetBytes(4096), CngPropertyOptions.None)); 
     var cngKey = CngKey.Create(CngAlgorithm2.Rsa, "Test", keyCreationParameters); 

     var x500DistinguishedName = new X500DistinguishedName("CN=" + machineName); 
     x500DistinguishedName.Oid.Value = "1.3.6.1.5.5.7.3.1"; 
     var certificateCreationParameters = new X509CertificateCreationParameters(x500DistinguishedName); 
     certificateCreationParameters.SignatureAlgorithm = X509CertificateSignatureAlgorithm.RsaSha512; 
     certificateCreationParameters.TakeOwnershipOfKey = true; 
     certificateCreationParameters.CertificateCreationOptions = X509CertificateCreationOptions.None; 
     certificateCreationParameters.EndTime = new DateTime(9999, 12,31, 23, 59, 59, 999, DateTimeKind.Utc); 
     var certificate = cngKey.CreateSelfSignedCertificate(certificateCreationParameters); 

     var certificateStore = new X509Store(StoreName.Root, StoreLocation.CurrentUser); 
     certificateStore.Open(OpenFlags.ReadWrite); 
     certificateStore.Add(certificate); 
     certificateStore.Close(); 


     var tcpListener = TcpListener.Create(6666); 
     tcpListener.Start(); 
     var client = new TcpClient("localhost", 6666); 
     var acceptedClient = tcpListener.AcceptTcpClient(); 
     var acceptedClinetSslStream = new SslStream(
      acceptedClient.GetStream(), false); 
     var serverAuthTask = acceptedClinetSslStream.AuthenticateAsServerAsync(certificate, 
          false, SslProtocols.Tls, true); 

     SslStream clientSslStream = new SslStream(
      client.GetStream(), 
      false, 
      delegate(object o, X509Certificate x509Certificate, X509Chain chain, SslPolicyErrors errors) 
       { 
        if (errors == SslPolicyErrors.None) 
         return true; 

        Console.WriteLine("Certificate error: {0}", errors); 

        // Do not allow this client to communicate with unauthenticated servers. 
        return false; 
       }, 
      null); 
     var clientAuthTask = clientSslStream.AuthenticateAsClientAsync(machineName); 

     Task.WaitAll(serverAuthTask, clientAuthTask);