#!/usr/bin/env python3 """Independent checker for the CAIN-42 hardening bundle. Imports NOTHING from CAIN; needs Python 3 and (for the signature check) the `cryptography` package. python3 verify_bundle.py [DIR_OR_BASE_URL] (published as verify_bundle.py.txt: save without the .txt) Checks: (1) every file's SHA-256 and size match manifest.json; (2) the Merkle-style root matches; (3) the Ed25519 signature over the canonical manifest verifies against the embedded public key; (4) every number in manifest["claims"] is RECOMPUTED from the bundle's own files and must match. It does NOT re-run the tests: the commands to do that are in REPRODUCE.txt. Exit code 0 only if every check passes. """ import base64, hashlib, json, re, sys, urllib.request from pathlib import Path src = sys.argv[1] if len(sys.argv) > 1 else "." def read(name): if src.startswith("http"): return urllib.request.urlopen(src.rstrip("/") + "/" + name, timeout=30).read() return (Path(src) / name).read_bytes() problems = [] m = json.loads(read("manifest.json")) sha = lambda b: hashlib.sha256(b).hexdigest() leaves = [] for f in m["files"]: try: b = read(f["path"]) except Exception as e: problems.append(f"cannot read {f['path']}: {e}"); continue if sha(b) != f["sha256"] or len(b) != f["bytes"]: problems.append(f"HASH/SIZE MISMATCH {f['path']}") leaves.append(f["sha256"]) root = sha("\n".join(sorted(leaves)).encode()) if root != m["bundle_root"]: problems.append("bundle_root mismatch") def canon(o): return json.dumps(o, sort_keys=True, separators=(",", ":")).encode() body = {k: v for k, v in m.items() if k not in ("signature",)} try: from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey Ed25519PublicKey.from_public_bytes(base64.b64decode(m["signer_public_key"])).verify(base64.b64decode(m["signature"]), canon(body)) sig = "valid" except ImportError: sig = "SKIPPED (cryptography not installed)" except Exception: sig = "INVALID"; problems.append("signature invalid") adv = json.loads(read("adversarial-suite-run.json"))["attacks"] c = {} for a in adv: c[a["result"]] = c.get(a["result"], 0) + 1 fi = json.loads(read("formal-invariants-run.json")) bm = json.loads(read("benchmark.json")) tr = read("test-run.txt").decode() passed = sum(int(x) for x in re.findall(r"(\d+) passed", tr)); failed = sum(int(x) for x in re.findall(r"(\d+) failed", tr)) derived = { "adversarial_total": len(adv), "adversarial_blocked": c.get("ATTACK_BLOCKED", 0), "adversarial_inconclusive": c.get("INCONCLUSIVE", 0), "adversarial_succeeded": c.get("ATTACK_SUCCEEDED", 0), "formal_passed": fi["passed"], "formal_total": fi["total"], "benchmark_sustained_iterations": bm["duration_tests"]["1_minute_sustained_load"]["iterations"], "benchmark_sustained_elapsed_s": bm["duration_tests"]["1_minute_sustained_load"]["elapsed_s"], "test_run_passed": passed, "test_run_failed": failed, } for k, v in derived.items(): if m["claims"].get(k) != v: problems.append(f"claim {k}: manifest says {m['claims'].get(k)!r}, files give {v!r}") print(json.dumps({"bundle": m["bundle_id"], "files": len(m["files"]), "signature": sig, "derived": derived}, indent=1)) print("VALID" if not problems else "INVALID:\n " + "\n ".join(problems)) sys.exit(0 if not problems else 1)