#!/usr/bin/env python3
"""Standalone verifier for a CAIN-42 signed release manifest. No CAIN imports.
Needs Python 3.8+ and `cryptography`.

    python3 verify_release_manifest.py RELEASE_MANIFEST.json          (or a URL, or - for stdin)
    python3 verify_release_manifest.py URL --pin <release public key b64>

Checks: body_sha256 recomputed from canonical JSON; Ed25519 signature by the
release key (pinned if given, and it must equal the key in the manifest); every
replica on every host runs the manifest's image ID; each host holds that image
ID; the software-measurement digests of the image and the commit match and the
measurement admits it is not hardware-rooted; an SBOM is present; the build
section states reproducibility honestly (a manifest claiming reproducible=true
without evidence fails).
Verdict: VERIFIED (with its stated limitations) or FAILED.
"""
from __future__ import annotations

import base64
import hashlib
import json
import sys
import urllib.request

from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey


def canon(o) -> bytes:
    return json.dumps(o, sort_keys=True, separators=(",", ":"), ensure_ascii=True).encode()


def load(src):
    if src == "-":
        return json.load(sys.stdin)
    if src.startswith(("http://", "https://")):
        with urllib.request.urlopen(src, timeout=30) as r:
            return json.loads(r.read())
    with open(src) as f:
        return json.load(f)


def verify(m, pin=None):
    checks = []

    def check(name, ok, detail=""):
        checks.append((name, bool(ok), detail))
    b = m["body"]
    check("body_sha256 recomputed", hashlib.sha256(canon(b)).hexdigest() == m["body_sha256"])
    key = m["release_public_key_b64"]
    if pin:
        check("release key equals the pinned key", key == pin)
    try:
        Ed25519PublicKey.from_public_bytes(base64.b64decode(key)).verify(base64.b64decode(m["signature_b64"]),
                                                                          m["body_sha256"].encode())
        sig = True
    except Exception:
        sig = False
    check("Ed25519 signature by the release key", sig)
    img = b["image_id"]
    for host, h in sorted(b["deployment"].items()):
        check(f"{host}: host holds the manifest image", h["image_id_on_host"] == img, h["image_id_on_host"][:19])
        for r in h["replicas"]:
            check(f"{host}: {r['container']} runs the manifest image", r["image_id"] == img)
    reps = sum(len(h["replicas"]) for h in b["deployment"].values())
    check("replicas covered", reps >= 4, f"{reps} replicas on {len(b['deployment'])} hosts")
    sm = b["software_measurement"]
    check("image files == commit files (software measurement)",
          sm["image_files_sha256"] == sm["commit_files_sha256"] and sm["verdict"] == "SOFTWARE_MEASURED_MATCH",
          sm["image_files_sha256"][:16])
    check("measurement admits it is not hardware-rooted", sm["hardware_rooted"] is False)
    check("measured commit is the manifest commit", sm["claimed_commit"].startswith(b["git_commit"][:7]))
    check("SBOM present", len(b["sbom"].get("packages", [])) > 0, f"{len(b['sbom'].get('packages', []))} packages")
    bd = b["build"]
    ev = bd.get("reproducibility_evidence") or {}
    builds = ev.get("independent_builds") or []
    if bd["reproducible"]:
        check("reproducible: >= 2 independent builds with the SAME image ID as the deployed image",
              len(builds) >= 2 and all(x.get("image_id") == img for x in builds), f"{len(builds)} builds")
        check("reproducible: base image pinned by digest", bd["base_image"].get("digest_pinned") is True and "@sha256:" in bd["base_image"]["reference"])
    else:
        check("reproducibility stated honestly", True, "reproducible=false")
    return checks


def main(argv):
    if len(argv) < 2:
        print(__doc__); return 2
    pin = argv[argv.index("--pin") + 1] if "--pin" in argv else None
    checks = verify(load(argv[1]), pin)
    for n, ok, d in checks:
        print(f"[{'PASS' if ok else 'FAIL'}] {n}" + (f"  ({d})" if d else ""))
    bad = [c for c in checks if not c[1]]
    print(f"\n{'VERIFIED' if not bad else 'FAILED'}: {len(checks) - len(bad)}/{len(checks)} checks"
          + ("  (limitations: see body.not_claimed)" if not bad else ""))
    return 0 if not bad else 1


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