2013-08-20 22 views
13

私は、ローカルシステムアカウントの下でWindowsサービスとして実行している自己ホストWCFサーバーを持っています。私はメッセージレベルのセキュリティを使用してnet.tcpエンドポイントで使用するためにC#でプログラムで自己署名入りの証明書を作成しようとしています。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.43キロバイトのファイルを見ることができます。プロパティ|セキュリティを見ると、フルコントロールのシステムと管理者の両方が表示されます。

私はこのエラーを調べる際に、プライベートキーの不足または不正なアクセス許可に関する多くの回答を見てきました。私は問題が何かを見ることができません。

本当に変わったことは、mmc証明書プラグインを使用する場合は、証明書に移動して[すべてのタスク|管理秘密鍵...]と同じセキュリティ設定が表示されることです。このダイアログを表示してキャンセルボタンを押すだけで、証明書がWCFで正しく動作するようになりました。サービスを再開するだけで、すべてが完璧に動作します。

MakeCertを使用して証明書を作成すると、最初からうまく動作します。私はそれが何をしているのか分かりません。適切ではないかもしれません情報の

もう一つの作品は、私が入れ得るためにそれを話したところ、証明書だけでなく、マイストアに入れますが、それはまた、「中間証明機関」ストアに入れてしまうことです。なぜか、それが重要かどうかわかりません。

だから私は間違っていますか?

更新:これは単なる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 isオブジェクトが見つかりませんでした。したがって、これは同じ問題であるように見えます。ここでも、証明書の[秘密鍵の管理]ダイアログを開いた後、これも完全に機能します。

私はまだ問題の原因を探しています。

更新#2:私は以下の受け入れられた回答を使用してこれを動作させることができました。興味深いことに、このコードはX509Storeコードを呼び出さずに証明書をMachineストアに置くように見えます。私はまだコードを呼んでいます。なぜなら、わからないし、何も傷つけないからです。ここでは、証明書を作成するために使用している最終的なコードです。

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

1年後、私はついにこの答えをチェックし、それは動作します! PersistKeySetフラグをコンストラクタに追加し、キーが消えないようにしました。元の投稿を更新して作業コードを表示しました。 – MarkR

5

私はこの作業を行うことができませんでしたが、別の解決策が見つかりました。 (2014年12月更新:受け入れられた回答を使用して作業するようになりました)

私が必要とするものを達成するためにPluralSight.Crypto libraryを使用できました。 LocalMachineストアに格納する秘密鍵を取得するには、ソースコードを少し修正する必要がありました。変更はCryptContext.csファイルに行われました。私はCreateSelfSignedCertificateメソッドを変更しました。以下は、私が行った変更を含むコードスニペットです。本質的には、CryptContextオブジェクトのFlagsにこの値が含まれている場合、CryptKeyProviderInformation構造体のFlagsメンバーを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 
     }; 

それから私はこのような自分自身のコード内の関数を使用します。私は0x8のすべきCryptContextオブジェクトのフラグを設定し

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

お知らせ| 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:http: ://www.pinvoke.net/default.aspx/advapi32.cryptacquirecontext – MarkR

3

CodePlex(https://clrsecurity.codeplex.com/)でCLRセキュリティライブラリを使用することもできます。次に、自己署名証明書を作成し、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); 
関連する問題