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 which Directory, Nonce, Account, Order, Authorization, Challenge, Validation, CSR, Certificate and JWS;
  • Acme.NotAJoke.Client has the calls to an ACME server;
  • Acme.NotAJoke.Dancer chains these calls into a whole “dance”, from an order to a certificate;
  • Acme.NotAJoke.KeyManagement and Acme.NotAJoke.CertManagement have helpers to load and store keys, certificate signing requests and certificates;
  • Acme.NotAJoke.LetsEncrypt has 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.