2012-08-25 18 views
15

最近C#でSSL暗号化サーバー/クライアントを作成しようとしています。クライアントでサーバー認証のサーバー名を特定する方法C#

私はMSDNにthisのチュートリアルに従ってきたが、しかし、それはmakecert.exeを使用して、サーバーとクライアントの使用のために作成する証明書を必要と私は例を発見し、それが正常に証明書を作成しました:

makecert -sr LocalMachine -ss My -n "CN=Test" -sky exchange -sk 123456 c:/Test.cer

が、クライアントは、それがどのようにこれまで私が収集できるようマシン名を使用しています。この場合、私のIPで接続したときに、今の問題は、クライアント用のサーバが起動して待機している:

127.0.0.1

、そしてそれが証明書にサーバ名と一致している必要がありサーバ名Test.cer)が必要です。私は、このような「テスト」「LOCALMACHINE」、「127.0.0.1」のように複数の組み合わせを(試してみましたが、クライアントが接続を許可するように一致させるためにサーバー名を与え得るように見えるカント私が手にエラーがある:。

Certificate error: RemoteCertificateNameMismatch, RemoteCertificateChainErrors Exception: the remote certificate is invalid according to the validation procedure

SslTcpServer:ここ

は、私はそれだけで私はあまりにもアプリでサーバとクライアントのマシン名とサーバー名の証明書パスを割り当てることにMSDNの例と異なる使用しているコードです.cs

using System; 
using System.Collections; 
using System.Net; 
using System.Net.Sockets; 
using System.Net.Security; 
using System.Security.Authentication; 
using System.Text; 
using System.Security.Cryptography.X509Certificates; 
using System.IO; 

namespace Examples.System.Net 
{ 
    public sealed class SslTcpServer 
    { 
     static X509Certificate serverCertificate = null; 
     // The certificate parameter specifies the name of the file 
     // containing the machine certificate. 
     public static void RunServer(string certificate) 
     { 
      serverCertificate = X509Certificate.CreateFromCertFile(certificate); 
      // Create a TCP/IP (IPv4) socket and listen for incoming connections. 
      TcpListener listener = new TcpListener(IPAddress.Any, 8080); 
      listener.Start(); 
      while (true) 
      { 
       Console.WriteLine("Waiting for a client to connect..."); 
       // Application blocks while waiting for an incoming connection. 
       // Type CNTL-C to terminate the server. 
       TcpClient client = listener.AcceptTcpClient(); 
       ProcessClient(client); 
      } 
     } 
     static void ProcessClient(TcpClient client) 
     { 
      // A client has connected. Create the 
      // SslStream using the client's network stream. 
      SslStream sslStream = new SslStream(
       client.GetStream(), false); 
      // Authenticate the server but don't require the client to authenticate. 
      try 
      { 
       sslStream.AuthenticateAsServer(serverCertificate, 
        false, SslProtocols.Tls, true); 
       // Display the properties and settings for the authenticated stream. 
       DisplaySecurityLevel(sslStream); 
       DisplaySecurityServices(sslStream); 
       DisplayCertificateInformation(sslStream); 
       DisplayStreamProperties(sslStream); 

       // Set timeouts for the read and write to 5 seconds. 
       sslStream.ReadTimeout = 5000; 
       sslStream.WriteTimeout = 5000; 
       // Read a message from the client. 
       Console.WriteLine("Waiting for client message..."); 
       string messageData = ReadMessage(sslStream); 
       Console.WriteLine("Received: {0}", messageData); 

       // Write a message to the client. 
       byte[] message = Encoding.UTF8.GetBytes("Hello from the server.<EOF>"); 
       Console.WriteLine("Sending hello message."); 
       sslStream.Write(message); 
      } 
      catch (AuthenticationException e) 
      { 
       Console.WriteLine("Exception: {0}", e.Message); 
       if (e.InnerException != null) 
       { 
        Console.WriteLine("Inner exception: {0}", e.InnerException.Message); 
       } 
       Console.WriteLine("Authentication failed - closing the connection."); 
       sslStream.Close(); 
       client.Close(); 
       return; 
      } 
      finally 
      { 
       // The client stream will be closed with the sslStream 
       // because we specified this behavior when creating 
       // the sslStream. 
       sslStream.Close(); 
       client.Close(); 
      } 
     } 
     static string ReadMessage(SslStream sslStream) 
     { 
      // Read the message sent by the client. 
      // The client signals the end of the message using the 
      // "<EOF>" marker. 
      byte[] buffer = new byte[2048]; 
      StringBuilder messageData = new StringBuilder(); 
      int bytes = -1; 
      do 
      { 
       // Read the client's test message. 
       bytes = sslStream.Read(buffer, 0, buffer.Length); 

       // Use Decoder class to convert from bytes to UTF8 
       // in case a character spans two buffers. 
       Decoder decoder = Encoding.UTF8.GetDecoder(); 
       char[] chars = new char[decoder.GetCharCount(buffer, 0, bytes)]; 
       decoder.GetChars(buffer, 0, bytes, chars, 0); 
       messageData.Append(chars); 
       // Check for EOF or an empty message. 
       if (messageData.ToString().IndexOf("<EOF>") != -1) 
       { 
        break; 
       } 
      } while (bytes != 0); 

      return messageData.ToString(); 
     } 
     static void DisplaySecurityLevel(SslStream stream) 
     { 
      Console.WriteLine("Cipher: {0} strength {1}", stream.CipherAlgorithm, stream.CipherStrength); 
      Console.WriteLine("Hash: {0} strength {1}", stream.HashAlgorithm, stream.HashStrength); 
      Console.WriteLine("Key exchange: {0} strength {1}", stream.KeyExchangeAlgorithm, stream.KeyExchangeStrength); 
      Console.WriteLine("Protocol: {0}", stream.SslProtocol); 
     } 
     static void DisplaySecurityServices(SslStream stream) 
     { 
      Console.WriteLine("Is authenticated: {0} as server? {1}", stream.IsAuthenticated, stream.IsServer); 
      Console.WriteLine("IsSigned: {0}", stream.IsSigned); 
      Console.WriteLine("Is Encrypted: {0}", stream.IsEncrypted); 
     } 
     static void DisplayStreamProperties(SslStream stream) 
     { 
      Console.WriteLine("Can read: {0}, write {1}", stream.CanRead, stream.CanWrite); 
      Console.WriteLine("Can timeout: {0}", stream.CanTimeout); 
     } 
     static void DisplayCertificateInformation(SslStream stream) 
     { 
      Console.WriteLine("Certificate revocation list checked: {0}", stream.CheckCertRevocationStatus); 

      X509Certificate localCertificate = stream.LocalCertificate; 
      if (stream.LocalCertificate != null) 
      { 
       Console.WriteLine("Local cert was issued to {0} and is valid from {1} until {2}.", 
        localCertificate.Subject, 
        localCertificate.GetEffectiveDateString(), 
        localCertificate.GetExpirationDateString()); 
      } 
      else 
      { 
       Console.WriteLine("Local certificate is null."); 
      } 
      // Display the properties of the client's certificate. 
      X509Certificate remoteCertificate = stream.RemoteCertificate; 
      if (stream.RemoteCertificate != null) 
      { 
       Console.WriteLine("Remote cert was issued to {0} and is valid from {1} until {2}.", 
        remoteCertificate.Subject, 
        remoteCertificate.GetEffectiveDateString(), 
        remoteCertificate.GetExpirationDateString()); 
      } 
      else 
      { 
       Console.WriteLine("Remote certificate is null."); 
      } 
     } 
     public static void Main(string[] args) 
     { 
      string certificate = "c:/Test.cer"; 
      SslTcpServer.RunServer(certificate); 
     } 
    } 
} 

SslTcpClient.cs

using System; 
using System.Collections; 
using System.Net; 
using System.Net.Security; 
using System.Net.Sockets; 
using System.Security.Authentication; 
using System.Text; 
using System.Security.Cryptography.X509Certificates; 
using System.IO; 

namespace Examples.System.Net 
{ 
    public class SslTcpClient 
    { 
     private static Hashtable certificateErrors = new Hashtable(); 

     // The following method is invoked by the RemoteCertificateValidationDelegate. 
     public static bool ValidateServerCertificate(
       object sender, 
       X509Certificate certificate, 
       X509Chain chain, 
       SslPolicyErrors sslPolicyErrors) 
     { 
      if (sslPolicyErrors == SslPolicyErrors.None) 
       return true; 

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

      // Do not allow this client to communicate with unauthenticated servers. 
      return false; 
     } 
     public static void RunClient(string machineName, string serverName) 
     { 
      // Create a TCP/IP client socket. 
      // machineName is the host running the server application. 
      TcpClient client = new TcpClient(machineName, 8080); 
      Console.WriteLine("Client connected."); 
      // Create an SSL stream that will close the client's stream. 
      SslStream sslStream = new SslStream(
       client.GetStream(), 
       false, 
       new RemoteCertificateValidationCallback(ValidateServerCertificate), 
       null 
       ); 
      // The server name must match the name on the server certificate. 
      try 
      { 
       sslStream.AuthenticateAsClient(serverName); 
      } 
      catch (AuthenticationException e) 
      { 
       Console.WriteLine("Exception: {0}", e.Message); 
       if (e.InnerException != null) 
       { 
        Console.WriteLine("Inner exception: {0}", e.InnerException.Message); 
       } 
       Console.WriteLine("Authentication failed - closing the connection."); 
       client.Close(); 
       return; 
      } 
      // Encode a test message into a byte array. 
      // Signal the end of the message using the "<EOF>". 
      byte[] messsage = Encoding.UTF8.GetBytes("Hello from the client.<EOF>"); 
      // Send hello message to the server. 
      sslStream.Write(messsage); 
      sslStream.Flush(); 
      // Read message from the server. 
      string serverMessage = ReadMessage(sslStream); 
      Console.WriteLine("Server says: {0}", serverMessage); 
      // Close the client connection. 
      client.Close(); 
      Console.WriteLine("Client closed."); 
     } 
     static string ReadMessage(SslStream sslStream) 
     { 
      // Read the message sent by the server. 
      // The end of the message is signaled using the 
      // "<EOF>" marker. 
      byte[] buffer = new byte[2048]; 
      StringBuilder messageData = new StringBuilder(); 
      int bytes = -1; 
      do 
      { 
       bytes = sslStream.Read(buffer, 0, buffer.Length); 

       // Use Decoder class to convert from bytes to UTF8 
       // in case a character spans two buffers. 
       Decoder decoder = Encoding.UTF8.GetDecoder(); 
       char[] chars = new char[decoder.GetCharCount(buffer, 0, bytes)]; 
       decoder.GetChars(buffer, 0, bytes, chars, 0); 
       messageData.Append(chars); 
       // Check for EOF. 
       if (messageData.ToString().IndexOf("<EOF>") != -1) 
       { 
        break; 
       } 
      } while (bytes != 0); 

      return messageData.ToString(); 
     } 
     public static void Main(string[] args) 
     { 
      string serverCertificateName = null; 
      string machineName = null; 
      /* 
      // User can specify the machine name and server name. 
      // Server name must match the name on the server's certificate. 
      machineName = args[0]; 
      if (args.Length < 2) 
      { 
       serverCertificateName = machineName; 
      } 
      else 
      { 
       serverCertificateName = args[1]; 
      }*/ 
      machineName = "127.0.0.1"; 
      serverCertificateName = "David-PC";// tried Test, LocalMachine and 127.0.0.1 
      SslTcpClient.RunClient(machineName, serverCertificateName); 
      Console.ReadKey(); 
     } 
    } 
} 

EDIT:

メッセージを送信するためにクライアントを待っている間に、サーバはそれが出倍のクライアント接続と、すべてを受け入れますが。回答Iによる

:ちょうど

UPDATEを明確にするだけでなく、その上に自分の考えthatsの(クライアントが文句を言わない私がクライアントに提供さ1異なっているため、証明書内のサーバー名にサーバーを使用して認証します)今、私は例外を取得

makecert -sr LocalMachine -ss My -n "CN=localhost" -sky exchange -sk 123456 c:/Test.cer and in my client I have:

 machineName = "127.0.0.1"; 
     serverCertificateName = "localhost";// tried Test, LocalMachine and 127.0.0.1 
     SslTcpClient.RunClient(machineName, serverCertificateName); 

RemoteCertificateChainErrors Exception: the remote certificate is invalid according to the validation procedure

にcertficiateメーカーを変更しましたここで発生している

// The server name must match the name on the server certificate. 
      try 
      { 
       sslStream.AuthenticateAsClient(serverName); 
      } 
      catch (AuthenticationException e) 
      { 

       Console.WriteLine("Exception: {0}", e.Message); 
       if (e.InnerException != null) 
       { 
        Console.WriteLine("Inner exception: {0}", e.InnerException.Message); 
       } 
       Console.WriteLine("Authentication failed - closing the connection. "+ e.Message); 
       client.Close(); 
       return; 
      } 
+0

あなたはクライアントの証明書を使用していますか?後者のコードスニペットの 'serverName'の値は何ですか?また、 'sslPolicyErrors'の値をクライアントの検証メソッドに投稿してください。 –

答えて

9

に答えがSslStream.AuthenticateAsClient Method備考セクションにあります。

The value specified for targetHost must match the name on the server's certificate.

あなたは対象の証明書は、「CN = localhost」をあるサーバーに使用する場合は、あなたとAuthenticateAsClientを呼び出す必要がありますtargetHostパラメータとして "localhost"を指定すると、クライアント側で正常に認証されます。証明書サブジェクトとして "CN = David-PC"を使用する場合は、 "David-PC"をtargetHostとしてAuthenticateAsClientを呼び出す必要があります。 SslStreamは、接続しようとする(およびAuthenticateAsClientに渡す)サーバー名を、サーバーから受信した証明書のサブジェクトと照合することによってサーバーIDをチェックします。実際には、サーバーを実行するマシン名が証明書のサブジェクトの名前と一致し、クライアントでは、接続を開くために使用したのと同じホスト名をAuthenticateAsClientに渡します(この場合はTcpClientを使用します)。

ただし、サーバーとクライアントの間でSSL接続を確立するための条件は他にもあります。AuthenticateAsServerに渡される証明書には秘密鍵が必要です。クライアントマシンで信頼されている必要があります。 SSLセッションを確立する。

コードサンプルに関連する問題は、証明書の生成と使用に関連しています。

  • あなたはあなたの証明書と、それが信頼することはできません。この方法で発行者を提供していない - これはRemoteCertificateChainErrors例外の原因です。 makecertの-rオプションを指定して開発目的で自己署名証明書を作成することをお勧めします。

  • 信頼できる証明書は、自己署名され、Windows証明書ストアの信頼できる場所に置かれているか、既に信頼されている認証局に署名のチェーンでリンクされている必要があります。そのため、個人ストアに証明書を置く-ss Myオプションの代わりに、Trusted Root Certification Authorityに置く-ss rootを使用し、それがあなたのマシン上で信頼されます(あなたのクライアントが動作していると仮定したコードからサーバーと同じマシン上にあり、証明書も生成されます)。

  • makecertに出力ファイルを指定すると、証明書を.cerとしてエクスポートしますが、この形式にはSSL接続を確立するためにサーバーが必要とする秘密キーではなく、公開キーのみが含まれます。最も簡単な方法は、サーバーコードのWindows証明書ストアから証明書を読み取ることです。 (ここに記載されている秘密鍵を格納できる別の形式でストアからエクスポートして、Export a certificate with the private keyとそのファイルをサーバーコードで読み取ることもできます)。

あなたがここにCertificate Creation Tool (Makecert.exe)あなたのコードが実行される次の変更を必要とする結論

使用makecertオプションの詳細を見つけることができます(最新のコード更新でテスト):

  • 使用以下証明書を生成するコマンド:

makecert -sr LocalMachine -ss root -r -n "CN=localhost" -sky exchange -sk 123456

  • (この例の簡略化のために)ファイルの代わりにWindowsの証明書ストアから証明書を読んで、そうでサーバーコードで

serverCertificate = X509Certificate.CreateFromCertFile(certificate);

を置き換える:

X509Store store = new X509Store(StoreName.Root, StoreLocation.LocalMachine); 
store.Open(OpenFlags.ReadOnly); 
var certificates = store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, "CN=localhost", false); 
store.Close(); 

if (certificates.Count == 0) 
{ 
    Console.WriteLine("Server certificate not found..."); 
    return; 
} 
else 
{ 
    serverCertificate = certificates[0]; 
} 

に覚えておいてください後でコードを変更する場合は、 "CN = localhost"を使用する証明書の件名に置き換えます(この場合、makecertに渡される-nオプションと同じ値にする必要があります)。また、サーバー証明書の件名にlocalhostではなくサーバーを実行するマシン名を使用することを検討してください。

+1

+1このように多くのこの問題を解決していただきありがとうございます:) –

+0

喜んでされて助け:) –

5

サーバ証明書のCNは、サーバーのドメイン名とまったく同じでなければなりません。あなたの場合、共通名は "localhost"(引用符なし)でなければならないと思います。

重要:確かに、他の回答をお読みになったことがありますので、本番環境ではCN="localhost"を使用しないでください。

+0

@DavidKroukamp、あなたは私の最後のコメントを見たことがないかもしれません。あなたは答えを出すことができますか? –

1

これをWCFで使用するには、まず自己署名入りのルート権限証明書を作成し、それを使用してlocalhostの証明書を作成する必要があります。

私はあなたのプロジェクトにも同様のことが当てはまると考えています。詳細はHow to: Create Temporary Certificates for Use During Developmentをご覧ください。

1

試しましたか?それは単一のサイトだと場合

example.netのような完全なドメイン名の証明書を作成したり、ライブ使用に使用される名前(それは意図的ではありません何も本当の名前をexample.netexample.comまたはexample.orgを使用することが良いことだ)君それが何であるかを知る。

ホストファイルを更新して、その名前に127.0.0.1を使用するようにします。

4

まず、件名「CN = localhost」または同等の証明書を作成しないでください。それは決して生産で使われることはないので、それをしないでください。常にコンピュータのホスト名に発行してください。 CN = "mycomputer"で、ローカルホストではなくホスト名を使用します。 「サブジェクト代替名」拡張子を使用して複数の名前を指定できますが、makecertはサポートされていません。

第2に、サーバーSSL証明書の発行時に、証明書の拡張キー使用(EKU)拡張に「サーバー認証」OIDを追加する必要があります。あなたの例では、-eku 1.3.6.1.5.5.7.3.1パラメータをmakecertに追加してください。クライアント証明書認証を実行する場合は、1.3.6.1.5.5.7.3.2の「クライアント認証」OIDを使用します。

最後に、makecertによって作成されるデフォルトの証明書は、ハッシュアルゴリズムとしてMD5を使用します。 MD5は安全ではないと考えられていますが、テストには影響しませんが、SHA1を使用する習慣を身につけてください。上のパラメータ-a sha1makecertに追加して、SHA1を強制します。デフォルトのキーサイズも1024ビットから2048ビットに増やす必要がありますが、あなたはそのアイディアを得ています。あなたのアップデートに関しては

+0

私が知る限り、sha1も今日は安全ではありません...AFAIKが短く、かつ/または知られているものを使用している「弱いキー」について不満を訴え始めているので、鍵長も重要であることを強調することが重要です。壊れたハッシュアルゴリズム – Luke

+1

@あなたは正しいが、古いバージョンのWindows(XPおよび2003)はSHA256(またはそれ以上)を使用した証明書をサポートしていない。これが問題かどうかは、顧客によって異なります。 – akton

+0

右...そこにかなりの混乱があります! 私が覚えている限り、数ヶ月前に、おそらく、システムに新しいハッシュアルゴリズムをサポートする方法を「システムに知らせる」方法を見つけたかもしれませんが、それを行うのはかなり厄介な方法でした。 .. さらに、makecert.exeファイルのいくつかのバージョンがあり、古いものはsha256パラメータをまったく受け入れていませんでした。私はdevのPC上の様々なVS、SDKとシステムフォルダの中で新しいものを見つける必要があった... – Luke

関連する問題