diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSource.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSource.kt index 74b76880e5..b1ef0d0522 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSource.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSource.kt @@ -1,6 +1,6 @@ package com.x8bit.bitwarden.data.auth.datasource.disk -import com.bitwarden.network.model.AccountKeysJson +import com.bitwarden.core.WrappedAccountCryptographicState import com.bitwarden.network.model.SyncResponseJson import com.bitwarden.network.provider.AppIdProvider import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson @@ -125,34 +125,16 @@ interface AuthDiskSource : AppIdProvider { fun storeLocalUserDataKey(userId: String, wrappedKey: String?) /** - * Retrieves a private key using a [userId]. + * Returns the Wrapped Account Cryptographic State for the given [userId]. */ - @Deprecated( - message = "Use getAccountKeys instead.", - replaceWith = ReplaceWith("getAccountKeys"), - ) - fun getPrivateKey(userId: String): String? + fun getAccountCryptographicState(userId: String): WrappedAccountCryptographicState? /** - * Stores a private key using a [userId]. + * Stores the Wrapped Account Cryptographic State for a given [userId]. */ - @Deprecated( - message = "Use storeAccountKeys instead.", - replaceWith = ReplaceWith("storeAccountKeys"), - ) - fun storePrivateKey(userId: String, privateKey: String?) - - /** - * Returns the profile account keys for the given [userId]. - */ - fun getAccountKeys(userId: String): AccountKeysJson? - - /** - * Stores the profile account keys for the given [userId]. - */ - fun storeAccountKeys( + fun storeAccountCryptographicState( userId: String, - accountKeys: AccountKeysJson?, + accountCryptographicState: WrappedAccountCryptographicState?, ) /** diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceImpl.kt index ca9896028a..dbaa71ab24 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceImpl.kt @@ -1,6 +1,7 @@ package com.x8bit.bitwarden.data.auth.datasource.disk import android.content.SharedPreferences +import com.bitwarden.core.WrappedAccountCryptographicState import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow import com.bitwarden.core.data.serializer.SafeMapSerializer import com.bitwarden.core.data.util.decodeFromStringOrNull @@ -11,6 +12,8 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus 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.auth.datasource.disk.serializer.WrappedAccountCryptographicStateSerializer +import com.x8bit.bitwarden.data.auth.repository.util.toAccountCryptographicState import com.x8bit.bitwarden.data.platform.datasource.disk.legacy.LegacySecureStorageMigrator import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow @@ -54,6 +57,7 @@ private const val ONBOARDING_STATUS_KEY = "onboardingStatus" private const val SHOW_IMPORT_LOGINS_KEY = "showImportLogins" private const val LAST_LOCK_TIMESTAMP = "lastLockTimestamp" private const val PROFILE_ACCOUNT_KEYS_KEY = "profileAccountKeys" +private const val ACCOUNT_CRYPTOGRAPHIC_STATE_KEY = "accountCryptographicState" /** * Primary implementation of [AuthDiskSource]. @@ -91,6 +95,10 @@ class AuthDiskSourceImpl( mutableMapOf>() private val mutableUserStateFlow = bufferedMutableSharedFlow(replay = 1) + private val wrappedAccountCryptographicStateSerializer by lazy { + WrappedAccountCryptographicStateSerializer() + } + override var userState: UserStateJson? get() = getString(key = STATE_KEY)?.let { json.decodeFromStringOrNull(it) } set(value) { @@ -113,6 +121,10 @@ class AuthDiskSourceImpl( // We want to make sure that any left over encrypted user keys are scrubbed from storage // Since it is no longer supported. removeLegacyUserKeys() + + // We must migrate the Private Key and Account Keys to use the Account Cryptographic state + // from now on. + migrateAccountKeys() } override var authenticatorSyncSymmetricKey: ByteArray? @@ -152,8 +164,7 @@ class AuthDiskSourceImpl( storeInvalidUnlockAttempts(userId = userId, invalidUnlockAttempts = null) storeLocalUserDataKey(userId = userId, wrappedKey = null) storeUserAutoUnlockKey(userId = userId, userAutoUnlockKey = null) - storePrivateKey(userId = userId, privateKey = null) - storeAccountKeys(userId = userId, accountKeys = null) + storeAccountCryptographicState(userId = userId, accountCryptographicState = null) storeOrganizationKeys(userId = userId, organizationKeys = null) storeOrganizations(userId = userId, organizations = null) storeUserBiometricInitVector(userId = userId, iv = null) @@ -241,29 +252,20 @@ class AuthDiskSourceImpl( putString(key = LOCAL_USER_DATA_KEY.appendIdentifier(userId), value = wrappedKey) } - @Deprecated("Use getAccountKeys instead.", replaceWith = ReplaceWith("getAccountKeys")) - override fun getPrivateKey(userId: String): String? = - getString(key = MASTER_KEY_ENCRYPTION_PRIVATE_KEY.appendIdentifier(userId)) + override fun getAccountCryptographicState(userId: String): WrappedAccountCryptographicState? = + getEncryptedString(key = ACCOUNT_CRYPTOGRAPHIC_STATE_KEY.appendIdentifier(userId))?.let { + json.decodeFromStringOrNull(wrappedAccountCryptographicStateSerializer, it) + } - @Deprecated("Use storeAccountKeys instead.", replaceWith = ReplaceWith("storeAccountKeys")) - override fun storePrivateKey(userId: String, privateKey: String?) { - putString( - key = MASTER_KEY_ENCRYPTION_PRIVATE_KEY.appendIdentifier(userId), - value = privateKey, - ) - } - - override fun getAccountKeys(userId: String): AccountKeysJson? = - getEncryptedString(key = PROFILE_ACCOUNT_KEYS_KEY.appendIdentifier(userId)) - ?.let { json.decodeFromStringOrNull(it) } - - override fun storeAccountKeys( + override fun storeAccountCryptographicState( userId: String, - accountKeys: AccountKeysJson?, + accountCryptographicState: WrappedAccountCryptographicState?, ) { putEncryptedString( - key = PROFILE_ACCOUNT_KEYS_KEY.appendIdentifier(userId), - value = accountKeys?.let { json.encodeToString(it) }, + key = ACCOUNT_CRYPTOGRAPHIC_STATE_KEY.appendIdentifier(userId), + value = accountCryptographicState?.let { + json.encodeToString(wrappedAccountCryptographicStateSerializer, it) + }, ) } @@ -659,4 +661,31 @@ class AuthDiskSourceImpl( private fun removeLegacyUserKeys() { removeWithPrefix(prefix = MASTER_KEY_ENCRYPTION_USER_KEY) } + + private fun migrateAccountKeys() { + userState + ?.accounts + .orEmpty() + .values + .forEach { account -> + val userId = account.profile.userId + val accountKeysKey = PROFILE_ACCOUNT_KEYS_KEY.appendIdentifier(userId) + val privateKeyKey = MASTER_KEY_ENCRYPTION_PRIVATE_KEY.appendIdentifier(userId) + val accountKeys = getEncryptedString(key = accountKeysKey) + ?.let { json.decodeFromStringOrNull(it) } + val privateKey = accountKeys + ?.publicKeyEncryptionKeyPair + ?.wrappedPrivateKey + ?: getString(key = privateKeyKey) + privateKey?.let { + storeAccountCryptographicState( + userId = userId, + accountCryptographicState = accountKeys.toAccountCryptographicState(it), + ) + // Remove the Account Keys and Private Key + putEncryptedString(key = accountKeysKey, value = null) + putString(key = privateKeyKey, value = null) + } + } + } } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/serializer/WrappedAccountCryptographicStateSerializer.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/serializer/WrappedAccountCryptographicStateSerializer.kt new file mode 100644 index 0000000000..668df9bb2a --- /dev/null +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/serializer/WrappedAccountCryptographicStateSerializer.kt @@ -0,0 +1,85 @@ +package com.x8bit.bitwarden.data.auth.datasource.disk.serializer + +import com.bitwarden.core.WrappedAccountCryptographicState +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +/** + * Custom [KSerializer] for [WrappedAccountCryptographicState]. + * + * Encodes the sealed class with a `"type"` discriminator field: + * - `"v1"`: [WrappedAccountCryptographicState.V1] — wrapped private key only. + * - `"v2"`: [WrappedAccountCryptographicState.V2] — wrapped private key, signing key, signed + * public key, and signed security state. + */ +internal class WrappedAccountCryptographicStateSerializer : + KSerializer { + + private val surrogateSerializer = Surrogate.serializer() + + override val descriptor: SerialDescriptor = surrogateSerializer.descriptor + + override fun deserialize(decoder: Decoder): WrappedAccountCryptographicState = + when (val surrogate = decoder.decodeSerializableValue(surrogateSerializer)) { + is Surrogate.V1 -> { + WrappedAccountCryptographicState.V1(privateKey = surrogate.privateKey) + } + + is Surrogate.V2 -> { + WrappedAccountCryptographicState.V2( + privateKey = surrogate.privateKey, + signingKey = surrogate.signingKey, + signedPublicKey = surrogate.signedPublicKey, + securityState = surrogate.securityState, + ) + } + } + + override fun serialize(encoder: Encoder, value: WrappedAccountCryptographicState) { + val surrogate = when (value) { + is WrappedAccountCryptographicState.V1 -> { + Surrogate.V1(privateKey = value.privateKey) + } + + is WrappedAccountCryptographicState.V2 -> { + Surrogate.V2( + privateKey = value.privateKey, + signingKey = value.signingKey, + signedPublicKey = value.signedPublicKey, + securityState = value.securityState, + ) + } + } + encoder.encodeSerializableValue(surrogateSerializer, surrogate) + } +} + +@Serializable +private sealed class Surrogate { + @Serializable + @SerialName("v1") + data class V1( + @SerialName("privateKey") + val privateKey: String, + ) : Surrogate() + + @Serializable + @SerialName("v2") + data class V2( + @SerialName("privateKey") + val privateKey: String, + + @SerialName("signingKey") + val signingKey: String, + + @SerialName("signedPublicKey") + val signedPublicKey: String?, + + @SerialName("securityState") + val securityState: String, + ) : Surrogate() +} diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryImpl.kt index 21d0a7e2b7..a083a8120b 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryImpl.kt @@ -104,10 +104,8 @@ import com.x8bit.bitwarden.data.auth.repository.util.CookieCallbackResult import com.x8bit.bitwarden.data.auth.repository.util.DuoCallbackTokenResult import com.x8bit.bitwarden.data.auth.repository.util.SsoCallbackResult import com.x8bit.bitwarden.data.auth.repository.util.WebAuthResult -import com.x8bit.bitwarden.data.auth.repository.util.accountKeysJson import com.x8bit.bitwarden.data.auth.repository.util.activeUserIdChangesFlow import com.x8bit.bitwarden.data.auth.repository.util.policyInformation -import com.x8bit.bitwarden.data.auth.repository.util.privateKey import com.x8bit.bitwarden.data.auth.repository.util.toAccountCryptographicState import com.x8bit.bitwarden.data.auth.repository.util.toDeviceInfo import com.x8bit.bitwarden.data.auth.repository.util.toOrganizations @@ -535,17 +533,13 @@ class AuthRepositoryImpl( .map { registerTdeKeyResponse to createAccountKeysResponse } } .onSuccess { (registerTdeKeyResponse, createAccountKeysResponse) -> - authDiskSource.storeAccountKeys( + authDiskSource.storeAccountCryptographicState( userId = userId, - accountKeys = createAccountKeysResponse.accountKeys, - ) - // TDE and SSO user creation still uses crypto-v1. These users are not expected to - // have the AEAD keys so we only store the private key for now. - // See https://github.com/bitwarden/android/pull/5682#discussion_r2273940332 - // for more details. - authDiskSource.storePrivateKey( - userId = userId, - privateKey = registerTdeKeyResponse.privateKey, + accountCryptographicState = createAccountKeysResponse + .accountKeys + .toAccountCryptographicState( + privateKey = registerTdeKeyResponse.privateKey, + ), ) vaultRepository.syncVaultState(userId = userId) registerTdeKeyResponse.deviceKey?.let { response -> @@ -585,16 +579,9 @@ class AuthRepositoryImpl( ) .also { result -> if (result is VaultUnlockResult.Success) { - authDiskSource.storeAccountKeys( + authDiskSource.storeAccountCryptographicState( userId = userId, - accountKeys = response.accountCryptographicState.accountKeysJson, - ) - - // Storing the private key here for legacy purposes, the - // `accountKeysJson` stored above will be used for most purposes. - authDiskSource.storePrivateKey( - userId = userId, - privateKey = response.accountCryptographicState.privateKey, + accountCryptographicState = response.accountCryptographicState, ) if (shouldTrustDevice) { authDiskSource.storeDeviceKey( @@ -614,20 +601,16 @@ class AuthRepositoryImpl( val profile = authDiskSource.userState?.activeAccount?.profile ?: return LoginResult.Error(error = NoActiveUserException()) val userId = profile.userId - val accountKeys = authDiskSource.getAccountKeys(userId = userId) - val privateKey = accountKeys?.publicKeyEncryptionKeyPair?.wrappedPrivateKey - ?: authDiskSource.getPrivateKey(userId = userId) - ?: return LoginResult.Error(error = MissingPropertyException("Private Key")) - + val accountCryptographicState = authDiskSource + .getAccountCryptographicState(userId = userId) + ?: return LoginResult.Error(MissingPropertyException("Account Cryptographic State")) checkForVaultUnlockError( onVaultUnlockError = { error -> return error.toLoginErrorResult() }, ) { unlockVault( - accountCryptographicState = accountKeys.toAccountCryptographicState( - privateKey = privateKey, - ), + accountCryptographicState = accountCryptographicState, accountProfile = profile, initUserCryptoMethod = InitUserCryptoMethod.AuthRequest( requestPrivateKey = requestPrivateKey, @@ -1279,17 +1262,9 @@ class AuthRepositoryImpl( ) } .onSuccess { response -> - authDiskSource.storeAccountKeys( + authDiskSource.storeAccountCryptographicState( userId = userId, - accountKeys = response.accountCryptographicState.accountKeysJson, - ) - // TDE and SSO user creation still uses crypto-v1. These users are not - // expected to have the AEAD keys so we only store the private key for now. - // See https://github.com/bitwarden/android/pull/5682#discussion_r2273940332 - // for more details. - authDiskSource.storePrivateKey( - userId = userId, - privateKey = response.accountCryptographicState.privateKey, + accountCryptographicState = response.accountCryptographicState, ) authDiskSource.userState = authDiskSource.userState?.toUserStateJsonWithPassword( masterPasswordUnlock = response.masterPasswordUnlock, @@ -1346,12 +1321,11 @@ class AuthRepositoryImpl( ), ) .onSuccess { - // This process is used by TDE and Enterprise accounts during initial - // login. We continue to store the locally generated keys - // until TDE and Enterprise accounts support AEAD keys. - authDiskSource.storePrivateKey( + authDiskSource.storeAccountCryptographicState( userId = userId, - privateKey = response.keys.private, + accountCryptographicState = WrappedAccountCryptographicState.V1( + privateKey = response.keys.private, + ), ) } .map { response.masterPasswordHash } @@ -1963,17 +1937,16 @@ class AuthRepositoryImpl( } } } - // We continue to store the private key for backwards compatibility. Key connector - // conversion still relies on the private key. - loginResponse.privateKeyOrNull()?.let { - // Only set the value if it's present, since we may have set it already - // when we completed the key connector conversion. - authDiskSource.storePrivateKey(userId = userId, privateKey = it) - } - loginResponse.accountKeys?.let { - // Only set the value if it's present, since we may have set it already - // when we completed the key connector conversion. - authDiskSource.storeAccountKeys(userId = userId, accountKeys = it) + + loginResponse.privateKeyOrNull()?.let { privateKey -> + // Only set the value if the private key is present, since we may have set + // the value already when we completed the key connector conversion. + authDiskSource.storeAccountCryptographicState( + userId = userId, + accountCryptographicState = loginResponse.accountKeys.toAccountCryptographicState( + privateKey = privateKey, + ), + ) } // If the user just authenticated with a two-factor code and selected the option to // remember it, then the API response will return a token that will be used in place @@ -2088,9 +2061,10 @@ class AuthRepositoryImpl( organizationIdentifier = orgIdentifier, ) .map { keyConnector -> + val accountCryptographicState = keyConnector.accountCryptographicState this .unlockVault( - accountCryptographicState = keyConnector.accountCryptographicState, + accountCryptographicState = accountCryptographicState, accountProfile = profile, initUserCryptoMethod = InitUserCryptoMethod.KeyConnector( masterKey = keyConnector.masterKey, @@ -2099,15 +2073,9 @@ class AuthRepositoryImpl( ) .also { result -> if (result is VaultUnlockResult.Success) { - // We continue to store the private key for backwards compatibility - // since key connector conversion still relies on the private key. - authDiskSource.storePrivateKey( + authDiskSource.storeAccountCryptographicState( userId = userId, - privateKey = keyConnector.privateKey, - ) - authDiskSource.storeAccountKeys( - userId = userId, - accountKeys = loginResponse.accountKeys, + accountCryptographicState = accountCryptographicState, ) } } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/repository/util/AccountKeysJsonExtensions.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/repository/util/AccountKeysJsonExtensions.kt index a3c7260bdb..e3273edd12 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/repository/util/AccountKeysJsonExtensions.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/repository/util/AccountKeysJsonExtensions.kt @@ -2,7 +2,6 @@ package com.x8bit.bitwarden.data.auth.repository.util import com.bitwarden.core.WrappedAccountCryptographicState import com.bitwarden.network.model.AccountKeysJson -import com.x8bit.bitwarden.data.vault.repository.util.createWrappedAccountCryptographicState /** * Creates a [WrappedAccountCryptographicState] based on the available cryptographic parameters. @@ -15,9 +14,20 @@ import com.x8bit.bitwarden.data.vault.repository.util.createWrappedAccountCrypto */ fun AccountKeysJson?.toAccountCryptographicState( privateKey: String, -): WrappedAccountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = this?.securityState?.securityState, - signingKey = this?.signatureKeyPair?.wrappedSigningKey, - signedPublicKey = this?.publicKeyEncryptionKeyPair?.signedPublicKey, -) +): WrappedAccountCryptographicState { + val securityState = this?.securityState?.securityState + val signingKey = this?.signatureKeyPair?.wrappedSigningKey + val signedPublicKey = this?.publicKeyEncryptionKeyPair?.signedPublicKey + return if (signingKey != null && securityState != null && signedPublicKey != null) { + WrappedAccountCryptographicState.V2( + privateKey = privateKey, + securityState = securityState, + signingKey = signingKey, + signedPublicKey = signedPublicKey, + ) + } else { + WrappedAccountCryptographicState.V1( + privateKey = privateKey, + ) + } +} diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/repository/AuthenticatorBridgeRepositoryImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/repository/AuthenticatorBridgeRepositoryImpl.kt index f112add4e8..522c4c506d 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/repository/AuthenticatorBridgeRepositoryImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/repository/AuthenticatorBridgeRepositoryImpl.kt @@ -10,7 +10,6 @@ import com.bitwarden.core.data.util.flatMap import com.bitwarden.data.repository.util.toEnvironmentUrlsOrDefault import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson -import com.x8bit.bitwarden.data.auth.repository.util.toAccountCryptographicState import com.x8bit.bitwarden.data.auth.repository.util.toSdkParams import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource import com.x8bit.bitwarden.data.vault.datasource.sdk.ScopedVaultSdkSource @@ -135,20 +134,17 @@ class AuthenticatorBridgeRepositoryImpl( account: AccountJson, decryptedUserKey: String, ): VaultUnlockResult { - val accountKeys = authDiskSource.getAccountKeys(userId = userId) - val privateKey = accountKeys?.publicKeyEncryptionKeyPair?.wrappedPrivateKey - ?: authDiskSource.getPrivateKey(userId = userId) + val accountCryptographicState = authDiskSource + .getAccountCryptographicState(userId = userId) ?: return VaultUnlockResult.InvalidStateError( - MissingPropertyException("Private key"), + error = MissingPropertyException("Account Cryptographic State"), ) return scopedVaultSdkSource .initializeCrypto( userId = userId, request = InitUserCryptoRequest( - accountCryptographicState = accountKeys.toAccountCryptographicState( - privateKey = privateKey, - ), + accountCryptographicState = accountCryptographicState, userId = userId, kdfParams = account.profile.toSdkParams(), email = account.profile.email, diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/VaultLockManagerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/VaultLockManagerImpl.kt index e41c4b748f..6d69d3fb38 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/VaultLockManagerImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/VaultLockManagerImpl.kt @@ -28,7 +28,6 @@ import com.x8bit.bitwarden.data.auth.manager.UserLogoutManager import com.x8bit.bitwarden.data.auth.repository.model.LogoutReason import com.x8bit.bitwarden.data.auth.repository.model.UpdateKdfMinimumsResult import com.x8bit.bitwarden.data.auth.repository.util.activeUserIdChangesFlow -import com.x8bit.bitwarden.data.auth.repository.util.toAccountCryptographicState import com.x8bit.bitwarden.data.auth.repository.util.toSdkParams import com.x8bit.bitwarden.data.auth.repository.util.userAccountTokens import com.x8bit.bitwarden.data.auth.repository.util.userSwitchingChangesFlow @@ -675,17 +674,14 @@ class VaultLockManagerImpl( ): VaultUnlockResult { val account = authDiskSource.userState?.accounts?.get(userId) ?: return VaultUnlockResult.InvalidStateError(error = NoActiveUserException()) - val accountKeys = authDiskSource.getAccountKeys(userId = userId) - val privateKey = accountKeys?.publicKeyEncryptionKeyPair?.wrappedPrivateKey - ?: authDiskSource.getPrivateKey(userId = userId) + val accountCryptographicState = authDiskSource + .getAccountCryptographicState(userId = userId) ?: return VaultUnlockResult.InvalidStateError( - error = MissingPropertyException("Private key"), + error = MissingPropertyException("Account Cryptographic State"), ) val organizationKeys = authDiskSource.getOrganizationKeys(userId = userId) return unlockVault( - accountCryptographicState = accountKeys.toAccountCryptographicState( - privateKey = privateKey, - ), + accountCryptographicState = accountCryptographicState, userId = userId, email = account.profile.email, kdf = account.profile.toSdkParams(), diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/VaultSyncManagerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/VaultSyncManagerImpl.kt index d333773aae..3a343e4b22 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/VaultSyncManagerImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/VaultSyncManagerImpl.kt @@ -17,6 +17,7 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource import com.x8bit.bitwarden.data.auth.manager.UserLogoutManager import com.x8bit.bitwarden.data.auth.manager.UserStateManager import com.x8bit.bitwarden.data.auth.repository.model.LogoutReason +import com.x8bit.bitwarden.data.auth.repository.util.toAccountCryptographicState import com.x8bit.bitwarden.data.auth.repository.util.toUpdatedUserStateJson import com.x8bit.bitwarden.data.auth.repository.util.userSwitchingChangesFlow import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource @@ -377,8 +378,14 @@ class VaultSyncManagerImpl( val profile = syncResponse.profile val userId = profile.id authDiskSource.apply { - storePrivateKey(userId = userId, privateKey = profile.privateKey) - storeAccountKeys(userId = userId, accountKeys = profile.accountKeys) + storeAccountCryptographicState( + userId = userId, + accountCryptographicState = profile.privateKeyOrNull()?.let { + profile.accountKeys.toAccountCryptographicState( + privateKey = it, + ) + }, + ) storeOrganizationKeys( userId = userId, organizationKeys = profile.organizations @@ -539,6 +546,13 @@ class VaultSyncManagerImpl( ?: DataState.Loading } +/** + * Convenience function to extract the private key from the [SyncResponseJson.Profile] response. + */ +private fun SyncResponseJson.Profile.privateKeyOrNull(): String? = + this.accountKeys?.publicKeyEncryptionKeyPair?.wrappedPrivateKey + ?: this.privateKey + private fun Throwable.toNetworkOrErrorState( data: T?, ): DataState = diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryImpl.kt index 124b0ef83c..aa68c76a66 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryImpl.kt @@ -19,7 +19,6 @@ import com.bitwarden.vault.CipherType import com.bitwarden.vault.CipherView import com.bitwarden.vault.FolderView import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource -import com.x8bit.bitwarden.data.auth.repository.util.toAccountCryptographicState import com.x8bit.bitwarden.data.auth.repository.util.toSdkParams import com.x8bit.bitwarden.data.autofill.util.login import com.x8bit.bitwarden.data.platform.error.NoActiveUserException @@ -559,19 +558,14 @@ class VaultRepositoryImpl( ): VaultUnlockResult { val account = authDiskSource.userState?.accounts?.get(userId) ?: return VaultUnlockResult.InvalidStateError(error = NoActiveUserException()) - val accountKeys = authDiskSource.getAccountKeys(userId = userId) - val privateKey = accountKeys - ?.publicKeyEncryptionKeyPair - ?.wrappedPrivateKey - ?: authDiskSource.getPrivateKey(userId = userId) + val accountCryptographicState = authDiskSource + .getAccountCryptographicState(userId = userId) ?: return VaultUnlockResult.InvalidStateError( - error = MissingPropertyException("Private key"), + error = MissingPropertyException("Account Cryptographic State"), ) val organizationKeys = authDiskSource.getOrganizationKeys(userId = userId) return vaultLockManager.unlockVault( - accountCryptographicState = accountKeys.toAccountCryptographicState( - privateKey = privateKey, - ), + accountCryptographicState = accountCryptographicState, userId = userId, email = account.profile.email, kdf = account.profile.toSdkParams(), diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/util/WrappedAccountCryptographicStateExtensions.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/util/WrappedAccountCryptographicStateExtensions.kt deleted file mode 100644 index b1c948e877..0000000000 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/util/WrappedAccountCryptographicStateExtensions.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.x8bit.bitwarden.data.vault.repository.util - -import com.bitwarden.core.WrappedAccountCryptographicState - -/** - * Creates a [WrappedAccountCryptographicState] based on the available cryptographic parameters. - * - * Returns [WrappedAccountCryptographicState.V2] if signing key, signed public key, and security - * state are all present, otherwise returns [WrappedAccountCryptographicState.V1]. - * - * @param privateKey The user's wrapped private key. - * @param securityState The user's signed security state (V2 only). - * @param signingKey The user's wrapped signing key (V2 only). - * @param signedPublicKey The user's signed public key (V2 only). - */ -fun createWrappedAccountCryptographicState( - privateKey: String, - securityState: String?, - signingKey: String?, - signedPublicKey: String?, -): WrappedAccountCryptographicState { - return if (signingKey != null && securityState != null && signedPublicKey != null) { - WrappedAccountCryptographicState.V2( - privateKey = privateKey, - securityState = securityState, - signingKey = signingKey, - signedPublicKey = signedPublicKey, - ) - } else { - WrappedAccountCryptographicState.V1( - privateKey = privateKey, - ) - } -} diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceTest.kt index 2ffbf0f708..6133de6d19 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceTest.kt @@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.auth.datasource.disk import androidx.core.content.edit import app.cash.turbine.test import com.bitwarden.authenticatorbridge.util.generateSecretKey +import com.bitwarden.core.WrappedAccountCryptographicState import com.bitwarden.core.di.CoreModule import com.bitwarden.data.datasource.disk.base.FakeSharedPreferences import com.bitwarden.data.datasource.disk.model.EnvironmentUrlDataJson @@ -10,7 +11,6 @@ import com.bitwarden.network.model.KdfTypeJson import com.bitwarden.network.model.KeyConnectorUserDecryptionOptionsJson import com.bitwarden.network.model.TrustedDeviceUserDecryptionOptionsJson import com.bitwarden.network.model.UserDecryptionOptionsJson -import com.bitwarden.network.model.createMockAccountKeysJson import com.bitwarden.network.model.createMockOrganizationNetwork import com.bitwarden.network.model.createMockPolicy import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson @@ -19,6 +19,7 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.model.ForcePasswordResetRea import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus 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.auth.datasource.disk.serializer.WrappedAccountCryptographicStateSerializer import com.x8bit.bitwarden.data.platform.datasource.disk.legacy.LegacySecureStorageMigrator import io.mockk.every import io.mockk.just @@ -294,10 +295,9 @@ class AuthDiskSourceTest { userId = userId, userAutoUnlockKey = "userAutoUnlockKey", ) - authDiskSource.storePrivateKey(userId = userId, privateKey = "privateKey") - authDiskSource.storeAccountKeys( + authDiskSource.storeAccountCryptographicState( userId = userId, - accountKeys = createMockAccountKeysJson(number = 1), + accountCryptographicState = WrappedAccountCryptographicState.V1("privateKey"), ) authDiskSource.storeOrganizationKeys( userId = userId, @@ -345,8 +345,7 @@ class AuthDiskSourceTest { assertNull(authDiskSource.getUserBiometricUnlockKey(userId = userId)) assertNull(authDiskSource.getInvalidUnlockAttempts(userId = userId)) assertNull(authDiskSource.getUserAutoUnlockKey(userId = userId)) - assertNull(authDiskSource.getPrivateKey(userId = userId)) - assertNull(authDiskSource.getAccountKeys(userId = userId)) + assertNull(authDiskSource.getAccountCryptographicState(userId = userId)) assertNull(authDiskSource.getOrganizationKeys(userId = userId)) assertNull(authDiskSource.getOrganizations(userId = userId)) assertNull(authDiskSource.getPolicies(userId = userId)) @@ -441,76 +440,48 @@ class AuthDiskSourceTest { } @Test - fun `getPrivateKey should pull from SharedPreferences`() { - val privateKeyBaseKey = "bwPreferencesStorage:encPrivateKey" + fun `getAccountCryptographicState should pull from SharedPreferences`() { + val accountKeysBaseKey = "bwSecureStorage:accountCryptographicState" val mockUserId = "mockUserId" - val mockPrivateKey = "mockPrivateKey" - fakeSharedPreferences - .edit { - putString( - "${privateKeyBaseKey}_$mockUserId", - mockPrivateKey, - ) - } - val actual = authDiskSource.getPrivateKey(userId = mockUserId) - assertEquals( - mockPrivateKey, - actual, + val mockAccountCryptographicState = WrappedAccountCryptographicState.V1( + privateKey = "privateKey", ) - } - - @Test - fun `storePrivateKey should update SharedPreferences`() { - val privateKeyBaseKey = "bwPreferencesStorage:encPrivateKey" - val mockUserId = "mockUserId" - val mockPrivateKey = "mockPrivateKey" - authDiskSource.storePrivateKey( - userId = mockUserId, - privateKey = mockPrivateKey, - ) - val actual = fakeSharedPreferences.getString( - "${privateKeyBaseKey}_$mockUserId", - null, - ) - assertEquals( - mockPrivateKey, - actual, - ) - } - - @Test - fun `getAccountKeys should pull from SharedPreferences`() { - val accountKeysBaseKey = "bwSecureStorage:profileAccountKeys" - val mockUserId = "mockUserId" - val mockAccountKeys = createMockAccountKeysJson(number = 1) fakeEncryptedSharedPreferences.edit { putString( "${accountKeysBaseKey}_$mockUserId", - json.encodeToString(mockAccountKeys), + json.encodeToString( + WrappedAccountCryptographicStateSerializer(), + mockAccountCryptographicState, + ), ) } - val actual = authDiskSource.getAccountKeys(userId = mockUserId) + val actual = authDiskSource.getAccountCryptographicState(userId = mockUserId) assertEquals( - mockAccountKeys, + mockAccountCryptographicState, actual, ) } @Test - fun `storeAccountKeys should update sharedPreferences`() { - val accountKeysBaseKey = "bwSecureStorage:profileAccountKeys" + fun `storeAccountCryptographicState should update sharedPreferences`() { + val accountKeysBaseKey = "bwSecureStorage:accountCryptographicState" val mockUserId = "mockUserId" - val mockAccountKeys = createMockAccountKeysJson(number = 1) - authDiskSource.storeAccountKeys( + val mockAccountCryptographicState = WrappedAccountCryptographicState.V1( + privateKey = "privateKey", + ) + authDiskSource.storeAccountCryptographicState( userId = mockUserId, - accountKeys = mockAccountKeys, + accountCryptographicState = mockAccountCryptographicState, ) val actual = fakeEncryptedSharedPreferences.getString( "${accountKeysBaseKey}_$mockUserId", null, ) assertEquals( - json.encodeToJsonElement(mockAccountKeys), + json.encodeToJsonElement( + WrappedAccountCryptographicStateSerializer(), + mockAccountCryptographicState, + ), json.parseToJsonElement(requireNotNull(actual)), ) } diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/serializer/WrappedAccountCryptographicStateSerializerTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/serializer/WrappedAccountCryptographicStateSerializerTest.kt new file mode 100644 index 0000000000..c1c0df8819 --- /dev/null +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/serializer/WrappedAccountCryptographicStateSerializerTest.kt @@ -0,0 +1,163 @@ +package com.x8bit.bitwarden.data.auth.datasource.disk.serializer + +import com.bitwarden.core.WrappedAccountCryptographicState +import com.bitwarden.core.di.CoreModule +import io.mockk.mockk +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class WrappedAccountCryptographicStateSerializerTest { + private val json = CoreModule.providesJson(buildInfoManager = mockk(relaxed = true)) + private val serializer = WrappedAccountCryptographicStateSerializer() + + @Test + fun `serialize V1 produces JSON with v1 type discriminator`() { + assertEquals( + buildJsonObject { + put(key = "type", value = "v1") + put(key = "privateKey", value = "mockPrivateKey") + }, + json.encodeToJsonElement( + serializer = serializer, + value = WrappedAccountCryptographicState.V1(privateKey = "mockPrivateKey"), + ), + ) + } + + @Test + fun `serialize V2 produces JSON with v2 type discriminator and all properties`() { + assertEquals( + buildJsonObject { + put(key = "type", value = "v2") + put(key = "privateKey", value = "mockPrivateKey") + put(key = "signingKey", value = "mockSigningKey") + put(key = "signedPublicKey", value = "mockSignedPublicKey") + put(key = "securityState", value = "mockSecurityState") + }, + json.encodeToJsonElement( + serializer = serializer, + value = WrappedAccountCryptographicState.V2( + privateKey = "mockPrivateKey", + signingKey = "mockSigningKey", + signedPublicKey = "mockSignedPublicKey", + securityState = "mockSecurityState", + ), + ), + ) + } + + @Test + fun `serialize V2 with null signedPublicKey omits the signedPublicKey key`() { + assertEquals( + buildJsonObject { + put(key = "type", value = "v2") + put(key = "privateKey", value = "mockPrivateKey") + put(key = "signingKey", value = "mockSigningKey") + put(key = "securityState", value = "mockSecurityState") + }, + json.encodeToJsonElement( + serializer = serializer, + value = WrappedAccountCryptographicState.V2( + privateKey = "mockPrivateKey", + signingKey = "mockSigningKey", + signedPublicKey = null, + securityState = "mockSecurityState", + ), + ), + ) + } + + @Test + fun `deserialize v1 JSON produces a V1 state`() { + assertEquals( + WrappedAccountCryptographicState.V1(privateKey = "mockPrivateKey"), + json.decodeFromString( + deserializer = serializer, + string = """ + { + "type": "v1", + "privateKey": "mockPrivateKey" + } + """, + ), + ) + } + + @Test + fun `deserialize v2 JSON produces a V2 state`() { + assertEquals( + WrappedAccountCryptographicState.V2( + privateKey = "mockPrivateKey", + signingKey = "mockSigningKey", + signedPublicKey = "mockSignedPublicKey", + securityState = "mockSecurityState", + ), + json.decodeFromString( + deserializer = serializer, + string = """ + { + "type": "v2", + "privateKey": "mockPrivateKey", + "signingKey": "mockSigningKey", + "signedPublicKey": "mockSignedPublicKey", + "securityState": "mockSecurityState" + } + """, + ), + ) + } + + @Test + fun `deserialize v2 JSON with missing signedPublicKey produces a V2 state with null`() { + assertEquals( + WrappedAccountCryptographicState.V2( + privateKey = "mockPrivateKey", + signingKey = "mockSigningKey", + signedPublicKey = null, + securityState = "mockSecurityState", + ), + json.decodeFromString( + deserializer = serializer, + string = """ + { + "type": "v2", + "privateKey": "mockPrivateKey", + "signingKey": "mockSigningKey", + "securityState": "mockSecurityState" + } + """, + ), + ) + } + + @Test + fun `serialize then deserialize V1 returns an equivalent state`() { + val original = WrappedAccountCryptographicState.V1(privateKey = "mockPrivateKey") + assertEquals( + original, + json.decodeFromString( + deserializer = serializer, + string = json.encodeToString(serializer = serializer, value = original), + ), + ) + } + + @Test + fun `serialize then deserialize V2 returns an equivalent state`() { + val original = WrappedAccountCryptographicState.V2( + privateKey = "mockPrivateKey", + signingKey = "mockSigningKey", + signedPublicKey = "mockSignedPublicKey", + securityState = "mockSecurityState", + ) + assertEquals( + original, + json.decodeFromString( + deserializer = serializer, + string = json.encodeToString(serializer = serializer, value = original), + ), + ) + } +} diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/util/FakeAuthDiskSource.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/util/FakeAuthDiskSource.kt index b3d593fdd8..e6271872a3 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/util/FakeAuthDiskSource.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/util/FakeAuthDiskSource.kt @@ -1,7 +1,7 @@ package com.x8bit.bitwarden.data.auth.datasource.disk.util +import com.bitwarden.core.WrappedAccountCryptographicState import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow -import com.bitwarden.network.model.AccountKeysJson import com.bitwarden.network.model.SyncResponseJson import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson @@ -45,7 +45,6 @@ class FakeAuthDiskSource : AuthDiskSource { private val storedShouldTrustDevice = mutableMapOf() private val storedInvalidUnlockAttempts = mutableMapOf() private val storedLocalUserDataKeys = mutableMapOf() - private val storedPrivateKeys = mutableMapOf() private val storedTwoFactorTokens = mutableMapOf() private val storedUserAutoUnlockKeys = mutableMapOf() private val storedPinProtectedUserKeys = mutableMapOf>() @@ -64,7 +63,8 @@ class FakeAuthDiskSource : AuthDiskSource { private val storedOnboardingStatus = mutableMapOf() private val storedShowImportLogins = mutableMapOf() private val storedLastLockTimestampState = mutableMapOf() - private val storedAccountKeys = mutableMapOf() + private val storedAccountCryptographicState = + mutableMapOf() private val storedPinProtectedUserKeyEnvelopes = mutableMapOf>() private val mutablePinProtectedUserKeyEnvelopesFlowMap = mutableMapOf>() @@ -81,7 +81,7 @@ class FakeAuthDiskSource : AuthDiskSource { override fun clearData(userId: String) { storedInvalidUnlockAttempts.remove(userId) storedLocalUserDataKeys.remove(userId) - storedPrivateKeys.remove(userId) + storedAccountCryptographicState.remove(userId) storedTwoFactorTokens.clear() storedUserAutoUnlockKeys.remove(userId) storedOrganizations.remove(userId) @@ -150,22 +150,15 @@ class FakeAuthDiskSource : AuthDiskSource { storedLocalUserDataKeys[userId] = wrappedKey } - @Deprecated("Use getAccountKeys instead.", replaceWith = ReplaceWith("getAccountKeys")) - override fun getPrivateKey(userId: String): String? = storedPrivateKeys[userId] - - @Deprecated("Use storeAccountKeys instead.", replaceWith = ReplaceWith("storeAccountKeys")) - override fun storePrivateKey(userId: String, privateKey: String?) { - storedPrivateKeys[userId] = privateKey - } - - override fun getAccountKeys(userId: String): AccountKeysJson? = - storedAccountKeys[userId] - - override fun storeAccountKeys( + override fun getAccountCryptographicState( userId: String, - accountKeys: AccountKeysJson?, + ): WrappedAccountCryptographicState? = storedAccountCryptographicState[userId] + + override fun storeAccountCryptographicState( + userId: String, + accountCryptographicState: WrappedAccountCryptographicState?, ) { - storedAccountKeys[userId] = accountKeys + storedAccountCryptographicState[userId] = accountCryptographicState } override fun getTwoFactorToken(email: String): String? = storedTwoFactorTokens[email] @@ -415,18 +408,13 @@ class FakeAuthDiskSource : AuthDiskSource { } /** - * Assert that the [privateKey] was stored successfully using the [userId]. + * Assert that the [accountCryptographicState] was stored successfully using the [userId]. */ - @Deprecated("Use assertAccountKeys instead.", replaceWith = ReplaceWith("assertAccountKeys")) - fun assertPrivateKey(userId: String, privateKey: String?) { - assertEquals(privateKey, storedPrivateKeys[userId]) - } - - /** - * Assert that the [accountKeys] was stored successfully using the [userId]. - */ - fun assertAccountKeys(userId: String, accountKeys: AccountKeysJson?) { - assertEquals(accountKeys, storedAccountKeys[userId]) + fun assertAccountCryptographicState( + userId: String, + accountCryptographicState: WrappedAccountCryptographicState?, + ) { + assertEquals(accountCryptographicState, storedAccountCryptographicState[userId]) } /** diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryTest.kt index 2557c3b932..eed250bc7e 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryTest.kt @@ -126,11 +126,11 @@ import com.x8bit.bitwarden.data.auth.repository.model.ValidatePinResult import com.x8bit.bitwarden.data.auth.repository.model.VerifiedOrganizationDomainSsoDetailsResult import com.x8bit.bitwarden.data.auth.repository.model.VerifyOtpResult import com.x8bit.bitwarden.data.auth.repository.model.createMockOrganization +import com.x8bit.bitwarden.data.auth.repository.model.createMockWrappedAccountCryptographicState import com.x8bit.bitwarden.data.auth.repository.util.CookieCallbackResult import com.x8bit.bitwarden.data.auth.repository.util.DuoCallbackTokenResult import com.x8bit.bitwarden.data.auth.repository.util.SsoCallbackResult import com.x8bit.bitwarden.data.auth.repository.util.WebAuthResult -import com.x8bit.bitwarden.data.auth.repository.util.accountKeysJson import com.x8bit.bitwarden.data.auth.repository.util.toRemovedPasswordUserStateJson import com.x8bit.bitwarden.data.auth.repository.util.toSdkParams import com.x8bit.bitwarden.data.auth.repository.util.toUserState @@ -150,7 +150,6 @@ import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockPolicyView import com.x8bit.bitwarden.data.vault.repository.VaultRepository import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockData import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult -import com.x8bit.bitwarden.data.vault.repository.util.createWrappedAccountCryptographicState import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every @@ -424,20 +423,7 @@ class AuthRepositoryTest { } returns successResponse.asSuccess() coEvery { vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .wrappedPrivateKey, - securityState = successResponse.accountKeys - ?.securityState - ?.securityState, - signedPublicKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .signedPublicKey, - signingKey = successResponse.accountKeys - ?.signatureKeyPair - ?.wrappedSigningKey, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, userId = USER_ID_1, email = EMAIL, kdf = ACCOUNT_1.profile.toSdkParams(), @@ -484,9 +470,9 @@ class AuthRepositoryTest { assertEquals(LoginResult.Success, result) assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value) coVerify { identityService.preLogin(email = EMAIL) } - fakeAuthDiskSource.assertAccountKeys( + fakeAuthDiskSource.assertAccountCryptographicState( userId = USER_ID_1, - accountKeys = ACCOUNT_KEYS, + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, ) fakeAuthDiskSource.assertMasterPasswordHash( userId = USER_ID_1, @@ -503,20 +489,7 @@ class AuthRepositoryTest { deeplinkScheme = DEEPLINK_SCHEME, ) vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .wrappedPrivateKey, - securityState = successResponse.accountKeys - ?.securityState - ?.securityState, - signedPublicKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .signedPublicKey, - signingKey = successResponse.accountKeys - ?.signatureKeyPair - ?.wrappedSigningKey, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, userId = USER_ID_1, email = EMAIL, kdf = ACCOUNT_1.profile.toSdkParams(), @@ -1163,11 +1136,10 @@ class AuthRepositoryTest { val result = repository.createNewSsoUser() assertEquals(NewSsoUserResult.Success, result) - fakeAuthDiskSource.assertAccountKeys( + fakeAuthDiskSource.assertAccountCryptographicState( userId = USER_ID_1, - accountKeys = accountCryptographicState.accountKeysJson, + accountCryptographicState = accountCryptographicState, ) - fakeAuthDiskSource.assertPrivateKey(userId = USER_ID_1, privateKey = privateKey) fakeAuthDiskSource.assertDeviceKey(userId = USER_ID_1, deviceKey = deviceKey) coVerify(exactly = 1) { organizationService.getOrganizationAutoEnrollStatus(orgIdentifier) @@ -1415,8 +1387,13 @@ class AuthRepositoryTest { val result = repository.createNewSsoUser() - fakeAuthDiskSource.assertPrivateKey(userId = USER_ID_1, privateKey = userPrivateKey) - fakeAuthDiskSource.assertAccountKeys(userId = USER_ID_1, accountKeys = ACCOUNT_KEYS) + fakeAuthDiskSource.assertAccountCryptographicState( + userId = USER_ID_1, + accountCryptographicState = createMockWrappedAccountCryptographicState( + number = 1, + privateKey = userPrivateKey, + ), + ) assertEquals(NewSsoUserResult.Success, result) coVerify(exactly = 1) { organizationService.getOrganizationAutoEnrollStatus(orgIdentifier) @@ -1517,8 +1494,13 @@ class AuthRepositoryTest { val result = repository.createNewSsoUser() - fakeAuthDiskSource.assertPrivateKey(userId = USER_ID_1, privateKey = userPrivateKey) - fakeAuthDiskSource.assertAccountKeys(userId = USER_ID_1, accountKeys = ACCOUNT_KEYS) + fakeAuthDiskSource.assertAccountCryptographicState( + userId = USER_ID_1, + accountCryptographicState = createMockWrappedAccountCryptographicState( + number = 1, + privateKey = userPrivateKey, + ), + ) assertEquals(NewSsoUserResult.Success, result) coVerify(exactly = 1) { organizationService.getOrganizationAutoEnrollStatus(orgIdentifier) @@ -1598,22 +1580,18 @@ class AuthRepositoryTest { @Test fun `completeTdeLogin without account keys unlocks vault with private key when EnrollAeadOnKeyRotation is enabled`() = runTest { - val privateKey = "privateKey" val requestPrivateKey = "requestPrivateKey" val asymmetricalKey = "asymmetricalKey" val orgKeys = mapOf("orgId" to "orgKey") fakeAuthDiskSource.userState = SINGLE_USER_STATE_1 - fakeAuthDiskSource.storePrivateKey(userId = USER_ID_1, privateKey = privateKey) + fakeAuthDiskSource.storeAccountCryptographicState( + userId = USER_ID_1, + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V1, + ) fakeAuthDiskSource.storeOrganizationKeys(userId = USER_ID_1, organizationKeys = orgKeys) - fakeAuthDiskSource.storeAccountKeys(userId = USER_ID_1, accountKeys = null) coEvery { vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V1, userId = USER_ID_1, email = SINGLE_USER_STATE_1.activeAccount.profile.email, kdf = SINGLE_USER_STATE_1.activeAccount.profile.toSdkParams(), @@ -1632,12 +1610,7 @@ class AuthRepositoryTest { ) coVerify(exactly = 1) { vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V1, userId = USER_ID_1, email = SINGLE_USER_STATE_1.activeAccount.profile.email, kdf = SINGLE_USER_STATE_1.activeAccount.profile.toSdkParams(), @@ -1659,21 +1632,16 @@ class AuthRepositoryTest { runTest { val requestPrivateKey = "requestPrivateKey" val asymmetricKey = "asymmetricalKey" - val privateKey = "privateKey" - val accountKeys = createMockAccountKeysJson(number = 1) val orgKeys = mapOf("orgId" to "orgKey") fakeAuthDiskSource.userState = SINGLE_USER_STATE_1 - fakeAuthDiskSource.storePrivateKey(userId = USER_ID_1, privateKey = privateKey) - fakeAuthDiskSource.storeAccountKeys(userId = USER_ID_1, accountKeys = accountKeys) + fakeAuthDiskSource.storeAccountCryptographicState( + userId = USER_ID_1, + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, + ) fakeAuthDiskSource.storeOrganizationKeys(userId = USER_ID_1, organizationKeys = orgKeys) coEvery { vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = accountKeys.publicKeyEncryptionKeyPair.wrappedPrivateKey, - securityState = accountKeys.securityState?.securityState, - signedPublicKey = accountKeys.publicKeyEncryptionKeyPair.signedPublicKey, - signingKey = accountKeys.signatureKeyPair?.wrappedSigningKey, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, userId = USER_ID_1, email = SINGLE_USER_STATE_1.activeAccount.profile.email, kdf = SINGLE_USER_STATE_1.activeAccount.profile.toSdkParams(), @@ -1693,12 +1661,7 @@ class AuthRepositoryTest { coVerify(exactly = 1) { vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = accountKeys.publicKeyEncryptionKeyPair.wrappedPrivateKey, - securityState = accountKeys.securityState?.securityState, - signedPublicKey = accountKeys.publicKeyEncryptionKeyPair.signedPublicKey, - signingKey = accountKeys.signatureKeyPair?.wrappedSigningKey, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, userId = USER_ID_1, email = SINGLE_USER_STATE_1.activeAccount.profile.email, kdf = SINGLE_USER_STATE_1.activeAccount.profile.toSdkParams(), @@ -1720,19 +1683,16 @@ class AuthRepositoryTest { runTest { val requestPrivateKey = "requestPrivateKey" val asymmetricalKey = "asymmetricalKey" - val privateKey = "privateKey" val orgKeys = mapOf("orgId" to "orgKey") fakeAuthDiskSource.userState = SINGLE_USER_STATE_1 - fakeAuthDiskSource.storePrivateKey(userId = USER_ID_1, privateKey = privateKey) + fakeAuthDiskSource.storeAccountCryptographicState( + userId = USER_ID_1, + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V1, + ) fakeAuthDiskSource.storeOrganizationKeys(userId = USER_ID_1, organizationKeys = orgKeys) coEvery { vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V1, userId = USER_ID_1, email = SINGLE_USER_STATE_1.activeAccount.profile.email, kdf = SINGLE_USER_STATE_1.activeAccount.profile.toSdkParams(), @@ -1752,12 +1712,7 @@ class AuthRepositoryTest { coVerify(exactly = 1) { vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V1, userId = USER_ID_1, email = SINGLE_USER_STATE_1.activeAccount.profile.email, kdf = SINGLE_USER_STATE_1.activeAccount.profile.toSdkParams(), @@ -1777,20 +1732,17 @@ class AuthRepositoryTest { fun `completeTdeLogin where vault unlock fails should return LoginResult error`() = runTest { val requestPrivateKey = "requestPrivateKey" val asymmetricalKey = "asymmetricalKey" - val privateKey = "privateKey" val orgKeys = mapOf("orgId" to "orgKey") fakeAuthDiskSource.userState = SINGLE_USER_STATE_1 - fakeAuthDiskSource.storePrivateKey(userId = USER_ID_1, privateKey = privateKey) + fakeAuthDiskSource.storeAccountCryptographicState( + userId = USER_ID_1, + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V1, + ) fakeAuthDiskSource.storeOrganizationKeys(userId = USER_ID_1, organizationKeys = orgKeys) val error = Throwable("Fail") coEvery { vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V1, userId = USER_ID_1, email = SINGLE_USER_STATE_1.activeAccount.profile.email, kdf = SINGLE_USER_STATE_1.activeAccount.profile.toSdkParams(), @@ -1810,12 +1762,7 @@ class AuthRepositoryTest { coVerify(exactly = 1) { vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V1, userId = USER_ID_1, email = SINGLE_USER_STATE_1.activeAccount.profile.email, kdf = SINGLE_USER_STATE_1.activeAccount.profile.toSdkParams(), @@ -1839,19 +1786,17 @@ class AuthRepositoryTest { runTest { val requestPrivateKey = "requestPrivateKey" val asymmetricalKey = "asymmetricalKey" - val accountKeys = ACCOUNT_KEYS_WITH_NULL_FIELDS + val accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V1 val orgKeys = mapOf("orgId" to "orgKey") fakeAuthDiskSource.userState = SINGLE_USER_STATE_1 - fakeAuthDiskSource.storeAccountKeys(userId = USER_ID_1, accountKeys = accountKeys) + fakeAuthDiskSource.storeAccountCryptographicState( + userId = USER_ID_1, + accountCryptographicState = accountCryptographicState, + ) fakeAuthDiskSource.storeOrganizationKeys(userId = USER_ID_1, organizationKeys = orgKeys) coEvery { vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = accountKeys.publicKeyEncryptionKeyPair.wrappedPrivateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = accountCryptographicState, userId = USER_ID_1, email = SINGLE_USER_STATE_1.activeAccount.profile.email, kdf = SINGLE_USER_STATE_1.activeAccount.profile.toSdkParams(), @@ -1871,12 +1816,7 @@ class AuthRepositoryTest { coVerify(exactly = 1) { vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = accountKeys.publicKeyEncryptionKeyPair.wrappedPrivateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = accountCryptographicState, userId = USER_ID_1, email = SINGLE_USER_STATE_1.activeAccount.profile.email, kdf = SINGLE_USER_STATE_1.activeAccount.profile.toSdkParams(), @@ -2095,20 +2035,7 @@ class AuthRepositoryTest { } returns successResponse.asSuccess() coEvery { vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .wrappedPrivateKey, - securityState = successResponse.accountKeys - ?.securityState - ?.securityState, - signedPublicKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .signedPublicKey, - signingKey = successResponse.accountKeys - ?.signatureKeyPair - ?.wrappedSigningKey, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, userId = USER_ID_1, email = EMAIL, kdf = ACCOUNT_1.profile.toSdkParams(), @@ -2130,13 +2057,9 @@ class AuthRepositoryTest { assertEquals(LoginResult.Success, result) assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value) coVerify { identityService.preLogin(email = EMAIL) } - fakeAuthDiskSource.assertPrivateKey( + fakeAuthDiskSource.assertAccountCryptographicState( userId = USER_ID_1, - privateKey = "mockWrappedPrivateKey-1", - ) - fakeAuthDiskSource.assertAccountKeys( - userId = USER_ID_1, - accountKeys = ACCOUNT_KEYS, + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, ) fakeAuthDiskSource.assertMasterPasswordHash( userId = USER_ID_1, @@ -2153,20 +2076,7 @@ class AuthRepositoryTest { deeplinkScheme = DEEPLINK_SCHEME, ) vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .wrappedPrivateKey, - securityState = successResponse.accountKeys - ?.securityState - ?.securityState, - signedPublicKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .signedPublicKey, - signingKey = successResponse.accountKeys - ?.signatureKeyPair - ?.wrappedSigningKey, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, userId = USER_ID_1, email = EMAIL, kdf = ACCOUNT_1.profile.toSdkParams(), @@ -2196,6 +2106,9 @@ class AuthRepositoryTest { val successResponse = GET_TOKEN_WITH_ACCOUNT_KEYS_RESPONSE_SUCCESS.copy( accountKeys = ACCOUNT_KEYS_WITH_NULL_FIELDS, ) + val accountCryptographicState = WrappedAccountCryptographicState.V1( + privateKey = "mockWrappedPrivateKey-1", + ) coEvery { identityService.preLogin(email = EMAIL) } returns PRE_LOGIN_SUCCESS.asSuccess() @@ -2212,14 +2125,7 @@ class AuthRepositoryTest { } returns successResponse.asSuccess() coEvery { vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .wrappedPrivateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = accountCryptographicState, userId = USER_ID_1, email = EMAIL, kdf = ACCOUNT_1.profile.toSdkParams(), @@ -2241,13 +2147,9 @@ class AuthRepositoryTest { assertEquals(LoginResult.Success, result) assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value) coVerify { identityService.preLogin(email = EMAIL) } - fakeAuthDiskSource.assertPrivateKey( + fakeAuthDiskSource.assertAccountCryptographicState( userId = USER_ID_1, - privateKey = "mockWrappedPrivateKey-1", - ) - fakeAuthDiskSource.assertAccountKeys( - userId = USER_ID_1, - accountKeys = ACCOUNT_KEYS_WITH_NULL_FIELDS, + accountCryptographicState = accountCryptographicState, ) fakeAuthDiskSource.assertMasterPasswordHash( userId = USER_ID_1, @@ -2264,14 +2166,7 @@ class AuthRepositoryTest { deeplinkScheme = DEEPLINK_SCHEME, ) vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .wrappedPrivateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = accountCryptographicState, userId = USER_ID_1, email = EMAIL, kdf = ACCOUNT_1.profile.toSdkParams(), @@ -2317,20 +2212,7 @@ class AuthRepositoryTest { val error = Throwable("Fail") coEvery { vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .wrappedPrivateKey, - securityState = successResponse.accountKeys - ?.securityState - ?.securityState, - signedPublicKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .signedPublicKey, - signingKey = successResponse.accountKeys - ?.signatureKeyPair - ?.wrappedSigningKey, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, userId = USER_ID_1, email = EMAIL, kdf = ACCOUNT_1.profile.toSdkParams(), @@ -2358,13 +2240,9 @@ class AuthRepositoryTest { ) assertEquals(AuthState.Unauthenticated, repository.authStateFlow.value) coVerify { identityService.preLogin(email = EMAIL) } - fakeAuthDiskSource.assertPrivateKey( + fakeAuthDiskSource.assertAccountCryptographicState( userId = USER_ID_1, - privateKey = null, - ) - fakeAuthDiskSource.assertAccountKeys( - userId = USER_ID_1, - accountKeys = null, + accountCryptographicState = null, ) fakeAuthDiskSource.assertMasterPasswordHash( userId = USER_ID_1, @@ -2382,20 +2260,7 @@ class AuthRepositoryTest { ) vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .wrappedPrivateKey, - securityState = successResponse.accountKeys - ?.securityState - ?.securityState, - signedPublicKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .signedPublicKey, - signingKey = successResponse.accountKeys - ?.signatureKeyPair - ?.wrappedSigningKey, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, userId = USER_ID_1, email = EMAIL, kdf = ACCOUNT_1.profile.toSdkParams(), @@ -2488,20 +2353,7 @@ class AuthRepositoryTest { } coVerify(exactly = 0) { vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .wrappedPrivateKey, - securityState = successResponse.accountKeys - ?.securityState - ?.securityState, - signedPublicKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .signedPublicKey, - signingKey = successResponse.accountKeys - ?.signatureKeyPair - ?.wrappedSigningKey, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, userId = USER_ID_1, email = EMAIL, kdf = ACCOUNT_1.profile.toSdkParams(), @@ -2537,20 +2389,7 @@ class AuthRepositoryTest { } returns successResponse.asSuccess() coEvery { vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .wrappedPrivateKey, - securityState = successResponse.accountKeys - ?.securityState - ?.securityState, - signedPublicKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .signedPublicKey, - signingKey = successResponse.accountKeys - ?.signatureKeyPair - ?.wrappedSigningKey, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, userId = USER_ID_1, email = EMAIL, kdf = ACCOUNT_1.profile.toSdkParams(), @@ -2586,13 +2425,9 @@ class AuthRepositoryTest { assertEquals(LoginResult.Success, result) assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value) coVerify { identityService.preLogin(email = EMAIL) } - fakeAuthDiskSource.assertPrivateKey( + fakeAuthDiskSource.assertAccountCryptographicState( userId = USER_ID_1, - privateKey = "mockWrappedPrivateKey-1", - ) - fakeAuthDiskSource.assertAccountKeys( - userId = USER_ID_1, - accountKeys = ACCOUNT_KEYS, + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, ) coVerify { identityService.getToken( @@ -2605,20 +2440,7 @@ class AuthRepositoryTest { deeplinkScheme = DEEPLINK_SCHEME, ) vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .wrappedPrivateKey, - securityState = successResponse.accountKeys - ?.securityState - ?.securityState, - signedPublicKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .signedPublicKey, - signingKey = successResponse.accountKeys - ?.signatureKeyPair - ?.wrappedSigningKey, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, userId = USER_ID_1, email = EMAIL, kdf = ACCOUNT_1.profile.toSdkParams(), @@ -2743,20 +2565,7 @@ class AuthRepositoryTest { } returns successResponse.asSuccess() coEvery { vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .wrappedPrivateKey, - securityState = successResponse.accountKeys - ?.securityState - ?.securityState, - signedPublicKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .signedPublicKey, - signingKey = successResponse.accountKeys - ?.signatureKeyPair - ?.wrappedSigningKey, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, userId = USER_ID_1, email = EMAIL, kdf = ACCOUNT_1.profile.toSdkParams(), @@ -2853,20 +2662,7 @@ class AuthRepositoryTest { val error = Throwable("Fail") coEvery { vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .wrappedPrivateKey, - securityState = successResponse.accountKeys - ?.securityState - ?.securityState, - signedPublicKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .signedPublicKey, - signingKey = successResponse.accountKeys - ?.signatureKeyPair - ?.wrappedSigningKey, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, userId = USER_ID_1, email = EMAIL, kdf = ACCOUNT_1.profile.toSdkParams(), @@ -2928,20 +2724,7 @@ class AuthRepositoryTest { } returns successResponse.asSuccess() coEvery { vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .wrappedPrivateKey, - securityState = successResponse.accountKeys - ?.securityState - ?.securityState, - signedPublicKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .signedPublicKey, - signingKey = successResponse.accountKeys - ?.signatureKeyPair - ?.wrappedSigningKey, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, userId = USER_ID_1, email = EMAIL, kdf = ACCOUNT_1.profile.toSdkParams(), @@ -2963,13 +2746,9 @@ class AuthRepositoryTest { assertEquals(LoginResult.Success, result) assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value) coVerify { identityService.preLogin(email = EMAIL) } - fakeAuthDiskSource.assertPrivateKey( + fakeAuthDiskSource.assertAccountCryptographicState( userId = USER_ID_1, - privateKey = "mockWrappedPrivateKey-1", - ) - fakeAuthDiskSource.assertAccountKeys( - userId = USER_ID_1, - accountKeys = ACCOUNT_KEYS, + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, ) coVerify { identityService.getToken( @@ -2983,20 +2762,7 @@ class AuthRepositoryTest { deeplinkScheme = DEEPLINK_SCHEME, ) vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .wrappedPrivateKey, - securityState = successResponse.accountKeys - ?.securityState - ?.securityState, - signedPublicKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .signedPublicKey, - signingKey = successResponse.accountKeys - ?.signatureKeyPair - ?.wrappedSigningKey, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, userId = USER_ID_1, email = EMAIL, kdf = ACCOUNT_1.profile.toSdkParams(), @@ -3188,20 +2954,7 @@ class AuthRepositoryTest { } returns SINGLE_USER_STATE_1 coEvery { vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .wrappedPrivateKey, - securityState = successResponse.accountKeys - ?.securityState - ?.securityState, - signedPublicKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .signedPublicKey, - signingKey = successResponse.accountKeys - ?.signatureKeyPair - ?.wrappedSigningKey, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, userId = USER_ID_1, email = EMAIL, kdf = ACCOUNT_1.profile.toSdkParams(), @@ -3225,13 +2978,9 @@ class AuthRepositoryTest { ) assertEquals(LoginResult.Success, result) assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value) - fakeAuthDiskSource.assertPrivateKey( + fakeAuthDiskSource.assertAccountCryptographicState( userId = USER_ID_1, - privateKey = "mockWrappedPrivateKey-1", - ) - fakeAuthDiskSource.assertAccountKeys( - userId = USER_ID_1, - accountKeys = ACCOUNT_KEYS, + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, ) coVerify { identityService.getToken( @@ -3246,20 +2995,7 @@ class AuthRepositoryTest { ) vaultRepository.syncIfNecessary() vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .wrappedPrivateKey, - securityState = successResponse.accountKeys - ?.securityState - ?.securityState, - signedPublicKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .signedPublicKey, - signingKey = successResponse.accountKeys - ?.signatureKeyPair - ?.wrappedSigningKey, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, userId = USER_ID_1, email = EMAIL, kdf = ACCOUNT_1.profile.toSdkParams(), @@ -3310,20 +3046,7 @@ class AuthRepositoryTest { } returns SINGLE_USER_STATE_1 coEvery { vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .wrappedPrivateKey, - securityState = successResponse.accountKeys - ?.securityState - ?.securityState, - signedPublicKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .signedPublicKey, - signingKey = successResponse.accountKeys - ?.signatureKeyPair - ?.wrappedSigningKey, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, userId = USER_ID_1, email = EMAIL, kdf = ACCOUNT_1.profile.toSdkParams(), @@ -3347,13 +3070,9 @@ class AuthRepositoryTest { ) assertEquals(LoginResult.Success, result) assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value) - fakeAuthDiskSource.assertPrivateKey( + fakeAuthDiskSource.assertAccountCryptographicState( userId = USER_ID_1, - privateKey = "mockWrappedPrivateKey-1", - ) - fakeAuthDiskSource.assertAccountKeys( - userId = USER_ID_1, - accountKeys = ACCOUNT_KEYS, + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, ) coVerify { identityService.getToken( @@ -3368,20 +3087,7 @@ class AuthRepositoryTest { ) vaultRepository.syncIfNecessary() vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .wrappedPrivateKey, - securityState = successResponse.accountKeys - ?.securityState - ?.securityState, - signedPublicKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .signedPublicKey, - signingKey = successResponse.accountKeys - ?.signatureKeyPair - ?.wrappedSigningKey, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, userId = USER_ID_1, email = EMAIL, kdf = ACCOUNT_1.profile.toSdkParams(), @@ -3528,20 +3234,7 @@ class AuthRepositoryTest { } returns SINGLE_USER_STATE_1 coEvery { vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .wrappedPrivateKey, - securityState = successResponse.accountKeys - ?.securityState - ?.securityState, - signedPublicKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .signedPublicKey, - signingKey = successResponse.accountKeys - ?.signatureKeyPair - ?.wrappedSigningKey, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, userId = SINGLE_USER_STATE_1.activeUserId, email = SINGLE_USER_STATE_1.activeAccount.profile.email, kdf = SINGLE_USER_STATE_1.activeAccount.profile.toSdkParams(), @@ -3687,13 +3380,9 @@ class AuthRepositoryTest { ) assertEquals(LoginResult.Success, result) assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value) - fakeAuthDiskSource.assertPrivateKey( + fakeAuthDiskSource.assertAccountCryptographicState( userId = USER_ID_1, - privateKey = "mockWrappedPrivateKey-1", - ) - fakeAuthDiskSource.assertAccountKeys( - userId = USER_ID_1, - accountKeys = ACCOUNT_KEYS, + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, ) coVerify { identityService.getToken( @@ -3759,11 +3448,10 @@ class AuthRepositoryTest { ) assertEquals(LoginResult.Success, result) assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value) - fakeAuthDiskSource.assertPrivateKey( + fakeAuthDiskSource.assertAccountCryptographicState( userId = USER_ID_1, - privateKey = "mockWrappedPrivateKey-1", + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, ) - fakeAuthDiskSource.assertAccountKeys(userId = USER_ID_1, accountKeys = ACCOUNT_KEYS) coVerify(exactly = 1) { identityService.getToken( email = EMAIL, @@ -3811,12 +3499,7 @@ class AuthRepositoryTest { } returns successResponse.asSuccess() coEvery { vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = "mockWrappedPrivateKey-1", - securityState = "mockSecurityState-1", - signedPublicKey = "mockSignedPublicKey-1", - signingKey = "mockWrappedSigningKey-1", - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, userId = USER_ID_1, email = EMAIL, kdf = ACCOUNT_1.profile.toSdkParams(), @@ -3843,8 +3526,10 @@ class AuthRepositoryTest { ) assertEquals(LoginResult.Error(error = error), result) - fakeAuthDiskSource.assertPrivateKey(userId = USER_ID_1, privateKey = null) - fakeAuthDiskSource.assertAccountKeys(userId = USER_ID_1, accountKeys = null) + fakeAuthDiskSource.assertAccountCryptographicState( + userId = USER_ID_1, + accountCryptographicState = null, + ) fakeAuthDiskSource.assertAccountTokens(userId = USER_ID_1, accountTokens = null) coVerify(exactly = 1) { identityService.getToken( @@ -3858,12 +3543,7 @@ class AuthRepositoryTest { deeplinkScheme = DEEPLINK_SCHEME, ) vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = "mockWrappedPrivateKey-1", - securityState = "mockSecurityState-1", - signedPublicKey = "mockSignedPublicKey-1", - signingKey = "mockWrappedSigningKey-1", - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, userId = USER_ID_1, email = EMAIL, kdf = ACCOUNT_1.profile.toSdkParams(), @@ -3888,6 +3568,7 @@ class AuthRepositoryTest { trustedDeviceUserDecryptionOptions = null, ), ) + val accountCryptographicState = WrappedAccountCryptographicState.V1("privateKey") coEvery { identityService.getToken( email = EMAIL, @@ -3902,12 +3583,7 @@ class AuthRepositoryTest { } returns successResponse.asSuccess() coEvery { vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = "privateKey", - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = accountCryptographicState, userId = USER_ID_1, email = EMAIL, kdf = ACCOUNT_1.profile.toSdkParams(), @@ -3935,7 +3611,10 @@ class AuthRepositoryTest { assertEquals(LoginResult.Success, result) assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value) - fakeAuthDiskSource.assertPrivateKey(userId = USER_ID_1, privateKey = "privateKey") + fakeAuthDiskSource.assertAccountCryptographicState( + userId = USER_ID_1, + accountCryptographicState = accountCryptographicState, + ) coVerify(exactly = 1) { identityService.getToken( email = EMAIL, @@ -3948,12 +3627,7 @@ class AuthRepositoryTest { deeplinkScheme = DEEPLINK_SCHEME, ) vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = "privateKey", - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = accountCryptographicState, userId = USER_ID_1, email = EMAIL, kdf = ACCOUNT_1.profile.toSdkParams(), @@ -3985,6 +3659,9 @@ class AuthRepositoryTest { trustedDeviceUserDecryptionOptions = null, ), ) + val accountCryptographicState = WrappedAccountCryptographicState.V1( + privateKey = "mockWrappedPrivateKey-1", + ) coEvery { identityService.getToken( email = EMAIL, @@ -3999,12 +3676,7 @@ class AuthRepositoryTest { } returns successResponse.asSuccess() coEvery { vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = "mockWrappedPrivateKey-1", - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = accountCryptographicState, userId = USER_ID_1, email = EMAIL, kdf = ACCOUNT_1.profile.toSdkParams(), @@ -4032,9 +3704,9 @@ class AuthRepositoryTest { assertEquals(LoginResult.Success, result) assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value) - fakeAuthDiskSource.assertPrivateKey( + fakeAuthDiskSource.assertAccountCryptographicState( userId = USER_ID_1, - privateKey = "mockWrappedPrivateKey-1", + accountCryptographicState = accountCryptographicState, ) coVerify(exactly = 1) { identityService.getToken( @@ -4048,12 +3720,7 @@ class AuthRepositoryTest { deeplinkScheme = DEEPLINK_SCHEME, ) vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = "mockWrappedPrivateKey-1", - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = accountCryptographicState, userId = USER_ID_1, email = EMAIL, kdf = ACCOUNT_1.profile.toSdkParams(), @@ -4135,8 +3802,10 @@ class AuthRepositoryTest { email = EMAIL, ) assertEquals(LoginResult.Error(error = error), continueResult) - fakeAuthDiskSource.assertPrivateKey(userId = USER_ID_1, privateKey = null) - fakeAuthDiskSource.assertAccountKeys(userId = USER_ID_1, accountKeys = null) + fakeAuthDiskSource.assertAccountCryptographicState( + userId = USER_ID_1, + accountCryptographicState = null, + ) coVerify(exactly = 1) { identityService.getToken( email = EMAIL, @@ -4246,7 +3915,10 @@ class AuthRepositoryTest { ) assertEquals(LoginResult.Success, continueResult) assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value) - fakeAuthDiskSource.assertPrivateKey(userId = USER_ID_1, privateKey = PRIVATE_KEY) + fakeAuthDiskSource.assertAccountCryptographicState( + userId = USER_ID_1, + accountCryptographicState = keyConnectorResult.accountCryptographicState, + ) coVerify(exactly = 1) { identityService.getToken( email = EMAIL, @@ -4420,7 +4092,10 @@ class AuthRepositoryTest { ) assertEquals(LoginResult.Success, result) assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value) - fakeAuthDiskSource.assertPrivateKey(userId = USER_ID_1, privateKey = "privateKey") + fakeAuthDiskSource.assertAccountCryptographicState( + userId = USER_ID_1, + accountCryptographicState = keyConnectorResult.accountCryptographicState, + ) coVerify(exactly = 1) { identityService.getToken( email = EMAIL, @@ -4467,20 +4142,7 @@ class AuthRepositoryTest { } returns SINGLE_USER_STATE_1 coEvery { vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .wrappedPrivateKey, - securityState = successResponse.accountKeys - ?.securityState - ?.securityState, - signedPublicKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .signedPublicKey, - signingKey = successResponse.accountKeys - ?.signatureKeyPair - ?.wrappedSigningKey, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, userId = USER_ID_1, email = EMAIL, kdf = ACCOUNT_1.profile.toSdkParams(), @@ -4503,13 +4165,9 @@ class AuthRepositoryTest { ) assertEquals(LoginResult.Success, result) assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value) - fakeAuthDiskSource.assertPrivateKey( + fakeAuthDiskSource.assertAccountCryptographicState( userId = USER_ID_1, - privateKey = "mockWrappedPrivateKey-1", - ) - fakeAuthDiskSource.assertAccountKeys( - userId = USER_ID_1, - accountKeys = ACCOUNT_KEYS, + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, ) coVerify { identityService.getToken( @@ -4524,20 +4182,7 @@ class AuthRepositoryTest { ) vaultRepository.syncIfNecessary() vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .wrappedPrivateKey, - securityState = successResponse.accountKeys - ?.securityState - ?.securityState, - signedPublicKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .signedPublicKey, - signingKey = successResponse.accountKeys - ?.signatureKeyPair - ?.wrappedSigningKey, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, userId = USER_ID_1, email = EMAIL, kdf = ACCOUNT_1.profile.toSdkParams(), @@ -4600,11 +4245,10 @@ class AuthRepositoryTest { assertEquals(LoginResult.Success, result) assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value) - fakeAuthDiskSource.assertPrivateKey( + fakeAuthDiskSource.assertAccountCryptographicState( userId = USER_ID_1, - privateKey = "mockWrappedPrivateKey-1", + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, ) - fakeAuthDiskSource.assertAccountKeys(userId = USER_ID_1, accountKeys = ACCOUNT_KEYS) fakeAuthDiskSource.assertDeviceKey(userId = USER_ID_1, deviceKey = null) assertEquals(SINGLE_USER_STATE_1, fakeAuthDiskSource.userState) coVerify(exactly = 1) { @@ -4645,20 +4289,7 @@ class AuthRepositoryTest { ) coEvery { vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .wrappedPrivateKey, - securityState = successResponse.accountKeys - ?.securityState - ?.securityState, - signedPublicKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .signedPublicKey, - signingKey = successResponse.accountKeys - ?.signatureKeyPair - ?.wrappedSigningKey, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, userId = USER_ID_1, email = SINGLE_USER_STATE_1.activeAccount.profile.email, kdf = SINGLE_USER_STATE_1.activeAccount.profile.toSdkParams(), @@ -4700,11 +4331,11 @@ class AuthRepositoryTest { assertEquals(LoginResult.Success, result) assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value) - fakeAuthDiskSource.assertPrivateKey( + + fakeAuthDiskSource.assertAccountCryptographicState( userId = USER_ID_1, - privateKey = "mockWrappedPrivateKey-1", + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, ) - fakeAuthDiskSource.assertAccountKeys(userId = USER_ID_1, accountKeys = ACCOUNT_KEYS) fakeAuthDiskSource.assertDeviceKey(userId = USER_ID_1, deviceKey = deviceKey) assertEquals(SINGLE_USER_STATE_1, fakeAuthDiskSource.userState) coVerify { @@ -4719,20 +4350,7 @@ class AuthRepositoryTest { deeplinkScheme = DEEPLINK_SCHEME, ) vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .wrappedPrivateKey, - securityState = successResponse.accountKeys - ?.securityState - ?.securityState, - signedPublicKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .signedPublicKey, - signingKey = successResponse.accountKeys - ?.signatureKeyPair - ?.wrappedSigningKey, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, userId = USER_ID_1, email = SINGLE_USER_STATE_1.activeAccount.profile.email, kdf = SINGLE_USER_STATE_1.activeAccount.profile.toSdkParams(), @@ -4775,20 +4393,7 @@ class AuthRepositoryTest { fakeAuthDiskSource.storePendingAuthRequest(USER_ID_1, pendingAuthRequest) coEvery { vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .wrappedPrivateKey, - securityState = successResponse.accountKeys - ?.securityState - ?.securityState, - signedPublicKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .signedPublicKey, - signingKey = successResponse.accountKeys - ?.signatureKeyPair - ?.wrappedSigningKey, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, userId = USER_ID_1, email = SINGLE_USER_STATE_1.activeAccount.profile.email, kdf = SINGLE_USER_STATE_1.activeAccount.profile.toSdkParams(), @@ -4829,11 +4434,10 @@ class AuthRepositoryTest { assertEquals(LoginResult.Success, result) assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value) - fakeAuthDiskSource.assertPrivateKey( + fakeAuthDiskSource.assertAccountCryptographicState( userId = USER_ID_1, - privateKey = "mockWrappedPrivateKey-1", + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, ) - fakeAuthDiskSource.assertAccountKeys(userId = USER_ID_1, accountKeys = ACCOUNT_KEYS) assertEquals(SINGLE_USER_STATE_1, fakeAuthDiskSource.userState) coVerify(exactly = 1) { identityService.getToken( @@ -4847,20 +4451,7 @@ class AuthRepositoryTest { deeplinkScheme = DEEPLINK_SCHEME, ) vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .wrappedPrivateKey, - securityState = successResponse.accountKeys - ?.securityState - ?.securityState, - signedPublicKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .signedPublicKey, - signingKey = successResponse.accountKeys - ?.signatureKeyPair - ?.wrappedSigningKey, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, userId = USER_ID_1, email = SINGLE_USER_STATE_1.activeAccount.profile.email, kdf = SINGLE_USER_STATE_1.activeAccount.profile.toSdkParams(), @@ -4918,13 +4509,9 @@ class AuthRepositoryTest { assertEquals(LoginResult.Success, result) assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value) - fakeAuthDiskSource.assertPrivateKey( + fakeAuthDiskSource.storeAccountCryptographicState( userId = USER_ID_1, - privateKey = "mockWrappedPrivateKey-1", - ) - fakeAuthDiskSource.assertAccountKeys( - userId = USER_ID_1, - accountKeys = ACCOUNT_KEYS, + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, ) coVerify { identityService.getToken( @@ -5126,13 +4713,9 @@ class AuthRepositoryTest { ) assertEquals(LoginResult.Success, result) assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value) - fakeAuthDiskSource.assertPrivateKey( + fakeAuthDiskSource.storeAccountCryptographicState( userId = USER_ID_1, - privateKey = "mockWrappedPrivateKey-1", - ) - fakeAuthDiskSource.assertAccountKeys( - userId = USER_ID_1, - accountKeys = ACCOUNT_KEYS, + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, ) coVerify { identityService.getToken( @@ -5614,8 +5197,10 @@ class AuthRepositoryTest { assertEquals(SetPasswordResult.Error(error = NoActiveUserException()), result) fakeAuthDiskSource.assertMasterPasswordHash(userId = USER_ID_1, passwordHash = null) - fakeAuthDiskSource.assertPrivateKey(userId = USER_ID_1, privateKey = null) - fakeAuthDiskSource.assertAccountKeys(userId = USER_ID_1, accountKeys = null) + fakeAuthDiskSource.storeAccountCryptographicState( + userId = USER_ID_1, + accountCryptographicState = null, + ) } @Test @@ -5643,8 +5228,10 @@ class AuthRepositoryTest { ) assertEquals(SetPasswordResult.Error(error = error), result) - fakeAuthDiskSource.assertPrivateKey(userId = USER_ID_1, privateKey = null) - fakeAuthDiskSource.assertAccountKeys(userId = USER_ID_1, accountKeys = null) + fakeAuthDiskSource.storeAccountCryptographicState( + userId = USER_ID_1, + accountCryptographicState = null, + ) } @Test @@ -5667,8 +5254,10 @@ class AuthRepositoryTest { coVerify(exactly = 1) { organizationService.getOrganizationAutoEnrollStatus(organizationIdentifier) } - fakeAuthDiskSource.assertPrivateKey(userId = USER_ID_1, privateKey = null) - fakeAuthDiskSource.assertAccountKeys(userId = USER_ID_1, accountKeys = null) + fakeAuthDiskSource.storeAccountCryptographicState( + userId = USER_ID_1, + accountCryptographicState = null, + ) } @Test @@ -5700,8 +5289,10 @@ class AuthRepositoryTest { organizationService.getOrganizationAutoEnrollStatus(organizationIdentifier) organizationService.getOrganizationKeys(organizationId) } - fakeAuthDiskSource.assertPrivateKey(userId = USER_ID_1, privateKey = null) - fakeAuthDiskSource.assertAccountKeys(userId = USER_ID_1, accountKeys = null) + fakeAuthDiskSource.storeAccountCryptographicState( + userId = USER_ID_1, + accountCryptographicState = null, + ) } @Test @@ -5763,8 +5354,10 @@ class AuthRepositoryTest { shouldResetPasswordEnroll = isResetPasswordEnabled, ) } - fakeAuthDiskSource.assertPrivateKey(userId = USER_ID_1, privateKey = null) - fakeAuthDiskSource.assertAccountKeys(userId = USER_ID_1, accountKeys = null) + fakeAuthDiskSource.storeAccountCryptographicState( + userId = USER_ID_1, + accountCryptographicState = null, + ) } @Test @@ -5847,10 +5440,9 @@ class AuthRepositoryTest { ) vaultRepository.unlockVaultWithMasterPassword(password) } - fakeAuthDiskSource.assertPrivateKey(userId = USER_ID_1, privateKey = privateKey) - fakeAuthDiskSource.assertAccountKeys( + fakeAuthDiskSource.storeAccountCryptographicState( userId = USER_ID_1, - accountKeys = accountCryptographicState.accountKeysJson, + accountCryptographicState = accountCryptographicState, ) } @@ -5932,10 +5524,9 @@ class AuthRepositoryTest { ) vaultRepository.unlockVaultWithMasterPassword(password) } - fakeAuthDiskSource.assertPrivateKey(userId = USER_ID_1, privateKey = privateKey) - fakeAuthDiskSource.assertAccountKeys( + fakeAuthDiskSource.assertAccountCryptographicState( userId = USER_ID_1, - accountKeys = accountCryptographicState.accountKeysJson, + accountCryptographicState = accountCryptographicState, ) } @@ -5964,8 +5555,10 @@ class AuthRepositoryTest { ) assertEquals(SetPasswordResult.Error(error = error), result) - fakeAuthDiskSource.assertPrivateKey(userId = USER_ID_1, privateKey = null) - fakeAuthDiskSource.assertAccountKeys(userId = USER_ID_1, accountKeys = null) + fakeAuthDiskSource.storeAccountCryptographicState( + userId = USER_ID_1, + accountCryptographicState = null, + ) } @Test @@ -6018,8 +5611,10 @@ class AuthRepositoryTest { ) assertEquals(SetPasswordResult.Error(error = error), result) - fakeAuthDiskSource.assertPrivateKey(userId = USER_ID_1, privateKey = null) - fakeAuthDiskSource.assertAccountKeys(userId = USER_ID_1, accountKeys = null) + fakeAuthDiskSource.storeAccountCryptographicState( + userId = USER_ID_1, + accountCryptographicState = null, + ) } @Test @@ -6105,7 +5700,10 @@ class AuthRepositoryTest { ) assertEquals(SetPasswordResult.Success, result) - fakeAuthDiskSource.assertPrivateKey(userId = USER_ID_1, privateKey = privateRsaKey) + fakeAuthDiskSource.storeAccountCryptographicState( + userId = USER_ID_1, + accountCryptographicState = WrappedAccountCryptographicState.V1(privateRsaKey), + ) fakeAuthDiskSource.assertUserState(SINGLE_USER_STATE_1_WITH_PASS) coVerify(exactly = 1) { authSdkSource.makeRegisterKeys(email = EMAIL, password = password, kdf = kdf) @@ -6208,8 +5806,10 @@ class AuthRepositoryTest { ) assertEquals(SetPasswordResult.Success, result) - fakeAuthDiskSource.assertPrivateKey(userId = USER_ID_1, privateKey = null) - fakeAuthDiskSource.assertAccountKeys(userId = USER_ID_1, accountKeys = null) + fakeAuthDiskSource.assertAccountCryptographicState( + userId = USER_ID_1, + accountCryptographicState = null, + ) fakeAuthDiskSource.assertUserState(SINGLE_USER_STATE_1_WITH_PASS) coVerify(exactly = 1) { vaultSdkSource.updatePassword(userId = USER_ID_1, newPassword = password) @@ -6286,7 +5886,12 @@ class AuthRepositoryTest { ) assertEquals(SetPasswordResult.Error(error = error), result) - fakeAuthDiskSource.assertPrivateKey(userId = USER_ID_1, privateKey = privateRsaKey) + fakeAuthDiskSource.assertAccountCryptographicState( + userId = USER_ID_1, + accountCryptographicState = WrappedAccountCryptographicState.V1( + privateKey = privateRsaKey, + ), + ) fakeAuthDiskSource.assertUserState(SINGLE_USER_STATE_1) coVerify(exactly = 1) { authSdkSource.makeRegisterKeys(email = EMAIL, password = password, kdf = kdf) @@ -7559,20 +7164,7 @@ class AuthRepositoryTest { } returns successResponse.asSuccess() coEvery { vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .wrappedPrivateKey, - securityState = successResponse.accountKeys - ?.securityState - ?.securityState, - signedPublicKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .signedPublicKey, - signingKey = successResponse.accountKeys - ?.signatureKeyPair - ?.wrappedSigningKey, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, userId = USER_ID_1, email = EMAIL, kdf = ACCOUNT_1.profile.toSdkParams(), @@ -7595,13 +7187,9 @@ class AuthRepositoryTest { assertEquals(LoginResult.Success, result) assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value) coVerify { identityService.preLogin(email = EMAIL) } - fakeAuthDiskSource.assertPrivateKey( + fakeAuthDiskSource.assertAccountCryptographicState( userId = USER_ID_1, - privateKey = "mockWrappedPrivateKey-1", - ) - fakeAuthDiskSource.assertAccountKeys( - userId = USER_ID_1, - accountKeys = ACCOUNT_KEYS, + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, ) fakeAuthDiskSource.assertMasterPasswordHash( userId = USER_ID_1, @@ -7634,20 +7222,7 @@ class AuthRepositoryTest { } returns successResponse.asSuccess() coEvery { vaultRepository.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .wrappedPrivateKey, - securityState = successResponse.accountKeys - ?.securityState - ?.securityState, - signedPublicKey = successResponse.accountKeys!! - .publicKeyEncryptionKeyPair - .signedPublicKey, - signingKey = successResponse.accountKeys - ?.signatureKeyPair - ?.wrappedSigningKey, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, userId = USER_ID_1, email = EMAIL, kdf = ACCOUNT_1.profile.toSdkParams(), @@ -7670,13 +7245,9 @@ class AuthRepositoryTest { assertEquals(LoginResult.Success, result) assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value) coVerify { identityService.preLogin(email = EMAIL) } - fakeAuthDiskSource.assertPrivateKey( + fakeAuthDiskSource.assertAccountCryptographicState( userId = USER_ID_1, - privateKey = "mockWrappedPrivateKey-1", - ) - fakeAuthDiskSource.assertAccountKeys( - userId = USER_ID_1, - accountKeys = ACCOUNT_KEYS, + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE_V2, ) fakeAuthDiskSource.assertMasterPasswordHash( userId = USER_ID_1, @@ -7787,6 +7358,10 @@ class AuthRepositoryTest { private val ACCOUNT_KEYS = createMockAccountKeysJson(number = 1) private val ACCOUNT_KEYS_WITH_NULL_FIELDS = createMockAccountKeysJsonWithNullFields(number = 1) + private val ACCOUNT_CRYPTOGRAPHIC_STATE_V2 = + createMockWrappedAccountCryptographicState(number = 1) + private val ACCOUNT_CRYPTOGRAPHIC_STATE_V1 = + WrappedAccountCryptographicState.V1(privateKey = "privateKey") private val TWO_FACTOR_AUTH_METHODS_DATA = mapOf( TwoFactorAuthMethod.EMAIL to JsonObject( mapOf("Email" to JsonPrimitive("ex***@email.com")), diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/repository/model/AccountCryptographicStateUtil.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/repository/model/AccountCryptographicStateUtil.kt new file mode 100644 index 0000000000..3163e5ea59 --- /dev/null +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/repository/model/AccountCryptographicStateUtil.kt @@ -0,0 +1,20 @@ +package com.x8bit.bitwarden.data.auth.repository.model + +import com.bitwarden.core.WrappedAccountCryptographicState + +/** + * Create a mock [WrappedAccountCryptographicState] with a given number. + */ +fun createMockWrappedAccountCryptographicState( + number: Int, + signedPublicKey: String? = "mockSignedPublicKey-$number", + privateKey: String = "mockWrappedPrivateKey-$number", + signingKey: String = "mockWrappedSigningKey-$number", + securityState: String = "mockSecurityState-$number", +): WrappedAccountCryptographicState = + WrappedAccountCryptographicState.V2( + signedPublicKey = signedPublicKey, + privateKey = privateKey, + signingKey = signingKey, + securityState = securityState, + ) diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/repository/util/AccountKeysExtensionsTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/repository/util/AccountKeysExtensionsTest.kt new file mode 100644 index 0000000000..5d8bb01219 --- /dev/null +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/repository/util/AccountKeysExtensionsTest.kt @@ -0,0 +1,98 @@ +package com.x8bit.bitwarden.data.auth.repository.util + +import com.bitwarden.core.WrappedAccountCryptographicState +import com.bitwarden.network.model.AccountKeysJson +import com.bitwarden.network.model.AccountKeysJson.PublicKeyEncryptionKeyPair +import com.bitwarden.network.model.AccountKeysJson.SignatureKeyPair +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class AccountKeysExtensionsTest { + + @Suppress("MaxLineLength") + @Test + fun `toAccountCryptographicState returns V2 when securityState, signedPublicKey and signingKey are non-null`() { + val result = ACCOUNT_KEYS.toAccountCryptographicState(privateKey = PRIVATE_KEY) + + assertEquals( + WrappedAccountCryptographicState.V2( + privateKey = PRIVATE_KEY, + signedPublicKey = SIGNED_PUBLIC_KEY, + signingKey = SIGNING_KEY, + securityState = SECURITY_STATE, + ), + result, + ) + } + + @Test + fun `toAccountCryptographicState returns V1 when securityState is null`() { + val result = ACCOUNT_KEYS + .copy(securityState = null) + .toAccountCryptographicState(privateKey = PRIVATE_KEY) + + assertEquals( + WrappedAccountCryptographicState.V1(privateKey = PRIVATE_KEY), + result, + ) + } + + @Test + fun `toAccountCryptographicState returns V1 when signedPublicKey is null`() { + val result = ACCOUNT_KEYS + .copy( + publicKeyEncryptionKeyPair = ACCOUNT_KEYS + .publicKeyEncryptionKeyPair + .copy(signedPublicKey = null), + ) + .toAccountCryptographicState(privateKey = PRIVATE_KEY) + + assertEquals( + WrappedAccountCryptographicState.V1(privateKey = PRIVATE_KEY), + result, + ) + } + + @Test + fun `toAccountCryptographicState returns V1 when signingKey is null`() { + val result = ACCOUNT_KEYS + .copy(signatureKeyPair = null) + .toAccountCryptographicState(privateKey = PRIVATE_KEY) + + assertEquals( + WrappedAccountCryptographicState.V1(privateKey = PRIVATE_KEY), + result, + ) + } + + @Test + fun `toAccountCryptographicState returns V1 when AccountKeysJson is null`() { + val result = null.toAccountCryptographicState(privateKey = PRIVATE_KEY) + + assertEquals( + WrappedAccountCryptographicState.V1(privateKey = PRIVATE_KEY), + result, + ) + } +} + +private const val PRIVATE_KEY = "test-private-key" +private const val SECURITY_STATE = "test-security-state" +private const val SIGNING_KEY = "test-signing-key" +private const val SIGNED_PUBLIC_KEY = "test-signed-public-key" + +private val ACCOUNT_KEYS = AccountKeysJson( + signatureKeyPair = SignatureKeyPair( + wrappedSigningKey = SIGNING_KEY, + verifyingKey = "verifying-key", + ), + publicKeyEncryptionKeyPair = PublicKeyEncryptionKeyPair( + wrappedPrivateKey = PRIVATE_KEY, + signedPublicKey = SIGNED_PUBLIC_KEY, + publicKey = "public-key", + ), + securityState = AccountKeysJson.SecurityState( + securityState = SECURITY_STATE, + securityVersion = 2, + ), +) diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/repository/AuthenticatorBridgeRepositoryTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/repository/AuthenticatorBridgeRepositoryTest.kt index 94724c0123..e40a672e2a 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/repository/AuthenticatorBridgeRepositoryTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/repository/AuthenticatorBridgeRepositoryTest.kt @@ -6,6 +6,7 @@ import com.bitwarden.authenticatorbridge.util.toSymmetricEncryptionKeyData import com.bitwarden.core.InitOrgCryptoRequest import com.bitwarden.core.InitUserCryptoMethod import com.bitwarden.core.InitUserCryptoRequest +import com.bitwarden.core.WrappedAccountCryptographicState import com.bitwarden.core.data.util.asSuccess import com.bitwarden.crypto.Kdf import com.bitwarden.data.datasource.disk.model.EnvironmentUrlDataJson @@ -22,7 +23,6 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.util.FakeAuthDiskSource import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource import com.x8bit.bitwarden.data.vault.datasource.sdk.ScopedVaultSdkSource import com.x8bit.bitwarden.data.vault.datasource.sdk.model.InitializeCryptoResult -import com.x8bit.bitwarden.data.vault.repository.util.createWrappedAccountCryptographicState import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkCipher import io.mockk.coEvery import io.mockk.coVerify @@ -80,18 +80,19 @@ class AuthenticatorBridgeRepositoryTest { userId = USER_2_ID, authenticatorSyncUnlockKey = USER_2_UNLOCK_KEY, ) - fakeAuthDiskSource.storePrivateKey(userId = USER_1_ID, privateKey = USER_1_PRIVATE_KEY) - fakeAuthDiskSource.storePrivateKey(userId = USER_2_ID, privateKey = USER_2_PRIVATE_KEY) + fakeAuthDiskSource.storeAccountCryptographicState( + userId = USER_1_ID, + accountCryptographicState = USER_1_ACCOUNT_CRYPTOGRAPHIC_STATE, + ) + fakeAuthDiskSource.storeAccountCryptographicState( + userId = USER_2_ID, + accountCryptographicState = USER_2_ACCOUNT_CRYPTOGRAPHIC_STATE, + ) coEvery { scopedVaultSdkSource.initializeCrypto( userId = USER_1_ID, request = InitUserCryptoRequest( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = USER_1_PRIVATE_KEY, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = USER_1_ACCOUNT_CRYPTOGRAPHIC_STATE, userId = USER_1_ID, kdfParams = Kdf.Argon2id(iterations = 0U, memory = 0U, parallelism = 0U), email = USER_1_EMAIL, @@ -106,12 +107,7 @@ class AuthenticatorBridgeRepositoryTest { scopedVaultSdkSource.initializeCrypto( userId = USER_2_ID, request = InitUserCryptoRequest( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = USER_2_PRIVATE_KEY, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = USER_2_ACCOUNT_CRYPTOGRAPHIC_STATE, userId = USER_2_ID, kdfParams = Kdf.Argon2id(iterations = 0U, memory = 0U, parallelism = 0U), email = USER_2_EMAIL, @@ -203,12 +199,7 @@ class AuthenticatorBridgeRepositoryTest { scopedVaultSdkSource.initializeCrypto( userId = USER_2_ID, request = InitUserCryptoRequest( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = USER_2_PRIVATE_KEY, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = USER_2_ACCOUNT_CRYPTOGRAPHIC_STATE, userId = USER_2_ID, kdfParams = Kdf.Argon2id(iterations = 0U, memory = 0U, parallelism = 0U), email = USER_2_EMAIL, @@ -246,12 +237,7 @@ class AuthenticatorBridgeRepositoryTest { scopedVaultSdkSource.initializeCrypto( userId = USER_1_ID, request = InitUserCryptoRequest( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = USER_1_PRIVATE_KEY, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = USER_1_ACCOUNT_CRYPTOGRAPHIC_STATE, userId = USER_1_ID, kdfParams = Kdf.Argon2id(iterations = 0U, memory = 0U, parallelism = 0U), email = USER_1_EMAIL, @@ -273,12 +259,7 @@ class AuthenticatorBridgeRepositoryTest { scopedVaultSdkSource.initializeCrypto( userId = USER_2_ID, request = InitUserCryptoRequest( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = USER_2_PRIVATE_KEY, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = USER_2_ACCOUNT_CRYPTOGRAPHIC_STATE, userId = USER_2_ID, kdfParams = Kdf.Argon2id(iterations = 0U, memory = 0U, parallelism = 0U), email = USER_2_EMAIL, @@ -312,12 +293,7 @@ class AuthenticatorBridgeRepositoryTest { scopedVaultSdkSource.initializeCrypto( userId = USER_1_ID, request = InitUserCryptoRequest( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = USER_1_PRIVATE_KEY, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = USER_1_ACCOUNT_CRYPTOGRAPHIC_STATE, userId = USER_1_ID, kdfParams = Kdf.Argon2id(iterations = 0U, memory = 0U, parallelism = 0U), email = USER_1_EMAIL, @@ -338,12 +314,7 @@ class AuthenticatorBridgeRepositoryTest { scopedVaultSdkSource.initializeCrypto( userId = USER_1_ID, request = InitUserCryptoRequest( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = USER_1_PRIVATE_KEY, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = USER_1_ACCOUNT_CRYPTOGRAPHIC_STATE, userId = USER_1_ID, kdfParams = Kdf.Argon2id(iterations = 0U, memory = 0U, parallelism = 0U), email = USER_1_EMAIL, @@ -356,12 +327,7 @@ class AuthenticatorBridgeRepositoryTest { scopedVaultSdkSource.initializeCrypto( userId = USER_2_ID, request = InitUserCryptoRequest( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = USER_2_PRIVATE_KEY, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = USER_2_ACCOUNT_CRYPTOGRAPHIC_STATE, userId = USER_2_ID, kdfParams = Kdf.Argon2id(iterations = 0U, memory = 0U, parallelism = 0U), email = USER_2_EMAIL, @@ -440,7 +406,13 @@ private const val USER_1_EMAIL = "john@doe.com" private const val USER_2_EMAIL = "jane@doe.com" private const val USER_1_PRIVATE_KEY = "user1PrivateKey" +private val USER_1_ACCOUNT_CRYPTOGRAPHIC_STATE = WrappedAccountCryptographicState.V1( + privateKey = USER_1_PRIVATE_KEY, +) private const val USER_2_PRIVATE_KEY = "user2PrivateKey" +private val USER_2_ACCOUNT_CRYPTOGRAPHIC_STATE = WrappedAccountCryptographicState.V1( + privateKey = USER_2_PRIVATE_KEY, +) private const val USER_1_UNLOCK_KEY = "user1UnlockKey" private const val USER_2_UNLOCK_KEY = "user2UnlockKey" diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/manager/VaultLockManagerTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/manager/VaultLockManagerTest.kt index b56a0971b8..d9d7121dd8 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/manager/VaultLockManagerTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/manager/VaultLockManagerTest.kt @@ -9,6 +9,7 @@ import com.bitwarden.core.InitOrgCryptoRequest import com.bitwarden.core.InitUserCryptoMethod import com.bitwarden.core.InitUserCryptoRequest import com.bitwarden.core.MasterPasswordUnlockData +import com.bitwarden.core.WrappedAccountCryptographicState import com.bitwarden.core.data.manager.dispatcher.FakeDispatcherManager import com.bitwarden.core.data.manager.realtime.RealtimeManager import com.bitwarden.core.data.util.asFailure @@ -37,7 +38,6 @@ import com.x8bit.bitwarden.data.vault.datasource.sdk.model.InitializeCryptoResul import com.x8bit.bitwarden.data.vault.manager.model.VaultStateEvent import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockData import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult -import com.x8bit.bitwarden.data.vault.repository.util.createWrappedAccountCryptographicState import io.mockk.clearMocks import io.mockk.coEvery import io.mockk.coVerify @@ -725,12 +725,12 @@ class VaultLockManagerTest { @Suppress("MaxLineLength") @Test fun `vaultTimeout update to Never for a locked account when there is no stored auto-unlock key should do nothing`() { - val privateKey = "privateKey" + val accountCryptographicState = WrappedAccountCryptographicState.V1("privateKey") fakeAuthDiskSource.apply { userState = MOCK_USER_STATE - storePrivateKey( + storeAccountCryptographicState( userId = USER_ID, - privateKey = privateKey, + accountCryptographicState = accountCryptographicState, ) } assertFalse(vaultLockManager.isVaultUnlocked(userId = USER_ID)) @@ -743,13 +743,13 @@ class VaultLockManagerTest { @Suppress("MaxLineLength") @Test fun `vaultTimeout update to Never for a locked account when there is a stored auto-unlock key should unlock the vault`() { - val privateKey = "privateKey" + val accountCryptographicState = WrappedAccountCryptographicState.V1("privateKey") val userAutoUnlockKey = "userAutoUnlockKey" fakeAuthDiskSource.apply { userState = MOCK_USER_STATE - storePrivateKey( + storeAccountCryptographicState( userId = USER_ID, - privateKey = privateKey, + accountCryptographicState = accountCryptographicState, ) storeUserAutoUnlockKey( userId = USER_ID, @@ -760,12 +760,7 @@ class VaultLockManagerTest { vaultSdkSource.initializeCrypto( userId = USER_ID, request = InitUserCryptoRequest( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = accountCryptographicState, userId = USER_ID, kdfParams = MOCK_PROFILE.toSdkParams(), email = MOCK_PROFILE.email, @@ -790,12 +785,7 @@ class VaultLockManagerTest { vaultSdkSource.initializeCrypto( userId = USER_ID, request = InitUserCryptoRequest( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = accountCryptographicState, userId = USER_ID, kdfParams = MOCK_PROFILE.toSdkParams(), email = MOCK_PROFILE.email, @@ -920,18 +910,12 @@ class VaultLockManagerTest { val kdf = MOCK_PROFILE.toSdkParams() val email = MOCK_PROFILE.email val masterPassword = "mockValue" - val privateKey = "54321" val organizationKeys = mapOf("orgId1" to "orgKey1") coEvery { vaultSdkSource.initializeCrypto( userId = USER_ID, request = InitUserCryptoRequest( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE, userId = USER_ID, kdfParams = kdf, email = email, @@ -963,12 +947,7 @@ class VaultLockManagerTest { ) val result = vaultLockManager.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE, userId = USER_ID, email = email, kdf = kdf, @@ -1002,12 +981,7 @@ class VaultLockManagerTest { vaultSdkSource.initializeCrypto( userId = USER_ID, request = InitUserCryptoRequest( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE, userId = USER_ID, kdfParams = kdf, email = email, @@ -1034,19 +1008,13 @@ class VaultLockManagerTest { val kdf = MOCK_PROFILE.toSdkParams() val email = MOCK_PROFILE.email val masterPassword = "mockValue" - val privateKey = "54321" val organizationKeys = mapOf("orgId1" to "orgKey1") val userAutoUnlockKey = "userAutoUnlockKey" coEvery { vaultSdkSource.initializeCrypto( userId = USER_ID, request = InitUserCryptoRequest( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE, userId = USER_ID, kdfParams = kdf, email = email, @@ -1087,12 +1055,7 @@ class VaultLockManagerTest { } val result = vaultLockManager.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE, userId = USER_ID, email = email, kdf = kdf, @@ -1128,12 +1091,7 @@ class VaultLockManagerTest { vaultSdkSource.initializeCrypto( userId = USER_ID, request = InitUserCryptoRequest( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE, userId = USER_ID, kdfParams = kdf, email = email, @@ -1160,19 +1118,13 @@ class VaultLockManagerTest { val kdf = MOCK_PROFILE.toSdkParams() val email = MOCK_PROFILE.email val masterPassword = "mockValue" - val privateKey = "54321" val organizationKeys = mapOf("orgId1" to "orgKey1") val error = Throwable("Fail") coEvery { vaultSdkSource.initializeCrypto( userId = USER_ID, request = InitUserCryptoRequest( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE, userId = USER_ID, kdfParams = kdf, email = email, @@ -1195,12 +1147,7 @@ class VaultLockManagerTest { ) val result = vaultLockManager.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE, userId = USER_ID, email = email, kdf = kdf, @@ -1224,12 +1171,7 @@ class VaultLockManagerTest { vaultSdkSource.initializeCrypto( userId = USER_ID, request = InitUserCryptoRequest( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE, userId = USER_ID, kdfParams = kdf, email = email, @@ -1250,18 +1192,12 @@ class VaultLockManagerTest { val kdf = MOCK_PROFILE.toSdkParams() val email = MOCK_PROFILE.email val masterPassword = "mockValue" - val privateKey = "54321" val organizationKeys = mapOf("orgId1" to "orgKey1") coEvery { vaultSdkSource.initializeCrypto( userId = USER_ID, request = InitUserCryptoRequest( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE, userId = USER_ID, kdfParams = kdf, email = email, @@ -1291,12 +1227,7 @@ class VaultLockManagerTest { ) val result = vaultLockManager.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE, userId = USER_ID, email = email, kdf = kdf, @@ -1320,12 +1251,7 @@ class VaultLockManagerTest { vaultSdkSource.initializeCrypto( userId = USER_ID, request = InitUserCryptoRequest( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE, userId = USER_ID, kdfParams = kdf, email = email, @@ -1352,19 +1278,13 @@ class VaultLockManagerTest { val kdf = MOCK_PROFILE.toSdkParams() val email = MOCK_PROFILE.email val masterPassword = "mockValue" - val privateKey = "54321" val organizationKeys = mapOf("orgId1" to "orgKey1") val error = Throwable("Fail") coEvery { vaultSdkSource.initializeCrypto( userId = USER_ID, request = InitUserCryptoRequest( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE, userId = USER_ID, kdfParams = kdf, email = email, @@ -1386,12 +1306,7 @@ class VaultLockManagerTest { ) val result = vaultLockManager.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE, userId = USER_ID, email = email, kdf = kdf, @@ -1415,12 +1330,7 @@ class VaultLockManagerTest { vaultSdkSource.initializeCrypto( userId = USER_ID, request = InitUserCryptoRequest( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE, userId = USER_ID, kdfParams = kdf, email = email, @@ -1441,18 +1351,12 @@ class VaultLockManagerTest { val kdf = MOCK_PROFILE.toSdkParams() val email = MOCK_PROFILE.email val masterPassword = "mockValue" - val privateKey = "54321" val organizationKeys = mapOf("orgId1" to "orgKey1") coEvery { vaultSdkSource.initializeCrypto( userId = USER_ID, request = InitUserCryptoRequest( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE, userId = USER_ID, kdfParams = kdf, email = email, @@ -1481,12 +1385,7 @@ class VaultLockManagerTest { ) val result = vaultLockManager.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE, userId = USER_ID, email = email, kdf = kdf, @@ -1510,12 +1409,7 @@ class VaultLockManagerTest { vaultSdkSource.initializeCrypto( userId = USER_ID, request = InitUserCryptoRequest( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE, userId = USER_ID, kdfParams = kdf, email = email, @@ -1542,18 +1436,12 @@ class VaultLockManagerTest { val kdf = MOCK_PROFILE.toSdkParams() val email = MOCK_PROFILE.email val masterPassword = "mockValue" - val privateKey = "54321" val organizationKeys = mapOf("orgId1" to "orgKey1") coEvery { vaultSdkSource.initializeCrypto( userId = USER_ID, request = InitUserCryptoRequest( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE, userId = USER_ID, kdfParams = kdf, email = email, @@ -1582,12 +1470,7 @@ class VaultLockManagerTest { ) val result = vaultLockManager.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE, userId = USER_ID, email = email, kdf = kdf, @@ -1618,12 +1501,7 @@ class VaultLockManagerTest { vaultSdkSource.initializeCrypto( userId = USER_ID, request = InitUserCryptoRequest( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE, userId = USER_ID, kdfParams = kdf, email = email, @@ -1731,12 +1609,7 @@ class VaultLockManagerTest { vaultSdkSource.initializeCrypto( userId = USER_ID, request = InitUserCryptoRequest( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE, userId = USER_ID, kdfParams = kdf, email = email, @@ -1764,12 +1637,7 @@ class VaultLockManagerTest { mutableVaultTimeoutStateFlow.value = VaultTimeout.ThirtyMinutes val result = vaultLockManager.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE, userId = USER_ID, email = email, kdf = kdf, @@ -1795,12 +1663,7 @@ class VaultLockManagerTest { vaultSdkSource.initializeCrypto( userId = USER_ID, request = InitUserCryptoRequest( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE, userId = USER_ID, kdfParams = kdf, email = email, @@ -1827,7 +1690,6 @@ class VaultLockManagerTest { val kdf = MOCK_PROFILE.toSdkParams() val email = MOCK_PROFILE.email val masterPassword = "mockValue" - val privateKey = "54321" val organizationKeys = mapOf("orgId1" to "orgKey1") val initUserCryptoMethod = InitUserCryptoMethod.MasterPasswordUnlock( password = masterPassword, @@ -1837,12 +1699,7 @@ class VaultLockManagerTest { vaultSdkSource.initializeCrypto( userId = USER_ID, request = InitUserCryptoRequest( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE, userId = USER_ID, kdfParams = kdf, email = email, @@ -1871,12 +1728,7 @@ class VaultLockManagerTest { ) val result = vaultLockManager.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE, userId = USER_ID, email = email, kdf = kdf, @@ -1907,12 +1759,7 @@ class VaultLockManagerTest { vaultSdkSource.initializeCrypto( userId = USER_ID, request = InitUserCryptoRequest( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE, userId = USER_ID, kdfParams = kdf, email = email, @@ -1953,18 +1800,12 @@ class VaultLockManagerTest { val email = MOCK_PROFILE.email val userKey = "12345" val masterPassword = "mockValue" - val privateKey = "54321" val organizationKeys = null coEvery { vaultSdkSource.initializeCrypto( userId = userId, request = InitUserCryptoRequest( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE, userId = userId, kdfParams = kdf, email = email, @@ -1981,12 +1822,7 @@ class VaultLockManagerTest { } vaultLockManager.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE, userId = userId, email = email, kdf = kdf, @@ -2009,7 +1845,6 @@ class VaultLockManagerTest { val kdf = MOCK_PROFILE.toSdkParams() val email = MOCK_PROFILE.email val masterPassword = "mockValue" - val privateKey = "54321" val organizationKeys = null val userAutoUnlockKey = "userAutoUnlockKey" // Clear recorded calls so this helper can be called multiple times and assert a unique @@ -2019,12 +1854,7 @@ class VaultLockManagerTest { vaultSdkSource.initializeCrypto( userId = userId, request = InitUserCryptoRequest( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE, userId = userId, kdfParams = kdf, email = email, @@ -2044,12 +1874,7 @@ class VaultLockManagerTest { } returns true.asSuccess() val result = vaultLockManager.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE, userId = userId, email = email, kdf = kdf, @@ -2065,12 +1890,7 @@ class VaultLockManagerTest { vaultSdkSource.initializeCrypto( userId = userId, request = InitUserCryptoRequest( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = ACCOUNT_CRYPTOGRAPHIC_STATE, userId = userId, kdfParams = kdf, email = email, @@ -2167,3 +1987,7 @@ private val MOCK_MASTER_PASSWORD_UNLOCK_DATA = MasterPasswordUnlockData( masterKeyWrappedUserKey = "12345", salt = "mockSalt", ) + +private val ACCOUNT_CRYPTOGRAPHIC_STATE = WrappedAccountCryptographicState.V1( + privateKey = "privateKey", +) diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/manager/VaultSyncManagerTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/manager/VaultSyncManagerTest.kt index d4a159e560..aabad13027 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/manager/VaultSyncManagerTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/manager/VaultSyncManagerTest.kt @@ -35,6 +35,7 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.util.FakeAuthDiskSource import com.x8bit.bitwarden.data.auth.manager.UserLogoutManager import com.x8bit.bitwarden.data.auth.manager.UserStateManager import com.x8bit.bitwarden.data.auth.repository.model.LogoutReason +import com.x8bit.bitwarden.data.auth.repository.model.createMockWrappedAccountCryptographicState import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource import com.x8bit.bitwarden.data.platform.error.NoActiveUserException import com.x8bit.bitwarden.data.platform.manager.DatabaseSchemeManager @@ -747,7 +748,10 @@ class VaultSyncManagerTest { ), ) fakeAuthDiskSource.assertUserState(userState = updatedUserState) - fakeAuthDiskSource.assertPrivateKey(userId = userId, privateKey = "mockPrivateKey-1") + fakeAuthDiskSource.assertAccountCryptographicState( + userId = userId, + accountCryptographicState = createMockWrappedAccountCryptographicState(number = 1), + ) fakeAuthDiskSource.assertOrganizationKeys( userId = userId, organizationKeys = mapOf(userId to "mockKey-1"), diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryTest.kt index ce5e55c103..2426d27eaf 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryTest.kt @@ -32,6 +32,7 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson import com.x8bit.bitwarden.data.auth.datasource.disk.util.FakeAuthDiskSource import com.x8bit.bitwarden.data.auth.datasource.sdk.util.toKdfRequestModel +import com.x8bit.bitwarden.data.auth.repository.model.createMockWrappedAccountCryptographicState import com.x8bit.bitwarden.data.auth.repository.util.toSdkParams import com.x8bit.bitwarden.data.platform.error.NoActiveUserException import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource @@ -58,7 +59,6 @@ import com.x8bit.bitwarden.data.vault.repository.model.ImportCredentialsResult import com.x8bit.bitwarden.data.vault.repository.model.SendData import com.x8bit.bitwarden.data.vault.repository.model.VaultData import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult -import com.x8bit.bitwarden.data.vault.repository.util.createWrappedAccountCryptographicState import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkCipher import com.x8bit.bitwarden.data.vault.repository.util.toSdkMasterPasswordUnlock import com.x8bit.bitwarden.ui.vault.feature.verificationcode.util.createVerificationCodeItem @@ -199,7 +199,6 @@ class VaultRepositoryTest { fun `unlockVaultWithBiometrics with failure to decode biometrics key should return BiometricDecodingError`() = runTest { val userId = MOCK_USER_STATE.activeUserId - val privateKey = "mockPrivateKey-1" val biometricsKey = "asdf1234" fakeAuthDiskSource.userState = MOCK_USER_STATE val initVector = byteArrayOf(2, 2) @@ -210,7 +209,10 @@ class VaultRepositoryTest { fakeAuthDiskSource.apply { storeUserBiometricInitVector(userId = userId, iv = initVector) storeUserBiometricUnlockKey(userId = userId, biometricsKey = biometricsKey) - storePrivateKey(userId = userId, privateKey = privateKey) + storeAccountCryptographicState( + userId = userId, + accountCryptographicState = MOCK_ACCOUNT_CRYPTOGRAPHIC_STATE, + ) } val result = vaultRepository.unlockVaultWithBiometrics(cipher = cipher) @@ -250,7 +252,6 @@ class VaultRepositoryTest { fun `unlockVaultWithBiometrics with an IV and VaultLockManager Success should store the updated key and IV and unlock for the current user and return Success`() = runTest { val userId = MOCK_USER_STATE.activeUserId - val privateKey = "mockPrivateKey-1" val biometricsKey = "asdf1234" fakeAuthDiskSource.userState = MOCK_USER_STATE val encryptedBytes = byteArrayOf(1, 1) @@ -260,12 +261,7 @@ class VaultRepositoryTest { } coEvery { vaultLockManager.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = MOCK_ACCOUNT_CRYPTOGRAPHIC_STATE, userId = userId, email = "email", kdf = MOCK_PROFILE.toSdkParams(), @@ -278,7 +274,10 @@ class VaultRepositoryTest { fakeAuthDiskSource.apply { storeUserBiometricInitVector(userId = userId, iv = initVector) storeUserBiometricUnlockKey(userId = userId, biometricsKey = biometricsKey) - storePrivateKey(userId = userId, privateKey = privateKey) + storeAccountCryptographicState( + userId = userId, + accountCryptographicState = MOCK_ACCOUNT_CRYPTOGRAPHIC_STATE, + ) } val result = vaultRepository.unlockVaultWithBiometrics(cipher = cipher) @@ -286,12 +285,7 @@ class VaultRepositoryTest { assertEquals(VaultUnlockResult.Success, result) coVerify(exactly = 1) { vaultLockManager.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = MOCK_ACCOUNT_CRYPTOGRAPHIC_STATE, userId = userId, email = "email", kdf = MOCK_PROFILE.toSdkParams(), @@ -308,7 +302,6 @@ class VaultRepositoryTest { fun `unlockVaultWithBiometrics with VaultLockManager Success and a stored encrypted pin should unlock for the current user, derive a new pin-protected key, and return Success`() = runTest { val userId = MOCK_USER_STATE.activeUserId - val privateKey = "mockPrivateKey-1" val biometricsKey = "asdf1234" fakeAuthDiskSource.userState = MOCK_USER_STATE val encryptedBytes = byteArrayOf(1, 1) @@ -319,12 +312,7 @@ class VaultRepositoryTest { } coEvery { vaultLockManager.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = MOCK_ACCOUNT_CRYPTOGRAPHIC_STATE, userId = userId, email = "email", kdf = MOCK_PROFILE.toSdkParams(), @@ -337,7 +325,10 @@ class VaultRepositoryTest { fakeAuthDiskSource.apply { storeUserBiometricInitVector(userId = userId, iv = null) storeUserBiometricUnlockKey(userId = userId, biometricsKey = biometricsKey) - storePrivateKey(userId = userId, privateKey = privateKey) + storeAccountCryptographicState( + userId = userId, + accountCryptographicState = MOCK_ACCOUNT_CRYPTOGRAPHIC_STATE, + ) } val result = vaultRepository.unlockVaultWithBiometrics(cipher = cipher) @@ -345,12 +336,7 @@ class VaultRepositoryTest { assertEquals(VaultUnlockResult.Success, result) coVerify { vaultLockManager.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = "mockPrivateKey-1", - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = MOCK_ACCOUNT_CRYPTOGRAPHIC_STATE, userId = userId, email = "email", kdf = MOCK_PROFILE.toSdkParams(), @@ -378,16 +364,10 @@ class VaultRepositoryTest { runTest { val userId = MOCK_USER_STATE.activeUserId val authenticatorSyncUnlockKey = "asdf1234" - val privateKey = "mockPrivateKey-1" fakeAuthDiskSource.userState = MOCK_USER_STATE coEvery { vaultLockManager.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = MOCK_ACCOUNT_CRYPTOGRAPHIC_STATE, userId = userId, email = "email", kdf = MOCK_PROFILE.toSdkParams(), @@ -402,7 +382,10 @@ class VaultRepositoryTest { userId = userId, authenticatorSyncUnlockKey = authenticatorSyncUnlockKey, ) - storePrivateKey(userId = userId, privateKey = privateKey) + storeAccountCryptographicState( + userId = userId, + accountCryptographicState = MOCK_ACCOUNT_CRYPTOGRAPHIC_STATE, + ) } val result = vaultRepository.unlockVaultWithDecryptedUserKey( @@ -412,12 +395,7 @@ class VaultRepositoryTest { assertEquals(VaultUnlockResult.Success, result) coVerify { vaultLockManager.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = "mockPrivateKey-1", - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = MOCK_ACCOUNT_CRYPTOGRAPHIC_STATE, userId = userId, email = "email", kdf = MOCK_PROFILE.toSdkParams(), @@ -435,17 +413,11 @@ class VaultRepositoryTest { runTest { val userId = MOCK_USER_STATE.activeUserId val authenticatorSyncUnlockKey = "asdf1234" - val privateKey = "mockPrivateKey-1" fakeAuthDiskSource.userState = MOCK_USER_STATE val error = Throwable("Fail") coEvery { vaultLockManager.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = privateKey, - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = MOCK_ACCOUNT_CRYPTOGRAPHIC_STATE, userId = userId, email = "email", kdf = MOCK_PROFILE.toSdkParams(), @@ -460,7 +432,10 @@ class VaultRepositoryTest { userId = userId, authenticatorSyncUnlockKey = authenticatorSyncUnlockKey, ) - storePrivateKey(userId = userId, privateKey = privateKey) + storeAccountCryptographicState( + userId = userId, + accountCryptographicState = MOCK_ACCOUNT_CRYPTOGRAPHIC_STATE, + ) } val result = vaultRepository.unlockVaultWithDecryptedUserKey( @@ -470,12 +445,7 @@ class VaultRepositoryTest { assertEquals(VaultUnlockResult.InvalidStateError(error = error), result) coVerify { vaultLockManager.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = "mockPrivateKey-1", - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = MOCK_ACCOUNT_CRYPTOGRAPHIC_STATE, userId = userId, email = "email", kdf = MOCK_PROFILE.toSdkParams(), @@ -504,9 +474,9 @@ class VaultRepositoryTest { @Test fun `unlockVaultWithMasterPassword with missing private key should return InvalidStateError`() = runTest { - fakeAuthDiskSource.storePrivateKey( + fakeAuthDiskSource.storeAccountCryptographicState( userId = "mockId-1", - privateKey = null, + accountCryptographicState = null, ) fakeAuthDiskSource.userState = MOCK_USER_STATE @@ -536,12 +506,7 @@ class VaultRepositoryTest { ) coVerify { vaultLockManager.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = "mockPrivateKey-1", - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = MOCK_ACCOUNT_CRYPTOGRAPHIC_STATE, userId = userId, email = "email", kdf = MOCK_PROFILE.toSdkParams(), @@ -585,16 +550,14 @@ class VaultRepositoryTest { ) fakeAuthDiskSource.userState = userState - fakeAuthDiskSource.storePrivateKey(userId = userId, privateKey = "mockPrivateKey-1") + fakeAuthDiskSource.storeAccountCryptographicState( + userId = userId, + accountCryptographicState = MOCK_ACCOUNT_CRYPTOGRAPHIC_STATE, + ) coEvery { vaultLockManager.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = "mockPrivateKey-1", - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = MOCK_ACCOUNT_CRYPTOGRAPHIC_STATE, userId = userId, email = "email", kdf = MOCK_PROFILE.toSdkParams(), @@ -613,12 +576,7 @@ class VaultRepositoryTest { assertEquals(VaultUnlockResult.Success, result) coVerify { vaultLockManager.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = "mockPrivateKey-1", - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = MOCK_ACCOUNT_CRYPTOGRAPHIC_STATE, userId = userId, email = "email", kdf = MOCK_PROFILE.toSdkParams(), @@ -637,7 +595,6 @@ class VaultRepositoryTest { runTest { val userId = "mockId-1" val masterPassword = "mockPassword-1" - val userKey = "mockUserKey-1" val userState = MOCK_USER_STATE.copy( accounts = mapOf( "mockId-1" to MOCK_ACCOUNT.copy( @@ -648,7 +605,10 @@ class VaultRepositoryTest { ), ) fakeAuthDiskSource.userState = userState - fakeAuthDiskSource.storePrivateKey(userId = userId, privateKey = "mockPrivateKey-1") + fakeAuthDiskSource.storeAccountCryptographicState( + userId = userId, + accountCryptographicState = MOCK_ACCOUNT_CRYPTOGRAPHIC_STATE, + ) val result = vaultRepository.unlockVaultWithMasterPassword( masterPassword = masterPassword, @@ -678,12 +638,7 @@ class VaultRepositoryTest { ) coVerify { vaultLockManager.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = "mockPrivateKey-1", - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = MOCK_ACCOUNT_CRYPTOGRAPHIC_STATE, userId = userId, email = "email", kdf = MOCK_PROFILE.toSdkParams(), @@ -724,9 +679,9 @@ class VaultRepositoryTest { userId = "mockId-1", pinProtectedUserKeyEnvelope = null, ) - fakeAuthDiskSource.storePrivateKey( + fakeAuthDiskSource.storeAccountCryptographicState( userId = "mockId-1", - privateKey = "mockPrivateKey-1", + accountCryptographicState = MOCK_ACCOUNT_CRYPTOGRAPHIC_STATE, ) fakeAuthDiskSource.userState = MOCK_USER_STATE @@ -748,9 +703,9 @@ class VaultRepositoryTest { userId = "mockId-1", pinProtectedUserKeyEnvelope = null, ) - fakeAuthDiskSource.storePrivateKey( + fakeAuthDiskSource.storeAccountCryptographicState( userId = "mockId-1", - privateKey = null, + accountCryptographicState = null, ) fakeAuthDiskSource.userState = MOCK_USER_STATE @@ -778,12 +733,7 @@ class VaultRepositoryTest { ) coVerify { vaultLockManager.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = "mockPrivateKey-1", - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = MOCK_ACCOUNT_CRYPTOGRAPHIC_STATE, userId = userId, email = "email", kdf = MOCK_PROFILE.toSdkParams(), @@ -816,12 +766,7 @@ class VaultRepositoryTest { ) coVerify { vaultLockManager.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = "mockPrivateKey-1", - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = MOCK_ACCOUNT_CRYPTOGRAPHIC_STATE, userId = userId, email = "email", kdf = MOCK_PROFILE.toSdkParams(), @@ -850,12 +795,7 @@ class VaultRepositoryTest { ) coVerify { vaultLockManager.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = "mockPrivateKey-1", - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = MOCK_ACCOUNT_CRYPTOGRAPHIC_STATE, userId = userId, email = "email", kdf = MOCK_PROFILE.toSdkParams(), @@ -1637,9 +1577,9 @@ class VaultRepositoryTest { sendList = listOf(createMockSdkSend(number = 1)), ) } returns listOf(createMockSendView(number = 1)).asSuccess() - fakeAuthDiskSource.storePrivateKey( + fakeAuthDiskSource.storeAccountCryptographicState( userId = userId, - privateKey = "mockPrivateKey-1", + accountCryptographicState = MOCK_ACCOUNT_CRYPTOGRAPHIC_STATE, ) fakeAuthDiskSource.storePinProtectedUserKey( userId = userId, @@ -1658,12 +1598,7 @@ class VaultRepositoryTest { // Master password unlock coEvery { vaultLockManager.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = "mockPrivateKey-1", - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = MOCK_ACCOUNT_CRYPTOGRAPHIC_STATE, userId = userId, email = "email", kdf = MOCK_PROFILE.toSdkParams(), @@ -1682,12 +1617,7 @@ class VaultRepositoryTest { // PIN unlock coEvery { vaultLockManager.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = "mockPrivateKey-1", - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = MOCK_ACCOUNT_CRYPTOGRAPHIC_STATE, userId = userId, email = "email", kdf = MOCK_PROFILE.toSdkParams(), @@ -1702,12 +1632,7 @@ class VaultRepositoryTest { // PIN ENVELOPE unlock coEvery { vaultLockManager.unlockVault( - accountCryptographicState = createWrappedAccountCryptographicState( - privateKey = "mockPrivateKey-1", - securityState = null, - signedPublicKey = null, - signingKey = null, - ), + accountCryptographicState = MOCK_ACCOUNT_CRYPTOGRAPHIC_STATE, userId = userId, email = "email", kdf = MOCK_PROFILE.toSdkParams(), @@ -1778,3 +1703,7 @@ private val MOCK_MASTER_PASSWORD_UNLOCK_DATA = MasterPasswordUnlockDataJson( kdf = MOCK_ACCOUNT.profile.toSdkParams().toKdfRequestModel(), masterKeyWrappedUserKey = "masterKeyWrappedUserKeyMock", ) + +private val MOCK_ACCOUNT_CRYPTOGRAPHIC_STATE = createMockWrappedAccountCryptographicState( + number = 1, +) diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/repository/util/WrappedAccountCryptographicStateExtensionsTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/repository/util/WrappedAccountCryptographicStateExtensionsTest.kt deleted file mode 100644 index 75a4b07cf5..0000000000 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/repository/util/WrappedAccountCryptographicStateExtensionsTest.kt +++ /dev/null @@ -1,111 +0,0 @@ -package com.x8bit.bitwarden.data.vault.repository.util - -import com.bitwarden.core.WrappedAccountCryptographicState -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test - -private const val PRIVATE_KEY = "test-private-key" -private const val SECURITY_STATE = "test-security-state" -private const val SIGNING_KEY = "test-signing-key" -private const val SIGNED_PUBLIC_KEY = "test-signed-public-key" - -class WrappedAccountCryptographicStateExtensionsTest { - - @Suppress("MaxLineLength") - @Test - fun `createWrappedAccountCryptographicState returns V2 when securityState, signedPublicKey and signingKey are non-null`() { - val result = createWrappedAccountCryptographicState( - privateKey = PRIVATE_KEY, - securityState = SECURITY_STATE, - signedPublicKey = SIGNED_PUBLIC_KEY, - signingKey = SIGNING_KEY, - ) - - val v2State = result as WrappedAccountCryptographicState.V2 - assertEquals(PRIVATE_KEY, v2State.privateKey) - assertEquals(SECURITY_STATE, v2State.securityState) - assertEquals(SIGNED_PUBLIC_KEY, v2State.signedPublicKey) - assertEquals(SIGNING_KEY, v2State.signingKey) - } - - @Test - fun `createWrappedAccountCryptographicState returns V1 when securityState is null`() { - val result = createWrappedAccountCryptographicState( - privateKey = PRIVATE_KEY, - securityState = null, - signedPublicKey = SIGNED_PUBLIC_KEY, - signingKey = SIGNING_KEY, - ) - - val v1State = result as WrappedAccountCryptographicState.V1 - assertEquals(PRIVATE_KEY, v1State.privateKey) - } - - @Test - fun `createWrappedAccountCryptographicState returns V1 when signedPublicKey is null`() { - val result = createWrappedAccountCryptographicState( - privateKey = PRIVATE_KEY, - securityState = SECURITY_STATE, - signedPublicKey = null, - signingKey = SIGNING_KEY, - ) - - val v1State = result as WrappedAccountCryptographicState.V1 - assertEquals(PRIVATE_KEY, v1State.privateKey) - } - - @Test - fun `createWrappedAccountCryptographicState returns V1 when signingKey is null`() { - val result = createWrappedAccountCryptographicState( - privateKey = PRIVATE_KEY, - securityState = SECURITY_STATE, - signedPublicKey = SIGNED_PUBLIC_KEY, - signingKey = null, - ) - - val v1State = result as WrappedAccountCryptographicState.V1 - assertEquals(PRIVATE_KEY, v1State.privateKey) - } - - @Suppress("MaxLineLength") - @Test - fun `createWrappedAccountCryptographicState returns V1 when both securityState and signedPublicKey are null`() { - val result = createWrappedAccountCryptographicState( - privateKey = PRIVATE_KEY, - securityState = null, - signedPublicKey = null, - signingKey = SIGNING_KEY, - ) - - val v1State = result as WrappedAccountCryptographicState.V1 - assertEquals(PRIVATE_KEY, v1State.privateKey) - } - - @Suppress("MaxLineLength") - @Test - fun `createWrappedAccountCryptographicState returns V1 when both securityState and signingKey are null`() { - val result = createWrappedAccountCryptographicState( - privateKey = PRIVATE_KEY, - securityState = null, - signedPublicKey = SIGNED_PUBLIC_KEY, - signingKey = null, - ) - - val v1State = result as WrappedAccountCryptographicState.V1 - assertEquals(PRIVATE_KEY, v1State.privateKey) - } - - @Suppress("MaxLineLength") - @Test - fun `createWrappedAccountCryptographicState returns V1 when both signedPublicKey and signingKey are null`() { - val result = createWrappedAccountCryptographicState( - privateKey = PRIVATE_KEY, - securityState = SECURITY_STATE, - signedPublicKey = null, - signingKey = null, - ) - - val v1State = result as WrappedAccountCryptographicState.V1 - assertEquals(PRIVATE_KEY, v1State.privateKey) - } -}