#!/usr/bin/env bash set -euo pipefail usage() { cat <<'EOF' Usage: scripts/verify_release.sh --dir [--repo /] What it does: - Verifies checksums.txt signature (keyless cosign) if cosign + checksums.txt.sig/.cert are present - Verifies *.tar.gz files against checksums.txt Notes: - --repo enables strict Sigstore identity checking against the release workflow. - Without cosign, the script still verifies SHA256 hashes. Examples: scripts/verify_release.sh --dir ./release --repo jfraeys/fetch_ml scripts/verify_release.sh --dir . EOF } release_dir="" repo="" while [[ $# -gt 0 ]]; do case "$1" in --dir) release_dir="${2:-}" shift 2 ;; --repo) repo="${2:-}" shift 2 ;; -h|--help) usage exit 0 ;; *) echo "unknown argument: $1" >&2 usage >&2 exit 2 ;; esac done if [[ -z "$release_dir" ]]; then echo "missing --dir" >&2 usage >&2 exit 2 fi if [[ ! -d "$release_dir" ]]; then echo "directory not found: $release_dir" >&2 exit 2 fi cd "$release_dir" if [[ ! -f checksums.txt ]]; then echo "missing checksums.txt in $release_dir" >&2 exit 2 fi has_cosign=false if command -v cosign >/dev/null 2>&1; then has_cosign=true fi verify_sigstore() { if [[ ! -f checksums.txt.sig ]] || [[ ! -f checksums.txt.cert ]]; then echo "[verify] cosign available, but checksums.txt.sig/.cert not found; skipping signature verification" >&2 return 0 fi if [[ -z "$repo" ]]; then echo "[verify] verifying signature (no repo identity pin; pass --repo to pin identity)" >&2 COSIGN_YES=true cosign verify-blob \ --certificate checksums.txt.cert \ --signature checksums.txt.sig \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ checksums.txt >/dev/null echo "[ok] checksums.txt signature verified (un-pinned identity)" return 0 fi local identity identity="^https://github.com/${repo}/\.github/workflows/release\.yml@refs/tags/v.*$" COSIGN_YES=true cosign verify-blob \ --certificate checksums.txt.cert \ --signature checksums.txt.sig \ --certificate-identity-regexp "$identity" \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ checksums.txt >/dev/null echo "[ok] checksums.txt signature verified (pinned to ${repo} release workflow)" } verify_hashes() { local failures=0 local has_sha256sum=false if command -v sha256sum >/dev/null 2>&1; then has_sha256sum=true fi while IFS= read -r expected file; do [[ -z "${expected}" ]] && continue [[ -z "${file}" ]] && continue if [[ ! -f "$file" ]]; then continue fi local actual if [[ "$has_sha256sum" == true ]]; then actual="$(sha256sum "$file" | awk '{print $1}')" else actual="$(shasum -a 256 "$file" | awk '{print $1}')" fi if [[ "$actual" != "$expected" ]]; then echo "[fail] $file" >&2 echo " expected: $expected" >&2 echo " actual: $actual" >&2 failures=$((failures+1)) fi done < <(awk '{print $1, $2}' checksums.txt) if [[ $failures -gt 0 ]]; then echo "[fail] checksum verification failed ($failures file(s))" >&2 exit 1 fi echo "[ok] all available artifacts match checksums.txt" } if [[ "$has_cosign" == true ]]; then verify_sigstore else echo "[verify] cosign not installed; skipping signature verification" >&2 fi verify_hashes echo "[ok] release verification complete"