lemon-tls 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +168 -41
- package/index.cjs +9 -7
- package/index.d.ts +626 -30
- package/package.json +45 -7
- package/src/ca_store.js +31 -0
- package/src/compat.js +76 -116
- package/src/crypto.js +21 -114
- package/src/dtls_session.js +1284 -390
- package/src/dtls_socket.js +318 -96
- package/src/node_options.js +154 -17
- package/src/record.js +355 -191
- package/src/secure_context.js +97 -39
- package/src/session/ecdh.js +140 -122
- package/src/session/ech.js +92 -32
- package/src/session/hpke.js +12 -44
- package/src/session/message.js +29 -9
- package/src/session/signing.js +80 -22
- package/src/session/ticket.js +5 -3
- package/src/session/x509.js +385 -0
- package/src/tls_session.js +2221 -902
- package/src/tls_socket.js +451 -452
- package/src/wire.js +271 -158
- package/types/crypto.d.ts +40 -0
- package/types/record.d.ts +40 -0
- package/types/session.d.ts +3 -0
- package/types/wire.d.ts +62 -0
package/src/tls_socket.js
CHANGED
|
@@ -3,26 +3,11 @@ import TLSSession from './tls_session.js';
|
|
|
3
3
|
import crypto from 'node:crypto';
|
|
4
4
|
import { Duplex } from 'node:stream';
|
|
5
5
|
|
|
6
|
-
import { TLS_CIPHER_SUITES
|
|
7
|
-
import { TLS_CONTENT_TYPE as CT, TLS_ALERT_LEVEL, TLS_ALERT,
|
|
8
|
-
import {
|
|
9
|
-
import { default_signature_schemes } from './session/signing.js';
|
|
6
|
+
import { TLS_CIPHER_SUITES } from './crypto.js';
|
|
7
|
+
import { TLS_CONTENT_TYPE as CT, TLS_ALERT_LEVEL, TLS_ALERT, peer_alert_error, read_peer_alert, MAX_WARNING_ALERTS, MAX_RECORD_PLAINTEXT, local_alert_error, version_name } from './wire.js';
|
|
8
|
+
import { group_name as ecdh_group_name, dh_group_info } from './session/ecdh.js';
|
|
10
9
|
import { certificateObject } from './utils.js';
|
|
11
|
-
|
|
12
|
-
// The suite list a socket offers or accepts, from the defaults: ChaCha20
|
|
13
|
-
// suites moved to the front when asked (prioritizeChaCha), then narrowed to an
|
|
14
|
-
// allow-list. One function for the client and server paths, which each kept a
|
|
15
|
-
// copy — and both copies knew ChaCha20 by two codes (0x1303, 0xCCA8), so
|
|
16
|
-
// ECDHE_ECDSA_WITH_CHACHA20_POLY1305 (0xCCA9) was never moved forward. The
|
|
17
|
-
// suite table says which suites are ChaCha20.
|
|
18
|
-
function chooseCipherSuites(ciphers, prioritizeChaCha, allowed) {
|
|
19
|
-
if (prioritizeChaCha) {
|
|
20
|
-
let isChaCha = function (c) { return !!TLS_CIPHER_SUITES[c] && TLS_CIPHER_SUITES[c].cipher === 'CHACHA20_POLY1305'; };
|
|
21
|
-
ciphers = ciphers.filter(isChaCha).concat(ciphers.filter(function (c) { return !isChaCha(c); }));
|
|
22
|
-
}
|
|
23
|
-
if (allowed) ciphers = ciphers.filter(function (c) { return allowed.includes(c); });
|
|
24
|
-
return ciphers;
|
|
25
|
-
}
|
|
10
|
+
import { versionsInRange, localLists, pskOptions } from './node_options.js';
|
|
26
11
|
|
|
27
12
|
import {
|
|
28
13
|
getAeadAlgo,
|
|
@@ -46,8 +31,6 @@ import {
|
|
|
46
31
|
// not for an upload — what does not fit goes after the handshake.
|
|
47
32
|
const EARLY_DATA_MAX_TCP = 16384;
|
|
48
33
|
|
|
49
|
-
// Handshake messages: refused on sight past HANDSHAKE_MESSAGE_MAX (wire.js) —
|
|
50
|
-
// before their bytes are kept.
|
|
51
34
|
|
|
52
35
|
// legacy_record_version (TLS 1.3 uses 0x0303 in record header)
|
|
53
36
|
const REC_VERSION = 0x0303;
|
|
@@ -77,17 +60,7 @@ function sigalgCodeToName(code) {
|
|
|
77
60
|
// ==== עזרי המרה ====
|
|
78
61
|
function toBuf(u8){ return Buffer.isBuffer(u8) ? u8 : Buffer.from(u8 || []); }
|
|
79
62
|
|
|
80
|
-
|
|
81
|
-
if (typeof v === 'number') return v;
|
|
82
|
-
if (typeof v === 'string') {
|
|
83
|
-
let s = v.toUpperCase().replace(/[^0-9.]/g, '');
|
|
84
|
-
if (s === '1.3' || s === '13') return 0x0304;
|
|
85
|
-
if (s === '1.2' || s === '12') return 0x0303;
|
|
86
|
-
if (s === '1.1' || s === '11') return 0x0302;
|
|
87
|
-
if (s === '1.0' || s === '10') return 0x0301;
|
|
88
|
-
}
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
63
|
+
|
|
91
64
|
|
|
92
65
|
|
|
93
66
|
|
|
@@ -103,6 +76,10 @@ function TLSSocket(duplex, options){
|
|
|
103
76
|
if (!(this instanceof TLSSocket)) return new TLSSocket(duplex, options);
|
|
104
77
|
options = options || {};
|
|
105
78
|
|
|
79
|
+
// The versions to offer (client) or take (server), newest first — checked
|
|
80
|
+
// here, at construction (node_options.js: one reading for TLS and DTLS).
|
|
81
|
+
const localVersions = versionsInRange(options.minVersion, options.maxVersion, false);
|
|
82
|
+
|
|
106
83
|
// allowHalfOpen follows Node: false unless the caller asks otherwise, so
|
|
107
84
|
// that when the peer stops writing our writable side ends too. This was
|
|
108
85
|
// hardcoded true, which is the OPPOSITE of the documented default — code
|
|
@@ -160,8 +137,11 @@ function TLSSocket(duplex, options){
|
|
|
160
137
|
// External transport (Duplex)
|
|
161
138
|
transport: (duplex && typeof duplex.write === 'function') ? duplex : null,
|
|
162
139
|
|
|
163
|
-
// Internal TLSSession
|
|
164
|
-
|
|
140
|
+
// Internal TLSSession: every option goes on to it (as DTLSSession hands
|
|
141
|
+
// its own on), with what this socket settles itself over them. Picked one
|
|
142
|
+
// by one, an option the list did not name never reached the session —
|
|
143
|
+
// the class of bug that dropped DTLS's (r99) and record_size_limit here.
|
|
144
|
+
session: new TLSSession(Object.assign({}, options, {
|
|
165
145
|
isServer: !!options.isServer,
|
|
166
146
|
servername: options.servername,
|
|
167
147
|
ALPNProtocols: options.ALPNProtocols || null,
|
|
@@ -183,21 +163,12 @@ function TLSSocket(duplex, options){
|
|
|
183
163
|
ocspRequest: typeof options.ocspRequest === 'function' ? options.ocspRequest : null,
|
|
184
164
|
requestOCSP: options.requestOCSP === true,
|
|
185
165
|
session: options.session || null,
|
|
186
|
-
|
|
166
|
+
// External PSK, as node:tls takes it (pskOptions).
|
|
167
|
+
...pskOptions(options, self),
|
|
187
168
|
rejectUnauthorized: options.rejectUnauthorized,
|
|
188
169
|
checkServerIdentity: options.checkServerIdentity,
|
|
189
170
|
ALPNCallback: options.ALPNCallback || null,
|
|
190
171
|
ca: options.ca || _sc('ca') || null,
|
|
191
|
-
// The platform trust store, for when no `ca` is given. compat.js
|
|
192
|
-
// hands both to this socket (defaultCA: Node's root certificates);
|
|
193
|
-
// they were never passed on, so the session had no trust anchors at
|
|
194
|
-
// all, and with none it checks only the name in the certificate. A
|
|
195
|
-
// self-signed certificate for the right name — anyone can make one —
|
|
196
|
-
// was accepted as verified: every connection this library opened
|
|
197
|
-
// (quico.request, WebSocket over wss://) was open to a
|
|
198
|
-
// man-in-the-middle, with rejectUnauthorized true.
|
|
199
|
-
useDefaultCA: options.useDefaultCA,
|
|
200
|
-
defaultCA: options.defaultCA,
|
|
201
172
|
sessionTickets: options.sessionTickets,
|
|
202
173
|
maxHandshakeSize: options.maxHandshakeSize || 0,
|
|
203
174
|
customExtensions: options.customExtensions || [],
|
|
@@ -218,7 +189,7 @@ function TLSSocket(duplex, options){
|
|
|
218
189
|
earlyDataContext: options.earlyDataContext,
|
|
219
190
|
maxEarlyData: typeof options.maxEarlyData === 'number' ? options.maxEarlyData : EARLY_DATA_MAX_TCP,
|
|
220
191
|
endOfEarlyData: true,
|
|
221
|
-
}),
|
|
192
|
+
})),
|
|
222
193
|
|
|
223
194
|
// Handshake write
|
|
224
195
|
handshake_write_key: null,
|
|
@@ -274,6 +245,11 @@ function TLSSocket(duplex, options){
|
|
|
274
245
|
// the records are counted — not in any one message handler.
|
|
275
246
|
consecutive_empty_records: 0,
|
|
276
247
|
consecutive_ccs_records: 0,
|
|
248
|
+
// Warning alerts with nothing else between them, and peer KeyUpdates
|
|
249
|
+
// with no application data between them: each is legal once, and each
|
|
250
|
+
// costs us work (a KeyUpdate a key derivation) while moving nothing.
|
|
251
|
+
consecutive_warning_alerts: 0,
|
|
252
|
+
consecutive_key_updates: 0,
|
|
277
253
|
|
|
278
254
|
// Buffers and queues.
|
|
279
255
|
//
|
|
@@ -306,7 +282,13 @@ function TLSSocket(duplex, options){
|
|
|
306
282
|
// header. Without this, a record carrying EE+Cert+CV+Finished (as
|
|
307
283
|
// OpenSSL/BoringSSL routinely pack) fed only its FIRST message to the
|
|
308
284
|
// session and silently dropped the rest.
|
|
309
|
-
|
|
285
|
+
// A handshake message in pieces (feedHandshakeBytes): its header so far
|
|
286
|
+
// (under 4 bytes), then its pieces and their total.
|
|
287
|
+
hsHdr: null,
|
|
288
|
+
hsParts: null,
|
|
289
|
+
hsFill: 0,
|
|
290
|
+
hsLen: 0,
|
|
291
|
+
hsBufState: null, // the read state the pending bytes arrived under
|
|
310
292
|
|
|
311
293
|
appWriteQueue: [],
|
|
312
294
|
|
|
@@ -329,11 +311,10 @@ function TLSSocket(duplex, options){
|
|
|
329
311
|
ticketKeys: _ticketKeys,
|
|
330
312
|
|
|
331
313
|
// Advanced options
|
|
332
|
-
maxRecordSize: options.maxRecordSize ||
|
|
314
|
+
maxRecordSize: Math.min(options.maxRecordSize || MAX_RECORD_PLAINTEXT, MAX_RECORD_PLAINTEXT),
|
|
333
315
|
sessionTickets: options.sessionTickets !== false,
|
|
334
316
|
pins: options.pins || null, // ['sha256/AAAA...'] certificate pinning
|
|
335
317
|
handshakeTimeout: options.handshakeTimeout || 0, // ms, 0 = no timeout
|
|
336
|
-
allowedCipherSuites: options.allowedCipherSuites || null, // [0x1301, ...] whitelist
|
|
337
318
|
certificateCallback: options.certificateCallback || null, // (info, cb) => cb(null, ctx)
|
|
338
319
|
handshakeTimer: null,
|
|
339
320
|
|
|
@@ -392,16 +373,17 @@ function TLSSocket(duplex, options){
|
|
|
392
373
|
});
|
|
393
374
|
}
|
|
394
375
|
|
|
395
|
-
const MAX_RECORD_PLAINTEXT = 16384; // TLS max record size (2^14)
|
|
396
376
|
|
|
397
377
|
// writeAppData / writeAppDataSingle return the transport's backpressure signal:
|
|
398
378
|
// true → transport is ready for more writes
|
|
399
379
|
// false → transport's internal buffer is full; caller should wait for 'drain'
|
|
400
380
|
// on context.transport before writing more. Propagated up to _write so
|
|
401
381
|
// user sock.write() returns false and Node's stream flow control kicks in.
|
|
382
|
+
// The most a record of ours may carry: 2^14, or the peer's
|
|
383
|
+
// record_size_limit (RFC 8449) when negotiated.
|
|
402
384
|
function writeAppData(plain){
|
|
403
385
|
const total = plain.length;
|
|
404
|
-
const maxSize = context.maxRecordSize || MAX_RECORD_PLAINTEXT;
|
|
386
|
+
const maxSize = Math.min(context.maxRecordSize || MAX_RECORD_PLAINTEXT, session.recordSendLimit());
|
|
405
387
|
if (total > maxSize) {
|
|
406
388
|
// Fragment across multiple records. Cork+uncork lets Node batch the
|
|
407
389
|
// individual writes into a single TCP send (fewer syscalls, less overhead).
|
|
@@ -491,147 +473,239 @@ function TLSSocket(duplex, options){
|
|
|
491
473
|
}
|
|
492
474
|
}
|
|
493
475
|
|
|
494
|
-
//
|
|
495
|
-
//
|
|
496
|
-
//
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
476
|
+
// ============================================================================
|
|
477
|
+
// Reading records: one state, one set of rules
|
|
478
|
+
// ============================================================================
|
|
479
|
+
//
|
|
480
|
+
// What protects the next record from the peer is one of four states, and
|
|
481
|
+
// every rule about what a record may be follows from it — rather than from
|
|
482
|
+
// flags tested here and there (remote_ccs_seen, using_app_keys), which is
|
|
483
|
+
// how a plaintext Finished came to be read after the ServerHello, how a
|
|
484
|
+
// TLS 1.2 record's type came to be guessed instead of authenticated, and
|
|
485
|
+
// how application data came to be taken before the handshake was done.
|
|
486
|
+
//
|
|
487
|
+
// 'plain' — nothing yet: before the TLS 1.3 handshake keys (or before the
|
|
488
|
+
// version is known); a TLS 1.2 handshake before the peer's
|
|
489
|
+
// ChangeCipherSpec.
|
|
490
|
+
// 'hs13' — TLS 1.3 under the handshake traffic keys: after the
|
|
491
|
+
// ServerHello, until the peer's Finished.
|
|
492
|
+
// 'app13' — TLS 1.3 under the application traffic keys.
|
|
493
|
+
// 'tls12' — TLS 1.2 after the peer's ChangeCipherSpec: every record
|
|
494
|
+
// protected, its type authenticated (it is in the AAD).
|
|
495
|
+
function negotiated13() {
|
|
496
|
+
if (context.isTls13 !== undefined) return context.isTls13;
|
|
497
|
+
const v = session.getVersion();
|
|
498
|
+
return v === 0x0304 ? true : v === 0x0303 ? false : undefined;
|
|
499
|
+
}
|
|
500
|
+
// The peer's Finished, verified: from here it writes under its
|
|
501
|
+
// application keys (TLS 1.3), and what it sends is application data it
|
|
502
|
+
// may send (both). Not using_app_keys, which flips at 'secureConnect' —
|
|
503
|
+
// later, for a server (its ticket goes first): a record in between was
|
|
504
|
+
// read under the wrong keys, and a CCS there passed as one before the
|
|
505
|
+
// Finished.
|
|
506
|
+
function peerFinished() {
|
|
507
|
+
return context.using_app_keys === true || (session.context && session.context.remote_finished_ok === true);
|
|
508
|
+
}
|
|
509
|
+
function readState() {
|
|
510
|
+
const v13 = negotiated13();
|
|
511
|
+
if (v13 === false) return context.remote_ccs_seen ? 'tls12' : 'plain';
|
|
512
|
+
if (v13 === true) {
|
|
513
|
+
if (peerFinished()) return 'app13';
|
|
514
|
+
const hs = session.getHandshakeSecrets();
|
|
515
|
+
if (hs && hs.remoteSecret) return 'hs13';
|
|
516
|
+
}
|
|
517
|
+
return 'plain';
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Every fatal condition of the record layer ends the same way: our alert,
|
|
521
|
+
// both layers inert, the transport ended after the alert has gone (end(),
|
|
522
|
+
// which also uncorks the flight the alert sits in — destroy() dropped it:
|
|
523
|
+
// 336 of 338 bad_record_mac alerts were never sent), then the report.
|
|
524
|
+
function abort(alert, message, code) {
|
|
525
|
+
if (context.destroyed) return;
|
|
526
|
+
const err = local_alert_error(alert, message, code);
|
|
527
|
+
try { session.sendAlert(2, alert); } catch (e) {}
|
|
528
|
+
markTransportDead();
|
|
529
|
+
closeAfterAlert();
|
|
530
|
+
self.emit('error', err);
|
|
531
|
+
}
|
|
525
532
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
533
|
+
// A record's plaintext, by its content type — the decrypted one, or a
|
|
534
|
+
// cleartext record's own (`clear`). Content longer than a record carries
|
|
535
|
+
// is record_overflow (RFC 8446 §5.4, RFC 5246 §6.2.1); a handshake or
|
|
536
|
+
// alert fragment of zero bytes is not one (§5.1).
|
|
537
|
+
function dispatchContent(contentType, content, state, clear) {
|
|
538
|
+
if (content.length > MAX_RECORD_PLAINTEXT) {
|
|
539
|
+
abort(TLS_ALERT.RECORD_OVERFLOW, 'record of ' + content.length + ' bytes of plaintext, more than 2^14', 'ERR_SSL_RECORD_OVERFLOW');
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
// Ours, once negotiated (RFC 8449 §4: "MUST generate a fatal
|
|
543
|
+
// record_overflow alert") — on protected records: "Unprotected
|
|
544
|
+
// messages are not subject to this limit" (a TLS 1.2 Certificate in
|
|
545
|
+
// the clear, in full records, was refused). As DTLS.
|
|
546
|
+
const rsl = clear ? null : session.getRecordSizeLimit();
|
|
547
|
+
if (rsl && content.length > rsl.receive) {
|
|
548
|
+
abort(TLS_ALERT.RECORD_OVERFLOW, 'record of ' + content.length + ' bytes of plaintext, more than our record_size_limit (' + rsl.receive + ')', 'ERR_SSL_RECORD_OVERFLOW');
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
if (contentType === CT.APPLICATION_DATA) {
|
|
552
|
+
// Only once the handshake is done: under TLS 1.3's application
|
|
553
|
+
// keys; in TLS 1.2 after the peer's Finished.
|
|
554
|
+
// Before that, it was handed to the reader all the same.
|
|
555
|
+
if (state === 'app13' || (state === 'tls12' && peerFinished())) {
|
|
556
|
+
// In the middle of a handshake message (a post-handshake one
|
|
557
|
+
// in pieces): "MUST NOT be interleaved with other record
|
|
558
|
+
// types" (RFC 8446 §5.1).
|
|
559
|
+
if (hsPending()) {
|
|
560
|
+
abort(TLS_ALERT.UNEXPECTED_MESSAGE, 'application data inside a handshake message', 'ERR_SSL_UNEXPECTED_MESSAGE');
|
|
529
561
|
return;
|
|
530
562
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
563
|
+
if (content.length > 0) {
|
|
564
|
+
context.consecutive_key_updates = 0;
|
|
565
|
+
context.consecutive_warning_alerts = 0;
|
|
566
|
+
pushAppData(content);
|
|
567
|
+
}
|
|
568
|
+
return;
|
|
537
569
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
570
|
+
abort(TLS_ALERT.UNEXPECTED_MESSAGE, 'application data before the handshake is complete', 'ERR_SSL_UNEXPECTED_MESSAGE');
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
if (contentType === CT.HANDSHAKE) {
|
|
574
|
+
if (content.length === 0) { abort(TLS_ALERT.UNEXPECTED_MESSAGE, 'empty handshake record', 'ERR_SSL_UNEXPECTED_MESSAGE'); return; }
|
|
575
|
+
context.consecutive_warning_alerts = 0;
|
|
576
|
+
// RFC 8446 §5.1: "Handshake messages MUST NOT span key changes"
|
|
577
|
+
// — a message begun under one read state and continued under
|
|
578
|
+
// another (RFC 5246's CCS has its own check, handleChangeCipherSpec).
|
|
579
|
+
if (hsPending() && context.hsBufState !== state) {
|
|
580
|
+
abort(TLS_ALERT.UNEXPECTED_MESSAGE, 'a handshake message spans a key change', 'ERR_SSL_UNEXPECTED_MESSAGE');
|
|
581
|
+
return;
|
|
550
582
|
}
|
|
583
|
+
context.hsBufState = state;
|
|
584
|
+
feedHandshakeBytes(content);
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
if (contentType === CT.ALERT) { handleAlert(content); return; }
|
|
588
|
+
// Anything else — a ChangeCipherSpec inside protection (§5: "MUST
|
|
589
|
+
// abort"), content type 0, an unknown one: unexpected_message.
|
|
590
|
+
abort(TLS_ALERT.UNEXPECTED_MESSAGE, 'unexpected content type ' + contentType + ' in a ' + state + ' record', 'ERR_SSL_UNEXPECTED_MESSAGE');
|
|
591
|
+
}
|
|
551
592
|
|
|
593
|
+
// TLS 1.3 protected record (outer type application_data), under the keys
|
|
594
|
+
// of `state`. trial: early data not taken may come before the client's
|
|
595
|
+
// handshake flight (RFC 8446 §4.2.10): a record the handshake key does not
|
|
596
|
+
// open is that, and is skipped (false) rather than fatal.
|
|
597
|
+
function openRecord13(body, header, state, trial) {
|
|
598
|
+
let out;
|
|
599
|
+
try {
|
|
552
600
|
const algo = context.aeadAlgo || getAeadAlgo(session.getCipher());
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
601
|
+
if (state === 'app13') {
|
|
602
|
+
if (context.app_read_key === null) {
|
|
603
|
+
const ts = session.getTrafficSecrets();
|
|
604
|
+
if (ts.remoteAppSecret === null) throw new Error('no application read key');
|
|
605
|
+
const d = tls_derive_from_tls_secrets(ts.remoteAppSecret, ts.cipher);
|
|
606
|
+
context.app_read_key = d.key;
|
|
607
|
+
context.app_read_iv = d.iv;
|
|
608
|
+
}
|
|
609
|
+
out = decrypt_tls_record_with_aad(header, body, context.app_read_key,
|
|
610
|
+
get_nonce_into(context._nonceDecScratch, context.app_read_iv, context.app_read_seq), algo);
|
|
611
|
+
context.app_read_seq++;
|
|
560
612
|
} else {
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
context.handshake_read_key = d.key;
|
|
572
|
-
context.handshake_read_iv = d.iv;
|
|
613
|
+
if (context.handshake_read_key === null) {
|
|
614
|
+
const hs = session.getHandshakeSecrets();
|
|
615
|
+
if (hs.remoteSecret === null || hs.cipher === null) throw new Error('no handshake read key');
|
|
616
|
+
const d = tls_derive_from_tls_secrets(hs.remoteSecret, hs.cipher);
|
|
617
|
+
context.handshake_read_key = d.key;
|
|
618
|
+
context.handshake_read_iv = d.iv;
|
|
619
|
+
}
|
|
620
|
+
out = decrypt_tls_record_with_aad(header, body, context.handshake_read_key,
|
|
621
|
+
get_nonce_into(context._nonceDecScratch, context.handshake_read_iv, context.handshake_read_seq), algo);
|
|
622
|
+
context.handshake_read_seq++;
|
|
573
623
|
}
|
|
624
|
+
} catch (e) {
|
|
625
|
+
// RFC 8446 §5.2: a record that does not open is fatal. The read
|
|
626
|
+
// sequence is not advanced — the rejected record never counts.
|
|
627
|
+
if (trial) return false;
|
|
628
|
+
abort(TLS_ALERT.BAD_RECORD_MAC, 'TLS record decryption failed (bad_record_mac)', 'ERR_SSL_BAD_RECORD_MAC');
|
|
629
|
+
return false;
|
|
630
|
+
}
|
|
631
|
+
if (trial) context.early.trialDone = true;
|
|
632
|
+
// TLSInnerPlaintext — content, type and padding together — is at
|
|
633
|
+
// most 2^14 + 1 bytes (RFC 8446 §5.4: "MUST NOT exceed"). Checked on
|
|
634
|
+
// the whole, not on the content once the padding is gone: 2^14 − 8
|
|
635
|
+
// bytes with 9 of padding passed.
|
|
636
|
+
if (out.length > MAX_RECORD_PLAINTEXT + 1) {
|
|
637
|
+
abort(TLS_ALERT.RECORD_OVERFLOW, 'TLSInnerPlaintext of ' + out.length + ' bytes, more than 2^14 + 1', 'ERR_SSL_RECORD_OVERFLOW');
|
|
638
|
+
return true;
|
|
639
|
+
}
|
|
640
|
+
// TLSInnerPlaintext: content, the type, then zeros. All zeros: no
|
|
641
|
+
// type at all (§5.4: unexpected_message) — it was an 'error' event
|
|
642
|
+
// and a connection left open.
|
|
643
|
+
let j = out.length - 1;
|
|
644
|
+
while (j >= 0 && out[j] === 0) j--;
|
|
645
|
+
if (j < 0) {
|
|
646
|
+
abort(TLS_ALERT.UNEXPECTED_MESSAGE, 'TLSInnerPlaintext with no content type', 'ERR_SSL_UNEXPECTED_MESSAGE');
|
|
647
|
+
return true;
|
|
648
|
+
}
|
|
649
|
+
dispatchContent(out[j], out.subarray(0, j), state);
|
|
650
|
+
return true;
|
|
651
|
+
}
|
|
574
652
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
653
|
+
// TLS 1.2 protected record: its type — the header's — is part of the AAD
|
|
654
|
+
// (RFC 5246 §6.2.3.3). It was guessed from the state (handshake until the
|
|
655
|
+
// handshake was done, application data after), so a record relabelled
|
|
656
|
+
// from one to the other on the way still opened; and an encrypted alert
|
|
657
|
+
// was read as a cleartext one.
|
|
658
|
+
function openRecord12(type, body) {
|
|
659
|
+
let out;
|
|
660
|
+
try {
|
|
661
|
+
if (context.app_read_key === null) {
|
|
662
|
+
const ts = session.getTrafficSecrets();
|
|
663
|
+
if (!ts.masterSecret) throw new Error('no master secret');
|
|
664
|
+
const d12 = deriveKeys12(ts.masterSecret, ts.localRandom, ts.remoteRandom, ts.cipher, ts.isServer);
|
|
665
|
+
context.app_read_key = d12.readKey;
|
|
666
|
+
context.app_read_iv = d12.readIv;
|
|
667
|
+
context.app_write_key = d12.writeKey;
|
|
668
|
+
context.app_write_iv = d12.writeIv;
|
|
581
669
|
}
|
|
582
|
-
context.
|
|
583
|
-
|
|
670
|
+
out = decrypt_tls12_fragment(body, context.app_read_key, context.app_read_iv, context.tls12_read_seq, type);
|
|
671
|
+
context.tls12_read_seq++;
|
|
584
672
|
} catch (e) {
|
|
585
|
-
|
|
586
|
-
// Record failed authentication → fatal bad_record_mac (RFC 8446 §5.2).
|
|
587
|
-
try { session.sendAlert ? session.sendAlert(2, TLS_ALERT.BAD_RECORD_MAC) : null; } catch (_) {}
|
|
588
|
-
self.emit('error', new Error('TLS record decryption failed (bad_record_mac)'));
|
|
589
|
-
self.destroy();
|
|
673
|
+
abort(TLS_ALERT.BAD_RECORD_MAC, 'TLS record decryption failed (bad_record_mac)', 'ERR_SSL_BAD_RECORD_MAC');
|
|
590
674
|
return;
|
|
591
675
|
}
|
|
676
|
+
dispatchContent(type, out, 'tls12');
|
|
677
|
+
}
|
|
592
678
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
// They were decrypted and dropped here.
|
|
619
|
-
handleAlert(out.subarray(0, j));
|
|
620
|
-
}
|
|
621
|
-
} else {
|
|
622
|
-
// TLS 1.2: no inner plaintext wrapping — `out` is the Buffer from decrypt12
|
|
623
|
-
if (context.using_app_keys === true) {
|
|
624
|
-
pushAppData(out);
|
|
625
|
-
} else {
|
|
626
|
-
feedHandshakeBytes(out);
|
|
627
|
-
}
|
|
679
|
+
// ChangeCipherSpec: one byte, 1 (RFC 8446 §5, RFC 5246 §7.1).
|
|
680
|
+
// TLS 1.2: once, when the peer's keys exist (the master secret is
|
|
681
|
+
// derived) and no handshake message is cut by it (§5.1 of 8446 holds
|
|
682
|
+
// the same for 1.2: messages do not straddle a key change).
|
|
683
|
+
// TLS 1.3: a middlebox-compatibility record, dropped — "at any time
|
|
684
|
+
// after the first ClientHello ... and before the peer's Finished"
|
|
685
|
+
// (§5); not before the version is known, not after the Finished.
|
|
686
|
+
// Anything else: unexpected_message. A CCS of two bytes, or one after the
|
|
687
|
+
// Finished, used to be passed over in silence.
|
|
688
|
+
function handleChangeCipherSpec(body, state) {
|
|
689
|
+
if (body.length !== 1 || body[0] !== 0x01) {
|
|
690
|
+
abort(TLS_ALERT.UNEXPECTED_MESSAGE, 'malformed ChangeCipherSpec (' + body.length + ' bytes)', 'ERR_SSL_UNEXPECTED_MESSAGE');
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
const v13 = negotiated13();
|
|
694
|
+
if (v13 === true) {
|
|
695
|
+
if (state === 'app13') abort(TLS_ALERT.UNEXPECTED_MESSAGE, 'ChangeCipherSpec after the Finished', 'ERR_SSL_UNEXPECTED_MESSAGE');
|
|
696
|
+
return; // compatibility record: dropped, no sequence advanced
|
|
697
|
+
}
|
|
698
|
+
if (v13 === false && state === 'plain') {
|
|
699
|
+
const ts = session.getTrafficSecrets();
|
|
700
|
+
if (ts && ts.masterSecret && !hsPending()) {
|
|
701
|
+
context.tls12_read_seq = 0;
|
|
702
|
+
context.remote_ccs_seen = true;
|
|
703
|
+
return;
|
|
628
704
|
}
|
|
629
705
|
}
|
|
630
|
-
|
|
631
|
-
return out !== null;
|
|
706
|
+
abort(TLS_ALERT.UNEXPECTED_MESSAGE, 'unexpected ChangeCipherSpec', 'ERR_SSL_UNEXPECTED_MESSAGE');
|
|
632
707
|
}
|
|
633
708
|
|
|
634
|
-
|
|
635
709
|
// ============================================================================
|
|
636
710
|
// Early data (0-RTT over TCP, RFC 8446 §4.2.10, §4.5)
|
|
637
711
|
// ============================================================================
|
|
@@ -670,10 +744,7 @@ function TLSSocket(duplex, options){
|
|
|
670
744
|
}
|
|
671
745
|
|
|
672
746
|
function earlyTooMuch() {
|
|
673
|
-
|
|
674
|
-
self.emit('error', new Error('More early data than max_early_data_size'));
|
|
675
|
-
markTransportDead();
|
|
676
|
-
closeAfterAlert();
|
|
747
|
+
abort(TLS_ALERT.UNEXPECTED_MESSAGE, 'More early data than max_early_data_size', 'ERR_SSL_UNEXPECTED_MESSAGE');
|
|
677
748
|
}
|
|
678
749
|
|
|
679
750
|
// A record of early data not taken: skipped, counted (its plaintext at
|
|
@@ -690,24 +761,23 @@ function TLSSocket(duplex, options){
|
|
|
690
761
|
try {
|
|
691
762
|
out = decrypt_tls_record_with_aad(header, body, r.key, get_nonce_into(context._nonceDecScratch, r.iv, r.seq), r.algo);
|
|
692
763
|
} catch (e) {
|
|
693
|
-
|
|
694
|
-
self.emit('error', new Error('TLS early data record decryption failed (bad_record_mac)'));
|
|
695
|
-
self.destroy();
|
|
764
|
+
abort(TLS_ALERT.BAD_RECORD_MAC, 'TLS early data record decryption failed (bad_record_mac)', 'ERR_SSL_BAD_RECORD_MAC');
|
|
696
765
|
return;
|
|
697
766
|
}
|
|
698
767
|
r.seq++;
|
|
699
768
|
let j = out.length - 1;
|
|
700
769
|
while (j >= 0 && out[j] === 0) j--;
|
|
701
|
-
if (j < 0) {
|
|
770
|
+
if (j < 0) { abort(TLS_ALERT.UNEXPECTED_MESSAGE, 'TLSInnerPlaintext with no content type', 'ERR_SSL_UNEXPECTED_MESSAGE'); return; }
|
|
702
771
|
const ct = out[j];
|
|
703
772
|
if (ct === CT.APPLICATION_DATA) {
|
|
773
|
+
if (j > MAX_RECORD_PLAINTEXT) { abort(TLS_ALERT.RECORD_OVERFLOW, 'early data record of ' + j + ' bytes, more than 2^14', 'ERR_SSL_RECORD_OVERFLOW'); return; }
|
|
704
774
|
context.early.recv += j;
|
|
705
775
|
if (context.early.recv > earlyLimit()) { earlyTooMuch(); return; }
|
|
706
776
|
if (j > 0) pushAppData(out.subarray(0, j));
|
|
707
|
-
} else
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
777
|
+
} else {
|
|
778
|
+
// EndOfEarlyData (the session checks it is that), an alert, or
|
|
779
|
+
// anything else — the same rules as any other record.
|
|
780
|
+
dispatchContent(ct, out.subarray(0, j), 'hs13');
|
|
711
781
|
}
|
|
712
782
|
}
|
|
713
783
|
|
|
@@ -830,6 +900,35 @@ function TLSSocket(duplex, options){
|
|
|
830
900
|
context.readEnd = needed;
|
|
831
901
|
}
|
|
832
902
|
|
|
903
|
+
// A handshake message, protected: in its records (handshakeFragments),
|
|
904
|
+
// each sealed and framed in one step (as writeAppDataSingle) under the
|
|
905
|
+
// keys named — TLS 1.3's (handshake or application) or 1.2's.
|
|
906
|
+
function writeProtectedHandshake(buf, v13, keyField, ivField, seqField) {
|
|
907
|
+
const algo = v13 ? (context.aeadAlgo || getAeadAlgo(session.getCipher())) : null;
|
|
908
|
+
for (const part of handshakeFragments(buf)) {
|
|
909
|
+
const rec = v13
|
|
910
|
+
? encryptCompleteRecord13(CT.HANDSHAKE, part, context[keyField],
|
|
911
|
+
get_nonce_into(context._nonceEncScratch, context[ivField], context[seqField]), algo, context.rec_version)
|
|
912
|
+
: encryptCompleteRecord12(part, context[keyField], context[ivField], context[seqField], CT.HANDSHAKE, context.rec_version);
|
|
913
|
+
context[seqField]++;
|
|
914
|
+
if (canWrite()) context.transport.write(rec);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// A handshake message, in records of at most 2^14 bytes (RFC 8446 §5.1,
|
|
919
|
+
// RFC 5246 §6.2.1: "MUST NOT exceed 2^14 bytes") — and of at most the
|
|
920
|
+
// peer's record_size_limit, once it is known. It went in one record
|
|
921
|
+
// however long: a chain over 16 KB was refused by OpenSSL (record
|
|
922
|
+
// overflow), and over 64 KB the record's 16-bit length wrapped — the
|
|
923
|
+
// stream after it unreadable. Post-quantum (ML-DSA) chains reach both.
|
|
924
|
+
function handshakeFragments(buf) {
|
|
925
|
+
const max = session.recordSendLimit();
|
|
926
|
+
if (buf.length <= max) return [buf];
|
|
927
|
+
let out = [];
|
|
928
|
+
for (let o = 0; o < buf.length; o += max) out.push(buf.subarray(o, Math.min(o + max, buf.length)));
|
|
929
|
+
return out;
|
|
930
|
+
}
|
|
931
|
+
|
|
833
932
|
/**
|
|
834
933
|
* Handshake message reassembly (RFC 8446 §5.1).
|
|
835
934
|
*
|
|
@@ -842,38 +941,63 @@ function TLSSocket(duplex, options){
|
|
|
842
941
|
*
|
|
843
942
|
* Note on memory: session.message() stashes views of the fed bytes into
|
|
844
943
|
* its transcript, so every message handed over must be backed by memory
|
|
845
|
-
* we never overwrite
|
|
846
|
-
*
|
|
847
|
-
*
|
|
944
|
+
* we never overwrite: each is copied out of the (reused) record buffers.
|
|
945
|
+
*
|
|
946
|
+
* Linear in the message: its pieces are kept as they come and joined
|
|
947
|
+
* once, when the last arrives. The buffer was joined anew with every
|
|
948
|
+
* record — a 128 KB ClientHello in 1-byte records cost 1.9 s of CPU and
|
|
949
|
+
* 200 MB, before anything about it was checked.
|
|
848
950
|
*/
|
|
951
|
+
function hsPending() { return context.hsHdr !== null || context.hsParts !== null; }
|
|
952
|
+
function hsReset() { context.hsHdr = null; context.hsParts = null; context.hsFill = 0; context.hsLen = 0; }
|
|
849
953
|
function feedHandshakeBytes(bytes){
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
954
|
+
let p = 0;
|
|
955
|
+
while (p < bytes.length) {
|
|
956
|
+
if (context.hsParts === null) {
|
|
957
|
+
// The header: 4 bytes, maybe across records.
|
|
958
|
+
let have = context.hsHdr === null ? 0 : context.hsHdr.length;
|
|
959
|
+
let take = Math.min(4 - have, bytes.length - p);
|
|
960
|
+
let hdr = have === 0 ? Buffer.from(bytes.subarray(p, p + take))
|
|
961
|
+
: Buffer.concat([context.hsHdr, Buffer.from(bytes.subarray(p, p + take))]);
|
|
962
|
+
p += take;
|
|
963
|
+
if (hdr.length < 4) { context.hsHdr = hdr; return; }
|
|
964
|
+
context.hsHdr = null;
|
|
965
|
+
const len = (hdr[1] << 16) | (hdr[2] << 8) | hdr[3];
|
|
966
|
+
const maxMsg = session.handshakeMessageLimit();
|
|
967
|
+
if (len > maxMsg) {
|
|
968
|
+
// OpenSSL's answer to an excessive message: illegal_parameter.
|
|
969
|
+
hsReset();
|
|
970
|
+
abort(TLS_ALERT.ILLEGAL_PARAMETER, 'TLS handshake message of ' + len + ' bytes, more than ' + maxMsg);
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
context.hsParts = [hdr]; context.hsFill = 4; context.hsLen = 4 + len;
|
|
974
|
+
}
|
|
975
|
+
let take = Math.min(context.hsLen - context.hsFill, bytes.length - p);
|
|
976
|
+
if (take > 0) {
|
|
977
|
+
context.hsParts.push(Buffer.from(bytes.subarray(p, p + take)));
|
|
978
|
+
context.hsFill += take; p += take;
|
|
866
979
|
}
|
|
867
|
-
if (context.
|
|
980
|
+
if (context.hsFill < context.hsLen) return; // partial message — wait for more records
|
|
868
981
|
|
|
869
|
-
const msg = context.
|
|
870
|
-
|
|
982
|
+
const msg = context.hsParts.length === 1 ? context.hsParts[0] : Buffer.concat(context.hsParts, context.hsLen);
|
|
983
|
+
hsReset();
|
|
871
984
|
|
|
872
985
|
session.message(msg);
|
|
873
986
|
|
|
874
987
|
// A message may have fatally ended the session (alert sent,
|
|
875
988
|
// transport torn down). Don't keep feeding a corpse.
|
|
876
989
|
if (context.destroyed) return;
|
|
990
|
+
|
|
991
|
+
// The message changed the read keys (a ServerHello, a Finished, a
|
|
992
|
+
// KeyUpdate): what follows it in the same record was protected
|
|
993
|
+
// under the old ones, and is no message of the new state (RFC
|
|
994
|
+
// 8446 §5.1: "MUST align with a record boundary"). A KeyUpdate
|
|
995
|
+
// leaves the read state's name as it was ('app13'), so it is
|
|
996
|
+
// named here: two in one record were taken.
|
|
997
|
+
if (p < bytes.length && (readState() !== context.hsBufState || msg[0] === 24)) {
|
|
998
|
+
abort(TLS_ALERT.UNEXPECTED_MESSAGE, 'handshake data after a key change, in the record before it', 'ERR_SSL_UNEXPECTED_MESSAGE');
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
877
1001
|
}
|
|
878
1002
|
}
|
|
879
1003
|
|
|
@@ -923,25 +1047,25 @@ function TLSSocket(duplex, options){
|
|
|
923
1047
|
// in silence; OpenSSL rejects on the header ("wrong version
|
|
924
1048
|
// number"), and so does this now, with node:tls's codes.
|
|
925
1049
|
// RFC 8446 §5.1: legacy_record_version is 0x03xx, ContentType
|
|
926
|
-
// one of the four
|
|
927
|
-
//
|
|
1050
|
+
// one of the four. The length, by the state the record is read
|
|
1051
|
+
// in: a cleartext record carries at most 2^14 bytes; a TLS 1.3
|
|
1052
|
+
// protected one 2^14 + 256 (§5.2) — early data, still before
|
|
1053
|
+
// the handshake keys, is one; a TLS 1.2 one 2^14 + 2048
|
|
1054
|
+
// (RFC 5246 §6.2.3). It was 2^14 + 2048 for all.
|
|
1055
|
+
const state = readState();
|
|
928
1056
|
{
|
|
929
1057
|
const recVer = (rb[off + 1] << 8) | rb[off + 2];
|
|
930
|
-
|
|
1058
|
+
const maxLen = state === 'tls12' ? MAX_RECORD_PLAINTEXT + 2048
|
|
1059
|
+
: (state === 'hs13' || state === 'app13' || type === CT.APPLICATION_DATA) ? MAX_RECORD_PLAINTEXT + 256
|
|
1060
|
+
: MAX_RECORD_PLAINTEXT;
|
|
931
1061
|
if ((recVer >>> 8) !== 0x03 || type < CT.CHANGE_CIPHER_SPEC || type > CT.APPLICATION_DATA) {
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
bad = new Error('record overflow: ' + len + ' bytes');
|
|
937
|
-
bad.code = 'ERR_SSL_RECORD_OVERFLOW';
|
|
938
|
-
alert = TLS_ALERT.RECORD_OVERFLOW;
|
|
1062
|
+
abort((recVer >>> 8) !== 0x03 ? TLS_ALERT.PROTOCOL_VERSION : TLS_ALERT.UNEXPECTED_MESSAGE,
|
|
1063
|
+
'wrong version number: not a TLS record (type 0x' + type.toString(16) + ', version 0x' + recVer.toString(16) + ')',
|
|
1064
|
+
'ERR_SSL_WRONG_VERSION_NUMBER');
|
|
1065
|
+
return;
|
|
939
1066
|
}
|
|
940
|
-
if (
|
|
941
|
-
|
|
942
|
-
self.emit('error', bad);
|
|
943
|
-
markTransportDead();
|
|
944
|
-
closeAfterAlert();
|
|
1067
|
+
if (len > maxLen) {
|
|
1068
|
+
abort(TLS_ALERT.RECORD_OVERFLOW, 'record overflow: ' + len + ' bytes, more than ' + maxLen, 'ERR_SSL_RECORD_OVERFLOW');
|
|
945
1069
|
return;
|
|
946
1070
|
}
|
|
947
1071
|
}
|
|
@@ -959,86 +1083,57 @@ function TLSSocket(duplex, options){
|
|
|
959
1083
|
// CPU (BoGo: TooManyEmptyFragments / TooManyChangeCipherSpec).
|
|
960
1084
|
if (len === 0) {
|
|
961
1085
|
if (++context.consecutive_empty_records > 32) {
|
|
962
|
-
|
|
963
|
-
self.emit('error', new Error('Too many consecutive empty records'));
|
|
964
|
-
markTransportDead();
|
|
965
|
-
closeAfterAlert();
|
|
1086
|
+
abort(TLS_ALERT.UNEXPECTED_MESSAGE, 'Too many consecutive empty records');
|
|
966
1087
|
return;
|
|
967
1088
|
}
|
|
968
1089
|
} else {
|
|
969
1090
|
context.consecutive_empty_records = 0;
|
|
970
1091
|
}
|
|
971
|
-
|
|
972
1092
|
if (type === CT.CHANGE_CIPHER_SPEC) {
|
|
973
1093
|
if (++context.consecutive_ccs_records > 8) {
|
|
974
|
-
|
|
975
|
-
self.emit('error', new Error('Too many consecutive ChangeCipherSpec records'));
|
|
976
|
-
markTransportDead();
|
|
977
|
-
closeAfterAlert();
|
|
1094
|
+
abort(TLS_ALERT.UNEXPECTED_MESSAGE, 'Too many consecutive ChangeCipherSpec records');
|
|
978
1095
|
return;
|
|
979
1096
|
}
|
|
980
1097
|
} else {
|
|
981
1098
|
context.consecutive_ccs_records = 0;
|
|
982
1099
|
}
|
|
983
1100
|
|
|
984
|
-
|
|
1101
|
+
// Early data, at a TLS 1.3 server that has not yet the client's
|
|
1102
|
+
// Finished: its own rules (see "Early data").
|
|
1103
|
+
if (type === CT.APPLICATION_DATA && session.isServer && state !== 'app13' && state !== 'tls12' && negotiated13() !== false) {
|
|
985
1104
|
const em = serverEarlyMode();
|
|
986
1105
|
if (em === 'pending') { off -= 5 + len; break; } // the decision first; this record waits
|
|
987
1106
|
if (em === 'skip') { if (!skipEarly(len)) return; continue; }
|
|
988
1107
|
if (em === 'accepted') { processEarlyRecord(body, header); if (context.destroyed) return; continue; }
|
|
989
|
-
if (em === 'trial') { if (!
|
|
1108
|
+
if (em === 'trial' && state === 'hs13') { if (!openRecord13(body, header, state, true) && !skipEarly(len)) return; continue; }
|
|
990
1109
|
}
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
} else if (
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1110
|
+
|
|
1111
|
+
if (type === CT.CHANGE_CIPHER_SPEC) {
|
|
1112
|
+
handleChangeCipherSpec(body, state);
|
|
1113
|
+
} else if (state === 'tls12') {
|
|
1114
|
+
// Everything after the peer's CCS is protected, its type
|
|
1115
|
+
// authenticated with it.
|
|
1116
|
+
openRecord12(type, body);
|
|
1117
|
+
} else if (state === 'hs13' || state === 'app13') {
|
|
1118
|
+
if (type === CT.APPLICATION_DATA) {
|
|
1119
|
+
openRecord13(body, header, state, false);
|
|
1120
|
+
} else if (type === CT.ALERT && state === 'hs13') {
|
|
1121
|
+
// A peer that cannot yet protect its alert — a client
|
|
1122
|
+
// refusing the ServerHello before it has the keys.
|
|
1123
|
+
dispatchContent(CT.ALERT, body, state, true);
|
|
999
1124
|
} else {
|
|
1000
|
-
//
|
|
1001
|
-
//
|
|
1002
|
-
//
|
|
1003
|
-
//
|
|
1004
|
-
|
|
1005
|
-
//
|
|
1006
|
-
// When the NEXT incoming chunk triggers readBuffer compact/reuse,
|
|
1007
|
-
// those stashed views would point to overwritten bytes — causing
|
|
1008
|
-
// bad key derivations and GCM tag failures much later. We must hand
|
|
1009
|
-
// the session an owned Buffer so its transcript survives.
|
|
1010
|
-
feedHandshakeBytes(body);
|
|
1125
|
+
// A cleartext handshake record — or alert — where only
|
|
1126
|
+
// protected records may be (RFC 8446 §5: unexpected_message).
|
|
1127
|
+
// A cleartext Finished was taken here, and a KeyUpdate
|
|
1128
|
+
// injected on the path was acted on.
|
|
1129
|
+
abort(TLS_ALERT.UNEXPECTED_MESSAGE, 'unprotected record (type ' + type + ') after the handshake keys', 'ERR_SSL_UNEXPECTED_MESSAGE');
|
|
1011
1130
|
}
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
//
|
|
1015
|
-
|
|
1016
|
-
// are encrypted. Flip remote_ccs_seen so type-22 records are
|
|
1017
|
-
// routed to the decrypt path.
|
|
1018
|
-
// - TLS 1.3: a middlebox-compat artifact (RFC 8446 §D.4)
|
|
1019
|
-
// carrying no meaning; cipher state is driven by the key
|
|
1020
|
-
// schedule. It MUST be ignored and MUST NOT advance any
|
|
1021
|
-
// read sequence.
|
|
1022
|
-
//
|
|
1023
|
-
// The test is "do we POSITIVELY know this is TLS 1.2", not
|
|
1024
|
-
// "is it not 1.3", and that is the correct test on its own
|
|
1025
|
-
// merits: a CCS carries no meaning unless we have actually
|
|
1026
|
-
// negotiated 1.2, so an unknown version must not be treated as
|
|
1027
|
-
// a cipher change. (It also used to be load-bearing for a
|
|
1028
|
-
// second reason — the HelloRetryRequest path did not record
|
|
1029
|
-
// selected_version, so mid-HRR the version was simply unknown.
|
|
1030
|
-
// That gap is fixed at its source now, but the rule stands.)
|
|
1031
|
-
const ccsVersion = (context.isTls13 !== undefined)
|
|
1032
|
-
? (context.isTls13 ? 0x0304 : 0x0303)
|
|
1033
|
-
: session.getVersion();
|
|
1034
|
-
if (ccsVersion === 0x0303) {
|
|
1035
|
-
context.tls12_read_seq = 0;
|
|
1036
|
-
context.remote_ccs_seen = true;
|
|
1037
|
-
}
|
|
1038
|
-
// TLS 1.3, or version not yet negotiated: swallow silently.
|
|
1039
|
-
} else if (type === CT.ALERT) {
|
|
1040
|
-
handleAlert(body);
|
|
1131
|
+
} else {
|
|
1132
|
+
// 'plain': handshake and alerts in the clear; application data
|
|
1133
|
+
// has no place before the keys.
|
|
1134
|
+
dispatchContent(type, body, state, true);
|
|
1041
1135
|
}
|
|
1136
|
+
if (context.destroyed) return;
|
|
1042
1137
|
}
|
|
1043
1138
|
|
|
1044
1139
|
// Reset offsets when buffer is fully drained (enables fast path on next chunk)
|
|
@@ -1108,10 +1203,16 @@ function TLSSocket(duplex, options){
|
|
|
1108
1203
|
// certificate") — without it the socket just closed and nothing learned
|
|
1109
1204
|
// why: a server could not see that clients were refusing its certificate.
|
|
1110
1205
|
function handleAlert(body) {
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
const desc
|
|
1114
|
-
|
|
1206
|
+
const a = read_peer_alert(body, negotiated13() === true);
|
|
1207
|
+
if (a.error) { abort(a.error.alert, a.error.message); return; }
|
|
1208
|
+
const desc = a.desc;
|
|
1209
|
+
const level = a.fatal ? TLS_ALERT_LEVEL.FATAL : TLS_ALERT_LEVEL.WARNING;
|
|
1210
|
+
self.emit('alert', { level: a.level, description: desc });
|
|
1211
|
+
// A run of warnings, nothing else between them.
|
|
1212
|
+
if (!a.fatal && desc !== TLS_ALERT.CLOSE_NOTIFY && ++context.consecutive_warning_alerts > MAX_WARNING_ALERTS) {
|
|
1213
|
+
abort(TLS_ALERT.UNEXPECTED_MESSAGE, 'too many consecutive warning alerts', 'ERR_SSL_UNEXPECTED_MESSAGE');
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1115
1216
|
if (desc === TLS_ALERT.CLOSE_NOTIFY) {
|
|
1116
1217
|
// The peer is done sending: end our readable side, so 'end' is
|
|
1117
1218
|
// emitted once what came before it is read.
|
|
@@ -1135,12 +1236,7 @@ function TLSSocket(duplex, options){
|
|
|
1135
1236
|
// reacting to it finds the session already over.
|
|
1136
1237
|
markTransportDead();
|
|
1137
1238
|
if (desc !== TLS_ALERT.CLOSE_NOTIFY) {
|
|
1138
|
-
|
|
1139
|
-
for (const k in TLS_ALERT) if (TLS_ALERT[k] === desc) { name = k.toLowerCase(); break; }
|
|
1140
|
-
const err = new Error('peer sent fatal alert: ' + (name || 'alert') + ' (' + desc + ')');
|
|
1141
|
-
err.code = 'ERR_TLS_ALERT_RECEIVED';
|
|
1142
|
-
err.alert = desc;
|
|
1143
|
-
self.emit('error', err);
|
|
1239
|
+
self.emit('error', peer_alert_error(desc));
|
|
1144
1240
|
}
|
|
1145
1241
|
// Nothing of ours to flush: the peer has ended the connection.
|
|
1146
1242
|
if (context.transport && typeof context.transport.destroy === 'function') context.transport.destroy();
|
|
@@ -1242,11 +1338,8 @@ function TLSSocket(duplex, options){
|
|
|
1242
1338
|
const buf = toBuf(data || []);
|
|
1243
1339
|
coalesceFlight();
|
|
1244
1340
|
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
const isTls13 = context.isTls13 !== undefined
|
|
1248
|
-
? context.isTls13
|
|
1249
|
-
: session.getVersion() === 0x0304;
|
|
1341
|
+
noteNegotiated();
|
|
1342
|
+
const isTls13 = context.isTls13 === true;
|
|
1250
1343
|
|
|
1251
1344
|
// Alert messages — encrypt based on current epoch (0=plaintext, 1=handshake, 2=app)
|
|
1252
1345
|
if (type === 'alert') {
|
|
@@ -1418,21 +1511,8 @@ function TLSSocket(duplex, options){
|
|
|
1418
1511
|
context.handshake_write_iv = d.iv;
|
|
1419
1512
|
}
|
|
1420
1513
|
|
|
1421
|
-
const algo = context.aeadAlgo || getAeadAlgo(session.getCipher());
|
|
1422
1514
|
try {
|
|
1423
|
-
|
|
1424
|
-
// ready for transport.write. Same optimization as writeAppDataSingle.
|
|
1425
|
-
const rec = encryptCompleteRecord13(
|
|
1426
|
-
CT.HANDSHAKE, buf,
|
|
1427
|
-
context.handshake_write_key,
|
|
1428
|
-
get_nonce_into(context._nonceEncScratch, context.handshake_write_iv, context.handshake_write_seq),
|
|
1429
|
-
algo,
|
|
1430
|
-
context.rec_version
|
|
1431
|
-
);
|
|
1432
|
-
context.handshake_write_seq++;
|
|
1433
|
-
if (canWrite()) {
|
|
1434
|
-
context.transport.write(rec);
|
|
1435
|
-
}
|
|
1515
|
+
writeProtectedHandshake(buf, true, 'handshake_write_key', 'handshake_write_iv', 'handshake_write_seq');
|
|
1436
1516
|
if (session.isServer && type === 'finished' && context.early.status === 'accepted' && !context.early.srvWrite) {
|
|
1437
1517
|
context.early.srvWrite = true;
|
|
1438
1518
|
process.nextTick(flushServerEarlyWrites);
|
|
@@ -1459,14 +1539,7 @@ function TLSSocket(duplex, options){
|
|
|
1459
1539
|
}
|
|
1460
1540
|
|
|
1461
1541
|
try {
|
|
1462
|
-
|
|
1463
|
-
buf, context.app_write_key, context.app_write_iv,
|
|
1464
|
-
context.app_write_seq, CT.HANDSHAKE, context.rec_version
|
|
1465
|
-
);
|
|
1466
|
-
context.app_write_seq++;
|
|
1467
|
-
if (canWrite()) {
|
|
1468
|
-
context.transport.write(rec);
|
|
1469
|
-
}
|
|
1542
|
+
writeProtectedHandshake(buf, false, 'app_write_key', 'app_write_iv', 'app_write_seq');
|
|
1470
1543
|
} catch(e) {
|
|
1471
1544
|
self.emit('error', e);
|
|
1472
1545
|
}
|
|
@@ -1484,19 +1557,8 @@ function TLSSocket(duplex, options){
|
|
|
1484
1557
|
}
|
|
1485
1558
|
}
|
|
1486
1559
|
if (context.app_write_key) {
|
|
1487
|
-
const algo = context.aeadAlgo || getAeadAlgo(session.getCipher());
|
|
1488
1560
|
try {
|
|
1489
|
-
|
|
1490
|
-
CT.HANDSHAKE, buf,
|
|
1491
|
-
context.app_write_key,
|
|
1492
|
-
get_nonce_into(context._nonceEncScratch, context.app_write_iv, context.app_write_seq),
|
|
1493
|
-
algo,
|
|
1494
|
-
context.rec_version
|
|
1495
|
-
);
|
|
1496
|
-
context.app_write_seq++;
|
|
1497
|
-
if (canWrite()) {
|
|
1498
|
-
context.transport.write(rec);
|
|
1499
|
-
}
|
|
1561
|
+
writeProtectedHandshake(buf, true, 'app_write_key', 'app_write_iv', 'app_write_seq');
|
|
1500
1562
|
} catch(e) {
|
|
1501
1563
|
self.emit('error', e);
|
|
1502
1564
|
}
|
|
@@ -1506,54 +1568,23 @@ function TLSSocket(duplex, options){
|
|
|
1506
1568
|
});
|
|
1507
1569
|
|
|
1508
1570
|
|
|
1571
|
+
// The version and AEAD, cached for the hot paths once negotiated: a
|
|
1572
|
+
// client's at its ServerHello ('hello'), a server's when it answers —
|
|
1573
|
+
// after its 'hello', which comes first (the application may still
|
|
1574
|
+
// configure the session there): its ServerHello's 'message'. Read at
|
|
1575
|
+
// 'hello' alone, a server had neither, and its 0.5-RTT data went out
|
|
1576
|
+
// as TLS 1.2.
|
|
1577
|
+
function noteNegotiated() {
|
|
1578
|
+
if (context.isTls13 !== undefined) return;
|
|
1579
|
+
const v = session.getVersion();
|
|
1580
|
+
if (!v) return;
|
|
1581
|
+
context.isTls13 = v === 0x0304;
|
|
1582
|
+
if (session.getCipher()) context.aeadAlgo = getAeadAlgo(session.getCipher());
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1509
1585
|
session.on('hello', function(){
|
|
1510
1586
|
context.rec_version = 0x0303; // legacy record version (both 1.2 and 1.3)
|
|
1511
|
-
|
|
1512
|
-
// Client already sent its preferences in ClientHello — don't override
|
|
1513
|
-
if (!session.isServer) return;
|
|
1514
|
-
|
|
1515
|
-
// Parse version range from options
|
|
1516
|
-
let maxVer = parseVersion(options.maxVersion) || 0x0304;
|
|
1517
|
-
let minVer = parseVersion(options.minVersion) || 0x0303;
|
|
1518
|
-
|
|
1519
|
-
let versions = [];
|
|
1520
|
-
if (maxVer >= 0x0304 && minVer <= 0x0304) versions.push(0x0304);
|
|
1521
|
-
if (maxVer >= 0x0303 && minVer <= 0x0303) versions.push(0x0303);
|
|
1522
|
-
if (versions.length === 0) versions.push(0x0303); // fallback
|
|
1523
|
-
|
|
1524
|
-
// Cipher suites based on supported versions
|
|
1525
|
-
// crypto.js owns the default suite list (see default_cipher_suites).
|
|
1526
|
-
let ciphers = default_cipher_suites(versions.includes(0x0304), versions.includes(0x0303));
|
|
1527
|
-
|
|
1528
|
-
// Signature algorithms: override or default
|
|
1529
|
-
// signing.js owns the scheme list (see default_signature_schemes):
|
|
1530
|
-
// the PKCS#1 set is only added when TLS 1.2 is actually in range.
|
|
1531
|
-
let sigalgs = options.signatureAlgorithms
|
|
1532
|
-
|| default_signature_schemes();
|
|
1533
|
-
|
|
1534
|
-
// Groups: override or default
|
|
1535
|
-
// A server's default: post-quantum hybrid first (ecdh.js). A client that
|
|
1536
|
-
// offers it gets it; any other gets the classical group it asked for.
|
|
1537
|
-
let groups = options.groups || SERVER_DEFAULT_GROUPS.slice();
|
|
1538
|
-
|
|
1539
|
-
ciphers = chooseCipherSuites(ciphers, options.prioritizeChaCha, context.allowedCipherSuites);
|
|
1540
|
-
|
|
1541
|
-
// ALPN from options
|
|
1542
|
-
let alpns = Array.isArray(options.ALPNProtocols) ? options.ALPNProtocols : [];
|
|
1543
|
-
|
|
1544
|
-
session.set_context({
|
|
1545
|
-
local_supported_versions: versions,
|
|
1546
|
-
local_supported_alpns: alpns,
|
|
1547
|
-
local_supported_groups: groups,
|
|
1548
|
-
local_supported_cipher_suites: ciphers,
|
|
1549
|
-
local_supported_signature_algorithms: sigalgs,
|
|
1550
|
-
});
|
|
1551
|
-
|
|
1552
|
-
// Resolve AEAD algorithm + isTls13 flag once cipher/version are negotiated.
|
|
1553
|
-
// For client, both are known after receiving ServerHello (this event fires after).
|
|
1554
|
-
// For server, these are cached again at secureConnect as a fallback.
|
|
1555
|
-
if (session.getCipher()) context.aeadAlgo = getAeadAlgo(session.getCipher());
|
|
1556
|
-
if (session.getVersion()) context.isTls13 = (session.getVersion() === 0x0304);
|
|
1587
|
+
noteNegotiated();
|
|
1557
1588
|
});
|
|
1558
1589
|
|
|
1559
1590
|
session.on('secureConnect', function(){
|
|
@@ -1561,12 +1592,10 @@ function TLSSocket(duplex, options){
|
|
|
1561
1592
|
// and the flight ending in Finished must end EXACTLY there. Leftover
|
|
1562
1593
|
// bytes in the message stream at completion are trailing garbage the
|
|
1563
1594
|
// peer packed after its Finished — a framing violation, not padding.
|
|
1564
|
-
if (
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
const t = context.transport;
|
|
1569
|
-
if (t && typeof t.destroy === 'function' && !t.destroyed) t.destroy();
|
|
1595
|
+
if (hsPending()) {
|
|
1596
|
+
hsReset();
|
|
1597
|
+
// An alert this time: the peer learns why.
|
|
1598
|
+
abort(TLS_ALERT.UNEXPECTED_MESSAGE, 'Trailing data after handshake completion', 'ERR_SSL_UNEXPECTED_MESSAGE');
|
|
1570
1599
|
return;
|
|
1571
1600
|
}
|
|
1572
1601
|
|
|
@@ -1579,19 +1608,20 @@ function TLSSocket(duplex, options){
|
|
|
1579
1608
|
// Clear handshake timeout
|
|
1580
1609
|
if (context.handshakeTimer) { clearTimeout(context.handshakeTimer); context.handshakeTimer = null; }
|
|
1581
1610
|
|
|
1582
|
-
// Certificate pinning
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1611
|
+
// Certificate pinning: the leaf's DER, 'sha256/' + base64 of its
|
|
1612
|
+
// SHA-256. The chain is a list of { cert } entries — this check once
|
|
1613
|
+
// read `.raw` off the list itself, found nothing, and let every
|
|
1614
|
+
// certificate through behind a "best-effort" catch. A pin fails closed:
|
|
1615
|
+
// no certificate to hash is a mismatch too. A client's option (the
|
|
1616
|
+
// server's certificate), as documented.
|
|
1617
|
+
if (!context.isServer && context.pins && context.pins.length > 0) {
|
|
1618
|
+
let chain = session.getPeerCertificate();
|
|
1619
|
+
let leaf = chain && chain.length > 0 ? chain[0].cert : null;
|
|
1620
|
+
let hash = leaf ? 'sha256/' + crypto.createHash('sha256').update(leaf).digest('base64') : null;
|
|
1621
|
+
if (hash === null || !context.pins.includes(hash)) {
|
|
1622
|
+
abort(TLS_ALERT.BAD_CERTIFICATE, 'Certificate pin mismatch: ' + (hash || 'no peer certificate'), 'ERR_TLS_CERT_PIN_MISMATCH');
|
|
1623
|
+
return;
|
|
1624
|
+
}
|
|
1595
1625
|
}
|
|
1596
1626
|
|
|
1597
1627
|
// Emit keylog lines (SSLKEYLOGFILE compatible)
|
|
@@ -1731,6 +1761,13 @@ function TLSSocket(duplex, options){
|
|
|
1731
1761
|
context.app_write_iv = d.iv;
|
|
1732
1762
|
context.app_write_seq = 0;
|
|
1733
1763
|
} else if (info.direction === 'receive') {
|
|
1764
|
+
// A run of KeyUpdates with no data between them (BoGo:
|
|
1765
|
+
// TooManyKeyUpdates) — 32, BoringSSL's limit. The peer's answers
|
|
1766
|
+
// to ours aside (info.answer): those we asked for.
|
|
1767
|
+
if (!info.answer && ++context.consecutive_key_updates > 32) {
|
|
1768
|
+
abort(TLS_ALERT.UNEXPECTED_MESSAGE, 'too many KeyUpdates without application data', 'ERR_SSL_UNEXPECTED_MESSAGE');
|
|
1769
|
+
return;
|
|
1770
|
+
}
|
|
1734
1771
|
// Peer updated their send secret → derive new read keys, reset seq
|
|
1735
1772
|
let d = tls_derive_from_tls_secrets(info.secret, session.getCipher());
|
|
1736
1773
|
context.app_read_key = d.key;
|
|
@@ -1798,45 +1835,12 @@ function TLSSocket(duplex, options){
|
|
|
1798
1835
|
* The other four are applied for the same reason — each is documented as a
|
|
1799
1836
|
* per-connection option, and each was being dropped on the client side.
|
|
1800
1837
|
*/
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
update.local_supported_alpns = options.ALPNProtocols.map(function (p) {
|
|
1808
|
-
return Buffer.isBuffer(p) ? p.toString('latin1') : String(p);
|
|
1809
|
-
});
|
|
1810
|
-
}
|
|
1811
|
-
|
|
1812
|
-
let maxVer = parseVersion(options.maxVersion);
|
|
1813
|
-
let minVer = parseVersion(options.minVersion);
|
|
1814
|
-
if (maxVer || minVer) {
|
|
1815
|
-
maxVer = maxVer || 0x0304;
|
|
1816
|
-
minVer = minVer || 0x0303;
|
|
1817
|
-
let versions = [];
|
|
1818
|
-
if (maxVer >= 0x0304 && minVer <= 0x0304) versions.push(0x0304);
|
|
1819
|
-
if (maxVer >= 0x0303 && minVer <= 0x0303) versions.push(0x0303);
|
|
1820
|
-
if (versions.length === 0) versions.push(0x0303);
|
|
1821
|
-
update.local_supported_versions = versions;
|
|
1822
|
-
}
|
|
1823
|
-
|
|
1824
|
-
if (options.groups) update.local_supported_groups = options.groups;
|
|
1825
|
-
if (options.signatureAlgorithms) {
|
|
1826
|
-
update.local_supported_signature_algorithms = options.signatureAlgorithms;
|
|
1827
|
-
}
|
|
1828
|
-
|
|
1829
|
-
if (options.allowedCipherSuites || options.prioritizeChaCha) {
|
|
1830
|
-
let vers = update.local_supported_versions || [0x0304, 0x0303];
|
|
1831
|
-
let ciphers = default_cipher_suites(vers.indexOf(0x0304) >= 0,
|
|
1832
|
-
vers.indexOf(0x0303) >= 0);
|
|
1833
|
-
update.local_supported_cipher_suites = chooseCipherSuites(ciphers, options.prioritizeChaCha, options.allowedCipherSuites);
|
|
1834
|
-
}
|
|
1835
|
-
|
|
1836
|
-
if (Object.keys(update).length > 0) session.set_context(update);
|
|
1837
|
-
}
|
|
1838
|
-
|
|
1839
|
-
applyClientContext();
|
|
1838
|
+
// What the options say about the lists the session negotiates with
|
|
1839
|
+
// (node_options.js localLists — the versions, and suites, groups,
|
|
1840
|
+
// signature schemes and ALPN where given); TLSSession fills the rest by
|
|
1841
|
+
// its role. For a server this was done on its 'hello', a client's
|
|
1842
|
+
// separately, each its own way.
|
|
1843
|
+
session.set_context(localLists(options, localVersions));
|
|
1840
1844
|
|
|
1841
1845
|
// If duplex was passed in constructor, start reading
|
|
1842
1846
|
if (context.transport) {
|
|
@@ -2030,7 +2034,7 @@ function TLSSocket(duplex, options){
|
|
|
2030
2034
|
sendCompatCCS();
|
|
2031
2035
|
}
|
|
2032
2036
|
|
|
2033
|
-
writeRecord(CT.HANDSHAKE,
|
|
2037
|
+
for (const part of handshakeFragments(buf)) writeRecord(CT.HANDSHAKE, part);
|
|
2034
2038
|
|
|
2035
2039
|
// Offering early data: the compatibility CCS right after this
|
|
2036
2040
|
// first hello (§D.4), then the early data.
|
|
@@ -2040,9 +2044,11 @@ function TLSSocket(duplex, options){
|
|
|
2040
2044
|
flushEarly();
|
|
2041
2045
|
}
|
|
2042
2046
|
|
|
2047
|
+
// A HelloRetryRequest is TLS 1.3 by definition — the version need not
|
|
2048
|
+
// be recorded yet, and was not: the CCS went after the second
|
|
2049
|
+
// ServerHello instead of right after the HRR.
|
|
2043
2050
|
if (session.isServer &&
|
|
2044
|
-
(type === '
|
|
2045
|
-
session.getVersion() === 0x0304) {
|
|
2051
|
+
(type === 'hello_retry_request' || (type === 'hello' && session.getVersion() === 0x0304))) {
|
|
2046
2052
|
sendCompatCCS();
|
|
2047
2053
|
}
|
|
2048
2054
|
}
|
|
@@ -2136,12 +2142,7 @@ function TLSSocket(duplex, options){
|
|
|
2136
2142
|
|
|
2137
2143
|
/** Returns negotiated protocol string: 'TLSv1.3', 'TLSv1.2', etc. */
|
|
2138
2144
|
self.getProtocol = function(){
|
|
2139
|
-
|
|
2140
|
-
if (v === 0x0304) return 'TLSv1.3';
|
|
2141
|
-
if (v === 0x0303) return 'TLSv1.2';
|
|
2142
|
-
if (v === 0x0302) return 'TLSv1.1';
|
|
2143
|
-
if (v === 0x0301) return 'TLSv1';
|
|
2144
|
-
return null;
|
|
2145
|
+
return version_name(session.getVersion());
|
|
2145
2146
|
};
|
|
2146
2147
|
|
|
2147
2148
|
/** Returns cipher info: { name, standardName, version }. */
|
|
@@ -2161,6 +2162,11 @@ function TLSSocket(duplex, options){
|
|
|
2161
2162
|
// Encrypted Client Hello (server): null (not offered) | 'accepted' | 'rejected'.
|
|
2162
2163
|
Object.defineProperty(self, 'echStatus', { get: function(){ return session.getEchStatus(); } });
|
|
2163
2164
|
Object.defineProperty(self, 'echAccepted', { get: function(){ return session.getEchStatus() === 'accepted'; } });
|
|
2165
|
+
// Server, split mode: ECH accepted by the client-facing server in front of this one.
|
|
2166
|
+
Object.defineProperty(self, 'echBackend', { get: function(){ return session.isEchBackend(); } });
|
|
2167
|
+
/** External PSK: the identity this connection was authenticated with
|
|
2168
|
+
* (a Buffer), or null. */
|
|
2169
|
+
self.getPskIdentity = function(){ let id = session.getPskIdentity(); return id ? Buffer.from(id) : null; };
|
|
2164
2170
|
Object.defineProperty(self, 'alpnProtocol', {
|
|
2165
2171
|
get: function(){ return session.getALPN() || false; },
|
|
2166
2172
|
enumerable: true
|
|
@@ -2294,7 +2300,7 @@ function TLSSocket(duplex, options){
|
|
|
2294
2300
|
* encrypt path chunk on this boundary; values outside the range throw. */
|
|
2295
2301
|
self.setMaxSendFragment = function(size){
|
|
2296
2302
|
size = Number(size);
|
|
2297
|
-
if (!Number.isInteger(size) || size < 512 || size >
|
|
2303
|
+
if (!Number.isInteger(size) || size < 512 || size > MAX_RECORD_PLAINTEXT) {
|
|
2298
2304
|
throw new RangeError('setMaxSendFragment: size must be an integer in [512, 16384]');
|
|
2299
2305
|
}
|
|
2300
2306
|
self._maxSendFragment = size;
|
|
@@ -2455,14 +2461,7 @@ function TLSSocket(duplex, options){
|
|
|
2455
2461
|
if (session.context && session.context.isServer) return null;
|
|
2456
2462
|
let g = session.context ? session.context.selected_group : null;
|
|
2457
2463
|
if (g == null) return {};
|
|
2458
|
-
let
|
|
2459
|
-
0x0017: { name: 'prime256v1', size: 256 },
|
|
2460
|
-
0x0018: { name: 'secp384r1', size: 384 },
|
|
2461
|
-
0x0019: { name: 'secp521r1', size: 521 },
|
|
2462
|
-
0x001d: { name: 'x25519', size: 253 },
|
|
2463
|
-
0x001e: { name: 'x448', size: 448 }
|
|
2464
|
-
};
|
|
2465
|
-
let k = known[g];
|
|
2464
|
+
let k = dh_group_info(g);
|
|
2466
2465
|
// Not an ECDH curve, so not in that table, but a group with a name.
|
|
2467
2466
|
if (!k && ecdh_group_name(g)) return { type: 'TLSGroup', name: ecdh_group_name(g) };
|
|
2468
2467
|
// Anything outside that table is still a negotiated TLS group — report
|