Maîtriser le TLS bidirectionnel
Ce didacticiel vous guidera tout au long du processus de protection de votre application avec l’authentification TLS/SSL, en n’autorisant l’accès qu’à certains utilisateurs en fonction de leurs certificats. Cela signifie que vous pouvez choisir les utilisateurs autorisés à appeler votre application.
Table des matières
- introduction
- Didacticiel
- Démarrage du serveur
- Dire bonjour au serveur (sans cryptage)
- Activer HTTPS sur le serveur (TLS unidirectionnel)
- Exiger que le client s’identifie (TLS bidirectionnel)
- TLS bidirectionnel basé sur la confiance de l’autorité de certification
- Scripts automatisés
- Clients HTTP testés
- Démo et vidéo de présentation
introduction
Cet exemple de projet illustre une configuration de base d’un serveur et d’un client. La communication entre le serveur et le client se fait via HTTP, il n’y a donc pas encore de cryptage. L’objectif est de s’assurer que toutes les communications seront cryptées.
Définition
- Identité : un magasin de clés qui contient la paire de clés également appelée clé privée et clé publique
- TrustStore : Un KeyStore contenant un ou plusieurs certificats, également appelé clé publique. Ce KeyStore contient une liste de certificats de confiance
- Authentification unidirectionnelle (également appelée tls unidirectionnel, ssl unidirectionnel) : connexion HTTP où le client valide le certificat de la contrepartie
- Authentification bidirectionnelle (également appelée tls bidirectionnel, ssl bidirectionnel, authentification mutuelle) : connexion HTTP où le client ainsi que la contrepartie valident le certificat, également appelée authentification mutuelle
Liens utiles
Un peu d’histoire
J’ai principalement travaillé avec Apache HTTP Client et j’ai donc initialement créé ce projet avec uniquement un client HTTP d’Apache. Après un certain temps, j’ai découvert qu’il y avait beaucoup plus de clients Java et qu’il y avait aussi des clients disponibles basés sur Kotlin et Scala. La configuration de SSL/TLS peut être difficile et chaque client nécessite une configuration différente. Je souhaite partager mes connaissances avec d’autres développeurs, car chaque développeur travaillera probablement avec un client HTTP différent. Je veux aussi offrir un aide-mémoire pour tous les développeurs et la communauté, voir ici pour une liste de plus de 40 clients HTTP) avec un exemple de configuration client et un exemple de requête HTTP. Le module client a grandi au fil du temps et contient de nombreuses dépendances pour tester tous ces clients HTTP pour Java, Scala et Kotlin. Par conséquent, le module client peut sembler compliqué. Attention, pour cette raison spécifique, il téléchargera beaucoup de dépendances lors de la construction initiale. De plus, GitHub – SSLContext Kickstart a vu le jour pendant le cycle de vie de ce projet pour configurer facilement tous ces clients. Chaque client HTTP peut nécessiter un objet SSL différent pour activer SSL et cette bibliothèque garantit qu’elle peut fournir tous les types pour configurer ces clients tout en ayant une configuration SSL de base.
Démarrage du serveur
Tout d’abord, nous aurons besoin des éléments suivants :
- Java 11
- Maven 3.5.0
- Eclipse, Intellij IDEA (ou tout autre éditeur de texte comme VIM)
- Un terminal
- Clonez le projet depuis : https://github.com/Hakky54/mutual-tls
Si vous souhaitez démarrer instantanément sans installer de logiciel, cliquez sur le bouton ci-dessous pour ouvrir le projet dans un environnement de développement en ligne
Ce projet contient un wrapper maven, vous pouvez donc exécuter ce projet sans installer maven. La documentation de ce didacticiel contient à côté de la commande mvn par défaut également les commandes du wrapper maven.
Si vous souhaitez exécuter ce projet avec Java 8, vous pouvez obtenir une version plus ancienne avec la commande git ci-dessous. Et il est recommandé de suivre les instructions pour cette version spécifique, qui sont disponibles sur cette page.
git checkout tags/java-8-compatible
Démarrez le serveur en exécutant la méthode principale de la classe App dans le projet de serveur ou en exécutant la commande suivante depuis le terminal dans le répertoire racine :
cd server/ && mvn spring-boot:run
Ou avec le wrapper maven :
cd server/ && ./../mvnw spring-boot:run
Dire bonjour au serveur (sans cryptage)
Actuellement, le serveur s’exécute sur le port par défaut 8080 sans cryptage. Vous pouvez appeler le point de terminaison hello avec la commande curl suivante dans le terminal :
curl -i -XGET http://localhost:8080/api/hello
Il devrait vous donner la réponse suivante :
HTTP/1.1 200
Content-Type: text/plain;charset=UTF-8
Content-Length: 5
Date: Sun, 11 Nov 2018 14:21:50 GMT
Hello
Vous pouvez également appeler le serveur avec le client fourni dans le répertoire client. Le client dépend des autres composants du projet, alors lancez mvn install
ou ./mvnw install
dans le répertoire racine en premier.
Le client est un test d’intégration basé sur Cucumber, et vous pouvez le démarrer en exécutant la classe ClientRunnerIT depuis votre IDE ou en exécutant la commande suivante depuis le terminal dans le répertoire racine cd client/ && mvn exec:java
ou avec le wrapper maven cd client/ && ./../mvnw exec:java
. Il existe un fichier Hello.feature qui décrit les étapes du test d’intégration. Vous pouvez le trouver dans les ressources de test du projet client. Il existe une autre façon d’exécuter à la fois le serveur et le client et c’est avec la commande suivante dans le répertoire racine : mvn clean verify
ou avec le wrapper maven ./mvnw clean verify
. Le client envoie par défaut des requêtes à localhost, car il attend le serveur sur la même machine. Si le serveur s’exécute sur une autre machine, vous pouvez toujours fournir une URL personnalisée avec l’argument VM suivant lors de l’exécution du client : -Durl=http://[HOST]:[PORT]
Activation de HTTPS sur le serveur (TLS unidirectionnel)
Vous allez maintenant apprendre à sécuriser votre serveur en activant TLS. Vous pouvez le faire en ajoutant les propriétés requises au fichier de propriétés de l’application nommé : application.yml
Ajoutez la propriété suivante :
server:
port: 8443
ssl:
enabled: true
Vous vous demanderez probablement pourquoi le port est défini sur 8443. La convention de port pour un serveur Tomcat avec HTTPS est 8443, et pour HTTP, c’est 8080. Ainsi, nous pourrions utiliser le port 8080 pour les connexions HTTPS, mais c’est une mauvaise pratique . Voir Wikipedia pour plus d’informations sur les conventions portuaires.
Redémarrez le serveur pour qu’il puisse appliquer les modifications que vous avez apportées. Vous obtiendrez probablement l’exception suivante : IllegalArgumentException: Resource location must not be null
.
Vous recevez ce message car le serveur requiert un magasin de clés avec le certificat du serveur pour garantir qu’il existe une connexion sécurisée avec le monde extérieur. Le serveur peut vous fournir plus d’informations si vous fournissez l’argument VM suivant : -Djavax.net.debug=SSL,keymanager,trustmanager,ssl:handshake
Pour résoudre ce problème, vous allez créer un magasin de clés avec une clé publique et privée pour le serveur. La clé publique sera partagée avec les utilisateurs afin qu’ils puissent crypter la communication. La communication entre l’utilisateur et le serveur peut être déchiffrée avec la clé privée du serveur. Veuillez ne jamais partager la clé privée du serveur, car d’autres pourraient intercepter la communication et pourront voir le contenu de la communication cryptée.
Pour créer un magasin de clés avec une clé publique et privée, exécutez la commande suivante dans votre terminal :
keytool -v -genkeypair -dname "CN=Hakan,OU=Amsterdam,O=Thunderberry,C=NL" -keystore shared-server-resources/src/main/resources/identity.jks -storepass secret -keypass secret -keyalg RSA -keysize 2048 -alias server -validity 3650 -deststoretype pkcs12 -ext KeyUsage=digitalSignature,dataEncipherment,keyEncipherment,keyAgreement -ext ExtendedKeyUsage=serverAuth,clientAuth -ext SubjectAlternativeName:c=DNS:localhost,DNS:raspberrypi.local,IP:127.0.0.1
Maintenant, vous devez indiquer à votre serveur où se trouve l’emplacement du magasin de clés et fournir les mots de passe. Collez ce qui suit dans votre application.yml
déposer:
server:
port: 8443
ssl:
enabled: true
key-store: classpath:identity.jks
key-password: secret
key-store-password: secret
Toutes nos félicitations! Vous avez activé une connexion cryptée TLS entre le serveur et le client ! Maintenant, vous pouvez essayer d’appeler le serveur avec la commande curl suivante : curl -i --insecure -v -XGET https://localhost:8443/api/hello
Exécutons également le client dans la classe ClientRunnerIT.
Vous verrez le message d’erreur suivant : java.net.ConnectException: Connection refused (Connection refused)
. Il semble que le client essaie de dire bonjour au serveur mais le serveur n’est pas là. Le problème est que le client essaie de dire bonjour au serveur sur le port 8080 alors qu’il est actif sur le port 8443. Appliquez les modifications suivantes à la classe Constants :
De:
private static final String DEFAULT_SERVER_URL = "http://localhost:8080";
À:
private static final String DEFAULT_SERVER_URL = "https://localhost:8443";
Essayons de relancer le client et vous verrez que le message suivant apparaîtra : « javax.net.ssl.SSLHandshakeException : échec de la création du chemin PKIX : sun.security.provider.certpath.SunCertPathBuilderException : impossible de trouver un chemin de certification valide vers la cible demandée« . Cela signifie que le client souhaite communiquer via HTTPS et qu’au cours de la procédure de prise de contact, il a reçu le certificat du serveur qu’il ne reconnaît pas encore. Vous devez donc également créer un truststore. Un truststore est une valise contenant des certificats de confiance. Le client comparera le certificat, qu’il recevra pendant le processus SSL Handshake avec le contenu de son truststore. S’il y a une correspondance, alors le processus SSL Handshake continuera.Avant de créer les truststores, vous devez avoir les certificats du serveur. Vous pouvez l’obtenir avec la commande suivante :
Certificat d’exportation du serveur
keytool -v -exportcert -file shared-server-resources/src/main/resources/server.cer -alias server -keystore shared-server-resources/src/main/resources/identity.jks -storepass secret -rfc
Maintenant, vous pouvez créer le truststore pour le client et importer le certificat du serveur avec la commande suivante :
keytool -v -importcert -file shared-server-resources/src/main/resources/server.cer -alias server -keystore client/src/test/resources/truststore.jks -storepass secret -noprompt
Vous avez créé le truststore pour le client. Malheureusement, le client n’est pas au courant. Maintenant, vous devez dire qu’il doit utiliser le truststore avec l’emplacement et le mot de passe corrects. Vous devez également indiquer au client que l’authentification est activée. Fournissez la propriété suivante dans le application.yml
dossier du client :
client:
ssl:
one-way-authentication-enabled: true
two-way-authentication-enabled: false
trust-store: truststore.jks
trust-store-password: secret
Exiger que le client s’identifie (TLS bidirectionnel)
L’étape suivante consiste à exiger l’authentification du client. Cela forcera le client à s’identifier, et de cette façon, le serveur peut également valider l’identité du client et s’il est fiable ou non. Vous pouvez l’activer en indiquant au serveur que vous souhaitez également valider le client avec la propriété client-auth. Mettez les propriétés suivantes dans le application.yml
du serveur :
server:
port: 8443
ssl:
enabled: true
key-store: classpath:identity.jks
key-password: secret
key-store-password: secret
client-auth: need
Si vous exécutez le client, il échouera avec le message d’erreur suivant : javax.net.ssl.SSLHandshakeException: Received fatal alert: bad_certificate
. Cela indique que le certificat du client n’est pas valide car il n’y a pas de certificat du tout. Alors, créons-en un avec la commande suivante
keytool -v -genkeypair -dname...