11import * as tls from 'tls' ;
2+ import * as crypto from 'node:crypto' ;
23
34import { ConnectionProcessor } from './process-connection.js' ;
45import { LocalCA } from './tls-certificates/local-ca.js' ;
5- import { CertOptions } from './tls-certificates/cert-definitions.js' ;
6+ import { CertOptions , calculateCertCacheKey } from './tls-certificates/cert-definitions.js' ;
7+ import { SecureContextCache } from './tls-certificates/secure-context-cache.js' ;
68import { tlsEndpoints } from './endpoints/endpoint-index.js' ;
79
10+ const secureContextCache = new SecureContextCache ( ) ;
11+
12+ function calculateContextCacheKey (
13+ domain : string ,
14+ certOptions : CertOptions ,
15+ tlsOptions : tls . SecureContextOptions
16+ ) : string {
17+ const certKey = calculateCertCacheKey ( domain , certOptions ) ;
18+ const tlsKey = Object . keys ( tlsOptions ) . length > 0
19+ ? '|' + JSON . stringify ( tlsOptions , Object . keys ( tlsOptions ) . sort ( ) )
20+ : '' ;
21+ return certKey + tlsKey ;
22+ }
23+
24+ function getCertExpiry ( certPem : string ) : number {
25+ const cert = new crypto . X509Certificate ( certPem ) ;
26+ return new Date ( cert . validTo ) . getTime ( ) ;
27+ }
28+
829export type CertGenerator = ( domain : string , certOptions : CertOptions ) => Promise < {
930 key : string ,
1031 cert : string ,
@@ -38,33 +59,37 @@ const MAX_SNI_PARTS = 3;
3859const PROACTIVE_DOMAIN_REFRESH_INTERVAL = 1000 * 60 * 60 * 24 ; // Daily cert check for proactive domains
3960
4061function proactivelyRefreshDomains ( rootDomain : string , domains : string [ ] , certGenerator : CertGenerator ) {
41- domains . forEach ( domain => {
42- const serverNameParts = getSNIPrefixParts ( domain , rootDomain ) ;
43-
44- const endpoints = getEndpoints ( serverNameParts ) ;
45- let certOptions : CertOptions = { } ;
46- for ( let endpoint of endpoints ) {
47- certOptions = Object . assign ( certOptions , endpoint . configureCertOptions ?.( ) ) ;
48- }
62+ for ( const domain of domains ) {
63+ const { certOptions } = getEndpointConfig ( getSNIPrefixParts ( domain , rootDomain ) ) ;
4964
50- console . log ( `Proactively checking cert at startup for ${ domain } ` ) ;
51- certGenerator ( domain , certOptions ) . catch ( e => console . error ( `Failed to generate cert for ${ domain } :` , e ) ) ;
52-
53- setInterval ( ( ) => {
65+ const refresh = ( ) => {
5466 console . log ( `Proactively checking cert for ${ domain } ` ) ;
55- certGenerator ( domain , certOptions ) . catch ( e => console . error ( `Failed to generate cert for ${ domain } :` , e ) ) ;
56- } , PROACTIVE_DOMAIN_REFRESH_INTERVAL ) ;
57- } ) ;
67+ certGenerator ( domain , certOptions ) . catch ( e =>
68+ console . error ( `Failed to generate cert for ${ domain } :` , e )
69+ ) ;
70+ } ;
71+
72+ refresh ( ) ;
73+ setInterval ( refresh , PROACTIVE_DOMAIN_REFRESH_INTERVAL ) ;
74+ }
5875}
5976
60- function getEndpoints ( serverNameParts : string [ ] ) {
61- return serverNameParts . map ( ( part ) => {
62- const endpoint = tlsEndpoints . find ( e => e . sniPart === part )
77+ function getEndpointConfig ( serverNameParts : string [ ] ) {
78+ let certOptions : CertOptions = { } ;
79+ let tlsOptions : tls . SecureContextOptions = { } ;
80+ let alpnPreferences : string [ ] = [ ] ;
81+
82+ for ( const part of serverNameParts ) {
83+ const endpoint = tlsEndpoints . find ( e => e . sniPart === part ) ;
6384 if ( ! endpoint ) {
6485 throw new Error ( `Unknown SNI part ${ part } ` ) ;
6586 }
66- return endpoint ;
67- } ) ;
87+ certOptions = Object . assign ( certOptions , endpoint . configureCertOptions ?.( ) ) ;
88+ tlsOptions = endpoint . configureTlsOptions ?.( tlsOptions ) ?? tlsOptions ;
89+ alpnPreferences = endpoint . configureAlpnPreferences ?.( alpnPreferences ) ?? alpnPreferences ;
90+ }
91+
92+ return { certOptions, tlsOptions, alpnPreferences } ;
6893}
6994
7095export async function createTlsHandler (
@@ -77,24 +102,10 @@ export async function createTlsHandler(
77102 ca : [ tlsConfig . ca ] ,
78103
79104 ALPNCallback : ( { servername, protocols : clientProtocols } ) => {
80- // If specific protocol(s) are provided as part of the server name,
81- // only negotiate those via ALPN.
82- const serverNameParts = getSNIPrefixParts ( servername , tlsConfig . rootDomain ) ;
83- const endpoints = getEndpoints ( serverNameParts ) ;
84-
85- let alpnPreferences : string [ ] = [ ] ;
86- for ( let endpoint of endpoints ) {
87- alpnPreferences = endpoint . configureAlpnPreferences ?.( alpnPreferences ) ?? alpnPreferences ;
88- }
89-
90- if ( alpnPreferences . length === 0 ) {
91- alpnPreferences = DEFAULT_ALPN_PROTOCOLS ;
92- }
93-
94- // Enforce our own protocol preference over the client's (they can
95- // specify a preference via SNI, if they so choose). This also means
96- // we accept a preference order in our SNI as well e.g. http2.http1.*.
97- return alpnPreferences . find ( protocol => clientProtocols . includes ( protocol ) ) ;
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 ) ) ;
98109 } ,
99110 SNICallback : async ( domain : string , cb : Function ) => {
100111 try {
@@ -109,30 +120,28 @@ export async function createTlsHandler(
109120 return cb ( new Error ( `Duplicate SNI parts in '${ domain } '` ) , null ) ;
110121 }
111122
112- const endpoints = getEndpoints ( serverNameParts ) ;
113-
114- let certOptions : CertOptions = { } ;
115- let tlsOptions : tls . SecureContextOptions = { } ;
116- for ( let endpoint of endpoints ) {
117- // Cert options are merged together directly:
118- certOptions = Object . assign ( certOptions , endpoint . configureCertOptions ?.( ) ) ;
123+ const { certOptions, tlsOptions } = getEndpointConfig ( serverNameParts ) ;
119124
120- // TLS options may be combined in more clever ways:
121- tlsOptions = endpoint . configureTlsOptions ?.( tlsOptions ) ?? tlsOptions ;
122- }
123-
124- const certDomain = ( certOptions . overridePrefix )
125+ const certDomain = certOptions . overridePrefix
125126 ? `${ certOptions . overridePrefix } .${ tlsConfig . rootDomain } `
126127 : domain ;
127128
128- const generatedCert = await tlsConfig . generateCertificate ( certDomain , certOptions ) ;
129-
130- cb ( null , tls . createSecureContext ( {
131- key : generatedCert . key ,
132- cert : generatedCert . cert ,
133- ca : generatedCert . ca ,
134- ...tlsOptions
135- } ) ) ;
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 ) ;
136145 } catch ( e ) {
137146 console . error ( 'TLS setup error' , e ) ;
138147 cb ( e ) ;
0 commit comments