Browse Source

Update to latest letsencrypt.sh.

master
Chris Smith 7 years ago
parent
commit
f9c1714118
1 changed files with 256 additions and 58 deletions
  1. 256
    58
      letsencrypt.sh

+ 256
- 58
letsencrypt.sh View File

@@ -35,6 +35,7 @@ check_dependencies() {
35 35
   _sed "" < /dev/null > /dev/null 2>&1 || _exiterr "This script requires sed with support for extended (modern) regular expressions."
36 36
   command -v grep > /dev/null 2>&1 || _exiterr "This script requires grep."
37 37
   _mktemp -u > /dev/null 2>&1 || _exiterr "This script requires mktemp."
38
+  diff -u /dev/null /dev/null || _exiterr "This script requires diff."
38 39
 
39 40
   # curl returns with an error code in some ancient versions so we have to catch that
40 41
   set +e
@@ -46,14 +47,57 @@ check_dependencies() {
46 47
   fi
47 48
 }
48 49
 
50
+store_configvars() {
51
+  __KEY_ALGO="${KEY_ALGO}"
52
+  __OCSP_MUST_STAPLE="${OCSP_MUST_STAPLE}"
53
+  __PRIVATE_KEY_RENEW="${PRIVATE_KEY_RENEW}"
54
+  __KEYSIZE="${KEYSIZE}"
55
+  __CHALLENGETYPE="${CHALLENGETYPE}"
56
+  __HOOK="${HOOK}"
57
+  __WELLKNOWN="${WELLKNOWN}"
58
+  __HOOK_CHAIN="${HOOK_CHAIN}"
59
+  __OPENSSL_CNF="${OPENSSL_CNF}"
60
+  __RENEW_DAYS="${RENEW_DAYS}"
61
+  __IP_VERSION="${IP_VERSION}"
62
+}
63
+
64
+reset_configvars() {
65
+  KEY_ALGO="${__KEY_ALGO}"
66
+  OCSP_MUST_STAPLE="${__OCSP_MUST_STAPLE}"
67
+  PRIVATE_KEY_RENEW="${__PRIVATE_KEY_RENEW}"
68
+  KEYSIZE="${__KEYSIZE}"
69
+  CHALLENGETYPE="${__CHALLENGETYPE}"
70
+  HOOK="${__HOOK}"
71
+  WELLKNOWN="${__WELLKNOWN}"
72
+  HOOK_CHAIN="${__HOOK_CHAIN}"
73
+  OPENSSL_CNF="${__OPENSSL_CNF}"
74
+  RENEW_DAYS="${__RENEW_DAYS}"
75
+  IP_VERSION="${__IP_VERSION}"
76
+}
77
+
78
+# verify configuration values
79
+verify_config() {
80
+  [[ "${CHALLENGETYPE}" =~ (http-01|dns-01) ]] || _exiterr "Unknown challenge type ${CHALLENGETYPE}... can not continue."
81
+  if [[ "${CHALLENGETYPE}" = "dns-01" ]] && [[ -z "${HOOK}" ]]; then
82
+    _exiterr "Challenge type dns-01 needs a hook script for deployment... can not continue."
83
+  fi
84
+  if [[ "${CHALLENGETYPE}" = "http-01" && ! -d "${WELLKNOWN}" ]]; then
85
+    _exiterr "WELLKNOWN directory doesn't exist, please create ${WELLKNOWN} and set appropriate permissions."
86
+  fi
87
+  [[ "${KEY_ALGO}" =~ ^(rsa|prime256v1|secp384r1)$ ]] || _exiterr "Unknown public key algorithm ${KEY_ALGO}... can not continue."
88
+  if [[ -n "${IP_VERSION}" ]]; then
89
+    [[ "${IP_VERSION}" = "4" || "${IP_VERSION}" = "6" ]] || _exiterr "Unknown IP version ${IP_VERSION}... can not continue."
90
+  fi
91
+}
92
+
49 93
 # Setup default config values, search for and load configuration files
50 94
 load_config() {
51 95
   # Check for config in various locations
52 96
   if [[ -z "${CONFIG:-}" ]]; then
53 97
     for check_config in "/etc/letsencrypt.sh" "/usr/local/etc/letsencrypt.sh" "${PWD}" "${SCRIPTDIR}"; do
54
-      if [[ -e "${check_config}/config.sh" ]]; then
98
+      if [[ -f "${check_config}/config" ]]; then
55 99
         BASEDIR="${check_config}"
56
-        CONFIG="${check_config}/config.sh"
100
+        CONFIG="${check_config}/config"
57 101
         break
58 102
       fi
59 103
     done
@@ -61,14 +105,16 @@ load_config() {
61 105
 
62 106
   # Default values
63 107
   CA="https://acme-v01.api.letsencrypt.org/directory"
64
-  LICENSE="https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf"
108
+  LICENSE="https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf"
109
+  CERTDIR=
110
+  ACCOUNTDIR=
65 111
   CHALLENGETYPE="http-01"
66 112
   CONFIG_D=
113
+  DOMAINS_D=
114
+  DOMAINS_TXT=
67 115
   HOOK=
68 116
   HOOK_CHAIN="no"
69 117
   RENEW_DAYS="30"
70
-  ACCOUNT_KEY=
71
-  ACCOUNT_KEY_JSON=
72 118
   KEYSIZE="4096"
73 119
   WELLKNOWN=
74 120
   PRIVATE_KEY_RENEW="yes"
@@ -76,12 +122,14 @@ load_config() {
76 122
   OPENSSL_CNF="$(openssl version -d | cut -d\" -f2)/openssl.cnf"
77 123
   CONTACT_EMAIL=
78 124
   LOCKFILE=
125
+  OCSP_MUST_STAPLE="no"
126
+  IP_VERSION=
79 127
 
80 128
   if [[ -z "${CONFIG:-}" ]]; then
81 129
     echo "#" >&2
82 130
     echo "# !! WARNING !! No main config file found, using default config!" >&2
83 131
     echo "#" >&2
84
-  elif [[ -e "${CONFIG}" ]]; then
132
+  elif [[ -f "${CONFIG}" ]]; then
85 133
     echo "# INFO: Using main config file ${CONFIG}"
86 134
     BASEDIR="$(dirname "${CONFIG}")"
87 135
     # shellcheck disable=SC1090
@@ -115,20 +163,37 @@ load_config() {
115 163
   # Check BASEDIR and set default variables
116 164
   [[ -d "${BASEDIR}" ]] || _exiterr "BASEDIR does not exist: ${BASEDIR}"
117 165
 
118
-  [[ -z "${ACCOUNT_KEY}" ]] && ACCOUNT_KEY="${BASEDIR}/private_key.pem"
119
-  [[ -z "${ACCOUNT_KEY_JSON}" ]] && ACCOUNT_KEY_JSON="${BASEDIR}/private_key.json"
120
-  [[ -z "${WELLKNOWN}" ]] && WELLKNOWN="${BASEDIR}/.acme-challenges"
166
+  CAHASH="$(echo "${CA}" | urlbase64)"
167
+  [[ -z "${ACCOUNTDIR}" ]] && ACCOUNTDIR="${BASEDIR}/accounts"
168
+  mkdir -p "${ACCOUNTDIR}/${CAHASH}"
169
+  [[ -f "${ACCOUNTDIR}/${CAHASH}/config" ]] && . "${ACCOUNTDIR}/${CAHASH}/config"
170
+  ACCOUNT_KEY="${ACCOUNTDIR}/${CAHASH}/account_key.pem"
171
+  ACCOUNT_KEY_JSON="${ACCOUNTDIR}/${CAHASH}/registration_info.json"
172
+
173
+  if [[ -f "${BASEDIR}/private_key.pem" ]] && [[ ! -f "${ACCOUNT_KEY}" ]]; then
174
+    echo "! Moving private_key.pem to ${ACCOUNT_KEY}"
175
+    mv "${BASEDIR}/private_key.pem" "${ACCOUNT_KEY}"
176
+  fi
177
+  if [[ -f "${BASEDIR}/private_key.json" ]] && [[ ! -f "${ACCOUNT_KEY_JSON}" ]]; then
178
+    echo "! Moving private_key.json to ${ACCOUNT_KEY_JSON}"
179
+    mv "${BASEDIR}/private_key.json" "${ACCOUNT_KEY_JSON}"
180
+  fi
181
+
182
+  [[ -z "${CERTDIR}" ]] && CERTDIR="${BASEDIR}/certs"
183
+  [[ -z "${DOMAINS_TXT}" ]] && DOMAINS_TXT="${BASEDIR}/domains.txt"
184
+  [[ -z "${WELLKNOWN}" ]] && WELLKNOWN="/var/www/letsencrypt"
121 185
   [[ -z "${LOCKFILE}" ]] && LOCKFILE="${BASEDIR}/lock"
186
+  [[ -n "${PARAM_NO_LOCK:-}" ]] && LOCKFILE=""
122 187
 
123 188
   [[ -n "${PARAM_HOOK:-}" ]] && HOOK="${PARAM_HOOK}"
189
+  [[ -n "${PARAM_CERTDIR:-}" ]] && CERTDIR="${PARAM_CERTDIR}"
124 190
   [[ -n "${PARAM_CHALLENGETYPE:-}" ]] && CHALLENGETYPE="${PARAM_CHALLENGETYPE}"
125 191
   [[ -n "${PARAM_KEY_ALGO:-}" ]] && KEY_ALGO="${PARAM_KEY_ALGO}"
192
+  [[ -n "${PARAM_OCSP_MUST_STAPLE:-}" ]] && OCSP_MUST_STAPLE="${PARAM_OCSP_MUST_STAPLE}"
193
+  [[ -n "${PARAM_IP_VERSION:-}" ]] && IP_VERSION="${PARAM_IP_VERSION}"
126 194
 
127
-  [[ "${CHALLENGETYPE}" =~ (http-01|dns-01) ]] || _exiterr "Unknown challenge type ${CHALLENGETYPE}... can not continue."
128
-  if [[ "${CHALLENGETYPE}" = "dns-01" ]] && [[ -z "${HOOK}" ]]; then
129
-   _exiterr "Challenge type dns-01 needs a hook script for deployment... can not continue."
130
-  fi
131
-  [[ "${KEY_ALGO}" =~ ^(rsa|prime256v1|secp384r1)$ ]] || _exiterr "Unknown public key algorithm ${KEY_ALGO}... can not continue."
195
+  verify_config
196
+  store_configvars
132 197
 }
133 198
 
134 199
 # Initialize system
@@ -136,11 +201,13 @@ init_system() {
136 201
   load_config
137 202
 
138 203
   # Lockfile handling (prevents concurrent access)
139
-  LOCKDIR="$(dirname "${LOCKFILE}")"
140
-  [[ -w "${LOCKDIR}" ]] || _exiterr "Directory ${LOCKDIR} for LOCKFILE ${LOCKFILE} is not writable, aborting."
141
-  ( set -C; date > "${LOCKFILE}" ) 2>/dev/null || _exiterr "Lock file '${LOCKFILE}' present, aborting."
142
-  remove_lock() { rm -f "${LOCKFILE}"; }
143
-  trap 'remove_lock' EXIT
204
+  if [[ -n "${LOCKFILE}" ]]; then
205
+    LOCKDIR="$(dirname "${LOCKFILE}")"
206
+    [[ -w "${LOCKDIR}" ]] || _exiterr "Directory ${LOCKDIR} for LOCKFILE ${LOCKFILE} is not writable, aborting."
207
+    ( set -C; date > "${LOCKFILE}" ) 2>/dev/null || _exiterr "Lock file '${LOCKFILE}' present, aborting."
208
+    remove_lock() { rm -f "${LOCKFILE}"; }
209
+    trap 'remove_lock' EXIT
210
+  fi
144 211
 
145 212
   # Get CA URLs
146 213
   CA_DIRECTORY="$(http_request get "${CA}")"
@@ -152,7 +219,7 @@ init_system() {
152 219
   _exiterr "Problem retrieving ACME/CA-URLs, check if your configured CA points to the directory entrypoint."
153 220
 
154 221
   # Export some environment variables to be used in hook script
155
-  export WELLKNOWN BASEDIR CONFIG
222
+  export WELLKNOWN BASEDIR CERTDIR CONFIG
156 223
 
157 224
   # Checking for private key ...
158 225
   register_new_key="no"
@@ -182,16 +249,21 @@ init_system() {
182 249
     echo "+ Registering account key with letsencrypt..."
183 250
     [[ ! -z "${CA_NEW_REG}" ]] || _exiterr "Certificate authority doesn't allow registrations."
184 251
     # If an email for the contact has been provided then adding it to the registration request
252
+    FAILED=false
185 253
     if [[ -n "${CONTACT_EMAIL}" ]]; then
186
-      signed_request "${CA_NEW_REG}" '{"resource": "new-reg", "contact":["mailto:'"${CONTACT_EMAIL}"'"], "agreement": "'"$LICENSE"'"}' > "${ACCOUNT_KEY_JSON}"
254
+      (signed_request "${CA_NEW_REG}" '{"resource": "new-reg", "contact":["mailto:'"${CONTACT_EMAIL}"'"], "agreement": "'"$LICENSE"'"}' > "${ACCOUNT_KEY_JSON}") || FAILED=true
187 255
     else
188
-      signed_request "${CA_NEW_REG}" '{"resource": "new-reg", "agreement": "'"$LICENSE"'"}' > "${ACCOUNT_KEY_JSON}"
256
+      (signed_request "${CA_NEW_REG}" '{"resource": "new-reg", "agreement": "'"$LICENSE"'"}' > "${ACCOUNT_KEY_JSON}") || FAILED=true
257
+    fi
258
+    if [[ "${FAILED}" = "true" ]]; then
259
+      echo
260
+      echo
261
+      echo "Error registering account key. See message above for more information."
262
+      rm "${ACCOUNT_KEY}" "${ACCOUNT_KEY_JSON}"
263
+      exit 1
189 264
     fi
190 265
   fi
191 266
 
192
-  if [[ "${CHALLENGETYPE}" = "http-01" && ! -d "${WELLKNOWN}" ]]; then
193
-      _exiterr "WELLKNOWN directory doesn't exist, please create ${WELLKNOWN} and set appropriate permissions."
194
-  fi
195 267
 }
196 268
 
197 269
 # Different sed version for different os types...
@@ -254,15 +326,19 @@ _openssl() {
254 326
 http_request() {
255 327
   tempcont="$(_mktemp)"
256 328
 
329
+  if [[ -n "${IP_VERSION:-}" ]]; then
330
+      ip_version="-${IP_VERSION}"
331
+  fi
332
+
257 333
   set +e
258 334
   if [[ "${1}" = "head" ]]; then
259
-    statuscode="$(curl -s -w "%{http_code}" -o "${tempcont}" "${2}" -I)"
335
+    statuscode="$(curl ${ip_version:-} -s -w "%{http_code}" -o "${tempcont}" "${2}" -I)"
260 336
     curlret="${?}"
261 337
   elif [[ "${1}" = "get" ]]; then
262
-    statuscode="$(curl -s -w "%{http_code}" -o "${tempcont}" "${2}")"
338
+    statuscode="$(curl ${ip_version:-} -s -w "%{http_code}" -o "${tempcont}" "${2}")"
263 339
     curlret="${?}"
264 340
   elif [[ "${1}" = "post" ]]; then
265
-    statuscode="$(curl -s -w "%{http_code}" -o "${tempcont}" "${2}" -d "${3}")"
341
+    statuscode="$(curl ${ip_version:-} -s -w "%{http_code}" -o "${tempcont}" "${2}" -d "${3}")"
266 342
     curlret="${?}"
267 343
   else
268 344
     set -e
@@ -271,7 +347,7 @@ http_request() {
271 347
   set -e
272 348
 
273 349
   if [[ ! "${curlret}" = "0" ]]; then
274
-    _exiterr "Problem connecting to server (curl returned with ${curlret})"
350
+    _exiterr "Problem connecting to server (${1} for ${2}; curl returned with ${curlret})"
275 351
   fi
276 352
 
277 353
   if [[ ! "${statuscode:0:1}" = "2" ]]; then
@@ -279,6 +355,8 @@ http_request() {
279 355
     echo >&2
280 356
     echo "Details:" >&2
281 357
     cat "${tempcont}" >&2
358
+    echo >&2
359
+    echo >&2
282 360
     rm -f "${tempcont}"
283 361
 
284 362
     # Wait for hook script to clean the challenge if used
@@ -503,19 +581,19 @@ sign_domain() {
503 581
   fi
504 582
 
505 583
   # If there is no existing certificate directory => make it
506
-  if [[ ! -e "${BASEDIR}/certs/${domain}" ]]; then
507
-    echo " + Creating new directory ${BASEDIR}/certs/${domain} ..."
508
-    mkdir -p "${BASEDIR}/certs/${domain}"
584
+  if [[ ! -e "${CERTDIR}/${domain}" ]]; then
585
+    echo " + Creating new directory ${CERTDIR}/${domain} ..."
586
+    mkdir -p "${CERTDIR}/${domain}" || _exiterr "Unable to create directory ${CERTDIR}/${domain}"
509 587
   fi
510 588
 
511 589
   privkey="privkey.pem"
512 590
   # generate a new private key if we need or want one
513
-  if [[ ! -r "${BASEDIR}/certs/${domain}/privkey.pem" ]] || [[ "${PRIVATE_KEY_RENEW}" = "yes" ]]; then
591
+  if [[ ! -r "${CERTDIR}/${domain}/privkey.pem" ]] || [[ "${PRIVATE_KEY_RENEW}" = "yes" ]]; then
514 592
     echo " + Generating private key..."
515 593
     privkey="privkey-${timestamp}.pem"
516 594
     case "${KEY_ALGO}" in
517
-      rsa) _openssl genrsa -out "${BASEDIR}/certs/${domain}/privkey-${timestamp}.pem" "${KEYSIZE}";;
518
-      prime256v1|secp384r1) _openssl ecparam -genkey -name "${KEY_ALGO}" -out "${BASEDIR}/certs/${domain}/privkey-${timestamp}.pem";;
595
+      rsa) _openssl genrsa -out "${CERTDIR}/${domain}/privkey-${timestamp}.pem" "${KEYSIZE}";;
596
+      prime256v1|secp384r1) _openssl ecparam -genkey -name "${KEY_ALGO}" -out "${CERTDIR}/${domain}/privkey-${timestamp}.pem";;
519 597
     esac
520 598
   fi
521 599
 
@@ -530,33 +608,36 @@ sign_domain() {
530 608
   tmp_openssl_cnf="$(_mktemp)"
531 609
   cat "${OPENSSL_CNF}" > "${tmp_openssl_cnf}"
532 610
   printf "[SAN]\nsubjectAltName=%s" "${SAN}" >> "${tmp_openssl_cnf}"
533
-  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}"
611
+  if [ "${OCSP_MUST_STAPLE}" = "yes" ]; then
612
+    printf "\n1.3.6.1.5.5.7.1.24=DER:30:03:02:01:05" >> "${tmp_openssl_cnf}"
613
+  fi
614
+  openssl req -new -sha256 -key "${CERTDIR}/${domain}/${privkey}" -out "${CERTDIR}/${domain}/cert-${timestamp}.csr" -subj "/CN=${domain}/" -reqexts SAN -config "${tmp_openssl_cnf}"
534 615
   rm -f "${tmp_openssl_cnf}"
535 616
 
536
-  crt_path="${BASEDIR}/certs/${domain}/cert-${timestamp}.pem"
617
+  crt_path="${CERTDIR}/${domain}/cert-${timestamp}.pem"
537 618
   # shellcheck disable=SC2086
538
-  sign_csr "$(< "${BASEDIR}/certs/${domain}/cert-${timestamp}.csr" )" ${altnames} 3>"${crt_path}"
619
+  sign_csr "$(< "${CERTDIR}/${domain}/cert-${timestamp}.csr" )" ${altnames} 3>"${crt_path}"
539 620
 
540 621
   # Create fullchain.pem
541 622
   echo " + Creating fullchain.pem..."
542
-  cat "${crt_path}" > "${BASEDIR}/certs/${domain}/fullchain-${timestamp}.pem"
543
-  http_request get "$(openssl x509 -in "${BASEDIR}/certs/${domain}/cert-${timestamp}.pem" -noout -text | grep 'CA Issuers - URI:' | cut -d':' -f2-)" > "${BASEDIR}/certs/${domain}/chain-${timestamp}.pem"
544
-  if ! grep -q "BEGIN CERTIFICATE" "${BASEDIR}/certs/${domain}/chain-${timestamp}.pem"; then
545
-    openssl x509 -in "${BASEDIR}/certs/${domain}/chain-${timestamp}.pem" -inform DER -out "${BASEDIR}/certs/${domain}/chain-${timestamp}.pem" -outform PEM
623
+  cat "${crt_path}" > "${CERTDIR}/${domain}/fullchain-${timestamp}.pem"
624
+  http_request get "$(openssl x509 -in "${CERTDIR}/${domain}/cert-${timestamp}.pem" -noout -text | grep 'CA Issuers - URI:' | cut -d':' -f2-)" > "${CERTDIR}/${domain}/chain-${timestamp}.pem"
625
+  if ! grep -q "BEGIN CERTIFICATE" "${CERTDIR}/${domain}/chain-${timestamp}.pem"; then
626
+    openssl x509 -in "${CERTDIR}/${domain}/chain-${timestamp}.pem" -inform DER -out "${CERTDIR}/${domain}/chain-${timestamp}.pem" -outform PEM
546 627
   fi
547
-  cat "${BASEDIR}/certs/${domain}/chain-${timestamp}.pem" >> "${BASEDIR}/certs/${domain}/fullchain-${timestamp}.pem"
628
+  cat "${CERTDIR}/${domain}/chain-${timestamp}.pem" >> "${CERTDIR}/${domain}/fullchain-${timestamp}.pem"
548 629
 
549 630
   # Update symlinks
550
-  [[ "${privkey}" = "privkey.pem" ]] || ln -sf "privkey-${timestamp}.pem" "${BASEDIR}/certs/${domain}/privkey.pem"
631
+  [[ "${privkey}" = "privkey.pem" ]] || ln -sf "privkey-${timestamp}.pem" "${CERTDIR}/${domain}/privkey.pem"
551 632
 
552
-  ln -sf "chain-${timestamp}.pem" "${BASEDIR}/certs/${domain}/chain.pem"
553
-  ln -sf "fullchain-${timestamp}.pem" "${BASEDIR}/certs/${domain}/fullchain.pem"
554
-  ln -sf "cert-${timestamp}.csr" "${BASEDIR}/certs/${domain}/cert.csr"
555
-  ln -sf "cert-${timestamp}.pem" "${BASEDIR}/certs/${domain}/cert.pem"
633
+  ln -sf "chain-${timestamp}.pem" "${CERTDIR}/${domain}/chain.pem"
634
+  ln -sf "fullchain-${timestamp}.pem" "${CERTDIR}/${domain}/fullchain.pem"
635
+  ln -sf "cert-${timestamp}.csr" "${CERTDIR}/${domain}/cert.csr"
636
+  ln -sf "cert-${timestamp}.pem" "${CERTDIR}/${domain}/cert.pem"
556 637
 
557 638
   # Wait for hook script to clean the challenge and to deploy cert if used
558 639
   export KEY_ALGO
559
-  [[ -n "${HOOK}" ]] && "${HOOK}" "deploy_cert" "${domain}" "${BASEDIR}/certs/${domain}/privkey.pem" "${BASEDIR}/certs/${domain}/cert.pem" "${BASEDIR}/certs/${domain}/fullchain.pem" "${BASEDIR}/certs/${domain}/chain.pem" "${timestamp}"
640
+  [[ -n "${HOOK}" ]] && "${HOOK}" "deploy_cert" "${domain}" "${CERTDIR}/${domain}/privkey.pem" "${CERTDIR}/${domain}/cert.pem" "${CERTDIR}/${domain}/fullchain.pem" "${CERTDIR}/${domain}/chain.pem" "${timestamp}"
560 641
 
561 642
   unset challenge_token
562 643
   echo " + Done!"
@@ -570,8 +651,10 @@ command_sign_domains() {
570 651
   if [[ -n "${PARAM_DOMAIN:-}" ]]; then
571 652
     DOMAINS_TXT="$(_mktemp)"
572 653
     printf -- "${PARAM_DOMAIN}" > "${DOMAINS_TXT}"
573
-  elif [[ -e "${BASEDIR}/domains.txt" ]]; then
574
-    DOMAINS_TXT="${BASEDIR}/domains.txt"
654
+  elif [[ -e "${DOMAINS_TXT}" ]]; then
655
+    if [[ ! -r "${DOMAINS_TXT}" ]]; then
656
+      _exiterr "domains.txt found but not readable"
657
+    fi
575 658
   else
576 659
     _exiterr "domains.txt not found and --domain not given"
577 660
   fi
@@ -580,10 +663,11 @@ command_sign_domains() {
580 663
   ORIGIFS="${IFS}"
581 664
   IFS=$'\n'
582 665
   for line in $(<"${DOMAINS_TXT}" tr -d '\r' | tr '[:upper:]' '[:lower:]' | _sed -e 's/^[[:space:]]*//g' -e 's/[[:space:]]*$//g' -e 's/[[:space:]]+/ /g' | (grep -vE '^(#|$)' || true)); do
666
+    reset_configvars
583 667
     IFS="${ORIGIFS}"
584 668
     domain="$(printf '%s\n' "${line}" | cut -d' ' -f1)"
585 669
     morenames="$(printf '%s\n' "${line}" | cut -s -d' ' -f2-)"
586
-    cert="${BASEDIR}/certs/${domain}/cert.pem"
670
+    cert="${CERTDIR}/${domain}/cert.pem"
587 671
 
588 672
     force_renew="${PARAM_FORCE:-no}"
589 673
 
@@ -593,6 +677,46 @@ command_sign_domains() {
593 677
       echo "Processing ${domain} with alternative names: ${morenames}"
594 678
     fi
595 679
 
680
+    # read cert config
681
+    # for now this loads the certificate specific config in a subshell and parses a diff of set variables.
682
+    # we could just source the config file but i decided to go this way to protect people from accidentally overriding
683
+    # variables used internally by this script itself.
684
+    if [[ -n "${DOMAINS_D}" ]]; then
685
+      certconfig="${DOMAINS_D}/${domain}"
686
+    else
687
+      certconfig="${CERTDIR}/${domain}/config"
688
+    fi
689
+
690
+    if [ -f "${certconfig}" ]; then
691
+      echo " + Using certificate specific config file!"
692
+      ORIGIFS="${IFS}"
693
+      IFS=$'\n'
694
+      for cfgline in $(
695
+        beforevars="$(_mktemp)"
696
+        aftervars="$(_mktemp)"
697
+        set > "${beforevars}"
698
+        # shellcheck disable=SC1090
699
+        . "${certconfig}"
700
+        set > "${aftervars}"
701
+        diff -u "${beforevars}" "${aftervars}" | grep -E '^\+[^+]'
702
+        rm "${beforevars}"
703
+        rm "${aftervars}"
704
+      ); do
705
+        config_var="$(echo "${cfgline:1}" | cut -d'=' -f1)"
706
+        config_value="$(echo "${cfgline:1}" | cut -d'=' -f2-)"
707
+        case "${config_var}" in
708
+          KEY_ALGO|OCSP_MUST_STAPLE|PRIVATE_KEY_RENEW|KEYSIZE|CHALLENGETYPE|HOOK|WELLKNOWN|HOOK_CHAIN|OPENSSL_CNF|RENEW_DAYS)
709
+            echo "   + ${config_var} = ${config_value}"
710
+            declare -- "${config_var}=${config_value}"
711
+            ;;
712
+          _) ;;
713
+          *) echo "   ! Setting ${config_var} on a per-certificate base is not (yet) supported"
714
+        esac
715
+      done
716
+      IFS="${ORIGIFS}"
717
+    fi
718
+    verify_config
719
+
596 720
     if [[ -e "${cert}" ]]; then
597 721
       printf " + Checking domain name(s) of existing cert..."
598 722
 
@@ -623,7 +747,7 @@ command_sign_domains() {
623 747
         else
624 748
           # Certificate-Names unchanged and cert is still valid
625 749
           echo "Skipping renew!"
626
-          [[ -n "${HOOK}" ]] && "${HOOK}" "unchanged_cert" "${domain}" "${BASEDIR}/certs/${domain}/privkey.pem" "${BASEDIR}/certs/${domain}/cert.pem" "${BASEDIR}/certs/${domain}/fullchain.pem" "${BASEDIR}/certs/${domain}/chain.pem"
750
+          [[ -n "${HOOK}" ]] && "${HOOK}" "unchanged_cert" "${domain}" "${CERTDIR}/${domain}/privkey.pem" "${CERTDIR}/${domain}/cert.pem" "${CERTDIR}/${domain}/fullchain.pem" "${CERTDIR}/${domain}/chain.pem"
627 751
           continue
628 752
         fi
629 753
       else
@@ -632,7 +756,12 @@ command_sign_domains() {
632 756
     fi
633 757
 
634 758
     # shellcheck disable=SC2086
635
-    sign_domain ${line}
759
+    if [[ "${PARAM_KEEP_GOING:-}" = "yes" ]]; then
760
+      sign_domain ${line} &
761
+      wait $! || true
762
+    else
763
+      sign_domain ${line}
764
+    fi
636 765
   done
637 766
 
638 767
   # remove temporary domains.txt file if used
@@ -655,7 +784,33 @@ command_sign_csr() {
655 784
     _exiterr "Could not read certificate signing request ${csrfile}"
656 785
   fi
657 786
 
658
-  sign_csr "$(< "${csrfile}" )"
787
+  # gen cert
788
+  certfile="$(_mktemp)"
789
+  sign_csr "$(< "${csrfile}" )" 3> "${certfile}"
790
+
791
+  # print cert
792
+  echo "# CERT #" >&3
793
+  cat "${certfile}" >&3
794
+  echo >&3
795
+
796
+  # print chain
797
+  if [ -n "${PARAM_FULL_CHAIN:-}" ]; then
798
+    # get and convert ca cert
799
+    chainfile="$(_mktemp)"
800
+    http_request get "$(openssl x509 -in "${certfile}" -noout -text | grep 'CA Issuers - URI:' | cut -d':' -f2-)" > "${chainfile}"
801
+
802
+    if ! grep -q "BEGIN CERTIFICATE" "${chainfile}"; then
803
+      openssl x509 -inform DER -in "${chainfile}" -outform PEM -out "${chainfile}"
804
+    fi
805
+
806
+    echo "# CHAIN #" >&3
807
+    cat "${chainfile}" >&3
808
+
809
+    rm "${chainfile}"
810
+  fi
811
+
812
+  # cleanup
813
+  rm "${certfile}"
659 814
 
660 815
   exit 0
661 816
 }
@@ -702,7 +857,7 @@ command_cleanup() {
702 857
   fi
703 858
 
704 859
   # Loop over all certificate directories
705
-  for certdir in "${BASEDIR}/certs/"*; do
860
+  for certdir in "${CERTDIR}/"*; do
706 861
     # Skip if entry is not a folder
707 862
     [[ -d "${certdir}" ]] || continue
708 863
 
@@ -771,7 +926,7 @@ command_help() {
771 926
 command_env() {
772 927
   echo "# letsencrypt.sh configuration"
773 928
   load_config
774
-  typeset -p CA LICENSE CHALLENGETYPE HOOK HOOK_CHAIN RENEW_DAYS ACCOUNT_KEY ACCOUNT_KEY_JSON KEYSIZE WELLKNOWN PRIVATE_KEY_RENEW OPENSSL_CNF CONTACT_EMAIL LOCKFILE
929
+  typeset -p CA LICENSE CERTDIR CHALLENGETYPE DOMAINS_D DOMAINS_TXT HOOK HOOK_CHAIN RENEW_DAYS ACCOUNT_KEY ACCOUNT_KEY_JSON KEYSIZE WELLKNOWN PRIVATE_KEY_RENEW OPENSSL_CNF CONTACT_EMAIL LOCKFILE
775 930
 }
776 931
 
777 932
 # Main method (parses script arguments and calls command_* methods)
@@ -828,6 +983,24 @@ main() {
828 983
         set_command cleanup
829 984
         ;;
830 985
 
986
+      # PARAM_Usage: --full-chain (-fc)
987
+      # PARAM_Description: Print full chain when using --signcsr
988
+      --full-chain|-fc)
989
+        PARAM_FULL_CHAIN="1"
990
+        ;;
991
+
992
+      # PARAM_Usage: --ipv4 (-4)
993
+      # PARAM_Description: Resolve names to IPv4 addresses only
994
+      --ipv4|-4)
995
+        PARAM_IP_VERSION="4"
996
+        ;;
997
+
998
+      # PARAM_Usage: --ipv6 (-6)
999
+      # PARAM_Description: Resolve names to IPv6 addresses only
1000
+      --ipv6|-6)
1001
+        PARAM_IP_VERSION="6"
1002
+        ;;
1003
+
831 1004
       # PARAM_Usage: --domain (-d) domain.tld
832 1005
       # PARAM_Description: Use specified domain name(s) instead of domains.txt entry (one certificate!)
833 1006
       --domain|-d)
@@ -840,6 +1013,11 @@ main() {
840 1013
          fi
841 1014
         ;;
842 1015
 
1016
+      # PARAM_Usage: --keep-going (-g)
1017
+      # PARAM_Description: Keep going after encountering an error while creating/renewing multiple certificates in cron mode
1018
+      --keep-going|-g)
1019
+        PARAM_KEEP_GOING="yes"
1020
+        ;;
843 1021
 
844 1022
       # PARAM_Usage: --force (-x)
845 1023
       # PARAM_Description: Force renew of certificate even if it is longer valid than value in RENEW_DAYS
@@ -847,6 +1025,18 @@ main() {
847 1025
         PARAM_FORCE="yes"
848 1026
         ;;
849 1027
 
1028
+      # PARAM_Usage: --no-lock (-n)
1029
+      # PARAM_Description: Don't use lockfile (potentially dangerous!)
1030
+      --no-lock|-n)
1031
+        PARAM_NO_LOCK="yes"
1032
+        ;;
1033
+
1034
+      # PARAM_Usage: --ocsp
1035
+      # PARAM_Description: Sets option in CSR indicating OCSP stapling to be mandatory
1036
+      --ocsp)
1037
+        PARAM_OCSP_MUST_STAPLE="yes"
1038
+        ;;
1039
+
850 1040
       # PARAM_Usage: --privkey (-p) path/to/key.pem
851 1041
       # PARAM_Description: Use specified private key instead of account key (useful for revocation)
852 1042
       --privkey|-p)
@@ -855,7 +1045,7 @@ main() {
855 1045
         PARAM_ACCOUNT_KEY="${1}"
856 1046
         ;;
857 1047
 
858
-      # PARAM_Usage: --config (-f) path/to/config.sh
1048
+      # PARAM_Usage: --config (-f) path/to/config
859 1049
       # PARAM_Description: Use specified config file
860 1050
       --config|-f)
861 1051
         shift 1
@@ -871,6 +1061,14 @@ main() {
871 1061
         PARAM_HOOK="${1}"
872 1062
         ;;
873 1063
 
1064
+      # PARAM_Usage: --out (-o) certs/directory
1065
+      # PARAM_Description: Output certificates into the specified directory
1066
+      --out|-o)
1067
+        shift 1
1068
+        check_parameters "${1:-}"
1069
+        PARAM_CERTDIR="${1}"
1070
+        ;;
1071
+
874 1072
       # PARAM_Usage: --challenge (-t) http-01|dns-01
875 1073
       # PARAM_Description: Which challenge should be used? Currently http-01 and dns-01 are supported
876 1074
       --challenge|-t)

Loading…
Cancel
Save