lemon-tls 0.10.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 +164 -42
- package/index.d.ts +399 -15
- package/package.json +7 -2
- package/src/ca_store.js +31 -0
- package/src/compat.js +64 -112
- package/src/crypto.js +21 -114
- package/src/dtls_session.js +1238 -412
- package/src/dtls_socket.js +318 -96
- package/src/node_options.js +154 -17
- package/src/record.js +293 -197
- package/src/secure_context.js +86 -42
- 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 +2214 -895
- package/src/tls_socket.js +451 -452
- package/src/wire.js +271 -158
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,17 +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
|
|
294
366
|
finalFlightPending: false, // DTLS 1.3 client: final flight not yet confirmed (see secureConnect)
|
|
295
367
|
|
|
296
368
|
retransmitTimer: null,
|
|
297
369
|
retransmitCount: 0,
|
|
298
|
-
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.
|
|
299
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
|
|
300
389
|
|
|
301
390
|
// DTLS 1.2: CCS tracking
|
|
302
391
|
localCcsSent: false,
|
|
303
|
-
remoteCcsSeen: false,
|
|
304
392
|
|
|
305
393
|
// HelloVerifyRequest (DTLS 1.2 server)
|
|
306
394
|
hvrCookie: null, // cookie sent in HVR
|
|
@@ -313,58 +401,18 @@ function DTLSSession(options) {
|
|
|
313
401
|
// ============================================================
|
|
314
402
|
|
|
315
403
|
// Set DTLS versions and cookie (triggers DTLS format in build_hello)
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
let
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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), {
|
|
325
414
|
dtls_cookie: new Uint8Array(0), // empty cookie — triggers DTLS format
|
|
326
|
-
};
|
|
327
|
-
|
|
328
|
-
// Pass through cipher/group/alpn preferences
|
|
329
|
-
if (options.cipherSuites) tlsSetup.local_supported_cipher_suites = options.cipherSuites;
|
|
330
|
-
|
|
331
|
-
// Client cipher defaults: TLSSession auto-populates both TLS 1.3+1.2 ciphers,
|
|
332
|
-
// but if we only support DTLS 1.2, we must not offer TLS 1.3 ciphers
|
|
333
|
-
// (the server might erroneously select one for a 1.2 connection).
|
|
334
|
-
if (!isServer && !options.cipherSuites && versions.indexOf(DTLS_VERSION.DTLS1_3) < 0) {
|
|
335
|
-
tlsSetup.local_supported_cipher_suites = [0xC02F, 0xC030, 0xC02B, 0xC02C, 0xCCA8];
|
|
336
|
-
}
|
|
337
|
-
if (options.groups) tlsSetup.local_supported_groups = options.groups;
|
|
338
|
-
if (options.alpnProtocols) tlsSetup.local_supported_alpns = options.alpnProtocols;
|
|
339
|
-
|
|
340
|
-
// Default key-exchange groups, BOTH roles. With DTLS 1.3 offered: the
|
|
341
|
-
// hybrid post-quantum group first (X25519MLKEM768, when node:crypto has
|
|
342
|
-
// ML-KEM — see ecdh.js), then the classical ones. In WebRTC either peer can
|
|
343
|
-
// end up the DTLS client, and harvest-now-decrypt-later applies to media
|
|
344
|
-
// too (Chrome rolls PQ DTLS out from 149). A client leading with the hybrid
|
|
345
|
-
// group sends a classical share beside it (tls_session), so a classical
|
|
346
|
-
// server costs nothing extra. DTLS 1.2 only: classical — a KEM does not fit
|
|
347
|
-
// its key exchange, and the server skips KEM groups there anyway.
|
|
348
|
-
if (!tlsSetup.local_supported_groups) {
|
|
349
|
-
// (ECDH_SUPPORTED_GROUPS leads with the hybrid too now; tls_session leaves
|
|
350
|
-
// a KEM group out of a hello that does not offer 1.3.)
|
|
351
|
-
tlsSetup.local_supported_groups = (versions.indexOf(DTLS_VERSION.DTLS1_3) >= 0
|
|
352
|
-
? SERVER_DEFAULT_GROUPS : ECDH_SUPPORTED_GROUPS).slice();
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// Server defaults — TLSSession client auto-populates these, but server expects them set externally
|
|
356
|
-
if (isServer) {
|
|
357
|
-
if (!tlsSetup.local_supported_cipher_suites) {
|
|
358
|
-
// crypto.js owns the default suite list (see default_cipher_suites).
|
|
359
|
-
tlsSetup.local_supported_cipher_suites = default_cipher_suites(
|
|
360
|
-
versions.indexOf(DTLS_VERSION.DTLS1_3) >= 0,
|
|
361
|
-
versions.indexOf(DTLS_VERSION.DTLS1_2) >= 0
|
|
362
|
-
);
|
|
363
|
-
}
|
|
364
|
-
// (key-exchange groups: set above, for both roles)
|
|
365
|
-
// signing.js owns the scheme list; DTLS spans 1.2+1.3 so include PKCS#1.
|
|
366
|
-
tlsSetup.local_supported_signature_algorithms = default_signature_schemes();
|
|
367
|
-
}
|
|
415
|
+
});
|
|
368
416
|
|
|
369
417
|
// Server cert/key.
|
|
370
418
|
//
|
|
@@ -373,13 +421,11 @@ function DTLSSession(options) {
|
|
|
373
421
|
// pfx and ocsp, and picking out two fields silently dropped all of them —
|
|
374
422
|
// an encrypted private key could never be decrypted here no matter what the
|
|
375
423
|
// caller passed. Same defect the TLS paths carried.
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
tlsSetup.cert_private_key = sctx.privateKey;
|
|
380
|
-
}
|
|
381
|
-
|
|
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.
|
|
382
427
|
tls.set_context(tlsSetup);
|
|
428
|
+
if (options.cert && options.key) tls.setKeyCert(createSecureContext(options));
|
|
383
429
|
|
|
384
430
|
|
|
385
431
|
// ============================================================
|
|
@@ -595,24 +641,22 @@ function DTLSSession(options) {
|
|
|
595
641
|
if (ctx.writeEpoch === null || ctx.readEpoch === null) return;
|
|
596
642
|
|
|
597
643
|
if (info.direction === 'send') {
|
|
598
|
-
// §8: only one KeyUpdate may be in flight. requestKeyUpdate()
|
|
599
|
-
//
|
|
600
|
-
//
|
|
601
|
-
//
|
|
602
|
-
// 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.
|
|
603
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
|
+
}
|
|
604
655
|
ev.emit('error', new Error(
|
|
605
656
|
'DTLS: a KeyUpdate is already awaiting acknowledgement (RFC 9147 §8)'));
|
|
606
657
|
return;
|
|
607
658
|
}
|
|
608
|
-
|
|
609
|
-
// Install the new write keys, but keep protecting with the old epoch
|
|
610
|
-
// until the peer ACKs (§8). ctx.keys[next] may already carry a .read
|
|
611
|
-
// half from the peer's own update — merge, never overwrite.
|
|
612
|
-
ctx.keys[next] = Object.assign({}, ctx.keys[next], { write: deriveEpochKeys(info.secret) });
|
|
613
|
-
ctx.writeSeq[next] = 0;
|
|
614
|
-
ctx.pendingWriteEpoch = next;
|
|
615
|
-
|
|
659
|
+
installWriteUpdate(info.secret);
|
|
616
660
|
} else if (info.direction === 'receive') {
|
|
617
661
|
let next = ctx.readEpoch + 1;
|
|
618
662
|
ctx.keys[next] = Object.assign({}, ctx.keys[next], { read: deriveEpochKeys(info.secret) });
|
|
@@ -623,40 +667,259 @@ function DTLSSession(options) {
|
|
|
623
667
|
}
|
|
624
668
|
});
|
|
625
669
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
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);
|
|
754
|
+
};
|
|
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();
|
|
632
759
|
};
|
|
633
|
-
|
|
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
|
+
}
|
|
634
850
|
}
|
|
635
851
|
|
|
636
|
-
function
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
if (
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
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'));
|
|
644
861
|
return;
|
|
645
862
|
}
|
|
646
|
-
|
|
647
|
-
//
|
|
648
|
-
//
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
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();
|
|
886
|
+
}
|
|
887
|
+
|
|
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;
|
|
655
895
|
}
|
|
656
896
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
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
|
+
}
|
|
660
923
|
}
|
|
661
924
|
|
|
662
925
|
|
|
@@ -689,28 +952,25 @@ function DTLSSession(options) {
|
|
|
689
952
|
// epoch, which is 3 until a KeyUpdate moves it to 4, 5, ... Hardcoding 3
|
|
690
953
|
// meant every post-handshake message stayed on the pre-rekey epoch.
|
|
691
954
|
dtlsEpoch = (tlsEpoch === 1) ? 2
|
|
692
|
-
: (
|
|
955
|
+
: appEpoch();
|
|
693
956
|
} else {
|
|
694
957
|
dtlsEpoch = 1; // DTLS 1.2: epoch 1 for all encrypted
|
|
695
958
|
}
|
|
696
959
|
|
|
697
960
|
if (type === 'alert') {
|
|
698
|
-
|
|
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);
|
|
699
969
|
return;
|
|
700
970
|
}
|
|
701
971
|
|
|
702
|
-
// DTLS 1.2:
|
|
703
|
-
if (ctx.selectedVersion !== DTLS_VERSION.DTLS1_3 && tlsEpoch === 1
|
|
704
|
-
let ts = tls.getTrafficSecrets();
|
|
705
|
-
if (ts.masterSecret) {
|
|
706
|
-
let d12 = deriveKeys12(ts.masterSecret, ts.localRandom, ts.remoteRandom, tls.getCipher(), isServer);
|
|
707
|
-
ctx.keys[1] = {
|
|
708
|
-
write: { key: d12.writeKey, iv: d12.writeIv },
|
|
709
|
-
read: { key: d12.readKey, iv: d12.readIv },
|
|
710
|
-
};
|
|
711
|
-
ctx.writeSeq[1] = 0;
|
|
712
|
-
}
|
|
713
|
-
}
|
|
972
|
+
// DTLS 1.2: keys before the first encrypted message
|
|
973
|
+
if (ctx.selectedVersion !== DTLS_VERSION.DTLS1_3 && tlsEpoch === 1) installKeys12();
|
|
714
974
|
|
|
715
975
|
// Flight boundary: first outgoing record after accepting peer data
|
|
716
976
|
// starts a fresh flight (clears the previous flight's records and
|
|
@@ -725,50 +985,32 @@ function DTLSSession(options) {
|
|
|
725
985
|
sendCCS();
|
|
726
986
|
}
|
|
727
987
|
|
|
728
|
-
//
|
|
729
|
-
let dtlsMsg = build_dtls_handshake(data, seq);
|
|
730
|
-
|
|
731
|
-
// Fragment if needed
|
|
732
|
-
let frags = fragmentMessage(dtlsMsg, data, seq);
|
|
733
|
-
|
|
734
|
-
// Build DTLS records and emit
|
|
988
|
+
// Out as a DTLS handshake message — fragmented as it is packed (flushOut).
|
|
735
989
|
// RFC 9147 §5.8.4: post-handshake messages do NOT belong to the handshake
|
|
736
990
|
// flight. Each category gets an independent state machine; folding a
|
|
737
991
|
// KeyUpdate into currentFlight would make a handshake retransmission
|
|
738
992
|
// replay it, and a KeyUpdate ACK cancel the handshake's timer.
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
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;
|
|
745
1008
|
}
|
|
746
|
-
|
|
747
|
-
// timer until an ACK arrives. Only the last fragment is tracked; a
|
|
748
|
-
// KeyUpdate body is 1 byte and never fragments in practice.
|
|
749
|
-
armKeyUpdateRetransmit(frags[frags.length - 1], dtlsEpoch);
|
|
1009
|
+
sendPostHandshake(type, data, seq, dtlsEpoch);
|
|
750
1010
|
return;
|
|
751
1011
|
}
|
|
752
1012
|
|
|
753
|
-
|
|
754
|
-
// The flight captures the fragment, not the record — see currentFlight.
|
|
755
|
-
let entry = { epoch: dtlsEpoch, contentType: CT.HANDSHAKE, payload: frags[i] };
|
|
756
|
-
let record = buildFlightRecord(entry);
|
|
757
|
-
ctx.currentFlight.push(entry);
|
|
758
|
-
if (record) {
|
|
759
|
-
_ldbgTx(record, 'L543'); ev.emit('packet', record);
|
|
760
|
-
} else {
|
|
761
|
-
// buildRecord refused to build (no keys for an encrypted epoch).
|
|
762
|
-
// Dropping the record here without a word is how a key-epoch mismatch
|
|
763
|
-
// turns into an unexplained peer timeout instead of a diagnosable
|
|
764
|
-
// failure: nothing reaches the wire and nothing is reported. Surface
|
|
765
|
-
// it — the handshake cannot proceed either way.
|
|
766
|
-
ev.emit('error', new Error(
|
|
767
|
-
'DTLS: could not build outgoing handshake record for epoch ' + dtlsEpoch +
|
|
768
|
-
' — no keys installed for that epoch'));
|
|
769
|
-
return;
|
|
770
|
-
}
|
|
771
|
-
}
|
|
1013
|
+
queueFlightMessage(data, seq, dtlsEpoch);
|
|
772
1014
|
|
|
773
1015
|
// Start retransmit timer after sending flight messages.
|
|
774
1016
|
//
|
|
@@ -817,67 +1059,151 @@ function DTLSSession(options) {
|
|
|
817
1059
|
|
|
818
1060
|
if (ctx.selectedVersion === DTLS_VERSION.DTLS1_3) {
|
|
819
1061
|
// DTLS 1.3 encrypted (unified header)
|
|
820
|
-
return buildEncryptedDtls13(contentType, payload, nextWriteSeq(epoch), epoch, epochKeys.write);
|
|
1062
|
+
return buildEncryptedDtls13(contentType, payload, nextWriteSeq(epoch), epoch, epochKeys.write, peerCid());
|
|
821
1063
|
} else {
|
|
822
1064
|
// DTLS 1.2 encrypted (classic header)
|
|
823
|
-
return buildEncryptedDtls12(contentType, epoch, nextWriteSeq(epoch), payload, epochKeys.write);
|
|
1065
|
+
return buildEncryptedDtls12(contentType, epoch, nextWriteSeq(epoch), payload, epochKeys.write, peerCid());
|
|
824
1066
|
}
|
|
825
1067
|
}
|
|
826
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
|
+
|
|
827
1078
|
function nextWriteSeq(epoch) {
|
|
828
1079
|
if (!(epoch in ctx.writeSeq)) ctx.writeSeq[epoch] = 0;
|
|
829
1080
|
return ctx.writeSeq[epoch]++;
|
|
830
1081
|
}
|
|
831
1082
|
|
|
832
1083
|
/**
|
|
833
|
-
*
|
|
834
|
-
*
|
|
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.
|
|
1088
|
+
*/
|
|
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.
|
|
835
1183
|
*/
|
|
836
|
-
function
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
let isDtls13 = ctx.selectedVersion === DTLS_VERSION.DTLS1_3;
|
|
844
|
-
let recordOverhead = isDtls13 ? (5 + 16) : (13 + 16 + 8);
|
|
845
|
-
let maxFragment = ctx.mtu - recordOverhead;
|
|
846
|
-
|
|
847
|
-
// The DTLS handshake message header is 12 bytes; the body is what we chunk.
|
|
848
|
-
const HS_HDR = 12;
|
|
849
|
-
if (dtlsMsg.length <= maxFragment) return [dtlsMsg];
|
|
850
|
-
|
|
851
|
-
// Need to fragment: split the TLS body into chunks
|
|
852
|
-
let body = tlsMsg.subarray(4); // skip TLS header (type+length)
|
|
853
|
-
let totalLength = body.length;
|
|
854
|
-
let fragments = [];
|
|
855
|
-
let offset = 0;
|
|
856
|
-
|
|
857
|
-
// Each fragment carries its own 12-byte handshake header, so the payload
|
|
858
|
-
// budget per fragment is maxFragment - HS_HDR.
|
|
859
|
-
let maxChunk = Math.max(1, maxFragment - HS_HDR);
|
|
860
|
-
while (offset < totalLength) {
|
|
861
|
-
let chunkLen = Math.min(maxChunk, totalLength - offset);
|
|
862
|
-
let frag = build_dtls_handshake(tlsMsg, msgSeq, offset, chunkLen);
|
|
863
|
-
fragments.push(frag);
|
|
864
|
-
offset += chunkLen;
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
return fragments;
|
|
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;
|
|
868
1191
|
}
|
|
869
1192
|
|
|
870
1193
|
/**
|
|
871
1194
|
* Send an alert record.
|
|
872
1195
|
*/
|
|
873
1196
|
function sendAlertRecord(epoch, alertData) {
|
|
1197
|
+
// What is queued goes first, the alert in a datagram of its own (see
|
|
1198
|
+
// flushOut).
|
|
1199
|
+
flushOut();
|
|
874
1200
|
let record = buildRecord(epoch, CT.ALERT, alertData);
|
|
875
1201
|
// buildRecord returns null when it refuses to emit a malformed record
|
|
876
1202
|
// (DTLS 1.3 epoch >= 2 with no keys installed). Emitting that null as a
|
|
877
1203
|
// packet pushes `null` into the transport; drop it instead — we already
|
|
878
1204
|
// surfaced the reason through 'error'.
|
|
879
1205
|
if (!record) return;
|
|
880
|
-
_ldbgTx(record, '
|
|
1206
|
+
_ldbgTx(record, 'alert'); ev.emit('packet', record);
|
|
881
1207
|
}
|
|
882
1208
|
|
|
883
1209
|
/**
|
|
@@ -892,7 +1218,6 @@ function DTLSSession(options) {
|
|
|
892
1218
|
ctx.newFlightPending = false;
|
|
893
1219
|
startNewFlight();
|
|
894
1220
|
}
|
|
895
|
-
let record = buildDtlsPlaintext(CT.CHANGE_CIPHER_SPEC, 0, nextWriteSeq(0), new Uint8Array([1]));
|
|
896
1221
|
// RFC 6347 §4.2.4: the CCS is part of the flight and MUST be included
|
|
897
1222
|
// when the flight is retransmitted. Captured as a descriptor (epoch 0,
|
|
898
1223
|
// plaintext), so resendFlight rebuilds it with a fresh seq like every
|
|
@@ -900,8 +1225,16 @@ function DTLSSession(options) {
|
|
|
900
1225
|
// retransmitted final flight arrived WITHOUT the cipher-spec change and
|
|
901
1226
|
// the peer could never decrypt the accompanying Finished — an
|
|
902
1227
|
// unrecoverable stall under single-datagram loss.
|
|
903
|
-
|
|
904
|
-
|
|
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 });
|
|
905
1238
|
}
|
|
906
1239
|
|
|
907
1240
|
|
|
@@ -909,7 +1242,67 @@ function DTLSSession(options) {
|
|
|
909
1242
|
// Incoming: UDP datagram → parse → feed TLSSession
|
|
910
1243
|
// ============================================================
|
|
911
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.
|
|
912
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) {
|
|
913
1306
|
if (ctx.state === 'closed') return;
|
|
914
1307
|
if (ctx.state === 'idle') ctx.state = 'handshaking';
|
|
915
1308
|
|
|
@@ -921,7 +1314,7 @@ function DTLSSession(options) {
|
|
|
921
1314
|
}
|
|
922
1315
|
}
|
|
923
1316
|
|
|
924
|
-
let records = parseDtlsDatagram(data, keysByEpoch);
|
|
1317
|
+
let records = parseDtlsDatagram(data, keysByEpoch, localCid(), localCidRequired());
|
|
925
1318
|
|
|
926
1319
|
if (LEMON_DEBUG) {
|
|
927
1320
|
var _summ = [];
|
|
@@ -935,9 +1328,9 @@ function DTLSSession(options) {
|
|
|
935
1328
|
}
|
|
936
1329
|
|
|
937
1330
|
// RFC 9147 §7: collect the record numbers of handshake records we received
|
|
938
|
-
// in this datagram so we can ACK them once, after processing
|
|
939
|
-
//
|
|
940
|
-
//
|
|
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).
|
|
941
1334
|
let ackable = [];
|
|
942
1335
|
|
|
943
1336
|
// Whether these records arrived AFTER the handshake must be decided here,
|
|
@@ -948,8 +1341,27 @@ function DTLSSession(options) {
|
|
|
948
1341
|
// the spurious epoch-3 ACK that makes a peer answer decode_error on the
|
|
949
1342
|
// NEXT record — so a perfectly good SCTP COOKIE_ECHO takes the blame.
|
|
950
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;
|
|
951
1349
|
|
|
952
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;
|
|
953
1365
|
// A record we could not decrypt when the datagram was parsed. Keys may
|
|
954
1366
|
// have arrived since — processing an earlier record in THIS datagram
|
|
955
1367
|
// (the ServerHello) is exactly what installs them. Retry now, in order,
|
|
@@ -960,7 +1372,9 @@ function DTLSSession(options) {
|
|
|
960
1372
|
let readKeys = epochKeys ? epochKeys.read : null;
|
|
961
1373
|
if (!readKeys) continue; // still unreadable — let the peer retransmit
|
|
962
1374
|
let dec = null;
|
|
963
|
-
|
|
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; }
|
|
964
1378
|
if (!dec) continue;
|
|
965
1379
|
records[i] = {
|
|
966
1380
|
type: dec.type, epoch: dec.epoch, seq: dec.seq,
|
|
@@ -984,65 +1398,122 @@ function DTLSSession(options) {
|
|
|
984
1398
|
ackable.push({ epoch: records[i].epoch, seq: records[i].seq });
|
|
985
1399
|
}
|
|
986
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
|
+
}
|
|
987
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();
|
|
988
1409
|
|
|
989
|
-
//
|
|
990
|
-
|
|
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]) {
|
|
991
1413
|
let readKeys = ctx.keys[1].read;
|
|
992
1414
|
for (let j = i + 1; j < records.length; j++) {
|
|
993
|
-
if (!records[j].encrypted && records[j].epoch > 0 && readKeys)
|
|
994
|
-
try {
|
|
995
|
-
records[j].content = decryptDtls12(records[j].content, readKeys.key, readKeys.iv, records[j].epoch, records[j].seq, records[j].type);
|
|
996
|
-
records[j].encrypted = true;
|
|
997
|
-
} catch(e) {
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1415
|
+
if (!records[j].encrypted && records[j].epoch > 0 && readKeys) open12(records[j], readKeys);
|
|
1000
1416
|
}
|
|
1001
1417
|
}
|
|
1002
1418
|
}
|
|
1003
1419
|
|
|
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
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
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();
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
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;
|
|
1045
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);
|
|
1046
1517
|
}
|
|
1047
1518
|
|
|
1048
1519
|
function processRecord(record) {
|
|
@@ -1060,6 +1531,23 @@ function DTLSSession(options) {
|
|
|
1060
1531
|
if (ctx.state === 'connected' && !record.encrypted &&
|
|
1061
1532
|
(record.type === CT.ALERT || record.type === CT.APPLICATION_DATA)) return;
|
|
1062
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
|
+
|
|
1063
1551
|
if (record.type === CT.HANDSHAKE) {
|
|
1064
1552
|
// An epoch>0 record that could not be decrypted (keys not yet
|
|
1065
1553
|
// installed, AEAD failure) must NOT be fed to the handshake
|
|
@@ -1076,42 +1564,81 @@ function DTLSSession(options) {
|
|
|
1076
1564
|
processHandshakeRecord(record.content, record.epoch, record.encrypted);
|
|
1077
1565
|
} else if (record.type === CT.APPLICATION_DATA) {
|
|
1078
1566
|
if (ctx.state === 'connected') {
|
|
1079
|
-
//
|
|
1080
|
-
//
|
|
1081
|
-
|
|
1082
|
-
|
|
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));
|
|
1083
1585
|
}
|
|
1084
1586
|
} else if (record.type === CT.CHANGE_CIPHER_SPEC) {
|
|
1085
|
-
//
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
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();
|
|
1098
1601
|
} else if (record.type === CT.ACK) {
|
|
1099
|
-
|
|
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);
|
|
1100
1609
|
} else if (record.type === CT.ALERT) {
|
|
1101
|
-
|
|
1102
|
-
|
|
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;
|
|
1103
1619
|
// Name the alert either way. A close_notify is a CLEAN shutdown and
|
|
1104
1620
|
// a failure is not, but both present to the application as "the
|
|
1105
1621
|
// connection ended" — and telling them apart is the difference
|
|
1106
1622
|
// between "the peer hung up" and "the peer rejected us".
|
|
1107
|
-
_ldbg('RX Alert level=' + level + ' desc=' + desc +
|
|
1108
|
-
(desc ===
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
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
|
+
}
|
|
1113
1638
|
} else {
|
|
1114
|
-
|
|
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
|
|
1115
1642
|
}
|
|
1116
1643
|
}
|
|
1117
1644
|
}
|
|
@@ -1124,8 +1651,25 @@ function DTLSSession(options) {
|
|
|
1124
1651
|
let off = 0;
|
|
1125
1652
|
let sawMessage = false; // record contained ≥1 parseable handshake message
|
|
1126
1653
|
let anyNew = false; // ≥1 message advanced our state (not a duplicate)
|
|
1127
|
-
|
|
1128
|
-
|
|
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; }
|
|
1129
1673
|
|
|
1130
1674
|
let msgSeq = parsed.msg_seq;
|
|
1131
1675
|
let totalLen = parsed.length;
|
|
@@ -1139,10 +1683,10 @@ function DTLSSession(options) {
|
|
|
1139
1683
|
// Is this a complete message or a fragment?
|
|
1140
1684
|
if (fragOffset === 0 && fragLen === totalLen) {
|
|
1141
1685
|
// Complete message — feed directly
|
|
1142
|
-
if (deliverHandshakeMessage(parsed.type, parsed.body, msgSeq)) anyNew = true;
|
|
1686
|
+
if (deliverHandshakeMessage(parsed.type, parsed.body, msgSeq, epoch)) anyNew = true;
|
|
1143
1687
|
} else {
|
|
1144
1688
|
// Fragment — reassemble
|
|
1145
|
-
if (reassembleFragment(parsed)) anyNew = true;
|
|
1689
|
+
if (reassembleFragment(parsed, epoch)) anyNew = true;
|
|
1146
1690
|
}
|
|
1147
1691
|
|
|
1148
1692
|
off += 12 + fragLen;
|
|
@@ -1167,28 +1711,42 @@ function DTLSSession(options) {
|
|
|
1167
1711
|
// of the flight we JUST sent, not on the acknowledged one. The
|
|
1168
1712
|
// cancel happens at the acceptance points inside
|
|
1169
1713
|
// deliverHandshakeMessage instead, before the feed.
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
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()) {
|
|
1182
1743
|
ctx._lastDupElicitedResend = Date.now();
|
|
1183
|
-
//
|
|
1184
|
-
//
|
|
1185
|
-
// got our reply. Resend it immediately (RFC 6347 §4.2.4 responder
|
|
1186
|
-
// behavior) rather than waiting out our own backoff. The regular
|
|
1187
|
-
// timer keeps running unchanged, so the retransmit budget still
|
|
1188
|
-
// bounds the total effort; the resend rate is naturally bounded by
|
|
1189
|
-
// the peer's own retransmission schedule. DTLS 1.3 is excluded —
|
|
1190
|
-
// 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.
|
|
1191
1746
|
resendFlight();
|
|
1747
|
+
} else if (ctx.state === 'connected' && ctx.lastFlight12) {
|
|
1748
|
+
ctx._lastDupElicitedResend = Date.now();
|
|
1749
|
+
transmitUnacked(ctx.lastFlight12, 'lastFlight12', false);
|
|
1192
1750
|
}
|
|
1193
1751
|
}
|
|
1194
1752
|
|
|
@@ -1202,7 +1760,7 @@ function DTLSSession(options) {
|
|
|
1202
1760
|
* merged byte-ranges, rather than requiring each fragment to start exactly
|
|
1203
1761
|
* where the previous one ended.
|
|
1204
1762
|
*/
|
|
1205
|
-
function reassembleFragment(parsed) {
|
|
1763
|
+
function reassembleFragment(parsed, epoch) {
|
|
1206
1764
|
let key = parsed.msg_seq;
|
|
1207
1765
|
|
|
1208
1766
|
// Ignore fragments for messages we've already delivered.
|
|
@@ -1212,14 +1770,28 @@ function DTLSSession(options) {
|
|
|
1212
1770
|
// fragment. Reject anything claiming more than our cap BEFORE allocating,
|
|
1213
1771
|
// so a peer can't force huge buffers with a single unauthenticated packet.
|
|
1214
1772
|
if (parsed.length > ctx.maxHandshakeMessageSize) {
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
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);
|
|
1218
1777
|
}
|
|
1219
1778
|
|
|
1220
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
|
+
}
|
|
1221
1792
|
if (!frag) {
|
|
1222
1793
|
frag = ctx.fragments[key] = {
|
|
1794
|
+
epoch: epoch,
|
|
1223
1795
|
totalLength: parsed.length,
|
|
1224
1796
|
type: parsed.type,
|
|
1225
1797
|
buf: new Uint8Array(parsed.length),
|
|
@@ -1230,8 +1802,11 @@ function DTLSSession(options) {
|
|
|
1230
1802
|
let start = parsed.frag_offset;
|
|
1231
1803
|
let end = parsed.frag_offset + parsed.frag_length;
|
|
1232
1804
|
|
|
1233
|
-
//
|
|
1234
|
-
|
|
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
|
+
}
|
|
1235
1810
|
|
|
1236
1811
|
// Coverage before this fragment — used to decide whether it contributed
|
|
1237
1812
|
// anything new (a retransmitted fragment that only re-covers known bytes
|
|
@@ -1264,10 +1839,13 @@ function DTLSSession(options) {
|
|
|
1264
1839
|
}
|
|
1265
1840
|
|
|
1266
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
|
+
|
|
1267
1845
|
// Complete when a single range covers [0, totalLength).
|
|
1268
1846
|
if (merged.length === 1 && merged[0][0] === 0 && merged[0][1] === frag.totalLength) {
|
|
1269
1847
|
delete ctx.fragments[key];
|
|
1270
|
-
return deliverHandshakeMessage(frag.type, frag.buf, key);
|
|
1848
|
+
return deliverHandshakeMessage(frag.type, frag.buf, key, frag.epoch);
|
|
1271
1849
|
}
|
|
1272
1850
|
return coveredAfter > coveredBefore;
|
|
1273
1851
|
}
|
|
@@ -1282,12 +1860,19 @@ function DTLSSession(options) {
|
|
|
1282
1860
|
* dropped. processHandshakeRecord uses this for the implicit-ACK
|
|
1283
1861
|
* decision — only genuinely new data may cancel our retransmit timer.
|
|
1284
1862
|
*/
|
|
1285
|
-
function deliverHandshakeMessage(type, body, msgSeq) {
|
|
1863
|
+
function deliverHandshakeMessage(type, body, msgSeq, epoch) {
|
|
1286
1864
|
// Skip if we've already processed this msg_seq (duplicate / retransmit).
|
|
1287
1865
|
if (msgSeq < ctx.nextReadMsgSeq) return false;
|
|
1288
1866
|
|
|
1289
1867
|
// Check for HelloVerifyRequest (DTLS 1.2 server→client, type=3)
|
|
1290
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;
|
|
1291
1876
|
ctx.nextReadMsgSeq = msgSeq + 1;
|
|
1292
1877
|
// The HVR acknowledges CH1 (implicit ACK, RFC 6347 §4.2.4). No
|
|
1293
1878
|
// explicit cancel is needed: triggerClientHelloWithCookie arms the
|
|
@@ -1332,10 +1917,9 @@ function DTLSSession(options) {
|
|
|
1332
1917
|
// 'illegal_parameter' alert." DTLS 1.3 carries cookies in the `cookie`
|
|
1333
1918
|
// EXTENSION; the legacy body field is vestigial and must stay empty.
|
|
1334
1919
|
if (offersDtls13 && ch && ch.dtls_cookie && ch.dtls_cookie.length > 0) {
|
|
1335
|
-
|
|
1336
|
-
ev.emit('error', new Error(
|
|
1920
|
+
fail(new Error(
|
|
1337
1921
|
'DTLS 1.3 ClientHello carries a non-empty legacy_cookie (' +
|
|
1338
|
-
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);
|
|
1339
1923
|
return false;
|
|
1340
1924
|
}
|
|
1341
1925
|
|
|
@@ -1344,7 +1928,12 @@ function DTLSSession(options) {
|
|
|
1344
1928
|
// does return-routability with HelloRetryRequest + the cookie extension
|
|
1345
1929
|
// instead. The old guard tested ctx.selectedVersion, which is null here,
|
|
1346
1930
|
// so a DTLS 1.3 handshake could still be answered with an HVR.
|
|
1347
|
-
|
|
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) {
|
|
1348
1937
|
|
|
1349
1938
|
let incomingCookie = (ch && ch.dtls_cookie) ? ch.dtls_cookie : new Uint8Array(0);
|
|
1350
1939
|
|
|
@@ -1393,9 +1982,12 @@ function DTLSSession(options) {
|
|
|
1393
1982
|
// and wait for the gap to fill. If it's the next expected one, feed it, then
|
|
1394
1983
|
// drain any consecutive messages that were buffered earlier.
|
|
1395
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;
|
|
1396
1988
|
// Out of order — hold it (ignore a duplicate already buffered).
|
|
1397
1989
|
if (!(msgSeq in ctx.pendingMessages)) {
|
|
1398
|
-
ctx.pendingMessages[msgSeq] = { type: type, body: body };
|
|
1990
|
+
ctx.pendingMessages[msgSeq] = { type: type, body: body, epoch: epoch };
|
|
1399
1991
|
return true; // new information, even if not yet deliverable
|
|
1400
1992
|
}
|
|
1401
1993
|
return false; // duplicate of an already-buffered message
|
|
@@ -1426,6 +2018,8 @@ function DTLSSession(options) {
|
|
|
1426
2018
|
// When the feed does respond, the flight boundary (newFlightPending
|
|
1427
2019
|
// → startNewFlight) resets the retransmit budget and the outgoing
|
|
1428
2020
|
// path arms the new flight's timer, superseding this re-arm.
|
|
2021
|
+
if (!epochFitsHandshake(epoch, type)) return false;
|
|
2022
|
+
flightAnswered();
|
|
1429
2023
|
startRetransmitTimer();
|
|
1430
2024
|
ctx.newFlightPending = true;
|
|
1431
2025
|
feedHandshakeToTls(type, body, msgSeq);
|
|
@@ -1440,6 +2034,9 @@ function DTLSSession(options) {
|
|
|
1440
2034
|
while (ctx.nextReadMsgSeq in ctx.pendingMessages) {
|
|
1441
2035
|
let next = ctx.pendingMessages[ctx.nextReadMsgSeq];
|
|
1442
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;
|
|
1443
2040
|
startRetransmitTimer();
|
|
1444
2041
|
ctx.newFlightPending = true;
|
|
1445
2042
|
feedHandshakeToTls(next.type, next.body, ctx.nextReadMsgSeq);
|
|
@@ -1448,11 +2045,60 @@ function DTLSSession(options) {
|
|
|
1448
2045
|
return true;
|
|
1449
2046
|
}
|
|
1450
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
|
+
|
|
1451
2094
|
/**
|
|
1452
2095
|
* Feed one in-order handshake message to the TLSSession, converting from DTLS
|
|
1453
2096
|
* to TLS wire format and driving version detection.
|
|
1454
2097
|
*/
|
|
1455
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
|
+
|
|
1456
2102
|
// Build TLS-format message: type(1) + length(3) + body
|
|
1457
2103
|
let tlsMsg = build_message(type, body);
|
|
1458
2104
|
|
|
@@ -1471,6 +2117,54 @@ function DTLSSession(options) {
|
|
|
1471
2117
|
}
|
|
1472
2118
|
}
|
|
1473
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
|
+
|
|
1474
2168
|
/**
|
|
1475
2169
|
* Server: build and send a HelloVerifyRequest with a fresh cookie (DTLS 1.2).
|
|
1476
2170
|
* The cookie is a CSPRNG value stored on the context; a matching cookie must
|
|
@@ -1484,8 +2178,7 @@ function DTLSSession(options) {
|
|
|
1484
2178
|
|
|
1485
2179
|
// HVR always uses msg_seq 0 and is sent in the clear (epoch 0).
|
|
1486
2180
|
let hvrMsg = build_dtls_handshake(build_message(3, build_hello_verify_request({ cookie: cookie })), 0);
|
|
1487
|
-
|
|
1488
|
-
_ldbgTx(record, 'L1194'); ev.emit('packet', record);
|
|
2181
|
+
queueOut({ kind: 'raw', epoch: 0, contentType: CT.HANDSHAKE, payload: hvrMsg, label: 'hvr' });
|
|
1489
2182
|
}
|
|
1490
2183
|
|
|
1491
2184
|
/**
|
|
@@ -1534,6 +2227,7 @@ function DTLSSession(options) {
|
|
|
1534
2227
|
// a wire message sent with seq 1 would fail Finished verification.
|
|
1535
2228
|
tls.context.message_sent_seq = 1;
|
|
1536
2229
|
ctx.currentFlight = [];
|
|
2230
|
+
ctx.flightQueued = 0;
|
|
1537
2231
|
ctx.fragments = {}; // discard any partial reassembly from CH1 flight
|
|
1538
2232
|
ctx.pendingMessages = {}; // discard any out-of-order buffer from CH1 flight
|
|
1539
2233
|
transcriptMsgSeqs = [];
|
|
@@ -1545,11 +2239,9 @@ function DTLSSession(options) {
|
|
|
1545
2239
|
tls.context.transcriptHook ? tls.context.transcriptHook(ch2) : ch2
|
|
1546
2240
|
);
|
|
1547
2241
|
|
|
1548
|
-
//
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
ctx.currentFlight = [{ epoch: 0, contentType: CT.HANDSHAKE, payload: dtlsMsg }];
|
|
1552
|
-
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);
|
|
1553
2245
|
|
|
1554
2246
|
tls.context.message_sent_seq = 2;
|
|
1555
2247
|
startRetransmitTimer();
|
|
@@ -1583,25 +2275,44 @@ function DTLSSession(options) {
|
|
|
1583
2275
|
cancelRetransmit();
|
|
1584
2276
|
return;
|
|
1585
2277
|
}
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
if (f.acked) continue;
|
|
1590
|
-
let record = buildFlightRecord(f);
|
|
1591
|
-
if (record) { _ldbgTx(record, 'L1280'); ev.emit('packet', record); }
|
|
1592
|
-
}
|
|
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);
|
|
1593
2281
|
}
|
|
1594
2282
|
|
|
1595
2283
|
/**
|
|
1596
|
-
*
|
|
1597
|
-
*
|
|
1598
|
-
*
|
|
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.
|
|
1599
2287
|
*/
|
|
1600
|
-
function
|
|
1601
|
-
let
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
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 });
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
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;
|
|
1605
2316
|
}
|
|
1606
2317
|
|
|
1607
2318
|
function startRetransmitTimer() {
|
|
@@ -1613,16 +2324,16 @@ function DTLSSession(options) {
|
|
|
1613
2324
|
// connected (or closed) covers every caller, including any added
|
|
1614
2325
|
// later, without each having to remember the rule.
|
|
1615
2326
|
if (ctx.state === 'closed' || (ctx.state === 'connected' && !ctx.finalFlightPending)) return;
|
|
2327
|
+
if (ctx.flightBegan === null) ctx.flightBegan = Date.now();
|
|
1616
2328
|
ctx.retransmitTimer = setTimeout(function() {
|
|
1617
|
-
if (ctx.finalFlightPending && ctx.retransmitCount
|
|
2329
|
+
if (ctx.finalFlightPending && exhausted(ctx.retransmitCount, ctx.flightBegan)) {
|
|
1618
2330
|
// The connection is up on our side; stop resending without failing it.
|
|
1619
2331
|
finalFlightDone();
|
|
1620
2332
|
return;
|
|
1621
2333
|
}
|
|
1622
|
-
if (ctx.retransmitCount
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
ev.emit('close');
|
|
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'));
|
|
1626
2337
|
return;
|
|
1627
2338
|
}
|
|
1628
2339
|
|
|
@@ -1651,10 +2362,54 @@ function DTLSSession(options) {
|
|
|
1651
2362
|
}
|
|
1652
2363
|
}
|
|
1653
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
|
+
|
|
1654
2372
|
function startNewFlight() {
|
|
1655
2373
|
ctx.currentFlight = [];
|
|
2374
|
+
ctx.flightQueued = 0;
|
|
1656
2375
|
ctx.retransmitCount = 0;
|
|
1657
|
-
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;
|
|
1658
2413
|
}
|
|
1659
2414
|
|
|
1660
2415
|
|
|
@@ -1662,20 +2417,13 @@ function DTLSSession(options) {
|
|
|
1662
2417
|
// ACK (DTLS 1.3)
|
|
1663
2418
|
// ============================================================
|
|
1664
2419
|
|
|
1665
|
-
function processAck(
|
|
1666
|
-
let
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
//
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
if (ctx.pendingWriteEpoch !== null) {
|
|
1673
|
-
ctx.writeEpoch = ctx.pendingWriteEpoch;
|
|
1674
|
-
ctx.pendingWriteEpoch = null;
|
|
1675
|
-
cancelKeyUpdateRetransmit();
|
|
1676
|
-
ev.emit('keyUpdate', { direction: 'send', epoch: ctx.writeEpoch });
|
|
1677
|
-
return;
|
|
1678
|
-
}
|
|
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;
|
|
1679
2427
|
|
|
1680
2428
|
// During the handshake an ACK may cover only PART of our flight (RFC 9147
|
|
1681
2429
|
// §7.1): the peer lists the records it received. Those are done; the
|
|
@@ -1686,22 +2434,58 @@ function DTLSSession(options) {
|
|
|
1686
2434
|
// (≈ half of all handshakes at 5% loss, when the lost record was inside
|
|
1687
2435
|
// the server's flight).
|
|
1688
2436
|
if ((ctx.state !== 'connected' || ctx.finalFlightPending) && ctx.currentFlight.length > 0) {
|
|
1689
|
-
let
|
|
1690
|
-
|
|
1691
|
-
let remaining = 0;
|
|
2437
|
+
let newly = markAcked(ctx.currentFlight, got);
|
|
2438
|
+
if (newly > 0) flightAnswered();
|
|
2439
|
+
let remaining = 0, protectedEntries = 0, protectedAcked = 0;
|
|
1692
2440
|
for (let i = 0; i < ctx.currentFlight.length; i++) {
|
|
1693
2441
|
let f = ctx.currentFlight[i];
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
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();
|
|
1698
2485
|
}
|
|
1699
|
-
|
|
1700
|
-
// peer acknowledges them implicitly by answering — so they do not
|
|
1701
|
-
// keep the timer alive on their own.
|
|
1702
|
-
if (!f.acked && f.epoch !== 0) remaining++;
|
|
2486
|
+
return;
|
|
1703
2487
|
}
|
|
1704
|
-
if (
|
|
2488
|
+
if (newly === 0 && !ctx.finalFlightPending) return; // nothing this ACK confirmed
|
|
1705
2489
|
}
|
|
1706
2490
|
|
|
1707
2491
|
// The whole flight is acknowledged — or, the ACK for the client's final
|
|
@@ -1735,8 +2519,7 @@ function DTLSSession(options) {
|
|
|
1735
2519
|
if (!postHandshake && (ctx.state === 'connected' || ctx.state === 'closed')) return;
|
|
1736
2520
|
if (ctx.state === 'closed') return;
|
|
1737
2521
|
let payload = buildDtlsAck(recordsToAck);
|
|
1738
|
-
|
|
1739
|
-
_ldbgTx(record, 'L1334'); ev.emit('packet', record);
|
|
2522
|
+
queueOut({ kind: 'raw', epoch: epoch, contentType: CT.ACK, payload: payload, label: 'ack' });
|
|
1740
2523
|
}
|
|
1741
2524
|
|
|
1742
2525
|
|
|
@@ -1754,6 +2537,10 @@ function DTLSSession(options) {
|
|
|
1754
2537
|
ev.emit('clienthello', data, message);
|
|
1755
2538
|
});
|
|
1756
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
|
+
|
|
1757
2544
|
tls.on('secureConnect', function() {
|
|
1758
2545
|
_ldbg('handshake complete — secureConnect (version=0x' +
|
|
1759
2546
|
((ctx.selectedVersion || tls.context.selected_version || 0).toString(16)) + ')');
|
|
@@ -1763,10 +2550,17 @@ function DTLSSession(options) {
|
|
|
1763
2550
|
// retransmit timer until that ACK — or anything else from the peer at
|
|
1764
2551
|
// the application epoch — shows it arrived. Dropping it here meant one
|
|
1765
2552
|
// lost Finished left us "connected" and the peer waiting for good.
|
|
1766
|
-
if (!isServer && ctx.selectedVersion === DTLS_VERSION.DTLS1_3 &&
|
|
2553
|
+
if (!isServer && ctx.selectedVersion === DTLS_VERSION.DTLS1_3 && haveFlight()) {
|
|
1767
2554
|
ctx.finalFlightPending = true;
|
|
1768
2555
|
startRetransmitTimer();
|
|
1769
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
|
+
}
|
|
1770
2564
|
cancelRetransmit();
|
|
1771
2565
|
startNewFlight();
|
|
1772
2566
|
}
|
|
@@ -1779,12 +2573,14 @@ function DTLSSession(options) {
|
|
|
1779
2573
|
// only symptom is a connection that dies after a handshake that
|
|
1780
2574
|
// looked complete, which is exactly the DTLS 1.3 interop case.
|
|
1781
2575
|
_ldbg('ERROR ' + (e && e.message ? e.message : e) +
|
|
1782
|
-
(e && e.
|
|
2576
|
+
(e && e.alertDesc != null ? ' alertDesc=' + e.alertDesc : '') +
|
|
1783
2577
|
(e && e.code ? ' code=' + e.code : ''));
|
|
1784
|
-
|
|
2578
|
+
// TLSSession has sent its alert already.
|
|
2579
|
+
fail(e);
|
|
1785
2580
|
});
|
|
1786
2581
|
|
|
1787
2582
|
tls.on('session', function(ticket) {
|
|
2583
|
+
ctx.lastSession = ticket;
|
|
1788
2584
|
ev.emit('session', ticket);
|
|
1789
2585
|
});
|
|
1790
2586
|
|
|
@@ -1799,12 +2595,20 @@ function DTLSSession(options) {
|
|
|
1799
2595
|
return;
|
|
1800
2596
|
}
|
|
1801
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
|
+
}
|
|
1802
2607
|
|
|
1803
2608
|
let epoch = ctx.selectedVersion === DTLS_VERSION.DTLS1_3
|
|
1804
|
-
? (
|
|
2609
|
+
? appEpoch()
|
|
1805
2610
|
: 1;
|
|
1806
|
-
|
|
1807
|
-
_ldbgTx(record, 'L1383'); ev.emit('packet', record);
|
|
2611
|
+
queueOut({ kind: 'raw', epoch: epoch, contentType: CT.APPLICATION_DATA, payload: data, label: 'data' });
|
|
1808
2612
|
}
|
|
1809
2613
|
|
|
1810
2614
|
|
|
@@ -1821,23 +2625,10 @@ function DTLSSession(options) {
|
|
|
1821
2625
|
// the peer has switched to reading protected records, and a plaintext
|
|
1822
2626
|
// close_notify arrives with a classic DTLSPlaintext header where the peer
|
|
1823
2627
|
// requires the RFC 9147 §4 unified header. It then reports a bad record
|
|
1824
|
-
// type instead of a clean shutdown.
|
|
1825
|
-
//
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
// After a KeyUpdate the live epoch is 4, 5, ... — the peer has moved on
|
|
1829
|
-
// and no longer reads epoch 3, so a close_notify sent there is dropped
|
|
1830
|
-
// and the shutdown looks like a timeout instead of a clean close.
|
|
1831
|
-
epoch = (ctx.writeEpoch !== null && ctx.keys[ctx.writeEpoch])
|
|
1832
|
-
? ctx.writeEpoch
|
|
1833
|
-
: (ctx.keys[3] ? 3 : (ctx.keys[2] ? 2 : 0));
|
|
1834
|
-
} else {
|
|
1835
|
-
epoch = ctx.keys[1] ? 1 : 0;
|
|
1836
|
-
}
|
|
1837
|
-
sendAlertRecord(epoch, new Uint8Array([1, 0])); // warning, close_notify
|
|
1838
|
-
ctx.state = 'closed';
|
|
1839
|
-
cancelRetransmit();
|
|
1840
|
-
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();
|
|
1841
2632
|
ev.emit('close');
|
|
1842
2633
|
}
|
|
1843
2634
|
|
|
@@ -1944,6 +2735,15 @@ function DTLSSession(options) {
|
|
|
1944
2735
|
|
|
1945
2736
|
/** Negotiated ALPN. */
|
|
1946
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,
|
|
1947
2747
|
|
|
1948
2748
|
/** Peer certificate. */
|
|
1949
2749
|
getPeerCertificate: function() { return tls.getPeerCertificate(); },
|
|
@@ -1965,6 +2765,31 @@ function DTLSSession(options) {
|
|
|
1965
2765
|
|
|
1966
2766
|
/** Whether this connection resumed an earlier session. */
|
|
1967
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
|
+
},
|
|
1968
2793
|
};
|
|
1969
2794
|
|
|
1970
2795
|
for (let k in api) {
|
|
@@ -1980,4 +2805,5 @@ function DTLSSession(options) {
|
|
|
1980
2805
|
return this;
|
|
1981
2806
|
}
|
|
1982
2807
|
|
|
1983
|
-
export default DTLSSession;
|
|
2808
|
+
export default DTLSSession;
|
|
2809
|
+
export { SERVER_CID_LEN };
|