mail-address-semantics/mas_modes/signing.py
tegwick bdf200a600 MAS-WP-0011 finished: peer authentication in mode contract v0.1
CONTRACT.md section 7 (founder-reviewed): pairwise HMAC-SHA256 over
TIMESTAMP.BODY in MAS-Modes-* headers, 300 s window; master commands only
from the configured master; statuses only at the handoff's tokenized URL
from its peer; primed webhooks must name a known peer. mas_modes.signing,
signed contract.post, authenticating json_server, EmailMode/stubs/gateway
signing. conformance/auth.json vectors + 7 authorization flows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5787@bnt-lap001
Assistant-Session: 5db26b82-138b-40cc-9856-4d5eb0f752fd
2026-10-19 11:25:24 +02:00

53 lines
1.9 KiB
Python

"""Peer authentication for the mode contract (CONTRACT.md section 7).
HMAC-SHA256 over TIMESTAMP + "." + BODY with a pairwise key, sent in three
headers. Standard library only, so the gateway can use it without the rest
of `mas_modes`.
"""
from __future__ import annotations
import hashlib
import hmac
import time
from typing import Mapping, Optional
PEER = "MAS-Modes-Peer"
TIMESTAMP = "MAS-Modes-Timestamp"
SIGNATURE = "MAS-Modes-Signature"
WINDOW = 300 # seconds
def signature(key: bytes, timestamp: int, body: bytes) -> str:
return "v1=" + hmac.new(key, str(timestamp).encode() + b"." + body, hashlib.sha256).hexdigest()
def sign(body: bytes, peer: str, key: bytes, timestamp: Optional[int] = None) -> dict:
"""The headers that authenticate `body` as sent by `peer`."""
ts = int(time.time()) if timestamp is None else timestamp
return {PEER: peer, TIMESTAMP: str(ts), SIGNATURE: signature(key, ts, body)}
class AuthError(Exception):
def __init__(self, code: str, status: int = 401):
super().__init__(code)
self.code, self.status = code, status
def verify(headers: Mapping[str, str], body: bytes, keys: Mapping[str, bytes],
now: Optional[float] = None) -> str:
"""Return the authenticated peer id, or raise AuthError."""
get = {k.lower(): v for k, v in headers.items()}.get
peer, ts, sig = get(PEER.lower()), get(TIMESTAMP.lower()), get(SIGNATURE.lower())
key = keys.get(peer) if peer else None
if key is None or not ts or not sig:
raise AuthError("unauthenticated-peer")
try:
ts_i = int(ts)
except ValueError:
raise AuthError("unauthenticated-peer") from None
if not hmac.compare_digest(signature(key, ts_i, body), sig):
raise AuthError("unauthenticated-peer")
if abs((time.time() if now is None else now) - ts_i) > WINDOW:
raise AuthError("replayed")
return peer