#!/usr/bin/env python3
"""KEYSTONE CI offline verifier (stand-alone; imports no CAIN code). 2026-11-03.

Checks a KEYSTONE CI DSSE attestation against a key YOU pinned, and against the code YOU are about to merge:

  gh api repos/OWNER/REPO/pulls/N -H "Accept: application/vnd.github.diff" > pr.diff   # the exact bytes the Action sent
  python3 keystone-ci-verify.py attestation.json --pubkey b6hfA2rpAdV29FOdhNXGkk4t91mCV0AnfFYwq0HO0R8= \
      --diff pr.diff --head-sha "$(git rev-parse HEAD)" [--require PASS]

attestation.json may be the bare DSSE envelope or any JSON with an "attestation" or "dsse" field (a /check or /scan
reply, or a ledger record). Exit 0 = valid (and verdict allowed by --require), 1 = not valid, 2 = usage error.
Needs: Python 3.8+, `pip install cryptography`. The key above is the public KEYSTONE key published at
https://cainstudio.online/keystone-ci/public-key; pin it once, do not fetch it at verify time.
"""
import argparse
import base64
import hashlib
import json
import sys

PAYLOAD_TYPE = "application/vnd.in-toto+json"
STATEMENT = "https://in-toto.io/Statement/v1"
PREDICATE = "https://cainstudio.online/keystone-ci/predicate/v1"
VERDICTS = ("PASS", "REVIEW", "BLOCK")


def pae(payload_type, payload):
    t = payload_type.encode()
    return b"DSSEv1 %d %s %d %s" % (len(t), t, len(payload), payload)


def verify(env, pubkey_b64, diff_bytes=None, head_sha=None):
    from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
    problems = []
    if env.get("payloadType") != PAYLOAD_TYPE:
        return ["payloadType is not " + PAYLOAD_TYPE], None
    try:
        payload = base64.b64decode(env["payload"], validate=True)
    except Exception:
        return ["payload is not base64"], None
    pub = Ed25519PublicKey.from_public_bytes(base64.b64decode(pubkey_b64))
    ok = False
    for s in env.get("signatures") or []:
        try:
            pub.verify(base64.b64decode(s["sig"]), pae(env["payloadType"], payload))
            ok = True
            break
        except Exception:
            continue
    if not ok:
        return ["no signature verifies against the pinned key"], None
    st = json.loads(payload)
    if st.get("_type") != STATEMENT:
        problems.append("not an in-toto Statement v1")
    if st.get("predicateType") != PREDICATE:
        problems.append("not a KEYSTONE CI statement (predicateType %r)" % st.get("predicateType"))
    verdict = (st.get("predicate") or {}).get("verdict")
    if verdict not in VERDICTS:
        problems.append("no PASS/REVIEW/BLOCK verdict")
    digests = {}
    for s in st.get("subject") or []:
        digests.update(s.get("digest") or {})
    if diff_bytes is not None:
        got = hashlib.sha256(diff_bytes).hexdigest()
        if digests.get("sha256") != got:
            problems.append("issued for different code: attestation diff sha256 %s, your diff %s" % (digests.get("sha256"), got))
    if head_sha is not None and digests.get("gitCommit") != head_sha.lower():
        problems.append("names head commit %s, not %s" % (digests.get("gitCommit"), head_sha))
    return problems, st


def main():
    ap = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
    ap.add_argument("attestation")
    ap.add_argument("--pubkey", required=True, help="pinned Ed25519 public key, base64")
    ap.add_argument("--diff", help="the diff file the gate checked (binds the attestation to it)")
    ap.add_argument("--head-sha", help="the head commit you are about to merge")
    ap.add_argument("--require", nargs="*", choices=VERDICTS, help="verdicts you accept, e.g. --require PASS")
    a = ap.parse_args()
    try:
        doc = json.load(open(a.attestation))
    except Exception as e:
        print("cannot read %s: %s" % (a.attestation, e)); return 2
    env = doc.get("attestation") or doc.get("dsse") or doc
    diff = open(a.diff, "rb").read() if a.diff else None
    problems, st = verify(env, a.pubkey, diff, a.head_sha)
    verdict = ((st or {}).get("predicate") or {}).get("verdict")
    if not problems and a.require and verdict not in a.require:
        problems.append("verdict %s is not one of %s" % (verdict, "/".join(a.require)))
    bound = [x for x, v in (("diff", a.diff), ("head commit", a.head_sha)) if v]
    if problems:
        print("NOT VALID"); [print("  - " + p) for p in problems]; return 1
    print("VALID  verdict=%s  bound to: %s" % (verdict, ", ".join(bound) or "nothing (pass --diff and --head-sha)"))
    return 0


if __name__ == "__main__":
    sys.exit(main())
