Introducing acme-not-a-joke
On Sat, 03 Oct 2026, by @lucasdicioccio, 497 words, 3 code snippets, 2 links, 0images.
Introducing acme-not-a-joke
acme-not-a-joke is a Haskell library of primitives to perform ACME certifications, as specified by RFC 8555. ACME is the protocol spoken by providers such as Let’s Encrypt: a client proves that it controls a domain name and gets a TLS certificate for it in return.
About the name
acme- packages on Hackage typically are “joke” packages. This library
implements the ACME protocol in earnest, hence the name: the acme prefix
is not the marker of a joke here.
Status
The library is incomplete and subject to changes, but the happy path works.
Update: release 0.2.0.0 covers every item of the lists below; see what’s new in 0.2.0.0. The snippets of this article are written for 0.2.0.0.
Things the author would like to change, with low appetite:
- support more features (especially, revocations);
- allow to tweak algorithms (it works for RS256 only);
- better helpers to create, load and write various PKI-related formats;
- no longer use
wreq(it reports non-200 responses with an exception).
The todo list also has: some more documentation, keyChange, and
deactivating an account.
Design
The library uses a type-family pattern to specify which fields are available
for an ACME resource in the various API calls and states. For instance, an
Account in the "account-create" message has no “orders” field. The state
is a type-level string, so types read like Account "account-fetch" or
Order "order-create".
The modules are organised as follows:
Acme.NotAJoke.Api.*holds one module per ACME resource or concept, among whichDirectory,Nonce,Account,Order,Authorization,Challenge,Validation,CSR,CertificateandJWS;Acme.NotAJoke.Clienthas the calls to an ACME server;Acme.NotAJoke.Dancerchains these calls into a whole “dance”, from an order to a certificate;Acme.NotAJoke.KeyManagementandAcme.NotAJoke.CertManagementhave helpers to load and store keys, certificate signing requests and certificates;Acme.NotAJoke.LetsEncrypthas the base URLs of Let’s Encrypt, for production and for staging.
A first certificate
The examples below are meant for GHCi and target the Let’s Encrypt staging
environment, with the account key stored as a JWK in staging/key.jwk.
First, generate a key and a certificate signing request with the script shipped in the repository:
bash scripts/gen-csr.sh staging example dicioccio.fr
Then, create a new account:
import Acme.NotAJoke.Api.Account
import Acme.NotAJoke.Api.Directory
import Acme.NotAJoke.Api.Endpoint
import Acme.NotAJoke.Api.Nonce
import Acme.NotAJoke.KeyManagement
import Acme.NotAJoke.LetsEncrypt
Right jwk <- loadOrCreateJWKFile "staging/key.jwk"
let contacts = ["mailto:certmaster@dicioccio.fr"]
Right leDir <- fetchDirectory (directory staging_letsencryptv2)
Right nonce0 <- getNonce leDir.newNonce
created <- postCreateAccount jwk leDir.newAccount nonce0 (createAccount contacts)
either (\err -> print (readProblem err)) print created
Finally, create a new certificate:
import Acme.NotAJoke.CertManagement
import Acme.NotAJoke.Client
import Acme.NotAJoke.Dancer
import Acme.NotAJoke.KeyManagement
import Acme.NotAJoke.Api.Directory
import Acme.NotAJoke.Api.CSR
import Acme.NotAJoke.Api.Order
import Acme.NotAJoke.LetsEncrypt
import Data.Maybe
jwk <- fromJust <$> loadJWKFile "staging/key.jwk"
der <- loadDER "staging-example/certificate.csr.der"
let o = createOrder (Nothing, Nothing) [ OrderIdentifier DNSOrder "example.dicioccio.fr" ]
runAcmeDance_dns01 (AcmeDancer staging_letsencryptv2 jwk (fetchAccount ["mailto:certmaster@dicioccio.fr"]) (CSR der) o (ghciDance "staging-example/certificate.pem"))
runAcmeDance_dns01 runs the whole exchange with a DNS-01 challenge. The
last field of the AcmeDancer is a handler called at each step of the
dance. ghciDance is a handler meant for GHCi: it prints each step, and
when the validation proof is known it prints it and waits for you to press
ENTER, which leaves you the time to publish the proof in the DNS. Once the
certificate is fetched, it stores it at the given path.
Where to go next
The code and its README are on GitHub at
lucasdicioccio/acme-not-a-joke.