11import { ClientAuth } from '@libp2p/http-fetch/auth'
2- import { serviceCapabilities , serviceDependencies , start , stop } from '@libp2p/interface'
2+ import { serviceCapabilities , serviceDependencies , setMaxListeners , start , stop } from '@libp2p/interface'
33import { debounce } from '@libp2p/utils/debounce'
44import { X509Certificate } from '@peculiar/x509'
55import * as acme from 'acme-client'
6+ import { anySignal } from 'any-signal'
7+ import delay from 'delay'
68import { Key } from 'interface-datastore'
79import { base36 } from 'multiformats/bases/base36'
810import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
911import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
1012import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
11- import { DEFAULT_ACCOUNT_PRIVATE_KEY_BITS , DEFAULT_ACCOUNT_PRIVATE_KEY_NAME , DEFAULT_ACME_DIRECTORY , DEFAULT_CERTIFICATE_DATASTORE_KEY , DEFAULT_CERTIFICATE_PRIVATE_KEY_BITS , DEFAULT_CERTIFICATE_PRIVATE_KEY_NAME , DEFAULT_FORGE_DOMAIN , DEFAULT_FORGE_ENDPOINT , DEFAULT_PROVISION_DELAY , DEFAULT_PROVISION_TIMEOUT , DEFAULT_RENEWAL_THRESHOLD } from './constants.js'
13+ import { DEFAULT_ACCOUNT_PRIVATE_KEY_BITS , DEFAULT_ACCOUNT_PRIVATE_KEY_NAME , DEFAULT_ACME_DIRECTORY , DEFAULT_CERTIFICATE_DATASTORE_KEY , DEFAULT_CERTIFICATE_PRIVATE_KEY_BITS , DEFAULT_CERTIFICATE_PRIVATE_KEY_NAME , DEFAULT_FORGE_DOMAIN , DEFAULT_FORGE_ENDPOINT , DEFAULT_PROVISION_DELAY , DEFAULT_PROVISION_REQUEST_TIMEOUT , DEFAULT_PROVISION_TIMEOUT , DEFAULT_RENEWAL_THRESHOLD } from './constants.js'
1214import { DomainMapper } from './domain-mapper.js'
1315import { createCsr , importFromPem , loadOrCreateKey , supportedAddressesFilter } from './utils.js'
1416import type { AutoTLSComponents , AutoTLSInit , AutoTLS as AutoTLSInterface } from './index.js'
@@ -19,6 +21,8 @@ import type { DebouncedFunction } from '@libp2p/utils/debounce'
1921import type { Multiaddr } from '@multiformats/multiaddr'
2022import type { Datastore } from 'interface-datastore'
2123
24+ const RETRY_DELAY = 5_000
25+
2226type CertificateEvent = 'certificate:provision' | 'certificate:renew'
2327
2428interface Certificate {
@@ -40,6 +44,7 @@ export class AutoTLS implements AutoTLSInterface {
4044 private readonly acmeDirectory : URL
4145 private readonly clientAuth : ClientAuth
4246 private readonly provisionTimeout : number
47+ private readonly provisionRequestTimeout : number
4348 private readonly renewThreshold : number
4449 private started : boolean
4550 private shutdownController ?: AbortController
@@ -68,6 +73,7 @@ export class AutoTLS implements AutoTLSInterface {
6873 this . forgeDomain = init . forgeDomain ?? DEFAULT_FORGE_DOMAIN
6974 this . acmeDirectory = new URL ( init . acmeDirectory ?? DEFAULT_ACME_DIRECTORY )
7075 this . provisionTimeout = init . provisionTimeout ?? DEFAULT_PROVISION_TIMEOUT
76+ this . provisionRequestTimeout = init . provisionRequestTimeout ?? DEFAULT_PROVISION_REQUEST_TIMEOUT
7177 this . renewThreshold = init . renewThreshold ?? DEFAULT_RENEWAL_THRESHOLD
7278 this . accountPrivateKeyName = init . accountPrivateKeyName ?? DEFAULT_ACCOUNT_PRIVATE_KEY_NAME
7379 this . accountPrivateKeyBits = init . accountPrivateKeyBits ?? DEFAULT_ACCOUNT_PRIVATE_KEY_BITS
@@ -108,6 +114,7 @@ export class AutoTLS implements AutoTLSInterface {
108114 await start ( this . domainMapper )
109115 this . events . addEventListener ( 'self:peer:update' , this . onSelfPeerUpdate )
110116 this . shutdownController = new AbortController ( )
117+ setMaxListeners ( Infinity , this . shutdownController . signal )
111118 this . started = true
112119 }
113120
@@ -120,7 +127,8 @@ export class AutoTLS implements AutoTLSInterface {
120127 }
121128
122129 private _onSelfPeerUpdate ( ) : void {
123- const addresses = this . addressManager . getAddresses ( ) . filter ( supportedAddressesFilter )
130+ const addresses = this . addressManager . getAddresses ( )
131+ . filter ( supportedAddressesFilter )
124132
125133 if ( addresses . length === 0 ) {
126134 this . log ( 'not fetching certificate as we have no public addresses' )
@@ -139,11 +147,29 @@ export class AutoTLS implements AutoTLSInterface {
139147
140148 this . fetching = true
141149
142- this . fetchCertificate ( addresses , {
143- signal : AbortSignal . timeout ( this . provisionTimeout )
150+ Promise . resolve ( ) . then ( async ( ) => {
151+ let attempt = 0
152+
153+ while ( true ) {
154+ if ( this . shutdownController ?. signal . aborted === true ) {
155+ throw this . shutdownController . signal . reason
156+ }
157+
158+ try {
159+ await this . fetchCertificate ( addresses , {
160+ signal : AbortSignal . timeout ( this . provisionTimeout )
161+ } )
162+
163+ return
164+ } catch ( err ) {
165+ this . log . error ( 'provisioning certificate failed on attempt %d - %e' , attempt ++ , err )
166+ }
167+
168+ await delay ( RETRY_DELAY )
169+ }
144170 } )
145171 . catch ( err => {
146- this . log . error ( 'error fetching certificates - %e' , err )
172+ this . log . error ( 'giving up provisioning certificate - %e' , err )
147173 } )
148174 . finally ( ( ) => {
149175 this . fetching = false
@@ -190,7 +216,9 @@ export class AutoTLS implements AutoTLSInterface {
190216 // emit a certificate event
191217 this . log ( 'dispatching %s' , event )
192218 this . events . safeDispatchEvent ( event , {
193- detail : this . certificate
219+ detail : {
220+ ...this . certificate
221+ }
194222 } )
195223 }
196224
@@ -271,7 +299,33 @@ export class AutoTLS implements AutoTLSInterface {
271299 email : this . email ,
272300 termsOfServiceAgreed : true ,
273301 challengeCreateFn : async ( authz , challenge , keyAuthorization ) => {
274- await this . configureAcmeChallengeResponse ( multiaddrs , keyAuthorization , options )
302+ const signal = anySignal ( [ this . shutdownController ?. signal , options ?. signal ] )
303+ setMaxListeners ( Infinity , signal )
304+
305+ let attempt = 0
306+
307+ while ( true ) {
308+ if ( signal . aborted ) {
309+ throw signal . reason
310+ }
311+
312+ try {
313+ const timeout = AbortSignal . timeout ( this . provisionRequestTimeout )
314+ const signal = anySignal ( [ timeout , options ?. signal ] )
315+ setMaxListeners ( Infinity , timeout , signal )
316+
317+ await this . configureAcmeChallengeResponse ( multiaddrs , keyAuthorization , {
318+ ...options ,
319+ signal
320+ } )
321+
322+ return
323+ } catch ( err : any ) {
324+ this . log . error ( 'contacting %s failed on attempt %d - %e' , this . forgeEndpoint , attempt ++ , err . cause ?? err )
325+ }
326+
327+ await delay ( RETRY_DELAY )
328+ }
275329 } ,
276330 challengeRemoveFn : async ( authz , challenge , keyAuthorization ) => {
277331 // no-op
@@ -285,8 +339,9 @@ export class AutoTLS implements AutoTLSInterface {
285339 const addresses = multiaddrs . map ( ma => ma . toString ( ) )
286340
287341 const endpoint = `${ this . forgeEndpoint } v1/_acme-challenge`
288- this . log ( 'asking %sv1/_acme-challenge to respond to the acme DNS challenge on our behalf' , endpoint )
342+ this . log ( 'asking %s to respond to the acme DNS challenge on our behalf' , endpoint )
289343 this . log ( 'dialback public addresses: %s' , addresses . join ( ', ' ) )
344+
290345 const response = await this . clientAuth . authenticatedFetch ( endpoint , {
291346 method : 'POST' ,
292347 headers : {
0 commit comments