ADR-036 — Local RFC 3161 Timestamp Authority for the air-gapped evidence spine

Context

EvidenceLocker seals audit bundles (Merkle-rooted, DSSE-manifested) and is meant to stamp each sealed bundle with a trusted timestamp token so an auditor can prove when the evidence existed, independently of the sealing service’s own clock. RFC 3161 is the interoperable standard for such tokens.

Two facts constrain the choice:

  1. Offline / air-gap first (repo invariant §1.5). Stella Ops is self-hosted and must run with no outbound internet. Public TSAs (DigiCert, Sectigo, freetsa.org, Sigstore’s timestamping) require reaching an external host at seal time — a hard non-starter for sovereign and air-gapped installs, and a supply-chain/prompt-injection surface.
  2. Today the deployed EvidenceLocker runs LocalHarness (EvidenceLocker__LocalHarness__Enabled=true, DOTNET_ENVIRONMENT=Development): in-memory golden fixture, Null RFC3161 TSA, Null timeline. Nothing real is ever sealed or timestamped (PLAN_20260704 finding B2/B3). Flipping the locker to production mode requires a reachable TSA, or the runtime validator refuses to boot (it will not silently fall back to the Null TSA outside the harness).

Decision

Ship a self-hosted, local RFC 3161 Timestamp Authority as a first-class compose service (tsa, alias tsa.stella-ops.local) and point EvidenceLocker’s timestamping client at it:

Implementation: the TSA image is built in-repo from an OpenSSL base using the openssl ts timestamping facility wrapped by a minimal HTTP handler. We do not pull an unvetted third-party TSA image into the default supply chain. OpenSSL is Apache-License-2.0 (OpenSSL 3.x), which is compatible with BUSL-1.1 (see the dependency gate below). The TSA’s signing certificate is operator-provisioned for production (an internal CA / sovereign HSM-backed cert); the dev compose ships a clearly-labelled non-secret dev TSA cert for the golden path, mirroring the EvidenceLocker capsule dev-seed convention.

The in-memory golden fixture and the Null TSA remain test-only (the LocalHarness path is kept for unit/integration tests and explicitly rejected at runtime outside the harness by EvidenceLockerRuntimeConfigurationValidator).

Consequences

Dependency / license gate (§2.6)

ComponentUpstream licenseBUSL-1.1 compatible?Notes
OpenSSL 3.x (openssl ts)Apache License 2.0Yes (permissive)Already relied on across the platform; no new copyleft obligation. TSA image built in-repo from an OpenSSL base, not a vendored third-party TSA binary.
Alpine Linux baseMIT + assorted permissiveYes (permissive)Minimal base for the in-repo image (devops/docker/tsa/); no copyleft obligation on the userland.
CPython 3 (python3 http wrapper)PSF LicenseYes (permissive)Minimal http.server wrapping openssl ts -reply; from the Alpine base, no vendored code.

Implementation status (2026-07-07, CLO-2b): built + wired + proven live. The image lives at devops/docker/tsa/ and listens on :318 (non-root uid 10001 via sysctls: net.ipv4.ip_unprivileged_port_start=0). Two dark product wirings were fixed to make production-mode sealing actually emit a token: the real Rfc3161TimestampAuthorityClient was never DI-registered (only the Null client), and EvidenceSignatureService could not load the durable Ed25519 seed (Algorithm=ED25519). A live POST /evidence/snapshot now returns a Merkle root plus a 4836-byte RFC 3161 token from CN=StellaOps DEV Timestamp Authority.

NOTICE.md and docs/legal/THIRD-PARTY-DEPENDENCIES.md record the OpenSSL-based TSA image. No cloud-managed TSA is introduced (honors the on-prem/sovereign-first invariant).

Alternatives considered