40 lines
1.4 KiB
Python
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
|