mirror of
https://github.com/acmesh-official/acme.sh.git
synced 2026-08-21 15:43:29 +08:00
@@ -146,7 +146,7 @@
|
|||||||
| 🌐 DNS mode | Use DNS TXT records |
|
| 🌐 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 |
|
| 🔗 [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 |
|
| 📡 [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/)) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -399,6 +399,8 @@ acme.sh --renew -d example.com
|
|||||||
|
|
||||||
### 🔟 Use DNS Persist Mode
|
### 🔟 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/)
|
📚 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.
|
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.
|
||||||
@@ -502,6 +504,8 @@ acme.sh --renew -d example.com --force --ecc
|
|||||||
|
|
||||||
#### 📡 ACME Renewal Information (ARI) — RFC 9773
|
#### 📡 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**:
|
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**:
|
||||||
|
|
||||||
| What | When | Why |
|
| What | When | Why |
|
||||||
|
|||||||
34
acme.sh
34
acme.sh
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env sh
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
VER=3.1.3
|
VER=3.1.4
|
||||||
|
|
||||||
PROJECT_NAME="acme.sh"
|
PROJECT_NAME="acme.sh"
|
||||||
|
|
||||||
@@ -4870,8 +4870,12 @@ issue() {
|
|||||||
# renewal (--renew path), the CA advertises renewalInfo, and a prior
|
# renewal (--renew path), the CA advertises renewalInfo, and a prior
|
||||||
# cert exists. --issue (even with --force) is not a renewal per RFC 9773
|
# cert exists. --issue (even with --force) is not a renewal per RFC 9773
|
||||||
# which speaks of "a clear predecessor certificate" issued by this CA.
|
# 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=""
|
_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")"
|
_replaces_certID="$(_getARICertID "$CERT_PATH")"
|
||||||
_debug "Adding ARI replaces" "$_replaces_certID"
|
_debug "Adding ARI replaces" "$_replaces_certID"
|
||||||
fi
|
fi
|
||||||
@@ -5703,7 +5707,11 @@ $_authorizations_map"
|
|||||||
# with a time picked at random within the suggestedWindow. This both gives
|
# with a time picked at random within the suggestedWindow. This both gives
|
||||||
# the CA full control over renewal scheduling and disperses renewals across
|
# 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.
|
# 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")"
|
_ari_resp_new="$(_get_ARI "$CERT_PATH")"
|
||||||
_debug2 "_ari_resp_new" "$_ari_resp_new"
|
_debug2 "_ari_resp_new" "$_ari_resp_new"
|
||||||
_ari_start_new="$(echo "$_ari_resp_new" | _egrep_o '"start" *: *"[^"]*' | sed 's/.*"//')"
|
_ari_start_new="$(echo "$_ari_resp_new" | _egrep_o '"start" *: *"[^"]*' | sed 's/.*"//')"
|
||||||
@@ -5819,8 +5827,12 @@ renew() {
|
|||||||
|
|
||||||
# ARI (RFC 9773): fetch the CA's suggestedWindow on every renewal check.
|
# 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.
|
# 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=""
|
_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
|
if _initAPI && [ "$ACME_RENEWAL_INFO" ]; then
|
||||||
_ari_resp="$(_get_ARI "$CERT_PATH")"
|
_ari_resp="$(_get_ARI "$CERT_PATH")"
|
||||||
_debug2 "_ari_resp" "$_ari_resp"
|
_debug2 "_ari_resp" "$_ari_resp"
|
||||||
@@ -6853,13 +6865,13 @@ deactivate() {
|
|||||||
#cert
|
#cert
|
||||||
_getAKI() {
|
_getAKI() {
|
||||||
_cert="$1"
|
_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
|
#cert
|
||||||
_getSerial() {
|
_getSerial() {
|
||||||
_cert="$1"
|
_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
|
||||||
}
|
}
|
||||||
|
|
||||||
#cert
|
#cert
|
||||||
@@ -6872,6 +6884,16 @@ _getARICertID() {
|
|||||||
_debug2 "_aki" "$_aki"
|
_debug2 "_aki" "$_aki"
|
||||||
_debug2 "_ser" "$_ser"
|
_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)"
|
_akiurl="$(echo "$_aki" | _h2b | _base64 | _url_replace)"
|
||||||
_debug2 "_akiurl" "$_akiurl"
|
_debug2 "_akiurl" "$_akiurl"
|
||||||
_serurl="$(echo "$_ser" | _h2b | _base64 | _url_replace)"
|
_serurl="$(echo "$_ser" | _h2b | _base64 | _url_replace)"
|
||||||
|
|||||||
Reference in New Issue
Block a user