diff --git a/app/src/main/java/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceImpl.kt index dd3f0218b..fb31bf546 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceImpl.kt @@ -4,9 +4,7 @@ import android.content.SharedPreferences import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson import com.x8bit.bitwarden.data.auth.datasource.disk.model.PendingAuthRequestJson import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson -import com.x8bit.bitwarden.data.platform.datasource.disk.BaseDiskSource.Companion.BASE_KEY import com.x8bit.bitwarden.data.platform.datasource.disk.BaseEncryptedDiskSource -import com.x8bit.bitwarden.data.platform.datasource.disk.BaseEncryptedDiskSource.Companion.ENCRYPTED_BASE_KEY import com.x8bit.bitwarden.data.platform.datasource.disk.legacy.LegacySecureStorageMigrator import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow import com.x8bit.bitwarden.data.platform.util.decodeFromStringOrNull @@ -18,28 +16,30 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import java.util.UUID -private const val ACCOUNT_TOKENS_KEY = "$ENCRYPTED_BASE_KEY:accountTokens" -private const val BIOMETRICS_UNLOCK_KEY = "$ENCRYPTED_BASE_KEY:userKeyBiometricUnlock" -private const val USER_AUTO_UNLOCK_KEY_KEY = "$ENCRYPTED_BASE_KEY:userKeyAutoUnlock" -private const val DEVICE_KEY_KEY = "$ENCRYPTED_BASE_KEY:deviceKey" -private const val PENDING_ADMIN_AUTH_REQUEST_KEY = "$ENCRYPTED_BASE_KEY:pendingAdminAuthRequest" +// These keys should be encrypted +private const val ACCOUNT_TOKENS_KEY = "accountTokens" +private const val BIOMETRICS_UNLOCK_KEY = "userKeyBiometricUnlock" +private const val USER_AUTO_UNLOCK_KEY_KEY = "userKeyAutoUnlock" +private const val DEVICE_KEY_KEY = "deviceKey" +private const val PENDING_ADMIN_AUTH_REQUEST_KEY = "pendingAdminAuthRequest" -private const val UNIQUE_APP_ID_KEY = "$BASE_KEY:appId" -private const val REMEMBERED_EMAIL_ADDRESS_KEY = "$BASE_KEY:rememberedEmail" -private const val REMEMBERED_ORG_IDENTIFIER_KEY = "$BASE_KEY:rememberedOrgIdentifier" -private const val STATE_KEY = "$BASE_KEY:state" -private const val LAST_ACTIVE_TIME_KEY = "$BASE_KEY:lastActiveTime" -private const val INVALID_UNLOCK_ATTEMPTS_KEY = "$BASE_KEY:invalidUnlockAttempts" -private const val MASTER_KEY_ENCRYPTION_USER_KEY = "$BASE_KEY:masterKeyEncryptedUserKey" -private const val MASTER_KEY_ENCRYPTION_PRIVATE_KEY = "$BASE_KEY:encPrivateKey" -private const val PIN_PROTECTED_USER_KEY_KEY = "$BASE_KEY:pinKeyEncryptedUserKey" -private const val ENCRYPTED_PIN_KEY = "$BASE_KEY:protectedPin" -private const val ORGANIZATIONS_KEY = "$BASE_KEY:organizations" -private const val ORGANIZATION_KEYS_KEY = "$BASE_KEY:encOrgKeys" -private const val TWO_FACTOR_TOKEN_KEY = "$BASE_KEY:twoFactorToken" -private const val MASTER_PASSWORD_HASH_KEY = "$BASE_KEY:keyHash" -private const val POLICIES_KEY = "$BASE_KEY:policies" -private const val SHOULD_TRUST_DEVICE_KEY = "$BASE_KEY:shouldTrustDevice" +// These keys should not be encrypted +private const val UNIQUE_APP_ID_KEY = "appId" +private const val REMEMBERED_EMAIL_ADDRESS_KEY = "rememberedEmail" +private const val REMEMBERED_ORG_IDENTIFIER_KEY = "rememberedOrgIdentifier" +private const val STATE_KEY = "state" +private const val LAST_ACTIVE_TIME_KEY = "lastActiveTime" +private const val INVALID_UNLOCK_ATTEMPTS_KEY = "invalidUnlockAttempts" +private const val MASTER_KEY_ENCRYPTION_USER_KEY = "masterKeyEncryptedUserKey" +private const val MASTER_KEY_ENCRYPTION_PRIVATE_KEY = "encPrivateKey" +private const val PIN_PROTECTED_USER_KEY_KEY = "pinKeyEncryptedUserKey" +private const val ENCRYPTED_PIN_KEY = "protectedPin" +private const val ORGANIZATIONS_KEY = "organizations" +private const val ORGANIZATION_KEYS_KEY = "encOrgKeys" +private const val TWO_FACTOR_TOKEN_KEY = "twoFactorToken" +private const val MASTER_PASSWORD_HASH_KEY = "keyHash" +private const val POLICIES_KEY = "policies" +private const val SHOULD_TRUST_DEVICE_KEY = "shouldTrustDevice" /** * Primary implementation of [AuthDiskSource]. @@ -130,61 +130,63 @@ class AuthDiskSourceImpl( } override fun getShouldTrustDevice(userId: String): Boolean = - requireNotNull(getBoolean(key = "${SHOULD_TRUST_DEVICE_KEY}_$userId", default = false)) + requireNotNull( + getBoolean(key = SHOULD_TRUST_DEVICE_KEY.appendIdentifier(userId), default = false), + ) override fun storeShouldTrustDevice(userId: String, shouldTrustDevice: Boolean?) { - putBoolean("${SHOULD_TRUST_DEVICE_KEY}_$userId", shouldTrustDevice) + putBoolean(SHOULD_TRUST_DEVICE_KEY.appendIdentifier(userId), shouldTrustDevice) } override fun getLastActiveTimeMillis(userId: String): Long? = - getLong(key = "${LAST_ACTIVE_TIME_KEY}_$userId") + getLong(key = LAST_ACTIVE_TIME_KEY.appendIdentifier(userId)) override fun storeLastActiveTimeMillis( userId: String, lastActiveTimeMillis: Long?, ) { putLong( - key = "${LAST_ACTIVE_TIME_KEY}_$userId", + key = LAST_ACTIVE_TIME_KEY.appendIdentifier(userId), value = lastActiveTimeMillis, ) } override fun getInvalidUnlockAttempts(userId: String): Int? = - getInt(key = "${INVALID_UNLOCK_ATTEMPTS_KEY}_$userId") + getInt(key = INVALID_UNLOCK_ATTEMPTS_KEY.appendIdentifier(userId)) override fun storeInvalidUnlockAttempts( userId: String, invalidUnlockAttempts: Int?, ) { putInt( - key = "${INVALID_UNLOCK_ATTEMPTS_KEY}_$userId", + key = INVALID_UNLOCK_ATTEMPTS_KEY.appendIdentifier(userId), value = invalidUnlockAttempts, ) } override fun getUserKey(userId: String): String? = - getString(key = "${MASTER_KEY_ENCRYPTION_USER_KEY}_$userId") + getString(key = MASTER_KEY_ENCRYPTION_USER_KEY.appendIdentifier(userId)) override fun storeUserKey(userId: String, userKey: String?) { putString( - key = "${MASTER_KEY_ENCRYPTION_USER_KEY}_$userId", + key = MASTER_KEY_ENCRYPTION_USER_KEY.appendIdentifier(userId), value = userKey, ) } override fun getPrivateKey(userId: String): String? = - getString(key = "${MASTER_KEY_ENCRYPTION_PRIVATE_KEY}_$userId") + getString(key = MASTER_KEY_ENCRYPTION_PRIVATE_KEY.appendIdentifier(userId)) override fun storePrivateKey(userId: String, privateKey: String?) { putString( - key = "${MASTER_KEY_ENCRYPTION_PRIVATE_KEY}_$userId", + key = MASTER_KEY_ENCRYPTION_PRIVATE_KEY.appendIdentifier(userId), value = privateKey, ) } override fun getUserAutoUnlockKey(userId: String): String? = getEncryptedString( - key = "${USER_AUTO_UNLOCK_KEY_KEY}_$userId", + key = USER_AUTO_UNLOCK_KEY_KEY.appendIdentifier(userId), default = null, ) @@ -193,26 +195,26 @@ class AuthDiskSourceImpl( userAutoUnlockKey: String?, ) { putEncryptedString( - key = "${USER_AUTO_UNLOCK_KEY_KEY}_$userId", + key = USER_AUTO_UNLOCK_KEY_KEY.appendIdentifier(userId), value = userAutoUnlockKey, ) } override fun getDeviceKey( userId: String, - ): String? = getEncryptedString(key = "${DEVICE_KEY_KEY}_$userId") + ): String? = getEncryptedString(key = DEVICE_KEY_KEY.appendIdentifier(userId)) override fun storeDeviceKey( userId: String, deviceKey: String?, ) { - putEncryptedString(key = "${DEVICE_KEY_KEY}_$userId", value = deviceKey) + putEncryptedString(key = DEVICE_KEY_KEY.appendIdentifier(userId), value = deviceKey) } override fun getPendingAuthRequest( userId: String, ): PendingAuthRequestJson? = - getEncryptedString(key = "${PENDING_ADMIN_AUTH_REQUEST_KEY}_$userId") + getEncryptedString(key = PENDING_ADMIN_AUTH_REQUEST_KEY.appendIdentifier(userId)) ?.let { json.decodeFromStringOrNull(it) } override fun storePendingAuthRequest( @@ -220,27 +222,27 @@ class AuthDiskSourceImpl( pendingAuthRequest: PendingAuthRequestJson?, ) { putEncryptedString( - key = "${PENDING_ADMIN_AUTH_REQUEST_KEY}_$userId", + key = PENDING_ADMIN_AUTH_REQUEST_KEY.appendIdentifier(userId), value = pendingAuthRequest?.let { json.encodeToString(it) }, ) } override fun getUserBiometricUnlockKey(userId: String): String? = - getEncryptedString(key = "${BIOMETRICS_UNLOCK_KEY}_$userId") + getEncryptedString(key = BIOMETRICS_UNLOCK_KEY.appendIdentifier(userId)) override fun storeUserBiometricUnlockKey( userId: String, biometricsKey: String?, ) { putEncryptedString( - key = "${BIOMETRICS_UNLOCK_KEY}_$userId", + key = BIOMETRICS_UNLOCK_KEY.appendIdentifier(userId), value = biometricsKey, ) } override fun getPinProtectedUserKey(userId: String): String? = inMemoryPinProtectedUserKeys[userId] - ?: getString(key = "${PIN_PROTECTED_USER_KEY_KEY}_$userId") + ?: getString(key = PIN_PROTECTED_USER_KEY_KEY.appendIdentifier(userId)) override fun storePinProtectedUserKey( userId: String, @@ -250,36 +252,36 @@ class AuthDiskSourceImpl( inMemoryPinProtectedUserKeys[userId] = pinProtectedUserKey if (inMemoryOnly) return putString( - key = "${PIN_PROTECTED_USER_KEY_KEY}_$userId", + key = PIN_PROTECTED_USER_KEY_KEY.appendIdentifier(userId), value = pinProtectedUserKey, ) } override fun getTwoFactorToken(email: String): String? = - getString(key = "${TWO_FACTOR_TOKEN_KEY}_$email") + getString(key = TWO_FACTOR_TOKEN_KEY.appendIdentifier(email)) override fun storeTwoFactorToken(email: String, twoFactorToken: String?) { putString( - key = "${TWO_FACTOR_TOKEN_KEY}_$email", + key = TWO_FACTOR_TOKEN_KEY.appendIdentifier(email), value = twoFactorToken, ) } override fun getEncryptedPin(userId: String): String? = - getString(key = "${ENCRYPTED_PIN_KEY}_$userId") + getString(key = ENCRYPTED_PIN_KEY.appendIdentifier(userId)) override fun storeEncryptedPin( userId: String, encryptedPin: String?, ) { putString( - key = "${ENCRYPTED_PIN_KEY}_$userId", + key = ENCRYPTED_PIN_KEY.appendIdentifier(userId), value = encryptedPin, ) } override fun getOrganizationKeys(userId: String): Map<String, String>? = - getString(key = "${ORGANIZATION_KEYS_KEY}_$userId") + getString(key = ORGANIZATION_KEYS_KEY.appendIdentifier(userId)) ?.let { json.decodeFromStringOrNull(it) } override fun storeOrganizationKeys( @@ -287,7 +289,7 @@ class AuthDiskSourceImpl( organizationKeys: Map<String, String>?, ) { putString( - key = "${ORGANIZATION_KEYS_KEY}_$userId", + key = ORGANIZATION_KEYS_KEY.appendIdentifier(userId), value = organizationKeys?.let { json.encodeToString(it) }, ) } @@ -295,7 +297,7 @@ class AuthDiskSourceImpl( override fun getOrganizations( userId: String, ): List<SyncResponseJson.Profile.Organization>? = - getString(key = "${ORGANIZATIONS_KEY}_$userId") + getString(key = ORGANIZATIONS_KEY.appendIdentifier(userId)) ?.let { // The organizations are stored as a map val organizationMap: Map<String, SyncResponseJson.Profile.Organization>? = @@ -314,7 +316,7 @@ class AuthDiskSourceImpl( organizations: List<SyncResponseJson.Profile.Organization>?, ) { putString( - key = "${ORGANIZATIONS_KEY}_$userId", + key = ORGANIZATIONS_KEY.appendIdentifier(userId), value = organizations?.let { nonNullOrganizations -> // The organizations are stored as a map val organizationsMap = nonNullOrganizations.associateBy { it.id } @@ -325,14 +327,14 @@ class AuthDiskSourceImpl( } override fun getMasterPasswordHash(userId: String): String? = - getString(key = "${MASTER_PASSWORD_HASH_KEY}_$userId") + getString(key = MASTER_PASSWORD_HASH_KEY.appendIdentifier(userId)) override fun storeMasterPasswordHash(userId: String, passwordHash: String?) { - putString(key = "${MASTER_PASSWORD_HASH_KEY}_$userId", value = passwordHash) + putString(key = MASTER_PASSWORD_HASH_KEY.appendIdentifier(userId), value = passwordHash) } override fun getPolicies(userId: String): List<SyncResponseJson.Policy>? = - getString(key = "${POLICIES_KEY}_$userId") + getString(key = POLICIES_KEY.appendIdentifier(userId)) ?.let { // The policies are stored as a map. val policiesMap: Map<String, SyncResponseJson.Policy>? = @@ -348,7 +350,7 @@ class AuthDiskSourceImpl( override fun storePolicies(userId: String, policies: List<SyncResponseJson.Policy>?) { putString( - key = "${POLICIES_KEY}_$userId", + key = POLICIES_KEY.appendIdentifier(userId), value = policies?.let { nonNullPolicies -> // The policies are stored as a map. val policiesMap = nonNullPolicies.associateBy { it.id } @@ -359,7 +361,7 @@ class AuthDiskSourceImpl( } override fun getAccountTokens(userId: String): AccountTokensJson? = - getEncryptedString(key = "${ACCOUNT_TOKENS_KEY}_$userId") + getEncryptedString(key = ACCOUNT_TOKENS_KEY.appendIdentifier(userId)) ?.let { json.decodeFromStringOrNull(it) } override fun getAccountTokensFlow(userId: String): Flow<AccountTokensJson?> = @@ -368,7 +370,7 @@ class AuthDiskSourceImpl( override fun storeAccountTokens(userId: String, accountTokens: AccountTokensJson?) { putEncryptedString( - key = "${ACCOUNT_TOKENS_KEY}_$userId", + key = ACCOUNT_TOKENS_KEY.appendIdentifier(userId), value = accountTokens?.let { json.encodeToString(it) }, ) getMutableAccountTokensFlow(userId = userId).tryEmit(accountTokens) diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/BaseDiskSource.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/BaseDiskSource.kt index 8b6bfb711..aadff4468 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/BaseDiskSource.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/BaseDiskSource.kt @@ -18,8 +18,8 @@ abstract class BaseDiskSource( key: String, default: Boolean? = null, ): Boolean? = - if (sharedPreferences.contains(key)) { - sharedPreferences.getBoolean(key, false) + if (sharedPreferences.contains(key.withBase())) { + sharedPreferences.getBoolean(key.withBase(), false) } else { // Make sure we can return a null value as a default if necessary default @@ -35,9 +35,9 @@ abstract class BaseDiskSource( ): Unit = sharedPreferences.edit { if (value != null) { - putBoolean(key, value) + putBoolean(key.withBase(), value) } else { - remove(key) + remove(key.withBase()) } } @@ -49,8 +49,8 @@ abstract class BaseDiskSource( key: String, default: Int? = null, ): Int? = - if (sharedPreferences.contains(key)) { - sharedPreferences.getInt(key, 0) + if (sharedPreferences.contains(key.withBase())) { + sharedPreferences.getInt(key.withBase(), 0) } else { // Make sure we can return a null value as a default if necessary default @@ -66,9 +66,9 @@ abstract class BaseDiskSource( ): Unit = sharedPreferences.edit { if (value != null) { - putInt(key, value) + putInt(key.withBase(), value) } else { - remove(key) + remove(key.withBase()) } } @@ -80,8 +80,8 @@ abstract class BaseDiskSource( key: String, default: Long? = null, ): Long? = - if (sharedPreferences.contains(key)) { - sharedPreferences.getLong(key, 0) + if (sharedPreferences.contains(key.withBase())) { + sharedPreferences.getLong(key.withBase(), 0) } else { // Make sure we can return a null value as a default if necessary default @@ -97,31 +97,34 @@ abstract class BaseDiskSource( ): Unit = sharedPreferences.edit { if (value != null) { - putLong(key, value) + putLong(key.withBase(), value) } else { - remove(key) + remove(key.withBase()) } } protected fun getString( key: String, default: String? = null, - ): String? = sharedPreferences.getString(key, default) + ): String? = sharedPreferences.getString(key.withBase(), default) protected fun putString( key: String, value: String?, - ): Unit = sharedPreferences.edit { putString(key, value) } + ): Unit = sharedPreferences.edit { putString(key.withBase(), value) } protected fun removeWithPrefix(prefix: String) { sharedPreferences .all .keys - .filter { it.startsWith(prefix) } + .filter { it.startsWith(prefix.withBase()) } .forEach { sharedPreferences.edit { remove(it) } } } - companion object { - const val BASE_KEY: String = "bwPreferencesStorage" - } + protected fun String.appendIdentifier(identifier: String): String = "${this}_$identifier" } + +/** + * Helper method for prepending the key with the appropriate base storage key. + */ +private fun String.withBase(): String = "bwPreferencesStorage:$this" diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/BaseEncryptedDiskSource.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/BaseEncryptedDiskSource.kt index 87e1d37cb..591173ff9 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/BaseEncryptedDiskSource.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/BaseEncryptedDiskSource.kt @@ -18,14 +18,15 @@ abstract class BaseEncryptedDiskSource( protected fun getEncryptedString( key: String, default: String? = null, - ): String? = encryptedSharedPreferences.getString(key, default) + ): String? = encryptedSharedPreferences.getString(key.withBase(), default) protected fun putEncryptedString( key: String, value: String?, - ): Unit = encryptedSharedPreferences.edit { putString(key, value) } - - companion object { - const val ENCRYPTED_BASE_KEY: String = "bwSecureStorage" - } + ): Unit = encryptedSharedPreferences.edit { putString(key.withBase(), value) } } + +/** + * Helper method for prepending the key with the appropriate base storage key. + */ +private fun String.withBase(): String = "bwSecureStorage:$this" diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/EnvironmentDiskSourceImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/EnvironmentDiskSourceImpl.kt index 4a89edb47..fc4efe37a 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/EnvironmentDiskSourceImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/EnvironmentDiskSourceImpl.kt @@ -2,7 +2,6 @@ package com.x8bit.bitwarden.data.platform.datasource.disk import android.content.SharedPreferences import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson -import com.x8bit.bitwarden.data.platform.datasource.disk.BaseDiskSource.Companion.BASE_KEY import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow import com.x8bit.bitwarden.data.platform.util.decodeFromStringOrNull import kotlinx.coroutines.flow.Flow @@ -10,7 +9,7 @@ import kotlinx.coroutines.flow.onSubscription import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json -private const val PRE_AUTH_URLS_KEY = "$BASE_KEY:preAuthEnvironmentUrls" +private const val PRE_AUTH_URLS_KEY = "preAuthEnvironmentUrls" /** * Primary implementation of [EnvironmentDiskSource]. diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/PushDiskSourceImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/PushDiskSourceImpl.kt index e5d373208..67fb8b4e4 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/PushDiskSourceImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/PushDiskSourceImpl.kt @@ -1,14 +1,13 @@ package com.x8bit.bitwarden.data.platform.datasource.disk import android.content.SharedPreferences -import com.x8bit.bitwarden.data.platform.datasource.disk.BaseDiskSource.Companion.BASE_KEY import com.x8bit.bitwarden.data.platform.util.getBinaryLongFromZoneDateTime import com.x8bit.bitwarden.data.platform.util.getZoneDateTimeFromBinaryLong import java.time.ZonedDateTime -private const val CURRENT_PUSH_TOKEN_KEY = "${BASE_KEY}:pushCurrentToken" -private const val LAST_REGISTRATION_DATE_KEY = "${BASE_KEY}:pushLastRegistrationDate" -private const val REGISTERED_PUSH_TOKEN_KEY = "${BASE_KEY}:pushRegisteredToken" +private const val CURRENT_PUSH_TOKEN_KEY = "pushCurrentToken" +private const val LAST_REGISTRATION_DATE_KEY = "pushLastRegistrationDate" +private const val REGISTERED_PUSH_TOKEN_KEY = "pushRegisteredToken" /** * Primary implementation of [PushDiskSource]. @@ -32,17 +31,17 @@ class PushDiskSourceImpl( } override fun getCurrentPushToken(userId: String): String? { - return getString("${CURRENT_PUSH_TOKEN_KEY}_$userId") + return getString(CURRENT_PUSH_TOKEN_KEY.appendIdentifier(userId)) } override fun getLastPushTokenRegistrationDate(userId: String): ZonedDateTime? { - return getLong("${LAST_REGISTRATION_DATE_KEY}_$userId", null) + return getLong(LAST_REGISTRATION_DATE_KEY.appendIdentifier(userId), null) ?.let { getZoneDateTimeFromBinaryLong(it) } } override fun storeCurrentPushToken(userId: String, pushToken: String?) { putString( - key = "${CURRENT_PUSH_TOKEN_KEY}_$userId", + key = CURRENT_PUSH_TOKEN_KEY.appendIdentifier(userId), value = pushToken, ) } @@ -52,7 +51,7 @@ class PushDiskSourceImpl( registrationDate: ZonedDateTime?, ) { putLong( - key = "${LAST_REGISTRATION_DATE_KEY}_$userId", + key = LAST_REGISTRATION_DATE_KEY.appendIdentifier(userId), value = registrationDate?.let { getBinaryLongFromZoneDateTime(registrationDate) }, ) } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSourceImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSourceImpl.kt index e17541e88..dd8653bc0 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSourceImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSourceImpl.kt @@ -1,7 +1,6 @@ package com.x8bit.bitwarden.data.platform.datasource.disk import android.content.SharedPreferences -import com.x8bit.bitwarden.data.platform.datasource.disk.BaseDiskSource.Companion.BASE_KEY import com.x8bit.bitwarden.data.platform.repository.model.UriMatchType import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeoutAction import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow @@ -15,25 +14,25 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import java.time.Instant -private const val APP_LANGUAGE_KEY = "$BASE_KEY:appLocale" -private const val APP_THEME_KEY = "$BASE_KEY:theme" -private const val PULL_TO_REFRESH_KEY = "$BASE_KEY:syncOnRefresh" -private const val INLINE_AUTOFILL_ENABLED_KEY = "$BASE_KEY:inlineAutofillEnabled" -private const val BLOCKED_AUTOFILL_URIS_KEY = "$BASE_KEY:autofillBlacklistedUris" -private const val VAULT_LAST_SYNC_TIME = "$BASE_KEY:vaultLastSyncTime" -private const val VAULT_TIMEOUT_ACTION_KEY = "$BASE_KEY:vaultTimeoutAction" -private const val VAULT_TIME_IN_MINUTES_KEY = "$BASE_KEY:vaultTimeout" -private const val DEFAULT_URI_MATCH_TYPE_KEY = "$BASE_KEY:defaultUriMatch" -private const val DISABLE_AUTO_TOTP_COPY_KEY = "$BASE_KEY:disableAutoTotpCopy" -private const val DISABLE_AUTOFILL_SAVE_PROMPT_KEY = "$BASE_KEY:autofillDisableSavePrompt" -private const val DISABLE_ICON_LOADING_KEY = "$BASE_KEY:disableFavicon" -private const val APPROVE_PASSWORDLESS_LOGINS_KEY = "$BASE_KEY:approvePasswordlessLogins" -private const val SCREEN_CAPTURE_ALLOW_KEY = "$BASE_KEY:screenCaptureAllowed" -private const val SYSTEM_BIOMETRIC_INTEGRITY_SOURCE_KEY = "$BASE_KEY:biometricIntegritySource" -private const val ACCOUNT_BIOMETRIC_INTEGRITY_VALID_KEY = "$BASE_KEY:accountBiometricIntegrityValid" -private const val CRASH_LOGGING_ENABLED_KEY = "$BASE_KEY:crashLoggingEnabled" -private const val CLEAR_CLIPBOARD_INTERVAL_KEY = "$BASE_KEY:clearClipboard" -private const val INITIAL_AUTOFILL_DIALOG_SHOWN = "$BASE_KEY:addSitePromptShown" +private const val APP_LANGUAGE_KEY = "appLocale" +private const val APP_THEME_KEY = "theme" +private const val PULL_TO_REFRESH_KEY = "syncOnRefresh" +private const val INLINE_AUTOFILL_ENABLED_KEY = "inlineAutofillEnabled" +private const val BLOCKED_AUTOFILL_URIS_KEY = "autofillBlacklistedUris" +private const val VAULT_LAST_SYNC_TIME = "vaultLastSyncTime" +private const val VAULT_TIMEOUT_ACTION_KEY = "vaultTimeoutAction" +private const val VAULT_TIME_IN_MINUTES_KEY = "vaultTimeout" +private const val DEFAULT_URI_MATCH_TYPE_KEY = "defaultUriMatch" +private const val DISABLE_AUTO_TOTP_COPY_KEY = "disableAutoTotpCopy" +private const val DISABLE_AUTOFILL_SAVE_PROMPT_KEY = "autofillDisableSavePrompt" +private const val DISABLE_ICON_LOADING_KEY = "disableFavicon" +private const val APPROVE_PASSWORDLESS_LOGINS_KEY = "approvePasswordlessLogins" +private const val SCREEN_CAPTURE_ALLOW_KEY = "screenCaptureAllowed" +private const val SYSTEM_BIOMETRIC_INTEGRITY_SOURCE_KEY = "biometricIntegritySource" +private const val ACCOUNT_BIOMETRIC_INTEGRITY_VALID_KEY = "accountBiometricIntegrityValid" +private const val CRASH_LOGGING_ENABLED_KEY = "crashLoggingEnabled" +private const val CLEAR_CLIPBOARD_INTERVAL_KEY = "clearClipboard" +private const val INITIAL_AUTOFILL_DIALOG_SHOWN = "addSitePromptShown" /** * Primary implementation of [SettingsDiskSource]. @@ -44,8 +43,7 @@ class SettingsDiskSourceImpl( private val json: Json, ) : BaseDiskSource(sharedPreferences = sharedPreferences), SettingsDiskSource { - private val mutableAppThemeFlow = - bufferedMutableSharedFlow<AppTheme>(replay = 1) + private val mutableAppThemeFlow = bufferedMutableSharedFlow<AppTheme>(replay = 1) private val mutableLastSyncFlowMap = mutableMapOf<String, MutableSharedFlow<Instant?>>() @@ -58,11 +56,9 @@ class SettingsDiskSourceImpl( private val mutablePullToRefreshEnabledFlowMap = mutableMapOf<String, MutableSharedFlow<Boolean?>>() - private val mutableIsIconLoadingDisabledFlow = - bufferedMutableSharedFlow<Boolean?>() + private val mutableIsIconLoadingDisabledFlow = bufferedMutableSharedFlow<Boolean?>() - private val mutableIsCrashLoggingEnabledFlow = - bufferedMutableSharedFlow<Boolean?>() + private val mutableIsCrashLoggingEnabledFlow = bufferedMutableSharedFlow<Boolean?>() private val mutableScreenCaptureAllowedFlowMap = mutableMapOf<String, MutableSharedFlow<Boolean?>>() @@ -149,7 +145,7 @@ class SettingsDiskSourceImpl( ) storeLastSyncTime(userId = userId, lastSyncTime = null) storeClearClipboardFrequencySeconds(userId = userId, frequency = null) - removeWithPrefix(prefix = "${ACCOUNT_BIOMETRIC_INTEGRITY_VALID_KEY}_$userId") + removeWithPrefix(prefix = ACCOUNT_BIOMETRIC_INTEGRITY_VALID_KEY.appendIdentifier(userId)) // The following are intentionally not cleared so they can be // restored after logging out and back in: @@ -161,7 +157,9 @@ class SettingsDiskSourceImpl( systemBioIntegrityState: String, ): Boolean? = getBoolean( - key = "${ACCOUNT_BIOMETRIC_INTEGRITY_VALID_KEY}_${userId}_$systemBioIntegrityState", + key = ACCOUNT_BIOMETRIC_INTEGRITY_VALID_KEY + .appendIdentifier(userId) + .appendIdentifier(systemBioIntegrityState), ) override fun storeAccountBiometricIntegrityValidity( @@ -170,30 +168,33 @@ class SettingsDiskSourceImpl( value: Boolean?, ) { putBoolean( - key = "${ACCOUNT_BIOMETRIC_INTEGRITY_VALID_KEY}_${userId}_$systemBioIntegrityState", + key = ACCOUNT_BIOMETRIC_INTEGRITY_VALID_KEY + .appendIdentifier(userId) + .appendIdentifier(systemBioIntegrityState), value = value, ) } override fun getAutoCopyTotpDisabled(userId: String): Boolean? = - getBoolean(key = "${DISABLE_AUTO_TOTP_COPY_KEY}_$userId") + getBoolean(key = DISABLE_AUTO_TOTP_COPY_KEY.appendIdentifier(userId)) override fun storeAutoCopyTotpDisabled( userId: String, isAutomaticallyCopyTotpDisabled: Boolean?, ) { putBoolean( - key = "${DISABLE_AUTO_TOTP_COPY_KEY}_$userId", + key = DISABLE_AUTO_TOTP_COPY_KEY.appendIdentifier(userId), value = isAutomaticallyCopyTotpDisabled, ) } override fun getLastSyncTime(userId: String): Instant? = - getLong(key = "${VAULT_LAST_SYNC_TIME}_$userId")?.let { Instant.ofEpochMilli(it) } + getLong(key = VAULT_LAST_SYNC_TIME.appendIdentifier(userId)) + ?.let { Instant.ofEpochMilli(it) } override fun storeLastSyncTime(userId: String, lastSyncTime: Instant?) { putLong( - key = "${VAULT_LAST_SYNC_TIME}_$userId", + key = VAULT_LAST_SYNC_TIME.appendIdentifier(userId), value = lastSyncTime?.toEpochMilli(), ) getMutableLastSyncFlow(userId = userId).tryEmit(lastSyncTime) @@ -204,7 +205,7 @@ class SettingsDiskSourceImpl( .onSubscription { emit(getLastSyncTime(userId = userId)) } override fun getVaultTimeoutInMinutes(userId: String): Int? = - getInt(key = "${VAULT_TIME_IN_MINUTES_KEY}_$userId") + getInt(key = VAULT_TIME_IN_MINUTES_KEY.appendIdentifier(userId)) override fun getVaultTimeoutInMinutesFlow(userId: String): Flow<Int?> = getMutableVaultTimeoutInMinutesFlow(userId = userId) @@ -215,24 +216,24 @@ class SettingsDiskSourceImpl( vaultTimeoutInMinutes: Int?, ) { putInt( - key = "${VAULT_TIME_IN_MINUTES_KEY}_$userId", + key = VAULT_TIME_IN_MINUTES_KEY.appendIdentifier(userId), value = vaultTimeoutInMinutes, ) getMutableVaultTimeoutInMinutesFlow(userId = userId).tryEmit(vaultTimeoutInMinutes) } override fun getClearClipboardFrequencySeconds(userId: String): Int? = - getInt(key = "${CLEAR_CLIPBOARD_INTERVAL_KEY}_$userId") + getInt(key = CLEAR_CLIPBOARD_INTERVAL_KEY.appendIdentifier(userId)) override fun storeClearClipboardFrequencySeconds(userId: String, frequency: Int?) { putInt( - key = "${CLEAR_CLIPBOARD_INTERVAL_KEY}_$userId", + key = CLEAR_CLIPBOARD_INTERVAL_KEY.appendIdentifier(userId), value = frequency, ) } override fun getVaultTimeoutAction(userId: String): VaultTimeoutAction? = - getString(key = "${VAULT_TIMEOUT_ACTION_KEY}_$userId")?.let { storedValue -> + getString(key = VAULT_TIMEOUT_ACTION_KEY.appendIdentifier(userId))?.let { storedValue -> VaultTimeoutAction.entries.firstOrNull { storedValue == it.value } } @@ -245,14 +246,14 @@ class SettingsDiskSourceImpl( vaultTimeoutAction: VaultTimeoutAction?, ) { putString( - key = "${VAULT_TIMEOUT_ACTION_KEY}_$userId", + key = VAULT_TIMEOUT_ACTION_KEY.appendIdentifier(userId), value = vaultTimeoutAction?.value, ) getMutableVaultTimeoutActionFlow(userId = userId).tryEmit(vaultTimeoutAction) } override fun getDefaultUriMatchType(userId: String): UriMatchType? = - getInt(key = "${DEFAULT_URI_MATCH_TYPE_KEY}_$userId")?.let { storedValue -> + getInt(key = DEFAULT_URI_MATCH_TYPE_KEY.appendIdentifier(userId))?.let { storedValue -> UriMatchType.entries.find { it.value == storedValue } } @@ -261,51 +262,54 @@ class SettingsDiskSourceImpl( uriMatchType: UriMatchType?, ) { putInt( - key = "${DEFAULT_URI_MATCH_TYPE_KEY}_$userId", + key = DEFAULT_URI_MATCH_TYPE_KEY.appendIdentifier(userId), value = uriMatchType?.value, ) } override fun getAutofillSavePromptDisabled(userId: String): Boolean? = - getBoolean(key = "${DISABLE_AUTOFILL_SAVE_PROMPT_KEY}_$userId") + getBoolean(key = DISABLE_AUTOFILL_SAVE_PROMPT_KEY.appendIdentifier(userId)) override fun storeAutofillSavePromptDisabled( userId: String, isAutofillSavePromptDisabled: Boolean?, ) { putBoolean( - key = "${DISABLE_AUTOFILL_SAVE_PROMPT_KEY}_$userId", + key = DISABLE_AUTOFILL_SAVE_PROMPT_KEY.appendIdentifier(userId), value = isAutofillSavePromptDisabled, ) } override fun getPullToRefreshEnabled(userId: String): Boolean? = - getBoolean(key = "${PULL_TO_REFRESH_KEY}_$userId") + getBoolean(key = PULL_TO_REFRESH_KEY.appendIdentifier(userId)) override fun getPullToRefreshEnabledFlow(userId: String): Flow<Boolean?> = getMutablePullToRefreshEnabledFlowMap(userId = userId) .onSubscription { emit(getPullToRefreshEnabled(userId = userId)) } override fun storePullToRefreshEnabled(userId: String, isPullToRefreshEnabled: Boolean?) { - putBoolean(key = "${PULL_TO_REFRESH_KEY}_$userId", value = isPullToRefreshEnabled) + putBoolean( + key = PULL_TO_REFRESH_KEY.appendIdentifier(userId), + value = isPullToRefreshEnabled, + ) getMutablePullToRefreshEnabledFlowMap(userId = userId).tryEmit(isPullToRefreshEnabled) } override fun getInlineAutofillEnabled(userId: String): Boolean? = - getBoolean(key = "${INLINE_AUTOFILL_ENABLED_KEY}_$userId") + getBoolean(key = INLINE_AUTOFILL_ENABLED_KEY.appendIdentifier(userId)) override fun storeInlineAutofillEnabled( userId: String, isInlineAutofillEnabled: Boolean?, ) { putBoolean( - key = "${INLINE_AUTOFILL_ENABLED_KEY}_$userId", + key = INLINE_AUTOFILL_ENABLED_KEY.appendIdentifier(userId), value = isInlineAutofillEnabled, ) } override fun getBlockedAutofillUris(userId: String): List<String>? = - getString(key = "${BLOCKED_AUTOFILL_URIS_KEY}_$userId")?.let { + getString(key = BLOCKED_AUTOFILL_URIS_KEY.appendIdentifier(userId))?.let { json.decodeFromStringOrNull(it) } @@ -314,7 +318,7 @@ class SettingsDiskSourceImpl( blockedAutofillUris: List<String>?, ) { putString( - key = "${BLOCKED_AUTOFILL_URIS_KEY}_$userId", + key = BLOCKED_AUTOFILL_URIS_KEY.appendIdentifier(userId), value = blockedAutofillUris?.let { json.encodeToString(it) }, ) } @@ -353,7 +357,7 @@ class SettingsDiskSourceImpl( } override fun getApprovePasswordlessLoginsEnabled(userId: String): Boolean? { - return getBoolean(key = "${APPROVE_PASSWORDLESS_LOGINS_KEY}_$userId") + return getBoolean(key = APPROVE_PASSWORDLESS_LOGINS_KEY.appendIdentifier(userId)) } override fun storeApprovePasswordlessLoginsEnabled( @@ -361,13 +365,13 @@ class SettingsDiskSourceImpl( isApprovePasswordlessLoginsEnabled: Boolean?, ) { putBoolean( - key = "${APPROVE_PASSWORDLESS_LOGINS_KEY}_$userId", + key = APPROVE_PASSWORDLESS_LOGINS_KEY.appendIdentifier(userId), value = isApprovePasswordlessLoginsEnabled, ) } override fun getScreenCaptureAllowed(userId: String): Boolean? { - return getBoolean(key = "${SCREEN_CAPTURE_ALLOW_KEY}_$userId") + return getBoolean(key = SCREEN_CAPTURE_ALLOW_KEY.appendIdentifier(userId)) } override fun getScreenCaptureAllowedFlow(userId: String): Flow<Boolean?> = @@ -379,7 +383,7 @@ class SettingsDiskSourceImpl( isScreenCaptureAllowed: Boolean?, ) { putBoolean( - key = "${SCREEN_CAPTURE_ALLOW_KEY}_$userId", + key = SCREEN_CAPTURE_ALLOW_KEY.appendIdentifier(userId), value = isScreenCaptureAllowed, ) getMutableScreenCaptureAllowedFlow(userId).tryEmit(isScreenCaptureAllowed) diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/legacy/LegacySecureStorageMigratorImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/legacy/LegacySecureStorageMigratorImpl.kt index 91062e369..956d42a4e 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/legacy/LegacySecureStorageMigratorImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/legacy/LegacySecureStorageMigratorImpl.kt @@ -2,7 +2,6 @@ package com.x8bit.bitwarden.data.platform.datasource.disk.legacy import android.content.SharedPreferences import androidx.core.content.edit -import com.x8bit.bitwarden.data.platform.datasource.disk.BaseEncryptedDiskSource /** * Primary implementation of [LegacySecureStorageMigrator]. @@ -21,7 +20,7 @@ class LegacySecureStorageMigratorImpl( // which will all start with "bwSecureStorage". Hashing only occurred on devices with // SDK <23. val plaintextKeys = keys.filter { - it.startsWith(BaseEncryptedDiskSource.ENCRYPTED_BASE_KEY) + it.startsWith(ENCRYPTED_BASE_KEY) } plaintextKeys.forEach { unhashedKey -> @@ -32,4 +31,8 @@ class LegacySecureStorageMigratorImpl( legacySecureStorage.remove(unhashedKey) } } + + companion object { + private const val ENCRYPTED_BASE_KEY: String = "bwSecureStorage" + } } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/tools/generator/datasource/disk/GeneratorDiskSourceImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/tools/generator/datasource/disk/GeneratorDiskSourceImpl.kt index bbb582fe9..fd53940ab 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/tools/generator/datasource/disk/GeneratorDiskSourceImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/tools/generator/datasource/disk/GeneratorDiskSourceImpl.kt @@ -26,7 +26,7 @@ class GeneratorDiskSourceImpl( } override fun getPasscodeGenerationOptions(userId: String): PasscodeGenerationOptions? = - getString("${BASE_KEY}:${PASSWORD_GENERATION_OPTIONS_KEY}_$userId") + getString(PASSWORD_GENERATION_OPTIONS_KEY.appendIdentifier(userId)) ?.let { json.decodeFromStringOrNull(it) } override fun storePasscodeGenerationOptions( @@ -34,13 +34,13 @@ class GeneratorDiskSourceImpl( options: PasscodeGenerationOptions?, ) { putString( - "${BASE_KEY}:${PASSWORD_GENERATION_OPTIONS_KEY}_$userId", + PASSWORD_GENERATION_OPTIONS_KEY.appendIdentifier(userId), options?.let { json.encodeToString(options) }, ) } override fun getUsernameGenerationOptions(userId: String): UsernameGenerationOptions? = - getString("${BASE_KEY}:${USERNAME_GENERATION_OPTIONS_KEY}_$userId") + getString(USERNAME_GENERATION_OPTIONS_KEY.appendIdentifier(userId)) ?.let { json.decodeFromStringOrNull(it) } override fun storeUsernameGenerationOptions( @@ -48,7 +48,7 @@ class GeneratorDiskSourceImpl( options: UsernameGenerationOptions?, ) { putString( - "${BASE_KEY}:${USERNAME_GENERATION_OPTIONS_KEY}_$userId", + USERNAME_GENERATION_OPTIONS_KEY.appendIdentifier(userId), options?.let { json.encodeToString(it) }, ) }