lemon-tls 0.5.5 → 0.6.0

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