ExAcme is a lightweight, developer-friendly Elixir library for interacting with RFC 8555-compliant ACME servers like Let's Encrypt. It simplifies the process of managing X.509 (TLS/SSL) certificates by providing a straightforward API for registering accounts, handling domain challenges, and issuing certificates.
- Designed with developer productivity and Elixir idioms in mind.
- Easy integration into your projects with minimal configuration.
- Renewal information extension (DRAFT)
- Exposing
Retry-After
header information.
The package can be installed by adding ex_acme
to your list of dependencies in mix.exs
:
def deps do
[
{:ex_acme, "~> 0.5.2"}
]
end
Documentation can be generated with ExDoc and published on HexDocs. The docs can be found at https://hexdocs.pm/ex_acme.
You can add ExAcme to your supervision tree.
children = [
{ExAcme, name: MyAcme, directory_url: :lets_encrypt_staging}
]
To register a new account with the ACME server, you need to generate an account key, create a registration, and agree to the terms of service.
alias ExAcme.RegistrationBuilder
# Generate a new account key
key = ExAcme.generate_key()
# Create and configure the registration
registration =
RegistrationBuilder.new_registration()
|> RegistrationBuilder.contacts(email: "[email protected]")
|> RegistrationBuilder.agree_to_terms()
# Register the account
case ExAcme.register_account(registration, key, MyAcme) do
{:ok, account, account_key} ->
IO.puts("Account registered successfully!")
IO.inspect(account)
{:error, reason} ->
IO.puts("Failed to register account:")
IO.inspect(reason)
end
From now on you can use the account_key
to perform operations on the account. This structure holds the JSON Web Key (JWK) associated with the account and the Key ID (kid) assigned by the ACME server.
Once you have registered an account, you can create an order for a certificate by specifying the domain(s) you wish to obtain certificates for.
alias ExAcme.OrderBuilder
# Create a new order request
order_request =
OrderBuilder.new_order()
|> OrderBuilder.add_dns_identifier(["example.com", "www.example.com"])
# Submit the order
case ExAcme.submit_order(order_request, account_key, MyAcme) do
{:ok, order} ->
IO.puts("Order created successfully!")
IO.inspect(order)
{:error, reason} ->
IO.puts("Failed to create order:")
IO.inspect(reason)
end
After creating an order, you need to complete the necessary challenges to prove ownership of the domain.
alias ExAcme.Challenge
for auth_url <- order.authorizations do
{:ok, authorization} = ExAcme.fetch_authorization(auth_url, account_key, MyAcme)
challenge = Challenge.find_by_type(authorization, "dns-01")
if challenge do
value = Challenge.key_authorization(challenge.token, account_key)
# Set up challenge (implementation depends on your setup)
setup_challenge(authorization.identifier["value"], value)
# Trigger validation
{:ok, _validated_challenge} = ExAcme.start_challenge_validation(challenge.url, account_key, MyAcme)
# Optionally, wait and verify the challenge status
:timer.sleep(5000)
{:ok, validated_challenge} = ExAcme.fetch_challenge(challenge.url, account_key, MyAcme)
if validated_challenge.status == "valid" do
IO.puts("Challenge for #{authorization.identifier["value"]} validated successfully.")
else
IO.puts("Challenge for #{authorization.identifier["value"]} failed.")
end
else
IO.puts("No challenge found for #{authorization.identifier["value"]}.")
end
end
After all challenges are validated, you can finalize the order by submitting a CSR.
alias ExAcme.Order
# Create a private key for the certificate
private_key = X509.PrivateKey.new_ec(:secp256r1)
# Generate CSR from the order and private key
csr = Order.to_csr(order, private_key)
# Finalize the order by submitting the CSR
case ExAcme.finalize_order(order.finalize_url, csr, account_key, MyAcme) do
{:ok, finalized_order} ->
IO.puts("Order finalized successfully!")
IO.inspect(finalized_order)
{:error, reason} ->
IO.puts("Failed to finalize order:")
IO.inspect(reason)
end
Once the order is finalized and the certificate is issued, you can fetch the certificate from the ACME server.
case ExAcme.fetch_certificates(finalized_order.certificate_url, account_key, MyAcme) do
{:ok, certificates} ->
Enum.each(certificates, fn cert ->
IO.puts("Fetched Certificate:")
IO.puts(X509.Certificate.to_pem(cert))
end)
{:error, reason} ->
IO.puts("Failed to fetch certificate:")
IO.inspect(reason)
end
This library is licensed under the MIT License.