#!/bin/bash # # Integrity verification of the installed LibrePortal code tree. # # Re-hashes $script_dir against the signed SHA256SUMS manifest that ships INSIDE # every release tarball (built by scripts/release/make_release.sh) and checks # that manifest's minisign signature against the ROOT-OWNED public key in the # footprint (/usr/local/lib/libreportal/libreportal.pub). A pass proves the # installed files still match the release we published and signed. # # Honest scope: this runs BY the software it verifies, so it catches accidental # drift, partial tampering, and "is this a real signed release" — but it is NOT # tamper-proof on its own (a wholesale code swap could also fake the result). # The hard guarantee is out-of-band: the user running `minisign -Vm `. # # Results land in LP_VERIFY_* globals so the generator + CLI can format them: # LP_VERIFY_STATE verified|modified|tampered|unsigned|unverifiable|development # LP_VERIFY_SIGNED true|false (a real, non-placeholder key signed it) # LP_VERIFY_SIG_VALID true|false|null (null = not applicable / couldn't check) # LP_VERIFY_TOTAL files listed in the manifest # LP_VERIFY_OK / _MODIFIED / _MISSING # LP_VERIFY_SAMPLE up to 5 offending relative paths, newline-separated # LP_VERIFY_ERROR human message, or empty lpVerifyPubKeyPath() { echo "/usr/local/lib/libreportal/libreportal.pub"; } lpVerifyInstall() { LP_VERIFY_STATE="development" LP_VERIFY_SIGNED="false" LP_VERIFY_SIG_VALID="null" LP_VERIFY_TOTAL=0; LP_VERIFY_OK=0; LP_VERIFY_MODIFIED=0; LP_VERIFY_MISSING=0 LP_VERIFY_SAMPLE="" LP_VERIFY_ERROR="" local mode="${CFG_INSTALL_MODE:-release}" local root="${script_dir%/}" local manifest="$root/SHA256SUMS" local sig="$root/SHA256SUMS.minisig" local pub; pub="$(lpVerifyPubKeyPath)" # No manifest — a git/local dev install, or a build without one. There's # nothing to verify against, so report a neutral "development build" rather # than implying something is wrong. if [[ "$mode" != "release" || ! -f "$manifest" ]]; then LP_VERIFY_STATE="development" return 0 fi LP_VERIFY_TOTAL=$(wc -l < "$manifest" 2>/dev/null | tr -d ' ') [[ -n "$LP_VERIFY_TOTAL" ]] || LP_VERIFY_TOTAL=0 # Manifest signature. The pubkey is in the root-owned footprint so the # manager can't swap it to bless a forged manifest. A REPLACE_ME placeholder # means signing isn't activated for this build → treat the manifest as # unsigned (drift detection still works, we just can't vouch for it). if [[ -f "$pub" ]] && ! grep -q REPLACE_ME "$pub" 2>/dev/null; then LP_VERIFY_SIGNED="true" if ! command -v minisign >/dev/null 2>&1; then LP_VERIFY_STATE="unverifiable" LP_VERIFY_ERROR="minisign is not installed, so the signed manifest can't be checked." return 0 fi if [[ ! -f "$sig" ]]; then LP_VERIFY_STATE="tampered" LP_VERIFY_SIG_VALID="false" LP_VERIFY_ERROR="The release manifest signature (SHA256SUMS.minisig) is missing." return 0 fi if ! minisign -Vm "$manifest" -p "$pub" -x "$sig" >/dev/null 2>&1; then LP_VERIFY_STATE="tampered" LP_VERIFY_SIG_VALID="false" LP_VERIFY_ERROR="The release manifest signature is invalid." return 0 fi LP_VERIFY_SIG_VALID="true" fi # Re-hash every listed file. `--check --quiet` prints only failures; each is # either ": FAILED" (content differs) or ": FAILED open or read" # (missing/unreadable). Lines prefixed "sha256sum:" are diagnostics/summary # — skip them so a missing file isn't double-counted. Run from $root so the # manifest's ./relative paths resolve. local check_out line path sample_count=0 check_out="$(cd "$root" && sha256sum --check --quiet "$manifest" 2>&1)" while IFS= read -r line; do [[ -z "$line" || "$line" == sha256sum:* ]] && continue if [[ "$line" == *": FAILED open or read" ]]; then LP_VERIFY_MISSING=$((LP_VERIFY_MISSING + 1)); path="${line%: FAILED open or read}" elif [[ "$line" == *": FAILED" ]]; then LP_VERIFY_MODIFIED=$((LP_VERIFY_MODIFIED + 1)); path="${line%: FAILED}" else continue fi if (( sample_count < 5 )); then LP_VERIFY_SAMPLE+="${LP_VERIFY_SAMPLE:+$'\n'}${path#./}" sample_count=$((sample_count + 1)) fi done <<< "$check_out" local bad=$((LP_VERIFY_MODIFIED + LP_VERIFY_MISSING)) LP_VERIFY_OK=$((LP_VERIFY_TOTAL - bad)) (( LP_VERIFY_OK < 0 )) && LP_VERIFY_OK=0 if (( bad > 0 )); then LP_VERIFY_STATE="modified" elif [[ "$LP_VERIFY_SIGNED" == "true" ]]; then LP_VERIFY_STATE="verified" else LP_VERIFY_STATE="unsigned" fi return 0 }