@@ -15,6 +15,7 @@ import { ConnectionProcessor } from './process-connection.js';
1515import { AcmeCA , AcmeProvider } from './tls-certificates/acme.js' ;
1616import { LocalCA , generateCACertificate } from './tls-certificates/local-ca.js' ;
1717import { PersistentCertCache } from './tls-certificates/cert-cache.js' ;
18+ import { DnsServer } from './dns-server.js' ;
1819
1920declare module 'stream' {
2021 interface Duplex {
@@ -38,6 +39,13 @@ interface ServerOptions {
3839 certCacheDir ?: string ;
3940 localCaKey ?: string ;
4041 localCaCert ?: string ;
42+ dnsServer ?: boolean ;
43+ }
44+
45+ function isWildcardCoverable ( domain : string , rootDomain : string ) : boolean {
46+ if ( ! domain . endsWith ( `.${ rootDomain } ` ) ) return false ;
47+ const prefix = domain . slice ( 0 , - rootDomain . length - 1 ) ;
48+ return ! prefix . includes ( '.' ) ; // Single-level subdomain only
4149}
4250
4351async function generateTlsConfig ( options : ServerOptions ) {
@@ -95,7 +103,16 @@ async function generateTlsConfig(options: ServerOptions) {
95103 throw new Error ( `Can't enable ACME without configuring an account key (via $ACME_ACCOUNT_KEY)` ) ;
96104 }
97105
98- const acmeCA = new AcmeCA ( certCache ! , options . acmeProvider , options . acmeAccountKey ) ;
106+ // Set up in-process DNS server for wildcard certs via DNS-01 (optional)
107+ let dnsServer : DnsServer | undefined ;
108+
109+ if ( options . dnsServer ) {
110+ dnsServer = new DnsServer ( 53 ) ;
111+ await dnsServer . listen ( ) ;
112+ console . log ( 'Using in-process DNS server for wildcard certs' ) ;
113+ }
114+
115+ const acmeCA = new AcmeCA ( certCache ! , options . acmeProvider , options . acmeAccountKey , dnsServer ) ;
99116 acmeCA . tryGetCertificateSync ( rootDomain , { } ) ; // Preload the root domain every time
100117
101118 return {
@@ -105,22 +122,29 @@ async function generateTlsConfig(options: ServerOptions) {
105122 cert : defaultCert . cert ,
106123 ca : caCert . cert ,
107124 localCA,
108- generateCertificate : async ( domain : string , options : CertOptions ) => {
109- if ( options . requiredType === 'local' ) {
110- return await localCA . generateCertificate ( domain , options ) ;
125+ generateCertificate : async ( domain : string , certOptions : CertOptions ) => {
126+ if ( certOptions . requiredType === 'local' ) {
127+ return await localCA . generateCertificate ( domain , certOptions ) ;
111128 }
112129
113- const cert = acmeCA . tryGetCertificateSync ( domain , options ) ;
130+ // Use wildcard when: DNS server available, single-level subdomain, no overridePrefix
131+ const useWildcard = dnsServer
132+ && isWildcardCoverable ( domain , rootDomain )
133+ && ! certOptions . overridePrefix ;
134+
135+ const effectiveDomain = useWildcard ? `*.${ rootDomain } ` : domain ;
136+
137+ const cert = acmeCA . tryGetCertificateSync ( effectiveDomain , certOptions ) ;
114138
115139 if ( cert ) {
116140 return cert ;
117141 } else {
118- if ( options . requiredType === 'acme' ) {
119- return await acmeCA . waitForCertificate ( domain , options ) ;
142+ if ( certOptions . requiredType === 'acme' ) {
143+ return await acmeCA . waitForCertificate ( effectiveDomain , certOptions ) ;
120144 }
121145 // Local CA fallback while ACME cert is pending - mark as temporary
122146 // so it gets a short cache time and ACME cert is used once available
123- const fallbackCert = await localCA . generateCertificate ( domain , options ) ;
147+ const fallbackCert = await localCA . generateCertificate ( domain , certOptions ) ;
124148 return { ...fallbackCert , isTemporary : true } ;
125149 }
126150 } ,
@@ -193,7 +217,8 @@ if (wasRunDirectly) {
193217 acmeAccountKey : process . env . ACME_ACCOUNT_KEY ,
194218 certCacheDir : process . env . CERT_CACHE_DIR ,
195219 localCaKey : process . env . LOCAL_CA_KEY ,
196- localCaCert : process . env . LOCAL_CA_CERT
220+ localCaCert : process . env . LOCAL_CA_CERT ,
221+ dnsServer : process . env . DNS_SERVER === 'true'
197222 } ) . then ( ( tcpHandler ) => {
198223 ports . forEach ( ( port ) => {
199224 const server = createTcpServer ( tcpHandler ) ;
0 commit comments