From 6a32f20e004b9d835cd02de9d78300be02784cf1 Mon Sep 17 00:00:00 2001 From: Lukas Schauer Date: Sun, 29 Jan 2017 18:54:10 +0100 Subject: [PATCH] ask user to read and accept license, added register-command, fullchain.pem is now actually the full chain --- CHANGELOG | 3 ++ README.md | 3 ++ dehydrated | 121 +++++++++++++++++++++++++++++++++---------- docs/examples/config | 7 ++- docs/staging.md | 1 + test.sh | 23 ++++++-- 6 files changed, 125 insertions(+), 33 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index bb0a720..d76e268 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,9 +3,11 @@ This file contains a log of major changes in dehydrated ## [x.x.x] - xxxx-xx-xx ## Changed +- dehydrated now asks you to read and accept the CAs terms of service before creating an account - Skip challenges for already validated domains - Removed need for some special commands (BusyBox compatibility) - Exported a few more variables for use in hook-scripts +- fullchain.pem now actually contains the full chain instead of just the certificate with an intermediate cert ## Added - Added private-key rollover functionality @@ -13,6 +15,7 @@ This file contains a log of major changes in dehydrated - Added `invalid_challenge` hook - Added `request_failure` hook - Added `exit_hook` hook +- Added standalone `register` command ## [0.3.1] - 2016-09-13 ## Changed diff --git a/README.md b/README.md index e2634de..b3eab9f 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ Usage: ./dehydrated [-h] [command [argument]] [parameter [argument]] [parameter Default command: help Commands: + --register Register account key --cron (-c) Sign/renew non-existant/changed/expiring certificates. --signcsr (-s) path/to/csr.pem Sign a given CSR, output CRT on stdout (advanced usage) --revoke (-r) path/to/cert.pem Revoke specified certificate @@ -54,6 +55,7 @@ Commands: --env (-e) Output configuration variables for use in other scripts Parameters: + --accept-terms Accept CAs terms of service --full-chain (-fc) Print full chain when using --signcsr --ipv4 (-4) Resolve names to IPv4 addresses only --ipv6 (-6) Resolve names to IPv6 addresses only @@ -61,6 +63,7 @@ Parameters: --keep-going (-g) Keep going after encountering an error while creating/renewing multiple certificates in cron mode --force (-x) Force renew of certificate even if it is longer valid than value in RENEW_DAYS --no-lock (-n) Don't use lockfile (potentially dangerous!) + --lock-suffix example.com Suffix lockfile name with a string (useful for with -d) --ocsp Sets option in CSR indicating OCSP stapling to be mandatory --privkey (-p) path/to/key.pem Use specified private key instead of account key (useful for revocation) --config (-f) path/to/config Use specified config file diff --git a/dehydrated b/dehydrated index cb3bbc8..a578955 100755 --- a/dehydrated +++ b/dehydrated @@ -105,7 +105,8 @@ load_config() { # Default values CA="https://acme-v01.api.letsencrypt.org/directory" - LICENSE="https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf" + CA_TERMS="https://acme-v01.api.letsencrypt.org/terms" + LICENSE= CERTDIR= ACCOUNTDIR= CHALLENGETYPE="http-01" @@ -233,6 +234,24 @@ init_system() { else # Check if private account key exists, if it doesn't exist yet generate a new one (rsa key) if [[ ! -e "${ACCOUNT_KEY}" ]]; then + REAL_LICENSE="$(http_request head "${CA_TERMS}" | (grep Location: || true) | awk -F ': ' '{print $2}' | tr -d '\n\r')" + if [[ -z "${REAL_LICENSE}" ]]; then + printf '\n' + printf 'Error retrieving terms of service from certificate authority.\n' + printf 'Please set LICENSE in config manually.\n' + exit 1 + fi + if [[ ! "${LICENSE}" = "${REAL_LICENSE}" ]]; then + if [[ "${PARAM_ACCEPT_TERMS:-}" = "yes" ]]; then + LICENSE="${REAL_LICENSE}" + else + printf '\n' + 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}" + printf 'To accept these terms of service run `%s --register --accept-terms`.\n' "${0}" + exit 1 + fi + fi + echo "+ Generating account key..." _openssl genrsa -out "${ACCOUNT_KEY}" "${KEYSIZE}" register_new_key="yes" @@ -360,29 +379,31 @@ http_request() { fi if [[ ! "${statuscode:0:1}" = "2" ]]; then - echo " + ERROR: An error occurred while sending ${1}-request to ${2} (Status ${statuscode})" >&2 - echo >&2 - echo "Details:" >&2 - cat "${tempcont}" >&2 - echo >&2 - echo >&2 + if [[ ! "${2}" = "${CA_TERMS}" ]] || [[ ! "${statuscode:0:1}" = "3" ]]; then + echo " + ERROR: An error occurred while sending ${1}-request to ${2} (Status ${statuscode})" >&2 + echo >&2 + echo "Details:" >&2 + cat "${tempcont}" >&2 + echo >&2 + echo >&2 - # An exclusive hook for the {1}-request error might be useful (e.g., for sending an e-mail to admins) - if [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" != "yes" ]]; then - errtxt=`cat ${tempcont}` - "${HOOK}" "request_failure" "${statuscode}" "${errtxt}" "${1}" - fi + # An exclusive hook for the {1}-request error might be useful (e.g., for sending an e-mail to admins) + if [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" != "yes" ]]; then + errtxt=`cat ${tempcont}` + "${HOOK}" "request_failure" "${statuscode}" "${errtxt}" "${1}" + fi - rm -f "${tempcont}" + rm -f "${tempcont}" - # Wait for hook script to clean the challenge if used - if [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" != "yes" ]] && [[ -n "${challenge_token:+set}" ]]; then - "${HOOK}" "clean_challenge" '' "${challenge_token}" "${keyauth}" - fi + # Wait for hook script to clean the challenge if used + if [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" != "yes" ]] && [[ -n "${challenge_token:+set}" ]]; then + "${HOOK}" "clean_challenge" '' "${challenge_token}" "${keyauth}" + fi - # remove temporary domains.txt file if used - [[ -n "${PARAM_DOMAIN:-}" && -n "${DOMAINS_TXT:-}" ]] && rm "${DOMAINS_TXT}" - exit 1 + # remove temporary domains.txt file if used + [[ -n "${PARAM_DOMAIN:-}" && -n "${DOMAINS_TXT:-}" ]] && rm "${DOMAINS_TXT}" + exit 1 + fi fi cat "${tempcont}" @@ -600,6 +621,39 @@ sign_csr() { echo " + Done!" } +walk_chain() { + certificate="${1}" + + # grep uri from certificate + local issuer_cert_uri + issuer_cert_uri="$(openssl x509 -in "${certificate}" -noout -text | (grep 'CA Issuers - URI:' | cut -d':' -f2-) || true)" + if [[ -n "${issuer_cert_uri}" ]]; then + # create temporary files + local tmpcert + local tmpcert_raw + tmpcert_raw="$(_mktemp)" + tmpcert="$(_mktemp)" + + # download certificate + http_request get "${issuer_cert_uri}" > "${tmpcert_raw}" + + # PEM + if grep -q "BEGIN CERTIFICATE" "${tmpcert_raw}"; then mv "${tmpcert_raw}" "${tmpcert}" + # DER + elif openssl x509 -in "${tmpcert_raw}" -inform DER -out "${tmpcert}" -outform PEM 2> /dev/null > /dev/null; then : + # PKCS7 + elif openssl pkcs7 -in "${tmpcert_raw}" -inform DER -out "${tmpcert}" -outform PEM -print_certs 2> /dev/null > /dev/null; then : + # Unknown certificate type + else _exiterr "Unknown certificate type in chain" + fi + + printf "\n%s\n" "${issuer_cert_uri}" + cat "${tmpcert}" + walk_chain "${tmpcert}" + rm -f "${tmpcert}" "${tmpcert_raw}" + fi +} + # Create certificate for domain(s) sign_domain() { domain="${1}" @@ -672,14 +726,7 @@ sign_domain() { # Create fullchain.pem echo " + Creating fullchain.pem..." cat "${crt_path}" > "${CERTDIR}/${domain}/fullchain-${timestamp}.pem" - tmpchain="$(_mktemp)" - http_request get "$(openssl x509 -in "${CERTDIR}/${domain}/cert-${timestamp}.pem" -noout -text | grep 'CA Issuers - URI:' | cut -d':' -f2-)" > "${tmpchain}" - if grep -q "BEGIN CERTIFICATE" "${tmpchain}"; then - mv "${tmpchain}" "${CERTDIR}/${domain}/chain-${timestamp}.pem" - else - openssl x509 -in "${tmpchain}" -inform DER -out "${CERTDIR}/${domain}/chain-${timestamp}.pem" -outform PEM - rm "${tmpchain}" - fi + walk_chain "${crt_path}" > "${CERTDIR}/${domain}/chain-${timestamp}.pem" cat "${CERTDIR}/${domain}/chain-${timestamp}.pem" >> "${CERTDIR}/${domain}/fullchain-${timestamp}.pem" # Update symlinks @@ -697,6 +744,13 @@ sign_domain() { echo " + Done!" } +# Usage: --register +# Description: Register account key +command_register() { + init_system + exit 0 +} + # Usage: --cron (-c) # Description: Sign/renew non-existant/changed/expiring certificates. command_sign_domains() { @@ -1024,6 +1078,16 @@ main() { set_command sign_domains ;; + --register) + set_command register + ;; + + # PARAM_Usage: --accept-terms + # PARAM_Description: Accept CAs terms of service + --accept-terms) + PARAM_ACCEPT_TERMS="yes" + ;; + --signcsr|-s) shift 1 set_command sign_csr @@ -1166,6 +1230,7 @@ main() { case "${COMMAND}" in env) command_env;; sign_domains) command_sign_domains;; + register) command_register;; sign_csr) command_sign_csr "${PARAM_CSR}";; revoke) command_revoke "${PARAM_REVOKECERT}";; cleanup) command_cleanup;; diff --git a/docs/examples/config b/docs/examples/config index 92b2b83..1b1b3d8 100644 --- a/docs/examples/config +++ b/docs/examples/config @@ -18,8 +18,11 @@ # Path to certificate authority (default: https://acme-v01.api.letsencrypt.org/directory) #CA="https://acme-v01.api.letsencrypt.org/directory" -# Path to license agreement (default: https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf) -#LICENSE="https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf" +# Path to certificate authority license terms redirect (default: https://acme-v01.api.letsencrypt.org/terms) +#CA_TERMS="https://acme-v01.api.letsencrypt.org/terms" + +# Path to license agreement (default: ) +#LICENSE="" # Which challenge should be used? Currently http-01 and dns-01 are supported #CHALLENGETYPE="http-01" diff --git a/docs/staging.md b/docs/staging.md index 297db58..213ff57 100644 --- a/docs/staging.md +++ b/docs/staging.md @@ -9,4 +9,5 @@ To avoid this, please set the CA property to the Let’s Encrypt staging server ```bash CA="https://acme-staging.api.letsencrypt.org/directory" +CA_TERMS="https://acme-staging.api.letsencrypt.org/terms" ``` diff --git a/test.sh b/test.sh index c748a0a..8b35111 100755 --- a/test.sh +++ b/test.sh @@ -97,7 +97,7 @@ mkdir -p .acme-challenges/.well-known/acme-challenge # Generate config and create empty domains.txt echo 'CA="https://testca.kurz.pw/directory"' > config -echo 'LICENSE="https://testca.kurz.pw/terms/v1"' >> config +echo 'CA_TERMS="https://testca.kurz.pw/terms"' >> config echo 'WELLKNOWN=".acme-challenges/.well-known/acme-challenge"' >> config echo 'RENEW_DAYS="14"' >> config touch domains.txt @@ -110,6 +110,23 @@ _CHECK_LOG "--help (-h)" _CHECK_LOG "--domain (-d) domain.tld" _CHECK_ERRORLOG +# Register account key without LICENSE set +_TEST "Register account key without LICENSE set" +./dehydrated --register > tmplog 2> errorlog && _FAIL "Script execution failed" +_CHECK_LOG "To accept these terms" +_CHECK_ERRORLOG + +# Register account key and agreeing to terms +_TEST "Register account key without LICENSE set" +./dehydrated --register --accept-terms > tmplog 2> errorlog || _FAIL "Script execution failed" +_CHECK_LOG "Registering account key" +_CHECK_FILE accounts/*/account_key.pem +_CHECK_ERRORLOG + +# Delete accounts and add LICENSE to config for normal operation +rm -rf accounts +echo 'LICENSE="https://testca.kurz.pw/terms/v1"' >> config + # Run in cron mode with empty domains.txt (should only generate private key and exit) _TEST "First run in cron mode, checking if private key is generated and registered" ./dehydrated --cron > tmplog 2> errorlog || _FAIL "Script execution failed" @@ -120,7 +137,7 @@ _CHECK_ERRORLOG # Temporarily move config out of the way and try signing certificate by using temporary config location _TEST "Try signing using temporary config location and with domain as command line parameter" mv config tmp_config -./dehydrated --cron --domain "${TMP_URL}" --domain "${TMP2_URL}" -f tmp_config > tmplog 2> errorlog || _FAIL "Script execution failed" +./dehydrated --cron --domain "${TMP_URL}" --domain "${TMP2_URL}" --accept-terms -f tmp_config > tmplog 2> errorlog || _FAIL "Script execution failed" _CHECK_NOT_LOG "Checking domain name(s) of existing cert" _CHECK_LOG "Generating private key" _CHECK_LOG "Requesting challenge for ${TMP_URL}" @@ -168,7 +185,7 @@ _CHECK_NOT_LOG "Generating private key" _CHECK_LOG "Requesting challenge for ${TMP_URL}" _CHECK_LOG "Requesting challenge for ${TMP2_URL}" _CHECK_LOG "Requesting challenge for ${TMP3_URL}" -_CHECK_LOG "Challenge is valid!" +_CHECK_LOG "Already validated!" _CHECK_LOG "Creating fullchain.pem" _CHECK_LOG "Done!" _CHECK_ERRORLOG -- 2.47.2