11import * as tls from 'tls' ;
22import * as crypto from 'node:crypto' ;
3+ import * as stream from 'stream' ;
4+ import { EventEmitter } from 'events' ;
35
46import { ConnectionProcessor } from './process-connection.js' ;
57import { LocalCA } from './tls-certificates/local-ca.js' ;
68import { CertOptions , calculateCertCacheKey } from './tls-certificates/cert-definitions.js' ;
79import { SecureContextCache } from './tls-certificates/secure-context-cache.js' ;
810import { tlsEndpoints } from './endpoints/endpoint-index.js' ;
11+ import { ErrorLike } from '@httptoolkit/util' ;
912
1013const secureContextCache = new SecureContextCache ( ) ;
1114
@@ -40,7 +43,7 @@ interface TlsHandlerConfig {
4043 cert : string ;
4144 ca : string ;
4245 generateCertificate : CertGenerator ;
43- localCA ? : LocalCA ;
46+ localCA : LocalCA ;
4447}
4548
4649const DEFAULT_ALPN_PROTOCOLS = [ 'http/1.1' , 'h2' ] ;
@@ -54,7 +57,7 @@ const getSNIPrefixParts = (servername: string, rootDomain: string) => {
5457 return serverNamePrefix . split ( '.' ) ;
5558} ;
5659
57- const MAX_SNI_PARTS = 3 ;
60+ const MAX_SNI_PARTS = 4 ;
5861
5962const PROACTIVE_DOMAIN_REFRESH_INTERVAL = 1000 * 60 * 60 * 24 ; // Daily cert check for proactive domains
6063
@@ -92,82 +95,26 @@ function getEndpointConfig(serverNameParts: string[]) {
9295 return { certOptions, tlsOptions, alpnPreferences } ;
9396}
9497
95- export async function createTlsHandler (
96- tlsConfig : TlsHandlerConfig ,
97- connProcessor : ConnectionProcessor
98- ) {
99- const server = tls . createServer ( {
100- key : tlsConfig . key ,
101- cert : tlsConfig . cert ,
102- ca : [ tlsConfig . ca ] ,
103-
104- ALPNCallback : ( { servername, protocols : clientProtocols } ) => {
105- const { alpnPreferences } = getEndpointConfig ( getSNIPrefixParts ( servername , tlsConfig . rootDomain ) ) ;
106- const protocols = alpnPreferences . length > 0 ? alpnPreferences : DEFAULT_ALPN_PROTOCOLS ;
107- // Enforce our own preference order (client can specify via SNI e.g. http2.http1.*)
108- return protocols . find ( p => clientProtocols . includes ( p ) ) ;
109- } ,
110- SNICallback : async ( domain : string , cb : Function ) => {
111- try {
112- const serverNameParts = getSNIPrefixParts ( domain , tlsConfig . rootDomain ) ;
113-
114- if ( serverNameParts . length > MAX_SNI_PARTS ) {
115- return cb ( new Error ( `Too many SNI parts (${ serverNameParts . length } )` ) , null ) ;
116- }
117-
118- const uniqueParts = new Set ( serverNameParts ) ;
119- if ( uniqueParts . size !== serverNameParts . length ) {
120- return cb ( new Error ( `Duplicate SNI parts in '${ domain } '` ) , null ) ;
121- }
122-
123- const { certOptions, tlsOptions } = getEndpointConfig ( serverNameParts ) ;
124-
125- const certDomain = certOptions . overridePrefix
126- ? `${ certOptions . overridePrefix } .${ tlsConfig . rootDomain } `
127- : domain ;
128-
129- const cacheKey = calculateContextCacheKey ( certDomain , certOptions , tlsOptions ) ;
130-
131- const secureContext = await secureContextCache . getOrCreate ( cacheKey , async ( ) => {
132- const cert = await tlsConfig . generateCertificate ( certDomain , certOptions ) ;
133- return {
134- context : tls . createSecureContext ( {
135- key : cert . key ,
136- cert : cert . cert ,
137- ca : cert . ca ,
138- ...tlsOptions
139- } ) ,
140- expiry : getCertExpiry ( cert . cert )
141- } ;
142- } ) ;
143-
144- cb ( null , secureContext ) ;
145- } catch ( e ) {
146- console . error ( 'TLS setup error' , e ) ;
147- cb ( e ) ;
148- }
149- }
150- } ) ;
151-
152- proactivelyRefreshDomains ( tlsConfig . rootDomain , tlsConfig . proactiveCertDomains ?? [ ] , tlsConfig . generateCertificate ) ;
153-
154- // Copy TLS fingerprint from underlying socket to TLS socket
155- server . prependListener ( 'secureConnection' , ( tlsSocket ) => {
156- const parent = ( tlsSocket as any ) . _parent ;
157- if ( parent ?. tlsClientHello ) {
158- ( tlsSocket as any ) . tlsClientHello = parent . tlsClientHello ;
159- }
160- } ) ;
98+ class TlsConnectionHandler {
16199
162- // Handle OCSP stapling requests
163- if ( tlsConfig . localCA ) {
164- server . on ( 'OCSPRequest' , async ( cert , issuer , callback ) => {
100+ // To keep Node happy, we need a TLS server attached to our sockets in some cases
101+ // to enable some features (like OCSP). This'll do:
102+ private ocspServer = new EventEmitter ( ) ;
103+
104+ constructor (
105+ private tlsConfig : TlsHandlerConfig ,
106+ private connProcessor : ConnectionProcessor
107+ ) {
108+ this . ocspServer . on ( 'OCSPRequest' , async (
109+ certificate : Buffer ,
110+ _issuer : Buffer ,
111+ callback : ( err : Error | null , response : Buffer ) => void
112+ ) => {
165113 try {
166- const ocspResponse = await tlsConfig . localCA ! . getOcspResponse ( cert ) ;
114+ const ocspResponse = await this . tlsConfig . localCA ! . getOcspResponse ( certificate ) ;
167115 if ( ocspResponse ) {
168116 callback ( null , ocspResponse ) ;
169117 } else {
170- // No OCSP response available - don't staple anything
171118 callback ( null , Buffer . alloc ( 0 ) ) ;
172119 }
173120 } catch ( e ) {
@@ -177,9 +124,98 @@ export async function createTlsHandler(
177124 } ) ;
178125 }
179126
180- server . on ( 'secureConnection' , ( socket ) => {
181- connProcessor . processConnection ( socket ) ;
182- } ) ;
127+ async handleConnection ( rawSocket : stream . Duplex ) {
128+ try {
129+ const serverName = rawSocket . tlsClientHello ?. serverName ;
130+ const domain = serverName || this . tlsConfig . rootDomain ;
131+
132+ const serverNameParts = getSNIPrefixParts ( domain , this . tlsConfig . rootDomain ) ;
133+
134+ if ( serverNameParts . length > MAX_SNI_PARTS ) {
135+ console . error ( `Too many SNI parts (${ serverNameParts . length } )` ) ;
136+ rawSocket . destroy ( ) ;
137+ return ;
138+ }
139+
140+ const uniqueParts = new Set ( serverNameParts ) ;
141+ if ( uniqueParts . size !== serverNameParts . length ) {
142+ console . error ( `Duplicate SNI parts in '${ domain } '` ) ;
143+ rawSocket . destroy ( ) ;
144+ return ;
145+ }
146+
147+ const { certOptions, tlsOptions, alpnPreferences } = getEndpointConfig ( serverNameParts ) ;
148+
149+ const certDomain = certOptions . overridePrefix
150+ ? `${ certOptions . overridePrefix } .${ this . tlsConfig . rootDomain } `
151+ : domain ;
152+
153+ const cacheKey = calculateContextCacheKey ( certDomain , certOptions , tlsOptions ) ;
154+
155+ const secureContext = await secureContextCache . getOrCreate ( cacheKey , async ( ) => {
156+ const cert = await this . tlsConfig . generateCertificate ( certDomain , certOptions ) ;
157+ return {
158+ context : tls . createSecureContext ( {
159+ key : cert . key ,
160+ cert : cert . cert ,
161+ ca : cert . ca ,
162+ ...tlsOptions
163+ } ) ,
164+ expiry : getCertExpiry ( cert . cert )
165+ } ;
166+ } ) ;
167+
168+ const alpnProtocols = alpnPreferences . length > 0
169+ ? alpnPreferences
170+ : DEFAULT_ALPN_PROTOCOLS ;
171+
172+ // Check if client requested OCSP stapling (extension 5 = status_request)
173+ const clientExtensions = rawSocket . tlsClientHello ?. fingerprintData ?. [ 2 ] ;
174+ const clientRequestedOCSP = clientExtensions ?. includes ( 5 ) ?? false ;
175+
176+ const tlsSocket = new tls . TLSSocket ( rawSocket , {
177+ isServer : true ,
178+ secureContext,
179+ ALPNProtocols : alpnProtocols ,
180+ // Only set up OCSP machinery if client requested it
181+ ...( clientRequestedOCSP ? {
182+ server : this . ocspServer as tls . Server ,
183+ // Stub SNICallback to works around a Node limitation where non-server TLS
184+ // sockets don't call OCSPRequest in most cases.
185+ SNICallback : (
186+ _servername : string ,
187+ callback : ( err : Error | null , ctx ?: tls . SecureContext ) => void
188+ ) => callback ( null , secureContext )
189+ } : { } )
190+ } ) ;
191+
192+ // Transfer tlsClientHello metadata
193+ if ( rawSocket . tlsClientHello ) {
194+ tlsSocket . tlsClientHello = rawSocket . tlsClientHello ;
195+ }
196+
197+ tlsSocket . on ( 'secure' , ( ) => {
198+ this . connProcessor . processConnection ( tlsSocket ) ;
199+ } ) ;
200+
201+ tlsSocket . on ( 'error' , ( err : ErrorLike ) => {
202+ // Expected errors during handshake (version mismatch, etc.)
203+ if ( err . code !== 'ECONNRESET' ) {
204+ console . error ( 'TLS socket error:' , err . message ) ;
205+ }
206+ } ) ;
207+ } catch ( e ) {
208+ console . error ( 'TLS setup error' , e ) ;
209+ rawSocket . destroy ( ) ;
210+ }
211+ }
212+ }
183213
184- return server ;
185- }
214+ export async function createTlsHandler (
215+ tlsConfig : TlsHandlerConfig ,
216+ connProcessor : ConnectionProcessor
217+ ) {
218+ const handler = new TlsConnectionHandler ( tlsConfig , connProcessor ) ;
219+ proactivelyRefreshDomains ( tlsConfig . rootDomain , tlsConfig . proactiveCertDomains ?? [ ] , tlsConfig . generateCertificate ) ;
220+ return handler ;
221+ }
0 commit comments