lemon-tls 0.5.5 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -7
- package/src/compat.js +10 -0
- package/src/crypto.js +160 -51
- package/src/dtls_session.js +636 -73
- package/src/dtls_socket.js +7 -0
- package/src/record.js +226 -43
- package/src/session/ecdh.js +124 -0
- package/src/session/message.js +40 -1
- package/src/session/signing.js +240 -47
- package/src/session/ticket.js +6 -3
- package/src/tls_session.js +2000 -454
- package/src/tls_socket.js +378 -40
- package/src/utils.js +7 -1
- package/src/wire.js +344 -18
package/src/dtls_session.js
CHANGED
|
@@ -44,13 +44,17 @@ import {
|
|
|
44
44
|
|
|
45
45
|
import {
|
|
46
46
|
TLS_CIPHER_SUITES,
|
|
47
|
+
default_cipher_suites,
|
|
47
48
|
hkdf_expand_label,
|
|
48
49
|
derive_sn_key,
|
|
50
|
+
LABEL_PREFIX_TLS13,
|
|
51
|
+
LABEL_PREFIX_DTLS13,
|
|
49
52
|
} from './crypto.js';
|
|
50
53
|
|
|
51
54
|
import {
|
|
52
55
|
CT,
|
|
53
56
|
getAeadAlgo,
|
|
57
|
+
deriveKeys,
|
|
54
58
|
deriveKeys12,
|
|
55
59
|
decryptDtls12,
|
|
56
60
|
buildDtlsPlaintext,
|
|
@@ -59,9 +63,12 @@ import {
|
|
|
59
63
|
parseDtlsDatagram,
|
|
60
64
|
buildDtlsAck,
|
|
61
65
|
parseDtlsAck,
|
|
66
|
+
decryptEncryptedDtls13,
|
|
62
67
|
} from './record.js';
|
|
63
68
|
|
|
64
69
|
import { timingSafeEqualU8 } from './utils.js';
|
|
70
|
+
import { SUPPORTED_GROUPS as ECDH_SUPPORTED_GROUPS } from './session/ecdh.js';
|
|
71
|
+
import { default_signature_schemes } from './session/signing.js';
|
|
65
72
|
|
|
66
73
|
|
|
67
74
|
// ============================================================
|
|
@@ -121,6 +128,48 @@ function _ldbg() {
|
|
|
121
128
|
}
|
|
122
129
|
}
|
|
123
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Log an OUTBOUND record. The existing _ldbg traces only what arrives —
|
|
133
|
+
* which is half the picture when a peer rejects something WE sent, and
|
|
134
|
+
* that is precisely the DTLS 1.3 interop case: BoringSSL reads our
|
|
135
|
+
* flight, dislikes it, and closes. Decoding the record header here
|
|
136
|
+
* (content type, epoch, sequence, and the handshake message type when
|
|
137
|
+
* the record is still in the clear) shows exactly what left the socket.
|
|
138
|
+
*/
|
|
139
|
+
function _ldbgTx(record, whereFrom) {
|
|
140
|
+
if (!LEMON_DEBUG || !record || !record.length) return;
|
|
141
|
+
try {
|
|
142
|
+
var ct = record[0];
|
|
143
|
+
// DTLS 1.3 UNIFIED HEADER (RFC 9147 4.1): an encrypted record starts
|
|
144
|
+
// with 001CSLEE — first byte in 0x20..0x3F — instead of the classic
|
|
145
|
+
// content-type/version/epoch/sequence header. Its epoch is the low
|
|
146
|
+
// two bits and the body is ciphertext, so all we can honestly report
|
|
147
|
+
// is that one went out and how big it was. Reading it as a classic
|
|
148
|
+
// header produced nonsense like "ct46 epoch=323".
|
|
149
|
+
if (ct >= 0x20 && ct <= 0x3F) {
|
|
150
|
+
_ldbg('TX [1.3 encrypted] epoch=' + (ct & 0x03) + ' len=' + record.length +
|
|
151
|
+
(whereFrom ? ' <' + whereFrom + '>' : ''));
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
var epoch = (record[3] << 8) | record[4];
|
|
155
|
+
var names = { 20: 'ChangeCipherSpec', 21: 'Alert', 22: 'Handshake',
|
|
156
|
+
23: 'ApplicationData', 25: 'Heartbeat', 26: 'ACK' };
|
|
157
|
+
var hsNames = { 1:'ClientHello', 2:'ServerHello', 4:'NewSessionTicket',
|
|
158
|
+
8:'EncryptedExtensions', 11:'Certificate',
|
|
159
|
+
13:'CertificateRequest', 15:'CertificateVerify',
|
|
160
|
+
20:'Finished', 24:'KeyUpdate' };
|
|
161
|
+
var extra = '';
|
|
162
|
+
if (ct === 22 && record.length > 13) {
|
|
163
|
+
extra = ' hs=' + (hsNames[record[13]] || ('type' + record[13]));
|
|
164
|
+
}
|
|
165
|
+
if (ct === 21 && record.length >= 15) {
|
|
166
|
+
extra = ' alert level=' + record[13] + ' desc=' + record[14];
|
|
167
|
+
}
|
|
168
|
+
_ldbg('TX ' + (names[ct] || ('ct' + ct)) + ' epoch=' + epoch +
|
|
169
|
+
' len=' + record.length + extra + (whereFrom ? ' <' + whereFrom + '>' : ''));
|
|
170
|
+
} catch (e) {}
|
|
171
|
+
}
|
|
172
|
+
|
|
124
173
|
function DTLSSession(options) {
|
|
125
174
|
if (!(this instanceof DTLSSession)) return new DTLSSession(options);
|
|
126
175
|
options = options || {};
|
|
@@ -141,6 +190,7 @@ function DTLSSession(options) {
|
|
|
141
190
|
// byte and never the body, so any non-empty sid shears its whole
|
|
142
191
|
// ClientHello parse.)
|
|
143
192
|
sessionId: new Uint8Array(0),
|
|
193
|
+
srtpProfiles: options.srtpProfiles,
|
|
144
194
|
servername: options.servername,
|
|
145
195
|
SNICallback: options.SNICallback,
|
|
146
196
|
rejectUnauthorized: options.rejectUnauthorized,
|
|
@@ -194,8 +244,22 @@ function DTLSSession(options) {
|
|
|
194
244
|
// keyed by msg_seq, and drained in order once the gap is filled.
|
|
195
245
|
pendingMessages: {}, // msg_seq → { type, body }
|
|
196
246
|
|
|
197
|
-
// Flight tracking for retransmission
|
|
198
|
-
|
|
247
|
+
// Flight tracking for retransmission.
|
|
248
|
+
//
|
|
249
|
+
// Stores PRE-ENCRYPTION descriptors — { epoch, contentType, payload } —
|
|
250
|
+
// never built records. Rebuilding at retransmit time (resendFlight) gives
|
|
251
|
+
// every retransmitted record a FRESH record sequence number, which is not
|
|
252
|
+
// an optimization but a hard requirement on two independent grounds:
|
|
253
|
+
// 1. DTLS 1.3 (RFC 9147 §4.2.1): the AEAD nonce is derived from the
|
|
254
|
+
// record seq. Re-emitting a stored ciphertext repeats (key, nonce) —
|
|
255
|
+
// catastrophic for GCM (key-stream reuse reveals plaintext XOR and
|
|
256
|
+
// enables tag forgery). The old code stored fully-built records and
|
|
257
|
+
// replayed the exact bytes.
|
|
258
|
+
// 2. Receivers reconstruct the full seq from its truncated wire form
|
|
259
|
+
// relative to the highest seq seen (and BoGo outright requires
|
|
260
|
+
// monotonic seqs from the shim) — a replayed old seq reconstructs to
|
|
261
|
+
// a wrong full value and fails deprotection as "bad record MAC".
|
|
262
|
+
currentFlight: [], // array of { epoch, contentType, payload: Uint8Array }
|
|
199
263
|
// RFC 6347 §4.2.4 flight discipline: a NEW flight begins with the first
|
|
200
264
|
// record we send after having accepted new handshake data from the peer.
|
|
201
265
|
// Set when inbound processing advances state; consumed (and reset) by
|
|
@@ -206,6 +270,23 @@ function DTLSSession(options) {
|
|
|
206
270
|
// too — which a connected werift server answers with renegotiation(),
|
|
207
271
|
// destroying the session instead of recovering it.
|
|
208
272
|
newFlightPending: false,
|
|
273
|
+
|
|
274
|
+
// ---- Epoch tracking (RFC 9147 §6.1) ----
|
|
275
|
+
// Sending and receiving epochs advance INDEPENDENTLY: a KeyUpdate rekeys
|
|
276
|
+
// one direction only. Until KeyUpdate existed both directions moved
|
|
277
|
+
// together through 0 -> 2 -> 3, so a single number sufficed; it does not
|
|
278
|
+
// any more. ctx.keys[e] therefore holds .write and .read for possibly
|
|
279
|
+
// different generations, and each half is installed on its own.
|
|
280
|
+
writeEpoch: null, // epoch we currently PROTECT outgoing records with
|
|
281
|
+
readEpoch: null, // highest epoch we hold READ keys for
|
|
282
|
+
// A KeyUpdate we have sent whose ACK has not come back. RFC 9147 §8:
|
|
283
|
+
// "implementations MUST NOT send records with the new keys or send a new
|
|
284
|
+
// KeyUpdate until the previous KeyUpdate has been acknowledged". So the
|
|
285
|
+
// new write keys are installed immediately but stay dormant here until
|
|
286
|
+
// the ACK arrives.
|
|
287
|
+
pendingWriteEpoch: null,
|
|
288
|
+
keyUpdateFlight: null, // { payload, epoch, timer, attempts, timeout }
|
|
289
|
+
|
|
209
290
|
retransmitTimer: null,
|
|
210
291
|
retransmitCount: 0,
|
|
211
292
|
retransmitTimeout: 1000,
|
|
@@ -253,24 +334,17 @@ function DTLSSession(options) {
|
|
|
253
334
|
// Server defaults — TLSSession client auto-populates these, but server expects them set externally
|
|
254
335
|
if (isServer) {
|
|
255
336
|
if (!tlsSetup.local_supported_cipher_suites) {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
ciphers.push(0xC02F, 0xC030, 0xC02B, 0xC02C); // TLS 1.2 ECDHE ciphers
|
|
262
|
-
}
|
|
263
|
-
tlsSetup.local_supported_cipher_suites = ciphers;
|
|
337
|
+
// crypto.js owns the default suite list (see default_cipher_suites).
|
|
338
|
+
tlsSetup.local_supported_cipher_suites = default_cipher_suites(
|
|
339
|
+
versions.indexOf(DTLS_VERSION.DTLS1_3) >= 0,
|
|
340
|
+
versions.indexOf(DTLS_VERSION.DTLS1_2) >= 0
|
|
341
|
+
);
|
|
264
342
|
}
|
|
265
343
|
if (!tlsSetup.local_supported_groups) {
|
|
266
|
-
tlsSetup.local_supported_groups =
|
|
344
|
+
tlsSetup.local_supported_groups = ECDH_SUPPORTED_GROUPS.slice(); // whatever ecdh.js implements
|
|
267
345
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
0x0403, 0x0503, 0x0603, // ECDSA
|
|
271
|
-
0x0807, 0x0808, // EdDSA
|
|
272
|
-
0x0401, 0x0501, 0x0601, // PKCS#1
|
|
273
|
-
];
|
|
346
|
+
// signing.js owns the scheme list; DTLS spans 1.2+1.3 so include PKCS#1.
|
|
347
|
+
tlsSetup.local_supported_signature_algorithms = default_signature_schemes();
|
|
274
348
|
}
|
|
275
349
|
|
|
276
350
|
// Server cert/key
|
|
@@ -308,55 +382,151 @@ function DTLSSession(options) {
|
|
|
308
382
|
}
|
|
309
383
|
transcriptMsgSeqs.push(msgSeq);
|
|
310
384
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
385
|
+
// RFC 9147 §5.9, verbatim: "Hash calculations include entire handshake
|
|
386
|
+
// messages, including DTLS-specific fields: message_seq,
|
|
387
|
+
// fragment_offset, and fragment_length. However, in order to remove
|
|
388
|
+
// sensitivity to handshake message fragmentation, the CertificateVerify
|
|
389
|
+
// and Finished messages MUST be computed as if each handshake message
|
|
390
|
+
// had been sent as a single fragment."
|
|
391
|
+
//
|
|
392
|
+
// So DTLS 1.3 keeps the 12-byte DTLS header in the transcript, exactly
|
|
393
|
+
// like DTLS 1.2 (RFC 6347 §4.2.6) — with fragment_offset 0 and
|
|
394
|
+
// fragment_length equal to the whole body, which is what
|
|
395
|
+
// build_dtls_handshake produces by default.
|
|
396
|
+
//
|
|
397
|
+
// This code did the OPPOSITE for 1.3 and stored TLS-format messages,
|
|
398
|
+
// citing a section number that says the reverse of what it does. The
|
|
399
|
+
// effect is invisible between two copies of this library — both make
|
|
400
|
+
// the same choice and their MACs agree — and only shows up against a
|
|
401
|
+
// conforming peer, where every message parses cleanly and then the
|
|
402
|
+
// Finished MAC fails: "Finished verify_data mismatch" against Chrome,
|
|
403
|
+
// with a transcript whose entries were each 8 bytes short.
|
|
404
|
+
// MEASURED AGAINST CHROME, and it contradicts the plain reading of
|
|
405
|
+
// RFC 9147 §5.9 ("Hash calculations include entire handshake
|
|
406
|
+
// messages, including DTLS-specific fields"):
|
|
407
|
+
// • TLS-format transcript -> Chrome's CertificateVerify VERIFIES,
|
|
408
|
+
// Finished MAC mismatches;
|
|
409
|
+
// • DTLS-format transcript -> Chrome's CertificateVerify FAILS.
|
|
410
|
+
// So BoringSSL signs CertificateVerify over the TLS-format transcript.
|
|
411
|
+
// Since CertificateVerify is the one we can check against a real peer
|
|
412
|
+
// and it passes this way, TLS format stays for 1.3 — the Finished
|
|
413
|
+
// difference is something else and must be found without breaking a
|
|
414
|
+
// signature that demonstrably verifies.
|
|
415
|
+
let entry = (ctx.selectedVersion === DTLS_VERSION.DTLS1_2)
|
|
416
|
+
? build_dtls_handshake(data, msgSeq)
|
|
417
|
+
: data;
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
return entry;
|
|
315
421
|
};
|
|
316
422
|
|
|
317
423
|
/**
|
|
318
424
|
* Called when selectedVersion is first determined as DTLS 1.2.
|
|
319
425
|
* Retroactively converts all existing transcript entries to DTLS format.
|
|
320
426
|
*/
|
|
321
|
-
|
|
427
|
+
/**
|
|
428
|
+
* Convert transcript entries recorded BEFORE the version was known into
|
|
429
|
+
* DTLS handshake format.
|
|
430
|
+
*
|
|
431
|
+
* Both DTLS 1.2 (RFC 6347 §4.2.6) and DTLS 1.3 (RFC 9147 §5.9) hash the
|
|
432
|
+
* 12-byte DTLS handshake header, but the first messages — our ClientHello
|
|
433
|
+
* and the peer's ServerHello — are pushed while selectedVersion is still
|
|
434
|
+
* null, so they land in TLS format and have to be rewritten once the
|
|
435
|
+
* negotiation resolves. Entries already in DTLS format are left alone:
|
|
436
|
+
* a second conversion would wrap a 12-byte header around one that is
|
|
437
|
+
* already there.
|
|
438
|
+
*/
|
|
439
|
+
function fixTranscriptForDtls() {
|
|
322
440
|
let t = tls.context.transcript;
|
|
323
441
|
for (let i = 0; i < t.length; i++) {
|
|
324
|
-
if (i
|
|
325
|
-
|
|
326
|
-
|
|
442
|
+
if (i >= transcriptMsgSeqs.length) continue;
|
|
443
|
+
let e = t[i];
|
|
444
|
+
if (!e || e.length < 4) continue;
|
|
445
|
+
// A DTLS entry is 12 + body; a TLS entry is 4 + body. The declared
|
|
446
|
+
// length lives in the same place in both, so comparing it against the
|
|
447
|
+
// actual size tells them apart without guessing.
|
|
448
|
+
let declared = (e[1] << 16) | (e[2] << 8) | e[3];
|
|
449
|
+
if (e.length === declared + 12) continue; // already DTLS format
|
|
450
|
+
t[i] = build_dtls_handshake(e, transcriptMsgSeqs[i]);
|
|
327
451
|
}
|
|
328
452
|
}
|
|
329
453
|
|
|
454
|
+
// Kept for callers that still use the old name.
|
|
455
|
+
function fixTranscriptForDtls12() { fixTranscriptForDtls(); }
|
|
456
|
+
|
|
330
457
|
|
|
331
458
|
// ============================================================
|
|
332
459
|
// Key derivation
|
|
333
460
|
// ============================================================
|
|
334
461
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
462
|
+
/**
|
|
463
|
+
* RFC 9147 §5.9: DTLS 1.3 derives with the "dtls13" label prefix (no trailing
|
|
464
|
+
* space), NOT TLS 1.3's "tls13 ". Using the TLS prefix yields a handshake in
|
|
465
|
+
* which every message parses and the transcripts match on both sides, and
|
|
466
|
+
* only the keys differ — the peer reports "bad record MAC" and nothing else.
|
|
467
|
+
* DTLS 1.2 does not use HKDF-Expand-Label at all (it uses the 1.2 PRF), so
|
|
468
|
+
* the branch only has to distinguish 1.3.
|
|
469
|
+
*/
|
|
470
|
+
function dtlsLabelPrefix() {
|
|
471
|
+
return ctx.selectedVersion === DTLS_VERSION.DTLS1_3
|
|
472
|
+
? LABEL_PREFIX_DTLS13
|
|
473
|
+
: LABEL_PREFIX_TLS13;
|
|
474
|
+
}
|
|
340
475
|
|
|
341
|
-
|
|
476
|
+
function deriveEpochKeys(secret) {
|
|
477
|
+
let lp = dtlsLabelPrefix();
|
|
478
|
+
// record.js owns "traffic secret -> key + iv" and already exports it. This
|
|
479
|
+
// function used to re-derive both inline, which is the same two HKDF calls
|
|
480
|
+
// with the same arguments in a second place — precisely the shape of the
|
|
481
|
+
// label-prefix bug, where one copy of a derivation was fixed and another
|
|
482
|
+
// was not. Only the DTLS-specific extras belong here.
|
|
483
|
+
let result = deriveKeys(secret, ctx.cipherSuite, lp);
|
|
342
484
|
|
|
343
485
|
// DTLS 1.3: derive sn_key for record number encryption
|
|
344
486
|
if (ctx.selectedVersion === DTLS_VERSION.DTLS1_3) {
|
|
345
|
-
result.snKey = derive_sn_key(
|
|
487
|
+
result.snKey = derive_sn_key(TLS_CIPHER_SUITES[ctx.cipherSuite].hash, secret,
|
|
488
|
+
ctx.cipherSuite, lp);
|
|
346
489
|
result.algo = getAeadAlgo(ctx.cipherSuite);
|
|
347
490
|
}
|
|
348
491
|
|
|
349
492
|
return result;
|
|
350
493
|
}
|
|
351
494
|
|
|
495
|
+
/**
|
|
496
|
+
* The DTLS epoch a given key generation belongs to.
|
|
497
|
+
*
|
|
498
|
+
* DTLS 1.3 (RFC 9147 §6.1) numbers epochs 0=plaintext, 1=early data,
|
|
499
|
+
* 2=handshake, 3=application. DTLS 1.2 uses a single encrypted epoch 1.
|
|
500
|
+
* Which numbering applies depends on the negotiated version, so this must
|
|
501
|
+
* resolve the version FIRST and never assume a previous sync happened.
|
|
502
|
+
*
|
|
503
|
+
* The two callers used to differ: the handshake-secrets handler synced the
|
|
504
|
+
* version from the TLS session, the application-secrets handler did not sync
|
|
505
|
+
* at all. That asymmetry is only harmless while the version happens to be
|
|
506
|
+
* committed by the time each event fires. When it is not — the TLS engine
|
|
507
|
+
* commits negotiated values at the END of a reactive pass, and these events
|
|
508
|
+
* are emitted DURING one — the keys are filed under epoch 1 while records are
|
|
509
|
+
* later built for epoch 2/3. buildRecord then finds no keys, returns null,
|
|
510
|
+
* and the record is dropped silently: the peer sees nothing at all and times
|
|
511
|
+
* out. One helper, so both generations decide the same way.
|
|
512
|
+
*/
|
|
513
|
+
function dtlsEpochForSecrets(isApplication) {
|
|
514
|
+
if (ctx.selectedVersion === null && tls.context.selected_version) {
|
|
515
|
+
ctx.selectedVersion = tls.context.selected_version;
|
|
516
|
+
// Both DTLS versions hash the DTLS handshake header (RFC 6347 §4.2.6,
|
|
517
|
+
// RFC 9147 §5.9), so the early TLS-format entries must be rewritten
|
|
518
|
+
// whichever version was negotiated.
|
|
519
|
+
if (ctx.selectedVersion === DTLS_VERSION.DTLS1_2) fixTranscriptForDtls();
|
|
520
|
+
}
|
|
521
|
+
if (ctx.selectedVersion === DTLS_VERSION.DTLS1_3) return isApplication ? 3 : 2;
|
|
522
|
+
return 1;
|
|
523
|
+
}
|
|
524
|
+
|
|
352
525
|
tls.on('handshakeSecrets', function(localSecret, remoteSecret) {
|
|
353
|
-
|
|
354
|
-
if (ctx.selectedVersion === null) ctx.selectedVersion = tls.context.selected_version;
|
|
526
|
+
let dtlsEpoch = dtlsEpochForSecrets(false);
|
|
355
527
|
ctx.cipherSuite = tls.getCipher();
|
|
356
528
|
ctx.hashName = TLS_CIPHER_SUITES[ctx.cipherSuite].hash;
|
|
357
529
|
|
|
358
|
-
let dtlsEpoch = ctx.selectedVersion === DTLS_VERSION.DTLS1_3 ? 2 : 1;
|
|
359
|
-
|
|
360
530
|
ctx.keys[dtlsEpoch] = {
|
|
361
531
|
write: deriveEpochKeys(localSecret),
|
|
362
532
|
read: deriveEpochKeys(remoteSecret),
|
|
@@ -365,16 +535,106 @@ function DTLSSession(options) {
|
|
|
365
535
|
});
|
|
366
536
|
|
|
367
537
|
tls.on('appSecrets', function(localSecret, remoteSecret) {
|
|
368
|
-
let dtlsEpoch =
|
|
538
|
+
let dtlsEpoch = dtlsEpochForSecrets(true);
|
|
369
539
|
|
|
370
540
|
ctx.keys[dtlsEpoch] = {
|
|
371
541
|
write: deriveEpochKeys(localSecret),
|
|
372
542
|
read: deriveEpochKeys(remoteSecret),
|
|
373
543
|
};
|
|
374
544
|
ctx.writeSeq[dtlsEpoch] = 0;
|
|
545
|
+
|
|
546
|
+
// Baseline for KeyUpdate. Everything after this point counts up from here,
|
|
547
|
+
// per direction.
|
|
548
|
+
ctx.writeEpoch = dtlsEpoch;
|
|
549
|
+
ctx.readEpoch = dtlsEpoch;
|
|
375
550
|
});
|
|
376
551
|
|
|
377
552
|
|
|
553
|
+
// ============================================================
|
|
554
|
+
// KeyUpdate (RFC 9147 §8)
|
|
555
|
+
// ============================================================
|
|
556
|
+
//
|
|
557
|
+
// TLSSession derives the new traffic secret and emits 'keyUpdate'; the DTLS
|
|
558
|
+
// layer owns everything the epoch implies. Nothing consumed this event
|
|
559
|
+
// before, so a KeyUpdate updated the session's notion of the traffic secret
|
|
560
|
+
// while the record layer kept protecting and deprotecting with the old keys.
|
|
561
|
+
// That is worse than not implementing it: on DTLS an undeprotectable record
|
|
562
|
+
// is silently discarded (§4.5.2), so the symptom is a connection that simply
|
|
563
|
+
// stops carrying data, with no alert and no error anywhere.
|
|
564
|
+
|
|
565
|
+
tls.on('keyUpdate', function(info) {
|
|
566
|
+
// DTLS 1.2 has no KeyUpdate; the TLS session only emits this for 1.3, but
|
|
567
|
+
// the guard keeps the epoch arithmetic below unreachable on the 1.2 path.
|
|
568
|
+
if (ctx.selectedVersion !== DTLS_VERSION.DTLS1_3) return;
|
|
569
|
+
if (!info || !info.secret) return;
|
|
570
|
+
if (ctx.writeEpoch === null || ctx.readEpoch === null) return;
|
|
571
|
+
|
|
572
|
+
if (info.direction === 'send') {
|
|
573
|
+
// §8: only one KeyUpdate may be in flight. requestKeyUpdate() below
|
|
574
|
+
// refuses up front; reaching here with one pending means the TLS session
|
|
575
|
+
// rotated its secret anyway (e.g. an auto-response to the peer's
|
|
576
|
+
// update_requested while ours was still unacknowledged), which would
|
|
577
|
+
// strand an epoch. Say so rather than silently losing the connection.
|
|
578
|
+
if (ctx.pendingWriteEpoch !== null) {
|
|
579
|
+
ev.emit('error', new Error(
|
|
580
|
+
'DTLS: a KeyUpdate is already awaiting acknowledgement (RFC 9147 §8)'));
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
let next = ctx.writeEpoch + 1;
|
|
584
|
+
// Install the new write keys, but keep protecting with the old epoch
|
|
585
|
+
// until the peer ACKs (§8). ctx.keys[next] may already carry a .read
|
|
586
|
+
// half from the peer's own update — merge, never overwrite.
|
|
587
|
+
ctx.keys[next] = Object.assign({}, ctx.keys[next], { write: deriveEpochKeys(info.secret) });
|
|
588
|
+
ctx.writeSeq[next] = 0;
|
|
589
|
+
ctx.pendingWriteEpoch = next;
|
|
590
|
+
|
|
591
|
+
} else if (info.direction === 'receive') {
|
|
592
|
+
let next = ctx.readEpoch + 1;
|
|
593
|
+
ctx.keys[next] = Object.assign({}, ctx.keys[next], { read: deriveEpochKeys(info.secret) });
|
|
594
|
+
ctx.readEpoch = next;
|
|
595
|
+
// The previous epoch's read keys are deliberately RETAINED. §8: records
|
|
596
|
+
// already in flight under the old epoch must still deprotect, and our
|
|
597
|
+
// ACK may be lost, so the peer may retransmit under the old keys.
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
/** Arm the post-handshake retransmit for an unacknowledged KeyUpdate. */
|
|
602
|
+
function armKeyUpdateRetransmit(payload, epoch) {
|
|
603
|
+
cancelKeyUpdateRetransmit();
|
|
604
|
+
ctx.keyUpdateFlight = {
|
|
605
|
+
payload: payload, epoch: epoch,
|
|
606
|
+
attempts: 0, timeout: ctx.retransmitTimeout, timer: null,
|
|
607
|
+
};
|
|
608
|
+
scheduleKeyUpdateRetransmit();
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function scheduleKeyUpdateRetransmit() {
|
|
612
|
+
let f = ctx.keyUpdateFlight;
|
|
613
|
+
if (!f) return;
|
|
614
|
+
f.timer = setTimeout(function () {
|
|
615
|
+
if (!ctx.keyUpdateFlight) return;
|
|
616
|
+
if (f.attempts >= ctx.maxRetransmits) {
|
|
617
|
+
ev.emit('error', new Error('DTLS: KeyUpdate was never acknowledged'));
|
|
618
|
+
cancelKeyUpdateRetransmit();
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
f.attempts++;
|
|
622
|
+
// Rebuild rather than replay: a retransmission is a NEW record and needs
|
|
623
|
+
// a fresh sequence number, or the peer's replay window rejects it.
|
|
624
|
+
let record = buildRecord(f.epoch, CT.HANDSHAKE, f.payload);
|
|
625
|
+
if (record) { _ldbgTx(record, 'keyupdate-rtx'); ev.emit('packet', record); }
|
|
626
|
+
f.timeout = Math.min(f.timeout * 2, 60000);
|
|
627
|
+
scheduleKeyUpdateRetransmit();
|
|
628
|
+
}, f.timeout);
|
|
629
|
+
if (f.timer && typeof f.timer.unref === 'function') f.timer.unref();
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function cancelKeyUpdateRetransmit() {
|
|
633
|
+
if (ctx.keyUpdateFlight && ctx.keyUpdateFlight.timer) clearTimeout(ctx.keyUpdateFlight.timer);
|
|
634
|
+
ctx.keyUpdateFlight = null;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
|
|
378
638
|
// ============================================================
|
|
379
639
|
// Outgoing: TLSSession message → DTLS datagram
|
|
380
640
|
// ============================================================
|
|
@@ -400,7 +660,11 @@ function DTLSSession(options) {
|
|
|
400
660
|
if (tlsEpoch === 0) {
|
|
401
661
|
dtlsEpoch = 0;
|
|
402
662
|
} else if (ctx.selectedVersion === DTLS_VERSION.DTLS1_3) {
|
|
403
|
-
|
|
663
|
+
// epoch 2 = handshake. Anything above rides the CURRENT application
|
|
664
|
+
// epoch, which is 3 until a KeyUpdate moves it to 4, 5, ... Hardcoding 3
|
|
665
|
+
// meant every post-handshake message stayed on the pre-rekey epoch.
|
|
666
|
+
dtlsEpoch = (tlsEpoch === 1) ? 2
|
|
667
|
+
: (ctx.writeEpoch !== null ? ctx.writeEpoch : 3);
|
|
404
668
|
} else {
|
|
405
669
|
dtlsEpoch = 1; // DTLS 1.2: epoch 1 for all encrypted
|
|
406
670
|
}
|
|
@@ -443,10 +707,41 @@ function DTLSSession(options) {
|
|
|
443
707
|
let frags = fragmentMessage(dtlsMsg, data, seq);
|
|
444
708
|
|
|
445
709
|
// Build DTLS records and emit
|
|
710
|
+
// RFC 9147 §5.8.4: post-handshake messages do NOT belong to the handshake
|
|
711
|
+
// flight. Each category gets an independent state machine; folding a
|
|
712
|
+
// KeyUpdate into currentFlight would make a handshake retransmission
|
|
713
|
+
// replay it, and a KeyUpdate ACK cancel the handshake's timer.
|
|
714
|
+
let isPostHandshakeKU = (type === 'key_update' &&
|
|
715
|
+
ctx.selectedVersion === DTLS_VERSION.DTLS1_3);
|
|
716
|
+
if (isPostHandshakeKU) {
|
|
717
|
+
for (let i = 0; i < frags.length; i++) {
|
|
718
|
+
let kuRecord = buildRecord(dtlsEpoch, CT.HANDSHAKE, frags[i]);
|
|
719
|
+
if (kuRecord) { _ldbgTx(kuRecord, 'keyupdate'); ev.emit('packet', kuRecord); }
|
|
720
|
+
}
|
|
721
|
+
// KeyUpdate must be acknowledged (§8), so it is retransmitted on its own
|
|
722
|
+
// timer until an ACK arrives. Only the last fragment is tracked; a
|
|
723
|
+
// KeyUpdate body is 1 byte and never fragments in practice.
|
|
724
|
+
armKeyUpdateRetransmit(frags[frags.length - 1], dtlsEpoch);
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
|
|
446
728
|
for (let i = 0; i < frags.length; i++) {
|
|
447
729
|
let record = buildRecord(dtlsEpoch, CT.HANDSHAKE, frags[i]);
|
|
448
|
-
|
|
449
|
-
|
|
730
|
+
// The flight captures the fragment, not the record — see currentFlight.
|
|
731
|
+
ctx.currentFlight.push({ epoch: dtlsEpoch, contentType: CT.HANDSHAKE, payload: frags[i] });
|
|
732
|
+
if (record) {
|
|
733
|
+
_ldbgTx(record, 'L543'); ev.emit('packet', record);
|
|
734
|
+
} else {
|
|
735
|
+
// buildRecord refused to build (no keys for an encrypted epoch).
|
|
736
|
+
// Dropping the record here without a word is how a key-epoch mismatch
|
|
737
|
+
// turns into an unexplained peer timeout instead of a diagnosable
|
|
738
|
+
// failure: nothing reaches the wire and nothing is reported. Surface
|
|
739
|
+
// it — the handshake cannot proceed either way.
|
|
740
|
+
ev.emit('error', new Error(
|
|
741
|
+
'DTLS: could not build outgoing handshake record for epoch ' + dtlsEpoch +
|
|
742
|
+
' — no keys installed for that epoch'));
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
450
745
|
}
|
|
451
746
|
|
|
452
747
|
// Start retransmit timer after sending flight messages.
|
|
@@ -476,8 +771,21 @@ function DTLSSession(options) {
|
|
|
476
771
|
function buildRecord(epoch, contentType, payload) {
|
|
477
772
|
let epochKeys = ctx.keys[epoch];
|
|
478
773
|
|
|
774
|
+
|
|
479
775
|
if (!epochKeys) {
|
|
480
|
-
//
|
|
776
|
+
// DTLS 1.3 epochs 2 (handshake) and 3 (application) are ALWAYS
|
|
777
|
+
// encrypted — they carry the unified header (first byte 0x2c–0x2f). If
|
|
778
|
+
// we reach here without keys for such an epoch, emitting a classic
|
|
779
|
+
// 13-byte plaintext header (first byte = content type, e.g. 0x16/0x1a)
|
|
780
|
+
// would make the peer reject the record as "bad type byte" (RFC 9147
|
|
781
|
+
// §4). That only happens on a caller ordering bug (record built before
|
|
782
|
+
// its epoch's keys were installed); surface it instead of putting a
|
|
783
|
+
// malformed record on the wire. Epoch 0 is legitimately plaintext.
|
|
784
|
+
if (ctx.selectedVersion === DTLS_VERSION.DTLS1_3 && epoch >= 2) {
|
|
785
|
+
ev.emit('error', new Error('DTLS 1.3: attempted to build epoch ' + epoch + ' record before keys installed'));
|
|
786
|
+
return null;
|
|
787
|
+
}
|
|
788
|
+
// No keys → plaintext record (epoch 0, or DTLS 1.2 pre-CCS)
|
|
481
789
|
return buildDtlsPlaintext(contentType, epoch, nextWriteSeq(epoch), payload);
|
|
482
790
|
}
|
|
483
791
|
|
|
@@ -538,7 +846,12 @@ function DTLSSession(options) {
|
|
|
538
846
|
*/
|
|
539
847
|
function sendAlertRecord(epoch, alertData) {
|
|
540
848
|
let record = buildRecord(epoch, CT.ALERT, alertData);
|
|
541
|
-
|
|
849
|
+
// buildRecord returns null when it refuses to emit a malformed record
|
|
850
|
+
// (DTLS 1.3 epoch >= 2 with no keys installed). Emitting that null as a
|
|
851
|
+
// packet pushes `null` into the transport; drop it instead — we already
|
|
852
|
+
// surfaced the reason through 'error'.
|
|
853
|
+
if (!record) return;
|
|
854
|
+
_ldbgTx(record, 'L664'); ev.emit('packet', record);
|
|
542
855
|
}
|
|
543
856
|
|
|
544
857
|
/**
|
|
@@ -555,12 +868,14 @@ function DTLSSession(options) {
|
|
|
555
868
|
}
|
|
556
869
|
let record = buildDtlsPlaintext(CT.CHANGE_CIPHER_SPEC, 0, nextWriteSeq(0), new Uint8Array([1]));
|
|
557
870
|
// RFC 6347 §4.2.4: the CCS is part of the flight and MUST be included
|
|
558
|
-
// when the flight is retransmitted.
|
|
559
|
-
//
|
|
560
|
-
//
|
|
561
|
-
//
|
|
562
|
-
|
|
563
|
-
|
|
871
|
+
// when the flight is retransmitted. Captured as a descriptor (epoch 0,
|
|
872
|
+
// plaintext), so resendFlight rebuilds it with a fresh seq like every
|
|
873
|
+
// other record. Previously it was emitted but never captured, so a
|
|
874
|
+
// retransmitted final flight arrived WITHOUT the cipher-spec change and
|
|
875
|
+
// the peer could never decrypt the accompanying Finished — an
|
|
876
|
+
// unrecoverable stall under single-datagram loss.
|
|
877
|
+
ctx.currentFlight.push({ epoch: 0, contentType: CT.CHANGE_CIPHER_SPEC, payload: new Uint8Array([1]) });
|
|
878
|
+
_ldbgTx(record, 'L688'); ev.emit('packet', record);
|
|
564
879
|
}
|
|
565
880
|
|
|
566
881
|
|
|
@@ -599,9 +914,47 @@ function DTLSSession(options) {
|
|
|
599
914
|
// NewSessionTicket etc. are optional and skipped here for simplicity).
|
|
600
915
|
let ackable = [];
|
|
601
916
|
|
|
917
|
+
// Whether these records arrived AFTER the handshake must be decided here,
|
|
918
|
+
// before the loop — not after it. The datagram that completes the
|
|
919
|
+
// handshake flips ctx.state to 'connected' partway through, so reading the
|
|
920
|
+
// state afterwards misclassifies the handshake's own epoch-2 records as
|
|
921
|
+
// post-handshake and ACKs them inside the application-data epoch. That is
|
|
922
|
+
// the spurious epoch-3 ACK that makes a peer answer decode_error on the
|
|
923
|
+
// NEXT record — so a perfectly good SCTP COOKIE_ECHO takes the blame.
|
|
924
|
+
let wasConnectedOnEntry = (ctx.state === 'connected');
|
|
925
|
+
|
|
602
926
|
for (let i = 0; i < records.length; i++) {
|
|
927
|
+
// A record we could not decrypt when the datagram was parsed. Keys may
|
|
928
|
+
// have arrived since — processing an earlier record in THIS datagram
|
|
929
|
+
// (the ServerHello) is exactly what installs them. Retry now, in order,
|
|
930
|
+
// so a peer that packs its whole flight into one datagram is handled
|
|
931
|
+
// without waiting for a retransmit.
|
|
932
|
+
if (records[i].deferred) {
|
|
933
|
+
let epochKeys = ctx.keys[records[i].epoch];
|
|
934
|
+
let readKeys = epochKeys ? epochKeys.read : null;
|
|
935
|
+
if (!readKeys) continue; // still unreadable — let the peer retransmit
|
|
936
|
+
let dec = null;
|
|
937
|
+
try { dec = decryptEncryptedDtls13(records[i].raw, readKeys); } catch (e) { dec = null; }
|
|
938
|
+
if (!dec) continue;
|
|
939
|
+
records[i] = {
|
|
940
|
+
type: dec.type, epoch: dec.epoch, seq: dec.seq,
|
|
941
|
+
content: dec.content, encrypted: true, deferred: false,
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// NEVER ACK THE FIRST FLIGHT (RFC 9147 §7.1): "ACKs MUST NOT be
|
|
946
|
+
// sent for the ClientHello". The first flight is acknowledged by
|
|
947
|
+
// the RESPONSE to it — the ServerHello — and a peer that receives
|
|
948
|
+
// an ACK where none belongs answers decode_error.
|
|
949
|
+
// Epoch 0 records are exactly that first flight: everything later
|
|
950
|
+
// is protected under epoch 2 or above. This only bites when the
|
|
951
|
+
// ClientHello is large enough to FRAGMENT, because a single-record
|
|
952
|
+
// flight is consumed before any ACK decision is made — which is
|
|
953
|
+
// why a two-way call worked and an SFU, whose extra transceivers
|
|
954
|
+
// push the ClientHello past the MTU, did not.
|
|
603
955
|
if (records[i].type === CT.HANDSHAKE &&
|
|
604
|
-
ctx.selectedVersion === DTLS_VERSION.DTLS1_3
|
|
956
|
+
ctx.selectedVersion === DTLS_VERSION.DTLS1_3 &&
|
|
957
|
+
records[i].epoch > 0) {
|
|
605
958
|
ackable.push({ epoch: records[i].epoch, seq: records[i].seq });
|
|
606
959
|
}
|
|
607
960
|
|
|
@@ -626,8 +979,43 @@ function DTLSSession(options) {
|
|
|
626
979
|
// We ACK from the highest epoch for which we already have write keys (so the
|
|
627
980
|
// ACK itself is protected once handshake keys exist; epoch 0 before that).
|
|
628
981
|
if (ackable.length > 0) {
|
|
629
|
-
|
|
630
|
-
|
|
982
|
+
// ACKs RIDE THE HANDSHAKE EPOCH (RFC 9147 §7): they acknowledge
|
|
983
|
+
// handshake records, so epoch 2 once handshake keys exist and
|
|
984
|
+
// epoch 0 before that — never epoch 3. Picking "the highest epoch
|
|
985
|
+
// we have keys for" sent them under APPLICATION-DATA protection,
|
|
986
|
+
// where the peer decrypts with app keys and finds a content type
|
|
987
|
+
// that does not belong. Chrome answers decode_error.
|
|
988
|
+
// This shows up the moment a ClientHello is large enough to be
|
|
989
|
+
// FRAGMENTED — an SFU with several transceivers crosses the MTU
|
|
990
|
+
// and a two-way call does not, which is exactly why bidirectional
|
|
991
|
+
// worked and party mode did not.
|
|
992
|
+
// Post-handshake (§7.1: "For post-handshake messages, ACKs SHOULD be
|
|
993
|
+
// sent once for each received and processed handshake record") the
|
|
994
|
+
// handshake epoch is gone and §7 requires the HIGHEST available sending
|
|
995
|
+
// epoch. During the handshake it is still epoch 2, or 0 before keys.
|
|
996
|
+
// RFC 9147 §7.1 lists the flights that are IMPLICITLY acknowledged by
|
|
997
|
+
// the responding flight, and the client's final flight is pointedly not
|
|
998
|
+
// among them: "In general, flights MUST be ACKed unless they are
|
|
999
|
+
// implicitly acknowledged", excepting "handshake flights other than the
|
|
1000
|
+
// client's final flight of the main handshake".
|
|
1001
|
+
//
|
|
1002
|
+
// So the two roles need opposite answers here, which is why a
|
|
1003
|
+
// state-only test could not get both right:
|
|
1004
|
+
// client — the server's flight is implicitly acked by our own final
|
|
1005
|
+
// flight, so an explicit ACK is both redundant and (at the
|
|
1006
|
+
// application epoch) actively harmful;
|
|
1007
|
+
// server — the client's final flight has no responding flight, so
|
|
1008
|
+
// without an explicit ACK the client retransmits it forever.
|
|
1009
|
+
// §7 then fixes the epoch: after the handshake, the highest available
|
|
1010
|
+
// sending epoch.
|
|
1011
|
+
let justCompleted = (!wasConnectedOnEntry && ctx.state === 'connected');
|
|
1012
|
+
let finalFlightAck = isServer && justCompleted;
|
|
1013
|
+
let atAppEpoch = wasConnectedOnEntry || finalFlightAck;
|
|
1014
|
+
|
|
1015
|
+
let ackEpoch = atAppEpoch
|
|
1016
|
+
? (ctx.writeEpoch !== null ? ctx.writeEpoch : 3)
|
|
1017
|
+
: (ctx.keys[2] ? 2 : 0);
|
|
1018
|
+
sendAck(ackEpoch, ackable, atAppEpoch);
|
|
631
1019
|
}
|
|
632
1020
|
}
|
|
633
1021
|
|
|
@@ -669,6 +1057,12 @@ function DTLSSession(options) {
|
|
|
669
1057
|
} else if (record.type === CT.ALERT) {
|
|
670
1058
|
let level = record.content[0];
|
|
671
1059
|
let desc = record.content[1];
|
|
1060
|
+
// Name the alert either way. A close_notify is a CLEAN shutdown and
|
|
1061
|
+
// a failure is not, but both present to the application as "the
|
|
1062
|
+
// connection ended" — and telling them apart is the difference
|
|
1063
|
+
// between "the peer hung up" and "the peer rejected us".
|
|
1064
|
+
_ldbg('RX Alert level=' + level + ' desc=' + desc +
|
|
1065
|
+
(desc === 0 ? ' (close_notify — clean shutdown)' : ' (failure)'));
|
|
672
1066
|
if (desc === 0) {
|
|
673
1067
|
// close_notify
|
|
674
1068
|
ctx.state = 'closed';
|
|
@@ -751,9 +1145,7 @@ function DTLSSession(options) {
|
|
|
751
1145
|
// bounds the total effort; the resend rate is naturally bounded by
|
|
752
1146
|
// the peer's own retransmission schedule. DTLS 1.3 is excluded —
|
|
753
1147
|
// it uses explicit ACKs (RFC 9147 §7) instead of flight inference.
|
|
754
|
-
|
|
755
|
-
ev.emit('packet', ctx.currentFlight[f]);
|
|
756
|
-
}
|
|
1148
|
+
resendFlight();
|
|
757
1149
|
}
|
|
758
1150
|
}
|
|
759
1151
|
|
|
@@ -828,6 +1220,7 @@ function DTLSSession(options) {
|
|
|
828
1220
|
coveredAfter += merged[ca][1] - merged[ca][0];
|
|
829
1221
|
}
|
|
830
1222
|
|
|
1223
|
+
|
|
831
1224
|
// Complete when a single range covers [0, totalLength).
|
|
832
1225
|
if (merged.length === 1 && merged[0][0] === 0 && merged[0][1] === frag.totalLength) {
|
|
833
1226
|
delete ctx.fragments[key];
|
|
@@ -871,11 +1264,45 @@ function DTLSSession(options) {
|
|
|
871
1264
|
//
|
|
872
1265
|
// Note: msg_seq is NOT advanced for a rejected/HVR-triggering ClientHello, so
|
|
873
1266
|
// the client's retried CH (which reuses msg_seq per RFC 6347) is still accepted.
|
|
874
|
-
if (type === 1 && isServer
|
|
875
|
-
ctx.selectedVersion !== DTLS_VERSION.DTLS1_3) {
|
|
876
|
-
|
|
1267
|
+
if (type === 1 && isServer) {
|
|
877
1268
|
let ch = null;
|
|
878
1269
|
try { ch = parse_hello({ kind: 'client', body: body }); } catch (e) { ch = null; }
|
|
1270
|
+
|
|
1271
|
+
// Does this ClientHello offer DTLS 1.3?
|
|
1272
|
+
//
|
|
1273
|
+
// RULE: a decision derived from an incoming message is read from that
|
|
1274
|
+
// message, never from state the message itself is about to establish.
|
|
1275
|
+
// (Stated in full next to params_to_set in tls_session.js; this is the
|
|
1276
|
+
// fourth bug it has caused.) Read it from the MESSAGE, never
|
|
1277
|
+
// from ctx.selectedVersion — when the first ClientHello arrives nothing
|
|
1278
|
+
// has been negotiated yet, so a state-based test reads null and fails
|
|
1279
|
+
// OPEN. That matters because the two rules below are MUST-level and
|
|
1280
|
+
// version-specific.
|
|
1281
|
+
let offersDtls13 = false;
|
|
1282
|
+
if (ch && Array.isArray(ch.supported_versions)) {
|
|
1283
|
+
offersDtls13 = ch.supported_versions.indexOf(DTLS_VERSION.DTLS1_3) >= 0;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
// RFC 9147 §5.3: "A DTLS 1.3-only client MUST set the legacy_cookie field
|
|
1287
|
+
// to zero length. If a DTLS 1.3 ClientHello is received with any other
|
|
1288
|
+
// value in this field, the server MUST abort the handshake with an
|
|
1289
|
+
// 'illegal_parameter' alert." DTLS 1.3 carries cookies in the `cookie`
|
|
1290
|
+
// EXTENSION; the legacy body field is vestigial and must stay empty.
|
|
1291
|
+
if (offersDtls13 && ch && ch.dtls_cookie && ch.dtls_cookie.length > 0) {
|
|
1292
|
+
tls.sendAlert(2, 47); // fatal, illegal_parameter
|
|
1293
|
+
ev.emit('error', new Error(
|
|
1294
|
+
'DTLS 1.3 ClientHello carries a non-empty legacy_cookie (' +
|
|
1295
|
+
ch.dtls_cookie.length + ' bytes) — RFC 9147 §5.3 requires zero length'));
|
|
1296
|
+
return false;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
// RFC 9147 §5.1: "DTLS 1.3-compliant implementations MUST NOT use the
|
|
1300
|
+
// HelloVerifyRequest to execute a return-routability check." DTLS 1.3
|
|
1301
|
+
// does return-routability with HelloRetryRequest + the cookie extension
|
|
1302
|
+
// instead. The old guard tested ctx.selectedVersion, which is null here,
|
|
1303
|
+
// so a DTLS 1.3 handshake could still be answered with an HVR.
|
|
1304
|
+
if (options.useCookies === true && !offersDtls13) {
|
|
1305
|
+
|
|
879
1306
|
let incomingCookie = (ch && ch.dtls_cookie) ? ch.dtls_cookie : new Uint8Array(0);
|
|
880
1307
|
|
|
881
1308
|
if (incomingCookie.length === 0) {
|
|
@@ -911,9 +1338,10 @@ function DTLSSession(options) {
|
|
|
911
1338
|
// feedHandshakeToTls records the incoming wire seq and outgoing
|
|
912
1339
|
// messages record message_sent_seq at emission — which is exactly
|
|
913
1340
|
// what the Finished MAC hashes (RFC 6347 §4.2.6).
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
1341
|
+
ctx.nextReadMsgSeq = 1;
|
|
1342
|
+
tls.context.message_sent_seq = 1;
|
|
1343
|
+
// Fall through and process the ClientHello normally.
|
|
1344
|
+
}
|
|
917
1345
|
}
|
|
918
1346
|
|
|
919
1347
|
// ---- In-order delivery (RFC 6347 §4.2.4 / RFC 9147 §5.2) ----
|
|
@@ -1014,7 +1442,7 @@ function DTLSSession(options) {
|
|
|
1014
1442
|
// HVR always uses msg_seq 0 and is sent in the clear (epoch 0).
|
|
1015
1443
|
let hvrMsg = build_dtls_handshake(build_message(3, build_hello_verify_request({ cookie: cookie })), 0);
|
|
1016
1444
|
let record = buildDtlsPlaintext(CT.HANDSHAKE, 0, nextWriteSeq(0), hvrMsg);
|
|
1017
|
-
ev.emit('packet', record);
|
|
1445
|
+
_ldbgTx(record, 'L1194'); ev.emit('packet', record);
|
|
1018
1446
|
}
|
|
1019
1447
|
|
|
1020
1448
|
/**
|
|
@@ -1077,8 +1505,8 @@ function DTLSSession(options) {
|
|
|
1077
1505
|
// Build DTLS message and send — msg_seq 1 per the RFC figure above.
|
|
1078
1506
|
let dtlsMsg = build_dtls_handshake(ch2, 1);
|
|
1079
1507
|
let record = buildRecord(0, CT.HANDSHAKE, dtlsMsg);
|
|
1080
|
-
ctx.currentFlight = [
|
|
1081
|
-
ev.emit('packet', record);
|
|
1508
|
+
ctx.currentFlight = [{ epoch: 0, contentType: CT.HANDSHAKE, payload: dtlsMsg }];
|
|
1509
|
+
if (record) { _ldbgTx(record, 'L1258'); ev.emit('packet', record); }
|
|
1082
1510
|
|
|
1083
1511
|
tls.context.message_sent_seq = 2;
|
|
1084
1512
|
startRetransmitTimer();
|
|
@@ -1089,8 +1517,45 @@ function DTLSSession(options) {
|
|
|
1089
1517
|
// Flight tracking + retransmission
|
|
1090
1518
|
// ============================================================
|
|
1091
1519
|
|
|
1520
|
+
/**
|
|
1521
|
+
* Rebuild and re-emit the current flight with FRESH record sequence numbers.
|
|
1522
|
+
* See currentFlight's comment for why re-encryption (not byte replay) is
|
|
1523
|
+
* mandatory. Each descriptor goes back through buildRecord, which pulls the
|
|
1524
|
+
* next write seq for its epoch — so a retransmitted DTLS 1.3 record gets a
|
|
1525
|
+
* new nonce, and a monotonic seq the receiver can reconstruct.
|
|
1526
|
+
*/
|
|
1527
|
+
function resendFlight() {
|
|
1528
|
+
// NEVER AFTER THE HANDSHAKE COMPLETES. Once we are connected the
|
|
1529
|
+
// handshake flight is finished by definition, and re-emitting it
|
|
1530
|
+
// sends HANDSHAKE content inside an APPLICATION-DATA epoch — the
|
|
1531
|
+
// peer decrypts a record with its app-data keys and finds a message
|
|
1532
|
+
// it cannot place. Chrome tolerated one and then answered
|
|
1533
|
+
// decode_error on the next SCTP record, which read as an SCTP
|
|
1534
|
+
// problem for several rounds and was not one: the giveaway is a
|
|
1535
|
+
// record in the log with no matching sctp->dtls line, because the
|
|
1536
|
+
// payload never came from the application at all.
|
|
1537
|
+
// RFC 9147 §5.8: retransmission is bounded by the handshake; after
|
|
1538
|
+
// it, ACKs are the mechanism, not a timer.
|
|
1539
|
+
if (ctx.state === 'connected' || ctx.state === 'closed') {
|
|
1540
|
+
cancelRetransmit();
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1543
|
+
for (let i = 0; i < ctx.currentFlight.length; i++) {
|
|
1544
|
+
let f = ctx.currentFlight[i];
|
|
1545
|
+
let record = buildRecord(f.epoch, f.contentType, f.payload);
|
|
1546
|
+
if (record) { _ldbgTx(record, 'L1280'); ev.emit('packet', record); }
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1092
1550
|
function startRetransmitTimer() {
|
|
1093
1551
|
cancelRetransmit();
|
|
1552
|
+
// ONE GATE, AT THE SOURCE. Four call sites arm this timer and only
|
|
1553
|
+
// one of them checks the state, so a timer armed while the handshake
|
|
1554
|
+
// was still running could fire after it finished and re-emit the
|
|
1555
|
+
// flight into the application-data epoch. Refusing to arm once
|
|
1556
|
+
// connected (or closed) covers every caller, including any added
|
|
1557
|
+
// later, without each having to remember the rule.
|
|
1558
|
+
if (ctx.state === 'connected' || ctx.state === 'closed') return;
|
|
1094
1559
|
ctx.retransmitTimer = setTimeout(function() {
|
|
1095
1560
|
if (ctx.retransmitCount >= ctx.maxRetransmits) {
|
|
1096
1561
|
ev.emit('error', new Error('DTLS handshake timeout — max retransmits exceeded'));
|
|
@@ -1099,10 +1564,8 @@ function DTLSSession(options) {
|
|
|
1099
1564
|
return;
|
|
1100
1565
|
}
|
|
1101
1566
|
|
|
1102
|
-
// Retransmit entire current flight
|
|
1103
|
-
|
|
1104
|
-
ev.emit('packet', ctx.currentFlight[i]);
|
|
1105
|
-
}
|
|
1567
|
+
// Retransmit entire current flight with fresh record seqs.
|
|
1568
|
+
resendFlight();
|
|
1106
1569
|
|
|
1107
1570
|
ctx.retransmitCount++;
|
|
1108
1571
|
ctx.retransmitTimeout = Math.min(ctx.retransmitTimeout * 2, 60000);
|
|
@@ -1132,16 +1595,52 @@ function DTLSSession(options) {
|
|
|
1132
1595
|
|
|
1133
1596
|
function processAck(content) {
|
|
1134
1597
|
let acks = parseDtlsAck(content);
|
|
1135
|
-
|
|
1598
|
+
|
|
1599
|
+
// An outstanding KeyUpdate is what this ACK is for: §8 forbids protecting
|
|
1600
|
+
// anything with the new keys until it arrives, so THIS is the moment the
|
|
1601
|
+
// new epoch becomes live. Keys were installed when the KeyUpdate was sent;
|
|
1602
|
+
// only the switch happens here.
|
|
1603
|
+
if (ctx.pendingWriteEpoch !== null) {
|
|
1604
|
+
ctx.writeEpoch = ctx.pendingWriteEpoch;
|
|
1605
|
+
ctx.pendingWriteEpoch = null;
|
|
1606
|
+
cancelKeyUpdateRetransmit();
|
|
1607
|
+
ev.emit('keyUpdate', { direction: 'send', epoch: ctx.writeEpoch });
|
|
1608
|
+
return;
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
// Everything else keeps the original behaviour verbatim. In particular the
|
|
1612
|
+
// ACK for the client's final flight (which RFC 9147 §7.1 requires the peer
|
|
1613
|
+
// to send explicitly, and which arrives at the application epoch) must
|
|
1614
|
+
// still cancel that flight's retransmit timer.
|
|
1136
1615
|
cancelRetransmit();
|
|
1137
1616
|
startNewFlight();
|
|
1138
1617
|
}
|
|
1139
1618
|
|
|
1140
|
-
function sendAck(epoch, recordsToAck) {
|
|
1619
|
+
function sendAck(epoch, recordsToAck, postHandshake) {
|
|
1141
1620
|
if (ctx.selectedVersion !== DTLS_VERSION.DTLS1_3) return;
|
|
1621
|
+
// ACKs BELONG TO THE HANDSHAKE (RFC 9147 §7). They acknowledge
|
|
1622
|
+
// handshake records, and once the handshake is complete there is
|
|
1623
|
+
// nothing left to acknowledge — but this kept firing afterwards and
|
|
1624
|
+
// put an ACK record inside the APPLICATION-DATA epoch. The peer
|
|
1625
|
+
// decrypts it with app-data keys, finds a content type that has no
|
|
1626
|
+
// business there, and the stream is poisoned: Chrome tolerated it
|
|
1627
|
+
// and then answered decode_error on the next record, which made a
|
|
1628
|
+
// perfectly good SCTP COOKIE_ECHO look like the culprit for several
|
|
1629
|
+
// rounds. The giveaway in the log is a record with no matching
|
|
1630
|
+
// sctp->dtls line — the payload never came from the application.
|
|
1631
|
+
// ...that reasoning applies to HANDSHAKE flights. Post-handshake messages
|
|
1632
|
+
// are a separate case the original guard swept up with them: KeyUpdate,
|
|
1633
|
+
// NewSessionTicket and friends have no responding flight to acknowledge
|
|
1634
|
+
// them implicitly, so §7.1 requires an explicit ACK — without one the peer
|
|
1635
|
+
// retransmits its KeyUpdate forever and never activates its new keys.
|
|
1636
|
+
// `postHandshake` is set only for records actually received and processed
|
|
1637
|
+
// after the handshake, so the spurious-ACK bug this guard was written for
|
|
1638
|
+
// cannot return through it.
|
|
1639
|
+
if (!postHandshake && (ctx.state === 'connected' || ctx.state === 'closed')) return;
|
|
1640
|
+
if (ctx.state === 'closed') return;
|
|
1142
1641
|
let payload = buildDtlsAck(recordsToAck);
|
|
1143
1642
|
let record = buildRecord(epoch, CT.ACK, payload);
|
|
1144
|
-
ev.emit('packet', record);
|
|
1643
|
+
_ldbgTx(record, 'L1334'); ev.emit('packet', record);
|
|
1145
1644
|
}
|
|
1146
1645
|
|
|
1147
1646
|
|
|
@@ -1169,6 +1668,13 @@ function DTLSSession(options) {
|
|
|
1169
1668
|
});
|
|
1170
1669
|
|
|
1171
1670
|
tls.on('error', function(e) {
|
|
1671
|
+
// The peer's REASON, spelled out. When BoringSSL rejects part of our
|
|
1672
|
+
// flight it sends an alert and closes; without printing it here the
|
|
1673
|
+
// only symptom is a connection that dies after a handshake that
|
|
1674
|
+
// looked complete, which is exactly the DTLS 1.3 interop case.
|
|
1675
|
+
_ldbg('ERROR ' + (e && e.message ? e.message : e) +
|
|
1676
|
+
(e && e.alertDescription != null ? ' alertDesc=' + e.alertDescription : '') +
|
|
1677
|
+
(e && e.code ? ' code=' + e.code : ''));
|
|
1172
1678
|
ev.emit('error', e);
|
|
1173
1679
|
});
|
|
1174
1680
|
|
|
@@ -1188,9 +1694,11 @@ function DTLSSession(options) {
|
|
|
1188
1694
|
}
|
|
1189
1695
|
if (typeof data === 'string') data = new TextEncoder().encode(data);
|
|
1190
1696
|
|
|
1191
|
-
let epoch = ctx.selectedVersion === DTLS_VERSION.DTLS1_3
|
|
1697
|
+
let epoch = ctx.selectedVersion === DTLS_VERSION.DTLS1_3
|
|
1698
|
+
? (ctx.writeEpoch !== null ? ctx.writeEpoch : 3)
|
|
1699
|
+
: 1;
|
|
1192
1700
|
let record = buildRecord(epoch, CT.APPLICATION_DATA, data);
|
|
1193
|
-
ev.emit('packet', record);
|
|
1701
|
+
_ldbgTx(record, 'L1383'); ev.emit('packet', record);
|
|
1194
1702
|
}
|
|
1195
1703
|
|
|
1196
1704
|
|
|
@@ -1200,10 +1708,30 @@ function DTLSSession(options) {
|
|
|
1200
1708
|
|
|
1201
1709
|
function close() {
|
|
1202
1710
|
if (ctx.state === 'closed') return;
|
|
1203
|
-
|
|
1711
|
+
// Choose the epoch by what the PEER can read, not by whether WE consider
|
|
1712
|
+
// the handshake finished — the same rule the TLS branch uses for alerts.
|
|
1713
|
+
//
|
|
1714
|
+
// "not connected yet" does not mean "no keys": once handshake keys exist
|
|
1715
|
+
// the peer has switched to reading protected records, and a plaintext
|
|
1716
|
+
// close_notify arrives with a classic DTLSPlaintext header where the peer
|
|
1717
|
+
// requires the RFC 9147 §4 unified header. It then reports a bad record
|
|
1718
|
+
// type instead of a clean shutdown. Mirror the ACK path, which already
|
|
1719
|
+
// picks the highest epoch we hold write keys for.
|
|
1720
|
+
let epoch;
|
|
1721
|
+
if (ctx.selectedVersion === DTLS_VERSION.DTLS1_3) {
|
|
1722
|
+
// After a KeyUpdate the live epoch is 4, 5, ... — the peer has moved on
|
|
1723
|
+
// and no longer reads epoch 3, so a close_notify sent there is dropped
|
|
1724
|
+
// and the shutdown looks like a timeout instead of a clean close.
|
|
1725
|
+
epoch = (ctx.writeEpoch !== null && ctx.keys[ctx.writeEpoch])
|
|
1726
|
+
? ctx.writeEpoch
|
|
1727
|
+
: (ctx.keys[3] ? 3 : (ctx.keys[2] ? 2 : 0));
|
|
1728
|
+
} else {
|
|
1729
|
+
epoch = ctx.keys[1] ? 1 : 0;
|
|
1730
|
+
}
|
|
1204
1731
|
sendAlertRecord(epoch, new Uint8Array([1, 0])); // warning, close_notify
|
|
1205
1732
|
ctx.state = 'closed';
|
|
1206
1733
|
cancelRetransmit();
|
|
1734
|
+
cancelKeyUpdateRetransmit();
|
|
1207
1735
|
ev.emit('close');
|
|
1208
1736
|
}
|
|
1209
1737
|
|
|
@@ -1233,6 +1761,31 @@ function DTLSSession(options) {
|
|
|
1233
1761
|
/** Send application data (after connect). */
|
|
1234
1762
|
send: send,
|
|
1235
1763
|
|
|
1764
|
+
/**
|
|
1765
|
+
* Request a TLS 1.3 KeyUpdate (RFC 9147 §8).
|
|
1766
|
+
*
|
|
1767
|
+
* Returns false without doing anything if the session is not a connected
|
|
1768
|
+
* DTLS 1.3 association, or if an earlier KeyUpdate has not been
|
|
1769
|
+
* acknowledged yet — §8 permits only one in flight. The new epoch does not
|
|
1770
|
+
* become active on send; it activates when the peer's ACK arrives.
|
|
1771
|
+
*
|
|
1772
|
+
* @param {boolean} [requestPeer] also ask the peer to update its own keys
|
|
1773
|
+
* @returns {boolean} whether the update was initiated
|
|
1774
|
+
*/
|
|
1775
|
+
requestKeyUpdate: function(requestPeer) {
|
|
1776
|
+
if (ctx.selectedVersion !== DTLS_VERSION.DTLS1_3) return false;
|
|
1777
|
+
if (ctx.state !== 'connected') return false;
|
|
1778
|
+
if (ctx.pendingWriteEpoch !== null) return false;
|
|
1779
|
+
tls.requestKeyUpdate(!!requestPeer);
|
|
1780
|
+
return true;
|
|
1781
|
+
},
|
|
1782
|
+
|
|
1783
|
+
/** Current epoch used to protect outgoing records (null before connect). */
|
|
1784
|
+
getWriteEpoch: function() { return ctx.writeEpoch; },
|
|
1785
|
+
|
|
1786
|
+
/** Highest epoch for which read keys are installed (null before connect). */
|
|
1787
|
+
getReadEpoch: function() { return ctx.readEpoch; },
|
|
1788
|
+
|
|
1236
1789
|
/** Close the DTLS session. */
|
|
1237
1790
|
close: close,
|
|
1238
1791
|
|
|
@@ -1255,6 +1808,16 @@ function DTLSSession(options) {
|
|
|
1255
1808
|
return tls.exportKeyingMaterial(length, label, context_value);
|
|
1256
1809
|
},
|
|
1257
1810
|
|
|
1811
|
+
/**
|
|
1812
|
+
* The negotiated DTLS-SRTP protection profile (RFC 5764), or null.
|
|
1813
|
+
* Surfaced here because DTLS-SRTP is the reason the exporter exists for
|
|
1814
|
+
* this transport — a caller that can export keying material must also be
|
|
1815
|
+
* able to learn which profile those keys are for.
|
|
1816
|
+
*/
|
|
1817
|
+
getSelectedSrtpProfile: function() {
|
|
1818
|
+
return tls.getSelectedSrtpProfile();
|
|
1819
|
+
},
|
|
1820
|
+
|
|
1258
1821
|
/** Peer's hello extensions: [{ type, name, data, value }]. */
|
|
1259
1822
|
getRemoteExtensions: function() { return tls.getRemoteExtensions(); },
|
|
1260
1823
|
|