#!/usr/bin/env python3 """ Sign mirror-thin-v1 artefacts using an Ed25519 key and emit DSSE + TUF signatures. Usage: python scripts/mirror/sign_thin_bundle.py \ --key out/mirror/thin/tuf/keys/mirror-ed25519-test-1.pem \ --manifest out/mirror/thin/mirror-thin-v1.manifest.json \ --tar out/mirror/thin/mirror-thin-v1.tar.gz \ --tuf-dir out/mirror/thin/tuf Writes: - mirror-thin-v1.manifest.dsse.json - updates signatures in root.json, targets.json, snapshot.json, timestamp.json """ import argparse, base64, json, pathlib, hashlib from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey def b64url(data: bytes) -> str: return base64.urlsafe_b64encode(data).rstrip(b"=").decode() def load_key(path: pathlib.Path) -> Ed25519PrivateKey: return serialization.load_pem_private_key(path.read_bytes(), password=None) def keyid_from_pub(pub_path: pathlib.Path) -> str: raw = pub_path.read_bytes() return hashlib.sha256(raw).hexdigest() def sign_bytes(key: Ed25519PrivateKey, data: bytes) -> bytes: return key.sign(data) def write_json(path: pathlib.Path, obj): path.write_text(json.dumps(obj, indent=2, sort_keys=True) + "\n") def sign_tuf(path: pathlib.Path, keyid: str, key: Ed25519PrivateKey): data = path.read_bytes() sig = sign_bytes(key, data) obj = json.loads(data) obj["signatures"] = [{"keyid": keyid, "sig": b64url(sig)}] write_json(path, obj) def main(): ap = argparse.ArgumentParser() ap.add_argument("--key", required=True, type=pathlib.Path) ap.add_argument("--manifest", required=True, type=pathlib.Path) ap.add_argument("--tar", required=True, type=pathlib.Path) ap.add_argument("--tuf-dir", required=True, type=pathlib.Path) args = ap.parse_args() key = load_key(args.key) pub_path = args.key.with_suffix(".pub") keyid = keyid_from_pub(pub_path) manifest_bytes = args.manifest.read_bytes() sig = sign_bytes(key, manifest_bytes) dsse = { "payloadType": "application/vnd.stellaops.mirror.manifest+json", "payload": b64url(manifest_bytes), "signatures": [{"keyid": keyid, "sig": b64url(sig)}], } dsse_path = args.manifest.with_suffix(".dsse.json") write_json(dsse_path, dsse) # update TUF metadata for name in ["root.json", "targets.json", "snapshot.json", "timestamp.json"]: sign_tuf(args.tuf_dir / name, keyid, key) print(f"Signed DSSE + TUF using keyid {keyid}; DSSE -> {dsse_path}") if __name__ == "__main__": main()