Browse Source

sync (#2127)

* Support for MyDevil.net (#2076)

support mydevil

* Fix verification for namecheap domains not *owned* by the calling user (#2106)

* Peb (#2126)

* support pebble
* support async finalize order

* add Pebble
neil 6 years ago
parent
commit
693d692a47
7 changed files with 307 additions and 14 deletions
  1. 2 0
      README.md
  2. 74 14
      acme.sh
  3. 10 0
      deploy/README.md
  4. 59 0
      deploy/mydevil.sh
  5. 20 0
      dnsapi/README.md
  6. 97 0
      dnsapi/dns_mydevil.sh
  7. 45 0
      dnsapi/dns_namecheap.sh

+ 2 - 0
README.md

@@ -74,6 +74,7 @@ https://github.com/Neilpang/acmetest
 
 
 - Letsencrypt.org CA(default)
 - Letsencrypt.org CA(default)
 - [BuyPass.com CA](https://github.com/Neilpang/acme.sh/wiki/BuyPass.com-CA)
 - [BuyPass.com CA](https://github.com/Neilpang/acme.sh/wiki/BuyPass.com-CA)
+- [Pebble strict Mode](https://github.com/letsencrypt/pebble)
 
 
 # Supported modes
 # Supported modes
 
 
@@ -356,6 +357,7 @@ You don't have to do anything manually!
 1. Futurehosting API (https://www.futurehosting.com)
 1. Futurehosting API (https://www.futurehosting.com)
 1. Rackspace Cloud DNS (https://www.rackspace.com)
 1. Rackspace Cloud DNS (https://www.rackspace.com)
 1. Online.net API (https://online.net/)
 1. Online.net API (https://online.net/)
+1. MyDevil.net (https://www.mydevil.net/)
 
 
 And:
 And:
 
 

+ 74 - 14
acme.sh

@@ -1827,23 +1827,29 @@ _send_signed_request() {
         nonceurl="$ACME_NEW_NONCE"
         nonceurl="$ACME_NEW_NONCE"
         if _post "" "$nonceurl" "" "HEAD" "$__request_conent_type"; then
         if _post "" "$nonceurl" "" "HEAD" "$__request_conent_type"; then
           _headers="$(cat "$HTTP_HEADER")"
           _headers="$(cat "$HTTP_HEADER")"
+          _debug2 _headers "$_headers"
+          _CACHED_NONCE="$(echo "$_headers" | grep -i "Replay-Nonce:" | _head_n 1 | tr -d "\r\n " | cut -d ':' -f 2)"
         fi
         fi
       fi
       fi
-      if [ -z "$_headers" ]; then
+      if [ -z "$_CACHED_NONCE" ]; then
         _debug2 "Get nonce with GET. ACME_DIRECTORY" "$ACME_DIRECTORY"
         _debug2 "Get nonce with GET. ACME_DIRECTORY" "$ACME_DIRECTORY"
         nonceurl="$ACME_DIRECTORY"
         nonceurl="$ACME_DIRECTORY"
         _headers="$(_get "$nonceurl" "onlyheader")"
         _headers="$(_get "$nonceurl" "onlyheader")"
+        _debug2 _headers "$_headers"
+        _CACHED_NONCE="$(echo "$_headers" | grep -i "Replay-Nonce:" | _head_n 1 | tr -d "\r\n " | cut -d ':' -f 2)"
       fi
       fi
-
+      if [ -z "$_CACHED_NONCE" ] && [ "$ACME_NEW_NONCE" ]; then
+        _debug2 "Get nonce with GET. ACME_NEW_NONCE" "$ACME_NEW_NONCE"
+        nonceurl="$ACME_NEW_NONCE"
+        _headers="$(_get "$nonceurl" "onlyheader")"
+        _debug2 _headers "$_headers"
+        _CACHED_NONCE="$(echo "$_headers" | grep -i "Replay-Nonce:" | _head_n 1 | tr -d "\r\n " | cut -d ':' -f 2)"
+      fi
+      _debug2 _CACHED_NONCE "$_CACHED_NONCE"
       if [ "$?" != "0" ]; then
       if [ "$?" != "0" ]; then
         _err "Can not connect to $nonceurl to get nonce."
         _err "Can not connect to $nonceurl to get nonce."
         return 1
         return 1
       fi
       fi
-
-      _debug2 _headers "$_headers"
-
-      _CACHED_NONCE="$(echo "$_headers" | grep "Replay-Nonce:" | _head_n 1 | tr -d "\r\n " | cut -d ':' -f 2)"
-      _debug2 _CACHED_NONCE "$_CACHED_NONCE"
     else
     else
       _debug2 "Use _CACHED_NONCE" "$_CACHED_NONCE"
       _debug2 "Use _CACHED_NONCE" "$_CACHED_NONCE"
     fi
     fi
@@ -2060,6 +2066,7 @@ _clearcaconf() {
 _startserver() {
 _startserver() {
   content="$1"
   content="$1"
   ncaddr="$2"
   ncaddr="$2"
+  _debug "content" "$content"
   _debug "ncaddr" "$ncaddr"
   _debug "ncaddr" "$ncaddr"
 
 
   _debug "startserver: $$"
   _debug "startserver: $$"
@@ -2086,8 +2093,14 @@ _startserver() {
     SOCAT_OPTIONS="$SOCAT_OPTIONS,bind=${ncaddr}"
     SOCAT_OPTIONS="$SOCAT_OPTIONS,bind=${ncaddr}"
   fi
   fi
 
 
+  _content_len="$(printf "%s" "$content" | wc -c)"
+  _debug _content_len "$_content_len"
   _debug "_NC" "$_NC $SOCAT_OPTIONS"
   _debug "_NC" "$_NC $SOCAT_OPTIONS"
-  $_NC $SOCAT_OPTIONS SYSTEM:"sleep 1; echo HTTP/1.0 200 OK; echo ; echo  $content; echo;" &
+  $_NC $SOCAT_OPTIONS SYSTEM:"sleep 1; \
+echo 'HTTP/1.0 200 OK'; \
+echo 'Content-Length\: $_content_len'; \
+echo ''; \
+printf '$content';" &
   serverproc="$!"
   serverproc="$!"
 }
 }
 
 
@@ -3062,6 +3075,7 @@ _on_before_issue() {
       _info "Standalone mode."
       _info "Standalone mode."
       if [ -z "$Le_HTTPPort" ]; then
       if [ -z "$Le_HTTPPort" ]; then
         Le_HTTPPort=80
         Le_HTTPPort=80
+        _cleardomainconf "Le_HTTPPort"
       else
       else
         _savedomainconf "Le_HTTPPort" "$Le_HTTPPort"
         _savedomainconf "Le_HTTPPort" "$Le_HTTPPort"
       fi
       fi
@@ -3269,7 +3283,7 @@ _regAccount() {
   fi
   fi
 
 
   _debug2 responseHeaders "$responseHeaders"
   _debug2 responseHeaders "$responseHeaders"
-  _accUri="$(echo "$responseHeaders" | grep "^Location:" | _head_n 1 | cut -d ' ' -f 2 | tr -d "\r\n")"
+  _accUri="$(echo "$responseHeaders" | grep -i "^Location:" | _head_n 1 | cut -d ' ' -f 2 | tr -d "\r\n")"
   _debug "_accUri" "$_accUri"
   _debug "_accUri" "$_accUri"
   if [ -z "$_accUri" ]; then
   if [ -z "$_accUri" ]; then
     _err "Can not find account id url."
     _err "Can not find account id url."
@@ -3435,7 +3449,7 @@ __trigger_validation() {
   _t_vtype="$3"
   _t_vtype="$3"
   _debug2 _t_vtype "$_t_vtype"
   _debug2 _t_vtype "$_t_vtype"
   if [ "$ACME_VERSION" = "2" ]; then
   if [ "$ACME_VERSION" = "2" ]; then
-    _send_signed_request "$_t_url" "{\"keyAuthorization\": \"$_t_key_authz\"}"
+    _send_signed_request "$_t_url" "{}"
   else
   else
     _send_signed_request "$_t_url" "{\"resource\": \"challenge\", \"type\": \"$_t_vtype\", \"keyAuthorization\": \"$_t_key_authz\"}"
     _send_signed_request "$_t_url" "{\"resource\": \"challenge\", \"type\": \"$_t_vtype\", \"keyAuthorization\": \"$_t_key_authz\"}"
   fi
   fi
@@ -4205,20 +4219,66 @@ $_authorizations_map"
   der="$(_getfile "${CSR_PATH}" "${BEGIN_CSR}" "${END_CSR}" | tr -d "\r\n" | _url_replace)"
   der="$(_getfile "${CSR_PATH}" "${BEGIN_CSR}" "${END_CSR}" | tr -d "\r\n" | _url_replace)"
 
 
   if [ "$ACME_VERSION" = "2" ]; then
   if [ "$ACME_VERSION" = "2" ]; then
+    _info "Lets finalize the order, Le_OrderFinalize: $Le_OrderFinalize"
     if ! _send_signed_request "${Le_OrderFinalize}" "{\"csr\": \"$der\"}"; then
     if ! _send_signed_request "${Le_OrderFinalize}" "{\"csr\": \"$der\"}"; then
       _err "Sign failed."
       _err "Sign failed."
       _on_issue_err "$_post_hook"
       _on_issue_err "$_post_hook"
       return 1
       return 1
     fi
     fi
     if [ "$code" != "200" ]; then
     if [ "$code" != "200" ]; then
-      _err "Sign failed, code is not 200."
+      _err "Sign failed, finalize code is not 200."
       _err "$response"
       _err "$response"
       _on_issue_err "$_post_hook"
       _on_issue_err "$_post_hook"
       return 1
       return 1
     fi
     fi
-    Le_LinkCert="$(echo "$response" | tr -d '\r\n' | _egrep_o '"certificate" *: *"[^"]*"' | cut -d '"' -f 4)"
+    Le_LinkOrder="$(echo "$responseHeaders" | grep -i '^Location.*$' | _tail_n 1 | tr -d "\r\n" | cut -d " " -f 2)"
+    if [ -z "$Le_LinkOrder" ]; then
+      _err "Sign error, can not get order link location header"
+      _err "responseHeaders" "$responseHeaders"
+      _on_issue_err "$_post_hook"
+      return 1
+    fi
+    _savedomainconf "Le_LinkOrder" "$Le_LinkOrder"
+
+    _link_cert_retry=0
+    _MAX_CERT_RETRY=5
+    while [ -z "$Le_LinkCert" ] && [ "$_link_cert_retry" -lt "$_MAX_CERT_RETRY" ]; do
+      if _contains "$response" "\"status\":\"valid\""; then
+        _debug "Order status is valid."
+        Le_LinkCert="$(echo "$response" | tr -d '\r\n' | _egrep_o '"certificate" *: *"[^"]*"' | cut -d '"' -f 4)"
+        _debug Le_LinkCert "$Le_LinkCert"
+        if [ -z "$Le_LinkCert" ]; then
+          _err "Sign error, can not find Le_LinkCert"
+          _err "$response"
+          _on_issue_err "$_post_hook"
+          return 1
+        fi
+        break
+      elif _contains "$response" "\"processing\""; then
+        _info "Order status is processing, lets sleep and retry."
+        _sleep 2
+      else
+        _err "Sign error, wrong status"
+        _err "$response"
+        _on_issue_err "$_post_hook"
+        return 1
+      fi
+      if ! _send_signed_request "$Le_LinkOrder"; then
+        _err "Sign failed, can not post to Le_LinkOrder cert:$Le_LinkOrder."
+        _err "$response"
+        _on_issue_err "$_post_hook"
+        return 1
+      fi
+      _link_cert_retry="$(_math $_link_cert_retry + 1)"
+    done
 
 
-    _tempSignedResponse="$response"
+    if [ -z "$Le_LinkCert" ]; then
+      _err "Sign failed, can not get Le_LinkCert, retry time limit."
+      _err "$response"
+      _on_issue_err "$_post_hook"
+      return 1
+    fi
+    _info "Download cert, Le_LinkCert: $Le_LinkCert"
     if ! _send_signed_request "$Le_LinkCert"; then
     if ! _send_signed_request "$Le_LinkCert"; then
       _err "Sign failed, can not download cert:$Le_LinkCert."
       _err "Sign failed, can not download cert:$Le_LinkCert."
       _err "$response"
       _err "$response"
@@ -4237,7 +4297,7 @@ $_authorizations_map"
       _end_n="$(_math $_end_n + 1)"
       _end_n="$(_math $_end_n + 1)"
       sed -n "${_end_n},9999p" "$CERT_FULLCHAIN_PATH" >"$CA_CERT_PATH"
       sed -n "${_end_n},9999p" "$CERT_FULLCHAIN_PATH" >"$CA_CERT_PATH"
     fi
     fi
-    response="$_tempSignedResponse"
+
   else
   else
     if ! _send_signed_request "${ACME_NEW_ORDER}" "{\"resource\": \"$ACME_NEW_ORDER_RES\", \"csr\": \"$der\"}" "needbase64"; then
     if ! _send_signed_request "${ACME_NEW_ORDER}" "{\"resource\": \"$ACME_NEW_ORDER_RES\", \"csr\": \"$der\"}" "needbase64"; then
       _err "Sign failed. $response"
       _err "Sign failed. $response"

+ 10 - 0
deploy/README.md

@@ -381,3 +381,13 @@ you want to update:
 $ export QINIU_CDN_DOMAIN="cdn.example.com"
 $ export QINIU_CDN_DOMAIN="cdn.example.com"
 $ acme.sh --deploy -d example.com --deploy-hook qiniu
 $ acme.sh --deploy -d example.com --deploy-hook qiniu
 ```
 ```
+
+## 14. Deploy your cert on MyDevil.net
+
+Once you have acme.sh installed and certificate issued (see info in [DNS API](../dnsapi/README.md#61-use-mydevilnet)), you can install it by following command:
+
+```sh
+acme.sh --deploy --deploy-hook mydevil -d example.com
+```
+
+That will remove old certificate and install new one.

+ 59 - 0
deploy/mydevil.sh

@@ -0,0 +1,59 @@
+#!/usr/bin/env sh
+
+# MyDevil.net API (2019-02-03)
+#
+# MyDevil.net already supports automatic Let's Encrypt certificates,
+# except for wildcard domains.
+#
+# This script depends on `devil` command that MyDevil.net provides,
+# which means that it works only on server side.
+#
+# Author: Marcin Konicki <https://ahwayakchih.neoni.net>
+#
+########  Public functions #####################
+
+# Usage: mydevil_deploy domain keyfile certfile cafile fullchain
+mydevil_deploy() {
+  _cdomain="$1"
+  _ckey="$2"
+  _ccert="$3"
+  _cca="$4"
+  _cfullchain="$5"
+  ip=""
+
+  _debug _cdomain "$_cdomain"
+  _debug _ckey "$_ckey"
+  _debug _ccert "$_ccert"
+  _debug _cca "$_cca"
+  _debug _cfullchain "$_cfullchain"
+
+  if ! _exists "devil"; then
+    _err "Could not find 'devil' command."
+    return 1
+  fi
+
+  ip=$(mydevil_get_ip "$_cdomain")
+  if [ -z "$ip" ]; then
+    _err "Could not find IP for domain $_cdomain."
+    return 1
+  fi
+
+  # Delete old certificate first
+  _info "Removing old certificate for $_cdomain at $ip"
+  devil ssl www del "$ip" "$_cdomain"
+
+  # Add new certificate
+  _info "Adding new certificate for $_cdomain at $ip"
+  devil ssl www add "$ip" "$_cfullchain" "$_ckey" "$_cdomain" || return 1
+
+  return 0
+}
+
+####################  Private functions below ##################################
+
+# Usage: ip=$(mydevil_get_ip domain.com)
+#        echo $ip
+mydevil_get_ip() {
+  devil dns list "$1" | cut -w -s -f 3,7 | grep "^A$(printf '\t')" | cut -w -s -f 2 || return 1
+  return 0
+}

+ 20 - 0
dnsapi/README.md

@@ -1259,6 +1259,26 @@ acme.sh --issue --dns dns_online -d example.com -d www.example.com
 
 
 `ONLINE_API_KEY` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
 `ONLINE_API_KEY` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
 
 
+## 66. Use MyDevil.net
+
+Make sure that you can execute own binaries:
+
+```sh
+devil binexec on
+```
+
+Install acme.sh, or simply `git clone` it into some directory on your MyDevil host account (in which case you should link to it from your `~/bin` directory).
+
+If you're not using private IP and depend on default IP provided by host, you may want to edit `crontab` too, and make sure that `acme.sh --cron` is run also after reboot (you can find out how to do that on their wiki pages).
+
+To issue a new certificate, run:
+
+```sh
+acme.sh --issue --dns dns_mydevil -d example.com -d *.example.com
+```
+
+After certificate is ready, you can install it with [deploy command](../deploy/README.md#14-deploy-your-cert-on-mydevilnet).
+
 # Use custom API
 # Use custom API
 
 
 If your API is not supported yet, you can write your own DNS API.
 If your API is not supported yet, you can write your own DNS API.

+ 97 - 0
dnsapi/dns_mydevil.sh

@@ -0,0 +1,97 @@
+#!/usr/bin/env sh
+
+# MyDevil.net API (2019-02-03)
+#
+# MyDevil.net already supports automatic Let's Encrypt certificates,
+# except for wildcard domains.
+#
+# This script depends on `devil` command that MyDevil.net provides,
+# which means that it works only on server side.
+#
+# Author: Marcin Konicki <https://ahwayakchih.neoni.net>
+#
+########  Public functions #####################
+
+#Usage: dns_mydevil_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_mydevil_add() {
+  fulldomain=$1
+  txtvalue=$2
+  domain=""
+
+  if ! _exists "devil"; then
+    _err "Could not find 'devil' command."
+    return 1
+  fi
+
+  _info "Using mydevil"
+
+  domain=$(mydevil_get_domain "$fulldomain")
+  if [ -z "$domain" ]; then
+    _err "Invalid domain name: could not find root domain of $fulldomain."
+    return 1
+  fi
+
+  # No need to check if record name exists, `devil` always adds new record.
+  # In worst case scenario, we end up with multiple identical records.
+
+  _info "Adding $fulldomain record for domain $domain"
+  if devil dns add "$domain" "$fulldomain" TXT "$txtvalue"; then
+    _info "Successfully added TXT record, ready for validation."
+    return 0
+  else
+    _err "Unable to add DNS record."
+    return 1
+  fi
+}
+
+#Usage: fulldomain txtvalue
+#Remove the txt record after validation.
+dns_mydevil_rm() {
+  fulldomain=$1
+  txtvalue=$2
+  domain=""
+
+  if ! _exists "devil"; then
+    _err "Could not find 'devil' command."
+    return 1
+  fi
+
+  _info "Using mydevil"
+
+  domain=$(mydevil_get_domain "$fulldomain")
+  if [ -z "$domain" ]; then
+    _err "Invalid domain name: could not find root domain of $fulldomain."
+    return 1
+  fi
+
+  # catch one or more numbers
+  num='[0-9][0-9]*'
+  # catch one or more whitespace
+  w=$(printf '[\t ][\t ]*')
+  # catch anything, except newline
+  any='.*'
+  # filter to make sure we do not delete other records
+  validRecords="^${num}${w}${fulldomain}${w}TXT${w}${any}${txtvalue}$"
+  for id in $(devil dns list "$domain" | tail -n+2 | grep "${validRecords}" | cut -w -s -f 1); do
+    _info "Removing record $id from domain $domain"
+    devil dns del "$domain" "$id" || _err "Could not remove DNS record."
+  done
+}
+
+####################  Private functions below ##################################
+
+# Usage: domain=$(mydevil_get_domain "_acme-challenge.www.domain.com" || _err "Invalid domain name")
+#        echo $domain
+mydevil_get_domain() {
+  fulldomain=$1
+  domain=""
+
+  for domain in $(devil dns list | cut -w -s -f 1 | tail -n+2); do
+    if _endswith "$fulldomain" "$domain"; then
+      printf -- "%s" "$domain"
+      return 0
+    fi
+  done
+
+  return 1
+}

+ 45 - 0
dnsapi/dns_namecheap.sh

@@ -76,6 +76,22 @@ dns_namecheap_rm() {
 # _sub_domain=_acme-challenge.www
 # _sub_domain=_acme-challenge.www
 # _domain=domain.com
 # _domain=domain.com
 _get_root() {
 _get_root() {
+  fulldomain=$1
+
+  if ! _get_root_by_getList "$fulldomain"; then
+    _debug "Failed domain lookup via domains.getList api call. Trying domain lookup via domains.dns.getHosts api."
+    # The above "getList" api will only return hosts *owned* by the calling user. However, if the calling
+    # user is not the owner, but still has administrative rights, we must query the getHosts api directly.
+    # See this comment and the official namecheap response: http://disq.us/p/1q6v9x9
+    if ! _get_root_by_getHosts "$fulldomain"; then
+      return 1
+    fi
+  fi
+
+  return 0
+}
+
+_get_root_by_getList() {
   domain=$1
   domain=$1
 
 
   if ! _namecheap_post "namecheap.domains.getList"; then
   if ! _namecheap_post "namecheap.domains.getList"; then
@@ -94,6 +110,10 @@ _get_root() {
       #not valid
       #not valid
       return 1
       return 1
     fi
     fi
+    if ! _contains "$h" "\\."; then
+      #not valid
+      return 1
+    fi
 
 
     if ! _contains "$response" "$h"; then
     if ! _contains "$response" "$h"; then
       _debug "$h not found"
       _debug "$h not found"
@@ -108,6 +128,31 @@ _get_root() {
   return 1
   return 1
 }
 }
 
 
+_get_root_by_getHosts() {
+  i=100
+  p=99
+
+  while [ $p -ne 0 ]; do
+
+    h=$(printf "%s" "$1" | cut -d . -f $i-100)
+    if [ -n "$h" ]; then
+      if _contains "$h" "\\."; then
+        _debug h "$h"
+        if _namecheap_set_tld_sld "$h"; then
+          _sub_domain=$(printf "%s" "$1" | cut -d . -f 1-$p)
+          _domain="$h"
+          return 0
+        else
+          _debug "$h not found"
+        fi
+      fi
+    fi
+    i="$p"
+    p=$(_math "$p" - 1)
+  done
+  return 1
+}
+
 _namecheap_set_publicip() {
 _namecheap_set_publicip() {
 
 
   if [ -z "$NAMECHEAP_SOURCEIP" ]; then
   if [ -z "$NAMECHEAP_SOURCEIP" ]; then