|
@@ -1,7 +1,7 @@
|
1
|
1
|
#!/usr/bin/env bash
|
2
|
2
|
|
3
|
|
-# letsencrypt.sh by lukas2511
|
4
|
|
-# Source: https://github.com/lukas2511/letsencrypt.sh
|
|
3
|
+# dehydrated by lukas2511
|
|
4
|
+# Source: https://github.com/lukas2511/dehydrated
|
5
|
5
|
#
|
6
|
6
|
# This script is licensed under The MIT License (see LICENSE for more information).
|
7
|
7
|
|
|
@@ -25,7 +25,7 @@ BASEDIR="${SCRIPTDIR}"
|
25
|
25
|
# Create (identifiable) temporary files
|
26
|
26
|
_mktemp() {
|
27
|
27
|
# shellcheck disable=SC2068
|
28
|
|
- mktemp ${@:-} "${TMPDIR:-/tmp}/letsencrypt.sh-XXXXXX"
|
|
28
|
+ mktemp ${@:-} "${TMPDIR:-/tmp}/dehydrated-XXXXXX"
|
29
|
29
|
}
|
30
|
30
|
|
31
|
31
|
# Check for script dependencies
|
|
@@ -34,8 +34,8 @@ check_dependencies() {
|
34
|
34
|
openssl version > /dev/null 2>&1 || _exiterr "This script requires an openssl binary."
|
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
|
|
- _mktemp -u > /dev/null 2>&1 || _exiterr "This script requires mktemp."
|
38
|
|
- diff -u /dev/null /dev/null || _exiterr "This script requires diff."
|
|
37
|
+ command -v mktemp > /dev/null 2>&1 || _exiterr "This script requires mktemp."
|
|
38
|
+ command -v diff > /dev/null 2>&1 || _exiterr "This script requires diff."
|
39
|
39
|
|
40
|
40
|
# curl returns with an error code in some ancient versions so we have to catch that
|
41
|
41
|
set +e
|
|
@@ -81,7 +81,7 @@ verify_config() {
|
81
|
81
|
if [[ "${CHALLENGETYPE}" = "dns-01" ]] && [[ -z "${HOOK}" ]]; then
|
82
|
82
|
_exiterr "Challenge type dns-01 needs a hook script for deployment... can not continue."
|
83
|
83
|
fi
|
84
|
|
- if [[ "${CHALLENGETYPE}" = "http-01" && ! -d "${WELLKNOWN}" ]]; then
|
|
84
|
+ if [[ "${CHALLENGETYPE}" = "http-01" && ! -d "${WELLKNOWN}" && ! "${COMMAND:-}" = "register" ]]; then
|
85
|
85
|
_exiterr "WELLKNOWN directory doesn't exist, please create ${WELLKNOWN} and set appropriate permissions."
|
86
|
86
|
fi
|
87
|
87
|
[[ "${KEY_ALGO}" =~ ^(rsa|prime256v1|secp384r1)$ ]] || _exiterr "Unknown public key algorithm ${KEY_ALGO}... can not continue."
|
|
@@ -94,7 +94,7 @@ verify_config() {
|
94
|
94
|
load_config() {
|
95
|
95
|
# Check for config in various locations
|
96
|
96
|
if [[ -z "${CONFIG:-}" ]]; then
|
97
|
|
- for check_config in "/etc/letsencrypt.sh" "/usr/local/etc/letsencrypt.sh" "${PWD}" "${SCRIPTDIR}"; do
|
|
97
|
+ for check_config in "/etc/dehydrated" "/usr/local/etc/dehydrated" "${PWD}" "${SCRIPTDIR}"; do
|
98
|
98
|
if [[ -f "${check_config}/config" ]]; then
|
99
|
99
|
BASEDIR="${check_config}"
|
100
|
100
|
CONFIG="${check_config}/config"
|
|
@@ -105,7 +105,8 @@ load_config() {
|
105
|
105
|
|
106
|
106
|
# Default values
|
107
|
107
|
CA="https://acme-v01.api.letsencrypt.org/directory"
|
108
|
|
- LICENSE="https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf"
|
|
108
|
+ CA_TERMS="https://acme-v01.api.letsencrypt.org/terms"
|
|
109
|
+ LICENSE=
|
109
|
110
|
CERTDIR=
|
110
|
111
|
ACCOUNTDIR=
|
111
|
112
|
CHALLENGETYPE="http-01"
|
|
@@ -118,6 +119,7 @@ load_config() {
|
118
|
119
|
KEYSIZE="4096"
|
119
|
120
|
WELLKNOWN=
|
120
|
121
|
PRIVATE_KEY_RENEW="yes"
|
|
122
|
+ PRIVATE_KEY_ROLLOVER="no"
|
121
|
123
|
KEY_ALGO=rsa
|
122
|
124
|
OPENSSL_CNF="$(openssl version -d | cut -d\" -f2)/openssl.cnf"
|
123
|
125
|
CONTACT_EMAIL=
|
|
@@ -181,8 +183,9 @@ load_config() {
|
181
|
183
|
|
182
|
184
|
[[ -z "${CERTDIR}" ]] && CERTDIR="${BASEDIR}/certs"
|
183
|
185
|
[[ -z "${DOMAINS_TXT}" ]] && DOMAINS_TXT="${BASEDIR}/domains.txt"
|
184
|
|
- [[ -z "${WELLKNOWN}" ]] && WELLKNOWN="/var/www/letsencrypt"
|
|
186
|
+ [[ -z "${WELLKNOWN}" ]] && WELLKNOWN="/var/www/dehydrated"
|
185
|
187
|
[[ -z "${LOCKFILE}" ]] && LOCKFILE="${BASEDIR}/lock"
|
|
188
|
+ [[ -n "${PARAM_LOCKFILE_SUFFIX:-}" ]] && LOCKFILE="${LOCKFILE}-${PARAM_LOCKFILE_SUFFIX}"
|
186
|
189
|
[[ -n "${PARAM_NO_LOCK:-}" ]] && LOCKFILE=""
|
187
|
190
|
|
188
|
191
|
[[ -n "${PARAM_HOOK:-}" ]] && HOOK="${PARAM_HOOK}"
|
|
@@ -219,7 +222,7 @@ init_system() {
|
219
|
222
|
_exiterr "Problem retrieving ACME/CA-URLs, check if your configured CA points to the directory entrypoint."
|
220
|
223
|
|
221
|
224
|
# Export some environment variables to be used in hook script
|
222
|
|
- export WELLKNOWN BASEDIR CERTDIR CONFIG
|
|
225
|
+ export WELLKNOWN BASEDIR CERTDIR CONFIG COMMAND
|
223
|
226
|
|
224
|
227
|
# Checking for private key ...
|
225
|
228
|
register_new_key="no"
|
|
@@ -231,6 +234,24 @@ init_system() {
|
231
|
234
|
else
|
232
|
235
|
# Check if private account key exists, if it doesn't exist yet generate a new one (rsa key)
|
233
|
236
|
if [[ ! -e "${ACCOUNT_KEY}" ]]; then
|
|
237
|
+ REAL_LICENSE="$(http_request head "${CA_TERMS}" | (grep Location: || true) | awk -F ': ' '{print $2}' | tr -d '\n\r')"
|
|
238
|
+ if [[ -z "${REAL_LICENSE}" ]]; then
|
|
239
|
+ printf '\n'
|
|
240
|
+ printf 'Error retrieving terms of service from certificate authority.\n'
|
|
241
|
+ printf 'Please set LICENSE in config manually.\n'
|
|
242
|
+ exit 1
|
|
243
|
+ fi
|
|
244
|
+ if [[ ! "${LICENSE}" = "${REAL_LICENSE}" ]]; then
|
|
245
|
+ if [[ "${PARAM_ACCEPT_TERMS:-}" = "yes" ]]; then
|
|
246
|
+ LICENSE="${REAL_LICENSE}"
|
|
247
|
+ else
|
|
248
|
+ printf '\n'
|
|
249
|
+ printf 'To use dehydrated with this certificate authority you have to agree to their terms of service which you can find here: %s\n\n' "${REAL_LICENSE}"
|
|
250
|
+ printf 'To accept these terms of service run `%s --register --accept-terms`.\n' "${0}"
|
|
251
|
+ exit 1
|
|
252
|
+ fi
|
|
253
|
+ fi
|
|
254
|
+
|
234
|
255
|
echo "+ Generating account key..."
|
235
|
256
|
_openssl genrsa -out "${ACCOUNT_KEY}" "${KEYSIZE}"
|
236
|
257
|
register_new_key="yes"
|
|
@@ -246,15 +267,23 @@ init_system() {
|
246
|
267
|
|
247
|
268
|
# If we generated a new private key in the step above we have to register it with the acme-server
|
248
|
269
|
if [[ "${register_new_key}" = "yes" ]]; then
|
249
|
|
- echo "+ Registering account key with letsencrypt..."
|
250
|
|
- [[ ! -z "${CA_NEW_REG}" ]] || _exiterr "Certificate authority doesn't allow registrations."
|
251
|
|
- # If an email for the contact has been provided then adding it to the registration request
|
|
270
|
+ echo "+ Registering account key with ACME server..."
|
252
|
271
|
FAILED=false
|
253
|
|
- if [[ -n "${CONTACT_EMAIL}" ]]; then
|
254
|
|
- (signed_request "${CA_NEW_REG}" '{"resource": "new-reg", "contact":["mailto:'"${CONTACT_EMAIL}"'"], "agreement": "'"$LICENSE"'"}' > "${ACCOUNT_KEY_JSON}") || FAILED=true
|
255
|
|
- else
|
256
|
|
- (signed_request "${CA_NEW_REG}" '{"resource": "new-reg", "agreement": "'"$LICENSE"'"}' > "${ACCOUNT_KEY_JSON}") || FAILED=true
|
|
272
|
+
|
|
273
|
+ if [[ -z "${CA_NEW_REG}" ]]; then
|
|
274
|
+ echo "Certificate authority doesn't allow registrations."
|
|
275
|
+ FAILED=true
|
257
|
276
|
fi
|
|
277
|
+
|
|
278
|
+ # If an email for the contact has been provided then adding it to the registration request
|
|
279
|
+ if [[ "${FAILED}" = "false" ]]; then
|
|
280
|
+ if [[ -n "${CONTACT_EMAIL}" ]]; then
|
|
281
|
+ (signed_request "${CA_NEW_REG}" '{"resource": "new-reg", "contact":["mailto:'"${CONTACT_EMAIL}"'"], "agreement": "'"$LICENSE"'"}' > "${ACCOUNT_KEY_JSON}") || FAILED=true
|
|
282
|
+ else
|
|
283
|
+ (signed_request "${CA_NEW_REG}" '{"resource": "new-reg", "agreement": "'"$LICENSE"'"}' > "${ACCOUNT_KEY_JSON}") || FAILED=true
|
|
284
|
+ fi
|
|
285
|
+ fi
|
|
286
|
+
|
258
|
287
|
if [[ "${FAILED}" = "true" ]]; then
|
259
|
288
|
echo
|
260
|
289
|
echo
|
|
@@ -262,8 +291,10 @@ init_system() {
|
262
|
291
|
rm "${ACCOUNT_KEY}" "${ACCOUNT_KEY_JSON}"
|
263
|
292
|
exit 1
|
264
|
293
|
fi
|
|
294
|
+ elif [[ "${COMMAND:-}" = "register" ]]; then
|
|
295
|
+ echo "+ Account already registered!"
|
|
296
|
+ exit 0
|
265
|
297
|
fi
|
266
|
|
-
|
267
|
298
|
}
|
268
|
299
|
|
269
|
300
|
# Different sed version for different os types...
|
|
@@ -305,6 +336,13 @@ get_json_string_value() {
|
305
|
336
|
sed -n "${filter}"
|
306
|
337
|
}
|
307
|
338
|
|
|
339
|
+rm_json_arrays() {
|
|
340
|
+ local filter
|
|
341
|
+ filter='s/\[[^][]*\]/null/g'
|
|
342
|
+ # remove three levels of nested arrays
|
|
343
|
+ sed -e "${filter}" -e "${filter}" -e "${filter}"
|
|
344
|
+}
|
|
345
|
+
|
308
|
346
|
# OpenSSL writes to stderr/stdout even when there are no errors. So just
|
309
|
347
|
# display the output if the exit code was != 0 to simplify debugging.
|
310
|
348
|
_openssl() {
|
|
@@ -351,22 +389,31 @@ http_request() {
|
351
|
389
|
fi
|
352
|
390
|
|
353
|
391
|
if [[ ! "${statuscode:0:1}" = "2" ]]; then
|
354
|
|
- echo " + ERROR: An error occurred while sending ${1}-request to ${2} (Status ${statuscode})" >&2
|
355
|
|
- echo >&2
|
356
|
|
- echo "Details:" >&2
|
357
|
|
- cat "${tempcont}" >&2
|
358
|
|
- echo >&2
|
359
|
|
- echo >&2
|
360
|
|
- rm -f "${tempcont}"
|
|
392
|
+ if [[ ! "${2}" = "${CA_TERMS}" ]] || [[ ! "${statuscode:0:1}" = "3" ]]; then
|
|
393
|
+ echo " + ERROR: An error occurred while sending ${1}-request to ${2} (Status ${statuscode})" >&2
|
|
394
|
+ echo >&2
|
|
395
|
+ echo "Details:" >&2
|
|
396
|
+ cat "${tempcont}" >&2
|
|
397
|
+ echo >&2
|
|
398
|
+ echo >&2
|
361
|
399
|
|
362
|
|
- # Wait for hook script to clean the challenge if used
|
363
|
|
- if [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" != "yes" ]] && [[ -n "${challenge_token:+set}" ]]; then
|
364
|
|
- "${HOOK}" "clean_challenge" '' "${challenge_token}" "${keyauth}"
|
365
|
|
- fi
|
|
400
|
+ # An exclusive hook for the {1}-request error might be useful (e.g., for sending an e-mail to admins)
|
|
401
|
+ if [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" != "yes" ]]; then
|
|
402
|
+ errtxt=`cat ${tempcont}`
|
|
403
|
+ "${HOOK}" "request_failure" "${statuscode}" "${errtxt}" "${1}"
|
|
404
|
+ fi
|
|
405
|
+
|
|
406
|
+ rm -f "${tempcont}"
|
366
|
407
|
|
367
|
|
- # remove temporary domains.txt file if used
|
368
|
|
- [[ -n "${PARAM_DOMAIN:-}" && -n "${DOMAINS_TXT:-}" ]] && rm "${DOMAINS_TXT}"
|
369
|
|
- exit 1
|
|
408
|
+ # Wait for hook script to clean the challenge if used
|
|
409
|
+ if [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" != "yes" ]] && [[ -n "${challenge_token:+set}" ]]; then
|
|
410
|
+ "${HOOK}" "clean_challenge" '' "${challenge_token}" "${keyauth}"
|
|
411
|
+ fi
|
|
412
|
+
|
|
413
|
+ # remove temporary domains.txt file if used
|
|
414
|
+ [[ -n "${PARAM_DOMAIN:-}" && -n "${DOMAINS_TXT:-}" ]] && rm "${DOMAINS_TXT}"
|
|
415
|
+ exit 1
|
|
416
|
+ fi
|
370
|
417
|
fi
|
371
|
418
|
|
372
|
419
|
cat "${tempcont}"
|
|
@@ -409,7 +456,7 @@ extract_altnames() {
|
409
|
456
|
reqtext="$( <<<"${csr}" openssl req -noout -text )"
|
410
|
457
|
if <<<"${reqtext}" grep -q '^[[:space:]]*X509v3 Subject Alternative Name:[[:space:]]*$'; then
|
411
|
458
|
# SANs used, extract these
|
412
|
|
- altnames="$( <<<"${reqtext}" grep -A1 '^[[:space:]]*X509v3 Subject Alternative Name:[[:space:]]*$' | tail -n1 )"
|
|
459
|
+ altnames="$( <<<"${reqtext}" awk '/X509v3 Subject Alternative Name:/{print;getline;print;}' | tail -n1 )"
|
413
|
460
|
# split to one per line:
|
414
|
461
|
# shellcheck disable=SC1003
|
415
|
462
|
altnames="$( <<<"${altnames}" _sed -e 's/^[[:space:]]*//; s/, /\'$'\n''/g' )"
|
|
@@ -450,9 +497,9 @@ sign_csr() {
|
450
|
497
|
|
451
|
498
|
local idx=0
|
452
|
499
|
if [[ -n "${ZSH_VERSION:-}" ]]; then
|
453
|
|
- local -A challenge_uris challenge_tokens keyauths deploy_args
|
|
500
|
+ local -A challenge_altnames challenge_uris challenge_tokens keyauths deploy_args
|
454
|
501
|
else
|
455
|
|
- local -a challenge_uris challenge_tokens keyauths deploy_args
|
|
502
|
+ local -a challenge_altnames challenge_uris challenge_tokens keyauths deploy_args
|
456
|
503
|
fi
|
457
|
504
|
|
458
|
505
|
# Request challenges
|
|
@@ -461,6 +508,12 @@ sign_csr() {
|
461
|
508
|
echo " + Requesting challenge for ${altname}..."
|
462
|
509
|
response="$(signed_request "${CA_NEW_AUTHZ}" '{"resource": "new-authz", "identifier": {"type": "dns", "value": "'"${altname}"'"}}' | clean_json)"
|
463
|
510
|
|
|
511
|
+ challenge_status="$(printf '%s' "${response}" | rm_json_arrays | get_json_string_value status)"
|
|
512
|
+ if [ "${challenge_status}" = "valid" ]; then
|
|
513
|
+ echo " + Already validated!"
|
|
514
|
+ continue
|
|
515
|
+ fi
|
|
516
|
+
|
464
|
517
|
challenges="$(printf '%s\n' "${response}" | sed -n 's/.*\("challenges":[^\[]*\[[^]]*]\).*/\1/p')"
|
465
|
518
|
repl=$'\n''{' # fix syntax highlighting in Vim
|
466
|
519
|
challenge="$(printf "%s" "${challenges//\{/${repl}}" | grep \""${CHALLENGETYPE}"\")"
|
|
@@ -487,6 +540,7 @@ sign_csr() {
|
487
|
540
|
;;
|
488
|
541
|
esac
|
489
|
542
|
|
|
543
|
+ challenge_altnames[${idx}]="${altname}"
|
490
|
544
|
challenge_uris[${idx}]="${challenge_uri}"
|
491
|
545
|
keyauths[${idx}]="${keyauth}"
|
492
|
546
|
challenge_tokens[${idx}]="${challenge_token}"
|
|
@@ -494,56 +548,64 @@ sign_csr() {
|
494
|
548
|
deploy_args[${idx}]="${altname} ${challenge_token} ${keyauth_hook}"
|
495
|
549
|
idx=$((idx+1))
|
496
|
550
|
done
|
|
551
|
+ challenge_count="${idx}"
|
497
|
552
|
|
498
|
553
|
# Wait for hook script to deploy the challenges if used
|
499
|
|
- # shellcheck disable=SC2068
|
500
|
|
- [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" = "yes" ]] && "${HOOK}" "deploy_challenge" ${deploy_args[@]}
|
|
554
|
+ if [[ ${challenge_count} -ne 0 ]]; then
|
|
555
|
+ # shellcheck disable=SC2068
|
|
556
|
+ [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" = "yes" ]] && "${HOOK}" "deploy_challenge" ${deploy_args[@]}
|
|
557
|
+ fi
|
501
|
558
|
|
502
|
559
|
# Respond to challenges
|
|
560
|
+ reqstatus="valid"
|
503
|
561
|
idx=0
|
504
|
|
- for altname in ${altnames}; do
|
505
|
|
- challenge_token="${challenge_tokens[${idx}]}"
|
506
|
|
- keyauth="${keyauths[${idx}]}"
|
507
|
|
-
|
508
|
|
- # Wait for hook script to deploy the challenge if used
|
509
|
|
- # shellcheck disable=SC2086
|
510
|
|
- [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" != "yes" ]] && "${HOOK}" "deploy_challenge" ${deploy_args[${idx}]}
|
|
562
|
+ if [ ${challenge_count} -ne 0 ]; then
|
|
563
|
+ for altname in "${challenge_altnames[@]:0}"; do
|
|
564
|
+ challenge_token="${challenge_tokens[${idx}]}"
|
|
565
|
+ keyauth="${keyauths[${idx}]}"
|
511
|
566
|
|
512
|
|
- # Ask the acme-server to verify our challenge and wait until it is no longer pending
|
513
|
|
- echo " + Responding to challenge for ${altname}..."
|
514
|
|
- result="$(signed_request "${challenge_uris[${idx}]}" '{"resource": "challenge", "keyAuthorization": "'"${keyauth}"'"}' | clean_json)"
|
|
567
|
+ # Wait for hook script to deploy the challenge if used
|
|
568
|
+ # shellcheck disable=SC2086
|
|
569
|
+ [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" != "yes" ]] && "${HOOK}" "deploy_challenge" ${deploy_args[${idx}]}
|
515
|
570
|
|
516
|
|
- reqstatus="$(printf '%s\n' "${result}" | get_json_string_value status)"
|
|
571
|
+ # Ask the acme-server to verify our challenge and wait until it is no longer pending
|
|
572
|
+ echo " + Responding to challenge for ${altname}..."
|
|
573
|
+ result="$(signed_request "${challenge_uris[${idx}]}" '{"resource": "challenge", "keyAuthorization": "'"${keyauth}"'"}' | clean_json)"
|
517
|
574
|
|
518
|
|
- while [[ "${reqstatus}" = "pending" ]]; do
|
519
|
|
- sleep 1
|
520
|
|
- result="$(http_request get "${challenge_uris[${idx}]}")"
|
521
|
575
|
reqstatus="$(printf '%s\n' "${result}" | get_json_string_value status)"
|
522
|
|
- done
|
523
|
576
|
|
524
|
|
- [[ "${CHALLENGETYPE}" = "http-01" ]] && rm -f "${WELLKNOWN}/${challenge_token}"
|
|
577
|
+ while [[ "${reqstatus}" = "pending" ]]; do
|
|
578
|
+ sleep 1
|
|
579
|
+ result="$(http_request get "${challenge_uris[${idx}]}")"
|
|
580
|
+ reqstatus="$(printf '%s\n' "${result}" | get_json_string_value status)"
|
|
581
|
+ done
|
525
|
582
|
|
526
|
|
- # Wait for hook script to clean the challenge if used
|
527
|
|
- if [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" != "yes" ]] && [[ -n "${challenge_token}" ]]; then
|
528
|
|
- # shellcheck disable=SC2086
|
529
|
|
- "${HOOK}" "clean_challenge" ${deploy_args[${idx}]}
|
530
|
|
- fi
|
531
|
|
- idx=$((idx+1))
|
|
583
|
+ [[ "${CHALLENGETYPE}" = "http-01" ]] && rm -f "${WELLKNOWN}/${challenge_token}"
|
532
|
584
|
|
533
|
|
- if [[ "${reqstatus}" = "valid" ]]; then
|
534
|
|
- echo " + Challenge is valid!"
|
535
|
|
- else
|
536
|
|
- break
|
537
|
|
- fi
|
538
|
|
- done
|
|
585
|
+ # Wait for hook script to clean the challenge if used
|
|
586
|
+ if [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" != "yes" ]] && [[ -n "${challenge_token}" ]]; then
|
|
587
|
+ # shellcheck disable=SC2086
|
|
588
|
+ "${HOOK}" "clean_challenge" ${deploy_args[${idx}]}
|
|
589
|
+ fi
|
|
590
|
+ idx=$((idx+1))
|
|
591
|
+
|
|
592
|
+ if [[ "${reqstatus}" = "valid" ]]; then
|
|
593
|
+ echo " + Challenge is valid!"
|
|
594
|
+ else
|
|
595
|
+ [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" != "yes" ]] && "${HOOK}" "invalid_challenge" "${altname}" "${result}"
|
|
596
|
+ fi
|
|
597
|
+ done
|
|
598
|
+ fi
|
539
|
599
|
|
540
|
600
|
# Wait for hook script to clean the challenges if used
|
541
|
601
|
# shellcheck disable=SC2068
|
542
|
|
- [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" = "yes" ]] && "${HOOK}" "clean_challenge" ${deploy_args[@]}
|
|
602
|
+ if [[ ${challenge_count} -ne 0 ]]; then
|
|
603
|
+ [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" = "yes" ]] && "${HOOK}" "clean_challenge" ${deploy_args[@]}
|
|
604
|
+ fi
|
543
|
605
|
|
544
|
606
|
if [[ "${reqstatus}" != "valid" ]]; then
|
545
|
607
|
# Clean up any remaining challenge_tokens if we stopped early
|
546
|
|
- if [[ "${CHALLENGETYPE}" = "http-01" ]]; then
|
|
608
|
+ if [[ "${CHALLENGETYPE}" = "http-01" ]] && [[ ${challenge_count} -ne 0 ]]; then
|
547
|
609
|
while [ ${idx} -lt ${#challenge_tokens[@]} ]; do
|
548
|
610
|
rm -f "${WELLKNOWN}/${challenge_tokens[${idx}]}"
|
549
|
611
|
idx=$((idx+1))
|
|
@@ -569,6 +631,51 @@ sign_csr() {
|
569
|
631
|
echo " + Done!"
|
570
|
632
|
}
|
571
|
633
|
|
|
634
|
+# grep issuer cert uri from certificate
|
|
635
|
+get_issuer_cert_uri() {
|
|
636
|
+ certificate="${1}"
|
|
637
|
+ openssl x509 -in "${certificate}" -noout -text | (grep 'CA Issuers - URI:' | cut -d':' -f2-) || true
|
|
638
|
+}
|
|
639
|
+
|
|
640
|
+# walk certificate chain, retrieving all intermediate certificates
|
|
641
|
+walk_chain() {
|
|
642
|
+ local certificate
|
|
643
|
+ certificate="${1}"
|
|
644
|
+
|
|
645
|
+ local issuer_cert_uri
|
|
646
|
+ issuer_cert_uri="${2:-}"
|
|
647
|
+ if [[ -z "${issuer_cert_uri}" ]]; then issuer_cert_uri="$(get_issuer_cert_uri "${certificate}")"; fi
|
|
648
|
+ if [[ -n "${issuer_cert_uri}" ]]; then
|
|
649
|
+ # create temporary files
|
|
650
|
+ local tmpcert
|
|
651
|
+ local tmpcert_raw
|
|
652
|
+ tmpcert_raw="$(_mktemp)"
|
|
653
|
+ tmpcert="$(_mktemp)"
|
|
654
|
+
|
|
655
|
+ # download certificate
|
|
656
|
+ http_request get "${issuer_cert_uri}" > "${tmpcert_raw}"
|
|
657
|
+
|
|
658
|
+ # PEM
|
|
659
|
+ if grep -q "BEGIN CERTIFICATE" "${tmpcert_raw}"; then mv "${tmpcert_raw}" "${tmpcert}"
|
|
660
|
+ # DER
|
|
661
|
+ elif openssl x509 -in "${tmpcert_raw}" -inform DER -out "${tmpcert}" -outform PEM 2> /dev/null > /dev/null; then :
|
|
662
|
+ # PKCS7
|
|
663
|
+ elif openssl pkcs7 -in "${tmpcert_raw}" -inform DER -out "${tmpcert}" -outform PEM -print_certs 2> /dev/null > /dev/null; then :
|
|
664
|
+ # Unknown certificate type
|
|
665
|
+ else _exiterr "Unknown certificate type in chain"
|
|
666
|
+ fi
|
|
667
|
+
|
|
668
|
+ local next_issuer_cert_uri
|
|
669
|
+ next_issuer_cert_uri="$(get_issuer_cert_uri "${tmpcert}")"
|
|
670
|
+ if [[ -n "${next_issuer_cert_uri}" ]]; then
|
|
671
|
+ printf "\n%s\n" "${issuer_cert_uri}"
|
|
672
|
+ cat "${tmpcert}"
|
|
673
|
+ walk_chain "${tmpcert}" "${next_issuer_cert_uri}"
|
|
674
|
+ fi
|
|
675
|
+ rm -f "${tmpcert}" "${tmpcert_raw}"
|
|
676
|
+ fi
|
|
677
|
+}
|
|
678
|
+
|
572
|
679
|
# Create certificate for domain(s)
|
573
|
680
|
sign_domain() {
|
574
|
681
|
domain="${1}"
|
|
@@ -596,6 +703,26 @@ sign_domain() {
|
596
|
703
|
prime256v1|secp384r1) _openssl ecparam -genkey -name "${KEY_ALGO}" -out "${CERTDIR}/${domain}/privkey-${timestamp}.pem";;
|
597
|
704
|
esac
|
598
|
705
|
fi
|
|
706
|
+ # move rolloverkey into position (if any)
|
|
707
|
+ if [[ -r "${CERTDIR}/${domain}/privkey.pem" && -r "${CERTDIR}/${domain}/privkey.roll.pem" && "${PRIVATE_KEY_RENEW}" = "yes" && "${PRIVATE_KEY_ROLLOVER}" = "yes" ]]; then
|
|
708
|
+ echo " + Moving Rolloverkey into position.... "
|
|
709
|
+ mv "${CERTDIR}/${domain}/privkey.roll.pem" "${CERTDIR}/${domain}/privkey-tmp.pem"
|
|
710
|
+ mv "${CERTDIR}/${domain}/privkey-${timestamp}.pem" "${CERTDIR}/${domain}/privkey.roll.pem"
|
|
711
|
+ mv "${CERTDIR}/${domain}/privkey-tmp.pem" "${CERTDIR}/${domain}/privkey-${timestamp}.pem"
|
|
712
|
+ fi
|
|
713
|
+ # generate a new private rollover key if we need or want one
|
|
714
|
+ if [[ ! -r "${CERTDIR}/${domain}/privkey.roll.pem" && "${PRIVATE_KEY_ROLLOVER}" = "yes" && "${PRIVATE_KEY_RENEW}" = "yes" ]]; then
|
|
715
|
+ echo " + Generating private rollover key..."
|
|
716
|
+ case "${KEY_ALGO}" in
|
|
717
|
+ rsa) _openssl genrsa -out "${CERTDIR}/${domain}/privkey.roll.pem" "${KEYSIZE}";;
|
|
718
|
+ prime256v1|secp384r1) _openssl ecparam -genkey -name "${KEY_ALGO}" -out "${CERTDIR}/${domain}/privkey.roll.pem";;
|
|
719
|
+ esac
|
|
720
|
+ fi
|
|
721
|
+ # delete rolloverkeys if disabled
|
|
722
|
+ if [[ -r "${CERTDIR}/${domain}/privkey.roll.pem" && ! "${PRIVATE_KEY_ROLLOVER}" = "yes" ]]; then
|
|
723
|
+ echo " + Removing Rolloverkey (feature disabled)..."
|
|
724
|
+ rm -f "${CERTDIR}/${domain}/privkey.roll.pem"
|
|
725
|
+ fi
|
599
|
726
|
|
600
|
727
|
# Generate signing request config and the actual signing request
|
601
|
728
|
echo " + Generating signing request..."
|
|
@@ -621,10 +748,7 @@ sign_domain() {
|
621
|
748
|
# Create fullchain.pem
|
622
|
749
|
echo " + Creating fullchain.pem..."
|
623
|
750
|
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
|
627
|
|
- fi
|
|
751
|
+ walk_chain "${crt_path}" > "${CERTDIR}/${domain}/chain-${timestamp}.pem"
|
628
|
752
|
cat "${CERTDIR}/${domain}/chain-${timestamp}.pem" >> "${CERTDIR}/${domain}/fullchain-${timestamp}.pem"
|
629
|
753
|
|
630
|
754
|
# Update symlinks
|
|
@@ -636,13 +760,20 @@ sign_domain() {
|
636
|
760
|
ln -sf "cert-${timestamp}.pem" "${CERTDIR}/${domain}/cert.pem"
|
637
|
761
|
|
638
|
762
|
# Wait for hook script to clean the challenge and to deploy cert if used
|
639
|
|
- export KEY_ALGO
|
640
|
763
|
[[ -n "${HOOK}" ]] && "${HOOK}" "deploy_cert" "${domain}" "${CERTDIR}/${domain}/privkey.pem" "${CERTDIR}/${domain}/cert.pem" "${CERTDIR}/${domain}/fullchain.pem" "${CERTDIR}/${domain}/chain.pem" "${timestamp}"
|
641
|
764
|
|
642
|
765
|
unset challenge_token
|
643
|
766
|
echo " + Done!"
|
644
|
767
|
}
|
645
|
768
|
|
|
769
|
+# Usage: --register
|
|
770
|
+# Description: Register account key
|
|
771
|
+command_register() {
|
|
772
|
+ init_system
|
|
773
|
+ echo "+ Done!"
|
|
774
|
+ exit 0
|
|
775
|
+}
|
|
776
|
+
|
646
|
777
|
# Usage: --cron (-c)
|
647
|
778
|
# Description: Sign/renew non-existant/changed/expiring certificates.
|
648
|
779
|
command_sign_domains() {
|
|
@@ -662,7 +793,7 @@ command_sign_domains() {
|
662
|
793
|
# Generate certificates for all domains found in domains.txt. Check if existing certificate are about to expire
|
663
|
794
|
ORIGIFS="${IFS}"
|
664
|
795
|
IFS=$'\n'
|
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
|
|
796
|
+ for line in $(<"${DOMAINS_TXT}" tr -d '\r' | awk '{print tolower($0)}' | _sed -e 's/^[[:space:]]*//g' -e 's/[[:space:]]*$//g' -e 's/[[:space:]]+/ /g' | (grep -vE '^(#|$)' || true)); do
|
666
|
797
|
reset_configvars
|
667
|
798
|
IFS="${ORIGIFS}"
|
668
|
799
|
domain="$(printf '%s\n' "${line}" | cut -d' ' -f1)"
|
|
@@ -705,7 +836,7 @@ command_sign_domains() {
|
705
|
836
|
config_var="$(echo "${cfgline:1}" | cut -d'=' -f1)"
|
706
|
837
|
config_value="$(echo "${cfgline:1}" | cut -d'=' -f2-)"
|
707
|
838
|
case "${config_var}" in
|
708
|
|
- KEY_ALGO|OCSP_MUST_STAPLE|PRIVATE_KEY_RENEW|KEYSIZE|CHALLENGETYPE|HOOK|WELLKNOWN|HOOK_CHAIN|OPENSSL_CNF|RENEW_DAYS)
|
|
839
|
+ KEY_ALGO|OCSP_MUST_STAPLE|PRIVATE_KEY_RENEW|PRIVATE_KEY_ROLLOVER|KEYSIZE|CHALLENGETYPE|HOOK|WELLKNOWN|HOOK_CHAIN|OPENSSL_CNF|RENEW_DAYS)
|
709
|
840
|
echo " + ${config_var} = ${config_value}"
|
710
|
841
|
declare -- "${config_var}=${config_value}"
|
711
|
842
|
;;
|
|
@@ -716,6 +847,7 @@ command_sign_domains() {
|
716
|
847
|
IFS="${ORIGIFS}"
|
717
|
848
|
fi
|
718
|
849
|
verify_config
|
|
850
|
+ export WELLKNOWN CHALLENGETYPE KEY_ALGO PRIVATE_KEY_ROLLOVER
|
719
|
851
|
|
720
|
852
|
if [[ -e "${cert}" ]]; then
|
721
|
853
|
printf " + Checking domain name(s) of existing cert..."
|
|
@@ -767,6 +899,7 @@ command_sign_domains() {
|
767
|
899
|
# remove temporary domains.txt file if used
|
768
|
900
|
[[ -n "${PARAM_DOMAIN:-}" ]] && rm -f "${DOMAINS_TXT}"
|
769
|
901
|
|
|
902
|
+ [[ -n "${HOOK}" ]] && "${HOOK}" "exit_hook"
|
770
|
903
|
exit 0
|
771
|
904
|
}
|
772
|
905
|
|
|
@@ -797,10 +930,13 @@ command_sign_csr() {
|
797
|
930
|
if [ -n "${PARAM_FULL_CHAIN:-}" ]; then
|
798
|
931
|
# get and convert ca cert
|
799
|
932
|
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}"
|
|
933
|
+ tmpchain="$(_mktemp)"
|
|
934
|
+ http_request get "$(openssl x509 -in "${certfile}" -noout -text | grep 'CA Issuers - URI:' | cut -d':' -f2-)" > "${tmpchain}"
|
|
935
|
+ if grep -q "BEGIN CERTIFICATE" "${tmpchain}"; then
|
|
936
|
+ mv "${tmpchain}" "${chainfile}"
|
|
937
|
+ else
|
|
938
|
+ openssl x509 -in "${tmpchain}" -inform DER -out "${chainfile}" -outform PEM
|
|
939
|
+ rm "${tmpchain}"
|
804
|
940
|
fi
|
805
|
941
|
|
806
|
942
|
echo "# CHAIN #" >&3
|
|
@@ -924,7 +1060,7 @@ command_help() {
|
924
|
1060
|
# Usage: --env (-e)
|
925
|
1061
|
# Description: Output configuration variables for use in other scripts
|
926
|
1062
|
command_env() {
|
927
|
|
- echo "# letsencrypt.sh configuration"
|
|
1063
|
+ echo "# dehydrated configuration"
|
928
|
1064
|
load_config
|
929
|
1065
|
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
|
930
|
1066
|
}
|
|
@@ -965,6 +1101,16 @@ main() {
|
965
|
1101
|
set_command sign_domains
|
966
|
1102
|
;;
|
967
|
1103
|
|
|
1104
|
+ --register)
|
|
1105
|
+ set_command register
|
|
1106
|
+ ;;
|
|
1107
|
+
|
|
1108
|
+ # PARAM_Usage: --accept-terms
|
|
1109
|
+ # PARAM_Description: Accept CAs terms of service
|
|
1110
|
+ --accept-terms)
|
|
1111
|
+ PARAM_ACCEPT_TERMS="yes"
|
|
1112
|
+ ;;
|
|
1113
|
+
|
968
|
1114
|
--signcsr|-s)
|
969
|
1115
|
shift 1
|
970
|
1116
|
set_command sign_csr
|
|
@@ -1031,6 +1177,14 @@ main() {
|
1031
|
1177
|
PARAM_NO_LOCK="yes"
|
1032
|
1178
|
;;
|
1033
|
1179
|
|
|
1180
|
+ # PARAM_Usage: --lock-suffix example.com
|
|
1181
|
+ # PARAM_Description: Suffix lockfile name with a string (useful for with -d)
|
|
1182
|
+ --lock-suffix)
|
|
1183
|
+ shift 1
|
|
1184
|
+ check_parameters "${1:-}"
|
|
1185
|
+ PARAM_LOCKFILE_SUFFIX="${1}"
|
|
1186
|
+ ;;
|
|
1187
|
+
|
1034
|
1188
|
# PARAM_Usage: --ocsp
|
1035
|
1189
|
# PARAM_Description: Sets option in CSR indicating OCSP stapling to be mandatory
|
1036
|
1190
|
--ocsp)
|
|
@@ -1099,6 +1253,7 @@ main() {
|
1099
|
1253
|
case "${COMMAND}" in
|
1100
|
1254
|
env) command_env;;
|
1101
|
1255
|
sign_domains) command_sign_domains;;
|
|
1256
|
+ register) command_register;;
|
1102
|
1257
|
sign_csr) command_sign_csr "${PARAM_CSR}";;
|
1103
|
1258
|
revoke) command_revoke "${PARAM_REVOKECERT}";;
|
1104
|
1259
|
cleanup) command_cleanup;;
|