Skip to main content

Implémenter TLS en Java

Écrit par

Himanish Munjal

blog feature toolkit

9 novembre 2022

0 minutes de lecture

TLS, ou sécurité de la couche transport, est un protocole utilisé dans le monde entier pour chiffrer et sécuriser les communications sur Internet.

Dans cet article, nous allons voir ce qu’est TLS, quels sont ses avantages et pourquoi vous en avez besoin. Nous verrons ensuite comment implémenter TLS en Java.

Qu’est-ce que TLS ?

TLS et son prédécesseur SSL (Secure Socket Layer) sont les protocoles cryptographiques les plus utilisés pour assurer le chiffrement, l’authenticité et l’intégrité, et ainsi garantir la sécurité de bout en bout des données échangées entre des applications sur Internet.

Les protocoles TLS et SSL se situent entre la couche application et la couche TCP/IP, où ils peuvent sécuriser les données des applications avant de les transmettre à la couche transport. De par leur position, TLS et SSL peuvent prendre en charge plusieurs protocoles de la couche application. Consultez cet article pour en savoir plus sur le modèle OSI.

Développé à l’origine par Netscape, SSL 1.0 comportait plusieurs failles de sécurité et n’a jamais été publié. SSL 2.0 a été introduit en 1995, puis SSL 3.0 en 1996. SSL a ensuite été renommé, et TLS 1.0 est sorti en 1999. La version actuelle, TLS 1.3, a été introduite en 2018. Pour vous protéger des vulnérabilités connues, il est toujours recommandé d’utiliser la dernière version de TLS. Vous pouvez en savoir plus sur TLS dans cet article.

L’importance de TLS

Historiquement, les communications sur Internet se faisaient en texte brut et avec peu de sécurité. Les données que vous envoyiez à un serveur étaient donc visibles sur le réseau et exposées aux attaques de l’homme du milieu : un acteur malveillant s’interpose dans l’échange entre un utilisateur et une application. Il peut alors consulter et modifier les données transmises, et ainsi exposer des informations personnelles, comme des mots de passe ou des coordonnées bancaires.

Pour résoudre ce type de problème, SSL et TLS ont été introduits. Ils offrent trois avantages principaux :

  • Protection de l’intégrité : TLS protège les messages contre les modifications effectuées par un intercepteur actif sur le réseau.

  • Authentification : Dans la plupart des modes, TLS authentifie les pairs à l’aide de certificats signés. Les serveurs sont généralement authentifiés, tandis que les clients peuvent l’être à la demande des serveurs. Cette authentification a lieu lors de la négociation TLS.

  • Confidentialité : TLS chiffre les données échangées entre le client et le serveur, ce qui en protège la confidentialité et la vie privée. Les intercepteurs passifs ne peuvent ainsi pas voir les données sensibles échangées entre les machines.

Avec SSL ou TLS, les utilisateurs ont l’assurance que leurs données ne sont pas exposées et que celles qu’ils envoient ou reçoivent ne sont pas altérées. L’implémentation de TLS est donc indispensable pour les applications accessibles sur Internet, car elle vous permet de sécuriser les données de vos clients.

Concepts fondamentaux

Avant d’entrer dans le détail de l’implémentation de TLS en Java, passons en revue quelques concepts qui vous aideront à comprendre les aspects techniques de TLS.

Sockets

Un socket est l’une des extrémités d’une interface de communication bidirectionnelle entre des programmes en réseau. La couche transport sous-jacente (TCP ou UDP) identifie l’application de destination à laquelle les données seront transmises en associant un socket à un numéro de port.

Les classes Socket représentent la connexion entre les applications cliente et serveur. Le package java.net fournit deux classes — Socket et ServerSocket — qui implémentent respectivement le côté client et le côté serveur de la connexion.

Pour en savoir plus sur les sockets, consultez cet article.

Les communications TLS utilisent des sockets sécurisés : SSLSocket et SSLServerSocket. Ces classes étendent respectivement les classes Socket et ServerSocket.

Négociation TLS

Une négociation SSL ou TLS est une série d’actions menées par le client et le serveur pour s’authentifier mutuellement et établir les clés secrètes qu’ils utiliseront pour communiquer.

La négociation TLS est un sujet très vaste qui dépasse le cadre de cet article. Pour commencer à comprendre les négociations TLS, consultez cet article sur le déroulement d’une négociation TLS et cette présentation des négociations SSL ou TLS.

Versions LTS de Java

Les versions LTS (support à long terme) de Java bénéficient pendant une période prolongée de mises à jour trimestrielles de sécurité, de stabilité et de performances. Les versions standard (non-LTS) ne reçoivent des mises à jour et des correctifs que jusqu’à la sortie de la version suivante.

Java 7, 8, 11 et la version 17, récemment publiée, sont des versions LTS. En raison de leur période de support prolongée, il est recommandé d’utiliser exclusivement des versions LTS dans les environnements de production.

Comme pour tout logiciel, chaque version apporte de nouvelles fonctionnalités et améliorations. Vous trouverez ici les fonctionnalités de Java 11 et ici celles de Java 17. Les fonctionnalités variant d’une version de Java à l’autre, consultez la documentation de votre version si vous rencontrez des problèmes inattendus lors de l’implémentation de TLS.

Implémenter TLS en Java

Après toute cette théorie, passons enfin à la partie intéressante : l’implémentation. Dans les sections suivantes, vous découvrirez le framework JSSE et comment utiliser ses API pour implémenter TLS.

API JSSE

Java Secure Socket Extension, ou JSSE, est un framework qui abstrait l’implémentation sous-jacente de la sécurité de la couche transport (TLS). En masquant la complexité des protocoles sous-jacents, JSSE permet aux programmeurs d’implémenter facilement des communications chiffrées et sécurisées. JSSE utilise le protocole TLS pour sécuriser et chiffrer les communications entre vos clients et vos serveurs.

JSSE fournit quatre classes principales dans son implémentation standard :

  • SSLSocketFactory : cette classe sert de fabrique pour créer des sockets sécurisés.

  • SSLServerSocketFactory : cette classe est analogue à SSLSocketFactory, mais sert spécifiquement à créer des sockets serveur.

  • SSLSocket : SSLSocket étend Socket en ajoutant une couche de protection au protocole de transport réseau sous-jacent, tel que TCP et UDP, et offre les avantages de SSL et TLS.

  • SSLServerSocket : cette classe est similaire à SSLSocket. La principale différence est que SSLServerSocket sert à créer des sockets côté serveur, tandis que SSLSocket sert à en créer côté client.

Vous utiliserez ces classes pour implémenter TLS côté client et côté serveur.

Implémenter TLS côté client

Dans cette section, vous allez voir comment implémenter TLS côté client à l’aide de SSLSocketFactory et SSLSocket.

Créez un fichier texte nommé JavaTLSClientExample.class, puis copiez-collez le code suivant et enregistrez le fichier :

package io.snyk.programming.tls;

import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.*;

public class JavaTLSClientExample {

    private static final String[] protocols = new String[]{"TLSv1.3"};
    private static final String[] cipher_suites = new String[]{"TLS_AES_128_GCM_SHA256"};

    public static void main(String[] args) throws Exception {

        SSLSocket socket = null;
        PrintWriter out = null;
        BufferedReader in = null;

        try {

            // Step : 1
            SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();

            // Step : 2
            socket = (SSLSocket) factory.createSocket("google.com", 443);

            // Step : 3
            socket.setEnabledProtocols(protocols);
            socket.setEnabledCipherSuites(cipher_suites); 

            // Step : 4 {optional}
            socket.startHandshake(); 

            // Step : 5
            out = new PrintWriter(
                    new BufferedWriter(
                            new OutputStreamWriter(
                                    socket.getOutputStream())));

            out.println("GET / HTTP/1.0");
            out.println();
            out.flush();

            if (out.checkError()) {
                System.out.println("SSLSocketClient:  java.io.PrintWriter error");
            }

            // Step : 6
            in = new BufferedReader(
                    new InputStreamReader(
                            socket.getInputStream()));

            String inputLine;
            while ((inputLine = in.readLine()) != null) {
                System.out.println(inputLine);
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (socket != null) {
                socket.close();
            }
            if (out != null) {
                out.close();
            }
            if (in != null) {
                in.close();
            }
        }
    }
}

Pour exécuter le code, ouvrez votre terminal et utilisez la commande cd pour accéder au dossier où vous avez enregistré le fichier ci-dessus — cd <PathToFile>.

Compilez le fichier en exécutant javac JavaTLSClientExample.class, puis exécutez-le avec la commande java JavaTLSClientExample.class.

Voici le détail de ce qui se passe à chaque étape de l’exemple de code.

La première étape consiste à obtenir une instance de SSLSocketFactory, qui servira ensuite à créer le socket.

Il existe plusieurs façons d’obtenir une instance de SSLSocketFactory. Dans cet exemple, vous récupérez la fabrique par défaut en appelant la méthode statique SSLSocketFactory.getDefault().

La deuxième étape consiste à créer un socket SSL. Vous appelez la méthode createSocket et fournissez des informations telles que l’hôte distant et le port distant. Un socket est alors créé et connecté à l’hôte distant spécifié sur le port indiqué.

La troisième étape consiste à configurer les paramètres. Vous indiquez d’abord au socket la version de TLS à prendre en charge. Au moment de la rédaction de cet article, TLS v1.3 est la dernière version ; c’est donc celle qui est utilisée dans l’exemple ci-dessus. De plus, CipherSuite TLS_AES_128_GCM_SHA256 définit les différents paramètres à utiliser à chaque étape de la négociation TLS.

À la quatrième étape, vous lancez la négociation. C’est à ce moment que la négociation TLS est explicitement initiée.

Pendant la négociation, le socket utilise le protocole et le CipherSuite définis à l’étape précédente.

À la cinquième étape, la configuration est terminée et vous envoyez une véritable requête au serveur. Les informations textuelles sont alors transmises au flux de sortie du socket. OutputStreamWriter convertit le flux de caractères en flux d’octets. BufferedWriter évite les conversions répétées, et PrintWriter écrit la représentation formatée des objets dans le flux de sortie texte. Comme PrintWriter ne lève aucune exception, vous devez vérifier explicitement les erreurs à l’aide de la méthode checkError.

Enfin, à la sixième étape, vous lisez la réponse. Une fois la requête envoyée, la réponse est lue depuis le flux inputStream du socket, puis affichée.

Implémenter TLS côté serveur

Vous allez maintenant découvrir comment implémenter TLS côté serveur à l’aide de SSLServerSocketFactory et de SSLServerSocket.

Créez un fichier nommé JavaTLSServerExample.class, puis collez-y le code suivant :

package io.snyk.programming.tls;

import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import java.io.*;

public class JavaTLSServerExample {

    private static final String[] protocols = new String[]{"TLSv1.3"};
    private static final String[] cipher_suites = new String[]{"TLS_AES_128_GCM_SHA256"};

    public static void main(String[] args) throws Exception {

        SSLServerSocket serverSocket = null;

        try {

            // Step : 1
            SSLServerSocketFactory factory =
                    (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();

            // Step : 2
            serverSocket = (SSLServerSocket) factory.createServerSocket(8980);

            // Step : 3
            serverSocket.setEnabledProtocols(protocols);
            serverSocket.setEnabledCipherSuites(cipher_suites);

            // Step : 4
            SSLSocket sslSocket = (SSLSocket) serverSocket.accept();

            // Step : 5
            InputStream inputStream = sslSocket.getInputStream();
            InputStreamReader inputStreamReader = new
                    InputStreamReader(inputStream);

            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String request = null;
            while((request = bufferedReader.readLine()) != null) {
                 System.out.println(request);
                 System.out.flush();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (serverSocket != null) {
                serverSocket.close();
            }
        }
    }
}

Pour exécuter le code, ouvrez de nouveau un terminal dans le dossier où vous avez enregistré le fichier ci-dessus.

Compilez le fichier en exécutant javac JavaTLSServerExample.class.

Suivez ce guide pour configurer et ajouter un certificat d’AC à votre keystore.

Exécutez java -Djavax.net.ssl.keyStore=<KeystoreFilePath> -Djavax.net.ssl.keyStorePassword=<KeystorePassword> JavaTLSServerExample pour lancer le fichier. Djavax.net.ssl.keyStore et -Djavax.net.ssl.keyStorePassword sont nécessaires pour fournir au runtime Java le certificat SSL du serveur.

Reprenons une dernière fois les étapes du code. Les étapes 1 à 3 sont similaires à celles de l’exemple d’implémentation de TLS côté client.

À la première étape, vous obtenez une instance par défaut de SSLSocketFactory.

À la deuxième étape, vous créez un SSLServerSocket et le liez au port 8990.

À la troisième étape, vous définissez les protocoles et CipherSuites pris en charge par votre SSLServerSocket.

À la quatrième étape, vous acceptez la requête entrante. SSLServerSocket.accept() écoute les connexions à ce socket serveur, accepte la connexion et crée un nouveau socket. La méthode reste bloquée jusqu’à ce qu’une connexion soit établie.

À la cinquième étape, les données sont lues depuis le flux d’entrée du socket, puis affichées. InputStreamReader convertit le flux d’octets en flux de caractères, tandis que BufferedReader évite les conversions répétées.

Lien entre l’API JSSE et SSLSocketFactory

Schéma présentant les API standard de JSSE, notamment SSLSocketFactory, SSLServerSocketFactory, SSLSocket et SSLServerSocket.

Comme indiqué précédemment, JSSE est un framework. Il fournit diverses classes et implémentations qui masquent la complexité de la sécurisation de la connexion entre le client et le serveur. JSSE propose également un ensemble de classes d’implémentation pouvant servir d’implémentations par défaut pour configurer des sockets sécurisés côté client et côté serveur.

Côté client, SSLSocketFactory permet d’obtenir une fabrique par défaut, qui sert ensuite à obtenir un SSLSocket côté client. Côté serveur, SSLServerSocketFactory sert à initialiser le SSLServerSocket.

Conclusion

Si vous avez créé une application Java qui communique sur Internet, il est nécessaire d’implémenter TLS côté client comme côté serveur. TLS garantit un transfert sécurisé des données grâce au chiffrement et à la protection de leur intégrité, et contribue également à authentifier les deux parties.

JSSE est un framework fourni par Java qui peut vous aider à implémenter TLS dans votre application. Vous pouvez utiliser ses classes, comme SSLSocket et SSLServerSocket, pour créer des sockets sécurisés.

Lire la suite

feature insights context
Blog

Les attaques autonomes sont déjà là. La défense doit suivre leur rythme.

Les attaquants autonomes réduisent la fenêtre de défense. Découvrez comment la découverte, la correction, la validation et la prévention continues peuvent aider les équipes de sécurité à suivre le rythme.

illustration hero ai
Blog

L’ouragan de l’IA est là

L’IA accélère à la fois la création de logiciels et les cyberattaques. Les dirigeants doivent sécuriser les agents et le code dès leur conception, appliquer des contrôles à l’exécution et valider les défenses de manière indépendante.

feature insights context
Blog

La prévention est-elle essentiellement un problème résolu ?

La prévention dans le code généré par les agents est résolue sur le plan architectural, mais le défi reste de choisir des contrôles qui protègent la sécurité sans ralentir le développement.