Même déroulement que Parcours complet — Du certificat à la connexion HTTPS, mais avec authentification mutuelle : cette fois deux certificats doivent être fabriqués à l’avance (serveur ET client), et deux preuves de possession de clé privée ont lieu pendant le handshake, pas une seule. Lire d’abord la version TLS standard si ce n’est pas encore fait — cette note ne détaille que ce qui change.
Ce qui est différent, en un coup d’œil
| TLS standard | mTLS | |
|---|---|---|
| Certificats à fabriquer en Phase 1 | 1 (serveur) | 2 (serveur + client) |
| CA typique | CA publique (Let’s Encrypt) | Serveur : CA publique. Client : CA privée, interne |
CertificateVerify pendant le handshake | 1 (le serveur prouve sa clé) | 2 (le serveur ET le client prouvent chacun leur clé) |
| Ce que le serveur doit connaître en plus | — | Le certificat de la CA privée, pour pouvoir vérifier les certificats clients |
PHASE 1 — Fabrication des DEUX certificats
1a. Génération de la clé du serveur — site.key, comme dans le cas standard.
1b. Génération de la clé du client — client.key, une paire complètement différente, appartenant à l’entité cliente (un service, un utilisateur, un device…).
2a. CSR du serveur, signé avec site.key — identique au cas standard.
2b. CSR du client, signé avec client.key :
Bloc 3 du CSR client = signature(hash(Bloc 1 du CSR client), clé_privée_DU_CLIENT)
Même logique que le CSR serveur — prouve que le client possède bien la clé privée correspondant à la clé publique qu’il propose.
3. Deux chemins de validation différents, en pratique :
- Le certificat serveur passe généralement par une CA publique (Let’s Encrypt/ACME, challenge HTTP-01/DNS-01) — il doit être reconnu par n’importe quel visiteur anonyme.
- Le certificat client passe généralement par une CA privée/interne — toi seul contrôles qui a le droit d’en obtenir un, pas besoin qu’un navigateur grand public lui fasse confiance. Voir PKI — Infrastructure à Clés Publiques, section “PKI publique vs PKI privée”.
4. Signature de chacun des deux certificats :
Bloc 3 du certificat SERVEUR = signature(hash(Bloc 1), clé_privée_DE_LA_CA_PUBLIQUE)
Bloc 3 du certificat CLIENT = signature(hash(Bloc 1), clé_privée_DE_LA_CA_PRIVÉE)
Deux CA différentes, deux clés privées de CA différentes — rien à voir l’une avec l’autre.
5. Déploiement — la partie qui change vraiment :
Côté SERVEUR, il faut maintenant TROIS choses (pas deux) :
fullchain.pem ← son propre certificat (public), envoyé aux clients
site.key ← sa propre clé privée
ca-interne.crt ← le certificat de la CA PRIVÉE, pour pouvoir
VÉRIFIER les certificats clients qui se présenteront
Côté CLIENT, il faut :
client-fullchain.pem ← son certificat (signé par la CA privée)
client.key ← sa clé privée
Exemple nginx :
ssl_certificate /etc/ssl/fullchain.pem;
ssl_certificate_key /etc/ssl/site.key;
ssl_client_certificate /etc/ssl/ca-interne.crt; # nouveau : pour vérifier les clients
ssl_verify_client on; # exige un certificat client valideSans ssl_client_certificate, le serveur n’aurait tout simplement aucun moyen de vérifier un certificat client — même parfaitement valide, il n’aurait pas la clé publique de la CA privée qui l’a signé pour vérifier la chaîne.
PHASE 2 — Une connexion mTLS réelle
6-9. DNS, TCP, ClientHello, ServerHello — strictement identique au TLS standard. L’échange Diffie-Hellman (key_share) ne change absolument rien en mTLS — voir Diffie-Hellman — Échange de clés. Le secret partagé, et la clé symétrique qui en dérive, sont établis exactement pareil.
10. Le serveur envoie (chiffré) :
Certificate— sonfullchain.pemCertificateRequest— nouveau : “présente-moi aussi ton certificat”CertificateVerify— preuve de possession desite.key, comme en TLS standardFinished
11. Le client vérifie le serveur — exactement les étapes 12-14 du parcours TLS standard (chaîne de certificats, CertificateVerify, domaine) : rien de différent ici.
12. Le client envoie à son tour (chiffré) :
Certificate— son propre certificat, signé par la CA privéeCertificateVerify— hash de tout le transcript jusqu’ici (incluant maintenant leCertificatedu serveur ET le sien), signé avecclient.key:
CertificateVerify_client = signature(hash(transcript complet), clé_privée_DU_CLIENT)
Finished
13. Le serveur vérifie le client — miroir exact des étapes 12-13 du parcours standard, mais appliqué au certificat client :
a) Vérifier la chaîne du certificat client :
recalculer hash(Bloc 1 du cert client)
comparer à ce qu'on extrait du Bloc 3 en déchiffrant avec
la clé publique de la CA PRIVÉE (celle dans ca-interne.crt)
b) Vérifier CertificateVerify_client :
recalculer hash(transcript), comparer à ce qu'on extrait en
déchiffrant avec la clé publique DU CLIENT (trouvée dans son certificat)
14. Données chiffrées — comme toujours, avec la clé symétrique issue du secret Diffie-Hellman (étapes 8-9), sans aucun rapport avec les 4 opérations de signature/vérification qui viennent d’avoir lieu (2 pour le serveur, 2 pour le client).
Les 5 paires de clés à ne jamais confondre (2 de plus qu’en TLS standard)
| Paire de clés | Qui l’utilise | Rôle |
|---|---|---|
| Clé du serveur | Serveur (CSR, CertificateVerify serveur) | Authentifier le serveur |
| Clé de la CA publique | CA publique (signe le cert serveur) | Vérifiée par le client, via trust store |
| Clé du client | Client (CSR, CertificateVerify client) | Authentifier le client — nouveau en mTLS |
| Clé de la CA privée | CA privée (signe le cert client) | Vérifiée par le serveur, via ca-interne.crt — nouveau |
| Clé DH éphémère | Les deux, à chaque connexion | Chiffre les données, sans rapport avec l’identité |
En relation avec
- Parcours complet — Du certificat à la connexion HTTPS — la version TLS standard, base de cette note
- TLS et SSL — section mTLS, schémas mtls_handshake.svg et tls_vs_mtls.svg
- PKI — Infrastructure à Clés Publiques — PKI publique vs privée, pourquoi le client passe généralement par une CA interne
- Diffie-Hellman — Échange de clés — l’échange de clé, strictement identique en mTLS