Phase 1 server: TLS + token auth, session registry, EchoDriver, SSE with cursors

The whole pipe behind one Driver trait and a common event model:
spawn/list/delete sessions, message + question answering, append-only
JSONL transcripts whose sequence numbers are the phone's resume cursor
(surviving backend restarts), bearer-token middleware wrapping every
route including the fallback, wg0-only binding that fails closed, and
first-run token enrollment via a terminal QR.

Verified: cargo test (10), clippy clean, and curl end-to-end over pinned
TLS -- auth rejection, spawn, streamed SSE replay/resume, /question
round trip, restart continuing seq numbers, delete removing everything.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017xn8nHw1tw1R6PtiY1eEtw
This commit is contained in:
irisandClaude Fable 5 committed 2026-08-24 20:51:34 -04:00
1 parent a6ece28344
commit 967fc814ab
13 files changed
+3349

No files matched your search

+105
View File
@@ -0,0 +1,105 @@
#!/bin/sh
# Generates the self-signed dev CA and leaf certificate `server/` serves its
# TLS listener with. Run once before the first `cargo run`; the server exits
# with a clear message if `certs/` is missing.
#
# Same scheme as ../local-updater's: nothing on a device trusts this
# automatically -- the app embeds the CA certificate verbatim and pins to it
# (`PinnedCert.kt`), rather than relying on the device's system trust store.
# This server's API *is* remote code execution (it spawns AI sessions on
# request), so a MITM on it would be as bad as it gets -- hence pinning.
#
# The CA is idempotent -- skipped if `certs/ca.pem` already exists, so
# re-running this doesn't invalidate the certificate the installed app has
# pinned against without a reason to. The leaf is cheap and reissued on
# every run (still signed by that same, unchanged CA), so adding another SAN
# entry only means rerunning this script, not touching anything pinned.
#
# The leaf's SANs must cover every address a device reaches this server at.
# In production that is exactly one: the backend's WireGuard address, which
# the phone uses from everywhere (see PLAN.md's off-network section).
# Override with SERVER_IP=... if your wg0 address differs.
#
# Outputs into `certs/` (gitignored -- private key material, and the whole
# thing is trivially regeneratable anyway):
# ca.pem the CA certificate (not its private key) -- what the
# app embeds and pins against.
# ca-key.pem the CA's private key -- only this script needs it, to
# sign the leaf below. Never shipped anywhere.
# leaf.pem the server's own certificate (CA-signed), presented on
# every TLS handshake.
# leaf-key.pem the leaf's private key -- what the server loads to
# terminate TLS.
# ca-sha256.txt the CA certificate's SPKI SHA-256 fingerprint, printed
# below too. Informational -- the app embeds the whole
# `ca.pem`, not this digest.
# leaf-sha256.txt the leaf's SPKI SHA-256 fingerprint. Informational --
# nothing pins the leaf; the app pins the CA and
# validates the chain.
set -eu
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
CERTS_DIR="$SCRIPT_DIR/certs"
# The backend's WireGuard address -- the one address the phone ever dials in
# production (PLAN.md: single-path addressing, no home/away distinction).
SERVER_IP="${SERVER_IP:-10.66.0.1}"
# Also covered so development before the tunnel exists can complete a real
# handshake against the same pinned CA:
# 127.0.0.1 curl from the machine itself, and tests binding loopback
# 10.0.2.2 the Android emulator's alias for the host's loopback
# LAN_IP a real phone on the same LAN, pre-WireGuard
LOOPBACK_IP="127.0.0.1"
EMULATOR_HOST_IP="10.0.2.2"
LAN_IP="${LAN_IP:-192.168.1.168}"
mkdir -p "$CERTS_DIR"
cd "$CERTS_DIR"
if [ -f ca.pem ]; then
echo "==> ca.pem already exists, reusing existing CA."
else
echo "==> Generating CA key + self-signed CA certificate"
openssl ecparam -name prime256v1 -genkey -noout -out ca-key.pem
openssl req -new -x509 -key ca-key.pem -out ca.pem -days 3650 \
-subj "/O=ai-app dev/CN=ai-app dev CA"
fi
echo "==> Generating leaf key + CSR for $SERVER_IP (+ dev addresses)"
openssl ecparam -name prime256v1 -genkey -noout -out leaf-key.pem
openssl req -new -key leaf-key.pem -out leaf.csr \
-subj "/O=ai-app dev/CN=$SERVER_IP"
echo "==> Signing leaf certificate with the dev CA"
cat > leaf.ext <<EOF
subjectAltName = IP:$SERVER_IP,IP:$LOOPBACK_IP,IP:$EMULATOR_HOST_IP,IP:$LAN_IP
EOF
openssl x509 -req -in leaf.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial \
-out leaf.pem -days 3650 -extfile leaf.ext
rm -f leaf.csr leaf.ext ca.srl
echo "==> Computing certificate fingerprints (SPKI SHA-256)"
CA_SHA256=$(openssl x509 -in ca.pem -pubkey -noout \
| openssl pkey -pubin -outform der \
| openssl dgst -sha256 -binary \
| openssl base64)
echo "$CA_SHA256" > ca-sha256.txt
LEAF_SHA256=$(openssl x509 -in leaf.pem -pubkey -noout \
| openssl pkey -pubin -outform der \
| openssl dgst -sha256 -binary \
| openssl base64)
echo "$LEAF_SHA256" > leaf-sha256.txt
echo
echo "==> Done."
echo " CA fingerprint (base64): $CA_SHA256"
echo " Leaf fingerprint (base64): $LEAF_SHA256"
echo
echo " Only relevant if the CA was regenerated just now (i.e. certs/ca.pem"
echo " did not already exist): the app embeds PINNED_CA_PEM and needs the"
echo " new certs/ca.pem contents pasted in, or it silently stops being able"
echo " to reach this server. The app installs via Local Updater, so"
echo " recovery is a reinstall through that -- but it's still a one-way"
echo " door for the installed copy."
echo
echo " app/androidApp/src/main/kotlin/com/example/aiapp/PinnedCert.kt"