#!/usr/bin/env bash
set -Eeuo pipefail

CERT_DIR="${LEGO_CERT_DIR:-/etc/lego/certificates}"
MANAGED_NAME="${LEGO_TRUST_ISSUER_DIR_NAME:-}"

log() { printf 'lego trust-issuer hook: %s\n' "$*"; }
die() { printf 'lego trust-issuer hook: ERROR: %s\n' "$*" >&2; exit 1; }

[[ ${EUID} -eq 0 ]] || die "must run as root"
command -v openssl >/dev/null 2>&1 || die "openssl is required"
[[ -d "${CERT_DIR}" ]] || die "certificate directory not found: ${CERT_DIR}"

if command -v update-ca-certificates >/dev/null 2>&1; then
    trust_root="/usr/local/share/ca-certificates"
    refresh_cmd=(update-ca-certificates --fresh)
elif command -v update-ca-trust >/dev/null 2>&1; then
    trust_root="/etc/pki/ca-trust/source/anchors"
    refresh_cmd=(update-ca-trust extract)
else
    die "no supported system CA trust updater found"
fi

# Derive the managed trust directory from the certificate name that produced
# the issuer file. lego names these <certificate-name>.issuer.crt. For a host
# such as d-cp.bmware.com this yields bmware.com. An explicit
# LEGO_TRUST_ISSUER_DIR_NAME still overrides automatic derivation.
derive_parent_domain() {
    local issuer_path="$1" name
    name="$(basename -- "${issuer_path}" .issuer.crt)"
    name="${name#\*.}"

    # We intentionally require at least three labels before stripping the host
    # label. With only two labels, the name is already a parent domain.
    if [[ "${name}" == *.*.* ]]; then
        printf '%s\n' "${name#*.}"
    elif [[ "${name}" == *.* ]]; then
        printf '%s\n' "${name}"
    else
        return 1
    fi
}

count=0
declare -A seen=()
shopt -s nullglob
for issuer in "${CERT_DIR}"/*.issuer.crt; do
    openssl x509 -in "${issuer}" -noout >/dev/null 2>&1 ||
        die "invalid issuer certificate: ${issuer}"

    # Only CA certificates belong in a system trust store.
    openssl x509 -in "${issuer}" -noout -text 2>/dev/null |
        grep -A2 'X509v3 Basic Constraints' |
        grep -q 'CA:TRUE' || die "issuer is not a CA certificate: ${issuer}"

    fingerprint="$(
        openssl x509 -in "${issuer}" -noout -fingerprint -sha256 |
            sed 's/^.*=//' | tr -d ':' | tr '[:upper:]' '[:lower:]'
    )"
    [[ "${fingerprint}" =~ ^[0-9a-f]{64}$ ]] ||
        die "unable to calculate SHA-256 fingerprint for ${issuer}"

    if [[ -z "${MANAGED_NAME}" ]]; then
        derived_name="$(derive_parent_domain "${issuer}")" ||
            die "unable to derive parent domain from issuer filename: ${issuer}"
        MANAGED_NAME="${derived_name}"
        managed_dir="${trust_root}/${MANAGED_NAME}"
        staging_dir="$(mktemp -d "${trust_root}/.${MANAGED_NAME}.staging.XXXXXX")"
        backup_dir="${managed_dir}.previous.$$"
        cleanup() { rm -rf -- "${staging_dir:-}" "${backup_dir:-}"; }
        trap cleanup EXIT
    else
        managed_dir="${trust_root}/${MANAGED_NAME}"
        if [[ -z "${staging_dir:-}" ]]; then
            staging_dir="$(mktemp -d "${trust_root}/.${MANAGED_NAME}.staging.XXXXXX")"
            backup_dir="${managed_dir}.previous.$$"
            cleanup() { rm -rf -- "${staging_dir:-}" "${backup_dir:-}"; }
            trap cleanup EXIT
        fi
    fi

    [[ -z "${seen[${fingerprint}]:-}" ]] || continue
    seen["${fingerprint}"]=1
    install -m 0644 "${issuer}" "${staging_dir}/${fingerprint}.crt"
    count=$((count + 1))
done

(( count > 0 )) || die "no *.issuer.crt files found in ${CERT_DIR}"

rm -rf -- "${backup_dir}"
if [[ -d "${managed_dir}" ]]; then
    mv -- "${managed_dir}" "${backup_dir}"
fi
if ! mv -- "${staging_dir}" "${managed_dir}"; then
    [[ -d "${backup_dir}" ]] && mv -- "${backup_dir}" "${managed_dir}"
    die "unable to replace managed issuer directory"
fi
staging_dir=""
rm -rf -- "${backup_dir}"

log "installed ${count} issuer CA certificate(s) in ${managed_dir}"
log "refreshing system CA trust"
"${refresh_cmd[@]}"
log "system CA trust updated"
