lemon-tls 0.3.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.
- package/package.json +2 -2
- package/src/dtls_socket.js +3 -0
- package/src/session/message.js +1 -1
- package/src/tls_session.js +121 -30
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lemon-tls",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.4.1",
|
|
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",
|
|
7
7
|
"types": "index.d.ts",
|
package/src/dtls_socket.js
CHANGED
|
@@ -44,6 +44,7 @@ function DTLSSocket(udpSocket, options) {
|
|
|
44
44
|
cert: options.cert,
|
|
45
45
|
key: options.key,
|
|
46
46
|
rejectUnauthorized: options.rejectUnauthorized,
|
|
47
|
+
requestCert: options.requestCert,
|
|
47
48
|
ca: options.ca,
|
|
48
49
|
alpnProtocols: options.alpnProtocols,
|
|
49
50
|
minVersion: options.minVersion,
|
|
@@ -159,6 +160,7 @@ function createDTLSServer(options, connectionListener) {
|
|
|
159
160
|
remotePort: rinfo.port,
|
|
160
161
|
cert: options.cert,
|
|
161
162
|
key: options.key,
|
|
163
|
+
requestCert: options.requestCert,
|
|
162
164
|
SNICallback: options.SNICallback,
|
|
163
165
|
alpnProtocols: options.alpnProtocols,
|
|
164
166
|
minVersion: options.minVersion,
|
|
@@ -239,6 +241,7 @@ function connectDTLS(options, callback) {
|
|
|
239
241
|
remotePort: port,
|
|
240
242
|
servername: options.servername || host,
|
|
241
243
|
rejectUnauthorized: options.rejectUnauthorized,
|
|
244
|
+
requestCert: options.requestCert,
|
|
242
245
|
ca: options.ca,
|
|
243
246
|
alpnProtocols: options.alpnProtocols,
|
|
244
247
|
minVersion: options.minVersion,
|
package/src/session/message.js
CHANGED
|
@@ -29,7 +29,7 @@ function normalize_hello(hello) {
|
|
|
29
29
|
} else if (name === 'KEY_SHARE') {
|
|
30
30
|
if (!('key_groups' in out)) out.key_groups = [];
|
|
31
31
|
if (!('supported_groups' in out)) out.supported_groups = [];
|
|
32
|
-
for (let i2
|
|
32
|
+
for (let i2 = 0; i2 < value.length; i2++) {
|
|
33
33
|
if (out.supported_groups.indexOf(value[i2].group) < 0) {
|
|
34
34
|
out.supported_groups.push(value[i2].group);
|
|
35
35
|
}
|
package/src/tls_session.js
CHANGED
|
@@ -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 });
|
|
@@ -533,7 +537,7 @@ function TLSSession(options){
|
|
|
533
537
|
}
|
|
534
538
|
|
|
535
539
|
// Custom extensions (e.g. QUIC transport params 0x39)
|
|
536
|
-
for (let ci
|
|
540
|
+
for (let ci = 0; ci < context.local_extensions.length; ci++) {
|
|
537
541
|
extensions.push(context.local_extensions[ci]);
|
|
538
542
|
}
|
|
539
543
|
|
|
@@ -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
|
});
|
|
@@ -999,7 +1008,7 @@ function TLSSession(options){
|
|
|
999
1008
|
|
|
1000
1009
|
|
|
1001
1010
|
if('add_local_key_groups' in options){
|
|
1002
|
-
for(let i
|
|
1011
|
+
for(let i = 0; i < options['add_local_key_groups'].length; i++){
|
|
1003
1012
|
|
|
1004
1013
|
let group=options['add_local_key_groups'][i].group;
|
|
1005
1014
|
if(group in context.local_key_groups==false){
|
|
@@ -1029,7 +1038,7 @@ function TLSSession(options){
|
|
|
1029
1038
|
|
|
1030
1039
|
|
|
1031
1040
|
if('add_remote_key_groups' in options){
|
|
1032
|
-
for(let i
|
|
1041
|
+
for(let i = 0; i < options['add_remote_key_groups'].length; i++){
|
|
1033
1042
|
|
|
1034
1043
|
let group=options['add_remote_key_groups'][i].group;
|
|
1035
1044
|
if(group in context.remote_key_groups==false){
|
|
@@ -1779,7 +1788,7 @@ function TLSSession(options){
|
|
|
1779
1788
|
}
|
|
1780
1789
|
|
|
1781
1790
|
|
|
1782
|
-
for(let i
|
|
1791
|
+
for(let i = 0; i < context.local_extensions.length; i++){
|
|
1783
1792
|
extensions.push(context.local_extensions[i]);
|
|
1784
1793
|
}
|
|
1785
1794
|
|
|
@@ -2105,6 +2114,41 @@ function TLSSession(options){
|
|
|
2105
2114
|
}
|
|
2106
2115
|
}
|
|
2107
2116
|
|
|
2117
|
+
// TLS 1.2 / DTLS 1.2 server: send CertificateRequest if mutual auth was
|
|
2118
|
+
// requested via { requestCert: true }. Per RFC 5246 §7.4.4 this message
|
|
2119
|
+
// goes between ServerKeyExchange and ServerHelloDone. The TLS 1.3 path
|
|
2120
|
+
// (line ~1805) sends CertificateRequest between EncryptedExtensions and
|
|
2121
|
+
// Certificate and is handled separately — version branching matters
|
|
2122
|
+
// because the wire formats differ (RFC 5246 §7.4.4 vs RFC 8446 §4.3.2).
|
|
2123
|
+
//
|
|
2124
|
+
// Without this block, a 1.2 server that sets requestCert never actually
|
|
2125
|
+
// requests the client's certificate, the client never sends one (TLS
|
|
2126
|
+
// clients only send a cert in response to CertificateRequest), and any
|
|
2127
|
+
// application-layer fingerprint check on the server fails with "peer
|
|
2128
|
+
// presented no certificate" — most notably breaking WebRTC, which
|
|
2129
|
+
// mandates mutual authentication (RFC 8827 §6.5) and pins to DTLS 1.2.
|
|
2130
|
+
if (context.isServer == true && context.requestCert == true &&
|
|
2131
|
+
!context.certificateRequestSent &&
|
|
2132
|
+
context.key_exchange_sent == true && !context.hello_done_sent &&
|
|
2133
|
+
(context.selected_version === wire.TLS_VERSION.TLS1_2 ||
|
|
2134
|
+
context.selected_version === wire.DTLS_VERSION.DTLS1_2)) {
|
|
2135
|
+
|
|
2136
|
+
let cr_body = wire.build_certificate_request({
|
|
2137
|
+
version: wire.TLS_VERSION.TLS1_2,
|
|
2138
|
+
// rsa_sign(1), ecdsa_sign(64) — accept both. WebRTC uses ECDSA,
|
|
2139
|
+
// most other 1.2 deployments use RSA. The client picks whichever
|
|
2140
|
+
// matches its certificate.
|
|
2141
|
+
certificate_types: [1, 64],
|
|
2142
|
+
signature_algorithms: context.local_supported_signature_algorithms || [],
|
|
2143
|
+
certificate_authorities: [], // empty: accept any CA
|
|
2144
|
+
});
|
|
2145
|
+
let cr_data = wire.build_message(wire.TLS_MESSAGE_TYPE.CERTIFICATE_REQUEST, cr_body);
|
|
2146
|
+
pushTranscript(cr_data);
|
|
2147
|
+
context.certificateRequestSent = true;
|
|
2148
|
+
ev.emit('message', 0, context.message_sent_seq, 'certificate_request', cr_data);
|
|
2149
|
+
context.message_sent_seq++;
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2108
2152
|
//server hello done - 1.2 only...
|
|
2109
2153
|
if(context.isServer==true && (context.selected_version === wire.TLS_VERSION.TLS1_2 || context.selected_version === wire.DTLS_VERSION.DTLS1_2)){
|
|
2110
2154
|
if(context.hello_done_sent==false && context.key_exchange_sent==true){
|
|
@@ -2655,9 +2699,22 @@ function TLSSession(options){
|
|
|
2655
2699
|
context.local_session_id=new Uint8Array(crypto.randomBytes(32));
|
|
2656
2700
|
}
|
|
2657
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
|
+
|
|
2658
2710
|
// Support both TLS 1.3 and 1.2 (server picks the best)
|
|
2659
2711
|
if(context.local_supported_cipher_suites.length<=0){
|
|
2660
|
-
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
|
+
] : [
|
|
2661
2718
|
// TLS 1.3
|
|
2662
2719
|
0x1301, // TLS_AES_128_GCM_SHA256
|
|
2663
2720
|
0x1302, // TLS_AES_256_GCM_SHA384
|
|
@@ -2687,6 +2744,23 @@ function TLSSession(options){
|
|
|
2687
2744
|
private_key: private_key
|
|
2688
2745
|
};
|
|
2689
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
|
+
|
|
2690
2764
|
let extensions = [
|
|
2691
2765
|
{
|
|
2692
2766
|
type: 'SUPPORTED_VERSIONS',
|
|
@@ -2705,19 +2779,20 @@ function TLSSession(options){
|
|
|
2705
2779
|
},
|
|
2706
2780
|
{
|
|
2707
2781
|
type: 'SIGNATURE_ALGORITHMS',
|
|
2708
|
-
value:
|
|
2709
|
-
|
|
2710
|
-
0x0804, 0x0805, 0x0806,
|
|
2711
|
-
0x0403, 0x0503, 0x0603,
|
|
2712
|
-
0x0807, 0x0808,
|
|
2713
|
-
// TLS 1.2 (PKCS1)
|
|
2714
|
-
0x0401, 0x0501, 0x0601
|
|
2715
|
-
]
|
|
2716
|
-
},
|
|
2717
|
-
// TLS 1.2 compatibility
|
|
2718
|
-
{ type: 'RENEGOTIATION_INFO', value: new Uint8Array(0) },
|
|
2719
|
-
{ type: 'EXTENDED_MASTER_SECRET', value: null }
|
|
2782
|
+
value: ch_sigalgs
|
|
2783
|
+
}
|
|
2720
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
|
+
}
|
|
2721
2796
|
|
|
2722
2797
|
// Add SNI if servername was provided
|
|
2723
2798
|
if (context.local_sni) {
|
|
@@ -2730,7 +2805,7 @@ function TLSSession(options){
|
|
|
2730
2805
|
}
|
|
2731
2806
|
|
|
2732
2807
|
// Add custom extensions (e.g. QUIC transport params 0x39)
|
|
2733
|
-
for (let i
|
|
2808
|
+
for (let i = 0; i < context.local_extensions.length; i++) {
|
|
2734
2809
|
extensions.push(context.local_extensions[i]);
|
|
2735
2810
|
}
|
|
2736
2811
|
|
|
@@ -2787,6 +2862,11 @@ function TLSSession(options){
|
|
|
2787
2862
|
random: context.local_random,
|
|
2788
2863
|
session_id: context.local_session_id,
|
|
2789
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,
|
|
2790
2870
|
cipher_suite: context.local_supported_cipher_suites,
|
|
2791
2871
|
extensions: extensions
|
|
2792
2872
|
};
|
|
@@ -2842,6 +2922,11 @@ function TLSSession(options){
|
|
|
2842
2922
|
random: context.local_random,
|
|
2843
2923
|
session_id: sid,
|
|
2844
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,
|
|
2845
2930
|
cipher_suite: context.local_supported_cipher_suites,
|
|
2846
2931
|
extensions: extensions
|
|
2847
2932
|
};
|
|
@@ -2852,7 +2937,9 @@ function TLSSession(options){
|
|
|
2852
2937
|
// Skip for DTLS (DTLS clients/servers often don't implement RFC 5077 fully,
|
|
2853
2938
|
// and adding it caused interop issues with openssl s_server -dtls1_2).
|
|
2854
2939
|
let isDtls = context.local_supported_versions && context.local_supported_versions.some(v => (v & 0xFF00) === 0xFE00);
|
|
2855
|
-
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.
|
|
2856
2943
|
extensions.push({ type: 'SESSION_TICKET', value: new Uint8Array(0) });
|
|
2857
2944
|
}
|
|
2858
2945
|
|
|
@@ -2863,6 +2950,11 @@ function TLSSession(options){
|
|
|
2863
2950
|
random: context.local_random,
|
|
2864
2951
|
session_id: context.local_session_id,
|
|
2865
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,
|
|
2866
2958
|
cipher_suite: context.local_supported_cipher_suites,
|
|
2867
2959
|
extensions: extensions
|
|
2868
2960
|
};
|
|
@@ -3073,5 +3165,4 @@ function TLSSession(options){
|
|
|
3073
3165
|
return this;
|
|
3074
3166
|
}
|
|
3075
3167
|
|
|
3076
|
-
export default TLSSession;
|
|
3077
|
-
|
|
3168
|
+
export default TLSSession;
|