#!/usr/bin/env python3
"""Build an Apple-compatible PKCS#10 CSR backed by Google Cloud KMS.

The script intentionally implements only the RSA/SHA-256 subset Burrow needs for
Apple Developer ID and iOS Distribution certificate requests. It fetches the KMS
public key, builds CertificationRequestInfo locally, asks KMS to sign that DER
payload, and writes a standard PEM CSR.
"""

from __future__ import annotations

import argparse
import base64
import os
import subprocess
import sys
import tempfile
from pathlib import Path


DEFAULT_PROJECT = "project-88c23ce9-918a-470a-b33"
DEFAULT_LOCATION = "global"
DEFAULT_KEYRING = "burrow-identity"
DEFAULT_KEY = "apple-developer-id-application"
DEFAULT_VERSION = "1"
DEFAULT_COMMON_NAME = "Burrow Developer ID Application"
DEFAULT_EMAIL = "release@burrow.net"


def der_length(length: int) -> bytes:
    if length < 0:
        raise ValueError("negative DER length")
    if length < 0x80:
        return bytes([length])
    encoded = length.to_bytes((length.bit_length() + 7) // 8, "big")
    return bytes([0x80 | len(encoded)]) + encoded


def der(tag: int, body: bytes) -> bytes:
    return bytes([tag]) + der_length(len(body)) + body


def der_sequence(*items: bytes) -> bytes:
    return der(0x30, b"".join(items))


def der_set(*items: bytes) -> bytes:
    return der(0x31, b"".join(items))


def der_integer(value: int) -> bytes:
    if value < 0:
        raise ValueError("negative INTEGER is not supported")
    raw = value.to_bytes(max(1, (value.bit_length() + 7) // 8), "big")
    if raw[0] & 0x80:
        raw = b"\x00" + raw
    return der(0x02, raw)


def der_null() -> bytes:
    return der(0x05, b"")


def der_bit_string(value: bytes) -> bytes:
    return der(0x03, b"\x00" + value)


def der_utf8(value: str) -> bytes:
    return der(0x0C, value.encode("utf-8"))


def der_printable(value: str) -> bytes:
    return der(0x13, value.encode("ascii"))


def der_ia5(value: str) -> bytes:
    return der(0x16, value.encode("ascii"))


def der_oid(dotted: str) -> bytes:
    parts = [int(part) for part in dotted.split(".")]
    if len(parts) < 2 or parts[0] > 2 or parts[1] > 39:
        raise ValueError(f"unsupported OID: {dotted}")
    body = bytes([40 * parts[0] + parts[1]])
    for value in parts[2:]:
        if value < 0:
            raise ValueError(f"negative OID component: {dotted}")
        encoded = [value & 0x7F]
        value >>= 7
        while value:
            encoded.append(0x80 | (value & 0x7F))
            value >>= 7
        body += bytes(reversed(encoded))
    return der(0x06, body)


def name_attribute(oid: str, value_der: bytes) -> bytes:
    return der_set(der_sequence(der_oid(oid), value_der))


def subject_name(common_name: str, email_address: str, country: str | None) -> bytes:
    attrs: list[bytes] = []
    if country:
        if len(country) != 2 or not country.isalpha():
            raise ValueError("--country must be a two-letter ISO country code")
        attrs.append(name_attribute("2.5.4.6", der_printable(country.upper())))
    if email_address:
        attrs.append(name_attribute("1.2.840.113549.1.9.1", der_ia5(email_address)))
    attrs.append(name_attribute("2.5.4.3", der_utf8(common_name)))
    return der_sequence(*attrs)


def pem_to_der(pem: bytes) -> bytes:
    lines = [
        line.strip()
        for line in pem.splitlines()
        if line and not line.startswith(b"-----")
    ]
    if not lines:
        raise ValueError("PEM input did not contain base64 data")
    return base64.b64decode(b"".join(lines), validate=True)


def pem_wrap(label: str, body: bytes) -> str:
    encoded = base64.b64encode(body).decode("ascii")
    lines = [f"-----BEGIN {label}-----"]
    lines.extend(encoded[i : i + 64] for i in range(0, len(encoded), 64))
    lines.append(f"-----END {label}-----")
    return "\n".join(lines) + "\n"


def run(command: list[str]) -> None:
    subprocess.run(command, check=True)


def fetch_public_key(args: argparse.Namespace, output: Path) -> None:
    command = [
        args.gcloud,
        "kms",
        "keys",
        "versions",
        "get-public-key",
        args.kms_version,
        "--location",
        args.kms_location,
        "--keyring",
        args.kms_keyring,
        "--key",
        args.kms_key,
        "--output-file",
        str(output),
    ]
    if args.gcloud_project:
        command.extend(["--project", args.gcloud_project])
    run(command)


def kms_sign(args: argparse.Namespace, payload: bytes) -> bytes:
    with tempfile.TemporaryDirectory(prefix="burrow-kms-csr.") as tmp:
        payload_path = Path(tmp) / "certification-request-info.der"
        signature_path = Path(tmp) / "signature.b64"
        payload_path.write_bytes(payload)
        command = [
            args.gcloud,
            "kms",
            "asymmetric-sign",
            "--location",
            args.kms_location,
            "--keyring",
            args.kms_keyring,
            "--key",
            args.kms_key,
            "--version",
            args.kms_version,
            "--digest-algorithm",
            "sha256",
            "--input-file",
            str(payload_path),
            "--signature-file",
            str(signature_path),
        ]
        if args.gcloud_project:
            command.extend(["--project", args.gcloud_project])
        run(command)
        signature = signature_path.read_bytes()
        try:
            return base64.b64decode(signature.strip(), validate=True)
        except ValueError:
            return signature


def build_csr(args: argparse.Namespace, spki_der: bytes) -> bytes:
    cri = der_sequence(
        der_integer(0),
        subject_name(args.common_name, args.email_address, args.country),
        spki_der,
        b"\xA0\x00",
    )
    signature = kms_sign(args, cri)
    signature_algorithm = der_sequence(
        der_oid("1.2.840.113549.1.1.11"),
        der_null(),
    )
    return der_sequence(cri, signature_algorithm, der_bit_string(signature))


def parse_args(argv: list[str]) -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="Generate an Apple-compatible CSR backed by a Google Cloud KMS RSA signing key.",
    )
    parser.add_argument("--gcloud-project", default=os.environ.get("GOOGLE_CLOUD_PROJECT") or os.environ.get("BURROW_GOOGLE_PROJECT_ID") or DEFAULT_PROJECT)
    parser.add_argument("--kms-location", default=os.environ.get("BURROW_KMS_LOCATION", DEFAULT_LOCATION))
    parser.add_argument("--kms-keyring", default=os.environ.get("BURROW_KMS_KEYRING", DEFAULT_KEYRING))
    parser.add_argument("--kms-key", default=os.environ.get("BURROW_APPLE_KMS_KEY") or os.environ.get("BURROW_APPLE_DEVELOPER_ID_KMS_KEY", DEFAULT_KEY))
    parser.add_argument("--kms-version", default=os.environ.get("BURROW_KMS_VERSION", DEFAULT_VERSION))
    parser.add_argument("--common-name", default=os.environ.get("BURROW_APPLE_CSR_COMMON_NAME") or os.environ.get("BURROW_APPLE_DEVELOPER_ID_CSR_COMMON_NAME", DEFAULT_COMMON_NAME))
    parser.add_argument("--email-address", default=os.environ.get("BURROW_APPLE_CSR_EMAIL") or os.environ.get("BURROW_APPLE_DEVELOPER_ID_CSR_EMAIL", DEFAULT_EMAIL))
    parser.add_argument("--country", default=os.environ.get("BURROW_APPLE_CSR_COUNTRY") or os.environ.get("BURROW_APPLE_DEVELOPER_ID_CSR_COUNTRY", "US"))
    parser.add_argument("--public-key-pem", help="Existing KMS public-key PEM path. If omitted, gcloud fetches it.")
    parser.add_argument("--public-key-output", help="Write the fetched/used public key PEM to this path.")
    parser.add_argument("--output", default="developer-id-application.certSigningRequest")
    parser.add_argument("--gcloud", default=os.environ.get("GCLOUD", "gcloud"))
    return parser.parse_args(argv)


def main(argv: list[str]) -> int:
    args = parse_args(argv)
    output = Path(args.output)

    with tempfile.TemporaryDirectory(prefix="burrow-kms-csr.") as tmp:
        public_key_path = Path(args.public_key_pem) if args.public_key_pem else Path(tmp) / "kms-public.pem"
        if not args.public_key_pem:
            fetch_public_key(args, public_key_path)
        public_key_pem = public_key_path.read_bytes()
        spki_der = pem_to_der(public_key_pem)
        csr = build_csr(args, spki_der)

    output.parent.mkdir(parents=True, exist_ok=True)
    output.write_text(pem_wrap("CERTIFICATE REQUEST", csr), encoding="ascii")
    if args.public_key_output:
        public_output = Path(args.public_key_output)
        public_output.parent.mkdir(parents=True, exist_ok=True)
        public_output.write_bytes(public_key_pem)
    print(f"wrote {output}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main(sys.argv[1:]))
