Aller au contenu principal

RFC 6347 - 4.2. Protocole de poignée de main DTLS

4.2. The DTLS Handshake Protocol

DTLS utilise les mêmes messages et flux de handshake que TLS, avec trois changements principaux :

  1. Un échange de cookie sans état empêche les attaques par déni de service.

  2. L'en-tête de handshake est modifié pour gérer la perte, le réordonnancement et la fragmentation des messages DTLS (éviter la fragmentation IP).

  3. Des temporisateurs de retransmission gèrent la perte de messages.

Hormis ces points, les formats, flux et logique DTLS sont ceux de TLS 1.2.

4.2.1. Denial-of-Service Countermeasures

Les protocoles de sécurité à datagrammes sont très vulnérables aux attaques DoS. Deux cas importants :

  1. L'attaquant envoie des demandes de handshake en série pour épuiser les ressources du serveur (état, opérations cryptographiques coûteuses).

  2. L'attaquant utilise le serveur comme amplificateur en forgant la source de la victime ; le serveur envoie ensuite (p. ex. un gros Certificate) vers la victime.

Pour contrer les deux, DTLS reprend la technique de cookie sans état de Photuris [PHOTURIS] et IKE [IKEv2]. Lors du ClientHello, le serveur PEUT répondre par HelloVerifyRequest contenant un cookie généré comme dans [PHOTURIS]. Le client DOIT renvoyer ClientHello avec le cookie. Le serveur vérifie le cookie et ne continue que s'il est valide. L'attaquant doit pouvoir recevoir le cookie, ce qui rend difficile le DoS avec IP falsifiée. Aucune défense contre le DoS depuis des IP légitimes.

L'échange est illustré ci-dessous :

     Client                                   Server
------ ------
ClientHello ------>

<----- HelloVerifyRequest
(contains cookie)

ClientHello ------>
(with cookie)

[Rest of handshake]

DTLS modifie donc ClientHello pour ajouter le cookie.

struct {
ProtocolVersion client_version;
Random random;
SessionID session_id;
opaque cookie<0..2^8-1>; // New field
CipherSuite cipher_suites<2..2^16-1>;
CompressionMethod compression_methods<1..2^8-1>;
} ClientHello;

Lors du premier ClientHello, le client n'a pas encore de cookie ; le champ Cookie est vide (longueur nulle).

HelloVerifyRequest est défini comme suit :

struct {
ProtocolVersion server_version;
opaque cookie<0..2^8-1>;
} HelloVerifyRequest;

Le type de message HelloVerifyRequest est hello_verify_request(3).

server_version a la même syntaxe qu'en TLS. Pour éviter la négociation de version dans le handshake initial, les serveurs DTLS 1.2 DEVRAIENT utiliser la version DTLS 1.0 quelle que soit la version TLS attendue. Les clients DTLS 1.2 et 1.0 DOIVENT utiliser la version uniquement pour le format de paquet (identique en 1.2 et 1.0), pas pour la négociation. En particulier, un client DTLS 1.2 NE DOIT PAS déduire du 1.0 dans HelloVerifyRequest que le serveur n'est pas 1.2 ou qu'il négociera 1.0.

Répondant à HelloVerifyRequest, le client DOIT réemployer les mêmes paramètres (version, random, session_id, cipher_suites, compression_method) que dans le ClientHello initial. Le serveur DEVRAIT s'en servir pour générer et vérifier le cookie. Le serveur DOIT utiliser dans HelloVerifyRequest le même numéro de version que pour ServerHello. À réception de ServerHello, le client DOIT vérifier la cohérence des versions. Pour éviter les doublons de numéros de séquence avec plusieurs HelloVerifyRequest, le serveur DOIT reprendre le numéro de séquence d'enregistrement du ClientHello dans HelloVerifyRequest.

Note : la limite de taille du cookie est portée à 255 octets ; elle reste 32 pour les versions antérieures de DTLS.

Le serveur DTLS DEVRAIT générer des cookies vérifiables sans état par client. Une technique utilise un secret aléatoire :

Cookie = HMAC(Secret, Client-IP, Client-Parameters)

À la réception du second ClientHello, le serveur vérifie le cookie et que le client reçoit bien à cette adresse IP. Pour éviter les doublons lors d'échanges multiples de cookies, le serveur DOIT réutiliser le numéro de séquence d'enregistrement du ClientHello dans son ServerHello initial. Les ServerHello suivants n'interviennent qu'après création d'état et DOIVENT s'incrémenter normalement.

Un attaquant peut collecter des cookies sur plusieurs adresses et les réutiliser ; le serveur peut changer souvent Secret pour les invalider. Pour la transition entre secrets, une fenêtre acceptant les deux secrets est possible ; [IKEv2] propose un numéro de version dans le cookie ; on peut aussi vérifier avec les deux secrets.

Les serveurs DTLS DEVRAIENT faire un échange de cookie à chaque nouveau handshake. S'il n'y a pas de risque d'amplification, la désactivation est possible (MAY) ; par défaut l'échange DEVRAIT être actif. Lors d'une reprise de session, le serveur PEUT omettre l'échange. Les clients DOIVENT être prêts à un échange à chaque handshake.

Si HelloVerifyRequest est utilisé, le ClientHello initial et HelloVerifyRequest ne comptent pas dans handshake_messages (CertificateVerify) ni verify_data (Finished).

Un ClientHello avec cookie invalide DEVRAIT être traité comme sans cookie, pour éviter course critique ou blocage si le client a un mauvais cookie (changement de clé de signature côté serveur).

Note aux implémenteurs : plusieurs HelloVerifyRequest avec cookies différents peuvent arriver ; le client DEVRAIT répondre par un nouveau ClientHello avec cookie.

4.2.2. Handshake Message Format

Pour la perte, le réordonnancement et la fragmentation, DTLS modifie l'en-tête de handshake TLS 1.2 :

struct {
HandshakeType msg_type;
uint24 length;
uint16 message_seq; // New field
uint24 fragment_offset; // New field
uint24 fragment_length; // New field
select (HandshakeType) {
case hello_request: HelloRequest;
case client_hello: ClientHello;
case hello_verify_request: HelloVerifyRequest; // New type
case server_hello: ServerHello;
case certificate:Certificate;
case server_key_exchange: ServerKeyExchange;
case certificate_request: CertificateRequest;
case server_hello_done:ServerHelloDone;
case certificate_verify: CertificateVerify;
case client_key_exchange: ClientKeyExchange;
case finished: Finished;
} body;
} Handshake;

Le premier message de chaque côté dans chaque handshake a toujours message_seq = 0. Chaque nouveau message incrémente message_seq. Lors d'un re-handshake, HelloRequest a message_seq = 0 et ServerHello = 1. La retransmission réutilise le même message_seq. Exemple :

      Client                             Server
------ ------
ClientHello (seq=0) ------>

X<-- HelloVerifyRequest (seq=0)
(lost)

[Timer Expires]

ClientHello (seq=0) ------>
(retransmit)

<------ HelloVerifyRequest (seq=0)

ClientHello (seq=1) ------>
(with cookie)

<------ ServerHello (seq=1)
<------ Certificate (seq=2)
<------ ServerHelloDone (seq=3)

[Rest of handshake]

Du point de vue de la couche d'enregistrement DTLS, la retransmission est un nouvel enregistrement avec un nouveau DTLSPlaintext.sequence_number.

Les implémentations maintiennent un compteur next_receive_seq (au moins conceptuellement), initialement zéro. Si le numéro reçu égale next_receive_seq, on incrémente et on traite ; s'il est inférieur, on rejette (MUST) ; s'il est supérieur, on met en file d'attente (SHOULD) ou on rejette (MAY) (compromis espace/débit).

4.2.3. Handshake Message Fragmentation and Reassembly

Comme en 4.1.1, chaque message DTLS DOIT tenir dans un datagramme de la couche transport. Les messages de handshake peuvent dépasser la taille maximale d'enregistrement ; DTLS peut fragmenter sur plusieurs enregistrements transmis séparément pour éviter la fragmentation IP.

L'émetteur divise le message en N plages contiguës, sans dépasser la taille max de fragment, couvrant tout le message ; elles NE DEVRAIENT PAS se chevaucher. N messages handshake partagent le message_seq d'origine ; chaque fragment a fragment_offset et fragment_length ; length reste celui du message complet. Sans fragmentation : offset 0 et fragment_length = length.

Le récepteur DOIT mettre en buffer jusqu'à réception complète et DOIT accepter des plages qui se chevauchent (retransmissions avec fragments plus petits si la PMTU change).

Comme TLS, plusieurs messages du même vol peuvent partager un enregistrement DTLS s'il y a de la place ; deux messages dans un datagramme peuvent être dans le même enregistrement ou des enregistrements distincts.

4.2.4. Timeout and Retransmission

Les messages DTLS sont regroupés en « vols » selon les figures ci-dessous. Chaque vol peut contenir plusieurs messages mais est traité comme monolithique pour le timeout et la retransmission.

Client                                          Server
------ ------

ClientHello --------> Flight 1

<------- HelloVerifyRequest Flight 2

ClientHello --------> Flight 3

ServerHello \
Certificate* \
ServerKeyExchange* Flight 4
CertificateRequest* /
<-------- ServerHelloDone /

Certificate* \
ClientKeyExchange \
CertificateVerify* Flight 5
[ChangeCipherSpec] /
Finished --------> /

[ChangeCipherSpec] \ Flight 6
<-------- Finished /

Figure 1. Message Flights for Full Handshake
Client                                           Server
------ ------

ClientHello --------> Flight 1

ServerHello \
[ChangeCipherSpec] Flight 2
<-------- Finished /

[ChangeCipherSpec] \Flight 3
Finished --------> /

Figure 2. Message Flights for Session-Resuming Handshake
(No Cookie Exchange)

DTLS utilise un schéma simple de timeout et retransmission avec la machine d'états ci-dessous. Les clients envoient ClientHello en premier et démarrent en PREPARING. Les serveurs démarrent en WAITING avec buffers vides et sans temporisateur de retransmission.

                     +-----------+
| PREPARING |
+---> | | <--------------------+
| | | |
| +-----------+ |
| | |
| | Buffer next flight |
| | |
| \|/ |
| +-----------+ |
| | | |
| | SENDING |<------------------+ |
| | | | | Send
Receive | | | | HelloRequest
next | | Send flight | |
flight | +--------+ | | or
| | | Set retransmit timer | | Receive
| | \|/ | | HelloRequest
| | +-----------+ | | Send
| | | | | | ClientHello
+--)--| WAITING |-------------------+ |
| | | | Timer expires | |
| | +-----------+ | |
| | | | |
| | | | |
| | +------------------------+ |
| | Read retransmit |
Receive | | |
last | | |
flight | | |
| | |
\|/\|/ |
|
+-----------+ |
| | |
| FINISHED | -------------------------------+
| |
+-----------+
| /|\
| |
| |
+---+

Read retransmit
Retransmit last flight

Figure 3. DTLS Timeout and Retransmission State Machine

La machine a trois états de base.

En PREPARING, l'implémentation prépare le prochain vol, vide le buffer, stocke les messages et passe à SENDING.

En SENDING, elle envoie le vol ; si c'est le dernier vol du handshake, passage à FINISHED ; sinon temporisateur de retransmission et passage à WAITING.

Trois sorties de WAITING :

  1. Expiration du temporisateur : retour à SENDING, retransmission du vol, réinitialisation du temporisateur, retour à WAITING.

  2. Lecture d'un vol retransmis par le pair : même traitement ; la duplication indique souvent l'expiration du temporisateur chez le pair et une perte partielle de notre vol précédent.

  3. Réception du vol suivant : si final → FINISHED ; sinon → PREPARING. Les lectures partielles ne changent pas l'état ni le temporisateur.

Les clients démarrent en PREPARING, les serveurs en WAITING (buffers vides, pas de temporisateur).

Re-handshake serveur : FINISHED → PREPARING (HelloRequest). Client recevant HelloRequest : FINISHED → PREPARING (ClientHello).

Pendant au moins deux fois le MSL par défaut de [TCP], en FINISHED, le nœud qui a envoyé le dernier vol (serveur en handshake normal, client en reprise) DOIT répondre à une retransmission du dernier vol du pair par une retransmission de son propre dernier vol, pour éviter le blocage si ce vol est perdu. S'applique aussi à DTLS 1.0 ([DTLS1] ne le disait pas explicitement). Exemple : Finished serveur perdu → le client retransmet son Finished → le serveur renvoie Finished.

À cause des pertes, une extrémité peut envoyer des données applicatives avant que l'autre n'ait reçu Finished. Les implémentations DOIVENT rejeter ou mettre en buffer toutes les données applicatives de la nouvelle epoch jusqu'à réception du Finished correspondant. Elles PEUVENT interpréter des données applicatives d'une nouvelle epoch avant Finished comme réordonnancement/perte et retransmettre immédiatement le dernier vol.

4.2.4.1. Timer Values

Les valeurs sont au choix de l'implémentation ; une mauvaise gestion provoque de la congestion. Valeur initiale DEVRAIT être 1 s (minimum RFC 6298 [RFC6298]), doublée à chaque retransmission jusqu'au moins 60 s (maximum RFC 6298). 1 s plutôt que 3 s par défaut pour la latence. DTLS ne retransmet que le handshake.

Conserver la valeur courante jusqu'à une transmission sans perte, puis réinitialiser possible. Après longue inactivité (≥ 10 × valeur actuelle), réinitialisation possible (re-handshake après gros transfert).

4.2.5. ChangeCipherSpec

Comme en TLS, ChangeCipherSpec n'est pas techniquement un message de handshake mais DOIT être traité dans le même vol que le Finished associé pour timeout et retransmission, ce qui peut créer une ambiguïté d'ordre en cas de perte.

Les modes TLS actuels restent prévisibles ; les modes futurs DOIVENT éviter l'ambiguïté.

4.2.6. CertificateVerify and Finished Messages

Même format qu'en TLS. Les hachages incluent les messages entiers avec message_seq, fragment_offset, fragment_length. Le MAC Finished DOIT être calculé comme si chaque message handshake avait été envoyé en un seul fragment. Avec échange de cookies, le ClientHello initial et HelloVerifyRequest NE DOIVENT PAS entrer dans CertificateVerify ni Finished.

4.2.7. Alert Messages

Les Alert ne sont jamais retransmises, même pendant le handshake. Une implémentation qui émettrait une alerte DEVRAIT en générer une nouvelle si l'enregistrement fautif revient. Détecter un pair qui envoie continuellement de mauvais messages et terminer l'état local.

4.2.8. Establishing New Associations with Existing Parameters

Si le même quartet hôte/port voit des connexions répétées, un client peut abandonner silencieusement une connexion puis en rouvrir une autre (reboot) : le serveur voit un handshake epoch=0. S'il croit détenir une association, il DEVRAIT poursuivre le nouveau handshake mais NE DOIT PAS détruire l'ancienne tant que le client n'a pas prouvé l'atteignabilité (cookie ou handshake complet avec Finished vérifiable). Après un Finished correct, il DOIT abandonner l'ancienne association pour éviter deux associations valides aux epochs qui se chevauchent. L'exigence d'atteignabilité empêche un attaquant hors chemin de détruire l'association avec de simples ClientHello forgés.