lemon-tls 0.4.1 → 0.5.5

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.
@@ -27,6 +27,7 @@
27
27
  */
28
28
 
29
29
  import { EventEmitter } from 'node:events';
30
+ import crypto from 'node:crypto';
30
31
 
31
32
  import TLSSession from './tls_session.js';
32
33
  import createSecureContext from './secure_context.js';
@@ -38,6 +39,7 @@ import {
38
39
  build_message,
39
40
  build_hello_verify_request,
40
41
  parse_hello_verify_request,
42
+ parse_hello,
41
43
  } from './wire.js';
42
44
 
43
45
  import {
@@ -59,6 +61,8 @@ import {
59
61
  parseDtlsAck,
60
62
  } from './record.js';
61
63
 
64
+ import { timingSafeEqualU8 } from './utils.js';
65
+
62
66
 
63
67
  // ============================================================
64
68
  // Helpers
@@ -103,6 +107,20 @@ function insertCookieIntoClientHello(ch1, cookie) {
103
107
  return ch2;
104
108
  }
105
109
 
110
+ // Handshake diagnostics — off by default; enabled via LEMON_DEBUG=1 (or
111
+ // WEBRTC_DEBUG=1 so one flag lights up the whole webrtc-server stack).
112
+ // Interop failures in DTLS are notoriously silent (a record that fails
113
+ // decryption or ordering simply vanishes); these logs make every inbound
114
+ // record, ordering decision, and completion visible without a packet dump.
115
+ var LEMON_DEBUG = typeof process !== 'undefined' && process.env &&
116
+ (process.env.LEMON_DEBUG === '1' || process.env.WEBRTC_DEBUG === '1');
117
+ function _ldbg() {
118
+ if (!LEMON_DEBUG) return;
119
+ if (typeof console !== 'undefined' && console.log) {
120
+ console.log.apply(console, ['[lemon-dtls]'].concat([].slice.call(arguments)));
121
+ }
122
+ }
123
+
106
124
  function DTLSSession(options) {
107
125
  if (!(this instanceof DTLSSession)) return new DTLSSession(options);
108
126
  options = options || {};
@@ -113,6 +131,16 @@ function DTLSSession(options) {
113
131
  // ---- Internal TLSSession ----
114
132
  let tls = new TLSSession({
115
133
  isServer: isServer,
134
+ // RFC 9147 §5.3: the DTLS ClientHello's legacy_session_id "MUST be
135
+ // set to a zero-length vector" — the 32-byte compatibility sid is a
136
+ // TLS-over-TCP middlebox trick (RFC 8446 §4.1.2) that DTLS excludes
137
+ // (8446 appendix D.4: no compatibility mode over DTLS). The TLS
138
+ // engine generates the compat sid when none is supplied, so pin it
139
+ // empty for DTLS, both 1.2 and 1.3 profiles. (Also sidesteps a
140
+ // deployed-parser landmine: webrtc-dtls ≤0.7 reads the sid LENGTH
141
+ // byte and never the body, so any non-empty sid shears its whole
142
+ // ClientHello parse.)
143
+ sessionId: new Uint8Array(0),
116
144
  servername: options.servername,
117
145
  SNICallback: options.SNICallback,
118
146
  rejectUnauthorized: options.rejectUnauthorized,
@@ -130,6 +158,15 @@ function DTLSSession(options) {
130
158
  isServer: isServer,
131
159
  mtu: options.mtu || 1200,
132
160
 
161
+ // Upper bound on a single reassembled handshake message. The DTLS handshake
162
+ // header carries a 24-bit length (up to 16MB), which is attacker-controlled
163
+ // on the very first fragment. Without a cap, a peer could send one fragment
164
+ // claiming length 0xFFFFFF for several msg_seqs and force large allocations
165
+ // before anything is authenticated. 256KB comfortably fits a real
166
+ // certificate-chain flight while bounding the damage. Configurable via
167
+ // options.maxHandshakeMessageSize.
168
+ maxHandshakeMessageSize: options.maxHandshakeMessageSize || (256 * 1024),
169
+
133
170
  // Version (determined after negotiation)
134
171
  selectedVersion: null, // DTLS version (0xFEFC or 0xFEFD)
135
172
 
@@ -146,14 +183,29 @@ function DTLSSession(options) {
146
183
  cipherSuite: null,
147
184
  hashName: null,
148
185
 
149
- // Fragment reassembly: msg_seq → { totalLength, parts: { offset: Uint8Array } }
186
+ // Fragment reassembly: msg_seq → { totalLength, type, chunks:[{off,end,body}], covered }
150
187
  fragments: {},
151
188
 
152
189
  // Incoming handshake msg_seq tracking
153
190
  nextReadMsgSeq: 0,
154
191
 
192
+ // Out-of-order handshake buffering (RFC 6347 §4.2.4 / RFC 9147 §5.2):
193
+ // complete messages that arrive ahead of nextReadMsgSeq are held here,
194
+ // keyed by msg_seq, and drained in order once the gap is filled.
195
+ pendingMessages: {}, // msg_seq → { type, body }
196
+
155
197
  // Flight tracking for retransmission
156
198
  currentFlight: [], // array of { epoch, data: Uint8Array (complete datagram) }
199
+ // RFC 6347 §4.2.4 flight discipline: a NEW flight begins with the first
200
+ // record we send after having accepted new handshake data from the peer.
201
+ // Set when inbound processing advances state; consumed (and reset) by
202
+ // the outgoing path, which then starts a fresh currentFlight. Without
203
+ // this boundary, flights ACCUMULATED: after the HVR exchange the client's
204
+ // flight 5 (CKE/CCS/Finished) was appended onto the CH2 still sitting in
205
+ // currentFlight, so a flight-5 retransmission replayed the ClientHello
206
+ // too — which a connected werift server answers with renegotiation(),
207
+ // destroying the session instead of recovering it.
208
+ newFlightPending: false,
157
209
  retransmitTimer: null,
158
210
  retransmitCount: 0,
159
211
  retransmitTimeout: 1000,
@@ -371,6 +423,14 @@ function DTLSSession(options) {
371
423
  }
372
424
  }
373
425
 
426
+ // Flight boundary: first outgoing record after accepting peer data
427
+ // starts a fresh flight (clears the previous flight's records and
428
+ // resets the retransmit budget — RFC 6347 §4.2.4).
429
+ if (ctx.newFlightPending) {
430
+ ctx.newFlightPending = false;
431
+ startNewFlight();
432
+ }
433
+
374
434
  // DTLS 1.2: send CCS before first encrypted message (Finished)
375
435
  if (ctx.selectedVersion !== DTLS_VERSION.DTLS1_3 && tlsEpoch === 1 && !ctx.localCcsSent) {
376
436
  sendCCS();
@@ -389,7 +449,22 @@ function DTLSSession(options) {
389
449
  ev.emit('packet', record);
390
450
  }
391
451
 
392
- // Start retransmit timer after sending flight messages
452
+ // Start retransmit timer after sending flight messages.
453
+ //
454
+ // BUGFIX (one-shot initial ClientHello): ctx.state only left 'idle'
455
+ // inside feedDatagram — i.e. on the first INBOUND datagram. A
456
+ // client's first flight is sent before anything has been received,
457
+ // so the 'handshaking'-only guard below never armed the retransmit
458
+ // timer for flight 1. A lost or early-dropped ClientHello — e.g. one
459
+ // arriving while the peer's ICE layer still discards non-STUN
460
+ // traffic, a routine race right after nomination — was therefore
461
+ // never retransmitted, and the handshake hung silently forever
462
+ // (surfacing as connectionState stuck at 'connecting'). RFC 6347
463
+ // §4.2.4's flight state machine has no unguarded flights: every
464
+ // flight except the handshake-final one must sit under a retransmit
465
+ // timer. Sending a handshake flight IS handshaking — reflect that in
466
+ // the state before consulting it.
467
+ if (ctx.state === 'idle') ctx.state = 'handshaking';
393
468
  if (ctx.state === 'handshaking') {
394
469
  startRetransmitTimer();
395
470
  }
@@ -425,9 +500,18 @@ function DTLSSession(options) {
425
500
  * Returns array of DTLS handshake message fragments (each with reconstruction header).
426
501
  */
427
502
  function fragmentMessage(dtlsMsg, tlsMsg, msgSeq) {
428
- let overhead = 13 + 16; // record header + AEAD tag (approximate)
429
- let maxFragment = ctx.mtu - overhead;
430
-
503
+ // Record-layer overhead we must leave room for inside one datagram/MTU:
504
+ // DTLS 1.2: 13-byte classic record header + 16-byte AEAD tag (+8 explicit
505
+ // nonce for GCM, but that lives inside the encrypted payload the
506
+ // record layer builds, so we keep a small safety margin).
507
+ // DTLS 1.3: 5-byte unified header (2-byte seq + length) + 16-byte AEAD tag.
508
+ // Using the correct, smaller 1.3 overhead avoids over-fragmenting.
509
+ let isDtls13 = ctx.selectedVersion === DTLS_VERSION.DTLS1_3;
510
+ let recordOverhead = isDtls13 ? (5 + 16) : (13 + 16 + 8);
511
+ let maxFragment = ctx.mtu - recordOverhead;
512
+
513
+ // The DTLS handshake message header is 12 bytes; the body is what we chunk.
514
+ const HS_HDR = 12;
431
515
  if (dtlsMsg.length <= maxFragment) return [dtlsMsg];
432
516
 
433
517
  // Need to fragment: split the TLS body into chunks
@@ -436,8 +520,11 @@ function DTLSSession(options) {
436
520
  let fragments = [];
437
521
  let offset = 0;
438
522
 
523
+ // Each fragment carries its own 12-byte handshake header, so the payload
524
+ // budget per fragment is maxFragment - HS_HDR.
525
+ let maxChunk = Math.max(1, maxFragment - HS_HDR);
439
526
  while (offset < totalLength) {
440
- let chunkLen = Math.min(maxFragment - 12, totalLength - offset); // 12 = DTLS handshake header
527
+ let chunkLen = Math.min(maxChunk, totalLength - offset);
441
528
  let frag = build_dtls_handshake(tlsMsg, msgSeq, offset, chunkLen);
442
529
  fragments.push(frag);
443
530
  offset += chunkLen;
@@ -460,7 +547,19 @@ function DTLSSession(options) {
460
547
  function sendCCS() {
461
548
  if (ctx.localCcsSent) return;
462
549
  ctx.localCcsSent = true;
550
+ // The server's final flight OPENS with CCS — honor the flight boundary
551
+ // here too, before this record is captured.
552
+ if (ctx.newFlightPending) {
553
+ ctx.newFlightPending = false;
554
+ startNewFlight();
555
+ }
463
556
  let record = buildDtlsPlaintext(CT.CHANGE_CIPHER_SPEC, 0, nextWriteSeq(0), new Uint8Array([1]));
557
+ // RFC 6347 §4.2.4: the CCS is part of the flight and MUST be included
558
+ // when the flight is retransmitted. Previously it was emitted but never
559
+ // captured, so a retransmitted final flight arrived WITHOUT the cipher-
560
+ // spec change and the peer could never decrypt the accompanying
561
+ // Finished — an unrecoverable stall under single-datagram loss.
562
+ ctx.currentFlight.push(record);
464
563
  ev.emit('packet', record);
465
564
  }
466
565
 
@@ -483,7 +582,29 @@ function DTLSSession(options) {
483
582
 
484
583
  let records = parseDtlsDatagram(data, keysByEpoch);
485
584
 
585
+ if (LEMON_DEBUG) {
586
+ var _summ = [];
587
+ for (var _ri = 0; _ri < records.length; _ri++) {
588
+ var _r = records[_ri];
589
+ _summ.push('type=' + _r.type + ' epoch=' + _r.epoch + ' seq=' + _r.seq +
590
+ (_r.epoch > 0 && !_r.encrypted ? ' UNDECRYPTED' : ''));
591
+ }
592
+ _ldbg('rx datagram len=' + data.length + ' state=' + ctx.state +
593
+ ' records: [' + _summ.join(' | ') + ']');
594
+ }
595
+
596
+ // RFC 9147 §7: collect the record numbers of handshake records we received
597
+ // in this datagram so we can ACK them once, after processing. Only for
598
+ // DTLS 1.3, and only while we're still handshaking (post-handshake ACKs of
599
+ // NewSessionTicket etc. are optional and skipped here for simplicity).
600
+ let ackable = [];
601
+
486
602
  for (let i = 0; i < records.length; i++) {
603
+ if (records[i].type === CT.HANDSHAKE &&
604
+ ctx.selectedVersion === DTLS_VERSION.DTLS1_3) {
605
+ ackable.push({ epoch: records[i].epoch, seq: records[i].seq });
606
+ }
607
+
487
608
  processRecord(records[i]);
488
609
 
489
610
  // After CCS, keys are newly available — re-decrypt remaining epoch>0 records
@@ -500,10 +621,30 @@ function DTLSSession(options) {
500
621
  }
501
622
  }
502
623
  }
624
+
625
+ // Send a single ACK covering all handshake records received in this datagram.
626
+ // We ACK from the highest epoch for which we already have write keys (so the
627
+ // ACK itself is protected once handshake keys exist; epoch 0 before that).
628
+ if (ackable.length > 0) {
629
+ let ackEpoch = ctx.keys[3] ? 3 : (ctx.keys[2] ? 2 : 0);
630
+ sendAck(ackEpoch, ackable);
631
+ }
503
632
  }
504
633
 
505
634
  function processRecord(record) {
506
635
  if (record.type === CT.HANDSHAKE) {
636
+ // An epoch>0 record that could not be decrypted (keys not yet
637
+ // installed, AEAD failure) must NOT be fed to the handshake
638
+ // parser: its content is ciphertext, and parsing it yields a
639
+ // garbage "message" with attacker-controllable type/length fields.
640
+ // Drop it — DTLS is loss-tolerant by design, and the peer's (or
641
+ // our own flight-elicited) retransmission delivers a fresh copy
642
+ // once it can be decrypted.
643
+ if (record.epoch > 0 && !record.encrypted) {
644
+ _ldbg('skip undecrypted epoch=' + record.epoch + ' seq=' + record.seq +
645
+ ' len=' + (record.content ? record.content.length : 0));
646
+ return;
647
+ }
507
648
  processHandshakeRecord(record.content, record.epoch, record.encrypted);
508
649
  } else if (record.type === CT.APPLICATION_DATA) {
509
650
  if (ctx.state === 'connected') {
@@ -544,6 +685,8 @@ function DTLSSession(options) {
544
685
  function processHandshakeRecord(data, epoch, encrypted) {
545
686
  // A handshake record may contain multiple handshake messages
546
687
  let off = 0;
688
+ let sawMessage = false; // record contained ≥1 parseable handshake message
689
+ let anyNew = false; // ≥1 message advanced our state (not a duplicate)
547
690
  while (off + 12 <= data.length) {
548
691
  let parsed = parse_dtls_handshake(data.subarray(off));
549
692
 
@@ -551,70 +694,294 @@ function DTLSSession(options) {
551
694
  let totalLen = parsed.length;
552
695
  let fragOffset = parsed.frag_offset;
553
696
  let fragLen = parsed.frag_length;
697
+ sawMessage = true;
698
+ _ldbg('hs msg: type=' + parsed.type + ' msgSeq=' + msgSeq +
699
+ ' len=' + totalLen + ' frag=' + fragOffset + '+' + fragLen +
700
+ ' nextReadMsgSeq=' + ctx.nextReadMsgSeq);
554
701
 
555
702
  // Is this a complete message or a fragment?
556
703
  if (fragOffset === 0 && fragLen === totalLen) {
557
704
  // Complete message — feed directly
558
- deliverHandshakeMessage(parsed.type, parsed.body, msgSeq);
705
+ if (deliverHandshakeMessage(parsed.type, parsed.body, msgSeq)) anyNew = true;
559
706
  } else {
560
707
  // Fragment — reassemble
561
- reassembleFragment(parsed);
708
+ if (reassembleFragment(parsed)) anyNew = true;
562
709
  }
563
710
 
564
711
  off += 12 + fragLen;
565
712
  }
566
713
 
567
- // Received handshake data → cancel retransmit (implicit ACK)
568
- cancelRetransmit();
714
+ // ── Implicit-ACK discipline (RFC 6347 §4.2.4) ──
715
+ // Receiving a message from the peer's NEXT flight is the implicit ACK
716
+ // of ours — only then may our retransmit timer be cancelled. The
717
+ // previous unconditional cancelRetransmit() here also fired on pure
718
+ // RETRANSMISSIONS of the peer's PREVIOUS flight, which mean the exact
719
+ // opposite: our reply was lost. Two concrete deadlocks that caused:
720
+ // 1. Our cookie-bearing CH2 is lost → server retransmits its HVR
721
+ // (seq 0, discarded as a duplicate) → cancel killed the CH2
722
+ // timer → CH2 never resent → handshake dead.
723
+ // 2. Our final flight (CKE/CCS/Finished) is lost → server
724
+ // retransmits its flight (all seqs below nextReadMsgSeq,
725
+ // discarded) → cancel killed the flight timer → dead.
726
+ // NOTE: the implicit-ACK cancel deliberately does NOT live here.
727
+ // Everything in this function runs AFTER deliverHandshakeMessage,
728
+ // and TLS produces our response flight synchronously inside the
729
+ // feed — so a cancel at this point lands on the freshly-armed timer
730
+ // of the flight we JUST sent, not on the acknowledged one. The
731
+ // cancel happens at the acceptance points inside
732
+ // deliverHandshakeMessage instead, before the feed.
733
+ if (anyNew) {
734
+ // handled at acceptance time
735
+ } else if (sawMessage && ctx.currentFlight.length > 0 &&
736
+ ctx.state !== 'connected' &&
737
+ ctx.selectedVersion !== DTLS_VERSION.DTLS1_3 &&
738
+ // Damping: a peer retransmits its flight as SEVERAL
739
+ // datagrams; without a guard, EACH duplicate datagram
740
+ // elicited a full resend of our flight — a ×N
741
+ // amplification burst that itself increased loss
742
+ // pressure. One resend per 250ms answers the peer's
743
+ // whole retransmitted flight exactly once.
744
+ (Date.now() - (ctx._lastDupElicitedResend || 0)) >= 250) {
745
+ ctx._lastDupElicitedResend = Date.now();
746
+ // Pure duplicate of a previous peer flight while we still have an
747
+ // outstanding flight of our own: the peer is telling us it never
748
+ // got our reply. Resend it immediately (RFC 6347 §4.2.4 responder
749
+ // behavior) rather than waiting out our own backoff. The regular
750
+ // timer keeps running unchanged, so the retransmit budget still
751
+ // bounds the total effort; the resend rate is naturally bounded by
752
+ // the peer's own retransmission schedule. DTLS 1.3 is excluded —
753
+ // it uses explicit ACKs (RFC 9147 §7) instead of flight inference.
754
+ for (let f = 0; f < ctx.currentFlight.length; f++) {
755
+ ev.emit('packet', ctx.currentFlight[f]);
756
+ }
757
+ }
569
758
  }
570
759
 
571
760
  /**
572
- * Reassemble a fragmented handshake message.
761
+ * Reassemble a (possibly fragmented) handshake message.
762
+ *
763
+ * RFC 6347 §4.2.3 / RFC 9147 §5.5: fragments may overlap and arrive out of
764
+ * order, because a retransmission can re-fragment the same message with
765
+ * different boundaries (e.g. after a PMTU change). We therefore copy each
766
+ * fragment's bytes into a full-length buffer and track coverage as a set of
767
+ * merged byte-ranges, rather than requiring each fragment to start exactly
768
+ * where the previous one ended.
573
769
  */
574
770
  function reassembleFragment(parsed) {
575
771
  let key = parsed.msg_seq;
576
- if (!(key in ctx.fragments)) {
577
- ctx.fragments[key] = { totalLength: parsed.length, type: parsed.type, parts: {} };
772
+
773
+ // Ignore fragments for messages we've already delivered.
774
+ if (key < ctx.nextReadMsgSeq) return false;
775
+
776
+ // DoS guard: the 24-bit `length` is attacker-controlled on the first
777
+ // fragment. Reject anything claiming more than our cap BEFORE allocating,
778
+ // so a peer can't force huge buffers with a single unauthenticated packet.
779
+ if (parsed.length > ctx.maxHandshakeMessageSize) {
780
+ ev.emit('error', new Error('DTLS handshake message exceeds maxHandshakeMessageSize (' +
781
+ parsed.length + ' > ' + ctx.maxHandshakeMessageSize + ')'));
782
+ return false;
578
783
  }
784
+
579
785
  let frag = ctx.fragments[key];
580
- frag.parts[parsed.frag_offset] = parsed.body;
786
+ if (!frag) {
787
+ frag = ctx.fragments[key] = {
788
+ totalLength: parsed.length,
789
+ type: parsed.type,
790
+ buf: new Uint8Array(parsed.length),
791
+ ranges: [], // sorted, merged [start, end) covered ranges
792
+ };
793
+ }
794
+
795
+ let start = parsed.frag_offset;
796
+ let end = parsed.frag_offset + parsed.frag_length;
581
797
 
582
- // Check if complete
583
- let assembled = new Uint8Array(frag.totalLength);
584
- let filled = 0;
585
- let offsets = Object.keys(frag.parts).map(Number).sort((a, b) => a - b);
798
+ // Bounds guard against a malformed fragment claiming to exceed totalLength.
799
+ if (end > frag.totalLength) return false;
800
+
801
+ // Coverage before this fragment — used to decide whether it contributed
802
+ // anything new (a retransmitted fragment that only re-covers known bytes
803
+ // is a duplicate for implicit-ACK purposes).
804
+ let coveredBefore = 0;
805
+ for (let cr = 0; cr < frag.ranges.length; cr++) {
806
+ coveredBefore += frag.ranges[cr][1] - frag.ranges[cr][0];
807
+ }
586
808
 
587
- for (let i = 0; i < offsets.length; i++) {
588
- let part = frag.parts[offsets[i]];
589
- if (offsets[i] !== filled) return; // gap — not complete yet
590
- assembled.set(part, offsets[i]);
591
- filled = offsets[i] + part.length;
809
+ // Copy the fragment bytes in (overlaps simply overwrite with identical data).
810
+ frag.buf.set(parsed.body.subarray(0, parsed.frag_length), start);
811
+
812
+ // Merge [start, end) into the covered-range set.
813
+ frag.ranges.push([start, end]);
814
+ frag.ranges.sort((a, b) => a[0] - b[0]);
815
+ let merged = [];
816
+ for (let r of frag.ranges) {
817
+ if (merged.length && r[0] <= merged[merged.length - 1][1]) {
818
+ // Overlapping/adjacent → extend the previous range.
819
+ if (r[1] > merged[merged.length - 1][1]) merged[merged.length - 1][1] = r[1];
820
+ } else {
821
+ merged.push([r[0], r[1]]);
822
+ }
592
823
  }
824
+ frag.ranges = merged;
593
825
 
594
- if (filled < frag.totalLength) return; // not complete
826
+ let coveredAfter = 0;
827
+ for (let ca = 0; ca < merged.length; ca++) {
828
+ coveredAfter += merged[ca][1] - merged[ca][0];
829
+ }
595
830
 
596
- // Complete — deliver
597
- delete ctx.fragments[key];
598
- deliverHandshakeMessage(frag.type, assembled, key);
831
+ // Complete when a single range covers [0, totalLength).
832
+ if (merged.length === 1 && merged[0][0] === 0 && merged[0][1] === frag.totalLength) {
833
+ delete ctx.fragments[key];
834
+ return deliverHandshakeMessage(frag.type, frag.buf, key);
835
+ }
836
+ return coveredAfter > coveredBefore;
599
837
  }
600
838
 
601
839
  /**
602
840
  * Deliver a complete handshake message to TLSSession.
603
841
  * Strips DTLS reconstruction → builds TLS format.
842
+ *
843
+ * @returns {boolean} true if the message advanced our handshake state
844
+ * (delivered in-order, buffered out-of-order, or consumed as a valid
845
+ * HVR / cookie-exchange step); false if it was a duplicate or was
846
+ * dropped. processHandshakeRecord uses this for the implicit-ACK
847
+ * decision — only genuinely new data may cancel our retransmit timer.
604
848
  */
605
849
  function deliverHandshakeMessage(type, body, msgSeq) {
606
- // Skip if we've already processed this msg_seq
607
- if (msgSeq < ctx.nextReadMsgSeq) return;
608
- ctx.nextReadMsgSeq = msgSeq + 1;
609
-
850
+ // Skip if we've already processed this msg_seq (duplicate / retransmit).
851
+ if (msgSeq < ctx.nextReadMsgSeq) return false;
610
852
 
611
853
  // Check for HelloVerifyRequest (DTLS 1.2 server→client, type=3)
612
854
  if (type === 3 && !isServer) {
855
+ ctx.nextReadMsgSeq = msgSeq + 1;
856
+ // The HVR acknowledges CH1 (implicit ACK, RFC 6347 §4.2.4). No
857
+ // explicit cancel is needed: triggerClientHelloWithCookie arms the
858
+ // CH2 flight's own timer, and startRetransmitTimer clears any
859
+ // previous timer as its first step.
613
860
  let hvr = parse_hello_verify_request(body);
614
861
  triggerClientHelloWithCookie(hvr.cookie);
615
- return;
862
+ return true;
863
+ }
864
+
865
+ // ---- Server-side HelloVerifyRequest cookie exchange (RFC 6347 §4.2.1) ----
866
+ // For DTLS 1.2 with cookies enabled, the first ClientHello carries no cookie
867
+ // (or an empty one). We answer with a HelloVerifyRequest and do NOT process
868
+ // the ClientHello — that proves return-routability (the client must be at the
869
+ // address it claims before we allocate any handshake state). Only a second
870
+ // ClientHello echoing the correct cookie is processed.
871
+ //
872
+ // Note: msg_seq is NOT advanced for a rejected/HVR-triggering ClientHello, so
873
+ // the client's retried CH (which reuses msg_seq per RFC 6347) is still accepted.
874
+ if (type === 1 && isServer && options.useCookies === true &&
875
+ ctx.selectedVersion !== DTLS_VERSION.DTLS1_3) {
876
+
877
+ let ch = null;
878
+ try { ch = parse_hello({ kind: 'client', body: body }); } catch (e) { ch = null; }
879
+ let incomingCookie = (ch && ch.dtls_cookie) ? ch.dtls_cookie : new Uint8Array(0);
880
+
881
+ if (incomingCookie.length === 0) {
882
+ // First ClientHello → send HVR with a fresh cookie. Don't process the CH,
883
+ // don't advance msg_seq (the retry reuses the same msg_seq).
884
+ // Counts as progress: a retransmitted cookieless CH means the client
885
+ // never got our HVR — resending it here IS the correct response.
886
+ sendHelloVerifyRequest();
887
+ return true;
888
+ }
889
+
890
+ // Second ClientHello: the cookie MUST match what we issued.
891
+ if (!ctx.hvrCookie || !timingSafeEqualU8(incomingCookie, ctx.hvrCookie)) {
892
+ // Cookie mismatch → silently drop (RFC 6347: server discards). Don't
893
+ // advance msg_seq; a correct retry can still arrive.
894
+ return false;
895
+ }
896
+ // Cookie verified → accept this ClientHello as the handshake anchor.
897
+ //
898
+ // Strict RFC 6347 §4.2.2 (see the figure in the RFC): the cookie-
899
+ // bearing CH2 arrives with message_seq 1, and our ServerHello must
900
+ // be numbered 1 as well. The previous code assumed the retried CH
901
+ // "reuses msg_seq" 0 — that is only true for a pure retransmission
902
+ // of CH1 (HVR lost), not for the cookie-bearing CH2, so RFC-
903
+ // compliant clients (pion, OpenSSL, fixed lemon-tls) had their CH2
904
+ // buffered as "out of order" forever and the handshake deadlocked.
905
+ //
906
+ // Set the read cursor and the outgoing counter to 1; the in-order
907
+ // check below then accepts exactly a seq-1 CH2 and rejects anything
908
+ // else (including seq-0 CH2s from pre-fix lemon-tls clients — non-
909
+ // compliant numbering is intentionally not accommodated). The
910
+ // transcript stays consistent on both sides because
911
+ // feedHandshakeToTls records the incoming wire seq and outgoing
912
+ // messages record message_sent_seq at emission — which is exactly
913
+ // what the Finished MAC hashes (RFC 6347 §4.2.6).
914
+ ctx.nextReadMsgSeq = 1;
915
+ tls.context.message_sent_seq = 1;
916
+ // Fall through and process the ClientHello normally.
917
+ }
918
+
919
+ // ---- In-order delivery (RFC 6347 §4.2.4 / RFC 9147 §5.2) ----
920
+ // DTLS handshake messages must be handed to the TLS state machine strictly
921
+ // in msg_seq order. If this message is ahead of what we expect, buffer it
922
+ // and wait for the gap to fill. If it's the next expected one, feed it, then
923
+ // drain any consecutive messages that were buffered earlier.
924
+ if (msgSeq > ctx.nextReadMsgSeq) {
925
+ // Out of order — hold it (ignore a duplicate already buffered).
926
+ if (!(msgSeq in ctx.pendingMessages)) {
927
+ ctx.pendingMessages[msgSeq] = { type: type, body: body };
928
+ return true; // new information, even if not yet deliverable
929
+ }
930
+ return false; // duplicate of an already-buffered message
931
+ }
932
+
933
+ // msgSeq === nextReadMsgSeq → accept. Timer discipline, per the
934
+ // RFC 6347 §4.2.4 state machine, in two halves:
935
+ //
936
+ // 1. A message from the peer's next flight is the implicit ACK of
937
+ // ours — but it must RE-ARM the timer, not cancel it. While we
938
+ // are in WAITING (peer's flight only partially received — e.g.
939
+ // one datagram of the server's SH..SHD flight lost), timer
940
+ // expiry retransmits OUR last flight, which elicits a full
941
+ // retransmission of the peer's flight (a cookie-valid duplicate
942
+ // CH2 makes werift resend flight 4, a duplicate Finished makes
943
+ // it resend flight 6). An earlier revision cancelled here: one
944
+ // lost datagram inside the peer's flight then left BOTH sides
945
+ // timer-less and deadlocked — ~50% handshake failure at just 5%
946
+ // random loss.
947
+ //
948
+ // 2. The re-arm must happen BEFORE feeding TLS: the feed can
949
+ // synchronously produce our entire next flight, whose fresh
950
+ // timer must not be clobbered afterwards (the original code
951
+ // cancelled at the end of processHandshakeRecord — after the
952
+ // response — so no responder flight was EVER protected by
953
+ // retransmission).
954
+ //
955
+ // When the feed does respond, the flight boundary (newFlightPending
956
+ // → startNewFlight) resets the retransmit budget and the outgoing
957
+ // path arms the new flight's timer, superseding this re-arm.
958
+ startRetransmitTimer();
959
+ ctx.newFlightPending = true;
960
+ feedHandshakeToTls(type, body, msgSeq);
961
+ ctx.nextReadMsgSeq = msgSeq + 1;
962
+
963
+ // Drain consecutively-numbered buffered messages. Same pre-feed
964
+ // ordering applies; for a timer armed by an earlier feed in this very
965
+ // drain, cancelling here would be wrong — but a mid-flight response
966
+ // does not occur in TLS flows (a flight is answered only after its
967
+ // final message), so each pre-feed cancel can only ever hit an
968
+ // already-acknowledged flight's timer (usually already null).
969
+ while (ctx.nextReadMsgSeq in ctx.pendingMessages) {
970
+ let next = ctx.pendingMessages[ctx.nextReadMsgSeq];
971
+ delete ctx.pendingMessages[ctx.nextReadMsgSeq];
972
+ startRetransmitTimer();
973
+ ctx.newFlightPending = true;
974
+ feedHandshakeToTls(next.type, next.body, ctx.nextReadMsgSeq);
975
+ ctx.nextReadMsgSeq++;
616
976
  }
977
+ return true;
978
+ }
617
979
 
980
+ /**
981
+ * Feed one in-order handshake message to the TLSSession, converting from DTLS
982
+ * to TLS wire format and driving version detection.
983
+ */
984
+ function feedHandshakeToTls(type, body, msgSeq) {
618
985
  // Build TLS-format message: type(1) + length(3) + body
619
986
  let tlsMsg = build_message(type, body);
620
987
 
@@ -633,6 +1000,23 @@ function DTLSSession(options) {
633
1000
  }
634
1001
  }
635
1002
 
1003
+ /**
1004
+ * Server: build and send a HelloVerifyRequest with a fresh cookie (DTLS 1.2).
1005
+ * The cookie is a CSPRNG value stored on the context; a matching cookie must
1006
+ * come back in the client's second ClientHello (verified in
1007
+ * deliverHandshakeMessage) before we process the handshake.
1008
+ */
1009
+ function sendHelloVerifyRequest() {
1010
+ let cookie = new Uint8Array(crypto.randomBytes(32));
1011
+ ctx.hvrCookie = cookie;
1012
+ ctx.hvrDone = true;
1013
+
1014
+ // HVR always uses msg_seq 0 and is sent in the clear (epoch 0).
1015
+ let hvrMsg = build_dtls_handshake(build_message(3, build_hello_verify_request({ cookie: cookie })), 0);
1016
+ let record = buildDtlsPlaintext(CT.HANDSHAKE, 0, nextWriteSeq(0), hvrMsg);
1017
+ ev.emit('packet', record);
1018
+ }
1019
+
636
1020
  /**
637
1021
  * Trigger a new ClientHello with cookie (DTLS 1.2 HVR response).
638
1022
  */
@@ -643,13 +1027,44 @@ function DTLSSession(options) {
643
1027
  // RFC 6347: "the client MUST use the same parameter values"
644
1028
  let ch2 = insertCookieIntoClientHello(ctx.savedClientHello, cookie);
645
1029
 
646
- // Reset state
1030
+ // Reset state for the post-HVR handshake restart.
1031
+ //
1032
+ // RFC 6347 §4.2.1: the handshake transcript restarts at CH2 — the
1033
+ // cookieless CH1 and the HelloVerifyRequest are NOT included in the
1034
+ // handshake hash. Resetting the transcript is therefore correct.
1035
+ //
1036
+ // RFC 6347 §4.2.2, however, is explicit (see the figure in the RFC)
1037
+ // that message_seq does NOT restart:
1038
+ //
1039
+ // ClientHello (seq=1) ------> ← CH2 carries seq 1
1040
+ // (with cookie)
1041
+ // <------ ServerHello (seq=1)
1042
+ //
1043
+ // The previous code reset BOTH directions of the counter to 0: it
1044
+ // sent CH2 with msg_seq=0 and reset nextReadMsgSeq to 0. Against a
1045
+ // spec-compliant server (pion, werift, OpenSSL — all send an HVR and
1046
+ // then number ServerHello=1), the entire server flight (seq 1..N)
1047
+ // landed in pendingMessages waiting forever for a seq-0 message that
1048
+ // never comes: the handshake stalled silently — no error, no alert.
1049
+ // It only ever worked lemon-tls↔lemon-tls because our own server
1050
+ // sends an HVR only when options.useCookies is set, so the broken
1051
+ // path was never exercised same-stack.
1052
+ //
1053
+ // nextReadMsgSeq is deliberately NOT touched here: the HVR branch in
1054
+ // deliverHandshakeMessage already advanced it to 1, which is exactly
1055
+ // where the server's ServerHello will arrive.
647
1056
  tls.context.transcript = [];
648
1057
  tls.context.hello_sent = true;
649
1058
  tls.context.dtls_cookie = cookie;
650
- tls.context.message_sent_seq = 0;
651
- ctx.nextReadMsgSeq = 0;
1059
+ // CH2 is message_seq 1. Set message_sent_seq BEFORE the transcript
1060
+ // push so transcriptHook / transcriptMsgSeqs record seq 1 for CH2 —
1061
+ // the Finished MAC hashes the DTLS reconstruction data including
1062
+ // message_seq (RFC 6347 §4.2.6), so a transcript recording seq 0 for
1063
+ // a wire message sent with seq 1 would fail Finished verification.
1064
+ tls.context.message_sent_seq = 1;
652
1065
  ctx.currentFlight = [];
1066
+ ctx.fragments = {}; // discard any partial reassembly from CH1 flight
1067
+ ctx.pendingMessages = {}; // discard any out-of-order buffer from CH1 flight
653
1068
  transcriptMsgSeqs = [];
654
1069
 
655
1070
  // Push CH2 to transcript (transcriptHook will store TLS format for now,
@@ -659,13 +1074,13 @@ function DTLSSession(options) {
659
1074
  tls.context.transcriptHook ? tls.context.transcriptHook(ch2) : ch2
660
1075
  );
661
1076
 
662
- // Build DTLS message and send
663
- let dtlsMsg = build_dtls_handshake(ch2, 0);
1077
+ // Build DTLS message and send — msg_seq 1 per the RFC figure above.
1078
+ let dtlsMsg = build_dtls_handshake(ch2, 1);
664
1079
  let record = buildRecord(0, CT.HANDSHAKE, dtlsMsg);
665
1080
  ctx.currentFlight = [record];
666
1081
  ev.emit('packet', record);
667
1082
 
668
- tls.context.message_sent_seq = 1;
1083
+ tls.context.message_sent_seq = 2;
669
1084
  startRetransmitTimer();
670
1085
  }
671
1086
 
@@ -734,7 +1149,19 @@ function DTLSSession(options) {
734
1149
  // Handshake completion
735
1150
  // ============================================================
736
1151
 
1152
+ // Forward 'clienthello' so server-side callers can inspect the peer's
1153
+ // offered extensions (tls.getRemoteExtension / message.extensions) and
1154
+ // set answering extensions via set_context({ local_extensions }) BEFORE
1155
+ // the ServerHello is built — the emit is synchronous within ClientHello
1156
+ // processing, ahead of hello construction. This is how DTLS-SRTP's
1157
+ // use_srtp answer (RFC 5764 §4.1) gets negotiated generically.
1158
+ tls.on('clienthello', function(data, message) {
1159
+ ev.emit('clienthello', data, message);
1160
+ });
1161
+
737
1162
  tls.on('secureConnect', function() {
1163
+ _ldbg('handshake complete — secureConnect (version=0x' +
1164
+ ((ctx.selectedVersion || tls.context.selected_version || 0).toString(16)) + ')');
738
1165
  ctx.state = 'connected';
739
1166
  cancelRetransmit();
740
1167
  startNewFlight();
@@ -786,23 +1213,12 @@ function DTLSSession(options) {
786
1213
  // ============================================================
787
1214
 
788
1215
  tls.on('hello', function() {
789
- // Version is detected lazily in handshakeSecrets and message handlers
790
-
791
- // DTLS 1.2 server: optionally send HelloVerifyRequest
792
- if (isServer && ctx.selectedVersion !== DTLS_VERSION.DTLS1_3 && !ctx.hvrDone && options.useCookies === true) {
793
- ctx.hvrDone = true;
794
- let cookie = new Uint8Array(32);
795
- for (let i = 0; i < 32; i++) cookie[i] = Math.floor(Math.random() * 256);
796
- ctx.hvrCookie = cookie;
797
-
798
- let hvrBody = build_hello_verify_request({ cookie: cookie });
799
- let hvrMsg = build_dtls_handshake(
800
- build_message(3, hvrBody),
801
- 0
802
- );
803
- let record = buildDtlsPlaintext(CT.HANDSHAKE, 0, nextWriteSeq(0), hvrMsg);
804
- ev.emit('packet', record);
805
- }
1216
+ // Version is detected lazily in handshakeSecrets and message handlers.
1217
+ //
1218
+ // Note: DTLS 1.2 HelloVerifyRequest is handled earlier, in
1219
+ // deliverHandshakeMessage — the first (cookieless) ClientHello never reaches
1220
+ // TLSSession, so by the time 'hello' fires here the cookie has already been
1221
+ // verified. Nothing to do for HVR at this point.
806
1222
  });
807
1223
 
808
1224
 
@@ -830,6 +1246,21 @@ function DTLSSession(options) {
830
1246
  /** Access to internal TLSSession (for advanced use). */
831
1247
  get tls() { return tls; },
832
1248
 
1249
+ /**
1250
+ * Export keying material (RFC 5705 / RFC 8446 §7.5).
1251
+ * DTLS-SRTP: exportKeyingMaterial(len, 'EXTRACTOR-dtls_srtp') — no context.
1252
+ * Returns Buffer, or null before secrets are available.
1253
+ */
1254
+ exportKeyingMaterial: function(length, label, context_value) {
1255
+ return tls.exportKeyingMaterial(length, label, context_value);
1256
+ },
1257
+
1258
+ /** Peer's hello extensions: [{ type, name, data, value }]. */
1259
+ getRemoteExtensions: function() { return tls.getRemoteExtensions(); },
1260
+
1261
+ /** One peer extension by numeric type (e.g. 14 = use_srtp), or null. */
1262
+ getRemoteExtension: function(type) { return tls.getRemoteExtension(type); },
1263
+
833
1264
  /** Current DTLS state. */
834
1265
  get state() { return ctx.state; },
835
1266
 
@@ -862,4 +1293,4 @@ function DTLSSession(options) {
862
1293
  return this;
863
1294
  }
864
1295
 
865
- export default DTLSSession;
1296
+ export default DTLSSession;