research/examples/revoke_three_rungs.py
Meanwhile Research c8a04e144b Initial public release: capability-licensing reference implementation + reproducible claim demos
Clean-room reference implementation (Ed25519 certificate chain, AES-256-GCM
envelope, three-rung revocation ladder, crypto-erasure) with runnable examples
and 36 tests that reproduce each demonstrated claim on stand-in payloads.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 16:31:58 +10:00

178 lines
6.2 KiB
Python

#!/usr/bin/env python3
"""Claim 3: the three-rung revocation ladder on an encrypted capability unit.
The payload is a STAND-IN blob, not a real model. The three rungs, weakest to
strongest:
(a) SOFT / advisory: a revocation-list entry; verifiers report revoked; the
ciphertext and its key still exist. Reversible.
(b) ACCESS-GATED: the authority withholds the wrapping key; decryption is
denied; recoverable if the authority re-releases.
(c) CRYPTO-ERASURE: the wrapping key is destroyed; decryption is
permanently impossible. Irreversible.
"""
import hashlib
import os
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
from capability_licensing import (
CertificateAuthority,
KeyAuthority,
KeyDestroyedError,
KeyState,
KeyWithheldError,
LicenceInvalidError,
RevocationList,
RUNG_CRYPTO_ERASURE,
verify_chain,
)
failures = []
def check(label: str, condition: bool, evidence: str) -> None:
marker = "ok " if condition else "FAIL"
print(f" [{marker}] {label}")
print(f" {evidence}")
if not condition:
failures.append(label)
def main() -> int:
print("=== Claim 3: three-rung revocation on an encrypted capability unit ===\n")
authority = CertificateAuthority()
root = authority.create_root("Reference Root Authority")
organisation = authority.issue_organisation(root.serial, "Reference Research Organisation")
licence, _licence_key = authority.issue_capability(
organisation.serial,
"demonstration-capability",
claims={"grant": "research demonstration only"},
)
revocations = RevocationList()
keys = KeyAuthority(
trusted_roots=[root],
intermediates=[organisation],
revocation_list=revocations,
)
payload = b"STAND-IN CAPABILITY UNIT (not a real model)\n" + os.urandom(64 * 1024)
payload_digest = hashlib.sha256(payload).hexdigest()
sealed = keys.seal_unit("unit-demo-001", payload, licence.serial)
print(f" stand-in payload: {len(payload):,} bytes, sha256 {payload_digest[:16]}...")
print(f" sealed unit: {sealed.ciphertext_size():,} bytes AES-256-GCM ciphertext, "
f"licensed to {licence.serial}\n")
# Baseline: a valid licence decrypts.
plaintext = keys.request_decrypt(sealed, licence)
check(
"baseline: a valid licence decrypts the unit",
hashlib.sha256(plaintext).hexdigest() == payload_digest,
"decrypted plaintext digest matches the original payload",
)
# ---- Rung (a): SOFT / advisory -------------------------------------
print("\n --- rung (a): SOFT (advisory revocation-list entry) ---")
revocations.revoke(licence.serial, "licence terms breached")
chain_check = verify_chain(
licence, [organisation], [root], revocation_list=revocations
)
check(
"verification reports the certificate revoked",
not chain_check.valid and "revoked" in chain_check.reason,
f"verify_chain: INVALID - {chain_check.reason}",
)
denied_soft = None
try:
keys.request_decrypt(sealed, licence)
except LicenceInvalidError as exc:
denied_soft = exc
check(
"a compliant key authority honours the list and refuses decryption",
denied_soft is not None,
f"refused with LicenceInvalidError: {denied_soft}",
)
check(
"advisory honesty: the ciphertext and wrapping key still exist",
keys.key_state(sealed.unit_id) is KeyState.RELEASED,
"key state is still 'released'; enforcement rests on verifiers honouring "
"the list, not on the cryptography",
)
revocations.reinstate(licence.serial)
recovered_soft = keys.request_decrypt(sealed, licence)
check(
"soft revocation is reversible: reinstating restores decryption",
hashlib.sha256(recovered_soft).hexdigest() == payload_digest,
"after reinstatement the same licence decrypts again",
)
# ---- Rung (b): ACCESS-GATED ----------------------------------------
print("\n --- rung (b): ACCESS-GATED (wrapping key withheld) ---")
keys.withhold(sealed.unit_id)
denied_gated = None
try:
keys.request_decrypt(sealed, licence)
except KeyWithheldError as exc:
denied_gated = exc
check(
"with the key withheld, decryption is denied even for a valid licence",
denied_gated is not None,
f"refused with KeyWithheldError: {denied_gated}",
)
keys.re_release(sealed.unit_id)
recovered_gated = keys.request_decrypt(sealed, licence)
check(
"access-gating is recoverable: re-release restores decryption",
hashlib.sha256(recovered_gated).hexdigest() == payload_digest,
"after re-release the same licence decrypts again",
)
# ---- Rung (c): CRYPTO-ERASURE --------------------------------------
print("\n --- rung (c): CRYPTO-ERASURE (wrapping key destroyed) ---")
keys.destroy(sealed.unit_id)
revocations.revoke(
licence.serial, "capability unit crypto-erased", rung=RUNG_CRYPTO_ERASURE
)
denied_erased = None
try:
keys.request_decrypt(sealed, licence)
except KeyDestroyedError as exc:
denied_erased = exc
check(
"after erasure, every decrypt request fails permanently",
denied_erased is not None,
f"refused with KeyDestroyedError: {denied_erased}",
)
unrecoverable = None
try:
keys.re_release(sealed.unit_id)
except KeyDestroyedError as exc:
unrecoverable = exc
check(
"erasure is irreversible: the authority cannot re-release a destroyed key",
unrecoverable is not None,
f"re-release refused with KeyDestroyedError: {unrecoverable}",
)
print()
print(" ladder asymmetry, stated honestly: rungs (a) and (b) are reversible")
print(" administrative states; only rung (c) is final at the level of the")
print(" cryptography itself.")
print()
if failures:
print(f"RESULT: FAIL - {len(failures)} check(s) did not behave as claimed")
return 1
print(
"RESULT: PASS - soft flags and is honoured, access-gating denies and "
"recovers, crypto-erasure denies permanently"
)
return 0
if __name__ == "__main__":
sys.exit(main())