# Check for script dependencies
check_dependencies() {
# look for required binaries
- for binary in grep mktemp diff sed awk curl cut; do
+ for binary in grep mktemp diff sed awk curl cut; do
bin_path="$(command -v "${binary}" 2>/dev/null)" || _exiterr "This script requires ${binary}."
[[ -x "${bin_path}" ]] || _exiterr "${binary} found in PATH but it's not executable"
done
fi
# Preset
- CA_ZEROSSL=""
+ CA_ZEROSSL="https://acme.zerossl.com/v2/DV90"
CA_LETSENCRYPT="https://acme-v02.api.letsencrypt.org/directory"
# Default values
# Check BASEDIR and set default variables
[[ -d "${BASEDIR}" ]] || _exiterr "BASEDIR does not exist: ${BASEDIR}"
+ # Check for ca cli parameter
+ if [ -n "${PARAM_CA:-}" ]; then
+ CA="${PARAM_CA}"
+ fi
+
# Preset CAs
if [ "${CA}" = "letsencrypt" ]; then
CA="{$CA_LETSENCRYPT}"
CA_NEW_ORDER="$(printf "%s" "${CA_DIRECTORY}" | get_json_string_value newOrder)" &&
CA_NEW_NONCE="$(printf "%s" "${CA_DIRECTORY}" | get_json_string_value newNonce)" &&
CA_NEW_ACCOUNT="$(printf "%s" "${CA_DIRECTORY}" | get_json_string_value newAccount)" &&
- CA_TERMS="$(printf "%s" "${CA_DIRECTORY}" | get_json_string_value termsOfService)" &&
+ CA_TERMS="$(printf "%s" "${CA_DIRECTORY}" | get_json_string_value -p '"meta","termsOfService"')" &&
+ CA_REQUIRES_EAB="$(printf "%s" "${CA_DIRECTORY}" | get_json_bool_value -p '"meta","externalAccountRequired"' || echo false)" &&
CA_REVOKE_CERT="$(printf "%s" "${CA_DIRECTORY}" | get_json_string_value revokeCert)" ||
_exiterr "Problem retrieving ACME/CA-URLs, check if your configured CA points to the directory entrypoint."
# Since acct URI is missing from directory we will assume it is the same as CA_NEW_ACCOUNT without the new part
# Checking for private key ...
register_new_key="no"
+ generated="false"
if [[ -n "${PARAM_ACCOUNT_KEY:-}" ]]; then
# a private key was specified from the command line so use it for this run
echo "Using private key ${PARAM_ACCOUNT_KEY} instead of account key"
fi
echo "+ Generating account key..."
+ generated="true"
local tmp_account_key="$(_mktemp)"
_openssl genrsa -out "${tmp_account_key}" "${KEYSIZE}"
cat "${tmp_account_key}" > "${ACCOUNT_KEY}"
FAILED=true
fi
+ # ZeroSSL special sauce
+ if [[ "${CA}" = "${CA_ZEROSSL}" ]]; then
+ if [[ -z "${EAB_KID:-}" ]] || [[ -z "${EAB_HMAC_KEY:-}" ]]; then
+ if [[ -z "${CONTACT_EMAIL}" ]]; then
+ echo "ZeroSSL requires contact email to be set or EAB_KID/EAB_HMAC_KEY to be manually configured"
+ FAILED=true
+ else
+ zeroapi="$(curl -s "https://api.zerossl.com/acme/eab-credentials-email" -d "email=${CONTACT_EMAIL}" | jsonsh)"
+ EAB_KID="$(printf "%s" "${zeroapi}" | get_json_string_value eab_kid)"
+ EAB_HMAC_KEY="$(printf "%s" "${zeroapi}" | get_json_string_value eab_hmac_key)"
+ if [[ -z "${EAB_KID:-}" ]] || [[ -z "${EAB_HMAC_KEY:-}" ]]; then
+ echo "Unknown error retrieving ZeroSSL API credentials"
+ echo "${zeroapi}"
+ FAILED=true
+ fi
+ fi
+ fi
+ fi
+
+ # Check if external account is required
+ if [[ "${FAILED}" = "false" ]]; then
+ if [[ "${CA_REQUIRES_EAB}" = "true" ]]; then
+ if [[ -z "${EAB_KID:-}" ]] || [[ -z "${EAB_HMAC_KEY:-}" ]]; then
+ FAILED=true
+ echo "This CA requires an external account but no EAB_KID/EAB_HMAC_KEY has been configured"
+ fi
+ fi
+ fi
+
# If an email for the contact has been provided then adding it to the registration request
if [[ "${FAILED}" = "false" ]]; then
if [[ ${API} -eq 1 ]]; then
(signed_request "${CA_NEW_REG}" '{"resource": "new-reg", "agreement": "'"${CA_TERMS}"'"}' > "${ACCOUNT_KEY_JSON}") || FAILED=true
fi
else
- if [[ -n "${CONTACT_EMAIL}" ]]; then
- (signed_request "${CA_NEW_ACCOUNT}" '{"contact":["mailto:'"${CONTACT_EMAIL}"'"], "termsOfServiceAgreed": true}' > "${ACCOUNT_KEY_JSON}") || FAILED=true
+ if [[ -n "${EAB_KID:-}" ]] && [[ -n "${EAB_HMAC_KEY:-}" ]]; then
+ eab_url="${CA_NEW_ACCOUNT}"
+ eab_protected64="$(printf '{"alg":"HS256","kid":"%s","url":"%s"}' "${EAB_KID}" "${eab_url}" | urlbase64)"
+ eab_payload64="$(printf "%s" '{"e": "'"${pubExponent64}"'", "kty": "RSA", "n": "'"${pubMod64}"'"}' | urlbase64)"
+ eab_key="$(printf "%s" "${EAB_HMAC_KEY}" | deurlbase64)"
+ eab_signed64="$(printf '%s' "${eab_protected64}.${eab_payload64}" | "${OPENSSL}" dgst -binary -sha256 -hmac "${eab_key}" | urlbase64)"
+
+ if [[ -n "${CONTACT_EMAIL}" ]]; then
+ regjson='{"contact":["mailto:'"${CONTACT_EMAIL}"'"], "termsOfServiceAgreed": true, "externalAccountBinding": {"protected": "'"${eab_protected64}"'", "payload": "'"${eab_payload64}"'", "signature": "'"${eab_signed64}"'"}}'
+ else
+ regjson='{"termsOfServiceAgreed": true, "externalAccountBinding": {"protected": "'"${eab_protected64}"'", "payload": "'"${eab_payload64}"'", "signature": "'"${eab_signed64}"'"}}'
+ fi
else
- (signed_request "${CA_NEW_ACCOUNT}" '{"termsOfServiceAgreed": true}' > "${ACCOUNT_KEY_JSON}") || FAILED=true
+ if [[ -n "${CONTACT_EMAIL}" ]]; then
+ regjson='{"contact":["mailto:'"${CONTACT_EMAIL}"'"], "termsOfServiceAgreed": true}'
+ else
+ regjson='{"termsOfServiceAgreed": true}'
+ fi
fi
+ (signed_request "${CA_NEW_ACCOUNT}" "${regjson}" > "${ACCOUNT_KEY_JSON}") || FAILED=true
fi
fi
echo >&2
echo >&2
echo "Error registering account key. See message above for more information." >&2
- rm "${ACCOUNT_KEY}" "${ACCOUNT_KEY_JSON}"
+ if [[ "${generated}" = "true" ]]; then
+ rm "${ACCOUNT_KEY}"
+ fi
+ rm -f "${ACCOUNT_KEY_JSON}"
exit 1
fi
elif [[ "${COMMAND:-}" = "register" ]]; then
"${OPENSSL}" base64 -e | tr -d '\n\r' | _sed -e 's:=*$::g' -e 'y:+/:-_:'
}
+# Decode data from url-safe formatted base64
+deurlbase64() {
+ data="$(cat | tr -d ' \n\r')"
+ modlen=$((${#data} % 4))
+ padding=""
+ if [[ "${modlen}" = "2" ]]; then padding="==";
+ elif [[ "${modlen}" = "3" ]]; then padding="="; fi
+ printf "%s%s" "${data}" "${padding}" | tr -d '\n\r' | _sed -e 'y:-_:+/:' | "${OPENSSL}" base64 -d -A
+}
+
# Convert hex string to binary data
hex2bin() {
# Remove spaces, add leading zero, escape as hex string and parse with printf
printf -- "$(cat | _sed -e 's/[[:space:]]//g' -e 's/^(.(.{2})*)$/0\1/' -e 's/(.{2})/\\x\1/g')"
}
+# Convert binary data to hex string
+bin2hex() {
+ hexdump | _sed 's/^[^ ]*//' | tr -d ' \n\r'
+}
+
# OpenSSL writes to stderr/stdout even when there are no errors. So just
# display the output if the exit code was != 0 to simplify debugging.
_openssl() {
fi
;;
+ # PARAM_Usage: --ca url/preset
+ # PARAM_Description: Use specified CA URL or preset
+ --ca)
+ shift 1
+ check_parameters "${1:-}"
+ [[ -n "${PARAM_CA:-}" ]] && _exiterr "CA can only be specified once!"
+ PARAM_CA="${1}"
+ ;;
+
# 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)