mirror of
https://github.com/vacp2p/nim-libp2p.git
synced 2026-01-10 11:48:15 -05:00
Compare commits
1 Commits
no-splitti
...
autotls-ma
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b755f9e13e |
@@ -19,8 +19,10 @@ const
|
||||
DefaultRandStringSize = 256
|
||||
ACMEHttpHeaders = [("Content-Type", "application/jose+json")]
|
||||
|
||||
type Nonce* = string
|
||||
type Domain* = string
|
||||
type Kid* = string
|
||||
type Nonce* = string
|
||||
type SignedACMERequest* = string
|
||||
|
||||
type ACMEDirectory* = object
|
||||
newNonce*: string
|
||||
@@ -95,12 +97,12 @@ type ACMEChallengeRequest = object
|
||||
type ACMEChallengeResponseBody = object
|
||||
status: ACMEChallengeStatus
|
||||
authorizations: seq[string]
|
||||
finalize: string
|
||||
finalizeURL: string
|
||||
|
||||
type ACMEChallengeResponse* = object
|
||||
status*: ACMEChallengeStatus
|
||||
authorizations*: seq[string]
|
||||
finalize*: string
|
||||
finalizeURL*: string
|
||||
orderURL*: string
|
||||
|
||||
type ACMEChallengeResponseWrapper* = object
|
||||
@@ -161,13 +163,13 @@ template handleError*(msg: string, body: untyped): untyped =
|
||||
raise newException(ACMEError, msg & ": Unexpected error", exc)
|
||||
|
||||
method post*(
|
||||
self: ACMEApi, url: string, payload: string
|
||||
self: ACMEApi, url: Uri, payload: SignedACMERequest
|
||||
): Future[HTTPResponse] {.
|
||||
async: (raises: [ACMEError, HttpError, CancelledError]), base
|
||||
.}
|
||||
|
||||
method get*(
|
||||
self: ACMEApi, url: string
|
||||
self: ACMEApi, url: Uri
|
||||
): Future[HTTPResponse] {.
|
||||
async: (raises: [ACMEError, HttpError, CancelledError]), base
|
||||
.}
|
||||
@@ -225,39 +227,39 @@ proc acmeHeader(
|
||||
)
|
||||
|
||||
method post*(
|
||||
self: ACMEApi, url: string, payload: string
|
||||
self: ACMEApi, uri: Uri, payload: SignedACMERequest
|
||||
): Future[HTTPResponse] {.
|
||||
async: (raises: [ACMEError, HttpError, CancelledError]), base
|
||||
.} =
|
||||
let rawResponse = await HttpClientRequestRef
|
||||
.post(self.session, url, body = payload, headers = ACMEHttpHeaders)
|
||||
.post(self.session, $uri, body = payload, headers = ACMEHttpHeaders)
|
||||
.get()
|
||||
.send()
|
||||
let body = await rawResponse.getResponseBody()
|
||||
HTTPResponse(body: body, headers: rawResponse.headers)
|
||||
|
||||
method get*(
|
||||
self: ACMEApi, url: string
|
||||
self: ACMEApi, uri: Uri
|
||||
): Future[HTTPResponse] {.
|
||||
async: (raises: [ACMEError, HttpError, CancelledError]), base
|
||||
.} =
|
||||
let rawResponse = await HttpClientRequestRef.get(self.session, url).get().send()
|
||||
let rawResponse = await HttpClientRequestRef.get(self.session, $uri).get().send()
|
||||
let body = await rawResponse.getResponseBody()
|
||||
HTTPResponse(body: body, headers: rawResponse.headers)
|
||||
|
||||
proc createSignedAcmeRequest(
|
||||
proc createSignedACMERequest(
|
||||
self: ACMEApi,
|
||||
url: string,
|
||||
uri: Uri,
|
||||
payload: auto,
|
||||
key: KeyPair,
|
||||
needsJwk: bool = false,
|
||||
kid: Opt[Kid] = Opt.none(Kid),
|
||||
): Future[string] {.async: (raises: [ACMEError, CancelledError]).} =
|
||||
): Future[SignedACMERequest] {.async: (raises: [ACMEError, CancelledError]).} =
|
||||
if key.pubkey.scheme != PKScheme.RSA or key.seckey.scheme != PKScheme.RSA:
|
||||
raise newException(ACMEError, "Unsupported signing key type")
|
||||
|
||||
let acmeHeader = await self.acmeHeader(url, key, needsJwk, kid)
|
||||
handleError("createSignedAcmeRequest"):
|
||||
handleError("createSignedACMERequest"):
|
||||
var token = toJWT(%*{"header": acmeHeader, "claims": payload})
|
||||
let derPrivKey = key.seckey.rsakey.getBytes.get
|
||||
let pemPrivKey: string = pemEncode(derPrivKey, "PRIVATE KEY")
|
||||
@@ -269,7 +271,7 @@ proc requestRegister*(
|
||||
): Future[ACMERegisterResponse] {.async: (raises: [ACMEError, CancelledError]).} =
|
||||
let registerRequest = ACMERegisterRequest(termsOfServiceAgreed: true)
|
||||
handleError("acmeRegister"):
|
||||
let payload = await self.createSignedAcmeRequest(
|
||||
let payload = await self.createSignedACMERequest(
|
||||
self.directory.newAccount, registerRequest, key, needsJwk = true
|
||||
)
|
||||
let acmeResponse = await self.post(self.directory.newAccount, payload)
|
||||
@@ -280,14 +282,14 @@ proc requestRegister*(
|
||||
)
|
||||
|
||||
proc requestNewOrder*(
|
||||
self: ACMEApi, domains: seq[string], key: KeyPair, kid: Kid
|
||||
self: ACMEApi, domains: seq[Domain], key: KeyPair, kid: Kid
|
||||
): Future[ACMEChallengeResponse] {.async: (raises: [ACMEError, CancelledError]).} =
|
||||
# request challenge from ACME server
|
||||
let orderRequest = ACMEChallengeRequest(
|
||||
identifiers: domains.mapIt(ACMEChallengeIdentifier(`type`: "dns", value: it))
|
||||
)
|
||||
handleError("requestNewOrder"):
|
||||
let payload = await self.createSignedAcmeRequest(
|
||||
let payload = await self.createSignedACMERequest(
|
||||
self.directory.newOrder, orderRequest, key, kid = Opt.some(kid)
|
||||
)
|
||||
let acmeResponse = await self.post(self.directory.newOrder, payload)
|
||||
@@ -298,7 +300,7 @@ proc requestNewOrder*(
|
||||
ACMEChallengeResponse(
|
||||
status: challengeResponseBody.status,
|
||||
authorizations: challengeResponseBody.authorizations,
|
||||
finalize: challengeResponseBody.finalize,
|
||||
finalizeURL: challengeResponseBody.finalize,
|
||||
orderURL: acmeResponse.headers.keyOrError("location"),
|
||||
)
|
||||
|
||||
@@ -311,7 +313,7 @@ proc requestAuthorizations*(
|
||||
acmeResponse.body.to(ACMEAuthorizationsResponse)
|
||||
|
||||
proc requestChallenge*(
|
||||
self: ACMEApi, domains: seq[string], key: KeyPair, kid: Kid
|
||||
self: ACMEApi, domains: seq[Domain], key: KeyPair, kid: Kid
|
||||
): Future[ACMEChallengeResponseWrapper] {.async: (raises: [ACMEError, CancelledError]).} =
|
||||
let challengeResponse = await self.requestNewOrder(domains, key, kid)
|
||||
|
||||
@@ -325,7 +327,7 @@ proc requestChallenge*(
|
||||
)
|
||||
|
||||
proc requestCheck*(
|
||||
self: ACMEApi, checkURL: string, checkKind: ACMECheckKind, key: KeyPair, kid: Kid
|
||||
self: ACMEApi, checkURL: Uri, checkKind: ACMECheckKind, key: KeyPair, kid: Kid
|
||||
): Future[ACMECheckResponse] {.async: (raises: [ACMEError, CancelledError]).} =
|
||||
handleError("requestCheck"):
|
||||
let acmeResponse = await self.get(checkURL)
|
||||
@@ -364,7 +366,7 @@ proc requestCompleted*(
|
||||
): Future[ACMECompletedResponse] {.async: (raises: [ACMEError, CancelledError]).} =
|
||||
handleError("requestCompleted (send notify)"):
|
||||
let payload =
|
||||
await self.createSignedAcmeRequest(chalURL, %*{}, key, kid = Opt.some(kid))
|
||||
await self.createSignedACMERequest(chalURL, %*{}, key, kid = Opt.some(kid))
|
||||
let acmeResponse = await self.post(chalURL, payload)
|
||||
acmeResponse.body.to(ACMECompletedResponse)
|
||||
|
||||
@@ -402,13 +404,13 @@ proc completeChallenge*(
|
||||
return await self.checkChallengeCompleted(chalURL, key, kid, retries = retries)
|
||||
|
||||
proc requestFinalize*(
|
||||
self: ACMEApi, domain: string, finalizeURL: string, key: KeyPair, kid: Kid
|
||||
self: ACMEApi, domain: Domain, finalizeURL: uri, key: KeyPair, kid: Kid
|
||||
): Future[ACMEFinalizeResponse] {.async: (raises: [ACMEError, CancelledError]).} =
|
||||
let derCSR = createCSR(domain)
|
||||
let b64CSR = base64.encode(derCSR.toSeq, safe = true)
|
||||
|
||||
handleError("requestFinalize"):
|
||||
let payload = await self.createSignedAcmeRequest(
|
||||
let payload = await self.createSignedACMERequest(
|
||||
finalizeURL, %*{"csr": b64CSR}, key, kid = Opt.some(kid)
|
||||
)
|
||||
let acmeResponse = await self.post(finalizeURL, payload)
|
||||
@@ -417,7 +419,7 @@ proc requestFinalize*(
|
||||
|
||||
proc checkCertFinalized*(
|
||||
self: ACMEApi,
|
||||
orderURL: string,
|
||||
orderURL: Uri,
|
||||
key: KeyPair,
|
||||
kid: Kid,
|
||||
retries: int = DefaultChalCompletedRetries,
|
||||
@@ -441,9 +443,9 @@ proc checkCertFinalized*(
|
||||
|
||||
proc certificateFinalized*(
|
||||
self: ACMEApi,
|
||||
domain: string,
|
||||
finalizeURL: string,
|
||||
orderURL: string,
|
||||
domain: Domain,
|
||||
finalizeURL: Uri,
|
||||
orderURL: Uri,
|
||||
key: KeyPair,
|
||||
kid: Kid,
|
||||
retries: int = DefaultFinalizeRetries,
|
||||
@@ -453,16 +455,16 @@ proc certificateFinalized*(
|
||||
return await self.checkCertFinalized(orderURL, key, kid, retries = retries)
|
||||
|
||||
proc requestGetOrder*(
|
||||
self: ACMEApi, orderURL: string
|
||||
self: ACMEApi, orderURL: Uri
|
||||
): Future[ACMEOrderResponse] {.async: (raises: [ACMEError, CancelledError]).} =
|
||||
handleError("requestGetOrder"):
|
||||
let acmeResponse = await self.get(orderURL)
|
||||
let acmeResponse = await self.get($orderURL)
|
||||
acmeResponse.body.to(ACMEOrderResponse)
|
||||
|
||||
proc downloadCertificate*(
|
||||
self: ACMEApi, orderURL: string
|
||||
self: ACMEApi, orderURL: Uri
|
||||
): Future[ACMECertificateResponse] {.async: (raises: [ACMEError, CancelledError]).} =
|
||||
let orderResponse = await self.requestGetOrder(orderURL)
|
||||
let orderResponse = await self.requestGetOrder($orderURL)
|
||||
|
||||
handleError("downloadCertificate"):
|
||||
let rawResponse = await HttpClientRequestRef
|
||||
|
||||
38
libp2p/autotls/acme/client.nim
Normal file
38
libp2p/autotls/acme/client.nim
Normal file
@@ -0,0 +1,38 @@
|
||||
# Nim-Libp2p
|
||||
# Copyright (c) 2025 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import chronos, results
|
||||
|
||||
import ./api
|
||||
|
||||
type KeyAuthorization* = string
|
||||
|
||||
type ACMEClient* = object
|
||||
api: ACMEApi
|
||||
key*: KeyPair
|
||||
kid*: Kid
|
||||
|
||||
proc new*(
|
||||
T: typedesc[ACMEClient],
|
||||
key: Opt[KeyPair] = Opt.none(KeyPair),
|
||||
acmeServerURL: string = LetsEncryptURL,
|
||||
): Future[T] {.async: (raises: [ACMEError, CancelledError]).} =
|
||||
let api = await ACMEApi.new()
|
||||
let key = key.valueOr:
|
||||
KeyPair.random(PKScheme.RSA, self.rng[]).get()
|
||||
let kid = await api.requestRegister(key)
|
||||
T(api: api, key: key, kid: kid)
|
||||
|
||||
proc genKeyAuthorization*(self: ACMEClient, domains: seq[Domain]): KeyAuthorization =
|
||||
let dns01 = self.api.requestChallenge(domains, self.key, self.kid)
|
||||
base64UrlEncode(
|
||||
@(sha256.digest((dns01.token & "." & thumbprint(self.key)).toByteSeq).data)
|
||||
)
|
||||
@@ -27,11 +27,11 @@ method requestNonce*(
|
||||
return self.acmeServerURL & "/acme/1234"
|
||||
|
||||
method post*(
|
||||
self: MockACMEApi, url: string, payload: string
|
||||
self: MockACMEApi, uri: Uri, payload: SignedACMERequest
|
||||
): Future[HTTPResponse] {.async: (raises: [ACMEError, HttpError, CancelledError]).} =
|
||||
HTTPResponse(body: self.mockedBody, headers: self.mockedHeaders)
|
||||
|
||||
method get*(
|
||||
self: MockACMEApi, url: string
|
||||
self: MockACMEApi, uri: Uri
|
||||
): Future[HTTPResponse] {.async: (raises: [ACMEError, HttpError, CancelledError]).} =
|
||||
HTTPResponse(body: self.mockedBody, headers: self.mockedHeaders)
|
||||
|
||||
238
libp2p/autotls/client.nim
Normal file
238
libp2p/autotls/client.nim
Normal file
@@ -0,0 +1,238 @@
|
||||
# Nim-Libp2p
|
||||
# Copyright (c) 2025 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
{.push raises: [].}
|
||||
{.push public.}
|
||||
|
||||
import
|
||||
net,
|
||||
results,
|
||||
chronos,
|
||||
chronicles,
|
||||
bearssl/rand,
|
||||
bio,
|
||||
json,
|
||||
sequtils,
|
||||
chronos/apps/http/httpclient
|
||||
|
||||
import ./acme/client
|
||||
import ../peeridauth/client
|
||||
import ../nameresolving/dnsresolver
|
||||
import ../wire
|
||||
import ../crypto/crypto
|
||||
import ../peerinfo
|
||||
import ../utils/heartbeat
|
||||
|
||||
logScope:
|
||||
topics = "libp2p autotls"
|
||||
|
||||
const
|
||||
DefaultDnsServers =
|
||||
@[
|
||||
initTAddress("1.1.1.1:53"),
|
||||
initTAddress("1.0.0.1:53"),
|
||||
initTAddress("[2606:4700:4700::1111]:53"),
|
||||
]
|
||||
DefaultRenewCheckTime = 1.hours
|
||||
DefaultDnsRetries = 10
|
||||
DefaultDnsRetryTime = 1.seconds
|
||||
|
||||
type AutoTLSCertificate* = object
|
||||
cert*: TLSCertificate
|
||||
expiry*: Moment
|
||||
|
||||
type AutoTLSClient = ref object
|
||||
bearer*: Opt[BearerToken]
|
||||
peerInfo: Opt[PeerInfo]
|
||||
peerIDAuthClient: PeerIDAuthClient
|
||||
|
||||
type AutoTLSManager* = object
|
||||
autoTLSClient: AutoTLSClient
|
||||
acmeClient: ACMEClient
|
||||
cert*: Opt[AutoTLSCertificate]
|
||||
certReady*: AsyncEvent
|
||||
dnsResolver: DnsResolver
|
||||
fut: Future[void]
|
||||
ipAddress: Opt[IpAddress]
|
||||
renewCheckTime: Duration
|
||||
|
||||
proc new*(
|
||||
T: typedesc[AutoTLSManager],
|
||||
acmeClient: ref ACMEClient = nil,
|
||||
dnsResolver: DnsResolver = DnsResolver.new(DefaultDnsServers),
|
||||
ipAddress: Opt[IpAddress] = Opt.none(IpAddress),
|
||||
): AutoTLSManager =
|
||||
T(
|
||||
fut: nil,
|
||||
cert: Opt.none(AutoTLSCertificate),
|
||||
certReady: newAsyncEvent(),
|
||||
acmeClient: acmeClient,
|
||||
dnsResolver: dnsResolver,
|
||||
peerInfo: Opt.none(PeerInfo),
|
||||
bearerToken: Opt.none(BearerToken),
|
||||
renewCheckTime: DefaultRenewCheckTime,
|
||||
ipAddress: ipAddress,
|
||||
)
|
||||
|
||||
proc checkDNSRecords(
|
||||
self: AutoTLSManager,
|
||||
ip4Domain: string,
|
||||
acmeChalDomain: string,
|
||||
keyAuthorization: KeyAuthorization
|
||||
retries: int = DefaultDnsRetries,
|
||||
): Future[bool] {.async: (raises: [AutoTLSError, CancelledError]).} =
|
||||
var txt: seq[string]
|
||||
var ip4: seq[TransportAddress]
|
||||
|
||||
for _ in 0 .. retries:
|
||||
txt = await self.dnsResolver.resolveTxt(acmeChalDomain)
|
||||
try:
|
||||
ip4 = await self.dnsResolver.resolveIp(ip4Domain, 0.Port)
|
||||
except CatchableError as exc:
|
||||
error "Failed to resolve IP", description = exc.msg # retry
|
||||
if txt.len > 0 and txt[0] == keyAuthorization and ip4.len > 0:
|
||||
return true
|
||||
await sleepAsync(DefaultDnsRetryTime)
|
||||
|
||||
return false
|
||||
|
||||
method issueCertificate(
|
||||
self: AutoTLSManager
|
||||
): Future[void] {.base, async: (raises: [AutoTLSError, CancelledError]).} =
|
||||
trace "Issuing new certificate"
|
||||
let peerInfo = self.peerInfo.valueOr:
|
||||
raise newException(AutoTLSError, "Cannot issue new certificate: peerInfo not set")
|
||||
|
||||
# generate autotls domain string: "*.{peerID}.libp2p.direct"
|
||||
let base36PeerId = encodePeerId(peerInfo.peerId)
|
||||
let baseDomain = base36PeerId & "." & AutoTLSDNSServer
|
||||
let domain = "*." & baseDomain
|
||||
|
||||
trace "Requesting ACME challenge"
|
||||
let keyAuthorization = self.acmeClient.getKeyAuthorization(@[domain])
|
||||
|
||||
trace "Sending challenge to AutoTLS broker"
|
||||
let strMultiaddresses: seq[string] = peerInfo.addrs.mapIt($it)
|
||||
let payload = %*{"value": keyAuthorization, "addresses": strMultiaddresses}
|
||||
let registrationURL = "https://" & AutoTLSBroker & "/v1/_acme-challenge"
|
||||
var response: HttpClientResponseRef
|
||||
var bearerToken: BearerToken
|
||||
if self.bearerToken.isSome:
|
||||
(bearerToken, response) = await peerIdAuthSend(
|
||||
registrationURL,
|
||||
self.httpSession,
|
||||
peerInfo,
|
||||
payload,
|
||||
bearerToken = self.bearerToken,
|
||||
)
|
||||
else:
|
||||
# authenticate, send challenge and save bearerToken for future requests
|
||||
(bearerToken, response) =
|
||||
await peerIdAuthSend(registrationURL, self.httpSession, peerInfo, payload)
|
||||
self.bearerToken = Opt.some(bearerToken)
|
||||
if response.status != HttpOk:
|
||||
raise newException(
|
||||
AutoTLSError, "Failed to authenticate with AutoTLS Broker at " & AutoTLSBroker
|
||||
)
|
||||
|
||||
# no need to do anything from this point forward if there are not public ip addresses on host
|
||||
let hostPrimaryIP: IpAddress =
|
||||
try:
|
||||
let ip = self.ipAddress.valueOr:
|
||||
checkedGetPrimaryIPAddr()
|
||||
if not isPublicIPv4(ip):
|
||||
raise newException(AutoTLSError, "Host does not have a public IPv4 address")
|
||||
ip
|
||||
except GetPrimaryIPError as exc:
|
||||
raise newException(AutoTLSError, "Failed to get primary IP address for host", exc)
|
||||
except CatchableError as exc:
|
||||
raise newException(
|
||||
AutoTLSError, "Unexpected error while getting primary IP address for host", exc
|
||||
)
|
||||
|
||||
debug "Waiting for DNS record to be set"
|
||||
|
||||
# if my ip address is 100.10.10.3 then the ip4Domain will be:
|
||||
# 100-10-10-3.{peerIdBase36}.libp2p.direct
|
||||
# and acme challenge TXT domain will be:
|
||||
# _acme-challenge.{peerIdBase36}.libp2p.direct
|
||||
let dashedIpAddr = ($hostPrimaryIP).replace(".", "-")
|
||||
let acmeChalDomain = "_acme-challenge." & baseDomain
|
||||
let ip4Domain = dashedIpAddr & "." & baseDomain
|
||||
if not await self.checkDNSRecords(ip4Domain, acmeChalDomain, keyAuthorization):
|
||||
raise newException(AutoTLSError, "DNS records not set")
|
||||
|
||||
debug "Notifying challenge completion to ACME server"
|
||||
let chalURL = dns01Challenge.getJSONField("url").getStr
|
||||
await self.acmeClient.notifyChallengeCompleted(chalURL)
|
||||
|
||||
debug "Finalize cert request with CSR"
|
||||
if not await self.acmeClient.finalizeCertificate(domain, finalizeURL, orderURL):
|
||||
raise newException(AutoTLSError, "ACME certificate finalization request failed")
|
||||
|
||||
debug "Downloading certificate"
|
||||
let (rawCert, expiry) = await self.acmeClient.downloadCertificate(orderURL)
|
||||
|
||||
trace "Installing certificate"
|
||||
try:
|
||||
self.cert = Opt.some(TLSCertificate.init(rawCert))
|
||||
self.certExpiry = Opt.some(asMoment(expiry))
|
||||
except TLSStreamProtocolError:
|
||||
raise newException(AutoTLSError, "Could not parse downloaded certificates")
|
||||
self.certReady.fire()
|
||||
|
||||
proc manageCertificate(
|
||||
self: AutoTLSManager
|
||||
): Future[void] {.async: (raises: [AutoTLSError, CancelledError]).} =
|
||||
trace "Starting AutoTLS manager"
|
||||
|
||||
debug "Registering ACME Client"
|
||||
if self.acmeClient.isNil:
|
||||
self.acmeClient = await ACMEClient.new()
|
||||
|
||||
heartbeat "Certificate Management", self.renewCheckTime:
|
||||
if self.cert.isNone or self.certExpiry.isNone:
|
||||
try:
|
||||
await self.issueCertificate()
|
||||
except CatchableError as exc:
|
||||
error "Failed to issue certificate", err = exc.msg
|
||||
break
|
||||
|
||||
# AutoTLSManager will renew the cert 1h before it expires
|
||||
let expiry = self.certExpiry.get
|
||||
let waitTime: Duration = expiry - Moment.now - self.renewCheckTime
|
||||
if waitTime <= self.renewCheckTime:
|
||||
try:
|
||||
await self.issueCertificate()
|
||||
except CatchableError as exc:
|
||||
error "Failed to renew certificate", err = exc.msg
|
||||
break
|
||||
|
||||
method start*(
|
||||
self: AutoTLSManager, peerInfo: PeerInfo
|
||||
): Future[void] {.base, async: (raises: [CancelledError]).} =
|
||||
if not self.fut.isNil:
|
||||
warn "Starting AutoTLS twice"
|
||||
return
|
||||
|
||||
self.peerInfo = Opt.some(peerInfo)
|
||||
self.fut = self.manageCertificate()
|
||||
|
||||
method stop*(self: AutoTLSManager): Future[void] {.base, async: (raises: []).} =
|
||||
trace "AutoTLS stop"
|
||||
if self.fut.isNil:
|
||||
warn "Stopping AutoTLS without starting it"
|
||||
return
|
||||
|
||||
await self.fut.cancelAndWait()
|
||||
self.fut = nil
|
||||
if not self.acmeClient.isNil:
|
||||
await self.acmeClient.session.closeWait()
|
||||
if not self.httpSession.isNil:
|
||||
await self.httpSession.closeWait()
|
||||
Reference in New Issue
Block a user