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 standardmTLS
Certificats à fabriquer en Phase 11 (serveur)2 (serveur + client)
CA typiqueCA publique (Let’s Encrypt)Serveur : CA publique. Client : CA privée, interne
CertificateVerify pendant le handshake1 (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 valide

Sans 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 — son fullchain.pem
  • CertificateRequest — nouveau : “présente-moi aussi ton certificat”
  • CertificateVerify — preuve de possession de site.key, comme en TLS standard
  • Finished

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ée
  • CertificateVerify — hash de tout le transcript jusqu’ici (incluant maintenant le Certificate du serveur ET le sien), signé avec client.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ésQui l’utiliseRôle
Clé du serveurServeur (CSR, CertificateVerify serveur)Authentifier le serveur
Clé de la CA publiqueCA publique (signe le cert serveur)Vérifiée par le client, via trust store
Clé du clientClient (CSR, CertificateVerify client)Authentifier le client — nouveau en mTLS
Clé de la CA privéeCA privée (signe le cert client)Vérifiée par le serveur, via ca-interne.crt — nouveau
Clé DH éphémèreLes deux, à chaque connexionChiffre les données, sans rapport avec l’identité

En relation avec