lemon-tls 0.5.5 → 0.7.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.
@@ -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,
@@ -21,9 +22,14 @@ import {
21
22
  derive_handshake_traffic_secrets_psk_with_hash,
22
23
  hkdf_expand_label,
23
24
  getHashFn,
25
+ getHashLen,
24
26
  derive_exporter_master_secret_with_hash,
25
27
  tls13_exporter,
26
28
  tls12_exporter,
29
+ LABEL_PREFIX_TLS13,
30
+ LABEL_PREFIX_DTLS13,
31
+ suite_matches_version,
32
+ is_usable_cipher_suite,
27
33
  } from './crypto.js';
28
34
 
29
35
  import {
@@ -36,9 +42,10 @@ import {
36
42
  import * as wire from './wire.js';
37
43
 
38
44
  // Extracted modules
39
- import { pick_scheme, sign_with_scheme } from './session/signing.js';
45
+ import { pick_scheme, sign_with_scheme, verify_with_scheme, default_signature_schemes } from './session/signing.js';
40
46
  import createSecureContext from './secure_context.js';
41
- import { x25519_get_public_key, x25519_get_shared_secret, p256_generate_keypair, p256_get_shared_secret, p384_generate_keypair, p384_get_shared_secret } from './session/ecdh.js';
47
+ 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
48
+ } from './session/ecdh.js';
42
49
  import { build_tls_message, parse_tls_message } from './session/message.js';
43
50
  import { encrypt_session_blob, decrypt_session_blob, encode_client_session, decode_client_session } from './session/ticket.js';
44
51
 
@@ -62,6 +69,96 @@ function hexPreview(buf, max) {
62
69
  }
63
70
 
64
71
 
72
+ const MAX_CHAIN_DEPTH = 10;
73
+
74
+ function _x509(x) {
75
+ try { return new crypto.X509Certificate(Buffer.isBuffer(x) ? x : Buffer.from(x)); }
76
+ catch (e) { return null; }
77
+ }
78
+ function _sameCert(a, b) { return a.raw.length === b.raw.length && a.raw.equals(b.raw); }
79
+ function _validNow(x509, now) {
80
+ return !(now < new Date(x509.validFrom) || now > new Date(x509.validTo));
81
+ }
82
+
83
+ /**
84
+ * Build and verify a certificate chain from `chainDer` (leaf first) up to one
85
+ * of `anchors`.
86
+ *
87
+ * This replaces a check that compared ONLY the leaf against each configured CA,
88
+ * which was wrong in both directions:
89
+ *
90
+ * - A real chain (leaf <- intermediate <- root) FAILED, because nothing ever
91
+ * walked the intermediates the peer sent.
92
+ * - The whole check sat behind `if (context.ca)`, so when no CA was
93
+ * configured a peer presenting ANY self-signed certificate was authorized
94
+ * outright — with rejectUnauthorized defaulting to true, which reads as if
95
+ * verification were on.
96
+ *
97
+ * Each link has its signature verified, each non-leaf must actually assert
98
+ * CA:TRUE, and every certificate in the path must be inside its validity
99
+ * window. Intermediates come from the peer and are never trusted on their own;
100
+ * only reaching an anchor ends the walk.
101
+ *
102
+ * Returns { ok: true, depth } or { ok: false, error }.
103
+ */
104
+ function verify_cert_chain(chainDer, anchors, now) {
105
+ if (!chainDer || chainDer.length === 0) return { ok: false, error: 'NO_PEER_CERTIFICATE' };
106
+ now = now || new Date();
107
+
108
+ const leaf = _x509(chainDer[0].cert || chainDer[0]);
109
+ if (!leaf) return { ok: false, error: 'CERTIFICATE_PARSE_ERROR' };
110
+
111
+ const pool = [];
112
+ for (let i = 1; i < chainDer.length; i++) {
113
+ const x = _x509(chainDer[i].cert || chainDer[i]);
114
+ if (x) pool.push(x);
115
+ }
116
+
117
+ const trusted = [];
118
+ for (let i = 0; i < (anchors || []).length; i++) {
119
+ const x = _x509(anchors[i]);
120
+ if (x) trusted.push(x);
121
+ }
122
+ if (trusted.length === 0) return { ok: false, error: 'NO_TRUST_ANCHORS' };
123
+
124
+ let current = leaf;
125
+ const used = new Set();
126
+
127
+ for (let depth = 0; depth < MAX_CHAIN_DEPTH; depth++) {
128
+ // Explicitly trusted certificate (pinned leaf, or a self-signed cert the
129
+ // caller deliberately placed in the trust store).
130
+ for (const a of trusted) if (_sameCert(current, a)) return { ok: true, depth: depth };
131
+
132
+ for (const a of trusted) {
133
+ try {
134
+ if (current.checkIssued(a) && current.verify(a.publicKey)) {
135
+ if (!_validNow(a, now)) return { ok: false, error: 'CERT_CHAIN_ANCHOR_EXPIRED' };
136
+ return { ok: true, depth: depth + 1 };
137
+ }
138
+ } catch (e) { /* try next anchor */ }
139
+ }
140
+
141
+ let next = null;
142
+ for (let i = 0; i < pool.length; i++) {
143
+ if (used.has(i)) continue; // cycle guard
144
+ const cand = pool[i];
145
+ try {
146
+ if (!current.checkIssued(cand)) continue;
147
+ if (!current.verify(cand.publicKey)) continue;
148
+ if (cand.ca !== true) return { ok: false, error: 'INVALID_CA_BASIC_CONSTRAINT' };
149
+ if (!_validNow(cand, now)) return { ok: false, error: 'CERT_CHAIN_INTERMEDIATE_EXPIRED' };
150
+ used.add(i); next = cand; break;
151
+ } catch (e) { /* try next candidate */ }
152
+ }
153
+
154
+ if (!next) return { ok: false, error: 'UNABLE_TO_GET_ISSUER_CERT' };
155
+ current = next;
156
+ }
157
+
158
+ return { ok: false, error: 'CERT_CHAIN_TOO_LONG' };
159
+ }
160
+
161
+
65
162
  function TLSSession(options){
66
163
  if (!(this instanceof TLSSession)) return new TLSSession(options);
67
164
  options = options || {};
@@ -93,6 +190,13 @@ function TLSSession(options){
93
190
 
94
191
  local_random: null,
95
192
  local_extensions: [],
193
+ // DTLS-SRTP protection profiles we accept, in preference order (RFC 5764).
194
+ // Value-based, not key-based: transports forward the option unconditionally,
195
+ // so the key is present with an undefined value when it was never set.
196
+ srtp_profiles: Array.isArray(options.srtpProfiles) ? options.srtpProfiles.slice() : [],
197
+ selected_srtp_profile: null,
198
+ // Extension types we offered — see note_offered_extensions().
199
+ offered_extension_types: [],
96
200
 
97
201
 
98
202
  local_supported_versions: [],
@@ -182,12 +286,30 @@ function TLSSession(options){
182
286
  remote_handshake_traffic_secret: null,
183
287
  local_handshake_traffic_secret: null,
184
288
 
185
- remote_app_traffic_secret: null,
289
+ // RFC 8446 §7.1: the application traffic secrets (and exporter_master_secret)
290
+ // are derived from the transcript hash through the SERVER's Finished —
291
+ // ClientHello..server Finished, and nothing after it.
292
+ //
293
+ // This is a SEMANTIC checkpoint, so it is captured at the moment that event
294
+ // happens (server: when it pushes its own Finished; client: when it accepts
295
+ // the server's), NOT read off the live transcript wherever the derivation
296
+ // block happens to sit in the reactive loop. Those two differ: on the
297
+ // client, the loop may already have emitted its own Certificate (when the
298
+ // server requested client auth) before reaching the derivation block, which
299
+ // silently derived app keys over a longer transcript than the server used —
300
+ // a split-key handshake that surfaced only as a late bad_record_mac.
301
+ tls13_app_transcript_hash: null,
302
+
186
303
  local_app_traffic_secret: null,
187
304
 
188
305
  local_cert_chain: null,
189
306
  remote_cert_chain: null,
190
307
  peerAuthorized: false,
308
+ // Set only after the peer's handshake signature (TLS 1.3
309
+ // CertificateVerify, or TLS 1.2 ServerKeyExchange) actually verified
310
+ // against its certificate's public key. Gates handshake completion.
311
+ peerSignatureVerified: false,
312
+ peerSignatureScheme: null,
191
313
  authorizationError: null,
192
314
 
193
315
  selected_cert: null,
@@ -206,6 +328,11 @@ function TLSSession(options){
206
328
 
207
329
  // HelloRetryRequest
208
330
  helloRetried: false, // true if HRR was sent/received
331
+ // What a received HelloRetryRequest committed the server to; the second
332
+ // ServerHello is validated against these (RFC 8446 §4.1.4).
333
+ hrr_version: null,
334
+ hrr_group: null,
335
+ hrr_cipher: null,
209
336
 
210
337
  // DTLS cookie (set by DTLSSession via set_context)
211
338
  dtls_cookie: undefined, // Uint8Array or undefined
@@ -301,6 +428,404 @@ function TLSSession(options){
301
428
  * so subsequent calls to get_transcript_hash() run in O(1) clone+digest time
302
429
  * instead of re-hashing the entire transcript.
303
430
  */
431
+ /**
432
+ * Negotiated-version predicates.
433
+ *
434
+ * The engine deliberately handles TLS 1.2 and 1.3 in ONE state machine: the
435
+ * version is chosen *during* the handshake, so the machine must start
436
+ * version-agnostic, and a large part of the flow (hello parsing, extensions,
437
+ * transcript, certificates, ALPN/SNI, alerts) is genuinely shared. What
438
+ * differs is expressed as guards on individual steps.
439
+ *
440
+ * These two helpers exist so those guards read as the protocol question they
441
+ * actually are. Each site used to spell out
442
+ * (context.selected_version === wire.TLS_VERSION.TLS1_3 ||
443
+ * context.selected_version === wire.DTLS_VERSION.DTLS1_3)
444
+ * — ~90 characters that buried the *rest* of the condition it was ANDed with.
445
+ * (D)TLS 1.3 and 1.2 are treated as one version each because the record layer
446
+ * differences live in record.js / the transports, not here.
447
+ */
448
+ // Delegate rather than restate: wire.js owns the "which message format does
449
+ // this version constant mean" question, because that is where getting it
450
+ // wrong silently produced a TLS 1.2 Certificate on a DTLS 1.3 connection.
451
+ // Introducing is_tls13_wire there and leaving a second copy of the same test
452
+ // here would have recreated exactly the drift it was added to remove.
453
+ function is13() { return wire.is_tls13_wire(context.selected_version); }
454
+ function is12() { return wire.is_tls12_wire(context.selected_version); }
455
+
456
+ /**
457
+ * Assemble the ClientHello extension list.
458
+ *
459
+ * ONE builder for both the initial ClientHello and the post-HelloRetryRequest
460
+ * CH2. RFC 8446 §4.1.2 requires CH2 to be identical to CH1 except for
461
+ * key_share, cookie and (optionally) a trimmed PSK — so building them from
462
+ * two hand-maintained copies is a standing invitation to divergence, and the
463
+ * copies HAD already diverged: one gated supported_versions/key_share on
464
+ * "do we offer 1.3", the other always emitted them, and the 1.3-only test
465
+ * differed between them (one accounted for DTLS 1.3, the other did not).
466
+ *
467
+ * @param {object} opts
468
+ * keyShareGroup — NamedGroup for the key_share entry (null = omit)
469
+ * keySharePublic — public key bytes for that group
470
+ * cookie — HRR cookie to echo (RFC 8446 §4.2.2), or null
471
+ */
472
+ function build_client_hello_extensions(opts) {
473
+ opts = opts || {};
474
+
475
+ let offers13 = context.local_supported_versions.some(function (v) {
476
+ return v === wire.TLS_VERSION.TLS1_3 || v === wire.DTLS_VERSION.DTLS1_3;
477
+ });
478
+ // "1.3-only" means every offered version is (D)TLS 1.3 — a QUIC-style
479
+ // profile that must not carry TLS 1.2 legacy extensions.
480
+ let tls13Only = context.local_supported_versions.length > 0 &&
481
+ context.local_supported_versions.every(function (v) {
482
+ return v === wire.TLS_VERSION.TLS1_3 || v === wire.DTLS_VERSION.DTLS1_3;
483
+ });
484
+
485
+ let extensions = [];
486
+
487
+ if (offers13) {
488
+ extensions.push({ type: 'SUPPORTED_VERSIONS', value: context.local_supported_versions });
489
+ }
490
+ // supported_groups is a CAPABILITY claim, not a copy of configuration.
491
+ //
492
+ // A configured list may name groups ecdh.js does not implement — a caller
493
+ // passing a curve list through, a post-quantum group in a profile we do not
494
+ // yet support. Advertising one is a promise: RFC 8446 §4.2.7 lets the
495
+ // server pick any group in this list and send a HelloRetryRequest for it.
496
+ // We would then have to abort a handshake the server conducted correctly,
497
+ // purely because we over-promised. Filtering here keeps the configured list
498
+ // intact for introspection while never claiming more than we can do.
499
+ let advertisable = [];
500
+ for (let gi = 0; gi < context.local_supported_groups.length; gi++) {
501
+ let g = context.local_supported_groups[gi];
502
+ if (is_supported_group(g)) advertisable.push(g);
503
+ }
504
+ extensions.push({ type: 'SUPPORTED_GROUPS', value: advertisable });
505
+ if (offers13 && opts.keySharePublic) {
506
+ extensions.push({
507
+ type: 'KEY_SHARE',
508
+ value: [{ group: opts.keyShareGroup, key_exchange: opts.keySharePublic }]
509
+ });
510
+ }
511
+ extensions.push({
512
+ type: 'SIGNATURE_ALGORITHMS',
513
+ value: context.local_supported_signature_algorithms
514
+ });
515
+
516
+ if (!tls13Only) {
517
+ // TLS 1.2 compatibility — meaningless (and suspicious to strict stacks)
518
+ // in a 1.3-only hello.
519
+ extensions.push({ type: 'RENEGOTIATION_INFO', value: new Uint8Array(0) });
520
+ extensions.push({ type: 'EXTENDED_MASTER_SECRET', value: null });
521
+ } else {
522
+ // psk_key_exchange_modes: a server MUST NOT send NewSessionTicket to a
523
+ // client that omitted it (RFC 8446 §4.2.9).
524
+ extensions.push({ type: 'PSK_KEY_EXCHANGE_MODES', value: [1] });
525
+ }
526
+
527
+ // SNI must come first.
528
+ if (context.local_sni) {
529
+ extensions.unshift({ type: 'SERVER_NAME', value: context.local_sni });
530
+ }
531
+
532
+ if (context.local_supported_alpns && context.local_supported_alpns.length > 0) {
533
+ extensions.push({ type: 'ALPN', value: context.local_supported_alpns });
534
+ }
535
+
536
+ // RFC 5077 SessionTicket. This belongs HERE, not at the CH1 call site,
537
+ // because RFC 8446 §4.1.2 requires the second ClientHello to be identical
538
+ // to the first except for key_share, cookie and pre_shared_key. Anything
539
+ // appended to CH1 *after* this assembler returns is silently absent from
540
+ // CH2, and a strict peer rejects the retry ("second ClientHello missing
541
+ // extension 35"). Every extension common to both hellos has to be produced
542
+ // by the one function that builds both.
543
+ //
544
+ // It is a TLS 1.2 mechanism (1.3 resumes via NewSessionTicket/PSK), so it
545
+ // is never offered in a 1.3-only profile. Skipped for DTLS as well:
546
+ // implementations often do not handle RFC 5077 there and it broke interop
547
+ // with openssl s_server -dtls1_2.
548
+ let isDtlsProfile = context.local_supported_versions &&
549
+ context.local_supported_versions.some(function (v) { return (v & 0xFF00) === 0xFE00; });
550
+ if (!isDtlsProfile && !tls13Only && context.sessionTickets) {
551
+ if (opts.sessionTicket) {
552
+ extensions.push({ type: 'SESSION_TICKET', value: opts.sessionTicket });
553
+ } else {
554
+ extensions.push({ type: 'SESSION_TICKET', value: new Uint8Array(0) });
555
+ }
556
+ }
557
+
558
+ // Cookie from HelloRetryRequest — MUST be echoed when present.
559
+ if (opts.cookie) {
560
+ extensions.push({ type: 'COOKIE', value: opts.cookie });
561
+ }
562
+
563
+ // Caller-supplied extensions (e.g. transport parameters).
564
+ // DTLS-SRTP offer (RFC 5764 §4.1.1): the client advertises every profile it
565
+ // accepts. Built here so CH1 and CH2 stay identical (RFC 8446 §4.1.2).
566
+ if (!context.isServer && Array.isArray(context.srtp_profiles) &&
567
+ context.srtp_profiles.length > 0) {
568
+ extensions.push({ type: 'USE_SRTP',
569
+ value: { profiles: context.srtp_profiles.slice(),
570
+ mki: new Uint8Array(0) } });
571
+ }
572
+
573
+ for (let i = 0; i < context.local_extensions.length; i++) {
574
+ extensions.push(context.local_extensions[i]);
575
+ }
576
+
577
+ note_offered_extensions(extensions);
578
+ return extensions;
579
+ }
580
+
581
+ /**
582
+ * Remember which extension types we actually offered.
583
+ *
584
+ * RFC 8446 §4.2: "Implementations MUST NOT send extension responses if the
585
+ * remote endpoint did not send the corresponding extension requests ...
586
+ * Upon receiving such an extension, an endpoint MUST abort the handshake
587
+ * with an unsupported_extension alert."
588
+ *
589
+ * Enforcing that needs one fact — what we asked for — recorded in one place.
590
+ * The assembler is that place: every ClientHello, first or post-HRR, is built
591
+ * through it, so the record cannot drift from what actually went on the wire.
592
+ * Extensions appended after assembly (pre_shared_key, session_ticket) call
593
+ * this again to add themselves.
594
+ */
595
+ /**
596
+ * RFC 8446 §4.2 (TLS 1.3): a server MUST NOT answer with an extension the
597
+ * client did not request, and a client receiving one MUST abort with
598
+ * unsupported_extension.
599
+ *
600
+ * IMPORTANT: this function does NOT gate on version itself, because the
601
+ * ServerHello caller cannot rely on context.selected_version yet (it is set
602
+ * in a later pass of the reactive loop). The caller decides scope from the
603
+ * message's own supported_versions, which is authoritative.
604
+ *
605
+ * TLS/DTLS 1.2 (RFC 5246 §7.4.1.4) is deliberately looser: unknown
606
+ * extensions must be ignored. Applying the 1.3 rule to a 1.2 handshake
607
+ * rejects entirely valid peers (the WebRTC regression against pion /
608
+ * webrtc-rs).
609
+ *
610
+ * One rule for every server-originated extension block (ServerHello,
611
+ * EncryptedExtensions) — they all fail for the same reason and must fail
612
+ * the same way. Returns true when the caller should stop processing.
613
+ *
614
+ * HelloRetryRequest is handled earlier and returns before this point, so its
615
+ * cookie — which by definition we could not have offered — never lands here.
616
+ */
617
+ function reject_unsolicited_extensions(message, where) {
618
+ if (context.isServer) return false; // servers answer, not ask
619
+ if (!Array.isArray(message.extensions)) return false;
620
+ if (context.offered_extension_types.length === 0) return false; // nothing recorded yet
621
+
622
+ for (let i = 0; i < message.extensions.length; i++) {
623
+ let et = message.extensions[i] && message.extensions[i].type;
624
+ if (typeof et !== 'number') continue;
625
+ if (context.offered_extension_types.indexOf(et) < 0) {
626
+ fatalAlert(wire.TLS_ALERT.UNSUPPORTED_EXTENSION,
627
+ where + ' carried extension ' + et + ' which we did not offer');
628
+ return true;
629
+ }
630
+ }
631
+ return false;
632
+ }
633
+
634
+ function note_offered_extensions(list) {
635
+ if (!Array.isArray(context.offered_extension_types)) {
636
+ context.offered_extension_types = [];
637
+ }
638
+ for (let i = 0; i < list.length; i++) {
639
+ let t = list[i] && list[i].type;
640
+ let code = (typeof t === 'number') ? t : wire.TLS_EXT[t];
641
+ if (typeof code === 'number' && context.offered_extension_types.indexOf(code) < 0) {
642
+ context.offered_extension_types.push(code);
643
+ }
644
+ }
645
+ }
646
+
647
+ /**
648
+ * The peer's leaf-certificate public key, as a crypto.KeyObject.
649
+ *
650
+ * Single place that turns remote_cert_chain[0] into a usable key. Both the
651
+ * TLS 1.3 CertificateVerify path and the TLS 1.2 ServerKeyExchange path need
652
+ * it, and having two copies means a future change to how we read the chain
653
+ * (chain building, alternative encodings) could be applied to one and missed
654
+ * on the other — the kind of half-fix that has bitten us before.
655
+ *
656
+ * Returns null if there is no chain or the leaf cannot be parsed; callers
657
+ * turn that into the appropriate alert for their context.
658
+ */
659
+ function peer_leaf_public_key() {
660
+ if (!context.remote_cert_chain || context.remote_cert_chain.length === 0) return null;
661
+ try {
662
+ return new crypto.X509Certificate(Buffer.from(context.remote_cert_chain[0].cert)).publicKey;
663
+ } catch (e) {
664
+ return null;
665
+ }
666
+ }
667
+
668
+ /**
669
+ * The cipher suite a ServerHello / HelloRetryRequest selected.
670
+ *
671
+ * parse_hello normalises the server's single choice into `cipher_suites[0]`,
672
+ * but some paths surface it as `cipher_suite`. Both spellings are in use, so
673
+ * reading only one of them silently yields undefined — which is how the
674
+ * HelloRetryRequest cipher-consistency check came to accept every mismatch.
675
+ */
676
+ function hello_cipher_suite(message) {
677
+ if (Array.isArray(message.cipher_suites) && message.cipher_suites.length > 0) {
678
+ return message.cipher_suites[0];
679
+ }
680
+ if (typeof message.cipher_suite === 'number') return message.cipher_suite;
681
+ return null;
682
+ }
683
+
684
+ /**
685
+ * Build and send the second ClientHello after a HelloRetryRequest.
686
+ *
687
+ * ONE emitter for both retry shapes — the server asked for a different group,
688
+ * or it asked only for a cookie. They differ in exactly two values
689
+ * (which group's key share to carry, and whether a cookie is echoed) and in
690
+ * nothing else, so writing them out twice means every future change to CH2
691
+ * has to be made in two places. That is precisely how the "second ClientHello
692
+ * missing extension 35" bug arose: an extension was added to one path and not
693
+ * the other.
694
+ *
695
+ * RFC 8446 §4.1.2 requires CH2 to be identical to CH1 apart from key_share,
696
+ * cookie and pre_shared_key — an invariant that is only cheap to hold when a
697
+ * single function produces it.
698
+ */
699
+ function send_second_client_hello(keyShareGroup, keySharePublic, cookie) {
700
+ let extensions = build_client_hello_extensions({
701
+ keyShareGroup: keyShareGroup,
702
+ keySharePublic: keySharePublic,
703
+ cookie: cookie
704
+ });
705
+
706
+ let ch2 = build_tls_message({
707
+ type: 'client_hello',
708
+ version: 0x0303,
709
+ random: context.local_random,
710
+ session_id: context.local_session_id,
711
+ cookie: context.dtls_cookie,
712
+ cipher_suites: context.local_supported_cipher_suites,
713
+ cipher_suite: context.local_supported_cipher_suites,
714
+ extensions: extensions,
715
+ });
716
+
717
+ pushTranscript(ch2);
718
+ ev.emit('message', 0, context.message_sent_seq, 'hello', ch2);
719
+ context.message_sent_seq++;
720
+ }
721
+
722
+ /**
723
+ * The signature algorithms the peer asked us to sign a client certificate
724
+ * with, from its CertificateRequest.
725
+ *
726
+ * There is deliberately NO fallback to our own offer list. Both call sites
727
+ * used to do `certificateRequestSigAlgs.length > 0 ? … : local_supported…`,
728
+ * which reads as caution but is not: signing with an algorithm the verifier
729
+ * never requested produces a signature it rejects, and it hid the real bug
730
+ * (the 1.3 list was never extracted from the CertificateRequest extensions,
731
+ * so the fallback fired on every handshake and looked like it worked).
732
+ *
733
+ * An empty list is a genuine protocol error — RFC 8446 §4.3.2 makes
734
+ * signature_algorithms mandatory in a TLS 1.3 CertificateRequest, and the
735
+ * decoder now rejects one without it — so returning empty here lets
736
+ * pick_scheme return null and the caller raise handshake_failure, which is
737
+ * the honest outcome.
738
+ */
739
+ function peer_requested_sig_algs() {
740
+ return context.certificateRequestSigAlgs || [];
741
+ }
742
+
743
+ /**
744
+ * Hash of the negotiated cipher suite — the hash the whole TLS 1.3 key
745
+ * schedule, every transcript hash and every Finished MAC are computed with.
746
+ *
747
+ * It was spelled out as negotiated_hash()
748
+ * at nineteen sites. That is a named protocol concept, not an incidental
749
+ * lookup, and writing it out each time means nineteen places to touch if the
750
+ * suite table ever changes shape — and nineteen chances to read the wrong
751
+ * field.
752
+ */
753
+ function negotiated_hash() {
754
+ let meta = TLS_CIPHER_SUITES[context.selected_cipher_suite];
755
+ return meta ? meta.hash : null;
756
+ }
757
+
758
+ /**
759
+ * Our certificate's private key as a crypto.KeyObject.
760
+ *
761
+ * The DER/pkcs8 encoding of `cert_private_key` is an internal storage
762
+ * detail; four sites repeated the same three-line construction to undo it.
763
+ * Keeping the shape in one place means a future change to how keys are held
764
+ * (PEM, an external signer, a KeyObject cached up front) is a change here
765
+ * and nowhere else.
766
+ */
767
+ function local_private_key() {
768
+ if (!context.cert_private_key) return null;
769
+ return crypto.createPrivateKey({
770
+ key: Buffer.from(context.cert_private_key),
771
+ format: 'der',
772
+ type: 'pkcs8',
773
+ });
774
+ }
775
+
776
+ /**
777
+ * HKDF-Expand-Label prefix for THIS session's key schedule.
778
+ *
779
+ * RFC 8446 §7.1 uses "tls13 "; RFC 9147 §5.9 requires "dtls13" (no trailing
780
+ * space) for DTLS 1.3, precisely so that the two protocols cannot derive the
781
+ * same keys from the same secrets. Using the TLS prefix over DTLS produces a
782
+ * handshake where every message parses and every transcript matches, and only
783
+ * the keys differ — which surfaces as "bad record MAC" with no other clue.
784
+ */
785
+ function label_prefix() {
786
+ return context.selected_version === wire.DTLS_VERSION.DTLS1_3
787
+ ? LABEL_PREFIX_DTLS13
788
+ : LABEL_PREFIX_TLS13;
789
+ }
790
+
791
+ /**
792
+ * DTLS-SRTP profile negotiation (RFC 5764 §4.1).
793
+ *
794
+ * The library previously decoded the client's use_srtp extension and handed
795
+ * it to the caller, but selected nothing and answered nothing — so every
796
+ * consumer had to re-implement the negotiation, and a peer that expects the
797
+ * stack to answer (as BoringSSL's SSL_CTX_set_tlsext_use_srtp does) got no
798
+ * use_srtp back at all.
799
+ *
800
+ * Selection is by SERVER preference over the mutually supported set, matching
801
+ * how the cipher suite and group are chosen. The answer carries exactly one
802
+ * profile and an empty MKI: RFC 5764 §4.1.1 says the server "MUST include
803
+ * exactly one" protection profile, and §4.1.2 that an MKI it does not
804
+ * understand is ignored rather than fatal.
805
+ *
806
+ * Returns the chosen profile, or null when there is no overlap — in which
807
+ * case NO use_srtp extension is sent, which is how RFC 5764 §4.1.2 says a
808
+ * server declines ("if no acceptable profile is found, the server SHOULD NOT
809
+ * include the extension").
810
+ */
811
+ function negotiate_srtp_profile() {
812
+ if (!context.isServer) return null;
813
+ if (!Array.isArray(context.srtp_profiles) || context.srtp_profiles.length === 0) return null;
814
+
815
+ let offered = null;
816
+ for (let i = 0; i < context.remote_extensions.length; i++) {
817
+ let e = context.remote_extensions[i];
818
+ if (e && e.type === wire.TLS_EXT.USE_SRTP) { offered = e.value; break; }
819
+ }
820
+ if (!offered || !Array.isArray(offered.profiles) || offered.profiles.length === 0) return null;
821
+
822
+ for (let i = 0; i < context.srtp_profiles.length; i++) {
823
+ let p = context.srtp_profiles[i] | 0;
824
+ if (offered.profiles.indexOf(p) >= 0) return p;
825
+ }
826
+ return null;
827
+ }
828
+
304
829
  function pushTranscript(data) {
305
830
  if (context.transcriptHook) {
306
831
  data = context.transcriptHook(data);
@@ -353,25 +878,111 @@ function TLSSession(options){
353
878
  }
354
879
  }
355
880
 
881
+ /**
882
+ * Abort the handshake with a fatal alert.
883
+ * Single funnel for every protocol-violation path: sends the alert (which
884
+ * also flips context.state to 'error', making the session inert — the
885
+ * reactive loop and process_income_message both early-return on terminal
886
+ * states, so a poisoned context can never keep producing messages), then
887
+ * surfaces the reason to the owner via 'error'.
888
+ */
889
+ function fatalAlert(description, msg) {
890
+ if (context.state === 'error' || context.state === 'closed') return;
891
+ sendAlert(2, description); // sets context.state = 'error'
892
+ ev.emit('error', new Error(msg));
893
+ }
894
+
356
895
  function process_income_message(data){
357
896
 
897
+ // Terminal states: a fatal alert (ours or a parse failure) already ended
898
+ // this session. Feeding more data must not resurrect the state machine.
899
+ if (context.state === 'error' || context.state === 'closed') return;
900
+
358
901
  // Track handshake start time
359
902
  if (context.handshakeStartTime === null) context.handshakeStartTime = Date.now();
360
903
 
361
904
  // Track handshake size and enforce limit
362
905
  context.handshakeBytes += data.length;
363
906
  if (context.maxHandshakeSize > 0 && context.handshakeBytes > context.maxHandshakeSize) {
364
- ev.emit('error', new Error('Handshake size exceeded maxHandshakeSize (' + context.maxHandshakeSize + ')'));
907
+ // Must go through the same funnel as every other abort: previously this
908
+ // emitted 'error' but sent no alert and left the session in a live state,
909
+ // so the reactive loop could keep processing the very flood the limit
910
+ // exists to stop, and the peer was never told why we went quiet.
911
+ fatalAlert(wire.TLS_ALERT.INTERNAL_ERROR,
912
+ 'Handshake size exceeded maxHandshakeSize (' + context.maxHandshakeSize + ')');
913
+ return;
914
+ }
915
+
916
+ // The ONE place peer bytes become structured messages. wire.js throws
917
+ // parse errors carrying .alertDesc (decode_error / illegal_parameter);
918
+ // convert them to a fatal alert instead of letting them crash the
919
+ // transport's data handler (remote DoS) or be silently swallowed.
920
+ let message;
921
+ try {
922
+ message = parse_tls_message(data, context.selected_version);
923
+ } catch (e) {
924
+ fatalAlert(e.alertDesc || wire.TLS_ALERT.DECODE_ERROR, 'Malformed handshake message: ' + e.message);
365
925
  return;
366
926
  }
367
927
 
368
- let message = parse_tls_message(data, context.selected_version);
928
+ // Direction enforcement (RFC 8446 §5.1 / unexpected_message): each
929
+ // handshake type is legal from exactly one peer role. Without this, a
930
+ // client-sent EncryptedExtensions (etc.) walks straight into server
931
+ // state it was never meant to touch.
932
+ {
933
+ let t = message.type;
934
+ let serverOnly = t === 'server_hello' || t === 'encrypted_extensions' ||
935
+ t === 'server_hello_done' || t === 'certificate_request' ||
936
+ t === 'server_key_exchange' || t === 'new_session_ticket';
937
+ let clientOnly = t === 'client_hello' || t === 'client_key_exchange';
938
+ if ((context.isServer && serverOnly) || (!context.isServer && clientOnly)) {
939
+ fatalAlert(wire.TLS_ALERT.UNEXPECTED_MESSAGE, 'Unexpected ' + t + ' from ' + (context.isServer ? 'client' : 'server'));
940
+ return;
941
+ }
942
+ }
369
943
 
370
944
  // Emit 'handshakeMessage' hook for every message
371
945
  ev.emit('handshakeMessage', message.type, data, message);
372
946
 
373
947
  if((context.isServer==false && message.type=='server_hello') || (context.isServer==true && message.type=='client_hello')){
374
948
 
949
+ // Server-side ClientHello policy checks — BEFORE the transcript push,
950
+ // the 'clienthello' emit and set_context's reactive loop, because the
951
+ // loop may synchronously build and send a ServerHello for a hello we
952
+ // are about to reject.
953
+ if (context.isServer && message.type === 'client_hello') {
954
+ let comp = message.legacy_compression || [];
955
+
956
+ // RFC 5246 §7.4.1.2: null compression MUST be present in every CH.
957
+ if (comp.indexOf(0) < 0) {
958
+ fatalAlert(wire.TLS_ALERT.ILLEGAL_PARAMETER, 'ClientHello without null compression');
959
+ return;
960
+ }
961
+
962
+ // RFC 8446 §4.1.2: a CH offering TLS 1.3 must carry exactly one
963
+ // compression method: null. Enforced when 1.3 is on the table for
964
+ // this negotiation (client offers it and we support it — an empty
965
+ // local list means "not configured yet", which defaults to 1.3+1.2).
966
+ let sv = message.supported_versions || [];
967
+ let offers13 = sv.indexOf(wire.TLS_VERSION.TLS1_3) >= 0 || sv.indexOf(wire.DTLS_VERSION.DTLS1_3) >= 0;
968
+ let lv = context.local_supported_versions || [];
969
+ let we13 = lv.length === 0 || lv.indexOf(wire.TLS_VERSION.TLS1_3) >= 0 || lv.indexOf(wire.DTLS_VERSION.DTLS1_3) >= 0;
970
+ if (offers13 && we13 && comp.length !== 1) {
971
+ fatalAlert(wire.TLS_ALERT.ILLEGAL_PARAMETER, 'TLS 1.3 ClientHello must offer exactly the null compression method');
972
+ return;
973
+ }
974
+
975
+ // RFC 8446 §4.2.11: one binder per identity, and never zero of either.
976
+ if (message.pre_shared_key && Array.isArray(message.pre_shared_key.identities)) {
977
+ let nIds = message.pre_shared_key.identities.length;
978
+ let nBinders = Array.isArray(message.pre_shared_key.binders) ? message.pre_shared_key.binders.length : 0;
979
+ if (nIds === 0 || nBinders !== nIds) {
980
+ fatalAlert(wire.TLS_ALERT.ILLEGAL_PARAMETER, 'pre_shared_key identities/binders count mismatch (' + nIds + '/' + nBinders + ')');
981
+ return;
982
+ }
983
+ }
984
+ }
985
+
375
986
  pushTranscript(data);
376
987
 
377
988
  // Save raw ClientHello + emit event (server side)
@@ -422,12 +1033,12 @@ function TLSSession(options){
422
1033
  if (pskResult && pskResult.psk) {
423
1034
  let pskCipher = pskResult.cipher || 0x1301;
424
1035
  let hashName = TLS_CIPHER_SUITES[pskCipher] ? TLS_CIPHER_SUITES[pskCipher].hash : 'sha256';
425
- let binder_key = derive_binder_key(hashName, pskResult.psk, false);
1036
+ let binder_key = derive_binder_key(hashName, pskResult.psk, false, label_prefix());
426
1037
 
427
1038
  let hashLen = getHashFn(hashName).outputLen;
428
1039
  let bindersSize = 2 + 1 + hashLen;
429
1040
  let truncatedCH = data.slice(0, data.length - bindersSize);
430
- let expectedBinder = compute_psk_binder(hashName, binder_key, truncatedCH);
1041
+ let expectedBinder = compute_psk_binder(hashName, binder_key, truncatedCH, label_prefix());
431
1042
 
432
1043
  dbg('SRV-PSK', 'hash:', hashName, 'hashLen:', hashLen,
433
1044
  'truncatedCH len:', truncatedCH.length,
@@ -448,6 +1059,10 @@ function TLSSession(options){
448
1059
  context.psk_offered = {
449
1060
  psk: pskResult.psk instanceof Uint8Array ? pskResult.psk : new Uint8Array(pskResult.psk),
450
1061
  cipher: pskCipher,
1062
+ // The HASH is what actually constrains cipher selection
1063
+ // (RFC 8446 §4.2.11), not the exact suite the ticket was issued
1064
+ // under. Stored here so selection does not have to re-derive it.
1065
+ hash: hashName,
451
1066
  };
452
1067
  }
453
1068
  }
@@ -500,91 +1115,318 @@ function TLSSession(options){
500
1115
  // Extract cookie from HRR (if present, must be echoed in CH2)
501
1116
  let hrrCookie = message.cookie || null;
502
1117
 
1118
+ // RFC 8446 §4.1.4: "Clients MUST abort the handshake with an
1119
+ // illegal_parameter alert if the HelloRetryRequest would not result in
1120
+ // any change in the ClientHello."
1121
+ //
1122
+ // A retry that names neither a new group nor a cookie asks us to resend
1123
+ // a byte-identical ClientHello, which the server would answer with the
1124
+ // same retry — an unbounded loop. Detecting it is not an optimisation:
1125
+ // without it we neither retried nor failed, we simply stopped, and the
1126
+ // peer sat waiting until it timed out. A stall is a denial of service.
1127
+ if (!requestedGroup && !hrrCookie) {
1128
+ fatalAlert(wire.TLS_ALERT.ILLEGAL_PARAMETER,
1129
+ 'HelloRetryRequest requests no change (no key_share and no cookie)');
1130
+ return;
1131
+ }
1132
+
1133
+ // Record EVERYTHING the retry committed to, in one place, before the
1134
+ // branch below splits on whether a group was requested.
1135
+ //
1136
+ // RFC 8446 §4.1.4: the second ServerHello must agree with the retry on
1137
+ // version, cipher suite and group. Those three checks need three
1138
+ // recorded values, and recording them in two different places — the
1139
+ // cipher here, the version and group inside the requestedGroup branch —
1140
+ // meant a cookie-only retry recorded the cipher and nothing else, so
1141
+ // two of the three consistency checks silently never fired for it. One
1142
+ // concept, one place.
1143
+ let hrrCipherSel = hello_cipher_suite(message);
1144
+ if (hrrCipherSel !== null) context.hrr_cipher = hrrCipherSel;
1145
+
1146
+ let hrrVersionSel = null;
1147
+ if (Array.isArray(message.supported_versions) && message.supported_versions.length > 0) {
1148
+ hrrVersionSel = message.supported_versions[0];
1149
+ } else if (typeof message.supported_versions === 'number') {
1150
+ hrrVersionSel = message.supported_versions;
1151
+ }
1152
+ if (hrrVersionSel !== null) context.hrr_version = hrrVersionSel;
1153
+
1154
+ // Null for a cookie-only retry — which is correct: it committed to no
1155
+ // group, so there is nothing for SH2 to contradict.
1156
+ context.hrr_group = requestedGroup;
1157
+
1158
+ // A cookie-only retry is legal: the server is asking us to prove
1159
+ // reachability without changing our key share. The group path below is
1160
+ // skipped, so drive CH2 from here — previously this case produced no
1161
+ // second ClientHello at all and stalled exactly like the empty retry.
1162
+ if (!requestedGroup && hrrCookie) {
1163
+ set_context({
1164
+ selected_version: (Array.isArray(message.supported_versions) && message.supported_versions.length > 0)
1165
+ ? message.supported_versions[0]
1166
+ : (typeof message.supported_versions === 'number' ? message.supported_versions : null),
1167
+ // NOTE: dtls_cookie is the DTLS ClientHello's own cookie FIELD
1168
+ // (RFC 6347 §4.2.1), which lives in the message body and makes
1169
+ // wire.js encode the hello in DTLS framing. The HelloRetryRequest
1170
+ // cookie is a completely different thing — a TLS EXTENSION
1171
+ // (RFC 8446 §4.2.2) that build_client_hello_extensions already
1172
+ // carries. They share a name and nothing else. Assigning the HRR
1173
+ // cookie here injected a 1-byte length plus the cookie into a TLS
1174
+ // ClientHello body, which strict peers reject outright ("error
1175
+ // decoding ClientHello message"). Leave the DTLS field alone.
1176
+ });
1177
+
1178
+ let firstGroup = context.local_supported_groups[0];
1179
+ let firstShare = context.local_key_groups[firstGroup]
1180
+ ? context.local_key_groups[firstGroup].public_key : null;
1181
+ send_second_client_hello(firstGroup, firstShare, hrrCookie);
1182
+ return;
1183
+ }
1184
+
503
1185
  if (requestedGroup) {
504
- // Generate key for the requested group
1186
+ // RFC 8446 §4.1.4: the retry must ask for a group the client offered
1187
+ // in supported_groups but did NOT already send a key_share for.
1188
+ // Asking for one we already shared is pointless — the server had
1189
+ // everything it needed — and the RFC requires the client to abort
1190
+ // rather than send a second share for the same group.
1191
+ if (requestedGroup in context.local_key_groups) {
1192
+ fatalAlert(wire.TLS_ALERT.ILLEGAL_PARAMETER,
1193
+ 'HelloRetryRequest asked for group 0x' + (requestedGroup >>> 0).toString(16) +
1194
+ ' for which we already sent a key share');
1195
+ return;
1196
+ }
1197
+
1198
+ // The retry must also stay within what we advertised: a group absent
1199
+ // from our supported_groups was never on offer.
1200
+ if (context.local_supported_groups.length > 0 &&
1201
+ context.local_supported_groups.indexOf(requestedGroup) < 0) {
1202
+ fatalAlert(wire.TLS_ALERT.ILLEGAL_PARAMETER,
1203
+ 'HelloRetryRequest asked for group 0x' + (requestedGroup >>> 0).toString(16) +
1204
+ ' which we did not offer');
1205
+ return;
1206
+ }
1207
+
1208
+ // Generate key for the requested group (ecdh.js owns curve dispatch).
1209
+ // A group we don't implement means the server asked for something we
1210
+ // never offered -- RFC 8446 4.1.4 makes that illegal_parameter.
505
1211
  let newKeyGroup = null;
506
- if (requestedGroup === 0x001d) {
507
- let pk = new Uint8Array(crypto.randomBytes(32));
508
- let pub = x25519_get_public_key(pk);
509
- newKeyGroup = { group: requestedGroup, public_key: pub, private_key: pk };
510
- context.local_key_groups[requestedGroup] = { public_key: pub, private_key: pk };
511
- } else if (requestedGroup === 0x0017) {
512
- let kp = p256_generate_keypair();
513
- newKeyGroup = { group: requestedGroup, public_key: kp.public_key, private_key: kp.private_key };
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 };
1212
+ let hrrKp = ecdh_generate_keypair(requestedGroup);
1213
+ if (hrrKp) {
1214
+ newKeyGroup = { group: requestedGroup, public_key: hrrKp.public_key, private_key: hrrKp.private_key };
1215
+ context.local_key_groups[requestedGroup] = { public_key: hrrKp.public_key, private_key: hrrKp.private_key };
1216
+ } else {
1217
+ fatalAlert(wire.TLS_ALERT.ILLEGAL_PARAMETER,
1218
+ 'HelloRetryRequest asked for unsupported group 0x' + (requestedGroup >>> 0).toString(16));
1219
+ return;
519
1220
  }
520
1221
 
521
1222
  if (newKeyGroup) {
522
- // Build and send new ClientHello (CH2) with:
523
- // - key_share for requested group
524
- // - cookie (if HRR included one)
525
- // - ALPN (same as CH1)
526
- // - custom extensions (QUIC transport params etc.)
527
- // - same cipher_suites, session_id, random as CH1
528
- // CH2 MUST match CH1 exactly except key_share/cookie (RFC 8446
529
- // §4.1.2) — so mirror CH1's conditional composition: same sigalg
530
- // source (CH1 wrote its final list back to the context), and the
531
- // same tls13Only rules for the legacy extensions and psk modes.
532
- let tls13Only2 = context.local_supported_versions.length > 0 &&
533
- context.local_supported_versions.every(v => v === 0x0304);
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] });
1223
+ // Record what the HelloRetryRequest told us THROUGH set_context,
1224
+ // rather than only acting on it locally. The HRR carries the
1225
+ // server's chosen version and group; skipping the reactive setter
1226
+ // left selected_version unset for the whole CH2 exchange, which is
1227
+ // why the record layer could not tell a 1.3 compat-CCS from a 1.2
1228
+ // cipher change and had to be given a weaker test. State the peer
1229
+ // told us belongs in the context like any other negotiated value.
1230
+ let hrrVersion = null;
1231
+ if (Array.isArray(message.supported_versions) && message.supported_versions.length > 0) {
1232
+ hrrVersion = message.supported_versions[0];
1233
+ } else if (typeof message.supported_versions === 'number') {
1234
+ hrrVersion = message.supported_versions;
545
1235
  }
546
1236
 
547
- // SNI (must be first)
548
- if (context.local_sni) extensions.unshift({ type: 'SERVER_NAME', value: context.local_sni });
1237
+ set_context({
1238
+ selected_version: hrrVersion,
1239
+ selected_group: requestedGroup,
1240
+ // See the note above: the HRR cookie is an extension, not the
1241
+ // DTLS ClientHello cookie field. Preserve whatever DTLS cookie
1242
+ // the transport established, and nothing more.
1243
+ dtls_cookie: context.dtls_cookie,
1244
+ add_local_key_groups: [{
1245
+ group: requestedGroup,
1246
+ private_key: newKeyGroup.private_key,
1247
+ public_key: newKeyGroup.public_key
1248
+ }],
1249
+ });
549
1250
 
550
- // ALPN (same as CH1 — required for QUIC/h3)
551
- if (context.local_supported_alpns && context.local_supported_alpns.length > 0) {
552
- extensions.push({ type: 'ALPN', value: context.local_supported_alpns });
553
- }
1251
+ // CH2 MUST match CH1 except key_share/cookie (RFC 8446 §4.1.2) —
1252
+ // guaranteed here by building both from the same assembler.
1253
+ send_second_client_hello(requestedGroup, newKeyGroup.public_key, hrrCookie);
1254
+ }
1255
+ }
554
1256
 
555
- // Cookie from HRR (RFC 8446 §4.2.2 — MUST echo if present)
556
- if (hrrCookie) {
557
- extensions.push({ type: 'COOKIE', value: hrrCookie });
558
- }
1257
+ // Don't process HRR as a regular ServerHello
1258
+ return;
1259
+ }
559
1260
 
560
- // Custom extensions (e.g. QUIC transport params 0x39)
561
- for (let ci = 0; ci < context.local_extensions.length; ci++) {
562
- extensions.push(context.local_extensions[ci]);
563
- }
1261
+ // Whether THIS ServerHello is 1.3: the server declares its choice via
1262
+ // the supported_versions extension (RFC 8446 §4.2.1). Reading
1263
+ // context.selected_version here would be too early — that field is set
1264
+ // in the set_context below, in a later pass of the reactive loop. Base
1265
+ // the decision on the message itself, which is authoritative.
1266
+ let sh_is_13 = false;
1267
+ if (Array.isArray(message.supported_versions)) {
1268
+ sh_is_13 = message.supported_versions.indexOf(wire.TLS_VERSION.TLS1_3) >= 0 ||
1269
+ message.supported_versions.indexOf(wire.DTLS_VERSION.DTLS1_3) >= 0;
1270
+ } else if (typeof message.supported_versions === 'number') {
1271
+ sh_is_13 = message.supported_versions === wire.TLS_VERSION.TLS1_3 ||
1272
+ message.supported_versions === wire.DTLS_VERSION.DTLS1_3;
1273
+ }
564
1274
 
565
- let ch2 = build_tls_message({
566
- type: 'client_hello',
567
- version: 0x0303,
568
- random: context.local_random,
569
- session_id: context.local_session_id,
570
- cookie: context.dtls_cookie,
571
- // BUGFIX: `cipher_suites` (plural) is what wire.js's client_hello
572
- // builder actually reads; the singular key was silently ignored and
573
- // wire.js substituted its own hardcoded fallback list — the
574
- // configured cipher suites never reached the wire. Both names passed.
575
- cipher_suites: context.local_supported_cipher_suites,
576
- cipher_suite: context.local_supported_cipher_suites,
577
- extensions: extensions,
578
- });
1275
+ // ── Second ServerHello must honour the HelloRetryRequest ──
1276
+ // RFC 8446 §4.1.4: after a retry, the ServerHello that follows must name
1277
+ // the SAME version and the SAME group the retry asked for. A server that
1278
+ // changes its mind between the two is either broken or steering us, and
1279
+ // §4.1.4 makes the mismatch an illegal_parameter. Checked here, once,
1280
+ // against the values the HRR committed to (hrr_version / hrr_group) —
1281
+ // the HRR branch itself has already returned by this point, so this only
1282
+ // ever sees the real ServerHello.
1283
+ if (!context.isServer && message.type === 'server_hello' && context.helloRetried) {
1284
+
1285
+ if (context.hrr_version !== null) {
1286
+ let shVersion = null;
1287
+ if (Array.isArray(message.supported_versions) && message.supported_versions.length > 0) {
1288
+ shVersion = message.supported_versions[0];
1289
+ } else if (typeof message.supported_versions === 'number') {
1290
+ shVersion = message.supported_versions;
1291
+ }
1292
+ if (shVersion !== null && shVersion !== context.hrr_version) {
1293
+ fatalAlert(wire.TLS_ALERT.ILLEGAL_PARAMETER,
1294
+ 'Second ServerHello version 0x' + (shVersion >>> 0).toString(16) +
1295
+ ' does not match the HelloRetryRequest (0x' + (context.hrr_version >>> 0).toString(16) + ')');
1296
+ return;
1297
+ }
1298
+ }
579
1299
 
580
- pushTranscript(ch2);
581
- ev.emit('message', 0, context.message_sent_seq, 'hello', ch2);
582
- context.message_sent_seq++;
1300
+ // RFC 8446 §4.1.4: "the server MUST send the same cipher suite in the
1301
+ // ServerHello as it did in the HelloRetryRequest." The retry already
1302
+ // fixed the hash that the whole key schedule depends on, so a change
1303
+ // here is not a preference the client may follow — it is a mismatch
1304
+ // that would silently derive different keys on each side.
1305
+ let shCipherSel = hello_cipher_suite(message);
1306
+ if (context.hrr_cipher !== null && shCipherSel !== null &&
1307
+ shCipherSel !== context.hrr_cipher) {
1308
+ fatalAlert(wire.TLS_ALERT.ILLEGAL_PARAMETER,
1309
+ 'ServerHello cipher suite 0x' + (shCipherSel >>> 0).toString(16) +
1310
+ ' differs from the HelloRetryRequest (0x' + (context.hrr_cipher >>> 0).toString(16) + ')');
1311
+ return;
1312
+ }
1313
+
1314
+ if (context.hrr_group !== null &&
1315
+ Array.isArray(message.key_groups) && message.key_groups.length > 0) {
1316
+ let shGroup = message.key_groups[0] && message.key_groups[0].group;
1317
+ if (typeof shGroup === 'number' && shGroup !== context.hrr_group) {
1318
+ fatalAlert(wire.TLS_ALERT.ILLEGAL_PARAMETER,
1319
+ 'Second ServerHello selected group 0x' + (shGroup >>> 0).toString(16) +
1320
+ ' but the HelloRetryRequest asked for 0x' + (context.hrr_group >>> 0).toString(16));
1321
+ return;
583
1322
  }
584
1323
  }
1324
+ }
585
1325
 
586
- // Don't process HRR as a regular ServerHello
587
- return;
1326
+ // Record the server's DTLS-SRTP choice (it answers in EncryptedExtensions
1327
+ // for 1.3, in the ServerHello for 1.2 — both land in remote_extensions).
1328
+ if (!context.isServer && Array.isArray(message.extensions)) {
1329
+ for (let sx = 0; sx < message.extensions.length; sx++) {
1330
+ let e = message.extensions[sx];
1331
+ if (e && e.type === wire.TLS_EXT.USE_SRTP && e.value &&
1332
+ Array.isArray(e.value.profiles) && e.value.profiles.length > 0) {
1333
+ context.selected_srtp_profile = e.value.profiles[0];
1334
+ }
1335
+ }
1336
+ }
1337
+
1338
+ if (sh_is_13 && reject_unsolicited_extensions(message, 'ServerHello')) return;
1339
+
1340
+ // ── legacy_session_id_echo (RFC 8446 §4.1.3) ──
1341
+ // "A client which receives a legacy_session_id_echo field that does not
1342
+ // match what it sent in the ClientHello MUST abort the handshake with an
1343
+ // illegal_parameter alert."
1344
+ //
1345
+ // This is unconditional for the client, and RFC 9147's errata is explicit
1346
+ // that it still applies over DTLS 1.3 — DTLS disables the *server* side of
1347
+ // TLS 1.3's compatibility mode (Appendix D.4) but NOT this check. We were
1348
+ // not verifying it at all: a server could echo anything, or nothing, and
1349
+ // we accepted it. The field is also the one signal that distinguishes a
1350
+ // genuine ServerHello from one replayed or crafted for a different
1351
+ // ClientHello, so skipping it is not cosmetic.
1352
+ // TLS 1.3 ONLY. In TLS 1.2 the server is free to return a NEW session_id
1353
+ // to establish a new session (RFC 5246 §7.4.1.3) — echoing the client's is
1354
+ // how it signals RESUMPTION, not a requirement. Applying the 1.3 rule there
1355
+ // rejects every ordinary 1.2 handshake.
1356
+ if (sh_is_13 && !context.isServer && message.type === 'server_hello') {
1357
+ let sentSid = context.local_session_id || new Uint8Array(0);
1358
+ let echoSid = message.session_id || new Uint8Array(0);
1359
+ let sameSid = sentSid.length === echoSid.length;
1360
+ if (sameSid) {
1361
+ for (let si = 0; si < sentSid.length; si++) {
1362
+ if (sentSid[si] !== echoSid[si]) { sameSid = false; break; }
1363
+ }
1364
+ }
1365
+ if (!sameSid) {
1366
+ fatalAlert(wire.TLS_ALERT.ILLEGAL_PARAMETER,
1367
+ 'ServerHello legacy_session_id_echo does not match the ClientHello (' +
1368
+ 'sent ' + sentSid.length + ' bytes, echoed ' + echoSid.length + ')');
1369
+ return;
1370
+ }
1371
+ }
1372
+
1373
+
1374
+ // ── Client side of the negotiation-outcome rule ──
1375
+ // RFC 8446 §4.2.8 (TLS 1.3 ONLY): "servers offer exactly one
1376
+ // KeyShareEntry in the ServerHello ... This value MUST be in the same
1377
+ // group as the KeyShareEntry value offered by the client." §4.1.4 says a
1378
+ // server that wants a different group must send HelloRetryRequest
1379
+ // instead.
1380
+ //
1381
+ // Scoped to 1.3 explicitly. DTLS 1.2 (and TLS 1.2) do not use key_share
1382
+ // at all — they carry the server's ECDHE public key in
1383
+ // ServerKeyExchange, and the ServerHello may legitimately carry
1384
+ // extensions the parser exposes on the same field. Running this check
1385
+ // there rejects entirely valid 1.2 handshakes and was the WebRTC
1386
+ // regression against pion / webrtc-rs.
1387
+ //
1388
+ // Testing membership in local_key_groups — the shares we actually SENT —
1389
+ // is the RFC's own test, and is stricter than "was it in our
1390
+ // supported_groups": offering a group is not the same as having sent a
1391
+ // key share for it. HelloRetryRequest has returned earlier, and its
1392
+ // requested group is added to local_key_groups before CH2, so a genuine
1393
+ // retry passes here.
1394
+ if (!context.isServer && message.type === 'server_hello' &&
1395
+ sh_is_13 &&
1396
+ Array.isArray(message.key_groups) && message.key_groups.length > 0) {
1397
+ for (let ki = 0; ki < message.key_groups.length; ki++) {
1398
+ let g = message.key_groups[ki] && message.key_groups[ki].group;
1399
+ if (typeof g !== 'number') continue;
1400
+ if (!(g in context.local_key_groups)) {
1401
+ fatalAlert(wire.TLS_ALERT.ILLEGAL_PARAMETER,
1402
+ 'ServerHello selected key_share group 0x' + (g >>> 0).toString(16) +
1403
+ ' for which we sent no key share');
1404
+ return;
1405
+ }
1406
+ }
1407
+ }
1408
+
1409
+ // Resolve our certificate BEFORE the set_context below.
1410
+ //
1411
+ // The reactive loop derives the cipher suite from the inputs present when
1412
+ // it runs, and (D)TLS 1.2 suites name an authentication algorithm that
1413
+ // MUST match our certificate's key type (RFC 4492 §2). Resolving the
1414
+ // certificate afterwards — as the SNI callback used to — meant selection
1415
+ // ran with no key in hand, so a server holding an ECDSA certificate could
1416
+ // pick an ECDHE_RSA suite and then present a certificate the peer rejects
1417
+ // as "wrong certificate type". Inputs first, then the loop.
1418
+ if (context.isServer === true && message.type === 'client_hello' &&
1419
+ typeof context.SNICallback === 'function') {
1420
+ context.SNICallback(message.sni || null, function (err, creds) {
1421
+ if (!err && creds) {
1422
+ // Direct assignment, not set_context: we are deliberately seeding
1423
+ // inputs ahead of the single set_context below, which is what
1424
+ // actually runs the loop. Going through set_context here would run
1425
+ // it twice, once with a half-populated hello.
1426
+ context.local_cert_chain = creds.certificateChain;
1427
+ context.cert_private_key = creds.privateKey;
1428
+ }
1429
+ });
588
1430
  }
589
1431
 
590
1432
  set_context({
@@ -618,8 +1460,11 @@ function TLSSession(options){
618
1460
  if (timingSafeEqualU8(tail, DOWNGRADE_SENTINEL_TLS12) ||
619
1461
  timingSafeEqualU8(tail, DOWNGRADE_SENTINEL_TLS11)) {
620
1462
  dbg('DOWNGRADE', 'sentinel detected in ServerHello.random — aborting');
621
- sendAlert(2, wire.TLS_ALERT.ILLEGAL_PARAMETER); // fatal, illegal_parameter (47)
622
- ev.emit('error', new Error('TLS downgrade attack detected (RFC 8446 §4.1.3 sentinel present)'));
1463
+ // Same abort funnel as everything else (fatalAlert = alert + terminal
1464
+ // state + 'error'); the hand-rolled pair here predated it and was the
1465
+ // one abort path that could drift out of sync with the rest.
1466
+ fatalAlert(wire.TLS_ALERT.ILLEGAL_PARAMETER,
1467
+ 'TLS downgrade attack detected (RFC 8446 §4.1.3 sentinel present)');
623
1468
  return;
624
1469
  }
625
1470
  }
@@ -742,18 +1587,6 @@ function TLSSession(options){
742
1587
 
743
1588
  ev.emit('hello');
744
1589
 
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
1590
 
758
1591
 
759
1592
 
@@ -761,6 +1594,53 @@ function TLSSession(options){
761
1594
 
762
1595
  pushTranscript(data);
763
1596
 
1597
+ // TLS 1.2 ECDHE: the ServerKeyExchange is signed over
1598
+ // client_random | server_random | ServerECDHParams
1599
+ // (RFC 4492 §5.4). Verifying it is what binds the ephemeral key to the
1600
+ // certificate — without it an active attacker can substitute their own
1601
+ // ECDHE public key and the handshake still completes. This was never
1602
+ // checked before.
1603
+ if (message.type === 'server_key_exchange' && !context.isServer &&
1604
+ message.signature && message.public_key) {
1605
+
1606
+ if (!context.remote_cert_chain || context.remote_cert_chain.length === 0) {
1607
+ fatalAlert(wire.TLS_ALERT.UNEXPECTED_MESSAGE, 'ServerKeyExchange before Certificate');
1608
+ return;
1609
+ }
1610
+
1611
+ let skePubKey = peer_leaf_public_key();
1612
+ if (!skePubKey) {
1613
+ fatalAlert(wire.TLS_ALERT.BAD_CERTIFICATE, 'Cannot read server certificate public key');
1614
+ return;
1615
+ }
1616
+
1617
+ // Rebuild the exact signed payload: randoms in wire order (client
1618
+ // first) followed by the ServerECDHParams as they appeared.
1619
+ let ecdhParams = wire.build_server_ecdh_params(message.group, message.public_key);
1620
+ let skeTbs = concatUint8Arrays([
1621
+ context.local_random, // client_random (we are the client)
1622
+ context.remote_random, // server_random
1623
+ ecdhParams
1624
+ ]);
1625
+
1626
+ let skeOk = verify_with_scheme(
1627
+ wire.TLS_VERSION.TLS1_2,
1628
+ message.sig_alg,
1629
+ skeTbs,
1630
+ skePubKey,
1631
+ message.signature
1632
+ );
1633
+
1634
+ // Same strict test as the CertificateVerify gate: fail closed.
1635
+ if (skeOk !== true) {
1636
+ fatalAlert(wire.TLS_ALERT.DECRYPT_ERROR, 'ServerKeyExchange signature verification failed');
1637
+ return;
1638
+ }
1639
+
1640
+ context.peerSignatureVerified = true;
1641
+ context.peerSignatureScheme = message.sig_alg;
1642
+ }
1643
+
764
1644
  if ([0xC02F,0xC02B,0xC030,0xC02C,0xC013,0xC014,0xC009,0xC00A].includes(context.selected_cipher_suite)==true) {//ECDHE
765
1645
 
766
1646
  // ServerKeyExchange carries the group; ClientKeyExchange does not (server already chose it)
@@ -804,6 +1684,38 @@ function TLSSession(options){
804
1684
 
805
1685
  }else if(message.type=='encrypted_extensions'){
806
1686
 
1687
+ if (reject_unsolicited_extensions(message, 'EncryptedExtensions')) return;
1688
+
1689
+ // RFC 8446 §4.2 (table): extensions with a defined home elsewhere MUST
1690
+ // NOT appear in EncryptedExtensions — a peer that puts key_share (etc.)
1691
+ // here is violating the extension placement rules → illegal_parameter.
1692
+ // Everything unknown/custom is allowed through: EE is exactly where
1693
+ // application extensions (QUIC transport params, use_srtp, ALPN) live.
1694
+ {
1695
+ let forbiddenInEE = {
1696
+ 5: 1, // status_request (CH/CR/Certificate only)
1697
+ 13: 1, // signature_algorithms
1698
+ 21: 1, // padding (CH only)
1699
+ 23: 1, // extended_master_secret (not a TLS 1.3 extension)
1700
+ 35: 1, // session_ticket (not a TLS 1.3 extension)
1701
+ 41: 1, // pre_shared_key
1702
+ 43: 1, // supported_versions
1703
+ 44: 1, // cookie
1704
+ 45: 1, // psk_key_exchange_modes
1705
+ 47: 1, // certificate_authorities (CH/CR only)
1706
+ 50: 1, // signature_algorithms_cert
1707
+ 51: 1, // key_share
1708
+ 0xff01: 1, // renegotiation_info (not a TLS 1.3 extension)
1709
+ };
1710
+ let eeExts = Array.isArray(message.extensions) ? message.extensions : [];
1711
+ for (let fi = 0; fi < eeExts.length; fi++) {
1712
+ if (eeExts[fi] && forbiddenInEE[eeExts[fi].type] === 1) {
1713
+ fatalAlert(wire.TLS_ALERT.ILLEGAL_PARAMETER, 'Forbidden extension ' + eeExts[fi].type + ' in EncryptedExtensions');
1714
+ return;
1715
+ }
1716
+ }
1717
+ }
1718
+
807
1719
  pushTranscript(data);
808
1720
 
809
1721
  // TLS 1.3: most server extensions arrive HERE, not in the
@@ -834,23 +1746,147 @@ function TLSSession(options){
834
1746
 
835
1747
  pushTranscript(data);
836
1748
 
837
- set_context({
838
- remote_cert_chain: message.entries,
839
- });
1749
+ let entries = message.entries || [];
1750
+
1751
+ // RFC 8446 §4.4.2.4 / RFC 5246 §7.4.6.
1752
+ // - From a SERVER an empty certificate_list is always illegal: the
1753
+ // server has nothing else to authenticate with, so this is fatal here
1754
+ // and now.
1755
+ // - From a CLIENT it is legal — it means "I have no certificate". Whether
1756
+ // that is acceptable is a POLICY question answered at the completion
1757
+ // gate, which also catches the client that omits the message entirely.
1758
+ // So we only record the outcome and let the reactive loop proceed;
1759
+ // returning early here would skip the set_context that drives it.
1760
+ if (entries.length === 0) {
1761
+ if (!context.isServer) {
1762
+ fatalAlert(wire.TLS_ALERT.DECODE_ERROR, 'Server sent an empty certificate_list');
1763
+ return;
1764
+ }
1765
+ context.authorizationError = 'NO_PEER_CERTIFICATE';
1766
+ context.peerAuthorized = false; // no set_context setter for this field
1767
+ set_context({ remote_cert_chain: [] });
1768
+ } else {
1769
+ set_context({
1770
+ remote_cert_chain: entries,
1771
+ });
840
1772
 
841
- // Validate peer certificate
842
- validatePeerCertificate();
843
- if (context.rejectUnauthorized && !context.peerAuthorized) {
844
- sendAlert(2, 42); // fatal, bad_certificate
845
- return;
1773
+ // Validate peer certificate
1774
+ validatePeerCertificate();
1775
+ if (context.rejectUnauthorized && !context.peerAuthorized) {
1776
+ fatalAlert(wire.TLS_ALERT.BAD_CERTIFICATE,
1777
+ 'Peer certificate validation failed: ' + (context.authorizationError || 'unknown'));
1778
+ return;
1779
+ }
846
1780
  }
847
1781
 
848
1782
  }else if(message.type=='certificate_verify'){
849
1783
 
1784
+ // RFC 8446 §4.4.3 — verify the peer's handshake signature.
1785
+ //
1786
+ // This is the step that actually authenticates the peer: without it,
1787
+ // ANY party able to send us a certificate (which is public data) is
1788
+ // accepted, because nothing proves they hold the matching private key.
1789
+ // Previously this branch only pushed to the transcript.
1790
+ //
1791
+ // Ordering is critical: the signed transcript covers everything up to
1792
+ // but NOT including this message, so the hash MUST be taken before
1793
+ // pushTranscript(data) below.
1794
+ // A TLS 1.3 CertificateVerify that we cannot verify must NEVER be
1795
+ // silently skipped. The block below is the only thing standing between
1796
+ // us and accepting an unauthenticated peer, so every path that isn't a
1797
+ // successful verification has to be an abort — including "we somehow got
1798
+ // here without a negotiated cipher suite", which would otherwise fall
1799
+ // through with peerSignatureVerified still false and no explanation.
1800
+ if (context.selected_version === wire.TLS_VERSION.TLS1_3 ||
1801
+ context.selected_version === wire.DTLS_VERSION.DTLS1_3) {
1802
+
1803
+ if (context.selected_cipher_suite === null) {
1804
+ fatalAlert(wire.TLS_ALERT.UNEXPECTED_MESSAGE,
1805
+ 'CertificateVerify received before a cipher suite was negotiated');
1806
+ return;
1807
+ }
1808
+
1809
+ if (!context.remote_cert_chain || context.remote_cert_chain.length === 0) {
1810
+ fatalAlert(wire.TLS_ALERT.UNEXPECTED_MESSAGE, 'CertificateVerify without a preceding Certificate');
1811
+ return;
1812
+ }
1813
+
1814
+ // Note on ordering: the cryptographic check below is the SECURITY
1815
+ // decision and must be the thing that gates acceptance. An additional
1816
+ // "was this scheme in our signature_algorithms" policy check
1817
+ // (RFC 8446 §4.4.3) used to run FIRST here and abort with
1818
+ // illegal_parameter. That was wrong in two ways: it let a
1819
+ // scheme-decoding anomaly turn into a spurious rejection before the
1820
+ // signature was ever checked, and it reported a policy error for what
1821
+ // may be a signature failure. verify_with_scheme already rejects any
1822
+ // scheme it cannot map to a (hash, algorithm, padding) triple, and
1823
+ // binds the scheme to the certificate's key type, so an unoffered or
1824
+ // unknown scheme still cannot verify.
1825
+
1826
+ let peerPubKey = peer_leaf_public_key();
1827
+ if (!peerPubKey) {
1828
+ fatalAlert(wire.TLS_ALERT.BAD_CERTIFICATE, 'Cannot read peer certificate public key');
1829
+ return;
1830
+ }
1831
+
1832
+ let cvHashName = negotiated_hash();
1833
+ // isServer=false here means "we are the client, so the signature we
1834
+ // are checking is the SERVER's" — the label follows the SIGNER's role,
1835
+ // which is the opposite of ours.
1836
+ let signerIsServer = !context.isServer;
1837
+ let tbs = build_cert_verify_tbs_with_hash(cvHashName, signerIsServer, get_transcript_hash(cvHashName));
1838
+
1839
+ let sigOk = verify_with_scheme(
1840
+ wire.TLS_VERSION.TLS1_3, // DTLS 1.3 shares TLS 1.3 scheme semantics
1841
+ message.scheme,
1842
+ tbs,
1843
+ peerPubKey,
1844
+ message.signature
1845
+ );
1846
+
1847
+ // Gate on "is exactly true", not on truthiness. verify_with_scheme
1848
+ // already returns a strict boolean, but writing the test this way means
1849
+ // a future refactor that lets some path fall through returning
1850
+ // undefined fails CLOSED rather than open. The distinction matters here
1851
+ // more than almost anywhere else in the library: this single value is
1852
+ // what stands between us and an unauthenticated peer.
1853
+ if (sigOk !== true) {
1854
+ fatalAlert(wire.TLS_ALERT.DECRYPT_ERROR, 'CertificateVerify signature verification failed');
1855
+ return;
1856
+ }
1857
+
1858
+ context.peerSignatureVerified = true;
1859
+ context.peerSignatureScheme = message.scheme;
1860
+ }
1861
+
850
1862
  pushTranscript(data);
851
1863
 
852
1864
  }else if(message.type=='finished'){
853
1865
 
1866
+ // ── Order enforcement: Finished may not follow a bare Certificate ──
1867
+ //
1868
+ // RFC 8446 §4.4.3: a peer that sends a non-empty Certificate MUST follow
1869
+ // it with CertificateVerify. Skipping it and going straight to Finished
1870
+ // means the peer never proved possession of the certificate's private
1871
+ // key — it merely presented somebody's certificate.
1872
+ //
1873
+ // A completion-time check alone is not enough here. A peer that omits
1874
+ // CertificateVerify still shares the ECDHE secret, so it can compute a
1875
+ // perfectly valid Finished; we would verify that Finished, derive
1876
+ // application keys and only then notice. Rejecting on ARRIVAL keeps the
1877
+ // omission from ever influencing the key schedule, and reports the
1878
+ // protocol error (unexpected_message) rather than a downstream symptom.
1879
+ //
1880
+ // Applies in both roles: a client checks the server's certificate, and a
1881
+ // server checks a client certificate it was offered.
1882
+ if (is13() && context.remote_cert_chain && context.remote_cert_chain.length > 0 &&
1883
+ context.peerSignatureVerified !== true) {
1884
+ fatalAlert(wire.TLS_ALERT.UNEXPECTED_MESSAGE,
1885
+ 'Finished received after a certificate that was never proved by a CertificateVerify');
1886
+ return;
1887
+ }
1888
+
1889
+
854
1890
  set_context({
855
1891
  remote_finished: message.body
856
1892
  });
@@ -859,10 +1895,10 @@ function TLSSession(options){
859
1895
 
860
1896
  // Client receives NewSessionTicket from server (post-handshake in TLS 1.3, pre-CCS in TLS 1.2)
861
1897
  if(!context.isServer){
862
- if ((context.selected_version === wire.TLS_VERSION.TLS1_3 || context.selected_version === wire.DTLS_VERSION.DTLS1_3) && context.resumption_master_secret) {
1898
+ if ((is13()) && context.resumption_master_secret) {
863
1899
  // TLS 1.3: derive PSK from resumption_master_secret + ticket_nonce
864
- let hashName = TLS_CIPHER_SUITES[context.selected_cipher_suite].hash;
865
- let psk = derive_psk(hashName, context.resumption_master_secret, message.ticket_nonce);
1900
+ let hashName = negotiated_hash();
1901
+ let psk = derive_psk(hashName, context.resumption_master_secret, message.ticket_nonce, label_prefix());
866
1902
 
867
1903
  dbg('CLI-NST', 'received TLS 1.3 NST — cipher:', '0x' + context.selected_cipher_suite.toString(16),
868
1904
  'hash:', hashName,
@@ -919,16 +1955,16 @@ function TLSSession(options){
919
1955
  }else if(message.type=='key_update'){
920
1956
 
921
1957
  // Peer is updating their traffic secret (we update our read key)
922
- if(context.state==='connected' && (context.selected_version === wire.TLS_VERSION.TLS1_3 || context.selected_version === wire.DTLS_VERSION.DTLS1_3)){
923
- let hashName = TLS_CIPHER_SUITES[context.selected_cipher_suite].hash;
1958
+ if(context.state==='connected' && (is13())){
1959
+ let hashName = negotiated_hash();
924
1960
  let hashLen = getHashLen(hashName);
925
- let newRemoteSecret = hkdf_expand_label(hashName, context.remote_app_traffic_secret, 'traffic upd', new Uint8Array(0), hashLen);
1961
+ let newRemoteSecret = hkdf_expand_label(hashName, context.remote_app_traffic_secret, 'traffic upd', new Uint8Array(0), hashLen, label_prefix());
926
1962
  context.remote_app_traffic_secret = newRemoteSecret;
927
1963
  ev.emit('keyUpdate', { direction: 'receive', secret: newRemoteSecret });
928
1964
 
929
1965
  // If peer requested us to update too
930
1966
  if(message.request_update === 1){
931
- let newLocalSecret = hkdf_expand_label(hashName, context.local_app_traffic_secret, 'traffic upd', new Uint8Array(0), hashLen);
1967
+ let newLocalSecret = hkdf_expand_label(hashName, context.local_app_traffic_secret, 'traffic upd', new Uint8Array(0), hashLen, label_prefix());
932
1968
  context.local_app_traffic_secret = newLocalSecret;
933
1969
 
934
1970
  // Send our KeyUpdate (not requesting back)
@@ -951,12 +1987,32 @@ function TLSSession(options){
951
1987
  ev.emit('certificateRequest', message);
952
1988
  }
953
1989
 
1990
+ }else{
1991
+
1992
+ // Terminal branch: a handshake message we do not implement, or one that
1993
+ // is illegal at the negotiated version (RFC 8446 §4: HelloRequest and the
1994
+ // other TLS 1.2-only types have no place in a 1.3 handshake). Anything
1995
+ // reaching here was NOT processed, so it must not be ignored — silently
1996
+ // dropping unknown messages let a peer stream them forever and hid real
1997
+ // protocol errors behind a handshake that simply never progressed.
1998
+ fatalAlert(wire.TLS_ALERT.UNEXPECTED_MESSAGE,
1999
+ 'Unexpected handshake message type ' +
2000
+ (message.handshake_type !== undefined ? message.handshake_type : message.type));
2001
+ return;
2002
+
954
2003
  }
955
2004
 
956
2005
  }
957
2006
 
958
2007
 
959
2008
  function set_context(options){
2009
+ // Terminal states are terminal: after a fatal alert ('error') or a close,
2010
+ // the reactive loop must not keep selecting parameters, deriving keys or
2011
+ // emitting messages. This is also what stops it from writing into a
2012
+ // transport whose peer already went away (see TLSSocket's destroyed
2013
+ // marking — the two guards meet in the middle).
2014
+ if (context.state === 'error' || context.state === 'closed') return;
2015
+
960
2016
  let has_changed=false;
961
2017
 
962
2018
  if (options && typeof options === 'object'){
@@ -1356,8 +2412,64 @@ function TLSSession(options){
1356
2412
 
1357
2413
  if(has_changed==true){
1358
2414
 
2415
+ /**
2416
+ * Values decided during THIS pass of the reactive loop.
2417
+ *
2418
+ * They are written here and only copied into `context` when the pass
2419
+ * commits. That gives one rule, and getting it wrong has already caused
2420
+ * three separate bugs (a spurious ServerHello rejection, the wrong cipher
2421
+ * on PSK resumption, and a HelloRetryRequest that was never sent — the
2422
+ * last two presenting as hangs rather than as wrong answers):
2423
+ *
2424
+ * A gate whose decision MUST be made on the same pass that the input
2425
+ * arrived reads params_to_set first, falling back to context. Use the
2426
+ * `pending()` helper defined just below rather than writing the
2427
+ * ternary out:
2428
+ *
2429
+ * if (pending('selected_version') === wire.TLS_VERSION.TLS1_3) { … }
2430
+ *
2431
+ * A step that can simply wait may read `context` directly. Writing to
2432
+ * params_to_set sets has_changed, so the loop runs again and the value
2433
+ * will be in `context` next time round.
2434
+ *
2435
+ * A SECOND rule, same root, different shape — worth stating because it has
2436
+ * now caused four bugs (a spurious ServerHello rejection, the wrong cipher
2437
+ * on PSK resumption, a HelloRetryRequest that was never sent, and a
2438
+ * HelloVerifyRequest sent in violation of RFC 9147 §5.1):
2439
+ *
2440
+ * A decision derived from an INCOMING MESSAGE must be read from that
2441
+ * message, never from state the message itself is about to establish.
2442
+ *
2443
+ * `is13()` answers "what did we negotiate", which is the right question
2444
+ * once negotiation is done and the wrong one while processing the very
2445
+ * message that decides it. When handling a ClientHello or ServerHello,
2446
+ * take the version from the message's own supported_versions — see
2447
+ * `sh_is_13` in the ServerHello path and `offersDtls13` in dtls_session.
2448
+ *
2449
+ * The test is whether deferring changes the OUTCOME. Key generation and
2450
+ * shared-secret derivation can wait — they just happen a pass later.
2451
+ * HelloRetryRequest cannot: by the next pass the ServerHello may already
2452
+ * have been sent, and the opportunity is gone. Anything gated on
2453
+ * `hello_sent`, `finished_sent` or another one-shot latch is in that
2454
+ * category.
2455
+ */
1359
2456
  let params_to_set = {};
1360
2457
 
2458
+ /**
2459
+ * The value of a negotiated field AS OF THIS PASS.
2460
+ *
2461
+ * Reads params_to_set first, then context — the rule documented above,
2462
+ * expressed once so no gate can forget the fallback half of it. Seven
2463
+ * gates spelled this ternary out under seven different local names
2464
+ * (pickedVersion, pskPassVersion, hrrVersionNow …), which meant a reader
2465
+ * had to re-derive the intent each time and a new gate could easily copy
2466
+ * only the `context` half. `pending('selected_version')` says what it is.
2467
+ */
2468
+ function pending(field) {
2469
+ return (field in params_to_set) ? params_to_set[field] : context[field];
2470
+ }
2471
+
2472
+
1361
2473
 
1362
2474
 
1363
2475
 
@@ -1407,23 +2519,112 @@ function TLSSession(options){
1407
2519
  }
1408
2520
  }
1409
2521
 
1410
- // TLS 1.3 PSK resumption: per RFC 8446 §4.2.11, server MUST select a cipher compatible
1411
- // with the selected PSK (same hash algorithm). Since we accepted the PSK based on its
1412
- // stored cipher's hash (for binder verification), we force the same cipher here.
1413
- if (context.isServer && context.psk_accepted && context.psk_offered && context.psk_offered.cipher &&
1414
- (context.selected_version === wire.TLS_VERSION.TLS1_3 || context.selected_version === wire.DTLS_VERSION.DTLS1_3)) {
1415
- let pskCipher = context.psk_offered.cipher | 0;
1416
- if (context.remote_supported_cipher_suites.indexOf(pskCipher) >= 0 &&
1417
- context.local_supported_cipher_suites.indexOf(pskCipher) >= 0) {
1418
- params_to_set['selected_cipher_suite'] = pskCipher;
2522
+ // TLS 1.3 PSK resumption — RFC 8446 §4.2.11:
2523
+ // "The server MUST ensure that it selects a compatible PSK (if any)
2524
+ // and cipher suite ... a cipher suite with the same hash algorithm."
2525
+ //
2526
+ // The constraint is on the HASH, not on the exact suite the ticket was
2527
+ // issued under. Two bugs lived in the previous version, which forced
2528
+ // the stored suite and otherwise fell through:
2529
+ //
2530
+ // - it ignored our own preference order among the suites that DO
2531
+ // match the hash, so a client offering several compatible suites
2532
+ // got whichever one the ticket happened to carry;
2533
+ // - when the stored suite was no longer on offer it silently
2534
+ // continued with the PSK anyway and let ordinary selection pick a
2535
+ // suite with a DIFFERENT hash — resuming under a key schedule the
2536
+ // peer never agreed to.
2537
+ //
2538
+ // Correct rule: choose, by our normal preference, among the mutually
2539
+ // supported suites whose hash matches the PSK. If none exists the PSK
2540
+ // is unusable and must be dropped so the handshake falls back to a full
2541
+ // one — accepting a PSK we cannot honour is not an "edge case", it is a
2542
+ // resumption the peer will reject.
2543
+ // Version check reads params_to_set first: selection runs in the same
2544
+ // pass that decides the version, so context.selected_version is still
2545
+ // null here and is13() would report false for every 1.3 handshake.
2546
+ let pskPassVersion = pending('selected_version');
2547
+ let pskPassIs13 = pskPassVersion === wire.TLS_VERSION.TLS1_3 ||
2548
+ pskPassVersion === wire.DTLS_VERSION.DTLS1_3;
2549
+
2550
+
2551
+ if (context.isServer && context.psk_accepted && context.psk_offered &&
2552
+ context.psk_offered.hash && pskPassIs13) {
2553
+
2554
+ let pskHash = context.psk_offered.hash;
2555
+ let chosen = null;
2556
+
2557
+ for (let i = 0; i < context.local_supported_cipher_suites.length; i++) {
2558
+ let cs = context.local_supported_cipher_suites[i] | 0;
2559
+ if (context.remote_supported_cipher_suites.indexOf(cs) < 0) continue;
2560
+ let meta = TLS_CIPHER_SUITES[cs];
2561
+ if (!meta || meta.hash !== pskHash) continue;
2562
+ // Describable is not usable — record.js implements AEAD only.
2563
+ if (!is_usable_cipher_suite(cs)) continue;
2564
+ chosen = cs;
2565
+ break;
2566
+ }
2567
+
2568
+
2569
+ if (chosen !== null) {
2570
+ params_to_set['selected_cipher_suite'] = chosen;
2571
+ } else {
2572
+ // No compatible suite → the PSK cannot be used. Undo acceptance and
2573
+ // let the normal path run a full handshake.
2574
+ dbg('SRV-PSK', 'no mutually-supported cipher with hash', pskHash,
2575
+ '— rejecting PSK, falling back to full handshake');
2576
+ context.psk_accepted = false;
2577
+ context.isResumed = false;
2578
+ context.psk_offered = null;
1419
2579
  }
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
2580
  }
1423
2581
 
1424
2582
  if (!('selected_cipher_suite' in params_to_set)) {
2583
+ // RFC 4492 §2 / RFC 5246 §7.4.2: a (D)TLS 1.2 cipher suite names the
2584
+ // authentication algorithm, and it MUST match the key type of the
2585
+ // certificate we are going to present — an ECDHE_RSA suite cannot be
2586
+ // used with an ECDSA certificate. Selecting purely by mutual support
2587
+ // let a server with an EC certificate pick ECDHE_RSA and then send a
2588
+ // certificate the peer rejects as "wrong certificate type".
2589
+ //
2590
+ // The suite→auth mapping already exists in crypto.js's cipher table
2591
+ // (the `sig` field), so this reads that authority rather than
2592
+ // re-deriving it. TLS 1.3 suites are authentication-agnostic
2593
+ // (sig: 'TLS13'), so they are never filtered here.
2594
+ let localAuth = null;
2595
+ if (context.isServer) {
2596
+ try {
2597
+ if (context.cert_private_key) {
2598
+ let lk = local_private_key();
2599
+ localAuth = lk ? lk.asymmetricKeyType : null;
2600
+ }
2601
+ } catch (e) { localAuth = null; }
2602
+ }
2603
+
2604
+ let suiteAuthOk = function (suite) {
2605
+ if (!context.isServer || localAuth === null) return true; // client, or key type unknown
2606
+ let meta = TLS_CIPHER_SUITES[suite];
2607
+ if (!meta || !meta.sig || meta.sig === 'TLS13') return true; // not auth-bound
2608
+ if (meta.sig === 'RSA') return localAuth === 'rsa' || localAuth === 'rsa-pss';
2609
+ if (meta.sig === 'ECDSA') return localAuth === 'ec';
2610
+ return true;
2611
+ };
2612
+
1425
2613
  for (let i2 = 0; i2 < context.local_supported_cipher_suites.length; i2++) {
1426
2614
  let cs = context.local_supported_cipher_suites[i2] | 0;
2615
+ if (!suiteAuthOk(cs)) continue;
2616
+ // A suite belongs to exactly one protocol generation (RFC 8446
2617
+ // §B.4). Selecting a 1.3 suite while negotiating 1.2 — which a
2618
+ // configured list can easily contain — yields a ServerHello the
2619
+ // peer cannot act on. This is the server-side mirror of not
2620
+ // OFFERING 1.3 suites from a 1.2-only client profile.
2621
+ if (!suite_matches_version(cs, pending('selected_version'))) continue;
2622
+ // The suite table names more suites than the record layer can
2623
+ // protect (CBC entries exist for parsing peer offers). Selecting a
2624
+ // described-but-unimplemented suite yields a handshake that agrees
2625
+ // on parameters and then cannot encrypt a record — the cipher-suite
2626
+ // analogue of picking a key-exchange group we cannot compute.
2627
+ if (!is_usable_cipher_suite(cs)) continue;
1427
2628
  for (let j2 = 0; j2 < context.remote_supported_cipher_suites.length; j2++) {
1428
2629
 
1429
2630
  if ((context.remote_supported_cipher_suites[j2] | 0) == cs) {
@@ -1494,67 +2695,124 @@ function TLSSession(options){
1494
2695
  if (context.selected_group == null){
1495
2696
  if(context.local_supported_groups.length > 0 && context.remote_supported_groups.length > 0) {
1496
2697
  for (let i = 0; i < context.local_supported_groups.length; i++) {
1497
- if(context.remote_supported_groups.indexOf(context.local_supported_groups[i])>=0){
1498
- params_to_set['selected_group'] = context.local_supported_groups[i];
1499
- break;
1500
- }
2698
+ let g = context.local_supported_groups[i];
2699
+ if (context.remote_supported_groups.indexOf(g) < 0) continue;
2700
+
2701
+ // Mutual support is not enough: we must be able to actually PRODUCE
2702
+ // a key share for the group. ecdh.js is the single source of truth
2703
+ // for which groups are implemented, and generate_keypair returns
2704
+ // null (rather than throwing) for one it does not know — so a group
2705
+ // that reaches here from configuration alone, such as a
2706
+ // post-quantum group named in a --curves list we merely echo back,
2707
+ // gets selected and then silently never produces a key. The
2708
+ // reactive loop then waits forever for a key share that cannot
2709
+ // exist: no ServerHello, no alert, connection held open. That is a
2710
+ // denial of service, and it is caused by trusting a configured list
2711
+ // over the implementation.
2712
+ if (!is_supported_group(g)) continue;
2713
+
2714
+ params_to_set['selected_group'] = g;
2715
+ break;
1501
2716
  }
1502
2717
  }
1503
2718
  }
1504
2719
 
1505
2720
 
1506
2721
 
1507
- //create the key by the group if dont have...
1508
- if(context.selected_group !== null && context.selected_group in context.local_key_groups==false){
1509
-
1510
- if (context.selected_group === 0x001d) {
1511
-
1512
- const private_key = new Uint8Array(crypto.randomBytes(32));
1513
- let public_key = x25519_get_public_key(private_key);
1514
-
1515
- params_to_set['add_local_key_groups']=[
1516
- {
1517
- group: context.selected_group,
1518
- private_key: private_key,
1519
- public_key: public_key
1520
- }
1521
- ];
1522
-
1523
- } else if (context.selected_group === 0x0017) {
1524
-
1525
- let kp = p256_generate_keypair();
1526
- let private_key = kp.private_key;
1527
- let public_key = kp.public_key;
2722
+ // ── Negotiation outcome: decidable, or impossible? ──
2723
+ //
2724
+ // Every selection step above is written as "if not chosen yet, try to
2725
+ // choose" — which quietly conflates two very different states: the
2726
+ // inputs have not arrived yet, and the inputs HAVE arrived and share
2727
+ // nothing with ours. The first must wait; the second can never succeed.
2728
+ // Treating both as "wait" is what made a ClientHello offering only
2729
+ // groups/ciphers/versions we do not implement hang forever instead of
2730
+ // being refused — a stall is a DoS, and it is also how an unimplemented
2731
+ // post-quantum group (Kyber, X25519MLKEM768) took the connection down
2732
+ // without a single line of code mentioning it.
2733
+ //
2734
+ // This is deliberately ONE check covering all three negotiated
2735
+ // parameters rather than a special case per parameter: they fail for the
2736
+ // same reason and must fail the same way. RFC 8446 §4.1.1 / RFC 5246
2737
+ // §7.4.1.3.
2738
+ //
2739
+ // Only the server decides these; the client validates the server's
2740
+ // choice elsewhere. A non-empty remote cipher list is the signal that a
2741
+ // ClientHello was actually processed, so we never fire before the peer
2742
+ // has spoken.
2743
+ if (context.isServer && context.remote_supported_cipher_suites.length > 0) {
2744
+
2745
+ let pickedVersion = pending('selected_version');
2746
+
2747
+ if (pickedVersion === null && context.local_supported_versions.length > 0 &&
2748
+ context.remote_supported_versions.length > 0) {
2749
+ fatalAlert(wire.TLS_ALERT.PROTOCOL_VERSION,
2750
+ 'No protocol version in common with the peer');
2751
+ return;
2752
+ }
1528
2753
 
1529
- params_to_set['add_local_key_groups']=[
1530
- {
1531
- group: context.selected_group,
1532
- private_key: private_key,
1533
- public_key: public_key
1534
- }
1535
- ];
2754
+ // Cipher and group are checked INDEPENDENTLY of the version outcome.
2755
+ //
2756
+ // These used to be nested inside `if (pickedVersion !== null)`, which
2757
+ // silently made "we could not agree on a group" undetectable whenever
2758
+ // version selection had not completed — and version selection does not
2759
+ // complete when local_supported_versions has not been configured yet,
2760
+ // which is exactly the state a bare server session is in. The result
2761
+ // was the original stall surviving for the one shape that matters:
2762
+ // a ClientHello offering ONLY an unimplemented group (Kyber,
2763
+ // X25519MLKEM768). Each parameter's failure is terminal on its own; one
2764
+ // undecided parameter must not mask another's impossibility.
2765
+ let pickedCipher = pending('selected_cipher_suite');
2766
+ if (pickedCipher === null &&
2767
+ context.local_supported_cipher_suites.length > 0 &&
2768
+ context.remote_supported_cipher_suites.length > 0) {
2769
+ fatalAlert(wire.TLS_ALERT.HANDSHAKE_FAILURE,
2770
+ 'No cipher suite in common with the peer');
2771
+ return;
2772
+ }
1536
2773
 
1537
- } else if (context.selected_group === 0x0018) {
2774
+ // Groups: only meaningful once the peer actually offered some. An
2775
+ // offer we share nothing with is terminal; note that a shared group
2776
+ // for which the peer merely sent no key_share is NOT this case —
2777
+ // selected_group is set there and HelloRetryRequest handles it.
2778
+ let pickedGroup = pending('selected_group');
2779
+ if (pickedGroup === null &&
2780
+ context.remote_supported_groups.length > 0 &&
2781
+ context.local_supported_groups.length > 0) {
2782
+ fatalAlert(wire.TLS_ALERT.HANDSHAKE_FAILURE,
2783
+ 'No key-exchange group in common with the peer');
2784
+ return;
2785
+ }
2786
+ }
1538
2787
 
1539
- let kp = p384_generate_keypair();
1540
- let private_key = kp.private_key;
1541
- let public_key = kp.public_key;
2788
+ //create the key by the group if dont have...
2789
+ // Curve-specific keygen lives in ecdh.js (single source of truth for
2790
+ // which groups exist and how their keys are formed); the loop only
2791
+ // decides WHEN a key is needed.
2792
+ if(context.selected_group !== null && context.selected_group in context.local_key_groups==false){
1542
2793
 
2794
+ let kp = ecdh_generate_keypair(context.selected_group);
2795
+ if (kp) {
1543
2796
  params_to_set['add_local_key_groups']=[
1544
2797
  {
1545
2798
  group: context.selected_group,
1546
- private_key: private_key,
1547
- public_key: public_key
2799
+ private_key: kp.private_key,
2800
+ public_key: kp.public_key
1548
2801
  }
1549
2802
  ];
1550
-
1551
2803
  }
1552
2804
 
1553
-
1554
2805
  }
1555
2806
 
1556
2807
  //get shared_secret...
1557
- if(context.selected_group !== null && context.ecdhe_shared_secret == null && context.selected_group in context.local_key_groups==true && context.selected_group in context.remote_key_groups==true){
2808
+ // Can we compute the ECDHE secret yet? Needs a chosen group, both halves
2809
+ // of the exchange present, and no secret computed already.
2810
+ let ecdheReady = context.selected_group !== null &&
2811
+ context.ecdhe_shared_secret == null &&
2812
+ (context.selected_group in context.local_key_groups) &&
2813
+ (context.selected_group in context.remote_key_groups);
2814
+
2815
+ if (ecdheReady) {
1558
2816
 
1559
2817
  //check we have remote public key and local private key...
1560
2818
  if(context.remote_key_groups[context.selected_group].public_key!==null && context.local_key_groups[context.selected_group].private_key!==null){
@@ -1562,24 +2820,21 @@ function TLSSession(options){
1562
2820
  let remote_public_key=context.remote_key_groups[context.selected_group].public_key;
1563
2821
  let local_private_key=context.local_key_groups[context.selected_group].private_key;
1564
2822
 
1565
- if (context.selected_group === 0x001d) { // X25519
1566
-
1567
- let ecdhe_shared_secret = x25519_get_shared_secret(local_private_key, remote_public_key);
1568
-
1569
- params_to_set['ecdhe_shared_secret']=ecdhe_shared_secret;
1570
-
1571
- } else if (context.selected_group === 0x0017) { // secp256r1 (P-256)
1572
-
1573
- let ecdhe_shared_secret = p256_get_shared_secret(local_private_key, remote_public_key);
1574
-
1575
- params_to_set['ecdhe_shared_secret']=ecdhe_shared_secret;
1576
-
1577
- } else if (context.selected_group === 0x0018) { // secp384r1 (P-384)
1578
-
1579
- let ecdhe_shared_secret = p384_get_shared_secret(local_private_key, remote_public_key);
1580
-
1581
- params_to_set['ecdhe_shared_secret']=ecdhe_shared_secret;
1582
-
2823
+ // The peer's key share is fully attacker-controlled. ecdh.js owns the
2824
+ // point-format rules and throws on anything malformed (wrong length,
2825
+ // bad prefix, off-curve, low-order producing an all-zero secret per
2826
+ // RFC 7748 6.1); node:crypto throws too. Our job here is only to turn
2827
+ // any such failure into a fatal illegal_parameter (RFC 8446 4.2.8)
2828
+ // rather than let it escape through set_context into the transport's
2829
+ // data handler -- that would be a remote DoS on every transport.
2830
+ // fatalAlert moves us to a terminal state, so the reactive loop
2831
+ // cannot re-enter and retry the poisoned computation.
2832
+ try {
2833
+ params_to_set['ecdhe_shared_secret'] =
2834
+ ecdh_get_shared_secret(context.selected_group, local_private_key, remote_public_key);
2835
+ } catch (e) {
2836
+ fatalAlert(wire.TLS_ALERT.ILLEGAL_PARAMETER, 'Invalid peer key share for group 0x' + context.selected_group.toString(16) + ': ' + e.message);
2837
+ return;
1583
2838
  }
1584
2839
 
1585
2840
  }
@@ -1592,14 +2847,31 @@ function TLSSession(options){
1592
2847
  if(context.isServer==true){
1593
2848
 
1594
2849
  // HelloRetryRequest: if we selected a group but client didn't send a key_share for it
1595
- if(context.hello_sent==false && !context.helloRetried && (context.selected_version === wire.TLS_VERSION.TLS1_3 || context.selected_version === wire.DTLS_VERSION.DTLS1_3) &&
1596
- context.selected_group !== null && context.selected_cipher_suite !== null &&
1597
- !(context.selected_group in context.remote_key_groups)){
2850
+ // Read the negotiated values from params_to_set first. Version, cipher
2851
+ // and group are all decided EARLIER IN THIS SAME PASS and are only
2852
+ // copied into `context` when the pass commits, so reading `context`
2853
+ // here sees nulls on exactly the pass that processes the ClientHello.
2854
+ //
2855
+ // The consequence was a hang, not a wrong choice: a client that offers
2856
+ // a group we support but sends its key_share for a different one (very
2857
+ // much including an unimplemented post-quantum group offered alongside
2858
+ // X25519) needs a HelloRetryRequest, and we silently sent nothing at
2859
+ // all. The connection then sat open until the peer timed out — the same
2860
+ // "impossible mistaken for not-yet-decidable" confusion, one layer up.
2861
+ let hrrVersionNow = pending('selected_version');
2862
+ let hrrIs13 = hrrVersionNow === wire.TLS_VERSION.TLS1_3 ||
2863
+ hrrVersionNow === wire.DTLS_VERSION.DTLS1_3;
2864
+ let hrrGroupNow = pending('selected_group');
2865
+ let hrrCipherNow = pending('selected_cipher_suite');
2866
+
2867
+ if(context.hello_sent==false && !context.helloRetried && hrrIs13 &&
2868
+ hrrGroupNow !== null && hrrCipherNow !== null &&
2869
+ !(hrrGroupNow in context.remote_key_groups)){
1598
2870
 
1599
2871
  context.helloRetried = true;
1600
2872
 
1601
2873
  // Replace transcript with message_hash (RFC 8446 §4.4.1)
1602
- let hashName = TLS_CIPHER_SUITES[context.selected_cipher_suite].hash;
2874
+ let hashName = TLS_CIPHER_SUITES[hrrCipherNow].hash;
1603
2875
  let ch1_hash = getHashFn(hashName)(concatUint8Arrays(context.transcript));
1604
2876
  let message_hash = wire.build_message(wire.TLS_MESSAGE_TYPE.MESSAGE_HASH, ch1_hash);
1605
2877
  context.transcript = [message_hash];
@@ -1609,9 +2881,9 @@ function TLSSession(options){
1609
2881
 
1610
2882
  // Build and send HRR (it's a ServerHello with magic random)
1611
2883
  let hrr_body = wire.build_hello_retry_request({
1612
- cipher_suite: context.selected_cipher_suite,
2884
+ cipher_suite: hrrCipherNow,
1613
2885
  selected_version: context.selected_version,
1614
- selected_group: context.selected_group,
2886
+ selected_group: hrrGroupNow,
1615
2887
  session_id: context.remote_session_id,
1616
2888
  });
1617
2889
  let hrr_data = wire.build_message(wire.TLS_MESSAGE_TYPE.SERVER_HELLO, hrr_body);
@@ -1635,14 +2907,14 @@ function TLSSession(options){
1635
2907
  if(context.hello_sent==false){
1636
2908
 
1637
2909
  if(context.selected_version!==null && context.selected_cipher_suite!==null && context.selected_session_id!==null){
1638
- if((context.selected_version === wire.TLS_VERSION.TLS1_3 || context.selected_version === wire.DTLS_VERSION.DTLS1_3)){
2910
+ if((is13())){
1639
2911
  if(context.selected_group in context.local_key_groups==true && context.local_key_groups[context.selected_group].public_key!==null){
1640
2912
  // After HRR, don't send ServerHello until CH2 provides the requested key_share
1641
2913
  if (!context.helloRetried || (context.selected_group in context.remote_key_groups)) {
1642
2914
  can_send_hello=true;
1643
2915
  }
1644
2916
  }
1645
- }else if((context.selected_version === wire.TLS_VERSION.TLS1_2 || context.selected_version === wire.DTLS_VERSION.DTLS1_2)){
2917
+ }else if((is12())){
1646
2918
  // Block ServerHello while waiting for async resumeSession decision
1647
2919
  if (!context.tls12_resume_pending) {
1648
2920
  can_send_hello=true;
@@ -1673,7 +2945,7 @@ function TLSSession(options){
1673
2945
 
1674
2946
  let build_message_params=null;
1675
2947
 
1676
- if((context.selected_version === wire.TLS_VERSION.TLS1_3 || context.selected_version === wire.DTLS_VERSION.DTLS1_3)){
2948
+ if((is13())){
1677
2949
 
1678
2950
  let shExtensions = [
1679
2951
  {
@@ -1698,13 +2970,34 @@ function TLSSession(options){
1698
2970
  type: 'server_hello',
1699
2971
  version: context.selected_version,
1700
2972
  random: context.local_random,
1701
- session_id: context.remote_session_id,
2973
+ // legacy_session_id_echo.
2974
+ //
2975
+ // TLS 1.3 (RFC 8446 §4.1.3) requires the server to echo the
2976
+ // client's legacy_session_id verbatim — that is the whole point
2977
+ // of the Appendix D.4 compatibility mode.
2978
+ //
2979
+ // DTLS 1.3 requires the OPPOSITE. RFC 9147 §5: "DTLS
2980
+ // implementations do not use the TLS 1.3 'compatibility mode'
2981
+ // described in Appendix D.4 of [TLS13]. DTLS servers MUST NOT
2982
+ // echo the 'legacy_session_id' value from the client and
2983
+ // endpoints MUST NOT send ChangeCipherSpec messages."
2984
+ //
2985
+ // Same field, same struct, opposite MUST — and the shared engine
2986
+ // was doing the TLS thing for both transports. A DTLS peer that
2987
+ // enforces §5 sees an echo it never expected. This is exactly the
2988
+ // shape of the other DTLS 1.3 bugs: a legacy field whose meaning
2989
+ // inverted between the two protocols.
2990
+ session_id: (function () {
2991
+ // RFC 9147 §5 — see the note above.
2992
+ let isDtls = context.selected_version === wire.DTLS_VERSION.DTLS1_3;
2993
+ return isDtls ? new Uint8Array(0) : context.remote_session_id;
2994
+ })(),
1702
2995
  cipher_suite: context.selected_cipher_suite,
1703
2996
  extensions: shExtensions
1704
2997
  };
1705
2998
 
1706
2999
 
1707
- }else if((context.selected_version === wire.TLS_VERSION.TLS1_2 || context.selected_version === wire.DTLS_VERSION.DTLS1_2)){
3000
+ }else if((is12())){
1708
3001
 
1709
3002
 
1710
3003
  // TLS 1.2 ServerHello: no SUPPORTED_VERSIONS or KEY_SHARE.
@@ -1742,6 +3035,15 @@ function TLSSession(options){
1742
3035
  // Responsibility for only answering extensions the client
1743
3036
  // actually offered stays with the caller (it can inspect the
1744
3037
  // ClientHello via the 'clienthello' event / getRemoteExtension).
3038
+ // DTLS-SRTP answer for (D)TLS 1.2, where it rides the ServerHello
3039
+ // rather than EncryptedExtensions (which does not exist in 1.2).
3040
+ let srtpSel12 = negotiate_srtp_profile();
3041
+ if (srtpSel12 !== null) {
3042
+ context.selected_srtp_profile = srtpSel12;
3043
+ ext_list.push({ type: 'USE_SRTP',
3044
+ value: { profiles: [srtpSel12], mki: new Uint8Array(0) } });
3045
+ }
3046
+
1745
3047
  for (let lei = 0; lei < context.local_extensions.length; lei++) {
1746
3048
  ext_list.push(context.local_extensions[lei]);
1747
3049
  }
@@ -1803,18 +3105,18 @@ function TLSSession(options){
1803
3105
 
1804
3106
  //get base_secret
1805
3107
  if (context.base_secret==null && context.selected_cipher_suite !== null){
1806
- if((context.selected_version === wire.TLS_VERSION.TLS1_3 || context.selected_version === wire.DTLS_VERSION.DTLS1_3) && (context.ecdhe_shared_secret !== null)){
3108
+ if((is13()) && (context.ecdhe_shared_secret !== null)){
1807
3109
 
1808
- let hashName = TLS_CIPHER_SUITES[context.selected_cipher_suite].hash;
3110
+ let hashName = negotiated_hash();
1809
3111
  let result;
1810
3112
  // Use incremental transcript hash — avoids concat+hash of full transcript
1811
3113
  let tx_hash = get_transcript_hash(hashName);
1812
3114
  if (context.psk_accepted && context.psk_offered && context.psk_offered.psk) {
1813
3115
  // PSK + ECDHE key schedule
1814
- result = derive_handshake_traffic_secrets_psk_with_hash(hashName, context.psk_offered.psk, context.ecdhe_shared_secret, tx_hash);
3116
+ result = derive_handshake_traffic_secrets_psk_with_hash(hashName, context.psk_offered.psk, context.ecdhe_shared_secret, tx_hash, label_prefix());
1815
3117
  } else {
1816
3118
  // Standard key schedule (no PSK)
1817
- result = derive_handshake_traffic_secrets_with_hash(hashName, context.ecdhe_shared_secret, tx_hash);
3119
+ result = derive_handshake_traffic_secrets_with_hash(hashName, context.ecdhe_shared_secret, tx_hash, label_prefix());
1818
3120
  }
1819
3121
 
1820
3122
  params_to_set['base_secret']=result.handshake_secret;
@@ -1827,7 +3129,7 @@ function TLSSession(options){
1827
3129
  params_to_set['remote_handshake_traffic_secret']=result.server_handshake_traffic_secret;
1828
3130
  }
1829
3131
 
1830
- }else if((context.selected_version === wire.TLS_VERSION.TLS1_2 || context.selected_version === wire.DTLS_VERSION.DTLS1_2) && context.local_random!==null && context.remote_random!==null){
3132
+ }else if((is12()) && context.local_random!==null && context.remote_random!==null){
1831
3133
  if(context.ecdhe_shared_secret !== null){
1832
3134
 
1833
3135
 
@@ -1845,7 +3147,7 @@ function TLSSession(options){
1845
3147
  // Server: CKE just arrived, transcript is complete.
1846
3148
  // Client: must wait until CKE is sent and in transcript.
1847
3149
  if(context.isServer || context.key_exchange_sent){
1848
- let hashFn = getHashFn(TLS_CIPHER_SUITES[context.selected_cipher_suite].hash);
3150
+ let hashFn = getHashFn(negotiated_hash());
1849
3151
 
1850
3152
  // Use snapshot up to CKE if available (excludes CertificateVerify)
1851
3153
  let emsTranscript = context._emsTranscriptLen
@@ -1853,12 +3155,12 @@ function TLSSession(options){
1853
3155
  : context.transcript;
1854
3156
  let transcript_hash = hashFn(concatUint8Arrays(emsTranscript));
1855
3157
 
1856
- let master_secret = tls12_prf(context.ecdhe_shared_secret, "extended master secret", transcript_hash, 48, TLS_CIPHER_SUITES[context.selected_cipher_suite].hash);
3158
+ let master_secret = tls12_prf(context.ecdhe_shared_secret, "extended master secret", transcript_hash, 48, negotiated_hash());
1857
3159
 
1858
3160
  params_to_set['base_secret']=master_secret;
1859
3161
  }
1860
3162
  }else{
1861
- let master_secret = tls12_prf(context.ecdhe_shared_secret, "master secret", concatUint8Arrays([client_random, server_random]), 48, TLS_CIPHER_SUITES[context.selected_cipher_suite].hash);
3163
+ let master_secret = tls12_prf(context.ecdhe_shared_secret, "master secret", concatUint8Arrays([client_random, server_random]), 48, negotiated_hash());
1862
3164
 
1863
3165
  params_to_set['base_secret']=master_secret;
1864
3166
  }
@@ -1875,7 +3177,7 @@ function TLSSession(options){
1875
3177
 
1876
3178
 
1877
3179
  //send encrypted_extensions...
1878
- if (context.isServer==true && (context.selected_version === wire.TLS_VERSION.TLS1_3 || context.selected_version === wire.DTLS_VERSION.DTLS1_3)){
3180
+ if (context.isServer==true && (is13())){
1879
3181
  if(context.encrypted_exts_sent==false && context.hello_sent==true && context.local_handshake_traffic_secret!==null){
1880
3182
 
1881
3183
  let extensions=[];
@@ -1883,6 +3185,31 @@ function TLSSession(options){
1883
3185
  extensions.push({ type: 'ALPN', value: [context.selected_alpn] });
1884
3186
  }
1885
3187
 
3188
+ // server_name acknowledgement (RFC 6066 §3, RFC 8446 §4.4.2).
3189
+ // When a client sent SNI and we accepted it, the server confirms by
3190
+ // echoing an EMPTY server_name extension here — no ServerNameList,
3191
+ // zero-length body. The extension is asymmetric: the list only ever
3192
+ // travels client→server.
3193
+ //
3194
+ // NOT on a resumed handshake. RFC 6066 §3: the acknowledgement means
3195
+ // "I used this name to select my certificate", which only happens in
3196
+ // a full handshake — on resumption the identity comes from the
3197
+ // established session instead, and sending the extension anyway is a
3198
+ // protocol error ("server acknowledged server_name when resuming").
3199
+ // The psk_accepted flag is the same signal the Certificate and
3200
+ // CertificateVerify steps already use to know they must be skipped.
3201
+ if (context.remote_sni && !context.psk_accepted) {
3202
+ extensions.push({ type: 'SERVER_NAME', value: null });
3203
+ }
3204
+
3205
+
3206
+ // DTLS-SRTP answer (RFC 5764 §4.1). One profile, empty MKI.
3207
+ let srtpSel = negotiate_srtp_profile();
3208
+ if (srtpSel !== null) {
3209
+ context.selected_srtp_profile = srtpSel;
3210
+ extensions.push({ type: 'USE_SRTP',
3211
+ value: { profiles: [srtpSel], mki: new Uint8Array(0) } });
3212
+ }
1886
3213
 
1887
3214
  for(let i = 0; i < context.local_extensions.length; i++){
1888
3215
  extensions.push(context.local_extensions[i]);
@@ -1907,7 +3234,7 @@ function TLSSession(options){
1907
3234
 
1908
3235
  //send certificate... (skip for PSK resumption — no cert needed)
1909
3236
  // 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 && (context.selected_version === wire.TLS_VERSION.TLS1_3 || context.selected_version === wire.DTLS_VERSION.DTLS1_3) && !context.psk_accepted){
3237
+ 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
3238
  let cr_data = build_tls_message({
1912
3239
  type: 'certificate_request',
1913
3240
  certificate_request_context: new Uint8Array(0),
@@ -1920,7 +3247,15 @@ function TLSSession(options){
1920
3247
  }
1921
3248
 
1922
3249
  if(context.isServer==true && context.cert_sent==false && context.local_cert_chain!==null && !context.psk_accepted && !context.tls12_abbreviated){
1923
- if(((context.selected_version === wire.TLS_VERSION.TLS1_3 || context.selected_version === wire.DTLS_VERSION.DTLS1_3) && context.encrypted_exts_sent==true && context.local_handshake_traffic_secret!==null) || ((context.selected_version === wire.TLS_VERSION.TLS1_2 || context.selected_version === wire.DTLS_VERSION.DTLS1_2) && context.hello_sent==true)){
3250
+ // When may we put our Certificate on the wire?
3251
+ // 1.3: it is encrypted, so it follows EncryptedExtensions and needs
3252
+ // the handshake traffic secret to exist.
3253
+ // 1.2: it is cleartext and follows the ServerHello directly.
3254
+ let certDue13 = is13() && context.encrypted_exts_sent === true &&
3255
+ context.local_handshake_traffic_secret !== null;
3256
+ let certDue12 = is12() && context.hello_sent === true;
3257
+
3258
+ if(certDue13 || certDue12){
1924
3259
 
1925
3260
  let message_data = build_tls_message({
1926
3261
  type: 'certificate',
@@ -1931,7 +3266,7 @@ function TLSSession(options){
1931
3266
 
1932
3267
  context.cert_sent=true;
1933
3268
 
1934
- if ((context.selected_version === wire.TLS_VERSION.TLS1_3 || context.selected_version === wire.DTLS_VERSION.DTLS1_3)){
3269
+ if ((is13())){
1935
3270
  ev.emit('message',1,context.message_sent_seq,'certificate',message_data);
1936
3271
  }else{
1937
3272
  ev.emit('message',0,context.message_sent_seq,'certificate',message_data);
@@ -1949,107 +3284,44 @@ function TLSSession(options){
1949
3284
 
1950
3285
 
1951
3286
  //send certificate verify...
1952
- if (context.isServer==true && (context.selected_version === wire.TLS_VERSION.TLS1_3 || context.selected_version === wire.DTLS_VERSION.DTLS1_3)){
3287
+ if (context.isServer==true && (is13())){
1953
3288
  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
3289
 
1955
- let tbsHashName = TLS_CIPHER_SUITES[context.selected_cipher_suite].hash;
3290
+ let tbsHashName = negotiated_hash();
1956
3291
  let tbs_data = build_cert_verify_tbs_with_hash(tbsHashName, true, get_transcript_hash(tbsHashName));
1957
3292
 
1958
- let cert_private_key_obj = crypto.createPrivateKey({
1959
- key: Buffer.from(context.cert_private_key),
1960
- format: 'der',
1961
- type: 'pkcs8',
1962
- });
1963
-
1964
- const SIG = {
1965
- ECDSA_P256_SHA256: 0x0403,
1966
- ECDSA_P384_SHA384: 0x0503,
1967
- ECDSA_P521_SHA512: 0x0603,
1968
- RSA_PSS_SHA256: 0x0804,
1969
- RSA_PSS_SHA384: 0x0805,
1970
- RSA_PSS_SHA512: 0x0806,
1971
- ED25519: 0x0807,
1972
- ED448: 0x0808
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
3293
+ let cert_private_key_obj = local_private_key();
3294
+
3295
+ // Scheme choice and signing belong to session/signing.js, which owns
3296
+ // the SignatureScheme registry for BOTH signing and verification.
3297
+ // This block used to re-declare the scheme constants, re-implement
3298
+ // pick_scheme's candidate/preference logic and re-implement
3299
+ // sign_with_scheme's padding+salt switch — ~90 lines duplicating that
3300
+ // module. It was already drifting: the fix that made RSA-PSS and
3301
+ // EdDSA usable in TLS 1.2 (RFC 8447/8422) landed in signing.js and
3302
+ // never reached this copy. One implementation, used by both roles.
3303
+ let selected_scheme = pick_scheme(
3304
+ wire.TLS_VERSION.TLS1_3,
3305
+ cert_private_key_obj,
3306
+ context.remote_supported_signature_algorithms || []
3307
+ );
1979
3308
 
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);
3309
+ let sig_data = null;
3310
+ if (selected_scheme !== null) {
3311
+ sig_data = sign_with_scheme(
3312
+ wire.TLS_VERSION.TLS1_3,
3313
+ selected_scheme,
3314
+ tbs_data,
3315
+ cert_private_key_obj
3316
+ );
1985
3317
  }
1986
3318
 
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
- ];
1998
-
1999
- let selected_scheme = null;
2000
- for (let s of preference_order) {
2001
- if (context.remote_supported_signature_algorithms.includes(s)==true && candidates.includes(s)==true) {
2002
- selected_scheme = s;
2003
- break;
2004
- }
3319
+ if (selected_scheme === null || !sig_data) {
3320
+ fatalAlert(wire.TLS_ALERT.HANDSHAKE_FAILURE,
3321
+ 'No signature scheme shared with peer for our certificate');
3322
+ return;
2005
3323
  }
2006
3324
 
2007
- let sig_data=null;
2008
-
2009
- switch (selected_scheme) {
2010
- case SIG.ED25519:
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;
2049
- }
2050
-
2051
-
2052
-
2053
3325
  if(sig_data){
2054
3326
 
2055
3327
  let message_data = build_tls_message({
@@ -2083,7 +3355,7 @@ function TLSSession(options){
2083
3355
 
2084
3356
 
2085
3357
  // client/server key exchange - 1.2 only...
2086
- if (context.key_exchange_sent == false && (context.selected_version === wire.TLS_VERSION.TLS1_2 || context.selected_version === wire.DTLS_VERSION.DTLS1_2)) {
3358
+ if (context.key_exchange_sent == false && (is12())) {
2087
3359
  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
3360
 
2089
3361
  if (context.isServer==false && context.remote_hello_done==true) {
@@ -2146,10 +3418,8 @@ function TLSSession(options){
2146
3418
  let transcript_data = concatUint8Arrays(context.transcript);
2147
3419
 
2148
3420
  // Pick scheme matching our cert + server's requested algorithms
2149
- let cert_key_obj = crypto.createPrivateKey({ key: Buffer.from(context.cert_private_key), format: 'der', type: 'pkcs8' });
2150
- let reqAlgs = context.certificateRequestSigAlgs.length > 0
2151
- ? context.certificateRequestSigAlgs
2152
- : context.local_supported_signature_algorithms;
3421
+ let cert_key_obj = local_private_key();
3422
+ let reqAlgs = peer_requested_sig_algs();
2153
3423
  let scheme = pick_scheme(wire.TLS_VERSION.TLS1_2, cert_key_obj, reqAlgs);
2154
3424
  let signature = sign_with_scheme(wire.TLS_VERSION.TLS1_2, scheme, transcript_data, cert_key_obj);
2155
3425
 
@@ -2180,11 +3450,7 @@ function TLSSession(options){
2180
3450
  let tbs_data = concatUint8Arrays([ context.remote_random, context.local_random, params_head ]);
2181
3451
 
2182
3452
 
2183
- let cert_private_key_obj = crypto.createPrivateKey({
2184
- key: Buffer.from(context.cert_private_key),
2185
- format: 'der',
2186
- type: 'pkcs8'
2187
- });
3453
+ let cert_private_key_obj = local_private_key();
2188
3454
 
2189
3455
  let scheme12 = pick_scheme(wire.TLS_VERSION.TLS1_2, cert_private_key_obj, context.remote_supported_signature_algorithms);
2190
3456
 
@@ -2246,7 +3512,7 @@ function TLSSession(options){
2246
3512
  }
2247
3513
 
2248
3514
  //server hello done - 1.2 only...
2249
- if(context.isServer==true && (context.selected_version === wire.TLS_VERSION.TLS1_2 || context.selected_version === wire.DTLS_VERSION.DTLS1_2)){
3515
+ if(context.isServer==true && (is12())){
2250
3516
  if(context.hello_done_sent==false && context.key_exchange_sent==true){
2251
3517
 
2252
3518
  let message_data = build_tls_message({
@@ -2351,10 +3617,31 @@ function TLSSession(options){
2351
3617
  context.message_sent_seq++;
2352
3618
 
2353
3619
  // Send CertificateVerify
2354
- let hashName = TLS_CIPHER_SUITES[context.selected_cipher_suite].hash;
3620
+ let hashName = negotiated_hash();
2355
3621
  let transcript_hash = get_transcript_hash(hashName);
2356
- let scheme = pick_scheme(context.certificateRequestSigAlgs.length > 0 ? context.certificateRequestSigAlgs : context.local_supported_signature_algorithms, certCtx.privateKey);
2357
- let signature = sign_with_scheme(scheme, certCtx.privateKey, transcript_hash, false);
3622
+ // Both calls had the wrong arity/order: pick_scheme takes
3623
+ // (version, keyObject, peerSupportedList) and sign_with_scheme takes
3624
+ // (version, scheme, tbs, keyObject). They were being passed a raw
3625
+ // DER key and shuffled arguments, so client-certificate auth could
3626
+ // never produce a valid signature.
3627
+ let clientKeyObj = crypto.createPrivateKey({
3628
+ key: Buffer.from(certCtx.privateKey),
3629
+ format: 'der',
3630
+ type: 'pkcs8',
3631
+ });
3632
+ let peerSigAlgs = peer_requested_sig_algs();
3633
+
3634
+ let scheme = pick_scheme(wire.TLS_VERSION.TLS1_3, clientKeyObj, peerSigAlgs);
3635
+ if (scheme === null) {
3636
+ fatalAlert(wire.TLS_ALERT.HANDSHAKE_FAILURE, 'No signature scheme shared with server for client certificate');
3637
+ return;
3638
+ }
3639
+
3640
+ // TBS uses the CLIENT CertificateVerify label, over the transcript
3641
+ // hash taken before this message is pushed.
3642
+ let cvHash = negotiated_hash();
3643
+ let clientTbs = build_cert_verify_tbs_with_hash(cvHash, false, get_transcript_hash(cvHash));
3644
+ let signature = sign_with_scheme(wire.TLS_VERSION.TLS1_3, scheme, clientTbs, clientKeyObj);
2358
3645
  let cv_data = build_tls_message({
2359
3646
  type: 'certificate_verify',
2360
3647
  scheme: scheme,
@@ -2381,12 +3668,45 @@ function TLSSession(options){
2381
3668
  // base_secret may be null after app secrets are derived, so we also check handshake secret.
2382
3669
  if (context.finished_sent==false && context.selected_cipher_suite!==null && (context.base_secret!==null || context.local_handshake_traffic_secret!==null)){
2383
3670
 
2384
- if((context.selected_version === wire.TLS_VERSION.TLS1_3 || context.selected_version === wire.DTLS_VERSION.DTLS1_3) && context.local_handshake_traffic_secret!==null){
2385
-
2386
- if((context.isServer==false && context.remote_finished_ok==true && context.local_app_traffic_secret!==null && context.remote_app_traffic_secret!==null) || (context.isServer==true && context.cert_verify_sent==true && context.local_cert_chain!==null) || (context.isServer==true && context.psk_accepted==true && context.encrypted_exts_sent==true)){
2387
-
2388
- let finHashName = TLS_CIPHER_SUITES[context.selected_cipher_suite].hash;
2389
- let finished_data=get_handshake_finished_with_hash(finHashName,context.local_handshake_traffic_secret,get_transcript_hash(finHashName));
3671
+ if((is13()) && context.local_handshake_traffic_secret!==null){
3672
+
3673
+ // When is our Finished due (TLS 1.3, RFC 8446 §4.4.4)? Three distinct
3674
+ // situations, one per role/handshake kind — named rather than ORed
3675
+ // inline so each stays readable and independently checkable.
3676
+ //
3677
+ // client: only after the server's Finished verified AND both
3678
+ // application secrets exist (ours is the last message of
3679
+ // the handshake).
3680
+ // server, certificate handshake: after our CertificateVerify.
3681
+ // server, PSK handshake: no Certificate/CertificateVerify at all,
3682
+ // so EncryptedExtensions is the predecessor.
3683
+ let clientFinishedDue = context.isServer === false &&
3684
+ context.remote_finished_ok === true &&
3685
+ context.local_app_traffic_secret !== null &&
3686
+ context.remote_app_traffic_secret !== null;
3687
+
3688
+ let serverCertFinishedDue = context.isServer === true &&
3689
+ context.cert_verify_sent === true &&
3690
+ context.local_cert_chain !== null;
3691
+
3692
+ let serverPskFinishedDue = context.isServer === true &&
3693
+ context.psk_accepted === true &&
3694
+ context.encrypted_exts_sent === true;
3695
+
3696
+ if(clientFinishedDue || serverCertFinishedDue || serverPskFinishedDue){
3697
+
3698
+ let finHashName = negotiated_hash();
3699
+ // THE LABEL PREFIX BELONGS IN THE *FOURTH* ARGUMENT. It was being
3700
+ // passed to get_transcript_hash(), which takes only a hash name and
3701
+ // silently ignored it — so get_handshake_finished_with_hash received
3702
+ // labelPrefix=undefined and derived finished_key with the TLS 1.3
3703
+ // prefix "tls13 " instead of DTLS 1.3's "dtls13" (RFC 9147 §5.9).
3704
+ // Both peers running this library make the same substitution and
3705
+ // their Finished MACs agree, so it is invisible in a self-test and
3706
+ // shows up only against a conforming peer as
3707
+ // "Finished verify_data mismatch" — after every message parsed and
3708
+ // every signature verified.
3709
+ let finished_data=get_handshake_finished_with_hash(finHashName,context.local_handshake_traffic_secret,get_transcript_hash(finHashName), label_prefix());
2390
3710
  context.local_finished_data = finished_data;
2391
3711
 
2392
3712
  let message_data = build_tls_message({
@@ -2396,6 +3716,13 @@ function TLSSession(options){
2396
3716
 
2397
3717
  pushTranscript(message_data);
2398
3718
 
3719
+ // Checkpoint for app-secret derivation (see tls13_app_transcript_hash).
3720
+ // On the SERVER our own Finished IS the server Finished, so the
3721
+ // transcript is now exactly ClientHello..server Finished.
3722
+ if (context.isServer && context.tls13_app_transcript_hash === null) {
3723
+ context.tls13_app_transcript_hash = get_transcript_hash(finHashName);
3724
+ }
3725
+
2399
3726
  context.finished_sent=true;
2400
3727
 
2401
3728
  ev.emit('message',1,context.message_sent_seq,'finished',message_data);
@@ -2404,7 +3731,7 @@ function TLSSession(options){
2404
3731
 
2405
3732
  }
2406
3733
 
2407
- }else if((context.selected_version === wire.TLS_VERSION.TLS1_2 || context.selected_version === wire.DTLS_VERSION.DTLS1_2)){
3734
+ }else if((is12())){
2408
3735
 
2409
3736
  // Finished ordering differs between full and abbreviated handshake:
2410
3737
  // Full: client sends first (after CKE), then server (after client's Finished).
@@ -2426,7 +3753,7 @@ function TLSSession(options){
2426
3753
 
2427
3754
  if (can_send_finished) {
2428
3755
 
2429
- let finishedHashName = TLS_CIPHER_SUITES[context.selected_cipher_suite].hash;
3756
+ let finishedHashName = negotiated_hash();
2430
3757
  let transcript_hash = get_transcript_hash(finishedHashName);
2431
3758
 
2432
3759
  let finished_data;
@@ -2457,14 +3784,26 @@ function TLSSession(options){
2457
3784
  }
2458
3785
 
2459
3786
  //get app traffic secret...
2460
- if ((context.selected_version === wire.TLS_VERSION.TLS1_3 || context.selected_version === wire.DTLS_VERSION.DTLS1_3)){
3787
+ if ((is13())){
2461
3788
  if(context.base_secret!==null && context.local_app_traffic_secret==null && context.remote_app_traffic_secret==null){
2462
3789
 
2463
3790
  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
3791
 
2465
- let appHashName = TLS_CIPHER_SUITES[context.selected_cipher_suite].hash;
2466
- let appTranscriptHash = get_transcript_hash(appHashName);
2467
- let result2 = derive_app_traffic_secrets_with_hash(appHashName, context.base_secret, appTranscriptHash);
3792
+ let appHashName = negotiated_hash();
3793
+ // Use the checkpoint captured when the server's Finished entered the
3794
+ // transcript. Reading the live transcript here would silently pick up
3795
+ // anything the loop emitted in between (client Certificate /
3796
+ // CertificateVerify), producing app keys the peer never derives.
3797
+ let appTranscriptHash = context.tls13_app_transcript_hash;
3798
+ if (appTranscriptHash === null) {
3799
+ // No checkpoint means we reached derivation without either side's
3800
+ // Finished passing through the transcript — a state-machine bug,
3801
+ // not a peer error. Refuse rather than derive from a guess.
3802
+ fatalAlert(wire.TLS_ALERT.INTERNAL_ERROR,
3803
+ 'Internal error: app traffic secrets requested before the server Finished transcript checkpoint');
3804
+ return;
3805
+ }
3806
+ let result2 = derive_app_traffic_secrets_with_hash(appHashName, context.base_secret, appTranscriptHash, label_prefix());
2468
3807
 
2469
3808
  // Save master_secret for resumption before clearing
2470
3809
  params_to_set['tls13_master_secret'] = result2.master_secret;
@@ -2474,7 +3813,7 @@ function TLSSession(options){
2474
3813
  // Kept for the lifetime of the connection so exportKeyingMaterial
2475
3814
  // (RFC 8446 §7.5) works after handshake completion.
2476
3815
  params_to_set['exporter_master_secret'] =
2477
- derive_exporter_master_secret_with_hash(appHashName, result2.master_secret, appTranscriptHash);
3816
+ derive_exporter_master_secret_with_hash(appHashName, result2.master_secret, appTranscriptHash, label_prefix());
2478
3817
  params_to_set['base_secret']=null;
2479
3818
 
2480
3819
  if (context.isServer === true) {
@@ -2492,21 +3831,84 @@ function TLSSession(options){
2492
3831
  //expected_remote_finished...
2493
3832
  if (context.expected_remote_finished==null && context.selected_cipher_suite!==null){
2494
3833
 
2495
- if((context.selected_version === wire.TLS_VERSION.TLS1_3 || context.selected_version === wire.DTLS_VERSION.DTLS1_3) && context.remote_handshake_traffic_secret!==null){
2496
-
2497
- if((context.isServer==true && context.finished_sent==true) || (context.isServer==false && context.remote_finished !== null)){
2498
-
2499
- let remoteFinHashName = TLS_CIPHER_SUITES[context.selected_cipher_suite].hash;
2500
- params_to_set['expected_remote_finished']=get_handshake_finished_with_hash(remoteFinHashName,context.remote_handshake_traffic_secret,get_transcript_hash(remoteFinHashName));
3834
+ if((is13()) && context.remote_handshake_traffic_secret!==null){
3835
+
3836
+ // Compute the expected peer Finished only once the peer's Finished
3837
+ // has actually arrived — for BOTH roles.
3838
+ //
3839
+ // verify_data is a MAC over the transcript as it stands immediately
3840
+ // before the peer's Finished, so the value is only well-defined at
3841
+ // that moment. The server used to compute it as soon as it had sent
3842
+ // its OWN Finished, which is a different point in the transcript:
3843
+ // with client authentication the client's Certificate and
3844
+ // CertificateVerify arrive in between, so the server's expectation
3845
+ // was fixed too early and every client-auth handshake failed with a
3846
+ // verify_data mismatch. (Without client auth the two points coincide,
3847
+ // which is why it went unnoticed.) The client already did this
3848
+ // lazily; both roles now share the same rule.
3849
+ if(context.remote_finished !== null){
3850
+
3851
+ let remoteFinHashName = negotiated_hash();
3852
+ // Same fix as the local Finished above: the prefix is the FOURTH
3853
+ // argument, not an argument to get_transcript_hash().
3854
+ params_to_set['expected_remote_finished']=get_handshake_finished_with_hash(remoteFinHashName,context.remote_handshake_traffic_secret,get_transcript_hash(remoteFinHashName), label_prefix());
3855
+
3856
+ // ── DIAGNOSTIC (LEMON_DEBUG=1 or WEBRTC_DEBUG=1) ──────────────
3857
+ // verify_data is a MAC over three inputs, and a mismatch means
3858
+ // ONE of them differs from what the peer computed. Two peers
3859
+ // running this same library make the SAME choice and agree with
3860
+ // each other, so a self-test can never expose it — only a real
3861
+ // peer (BoringSSL) can. Print all three so the difference is
3862
+ // visible instead of inferred:
3863
+ // • which handshake messages went into the transcript, in
3864
+ // order, with their lengths — a missing or duplicated
3865
+ // message is the usual cause;
3866
+ // • the transcript hash and the label prefix used;
3867
+ // • both verify_data values.
3868
+ try {
3869
+ if (typeof process !== 'undefined' && process.env &&
3870
+ (process.env.LEMON_DEBUG === '1' || process.env.WEBRTC_DEBUG === '1')) {
3871
+ var _hsNames = { 1:'ClientHello', 2:'ServerHello', 4:'NewSessionTicket',
3872
+ 5:'EndOfEarlyData', 8:'EncryptedExtensions', 11:'Certificate',
3873
+ 13:'CertificateRequest', 15:'CertificateVerify',
3874
+ 20:'Finished', 24:'KeyUpdate', 254:'MessageHash' };
3875
+ var _t = context.transcript || [];
3876
+ var _list = [];
3877
+ for (var _ti = 0; _ti < _t.length; _ti++) {
3878
+ var _e = _t[_ti];
3879
+ if (!_e || !_e.length) { _list.push('?'); continue; }
3880
+ _list.push((_hsNames[_e[0]] || ('type' + _e[0])) + '(' + _e.length + ')');
3881
+ }
3882
+ var _hex = function (u8) {
3883
+ if (!u8) return 'null';
3884
+ var s = '';
3885
+ for (var _i = 0; _i < u8.length && _i < 16; _i++) {
3886
+ s += ('0' + u8[_i].toString(16)).slice(-2);
3887
+ }
3888
+ return s + (u8.length > 16 ? '..' : '');
3889
+ };
3890
+ console.log('[lemon-fin] role=' + (context.is_server ? 'server' : 'client') +
3891
+ ' version=0x' + (context.selected_version || 0).toString(16) +
3892
+ ' labelPrefix="' + label_prefix() + '"' +
3893
+ ' hash=' + remoteFinHashName);
3894
+ console.log('[lemon-fin] transcript(' + _t.length + '): ' + _list.join(' → '));
3895
+ console.log('[lemon-fin] transcriptHash=' +
3896
+ _hex(get_transcript_hash(remoteFinHashName, label_prefix())));
3897
+ console.log('[lemon-fin] remoteSecret=' + _hex(context.remote_handshake_traffic_secret));
3898
+ console.log('[lemon-fin] peerSent=' + _hex(context.remote_finished));
3899
+ console.log('[lemon-fin] weExpect=' + _hex(params_to_set['expected_remote_finished']));
3900
+ }
3901
+ } catch (_eDiag) {}
3902
+ // ─────────────────────────────────────────────────────────────
2501
3903
 
2502
3904
  }
2503
3905
 
2504
- }else if((context.selected_version === wire.TLS_VERSION.TLS1_2 || context.selected_version === wire.DTLS_VERSION.DTLS1_2) && context.base_secret!==null){
3906
+ }else if((is12()) && context.base_secret!==null){
2505
3907
 
2506
3908
  if(context.remote_finished!==null){
2507
3909
 
2508
3910
 
2509
- let tls12FinHashName = TLS_CIPHER_SUITES[context.selected_cipher_suite].hash;
3911
+ let tls12FinHashName = negotiated_hash();
2510
3912
  let transcript_hash = get_transcript_hash(tls12FinHashName);
2511
3913
 
2512
3914
  if(context.isServer==true){
@@ -2530,6 +3932,16 @@ function TLSSession(options){
2530
3932
  //compare finished to expected...
2531
3933
  if(context.remote_finished_ok==false && context.remote_finished!==null && context.expected_remote_finished!==null){
2532
3934
 
3935
+ // RFC 8446 §4.4.4: verify_data has exactly Hash.length bytes. A body
3936
+ // of any other length (BoGo: TrailingDataWithFinished) is a framing
3937
+ // violation — decode_error, checked BEFORE the value comparison so the
3938
+ // constant-time compare below always runs on equal-length inputs.
3939
+ if (context.remote_finished.length !== context.expected_remote_finished.length) {
3940
+ fatalAlert(wire.TLS_ALERT.DECODE_ERROR, 'Finished verify_data has wrong length (' +
3941
+ context.remote_finished.length + ', expected ' + context.expected_remote_finished.length + ')');
3942
+ return;
3943
+ }
3944
+
2533
3945
  // Constant-time Finished comparison — verify_data is a MAC; a timing
2534
3946
  // oracle here would let a peer forge a valid Finished byte by byte.
2535
3947
  if(timingSafeEqualU8(context.remote_finished, context.expected_remote_finished)==true){
@@ -2541,6 +3953,18 @@ function TLSSession(options){
2541
3953
 
2542
3954
  pushTranscript(message_data);
2543
3955
 
3956
+ // Checkpoint for app-secret derivation (see tls13_app_transcript_hash).
3957
+ // On the CLIENT the peer's Finished IS the server's Finished, so the
3958
+ // transcript is now exactly ClientHello..server Finished. Capture it
3959
+ // here, before the loop can append our own Certificate/CertificateVerify.
3960
+ if (!context.isServer && context.selected_cipher_suite !== null &&
3961
+ (context.selected_version === wire.TLS_VERSION.TLS1_3 ||
3962
+ context.selected_version === wire.DTLS_VERSION.DTLS1_3) &&
3963
+ context.tls13_app_transcript_hash === null) {
3964
+ let ckHash = negotiated_hash();
3965
+ context.tls13_app_transcript_hash = get_transcript_hash(ckHash);
3966
+ }
3967
+
2544
3968
  params_to_set['remote_finished_ok']=true;
2545
3969
 
2546
3970
  context.remote_finished_data = context.remote_finished;
@@ -2550,7 +3974,14 @@ function TLSSession(options){
2550
3974
 
2551
3975
 
2552
3976
  }else{
3977
+ // RFC 8446 §4.4.4: "Recipients of Finished messages MUST verify
3978
+ // that the contents are correct and if incorrect MUST terminate the
3979
+ // connection with a decrypt_error alert." The old behavior (null it
3980
+ // out and keep waiting) left the peer hanging with a half-open
3981
+ // handshake — and hid real key-schedule bugs.
2553
3982
  context.remote_finished=null;
3983
+ fatalAlert(wire.TLS_ALERT.DECRYPT_ERROR, 'Finished verify_data mismatch');
3984
+ return;
2554
3985
  }
2555
3986
 
2556
3987
  }
@@ -2558,26 +3989,86 @@ function TLSSession(options){
2558
3989
 
2559
3990
 
2560
3991
 
2561
- if(context.state!=='connected' && context.remote_finished_ok==true && (((context.selected_version === wire.TLS_VERSION.TLS1_3 || context.selected_version === wire.DTLS_VERSION.DTLS1_3) && context.local_app_traffic_secret!==null && context.remote_app_traffic_secret!==null) || (context.selected_version === wire.TLS_VERSION.TLS1_2 || context.selected_version === wire.DTLS_VERSION.DTLS1_2))){
3992
+ if(context.state!=='connected' && context.remote_finished_ok==true && (((is13()) && context.local_app_traffic_secret!==null && context.remote_app_traffic_secret!==null) || (is12()))){
3993
+
3994
+ // ── Authentication gate ──
3995
+ // Peer authentication is enforced HERE, at the single point where the
3996
+ // handshake is declared complete, and deliberately not (only) in the
3997
+ // per-message branches. A message-level check can be bypassed simply by
3998
+ // OMITTING the message: BoGo's SkipClientCertificate does exactly that,
3999
+ // and an "empty certificate_list" check in the Certificate branch never
4000
+ // runs because no Certificate ever arrives. In a reactive state machine
4001
+ // the durable place for a MUST-have-happened invariant is the state
4002
+ // transition it guards, not the event that usually satisfies it.
4003
+
4004
+ // Client: the server must have proved possession of its certificate
4005
+ // key — TLS 1.3 CertificateVerify, or the TLS 1.2 ServerKeyExchange
4006
+ // signature. PSK/abbreviated handshakes authenticate via the binder /
4007
+ // resumed master secret instead.
4008
+ if (!context.isServer && !context.psk_accepted && !context.tls12_abbreviated &&
4009
+ context.peerSignatureVerified !== true) {
4010
+ fatalAlert(wire.TLS_ALERT.UNEXPECTED_MESSAGE,
4011
+ 'Handshake completed without a verified server signature');
4012
+ return;
4013
+ }
4014
+
4015
+ // Server-side client authentication. Two DIFFERENT questions, which
4016
+ // were previously collapsed into one condition:
4017
+ //
4018
+ // (a) Must a client certificate arrive at all? -> policy, and only
4019
+ // when we both asked for one and refuse to proceed without it.
4020
+ // (b) If a certificate DID arrive, must it be proved? -> ALWAYS.
4021
+ //
4022
+ // Collapsing them meant a server with requestCert but without
4023
+ // rejectUnauthorized skipped the whole block, so a client could send a
4024
+ // certificate — any certificate, including one it does not own — and
4025
+ // never prove possession, because the CertificateVerify check lived
4026
+ // behind the (a) policy flag. Presenting someone else's certificate is
4027
+ // an impersonation, not a policy preference, so (b) cannot be optional.
4028
+ if (context.isServer && !context.psk_accepted && !context.tls12_abbreviated) {
4029
+
4030
+ let clientCertPresent = !!(context.remote_cert_chain && context.remote_cert_chain.length > 0);
4031
+
4032
+ // (a) required-but-absent
4033
+ if (context.requestCert && context.rejectUnauthorized && !clientCertPresent) {
4034
+ fatalAlert(wire.TLS_ALERT.CERTIFICATE_REQUIRED,
4035
+ 'Client certificate was required but none was provided');
4036
+ return;
4037
+ }
4038
+
4039
+ // (b) present-but-unproved. Unconditional: it applies even when the
4040
+ // certificate was optional, because the question is not "did we want
4041
+ // one" but "does this peer own the one it sent". In TLS 1.2 the
4042
+ // client's CertificateVerify sets peerSignatureVerified the same way.
4043
+ if (clientCertPresent &&
4044
+ (context.selected_version === wire.TLS_VERSION.TLS1_3 ||
4045
+ context.selected_version === wire.DTLS_VERSION.DTLS1_3) &&
4046
+ context.peerSignatureVerified !== true) {
4047
+ fatalAlert(wire.TLS_ALERT.UNEXPECTED_MESSAGE,
4048
+ 'Client sent a certificate without a valid CertificateVerify');
4049
+ return;
4050
+ }
4051
+ }
4052
+
2562
4053
  context.state='connected';
2563
4054
  context.handshakeEndTime = Date.now();
2564
4055
  ev.emit('secureConnect');
2565
4056
 
2566
4057
  // TLS 1.3: compute resumption_master_secret (both client and server need it)
2567
- if ((context.selected_version === wire.TLS_VERSION.TLS1_3 || context.selected_version === wire.DTLS_VERSION.DTLS1_3) && context.tls13_master_secret && !context.resumption_master_secret) {
2568
- let hashName = TLS_CIPHER_SUITES[context.selected_cipher_suite].hash;
4058
+ if ((is13()) && context.tls13_master_secret && !context.resumption_master_secret) {
4059
+ let hashName = negotiated_hash();
2569
4060
  context.resumption_master_secret = derive_resumption_master_secret_with_hash(
2570
- hashName, context.tls13_master_secret, get_transcript_hash(hashName)
4061
+ hashName, context.tls13_master_secret, get_transcript_hash(hashName, label_prefix())
2571
4062
  );
2572
4063
  }
2573
4064
 
2574
4065
  // TLS 1.3 server: send NewSessionTicket
2575
- if ((context.selected_version === wire.TLS_VERSION.TLS1_3 || context.selected_version === wire.DTLS_VERSION.DTLS1_3) && context.isServer && !context.session_ticket_sent && context.sessionTickets && context.resumption_master_secret) {
4066
+ if ((is13()) && context.isServer && !context.session_ticket_sent && context.sessionTickets && context.resumption_master_secret) {
2576
4067
  context.session_ticket_sent = true;
2577
4068
 
2578
- let hashName = TLS_CIPHER_SUITES[context.selected_cipher_suite].hash;
4069
+ let hashName = negotiated_hash();
2579
4070
  let ticket_nonce = new Uint8Array([context.ticket_nonce_counter++]);
2580
- let psk = derive_psk(hashName, context.resumption_master_secret, ticket_nonce);
4071
+ let psk = derive_psk(hashName, context.resumption_master_secret, ticket_nonce, label_prefix());
2581
4072
  let ticket_age_add = crypto.randomBytes(4).readUInt32BE(0);
2582
4073
  let ticket_lifetime = context.ticketLifetime;
2583
4074
 
@@ -2743,24 +4234,31 @@ function TLSSession(options){
2743
4234
  }
2744
4235
  }
2745
4236
 
2746
- // Verify against CA if provided
2747
- if (context.ca) {
2748
- let cas = Array.isArray(context.ca) ? context.ca : [context.ca];
2749
- let verified = false;
2750
- for (let i = 0; i < cas.length; i++) {
2751
- try {
2752
- let caX509 = new crypto.X509Certificate(cas[i]);
2753
- if (x509.checkIssued(caX509) && x509.verify(caX509.publicKey)) {
2754
- verified = true;
2755
- break;
2756
- }
2757
- } catch(e) { /* try next CA */ }
2758
- }
2759
- if (!verified) {
2760
- context.authorizationError = 'UNABLE_TO_VERIFY_LEAF_SIGNATURE';
2761
- context.peerAuthorized = false;
2762
- return;
2763
- }
4237
+ // Verify the full chain against the configured trust anchors.
4238
+ //
4239
+ // NOTE the `context.ca == null` branch. Previously the entire check was
4240
+ // wrapped in `if (context.ca)`, so "no CA configured" meant "authorize
4241
+ // everything" — while rejectUnauthorized defaulted to true, which reads
4242
+ // as though verification were on. That is a MITM hole for any plain-TLS
4243
+ // consumer of this library. It is preserved here ONLY because callers
4244
+ // (notably the WebRTC/DTLS path, which authenticates by SDP fingerprint
4245
+ // and sets rejectUnauthorized:false) may rely on it — but it is now
4246
+ // explicit, recorded in authorizationError, and trivial to flip.
4247
+ // See LEMON_TLS_INSECURE_NO_CA below.
4248
+ if (context.ca == null ||
4249
+ (Array.isArray(context.ca) && context.ca.length === 0)) {
4250
+ context.peerAuthorized = true;
4251
+ context.authorizationError = null;
4252
+ context.certChainUnverified = true; // no trust anchors were available
4253
+ return;
4254
+ }
4255
+
4256
+ let cas = Array.isArray(context.ca) ? context.ca : [context.ca];
4257
+ let chainResult = verify_cert_chain(context.remote_cert_chain, cas, now);
4258
+ if (!chainResult.ok) {
4259
+ context.authorizationError = chainResult.error;
4260
+ context.peerAuthorized = false;
4261
+ return;
2764
4262
  }
2765
4263
 
2766
4264
  // All checks passed
@@ -2775,9 +4273,46 @@ function TLSSession(options){
2775
4273
 
2776
4274
 
2777
4275
  function sendAlert(level, description) {
4276
+ // Nothing may follow a fatal alert (RFC 8446 §6.2). The guard lives here,
4277
+ // at the single point every alert leaves through, rather than at each
4278
+ // caller — a caller that forgets is exactly how the stray close_notify
4279
+ // arose. `fatalAlert` has its own early return for the same reason; this
4280
+ // covers the warning-level paths too.
4281
+ if (context.state === 'error' || context.state === 'closed') return;
4282
+
2778
4283
  let alertData = new Uint8Array([level, description]);
2779
- // Epoch 0 for alerts during/before handshake
2780
- let epoch = (context.state === 'connected') ? 2 : 0;
4284
+
4285
+ // Alerts must go out at the protection level the PEER is currently reading
4286
+ // at, otherwise they are unreadable and the real reason for the abort is
4287
+ // lost behind a decrypt failure.
4288
+ //
4289
+ // epoch 0 — cleartext: before any keys exist, and for all of TLS 1.2's
4290
+ // handshake (alerts stay cleartext until CCS).
4291
+ // epoch 1 — handshake keys: TLS 1.3 after ServerHello but before our
4292
+ // Finished has been sent. This case was missing entirely.
4293
+ // epoch 2 — application keys.
4294
+ //
4295
+ // The subtlety is that the right answer depends on what the PEER reads,
4296
+ // not on whether WE consider the handshake finished. In TLS 1.3 a server
4297
+ // that has already sent its Finished has handed the client everything it
4298
+ // needs to switch to application keys, and the client does so immediately —
4299
+ // so an abort discovered afterwards (a missing client Certificate is
4300
+ // discovered exactly there) must be encrypted under application keys even
4301
+ // though our own state is not yet 'connected'. Encrypting it under
4302
+ // handshake keys produced an undecryptable record and the peer reported a
4303
+ // decrypt error instead of certificate_required.
4304
+ let epoch;
4305
+ if (context.state === 'connected') {
4306
+ epoch = 2;
4307
+ } else if (is13() && context.local_app_traffic_secret !== null && context.finished_sent) {
4308
+ epoch = 2;
4309
+ } else if (is13() && context.local_handshake_traffic_secret !== null) {
4310
+ epoch = 1;
4311
+ } else {
4312
+ epoch = 0;
4313
+ }
4314
+
4315
+
2781
4316
  ev.emit('message', epoch, 0, 'alert', alertData);
2782
4317
  ev.emit('alert', { level: level, description: description });
2783
4318
  if (level === 2) {
@@ -2787,8 +4322,17 @@ function TLSSession(options){
2787
4322
  }
2788
4323
 
2789
4324
  function close(){
2790
- if (context.state === 'closed') return;
2791
- // Send close_notify (warning level, description 0)
4325
+ // 'error' counts as closed. RFC 8446 §6.2: after a fatal alert the
4326
+ // connection is torn down immediately and NO further record may be sent.
4327
+ // close_notify signals an ORDERLY shutdown, so emitting one after a failure
4328
+ // both contradicts the alert we just sent and appends a record the peer is
4329
+ // not expecting — it reads the fatal alert, then hits an extra record and
4330
+ // reports a decrypt failure instead of the reason we actually gave it.
4331
+ // Testing only for 'closed' missed exactly this case.
4332
+ if (context.state === 'closed' || context.state === 'error') {
4333
+ context.state = 'closed';
4334
+ return;
4335
+ }
2792
4336
  sendAlert(1, 0);
2793
4337
  context.state = 'closed';
2794
4338
  }
@@ -2810,44 +4354,69 @@ function TLSSession(options){
2810
4354
  // (RFC 9001), and strict 1.3-only stacks reject hellos with legacy
2811
4355
  // artifacts. Detected from the configured versions, so plain TLS over
2812
4356
  // TCP (which offers 1.3+1.2) keeps the full compatibility behavior.
2813
- let tls13Only = context.local_supported_versions.length > 0 &&
2814
- context.local_supported_versions.every(v => v === 0x0304);
4357
+ // Which versions does this profile actually offer? The suite list must
4358
+ // follow BOTH answers, not just one of them.
4359
+ //
4360
+ // The old form asked only "is this 1.3-only?" and hardcoded the 1.3 half
4361
+ // to true, so a 1.2-ONLY profile still advertised 0x1301..0x1303. Those
4362
+ // suites cannot be negotiated at 1.2 — a TLS 1.3 suite is meaningless in
4363
+ // a 1.2 handshake — so the hello claimed capabilities the profile had
4364
+ // ruled out. The asymmetry was invisible because only the 1.3-only case
4365
+ // had ever been considered.
4366
+ let offers13 = context.local_supported_versions.length === 0 ||
4367
+ context.local_supported_versions.indexOf(wire.TLS_VERSION.TLS1_3) >= 0 ||
4368
+ context.local_supported_versions.indexOf(wire.DTLS_VERSION.DTLS1_3) >= 0;
4369
+ let offers12 = context.local_supported_versions.length === 0 ||
4370
+ context.local_supported_versions.some(v => v !== wire.TLS_VERSION.TLS1_3 &&
4371
+ v !== wire.DTLS_VERSION.DTLS1_3);
2815
4372
 
2816
- // Support both TLS 1.3 and 1.2 (server picks the best)
2817
4373
  if(context.local_supported_cipher_suites.length<=0){
2818
- context.local_supported_cipher_suites = tls13Only ? [
2819
- // TLS 1.3 only — no ECDHE 1.2 suites in a QUIC hello
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
- ];
4374
+ // crypto.js owns the default suite list; we only say which halves apply.
4375
+ context.local_supported_cipher_suites = default_cipher_suites(offers13, offers12);
2834
4376
  }
2835
4377
 
2836
4378
  if(context.local_supported_groups.length<=0){
2837
- context.local_supported_groups=[0x001d, 0x0017]; // X25519, P-256
4379
+ context.local_supported_groups = ECDH_SUPPORTED_GROUPS.slice(); // whatever ecdh.js implements
2838
4380
  }
2839
4381
 
2840
4382
  if(context.local_supported_versions.length<=0){
2841
4383
  context.local_supported_versions=[0x0304, 0x0303]; // TLS 1.3, TLS 1.2
2842
4384
  }
2843
4385
 
2844
- // Generate X25519 keypair for key_share (TLS 1.3) and ECDHE (TLS 1.2)
2845
- const private_key = new Uint8Array(crypto.randomBytes(32));
2846
- let public_key = x25519_get_public_key(private_key);
4386
+ // Initial key_share: generate for our most-preferred group that we can
4387
+ // actually PRODUCE a share for. If the server wants a different one it
4388
+ // sends HelloRetryRequest, handled above.
4389
+ //
4390
+ // The list may name groups ecdh.js does not implement — a configured
4391
+ // curve list is not a capability list, and a post-quantum group can sit
4392
+ // in it because a caller passed it straight through. generate_keypair
4393
+ // returns null for those (it does not throw), and this code used to read
4394
+ // `.public_key` off that null and crash the whole session with a
4395
+ // TypeError before a single byte was sent. Skip what we cannot make; if
4396
+ // that leaves nothing, say so plainly rather than dying.
4397
+ let initialGroup = null;
4398
+ let initialKp = null;
4399
+ for (let gi = 0; gi < context.local_supported_groups.length; gi++) {
4400
+ let g = context.local_supported_groups[gi];
4401
+ if (!is_supported_group(g)) continue;
4402
+ let kp = ecdh_generate_keypair(g);
4403
+ if (!kp) continue;
4404
+ initialGroup = g;
4405
+ initialKp = kp;
4406
+ break;
4407
+ }
4408
+
4409
+ if (initialKp === null) {
4410
+ fatalAlert(wire.TLS_ALERT.INTERNAL_ERROR,
4411
+ 'No configured key-exchange group is implemented — cannot build a key_share');
4412
+ return;
4413
+ }
2847
4414
 
2848
- context.local_key_groups[0x001d]={
2849
- public_key: public_key,
2850
- private_key: private_key
4415
+ let public_key = initialKp.public_key;
4416
+
4417
+ context.local_key_groups[initialGroup]={
4418
+ public_key: initialKp.public_key,
4419
+ private_key: initialKp.private_key
2851
4420
  };
2852
4421
 
2853
4422
  // Honor the configured signature algorithms; the hardcoded list is only
@@ -2857,14 +4426,7 @@ function TLSSession(options){
2857
4426
  let ch_sigalgs = (context.local_supported_signature_algorithms &&
2858
4427
  context.local_supported_signature_algorithms.length > 0)
2859
4428
  ? 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
- ];
4429
+ : default_signature_schemes(); // signing.js owns the list
2868
4430
  context.local_supported_signature_algorithms = ch_sigalgs; // for CH2 reuse
2869
4431
 
2870
4432
  // supported_versions (43) and key_share (51) are the TLS 1.3
@@ -2879,59 +4441,13 @@ function TLSSession(options){
2879
4441
  // at least one deployed stack (webrtc-dtls ≤0.7 hard-errors on any
2880
4442
  // extension outside its known set, then poisons its replay window
2881
4443
  // so no retransmission can ever recover the handshake).
2882
- let offers13 = context.local_supported_versions.some(function (v) {
2883
- return v === wire.TLS_VERSION.TLS1_3 || v === wire.DTLS_VERSION.DTLS1_3;
4444
+ // Same assembler as the post-HelloRetryRequest CH2 (RFC 8446 §4.1.2
4445
+ // requires the two hellos to be identical apart from key_share/cookie).
4446
+ let extensions = build_client_hello_extensions({
4447
+ keyShareGroup: initialGroup,
4448
+ keySharePublic: public_key,
4449
+ cookie: null
2884
4450
  });
2885
- let extensions = [];
2886
- if (offers13) {
2887
- extensions.push({
2888
- type: 'SUPPORTED_VERSIONS',
2889
- value: context.local_supported_versions
2890
- });
2891
- }
2892
- extensions.push({
2893
- type: 'SUPPORTED_GROUPS',
2894
- value: context.local_supported_groups
2895
- });
2896
- if (offers13) {
2897
- extensions.push({
2898
- type: 'KEY_SHARE',
2899
- value: [{
2900
- group: 0x001d,
2901
- key_exchange: public_key
2902
- }]
2903
- });
2904
- }
2905
- extensions.push({
2906
- type: 'SIGNATURE_ALGORITHMS',
2907
- value: ch_sigalgs
2908
- });
2909
- if (!tls13Only) {
2910
- // TLS 1.2 compatibility — meaningless (and suspicious to strict
2911
- // stacks) in a 1.3-only/QUIC hello
2912
- extensions.push({ type: 'RENEGOTIATION_INFO', value: new Uint8Array(0) });
2913
- extensions.push({ type: 'EXTENDED_MASTER_SECRET', value: null });
2914
- } else {
2915
- // psk_key_exchange_modes: every mainstream 1.3 client sends it, and a
2916
- // server MUST NOT send NewSessionTicket to a client that omitted it
2917
- // (RFC 8446 §4.2.9) — required for future 0-RTT/resumption anyway.
2918
- extensions.push({ type: 'PSK_KEY_EXCHANGE_MODES', value: [1] });
2919
- }
2920
-
2921
- // Add SNI if servername was provided
2922
- if (context.local_sni) {
2923
- extensions.unshift({ type: 'SERVER_NAME', value: context.local_sni });
2924
- }
2925
-
2926
- // Add ALPN if provided (e.g. 'h3' for QUIC)
2927
- if (context.local_supported_alpns && context.local_supported_alpns.length > 0) {
2928
- extensions.push({ type: 'ALPN', value: context.local_supported_alpns });
2929
- }
2930
-
2931
- // Add custom extensions (e.g. QUIC transport params 0x39)
2932
- for (let i = 0; i < context.local_extensions.length; i++) {
2933
- extensions.push(context.local_extensions[i]);
2934
- }
2935
4451
 
2936
4452
  // Resumption: check if session was provided (opaque Buffer) — decode to structured data
2937
4453
  let sessionData = null;
@@ -2978,6 +4494,7 @@ function TLSSession(options){
2978
4494
  }
2979
4495
  };
2980
4496
  extensions.push(pskExt); // MUST be last
4497
+ note_offered_extensions([pskExt]);
2981
4498
 
2982
4499
  // Build the full message with placeholder
2983
4500
  let build_message_params = {
@@ -3001,8 +4518,8 @@ function TLSSession(options){
3001
4518
  let truncatedMessage = tempMessage.slice(0, tempMessage.length - bindersSize);
3002
4519
 
3003
4520
  // Compute real binder
3004
- let binder_key = derive_binder_key(hashName, context.psk_offered.psk, false);
3005
- let binder = compute_psk_binder(hashName, binder_key, truncatedMessage);
4521
+ let binder_key = derive_binder_key(hashName, context.psk_offered.psk, false, label_prefix());
4522
+ let binder = compute_psk_binder(hashName, binder_key, truncatedMessage, label_prefix());
3006
4523
 
3007
4524
  dbg('CLI-PSK', 'ticket:', hexPreview(sessionData.ticket, 24),
3008
4525
  'cipher:', '0x' + sessionData.cipher.toString(16),
@@ -3029,8 +4546,18 @@ function TLSSession(options){
3029
4546
  // If we only have a session_id (no ticket), don't include empty SESSION_TICKET ext:
3030
4547
  // servers with SSL_OP_NO_TICKET can behave inconsistently when the extension appears
3031
4548
  // alongside a session_id resumption attempt — they may skip the session_id lookup.
4549
+ // Fill in the ticket on the SESSION_TICKET entry the assembler already
4550
+ // produced, rather than pushing a second one — two extensions of the
4551
+ // same type is itself a protocol violation (RFC 8446 §4.2), and it
4552
+ // would also change CH2's extension list relative to CH1. If the
4553
+ // assembler did not emit one (1.3-only or DTLS profile), there is
4554
+ // nothing to resume with here anyway.
3032
4555
  if (sessionData.ticket && sessionData.ticket.length > 0) {
3033
- extensions.push({ type: 'SESSION_TICKET', value: sessionData.ticket });
4556
+ let stEntry = null;
4557
+ for (let ei = 0; ei < extensions.length; ei++) {
4558
+ if (extensions[ei] && extensions[ei].type === 'SESSION_TICKET') { stEntry = extensions[ei]; break; }
4559
+ }
4560
+ if (stEntry) stEntry.value = sessionData.ticket;
3034
4561
  }
3035
4562
 
3036
4563
  // If we have a session_id → put it in ClientHello.session_id (overrides the random one)
@@ -3060,13 +4587,6 @@ function TLSSession(options){
3060
4587
  // No resumption — advertise empty SessionTicket extension to offer support.
3061
4588
  // Skip for DTLS (DTLS clients/servers often don't implement RFC 5077 fully,
3062
4589
  // and adding it caused interop issues with openssl s_server -dtls1_2).
3063
- let isDtls = context.local_supported_versions && context.local_supported_versions.some(v => (v & 0xFF00) === 0xFE00);
3064
- if (!isDtls && !tls13Only && context.sessionTickets) {
3065
- // RFC 5077 SessionTicket is a TLS 1.2 mechanism; 1.3 resumption uses
3066
- // NewSessionTicket/PSK. Never advertise it in a 1.3-only/QUIC hello.
3067
- extensions.push({ type: 'SESSION_TICKET', value: new Uint8Array(0) });
3068
- }
3069
-
3070
4590
  // Standard ClientHello
3071
4591
  let build_message_params = {
3072
4592
  type: 'client_hello',
@@ -3144,6 +4664,18 @@ function TLSSession(options){
3144
4664
  return context.selected_version;
3145
4665
  },
3146
4666
 
4667
+ /**
4668
+ * Current session state: 'new' | 'handshaking' | 'connected' | 'error' | 'closed'.
4669
+ *
4670
+ * Exposed so a transport can tell whether the session is still alive
4671
+ * WITHOUT reaching into context. A transport that keeps feeding records to
4672
+ * an aborted session decrypts them under keys the peer no longer uses and
4673
+ * reports a misleading bad_record_mac, hiding the real failure.
4674
+ */
4675
+ getState: function(){
4676
+ return context.state;
4677
+ },
4678
+
3147
4679
  /** Returns the negotiated cipher suite code (e.g. 0x1301, 0xC02F), or null. */
3148
4680
  getCipher: function(){
3149
4681
  return context.selected_cipher_suite;
@@ -3178,6 +4710,12 @@ function TLSSession(options){
3178
4710
  // TLS 1.3
3179
4711
  localAppSecret: context.local_app_traffic_secret,
3180
4712
  remoteAppSecret: context.remote_app_traffic_secret,
4713
+ // Handshake-epoch secrets. The record layer needs these to encrypt an
4714
+ // alert raised BEFORE anything has been written at epoch 1 — an abort
4715
+ // during the peer's flight is exactly that case, and without them the
4716
+ // alert could only go out in cleartext.
4717
+ localHandshakeSecret: context.local_handshake_traffic_secret,
4718
+ remoteHandshakeSecret: context.remote_handshake_traffic_secret,
3181
4719
  // TLS 1.2
3182
4720
  masterSecret: context.base_secret,
3183
4721
  localRandom: context.local_random,
@@ -3218,6 +4756,14 @@ function TLSSession(options){
3218
4756
  * @param {Uint8Array|Buffer|null} [context_value]
3219
4757
  * @returns {Buffer|null}
3220
4758
  */
4759
+ /**
4760
+ * The negotiated DTLS-SRTP protection profile (RFC 5764), or null if none
4761
+ * was agreed. Pair with exportKeyingMaterial(len, 'EXTRACTOR-dtls_srtp').
4762
+ */
4763
+ getSelectedSrtpProfile: function(){
4764
+ return context.selected_srtp_profile;
4765
+ },
4766
+
3221
4767
  exportKeyingMaterial: function(length, label, context_value){
3222
4768
  if (!context.selected_cipher_suite || !length || !label) return null;
3223
4769
  var suite = TLS_CIPHER_SUITES[context.selected_cipher_suite];
@@ -3231,7 +4777,7 @@ function TLSSession(options){
3231
4777
  return Buffer.from(tls13_exporter(
3232
4778
  suite.hash, context.exporter_master_secret, label,
3233
4779
  (context_value != null) ? context_value : null, length
3234
- ));
4780
+ , label_prefix()));
3235
4781
  }
3236
4782
 
3237
4783
  // TLS 1.2 — RFC 5705 over the master secret + hello randoms.
@@ -3335,11 +4881,11 @@ function TLSSession(options){
3335
4881
  /** Request a TLS 1.3 Key Update. requestPeer=true means ask the other side to update too. */
3336
4882
  requestKeyUpdate: function(requestPeer){
3337
4883
  if (context.state !== 'connected' || (context.selected_version !== wire.TLS_VERSION.TLS1_3 && context.selected_version !== wire.DTLS_VERSION.DTLS1_3)) return;
3338
- let hashName = TLS_CIPHER_SUITES[context.selected_cipher_suite].hash;
4884
+ let hashName = negotiated_hash();
3339
4885
  let hashLen = getHashLen(hashName);
3340
4886
 
3341
4887
  // Derive new local traffic secret
3342
- let newLocalSecret = hkdf_expand_label(hashName, context.local_app_traffic_secret, 'traffic upd', new Uint8Array(0), hashLen);
4888
+ let newLocalSecret = hkdf_expand_label(hashName, context.local_app_traffic_secret, 'traffic upd', new Uint8Array(0), hashLen, label_prefix());
3343
4889
  context.local_app_traffic_secret = newLocalSecret;
3344
4890
 
3345
4891
  // Send KeyUpdate message