RedFlag/installer/windows/verify-msi.sh
Fimeg 67e26be2d9 projection: begin the exact-path public epoch
The public tree and its history contain only the listed paths. Earlier projection history remains preserved internally.

Source-Sha: 913fde029b935671833254797f0f20f1eb9fabba

Policy-Sha: 913fde029b935671833254797f0f20f1eb9fabba

Tree-Digest: 180ae530c1058a2a5c89837bdce2d323ae83e669e38590ca72e75b8d92b7262f
2026-09-08 21:59:33 -04:00

179 lines
7.4 KiB
Shell
Executable file

#!/usr/bin/env bash
# Prove that a RedFlag Server MSI contains the exact bytes staged for it.
# MSI compressed size is diagnostic only; it is never an acceptance gate.
set -uo pipefail
MSI="${1:-}"
STAGED_EXE="${2:-}"
STAGED_ENV="${3:-}"
shift "$(( $# >= 3 ? 3 : $# ))"
REDFLAG_VERSION=""
MSI_VERSION=""
PROOF_RECORD=""
SOURCE_COMMIT="${GITHUB_SHA:-}"
SOURCE_REF="${GITHUB_REF:-}"
while [ "$#" -gt 0 ]; do
[ "$#" -ge 2 ] || { echo "missing value for $1" >&2; exit 2; }
case "$1" in
--redflag-version) REDFLAG_VERSION="${2:-}"; shift 2 ;;
--msi-version) MSI_VERSION="${2:-}"; shift 2 ;;
--proof-record) PROOF_RECORD="${2:-}"; shift 2 ;;
--source-commit) SOURCE_COMMIT="${2:-}"; shift 2 ;;
--source-ref) SOURCE_REF="${2:-}"; shift 2 ;;
*) echo "unknown argument: $1" >&2; exit 2 ;;
esac
done
[ -n "$MSI" ] && [ -n "$STAGED_EXE" ] && [ -n "$STAGED_ENV" ] || {
echo "usage: $0 <msi> <staged-server-exe> <staged-env-example> [options]" >&2
exit 2
}
for f in "$MSI" "$STAGED_EXE" "$STAGED_ENV"; do
[ -f "$f" ] || { echo "no such file: $f" >&2; exit 2; }
done
for tool in msiinfo msiextract sha256sum; do
command -v "$tool" >/dev/null || { echo "$tool not found" >&2; exit 2; }
done
if [ -n "$PROOF_RECORD" ]; then
[ -n "$REDFLAG_VERSION" ] && [ -n "$MSI_VERSION" ] || {
echo "--proof-record requires --redflag-version and --msi-version" >&2; exit 2; }
command -v jq >/dev/null || { echo "jq not found" >&2; exit 2; }
fi
FAIL=0
EMBEDDED=false
ok() { echo "[ok] $*"; }
bad() { echo "[FAIL] $*"; FAIL=1; }
MSI_SIZE=$(stat -c%s "$MSI")
MSI_SHA=$(sha256sum "$MSI" | cut -d' ' -f1)
STAGED_SERVER_SIZE=$(stat -c%s "$STAGED_EXE")
STAGED_SERVER_SHA=$(sha256sum "$STAGED_EXE" | cut -d' ' -f1)
STAGED_CONFIG_SIZE=$(stat -c%s "$STAGED_ENV")
STAGED_CONFIG_SHA=$(sha256sum "$STAGED_ENV" | cut -d' ' -f1)
echo "=== package under test ==="
echo " path: $MSI"
echo " size: $MSI_SIZE bytes <- diagnostic only, never a gate"
echo " sha256: $MSI_SHA"
echo "=== tables ==="
for t in Property Directory Component File Feature FeatureComponents \
ServiceInstall ServiceControl Upgrade Media; do
if msiinfo export "$MSI" "$t" >/dev/null 2>&1; then ok "table $t present"
else bad "table $t missing"; fi
done
carries() {
if msiinfo export "$MSI" "$1" 2>/dev/null | grep -F "$2" >/dev/null; then ok "$1 carries $2"
else bad "$1 does not carry $2"; fi
}
carries ServiceInstall RedFlagServer
carries ServiceControl RedFlagServer
carries Component ServerBinary
carries Component ConfigTemplate
carries File redflag-server.exe
carries File redflag.env.example
echo "=== version identity ==="
PRODUCT_VERSION=$(msiinfo export "$MSI" Property 2>/dev/null |
awk -F'\t' '$1 == "ProductVersion" {print $2; exit}' | tr -d '\r')
if [ -n "$MSI_VERSION" ]; then
[ "$PRODUCT_VERSION" = "$MSI_VERSION" ] && ok "MSI ProductVersion is $MSI_VERSION" || \
bad "MSI ProductVersion '${PRODUCT_VERSION:-missing}' != expected $MSI_VERSION"
fi
if [ -n "$REDFLAG_VERSION" ]; then
if ! command -v go >/dev/null; then
bad "go is required to inspect the staged Server build version"
else
SERVER_VERSION=$(go version -m "$STAGED_EXE" 2>/dev/null |
sed -n 's/.*versions\.AgentVersion=\([^[:space:]]*\).*/\1/p' | head -1)
[ "$SERVER_VERSION" = "$REDFLAG_VERSION" ] && \
ok "staged Server build version is $REDFLAG_VERSION" || \
bad "staged Server build version '${SERVER_VERSION:-missing}' != release $REDFLAG_VERSION"
fi
fi
echo "=== cabinet custody ==="
msiinfo export "$MSI" Media 2>/dev/null | sed 's/^/ media| /'
CAB=$(msiinfo export "$MSI" Media 2>/dev/null |
awk -F'\t' 'NR > 3 && $4 != "" {print $4; exit}')
if [ -z "$CAB" ]; then
bad "Media table names no cabinet at all"
elif [ "${CAB#\#}" = "$CAB" ]; then
bad "Media names external cabinet '$CAB'"
else
STREAM="${CAB#\#}"
if msiinfo streams "$MSI" 2>/dev/null | grep -Fx "$STREAM" >/dev/null; then
EMBEDDED=true
ok "Media names embedded cabinet '$CAB' and storage carries '$STREAM'"
else
bad "embedded cabinet stream '$STREAM' is absent"
fi
fi
echo "=== extraction (MSI copied alone to an empty directory) ==="
ISO=$(mktemp -d)
OUT=$(mktemp -d)
trap 'rm -rf "$ISO" "$OUT"' EXIT
cp "$MSI" "$ISO/package.msi"
if (cd "$ISO" && msiextract -C "$OUT" package.msi >/dev/null 2>&1); then
ok "msiextract succeeded without a sibling cabinet"
else
bad "msiextract failed without a sibling cabinet"
fi
find "$OUT" -type f -printf ' %s bytes %P\n' 2>/dev/null | sort || true
EXTRACTED_SERVER=""
EXTRACTED_CONFIG=""
compare() {
local name="$1" staged="$2" destination="$3" got gs ss gh sh
got=$(find "$OUT" -type f -name "$name" -print -quit 2>/dev/null)
if [ -z "$got" ]; then bad "$name is not in the extracted payload"; return; fi
printf -v "$destination" '%s' "$got"
gs=$(stat -c%s "$got"); ss=$(stat -c%s "$staged")
gh=$(sha256sum "$got" | cut -d' ' -f1); sh=$(sha256sum "$staged" | cut -d' ' -f1)
[ "$gs" = "$ss" ] && ok "$name size matches staged ($gs bytes)" || bad "$name size $gs != staged $ss"
[ "$gh" = "$sh" ] && ok "$name sha256 matches staged ($gh)" || bad "$name sha256 $gh != staged $sh"
}
echo "=== payload identity ==="
compare redflag-server.exe "$STAGED_EXE" EXTRACTED_SERVER
compare redflag.env.example "$STAGED_ENV" EXTRACTED_CONFIG
if [ "$FAIL" -ne 0 ]; then
echo "RESULT: payload custody NOT proven"
exit 1
fi
EXTRACTED_SERVER_SHA=$(sha256sum "$EXTRACTED_SERVER" | cut -d' ' -f1)
EXTRACTED_CONFIG_SHA=$(sha256sum "$EXTRACTED_CONFIG" | cut -d' ' -f1)
if [ -n "$PROOF_RECORD" ]; then
mkdir -p "$(dirname "$PROOF_RECORD")"
WIXL_VERSION=$(wixl --version 2>&1 | head -1 || true)
MSIINFO_VERSION=$(msiinfo --version 2>&1 | head -1 || true)
MSIEXTRACT_VERSION=$(msiextract --version 2>&1 | head -1 || true)
GO_VERSION=$(go version 2>&1 || true)
jq -n \
--arg source_commit "$SOURCE_COMMIT" --arg source_ref "$SOURCE_REF" \
--arg redflag_version "$REDFLAG_VERSION" --arg msi_product_version "$PRODUCT_VERSION" \
--arg msi_file "$(basename "$MSI")" --arg msi_sha256 "$MSI_SHA" --argjson msi_size "$MSI_SIZE" \
--arg staged_server_sha256 "$STAGED_SERVER_SHA" --argjson staged_server_size "$STAGED_SERVER_SIZE" \
--arg extracted_server_sha256 "$EXTRACTED_SERVER_SHA" \
--arg staged_config_sha256 "$STAGED_CONFIG_SHA" --argjson staged_config_size "$STAGED_CONFIG_SIZE" \
--arg extracted_config_sha256 "$EXTRACTED_CONFIG_SHA" \
--arg wixl "$WIXL_VERSION" --arg msiinfo "$MSIINFO_VERSION" \
--arg msiextract "$MSIEXTRACT_VERSION" --arg go "$GO_VERSION" \
'{record_type:"redflag-installer-custody-proof",signed_release_manifest:false,
source:{commit:$source_commit,ref:$source_ref},redflag_version:$redflag_version,
msi:{filename:$msi_file,product_version:$msi_product_version,size:$msi_size,sha256:$msi_sha256},
staged:{server:{size:$staged_server_size,sha256:$staged_server_sha256},config:{size:$staged_config_size,sha256:$staged_config_sha256}},
extracted:{server:{sha256:$extracted_server_sha256},config:{sha256:$extracted_config_sha256}},
cabinet_embedded:true,verifier_result:"passed",
tools:{wixl:$wixl,msiinfo:$msiinfo,msiextract:$msiextract,go:$go}}' > "$PROOF_RECORD"
ok "wrote installer proof record $PROOF_RECORD"
fi
echo "RESULT: payload custody proven"
echo "BOUNDARY: this is not a Windows installation or publisher-authentication proof"