#!/usr/bin/env python3 """Mint a mock IdP RS256 approval JWT for CASAN tests/dev. The token is bound to the same high-risk request that governance-check verifies: sub=, role=, action, actor, and input_sha256. """ import argparse import base64 import hashlib import json import os import subprocess import sys import tempfile import time def b64u(data: bytes) -> str: return base64.urlsafe_b64encode(data).decode().rstrip("=") def main() -> int: ap = argparse.ArgumentParser() ap.add_argument("--key", required=True) ap.add_argument("--sub", required=True) ap.add_argument("--role", required=True) ap.add_argument("--action", required=True) ap.add_argument("--actor", required=True) ap.add_argument("--input", required=True) ap.add_argument("--exp-offset", type=int, default=300) args = ap.parse_args() with open(args.input, "rb") as f: input_sha = hashlib.sha256(f.read()).hexdigest() now = int(time.time()) header = {"alg": "RS256", "typ": "JWT"} claims = { "iss": "casan-mock-idp", "sub": args.sub, "role": args.role, "action": args.action, "actor": args.actor, "input_sha256": input_sha, "iat": now, "exp": now + args.exp_offset, } signing_input = ".".join([ b64u(json.dumps(header, separators=(",", ":"), sort_keys=True).encode()), b64u(json.dumps(claims, separators=(",", ":"), sort_keys=True).encode()), ]) with tempfile.TemporaryDirectory() as td: msg = os.path.join(td, "msg.txt") sig = os.path.join(td, "sig.bin") open(msg, "wb").write(signing_input.encode()) rc = subprocess.run( ["openssl", "dgst", "-sha256", "-sign", args.key, "-out", sig, msg], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ).returncode if rc != 0: print("approval-jwt-mint: signing failed", file=sys.stderr) return 1 token = signing_input + "." + b64u(open(sig, "rb").read()) print(token) return 0 if __name__ == "__main__": raise SystemExit(main())