#!/usr/bin/env bash # Install Let's Encrypt staging root CAs into a trust store so tools # accept staging-issued certificates. Idempotent. # # Usage: # scripts/trust-letsencrypt-staging.sh [install|remove] [--target system|firefox|all] # # Targets: # system — /etc/ca-certificates/trust-source/anchors (Arch/Manjaro) # or /usr/local/share/ca-certificates (Debian/Ubuntu). # Trusted by curl, openssl, wget, Python, Go. Needs sudo. # firefox — every Firefox profile under ~/.mozilla/firefox/*/cert9.db # plus Flatpak/Snap profiles. User-level; do NOT run as root. # all — both. Invoke twice (with sudo for system, as user for firefox). set -euo pipefail ROOTS=( "https://letsencrypt.org/certs/staging/letsencrypt-stg-root-x1.pem" "https://letsencrypt.org/certs/staging/letsencrypt-stg-root-x2.pem" ) cmd=install target=system while [[ $# -gt 0 ]]; do case "$1" in install|remove) cmd=$1; shift ;; --target) target=${2:-}; shift 2 ;; --target=*) target=${1#*=}; shift ;; -h|--help) sed -n '2,14p' "$0"; exit 0 ;; *) echo "unknown arg: $1" >&2; exit 2 ;; esac done [[ "$target" =~ ^(system|firefox|all)$ ]] || { echo "invalid --target: $target" >&2; exit 2; } tmpdir=$(mktemp -d) trap 'rm -rf "$tmpdir"' EXIT # Download once; used by both targets. download_roots() { for url in "${ROOTS[@]}"; do name=$(basename "$url" .pem) if [[ ! -s "$tmpdir/${name}.pem" ]]; then curl -fsSL "$url" -o "$tmpdir/${name}.pem" fi done } run_system() { if [[ $EUID -ne 0 ]]; then echo "target=system: must run as root (writes to /etc)" >&2 exit 1 fi if [[ -d /etc/ca-certificates/trust-source/anchors ]]; then anchor_dir=/etc/ca-certificates/trust-source/anchors ext=.pem update=(update-ca-trust extract) elif [[ -d /usr/local/share/ca-certificates ]]; then anchor_dir=/usr/local/share/ca-certificates ext=.crt update=(update-ca-certificates) else echo "no known CA anchor directory" >&2 exit 1 fi case "$cmd" in install) download_roots changed=0 for url in "${ROOTS[@]}"; do name=$(basename "$url" .pem) dest="$anchor_dir/${name}${ext}" if [[ -f "$dest" ]]; then echo "system: already installed $dest" continue fi cp "$tmpdir/${name}.pem" "$dest" chmod 644 "$dest" echo "system: installed $dest" changed=1 done [[ $changed -eq 1 ]] && "${update[@]}" ;; remove) changed=0 for url in "${ROOTS[@]}"; do name=$(basename "$url" .pem) dest="$anchor_dir/${name}${ext}" [[ -f "$dest" ]] || continue rm -f "$dest" echo "system: removed $dest" changed=1 done [[ $changed -eq 1 ]] && "${update[@]}" || echo "system: nothing to remove" ;; esac } run_firefox() { if [[ $EUID -eq 0 ]]; then echo "target=firefox: do NOT run as root (needs to write to user's profile)" >&2 exit 1 fi command -v certutil >/dev/null || { echo "certutil not found — install the 'nss' package (Arch: pacman -S nss)" >&2 exit 1 } # Find every Firefox profile (directory containing cert9.db) across # known Firefox install flavors: native, XDG-config location some # distro builds use, Flatpak, and Snap. search_dirs=( "$HOME/.mozilla/firefox" "$HOME/.config/mozilla/firefox" "$HOME/.var/app/org.mozilla.firefox/.mozilla/firefox" "$HOME/snap/firefox/common/.mozilla/firefox" ) existing=() for d in "${search_dirs[@]}"; do [[ -d "$d" ]] && existing+=("$d"); done if [[ ${#existing[@]} -eq 0 ]]; then echo "firefox: no Firefox config directory found in any of:" >&2 printf " %s\n" "${search_dirs[@]}" >&2 exit 1 fi mapfile -t profiles < <( find "${existing[@]}" -maxdepth 2 -type f -name cert9.db 2>/dev/null \ | xargs -r -n1 dirname ) if [[ ${#profiles[@]} -eq 0 ]]; then echo "firefox: no profiles with cert9.db found" >&2 exit 1 fi [[ "$cmd" == "install" ]] && download_roots for prof in "${profiles[@]}"; do echo "firefox: profile $prof" for url in "${ROOTS[@]}"; do name=$(basename "$url" .pem) case "$cmd" in install) if certutil -d "sql:$prof" -L -n "$name" >/dev/null 2>&1; then echo " already installed: $name" continue fi certutil -d "sql:$prof" -A -t "C,," -n "$name" -i "$tmpdir/${name}.pem" echo " installed: $name" ;; remove) if certutil -d "sql:$prof" -L -n "$name" >/dev/null 2>&1; then certutil -d "sql:$prof" -D -n "$name" echo " removed: $name" fi ;; esac done done echo "firefox: restart Firefox to pick up changes" } case "$target" in system) run_system ;; firefox) run_firefox ;; all) run_system; run_firefox ;; esac