Sovite.TLS.ACME (sovite v0.2.0)

Copy Markdown View Source

An ACME client (RFC 8555) for getting certificates from a CA such as Let's Encrypt, with HTTP-01 challenges.

{:ok, acme} = ACME.connect("https://acme-v02.api.letsencrypt.org/directory", account_key)
{:ok, acme} = ACME.register(acme, "postmaster@example.com")

{:ok, pem_chain, acme} =
  ACME.obtain(acme, ["mx.example.com"], certificate_key, fn
    {:put, token, key_authorization} -> :ets.insert(table, {token, key_authorization})
    {:delete, token} -> :ets.delete(table, token)
  end)

The challenge function publishes the key authorization, for example through Sovite.TLS.ACME.HTTPChallenge on port 80, before the CA is asked to check it.

Requests are signed with the account key (ECDSA P-256, ES256) as flattened JWS. Keys are :public_key EC private key records.

Options

  • :cacerts - CAs to verify the ACME server against. Defaults to the system's. Plain http directory URLs are allowed for testing.
  • :timeout - per HTTP request, in milliseconds. Defaults to 30 seconds.
  • :poll_interval / :poll_attempts - how often and how many times to check a pending authorization or order. Default to 2 seconds and 30 times.

Errors

  • {:acme, type, detail} - an RFC 8555 problem document from the CA, such as {:acme, "urn:ietf:params:acme:error:rateLimited", "..."}.
  • {:challenge_failed, domain, detail} - the CA could not validate a domain.
  • {:http, status}, {:invalid_response, what}, :timeout, or an :httpc error.

Summary

Functions

The key authorization for a challenge token: the token and the account key's thumbprint (RFC 8555 §8.1, RFC 7638).

Orders a certificate for domains, proves control of each with HTTP-01, and downloads the chain (PEM). certificate_key is the key the certificate is for.

Creates the account, or finds the existing one for this key, agreeing to the CA's terms of service.

Types

challenge_fun()

@type challenge_fun() :: ({:put, String.t(), String.t()} | {:delete, String.t()} ->
                      any())

t()

@opaque t()

Functions

connect(directory_url, account_key, opts \\ [])

@spec connect(String.t(), tuple(), keyword()) :: {:ok, t()} | {:error, term()}

Fetches the CA's directory.

key_authorization(acme, token)

@spec key_authorization(t(), String.t()) :: String.t()

The key authorization for a challenge token: the token and the account key's thumbprint (RFC 8555 §8.1, RFC 7638).

obtain(acme, domains, certificate_key, challenge_fun)

@spec obtain(t(), [String.t(), ...], tuple(), challenge_fun()) ::
  {:ok, binary(), t()} | {:error, term()}

Orders a certificate for domains, proves control of each with HTTP-01, and downloads the chain (PEM). certificate_key is the key the certificate is for.

register(acme, email)

@spec register(t(), String.t()) :: {:ok, t()} | {:error, term()}

Creates the account, or finds the existing one for this key, agreeing to the CA's terms of service.