#!/usr/bin/env bash # Verify a downloaded MoonBite release artifact. # # scripts/release/verify-release.sh [SHA256SUMS.txt] [--pub deploy/moonbite-release.pub] [--owner moonbitecoin] # # Checks, in order: # 1. sha256 - the artifact's hash appears in SHA256SUMS.txt under its own name (REQUIRED) # 2. minisign - SHA256SUMS.txt.minisig verifies against the pinned public key (when minisign is installed and the key is not the placeholder) # 3. sigstore - `gh attestation verify --owner ` passes (when gh is installed) # # Exit 0 only when every check that could run passed. A check that could not run # (tool missing, placeholder key, no .minisig) is reported as SKIP and does not # fail the result, but the summary says what was and was not proven. # Works in bash on Linux, macOS and Git Bash. No emoji; plain PASS/FAIL/SKIP. set -euo pipefail usage() { sed -n '2,15p' "$0" | sed 's/^# \{0,1\}//'; exit 2; } ARTIFACT=""; SUMS=""; PUB=""; OWNER="moonbitecoin" while [ $# -gt 0 ]; do case "$1" in --pub) PUB="$2"; shift 2 ;; --owner) OWNER="$2"; shift 2 ;; -h|--help) usage ;; -*) echo "unknown option: $1"; usage ;; *) if [ -z "$ARTIFACT" ]; then ARTIFACT="$1"; elif [ -z "$SUMS" ]; then SUMS="$1"; else usage; fi; shift ;; esac done [ -n "$ARTIFACT" ] || usage [ -f "$ARTIFACT" ] || { echo "FAIL artifact not found: $ARTIFACT"; exit 1; } DIR="$(cd "$(dirname "$ARTIFACT")" && pwd)" NAME="$(basename "$ARTIFACT")" [ -n "$SUMS" ] || SUMS="$DIR/SHA256SUMS.txt" if [ -z "$PUB" ]; then HERE="$(cd "$(dirname "$0")" && pwd)" for c in "$DIR/moonbite-release.pub" "$HERE/../../deploy/moonbite-release.pub"; do [ -f "$c" ] && { PUB="$c"; break; } done fi pass=0; fail=0; skip=0 ok() { echo "PASS $*"; pass=$((pass+1)); } bad() { echo "FAIL $*"; fail=$((fail+1)); } skp() { echo "SKIP $*"; skip=$((skip+1)); } sha256_of() { if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | cut -d' ' -f1 elif command -v shasum >/dev/null 2>&1; then shasum -a 256 "$1" | cut -d' ' -f1 else openssl dgst -sha256 "$1" | awk '{print $NF}'; fi } echo "artifact : $ARTIFACT" echo "manifest : $SUMS" echo "pubkey : ${PUB:-}" echo # 1. sha256 ------------------------------------------------------------------ if [ ! -f "$SUMS" ]; then bad "sha256: manifest not found ($SUMS). Download SHA256SUMS.txt from the same release." else ACTUAL="$(sha256_of "$ARTIFACT" | tr 'A-F' 'a-f')" # " name" or " *name"; match on basename EXPECTED="$(grep -Ei "^[0-9a-f]{64}[[:space:]]+\*?(.*/)?${NAME//./\\.}[[:space:]]*$" "$SUMS" | head -1 | awk '{print tolower($1)}' || true)" if [ -z "$EXPECTED" ]; then bad "sha256: '$NAME' is not listed in $SUMS" elif [ "$EXPECTED" = "$ACTUAL" ]; then ok "sha256: $ACTUAL matches manifest" else bad "sha256: MISMATCH manifest=$EXPECTED actual=$ACTUAL -- do not run this file" fi fi # 2. minisign ------------------------------------------------------------------ KEYLINE="" if [ -n "$PUB" ] && [ -f "$PUB" ]; then KEYLINE="$(grep -v '^[[:space:]]*#' "$PUB" | grep -v '^[[:space:]]*$' | grep -v '^untrusted comment' | head -1 || true)" fi if ! command -v minisign >/dev/null 2>&1; then skp "minisign: tool not installed (https://jedisct1.github.io/minisign/)" elif [ -z "$KEYLINE" ] || [ "${KEYLINE#REPLACE_ME}" != "$KEYLINE" ]; then skp "minisign: release public key is still a placeholder (key ceremony pending, deploy/RELEASE-SIGNING.md)" elif [ ! -f "$SUMS.minisig" ]; then skp "minisign: no $(basename "$SUMS").minisig next to the manifest" elif minisign -Vm "$SUMS" -P "$KEYLINE" >/dev/null 2>&1; then ok "minisign: $(basename "$SUMS").minisig verifies against pinned key" else bad "minisign: signature on $(basename "$SUMS") does NOT verify -- manifest may be forged" fi # 3. Sigstore attestation ------------------------------------------------------ if ! command -v gh >/dev/null 2>&1; then skp "sigstore: GitHub CLI (gh) not installed; cannot check build provenance" else if OUT="$(gh attestation verify "$ARTIFACT" --owner "$OWNER" 2>&1)"; then ok "sigstore: attestation verified for owner '$OWNER' (built by GitHub Actions from the public repo)" echo "$OUT" | grep -Ei 'repo|workflow|ref' | sed 's/^/ /' || true else case "$OUT" in *"not logged"*|*"auth login"*|*"authentication"*|*"Bad credentials"*|*"HTTP 401"*) skp "sigstore: gh is not authenticated or its token is invalid (run 'gh auth login'); attestation not checked" ;; *) bad "sigstore: no valid attestation for this file under owner '$OWNER'" echo "$OUT" | tail -5 | sed 's/^/ /' ;; esac fi fi # Summary ---------------------------------------------------------------------- echo echo "result: $pass passed, $fail failed, $skip skipped" if [ "$fail" -gt 0 ]; then echo "VERDICT: FAIL - do not use this artifact." exit 1 fi if [ "$pass" -eq 0 ]; then echo "VERDICT: NOTHING PROVEN - no check could run." exit 1 fi if [ "$skip" -gt 0 ]; then echo "VERDICT: PASS (with $skip check(s) skipped - integrity shown; see SKIP lines for what was not proven)" else echo "VERDICT: PASS - integrity, authenticity and build provenance all verified." fi exit 0