Hello community, here is the log from the commit of package dehydrated for openSUSE:Factory checked in at 2017-03-02 19:38:39 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/dehydrated (Old) and /work/SRC/openSUSE:Factory/.dehydrated.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "dehydrated" Thu Mar 2 19:38:39 2017 rev:3 rq:460891 version:0.4.0 Changes: -------- --- /work/SRC/openSUSE:Factory/dehydrated/dehydrated.changes 2017-02-13 07:49:05.430491137 +0100 +++ /work/SRC/openSUSE:Factory/.dehydrated.new/dehydrated.changes 2017-03-02 19:38:40.344285655 +0100 @@ -1,0 +2,15 @@ +Tue Feb 21 13:12:19 UTC 2017 - daniel.molken...@suse.com + +- Drop the (undocumented) dependeny for mod_headers + +------------------------------------------------------------------- +Sat Feb 18 16:51:10 UTC 2017 - dan...@molkentin.de + +- Unify configuration file source names + +------------------------------------------------------------------- +Sat Feb 18 14:08:02 UTC 2017 - dan...@molkentin.de + +- Bump to 0.4.0 + +------------------------------------------------------------------- Old: ---- acme-challenge.conf.in acme-challenge.in dehydrated-0.3.1.tar.gz New: ---- acme-challenge.conf.apache.in acme-challenge.conf.nginx.in dehydrated-0.4.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ dehydrated.spec ++++++ --- /var/tmp/diff_new_pack.QXanHK/_old 2017-03-02 19:38:41.056184917 +0100 +++ /var/tmp/diff_new_pack.QXanHK/_new 2017-03-02 19:38:41.060184351 +0100 @@ -46,15 +46,15 @@ %{!?_tmpfilesdir: %global _tmpfilesdir /usr/lib/tmpfiles.d } Name: dehydrated -Version: 0.3.1 +Version: 0.4.0 Release: 0 Summary: A client for signing certificates with an ACME server License: MIT Group: Productivity/Networking/Security Url: https://github.com/lukas2511/dehydrated Source0: %{name}-%{version}.tar.gz -Source1: acme-challenge.conf.in -Source2: acme-challenge.in +Source1: acme-challenge.conf.apache.in +Source2: acme-challenge.conf.nginx.in Source3: acme-challenge.conf.lighttpd.in Source4: dehydrated.cron.in Source5: dehydrated.tmpfiles.d ++++++ acme-challenge.conf.apache.in ++++++ Alias /.well-known/acme-challenge @CHALLENGEDIR@ <Directory "@CHALLENGEDIR@"> Options None AllowOverride None Require all granted ForceType text/plain </Directory> ++++++ acme-challenge.conf.nginx.in ++++++ # This adds a the acme challenge directory to # your hosts config file. You will only need # this on port 80. The following snippet shows # how to use in on a HTTP server that only # redirects to HTTPS otherwise. it's important # to wrap the rest into a "location /" block. # #server { # listen 80 default_server; # listen [::]:80 default_server; # # include "acme-challenge"; # location / { # return 301 https://$host$request_uri; # } #} location /.well-known/acme-challenge { alias @CHALLENGEDIR@; } ++++++ dehydrated-0.3.1.tar.gz -> dehydrated-0.4.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dehydrated-0.3.1/.travis.yml new/dehydrated-0.4.0/.travis.yml --- old/dehydrated-0.3.1/.travis.yml 2016-09-13 20:00:43.000000000 +0200 +++ new/dehydrated-0.4.0/.travis.yml 2017-02-05 15:33:17.000000000 +0100 @@ -1,6 +1,10 @@ sudo: false language: shell +os: + - linux + - osx + cache: directories: - ngrok diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dehydrated-0.3.1/CHANGELOG new/dehydrated-0.4.0/CHANGELOG --- old/dehydrated-0.3.1/CHANGELOG 2016-09-13 20:00:43.000000000 +0200 +++ new/dehydrated-0.4.0/CHANGELOG 2017-02-05 15:33:17.000000000 +0100 @@ -5,6 +5,22 @@ ## Changed - ... +## [0.4.0] - 2017-02-05 +## 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 +- Added `--lock-suffix` option for allowing parallel execution +- Added `invalid_challenge` hook +- Added `request_failure` hook +- Added `exit_hook` hook +- Added standalone `register` command + ## [0.3.1] - 2016-09-13 ## Changed - Renamed project to `dehydrated`. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dehydrated-0.3.1/LICENSE new/dehydrated-0.4.0/LICENSE --- old/dehydrated-0.3.1/LICENSE 2016-09-13 20:00:43.000000000 +0200 +++ new/dehydrated-0.4.0/LICENSE 2017-02-05 15:33:17.000000000 +0100 @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015 Lukas Schauer +Copyright (c) 2015-2017 Lukas Schauer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dehydrated-0.3.1/README.md new/dehydrated-0.4.0/README.md --- old/dehydrated-0.3.1/README.md 2016-09-13 20:00:43.000000000 +0200 +++ new/dehydrated-0.4.0/README.md 2017-02-05 15:33:17.000000000 +0100 @@ -2,11 +2,11 @@ ![](docs/logo.jpg) -This is a client for signing certificates with an ACME-server (currently only provided by letsencrypt) implemented as a relatively simple bash-script. +This is a client for signing certificates with an ACME-server (currently only provided by Let's Encrypt) implemented as a relatively simple bash-script. It uses the `openssl` utility for everything related to actually handling keys and certificates, so you need to have that installed. -Other dependencies are: curl, sed, grep, mktemp (all found on almost any system, curl being the only exception) +Other dependencies are: cURL, sed, grep, mktemp (all found on almost any system, cURL being the only exception) Current features: - Signing of a list of domains @@ -14,19 +14,30 @@ - Renewal if a certificate is about to expire or SAN (subdomains) changed - Certificate revocation -Please keep in mind that this software and even the acme-protocol are relatively young and may still have some unresolved issues. -Feel free to report any issues you find with this script or contribute by submitting a pullrequest. +Please keep in mind that this software and even the acme-protocol are relatively young and may still have some unresolved issues. Feel free to report any issues you find with this script or contribute by submitting a pull request. -### Getting started +## Getting started For getting started I recommend taking a look at [docs/domains_txt.md](docs/domains_txt.md), [docs/wellknown.md](docs/wellknown.md) and the [Usage](#usage) section on this page (you'll probably only need the `-c` option). Generally you want to set up your WELLKNOWN path first, and then fill in domains.txt. -**Please note that you should use the staging URL when experimenting with this script to not hit letsencrypts rate limits.** See [docs/staging.md](docs/staging.md). +**Please note that you should use the staging URL when experimenting with this script to not hit Let's Encrypt's rate limits.** See [docs/staging.md](docs/staging.md). If you have any problems take a look at our [Troubleshooting](docs/troubleshooting.md) guide. +## Config + +dehydrated is looking for a config file in a few different places, it will use the first one it can find in this order: + +- `/etc/dehydrated/config` +- `/usr/local/etc/dehydrated/config` +- The current working directory of your shell +- The directory from which dehydrated was ran + +Have a look at [docs/examples/config](docs/examples/config) to get started, copy it to e.g. `/etc/dehydrated/config` +and edit it to fit your needs. + ## Usage: ```text @@ -35,6 +46,7 @@ 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 @@ -43,6 +55,7 @@ --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 @@ -50,6 +63,7 @@ --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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dehydrated-0.3.1/dehydrated new/dehydrated-0.4.0/dehydrated --- old/dehydrated-0.3.1/dehydrated 2016-09-13 20:00:43.000000000 +0200 +++ new/dehydrated-0.4.0/dehydrated 2017-02-05 15:33:17.000000000 +0100 @@ -34,8 +34,8 @@ openssl version > /dev/null 2>&1 || _exiterr "This script requires an openssl binary." _sed "" < /dev/null > /dev/null 2>&1 || _exiterr "This script requires sed with support for extended (modern) regular expressions." command -v grep > /dev/null 2>&1 || _exiterr "This script requires grep." - _mktemp -u > /dev/null 2>&1 || _exiterr "This script requires mktemp." - diff -u /dev/null /dev/null || _exiterr "This script requires diff." + command -v mktemp > /dev/null 2>&1 || _exiterr "This script requires mktemp." + command -v diff > /dev/null 2>&1 || _exiterr "This script requires diff." # curl returns with an error code in some ancient versions so we have to catch that set +e @@ -81,7 +81,7 @@ if [[ "${CHALLENGETYPE}" = "dns-01" ]] && [[ -z "${HOOK}" ]]; then _exiterr "Challenge type dns-01 needs a hook script for deployment... can not continue." fi - if [[ "${CHALLENGETYPE}" = "http-01" && ! -d "${WELLKNOWN}" ]]; then + if [[ "${CHALLENGETYPE}" = "http-01" && ! -d "${WELLKNOWN}" && ! "${COMMAND:-}" = "register" ]]; then _exiterr "WELLKNOWN directory doesn't exist, please create ${WELLKNOWN} and set appropriate permissions." fi [[ "${KEY_ALGO}" =~ ^(rsa|prime256v1|secp384r1)$ ]] || _exiterr "Unknown public key algorithm ${KEY_ALGO}... can not continue." @@ -105,7 +105,8 @@ # 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" @@ -118,6 +119,7 @@ KEYSIZE="4096" WELLKNOWN= PRIVATE_KEY_RENEW="yes" + PRIVATE_KEY_ROLLOVER="no" KEY_ALGO=rsa OPENSSL_CNF="$(openssl version -d | cut -d\" -f2)/openssl.cnf" CONTACT_EMAIL= @@ -183,6 +185,7 @@ [[ -z "${DOMAINS_TXT}" ]] && DOMAINS_TXT="${BASEDIR}/domains.txt" [[ -z "${WELLKNOWN}" ]] && WELLKNOWN="/var/www/dehydrated" [[ -z "${LOCKFILE}" ]] && LOCKFILE="${BASEDIR}/lock" + [[ -n "${PARAM_LOCKFILE_SUFFIX:-}" ]] && LOCKFILE="${LOCKFILE}-${PARAM_LOCKFILE_SUFFIX}" [[ -n "${PARAM_NO_LOCK:-}" ]] && LOCKFILE="" [[ -n "${PARAM_HOOK:-}" ]] && HOOK="${PARAM_HOOK}" @@ -219,7 +222,7 @@ _exiterr "Problem retrieving ACME/CA-URLs, check if your configured CA points to the directory entrypoint." # Export some environment variables to be used in hook script - export WELLKNOWN BASEDIR CERTDIR CONFIG + export WELLKNOWN BASEDIR CERTDIR CONFIG COMMAND # Checking for private key ... register_new_key="no" @@ -231,6 +234,24 @@ 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" @@ -247,14 +268,22 @@ # If we generated a new private key in the step above we have to register it with the acme-server if [[ "${register_new_key}" = "yes" ]]; then echo "+ Registering account key with ACME server..." - [[ ! -z "${CA_NEW_REG}" ]] || _exiterr "Certificate authority doesn't allow registrations." - # If an email for the contact has been provided then adding it to the registration request FAILED=false - if [[ -n "${CONTACT_EMAIL}" ]]; then - (signed_request "${CA_NEW_REG}" '{"resource": "new-reg", "contact":["mailto:'"${CONTACT_EMAIL}"'"], "agreement": "'"$LICENSE"'"}' > "${ACCOUNT_KEY_JSON}") || FAILED=true - else - (signed_request "${CA_NEW_REG}" '{"resource": "new-reg", "agreement": "'"$LICENSE"'"}' > "${ACCOUNT_KEY_JSON}") || FAILED=true + + if [[ -z "${CA_NEW_REG}" ]]; then + echo "Certificate authority doesn't allow registrations." + FAILED=true + fi + + # If an email for the contact has been provided then adding it to the registration request + if [[ "${FAILED}" = "false" ]]; then + if [[ -n "${CONTACT_EMAIL}" ]]; then + (signed_request "${CA_NEW_REG}" '{"resource": "new-reg", "contact":["mailto:'"${CONTACT_EMAIL}"'"], "agreement": "'"$LICENSE"'"}' > "${ACCOUNT_KEY_JSON}") || FAILED=true + else + (signed_request "${CA_NEW_REG}" '{"resource": "new-reg", "agreement": "'"$LICENSE"'"}' > "${ACCOUNT_KEY_JSON}") || FAILED=true + fi fi + if [[ "${FAILED}" = "true" ]]; then echo echo @@ -262,8 +291,10 @@ rm "${ACCOUNT_KEY}" "${ACCOUNT_KEY_JSON}" exit 1 fi + elif [[ "${COMMAND:-}" = "register" ]]; then + echo "+ Account already registered!" + exit 0 fi - } # Different sed version for different os types... @@ -305,6 +336,13 @@ sed -n "${filter}" } +rm_json_arrays() { + local filter + filter='s/\[[^][]*\]/null/g' + # remove three levels of nested arrays + sed -e "${filter}" -e "${filter}" -e "${filter}" +} + # OpenSSL writes to stderr/stdout even when there are no errors. So just # display the output if the exit code was != 0 to simplify debugging. _openssl() { @@ -351,22 +389,31 @@ 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 - rm -f "${tempcont}" + 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 - # 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 + # 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 - # remove temporary domains.txt file if used - [[ -n "${PARAM_DOMAIN:-}" && -n "${DOMAINS_TXT:-}" ]] && rm "${DOMAINS_TXT}" - exit 1 + 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 + + # remove temporary domains.txt file if used + [[ -n "${PARAM_DOMAIN:-}" && -n "${DOMAINS_TXT:-}" ]] && rm "${DOMAINS_TXT}" + exit 1 + fi fi cat "${tempcont}" @@ -409,7 +456,7 @@ reqtext="$( <<<"${csr}" openssl req -noout -text )" if <<<"${reqtext}" grep -q '^[[:space:]]*X509v3 Subject Alternative Name:[[:space:]]*$'; then # SANs used, extract these - altnames="$( <<<"${reqtext}" grep -A1 '^[[:space:]]*X509v3 Subject Alternative Name:[[:space:]]*$' | tail -n1 )" + altnames="$( <<<"${reqtext}" awk '/X509v3 Subject Alternative Name:/{print;getline;print;}' | tail -n1 )" # split to one per line: # shellcheck disable=SC1003 altnames="$( <<<"${altnames}" _sed -e 's/^[[:space:]]*//; s/, /\'$'\n''/g' )" @@ -450,9 +497,9 @@ local idx=0 if [[ -n "${ZSH_VERSION:-}" ]]; then - local -A challenge_uris challenge_tokens keyauths deploy_args + local -A challenge_altnames challenge_uris challenge_tokens keyauths deploy_args else - local -a challenge_uris challenge_tokens keyauths deploy_args + local -a challenge_altnames challenge_uris challenge_tokens keyauths deploy_args fi # Request challenges @@ -461,6 +508,12 @@ echo " + Requesting challenge for ${altname}..." response="$(signed_request "${CA_NEW_AUTHZ}" '{"resource": "new-authz", "identifier": {"type": "dns", "value": "'"${altname}"'"}}' | clean_json)" + challenge_status="$(printf '%s' "${response}" | rm_json_arrays | get_json_string_value status)" + if [ "${challenge_status}" = "valid" ]; then + echo " + Already validated!" + continue + fi + challenges="$(printf '%s\n' "${response}" | sed -n 's/.*\("challenges":[^\[]*\[[^]]*]\).*/\1/p')" repl=$'\n''{' # fix syntax highlighting in Vim challenge="$(printf "%s" "${challenges//\{/${repl}}" | grep \""${CHALLENGETYPE}"\")" @@ -487,6 +540,7 @@ ;; esac + challenge_altnames[${idx}]="${altname}" challenge_uris[${idx}]="${challenge_uri}" keyauths[${idx}]="${keyauth}" challenge_tokens[${idx}]="${challenge_token}" @@ -494,56 +548,64 @@ deploy_args[${idx}]="${altname} ${challenge_token} ${keyauth_hook}" idx=$((idx+1)) done + challenge_count="${idx}" # Wait for hook script to deploy the challenges if used - # shellcheck disable=SC2068 - [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" = "yes" ]] && "${HOOK}" "deploy_challenge" ${deploy_args[@]} + if [[ ${challenge_count} -ne 0 ]]; then + # shellcheck disable=SC2068 + [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" = "yes" ]] && "${HOOK}" "deploy_challenge" ${deploy_args[@]} + fi # Respond to challenges + reqstatus="valid" idx=0 - for altname in ${altnames}; do - challenge_token="${challenge_tokens[${idx}]}" - keyauth="${keyauths[${idx}]}" - - # Wait for hook script to deploy the challenge if used - # shellcheck disable=SC2086 - [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" != "yes" ]] && "${HOOK}" "deploy_challenge" ${deploy_args[${idx}]} + if [ ${challenge_count} -ne 0 ]; then + for altname in "${challenge_altnames[@]:0}"; do + challenge_token="${challenge_tokens[${idx}]}" + keyauth="${keyauths[${idx}]}" - # Ask the acme-server to verify our challenge and wait until it is no longer pending - echo " + Responding to challenge for ${altname}..." - result="$(signed_request "${challenge_uris[${idx}]}" '{"resource": "challenge", "keyAuthorization": "'"${keyauth}"'"}' | clean_json)" + # Wait for hook script to deploy the challenge if used + # shellcheck disable=SC2086 + [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" != "yes" ]] && "${HOOK}" "deploy_challenge" ${deploy_args[${idx}]} - reqstatus="$(printf '%s\n' "${result}" | get_json_string_value status)" + # Ask the acme-server to verify our challenge and wait until it is no longer pending + echo " + Responding to challenge for ${altname}..." + result="$(signed_request "${challenge_uris[${idx}]}" '{"resource": "challenge", "keyAuthorization": "'"${keyauth}"'"}' | clean_json)" - while [[ "${reqstatus}" = "pending" ]]; do - sleep 1 - result="$(http_request get "${challenge_uris[${idx}]}")" reqstatus="$(printf '%s\n' "${result}" | get_json_string_value status)" - done - [[ "${CHALLENGETYPE}" = "http-01" ]] && rm -f "${WELLKNOWN}/${challenge_token}" + while [[ "${reqstatus}" = "pending" ]]; do + sleep 1 + result="$(http_request get "${challenge_uris[${idx}]}")" + reqstatus="$(printf '%s\n' "${result}" | get_json_string_value status)" + done - # Wait for hook script to clean the challenge if used - if [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" != "yes" ]] && [[ -n "${challenge_token}" ]]; then - # shellcheck disable=SC2086 - "${HOOK}" "clean_challenge" ${deploy_args[${idx}]} - fi - idx=$((idx+1)) + [[ "${CHALLENGETYPE}" = "http-01" ]] && rm -f "${WELLKNOWN}/${challenge_token}" - if [[ "${reqstatus}" = "valid" ]]; then - echo " + Challenge is valid!" - else - break - fi - done + # Wait for hook script to clean the challenge if used + if [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" != "yes" ]] && [[ -n "${challenge_token}" ]]; then + # shellcheck disable=SC2086 + "${HOOK}" "clean_challenge" ${deploy_args[${idx}]} + fi + idx=$((idx+1)) + + if [[ "${reqstatus}" = "valid" ]]; then + echo " + Challenge is valid!" + else + [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" != "yes" ]] && "${HOOK}" "invalid_challenge" "${altname}" "${result}" + fi + done + fi # Wait for hook script to clean the challenges if used # shellcheck disable=SC2068 - [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" = "yes" ]] && "${HOOK}" "clean_challenge" ${deploy_args[@]} + if [[ ${challenge_count} -ne 0 ]]; then + [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" = "yes" ]] && "${HOOK}" "clean_challenge" ${deploy_args[@]} + fi if [[ "${reqstatus}" != "valid" ]]; then # Clean up any remaining challenge_tokens if we stopped early - if [[ "${CHALLENGETYPE}" = "http-01" ]]; then + if [[ "${CHALLENGETYPE}" = "http-01" ]] && [[ ${challenge_count} -ne 0 ]]; then while [ ${idx} -lt ${#challenge_tokens[@]} ]; do rm -f "${WELLKNOWN}/${challenge_tokens[${idx}]}" idx=$((idx+1)) @@ -569,6 +631,51 @@ echo " + Done!" } +# grep issuer cert uri from certificate +get_issuer_cert_uri() { + certificate="${1}" + openssl x509 -in "${certificate}" -noout -text | (grep 'CA Issuers - URI:' | cut -d':' -f2-) || true +} + +# walk certificate chain, retrieving all intermediate certificates +walk_chain() { + local certificate + certificate="${1}" + + local issuer_cert_uri + issuer_cert_uri="${2:-}" + if [[ -z "${issuer_cert_uri}" ]]; then issuer_cert_uri="$(get_issuer_cert_uri "${certificate}")"; fi + 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 + + local next_issuer_cert_uri + next_issuer_cert_uri="$(get_issuer_cert_uri "${tmpcert}")" + if [[ -n "${next_issuer_cert_uri}" ]]; then + printf "\n%s\n" "${issuer_cert_uri}" + cat "${tmpcert}" + walk_chain "${tmpcert}" "${next_issuer_cert_uri}" + fi + rm -f "${tmpcert}" "${tmpcert_raw}" + fi +} + # Create certificate for domain(s) sign_domain() { domain="${1}" @@ -596,6 +703,26 @@ prime256v1|secp384r1) _openssl ecparam -genkey -name "${KEY_ALGO}" -out "${CERTDIR}/${domain}/privkey-${timestamp}.pem";; esac fi + # move rolloverkey into position (if any) + if [[ -r "${CERTDIR}/${domain}/privkey.pem" && -r "${CERTDIR}/${domain}/privkey.roll.pem" && "${PRIVATE_KEY_RENEW}" = "yes" && "${PRIVATE_KEY_ROLLOVER}" = "yes" ]]; then + echo " + Moving Rolloverkey into position.... " + mv "${CERTDIR}/${domain}/privkey.roll.pem" "${CERTDIR}/${domain}/privkey-tmp.pem" + mv "${CERTDIR}/${domain}/privkey-${timestamp}.pem" "${CERTDIR}/${domain}/privkey.roll.pem" + mv "${CERTDIR}/${domain}/privkey-tmp.pem" "${CERTDIR}/${domain}/privkey-${timestamp}.pem" + fi + # generate a new private rollover key if we need or want one + if [[ ! -r "${CERTDIR}/${domain}/privkey.roll.pem" && "${PRIVATE_KEY_ROLLOVER}" = "yes" && "${PRIVATE_KEY_RENEW}" = "yes" ]]; then + echo " + Generating private rollover key..." + case "${KEY_ALGO}" in + rsa) _openssl genrsa -out "${CERTDIR}/${domain}/privkey.roll.pem" "${KEYSIZE}";; + prime256v1|secp384r1) _openssl ecparam -genkey -name "${KEY_ALGO}" -out "${CERTDIR}/${domain}/privkey.roll.pem";; + esac + fi + # delete rolloverkeys if disabled + if [[ -r "${CERTDIR}/${domain}/privkey.roll.pem" && ! "${PRIVATE_KEY_ROLLOVER}" = "yes" ]]; then + echo " + Removing Rolloverkey (feature disabled)..." + rm -f "${CERTDIR}/${domain}/privkey.roll.pem" + fi # Generate signing request config and the actual signing request echo " + Generating signing request..." @@ -621,10 +748,7 @@ # Create fullchain.pem echo " + Creating fullchain.pem..." cat "${crt_path}" > "${CERTDIR}/${domain}/fullchain-${timestamp}.pem" - 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" - if ! grep -q "BEGIN CERTIFICATE" "${CERTDIR}/${domain}/chain-${timestamp}.pem"; then - openssl x509 -in "${CERTDIR}/${domain}/chain-${timestamp}.pem" -inform DER -out "${CERTDIR}/${domain}/chain-${timestamp}.pem" -outform PEM - fi + walk_chain "${crt_path}" > "${CERTDIR}/${domain}/chain-${timestamp}.pem" cat "${CERTDIR}/${domain}/chain-${timestamp}.pem" >> "${CERTDIR}/${domain}/fullchain-${timestamp}.pem" # Update symlinks @@ -636,13 +760,20 @@ ln -sf "cert-${timestamp}.pem" "${CERTDIR}/${domain}/cert.pem" # Wait for hook script to clean the challenge and to deploy cert if used - export KEY_ALGO [[ -n "${HOOK}" ]] && "${HOOK}" "deploy_cert" "${domain}" "${CERTDIR}/${domain}/privkey.pem" "${CERTDIR}/${domain}/cert.pem" "${CERTDIR}/${domain}/fullchain.pem" "${CERTDIR}/${domain}/chain.pem" "${timestamp}" unset challenge_token echo " + Done!" } +# Usage: --register +# Description: Register account key +command_register() { + init_system + echo "+ Done!" + exit 0 +} + # Usage: --cron (-c) # Description: Sign/renew non-existant/changed/expiring certificates. command_sign_domains() { @@ -662,7 +793,7 @@ # Generate certificates for all domains found in domains.txt. Check if existing certificate are about to expire ORIGIFS="${IFS}" IFS=$'\n' - 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 + 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 reset_configvars IFS="${ORIGIFS}" domain="$(printf '%s\n' "${line}" | cut -d' ' -f1)" @@ -705,7 +836,7 @@ config_var="$(echo "${cfgline:1}" | cut -d'=' -f1)" config_value="$(echo "${cfgline:1}" | cut -d'=' -f2-)" case "${config_var}" in - KEY_ALGO|OCSP_MUST_STAPLE|PRIVATE_KEY_RENEW|KEYSIZE|CHALLENGETYPE|HOOK|WELLKNOWN|HOOK_CHAIN|OPENSSL_CNF|RENEW_DAYS) + KEY_ALGO|OCSP_MUST_STAPLE|PRIVATE_KEY_RENEW|PRIVATE_KEY_ROLLOVER|KEYSIZE|CHALLENGETYPE|HOOK|WELLKNOWN|HOOK_CHAIN|OPENSSL_CNF|RENEW_DAYS) echo " + ${config_var} = ${config_value}" declare -- "${config_var}=${config_value}" ;; @@ -716,6 +847,7 @@ IFS="${ORIGIFS}" fi verify_config + export WELLKNOWN CHALLENGETYPE KEY_ALGO PRIVATE_KEY_ROLLOVER if [[ -e "${cert}" ]]; then printf " + Checking domain name(s) of existing cert..." @@ -767,6 +899,7 @@ # remove temporary domains.txt file if used [[ -n "${PARAM_DOMAIN:-}" ]] && rm -f "${DOMAINS_TXT}" + [[ -n "${HOOK}" ]] && "${HOOK}" "exit_hook" exit 0 } @@ -797,10 +930,13 @@ if [ -n "${PARAM_FULL_CHAIN:-}" ]; then # get and convert ca cert chainfile="$(_mktemp)" - http_request get "$(openssl x509 -in "${certfile}" -noout -text | grep 'CA Issuers - URI:' | cut -d':' -f2-)" > "${chainfile}" - - if ! grep -q "BEGIN CERTIFICATE" "${chainfile}"; then - openssl x509 -inform DER -in "${chainfile}" -outform PEM -out "${chainfile}" + tmpchain="$(_mktemp)" + http_request get "$(openssl x509 -in "${certfile}" -noout -text | grep 'CA Issuers - URI:' | cut -d':' -f2-)" > "${tmpchain}" + if grep -q "BEGIN CERTIFICATE" "${tmpchain}"; then + mv "${tmpchain}" "${chainfile}" + else + openssl x509 -in "${tmpchain}" -inform DER -out "${chainfile}" -outform PEM + rm "${tmpchain}" fi echo "# CHAIN #" >&3 @@ -965,6 +1101,16 @@ 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 @@ -1031,6 +1177,14 @@ PARAM_NO_LOCK="yes" ;; + # PARAM_Usage: --lock-suffix example.com + # PARAM_Description: Suffix lockfile name with a string (useful for with -d) + --lock-suffix) + shift 1 + check_parameters "${1:-}" + PARAM_LOCKFILE_SUFFIX="${1}" + ;; + # PARAM_Usage: --ocsp # PARAM_Description: Sets option in CSR indicating OCSP stapling to be mandatory --ocsp) @@ -1099,6 +1253,7 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dehydrated-0.3.1/docs/dns-verification.md new/dehydrated-0.4.0/docs/dns-verification.md --- old/dehydrated-0.3.1/docs/dns-verification.md 2016-09-13 20:00:43.000000000 +0200 +++ new/dehydrated-0.4.0/docs/dns-verification.md 2017-02-05 15:33:17.000000000 +0100 @@ -4,7 +4,7 @@ You need a hook script that deploys the challenge to your DNS server! -The hook script (indicated in the config file or the --hook/-k command line argument) gets four arguments: an operation name (clean_challenge, deploy_challenge, or deploy_cert) and some operands for that. For deploy_challenge $2 is the domain name for which the certificate is required, $3 is a "challenge token" (which is not needed for dns-01), and $4 is a token which needs to be inserted in a TXT record for the domain. +The hook script (indicated in the config file or the --hook/-k command line argument) gets four arguments: an operation name (clean_challenge, deploy_challenge, deploy_cert, invalid_challenge or request_failure) and some operands for that. For deploy_challenge $2 is the domain name for which the certificate is required, $3 is a "challenge token" (which is not needed for dns-01), and $4 is a token which needs to be inserted in a TXT record for the domain. Typically, you will need to split the subdomain name in two, the subdomain name and the domain name separately. For example, for "my.example.com", you'll need "my" and "example.com" separately. You then have to prefix "_acme-challenge." before the subdomain name, as in "_acme-challenge.my" and set a TXT record for that on the domain (e.g. "example.com") which has the value supplied in $4 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dehydrated-0.3.1/docs/examples/config new/dehydrated-0.4.0/docs/examples/config --- old/dehydrated-0.3.1/docs/examples/config 2016-09-13 20:00:43.000000000 +0200 +++ new/dehydrated-0.4.0/docs/examples/config 2017-02-05 15:33:17.000000000 +0100 @@ -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: <unset>) +#LICENSE="" # Which challenge should be used? Currently http-01 and dns-01 are supported #CHALLENGETYPE="http-01" @@ -72,6 +75,9 @@ # Regenerate private keys instead of just signing new certificates on renewal (default: yes) #PRIVATE_KEY_RENEW="yes" +# Create an extra private key for rollover (default: no) +#PRIVATE_KEY_ROLLOVER="no" + # Which public key algorithm should be used? Supported: rsa, prime256v1 and secp384r1 #KEY_ALGO=rsa diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dehydrated-0.3.1/docs/examples/hook.sh new/dehydrated-0.4.0/docs/examples/hook.sh --- old/dehydrated-0.3.1/docs/examples/hook.sh 2016-09-13 20:00:43.000000000 +0200 +++ new/dehydrated-0.4.0/docs/examples/hook.sh 2017-02-05 15:33:17.000000000 +0100 @@ -1,6 +1,6 @@ #!/usr/bin/env bash -function deploy_challenge { +deploy_challenge() { local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}" # This hook is called once for every domain that needs to be @@ -21,7 +21,7 @@ # be found in the $TOKEN_FILENAME file. } -function clean_challenge { +clean_challenge() { local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}" # This hook is called after attempting to validate each domain, @@ -31,7 +31,7 @@ # The parameters are the same as for deploy_challenge. } -function deploy_cert { +deploy_cert() { local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}" TIMESTAMP="${6}" # This hook is called once for each certificate that has been @@ -54,7 +54,7 @@ # Timestamp when the specified certificate was created. } -function unchanged_cert { +unchanged_cert() { local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}" # This hook is called once for each certificate that is still @@ -74,4 +74,45 @@ # The path of the file containing the intermediate certificate(s). } -HANDLER=$1; shift; $HANDLER $@ +invalid_challenge() { + local DOMAIN="${1}" RESPONSE="${2}" + + # This hook is called if the challenge response has failed, so domain + # owners can be aware and act accordingly. + # + # Parameters: + # - DOMAIN + # The primary domain name, i.e. the certificate common + # name (CN). + # - RESPONSE + # The response that the verification server returned +} + +request_failure() { + local STATUSCODE="${1}" REASON="${2}" REQTYPE="${3}" + + # This hook is called when a HTTP request fails (e.g., when the ACME + # server is busy, returns an error, etc). It will be called upon any + # response code that does not start with '2'. Useful to alert admins + # about problems with requests. + # + # Parameters: + # - STATUSCODE + # The HTML status code that originated the error. + # - REASON + # The specified reason for the error. + # - REQTYPE + # The kind of request that was made (GET, POST...) +} + +exit_hook() { + # This hook is called at the end of a dehydrated command and can be used + # to do some final (cleanup or other) tasks. + + : +} + +HANDLER="$1"; shift +if [[ "${HANDLER}" =~ ^(deploy_challenge|clean_challenge|deploy_cert|unchanged_cert|invalid_challenge|request_failure|exit_hook)$ ]]; then + "$HANDLER" "$@" +fi diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dehydrated-0.3.1/docs/staging.md new/dehydrated-0.4.0/docs/staging.md --- old/dehydrated-0.3.1/docs/staging.md 2016-09-13 20:00:43.000000000 +0200 +++ new/dehydrated-0.4.0/docs/staging.md 2017-02-05 15:33:17.000000000 +0100 @@ -9,4 +9,5 @@ ```bash CA="https://acme-staging.api.letsencrypt.org/directory" +CA_TERMS="https://acme-staging.api.letsencrypt.org/terms" ``` diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dehydrated-0.3.1/docs/wellknown.md new/dehydrated-0.4.0/docs/wellknown.md --- old/dehydrated-0.3.1/docs/wellknown.md 2016-09-13 20:00:43.000000000 +0200 +++ new/dehydrated-0.4.0/docs/wellknown.md 2017-02-05 15:33:17.000000000 +0100 @@ -60,9 +60,8 @@ With Lighttpd just add this to your config and it should work in any VHost: ```lighttpd -modules += "alias" - +server.modules += ("alias") alias.url += ( - "/.well-known/acme-challenge/" => "/var/www/dehydrated/" + "/.well-known/acme-challenge/" => "/var/www/dehydrated/", ) ``` diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dehydrated-0.3.1/test.sh new/dehydrated-0.4.0/test.sh --- old/dehydrated-0.3.1/test.sh 2016-09-13 20:00:43.000000000 +0200 +++ new/dehydrated-0.4.0/test.sh 2017-02-05 15:33:17.000000000 +0100 @@ -69,7 +69,14 @@ ( mkdir -p ngrok cd ngrok - wget https://dl.ngrok.com/ngrok_2.0.19_linux_amd64.zip -O ngrok.zip + if [ "${TRAVIS_OS_NAME}" = "linux" ]; then + wget -O ngrok.zip https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip + elif [ "${TRAVIS_OS_NAME}" = "osx" ]; then + wget -O ngrok.zip https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-darwin-amd64.zip + else + echo "No ngrok for ${TRAVIS_OS_NAME}" + exit 1 + fi unzip ngrok.zip ngrok chmod +x ngrok ) @@ -97,7 +104,7 @@ # 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 +117,23 @@ _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 +144,7 @@ # 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 +192,7 @@ _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 @@ -197,7 +221,8 @@ _SUBTEST "Verifying file with full chain..." openssl x509 -in "certs/${TMP_URL}/fullchain.pem" -noout -text > /dev/null 2>> errorlog && _PASS || _FAIL _SUBTEST "Verifying certificate against CA certificate..." -(openssl verify -verbose -CAfile "certs/${TMP_URL}/fullchain.pem" -purpose sslserver "certs/${TMP_URL}/fullchain.pem" 2>&1 || true) | (grep -v ': OK$' || true) >> errorlog 2>> errorlog && _PASS || _FAIL +curl -s https://testca.kurz.pw/acme/issuer-cert | openssl x509 -inform DER -outform PEM > ca.pem +(openssl verify -verbose -CAfile "ca.pem" -purpose sslserver "certs/${TMP_URL}/fullchain.pem" 2>&1 || true) | (grep -v ': OK$' || true) >> errorlog 2>> errorlog && _PASS || _FAIL _CHECK_ERRORLOG # Revoke certificate using certificate key @@ -209,6 +234,26 @@ _CHECK_FILE "certs/${TMP_URL}/${REAL_CERT}-revoked" _CHECK_ERRORLOG +# Enable private key renew +echo 'PRIVATE_KEY_RENEW="yes"' >> config +echo 'PRIVATE_KEY_ROLLOVER="yes"' >> config + +# Check if Rolloverkey creation works +_TEST "Testing Rolloverkeys..." +_SUBTEST "First Run: Creating rolloverkey" +./dehydrated --cron --domain "${TMP2_URL}" > tmplog 2> errorlog || _FAIL "Script execution failed" +CERT_ROLL_HASH=$(openssl rsa -in certs/${TMP2_URL}/privkey.roll.pem -outform DER -pubout 2>/dev/null | openssl sha -sha256) +_CHECK_LOG "Generating private key" +_CHECK_LOG "Generating private rollover key" +_SUBTEST "Second Run: Force Renew, Use rolloverkey" +./dehydrated --cron --force --domain "${TMP2_URL}" > tmplog 2> errorlog || _FAIL "Script execution failed" +CERT_NEW_HASH=$(openssl rsa -in certs/${TMP2_URL}/privkey.pem -outform DER -pubout 2>/dev/null | openssl sha -sha256) +_CHECK_LOG "Generating private key" +_CHECK_LOG "Moving Rolloverkey into position" +_SUBTEST "Verifying Hash Rolloverkey and private key second run" +[[ "${CERT_ROLL_HASH}" = "${CERT_NEW_HASH}" ]] && _PASS || _FAIL +_CHECK_ERRORLOG + # Test cleanup command _TEST "Cleaning up certificates" ./dehydrated --cleanup > tmplog 2> errorlog || _FAIL "Script execution failed"