SSL-Handshake-Fehler

SSL-Handshake-Fehler

1. Überblick

Secured Socket Layer (SSL) ist ein kryptografisches Protokoll, das Sicherheit bei der Kommunikation über das Netzwerk bietet. In this tutorial, we’ll discuss various scenarios that can result in an SSL handshake failure and how to it.

Beachten Sie, dass unserIntroduction to SSL using JSSE die Grundlagen von SSL ausführlicher behandelt.

2. Terminologie

Es ist wichtig zu beachten, dass SSL als Standard aufgrund von Sicherheitslücken durch TLS (Transport Layer Security) ersetzt wird. Die meisten Programmiersprachen, einschließlich Java, verfügen über Bibliotheken, die sowohl SSL als auch TLS unterstützen.

Seit der Einführung von SSL hatten viele Produkte und Sprachen wie OpenSSL und Java Verweise auf SSL, die sie auch nach der Übernahme von TLS behielten. Aus diesem Grund wird im weiteren Verlauf dieses Lernprogramms der Begriff SSL verwendet, um sich allgemein auf kryptografische Protokolle zu beziehen.

3. Konfiguration

In diesem Lernprogramm erstellen wir einfache Server- und Clientanwendungen mitthe Java Socket API, um eine Netzwerkverbindung zu simulieren.

3.1. Erstellen eines Clients und eines Servers

In Java können wirsockets to establish a communication channel between a server and client over the network verwenden. Sockets sind Teil der Java Secure Socket Extension (JSSE) in Java.

Beginnen wir mit der Definition eines einfachen Servers:

int port = 8443;
ServerSocketFactory factory = SSLServerSocketFactory.getDefault();
try (ServerSocket listener = factory.createServerSocket(port)) {
    SSLServerSocket sslListener = (SSLServerSocket) listener;
    sslListener.setNeedClientAuth(true);
    sslListener.setEnabledCipherSuites(
      new String[] { "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256" });
    sslListener.setEnabledProtocols(
      new String[] { "TLSv1.2" });
    while (true) {
        try (Socket socket = sslListener.accept()) {
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
            out.println("Hello World!");
        }
    }
}

Der oben definierte Server gibt die Meldung "Hello World!" An einen verbundenen Client zurück.

Als Nächstes definieren wir einen Basisclient, den wir mit unserenSimpleServer: verbinden

String host = "localhost";
int port = 8443;
SocketFactory factory = SSLSocketFactory.getDefault();
try (Socket connection = factory.createSocket(host, port)) {
    ((SSLSocket) connection).setEnabledCipherSuites(
      new String[] { "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256" });
    ((SSLSocket) connection).setEnabledProtocols(
      new String[] { "TLSv1.2" });

    SSLParameters sslParams = new SSLParameters();
    sslParams.setEndpointIdentificationAlgorithm("HTTPS");
    ((SSLSocket) connection).setSSLParameters(sslParams);

    BufferedReader input = new BufferedReader(
      new InputStreamReader(connection.getInputStream()));
    return input.readLine();
}

Unser Client druckt die vom Server zurückgegebene Nachricht.

3.2. Erstellen von Zertifikaten in Java

SSL bietet Geheimhaltung, Integrität und Authentizität bei der Netzwerkkommunikation. Zertifikate spielen eine wichtige Rolle für die Echtheitsfeststellung.

Normalerweise werden diese Zertifikate von einer Zertifizierungsstelle gekauft und signiert. Für dieses Lernprogramm werden jedoch selbstsignierte Zertifikate verwendet.

Um dies zu erreichen, können wirkeytool, verwenden, die mit dem JDK ausgeliefert werden:

$ keytool -genkey -keypass password \
                  -storepass password \
                  -keystore serverkeystore.jks

Der obige Befehl startet eine interaktive Shell, um Informationen für das Zertifikat wie Common Name (CN) und Distinguished Name (DN) zu sammeln. Wenn wir alle relevanten Details angeben, wird die Dateiserverkeystore.jks generiert, die den privaten Schlüssel des Servers und sein öffentliches Zertifikat enthält.

Beachten Sie, dassserverkeystore.jks im Java Key Store (JKS) -Format gespeichert ist, das für Java proprietär ist. These days, keytool will remind us that we ought to consider using PKCS#12, which it also supports.

Wir können weiterhinkeytool verwenden, um das öffentliche Zertifikat aus der generierten Keystore-Datei zu extrahieren:

$ keytool -export -storepass password \
                  -file server.cer \
                  -keystore serverkeystore.jks

Der obige Befehl exportiert das öffentliche Zertifikat aus dem Keystore als Dateiserver.cer. Verwenden Sie das exportierte Zertifikat für den Client, indem Sie es seinem Truststore hinzufügen:

$ keytool -import -v -trustcacerts \
                     -file server.cer \
                     -keypass password \
                     -storepass password \
                     -keystore clienttruststore.jks

Wir haben jetzt einen Keystore für den Server und einen entsprechenden Truststore für den Client generiert. Wir werden die Verwendung dieser generierten Dateien untersuchen, wenn wir mögliche Handshake-Fehler besprechen.

Weitere Informationen zur Verwendung des Java-Keystores finden Sie in unserenprevious tutorial.

4. SSL-Handshake

SSL-Handshakes sinda mechanism by which a client and server establish the trust and logistics required to secure their connection over the network.

Dies ist eine sehr orchestrierte Prozedur, und das Verständnis der Details kann dazu beitragen, zu verstehen, warum es häufig fehlschlägt, worauf wir im nächsten Abschnitt eingehen wollen.

Typische Schritte bei einem SSL-Handshake sind:

  1. Der Client stellt eine Liste der möglichen zu verwendenden SSL-Versionen und Cipher Suites zur Verfügung

  2. Der Server stimmt einer bestimmten SSL-Version und Cipher Suite zu und antwortet mit seinem Zertifikat

  3. Der Client extrahiert den öffentlichen Schlüssel aus dem Zertifikat und antwortet mit einem verschlüsselten „Pre-Master-Schlüssel“.

  4. Der Server entschlüsselt den "Pre-Master-Schlüssel" mit seinem privaten Schlüssel

  5. Client und Server berechnen ein "gemeinsames Geheimnis" mit dem ausgetauschten "Pre-Master-Schlüssel"

  6. Client- und Server-Austauschnachrichten, die die erfolgreiche Ver- und Entschlüsselung mit dem "Shared Secret" bestätigen

Während die meisten Schritte für jeden SSL-Handshake gleich sind, gibt es einen subtilen Unterschied zwischen Einweg- und Zweiweg-SSL. Lassen Sie uns diese Unterschiede schnell überprüfen.

4.1. Der Handshake in One-Way-SSL

Wenn wir uns auf die oben genannten Schritte beziehen, erwähnt Schritt zwei den Zertifikataustausch. Einweg-SSL erfordert, dass ein Client dem Server über sein öffentliches Zertifikat vertrauen kann. Dieseleaves the server to trust all clientsfordern eine Verbindung an. Ein Server kann das öffentliche Zertifikat nicht von Clients anfordern und validieren, die ein Sicherheitsrisiko darstellen können.

4.2. Der Handshake in Zwei-Wege-SSL

Bei Einweg-SSL muss der Server allen Clients vertrauen. Durch bidirektionales SSL kann der Server jedoch auch vertrauenswürdige Clients einrichten. Während eines bidirektionalen Handshakes könnenboth the client and server must present and accept each other’s public certificates vor einer erfolgreichen Verbindung hergestellt werden.

5. Handshake-Fehlerszenarien

Nach dieser kurzen Überprüfung können wir Fehlerszenarien klarer betrachten.

Ein SSL-Handshake in der Einweg- oder Zweiwegkommunikation kann aus mehreren Gründen fehlschlagen. Wir werden jeden dieser Gründe durchgehen, den Ausfall simulieren und verstehen, wie wir solche Szenarien vermeiden können.

In jedem dieser Szenarien verwenden wir die zuvor erstelltenSimpleClient undSimpleServer.

5.1. Fehlendes Serverzertifikat

Versuchen wir,SimpleServer auszuführen und überSimpleClient zu verbinden. Wir erwarten zwar die Meldung "Hello World!", Es wird jedoch eine Ausnahme angezeigt:

Exception in thread "main" javax.net.ssl.SSLHandshakeException:
  Received fatal alert: handshake_failure

Dies deutet darauf hin, dass etwas schief gelaufen ist. DieSSLHandshakeException über, abstrakt,is stating that the client when connecting to the server did not receive any certificate.

Um dieses Problem zu beheben, verwenden wir den zuvor generierten Keystore, indem wir sie als Systemeigenschaften an den Server übergeben:

-Djavax.net.ssl.keyStore=clientkeystore.jks -Djavax.net.ssl.keyStorePassword=password

Es ist wichtig zu beachten, dass die Systemeigenschaft für den Pfad der Keystore-Datei entweder ein absoluter Pfad sein sollte oder dass die Keystore-Datei in demselben Verzeichnis abgelegt werden sollte, von dem aus der Java-Befehl aufgerufen wird, um den Server zu starten. Java system property for keystore does not support relative paths.

Hilft uns dies, den erwarteten Output zu erzielen? Lassen Sie es uns im nächsten Unterabschnitt herausfinden.

5.2. Nicht vertrauenswürdiges Serverzertifikat

Was erhalten wir als Ausgabe, wenn wir dieSimpleServer und dieSimpleClient mit den Änderungen im vorherigen Unterabschnitt erneut ausführen:

Exception in thread "main" javax.net.ssl.SSLHandshakeException:
  sun.security.validator.ValidatorException:
  PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException:
  unable to find valid certification path to requested target

Nun, es hat nicht genau so funktioniert, wie wir es erwartet hatten, aber anscheinend ist es aus einem anderen Grund gescheitert.

Dieser besondere Fehler wird durch die Tatsache verursacht, dass unser Server einself-signed-Zertifikat verwendet, das nicht von einer Zertifizierungsstelle (CA) signiert ist.

Really, any time the certificate is signed by something other than what is in the default truststore, we’ll see this error. Der Standard-Truststore in JDK enthält normalerweise Informationen zu häufig verwendeten allgemeinen Zertifizierungsstellen.

Um dieses Problem hier zu lösen, müssen wirSimpleClient zwingen, dem vonSimpleServer präsentierten Zertifikat zu vertrauen. Verwenden wir den zuvor generierten Truststore, indem wir ihn als Systemeigenschaften an den Client übergeben:

-Djavax.net.ssl.trustStore=clienttruststore.jks -Djavax.net.ssl.trustStorePassword=password

Bitte beachten Sie, dass dies keine ideale Lösung ist. In an ideal scenario, we should not use a self-signed certificate but a certificate which has been certified by a Certificate Authority (CA) which clients can trust by default.

Gehen wir zum nächsten Unterabschnitt, um herauszufinden, ob wir jetzt unsere erwartete Ausgabe erhalten.

5.3. Fehlendes Client-Zertifikat

Versuchen wir noch einmal, den SimpleServer und den SimpleClient auszuführen, nachdem wir die Änderungen aus den vorherigen Unterabschnitten übernommen haben:

Exception in thread "main" java.net.SocketException:
  Software caused connection abort: recv failed

Wieder nicht etwas, was wir erwartet hatten. DasSocketException hier sagt uns, dass der Server dem Client nicht vertrauen konnte. Dies liegt daran, dass wir ein bidirektionales SSL eingerichtet haben. In unseremSimpleServer we haben:

((SSLServerSocket) listener).setNeedClientAuth(true);

Der obige Code gibt an, dass einSSLServerSocket für die Clientauthentifizierung über das öffentliche Zertifikat erforderlich ist.

Wir können einen Keystore für den Client und einen entsprechenden Truststore für den Server auf ähnliche Weise wie beim Erstellen des vorherigen Keystore und Truststore erstellen.

Wir werden den Server neu starten und ihm die folgenden Systemeigenschaften übergeben:

-Djavax.net.ssl.keyStore=serverkeystore.jks \
    -Djavax.net.ssl.keyStorePassword=password \
    -Djavax.net.ssl.trustStore=servertruststore.jks \
    -Djavax.net.ssl.trustStorePassword=password

Anschließend starten wir den Client neu, indem wir die folgenden Systemeigenschaften übergeben:

-Djavax.net.ssl.keyStore=clientkeystore.jks \
    -Djavax.net.ssl.keyStorePassword=password \
    -Djavax.net.ssl.trustStore=clienttruststore.jks \
    -Djavax.net.ssl.trustStorePassword=password

Schließlich haben wir die gewünschte Ausgabe:

Hello World!

5.4. Falsche Zertifikate

Abgesehen von den oben genannten Fehlern kann ein Handshake aus verschiedenen Gründen fehlschlagen, die mit der Erstellung der Zertifikate zusammenhängen. Ein häufiger Fehler hängt mit einem falschen CN zusammen. Lassen Sie uns die Details des zuvor erstellten Server-Keystores erforschen:

keytool -v -list -keystore serverkeystore.jks

Wenn wir den obigen Befehl ausführen, können wir die Details des Schlüsselspeichers sehen, insbesondere den Eigentümer:

...
Owner: CN=localhost, OU=technology, O=example, L=city, ST=state, C=xx
...

Der CN des Inhabers dieses Zertifikats ist auf localhost festgelegt. Der CN des Besitzers muss genau mit dem Host des Servers übereinstimmen. Wenn es eine Nichtübereinstimmung gibt, führt dies zu einemSSLHandshakeException.

Versuchen wir, das Serverzertifikat mit CN als etwas anderem als localhost neu zu generieren. Wenn wir das neu generierte Zertifikat jetzt verwenden, umSimpleServer undSimpleClient auszuführen, schlägt dies sofort fehl:

Exception in thread "main" javax.net.ssl.SSLHandshakeException:
    java.security.cert.CertificateException:
    No name matching localhost found

Die obige Ausnahmeverfolgung zeigt deutlich an, dass der Client ein Zertifikat mit dem Namen localhost erwartet hat, das er nicht gefunden hat.

Bitte beachten Sie, dassJSSE does not mandate hostname verification by default. Wir haben die Überprüfung des Hostnamens inSimpleClient durch explizite Verwendung von HTTPS aktiviert:

SSLParameters sslParams = new SSLParameters();
sslParams.setEndpointIdentificationAlgorithm("HTTPS");
((SSLSocket) connection).setSSLParameters(sslParams);

Die Überprüfung des Hostnamens ist eine häufige Fehlerursache und sollte aus Sicherheitsgründen immer erzwungen werden. Einzelheiten zur Überprüfung des Hostnamens und seiner Bedeutung für die Sicherheit mit TLS finden Sie unterthis article.

5.5. Inkompatible SSL-Version

Derzeit sind verschiedene kryptografische Protokolle in Betrieb, einschließlich verschiedener Versionen von SSL und TLS.

Wie bereits erwähnt, wurde SSL im Allgemeinen aufgrund seiner kryptografischen Stärke von TLS abgelöst. Das kryptografische Protokoll und die Version sind ein zusätzliches Element, auf das sich ein Client und ein Server während eines Handshakes einigen müssen.

Wenn der Server beispielsweise ein kryptografisches Protokoll von SSL3 verwendet und der Client TLS1.3 verwendet, kann er sich nicht auf ein kryptografisches Protokoll einigen, und es werdenSSLHandshakeException generiert.

In unserenSimpleClientändern wir das Protokoll in etwas, das nicht mit dem für den Server festgelegten Protokoll kompatibel ist:

((SSLSocket) connection).setEnabledProtocols(new String[] { "TLSv1.1" });

Wenn wir unseren Client erneut ausführen, erhalten wirSSLHandshakeException:

Exception in thread "main" javax.net.ssl.SSLHandshakeException:
  No appropriate protocol (protocol is disabled or cipher suites are inappropriate)

Die Ausnahmeverfolgung ist in solchen Fällen abstrakt und gibt nicht das genaue Problem an. To resolve these types of problems it is necessary to verify that both the client and server are using either the same or compatible cryptographic protocols.

5.6. Inkompatible Cipher Suite

Der Client und der Server müssen sich auch auf die Verschlüsselungssuite einigen, die sie zum Verschlüsseln von Nachrichten verwenden.

Während eines Handshakes zeigt der Client eine Liste der möglichen zu verwendenden Chiffren an und der Server antwortet mit einer ausgewählten Chiffre aus der Liste. Der Server generiert einSSLHandshakeException , wenn er keine geeignete Verschlüsselung auswählen kann.

In unserenSimpleClient ändern wir die Verschlüsselungssuite in etwas, das nicht mit der von unserem Server verwendeten Verschlüsselungssuite kompatibel ist:

((SSLSocket) connection).setEnabledCipherSuites(
  new String[] { "TLS_RSA_WITH_AES_128_GCM_SHA256" });

Wenn wir unseren Client neu starten, erhalten wirSSLHandshakeException:

Exception in thread "main" javax.net.ssl.SSLHandshakeException:
  Received fatal alert: handshake_failure

Auch hier ist die Ausnahmeverfolgung ziemlich abstrakt und gibt nicht das genaue Problem an. Die Lösung für einen solchen Fehler besteht darin, die aktivierten Cipher Suites zu überprüfen, die sowohl vom Client als auch vom Server verwendet werden, und sicherzustellen, dass mindestens eine gemeinsame Suite verfügbar ist.

Normalerweise sind Clients und Server für die Verwendung einer Vielzahl von Chiffresuiten konfiguriert, sodass dieser Fehler seltener auftritt. If we encounter this error it is typically because the server has been configured to use a very selective cipher. Ein Server kann aus Sicherheitsgründen einen ausgewählten Satz von Chiffren erzwingen.

6. Fazit

In diesem Tutorial haben wir gelernt, wie Sie SSL mit Java-Sockets einrichten. Anschließend haben wir über SSL-Handshakes mit Einweg- und Zweiweg-SSL gesprochen. Schließlich haben wir eine Liste möglicher Gründe für das Fehlschlagen von SSL-Handshakes durchgearbeitet und die Lösungen erörtert.

Wie immer ist der Code für die Beispieleover on GitHub verfügbar.