feat: implement end-to-end encryption for room keys with secure storage and migration support

This commit is contained in:
HueByte
2026-07-16 18:47:28 +02:00
parent b6c01dab15
commit e439c8ae72
13 changed files with 531 additions and 26 deletions
+1
View File
@@ -4,6 +4,7 @@ Release history for EchoHub.
## Releases
- [v0.2.14](v0.2.14.md) - E2E Room Unlock Fixes, Encrypted Key Cache & IRC Gateway Polish
- [v0.2.13](v0.2.13.md) - Chat Visual Overhaul, Auto-Join All Channels & Persistent Read Positions
- [v0.2.12](v0.2.12.md) - End-to-End Encrypted Channels, IRC Channel Keys, Image Save & Ctrl+W Crash Fix
- [v0.2.11](v0.2.11.md) - EchoHubSpace Auth, Live Directory Updates & Server Browser Metadata
+2
View File
@@ -1,5 +1,7 @@
- name: Overview
href: index.md
- name: v0.2.14
href: v0.2.14.md
- name: v0.2.13
href: v0.2.13.md
- name: v0.2.12
+20
View File
@@ -0,0 +1,20 @@
# v0.2.14
A reliability and security pass over end-to-end encrypted rooms: locked channels now offer the passphrase prompt instead of dead-ending on the "rejoin to unlock" placeholder, the client can no longer be tricked into sending plaintext into an encrypted room, and cached room keys are encrypted at rest instead of sitting in the config as base64. Plus a set of IRC gateway fixes — decrypted image previews, display-name plumbing, and user/channel indicators.
## New Features
- **Room keys encrypted at rest** — the per-channel room keys cached so you don't retype a passphrase every launch are no longer stored as plain base64 in `config.json`. On Windows they're protected with DPAPI (current-user scope); on Linux/macOS with AES-GCM under a per-user key file created with `0600` permissions next to the config. Existing plain entries migrate to the encrypted format automatically on first load. The passphrase itself is never stored in any form.
- **`[irc]` tag in the users panel** — users online only through the IRC gateway are tagged `[irc]`, useful context since IRC clients lack encryption, attachments, and profiles. Someone also running the TUI shows untagged.
- **`~` marker for private channels** — the channel list now marks private (unlisted) channels with a trailing `~`, alongside the existing `*` for password-protected ones (`#room*~` when both apply).
## Bug Fixes
- **Locked encrypted channels now prompt for the passphrase.** Auto-joining your channels at connect silently entered end-to-end encrypted rooms you're a member of without running the unlock flow — on a new device (or after a cancelled prompt) the room showed only `[encrypted — rejoin this channel with its passphrase to unlock]` placeholders, and only a manual `/join` recovered it. Selecting the channel now offers the passphrase prompt; entering it unlocks history and live messages in place. Cancelling is remembered for the session so reselecting the channel doesn't nag — `/join` or trying to send always re-offers the prompt.
- **A stale cached room key no longer beats a fresh one.** If an encrypted channel was deleted and recreated under the same name, a client that still had the old key cached kept encrypting messages nobody else could read. Typing the passphrase on join now always adopts the key from the server's current envelope, replacing the stale cache.
- **IRC clients no longer get flooded with ciphertext for image messages.** The gateway forwarded image ASCII previews without stripping transport encryption, spamming IRC clients with one enormous `$ENC$v1$…` line per image. Previews are now decrypted before formatting, and any that still can't be read (e.g. end-to-end room ciphertext the server cannot decrypt) are skipped in favor of the plain `[Image: name] url` line.
- **Display names now show on chat messages.** Messages only carried the sender's username, so a configured display name appeared in the user list but not on the messages themselves. Live messages, history, and attachment messages now all carry it; mention and profile lookups stay keyed to the username.
## Security
- **No plaintext can leak into an encrypted room.** Previously, a client without the room key silently sent unencrypted text into an end-to-end encrypted channel (and other members saw it as a normal message, none the wiser it went over the wire readable by the server). All send paths — typed messages, staged file attachments, and URL sends — are now blocked while a room is locked: the client offers the unlock prompt, keeps staged files in the tray, and refuses to transmit until the key is present, with a hard guard at the connection layer as backstop.