#!/bin/bash # /usr/share/BastionGuard/data/scripts/install-ca-system.sh set -euo pipefail export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" CA_NAME="BastionGuard-CA" log() { echo "[BastionGuard] $*"; } warn() { echo "[BastionGuard] ⚠️ $*" >&2; } err() { echo "[BastionGuard] ❌ $*" >&2; } have_cmd() { command -v "$1" >/dev/null 2>&1; } # ── Utente/home reali + layout CA utente ───────────────────────────────────── # Regola: anche se questo helper gira come root (pkexec/sudo), i file privati # della CA devono vivere nella HOME dell'utente grafico/reale e devono restare # posseduti da quell'utente. Root serve solo per copiare la CA pubblica nei # trust store di sistema. get_real_uid() { # pkexec espone l'UID del chiamante; sudo espone SUDO_UID. Sono più # affidabili di USER/HOME, perché in root diventano spesso root:/root. if [[ -n "${PKEXEC_UID:-}" && "${PKEXEC_UID:-}" =~ ^[0-9]+$ && "${PKEXEC_UID}" != "0" ]]; then printf '%s' "$PKEXEC_UID" return 0 fi if [[ -n "${SUDO_UID:-}" && "${SUDO_UID:-}" =~ ^[0-9]+$ && "${SUDO_UID}" != "0" ]]; then printf '%s' "$SUDO_UID" return 0 fi local u="" if [[ -n "${SUDO_USER:-}" && "${SUDO_USER:-}" != "root" ]] && id "$SUDO_USER" >/dev/null 2>&1; then id -u "$SUDO_USER" return 0 fi u="$(logname 2>/dev/null || true)" if [[ -n "$u" && "$u" != "root" ]] && id "$u" >/dev/null 2>&1; then id -u "$u" return 0 fi for cand in "${PKEXEC_USER:-}" "${USER:-}" "${LOGNAME:-}"; do if [[ -n "${cand:-}" && "$cand" != "root" ]] && id "$cand" >/dev/null 2>&1; then id -u "$cand" return 0 fi done return 1 } get_user_for_uid() { local uid="$1" [[ -n "$uid" ]] || return 1 getent passwd "$uid" | cut -d: -f1 } get_group_for_uid() { local uid="$1" [[ -n "$uid" ]] || return 1 getent passwd "$uid" | cut -d: -f4 | xargs -r getent group | cut -d: -f1 } get_home_for_uid() { local uid="$1" [[ -n "$uid" ]] || return 1 getent passwd "$uid" | cut -d: -f6 } path_under() { local child="$1" local parent="$2" [[ -n "$child" && -n "$parent" ]] || return 1 child="$(readlink -m -- "$child")" parent="$(readlink -m -- "$parent")" [[ "$child" == "$parent" || "$child" == "$parent"/* ]] } selinux_status() { if have_cmd getenforce; then getenforce 2>/dev/null || printf 'Disabled' elif [[ -r /sys/fs/selinux/enforce ]]; then if [[ "$(cat /sys/fs/selinux/enforce 2>/dev/null || echo 0)" == "1" ]]; then printf 'Enforcing' else printf 'Permissive' fi else printf 'Disabled' fi } selinux_enabled() { local s s="$(selinux_status 2>/dev/null || printf 'Disabled')" [[ "$s" != "Disabled" ]] } selinux_enforcing() { [[ "$(selinux_status 2>/dev/null || printf 'Disabled')" == "Enforcing" ]] } restore_selinux_context() { # Fedora/RHEL hanno SELinux attivo di default: dopo install/chown/mkdir # ripristiniamo sempre i label standard. Su distro senza SELinux non è fatale. if ! have_cmd restorecon; then if selinux_enabled; then warn "SELinux è attivo ma restorecon non è disponibile; installare policycoreutils" fi return 0 fi local p for p in "$@"; do [[ -n "${p:-}" && -e "$p" ]] || continue if ! restorecon -RF "$p" >/dev/null 2>&1; then selinux_enforcing && warn "restorecon fallito su: $p" fi done } fix_selinux_system_trust_contexts() { selinux_enabled || return 0 restore_selinux_context \ /etc/pki/ca-trust \ /etc/pki/ca-trust/source \ /etc/pki/ca-trust/source/anchors \ /etc/pki/tls \ /etc/pki/nssdb \ /usr/share/pki/nssdb \ /etc/ssl/certs \ /usr/share/ca-certificates \ /usr/local/share/ca-certificates \ /etc/ca-certificates 2>/dev/null || true } fix_selinux_user_browser_contexts() { selinux_enabled || return 0 [[ -n "${REAL_HOME:-}" ]] || return 0 restore_selinux_context \ "$REAL_HOME/.pki" \ "$REAL_HOME/.mozilla" \ "$REAL_HOME/.config/google-chrome" \ "$REAL_HOME/.config/chromium" \ "$REAL_HOME/.config/BraveSoftware" \ "$REAL_HOME/.config/vivaldi" \ "$REAL_HOME/.config/opera" \ "$REAL_HOME/.config/microsoft-edge" \ "$REAL_HOME/.config/thorium" \ "$REAL_HOME/.config/ungoogled-chromium" \ "$REAL_HOME/.var/app" \ "$REAL_HOME/snap" 2>/dev/null || true } check_fedora_selinux_requirements() { if distro_is "fedora" || distro_is "rhel" || distro_is "centos"; then selinux_enabled && log "SELinux rilevato: $(selinux_status)" || true have_cmd update-ca-trust || warn "Fedora/RHEL: update-ca-trust non trovato" have_cmd certutil || warn "Fedora/RHEL: certutil non trovato; installare nss-tools per import NSS/browser" if selinux_enabled && ! have_cmd restorecon; then warn "Fedora/RHEL: restorecon non trovato; installare policycoreutils" fi fi return 0 } run_as_real_user() { [[ -n "${REAL_USER:-}" && -n "${REAL_HOME:-}" ]] || return 1 local runtime_dir="" local bus_addr="" if [[ -n "${REAL_UID:-}" && -d "/run/user/${REAL_UID}" ]]; then runtime_dir="/run/user/${REAL_UID}" [[ -S "${runtime_dir}/bus" ]] && bus_addr="unix:path=${runtime_dir}/bus" fi if have_cmd runuser; then local env_args=( "HOME=$REAL_HOME" "USER=$REAL_USER" "LOGNAME=$REAL_USER" "XDG_DATA_HOME=$REAL_HOME/.local/share" "XDG_CONFIG_HOME=$REAL_HOME/.config" ) [[ -n "$runtime_dir" ]] && env_args+=("XDG_RUNTIME_DIR=$runtime_dir") [[ -n "$bus_addr" ]] && env_args+=("DBUS_SESSION_BUS_ADDRESS=$bus_addr") runuser -u "$REAL_USER" -- env "${env_args[@]}" "$@" elif have_cmd su; then local env_cmd env_cmd="HOME=$(printf '%q' "$REAL_HOME") USER=$(printf '%q' "$REAL_USER") LOGNAME=$(printf '%q' "$REAL_USER") XDG_DATA_HOME=$(printf '%q' "$REAL_HOME/.local/share") XDG_CONFIG_HOME=$(printf '%q' "$REAL_HOME/.config")" [[ -n "$runtime_dir" ]] && env_cmd="$env_cmd XDG_RUNTIME_DIR=$(printf '%q' "$runtime_dir")" [[ -n "$bus_addr" ]] && env_cmd="$env_cmd DBUS_SESSION_BUS_ADDRESS=$(printf '%q' "$bus_addr")" su -s /bin/sh - "$REAL_USER" -c "$env_cmd $(printf '%q ' "$@")" else return 1 fi } REAL_UID="$(get_real_uid || true)" REAL_USER="" REAL_GROUP="" REAL_HOME="" if [[ -n "$REAL_UID" ]]; then REAL_USER="$(get_user_for_uid "$REAL_UID" || true)" REAL_GROUP="$(get_group_for_uid "$REAL_UID" || true)" REAL_HOME="$(get_home_for_uid "$REAL_UID" || true)" fi if [[ -z "$REAL_GROUP" && -n "$REAL_USER" ]]; then REAL_GROUP="$REAL_USER" fi if [[ -n "$REAL_USER" && -n "$REAL_HOME" ]]; then log "Utente reale: $REAL_USER ($REAL_HOME)" else warn "Impossibile determinare utente/home reali; userò solo path di sistema" fi USER_BG_DIR="" USER_CA_DIR="" if [[ -n "$REAL_HOME" ]]; then USER_BG_DIR="$REAL_HOME/.local/share/BastionGuard" USER_CA_DIR="$USER_BG_DIR/certs" fi fix_user_ca_permissions() { [[ -n "${REAL_USER:-}" && -n "${REAL_GROUP:-}" && -n "${USER_BG_DIR:-}" && -n "${USER_CA_DIR:-}" ]] || return 0 [[ -d "$USER_BG_DIR" ]] || return 0 # Ripara anche directory intermedie create in precedenza da root. chown -R "$REAL_USER:$REAL_GROUP" "$USER_BG_DIR" 2>/dev/null || true chmod 700 "$USER_BG_DIR" 2>/dev/null || true [[ -d "$USER_CA_DIR" ]] && chmod 700 "$USER_CA_DIR" 2>/dev/null || true if [[ -d "$USER_CA_DIR" ]]; then find "$USER_CA_DIR" -type d -exec chmod 700 {} + 2>/dev/null || true find "$USER_CA_DIR" -type f \( -name '*.key' -o -name '*.key.pem' \) -exec chmod 600 {} + 2>/dev/null || true find "$USER_CA_DIR" -type f \( -name '*.crt' -o -name '*.crt.pem' -o -name '*.pem' \) ! -name '*.key.pem' -exec chmod 644 {} + 2>/dev/null || true fi restore_selinux_context "$USER_BG_DIR" } ensure_user_ca_dir() { [[ -n "${REAL_USER:-}" && -n "${REAL_GROUP:-}" && -n "${USER_CA_DIR:-}" ]] || return 0 # Crea prima da root, poi corregge ownership/mode: funziona anche se # ~/.local/share/BastionGuard era già stato creato male da root. mkdir -p "$USER_CA_DIR" fix_user_ca_permissions } # Il path della CA può essere passato come argomento, ma non deve mai cadere # accidentalmente sotto /root quando conosciamo l'utente reale. if [[ -n "${USER_CA_DIR:-}" ]]; then ensure_user_ca_dir fi if [[ $# -ge 1 && -n "${1:-}" ]]; then CA_SRC="$(readlink -m -- "$1")" # Caso tipico del bug: il chiamante, essendo root, ha espanso HOME=/root. # In quel caso correggiamo verso la home reale invece di generare certs root-owned. if [[ -n "${REAL_HOME:-}" ]] && path_under "$CA_SRC" "/root"; then warn "Path CA sotto /root rilevato ($CA_SRC); uso la home reale dell'utente" CA_SRC="$USER_CA_DIR/intercept-ca.crt.pem" fi else if [[ -n "${USER_CA_DIR:-}" ]]; then CA_SRC="$USER_CA_DIR/intercept-ca.crt.pem" else CA_SRC="/etc/BastionGuard/certs/intercept-ca.crt.pem" fi fi CA_DIR="$(dirname "$CA_SRC")" if [[ -n "${REAL_HOME:-}" ]] && path_under "$CA_DIR" "$REAL_HOME"; then mkdir -p "$CA_DIR" [[ -n "${REAL_USER:-}" && -n "${REAL_GROUP:-}" ]] && chown -R "$REAL_USER:$REAL_GROUP" "$CA_DIR" 2>/dev/null || true chmod 700 "$CA_DIR" 2>/dev/null || true restore_selinux_context "$CA_DIR" # Se è il percorso canonico di BastionGuard, ripara anche l'albero completo. ensure_user_ca_dir elif [[ "$CA_DIR" == /etc/* ]]; then mkdir -p "$CA_DIR" else mkdir -p "$CA_DIR" [[ -n "${REAL_USER:-}" && -n "${REAL_GROUP:-}" ]] && chown "$REAL_USER:$REAL_GROUP" "$CA_DIR" 2>/dev/null || true fi [ -f "$CA_SRC" ] || warn "CA non trovata: $CA_SRC — verrà generata automaticamente" # ── Legge /etc/os-release in modo sicuro ────────────────────────────────────── ID="" ID_LIKE="" [ -f /etc/os-release ] && . /etc/os-release distro_is() { [[ "${ID:-}" == "$1" ]] && return 0 [[ "${ID_LIKE:-}" == *"$1"* ]] && return 0 return 1 } check_fedora_selinux_requirements # ── Valida il certificato sorgente ──────────────────────────────────────────── validate_ca() { if ! openssl x509 -in "$CA_SRC" -noout >/dev/null 2>&1; then err "Certificato non valido o non leggibile: $CA_SRC" exit 1 fi if ! openssl x509 -in "$CA_SRC" -noout -text 2>/dev/null | grep -q "CA:TRUE"; then err "Il certificato non ha Basic Constraints CA:TRUE" exit 1 fi } # ── Pulizia vecchie CA BastionGuard ────────────────────────────────────────── # Obiettivo: prima di installare/importare la CA corrente, rimuovere copie # precedenti dai trust store di sistema e dai database NSS. Non tocca mai # CA_SRC né la chiave privata: elimina solo vecchie copie pubbliche installate. ca_fingerprint_sha256() { local cert="$1" [[ -f "$cert" ]] || return 1 openssl x509 -in "$cert" -noout -fingerprint -sha256 2>/dev/null \ | sed 's/^sha256 Fingerprint=//; s/^SHA256 Fingerprint=//' } current_ca_fingerprint_sha256() { ca_fingerprint_sha256 "$CA_SRC" } is_current_ca_file() { local cert="$1" [[ -f "$cert" && -f "$CA_SRC" ]] || return 1 local fp_current fp_candidate fp_current="$(current_ca_fingerprint_sha256 || true)" fp_candidate="$(ca_fingerprint_sha256 "$cert" || true)" [[ -n "$fp_current" && -n "$fp_candidate" && "$fp_current" == "$fp_candidate" ]] } remove_old_ca_file_if_needed() { local cert="$1" [[ -n "$cert" && -f "$cert" ]] || return 0 # Mai rimuovere il sorgente della CA corrente, anche se è in /etc. if [[ "$(readlink -m -- "$cert")" == "$(readlink -m -- "$CA_SRC")" ]]; then return 0 fi # Rimuove solo file che sembrano davvero CA/cert BastionGuard. if ! openssl x509 -in "$cert" -noout >/dev/null 2>&1; then return 0 fi if ! openssl x509 -in "$cert" -noout -subject -issuer 2>/dev/null | grep -qiE 'BastionGuard|intercept'; then return 0 fi if is_current_ca_file "$cert"; then log "CA già aggiornata nel trust store: $cert" return 0 fi log "Rimuovo vecchia CA BastionGuard: $cert" rm -f -- "$cert" 2>/dev/null || warn "Impossibile rimuovere vecchia CA: $cert" } purge_old_system_ca_copies() { [[ -f "$CA_SRC" ]] || return 0 log "Pulizia vecchie CA BastionGuard dai trust store di sistema…" local candidates=() candidates+=( "/usr/local/share/ca-certificates/BastionGuard-ca.crt" "/usr/local/share/ca-certificates/BastionGuard-ca.pem" "/usr/local/share/ca-certificates/BastionGuard-CA.crt" "/usr/local/share/ca-certificates/BastionGuard-CA.pem" "/usr/local/share/ca-certificates/BastionGuard Intercept CA.crt" "/usr/local/share/ca-certificates/BastionGuard Intercept CA.pem" "/usr/share/ca-certificates/local/BastionGuard-ca.crt" "/usr/share/ca-certificates/local/BastionGuard-ca.pem" "/usr/share/ca-certificates/local/BastionGuard-CA.crt" "/usr/share/ca-certificates/local/BastionGuard-CA.pem" "/usr/share/ca-certificates/local/BastionGuard Intercept CA.crt" "/usr/share/ca-certificates/local/BastionGuard Intercept CA.pem" "/etc/pki/ca-trust/source/anchors/BastionGuard-ca.pem" "/etc/pki/ca-trust/source/anchors/BastionGuard-ca.crt" "/etc/pki/ca-trust/source/anchors/BastionGuard-CA.pem" "/etc/pki/ca-trust/source/anchors/BastionGuard-CA.crt" "/etc/pki/ca-trust/source/anchors/BastionGuard Intercept CA.pem" "/etc/pki/ca-trust/source/anchors/BastionGuard Intercept CA.crt" "/etc/ca-certificates/trust-source/anchors/BastionGuard-ca.crt" "/etc/ca-certificates/trust-source/anchors/BastionGuard-ca.pem" "/etc/ca-certificates/trust-source/anchors/BastionGuard-CA.crt" "/etc/ca-certificates/trust-source/anchors/BastionGuard-CA.pem" "/etc/pki/trust/anchors/BastionGuard-ca.pem" "/etc/pki/trust/anchors/BastionGuard-ca.crt" "/etc/pki/trust/anchors/BastionGuard-CA.pem" "/etc/pki/trust/anchors/BastionGuard-CA.crt" "/usr/share/ca-certificates/bastionguard/BastionGuard-ca.pem" "/usr/share/ca-certificates/bastionguard/BastionGuard-ca.crt" "/usr/share/ca-certificates/bastionguard/BastionGuard-CA.pem" "/usr/share/ca-certificates/bastionguard/BastionGuard-CA.crt" ) local f for f in "${candidates[@]}"; do remove_old_ca_file_if_needed "$f" done # Cerca anche copie residue con nomi BastionGuard/intercept nelle directory # dei trust store, senza scandire l'intero filesystem. local roots=( /usr/local/share/ca-certificates /usr/share/ca-certificates/local /etc/pki/ca-trust/source/anchors /etc/ca-certificates/trust-source/anchors /etc/pki/trust/anchors /usr/share/ca-certificates/bastionguard ) local root for root in "${roots[@]}"; do [[ -d "$root" ]] || continue while IFS= read -r -d '' f; do remove_old_ca_file_if_needed "$f" done < <(find "$root" -maxdepth 1 -type f \ \( -iname '*bastionguard*.crt' -o -iname '*bastionguard*.pem' -o -iname '*intercept*.crt' -o -iname '*intercept*.pem' \) \ -print0 2>/dev/null || true) done # Pulisce le entry duplicate/obsolete su Debian-like; l'entry corretta # verrà riscritta da install_debian_style(). [[ -f /etc/ca-certificates.conf ]] && sed -i '/[Bb]astion[Gg]uard\|[Ii]ntercept/d' /etc/ca-certificates.conf || true fix_selinux_system_trust_contexts } nss_delete_matching_nicknames_root() { local prefix="$1" [[ -n "$prefix" ]] || return 0 local nick line # Nickname noti. for nick in "$CA_NAME" "BastionGuard Intercept CA" "BastionGuard CA" "BastionGuard-ca"; do certutil -D -d "$prefix" -n "$nick" >/dev/null 2>&1 || true done # Nickname imprevisti ma riconoscibili in certutil -L. while IFS= read -r line; do [[ "$line" =~ [Bb]astion[Gg]uard|[Ii]ntercept ]] || continue [[ "$line" =~ ^Certificate ]] && continue [[ "$line" =~ ^-+ ]] && continue nick="$(printf '%s\n' "$line" | sed -E 's/[[:space:]]+[A-Za-z,]+$//; s/[[:space:]]+$//')" [[ -n "$nick" ]] || continue certutil -D -d "$prefix" -n "$nick" >/dev/null 2>&1 || true done < <(certutil -L -d "$prefix" 2>/dev/null || true) } nss_delete_matching_nicknames_user() { local prefix="$1" [[ -n "$prefix" ]] || return 0 [[ -n "${REAL_USER:-}" && -n "${REAL_HOME:-}" ]] || return 0 local nick line # Nickname noti. for nick in "$CA_NAME" "BastionGuard Intercept CA" "BastionGuard CA" "BastionGuard-ca"; do run_as_real_user certutil -D -d "$prefix" -n "$nick" >/dev/null 2>&1 || true done # Nickname imprevisti ma riconoscibili in certutil -L. while IFS= read -r line; do [[ "$line" =~ [Bb]astion[Gg]uard|[Ii]ntercept ]] || continue [[ "$line" =~ ^Certificate ]] && continue [[ "$line" =~ ^-+ ]] && continue nick="$(printf '%s\n' "$line" | sed -E 's/[[:space:]]+[A-Za-z,]+$//; s/[[:space:]]+$//')" [[ -n "$nick" ]] || continue run_as_real_user certutil -D -d "$prefix" -n "$nick" >/dev/null 2>&1 || true done < <(run_as_real_user certutil -L -d "$prefix" 2>/dev/null || true) } close_running_browsers() { # I browser tengono spesso aperti/lockati i DB NSS dei profili. Se restano # in esecuzione, la rimozione delle vecchie CA o l'import della nuova CA può # fallire o risultare visibile solo al riavvio. Per questo li chiudiamo in # modo controllato prima dell'import. [[ -n "${REAL_UID:-}" ]] || return 0 local patterns=( '(^|/)(firefox|firefox-bin)([[:space:]]|$)' '(^|/)(librewolf|librewolf-bin)([[:space:]]|$)' '(^|/)(waterfox|waterfox-bin)([[:space:]]|$)' '(^|/)(floorp|floorp-bin)([[:space:]]|$)' '(^|/)(zen|zen-bin|zen-browser)([[:space:]]|$)' '(^|/)(mullvadbrowser|mullvad-browser)([[:space:]]|$)' '(^|/)(chromium|chromium-browser)([[:space:]]|$)' '(^|/)(google-chrome|google-chrome-stable|google-chrome-beta|google-chrome-unstable|chrome)([[:space:]]|$)' '(^|/)(brave|brave-browser|brave-browser-beta|brave-browser-nightly)([[:space:]]|$)' '(^|/)(vivaldi|vivaldi-bin|vivaldi-snapshot)([[:space:]]|$)' '(^|/)(opera|opera-browser|opera-beta|opera-developer)([[:space:]]|$)' '(^|/)(microsoft-edge|microsoft-edge-beta|microsoft-edge-dev|msedge)([[:space:]]|$)' '(^|/)(thorium|thorium-browser|ungoogled-chromium|epiphany|yandex-browser)([[:space:]]|$)' '(org\.mozilla\.firefox|org\.chromium\.Chromium|com\.google\.Chrome|com\.google\.ChromeDev|com\.brave\.Browser|com\.vivaldi\.Vivaldi|com\.opera\.Opera|com\.microsoft\.Edge|io\.gitlab\.librewolf-community|net\.waterfox\.waterfox|one\.ablaze\.floorp|io\.github\.ungoogled_software\.ungoogled_chromium|com\.github\.Eloston\.UngoogledChromium|org\.gnome\.Epiphany)' ) local pattern found=0 for pattern in "${patterns[@]}"; do if pgrep -u "$REAL_UID" -f -- "$pattern" >/dev/null 2>&1; then found=1 break fi done [[ "$found" -eq 1 ]] || return 0 warn "Browser in esecuzione rilevati: li chiudo automaticamente prima dell'import certificati" for pattern in "${patterns[@]}"; do pkill -TERM -u "$REAL_UID" -f -- "$pattern" >/dev/null 2>&1 || true done local i still_running for i in $(seq 1 20); do still_running=0 for pattern in "${patterns[@]}"; do if pgrep -u "$REAL_UID" -f -- "$pattern" >/dev/null 2>&1; then still_running=1 break fi done [[ "$still_running" -eq 0 ]] && { log "✅ Browser chiusi"; return 0; } sleep 0.5 done warn "Alcuni processi browser sono ancora attivi: forzo la chiusura" for pattern in "${patterns[@]}"; do pkill -KILL -u "$REAL_UID" -f -- "$pattern" >/dev/null 2>&1 || true done sleep 1 } # ── Controlla e corregge il mismatch tra certificato e chiave privata ───────── # # Logica: # 1. Cerca la chiave privata nello stesso percorso del certificato, # oppure nelle posizioni canoniche di BastionGuard. # 2. Confronta il modulus/pubkey del cert con quello della chiave. # 3. Se combaciano → tutto OK, nessuna azione. # 4. Se NON combaciano → rigenera SOLO il certificato (self-signed) usando # la chiave esistente, mantenendo i metadati originali (CN, days). # 5. Se la chiave non esiste affatto → genera ex-novo coppia chiave + cert. # check_and_fix_key_mismatch() { log "Controllo mismatch chiave/certificato…" # Se il certificato non esiste affatto, salta direttamente al CASO B if [[ ! -f "$CA_SRC" ]]; then warn "Certificato non trovato: $CA_SRC — genero nuova coppia chiave+certificato" mkdir -p "$(dirname "$CA_SRC")" fix_user_ca_permissions local new_key_path new_key_path="$(dirname "$CA_SRC")/$(basename "${CA_SRC%.crt.pem}").key.pem" new_key_path="${new_key_path%.pem.pem}.pem" local orig_subject="/CN=${CA_NAME}/O=BastionGuard/OU=Security" if ! openssl genrsa -out "$new_key_path" 4096 >/dev/null 2>&1; then err "Generazione chiave RSA fallita"; exit 1 fi chmod 600 "$new_key_path" [[ -n "${REAL_USER:-}" ]] && chown "$REAL_USER:$REAL_GROUP" "$new_key_path" 2>/dev/null || true log "Nuova chiave privata: $new_key_path" if openssl req -new -x509 -key "$new_key_path" -out "$CA_SRC" -days 3650 \ -subj "$orig_subject" \ -addext "basicConstraints=critical,CA:TRUE" \ -addext "keyUsage=critical,keyCertSign,cRLSign" \ >/dev/null 2>&1; then log "✅ Nuova coppia chiave+certificato generata (3650 giorni)" else local tmp_ext; tmp_ext="$(mktemp /tmp/ca_ext_XXXXXX.cnf)" printf '[req] distinguished_name = req_dn x509_extensions = v3_ca prompt = no [req_dn] CN = %s O = BastionGuard OU = Security [v3_ca] basicConstraints = critical, CA:TRUE keyUsage = critical, keyCertSign, cRLSign subjectKeyIdentifier = hash ' "${CA_NAME}" > "$tmp_ext" openssl req -new -x509 -key "$new_key_path" -out "$CA_SRC" -days 3650 -config "$tmp_ext" >/dev/null 2>&1 \ && log "✅ Nuova coppia chiave+certificato generata (compat mode, 3650 giorni)" \ || { err "Impossibile generare il certificato"; rm -f "$tmp_ext"; exit 1; } rm -f "$tmp_ext" fi [[ -n "${REAL_USER:-}" ]] && chown "$REAL_USER:$REAL_GROUP" "$CA_SRC" 2>/dev/null || true fix_user_ca_permissions log "✅ Certificato generato, procedo con l'installazione" return 0 fi # ── Individua la chiave privata ────────────────────────────────────────── local key_candidates=() # 1. Stesso basename del cert, estensione .key o .key.pem local base="${CA_SRC%.crt.pem}" base="${base%.crt}" base="${base%.pem}" key_candidates+=( "${base}.key" "${base}.key.pem" ) # 2. Posizioni canoniche BastionGuard local cert_dir cert_dir="$(dirname "$CA_SRC")" key_candidates+=( "${cert_dir}/intercept-ca.key.pem" "${cert_dir}/intercept-ca.key" "${cert_dir}/${CA_NAME}.key.pem" "${cert_dir}/${CA_NAME}.key" "/etc/BastionGuard/certs/intercept-ca.key.pem" "/etc/BastionGuard/certs/intercept-ca.key" "/etc/BastionGuard/certs/${CA_NAME}.key.pem" "/etc/BastionGuard/certs/${CA_NAME}.key" ) # Se l'utente reale ha una home, aggiungi anche lì if [[ -n "${REAL_HOME:-}" ]]; then local ud="$REAL_HOME/.local/share/BastionGuard/certs" key_candidates+=( "${ud}/intercept-ca.key.pem" "${ud}/intercept-ca.key" "${ud}/${CA_NAME}.key.pem" "${ud}/${CA_NAME}.key" ) fi local CA_KEY="" for c in "${key_candidates[@]}"; do if [[ -f "$c" ]]; then CA_KEY="$c" break fi done # ── Funzione interna: ottieni fingerprint pubkey di cert o key ─────────── _pubkey_fp_from_cert() { openssl x509 -in "$1" -noout -pubkey 2>/dev/null | openssl pkey -pubin -noout -text 2>/dev/null | sha256sum; } _pubkey_fp_from_key() { openssl pkey -in "$1" -pubout 2>/dev/null | openssl pkey -pubin -noout -text 2>/dev/null | sha256sum; } # ── CASO A: chiave trovata ─────────────────────────────────────────────── if [[ -n "$CA_KEY" ]]; then log "Chiave privata trovata: $CA_KEY" # Verifica che la chiave sia leggibile/valida if ! openssl pkey -in "$CA_KEY" -noout >/dev/null 2>&1; then warn "Chiave privata non valida o corrotta: $CA_KEY" warn "Genero nuova coppia chiave+certificato" CA_KEY="" # cade nel CASO B fi fi if [[ -n "$CA_KEY" ]]; then local fp_cert fp_key fp_cert="$(_pubkey_fp_from_cert "$CA_SRC")" fp_key="$( _pubkey_fp_from_key "$CA_KEY")" if [[ "$fp_cert" == "$fp_key" ]]; then fix_user_ca_permissions log "✅ Chiave e certificato combaciano — nessun intervento necessario" return 0 fi warn "⚠️ MISMATCH rilevato: la chiave pubblica nel certificato NON corrisponde a $CA_KEY" log "Rigenero il certificato usando la chiave esistente…" # Leggo i metadati dall'attuale certificato local subject days_left not_after now_ts exp_ts remaining_days subject="$(openssl x509 -in "$CA_SRC" -noout -subject 2>/dev/null | sed 's/^subject=//')" not_after="$(openssl x509 -in "$CA_SRC" -noout -enddate 2>/dev/null | cut -d= -f2)" now_ts="$(date +%s)" exp_ts="$(date -d "$not_after" +%s 2>/dev/null || python3 -c "import ssl,time; print(int(time.mktime(__import__('email.utils',fromlist=['parsedate']).parsedate('$not_after'))))" 2>/dev/null || echo 0)" if [[ "$exp_ts" -gt "$now_ts" ]]; then remaining_days=$(( (exp_ts - now_ts) / 86400 )) # Proroga al massimo a 3650 gg se il residuo è troppo basso [[ "$remaining_days" -lt 365 ]] && remaining_days=3650 else remaining_days=3650 warn "Certificato scaduto — uso validità predefinita di 3650 giorni" fi # Backup del vecchio certificato local backup="${CA_SRC}.bak.$(date +%Y%m%d%H%M%S)" cp -a "$CA_SRC" "$backup" log "Backup vecchio certificato: $backup" # Genera nuovo certificato self-signed con la stessa chiave if openssl req -new -x509 \ -key "$CA_KEY" \ -out "$CA_SRC" \ -days "$remaining_days" \ -subj "$subject" \ -addext "basicConstraints=critical,CA:TRUE" \ -addext "keyUsage=critical,keyCertSign,cRLSign" \ >/dev/null 2>&1; then log "✅ Certificato rigenerato con la chiave esistente ($remaining_days giorni)" else # Fallback: openssl < 1.1.1 non supporta -addext local tmp_ext tmp_ext="$(mktemp /tmp/ca_ext_XXXXXX.cnf)" cat > "$tmp_ext" </dev/null 2>&1 \ && log "✅ Certificato rigenerato (compat mode, $remaining_days giorni)" \ || { err "Impossibile rigenerare il certificato"; rm -f "$tmp_ext"; exit 1; } rm -f "$tmp_ext" fi else # ── CASO B: chiave non trovata — genera nuova coppia ───────────────── warn "Nessuna chiave privata trovata per $CA_SRC" log "Genero nuova coppia chiave RSA 4096 + certificato CA self-signed…" # Scelgo il percorso di output per la nuova chiave local new_key_path new_key_path="$(dirname "$CA_SRC")/$(basename "${CA_SRC%.crt.pem}").key.pem" new_key_path="${new_key_path%.pem.pem}.pem" # evita doppia estensione local backup="${CA_SRC}.bak.$(date +%Y%m%d%H%M%S)" [[ -f "$CA_SRC" ]] && { cp -a "$CA_SRC" "$backup"; log "Backup vecchio certificato: $backup"; } # Cerca di mantenere il subject originale local orig_subject="/CN=${CA_NAME}/O=BastionGuard/OU=Security" if [[ -f "$backup" ]]; then orig_subject="$(openssl x509 -in "$backup" -noout -subject 2>/dev/null | sed 's/^subject=//')" || true fi # Genera chiave privata if ! openssl genrsa -out "$new_key_path" 4096 >/dev/null 2>&1; then err "Generazione chiave RSA fallita" exit 1 fi chmod 600 "$new_key_path" [[ -n "${REAL_USER:-}" ]] && chown "$REAL_USER:$REAL_GROUP" "$new_key_path" 2>/dev/null || true log "Nuova chiave privata: $new_key_path" # Genera certificato self-signed if openssl req -new -x509 \ -key "$new_key_path" \ -out "$CA_SRC" \ -days 3650 \ -subj "$orig_subject" \ -addext "basicConstraints=critical,CA:TRUE" \ -addext "keyUsage=critical,keyCertSign,cRLSign" \ >/dev/null 2>&1; then log "✅ Nuova coppia chiave+certificato generata (3650 giorni)" else local tmp_ext tmp_ext="$(mktemp /tmp/ca_ext_XXXXXX.cnf)" cat > "$tmp_ext" </dev/null 2>&1 \ && log "✅ Nuova coppia chiave+certificato generata (compat mode, 3650 giorni)" \ || { err "Impossibile generare il certificato"; rm -f "$tmp_ext"; exit 1; } rm -f "$tmp_ext" fi CA_KEY="$new_key_path" fi fix_user_ca_permissions # Ri-valida dopo ogni intervento if ! openssl x509 -in "$CA_SRC" -noout >/dev/null 2>&1; then err "Il certificato rigenerato non è valido: $CA_SRC" exit 1 fi log "✅ Verifica post-fix superata" } # ── Helper Debian ───────────────────────────────────────────────────────────── install_debian_style() { local dst="$1" local conf_entry="$2" mkdir -p "$(dirname "$dst")" rm -f \ /usr/local/share/ca-certificates/BastionGuard-ca.crt \ /usr/local/share/ca-certificates/BastionGuard-ca.pem \ /usr/local/share/ca-certificates/BastionGuard-CA.crt \ /usr/local/share/ca-certificates/BastionGuard-CA.pem \ /usr/share/ca-certificates/local/BastionGuard-ca.crt \ /usr/share/ca-certificates/local/BastionGuard-ca.pem \ /usr/share/ca-certificates/local/BastionGuard-CA.crt \ /usr/share/ca-certificates/local/BastionGuard-CA.pem install -m 0644 "$CA_SRC" "$dst" restore_selinux_context "$dst" "$(dirname "$dst")" /etc/ca-certificates /usr/share/ca-certificates /usr/local/share/ca-certificates sed -i '/[Bb]astion[Gg]uard/d' /etc/ca-certificates.conf || true echo "$conf_entry" >> /etc/ca-certificates.conf log "Entry registrata in /etc/ca-certificates.conf: $conf_entry" update-ca-certificates } # ── Policy SELinux locale per BastionGuard-cef.service ─────────────────────── # BastionGuard-cef.service è un servizio systemd utente, quindi gira come utente # normale ma può comunque essere bloccato da SELinux su Fedora/RHEL mentre # legge/scrive la propria CA e cache in home. # Questa sezione crea una policy locale mirata al dominio SELinux effettivo # dell'utente/processo e limita i permessi all'albero ~/.local/share/BastionGuard. CEF_USER_SERVICE_NAME="BastionGuard-cef.service" CEF_SERVICE_COMM="bastionguard-cef" CEF_SERVICE_COMM_TRUNC="bastionguard-ce" CEF_SERVICE_NAME_GLOB="BastionGuard-cef" CEF_POLICY_MODULE="bastionguard_cef_local" CEF_DATA_TYPE="bastionguard_cef_home_t" regex_escape() { printf '%s' "$1" | sed -E 's/[][().^$*+?{}|\\]/\\&/g' } selinux_type_from_context() { awk -F: '{print $3}' <<<"$1" } selinux_type_name_valid() { local t="$1" [[ "$t" =~ ^[A-Za-z0-9_]+$ ]] || return 1 [[ "$t" == *_t ]] || return 1 } detect_bastionguard_cef_source_domains() { local d label comm rest raw svc_lc comm_lc rest_lc svc_lc="${CEF_SERVICE_NAME_GLOB,,}" # Dominio SELinux della sessione utente. Per un servizio systemd --user # avviato come utente normale, spesso è lo stesso dominio del processo. if [[ -n "${REAL_USER:-}" ]] && label="$(run_as_real_user id -Z 2>/dev/null || true)"; then d="$(selinux_type_from_context "$label" 2>/dev/null || true)" selinux_type_name_valid "$d" && printf '%s\n' "$d" fi # Dominio reale del processo BastionGuard-cef, se è già avviato. # Nota: Linux tronca comm a 15 caratteri; bastionguard-cef può comparire # come bastionguard-ce nelle AVC e in ps -o comm. if have_cmd ps; then while read -r label comm rest; do [[ -n "${label:-}" ]] || continue comm_lc="${comm,,}" rest_lc="${rest,,}" if [[ "$comm_lc" == *"${CEF_SERVICE_COMM,,}"* || \ "$comm_lc" == *"${CEF_SERVICE_COMM_TRUNC,,}"* || \ "$rest_lc" == *"${CEF_SERVICE_COMM,,}"* || \ "$rest_lc" == *"${svc_lc}"* ]]; then d="$(selinux_type_from_context "$label" 2>/dev/null || true)" selinux_type_name_valid "$d" && printf '%s\n' "$d" fi done < <(ps -eZ -o label=,comm=,args= 2>/dev/null || true) fi # Domini sorgente comparsi nelle denial recenti del servizio/processo. if have_cmd ausearch; then raw="$( { ausearch -m AVC,USER_AVC -ts recent -c "$CEF_SERVICE_COMM" 2>/dev/null || true ausearch -m AVC,USER_AVC -ts recent -c "$CEF_SERVICE_COMM_TRUNC" 2>/dev/null || true ausearch -m AVC,USER_AVC -ts recent 2>/dev/null \ | grep -iE 'bastionguard-ce|BastionGuard-cef' || true } | sort -u )" while IFS= read -r d; do selinux_type_name_valid "$d" && printf '%s\n' "$d" done < <( printf '%s\n' "$raw" \ | grep -oE 'scontext=[^ ]+' \ | sed -E 's/^scontext=([^:]+:){2}([^:]+):.*/\2/' \ | sort -u || true ) fi } write_bastionguard_cef_base_te() { local te="$1"; shift local domains=("$@") local d { echo "module ${CEF_POLICY_MODULE} 1.0;" echo echo "require {" for d in "${domains[@]}"; do echo " type ${d};" done echo " type user_home_dir_t;" echo " type user_home_t;" echo " attribute file_type;" echo " attribute user_home_type;" echo " class dir { add_name create getattr ioctl lock open read remove_name rmdir search setattr write };" echo " class file { append create getattr ioctl lock map open read rename setattr unlink write };" echo " class lnk_file { create getattr read rename setattr unlink write };" echo " class sock_file { create getattr setattr unlink write };" echo " class fifo_file { create getattr ioctl lock open read setattr unlink write };" echo "}" echo echo "type ${CEF_DATA_TYPE};" echo "typeattribute ${CEF_DATA_TYPE} file_type;" echo "typeattribute ${CEF_DATA_TYPE} user_home_type;" echo for d in "${domains[@]}"; do echo "# Permessi sui dati BastionGuard dell'utente per ${d}." echo "allow ${d} ${CEF_DATA_TYPE}:dir { add_name create getattr ioctl lock open read remove_name rmdir search setattr write };" echo "allow ${d} ${CEF_DATA_TYPE}:file { append create getattr ioctl lock map open read rename setattr unlink write };" echo "allow ${d} ${CEF_DATA_TYPE}:lnk_file { create getattr read rename setattr unlink write };" echo "allow ${d} ${CEF_DATA_TYPE}:sock_file { create getattr setattr unlink write };" echo "allow ${d} ${CEF_DATA_TYPE}:fifo_file { create getattr ioctl lock open read setattr unlink write };" echo "allow ${d} user_home_dir_t:dir { getattr open read search };" echo "allow ${d} user_home_t:dir { getattr open read search };" echo done } > "$te" } compile_and_install_selinux_te() { local te="$1" local base="$2" checkmodule -M -m -o "${base}.mod" "$te" semodule_package -o "${base}.pp" -m "${base}.mod" semodule -i "${base}.pp" } install_bastionguard_cef_observed_avc_policy() { # Facoltativo: se ci sono già denial recenti, crea un secondo modulo solo # dalle AVC del servizio/processo BastionGuard-cef. È mirato alle denial reali osservate. selinux_enabled || return 0 have_cmd ausearch || return 0 have_cmd audit2allow || return 0 local tmpd avc base tmpd="$(mktemp -d /tmp/bastionguard-cef-avc.XXXXXX)" avc="$tmpd/${CEF_SERVICE_COMM}.avc" base="bastionguard_cef_observed" { ausearch -m AVC,USER_AVC -ts recent -c "$CEF_SERVICE_COMM" --raw 2>/dev/null || true ausearch -m AVC,USER_AVC -ts recent -c "$CEF_SERVICE_COMM_TRUNC" --raw 2>/dev/null || true ausearch -m AVC,USER_AVC -ts recent --raw 2>/dev/null \ | grep -iE 'bastionguard-ce|BastionGuard-cef' || true } | sort -u > "$avc" if ! grep -qE 'avc:.*denied|type=AVC|type=USER_AVC' "$avc" 2>/dev/null; then rm -rf "$tmpd" log "Nessuna AVC denial recente per $CEF_USER_SERVICE_NAME / $CEF_SERVICE_COMM" return 0 fi if ( cd "$tmpd" && \ audit2allow -M "$base" -i "$avc" >/dev/null && \ semodule -i "${base}.pp" ); then log "✅ Modulo SELinux da AVC recenti installato: ${base}" else warn "Impossibile generare/installare modulo da AVC recenti per $CEF_USER_SERVICE_NAME" fi rm -rf "$tmpd" } install_bastionguard_cef_selinux_policy() { selinux_enabled || return 0 distro_is "fedora" || distro_is "rhel" || distro_is "centos" || return 0 [[ -n "${REAL_USER:-}" && -n "${REAL_HOME:-}" && -n "${REAL_GROUP:-}" ]] || return 0 local missing=0 cmd for cmd in checkmodule semodule_package semodule semanage restorecon; do if ! have_cmd "$cmd"; then warn "SELinux policy $CEF_USER_SERVICE_NAME: comando mancante: $cmd" missing=1 fi done if [[ "$missing" -eq 1 ]]; then warn "Fedora/RHEL: installare checkpolicy policycoreutils-python-utils policycoreutils per la policy di $CEF_USER_SERVICE_NAME" return 0 fi local domains_raw domains=() d tmpd te home_re data_re domains_raw="$(detect_bastionguard_cef_source_domains | sort -u || true)" if [[ -z "$domains_raw" ]]; then warn "Nessun dominio SELinux rilevato per $CEF_USER_SERVICE_NAME; avvia il servizio utente una volta e rilancia lo script se SELinux blocca" install_bastionguard_cef_observed_avc_policy return 0 fi while IFS= read -r d; do [[ -n "$d" ]] && domains+=("$d") done <<< "$domains_raw" log "Domini SELinux per $CEF_USER_SERVICE_NAME: ${domains[*]}" tmpd="$(mktemp -d /tmp/bastionguard-cef-selinux.XXXXXX)" te="$tmpd/${CEF_POLICY_MODULE}.te" write_bastionguard_cef_base_te "$te" "${domains[@]}" if ( cd "$tmpd" && compile_and_install_selinux_te "$te" "$CEF_POLICY_MODULE" ); then log "✅ Policy SELinux base installata: $CEF_POLICY_MODULE" else warn "Compilazione/installazione policy SELinux base fallita per $CEF_USER_SERVICE_NAME" rm -rf "$tmpd" install_bastionguard_cef_observed_avc_policy return 0 fi rm -rf "$tmpd" home_re="$(regex_escape "$REAL_HOME")" data_re="${home_re}/\.local/share/BastionGuard(/.*)?" semanage fcontext -a -t "$CEF_DATA_TYPE" "$data_re" 2>/dev/null \ || semanage fcontext -m -t "$CEF_DATA_TYPE" "$data_re" 2>/dev/null \ || warn "Impossibile registrare fcontext SELinux per $REAL_HOME/.local/share/BastionGuard" mkdir -p "$REAL_HOME/.local/share/BastionGuard" chown -R "$REAL_USER:$REAL_GROUP" "$REAL_HOME/.local/share/BastionGuard" 2>/dev/null || true chmod 700 "$REAL_HOME/.local/share/BastionGuard" 2>/dev/null || true restore_selinux_context "$REAL_HOME/.local/share/BastionGuard" install_bastionguard_cef_observed_avc_policy log "✅ Policy SELinux $CEF_USER_SERVICE_NAME completata" } # ── Gestione servizio systemd utente BastionGuard-cef.service ──────────────── user_systemctl() { [[ -n "${REAL_USER:-}" && -n "${REAL_UID:-}" ]] || return 1 [[ -d "/run/user/${REAL_UID}" ]] || return 1 run_as_real_user systemctl --user "$@" } bastionguard_cef_user_service_exists() { local state="" state="$(user_systemctl show "$CEF_USER_SERVICE_NAME" -p LoadState --value 2>/dev/null || true)" [[ -n "$state" && "$state" != "not-found" ]] && return 0 user_systemctl status "$CEF_USER_SERVICE_NAME" >/dev/null 2>&1 && return 0 return 1 } restart_bastionguard_cef_user_service_if_active() { [[ -n "${REAL_USER:-}" && -n "${REAL_UID:-}" ]] || return 0 if ! bastionguard_cef_user_service_exists; then log "Servizio utente $CEF_USER_SERVICE_NAME non trovato o bus utente non disponibile: skip restart" return 0 fi if user_systemctl is-active --quiet "$CEF_USER_SERVICE_NAME"; then log "Riavvio servizio utente $CEF_USER_SERVICE_NAME per ricaricare CA/policy…" if user_systemctl restart "$CEF_USER_SERVICE_NAME"; then log "✅ Servizio utente riavviato: $CEF_USER_SERVICE_NAME" else warn "Restart di $CEF_USER_SERVICE_NAME fallito; prova manualmente: systemctl --user restart $CEF_USER_SERVICE_NAME" fi else log "Servizio utente $CEF_USER_SERVICE_NAME non attivo: non lo avvio automaticamente" fi } # ── 1. Installa nel trust store di sistema ──────────────────────────────────── check_and_fix_key_mismatch fix_user_ca_permissions close_running_browsers purge_old_system_ca_copies installed_sys=false if distro_is "debian" || distro_is "ubuntu"; then log "Rilevato: Debian/Ubuntu" install_debian_style \ "/usr/share/ca-certificates/local/${CA_NAME}.crt" \ "local/${CA_NAME}.crt" log "✅ CA installata (Debian/Ubuntu)" installed_sys=true elif distro_is "fedora" || distro_is "rhel" || distro_is "centos"; then log "Rilevato: Fedora/RHEL/CentOS" DST="/etc/pki/ca-trust/source/anchors/${CA_NAME}.pem" mkdir -p "$(dirname "$DST")" install -m 0644 "$CA_SRC" "$DST" # Su Fedora/RHEL il label SELinux errato su /etc/pki/ca-trust può impedire # lettura/estrazione corretta in ambienti enforcing. Ripristina prima e dopo. restore_selinux_context "$DST" "$(dirname "$DST")" /etc/pki/ca-trust update-ca-trust extract fix_selinux_system_trust_contexts log "✅ CA installata (Fedora/RHEL)" installed_sys=true elif distro_is "arch" || distro_is "archlinux"; then log "Rilevato: Arch Linux" DST="/etc/ca-certificates/trust-source/anchors/${CA_NAME}.crt" mkdir -p "$(dirname "$DST")" install -m 0644 "$CA_SRC" "$DST" trust extract-compat log "✅ CA installata (Arch Linux)" installed_sys=true elif distro_is "opensuse" || distro_is "suse"; then log "Rilevato: openSUSE/SLES" DST="/etc/pki/trust/anchors/${CA_NAME}.pem" mkdir -p "$(dirname "$DST")" install -m 0644 "$CA_SRC" "$DST" update-ca-certificates log "✅ CA installata (openSUSE)" installed_sys=true elif distro_is "alpine"; then log "Rilevato: Alpine Linux" DST="/usr/local/share/ca-certificates/${CA_NAME}.crt" mkdir -p "$(dirname "$DST")" install -m 0644 "$CA_SRC" "$DST" update-ca-certificates log "✅ CA installata (Alpine)" installed_sys=true elif distro_is "void"; then log "Rilevato: Void Linux" DST="/usr/share/ca-certificates/bastionguard/${CA_NAME}.pem" mkdir -p "$(dirname "$DST")" install -m 0644 "$CA_SRC" "$DST" CONF="/etc/ca-certificates/update.d/bastionguard.conf" mkdir -p "$(dirname "$CONF")" echo "bastionguard/${CA_NAME}.pem" > "$CONF" update-ca-certificates log "✅ CA installata (Void Linux)" installed_sys=true fi if ! $installed_sys; then warn "Distro non riconosciuta (ID='${ID:-?}', ID_LIKE='${ID_LIKE:-?}') — fallback" if have_cmd trust; then DST="/etc/ca-certificates/trust-source/anchors/${CA_NAME}.crt" mkdir -p "$(dirname "$DST")" install -m 0644 "$CA_SRC" "$DST" trust extract-compat log "✅ CA installata (fallback: trust)" installed_sys=true elif have_cmd update-ca-trust; then DST="/etc/pki/ca-trust/source/anchors/${CA_NAME}.pem" mkdir -p "$(dirname "$DST")" install -m 0644 "$CA_SRC" "$DST" restore_selinux_context "$DST" "$(dirname "$DST")" /etc/pki/ca-trust update-ca-trust extract fix_selinux_system_trust_contexts log "✅ CA installata (fallback: update-ca-trust)" installed_sys=true elif have_cmd update-ca-certificates; then install_debian_style \ "/usr/share/ca-certificates/local/${CA_NAME}.crt" \ "local/${CA_NAME}.crt" log "✅ CA installata (fallback: update-ca-certificates)" installed_sys=true fi fi if ! $installed_sys; then err "Nessun metodo di installazione trovato per questa distro" exit 1 fi # Ripristina label SELinux sui trust store toccati, senza rendere obbligatorio SELinux. fix_selinux_system_trust_contexts fix_user_ca_permissions # ── validate_ca dopo installazione sistema ─────────────────────────────────── validate_ca # ── 2. NSS system-wide ──────────────────────────────────────────────────────── if have_cmd certutil; then for db in /etc/pki/nssdb /usr/share/pki/nssdb; do [ -d "$db" ] || continue prefix="dbm:" [ -f "$db/cert9.db" ] && prefix="sql:" nss_delete_matching_nicknames_root "${prefix}${db}" certutil -A -d "${prefix}${db}" -n "$CA_NAME" -t "CT,," -i "$CA_SRC" 2>/dev/null \ && log "✅ NSS system: $db" \ || warn "NSS system $db: certutil fallito (non fatale)" restore_selinux_context "$db" done else warn "certutil non trovato — NSS system-wide non aggiornato" fi # ── 3. NSS utente ───────────────────────────────────────────────────────────── nss_db_prefix() { [[ -f "$1/cert9.db" ]] && printf 'sql:%s' "$1" || printf 'dbm:%s' "$1" } import_nss_db() { local db="$1" [[ -d "$db" ]] || return 0 [[ -f "$db/cert9.db" || -f "$db/cert8.db" ]] || return 0 local prefix prefix="$(nss_db_prefix "$db")" nss_delete_matching_nicknames_user "$prefix" if run_as_real_user certutil -A -d "$prefix" -n "$CA_NAME" -t "CT,," -i "$CA_SRC" >/dev/null 2>&1; then log "✅ NSS utente: $db" else warn "NSS utente $db: import fallito (non fatale)" fi [[ -n "${REAL_USER:-}" && -n "${REAL_GROUP:-}" ]] && chown -R "$REAL_USER:$REAL_GROUP" "$db" 2>/dev/null || true restore_selinux_context "$db" } ensure_nssdb() { local db="$1" [[ -n "${REAL_USER:-}" ]] || return 0 if [[ ! -d "$db" ]]; then log "Creo directory NSS DB: $db" mkdir -p "$db" chown "$REAL_USER:$REAL_GROUP" "$db" chmod 700 "$db" restore_selinux_context "$db" fi if [[ ! -f "$db/cert9.db" && ! -f "$db/cert8.db" ]]; then log "Inizializzo NSS DB: $db" if run_as_real_user certutil -N -d "sql:$db" --empty-password 2>/dev/null; then log "✅ NSS DB creato: $db" chown -R "$REAL_USER:$REAL_GROUP" "$db" restore_selinux_context "$db" else if run_as_real_user certutil -N -d "dbm:$db" --empty-password 2>/dev/null; then log "✅ NSS DB (dbm) creato: $db" chown -R "$REAL_USER:$REAL_GROUP" "$db" restore_selinux_context "$db" else warn "Impossibile creare NSS DB: $db" return 1 fi fi fi } import_firefox_profile_dir() { local prof="$1" [[ -d "$prof" ]] || return 0 if [[ ! -f "$prof/cert9.db" && ! -f "$prof/cert8.db" ]]; then log "Inizializzo NSS DB nel profilo Firefox: $prof" if run_as_real_user certutil -N -d "sql:$prof" --empty-password >/dev/null 2>&1; then : elif run_as_real_user certutil -N -d "dbm:$prof" --empty-password >/dev/null 2>&1; then : else warn "Impossibile inizializzare NSS DB nel profilo: $prof" return 1 fi chown -R "$REAL_USER:$REAL_GROUP" "$prof" 2>/dev/null || true restore_selinux_context "$prof" fi import_nss_db "$prof" } firefox_imported_seen="" already_imported_firefox_profile() { local prof="$1" case " $firefox_imported_seen " in *" $prof "*) return 0 ;; *) return 1 ;; esac } mark_imported_firefox_profile() { local prof="$1" firefox_imported_seen="${firefox_imported_seen} $prof" } import_firefox_profile_once() { local prof="$1" [[ -n "$prof" ]] || return 0 [[ -d "$prof" ]] || return 0 if already_imported_firefox_profile "$prof"; then return 0 fi mark_imported_firefox_profile "$prof" import_firefox_profile_dir "$prof" } import_firefox_root() { local root="$1" local found=0 local ini="$root/profiles.ini" [[ -d "$root" ]] || return 0 firefox_imported_seen="" log "Scansione profili Firefox in: $root" # 1. Sezioni [Install...] con Default=... if [[ -f "$ini" ]]; then while IFS= read -r relpath; do [[ -n "$relpath" ]] || continue local prof="$root/$relpath" if [[ -d "$prof" ]]; then found=1 log "Profilo Firefox da sezione [Install*] Default=: $prof" import_firefox_profile_once "$prof" fi done < <( awk -F= ' BEGIN { in_install=0 } /^\[Install[^]]*\]/ { in_install=1; next } /^\[/ { in_install=0; next } in_install && $1=="Default" { gsub(/\r/,"",$2) print $2 } ' "$ini" 2>/dev/null || true ) fi # 2. Profili [ProfileN] con Default=1 if [[ -f "$ini" ]]; then while IFS='|' read -r is_relative path; do [[ -n "$path" ]] || continue local prof="" if [[ "$is_relative" == "1" ]]; then prof="$root/$path" else prof="$path" fi if [[ -d "$prof" ]]; then found=1 log "Profilo Firefox da profiles.ini (Default=1): $prof" import_firefox_profile_once "$prof" fi done < <( awk -F= -v ROOT="$root" ' BEGIN { in_profile=0 is_default=0 path="" is_relative=1 } /^\[Profile[0-9]+\]/ { if (in_profile && is_default == 1 && path != "") { printf "%s|%s\n", is_relative, path } in_profile=1 is_default=0 path="" is_relative=1 next } /^\[/ { if (in_profile && is_default == 1 && path != "") { printf "%s|%s\n", is_relative, path } in_profile=0 next } in_profile && $1=="Default" { gsub(/\r/,"",$2) is_default=$2 next } in_profile && $1=="Path" { gsub(/\r/,"",$2) path=$2 next } in_profile && $1=="IsRelative" { gsub(/\r/,"",$2) is_relative=$2 next } END { if (in_profile && is_default == 1 && path != "") { printf "%s|%s\n", is_relative, path } } ' "$ini" 2>/dev/null || true ) fi # 3. Forza tutti i *.default-release while IFS= read -r -d '' prof; do found=1 log "Profilo Firefox forzato (*.default-release): $prof" import_firefox_profile_once "$prof" done < <( find "$root" -mindepth 1 -maxdepth 1 -type d -name '*.default-release' -print0 2>/dev/null || true ) # 4. Fallback finale su profili comuni while IFS= read -r -d '' prof; do found=1 log "Profilo Firefox fallback: $prof" import_firefox_profile_once "$prof" done < <( find "$root" -mindepth 1 -maxdepth 1 -type d \ \( \ -name '*.default' -o \ -name '*.default-esr' -o \ -name '*.default-release' -o \ -name '*.profile' -o \ -name '*.release' -o \ -name '*.esr' \ \) \ -print0 2>/dev/null || true ) if [[ "$found" -eq 0 ]]; then warn "Nessun profilo Firefox trovato in: $root" fi } if have_cmd certutil && [[ -n "${REAL_USER:-}" && -n "${REAL_HOME:-}" ]]; then close_running_browsers log "Aggiornamento NSS utente per: $REAL_USER" ensure_nssdb "$REAL_HOME/.pki/nssdb" import_nss_db "$REAL_HOME/.pki/nssdb" # Firefox import_firefox_root "$REAL_HOME/.mozilla/firefox" import_firefox_root "$REAL_HOME/snap/firefox/common/.mozilla/firefox" import_firefox_root "$REAL_HOME/.var/app/org.mozilla.firefox/.mozilla/firefox" # Fork Firefox-like (nativi) import_firefox_root "$REAL_HOME/.floorp" import_firefox_root "$REAL_HOME/.librewolf" import_firefox_root "$REAL_HOME/.waterfox" import_firefox_root "$REAL_HOME/.zen" import_firefox_root "$REAL_HOME/.mullvad" import_firefox_root "$REAL_HOME/.ghostery" import_firefox_root "$REAL_HOME/.basilisk" import_firefox_root "$REAL_HOME/.moonchild productions/pale moon" import_firefox_root "$REAL_HOME/.config/floorp" import_firefox_root "$REAL_HOME/.config/librewolf" # Fork Firefox-like (flatpak) import_firefox_root "$REAL_HOME/.var/app/io.gitlab.librewolf-community/.librewolf" import_firefox_root "$REAL_HOME/.var/app/net.waterfox.waterfox/.waterfox" import_firefox_root "$REAL_HOME/.var/app/one.ablaze.floorp/.floorp" import_firefox_root "$REAL_HOME/.var/app/com.mullvad.MullvadBrowser/.mullvad" # Browser snap Chromium-based / affini for _snap_nssdb in \ "$REAL_HOME/snap/chromium/current/.pki/nssdb" \ "$REAL_HOME/snap/chromium/common/.pki/nssdb" \ "$REAL_HOME/snap/brave/current/.pki/nssdb" \ "$REAL_HOME/snap/microsoft-edge/current/.pki/nssdb" \ "$REAL_HOME/snap/opera/current/.pki/nssdb" \ "$REAL_HOME/snap/vivaldi/current/.pki/nssdb" \ ; do ensure_nssdb "$_snap_nssdb" import_nss_db "$_snap_nssdb" done # Browser Chromium-based nativi — tutti usano ~/.pki/nssdb (già importato sopra) # ma alcuni hanno il proprio nssdb separato for _chrome_cfg in \ "$REAL_HOME/.config/google-chrome" \ "$REAL_HOME/.config/google-chrome-beta" \ "$REAL_HOME/.config/google-chrome-unstable" \ "$REAL_HOME/.config/chromium" \ "$REAL_HOME/.config/chromium-browser" \ "$REAL_HOME/.config/BraveSoftware/Brave-Browser" \ "$REAL_HOME/.config/BraveSoftware/Brave-Browser-Beta" \ "$REAL_HOME/.config/BraveSoftware/Brave-Browser-Nightly" \ "$REAL_HOME/.config/vivaldi" \ "$REAL_HOME/.config/vivaldi-snapshot" \ "$REAL_HOME/.config/opera" \ "$REAL_HOME/.config/opera-beta" \ "$REAL_HOME/.config/opera-developer" \ "$REAL_HOME/.config/microsoft-edge" \ "$REAL_HOME/.config/microsoft-edge-beta" \ "$REAL_HOME/.config/microsoft-edge-dev" \ "$REAL_HOME/.config/thorium" \ "$REAL_HOME/.config/Thorium" \ "$REAL_HOME/.config/ungoogled-chromium" \ "$REAL_HOME/.config/yandex-browser" \ "$REAL_HOME/.config/epiphany" \ ; do [[ -d "$_chrome_cfg" ]] || continue while IFS= read -r -d '' p; do ensure_nssdb "$p" import_nss_db "$p" done < <(find "$_chrome_cfg" -type d -name nssdb -print0 2>/dev/null || true) done # Chromium / Chrome / Brave / Vivaldi / Edge / Opera flatpak for _flatpak_id in \ org.chromium.Chromium \ com.google.Chrome \ com.google.ChromeDev \ com.brave.Browser \ com.vivaldi.Vivaldi \ com.opera.Opera \ com.microsoft.Edge \ io.github.ungoogled_software.ungoogled_chromium \ com.github.Eloston.UngoogledChromium \ org.gnome.Epiphany \ ; do if [[ -d "$REAL_HOME/.var/app/${_flatpak_id}" ]]; then ensure_nssdb "$REAL_HOME/.var/app/${_flatpak_id}/.pki/nssdb" import_nss_db "$REAL_HOME/.var/app/${_flatpak_id}/.pki/nssdb" fi while IFS= read -r -d '' p; do ensure_nssdb "$p" import_nss_db "$p" done < <(find "$REAL_HOME/.var/app/${_flatpak_id}" -type d -name nssdb -print0 2>/dev/null || true) done fix_selinux_user_browser_contexts else warn "certutil non disponibile o utente reale non determinato — NSS utente saltato" fi # ── 4. Step finale di sicurezza ─────────────────────────────────────────────── if have_cmd certutil && [[ -n "${REAL_USER:-}" && -n "${REAL_HOME:-}" ]]; then FINAL_NSS_DB="$REAL_HOME/.pki/nssdb" log "Step finale di sicurezza su NSS utente: $FINAL_NSS_DB" ensure_nssdb "$FINAL_NSS_DB" nss_delete_matching_nicknames_user "sql:$FINAL_NSS_DB" if run_as_real_user certutil -A \ -d "sql:$FINAL_NSS_DB" \ -n "$CA_NAME" \ -t "CT,," \ -i "$CA_SRC" >/dev/null 2>&1; then log "✅ Step finale NSS utente completato" else warn "Step finale NSS utente fallito in sql:, provo dbm:" nss_delete_matching_nicknames_user "dbm:$FINAL_NSS_DB" if run_as_real_user certutil -A \ -d "dbm:$FINAL_NSS_DB" \ -n "$CA_NAME" \ -t "CT,," \ -i "$CA_SRC" >/dev/null 2>&1; then log "✅ Step finale NSS utente completato (dbm)" else warn "Step finale NSS utente fallito anche in dbm:" fi fi fi fix_selinux_user_browser_contexts # ── 5. Verifica finale ──────────────────────────────────────────────────────── if have_cmd update-ca-certificates && [[ -d /etc/ssl/certs ]]; then hash_val="$(openssl x509 -in "$CA_SRC" -noout -hash 2>/dev/null || true)" if [[ -n "$hash_val" ]] && ls "/etc/ssl/certs/${hash_val}".* >/dev/null 2>&1; then log "✅ Verifica bundle OK: /etc/ssl/certs/${hash_val}.0 presente" else warn "Hash non trovato in /etc/ssl/certs — verifica: ls /etc/ssl/certs/${hash_val}.*" fi fi if have_cmd certutil && [[ -n "${REAL_USER:-}" && -n "${REAL_HOME:-}" ]]; then if run_as_real_user certutil -L -d "sql:$REAL_HOME/.pki/nssdb" -n "$CA_NAME" >/dev/null 2>&1; then log "✅ Verifica NSS utente OK: $REAL_HOME/.pki/nssdb" else warn "Verifica NSS utente non riuscita su sql:$REAL_HOME/.pki/nssdb" fi fi install_bastionguard_cef_selinux_policy fix_user_ca_permissions fix_selinux_system_trust_contexts fix_selinux_user_browser_contexts restart_bastionguard_cef_user_service_if_active log "✅ Installazione completata" exit 0