@@ -43,83 +43,117 @@ const SSL_OP_NO_ENCRYPT_THEN_MAC = 1 << 19;
4343
4444// All settings are designed to exactly match Firefox v103, since that's a good baseline
4545// that seems to be widely accepted and is easy to emulate from Node.js.
46- export const getUpstreamTlsOptions = ( { strictHttpsChecks, serverName } : {
47- strictHttpsChecks : boolean ,
48- serverName ?: string
49- } ) : tls . ConnectionOptions => ( {
50- servername : serverName && ! isIP ( serverName )
51- ? serverName
52- : undefined , // Can't send IPs in SNI
53- ecdhCurve : [
54- 'X25519' ,
55- 'prime256v1' , // N.B. Equivalent to secp256r1
56- 'secp384r1' ,
57- 'secp521r1' ,
58- ...( NEW_CURVES_SUPPORTED
59- ? [ // Only available with OpenSSL v3+:
60- 'ffdhe2048' ,
61- 'ffdhe3072'
62- ] : [ ]
63- )
64- ] . join ( ':' ) ,
65- sigalgs : [
66- 'ecdsa_secp256r1_sha256' ,
67- 'ecdsa_secp384r1_sha384' ,
68- 'ecdsa_secp521r1_sha512' ,
69- 'rsa_pss_rsae_sha256' ,
70- 'rsa_pss_rsae_sha384' ,
71- 'rsa_pss_rsae_sha512' ,
72- 'rsa_pkcs1_sha256' ,
73- 'rsa_pkcs1_sha384' ,
74- 'rsa_pkcs1_sha512' ,
75- 'ECDSA+SHA1' ,
76- 'rsa_pkcs1_sha1'
77- ] . join ( ':' ) ,
78- ciphers : [
79- 'TLS_AES_128_GCM_SHA256' ,
80- 'TLS_CHACHA20_POLY1305_SHA256' ,
81- 'TLS_AES_256_GCM_SHA384' ,
82- 'ECDHE-ECDSA-AES128-GCM-SHA256' ,
83- 'ECDHE-RSA-AES128-GCM-SHA256' ,
84- 'ECDHE-ECDSA-CHACHA20-POLY1305' ,
85- 'ECDHE-RSA-CHACHA20-POLY1305' ,
86- 'ECDHE-ECDSA-AES256-GCM-SHA384' ,
87- 'ECDHE-RSA-AES256-GCM-SHA384' ,
88- 'ECDHE-ECDSA-AES256-SHA' ,
89- 'ECDHE-ECDSA-AES128-SHA' ,
90- 'ECDHE-RSA-AES128-SHA' ,
91- 'ECDHE-RSA-AES256-SHA' ,
92- 'AES128-GCM-SHA256' ,
93- 'AES256-GCM-SHA384' ,
94- 'AES128-SHA' ,
95- 'AES256-SHA' ,
96-
97- // This magic cipher is the very obtuse way that OpenSSL downgrades the overall
98- // security level to allow various legacy settings, protocols & ciphers:
99- ...( ! strictHttpsChecks
100- ? [ '@SECLEVEL=0' ]
101- : [ ]
102- )
103- ] . join ( ':' ) ,
104- secureOptions : strictHttpsChecks
105- ? SSL_OP_TLSEXT_PADDING | SSL_OP_NO_ENCRYPT_THEN_MAC
106- : SSL_OP_TLSEXT_PADDING | SSL_OP_NO_ENCRYPT_THEN_MAC | SSL_OP_LEGACY_SERVER_CONNECT ,
107- ...( {
108- // Valid, but not included in Node.js TLS module types:
109- requestOSCP : true
110- } as any ) ,
111-
112- // Trust intermediate certificates from the trusted CA list too. Without this, trusted CAs
113- // are only used when they are self-signed root certificates. Seems to cause issues in Node v20
114- // in HTTP/2 tests, so disabled below the supported v22 version.
115- allowPartialTrustChain : semver . satisfies ( process . version , '>=22.9.0' ) ,
116-
117- // Allow TLSv1, if !strict:
118- minVersion : strictHttpsChecks ? tls . DEFAULT_MIN_VERSION : 'TLSv1' ,
119-
120- // Skip certificate validation entirely, if not strict:
121- rejectUnauthorized : strictHttpsChecks ,
122- } ) ;
46+ export function getUpstreamTlsOptions ( {
47+ hostname,
48+ port,
49+
50+ ignoreHostHttpsErrors,
51+ clientCertificateHostMap,
52+ trustedCAs
53+ } : {
54+ // The effective hostname & port we're connecting to - note that this isn't exactly
55+ // the same as the destination (e.g. if you tunnel to an IP but set a hostname via SNI
56+ // then this is the hostname, not the IP).
57+ hostname : string ,
58+ port : number ,
59+
60+ // The general config that's relevant to this request:
61+ ignoreHostHttpsErrors : string [ ] | boolean ,
62+ clientCertificateHostMap : { [ host : string ] : { pfx : Buffer , passphrase ?: string } } ,
63+ trustedCAs : Array < string > | undefined
64+ } ) : tls . ConnectionOptions {
65+ const strictHttpsChecks = shouldUseStrictHttps ( hostname , port , ignoreHostHttpsErrors ) ;
66+
67+ const hostWithPort = `${ hostname } :${ port } ` ;
68+ const clientCert = clientCertificateHostMap [ hostWithPort ] ||
69+ clientCertificateHostMap [ hostname ] ||
70+ { } ;
71+
72+ return {
73+ servername : hostname && ! isIP ( hostname )
74+ ? hostname
75+ : undefined , // Can't send IPs in SNI
76+
77+ // We precisely control the various TLS parameters here to limit TLS fingerprinting issues:
78+ ecdhCurve : [
79+ 'X25519' ,
80+ 'prime256v1' , // N.B. Equivalent to secp256r1
81+ 'secp384r1' ,
82+ 'secp521r1' ,
83+ ...( NEW_CURVES_SUPPORTED
84+ ? [ // Only available with OpenSSL v3+:
85+ 'ffdhe2048' ,
86+ 'ffdhe3072'
87+ ] : [ ]
88+ )
89+ ] . join ( ':' ) ,
90+ sigalgs : [
91+ 'ecdsa_secp256r1_sha256' ,
92+ 'ecdsa_secp384r1_sha384' ,
93+ 'ecdsa_secp521r1_sha512' ,
94+ 'rsa_pss_rsae_sha256' ,
95+ 'rsa_pss_rsae_sha384' ,
96+ 'rsa_pss_rsae_sha512' ,
97+ 'rsa_pkcs1_sha256' ,
98+ 'rsa_pkcs1_sha384' ,
99+ 'rsa_pkcs1_sha512' ,
100+ 'ECDSA+SHA1' ,
101+ 'rsa_pkcs1_sha1'
102+ ] . join ( ':' ) ,
103+ ciphers : [
104+ 'TLS_AES_128_GCM_SHA256' ,
105+ 'TLS_CHACHA20_POLY1305_SHA256' ,
106+ 'TLS_AES_256_GCM_SHA384' ,
107+ 'ECDHE-ECDSA-AES128-GCM-SHA256' ,
108+ 'ECDHE-RSA-AES128-GCM-SHA256' ,
109+ 'ECDHE-ECDSA-CHACHA20-POLY1305' ,
110+ 'ECDHE-RSA-CHACHA20-POLY1305' ,
111+ 'ECDHE-ECDSA-AES256-GCM-SHA384' ,
112+ 'ECDHE-RSA-AES256-GCM-SHA384' ,
113+ 'ECDHE-ECDSA-AES256-SHA' ,
114+ 'ECDHE-ECDSA-AES128-SHA' ,
115+ 'ECDHE-RSA-AES128-SHA' ,
116+ 'ECDHE-RSA-AES256-SHA' ,
117+ 'AES128-GCM-SHA256' ,
118+ 'AES256-GCM-SHA384' ,
119+ 'AES128-SHA' ,
120+ 'AES256-SHA' ,
121+
122+ // This magic cipher is the very obtuse way that OpenSSL downgrades the overall
123+ // security level to allow various legacy settings, protocols & ciphers:
124+ ...( ! strictHttpsChecks
125+ ? [ '@SECLEVEL=0' ]
126+ : [ ]
127+ )
128+ ] . join ( ':' ) ,
129+ secureOptions : strictHttpsChecks
130+ ? SSL_OP_TLSEXT_PADDING | SSL_OP_NO_ENCRYPT_THEN_MAC
131+ : SSL_OP_TLSEXT_PADDING | SSL_OP_NO_ENCRYPT_THEN_MAC | SSL_OP_LEGACY_SERVER_CONNECT ,
132+ ...( {
133+ // Valid, but not included in Node.js TLS module types:
134+ requestOSCP : true
135+ } as any ) ,
136+
137+ // Trust intermediate certificates from the trusted CA list too. Without this, trusted CAs
138+ // are only used when they are self-signed root certificates. Seems to cause issues in Node v20
139+ // in HTTP/2 tests, so disabled below the supported v22 version.
140+ allowPartialTrustChain : semver . satisfies ( process . version , '>=22.9.0' ) ,
141+
142+ // Allow TLSv1, if !strict:
143+ minVersion : strictHttpsChecks ? tls . DEFAULT_MIN_VERSION : 'TLSv1' ,
144+
145+ // Skip certificate validation entirely, if not strict:
146+ rejectUnauthorized : strictHttpsChecks ,
147+
148+ // Override the set of trusted CAs, if configured to do so:
149+ ...( trustedCAs ? {
150+ ca : trustedCAs
151+ } : { } ) ,
152+
153+ // Use a client cert, if one matches for this hostname+port:
154+ ...clientCert
155+ }
156+ }
123157
124158export async function getTrustedCAs (
125159 trustedCAs : Array < CADefinition > | undefined ,
@@ -478,7 +512,7 @@ export function getResponseContentLengthAfterModification(
478512
479513// Function to check if we should skip https errors for the current hostname and port,
480514// based on the given config
481- export function shouldUseStrictHttps (
515+ function shouldUseStrictHttps (
482516 hostname : string ,
483517 port : number ,
484518 ignoreHostHttpsErrors : string [ ] | boolean
0 commit comments