feat: fail-closed DatabaseKeyStore TurboModule#7418
Conversation
…dule - iOS: new `DatabaseKeyStore.swift` Keychain helper with `AfterFirstUnlockThisDeviceOnly` accessibility and dedicated `chat.rocket.reactnative.dbkeys` service; distinguishes a true not-found (errSecItemNotFound → null, no error) from any other Keychain failure (→ KEYCHAIN_READ_ERROR reject), closing the silent-overwrite hole. - iOS: new `DatabaseKeyStore.mm` TurboModule (`RCT_EXPORT_MODULE(DatabaseKeyStoreModule)` + `getTurboModule:`) replacing the legacy `RCTBridgeModule`/`RCT_EXPORT_METHOD` module that did not vend under bridgeless New Architecture. - Android: self-contained AES-GCM + AndroidKeyStore implementation in a dedicated `RCDatabaseKeyStore` SharedPreferences file (separate from MMKV); `getItem` rejects on decrypt failure instead of resolving null, closing the same silent-overwrite hole; adds package-visible `getItemInternal`/`setItemInternal`/`removeItemInternal` for Encryption.java native callers. - Xcode: registers DatabaseKeyStore.swift (both main-app and NotificationService targets) and DatabaseKeyStore.mm (main-app); removes DatabaseKeyStoreModule.mm. - Android: registers DatabaseKeyStoreTurboPackage in MainApplication.kt. - TS codegen spec: registration name corrected to `DatabaseKeyStoreModule` (was `DatabaseKeyStore`, which could collide with the Swift helper class under bridgeless). Claude-Session: https://claude.ai/code/session_01Syt2ncLcXKw1oL1tCeFmko
Thin wrapper over the DatabaseKeyStore TurboModule: - Storage key format `db_key_v1:<dbName>`. - Returns the stored 64-hex-char key if present. - Mints a fresh `randomKey(32)` (64 hex chars) on a genuine not-found (null). - Propagates KEYCHAIN_READ_ERROR / KEYSTORE_READ_ERROR rejections without minting — prevents a transient read failure from silently replacing an existing encrypted database's key (silent data loss). - Treats `undefined` as a contract violation (throws) rather than falling through to the mint path. Tests cover: existing key round-trip, fresh-mint path, read-error propagation, null-as-not-found, and undefined-as-violation. Claude-Session: https://claude.ai/code/session_01Syt2ncLcXKw1oL1tCeFmko
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
🚧 Files skipped from review as they are similar to previous changes (1)
📜 Recent review details🧰 Additional context used📓 Path-based instructions (5)**/*.{js,ts,jsx,tsx}📄 CodeRabbit inference engine (AGENTS.md)
Files:
**/*.{ts,tsx}📄 CodeRabbit inference engine (AGENTS.md)
Files:
**/*.{js,jsx,ts,tsx,json}📄 CodeRabbit inference engine (CLAUDE.md)
Files:
**/*.{js,jsx,ts,tsx}📄 CodeRabbit inference engine (CLAUDE.md)
Files:
app/lib/encryption/**/*.{ts,tsx}📄 CodeRabbit inference engine (CLAUDE.md)
Files:
🧠 Learnings (1)📚 Learning: 2026-04-30T17:07:51.020ZApplied to files:
🪛 ast-grep (0.43.0)android/app/src/main/java/chat/rocket/reactnative/storage/DatabaseKeyStoreModule.java[warning] 135-135: Triple DES (3DES or DESede) is considered deprecated. AES is the recommended cipher. Upgrade to use AES. (desede-is-deprecated-java) [warning] 135-135: Use of AES with ECB mode detected. ECB doesn't provide message confidentiality and is not semantically secure so should not be used. Instead, use a strong, secure cipher: Cipher.getInstance("AES/CBC/PKCS7PADDING"). See https://owasp.org/www-community/Using_the_Java_Cryptographic_Extensions for more information. (use-of-aes-ecb-java) 🔇 Additional comments (11)
WalkthroughIntroduces a ChangesSecure Database Key Store TurboModule
Sequence Diagram(s)sequenceDiagram
rect rgba(70, 130, 180, 0.5)
Note over App,NativeDatabaseKeyStore: Key retrieval or first-time minting
end
participant App
participant getOrCreateDatabaseKey
participant NativeDatabaseKeyStore
participant PlatformStore
App->>getOrCreateDatabaseKey: getOrCreateDatabaseKey("mydb")
getOrCreateDatabaseKey->>NativeDatabaseKeyStore: getItem("db_key_v1:mydb")
NativeDatabaseKeyStore->>PlatformStore: read key (Keychain / Keystore+SharedPrefs)
PlatformStore-->>NativeDatabaseKeyStore: stored value or null
NativeDatabaseKeyStore-->>getOrCreateDatabaseKey: string | null
alt Key exists
getOrCreateDatabaseKey-->>App: existing 64-char hex key
else Not found (null)
getOrCreateDatabaseKey->>getOrCreateDatabaseKey: randomKey(32) → new 64-char hex
getOrCreateDatabaseKey->>NativeDatabaseKeyStore: setItem("db_key_v1:mydb", newKey)
NativeDatabaseKeyStore->>PlatformStore: write encrypted key
getOrCreateDatabaseKey-->>App: new 64-char hex key
else Read error
getOrCreateDatabaseKey-->>App: throws (fail closed)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Warning Review ran into problems🔥 ProblemsErrors were encountered while retrieving linked issues. Errors (3)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (1)
app/lib/encryption/keyStore.ts (1)
7-12: ⚡ Quick winAnnotate the native-module narrowing helper.
getNativeModuleis the boundary that narrowsSpec | nulltoSpec; make that return type explicit. As per coding guidelines,**/*.{ts,tsx}should “add explicit type annotations to function parameters and return types.”Proposed fix
-import NativeDatabaseKeyStore from '../native/NativeDatabaseKeyStore'; +import NativeDatabaseKeyStore, { type Spec } from '../native/NativeDatabaseKeyStore'; const KEY_PREFIX = 'db_key_v1:'; -function getNativeModule() { +function getNativeModule(): Spec { if (!NativeDatabaseKeyStore) { throw new Error('DatabaseKeyStore native module not found — ensure the module is linked and the app is rebuilt'); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/lib/encryption/keyStore.ts` around lines 7 - 12, Add an explicit return type annotation to the getNativeModule function to make the type narrowing boundary clear. The function currently lacks a return type annotation and should be updated to explicitly declare that it returns the Spec type (the type of NativeDatabaseKeyStore). This makes the type guard contract explicit and aligns with the coding guideline requiring explicit type annotations on function return types.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@android/app/src/main/java/chat/rocket/reactnative/storage/DatabaseKeyStoreModule.java`:
- Around line 74-81: In the removeItem method, the catch block incorrectly calls
promise.resolve(null) when an exception occurs, which hides the failure from the
caller. Replace the promise.resolve(null) call in the exception handler with
promise.reject(e) to properly propagate the error. Apply the same fix to the
similar code pattern mentioned at lines 159-162 where removal operations fail
but incorrectly resolve with success.
- Around line 154-156: The setItem method in DatabaseKeyStoreModule uses apply()
for SharedPreferences writes, which is asynchronous and allows the method to
resolve before the data is actually persisted to disk. This creates a crash
window where the database key could be lost before being written, making
encrypted data unreadable. Replace the apply() call with commit() to ensure a
synchronous, durable write that blocks until the SharedPreferences data is
persisted before the method returns.
In `@app/lib/encryption/__tests__/keyStore.test.ts`:
- Around line 33-50: The test fixtures and assertions do not properly validate
the hexadecimal contract for SQLCipher keys. In the first test (the one checking
that existing keys are returned unchanged), replace the mock fixture string
'existingHexKey64chars0000000000000000000000000000000000000000000000' with a
valid 64-character hexadecimal string containing only 0-9 and a-f characters. In
the second test (the one checking that new keys are minted and stored), add an
additional assertion after the length check to verify that the result is a valid
hexadecimal string, such as by testing it against a hex pattern. This ensures
both tests will catch any malformed key generation or storage.
In `@app/lib/encryption/keyStore.ts`:
- Around line 28-39: The validation for the existing key in the
DatabaseKeyStore.getItem block only checks if the value is a string, but does
not validate that it conforms to the API contract of being a 32-byte key encoded
as exactly 64 hexadecimal characters. Add validation logic when checking typeof
existing === 'string' to ensure the string matches the expected format (64 hex
characters) and throw an error with a descriptive message if it does not.
Additionally, before persisting the newly generated key with native.setItem
after calling randomKey(32), validate that the generated key also matches the
expected 64-character hex format to ensure consistency.
- Around line 22-40: The getOrCreateDatabaseKey function has a race condition
where two concurrent calls with the same dbName can both read null, mint
different keys, and race on setItem, causing one key to overwrite the other and
making data unreadable. Add serialization using a per-dbName lock or similar
single-flight guard to ensure only one concurrent request actually creates and
stores the key for each database name. This prevents multiple keys from being
generated and ensures all callers for the same dbName receive the same key
value.
In `@ios/Libraries/DatabaseKeyStore.swift`:
- Around line 80-87: The current implementation overwrites an existing Keychain
item when SecItemAdd returns errSecDuplicateItem, which breaks the fail-closed
guarantee by replacing a key that may have been created by another caller.
Instead of updating the existing key with new data in the duplicate item case,
remove the SecItemUpdate call and its attrs manipulation, and simply return true
(indicating the key now exists and can be used) or return false to signal that a
duplicate was encountered, depending on your intended behavior for handling
pre-existing keys.
---
Nitpick comments:
In `@app/lib/encryption/keyStore.ts`:
- Around line 7-12: Add an explicit return type annotation to the
getNativeModule function to make the type narrowing boundary clear. The function
currently lacks a return type annotation and should be updated to explicitly
declare that it returns the Spec type (the type of NativeDatabaseKeyStore). This
makes the type guard contract explicit and aligns with the coding guideline
requiring explicit type annotations on function return types.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: d91e1b50-aa08-46b7-bfa6-eee799c6a139
📒 Files selected for processing (10)
android/app/src/main/java/chat/rocket/reactnative/MainApplication.ktandroid/app/src/main/java/chat/rocket/reactnative/storage/DatabaseKeyStoreModule.javaandroid/app/src/main/java/chat/rocket/reactnative/storage/DatabaseKeyStoreTurboPackage.javaandroid/app/src/main/java/chat/rocket/reactnative/storage/NativeDatabaseKeyStoreSpec.javaapp/lib/encryption/__tests__/keyStore.test.tsapp/lib/encryption/keyStore.tsapp/lib/native/NativeDatabaseKeyStore.tsios/Libraries/DatabaseKeyStore.mmios/Libraries/DatabaseKeyStore.swiftios/RocketChatRN.xcodeproj/project.pbxproj
📜 Review details
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,ts,jsx,tsx}: Use descriptive names for functions, variables, and classes that clearly convey their purpose
Write comments that explain the 'why' behind code decisions, not the 'what'
Keep functions small and focused on a single responsibility
Use const by default, let when reassignment is needed, and avoid var
Prefer async/await over .then() chains for handling asynchronous operations
Use explicit error handling with try/catch blocks for async operations
Avoid deeply nested code; refactor complex logic into helper functions
Files:
app/lib/encryption/keyStore.tsapp/lib/native/NativeDatabaseKeyStore.tsapp/lib/encryption/__tests__/keyStore.test.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Use TypeScript for type safety; add explicit type annotations to function parameters and return types
Prefer interfaces over type aliases for defining object shapes in TypeScript
Use enums for sets of related constants rather than magic strings or numbersUse TypeScript with strict mode enabled
Files:
app/lib/encryption/keyStore.tsapp/lib/native/NativeDatabaseKeyStore.tsapp/lib/encryption/__tests__/keyStore.test.ts
**/*.{js,jsx,ts,tsx,json}
📄 CodeRabbit inference engine (CLAUDE.md)
Use Prettier formatting with tabs, single quotes, 130 character line width, no trailing commas, and avoid arrow function parentheses
Files:
app/lib/encryption/keyStore.tsapp/lib/native/NativeDatabaseKeyStore.tsapp/lib/encryption/__tests__/keyStore.test.ts
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Enforce ESLint rules from
@rocket.chat/eslint-configwith React, React Native, TypeScript, and Jest plugins
Files:
app/lib/encryption/keyStore.tsapp/lib/native/NativeDatabaseKeyStore.tsapp/lib/encryption/__tests__/keyStore.test.ts
app/lib/encryption/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Place end-to-end encryption implementation in 'app/lib/encryption/' directory using
@rocket.chat/mobile-crypto
Files:
app/lib/encryption/keyStore.tsapp/lib/encryption/__tests__/keyStore.test.ts
🧠 Learnings (3)
📚 Learning: 2026-04-30T17:07:51.020Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7274
File: app/lib/services/voip/MediaCallEvents.ts:0-0
Timestamp: 2026-04-30T17:07:51.020Z
Learning: In this Rocket.Chat React Native codebase, the ESLint rule `no-void: error` is enforced. When you see a promise returned from an async call that is not awaited (a “floating promise”), do not silence it with the `void somePromise()` pattern. Instead, handle the promise explicitly by attaching `.catch(...)` (or otherwise awaiting/handling the error) so unhandled-rejection risks are addressed in a way that satisfies the existing ESLint configuration.
Applied to files:
app/lib/encryption/keyStore.tsapp/lib/native/NativeDatabaseKeyStore.tsapp/lib/encryption/__tests__/keyStore.test.ts
📚 Learning: 2026-03-05T06:06:12.277Z
Learnt from: divyanshu-patil
Repo: RocketChat/Rocket.Chat.ReactNative PR: 6957
File: ios/RCTWatchModule.mm:19-24
Timestamp: 2026-03-05T06:06:12.277Z
Learning: Do not re-activate or reset the WCSession singleton in iOS Objective-C/Swift bridge modules. Ensure WCSession is activated and its delegate is set in a single, central place (e.g., ios/RocketChat Watch App/Loaders/WatchSession.swift) and avoid duplicating activation or delegate assignment in other iOS bridge files like ios/RCTWatchModule.mm. If WCSession is already activated via the central loader, relying on WCSession.defaultSession is sufficient and maintains a single session lifecycle.
Applied to files:
ios/Libraries/DatabaseKeyStore.mm
📚 Learning: 2026-05-12T21:58:10.053Z
Learnt from: Rohit3523
Repo: RocketChat/Rocket.Chat.ReactNative PR: 6991
File: ios/Libraries/Challenge.mm:29-37
Timestamp: 2026-05-12T21:58:10.053Z
Learning: For iOS code using react-native-mmkv, when calling `[MMKV initializeMMKV:nil groupDir:groupDir logLevel:...]` (and when passing `rootPath` to MMKVBridge), pass the raw App Group container URL path only (e.g., `[[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:appGroup] path]`). Do NOT append `/mmkv` yourself. The MMKV SDK already appends `/mmkv` internally (`...stringByAppendingPathComponent:@"mmkv"`), so passing `<AppGroup>/mmkv` will cause double-nesting (`<AppGroup>/mmkv/mmkv`) and can break shared storage/SSL cert lookups.
Applied to files:
ios/Libraries/DatabaseKeyStore.mm
🪛 ast-grep (0.43.0)
android/app/src/main/java/chat/rocket/reactnative/storage/DatabaseKeyStoreModule.java
[warning] 101-101: Triple DES (3DES or DESede) is considered deprecated. AES is the recommended cipher. Upgrade to use AES.
Context: KeyStore.getInstance(KEYSTORE_PROVIDER)
Note: [CWE-326]: Inadequate Encryption Strength [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures
(desede-is-deprecated-java)
[warning] 116-116: Triple DES (3DES or DESede) is considered deprecated. AES is the recommended cipher. Upgrade to use AES.
Context: Cipher.getInstance(TRANSFORMATION)
Note: [CWE-326]: Inadequate Encryption Strength [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures
(desede-is-deprecated-java)
[warning] 124-124: Triple DES (3DES or DESede) is considered deprecated. AES is the recommended cipher. Upgrade to use AES.
Context: KeyStore.getInstance(KEYSTORE_PROVIDER)
Note: [CWE-326]: Inadequate Encryption Strength [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures
(desede-is-deprecated-java)
[warning] 129-129: Triple DES (3DES or DESede) is considered deprecated. AES is the recommended cipher. Upgrade to use AES.
Context: KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE_PROVIDER)
Note: [CWE-326]: Inadequate Encryption Strength [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures
(desede-is-deprecated-java)
[warning] 142-142: Triple DES (3DES or DESede) is considered deprecated. AES is the recommended cipher. Upgrade to use AES.
Context: Cipher.getInstance(TRANSFORMATION)
Note: [CWE-326]: Inadequate Encryption Strength [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures
(desede-is-deprecated-java)
[warning] 101-101: Use of AES with ECB mode detected. ECB doesn't provide message confidentiality and is not semantically secure so should not be used. Instead, use a strong, secure cipher: Cipher.getInstance("AES/CBC/PKCS7PADDING"). See https://owasp.org/www-community/Using_the_Java_Cryptographic_Extensions for more information.
Context: KeyStore.getInstance(KEYSTORE_PROVIDER)
Note: [CWE-327]: Use of a Broken or Risky Cryptographic Algorithm [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures
(use-of-aes-ecb-java)
[warning] 116-116: Use of AES with ECB mode detected. ECB doesn't provide message confidentiality and is not semantically secure so should not be used. Instead, use a strong, secure cipher: Cipher.getInstance("AES/CBC/PKCS7PADDING"). See https://owasp.org/www-community/Using_the_Java_Cryptographic_Extensions for more information.
Context: Cipher.getInstance(TRANSFORMATION)
Note: [CWE-327]: Use of a Broken or Risky Cryptographic Algorithm [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures
(use-of-aes-ecb-java)
[warning] 124-124: Use of AES with ECB mode detected. ECB doesn't provide message confidentiality and is not semantically secure so should not be used. Instead, use a strong, secure cipher: Cipher.getInstance("AES/CBC/PKCS7PADDING"). See https://owasp.org/www-community/Using_the_Java_Cryptographic_Extensions for more information.
Context: KeyStore.getInstance(KEYSTORE_PROVIDER)
Note: [CWE-327]: Use of a Broken or Risky Cryptographic Algorithm [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures
(use-of-aes-ecb-java)
[warning] 129-129: Use of AES with ECB mode detected. ECB doesn't provide message confidentiality and is not semantically secure so should not be used. Instead, use a strong, secure cipher: Cipher.getInstance("AES/CBC/PKCS7PADDING"). See https://owasp.org/www-community/Using_the_Java_Cryptographic_Extensions for more information.
Context: KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE_PROVIDER)
Note: [CWE-327]: Use of a Broken or Risky Cryptographic Algorithm [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures
(use-of-aes-ecb-java)
[warning] 142-142: Use of AES with ECB mode detected. ECB doesn't provide message confidentiality and is not semantically secure so should not be used. Instead, use a strong, secure cipher: Cipher.getInstance("AES/CBC/PKCS7PADDING"). See https://owasp.org/www-community/Using_the_Java_Cryptographic_Extensions for more information.
Context: Cipher.getInstance(TRANSFORMATION)
Note: [CWE-327]: Use of a Broken or Risky Cryptographic Algorithm [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures
(use-of-aes-ecb-java)
🔇 Additional comments (9)
app/lib/native/NativeDatabaseKeyStore.ts (1)
1-10: LGTM!app/lib/encryption/__tests__/keyStore.test.ts (1)
53-75: LGTM!ios/Libraries/DatabaseKeyStore.swift (1)
39-63: LGTM!Also applies to: 93-102
ios/Libraries/DatabaseKeyStore.mm (2)
25-57: LGTM!
1-3: Import the generated DatabaseKeyStore TurboModule spec or verify the spec header path.This file uses
<NativeDatabaseKeyStoreSpec>at line 14 andfacebook::react::NativeDatabaseKeyStoreSpecJSIat line 63, but the corresponding generated spec header does not exist in the repository. ThecodegenConfiginpackage.jsondoes not include DatabaseKeyStore (only SSLPinning is configured). WhileVoipModule.mmuses a similar pattern with<NativeVoipSpec>, the missing spec header declaration requires verification that this code actually compiles or confirmation of the correct import path through React Native's codegen setup.ios/RocketChatRN.xcodeproj/project.pbxproj (1)
151-153: LGTM!Also applies to: 438-439, 942-943, 1684-1684, 1735-1736
android/app/src/main/java/chat/rocket/reactnative/storage/NativeDatabaseKeyStoreSpec.java (1)
1-30: LGTM!android/app/src/main/java/chat/rocket/reactnative/storage/DatabaseKeyStoreTurboPackage.java (1)
1-43: LGTM!android/app/src/main/java/chat/rocket/reactnative/MainApplication.kt (1)
22-22: LGTM!Also applies to: 47-47
The DatabaseKeyStore mints and stores the per-database SQLCipher key; losing or overwriting that key permanently bricks the encrypted database, so every write path must be fail-closed. - Android: persist the encrypted key blob with commit() instead of apply(), and throw if it returns false. apply() flushes asynchronously, so a crash between mint and flush left the just-encrypted database unrecoverable. - Android & iOS: make writes write-once. Both layers previously overwrote an existing key (Android re-encrypted unconditionally; iOS ran SecItemUpdate on errSecDuplicateItem). Now a write with a differing value for an existing key is refused; an identical value is a no-op success. - JS: validate the 64-hex-char key contract on both read and mint, rejecting a malformed value before it reaches SQLCipher's PRAGMA key. - JS: register the TurboModule with getEnforcing (always-present cross-platform module), making the export non-nullable and removing the dead null guard. Claude-Session: https://claude.ai/code/session_01TE9VsFTeXsXc8ssqeR8MJ7
38ffab5
into
feat/native-1272-sqlcipher-migration
Proposed changes
Adds a native, fail-closed key store for the per-database SQLCipher keys the upcoming encrypted-database work depends on.
DatabaseKeyStoreTurboModule — stores and retrieves a per-database key in the iOS Keychain and the Android Keystore. New-Architecture TurboModule with a codegen spec (app/lib/native/NativeDatabaseKeyStore.ts); replaces the earlier legacy-bridge variant.getOrCreateDatabaseKey(dbName)(app/lib/encryption/keyStore.ts) — returns the stored 64-hex-char key for a database, minting a new 32-byte key only when none exists yet (storage-key formatdb_key_v1:<dbName>).SharedPreferencesfile.Additive and inert: nothing reads these keys yet. They are consumed once the databases are actually encrypted at the SQLCipher cutover.
Issue(s)
https://rocketchat.atlassian.net/browse/NATIVE-1311
The native readers work (https://rocketchat.atlassian.net/browse/NATIVE-1276) depends on this module and reads keys by the shared
db_key_v1:format.How to test or reproduce
TZ=UTC pnpm test --testPathPattern='keyStore'— 5 tests pass, including the fail-closed case (a read error rejects and does not write a new key).pnpm lint— 0 errors.getOrCreateDatabaseKey('test.db')twice — the same 64-hex key is returned both times and is persisted in the iOS Keychain (servicechat.rocket.reactnative.dbkeys) / Android Keystore-backed preferences.Screenshots
Types of changes
Checklist
Further comments
Targets the migration integration branch
feat/native-1272-sqlcipher-migration, notdevelop. Native build + Keychain/Keystore round-trip verification is pending on a device/simulator.https://claude.ai/code/session_01TE9VsFTeXsXc8ssqeR8MJ7
Summary by CodeRabbit