ask user to read and accept license, added register-command, fullchain.pem is now actually the full chain

This commit is contained in:
Lukas Schauer
2017-01-29 18:54:10 +01:00
parent b2376ed437
commit 6a32f20e00
6 changed files with 127 additions and 35 deletions

View File

@@ -3,9 +3,11 @@ This file contains a log of major changes in dehydrated
## [x.x.x] - xxxx-xx-xx ## [x.x.x] - xxxx-xx-xx
## Changed ## Changed
- dehydrated now asks you to read and accept the CAs terms of service before creating an account
- Skip challenges for already validated domains - Skip challenges for already validated domains
- Removed need for some special commands (BusyBox compatibility) - Removed need for some special commands (BusyBox compatibility)
- Exported a few more variables for use in hook-scripts - Exported a few more variables for use in hook-scripts
- fullchain.pem now actually contains the full chain instead of just the certificate with an intermediate cert
## Added ## Added
- Added private-key rollover functionality - Added private-key rollover functionality
@@ -13,6 +15,7 @@ This file contains a log of major changes in dehydrated
- Added `invalid_challenge` hook - Added `invalid_challenge` hook
- Added `request_failure` hook - Added `request_failure` hook
- Added `exit_hook` hook - Added `exit_hook` hook
- Added standalone `register` command
## [0.3.1] - 2016-09-13 ## [0.3.1] - 2016-09-13
## Changed ## Changed

View File

@@ -46,6 +46,7 @@ Usage: ./dehydrated [-h] [command [argument]] [parameter [argument]] [parameter
Default command: help Default command: help
Commands: Commands:
--register Register account key
--cron (-c) Sign/renew non-existant/changed/expiring certificates. --cron (-c) Sign/renew non-existant/changed/expiring certificates.
--signcsr (-s) path/to/csr.pem Sign a given CSR, output CRT on stdout (advanced usage) --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 --revoke (-r) path/to/cert.pem Revoke specified certificate
@@ -54,6 +55,7 @@ Commands:
--env (-e) Output configuration variables for use in other scripts --env (-e) Output configuration variables for use in other scripts
Parameters: Parameters:
--accept-terms Accept CAs terms of service
--full-chain (-fc) Print full chain when using --signcsr --full-chain (-fc) Print full chain when using --signcsr
--ipv4 (-4) Resolve names to IPv4 addresses only --ipv4 (-4) Resolve names to IPv4 addresses only
--ipv6 (-6) Resolve names to IPv6 addresses only --ipv6 (-6) Resolve names to IPv6 addresses only
@@ -61,6 +63,7 @@ Parameters:
--keep-going (-g) Keep going after encountering an error while creating/renewing multiple certificates in cron mode --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 --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!) --no-lock (-n) Don't use lockfile (potentially dangerous!)
--lock-suffix example.com Suffix lockfile name with a string (useful for with -d)
--ocsp Sets option in CSR indicating OCSP stapling to be mandatory --ocsp Sets option in CSR indicating OCSP stapling to be mandatory
--privkey (-p) path/to/key.pem Use specified private key instead of account key (useful for revocation) --privkey (-p) path/to/key.pem Use specified private key instead of account key (useful for revocation)
--config (-f) path/to/config Use specified config file --config (-f) path/to/config Use specified config file

View File

@@ -105,7 +105,8 @@ load_config() {
# Default values # Default values
CA="https://acme-v01.api.letsencrypt.org/directory" CA="https://acme-v01.api.letsencrypt.org/directory"
LICENSE="https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf" CA_TERMS="https://acme-v01.api.letsencrypt.org/terms"
LICENSE=
CERTDIR= CERTDIR=
ACCOUNTDIR= ACCOUNTDIR=
CHALLENGETYPE="http-01" CHALLENGETYPE="http-01"
@@ -233,6 +234,24 @@ init_system() {
else else
# Check if private account key exists, if it doesn't exist yet generate a new one (rsa key) # Check if private account key exists, if it doesn't exist yet generate a new one (rsa key)
if [[ ! -e "${ACCOUNT_KEY}" ]]; then if [[ ! -e "${ACCOUNT_KEY}" ]]; then
REAL_LICENSE="$(http_request head "${CA_TERMS}" | (grep Location: || true) | awk -F ': ' '{print $2}' | tr -d '\n\r')"
if [[ -z "${REAL_LICENSE}" ]]; then
printf '\n'
printf 'Error retrieving terms of service from certificate authority.\n'
printf 'Please set LICENSE in config manually.\n'
exit 1
fi
if [[ ! "${LICENSE}" = "${REAL_LICENSE}" ]]; then
if [[ "${PARAM_ACCEPT_TERMS:-}" = "yes" ]]; then
LICENSE="${REAL_LICENSE}"
else
printf '\n'
printf 'To use dehydrated with this certificate authority you have to agree to their terms of service which you can find here: %s\n\n' "${REAL_LICENSE}"
printf 'To accept these terms of service run `%s --register --accept-terms`.\n' "${0}"
exit 1
fi
fi
echo "+ Generating account key..." echo "+ Generating account key..."
_openssl genrsa -out "${ACCOUNT_KEY}" "${KEYSIZE}" _openssl genrsa -out "${ACCOUNT_KEY}" "${KEYSIZE}"
register_new_key="yes" register_new_key="yes"
@@ -360,29 +379,31 @@ http_request() {
fi fi
if [[ ! "${statuscode:0:1}" = "2" ]]; then if [[ ! "${statuscode:0:1}" = "2" ]]; then
echo " + ERROR: An error occurred while sending ${1}-request to ${2} (Status ${statuscode})" >&2 if [[ ! "${2}" = "${CA_TERMS}" ]] || [[ ! "${statuscode:0:1}" = "3" ]]; then
echo >&2 echo " + ERROR: An error occurred while sending ${1}-request to ${2} (Status ${statuscode})" >&2
echo "Details:" >&2 echo >&2
cat "${tempcont}" >&2 echo "Details:" >&2
echo >&2 cat "${tempcont}" >&2
echo >&2 echo >&2
echo >&2
# An exclusive hook for the {1}-request error might be useful (e.g., for sending an e-mail to admins) # 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 if [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" != "yes" ]]; then
errtxt=`cat ${tempcont}` errtxt=`cat ${tempcont}`
"${HOOK}" "request_failure" "${statuscode}" "${errtxt}" "${1}" "${HOOK}" "request_failure" "${statuscode}" "${errtxt}" "${1}"
fi
rm -f "${tempcont}"
# Wait for hook script to clean the challenge if used
if [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" != "yes" ]] && [[ -n "${challenge_token:+set}" ]]; then
"${HOOK}" "clean_challenge" '' "${challenge_token}" "${keyauth}"
fi
# remove temporary domains.txt file if used
[[ -n "${PARAM_DOMAIN:-}" && -n "${DOMAINS_TXT:-}" ]] && rm "${DOMAINS_TXT}"
exit 1
fi fi
rm -f "${tempcont}"
# Wait for hook script to clean the challenge if used
if [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" != "yes" ]] && [[ -n "${challenge_token:+set}" ]]; then
"${HOOK}" "clean_challenge" '' "${challenge_token}" "${keyauth}"
fi
# remove temporary domains.txt file if used
[[ -n "${PARAM_DOMAIN:-}" && -n "${DOMAINS_TXT:-}" ]] && rm "${DOMAINS_TXT}"
exit 1
fi fi
cat "${tempcont}" cat "${tempcont}"
@@ -600,6 +621,39 @@ sign_csr() {
echo " + Done!" echo " + Done!"
} }
walk_chain() {
certificate="${1}"
# grep uri from certificate
local issuer_cert_uri
issuer_cert_uri="$(openssl x509 -in "${certificate}" -noout -text | (grep 'CA Issuers - URI:' | cut -d':' -f2-) || true)"
if [[ -n "${issuer_cert_uri}" ]]; then
# create temporary files
local tmpcert
local tmpcert_raw
tmpcert_raw="$(_mktemp)"
tmpcert="$(_mktemp)"
# download certificate
http_request get "${issuer_cert_uri}" > "${tmpcert_raw}"
# PEM
if grep -q "BEGIN CERTIFICATE" "${tmpcert_raw}"; then mv "${tmpcert_raw}" "${tmpcert}"
# DER
elif openssl x509 -in "${tmpcert_raw}" -inform DER -out "${tmpcert}" -outform PEM 2> /dev/null > /dev/null; then :
# PKCS7
elif openssl pkcs7 -in "${tmpcert_raw}" -inform DER -out "${tmpcert}" -outform PEM -print_certs 2> /dev/null > /dev/null; then :
# Unknown certificate type
else _exiterr "Unknown certificate type in chain"
fi
printf "\n%s\n" "${issuer_cert_uri}"
cat "${tmpcert}"
walk_chain "${tmpcert}"
rm -f "${tmpcert}" "${tmpcert_raw}"
fi
}
# Create certificate for domain(s) # Create certificate for domain(s)
sign_domain() { sign_domain() {
domain="${1}" domain="${1}"
@@ -672,14 +726,7 @@ sign_domain() {
# Create fullchain.pem # Create fullchain.pem
echo " + Creating fullchain.pem..." echo " + Creating fullchain.pem..."
cat "${crt_path}" > "${CERTDIR}/${domain}/fullchain-${timestamp}.pem" cat "${crt_path}" > "${CERTDIR}/${domain}/fullchain-${timestamp}.pem"
tmpchain="$(_mktemp)" walk_chain "${crt_path}" > "${CERTDIR}/${domain}/chain-${timestamp}.pem"
http_request get "$(openssl x509 -in "${CERTDIR}/${domain}/cert-${timestamp}.pem" -noout -text | grep 'CA Issuers - URI:' | cut -d':' -f2-)" > "${tmpchain}"
if grep -q "BEGIN CERTIFICATE" "${tmpchain}"; then
mv "${tmpchain}" "${CERTDIR}/${domain}/chain-${timestamp}.pem"
else
openssl x509 -in "${tmpchain}" -inform DER -out "${CERTDIR}/${domain}/chain-${timestamp}.pem" -outform PEM
rm "${tmpchain}"
fi
cat "${CERTDIR}/${domain}/chain-${timestamp}.pem" >> "${CERTDIR}/${domain}/fullchain-${timestamp}.pem" cat "${CERTDIR}/${domain}/chain-${timestamp}.pem" >> "${CERTDIR}/${domain}/fullchain-${timestamp}.pem"
# Update symlinks # Update symlinks
@@ -697,6 +744,13 @@ sign_domain() {
echo " + Done!" echo " + Done!"
} }
# Usage: --register
# Description: Register account key
command_register() {
init_system
exit 0
}
# Usage: --cron (-c) # Usage: --cron (-c)
# Description: Sign/renew non-existant/changed/expiring certificates. # Description: Sign/renew non-existant/changed/expiring certificates.
command_sign_domains() { command_sign_domains() {
@@ -1024,6 +1078,16 @@ main() {
set_command sign_domains set_command sign_domains
;; ;;
--register)
set_command register
;;
# PARAM_Usage: --accept-terms
# PARAM_Description: Accept CAs terms of service
--accept-terms)
PARAM_ACCEPT_TERMS="yes"
;;
--signcsr|-s) --signcsr|-s)
shift 1 shift 1
set_command sign_csr set_command sign_csr
@@ -1166,6 +1230,7 @@ main() {
case "${COMMAND}" in case "${COMMAND}" in
env) command_env;; env) command_env;;
sign_domains) command_sign_domains;; sign_domains) command_sign_domains;;
register) command_register;;
sign_csr) command_sign_csr "${PARAM_CSR}";; sign_csr) command_sign_csr "${PARAM_CSR}";;
revoke) command_revoke "${PARAM_REVOKECERT}";; revoke) command_revoke "${PARAM_REVOKECERT}";;
cleanup) command_cleanup;; cleanup) command_cleanup;;

View File

@@ -18,8 +18,11 @@
# Path to certificate authority (default: https://acme-v01.api.letsencrypt.org/directory) # Path to certificate authority (default: https://acme-v01.api.letsencrypt.org/directory)
#CA="https://acme-v01.api.letsencrypt.org/directory" #CA="https://acme-v01.api.letsencrypt.org/directory"
# Path to license agreement (default: https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf) # Path to certificate authority license terms redirect (default: https://acme-v01.api.letsencrypt.org/terms)
#LICENSE="https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf" #CA_TERMS="https://acme-v01.api.letsencrypt.org/terms"
# Path to license agreement (default: <unset>)
#LICENSE=""
# Which challenge should be used? Currently http-01 and dns-01 are supported # Which challenge should be used? Currently http-01 and dns-01 are supported
#CHALLENGETYPE="http-01" #CHALLENGETYPE="http-01"

View File

@@ -9,4 +9,5 @@ To avoid this, please set the CA property to the Lets Encrypt staging server
```bash ```bash
CA="https://acme-staging.api.letsencrypt.org/directory" CA="https://acme-staging.api.letsencrypt.org/directory"
CA_TERMS="https://acme-staging.api.letsencrypt.org/terms"
``` ```

23
test.sh
View File

@@ -97,7 +97,7 @@ mkdir -p .acme-challenges/.well-known/acme-challenge
# Generate config and create empty domains.txt # Generate config and create empty domains.txt
echo 'CA="https://testca.kurz.pw/directory"' > config echo 'CA="https://testca.kurz.pw/directory"' > config
echo 'LICENSE="https://testca.kurz.pw/terms/v1"' >> config echo 'CA_TERMS="https://testca.kurz.pw/terms"' >> config
echo 'WELLKNOWN=".acme-challenges/.well-known/acme-challenge"' >> config echo 'WELLKNOWN=".acme-challenges/.well-known/acme-challenge"' >> config
echo 'RENEW_DAYS="14"' >> config echo 'RENEW_DAYS="14"' >> config
touch domains.txt touch domains.txt
@@ -110,6 +110,23 @@ _CHECK_LOG "--help (-h)"
_CHECK_LOG "--domain (-d) domain.tld" _CHECK_LOG "--domain (-d) domain.tld"
_CHECK_ERRORLOG _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) # 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" _TEST "First run in cron mode, checking if private key is generated and registered"
./dehydrated --cron > tmplog 2> errorlog || _FAIL "Script execution failed" ./dehydrated --cron > tmplog 2> errorlog || _FAIL "Script execution failed"
@@ -120,7 +137,7 @@ _CHECK_ERRORLOG
# Temporarily move config out of the way and try signing certificate by using temporary config location # 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" _TEST "Try signing using temporary config location and with domain as command line parameter"
mv config tmp_config mv config tmp_config
./dehydrated --cron --domain "${TMP_URL}" --domain "${TMP2_URL}" -f tmp_config > tmplog 2> errorlog || _FAIL "Script execution failed" ./dehydrated --cron --domain "${TMP_URL}" --domain "${TMP2_URL}" --accept-terms -f tmp_config > tmplog 2> errorlog || _FAIL "Script execution failed"
_CHECK_NOT_LOG "Checking domain name(s) of existing cert" _CHECK_NOT_LOG "Checking domain name(s) of existing cert"
_CHECK_LOG "Generating private key" _CHECK_LOG "Generating private key"
_CHECK_LOG "Requesting challenge for ${TMP_URL}" _CHECK_LOG "Requesting challenge for ${TMP_URL}"
@@ -168,7 +185,7 @@ _CHECK_NOT_LOG "Generating private key"
_CHECK_LOG "Requesting challenge for ${TMP_URL}" _CHECK_LOG "Requesting challenge for ${TMP_URL}"
_CHECK_LOG "Requesting challenge for ${TMP2_URL}" _CHECK_LOG "Requesting challenge for ${TMP2_URL}"
_CHECK_LOG "Requesting challenge for ${TMP3_URL}" _CHECK_LOG "Requesting challenge for ${TMP3_URL}"
_CHECK_LOG "Challenge is valid!" _CHECK_LOG "Already validated!"
_CHECK_LOG "Creating fullchain.pem" _CHECK_LOG "Creating fullchain.pem"
_CHECK_LOG "Done!" _CHECK_LOG "Done!"
_CHECK_ERRORLOG _CHECK_ERRORLOG