#!/usr/bin/env bash set -uo pipefail # CASAN H5 — Reviewer-side approval signer (Approval-identity MVP · C4 / V20). # # A reviewer runs this to APPROVE a specific high-risk request by signing the # canonical assertion with THEIR OWN private key. The resulting signature is # handed to governance-check.sh via CASAN_APPROVAL_SIG (with CASAN_APPROVER= # and CASAN_APPROVAL_STRICT=1). The private key stays with the reviewer / in an # IdP-issued credential — never in the pipeline env. # # Assertion (must match approval-verify.sh): # casan-approval|v1|||| # # Usage: # approval-sign.sh # Exit: 0 signed, 64 usage, 1 sign error. ACTION="${1:-}"; ACTOR="${2:-}"; INPUT_FILE="${3:-}"; APPROVER="${4:-}"; PRIV="${5:-}"; OUT="${6:-}" if [[ -z "$ACTION" || -z "$ACTOR" || -z "$INPUT_FILE" || -z "$APPROVER" || -z "$PRIV" || -z "$OUT" ]]; then echo "Usage: approval-sign.sh " >&2 exit 64 fi [[ -f "$INPUT_FILE" ]] || { echo "approval-sign: input file not found: $INPUT_FILE" >&2; exit 1; } [[ -f "$PRIV" ]] || { echo "approval-sign: private key not found: $PRIV" >&2; exit 1; } command -v openssl >/dev/null 2>&1 || { echo "approval-sign: openssl required" >&2; exit 1; } hash_file() { if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | awk '{print $1}' else shasum -a 256 "$1" | awk '{print $1}'; fi } INPUT_SHA="$(hash_file "$INPUT_FILE")" MSG="casan-approval|v1|$ACTION|$ACTOR|$INPUT_SHA|$APPROVER" TMP="$(mktemp)"; trap 'rm -f "$TMP"' EXIT printf '%s' "$MSG" > "$TMP" openssl dgst -sha256 -sign "$PRIV" -out "$OUT" "$TMP" \ || { echo "approval-sign: signing failed" >&2; exit 1; } echo "APPROVAL_SIGNED approver=$APPROVER action=$ACTION sig=$OUT"