S06-S08: Python spec, Go in-process leaf, tests and Forgejo CI
Some checks are pending
ci / python (push) Waiting to run
ci / go (push) Waiting to run

This commit is contained in:
George Lambert 2026-09-15 22:17:19 -04:00
commit d67509e470
16 changed files with 726 additions and 0 deletions

View file

@ -0,0 +1,13 @@
# Public package surface for the readable Python spec.
from .signed_config import SignedConfig, UnsignedConfig # signed wrapper load/save
from .envelope import PassthroughEnvelope # dest-in-clear, body ciphertext
from .error_bundle import NetworkErrorBundle # system-key + sender-only
from .admin_history import AdminHistory # DataCube-shaped append-only config log
__all__ = [
"SignedConfig",
"UnsignedConfig",
"PassthroughEnvelope",
"NetworkErrorBundle",
"AdminHistory",
]

View file

@ -0,0 +1,89 @@
"""Append-only admin configuration history (Verae DataCube-shaped JSONL).
Each row stores previous file, new file, unified diff, actor, and dual hashes.
Compatible with pfc.chain field names so a real cube can ingest the log.
"""
from __future__ import annotations # annotations
import difflib # unified diff
import hashlib # sha256
import json # rows
import time # created_ts
from pathlib import Path # file
from typing import Any, Dict, List, Optional # types
def sha256_hex(data: bytes) -> str:
"""Document hash for the history row."""
return hashlib.sha256(data).hexdigest() # hex
def unified_diff(prev: str, new: str, name: str = "config") -> str:
"""Unified diff of previous and new config text."""
return "".join( # join generator
difflib.unified_diff(
prev.splitlines(True), # old lines keepends
new.splitlines(True), # new lines
fromfile=name + ".prev", # label
tofile=name + ".new", # label
)
)
class AdminHistory:
"""JSONL chain under a cube directory: blockchain/chain.jsonl."""
def __init__(self, cube_dir: Path):
self.path = Path(cube_dir) / "blockchain" / "chain.jsonl" # pfc layout
self.path.parent.mkdir(parents=True, exist_ok=True) # ensure dir
if not self.path.exists(): # empty chain
self.path.touch() # create
def _rows(self) -> List[Dict[str, Any]]:
"""Read all rows."""
rows = [] # accumulator
for line in self.path.read_text().splitlines(): # each line
if line.strip(): # skip blanks
rows.append(json.loads(line)) # parse
return rows # list
def tip(self) -> Optional[Dict[str, Any]]:
"""Last row or None."""
rows = self._rows() # load
return rows[-1] if rows else None # tip
def append_change(
self,
*,
actor: str,
prev_text: str,
new_text: str,
name: str = "secure-messaging.json",
) -> Dict[str, Any]:
"""Append prev, new, diff. Empty actor is rejected."""
if not (actor or "").strip(): # required
raise ValueError("actor required")
prev_h = sha256_hex(prev_text.encode()) # hash old
new_h = sha256_hex(new_text.encode()) # hash new
payload = { # chain payload
"type": "admin-config",
"actor": actor,
"prev": prev_text,
"new": new_text,
"diff": unified_diff(prev_text, new_text, name),
"prev_sha256": prev_h,
"new_sha256": new_h,
"created_ts": time.time(),
}
body = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode() # canonical
prev = self.tip() # previous chain row
row = { # pfc-like envelope
"seq": (prev["seq"] + 1) if prev else 0,
"prev_sha256": prev["sha256"] if prev else "0" * 64,
"payload": payload,
"sha256": sha256_hex(body),
}
with self.path.open("a") as f: # append-only
f.write(json.dumps(row, sort_keys=True) + "\n") # one line
return row # for tests

View file

@ -0,0 +1,102 @@
"""Passthrough envelope: destination in the clear, body encrypted.
After send, the sender cannot open the ciphertext; they keep lookup_id only.
Production alg is npe (HPKE). Lab algs are lab-xor and plain-lab.
"""
from __future__ import annotations # annotations as strings
import hashlib # lookup_id and lab key
import json # body encode
import os # urandom nonce
from dataclasses import dataclass # envelope fields
from typing import Any, Dict, Mapping, Optional # maps
def lookup_id(sender: str, nonce: bytes) -> str:
"""Opaque id: HMAC-SHA256(sender, nonce) hex. Not reversible at the broker."""
return hashlib.sha256(nonce + sender.encode("utf-8")).hexdigest() # bind sender+nonce
def _xor(key: bytes, data: bytes) -> bytes:
"""Lab-only repeating XOR (same construction as pfc-lab-xor)."""
out = bytearray(len(data)) # output buffer
for i, b in enumerate(data): # each plaintext byte
out[i] = b ^ key[i % len(key)] # xor with cycling key
return bytes(out) # immutable
@dataclass
class PassthroughEnvelope:
"""On-wire object. Header-like fields stay JSON; body is ct."""
to: str # mailbox dest, in the clear for routing
from_lookup_id: str # sender cannot be recovered by broker
alg: str # npe | lab-xor | plain-lab
ct: str # hex ciphertext or empty if plain-lab
nonce: str # hex nonce used in lookup_id
error_token: str # public-key token placeholder for return path
def header(self) -> Dict[str, str]:
"""Routing/logging header (no payload)."""
return { # allowed on the untrusted broker
"to": self.to,
"from_lookup_id": self.from_lookup_id,
"alg": self.alg,
"nonce": self.nonce,
"error_token": self.error_token,
}
def wire(self) -> Dict[str, Any]:
"""Full JSON for NATS."""
w = self.header() # start with header
w["ct"] = self.ct # ciphertext only
w["v"] = 1 # version
w["mode"] = "passthrough" # dest in clear
return w # ready to publish
def seal(
*,
to: str,
sender: str,
body: Mapping[str, Any],
mode: str,
lab_key: bytes = b"",
) -> PassthroughEnvelope:
"""Encrypt body for recipient; return passthrough envelope."""
nonce = os.urandom(16) # fresh nonce
lid = lookup_id(sender, nonce) # opaque sender id
raw = json.dumps(body, sort_keys=True, separators=(",", ":")).encode() # body bytes
if mode == "plain-lab": # tests only
ct = raw.hex() # not secret
elif mode == "lab-xor": # lab PSK
if not lab_key: # require key
raise ValueError("lab_key required for lab-xor")
key = hashlib.sha256(lab_key).digest() # 32-byte key
ct = _xor(key, raw).hex() # hex ct
elif mode == "npe": # production — sidecar not in this module
raise NotImplementedError("npe: attach NPE sidecar; do not HPKE here")
else: # unknown
raise ValueError("unknown crypto.mode " + mode)
token = hashlib.sha256(nonce + b"error-token").hexdigest()[:32] # return-path token stub
return PassthroughEnvelope( # dest in clear
to=to,
from_lookup_id=lid,
alg=mode,
ct=ct,
nonce=nonce.hex(),
error_token=token,
)
def open_lab(env: PassthroughEnvelope, lab_key: bytes = b"") -> Dict[str, Any]:
"""Decrypt lab envelopes only. Production uses NPE sidecar."""
raw = bytes.fromhex(env.ct) # ct bytes
if env.alg == "plain-lab": # tests
return json.loads(raw.decode()) # json body
if env.alg == "lab-xor": # lab
key = hashlib.sha256(lab_key).digest() # same kdf
pt = _xor(key, raw) # decrypt
return json.loads(pt.decode()) # json
raise ValueError("open_lab cannot open alg " + env.alg) # npe not here

View file

@ -0,0 +1,117 @@
"""Network Error Bundle + system bounce report.
ct_sender: only the sender can read the failure status.
ct_system: system public key material for logging/recovery.
Never include the intended recipient's message plaintext.
Sender identifies the original with lookup_id only.
"""
from __future__ import annotations # annotations
import hashlib # lab box
import json # encode reports
import os # nonce
from dataclasses import dataclass # bundle
from typing import Any, Dict # maps
def _xor(key: bytes, data: bytes) -> bytes:
"""Lab box: XOR with SHA-256(key||nonce) — replaced by X25519 in production."""
out = bytearray(len(data)) # buffer
for i, b in enumerate(data): # each byte
out[i] = b ^ key[i % len(key)] # xor
return bytes(out) # done
def _lab_box(pubkey_material: bytes, nonce: bytes, obj: Dict[str, Any]) -> str:
"""Encrypt obj to a lab 'public' blob (hash of material+nonce as key)."""
raw = json.dumps(obj, sort_keys=True, separators=(",", ":")).encode() # bytes
key = hashlib.sha256(pubkey_material + nonce).digest() # 32 bytes
return _xor(key, raw).hex() # hex ct
def _lab_unbox(pubkey_material: bytes, nonce: bytes, ct_hex: str) -> Dict[str, Any]:
"""Decrypt lab box (same keying as _lab_box)."""
key = hashlib.sha256(pubkey_material + nonce).digest() # same kdf
pt = _xor(key, bytes.fromhex(ct_hex)) # decrypt
return json.loads(pt.decode()) # json
@dataclass
class NetworkErrorBundle:
"""Two ciphertexts: sender-only status + system bounce report."""
lookup_id: str # how sender finds the failed send
error_code: str # machine code (e.g. SM-DEAD-1)
dest_class: str # mailbox | service (not a mailbox id)
nonce: str # hex
ct_sender: str # hex, sender-only
ct_system: str # hex, system-key bounce report
def header(self) -> Dict[str, str]:
"""Logging header: codes only."""
return { # never put ct in logs
"lookup_id": self.lookup_id,
"error_code": self.error_code,
"dest_class": self.dest_class,
}
def wire(self) -> Dict[str, Any]:
"""NATS body for verae.sm.dead / verae.sm.error."""
return { # both ciphertexts
"v": 1,
"type": "network-error-bundle",
"lookup_id": self.lookup_id,
"error_code": self.error_code,
"dest_class": self.dest_class,
"nonce": self.nonce,
"ct_sender": self.ct_sender,
"ct_system": self.ct_system,
}
def build_bundle(
*,
lookup_id: str,
error_code: str,
dest_class: str,
sender_pub: bytes,
system_pub: bytes,
detail: str,
) -> NetworkErrorBundle:
"""Build bundle. detail is a status phrase, never recipient plaintext."""
nonce = os.urandom(16) # fresh
sender_obj = { # only sender opens this
"lookup_id": lookup_id,
"error_code": error_code,
"detail": detail,
}
system_obj = { # bounce report — no mail body
"lookup_id": lookup_id,
"error_code": error_code,
"dest_class": dest_class,
"respond": True,
}
return NetworkErrorBundle( # pack
lookup_id=lookup_id,
error_code=error_code,
dest_class=dest_class,
nonce=nonce.hex(),
ct_sender=_lab_box(sender_pub, nonce, sender_obj),
ct_system=_lab_box(system_pub, nonce, system_obj),
)
def open_sender(bundle: NetworkErrorBundle, sender_pub: bytes) -> Dict[str, Any]:
"""Sender reads their status ciphertext."""
return _lab_unbox(sender_pub, bytes.fromhex(bundle.nonce), bundle.ct_sender) # sender only
def open_system(bundle: NetworkErrorBundle, system_pub: bytes) -> Dict[str, Any]:
"""Ops logger reads bounce metadata (still no mail body)."""
return _lab_unbox(system_pub, bytes.fromhex(bundle.nonce), bundle.ct_system) # system only
def log_summary(bundle: NetworkErrorBundle) -> Dict[str, str]:
"""Central log line: codes only."""
return bundle.header() # no ciphertext, no body

View file

@ -0,0 +1,125 @@
"""Ed25519 signed configuration wrapper.
Unsigned files are rejected. After an admin console change, sign again and
append prev + new + diff to AdminHistory (Verae DataCube-shaped JSONL).
Config key ``crypto.mode`` selects npe | lab-xor | plain-lab (see System-Git-Sync).
"""
from __future__ import annotations # postpone evaluation of annotations
import json # canonical JSON for signatures
from dataclasses import dataclass # SignedConfig container
from pathlib import Path # filesystem paths
from typing import Any, Dict, Mapping, Optional # types for payload maps
from cryptography.exceptions import InvalidSignature # bad sig → reject
from cryptography.hazmat.primitives.asymmetric.ed25519 import ( # Ed25519
Ed25519PrivateKey,
Ed25519PublicKey,
)
from cryptography.hazmat.primitives.serialization import ( # PEM/raw helpers
Encoding,
NoEncryption,
PrivateFormat,
PublicFormat,
)
class UnsignedConfig(ValueError):
"""Raised when a config file has no wrapper or a bad signature."""
def canonical(payload: Mapping[str, Any]) -> bytes:
"""Stable bytes for signing: sorted keys, no extra spaces."""
return json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8") # one canonical form
@dataclass
class SignedConfig:
"""A payload plus Ed25519 signature metadata."""
payload: Dict[str, Any] # the configuration object
key_id: str # fingerprint of the public key
signature: bytes # 64-byte Ed25519 signature
def wrapper(self) -> Dict[str, Any]:
"""On-disk JSON object (hex signature)."""
return { # never put the private key here
"payload": json.loads(json.dumps(self.payload)),
"sig": {
"alg": "ed25519",
"key_id": self.key_id,
"signature": self.signature.hex(),
},
}
def dumps(self) -> str:
"""Pretty JSON for files (payload still signed as canonical)."""
return json.dumps(self.wrapper(), indent=2) + "\n" # human-readable file
def key_id_from_public(pub: Ed25519PublicKey) -> str:
"""First 16 hex chars of SHA-256(raw 32-byte public key)."""
import hashlib # local import keeps module top smaller
raw = pub.public_bytes(Encoding.Raw, PublicFormat.Raw) # 32 bytes
return hashlib.sha256(raw).hexdigest()[:16] # short id for configs
def generate_signing_key() -> Ed25519PrivateKey:
"""Create a new Ed25519 key (lab). Production: load from HSM."""
return Ed25519PrivateKey.generate() # OS CSPRNG
def sign(payload: Mapping[str, Any], priv: Ed25519PrivateKey) -> SignedConfig:
"""Sign canonical JSON of payload."""
pub = priv.public_key() # derive public
kid = key_id_from_public(pub) # id printed in wrapper
sig = priv.sign(canonical(payload)) # 64 bytes
return SignedConfig(payload=dict(payload), key_id=kid, signature=sig) # copy payload
def verify(wrapper: Mapping[str, Any], pub: Ed25519PublicKey) -> Dict[str, Any]:
"""Return payload if signature matches; else UnsignedConfig."""
sig = wrapper.get("sig") or {} # missing sig → fail
if sig.get("alg") != "ed25519": # only this alg this round
raise UnsignedConfig("unsupported sig.alg")
payload = wrapper.get("payload") # unsigned body
if not isinstance(payload, dict): # must be object
raise UnsignedConfig("payload must be an object")
try: # decode hex
signature = bytes.fromhex(str(sig.get("signature") or ""))
except ValueError as exc: # not hex
raise UnsignedConfig("signature not hex") from exc
if key_id_from_public(pub) != str(sig.get("key_id") or ""): # wrong key
raise UnsignedConfig("key_id mismatch")
try: # cryptography raises InvalidSignature
pub.verify(signature, canonical(payload))
except InvalidSignature as exc:
raise UnsignedConfig("bad signature") from exc
return dict(payload) # verified copy
def load_signed(path: Path, pub: Ed25519PublicKey) -> Dict[str, Any]:
"""Read a file and reject if unsigned or tampered."""
raw = Path(path).read_text() # entire file
wrapper = json.loads(raw) # must be JSON
if not isinstance(wrapper, dict) or "sig" not in wrapper: # no wrapper
raise UnsignedConfig("missing sig wrapper")
return verify(wrapper, pub) # verified payload
def save_signed(path: Path, signed: SignedConfig) -> None:
"""Write wrapper JSON (does not write the private key)."""
Path(path).write_text(signed.dumps()) # replace file atomically enough for lab
def pem_private(priv: Ed25519PrivateKey) -> bytes:
"""PKCS8 PEM for gitignored key files."""
return priv.private_bytes(Encoding.PEM, PrivateFormat.PKCS8, NoEncryption()) # lab only
def pem_public(pub: Ed25519PublicKey) -> bytes:
"""SubjectPublicKeyInfo PEM for distribution in signed payload."""
return pub.public_bytes(Encoding.PEM, PublicFormat.SubjectPublicKeyInfo) # public