7cb2a8b618
Testing / remote-protocol-compat (0.9.5) (push) Successful in 1m4s
Testing / test (push) Successful in 1m22s
Testing / remote-protocol-compat (0.9.3) (push) Successful in 1m7s
Package Extension / package-extension (push) Successful in 1m1s
Build & Publish Package / publish (push) Successful in 1m5s
- Split auth into focused package modules for agent keys, file keys, signing, and post-quantum transport helpers while keeping the public browser_cli.auth import surface intact. - Move transport encoding internals into a package with separate codec and binary-hoisting helpers, preserving browser_cli.transport compatibility. - Extract remote TCP auth/socket helpers and serve challenge setup out of the runtime paths to make connection handling easier to reason about. - Move the extension markdown extractor into a dedicated content/markdown folder with separate root selection, code normalization, renderer, and utils. - Centralize CLI Rich rendering helpers for tab/window tree and table output, and add rendering tests for the shared builders. - Remove local typing ignores in SDK/decorator/script plumbing and bump the package and extension version to 0.15.3.
60 lines
2.1 KiB
Python
60 lines
2.1 KiB
Python
"""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
|