#!/usr/bin/python3
# SPDX-License-Identifier: GPL-2.0
#
# tsign-sign-file - Drop-in replacement for kernel's scripts/sign-file
#
# This script is installed as scripts/sign-file in the kernel build/devel
# directory. It is called by Kbuild when building out-of-tree (third-party)
# kernel modules:
#
#   scripts/sign-file <hash_algo> <private_key> <public_key> <module.ko>
#
# Since the actual private key is held by the remote signing server, the
# <private_key> and <public_key> arguments are ignored. Instead, this script
# delegates to module_signer.py for remote hash-based signing.

import os
import sys
import subprocess

SCRIPT_NAME = "tsign-sign-file"
TSIGN_LIBEXECDIR = os.environ.get("TSIGN_LIBEXECDIR", "/usr/libexec/tsign")

# Config file locations (checked in order, same as tsign)
TSIGN_CONFIG_FILES = [
    "/etc/tsign/tsign.conf",
    os.path.join(
        os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")),
        "tsign/tsign.conf",
    ),
]


def error(msg):
    print(f"{SCRIPT_NAME}: error: {msg}", file=sys.stderr)
    sys.exit(1)


def load_config():
    """Load configuration from config files (shell-style KEY=VALUE)."""
    config = {}
    for conf_path in TSIGN_CONFIG_FILES:
        if os.path.isfile(conf_path):
            with open(conf_path) as f:
                for line in f:
                    line = line.strip()
                    if not line or line.startswith("#"):
                        continue
                    if "=" in line:
                        key, _, value = line.partition("=")
                        key = key.strip()
                        value = value.strip().strip("\"'")
                        config[key] = value
            break
    return config


def get_config(key, config, default=None):
    """Get configuration value: env var > config file > default."""
    return os.environ.get(key) or config.get(key) or default


def find_module_signer():
    """Locate module_signer.py from script directory or libexec."""
    candidates = [
        os.path.join(os.path.dirname(os.path.realpath(__file__)), "module_signer.py"),
        os.path.join(TSIGN_LIBEXECDIR, "module_signer.py"),
    ]
    for path in candidates:
        if os.path.isfile(path):
            return path
    return None


def main():
    # Kbuild calls: scripts/sign-file <hash_algo> <private_key> <public_key> <module.ko>
    if len(sys.argv) < 5:
        print(
            f"Usage: {SCRIPT_NAME} <hash_algo> <private_key> <public_key> <module.ko>",
            file=sys.stderr,
        )
        sys.exit(1)

    # hash_algo = sys.argv[1]   # e.g. sha256 — ignored, signing server decides
    # private_key = sys.argv[2] # Ignored: private key is on the signing server
    # public_key = sys.argv[3]  # Ignored: public key is on the signing server
    module_file = sys.argv[4]

    if not os.path.isfile(module_file):
        error(f"Module file not found: {module_file}")

    # Load configuration
    config = load_config()
    sign_url = get_config("TSIGN_SIGN_URL", config)
    cert_id = get_config("TSIGN_MODULE_CERT_ID", config)

    if not sign_url:
        error("TSIGN_SIGN_URL is not set. Set via environment variable or /etc/tsign/tsign.conf")
    if not cert_id:
        error("TSIGN_MODULE_CERT_ID is not set. Set via environment variable or /etc/tsign/tsign.conf")

    # Find module_signer.py
    module_signer = find_module_signer()
    if module_signer is None:
        error("Cannot find module_signer.py")

    # Sign the module in-place
    result = subprocess.run(
        [
            sys.executable, module_signer,
            module_file,
            "-o", module_file,
            "--url", sign_url,
            "--cert-id", cert_id,
        ],
        capture_output=True,
        text=True,
    )

    if result.returncode != 0:
        error(f"Failed to sign {module_file}: {result.stderr.strip()}")

    # Verify signature was attached
    MAGIC = b"~Module signature appended~\n"
    with open(module_file, "rb") as f:
        f.seek(-len(MAGIC), 2)
        tail = f.read()

    if tail != MAGIC:
        error(f"Signature verification failed for {module_file}")


if __name__ == "__main__":
    main()
