mirror of
https://github.com/acmesh-official/acme.sh.git
synced 2026-05-18 20:03:55 +08:00
@@ -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/)) |
|
||||
|
||||
---
|
||||
|
||||
@@ -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.
|
||||
@@ -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**:
|
||||
|
||||
| What | When | Why |
|
||||
|
||||
34
acme.sh
34
acme.sh
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
VER=3.1.3
|
||||
VER=3.1.4
|
||||
|
||||
PROJECT_NAME="acme.sh"
|
||||
|
||||
@@ -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
|
||||
@@ -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/.*"//')"
|
||||
@@ -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"
|
||||
@@ -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
|
||||
}
|
||||
|
||||
#cert
|
||||
@@ -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)"
|
||||
|
||||
Reference in New Issue
Block a user