7.5 KiB
libp2p AutoTLS
| Lifecycle Stage | Maturity | Status | Latest Revision |
|---|---|---|---|
| ?? | ?????????????? | Active | r0, 2025-04-30 |
Authors: @gmelodie
See the lifecycle document for context about the maturity level and spec status.
Table of Contents
Overview
Most modern web browsers only establish TLS connections with peers that present certificates issued by a recognized Certificate Authority (CA). Self-signed certificates are generally not accepted. To obtain a CA-issued certificate, a requester must complete an ACME (Automatic Certificate Management Environment) challenge. This typically involves provisioning a DNS TXT record on a domain the requester controls.
However, most libp2p peers do not own or control domain names, making it impractical for them to complete DNS-based ACME challenges and, by extension, to obtain trusted TLS certificates. This limitation hinders direct communication between libp2p peers and standard web browsers.
AutoTLS addresses this problem by introducing an AutoTLS broker — a server that controls a domain and facilitates ACME challenges on behalf of libp2p peers. A peer can request the AutoTLS broker to fulfill an ACME DNS challenge on its behalf. Once the broker sets the appropriate DNS record, the requesting peer proceeds to notify the ACME server. The ACME server validates the challenge against the broker's domain, and if successful, issues a valid certificate.
This mechanism allows libp2p peers to obtain CA-issued certificates without needing to possess or manage their own domain names.
General Flow
- Start libp2p client with public IPv4 (or IPv6) and support for
identifyprotocol - Get
PeerIDas a base36 of the CID of the multihash with thelibp2p-key(0x72) multicodec:- Transform PeerID into a multihash
mh - Transform
mhinto a CIDv1 with thelibp2p-keymulticodec (which is the0x72multicodec) - Encode
cid.data.bufferto multibase base36, which is the same as regular base36 but does not trim leading zeroes and starts either withkorK) to getb36peerid
- Transform PeerID into a multihash
- Generate a key as specified in RFC7518, here we'll use an RSA key
myrsakey - Register an account on the ACME server (production server for Let's Encrypt or just the staging server for testing, but any other ACME server would work)
- Send a GET request to the
directoryendpoint, and extract thenewAccountvalue from the JSON response, which will be the registration URL we'll use - Send JWT-signed POST request to registration URL with the following
payload:{"termsOfServiceAgreed": true}(acontactfield containing a list ofmailto:bob@example.orgcontact information strings can also be optionally specified in the payload). The POST body is signed using JWT withmyrsakeyandnonce(nonceis a number returned by GETting the ACME server at the URL specified indirectory["newNonce"]). The JSON payload before JWT-signing should look like:{ "header": { "alg": "RS256", "typ": "JWT", "nonce": "`nonce`", "url": "`url`", "jwk": { "kty": "RSA", "n": "`myrsakey.n`", "e": "`myrsakey.e`" } }, "claims": { "payload": { "termsOfServiceAgreed": true, "contact": [ "mailto:alice@example.com", "mailto:bob@example.com" ] } } }
json { "payload": "`token.claims.toBase64`", "protected": "`token.header.toBase64`", "signature": "`base64UrlEncode(token.signature)`" }Obs: the response to the account registration containskidstring in thelocationheader that SHOULD be saved and used in following requests to ACME server - Send a GET request to the
- Request a certificate for the
*.{b36peerid}.libp2p.directdomain from the ACME server by issuing a POST request using the same JWT signature scheme (and a newnonce) but withkidinstead ofjwkfield and the following payload:{ "type": "dns", "value": "*.{b36peerid}.libp2p.direct" } - From the ACME server response, get the entry with
"type"of"dns-01"(calleddns01Challengehere) and derive theKey Authorizationfor it:sha256.digest((dns01Challenge["token"] + "." + thumbprint(myrsakey))- JWK thumbprint:
base64encode(sha256.digest({"e": myrsakey.e, "kty": "RSA", "n": myrsakey.n}))
- JWK thumbprint:
- Send challenge to AutoTLS broker (e.g.
registration.libp2p.direct). This requires a PeerID Authentication:- Send GET request to the AutoTLS broker's
v1/_acme-challengeendpoint and getwww-authenticateheader from the response. Extract the values of three substrings thatwww-authenticatecontains:challenge-client,public-keyandopaque - Generate random string with around 42 characters to be sent as a
challengeServer - Get the private key of the requesting libp2p peer as
peer-privkey. This is not necessarily the same key used to communicate with ACME server sig =(obs:varintis a protobuf varint field that encodes the length of each of thekey=valuestring)
sig = base64URL( peer-privkey.sign( bytes(varint + "challenge-client={challenge-client}") + bytes(varint + "hostname={hostname}") + bytes(varint + "server-public-key={publicKey}") ) )headers ={ "Content-Type": "application/json", "User-Agent": "nim-libp2p", "authorization": "libp2p-PeerID public-key=\"{clientPublicKeyB64}\", opaque=\"{opaque}\", challenge-server=\"{challengeServer}\", sig=\"{sig}\"" }- Send POST to
v1/_acme-challengeendpoint usingpayloadas body andheaders - Get the
bearertoken from theauthentication-infoheader of the response, which should be used for following requests from this client.
- Send GET request to the AutoTLS broker's
- Check that the AutoTLS server has added the
_acme-challenge.{b36peerid}.libp2p.directTXTand thedashed-public-ip-address.{b36peerid}.libp2p.directADNS resource records. - Notify ACME server of challenge completion so it can lookup the DNS resource records.
- Get URL from
dns01challenge["url"] - Send an empty signed JSON payload (
{}) to the ACME server using thekidobtained from the ACME registration step and get the response from the server (completedResponse). - From
completedResponse, theurlfield from the JSON body byGETting it, again withkidsigning.
- Get URL from
- Wait for ACME server to finish testing the domain.
- The response from the polling will contain a
statusfield that will bependingwhile ACME is still testing the challenge, andvalidorinvalidwhen it's done.
- The response from the polling will contain a
- Download certificate from ACME server.