Keep state and keys out of the shared repo
The dev VM is treated as untrusted, and the repo is a read-write virtiofs mount shared with the backend host -- so a CA private key sitting in it is a key that machine can sign with, and a leaf signed by this CA is one the phone's pinned app accepts without question. Pinning against a CA the attacker holds is no pinning at all. So certificates are now generated on the machine that serves them, into $XDG_CONFIG_HOME/ai-app/certs at 0700 with 0600 keys (AI_APP_CERTS overrides), and config.json and session transcripts move to the XDG config and data directories. Transcripts move for a plainer reason than the keys: they are whole conversations, and they were world-readable at 0644. Two smaller things fall out. The host and VM stop sharing one config, which had already put a test token on the production backend. And state stops living where `git clean -xdf` would take the enrollment and every transcript with it. State that predates the move is still read from the repo, with a warning naming where to move it, so an existing install keeps working rather than silently coming up on an empty config -- the precedence is covered by a test, since picking the wrong file would otherwise be silent. Verified: 31 tests, clippy clean; the certificate script writing 0700/0600 into an overridden directory; and the server logging the fallback and serving from it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017xn8nHw1tw1R6PtiY1eEtw
This commit is contained in:
1 parent
fff1fb49e8
commit
d2d2832ec8
8 files changed
+248
-37
No files matched your search
+38
-9
@@ -1,7 +1,8 @@
|
||||
#!/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.
|
||||
# TLS listener with. Run it once, ON THE MACHINE THAT RUNS THE BACKEND, before
|
||||
# the first start; the server exits with a clear message if the certificates
|
||||
# are 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
|
||||
@@ -9,6 +10,25 @@
|
||||
# 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.
|
||||
#
|
||||
# WHERE THE KEYS LIVE, AND WHY NOT IN THE REPO
|
||||
#
|
||||
# Output goes to $XDG_CONFIG_HOME/ai-app/certs (0700), deliberately *not*
|
||||
# beside this script. The repo is a virtiofs mount shared with the dev VM,
|
||||
# and that VM is treated as untrusted -- a machine that isn't malicious but
|
||||
# could become so. A CA private key it can read is a CA private key it can
|
||||
# sign with, and a leaf signed by this CA is one the phone's pinned app
|
||||
# accepts without question. Keeping the key off the shared mount is what
|
||||
# makes pinning mean anything.
|
||||
#
|
||||
# The same reasoning says the CA key doesn't belong on the backend either,
|
||||
# strictly: the server only ever reads leaf.pem and leaf-key.pem, and the CA
|
||||
# key is needed solely to reissue a leaf. Moving ca-key.pem somewhere offline
|
||||
# once the setup is stable costs nothing but having it to hand at reissue.
|
||||
#
|
||||
# Running this inside the VM is fine and expected for emulator work -- it
|
||||
# just produces a *different*, throwaway CA there. Never install a build
|
||||
# pinning that dev CA on the real phone.
|
||||
#
|
||||
# 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
|
||||
@@ -40,7 +60,7 @@
|
||||
set -eu
|
||||
|
||||
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
CERTS_DIR="$SCRIPT_DIR/certs"
|
||||
CERTS_DIR="${AI_APP_CERTS:-${XDG_CONFIG_HOME:-$HOME/.config}/ai-app/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}"
|
||||
@@ -53,8 +73,13 @@ LOOPBACK_IP="127.0.0.1"
|
||||
EMULATOR_HOST_IP="10.0.2.2"
|
||||
LAN_IP="${LAN_IP:-192.168.1.168}"
|
||||
|
||||
# Private key material: owner-only from the moment it exists, rather than
|
||||
# created world-readable and chmod'ed a beat later.
|
||||
umask 077
|
||||
mkdir -p "$CERTS_DIR"
|
||||
chmod 700 "$CERTS_DIR"
|
||||
cd "$CERTS_DIR"
|
||||
echo "==> Writing certificates to $CERTS_DIR"
|
||||
|
||||
if [ -f ca.pem ]; then
|
||||
echo "==> ca.pem already exists, reusing existing CA."
|
||||
@@ -103,11 +128,15 @@ 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 " Only relevant if the CA was regenerated just now (i.e. ca.pem did"
|
||||
echo " not already exist): the app embeds PINNED_CA_PEM and needs the new"
|
||||
echo " $CERTS_DIR/ca.pem contents pasted in, or it silently"
|
||||
echo " stops being able to reach this server. The app installs via Local"
|
||||
echo " Updater, so recovery is a reinstall through that -- but it's still"
|
||||
echo " a one-way door for the installed copy."
|
||||
echo
|
||||
echo " app/androidApp/src/main/kotlin/com/example/aiapp/PinnedCert.kt"
|
||||
echo
|
||||
echo " Only ca.pem is meant to leave this machine. Copy it by hand; do not"
|
||||
echo " put the certs directory back in the repo, which is shared with the"
|
||||
echo " VM (see this script's header)."
|
||||
Reference in new issue
Block a user