Échecs de prise de contact SSL

Échecs de négociation SSL

1. Vue d'ensemble

Secured Socket Layer (SSL) est un protocole cryptographique assurant la sécurité des communications sur le réseau. In this tutorial, we’ll discuss various scenarios that can result in an SSL handshake failure and how to it.

Notez que notreIntroduction to SSL using JSSE couvre plus en détail les bases de SSL.

2. Terminologie

Il est important de noter qu'en raison de vulnérabilités de sécurité, SSL en tant que norme est remplacé par Transport Layer Security (TLS). La plupart des langages de programmation, y compris Java, disposent de bibliothèques prenant en charge SSL et TLS.

Depuis la création de SSL, de nombreux produits et langages tels que OpenSSL et Java avaient des références à SSL, qu'ils conservaient même après la prise en charge de TLS. Pour cette raison, dans la suite de ce didacticiel, nous utiliserons le terme SSL pour désigner de manière générale les protocoles cryptographiques.

3. Installer

Dans le cadre de ce didacticiel, nous allons créer un serveur et des applications client simples à l'aide dethe Java Socket API pour simuler une connexion réseau.

3.1. Création d'un client et d'un serveur

En Java, nous pouvons utilisersockets to establish a communication channel between a server and client over the network. Les sockets font partie de Java Secure Socket Extension (JSSE).

Commençons par définir un serveur simple:

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!");
        }
    }
}

Le serveur défini ci-dessus renvoie le message «Hello World!» À un client connecté.

Ensuite, définissons un client de base, que nous allons connecter à nosSimpleServer:

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

Notre client imprime le message renvoyé par le serveur.

3.2. Création de certificats en Java

Le protocole SSL assure la confidentialité, l'intégrité et l'authenticité des communications réseau. Les certificats jouent un rôle important dans l'établissement de l'authenticité.

En règle générale, ces certificats sont achetés et signés par une autorité de certification, mais pour ce didacticiel, nous utiliserons des certificats auto-signés.

Pour y parvenir, nous pouvons utiliserkeytool, which livré avec le JDK:

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

La commande ci-dessus démarre un shell interactif pour collecter des informations sur le certificat, telles que Nom commun (CN) et Nom distinctif (DN). Lorsque nous fournissons tous les détails pertinents, il génère le fichierserverkeystore.jks, qui contient la clé privée du serveur et son certificat public.

Notez queserverkeystore.jks  est stocké au format Java Key Store (JKS), qui est propriétaire de Java. These days, keytool will remind us that we ought to consider using PKCS#12, which it also supports.

Nous pouvons en outre utiliserkeytool pour extraire le certificat public du fichier de keystore généré:

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

La commande ci-dessus exporte le certificat public du magasin de clés sous forme de fichierserver.cer. Utilisons le certificat exporté pour le client en l'ajoutant à son truststore:

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

Nous avons maintenant généré un fichier de clés pour le serveur et le fichier de clés certifiées correspondant pour le client. Nous allons passer en revue l'utilisation de ces fichiers générés lorsque nous discuterons des échecs possibles de négociation.

Et plus de détails sur l'utilisation du keystore de Java peuvent être trouvés dans nosprevious tutorial.

4. Prise de contact SSL

Les handshakes SSL sonta mechanism by which a client and server establish the trust and logistics required to secure their connection over the network.

C'est une procédure très orchestrée et comprendre les détails de cette procédure peut aider à comprendre pourquoi elle échoue souvent, ce que nous avons l'intention de couvrir dans la section suivante.

Les étapes typiques d'une négociation SSL sont les suivantes:

  1. Le client fournit une liste des versions possibles de la version SSL et des suites de chiffrement à utiliser.

  2. Le serveur s’accorde sur une version SSL et une suite de chiffrement particulières et répond avec son certificat.

  3. Le client extrait la clé publique du certificat et lui renvoie une «clé pré-maître» chiffrée.

  4. Le serveur déchiffre la «clé pré-maître» à l'aide de sa clé privée

  5. Le client et le serveur calculent un «secret partagé» à l'aide de la «clé pré-maître» échangée.

  6. Le client et le serveur échangent des messages confirmant le succès du chiffrement et du déchiffrement à l’aide du «secret partagé».

Bien que la plupart des étapes soient les mêmes pour toute négociation SSL, il existe une différence subtile entre les protocoles SSL unidirectionnel et bidirectionnel. Examinons rapidement ces différences.

4.1. La poignée de main dans SSL à sens unique

Si nous nous référons aux étapes mentionnées ci-dessus, la deuxième étape mentionne l'échange de certificats. Le protocole SSL unidirectionnel nécessite qu'un client puisse faire confiance au serveur via son certificat public. Celeaves the server to trust all clients qui demande une connexion. Il n’existe aucun moyen pour un serveur de demander et de valider le certificat public des clients, ce qui peut poser un risque pour la sécurité.

4.2. La poignée de main dans SSL bidirectionnel

Avec le protocole SSL unidirectionnel, le serveur doit faire confiance à tous les clients. Cependant, le protocole SSL bidirectionnel offre au serveur la possibilité d’établir des clients sécurisés. Lors d'une prise de contact bidirectionnelle,both the client and server must present and accept each other’s public certificates avant qu'une connexion réussie puisse être établie.

5. Scénarios d'échec de la poignée de main

Après avoir effectué cet examen rapide, nous pouvons examiner les scénarios de défaillance avec plus de clarté.

Une négociation SSL, en communication unidirectionnelle ou bidirectionnelle, peut échouer pour plusieurs raisons. Nous allons passer en revue chacune de ces raisons, simuler la défaillance et comprendre comment éviter de tels scénarios.

Dans chacun de ces scénarios, nous utiliserons lesSimpleClient etSimpleServer que nous avons créés précédemment.

5.1. Certificat de serveur manquant

Essayons d'exécuter lesSimpleServer et de les connecter via lesSimpleClient. Bien que nous nous attendions à voir le message «Hello World!», Une exception nous est présentée:

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

Maintenant, cela indique que quelque chose s'est mal passé. LesSSLHandshakeException ci-dessus, de manière abstraite,is stating that the client when connecting to the server did not receive any certificate.

Pour résoudre ce problème, nous allons utiliser le fichier de clés que nous avons généré précédemment en les transmettant au serveur en tant que propriétés système:

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

Il est important de noter que la propriété système du chemin du fichier de keystore doit être un chemin absolu ou que le fichier de keystore doit être placé dans le même répertoire à partir duquel la commande Java est appelée pour démarrer le serveur. Java system property for keystore does not support relative paths.

Est-ce que cela nous aide à obtenir le résultat attendu? Découvrons-le dans la sous-section suivante.

5.2. Certificat de serveur non approuvé

Lorsque nous exécutons à nouveau lesSimpleServer et lesSimpleClient avec les modifications de la sous-section précédente, qu'obtenons-nous en sortie:

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

Eh bien, cela n’a pas fonctionné exactement comme prévu, mais il semble qu’il ait échoué pour une raison différente.

Cet échec particulier est dû au fait que notre serveur utilise un certificatself-signed qui n'est pas signé par une autorité de certification (CA).

Really, any time the certificate is signed by something other than what is in the default truststore, we’ll see this error. Le magasin de clés de confiance par défaut dans JDK est généralement livré avec des informations sur les autorités de certification courantes utilisées.

Pour résoudre ce problème ici, nous devrons forcerSimpleClient à faire confiance au certificat présenté parSimpleServer. Utilisons le truststore que nous avons généré précédemment en les transmettant en tant que propriétés système au client:

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

Veuillez noter que ce n'est pas une solution idéale. 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.

Passons à la sous-section suivante pour savoir si nous obtenons maintenant le résultat attendu.

5.3. Certificat client manquant

Essayons une fois de plus d'exécuter SimpleServer et SimpleClient, après avoir appliqué les modifications des sous-sections précédentes:

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

Encore une fois, pas quelque chose que nous attendions. LesSocketException ici nous indiquent que le serveur ne pouvait pas faire confiance au client. En effet, nous avons mis en place un protocole SSL bidirectionnel. Dans notreSimpleServer we ont:

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

Le code ci-dessus indique qu'unSSLServerSocket est requis pour l'authentification du client via son certificat public.

Nous pouvons créer un magasin de clés pour le client et un magasin de clés correspondant pour le serveur de la même manière que celui utilisé lors de la création du magasin de clés et du magasin de clés de confiance précédents.

Nous allons redémarrer le serveur et lui transmettre les propriétés système suivantes:

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

Ensuite, nous redémarrerons le client en transmettant ces propriétés système:

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

Enfin, nous avons le résultat souhaité:

Hello World!

5.4. Certificats incorrects

Outre les erreurs ci-dessus, une poignée de main peut échouer pour diverses raisons liées à la manière dont nous avons créé les certificats. Une erreur commune est liée à un CN incorrect. Explorons les détails du keystore du serveur que nous avons créé précédemment:

keytool -v -list -keystore serverkeystore.jks

Lorsque nous exécutons la commande ci-dessus, nous pouvons voir les détails du magasin de clés, en particulier le propriétaire:

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

Le CN du propriétaire de ce certificat est défini sur localhost. Le CN du propriétaire doit correspondre exactement à l'hôte du serveur. S'il y a une discordance, cela entraînera unSSLHandshakeException.

Essayons de régénérer le certificat de serveur avec CN comme autre chose que localhost. Lorsque nous utilisons le certificat régénéré maintenant pour exécuter lesSimpleServer etSimpleClient, il échoue rapidement:

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

La trace d'exception ci-dessus indique clairement que le client attend un certificat portant le nom localhost qu'il n'a pas trouvé.

Veuillez noter queJSSE does not mandate hostname verification by default. Nous avons activé la vérification du nom d'hôte dans lesSimpleClient via l'utilisation explicite de HTTPS:

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

La vérification du nom d’hôte est une cause fréquente d’échec et doit en général être appliquée pour une meilleure sécurité. Pour plus de détails sur la vérification du nom d'hôte et son importance dans la sécurité avec TLS, veuillez vous référer àthis article.

5.5. Version SSL incompatible

Actuellement, il existe différents protocoles cryptographiques, y compris différentes versions de SSL et TLS en fonctionnement.

Comme mentionné précédemment, SSL a généralement été remplacé par TLS pour sa force cryptographique. Le protocole cryptographique et la version constituent un élément supplémentaire sur lequel un client et un serveur doivent s’accorder lors d’une prise de contact.

Par exemple, si le serveur utilise un protocole cryptographique de SSL3 et que le client utilise TLS1.3, il ne peut pas s'entendre sur un protocole cryptographique et unSSLHandshakeException sera généré.

Dans nosSimpleClient, changeons le protocole en quelque chose qui n'est pas compatible avec le protocole défini pour le serveur:

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

Lorsque nous exécuterons à nouveau notre client, nous obtiendrons unSSLHandshakeException:

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

La trace d'exception dans de tels cas est abstraite et ne nous dit pas le problème exact. 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. Suite de chiffrement incompatible

Le client et le serveur doivent également s'accorder sur la suite de chiffrement qu'ils utiliseront pour chiffrer les messages.

Au cours d'une poignée de main, le client présentera une liste des chiffrements possibles à utiliser et le serveur répondra avec un chiffre sélectionné dans la liste. Le serveur générera unSSLHandshakeException  s'il ne parvient pas à sélectionner un chiffrement approprié.

Dans notreSimpleClient, changeons la suite de chiffrement en quelque chose qui n'est pas compatible avec la suite de chiffrement utilisée par notre serveur:

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

Lorsque nous redémarrons notre client, nous obtiendrons unSSLHandshakeException:

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

Encore une fois, la trace d'exception est assez abstraite et ne nous dit pas le problème exact. La solution à une telle erreur consiste à vérifier les suites de chiffrement activées utilisées à la fois par le client et le serveur et à s'assurer qu'il existe au moins une suite commune disponible.

Normalement, les clients et les serveurs sont configurés pour utiliser une grande variété de suites de chiffrement, de sorte que cette erreur est moins susceptible de se produire. If we encounter this error it is typically because the server has been configured to use a very selective cipher. Un serveur peut choisir d'appliquer un ensemble sélectif de chiffrements pour des raisons de sécurité.

6. Conclusion

Dans ce tutoriel, nous avons appris comment configurer SSL à l'aide de sockets Java. Nous avons ensuite abordé les liaisons SSL avec les protocoles SSL unidirectionnel et bidirectionnel. Enfin, nous avons passé en revue une liste de raisons pouvant expliquer l'échec des négociations SSL et discuté des solutions.

Comme toujours, le code des exemples est disponibleover on GitHub.