RFC 6347 - 4.2. DTLS-Handshake-Protokoll
4.2. The DTLS Handshake Protocol
DTLS verwendet dieselben Handshake-Nachrichten und -abläufe wie TLS mit drei Hauptänderungen:
-
Zustandsloser Cookie-Austausch zum Schutz vor Denial-of-Service.
-
Angepasster Handshake-Header für Nachrichtenverlust, Umordnung und DTLS-Fragmentierung (Vermeidung von IP-Fragmentierung).
-
Retransmissions-Timer für Nachrichtenverlust.
Abgesehen davon entsprechen Formate, Abläufe und Logik TLS 1.2.
4.2.1. Denial-of-Service Countermeasures
Datagramm-Sicherheitsprotokolle sind sehr anfällig für DoS. Zwei Angriffe sind besonders relevant:
-
Der Angreifer erschöpft Serverressourcen durch viele Handshake-Starts (Zustand, teure Kryptoberechnungen).
-
Der Angreifer nutzt den Server als Verstärker, indem er die Opferadresse fälscht; der Server sendet große Nachrichten (z. B. Certificate) an das Opfer.
DTLS übernimmt den zustandslosen Cookie-Mechanismus von Photuris [PHOTURIS] und IKE [IKEv2]. Sendet der Client ClientHello, KANN der Server mit HelloVerifyRequest antworten, das einen zustandslosen Cookie gemäß [PHOTURIS] enthält. Der Client MUSS ClientHello mit Cookie erneut senden. Der Server prüft den Cookie und setzt nur bei Gültigkeit fort. Der Angreifer/Client muss den Cookie empfangen können, was IP-Spoofing-DoS erschwert. Schutz vor gültigen IP-Adressen besteht nicht.
Client Server
------ ------
ClientHello ------>
<----- HelloVerifyRequest
(contains cookie)
ClientHello ------>
(with cookie)
[Rest of handshake]
DTLS erweitert ClientHello um das Cookie-Feld.
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;
Beim ersten ClientHello ist das Cookie-Feld leer (Länge null).
struct {
ProtocolVersion server_version;
opaque cookie<0..2^8-1>;
} HelloVerifyRequest;
Nachrichtentyp hello_verify_request(3).
server_version hat TLS-Syntax. Um Versionsverhandlung im ersten Handshake zu vermeiden, SOLLTEN DTLS-1.2-Server DTLS 1.0 angeben, unabhängig vom erwarteten TLS. DTLS-1.2- und 1.0-Clients MÜSSEN die Version nur für das Paketformat nutzen (identisch in 1.2 und 1.0), nicht zur Verhandlung. Insbesondere DARF ein DTLS-1.2-Client nicht aus HelloVerifyRequest mit Version 1.0 schließen, der Server sei nicht 1.2 oder werde 1.0 verhandeln.
Auf HelloVerifyRequest MUSS der Client dieselben Parameter (version, random, session_id, cipher_suites, compression_method) wie im ursprünglichen ClientHello verwenden. Der Server SOLLTE daraus den Cookie erzeugen und bei Empfang prüfen. Der Server MUSS in HelloVerifyRequest dieselbe Versionsnummer verwenden wie später in ServerHello. Der Client MUSS die Serverversionen abgleichen. Um doppelte Sequenznummern bei mehreren HelloVerifyRequest zu vermeiden, MUSS der Server die Record-Sequenznummer aus ClientHello in HelloVerifyRequest übernehmen.
Hinweis: Cookie-Limit 255 Byte für künftige Flexibilität; frühere DTLS-Versionen bleiben bei 32.
Der Server SOLLTE Cookies ohne per-Client-Zustand verifizierbar erzeugen, z. B.:
Cookie = HMAC(Secret, Client-IP, Client-Parameters)
Beim zweiten ClientHello prüft der Server Cookie und Erreichbarkeit. Bei mehrfachen Cookie-Runden MUSS der Server die Record-Sequenznummer aus ClientHello im ersten ServerHello spiegeln; weitere ServerHellos erhöhen normal nach Zustandserzeugung.
Angreifer könnten Cookies sammeln und wiederverwenden; häufiger Wechsel von Secret invalidiert sie. Übergangsfenster mit zwei Secrets ist möglich; [IKEv2] schlägt Versionsnummern in Cookies vor, alternativ Prüfung mit beiden Secrets.
Server SOLLTEN bei neuem Handshake Cookie-Austausch durchführen. Ohne Verstärker-Risiko KANN der Server ihn deaktivieren; Standard SOLLTEN Austausch sein. Bei Session-Resume KANN der Server den Cookie-Schritt auslassen. Clients MÜSSEN immer mit Cookie rechnen.
Wird HelloVerifyRequest genutzt, fließen initiales ClientHello und HelloVerifyRequest nicht in handshake_messages (CertificateVerify) und verify_data (Finished) ein.
Empfängt der Server einen ungültigen Cookie, SOLLTE er wie ohne Cookie behandeln, um Wettlauf/Sperre bei Schlüsselwechsel zu vermeiden.
Hinweis für Implementierer: Mehrere HelloVerifyRequest mit unterschiedlichen Cookies sind möglich; Clients SOLLTEN mit neuem ClientHello (Cookie) antworten.
4.2.2. Handshake Message Format
Für Verlust, Umordnung und Fragmentierung ändert DTLS den TLS-1.2-Handshake-Header:
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;
Die erste Nachricht jeder Seite hat message_seq = 0; jede neue Nachricht erhöht um 1. Bei Re-Handshake hat HelloRequest 0 und ServerHello 1. Retransmissionen behalten message_seq:
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]
Aus Sicht der Record-Schicht ist die Retransmission ein neuer Record mit neuer DTLSPlaintext.sequence_number.
Implementierungen führen (mindestens konzeptionell) next_receive_seq, anfangs 0. Stimmt die empfangene Sequenz mit next_receive_seq überein, wird erhöht und verarbeitet. Ist sie kleiner, MUSS verworfen werden. Ist sie größer, SOLLTE gepuffert, KANN aber verworfen werden (Speicher/Bandbreite).
4.2.3. Handshake Message Fragmentation and Reassembly
Wie in 4.1.1 MUSS jede DTLS-Nachricht in ein Transport-Datagramm passen; Handshake-Nachrichten können größer sein. DTLS fragmentiert über mehrere Records, um IP-Fragmentierung zu vermeiden.
Der Sender teilt in N zusammenhängende Bereiche, jeweils höchstens maximale Fragmentgröße, zusammen die ganze Nachricht. Sie SOLLTEN sich nicht überlappen. Es entstehen N Handshake-Nachrichten mit gleichem message_seq, fragment_offset und fragment_length; length bleibt die des Originals. Unfragmentiert: fragment_offset=0, fragment_length=length.
Der Empfänger MUSS puffern, bis die komplette Nachricht vorliegt. Überlappende Fragmente MÜSSEN unterstützt werden (kleinere Fragmente nach PMTU-Änderung).
Wie bei TLS können mehrere Handshake-Nachrichten derselben Flight in einem DTLS-Record stehen, wenn Platz ist; zwei Nachrichten können im selben Datagramm im selben oder getrennten Records stehen.
4.2.4. Timeout and Retransmission
Nachrichten werden zu Flights gruppiert; für Timer und Retransmission gilt jede Flight als monolithisch.
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 /
Abbildung 1. Nachrichtenflüge für vollständigen Handshake
Client Server
------ ------
ClientHello --------> Flight 1
ServerHello \
[ChangeCipherSpec] Flight 2
<-------- Finished /
[ChangeCipherSpec] \Flight 3
Finished --------> /
Abbildung 2. Flüge bei Session-Resume (ohne Cookie)
DTLS nutzt Timeouts und Retransmission mit folgendem Zustandsautomaten. Clients starten in PREPARING (erste Nachricht ClientHello), Server in WAITING mit leeren Puffern ohne Timer.
+-----------+
| 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
Abbildung 3. DTLS-Timeout- und Retransmissions-Automat
Es gibt drei Grundzustände:
PREPARING: nächste Flight vorbereiten, Puffer leeren und füllen, nach SENDING wechseln.
SENDING: Flight senden; wenn letzte Flight des Handshakes, FINISHED; sonst Timer setzen und WAITING.
Drei Ausgänge aus WAITING:
-
Timer abgelaufen: nach SENDING, Flight erneut senden, Timer zurücksetzen, zurück WAITING.
-
Retransmission der Gegenseite gelesen: ebenfalls nach SENDING, eigene Flight erneut senden (Duplikat deutet auf Peer-Timeout und Verlust unserer vorherigen Flight hin).
-
Nächste Flight empfangen: wenn final, FINISHED; sonst PREPARING. Teilweise gelesene Nachrichten ändern Zustand/Timer nicht.
Clients starten PREPARING, Server WAITING.
Server-Re-Handshake: FINISHED → PREPARING (HelloRequest). Client bei HelloRequest: FINISHED → PREPARING (ClientHello).
Mindestens doppelt so lange wie Standard-MSL von [TCP] MUSS in FINISHED der Knoten, der die letzte Flight sendet (Server im normalen Handshake, Client bei Resume), auf Retransmission der letzten Gegenflight mit eigener letzter Flight antworten, um Deadlock bei Verlust zu vermeiden. Gilt auch für DTLS 1.0 (implizit in [DTLS1] erforderlich). Beispiel: Verlust von Server-Finished – Client-Timer feuert, Client sendet Finished erneut, Server antwortet mit Finished.
Bei Paketverlust kann eine Seite Anwendungsdaten senden, bevor die andere Finished erhält. Implementierungen MÜSSEN Anwendungsdaten der neuen Epoch verwerfen oder puffern, bis Finished für diese Epoch eintrifft. Sie KÖNNEN frühe Daten mit neuer Epoch als Umordnung/Verlust werten und sofort die eigene letzte Flight erneut senden.
4.2.4.1. Timer Values
Timerwerte sind implementationsabhängig; falsche Werte können Überlast erzeugen. Initiale Zeit SOLLTE 1 Sekunde sein (Minimum RFC 6298 [RFC6298]), bei jeder Retransmission verdoppeln, bis mindestens 60 Sekunden (Maximum RFC 6298). 1 Sekunde statt 3 verbessert Latenz für zeitkritische Anwendungen; DTLS retransmitiert nur den Handshake, nicht Datenstrom, daher geringe Überlastwirkung.
Aktuellen Timerwert SOLLTEN Sie bis zur ersten verlustfreien Übertragung behalten, dann zurücksetzen. Nach langer Inaktivität (mindestens 10× aktueller Timer) Reset möglich (z. B. nach vielen Daten und Re-Handshake).
4.2.5. ChangeCipherSpec
Wie in TLS ist ChangeCipherSpec technisch kein Handshake-Message, MUSS aber für Timeout/Retransmission zur gleichen Flight wie das zugehörige Finished zählen. Bei Verlust kann die Reihenfolge zu ChangeCipherSpec mehrdeutig sein.
Aktuelle TLS-Modi bleiben vorhersagbar; künftige Modi MÜSSEN Mehrdeutigkeit vermeiden.
4.2.6. CertificateVerify and Finished Messages
Gleiches Format wie TLS. Hash über ganze Handshake-Nachrichten inklusive message_seq, fragment_offset, fragment_length. Das Finished-MAC MUSS berechnet werden, als ob jede Handshake-Nachricht in einem Fragment stünde. Bei Cookie-Austausch dürfen initiales ClientHello und HelloVerifyRequest nicht in CertificateVerify/Finished-MAC einfließen.
4.2.7. Alert Messages
Alerts werden nicht retransmitiert, auch nicht während Handshake. Tritt der fehlerhafte Record erneut auf, SOLLTE bei sonst üblicher Alert-Erzeugung ein neuer Alert gesendet werden. Implementierungen SOLLTEN dauerhaft fehlerhafte Peers erkennen und lokalen Zustand beenden.
4.2.8. Establishing New Associations with Existing Parameters
Wiederholte Verbindungen auf demselben Host/Port-Quartett können dazu führen, dass ein Client still eine Verbindung abbricht und mit gleichen Parametern neu startet (z. B. nach Reboot) – dem Server erscheint das als neuer Handshake mit epoch=0. Hat der Server eine bestehende Association und empfängt ClientHello mit epoch=0, SOLLTE er neuen Handshake beginnen, DARF die alte Association aber nicht zerstören, bis Erreichbarkeit durch Cookie-Handshake oder vollständigen Handshake mit verifizierbarem Finished nachgewiesen ist. Nach korrektem Finished MUSS die alte Association aufgegeben werden, um zwei gültige Associations mit überlappenden Epochen zu vermeiden. Die Erreichbarkeitsanforderung verhindert, dass Off-Path-Angreifer mit gefälschten ClientHellos Associations löschen.