Deactivates the ACME account on the server and removes local
credential files so subsequent commands don't use the stale account.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add Display impls for KeyType, DaneUsage, DaneSelector, DaneMatching
so status output shows ecdsa-p256/ee/spki/sha256 instead of Debug
formatting
- Document multi-zone DNS clients (zones field) in README
- Document multiple listen addresses for HTTP-01 and TLS-ALPN-01
- Document default_solver with example
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DNS-01 solvers now accept a propagation_delay option (default 5s).
When multiple solvers are used for a certificate, the longest delay
is used. The Solver trait method changes from needs_propagation_delay()
returning bool to propagation_delay() returning Option<Duration>.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The default hickory TSigner only signs update messages. Wrap it in
AlwaysSignTsig that overrides should_finalize_message to return true
for all messages, so regular queries (used by dane-check) are also
TSIG-signed. This is needed when the DNS server uses TSIG keys for
view selection.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously replace_tlsa_records sent separate delete and add requests.
A failure between them would leave the name with no TLSA records. Now
both operations are combined into a single RFC 2136 update message.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Split AcmeClient::new into load (existing only) and create (register
new). account show now uses load and errors if no credentials exist,
account create always registers a new account.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The init command printed systemd-creds encrypt commands, but this is
already covered more thoroughly in the README credential storage docs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
dane-check now queries the authoritative DNS server for published TLSA
records and compares them against expected values computed from the
current certificate. Queries are TSIG-signed using the same key as the
DNS client config (for view selection support). Reports OK, MISSING,
or MISMATCH for each name and exits with code 1 if any check fails.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Reject duplicate certificate names and duplicate hook names
- Reject certificates where a domain has no solver (no solver/solvers
and no default_solver)
- Warn about DNS clients, solvers, and hooks that are defined but
never referenced
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Inline per-certificate hooks were always executed even in dry-run mode
because the dry_run parameter was hardcoded to false.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
listen now accepts a single address or a list. Certforge tries binding
all addresses and succeeds if at least one works. This supports
initial provisioning scenarios where a proxy (e.g., haproxy) may not
be running yet — certforge can bind port 80 directly as a fallback
alongside a proxy-forwarded port.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Named hooks defined in [[hook]] are referenced by certificates via
hooks = ["name"]. Triggered hooks run after all renewals complete,
in definition order.
Inline [[certificate.hook]] blocks are still supported for
per-certificate hooks that run immediately after each renewal.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Per RFC 8555 §8.4, the DNS-01 challenge for *.example.com uses
_acme-challenge.example.com (strip the *. prefix), not the invalid
_acme-challenge.*.example.com.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Omit --name= from systemd-creds encrypt so the encrypted credential
can be loaded under any name via LoadCredentialEncrypted=. Previously,
the embedded name had to match the credential name in the service
unit, causing decryption failures when services used different names.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DNS clients now accept both `zone` (single) and `zones` (list).
Zone matching uses longest suffix: `mail.example.com` matches
`example.com`, and `host.sub.example.com` prefers `sub.example.com`
over `example.com`.
Config validation checks that every certificate domain (via its DNS-01
solver) and every DANE name matches at least one zone in the
referenced DNS client.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Arch's makepkg LTO interferes with ring's asm objects. The standard
Arch workaround is options=(!lto) in the PKGBUILD. Also:
- Add backup= to CI release PKGBUILD for consistency
- Add /pkg/ and *.pkg.tar.zst to .gitignore
- Consolidate PKGBUILD changelog entries
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New per-certificate `profile` field for ACME profile selection
- Let's Encrypt requires "shortlived" for IP address certs (~6 day validity)
- Profile documented in README with link to LE profiles page
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
BIND 9.18+ truncates HMAC-SHA-512 MACs by default, and hickory-dns
does not support verifying truncated HMACs. Document the workaround
(use hmac-sha256) and add hmac-sha384 as an additional option.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Document named DNS clients, solver configuration (DNS-01, HTTP-01,
TLS-ALPN-01), per-domain solver assignment, and IP address certificates.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
HTTP-01 solver with two modes:
- Standalone: embedded hyper HTTP server on configurable address
- Webroot: writes token files for an existing web server
TLS-ALPN-01 solver:
- Embedded rustls TLS server with ALPN "acme-tls/1"
- Dynamic certificate resolution per-identifier
- Self-signed cert with critical acmeIdentifier extension (RFC 8737)
Both solvers use rustls/ring crypto backend (no aws-lc-rs dependency).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Solver trait with present/cleanup/needs_propagation_delay methods
- DNS-01 solver extracted from inline renew.rs code
- ACME module now supports mixed Identifier::Dns + Identifier::Ip
- Generic ChallengeInfo replaces DnsChallenge
- Per-domain challenge type selection based on solver config
- Propagation delay only applied when DNS-01 solvers are in use
- Solver instances deduplicated by name across domains
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Breaking config change. The DNS and challenge configuration is now
structured around named, reusable components:
- [dns.*] named DNS client connections with explicit zone field
- [solver.*] challenge solvers (dns-01, http-01, tls-alpn-01)
- Certificates reference solvers by name (solver/solvers fields)
- DANE blocks reference DNS clients by name (dns field)
- Removed automatic zone detection heuristics
- Added default_solver for global fallback
- Validation: DNS-01 rejected for IP addresses, solver/solvers
mutual exclusivity, length matching, reference integrity
HTTP-01 and TLS-ALPN-01 solver implementations to follow.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wait 5 seconds after publishing DNS-01 TXT records before notifying
the ACME server, giving the DNS server time to propagate the update.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use ring backend for instant-acme instead of aws-lc-rs (fixes Arch
build linker errors, also reduces binary from 9MB to 6.6MB)
- Remove redundant mv in deb packaging (dpkg-deb already outputs the
correct filename)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Enable LTO, single codegen unit, symbol stripping, and panic=abort
in the release profile. Trim tokio features to only what's needed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rust CLI tool for automated ACME certificate management with:
- DNS-01 challenge via RFC 2136 dynamic DNS updates with TSIG auth
- DANE TLSA record publication (RFC 6698/7671/7672)
- systemd credential integration (systemd-creds encrypt/decrypt)
- systemd unit management via D-Bus (zbus)
- Post-renewal hooks (systemctl reload/restart, arbitrary commands)
- Key rotation with DANE pre-publication protocol
- TOML configuration with per-zone DNS overrides
Tested against Let's Encrypt staging. 12 unit tests, zero warnings.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>