ACME protocol implementation in Python

5.5.0 · active · verified Thu Apr 09

The `acme` library is a low-level, comprehensive Python implementation of the Automated Certificate Management Environment (ACME) protocol, primarily maintained as part of the Certbot project. It enables programmatic certificate issuance, renewal, and management, supporting Let's Encrypt and other ACME-compatible certificate authorities. Currently at version 5.5.0, `acme` follows the release cadence of Certbot, typically seeing several updates per year, often driven by new ACME RFCs or internal Certbot architectural changes.

Warnings

Install

Imports

Quickstart

This quickstart demonstrates the absolute minimum to initialize the `acme` client and register an account with an ACME server (like Let's Encrypt staging). Note that `acme` is a low-level library; most users will prefer a higher-level client like Certbot for end-to-end certificate management. This example creates an RSA account key, sets up the network client, fetches the directory, and attempts to register a new ACME account, handling potential conflicts if the account already exists. It uses the Let's Encrypt staging environment by default.

import os
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.backends import default_backend

from acme import client
from acme import messages
import josepy as jose

# This is the staging ACME URL for Let's Encrypt
DIRECTORY_URL = os.environ.get('ACME_DIRECTORY_URL', 'https://acme-staging-v02.api.letsencrypt.org/directory')

def run_acme_client():
    # 1. Create a new ACME account private key
    acc_key_pem = rsa.generate_private_key(
        public_exponent=65537, key_size=2048, backend=default_backend()
    )
    acc_key = jose.JWK.from_pem(acc_key_pem.private_bytes(
        encoding=jose.serialization.Encoding.PEM,
        format=jose.serialization.PrivateFormat.PKCS8,
        encryption_algorithm=jose.serialization.NoEncryption()
    ))

    # 2. Initialize the client network
    net = client.ClientNetwork(acc_key, user_agent="my-acme-client/1.0")

    # 3. Get the ACME directory object
    directory = messages.Directory.from_json(net.get(DIRECTORY_URL).json())

    # 4. Create the ACME client
    acme_client = client.ClientV2(directory, net=net)

    # 5. Register a new account (or load existing)
    #   email_contact is optional for staging/testing
    new_reg = messages.NewRegistration(
        terms_of_service_agreed=True, 
        contact=('mailto:test@example.com',) if 'test@example.com' in os.environ.get('ACME_EMAIL', '') else None
    )
    
    try:
        regr = acme_client.new_account(new_reg)
        print(f"Account created/loaded: {regr.uri}")
    except client.errors.ConflictError as e:
        # Account already exists, retrieve it
        print(f"Account already exists, retrieving: {e.uri}")
        regr = acme_client.query_registration(messages.RegistrationResource(uri=e.uri, body=new_reg))

    print("ACME client initialized and account registered.")
    return acme_client, regr

if __name__ == "__main__":
    acme_client_instance, account_resource = run_acme_client()

view raw JSON →