secure-messaging/python/secure_messaging/npe_adapter.py
George Lambert 9eed0b1942
Some checks are pending
ci / python (push) Waiting to run
ci / go (push) Waiting to run
S14: NPE fail-closed adapter; npe mode never falls back to lab-xor
2026-09-15 22:22:56 -04:00

40 lines
1.4 KiB
Python

"""NPE sidecar adapter (fail-closed).
Production ``crypto.mode=npe`` must not fall back to lab-xor.
If the ``npe`` binary is missing, raise NpeRequired.
Live ns1 must not set this until CI review (see UserReview.MD).
"""
from __future__ import annotations # annotations as strings
import json # encode sidecar request
import shutil # look up npe on PATH
import subprocess # run sidecar
from typing import Any, Dict, Mapping # types
class NpeRequired(RuntimeError):
"""Raised when NPE is required but the sidecar is not usable."""
def npe_bin() -> str:
"""Return path to npe or raise."""
path = shutil.which("npe") # PATH lookup
if not path: # missing
raise NpeRequired("npe binary not on PATH; attach sidecar") # fail closed
return path # found
def seal_npe(to: str, body: Mapping[str, Any]) -> str:
"""Ask sidecar to HPKE-seal body for mailbox ``to``. Returns hex ct."""
raw = json.dumps(body, sort_keys=True, separators=(",", ":")).encode() # canonical body
proc = subprocess.run( # npe CLI contract: stdin body, arg dest
[npe_bin(), "seal", "--to", to],
input=raw,
capture_output=True,
timeout=15,
check=False,
)
if proc.returncode != 0: # sidecar failed
raise NpeRequired("npe seal failed: " + proc.stderr.decode(errors="replace")[:200])
return proc.stdout.strip().decode() # hex or token from sidecar