Skip to content

ACMEOW v1.1.2

Choose a tag to compare

@miichoow miichoow released this 20 May 06:15
· 10 commits to main since this release

ACMEOW v1.1.2

Release Date: May 20, 2026

Version: v1.1.2

Commit: a3d4de6

Production-grade ACME protocol client library for Python.


What's New

External CSR Support

finalize_order() now accepts a csr= argument (PEM or DER-encoded bytes), allowing
bring-your-own-key workflows where the key pair lives on the downstream client:

# Generate key and CSR externally
key = ec.generate_private_key(ec.SECP256R1())
csr_der = (
    x509.CertificateSigningRequestBuilder()
    .subject_name(x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "example.com")]))
    .add_extension(
        x509.SubjectAlternativeName([x509.DNSName("example.com")]),
        critical=False,
    )
    .sign(key, hashes.SHA256())
    .public_bytes(serialization.Encoding.DER)
)

# Pass it to ACMEOW — no key is generated or stored
with AcmeClient(...) as client:
    client.new_order(["example.com"])
    client.complete_challenges()
    client.finalize_order(csr=csr_der)
    cert_pem, key_pem = client.get_certificate()
    # key_pem is None — the key was never held by ACMEOW

When csr= is provided:

  • No private key is generated or saved to disk
  • get_certificate() returns (cert_pem, None) for the key element
  • common_name and key_type arguments to finalize_order() are ignored

When csr= is omitted, behaviour is unchanged (key generated internally, returned by get_certificate()).

The implementation uses key_path.exists() to determine whether a key was generated
internally, replacing the earlier _external_csr_used flag approach.


Fixes

Fast-fail on HTTP 500 + application/problem+json

A 500 response whose Content-Type is application/problem+json is now treated as a
definitive server-reported error (e.g. "domain not authorized for ACME") rather than a
transient failure. It raises AcmeServerError immediately with the server's detail
message, without consuming any retry budget.

Plain 500 responses (no problem body) continue to be retried as transient failures.

Server detail surfaced on retry exhaustion

When all retries are exhausted on a retryable status code (503, etc.), the last
response is now passed to _handle_error_response, propagating the ACME problem
detail to the caller instead of raising a generic exhaustion message.


Installation

pip install acmeow
pip install acmeow[dns-route53]  # AWS Route53
pip install acmeow[all]          # All optional dependencies

Requirements

  • Python 3.10+
  • cryptography >= 41.0.0
  • requests >= 2.31.0
  • requests[socks] >= 2.31.0

Testing & Quality Metrics

  • 433 tests passing (21 new tests added for external CSR and HTTP error-handling)
  • Tested on Python 3.10, 3.11, 3.12, 3.13
  • Cross-platform support (Linux, macOS, Windows)

License

Apache License 2.0