#!/usr/bin/env python3 """Verify a Wytness sealed Evidence Pack offline. Nothing is uploaded: the pack carries the signed bytes and the public keys that signed them. Exit code 0 when every check passes, 1 otherwise.""" EPILOG = """ setup: pip install cryptography the fingerprint: Get it from https://wytness.ai/trust. Without it the seal still proves the file is internally consistent, but NOT that Wytness issued it — anyone can sign their own document with their own key. That run reports INCOMPLETE and exits non-zero, so a passing exit code always means the issuer was checked. example: python verify-evidence-pack.py pack.json --fingerprint 0123456789abcdef """ import argparse import base64 import hashlib import json import sys from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives.serialization import load_pem_public_key OK, BAD, SKIP = " [ok] ", " [FAIL]", " [skip]" failures = 0 issuer_checked = False def check(passed: bool, message: str) -> bool: global failures if not passed: failures += 1 print(f"{OK if passed else BAD} {message}") return passed def platform_fingerprint(public_pem: bytes) -> str: """Last 16 hex of SHA-256 over the PEM — the form published on /trust.""" return hashlib.sha256(public_pem).hexdigest()[-16:] def sdk_fingerprint(public_pem: bytes) -> str: """The SDK's key id: base64url SHA-256 of the raw key bytes, 16 chars.""" raw = load_pem_public_key(public_pem).public_bytes_raw() return base64.urlsafe_b64encode(hashlib.sha256(raw).digest()).rstrip(b"=").decode()[:16] def verify_seal(env: dict, expected_fingerprint: str | None) -> dict | None: """Check the pack is intact and was sealed by the key it names.""" print("\nPack seal") payload = base64.b64decode(env["payload_b64"]) public_pem = base64.b64decode(env["public_key_pem"]) check(hashlib.sha256(payload).hexdigest() == env["payload_sha256"], "contents match the digest recorded in the seal") try: load_pem_public_key(public_pem).verify( base64.b64decode(env["signature"]), hashlib.sha256(payload).digest(), ) check(True, "seal signature verifies") except InvalidSignature: check(False, "seal signature does NOT verify — the pack was altered") return None actual = platform_fingerprint(public_pem) check(actual == env.get("fingerprint"), f"the key in the file is the key the file names ({actual})") global issuer_checked print("\nIssuer") if not expected_fingerprint: print(f"{SKIP} NOT CHECKED — pass --fingerprint to prove Wytness issued this.") print(" Without it the checks above prove only that the file is") print(" self-consistent: anyone can seal a document with their own key.") else: issuer_checked = True check(actual == expected_fingerprint.strip().lower(), f"sealed by the expected Wytness key ({expected_fingerprint.strip().lower()})") return json.loads(payload) def verify_events(pack: dict) -> None: """Check each sampled event against the customer's own signing key.""" print("\nEvents signed by the customer's agents") keys = {k["key_fingerprint"]: k for k in pack.get("crypto", {}).get("signing_keys", [])} for k in keys.values(): pem = k["public_key"].encode() check(sdk_fingerprint(pem) == k["key_fingerprint"], f"published key {k['key_fingerprint']} matches its own fingerprint") checked = skipped = 0 for event in pack.get("sample_events", []): event_id = event.get("event_id", "?") if not event.get("offline_verifiable"): skipped += 1 print(f"{SKIP} {event_id} — {event.get('not_verifiable_detail') or event.get('not_verifiable_reason')}") continue key = keys.get(event.get("wytness_envelope_key_fingerprint")) if key is None: check(False, f"{event_id} — signed by a key the pack does not publish") continue try: load_pem_public_key(key["public_key"].encode()).verify( base64.b64decode(event["wytness_envelope_signature"]), event["wytness_envelope_signed_payload"].encode("utf-8"), ) checked += 1 check(True, f"{event_id} — signature verifies") except InvalidSignature: check(False, f"{event_id} — signature does NOT verify, this event was altered") print(f"\n{checked} verified, {skipped} not independently verifiable " f"(the pack states a reason for each).") def main() -> int: ap = argparse.ArgumentParser(description=__doc__, epilog=EPILOG, formatter_class=argparse.RawDescriptionHelpFormatter) ap.add_argument("pack", help="the sealed pack JSON file") ap.add_argument("--fingerprint", help="expected platform fingerprint, from /trust") args = ap.parse_args() with open(args.pack, "rb") as fh: document = json.load(fh) env = document.get("wytness_signed_pack") if not isinstance(env, dict): print("This file has no wytness_signed_pack envelope — is it the signed download?") return 1 print(f"Organisation: {env.get('organisation') or '(not stated)'}") print(f"Framework: {env.get('framework')}") print(f"Period: {(env.get('window') or {}).get('from')} to {(env.get('window') or {}).get('to')}") pack = verify_seal(env, args.fingerprint) if pack is not None: verify_events(pack) if failures: print(f"\nFAIL — {failures} check(s) failed.") elif not issuer_checked: # Fail closed: a zero exit must never mean "verified" when the one check # that proves authorship never ran. print("\nINCOMPLETE — the file is self-consistent, but nothing here shows") print("Wytness issued it. Re-run with --fingerprint from https://wytness.ai/trust.") else: print("\nPASS — every check succeeded.") return 0 if (failures == 0 and issuer_checked) else 1 if __name__ == "__main__": sys.exit(main())