From 5e3766624961e56850619d5031354a6afad73d07 Mon Sep 17 00:00:00 2001 From: Patrick Honkonen Date: Tue, 21 Jan 2025 18:18:04 -0500 Subject: [PATCH] [PM-17424] Implement disk data source for key data This commit introduces a new disk data source, `KeyDiskSource`, for managing key-related data. This includes: - Storing the alias and host of the selected mTLS key. - Retrieving the alias and host of the selected mTLS key. A default implementation, `KeyDiskSourceImpl`, is provided using encrypted shared preferences to securely store the key data. The `PlatformDiskModule` is updated to provide an instance of `KeyDiskSource`. --- .../platform/datasource/disk/KeyDiskSource.kt | 28 ++++++++ .../datasource/disk/KeyDiskSourceImpl.kt | 37 ++++++++++ .../datasource/disk/di/PlatformDiskModule.kt | 13 ++++ .../datasource/disk/model/MutualTlsKeyHost.kt | 16 +++++ .../datasource/disk/KeyDiskSourceTest.kt | 67 +++++++++++++++++++ 5 files changed, 161 insertions(+) create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/KeyDiskSource.kt create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/KeyDiskSourceImpl.kt create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/model/MutualTlsKeyHost.kt create mode 100644 app/src/test/java/com/x8bit/bitwarden/data/platform/datasource/disk/KeyDiskSourceTest.kt diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/KeyDiskSource.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/KeyDiskSource.kt new file mode 100644 index 00000000000..a732ff94101 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/KeyDiskSource.kt @@ -0,0 +1,28 @@ +package com.x8bit.bitwarden.data.platform.datasource.disk + +import com.x8bit.bitwarden.data.platform.datasource.disk.model.MutualTlsKeyHost + +/** + * Primary access point for disk information related to key data. + */ +interface KeyDiskSource { + + /** + * Sets the alias and host of the selected mTLS key. + */ + fun storeMutualTlsKey( + userId: String, + alias: String?, + host: MutualTlsKeyHost?, + ) + + /** + * Alias of the mTLS key if one is selected. + */ + fun getMutualTlsKeyAlias(userId: String): String? + + /** + * Host of the mTLS key if one is selected. + */ + fun getMutualTlsKeyHost(userId: String): MutualTlsKeyHost? +} diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/KeyDiskSourceImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/KeyDiskSourceImpl.kt new file mode 100644 index 00000000000..49fb10e6361 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/KeyDiskSourceImpl.kt @@ -0,0 +1,37 @@ +package com.x8bit.bitwarden.data.platform.datasource.disk + +import android.content.SharedPreferences +import com.x8bit.bitwarden.data.platform.datasource.disk.model.MutualTlsKeyHost + +private const val MUTUAL_TLS_KEY_ALIAS = "mutualTlsKeyAlias" +private const val MUTUAL_TLS_KEY_HOST = "mutualTlsKeyHost" + +/** + * Default implementation of [KeyDiskSource]. + */ +class KeyDiskSourceImpl( + encryptedPreferences: SharedPreferences, + sharedPreferences: SharedPreferences, +) : BaseEncryptedDiskSource( + sharedPreferences = sharedPreferences, + encryptedSharedPreferences = encryptedPreferences, +), KeyDiskSource { + + override fun storeMutualTlsKey( + userId: String, + alias: String?, + host: MutualTlsKeyHost?, + ) { + putEncryptedString( + key = MUTUAL_TLS_KEY_ALIAS.appendIdentifier(userId), + value = alias, + ) + } + + override fun getMutualTlsKeyAlias(userId: String): String? = + getEncryptedString(key = MUTUAL_TLS_KEY_ALIAS.appendIdentifier(userId)) + + override fun getMutualTlsKeyHost(userId: String): MutualTlsKeyHost? = + getEncryptedString(MUTUAL_TLS_KEY_HOST.appendIdentifier(userId)) + ?.let { MutualTlsKeyHost.valueOf(it) } +} diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/di/PlatformDiskModule.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/di/PlatformDiskModule.kt index 985397d9c00..bdedb6efa85 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/di/PlatformDiskModule.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/di/PlatformDiskModule.kt @@ -14,6 +14,8 @@ import com.x8bit.bitwarden.data.platform.datasource.disk.EventDiskSource import com.x8bit.bitwarden.data.platform.datasource.disk.EventDiskSourceImpl import com.x8bit.bitwarden.data.platform.datasource.disk.FeatureFlagOverrideDiskSource import com.x8bit.bitwarden.data.platform.datasource.disk.FeatureFlagOverrideDiskSourceImpl +import com.x8bit.bitwarden.data.platform.datasource.disk.KeyDiskSource +import com.x8bit.bitwarden.data.platform.datasource.disk.KeyDiskSourceImpl import com.x8bit.bitwarden.data.platform.datasource.disk.PushDiskSource import com.x8bit.bitwarden.data.platform.datasource.disk.PushDiskSourceImpl import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource @@ -165,4 +167,15 @@ object PlatformDiskModule { ): FeatureFlagOverrideDiskSource = FeatureFlagOverrideDiskSourceImpl( sharedPreferences = sharedPreferences, ) + + @Provides + @Singleton + fun provideKeyDiskSource( + @UnencryptedPreferences sharedPreferences: SharedPreferences, + @EncryptedPreferences encryptedSharedPreferences: SharedPreferences, + ): KeyDiskSource = + KeyDiskSourceImpl( + sharedPreferences = sharedPreferences, + encryptedPreferences = encryptedSharedPreferences, + ) } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/model/MutualTlsKeyHost.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/model/MutualTlsKeyHost.kt new file mode 100644 index 00000000000..dd254b235d5 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/model/MutualTlsKeyHost.kt @@ -0,0 +1,16 @@ +package com.x8bit.bitwarden.data.platform.datasource.disk.model + +/** + * Location of the key data. + */ +enum class MutualTlsKeyHost { + /** + * Key is stored in the system key chain. + */ + KEY_CHAIN, + + /** + * Key is stored in a private instance of the Android Key Store. + */ + ANDROID_KEY_STORE, +} diff --git a/app/src/test/java/com/x8bit/bitwarden/data/platform/datasource/disk/KeyDiskSourceTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/platform/datasource/disk/KeyDiskSourceTest.kt new file mode 100644 index 00000000000..4958688c68c --- /dev/null +++ b/app/src/test/java/com/x8bit/bitwarden/data/platform/datasource/disk/KeyDiskSourceTest.kt @@ -0,0 +1,67 @@ +package com.x8bit.bitwarden.data.platform.datasource.disk + +import androidx.core.content.edit +import com.x8bit.bitwarden.data.platform.base.FakeSharedPreferences +import com.x8bit.bitwarden.data.platform.datasource.disk.model.MutualTlsKeyHost +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Test + +class KeyDiskSourceTest { + private val fakeEncryptedSharedPreferences = FakeSharedPreferences() + private val fakeSharedPreferences = FakeSharedPreferences() + + private val keyDiskSource = KeyDiskSourceImpl( + sharedPreferences = fakeSharedPreferences, + encryptedPreferences = fakeEncryptedSharedPreferences, + ) + + @Test + fun `storeMutualTlsKey should update encrypted SharedPreferences`() { + val certificateAliasBaseKey = "bwSecureStorage:mutualTlsKeyAlias" + val certificateHostBaseKey = "bwSecureStorage:mutualTlsKeyHost" + val mockUserId = "mockUserId" + val mockAlias = "mockCertificateAlias" + val mockHost = MutualTlsKeyHost.ANDROID_KEY_STORE + + // Verify that the SharedPreferences are initialized empty + assertNull( + fakeEncryptedSharedPreferences + .getString( + "${certificateAliasBaseKey}_$mockUserId", + null, + ), + ) + assertNull( + fakeEncryptedSharedPreferences + .getString( + "${certificateHostBaseKey}_$mockUserId", + null, + ), + ) + + fakeEncryptedSharedPreferences + .edit { + putString( + "${certificateAliasBaseKey}_$mockUserId", + mockAlias, + ) + putString( + "${certificateHostBaseKey}_$mockUserId", + mockHost.name, + ) + } + + // Verify alias and host are updated in SharedPreferences + val alias = keyDiskSource.getMutualTlsKeyAlias(userId = mockUserId) + val host = keyDiskSource.getMutualTlsKeyHost(userId = mockUserId) + assertEquals( + mockAlias, + alias, + ) + assertEquals( + mockHost, + host, + ) + } +}