lemon-tls 0.5.5 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lemon-tls",
3
- "version": "0.5.5",
3
+ "version": "0.7.0",
4
4
  "description": "TLS 1.3/1.2 DTLS 1.3/1.2 implementation for Node.js - full control over cryptographic keys, record layer, and handshake. Drop-in replacement for node:tls with advanced options impossible in OpenSSL.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -39,6 +39,7 @@
39
39
  "tls-1.3",
40
40
  "tls-1.2",
41
41
  "ssl",
42
+ "bogo",
42
43
  "https",
43
44
  "secure",
44
45
  "crypto",
@@ -70,7 +71,6 @@
70
71
  "ja3",
71
72
  "ja3-fingerprint",
72
73
  "fingerprint",
73
- "certificate-pinning",
74
74
  "keylog",
75
75
  "wireshark",
76
76
  "quic",
@@ -79,13 +79,8 @@
79
79
  "pure-javascript",
80
80
  "zero-dependencies",
81
81
  "no-openssl",
82
- "no-native",
83
- "nodejs",
84
- "node",
85
- "javascript",
86
82
  "security",
87
83
  "network",
88
- "protocol",
89
84
  "drop-in-replacement"
90
85
  ],
91
86
  "author": "colocohen",
package/src/compat.js CHANGED
@@ -281,6 +281,16 @@ function Server(options, connectionListener) {
281
281
  maxRecordSize: options.maxRecordSize,
282
282
  sessionTickets: options.sessionTickets,
283
283
  requestCert: options.requestCert,
284
+ // Client-authentication policy. These were missing, so a server's
285
+ // rejectUnauthorized/ca never reached TLSSession and it fell back to the
286
+ // session default (rejectUnauthorized: true). The effect was invisible
287
+ // until client auth was actually exercised: `requestCert: true` WITHOUT
288
+ // `rejectUnauthorized` is the standard "ask for a certificate but accept
289
+ // clients that have none" configuration, and it rejected them instead.
290
+ // requestCert was forwarded and its two companions were not, which is
291
+ // exactly the kind of half-wired option that looks correct at a glance.
292
+ rejectUnauthorized: options.rejectUnauthorized,
293
+ ca: options.ca,
284
294
  maxHandshakeSize: options.maxHandshakeSize,
285
295
  allowedCipherSuites: options.allowedCipherSuites,
286
296
  handshakeTimeout: options.handshakeTimeout,
package/src/crypto.js CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  import {
3
2
  concatUint8Arrays
4
3
  } from './utils.js';
@@ -303,8 +302,26 @@ function hkdf_expand(hashName, prkU8, infoU8, length) {
303
302
  // to handshakes (TextEncoder construction is not free in V8).
304
303
  const _TEXT_ENCODER = new TextEncoder();
305
304
 
306
- function build_hkdf_label(label, context, length) {
307
- const full = _TEXT_ENCODER.encode('tls13 ' + label);
305
+ // Label prefixes (RFC 8446 §7.1 / RFC 9147 §5.9). Exported so transports pick
306
+ // one explicitly instead of spelling the string out at each derivation site.
307
+ const LABEL_PREFIX_TLS13 = 'tls13 '; // trailing space is part of the label
308
+ const LABEL_PREFIX_DTLS13 = 'dtls13'; // no trailing space — deliberate
309
+
310
+ /**
311
+ * RFC 8446 §7.1 HkdfLabel, with the protocol's label prefix.
312
+ *
313
+ * TLS 1.3 uses "tls13 " (trailing space). DTLS 1.3 uses "dtls13" with NO
314
+ * trailing space — RFC 9147 §5.9: "For DTLS 1.3, that label SHALL be
315
+ * 'dtls13'. This ensures key separation between DTLS 1.3 and TLS 1.3. Note
316
+ * that there is no trailing space; this is necessary in order to keep the
317
+ * overall label size inside of one hash iteration."
318
+ *
319
+ * The prefix is threaded as a parameter rather than read from module state
320
+ * because it is a property of ONE session's key schedule; two sessions of
321
+ * different protocols can be live in the same process.
322
+ */
323
+ function build_hkdf_label(label, context, length, labelPrefix) {
324
+ const full = _TEXT_ENCODER.encode((labelPrefix || LABEL_PREFIX_TLS13) + label);
308
325
  const info = new Uint8Array(2 + 1 + full.length + 1 + context.length);
309
326
 
310
327
  info[0] = (length >>> 8) & 0xff;
@@ -319,8 +336,8 @@ function build_hkdf_label(label, context, length) {
319
336
  return info;
320
337
  }
321
338
 
322
- function hkdf_expand_label(hashName, secret, label, context, length) {
323
- let info = build_hkdf_label(label, context, length | 0);
339
+ function hkdf_expand_label(hashName, secret, label, context, length, labelPrefix) {
340
+ let info = build_hkdf_label(label, context, length | 0, labelPrefix);
324
341
  return hkdf_expand(hashName, secret, info, length | 0);
325
342
  }
326
343
 
@@ -329,7 +346,7 @@ function hkdf_expand_label(hashName, secret, label, context, length) {
329
346
  // TLS 1.3: derive handshake traffic secrets
330
347
  // ============================================================
331
348
 
332
- function derive_handshake_traffic_secrets(hashName, shared_secret, transcript) {
349
+ function derive_handshake_traffic_secrets(hashName, shared_secret, transcript, labelPrefix) {
333
350
  let hashFn = getHashFn(hashName);
334
351
  let hashLen = hashFn.outputLen | 0;
335
352
  const empty = new Uint8Array(0);
@@ -337,11 +354,11 @@ function derive_handshake_traffic_secrets(hashName, shared_secret, transcript) {
337
354
 
338
355
  let early_secret = hkdf_extract(hashName, empty, zeros);
339
356
  let h_empty = hashFn(empty);
340
- let derived_secret = hkdf_expand_label(hashName, early_secret, 'derived', h_empty, hashLen);
357
+ let derived_secret = hkdf_expand_label(hashName, early_secret, 'derived', h_empty, hashLen, labelPrefix);
341
358
  let handshake_secret = hkdf_extract(hashName, derived_secret, shared_secret);
342
359
  let transcript_hash = hashFn(transcript);
343
- let client_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 'c hs traffic', transcript_hash, hashLen);
344
- let server_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 's hs traffic', transcript_hash, hashLen);
360
+ let client_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 'c hs traffic', transcript_hash, hashLen, labelPrefix);
361
+ let server_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 's hs traffic', transcript_hash, hashLen, labelPrefix);
345
362
 
346
363
  return {
347
364
  handshake_secret: handshake_secret,
@@ -355,7 +372,7 @@ function derive_handshake_traffic_secrets(hashName, shared_secret, transcript) {
355
372
  * hash — skips the hashFn(transcript) step and its allocation. Use this when
356
373
  * the caller has already computed the hash via an incremental running hash.
357
374
  */
358
- function derive_handshake_traffic_secrets_with_hash(hashName, shared_secret, transcript_hash) {
375
+ function derive_handshake_traffic_secrets_with_hash(hashName, shared_secret, transcript_hash, labelPrefix) {
359
376
  let hashFn = getHashFn(hashName);
360
377
  let hashLen = hashFn.outputLen | 0;
361
378
  const empty = new Uint8Array(0);
@@ -363,10 +380,10 @@ function derive_handshake_traffic_secrets_with_hash(hashName, shared_secret, tra
363
380
 
364
381
  let early_secret = hkdf_extract(hashName, empty, zeros);
365
382
  let h_empty = hashFn(empty);
366
- let derived_secret = hkdf_expand_label(hashName, early_secret, 'derived', h_empty, hashLen);
383
+ let derived_secret = hkdf_expand_label(hashName, early_secret, 'derived', h_empty, hashLen, labelPrefix);
367
384
  let handshake_secret = hkdf_extract(hashName, derived_secret, shared_secret);
368
- let client_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 'c hs traffic', transcript_hash, hashLen);
369
- let server_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 's hs traffic', transcript_hash, hashLen);
385
+ let client_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 'c hs traffic', transcript_hash, hashLen, labelPrefix);
386
+ let server_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 's hs traffic', transcript_hash, hashLen, labelPrefix);
370
387
 
371
388
  return {
372
389
  handshake_secret: handshake_secret,
@@ -380,18 +397,18 @@ function derive_handshake_traffic_secrets_with_hash(hashName, shared_secret, tra
380
397
  // TLS 1.3: derive application traffic secrets
381
398
  // ============================================================
382
399
 
383
- function derive_app_traffic_secrets(hashName, handshake_secret, transcript) {
400
+ function derive_app_traffic_secrets(hashName, handshake_secret, transcript, labelPrefix) {
384
401
  let hashFn = getHashFn(hashName);
385
402
  let hashLen = hashFn.outputLen | 0;
386
403
  const empty = new Uint8Array(0);
387
404
  const zeros = new Uint8Array(hashLen);
388
405
 
389
406
  let h_empty = hashFn(empty);
390
- let derived_secret = hkdf_expand_label(hashName, handshake_secret, 'derived', h_empty, hashLen);
407
+ let derived_secret = hkdf_expand_label(hashName, handshake_secret, 'derived', h_empty, hashLen, labelPrefix);
391
408
  let master_secret = hkdf_extract(hashName, derived_secret, zeros);
392
409
  let transcript_hash = hashFn(transcript);
393
- let client_app_traffic_secret = hkdf_expand_label(hashName, master_secret, 'c ap traffic', transcript_hash, hashLen);
394
- let server_app_traffic_secret = hkdf_expand_label(hashName, master_secret, 's ap traffic', transcript_hash, hashLen);
410
+ let client_app_traffic_secret = hkdf_expand_label(hashName, master_secret, 'c ap traffic', transcript_hash, hashLen, labelPrefix);
411
+ let server_app_traffic_secret = hkdf_expand_label(hashName, master_secret, 's ap traffic', transcript_hash, hashLen, labelPrefix);
395
412
 
396
413
  return {
397
414
  client_app_traffic_secret: client_app_traffic_secret,
@@ -403,17 +420,17 @@ function derive_app_traffic_secrets(hashName, handshake_secret, transcript) {
403
420
  /**
404
421
  * Like derive_app_traffic_secrets but accepts a pre-computed transcript hash.
405
422
  */
406
- function derive_app_traffic_secrets_with_hash(hashName, handshake_secret, transcript_hash) {
423
+ function derive_app_traffic_secrets_with_hash(hashName, handshake_secret, transcript_hash, labelPrefix) {
407
424
  let hashFn = getHashFn(hashName);
408
425
  let hashLen = hashFn.outputLen | 0;
409
426
  const empty = new Uint8Array(0);
410
427
  const zeros = new Uint8Array(hashLen);
411
428
 
412
429
  let h_empty = hashFn(empty);
413
- let derived_secret = hkdf_expand_label(hashName, handshake_secret, 'derived', h_empty, hashLen);
430
+ let derived_secret = hkdf_expand_label(hashName, handshake_secret, 'derived', h_empty, hashLen, labelPrefix);
414
431
  let master_secret = hkdf_extract(hashName, derived_secret, zeros);
415
- let client_app_traffic_secret = hkdf_expand_label(hashName, master_secret, 'c ap traffic', transcript_hash, hashLen);
416
- let server_app_traffic_secret = hkdf_expand_label(hashName, master_secret, 's ap traffic', transcript_hash, hashLen);
432
+ let client_app_traffic_secret = hkdf_expand_label(hashName, master_secret, 'c ap traffic', transcript_hash, hashLen, labelPrefix);
433
+ let server_app_traffic_secret = hkdf_expand_label(hashName, master_secret, 's ap traffic', transcript_hash, hashLen, labelPrefix);
417
434
 
418
435
  return {
419
436
  client_app_traffic_secret: client_app_traffic_secret,
@@ -431,28 +448,28 @@ function derive_app_traffic_secrets_with_hash(hashName, handshake_secret, transc
431
448
  * Derive the resumption_master_secret from the master_secret.
432
449
  * transcript = all handshake messages including both Finished.
433
450
  */
434
- function derive_resumption_master_secret(hashName, master_secret, transcript) {
451
+ function derive_resumption_master_secret(hashName, master_secret, transcript, labelPrefix) {
435
452
  let hashFn = getHashFn(hashName);
436
453
  let hashLen = hashFn.outputLen | 0;
437
454
  let transcript_hash = hashFn(transcript);
438
- return hkdf_expand_label(hashName, master_secret, 'res master', transcript_hash, hashLen);
455
+ return hkdf_expand_label(hashName, master_secret, 'res master', transcript_hash, hashLen, labelPrefix);
439
456
  }
440
457
 
441
458
  /**
442
459
  * Like derive_resumption_master_secret but accepts a pre-computed transcript hash.
443
460
  */
444
- function derive_resumption_master_secret_with_hash(hashName, master_secret, transcript_hash) {
461
+ function derive_resumption_master_secret_with_hash(hashName, master_secret, transcript_hash, labelPrefix) {
445
462
  let hashLen = getHashLen(hashName);
446
- return hkdf_expand_label(hashName, master_secret, 'res master', transcript_hash, hashLen);
463
+ return hkdf_expand_label(hashName, master_secret, 'res master', transcript_hash, hashLen, labelPrefix);
447
464
  }
448
465
 
449
466
  /**
450
467
  * Derive PSK from a resumption_master_secret + ticket_nonce.
451
468
  * Used by the server when creating a ticket, and by the client when resuming.
452
469
  */
453
- function derive_psk(hashName, resumption_master_secret, ticket_nonce) {
470
+ function derive_psk(hashName, resumption_master_secret, ticket_nonce, labelPrefix) {
454
471
  let hashLen = getHashFn(hashName).outputLen | 0;
455
- return hkdf_expand_label(hashName, resumption_master_secret, 'resumption', ticket_nonce, hashLen);
472
+ return hkdf_expand_label(hashName, resumption_master_secret, 'resumption', ticket_nonce, hashLen, labelPrefix);
456
473
  }
457
474
 
458
475
  /**
@@ -460,7 +477,7 @@ function derive_psk(hashName, resumption_master_secret, ticket_nonce) {
460
477
  * For resumption PSK, label is "res binder".
461
478
  * For external PSK, label is "ext binder".
462
479
  */
463
- function derive_binder_key(hashName, psk, isExternal) {
480
+ function derive_binder_key(hashName, psk, isExternal, labelPrefix) {
464
481
  let hashFn = getHashFn(hashName);
465
482
  let hashLen = hashFn.outputLen | 0;
466
483
  const empty = new Uint8Array(0);
@@ -469,7 +486,7 @@ function derive_binder_key(hashName, psk, isExternal) {
469
486
  let early_secret = hkdf_extract(hashName, empty, psk);
470
487
  let h_empty = hashFn(empty);
471
488
  let label = isExternal ? 'ext binder' : 'res binder';
472
- return hkdf_expand_label(hashName, early_secret, label, h_empty, hashLen);
489
+ return hkdf_expand_label(hashName, early_secret, label, h_empty, hashLen, labelPrefix);
473
490
  }
474
491
 
475
492
  /**
@@ -484,11 +501,11 @@ function derive_binder_key(hashName, psk, isExternal) {
484
501
  * So we must derive a finished_key from binder_key (as in get_handshake_finished)
485
502
  * before the HMAC, NOT use binder_key directly.
486
503
  */
487
- function compute_psk_binder(hashName, binder_key, truncated_transcript) {
504
+ function compute_psk_binder(hashName, binder_key, truncated_transcript, labelPrefix) {
488
505
  let hashFn = getHashFn(hashName);
489
506
  let hashLen = hashFn.outputLen | 0;
490
507
  const empty = new Uint8Array(0);
491
- let finished_key = hkdf_expand_label(hashName, binder_key, 'finished', empty, hashLen);
508
+ let finished_key = hkdf_expand_label(hashName, binder_key, 'finished', empty, hashLen, labelPrefix);
492
509
  let transcript_hash = hashFn(truncated_transcript);
493
510
  return hmac(hashName, finished_key, transcript_hash);
494
511
  }
@@ -497,18 +514,18 @@ function compute_psk_binder(hashName, binder_key, truncated_transcript) {
497
514
  * Derive handshake secrets for a PSK-based handshake (with ECDHE).
498
515
  * Uses the PSK as input to early_secret instead of zeros.
499
516
  */
500
- function derive_handshake_traffic_secrets_psk(hashName, psk, shared_secret, transcript) {
517
+ function derive_handshake_traffic_secrets_psk(hashName, psk, shared_secret, transcript, labelPrefix) {
501
518
  let hashFn = getHashFn(hashName);
502
519
  let hashLen = hashFn.outputLen | 0;
503
520
  const empty = new Uint8Array(0);
504
521
 
505
522
  let early_secret = hkdf_extract(hashName, empty, psk);
506
523
  let h_empty = hashFn(empty);
507
- let derived_secret = hkdf_expand_label(hashName, early_secret, 'derived', h_empty, hashLen);
524
+ let derived_secret = hkdf_expand_label(hashName, early_secret, 'derived', h_empty, hashLen, labelPrefix);
508
525
  let handshake_secret = hkdf_extract(hashName, derived_secret, shared_secret);
509
526
  let transcript_hash = hashFn(transcript);
510
- let client_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 'c hs traffic', transcript_hash, hashLen);
511
- let server_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 's hs traffic', transcript_hash, hashLen);
527
+ let client_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 'c hs traffic', transcript_hash, hashLen, labelPrefix);
528
+ let server_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 's hs traffic', transcript_hash, hashLen, labelPrefix);
512
529
 
513
530
  return {
514
531
  handshake_secret: handshake_secret,
@@ -520,17 +537,17 @@ function derive_handshake_traffic_secrets_psk(hashName, psk, shared_secret, tran
520
537
  /**
521
538
  * Like derive_handshake_traffic_secrets_psk but accepts a pre-computed transcript hash.
522
539
  */
523
- function derive_handshake_traffic_secrets_psk_with_hash(hashName, psk, shared_secret, transcript_hash) {
540
+ function derive_handshake_traffic_secrets_psk_with_hash(hashName, psk, shared_secret, transcript_hash, labelPrefix) {
524
541
  let hashFn = getHashFn(hashName);
525
542
  let hashLen = hashFn.outputLen | 0;
526
543
  const empty = new Uint8Array(0);
527
544
 
528
545
  let early_secret = hkdf_extract(hashName, empty, psk);
529
546
  let h_empty = hashFn(empty);
530
- let derived_secret = hkdf_expand_label(hashName, early_secret, 'derived', h_empty, hashLen);
547
+ let derived_secret = hkdf_expand_label(hashName, early_secret, 'derived', h_empty, hashLen, labelPrefix);
531
548
  let handshake_secret = hkdf_extract(hashName, derived_secret, shared_secret);
532
- let client_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 'c hs traffic', transcript_hash, hashLen);
533
- let server_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 's hs traffic', transcript_hash, hashLen);
549
+ let client_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 'c hs traffic', transcript_hash, hashLen, labelPrefix);
550
+ let server_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 's hs traffic', transcript_hash, hashLen, labelPrefix);
534
551
 
535
552
  return {
536
553
  handshake_secret: handshake_secret,
@@ -648,10 +665,10 @@ function build_cert_verify_tbs_with_hash(hashName, isServer, transcript_hash) {
648
665
  // TLS 1.3 Finished verify_data
649
666
  // ============================================================
650
667
 
651
- function get_handshake_finished(hashName, traffic_secret, transcript) {
668
+ function get_handshake_finished(hashName, traffic_secret, transcript, labelPrefix) {
652
669
  let hashLen = getHashLen(hashName);
653
670
  const empty = new Uint8Array(0);
654
- let finished_key = hkdf_expand_label(hashName, traffic_secret, 'finished', empty, hashLen);
671
+ let finished_key = hkdf_expand_label(hashName, traffic_secret, 'finished', empty, hashLen, labelPrefix);
655
672
  let transcript_hash = getHashFn(hashName)(transcript);
656
673
  return hmac(hashName, finished_key, transcript_hash);
657
674
  }
@@ -659,10 +676,10 @@ function get_handshake_finished(hashName, traffic_secret, transcript) {
659
676
  /**
660
677
  * Like get_handshake_finished but accepts a pre-computed transcript hash.
661
678
  */
662
- function get_handshake_finished_with_hash(hashName, traffic_secret, transcript_hash) {
679
+ function get_handshake_finished_with_hash(hashName, traffic_secret, transcript_hash, labelPrefix) {
663
680
  let hashLen = getHashLen(hashName);
664
681
  const empty = new Uint8Array(0);
665
- let finished_key = hkdf_expand_label(hashName, traffic_secret, 'finished', empty, hashLen);
682
+ let finished_key = hkdf_expand_label(hashName, traffic_secret, 'finished', empty, hashLen, labelPrefix);
666
683
  return hmac(hashName, finished_key, transcript_hash);
667
684
  }
668
685
 
@@ -686,9 +703,9 @@ function get_handshake_finished_with_hash(hashName, traffic_secret, transcript_h
686
703
  * Called at the same point (and with the same transcript hash) as the
687
704
  * application traffic secrets.
688
705
  */
689
- function derive_exporter_master_secret_with_hash(hashName, master_secret, transcript_hash) {
706
+ function derive_exporter_master_secret_with_hash(hashName, master_secret, transcript_hash, labelPrefix) {
690
707
  let hashLen = getHashLen(hashName);
691
- return hkdf_expand_label(hashName, master_secret, 'exp master', transcript_hash, hashLen);
708
+ return hkdf_expand_label(hashName, master_secret, 'exp master', transcript_hash, hashLen, labelPrefix);
692
709
  }
693
710
 
694
711
  /**
@@ -711,13 +728,25 @@ function derive_exporter_master_secret_with_hash(hashName, master_secret, transc
711
728
  * @param {number} length
712
729
  * @returns {Uint8Array}
713
730
  */
714
- function tls13_exporter(hashName, exporter_master_secret, label, context_value, length) {
731
+ function tls13_exporter(hashName, exporter_master_secret, label, context_value, length, labelPrefix) {
715
732
  let hashFn = getHashFn(hashName);
716
733
  let hashLen = getHashLen(hashName);
717
734
  let h_empty = hashFn(new Uint8Array(0));
718
- let derived = hkdf_expand_label(hashName, exporter_master_secret, label, h_empty, hashLen);
735
+ let derived = hkdf_expand_label(hashName, exporter_master_secret, label, h_empty, hashLen, labelPrefix);
719
736
  let h_ctx = hashFn(context_value || new Uint8Array(0));
720
- return hkdf_expand(hashName, derived, build_hkdf_label('exporter', h_ctx, length), length);
737
+ // BOTH expansions carry the prefix. RFC 8446 §7.5 defines the exporter
738
+ // as two HKDF-Expand-Label steps, and in DTLS 1.3 both use "dtls13"
739
+ // (RFC 9147 §5.9) — this second one was omitting it and falling back
740
+ // to "tls13 ", so the exported key differed from a conforming peer's.
741
+ // For WebRTC that key IS the DTLS-SRTP keying material: the handshake
742
+ // completes, SRTP reports ready, packets flow in both directions, and
743
+ // NOTHING decrypts. Two peers running this library derive the same
744
+ // wrong key and interoperate perfectly with each other, which is why
745
+ // it only appears against Chrome — and appears as "media does not
746
+ // flow" rather than as a crypto error, since a failed unprotect is
747
+ // just a dropped packet.
748
+ return hkdf_expand(hashName, derived,
749
+ build_hkdf_label('exporter', h_ctx, length, labelPrefix), length);
721
750
  }
722
751
 
723
752
  /**
@@ -758,9 +787,9 @@ function tls12_exporter(hashName, master_secret, label, client_random, server_ra
758
787
  return tls12_prf(master_secret, label, seed, length, hashName);
759
788
  }
760
789
 
761
- function derive_sn_key(hashName, traffic_secret, cipher_suite) {
790
+ function derive_sn_key(hashName, traffic_secret, cipher_suite, labelPrefix) {
762
791
  let keylen = TLS_CIPHER_SUITES[cipher_suite].keylen;
763
- return hkdf_expand_label(hashName, traffic_secret, 'sn', new Uint8Array(0), keylen);
792
+ return hkdf_expand_label(hashName, traffic_secret, 'sn', new Uint8Array(0), keylen, labelPrefix);
764
793
  }
765
794
 
766
795
 
@@ -768,7 +797,87 @@ function derive_sn_key(hashName, traffic_secret, cipher_suite) {
768
797
  // Exports — identical API surface
769
798
  // ============================================================
770
799
 
800
+ /**
801
+ * Default cipher-suite offers, in preference order.
802
+ *
803
+ * SINGLE SOURCE OF TRUTH, alongside the TLS_CIPHER_SUITES table this module
804
+ * already owns — the same rule as ecdh.js/SUPPORTED_GROUPS and
805
+ * signing.js/default_signature_schemes. Previously TLSSocket, DTLSSession and
806
+ * the TLSSession client default each carried their own hardcoded list, and
807
+ * they had already drifted (one offered ChaCha20 for 1.2, another offered
808
+ * AES-256-ECDSA instead).
809
+ */
810
+ const DEFAULT_CIPHER_SUITES_TLS13 = [
811
+ 0x1301, // TLS_AES_128_GCM_SHA256
812
+ 0x1302, // TLS_AES_256_GCM_SHA384
813
+ 0x1303, // TLS_CHACHA20_POLY1305_SHA256
814
+ ];
815
+
816
+ const DEFAULT_CIPHER_SUITES_TLS12 = [
817
+ 0xC02F, // ECDHE_RSA_WITH_AES_128_GCM_SHA256
818
+ 0xC030, // ECDHE_RSA_WITH_AES_256_GCM_SHA384
819
+ 0xC02B, // ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
820
+ 0xC02C, // ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
821
+ 0xCCA8, // ECDHE_RSA_WITH_CHACHA20_POLY1305
822
+ 0xCCA9, // ECDHE_ECDSA_WITH_CHACHA20_POLY1305
823
+ ];
824
+
825
+ /** Default offer list for a version range. */
826
+ /**
827
+ * Can the record layer actually protect traffic with this cipher suite?
828
+ *
829
+ * TLS_CIPHER_SUITES describes suites the code can NAME — including CBC ones
830
+ * kept for parsing peer offers and for reporting — but record.js implements
831
+ * AEAD only. A suite that is merely described is not a capability: selecting
832
+ * one produces a handshake that agrees on parameters and then cannot encrypt a
833
+ * single record.
834
+ *
835
+ * This mirrors ecdh.js's is_supported_group, and exists for the same reason: a
836
+ * configured list is not an implementation. Selection asks this, never the
837
+ * table's mere presence.
838
+ */
839
+ function is_usable_cipher_suite(id) {
840
+ let m = TLS_CIPHER_SUITES[id];
841
+ return !!(m && m.aead === true);
842
+ }
843
+
844
+ /**
845
+ * May this cipher suite be used with this negotiated version?
846
+ *
847
+ * RFC 8446 §B.4 defines 0x1301..0x1303 for TLS 1.3 ONLY; they are not valid
848
+ * TLS 1.2 suites and a 1.2 peer must ignore them. The converse holds too — an
849
+ * ECDHE_RSA_WITH_AES_128_GCM suite has no meaning in a 1.3 handshake, where
850
+ * key exchange and authentication are negotiated separately from the AEAD.
851
+ *
852
+ * The suite table already records which version each entry belongs to (`tls`:
853
+ * 12 or 13); this makes selection ask. Without it a server whose configured
854
+ * list happened to contain a 1.3 suite would select it while negotiating 1.2,
855
+ * producing a ServerHello no 1.2 client can act on.
856
+ */
857
+ function suite_matches_version(id, negotiatedVersion) {
858
+ let m = TLS_CIPHER_SUITES[id];
859
+ if (!m) return false;
860
+ if (negotiatedVersion === null || negotiatedVersion === undefined) return true;
861
+ let is13 = negotiatedVersion === 0x0304 || negotiatedVersion === 0xFEFC;
862
+ return is13 ? (m.tls === 13) : (m.tls === 12);
863
+ }
864
+
865
+ function default_cipher_suites(includeTls13, includeTls12) {
866
+ let out = [];
867
+ if (includeTls13) out = out.concat(DEFAULT_CIPHER_SUITES_TLS13);
868
+ if (includeTls12) out = out.concat(DEFAULT_CIPHER_SUITES_TLS12);
869
+ return out;
870
+ }
871
+
771
872
  export {
873
+ is_usable_cipher_suite,
874
+ suite_matches_version,
875
+ LABEL_PREFIX_TLS13,
876
+ LABEL_PREFIX_DTLS13,
877
+ DEFAULT_CIPHER_SUITES_TLS13,
878
+ DEFAULT_CIPHER_SUITES_TLS12,
879
+ default_cipher_suites,
880
+
772
881
  TLS_CIPHER_SUITES,
773
882
  getHashFn,
774
883
  getHashLen,
@@ -798,4 +907,4 @@ export {
798
907
  derive_exporter_master_secret_with_hash,
799
908
  tls13_exporter,
800
909
  tls12_exporter,
801
- };
910
+ };