Skip to content

In-app encrypted connections: TLS with certificate pairing (Syncthing model) — design #248

Description

@jozef2svrcek

Design issue — future work, not scheduled. Follows the #247 LAN phase; analysis done 2026-08-07.

Problem

#247 gives remote clients plain HTTP + a shared token, honestly scoped to trusted LANs. First-class encrypted connections built into LPDO (no external tools) require solving two problems, and the second one dominates:

  1. Transport crypto — easy (rustls).
  2. Trust bootstrapping + webview delivery — the real work:
    • A LAN server (IP address, no domain) cannot obtain a publicly-trusted certificate (no CA issues for IPs/.local), so it must be self-signed.
    • The Tauri webview (WebView2/WebKit) enforces browser TLS rules: self-signed/pinned certs are rejected with no clean per-app override. Any design that ends in danger_accept_invalid_certs is encryption theater and must be rejected outright.

Proposed architecture (the Syncthing model)

  1. Server identity = certificate fingerprint. On first network bind, generate a self-signed cert (rcgen), serve HTTPS (axum-server + rustls). The fingerprint is the server's stable identity, like an SSH host key / Syncthing device ID.
  2. Pairing (TOFU + human verification). On first connect the client shows the fingerprint it sees; the server shows its own (console/log, or in the local GUI); user confirms they match — Bluetooth-pairing style. Pinned thereafter. A changed fingerprint is a hard failure with a loud warning, never a click-through.
  3. Webview delivery via a loopback proxy inside the client. A tiny native (Rust, in-process) proxy: webview + EventSource talk plain HTTP to 127.0.0.1:<ephemeral>; the proxy terminates TLS to the remote server using a custom rustls verifier that checks exactly the pinned fingerprint (pinning — NOT accept-anything). Native calls (upload GUI: large PGN import fails — 'File too large (max 100 MB)' (Megabase); path-first + streamed upload #154, backup CLI: import-pgn / backup fail against the hardened system daemon (ProtectHome/PrivateTmp) for files in $HOME or /tmp #121) and the CLI use the same pinned-reqwest logic directly.
  4. Auth upgrades in the same pass: keep the token, bind it to the TLS channel (channel binding — a stolen token is useless off-channel); store it in the OS keychain (Tauri keyring) instead of localStorage; failed-attempt rate limiting; optionally per-client tokens so one device can be revoked; tighten the permissive CORS when serving remotely.

Scope warning

The proxy + custom verifier + pairing UX is a larger chunk than all of #247 phase 1, and the security-sensitive parts (the rustls verifier above all) need adversarial review, not just tests. The pairing UX is where the design succeeds or fails — schedule design time for it, not just implementation.

Explicitly rejected alternatives (from the analysis)

  • danger_accept_invalid_certs anywhere — worse than the current honest no-TLS warning.
  • mTLS with user-managed client certs — cert distribution UX is not viable for this audience.
  • Hand-rolled internet-facing auth on the single shared token — internet exposure needs accounts/lockouts/audit or (better) stays on overlays.

Non-blocking status

Encrypted access is already documented and recommended via Tailscale/WireGuard or an SSH tunnel (docs/remote-server.md, "Encrypted connections") — the SSH variant even keeps the server loopback-only with no open port at all. This issue is for making encryption first-class in-app; it becomes worth its cost roughly when remote access turns into a headline feature (e.g. the #210 portable-client idea).

Acceptance sketch

  • Fresh pair of machines: pairing flow completes with fingerprint verification; traffic is TLS; a MITM (cert swap) is rejected with the loud-warning UX.
  • Token alone (no pairing) cannot authenticate; token replay off-channel fails (channel binding).
  • Loopback deployments remain zero-config and unencrypted-localhost as today.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions