acmesh-official / acmesh-official/acme.sh
Manual DNS renewal downloads previous cert due to stale Le_LinkOrder/Le_LinkCert reuse
- Lingua principale
- Shell
- Stelle
- 47.6k
- Fork
- 5.7k
- Merge medio
- 6g 5h
- PR unite (30g)
- 15
Descrizione
## Version
v3.1.2 confirmed reproducing; v3.1.4 still contains the same code paths.
## Environment
- macOS 15.5 (Darwin 25.5.0), bash
- CA: ZeroSSL (acme.zerossl.com/v2/DV90)
- Cert type: ECC wildcard (`*.example.com`)
- Renewal mode: manual DNS (`--dns --yes-I-know-dns-manual-mode-enough-go-ahead-please`)
## Reproduction
1. Issue a wildcard cert in manual DNS mode. `Le_LinkOrder` and `Le_LinkCert` get written to the domain's `.conf`.
2. ~90 days later, run `--renew ... --dns --yes-I-know-dns-manual-mode-enough-go-ahead-please`.
3. First invocation prints the TXT challenge and exits (expected).
4. Add the TXT record; second invocation reports \"Cert success\" and writes the cert files.
**Actual result:** the written cert is byte-for-byte identical to the previous cert (same serial, fingerprint, notAfter — still expired). acme.sh only reissues on the very first `--issue`; every subsequent manual-DNS `--renew` silently re-downloads the previous cert.
## Log excerpt showing the bug
\`\`\`
Le_OrderFinalize='https://acme.example/v2/.../order/NEW-ORDER-ID/finalize'
Order status is 'processing', let's sleep and retry.
Polling order status: https://acme.example/v2/.../order/OLD-ORDER-ID <-- previous run's order
Le_LinkCert='https://acme.example/v2/.../cert/OLD-CERT-ID' <-- previous run's cert
\`\`\`
## Root cause
In \`_issue()\`:
**acme.sh:4862-4866 (v3.1.4)** — the reset is intentionally skipped for DNS manual mode (to preserve state across the two-invocation flow), but this preserves state from the *previous cert's lifetime*, not just the current run:
\`\`\`sh
elif ! _hasfield \"\$_web_roots\" \"\$W_DNS\"; then
Le_OrderFinalize=\"\"
Le_LinkOrder=\"\"
Le_LinkCert=\"\"
fi
\`\`\`
**acme.sh:5689-5691 (v3.1.4)** — after finalizing a fresh order, \`Le_LinkOrder\` is only pulled from response headers when the variable is empty. On renewal it isn't, so the subsequent poll (\`_info \"Polling order status: \$Le_LinkOrder\"\`) hits the old order, which is still \`status:valid\` and returns the old certificate URL:
\`\`\`sh
if [ -z \"\$Le_LinkOrder\" ]; then
Le_LinkOrder=\"\$(echo \"\$responseHeaders\" | grep -i '^Location.*\$' | ...)\"
fi
\`\`\`
## Workaround
Manually strip the three variables from the domain \`.conf\` before running \`--renew\`:
\`\`\`sh
sed -i '' \"/^Le_LinkOrder=/d;/^Le_LinkCert=/d;/^Le_OrderFinalize=/d\" ~/.acme.sh/DOMAIN_ecc/DOMAIN.conf
\`\`\`
Then \`--issue --force\` produces a genuinely new cert.
## Suggested fixes (any one)
1. Overwrite unconditionally after finalize: drop the \`[ -z \"\$Le_LinkOrder\" ]\` guard and always pull from response headers.
2. Reset \`Le_LinkOrder\`/\`Le_LinkCert\` at the top of the finalize step (not the top of \`_issue\`), so the manual-DNS state preservation doesn't include them.
3. Reset these three variables whenever a new order is created in this run.
Happy to submit a PR if a preferred approach is confirmed.
Guida per i contributori
Apri la guida per i contributori
Direzione di ricerca
The bug is in the `_issue()` function in acme.sh, lines 4862-4866 and 5689-5691 (v3.1.4). Start by reading the manual DNS renewal flow and how state variables (Le_LinkOrder, Le_LinkCert, Le_OrderFinalize) are managed. The fix involves modifying the condition for resetting these variables or updating them after finalizing an order. Test by simulating a manual DNS renewal cycle and verifying a new certificate is issued.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Valutazione
- Stack tecnologico
- bash, shell
- Ambito
- cli, security
- Tipo di issue
- Bug
- Difficoltà
- 3/5
- Tempo stimato
- 1-2 giorni
- Stato di attività
- Tranquilla
- Chiarezza
- Specificata chiaramente
- Idoneità per principianti
- 45/100