Rework data-at-rest encryption and live read receipts #6

Merged
alex merged 1 commit from encryption-rework into main 2026-05-16 09:32:37 -04:00
Owner

Encryption is now opt-in and decoupled from app lock. Default mode keeps
the SQLCipher key auto-managed in the OS keystore. The opt-in tied mode
wraps the DB key with a KEK derived from the app-lock secret; cold start
prompts for the credential, derives the KEK, opens the DB, and caches it
in memory.

The cold-start unlock now uses the regular PIN/passphrase/pattern lock
screen rather than a bespoke widget, so there is one consistent UI for
both the warm UI gate and the cold DB-unlock. A small Reset link appears
only on the cold-start path; tapping it deletes the DB files and FSS
metadata, then reinitialises a fresh client so the user lands on login.

The KEK is shredded on a configurable timer (default 8h, options 0 to 24h
or until process dies). Backgrounding starts the timer; resuming cancels
it. Biometric auto-prompts when the lock screen becomes visible and the
DB is open (auto-key mode, or tied mode with KEK still cached). The
biometric button is hidden when the DB is locked because biometric alone
cannot derive the KEK.

EncryptedDbUnlockScreen remains as a fallback for the stranded state
(app lock disarmed but DB still wrapped). It derives the KEK from the
stored salt, unwraps to raw, and continues without prompting for app
lock setup.

builder.dart no longer deletes the DB file on every exception. Wrong-key
SQLCipher errors are now surfaced as DbCipherKeyMismatch so the unlock
screen appears with the file intact. Only genuine corruption falls
through to the delete path.

reconcileEncryptionState runs at startup and self-heals tied/wrapped/raw
inconsistencies without touching the DB file.

Optional foreground service (Settings > Security > App lock > Keep app
warm in background) requests battery-optimisation exemption and runs a
low-priority notification so Android keeps the process alive across
screen-off events instead of cold-starting on every wake.

Read receipts ride on sync ephemerals, which Timeline.onUpdate ignores.
Subscribing to client.onSync filtered to the current room makes the
checkmarks update live.

Unread badge now respects push rules: dontNotify rooms contribute zero,
mentionsOnly rooms contribute highlightCount instead of notificationCount.
The chat back-button badge uses notification count to match the chat
list nav rail.

Encryption is now opt-in and decoupled from app lock. Default mode keeps the SQLCipher key auto-managed in the OS keystore. The opt-in tied mode wraps the DB key with a KEK derived from the app-lock secret; cold start prompts for the credential, derives the KEK, opens the DB, and caches it in memory. The cold-start unlock now uses the regular PIN/passphrase/pattern lock screen rather than a bespoke widget, so there is one consistent UI for both the warm UI gate and the cold DB-unlock. A small Reset link appears only on the cold-start path; tapping it deletes the DB files and FSS metadata, then reinitialises a fresh client so the user lands on login. The KEK is shredded on a configurable timer (default 8h, options 0 to 24h or until process dies). Backgrounding starts the timer; resuming cancels it. Biometric auto-prompts when the lock screen becomes visible and the DB is open (auto-key mode, or tied mode with KEK still cached). The biometric button is hidden when the DB is locked because biometric alone cannot derive the KEK. EncryptedDbUnlockScreen remains as a fallback for the stranded state (app lock disarmed but DB still wrapped). It derives the KEK from the stored salt, unwraps to raw, and continues without prompting for app lock setup. builder.dart no longer deletes the DB file on every exception. Wrong-key SQLCipher errors are now surfaced as DbCipherKeyMismatch so the unlock screen appears with the file intact. Only genuine corruption falls through to the delete path. reconcileEncryptionState runs at startup and self-heals tied/wrapped/raw inconsistencies without touching the DB file. Optional foreground service (Settings > Security > App lock > Keep app warm in background) requests battery-optimisation exemption and runs a low-priority notification so Android keeps the process alive across screen-off events instead of cold-starting on every wake. Read receipts ride on sync ephemerals, which Timeline.onUpdate ignores. Subscribing to client.onSync filtered to the current room makes the checkmarks update live. Unread badge now respects push rules: dontNotify rooms contribute zero, mentionsOnly rooms contribute highlightCount instead of notificationCount. The chat back-button badge uses notification count to match the chat list nav rail.
Rework data-at-rest encryption and live read receipts
Some checks failed
Pull Request Workflow / code_tests (pull_request) Failing after 1m25s
Pull Request Workflow / build_debug_apk (pull_request) Has been skipped
Pull Request Workflow / build_debug_web (pull_request) Has been skipped
Pull Request Workflow / build_debug_linux (pull_request) Has been skipped
10332fea39
Encryption is now opt-in and decoupled from app lock. Default mode keeps
the SQLCipher key auto-managed in the OS keystore. The opt-in tied mode
wraps the DB key with a KEK derived from the app-lock secret; cold start
prompts for the credential, derives the KEK, opens the DB, and caches it
in memory.

The cold-start unlock now uses the regular PIN/passphrase/pattern lock
screen rather than a bespoke widget, so there is one consistent UI for
both the warm UI gate and the cold DB-unlock. A small Reset link appears
only on the cold-start path; tapping it deletes the DB files and FSS
metadata, then reinitialises a fresh client so the user lands on login.

The KEK is shredded on a configurable timer (default 8h, options 0 to 24h
or until process dies). Backgrounding starts the timer; resuming cancels
it. Biometric auto-prompts when the lock screen becomes visible and the
DB is open (auto-key mode, or tied mode with KEK still cached). The
biometric button is hidden when the DB is locked because biometric alone
cannot derive the KEK.

EncryptedDbUnlockScreen remains as a fallback for the stranded state
(app lock disarmed but DB still wrapped). It derives the KEK from the
stored salt, unwraps to raw, and continues without prompting for app
lock setup.

builder.dart no longer deletes the DB file on every exception. Wrong-key
SQLCipher errors are now surfaced as DbCipherKeyMismatch so the unlock
screen appears with the file intact. Only genuine corruption falls
through to the delete path.

reconcileEncryptionState runs at startup and self-heals tied/wrapped/raw
inconsistencies without touching the DB file.

Optional foreground service (Settings > Security > App lock > Keep app
warm in background) requests battery-optimisation exemption and runs a
low-priority notification so Android keeps the process alive across
screen-off events instead of cold-starting on every wake.

Read receipts ride on sync ephemerals, which Timeline.onUpdate ignores.
Subscribing to client.onSync filtered to the current room makes the
checkmarks update live.

Unread badge now respects push rules: dontNotify rooms contribute zero,
mentionsOnly rooms contribute highlightCount instead of notificationCount.
The chat back-button badge uses notification count to match the chat
list nav rail.
alex force-pushed encryption-rework from 10332fea39
Some checks failed
Pull Request Workflow / code_tests (pull_request) Failing after 1m25s
Pull Request Workflow / build_debug_apk (pull_request) Has been skipped
Pull Request Workflow / build_debug_web (pull_request) Has been skipped
Pull Request Workflow / build_debug_linux (pull_request) Has been skipped
to a617df94d2
Some checks failed
Pull Request Workflow / code_tests (pull_request) Failing after 1m24s
Pull Request Workflow / build_debug_apk (pull_request) Has been skipped
Pull Request Workflow / build_debug_web (pull_request) Has been skipped
Pull Request Workflow / build_debug_linux (pull_request) Has been skipped
2026-05-16 09:32:17 -04:00
Compare
alex merged commit eb546c42aa into main 2026-05-16 09:32:37 -04:00
alex deleted branch encryption-rework 2026-05-16 09:32:37 -04:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
alex/Mochi!6
No description provided.