lemon-tls 0.5.0 → 0.6.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.
@@ -44,8 +44,11 @@ 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 {
@@ -59,9 +62,12 @@ import {
59
62
  parseDtlsDatagram,
60
63
  buildDtlsAck,
61
64
  parseDtlsAck,
65
+ decryptEncryptedDtls13,
62
66
  } from './record.js';
63
67
 
64
68
  import { timingSafeEqualU8 } from './utils.js';
69
+ import { SUPPORTED_GROUPS as ECDH_SUPPORTED_GROUPS } from './session/ecdh.js';
70
+ import { default_signature_schemes } from './session/signing.js';
65
71
 
66
72
 
67
73
  // ============================================================
@@ -107,6 +113,20 @@ function insertCookieIntoClientHello(ch1, cookie) {
107
113
  return ch2;
108
114
  }
109
115
 
116
+ // Handshake diagnostics — off by default; enabled via LEMON_DEBUG=1 (or
117
+ // WEBRTC_DEBUG=1 so one flag lights up the whole webrtc-server stack).
118
+ // Interop failures in DTLS are notoriously silent (a record that fails
119
+ // decryption or ordering simply vanishes); these logs make every inbound
120
+ // record, ordering decision, and completion visible without a packet dump.
121
+ var LEMON_DEBUG = typeof process !== 'undefined' && process.env &&
122
+ (process.env.LEMON_DEBUG === '1' || process.env.WEBRTC_DEBUG === '1');
123
+ function _ldbg() {
124
+ if (!LEMON_DEBUG) return;
125
+ if (typeof console !== 'undefined' && console.log) {
126
+ console.log.apply(console, ['[lemon-dtls]'].concat([].slice.call(arguments)));
127
+ }
128
+ }
129
+
110
130
  function DTLSSession(options) {
111
131
  if (!(this instanceof DTLSSession)) return new DTLSSession(options);
112
132
  options = options || {};
@@ -117,6 +137,17 @@ function DTLSSession(options) {
117
137
  // ---- Internal TLSSession ----
118
138
  let tls = new TLSSession({
119
139
  isServer: isServer,
140
+ // RFC 9147 §5.3: the DTLS ClientHello's legacy_session_id "MUST be
141
+ // set to a zero-length vector" — the 32-byte compatibility sid is a
142
+ // TLS-over-TCP middlebox trick (RFC 8446 §4.1.2) that DTLS excludes
143
+ // (8446 appendix D.4: no compatibility mode over DTLS). The TLS
144
+ // engine generates the compat sid when none is supplied, so pin it
145
+ // empty for DTLS, both 1.2 and 1.3 profiles. (Also sidesteps a
146
+ // deployed-parser landmine: webrtc-dtls ≤0.7 reads the sid LENGTH
147
+ // byte and never the body, so any non-empty sid shears its whole
148
+ // ClientHello parse.)
149
+ sessionId: new Uint8Array(0),
150
+ srtpProfiles: options.srtpProfiles,
120
151
  servername: options.servername,
121
152
  SNICallback: options.SNICallback,
122
153
  rejectUnauthorized: options.rejectUnauthorized,
@@ -170,8 +201,32 @@ function DTLSSession(options) {
170
201
  // keyed by msg_seq, and drained in order once the gap is filled.
171
202
  pendingMessages: {}, // msg_seq → { type, body }
172
203
 
173
- // Flight tracking for retransmission
174
- currentFlight: [], // array of { epoch, data: Uint8Array (complete datagram) }
204
+ // Flight tracking for retransmission.
205
+ //
206
+ // Stores PRE-ENCRYPTION descriptors — { epoch, contentType, payload } —
207
+ // never built records. Rebuilding at retransmit time (resendFlight) gives
208
+ // every retransmitted record a FRESH record sequence number, which is not
209
+ // an optimization but a hard requirement on two independent grounds:
210
+ // 1. DTLS 1.3 (RFC 9147 §4.2.1): the AEAD nonce is derived from the
211
+ // record seq. Re-emitting a stored ciphertext repeats (key, nonce) —
212
+ // catastrophic for GCM (key-stream reuse reveals plaintext XOR and
213
+ // enables tag forgery). The old code stored fully-built records and
214
+ // replayed the exact bytes.
215
+ // 2. Receivers reconstruct the full seq from its truncated wire form
216
+ // relative to the highest seq seen (and BoGo outright requires
217
+ // monotonic seqs from the shim) — a replayed old seq reconstructs to
218
+ // a wrong full value and fails deprotection as "bad record MAC".
219
+ currentFlight: [], // array of { epoch, contentType, payload: Uint8Array }
220
+ // RFC 6347 §4.2.4 flight discipline: a NEW flight begins with the first
221
+ // record we send after having accepted new handshake data from the peer.
222
+ // Set when inbound processing advances state; consumed (and reset) by
223
+ // the outgoing path, which then starts a fresh currentFlight. Without
224
+ // this boundary, flights ACCUMULATED: after the HVR exchange the client's
225
+ // flight 5 (CKE/CCS/Finished) was appended onto the CH2 still sitting in
226
+ // currentFlight, so a flight-5 retransmission replayed the ClientHello
227
+ // too — which a connected werift server answers with renegotiation(),
228
+ // destroying the session instead of recovering it.
229
+ newFlightPending: false,
175
230
  retransmitTimer: null,
176
231
  retransmitCount: 0,
177
232
  retransmitTimeout: 1000,
@@ -219,24 +274,17 @@ function DTLSSession(options) {
219
274
  // Server defaults — TLSSession client auto-populates these, but server expects them set externally
220
275
  if (isServer) {
221
276
  if (!tlsSetup.local_supported_cipher_suites) {
222
- let ciphers = [];
223
- if (versions.indexOf(DTLS_VERSION.DTLS1_3) >= 0) {
224
- ciphers.push(0x1301, 0x1302, 0x1303); // TLS 1.3 AEAD ciphers
225
- }
226
- if (versions.indexOf(DTLS_VERSION.DTLS1_2) >= 0) {
227
- ciphers.push(0xC02F, 0xC030, 0xC02B, 0xC02C); // TLS 1.2 ECDHE ciphers
228
- }
229
- tlsSetup.local_supported_cipher_suites = ciphers;
277
+ // crypto.js owns the default suite list (see default_cipher_suites).
278
+ tlsSetup.local_supported_cipher_suites = default_cipher_suites(
279
+ versions.indexOf(DTLS_VERSION.DTLS1_3) >= 0,
280
+ versions.indexOf(DTLS_VERSION.DTLS1_2) >= 0
281
+ );
230
282
  }
231
283
  if (!tlsSetup.local_supported_groups) {
232
- tlsSetup.local_supported_groups = [0x001d, 0x0017, 0x0018];
284
+ tlsSetup.local_supported_groups = ECDH_SUPPORTED_GROUPS.slice(); // whatever ecdh.js implements
233
285
  }
234
- tlsSetup.local_supported_signature_algorithms = [
235
- 0x0804, 0x0805, 0x0806, // RSA-PSS
236
- 0x0403, 0x0503, 0x0603, // ECDSA
237
- 0x0807, 0x0808, // EdDSA
238
- 0x0401, 0x0501, 0x0601, // PKCS#1
239
- ];
286
+ // signing.js owns the scheme list; DTLS spans 1.2+1.3 so include PKCS#1.
287
+ tlsSetup.local_supported_signature_algorithms = default_signature_schemes();
240
288
  }
241
289
 
242
290
  // Server cert/key
@@ -274,10 +322,18 @@ function DTLSSession(options) {
274
322
  }
275
323
  transcriptMsgSeqs.push(msgSeq);
276
324
 
277
- if (ctx.selectedVersion === DTLS_VERSION.DTLS1_2) {
278
- return build_dtls_handshake(data, msgSeq);
279
- }
280
- return data; // TLS format for DTLS 1.3 or unknown version
325
+ // RFC 9147 §5.5: the DTLS 1.3 transcript is made of *TLS-style* Handshake
326
+ // messages — message_seq, fragment_offset and fragment_length are REMOVED.
327
+ // DTLS 1.2 is the opposite: RFC 6347 §4.2.6 keeps the 12-byte DTLS header
328
+ // in the transcript. Getting this backwards breaks the Finished MAC only,
329
+ // long after every message parsed cleanly — which is why it presents as a
330
+ // late, unrelated-looking failure.
331
+ let entry = (ctx.selectedVersion === DTLS_VERSION.DTLS1_2)
332
+ ? build_dtls_handshake(data, msgSeq)
333
+ : data; // TLS format for DTLS 1.3 or not-yet-negotiated
334
+
335
+
336
+ return entry;
281
337
  };
282
338
 
283
339
  /**
@@ -298,31 +354,70 @@ function DTLSSession(options) {
298
354
  // Key derivation
299
355
  // ============================================================
300
356
 
357
+ /**
358
+ * RFC 9147 §5.9: DTLS 1.3 derives with the "dtls13" label prefix (no trailing
359
+ * space), NOT TLS 1.3's "tls13 ". Using the TLS prefix yields a handshake in
360
+ * which every message parses and the transcripts match on both sides, and
361
+ * only the keys differ — the peer reports "bad record MAC" and nothing else.
362
+ * DTLS 1.2 does not use HKDF-Expand-Label at all (it uses the 1.2 PRF), so
363
+ * the branch only has to distinguish 1.3.
364
+ */
365
+ function dtlsLabelPrefix() {
366
+ return ctx.selectedVersion === DTLS_VERSION.DTLS1_3
367
+ ? LABEL_PREFIX_DTLS13
368
+ : LABEL_PREFIX_TLS13;
369
+ }
370
+
301
371
  function deriveEpochKeys(secret) {
302
372
  let cs = TLS_CIPHER_SUITES[ctx.cipherSuite];
303
373
  let empty = new Uint8Array(0);
304
- let key = hkdf_expand_label(cs.hash, secret, 'key', empty, cs.keylen);
305
- let iv = hkdf_expand_label(cs.hash, secret, 'iv', empty, 12);
374
+ let lp = dtlsLabelPrefix();
375
+ let key = hkdf_expand_label(cs.hash, secret, 'key', empty, cs.keylen, lp);
376
+ let iv = hkdf_expand_label(cs.hash, secret, 'iv', empty, 12, lp);
306
377
 
307
378
  let result = { key, iv };
308
379
 
309
380
  // DTLS 1.3: derive sn_key for record number encryption
310
381
  if (ctx.selectedVersion === DTLS_VERSION.DTLS1_3) {
311
- result.snKey = derive_sn_key(cs.hash, secret, ctx.cipherSuite);
382
+ result.snKey = derive_sn_key(cs.hash, secret, ctx.cipherSuite, dtlsLabelPrefix());
312
383
  result.algo = getAeadAlgo(ctx.cipherSuite);
313
384
  }
314
385
 
315
386
  return result;
316
387
  }
317
388
 
389
+ /**
390
+ * The DTLS epoch a given key generation belongs to.
391
+ *
392
+ * DTLS 1.3 (RFC 9147 §6.1) numbers epochs 0=plaintext, 1=early data,
393
+ * 2=handshake, 3=application. DTLS 1.2 uses a single encrypted epoch 1.
394
+ * Which numbering applies depends on the negotiated version, so this must
395
+ * resolve the version FIRST and never assume a previous sync happened.
396
+ *
397
+ * The two callers used to differ: the handshake-secrets handler synced the
398
+ * version from the TLS session, the application-secrets handler did not sync
399
+ * at all. That asymmetry is only harmless while the version happens to be
400
+ * committed by the time each event fires. When it is not — the TLS engine
401
+ * commits negotiated values at the END of a reactive pass, and these events
402
+ * are emitted DURING one — the keys are filed under epoch 1 while records are
403
+ * later built for epoch 2/3. buildRecord then finds no keys, returns null,
404
+ * and the record is dropped silently: the peer sees nothing at all and times
405
+ * out. One helper, so both generations decide the same way.
406
+ */
407
+ function dtlsEpochForSecrets(isApplication) {
408
+ if (ctx.selectedVersion === null && tls.context.selected_version) {
409
+ ctx.selectedVersion = tls.context.selected_version;
410
+ if (ctx.selectedVersion === DTLS_VERSION.DTLS1_2) fixTranscriptForDtls12();
411
+ }
412
+ if (ctx.selectedVersion === DTLS_VERSION.DTLS1_3) return isApplication ? 3 : 2;
413
+ return 1;
414
+ }
415
+
318
416
  tls.on('handshakeSecrets', function(localSecret, remoteSecret) {
319
- // Ensure version is detected before key derivation
320
- if (ctx.selectedVersion === null) ctx.selectedVersion = tls.context.selected_version;
417
+ let dtlsEpoch = dtlsEpochForSecrets(false);
321
418
  ctx.cipherSuite = tls.getCipher();
322
419
  ctx.hashName = TLS_CIPHER_SUITES[ctx.cipherSuite].hash;
323
420
 
324
- let dtlsEpoch = ctx.selectedVersion === DTLS_VERSION.DTLS1_3 ? 2 : 1;
325
-
326
421
  ctx.keys[dtlsEpoch] = {
327
422
  write: deriveEpochKeys(localSecret),
328
423
  read: deriveEpochKeys(remoteSecret),
@@ -331,7 +426,7 @@ function DTLSSession(options) {
331
426
  });
332
427
 
333
428
  tls.on('appSecrets', function(localSecret, remoteSecret) {
334
- let dtlsEpoch = ctx.selectedVersion === DTLS_VERSION.DTLS1_3 ? 3 : 1;
429
+ let dtlsEpoch = dtlsEpochForSecrets(true);
335
430
 
336
431
  ctx.keys[dtlsEpoch] = {
337
432
  write: deriveEpochKeys(localSecret),
@@ -389,6 +484,14 @@ function DTLSSession(options) {
389
484
  }
390
485
  }
391
486
 
487
+ // Flight boundary: first outgoing record after accepting peer data
488
+ // starts a fresh flight (clears the previous flight's records and
489
+ // resets the retransmit budget — RFC 6347 §4.2.4).
490
+ if (ctx.newFlightPending) {
491
+ ctx.newFlightPending = false;
492
+ startNewFlight();
493
+ }
494
+
392
495
  // DTLS 1.2: send CCS before first encrypted message (Finished)
393
496
  if (ctx.selectedVersion !== DTLS_VERSION.DTLS1_3 && tlsEpoch === 1 && !ctx.localCcsSent) {
394
497
  sendCCS();
@@ -403,11 +506,39 @@ function DTLSSession(options) {
403
506
  // Build DTLS records and emit
404
507
  for (let i = 0; i < frags.length; i++) {
405
508
  let record = buildRecord(dtlsEpoch, CT.HANDSHAKE, frags[i]);
406
- ctx.currentFlight.push(record);
407
- ev.emit('packet', record);
509
+ // The flight captures the fragment, not the record — see currentFlight.
510
+ ctx.currentFlight.push({ epoch: dtlsEpoch, contentType: CT.HANDSHAKE, payload: frags[i] });
511
+ if (record) {
512
+ ev.emit('packet', record);
513
+ } else {
514
+ // buildRecord refused to build (no keys for an encrypted epoch).
515
+ // Dropping the record here without a word is how a key-epoch mismatch
516
+ // turns into an unexplained peer timeout instead of a diagnosable
517
+ // failure: nothing reaches the wire and nothing is reported. Surface
518
+ // it — the handshake cannot proceed either way.
519
+ ev.emit('error', new Error(
520
+ 'DTLS: could not build outgoing handshake record for epoch ' + dtlsEpoch +
521
+ ' — no keys installed for that epoch'));
522
+ return;
523
+ }
408
524
  }
409
525
 
410
- // Start retransmit timer after sending flight messages
526
+ // Start retransmit timer after sending flight messages.
527
+ //
528
+ // BUGFIX (one-shot initial ClientHello): ctx.state only left 'idle'
529
+ // inside feedDatagram — i.e. on the first INBOUND datagram. A
530
+ // client's first flight is sent before anything has been received,
531
+ // so the 'handshaking'-only guard below never armed the retransmit
532
+ // timer for flight 1. A lost or early-dropped ClientHello — e.g. one
533
+ // arriving while the peer's ICE layer still discards non-STUN
534
+ // traffic, a routine race right after nomination — was therefore
535
+ // never retransmitted, and the handshake hung silently forever
536
+ // (surfacing as connectionState stuck at 'connecting'). RFC 6347
537
+ // §4.2.4's flight state machine has no unguarded flights: every
538
+ // flight except the handshake-final one must sit under a retransmit
539
+ // timer. Sending a handshake flight IS handshaking — reflect that in
540
+ // the state before consulting it.
541
+ if (ctx.state === 'idle') ctx.state = 'handshaking';
411
542
  if (ctx.state === 'handshaking') {
412
543
  startRetransmitTimer();
413
544
  }
@@ -419,8 +550,21 @@ function DTLSSession(options) {
419
550
  function buildRecord(epoch, contentType, payload) {
420
551
  let epochKeys = ctx.keys[epoch];
421
552
 
553
+
422
554
  if (!epochKeys) {
423
- // No keys → plaintext record
555
+ // DTLS 1.3 epochs 2 (handshake) and 3 (application) are ALWAYS
556
+ // encrypted — they carry the unified header (first byte 0x2c–0x2f). If
557
+ // we reach here without keys for such an epoch, emitting a classic
558
+ // 13-byte plaintext header (first byte = content type, e.g. 0x16/0x1a)
559
+ // would make the peer reject the record as "bad type byte" (RFC 9147
560
+ // §4). That only happens on a caller ordering bug (record built before
561
+ // its epoch's keys were installed); surface it instead of putting a
562
+ // malformed record on the wire. Epoch 0 is legitimately plaintext.
563
+ if (ctx.selectedVersion === DTLS_VERSION.DTLS1_3 && epoch >= 2) {
564
+ ev.emit('error', new Error('DTLS 1.3: attempted to build epoch ' + epoch + ' record before keys installed'));
565
+ return null;
566
+ }
567
+ // No keys → plaintext record (epoch 0, or DTLS 1.2 pre-CCS)
424
568
  return buildDtlsPlaintext(contentType, epoch, nextWriteSeq(epoch), payload);
425
569
  }
426
570
 
@@ -481,6 +625,11 @@ function DTLSSession(options) {
481
625
  */
482
626
  function sendAlertRecord(epoch, alertData) {
483
627
  let record = buildRecord(epoch, CT.ALERT, alertData);
628
+ // buildRecord returns null when it refuses to emit a malformed record
629
+ // (DTLS 1.3 epoch >= 2 with no keys installed). Emitting that null as a
630
+ // packet pushes `null` into the transport; drop it instead — we already
631
+ // surfaced the reason through 'error'.
632
+ if (!record) return;
484
633
  ev.emit('packet', record);
485
634
  }
486
635
 
@@ -490,7 +639,21 @@ function DTLSSession(options) {
490
639
  function sendCCS() {
491
640
  if (ctx.localCcsSent) return;
492
641
  ctx.localCcsSent = true;
642
+ // The server's final flight OPENS with CCS — honor the flight boundary
643
+ // here too, before this record is captured.
644
+ if (ctx.newFlightPending) {
645
+ ctx.newFlightPending = false;
646
+ startNewFlight();
647
+ }
493
648
  let record = buildDtlsPlaintext(CT.CHANGE_CIPHER_SPEC, 0, nextWriteSeq(0), new Uint8Array([1]));
649
+ // RFC 6347 §4.2.4: the CCS is part of the flight and MUST be included
650
+ // when the flight is retransmitted. Captured as a descriptor (epoch 0,
651
+ // plaintext), so resendFlight rebuilds it with a fresh seq like every
652
+ // other record. Previously it was emitted but never captured, so a
653
+ // retransmitted final flight arrived WITHOUT the cipher-spec change and
654
+ // the peer could never decrypt the accompanying Finished — an
655
+ // unrecoverable stall under single-datagram loss.
656
+ ctx.currentFlight.push({ epoch: 0, contentType: CT.CHANGE_CIPHER_SPEC, payload: new Uint8Array([1]) });
494
657
  ev.emit('packet', record);
495
658
  }
496
659
 
@@ -513,6 +676,17 @@ function DTLSSession(options) {
513
676
 
514
677
  let records = parseDtlsDatagram(data, keysByEpoch);
515
678
 
679
+ if (LEMON_DEBUG) {
680
+ var _summ = [];
681
+ for (var _ri = 0; _ri < records.length; _ri++) {
682
+ var _r = records[_ri];
683
+ _summ.push('type=' + _r.type + ' epoch=' + _r.epoch + ' seq=' + _r.seq +
684
+ (_r.epoch > 0 && !_r.encrypted ? ' UNDECRYPTED' : ''));
685
+ }
686
+ _ldbg('rx datagram len=' + data.length + ' state=' + ctx.state +
687
+ ' records: [' + _summ.join(' | ') + ']');
688
+ }
689
+
516
690
  // RFC 9147 §7: collect the record numbers of handshake records we received
517
691
  // in this datagram so we can ACK them once, after processing. Only for
518
692
  // DTLS 1.3, and only while we're still handshaking (post-handshake ACKs of
@@ -520,6 +694,24 @@ function DTLSSession(options) {
520
694
  let ackable = [];
521
695
 
522
696
  for (let i = 0; i < records.length; i++) {
697
+ // A record we could not decrypt when the datagram was parsed. Keys may
698
+ // have arrived since — processing an earlier record in THIS datagram
699
+ // (the ServerHello) is exactly what installs them. Retry now, in order,
700
+ // so a peer that packs its whole flight into one datagram is handled
701
+ // without waiting for a retransmit.
702
+ if (records[i].deferred) {
703
+ let epochKeys = ctx.keys[records[i].epoch];
704
+ let readKeys = epochKeys ? epochKeys.read : null;
705
+ if (!readKeys) continue; // still unreadable — let the peer retransmit
706
+ let dec = null;
707
+ try { dec = decryptEncryptedDtls13(records[i].raw, readKeys); } catch (e) { dec = null; }
708
+ if (!dec) continue;
709
+ records[i] = {
710
+ type: dec.type, epoch: dec.epoch, seq: dec.seq,
711
+ content: dec.content, encrypted: true, deferred: false,
712
+ };
713
+ }
714
+
523
715
  if (records[i].type === CT.HANDSHAKE &&
524
716
  ctx.selectedVersion === DTLS_VERSION.DTLS1_3) {
525
717
  ackable.push({ epoch: records[i].epoch, seq: records[i].seq });
@@ -553,6 +745,18 @@ function DTLSSession(options) {
553
745
 
554
746
  function processRecord(record) {
555
747
  if (record.type === CT.HANDSHAKE) {
748
+ // An epoch>0 record that could not be decrypted (keys not yet
749
+ // installed, AEAD failure) must NOT be fed to the handshake
750
+ // parser: its content is ciphertext, and parsing it yields a
751
+ // garbage "message" with attacker-controllable type/length fields.
752
+ // Drop it — DTLS is loss-tolerant by design, and the peer's (or
753
+ // our own flight-elicited) retransmission delivers a fresh copy
754
+ // once it can be decrypted.
755
+ if (record.epoch > 0 && !record.encrypted) {
756
+ _ldbg('skip undecrypted epoch=' + record.epoch + ' seq=' + record.seq +
757
+ ' len=' + (record.content ? record.content.length : 0));
758
+ return;
759
+ }
556
760
  processHandshakeRecord(record.content, record.epoch, record.encrypted);
557
761
  } else if (record.type === CT.APPLICATION_DATA) {
558
762
  if (ctx.state === 'connected') {
@@ -593,6 +797,8 @@ function DTLSSession(options) {
593
797
  function processHandshakeRecord(data, epoch, encrypted) {
594
798
  // A handshake record may contain multiple handshake messages
595
799
  let off = 0;
800
+ let sawMessage = false; // record contained ≥1 parseable handshake message
801
+ let anyNew = false; // ≥1 message advanced our state (not a duplicate)
596
802
  while (off + 12 <= data.length) {
597
803
  let parsed = parse_dtls_handshake(data.subarray(off));
598
804
 
@@ -600,21 +806,65 @@ function DTLSSession(options) {
600
806
  let totalLen = parsed.length;
601
807
  let fragOffset = parsed.frag_offset;
602
808
  let fragLen = parsed.frag_length;
809
+ sawMessage = true;
810
+ _ldbg('hs msg: type=' + parsed.type + ' msgSeq=' + msgSeq +
811
+ ' len=' + totalLen + ' frag=' + fragOffset + '+' + fragLen +
812
+ ' nextReadMsgSeq=' + ctx.nextReadMsgSeq);
603
813
 
604
814
  // Is this a complete message or a fragment?
605
815
  if (fragOffset === 0 && fragLen === totalLen) {
606
816
  // Complete message — feed directly
607
- deliverHandshakeMessage(parsed.type, parsed.body, msgSeq);
817
+ if (deliverHandshakeMessage(parsed.type, parsed.body, msgSeq)) anyNew = true;
608
818
  } else {
609
819
  // Fragment — reassemble
610
- reassembleFragment(parsed);
820
+ if (reassembleFragment(parsed)) anyNew = true;
611
821
  }
612
822
 
613
823
  off += 12 + fragLen;
614
824
  }
615
825
 
616
- // Received handshake data → cancel retransmit (implicit ACK)
617
- cancelRetransmit();
826
+ // ── Implicit-ACK discipline (RFC 6347 §4.2.4) ──
827
+ // Receiving a message from the peer's NEXT flight is the implicit ACK
828
+ // of ours — only then may our retransmit timer be cancelled. The
829
+ // previous unconditional cancelRetransmit() here also fired on pure
830
+ // RETRANSMISSIONS of the peer's PREVIOUS flight, which mean the exact
831
+ // opposite: our reply was lost. Two concrete deadlocks that caused:
832
+ // 1. Our cookie-bearing CH2 is lost → server retransmits its HVR
833
+ // (seq 0, discarded as a duplicate) → cancel killed the CH2
834
+ // timer → CH2 never resent → handshake dead.
835
+ // 2. Our final flight (CKE/CCS/Finished) is lost → server
836
+ // retransmits its flight (all seqs below nextReadMsgSeq,
837
+ // discarded) → cancel killed the flight timer → dead.
838
+ // NOTE: the implicit-ACK cancel deliberately does NOT live here.
839
+ // Everything in this function runs AFTER deliverHandshakeMessage,
840
+ // and TLS produces our response flight synchronously inside the
841
+ // feed — so a cancel at this point lands on the freshly-armed timer
842
+ // of the flight we JUST sent, not on the acknowledged one. The
843
+ // cancel happens at the acceptance points inside
844
+ // deliverHandshakeMessage instead, before the feed.
845
+ if (anyNew) {
846
+ // handled at acceptance time
847
+ } else if (sawMessage && ctx.currentFlight.length > 0 &&
848
+ ctx.state !== 'connected' &&
849
+ ctx.selectedVersion !== DTLS_VERSION.DTLS1_3 &&
850
+ // Damping: a peer retransmits its flight as SEVERAL
851
+ // datagrams; without a guard, EACH duplicate datagram
852
+ // elicited a full resend of our flight — a ×N
853
+ // amplification burst that itself increased loss
854
+ // pressure. One resend per 250ms answers the peer's
855
+ // whole retransmitted flight exactly once.
856
+ (Date.now() - (ctx._lastDupElicitedResend || 0)) >= 250) {
857
+ ctx._lastDupElicitedResend = Date.now();
858
+ // Pure duplicate of a previous peer flight while we still have an
859
+ // outstanding flight of our own: the peer is telling us it never
860
+ // got our reply. Resend it immediately (RFC 6347 §4.2.4 responder
861
+ // behavior) rather than waiting out our own backoff. The regular
862
+ // timer keeps running unchanged, so the retransmit budget still
863
+ // bounds the total effort; the resend rate is naturally bounded by
864
+ // the peer's own retransmission schedule. DTLS 1.3 is excluded —
865
+ // it uses explicit ACKs (RFC 9147 §7) instead of flight inference.
866
+ resendFlight();
867
+ }
618
868
  }
619
869
 
620
870
  /**
@@ -631,7 +881,7 @@ function DTLSSession(options) {
631
881
  let key = parsed.msg_seq;
632
882
 
633
883
  // Ignore fragments for messages we've already delivered.
634
- if (key < ctx.nextReadMsgSeq) return;
884
+ if (key < ctx.nextReadMsgSeq) return false;
635
885
 
636
886
  // DoS guard: the 24-bit `length` is attacker-controlled on the first
637
887
  // fragment. Reject anything claiming more than our cap BEFORE allocating,
@@ -639,7 +889,7 @@ function DTLSSession(options) {
639
889
  if (parsed.length > ctx.maxHandshakeMessageSize) {
640
890
  ev.emit('error', new Error('DTLS handshake message exceeds maxHandshakeMessageSize (' +
641
891
  parsed.length + ' > ' + ctx.maxHandshakeMessageSize + ')'));
642
- return;
892
+ return false;
643
893
  }
644
894
 
645
895
  let frag = ctx.fragments[key];
@@ -656,7 +906,15 @@ function DTLSSession(options) {
656
906
  let end = parsed.frag_offset + parsed.frag_length;
657
907
 
658
908
  // Bounds guard against a malformed fragment claiming to exceed totalLength.
659
- if (end > frag.totalLength) return;
909
+ if (end > frag.totalLength) return false;
910
+
911
+ // Coverage before this fragment — used to decide whether it contributed
912
+ // anything new (a retransmitted fragment that only re-covers known bytes
913
+ // is a duplicate for implicit-ACK purposes).
914
+ let coveredBefore = 0;
915
+ for (let cr = 0; cr < frag.ranges.length; cr++) {
916
+ coveredBefore += frag.ranges[cr][1] - frag.ranges[cr][0];
917
+ }
660
918
 
661
919
  // Copy the fragment bytes in (overlaps simply overwrite with identical data).
662
920
  frag.buf.set(parsed.body.subarray(0, parsed.frag_length), start);
@@ -675,27 +933,44 @@ function DTLSSession(options) {
675
933
  }
676
934
  frag.ranges = merged;
677
935
 
936
+ let coveredAfter = 0;
937
+ for (let ca = 0; ca < merged.length; ca++) {
938
+ coveredAfter += merged[ca][1] - merged[ca][0];
939
+ }
940
+
941
+
678
942
  // Complete when a single range covers [0, totalLength).
679
943
  if (merged.length === 1 && merged[0][0] === 0 && merged[0][1] === frag.totalLength) {
680
944
  delete ctx.fragments[key];
681
- deliverHandshakeMessage(frag.type, frag.buf, key);
945
+ return deliverHandshakeMessage(frag.type, frag.buf, key);
682
946
  }
947
+ return coveredAfter > coveredBefore;
683
948
  }
684
949
 
685
950
  /**
686
951
  * Deliver a complete handshake message to TLSSession.
687
952
  * Strips DTLS reconstruction → builds TLS format.
953
+ *
954
+ * @returns {boolean} true if the message advanced our handshake state
955
+ * (delivered in-order, buffered out-of-order, or consumed as a valid
956
+ * HVR / cookie-exchange step); false if it was a duplicate or was
957
+ * dropped. processHandshakeRecord uses this for the implicit-ACK
958
+ * decision — only genuinely new data may cancel our retransmit timer.
688
959
  */
689
960
  function deliverHandshakeMessage(type, body, msgSeq) {
690
961
  // Skip if we've already processed this msg_seq (duplicate / retransmit).
691
- if (msgSeq < ctx.nextReadMsgSeq) return;
962
+ if (msgSeq < ctx.nextReadMsgSeq) return false;
692
963
 
693
964
  // Check for HelloVerifyRequest (DTLS 1.2 server→client, type=3)
694
965
  if (type === 3 && !isServer) {
695
966
  ctx.nextReadMsgSeq = msgSeq + 1;
967
+ // The HVR acknowledges CH1 (implicit ACK, RFC 6347 §4.2.4). No
968
+ // explicit cancel is needed: triggerClientHelloWithCookie arms the
969
+ // CH2 flight's own timer, and startRetransmitTimer clears any
970
+ // previous timer as its first step.
696
971
  let hvr = parse_hello_verify_request(body);
697
972
  triggerClientHelloWithCookie(hvr.cookie);
698
- return;
973
+ return true;
699
974
  }
700
975
 
701
976
  // ---- Server-side HelloVerifyRequest cookie exchange (RFC 6347 §4.2.1) ----
@@ -707,27 +982,84 @@ function DTLSSession(options) {
707
982
  //
708
983
  // Note: msg_seq is NOT advanced for a rejected/HVR-triggering ClientHello, so
709
984
  // the client's retried CH (which reuses msg_seq per RFC 6347) is still accepted.
710
- if (type === 1 && isServer && options.useCookies === true &&
711
- ctx.selectedVersion !== DTLS_VERSION.DTLS1_3) {
712
-
985
+ if (type === 1 && isServer) {
713
986
  let ch = null;
714
987
  try { ch = parse_hello({ kind: 'client', body: body }); } catch (e) { ch = null; }
988
+
989
+ // Does this ClientHello offer DTLS 1.3?
990
+ //
991
+ // RULE: a decision derived from an incoming message is read from that
992
+ // message, never from state the message itself is about to establish.
993
+ // (Stated in full next to params_to_set in tls_session.js; this is the
994
+ // fourth bug it has caused.) Read it from the MESSAGE, never
995
+ // from ctx.selectedVersion — when the first ClientHello arrives nothing
996
+ // has been negotiated yet, so a state-based test reads null and fails
997
+ // OPEN. That matters because the two rules below are MUST-level and
998
+ // version-specific.
999
+ let offersDtls13 = false;
1000
+ if (ch && Array.isArray(ch.supported_versions)) {
1001
+ offersDtls13 = ch.supported_versions.indexOf(DTLS_VERSION.DTLS1_3) >= 0;
1002
+ }
1003
+
1004
+ // RFC 9147 §5.3: "A DTLS 1.3-only client MUST set the legacy_cookie field
1005
+ // to zero length. If a DTLS 1.3 ClientHello is received with any other
1006
+ // value in this field, the server MUST abort the handshake with an
1007
+ // 'illegal_parameter' alert." DTLS 1.3 carries cookies in the `cookie`
1008
+ // EXTENSION; the legacy body field is vestigial and must stay empty.
1009
+ if (offersDtls13 && ch && ch.dtls_cookie && ch.dtls_cookie.length > 0) {
1010
+ tls.sendAlert(2, 47); // fatal, illegal_parameter
1011
+ ev.emit('error', new Error(
1012
+ 'DTLS 1.3 ClientHello carries a non-empty legacy_cookie (' +
1013
+ ch.dtls_cookie.length + ' bytes) — RFC 9147 §5.3 requires zero length'));
1014
+ return false;
1015
+ }
1016
+
1017
+ // RFC 9147 §5.1: "DTLS 1.3-compliant implementations MUST NOT use the
1018
+ // HelloVerifyRequest to execute a return-routability check." DTLS 1.3
1019
+ // does return-routability with HelloRetryRequest + the cookie extension
1020
+ // instead. The old guard tested ctx.selectedVersion, which is null here,
1021
+ // so a DTLS 1.3 handshake could still be answered with an HVR.
1022
+ if (options.useCookies === true && !offersDtls13) {
1023
+
715
1024
  let incomingCookie = (ch && ch.dtls_cookie) ? ch.dtls_cookie : new Uint8Array(0);
716
1025
 
717
1026
  if (incomingCookie.length === 0) {
718
1027
  // First ClientHello → send HVR with a fresh cookie. Don't process the CH,
719
1028
  // don't advance msg_seq (the retry reuses the same msg_seq).
1029
+ // Counts as progress: a retransmitted cookieless CH means the client
1030
+ // never got our HVR — resending it here IS the correct response.
720
1031
  sendHelloVerifyRequest();
721
- return;
1032
+ return true;
722
1033
  }
723
1034
 
724
1035
  // Second ClientHello: the cookie MUST match what we issued.
725
1036
  if (!ctx.hvrCookie || !timingSafeEqualU8(incomingCookie, ctx.hvrCookie)) {
726
1037
  // Cookie mismatch → silently drop (RFC 6347: server discards). Don't
727
1038
  // advance msg_seq; a correct retry can still arrive.
728
- return;
1039
+ return false;
1040
+ }
1041
+ // Cookie verified → accept this ClientHello as the handshake anchor.
1042
+ //
1043
+ // Strict RFC 6347 §4.2.2 (see the figure in the RFC): the cookie-
1044
+ // bearing CH2 arrives with message_seq 1, and our ServerHello must
1045
+ // be numbered 1 as well. The previous code assumed the retried CH
1046
+ // "reuses msg_seq" 0 — that is only true for a pure retransmission
1047
+ // of CH1 (HVR lost), not for the cookie-bearing CH2, so RFC-
1048
+ // compliant clients (pion, OpenSSL, fixed lemon-tls) had their CH2
1049
+ // buffered as "out of order" forever and the handshake deadlocked.
1050
+ //
1051
+ // Set the read cursor and the outgoing counter to 1; the in-order
1052
+ // check below then accepts exactly a seq-1 CH2 and rejects anything
1053
+ // else (including seq-0 CH2s from pre-fix lemon-tls clients — non-
1054
+ // compliant numbering is intentionally not accommodated). The
1055
+ // transcript stays consistent on both sides because
1056
+ // feedHandshakeToTls records the incoming wire seq and outgoing
1057
+ // messages record message_sent_seq at emission — which is exactly
1058
+ // what the Finished MAC hashes (RFC 6347 §4.2.6).
1059
+ ctx.nextReadMsgSeq = 1;
1060
+ tls.context.message_sent_seq = 1;
1061
+ // Fall through and process the ClientHello normally.
729
1062
  }
730
- // Cookie verified → fall through and process the ClientHello normally.
731
1063
  }
732
1064
 
733
1065
  // ---- In-order delivery (RFC 6347 §4.2.4 / RFC 9147 §5.2) ----
@@ -739,21 +1071,56 @@ function DTLSSession(options) {
739
1071
  // Out of order — hold it (ignore a duplicate already buffered).
740
1072
  if (!(msgSeq in ctx.pendingMessages)) {
741
1073
  ctx.pendingMessages[msgSeq] = { type: type, body: body };
1074
+ return true; // new information, even if not yet deliverable
742
1075
  }
743
- return;
1076
+ return false; // duplicate of an already-buffered message
744
1077
  }
745
1078
 
746
- // msgSeq === nextReadMsgSeq → feed it and advance.
1079
+ // msgSeq === nextReadMsgSeq → accept. Timer discipline, per the
1080
+ // RFC 6347 §4.2.4 state machine, in two halves:
1081
+ //
1082
+ // 1. A message from the peer's next flight is the implicit ACK of
1083
+ // ours — but it must RE-ARM the timer, not cancel it. While we
1084
+ // are in WAITING (peer's flight only partially received — e.g.
1085
+ // one datagram of the server's SH..SHD flight lost), timer
1086
+ // expiry retransmits OUR last flight, which elicits a full
1087
+ // retransmission of the peer's flight (a cookie-valid duplicate
1088
+ // CH2 makes werift resend flight 4, a duplicate Finished makes
1089
+ // it resend flight 6). An earlier revision cancelled here: one
1090
+ // lost datagram inside the peer's flight then left BOTH sides
1091
+ // timer-less and deadlocked — ~50% handshake failure at just 5%
1092
+ // random loss.
1093
+ //
1094
+ // 2. The re-arm must happen BEFORE feeding TLS: the feed can
1095
+ // synchronously produce our entire next flight, whose fresh
1096
+ // timer must not be clobbered afterwards (the original code
1097
+ // cancelled at the end of processHandshakeRecord — after the
1098
+ // response — so no responder flight was EVER protected by
1099
+ // retransmission).
1100
+ //
1101
+ // When the feed does respond, the flight boundary (newFlightPending
1102
+ // → startNewFlight) resets the retransmit budget and the outgoing
1103
+ // path arms the new flight's timer, superseding this re-arm.
1104
+ startRetransmitTimer();
1105
+ ctx.newFlightPending = true;
747
1106
  feedHandshakeToTls(type, body, msgSeq);
748
1107
  ctx.nextReadMsgSeq = msgSeq + 1;
749
1108
 
750
- // Drain consecutively-numbered buffered messages.
1109
+ // Drain consecutively-numbered buffered messages. Same pre-feed
1110
+ // ordering applies; for a timer armed by an earlier feed in this very
1111
+ // drain, cancelling here would be wrong — but a mid-flight response
1112
+ // does not occur in TLS flows (a flight is answered only after its
1113
+ // final message), so each pre-feed cancel can only ever hit an
1114
+ // already-acknowledged flight's timer (usually already null).
751
1115
  while (ctx.nextReadMsgSeq in ctx.pendingMessages) {
752
1116
  let next = ctx.pendingMessages[ctx.nextReadMsgSeq];
753
1117
  delete ctx.pendingMessages[ctx.nextReadMsgSeq];
1118
+ startRetransmitTimer();
1119
+ ctx.newFlightPending = true;
754
1120
  feedHandshakeToTls(next.type, next.body, ctx.nextReadMsgSeq);
755
1121
  ctx.nextReadMsgSeq++;
756
1122
  }
1123
+ return true;
757
1124
  }
758
1125
 
759
1126
  /**
@@ -806,12 +1173,41 @@ function DTLSSession(options) {
806
1173
  // RFC 6347: "the client MUST use the same parameter values"
807
1174
  let ch2 = insertCookieIntoClientHello(ctx.savedClientHello, cookie);
808
1175
 
809
- // Reset state
1176
+ // Reset state for the post-HVR handshake restart.
1177
+ //
1178
+ // RFC 6347 §4.2.1: the handshake transcript restarts at CH2 — the
1179
+ // cookieless CH1 and the HelloVerifyRequest are NOT included in the
1180
+ // handshake hash. Resetting the transcript is therefore correct.
1181
+ //
1182
+ // RFC 6347 §4.2.2, however, is explicit (see the figure in the RFC)
1183
+ // that message_seq does NOT restart:
1184
+ //
1185
+ // ClientHello (seq=1) ------> ← CH2 carries seq 1
1186
+ // (with cookie)
1187
+ // <------ ServerHello (seq=1)
1188
+ //
1189
+ // The previous code reset BOTH directions of the counter to 0: it
1190
+ // sent CH2 with msg_seq=0 and reset nextReadMsgSeq to 0. Against a
1191
+ // spec-compliant server (pion, werift, OpenSSL — all send an HVR and
1192
+ // then number ServerHello=1), the entire server flight (seq 1..N)
1193
+ // landed in pendingMessages waiting forever for a seq-0 message that
1194
+ // never comes: the handshake stalled silently — no error, no alert.
1195
+ // It only ever worked lemon-tls↔lemon-tls because our own server
1196
+ // sends an HVR only when options.useCookies is set, so the broken
1197
+ // path was never exercised same-stack.
1198
+ //
1199
+ // nextReadMsgSeq is deliberately NOT touched here: the HVR branch in
1200
+ // deliverHandshakeMessage already advanced it to 1, which is exactly
1201
+ // where the server's ServerHello will arrive.
810
1202
  tls.context.transcript = [];
811
1203
  tls.context.hello_sent = true;
812
1204
  tls.context.dtls_cookie = cookie;
813
- tls.context.message_sent_seq = 0;
814
- ctx.nextReadMsgSeq = 0;
1205
+ // CH2 is message_seq 1. Set message_sent_seq BEFORE the transcript
1206
+ // push so transcriptHook / transcriptMsgSeqs record seq 1 for CH2 —
1207
+ // the Finished MAC hashes the DTLS reconstruction data including
1208
+ // message_seq (RFC 6347 §4.2.6), so a transcript recording seq 0 for
1209
+ // a wire message sent with seq 1 would fail Finished verification.
1210
+ tls.context.message_sent_seq = 1;
815
1211
  ctx.currentFlight = [];
816
1212
  ctx.fragments = {}; // discard any partial reassembly from CH1 flight
817
1213
  ctx.pendingMessages = {}; // discard any out-of-order buffer from CH1 flight
@@ -824,13 +1220,13 @@ function DTLSSession(options) {
824
1220
  tls.context.transcriptHook ? tls.context.transcriptHook(ch2) : ch2
825
1221
  );
826
1222
 
827
- // Build DTLS message and send
828
- let dtlsMsg = build_dtls_handshake(ch2, 0);
1223
+ // Build DTLS message and send — msg_seq 1 per the RFC figure above.
1224
+ let dtlsMsg = build_dtls_handshake(ch2, 1);
829
1225
  let record = buildRecord(0, CT.HANDSHAKE, dtlsMsg);
830
- ctx.currentFlight = [record];
831
- ev.emit('packet', record);
1226
+ ctx.currentFlight = [{ epoch: 0, contentType: CT.HANDSHAKE, payload: dtlsMsg }];
1227
+ if (record) ev.emit('packet', record);
832
1228
 
833
- tls.context.message_sent_seq = 1;
1229
+ tls.context.message_sent_seq = 2;
834
1230
  startRetransmitTimer();
835
1231
  }
836
1232
 
@@ -839,6 +1235,21 @@ function DTLSSession(options) {
839
1235
  // Flight tracking + retransmission
840
1236
  // ============================================================
841
1237
 
1238
+ /**
1239
+ * Rebuild and re-emit the current flight with FRESH record sequence numbers.
1240
+ * See currentFlight's comment for why re-encryption (not byte replay) is
1241
+ * mandatory. Each descriptor goes back through buildRecord, which pulls the
1242
+ * next write seq for its epoch — so a retransmitted DTLS 1.3 record gets a
1243
+ * new nonce, and a monotonic seq the receiver can reconstruct.
1244
+ */
1245
+ function resendFlight() {
1246
+ for (let i = 0; i < ctx.currentFlight.length; i++) {
1247
+ let f = ctx.currentFlight[i];
1248
+ let record = buildRecord(f.epoch, f.contentType, f.payload);
1249
+ if (record) ev.emit('packet', record);
1250
+ }
1251
+ }
1252
+
842
1253
  function startRetransmitTimer() {
843
1254
  cancelRetransmit();
844
1255
  ctx.retransmitTimer = setTimeout(function() {
@@ -849,10 +1260,8 @@ function DTLSSession(options) {
849
1260
  return;
850
1261
  }
851
1262
 
852
- // Retransmit entire current flight
853
- for (let i = 0; i < ctx.currentFlight.length; i++) {
854
- ev.emit('packet', ctx.currentFlight[i]);
855
- }
1263
+ // Retransmit entire current flight with fresh record seqs.
1264
+ resendFlight();
856
1265
 
857
1266
  ctx.retransmitCount++;
858
1267
  ctx.retransmitTimeout = Math.min(ctx.retransmitTimeout * 2, 60000);
@@ -910,6 +1319,8 @@ function DTLSSession(options) {
910
1319
  });
911
1320
 
912
1321
  tls.on('secureConnect', function() {
1322
+ _ldbg('handshake complete — secureConnect (version=0x' +
1323
+ ((ctx.selectedVersion || tls.context.selected_version || 0).toString(16)) + ')');
913
1324
  ctx.state = 'connected';
914
1325
  cancelRetransmit();
915
1326
  startNewFlight();
@@ -948,7 +1359,21 @@ function DTLSSession(options) {
948
1359
 
949
1360
  function close() {
950
1361
  if (ctx.state === 'closed') return;
951
- let epoch = ctx.state === 'connected' ? (ctx.selectedVersion === DTLS_VERSION.DTLS1_3 ? 3 : 1) : 0;
1362
+ // Choose the epoch by what the PEER can read, not by whether WE consider
1363
+ // the handshake finished — the same rule the TLS branch uses for alerts.
1364
+ //
1365
+ // "not connected yet" does not mean "no keys": once handshake keys exist
1366
+ // the peer has switched to reading protected records, and a plaintext
1367
+ // close_notify arrives with a classic DTLSPlaintext header where the peer
1368
+ // requires the RFC 9147 §4 unified header. It then reports a bad record
1369
+ // type instead of a clean shutdown. Mirror the ACK path, which already
1370
+ // picks the highest epoch we hold write keys for.
1371
+ let epoch;
1372
+ if (ctx.selectedVersion === DTLS_VERSION.DTLS1_3) {
1373
+ epoch = ctx.keys[3] ? 3 : (ctx.keys[2] ? 2 : 0);
1374
+ } else {
1375
+ epoch = ctx.keys[1] ? 1 : 0;
1376
+ }
952
1377
  sendAlertRecord(epoch, new Uint8Array([1, 0])); // warning, close_notify
953
1378
  ctx.state = 'closed';
954
1379
  cancelRetransmit();
@@ -1003,6 +1428,16 @@ function DTLSSession(options) {
1003
1428
  return tls.exportKeyingMaterial(length, label, context_value);
1004
1429
  },
1005
1430
 
1431
+ /**
1432
+ * The negotiated DTLS-SRTP protection profile (RFC 5764), or null.
1433
+ * Surfaced here because DTLS-SRTP is the reason the exporter exists for
1434
+ * this transport — a caller that can export keying material must also be
1435
+ * able to learn which profile those keys are for.
1436
+ */
1437
+ getSelectedSrtpProfile: function() {
1438
+ return tls.getSelectedSrtpProfile();
1439
+ },
1440
+
1006
1441
  /** Peer's hello extensions: [{ type, name, data, value }]. */
1007
1442
  getRemoteExtensions: function() { return tls.getRemoteExtensions(); },
1008
1443
 
@@ -1041,4 +1476,4 @@ function DTLSSession(options) {
1041
1476
  return this;
1042
1477
  }
1043
1478
 
1044
- export default DTLSSession;
1479
+ export default DTLSSession;