Chapter 38 / 51
Volume 5 · Extensions, Ops & Projects

xacme: automated certificates

The life-support of HTTPS: an ACME (RFC 8555) client packs account, order, dns-01 challenge and certificate fetch into one function call — paired with disk storage for expiry-driven renewal.

Overview

Positioning: the automated sequel to ch. 12 (certificate management) — manual openssl becomes API-driven; it feeds ch. 42's deployment certificate ledger. Prerequisite: the build.bat xacme variant. Live testing needs a real domain and DNS control.

38.1 The ACME flow

The RFC 8555 loop: register an account with the CA's directory (ES256 key + JOSE signatures) → order domains (wildcards allowed) → satisfy the dns-01 challenge (publish the CA-provided TXT record in DNS) → finalize with a CSR → download the chain. xacmeClientInit builds the client (parses the directory, registers/reuses the account); xacmeClientIssue runs the whole loop and returns a PEM chain. CA-neutral: LE / ZeroSSL / Google / Buypass presets — switching CA is switching URL.

38.2 DNS providers

dns-01 needs DNS write access: the provider interface is a plain struct (id / Add / Remove / optional Propagate callbacks); Aliyun is built in (dns_ali, V3 signing); other vendors plug in by filling the struct — no registration, no global state. The library carries its own TXT propagation probe — a UDP query to the authoritative NS confirms the record is visible before telling the CA to verify.

38.3 Issuing and renewing

One-stop renewal (IssueStored)
xacmeclient* pClient;
xstrview tDomain = xrtStrView("example.com");
bool bRenewed;

pClient = ...;  /* xacmeClientInit(engine, CA pem, directory URL, account key) */
char* sChain = xacmeClientIssueStored(pClient, &tDomain, 1,
    &tDnsProvider, sStoreRoot, 30, &bRenewed);
if ( sChain != NULL && bRenewed ) {
    install_cert(sChain);   /* hot swap (drilled in ch. 47) */
}
xrtFree(sChain);

IssueStored semantics: if the local certificate's remaining lifetime is at least the threshold in days, return the existing chain directly (pbRenewed=false); otherwise run a full issuance and persist. The remaining-days check is based on x509 parsing (xrt), with no external process.

38.4 Storage and recovery

The store is a directory convention: accounts/ holds account keys (PKCS#8 PEM; Init reuses them to skip re-registration), certs/<domain>/ holds the full chain and CA provenance, all written atomically (xrtFileWriteAtomic). Renewal checks, certificate readback and CA-chain readback each have standalone functions — you can build your own renewal policy without IssueStored.

38.5 The contract

ConcernKey points
Ownershipclient and Issue-returned chains are library-allocated; Destroy / xrtFree release
Networkingown synchronous HTTPS transport (borrow an engine or self-owned); traffic concentrates in the issuance window
Credentialsaccount and DNS keys go through store/env (ch. 44 layers), never into source
Failurethread error carries stable codes under the "xrt.acme.flow" domain, classifiable for retries

Common mistakes

Testing against the production CA
Symptom
Frequent re-issuing trips Let's Encrypt rate limits and the domain locks for hours.
Cause
Debug against the LE staging preset (no production quota); don't set IssueStored's renewal threshold to "renew every time".
Generating a new account key every time
Symptom
After restart the account doesn't match; the CA sees a fresh registration and order history is lost.
Cause
The account key must persist via the store and be passed to Init for reuse — IssueStored handles it; bypass it and you own the chore.

Exercises

BasicOffline checks

Run the staging directory + account config + provider registry (Count/Id); NeedRenew returns true for a near-expiry certificate.

Acceptance: the registry contains "ali"; remaining-days agrees with openssl.
IntermediateStaging issuance

Complete one dns-01 issuance on a real domain against LE staging and persist it.

Acceptance: the chain loads into xrt TLS; a rerun triggers "not due, returned as-is".
ChallengeRenewal robot

A ch. 41 timer checks daily; below 30 days, IssueStored plus a hot swap (the ch. 47 zero-downtime drill).

Acceptance: zero dropped connections during swap; failures raise classified alerts, not silence.

Summary

PointConclusion
Flowdirectory→account→order→dns-01→finalize→download, wrapped in one function
DNSplain-struct provider interface, Aliyun built in, TXT probe included
RenewalIssueStored decides by remaining days; atomic persistence
Storageaccount-key reuse + full-chain provenance, clear layout
Disciplinestaging for debug; persist keys; credentials out of source
Further reading on xrt