From d5b5bcef5631ae2e04d9df7c19be0947351145ab Mon Sep 17 00:00:00 2001 From: neil Date: Tue, 10 Dec 2024 20:54:20 +0100 Subject: [PATCH 01/12] support ARI, not finished yet https://github.com/acmesh-official/acme.sh/issues/4944 --- acme.sh | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/acme.sh b/acme.sh index 9842e3f1..bc146996 100755 --- a/acme.sh +++ b/acme.sh @@ -2746,6 +2746,7 @@ _clearAPI() { ACME_REVOKE_CERT="" ACME_NEW_NONCE="" ACME_AGREEMENT="" + ACME_RENEWAL_INFO="" } #server @@ -2790,6 +2791,9 @@ _initAPI() { ACME_AGREEMENT=$(echo "$response" | _egrep_o 'termsOfService" *: *"[^"]*"' | cut -d '"' -f 3) export ACME_AGREEMENT + ACME_RENEWAL_INFO=$(echo "$response" | _egrep_o 'renewalInfo" *: *"[^"]*"' | cut -d '"' -f 3) + export ACME_RENEWAL_INFO + _debug "ACME_KEY_CHANGE" "$ACME_KEY_CHANGE" _debug "ACME_NEW_AUTHZ" "$ACME_NEW_AUTHZ" _debug "ACME_NEW_ORDER" "$ACME_NEW_ORDER" @@ -2797,6 +2801,7 @@ _initAPI() { _debug "ACME_REVOKE_CERT" "$ACME_REVOKE_CERT" _debug "ACME_AGREEMENT" "$ACME_AGREEMENT" _debug "ACME_NEW_NONCE" "$ACME_NEW_NONCE" + _debug "ACME_RENEWAL_INFO" "$ACME_RENEWAL_INFO" if [ "$ACME_NEW_ACCOUNT" ] && [ "$ACME_NEW_ORDER" ]; then return 0 fi @@ -6416,6 +6421,36 @@ deactivate() { done } +#cert +_getAKI() { + _cert="$1" + openssl x509 -in "$_cert" -text -noout | grep "X509v3 Authority Key Identifier" -A 1 | _tail_n 1 | tr -d ' :' +} + +#cert +_getSerial() { + _cert="$1" + openssl x509 -in "$_cert" -serial -noout | cut -d = -f 2 +} + +#cert +_get_ARI() { + _cert="$1" + _aki=$(_getAKI "$_cert") + _ser=$(_getSerial "$_cert") + _debug2 "_aki" "$_aki" + _debug2 "_ser" "$_ser" + + _akiurl="$(echo "$_aki" | _h2b | _base64 | tr -d = | _url_encode)" + _debug2 "_akiurl" "$_akiurl" + _serurl="$(echo "$_ser" | _h2b | _base64 | tr -d = | _url_encode)" + _debug2 "_serurl" "$_serurl" + + _ARI_URL="$ACME_RENEWAL_INFO/$_akiurl.$_serurl" + _get "$_ARI_URL" + +} + # Detect profile file if not specified as environment variable _detect_profile() { if [ -n "$PROFILE" -a -f "$PROFILE" ]; then From 5ddffc9172e9dd00c90f4251e0e37310525db337 Mon Sep 17 00:00:00 2001 From: neil Date: Tue, 10 Dec 2024 21:01:37 +0100 Subject: [PATCH 02/12] fix format --- acme.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/acme.sh b/acme.sh index bc146996..4290d7a1 100755 --- a/acme.sh +++ b/acme.sh @@ -6441,9 +6441,9 @@ _get_ARI() { _debug2 "_aki" "$_aki" _debug2 "_ser" "$_ser" - _akiurl="$(echo "$_aki" | _h2b | _base64 | tr -d = | _url_encode)" + _akiurl="$(echo "$_aki" | _h2b | _base64 | tr -d = | _url_encode)" _debug2 "_akiurl" "$_akiurl" - _serurl="$(echo "$_ser" | _h2b | _base64 | tr -d = | _url_encode)" + _serurl="$(echo "$_ser" | _h2b | _base64 | tr -d = | _url_encode)" _debug2 "_serurl" "$_serurl" _ARI_URL="$ACME_RENEWAL_INFO/$_akiurl.$_serurl" From ee661e5d7112674cf432a6dacc6455b11c54f38e Mon Sep 17 00:00:00 2001 From: neil Date: Tue, 10 Dec 2024 21:02:54 +0100 Subject: [PATCH 03/12] fix format --- acme.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acme.sh b/acme.sh index 4290d7a1..b7558ee5 100755 --- a/acme.sh +++ b/acme.sh @@ -6445,7 +6445,7 @@ _get_ARI() { _debug2 "_akiurl" "$_akiurl" _serurl="$(echo "$_ser" | _h2b | _base64 | tr -d = | _url_encode)" _debug2 "_serurl" "$_serurl" - + _ARI_URL="$ACME_RENEWAL_INFO/$_akiurl.$_serurl" _get "$_ARI_URL" From 3245fe35f1fe882af398c5475a5257dc90001a10 Mon Sep 17 00:00:00 2001 From: sim0n-v <218359733+sim0n-v@users.noreply.github.com> Date: Sun, 29 Jun 2025 18:24:10 +0200 Subject: [PATCH 04/12] detecting support for ARI --- acme.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/acme.sh b/acme.sh index d70e323b..3807fdc1 100755 --- a/acme.sh +++ b/acme.sh @@ -2752,6 +2752,7 @@ _clearAPI() { ACME_KEY_CHANGE="" ACME_NEW_AUTHZ="" ACME_NEW_ORDER="" + ACME_RENEWAL_INFO="" ACME_REVOKE_CERT="" ACME_NEW_NONCE="" ACME_AGREEMENT="" @@ -2790,6 +2791,9 @@ _initAPI() { ACME_NEW_ACCOUNT=$(echo "$response" | _egrep_o 'newAccount" *: *"[^"]*"' | cut -d '"' -f 3) export ACME_NEW_ACCOUNT + ACME_RENEWAL_INFO=$(echo "$response" | _egrep_o 'renewalInfo" *: *"[^"]*"' | cut -d '"' -f 3) + export ACME_RENEWAL_INFO + ACME_REVOKE_CERT=$(echo "$response" | _egrep_o 'revokeCert" *: *"[^"]*"' | cut -d '"' -f 3) export ACME_REVOKE_CERT @@ -2803,6 +2807,7 @@ _initAPI() { _debug "ACME_NEW_AUTHZ" "$ACME_NEW_AUTHZ" _debug "ACME_NEW_ORDER" "$ACME_NEW_ORDER" _debug "ACME_NEW_ACCOUNT" "$ACME_NEW_ACCOUNT" + _debug "ACME_RENEWAL_INFO" "$ACME_RENEWAL_INFO" _debug "ACME_REVOKE_CERT" "$ACME_REVOKE_CERT" _debug "ACME_AGREEMENT" "$ACME_AGREEMENT" _debug "ACME_NEW_NONCE" "$ACME_NEW_NONCE" From 7a7f678222171d1d60f38dabac42a2c216993ec0 Mon Sep 17 00:00:00 2001 From: sim0n-v <218359733+sim0n-v@users.noreply.github.com> Date: Sun, 29 Jun 2025 18:52:12 +0200 Subject: [PATCH 05/12] constructing the ARI CertID --- acme.sh | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/acme.sh b/acme.sh index 3807fdc1..f4f48f85 100755 --- a/acme.sh +++ b/acme.sh @@ -2314,6 +2314,25 @@ _send_signed_request() { } +_calc_cert_id() { + _cf="$1" + _cert_serial=$(${ACME_OPENSSL_BIN:-openssl} x509 -noout -serial -in "$_cf" | cut -d '=' -f 2 | _h2b | _base64 | _url_replace) + _debug3 "Certificate Serial Number: $_cert_serial" + if [ -z "$_cert_serial" ]; then + _err "Failed to parse certificate Serial Number" + return 1 + fi + _cert_authority_kid=$(${ACME_OPENSSL_BIN:-openssl} x509 -noout -text -in "$_cf" | grep -i "authority key id" -A 1 | _tail_n 1 | _egrep_o "[A-F0-9:]+" | tr -d ':' | _h2b | _base64 | _url_replace) + _debug3 "Certificate Authority Key Identifier: $_cert_authority_kid" + if [ -z "$_cert_authority_kid" ]; then + _err "Failed to parse certificate Authority Key Identifier" + return 1 + fi + _cert_id="$_cert_authority_kid.$_cert_serial" + _debug2 "Certificate ID for Renewal Info: $_cert_id" + return 0 +} + #setopt "file" "opt" "=" "value" [";"] _setopt() { __conf="$1" @@ -5324,6 +5343,12 @@ $_authorizations_map" Le_CertCreateTimeStr=$(_time2str "$Le_CertCreateTime") _savedomainconf "Le_CertCreateTimeStr" "$Le_CertCreateTimeStr" + if _calc_cert_id "$CERT_PATH"; then + _savedomainconf "Le_RenewalInfoCertId" "$_cert_id" + else + _cleardomainconf "Le_RenewalInfoCertId" + fi + if [ -z "$Le_RenewalDays" ] || [ "$Le_RenewalDays" -lt "0" ]; then Le_RenewalDays="$DEFAULT_RENEW" else From f8f73d108607604de3df8dcb2a9008e71e3355d9 Mon Sep 17 00:00:00 2001 From: sim0n-v <218359733+sim0n-v@users.noreply.github.com> Date: Mon, 30 Jun 2025 23:02:30 +0200 Subject: [PATCH 06/12] enable ARI by default --- acme.sh | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/acme.sh b/acme.sh index f4f48f85..8baee588 100755 --- a/acme.sh +++ b/acme.sh @@ -4324,6 +4324,48 @@ _get_chain_subjects() { fi } +_update_renewal_info() { + if [ -z "$ACME_RENEWAL_INFO" ]; then + _debug3 "Renewal Info is not supported for $Le_API." + return 1 + fi + if [ -z "$Le_RenewalInfoCertId" ]; then + _debug3 "No Certificate Identifier found. Skipping ACME Renewal Info." + return 1 + fi + if [ -z "$Le_EnableRenewalInfo" ] || [ "$Le_EnableRenewalInfo" -ne "1" ]; then + _debug3 "Renewal Info is not enabled." + _cleardomainconf "Le_RenewalInfoLastUpdate" + _cleardomainconf "Le_RenewalInfoLastUpdateStr" + _cleardomainconf "Le_RenewalInfoExplanation" + return 1 + fi + response=$(_get "$ACME_RENEWAL_INFO/$Le_RenewalInfoCertId" | _json_decode | _normalizeJson) + if ! _contains "$response" "\"start\"" || ! _contains "$response" "\"end\""; then + _debug2 "Failed to parse Renewal Info." + return 1 + fi + _renewal_info_start_time_str="$(echo $response | _egrep_o '"start":"[^"]+"' | cut -d '"' -f 4)" + _renewal_info_start_time="$(_date2time "$_renewal_info_start_time_str")" + _renewal_info_end_time_str="$(echo $response | _egrep_o '"end":"[^"]+"' | cut -d '"' -f 4)" + _renewal_info_end_time="$(_date2time "$_renewal_info_end_time_str")" + if [ $_renewal_info_start_time -gt $_renewal_info_end_time ]; then + _debug2 "Malformed Renewal Info." + return 1 + fi + _savedomainconf "Le_NextRenewTime" "$_renewal_info_start_time" + _savedomainconf "Le_NextRenewTimeStr" "$_renewal_info_start_time_str" + if _contains "$response" "\"explanationURL\""; then + Le_RenewalInfoExplanation="$(echo $response | _egrep_o '"explanationURL":"[^"]+"' | cut -d '"' -f 4)" + export Le_RenewalInfoExplanation + fi + Le_RenewalInfoLastUpdate="$(_time)" + Le_RenewalInfoLastUpdateStr="$(_time2str "$Le_RenewalInfoLastUpdate")" + _savedomainconf "Le_RenewalInfoLastUpdate" "$Le_RenewalInfoLastUpdate" + _savedomainconf "Le_RenewalInfoLastUpdateStr" "$Le_RenewalInfoLastUpdateStr" + return 0 +} + #cert issuer _match_issuer() { _cfile="$1" @@ -4646,6 +4688,10 @@ issue() { if [ "$_notAfter" ]; then _newOrderObj="$_newOrderObj,\"notAfter\": \"$_notAfter\"" fi + Le_RenewalInfoCertId=$(_readdomainconf "Le_RenewalInfoCertId") + if [ "$_ACME_IS_RENEW" ] && [ -n "$Le_EnableRenewalInfo" ] && [ "$Le_EnableRenewalInfo" -eq "1" ] && [ -n "$Le_RenewalInfoCertId" ]; then + _newOrderObj="$_newOrderObj,\"replaces\": \"$Le_RenewalInfoCertId\"" + fi _debug "STEP 1, Ordering a Certificate" if ! _send_signed_request "$ACME_NEW_ORDER" "$_newOrderObj}"; then _err "Error creating new order." @@ -5325,6 +5371,10 @@ $_authorizations_map" _info "Your cert key is in: $(__green "$CERT_KEY_PATH")" fi + if [ "$_ACME_IS_RENEW" ] && [ -n "$Le_EnableRenewalInfo" ] && [ "$Le_EnableRenewalInfo" -eq "1" ] && [ -n "$Le_RenewalInfoExplanation" ]; then + _info "More info on this renewal: $(__green "$Le_RenewalInfoExplanation")." + fi + if [ ! "$USER_PATH" ] || [ ! "$_ACME_IN_CRON" ]; then USER_PATH="$PATH" _saveaccountconf "USER_PATH" "$USER_PATH" @@ -5344,7 +5394,7 @@ $_authorizations_map" _savedomainconf "Le_CertCreateTimeStr" "$Le_CertCreateTimeStr" if _calc_cert_id "$CERT_PATH"; then - _savedomainconf "Le_RenewalInfoCertId" "$_cert_id" + _savedomainconf "Le_RenewalInfoCertId" "$Le_RenewalInfoCertId" else _cleardomainconf "Le_RenewalInfoCertId" fi @@ -5416,6 +5466,14 @@ $_authorizations_map" _savedomainconf "Le_NextRenewTimeStr" "$Le_NextRenewTimeStr" _savedomainconf "Le_NextRenewTime" "$Le_NextRenewTime" + if [ -z "$Le_EnableRenewalInfo" ] || [ "$Le_EnableRenewalInfo" -eq "1" ]; then + _savedomainconf "Le_EnableRenewalInfo" "1" + else + _savedomainconf "Le_EnableRenewalInfo" "0" + fi + Le_EnableRenewalInfo="$(_readdomainconf "Le_EnableRenewalInfo")" + _update_renewal_info + #convert to pkcs12 if [ "$Le_PFXPassword" ]; then _toPkcs "$CERT_PFX_PATH" "$CERT_KEY_PATH" "$CERT_PATH" "$CA_CERT_PATH" "$Le_PFXPassword" @@ -5483,6 +5541,15 @@ renew() { . "$DOMAIN_CONF" _debug Le_API "$Le_API" + ACME_DIRECTORY="$Le_API" + + _initAPI + if _update_renewal_info; then + Le_NextRenewTime=$(_readdomainconf "Le_NextRenewTime") + Le_NextRenewTimeStr=$(_readdomainconf "Le_NextRenewTimeStr") + fi + _clearAPI + case "$Le_API" in "$CA_LETSENCRYPT_V2_TEST") _info "Switching back to $CA_LETSENCRYPT_V2" @@ -7069,6 +7136,9 @@ Parameters: -m, --email Specifies the account email, only valid for the '--install' and '--update-account' command. --accountkey Specifies the account key path, only valid for the '--install' command. --days Specifies the days to renew the cert when using '--issue' command. The default value is $DEFAULT_RENEW days. + --enable-ari <0|1> Enable/Disable ACME Renewal Info (ARI). Default value is: 1. + 0: disabled. Local check only. + 1: enabled. Ask the CA for Renewal Window. --httpport Specifies the standalone listening port. Only valid if the server is behind a reverse proxy or load balancer. --tlsport Specifies the standalone tls listening port. Only valid if the server is behind a reverse proxy or load balancer. --local-address Specifies the standalone/tls server listening address, in case you have multiple ip addresses. @@ -7357,6 +7427,7 @@ _process() { _accountkey="" _certhome="" _confighome="" + _enable_ari="" _httpport="" _tlsport="" _dnssleep="" @@ -7704,6 +7775,15 @@ _process() { Le_RenewalDays="$_days" shift ;; + --enable-ari) + _enable_ari="$2" + if [ -z "$_enable_ari" ] || _startswith "$_enable_ari" '-'; then + Le_EnableRenewalInfo="1" + else + shift + fi + Le_EnableRenewalInfo="$_enable_ari" + ;; --valid-from) _valid_from="$2" shift From c1251ed6135ad80b286ac54678ca470243d4e56e Mon Sep 17 00:00:00 2001 From: sim0n-v <218359733+sim0n-v@users.noreply.github.com> Date: Sat, 5 Jul 2025 10:31:43 +0200 Subject: [PATCH 07/12] fix cert_id storage --- acme.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/acme.sh b/acme.sh index 8baee588..30a5a826 100755 --- a/acme.sh +++ b/acme.sh @@ -2328,8 +2328,8 @@ _calc_cert_id() { _err "Failed to parse certificate Authority Key Identifier" return 1 fi - _cert_id="$_cert_authority_kid.$_cert_serial" - _debug2 "Certificate ID for Renewal Info: $_cert_id" + Le_RenewalInfoCertId="$_cert_authority_kid.$_cert_serial" + _debug2 "Certificate ID for Renewal Info: $Le_RenewalInfoCertId" return 0 } From 311654fb8afd6ca64b1cf6f44b80589bfbe26959 Mon Sep 17 00:00:00 2001 From: "Simon V." <218359733+sim0n-v@users.noreply.github.com> Date: Fri, 3 Oct 2025 21:34:34 +0200 Subject: [PATCH 08/12] Remove directory duplicate --- acme.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/acme.sh b/acme.sh index 0a867452..deaf57a0 100755 --- a/acme.sh +++ b/acme.sh @@ -2793,7 +2793,6 @@ _clearAPI() { ACME_REVOKE_CERT="" ACME_NEW_NONCE="" ACME_AGREEMENT="" - ACME_RENEWAL_INFO="" } #server From c6b43ad293b919514cb1d9a6a829160202c035b6 Mon Sep 17 00:00:00 2001 From: "Simon V." <218359733+sim0n-v@users.noreply.github.com> Date: Fri, 3 Oct 2025 21:39:34 +0200 Subject: [PATCH 09/12] Cleanup ARI Calculation --- acme.sh | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/acme.sh b/acme.sh index deaf57a0..e68d88ba 100755 --- a/acme.sh +++ b/acme.sh @@ -6603,36 +6603,6 @@ deactivate() { done } -#cert -_getAKI() { - _cert="$1" - openssl x509 -in "$_cert" -text -noout | grep "X509v3 Authority Key Identifier" -A 1 | _tail_n 1 | tr -d ' :' -} - -#cert -_getSerial() { - _cert="$1" - openssl x509 -in "$_cert" -serial -noout | cut -d = -f 2 -} - -#cert -_get_ARI() { - _cert="$1" - _aki=$(_getAKI "$_cert") - _ser=$(_getSerial "$_cert") - _debug2 "_aki" "$_aki" - _debug2 "_ser" "$_ser" - - _akiurl="$(echo "$_aki" | _h2b | _base64 | tr -d = | _url_encode)" - _debug2 "_akiurl" "$_akiurl" - _serurl="$(echo "$_ser" | _h2b | _base64 | tr -d = | _url_encode)" - _debug2 "_serurl" "$_serurl" - - _ARI_URL="$ACME_RENEWAL_INFO/$_akiurl.$_serurl" - _get "$_ARI_URL" - -} - # Detect profile file if not specified as environment variable _detect_profile() { if [ -n "$PROFILE" -a -f "$PROFILE" ]; then From f75105fbe3cb1cd7fc6e8cb9269abdeb24673cc7 Mon Sep 17 00:00:00 2001 From: "Simon V." <218359733+sim0n-v@users.noreply.github.com> Date: Fri, 3 Oct 2025 21:42:41 +0200 Subject: [PATCH 10/12] Fix incomplete merge --- acme.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/acme.sh b/acme.sh index e68d88ba..4a4a14c8 100755 --- a/acme.sh +++ b/acme.sh @@ -4720,6 +4720,7 @@ issue() { Le_RenewalInfoCertId=$(_readdomainconf "Le_RenewalInfoCertId") if [ "$_ACME_IS_RENEW" ] && [ -n "$Le_EnableRenewalInfo" ] && [ "$Le_EnableRenewalInfo" -eq "1" ] && [ -n "$Le_RenewalInfoCertId" ]; then _newOrderObj="$_newOrderObj,\"replaces\": \"$Le_RenewalInfoCertId\"" + fi if [ "$_certificate_profile" ]; then _newOrderObj="$_newOrderObj,\"profile\": \"$_certificate_profile\"" fi From 11831fc62c8c9c340f975121269ee532a3b0cba2 Mon Sep 17 00:00:00 2001 From: "Simon V." <218359733+sim0n-v@users.noreply.github.com> Date: Fri, 3 Oct 2025 21:43:29 +0200 Subject: [PATCH 11/12] Remove duplicated directory parsing --- acme.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/acme.sh b/acme.sh index 4a4a14c8..5c8469e5 100755 --- a/acme.sh +++ b/acme.sh @@ -2840,9 +2840,6 @@ _initAPI() { ACME_AGREEMENT=$(echo "$response" | _egrep_o 'termsOfService" *: *"[^"]*"' | cut -d '"' -f 3) export ACME_AGREEMENT - ACME_RENEWAL_INFO=$(echo "$response" | _egrep_o 'renewalInfo" *: *"[^"]*"' | cut -d '"' -f 3) - export ACME_RENEWAL_INFO - _debug "ACME_KEY_CHANGE" "$ACME_KEY_CHANGE" _debug "ACME_NEW_AUTHZ" "$ACME_NEW_AUTHZ" _debug "ACME_NEW_ORDER" "$ACME_NEW_ORDER" From 930304f6d2cd7d7a37cd8ff36ef193e9039a9b8d Mon Sep 17 00:00:00 2001 From: "Simon V." <218359733+sim0n-v@users.noreply.github.com> Date: Fri, 3 Oct 2025 21:44:12 +0200 Subject: [PATCH 12/12] Remove duplicated debug ARI URL --- acme.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/acme.sh b/acme.sh index 5c8469e5..5822be45 100755 --- a/acme.sh +++ b/acme.sh @@ -2848,7 +2848,6 @@ _initAPI() { _debug "ACME_REVOKE_CERT" "$ACME_REVOKE_CERT" _debug "ACME_AGREEMENT" "$ACME_AGREEMENT" _debug "ACME_NEW_NONCE" "$ACME_NEW_NONCE" - _debug "ACME_RENEWAL_INFO" "$ACME_RENEWAL_INFO" if [ "$ACME_NEW_ACCOUNT" ] && [ "$ACME_NEW_ORDER" ]; then return 0 fi