BITAU-70 Implement symmetric key storage (#226)

This commit is contained in:
Andrew Haisting
2024-10-08 13:01:55 -05:00
committed by GitHub
parent d8c80f7e28
commit 1015654d27
7 changed files with 162 additions and 6 deletions
@@ -30,4 +30,9 @@ interface AuthDiskSource {
* Stores the biometrics key.
*/
fun storeUserBiometricUnlockKey(biometricsKey: String?)
/**
* Stores the symmetric key data used for encrypting TOTP data.
*/
var authenticatorBridgeSymmetricSyncKey: ByteArray?
}
@@ -5,6 +5,8 @@ import com.bitwarden.authenticator.data.platform.datasource.disk.BaseDiskSource.
import com.bitwarden.authenticator.data.platform.datasource.disk.BaseEncryptedDiskSource
import com.bitwarden.authenticator.data.platform.datasource.disk.BaseEncryptedDiskSource.Companion.ENCRYPTED_BASE_KEY
private const val AUTHENTICATOR_SYNC_SYMMETRIC_KEY =
"$ENCRYPTED_BASE_KEY:authenticatorSyncSymmetricKey"
private const val LAST_ACTIVE_TIME_KEY = "$BASE_KEY:lastActiveTime"
private const val BIOMETRICS_UNLOCK_KEY = "$ENCRYPTED_BASE_KEY:userKeyBiometricUnlock"
@@ -43,4 +45,12 @@ class AuthDiskSourceImpl(
value = biometricsKey,
)
}
override var authenticatorBridgeSymmetricSyncKey: ByteArray?
set(value) {
val asString = value?.let { value.toString(Charsets.ISO_8859_1) }
putEncryptedString(AUTHENTICATOR_SYNC_SYMMETRIC_KEY, asString)
}
get() = getEncryptedString(AUTHENTICATOR_SYNC_SYMMETRIC_KEY)
?.toByteArray(Charsets.ISO_8859_1)
}
@@ -1,10 +1,11 @@
package com.bitwarden.authenticator.data.authenticator.repository.di
import android.content.Context
import com.bitwarden.authenticator.data.auth.datasource.disk.AuthDiskSource
import com.bitwarden.authenticator.data.authenticator.repository.util.SymmetricKeyStorageProviderImpl
import com.bitwarden.authenticatorbridge.factory.AuthenticatorBridgeFactory
import com.bitwarden.authenticatorbridge.manager.AuthenticatorBridgeManager
import com.bitwarden.authenticatorbridge.manager.model.AuthenticatorBridgeConnectionType
import com.bitwarden.authenticatorbridge.model.SymmetricEncryptionKeyData
import com.bitwarden.authenticatorbridge.provider.SymmetricKeyStorageProvider
import dagger.Module
import dagger.Provides
@@ -31,12 +32,17 @@ object AuthenticatorBridgeModule {
@Singleton
fun provideAuthenticatorBridgeManager(
factory: AuthenticatorBridgeFactory,
symmetricKeyStorageProvider: SymmetricKeyStorageProvider,
): AuthenticatorBridgeManager = factory.getAuthenticatorBridgeManager(
connectionType = AuthenticatorBridgeConnectionType.DEV,
symmetricKeyStorageProvider = object : SymmetricKeyStorageProvider {
// TODO: Implement symmetric key storage: BITAU-70
override var symmetricKey: SymmetricEncryptionKeyData? = null
},
symmetricKeyStorageProvider = symmetricKeyStorageProvider,
)
@Provides
fun providesSymmetricKeyStorageProvider(
authDiskSource: AuthDiskSource,
): SymmetricKeyStorageProvider =
SymmetricKeyStorageProviderImpl(
authDiskSource = authDiskSource,
)
}
@@ -0,0 +1,22 @@
package com.bitwarden.authenticator.data.authenticator.repository.util
import com.bitwarden.authenticator.data.auth.datasource.disk.AuthDiskSource
import com.bitwarden.authenticatorbridge.model.SymmetricEncryptionKeyData
import com.bitwarden.authenticatorbridge.provider.SymmetricKeyStorageProvider
import com.bitwarden.authenticatorbridge.util.toSymmetricEncryptionKeyData
/**
* Implementation of [SymmetricKeyStorageProvider] that stores symmetric key data in encrypted
* shared preferences.
*/
class SymmetricKeyStorageProviderImpl(
private val authDiskSource: AuthDiskSource,
) : SymmetricKeyStorageProvider {
override var symmetricKey: SymmetricEncryptionKeyData?
get() = authDiskSource.authenticatorBridgeSymmetricSyncKey?.toSymmetricEncryptionKeyData()
set(value) {
authDiskSource.authenticatorBridgeSymmetricSyncKey =
value?.symmetricEncryptionKey?.byteArray
}
}
@@ -0,0 +1,40 @@
package com.bitwarden.authenticator.data.auth.datasource.disk
import com.bitwarden.authenticator.data.platform.base.FakeSharedPreferences
import com.bitwarden.authenticatorbridge.util.generateSecretKey
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
class AuthDiskSourceTest {
private val fakeEncryptedSharedPreferences = FakeSharedPreferences()
private val fakeSharedPreferences = FakeSharedPreferences()
private val authDiskSource = AuthDiskSourceImpl(
encryptedSharedPreferences = fakeEncryptedSharedPreferences,
sharedPreferences = fakeSharedPreferences,
)
@Test
@Suppress("MaxLineLength")
fun `authenticatorBridgeSymmetricSyncKey should store and update from EncryptedSharedPreferences`() {
val sharedPrefsKey = "bwSecureStorage:authenticatorSyncSymmetricKey"
// Shared preferences and the repository start with the same value:
assertNull(authDiskSource.authenticatorBridgeSymmetricSyncKey)
assertNull(fakeEncryptedSharedPreferences.getString(sharedPrefsKey, null))
// Updating the repository updates shared preferences:
val symmetricKey = generateSecretKey().getOrThrow().encoded
authDiskSource.authenticatorBridgeSymmetricSyncKey = symmetricKey
assertEquals(
symmetricKey.toString(Charsets.ISO_8859_1),
fakeEncryptedSharedPreferences.getString(sharedPrefsKey, null),
)
// Retrieving the key from repository should give same byte array despite String conversion:
assertTrue(authDiskSource.authenticatorBridgeSymmetricSyncKey.contentEquals(symmetricKey))
}
}
@@ -0,0 +1,23 @@
package com.bitwarden.authenticator.data.auth.datasource.disk.util
import com.bitwarden.authenticator.data.auth.datasource.disk.AuthDiskSource
class FakeAuthDiskSource : AuthDiskSource {
private var lastActiveTimeMillis: Long? = null
private var userBiometricUnlockKey: String? = null
override fun getLastActiveTimeMillis(): Long? = lastActiveTimeMillis
override fun storeLastActiveTimeMillis(lastActiveTimeMillis: Long?) {
this@FakeAuthDiskSource.lastActiveTimeMillis = lastActiveTimeMillis
}
override fun getUserBiometricUnlockKey(): String? = userBiometricUnlockKey
override fun storeUserBiometricUnlockKey(biometricsKey: String?) {
this@FakeAuthDiskSource.userBiometricUnlockKey = biometricsKey
}
override var authenticatorBridgeSymmetricSyncKey: ByteArray? = null
}
@@ -0,0 +1,50 @@
package com.bitwarden.authenticator.data.authenticator.repository.util
import com.bitwarden.authenticator.data.auth.datasource.disk.util.FakeAuthDiskSource
import com.bitwarden.authenticatorbridge.util.generateSecretKey
import com.bitwarden.authenticatorbridge.util.toSymmetricEncryptionKeyData
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
class SymmetricKeyStorageProviderTest {
private val fakeAuthDiskSource = FakeAuthDiskSource()
private val provider = SymmetricKeyStorageProviderImpl(
authDiskSource = fakeAuthDiskSource,
)
@Test
fun `symmetricKey get should return null when disk source has no symmetric key`() {
fakeAuthDiskSource.authenticatorBridgeSymmetricSyncKey = null
assertNull(provider.symmetricKey)
}
@Test
fun `symmetricKey get should return symmetric key when disk source has symmetric key`() {
val key = generateSecretKey().getOrThrow()
fakeAuthDiskSource.authenticatorBridgeSymmetricSyncKey = key.encoded
assertEquals(
key.encoded.toSymmetricEncryptionKeyData(),
provider.symmetricKey,
)
}
@Test
fun `symmetricKey set should store key in AuthDiskSource`() {
val key = generateSecretKey().getOrThrow()
fakeAuthDiskSource.authenticatorBridgeSymmetricSyncKey = null
provider.symmetricKey = key.encoded.toSymmetricEncryptionKeyData()
assertTrue(
key.encoded.contentEquals(
fakeAuthDiskSource.authenticatorBridgeSymmetricSyncKey,
),
)
provider.symmetricKey = null
assertNull(fakeAuthDiskSource.authenticatorBridgeSymmetricSyncKey)
}
}