#!/usr/bin/env python3 """Generate an Ed25519 keypair for signing GovOPlaN release catalogs.""" from __future__ import annotations import argparse import base64 from datetime import UTC, datetime import json from pathlib import Path import stat from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--key-id", required=True, help="Public key id recorded in catalog signatures.") parser.add_argument("--private-key", type=Path, required=True, help="Private PEM output path. Keep outside git.") parser.add_argument("--public-key", type=Path, help="Optional base64 public key output path.") parser.add_argument("--keyring", type=Path, help="Optional public keyring JSON output path.") parser.add_argument("--status", default="active", choices=("active", "next", "retired", "revoked", "disabled")) parser.add_argument("--force", action="store_true", help="Overwrite existing key files.") args = parser.parse_args() private_path = args.private_key.expanduser() public_path = args.public_key.expanduser() if args.public_key else None keyring_path = args.keyring.expanduser() if args.keyring else None if private_path.exists() and not args.force: raise SystemExit(f"Private key already exists: {private_path}") if public_path is not None and public_path.exists() and not args.force: raise SystemExit(f"Public key already exists: {public_path}") private_key = Ed25519PrivateKey.generate() private_bytes = private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption(), ) public_bytes = private_key.public_key().public_bytes( encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw, ) public_base64 = base64.b64encode(public_bytes).decode("ascii") private_path.parent.mkdir(parents=True, exist_ok=True) private_path.write_bytes(private_bytes) private_path.chmod(stat.S_IRUSR | stat.S_IWUSR) if public_path is not None: public_path.parent.mkdir(parents=True, exist_ok=True) public_path.write_text(public_base64 + "\n", encoding="utf-8") if keyring_path is not None: keyring_path.parent.mkdir(parents=True, exist_ok=True) keyring = { "keyring_version": "1", "purpose": "govoplan module package catalog signatures", "generated_at": datetime.now(tz=UTC).isoformat().replace("+00:00", "Z"), "keys": [ { "key_id": args.key_id, "status": args.status, "public_key": public_base64, "not_before": datetime.now(tz=UTC).date().isoformat() + "T00:00:00Z", } ], } keyring_path.write_text(json.dumps(keyring, indent=2, sort_keys=True) + "\n", encoding="utf-8") print(f"private_key={private_path}") if public_path is not None: print(f"public_key={public_path}") if keyring_path is not None: print(f"keyring={keyring_path}") print(f"key_id={args.key_id}") print(f"public_key_base64={public_base64}") return 0 if __name__ == "__main__": raise SystemExit(main())