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:
1 parent
a6ece28344
commit
967fc814ab
13 files changed
+3349
No files matched your search
Executable
+105
@@ -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"
|
||||
Reference in new issue
Block a user