PM-40154: feat: Migrate user data to KeystoreEncryptedSharedPreferences - #7158
PM-40154: feat: Migrate user data to KeystoreEncryptedSharedPreferences#7158david-livefront wants to merge 1 commit into
Conversation
d7c24bb to
aa9e67c
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #7158 +/- ##
==========================================
- Coverage 86.11% 85.68% -0.43%
==========================================
Files 894 1052 +158
Lines 65201 68046 +2845
Branches 9756 9877 +121
==========================================
+ Hits 56147 58307 +2160
- Misses 5559 6210 +651
- Partials 3495 3529 +34
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
18d2899 to
0405c99
Compare
0405c99 to
ffcb25d
Compare
ffcb25d to
bafe9cf
Compare
quexten
left a comment
There was a problem hiding this comment.
If we had a tech breakdown beforehand, I'd recommend implementing this in the SDK based on our modern encryption primitives instead. Given the stage, I'll comment the minimum to make this reasonably secure. I recommend dropping the separate MAC key, and switching from CBC to GCM. Currently, the API is very easy to misuse, and you generally do not want to expose separate authentication and encryption (without authentication) APIs.
e7abca5 to
4eb424e
Compare
1249224 to
2fc6a32
Compare
ff16d4c to
2be57f3
Compare
2be57f3 to
5ae0b10
Compare
0592359 to
db1e9a9
Compare
db1e9a9 to
5fbdd28
Compare
5fbdd28 to
5427582
Compare
🤖 Bitwarden Claude Code ReviewOverall Assessment: REQUEST CHANGES Reviewed the migration of encrypted Code Review Details
|
| // Migrate to the Keystore Encrypted SharedPreferences. | ||
| migrateToKeystoreEncryption() |
There was a problem hiding this comment.
❌ CRITICAL: profileAccountKeys is never migrated, so migrateAccountKeys() silently reads null for users upgrading from ≤ v2026.5.x.
Details and fix
Two problems combine here:
migrateToKeystoreEncryption()runs last ininit, butmigrateAccountKeys()(line 137) already callsgetEncryptedString(key = accountKeysKey).getEncryptedStringnow reads only fromkeystoreEncryptedPreferences, which is still empty at that point.PROFILE_ACCOUNT_KEYS_KEYis not in themigrateToKeystoreEncryption()prefix list at all, sobwSecureStorage:profileAccountKeys_<userId>is never moved.
Before #7030 (shipped in v2026.6.0), account keys were written via putEncryptedString(PROFILE_ACCOUNT_KEYS_KEY…), so any user who last ran v2026.5.x or earlier still has that entry in the legacy store. For those users accountKeys resolves to null, and migrateAccountKeys falls through to getString(privateKeyKey). AccountKeysJson?.toAccountCryptographicState then returns WrappedAccountCryptographicState.V1 because signingKey, securityState, and signedPublicKey are all null — a V2 account is silently downgraded to V1.
Side effect: putEncryptedString(key = accountKeysKey, value = null) now clears the new store (a no-op), so the legacy bwSecureStorage:profileAccountKeys_* entry is left behind in the deprecated EncryptedSharedPreferences indefinitely. That also keeps encryptedPreferences.all.size > 2, suppressing the new DiskSourceMigrationLogger signal.
Suggested fix — run the keystore migration right after the legacy secure storage migration (which populates encryptedSharedPreferences) but before the other migrations, and include the profile account keys prefix:
init {
legacySecureStorageMigrator.migrateIfNecessary()
// Migrate to the Keystore Encrypted SharedPreferences before any encrypted values are read.
migrateToKeystoreEncryption()
migrateAccountTokens()
removeLegacyUserKeys()
migrateAccountKeys()
}and in migrateToKeystoreEncryption():
isMigrated = migrateKeyByPrefix(keyPrefix = PROFILE_ACCOUNT_KEYS_KEY) || isMigratedA regression test seeding bwSecureStorage:profileAccountKeys_<userId> and asserting a V2 WrappedAccountCryptographicState survives the upgrade would lock this down.
🎟️ Tracking
PM-40154
📔 Objective
This PR creates a new
KeystoreEncryptedSharedPreferencesthat utilizes theAndroidKeystoreto encrypt the data stored.Notable changes:
EncryptedSharedPreferenceswhich is deprecated and is not FIPS compliant.Strings(this is the only type we need at the moment).