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.
- package/README.md +104 -2
- package/index.js +10 -0
- package/package.json +5 -3
- package/src/compat.js +30 -17
- package/src/crypto.js +92 -2
- package/src/dtls_session.js +487 -56
- package/src/dtls_socket.js +22 -0
- package/src/record.js +133 -5
- package/src/session/ecdh.js +16 -1
- package/src/tls_session.js +224 -32
- package/src/tls_socket.js +44 -1
- package/src/utils.js +28 -1
package/src/dtls_session.js
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
429
|
-
|
|
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(
|
|
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
|
-
//
|
|
568
|
-
|
|
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
|
-
|
|
577
|
-
|
|
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
|
|
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
|
-
//
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
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
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
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
|
-
|
|
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
|
|
597
|
-
|
|
598
|
-
|
|
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
|
-
|
|
651
|
-
|
|
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,
|
|
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 =
|
|
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
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
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;
|