]> git.street.me.uk Git - andy/dehydrated.git/blobdiff - letsencrypt.sh
Merge pull request #85 from jaquer/fix-exit
[andy/dehydrated.git] / letsencrypt.sh
index fec1979c1789b55cbc6a721eceabf78ed14fff32..029ed745c60ffe18b09254a96dfe780932b8bfef 100755 (executable)
@@ -12,9 +12,9 @@ BASEDIR="${SCRIPTDIR}"
 check_dependencies() {
   curl -V > /dev/null 2>&1 || _exiterr "This script requires curl."
   openssl version > /dev/null 2>&1 || _exiterr "This script requres an openssl binary."
-  sed "" < /dev/null > /dev/null 2>&1 || _exiterr "This script requres sed."
+  sed -E "" < /dev/null > /dev/null 2>&1 || _exiterr "This script requres sed with support for extended (modern) regular expressions."
   grep -V > /dev/null 2>&1 || _exiterr "This script requres grep."
-  mktemp -u > /dev/null 2>&1 || _exiterr "This script requires mktemp."
+  mktemp -u -t XXXXXX > /dev/null 2>&1 || _exiterr "This script requires mktemp."
 }
 
 # Setup default config values, search for and load configuration files
@@ -33,6 +33,7 @@ load_config() {
   # Default values
   CA="https://acme-v01.api.letsencrypt.org/directory"
   LICENSE="https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf"
+  CHALLENGETYPE="http-01"
   HOOK=
   RENEW_DAYS="30"
   PRIVATE_KEY="${BASEDIR}/private_key.pem"
@@ -62,8 +63,12 @@ load_config() {
   # Check BASEDIR and set default variables
   [[ -d "${BASEDIR}" ]] || _exiterr "BASEDIR does not exist: ${BASEDIR}"
 
-  if [[ -n "${PARAM_HOOK:-}" ]]; then
-    HOOK="${PARAM_HOOK}"
+  [[ -n "${PARAM_HOOK:-}" ]] && HOOK="${PARAM_HOOK}"
+  [[ -n "${PARAM_CHALLENGETYPE:-}" ]] && CHALLENGETYPE="${PARAM_CHALLENGETYPE}"
+
+  [[ "${CHALLENGETYPE}" =~ (http-01|dns-01) ]] || _exiterr "Unknown challenge type ${CHALLENGETYPE}... can not continue."
+  if [[ "${CHALLENGETYPE}" = "dns-01" ]] && [[ -z "${HOOK}" ]]; then
+   _exiterr "Challenge type dns-01 needs a hook script for deployment... can not continue."
   fi
 }
 
@@ -72,6 +77,8 @@ init_system() {
   load_config
 
   # Lockfile handling (prevents concurrent access)
+  LOCKDIR="$(dirname "${LOCKFILE}")"
+  [[ -w "${LOCKDIR}" ]] || _exiterr "Directory ${LOCKDIR} for LOCKFILE ${LOCKFILE} is not writable, aborting."
   ( set -C; date > "${LOCKFILE}" ) 2>/dev/null || _exiterr "Lock file '${LOCKFILE}' present, aborting."
   remove_lock() { rm -f "${LOCKFILE}"; }
   trap 'remove_lock' EXIT
@@ -166,7 +173,7 @@ _openssl() {
 
 # Send http(s) request with specified method
 http_request() {
-  tempcont="$(mktemp)"
+  tempcont="$(mktemp -t XXXXXX)"
 
   if [[ "${1}" = "head" ]]; then
     statuscode="$(curl -s -w "%{http_code}" -o "${tempcont}" "${2}" -I)"
@@ -256,7 +263,7 @@ sign_domain() {
   done
   SAN="${SAN%%, }"
   local tmp_openssl_cnf
-  tmp_openssl_cnf="$(mktemp)"
+  tmp_openssl_cnf="$(mktemp -t XXXXXX)"
   cat "${OPENSSL_CNF}" > "${tmp_openssl_cnf}"
   printf "[SAN]\nsubjectAltName=%s" "${SAN}" >> "${tmp_openssl_cnf}"
   openssl req -new -sha256 -key "${BASEDIR}/certs/${domain}/${privkey}" -out "${BASEDIR}/certs/${domain}/cert-${timestamp}.csr" -subj "/CN=${domain}/" -reqexts SAN -config "${tmp_openssl_cnf}"
@@ -270,7 +277,7 @@ sign_domain() {
 
     challenges="$(printf '%s\n' "${response}" | grep -Eo '"challenges":[^\[]*\[[^]]*]')"
     repl=$'\n''{' # fix syntax highlighting in Vim
-    challenge="$(printf "%s" "${challenges//\{/${repl}}" | grep 'http-01')"
+    challenge="$(printf "%s" "${challenges//\{/${repl}}" | grep \""${CHALLENGETYPE}"\")"
     challenge_token="$(printf '%s' "${challenge}" | get_json_string_value token | sed 's/[^A-Za-z0-9_\-]/_/g')"
     challenge_uri="$(printf '%s' "${challenge}" | get_json_string_value uri)"
 
@@ -281,12 +288,21 @@ sign_domain() {
     # Challenge response consists of the challenge token and the thumbprint of our public certificate
     keyauth="${challenge_token}.${thumbprint}"
 
-    # Store challenge response in well-known location and make world-readable (so that a webserver can access it)
-    printf '%s' "${keyauth}" > "${WELLKNOWN}/${challenge_token}"
-    chmod a+r "${WELLKNOWN}/${challenge_token}"
+    case "${CHALLENGETYPE}" in
+      "http-01")
+        # Store challenge response in well-known location and make world-readable (so that a webserver can access it)
+        printf '%s' "${keyauth}" > "${WELLKNOWN}/${challenge_token}"
+        chmod a+r "${WELLKNOWN}/${challenge_token}"
+        keyauth_hook="${keyauth}"
+        ;;
+      "dns-01")
+        # Generate DNS entry content for dns-01 validation
+        keyauth_hook="$(printf '%s' "${keyauth}" | openssl sha -sha256 -binary | urlbase64)"
+        ;;
+    esac
 
     # Wait for hook script to deploy the challenge if used
-    [[ -n "${HOOK}" ]] && ${HOOK} "deploy_challenge" "${altname}" "${challenge_token}" "${keyauth}"
+    [[ -n "${HOOK}" ]] && ${HOOK} "deploy_challenge" "${altname}" "${challenge_token}" "${keyauth_hook}"
 
     # Ask the acme-server to verify our challenge and wait until it is no longer pending
     echo " + Responding to challenge for ${altname}..."
@@ -299,11 +315,11 @@ sign_domain() {
       status="$(http_request get "${challenge_uri}" | get_json_string_value status)"
     done
 
-    rm -f "${WELLKNOWN}/${challenge_token}"
+    [[ "${CHALLENGETYPE}" = "http-01" ]] && rm -f "${WELLKNOWN}/${challenge_token}"
 
     # Wait for hook script to clean the challenge if used
     if [[ -n "${HOOK}" ]] && [[ -n "${challenge_token}" ]]; then
-      ${HOOK} "clean_challenge" "${altname}" "${challenge_token}" "${keyauth}"
+      ${HOOK} "clean_challenge" "${altname}" "${challenge_token}" "${keyauth_hook}"
     fi
 
     if [[ "${status}" = "valid" ]]; then
@@ -354,7 +370,7 @@ command_sign_domains() {
   init_system
 
   if [[ -n "${PARAM_DOMAIN:-}" ]]; then
-    DOMAINS_TXT="$(mktemp)"
+    DOMAINS_TXT="$(mktemp -t XXXXXX)"
     printf -- "${PARAM_DOMAIN}" > "${DOMAINS_TXT}"
   elif [[ -e "${BASEDIR}/domains.txt" ]]; then
     DOMAINS_TXT="${BASEDIR}/domains.txt"
@@ -363,7 +379,7 @@ command_sign_domains() {
   fi
 
   # Generate certificates for all domains found in domains.txt. Check if existing certificate are about to expire
-  <"${DOMAINS_TXT}" sed 's/^[[:space:]]*//g;s/[[:space:]]*$//g' | (grep -vE '^(#|$)' || true) | while read -r line; do
+  <"${DOMAINS_TXT}" sed -E -e 's/^[[:space:]]*//g' -e 's/[[:space:]]*$//g' -e 's/[[:space:]]+/ /g' | (grep -vE '^(#|$)' || true) | while read -r line; do
     domain="$(printf '%s\n' "${line}" | cut -d' ' -f1)"
     morenames="$(printf '%s\n' "${line}" | cut -s -d' ' -f2-)"
     cert="${BASEDIR}/certs/${domain}/cert.pem"
@@ -479,7 +495,7 @@ command_help() {
 command_env() {
   echo "# letsencrypt.sh configuration"
   load_config
-  typeset -p CA LICENSE HOOK RENEW_DAYS PRIVATE_KEY KEYSIZE WELLKNOWN PRIVATE_KEY_RENEW OPENSSL_CNF CONTACT_EMAIL LOCKFILE
+  typeset -p CA LICENSE CHALLENGETYPE HOOK RENEW_DAYS PRIVATE_KEY KEYSIZE WELLKNOWN PRIVATE_KEY_RENEW OPENSSL_CNF CONTACT_EMAIL LOCKFILE
 }
 
 # Main method (parses script arguments and calls command_* methods)
@@ -501,6 +517,8 @@ main() {
     fi
   }
 
+  [[ -z "${@}" ]] && eval set -- "--help"
+
   while (( "${#}" )); do
     case "${1}" in
       --help|-h)
@@ -566,6 +584,14 @@ main() {
         PARAM_HOOK="${1}"
         ;;
 
+      # PARAM_Usage: --challenge (-t) http-01|dns-01
+      # PARAM_Description: Which challenge should be used? Currently http-01 and dns-01 are supported
+      --challenge|-t)
+        shift 1
+        check_parameters "${1:-}"
+        PARAM_CHALLENGETYPE="${1}"
+        ;;
+
       *)
         echo "Unknown parameter detected: ${1}" >&2
         echo >&2
@@ -581,7 +607,7 @@ main() {
     env) command_env;;
     sign_domains) command_sign_domains;;
     revoke) command_revoke "${PARAM_REVOKECERT}";;
-    *) command_help; exit1;;
+    *) command_help; exit 1;;
   esac
 }