TLS: fingerprint-confirm flow for self-signed gateway certs (issue #14)
CI / Gateway plugin tests (push) Successful in 5m29s
CI / Kotlin tests (android host + desktop) (push) Successful in 7m22s

docs/09 §9.4 promised a SSH-host-key-style fingerprint confirm on first
pair, but the app built a default OkHttpClient with no certificate
handling — self-signed gateway certs were simply rejected.

Build the flow:
- TlsPinning.kt: PinningTrustManager wraps the platform default trust
  manager; a rejected cert is accepted only when its SHA-256 fingerprint
  matches the user-confirmed pin, anything else fails with
  TlsFingerprintRequired (hostname verification still applies).
- SecureStore.pinnedCertFingerprint (Android EncryptedSharedPreferences +
  desktop settings.json), cleared on forget().
- GatewayClient: pinning socket factory on the shared client (all legs
  inherit it), new terminal State.TlsConfirmRequired, unwrap the nested
  TlsFingerprintRequired in connect loop / watchdog / SSE / poll /
  testHello.
- ConnectScreen: confirm dialog showing the fingerprint ("Confirm &
  pin" re-runs the connect); IrisApp routes TlsConfirmRequired there;
  ChatScreen + desktop tray handle the new state.
- Docs: §9.4 now describes the real flow (incl. SAN requirement), gap
  table item 6 → implemented, setup.md limitation note updated.
- Tests: TlsPinningTest (fingerprint vs openssl, pin accept/reject,
  live pin read, unwrap) + TlsPinningIntegrationTest (real TLS
  handshake: unpinned → confirm data, pinned → 200).

Live E2E verified on the phone: first pair against a self-signed
IRIS_HTTP_CERT gateway shows the dialog, confirm pins, chat works,
auto-reconnect after gateway restart uses the pin.
This commit is contained in:
ARIA committed 2026-08-24 21:30:42 +02:00
1 parent f90e40a3fc
commit 597a28050f
14 files changed
+634 -45

No files matched your search

+6 -3
View File
@@ -145,9 +145,12 @@ does nothing) — use ntfy (the default), or add Firebase later.
- **WSS:** set `IRIS_WS_CERT` / `IRIS_WS_KEY` (paths, in
`~/.hermes/.env`) and the server serves `wss://` instead of `ws://`.
> **Honest limitation:** the app has **no certificate pinning** yet, so
> self-signed certs won't work — remote access requires **CA-signed** WSS for
> now. Plain `ws://` on a trusted LAN (or inside Tailscale) stays the default.
> **Self-signed certs:** the app has a fingerprint-confirm flow (docs/09
> §9.4): on first pair it shows the gateway cert's SHA-256 fingerprint; once
> you confirm it, the cert is pinned in secure storage (like an SSH host
> key). The cert needs a SAN for the URL host. CA-signed certs work out of
> the box. Plain `ws://` on a trusted LAN (or inside Tailscale) stays the
> default.
## 6. Troubleshooting