#!/usr/bin/env python3
"""Infraveil release-approval signer. Run OFFLINE, on a machine Infraveil never touches.

Your agents will only run a release YOU have signed with a key WE never hold.

1) Generate your approval keypair once:
       python approve-release.py --genkey
   The PRIVATE key is written to ./approval-private.key (chmod 600) -- keep it
   offline and safe. Register the printed PUBLIC key in your Infraveil dashboard.

2) Approve a release -- take its payload hash (64 lowercase hex, from your
   dashboard) and sign it:
       python approve-release.py --key-file ./approval-private.key --hash <payload-hash>
   (If you omit --key-file you'll be prompted for the key with no echo.)
   Submit the printed signature in your dashboard.

Security notes:
  - Do NOT pass the private key as a command-line argument (shell history,
    process listings, logs). Use --key-file or the no-echo prompt.
  - The hash is normalized to lowercase and must be exactly 64 hex chars --
    the same form the agent computes locally.

Requires: pip install cryptography
"""
import sys, os, re, getpass
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives import serialization as S

KEY_FILE_DEFAULT = "approval-private.key"
_HEX64 = re.compile(r"^[0-9a-f]{64}$")
_HEX_KEY = re.compile(r"^[0-9a-f]{64}$")  # 32 bytes = 64 hex chars


def _arg(name):
    if name in sys.argv:
        i = sys.argv.index(name)
        if i + 1 < len(sys.argv):
            return sys.argv[i + 1]
    return None


def genkey():
    priv = Ed25519PrivateKey.generate()
    seed = priv.private_bytes(S.Encoding.Raw, S.PrivateFormat.Raw, S.NoEncryption()).hex()
    pub = priv.public_key().public_bytes(S.Encoding.Raw, S.PublicFormat.Raw).hex()
    path = _arg("--out") or KEY_FILE_DEFAULT
    if os.path.exists(path):
        print(f"Refusing to overwrite existing {path} (move it aside first).")
        return 1
    fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
    with os.fdopen(fd, "w") as f:
        f.write(seed + "\n")
    print(f"PRIVATE KEY written to {path} (chmod 600). Keep it offline. There is no recovery if you lose it.")
    print("PUBLIC KEY (register in your dashboard):")
    print("  " + pub)
    return 0


def _load_key():
    kf = _arg("--key-file")
    raw = None
    if kf:
        try:
            st = os.stat(kf)
            if os.name != "nt" and st.st_mode & 0o077:
                sys.stderr.write(f"warning: {kf} is readable by others; chmod 600 it.\n")
        except Exception:
            pass
        with open(kf, encoding="utf-8") as f:
            raw = f.read().strip()
    elif _arg("--key"):
        sys.stderr.write("warning: passing --key on the command line is insecure (shell history/process list). Prefer --key-file.\n")
        raw = _arg("--key").strip()
    else:
        raw = getpass.getpass("Approval private key (hex, no echo): ").strip()
    raw = raw.lower()
    if not _HEX_KEY.match(raw):
        print("Invalid private key: expected 32 bytes as 64 hex characters.")
        return None
    return Ed25519PrivateKey.from_private_bytes(bytes.fromhex(raw))


def sign(payload_hash):
    payload_hash = (payload_hash or "").strip().lower()
    if not _HEX64.match(payload_hash):
        print("Invalid --hash: expected exactly 64 hex characters (a SHA-256, lowercase).")
        return 1
    priv = _load_key()
    if priv is None:
        return 1
    sig = priv.sign(payload_hash.encode("utf-8")).hex()
    print("SIGNATURE (submit in your dashboard to approve this release):")
    print("  " + sig)
    return 0


def main():
    if "--genkey" in sys.argv:
        return genkey()
    h = _arg("--hash")
    if h is not None:
        return sign(h)
    print(__doc__)
    return 2


if __name__ == "__main__":
    sys.exit(main())
