Hello community, here is the log from the commit of package dehydrated for openSUSE:Factory checked in at 2018-01-16 09:43:17 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/dehydrated (Old) and /work/SRC/openSUSE:Factory/.dehydrated.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "dehydrated" Tue Jan 16 09:43:17 2018 rev:6 rq:565804 version:0.5.0 Changes: -------- --- /work/SRC/openSUSE:Factory/dehydrated/dehydrated.changes 2017-10-29 20:24:21.535360514 +0100 +++ /work/SRC/openSUSE:Factory/.dehydrated.new/dehydrated.changes 2018-01-16 09:43:17.839086345 +0100 @@ -1,0 +2,38 @@ +Mon Jan 15 12:15:07 UTC 2018 - [email protected] + +- Remove redundant noarch entries. They cause an error in RPM 4.14. + +------------------------------------------------------------------- +Mon Jan 15 11:29:11 UTC 2018 - [email protected] + +- Updated dehydrated to 0.5.0 + + This removes the following patches and files, which are now part of the + upstream package: + * 0001-Add-optional-user-and-group-configuration.patch + * 0002-use-nullglob-disable-warning-on-empty-CONFIG_D-direc.patch + * dehydrated.1: the man page has been adopted by upstream + + Starting with this version, upstream introduced signed releases, which + is now being used for source validation. + + Upstream changes: + + Changed + + * Certificate chain is now cached (CHAINCACHE) + * OpenSSL binary path is now configurable (OPENSSL) + * Cleanup now also moves revoked certificates + + Added + + * New feature for updating contact information (--account) + * Allow automatic cleanup on exit (AUTO_CLEANUP) + * Initial support for fetching OCSP status to be used for OCSP stapling + (OCSP_FETCH) + * Certificates can now have aliases to create multiple certificates with + identical set of domains (see --alias and domains.txt documentation) + * Allow dehydrated to run as specified user (/group). This was already + available previously as a patch to this package. + +------------------------------------------------------------------- Old: ---- 0001-Add-optional-user-and-group-configuration.patch 0002-use-nullglob-disable-warning-on-empty-CONFIG_D-direc.patch dehydrated-0.4.0.tar.gz dehydrated.1 New: ---- dehydrated-0.5.0.tar.gz dehydrated-0.5.0.tar.gz.asc dehydrated.keyring ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ dehydrated.spec ++++++ --- /var/tmp/diff_new_pack.039Syd/_old 2018-01-16 09:43:18.735044407 +0100 +++ /var/tmp/diff_new_pack.039Syd/_new 2018-01-16 09:43:18.735044407 +0100 @@ -1,7 +1,7 @@ # # spec file for package dehydrated # -# Copyright (c) 2017 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -46,7 +46,7 @@ %endif Name: dehydrated -Version: 0.4.0 +Version: 0.5.0 Release: 0 Summary: A client for signing certificates with an ACME server License: MIT @@ -60,14 +60,11 @@ Source5: dehydrated.tmpfiles.d Source6: dehydrated.service.in Source7: dehydrated.timer -Source8: dehydrated.1 Source9: README.SUSE Source10: README.Fedora Source11: README.hooks -# Patch submitted to upstream -Patch1: 0001-Add-optional-user-and-group-configuration.patch -# Patch from upstream -Patch2: 0002-use-nullglob-disable-warning-on-empty-CONFIG_D-direc.patch +Source12: %{name}-%{version}.tar.gz.asc +Source13: %{name}.keyring BuildRequires: %{_apache} Requires: coreutils Requires: curl @@ -78,7 +75,6 @@ Requires(pre): %{_sbindir}/useradd Obsoletes: letsencrypt.sh < %{version} Provides: letsencrypt.sh = %{version} -BuildArch: noarch %if %{with lighttpd} BuildRequires: lighttpd %endif @@ -135,7 +131,6 @@ %if ! 0%{?suse_version} Requires: mod_ssl %endif -BuildArch: noarch %description %{_apache} This adds a configuration file for dehydrated's acme-challenge to Apache. @@ -148,7 +143,6 @@ Requires: nginx Obsoletes: letsencrypt.sh-nginx < %{version} Provides: letsencrypt.sh-nginx = %{version} -BuildArch: noarch %description nginx This adds a configuration file for dehydrated's acme-challenge to nginx. @@ -160,7 +154,6 @@ Group: Productivity/Networking/Security Requires: %{name} Requires: lighttpd -BuildArch: noarch %description lighttpd This adds a configuration file for dehydrated's acme-challenge to lighttpd. @@ -189,8 +182,6 @@ %prep %setup -q -%patch1 -p1 -%patch2 -p1 cp %{SOURCE9} . cp %{SOURCE10} . @@ -198,13 +189,13 @@ %install # sensitive keys -mkdir -p %{buildroot}%{_home}/{accounts,certs} +mkdir -p %{buildroot}%{_home}/{accounts,certs,chains} mkdir -p %{buildroot}%{_sbindir} mkdir -p %{buildroot}%{_mandir}/man1 mkdir -p %{buildroot}%{_home}/config.d mkdir -p %{buildroot}%{_postrunhooks} -cat %{SOURCE8} | gzip > %{buildroot}%{_mandir}/man1/dehydrated.1.gz +cat dehydrated.1 | gzip > %{buildroot}%{_mandir}/man1/dehydrated.1.gz # Silence E: env-script-interpreter find \( -name \*.sh -o -name dehydrated \) -exec sed -i "s,#!/usr/bin/env bash,#!$(command -v bash),g" {} \; @@ -262,6 +253,7 @@ %attr(750,root,%{_user}) %dir %{_sysconfdir}/dehydrated %attr(700,%{_user},%{_user}) %dir %{_sysconfdir}/dehydrated/accounts %attr(700,%{_user},%{_user}) %dir %{_sysconfdir}/dehydrated/certs +%attr(700,%{_user},%{_user}) %dir %{_sysconfdir}/dehydrated/chains %config(noreplace) %attr(640,root,%{_user}) %{_sysconfdir}/dehydrated/config %config(noreplace) %attr(750,root,%{_user}) %{_sysconfdir}/dehydrated/config.d %config(noreplace) %attr(640,root,%{_user}) %{_sysconfdir}/dehydrated/domains.txt ++++++ README.SUSE ++++++ --- /var/tmp/diff_new_pack.039Syd/_old 2018-01-16 09:43:18.783042160 +0100 +++ /var/tmp/diff_new_pack.039Syd/_new 2018-01-16 09:43:18.787041973 +0100 @@ -110,7 +110,7 @@ Aqcuisition through DNS (dns-01) ================================ -Tnis is mostly useful under these conditions +This is mostly useful under these conditions 1. Your hosts are not directly exposed to the internet 2. Your host names are part of a public DNS zone visible on the internet. ++++++ dehydrated-0.4.0.tar.gz -> dehydrated-0.5.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dehydrated-0.4.0/.gitignore new/dehydrated-0.5.0/.gitignore --- old/dehydrated-0.4.0/.gitignore 2017-02-05 15:33:17.000000000 +0100 +++ new/dehydrated-0.5.0/.gitignore 2018-01-13 20:08:12.000000000 +0100 @@ -6,3 +6,4 @@ certs/* archive/* accounts/* +chains/* diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dehydrated-0.4.0/CHANGELOG new/dehydrated-0.5.0/CHANGELOG --- old/dehydrated-0.4.0/CHANGELOG 2017-02-05 15:33:17.000000000 +0100 +++ new/dehydrated-0.5.0/CHANGELOG 2018-01-13 20:08:12.000000000 +0100 @@ -1,9 +1,18 @@ # Change Log This file contains a log of major changes in dehydrated -## [x.x.x] - xxxx-xx-xx +## [0.5.0] - 2018-01-13 ## Changed -- ... +- Certificate chain is now cached (CHAINCACHE) +- OpenSSL binary path is now configurable (OPENSSL) +- Cleanup now also moves revoked certificates + +## Added +- New feature for updating contact information (--account) +- Allow automatic cleanup on exit (AUTO_CLEANUP) +- Initial support for fetching OCSP status to be used for OCSP stapling (OCSP_FETCH) +- Certificates can now have aliases to create multiple certificates with identical set of domains (see --alias and domains.txt documentation) +- Allow dehydrated to run as specified user (/group) ## [0.4.0] - 2017-02-05 ## Changed diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dehydrated-0.4.0/README.md new/dehydrated-0.5.0/README.md --- old/dehydrated-0.4.0/README.md 2017-02-05 15:33:17.000000000 +0100 +++ new/dehydrated-0.5.0/README.md 2018-01-13 20:08:12.000000000 +0100 @@ -1,4 +1,4 @@ -# dehydrated [](https://travis-ci.org/lukas2511/dehydrated) +# dehydrated  @@ -33,7 +33,7 @@ - `/etc/dehydrated/config` - `/usr/local/etc/dehydrated/config` - The current working directory of your shell -- The directory from which dehydrated was ran +- The directory from which dehydrated was run 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. @@ -46,8 +46,10 @@ Default command: help Commands: + --version (-v) Print version information --register Register account key - --cron (-c) Sign/renew non-existant/changed/expiring certificates. + --account Update account contact information + --cron (-c) Sign/renew non-existent/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 --cleanup (-gc) Move unused certificate files to archive directory @@ -60,6 +62,7 @@ --ipv4 (-4) Resolve names to IPv4 addresses only --ipv6 (-6) Resolve names to IPv6 addresses only --domain (-d) domain.tld Use specified domain name(s) instead of domains.txt entry (one certificate!) + --alias certalias Use specified name for certificate directory (and per-certificate config) instead of the primary domain (only used if --domain is specified) --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!) @@ -72,3 +75,34 @@ --challenge (-t) http-01|dns-01 Which challenge should be used? Currently http-01 and dns-01 are supported --algo (-a) rsa|prime256v1|secp384r1 Which public key algorithm should be used? Supported: rsa, prime256v1 and secp384r1 ``` + +## Donate + +I'm having fun developing dehydrated, but it takes time, and time is money, at least that's what I've been told. + +I will definitively continue developing dehydrated for free, but if you want to support me you can do so using the following ways: + +### PayPal + +[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=23P9DSJBTY7C8) + +### BitCoin + +Send bitcoins to 12487bHxcrREffTGwUDnoxF1uYxCA7ztKK + +### Server + +I'm still planning on building a bigger testing-suite for dehydrated, it would be really cool to have a big(ish) server running +in a datacenter somewhere without having to pay for it... If you are a server provider and can offer me a (dedicated!) machine, +please contact me at `[email protected]`. + +### Other ways + +I always like to play around with modern(ish) network and computer gear, 10G switches and stuff, modern ARM boards +(but please not that Raspberry Pi rubbish), tiny PCs for routing, etc. + +If you have something that seems of value and that you don't need anymore feel free to contact me at +`[email protected]`. + +Also here is my [Amazon Wishlist](http://www.amazon.de/registry/wishlist/1TUCFJK35IO4Q) :) + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dehydrated-0.4.0/dehydrated new/dehydrated-0.5.0/dehydrated --- old/dehydrated-0.4.0/dehydrated 2017-02-05 15:33:17.000000000 +0100 +++ new/dehydrated-0.5.0/dehydrated 2018-01-13 20:08:12.000000000 +0100 @@ -1,16 +1,20 @@ #!/usr/bin/env bash # dehydrated by lukas2511 -# Source: https://github.com/lukas2511/dehydrated +# Source: https://dehydrated.de # # This script is licensed under The MIT License (see LICENSE for more information). set -e set -u set -o pipefail -[[ -n "${ZSH_VERSION:-}" ]] && set -o SH_WORD_SPLIT && set +o FUNCTION_ARGZERO +[[ -n "${ZSH_VERSION:-}" ]] && set -o SH_WORD_SPLIT && set +o FUNCTION_ARGZERO && set -o NULL_GLOB +[[ -z "${ZSH_VERSION:-}" ]] && shopt -s nullglob + umask 077 # paranoid umask, we're creating private keys +VERSION="0.5.0" + # Find directory in which this script is stored by traversing all symbolic links SOURCE="${0}" while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink @@ -21,6 +25,7 @@ SCRIPTDIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" BASEDIR="${SCRIPTDIR}" +ORIGARGS="$@" # Create (identifiable) temporary files _mktemp() { @@ -31,7 +36,7 @@ # Check for script dependencies check_dependencies() { # just execute some dummy and/or version commands to see if required tools exist and are actually usable - openssl version > /dev/null 2>&1 || _exiterr "This script requires an openssl binary." + "${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." command -v mktemp > /dev/null 2>&1 || _exiterr "This script requires mktemp." @@ -77,16 +82,16 @@ # verify configuration values verify_config() { - [[ "${CHALLENGETYPE}" =~ (http-01|dns-01) ]] || _exiterr "Unknown challenge type ${CHALLENGETYPE}... can not continue." + [[ "${CHALLENGETYPE}" == "http-01" || "${CHALLENGETYPE}" == "dns-01" ]] || _exiterr "Unknown challenge type ${CHALLENGETYPE}... cannot continue." if [[ "${CHALLENGETYPE}" = "dns-01" ]] && [[ -z "${HOOK}" ]]; then - _exiterr "Challenge type dns-01 needs a hook script for deployment... can not continue." + _exiterr "Challenge type dns-01 needs a hook script for deployment... cannot continue." fi 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." + [[ "${KEY_ALGO}" == "rsa" || "${KEY_ALGO}" == "prime256v1" || "${KEY_ALGO}" == "secp384r1" ]] || _exiterr "Unknown public key algorithm ${KEY_ALGO}... cannot continue." if [[ -n "${IP_VERSION}" ]]; then - [[ "${IP_VERSION}" = "4" || "${IP_VERSION}" = "6" ]] || _exiterr "Unknown IP version ${IP_VERSION}... can not continue." + [[ "${IP_VERSION}" = "4" || "${IP_VERSION}" = "6" ]] || _exiterr "Unknown IP version ${IP_VERSION}... cannot continue." fi } @@ -111,6 +116,7 @@ ACCOUNTDIR= CHALLENGETYPE="http-01" CONFIG_D= + CURL_OPTS= DOMAINS_D= DOMAINS_TXT= HOOK= @@ -121,11 +127,17 @@ PRIVATE_KEY_RENEW="yes" PRIVATE_KEY_ROLLOVER="no" KEY_ALGO=rsa - OPENSSL_CNF="$(openssl version -d | cut -d\" -f2)/openssl.cnf" + OPENSSL=openssl + OPENSSL_CNF= CONTACT_EMAIL= LOCKFILE= OCSP_MUST_STAPLE="no" + OCSP_FETCH="no" IP_VERSION= + CHAINCACHE= + AUTO_CLEANUP="no" + DEHYDRATED_USER= + DEHYDRATED_GROUP= if [[ -z "${CONFIG:-}" ]]; then echo "#" >&2 @@ -142,25 +154,51 @@ if [[ -n "${CONFIG_D}" ]]; then if [[ ! -d "${CONFIG_D}" ]]; then - _exiterr "The path ${CONFIG_D} specified for CONFIG_D does not point to a directory." >&2 + _exiterr "The path ${CONFIG_D} specified for CONFIG_D does not point to a directory." fi for check_config_d in "${CONFIG_D}"/*.sh; do - if [[ ! -e "${check_config_d}" ]]; then - echo "# !! WARNING !! Extra configuration directory ${CONFIG_D} exists, but no configuration found in it." >&2 - break - elif [[ -f "${check_config_d}" ]] && [[ -r "${check_config_d}" ]]; then + if [[ -f "${check_config_d}" ]] && [[ -r "${check_config_d}" ]]; then echo "# INFO: Using additional config file ${check_config_d}" # shellcheck disable=SC1090 . "${check_config_d}" else - _exiterr "Specified additional config ${check_config_d} is not readable or not a file at all." >&2 + _exiterr "Specified additional config ${check_config_d} is not readable or not a file at all." fi done fi + # Check if we are running & are allowed to run as root + if [[ -n "$DEHYDRATED_USER" ]]; then + command -v sudo > /dev/null 2>&1 || _exiterr "DEHYDRATED_USER set but sudo not available. Please install sudo." + command -v getent > /dev/null 2>&1 || _exiterr "DEHYDRATED_USER set but getent not available. Please install getent." + + TARGET_UID="$(getent passwd "${DEHYDRATED_USER}" | cut -d':' -f3)" + if [[ -z "${DEHYDRATED_GROUP}" ]]; then + if [[ "${EUID}" != "${TARGET_UID}" ]]; then + echo "# INFO: Running $0 as ${DEHYDRATED_USER}" + exec sudo -u "${DEHYDRATED_USER}" "${0}" ${ORIGARGS} + fi + else + TARGET_GID="$(getent group "${DEHYDRATED_GROUP}" | cut -d':' -f3)" + if [[ -z "${EGID:-}" ]]; then + command -v id > /dev/null 2>&1 || _exiterr "DEHYDRATED_GROUP set, don't know current gid and 'id' not available... Please provide 'id' binary." + EGID="$(id -g)" + fi + if [[ "${EUID}" != "${TARGET_UID}" ]] || [[ "${EGID}" != "${TARGET_GID}" ]]; then + echo "# INFO: Running $0 as ${DEHYDRATED_USER}/${DEHYDRATED_GROUP}" + exec sudo -u "${DEHYDRATED_USER}" -g "${DEHYDRATED_GROUP}" "${0}" ${ORIGARGS} + fi + fi + elif [[ -n "${DEHYDRATED_GROUP}" ]]; then + _exiterr "DEHYDRATED_GROUP can only be used in combination with DEHYDRATED_USER." + fi + + # Check for missing dependencies + check_dependencies + # Remove slash from end of BASEDIR. Mostly for cleaner outputs, doesn't change functionality. - BASEDIR="${BASEDIR%%/}" + [[ "$BASEDIR" != "/" ]] && BASEDIR="${BASEDIR%%/}" # Check BASEDIR and set default variables [[ -d "${BASEDIR}" ]] || _exiterr "BASEDIR does not exist: ${BASEDIR}" @@ -182,9 +220,11 @@ fi [[ -z "${CERTDIR}" ]] && CERTDIR="${BASEDIR}/certs" + [[ -z "${CHAINCACHE}" ]] && CHAINCACHE="${BASEDIR}/chains" [[ -z "${DOMAINS_TXT}" ]] && DOMAINS_TXT="${BASEDIR}/domains.txt" [[ -z "${WELLKNOWN}" ]] && WELLKNOWN="/var/www/dehydrated" [[ -z "${LOCKFILE}" ]] && LOCKFILE="${BASEDIR}/lock" + [[ -z "${OPENSSL_CNF}" ]] && OPENSSL_CNF="$("${OPENSSL}" version -d | cut -d\" -f2)/openssl.cnf" [[ -n "${PARAM_LOCKFILE_SUFFIX:-}" ]] && LOCKFILE="${LOCKFILE}-${PARAM_LOCKFILE_SUFFIX}" [[ -n "${PARAM_NO_LOCK:-}" ]] && LOCKFILE="" @@ -195,7 +235,9 @@ [[ -n "${PARAM_OCSP_MUST_STAPLE:-}" ]] && OCSP_MUST_STAPLE="${PARAM_OCSP_MUST_STAPLE}" [[ -n "${PARAM_IP_VERSION:-}" ]] && IP_VERSION="${PARAM_IP_VERSION}" - verify_config + if [ ! "${1:-}" = "noverify" ]; then + verify_config + fi store_configvars } @@ -220,6 +262,8 @@ # shellcheck disable=SC2015 CA_REVOKE_CERT="$(printf "%s" "${CA_DIRECTORY}" | get_json_string_value revoke-cert)" || _exiterr "Problem retrieving ACME/CA-URLs, check if your configured CA points to the directory entrypoint." + # Since reg URI is missing from directory we will assume it is the same as CA_NEW_REG without the new part + CA_REG=${CA_NEW_REG/new-reg/reg} # Export some environment variables to be used in hook script export WELLKNOWN BASEDIR CERTDIR CONFIG COMMAND @@ -236,18 +280,18 @@ 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' + printf '\n' >&2 + printf 'Error retrieving terms of service from certificate authority.\n' >&2 + printf 'Please set LICENSE in config manually.\n' >&2 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}" + printf '\n' >&2 + 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}" >&2 + printf 'To accept these terms of service run `%s --register --accept-terms`.\n' "${0}" >&2 exit 1 fi fi @@ -257,13 +301,13 @@ register_new_key="yes" fi fi - openssl rsa -in "${ACCOUNT_KEY}" -check 2>/dev/null > /dev/null || _exiterr "Account key is not valid, can not continue." + "${OPENSSL}" rsa -in "${ACCOUNT_KEY}" -check 2>/dev/null > /dev/null || _exiterr "Account key is not valid, cannot continue." # Get public components from private key and calculate thumbprint - pubExponent64="$(printf '%x' "$(openssl rsa -in "${ACCOUNT_KEY}" -noout -text | awk '/publicExponent/ {print $2}')" | hex2bin | urlbase64)" - pubMod64="$(openssl rsa -in "${ACCOUNT_KEY}" -noout -modulus | cut -d'=' -f2 | hex2bin | urlbase64)" + pubExponent64="$(printf '%x' "$("${OPENSSL}" rsa -in "${ACCOUNT_KEY}" -noout -text | awk '/publicExponent/ {print $2}')" | hex2bin | urlbase64)" + pubMod64="$("${OPENSSL}" rsa -in "${ACCOUNT_KEY}" -noout -modulus | cut -d'=' -f2 | hex2bin | urlbase64)" - thumbprint="$(printf '{"e":"%s","kty":"RSA","n":"%s"}' "${pubExponent64}" "${pubMod64}" | openssl dgst -sha256 -binary | urlbase64)" + thumbprint="$(printf '{"e":"%s","kty":"RSA","n":"%s"}' "${pubExponent64}" "${pubMod64}" | "${OPENSSL}" dgst -sha256 -binary | urlbase64)" # 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 @@ -285,9 +329,9 @@ fi if [[ "${FAILED}" = "true" ]]; then - echo - echo - echo "Error registering account key. See message above for more information." + echo >&2 + echo >&2 + echo "Error registering account key. See message above for more information." >&2 rm "${ACCOUNT_KEY}" "${ACCOUNT_KEY_JSON}" exit 1 fi @@ -299,7 +343,7 @@ # Different sed version for different os types... _sed() { - if [[ "${OSTYPE}" = "Linux" ]]; then + if [[ "${OSTYPE}" = "Linux" || "${OSTYPE:0:5}" = "MINGW" ]]; then sed -r "${@}" else sed -E "${@}" @@ -320,7 +364,7 @@ # Encode data as url-safe formatted base64 urlbase64() { # urlbase64: base64 encoded string with '+' replaced with '-' and '/' replaced with '_' - openssl base64 -e | tr -d '\n\r' | _sed -e 's:=*$::g' -e 'y:+/:-_:' + "${OPENSSL}" base64 -e | tr -d '\n\r' | _sed -e 's:=*$::g' -e 'y:+/:-_:' } # Convert hex string to binary data @@ -336,6 +380,13 @@ sed -n "${filter}" } +# Get integer value from json +get_json_int_value() { + local filter + filter=$(printf 's/.*"%s": *\([0-9]*\).*/\\1/p' "$1") + sed -n "${filter}" +} + rm_json_arrays() { local filter filter='s/\[[^][]*\]/null/g' @@ -347,7 +398,7 @@ # display the output if the exit code was != 0 to simplify debugging. _openssl() { set +e - out="$(openssl "${@}" 2>&1)" + out="$("${OPENSSL}" "${@}" 2>&1)" res=$? set -e if [[ ${res} -ne 0 ]]; then @@ -370,13 +421,13 @@ set +e if [[ "${1}" = "head" ]]; then - statuscode="$(curl ${ip_version:-} -s -w "%{http_code}" -o "${tempcont}" "${2}" -I)" + statuscode="$(curl ${ip_version:-} ${CURL_OPTS} -s -w "%{http_code}" -o "${tempcont}" "${2}" -I)" curlret="${?}" elif [[ "${1}" = "get" ]]; then - statuscode="$(curl ${ip_version:-} -s -w "%{http_code}" -o "${tempcont}" "${2}")" + statuscode="$(curl ${ip_version:-} ${CURL_OPTS} -s -w "%{http_code}" -o "${tempcont}" "${2}")" curlret="${?}" elif [[ "${1}" = "post" ]]; then - statuscode="$(curl ${ip_version:-} -s -w "%{http_code}" -o "${tempcont}" "${2}" -d "${3}")" + statuscode="$(curl ${ip_version:-} ${CURL_OPTS} -s -w "%{http_code}" -o "${tempcont}" "${2}" -d "${3}")" curlret="${?}" else set -e @@ -389,7 +440,12 @@ fi if [[ ! "${statuscode:0:1}" = "2" ]]; then - if [[ ! "${2}" = "${CA_TERMS}" ]] || [[ ! "${statuscode:0:1}" = "3" ]]; then + if [[ -n "${CA_REVOKE_CERT:-}" ]] && [[ "${2}" = "${CA_REVOKE_CERT:-}" ]] && [[ "${statuscode}" = "409" ]]; then + grep -q "Certificate already revoked" "${tempcont}" && return + elif [[ -n "${CA_TERMS:-}" ]] && [[ "${2}" = "${CA_TERMS:-}" ]] && [[ "${statuscode:0:1}" = "3" ]]; then + # do nothing + : + else echo " + ERROR: An error occurred while sending ${1}-request to ${2} (Status ${statuscode})" >&2 echo >&2 echo "Details:" >&2 @@ -399,7 +455,7 @@ # 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}` + errtxt="$(cat ${tempcont})" "${HOOK}" "request_failure" "${statuscode}" "${errtxt}" "${1}" fi @@ -436,7 +492,7 @@ protected64="$(printf '%s' "${protected}" | urlbase64)" # Sign header with nonce and our payload with our private key and encode signature as urlbase64 - signed64="$(printf '%s' "${protected64}.${payload64}" | openssl dgst -sha256 -sign "${ACCOUNT_KEY}" | urlbase64)" + signed64="$(printf '%s' "${protected64}.${payload64}" | "${OPENSSL}" dgst -sha256 -sign "${ACCOUNT_KEY}" | urlbase64)" # Send header + extended header + payload + signature to the acme-server data='{"header": '"${header}"', "protected": "'"${protected64}"'", "payload": "'"${payload64}"'", "signature": "'"${signed64}"'"}' @@ -449,11 +505,11 @@ extract_altnames() { csr="${1}" # the CSR itself (not a file) - if ! <<<"${csr}" openssl req -verify -noout 2>/dev/null; then + if ! <<<"${csr}" "${OPENSSL}" req -verify -noout 2>/dev/null; then _exiterr "Certificate signing request isn't valid" fi - reqtext="$( <<<"${csr}" openssl req -noout -text )" + reqtext="$( <<<"${csr}" "${OPENSSL}" req -noout -text )" if <<<"${reqtext}" grep -q '^[[:space:]]*X509v3 Subject Alternative Name:[[:space:]]*$'; then # SANs used, extract these altnames="$( <<<"${reqtext}" awk '/X509v3 Subject Alternative Name:/{print;getline;print;}' | tail -n1 )" @@ -461,16 +517,16 @@ # shellcheck disable=SC1003 altnames="$( <<<"${altnames}" _sed -e 's/^[[:space:]]*//; s/, /\'$'\n''/g' )" # we can only get DNS: ones signed - if grep -qv '^DNS:' <<<"${altnames}"; then + if grep -qEv '^(DNS|othername):' <<<"${altnames}"; then _exiterr "Certificate signing request contains non-DNS Subject Alternative Names" fi # strip away the DNS: prefix - altnames="$( <<<"${altnames}" _sed -e 's/^DNS://' )" + altnames="$( <<<"${altnames}" _sed -e 's/^(DNS:|othername:<unsupported>)//' )" echo "${altnames}" else # No SANs, extract CN - altnames="$( <<<"${reqtext}" grep '^[[:space:]]*Subject:' | _sed -e 's/.* CN=([^ /,]*).*/\1/' )" + altnames="$( <<<"${reqtext}" grep '^[[:space:]]*Subject:' | _sed -e 's/.* CN ?= ?([^ /,]*).*/\1/' )" echo "${altnames}" fi } @@ -490,6 +546,7 @@ if [ -z "${altnames}" ]; then altnames="$( extract_altnames "${csr}" )" fi + export altnames if [[ -z "${CA_NEW_AUTHZ}" ]] || [[ -z "${CA_NEW_CERT}" ]]; then _exiterr "Certificate authority doesn't allow certificate signing" @@ -509,7 +566,7 @@ 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 + if [ "${challenge_status}" = "valid" ] && [ ! "${PARAM_FORCE:-no}" = "yes" ]; then echo " + Already validated!" continue fi @@ -536,7 +593,7 @@ ;; "dns-01") # Generate DNS entry content for dns-01 validation - keyauth_hook="$(printf '%s' "${keyauth}" | openssl dgst -sha256 -binary | urlbase64)" + keyauth_hook="$(printf '%s' "${keyauth}" | "${OPENSSL}" dgst -sha256 -binary | urlbase64)" ;; esac @@ -593,6 +650,7 @@ echo " + Challenge is valid!" else [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" != "yes" ]] && "${HOOK}" "invalid_challenge" "${altname}" "${result}" + break fi done fi @@ -617,8 +675,8 @@ # Finally request certificate from the acme-server and store it in cert-${timestamp}.pem and link from cert.pem echo " + Requesting certificate..." - csr64="$( <<<"${csr}" openssl req -outform DER | urlbase64)" - crt64="$(signed_request "${CA_NEW_CERT}" '{"resource": "new-cert", "csr": "'"${csr64}"'"}' | openssl base64 -e)" + csr64="$( <<<"${csr}" "${OPENSSL}" req -config "${OPENSSL_CNF}" -outform DER | urlbase64)" + crt64="$(signed_request "${CA_NEW_CERT}" '{"resource": "new-cert", "csr": "'"${csr64}"'"}' | "${OPENSSL}" base64 -e)" crt="$( printf -- '-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----\n' "${crt64}" )" # Try to load the certificate to detect corruption @@ -634,7 +692,17 @@ # 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 + "${OPENSSL}" x509 -in "${certificate}" -noout -text | (grep 'CA Issuers - URI:' | cut -d':' -f2-) || true +} + +get_issuer_hash() { + certificate="${1}" + "${OPENSSL}" x509 -in "${certificate}" -noout -issuer_hash +} + +get_ocsp_url() { + certificate="${1}" + "${OPENSSL}" x509 -in "${certificate}" -noout -ocsp_uri } # walk certificate chain, retrieving all intermediate certificates @@ -658,9 +726,9 @@ # 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 : + 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 : + 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 @@ -678,57 +746,65 @@ # Create certificate for domain(s) sign_domain() { + local certdir="${1}" + shift domain="${1}" altnames="${*}" timestamp="$(date +%s)" + export altnames + echo " + Signing domains..." if [[ -z "${CA_NEW_AUTHZ}" ]] || [[ -z "${CA_NEW_CERT}" ]]; then _exiterr "Certificate authority doesn't allow certificate signing" fi # If there is no existing certificate directory => make it - if [[ ! -e "${CERTDIR}/${domain}" ]]; then - echo " + Creating new directory ${CERTDIR}/${domain} ..." - mkdir -p "${CERTDIR}/${domain}" || _exiterr "Unable to create directory ${CERTDIR}/${domain}" + if [[ ! -e "${certdir}" ]]; then + echo " + Creating new directory ${certdir} ..." + mkdir -p "${certdir}" || _exiterr "Unable to create directory ${certdir}" + fi + if [ ! -d "${CHAINCACHE}" ]; then + echo " + Creating chain cache directory ${CHAINCACHE}" + mkdir "${CHAINCACHE}" fi privkey="privkey.pem" # generate a new private key if we need or want one - if [[ ! -r "${CERTDIR}/${domain}/privkey.pem" ]] || [[ "${PRIVATE_KEY_RENEW}" = "yes" ]]; then + if [[ ! -r "${certdir}/privkey.pem" ]] || [[ "${PRIVATE_KEY_RENEW}" = "yes" ]]; then echo " + Generating private key..." privkey="privkey-${timestamp}.pem" case "${KEY_ALGO}" in - rsa) _openssl genrsa -out "${CERTDIR}/${domain}/privkey-${timestamp}.pem" "${KEYSIZE}";; - prime256v1|secp384r1) _openssl ecparam -genkey -name "${KEY_ALGO}" -out "${CERTDIR}/${domain}/privkey-${timestamp}.pem";; + rsa) _openssl genrsa -out "${certdir}/privkey-${timestamp}.pem" "${KEYSIZE}";; + prime256v1|secp384r1) _openssl ecparam -genkey -name "${KEY_ALGO}" -out "${certdir}/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 + if [[ -r "${certdir}/privkey.pem" && -r "${certdir}/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" + mv "${certdir}/privkey.roll.pem" "${certdir}/privkey-tmp.pem" + mv "${certdir}/privkey-${timestamp}.pem" "${certdir}/privkey.roll.pem" + mv "${certdir}/privkey-tmp.pem" "${certdir}/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 + if [[ ! -r "${certdir}/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";; + rsa) _openssl genrsa -out "${certdir}/privkey.roll.pem" "${KEYSIZE}";; + prime256v1|secp384r1) _openssl ecparam -genkey -name "${KEY_ALGO}" -out "${certdir}/privkey.roll.pem";; esac fi # delete rolloverkeys if disabled - if [[ -r "${CERTDIR}/${domain}/privkey.roll.pem" && ! "${PRIVATE_KEY_ROLLOVER}" = "yes" ]]; then + if [[ -r "${certdir}/privkey.roll.pem" && ! "${PRIVATE_KEY_ROLLOVER}" = "yes" ]]; then echo " + Removing Rolloverkey (feature disabled)..." - rm -f "${CERTDIR}/${domain}/privkey.roll.pem" + rm -f "${certdir}/privkey.roll.pem" fi # Generate signing request config and the actual signing request echo " + Generating signing request..." SAN="" for altname in ${altnames}; do - SAN+="DNS:${altname}, " + SAN="${SAN}DNS:${altname}, " done SAN="${SAN%%, }" local tmp_openssl_cnf @@ -738,34 +814,86 @@ if [ "${OCSP_MUST_STAPLE}" = "yes" ]; then printf "\n1.3.6.1.5.5.7.1.24=DER:30:03:02:01:05" >> "${tmp_openssl_cnf}" fi - openssl req -new -sha256 -key "${CERTDIR}/${domain}/${privkey}" -out "${CERTDIR}/${domain}/cert-${timestamp}.csr" -subj "/CN=${domain}/" -reqexts SAN -config "${tmp_openssl_cnf}" + SUBJ="/CN=${domain}/" + if [[ "${OSTYPE:0:5}" = "MINGW" ]]; then + # The subject starts with a /, so MSYS will assume it's a path and convert + # it unless we escape it with another one: + SUBJ="/${SUBJ}" + fi + "${OPENSSL}" req -new -sha256 -key "${certdir}/${privkey}" -out "${certdir}/cert-${timestamp}.csr" -subj "${SUBJ}" -reqexts SAN -config "${tmp_openssl_cnf}" rm -f "${tmp_openssl_cnf}" - crt_path="${CERTDIR}/${domain}/cert-${timestamp}.pem" + crt_path="${certdir}/cert-${timestamp}.pem" # shellcheck disable=SC2086 - sign_csr "$(< "${CERTDIR}/${domain}/cert-${timestamp}.csr" )" ${altnames} 3>"${crt_path}" + sign_csr "$(< "${certdir}/cert-${timestamp}.csr" )" ${altnames} 3>"${crt_path}" # Create fullchain.pem echo " + Creating fullchain.pem..." - cat "${crt_path}" > "${CERTDIR}/${domain}/fullchain-${timestamp}.pem" - walk_chain "${crt_path}" > "${CERTDIR}/${domain}/chain-${timestamp}.pem" - cat "${CERTDIR}/${domain}/chain-${timestamp}.pem" >> "${CERTDIR}/${domain}/fullchain-${timestamp}.pem" + cat "${crt_path}" > "${certdir}/fullchain-${timestamp}.pem" + local issuer_hash + issuer_hash="$(get_issuer_hash "${crt_path}")" + if [ -e "${CHAINCACHE}/${issuer_hash}.chain" ]; then + echo " + Using cached chain!" + cat "${CHAINCACHE}/${issuer_hash}.chain" > "${certdir}/chain-${timestamp}.pem" + else + echo " + Walking chain..." + local issuer_cert_uri + issuer_cert_uri="$(get_issuer_cert_uri "${crt_path}" || echo "unknown")" + (walk_chain "${crt_path}" > "${certdir}/chain-${timestamp}.pem") || _exiterr "Walking chain has failed, your certificate has been created and can be found at ${crt_path}, the corresponding private key at ${privkey}. If you want you can manually continue on creating and linking all necessary files. If this error occurs again you should manually generate the certificate chain and place it under ${CHAINCACHE}/${issuer_hash}.chain (see ${issuer_cert_uri})" + cat "${certdir}/chain-${timestamp}.pem" > "${CHAINCACHE}/${issuer_hash}.chain" + fi + cat "${certdir}/chain-${timestamp}.pem" >> "${certdir}/fullchain-${timestamp}.pem" # Update symlinks - [[ "${privkey}" = "privkey.pem" ]] || ln -sf "privkey-${timestamp}.pem" "${CERTDIR}/${domain}/privkey.pem" + [[ "${privkey}" = "privkey.pem" ]] || ln -sf "privkey-${timestamp}.pem" "${certdir}/privkey.pem" - ln -sf "chain-${timestamp}.pem" "${CERTDIR}/${domain}/chain.pem" - ln -sf "fullchain-${timestamp}.pem" "${CERTDIR}/${domain}/fullchain.pem" - ln -sf "cert-${timestamp}.csr" "${CERTDIR}/${domain}/cert.csr" - ln -sf "cert-${timestamp}.pem" "${CERTDIR}/${domain}/cert.pem" + ln -sf "chain-${timestamp}.pem" "${certdir}/chain.pem" + ln -sf "fullchain-${timestamp}.pem" "${certdir}/fullchain.pem" + ln -sf "cert-${timestamp}.csr" "${certdir}/cert.csr" + ln -sf "cert-${timestamp}.pem" "${certdir}/cert.pem" # Wait for hook script to clean the challenge and to deploy cert if used - [[ -n "${HOOK}" ]] && "${HOOK}" "deploy_cert" "${domain}" "${CERTDIR}/${domain}/privkey.pem" "${CERTDIR}/${domain}/cert.pem" "${CERTDIR}/${domain}/fullchain.pem" "${CERTDIR}/${domain}/chain.pem" "${timestamp}" + [[ -n "${HOOK}" ]] && "${HOOK}" "deploy_cert" "${domain}" "${certdir}/privkey.pem" "${certdir}/cert.pem" "${certdir}/fullchain.pem" "${certdir}/chain.pem" "${timestamp}" unset challenge_token echo " + Done!" } +# Usage: --version (-v) +# Description: Print version information +command_version() { + load_config noverify + + echo "Dehydrated by Lukas Schauer" + echo "https://dehydrated.de" + echo "" + echo "Dehydrated version: ${VERSION}" + revision="$(cd "${SCRIPTDIR}"; git rev-parse HEAD 2>/dev/null || echo "unknown")" + echo "GIT-Revision: ${revision}" + echo "" + if [[ "${OSTYPE}" = "FreeBSD" ]]; then + echo "OS: $(uname -sr)" + else + echo "OS: $(cat /etc/issue | grep -v ^$ | head -n1 | _sed 's/\\(r|n|l) .*//g')" + fi + echo "Used software:" + [[ -n "${BASH_VERSION:-}" ]] && echo " bash: ${BASH_VERSION}" + [[ -n "${ZSH_VERSION:-}" ]] && echo " zsh: ${ZSH_VERSION}" + echo " curl: $(curl --version 2>&1 | head -n1 | cut -d" " -f1-2)" + if [[ "${OSTYPE}" = "FreeBSD" ]]; then + echo " awk, sed, mktemp: FreeBSD base system versions" + else + echo " awk: $(awk -W version 2>&1 | head -n1)" + echo " sed: $(sed --version 2>&1 | head -n1)" + echo " mktemp: $(mktemp --version 2>&1 | head -n1)" + fi + echo " grep: $(grep --version 2>&1 | head -n1)" + echo " diff: $(diff --version 2>&1 | head -n1)" + echo " openssl: $("${OPENSSL}" version 2>&1)" + + exit 0 +} + # Usage: --register # Description: Register account key command_register() { @@ -774,14 +902,59 @@ exit 0 } +# Usage: --account +# Description: Update account contact information +command_account() { + init_system + FAILED=false + + NEW_ACCOUNT_KEY_JSON="$(_mktemp)" + REG_ID=$(cat "${ACCOUNT_KEY_JSON}" | get_json_int_value id) + + # Check if we have the registration id + if [[ -z "${REG_ID}" ]]; then + _exiterr "Error retrieving registration id." + fi + + echo "+ Updating registration id: ${REG_ID} contact information..." + # If an email for the contact has been provided then adding it to the registered account + if [[ -n "${CONTACT_EMAIL}" ]]; then + (signed_request "${CA_REG}"/"${REG_ID}" '{"resource": "reg", "contact":["mailto:'"${CONTACT_EMAIL}"'"]}' > "${NEW_ACCOUNT_KEY_JSON}") || FAILED=true + else + (signed_request "${CA_REG}"/"${REG_ID}" '{"resource": "reg", "contact":[]}' > "${NEW_ACCOUNT_KEY_JSON}") || FAILED=true + fi + + if [[ "${FAILED}" = "true" ]]; then + rm "${NEW_ACCOUNT_KEY_JSON}" + _exiterr "Error updating account information. See message above for more information." + fi + if diff -q "${NEW_ACCOUNT_KEY_JSON}" "${ACCOUNT_KEY_JSON}" > /dev/null; then + echo "+ Account information was the same after the update" + rm "${NEW_ACCOUNT_KEY_JSON}" + else + ACCOUNT_KEY_JSON_BACKUP="${ACCOUNT_KEY_JSON%.*}-$(date +%s).json" + echo "+ Backup ${ACCOUNT_KEY_JSON} as ${ACCOUNT_KEY_JSON_BACKUP}" + cp -p "${ACCOUNT_KEY_JSON}" "${ACCOUNT_KEY_JSON_BACKUP}" + echo "+ Populate ${ACCOUNT_KEY_JSON}" + mv "${NEW_ACCOUNT_KEY_JSON}" "${ACCOUNT_KEY_JSON}" + fi + echo "+ Done!" + exit 0 +} + # Usage: --cron (-c) -# Description: Sign/renew non-existant/changed/expiring certificates. +# Description: Sign/renew non-existent/changed/expiring certificates. command_sign_domains() { init_system + [[ -n "${HOOK}" ]] && "${HOOK}" "startup_hook" if [[ -n "${PARAM_DOMAIN:-}" ]]; then DOMAINS_TXT="$(_mktemp)" - printf -- "${PARAM_DOMAIN}" > "${DOMAINS_TXT}" + if [[ -n "${PARAM_ALIAS:-}" ]]; then + printf -- "${PARAM_DOMAIN} > ${PARAM_ALIAS}" > "${DOMAINS_TXT}" + else + printf -- "${PARAM_DOMAIN}" > "${DOMAINS_TXT}" + fi elif [[ -e "${DOMAINS_TXT}" ]]; then if [[ ! -r "${DOMAINS_TXT}" ]]; then _exiterr "domains.txt found but not readable" @@ -793,12 +966,22 @@ # 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' | awk '{print tolower($0)}' | _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' -e 's/([^ ])>/\1 >/g' -e 's/> />/g' | (grep -vE '^(#|$)' || true)); do reset_configvars IFS="${ORIGIFS}" + alias="$(grep -Eo '>[^ ]+' <<< "${line}" || true)" + line="$(_sed -e 's/>[^ ]+[ ]*//g' <<< "${line}")" + aliascount="$(grep -Eo '>' <<< "${alias}" | awk 'END {print NR}' || true )" + [ ${aliascount} -gt 1 ] && _exiterr "Only one alias per line is allowed in domains.txt!" + domain="$(printf '%s\n' "${line}" | cut -d' ' -f1)" morenames="$(printf '%s\n' "${line}" | cut -s -d' ' -f2-)" - cert="${CERTDIR}/${domain}/cert.pem" + [ ${aliascount} -lt 1 ] && alias="${domain}" || alias="${alias#>}" + export alias + + local certdir="${CERTDIR}/${alias}" + cert="${certdir}/cert.pem" + chain="${certdir}/chain.pem" force_renew="${PARAM_FORCE:-no}" @@ -813,9 +996,9 @@ # we could just source the config file but i decided to go this way to protect people from accidentally overriding # variables used internally by this script itself. if [[ -n "${DOMAINS_D}" ]]; then - certconfig="${DOMAINS_D}/${domain}" + certconfig="${DOMAINS_D}/${alias}" else - certconfig="${CERTDIR}/${domain}/config" + certconfig="${certdir}/config" fi if [ -f "${certconfig}" ]; then @@ -841,7 +1024,7 @@ declare -- "${config_var}=${config_value}" ;; _) ;; - *) echo " ! Setting ${config_var} on a per-certificate base is not (yet) supported" + *) echo " ! Setting ${config_var} on a per-certificate base is not (yet) supported" >&2 esac done IFS="${ORIGIFS}" @@ -849,10 +1032,12 @@ verify_config export WELLKNOWN CHALLENGETYPE KEY_ALGO PRIVATE_KEY_ROLLOVER + skip="no" + if [[ -e "${cert}" ]]; then printf " + Checking domain name(s) of existing cert..." - certnames="$(openssl x509 -in "${cert}" -text -noout | grep DNS: | _sed 's/DNS://g' | tr -d ' ' | tr ',' '\n' | sort -u | tr '\n' ' ' | _sed 's/ $//')" + certnames="$("${OPENSSL}" x509 -in "${cert}" -text -noout | grep DNS: | _sed 's/DNS://g' | tr -d ' ' | tr ',' '\n' | sort -u | tr '\n' ' ' | _sed 's/ $//')" givennames="$(echo "${domain}" "${morenames}"| tr ' ' '\n' | sort -u | tr '\n' ' ' | _sed 's/ $//' | _sed 's/^ //')" if [[ "${certnames}" = "${givennames}" ]]; then @@ -869,37 +1054,72 @@ if [[ -e "${cert}" ]]; then echo " + Checking expire date of existing cert..." - valid="$(openssl x509 -enddate -noout -in "${cert}" | cut -d= -f2- )" + valid="$("${OPENSSL}" x509 -enddate -noout -in "${cert}" | cut -d= -f2- )" printf " + Valid till %s " "${valid}" - if openssl x509 -checkend $((RENEW_DAYS * 86400)) -noout -in "${cert}"; then + if "${OPENSSL}" x509 -checkend $((RENEW_DAYS * 86400)) -noout -in "${cert}"; then printf "(Longer than %d days). " "${RENEW_DAYS}" if [[ "${force_renew}" = "yes" ]]; then echo "Ignoring because renew was forced!" else # Certificate-Names unchanged and cert is still valid echo "Skipping renew!" - [[ -n "${HOOK}" ]] && "${HOOK}" "unchanged_cert" "${domain}" "${CERTDIR}/${domain}/privkey.pem" "${CERTDIR}/${domain}/cert.pem" "${CERTDIR}/${domain}/fullchain.pem" "${CERTDIR}/${domain}/chain.pem" - continue + [[ -n "${HOOK}" ]] && "${HOOK}" "unchanged_cert" "${domain}" "${certdir}/privkey.pem" "${certdir}/cert.pem" "${certdir}/fullchain.pem" "${certdir}/chain.pem" + skip="yes" fi else echo "(Less than ${RENEW_DAYS} days). Renewing!" fi fi + local update_ocsp + update_ocsp="no" + # shellcheck disable=SC2086 - if [[ "${PARAM_KEEP_GOING:-}" = "yes" ]]; then - sign_domain ${line} & - wait $! || true - else - sign_domain ${line} + if [[ ! "${skip}" = "yes" ]]; then + update_ocsp="yes" + if [[ "${PARAM_KEEP_GOING:-}" = "yes" ]]; then + sign_domain "${certdir}" ${domain} ${morenames} & + wait $! || true + else + sign_domain "${certdir}" ${domain} ${morenames} + fi + fi + + if [[ "${OCSP_FETCH}" = "yes" ]]; then + local ocsp_url + ocsp_url="$(get_ocsp_url "${cert}")" + + if [[ ! -e "${certdir}/ocsp.der" ]]; then + update_ocsp="yes" + elif ! ("${OPENSSL}" ocsp -no_nonce -issuer "${chain}" -verify_other "${chain}" -cert "${cert}" -respin "${certdir}/ocsp.der" -status_age 432000 2>&1 | grep -q "${cert}: good"); then + update_ocsp="yes" + fi + + if [[ "${update_ocsp}" = "yes" ]]; then + echo " + Updating OCSP stapling file" + ocsp_timestamp="$(date +%s)" + if grep -qE "^(0|(1\.0))\." <<< "$(${OPENSSL} version | awk '{print $2}')"; then + "${OPENSSL}" ocsp -no_nonce -issuer "${chain}" -verify_other "${chain}" -cert "${cert}" -respout "${certdir}/ocsp-${ocsp_timestamp}.der" -url "${ocsp_url}" -header "HOST" "$(echo "${ocsp_url}" | _sed -e 's/^http(s?):\/\///' -e 's/\/.*$//g')" > /dev/null 2>&1 + else + "${OPENSSL}" ocsp -no_nonce -issuer "${chain}" -verify_other "${chain}" -cert "${cert}" -respout "${certdir}/ocsp-${ocsp_timestamp}.der" -url "${ocsp_url}" > /dev/null 2>&1 + fi + ln -sf "ocsp-${ocsp_timestamp}.der" "${certdir}/ocsp.der" + else + echo " + OSCP stapling file is still valid (skipping update)" + fi fi done + reset_configvars # remove temporary domains.txt file if used [[ -n "${PARAM_DOMAIN:-}" ]] && rm -f "${DOMAINS_TXT}" [[ -n "${HOOK}" ]] && "${HOOK}" "exit_hook" + if [[ "${AUTO_CLEANUP}" == "yes" ]]; then + echo "+ Running automatic cleanup" + command_cleanup noinit + fi exit 0 } @@ -931,11 +1151,11 @@ # get and convert ca cert chainfile="$(_mktemp)" tmpchain="$(_mktemp)" - http_request get "$(openssl x509 -in "${certfile}" -noout -text | grep 'CA Issuers - URI:' | cut -d':' -f2-)" > "${tmpchain}" + 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 + "${OPENSSL}" x509 -in "${tmpchain}" -inform DER -out "${chainfile}" -outform PEM rm "${tmpchain}" fi @@ -973,7 +1193,7 @@ echo "Revoking ${cert}" - cert64="$(openssl x509 -in "${cert}" -inform PEM -outform DER | urlbase64)" + cert64="$("${OPENSSL}" x509 -in "${cert}" -inform PEM -outform DER | urlbase64)" response="$(signed_request "${CA_REVOKE_CERT}" '{"resource": "revoke-cert", "certificate": "'"${cert64}"'"}' | clean_json)" # if there is a problem with our revoke request _request (via signed_request) will report this and "exit 1" out # so if we are here, it is safe to assume the request was successful @@ -985,9 +1205,11 @@ # Usage: --cleanup (-gc) # Description: Move unused certificate files to archive directory command_cleanup() { - load_config + if [ ! "${1:-}" = "noinit" ]; then + load_config + fi - # Create global archive directory if not existant + # Create global archive directory if not existent if [[ ! -e "${BASEDIR}/archive" ]]; then mkdir "${BASEDIR}/archive" fi @@ -1000,14 +1222,14 @@ # Get certificate name certname="$(basename "${certdir}")" - # Create certitifaces archive directory if not existant + # Create certificates archive directory if not existent archivedir="${BASEDIR}/archive/${certname}" if [[ ! -e "${archivedir}" ]]; then mkdir "${archivedir}" fi # Loop over file-types (certificates, keys, signing-requests, ...) - for filetype in cert.csr cert.pem chain.pem fullchain.pem privkey.pem; do + for filetype in cert.csr cert.pem chain.pem fullchain.pem privkey.pem ocsp.der; do # Skip if symlink is broken [[ -r "${certdir}/${filetype}" ]] || continue @@ -1019,10 +1241,7 @@ fileext="$(echo "${filetype}" | cut -d. -f2)" # Loop over all files of this type - for file in "${certdir}/${filebase}-"*".${fileext}"; do - # Handle case where no files match the wildcard - [[ -f "${file}" ]] || break - + for file in "${certdir}/${filebase}-"*".${fileext}" "${certdir}/${filebase}-"*".${fileext}-revoked"; do # Check if current file is in use, if unused move to archive directory filename="$(basename "${file}")" if [[ ! "${filename}" = "${current}" ]]; then @@ -1105,6 +1324,10 @@ set_command register ;; + --account) + set_command account + ;; + # PARAM_Usage: --accept-terms # PARAM_Description: Accept CAs terms of service --accept-terms) @@ -1125,6 +1348,10 @@ PARAM_REVOKECERT="${1}" ;; + --version|-v) + set_command version + ;; + --cleanup|-gc) set_command cleanup ;; @@ -1159,6 +1386,15 @@ fi ;; + # PARAM_Usage: --alias certalias + # PARAM_Description: Use specified name for certificate directory (and per-certificate config) instead of the primary domain (only used if --domain is specified) + --alias) + shift 1 + check_parameters "${1:-}" + [[ -n "${PARAM_ALIAS:-}" ]] && _exiterr "Alias can only be specified once!" + PARAM_ALIAS="${1}" + ;; + # PARAM_Usage: --keep-going (-g) # PARAM_Description: Keep going after encountering an error while creating/renewing multiple certificates in cron mode --keep-going|-g) @@ -1180,9 +1416,9 @@ # PARAM_Usage: --lock-suffix example.com # PARAM_Description: Suffix lockfile name with a string (useful for with -d) --lock-suffix) - shift 1 + shift 1 check_parameters "${1:-}" - PARAM_LOCKFILE_SUFFIX="${1}" + PARAM_LOCKFILE_SUFFIX="${1}" ;; # PARAM_Usage: --ocsp @@ -1254,9 +1490,11 @@ env) command_env;; sign_domains) command_sign_domains;; register) command_register;; + account) command_account;; sign_csr) command_sign_csr "${PARAM_CSR}";; revoke) command_revoke "${PARAM_REVOKECERT}";; cleanup) command_cleanup;; + version) command_version;; *) command_help; exit 1;; esac } @@ -1264,8 +1502,7 @@ # Determine OS type OSTYPE="$(uname)" -# Check for missing dependencies -check_dependencies - -# Run script -main "${@:-}" +if [[ ! "${DEHYDRATED_NOOP:-}" = "NOOP" ]]; then + # Run script + main "${@:-}" +fi diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dehydrated-0.4.0/dehydrated.1 new/dehydrated-0.5.0/dehydrated.1 --- old/dehydrated-0.4.0/dehydrated.1 1970-01-01 01:00:00.000000000 +0100 +++ new/dehydrated-0.5.0/dehydrated.1 2018-01-13 20:08:12.000000000 +0100 @@ -0,0 +1,155 @@ +.TH DEHYDRATED 1 2017-09-20 "Dehydrated ACME Client" +.SH NAME +dehydrated \- ACME client implemented as a shell-script +.SH SYNOPSIS +.B dehydrated +[\fBcommand\fR [\fBargument\fR]] +[\fBargument\fR [\fBargument\fR]] +.IR ... +.SH DESCRIPTION +A client for ACME-based Certificate Authorities, such as LetsEncrypt. It +allows to request and obtain TLS certificates from an ACME-based +certificate authority. + +Before any certificates can be requested, Dehydrated needs +to acquire an account with the Certificate Authorities. Optionally, an email +address can be provided. It will be used to e.g. notify about expiring +certificates. You will usually need to accept the Terms of Service of the CA. +Dehydrated will notify if no account is configured. Run with \fB--register +--accept-terms\fR to create a new account. + +Next, all domain names must be provided in domains.txt. The format is line +based: If the file contains two lines "example.com" and "example.net", +Dehydrated will request two certificate, one for "example.com" and the other +for "example.net". A single line while "example.com example.net" will request a +single certificate valid for both "example.net" and "example.com" through the \fISubject +Alternative Name\fR (SAN) field. + +For the next step, one way of verifying domain name ownership needs to be +configured. Dehydrated implements \fIhttp-01\fR and \fIdns-01\fR verification. + +The \fIhttp-01\fR verification provides proof of ownership by providing a +challenge token. In order to do that, the directory referenced in the +\fIWELLKNOWN\fR config variable needs to be exposed at +\fIhttp://{domain}/.well-known/acme-challenge/\fR, where {domain} is every +domain name specified in \fIdomains.txt\fR. Dehydrated does not provide its +own challenge responder, but relies on an existing web server to provide the +challenge response. See \fIwellknown.md\fR for configuration examples of +popular web servers. + +The \fIdns-01\fR verification works by providing a challenge token through DNS. +This is especially interesting for hosts that cannot be exposed to the public +Internet. Because adding records to DNS zones is oftentimes highly specific to +the software or the DNS provider at hand, there are many third party hooks +available for dehydrated. See \fIdns-verification.md\fR for hooks for popular +DNS servers and DNS hosters. + +Finally, the certificates need to be requested and updated on a regular basis. +This can happen through a cron job or a timer. Initially, you may enforce this +by invoking \fIdehydrated -c\fR manually. + +After a successful run, certificates are stored in +\fI/etc/dehydrated/certs/{domain}\fR, where {domain} is the domain name in the +first column of \fIdomains.txt\fR. + +.SH OPTIONS + +.BR Commands +.TP +.BR \-\-version ", " \-v +Print version information +.TP +.BR \-\-register +Register account key +.TP +.BR \-\-account +Update account contact information +.TP +.BR \-\-cron ", " \-c +Sign/renew non\-existent/changed/expiring certificates. +.TP +.BR \-\-signcsr ", " \-s " " \fIpath/to/csr.pem\fR +Sign a given CSR, output CRT on stdout (advanced usage) +.TP +.BR \-\-revoke ", " \-r " " \fIpath/to/cert.pem\fR +Revoke specified certificate +.TP +.BR \-\-cleanup ", " \-gc +Move unused certificate files to archive directory +.TP +.BR \-\-help ", " \-h +Show help text +.TP +.BR \-\-env ", " \-e +Output configuration variables for use in other scripts + +.PP +.BR Parameters +.TP +.BR \-\-accept\-terms +Accept CAs terms of service +.TP +.BR \-\-full\-chain ", " \-fc +Print full chain when using \fB\-\-signcsr\fR +.TP +.BR \-\-ipv4 ", " \-4 +Resolve names to IPv4 addresses only +.TP +.BR \-\-ipv6 ", " \-6 +Resolve names to IPv6 addresses only +.TP +.BR \-\-domain ", " \-d " " \fIdomain.tld\fR +Use specified domain name(s) instead of domains.txt entry (one certificate!) +.TP +.BR \-\-keep\-going ", " \-g +Keep going after encountering an error while creating/renewing multiple +certificates in cron mode +.TP +.BR \-\-force ", " \-x +Force renew of certificate even if it is longer valid than value in RENEW_DAYS +.TP +.BR \-\-no\-lock ", " \-n +Don't use lockfile (potentially dangerous!) +.TP +.BR \-\-lock\-suffix " " \fIexample.com\fR +Suffix lockfile name with a string (useful for use with \-d) +.TP +.BR \-\-ocsp +Sets option in CSR indicating OCSP stapling to be mandatory +.TP +.BR \-\-privkey ", " \-p " " \fIpath/to/key.pem\fR +Use specified private key instead of account key (useful for revocation) +.TP +.BR \-\-config ", " \-f " " \fIpath/to/config\fR +Use specified config file +.TP +.BR \-\-hook ", " \-k " " \fIpath/to/hook.sh\fR +Use specified script for hooks +.TP +.BR \-\-out ", " \-o " " \fIcerts/directory\fR +Output certificates into the specified directory +.TP +.BR \-\-challenge ", " \-t " " \fI[http\-01|dns\-01]\fR +Which challenge should be used? Currently http\-01 and dns\-01 are supported +.TP +.BR \-\-algo ", " \-a " " \fI[rsa|prime256v1|secp384r1]\fR +Which public key algorithm should be used? Supported: rsa, prime256v1 and +secp384r1 +.SH DIAGNOSTICS +The program exits 0 if everything was fine, 1 if an error occurred. +.SH BUGS +Please report any bugs that you may encounter at the project web site +.UR https://github.com/lukas2511/dehydrated/issues +.UE . +.SH AUTHOR +Dehydrated was written by Lukas Schauer. This man page was contributed by +Daniel Molkentin. +.SH COPYRIGHT +Copyright 20015-2017 by Lukas Schauer and the respective contributors. +Provided under the MIT License. See the LICENSE file that accompanies the +distribution for licensing information. +.SH SEE ALSO +Full documentation along with configuration examples are provided in the \fIdocs\fR +directory of the distribution, or at +.UR https://github.com/lukas2511/dehydrated/tree/master/docs +.UE . diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dehydrated-0.4.0/docs/dns-verification.md new/dehydrated-0.5.0/docs/dns-verification.md --- old/dehydrated-0.4.0/docs/dns-verification.md 2017-02-05 15:33:17.000000000 +0100 +++ new/dehydrated-0.5.0/docs/dns-verification.md 2018-01-13 20:08:12.000000000 +0100 @@ -2,9 +2,18 @@ This script also supports the new `dns-01`-type verification. This type of verification requires you to be able to create a specific `TXT` DNS record for each hostname included in the certificate. -You need a hook script that deploys the challenge to your DNS server! +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, 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. +The hook script (indicated in the config file or the `--hook/-k` command line argument) gets four arguments: + +$1 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 @@ -13,10 +22,10 @@ _acme-challenge.my IN TXT $4 ``` -That could be done manually (as most providers don't have a DNS API), by having your hook script echo $1, $2 and $4 and then wait (read -s -r -e < /dev/tty) - give it a little time to get into their DNS system. Usually providers give you a boxes to put "_acme-challenge.my" and the token value in, and a dropdown to choose the record type, TXT. +That could be done manually (as most providers don't have a DNS API), by having your hook script echo $1, $2 and $4 and then wait (`read -s -r -e < /dev/tty`) - give it a little time to get into their DNS system. Usually providers give you a boxes to put "_acme-challenge.my" and the token value in, and a dropdown to choose the record type, TXT. Or when you do have a DNS API, pass the details accordingly to achieve the same thing. -You can delete the TXT record when called with operation clean_challenge, when $2 is also the domain name. +You can delete the TXT record when called with operation `clean_challenge`, when $2 is also the domain name. Here are some examples: [Examples for DNS-01 hooks](https://github.com/lukas2511/dehydrated/wiki/Examples-for-DNS-01-hooks) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dehydrated-0.4.0/docs/domains_txt.md new/dehydrated-0.5.0/docs/domains_txt.md --- old/dehydrated-0.4.0/docs/domains_txt.md 2017-02-05 15:33:17.000000000 +0100 +++ new/dehydrated-0.5.0/docs/domains_txt.md 2018-01-13 20:08:12.000000000 +0100 @@ -7,7 +7,13 @@ ```text example.com www.example.com example.net www.example.net wiki.example.net +example.net www.example.net wiki.example.net > certalias ``` This states that there should be two certificates `example.com` and `example.net`, with the other domains in the corresponding line being their alternative names. + +You can define an alias for your certificate which will (instead of the primary domain) be +used as directory name under your certdir and for a per-certificate lookup. +This allows multiple certificates with identical sets of domains but different configuration +to exist. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dehydrated-0.4.0/docs/examples/config new/dehydrated-0.5.0/docs/examples/config --- old/dehydrated-0.4.0/docs/examples/config 2017-02-05 15:33:17.000000000 +0100 +++ new/dehydrated-0.5.0/docs/examples/config 2018-01-13 20:08:12.000000000 +0100 @@ -10,6 +10,12 @@ # Default values of this config are in comments # ######################################################## +# Which user should dehydrated run as? This will be implictly enforced when running as root +#DEHYDRATED_USER= + +# Which group should dehydrated run as? This will be implictly enforced when running as root +#DEHYDRATED_GROUP= + # Resolve names to addresses of IP version only. (curl) # supported values: 4, 6 # default: <unset> @@ -54,6 +60,12 @@ # Path to openssl config file (default: <unset> - tries to figure out system default) #OPENSSL_CNF= +# Path to OpenSSL binary (default: "openssl") +#OPENSSL="openssl" + +# Extra options passed to the curl binary (default: <unset>) +#CURL_OPTS= + # Program or function called in certain situations # # After generating the challenge-response, or after failed challenge (in this case altname is empty) @@ -89,3 +101,12 @@ # Option to add CSR-flag indicating OCSP stapling to be mandatory (default: no) #OCSP_MUST_STAPLE="no" + +# Fetch OCSP responses (default: no) +#OCSP_FETCH="no" + +# Issuer chain cache directory (default: $BASEDIR/chains) +#CHAINCACHE="${BASEDIR}/chains" + +# Automatic cleanup (default: no) +#AUTO_CLEANUP="no" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dehydrated-0.4.0/docs/examples/hook.sh new/dehydrated-0.5.0/docs/examples/hook.sh --- old/dehydrated-0.4.0/docs/examples/hook.sh 2017-02-05 15:33:17.000000000 +0100 +++ new/dehydrated-0.5.0/docs/examples/hook.sh 2018-01-13 20:08:12.000000000 +0100 @@ -91,7 +91,7 @@ request_failure() { local STATUSCODE="${1}" REASON="${2}" REQTYPE="${3}" - # This hook is called when a HTTP request fails (e.g., when the ACME + # This hook is called when an 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. @@ -105,14 +105,21 @@ # The kind of request that was made (GET, POST...) } +startup_hook() { + # This hook is called before the cron command to do some initial tasks + # (e.g. starting a webserver). + + : +} + 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. + # This hook is called at the end of the cron 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 +if [[ "${HANDLER}" =~ ^(deploy_challenge|clean_challenge|deploy_cert|unchanged_cert|invalid_challenge|request_failure|startup_hook|exit_hook)$ ]]; then "$HANDLER" "$@" fi diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dehydrated-0.4.0/docs/per-certificate-config.md new/dehydrated-0.5.0/docs/per-certificate-config.md --- old/dehydrated-0.4.0/docs/per-certificate-config.md 2017-02-05 15:33:17.000000000 +0100 +++ new/dehydrated-0.5.0/docs/per-certificate-config.md 2018-01-13 20:08:12.000000000 +0100 @@ -16,3 +16,10 @@ - WELLKNOWN - OPENSSL_CNF - RENEW_DAYS + +## DOMAINS_D + +If `DOMAINS_D` is set, dehydrated will use it for your per-certificate configurations. +Instead of `certs/example.org/config` it will look for a configuration under `DOMAINS_D/example.org`. + +If an alias is set, it will be used instead of the primary domain name. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dehydrated-0.4.0/docs/troubleshooting.md new/dehydrated-0.5.0/docs/troubleshooting.md --- old/dehydrated-0.4.0/docs/troubleshooting.md 2017-02-05 15:33:17.000000000 +0100 +++ new/dehydrated-0.5.0/docs/troubleshooting.md 2018-01-13 20:08:12.000000000 +0100 @@ -31,8 +31,8 @@ There are a few factors that could result in invalid challenges. -If you are using http validation make sure that the path you have configured with WELLKNOWN is readable under your domain. +If you are using HTTP validation make sure that the path you have configured with WELLKNOWN is readable under your domain. -To test this create a file (e.g. `test.txt`) in that directory and try opening it with your browser: `http://example.org/.well-known/acme-challenge/test.txt`. +To test this create a file (e.g. `test.txt`) in that directory and try opening it with your browser: `http://example.org/.well-known/acme-challenge/test.txt`. Note that if you have an IPv6 address, the challenge connection will be on IPv6. Be sure that you test HTTP connections on both IPv4 and IPv6. Checking the test file in your browser is often not sufficient because the browser just fails over to IPv4. -If you get any error you'll have to fix your webserver configuration. +If you get any error you'll have to fix your web server configuration. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dehydrated-0.4.0/docs/wellknown.md new/dehydrated-0.5.0/docs/wellknown.md --- old/dehydrated-0.4.0/docs/wellknown.md 2017-02-05 15:33:17.000000000 +0100 +++ new/dehydrated-0.5.0/docs/wellknown.md 2018-01-13 20:08:12.000000000 +0100 @@ -65,3 +65,14 @@ "/.well-known/acme-challenge/" => "/var/www/dehydrated/", ) ``` + + +### Hiawatha example config + +With Hiawatha just add an alias to your config file for each VirtualHost and it should work: +```hiawatha +VirtualHost { + Hostname = example.tld subdomain.mywebsite.tld + Alias = /.well-known/acme-challenge:/var/www/dehydrated +} +``` ++++++ dehydrated.keyring ++++++ pub 2048R/F438F333 2013-04-05 uid [ unknown] Lukas Schauer <[email protected]> uid [ unknown] Lukas Schauer <[email protected]> sub 2048R/57805524 2013-04-05 -----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v2 mQENBFFfGhMBCADuxAL1vqC7J1AmxMrFGxobyPaY9tmUEueRF+JuUJlk48qSbcWg zAMEprSgw3HY/15Galu/7g8KxXnlN4WO2vgA6eu1CYx3CoukJ8dc/m6hEMxqwsIW H/1sI7P2hLGB/6YC3MqgpyZxrXzS3coe/JLLkeOtcnBgeT1VpGnodSEKsK4unkfV cmheLuF+zMb0t1DFtd//Ka99XtoF7HXW6p/n8NjiAXKkEkTWf+0qsOIzar3Hl7QE dnEMK1EjDbrqNufTe+TyvM9hVMyDTptvA0EDOj+5Jmt29pWpriOgUgm2D1JgZi9b YmGnTo149q5bUzfLvsTDI0IS7ClxXIES/dfXABEBAAG0IEx1a2FzIFNjaGF1ZXIg PGx1a2FzQHNjaGF1ZXIuc28+iQE5BBMBAgAjBQJSHiDfAhsDBwsJCAcDAgEGFQgC CQoLBBYCAwECHgECF4AACgkQnE2+bPQ48zPRxgf/Y1pJ9H6uB6rmCa3VHoxhvLkV ruUSpI+JXNUhwpUWUKNE1yk78jmjRhMMZf7UMYifyGkuK/0/cErktr5j8kqJ2r60 hOnmkC3jEq5H0hKfGzhosenUvzR9cENYzgnm/4BNWWz1I16jkWRcEGjeC8y033U3 Tjrtc6f0jLe7R6LzospUCWKzp8WUWgTgqpAyjJY6I44Y6QpTjmRF6t1Nz2yRxxf2 NAbOQWkSTueusgLVYyvqLZ51u3fsuDJxbQiBnNt0ZGYSDBKrs59Rvg0Xj1cBv1t7 SrzHuwyiiCQsEaLMvYCygk7qRmZBZ6PKA0gE8oYIr5f10Kx0Mjqnrs8wmpegiLQj THVrYXMgU2NoYXVlciA8bHVrYXMyNTExQHh4cHJvLm5ldD6JATgEEwECACIFAlFf GhMCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEJxNvmz0OPMzHXoH/jCy wwo+W3hy7WAzaKqgnIjRfMQD63OFSwrPI8P7mU0WWrxURwET4C/i+eYHIZYPVaP0 HvdMMcpgSbBZ0sAW+gtv2qMtL+kB4s+FGVchV4lfh4q5w4EDRknuCEpD5bj7NsOT ROvGu0gSvGbGG/EFLJuhrkct5s7ESH5sWonxstk6Ea9L5STR4PGH6swTq0WggbMq VFQuWOkjw6KpQOeFTp9koQl3R6P6I0uqe6tVLKJD/nSTKbMYPMZX9Q+TvzqRSRlH wL509ZIZV4IzdDFXGM28xvC7KIifbxEzWHVci2afdqbVNH2MBhgHt/SIaW8xBab2 wnd45rkdHuoK2wBPlPu5AQ0EUV8aEwEIAM1d0x6B/PUlXfUzkTlYtFmfm67OOPW2 EImld+53RgVc/HGY9RyYP0YwxNs1mjWalzJYV6/aQ9xke/Dz0pLYwIl2c1TCzwin qgymkR17krDJ/+hj2GZBsiEHlMDbWskgwIc7WldhcmxsOvsvRrHSCcw7ZFD+iA9l 6XJoUrtP9QhJLaj6WoX0fU377t3me6hji5387pzYoDKiq8cfJu4q/K6oB42kmo+L PVub+DvBBZPDakDnE46v0LfbgvPqjaVxM2KHjqllepk1CIOAbUbtyC9kVuavDgnI OMe1couHsy0+7fXeQE0xMLPjGGZAXt6OVI8o/1IbgA2EbiVR225Tu2cAEQEAAYkB HwQYAQIACQUCUV8aEwIbDAAKCRCcTb5s9DjzM09cCACGdENt71lx56EjzH6W5o/F OYHHTm4ewcfgGSHWmdScq8gOI414kBkOg9ds9IMQt5hp60hXteSxG1l0qxEXbMX7 cO5FNnjer/ikcwPDS6eZ2a5Gni/h/UFRnVYcw2c+7UAAgouswhwqbkVUrRMDodG2 DT05fQIdgfbQLUBW5qFToS/CXNzvG47jqBEUS/mFMtZgF2+myU2buMlIXmarTi0K EYMt0geGXhpS2DN9iQrQzQ8gjVz/EBgdHbEZOsHW4JMQaycYvouPFVqCIcZoN0s8 c9AilqEu9V8XLLWA0zRVC8Fp6m/ZpMX8t2kVQdBKMHb1NUz0b+uHynANCRQUGKIg =2fWi -----END PGP PUBLIC KEY BLOCK-----
