/****************************************************************************** __ ____ __ / / ___ ____ _/ __/_____________ _/ /__ / / / _ \/ __ `/ /_/ ___/ ___/ __ `/ / _ \ / /___/ __/ /_/ / __(__ ) /__/ /_/ / / __/ /_____/\___/\__,_/_/ /____/\___/\__,_/_/\___/ (C)opyright 2025, Leafscale, LLC - https://www.leafscale.com Project: Zygaena Filename: key.reef Authors: Chris Tusa License: Description: Key command - manage signing keys ******************************************************************************/ module commands.key import sys.args import sys.process import io.file import io.dir import core.str import core.signing import util.color import exitcodes as ec export fn execute(): int end export fn execute(): int let argc = args.count() if argc < 3 print_usage() return ec.EXIT_USAGE() end if let subcommand = args.get(2) if subcommand == "list" list_keys() return ec.EXIT_SUCCESS() elif subcommand == "generate" if argc < 4 color.print_error("Missing key name") println("Usage: coral key generate ") return ec.EXIT_USAGE() end if generate_key(args.get(3)) return ec.EXIT_SUCCESS() elif subcommand == "import" if argc < 4 color.print_error("Missing key file") println("Usage: coral key import [name]") return ec.EXIT_USAGE() end if let key_file = args.get(3) mut name = "" if argc >= 5 name = args.get(4) end if import_key(key_file, name) return ec.EXIT_SUCCESS() elif subcommand == "export" if argc < 4 color.print_error("Missing key name") println("Usage: coral key export ") return ec.EXIT_USAGE() end if export_key(args.get(3)) return ec.EXIT_SUCCESS() elif subcommand == "delete" if argc < 4 color.print_error("Missing key name") println("Usage: coral key delete ") return ec.EXIT_USAGE() end if delete_key(args.get(3)) return ec.EXIT_SUCCESS() elif subcommand == "init" init_keyring() return ec.EXIT_SUCCESS() elif subcommand == "trust" if argc < 4 color.print_error("Missing key name") println("Usage: coral key trust ") return ec.EXIT_USAGE() end if trust_key(args.get(3)) return ec.EXIT_SUCCESS() elif subcommand == "revoke" if argc < 4 color.print_error("Missing key name") println("Usage: coral key revoke ") return ec.EXIT_USAGE() end if revoke_key(args.get(3)) return ec.EXIT_SUCCESS() else color.print_error("Unknown subcommand: " + subcommand) print_usage() return ec.EXIT_USAGE() end if end execute proc list_keys() color.print_action("Signing keys:") println("") mut keys: [signing.KeyPair] = new [signing.KeyPair](64) let count = signing.list_keys(keys, 64) if count == 0 color.print_info("No keys found") println("") color.print_info("Generate a key with: coral key generate ") return end if mut i = 0 while i < count let key = keys[i] print(" ") print(key.name) if str.length(key.private_key) > 0 print(" [private+public]") else print(" [public only]") end if if key.trusted print(" [trusted]") end if println("") i = i + 1 end while println("") color.print_info("Total: " + int_to_str(count) + " key(s)") end list_keys proc generate_key(name: string) color.print_action("Generating Ed25519 keypair: " + name) let result = signing.generate_keypair(name) if result.success color.print_success("Keypair generated successfully") println("") color.print_info("Private key: " + signing.get_keyring_dir() + "/" + name + ".key") color.print_info("Public key: " + signing.get_keyring_dir() + "/" + name + ".pub") else color.print_error(result.error) end if end generate_key proc import_key(key_file: string, name: string) if not file.fileExists(key_file) color.print_error("Key file not found: " + key_file) return end if // Determine name from filename if not provided mut key_name = name if str.length(key_name) == 0 key_name = extract_name(key_file) end if if str.length(key_name) == 0 color.print_error("Could not determine key name. Please specify one.") return end if color.print_action("Importing key: " + key_name) let key_data = file.readFile(key_file) let result = signing.import_key(key_data, key_name) if result.success color.print_success("Key imported successfully") else color.print_error(result.error) end if end import_key proc export_key(name: string) let keyring = signing.get_keyring_dir() let pub_path = keyring + "/" + name + ".pub" if not file.fileExists(pub_path) color.print_error("Key not found: " + name) return end if color.print_action("Public key for " + name + ":") println("") let key_data = file.readFile(pub_path) println(key_data) end export_key proc delete_key(name: string) let keyring = signing.get_keyring_dir() // Check if key exists let pub_path = keyring + "/" + name + ".pub" let priv_path = keyring + "/" + name + ".key" if not file.fileExists(pub_path) and not file.fileExists(priv_path) color.print_error("Key not found: " + name) return end if // Ask for confirmation if private key exists if file.fileExists(priv_path) color.print_warning("This will delete the private key! Are you sure?") color.print_info("Use --force to confirm deletion") if not args.has_flag("force") return end if end if // Delete files if file.fileExists(pub_path) let cmd1 = "rm -f \"" + pub_path + "\"" let pid1 = process.process_spawn_shell(cmd1) if pid1 > 0 process.process_wait(pid1) end if end if if file.fileExists(priv_path) let cmd2 = "rm -f \"" + priv_path + "\"" let pid2 = process.process_spawn_shell(cmd2) if pid2 > 0 process.process_wait(pid2) end if end if color.print_success("Deleted key: " + name) end delete_key fn extract_name(path: string): string // Extract filename without extension let len = str.length(path) if len == 0 return "" end if // Find last slash mut start = 0 mut i = 0 while i < len if path[i] == '/' start = i + 1 end if i = i + 1 end while // Find last dot mut end_pos = len i = len - 1 while i >= start if path[i] == '.' end_pos = i end if i = i - 1 end while if end_pos <= start return str.substring(path, start, len - start) end if return str.substring(path, start, end_pos - start) end extract_name // Initialize the keyring directory structure proc init_keyring() color.print_action("Initializing keyring...") let keyring = signing.get_keyring_dir() let trusted_keys_dir = "/var/lib/coral/trusted-keys" // Create keyring directory if not dir.dir_exists(keyring) if not dir.create_dir_all(keyring) color.print_error("Failed to create keyring directory: " + keyring) return end if color.print_info("Created: " + keyring) else color.print_info("Keyring already exists: " + keyring) end if // Create trusted-keys directory if not dir.dir_exists(trusted_keys_dir) if not dir.create_dir_all(trusted_keys_dir) color.print_error("Failed to create trusted-keys directory") return end if color.print_info("Created: " + trusted_keys_dir) else color.print_info("Trusted keys directory already exists: " + trusted_keys_dir) end if // Set permissions (keyring should be root-only for private keys) let cmd = "chmod 700 \"" + keyring + "\"" let pid = process.process_spawn_shell(cmd) if pid > 0 process.process_wait(pid) end if color.print_success("Keyring initialized") color.print_info("Generate keys with: coral key generate ") end init_keyring // Add a key to the trusted keys list proc trust_key(name: string) let keyring = signing.get_keyring_dir() let trusted_dir = "/var/lib/coral/trusted-keys" let pub_path = keyring + "/" + name + ".pub" // Check if key exists if not file.fileExists(pub_path) color.print_error("Key not found: " + name) color.print_info("Import or generate the key first") return end if // Ensure trusted-keys directory exists if not dir.dir_exists(trusted_dir) if not dir.create_dir_all(trusted_dir) color.print_error("Failed to create trusted-keys directory") return end if end if // Check if already trusted let trusted_path = trusted_dir + "/" + name + ".pub" if file.fileExists(trusted_path) color.print_warning("Key already trusted: " + name) return end if // Copy public key to trusted directory let cmd = "cp \"" + pub_path + "\" \"" + trusted_path + "\"" let pid = process.process_spawn_shell(cmd) if pid < 0 color.print_error("Failed to copy key") return end if let exit_code = process.process_wait(pid) if exit_code != 0 color.print_error("Failed to trust key") return end if color.print_success("Trusted key: " + name) color.print_info("Packages signed with this key will now be accepted") end trust_key // Remove a key from the trusted keys list proc revoke_key(name: string) let trusted_dir = "/var/lib/coral/trusted-keys" let trusted_path = trusted_dir + "/" + name + ".pub" // Check if key is trusted if not file.fileExists(trusted_path) color.print_error("Key is not in trusted list: " + name) return end if // Confirm revocation if not args.has_flag("force") and not args.has_flag("yes") color.print_warning("This will revoke trust for key: " + name) color.print_info("Use --force or -y to confirm") return end if // Remove from trusted keys let cmd = "rm -f \"" + trusted_path + "\"" let pid = process.process_spawn_shell(cmd) if pid < 0 color.print_error("Failed to revoke key") return end if let exit_code = process.process_wait(pid) if exit_code != 0 color.print_error("Failed to remove key from trusted list") return end if color.print_success("Revoked trust for key: " + name) color.print_info("Packages signed with this key will no longer be accepted") end revoke_key // List trusted keys proc list_trusted_keys() let trusted_dir = "/var/lib/coral/trusted-keys" if not dir.dir_exists(trusted_dir) color.print_info("No trusted keys (directory does not exist)") return end if mut entries: [string] = new [string](64) let count = dir.list_dir(trusted_dir, entries, 64) if count == 0 color.print_info("No trusted keys") return end if println("") color.print_action("Trusted keys:") mut i = 0 while i < count let entry = entries[i] if str.ends_with(entry, ".pub") let name = str.substring(entry, 0, str.length(entry) - 4) println(" " + name) end if i = i + 1 end while end list_trusted_keys proc print_usage() println("Usage: coral key [options]") println("") println("Manage signing keys for package verification.") println("") println("Commands:") println(" list List all keys in keyring") println(" generate Generate a new Ed25519 keypair") println(" import [name] Import a public key") println(" export Export a public key") println(" delete Delete a key (use --force for private keys)") println("") println("Trust management:") println(" init Initialize keyring directories") println(" trust Add key to trusted list") println(" revoke Remove key from trusted list") println("") println("Examples:") println(" coral key init") println(" coral key generate maintainer") println(" coral key import repo.pub community") println(" coral key trust community") end print_usage // Helper: convert int to string fn int_to_str(n: int): string if n == 0 return "0" end if mut negative = false mut value = n if n < 0 negative = true value = 0 - n end if mut result = "" while value > 0 let digit = value % 10 result = str.concat(str.substring("0123456789", digit, 1), result) value = value / 10 end while if negative result = str.concat("-", result) end if return result end int_to_str end module