#!/usr/bin/env bash # SPDX-License-Identifier: AGPL-3.0-or-later # BENCH-AUTO-401-019: Online DSSE + Rekor verification script set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' log_pass() { echo -e "${GREEN}✓${NC} $*"; } log_fail() { echo -e "${RED}✗${NC} $*"; } log_warn() { echo -e "${YELLOW}!${NC} $*"; } usage() { echo "Usage: $0 [--catalog PATH] [--rekor-url URL]" echo "" echo "Verify a VEX proof bundle with DSSE signature and Rekor inclusion." echo "" echo "Options:" echo " --catalog PATH Path to justification catalog (default: docs/benchmarks/vex-justifications.catalog.json)" echo " --rekor-url URL Rekor URL (default: https://rekor.sigstore.dev)" echo " --offline Skip Rekor verification" echo " --help, -h Show this help" exit 1 } DSSE_FILE="" CATALOG="${REPO_ROOT}/docs/benchmarks/vex-justifications.catalog.json" REKOR_URL="https://rekor.sigstore.dev" OFFLINE=false while [[ $# -gt 0 ]]; do case $1 in --catalog) CATALOG="$2" shift 2 ;; --rekor-url) REKOR_URL="$2" shift 2 ;; --offline) OFFLINE=true shift ;; --help|-h) usage ;; *) if [[ -z "$DSSE_FILE" ]]; then DSSE_FILE="$1" else echo "Unknown option: $1" usage fi shift ;; esac done if [[ -z "$DSSE_FILE" ]]; then echo "Error: DSSE file required" usage fi if [[ ! -f "$DSSE_FILE" ]]; then echo "Error: DSSE file not found: $DSSE_FILE" exit 1 fi echo "Verifying: $DSSE_FILE" echo "" # Step 1: Validate JSON structure if ! python3 -c "import json; json.load(open('$DSSE_FILE'))" 2>/dev/null; then log_fail "Invalid JSON" exit 1 fi log_pass "Valid JSON structure" # Step 2: Check DSSE envelope structure PAYLOAD_TYPE=$(python3 -c "import json; print(json.load(open('$DSSE_FILE')).get('payloadType', ''))") if [[ -z "$PAYLOAD_TYPE" ]]; then log_fail "Missing payloadType" exit 1 fi log_pass "DSSE payloadType: $PAYLOAD_TYPE" # Step 3: Decode and validate payload PAYLOAD_B64=$(python3 -c "import json; print(json.load(open('$DSSE_FILE')).get('payload', ''))") if [[ -z "$PAYLOAD_B64" ]]; then log_fail "Missing payload" exit 1 fi # Decode payload PAYLOAD_JSON=$(echo "$PAYLOAD_B64" | base64 -d 2>/dev/null || echo "") if [[ -z "$PAYLOAD_JSON" ]]; then log_fail "Failed to decode payload" exit 1 fi log_pass "Payload decoded successfully" # Step 4: Validate OpenVEX structure (if applicable) if [[ "$PAYLOAD_TYPE" == *"openvex"* ]]; then STATEMENTS_COUNT=$(echo "$PAYLOAD_JSON" | python3 -c "import json,sys; d=json.load(sys.stdin); print(len(d.get('statements', [])))") if [[ "$STATEMENTS_COUNT" -eq 0 ]]; then log_warn "OpenVEX has no statements" else log_pass "OpenVEX contains $STATEMENTS_COUNT statement(s)" fi fi # Step 5: Check signature presence SIG_COUNT=$(python3 -c "import json; print(len(json.load(open('$DSSE_FILE')).get('signatures', [])))") if [[ "$SIG_COUNT" -eq 0 ]]; then log_fail "No signatures found" exit 1 fi log_pass "Found $SIG_COUNT signature(s)" # Step 6: Check for placeholder signatures SIG_VALUE=$(python3 -c "import json; sigs=json.load(open('$DSSE_FILE')).get('signatures', []); print(sigs[0].get('sig', '') if sigs else '')") if [[ "$SIG_VALUE" == "PLACEHOLDER"* ]]; then log_warn "Signature is a placeholder (not yet signed)" else log_pass "Signature present (verification requires public key)" fi # Step 7: Rekor verification (if online) if [[ "$OFFLINE" == false ]]; then # Check for rekor.txt in same directory DSSE_DIR=$(dirname "$DSSE_FILE") REKOR_FILE="${DSSE_DIR}/rekor.txt" if [[ -f "$REKOR_FILE" ]]; then LOG_INDEX=$(grep -E "^log_index:" "$REKOR_FILE" | cut -d: -f2 | tr -d ' ') if [[ "$LOG_INDEX" != "PENDING" && -n "$LOG_INDEX" ]]; then log_pass "Rekor log index: $LOG_INDEX" # Verify with Rekor API if command -v curl &>/dev/null; then REKOR_RESP=$(curl -s "${REKOR_URL}/api/v1/log/entries?logIndex=${LOG_INDEX}" 2>/dev/null || echo "") if [[ -n "$REKOR_RESP" && "$REKOR_RESP" != "null" ]]; then log_pass "Rekor inclusion verified" else log_warn "Could not verify Rekor inclusion (may be offline or index invalid)" fi else log_warn "curl not available for Rekor verification" fi else log_warn "Rekor entry pending submission" fi else log_warn "No rekor.txt found - Rekor verification skipped" fi else log_warn "Offline mode - Rekor verification skipped" fi # Step 8: Check justification catalog membership if [[ -f "$CATALOG" ]]; then # Extract justification from payload if present JUSTIFICATION=$(echo "$PAYLOAD_JSON" | python3 -c " import json, sys d = json.load(sys.stdin) stmts = d.get('statements', []) if stmts: print(stmts[0].get('justification', '')) " 2>/dev/null || echo "") if [[ -n "$JUSTIFICATION" ]]; then CATALOG_MATCH=$(python3 -c " import json catalog = json.load(open('$CATALOG')) entries = catalog if isinstance(catalog, list) else catalog.get('entries', []) ids = [e.get('id', '') for e in entries] print('yes' if '$JUSTIFICATION' in ids else 'no') " 2>/dev/null || echo "no") if [[ "$CATALOG_MATCH" == "yes" ]]; then log_pass "Justification '$JUSTIFICATION' found in catalog" else log_warn "Justification '$JUSTIFICATION' not in catalog" fi fi else log_warn "Justification catalog not found at $CATALOG" fi echo "" echo "Verification complete."