"""Canonical browser-cli auth payload signing and verification.""" from __future__ import annotations import hashlib import json from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey from browser_cli.auth.agent import AgentKey, agent_sign_raw def canonical_payload(msg: dict) -> bytes: """Deterministic JSON encoding of msg without auth protocol fields.""" return json.dumps( {key: value for key, value in msg.items() if key not in {"pubkey", "sig", "pq_kex"}}, sort_keys=True, separators=(",", ":"), ).encode("utf-8") def auth_message(nonce: bytes, msg: dict, pq_shared_secret: bytes | None = None) -> bytes: """Bytes signed for auth; optionally binds a post-quantum KEX secret.""" data = nonce + hashlib.sha256(canonical_payload(msg)).digest() if pq_shared_secret is not None: data += hashlib.sha256(b"browser-cli ml-kem-768 v1" + pq_shared_secret).digest() return data def sign(key: Ed25519PrivateKey | AgentKey, nonce: bytes, msg: dict, pq_shared_secret: bytes | None = None) -> bytes: """Sign nonce + payload hash, optionally bound to an ML-KEM shared secret.""" data = auth_message(nonce, msg, pq_shared_secret) if isinstance(key, AgentKey): return agent_sign_raw(key, data) return key.sign(data) def verify(pub_hex: str, nonce: bytes, msg: dict, sig_hex: str, pq_shared_secret: bytes | None = None) -> bool: """Return True if sig_hex is a valid signature over the canonical payload/auth secret.""" try: pub_bytes = bytes.fromhex(pub_hex) pub_key = Ed25519PublicKey.from_public_bytes(pub_bytes) pub_key.verify(bytes.fromhex(sig_hex), auth_message(nonce, msg, pq_shared_secret)) return True except (InvalidSignature, ValueError): return False