App embeds the CA of the machine that builds it

PinnedCert.kt no longer carries a pasted certificate. The build reads
$XDG_CONFIG_HOME/ai-app/certs/ca.pem (AI_APP_CA overrides) and generates
the constant, so the trust anchor follows the build machine: an APK built
on the backend host pins that host, and one built in the dev VM pins the
VM's throwaway CA and is good only for its emulator.

That removes the reason to add a second trust anchor for development --
there is nothing to add and then forget to remove -- and it means the
private key never has to exist near this repo, which the VM can write.
Regenerating a CA now needs a rebuild instead of a paste, so a stale
constant can't quietly disagree with the server.

build-apk.sh is the missing counterpart to run-android.sh: it produces
the APK to install through Local Updater and touches no emulator. It
finds the SDK from ANDROID_HOME/ANDROID_SDK_ROOT before falling back to
~/Android/Sdk, since the host doesn't share the VM's layout, and prints
the fingerprint of the CA being pinned so a wrong one is visible there
rather than as a handshake failure on the phone.

Verified end to end on the emulator against a server using a freshly
generated CA -- which is how the first attempt was caught: the generated
constant began with a newline, so CertificateFactory lost the "-----BEGIN"
sniff, tried DER, and failed at runtime with an ASN.1 decode error.

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-25 04:16:15 -04:00
1 parent ce349af414
commit eeb2dde4ab
6 files changed
+209 -37

No files matched your search

+10
View File
@@ -262,6 +262,16 @@ complete path out of everything spawning one created.
- TLS with a self-signed CA, pinned in the app — `gen-dev-cert.sh` and
`PinnedCert.kt` copied from local-updater, same idempotent-CA/reissued-leaf
scheme, same one-way-door caveat about regenerating the CA.
- Unlike local-updater, the pinned CA is **not a constant in the source**:
the build reads `$XDG_CONFIG_HOME/ai-app/certs/ca.pem` from the machine
doing the build and generates the constant (`generatePinnedCert` in
`app/androidApp/build.gradle.kts`; `AI_APP_CA` overrides). Decided
2026-08-25, and it does three things at once — the trust anchor follows
the build machine, so an APK built on the backend host pins that host
and one built in the dev VM pins the VM's throwaway CA and is only good
for its emulator; there is no second anchor to add for development and
forget to remove; and regenerating a CA needs a rebuild rather than a
paste, so a stale constant can't quietly disagree with the server.
- **The dev VM is untrusted** (decided 2026-08-25): a machine that isn't
malicious but could become so. It matters because the repo is a
read-write virtiofs mount shared between the VM and the backend host, so