Skip to content
Merged

sync #6948

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@
| 🌐 DNS mode | Use DNS TXT records |
| 🔗 [DNS alias mode](https://github.com/acmesh-official/acme.sh/wiki/DNS-alias-mode) | Use DNS alias for verification |
| 📡 [Stateless mode](https://github.com/acmesh-official/acme.sh/wiki/Stateless-Mode) | Stateless verification |
| 📌 DNS persist mode | Persistent DNS TXT record ([draft-ietf-acme-dns-persist-01](https://datatracker.ietf.org/doc/draft-ietf-acme-dns-persist/)) |
| 📌 [DNS persist mode](https://github.com/acmesh-official/acme.sh/wiki/DNS-persist-mode) | Persistent DNS TXT record ([draft-ietf-acme-dns-persist-01](https://datatracker.ietf.org/doc/draft-ietf-acme-dns-persist/)) |

---

Expand Down Expand Up @@ -399,6 +399,8 @@ acme.sh --renew -d example.com

### 🔟 Use DNS Persist Mode

📖 Wiki: https://github.com/acmesh-official/acme.sh/wiki/DNS-persist-mode

📚 Spec: [draft-ietf-acme-dns-persist-01](https://datatracker.ietf.org/doc/draft-ietf-acme-dns-persist/)

DNS persist mode lets you place a **single, long‑lived `_validation-persist` TXT record** in your zone and reuse it for every subsequent issuance and renewal. There is no per-issuance challenge token, so renewals require **no DNS edits** — useful when DNS API access is not available but you still want unattended renewals.
Expand Down Expand Up @@ -502,6 +504,8 @@ acme.sh --renew -d example.com --force --ecc

#### 📡 ACME Renewal Information (ARI) — RFC 9773

📖 Wiki: https://github.com/acmesh-official/acme.sh/wiki/ARI

If the CA exposes a `renewalInfo` endpoint in its ACME directory (Let's Encrypt, ZeroSSL, etc.), `acme.sh` follows [RFC 9773](https://www.rfc-editor.org/rfc/rfc9773.html) automatically — **no flag needed, no opt-in**:

Comment on lines 505 to 510
| What | When | Why |
Expand Down
34 changes: 28 additions & 6 deletions acme.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env sh

VER=3.1.3
VER=3.1.4

PROJECT_NAME="acme.sh"

Expand Down Expand Up @@ -4870,8 +4870,12 @@ issue() {
# renewal (--renew path), the CA advertises renewalInfo, and a prior
# cert exists. --issue (even with --force) is not a renewal per RFC 9773
# which speaks of "a clear predecessor certificate" issued by this CA.
# NO_ARI=1 (env, account.conf, or ca.conf) disables ARI entirely, so the
# "replaces" field is also omitted.
_replaces_certID=""
if [ "$_ACME_IS_RENEW" = "1" ] && [ "$ACME_RENEWAL_INFO" ] && [ -f "$CERT_PATH" ]; then
if [ "$NO_ARI" = "1" ]; then
_debug "NO_ARI=1, omitting ARI 'replaces' field from newOrder"
elif [ "$_ACME_IS_RENEW" = "1" ] && [ "$ACME_RENEWAL_INFO" ] && [ -f "$CERT_PATH" ]; then
_replaces_certID="$(_getARICertID "$CERT_PATH")"
_debug "Adding ARI replaces" "$_replaces_certID"
fi
Expand Down Expand Up @@ -5703,7 +5707,11 @@ $_authorizations_map"
# with a time picked at random within the suggestedWindow. This both gives
# the CA full control over renewal scheduling and disperses renewals across
# the network so all clients don't hit the CA at the same instant.
if [ "$ACME_RENEWAL_INFO" ] && [ -f "$CERT_PATH" ] && [ -z "$_notAfter" ]; then
# Set NO_ARI=1 (env, account.conf, or ca.conf) to opt out and fall back to
# the legacy time-based renewal calculation.
if [ "$NO_ARI" = "1" ]; then
_debug "NO_ARI=1, skipping ARI suggestedWindow override"
elif [ "$ACME_RENEWAL_INFO" ] && [ -f "$CERT_PATH" ] && [ -z "$_notAfter" ]; then
_ari_resp_new="$(_get_ARI "$CERT_PATH")"
_debug2 "_ari_resp_new" "$_ari_resp_new"
_ari_start_new="$(echo "$_ari_resp_new" | _egrep_o '"start" *: *"[^"]*' | sed 's/.*"//')"
Expand Down Expand Up @@ -5819,8 +5827,12 @@ renew() {

# ARI (RFC 9773): fetch the CA's suggestedWindow on every renewal check.
# If the window has started, renew now even if Le_NextRenewTime is in the future.
# Set NO_ARI=1 (env, account.conf, or ca.conf) to opt out and use only
# Le_NextRenewTime for the renewal decision.
_ari_should_renew=""
if [ -z "$FORCE" ] && [ -f "$CERT_PATH" ]; then
if [ "$NO_ARI" = "1" ]; then
_debug "NO_ARI=1, skipping ARI suggestedWindow check"
elif [ -z "$FORCE" ] && [ -f "$CERT_PATH" ]; then
if _initAPI && [ "$ACME_RENEWAL_INFO" ]; then
_ari_resp="$(_get_ARI "$CERT_PATH")"
_debug2 "_ari_resp" "$_ari_resp"
Expand Down Expand Up @@ -6853,13 +6865,13 @@ deactivate() {
#cert
_getAKI() {
_cert="$1"
openssl x509 -in "$_cert" -text -noout | grep "X509v3 Authority Key Identifier" -A 1 | _tail_n 1 | tr -d ' :'
${ACME_OPENSSL_BIN:-openssl} x509 -in "$_cert" -text -noout | grep "X509v3 Authority Key Identifier" -A 1 | _tail_n 1 | tr -d ': ' | sed "s/keyid//"
}

#cert
_getSerial() {
_cert="$1"
openssl x509 -in "$_cert" -serial -noout | cut -d = -f 2
${ACME_OPENSSL_BIN:-openssl} x509 -in "$_cert" -serial -noout | cut -d = -f 2
}
Comment on lines 6866 to 6875

#cert
Expand All @@ -6872,6 +6884,16 @@ _getARICertID() {
_debug2 "_aki" "$_aki"
_debug2 "_ser" "$_ser"

# RFC 9773 Section 4.1 requires the DER-encoded INTEGER value bytes of
# serialNumber. When the high bit of the first byte is set (>= 0x80) DER
# prepends a 0x00 sign byte to keep the integer positive; openssl's hex
# output strips that, so add it back. Boulder (LE) accepts either form,
# but Sectigo (ZeroSSL) is strict and rejects newOrder with HTTP 401
# "replaces field does not identify a certificate" if the byte is missing.
case "$_ser" in
[89aAbBcCdDeEfF]*) _ser="00$_ser" ;;
esac

_akiurl="$(echo "$_aki" | _h2b | _base64 | _url_replace)"
_debug2 "_akiurl" "$_akiurl"
_serurl="$(echo "$_ser" | _h2b | _base64 | _url_replace)"
Expand Down
Loading