From 0f69481e2b5f4550b3cbc0e41392ebaf10e49fd1 Mon Sep 17 00:00:00 2001 From: Lukas Schauer Date: Sun, 28 Jan 2018 05:02:18 +0100 Subject: [PATCH] rewrote challenge validation to iterate over authorizations instead of altnames (fixes some acmev2 validation edgecases), also removed broken test-script (for now) --- .travis.yml | 14 --- dehydrated | 223 +++++++++++++++++++------------------ docs/hook_chain.md | 11 ++ test.sh | 266 --------------------------------------------- 4 files changed, 130 insertions(+), 384 deletions(-) delete mode 100644 .travis.yml delete mode 100755 test.sh diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b4e42e5..0000000 --- a/.travis.yml +++ /dev/null @@ -1,14 +0,0 @@ -sudo: false -language: shell - -os: - - linux - - osx - -cache: - directories: - - ngrok - -script: - - export CI="true" - - ./test.sh diff --git a/dehydrated b/dehydrated index 4a162bc..6c3af64 100755 --- a/dehydrated +++ b/dehydrated @@ -407,6 +407,13 @@ get_json_array_value() { sed -n "${filter}" } +# Get sub-dictionary from json +get_json_dict_value() { + local filter + filter=$(printf 's/.*"%s": *{\([^}]*\)}.*/\\1/p' "$1") + sed -n "${filter}" +} + # Get integer value from json get_json_int_value() { local filter @@ -604,75 +611,86 @@ sign_csr() { fi if [[ -n "${ZSH_VERSION:-}" ]]; then - local -A challenge_altnames challenge_uris challenge_tokens keyauths deploy_args authorization + local -A challenge_identifiers challenge_uris challenge_tokens authorizations keyauths deploy_args else - local -a challenge_altnames challenge_uris challenge_tokens keyauths deploy_args authorization + local -a challenge_identifiers challenge_uris challenge_tokens authorizations keyauths deploy_args fi - if [[ ${API} -eq 2 ]]; then - # APIv2 + # Initial step: Find which authorizations we're dealing with + if [[ ${API} -eq 2 ]]; then + # Request new order and store authorization URIs for altname in ${altnames}; do challenge_identifiers+="$(printf '{"type": "dns", "value": "%s"}, ' "${altname}")" done challenge_identifiers="[${challenge_identifiers%, }]" - echo " + Requesting challenges for ${altnames}..." + echo " + Requesting new certificate order from CA..." result="$(signed_request "${CA_NEW_ORDER}" '{"identifiers": '"${challenge_identifiers}"'}')" - authorizations="$(echo ${result} | get_json_array_value authorizations)" + order_authorizations="$(echo ${result} | get_json_array_value authorizations)" finalize="$(echo "${result}" | get_json_string_value finalize)" local idx=0 - for uris in ${authorizations}; do - authorization[${idx}]="${uris}" + for uri in ${order_authorizations}; do + authorizations[${idx}]="$(echo "${uri}" | _sed -e 's/\"(.*)".*/\1/')" + idx=$((idx+1)) + done + echo " + Received ${idx} authorizations URLs from the CA" + else + # Copy $altnames to $authorizations (just doing this to reduce duplicate code later on) + local idx=0 + for altname in ${altnames}; do + authorizations[${idx}]="${altname}" idx=$((idx+1)) done - - unset challenge_identifiers authorizations fi - local idx=0; idy=-1 - # Request challenges - for altname in ${altnames}; do - idy=$((idy+1)) - if [[ ${API} -eq 1 ]]; then - # Ask the acme-server for new challenge token and extract them from the resulting json block - echo " + Requesting challenge for ${altname}..." - response="$(signed_request "${CA_NEW_AUTHZ}" '{"resource": "new-authz", "identifier": {"type": "dns", "value": "'"${altname}"'"}}' | clean_json)" + # Check if authorizations are valid and gather challenge information for pending authorizations + local idx=0 + for authorization in ${authorizations[*]}; do + if [[ "${API}" -eq 2 ]]; then + # Receive authorization ($authorization is authz uri) + response="$(http_request get "$(echo "${authorization}" | _sed -e 's/\"(.*)".*/\1/')" | clean_json)" + identifier="$(echo "${response}" | get_json_dict_value identifier | get_json_string_value value)" + echo " + Handling authorization for ${identifier}" else - echo " + Handling challenge for ${altname}..." - uris="$(<<<"${authorization[${idy}]}" _sed -e 's/\"(.*)".*/\1/')" - response="$(http_request get "${uris}" | clean_json)" + # Request new authorization ($authorization is altname) + identifier="${authorization}" + echo " + Requesting authorization for ${identifier}..." + response="$(signed_request "${CA_NEW_AUTHZ}" '{"resource": "new-authz", "identifier": {"type": "dns", "value": "'"${identifier}"'"}}' | clean_json)" fi - challenge_status="$(printf '%s' "${response}" | rm_json_arrays | get_json_string_value status)" - if [ "${challenge_status}" = "valid" ] && [ ! "${PARAM_FORCE:-no}" = "yes" ]; then - echo " + Already validated!" - continue + # Check if authorization has already been validated + if [ "$(echo "${response}" | sed -E 's/"challenges": \[\{.*\}\]//' | get_json_string_value status)" = "valid" ] && [ ! "${PARAM_FORCE:-no}" = "yes" ]; then + echo " + Found valid authorization for ${identifier}" + continue fi - challenges="$(printf '%s\n' "${response}" | sed -n 's/.*\("challenges":[^\[]*\[[^]]*]\).*/\1/p')" - challenge="$(<<<"${challenges}" _sed -e 's/^[^\[]+\[(.+)\]$/\1/' -e 's/\}(, (\{)|(\]))/}\'$'\n''\2/g' | grep \""${CHALLENGETYPE}"\")" - - challenge_token="$(printf '%s' "${challenge}" | get_json_string_value token | _sed 's/[^A-Za-z0-9_\-]/_/g')" - if [[ ${API} -eq 1 ]]; then - challenge_uri="$(printf '%s' "${challenge}" | get_json_string_value uri)" - else - challenge_uri="$(printf '%s' "${challenge}" | get_json_string_value url)" + # Find challenge in authorization + challenges="$(echo "${response}" | _sed 's/.*"challenges": \[(\{.*\})\].*/\1/')" + challenge="$(<<<"${challenges}" _sed -e 's/^[^\[]+\[(.+)\]$/\1/' -e 's/\}(, (\{)|(\]))/}\'$'\n''\2/g' | grep \""${CHALLENGETYPE}"\" || true)" + if [ -z "${challenge}" ]; then + allowed_validations="$(grep -Eo '"type": "[^"]+"' <<< "${challenges}" | grep -Eo ' "[^"]+"' | _sed -e 's/"//g' -e 's/^ //g')" + _exiterr "Validating this certificate is not possible using ${CHALLENGETYPE}. Possible validation methods are: ${allowed_validations}" fi - if [[ -z "${challenge_token}" ]] || [[ -z "${challenge_uri}" ]]; then - _exiterr "Can't retrieve challenges (${response})" + # Gather challenge information + challenge_identifier[${idx}]="${identifier}" + challenge_tokens[${idx}]="$(echo "${challenge}" | get_json_string_value token)" + if [[ ${API} -eq 2 ]]; then + challenge_uris[${idx}]="$(echo "${challenge}" | get_json_string_value url)" + else + challenge_uris[${idx}]="$(echo "${challenge}" | get_json_string_value uri)" fi - # Challenge response consists of the challenge token and the thumbprint of our public certificate - keyauth="${challenge_token}.${thumbprint}" + # Prepare challenge tokens and deployment parameters + keyauth="${challenge_tokens[${idx}]}.${thumbprint}" case "${CHALLENGETYPE}" in "http-01") # Store challenge response in well-known location and make world-readable (so that a webserver can access it) - printf '%s' "${keyauth}" > "${WELLKNOWN}/${challenge_token}" - chmod a+r "${WELLKNOWN}/${challenge_token}" + printf '%s' "${keyauth}" > "${WELLKNOWN}/${challenge_tokens[${idx}]}" + chmod a+r "${WELLKNOWN}/${challenge_tokens[${idx}]}" keyauth_hook="${keyauth}" ;; "dns-01") @@ -680,89 +698,86 @@ sign_csr() { keyauth_hook="$(printf '%s' "${keyauth}" | "${OPENSSL}" dgst -sha256 -binary | urlbase64)" ;; esac - - challenge_altnames[${idx}]="${altname}" - challenge_uris[${idx}]="${challenge_uri}" keyauths[${idx}]="${keyauth}" - challenge_tokens[${idx}]="${challenge_token}" - # Note: assumes args will never have spaces! - deploy_args[${idx}]="${altname} ${challenge_token} ${keyauth_hook}" + deploy_args[${idx}]="${identifier} ${challenge_tokens[${idx}]} ${keyauth_hook}" + idx=$((idx+1)) done - challenge_count="${idx}" + local num_pending_challenges=${idx} + echo " + ${num_pending_challenges} pending challenge(s)" - # Wait for hook script to deploy the challenges if used - if [[ ${challenge_count} -ne 0 ]]; then + # Detect duplicate challenge identifiers + if [ "${HOOK_CHAIN}" = "yes" ] && [ -n "$(tr ' ' '\n' <<< "${challenge_identifier[*]}" | sort | uniq -d)" ]; then + echo "!! Disabling HOOK_CHAIN for this certificate (see https://dehydrated.de/docs/hook_chain.md#problem-with-wildcard-certificates for more information)" + HOOK_CHAIN=no + fi + + # Deploy challenge tokens using chained hook + if [[ ${num_pending_challenges} -ne 0 ]]; then # shellcheck disable=SC2068 - [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" = "yes" ]] && "${HOOK}" "deploy_challenge" ${deploy_args[@]} + if [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" = "yes" ]]; then + echo " + Deploying challenge tokens..." + "${HOOK}" "deploy_challenge" ${deploy_args[@]} + fi fi - # Respond to challenges - reqstatus="valid" - idx=0 - if [ ${challenge_count} -ne 0 ]; then - for altname in "${challenge_altnames[@]:0}"; do - challenge_token="${challenge_tokens[${idx}]}" - keyauth="${keyauths[${idx}]}" + # Validate pending challenges + local idx=0 + while [ ${idx} -lt ${num_pending_challenges} ]; do + echo " + Responding to challenge for ${challenge_identifier[${idx}]} authorization..." - # Wait for hook script to deploy the challenge if used - # shellcheck disable=SC2086 - [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" != "yes" ]] && "${HOOK}" "deploy_challenge" ${deploy_args[${idx}]} + # Run hook script to deploy the challenge token + if [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" != "yes" ]]; then + "${HOOK}" "deploy_challenge" ${deploy_args[${idx}]} + fi - # Ask the acme-server to verify our challenge and wait until it is no longer pending - echo " + Responding to challenge for ${altname}..." - if [[ ${API} -eq 1 ]]; then - result="$(signed_request "${challenge_uris[${idx}]}" '{"resource": "challenge", "keyAuthorization": "'"${keyauth}"'"}' | clean_json)" - else - result="$(signed_request "${challenge_uris[${idx}]}" '{"keyAuthorization": "'"${keyauth}"'"}' | clean_json)" - fi + # Ask the acme-server to verify our challenge and wait until it is no longer pending + if [[ ${API} -eq 1 ]]; then + result="$(signed_request "${challenge_uris[${idx}]}" '{"resource": "challenge", "keyAuthorization": "'"${keyauths[${idx}]}"'"}' | clean_json)" + else + result="$(signed_request "${challenge_uris[${idx}]}" '{"keyAuthorization": "'"${keyauths[${idx}]}"'"}' | clean_json)" + fi - reqstatus="$(printf '%s\n' "${result}" | get_json_string_value status)" + reqstatus="$(printf '%s\n' "${result}" | get_json_string_value status)" - while [[ "${reqstatus}" = "pending" ]]; do - sleep 1 - if [[ ${API} -eq 1 ]]; then - result="$(http_request get "${challenge_uris[${idx}]}")" - else - result="$(http_request get "${challenge_uris[${idx}]}")" - fi - reqstatus="$(printf '%s\n' "${result}" | get_json_string_value status)" - done + 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}" + [[ "${CHALLENGETYPE}" = "http-01" ]] && rm -f "${WELLKNOWN}/${challenge_tokens[${idx}]}" - # 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)) + # Run hook script to clean the challenge token + if [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" != "yes" ]]; 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}" - break - fi - done - fi + if [[ "${reqstatus}" = "valid" ]]; then + echo " + Challenge is valid!" + else + [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" != "yes" ]] && "${HOOK}" "invalid_challenge" "${altname}" "${result}" + break + fi + done - # Wait for hook script to clean the challenges if used - # shellcheck disable=SC2068 - if [[ ${challenge_count} -ne 0 ]]; then + if [[ ${num_pending_challenges} -ne 0 ]]; then + # Clean challenge tokens using chained hook [[ -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" ]] && [[ ${challenge_count} -ne 0 ]]; then - while [ ${idx} -lt ${#challenge_tokens[@]} ]; do - rm -f "${WELLKNOWN}/${challenge_tokens[${idx}]}" - idx=$((idx+1)) - done - fi + # Clean remaining challenge tokens if validation has failed + if [[ "${reqstatus}" != "valid" ]]; then + if [[ "${CHALLENGETYPE}" = "http-01" ]] && [[ ${num_pending_challenges} -ne 0 ]]; then + while [ ${idx} -lt ${num_pending_challenges} ]; do + rm -f "${WELLKNOWN}/${challenge_tokens[${idx}]}" + idx=$((idx+1)) + done + fi - _exiterr "Challenge is invalid! (returned: ${reqstatus}) (result: ${result})" + _exiterr "Challenge is invalid! (returned: ${reqstatus}) (result: ${result})" + fi fi # Finally request certificate from the acme-server and store it in cert-${timestamp}.pem and link from cert.pem diff --git a/docs/hook_chain.md b/docs/hook_chain.md index c025783..5a3da12 100644 --- a/docs/hook_chain.md +++ b/docs/hook_chain.md @@ -61,3 +61,14 @@ HOOK: deploy_cert lukas.im /etc/dehydrated/certs/lukas.im/privkey.pem /etc/dehyd + Done! ``` +# Problem with wildcard certificates + +For wildcard certificates the upper level domain is used for verification, e.g. +`*.foo.example.com` will be verified at `foo.example.com`. + +In cases where both `foo.example.com` and `*.foo.example.com` would have to be +validated there would be a conflict since both will have different tokens but +both are expected to be resolved under `_acme-challenge.foo.example.com`. + +If dehydrated detects this kind of configuration it will automatically fall back +to non-chaining behaviour (until the next certificate). diff --git a/test.sh b/test.sh deleted file mode 100755 index e68f22e..0000000 --- a/test.sh +++ /dev/null @@ -1,266 +0,0 @@ -#!/usr/bin/env bash - -# Fail early -set -eu -o pipefail - -# Check if running in CI environment -if [[ ! "${CI:-false}" == "true" ]]; then - echo "ERROR: Not running in CI environment!" - exit 1 -fi - -_TEST() { - echo - echo "${1} " -} -_SUBTEST() { - echo -n " + ${1} " -} -_PASS() { - echo -e "[\u001B[32mPASS\u001B[0m]" -} -_FAIL() { - echo -e "[\u001B[31mFAIL\u001B[0m]" - echo - echo "Problem: ${@}" - echo - echo "STDOUT:" - cat tmplog - echo - echo "STDERR:" - cat errorlog - exit 1 -} -_CHECK_FILE() { - _SUBTEST "Checking if file '${1}' exists..." - if [[ -e "${1}" ]]; then - _PASS - else - _FAIL "Missing file: ${1}" - fi -} -_CHECK_LOG() { - _SUBTEST "Checking if log contains '${1}'..." - if grep -- "${1}" tmplog > /dev/null; then - _PASS - else - _FAIL "Missing in log: ${1}" - fi -} -_CHECK_NOT_LOG() { - _SUBTEST "Checking if log doesn't contain '${1}'..." - if grep -- "${1}" tmplog > /dev/null; then - _FAIL "Found in log: ${1}" - else - _PASS - fi -} -_CHECK_ERRORLOG() { - _SUBTEST "Checking if errorlog is empty..." - if [[ -z "$(cat errorlog)" ]]; then - _PASS - else - _FAIL "Non-empty errorlog" - fi -} - -# If not found (should be cached in travis) download ngrok -if [[ ! -e "ngrok/ngrok" ]]; then - ( - mkdir -p ngrok - cd ngrok - 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 - ) -fi - -# Run ngrok and grab temporary url from logfile -ngrok/ngrok http 8080 --log stdout --log-format logfmt --log-level debug > tmp.log & -ngrok/ngrok http 8080 --log stdout --log-format logfmt --log-level debug > tmp2.log & -ngrok/ngrok http 8080 --log stdout --log-format logfmt --log-level debug > tmp3.log & -sleep 2 -TMP_URL="$(grep -Eo "Hostname:[a-z0-9]+.ngrok.io" tmp.log | head -1 | cut -d':' -f2)" -TMP2_URL="$(grep -Eo "Hostname:[a-z0-9]+.ngrok.io" tmp2.log | head -1 | cut -d':' -f2)" -TMP3_URL="$(grep -Eo "Hostname:[a-z0-9]+.ngrok.io" tmp3.log | head -1 | cut -d':' -f2)" -if [[ -z "${TMP_URL}" ]] || [[ -z "${TMP2_URL}" ]] || [[ -z "${TMP3_URL}" ]]; then - echo "Couldn't get an url from ngrok, not a dehydrated bug, tests can't continue." - exit 1 -fi - -# Run python webserver in .acme-challenges directory to serve challenge responses -mkdir -p .acme-challenges/.well-known/acme-challenge -( - cd .acme-challenges - python -m SimpleHTTPServer 8080 > /dev/null 2> /dev/null -) & - -# Generate config and create empty domains.txt -echo 'CA="https://testca.kurz.pw/directory"' > 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 - -# Check if help command is working -_TEST "Checking if help command is working..." -./dehydrated --help > tmplog 2> errorlog || _FAIL "Script execution failed" -_CHECK_LOG "Default command: help" -_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" -_CHECK_LOG "Registering account key" -_CHECK_FILE accounts/*/account_key.pem -_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}" --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}" -_CHECK_LOG "Requesting challenge for ${TMP2_URL}" -_CHECK_LOG "Challenge is valid!" -_CHECK_LOG "Creating fullchain.pem" -_CHECK_LOG "Done!" -_CHECK_ERRORLOG -mv tmp_config config - -# Add third domain to command-lime, should force renewal. -_TEST "Run in cron mode again, this time adding third domain, should force renewal." -./dehydrated --cron --domain "${TMP_URL}" --domain "${TMP2_URL}" --domain "${TMP3_URL}" > tmplog 2> errorlog || _FAIL "Script execution failed" -_CHECK_LOG "Domain name(s) are not matching!" -_CHECK_LOG "Forcing renew." -_CHECK_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 "Creating fullchain.pem" -_CHECK_LOG "Done!" -_CHECK_ERRORLOG - -# Prepare domains.txt -# Modify TMP3_URL to be uppercase to check for upper-lower-case mismatch bugs -echo "${TMP_URL} ${TMP2_URL} $(tr 'a-z' 'A-Z' <<<"${TMP3_URL}")" >> domains.txt - -# Run in cron mode again (should find a non-expiring certificate and do nothing) -_TEST "Run in cron mode again, this time with domain in domains.txt, should find non-expiring certificate" -./dehydrated --cron > tmplog 2> errorlog || _FAIL "Script execution failed" -_CHECK_LOG "Checking domain name(s) of existing cert... unchanged." -_CHECK_LOG "Skipping renew" -_CHECK_ERRORLOG - -# Disable private key renew -echo 'PRIVATE_KEY_RENEW="no"' >> config - -# Run in cron mode one last time, with domain in domains.txt and force-resign (should find certificate, resign anyway, and not generate private key) -_TEST "Run in cron mode one last time, with domain in domains.txt and force-resign" -./dehydrated --cron --force > tmplog 2> errorlog || _FAIL "Script execution failed" -_CHECK_LOG "Checking domain name(s) of existing cert... unchanged." -_CHECK_LOG "Ignoring because renew was forced!" -_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 "Already validated!" -_CHECK_LOG "Creating fullchain.pem" -_CHECK_LOG "Done!" -_CHECK_ERRORLOG - -# Check if signcsr command is working -_TEST "Running signcsr command" -./dehydrated --signcsr certs/${TMP_URL}/cert.csr > tmplog 2> errorlog || _FAIL "Script execution failed" -_CHECK_LOG "BEGIN CERTIFICATE" -_CHECK_LOG "END CERTIFICATE" -_CHECK_NOT_LOG "ERROR" - -# Check if renewal works -_TEST "Run in cron mode again, to check if renewal works" -echo 'RENEW_DAYS="300"' >> config -./dehydrated --cron > tmplog 2> errorlog || _FAIL "Script execution failed" -_CHECK_LOG "Checking domain name(s) of existing cert... unchanged." -_CHECK_LOG "Renewing!" -_CHECK_ERRORLOG - -# Check if certificate is valid in various ways -_TEST "Verifying certificate..." -_SUBTEST "Verifying certificate on its own..." -openssl x509 -in "certs/${TMP_URL}/cert.pem" -noout -text > tmplog 2> errorlog && _PASS || _FAIL -_CHECK_LOG "CN=${TMP_URL}" -_CHECK_LOG "${TMP2_URL}" -_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..." -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 -_TEST "Revoking certificate..." -./dehydrated --revoke "certs/${TMP_URL}/cert.pem" --privkey "certs/${TMP_URL}/privkey.pem" > tmplog 2> errorlog || _FAIL "Script execution failed" -REAL_CERT="$(readlink -n "certs/${TMP_URL}/cert.pem")" -_CHECK_LOG "Revoking certs/${TMP_URL}/${REAL_CERT}" -_CHECK_LOG "Done." -_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" -_CHECK_LOG "Moving unused file to archive directory: ${TMP_URL}/cert-" -_CHECK_LOG "Moving unused file to archive directory: ${TMP_URL}/chain-" -_CHECK_LOG "Moving unused file to archive directory: ${TMP_URL}/fullchain-" -_CHECK_ERRORLOG - -# All done -exit 0 -- 2.47.2