Skip to content

Commit

Permalink
Merge pull request #2 from lukas2511/master
Browse files Browse the repository at this point in the history
Pull changes from upstream into local repo
  • Loading branch information
aareet authored Aug 24, 2016
2 parents f4decca + 6192b33 commit f39f445
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 38 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,3 @@ hook.sh
certs/*
archive/*
accounts/*
.acme-challenges/*
6 changes: 5 additions & 1 deletion CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ This file contains a log of major changes in letsencrypt.sh
- Config is now named `config` instead of `config.sh`!
- Location of domains.txt is now configurable via DOMAINS_TXT config variable
- Location of certs directory is now configurable via CERTDIR config variable
- signcsr command now also outputs chain certificate
- signcsr command now also outputs chain certificate if --full-chain/-fc is set
- Location of account-key(s) changed
- Default WELLKNOWN location is now `/var/www/letsencrypt`
- New version of Let's Encrypt Subscriber Agreement

## Added
- Added option to add CSR-flag indicating OCSP stapling to be mandatory
- Initial support for configuration on per-certificate base
- Support for per-CA account keys and custom config for output cert directory, license, etc.
- Added option to select IP version of name to address resolution
- Added option to run letsencrypt.sh without locks

## Fixed
- letsencrypt.sh no longer stores account keys from invalid registrations
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,13 @@ Commands:
--env (-e) Output configuration variables for use in other scripts
Parameters:
--full-chain (-fc) Print full chain when using --signcsr
--ipv4 (-4) Resolve names to IPv4 addresses only
--ipv6 (-6) Resolve names to IPv6 addresses only
--domain (-d) domain.tld Use specified domain name(s) instead of domains.txt entry (one certificate!)
--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!)
--ocsp Sets option in CSR indicating OCSP stapling to be mandatory
--privkey (-p) path/to/key.pem Use specified private key instead of account key (useful for revocation)
--config (-f) path/to/config Use specified config file
Expand Down
3 changes: 1 addition & 2 deletions docs/ecc.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
### Elliptic Curve Cryptography (ECC)

This script also supports certificates with Elliptic Curve public keys!
Be aware that at the moment this is not available on the production servers from letsencrypt.
Please read https://community.letsencrypt.org/t/ecdsa-testing-on-staging/8809/ for the current state of ECC support.
Simply set the `KEY_ALGO` variable in one of the config files.
13 changes: 9 additions & 4 deletions docs/examples/config
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,16 @@
# Default values of this config are in comments #
########################################################

# Resolve names to addresses of IP version only. (curl)
# supported values: 4, 6
# default: <unset>
#IP_VERSION=

# Path to certificate authority (default: https://acme-v01.api.letsencrypt.org/directory)
#CA="https://acme-v01.api.letsencrypt.org/directory"

# Path to license agreement (default: https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf)
#LICENSE="https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf"
# Path to license agreement (default: https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf)
#LICENSE="https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf"

# Which challenge should be used? Currently http-01 and dns-01 are supported
#CHALLENGETYPE="http-01"
Expand All @@ -37,8 +42,8 @@
# Directory for account keys and registration information
#ACCOUNTDIR="${BASEDIR}/accounts"

# Output directory for challenge-tokens to be served by webserver or deployed in HOOK (default: $BASEDIR/.acme-challenges)
#WELLKNOWN="${BASEDIR}/.acme-challenges"
# Output directory for challenge-tokens to be served by webserver or deployed in HOOK (default: /var/www/letsencrypt)
#WELLKNOWN="/var/www/letsencrypt"

# Default keysize for private keys (default: 4096)
#KEYSIZE="4096"
Expand Down
5 changes: 1 addition & 4 deletions docs/staging.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Staging

Let’s Encrypt has stringent rate limits in place during the public beta period.
Let’s Encrypt has stringent rate limits in place.

If you start testing using the production endpoint (which is the default),
you will quickly hit these limits and find yourself locked out.
Expand All @@ -10,6 +10,3 @@ To avoid this, please set the CA property to the Let’s Encrypt staging server
```bash
CA="https://acme-staging.api.letsencrypt.org/directory"
```

Please keep in mind that at the time of writing this letsencrypt.sh doesn't have support for registration management,
so if you change CA you'll have to move your `private_key.pem` (and, if you care, `private_key.json`) out of the way.
114 changes: 88 additions & 26 deletions letsencrypt.sh
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ store_configvars() {
__HOOK_CHAIN="${HOOK_CHAIN}"
__OPENSSL_CNF="${OPENSSL_CNF}"
__RENEW_DAYS="${RENEW_DAYS}"
__IP_VERSION="${IP_VERSION}"
}

reset_configvars() {
Expand All @@ -71,6 +72,7 @@ reset_configvars() {
HOOK_CHAIN="${__HOOK_CHAIN}"
OPENSSL_CNF="${__OPENSSL_CNF}"
RENEW_DAYS="${__RENEW_DAYS}"
IP_VERSION="${__IP_VERSION}"
}

# verify configuration values
Expand All @@ -83,6 +85,9 @@ verify_config() {
_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."
if [[ -n "${IP_VERSION}" ]]; then
[[ "${IP_VERSION}" = "4" || "${IP_VERSION}" = "6" ]] || _exiterr "Unknown IP version ${IP_VERSION}... can not continue."
fi
}

# Setup default config values, search for and load configuration files
Expand All @@ -100,11 +105,12 @@ load_config() {

# Default values
CA="https://acme-v01.api.letsencrypt.org/directory"
LICENSE="https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf"
LICENSE="https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf"
CERTDIR=
ACCOUNTDIR=
CHALLENGETYPE="http-01"
CONFIG_D=
DOMAINS_D=
DOMAINS_TXT=
HOOK=
HOOK_CHAIN="no"
Expand All @@ -117,6 +123,7 @@ load_config() {
CONTACT_EMAIL=
LOCKFILE=
OCSP_MUST_STAPLE="no"
IP_VERSION=

if [[ -z "${CONFIG:-}" ]]; then
echo "#" >&2
Expand Down Expand Up @@ -174,14 +181,16 @@ load_config() {

[[ -z "${CERTDIR}" ]] && CERTDIR="${BASEDIR}/certs"
[[ -z "${DOMAINS_TXT}" ]] && DOMAINS_TXT="${BASEDIR}/domains.txt"
[[ -z "${WELLKNOWN}" ]] && WELLKNOWN="${BASEDIR}/.acme-challenges"
[[ -z "${WELLKNOWN}" ]] && WELLKNOWN="/var/www/letsencrypt"
[[ -z "${LOCKFILE}" ]] && LOCKFILE="${BASEDIR}/lock"
[[ -n "${PARAM_NO_LOCK:-}" ]] && LOCKFILE=""

[[ -n "${PARAM_HOOK:-}" ]] && HOOK="${PARAM_HOOK}"
[[ -n "${PARAM_CERTDIR:-}" ]] && CERTDIR="${PARAM_CERTDIR}"
[[ -n "${PARAM_CHALLENGETYPE:-}" ]] && CHALLENGETYPE="${PARAM_CHALLENGETYPE}"
[[ -n "${PARAM_KEY_ALGO:-}" ]] && KEY_ALGO="${PARAM_KEY_ALGO}"
[[ -n "${PARAM_OCSP_MUST_STAPLE:-}" ]] && OCSP_MUST_STAPLE="${PARAM_OCSP_MUST_STAPLE}"
[[ -n "${PARAM_IP_VERSION:-}" ]] && IP_VERSION="${PARAM_IP_VERSION}"

verify_config
store_configvars
Expand All @@ -192,11 +201,13 @@ init_system() {
load_config

# Lockfile handling (prevents concurrent access)
LOCKDIR="$(dirname "${LOCKFILE}")"
[[ -w "${LOCKDIR}" ]] || _exiterr "Directory ${LOCKDIR} for LOCKFILE ${LOCKFILE} is not writable, aborting."
( set -C; date > "${LOCKFILE}" ) 2>/dev/null || _exiterr "Lock file '${LOCKFILE}' present, aborting."
remove_lock() { rm -f "${LOCKFILE}"; }
trap 'remove_lock' EXIT
if [[ -n "${LOCKFILE}" ]]; then
LOCKDIR="$(dirname "${LOCKFILE}")"
[[ -w "${LOCKDIR}" ]] || _exiterr "Directory ${LOCKDIR} for LOCKFILE ${LOCKFILE} is not writable, aborting."
( set -C; date > "${LOCKFILE}" ) 2>/dev/null || _exiterr "Lock file '${LOCKFILE}' present, aborting."
remove_lock() { rm -f "${LOCKFILE}"; }
trap 'remove_lock' EXIT
fi

# Get CA URLs
CA_DIRECTORY="$(http_request get "${CA}")"
Expand Down Expand Up @@ -315,15 +326,19 @@ _openssl() {
http_request() {
tempcont="$(_mktemp)"

if [[ -n "${IP_VERSION:-}" ]]; then
ip_version="-${IP_VERSION}"
fi

set +e
if [[ "${1}" = "head" ]]; then
statuscode="$(curl -s -w "%{http_code}" -o "${tempcont}" "${2}" -I)"
statuscode="$(curl ${ip_version:-} -s -w "%{http_code}" -o "${tempcont}" "${2}" -I)"
curlret="${?}"
elif [[ "${1}" = "get" ]]; then
statuscode="$(curl -s -w "%{http_code}" -o "${tempcont}" "${2}")"
statuscode="$(curl ${ip_version:-} -s -w "%{http_code}" -o "${tempcont}" "${2}")"
curlret="${?}"
elif [[ "${1}" = "post" ]]; then
statuscode="$(curl -s -w "%{http_code}" -o "${tempcont}" "${2}" -d "${3}")"
statuscode="$(curl ${ip_version:-} -s -w "%{http_code}" -o "${tempcont}" "${2}" -d "${3}")"
curlret="${?}"
else
set -e
Expand All @@ -340,6 +355,8 @@ http_request() {
echo >&2
echo "Details:" >&2
cat "${tempcont}" >&2
echo >&2
echo >&2
rm -f "${tempcont}"

# Wait for hook script to clean the challenge if used
Expand Down Expand Up @@ -664,7 +681,13 @@ command_sign_domains() {
# for now this loads the certificate specific config in a subshell and parses a diff of set variables.
# 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 [ -f "${CERTDIR}/${domain}/config" ]; then
if [[ -n "${DOMAINS_D}" ]]; then
certconfig="${DOMAINS_D}/${domain}"
else
certconfig="${CERTDIR}/${domain}/config"
fi

if [ -f "${certconfig}" ]; then
echo " + Using certificate specific config file!"
ORIGIFS="${IFS}"
IFS=$'\n'
Expand All @@ -673,7 +696,7 @@ command_sign_domains() {
aftervars="$(_mktemp)"
set > "${beforevars}"
# shellcheck disable=SC1090
. "${CERTDIR}/${domain}/config"
. "${certconfig}"
set > "${aftervars}"
diff -u "${beforevars}" "${aftervars}" | grep -E '^\+[^+]'
rm "${beforevars}"
Expand Down Expand Up @@ -733,7 +756,12 @@ command_sign_domains() {
fi

# shellcheck disable=SC2086
sign_domain ${line}
if [[ "${PARAM_KEEP_GOING:-}" = "yes" ]]; then
sign_domain ${line} &
wait $! || true
else
sign_domain ${line}
fi
done

# remove temporary domains.txt file if used
Expand All @@ -760,24 +788,29 @@ command_sign_csr() {
certfile="$(_mktemp)"
sign_csr "$(< "${csrfile}" )" 3> "${certfile}"

# get and convert ca cert
chainfile="$(_mktemp)"
http_request get "$(openssl x509 -in "${certfile}" -noout -text | grep 'CA Issuers - URI:' | cut -d':' -f2-)" > "${chainfile}"

if ! grep -q "BEGIN CERTIFICATE" "${chainfile}"; then
openssl x509 -inform DER -in "${chainfile}" -outform PEM -out "${chainfile}"
fi

# output full chain
# print cert
echo "# CERT #" >&3
cat "${certfile}" >&3
echo >&3
echo "# CHAIN #" >&3
cat "${chainfile}" >&3

# print chain
if [ -n "${PARAM_FULL_CHAIN:-}" ]; then
# get and convert ca cert
chainfile="$(_mktemp)"
http_request get "$(openssl x509 -in "${certfile}" -noout -text | grep 'CA Issuers - URI:' | cut -d':' -f2-)" > "${chainfile}"

if ! grep -q "BEGIN CERTIFICATE" "${chainfile}"; then
openssl x509 -inform DER -in "${chainfile}" -outform PEM -out "${chainfile}"
fi

echo "# CHAIN #" >&3
cat "${chainfile}" >&3

rm "${chainfile}"
fi

# cleanup
rm "${certfile}"
rm "${chainfile}"

exit 0
}
Expand Down Expand Up @@ -893,7 +926,7 @@ command_help() {
command_env() {
echo "# letsencrypt.sh configuration"
load_config
typeset -p CA LICENSE CERTDIR CHALLENGETYPE DOMAINS_TXT HOOK HOOK_CHAIN RENEW_DAYS ACCOUNT_KEY ACCOUNT_KEY_JSON KEYSIZE WELLKNOWN PRIVATE_KEY_RENEW OPENSSL_CNF CONTACT_EMAIL LOCKFILE
typeset -p CA LICENSE CERTDIR CHALLENGETYPE DOMAINS_D DOMAINS_TXT HOOK HOOK_CHAIN RENEW_DAYS ACCOUNT_KEY ACCOUNT_KEY_JSON KEYSIZE WELLKNOWN PRIVATE_KEY_RENEW OPENSSL_CNF CONTACT_EMAIL LOCKFILE
}

# Main method (parses script arguments and calls command_* methods)
Expand Down Expand Up @@ -950,6 +983,24 @@ main() {
set_command cleanup
;;

# PARAM_Usage: --full-chain (-fc)
# PARAM_Description: Print full chain when using --signcsr
--full-chain|-fc)
PARAM_FULL_CHAIN="1"
;;

# PARAM_Usage: --ipv4 (-4)
# PARAM_Description: Resolve names to IPv4 addresses only
--ipv4|-4)
PARAM_IP_VERSION="4"
;;

# PARAM_Usage: --ipv6 (-6)
# PARAM_Description: Resolve names to IPv6 addresses only
--ipv6|-6)
PARAM_IP_VERSION="6"
;;

# PARAM_Usage: --domain (-d) domain.tld
# PARAM_Description: Use specified domain name(s) instead of domains.txt entry (one certificate!)
--domain|-d)
Expand All @@ -962,13 +1013,24 @@ main() {
fi
;;

# PARAM_Usage: --keep-going (-g)
# PARAM_Description: Keep going after encountering an error while creating/renewing multiple certificates in cron mode
--keep-going|-g)
PARAM_KEEP_GOING="yes"
;;

# PARAM_Usage: --force (-x)
# PARAM_Description: Force renew of certificate even if it is longer valid than value in RENEW_DAYS
--force|-x)
PARAM_FORCE="yes"
;;

# PARAM_Usage: --no-lock (-n)
# PARAM_Description: Don't use lockfile (potentially dangerous!)
--no-lock|-n)
PARAM_NO_LOCK="yes"
;;

# PARAM_Usage: --ocsp
# PARAM_Description: Sets option in CSR indicating OCSP stapling to be mandatory
--ocsp)
Expand Down

0 comments on commit f39f445

Please sign in to comment.