lemon-tls 0.4.0 → 0.4.1

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/tls_session.js +81 -25
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lemon-tls",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "TLS 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",
@@ -504,20 +504,24 @@ function TLSSession(options){
504
504
  // - ALPN (same as CH1)
505
505
  // - custom extensions (QUIC transport params etc.)
506
506
  // - same cipher_suites, session_id, random as CH1
507
+ // CH2 MUST match CH1 exactly except key_share/cookie (RFC 8446
508
+ // §4.1.2) — so mirror CH1's conditional composition: same sigalg
509
+ // source (CH1 wrote its final list back to the context), and the
510
+ // same tls13Only rules for the legacy extensions and psk modes.
511
+ let tls13Only2 = context.local_supported_versions.length > 0 &&
512
+ context.local_supported_versions.every(v => v === 0x0304);
507
513
  let extensions = [
508
514
  { type: 'SUPPORTED_VERSIONS', value: context.local_supported_versions },
509
515
  { type: 'SUPPORTED_GROUPS', value: context.local_supported_groups },
510
516
  { type: 'KEY_SHARE', value: [{ group: requestedGroup, key_exchange: newKeyGroup.public_key }] },
511
- { type: 'SIGNATURE_ALGORITHMS', value: [
512
- // Must match CH1 exactly (RFC 8446 §4.1.2)
513
- 0x0804, 0x0805, 0x0806,
514
- 0x0403, 0x0503, 0x0603,
515
- 0x0807, 0x0808,
516
- 0x0401, 0x0501, 0x0601
517
- ] },
518
- { type: 'RENEGOTIATION_INFO', value: new Uint8Array(0) },
519
- { type: 'EXTENDED_MASTER_SECRET', value: null },
517
+ { type: 'SIGNATURE_ALGORITHMS', value: context.local_supported_signature_algorithms },
520
518
  ];
519
+ if (!tls13Only2) {
520
+ extensions.push({ type: 'RENEGOTIATION_INFO', value: new Uint8Array(0) });
521
+ extensions.push({ type: 'EXTENDED_MASTER_SECRET', value: null });
522
+ } else {
523
+ extensions.push({ type: 'PSK_KEY_EXCHANGE_MODES', value: [1] });
524
+ }
521
525
 
522
526
  // SNI (must be first)
523
527
  if (context.local_sni) extensions.unshift({ type: 'SERVER_NAME', value: context.local_sni });
@@ -543,6 +547,11 @@ function TLSSession(options){
543
547
  random: context.local_random,
544
548
  session_id: context.local_session_id,
545
549
  cookie: context.dtls_cookie,
550
+ // BUGFIX: `cipher_suites` (plural) is what wire.js's client_hello
551
+ // builder actually reads; the singular key was silently ignored and
552
+ // wire.js substituted its own hardcoded fallback list — the
553
+ // configured cipher suites never reached the wire. Both names passed.
554
+ cipher_suites: context.local_supported_cipher_suites,
546
555
  cipher_suite: context.local_supported_cipher_suites,
547
556
  extensions: extensions,
548
557
  });
@@ -2690,9 +2699,22 @@ function TLSSession(options){
2690
2699
  context.local_session_id=new Uint8Array(crypto.randomBytes(32));
2691
2700
  }
2692
2701
 
2702
+ // TLS 1.3-only mode (QUIC and any client that offers no other version):
2703
+ // the ClientHello must not carry TLS 1.2 baggage. QUIC mandates TLS 1.3
2704
+ // (RFC 9001), and strict 1.3-only stacks reject hellos with legacy
2705
+ // artifacts. Detected from the configured versions, so plain TLS over
2706
+ // TCP (which offers 1.3+1.2) keeps the full compatibility behavior.
2707
+ let tls13Only = context.local_supported_versions.length > 0 &&
2708
+ context.local_supported_versions.every(v => v === 0x0304);
2709
+
2693
2710
  // Support both TLS 1.3 and 1.2 (server picks the best)
2694
2711
  if(context.local_supported_cipher_suites.length<=0){
2695
- context.local_supported_cipher_suites=[
2712
+ context.local_supported_cipher_suites = tls13Only ? [
2713
+ // TLS 1.3 only — no ECDHE 1.2 suites in a QUIC hello
2714
+ 0x1301, // TLS_AES_128_GCM_SHA256
2715
+ 0x1302, // TLS_AES_256_GCM_SHA384
2716
+ 0x1303, // TLS_CHACHA20_POLY1305_SHA256
2717
+ ] : [
2696
2718
  // TLS 1.3
2697
2719
  0x1301, // TLS_AES_128_GCM_SHA256
2698
2720
  0x1302, // TLS_AES_256_GCM_SHA384
@@ -2722,6 +2744,23 @@ function TLSSession(options){
2722
2744
  private_key: private_key
2723
2745
  };
2724
2746
 
2747
+ // Honor the configured signature algorithms; the hardcoded list is only
2748
+ // the default when none were set (previously the configured list was
2749
+ // ignored here — while the HRR CH2 hardcoded its own, so CH1 and CH2
2750
+ // could differ, violating RFC 8446 §4.1.2's exact-match requirement).
2751
+ let ch_sigalgs = (context.local_supported_signature_algorithms &&
2752
+ context.local_supported_signature_algorithms.length > 0)
2753
+ ? context.local_supported_signature_algorithms
2754
+ : [
2755
+ // TLS 1.3 (PSS + ECDSA + EdDSA)
2756
+ 0x0804, 0x0805, 0x0806,
2757
+ 0x0403, 0x0503, 0x0603,
2758
+ 0x0807, 0x0808,
2759
+ // TLS 1.2 (PKCS1)
2760
+ 0x0401, 0x0501, 0x0601
2761
+ ];
2762
+ context.local_supported_signature_algorithms = ch_sigalgs; // for CH2 reuse
2763
+
2725
2764
  let extensions = [
2726
2765
  {
2727
2766
  type: 'SUPPORTED_VERSIONS',
@@ -2740,19 +2779,20 @@ function TLSSession(options){
2740
2779
  },
2741
2780
  {
2742
2781
  type: 'SIGNATURE_ALGORITHMS',
2743
- value: [
2744
- // TLS 1.3 (PSS + ECDSA)
2745
- 0x0804, 0x0805, 0x0806,
2746
- 0x0403, 0x0503, 0x0603,
2747
- 0x0807, 0x0808,
2748
- // TLS 1.2 (PKCS1)
2749
- 0x0401, 0x0501, 0x0601
2750
- ]
2751
- },
2752
- // TLS 1.2 compatibility
2753
- { type: 'RENEGOTIATION_INFO', value: new Uint8Array(0) },
2754
- { type: 'EXTENDED_MASTER_SECRET', value: null }
2782
+ value: ch_sigalgs
2783
+ }
2755
2784
  ];
2785
+ if (!tls13Only) {
2786
+ // TLS 1.2 compatibility — meaningless (and suspicious to strict
2787
+ // stacks) in a 1.3-only/QUIC hello
2788
+ extensions.push({ type: 'RENEGOTIATION_INFO', value: new Uint8Array(0) });
2789
+ extensions.push({ type: 'EXTENDED_MASTER_SECRET', value: null });
2790
+ } else {
2791
+ // psk_key_exchange_modes: every mainstream 1.3 client sends it, and a
2792
+ // server MUST NOT send NewSessionTicket to a client that omitted it
2793
+ // (RFC 8446 §4.2.9) — required for future 0-RTT/resumption anyway.
2794
+ extensions.push({ type: 'PSK_KEY_EXCHANGE_MODES', value: [1] });
2795
+ }
2756
2796
 
2757
2797
  // Add SNI if servername was provided
2758
2798
  if (context.local_sni) {
@@ -2822,6 +2862,11 @@ function TLSSession(options){
2822
2862
  random: context.local_random,
2823
2863
  session_id: context.local_session_id,
2824
2864
  cookie: context.dtls_cookie,
2865
+ // BUGFIX: `cipher_suites` (plural) is what wire.js's client_hello
2866
+ // builder actually reads; the singular key was silently ignored and
2867
+ // wire.js substituted its own hardcoded fallback list — the
2868
+ // configured cipher suites never reached the wire. Both names passed.
2869
+ cipher_suites: context.local_supported_cipher_suites,
2825
2870
  cipher_suite: context.local_supported_cipher_suites,
2826
2871
  extensions: extensions
2827
2872
  };
@@ -2877,6 +2922,11 @@ function TLSSession(options){
2877
2922
  random: context.local_random,
2878
2923
  session_id: sid,
2879
2924
  cookie: context.dtls_cookie,
2925
+ // BUGFIX: `cipher_suites` (plural) is what wire.js's client_hello
2926
+ // builder actually reads; the singular key was silently ignored and
2927
+ // wire.js substituted its own hardcoded fallback list — the
2928
+ // configured cipher suites never reached the wire. Both names passed.
2929
+ cipher_suites: context.local_supported_cipher_suites,
2880
2930
  cipher_suite: context.local_supported_cipher_suites,
2881
2931
  extensions: extensions
2882
2932
  };
@@ -2887,7 +2937,9 @@ function TLSSession(options){
2887
2937
  // Skip for DTLS (DTLS clients/servers often don't implement RFC 5077 fully,
2888
2938
  // and adding it caused interop issues with openssl s_server -dtls1_2).
2889
2939
  let isDtls = context.local_supported_versions && context.local_supported_versions.some(v => (v & 0xFF00) === 0xFE00);
2890
- if (!isDtls && context.sessionTickets) {
2940
+ if (!isDtls && !tls13Only && context.sessionTickets) {
2941
+ // RFC 5077 SessionTicket is a TLS 1.2 mechanism; 1.3 resumption uses
2942
+ // NewSessionTicket/PSK. Never advertise it in a 1.3-only/QUIC hello.
2891
2943
  extensions.push({ type: 'SESSION_TICKET', value: new Uint8Array(0) });
2892
2944
  }
2893
2945
 
@@ -2898,6 +2950,11 @@ function TLSSession(options){
2898
2950
  random: context.local_random,
2899
2951
  session_id: context.local_session_id,
2900
2952
  cookie: context.dtls_cookie,
2953
+ // BUGFIX: `cipher_suites` (plural) is what wire.js's client_hello
2954
+ // builder actually reads; the singular key was silently ignored and
2955
+ // wire.js substituted its own hardcoded fallback list — the
2956
+ // configured cipher suites never reached the wire. Both names passed.
2957
+ cipher_suites: context.local_supported_cipher_suites,
2901
2958
  cipher_suite: context.local_supported_cipher_suites,
2902
2959
  extensions: extensions
2903
2960
  };
@@ -3108,5 +3165,4 @@ function TLSSession(options){
3108
3165
  return this;
3109
3166
  }
3110
3167
 
3111
- export default TLSSession;
3112
-
3168
+ export default TLSSession;