2012-08-25 14 views
15

Ich habe kürzlich versucht, einen SSL-verschlüsselten Server/Client in C# zu machen.Wie identifiziere ich meinen Server-Namen für die Server-Authentifizierung durch Client in C#

ich this Tutorial auf MSDN gefolgt sind jedoch erforderlich, ein Zertifikat für den Server und Client-Nutzung unter Verwendung erstellt werden makecert.exe so fand ich ein Beispiel und es erstellt das Zertifikat fein:

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

aber jetzt ist das Problem der Server gestartet und wartet auf Kunden, wenn der Kunde es verwendet die Maschinennamen verbindet, das so weit wie ich meine IP in diesem Fall sammeln kann:

127.0.0.1

, und dann bedarf es der Server Name, die den Server Name auf dem Zertifikat übereinstimmen müssen (Test.cer). Ich habe mehrere Kombinationen (wie „Test“ „Localmachine“, „127.0.0.1“ ausprobiert, aber kann nicht scheinen, um die Kunden Servernamen gegeben bekommen somit passen die Verbindung erlaubt, die Fehler, die ich bekommen ist.

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

hier ist der Code ich verwende es nur in der Tatsache, von dem MSDN Beispiel unterscheidet sich, dass ich das Zertifikat Pfad für den Server in der App und dem Maschinennamen und den Servernamen des Clients zu vergeben:

SslTcpServer .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:

Der Server nimmt die Kunden-Verbindung und alles, aber es mal aus, während für die Kunden warten, um eine Nachricht zu senden. (Der Client wird nicht mit dem Server aufgrund des Servernamen im Zertifikat authentifiziert verschieden von dem, die ich in dem Client geliefert) auch das ist meine Gedanken über ihnen nur

UPDATE zu klären:

Nach einer Antwort, die ich haben die certficiate Hersteller geändert:

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

jetzt bekomme ich die Ausnahme:

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

die hier vorkommenden:

// 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

Verwenden Sie ein Zertifikat für den Client? Was ist der Wert von 'serverName' im späteren Code-Snippet? Bitte geben Sie auch den Wert von 'sslPolicyErrors' in der Validierungsmethode des Clients an. –

Antwort

9

Die Antwort kann auf SslStream.AuthenticateAsClient Method Bemerkungen Abschnitt:.

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

Wenn Sie für den Server ein Zertifikat verwenden, die Gegenstand ist, ist „CN = localhost“, müssen Sie AuthenticateAsClient rufen mit "localhost" als targetHost-Parameter, um ihn auf der Clientseite erfolgreich zu authentifizieren. Wenn Sie "CN = David-PC" als Zertifikat-Subjekt verwenden würden, müssen Sie AuthenticateAsClient mit "David-PC" als targetHost aufrufen. SslStream überprüft die Serveridentität, indem der Servername, den Sie verbinden möchten (und den Sie an AuthenticateAsClient übergeben), mit dem Subjekt in dem Zertifikat übereinstimmt, das vom Server empfangen wurde. In der Praxis entspricht der Computername, der den Server ausführt, dem Namen des Betreffs des Zertifikats. Im Client übergeben Sie denselben Hostnamen an AuthenticateAsClient, den Sie zum Öffnen der Verbindung verwendet haben (in diesem Fall mit TcpClient).

Es gibt jedoch andere Bedingungen, um eine SSL-Verbindung zwischen Servern und Clients erfolgreich herzustellen: Das an AuthenticateAsServer übermittelte Zertifikat muss einen privaten Schlüssel haben, es muss auf dem Clientcomputer vertrauenswürdig sein und darf keine Nutzungseinschränkungen für die Verwendung haben SSL-Sitzungen einrichten.

Jetzt mit Ihrem Codebeispiel verwandt, hängt Ihr Problem mit der Generierung und Verwendung des Zertifikats zusammen.

  • Sie sind nicht ein Emittent für das Zertifikat und auf diese Weise die Bereitstellung es nicht vertraut werden kann - dies ist die Ursache für die RemoteCertificateChainErrors Ausnahme. Ich schlage vor, ein selbstsigniertes Zertifikat für Entwicklungszwecke zu erstellen, das die Option -r von makecert angibt.

  • Damit ein Zertifikat vertrauenswürdig ist, muss es entweder selbstsigniert sein und an einem vertrauenswürdigen Speicherort im Windows-Zertifikatspeicher abgelegt sein oder mit einer Kette von Signaturen mit einer bereits vertrauenswürdigen Zertifizierungsstelle verknüpft sein. Anstatt der Option -ss My, die das Zertifikat in den persönlichen Speicher legt, verwenden Sie das -ss-Stammverzeichnis, das es in die vertrauenswürdigen Stammzertifizierungsstellen stellt, und es wird auf Ihrem Computer vertrauenswürdig sein (aus dem Code gehe ich davon aus, dass Ihr Client ausgeführt wird auf der gleichen Maschine mit dem Server und auch das Zertifikat wird darauf generiert).

  • Wenn Sie eine Ausgabedatei für makecert angeben, wird das Zertifikat als .cer exportiert. Dieses Format enthält jedoch nur den öffentlichen Schlüssel und nicht den privaten Schlüssel, der vom Server zum Herstellen einer SSL-Verbindung benötigt wird. Der einfachste Weg ist, das Zertifikat aus dem Windows-Zertifikatspeicher im Servercode zu lesen. (Sie können es auch aus dem Speicher in einem anderen Format exportieren, das das Speichern des privaten Schlüssels ermöglicht, wie hier beschrieben Export a certificate with the private key und lesen Sie diese Datei im Servercode).

Sie können hier Informationen über die makecert Optionen finden Certificate Creation Tool (Makecert.exe) verwendet

Abschließend Code die folgenden Änderungen muss laufen (mit dem neuesten Code-Updates getestet):

  • Verwenden Sie den folgenden befehlen, das Zertifikat zu generieren:

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

  • das Zertifikat aus dem Windows Certificate Store Lesen Sie statt einer Datei (für die Einfachheit dieses Beispiel), so mit

serverCertificate = X509Certificate.CreateFromCertFile(certificate);

im Server-Code ersetzt werden:

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

Bitte denken Sie daran, Ersetzen Sie "CN = localhost" durch den Betreff des Zertifikats, das Sie verwenden möchten, wenn Sie den Code später ändern (in diesem Fall sollte derselbe Wert wie die an makecert übergebene Option -n sein). Berücksichtigen Sie außerdem, den Computername zu verwenden, der den Server statt Localhost in dem Betreff des Serverzertifikats ausführt.

+1

+1 Vielen Dank, das hat das Problem gelöst :) –

+0

Ich bin froh, dass ich geholfen habe :) –

5

ein CN-Server-Zertifikat genau wie der Domain-Name des Servers gleich sein muss. Ich nehme an, in Ihrem Fall muss der allgemeine Name "localhost" sein (ohne Anführungszeichen).

Wichtig: Wie Sie vielleicht in anderen Antworten gelesen haben, verwenden Sie niemals CN="localhost" in der Produktion.

+0

@DavidKroukamp, ​​Sie haben vielleicht meinen letzten Kommentar nicht gesehen. Kannst du die Antworten geben? –

1

Damit dies mit WCF funktioniert, müssen Sie zuerst ein selbstsigniertes Root-Autoritätszertifikat erstellen und es dann zum Erstellen des Zertifikats für localhost verwenden.

Ich denke das gleiche könnte auch für Ihr Projekt gelten, bitte sehen Sie sich diesen Artikel How to: Create Temporary Certificates for Use During Development für Details.

1

Haben Sie versucht :?

Erstellen Sie das Zertifikat für einen vollständigen Domain-Namen wie example.net (es ist gut example.net zu verwenden, example.com oder example.org für alles, was absichtlich kein richtiger Name) oder den Namen, die in den Live-Einsatz verwendet werden, wenn, dass eine einzelne Website und Sie weiß, was es sein wird.

Aktualisieren Sie Ihre Hosts-Datei, so dass 127.0.0.1 für diesen Namen verwendet wird.

4

Zuerst erstellen Sie kein Zertifikat mit dem Betreff "CN = localhost" oder gleichwertig. Es wird nie in der Produktion verwendet werden, also tu es nicht. Geben Sie es immer an den Hostnamen Ihres Computers aus, z. CN = "mycomputer", und verwenden Sie den Host-Namen, wenn Sie sich mit ihm verbinden und nicht mit localhost. Sie können mehrere Namen mit der Erweiterung "Subject alternate name" angeben, aber makecert scheint dies nicht zu unterstützen.

Zweitens müssen Sie beim Ausstellen eines Server-SSL-Zertifikats die OID "server authentication" (Server-Authentifizierung) zu der erweiterten Schlüsselverwendung (EKU) des Zertifikats hinzufügen. Fügen Sie in Ihrem Beispiel den Parameter -eku 1.3.6.1.5.5.7.3.1 zu makecert hinzu. Wenn Sie die Clientzertifikatauthentifizierung durchführen möchten, verwenden Sie die OID "Clientauthentifizierung" von 1.3.6.1.5.5.7.3.2.

Schließlich verwendet das von makecert erstellte Standardzertifikat MD5 als Hashalgorithmus. MD5 gilt als unsicher und obwohl es Ihre Tests nicht beeinträchtigt, sollten Sie sich angewöhnen, SHA1 zu verwenden. Fügen Sie -a sha1 zu den obigen makecert Parametern hinzu, um SHA1 zu erzwingen. Die Standard-Schlüsselgröße sollte ebenfalls von 1024 Bits auf 2048 Bits erhöht werden, aber Sie erhalten die Idee.

+0

Soweit ich weiß, ist sha1 heutzutage auch nicht mehr so ​​sicher ...besser versuchen mit -a sha256 Darüber hinaus ist es wichtig zu betonen, dass auch Keylänge ist wichtig, da einige Browser (Chrom?) begann sich über "schwache Schlüssel" zu beschweren -> das ist AFAIK kurz und/oder mit bekannten be-broken Hash-Algorithmen – Luke

+1

@Luke Sie sind korrekt, aber ältere Versionen von Windows (XP und 2003) unterstützen keine Zertifikate mit SHA256 (oder besser). Ob dies ein Problem ist, hängt vom Kunden ab. – akton

+0

Richtig ... da drüben ist ziemlich viel Chaos! Soweit ich mich erinnere, habe ich vor einigen Monaten wahrscheinlich einen Weg gefunden, "das System wissen zu lassen", wie man die neueren Hashing-Algorithmen auf diesen Systemen unterstützt, aber es war eine ziemlich peinliche Art und Weise ... und ein Albtraum zu implementieren .. Außerdem gibt es mehrere Versionen der Datei makecert.exe, und die älteren akzeptierten den Parameter sha256 überhaupt nicht. Ich musste die neueren unter den verschiedenen VS-, SDK- und Systemordnern auf meinem Entwickler-PC herausfinden ... – Luke