ilusm.dev

acme

Let's Encrypt / ACME protocol - automatic TLS certificate issuance and renewal.

Load with: use acme

What this module does

acme implements the ACME protocol (RFC 8555) used by Let's Encrypt to issue free TLS certificates. It covers the full lifecycle: creating a client, registering an account, placing a certificate order, responding to HTTP-01 or DNS-01 challenges, finalising the order, downloading the certificate, checking renewal windows, and revoking certificates.

Two overloads of acmel() exist - one returns the production Let's Encrypt URL, the other the staging URL. Use staging while testing so you don't hit rate limits.

Quick example

use acme

# Create a client pointed at Let's Encrypt production
client = acmec(acmel(), "admin@example.com")

# Register an account (agrees to ToS)
client = acmer(client, tru)

# Place an order for a domain
order = acbme(client, ["example.com"])

# Get the authorisation challenges
authz = acmea(order, order.authorizations[0])
challenges = acmec(authz)

# For HTTP-01: write the challenge file
acmht("example.com", challenges[0].token, challenges[0].keyAuthorization)

# Tell Let's Encrypt to validate
acmeh(client, challenges[0].url, challenges[0].token, challenges[0].keyAuthorization)

# Finalise and download the certificate
acbme(order, my_csr)
cert = acmed(order)

Functions

Client setup

acmec(directory_url, contact_email)

Creates a new ACME client object. Pass the directory URL (use acmel() to get it) and your contact email. Returns an object with directory, contact, account, and nonce fields.

acmel()

Two overloads: returns the Let's Encrypt production directory URL, or the staging directory URL. Use staging for testing.

acmed(client)

Fetches the ACME directory from the server. Returns the directory object (contains URLs for newAccount, newOrder, etc.), or nil on failure.

Account management

acmer(client, tos_agreed)

Registers a new ACME account. Generates a 2048-bit RSA key, builds a JWK, and POSTs to the newAccount endpoint. Pass tru for tos_agreed to accept Let's Encrypt's terms of service. Returns the updated client with the account set.

acmej(pubkey)

Converts an RSA public key to JWK format via __acme_jwk_from_rsa.

acmes(payload, jwk, account_key, url, nonce)

Signs a payload using JWS (RS256). Returns an object with protected, payload, and signature fields, all base64-encoded. Used internally for all signed ACME requests.

acmeb(client, kid, hmac_key)

Binds an external account (EAB) to the client - used with CAs that require pre-registration. Calls __acme_eab_bind.

Order and authorisation

acbme(client, domains)

Places a new certificate order for a list of domain names. Returns the order object (with authorizations, finalize URL, etc.), or nil on failure. Note: acbme is overloaded - see also Certificate finalisation below.

acmea(order, authz_url)

Fetches the authorisation object for a given authorisation URL. Returns the authorisation (contains challenge list), or nil on failure.

acmec(authz)

Extracts the challenges list from an authorisation object. Note: acmec is overloaded - see also Client setup above.

Challenge response

acmht(domain, token, keyauth)

HTTP-01 challenge: writes the key authorisation string to .well-known/acme-challenge/{token} and returns the path.

acmeh(client, challenge_url, token, keyauth)

Notifies the ACME server that the challenge is ready for validation. POSTs an empty signed payload to the challenge URL.

acmdn(domain, keyauth_digest)

DNS-01 challenge: returns an object with the TXT record name (_acme-challenge.{domain}) and the base64-encoded value to set.

Certificate finalisation and download

acbme(order, csr)

Finalises the order by submitting a CSR (base64-encoded) to the finalize URL. Returns the updated order, or nil on failure.

acmed(order)

Downloads the issued certificate from the order's certificate URL. Returns the certificate PEM string, or nil on failure.

Renewal and revocation

accer(cert, days_before_expiry)

Returns tru if the certificate expires within days_before_expiry days. Parses the cert with cert.certp and checks the not_after field against the current time.

acmrn(domains, days_before_expiry)

Checks each domain in the list against the cert at /etc/letsencrypt/live/{domain}/cert.pem. Returns a list of domains that either have no cert or need renewal.

acbev(client, cert, reason)

Revokes a certificate. reason is an integer reason code (e.g. 0 = unspecified, 1 = keyCompromise). POSTs to the revokeCert URL.

Rate limits

acmlm()

Returns an object with Let's Encrypt's current rate limit values: 300 orders/account, 50 certificates/domain, 5 duplicate certificate limit, 5 failed validations limit, etc.

Notes

  • acmec and acbme and acmed are each overloaded with different signatures - which one runs depends on what arguments you pass.
  • Use the staging URL while developing: acmec(acmel(), "you@example.com") - the second overload of acmel() returns the staging URL.
  • Requires net, cryx, enc, jsn, cert, tim, and fs to be available.