lemon-tls 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +168 -41
- package/index.cjs +9 -7
- package/index.d.ts +626 -30
- package/package.json +45 -7
- package/src/ca_store.js +31 -0
- package/src/compat.js +76 -116
- package/src/crypto.js +21 -114
- package/src/dtls_session.js +1284 -390
- package/src/dtls_socket.js +318 -96
- package/src/node_options.js +154 -17
- package/src/record.js +355 -191
- package/src/secure_context.js +97 -39
- package/src/session/ecdh.js +140 -122
- package/src/session/ech.js +92 -32
- package/src/session/hpke.js +12 -44
- package/src/session/message.js +29 -9
- package/src/session/signing.js +80 -22
- package/src/session/ticket.js +5 -3
- package/src/session/x509.js +385 -0
- package/src/tls_session.js +2221 -902
- package/src/tls_socket.js +451 -452
- package/src/wire.js +271 -158
- package/types/crypto.d.ts +40 -0
- package/types/record.d.ts +40 -0
- package/types/session.d.ts +3 -0
- package/types/wire.d.ts +62 -0
package/src/dtls_session.js
CHANGED
|
@@ -35,16 +35,24 @@ import createSecureContext from './secure_context.js';
|
|
|
35
35
|
import {
|
|
36
36
|
DTLS_VERSION,
|
|
37
37
|
build_dtls_handshake,
|
|
38
|
+
build_dtls_fragment,
|
|
38
39
|
parse_dtls_handshake,
|
|
39
40
|
build_message,
|
|
40
41
|
build_hello_verify_request,
|
|
41
42
|
parse_hello_verify_request,
|
|
42
43
|
parse_hello,
|
|
44
|
+
peer_alert_error,
|
|
45
|
+
local_alert_error,
|
|
46
|
+
read_peer_alert,
|
|
47
|
+
MAX_WARNING_ALERTS,
|
|
48
|
+
MAX_RECORD_PLAINTEXT,
|
|
49
|
+
TLS_ALERT,
|
|
50
|
+
TLS_MESSAGE_TYPE,
|
|
51
|
+
parseError,
|
|
43
52
|
} from './wire.js';
|
|
44
53
|
|
|
45
54
|
import {
|
|
46
55
|
TLS_CIPHER_SUITES,
|
|
47
|
-
default_cipher_suites,
|
|
48
56
|
hkdf_expand_label,
|
|
49
57
|
derive_sn_key,
|
|
50
58
|
LABEL_PREFIX_TLS13,
|
|
@@ -56,7 +64,8 @@ import {
|
|
|
56
64
|
getAeadAlgo,
|
|
57
65
|
deriveKeys,
|
|
58
66
|
deriveKeys12,
|
|
59
|
-
|
|
67
|
+
openDtls12,
|
|
68
|
+
acceptRecordSeq,
|
|
60
69
|
buildDtlsPlaintext,
|
|
61
70
|
buildEncryptedDtls12,
|
|
62
71
|
buildEncryptedDtls13,
|
|
@@ -66,9 +75,8 @@ import {
|
|
|
66
75
|
decryptEncryptedDtls13,
|
|
67
76
|
} from './record.js';
|
|
68
77
|
|
|
69
|
-
import { timingSafeEqualU8 } from './utils.js';
|
|
70
|
-
import {
|
|
71
|
-
import { default_signature_schemes } from './session/signing.js';
|
|
78
|
+
import { timingSafeEqualU8, concatUint8Arrays } from './utils.js';
|
|
79
|
+
import { versionsInRange, localLists } from './node_options.js';
|
|
72
80
|
|
|
73
81
|
|
|
74
82
|
// ============================================================
|
|
@@ -121,6 +129,37 @@ function insertCookieIntoClientHello(ch1, cookie) {
|
|
|
121
129
|
// record, ordering decision, and completion visible without a packet dump.
|
|
122
130
|
var LEMON_DEBUG = typeof process !== 'undefined' && process.env &&
|
|
123
131
|
(process.env.LEMON_DEBUG === '1' || process.env.WEBRTC_DEBUG === '1');
|
|
132
|
+
// The two settable link parameters, checked where they are set (option or
|
|
133
|
+
// setter): a wrong value is a programming error, thrown at once.
|
|
134
|
+
// The connectionId option (RFC 9146, RFC 9147 §9): the CID this side asks
|
|
135
|
+
// for. true — a server, SERVER_CID_LEN random bytes (createDTLSServer finds
|
|
136
|
+
// its connections by them); a client, an empty one (it needs none: the server
|
|
137
|
+
// does not move) —, or the CID itself. TLSSession negotiates whatever it is.
|
|
138
|
+
const SERVER_CID_LEN = 8;
|
|
139
|
+
function connectionIdOption(v, isServer) {
|
|
140
|
+
if (v === true) return isServer ? new Uint8Array(crypto.randomBytes(SERVER_CID_LEN)) : new Uint8Array(0);
|
|
141
|
+
return v;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function checkedMtu(v, dflt) {
|
|
145
|
+
if (v === undefined || v === null) {
|
|
146
|
+
if (dflt !== undefined) return dflt;
|
|
147
|
+
throw new TypeError('DTLS mtu: a number of bytes is required');
|
|
148
|
+
}
|
|
149
|
+
if (!Number.isInteger(v) || v < 128 || v > 65507) throw new RangeError('DTLS mtu must be an integer in [128, 65507], got ' + v);
|
|
150
|
+
return v;
|
|
151
|
+
}
|
|
152
|
+
// The least a timer from a round-trip estimate starts at (currentRto).
|
|
153
|
+
const RTO_MIN_MS = 100;
|
|
154
|
+
function checkedRetransmitTimeout(v, dflt) {
|
|
155
|
+
if (v === undefined || v === null) {
|
|
156
|
+
if (dflt !== undefined) return dflt;
|
|
157
|
+
throw new TypeError('DTLS initialRetransmitTimeout: a number of milliseconds is required');
|
|
158
|
+
}
|
|
159
|
+
if (!Number.isInteger(v) || v < 10 || v > 60000) throw new RangeError('DTLS initialRetransmitTimeout must be an integer in [10, 60000] ms, got ' + v);
|
|
160
|
+
return v;
|
|
161
|
+
}
|
|
162
|
+
|
|
124
163
|
function _ldbg() {
|
|
125
164
|
if (!LEMON_DEBUG) return;
|
|
126
165
|
if (typeof console !== 'undefined' && console.log) {
|
|
@@ -178,7 +217,12 @@ function DTLSSession(options) {
|
|
|
178
217
|
let isServer = !!options.isServer;
|
|
179
218
|
|
|
180
219
|
// ---- Internal TLSSession ----
|
|
181
|
-
|
|
220
|
+
// Every option goes on to the TLS session as the caller gave it — the
|
|
221
|
+
// TLS options (customExtensions, ALPNCallback, ticketData, signature and
|
|
222
|
+
// group choices, ...) mean the same over DTLS — and DTLS sets only these.
|
|
223
|
+
// A hand-picked list here left out whatever it did not name, as
|
|
224
|
+
// createDTLSServer's did (r96, A6).
|
|
225
|
+
let tls = new TLSSession(Object.assign({}, options, {
|
|
182
226
|
isServer: isServer,
|
|
183
227
|
// RFC 9147 §5.3: the DTLS ClientHello's legacy_session_id "MUST be
|
|
184
228
|
// set to a zero-length vector" — the 32-byte compatibility sid is a
|
|
@@ -190,37 +234,57 @@ function DTLSSession(options) {
|
|
|
190
234
|
// byte and never the body, so any non-empty sid shears its whole
|
|
191
235
|
// ClientHello parse.)
|
|
192
236
|
sessionId: new Uint8Array(0),
|
|
193
|
-
srtpProfiles: options.srtpProfiles,
|
|
194
|
-
servername: options.servername,
|
|
195
|
-
SNICallback: options.SNICallback,
|
|
196
|
-
rejectUnauthorized: options.rejectUnauthorized,
|
|
197
|
-
checkServerIdentity: options.checkServerIdentity,
|
|
198
237
|
// DTLS peers in WebRTC present self-signed certificates by design and are
|
|
199
238
|
// authenticated by the SDP fingerprint, not by a CA chain. Falling back to
|
|
200
239
|
// the platform trust store here would mark every such peer unauthorized.
|
|
201
240
|
useDefaultCA: options.useDefaultCA === true,
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
241
|
+
// Resumption, as over TCP (TLSSocket): a 'session' Buffer from an
|
|
242
|
+
// earlier connection — a DTLS 1.3 PSK ticket, or a DTLS 1.2 RFC 5077
|
|
243
|
+
// ticket — and whether this side issues / asks for tickets (default
|
|
244
|
+
// true; noTickets: true is the older spelling of sessionTickets: false).
|
|
245
|
+
session: options.session || null,
|
|
246
|
+
sessionTickets: options.sessionTickets !== undefined ? options.sessionTickets !== false
|
|
247
|
+
: options.noTickets !== true,
|
|
248
|
+
// No 0-RTT over DTLS here: early data has no DTLS record path.
|
|
249
|
+
earlyData: false,
|
|
250
|
+
connectionId: connectionIdOption(options.connectionId, isServer),
|
|
251
|
+
}));
|
|
209
252
|
|
|
210
253
|
// ---- DTLS context ----
|
|
211
254
|
let ctx = {
|
|
212
255
|
state: 'idle', // idle → handshaking → connected → closed
|
|
256
|
+
// Connection IDs (RFC 9146, RFC 9147 §9): { local, peer } once
|
|
257
|
+
// negotiated ('connectionId'), the peer's as NewConnectionId moves it;
|
|
258
|
+
// and the newest record number authenticated (noteNewest), the one
|
|
259
|
+
// a peer's new address may be taken from.
|
|
260
|
+
cid: null,
|
|
261
|
+
rxNewest: null,
|
|
262
|
+
consecutiveWarnings: 0, // warning alerts with nothing else between them
|
|
263
|
+
// DTLS 1.3 ACK state (RFC 9147 §7.1), for the peer's flight in progress:
|
|
264
|
+
// the handshake records of it received so far, a timer for the ACK when
|
|
265
|
+
// the rest does not follow, and whether this datagram showed a gap.
|
|
266
|
+
lastFlight12: null, // DTLS 1.2: our last flight, kept for the peer's retransmissions
|
|
267
|
+
dgAnyNew: false, // this datagram: a handshake message not taken before
|
|
268
|
+
dgDuplicate: false, // this datagram: a handshake message already taken
|
|
269
|
+
dgNewest: false, // this datagram: a protected record newer than any before
|
|
270
|
+
pendingAcks: [],
|
|
271
|
+
ackTimer: null,
|
|
272
|
+
postAcks: [], // post-handshake records received, to ACK together
|
|
273
|
+
postAckTimer: null,
|
|
274
|
+
ackDisruption: false,
|
|
275
|
+
flightMessagesQueued: 0, // flight messages queued, ever (did this datagram answer with one?)
|
|
213
276
|
isServer: isServer,
|
|
214
|
-
mtu: options.mtu
|
|
277
|
+
mtu: checkedMtu(options.mtu, 1200),
|
|
215
278
|
|
|
216
279
|
// Upper bound on a single reassembled handshake message. The DTLS handshake
|
|
217
280
|
// header carries a 24-bit length (up to 16MB), which is attacker-controlled
|
|
218
281
|
// on the very first fragment. Without a cap, a peer could send one fragment
|
|
219
282
|
// claiming length 0xFFFFFF for several msg_seqs and force large allocations
|
|
220
|
-
// before anything is authenticated.
|
|
221
|
-
//
|
|
222
|
-
//
|
|
223
|
-
|
|
283
|
+
// before anything is authenticated. TLS over TCP's limit
|
|
284
|
+
// (tls.handshakeMessageLimit(): 128 KB, or maxHandshakeSize) — it was a
|
|
285
|
+
// limit of DTLS's own, 256 KB. maxHandshakeMessageSize, its older
|
|
286
|
+
// option, is still taken (never past the same 128 KB).
|
|
287
|
+
maxHandshakeMessageSize: options.maxHandshakeMessageSize > 0 ? Math.min(options.maxHandshakeMessageSize, tls.handshakeMessageLimit()) : tls.handshakeMessageLimit(),
|
|
224
288
|
|
|
225
289
|
// Version (determined after negotiation)
|
|
226
290
|
selectedVersion: null, // DTLS version (0xFEFC or 0xFEFD)
|
|
@@ -290,16 +354,41 @@ function DTLSSession(options) {
|
|
|
290
354
|
// new write keys are installed immediately but stay dormant here until
|
|
291
355
|
// the ACK arrives.
|
|
292
356
|
pendingWriteEpoch: null,
|
|
293
|
-
|
|
357
|
+
postHandshake: [], // DTLS 1.3 post-handshake messages awaiting their ACK
|
|
358
|
+
deferredKeyUpdate: null, // DTLS 1.3: our KeyUpdate held back while one is unACKed (§8)
|
|
359
|
+
feeding: 0, // inside feedDatagram: post-handshake messages wait for its end
|
|
360
|
+
seenPlainSeqs: [], // the last epoch-0 record numbers received (duplicates dropped)
|
|
361
|
+
out: [], // what is to be sent, packed at the end of the pass (flushOut)
|
|
362
|
+
flightQueued: 0, // messages of the current flight queued (haveFlight)
|
|
363
|
+
outScheduled: false,
|
|
364
|
+
heldPostHandshake: [], // ...these (sendPostHandshake)
|
|
365
|
+
lastAckResend: 0, // when an ACK that confirmed nothing new last caused a resend
|
|
366
|
+
finalFlightPending: false, // DTLS 1.3 client: final flight not yet confirmed (see secureConnect)
|
|
294
367
|
|
|
295
368
|
retransmitTimer: null,
|
|
296
369
|
retransmitCount: 0,
|
|
297
|
-
retransmitTimeout: 1000,
|
|
370
|
+
retransmitTimeout: checkedRetransmitTimeout(options.initialRetransmitTimeout, 1000),
|
|
371
|
+
initialRetransmitTimeout: checkedRetransmitTimeout(options.initialRetransmitTimeout, 1000), // where each flight's timer starts (RFC 9147 §5.8.2: 1 s)
|
|
372
|
+
// Given, the schedule is the caller's: no round-trip estimate moves it.
|
|
373
|
+
fixedRto: options.initialRetransmitTimeout !== undefined && options.initialRetransmitTimeout !== null,
|
|
374
|
+
lastSession: undefined, // the last 'session' Buffer (getSession)
|
|
375
|
+
// Giving up (RFC 9147 §5.8.2 leaves it to us): after at least this many
|
|
376
|
+
// retransmissions AND this much time since the flight first went out.
|
|
377
|
+
// A count alone was 6 tries: about a minute from a 1 s timer, but 6 s from
|
|
378
|
+
// a 100 ms one (as WebRTC stacks start), too soon on a lossy path.
|
|
298
379
|
maxRetransmits: 6,
|
|
380
|
+
giveUpAfter: 60000,
|
|
381
|
+
// Round-trip estimate (RFC 6298's, as RFC 9147 §5.8.2 invites: "the
|
|
382
|
+
// retransmit timer [...] SHOULD be adjusted from RTT samples"): from a
|
|
383
|
+
// flight sent once to the first new message of the answer (Karn: never
|
|
384
|
+
// from one retransmitted). Later flights' timers start from it.
|
|
385
|
+
srtt: null, rttvar: null,
|
|
386
|
+
flightBegan: null, // when the current flight first went out (giving up)
|
|
387
|
+
rttStart: null, // ...the same, until its answer is sampled (RTT)
|
|
388
|
+
flightResent: false, // whether it has been resent
|
|
299
389
|
|
|
300
390
|
// DTLS 1.2: CCS tracking
|
|
301
391
|
localCcsSent: false,
|
|
302
|
-
remoteCcsSeen: false,
|
|
303
392
|
|
|
304
393
|
// HelloVerifyRequest (DTLS 1.2 server)
|
|
305
394
|
hvrCookie: null, // cookie sent in HVR
|
|
@@ -312,58 +401,18 @@ function DTLSSession(options) {
|
|
|
312
401
|
// ============================================================
|
|
313
402
|
|
|
314
403
|
// Set DTLS versions and cookie (triggers DTLS format in build_hello)
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
let
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
404
|
+
// node_options.js: the same reading as TLS over TCP. Inline compares took
|
|
405
|
+
// an unknown name silently.
|
|
406
|
+
let versions = versionsInRange(options.minVersion, options.maxVersion, true);
|
|
407
|
+
|
|
408
|
+
// The lists from the options (node_options.js localLists, as TLS over TCP
|
|
409
|
+
// builds them: the same names, cipherSuites and alpnProtocols as aliases);
|
|
410
|
+
// TLSSession fills what they leave out, by role (fill_default_lists). A
|
|
411
|
+
// DTLS server took no signatureAlgorithms nor prioritizeChaCha before, and
|
|
412
|
+
// a DTLS client offered every post-quantum group a server takes.
|
|
413
|
+
let tlsSetup = Object.assign(localLists(options, versions), {
|
|
324
414
|
dtls_cookie: new Uint8Array(0), // empty cookie — triggers DTLS format
|
|
325
|
-
};
|
|
326
|
-
|
|
327
|
-
// Pass through cipher/group/alpn preferences
|
|
328
|
-
if (options.cipherSuites) tlsSetup.local_supported_cipher_suites = options.cipherSuites;
|
|
329
|
-
|
|
330
|
-
// Client cipher defaults: TLSSession auto-populates both TLS 1.3+1.2 ciphers,
|
|
331
|
-
// but if we only support DTLS 1.2, we must not offer TLS 1.3 ciphers
|
|
332
|
-
// (the server might erroneously select one for a 1.2 connection).
|
|
333
|
-
if (!isServer && !options.cipherSuites && versions.indexOf(DTLS_VERSION.DTLS1_3) < 0) {
|
|
334
|
-
tlsSetup.local_supported_cipher_suites = [0xC02F, 0xC030, 0xC02B, 0xC02C, 0xCCA8];
|
|
335
|
-
}
|
|
336
|
-
if (options.groups) tlsSetup.local_supported_groups = options.groups;
|
|
337
|
-
if (options.alpnProtocols) tlsSetup.local_supported_alpns = options.alpnProtocols;
|
|
338
|
-
|
|
339
|
-
// Default key-exchange groups, BOTH roles. With DTLS 1.3 offered: the
|
|
340
|
-
// hybrid post-quantum group first (X25519MLKEM768, when node:crypto has
|
|
341
|
-
// ML-KEM — see ecdh.js), then the classical ones. In WebRTC either peer can
|
|
342
|
-
// end up the DTLS client, and harvest-now-decrypt-later applies to media
|
|
343
|
-
// too (Chrome rolls PQ DTLS out from 149). A client leading with the hybrid
|
|
344
|
-
// group sends a classical share beside it (tls_session), so a classical
|
|
345
|
-
// server costs nothing extra. DTLS 1.2 only: classical — a KEM does not fit
|
|
346
|
-
// its key exchange, and the server skips KEM groups there anyway.
|
|
347
|
-
if (!tlsSetup.local_supported_groups) {
|
|
348
|
-
// (ECDH_SUPPORTED_GROUPS leads with the hybrid too now; tls_session leaves
|
|
349
|
-
// a KEM group out of a hello that does not offer 1.3.)
|
|
350
|
-
tlsSetup.local_supported_groups = (versions.indexOf(DTLS_VERSION.DTLS1_3) >= 0
|
|
351
|
-
? SERVER_DEFAULT_GROUPS : ECDH_SUPPORTED_GROUPS).slice();
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// Server defaults — TLSSession client auto-populates these, but server expects them set externally
|
|
355
|
-
if (isServer) {
|
|
356
|
-
if (!tlsSetup.local_supported_cipher_suites) {
|
|
357
|
-
// crypto.js owns the default suite list (see default_cipher_suites).
|
|
358
|
-
tlsSetup.local_supported_cipher_suites = default_cipher_suites(
|
|
359
|
-
versions.indexOf(DTLS_VERSION.DTLS1_3) >= 0,
|
|
360
|
-
versions.indexOf(DTLS_VERSION.DTLS1_2) >= 0
|
|
361
|
-
);
|
|
362
|
-
}
|
|
363
|
-
// (key-exchange groups: set above, for both roles)
|
|
364
|
-
// signing.js owns the scheme list; DTLS spans 1.2+1.3 so include PKCS#1.
|
|
365
|
-
tlsSetup.local_supported_signature_algorithms = default_signature_schemes();
|
|
366
|
-
}
|
|
415
|
+
});
|
|
367
416
|
|
|
368
417
|
// Server cert/key.
|
|
369
418
|
//
|
|
@@ -372,13 +421,11 @@ function DTLSSession(options) {
|
|
|
372
421
|
// pfx and ocsp, and picking out two fields silently dropped all of them —
|
|
373
422
|
// an encrypted private key could never be decrypted here no matter what the
|
|
374
423
|
// caller passed. Same defect the TLS paths carried.
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
tlsSetup.cert_private_key = sctx.privateKey;
|
|
379
|
-
}
|
|
380
|
-
|
|
424
|
+
// Through TLSSession's setKeyCert, as an SNICallback's context goes in:
|
|
425
|
+
// the key/certificate pairs — several (key: [...], cert: [...]), the one
|
|
426
|
+
// the client can verify chosen per handshake — not only the first.
|
|
381
427
|
tls.set_context(tlsSetup);
|
|
428
|
+
if (options.cert && options.key) tls.setKeyCert(createSecureContext(options));
|
|
382
429
|
|
|
383
430
|
|
|
384
431
|
// ============================================================
|
|
@@ -594,24 +641,22 @@ function DTLSSession(options) {
|
|
|
594
641
|
if (ctx.writeEpoch === null || ctx.readEpoch === null) return;
|
|
595
642
|
|
|
596
643
|
if (info.direction === 'send') {
|
|
597
|
-
// §8: only one KeyUpdate may be in flight. requestKeyUpdate()
|
|
598
|
-
//
|
|
599
|
-
//
|
|
600
|
-
//
|
|
601
|
-
// strand an epoch. Say so rather than silently losing the connection.
|
|
644
|
+
// §8: only one KeyUpdate may be in flight. requestKeyUpdate() refuses
|
|
645
|
+
// up front; the TLS session's answer to the peer's update_requested
|
|
646
|
+
// is held back (deferredKeyUpdate). Anything else reaching here with
|
|
647
|
+
// one pending would strand an epoch: said, not lost in silence.
|
|
602
648
|
if (ctx.pendingWriteEpoch !== null) {
|
|
649
|
+
// The secret of a KeyUpdate held back (deferredKeyUpdate): it takes
|
|
650
|
+
// effect when that one goes.
|
|
651
|
+
if (ctx.deferredKeyUpdate && ctx.deferredKeyUpdate.secret === null) {
|
|
652
|
+
ctx.deferredKeyUpdate.secret = info.secret;
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
603
655
|
ev.emit('error', new Error(
|
|
604
656
|
'DTLS: a KeyUpdate is already awaiting acknowledgement (RFC 9147 §8)'));
|
|
605
657
|
return;
|
|
606
658
|
}
|
|
607
|
-
|
|
608
|
-
// Install the new write keys, but keep protecting with the old epoch
|
|
609
|
-
// until the peer ACKs (§8). ctx.keys[next] may already carry a .read
|
|
610
|
-
// half from the peer's own update — merge, never overwrite.
|
|
611
|
-
ctx.keys[next] = Object.assign({}, ctx.keys[next], { write: deriveEpochKeys(info.secret) });
|
|
612
|
-
ctx.writeSeq[next] = 0;
|
|
613
|
-
ctx.pendingWriteEpoch = next;
|
|
614
|
-
|
|
659
|
+
installWriteUpdate(info.secret);
|
|
615
660
|
} else if (info.direction === 'receive') {
|
|
616
661
|
let next = ctx.readEpoch + 1;
|
|
617
662
|
ctx.keys[next] = Object.assign({}, ctx.keys[next], { read: deriveEpochKeys(info.secret) });
|
|
@@ -622,40 +667,259 @@ function DTLSSession(options) {
|
|
|
622
667
|
}
|
|
623
668
|
});
|
|
624
669
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
670
|
+
// ── Datagrams ──
|
|
671
|
+
// Everything this side sends goes through one queue (ctx.out), and leaves
|
|
672
|
+
// it packed (flushOut): handshake messages of one epoch share a record —
|
|
673
|
+
// a message is cut where the space runs out, and the next goes on in the
|
|
674
|
+
// same record (RFC 6347 §4.2.3, RFC 9147 §5.5: several handshake messages
|
|
675
|
+
// or fragments to a record); records share a datagram, up to the MTU, each
|
|
676
|
+
// with its length (the DTLS 1.3 unified header's L bit is always set here:
|
|
677
|
+
// RFC 9147 §4.3, RFC 6347 §4.1.1). One record per message and one datagram
|
|
678
|
+
// per record left room unused (BoGo MTU-DTLS13: "previous packet had 212
|
|
679
|
+
// bytes available"); records alone packed into a datagram left room in the
|
|
680
|
+
// record (BoGo: "previous handshake record had 1107 bytes available, but
|
|
681
|
+
// shim did not fit another fragment in it"). Retransmissions are packed the
|
|
682
|
+
// same way: what is still unACKed, cut again to fill.
|
|
683
|
+
// Items: { kind: 'msg', tlsMsg, msgSeq, epoch, list } — a whole handshake
|
|
684
|
+
// message, fragmented here; each fragment becomes an entry of `list` (the
|
|
685
|
+
// flight, a post-handshake message), what ACKs and retransmission act on;
|
|
686
|
+
// { kind: 'entry', entry } — one of those, (re)sent; { kind: 'raw', epoch,
|
|
687
|
+
// contentType, payload } — a record of its own (ACK, application data,
|
|
688
|
+
// HelloVerifyRequest).
|
|
689
|
+
// A pass ends at the next microtask; at once before an alert (its own
|
|
690
|
+
// datagram: wolfSSL took a Finished and a close_notify packed together for
|
|
691
|
+
// a failed handshake) and when the session ends, so its last word is out
|
|
692
|
+
// before 'close'.
|
|
693
|
+
function queueOut(item) {
|
|
694
|
+
ctx.out.push(item);
|
|
695
|
+
if (!ctx.outScheduled) { ctx.outScheduled = true; queueMicrotask(function () { ctx.outScheduled = false; flushOut(); }); }
|
|
696
|
+
}
|
|
697
|
+
function queueMessage(tlsMsg, msgSeq, epoch, list, label) {
|
|
698
|
+
queueOut({ kind: 'msg', tlsMsg: tlsMsg, msgSeq: msgSeq, epoch: epoch, list: list, label: label });
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// A handshake fragment entry cut in two: its first `n` body bytes stay, the
|
|
702
|
+
// rest becomes a new entry right after it in the same list.
|
|
703
|
+
function splitEntry(e, n) {
|
|
704
|
+
let p = e.payload, type = p[0], total = (p[1] << 16) | (p[2] << 8) | p[3];
|
|
705
|
+
let msgSeq = (p[4] << 8) | p[5], off = (p[6] << 16) | (p[7] << 8) | p[8];
|
|
706
|
+
let body = p.subarray(12);
|
|
707
|
+
let frag = function (o, b) { return build_dtls_fragment(type, total, msgSeq, o, b); };
|
|
708
|
+
let rest = { epoch: e.epoch, contentType: e.contentType, payload: frag(off + n, body.subarray(n)), list: e.list };
|
|
709
|
+
e.payload = frag(off, body.subarray(0, n));
|
|
710
|
+
if (e.list) { let i = e.list.indexOf(e); if (i >= 0) e.list.splice(i + 1, 0, rest); }
|
|
711
|
+
return rest;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// The most a record of ours carries: 2^14, or the peer's
|
|
715
|
+
// record_size_limit (RFC 8449) once negotiated — as TLSSocket's.
|
|
716
|
+
function flushOut() {
|
|
717
|
+
if (ctx.out.length === 0) return;
|
|
718
|
+
let queue = ctx.out;
|
|
719
|
+
ctx.out = [];
|
|
720
|
+
let parts = [], used = 0, rec = null, failed = false;
|
|
721
|
+
// Room left: in the datagram (the MTU), and in the record (2^14, or the
|
|
722
|
+
// peer's record_size_limit — RFC 8449).
|
|
723
|
+
let cap = tls.recordSendLimit();
|
|
724
|
+
let left = function () {
|
|
725
|
+
return Math.min(ctx.mtu - used - (rec ? recordOverhead(rec.epoch) + rec.len : 0), cap - (rec ? rec.len : 0));
|
|
726
|
+
};
|
|
727
|
+
let open = function (epoch, contentType, label) {
|
|
728
|
+
rec = { epoch: epoch, contentType: contentType, parts: [], entries: [], len: 0, label: label };
|
|
729
|
+
};
|
|
730
|
+
let closeRec = function () {
|
|
731
|
+
if (!rec) return;
|
|
732
|
+
let r = rec; rec = null;
|
|
733
|
+
let seq = (r.epoch in ctx.writeSeq) ? ctx.writeSeq[r.epoch] : 0;
|
|
734
|
+
let payload = r.parts.length === 1 ? r.parts[0] : concatUint8Arrays(r.parts);
|
|
735
|
+
let record = buildRecord(r.epoch, r.contentType, payload);
|
|
736
|
+
if (!record) {
|
|
737
|
+
// buildRecord refused (no keys for an encrypted epoch): said, not
|
|
738
|
+
// dropped in silence — the handshake cannot go on either way.
|
|
739
|
+
failed = true;
|
|
740
|
+
fail(new Error('DTLS: could not build outgoing record for epoch ' + r.epoch + ' — no keys installed for that epoch'), TLS_ALERT.INTERNAL_ERROR);
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
let rn = recordNumber(r.epoch, seq);
|
|
744
|
+
for (let i = 0; i < r.entries.length; i++) (r.entries[i].sent || (r.entries[i].sent = [])).push(rn);
|
|
745
|
+
_ldbgTx(record, r.label || 'out');
|
|
746
|
+
parts.push(record); used += record.length;
|
|
747
|
+
};
|
|
748
|
+
let endDatagram = function () {
|
|
749
|
+
closeRec();
|
|
750
|
+
if (parts.length === 0) return;
|
|
751
|
+
let dg = parts.length === 1 ? parts[0] : concatUint8Arrays(parts);
|
|
752
|
+
parts = []; used = 0;
|
|
753
|
+
ev.emit('packet', dg);
|
|
631
754
|
};
|
|
632
|
-
|
|
755
|
+
// A record of `epoch` with at least `need` payload bytes: in this
|
|
756
|
+
// datagram if it fits, else the next.
|
|
757
|
+
let room = function (epoch, need) {
|
|
758
|
+
if (used > 0 && ctx.mtu - used < recordOverhead(epoch) + need) endDatagram();
|
|
759
|
+
};
|
|
760
|
+
// Into the open handshake record of `epoch`, or a new one.
|
|
761
|
+
let intoHandshake = function (epoch, need, label) {
|
|
762
|
+
if (rec && (rec.epoch !== epoch || rec.contentType !== CT.HANDSHAKE || left() < need)) closeRec();
|
|
763
|
+
if (!rec) { room(epoch, need); open(epoch, CT.HANDSHAKE, label); }
|
|
764
|
+
};
|
|
765
|
+
|
|
766
|
+
for (let qi = 0; qi < queue.length && !failed; qi++) {
|
|
767
|
+
let item = queue[qi];
|
|
768
|
+
if (item.kind === 'msg') {
|
|
769
|
+
let body = item.tlsMsg.subarray(4), total = body.length, off = 0;
|
|
770
|
+
do {
|
|
771
|
+
intoHandshake(item.epoch, 12 + Math.min(total - off, 1), item.label);
|
|
772
|
+
let take = Math.max(0, Math.min(total - off, left() - 12));
|
|
773
|
+
let frag = build_dtls_handshake(item.tlsMsg, item.msgSeq, off, take);
|
|
774
|
+
let entry = { epoch: item.epoch, contentType: CT.HANDSHAKE, payload: frag, list: item.list };
|
|
775
|
+
item.list.push(entry);
|
|
776
|
+
rec.parts.push(frag); rec.entries.push(entry); rec.len += frag.length;
|
|
777
|
+
off += take;
|
|
778
|
+
} while (off < total && !failed);
|
|
779
|
+
} else if (item.kind === 'entry' && item.entry.contentType === CT.HANDSHAKE) {
|
|
780
|
+
let e = item.entry;
|
|
781
|
+
if (e.acked) continue;
|
|
782
|
+
intoHandshake(e.epoch, Math.min(e.payload.length, 13), item.label);
|
|
783
|
+
// Too long for what is left: cut to fill (a fragment's body only).
|
|
784
|
+
if (e.payload.length > left() && e.payload.length > 13 && left() >= 13) {
|
|
785
|
+
let rest = splitEntry(e, left() - 12);
|
|
786
|
+
queue.splice(qi + 1, 0, { kind: 'entry', entry: rest, label: item.label });
|
|
787
|
+
}
|
|
788
|
+
rec.parts.push(e.payload); rec.entries.push(e); rec.len += e.payload.length;
|
|
789
|
+
} else {
|
|
790
|
+
// A record of its own: a ChangeCipherSpec (an entry of the flight),
|
|
791
|
+
// an ACK, application data, a HelloVerifyRequest.
|
|
792
|
+
let e = item.kind === 'entry' ? item.entry : null;
|
|
793
|
+
if (e && e.acked) continue;
|
|
794
|
+
// A new entry (sendCCS): it joins its list here, in sending order.
|
|
795
|
+
if (e && item.join && e.list) e.list.push(e);
|
|
796
|
+
let epoch = e ? e.epoch : item.epoch, contentType = e ? e.contentType : item.contentType;
|
|
797
|
+
let payload = e ? e.payload : item.payload;
|
|
798
|
+
closeRec();
|
|
799
|
+
room(epoch, payload.length);
|
|
800
|
+
open(epoch, contentType, item.label);
|
|
801
|
+
rec.parts.push(payload); rec.len += payload.length;
|
|
802
|
+
if (e) rec.entries.push(e);
|
|
803
|
+
closeRec();
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
if (!failed) endDatagram();
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// Our next write epoch's keys, from a KeyUpdate we sent: installed now, used
|
|
810
|
+
// from its ACK on (§8). ctx.keys[next] may already carry a .read half from
|
|
811
|
+
// the peer's own update — merge, never overwrite.
|
|
812
|
+
function installWriteUpdate(secret) {
|
|
813
|
+
let next = ctx.writeEpoch + 1;
|
|
814
|
+
ctx.keys[next] = Object.assign({}, ctx.keys[next], { write: deriveEpochKeys(secret) });
|
|
815
|
+
ctx.writeSeq[next] = 0;
|
|
816
|
+
ctx.pendingWriteEpoch = next;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// ── Post-handshake messages (RFC 9147 §5.8.4, §7.1) ──
|
|
820
|
+
// NewSessionTicket and KeyUpdate have no answering flight: each is
|
|
821
|
+
// resent on its own timer until the peer ACKs it — independently of the
|
|
822
|
+
// handshake flight and of each other. Only KeyUpdate was; a lost
|
|
823
|
+
// NewSessionTicket was simply lost (BoGo DTLS-Retransmit-*NewSessionTicket*).
|
|
824
|
+
// KeyUpdate's ACK also switches our write epoch (§8) — on the ACK that
|
|
825
|
+
// covers it, not on any ACK, as before.
|
|
826
|
+
// One made while a datagram is being taken in (feedDatagram) waits for its
|
|
827
|
+
// end — for the ACK that datagram may call for (§7.1). The server's
|
|
828
|
+
// NewSessionTickets come of the client's Finished: they went out ahead of
|
|
829
|
+
// its ACK, and a peer that waits for the ACK first (BoringSSL's runner)
|
|
830
|
+
// read the first ticket as renegotiation and the second as out of order
|
|
831
|
+
// (BoGo N1). A KeyUpdate answering the peer's follows its ACK the same way.
|
|
832
|
+
function sendPostHandshake(kind, tlsMsg, msgSeq, epoch) {
|
|
833
|
+
let item = { kind: kind, msg: { tlsMsg: tlsMsg, msgSeq: msgSeq, epoch: epoch }, entries: [],
|
|
834
|
+
attempts: 0, timeout: currentRto(), timer: null, began: Date.now() };
|
|
835
|
+
ctx.postHandshake.push(item);
|
|
836
|
+
if (ctx.feeding > 0) { ctx.heldPostHandshake.push(item); return; }
|
|
837
|
+
transmitPostHandshake(item);
|
|
838
|
+
}
|
|
839
|
+
function transmitPostHandshake(item) {
|
|
840
|
+
queueMessage(item.msg.tlsMsg, item.msg.msgSeq, item.msg.epoch, item.entries, item.kind);
|
|
841
|
+
schedulePostHandshake(item);
|
|
842
|
+
}
|
|
843
|
+
function flushHeldPostHandshake() {
|
|
844
|
+
let held = ctx.heldPostHandshake;
|
|
845
|
+
ctx.heldPostHandshake = [];
|
|
846
|
+
for (let i = 0; i < held.length; i++) {
|
|
847
|
+
if (ctx.state === 'closed' || ctx.postHandshake.indexOf(held[i]) < 0) continue;
|
|
848
|
+
transmitPostHandshake(held[i]);
|
|
849
|
+
}
|
|
633
850
|
}
|
|
634
851
|
|
|
635
|
-
function
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
if (
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
852
|
+
function schedulePostHandshake(item) {
|
|
853
|
+
item.timer = setTimeout(function () {
|
|
854
|
+
item.timer = null;
|
|
855
|
+
if (ctx.postHandshake.indexOf(item) < 0 || ctx.state === 'closed') return;
|
|
856
|
+
if (exhausted(item.attempts, item.began)) {
|
|
857
|
+
removePostHandshake(item);
|
|
858
|
+
// An unacknowledged KeyUpdate leaves the next epoch unusable; a
|
|
859
|
+
// ticket is an offer, and the connection is fine without it.
|
|
860
|
+
if (item.kind === 'key_update') ev.emit('error', new Error('DTLS: KeyUpdate was never acknowledged'));
|
|
643
861
|
return;
|
|
644
862
|
}
|
|
645
|
-
|
|
646
|
-
//
|
|
647
|
-
//
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
863
|
+
item.attempts++;
|
|
864
|
+
// Rebuilt, not replayed: each retransmission is a new record with a
|
|
865
|
+
// fresh sequence number, or the peer's replay window drops it.
|
|
866
|
+
transmitUnacked(item.entries, item.kind + '-rtx', false);
|
|
867
|
+
item.timeout = Math.min(item.timeout * 2, 60000);
|
|
868
|
+
schedulePostHandshake(item);
|
|
869
|
+
}, item.timeout);
|
|
870
|
+
if (item.timer && typeof item.timer.unref === 'function') item.timer.unref();
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
function removePostHandshake(item) {
|
|
874
|
+
if (item.timer) clearTimeout(item.timer);
|
|
875
|
+
let i = ctx.postHandshake.indexOf(item);
|
|
876
|
+
if (i >= 0) ctx.postHandshake.splice(i, 1);
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// The session is over: every timer it runs stops with it.
|
|
880
|
+
function shutDown() {
|
|
881
|
+
flushOut();
|
|
882
|
+
ctx.state = 'closed';
|
|
883
|
+
cancelRetransmit();
|
|
884
|
+
cancelPostHandshake();
|
|
885
|
+
clearPendingAcks();
|
|
654
886
|
}
|
|
655
887
|
|
|
656
|
-
function
|
|
657
|
-
|
|
658
|
-
|
|
888
|
+
function cancelPostHandshake() {
|
|
889
|
+
for (let i = 0; i < ctx.postHandshake.length; i++) {
|
|
890
|
+
if (ctx.postHandshake[i].timer) clearTimeout(ctx.postHandshake[i].timer);
|
|
891
|
+
}
|
|
892
|
+
ctx.postHandshake = [];
|
|
893
|
+
ctx.heldPostHandshake = [];
|
|
894
|
+
ctx.deferredKeyUpdate = null;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// The post-handshake messages an ACK covers (by record number): done.
|
|
898
|
+
// A second ACK of them finds nothing left, and changes nothing
|
|
899
|
+
// (BoGo ACKPostHandshakeTwice).
|
|
900
|
+
function ackPostHandshake(got) {
|
|
901
|
+
for (let i = ctx.postHandshake.length - 1; i >= 0; i--) {
|
|
902
|
+
let item = ctx.postHandshake[i];
|
|
903
|
+
if (item.entries.length === 0) continue; // not out yet
|
|
904
|
+
markAcked(item.entries, got);
|
|
905
|
+
if (item.entries.some(function (e) { return !e.acked; })) continue;
|
|
906
|
+
removePostHandshake(item);
|
|
907
|
+
if (item.kind === 'key_update' && ctx.pendingWriteEpoch !== null) {
|
|
908
|
+
// §8: the new epoch becomes live on the ACK of the KeyUpdate.
|
|
909
|
+
ctx.writeEpoch = ctx.pendingWriteEpoch;
|
|
910
|
+
ctx.pendingWriteEpoch = null;
|
|
911
|
+
ev.emit('keyUpdate', { direction: 'send', epoch: ctx.writeEpoch });
|
|
912
|
+
if (ctx.state === 'closed') return; // a listener closed it
|
|
913
|
+
// The KeyUpdate held back for this one (§8): now, under the epoch
|
|
914
|
+
// just made live — the one it replaces.
|
|
915
|
+
let held = ctx.deferredKeyUpdate;
|
|
916
|
+
if (held && held.secret !== null) {
|
|
917
|
+
ctx.deferredKeyUpdate = null;
|
|
918
|
+
sendPostHandshake('key_update', held.tlsMsg, held.msgSeq, ctx.writeEpoch);
|
|
919
|
+
installWriteUpdate(held.secret);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
659
923
|
}
|
|
660
924
|
|
|
661
925
|
|
|
@@ -688,28 +952,25 @@ function DTLSSession(options) {
|
|
|
688
952
|
// epoch, which is 3 until a KeyUpdate moves it to 4, 5, ... Hardcoding 3
|
|
689
953
|
// meant every post-handshake message stayed on the pre-rekey epoch.
|
|
690
954
|
dtlsEpoch = (tlsEpoch === 1) ? 2
|
|
691
|
-
: (
|
|
955
|
+
: appEpoch();
|
|
692
956
|
} else {
|
|
693
957
|
dtlsEpoch = 1; // DTLS 1.2: epoch 1 for all encrypted
|
|
694
958
|
}
|
|
695
959
|
|
|
696
960
|
if (type === 'alert') {
|
|
697
|
-
|
|
961
|
+
// TLSSession names the epoch the PEER reads at (sendAlert); this layer
|
|
962
|
+
// holds the keys. Its choice when we hold write keys for it, else the
|
|
963
|
+
// highest epoch we do (alertEpoch) — never a record for an epoch we
|
|
964
|
+
// have no keys for, which a DTLS 1.3 alert before any keys could
|
|
965
|
+
// become: a unified header the peer could not even parse ("bad type
|
|
966
|
+
// byte"; BoGo FragmentMessage*Mismatch).
|
|
967
|
+
let ae = (dtlsEpoch === 0 || (ctx.keys[dtlsEpoch] && ctx.keys[dtlsEpoch].write)) ? dtlsEpoch : alertEpoch();
|
|
968
|
+
sendAlertRecord(ae, data);
|
|
698
969
|
return;
|
|
699
970
|
}
|
|
700
971
|
|
|
701
|
-
// DTLS 1.2:
|
|
702
|
-
if (ctx.selectedVersion !== DTLS_VERSION.DTLS1_3 && tlsEpoch === 1
|
|
703
|
-
let ts = tls.getTrafficSecrets();
|
|
704
|
-
if (ts.masterSecret) {
|
|
705
|
-
let d12 = deriveKeys12(ts.masterSecret, ts.localRandom, ts.remoteRandom, tls.getCipher(), isServer);
|
|
706
|
-
ctx.keys[1] = {
|
|
707
|
-
write: { key: d12.writeKey, iv: d12.writeIv },
|
|
708
|
-
read: { key: d12.readKey, iv: d12.readIv },
|
|
709
|
-
};
|
|
710
|
-
ctx.writeSeq[1] = 0;
|
|
711
|
-
}
|
|
712
|
-
}
|
|
972
|
+
// DTLS 1.2: keys before the first encrypted message
|
|
973
|
+
if (ctx.selectedVersion !== DTLS_VERSION.DTLS1_3 && tlsEpoch === 1) installKeys12();
|
|
713
974
|
|
|
714
975
|
// Flight boundary: first outgoing record after accepting peer data
|
|
715
976
|
// starts a fresh flight (clears the previous flight's records and
|
|
@@ -724,49 +985,32 @@ function DTLSSession(options) {
|
|
|
724
985
|
sendCCS();
|
|
725
986
|
}
|
|
726
987
|
|
|
727
|
-
//
|
|
728
|
-
let dtlsMsg = build_dtls_handshake(data, seq);
|
|
729
|
-
|
|
730
|
-
// Fragment if needed
|
|
731
|
-
let frags = fragmentMessage(dtlsMsg, data, seq);
|
|
732
|
-
|
|
733
|
-
// Build DTLS records and emit
|
|
988
|
+
// Out as a DTLS handshake message — fragmented as it is packed (flushOut).
|
|
734
989
|
// RFC 9147 §5.8.4: post-handshake messages do NOT belong to the handshake
|
|
735
990
|
// flight. Each category gets an independent state machine; folding a
|
|
736
991
|
// KeyUpdate into currentFlight would make a handshake retransmission
|
|
737
992
|
// replay it, and a KeyUpdate ACK cancel the handshake's timer.
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
993
|
+
// DTLS 1.3, after the handshake: KeyUpdate, NewSessionTicket — each on
|
|
994
|
+
// its own retransmission until ACKed (sendPostHandshake).
|
|
995
|
+
// (A TLS 1.3 NewSessionTicket is post-handshake whenever it goes — the
|
|
996
|
+
// server sends its first one just before it reports the handshake done.)
|
|
997
|
+
if (ctx.selectedVersion === DTLS_VERSION.DTLS1_3 &&
|
|
998
|
+
(type === 'new_session_ticket' || (type === 'key_update' && ctx.state === 'connected'))) {
|
|
999
|
+
// §8: one KeyUpdate at a time — "until the KeyUpdate message has been
|
|
1000
|
+
// acknowledged" no other. Ours answering the peer's update_requested
|
|
1001
|
+
// while our own is unACKed waits for that ACK (ackPostHandshake), then
|
|
1002
|
+
// goes under the epoch it replaces. Sent at once, the next one's keys
|
|
1003
|
+
// found an epoch still pending and the connection failed (BoringSSL,
|
|
1004
|
+
// which delays its ACKs: every time).
|
|
1005
|
+
if (type === 'key_update' && ctx.pendingWriteEpoch !== null) {
|
|
1006
|
+
ctx.deferredKeyUpdate = { tlsMsg: data, msgSeq: seq, secret: null };
|
|
1007
|
+
return;
|
|
744
1008
|
}
|
|
745
|
-
|
|
746
|
-
// timer until an ACK arrives. Only the last fragment is tracked; a
|
|
747
|
-
// KeyUpdate body is 1 byte and never fragments in practice.
|
|
748
|
-
armKeyUpdateRetransmit(frags[frags.length - 1], dtlsEpoch);
|
|
1009
|
+
sendPostHandshake(type, data, seq, dtlsEpoch);
|
|
749
1010
|
return;
|
|
750
1011
|
}
|
|
751
1012
|
|
|
752
|
-
|
|
753
|
-
let record = buildRecord(dtlsEpoch, CT.HANDSHAKE, frags[i]);
|
|
754
|
-
// The flight captures the fragment, not the record — see currentFlight.
|
|
755
|
-
ctx.currentFlight.push({ epoch: dtlsEpoch, contentType: CT.HANDSHAKE, payload: frags[i] });
|
|
756
|
-
if (record) {
|
|
757
|
-
_ldbgTx(record, 'L543'); ev.emit('packet', record);
|
|
758
|
-
} else {
|
|
759
|
-
// buildRecord refused to build (no keys for an encrypted epoch).
|
|
760
|
-
// Dropping the record here without a word is how a key-epoch mismatch
|
|
761
|
-
// turns into an unexplained peer timeout instead of a diagnosable
|
|
762
|
-
// failure: nothing reaches the wire and nothing is reported. Surface
|
|
763
|
-
// it — the handshake cannot proceed either way.
|
|
764
|
-
ev.emit('error', new Error(
|
|
765
|
-
'DTLS: could not build outgoing handshake record for epoch ' + dtlsEpoch +
|
|
766
|
-
' — no keys installed for that epoch'));
|
|
767
|
-
return;
|
|
768
|
-
}
|
|
769
|
-
}
|
|
1013
|
+
queueFlightMessage(data, seq, dtlsEpoch);
|
|
770
1014
|
|
|
771
1015
|
// Start retransmit timer after sending flight messages.
|
|
772
1016
|
//
|
|
@@ -815,67 +1059,151 @@ function DTLSSession(options) {
|
|
|
815
1059
|
|
|
816
1060
|
if (ctx.selectedVersion === DTLS_VERSION.DTLS1_3) {
|
|
817
1061
|
// DTLS 1.3 encrypted (unified header)
|
|
818
|
-
return buildEncryptedDtls13(contentType, payload, nextWriteSeq(epoch), epoch, epochKeys.write);
|
|
1062
|
+
return buildEncryptedDtls13(contentType, payload, nextWriteSeq(epoch), epoch, epochKeys.write, peerCid());
|
|
819
1063
|
} else {
|
|
820
1064
|
// DTLS 1.2 encrypted (classic header)
|
|
821
|
-
return buildEncryptedDtls12(contentType, epoch, nextWriteSeq(epoch), payload, epochKeys.write);
|
|
1065
|
+
return buildEncryptedDtls12(contentType, epoch, nextWriteSeq(epoch), payload, epochKeys.write, peerCid());
|
|
822
1066
|
}
|
|
823
1067
|
}
|
|
824
1068
|
|
|
1069
|
+
// The CID the peer asked for, in every protected record we send (null:
|
|
1070
|
+
// none). Ours, that the peer's carry: from the moment we ask for it — a
|
|
1071
|
+
// record with it can come in the datagram that agrees to it (a resumed
|
|
1072
|
+
// DTLS 1.2 server's Finished, behind its ServerHello) — and required once
|
|
1073
|
+
// agreed to.
|
|
1074
|
+
function peerCid() { return ctx.cid && ctx.cid.peer.length > 0 ? ctx.cid.peer : null; }
|
|
1075
|
+
function localCid() { let c = tls.context.local_cid; return c && c.length > 0 ? c : null; }
|
|
1076
|
+
function localCidRequired() { return !!(ctx.cid && ctx.cid.local.length > 0); }
|
|
1077
|
+
|
|
825
1078
|
function nextWriteSeq(epoch) {
|
|
826
1079
|
if (!(epoch in ctx.writeSeq)) ctx.writeSeq[epoch] = 0;
|
|
827
1080
|
return ctx.writeSeq[epoch]++;
|
|
828
1081
|
}
|
|
829
1082
|
|
|
830
1083
|
/**
|
|
831
|
-
*
|
|
832
|
-
*
|
|
1084
|
+
* The one way a handshake message of a flight goes out: queued, to be cut
|
|
1085
|
+
* and packed (flushOut) into entries of the current flight — kept for
|
|
1086
|
+
* retransmission — and counted as our answer (the implicit ACK, RFC 9147
|
|
1087
|
+
* §7.1). The cookie-bearing ClientHello goes the same way.
|
|
833
1088
|
*/
|
|
834
|
-
function
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
1089
|
+
function queueFlightMessage(tlsMsg, msgSeq, dtlsEpoch) {
|
|
1090
|
+
ctx.flightMessagesQueued++;
|
|
1091
|
+
ctx.flightQueued++;
|
|
1092
|
+
if (ctx.flightBegan === null) ctx.flightBegan = Date.now();
|
|
1093
|
+
if (ctx.rttStart === null && ctx.currentFlight.length === 0 && ctx.flightQueued === 1) ctx.rttStart = Date.now();
|
|
1094
|
+
queueMessage(tlsMsg, msgSeq, dtlsEpoch, ctx.currentFlight, 'flight');
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
// What one record adds around its payload, by the epoch it goes out in
|
|
1098
|
+
// (flushOut fills datagrams to the MTU with it):
|
|
1099
|
+
// unprotected (epoch 0, and DTLS 1.2 before the CCS): the 13-byte
|
|
1100
|
+
// DTLSPlaintext header, nothing else;
|
|
1101
|
+
// DTLS 1.3 protected: the unified header (1 + 2-byte sequence number +
|
|
1102
|
+
// 2-byte length), the inner content-type byte, the 16-byte tag;
|
|
1103
|
+
// DTLS 1.2 protected: 13 + the 16-byte tag + the 8-byte explicit nonce
|
|
1104
|
+
// of AES-GCM / CCM (ChaCha20-Poly1305 has none, RFC 7905 §2);
|
|
1105
|
+
// and, with the peer's CID, the CID — and in 1.2 the inner type byte.
|
|
1106
|
+
// A single figure for all — 37, or 21 for 1.3 — left 24 bytes unused in
|
|
1107
|
+
// every plaintext fragment (the whole DTLS 1.2 certificate flight), and
|
|
1108
|
+
// was a byte short for 1.3, which forgot the content type.
|
|
1109
|
+
function recordOverhead(epoch) {
|
|
1110
|
+
if (!ctx.keys[epoch] || !ctx.keys[epoch].write) return 13;
|
|
1111
|
+
let cid = peerCid(), cidLen = cid ? cid.length : 0;
|
|
1112
|
+
if (ctx.selectedVersion === DTLS_VERSION.DTLS1_3) return 5 + cidLen + 1 + 16;
|
|
1113
|
+
// The explicit nonce, as the suite table has it (record_ivlen).
|
|
1114
|
+
let meta = TLS_CIPHER_SUITES[tls.getCipher()];
|
|
1115
|
+
return 13 + (cid ? cidLen + 1 : 0) + 16 + (meta && meta.record_ivlen ? meta.record_ivlen : 0);
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
// DTLS 1.2, a ticket offered with no session_id (a hello offering DTLS
|
|
1119
|
+
// 1.3 too): an epoch-1 record before any keys is the resumed server's
|
|
1120
|
+
// ChangeCipherSpec or Finished — a full handshake has none before our
|
|
1121
|
+
// flight. Tried under the keys the saved session gives; one that opens
|
|
1122
|
+
// takes the resumption (tls.commitResume12), and the keys are installed.
|
|
1123
|
+
// One that does not — anyone may send a datagram — changes nothing.
|
|
1124
|
+
function tryResumedKeys12(record) {
|
|
1125
|
+
let ms = tls.resumeCandidate12();
|
|
1126
|
+
if (!ms) return;
|
|
1127
|
+
let d12;
|
|
1128
|
+
try { d12 = keys12(ms); } catch (e) { return; }
|
|
1129
|
+
let opened;
|
|
1130
|
+
try { opened = openDtls12(record.content, { key: d12.readKey, iv: d12.readIv }, record.epoch, record.seq, record.type, localCid()); }
|
|
1131
|
+
catch (e) { return; }
|
|
1132
|
+
if (!tls.commitResume12() || !installKeys12()) return;
|
|
1133
|
+
open12(record, ctx.keys[1].read, opened);
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
// A DTLS 1.2 record of an epoch whose keys came after the datagram was
|
|
1137
|
+
// parsed, opened now (or `opened` already: { type, content }), and through
|
|
1138
|
+
// the replay window, as the datagram parser does. False when it does not
|
|
1139
|
+
// open; one that opens and is malformed inside is the peer's (alertDesc).
|
|
1140
|
+
function open12(record, readKeys, opened) {
|
|
1141
|
+
try {
|
|
1142
|
+
opened = opened || openDtls12(record.content, readKeys, record.epoch, record.seq, record.type, localCid());
|
|
1143
|
+
} catch (e) { if (e && e.alertDesc) throw e; return false; }
|
|
1144
|
+
record.type = opened.type;
|
|
1145
|
+
record.content = opened.content;
|
|
1146
|
+
record.encrypted = true;
|
|
1147
|
+
if (!acceptRecordSeq(readKeys, record.seq)) record.replayed = true;
|
|
1148
|
+
return true;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
// DTLS 1.2: epoch 1's keys, from a master secret — the session's, once
|
|
1152
|
+
// there is one, or a candidate's (tryResumedKeys12). One place: they were
|
|
1153
|
+
// derived in three.
|
|
1154
|
+
function keys12(masterSecret) {
|
|
1155
|
+
let ts = tls.getTrafficSecrets();
|
|
1156
|
+
return deriveKeys12(masterSecret, ts.localRandom, ts.remoteRandom, tls.getCipher(), isServer);
|
|
1157
|
+
}
|
|
1158
|
+
// …installed, once there is a master secret to make them of. True when
|
|
1159
|
+
// they are there.
|
|
1160
|
+
function installKeys12() {
|
|
1161
|
+
if (ctx.keys[1]) return true;
|
|
1162
|
+
let ts = tls.getTrafficSecrets();
|
|
1163
|
+
if (!ts.masterSecret) return false;
|
|
1164
|
+
let d12 = keys12(ts.masterSecret);
|
|
1165
|
+
ctx.keys[1] = {
|
|
1166
|
+
write: { key: d12.writeKey, iv: d12.writeIv },
|
|
1167
|
+
read: { key: d12.readKey, iv: d12.readIv },
|
|
1168
|
+
};
|
|
1169
|
+
ctx.writeSeq[1] = 0;
|
|
1170
|
+
return true;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
// DTLS 1.3: the application epoch we write — 3 until a KeyUpdate moves it on.
|
|
1174
|
+
function appEpoch() { return ctx.writeEpoch !== null ? ctx.writeEpoch : 3; }
|
|
1175
|
+
|
|
1176
|
+
/**
|
|
1177
|
+
* The epoch our alerts (and close_notify) go out in: the highest one we
|
|
1178
|
+
* hold write keys for and the peer reads. DTLS 1.3: the live application
|
|
1179
|
+
* epoch once connected — after a KeyUpdate that is 4, 5, ..., and the peer
|
|
1180
|
+
* no longer reads 3 — else the handshake epoch once its keys exist, else 0
|
|
1181
|
+
* (a DTLSPlaintext record). DTLS 1.2: epoch 1 once our ChangeCipherSpec
|
|
1182
|
+
* has gone, else 0.
|
|
1183
|
+
*/
|
|
1184
|
+
function alertEpoch() {
|
|
1185
|
+
if (ctx.selectedVersion === DTLS_VERSION.DTLS1_3) {
|
|
1186
|
+
if (ctx.state === 'connected' && ctx.writeEpoch !== null && ctx.keys[ctx.writeEpoch] && ctx.keys[ctx.writeEpoch].write) return ctx.writeEpoch;
|
|
1187
|
+
if (ctx.keys[2] && ctx.keys[2].write) return 2;
|
|
1188
|
+
return 0;
|
|
1189
|
+
}
|
|
1190
|
+
return (ctx.localCcsSent && ctx.keys[1] && ctx.keys[1].write) ? 1 : 0;
|
|
866
1191
|
}
|
|
867
1192
|
|
|
868
1193
|
/**
|
|
869
1194
|
* Send an alert record.
|
|
870
1195
|
*/
|
|
871
1196
|
function sendAlertRecord(epoch, alertData) {
|
|
1197
|
+
// What is queued goes first, the alert in a datagram of its own (see
|
|
1198
|
+
// flushOut).
|
|
1199
|
+
flushOut();
|
|
872
1200
|
let record = buildRecord(epoch, CT.ALERT, alertData);
|
|
873
1201
|
// buildRecord returns null when it refuses to emit a malformed record
|
|
874
1202
|
// (DTLS 1.3 epoch >= 2 with no keys installed). Emitting that null as a
|
|
875
1203
|
// packet pushes `null` into the transport; drop it instead — we already
|
|
876
1204
|
// surfaced the reason through 'error'.
|
|
877
1205
|
if (!record) return;
|
|
878
|
-
_ldbgTx(record, '
|
|
1206
|
+
_ldbgTx(record, 'alert'); ev.emit('packet', record);
|
|
879
1207
|
}
|
|
880
1208
|
|
|
881
1209
|
/**
|
|
@@ -890,7 +1218,6 @@ function DTLSSession(options) {
|
|
|
890
1218
|
ctx.newFlightPending = false;
|
|
891
1219
|
startNewFlight();
|
|
892
1220
|
}
|
|
893
|
-
let record = buildDtlsPlaintext(CT.CHANGE_CIPHER_SPEC, 0, nextWriteSeq(0), new Uint8Array([1]));
|
|
894
1221
|
// RFC 6347 §4.2.4: the CCS is part of the flight and MUST be included
|
|
895
1222
|
// when the flight is retransmitted. Captured as a descriptor (epoch 0,
|
|
896
1223
|
// plaintext), so resendFlight rebuilds it with a fresh seq like every
|
|
@@ -898,8 +1225,16 @@ function DTLSSession(options) {
|
|
|
898
1225
|
// retransmitted final flight arrived WITHOUT the cipher-spec change and
|
|
899
1226
|
// the peer could never decrypt the accompanying Finished — an
|
|
900
1227
|
// unrecoverable stall under single-datagram loss.
|
|
901
|
-
|
|
902
|
-
|
|
1228
|
+
//
|
|
1229
|
+
// Into the flight where it goes out, as the messages' fragments are
|
|
1230
|
+
// (flushOut): the messages queued before it — the ClientKeyExchange, a
|
|
1231
|
+
// Certificate — are cut into entries only then, and a CCS put in the
|
|
1232
|
+
// list here, at once, went ahead of them. Every retransmission then
|
|
1233
|
+
// opened with the CCS (BoGo DTLS-RetransmitFinished-Fragmented: "record
|
|
1234
|
+
// of type 20 in retransmit, but expected 22"); a peer that reads in
|
|
1235
|
+
// order takes it before the key exchange it follows.
|
|
1236
|
+
let ccs = { epoch: 0, contentType: CT.CHANGE_CIPHER_SPEC, payload: new Uint8Array([1]), list: ctx.currentFlight };
|
|
1237
|
+
queueOut({ kind: 'entry', entry: ccs, label: 'ccs', join: true });
|
|
903
1238
|
}
|
|
904
1239
|
|
|
905
1240
|
|
|
@@ -907,7 +1242,67 @@ function DTLSSession(options) {
|
|
|
907
1242
|
// Incoming: UDP datagram → parse → feed TLSSession
|
|
908
1243
|
// ============================================================
|
|
909
1244
|
|
|
1245
|
+
/**
|
|
1246
|
+
* The one way a DTLS session ends in failure: our alert (when there is one
|
|
1247
|
+
* to send — none when the peer's alert or TLSSession's own already went),
|
|
1248
|
+
* the session closed (no more input, no retransmission), 'error', then
|
|
1249
|
+
* 'close'. 'close' is what DTLSServer drops the connection on: a session
|
|
1250
|
+
* that only emitted 'error' stayed in its table, deaf, and the address
|
|
1251
|
+
* could not start a new handshake.
|
|
1252
|
+
*/
|
|
1253
|
+
function fail(err, alertDesc) {
|
|
1254
|
+
if (ctx.state === 'closed') return;
|
|
1255
|
+
if (alertDesc !== undefined && alertDesc !== null) {
|
|
1256
|
+
try { tls.sendAlert(2, alertDesc); } catch (e) {}
|
|
1257
|
+
err = local_alert_error(alertDesc, err);
|
|
1258
|
+
}
|
|
1259
|
+
shutDown();
|
|
1260
|
+
ev.emit('error', err);
|
|
1261
|
+
ev.emit('close');
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
/**
|
|
1265
|
+
* The input boundary: nothing a peer sends may throw out of this session
|
|
1266
|
+
* (a single UDP datagram, spoofable by anyone, took the process down —
|
|
1267
|
+
* BoGo). What cannot be read as a record is dropped where it is read, as
|
|
1268
|
+
* RFC 9147 §4.5.2 / RFC 6347 §4.1.2.7 ask ("invalid records SHOULD be
|
|
1269
|
+
* silently discarded"); what is left to throw here comes from a record
|
|
1270
|
+
* that WAS read, or from a bug, and ends the session — the error's own
|
|
1271
|
+
* alert (alertDesc), internal_error otherwise. TLSSession has the same
|
|
1272
|
+
* boundary for what reaches it (run_guarded).
|
|
1273
|
+
*/
|
|
1274
|
+
//
|
|
1275
|
+
// Returns whether the datagram held a record that authenticated and is
|
|
1276
|
+
// the newest yet (by epoch, then sequence number) — RFC 9146 §6 / RFC
|
|
1277
|
+
// 9147 §9's condition for taking a new peer address, which the layer
|
|
1278
|
+
// above decides.
|
|
910
1279
|
function feedDatagram(data) {
|
|
1280
|
+
ctx.dgNewest = false;
|
|
1281
|
+
try {
|
|
1282
|
+
feedDatagramUnguarded(data);
|
|
1283
|
+
} catch (e) {
|
|
1284
|
+
let err = e instanceof Error ? e : new Error(String(e));
|
|
1285
|
+
fail(err, (e && e.alertDesc) || TLS_ALERT.INTERNAL_ERROR);
|
|
1286
|
+
return false;
|
|
1287
|
+
}
|
|
1288
|
+
return ctx.dgNewest;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
// A protected record taken: the newest so far?
|
|
1292
|
+
function noteNewest(record) {
|
|
1293
|
+
let n = ctx.rxNewest;
|
|
1294
|
+
if (n && (record.epoch < n.epoch || (record.epoch === n.epoch && record.seq <= n.seq))) return;
|
|
1295
|
+
ctx.rxNewest = { epoch: record.epoch, seq: record.seq };
|
|
1296
|
+
ctx.dgNewest = true;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
function feedDatagramUnguarded(data) {
|
|
1300
|
+
ctx.feeding++;
|
|
1301
|
+
try { takeDatagram(data); }
|
|
1302
|
+
finally { if (--ctx.feeding === 0) flushHeldPostHandshake(); }
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
function takeDatagram(data) {
|
|
911
1306
|
if (ctx.state === 'closed') return;
|
|
912
1307
|
if (ctx.state === 'idle') ctx.state = 'handshaking';
|
|
913
1308
|
|
|
@@ -919,7 +1314,7 @@ function DTLSSession(options) {
|
|
|
919
1314
|
}
|
|
920
1315
|
}
|
|
921
1316
|
|
|
922
|
-
let records = parseDtlsDatagram(data, keysByEpoch);
|
|
1317
|
+
let records = parseDtlsDatagram(data, keysByEpoch, localCid(), localCidRequired());
|
|
923
1318
|
|
|
924
1319
|
if (LEMON_DEBUG) {
|
|
925
1320
|
var _summ = [];
|
|
@@ -933,9 +1328,9 @@ function DTLSSession(options) {
|
|
|
933
1328
|
}
|
|
934
1329
|
|
|
935
1330
|
// RFC 9147 §7: collect the record numbers of handshake records we received
|
|
936
|
-
// in this datagram so we can ACK them once, after processing
|
|
937
|
-
//
|
|
938
|
-
//
|
|
1331
|
+
// in this datagram so we can ACK them once, after processing (DTLS 1.3):
|
|
1332
|
+
// a flight's at once or a quarter-timer later, post-handshake ones
|
|
1333
|
+
// together, delayed (see "ACKs" below).
|
|
939
1334
|
let ackable = [];
|
|
940
1335
|
|
|
941
1336
|
// Whether these records arrived AFTER the handshake must be decided here,
|
|
@@ -946,8 +1341,27 @@ function DTLSSession(options) {
|
|
|
946
1341
|
// the spurious epoch-3 ACK that makes a peer answer decode_error on the
|
|
947
1342
|
// NEXT record — so a perfectly good SCTP COOKIE_ECHO takes the blame.
|
|
948
1343
|
let wasConnectedOnEntry = (ctx.state === 'connected');
|
|
1344
|
+
let sentBefore = ctx.flightMessagesQueued;
|
|
1345
|
+
ctx.ackDisruption = false;
|
|
1346
|
+
ctx.dgAnyNew = false;
|
|
1347
|
+
ctx.dgDuplicate = false;
|
|
1348
|
+
ctx.dgNewest = false;
|
|
949
1349
|
|
|
950
1350
|
for (let i = 0; i < records.length; i++) {
|
|
1351
|
+
// An unprotected record received before — same epoch-0 sequence number
|
|
1352
|
+
// — is the same record again (replayed, or duplicated on the way), not
|
|
1353
|
+
// the peer retransmitting: a retransmission is a new record, with a new
|
|
1354
|
+
// number. Taken as one, it had us resend our flight (BoGo
|
|
1355
|
+
// DTLS-Replay-TLS12). Not a sliding window, as for protected records
|
|
1356
|
+
// (acceptRecordSeq): an unauthenticated number must not move one — a
|
|
1357
|
+
// forged high one would make every real record "too old". Just the
|
|
1358
|
+
// last numbers seen.
|
|
1359
|
+
if (records[i].epoch === 0 && !records[i].encrypted) {
|
|
1360
|
+
if (ctx.seenPlainSeqs.indexOf(records[i].seq) >= 0) continue;
|
|
1361
|
+
ctx.seenPlainSeqs.push(records[i].seq);
|
|
1362
|
+
if (ctx.seenPlainSeqs.length > 64) ctx.seenPlainSeqs.shift();
|
|
1363
|
+
}
|
|
1364
|
+
if (records[i].replayed) continue;
|
|
951
1365
|
// A record we could not decrypt when the datagram was parsed. Keys may
|
|
952
1366
|
// have arrived since — processing an earlier record in THIS datagram
|
|
953
1367
|
// (the ServerHello) is exactly what installs them. Retry now, in order,
|
|
@@ -958,7 +1372,9 @@ function DTLSSession(options) {
|
|
|
958
1372
|
let readKeys = epochKeys ? epochKeys.read : null;
|
|
959
1373
|
if (!readKeys) continue; // still unreadable — let the peer retransmit
|
|
960
1374
|
let dec = null;
|
|
961
|
-
|
|
1375
|
+
// A record that opened but is malformed inside (alertDesc) is the
|
|
1376
|
+
// peer's: to the boundary. Anything else: it did not open, dropped.
|
|
1377
|
+
try { dec = decryptEncryptedDtls13(records[i].raw, readKeys, records[i].epoch, localCid(), localCidRequired()); } catch (e) { if (e && e.alertDesc) throw e; dec = null; }
|
|
962
1378
|
if (!dec) continue;
|
|
963
1379
|
records[i] = {
|
|
964
1380
|
type: dec.type, epoch: dec.epoch, seq: dec.seq,
|
|
@@ -982,67 +1398,124 @@ function DTLSSession(options) {
|
|
|
982
1398
|
ackable.push({ epoch: records[i].epoch, seq: records[i].seq });
|
|
983
1399
|
}
|
|
984
1400
|
|
|
1401
|
+
let hadKeys12 = !!ctx.keys[1];
|
|
1402
|
+
if (!hadKeys12 && ctx.selectedVersion === DTLS_VERSION.DTLS1_2 && records[i].epoch === 1 && !records[i].encrypted) {
|
|
1403
|
+
tryResumedKeys12(records[i]);
|
|
1404
|
+
}
|
|
985
1405
|
processRecord(records[i]);
|
|
1406
|
+
if (ctx.state === 'closed') break;
|
|
1407
|
+
if (records[i].encrypted) noteNewest(records[i]);
|
|
1408
|
+
if (ctx.selectedVersion === DTLS_VERSION.DTLS1_2 && !ctx.keys[1]) installKeys12();
|
|
986
1409
|
|
|
987
|
-
//
|
|
988
|
-
|
|
1410
|
+
// DTLS 1.2 keys just installed: what of this datagram came protected
|
|
1411
|
+
// under them can be read now.
|
|
1412
|
+
if (!hadKeys12 && ctx.keys[1]) {
|
|
989
1413
|
let readKeys = ctx.keys[1].read;
|
|
990
1414
|
for (let j = i + 1; j < records.length; j++) {
|
|
991
|
-
if (!records[j].encrypted && records[j].epoch > 0 && readKeys)
|
|
992
|
-
try {
|
|
993
|
-
records[j].content = decryptDtls12(records[j].content, readKeys.key, readKeys.iv, records[j].epoch, records[j].seq, records[j].type);
|
|
994
|
-
records[j].encrypted = true;
|
|
995
|
-
} catch(e) {
|
|
996
|
-
}
|
|
997
|
-
}
|
|
1415
|
+
if (!records[j].encrypted && records[j].epoch > 0 && readKeys) open12(records[j], readKeys);
|
|
998
1416
|
}
|
|
999
1417
|
}
|
|
1000
1418
|
}
|
|
1001
1419
|
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
//
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1420
|
+
dtls12AnswerRetransmission();
|
|
1421
|
+
|
|
1422
|
+
// ── ACKs (RFC 9147 §7.1) ──
|
|
1423
|
+
// Post-handshake records (NewSessionTicket, KeyUpdate, or the client's
|
|
1424
|
+
// final flight again, its ACK lost): nothing answers them, so they are
|
|
1425
|
+
// ACKed — at the highest sending epoch (§7) — but not each at once: one
|
|
1426
|
+
// ACK a quarter of the timer later covers all that came meanwhile (§7.1,
|
|
1427
|
+
// "implementations SHOULD [...] delay sending ACKs"; a server's two
|
|
1428
|
+
// tickets, two records each, were four ACKs).
|
|
1429
|
+
if (wasConnectedOnEntry) {
|
|
1430
|
+
for (let ai = 0; ai < ackable.length; ai++) noteAck(ctx.postAcks, ackable[ai]);
|
|
1431
|
+
if (ctx.postAcks.length > 0 && ctx.postAckTimer === null) {
|
|
1432
|
+
ctx.postAckTimer = setTimeout(function () {
|
|
1433
|
+
ctx.postAckTimer = null;
|
|
1434
|
+
if (ctx.state === 'closed' || ctx.postAcks.length === 0) return;
|
|
1435
|
+
let all = ctx.postAcks; ctx.postAcks = [];
|
|
1436
|
+
sendAck(appEpoch(), all, true);
|
|
1437
|
+
}, ackDelay(currentRto()));
|
|
1438
|
+
if (ctx.postAckTimer.unref) ctx.postAckTimer.unref();
|
|
1439
|
+
}
|
|
1440
|
+
return;
|
|
1441
|
+
}
|
|
1442
|
+
for (let ai = 0; ai < ackable.length; ai++) noteAck(ctx.pendingAcks, ackable[ai]);
|
|
1443
|
+
if (ctx.pendingAcks.length === 0) return;
|
|
1444
|
+
|
|
1445
|
+
// The client's final flight, now complete (the handshake is): nothing of
|
|
1446
|
+
// ours answers it (a NewSessionTicket that follows is no answer — it is
|
|
1447
|
+
// checked first for that reason), so it is ACKed explicitly, at once — §7.1 "MUST send
|
|
1448
|
+
// ACKs upon receiving all of any flight which they do not respond to" —
|
|
1449
|
+
// at the highest sending epoch, now the application one (§7).
|
|
1450
|
+
if (isServer && ctx.state === 'connected') {
|
|
1451
|
+
let all = ctx.pendingAcks; clearPendingAcks();
|
|
1452
|
+
sendAck(appEpoch(), all, true);
|
|
1453
|
+
return;
|
|
1454
|
+
}
|
|
1455
|
+
// The flight is complete and we answered it: our flight is the ACK
|
|
1456
|
+
// (implicit). Sending one as well is what the RFC asks us not to do —
|
|
1457
|
+
// "ACKs SHOULD NOT be sent for these complete flights" — and what BoGo
|
|
1458
|
+
// saw: an ACK for every datagram of a flight, landing where the peer
|
|
1459
|
+
// does not expect one.
|
|
1460
|
+
if (ctx.flightMessagesQueued > sentBefore) { clearPendingAcks(); return; }
|
|
1461
|
+
|
|
1462
|
+
if (ctx.state !== 'handshaking') { clearPendingAcks(); return; }
|
|
1463
|
+
|
|
1464
|
+
// Part of a flight. A sign it is disrupted — a message or fragment out
|
|
1465
|
+
// of order — is ACKed now, so the peer resends only what is missing;
|
|
1466
|
+
// otherwise a timer of a quarter of the retransmit timeout (§7.1's
|
|
1467
|
+
// suggestion) gives the rest of the flight time to arrive.
|
|
1468
|
+
if (ctx.ackDisruption) {
|
|
1469
|
+
cancelAckTimer();
|
|
1470
|
+
sendHandshakeAck();
|
|
1471
|
+
} else if (ctx.ackTimer === null) {
|
|
1472
|
+
ctx.ackTimer = setTimeout(function () {
|
|
1473
|
+
ctx.ackTimer = null;
|
|
1474
|
+
if (ctx.state === 'handshaking' && ctx.pendingAcks.length > 0) sendHandshakeAck();
|
|
1475
|
+
// A quarter of the retransmit timer (§7.1) — its initial value: the
|
|
1476
|
+
// timer's backoff counts losses of OUR earlier flight (a ClientHello
|
|
1477
|
+
// resent until the ServerHello came), which say nothing about how long
|
|
1478
|
+
// the rest of the peer's flight may take. Read off the backed-off timer
|
|
1479
|
+
// it was 2 s after three lost ClientHellos.
|
|
1480
|
+
}, ackDelay(Math.min(ctx.retransmitTimeout, currentRto())));
|
|
1481
|
+
if (ctx.ackTimer.unref) ctx.ackTimer.unref();
|
|
1043
1482
|
}
|
|
1044
1483
|
}
|
|
1045
1484
|
|
|
1485
|
+
// How long an ACK waits: a quarter of the retransmit timer (§7.1's
|
|
1486
|
+
// suggestion), a millisecond at least.
|
|
1487
|
+
function ackDelay(rto) { return Math.max(1, Math.floor(rto / 4)); }
|
|
1488
|
+
// A record to ACK, kept in `list`: the records of the peer's flight in
|
|
1489
|
+
// progress (pendingAcks), ACKed cumulatively — each ACK lists all of them
|
|
1490
|
+
// received so far (§7.1 "the messages from that flight which it has
|
|
1491
|
+
// received and processed so far") — or post-handshake ones (postAcks);
|
|
1492
|
+
// either way, up to what fits in one ACK record: its header (13), the
|
|
1493
|
+
// record numbers' length (2) and an AEAD tag's worth (22) out of the MTU,
|
|
1494
|
+
// 16 bytes a record number (RFC 9147 §7).
|
|
1495
|
+
function noteAck(list, rn) {
|
|
1496
|
+
for (let i = 0; i < list.length; i++) {
|
|
1497
|
+
if (list[i].epoch === rn.epoch && list[i].seq === rn.seq) return;
|
|
1498
|
+
}
|
|
1499
|
+
list.push(rn);
|
|
1500
|
+
let room = Math.max(1, Math.floor((ctx.mtu - 13 - 22 - 2) / 16));
|
|
1501
|
+
if (list.length > room) list.splice(0, list.length - room);
|
|
1502
|
+
}
|
|
1503
|
+
function cancelAckTimer() {
|
|
1504
|
+
if (ctx.ackTimer !== null) { clearTimeout(ctx.ackTimer); ctx.ackTimer = null; }
|
|
1505
|
+
}
|
|
1506
|
+
function clearPendingAcks() {
|
|
1507
|
+
cancelAckTimer();
|
|
1508
|
+
ctx.pendingAcks = [];
|
|
1509
|
+
if (ctx.postAckTimer !== null) { clearTimeout(ctx.postAckTimer); ctx.postAckTimer = null; }
|
|
1510
|
+
ctx.postAcks = [];
|
|
1511
|
+
}
|
|
1512
|
+
// During the handshake: the handshake epoch (2), never 0 — only protected
|
|
1513
|
+
// records are ever ACKed (the first flight never is).
|
|
1514
|
+
function sendHandshakeAck() {
|
|
1515
|
+
if (!ctx.keys[2]) return;
|
|
1516
|
+
sendAck(2, ctx.pendingAcks.slice(), false);
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1046
1519
|
function processRecord(record) {
|
|
1047
1520
|
// A record in an epoch above 0 that did not decrypt is not the peer's
|
|
1048
1521
|
// (forged, corrupted, or a late packet from a previous session on the
|
|
@@ -1058,6 +1531,23 @@ function DTLSSession(options) {
|
|
|
1058
1531
|
if (ctx.state === 'connected' && !record.encrypted &&
|
|
1059
1532
|
(record.type === CT.ALERT || record.type === CT.APPLICATION_DATA)) return;
|
|
1060
1533
|
|
|
1534
|
+
if (record.type !== CT.ALERT) ctx.consecutiveWarnings = 0;
|
|
1535
|
+
|
|
1536
|
+
// A record that opened under its keys is the peer's: more plaintext
|
|
1537
|
+
// than a record may carry — 2^14 (RFC 8446 §5.1, RFC 5246 §6.2.1), or
|
|
1538
|
+
// our record_size_limit once negotiated (RFC 8449 §4) — is
|
|
1539
|
+
// record_overflow, as in TLS. Without a limit agreed, nothing was
|
|
1540
|
+
// checked at all (BoGo LargePlaintext-DTLS). An unprotected one is
|
|
1541
|
+
// anyone's: what arrives too long is dropped as the datagram is parsed
|
|
1542
|
+
// (record.js).
|
|
1543
|
+
if (record.encrypted && record.content) {
|
|
1544
|
+
let rsl = tls.getRecordSizeLimit(), max = rsl ? rsl.receive : MAX_RECORD_PLAINTEXT;
|
|
1545
|
+
if (record.content.length > max) {
|
|
1546
|
+
throw parseError('DTLS record of ' + record.content.length + ' bytes, more than ' +
|
|
1547
|
+
(rsl ? 'our record_size_limit (' + max + ')' : '2^14'), TLS_ALERT.RECORD_OVERFLOW);
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1061
1551
|
if (record.type === CT.HANDSHAKE) {
|
|
1062
1552
|
// An epoch>0 record that could not be decrypted (keys not yet
|
|
1063
1553
|
// installed, AEAD failure) must NOT be fed to the handshake
|
|
@@ -1074,39 +1564,81 @@ function DTLSSession(options) {
|
|
|
1074
1564
|
processHandshakeRecord(record.content, record.epoch, record.encrypted);
|
|
1075
1565
|
} else if (record.type === CT.APPLICATION_DATA) {
|
|
1076
1566
|
if (ctx.state === 'connected') {
|
|
1077
|
-
|
|
1567
|
+
// DTLS 1.2: protected data from the peer means it has our final
|
|
1568
|
+
// flight — nothing more to answer its retransmissions with.
|
|
1569
|
+
//
|
|
1570
|
+
// NOT DTLS 1.3's final flight (finalFlightPending): that one is
|
|
1571
|
+
// acknowledged by an ACK alone (RFC 9147 §7.1: it is the one flight
|
|
1572
|
+
// not implicitly acknowledged, and "flights MUST be ACKed unless
|
|
1573
|
+
// they are implicitly acknowledged"). A server sends data before it
|
|
1574
|
+
// has our Finished — half-RTT data, its tickets — so its data said
|
|
1575
|
+
// nothing of our Finished, and taken for it, a lost Finished was
|
|
1576
|
+
// never resent: the server's handshake never ended (BoGo
|
|
1577
|
+
// DTLS-Retransmit-Client-NoImplictACKFinished). A peer that never
|
|
1578
|
+
// ACKs costs only the retransmissions: when they run out the flight
|
|
1579
|
+
// is let go, the connection kept (startRetransmitTimer).
|
|
1580
|
+
ctx.lastFlight12 = null;
|
|
1581
|
+
// A Buffer, as node:tls gives (and every other buffer this API hands
|
|
1582
|
+
// out): a view of the decrypted record, not a copy.
|
|
1583
|
+
let c = record.content;
|
|
1584
|
+
ev.emit('data', Buffer.isBuffer(c) ? c : Buffer.from(c.buffer, c.byteOffset, c.byteLength));
|
|
1078
1585
|
}
|
|
1079
1586
|
} else if (record.type === CT.CHANGE_CIPHER_SPEC) {
|
|
1080
|
-
//
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1587
|
+
// One byte, 1 (RFC 6347 §4.1, RFC 5246 §7.1), and DTLS 1.2 only — DTLS
|
|
1588
|
+
// 1.3 has none (RFC 9147 §5: not sent, and none is a key change here).
|
|
1589
|
+
// Anything else changed our read state all the same. It is an
|
|
1590
|
+
// unprotected record that does not read as one: dropped (§4.1.2.7).
|
|
1591
|
+
if (ctx.selectedVersion === DTLS_VERSION.DTLS1_3) return;
|
|
1592
|
+
if (!record.content || record.content.length !== 1 || record.content[0] !== 1) return;
|
|
1593
|
+
// DTLS 1.2: the peer's key change. The keys come of the master
|
|
1594
|
+
// secret, and are installed when it exists (installKeys12, after each
|
|
1595
|
+
// record) — a CCS ahead of the key exchange (reordered: RFC 6347 §4.1),
|
|
1596
|
+
// or one lost, changes nothing: no alert that would end a good
|
|
1597
|
+
// handshake on one reordered datagram, and no flight resent for it
|
|
1598
|
+
// either (DTLS-Fuzzer D5). A Finished must still come protected
|
|
1599
|
+
// (epochFitsHandshake): the key change is proved by it, not by this.
|
|
1600
|
+
installKeys12();
|
|
1093
1601
|
} else if (record.type === CT.ACK) {
|
|
1094
|
-
|
|
1602
|
+
// DTLS 1.3 only (RFC 9147 §7). One that does not parse: protected, the
|
|
1603
|
+
// peer's (decode_error, thrown to the boundary); unprotected, dropped.
|
|
1604
|
+
if (ctx.selectedVersion !== DTLS_VERSION.DTLS1_3) return;
|
|
1605
|
+
let acks;
|
|
1606
|
+
try { acks = parseDtlsAck(record.content); }
|
|
1607
|
+
catch (e) { if (!record.encrypted) return; throw e; }
|
|
1608
|
+
processAck(acks);
|
|
1095
1609
|
} else if (record.type === CT.ALERT) {
|
|
1096
|
-
|
|
1097
|
-
|
|
1610
|
+
// As TLS over TCP reads it (wire.read_peer_alert). A malformed one:
|
|
1611
|
+
// protected, the alert for it; unprotected — anyone can send a
|
|
1612
|
+
// datagram — dropped.
|
|
1613
|
+
let a = read_peer_alert(record.content, ctx.selectedVersion === DTLS_VERSION.DTLS1_3);
|
|
1614
|
+
if (a.error) {
|
|
1615
|
+
if (!record.encrypted) return;
|
|
1616
|
+
throw parseError('DTLS ' + a.error.message, a.error.alert);
|
|
1617
|
+
}
|
|
1618
|
+
let desc = a.desc;
|
|
1098
1619
|
// Name the alert either way. A close_notify is a CLEAN shutdown and
|
|
1099
1620
|
// a failure is not, but both present to the application as "the
|
|
1100
1621
|
// connection ended" — and telling them apart is the difference
|
|
1101
1622
|
// between "the peer hung up" and "the peer rejected us".
|
|
1102
|
-
_ldbg('RX Alert level=' + level + ' desc=' + desc +
|
|
1103
|
-
(desc ===
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1623
|
+
_ldbg('RX Alert level=' + a.level + ' desc=' + desc +
|
|
1624
|
+
(desc === TLS_ALERT.CLOSE_NOTIFY ? ' (close_notify — clean shutdown)' : ' (failure)'));
|
|
1625
|
+
ev.emit('alert', { level: a.level, description: desc });
|
|
1626
|
+
if (desc === TLS_ALERT.CLOSE_NOTIFY) {
|
|
1627
|
+
// close_notify — answered with ours (RFC 5246 §7.2.1: "The other
|
|
1628
|
+
// party MUST respond with a close_notify alert of its own"; RFC 8446
|
|
1629
|
+
// §6.1 the same, in effect: each side closes its write side with
|
|
1630
|
+
// one). close() sends it and ends the session. It was not answered.
|
|
1631
|
+
close();
|
|
1632
|
+
} else if (!a.fatal) {
|
|
1633
|
+
// A warning (user_canceled among them): the connection goes on. A
|
|
1634
|
+
// run of them is not progress.
|
|
1635
|
+
if (++ctx.consecutiveWarnings > MAX_WARNING_ALERTS) {
|
|
1636
|
+
throw parseError('DTLS: too many consecutive warning alerts', TLS_ALERT.UNEXPECTED_MESSAGE);
|
|
1637
|
+
}
|
|
1108
1638
|
} else {
|
|
1109
|
-
|
|
1639
|
+
// Fatal: the session ends; it used to report and carry on,
|
|
1640
|
+
// retransmitting.
|
|
1641
|
+
fail(peer_alert_error(desc)); // as TLS over TCP reports it
|
|
1110
1642
|
}
|
|
1111
1643
|
}
|
|
1112
1644
|
}
|
|
@@ -1119,8 +1651,25 @@ function DTLSSession(options) {
|
|
|
1119
1651
|
let off = 0;
|
|
1120
1652
|
let sawMessage = false; // record contained ≥1 parseable handshake message
|
|
1121
1653
|
let anyNew = false; // ≥1 message advanced our state (not a duplicate)
|
|
1122
|
-
|
|
1123
|
-
|
|
1654
|
+
// A handshake fragment lies wholly inside its record (RFC 6347 §4.2.3,
|
|
1655
|
+
// RFC 9147 §5.5: fragments are not split across records). One that does
|
|
1656
|
+
// not — its 12-byte header cut short, or its body running past the end
|
|
1657
|
+
// — is decode_error: the record itself read fine, the handshake inside
|
|
1658
|
+
// it is malformed (§4.5.2's silent discard is for records that do not
|
|
1659
|
+
// read as records). Dropping it left the session waiting for good (BoGo
|
|
1660
|
+
// SplitFragments-Header/Body/Boundary). Except for an unprotected record
|
|
1661
|
+
// once we are connected: then it can only be a stray retransmission or
|
|
1662
|
+
// anyone's (spoofed) datagram, and one must not end the connection — it
|
|
1663
|
+
// is dropped, as an unprotected ACK or alert is.
|
|
1664
|
+
let strict = encrypted || ctx.state !== 'connected';
|
|
1665
|
+
while (off < data.length) {
|
|
1666
|
+
if (data.length - off < 12) {
|
|
1667
|
+
if (!strict) return;
|
|
1668
|
+
throw parseError('DTLS: a handshake fragment header cut short (' + (data.length - off) + ' bytes) at the end of its record', TLS_ALERT.DECODE_ERROR);
|
|
1669
|
+
}
|
|
1670
|
+
let parsed;
|
|
1671
|
+
try { parsed = parse_dtls_handshake(data.subarray(off)); } // decode_error when the body runs past
|
|
1672
|
+
catch (e) { if (!strict) return; throw e; }
|
|
1124
1673
|
|
|
1125
1674
|
let msgSeq = parsed.msg_seq;
|
|
1126
1675
|
let totalLen = parsed.length;
|
|
@@ -1134,10 +1683,10 @@ function DTLSSession(options) {
|
|
|
1134
1683
|
// Is this a complete message or a fragment?
|
|
1135
1684
|
if (fragOffset === 0 && fragLen === totalLen) {
|
|
1136
1685
|
// Complete message — feed directly
|
|
1137
|
-
if (deliverHandshakeMessage(parsed.type, parsed.body, msgSeq)) anyNew = true;
|
|
1686
|
+
if (deliverHandshakeMessage(parsed.type, parsed.body, msgSeq, epoch)) anyNew = true;
|
|
1138
1687
|
} else {
|
|
1139
1688
|
// Fragment — reassemble
|
|
1140
|
-
if (reassembleFragment(parsed)) anyNew = true;
|
|
1689
|
+
if (reassembleFragment(parsed, epoch)) anyNew = true;
|
|
1141
1690
|
}
|
|
1142
1691
|
|
|
1143
1692
|
off += 12 + fragLen;
|
|
@@ -1162,28 +1711,42 @@ function DTLSSession(options) {
|
|
|
1162
1711
|
// of the flight we JUST sent, not on the acknowledged one. The
|
|
1163
1712
|
// cancel happens at the acceptance points inside
|
|
1164
1713
|
// deliverHandshakeMessage instead, before the feed.
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1714
|
+
// Whether this record carried only messages already taken — the
|
|
1715
|
+
// peer retransmitting — is decided for the whole datagram, in
|
|
1716
|
+
// feedDatagram (dtls12AnswerRetransmission): a datagram that also
|
|
1717
|
+
// brings something new is progress, not a retransmission.
|
|
1718
|
+
if (anyNew) ctx.dgAnyNew = true;
|
|
1719
|
+
else if (sawMessage) ctx.dgDuplicate = true;
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
/**
|
|
1723
|
+
* DTLS 1.2 (RFC 6347 §4.2.4): the peer retransmitting its last flight
|
|
1724
|
+
* means it never got our answer — we resend ours at once, rather than
|
|
1725
|
+
* waiting out our own timer. DTLS 1.3 has ACKs for this instead.
|
|
1726
|
+
*
|
|
1727
|
+
* After the handshake, too: the side that sent the LAST flight (the
|
|
1728
|
+
* server's CCS + Finished in a full handshake) keeps it, and resends it
|
|
1729
|
+
* when the peer's final flight comes again — the state machine's FINISHED
|
|
1730
|
+
* state. It was dropped at 'connect', so a lost server Finished left the
|
|
1731
|
+
* client retransmitting into a server that had nothing to say: the
|
|
1732
|
+
* handshake never completed (BoGo Retransmit-*-TLS12; and 1 in 7 at 30%
|
|
1733
|
+
* loss in dtls_loss_test). Kept until the peer's application data shows
|
|
1734
|
+
* it finished.
|
|
1735
|
+
*/
|
|
1736
|
+
function dtls12AnswerRetransmission() {
|
|
1737
|
+
if (ctx.selectedVersion === DTLS_VERSION.DTLS1_3) return;
|
|
1738
|
+
if (!ctx.dgDuplicate || ctx.dgAnyNew) return;
|
|
1739
|
+
// Damping: a peer retransmits its flight as SEVERAL datagrams; one
|
|
1740
|
+
// resend per 250 ms answers the whole retransmitted flight once.
|
|
1741
|
+
if ((Date.now() - (ctx._lastDupElicitedResend || 0)) < 250) return;
|
|
1742
|
+
if (ctx.state === 'handshaking' && haveFlight()) {
|
|
1177
1743
|
ctx._lastDupElicitedResend = Date.now();
|
|
1178
|
-
//
|
|
1179
|
-
//
|
|
1180
|
-
// got our reply. Resend it immediately (RFC 6347 §4.2.4 responder
|
|
1181
|
-
// behavior) rather than waiting out our own backoff. The regular
|
|
1182
|
-
// timer keeps running unchanged, so the retransmit budget still
|
|
1183
|
-
// bounds the total effort; the resend rate is naturally bounded by
|
|
1184
|
-
// the peer's own retransmission schedule. DTLS 1.3 is excluded —
|
|
1185
|
-
// it uses explicit ACKs (RFC 9147 §7) instead of flight inference.
|
|
1744
|
+
// The regular timer keeps running unchanged, so the retransmit budget
|
|
1745
|
+
// still bounds the total effort.
|
|
1186
1746
|
resendFlight();
|
|
1747
|
+
} else if (ctx.state === 'connected' && ctx.lastFlight12) {
|
|
1748
|
+
ctx._lastDupElicitedResend = Date.now();
|
|
1749
|
+
transmitUnacked(ctx.lastFlight12, 'lastFlight12', false);
|
|
1187
1750
|
}
|
|
1188
1751
|
}
|
|
1189
1752
|
|
|
@@ -1197,7 +1760,7 @@ function DTLSSession(options) {
|
|
|
1197
1760
|
* merged byte-ranges, rather than requiring each fragment to start exactly
|
|
1198
1761
|
* where the previous one ended.
|
|
1199
1762
|
*/
|
|
1200
|
-
function reassembleFragment(parsed) {
|
|
1763
|
+
function reassembleFragment(parsed, epoch) {
|
|
1201
1764
|
let key = parsed.msg_seq;
|
|
1202
1765
|
|
|
1203
1766
|
// Ignore fragments for messages we've already delivered.
|
|
@@ -1207,14 +1770,28 @@ function DTLSSession(options) {
|
|
|
1207
1770
|
// fragment. Reject anything claiming more than our cap BEFORE allocating,
|
|
1208
1771
|
// so a peer can't force huge buffers with a single unauthenticated packet.
|
|
1209
1772
|
if (parsed.length > ctx.maxHandshakeMessageSize) {
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1773
|
+
// It was reported and the session carried on, waiting for a message
|
|
1774
|
+
// it had refused. The same alert as TLS over TCP for an oversized
|
|
1775
|
+
// message (illegal_parameter, as OpenSSL).
|
|
1776
|
+
throw parseError('DTLS handshake message of ' + parsed.length + ' bytes, more than ' + ctx.maxHandshakeMessageSize, TLS_ALERT.ILLEGAL_PARAMETER);
|
|
1213
1777
|
}
|
|
1214
1778
|
|
|
1215
1779
|
let frag = ctx.fragments[key];
|
|
1780
|
+
// Every fragment of one message names the same type and total length
|
|
1781
|
+
// (RFC 6347 §4.2.3, RFC 9147 §5.5). One that disagrees with those
|
|
1782
|
+
// already held was copied in anyway — or, past the buffer, dropped — and
|
|
1783
|
+
// the message never completed: the session waited for good.
|
|
1784
|
+
if (frag && frag.epoch !== epoch) {
|
|
1785
|
+
// Half a message under one key and half under another (RFC 8446
|
|
1786
|
+
// §5.1: handshake messages MUST NOT span key changes).
|
|
1787
|
+
throw parseError('DTLS: message ' + key + ' in fragments of epochs ' + frag.epoch + ' and ' + epoch, TLS_ALERT.UNEXPECTED_MESSAGE);
|
|
1788
|
+
}
|
|
1789
|
+
if (frag && (frag.type !== parsed.type || frag.totalLength !== parsed.length)) {
|
|
1790
|
+
throw parseError('DTLS: fragments of message ' + key + ' disagree on its type or length', TLS_ALERT.ILLEGAL_PARAMETER);
|
|
1791
|
+
}
|
|
1216
1792
|
if (!frag) {
|
|
1217
1793
|
frag = ctx.fragments[key] = {
|
|
1794
|
+
epoch: epoch,
|
|
1218
1795
|
totalLength: parsed.length,
|
|
1219
1796
|
type: parsed.type,
|
|
1220
1797
|
buf: new Uint8Array(parsed.length),
|
|
@@ -1225,8 +1802,11 @@ function DTLSSession(options) {
|
|
|
1225
1802
|
let start = parsed.frag_offset;
|
|
1226
1803
|
let end = parsed.frag_offset + parsed.frag_length;
|
|
1227
1804
|
|
|
1228
|
-
//
|
|
1229
|
-
|
|
1805
|
+
// A fragment past the message's end (parse_dtls_handshake already holds
|
|
1806
|
+
// the body to frag_length; this is offset + length > the total).
|
|
1807
|
+
if (end > frag.totalLength) {
|
|
1808
|
+
throw parseError('DTLS: fragment ' + start + '+' + parsed.frag_length + ' runs past message length ' + frag.totalLength, TLS_ALERT.ILLEGAL_PARAMETER);
|
|
1809
|
+
}
|
|
1230
1810
|
|
|
1231
1811
|
// Coverage before this fragment — used to decide whether it contributed
|
|
1232
1812
|
// anything new (a retransmitted fragment that only re-covers known bytes
|
|
@@ -1259,10 +1839,13 @@ function DTLSSession(options) {
|
|
|
1259
1839
|
}
|
|
1260
1840
|
|
|
1261
1841
|
|
|
1842
|
+
// A fragment past a hole: one before it is missing (§7.1).
|
|
1843
|
+
if (merged.length > 1 || merged[0][0] !== 0) ctx.ackDisruption = true;
|
|
1844
|
+
|
|
1262
1845
|
// Complete when a single range covers [0, totalLength).
|
|
1263
1846
|
if (merged.length === 1 && merged[0][0] === 0 && merged[0][1] === frag.totalLength) {
|
|
1264
1847
|
delete ctx.fragments[key];
|
|
1265
|
-
return deliverHandshakeMessage(frag.type, frag.buf, key);
|
|
1848
|
+
return deliverHandshakeMessage(frag.type, frag.buf, key, frag.epoch);
|
|
1266
1849
|
}
|
|
1267
1850
|
return coveredAfter > coveredBefore;
|
|
1268
1851
|
}
|
|
@@ -1277,12 +1860,19 @@ function DTLSSession(options) {
|
|
|
1277
1860
|
* dropped. processHandshakeRecord uses this for the implicit-ACK
|
|
1278
1861
|
* decision — only genuinely new data may cancel our retransmit timer.
|
|
1279
1862
|
*/
|
|
1280
|
-
function deliverHandshakeMessage(type, body, msgSeq) {
|
|
1863
|
+
function deliverHandshakeMessage(type, body, msgSeq, epoch) {
|
|
1281
1864
|
// Skip if we've already processed this msg_seq (duplicate / retransmit).
|
|
1282
1865
|
if (msgSeq < ctx.nextReadMsgSeq) return false;
|
|
1283
1866
|
|
|
1284
1867
|
// Check for HelloVerifyRequest (DTLS 1.2 server→client, type=3)
|
|
1285
1868
|
if (type === 3 && !isServer) {
|
|
1869
|
+
// Only where one can come (RFC 6347 §4.2.1): unprotected, before any
|
|
1870
|
+
// ServerHello, as the answer to a ClientHello (message_seq 0 or 1).
|
|
1871
|
+
// Anywhere else it is no server's — and it was taken anywhere: one
|
|
1872
|
+
// datagram, after our last flight or after the handshake, made the
|
|
1873
|
+
// client start over (a new ClientHello, transcript and flights wiped).
|
|
1874
|
+
// Dropped (D2).
|
|
1875
|
+
if (epoch !== 0 || tls.context.remote_random != null || msgSeq > 1) return false;
|
|
1286
1876
|
ctx.nextReadMsgSeq = msgSeq + 1;
|
|
1287
1877
|
// The HVR acknowledges CH1 (implicit ACK, RFC 6347 §4.2.4). No
|
|
1288
1878
|
// explicit cancel is needed: triggerClientHelloWithCookie arms the
|
|
@@ -1327,10 +1917,9 @@ function DTLSSession(options) {
|
|
|
1327
1917
|
// 'illegal_parameter' alert." DTLS 1.3 carries cookies in the `cookie`
|
|
1328
1918
|
// EXTENSION; the legacy body field is vestigial and must stay empty.
|
|
1329
1919
|
if (offersDtls13 && ch && ch.dtls_cookie && ch.dtls_cookie.length > 0) {
|
|
1330
|
-
|
|
1331
|
-
ev.emit('error', new Error(
|
|
1920
|
+
fail(new Error(
|
|
1332
1921
|
'DTLS 1.3 ClientHello carries a non-empty legacy_cookie (' +
|
|
1333
|
-
ch.dtls_cookie.length + ' bytes) — RFC 9147 §5.3 requires zero length'));
|
|
1922
|
+
ch.dtls_cookie.length + ' bytes) — RFC 9147 §5.3 requires zero length'), 47);
|
|
1334
1923
|
return false;
|
|
1335
1924
|
}
|
|
1336
1925
|
|
|
@@ -1339,7 +1928,12 @@ function DTLSSession(options) {
|
|
|
1339
1928
|
// does return-routability with HelloRetryRequest + the cookie extension
|
|
1340
1929
|
// instead. The old guard tested ctx.selectedVersion, which is null here,
|
|
1341
1930
|
// so a DTLS 1.3 handshake could still be answered with an HVR.
|
|
1342
|
-
|
|
1931
|
+
// Only for the handshake's first ClientHello (nothing read yet): a
|
|
1932
|
+
// later one with a valid cookie — the attacker's replay of the real
|
|
1933
|
+
// one — reset the read cursor mid-handshake (or after it) and stalled
|
|
1934
|
+
// it without a word (D3). Later, a ClientHello goes the normal way:
|
|
1935
|
+
// in order, to the TLS state machine, which refuses it.
|
|
1936
|
+
if (options.useCookies === true && !offersDtls13 && ctx.nextReadMsgSeq === 0) {
|
|
1343
1937
|
|
|
1344
1938
|
let incomingCookie = (ch && ch.dtls_cookie) ? ch.dtls_cookie : new Uint8Array(0);
|
|
1345
1939
|
|
|
@@ -1388,9 +1982,12 @@ function DTLSSession(options) {
|
|
|
1388
1982
|
// and wait for the gap to fill. If it's the next expected one, feed it, then
|
|
1389
1983
|
// drain any consecutive messages that were buffered earlier.
|
|
1390
1984
|
if (msgSeq > ctx.nextReadMsgSeq) {
|
|
1985
|
+
// Out of order: a message is missing before it — the peer should
|
|
1986
|
+
// learn now what arrived (an ACK, RFC 9147 §7.1).
|
|
1987
|
+
ctx.ackDisruption = true;
|
|
1391
1988
|
// Out of order — hold it (ignore a duplicate already buffered).
|
|
1392
1989
|
if (!(msgSeq in ctx.pendingMessages)) {
|
|
1393
|
-
ctx.pendingMessages[msgSeq] = { type: type, body: body };
|
|
1990
|
+
ctx.pendingMessages[msgSeq] = { type: type, body: body, epoch: epoch };
|
|
1394
1991
|
return true; // new information, even if not yet deliverable
|
|
1395
1992
|
}
|
|
1396
1993
|
return false; // duplicate of an already-buffered message
|
|
@@ -1421,6 +2018,8 @@ function DTLSSession(options) {
|
|
|
1421
2018
|
// When the feed does respond, the flight boundary (newFlightPending
|
|
1422
2019
|
// → startNewFlight) resets the retransmit budget and the outgoing
|
|
1423
2020
|
// path arms the new flight's timer, superseding this re-arm.
|
|
2021
|
+
if (!epochFitsHandshake(epoch, type)) return false;
|
|
2022
|
+
flightAnswered();
|
|
1424
2023
|
startRetransmitTimer();
|
|
1425
2024
|
ctx.newFlightPending = true;
|
|
1426
2025
|
feedHandshakeToTls(type, body, msgSeq);
|
|
@@ -1435,6 +2034,9 @@ function DTLSSession(options) {
|
|
|
1435
2034
|
while (ctx.nextReadMsgSeq in ctx.pendingMessages) {
|
|
1436
2035
|
let next = ctx.pendingMessages[ctx.nextReadMsgSeq];
|
|
1437
2036
|
delete ctx.pendingMessages[ctx.nextReadMsgSeq];
|
|
2037
|
+
// Held while a message before it was missing — and that message may
|
|
2038
|
+
// have changed the keys (a ServerHello): checked now, not when held.
|
|
2039
|
+
if (!epochFitsHandshake(next.epoch, next.type)) break;
|
|
1438
2040
|
startRetransmitTimer();
|
|
1439
2041
|
ctx.newFlightPending = true;
|
|
1440
2042
|
feedHandshakeToTls(next.type, next.body, ctx.nextReadMsgSeq);
|
|
@@ -1443,11 +2045,60 @@ function DTLSSession(options) {
|
|
|
1443
2045
|
return true;
|
|
1444
2046
|
}
|
|
1445
2047
|
|
|
2048
|
+
/**
|
|
2049
|
+
* DTLS 1.3: the epoch a handshake message must arrive in is the one the
|
|
2050
|
+
* handshake is at when it is taken — 0 up to the ServerHello, 2 (the
|
|
2051
|
+
* handshake keys) up to the peer's Finished, 3 and up after it (RFC 9147
|
|
2052
|
+
* §6.1, RFC 8446 §5.1). A message held out of order was checked when it
|
|
2053
|
+
* arrived, before the message ahead of it moved the keys: a plaintext
|
|
2054
|
+
* message buffered behind a missing ServerHello was then taken as if it
|
|
2055
|
+
* had come encrypted (BoGo: a key change with messages buffered).
|
|
2056
|
+
* Unprotected, it may be anyone's, and is dropped (RFC 9147 §4.5.2) — the
|
|
2057
|
+
* real one, protected, takes its place; protected, it is the peer's own
|
|
2058
|
+
* doing: unexpected_message.
|
|
2059
|
+
*
|
|
2060
|
+
* DTLS 1.2 is not checked here: its key change is the peer's
|
|
2061
|
+
* ChangeCipherSpec, which may arrive ahead of the ClientKeyExchange it
|
|
2062
|
+
* depends on, and a protected message cannot be read before it anyway.
|
|
2063
|
+
*/
|
|
2064
|
+
function epochFitsHandshake(epoch, type) {
|
|
2065
|
+
if (typeof epoch !== 'number') return true;
|
|
2066
|
+
if (ctx.selectedVersion === DTLS_VERSION.DTLS1_2) {
|
|
2067
|
+
// DTLS 1.2 (RFC 6347 §4.1, RFC 5246 §7.1): Finished is the first
|
|
2068
|
+
// message under the new keys — epoch 1. One in epoch 0 was taken: the
|
|
2069
|
+
// handshake ended with no key change at all, the Finished in the clear
|
|
2070
|
+
// (DTLS-Fuzzer D1; the CCS-injection family). Epoch 1 is the proof of
|
|
2071
|
+
// the change — the record opened under the new keys — not the CCS
|
|
2072
|
+
// record itself, which can be lost while the Finished behind it
|
|
2073
|
+
// arrives (dtls_loss_test: requiring both failed a third of the
|
|
2074
|
+
// handshakes at 15% loss). Messages before it may still come in epoch
|
|
2075
|
+
// 0 after the CCS: reordered, a CertificateVerify can trail it.
|
|
2076
|
+
if (type === 20 && epoch !== 1) {
|
|
2077
|
+
throw parseError('DTLS 1.2: Finished in epoch ' + epoch + ' — it must come protected, after the key change', TLS_ALERT.UNEXPECTED_MESSAGE);
|
|
2078
|
+
}
|
|
2079
|
+
// After the handshake an unprotected handshake message is anyone's — a
|
|
2080
|
+
// HelloVerifyRequest or ClientHello sent to tear the connection down
|
|
2081
|
+
// (D2, D3): dropped. (Renegotiation would come protected, epoch 1.)
|
|
2082
|
+
if (epoch === 0 && ctx.state === 'connected') return false;
|
|
2083
|
+
return true;
|
|
2084
|
+
}
|
|
2085
|
+
if (ctx.selectedVersion !== DTLS_VERSION.DTLS1_3) return true;
|
|
2086
|
+
let hsKeys = !!(ctx.keys[2] && ctx.keys[2].read);
|
|
2087
|
+
let fits = !hsKeys ? epoch === 0 : (ctx.state === 'connected' ? epoch >= 3 : epoch === 2);
|
|
2088
|
+
if (fits) return true;
|
|
2089
|
+
if (epoch === 0) return false;
|
|
2090
|
+
throw parseError('DTLS 1.3: a handshake message in epoch ' + epoch + ', where epoch ' +
|
|
2091
|
+
(!hsKeys ? 0 : (ctx.state === 'connected' ? '3 or later' : 2)) + ' was due', TLS_ALERT.UNEXPECTED_MESSAGE);
|
|
2092
|
+
}
|
|
2093
|
+
|
|
1446
2094
|
/**
|
|
1447
2095
|
* Feed one in-order handshake message to the TLSSession, converting from DTLS
|
|
1448
2096
|
* to TLS wire format and driving version detection.
|
|
1449
2097
|
*/
|
|
1450
2098
|
function feedHandshakeToTls(type, body, msgSeq) {
|
|
2099
|
+
// DTLS 1.3's own post-handshake messages: the TLS engine has no part in them.
|
|
2100
|
+
if (type === TLS_MESSAGE_TYPE.REQUEST_CONNECTION_ID || type === TLS_MESSAGE_TYPE.NEW_CONNECTION_ID) { takeConnectionIdMessage(type, body); return; }
|
|
2101
|
+
|
|
1451
2102
|
// Build TLS-format message: type(1) + length(3) + body
|
|
1452
2103
|
let tlsMsg = build_message(type, body);
|
|
1453
2104
|
|
|
@@ -1466,6 +2117,54 @@ function DTLSSession(options) {
|
|
|
1466
2117
|
}
|
|
1467
2118
|
}
|
|
1468
2119
|
|
|
2120
|
+
/**
|
|
2121
|
+
* RequestConnectionId / NewConnectionId (RFC 9147 §5.8, §9): DTLS 1.3,
|
|
2122
|
+
* after the handshake, and only where CIDs were agreed to — a peer that
|
|
2123
|
+
* did not, or asked for an empty one, "MUST NOT send" them
|
|
2124
|
+
* (unexpected_message otherwise).
|
|
2125
|
+
*
|
|
2126
|
+
* NewConnectionId: CIDs to put in what we send. cid_immediate — the first
|
|
2127
|
+
* of them, from now on (§9: the peer "wants the recipient to use" one of
|
|
2128
|
+
* them at once); cid_spare — there for later: we have no reason to move,
|
|
2129
|
+
* and are not obliged to.
|
|
2130
|
+
* RequestConnectionId: "does not force the receiver to send a
|
|
2131
|
+
* NewConnectionId": we give out one CID, the one agreed, and keep it.
|
|
2132
|
+
* Both are ACKed, as any handshake record.
|
|
2133
|
+
*/
|
|
2134
|
+
function takeConnectionIdMessage(type, body) {
|
|
2135
|
+
let unexpected = function (why) { throw parseError(why, TLS_ALERT.UNEXPECTED_MESSAGE); };
|
|
2136
|
+
let malformed = function (why) { throw parseError(why, TLS_ALERT.DECODE_ERROR); };
|
|
2137
|
+
let name = type === TLS_MESSAGE_TYPE.NEW_CONNECTION_ID ? 'NewConnectionId' : 'RequestConnectionId';
|
|
2138
|
+
if (ctx.selectedVersion !== DTLS_VERSION.DTLS1_3 || ctx.state !== 'connected' || !ctx.cid) unexpected(name + ' without connection IDs agreed to');
|
|
2139
|
+
if (type === TLS_MESSAGE_TYPE.REQUEST_CONNECTION_ID) {
|
|
2140
|
+
if (body.length !== 1) malformed('RequestConnectionId is malformed');
|
|
2141
|
+
if (ctx.cid.local.length === 0) unexpected('RequestConnectionId, and we asked for no CID');
|
|
2142
|
+
return;
|
|
2143
|
+
}
|
|
2144
|
+
if (ctx.cid.peer.length === 0) unexpected('NewConnectionId from a peer that asked for no CID');
|
|
2145
|
+
if (body.length < 3) malformed('NewConnectionId is malformed');
|
|
2146
|
+
let listLen = (body[0] << 8) | body[1];
|
|
2147
|
+
if (2 + listLen + 1 !== body.length) malformed('NewConnectionId is malformed');
|
|
2148
|
+
let cids = [];
|
|
2149
|
+
for (let off = 2; off < 2 + listLen;) {
|
|
2150
|
+
let n = body[off];
|
|
2151
|
+
if (n === 0 || off + 1 + n > 2 + listLen) malformed('NewConnectionId: an empty or truncated CID');
|
|
2152
|
+
cids.push(body.slice(off + 1, off + 1 + n));
|
|
2153
|
+
off += 1 + n;
|
|
2154
|
+
}
|
|
2155
|
+
let usage = body[2 + listLen];
|
|
2156
|
+
if (usage > 1) throw parseError('NewConnectionId: usage ' + usage, TLS_ALERT.ILLEGAL_PARAMETER);
|
|
2157
|
+
if (usage === 0 && cids.length > 0) {
|
|
2158
|
+
ctx.cid = { local: ctx.cid.local, peer: new Uint8Array(cids[0]) };
|
|
2159
|
+
ev.emit('connectionId', getConnectionIds());
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
// The Connection IDs as they are now: { local, peer }, or null.
|
|
2164
|
+
function getConnectionIds() {
|
|
2165
|
+
return ctx.cid ? { local: new Uint8Array(ctx.cid.local), peer: new Uint8Array(ctx.cid.peer) } : null;
|
|
2166
|
+
}
|
|
2167
|
+
|
|
1469
2168
|
/**
|
|
1470
2169
|
* Server: build and send a HelloVerifyRequest with a fresh cookie (DTLS 1.2).
|
|
1471
2170
|
* The cookie is a CSPRNG value stored on the context; a matching cookie must
|
|
@@ -1479,8 +2178,7 @@ function DTLSSession(options) {
|
|
|
1479
2178
|
|
|
1480
2179
|
// HVR always uses msg_seq 0 and is sent in the clear (epoch 0).
|
|
1481
2180
|
let hvrMsg = build_dtls_handshake(build_message(3, build_hello_verify_request({ cookie: cookie })), 0);
|
|
1482
|
-
|
|
1483
|
-
_ldbgTx(record, 'L1194'); ev.emit('packet', record);
|
|
2181
|
+
queueOut({ kind: 'raw', epoch: 0, contentType: CT.HANDSHAKE, payload: hvrMsg, label: 'hvr' });
|
|
1484
2182
|
}
|
|
1485
2183
|
|
|
1486
2184
|
/**
|
|
@@ -1529,6 +2227,7 @@ function DTLSSession(options) {
|
|
|
1529
2227
|
// a wire message sent with seq 1 would fail Finished verification.
|
|
1530
2228
|
tls.context.message_sent_seq = 1;
|
|
1531
2229
|
ctx.currentFlight = [];
|
|
2230
|
+
ctx.flightQueued = 0;
|
|
1532
2231
|
ctx.fragments = {}; // discard any partial reassembly from CH1 flight
|
|
1533
2232
|
ctx.pendingMessages = {}; // discard any out-of-order buffer from CH1 flight
|
|
1534
2233
|
transcriptMsgSeqs = [];
|
|
@@ -1540,11 +2239,9 @@ function DTLSSession(options) {
|
|
|
1540
2239
|
tls.context.transcriptHook ? tls.context.transcriptHook(ch2) : ch2
|
|
1541
2240
|
);
|
|
1542
2241
|
|
|
1543
|
-
//
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
ctx.currentFlight = [{ epoch: 0, contentType: CT.HANDSHAKE, payload: dtlsMsg }];
|
|
1547
|
-
if (record) { _ldbgTx(record, 'L1258'); ev.emit('packet', record); }
|
|
2242
|
+
// Send — msg_seq 1 per the RFC figure above — as every flight message
|
|
2243
|
+
// goes: fragmented to the MTU, each fragment kept for retransmission.
|
|
2244
|
+
queueFlightMessage(ch2, 1, 0);
|
|
1548
2245
|
|
|
1549
2246
|
tls.context.message_sent_seq = 2;
|
|
1550
2247
|
startRetransmitTimer();
|
|
@@ -1574,17 +2271,50 @@ function DTLSSession(options) {
|
|
|
1574
2271
|
// payload never came from the application at all.
|
|
1575
2272
|
// RFC 9147 §5.8: retransmission is bounded by the handshake; after
|
|
1576
2273
|
// it, ACKs are the mechanism, not a timer.
|
|
1577
|
-
if (ctx.state === '
|
|
2274
|
+
if (ctx.state === 'closed' || (ctx.state === 'connected' && !ctx.finalFlightPending)) {
|
|
1578
2275
|
cancelRetransmit();
|
|
1579
2276
|
return;
|
|
1580
2277
|
}
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
2278
|
+
// DTLS 1.3 (RFC 9147 §7.1): records the peer has ACKed are not resent.
|
|
2279
|
+
ctx.flightResent = true; // no RTT sample from it now (Karn)
|
|
2280
|
+
transmitUnacked(ctx.currentFlight, 'flight-rtx', false);
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2283
|
+
/**
|
|
2284
|
+
* (Re)send the entries of a flight or post-handshake message that are not
|
|
2285
|
+
* ACKed yet — in new records, with new numbers (flushOut). `skipPlaintext`:
|
|
2286
|
+
* not the epoch-0 ones.
|
|
2287
|
+
*/
|
|
2288
|
+
function transmitUnacked(entries, label, skipPlaintext) {
|
|
2289
|
+
for (let i = 0; i < entries.length; i++) {
|
|
2290
|
+
let f = entries[i];
|
|
2291
|
+
if (f.acked || (skipPlaintext && f.epoch === 0)) continue;
|
|
2292
|
+
queueOut({ kind: 'entry', entry: f, label: label });
|
|
1585
2293
|
}
|
|
1586
2294
|
}
|
|
1587
2295
|
|
|
2296
|
+
// A record number (RFC 9147 §4: epoch, sequence number) as one key — the
|
|
2297
|
+
// way sent records and ACKs are matched up. A string: epoch * 2^48 + seq
|
|
2298
|
+
// stops being exact past epoch 31 (2^53), which KeyUpdates reach, and two
|
|
2299
|
+
// records' numbers would collide.
|
|
2300
|
+
function recordNumber(epoch, seq) { return epoch + '/' + seq; }
|
|
2301
|
+
|
|
2302
|
+
// Entries (of a flight, or of a post-handshake message) an ACK covers:
|
|
2303
|
+
// any of the records an entry went out in, its first sending or a
|
|
2304
|
+
// retransmission, is in `got`.
|
|
2305
|
+
// Returns how many it marked now.
|
|
2306
|
+
function markAcked(entries, got) {
|
|
2307
|
+
let newly = 0;
|
|
2308
|
+
for (let i = 0; i < entries.length; i++) {
|
|
2309
|
+
let e = entries[i];
|
|
2310
|
+
if (e.acked || !e.sent) continue;
|
|
2311
|
+
for (let k = 0; k < e.sent.length; k++) {
|
|
2312
|
+
if (got.has(e.sent[k])) { e.acked = true; newly++; break; }
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
return newly;
|
|
2316
|
+
}
|
|
2317
|
+
|
|
1588
2318
|
function startRetransmitTimer() {
|
|
1589
2319
|
cancelRetransmit();
|
|
1590
2320
|
// ONE GATE, AT THE SOURCE. Four call sites arm this timer and only
|
|
@@ -1593,12 +2323,17 @@ function DTLSSession(options) {
|
|
|
1593
2323
|
// flight into the application-data epoch. Refusing to arm once
|
|
1594
2324
|
// connected (or closed) covers every caller, including any added
|
|
1595
2325
|
// later, without each having to remember the rule.
|
|
1596
|
-
if (ctx.state === '
|
|
2326
|
+
if (ctx.state === 'closed' || (ctx.state === 'connected' && !ctx.finalFlightPending)) return;
|
|
2327
|
+
if (ctx.flightBegan === null) ctx.flightBegan = Date.now();
|
|
1597
2328
|
ctx.retransmitTimer = setTimeout(function() {
|
|
1598
|
-
if (ctx.
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
2329
|
+
if (ctx.finalFlightPending && exhausted(ctx.retransmitCount, ctx.flightBegan)) {
|
|
2330
|
+
// The connection is up on our side; stop resending without failing it.
|
|
2331
|
+
finalFlightDone();
|
|
2332
|
+
return;
|
|
2333
|
+
}
|
|
2334
|
+
if (exhausted(ctx.retransmitCount, ctx.flightBegan)) {
|
|
2335
|
+
fail(new Error('DTLS handshake timeout — no answer after ' + ctx.retransmitCount +
|
|
2336
|
+
' retransmissions in ' + Math.round((Date.now() - ctx.flightBegan) / 1000) + ' s'));
|
|
1602
2337
|
return;
|
|
1603
2338
|
}
|
|
1604
2339
|
|
|
@@ -1613,6 +2348,13 @@ function DTLSSession(options) {
|
|
|
1613
2348
|
if (ctx.retransmitTimer.unref) ctx.retransmitTimer.unref();
|
|
1614
2349
|
}
|
|
1615
2350
|
|
|
2351
|
+
// The client's final flight is known to have arrived: stop resending it.
|
|
2352
|
+
function finalFlightDone() {
|
|
2353
|
+
ctx.finalFlightPending = false;
|
|
2354
|
+
cancelRetransmit();
|
|
2355
|
+
startNewFlight();
|
|
2356
|
+
}
|
|
2357
|
+
|
|
1616
2358
|
function cancelRetransmit() {
|
|
1617
2359
|
if (ctx.retransmitTimer !== null) {
|
|
1618
2360
|
clearTimeout(ctx.retransmitTimer);
|
|
@@ -1620,10 +2362,54 @@ function DTLSSession(options) {
|
|
|
1620
2362
|
}
|
|
1621
2363
|
}
|
|
1622
2364
|
|
|
2365
|
+
// Whether we have a flight out (or on its way out: its messages become
|
|
2366
|
+
// entries of ctx.currentFlight only when packed, at the end of the pass).
|
|
2367
|
+
// Asked of currentFlight alone, the client that had just queued its
|
|
2368
|
+
// Finished found nothing to keep under the timer — and a lost Finished
|
|
2369
|
+
// was never resent.
|
|
2370
|
+
function haveFlight() { return ctx.currentFlight.length > 0 || ctx.flightQueued > 0; }
|
|
2371
|
+
|
|
1623
2372
|
function startNewFlight() {
|
|
1624
2373
|
ctx.currentFlight = [];
|
|
2374
|
+
ctx.flightQueued = 0;
|
|
1625
2375
|
ctx.retransmitCount = 0;
|
|
1626
|
-
ctx.retransmitTimeout =
|
|
2376
|
+
ctx.retransmitTimeout = currentRto();
|
|
2377
|
+
ctx.flightBegan = null;
|
|
2378
|
+
ctx.rttStart = null;
|
|
2379
|
+
ctx.flightResent = false;
|
|
2380
|
+
}
|
|
2381
|
+
|
|
2382
|
+
// Where a timer starts: the initial value until there is a round-trip
|
|
2383
|
+
// sample, then SRTT + 4·RTTVAR (RFC 6298 §2) — not under RTO_MIN_MS, not
|
|
2384
|
+
// over 60 s. An initialRetransmitTimeout given (the option, or
|
|
2385
|
+
// setInitialRetransmitTimeout) is a schedule the caller chose: it is kept
|
|
2386
|
+
// — from it, doubling — and no round trip changes it (fixedRto).
|
|
2387
|
+
//
|
|
2388
|
+
// The floor was 50 ms. A loopback, or a peer that answers within the same
|
|
2389
|
+
// tick, measures a round trip of 0, and a real path that once measured
|
|
2390
|
+
// short then lost its next flight to a 50 ms timer. RFC 9147 sets no
|
|
2391
|
+
// floor; 100 ms is WebRTC's own initial timer. And a fixed schedule
|
|
2392
|
+
// matters to a peer that times itself by one (BoGo advances its clock by
|
|
2393
|
+
// BoringSSL's 400 ms, 800 ms …: an adaptive 50 ms timer fired several
|
|
2394
|
+
// times in each step).
|
|
2395
|
+
function currentRto() {
|
|
2396
|
+
if (ctx.fixedRto || ctx.srtt === null) return ctx.initialRetransmitTimeout;
|
|
2397
|
+
return Math.max(RTO_MIN_MS, Math.min(60000, Math.round(ctx.srtt + 4 * ctx.rttvar)));
|
|
2398
|
+
}
|
|
2399
|
+
function sampleRtt(r) {
|
|
2400
|
+
if (ctx.srtt === null) { ctx.srtt = r; ctx.rttvar = r / 2; }
|
|
2401
|
+
else { ctx.rttvar = 0.75 * ctx.rttvar + 0.25 * Math.abs(ctx.srtt - r); ctx.srtt = 0.875 * ctx.srtt + 0.125 * r; }
|
|
2402
|
+
}
|
|
2403
|
+
// The peer's answer to our flight has begun (a new message of its next
|
|
2404
|
+
// flight, or — DTLS 1.3 — an ACK of a protected record of ours).
|
|
2405
|
+
function flightAnswered() {
|
|
2406
|
+
if (ctx.rttStart !== null && !ctx.flightResent) sampleRtt(Date.now() - ctx.rttStart);
|
|
2407
|
+
ctx.rttStart = null;
|
|
2408
|
+
}
|
|
2409
|
+
// Time to give up on what keeps going unanswered: tried enough, and long
|
|
2410
|
+
// enough (maxRetransmits, giveUpAfter).
|
|
2411
|
+
function exhausted(attempts, began) {
|
|
2412
|
+
return attempts >= ctx.maxRetransmits && began !== null && Date.now() - began >= ctx.giveUpAfter;
|
|
1627
2413
|
}
|
|
1628
2414
|
|
|
1629
2415
|
|
|
@@ -1631,25 +2417,81 @@ function DTLSSession(options) {
|
|
|
1631
2417
|
// ACK (DTLS 1.3)
|
|
1632
2418
|
// ============================================================
|
|
1633
2419
|
|
|
1634
|
-
function processAck(
|
|
1635
|
-
let
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
//
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
2420
|
+
function processAck(acks) {
|
|
2421
|
+
let got = new Set();
|
|
2422
|
+
for (let i = 0; i < acks.length; i++) got.add(recordNumber(acks[i].epoch, acks[i].seq));
|
|
2423
|
+
|
|
2424
|
+
// Post-handshake messages first — a KeyUpdate's epoch switch among them.
|
|
2425
|
+
if (ctx.postHandshake.length > 0) ackPostHandshake(got);
|
|
2426
|
+
if (ctx.state === 'closed' || (ctx.state === 'connected' && !ctx.finalFlightPending)) return;
|
|
2427
|
+
|
|
2428
|
+
// During the handshake an ACK may cover only PART of our flight (RFC 9147
|
|
2429
|
+
// §7.1): the peer lists the records it received. Those are done; the
|
|
2430
|
+
// rest stay under the retransmit timer and are what the next
|
|
2431
|
+
// retransmission resends. Treating any ACK as acknowledging the whole
|
|
2432
|
+
// flight cancelled the timer while a record was still missing — the
|
|
2433
|
+
// peer, waiting for it, ACKed again and again, and the handshake hung
|
|
2434
|
+
// (≈ half of all handshakes at 5% loss, when the lost record was inside
|
|
2435
|
+
// the server's flight).
|
|
2436
|
+
if ((ctx.state !== 'connected' || ctx.finalFlightPending) && ctx.currentFlight.length > 0) {
|
|
2437
|
+
let newly = markAcked(ctx.currentFlight, got);
|
|
2438
|
+
if (newly > 0) flightAnswered();
|
|
2439
|
+
let remaining = 0, protectedEntries = 0, protectedAcked = 0;
|
|
2440
|
+
for (let i = 0; i < ctx.currentFlight.length; i++) {
|
|
2441
|
+
let f = ctx.currentFlight[i];
|
|
2442
|
+
// Plaintext epoch-0 records (ClientHello, ServerHello) are never
|
|
2443
|
+
// ACKed — the peer acknowledges them implicitly by answering — so
|
|
2444
|
+
// they do not keep the flight open on their own.
|
|
2445
|
+
if (f.epoch === 0) continue;
|
|
2446
|
+
protectedEntries++;
|
|
2447
|
+
if (f.acked) protectedAcked++; else remaining++;
|
|
2448
|
+
}
|
|
2449
|
+
// A flight of plaintext alone (a ClientHello) is answered, never
|
|
2450
|
+
// ACKed: an ACK — an empty one, from a peer that could not read
|
|
2451
|
+
// something of ours — confirms none of it, and ended its
|
|
2452
|
+
// retransmission (the timer cancelled below), the ClientHello lost
|
|
2453
|
+
// for good.
|
|
2454
|
+
if (protectedEntries === 0) return;
|
|
2455
|
+
if (remaining > 0) {
|
|
2456
|
+
// RFC 9147 §7.2: "Upon receipt of an ACK that leaves it with only
|
|
2457
|
+
// some messages from a flight having been acknowledged, an
|
|
2458
|
+
// implementation SHOULD retransmit the unacknowledged messages or
|
|
2459
|
+
// fragments." The peer ACKs a partial flight when it sees a gap, or
|
|
2460
|
+
// after a quarter of its timer with the rest still missing — either
|
|
2461
|
+
// way the rest is lost, and waiting for our own timer (doubled by
|
|
2462
|
+
// every earlier loss: 8 s and more) stalled the handshake past its
|
|
2463
|
+
// deadline (dtls_loss_test, 30% loss).
|
|
2464
|
+
//
|
|
2465
|
+
// At once when this ACK confirmed something new. One that confirms
|
|
2466
|
+
// nothing new — a repeat, or an empty one (wolfSSL sends one for
|
|
2467
|
+
// each record it cannot read: with our ServerHello lost, all of
|
|
2468
|
+
// them) — at most once a quarter of the initial timer: resending on
|
|
2469
|
+
// each, both sides fed each other 15 000–34 000 datagrams a second.
|
|
2470
|
+
// Epoch-0 records go too while the peer has ACKed none of the
|
|
2471
|
+
// protected ones — it may lack our ServerHello, and so the keys;
|
|
2472
|
+
// once it has ACKed one, it has them.
|
|
2473
|
+
let now = Date.now();
|
|
2474
|
+
if (newly > 0 || now - ctx.lastAckResend >= ackDelay(currentRto())) {
|
|
2475
|
+
ctx.lastAckResend = now;
|
|
2476
|
+
ctx.flightResent = true;
|
|
2477
|
+
transmitUnacked(ctx.currentFlight, 'partial-ack', protectedAcked > 0);
|
|
2478
|
+
// The records just resent start their wait again: the timer, left
|
|
2479
|
+
// as it was, fired soon after and sent the same records a second
|
|
2480
|
+
// time — wasted on a real path, and packed with the first copy
|
|
2481
|
+
// where both go out in one pass (BoGo DTLS-Retransmit-Server-
|
|
2482
|
+
// ACKMatchingEpoch-TLS13: "excess data"). Its value is kept: this
|
|
2483
|
+
// was no timeout.
|
|
2484
|
+
startRetransmitTimer();
|
|
2485
|
+
}
|
|
2486
|
+
return;
|
|
2487
|
+
}
|
|
2488
|
+
if (newly === 0 && !ctx.finalFlightPending) return; // nothing this ACK confirmed
|
|
1647
2489
|
}
|
|
1648
2490
|
|
|
1649
|
-
//
|
|
1650
|
-
//
|
|
1651
|
-
//
|
|
1652
|
-
|
|
2491
|
+
// The whole flight is acknowledged — or, the ACK for the client's final
|
|
2492
|
+
// flight (which RFC 9147 §7.1 requires the peer to send explicitly, and
|
|
2493
|
+
// which arrives at the application epoch): cancel the retransmit timer.
|
|
2494
|
+
ctx.finalFlightPending = false;
|
|
1653
2495
|
cancelRetransmit();
|
|
1654
2496
|
startNewFlight();
|
|
1655
2497
|
}
|
|
@@ -1677,8 +2519,7 @@ function DTLSSession(options) {
|
|
|
1677
2519
|
if (!postHandshake && (ctx.state === 'connected' || ctx.state === 'closed')) return;
|
|
1678
2520
|
if (ctx.state === 'closed') return;
|
|
1679
2521
|
let payload = buildDtlsAck(recordsToAck);
|
|
1680
|
-
|
|
1681
|
-
_ldbgTx(record, 'L1334'); ev.emit('packet', record);
|
|
2522
|
+
queueOut({ kind: 'raw', epoch: epoch, contentType: CT.ACK, payload: payload, label: 'ack' });
|
|
1682
2523
|
}
|
|
1683
2524
|
|
|
1684
2525
|
|
|
@@ -1696,12 +2537,33 @@ function DTLSSession(options) {
|
|
|
1696
2537
|
ev.emit('clienthello', data, message);
|
|
1697
2538
|
});
|
|
1698
2539
|
|
|
2540
|
+
// Agreed (the hello), or moved by the peer's NewConnectionId: the record
|
|
2541
|
+
// layer's from now on, and the application's to know.
|
|
2542
|
+
tls.on('connectionId', function(ids) { ctx.cid = ids; ev.emit('connectionId', getConnectionIds()); });
|
|
2543
|
+
|
|
1699
2544
|
tls.on('secureConnect', function() {
|
|
1700
2545
|
_ldbg('handshake complete — secureConnect (version=0x' +
|
|
1701
2546
|
((ctx.selectedVersion || tls.context.selected_version || 0).toString(16)) + ')');
|
|
1702
2547
|
ctx.state = 'connected';
|
|
1703
|
-
|
|
1704
|
-
|
|
2548
|
+
// DTLS 1.3 client (RFC 9147 §5.8.3, §7.1): our final flight (…Finished)
|
|
2549
|
+
// is answered by nothing but an ACK, so it must stay under the
|
|
2550
|
+
// retransmit timer until that ACK — or anything else from the peer at
|
|
2551
|
+
// the application epoch — shows it arrived. Dropping it here meant one
|
|
2552
|
+
// lost Finished left us "connected" and the peer waiting for good.
|
|
2553
|
+
if (!isServer && ctx.selectedVersion === DTLS_VERSION.DTLS1_3 && haveFlight()) {
|
|
2554
|
+
ctx.finalFlightPending = true;
|
|
2555
|
+
startRetransmitTimer();
|
|
2556
|
+
} else {
|
|
2557
|
+
// DTLS 1.2: if the last flight is ours — sent after the peer's last
|
|
2558
|
+
// message, so nothing of the peer's acknowledges it — it is kept for
|
|
2559
|
+
// the peer's retransmissions (dtls12AnswerRetransmission). No timer:
|
|
2560
|
+
// RFC 6347 §4.2.4's FINISHED state answers, it does not repeat.
|
|
2561
|
+
if (ctx.selectedVersion !== DTLS_VERSION.DTLS1_3 && !ctx.newFlightPending && haveFlight()) {
|
|
2562
|
+
ctx.lastFlight12 = ctx.currentFlight;
|
|
2563
|
+
}
|
|
2564
|
+
cancelRetransmit();
|
|
2565
|
+
startNewFlight();
|
|
2566
|
+
}
|
|
1705
2567
|
ev.emit('connect');
|
|
1706
2568
|
});
|
|
1707
2569
|
|
|
@@ -1711,12 +2573,14 @@ function DTLSSession(options) {
|
|
|
1711
2573
|
// only symptom is a connection that dies after a handshake that
|
|
1712
2574
|
// looked complete, which is exactly the DTLS 1.3 interop case.
|
|
1713
2575
|
_ldbg('ERROR ' + (e && e.message ? e.message : e) +
|
|
1714
|
-
(e && e.
|
|
2576
|
+
(e && e.alertDesc != null ? ' alertDesc=' + e.alertDesc : '') +
|
|
1715
2577
|
(e && e.code ? ' code=' + e.code : ''));
|
|
1716
|
-
|
|
2578
|
+
// TLSSession has sent its alert already.
|
|
2579
|
+
fail(e);
|
|
1717
2580
|
});
|
|
1718
2581
|
|
|
1719
2582
|
tls.on('session', function(ticket) {
|
|
2583
|
+
ctx.lastSession = ticket;
|
|
1720
2584
|
ev.emit('session', ticket);
|
|
1721
2585
|
});
|
|
1722
2586
|
|
|
@@ -1731,12 +2595,20 @@ function DTLSSession(options) {
|
|
|
1731
2595
|
return;
|
|
1732
2596
|
}
|
|
1733
2597
|
if (typeof data === 'string') data = new TextEncoder().encode(data);
|
|
2598
|
+
// One send, one record — a datagram's message is not split. Larger than
|
|
2599
|
+
// a record may carry (2^14, or the peer's record_size_limit): refused,
|
|
2600
|
+
// not sent as a record the peer must refuse.
|
|
2601
|
+
if (data.length > tls.recordSendLimit()) {
|
|
2602
|
+
let big = new RangeError('DTLS: ' + data.length + ' bytes in one record, more than ' + tls.recordSendLimit() + ' (the record limit)');
|
|
2603
|
+
big.code = 'ERR_DTLS_RECORD_TOO_LARGE';
|
|
2604
|
+
ev.emit('error', big);
|
|
2605
|
+
return;
|
|
2606
|
+
}
|
|
1734
2607
|
|
|
1735
2608
|
let epoch = ctx.selectedVersion === DTLS_VERSION.DTLS1_3
|
|
1736
|
-
? (
|
|
2609
|
+
? appEpoch()
|
|
1737
2610
|
: 1;
|
|
1738
|
-
|
|
1739
|
-
_ldbgTx(record, 'L1383'); ev.emit('packet', record);
|
|
2611
|
+
queueOut({ kind: 'raw', epoch: epoch, contentType: CT.APPLICATION_DATA, payload: data, label: 'data' });
|
|
1740
2612
|
}
|
|
1741
2613
|
|
|
1742
2614
|
|
|
@@ -1753,23 +2625,10 @@ function DTLSSession(options) {
|
|
|
1753
2625
|
// the peer has switched to reading protected records, and a plaintext
|
|
1754
2626
|
// close_notify arrives with a classic DTLSPlaintext header where the peer
|
|
1755
2627
|
// requires the RFC 9147 §4 unified header. It then reports a bad record
|
|
1756
|
-
// type instead of a clean shutdown.
|
|
1757
|
-
//
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
// After a KeyUpdate the live epoch is 4, 5, ... — the peer has moved on
|
|
1761
|
-
// and no longer reads epoch 3, so a close_notify sent there is dropped
|
|
1762
|
-
// and the shutdown looks like a timeout instead of a clean close.
|
|
1763
|
-
epoch = (ctx.writeEpoch !== null && ctx.keys[ctx.writeEpoch])
|
|
1764
|
-
? ctx.writeEpoch
|
|
1765
|
-
: (ctx.keys[3] ? 3 : (ctx.keys[2] ? 2 : 0));
|
|
1766
|
-
} else {
|
|
1767
|
-
epoch = ctx.keys[1] ? 1 : 0;
|
|
1768
|
-
}
|
|
1769
|
-
sendAlertRecord(epoch, new Uint8Array([1, 0])); // warning, close_notify
|
|
1770
|
-
ctx.state = 'closed';
|
|
1771
|
-
cancelRetransmit();
|
|
1772
|
-
cancelKeyUpdateRetransmit();
|
|
2628
|
+
// type instead of a clean shutdown. alertEpoch() decides, as for every
|
|
2629
|
+
// alert we send.
|
|
2630
|
+
sendAlertRecord(alertEpoch(), new Uint8Array([1, 0])); // warning, close_notify
|
|
2631
|
+
shutDown();
|
|
1773
2632
|
ev.emit('close');
|
|
1774
2633
|
}
|
|
1775
2634
|
|
|
@@ -1876,6 +2735,15 @@ function DTLSSession(options) {
|
|
|
1876
2735
|
|
|
1877
2736
|
/** Negotiated ALPN. */
|
|
1878
2737
|
getALPN: function() { return tls.getALPN(); },
|
|
2738
|
+
// Encrypted Client Hello, as TLSSocket's echStatus / echBackend.
|
|
2739
|
+
getEchStatus: function() { return tls.getEchStatus(); },
|
|
2740
|
+
isEchBackend: function() { return tls.isEchBackend(); },
|
|
2741
|
+
// External PSK: the identity this connection was authenticated with.
|
|
2742
|
+
getPskIdentity: function() { return tls.getPskIdentity(); },
|
|
2743
|
+
/** Connection IDs (RFC 9146), once agreed: { local, peer } — local, in
|
|
2744
|
+
* the records we receive; peer, in those we send (NewConnectionId may
|
|
2745
|
+
* have changed it since the handshake). Null otherwise. */
|
|
2746
|
+
getConnectionIds: getConnectionIds,
|
|
1879
2747
|
|
|
1880
2748
|
/** Peer certificate. */
|
|
1881
2749
|
getPeerCertificate: function() { return tls.getPeerCertificate(); },
|
|
@@ -1897,6 +2765,31 @@ function DTLSSession(options) {
|
|
|
1897
2765
|
|
|
1898
2766
|
/** Whether this connection resumed an earlier session. */
|
|
1899
2767
|
isSessionReused: function() { return !!tls.isResumed; },
|
|
2768
|
+
|
|
2769
|
+
/** The last session (a Buffer for `session` next time), as TLSSocket#getSession; undefined until one. */
|
|
2770
|
+
getSession: function() { return ctx.lastSession; },
|
|
2771
|
+
|
|
2772
|
+
/**
|
|
2773
|
+
* The path MTU: the largest datagram this session writes (every record
|
|
2774
|
+
* is one datagram). Takes effect for the next message fragmented —
|
|
2775
|
+
* a flight already sent is resent as it was cut. 128..65507 bytes.
|
|
2776
|
+
*/
|
|
2777
|
+
setMTU: function(mtu) { ctx.mtu = checkedMtu(mtu); },
|
|
2778
|
+
getMTU: function() { return ctx.mtu; },
|
|
2779
|
+
|
|
2780
|
+
/**
|
|
2781
|
+
* Where each flight's retransmission timer starts (it doubles on every
|
|
2782
|
+
* timeout, to 60 s). RFC 9147 §5.8.2 recommends 1 s; a known short
|
|
2783
|
+
* path can go lower. 10..60000 ms. The flight in progress keeps its own.
|
|
2784
|
+
*/
|
|
2785
|
+
setInitialRetransmitTimeout: function(ms) {
|
|
2786
|
+
ctx.initialRetransmitTimeout = checkedRetransmitTimeout(ms);
|
|
2787
|
+
// Said explicitly: the schedule from now on — every flight's timer
|
|
2788
|
+
// starts there, whatever round trip is measured (currentRto).
|
|
2789
|
+
ctx.fixedRto = true;
|
|
2790
|
+
ctx.srtt = null; ctx.rttvar = null;
|
|
2791
|
+
if (ctx.retransmitTimer === null) ctx.retransmitTimeout = ctx.initialRetransmitTimeout;
|
|
2792
|
+
},
|
|
1900
2793
|
};
|
|
1901
2794
|
|
|
1902
2795
|
for (let k in api) {
|
|
@@ -1912,4 +2805,5 @@ function DTLSSession(options) {
|
|
|
1912
2805
|
return this;
|
|
1913
2806
|
}
|
|
1914
2807
|
|
|
1915
|
-
export default DTLSSession;
|
|
2808
|
+
export default DTLSSession;
|
|
2809
|
+
export { SERVER_CID_LEN };
|