lemon-tls 0.5.0 → 0.6.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/package.json +5 -3
- package/src/compat.js +10 -0
- package/src/crypto.js +146 -48
- package/src/dtls_session.js +501 -66
- package/src/dtls_socket.js +7 -0
- package/src/record.js +88 -21
- package/src/session/ecdh.js +124 -0
- package/src/session/message.js +35 -0
- package/src/session/signing.js +240 -47
- package/src/tls_session.js +1839 -431
- package/src/tls_socket.js +378 -39
- package/src/wire.js +272 -14
package/src/tls_session.js
CHANGED
|
@@ -3,6 +3,7 @@ import { EventEmitter } from 'node:events';
|
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
TLS_CIPHER_SUITES,
|
|
6
|
+
default_cipher_suites,
|
|
6
7
|
build_cert_verify_tbs,
|
|
7
8
|
build_cert_verify_tbs_with_hash,
|
|
8
9
|
get_handshake_finished,
|
|
@@ -24,6 +25,10 @@ import {
|
|
|
24
25
|
derive_exporter_master_secret_with_hash,
|
|
25
26
|
tls13_exporter,
|
|
26
27
|
tls12_exporter,
|
|
28
|
+
LABEL_PREFIX_TLS13,
|
|
29
|
+
LABEL_PREFIX_DTLS13,
|
|
30
|
+
suite_matches_version,
|
|
31
|
+
is_usable_cipher_suite,
|
|
27
32
|
} from './crypto.js';
|
|
28
33
|
|
|
29
34
|
import {
|
|
@@ -36,9 +41,10 @@ import {
|
|
|
36
41
|
import * as wire from './wire.js';
|
|
37
42
|
|
|
38
43
|
// Extracted modules
|
|
39
|
-
import { pick_scheme, sign_with_scheme } from './session/signing.js';
|
|
44
|
+
import { pick_scheme, sign_with_scheme, verify_with_scheme, default_signature_schemes } from './session/signing.js';
|
|
40
45
|
import createSecureContext from './secure_context.js';
|
|
41
|
-
import { x25519_get_public_key,
|
|
46
|
+
import { x25519_get_public_key, SUPPORTED_GROUPS as ECDH_SUPPORTED_GROUPS, generate_keypair as ecdh_generate_keypair, get_shared_secret as ecdh_get_shared_secret, is_supported_group
|
|
47
|
+
} from './session/ecdh.js';
|
|
42
48
|
import { build_tls_message, parse_tls_message } from './session/message.js';
|
|
43
49
|
import { encrypt_session_blob, decrypt_session_blob, encode_client_session, decode_client_session } from './session/ticket.js';
|
|
44
50
|
|
|
@@ -93,6 +99,13 @@ function TLSSession(options){
|
|
|
93
99
|
|
|
94
100
|
local_random: null,
|
|
95
101
|
local_extensions: [],
|
|
102
|
+
// DTLS-SRTP protection profiles we accept, in preference order (RFC 5764).
|
|
103
|
+
// Value-based, not key-based: transports forward the option unconditionally,
|
|
104
|
+
// so the key is present with an undefined value when it was never set.
|
|
105
|
+
srtp_profiles: Array.isArray(options.srtpProfiles) ? options.srtpProfiles.slice() : [],
|
|
106
|
+
selected_srtp_profile: null,
|
|
107
|
+
// Extension types we offered — see note_offered_extensions().
|
|
108
|
+
offered_extension_types: [],
|
|
96
109
|
|
|
97
110
|
|
|
98
111
|
local_supported_versions: [],
|
|
@@ -182,12 +195,30 @@ function TLSSession(options){
|
|
|
182
195
|
remote_handshake_traffic_secret: null,
|
|
183
196
|
local_handshake_traffic_secret: null,
|
|
184
197
|
|
|
185
|
-
|
|
198
|
+
// RFC 8446 §7.1: the application traffic secrets (and exporter_master_secret)
|
|
199
|
+
// are derived from the transcript hash through the SERVER's Finished —
|
|
200
|
+
// ClientHello..server Finished, and nothing after it.
|
|
201
|
+
//
|
|
202
|
+
// This is a SEMANTIC checkpoint, so it is captured at the moment that event
|
|
203
|
+
// happens (server: when it pushes its own Finished; client: when it accepts
|
|
204
|
+
// the server's), NOT read off the live transcript wherever the derivation
|
|
205
|
+
// block happens to sit in the reactive loop. Those two differ: on the
|
|
206
|
+
// client, the loop may already have emitted its own Certificate (when the
|
|
207
|
+
// server requested client auth) before reaching the derivation block, which
|
|
208
|
+
// silently derived app keys over a longer transcript than the server used —
|
|
209
|
+
// a split-key handshake that surfaced only as a late bad_record_mac.
|
|
210
|
+
tls13_app_transcript_hash: null,
|
|
211
|
+
|
|
186
212
|
local_app_traffic_secret: null,
|
|
187
213
|
|
|
188
214
|
local_cert_chain: null,
|
|
189
215
|
remote_cert_chain: null,
|
|
190
216
|
peerAuthorized: false,
|
|
217
|
+
// Set only after the peer's handshake signature (TLS 1.3
|
|
218
|
+
// CertificateVerify, or TLS 1.2 ServerKeyExchange) actually verified
|
|
219
|
+
// against its certificate's public key. Gates handshake completion.
|
|
220
|
+
peerSignatureVerified: false,
|
|
221
|
+
peerSignatureScheme: null,
|
|
191
222
|
authorizationError: null,
|
|
192
223
|
|
|
193
224
|
selected_cert: null,
|
|
@@ -206,6 +237,11 @@ function TLSSession(options){
|
|
|
206
237
|
|
|
207
238
|
// HelloRetryRequest
|
|
208
239
|
helloRetried: false, // true if HRR was sent/received
|
|
240
|
+
// What a received HelloRetryRequest committed the server to; the second
|
|
241
|
+
// ServerHello is validated against these (RFC 8446 §4.1.4).
|
|
242
|
+
hrr_version: null,
|
|
243
|
+
hrr_group: null,
|
|
244
|
+
hrr_cipher: null,
|
|
209
245
|
|
|
210
246
|
// DTLS cookie (set by DTLSSession via set_context)
|
|
211
247
|
dtls_cookie: undefined, // Uint8Array or undefined
|
|
@@ -301,6 +337,406 @@ function TLSSession(options){
|
|
|
301
337
|
* so subsequent calls to get_transcript_hash() run in O(1) clone+digest time
|
|
302
338
|
* instead of re-hashing the entire transcript.
|
|
303
339
|
*/
|
|
340
|
+
/**
|
|
341
|
+
* Negotiated-version predicates.
|
|
342
|
+
*
|
|
343
|
+
* The engine deliberately handles TLS 1.2 and 1.3 in ONE state machine: the
|
|
344
|
+
* version is chosen *during* the handshake, so the machine must start
|
|
345
|
+
* version-agnostic, and a large part of the flow (hello parsing, extensions,
|
|
346
|
+
* transcript, certificates, ALPN/SNI, alerts) is genuinely shared. What
|
|
347
|
+
* differs is expressed as guards on individual steps.
|
|
348
|
+
*
|
|
349
|
+
* These two helpers exist so those guards read as the protocol question they
|
|
350
|
+
* actually are. Each site used to spell out
|
|
351
|
+
* (context.selected_version === wire.TLS_VERSION.TLS1_3 ||
|
|
352
|
+
* context.selected_version === wire.DTLS_VERSION.DTLS1_3)
|
|
353
|
+
* — ~90 characters that buried the *rest* of the condition it was ANDed with.
|
|
354
|
+
* (D)TLS 1.3 and 1.2 are treated as one version each because the record layer
|
|
355
|
+
* differences live in record.js / the transports, not here.
|
|
356
|
+
*/
|
|
357
|
+
function is13() {
|
|
358
|
+
return context.selected_version === wire.TLS_VERSION.TLS1_3 ||
|
|
359
|
+
context.selected_version === wire.DTLS_VERSION.DTLS1_3;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function is12() {
|
|
363
|
+
return context.selected_version === wire.TLS_VERSION.TLS1_2 ||
|
|
364
|
+
context.selected_version === wire.DTLS_VERSION.DTLS1_2;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Assemble the ClientHello extension list.
|
|
369
|
+
*
|
|
370
|
+
* ONE builder for both the initial ClientHello and the post-HelloRetryRequest
|
|
371
|
+
* CH2. RFC 8446 §4.1.2 requires CH2 to be identical to CH1 except for
|
|
372
|
+
* key_share, cookie and (optionally) a trimmed PSK — so building them from
|
|
373
|
+
* two hand-maintained copies is a standing invitation to divergence, and the
|
|
374
|
+
* copies HAD already diverged: one gated supported_versions/key_share on
|
|
375
|
+
* "do we offer 1.3", the other always emitted them, and the 1.3-only test
|
|
376
|
+
* differed between them (one accounted for DTLS 1.3, the other did not).
|
|
377
|
+
*
|
|
378
|
+
* @param {object} opts
|
|
379
|
+
* keyShareGroup — NamedGroup for the key_share entry (null = omit)
|
|
380
|
+
* keySharePublic — public key bytes for that group
|
|
381
|
+
* cookie — HRR cookie to echo (RFC 8446 §4.2.2), or null
|
|
382
|
+
*/
|
|
383
|
+
function build_client_hello_extensions(opts) {
|
|
384
|
+
opts = opts || {};
|
|
385
|
+
|
|
386
|
+
let offers13 = context.local_supported_versions.some(function (v) {
|
|
387
|
+
return v === wire.TLS_VERSION.TLS1_3 || v === wire.DTLS_VERSION.DTLS1_3;
|
|
388
|
+
});
|
|
389
|
+
// "1.3-only" means every offered version is (D)TLS 1.3 — a QUIC-style
|
|
390
|
+
// profile that must not carry TLS 1.2 legacy extensions.
|
|
391
|
+
let tls13Only = context.local_supported_versions.length > 0 &&
|
|
392
|
+
context.local_supported_versions.every(function (v) {
|
|
393
|
+
return v === wire.TLS_VERSION.TLS1_3 || v === wire.DTLS_VERSION.DTLS1_3;
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
let extensions = [];
|
|
397
|
+
|
|
398
|
+
if (offers13) {
|
|
399
|
+
extensions.push({ type: 'SUPPORTED_VERSIONS', value: context.local_supported_versions });
|
|
400
|
+
}
|
|
401
|
+
// supported_groups is a CAPABILITY claim, not a copy of configuration.
|
|
402
|
+
//
|
|
403
|
+
// A configured list may name groups ecdh.js does not implement — a caller
|
|
404
|
+
// passing a curve list through, a post-quantum group in a profile we do not
|
|
405
|
+
// yet support. Advertising one is a promise: RFC 8446 §4.2.7 lets the
|
|
406
|
+
// server pick any group in this list and send a HelloRetryRequest for it.
|
|
407
|
+
// We would then have to abort a handshake the server conducted correctly,
|
|
408
|
+
// purely because we over-promised. Filtering here keeps the configured list
|
|
409
|
+
// intact for introspection while never claiming more than we can do.
|
|
410
|
+
let advertisable = [];
|
|
411
|
+
for (let gi = 0; gi < context.local_supported_groups.length; gi++) {
|
|
412
|
+
let g = context.local_supported_groups[gi];
|
|
413
|
+
if (is_supported_group(g)) advertisable.push(g);
|
|
414
|
+
}
|
|
415
|
+
extensions.push({ type: 'SUPPORTED_GROUPS', value: advertisable });
|
|
416
|
+
if (offers13 && opts.keySharePublic) {
|
|
417
|
+
extensions.push({
|
|
418
|
+
type: 'KEY_SHARE',
|
|
419
|
+
value: [{ group: opts.keyShareGroup, key_exchange: opts.keySharePublic }]
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
extensions.push({
|
|
423
|
+
type: 'SIGNATURE_ALGORITHMS',
|
|
424
|
+
value: context.local_supported_signature_algorithms
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
if (!tls13Only) {
|
|
428
|
+
// TLS 1.2 compatibility — meaningless (and suspicious to strict stacks)
|
|
429
|
+
// in a 1.3-only hello.
|
|
430
|
+
extensions.push({ type: 'RENEGOTIATION_INFO', value: new Uint8Array(0) });
|
|
431
|
+
extensions.push({ type: 'EXTENDED_MASTER_SECRET', value: null });
|
|
432
|
+
} else {
|
|
433
|
+
// psk_key_exchange_modes: a server MUST NOT send NewSessionTicket to a
|
|
434
|
+
// client that omitted it (RFC 8446 §4.2.9).
|
|
435
|
+
extensions.push({ type: 'PSK_KEY_EXCHANGE_MODES', value: [1] });
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// SNI must come first.
|
|
439
|
+
if (context.local_sni) {
|
|
440
|
+
extensions.unshift({ type: 'SERVER_NAME', value: context.local_sni });
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (context.local_supported_alpns && context.local_supported_alpns.length > 0) {
|
|
444
|
+
extensions.push({ type: 'ALPN', value: context.local_supported_alpns });
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// RFC 5077 SessionTicket. This belongs HERE, not at the CH1 call site,
|
|
448
|
+
// because RFC 8446 §4.1.2 requires the second ClientHello to be identical
|
|
449
|
+
// to the first except for key_share, cookie and pre_shared_key. Anything
|
|
450
|
+
// appended to CH1 *after* this assembler returns is silently absent from
|
|
451
|
+
// CH2, and a strict peer rejects the retry ("second ClientHello missing
|
|
452
|
+
// extension 35"). Every extension common to both hellos has to be produced
|
|
453
|
+
// by the one function that builds both.
|
|
454
|
+
//
|
|
455
|
+
// It is a TLS 1.2 mechanism (1.3 resumes via NewSessionTicket/PSK), so it
|
|
456
|
+
// is never offered in a 1.3-only profile. Skipped for DTLS as well:
|
|
457
|
+
// implementations often do not handle RFC 5077 there and it broke interop
|
|
458
|
+
// with openssl s_server -dtls1_2.
|
|
459
|
+
let isDtlsProfile = context.local_supported_versions &&
|
|
460
|
+
context.local_supported_versions.some(function (v) { return (v & 0xFF00) === 0xFE00; });
|
|
461
|
+
if (!isDtlsProfile && !tls13Only && context.sessionTickets) {
|
|
462
|
+
if (opts.sessionTicket) {
|
|
463
|
+
extensions.push({ type: 'SESSION_TICKET', value: opts.sessionTicket });
|
|
464
|
+
} else {
|
|
465
|
+
extensions.push({ type: 'SESSION_TICKET', value: new Uint8Array(0) });
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Cookie from HelloRetryRequest — MUST be echoed when present.
|
|
470
|
+
if (opts.cookie) {
|
|
471
|
+
extensions.push({ type: 'COOKIE', value: opts.cookie });
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Caller-supplied extensions (e.g. transport parameters).
|
|
475
|
+
// DTLS-SRTP offer (RFC 5764 §4.1.1): the client advertises every profile it
|
|
476
|
+
// accepts. Built here so CH1 and CH2 stay identical (RFC 8446 §4.1.2).
|
|
477
|
+
if (!context.isServer && Array.isArray(context.srtp_profiles) &&
|
|
478
|
+
context.srtp_profiles.length > 0) {
|
|
479
|
+
extensions.push({ type: 'USE_SRTP',
|
|
480
|
+
value: { profiles: context.srtp_profiles.slice(),
|
|
481
|
+
mki: new Uint8Array(0) } });
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
for (let i = 0; i < context.local_extensions.length; i++) {
|
|
485
|
+
extensions.push(context.local_extensions[i]);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
note_offered_extensions(extensions);
|
|
489
|
+
return extensions;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Remember which extension types we actually offered.
|
|
494
|
+
*
|
|
495
|
+
* RFC 8446 §4.2: "Implementations MUST NOT send extension responses if the
|
|
496
|
+
* remote endpoint did not send the corresponding extension requests ...
|
|
497
|
+
* Upon receiving such an extension, an endpoint MUST abort the handshake
|
|
498
|
+
* with an unsupported_extension alert."
|
|
499
|
+
*
|
|
500
|
+
* Enforcing that needs one fact — what we asked for — recorded in one place.
|
|
501
|
+
* The assembler is that place: every ClientHello, first or post-HRR, is built
|
|
502
|
+
* through it, so the record cannot drift from what actually went on the wire.
|
|
503
|
+
* Extensions appended after assembly (pre_shared_key, session_ticket) call
|
|
504
|
+
* this again to add themselves.
|
|
505
|
+
*/
|
|
506
|
+
/**
|
|
507
|
+
* RFC 8446 §4.2 (TLS 1.3): a server MUST NOT answer with an extension the
|
|
508
|
+
* client did not request, and a client receiving one MUST abort with
|
|
509
|
+
* unsupported_extension.
|
|
510
|
+
*
|
|
511
|
+
* IMPORTANT: this function does NOT gate on version itself, because the
|
|
512
|
+
* ServerHello caller cannot rely on context.selected_version yet (it is set
|
|
513
|
+
* in a later pass of the reactive loop). The caller decides scope from the
|
|
514
|
+
* message's own supported_versions, which is authoritative.
|
|
515
|
+
*
|
|
516
|
+
* TLS/DTLS 1.2 (RFC 5246 §7.4.1.4) is deliberately looser: unknown
|
|
517
|
+
* extensions must be ignored. Applying the 1.3 rule to a 1.2 handshake
|
|
518
|
+
* rejects entirely valid peers (the WebRTC regression against pion /
|
|
519
|
+
* webrtc-rs).
|
|
520
|
+
*
|
|
521
|
+
* One rule for every server-originated extension block (ServerHello,
|
|
522
|
+
* EncryptedExtensions) — they all fail for the same reason and must fail
|
|
523
|
+
* the same way. Returns true when the caller should stop processing.
|
|
524
|
+
*
|
|
525
|
+
* HelloRetryRequest is handled earlier and returns before this point, so its
|
|
526
|
+
* cookie — which by definition we could not have offered — never lands here.
|
|
527
|
+
*/
|
|
528
|
+
function reject_unsolicited_extensions(message, where) {
|
|
529
|
+
if (context.isServer) return false; // servers answer, not ask
|
|
530
|
+
if (!Array.isArray(message.extensions)) return false;
|
|
531
|
+
if (context.offered_extension_types.length === 0) return false; // nothing recorded yet
|
|
532
|
+
|
|
533
|
+
for (let i = 0; i < message.extensions.length; i++) {
|
|
534
|
+
let et = message.extensions[i] && message.extensions[i].type;
|
|
535
|
+
if (typeof et !== 'number') continue;
|
|
536
|
+
if (context.offered_extension_types.indexOf(et) < 0) {
|
|
537
|
+
fatalAlert(wire.TLS_ALERT.UNSUPPORTED_EXTENSION,
|
|
538
|
+
where + ' carried extension ' + et + ' which we did not offer');
|
|
539
|
+
return true;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return false;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function note_offered_extensions(list) {
|
|
546
|
+
if (!Array.isArray(context.offered_extension_types)) {
|
|
547
|
+
context.offered_extension_types = [];
|
|
548
|
+
}
|
|
549
|
+
for (let i = 0; i < list.length; i++) {
|
|
550
|
+
let t = list[i] && list[i].type;
|
|
551
|
+
let code = (typeof t === 'number') ? t : wire.TLS_EXT[t];
|
|
552
|
+
if (typeof code === 'number' && context.offered_extension_types.indexOf(code) < 0) {
|
|
553
|
+
context.offered_extension_types.push(code);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* The peer's leaf-certificate public key, as a crypto.KeyObject.
|
|
560
|
+
*
|
|
561
|
+
* Single place that turns remote_cert_chain[0] into a usable key. Both the
|
|
562
|
+
* TLS 1.3 CertificateVerify path and the TLS 1.2 ServerKeyExchange path need
|
|
563
|
+
* it, and having two copies means a future change to how we read the chain
|
|
564
|
+
* (chain building, alternative encodings) could be applied to one and missed
|
|
565
|
+
* on the other — the kind of half-fix that has bitten us before.
|
|
566
|
+
*
|
|
567
|
+
* Returns null if there is no chain or the leaf cannot be parsed; callers
|
|
568
|
+
* turn that into the appropriate alert for their context.
|
|
569
|
+
*/
|
|
570
|
+
function peer_leaf_public_key() {
|
|
571
|
+
if (!context.remote_cert_chain || context.remote_cert_chain.length === 0) return null;
|
|
572
|
+
try {
|
|
573
|
+
return new crypto.X509Certificate(Buffer.from(context.remote_cert_chain[0].cert)).publicKey;
|
|
574
|
+
} catch (e) {
|
|
575
|
+
return null;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* The cipher suite a ServerHello / HelloRetryRequest selected.
|
|
581
|
+
*
|
|
582
|
+
* parse_hello normalises the server's single choice into `cipher_suites[0]`,
|
|
583
|
+
* but some paths surface it as `cipher_suite`. Both spellings are in use, so
|
|
584
|
+
* reading only one of them silently yields undefined — which is how the
|
|
585
|
+
* HelloRetryRequest cipher-consistency check came to accept every mismatch.
|
|
586
|
+
*/
|
|
587
|
+
function hello_cipher_suite(message) {
|
|
588
|
+
if (Array.isArray(message.cipher_suites) && message.cipher_suites.length > 0) {
|
|
589
|
+
return message.cipher_suites[0];
|
|
590
|
+
}
|
|
591
|
+
if (typeof message.cipher_suite === 'number') return message.cipher_suite;
|
|
592
|
+
return null;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Build and send the second ClientHello after a HelloRetryRequest.
|
|
597
|
+
*
|
|
598
|
+
* ONE emitter for both retry shapes — the server asked for a different group,
|
|
599
|
+
* or it asked only for a cookie. They differ in exactly two values
|
|
600
|
+
* (which group's key share to carry, and whether a cookie is echoed) and in
|
|
601
|
+
* nothing else, so writing them out twice means every future change to CH2
|
|
602
|
+
* has to be made in two places. That is precisely how the "second ClientHello
|
|
603
|
+
* missing extension 35" bug arose: an extension was added to one path and not
|
|
604
|
+
* the other.
|
|
605
|
+
*
|
|
606
|
+
* RFC 8446 §4.1.2 requires CH2 to be identical to CH1 apart from key_share,
|
|
607
|
+
* cookie and pre_shared_key — an invariant that is only cheap to hold when a
|
|
608
|
+
* single function produces it.
|
|
609
|
+
*/
|
|
610
|
+
function send_second_client_hello(keyShareGroup, keySharePublic, cookie) {
|
|
611
|
+
let extensions = build_client_hello_extensions({
|
|
612
|
+
keyShareGroup: keyShareGroup,
|
|
613
|
+
keySharePublic: keySharePublic,
|
|
614
|
+
cookie: cookie
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
let ch2 = build_tls_message({
|
|
618
|
+
type: 'client_hello',
|
|
619
|
+
version: 0x0303,
|
|
620
|
+
random: context.local_random,
|
|
621
|
+
session_id: context.local_session_id,
|
|
622
|
+
cookie: context.dtls_cookie,
|
|
623
|
+
cipher_suites: context.local_supported_cipher_suites,
|
|
624
|
+
cipher_suite: context.local_supported_cipher_suites,
|
|
625
|
+
extensions: extensions,
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
pushTranscript(ch2);
|
|
629
|
+
ev.emit('message', 0, context.message_sent_seq, 'hello', ch2);
|
|
630
|
+
context.message_sent_seq++;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* The signature algorithms the peer asked us to sign a client certificate
|
|
635
|
+
* with, from its CertificateRequest.
|
|
636
|
+
*
|
|
637
|
+
* There is deliberately NO fallback to our own offer list. Both call sites
|
|
638
|
+
* used to do `certificateRequestSigAlgs.length > 0 ? … : local_supported…`,
|
|
639
|
+
* which reads as caution but is not: signing with an algorithm the verifier
|
|
640
|
+
* never requested produces a signature it rejects, and it hid the real bug
|
|
641
|
+
* (the 1.3 list was never extracted from the CertificateRequest extensions,
|
|
642
|
+
* so the fallback fired on every handshake and looked like it worked).
|
|
643
|
+
*
|
|
644
|
+
* An empty list is a genuine protocol error — RFC 8446 §4.3.2 makes
|
|
645
|
+
* signature_algorithms mandatory in a TLS 1.3 CertificateRequest, and the
|
|
646
|
+
* decoder now rejects one without it — so returning empty here lets
|
|
647
|
+
* pick_scheme return null and the caller raise handshake_failure, which is
|
|
648
|
+
* the honest outcome.
|
|
649
|
+
*/
|
|
650
|
+
function peer_requested_sig_algs() {
|
|
651
|
+
return context.certificateRequestSigAlgs || [];
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Hash of the negotiated cipher suite — the hash the whole TLS 1.3 key
|
|
656
|
+
* schedule, every transcript hash and every Finished MAC are computed with.
|
|
657
|
+
*
|
|
658
|
+
* It was spelled out as negotiated_hash()
|
|
659
|
+
* at nineteen sites. That is a named protocol concept, not an incidental
|
|
660
|
+
* lookup, and writing it out each time means nineteen places to touch if the
|
|
661
|
+
* suite table ever changes shape — and nineteen chances to read the wrong
|
|
662
|
+
* field.
|
|
663
|
+
*/
|
|
664
|
+
function negotiated_hash() {
|
|
665
|
+
let meta = TLS_CIPHER_SUITES[context.selected_cipher_suite];
|
|
666
|
+
return meta ? meta.hash : null;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* Our certificate's private key as a crypto.KeyObject.
|
|
671
|
+
*
|
|
672
|
+
* The DER/pkcs8 encoding of `cert_private_key` is an internal storage
|
|
673
|
+
* detail; four sites repeated the same three-line construction to undo it.
|
|
674
|
+
* Keeping the shape in one place means a future change to how keys are held
|
|
675
|
+
* (PEM, an external signer, a KeyObject cached up front) is a change here
|
|
676
|
+
* and nowhere else.
|
|
677
|
+
*/
|
|
678
|
+
function local_private_key() {
|
|
679
|
+
if (!context.cert_private_key) return null;
|
|
680
|
+
return crypto.createPrivateKey({
|
|
681
|
+
key: Buffer.from(context.cert_private_key),
|
|
682
|
+
format: 'der',
|
|
683
|
+
type: 'pkcs8',
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* HKDF-Expand-Label prefix for THIS session's key schedule.
|
|
689
|
+
*
|
|
690
|
+
* RFC 8446 §7.1 uses "tls13 "; RFC 9147 §5.9 requires "dtls13" (no trailing
|
|
691
|
+
* space) for DTLS 1.3, precisely so that the two protocols cannot derive the
|
|
692
|
+
* same keys from the same secrets. Using the TLS prefix over DTLS produces a
|
|
693
|
+
* handshake where every message parses and every transcript matches, and only
|
|
694
|
+
* the keys differ — which surfaces as "bad record MAC" with no other clue.
|
|
695
|
+
*/
|
|
696
|
+
function label_prefix() {
|
|
697
|
+
return context.selected_version === wire.DTLS_VERSION.DTLS1_3
|
|
698
|
+
? LABEL_PREFIX_DTLS13
|
|
699
|
+
: LABEL_PREFIX_TLS13;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/**
|
|
703
|
+
* DTLS-SRTP profile negotiation (RFC 5764 §4.1).
|
|
704
|
+
*
|
|
705
|
+
* The library previously decoded the client's use_srtp extension and handed
|
|
706
|
+
* it to the caller, but selected nothing and answered nothing — so every
|
|
707
|
+
* consumer had to re-implement the negotiation, and a peer that expects the
|
|
708
|
+
* stack to answer (as BoringSSL's SSL_CTX_set_tlsext_use_srtp does) got no
|
|
709
|
+
* use_srtp back at all.
|
|
710
|
+
*
|
|
711
|
+
* Selection is by SERVER preference over the mutually supported set, matching
|
|
712
|
+
* how the cipher suite and group are chosen. The answer carries exactly one
|
|
713
|
+
* profile and an empty MKI: RFC 5764 §4.1.1 says the server "MUST include
|
|
714
|
+
* exactly one" protection profile, and §4.1.2 that an MKI it does not
|
|
715
|
+
* understand is ignored rather than fatal.
|
|
716
|
+
*
|
|
717
|
+
* Returns the chosen profile, or null when there is no overlap — in which
|
|
718
|
+
* case NO use_srtp extension is sent, which is how RFC 5764 §4.1.2 says a
|
|
719
|
+
* server declines ("if no acceptable profile is found, the server SHOULD NOT
|
|
720
|
+
* include the extension").
|
|
721
|
+
*/
|
|
722
|
+
function negotiate_srtp_profile() {
|
|
723
|
+
if (!context.isServer) return null;
|
|
724
|
+
if (!Array.isArray(context.srtp_profiles) || context.srtp_profiles.length === 0) return null;
|
|
725
|
+
|
|
726
|
+
let offered = null;
|
|
727
|
+
for (let i = 0; i < context.remote_extensions.length; i++) {
|
|
728
|
+
let e = context.remote_extensions[i];
|
|
729
|
+
if (e && e.type === wire.TLS_EXT.USE_SRTP) { offered = e.value; break; }
|
|
730
|
+
}
|
|
731
|
+
if (!offered || !Array.isArray(offered.profiles) || offered.profiles.length === 0) return null;
|
|
732
|
+
|
|
733
|
+
for (let i = 0; i < context.srtp_profiles.length; i++) {
|
|
734
|
+
let p = context.srtp_profiles[i] | 0;
|
|
735
|
+
if (offered.profiles.indexOf(p) >= 0) return p;
|
|
736
|
+
}
|
|
737
|
+
return null;
|
|
738
|
+
}
|
|
739
|
+
|
|
304
740
|
function pushTranscript(data) {
|
|
305
741
|
if (context.transcriptHook) {
|
|
306
742
|
data = context.transcriptHook(data);
|
|
@@ -353,25 +789,111 @@ function TLSSession(options){
|
|
|
353
789
|
}
|
|
354
790
|
}
|
|
355
791
|
|
|
792
|
+
/**
|
|
793
|
+
* Abort the handshake with a fatal alert.
|
|
794
|
+
* Single funnel for every protocol-violation path: sends the alert (which
|
|
795
|
+
* also flips context.state to 'error', making the session inert — the
|
|
796
|
+
* reactive loop and process_income_message both early-return on terminal
|
|
797
|
+
* states, so a poisoned context can never keep producing messages), then
|
|
798
|
+
* surfaces the reason to the owner via 'error'.
|
|
799
|
+
*/
|
|
800
|
+
function fatalAlert(description, msg) {
|
|
801
|
+
if (context.state === 'error' || context.state === 'closed') return;
|
|
802
|
+
sendAlert(2, description); // sets context.state = 'error'
|
|
803
|
+
ev.emit('error', new Error(msg));
|
|
804
|
+
}
|
|
805
|
+
|
|
356
806
|
function process_income_message(data){
|
|
357
807
|
|
|
808
|
+
// Terminal states: a fatal alert (ours or a parse failure) already ended
|
|
809
|
+
// this session. Feeding more data must not resurrect the state machine.
|
|
810
|
+
if (context.state === 'error' || context.state === 'closed') return;
|
|
811
|
+
|
|
358
812
|
// Track handshake start time
|
|
359
813
|
if (context.handshakeStartTime === null) context.handshakeStartTime = Date.now();
|
|
360
814
|
|
|
361
815
|
// Track handshake size and enforce limit
|
|
362
816
|
context.handshakeBytes += data.length;
|
|
363
817
|
if (context.maxHandshakeSize > 0 && context.handshakeBytes > context.maxHandshakeSize) {
|
|
364
|
-
|
|
818
|
+
// Must go through the same funnel as every other abort: previously this
|
|
819
|
+
// emitted 'error' but sent no alert and left the session in a live state,
|
|
820
|
+
// so the reactive loop could keep processing the very flood the limit
|
|
821
|
+
// exists to stop, and the peer was never told why we went quiet.
|
|
822
|
+
fatalAlert(wire.TLS_ALERT.INTERNAL_ERROR,
|
|
823
|
+
'Handshake size exceeded maxHandshakeSize (' + context.maxHandshakeSize + ')');
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// The ONE place peer bytes become structured messages. wire.js throws
|
|
828
|
+
// parse errors carrying .alertDesc (decode_error / illegal_parameter);
|
|
829
|
+
// convert them to a fatal alert instead of letting them crash the
|
|
830
|
+
// transport's data handler (remote DoS) or be silently swallowed.
|
|
831
|
+
let message;
|
|
832
|
+
try {
|
|
833
|
+
message = parse_tls_message(data, context.selected_version);
|
|
834
|
+
} catch (e) {
|
|
835
|
+
fatalAlert(e.alertDesc || wire.TLS_ALERT.DECODE_ERROR, 'Malformed handshake message: ' + e.message);
|
|
365
836
|
return;
|
|
366
837
|
}
|
|
367
838
|
|
|
368
|
-
|
|
839
|
+
// Direction enforcement (RFC 8446 §5.1 / unexpected_message): each
|
|
840
|
+
// handshake type is legal from exactly one peer role. Without this, a
|
|
841
|
+
// client-sent EncryptedExtensions (etc.) walks straight into server
|
|
842
|
+
// state it was never meant to touch.
|
|
843
|
+
{
|
|
844
|
+
let t = message.type;
|
|
845
|
+
let serverOnly = t === 'server_hello' || t === 'encrypted_extensions' ||
|
|
846
|
+
t === 'server_hello_done' || t === 'certificate_request' ||
|
|
847
|
+
t === 'server_key_exchange' || t === 'new_session_ticket';
|
|
848
|
+
let clientOnly = t === 'client_hello' || t === 'client_key_exchange';
|
|
849
|
+
if ((context.isServer && serverOnly) || (!context.isServer && clientOnly)) {
|
|
850
|
+
fatalAlert(wire.TLS_ALERT.UNEXPECTED_MESSAGE, 'Unexpected ' + t + ' from ' + (context.isServer ? 'client' : 'server'));
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
369
854
|
|
|
370
855
|
// Emit 'handshakeMessage' hook for every message
|
|
371
856
|
ev.emit('handshakeMessage', message.type, data, message);
|
|
372
857
|
|
|
373
858
|
if((context.isServer==false && message.type=='server_hello') || (context.isServer==true && message.type=='client_hello')){
|
|
374
859
|
|
|
860
|
+
// Server-side ClientHello policy checks — BEFORE the transcript push,
|
|
861
|
+
// the 'clienthello' emit and set_context's reactive loop, because the
|
|
862
|
+
// loop may synchronously build and send a ServerHello for a hello we
|
|
863
|
+
// are about to reject.
|
|
864
|
+
if (context.isServer && message.type === 'client_hello') {
|
|
865
|
+
let comp = message.legacy_compression || [];
|
|
866
|
+
|
|
867
|
+
// RFC 5246 §7.4.1.2: null compression MUST be present in every CH.
|
|
868
|
+
if (comp.indexOf(0) < 0) {
|
|
869
|
+
fatalAlert(wire.TLS_ALERT.ILLEGAL_PARAMETER, 'ClientHello without null compression');
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// RFC 8446 §4.1.2: a CH offering TLS 1.3 must carry exactly one
|
|
874
|
+
// compression method: null. Enforced when 1.3 is on the table for
|
|
875
|
+
// this negotiation (client offers it and we support it — an empty
|
|
876
|
+
// local list means "not configured yet", which defaults to 1.3+1.2).
|
|
877
|
+
let sv = message.supported_versions || [];
|
|
878
|
+
let offers13 = sv.indexOf(wire.TLS_VERSION.TLS1_3) >= 0 || sv.indexOf(wire.DTLS_VERSION.DTLS1_3) >= 0;
|
|
879
|
+
let lv = context.local_supported_versions || [];
|
|
880
|
+
let we13 = lv.length === 0 || lv.indexOf(wire.TLS_VERSION.TLS1_3) >= 0 || lv.indexOf(wire.DTLS_VERSION.DTLS1_3) >= 0;
|
|
881
|
+
if (offers13 && we13 && comp.length !== 1) {
|
|
882
|
+
fatalAlert(wire.TLS_ALERT.ILLEGAL_PARAMETER, 'TLS 1.3 ClientHello must offer exactly the null compression method');
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
// RFC 8446 §4.2.11: one binder per identity, and never zero of either.
|
|
887
|
+
if (message.pre_shared_key && Array.isArray(message.pre_shared_key.identities)) {
|
|
888
|
+
let nIds = message.pre_shared_key.identities.length;
|
|
889
|
+
let nBinders = Array.isArray(message.pre_shared_key.binders) ? message.pre_shared_key.binders.length : 0;
|
|
890
|
+
if (nIds === 0 || nBinders !== nIds) {
|
|
891
|
+
fatalAlert(wire.TLS_ALERT.ILLEGAL_PARAMETER, 'pre_shared_key identities/binders count mismatch (' + nIds + '/' + nBinders + ')');
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
375
897
|
pushTranscript(data);
|
|
376
898
|
|
|
377
899
|
// Save raw ClientHello + emit event (server side)
|
|
@@ -422,12 +944,12 @@ function TLSSession(options){
|
|
|
422
944
|
if (pskResult && pskResult.psk) {
|
|
423
945
|
let pskCipher = pskResult.cipher || 0x1301;
|
|
424
946
|
let hashName = TLS_CIPHER_SUITES[pskCipher] ? TLS_CIPHER_SUITES[pskCipher].hash : 'sha256';
|
|
425
|
-
let binder_key = derive_binder_key(hashName, pskResult.psk, false);
|
|
947
|
+
let binder_key = derive_binder_key(hashName, pskResult.psk, false, label_prefix());
|
|
426
948
|
|
|
427
949
|
let hashLen = getHashFn(hashName).outputLen;
|
|
428
950
|
let bindersSize = 2 + 1 + hashLen;
|
|
429
951
|
let truncatedCH = data.slice(0, data.length - bindersSize);
|
|
430
|
-
let expectedBinder = compute_psk_binder(hashName, binder_key, truncatedCH);
|
|
952
|
+
let expectedBinder = compute_psk_binder(hashName, binder_key, truncatedCH, label_prefix());
|
|
431
953
|
|
|
432
954
|
dbg('SRV-PSK', 'hash:', hashName, 'hashLen:', hashLen,
|
|
433
955
|
'truncatedCH len:', truncatedCH.length,
|
|
@@ -448,6 +970,10 @@ function TLSSession(options){
|
|
|
448
970
|
context.psk_offered = {
|
|
449
971
|
psk: pskResult.psk instanceof Uint8Array ? pskResult.psk : new Uint8Array(pskResult.psk),
|
|
450
972
|
cipher: pskCipher,
|
|
973
|
+
// The HASH is what actually constrains cipher selection
|
|
974
|
+
// (RFC 8446 §4.2.11), not the exact suite the ticket was issued
|
|
975
|
+
// under. Stored here so selection does not have to re-derive it.
|
|
976
|
+
hash: hashName,
|
|
451
977
|
};
|
|
452
978
|
}
|
|
453
979
|
}
|
|
@@ -500,91 +1026,318 @@ function TLSSession(options){
|
|
|
500
1026
|
// Extract cookie from HRR (if present, must be echoed in CH2)
|
|
501
1027
|
let hrrCookie = message.cookie || null;
|
|
502
1028
|
|
|
1029
|
+
// RFC 8446 §4.1.4: "Clients MUST abort the handshake with an
|
|
1030
|
+
// illegal_parameter alert if the HelloRetryRequest would not result in
|
|
1031
|
+
// any change in the ClientHello."
|
|
1032
|
+
//
|
|
1033
|
+
// A retry that names neither a new group nor a cookie asks us to resend
|
|
1034
|
+
// a byte-identical ClientHello, which the server would answer with the
|
|
1035
|
+
// same retry — an unbounded loop. Detecting it is not an optimisation:
|
|
1036
|
+
// without it we neither retried nor failed, we simply stopped, and the
|
|
1037
|
+
// peer sat waiting until it timed out. A stall is a denial of service.
|
|
1038
|
+
if (!requestedGroup && !hrrCookie) {
|
|
1039
|
+
fatalAlert(wire.TLS_ALERT.ILLEGAL_PARAMETER,
|
|
1040
|
+
'HelloRetryRequest requests no change (no key_share and no cookie)');
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
// Record EVERYTHING the retry committed to, in one place, before the
|
|
1045
|
+
// branch below splits on whether a group was requested.
|
|
1046
|
+
//
|
|
1047
|
+
// RFC 8446 §4.1.4: the second ServerHello must agree with the retry on
|
|
1048
|
+
// version, cipher suite and group. Those three checks need three
|
|
1049
|
+
// recorded values, and recording them in two different places — the
|
|
1050
|
+
// cipher here, the version and group inside the requestedGroup branch —
|
|
1051
|
+
// meant a cookie-only retry recorded the cipher and nothing else, so
|
|
1052
|
+
// two of the three consistency checks silently never fired for it. One
|
|
1053
|
+
// concept, one place.
|
|
1054
|
+
let hrrCipherSel = hello_cipher_suite(message);
|
|
1055
|
+
if (hrrCipherSel !== null) context.hrr_cipher = hrrCipherSel;
|
|
1056
|
+
|
|
1057
|
+
let hrrVersionSel = null;
|
|
1058
|
+
if (Array.isArray(message.supported_versions) && message.supported_versions.length > 0) {
|
|
1059
|
+
hrrVersionSel = message.supported_versions[0];
|
|
1060
|
+
} else if (typeof message.supported_versions === 'number') {
|
|
1061
|
+
hrrVersionSel = message.supported_versions;
|
|
1062
|
+
}
|
|
1063
|
+
if (hrrVersionSel !== null) context.hrr_version = hrrVersionSel;
|
|
1064
|
+
|
|
1065
|
+
// Null for a cookie-only retry — which is correct: it committed to no
|
|
1066
|
+
// group, so there is nothing for SH2 to contradict.
|
|
1067
|
+
context.hrr_group = requestedGroup;
|
|
1068
|
+
|
|
1069
|
+
// A cookie-only retry is legal: the server is asking us to prove
|
|
1070
|
+
// reachability without changing our key share. The group path below is
|
|
1071
|
+
// skipped, so drive CH2 from here — previously this case produced no
|
|
1072
|
+
// second ClientHello at all and stalled exactly like the empty retry.
|
|
1073
|
+
if (!requestedGroup && hrrCookie) {
|
|
1074
|
+
set_context({
|
|
1075
|
+
selected_version: (Array.isArray(message.supported_versions) && message.supported_versions.length > 0)
|
|
1076
|
+
? message.supported_versions[0]
|
|
1077
|
+
: (typeof message.supported_versions === 'number' ? message.supported_versions : null),
|
|
1078
|
+
// NOTE: dtls_cookie is the DTLS ClientHello's own cookie FIELD
|
|
1079
|
+
// (RFC 6347 §4.2.1), which lives in the message body and makes
|
|
1080
|
+
// wire.js encode the hello in DTLS framing. The HelloRetryRequest
|
|
1081
|
+
// cookie is a completely different thing — a TLS EXTENSION
|
|
1082
|
+
// (RFC 8446 §4.2.2) that build_client_hello_extensions already
|
|
1083
|
+
// carries. They share a name and nothing else. Assigning the HRR
|
|
1084
|
+
// cookie here injected a 1-byte length plus the cookie into a TLS
|
|
1085
|
+
// ClientHello body, which strict peers reject outright ("error
|
|
1086
|
+
// decoding ClientHello message"). Leave the DTLS field alone.
|
|
1087
|
+
});
|
|
1088
|
+
|
|
1089
|
+
let firstGroup = context.local_supported_groups[0];
|
|
1090
|
+
let firstShare = context.local_key_groups[firstGroup]
|
|
1091
|
+
? context.local_key_groups[firstGroup].public_key : null;
|
|
1092
|
+
send_second_client_hello(firstGroup, firstShare, hrrCookie);
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
503
1096
|
if (requestedGroup) {
|
|
504
|
-
//
|
|
1097
|
+
// RFC 8446 §4.1.4: the retry must ask for a group the client offered
|
|
1098
|
+
// in supported_groups but did NOT already send a key_share for.
|
|
1099
|
+
// Asking for one we already shared is pointless — the server had
|
|
1100
|
+
// everything it needed — and the RFC requires the client to abort
|
|
1101
|
+
// rather than send a second share for the same group.
|
|
1102
|
+
if (requestedGroup in context.local_key_groups) {
|
|
1103
|
+
fatalAlert(wire.TLS_ALERT.ILLEGAL_PARAMETER,
|
|
1104
|
+
'HelloRetryRequest asked for group 0x' + (requestedGroup >>> 0).toString(16) +
|
|
1105
|
+
' for which we already sent a key share');
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// The retry must also stay within what we advertised: a group absent
|
|
1110
|
+
// from our supported_groups was never on offer.
|
|
1111
|
+
if (context.local_supported_groups.length > 0 &&
|
|
1112
|
+
context.local_supported_groups.indexOf(requestedGroup) < 0) {
|
|
1113
|
+
fatalAlert(wire.TLS_ALERT.ILLEGAL_PARAMETER,
|
|
1114
|
+
'HelloRetryRequest asked for group 0x' + (requestedGroup >>> 0).toString(16) +
|
|
1115
|
+
' which we did not offer');
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
// Generate key for the requested group (ecdh.js owns curve dispatch).
|
|
1120
|
+
// A group we don't implement means the server asked for something we
|
|
1121
|
+
// never offered -- RFC 8446 4.1.4 makes that illegal_parameter.
|
|
505
1122
|
let newKeyGroup = null;
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
context.local_key_groups[requestedGroup] = { public_key: kp.public_key, private_key: kp.private_key };
|
|
515
|
-
} else if (requestedGroup === 0x0018) {
|
|
516
|
-
let kp = p384_generate_keypair();
|
|
517
|
-
newKeyGroup = { group: requestedGroup, public_key: kp.public_key, private_key: kp.private_key };
|
|
518
|
-
context.local_key_groups[requestedGroup] = { public_key: kp.public_key, private_key: kp.private_key };
|
|
1123
|
+
let hrrKp = ecdh_generate_keypair(requestedGroup);
|
|
1124
|
+
if (hrrKp) {
|
|
1125
|
+
newKeyGroup = { group: requestedGroup, public_key: hrrKp.public_key, private_key: hrrKp.private_key };
|
|
1126
|
+
context.local_key_groups[requestedGroup] = { public_key: hrrKp.public_key, private_key: hrrKp.private_key };
|
|
1127
|
+
} else {
|
|
1128
|
+
fatalAlert(wire.TLS_ALERT.ILLEGAL_PARAMETER,
|
|
1129
|
+
'HelloRetryRequest asked for unsupported group 0x' + (requestedGroup >>> 0).toString(16));
|
|
1130
|
+
return;
|
|
519
1131
|
}
|
|
520
1132
|
|
|
521
1133
|
if (newKeyGroup) {
|
|
522
|
-
//
|
|
523
|
-
//
|
|
524
|
-
//
|
|
525
|
-
//
|
|
526
|
-
//
|
|
527
|
-
//
|
|
528
|
-
//
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
let extensions = [
|
|
535
|
-
{ type: 'SUPPORTED_VERSIONS', value: context.local_supported_versions },
|
|
536
|
-
{ type: 'SUPPORTED_GROUPS', value: context.local_supported_groups },
|
|
537
|
-
{ type: 'KEY_SHARE', value: [{ group: requestedGroup, key_exchange: newKeyGroup.public_key }] },
|
|
538
|
-
{ type: 'SIGNATURE_ALGORITHMS', value: context.local_supported_signature_algorithms },
|
|
539
|
-
];
|
|
540
|
-
if (!tls13Only2) {
|
|
541
|
-
extensions.push({ type: 'RENEGOTIATION_INFO', value: new Uint8Array(0) });
|
|
542
|
-
extensions.push({ type: 'EXTENDED_MASTER_SECRET', value: null });
|
|
543
|
-
} else {
|
|
544
|
-
extensions.push({ type: 'PSK_KEY_EXCHANGE_MODES', value: [1] });
|
|
1134
|
+
// Record what the HelloRetryRequest told us THROUGH set_context,
|
|
1135
|
+
// rather than only acting on it locally. The HRR carries the
|
|
1136
|
+
// server's chosen version and group; skipping the reactive setter
|
|
1137
|
+
// left selected_version unset for the whole CH2 exchange, which is
|
|
1138
|
+
// why the record layer could not tell a 1.3 compat-CCS from a 1.2
|
|
1139
|
+
// cipher change and had to be given a weaker test. State the peer
|
|
1140
|
+
// told us belongs in the context like any other negotiated value.
|
|
1141
|
+
let hrrVersion = null;
|
|
1142
|
+
if (Array.isArray(message.supported_versions) && message.supported_versions.length > 0) {
|
|
1143
|
+
hrrVersion = message.supported_versions[0];
|
|
1144
|
+
} else if (typeof message.supported_versions === 'number') {
|
|
1145
|
+
hrrVersion = message.supported_versions;
|
|
545
1146
|
}
|
|
546
1147
|
|
|
547
|
-
|
|
548
|
-
|
|
1148
|
+
set_context({
|
|
1149
|
+
selected_version: hrrVersion,
|
|
1150
|
+
selected_group: requestedGroup,
|
|
1151
|
+
// See the note above: the HRR cookie is an extension, not the
|
|
1152
|
+
// DTLS ClientHello cookie field. Preserve whatever DTLS cookie
|
|
1153
|
+
// the transport established, and nothing more.
|
|
1154
|
+
dtls_cookie: context.dtls_cookie,
|
|
1155
|
+
add_local_key_groups: [{
|
|
1156
|
+
group: requestedGroup,
|
|
1157
|
+
private_key: newKeyGroup.private_key,
|
|
1158
|
+
public_key: newKeyGroup.public_key
|
|
1159
|
+
}],
|
|
1160
|
+
});
|
|
549
1161
|
|
|
550
|
-
//
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
1162
|
+
// CH2 MUST match CH1 except key_share/cookie (RFC 8446 §4.1.2) —
|
|
1163
|
+
// guaranteed here by building both from the same assembler.
|
|
1164
|
+
send_second_client_hello(requestedGroup, newKeyGroup.public_key, hrrCookie);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
554
1167
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
}
|
|
1168
|
+
// Don't process HRR as a regular ServerHello
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
559
1171
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
1172
|
+
// Whether THIS ServerHello is 1.3: the server declares its choice via
|
|
1173
|
+
// the supported_versions extension (RFC 8446 §4.2.1). Reading
|
|
1174
|
+
// context.selected_version here would be too early — that field is set
|
|
1175
|
+
// in the set_context below, in a later pass of the reactive loop. Base
|
|
1176
|
+
// the decision on the message itself, which is authoritative.
|
|
1177
|
+
let sh_is_13 = false;
|
|
1178
|
+
if (Array.isArray(message.supported_versions)) {
|
|
1179
|
+
sh_is_13 = message.supported_versions.indexOf(wire.TLS_VERSION.TLS1_3) >= 0 ||
|
|
1180
|
+
message.supported_versions.indexOf(wire.DTLS_VERSION.DTLS1_3) >= 0;
|
|
1181
|
+
} else if (typeof message.supported_versions === 'number') {
|
|
1182
|
+
sh_is_13 = message.supported_versions === wire.TLS_VERSION.TLS1_3 ||
|
|
1183
|
+
message.supported_versions === wire.DTLS_VERSION.DTLS1_3;
|
|
1184
|
+
}
|
|
564
1185
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
1186
|
+
// ── Second ServerHello must honour the HelloRetryRequest ──
|
|
1187
|
+
// RFC 8446 §4.1.4: after a retry, the ServerHello that follows must name
|
|
1188
|
+
// the SAME version and the SAME group the retry asked for. A server that
|
|
1189
|
+
// changes its mind between the two is either broken or steering us, and
|
|
1190
|
+
// §4.1.4 makes the mismatch an illegal_parameter. Checked here, once,
|
|
1191
|
+
// against the values the HRR committed to (hrr_version / hrr_group) —
|
|
1192
|
+
// the HRR branch itself has already returned by this point, so this only
|
|
1193
|
+
// ever sees the real ServerHello.
|
|
1194
|
+
if (!context.isServer && message.type === 'server_hello' && context.helloRetried) {
|
|
1195
|
+
|
|
1196
|
+
if (context.hrr_version !== null) {
|
|
1197
|
+
let shVersion = null;
|
|
1198
|
+
if (Array.isArray(message.supported_versions) && message.supported_versions.length > 0) {
|
|
1199
|
+
shVersion = message.supported_versions[0];
|
|
1200
|
+
} else if (typeof message.supported_versions === 'number') {
|
|
1201
|
+
shVersion = message.supported_versions;
|
|
1202
|
+
}
|
|
1203
|
+
if (shVersion !== null && shVersion !== context.hrr_version) {
|
|
1204
|
+
fatalAlert(wire.TLS_ALERT.ILLEGAL_PARAMETER,
|
|
1205
|
+
'Second ServerHello version 0x' + (shVersion >>> 0).toString(16) +
|
|
1206
|
+
' does not match the HelloRetryRequest (0x' + (context.hrr_version >>> 0).toString(16) + ')');
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
579
1210
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
1211
|
+
// RFC 8446 §4.1.4: "the server MUST send the same cipher suite in the
|
|
1212
|
+
// ServerHello as it did in the HelloRetryRequest." The retry already
|
|
1213
|
+
// fixed the hash that the whole key schedule depends on, so a change
|
|
1214
|
+
// here is not a preference the client may follow — it is a mismatch
|
|
1215
|
+
// that would silently derive different keys on each side.
|
|
1216
|
+
let shCipherSel = hello_cipher_suite(message);
|
|
1217
|
+
if (context.hrr_cipher !== null && shCipherSel !== null &&
|
|
1218
|
+
shCipherSel !== context.hrr_cipher) {
|
|
1219
|
+
fatalAlert(wire.TLS_ALERT.ILLEGAL_PARAMETER,
|
|
1220
|
+
'ServerHello cipher suite 0x' + (shCipherSel >>> 0).toString(16) +
|
|
1221
|
+
' differs from the HelloRetryRequest (0x' + (context.hrr_cipher >>> 0).toString(16) + ')');
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
if (context.hrr_group !== null &&
|
|
1226
|
+
Array.isArray(message.key_groups) && message.key_groups.length > 0) {
|
|
1227
|
+
let shGroup = message.key_groups[0] && message.key_groups[0].group;
|
|
1228
|
+
if (typeof shGroup === 'number' && shGroup !== context.hrr_group) {
|
|
1229
|
+
fatalAlert(wire.TLS_ALERT.ILLEGAL_PARAMETER,
|
|
1230
|
+
'Second ServerHello selected group 0x' + (shGroup >>> 0).toString(16) +
|
|
1231
|
+
' but the HelloRetryRequest asked for 0x' + (context.hrr_group >>> 0).toString(16));
|
|
1232
|
+
return;
|
|
583
1233
|
}
|
|
584
1234
|
}
|
|
1235
|
+
}
|
|
585
1236
|
|
|
586
|
-
|
|
587
|
-
|
|
1237
|
+
// Record the server's DTLS-SRTP choice (it answers in EncryptedExtensions
|
|
1238
|
+
// for 1.3, in the ServerHello for 1.2 — both land in remote_extensions).
|
|
1239
|
+
if (!context.isServer && Array.isArray(message.extensions)) {
|
|
1240
|
+
for (let sx = 0; sx < message.extensions.length; sx++) {
|
|
1241
|
+
let e = message.extensions[sx];
|
|
1242
|
+
if (e && e.type === wire.TLS_EXT.USE_SRTP && e.value &&
|
|
1243
|
+
Array.isArray(e.value.profiles) && e.value.profiles.length > 0) {
|
|
1244
|
+
context.selected_srtp_profile = e.value.profiles[0];
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
if (sh_is_13 && reject_unsolicited_extensions(message, 'ServerHello')) return;
|
|
1250
|
+
|
|
1251
|
+
// ── legacy_session_id_echo (RFC 8446 §4.1.3) ──
|
|
1252
|
+
// "A client which receives a legacy_session_id_echo field that does not
|
|
1253
|
+
// match what it sent in the ClientHello MUST abort the handshake with an
|
|
1254
|
+
// illegal_parameter alert."
|
|
1255
|
+
//
|
|
1256
|
+
// This is unconditional for the client, and RFC 9147's errata is explicit
|
|
1257
|
+
// that it still applies over DTLS 1.3 — DTLS disables the *server* side of
|
|
1258
|
+
// TLS 1.3's compatibility mode (Appendix D.4) but NOT this check. We were
|
|
1259
|
+
// not verifying it at all: a server could echo anything, or nothing, and
|
|
1260
|
+
// we accepted it. The field is also the one signal that distinguishes a
|
|
1261
|
+
// genuine ServerHello from one replayed or crafted for a different
|
|
1262
|
+
// ClientHello, so skipping it is not cosmetic.
|
|
1263
|
+
// TLS 1.3 ONLY. In TLS 1.2 the server is free to return a NEW session_id
|
|
1264
|
+
// to establish a new session (RFC 5246 §7.4.1.3) — echoing the client's is
|
|
1265
|
+
// how it signals RESUMPTION, not a requirement. Applying the 1.3 rule there
|
|
1266
|
+
// rejects every ordinary 1.2 handshake.
|
|
1267
|
+
if (sh_is_13 && !context.isServer && message.type === 'server_hello') {
|
|
1268
|
+
let sentSid = context.local_session_id || new Uint8Array(0);
|
|
1269
|
+
let echoSid = message.session_id || new Uint8Array(0);
|
|
1270
|
+
let sameSid = sentSid.length === echoSid.length;
|
|
1271
|
+
if (sameSid) {
|
|
1272
|
+
for (let si = 0; si < sentSid.length; si++) {
|
|
1273
|
+
if (sentSid[si] !== echoSid[si]) { sameSid = false; break; }
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
if (!sameSid) {
|
|
1277
|
+
fatalAlert(wire.TLS_ALERT.ILLEGAL_PARAMETER,
|
|
1278
|
+
'ServerHello legacy_session_id_echo does not match the ClientHello (' +
|
|
1279
|
+
'sent ' + sentSid.length + ' bytes, echoed ' + echoSid.length + ')');
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
|
|
1285
|
+
// ── Client side of the negotiation-outcome rule ──
|
|
1286
|
+
// RFC 8446 §4.2.8 (TLS 1.3 ONLY): "servers offer exactly one
|
|
1287
|
+
// KeyShareEntry in the ServerHello ... This value MUST be in the same
|
|
1288
|
+
// group as the KeyShareEntry value offered by the client." §4.1.4 says a
|
|
1289
|
+
// server that wants a different group must send HelloRetryRequest
|
|
1290
|
+
// instead.
|
|
1291
|
+
//
|
|
1292
|
+
// Scoped to 1.3 explicitly. DTLS 1.2 (and TLS 1.2) do not use key_share
|
|
1293
|
+
// at all — they carry the server's ECDHE public key in
|
|
1294
|
+
// ServerKeyExchange, and the ServerHello may legitimately carry
|
|
1295
|
+
// extensions the parser exposes on the same field. Running this check
|
|
1296
|
+
// there rejects entirely valid 1.2 handshakes and was the WebRTC
|
|
1297
|
+
// regression against pion / webrtc-rs.
|
|
1298
|
+
//
|
|
1299
|
+
// Testing membership in local_key_groups — the shares we actually SENT —
|
|
1300
|
+
// is the RFC's own test, and is stricter than "was it in our
|
|
1301
|
+
// supported_groups": offering a group is not the same as having sent a
|
|
1302
|
+
// key share for it. HelloRetryRequest has returned earlier, and its
|
|
1303
|
+
// requested group is added to local_key_groups before CH2, so a genuine
|
|
1304
|
+
// retry passes here.
|
|
1305
|
+
if (!context.isServer && message.type === 'server_hello' &&
|
|
1306
|
+
sh_is_13 &&
|
|
1307
|
+
Array.isArray(message.key_groups) && message.key_groups.length > 0) {
|
|
1308
|
+
for (let ki = 0; ki < message.key_groups.length; ki++) {
|
|
1309
|
+
let g = message.key_groups[ki] && message.key_groups[ki].group;
|
|
1310
|
+
if (typeof g !== 'number') continue;
|
|
1311
|
+
if (!(g in context.local_key_groups)) {
|
|
1312
|
+
fatalAlert(wire.TLS_ALERT.ILLEGAL_PARAMETER,
|
|
1313
|
+
'ServerHello selected key_share group 0x' + (g >>> 0).toString(16) +
|
|
1314
|
+
' for which we sent no key share');
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
// Resolve our certificate BEFORE the set_context below.
|
|
1321
|
+
//
|
|
1322
|
+
// The reactive loop derives the cipher suite from the inputs present when
|
|
1323
|
+
// it runs, and (D)TLS 1.2 suites name an authentication algorithm that
|
|
1324
|
+
// MUST match our certificate's key type (RFC 4492 §2). Resolving the
|
|
1325
|
+
// certificate afterwards — as the SNI callback used to — meant selection
|
|
1326
|
+
// ran with no key in hand, so a server holding an ECDSA certificate could
|
|
1327
|
+
// pick an ECDHE_RSA suite and then present a certificate the peer rejects
|
|
1328
|
+
// as "wrong certificate type". Inputs first, then the loop.
|
|
1329
|
+
if (context.isServer === true && message.type === 'client_hello' &&
|
|
1330
|
+
typeof context.SNICallback === 'function') {
|
|
1331
|
+
context.SNICallback(message.sni || null, function (err, creds) {
|
|
1332
|
+
if (!err && creds) {
|
|
1333
|
+
// Direct assignment, not set_context: we are deliberately seeding
|
|
1334
|
+
// inputs ahead of the single set_context below, which is what
|
|
1335
|
+
// actually runs the loop. Going through set_context here would run
|
|
1336
|
+
// it twice, once with a half-populated hello.
|
|
1337
|
+
context.local_cert_chain = creds.certificateChain;
|
|
1338
|
+
context.cert_private_key = creds.privateKey;
|
|
1339
|
+
}
|
|
1340
|
+
});
|
|
588
1341
|
}
|
|
589
1342
|
|
|
590
1343
|
set_context({
|
|
@@ -618,8 +1371,11 @@ function TLSSession(options){
|
|
|
618
1371
|
if (timingSafeEqualU8(tail, DOWNGRADE_SENTINEL_TLS12) ||
|
|
619
1372
|
timingSafeEqualU8(tail, DOWNGRADE_SENTINEL_TLS11)) {
|
|
620
1373
|
dbg('DOWNGRADE', 'sentinel detected in ServerHello.random — aborting');
|
|
621
|
-
|
|
622
|
-
|
|
1374
|
+
// Same abort funnel as everything else (fatalAlert = alert + terminal
|
|
1375
|
+
// state + 'error'); the hand-rolled pair here predated it and was the
|
|
1376
|
+
// one abort path that could drift out of sync with the rest.
|
|
1377
|
+
fatalAlert(wire.TLS_ALERT.ILLEGAL_PARAMETER,
|
|
1378
|
+
'TLS downgrade attack detected (RFC 8446 §4.1.3 sentinel present)');
|
|
623
1379
|
return;
|
|
624
1380
|
}
|
|
625
1381
|
}
|
|
@@ -742,18 +1498,6 @@ function TLSSession(options){
|
|
|
742
1498
|
|
|
743
1499
|
ev.emit('hello');
|
|
744
1500
|
|
|
745
|
-
if(context.isServer==true){
|
|
746
|
-
if(typeof context.SNICallback=='function'){
|
|
747
|
-
context.SNICallback(context.remote_sni, function (err, creds) {
|
|
748
|
-
if (!err && creds) {
|
|
749
|
-
set_context({
|
|
750
|
-
local_cert_chain: creds.certificateChain,
|
|
751
|
-
cert_private_key: creds.privateKey
|
|
752
|
-
});
|
|
753
|
-
}
|
|
754
|
-
});
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
1501
|
|
|
758
1502
|
|
|
759
1503
|
|
|
@@ -761,6 +1505,53 @@ function TLSSession(options){
|
|
|
761
1505
|
|
|
762
1506
|
pushTranscript(data);
|
|
763
1507
|
|
|
1508
|
+
// TLS 1.2 ECDHE: the ServerKeyExchange is signed over
|
|
1509
|
+
// client_random | server_random | ServerECDHParams
|
|
1510
|
+
// (RFC 4492 §5.4). Verifying it is what binds the ephemeral key to the
|
|
1511
|
+
// certificate — without it an active attacker can substitute their own
|
|
1512
|
+
// ECDHE public key and the handshake still completes. This was never
|
|
1513
|
+
// checked before.
|
|
1514
|
+
if (message.type === 'server_key_exchange' && !context.isServer &&
|
|
1515
|
+
message.signature && message.public_key) {
|
|
1516
|
+
|
|
1517
|
+
if (!context.remote_cert_chain || context.remote_cert_chain.length === 0) {
|
|
1518
|
+
fatalAlert(wire.TLS_ALERT.UNEXPECTED_MESSAGE, 'ServerKeyExchange before Certificate');
|
|
1519
|
+
return;
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
let skePubKey = peer_leaf_public_key();
|
|
1523
|
+
if (!skePubKey) {
|
|
1524
|
+
fatalAlert(wire.TLS_ALERT.BAD_CERTIFICATE, 'Cannot read server certificate public key');
|
|
1525
|
+
return;
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
// Rebuild the exact signed payload: randoms in wire order (client
|
|
1529
|
+
// first) followed by the ServerECDHParams as they appeared.
|
|
1530
|
+
let ecdhParams = wire.build_server_ecdh_params(message.group, message.public_key);
|
|
1531
|
+
let skeTbs = concatUint8Arrays([
|
|
1532
|
+
context.local_random, // client_random (we are the client)
|
|
1533
|
+
context.remote_random, // server_random
|
|
1534
|
+
ecdhParams
|
|
1535
|
+
]);
|
|
1536
|
+
|
|
1537
|
+
let skeOk = verify_with_scheme(
|
|
1538
|
+
wire.TLS_VERSION.TLS1_2,
|
|
1539
|
+
message.sig_alg,
|
|
1540
|
+
skeTbs,
|
|
1541
|
+
skePubKey,
|
|
1542
|
+
message.signature
|
|
1543
|
+
);
|
|
1544
|
+
|
|
1545
|
+
// Same strict test as the CertificateVerify gate: fail closed.
|
|
1546
|
+
if (skeOk !== true) {
|
|
1547
|
+
fatalAlert(wire.TLS_ALERT.DECRYPT_ERROR, 'ServerKeyExchange signature verification failed');
|
|
1548
|
+
return;
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
context.peerSignatureVerified = true;
|
|
1552
|
+
context.peerSignatureScheme = message.sig_alg;
|
|
1553
|
+
}
|
|
1554
|
+
|
|
764
1555
|
if ([0xC02F,0xC02B,0xC030,0xC02C,0xC013,0xC014,0xC009,0xC00A].includes(context.selected_cipher_suite)==true) {//ECDHE
|
|
765
1556
|
|
|
766
1557
|
// ServerKeyExchange carries the group; ClientKeyExchange does not (server already chose it)
|
|
@@ -804,6 +1595,38 @@ function TLSSession(options){
|
|
|
804
1595
|
|
|
805
1596
|
}else if(message.type=='encrypted_extensions'){
|
|
806
1597
|
|
|
1598
|
+
if (reject_unsolicited_extensions(message, 'EncryptedExtensions')) return;
|
|
1599
|
+
|
|
1600
|
+
// RFC 8446 §4.2 (table): extensions with a defined home elsewhere MUST
|
|
1601
|
+
// NOT appear in EncryptedExtensions — a peer that puts key_share (etc.)
|
|
1602
|
+
// here is violating the extension placement rules → illegal_parameter.
|
|
1603
|
+
// Everything unknown/custom is allowed through: EE is exactly where
|
|
1604
|
+
// application extensions (QUIC transport params, use_srtp, ALPN) live.
|
|
1605
|
+
{
|
|
1606
|
+
let forbiddenInEE = {
|
|
1607
|
+
5: 1, // status_request (CH/CR/Certificate only)
|
|
1608
|
+
13: 1, // signature_algorithms
|
|
1609
|
+
21: 1, // padding (CH only)
|
|
1610
|
+
23: 1, // extended_master_secret (not a TLS 1.3 extension)
|
|
1611
|
+
35: 1, // session_ticket (not a TLS 1.3 extension)
|
|
1612
|
+
41: 1, // pre_shared_key
|
|
1613
|
+
43: 1, // supported_versions
|
|
1614
|
+
44: 1, // cookie
|
|
1615
|
+
45: 1, // psk_key_exchange_modes
|
|
1616
|
+
47: 1, // certificate_authorities (CH/CR only)
|
|
1617
|
+
50: 1, // signature_algorithms_cert
|
|
1618
|
+
51: 1, // key_share
|
|
1619
|
+
0xff01: 1, // renegotiation_info (not a TLS 1.3 extension)
|
|
1620
|
+
};
|
|
1621
|
+
let eeExts = Array.isArray(message.extensions) ? message.extensions : [];
|
|
1622
|
+
for (let fi = 0; fi < eeExts.length; fi++) {
|
|
1623
|
+
if (eeExts[fi] && forbiddenInEE[eeExts[fi].type] === 1) {
|
|
1624
|
+
fatalAlert(wire.TLS_ALERT.ILLEGAL_PARAMETER, 'Forbidden extension ' + eeExts[fi].type + ' in EncryptedExtensions');
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
|
|
807
1630
|
pushTranscript(data);
|
|
808
1631
|
|
|
809
1632
|
// TLS 1.3: most server extensions arrive HERE, not in the
|
|
@@ -834,23 +1657,147 @@ function TLSSession(options){
|
|
|
834
1657
|
|
|
835
1658
|
pushTranscript(data);
|
|
836
1659
|
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
1660
|
+
let entries = message.entries || [];
|
|
1661
|
+
|
|
1662
|
+
// RFC 8446 §4.4.2.4 / RFC 5246 §7.4.6.
|
|
1663
|
+
// - From a SERVER an empty certificate_list is always illegal: the
|
|
1664
|
+
// server has nothing else to authenticate with, so this is fatal here
|
|
1665
|
+
// and now.
|
|
1666
|
+
// - From a CLIENT it is legal — it means "I have no certificate". Whether
|
|
1667
|
+
// that is acceptable is a POLICY question answered at the completion
|
|
1668
|
+
// gate, which also catches the client that omits the message entirely.
|
|
1669
|
+
// So we only record the outcome and let the reactive loop proceed;
|
|
1670
|
+
// returning early here would skip the set_context that drives it.
|
|
1671
|
+
if (entries.length === 0) {
|
|
1672
|
+
if (!context.isServer) {
|
|
1673
|
+
fatalAlert(wire.TLS_ALERT.DECODE_ERROR, 'Server sent an empty certificate_list');
|
|
1674
|
+
return;
|
|
1675
|
+
}
|
|
1676
|
+
context.authorizationError = 'NO_PEER_CERTIFICATE';
|
|
1677
|
+
context.peerAuthorized = false; // no set_context setter for this field
|
|
1678
|
+
set_context({ remote_cert_chain: [] });
|
|
1679
|
+
} else {
|
|
1680
|
+
set_context({
|
|
1681
|
+
remote_cert_chain: entries,
|
|
1682
|
+
});
|
|
840
1683
|
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
1684
|
+
// Validate peer certificate
|
|
1685
|
+
validatePeerCertificate();
|
|
1686
|
+
if (context.rejectUnauthorized && !context.peerAuthorized) {
|
|
1687
|
+
fatalAlert(wire.TLS_ALERT.BAD_CERTIFICATE,
|
|
1688
|
+
'Peer certificate validation failed: ' + (context.authorizationError || 'unknown'));
|
|
1689
|
+
return;
|
|
1690
|
+
}
|
|
846
1691
|
}
|
|
847
1692
|
|
|
848
1693
|
}else if(message.type=='certificate_verify'){
|
|
849
1694
|
|
|
1695
|
+
// RFC 8446 §4.4.3 — verify the peer's handshake signature.
|
|
1696
|
+
//
|
|
1697
|
+
// This is the step that actually authenticates the peer: without it,
|
|
1698
|
+
// ANY party able to send us a certificate (which is public data) is
|
|
1699
|
+
// accepted, because nothing proves they hold the matching private key.
|
|
1700
|
+
// Previously this branch only pushed to the transcript.
|
|
1701
|
+
//
|
|
1702
|
+
// Ordering is critical: the signed transcript covers everything up to
|
|
1703
|
+
// but NOT including this message, so the hash MUST be taken before
|
|
1704
|
+
// pushTranscript(data) below.
|
|
1705
|
+
// A TLS 1.3 CertificateVerify that we cannot verify must NEVER be
|
|
1706
|
+
// silently skipped. The block below is the only thing standing between
|
|
1707
|
+
// us and accepting an unauthenticated peer, so every path that isn't a
|
|
1708
|
+
// successful verification has to be an abort — including "we somehow got
|
|
1709
|
+
// here without a negotiated cipher suite", which would otherwise fall
|
|
1710
|
+
// through with peerSignatureVerified still false and no explanation.
|
|
1711
|
+
if (context.selected_version === wire.TLS_VERSION.TLS1_3 ||
|
|
1712
|
+
context.selected_version === wire.DTLS_VERSION.DTLS1_3) {
|
|
1713
|
+
|
|
1714
|
+
if (context.selected_cipher_suite === null) {
|
|
1715
|
+
fatalAlert(wire.TLS_ALERT.UNEXPECTED_MESSAGE,
|
|
1716
|
+
'CertificateVerify received before a cipher suite was negotiated');
|
|
1717
|
+
return;
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
if (!context.remote_cert_chain || context.remote_cert_chain.length === 0) {
|
|
1721
|
+
fatalAlert(wire.TLS_ALERT.UNEXPECTED_MESSAGE, 'CertificateVerify without a preceding Certificate');
|
|
1722
|
+
return;
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
// Note on ordering: the cryptographic check below is the SECURITY
|
|
1726
|
+
// decision and must be the thing that gates acceptance. An additional
|
|
1727
|
+
// "was this scheme in our signature_algorithms" policy check
|
|
1728
|
+
// (RFC 8446 §4.4.3) used to run FIRST here and abort with
|
|
1729
|
+
// illegal_parameter. That was wrong in two ways: it let a
|
|
1730
|
+
// scheme-decoding anomaly turn into a spurious rejection before the
|
|
1731
|
+
// signature was ever checked, and it reported a policy error for what
|
|
1732
|
+
// may be a signature failure. verify_with_scheme already rejects any
|
|
1733
|
+
// scheme it cannot map to a (hash, algorithm, padding) triple, and
|
|
1734
|
+
// binds the scheme to the certificate's key type, so an unoffered or
|
|
1735
|
+
// unknown scheme still cannot verify.
|
|
1736
|
+
|
|
1737
|
+
let peerPubKey = peer_leaf_public_key();
|
|
1738
|
+
if (!peerPubKey) {
|
|
1739
|
+
fatalAlert(wire.TLS_ALERT.BAD_CERTIFICATE, 'Cannot read peer certificate public key');
|
|
1740
|
+
return;
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
let cvHashName = negotiated_hash();
|
|
1744
|
+
// isServer=false here means "we are the client, so the signature we
|
|
1745
|
+
// are checking is the SERVER's" — the label follows the SIGNER's role,
|
|
1746
|
+
// which is the opposite of ours.
|
|
1747
|
+
let signerIsServer = !context.isServer;
|
|
1748
|
+
let tbs = build_cert_verify_tbs_with_hash(cvHashName, signerIsServer, get_transcript_hash(cvHashName));
|
|
1749
|
+
|
|
1750
|
+
let sigOk = verify_with_scheme(
|
|
1751
|
+
wire.TLS_VERSION.TLS1_3, // DTLS 1.3 shares TLS 1.3 scheme semantics
|
|
1752
|
+
message.scheme,
|
|
1753
|
+
tbs,
|
|
1754
|
+
peerPubKey,
|
|
1755
|
+
message.signature
|
|
1756
|
+
);
|
|
1757
|
+
|
|
1758
|
+
// Gate on "is exactly true", not on truthiness. verify_with_scheme
|
|
1759
|
+
// already returns a strict boolean, but writing the test this way means
|
|
1760
|
+
// a future refactor that lets some path fall through returning
|
|
1761
|
+
// undefined fails CLOSED rather than open. The distinction matters here
|
|
1762
|
+
// more than almost anywhere else in the library: this single value is
|
|
1763
|
+
// what stands between us and an unauthenticated peer.
|
|
1764
|
+
if (sigOk !== true) {
|
|
1765
|
+
fatalAlert(wire.TLS_ALERT.DECRYPT_ERROR, 'CertificateVerify signature verification failed');
|
|
1766
|
+
return;
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
context.peerSignatureVerified = true;
|
|
1770
|
+
context.peerSignatureScheme = message.scheme;
|
|
1771
|
+
}
|
|
1772
|
+
|
|
850
1773
|
pushTranscript(data);
|
|
851
1774
|
|
|
852
1775
|
}else if(message.type=='finished'){
|
|
853
1776
|
|
|
1777
|
+
// ── Order enforcement: Finished may not follow a bare Certificate ──
|
|
1778
|
+
//
|
|
1779
|
+
// RFC 8446 §4.4.3: a peer that sends a non-empty Certificate MUST follow
|
|
1780
|
+
// it with CertificateVerify. Skipping it and going straight to Finished
|
|
1781
|
+
// means the peer never proved possession of the certificate's private
|
|
1782
|
+
// key — it merely presented somebody's certificate.
|
|
1783
|
+
//
|
|
1784
|
+
// A completion-time check alone is not enough here. A peer that omits
|
|
1785
|
+
// CertificateVerify still shares the ECDHE secret, so it can compute a
|
|
1786
|
+
// perfectly valid Finished; we would verify that Finished, derive
|
|
1787
|
+
// application keys and only then notice. Rejecting on ARRIVAL keeps the
|
|
1788
|
+
// omission from ever influencing the key schedule, and reports the
|
|
1789
|
+
// protocol error (unexpected_message) rather than a downstream symptom.
|
|
1790
|
+
//
|
|
1791
|
+
// Applies in both roles: a client checks the server's certificate, and a
|
|
1792
|
+
// server checks a client certificate it was offered.
|
|
1793
|
+
if (is13() && context.remote_cert_chain && context.remote_cert_chain.length > 0 &&
|
|
1794
|
+
context.peerSignatureVerified !== true) {
|
|
1795
|
+
fatalAlert(wire.TLS_ALERT.UNEXPECTED_MESSAGE,
|
|
1796
|
+
'Finished received after a certificate that was never proved by a CertificateVerify');
|
|
1797
|
+
return;
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
|
|
854
1801
|
set_context({
|
|
855
1802
|
remote_finished: message.body
|
|
856
1803
|
});
|
|
@@ -859,10 +1806,10 @@ function TLSSession(options){
|
|
|
859
1806
|
|
|
860
1807
|
// Client receives NewSessionTicket from server (post-handshake in TLS 1.3, pre-CCS in TLS 1.2)
|
|
861
1808
|
if(!context.isServer){
|
|
862
|
-
if ((
|
|
1809
|
+
if ((is13()) && context.resumption_master_secret) {
|
|
863
1810
|
// TLS 1.3: derive PSK from resumption_master_secret + ticket_nonce
|
|
864
|
-
let hashName =
|
|
865
|
-
let psk = derive_psk(hashName, context.resumption_master_secret, message.ticket_nonce);
|
|
1811
|
+
let hashName = negotiated_hash();
|
|
1812
|
+
let psk = derive_psk(hashName, context.resumption_master_secret, message.ticket_nonce, label_prefix());
|
|
866
1813
|
|
|
867
1814
|
dbg('CLI-NST', 'received TLS 1.3 NST — cipher:', '0x' + context.selected_cipher_suite.toString(16),
|
|
868
1815
|
'hash:', hashName,
|
|
@@ -919,16 +1866,16 @@ function TLSSession(options){
|
|
|
919
1866
|
}else if(message.type=='key_update'){
|
|
920
1867
|
|
|
921
1868
|
// Peer is updating their traffic secret (we update our read key)
|
|
922
|
-
if(context.state==='connected' && (
|
|
923
|
-
let hashName =
|
|
1869
|
+
if(context.state==='connected' && (is13())){
|
|
1870
|
+
let hashName = negotiated_hash();
|
|
924
1871
|
let hashLen = getHashLen(hashName);
|
|
925
|
-
let newRemoteSecret = hkdf_expand_label(hashName, context.remote_app_traffic_secret, 'traffic upd', new Uint8Array(0), hashLen);
|
|
1872
|
+
let newRemoteSecret = hkdf_expand_label(hashName, context.remote_app_traffic_secret, 'traffic upd', new Uint8Array(0, label_prefix()), hashLen);
|
|
926
1873
|
context.remote_app_traffic_secret = newRemoteSecret;
|
|
927
1874
|
ev.emit('keyUpdate', { direction: 'receive', secret: newRemoteSecret });
|
|
928
1875
|
|
|
929
1876
|
// If peer requested us to update too
|
|
930
1877
|
if(message.request_update === 1){
|
|
931
|
-
let newLocalSecret = hkdf_expand_label(hashName, context.local_app_traffic_secret, 'traffic upd', new Uint8Array(0), hashLen);
|
|
1878
|
+
let newLocalSecret = hkdf_expand_label(hashName, context.local_app_traffic_secret, 'traffic upd', new Uint8Array(0, label_prefix()), hashLen);
|
|
932
1879
|
context.local_app_traffic_secret = newLocalSecret;
|
|
933
1880
|
|
|
934
1881
|
// Send our KeyUpdate (not requesting back)
|
|
@@ -951,12 +1898,32 @@ function TLSSession(options){
|
|
|
951
1898
|
ev.emit('certificateRequest', message);
|
|
952
1899
|
}
|
|
953
1900
|
|
|
1901
|
+
}else{
|
|
1902
|
+
|
|
1903
|
+
// Terminal branch: a handshake message we do not implement, or one that
|
|
1904
|
+
// is illegal at the negotiated version (RFC 8446 §4: HelloRequest and the
|
|
1905
|
+
// other TLS 1.2-only types have no place in a 1.3 handshake). Anything
|
|
1906
|
+
// reaching here was NOT processed, so it must not be ignored — silently
|
|
1907
|
+
// dropping unknown messages let a peer stream them forever and hid real
|
|
1908
|
+
// protocol errors behind a handshake that simply never progressed.
|
|
1909
|
+
fatalAlert(wire.TLS_ALERT.UNEXPECTED_MESSAGE,
|
|
1910
|
+
'Unexpected handshake message type ' +
|
|
1911
|
+
(message.handshake_type !== undefined ? message.handshake_type : message.type));
|
|
1912
|
+
return;
|
|
1913
|
+
|
|
954
1914
|
}
|
|
955
1915
|
|
|
956
1916
|
}
|
|
957
1917
|
|
|
958
1918
|
|
|
959
1919
|
function set_context(options){
|
|
1920
|
+
// Terminal states are terminal: after a fatal alert ('error') or a close,
|
|
1921
|
+
// the reactive loop must not keep selecting parameters, deriving keys or
|
|
1922
|
+
// emitting messages. This is also what stops it from writing into a
|
|
1923
|
+
// transport whose peer already went away (see TLSSocket's destroyed
|
|
1924
|
+
// marking — the two guards meet in the middle).
|
|
1925
|
+
if (context.state === 'error' || context.state === 'closed') return;
|
|
1926
|
+
|
|
960
1927
|
let has_changed=false;
|
|
961
1928
|
|
|
962
1929
|
if (options && typeof options === 'object'){
|
|
@@ -1356,8 +2323,64 @@ function TLSSession(options){
|
|
|
1356
2323
|
|
|
1357
2324
|
if(has_changed==true){
|
|
1358
2325
|
|
|
2326
|
+
/**
|
|
2327
|
+
* Values decided during THIS pass of the reactive loop.
|
|
2328
|
+
*
|
|
2329
|
+
* They are written here and only copied into `context` when the pass
|
|
2330
|
+
* commits. That gives one rule, and getting it wrong has already caused
|
|
2331
|
+
* three separate bugs (a spurious ServerHello rejection, the wrong cipher
|
|
2332
|
+
* on PSK resumption, and a HelloRetryRequest that was never sent — the
|
|
2333
|
+
* last two presenting as hangs rather than as wrong answers):
|
|
2334
|
+
*
|
|
2335
|
+
* A gate whose decision MUST be made on the same pass that the input
|
|
2336
|
+
* arrived reads params_to_set first, falling back to context. Use the
|
|
2337
|
+
* `pending()` helper defined just below rather than writing the
|
|
2338
|
+
* ternary out:
|
|
2339
|
+
*
|
|
2340
|
+
* if (pending('selected_version') === wire.TLS_VERSION.TLS1_3) { … }
|
|
2341
|
+
*
|
|
2342
|
+
* A step that can simply wait may read `context` directly. Writing to
|
|
2343
|
+
* params_to_set sets has_changed, so the loop runs again and the value
|
|
2344
|
+
* will be in `context` next time round.
|
|
2345
|
+
*
|
|
2346
|
+
* A SECOND rule, same root, different shape — worth stating because it has
|
|
2347
|
+
* now caused four bugs (a spurious ServerHello rejection, the wrong cipher
|
|
2348
|
+
* on PSK resumption, a HelloRetryRequest that was never sent, and a
|
|
2349
|
+
* HelloVerifyRequest sent in violation of RFC 9147 §5.1):
|
|
2350
|
+
*
|
|
2351
|
+
* A decision derived from an INCOMING MESSAGE must be read from that
|
|
2352
|
+
* message, never from state the message itself is about to establish.
|
|
2353
|
+
*
|
|
2354
|
+
* `is13()` answers "what did we negotiate", which is the right question
|
|
2355
|
+
* once negotiation is done and the wrong one while processing the very
|
|
2356
|
+
* message that decides it. When handling a ClientHello or ServerHello,
|
|
2357
|
+
* take the version from the message's own supported_versions — see
|
|
2358
|
+
* `sh_is_13` in the ServerHello path and `offersDtls13` in dtls_session.
|
|
2359
|
+
*
|
|
2360
|
+
* The test is whether deferring changes the OUTCOME. Key generation and
|
|
2361
|
+
* shared-secret derivation can wait — they just happen a pass later.
|
|
2362
|
+
* HelloRetryRequest cannot: by the next pass the ServerHello may already
|
|
2363
|
+
* have been sent, and the opportunity is gone. Anything gated on
|
|
2364
|
+
* `hello_sent`, `finished_sent` or another one-shot latch is in that
|
|
2365
|
+
* category.
|
|
2366
|
+
*/
|
|
1359
2367
|
let params_to_set = {};
|
|
1360
2368
|
|
|
2369
|
+
/**
|
|
2370
|
+
* The value of a negotiated field AS OF THIS PASS.
|
|
2371
|
+
*
|
|
2372
|
+
* Reads params_to_set first, then context — the rule documented above,
|
|
2373
|
+
* expressed once so no gate can forget the fallback half of it. Seven
|
|
2374
|
+
* gates spelled this ternary out under seven different local names
|
|
2375
|
+
* (pickedVersion, pskPassVersion, hrrVersionNow …), which meant a reader
|
|
2376
|
+
* had to re-derive the intent each time and a new gate could easily copy
|
|
2377
|
+
* only the `context` half. `pending('selected_version')` says what it is.
|
|
2378
|
+
*/
|
|
2379
|
+
function pending(field) {
|
|
2380
|
+
return (field in params_to_set) ? params_to_set[field] : context[field];
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
|
|
1361
2384
|
|
|
1362
2385
|
|
|
1363
2386
|
|
|
@@ -1407,23 +2430,112 @@ function TLSSession(options){
|
|
|
1407
2430
|
}
|
|
1408
2431
|
}
|
|
1409
2432
|
|
|
1410
|
-
// TLS 1.3 PSK resumption
|
|
1411
|
-
//
|
|
1412
|
-
//
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
2433
|
+
// TLS 1.3 PSK resumption — RFC 8446 §4.2.11:
|
|
2434
|
+
// "The server MUST ensure that it selects a compatible PSK (if any)
|
|
2435
|
+
// and cipher suite ... a cipher suite with the same hash algorithm."
|
|
2436
|
+
//
|
|
2437
|
+
// The constraint is on the HASH, not on the exact suite the ticket was
|
|
2438
|
+
// issued under. Two bugs lived in the previous version, which forced
|
|
2439
|
+
// the stored suite and otherwise fell through:
|
|
2440
|
+
//
|
|
2441
|
+
// - it ignored our own preference order among the suites that DO
|
|
2442
|
+
// match the hash, so a client offering several compatible suites
|
|
2443
|
+
// got whichever one the ticket happened to carry;
|
|
2444
|
+
// - when the stored suite was no longer on offer it silently
|
|
2445
|
+
// continued with the PSK anyway and let ordinary selection pick a
|
|
2446
|
+
// suite with a DIFFERENT hash — resuming under a key schedule the
|
|
2447
|
+
// peer never agreed to.
|
|
2448
|
+
//
|
|
2449
|
+
// Correct rule: choose, by our normal preference, among the mutually
|
|
2450
|
+
// supported suites whose hash matches the PSK. If none exists the PSK
|
|
2451
|
+
// is unusable and must be dropped so the handshake falls back to a full
|
|
2452
|
+
// one — accepting a PSK we cannot honour is not an "edge case", it is a
|
|
2453
|
+
// resumption the peer will reject.
|
|
2454
|
+
// Version check reads params_to_set first: selection runs in the same
|
|
2455
|
+
// pass that decides the version, so context.selected_version is still
|
|
2456
|
+
// null here and is13() would report false for every 1.3 handshake.
|
|
2457
|
+
let pskPassVersion = pending('selected_version');
|
|
2458
|
+
let pskPassIs13 = pskPassVersion === wire.TLS_VERSION.TLS1_3 ||
|
|
2459
|
+
pskPassVersion === wire.DTLS_VERSION.DTLS1_3;
|
|
2460
|
+
|
|
2461
|
+
|
|
2462
|
+
if (context.isServer && context.psk_accepted && context.psk_offered &&
|
|
2463
|
+
context.psk_offered.hash && pskPassIs13) {
|
|
2464
|
+
|
|
2465
|
+
let pskHash = context.psk_offered.hash;
|
|
2466
|
+
let chosen = null;
|
|
2467
|
+
|
|
2468
|
+
for (let i = 0; i < context.local_supported_cipher_suites.length; i++) {
|
|
2469
|
+
let cs = context.local_supported_cipher_suites[i] | 0;
|
|
2470
|
+
if (context.remote_supported_cipher_suites.indexOf(cs) < 0) continue;
|
|
2471
|
+
let meta = TLS_CIPHER_SUITES[cs];
|
|
2472
|
+
if (!meta || meta.hash !== pskHash) continue;
|
|
2473
|
+
// Describable is not usable — record.js implements AEAD only.
|
|
2474
|
+
if (!is_usable_cipher_suite(cs)) continue;
|
|
2475
|
+
chosen = cs;
|
|
2476
|
+
break;
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
|
|
2480
|
+
if (chosen !== null) {
|
|
2481
|
+
params_to_set['selected_cipher_suite'] = chosen;
|
|
2482
|
+
} else {
|
|
2483
|
+
// No compatible suite → the PSK cannot be used. Undo acceptance and
|
|
2484
|
+
// let the normal path run a full handshake.
|
|
2485
|
+
dbg('SRV-PSK', 'no mutually-supported cipher with hash', pskHash,
|
|
2486
|
+
'— rejecting PSK, falling back to full handshake');
|
|
2487
|
+
context.psk_accepted = false;
|
|
2488
|
+
context.isResumed = false;
|
|
2489
|
+
context.psk_offered = null;
|
|
1419
2490
|
}
|
|
1420
|
-
// If client no longer offers this cipher, PSK can't be used — but we already accepted it.
|
|
1421
|
-
// This is an edge case; fall through to normal selection and hope for the best.
|
|
1422
2491
|
}
|
|
1423
2492
|
|
|
1424
2493
|
if (!('selected_cipher_suite' in params_to_set)) {
|
|
2494
|
+
// RFC 4492 §2 / RFC 5246 §7.4.2: a (D)TLS 1.2 cipher suite names the
|
|
2495
|
+
// authentication algorithm, and it MUST match the key type of the
|
|
2496
|
+
// certificate we are going to present — an ECDHE_RSA suite cannot be
|
|
2497
|
+
// used with an ECDSA certificate. Selecting purely by mutual support
|
|
2498
|
+
// let a server with an EC certificate pick ECDHE_RSA and then send a
|
|
2499
|
+
// certificate the peer rejects as "wrong certificate type".
|
|
2500
|
+
//
|
|
2501
|
+
// The suite→auth mapping already exists in crypto.js's cipher table
|
|
2502
|
+
// (the `sig` field), so this reads that authority rather than
|
|
2503
|
+
// re-deriving it. TLS 1.3 suites are authentication-agnostic
|
|
2504
|
+
// (sig: 'TLS13'), so they are never filtered here.
|
|
2505
|
+
let localAuth = null;
|
|
2506
|
+
if (context.isServer) {
|
|
2507
|
+
try {
|
|
2508
|
+
if (context.cert_private_key) {
|
|
2509
|
+
let lk = local_private_key();
|
|
2510
|
+
localAuth = lk ? lk.asymmetricKeyType : null;
|
|
2511
|
+
}
|
|
2512
|
+
} catch (e) { localAuth = null; }
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
let suiteAuthOk = function (suite) {
|
|
2516
|
+
if (!context.isServer || localAuth === null) return true; // client, or key type unknown
|
|
2517
|
+
let meta = TLS_CIPHER_SUITES[suite];
|
|
2518
|
+
if (!meta || !meta.sig || meta.sig === 'TLS13') return true; // not auth-bound
|
|
2519
|
+
if (meta.sig === 'RSA') return localAuth === 'rsa' || localAuth === 'rsa-pss';
|
|
2520
|
+
if (meta.sig === 'ECDSA') return localAuth === 'ec';
|
|
2521
|
+
return true;
|
|
2522
|
+
};
|
|
2523
|
+
|
|
1425
2524
|
for (let i2 = 0; i2 < context.local_supported_cipher_suites.length; i2++) {
|
|
1426
2525
|
let cs = context.local_supported_cipher_suites[i2] | 0;
|
|
2526
|
+
if (!suiteAuthOk(cs)) continue;
|
|
2527
|
+
// A suite belongs to exactly one protocol generation (RFC 8446
|
|
2528
|
+
// §B.4). Selecting a 1.3 suite while negotiating 1.2 — which a
|
|
2529
|
+
// configured list can easily contain — yields a ServerHello the
|
|
2530
|
+
// peer cannot act on. This is the server-side mirror of not
|
|
2531
|
+
// OFFERING 1.3 suites from a 1.2-only client profile.
|
|
2532
|
+
if (!suite_matches_version(cs, pending('selected_version'))) continue;
|
|
2533
|
+
// The suite table names more suites than the record layer can
|
|
2534
|
+
// protect (CBC entries exist for parsing peer offers). Selecting a
|
|
2535
|
+
// described-but-unimplemented suite yields a handshake that agrees
|
|
2536
|
+
// on parameters and then cannot encrypt a record — the cipher-suite
|
|
2537
|
+
// analogue of picking a key-exchange group we cannot compute.
|
|
2538
|
+
if (!is_usable_cipher_suite(cs)) continue;
|
|
1427
2539
|
for (let j2 = 0; j2 < context.remote_supported_cipher_suites.length; j2++) {
|
|
1428
2540
|
|
|
1429
2541
|
if ((context.remote_supported_cipher_suites[j2] | 0) == cs) {
|
|
@@ -1494,67 +2606,124 @@ function TLSSession(options){
|
|
|
1494
2606
|
if (context.selected_group == null){
|
|
1495
2607
|
if(context.local_supported_groups.length > 0 && context.remote_supported_groups.length > 0) {
|
|
1496
2608
|
for (let i = 0; i < context.local_supported_groups.length; i++) {
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
2609
|
+
let g = context.local_supported_groups[i];
|
|
2610
|
+
if (context.remote_supported_groups.indexOf(g) < 0) continue;
|
|
2611
|
+
|
|
2612
|
+
// Mutual support is not enough: we must be able to actually PRODUCE
|
|
2613
|
+
// a key share for the group. ecdh.js is the single source of truth
|
|
2614
|
+
// for which groups are implemented, and generate_keypair returns
|
|
2615
|
+
// null (rather than throwing) for one it does not know — so a group
|
|
2616
|
+
// that reaches here from configuration alone, such as a
|
|
2617
|
+
// post-quantum group named in a --curves list we merely echo back,
|
|
2618
|
+
// gets selected and then silently never produces a key. The
|
|
2619
|
+
// reactive loop then waits forever for a key share that cannot
|
|
2620
|
+
// exist: no ServerHello, no alert, connection held open. That is a
|
|
2621
|
+
// denial of service, and it is caused by trusting a configured list
|
|
2622
|
+
// over the implementation.
|
|
2623
|
+
if (!is_supported_group(g)) continue;
|
|
2624
|
+
|
|
2625
|
+
params_to_set['selected_group'] = g;
|
|
2626
|
+
break;
|
|
1501
2627
|
}
|
|
1502
2628
|
}
|
|
1503
2629
|
}
|
|
1504
2630
|
|
|
1505
2631
|
|
|
1506
2632
|
|
|
1507
|
-
//
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
2633
|
+
// ── Negotiation outcome: decidable, or impossible? ──
|
|
2634
|
+
//
|
|
2635
|
+
// Every selection step above is written as "if not chosen yet, try to
|
|
2636
|
+
// choose" — which quietly conflates two very different states: the
|
|
2637
|
+
// inputs have not arrived yet, and the inputs HAVE arrived and share
|
|
2638
|
+
// nothing with ours. The first must wait; the second can never succeed.
|
|
2639
|
+
// Treating both as "wait" is what made a ClientHello offering only
|
|
2640
|
+
// groups/ciphers/versions we do not implement hang forever instead of
|
|
2641
|
+
// being refused — a stall is a DoS, and it is also how an unimplemented
|
|
2642
|
+
// post-quantum group (Kyber, X25519MLKEM768) took the connection down
|
|
2643
|
+
// without a single line of code mentioning it.
|
|
2644
|
+
//
|
|
2645
|
+
// This is deliberately ONE check covering all three negotiated
|
|
2646
|
+
// parameters rather than a special case per parameter: they fail for the
|
|
2647
|
+
// same reason and must fail the same way. RFC 8446 §4.1.1 / RFC 5246
|
|
2648
|
+
// §7.4.1.3.
|
|
2649
|
+
//
|
|
2650
|
+
// Only the server decides these; the client validates the server's
|
|
2651
|
+
// choice elsewhere. A non-empty remote cipher list is the signal that a
|
|
2652
|
+
// ClientHello was actually processed, so we never fire before the peer
|
|
2653
|
+
// has spoken.
|
|
2654
|
+
if (context.isServer && context.remote_supported_cipher_suites.length > 0) {
|
|
2655
|
+
|
|
2656
|
+
let pickedVersion = pending('selected_version');
|
|
2657
|
+
|
|
2658
|
+
if (pickedVersion === null && context.local_supported_versions.length > 0 &&
|
|
2659
|
+
context.remote_supported_versions.length > 0) {
|
|
2660
|
+
fatalAlert(wire.TLS_ALERT.PROTOCOL_VERSION,
|
|
2661
|
+
'No protocol version in common with the peer');
|
|
2662
|
+
return;
|
|
2663
|
+
}
|
|
1528
2664
|
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
2665
|
+
// Cipher and group are checked INDEPENDENTLY of the version outcome.
|
|
2666
|
+
//
|
|
2667
|
+
// These used to be nested inside `if (pickedVersion !== null)`, which
|
|
2668
|
+
// silently made "we could not agree on a group" undetectable whenever
|
|
2669
|
+
// version selection had not completed — and version selection does not
|
|
2670
|
+
// complete when local_supported_versions has not been configured yet,
|
|
2671
|
+
// which is exactly the state a bare server session is in. The result
|
|
2672
|
+
// was the original stall surviving for the one shape that matters:
|
|
2673
|
+
// a ClientHello offering ONLY an unimplemented group (Kyber,
|
|
2674
|
+
// X25519MLKEM768). Each parameter's failure is terminal on its own; one
|
|
2675
|
+
// undecided parameter must not mask another's impossibility.
|
|
2676
|
+
let pickedCipher = pending('selected_cipher_suite');
|
|
2677
|
+
if (pickedCipher === null &&
|
|
2678
|
+
context.local_supported_cipher_suites.length > 0 &&
|
|
2679
|
+
context.remote_supported_cipher_suites.length > 0) {
|
|
2680
|
+
fatalAlert(wire.TLS_ALERT.HANDSHAKE_FAILURE,
|
|
2681
|
+
'No cipher suite in common with the peer');
|
|
2682
|
+
return;
|
|
2683
|
+
}
|
|
1536
2684
|
|
|
1537
|
-
|
|
2685
|
+
// Groups: only meaningful once the peer actually offered some. An
|
|
2686
|
+
// offer we share nothing with is terminal; note that a shared group
|
|
2687
|
+
// for which the peer merely sent no key_share is NOT this case —
|
|
2688
|
+
// selected_group is set there and HelloRetryRequest handles it.
|
|
2689
|
+
let pickedGroup = pending('selected_group');
|
|
2690
|
+
if (pickedGroup === null &&
|
|
2691
|
+
context.remote_supported_groups.length > 0 &&
|
|
2692
|
+
context.local_supported_groups.length > 0) {
|
|
2693
|
+
fatalAlert(wire.TLS_ALERT.HANDSHAKE_FAILURE,
|
|
2694
|
+
'No key-exchange group in common with the peer');
|
|
2695
|
+
return;
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
1538
2698
|
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
2699
|
+
//create the key by the group if dont have...
|
|
2700
|
+
// Curve-specific keygen lives in ecdh.js (single source of truth for
|
|
2701
|
+
// which groups exist and how their keys are formed); the loop only
|
|
2702
|
+
// decides WHEN a key is needed.
|
|
2703
|
+
if(context.selected_group !== null && context.selected_group in context.local_key_groups==false){
|
|
1542
2704
|
|
|
2705
|
+
let kp = ecdh_generate_keypair(context.selected_group);
|
|
2706
|
+
if (kp) {
|
|
1543
2707
|
params_to_set['add_local_key_groups']=[
|
|
1544
2708
|
{
|
|
1545
2709
|
group: context.selected_group,
|
|
1546
|
-
private_key: private_key,
|
|
1547
|
-
public_key: public_key
|
|
2710
|
+
private_key: kp.private_key,
|
|
2711
|
+
public_key: kp.public_key
|
|
1548
2712
|
}
|
|
1549
2713
|
];
|
|
1550
|
-
|
|
1551
2714
|
}
|
|
1552
2715
|
|
|
1553
|
-
|
|
1554
2716
|
}
|
|
1555
2717
|
|
|
1556
2718
|
//get shared_secret...
|
|
1557
|
-
|
|
2719
|
+
// Can we compute the ECDHE secret yet? Needs a chosen group, both halves
|
|
2720
|
+
// of the exchange present, and no secret computed already.
|
|
2721
|
+
let ecdheReady = context.selected_group !== null &&
|
|
2722
|
+
context.ecdhe_shared_secret == null &&
|
|
2723
|
+
(context.selected_group in context.local_key_groups) &&
|
|
2724
|
+
(context.selected_group in context.remote_key_groups);
|
|
2725
|
+
|
|
2726
|
+
if (ecdheReady) {
|
|
1558
2727
|
|
|
1559
2728
|
//check we have remote public key and local private key...
|
|
1560
2729
|
if(context.remote_key_groups[context.selected_group].public_key!==null && context.local_key_groups[context.selected_group].private_key!==null){
|
|
@@ -1562,24 +2731,21 @@ function TLSSession(options){
|
|
|
1562
2731
|
let remote_public_key=context.remote_key_groups[context.selected_group].public_key;
|
|
1563
2732
|
let local_private_key=context.local_key_groups[context.selected_group].private_key;
|
|
1564
2733
|
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
params_to_set['ecdhe_shared_secret']=
|
|
1576
|
-
|
|
1577
|
-
}
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
params_to_set['ecdhe_shared_secret']=ecdhe_shared_secret;
|
|
1582
|
-
|
|
2734
|
+
// The peer's key share is fully attacker-controlled. ecdh.js owns the
|
|
2735
|
+
// point-format rules and throws on anything malformed (wrong length,
|
|
2736
|
+
// bad prefix, off-curve, low-order producing an all-zero secret per
|
|
2737
|
+
// RFC 7748 6.1); node:crypto throws too. Our job here is only to turn
|
|
2738
|
+
// any such failure into a fatal illegal_parameter (RFC 8446 4.2.8)
|
|
2739
|
+
// rather than let it escape through set_context into the transport's
|
|
2740
|
+
// data handler -- that would be a remote DoS on every transport.
|
|
2741
|
+
// fatalAlert moves us to a terminal state, so the reactive loop
|
|
2742
|
+
// cannot re-enter and retry the poisoned computation.
|
|
2743
|
+
try {
|
|
2744
|
+
params_to_set['ecdhe_shared_secret'] =
|
|
2745
|
+
ecdh_get_shared_secret(context.selected_group, local_private_key, remote_public_key);
|
|
2746
|
+
} catch (e) {
|
|
2747
|
+
fatalAlert(wire.TLS_ALERT.ILLEGAL_PARAMETER, 'Invalid peer key share for group 0x' + context.selected_group.toString(16) + ': ' + e.message);
|
|
2748
|
+
return;
|
|
1583
2749
|
}
|
|
1584
2750
|
|
|
1585
2751
|
}
|
|
@@ -1592,14 +2758,31 @@ function TLSSession(options){
|
|
|
1592
2758
|
if(context.isServer==true){
|
|
1593
2759
|
|
|
1594
2760
|
// HelloRetryRequest: if we selected a group but client didn't send a key_share for it
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
2761
|
+
// Read the negotiated values from params_to_set first. Version, cipher
|
|
2762
|
+
// and group are all decided EARLIER IN THIS SAME PASS and are only
|
|
2763
|
+
// copied into `context` when the pass commits, so reading `context`
|
|
2764
|
+
// here sees nulls on exactly the pass that processes the ClientHello.
|
|
2765
|
+
//
|
|
2766
|
+
// The consequence was a hang, not a wrong choice: a client that offers
|
|
2767
|
+
// a group we support but sends its key_share for a different one (very
|
|
2768
|
+
// much including an unimplemented post-quantum group offered alongside
|
|
2769
|
+
// X25519) needs a HelloRetryRequest, and we silently sent nothing at
|
|
2770
|
+
// all. The connection then sat open until the peer timed out — the same
|
|
2771
|
+
// "impossible mistaken for not-yet-decidable" confusion, one layer up.
|
|
2772
|
+
let hrrVersionNow = pending('selected_version');
|
|
2773
|
+
let hrrIs13 = hrrVersionNow === wire.TLS_VERSION.TLS1_3 ||
|
|
2774
|
+
hrrVersionNow === wire.DTLS_VERSION.DTLS1_3;
|
|
2775
|
+
let hrrGroupNow = pending('selected_group');
|
|
2776
|
+
let hrrCipherNow = pending('selected_cipher_suite');
|
|
2777
|
+
|
|
2778
|
+
if(context.hello_sent==false && !context.helloRetried && hrrIs13 &&
|
|
2779
|
+
hrrGroupNow !== null && hrrCipherNow !== null &&
|
|
2780
|
+
!(hrrGroupNow in context.remote_key_groups)){
|
|
1598
2781
|
|
|
1599
2782
|
context.helloRetried = true;
|
|
1600
2783
|
|
|
1601
2784
|
// Replace transcript with message_hash (RFC 8446 §4.4.1)
|
|
1602
|
-
let hashName = TLS_CIPHER_SUITES[
|
|
2785
|
+
let hashName = TLS_CIPHER_SUITES[hrrCipherNow].hash;
|
|
1603
2786
|
let ch1_hash = getHashFn(hashName)(concatUint8Arrays(context.transcript));
|
|
1604
2787
|
let message_hash = wire.build_message(wire.TLS_MESSAGE_TYPE.MESSAGE_HASH, ch1_hash);
|
|
1605
2788
|
context.transcript = [message_hash];
|
|
@@ -1609,9 +2792,9 @@ function TLSSession(options){
|
|
|
1609
2792
|
|
|
1610
2793
|
// Build and send HRR (it's a ServerHello with magic random)
|
|
1611
2794
|
let hrr_body = wire.build_hello_retry_request({
|
|
1612
|
-
cipher_suite:
|
|
2795
|
+
cipher_suite: hrrCipherNow,
|
|
1613
2796
|
selected_version: context.selected_version,
|
|
1614
|
-
selected_group:
|
|
2797
|
+
selected_group: hrrGroupNow,
|
|
1615
2798
|
session_id: context.remote_session_id,
|
|
1616
2799
|
});
|
|
1617
2800
|
let hrr_data = wire.build_message(wire.TLS_MESSAGE_TYPE.SERVER_HELLO, hrr_body);
|
|
@@ -1635,14 +2818,14 @@ function TLSSession(options){
|
|
|
1635
2818
|
if(context.hello_sent==false){
|
|
1636
2819
|
|
|
1637
2820
|
if(context.selected_version!==null && context.selected_cipher_suite!==null && context.selected_session_id!==null){
|
|
1638
|
-
if((
|
|
2821
|
+
if((is13())){
|
|
1639
2822
|
if(context.selected_group in context.local_key_groups==true && context.local_key_groups[context.selected_group].public_key!==null){
|
|
1640
2823
|
// After HRR, don't send ServerHello until CH2 provides the requested key_share
|
|
1641
2824
|
if (!context.helloRetried || (context.selected_group in context.remote_key_groups)) {
|
|
1642
2825
|
can_send_hello=true;
|
|
1643
2826
|
}
|
|
1644
2827
|
}
|
|
1645
|
-
}else if((
|
|
2828
|
+
}else if((is12())){
|
|
1646
2829
|
// Block ServerHello while waiting for async resumeSession decision
|
|
1647
2830
|
if (!context.tls12_resume_pending) {
|
|
1648
2831
|
can_send_hello=true;
|
|
@@ -1673,7 +2856,7 @@ function TLSSession(options){
|
|
|
1673
2856
|
|
|
1674
2857
|
let build_message_params=null;
|
|
1675
2858
|
|
|
1676
|
-
if((
|
|
2859
|
+
if((is13())){
|
|
1677
2860
|
|
|
1678
2861
|
let shExtensions = [
|
|
1679
2862
|
{
|
|
@@ -1698,13 +2881,34 @@ function TLSSession(options){
|
|
|
1698
2881
|
type: 'server_hello',
|
|
1699
2882
|
version: context.selected_version,
|
|
1700
2883
|
random: context.local_random,
|
|
1701
|
-
|
|
2884
|
+
// legacy_session_id_echo.
|
|
2885
|
+
//
|
|
2886
|
+
// TLS 1.3 (RFC 8446 §4.1.3) requires the server to echo the
|
|
2887
|
+
// client's legacy_session_id verbatim — that is the whole point
|
|
2888
|
+
// of the Appendix D.4 compatibility mode.
|
|
2889
|
+
//
|
|
2890
|
+
// DTLS 1.3 requires the OPPOSITE. RFC 9147 §5: "DTLS
|
|
2891
|
+
// implementations do not use the TLS 1.3 'compatibility mode'
|
|
2892
|
+
// described in Appendix D.4 of [TLS13]. DTLS servers MUST NOT
|
|
2893
|
+
// echo the 'legacy_session_id' value from the client and
|
|
2894
|
+
// endpoints MUST NOT send ChangeCipherSpec messages."
|
|
2895
|
+
//
|
|
2896
|
+
// Same field, same struct, opposite MUST — and the shared engine
|
|
2897
|
+
// was doing the TLS thing for both transports. A DTLS peer that
|
|
2898
|
+
// enforces §5 sees an echo it never expected. This is exactly the
|
|
2899
|
+
// shape of the other DTLS 1.3 bugs: a legacy field whose meaning
|
|
2900
|
+
// inverted between the two protocols.
|
|
2901
|
+
session_id: (function () {
|
|
2902
|
+
// RFC 9147 §5 — see the note above.
|
|
2903
|
+
let isDtls = context.selected_version === wire.DTLS_VERSION.DTLS1_3;
|
|
2904
|
+
return isDtls ? new Uint8Array(0) : context.remote_session_id;
|
|
2905
|
+
})(),
|
|
1702
2906
|
cipher_suite: context.selected_cipher_suite,
|
|
1703
2907
|
extensions: shExtensions
|
|
1704
2908
|
};
|
|
1705
2909
|
|
|
1706
2910
|
|
|
1707
|
-
}else if((
|
|
2911
|
+
}else if((is12())){
|
|
1708
2912
|
|
|
1709
2913
|
|
|
1710
2914
|
// TLS 1.2 ServerHello: no SUPPORTED_VERSIONS or KEY_SHARE.
|
|
@@ -1742,6 +2946,15 @@ function TLSSession(options){
|
|
|
1742
2946
|
// Responsibility for only answering extensions the client
|
|
1743
2947
|
// actually offered stays with the caller (it can inspect the
|
|
1744
2948
|
// ClientHello via the 'clienthello' event / getRemoteExtension).
|
|
2949
|
+
// DTLS-SRTP answer for (D)TLS 1.2, where it rides the ServerHello
|
|
2950
|
+
// rather than EncryptedExtensions (which does not exist in 1.2).
|
|
2951
|
+
let srtpSel12 = negotiate_srtp_profile();
|
|
2952
|
+
if (srtpSel12 !== null) {
|
|
2953
|
+
context.selected_srtp_profile = srtpSel12;
|
|
2954
|
+
ext_list.push({ type: 'USE_SRTP',
|
|
2955
|
+
value: { profiles: [srtpSel12], mki: new Uint8Array(0) } });
|
|
2956
|
+
}
|
|
2957
|
+
|
|
1745
2958
|
for (let lei = 0; lei < context.local_extensions.length; lei++) {
|
|
1746
2959
|
ext_list.push(context.local_extensions[lei]);
|
|
1747
2960
|
}
|
|
@@ -1803,18 +3016,18 @@ function TLSSession(options){
|
|
|
1803
3016
|
|
|
1804
3017
|
//get base_secret
|
|
1805
3018
|
if (context.base_secret==null && context.selected_cipher_suite !== null){
|
|
1806
|
-
if((
|
|
3019
|
+
if((is13()) && (context.ecdhe_shared_secret !== null)){
|
|
1807
3020
|
|
|
1808
|
-
let hashName =
|
|
3021
|
+
let hashName = negotiated_hash();
|
|
1809
3022
|
let result;
|
|
1810
3023
|
// Use incremental transcript hash — avoids concat+hash of full transcript
|
|
1811
3024
|
let tx_hash = get_transcript_hash(hashName);
|
|
1812
3025
|
if (context.psk_accepted && context.psk_offered && context.psk_offered.psk) {
|
|
1813
3026
|
// PSK + ECDHE key schedule
|
|
1814
|
-
result = derive_handshake_traffic_secrets_psk_with_hash(hashName, context.psk_offered.psk, context.ecdhe_shared_secret, tx_hash);
|
|
3027
|
+
result = derive_handshake_traffic_secrets_psk_with_hash(hashName, context.psk_offered.psk, context.ecdhe_shared_secret, tx_hash, label_prefix());
|
|
1815
3028
|
} else {
|
|
1816
3029
|
// Standard key schedule (no PSK)
|
|
1817
|
-
result = derive_handshake_traffic_secrets_with_hash(hashName, context.ecdhe_shared_secret, tx_hash);
|
|
3030
|
+
result = derive_handshake_traffic_secrets_with_hash(hashName, context.ecdhe_shared_secret, tx_hash, label_prefix());
|
|
1818
3031
|
}
|
|
1819
3032
|
|
|
1820
3033
|
params_to_set['base_secret']=result.handshake_secret;
|
|
@@ -1827,7 +3040,7 @@ function TLSSession(options){
|
|
|
1827
3040
|
params_to_set['remote_handshake_traffic_secret']=result.server_handshake_traffic_secret;
|
|
1828
3041
|
}
|
|
1829
3042
|
|
|
1830
|
-
}else if((
|
|
3043
|
+
}else if((is12()) && context.local_random!==null && context.remote_random!==null){
|
|
1831
3044
|
if(context.ecdhe_shared_secret !== null){
|
|
1832
3045
|
|
|
1833
3046
|
|
|
@@ -1845,7 +3058,7 @@ function TLSSession(options){
|
|
|
1845
3058
|
// Server: CKE just arrived, transcript is complete.
|
|
1846
3059
|
// Client: must wait until CKE is sent and in transcript.
|
|
1847
3060
|
if(context.isServer || context.key_exchange_sent){
|
|
1848
|
-
let hashFn = getHashFn(
|
|
3061
|
+
let hashFn = getHashFn(negotiated_hash());
|
|
1849
3062
|
|
|
1850
3063
|
// Use snapshot up to CKE if available (excludes CertificateVerify)
|
|
1851
3064
|
let emsTranscript = context._emsTranscriptLen
|
|
@@ -1853,12 +3066,12 @@ function TLSSession(options){
|
|
|
1853
3066
|
: context.transcript;
|
|
1854
3067
|
let transcript_hash = hashFn(concatUint8Arrays(emsTranscript));
|
|
1855
3068
|
|
|
1856
|
-
let master_secret = tls12_prf(context.ecdhe_shared_secret, "extended master secret", transcript_hash, 48,
|
|
3069
|
+
let master_secret = tls12_prf(context.ecdhe_shared_secret, "extended master secret", transcript_hash, 48, negotiated_hash());
|
|
1857
3070
|
|
|
1858
3071
|
params_to_set['base_secret']=master_secret;
|
|
1859
3072
|
}
|
|
1860
3073
|
}else{
|
|
1861
|
-
let master_secret = tls12_prf(context.ecdhe_shared_secret, "master secret", concatUint8Arrays([client_random, server_random]), 48,
|
|
3074
|
+
let master_secret = tls12_prf(context.ecdhe_shared_secret, "master secret", concatUint8Arrays([client_random, server_random]), 48, negotiated_hash());
|
|
1862
3075
|
|
|
1863
3076
|
params_to_set['base_secret']=master_secret;
|
|
1864
3077
|
}
|
|
@@ -1875,7 +3088,7 @@ function TLSSession(options){
|
|
|
1875
3088
|
|
|
1876
3089
|
|
|
1877
3090
|
//send encrypted_extensions...
|
|
1878
|
-
if (context.isServer==true && (
|
|
3091
|
+
if (context.isServer==true && (is13())){
|
|
1879
3092
|
if(context.encrypted_exts_sent==false && context.hello_sent==true && context.local_handshake_traffic_secret!==null){
|
|
1880
3093
|
|
|
1881
3094
|
let extensions=[];
|
|
@@ -1883,6 +3096,31 @@ function TLSSession(options){
|
|
|
1883
3096
|
extensions.push({ type: 'ALPN', value: [context.selected_alpn] });
|
|
1884
3097
|
}
|
|
1885
3098
|
|
|
3099
|
+
// server_name acknowledgement (RFC 6066 §3, RFC 8446 §4.4.2).
|
|
3100
|
+
// When a client sent SNI and we accepted it, the server confirms by
|
|
3101
|
+
// echoing an EMPTY server_name extension here — no ServerNameList,
|
|
3102
|
+
// zero-length body. The extension is asymmetric: the list only ever
|
|
3103
|
+
// travels client→server.
|
|
3104
|
+
//
|
|
3105
|
+
// NOT on a resumed handshake. RFC 6066 §3: the acknowledgement means
|
|
3106
|
+
// "I used this name to select my certificate", which only happens in
|
|
3107
|
+
// a full handshake — on resumption the identity comes from the
|
|
3108
|
+
// established session instead, and sending the extension anyway is a
|
|
3109
|
+
// protocol error ("server acknowledged server_name when resuming").
|
|
3110
|
+
// The psk_accepted flag is the same signal the Certificate and
|
|
3111
|
+
// CertificateVerify steps already use to know they must be skipped.
|
|
3112
|
+
if (context.remote_sni && !context.psk_accepted) {
|
|
3113
|
+
extensions.push({ type: 'SERVER_NAME', value: null });
|
|
3114
|
+
}
|
|
3115
|
+
|
|
3116
|
+
|
|
3117
|
+
// DTLS-SRTP answer (RFC 5764 §4.1). One profile, empty MKI.
|
|
3118
|
+
let srtpSel = negotiate_srtp_profile();
|
|
3119
|
+
if (srtpSel !== null) {
|
|
3120
|
+
context.selected_srtp_profile = srtpSel;
|
|
3121
|
+
extensions.push({ type: 'USE_SRTP',
|
|
3122
|
+
value: { profiles: [srtpSel], mki: new Uint8Array(0) } });
|
|
3123
|
+
}
|
|
1886
3124
|
|
|
1887
3125
|
for(let i = 0; i < context.local_extensions.length; i++){
|
|
1888
3126
|
extensions.push(context.local_extensions[i]);
|
|
@@ -1907,7 +3145,7 @@ function TLSSession(options){
|
|
|
1907
3145
|
|
|
1908
3146
|
//send certificate... (skip for PSK resumption — no cert needed)
|
|
1909
3147
|
// But first: send CertificateRequest if requestCert is set (TLS 1.3 only, between EE and Cert)
|
|
1910
|
-
if(context.isServer==true && context.requestCert==true && !context.certificateRequestSent && context.encrypted_exts_sent==true && context.local_handshake_traffic_secret!==null && (
|
|
3148
|
+
if(context.isServer==true && context.requestCert==true && !context.certificateRequestSent && context.encrypted_exts_sent==true && context.local_handshake_traffic_secret!==null && (is13()) && !context.psk_accepted){
|
|
1911
3149
|
let cr_data = build_tls_message({
|
|
1912
3150
|
type: 'certificate_request',
|
|
1913
3151
|
certificate_request_context: new Uint8Array(0),
|
|
@@ -1920,7 +3158,15 @@ function TLSSession(options){
|
|
|
1920
3158
|
}
|
|
1921
3159
|
|
|
1922
3160
|
if(context.isServer==true && context.cert_sent==false && context.local_cert_chain!==null && !context.psk_accepted && !context.tls12_abbreviated){
|
|
1923
|
-
|
|
3161
|
+
// When may we put our Certificate on the wire?
|
|
3162
|
+
// 1.3: it is encrypted, so it follows EncryptedExtensions and needs
|
|
3163
|
+
// the handshake traffic secret to exist.
|
|
3164
|
+
// 1.2: it is cleartext and follows the ServerHello directly.
|
|
3165
|
+
let certDue13 = is13() && context.encrypted_exts_sent === true &&
|
|
3166
|
+
context.local_handshake_traffic_secret !== null;
|
|
3167
|
+
let certDue12 = is12() && context.hello_sent === true;
|
|
3168
|
+
|
|
3169
|
+
if(certDue13 || certDue12){
|
|
1924
3170
|
|
|
1925
3171
|
let message_data = build_tls_message({
|
|
1926
3172
|
type: 'certificate',
|
|
@@ -1931,7 +3177,7 @@ function TLSSession(options){
|
|
|
1931
3177
|
|
|
1932
3178
|
context.cert_sent=true;
|
|
1933
3179
|
|
|
1934
|
-
if ((
|
|
3180
|
+
if ((is13())){
|
|
1935
3181
|
ev.emit('message',1,context.message_sent_seq,'certificate',message_data);
|
|
1936
3182
|
}else{
|
|
1937
3183
|
ev.emit('message',0,context.message_sent_seq,'certificate',message_data);
|
|
@@ -1949,107 +3195,44 @@ function TLSSession(options){
|
|
|
1949
3195
|
|
|
1950
3196
|
|
|
1951
3197
|
//send certificate verify...
|
|
1952
|
-
if (context.isServer==true && (
|
|
3198
|
+
if (context.isServer==true && (is13())){
|
|
1953
3199
|
if(context.cert_sent==true && context.cert_verify_sent==false && context.local_cert_chain!==null && context.local_handshake_traffic_secret!==null && context.selected_cipher_suite!==null){
|
|
1954
3200
|
|
|
1955
|
-
let tbsHashName =
|
|
3201
|
+
let tbsHashName = negotiated_hash();
|
|
1956
3202
|
let tbs_data = build_cert_verify_tbs_with_hash(tbsHashName, true, get_transcript_hash(tbsHashName));
|
|
1957
3203
|
|
|
1958
|
-
let cert_private_key_obj =
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
};
|
|
1974
|
-
|
|
1975
|
-
let candidates=[];
|
|
1976
|
-
if (cert_private_key_obj.asymmetricKeyType === 'ed25519') candidates.push(SIG.ED25519);
|
|
1977
|
-
if (cert_private_key_obj.asymmetricKeyType === 'ed448') candidates.push(SIG.ED448);
|
|
1978
|
-
if (cert_private_key_obj.asymmetricKeyType === 'rsa') candidates.push(SIG.RSA_PSS_SHA256, SIG.RSA_PSS_SHA384, SIG.RSA_PSS_SHA512); // TLS 1.3: PSS only
|
|
1979
|
-
|
|
1980
|
-
if (cert_private_key_obj.asymmetricKeyType === 'ec') {
|
|
1981
|
-
let c = (cert_private_key_obj.asymmetricKeyDetails && cert_private_key_obj.asymmetricKeyDetails && cert_private_key_obj.asymmetricKeyDetails.namedCurve) || '';
|
|
1982
|
-
if (c === 'prime256v1') candidates.push(SIG.ECDSA_P256_SHA256);
|
|
1983
|
-
if (c === 'secp384r1') candidates.push(SIG.ECDSA_P384_SHA384);
|
|
1984
|
-
if (c === 'secp521r1') candidates.push(SIG.ECDSA_P521_SHA512);
|
|
1985
|
-
}
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
let preference_order = [
|
|
1989
|
-
SIG.ED25519,
|
|
1990
|
-
SIG.ED448,
|
|
1991
|
-
SIG.ECDSA_P256_SHA256,
|
|
1992
|
-
SIG.ECDSA_P384_SHA384,
|
|
1993
|
-
SIG.ECDSA_P521_SHA512,
|
|
1994
|
-
SIG.RSA_PSS_SHA256,
|
|
1995
|
-
SIG.RSA_PSS_SHA384,
|
|
1996
|
-
SIG.RSA_PSS_SHA512
|
|
1997
|
-
];
|
|
3204
|
+
let cert_private_key_obj = local_private_key();
|
|
3205
|
+
|
|
3206
|
+
// Scheme choice and signing belong to session/signing.js, which owns
|
|
3207
|
+
// the SignatureScheme registry for BOTH signing and verification.
|
|
3208
|
+
// This block used to re-declare the scheme constants, re-implement
|
|
3209
|
+
// pick_scheme's candidate/preference logic and re-implement
|
|
3210
|
+
// sign_with_scheme's padding+salt switch — ~90 lines duplicating that
|
|
3211
|
+
// module. It was already drifting: the fix that made RSA-PSS and
|
|
3212
|
+
// EdDSA usable in TLS 1.2 (RFC 8447/8422) landed in signing.js and
|
|
3213
|
+
// never reached this copy. One implementation, used by both roles.
|
|
3214
|
+
let selected_scheme = pick_scheme(
|
|
3215
|
+
wire.TLS_VERSION.TLS1_3,
|
|
3216
|
+
cert_private_key_obj,
|
|
3217
|
+
context.remote_supported_signature_algorithms || []
|
|
3218
|
+
);
|
|
1998
3219
|
|
|
1999
|
-
let
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
3220
|
+
let sig_data = null;
|
|
3221
|
+
if (selected_scheme !== null) {
|
|
3222
|
+
sig_data = sign_with_scheme(
|
|
3223
|
+
wire.TLS_VERSION.TLS1_3,
|
|
3224
|
+
selected_scheme,
|
|
3225
|
+
tbs_data,
|
|
3226
|
+
cert_private_key_obj
|
|
3227
|
+
);
|
|
2005
3228
|
}
|
|
2006
3229
|
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
sig_data = new Uint8Array(crypto.sign(null, tbs_data, cert_private_key_obj));
|
|
2012
|
-
break;
|
|
2013
|
-
|
|
2014
|
-
case SIG.ECDSA_P256_SHA256:
|
|
2015
|
-
sig_data = new Uint8Array(crypto.sign('sha256', tbs_data, cert_private_key_obj));
|
|
2016
|
-
break;
|
|
2017
|
-
|
|
2018
|
-
case SIG.ECDSA_P384_SHA384:
|
|
2019
|
-
sig_data = new Uint8Array(crypto.sign('sha384', tbs_data, cert_private_key_obj));
|
|
2020
|
-
break;
|
|
2021
|
-
|
|
2022
|
-
case SIG.ECDSA_P521_SHA512:
|
|
2023
|
-
sig_data = new Uint8Array(crypto.sign('sha512', tbs_data, cert_private_key_obj));
|
|
2024
|
-
break;
|
|
2025
|
-
|
|
2026
|
-
case SIG.RSA_PSS_SHA256:
|
|
2027
|
-
sig_data = new Uint8Array(crypto.sign('sha256', tbs_data, {
|
|
2028
|
-
key: cert_private_key_obj,
|
|
2029
|
-
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
|
|
2030
|
-
saltLength: 32
|
|
2031
|
-
}));
|
|
2032
|
-
break;
|
|
2033
|
-
|
|
2034
|
-
case SIG.RSA_PSS_SHA384:
|
|
2035
|
-
sig_data = new Uint8Array(crypto.sign('sha384', tbs_data, {
|
|
2036
|
-
key: cert_private_key_obj,
|
|
2037
|
-
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
|
|
2038
|
-
saltLength: 48
|
|
2039
|
-
}));
|
|
2040
|
-
break;
|
|
2041
|
-
|
|
2042
|
-
case SIG.RSA_PSS_SHA512:
|
|
2043
|
-
sig_data = new Uint8Array(crypto.sign('sha512', tbs_data, {
|
|
2044
|
-
key: cert_private_key_obj,
|
|
2045
|
-
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
|
|
2046
|
-
saltLength: 64
|
|
2047
|
-
}));
|
|
2048
|
-
break;
|
|
3230
|
+
if (selected_scheme === null || !sig_data) {
|
|
3231
|
+
fatalAlert(wire.TLS_ALERT.HANDSHAKE_FAILURE,
|
|
3232
|
+
'No signature scheme shared with peer for our certificate');
|
|
3233
|
+
return;
|
|
2049
3234
|
}
|
|
2050
3235
|
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
3236
|
if(sig_data){
|
|
2054
3237
|
|
|
2055
3238
|
let message_data = build_tls_message({
|
|
@@ -2083,7 +3266,7 @@ function TLSSession(options){
|
|
|
2083
3266
|
|
|
2084
3267
|
|
|
2085
3268
|
// client/server key exchange - 1.2 only...
|
|
2086
|
-
if (context.key_exchange_sent == false && (
|
|
3269
|
+
if (context.key_exchange_sent == false && (is12())) {
|
|
2087
3270
|
if(context.selected_group!==null && context.selected_group in context.local_key_groups==true && context.local_key_groups[context.selected_group].public_key!==null){
|
|
2088
3271
|
|
|
2089
3272
|
if (context.isServer==false && context.remote_hello_done==true) {
|
|
@@ -2146,10 +3329,8 @@ function TLSSession(options){
|
|
|
2146
3329
|
let transcript_data = concatUint8Arrays(context.transcript);
|
|
2147
3330
|
|
|
2148
3331
|
// Pick scheme matching our cert + server's requested algorithms
|
|
2149
|
-
let cert_key_obj =
|
|
2150
|
-
let reqAlgs =
|
|
2151
|
-
? context.certificateRequestSigAlgs
|
|
2152
|
-
: context.local_supported_signature_algorithms;
|
|
3332
|
+
let cert_key_obj = local_private_key();
|
|
3333
|
+
let reqAlgs = peer_requested_sig_algs();
|
|
2153
3334
|
let scheme = pick_scheme(wire.TLS_VERSION.TLS1_2, cert_key_obj, reqAlgs);
|
|
2154
3335
|
let signature = sign_with_scheme(wire.TLS_VERSION.TLS1_2, scheme, transcript_data, cert_key_obj);
|
|
2155
3336
|
|
|
@@ -2180,11 +3361,7 @@ function TLSSession(options){
|
|
|
2180
3361
|
let tbs_data = concatUint8Arrays([ context.remote_random, context.local_random, params_head ]);
|
|
2181
3362
|
|
|
2182
3363
|
|
|
2183
|
-
let cert_private_key_obj =
|
|
2184
|
-
key: Buffer.from(context.cert_private_key),
|
|
2185
|
-
format: 'der',
|
|
2186
|
-
type: 'pkcs8'
|
|
2187
|
-
});
|
|
3364
|
+
let cert_private_key_obj = local_private_key();
|
|
2188
3365
|
|
|
2189
3366
|
let scheme12 = pick_scheme(wire.TLS_VERSION.TLS1_2, cert_private_key_obj, context.remote_supported_signature_algorithms);
|
|
2190
3367
|
|
|
@@ -2246,7 +3423,7 @@ function TLSSession(options){
|
|
|
2246
3423
|
}
|
|
2247
3424
|
|
|
2248
3425
|
//server hello done - 1.2 only...
|
|
2249
|
-
if(context.isServer==true && (
|
|
3426
|
+
if(context.isServer==true && (is12())){
|
|
2250
3427
|
if(context.hello_done_sent==false && context.key_exchange_sent==true){
|
|
2251
3428
|
|
|
2252
3429
|
let message_data = build_tls_message({
|
|
@@ -2351,10 +3528,31 @@ function TLSSession(options){
|
|
|
2351
3528
|
context.message_sent_seq++;
|
|
2352
3529
|
|
|
2353
3530
|
// Send CertificateVerify
|
|
2354
|
-
let hashName =
|
|
3531
|
+
let hashName = negotiated_hash();
|
|
2355
3532
|
let transcript_hash = get_transcript_hash(hashName);
|
|
2356
|
-
|
|
2357
|
-
|
|
3533
|
+
// Both calls had the wrong arity/order: pick_scheme takes
|
|
3534
|
+
// (version, keyObject, peerSupportedList) and sign_with_scheme takes
|
|
3535
|
+
// (version, scheme, tbs, keyObject). They were being passed a raw
|
|
3536
|
+
// DER key and shuffled arguments, so client-certificate auth could
|
|
3537
|
+
// never produce a valid signature.
|
|
3538
|
+
let clientKeyObj = crypto.createPrivateKey({
|
|
3539
|
+
key: Buffer.from(certCtx.privateKey),
|
|
3540
|
+
format: 'der',
|
|
3541
|
+
type: 'pkcs8',
|
|
3542
|
+
});
|
|
3543
|
+
let peerSigAlgs = peer_requested_sig_algs();
|
|
3544
|
+
|
|
3545
|
+
let scheme = pick_scheme(wire.TLS_VERSION.TLS1_3, clientKeyObj, peerSigAlgs);
|
|
3546
|
+
if (scheme === null) {
|
|
3547
|
+
fatalAlert(wire.TLS_ALERT.HANDSHAKE_FAILURE, 'No signature scheme shared with server for client certificate');
|
|
3548
|
+
return;
|
|
3549
|
+
}
|
|
3550
|
+
|
|
3551
|
+
// TBS uses the CLIENT CertificateVerify label, over the transcript
|
|
3552
|
+
// hash taken before this message is pushed.
|
|
3553
|
+
let cvHash = negotiated_hash();
|
|
3554
|
+
let clientTbs = build_cert_verify_tbs_with_hash(cvHash, false, get_transcript_hash(cvHash));
|
|
3555
|
+
let signature = sign_with_scheme(wire.TLS_VERSION.TLS1_3, scheme, clientTbs, clientKeyObj);
|
|
2358
3556
|
let cv_data = build_tls_message({
|
|
2359
3557
|
type: 'certificate_verify',
|
|
2360
3558
|
scheme: scheme,
|
|
@@ -2381,12 +3579,35 @@ function TLSSession(options){
|
|
|
2381
3579
|
// base_secret may be null after app secrets are derived, so we also check handshake secret.
|
|
2382
3580
|
if (context.finished_sent==false && context.selected_cipher_suite!==null && (context.base_secret!==null || context.local_handshake_traffic_secret!==null)){
|
|
2383
3581
|
|
|
2384
|
-
if((
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
3582
|
+
if((is13()) && context.local_handshake_traffic_secret!==null){
|
|
3583
|
+
|
|
3584
|
+
// When is our Finished due (TLS 1.3, RFC 8446 §4.4.4)? Three distinct
|
|
3585
|
+
// situations, one per role/handshake kind — named rather than ORed
|
|
3586
|
+
// inline so each stays readable and independently checkable.
|
|
3587
|
+
//
|
|
3588
|
+
// client: only after the server's Finished verified AND both
|
|
3589
|
+
// application secrets exist (ours is the last message of
|
|
3590
|
+
// the handshake).
|
|
3591
|
+
// server, certificate handshake: after our CertificateVerify.
|
|
3592
|
+
// server, PSK handshake: no Certificate/CertificateVerify at all,
|
|
3593
|
+
// so EncryptedExtensions is the predecessor.
|
|
3594
|
+
let clientFinishedDue = context.isServer === false &&
|
|
3595
|
+
context.remote_finished_ok === true &&
|
|
3596
|
+
context.local_app_traffic_secret !== null &&
|
|
3597
|
+
context.remote_app_traffic_secret !== null;
|
|
3598
|
+
|
|
3599
|
+
let serverCertFinishedDue = context.isServer === true &&
|
|
3600
|
+
context.cert_verify_sent === true &&
|
|
3601
|
+
context.local_cert_chain !== null;
|
|
3602
|
+
|
|
3603
|
+
let serverPskFinishedDue = context.isServer === true &&
|
|
3604
|
+
context.psk_accepted === true &&
|
|
3605
|
+
context.encrypted_exts_sent === true;
|
|
3606
|
+
|
|
3607
|
+
if(clientFinishedDue || serverCertFinishedDue || serverPskFinishedDue){
|
|
3608
|
+
|
|
3609
|
+
let finHashName = negotiated_hash();
|
|
3610
|
+
let finished_data=get_handshake_finished_with_hash(finHashName,context.local_handshake_traffic_secret,get_transcript_hash(finHashName, label_prefix()));
|
|
2390
3611
|
context.local_finished_data = finished_data;
|
|
2391
3612
|
|
|
2392
3613
|
let message_data = build_tls_message({
|
|
@@ -2396,6 +3617,13 @@ function TLSSession(options){
|
|
|
2396
3617
|
|
|
2397
3618
|
pushTranscript(message_data);
|
|
2398
3619
|
|
|
3620
|
+
// Checkpoint for app-secret derivation (see tls13_app_transcript_hash).
|
|
3621
|
+
// On the SERVER our own Finished IS the server Finished, so the
|
|
3622
|
+
// transcript is now exactly ClientHello..server Finished.
|
|
3623
|
+
if (context.isServer && context.tls13_app_transcript_hash === null) {
|
|
3624
|
+
context.tls13_app_transcript_hash = get_transcript_hash(finHashName);
|
|
3625
|
+
}
|
|
3626
|
+
|
|
2399
3627
|
context.finished_sent=true;
|
|
2400
3628
|
|
|
2401
3629
|
ev.emit('message',1,context.message_sent_seq,'finished',message_data);
|
|
@@ -2404,7 +3632,7 @@ function TLSSession(options){
|
|
|
2404
3632
|
|
|
2405
3633
|
}
|
|
2406
3634
|
|
|
2407
|
-
}else if((
|
|
3635
|
+
}else if((is12())){
|
|
2408
3636
|
|
|
2409
3637
|
// Finished ordering differs between full and abbreviated handshake:
|
|
2410
3638
|
// Full: client sends first (after CKE), then server (after client's Finished).
|
|
@@ -2426,7 +3654,7 @@ function TLSSession(options){
|
|
|
2426
3654
|
|
|
2427
3655
|
if (can_send_finished) {
|
|
2428
3656
|
|
|
2429
|
-
let finishedHashName =
|
|
3657
|
+
let finishedHashName = negotiated_hash();
|
|
2430
3658
|
let transcript_hash = get_transcript_hash(finishedHashName);
|
|
2431
3659
|
|
|
2432
3660
|
let finished_data;
|
|
@@ -2457,14 +3685,26 @@ function TLSSession(options){
|
|
|
2457
3685
|
}
|
|
2458
3686
|
|
|
2459
3687
|
//get app traffic secret...
|
|
2460
|
-
if ((
|
|
3688
|
+
if ((is13())){
|
|
2461
3689
|
if(context.base_secret!==null && context.local_app_traffic_secret==null && context.remote_app_traffic_secret==null){
|
|
2462
3690
|
|
|
2463
3691
|
if((context.isServer==true && context.finished_sent==true && context.remote_finished_ok==false) || (context.isServer==false && context.finished_sent==false && context.remote_finished_ok==true)){
|
|
2464
3692
|
|
|
2465
|
-
let appHashName =
|
|
2466
|
-
|
|
2467
|
-
|
|
3693
|
+
let appHashName = negotiated_hash();
|
|
3694
|
+
// Use the checkpoint captured when the server's Finished entered the
|
|
3695
|
+
// transcript. Reading the live transcript here would silently pick up
|
|
3696
|
+
// anything the loop emitted in between (client Certificate /
|
|
3697
|
+
// CertificateVerify), producing app keys the peer never derives.
|
|
3698
|
+
let appTranscriptHash = context.tls13_app_transcript_hash;
|
|
3699
|
+
if (appTranscriptHash === null) {
|
|
3700
|
+
// No checkpoint means we reached derivation without either side's
|
|
3701
|
+
// Finished passing through the transcript — a state-machine bug,
|
|
3702
|
+
// not a peer error. Refuse rather than derive from a guess.
|
|
3703
|
+
fatalAlert(wire.TLS_ALERT.INTERNAL_ERROR,
|
|
3704
|
+
'Internal error: app traffic secrets requested before the server Finished transcript checkpoint');
|
|
3705
|
+
return;
|
|
3706
|
+
}
|
|
3707
|
+
let result2 = derive_app_traffic_secrets_with_hash(appHashName, context.base_secret, appTranscriptHash, label_prefix());
|
|
2468
3708
|
|
|
2469
3709
|
// Save master_secret for resumption before clearing
|
|
2470
3710
|
params_to_set['tls13_master_secret'] = result2.master_secret;
|
|
@@ -2474,7 +3714,7 @@ function TLSSession(options){
|
|
|
2474
3714
|
// Kept for the lifetime of the connection so exportKeyingMaterial
|
|
2475
3715
|
// (RFC 8446 §7.5) works after handshake completion.
|
|
2476
3716
|
params_to_set['exporter_master_secret'] =
|
|
2477
|
-
derive_exporter_master_secret_with_hash(appHashName, result2.master_secret, appTranscriptHash);
|
|
3717
|
+
derive_exporter_master_secret_with_hash(appHashName, result2.master_secret, appTranscriptHash, label_prefix());
|
|
2478
3718
|
params_to_set['base_secret']=null;
|
|
2479
3719
|
|
|
2480
3720
|
if (context.isServer === true) {
|
|
@@ -2492,21 +3732,34 @@ function TLSSession(options){
|
|
|
2492
3732
|
//expected_remote_finished...
|
|
2493
3733
|
if (context.expected_remote_finished==null && context.selected_cipher_suite!==null){
|
|
2494
3734
|
|
|
2495
|
-
if((
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
3735
|
+
if((is13()) && context.remote_handshake_traffic_secret!==null){
|
|
3736
|
+
|
|
3737
|
+
// Compute the expected peer Finished only once the peer's Finished
|
|
3738
|
+
// has actually arrived — for BOTH roles.
|
|
3739
|
+
//
|
|
3740
|
+
// verify_data is a MAC over the transcript as it stands immediately
|
|
3741
|
+
// before the peer's Finished, so the value is only well-defined at
|
|
3742
|
+
// that moment. The server used to compute it as soon as it had sent
|
|
3743
|
+
// its OWN Finished, which is a different point in the transcript:
|
|
3744
|
+
// with client authentication the client's Certificate and
|
|
3745
|
+
// CertificateVerify arrive in between, so the server's expectation
|
|
3746
|
+
// was fixed too early and every client-auth handshake failed with a
|
|
3747
|
+
// verify_data mismatch. (Without client auth the two points coincide,
|
|
3748
|
+
// which is why it went unnoticed.) The client already did this
|
|
3749
|
+
// lazily; both roles now share the same rule.
|
|
3750
|
+
if(context.remote_finished !== null){
|
|
3751
|
+
|
|
3752
|
+
let remoteFinHashName = negotiated_hash();
|
|
3753
|
+
params_to_set['expected_remote_finished']=get_handshake_finished_with_hash(remoteFinHashName,context.remote_handshake_traffic_secret,get_transcript_hash(remoteFinHashName, label_prefix()));
|
|
2501
3754
|
|
|
2502
3755
|
}
|
|
2503
3756
|
|
|
2504
|
-
}else if((
|
|
3757
|
+
}else if((is12()) && context.base_secret!==null){
|
|
2505
3758
|
|
|
2506
3759
|
if(context.remote_finished!==null){
|
|
2507
3760
|
|
|
2508
3761
|
|
|
2509
|
-
let tls12FinHashName =
|
|
3762
|
+
let tls12FinHashName = negotiated_hash();
|
|
2510
3763
|
let transcript_hash = get_transcript_hash(tls12FinHashName);
|
|
2511
3764
|
|
|
2512
3765
|
if(context.isServer==true){
|
|
@@ -2530,6 +3783,16 @@ function TLSSession(options){
|
|
|
2530
3783
|
//compare finished to expected...
|
|
2531
3784
|
if(context.remote_finished_ok==false && context.remote_finished!==null && context.expected_remote_finished!==null){
|
|
2532
3785
|
|
|
3786
|
+
// RFC 8446 §4.4.4: verify_data has exactly Hash.length bytes. A body
|
|
3787
|
+
// of any other length (BoGo: TrailingDataWithFinished) is a framing
|
|
3788
|
+
// violation — decode_error, checked BEFORE the value comparison so the
|
|
3789
|
+
// constant-time compare below always runs on equal-length inputs.
|
|
3790
|
+
if (context.remote_finished.length !== context.expected_remote_finished.length) {
|
|
3791
|
+
fatalAlert(wire.TLS_ALERT.DECODE_ERROR, 'Finished verify_data has wrong length (' +
|
|
3792
|
+
context.remote_finished.length + ', expected ' + context.expected_remote_finished.length + ')');
|
|
3793
|
+
return;
|
|
3794
|
+
}
|
|
3795
|
+
|
|
2533
3796
|
// Constant-time Finished comparison — verify_data is a MAC; a timing
|
|
2534
3797
|
// oracle here would let a peer forge a valid Finished byte by byte.
|
|
2535
3798
|
if(timingSafeEqualU8(context.remote_finished, context.expected_remote_finished)==true){
|
|
@@ -2541,6 +3804,18 @@ function TLSSession(options){
|
|
|
2541
3804
|
|
|
2542
3805
|
pushTranscript(message_data);
|
|
2543
3806
|
|
|
3807
|
+
// Checkpoint for app-secret derivation (see tls13_app_transcript_hash).
|
|
3808
|
+
// On the CLIENT the peer's Finished IS the server's Finished, so the
|
|
3809
|
+
// transcript is now exactly ClientHello..server Finished. Capture it
|
|
3810
|
+
// here, before the loop can append our own Certificate/CertificateVerify.
|
|
3811
|
+
if (!context.isServer && context.selected_cipher_suite !== null &&
|
|
3812
|
+
(context.selected_version === wire.TLS_VERSION.TLS1_3 ||
|
|
3813
|
+
context.selected_version === wire.DTLS_VERSION.DTLS1_3) &&
|
|
3814
|
+
context.tls13_app_transcript_hash === null) {
|
|
3815
|
+
let ckHash = negotiated_hash();
|
|
3816
|
+
context.tls13_app_transcript_hash = get_transcript_hash(ckHash);
|
|
3817
|
+
}
|
|
3818
|
+
|
|
2544
3819
|
params_to_set['remote_finished_ok']=true;
|
|
2545
3820
|
|
|
2546
3821
|
context.remote_finished_data = context.remote_finished;
|
|
@@ -2550,7 +3825,14 @@ function TLSSession(options){
|
|
|
2550
3825
|
|
|
2551
3826
|
|
|
2552
3827
|
}else{
|
|
3828
|
+
// RFC 8446 §4.4.4: "Recipients of Finished messages MUST verify
|
|
3829
|
+
// that the contents are correct and if incorrect MUST terminate the
|
|
3830
|
+
// connection with a decrypt_error alert." The old behavior (null it
|
|
3831
|
+
// out and keep waiting) left the peer hanging with a half-open
|
|
3832
|
+
// handshake — and hid real key-schedule bugs.
|
|
2553
3833
|
context.remote_finished=null;
|
|
3834
|
+
fatalAlert(wire.TLS_ALERT.DECRYPT_ERROR, 'Finished verify_data mismatch');
|
|
3835
|
+
return;
|
|
2554
3836
|
}
|
|
2555
3837
|
|
|
2556
3838
|
}
|
|
@@ -2558,26 +3840,86 @@ function TLSSession(options){
|
|
|
2558
3840
|
|
|
2559
3841
|
|
|
2560
3842
|
|
|
2561
|
-
if(context.state!=='connected' && context.remote_finished_ok==true && (((
|
|
3843
|
+
if(context.state!=='connected' && context.remote_finished_ok==true && (((is13()) && context.local_app_traffic_secret!==null && context.remote_app_traffic_secret!==null) || (is12()))){
|
|
3844
|
+
|
|
3845
|
+
// ── Authentication gate ──
|
|
3846
|
+
// Peer authentication is enforced HERE, at the single point where the
|
|
3847
|
+
// handshake is declared complete, and deliberately not (only) in the
|
|
3848
|
+
// per-message branches. A message-level check can be bypassed simply by
|
|
3849
|
+
// OMITTING the message: BoGo's SkipClientCertificate does exactly that,
|
|
3850
|
+
// and an "empty certificate_list" check in the Certificate branch never
|
|
3851
|
+
// runs because no Certificate ever arrives. In a reactive state machine
|
|
3852
|
+
// the durable place for a MUST-have-happened invariant is the state
|
|
3853
|
+
// transition it guards, not the event that usually satisfies it.
|
|
3854
|
+
|
|
3855
|
+
// Client: the server must have proved possession of its certificate
|
|
3856
|
+
// key — TLS 1.3 CertificateVerify, or the TLS 1.2 ServerKeyExchange
|
|
3857
|
+
// signature. PSK/abbreviated handshakes authenticate via the binder /
|
|
3858
|
+
// resumed master secret instead.
|
|
3859
|
+
if (!context.isServer && !context.psk_accepted && !context.tls12_abbreviated &&
|
|
3860
|
+
context.peerSignatureVerified !== true) {
|
|
3861
|
+
fatalAlert(wire.TLS_ALERT.UNEXPECTED_MESSAGE,
|
|
3862
|
+
'Handshake completed without a verified server signature');
|
|
3863
|
+
return;
|
|
3864
|
+
}
|
|
3865
|
+
|
|
3866
|
+
// Server-side client authentication. Two DIFFERENT questions, which
|
|
3867
|
+
// were previously collapsed into one condition:
|
|
3868
|
+
//
|
|
3869
|
+
// (a) Must a client certificate arrive at all? -> policy, and only
|
|
3870
|
+
// when we both asked for one and refuse to proceed without it.
|
|
3871
|
+
// (b) If a certificate DID arrive, must it be proved? -> ALWAYS.
|
|
3872
|
+
//
|
|
3873
|
+
// Collapsing them meant a server with requestCert but without
|
|
3874
|
+
// rejectUnauthorized skipped the whole block, so a client could send a
|
|
3875
|
+
// certificate — any certificate, including one it does not own — and
|
|
3876
|
+
// never prove possession, because the CertificateVerify check lived
|
|
3877
|
+
// behind the (a) policy flag. Presenting someone else's certificate is
|
|
3878
|
+
// an impersonation, not a policy preference, so (b) cannot be optional.
|
|
3879
|
+
if (context.isServer && !context.psk_accepted && !context.tls12_abbreviated) {
|
|
3880
|
+
|
|
3881
|
+
let clientCertPresent = !!(context.remote_cert_chain && context.remote_cert_chain.length > 0);
|
|
3882
|
+
|
|
3883
|
+
// (a) required-but-absent
|
|
3884
|
+
if (context.requestCert && context.rejectUnauthorized && !clientCertPresent) {
|
|
3885
|
+
fatalAlert(wire.TLS_ALERT.CERTIFICATE_REQUIRED,
|
|
3886
|
+
'Client certificate was required but none was provided');
|
|
3887
|
+
return;
|
|
3888
|
+
}
|
|
3889
|
+
|
|
3890
|
+
// (b) present-but-unproved. Unconditional: it applies even when the
|
|
3891
|
+
// certificate was optional, because the question is not "did we want
|
|
3892
|
+
// one" but "does this peer own the one it sent". In TLS 1.2 the
|
|
3893
|
+
// client's CertificateVerify sets peerSignatureVerified the same way.
|
|
3894
|
+
if (clientCertPresent &&
|
|
3895
|
+
(context.selected_version === wire.TLS_VERSION.TLS1_3 ||
|
|
3896
|
+
context.selected_version === wire.DTLS_VERSION.DTLS1_3) &&
|
|
3897
|
+
context.peerSignatureVerified !== true) {
|
|
3898
|
+
fatalAlert(wire.TLS_ALERT.UNEXPECTED_MESSAGE,
|
|
3899
|
+
'Client sent a certificate without a valid CertificateVerify');
|
|
3900
|
+
return;
|
|
3901
|
+
}
|
|
3902
|
+
}
|
|
3903
|
+
|
|
2562
3904
|
context.state='connected';
|
|
2563
3905
|
context.handshakeEndTime = Date.now();
|
|
2564
3906
|
ev.emit('secureConnect');
|
|
2565
3907
|
|
|
2566
3908
|
// TLS 1.3: compute resumption_master_secret (both client and server need it)
|
|
2567
|
-
if ((
|
|
2568
|
-
let hashName =
|
|
3909
|
+
if ((is13()) && context.tls13_master_secret && !context.resumption_master_secret) {
|
|
3910
|
+
let hashName = negotiated_hash();
|
|
2569
3911
|
context.resumption_master_secret = derive_resumption_master_secret_with_hash(
|
|
2570
|
-
hashName, context.tls13_master_secret, get_transcript_hash(hashName)
|
|
3912
|
+
hashName, context.tls13_master_secret, get_transcript_hash(hashName, label_prefix())
|
|
2571
3913
|
);
|
|
2572
3914
|
}
|
|
2573
3915
|
|
|
2574
3916
|
// TLS 1.3 server: send NewSessionTicket
|
|
2575
|
-
if ((
|
|
3917
|
+
if ((is13()) && context.isServer && !context.session_ticket_sent && context.sessionTickets && context.resumption_master_secret) {
|
|
2576
3918
|
context.session_ticket_sent = true;
|
|
2577
3919
|
|
|
2578
|
-
let hashName =
|
|
3920
|
+
let hashName = negotiated_hash();
|
|
2579
3921
|
let ticket_nonce = new Uint8Array([context.ticket_nonce_counter++]);
|
|
2580
|
-
let psk = derive_psk(hashName, context.resumption_master_secret, ticket_nonce);
|
|
3922
|
+
let psk = derive_psk(hashName, context.resumption_master_secret, ticket_nonce, label_prefix());
|
|
2581
3923
|
let ticket_age_add = crypto.randomBytes(4).readUInt32BE(0);
|
|
2582
3924
|
let ticket_lifetime = context.ticketLifetime;
|
|
2583
3925
|
|
|
@@ -2775,9 +4117,46 @@ function TLSSession(options){
|
|
|
2775
4117
|
|
|
2776
4118
|
|
|
2777
4119
|
function sendAlert(level, description) {
|
|
4120
|
+
// Nothing may follow a fatal alert (RFC 8446 §6.2). The guard lives here,
|
|
4121
|
+
// at the single point every alert leaves through, rather than at each
|
|
4122
|
+
// caller — a caller that forgets is exactly how the stray close_notify
|
|
4123
|
+
// arose. `fatalAlert` has its own early return for the same reason; this
|
|
4124
|
+
// covers the warning-level paths too.
|
|
4125
|
+
if (context.state === 'error' || context.state === 'closed') return;
|
|
4126
|
+
|
|
2778
4127
|
let alertData = new Uint8Array([level, description]);
|
|
2779
|
-
|
|
2780
|
-
|
|
4128
|
+
|
|
4129
|
+
// Alerts must go out at the protection level the PEER is currently reading
|
|
4130
|
+
// at, otherwise they are unreadable and the real reason for the abort is
|
|
4131
|
+
// lost behind a decrypt failure.
|
|
4132
|
+
//
|
|
4133
|
+
// epoch 0 — cleartext: before any keys exist, and for all of TLS 1.2's
|
|
4134
|
+
// handshake (alerts stay cleartext until CCS).
|
|
4135
|
+
// epoch 1 — handshake keys: TLS 1.3 after ServerHello but before our
|
|
4136
|
+
// Finished has been sent. This case was missing entirely.
|
|
4137
|
+
// epoch 2 — application keys.
|
|
4138
|
+
//
|
|
4139
|
+
// The subtlety is that the right answer depends on what the PEER reads,
|
|
4140
|
+
// not on whether WE consider the handshake finished. In TLS 1.3 a server
|
|
4141
|
+
// that has already sent its Finished has handed the client everything it
|
|
4142
|
+
// needs to switch to application keys, and the client does so immediately —
|
|
4143
|
+
// so an abort discovered afterwards (a missing client Certificate is
|
|
4144
|
+
// discovered exactly there) must be encrypted under application keys even
|
|
4145
|
+
// though our own state is not yet 'connected'. Encrypting it under
|
|
4146
|
+
// handshake keys produced an undecryptable record and the peer reported a
|
|
4147
|
+
// decrypt error instead of certificate_required.
|
|
4148
|
+
let epoch;
|
|
4149
|
+
if (context.state === 'connected') {
|
|
4150
|
+
epoch = 2;
|
|
4151
|
+
} else if (is13() && context.local_app_traffic_secret !== null && context.finished_sent) {
|
|
4152
|
+
epoch = 2;
|
|
4153
|
+
} else if (is13() && context.local_handshake_traffic_secret !== null) {
|
|
4154
|
+
epoch = 1;
|
|
4155
|
+
} else {
|
|
4156
|
+
epoch = 0;
|
|
4157
|
+
}
|
|
4158
|
+
|
|
4159
|
+
|
|
2781
4160
|
ev.emit('message', epoch, 0, 'alert', alertData);
|
|
2782
4161
|
ev.emit('alert', { level: level, description: description });
|
|
2783
4162
|
if (level === 2) {
|
|
@@ -2787,8 +4166,17 @@ function TLSSession(options){
|
|
|
2787
4166
|
}
|
|
2788
4167
|
|
|
2789
4168
|
function close(){
|
|
2790
|
-
|
|
2791
|
-
//
|
|
4169
|
+
// 'error' counts as closed. RFC 8446 §6.2: after a fatal alert the
|
|
4170
|
+
// connection is torn down immediately and NO further record may be sent.
|
|
4171
|
+
// close_notify signals an ORDERLY shutdown, so emitting one after a failure
|
|
4172
|
+
// both contradicts the alert we just sent and appends a record the peer is
|
|
4173
|
+
// not expecting — it reads the fatal alert, then hits an extra record and
|
|
4174
|
+
// reports a decrypt failure instead of the reason we actually gave it.
|
|
4175
|
+
// Testing only for 'closed' missed exactly this case.
|
|
4176
|
+
if (context.state === 'closed' || context.state === 'error') {
|
|
4177
|
+
context.state = 'closed';
|
|
4178
|
+
return;
|
|
4179
|
+
}
|
|
2792
4180
|
sendAlert(1, 0);
|
|
2793
4181
|
context.state = 'closed';
|
|
2794
4182
|
}
|
|
@@ -2810,44 +4198,69 @@ function TLSSession(options){
|
|
|
2810
4198
|
// (RFC 9001), and strict 1.3-only stacks reject hellos with legacy
|
|
2811
4199
|
// artifacts. Detected from the configured versions, so plain TLS over
|
|
2812
4200
|
// TCP (which offers 1.3+1.2) keeps the full compatibility behavior.
|
|
2813
|
-
|
|
2814
|
-
|
|
4201
|
+
// Which versions does this profile actually offer? The suite list must
|
|
4202
|
+
// follow BOTH answers, not just one of them.
|
|
4203
|
+
//
|
|
4204
|
+
// The old form asked only "is this 1.3-only?" and hardcoded the 1.3 half
|
|
4205
|
+
// to true, so a 1.2-ONLY profile still advertised 0x1301..0x1303. Those
|
|
4206
|
+
// suites cannot be negotiated at 1.2 — a TLS 1.3 suite is meaningless in
|
|
4207
|
+
// a 1.2 handshake — so the hello claimed capabilities the profile had
|
|
4208
|
+
// ruled out. The asymmetry was invisible because only the 1.3-only case
|
|
4209
|
+
// had ever been considered.
|
|
4210
|
+
let offers13 = context.local_supported_versions.length === 0 ||
|
|
4211
|
+
context.local_supported_versions.indexOf(wire.TLS_VERSION.TLS1_3) >= 0 ||
|
|
4212
|
+
context.local_supported_versions.indexOf(wire.DTLS_VERSION.DTLS1_3) >= 0;
|
|
4213
|
+
let offers12 = context.local_supported_versions.length === 0 ||
|
|
4214
|
+
context.local_supported_versions.some(v => v !== wire.TLS_VERSION.TLS1_3 &&
|
|
4215
|
+
v !== wire.DTLS_VERSION.DTLS1_3);
|
|
2815
4216
|
|
|
2816
|
-
// Support both TLS 1.3 and 1.2 (server picks the best)
|
|
2817
4217
|
if(context.local_supported_cipher_suites.length<=0){
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
0x1301, // TLS_AES_128_GCM_SHA256
|
|
2821
|
-
0x1302, // TLS_AES_256_GCM_SHA384
|
|
2822
|
-
0x1303, // TLS_CHACHA20_POLY1305_SHA256
|
|
2823
|
-
] : [
|
|
2824
|
-
// TLS 1.3
|
|
2825
|
-
0x1301, // TLS_AES_128_GCM_SHA256
|
|
2826
|
-
0x1302, // TLS_AES_256_GCM_SHA384
|
|
2827
|
-
0x1303, // TLS_CHACHA20_POLY1305_SHA256
|
|
2828
|
-
// TLS 1.2 ECDHE
|
|
2829
|
-
0xC02F, // ECDHE_RSA_WITH_AES_128_GCM_SHA256
|
|
2830
|
-
0xC030, // ECDHE_RSA_WITH_AES_256_GCM_SHA384
|
|
2831
|
-
0xC02B, // ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
|
|
2832
|
-
0xCCA8, // ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
|
|
2833
|
-
];
|
|
4218
|
+
// crypto.js owns the default suite list; we only say which halves apply.
|
|
4219
|
+
context.local_supported_cipher_suites = default_cipher_suites(offers13, offers12);
|
|
2834
4220
|
}
|
|
2835
4221
|
|
|
2836
4222
|
if(context.local_supported_groups.length<=0){
|
|
2837
|
-
context.local_supported_groups=
|
|
4223
|
+
context.local_supported_groups = ECDH_SUPPORTED_GROUPS.slice(); // whatever ecdh.js implements
|
|
2838
4224
|
}
|
|
2839
4225
|
|
|
2840
4226
|
if(context.local_supported_versions.length<=0){
|
|
2841
4227
|
context.local_supported_versions=[0x0304, 0x0303]; // TLS 1.3, TLS 1.2
|
|
2842
4228
|
}
|
|
2843
4229
|
|
|
2844
|
-
//
|
|
2845
|
-
|
|
2846
|
-
|
|
4230
|
+
// Initial key_share: generate for our most-preferred group that we can
|
|
4231
|
+
// actually PRODUCE a share for. If the server wants a different one it
|
|
4232
|
+
// sends HelloRetryRequest, handled above.
|
|
4233
|
+
//
|
|
4234
|
+
// The list may name groups ecdh.js does not implement — a configured
|
|
4235
|
+
// curve list is not a capability list, and a post-quantum group can sit
|
|
4236
|
+
// in it because a caller passed it straight through. generate_keypair
|
|
4237
|
+
// returns null for those (it does not throw), and this code used to read
|
|
4238
|
+
// `.public_key` off that null and crash the whole session with a
|
|
4239
|
+
// TypeError before a single byte was sent. Skip what we cannot make; if
|
|
4240
|
+
// that leaves nothing, say so plainly rather than dying.
|
|
4241
|
+
let initialGroup = null;
|
|
4242
|
+
let initialKp = null;
|
|
4243
|
+
for (let gi = 0; gi < context.local_supported_groups.length; gi++) {
|
|
4244
|
+
let g = context.local_supported_groups[gi];
|
|
4245
|
+
if (!is_supported_group(g)) continue;
|
|
4246
|
+
let kp = ecdh_generate_keypair(g);
|
|
4247
|
+
if (!kp) continue;
|
|
4248
|
+
initialGroup = g;
|
|
4249
|
+
initialKp = kp;
|
|
4250
|
+
break;
|
|
4251
|
+
}
|
|
4252
|
+
|
|
4253
|
+
if (initialKp === null) {
|
|
4254
|
+
fatalAlert(wire.TLS_ALERT.INTERNAL_ERROR,
|
|
4255
|
+
'No configured key-exchange group is implemented — cannot build a key_share');
|
|
4256
|
+
return;
|
|
4257
|
+
}
|
|
2847
4258
|
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
4259
|
+
let public_key = initialKp.public_key;
|
|
4260
|
+
|
|
4261
|
+
context.local_key_groups[initialGroup]={
|
|
4262
|
+
public_key: initialKp.public_key,
|
|
4263
|
+
private_key: initialKp.private_key
|
|
2851
4264
|
};
|
|
2852
4265
|
|
|
2853
4266
|
// Honor the configured signature algorithms; the hardcoded list is only
|
|
@@ -2857,63 +4270,28 @@ function TLSSession(options){
|
|
|
2857
4270
|
let ch_sigalgs = (context.local_supported_signature_algorithms &&
|
|
2858
4271
|
context.local_supported_signature_algorithms.length > 0)
|
|
2859
4272
|
? context.local_supported_signature_algorithms
|
|
2860
|
-
:
|
|
2861
|
-
// TLS 1.3 (PSS + ECDSA + EdDSA)
|
|
2862
|
-
0x0804, 0x0805, 0x0806,
|
|
2863
|
-
0x0403, 0x0503, 0x0603,
|
|
2864
|
-
0x0807, 0x0808,
|
|
2865
|
-
// TLS 1.2 (PKCS1)
|
|
2866
|
-
0x0401, 0x0501, 0x0601
|
|
2867
|
-
];
|
|
4273
|
+
: default_signature_schemes(); // signing.js owns the list
|
|
2868
4274
|
context.local_supported_signature_algorithms = ch_sigalgs; // for CH2 reuse
|
|
2869
4275
|
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
}
|
|
2890
|
-
];
|
|
2891
|
-
if (!tls13Only) {
|
|
2892
|
-
// TLS 1.2 compatibility — meaningless (and suspicious to strict
|
|
2893
|
-
// stacks) in a 1.3-only/QUIC hello
|
|
2894
|
-
extensions.push({ type: 'RENEGOTIATION_INFO', value: new Uint8Array(0) });
|
|
2895
|
-
extensions.push({ type: 'EXTENDED_MASTER_SECRET', value: null });
|
|
2896
|
-
} else {
|
|
2897
|
-
// psk_key_exchange_modes: every mainstream 1.3 client sends it, and a
|
|
2898
|
-
// server MUST NOT send NewSessionTicket to a client that omitted it
|
|
2899
|
-
// (RFC 8446 §4.2.9) — required for future 0-RTT/resumption anyway.
|
|
2900
|
-
extensions.push({ type: 'PSK_KEY_EXCHANGE_MODES', value: [1] });
|
|
2901
|
-
}
|
|
2902
|
-
|
|
2903
|
-
// Add SNI if servername was provided
|
|
2904
|
-
if (context.local_sni) {
|
|
2905
|
-
extensions.unshift({ type: 'SERVER_NAME', value: context.local_sni });
|
|
2906
|
-
}
|
|
2907
|
-
|
|
2908
|
-
// Add ALPN if provided (e.g. 'h3' for QUIC)
|
|
2909
|
-
if (context.local_supported_alpns && context.local_supported_alpns.length > 0) {
|
|
2910
|
-
extensions.push({ type: 'ALPN', value: context.local_supported_alpns });
|
|
2911
|
-
}
|
|
2912
|
-
|
|
2913
|
-
// Add custom extensions (e.g. QUIC transport params 0x39)
|
|
2914
|
-
for (let i = 0; i < context.local_extensions.length; i++) {
|
|
2915
|
-
extensions.push(context.local_extensions[i]);
|
|
2916
|
-
}
|
|
4276
|
+
// supported_versions (43) and key_share (51) are the TLS 1.3
|
|
4277
|
+
// negotiation machinery (RFC 8446 §4.1.2, §4.2.8). Offer them only
|
|
4278
|
+
// when 1.3 is actually in the offered version set — a pure 1.2
|
|
4279
|
+
// hello must look like a 1.2 hello, exactly as OpenSSL/BoringSSL
|
|
4280
|
+
// behave with max_version=1.2. Previously both were emitted
|
|
4281
|
+
// unconditionally, so even a maxVersion:'DTLSv1.2' profile (the
|
|
4282
|
+
// WebRTC default) advertised a single-entry supported_versions and
|
|
4283
|
+
// a dangling key_share: spec-legal (receivers MUST ignore unknown
|
|
4284
|
+
// extensions, RFC 5246 §7.4.1.4) but non-canonical — and fatal to
|
|
4285
|
+
// at least one deployed stack (webrtc-dtls ≤0.7 hard-errors on any
|
|
4286
|
+
// extension outside its known set, then poisons its replay window
|
|
4287
|
+
// so no retransmission can ever recover the handshake).
|
|
4288
|
+
// Same assembler as the post-HelloRetryRequest CH2 (RFC 8446 §4.1.2
|
|
4289
|
+
// requires the two hellos to be identical apart from key_share/cookie).
|
|
4290
|
+
let extensions = build_client_hello_extensions({
|
|
4291
|
+
keyShareGroup: initialGroup,
|
|
4292
|
+
keySharePublic: public_key,
|
|
4293
|
+
cookie: null
|
|
4294
|
+
});
|
|
2917
4295
|
|
|
2918
4296
|
// Resumption: check if session was provided (opaque Buffer) — decode to structured data
|
|
2919
4297
|
let sessionData = null;
|
|
@@ -2960,6 +4338,7 @@ function TLSSession(options){
|
|
|
2960
4338
|
}
|
|
2961
4339
|
};
|
|
2962
4340
|
extensions.push(pskExt); // MUST be last
|
|
4341
|
+
note_offered_extensions([pskExt]);
|
|
2963
4342
|
|
|
2964
4343
|
// Build the full message with placeholder
|
|
2965
4344
|
let build_message_params = {
|
|
@@ -2983,8 +4362,8 @@ function TLSSession(options){
|
|
|
2983
4362
|
let truncatedMessage = tempMessage.slice(0, tempMessage.length - bindersSize);
|
|
2984
4363
|
|
|
2985
4364
|
// Compute real binder
|
|
2986
|
-
let binder_key = derive_binder_key(hashName, context.psk_offered.psk, false);
|
|
2987
|
-
let binder = compute_psk_binder(hashName, binder_key, truncatedMessage);
|
|
4365
|
+
let binder_key = derive_binder_key(hashName, context.psk_offered.psk, false, label_prefix());
|
|
4366
|
+
let binder = compute_psk_binder(hashName, binder_key, truncatedMessage, label_prefix());
|
|
2988
4367
|
|
|
2989
4368
|
dbg('CLI-PSK', 'ticket:', hexPreview(sessionData.ticket, 24),
|
|
2990
4369
|
'cipher:', '0x' + sessionData.cipher.toString(16),
|
|
@@ -3011,8 +4390,18 @@ function TLSSession(options){
|
|
|
3011
4390
|
// If we only have a session_id (no ticket), don't include empty SESSION_TICKET ext:
|
|
3012
4391
|
// servers with SSL_OP_NO_TICKET can behave inconsistently when the extension appears
|
|
3013
4392
|
// alongside a session_id resumption attempt — they may skip the session_id lookup.
|
|
4393
|
+
// Fill in the ticket on the SESSION_TICKET entry the assembler already
|
|
4394
|
+
// produced, rather than pushing a second one — two extensions of the
|
|
4395
|
+
// same type is itself a protocol violation (RFC 8446 §4.2), and it
|
|
4396
|
+
// would also change CH2's extension list relative to CH1. If the
|
|
4397
|
+
// assembler did not emit one (1.3-only or DTLS profile), there is
|
|
4398
|
+
// nothing to resume with here anyway.
|
|
3014
4399
|
if (sessionData.ticket && sessionData.ticket.length > 0) {
|
|
3015
|
-
|
|
4400
|
+
let stEntry = null;
|
|
4401
|
+
for (let ei = 0; ei < extensions.length; ei++) {
|
|
4402
|
+
if (extensions[ei] && extensions[ei].type === 'SESSION_TICKET') { stEntry = extensions[ei]; break; }
|
|
4403
|
+
}
|
|
4404
|
+
if (stEntry) stEntry.value = sessionData.ticket;
|
|
3016
4405
|
}
|
|
3017
4406
|
|
|
3018
4407
|
// If we have a session_id → put it in ClientHello.session_id (overrides the random one)
|
|
@@ -3042,13 +4431,6 @@ function TLSSession(options){
|
|
|
3042
4431
|
// No resumption — advertise empty SessionTicket extension to offer support.
|
|
3043
4432
|
// Skip for DTLS (DTLS clients/servers often don't implement RFC 5077 fully,
|
|
3044
4433
|
// and adding it caused interop issues with openssl s_server -dtls1_2).
|
|
3045
|
-
let isDtls = context.local_supported_versions && context.local_supported_versions.some(v => (v & 0xFF00) === 0xFE00);
|
|
3046
|
-
if (!isDtls && !tls13Only && context.sessionTickets) {
|
|
3047
|
-
// RFC 5077 SessionTicket is a TLS 1.2 mechanism; 1.3 resumption uses
|
|
3048
|
-
// NewSessionTicket/PSK. Never advertise it in a 1.3-only/QUIC hello.
|
|
3049
|
-
extensions.push({ type: 'SESSION_TICKET', value: new Uint8Array(0) });
|
|
3050
|
-
}
|
|
3051
|
-
|
|
3052
4434
|
// Standard ClientHello
|
|
3053
4435
|
let build_message_params = {
|
|
3054
4436
|
type: 'client_hello',
|
|
@@ -3126,6 +4508,18 @@ function TLSSession(options){
|
|
|
3126
4508
|
return context.selected_version;
|
|
3127
4509
|
},
|
|
3128
4510
|
|
|
4511
|
+
/**
|
|
4512
|
+
* Current session state: 'new' | 'handshaking' | 'connected' | 'error' | 'closed'.
|
|
4513
|
+
*
|
|
4514
|
+
* Exposed so a transport can tell whether the session is still alive
|
|
4515
|
+
* WITHOUT reaching into context. A transport that keeps feeding records to
|
|
4516
|
+
* an aborted session decrypts them under keys the peer no longer uses and
|
|
4517
|
+
* reports a misleading bad_record_mac, hiding the real failure.
|
|
4518
|
+
*/
|
|
4519
|
+
getState: function(){
|
|
4520
|
+
return context.state;
|
|
4521
|
+
},
|
|
4522
|
+
|
|
3129
4523
|
/** Returns the negotiated cipher suite code (e.g. 0x1301, 0xC02F), or null. */
|
|
3130
4524
|
getCipher: function(){
|
|
3131
4525
|
return context.selected_cipher_suite;
|
|
@@ -3160,6 +4554,12 @@ function TLSSession(options){
|
|
|
3160
4554
|
// TLS 1.3
|
|
3161
4555
|
localAppSecret: context.local_app_traffic_secret,
|
|
3162
4556
|
remoteAppSecret: context.remote_app_traffic_secret,
|
|
4557
|
+
// Handshake-epoch secrets. The record layer needs these to encrypt an
|
|
4558
|
+
// alert raised BEFORE anything has been written at epoch 1 — an abort
|
|
4559
|
+
// during the peer's flight is exactly that case, and without them the
|
|
4560
|
+
// alert could only go out in cleartext.
|
|
4561
|
+
localHandshakeSecret: context.local_handshake_traffic_secret,
|
|
4562
|
+
remoteHandshakeSecret: context.remote_handshake_traffic_secret,
|
|
3163
4563
|
// TLS 1.2
|
|
3164
4564
|
masterSecret: context.base_secret,
|
|
3165
4565
|
localRandom: context.local_random,
|
|
@@ -3200,6 +4600,14 @@ function TLSSession(options){
|
|
|
3200
4600
|
* @param {Uint8Array|Buffer|null} [context_value]
|
|
3201
4601
|
* @returns {Buffer|null}
|
|
3202
4602
|
*/
|
|
4603
|
+
/**
|
|
4604
|
+
* The negotiated DTLS-SRTP protection profile (RFC 5764), or null if none
|
|
4605
|
+
* was agreed. Pair with exportKeyingMaterial(len, 'EXTRACTOR-dtls_srtp').
|
|
4606
|
+
*/
|
|
4607
|
+
getSelectedSrtpProfile: function(){
|
|
4608
|
+
return context.selected_srtp_profile;
|
|
4609
|
+
},
|
|
4610
|
+
|
|
3203
4611
|
exportKeyingMaterial: function(length, label, context_value){
|
|
3204
4612
|
if (!context.selected_cipher_suite || !length || !label) return null;
|
|
3205
4613
|
var suite = TLS_CIPHER_SUITES[context.selected_cipher_suite];
|
|
@@ -3213,7 +4621,7 @@ function TLSSession(options){
|
|
|
3213
4621
|
return Buffer.from(tls13_exporter(
|
|
3214
4622
|
suite.hash, context.exporter_master_secret, label,
|
|
3215
4623
|
(context_value != null) ? context_value : null, length
|
|
3216
|
-
));
|
|
4624
|
+
, label_prefix()));
|
|
3217
4625
|
}
|
|
3218
4626
|
|
|
3219
4627
|
// TLS 1.2 — RFC 5705 over the master secret + hello randoms.
|
|
@@ -3317,11 +4725,11 @@ function TLSSession(options){
|
|
|
3317
4725
|
/** Request a TLS 1.3 Key Update. requestPeer=true means ask the other side to update too. */
|
|
3318
4726
|
requestKeyUpdate: function(requestPeer){
|
|
3319
4727
|
if (context.state !== 'connected' || (context.selected_version !== wire.TLS_VERSION.TLS1_3 && context.selected_version !== wire.DTLS_VERSION.DTLS1_3)) return;
|
|
3320
|
-
let hashName =
|
|
4728
|
+
let hashName = negotiated_hash();
|
|
3321
4729
|
let hashLen = getHashLen(hashName);
|
|
3322
4730
|
|
|
3323
4731
|
// Derive new local traffic secret
|
|
3324
|
-
let newLocalSecret = hkdf_expand_label(hashName, context.local_app_traffic_secret, 'traffic upd', new Uint8Array(0), hashLen);
|
|
4732
|
+
let newLocalSecret = hkdf_expand_label(hashName, context.local_app_traffic_secret, 'traffic upd', new Uint8Array(0, label_prefix()), hashLen);
|
|
3325
4733
|
context.local_app_traffic_secret = newLocalSecret;
|
|
3326
4734
|
|
|
3327
4735
|
// Send KeyUpdate message
|