Skip to content

Commit 2e6b1d2

Browse files
authored
feat(wstransport): add autotls support (#1535)
1 parent 9e6c4cb commit 2e6b1d2

26 files changed

Lines changed: 863 additions & 423 deletions

libp2p/autotls/acme/api.nim

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,11 @@ type ACMECertificateResponse* = object
158158
rawCertificate*: string
159159
certificateExpiry*: DateTime
160160

161+
type ACMECertificate* = object
162+
rawCertificate*: string
163+
certificateExpiry*: DateTime
164+
certKeyPair*: KeyPair
165+
161166
when defined(libp2p_autotls_support):
162167
import options, sequtils, strutils, jwt, bearssl/pem
163168

@@ -448,11 +453,16 @@ when defined(libp2p_autotls_support):
448453
return await self.checkChallengeCompleted(chalURL, key, kid, retries = retries)
449454

450455
proc requestFinalize*(
451-
self: ACMEApi, domain: Domain, finalize: Uri, key: KeyPair, kid: Kid
456+
self: ACMEApi,
457+
domain: Domain,
458+
finalize: Uri,
459+
certKeyPair: KeyPair,
460+
key: KeyPair,
461+
kid: Kid,
452462
): Future[ACMEFinalizeResponse] {.async: (raises: [ACMEError, CancelledError]).} =
453463
handleError("requestFinalize"):
454464
let payload = await self.createSignedAcmeRequest(
455-
finalize, %*{"csr": createCSR(domain)}, key, kid = Opt.some(kid)
465+
finalize, %*{"csr": createCSR(domain, certKeyPair)}, key, kid = Opt.some(kid)
456466
)
457467
let acmeResponse = await self.post(finalize, payload)
458468
# server responds with updated order response
@@ -484,11 +494,13 @@ when defined(libp2p_autotls_support):
484494
domain: Domain,
485495
finalize: Uri,
486496
order: Uri,
497+
certKeyPair: KeyPair,
487498
key: KeyPair,
488499
kid: Kid,
489500
retries: int = DefaultFinalizeRetries,
490501
): Future[bool] {.async: (raises: [ACMEError, CancelledError]).} =
491-
let finalizeResponse = await self.requestFinalize(domain, finalize, key, kid)
502+
let finalizeResponse =
503+
await self.requestFinalize(domain, finalize, certKeyPair, key, kid)
492504
# keep checking order until cert is valid (done)
493505
return await self.checkCertFinalized(order, key, kid, retries = retries)
494506

libp2p/autotls/acme/client.nim

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,17 +61,20 @@ when defined(libp2p_autotls_support):
6161
await self.api.requestChallenge(domains, self.key, await self.getOrInitKid())
6262

6363
proc getCertificate*(
64-
self: ACMEClient, domain: api.Domain, challenge: ACMEChallengeResponseWrapper
64+
self: ACMEClient,
65+
domain: api.Domain,
66+
certKeyPair: KeyPair,
67+
challenge: ACMEChallengeResponseWrapper,
6568
): Future[ACMECertificateResponse] {.async: (raises: [ACMEError, CancelledError]).} =
6669
let chalURL = parseUri(challenge.dns01.url)
6770
let orderURL = parseUri(challenge.order)
6871
let finalizeURL = parseUri(challenge.finalize)
69-
trace "sending challenge completed notification"
72+
trace "Sending challenge completed notification"
7073
discard await self.api.sendChallengeCompleted(
7174
chalURL, self.key, await self.getOrInitKid()
7275
)
7376

74-
trace "checking for completed challenge"
77+
trace "Checking for completed challenge"
7578
let completed = await self.api.checkChallengeCompleted(
7679
chalURL, self.key, await self.getOrInitKid()
7780
)
@@ -80,15 +83,15 @@ when defined(libp2p_autotls_support):
8083
ACMEError, "Failed to signal ACME server about challenge completion"
8184
)
8285

83-
trace "waiting for certificate to be finalized"
86+
trace "Waiting for certificate to be finalized"
8487
let finalized = await self.api.certificateFinalized(
85-
domain, finalizeURL, orderURL, self.key, await self.getOrInitKid()
88+
domain, finalizeURL, orderURL, certKeyPair, self.key, await self.getOrInitKid()
8689
)
8790
if not finalized:
8891
raise
8992
newException(ACMEError, "Failed to finalize certificate for domain " & domain)
9093

91-
trace "downloading certificate"
94+
trace "Downloading certificate"
9295
await self.api.downloadCertificate(orderURL)
9396

9497
proc close*(self: ACMEClient) {.async: (raises: [CancelledError]).} =

libp2p/autotls/acme/mockapi.nim

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,20 @@ proc new*(
2121
acmeServerURL: parseUri(LetsEncryptURL),
2222
)
2323

24-
method requestNonce*(
25-
self: MockACMEApi
26-
): Future[Nonce] {.async: (raises: [ACMEError, CancelledError]).} =
27-
return $self.acmeServerURL & "/acme/1234"
24+
when defined(libp2p_autotls_support):
25+
method requestNonce*(
26+
self: MockACMEApi
27+
): Future[Nonce] {.async: (raises: [ACMEError, CancelledError]).} =
28+
return $self.acmeServerURL & "/acme/1234"
2829

29-
method post*(
30-
self: MockACMEApi, uri: Uri, payload: string
31-
): Future[HTTPResponse] {.async: (raises: [ACMEError, HttpError, CancelledError]).} =
32-
result = self.mockedResponses[0]
33-
self.mockedResponses.delete(0)
30+
method post*(
31+
self: MockACMEApi, uri: Uri, payload: string
32+
): Future[HTTPResponse] {.async: (raises: [ACMEError, HttpError, CancelledError]).} =
33+
result = self.mockedResponses[0]
34+
self.mockedResponses.delete(0)
3435

35-
method get*(
36-
self: MockACMEApi, uri: Uri
37-
): Future[HTTPResponse] {.async: (raises: [ACMEError, HttpError, CancelledError]).} =
38-
result = self.mockedResponses[0]
39-
self.mockedResponses.delete(0)
36+
method get*(
37+
self: MockACMEApi, uri: Uri
38+
): Future[HTTPResponse] {.async: (raises: [ACMEError, HttpError, CancelledError]).} =
39+
result = self.mockedResponses[0]
40+
self.mockedResponses.delete(0)

libp2p/autotls/acme/utils.nim

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,19 +52,21 @@ when defined(libp2p_autotls_support):
5252
ACMEError, "Unexpected error occurred while getting body bytes", exc
5353
)
5454

55-
proc createCSR*(domain: string): string {.raises: [ACMEError].} =
55+
proc createCSR*(
56+
domain: string, certKeyPair: KeyPair
57+
): string {.raises: [ACMEError].} =
5658
var certKey: cert_key_t
5759
var certCtx: cert_context_t
5860
var derCSR: ptr cert_buffer = nil
5961

60-
let personalizationStr = "libp2p_autotls"
61-
if cert_init_drbg(
62-
personalizationStr.cstring, personalizationStr.len.csize_t, certCtx.addr
63-
) != CERT_SUCCESS:
64-
raise newException(ACMEError, "Failed to initialize certCtx")
65-
if cert_generate_key(certCtx, certKey.addr) != CERT_SUCCESS:
66-
raise newException(ACMEError, "Failed to generate cert key")
62+
# convert KeyPair to cert_key_t
63+
let rawSeckey: seq[byte] = certKeyPair.seckey.getRawBytes.valueOr:
64+
raise newException(ACMEError, "Failed to get seckey raw bytes (DER)")
65+
let seckeyBuffer = rawSeckey.toCertBuffer()
66+
if cert_new_key_t(seckeyBuffer.unsafeAddr, certKey.addr) != CERT_SUCCESS:
67+
raise newException(ACMEError, "Failed to convert key pair to cert_key_t")
6768

69+
# create CSR
6870
if cert_signing_req(domain.cstring, certKey, derCSR.addr) != CERT_SUCCESS:
6971
raise newException(ACMEError, "Failed to create CSR")
7072

libp2p/autotls/mockservice.nim

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
when defined(libp2p_autotls_support):
2+
import ./service, ./acme/client, ../peeridauth/client
3+
4+
import ../crypto/crypto, ../crypto/rsa, websock/websock
5+
6+
type MockAutotlsService* = ref object of AutotlsService
7+
mockedCert*: TLSCertificate
8+
mockedKey*: TLSPrivateKey
9+
10+
proc new*(
11+
T: typedesc[MockAutotlsService],
12+
rng: ref HmacDrbgContext = newRng(),
13+
config: AutotlsConfig = AutotlsConfig.new(),
14+
): T =
15+
T(
16+
acmeClient:
17+
ACMEClient.new(api = ACMEApi.new(acmeServerURL = config.acmeServerURL)),
18+
brokerClient: PeerIDAuthClient.new(),
19+
bearer: Opt.none(BearerToken),
20+
cert: Opt.none(AutotlsCert),
21+
certReady: newAsyncEvent(),
22+
running: newAsyncEvent(),
23+
config: config,
24+
rng: rng,
25+
)
26+
27+
method getCertWhenReady*(
28+
self: MockAutotlsService
29+
): Future[AutotlsCert] {.async: (raises: [AutoTLSError, CancelledError]).} =
30+
AutotlsCert.new(self.mockedCert, self.mockedKey, Moment.now)
31+
32+
method setup*(self: MockAutotlsService) {.base, async.} =
33+
self.running.fire()

0 commit comments

Comments
 (0)