From 3cc2f2143f81e8eeee1de204372524b29117184d Mon Sep 17 00:00:00 2001 From: Brian Yencho Date: Mon, 8 Jan 2024 22:18:40 -0600 Subject: [PATCH] Add auto unlock key storage (#544) --- .../auth/datasource/disk/AuthDiskSource.kt | 11 ++++++ .../datasource/disk/AuthDiskSourceImpl.kt | 19 +++++++++ .../datasource/disk/AuthDiskSourceTest.kt | 39 +++++++++++++++++++ .../disk/util/FakeAuthDiskSource.kt | 8 ++++ 4 files changed, 77 insertions(+) diff --git a/app/src/main/java/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSource.kt b/app/src/main/java/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSource.kt index 99b31d8412..0ab08153e9 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSource.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSource.kt @@ -7,6 +7,7 @@ import kotlinx.coroutines.flow.Flow /** * Primary access point for disk information. */ +@Suppress("TooManyFunctions") interface AuthDiskSource { /** * Retrieves a unique ID for the application that is stored locally. This will generate a new @@ -50,6 +51,16 @@ interface AuthDiskSource { */ fun storePrivateKey(userId: String, privateKey: String?) + /** + * Retrieves a user auto-unlock key for the given [userId]. + */ + fun getUserAutoUnlockKey(userId: String): String? + + /** + * Stores a user auto-unlock key for the given [userId]. + */ + fun storeUserAutoUnlockKey(userId: String, userAutoUnlockKey: String?) + /** * Gets the organization keys for the given [userId] in the form of a mapping from organization * ID to encrypted organization key. 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 b6523df20b..d32b040db5 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,6 +4,8 @@ import android.content.SharedPreferences 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.repository.util.bufferedMutableSharedFlow import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson import kotlinx.coroutines.flow.Flow @@ -13,6 +15,7 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import java.util.UUID +private const val USER_AUTO_UNLOCK_KEY_KEY = "$ENCRYPTED_BASE_KEY:userKeyAutoUnlock" private const val UNIQUE_APP_ID_KEY = "$BASE_KEY:appId" private const val REMEMBERED_EMAIL_ADDRESS_KEY = "$BASE_KEY:rememberedEmail" private const val STATE_KEY = "$BASE_KEY:state" @@ -85,6 +88,22 @@ class AuthDiskSourceImpl( ) } + override fun getUserAutoUnlockKey(userId: String): String? = + getEncryptedString( + key = "${USER_AUTO_UNLOCK_KEY_KEY}_$userId", + default = null, + ) + + override fun storeUserAutoUnlockKey( + userId: String, + userAutoUnlockKey: String?, + ) { + putEncryptedString( + key = "${USER_AUTO_UNLOCK_KEY_KEY}_$userId", + value = userAutoUnlockKey, + ) + } + override fun getOrganizationKeys(userId: String): Map? = getString(key = "${ORGANIZATION_KEYS_KEY}_$userId") ?.let { json.decodeFromString(it) } diff --git a/app/src/test/java/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceTest.kt index b0bc426f33..f3cf14701f 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceTest.kt @@ -193,6 +193,45 @@ class AuthDiskSourceTest { ) } + @Test + fun `getUserAutoUnlockKey should pull from SharedPreferences`() { + val userAutoUnlockKeyBaseKey = "bwSecureStorage:userKeyAutoUnlock" + val mockUserId = "mockUserId" + val mockUserAutoUnlockKey = "mockUserAutoUnlockKey" + fakeEncryptedSharedPreferences + .edit() + .putString( + "${userAutoUnlockKeyBaseKey}_$mockUserId", + mockUserAutoUnlockKey, + ) + .apply() + val actual = authDiskSource.getUserAutoUnlockKey(userId = mockUserId) + assertEquals( + mockUserAutoUnlockKey, + actual, + ) + } + + @Test + fun `storeUserAutoUnlockKey should update SharedPreferences`() { + val userAutoUnlockKeyBaseKey = "bwSecureStorage:userKeyAutoUnlock" + val mockUserId = "mockUserId" + val mockUserAutoUnlockKey = "mockUserAutoUnlockKey" + authDiskSource.storeUserAutoUnlockKey( + userId = mockUserId, + userAutoUnlockKey = mockUserAutoUnlockKey, + ) + val actual = fakeEncryptedSharedPreferences + .getString( + "${userAutoUnlockKeyBaseKey}_$mockUserId", + null, + ) + assertEquals( + mockUserAutoUnlockKey, + actual, + ) + } + @Test fun `getOrganizationKeys should pull from SharedPreferences`() { val organizationKeysBaseKey = "bwPreferencesStorage:encOrgKeys" diff --git a/app/src/test/java/com/x8bit/bitwarden/data/auth/datasource/disk/util/FakeAuthDiskSource.kt b/app/src/test/java/com/x8bit/bitwarden/data/auth/datasource/disk/util/FakeAuthDiskSource.kt index c80c25087e..0e9863b596 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/auth/datasource/disk/util/FakeAuthDiskSource.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/auth/datasource/disk/util/FakeAuthDiskSource.kt @@ -21,6 +21,7 @@ class FakeAuthDiskSource : AuthDiskSource { private val storedUserKeys = mutableMapOf() private val storedPrivateKeys = mutableMapOf() + private val storedUserAutoUnlockKeys = mutableMapOf() private val storedOrganizations = mutableMapOf?>() private val storedOrganizationKeys = mutableMapOf?>() @@ -46,6 +47,13 @@ class FakeAuthDiskSource : AuthDiskSource { storedPrivateKeys[userId] = privateKey } + override fun getUserAutoUnlockKey(userId: String): String? = + storedUserAutoUnlockKeys[userId] + + override fun storeUserAutoUnlockKey(userId: String, userAutoUnlockKey: String?) { + storedUserAutoUnlockKeys[userId] = userAutoUnlockKey + } + override fun getOrganizationKeys( userId: String, ): Map? = storedOrganizationKeys[userId]