#!/bin/bash
#
# FileWave Booster (macOS / Linux): remove the compromised eMudhra emSign G3 root certificates from the
# booster trust store (fw_cacert.pem).
#
# Background: in October 2026 the private keys of eight eMudhra intermediate CAs were
# compromised. The booster trusts every certificate in its trust_store folder, plus the OS
# trust store, so the emSign G3 roots are removed from fw_cacert.pem, as upstream certifi 2026.10.19
# does, and the OS store is checked. The other emSign roots (G1, C1, C3) are not affected.
#
# Usage:  sudo ./fw_booster_remove_emsign.sh [--dry-run] [--no-restart] [--file PATH]
#   --dry-run     only report what would be removed
#   --no-restart  do not restart the booster (restart it later yourself:
#                 sudo /usr/local/sbin/fwcontrol booster restart)
#   --file PATH   clean this CA file instead of the booster trust store file
#
# The backup of fw_cacert.pem is written OUTSIDE the trust_store folder, because the booster
# loads every file in that folder. The script is idempotent: running it again changes nothing.

set -euo pipefail

FWCONTROL=/usr/local/sbin/fwcontrol

DRY_RUN=0
RESTART=1
FILES=()

usage() { sed -n '2,19p' "$0" | sed 's/^# \{0,1\}//'; }

while [ $# -gt 0 ]; do
    case "$1" in
        --dry-run) DRY_RUN=1 ;;
        --no-restart) RESTART=0 ;;
        --file) shift; FILES+=("${1:?--file needs a path}") ;;
        -h|--help) usage; exit 0 ;;
        *) echo "Unknown option: $1" >&2; usage; exit 2 ;;
    esac
    shift
done

command -v openssl >/dev/null || { echo "ERROR: openssl not found in PATH" >&2; exit 1; }

if [ "$(uname)" = "Darwin" ]; then
    TRUST_STORE_FILE=/private/var/FileWave/trust_store/fw_cacert.pem
else
    TRUST_STORE_FILE=/var/FileWave/trust_store/fw_cacert.pem
fi

if [ ${#FILES[@]} -eq 0 ]; then
    [ -f "$TRUST_STORE_FILE" ] || { echo "ERROR: $TRUST_STORE_FILE not found, is this a FileWave Booster?" >&2; exit 1; }
    FILES+=("$TRUST_STORE_FILE")
    # The Linux booster also ships FileWave Python (supervisord); clean its bundle as well.
    for f in /usr/local/filewave/python/lib/python3*/site-packages/certifi/cacert.pem; do
        [ -f "$f" ] && FILES+=("$f")
    done
fi

REMOVED=0
KEPT=0
CHANGED=0

# The roots excluded by certifi 2026.10.19; the TLS one is not in certifi but may be in an OS store.
AFFECTED_ROOTS='CN ?= ?emSign (ECC Root CA|Root TLS CA) - G3'

# filter_bundle IN OUT: writes IN to OUT without the affected roots (and their comment headers)
filter_bundle() {
    local in="$1" out="$2" chunk="" pem="" in_pem=0 line subject
    REMOVED=0
    KEPT=0
    : > "$out"
    while IFS= read -r line || [ -n "$line" ]; do
        chunk+="$line"$'\n'
        if [ "$line" = "-----BEGIN CERTIFICATE-----" ]; then
            in_pem=1
            pem="$line"$'\n'
        elif [ "$in_pem" -eq 1 ] && [ "$line" = "-----END CERTIFICATE-----" ]; then
            in_pem=0
            pem+="$line"$'\n'
            subject=$(printf '%s' "$pem" | openssl x509 -noout -subject 2>/dev/null || true)
            if printf '%s' "$subject" | grep -qE "$AFFECTED_ROOTS"; then
                echo "  affected root: ${subject#subject=}"
                REMOVED=$((REMOVED + 1))
            else
                printf '%s' "$chunk" >> "$out"
                KEPT=$((KEPT + 1))
            fi
            chunk=""
        elif [ "$in_pem" -eq 1 ]; then
            pem+="$line"$'\n'
        fi
    done < "$in"
    printf '%s' "$chunk" >> "$out"
}

# clean_file FILE BACKUP_DIR
clean_file() {
    local file="$1" backup_dir="$2" tmp backup
    echo "Checking $file"
    [ -f "$file" ] || { echo "  ERROR: file not found" >&2; return 1; }
    if [ "$DRY_RUN" -eq 0 ] && { [ ! -w "$file" ] || [ ! -w "$backup_dir" ]; }; then
        echo "  ERROR: no write access, run with sudo" >&2
        return 1
    fi
    tmp=$(mktemp "$backup_dir/.emsign.XXXXXX")
    cp -p "$file" "$tmp"
    filter_bundle "$file" "$tmp"
    if [ "$REMOVED" -eq 0 ]; then
        echo "  no affected emSign root found, nothing to do"
        rm -f "$tmp"
        return 0
    fi
    if [ "$KEPT" -eq 0 ]; then
        echo "  ERROR: nothing would be left in the file, left untouched" >&2
        rm -f "$tmp"
        return 1
    fi
    if [ "$DRY_RUN" -eq 1 ]; then
        echo "  dry run: would remove $REMOVED certificate(s) and keep $KEPT"
        rm -f "$tmp"
        return 0
    fi
    backup="$backup_dir/$(basename "$file").bak-emsign-$(date +%Y%m%d%H%M%S)"
    cp -p "$file" "$backup"
    mv -f "$tmp" "$file"
    CHANGED=1
    echo "  removed $REMOVED certificate(s), kept $KEPT, backup: $backup"
}

for f in "${FILES[@]}"; do
    if [ "$(basename "$(dirname "$f")")" = "trust_store" ]; then
        clean_file "$f" "$(dirname "$(dirname "$f")")"
    else
        clean_file "$f" "$(dirname "$f")"
    fi
done

echo "Checking the OS trust store (the booster trusts it too)..."
os_count=0
if [ "$(uname)" = "Darwin" ]; then
    os_count=$(security find-certificate -a -c "emSign" \
        /System/Library/Keychains/SystemRootCertificates.keychain /Library/Keychains/System.keychain 2>/dev/null \
        | grep '"labl"' | grep -cE 'emSign (ECC Root CA|Root TLS CA) - G3' || true)
else
    for bundle in /etc/ssl/certs/ca-certificates.crt /etc/pki/tls/certs/ca-bundle.crt; do
        if [ -f "$bundle" ]; then
            filter_bundle "$bundle" /dev/null
            os_count=$REMOVED
            break
        fi
    done
fi
if [ "$os_count" -gt 0 ]; then
    echo "  WARNING: the OS trust store still holds $os_count affected emSign root(s), the booster keeps trusting them."
    echo "           Install the latest OS updates (Linux: the ca-certificates package) once the vendor ships the removal."
else
    echo "  OK, no affected emSign root in the OS trust store"
fi

if [ "$CHANGED" -eq 1 ]; then
    if [ "$RESTART" -eq 1 ]; then
        echo "Restarting the FileWave Booster..."
        if command -v systemctl >/dev/null && systemctl cat fw-booster.service >/dev/null 2>&1; then
            systemctl restart fw-booster.service
        else
            "$FWCONTROL" booster restart
        fi
    else
        echo "Restart the booster to apply: sudo $FWCONTROL booster restart"
    fi
fi
echo "Done."
