76 lines
2.2 KiB
Python
76 lines
2.2 KiB
Python
#!/usr/bin/env python3
|
|
import importlib.util
|
|
import json
|
|
import re
|
|
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
def load_verifier():
|
|
path = ROOT / "public" / "verify_receipt.py"
|
|
spec = importlib.util.spec_from_file_location("scalaralias_verify", path)
|
|
module = importlib.util.module_from_spec(spec)
|
|
assert spec.loader is not None
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
verifier = load_verifier()
|
|
|
|
|
|
def malleate_receipt(receipt: dict) -> dict:
|
|
changed = json.loads(json.dumps(receipt))
|
|
changed["signature"]["s"] = str(int(changed["signature"]["s"]) + verifier.Q)
|
|
return changed
|
|
|
|
|
|
def binding_key(receipt: dict) -> bytes:
|
|
return verifier.hashlib.sha256(
|
|
b"KiteLedger sealed export binding\0" + verifier.canonical_json(receipt)
|
|
).digest()
|
|
|
|
|
|
def keystream(key: bytes, nonce: bytes, length: int) -> bytes:
|
|
out = b""
|
|
counter = 0
|
|
while len(out) < length:
|
|
out += verifier.hashlib.sha256(
|
|
key + b"scalaralias-stream" + nonce + counter.to_bytes(4, "big")
|
|
).digest()
|
|
counter += 1
|
|
return out[:length]
|
|
|
|
|
|
def xor_bytes(left: bytes, right: bytes) -> bytes:
|
|
return bytes(a ^ b for a, b in zip(left, right))
|
|
|
|
|
|
def open_export(receipt: dict, sealed: dict) -> str:
|
|
if not verifier.verify_receipt(receipt):
|
|
raise ValueError("receipt does not verify")
|
|
|
|
key = binding_key(receipt)
|
|
nonce = bytes.fromhex(sealed["nonce_hex"])
|
|
ciphertext = bytes.fromhex(sealed["ciphertext_hex"])
|
|
tag = bytes.fromhex(sealed["tag_hex"])
|
|
expected = verifier.hashlib.sha256(key + b"export-tag" + nonce + ciphertext).digest()[:16]
|
|
if expected != tag:
|
|
raise ValueError("sealed export tag mismatch")
|
|
plaintext = xor_bytes(ciphertext, keystream(key, nonce, len(ciphertext))).decode()
|
|
match = re.search(r"caplag\{[^}]+\}", plaintext)
|
|
if not match:
|
|
raise ValueError("flag not found")
|
|
return match.group(0)
|
|
|
|
|
|
def main() -> None:
|
|
receipt = json.loads((ROOT / "public" / "receipt.json").read_text())
|
|
sealed = json.loads((ROOT / "public" / "sealed_export.json").read_text())
|
|
print(open_export(malleate_receipt(receipt), sealed))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|