Spaces:
Running
Running
DEV2: add szl_intoto.py + szl_intoto_routes.py + verify-intoto-receipt.py + patched serve.py (in-toto Statement v1 + Merkle log + verify guide endpoint). Signed-off-by: Stephen Lutar <stephenlutar2@gmail.com>
f2c745e verified | #!/usr/bin/env python3 | |
| # SPDX-License-Identifier: Apache-2.0 | |
| # © 2026 Lutar, Stephen P. — SZL Holdings · ORCID 0009-0001-0110-4173 | |
| # Signed-off-by: Stephen Lutar <stephenlutar2@gmail.com> | |
| """ | |
| szl-cookbook: verify-intoto-receipt.py | |
| ======================================= | |
| OFFLINE verifier for SZL a11oy Khipu receipts in in-toto Statement v1 format. | |
| WHAT THIS PROVES (without trusting SZL at runtime): | |
| 1. DSSE signature — receipt was signed by the SZL ECDSA P-256 keypair. | |
| Verified against the published cosign.pub (embedded below; also at | |
| https://github.com/szl-holdings/.github/blob/main/cosign.pub). | |
| 2. in-toto Statement v1 structure — receipt payload is a valid Statement v1 | |
| (_type, subject, predicateType, predicate all present and typed correctly). | |
| 3. Hard binding — subject.digest["sha3-256"] matches SHA3-256(answer_text) | |
| (C2PA pattern: receipt cannot be recycled for a different model output). | |
| 4. Merkle inclusion proof — leaf_hash(statement) walks the audit_path to the | |
| root_hash. OFFLINE math: no trust in SZL required for the proof check. | |
| USAGE: | |
| # Fetch a receipt bundle from the live API | |
| curl -s https://szlholdings-a11oy.hf.space/khipu/intoto/<receipt_id> > receipt.json | |
| # Run the verifier (stdlib only; no pip installs required except `cryptography`) | |
| python3 verify-intoto-receipt.py receipt.json | |
| # Or pipe directly | |
| curl -s https://szlholdings-a11oy.hf.space/khipu/intoto/<receipt_id> | python3 verify-intoto-receipt.py - | |
| # Verbose output | |
| python3 verify-intoto-receipt.py receipt.json --verbose | |
| DEPENDENCIES: | |
| stdlib only for chain verification. | |
| `cryptography` (pip install cryptography) for DSSE signature verification. | |
| NO in-toto library, NO sigstore library, NO network call required once you | |
| have the receipt JSON. | |
| EXIT CODES: | |
| 0 — all checks PASSED | |
| 1 — one or more checks FAILED (details printed to stdout) | |
| 2 — usage error or unreadable input | |
| WHAT IS NOT VERIFIED BY THIS SCRIPT (honest limits): | |
| - Whether the model output is correct or the governance decision was right. | |
| - Whether the SZL keypair itself is trustworthy (trust anchor is cosign.pub). | |
| - Public Rekor inclusion (per-receipt Rekor submission is ROADMAP). | |
| The self-hosted Merkle log inclusion proof IS verified here. | |
| - TEE attestation (AWS Nitro PCR) — not yet implemented (Phase II roadmap). | |
| Apache-2.0 — SZL Holdings 2026. Reimplement, modify, redistribute freely. | |
| """ | |
| from __future__ import annotations | |
| import base64 | |
| import hashlib | |
| import json | |
| import sys | |
| from typing import Any | |
| # --------------------------------------------------------------------------- | |
| # EMBEDDED PUBLIC KEY (szl-holdings/.github/cosign.pub — public, not a secret) | |
| # Also fetchable at https://a-11-oy.com/cosign.pub | |
| # --------------------------------------------------------------------------- | |
| _COSIGN_PUBLIC_PEM = """ | |
| -----BEGIN PUBLIC KEY----- | |
| MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEyq9ALpZuegbE67GRpWp8FfGSX1IJ | |
| bt5gw4jQ3RuBuIYIZchnfn9XLZf5KKw+zRfq5EJ8S+5cqwai5Wz0FDSyyA== | |
| -----END PUBLIC KEY----- | |
| """.strip() | |
| _PUBLIC_KEY_URL = "https://github.com/szl-holdings/.github/blob/main/cosign.pub" | |
| # --------------------------------------------------------------------------- | |
| # CONSTANTS | |
| # --------------------------------------------------------------------------- | |
| STATEMENT_TYPE = "https://in-toto.io/Statement/v1" | |
| GOVERNED_INFERENCE_PREDICATE = "https://szl.holdings/khipu-governed-inference/v1" | |
| INTOTO_PAYLOAD_TYPE = "application/vnd.in-toto+json" | |
| # --------------------------------------------------------------------------- | |
| # HELPER: DSSE PAE | |
| # --------------------------------------------------------------------------- | |
| def _pae(payload_type: str, body: bytes) -> bytes: | |
| """DSSE Pre-Authentication Encoding (DSSEv1).""" | |
| t = payload_type.encode("utf-8") | |
| return (b"DSSEv1 " + str(len(t)).encode() + b" " + t | |
| + b" " + str(len(body)).encode() + b" " + body) | |
| def _canonical_json(obj: Any) -> bytes: | |
| return json.dumps(obj, sort_keys=True, separators=(",", ":"), | |
| ensure_ascii=False).encode("utf-8") | |
| def _sha3_256(b: bytes) -> str: | |
| return hashlib.sha3_256(b).hexdigest() | |
| # --------------------------------------------------------------------------- | |
| # CHECK 1: DSSE SIGNATURE | |
| # --------------------------------------------------------------------------- | |
| def check_dsse_signature(envelope: dict, verbose: bool = False) -> dict: | |
| """ | |
| Verify the DSSE envelope signature against the embedded SZL cosign public key. | |
| The DSSE protocol: | |
| PAE = DSSEv1 SP LEN(payloadType) SP payloadType SP LEN(payload_bytes) SP payload_bytes | |
| signature = ECDSA-P256-SHA256(PAE) | |
| payload_bytes = base64.decode(envelope["payload"]) | |
| Returns {passed, reason, ...} | |
| """ | |
| result = {"check": "dsse_signature", "passed": False, "reason": ""} | |
| payload_type = envelope.get("payloadType", "") | |
| payload_b64 = envelope.get("payload", "") | |
| signatures = envelope.get("signatures", []) | |
| if not payload_b64: | |
| result["reason"] = "envelope missing 'payload' field" | |
| return result | |
| if not signatures: | |
| # Check for honest unsigned label | |
| if not envelope.get("signed", True): | |
| result["passed"] = False | |
| result["reason"] = f"receipt is UNSIGNED (honesty: {envelope.get('honesty', 'no signing key')})" | |
| result["warning"] = "Receipt was emitted without signing key present — not a forgery, but unverified." | |
| return result | |
| result["reason"] = "no signatures in envelope" | |
| return result | |
| try: | |
| payload_bytes = base64.b64decode(payload_b64 + "==") | |
| except Exception as exc: | |
| result["reason"] = f"base64 decode failed: {exc}" | |
| return result | |
| try: | |
| from cryptography.hazmat.primitives.serialization import load_pem_public_key | |
| from cryptography.hazmat.primitives.asymmetric import ec | |
| from cryptography.hazmat.primitives import hashes | |
| from cryptography.exceptions import InvalidSignature | |
| except ImportError: | |
| result["passed"] = None | |
| result["reason"] = ( | |
| "cryptography library not installed — DSSE sig check skipped. " | |
| "Install with: pip install cryptography" | |
| ) | |
| result["skipped"] = True | |
| return result | |
| pub_key = load_pem_public_key(_COSIGN_PUBLIC_PEM.encode("utf-8")) | |
| pae_bytes = _pae(payload_type, payload_bytes) | |
| verified = False | |
| last_exc = None | |
| for sig_entry in signatures: | |
| sig_b64 = sig_entry.get("sig", "") | |
| if not sig_b64: | |
| continue | |
| try: | |
| sig_bytes = base64.b64decode(sig_b64 + "==") | |
| pub_key.verify(sig_bytes, pae_bytes, ec.ECDSA(hashes.SHA256())) | |
| verified = True | |
| if verbose: | |
| print(f" [sig] keyid={sig_entry.get('keyid')} → VALID (ECDSA-P256-SHA256)") | |
| break | |
| except InvalidSignature: | |
| last_exc = "InvalidSignature" | |
| except Exception as exc: | |
| last_exc = str(exc) | |
| if verified: | |
| result["passed"] = True | |
| result["reason"] = f"ECDSA-P256-SHA256 signature verified against {_PUBLIC_KEY_URL}" | |
| result["payload_type"] = payload_type | |
| result["payload_sha3_256"] = _sha3_256(payload_bytes) | |
| else: | |
| result["passed"] = False | |
| result["reason"] = f"signature verification failed: {last_exc}" | |
| return result | |
| # --------------------------------------------------------------------------- | |
| # CHECK 2: in-toto Statement v1 STRUCTURE | |
| # --------------------------------------------------------------------------- | |
| def check_intoto_statement(statement: dict, verbose: bool = False) -> dict: | |
| """ | |
| Verify the in-toto Statement v1 structure. | |
| Required fields per spec (https://github.com/in-toto/attestation/blob/main/spec/v1/statement.md): | |
| _type: "https://in-toto.io/Statement/v1" | |
| subject: [{name: str, digest: {alg: hex}}] | |
| predicateType: URI string | |
| predicate: object | |
| """ | |
| result = {"check": "intoto_statement_v1", "passed": False, "reason": ""} | |
| errors = [] | |
| _type = statement.get("_type") | |
| if _type != STATEMENT_TYPE: | |
| errors.append(f"_type is {_type!r}, expected {STATEMENT_TYPE!r}") | |
| subject = statement.get("subject") | |
| if not isinstance(subject, list) or len(subject) == 0: | |
| errors.append("subject must be a non-empty list") | |
| else: | |
| for i, s in enumerate(subject): | |
| if not isinstance(s.get("name"), str): | |
| errors.append(f"subject[{i}].name missing or not a string") | |
| digest = s.get("digest", {}) | |
| if not isinstance(digest, dict) or not digest: | |
| errors.append(f"subject[{i}].digest missing or empty") | |
| predicate_type = statement.get("predicateType") | |
| if not isinstance(predicate_type, str) or not predicate_type.startswith("http"): | |
| errors.append(f"predicateType is {predicate_type!r}, expected a URI") | |
| predicate = statement.get("predicate") | |
| if not isinstance(predicate, dict): | |
| errors.append("predicate must be an object") | |
| if errors: | |
| result["passed"] = False | |
| result["reason"] = "; ".join(errors) | |
| result["errors"] = errors | |
| else: | |
| result["passed"] = True | |
| result["reason"] = "valid in-toto Statement v1" | |
| result["_type"] = _type | |
| result["predicate_type"] = predicate_type | |
| result["subject_count"] = len(subject) | |
| result["subject_names"] = [s.get("name") for s in subject] | |
| if verbose: | |
| print(f" [stmt] predicateType={predicate_type}") | |
| for s in subject: | |
| print(f" [stmt] subject: {s.get('name')} → digest={s.get('digest')}") | |
| return result | |
| # --------------------------------------------------------------------------- | |
| # CHECK 3: HARD BINDING (C2PA pattern) | |
| # --------------------------------------------------------------------------- | |
| def check_hard_binding(statement: dict, answer: str | None = None, | |
| verbose: bool = False) -> dict: | |
| """ | |
| Verify that subject.digest["sha3-256"] matches SHA3-256(answer_text). | |
| C2PA hard-binding pattern: the receipt binds to the EXACT model output. | |
| If the answer was replaced after signing, the digest will not match. | |
| If answer is None (e.g. denied turn), we verify the digest is a valid hex | |
| string but cannot check the content binding (labeled PARTIAL). | |
| """ | |
| result = {"check": "hard_binding", "passed": False, "reason": ""} | |
| subject = statement.get("subject", [{}]) | |
| if not subject: | |
| result["reason"] = "no subject in statement" | |
| return result | |
| digest = subject[0].get("digest", {}) | |
| stored_hash = digest.get("sha3-256", "") | |
| if not stored_hash or len(stored_hash) != 64: | |
| result["passed"] = False | |
| result["reason"] = f"subject.digest[sha3-256] missing or invalid: {stored_hash!r}" | |
| return result | |
| if answer is None: | |
| # Try to extract from predicate | |
| predicate = statement.get("predicate", {}) | |
| answer = predicate.get("answer") or predicate.get("output") or None | |
| if answer is None: | |
| result["passed"] = True | |
| result["reason"] = ( | |
| "PARTIAL: no answer text available to verify content binding. " | |
| "Digest is present and well-formed. " | |
| "To fully verify: supply the model output text." | |
| ) | |
| result["stored_digest"] = stored_hash | |
| result["partial"] = True | |
| return result | |
| computed_hash = _sha3_256(answer.encode("utf-8")) | |
| # Also check alternative binding methods documented in szl_intoto.py | |
| if stored_hash == computed_hash: | |
| result["passed"] = True | |
| result["reason"] = "output hash verified: SHA3-256(answer) matches subject.digest" | |
| result["computed_hash"] = computed_hash | |
| result["stored_hash"] = stored_hash | |
| if verbose: | |
| print(f" [bind] SHA3-256(answer)={computed_hash} ✓") | |
| else: | |
| # Check fallback binding (receipt_id + canonical_json of predicate) | |
| predicate = statement.get("predicate", {}) | |
| receipt_id = (predicate.get("receipt_id") or predicate.get("id") | |
| or predicate.get("hash") or "") | |
| _STRIP_KEYS = frozenset({"dsse", "signatures", "_dsse", "_pae_sha256", | |
| "honesty", "verify_key_url"}) | |
| stripped_predicate = {k: v for k, v in predicate.items() | |
| if k not in _STRIP_KEYS} | |
| fallback_input = (receipt_id + ":").encode("utf-8") + _canonical_json(stripped_predicate) | |
| fallback_hash = _sha3_256(fallback_input) | |
| if stored_hash == fallback_hash: | |
| result["passed"] = True | |
| result["reason"] = "output hash verified via fallback binding (receipt_id + predicate)" | |
| result["computed_hash"] = fallback_hash | |
| result["binding_method"] = "fallback" | |
| if verbose: | |
| print(f" [bind] fallback binding={fallback_hash} ✓") | |
| else: | |
| result["passed"] = False | |
| result["reason"] = ( | |
| f"output hash MISMATCH: stored={stored_hash!r}, " | |
| f"computed_direct={computed_hash!r}. " | |
| "Receipt may have been signed for a different output (tampering)." | |
| ) | |
| result["stored_hash"] = stored_hash | |
| result["computed_hash"] = computed_hash | |
| return result | |
| # --------------------------------------------------------------------------- | |
| # CHECK 4: MERKLE INCLUSION PROOF | |
| # --------------------------------------------------------------------------- | |
| def _leaf_hash_sha3(data: bytes) -> bytes: | |
| """RFC 6962 leaf hash: SHA3-256(0x00 || data).""" | |
| return hashlib.sha3_256(b"\x00" + data).digest() | |
| def _node_hash_sha3(left: bytes, right: bytes) -> bytes: | |
| """RFC 6962 interior node: SHA3-256(0x01 || left || right).""" | |
| return hashlib.sha3_256(b"\x01" + left + right).digest() | |
| def check_merkle_inclusion(statement: dict, transparency: dict, | |
| verbose: bool = False) -> dict: | |
| """ | |
| Verify the Merkle inclusion proof for the self-hosted SZL transparency log. | |
| Algorithm (RFC 6962): | |
| 1. Compute leaf_hash = SHA3-256(0x00 || canonical_json(statement)) | |
| 2. Walk audit_path: for each sibling, compute parent = node_hash(node, sibling) | |
| (left/right determined by leaf_index parity at each level) | |
| 3. The final computed node should equal root_hash | |
| This is pure math — no trust in SZL required for verification. | |
| """ | |
| result = {"check": "merkle_inclusion", "passed": False, "reason": ""} | |
| log_type = transparency.get("transparency_log", "") | |
| if log_type == "rekor-public": | |
| result["passed"] = True | |
| result["reason"] = ( | |
| f"Rekor public log inclusion: logIndex={transparency.get('log_index')}. " | |
| f"Verify at {transparency.get('log_url', 'rekor.sigstore.dev')}. " | |
| "Note: per-receipt Rekor submission may not be available in all deployments." | |
| ) | |
| result["transparency_log"] = "rekor-public" | |
| return result | |
| if "error" in transparency: | |
| result["passed"] = False | |
| result["reason"] = f"No inclusion proof: {transparency['error']}" | |
| return result | |
| hashes_hex = transparency.get("hashes", []) | |
| root_hash_hex = transparency.get("root_hash", "") | |
| leaf_index = transparency.get("leaf_index") | |
| leaf_hash_stored_hex = transparency.get("leaf_hash", "") | |
| if not root_hash_hex: | |
| result["reason"] = "root_hash missing from proof" | |
| return result | |
| if leaf_index is None: | |
| result["reason"] = "leaf_index missing from proof" | |
| return result | |
| # Step 1: compute leaf hash from the statement | |
| leaf_data = _canonical_json(statement) | |
| computed_leaf = _leaf_hash_sha3(leaf_data) | |
| computed_leaf_hex = computed_leaf.hex() | |
| if verbose: | |
| print(f" [merkle] computed leaf_hash={computed_leaf_hex}") | |
| if leaf_hash_stored_hex: | |
| print(f" [merkle] stored leaf_hash ={leaf_hash_stored_hex}") | |
| if leaf_hash_stored_hex and computed_leaf_hex != leaf_hash_stored_hex: | |
| result["passed"] = False | |
| result["reason"] = ( | |
| f"Leaf hash mismatch: computed={computed_leaf_hex}, " | |
| f"stored={leaf_hash_stored_hex}. " | |
| "Statement was modified after log append." | |
| ) | |
| return result | |
| # Step 2: walk audit path | |
| audit_hashes = [bytes.fromhex(h) for h in hashes_hex] | |
| node = computed_leaf | |
| i = leaf_index | |
| for level_idx, sibling in enumerate(audit_hashes): | |
| if i % 2 == 0: | |
| # current node is left child | |
| node = _node_hash_sha3(node, sibling) | |
| if verbose: | |
| print(f" [merkle] level {level_idx}: node(L,sibling) → {node.hex()[:16]}…") | |
| else: | |
| # current node is right child | |
| node = _node_hash_sha3(sibling, node) | |
| if verbose: | |
| print(f" [merkle] level {level_idx}: node(sibling,R) → {node.hex()[:16]}…") | |
| i //= 2 | |
| computed_root = node.hex() | |
| if verbose: | |
| print(f" [merkle] computed root={computed_root}") | |
| print(f" [merkle] stored root ={root_hash_hex}") | |
| if computed_root == root_hash_hex: | |
| result["passed"] = True | |
| result["reason"] = ( | |
| f"Merkle inclusion proof verified: " | |
| f"leaf_index={leaf_index}, tree_size={transparency.get('tree_size')}, " | |
| f"root_hash={root_hash_hex}. " | |
| f"transparency_log={log_type}" | |
| ) | |
| result["transparency_log"] = log_type | |
| result["root_hash"] = root_hash_hex | |
| result["leaf_index"] = leaf_index | |
| else: | |
| result["passed"] = False | |
| result["reason"] = ( | |
| f"Merkle root mismatch: computed={computed_root}, " | |
| f"stored={root_hash_hex}. " | |
| "Proof is invalid or the log was mutated." | |
| ) | |
| return result | |
| # --------------------------------------------------------------------------- | |
| # MAIN VERIFIER | |
| # --------------------------------------------------------------------------- | |
| def verify(receipt_bundle: dict, verbose: bool = False) -> dict: | |
| """ | |
| Run all verification checks on a receipt bundle from /khipu/intoto/<id>. | |
| Returns: | |
| { | |
| "overall": "PASS" | "FAIL" | "PARTIAL", | |
| "checks": [...], | |
| "receipt_id": "<id>", | |
| "honest_limits": ["..."] | |
| } | |
| """ | |
| checks = [] | |
| receipt_id = receipt_bundle.get("receipt_id", "unknown") | |
| intoto_envelope = receipt_bundle.get("intoto_envelope", {}) | |
| intoto_statement = receipt_bundle.get("intoto_statement", {}) | |
| transparency = receipt_bundle.get("transparency", {}) | |
| if not intoto_envelope and not intoto_statement: | |
| return { | |
| "overall": "FAIL", | |
| "receipt_id": receipt_id, | |
| "checks": [], | |
| "error": ( | |
| "Input does not contain 'intoto_envelope' or 'intoto_statement'. " | |
| "Fetch from /khipu/intoto/<receipt_id> to get the in-toto format." | |
| ), | |
| } | |
| # If statement is embedded in the envelope payload, decode it | |
| if not intoto_statement and intoto_envelope.get("payload"): | |
| try: | |
| payload_bytes = base64.b64decode(intoto_envelope["payload"] + "==") | |
| intoto_statement = json.loads(payload_bytes) | |
| except Exception: | |
| pass | |
| if verbose: | |
| print(f"\n[verify] receipt_id: {receipt_id}") | |
| print(f"[verify] transparency_log: {transparency.get('transparency_log', 'none')}") | |
| # CHECK 1: DSSE signature | |
| c1 = check_dsse_signature(intoto_envelope, verbose=verbose) | |
| checks.append(c1) | |
| if verbose: | |
| status = "PASS" if c1["passed"] else ("SKIP" if c1.get("skipped") else "FAIL") | |
| print(f"[1] DSSE Signature: {status} — {c1['reason']}") | |
| # CHECK 2: in-toto Statement v1 structure | |
| c2 = check_intoto_statement(intoto_statement, verbose=verbose) | |
| checks.append(c2) | |
| if verbose: | |
| print(f"[2] Statement v1: {'PASS' if c2['passed'] else 'FAIL'} — {c2['reason']}") | |
| # CHECK 3: Hard binding | |
| c3 = check_hard_binding(intoto_statement, verbose=verbose) | |
| checks.append(c3) | |
| if verbose: | |
| partial = " (PARTIAL)" if c3.get("partial") else "" | |
| print(f"[3] Hard Binding: {'PASS' if c3['passed'] else 'FAIL'}{partial} — {c3['reason']}") | |
| # CHECK 4: Merkle inclusion proof | |
| if transparency: | |
| c4 = check_merkle_inclusion(intoto_statement, transparency, verbose=verbose) | |
| checks.append(c4) | |
| if verbose: | |
| print(f"[4] Merkle Proof: {'PASS' if c4['passed'] else 'FAIL'} — {c4['reason']}") | |
| # Determine overall result | |
| failed = [c for c in checks if c["passed"] is False] | |
| skipped = [c for c in checks if c.get("skipped")] | |
| partial = [c for c in checks if c.get("partial")] | |
| if failed: | |
| overall = "FAIL" | |
| elif skipped or partial: | |
| overall = "PARTIAL" | |
| else: | |
| overall = "PASS" | |
| return { | |
| "overall": overall, | |
| "receipt_id": receipt_id, | |
| "checks": checks, | |
| "failed_checks": [c["check"] for c in failed], | |
| "honest_limits": [ | |
| "Per-receipt public Rekor inclusion is ROADMAP (self-hosted Merkle log is current).", | |
| "TEE attestation (AWS Nitro PCR) is Phase II roadmap.", | |
| "SZL keypair trust depends on cosign.pub authenticity — verify at " + _PUBLIC_KEY_URL, | |
| "governance correctness (Λ, gates) is NOT verified by this script.", | |
| ], | |
| } | |
| # --------------------------------------------------------------------------- | |
| # CLI | |
| # --------------------------------------------------------------------------- | |
| def main(): | |
| import argparse | |
| parser = argparse.ArgumentParser( | |
| description="Offline verifier for SZL a11oy in-toto Khipu receipts.", | |
| formatter_class=argparse.RawDescriptionHelpFormatter, | |
| epilog=__doc__, | |
| ) | |
| parser.add_argument( | |
| "receipt_file", | |
| nargs="?", | |
| default="-", | |
| help="Path to receipt JSON file (or '-' to read from stdin). " | |
| "Fetch from: curl -s https://szlholdings-a11oy.hf.space/khipu/intoto/<id>", | |
| ) | |
| parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output") | |
| parser.add_argument("--json", "-j", action="store_true", help="Output raw JSON result") | |
| args = parser.parse_args() | |
| # Read input | |
| try: | |
| if args.receipt_file == "-": | |
| raw = sys.stdin.read() | |
| else: | |
| with open(args.receipt_file, "r", encoding="utf-8") as fh: | |
| raw = fh.read() | |
| receipt_bundle = json.loads(raw) | |
| except FileNotFoundError: | |
| print(f"ERROR: file not found: {args.receipt_file}", file=sys.stderr) | |
| sys.exit(2) | |
| except json.JSONDecodeError as exc: | |
| print(f"ERROR: invalid JSON: {exc}", file=sys.stderr) | |
| sys.exit(2) | |
| # Run verification | |
| result = verify(receipt_bundle, verbose=args.verbose) | |
| if args.json: | |
| print(json.dumps(result, indent=2, default=str)) | |
| else: | |
| print(f"\n{'='*60}") | |
| print(f"SZL a11oy in-toto Receipt Verifier") | |
| print(f"{'='*60}") | |
| print(f"Receipt ID : {result['receipt_id']}") | |
| print(f"Overall : {result['overall']}") | |
| print() | |
| for check in result["checks"]: | |
| status = "PASS" if check["passed"] else ("SKIP" if check.get("skipped") else "FAIL") | |
| print(f" [{status:4s}] {check['check']}: {check['reason']}") | |
| if result.get("failed_checks"): | |
| print(f"\nFailed checks: {result['failed_checks']}") | |
| print(f"\nHonest limits:") | |
| for limit in result["honest_limits"]: | |
| print(f" - {limit}") | |
| print(f"{'='*60}\n") | |
| sys.exit(0 if result["overall"] == "PASS" else (0 if result["overall"] == "PARTIAL" else 1)) | |
| if __name__ == "__main__": | |
| main() | |