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.
43 lines
1.8 KiB
Python
43 lines
1.8 KiB
Python
"""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
|