Files
Amber/gradle
greenart7c3 98dfca7095 Envelope-encrypt NIP-46 connection secrets at rest (GHSA-5fjp-ghh8-wch8)
The per-account Room database (amber_db_<npub>) stored two NIP-46 secret
values as cleartext TEXT columns: the bunker connection `secret` and the
`localKey` — the latter being a full Nostr private key. Anyone with access
to the app's internal storage (rooted device, privilege-escalating malware,
or a future bug exporting the DB) could recover these values and bypass the
Keystore protection the main account nsec enjoys (CWE-312).

Fix: envelope-encrypt both columns with the existing Keystore-backed AES-256-GCM
key (SecureCryptoHelper) before Room persistence, and decrypt on read, so every
existing consumer continues to see the plaintext values it already expects.

- SecureCryptoHelper: add non-suspend encryptBlocking/decryptBlocking so
  Migration.migrate() and getByKeySync() can call them without a runBlocking
  bridge; suspend variants now delegate to the blocking implementations.
- ApplicationEntityCrypto.kt (new): encryptForStorage/decryptFromStorage
  mappers + DecryptingPagingSource. Sentinel rule: empty values stay "" at
  rest (matches the WebDAV password idiom in LocalPreferences.kt:661-681),
  preserving `WHERE localKey != ''` enumeration in NotificationSubscription
  and the `localPubKey` derivation on empty localKey.
- ApplicationDao: split methods touching `secret`/`localKey` into Room-
  generated `*Raw` (encrypted columns) and default-method wrappers that
  apply the mappers. `getBySecret` rewritten to decrypt and filter in Kotlin
  (random GCM IV breaks `WHERE secret = :secret`).
- CachingApplicationDao: add delegating `*Raw` overrides so the decorator
  still instantiates; cache logic unchanged.
- AppDatabase: add MIGRATION_18_19 (in-place envelope-encrypt of existing
  plaintext rows via compiled statement + transaction; empty values stay
  empty). Bump @Database version to 19.
- Backup/restore: no changes — ApplicationBackup.buildPayload reads via the
  wrapped DAO (plaintext) and the JSON is already NIP-44 encrypted by the
  account key; restore goes through the wrapped insert (auto-encrypts).
- Tests: new androidTest ApplicationEntityCryptoTest covers round-trip,
  raw-column-ciphertext assertion, empty sentinel, localPubKey derivation,
  getAllWithLocalKey filter, getBySecret (hit/miss/empty),
  insertApplicationWithPermissions, getAll, and the MIGRATION_18_19 row
  re-encryption. Requires a device/emulator (AndroidKeyStore unavailable
  under JVM test).
- New room-testing androidTestImplementation dependency.

Verified: ktlintCheck, lint (no issues), testFreeDebugUnitTest (0 failures),
compileFreeDebugAndroidTestKotlin, assembleFreeDebug, assembleOfflineDebug,
and the offline merged manifest check (no INTERNET/ACCESS_NETWORK_STATE/
CHANGE_NETWORK_STATE permissions leaked).
2026-08-10 14:55:51 -03:00
..
2024-10-19 15:36:03 -03:00