Test des clients HTTPS à l’aide d’openssl pour simuler un serveur
Cet article explique comment tester votre client ou navigateur HTTPS à l’aide d’openssl. Pour tester votre client HTTPS, vous avez besoin d’un serveur HTTPS ou d’un serveur Web, tel que IIS, apache, nginx ou openssl. Vous avez également besoin de quelques cas de test. Il existe trois modes de défaillance courants dans SSL/TLS :
- Le client établit la connexion alors qu’il ne devrait pas,
- La connexion échoue alors qu’elle devrait réussir, et
- La connexion est correctement établie, mais les données sont corrompues lors de la transmission.
- Il existe un 4ème mode de défaillance : il se peut que les données ne soient pas transmises de manière sécurisée. Ce mode d’échec n’est pas dans le cadre de cet article.
Pour nous assurer que les problèmes découverts lors des tests sont dus à des problèmes dans votre client HTTPS, nous voulons utiliser un serveur HTTPS « dont le fonctionnement a été vérifié ». On veut aussi un serveur qui soit « pédant » ou « impitoyable ». OpenSSL répond précisément à ces exigences.
Dans cet article, je vais décrire comment utiliser la commande openssl s_server pour être un serveur HTTPS. Il y a beaucoup d’éléments de configuration qui doivent être parfaits, donc je ne vais pas seulement vous montrer comment le faire correctement, mais je vais également partager avec vous ce qui n’a pas fonctionné et comment je l’ai diagnostiqué et corrigé.
Un « client » est un ordinateur ou un programme informatique qui initie une connexion à un « serveur ». Un « serveur » est un programme informatique qui attend l’arrivée d’une connexion d’un « client ». Pour HTTP et HTTPS, il existe des « navigateurs » et des « clients ». Les navigateurs sont conçus pour interagir avec les humains et ont généralement des interfaces utilisateur graphiques. Tous les navigateurs sont des clients HTTP/HTTPS.
Cependant, il existe des clients HTTP/HTTPS qui ne sont pas des navigateurs. Ces clients sont conçus pour être utilisés comme des systèmes automatisés. Le concepteur de serveur avisé s’assurera que son système peut être utilisé efficacement avec des clients HTTPS qui sont des navigateurs et des clients HTTPS qui ne sont pas des navigateurs.
Dans ce tutoriel, vous allez apprendre :
- Comment choisir un bon client ou navigateur HTTPS
- Comment utiliser openssl comme serveur HTTPS
- Comment utiliser un serveur HTTPS pour tester un client HTTPS
Configuration logicielle requise et conventions utilisées
Comment tester votre client HTTPS étape par étape Instructions
J’utiliserai les adjectifs « vertueux » pour indiquer qu’un test a fait quelque chose correctement, et « erroné » pour indiquer qu’un test a fait quelque chose de mal. Si un test échoue alors qu’il le devrait, alors c’est un échec juste. Si un test réussit alors qu’il ne devrait pas, alors c’est une réussite erronée.
Je voulais utiliser un client HTTPS que je pourrais casser et réparer à volonté, et je l’ai trouvé : la commande http (c’est dans github comme httpie). Si j’utilise l’option -verify=no, alors le client est cassé : il passera par erreur les tests. Je n’ai pas réussi à créer un échec erroné, et c’est une bonne chose car cela signifie que si le client échoue, c’est que quelque chose ne va pas.
Au cœur du protocole SSL/TLS (ils ont changé le nom et rien d’autre) se trouvent deux fichiers, un « certificat » (ou « cert » en abrégé) et une « clé » secrète. Partout dans le protocole, une extrémité de la connexion demandera un certificat à l’autre extrémité. La première extrémité utilisera certaines des informations du certificat pour créer un puzzle mathématique auquel seul quelque chose qui a la clé secrète peut répondre. La clé secrète ne quitte jamais sa machine : résoudre le problème signifie que l’extrémité proche sait que l’extrémité éloignée a la clé, mais pas ce qu’est la clé.
La commande openssl est essentiellement une interface de ligne de commande pour libssl. Il contient un serveur rudimentaire appelé avec la sous-commande s_server. OpenSSL aura besoin d’une paire de clés publiques Cert/privées. Dans mon cas, je les avais déjà pour mon serveur web de production. Je les ai obtenus de let’s encrypt, gratuitement.
Comme preuve de concept que le serveur fonctionne correctement, j’ai copié le certificat et la clé sur ma machine de développement et démarré le serveur HTTPS openssl.
Côté serveur :
openssl s_server -status_verbose -HTTP -cert fullchain.pem -key privkey.pem
Using default temp DH parameters
ACCEPT
Ma première tentative a échoué !
http --verify=yes jeffs-desktop:4433/index.html
http: error: ConnectionError: (Connection aborted., RemoteDisconnected(Remote end closed connection without response)) while doing GET request to URL: http://jeffs-desktop:4433/index.html
Première hypothèse : la clé et le cert ne correspondent pas. J’ai vérifié que :
openssl x509 -noout -modulus -in fullchain.pemls | openssl md5
(stdin)= b9dbd040d9a0c3b5d3d50af46bc87784
openssl rsa -noout -modulus -in privkey.pem | openssl md5
(stdin)= b9dbd040d9a0c3b5d3d50af46bc87784
Ils correspondent. Alors pourquoi cela échoue-t-il ? Parce que mon certificat est pour linuxconfig.dns.net mais j’utilise jeffs-desktop comme nom d’hôte.
jeffs@jeffs-desktop:~/documents$ openssl x509 -text -noout -in fullchain.pem | fgrep CN
Issuer: C = US, O = Let’s Encrypt, CN = R3
Subject: CN = linuxconfig.ddns.net
Il s’agit d’un échec légitime : le serveur a été mal configuré et mon client l’a détecté. Si j’avais utilisé l’option
-verify=no, j’aurais eu un client cassé et il n’aurait pas détecté le problème. Notez que toutes les données transmises seraient toujours protégées contre les écoutes clandestines. Je peux résoudre ce problème en modifiant mon fichier /etc/hosts avec mes propres adresses IPv4 et IPv6.
192.168.1.149 linuxconfig.ddns.net
2601:602:8500:b65:155a:7b81:65c:21fa linuxconfig.ddns.net
(soit dit en passant, la facilité avec laquelle vous pouvez falsifier une adresse IP est l’une des motivations de SSL/TLS en premier lieu).
Réessayez. Côté serveur :
openssl s_server -status_verbose -HTTP -cert fullchain.pem -key privkey.pem
Using default temp DH parameters
ACCEPT
Côté client :
http --verify=yes https://linuxconfig.ddns.net:4433/index.html
On the server side, I get the error message:
140101997737280:error:14094418:SSL routines:ssl3_read_bytes:tlsv1 alert unknown ca:../ssl/record/rec_layer_s3.c:1543:SSL alert number 48
On the client side, I get the error message:
http: error: SSLError: HTTPSConnectionPool(host='linuxconfig.ddns.net', port=4433): Max retries exceeded with url: / (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1131)'))) while doing GET request to URL: https://linuxconfig.ddns.net:4433/
Ce message d’erreur, CERTIFICATE_VERIFY_FAILED, est un indice important : cela signifie que l’autorité de certification (CA) du certificat n’a pas pu être vérifiée. Étant donné que le client n’a pas pu vérifier le certificat, s’il n’a pas réussi à établir la connexion. C’est un autre échec juste.
Le certificat lui-même pourrait être falsifié – et le client n’a aucun moyen de le savoir. Toutefois, le certificat fait référence à une autorité de certification (CA) et l’AC sait que le certificat est valide ou rejette la vérification. Comment savons-nous que l’AC est digne de confiance ?
L’autorité de certification elle-même dispose d’un certificat, d’un certificat intermédiaire, et ce certificat fait référence à une autre autorité de certification. Finalement, cette chaîne de certificats atteint un certificat racine. Un certificat racine se signe lui-même et est donc, par définition, digne de confiance. Dans ce cas, quelque chose a mal tourné avec cette chaîne de certificats, cette chaîne de confiance.
openssl s_client -showcerts -connect linuxconfig.ddns.net:4433
CONNECTED(00000003)
depth=0 CN = linuxconfigan.ddns.net
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 CN = linuxconfigan.ddns.net
verify error:num=21:unable to verify the first certificate
verify return:1
---
Certificate chain
0 s:CN = linuxconfigan.ddns.net
i:C = US, O = Let's Encrypt, CN = R3
-----BEGIN CERTIFICATE-----
Je sais que mon serveur de production fonctionne correctement. Voici à quoi la chaîne est censée ressembler (notez le numéro de port 443, pas 4433) :
openssl s_client -showcerts -connect linuxconfig.ddns.net:443
CONNECTED(00000003)
depth=2 C = US, O = Internet Security Research Group, CN = ISRG Root X1
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = R3
verify return:1
depth=0 CN = linuxconfig.ddns.net
verify return:1
---
Certificate chain
0 s:CN = linuxconfig.ddns.net
i:C = US, O = Let's Encrypt, CN = R3
-----BEGIN CERTIFICATE-----
MIIFYjCCBEqgAwIBAgISA0MTOSmISSsIyRls8O/2XpAaMA0GCSqGSIb3DQEBCwUA
...
-----END CERTIFICATE-----
1 s:C = US, O = Let's Encrypt, CN = R3
i:C = US, O = Internet Security Research Group, CN = ISRG Root X1
-----BEGIN CERTIFICATE----
...
-----END CERTIFICATE-----
2 s:C = US, O = Internet Security Research Group, CN = ISRG Root X1
i:O = Digital Signature Trust Co., CN = DST Root CA X3
-----BEGIN CERTIFICATE-----
…
Il y a deux façons de procéder à partir de là : je peux désactiver la vérification des certificats ou je peux ajouter le certificat de Let’s Encrypt à la liste des autorités de certification connues. La désactivation de la vérification est rapide et sûre. L’ajout de l’AC à la liste des AC connus est plus obscur. Faisons les deux. Côté serveur, je n’ai rien touché. Côté client, je désactive la vérification et j’obtiens :
http –verify=no https://linuxconfig.ddns.net:4433/index.html
http: error: ConnectionError: ('Connection aborted.', BadStatusLine('\n')) while doing GET request to URL: https://linuxconfig.ddns.net:4433/index.html
echo $?
1
Ce message d’erreur m’indique qu’il y a eu une violation du protocole HTTP (et non HTTPS). Le serveur a servi la première ligne du fichier, index.html, alors qu’il aurait dû renvoyer un bloc d’en-tête de retour HTTP. Il s’agit d’une faille côté serveur, qui casserait tous les clients HTTP. Un examen attentif de la documentation me dit d’utiliser l’option -WWW (et non -www) avec openssl, au lieu de l’option -HTTP. Je fais cela :
openssl s_server -status_verbose -WWW -cert fullchain.pem -key privkey.pem et cela fonctionne correctement, avec la mise en garde que je n’ai pas encore réussi à faire fonctionner la validation du certificat.
http -verify=no https://linuxconfig.ddns.net:4433/helloworld.c
HTTP/1.0 200 ok
Content-type: text/plain
#include
int main (int argc, char *argv[]) {
printf("Hello, world\n\n");
}
Comme j’ai utilisé -verify=no, il s’agit en fait d’une passe erronée.
Pour vérifier que ma chaîne de certificats est valide, je peux utiliser la commande openssl verify :
openssl verify -purpose sslserver fullchain.pem
CN = linuxconfig.ddns.net
error 20 at 0 depth lookup: unable to get local issuer certificate
error cert.pem: verification failed
La solution rapide a été d’essayer la commande openssl s_server sur mon serveur web de production, en utilisant les fichiers de configuration de production. C’est (raisonnablement) sûr à faire car le serveur openssl fonctionnera sur le port 4433 tandis que mon serveur de production fonctionnera sur le port 443.
openssl s_server -status_verbose -WWW \
-cert /etc/letsencrypt/live/linuxconfig.ddns.net/fullchain.pem \
-key /etc/letsencrypt/live/linuxconfig.ddns.net/privkey.pem -accept 4433
Hmm. Nginx travaille comme un champion. OpenSSL ne l’est pas. C’est pourquoi openssl est un meilleur banc d’essai que nginx : si la configuration de nginx est incorrecte, il essaiera de se débrouiller. Si la configuration d’openssl est incorrecte, il vous appellera dessus. La configuration d’OpenSSL est stockée dans /etc/ssl/openssl.cnf.
Il est dit que les certificats de l’autorité de certification sont dans /etc/ssl/certs . Le certificat racine de l’Internet Services Research Group (ISRG) s’y trouve. Mais le certificat intermédiaire de Letch ne l’est pas. Cela a du sens d’une certaine manière : Let’s encrypt a un certbot merveilleux qui savait tout sur nginx quand je l’ai exécuté, mais je n’ai pas exécuté certbot avec openssl, donc le certificat de let’s encrypt n’était pas dans /etc/ssl/certs/ . J’ai obtenu le certificat de let’s encrypt avec le :
wget https://letsencrypt.org/certs/lets-encrypt-r3.pem
La commande ci-dessus, a copié le fichier lets_encrypt_r3.pem dans /etc/ssl/certs/ , a lancé le programme c_rehash, et voilà :
openssl verify -CApath /etc/ssl/certs/ \ /etc/letsencrypt/live/linuxconfig.ddns.net/fullchain.pem
/etc/letsencrypt/live/linuxconfig.ddns.net/fullchain.pem: OK
C’est bien, mais le test est, puis-je voir helloworld.c ?
http --verify=yes https://linuxconfig.ddns.net:4433/helloworld.c
HTTP/1.0 200 ok
Content-type: text/plain
#include
int main (int argc, char *argv[]) {
printf("Hello, world\n\n");
}
Oui. J’ai maintenant vérifié que mon client HTTPS fonctionnel réussira et échouera à juste titre, au moins pour les cas de test avec lesquels j’ai travaillé. Il y a d’autres choses qui ne vont pas avec SSL/TLS, comme les listes de révocation de certificats (CRL), mais j’espère que vous avez une bonne idée.
Ensuite, je veux vérifier que les fichiers envoyés entre le serveur HTTPS openssl et mon client HTTPS ne seront pas corrompus, pas même d’un bit. Je ne peux pas vérifier que chaque fichier sera transmis sans erreur, mais ce que je peux faire, c’est transmettre un fichier binaire volumineux, vérifier qu’il a été transmis correctement, puis en déduire que les fichiers volumineux ne seront pas corrompus.
J’ai utilisé la commande ls -lorS pour trouver un fichier volumineux, calculé sa somme SHA256, l’ai transmise en utilisant openssl comme serveur, enregistré le fichier reçu et calculé la somme SHA256 sur ce fichier. Les sommes de 256 SHA devraient correspondre.
Côté serveur :
ls -lorS | tail -1
-rw-rw-r-- 1 jeffs 121329853 May 23 2020 CybersecurityEssentials.pdf
sha256sum CybersecurityEssentials.pdf
49a49c8e525a3d6830fce1c1ee0bfce2d3dd4b000eeff5925b074802e62024e0 CybersecurityEssentials.pdf
Côté client :
http --verify=no https://linuxconfig.ddns.net:4433/CybersecurityEssentials.pdf -o /tmp/CybersecurityEssentials.pdf
sha256sum /tmp/CybersecurityEssentials.pdf
49a49c8e525a3d6830fce1c1ee0bfce2d3dd4b000eeff5925b074802e62024e0 /tmp/CybersecurityEssentials.pdf
Ce fichier PDF fait 121 Mo, assez grand pour mes besoins. Les sommes SHA256 correspondent, de sorte que le fichier a été transmis correctement.
Conclusion
Dans cet article, j’ai décrit les modes de défaillance courants du protocole HTTPS. J’ai utilisé quelques critères pour sélectionner un serveur HTTPS à utiliser pour tester un client HTTPS, et j’ai sélectionné openssl. J’ai choisi un client HTTPS facile à utiliser. J’ai montré certains modes de défaillance courants et j’ai observé que le client détectait ces défaillances.
La partie la plus difficile était de configurer openssl correctement, alors j’ai montré ce qui peut mal tourner et comment y remédier. Enfin, j’ai démontré qu’en utilisant openssl comme serveur et mon client HTTPS, je pouvais transmettre un fichier sans corruption de données.

