aboutsummaryrefslogtreecommitdiffstats
path: root/lfacme-renew.sh
diff options
context:
space:
mode:
Diffstat (limited to 'lfacme-renew.sh')
-rw-r--r--lfacme-renew.sh229
1 files changed, 229 insertions, 0 deletions
diff --git a/lfacme-renew.sh b/lfacme-renew.sh
new file mode 100644
index 0000000..e29788c
--- /dev/null
+++ b/lfacme-renew.sh
@@ -0,0 +1,229 @@
+#! /bin/sh
+# This source code is released into the public domain.
+
+. /usr/local/share/lfacme/init.sh
+
+if ! [ -d "$_UACME_DIR" ]; then
+ _fatal "run lfacme-setup first"
+fi
+
+if ! [ -f "$_DOMAINS" ]; then
+ _fatal "missing $_DOMAINS"
+fi
+
+args=$(getopt v $*)
+if [ $? -ne 0 ]; then
+ exit 1
+fi
+set -- $args
+
+# ARI is broken due to https://github.com/ndilieto/uacme/issues/91
+_uacme_flags="--no-ari"
+
+while :; do
+ case "$1" in
+ -v)
+ _uacme_flags="$_uacme_flags -v"
+ shift;;
+ --)
+ shift; break;;
+ esac
+done
+
+# Create a key if it doesn't already exist. It would be better to always
+# create a new key here, but currently uacme doesn't have a way to tell us
+# that we need to do that.
+_make_key() {
+ local keytype="$1"
+ local keyfile="$2"
+
+ if [ -s "$keyfile" ]; then
+ return 0
+ fi
+
+ local _umask=$(umask)
+ umask 077
+
+ case $keytype in
+ ec) openssl ecparam -name secp384r1 -genkey -noout -out "$keyfile";;
+ rsa) openssl genrsa -out "$keyfile" 3072;;
+ *) _error "%s: unknown key type %s?" "$keyfile" "$keytype"
+ return 1;;
+ esac
+
+ local _ret=$?
+ umask $_umask
+
+ return $_ret
+}
+
+# Create a new CSR for a domain.
+_make_csr() {
+ local csrfile="$1"
+ local keyfile="$2"
+ local domain="$3"
+ local altnames="$4"
+ local csrconf="${csrfile}.cnf"
+
+ cat >"$csrconf" <<EOF
+[req]
+distinguished_name = req_distinguished_name
+req_extensions = req_ext
+prompt = no
+
+[req_distinguished_name]
+commonName = $domain
+
+[req_ext]
+subjectAltName = @alt_names
+
+[alt_names]
+DNS.1 = $domain
+EOF
+
+ local _i=2
+ for altname in $altnames; do
+ printf >>"$csrconf" 'DNS.%d = %s\n' "$_i" "$altname"
+ _i=$((_i + 1))
+ done
+
+ # Generate the CSR
+ openssl req -new -key "$keyfile" -out "$csrfile" -config "$csrconf"
+ return $?
+}
+
+# Process a single cert.
+_docert() {
+ local identifier="$1"; shift
+
+ # uacme creates the cert name by stripping the extension from the
+ # CSR filename, so the basename has to match the identifier.
+ local dir="${_UACME_DIR}/${identifier}"
+ local keyfile="${dir}/${identifier}-key.pem"
+ local csrfile="${dir}/${identifier}.csr"
+ local certfile="${dir}/${identifier}-cert.pem"
+
+ # these can be overridden by args
+ local keytype="ec"
+ local altnames=""
+ local hooks=""
+ local domain=""
+
+ # parse arguments for this cert
+ while ! [ -z "$1" ]; do
+ case "$1" in
+ type=rsa) keytype=rsa;;
+ type=ec) keytype=ec;;
+ type=*) _error "%s: unknown key type: %s" \
+ "$identifier" "${1#type=*}"
+ return 1;;
+ hook=*) hooks="$hooks ${1#hook=*}";;
+ *=*) _error "%s: unknown option: %s" \
+ "$identifier" "$1"
+ return 1;;
+ *.*) altnames="$altnames $1"
+ # Take the domain from the first altname.
+ if [ -z "$domain" ]; then
+ domain="$1"
+ fi
+ ;;
+ *) _error "%s: unknown option: %s" \
+ "$identifier" "$1"
+ return 1;;
+ esac
+ shift
+ done
+
+ # If no altnames were given, the identifier is the domain.
+ if [ -z "$domain" ]; then
+ domain="$identifier"
+ fi
+
+ # make sure all the hook scripts are valid. if the hook name
+ # begins with a '/' it's a full path, otherwise it's related to
+ # ACME_HOOKDIR.
+ local _rhooks=""
+ for hook in $hooks; do
+ if [ "${hook#/*}" = "$hook" ]; then
+ hook="${ACME_HOOKDIR}/$hook"
+ fi
+
+ if ! [ -f "$hook" ]; then
+ _error "%s: hook does not exist: %s" \
+ "$identifier" "$hook"
+ return 1
+ fi
+
+ if ! [ -x "$hook" ]; then
+ _error "%s: hook is not executable: %s" \
+ "$identifier" "$hook"
+ return 1
+ fi
+
+ _rhooks="$_rhooks $hook"
+ done
+
+ mkdir -p -m0700 "$dir"
+
+ if ! _make_key "$keytype" "$keyfile"; then
+ _error "%s: could not create a new private key" "$identifier"
+ return 1
+ fi
+
+ if ! _make_csr "$csrfile" "$keyfile" "$domain" "$altnames"; then
+ _error "%s: could not create the certificate signing request" \
+ "$identifier"
+ return 1
+ fi
+
+ _uacme $_uacme_flags \
+ -h "${_SHARE}/kerberos-challenge.sh" \
+ issue "$csrfile"
+ _ret=$?
+
+ # exit 1 means the cert wasn't reissued
+ if [ "$_ret" -eq 1 ]; then
+ return 0
+ fi
+
+ # exit 2 means an actual error
+ if [ "$_ret" -eq 2 ]; then
+ _error "%s: failed to issue certificate" "$identifier"
+ return 1
+ fi
+
+ # any other non-zero exit code is unexpected
+ if [ "$_ret" -ne 0 ]; then
+ _error "%s: unexpected exit code from uacme: %d" \
+ "$identifier" "$_ret"
+ return 1
+ fi
+
+ # otherwise, exit code is 0 which means we (re)issued the cert,
+ # so run the hooks.
+ for hook in $_rhooks; do
+ env "LFACME_CERT=${identifier}" \
+ "LFACME_KEYFILE=${keyfile}" \
+ "LFACME_CERTFILE=${certfile}" \
+ $hook newcert
+ if [ "$?" -ne 0 ]; then
+ _warn "%s: hook script '%s' failed" \
+ "$identifier" "$hook"
+ fi
+ # should we do anything if the hook failed?
+ done
+
+ return $?
+}
+
+_exit=0
+
+cat "$_DOMAINS" \
+| egrep -v '^(#|[[:space:]]*$)' \
+| while read identifier args; do
+ if ! _docert "$identifier" $args; then
+ _exit=1
+ fi
+done
+
+exit $_exit