"""File-based Ed25519 keys and authorized_keys helpers.""" from __future__ import annotations from pathlib import Path from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey from cryptography.hazmat.primitives.serialization import ( Encoding, NoEncryption, PrivateFormat, PublicFormat, load_pem_private_key, ) from browser_cli.auth.agent import AgentKey def generate_keypair() -> tuple[bytes, str]: """Return (private_key_pem_bytes, public_key_hex).""" private_key = Ed25519PrivateKey.generate() pem = private_key.private_bytes(Encoding.PEM, PrivateFormat.PKCS8, NoEncryption()) public_hex = private_key.public_key().public_bytes(Encoding.Raw, PublicFormat.Raw).hex() return pem, public_hex def load_private_key(path: Path) -> Ed25519PrivateKey: return load_pem_private_key(path.read_bytes(), password=None) def public_key_hex(key: Ed25519PrivateKey | AgentKey) -> str: if isinstance(key, AgentKey): return key.pubkey_bytes.hex() return key.public_key().public_bytes(Encoding.Raw, PublicFormat.Raw).hex() def load_authorized_keys_with_names(path: Path) -> list[tuple[str, str]]: """Return list of (pubkey_hex, name) pairs. Name is empty string if not set.""" if not path.exists(): return [] result = [] for line in path.read_text(encoding="utf-8").splitlines(): line = line.strip() if not line or line.startswith("#"): continue parts = line.split(None, 1) pubkey = parts[0] name = parts[1].strip() if len(parts) > 1 else "" result.append((pubkey, name)) return result def load_authorized_keys(path: Path) -> list[str]: return [pubkey for pubkey, _name in load_authorized_keys_with_names(path)] def add_authorized_key(path: Path, pub_hex: str, name: str = "") -> bool: """Append pub_hex to authorized_keys. Returns False if already present.""" path.parent.mkdir(parents=True, exist_ok=True) existing = {pubkey for pubkey, _name in load_authorized_keys_with_names(path)} if pub_hex in existing: return False line = (f"{pub_hex} {name}".rstrip()) + "\n" with open(path, "a", encoding="utf-8") as file: file.write(line) return True