BastionGuard/data/scripts/install-ca-system.sh
specialworld83 f0f913a209 Release 2.0
2026-07-15 10:52:22 +02:00

1643 lines
61 KiB
Bash

#!/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" <<EOF
[req]
distinguished_name = req_dn
x509_extensions = v3_ca
prompt = no
[req_dn]
$(echo "$subject" | sed 's|/\([^=]*\)=|\1 = |g; s|^ ||')
[v3_ca]
basicConstraints = critical, CA:TRUE
keyUsage = critical, keyCertSign, cRLSign
subjectKeyIdentifier = hash
EOF
openssl req -new -x509 \
-key "$CA_KEY" \
-out "$CA_SRC" \
-days "$remaining_days" \
-config "$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" <<EOF
[req]
distinguished_name = req_dn
x509_extensions = v3_ca
prompt = no
[req_dn]
$(echo "$orig_subject" | sed 's|/\([^=]*\)=|\1 = |g; s|^ ||')
[v3_ca]
basicConstraints = critical, CA:TRUE
keyUsage = critical, keyCertSign, cRLSign
subjectKeyIdentifier = hash
EOF
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
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