mirror of
https://github.com/bitwarden/android.git
synced 2025-12-10 20:07:59 -06:00
[PM-27176] Switch to using SDK's init crypto with MasterPasswordUnlock (#6073)
This commit is contained in:
parent
78b1676745
commit
7d7951d4ca
@ -51,7 +51,6 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.DeviceDataModel
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSource
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.util.toInt
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.util.toKdf
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.util.toKdfTypeJson
|
||||
import com.x8bit.bitwarden.data.auth.manager.AuthRequestManager
|
||||
import com.x8bit.bitwarden.data.auth.manager.KdfManager
|
||||
@ -113,6 +112,7 @@ import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockError
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.util.toSdkMasterPasswordUnlock
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
@ -1886,15 +1886,27 @@ class AuthRepositoryImpl(
|
||||
val masterPassword = password ?: return null
|
||||
val privateKey = loginResponse.privateKeyOrNull() ?: return null
|
||||
val key = loginResponse.key ?: return null
|
||||
|
||||
val initUserCryptoMethod = loginResponse
|
||||
.userDecryptionOptions
|
||||
?.masterPasswordUnlock
|
||||
?.let { masterPasswordUnlock ->
|
||||
InitUserCryptoMethod.MasterPasswordUnlock(
|
||||
password = masterPassword,
|
||||
masterPasswordUnlock = masterPasswordUnlock.toSdkMasterPasswordUnlock(),
|
||||
)
|
||||
}
|
||||
?: InitUserCryptoMethod.Password(
|
||||
password = masterPassword,
|
||||
userKey = key,
|
||||
)
|
||||
|
||||
return unlockVault(
|
||||
accountProfile = profile,
|
||||
privateKey = privateKey,
|
||||
securityState = loginResponse.accountKeys?.securityState?.securityState,
|
||||
signingKey = loginResponse.accountKeys?.signatureKeyPair?.wrappedSigningKey,
|
||||
initUserCryptoMethod = InitUserCryptoMethod.Password(
|
||||
password = masterPassword,
|
||||
userKey = key,
|
||||
),
|
||||
initUserCryptoMethod = initUserCryptoMethod,
|
||||
)
|
||||
}
|
||||
|
||||
@ -2047,20 +2059,10 @@ class AuthRepositoryImpl(
|
||||
initUserCryptoMethod: InitUserCryptoMethod,
|
||||
): VaultUnlockResult {
|
||||
val userId = accountProfile.userId
|
||||
val kdfParams = (initUserCryptoMethod as? InitUserCryptoMethod.Password)
|
||||
?.let {
|
||||
accountProfile
|
||||
.userDecryptionOptions
|
||||
?.masterPasswordUnlock
|
||||
?.kdf
|
||||
?.toKdf()
|
||||
}
|
||||
?: accountProfile.toSdkParams()
|
||||
|
||||
return vaultRepository.unlockVault(
|
||||
userId = userId,
|
||||
email = accountProfile.email,
|
||||
kdf = kdfParams,
|
||||
kdf = accountProfile.toSdkParams(),
|
||||
privateKey = privateKey,
|
||||
signingKey = signingKey,
|
||||
securityState = securityState,
|
||||
|
||||
@ -219,22 +219,12 @@ class VaultLockManagerImpl(
|
||||
initializeCryptoResult
|
||||
.toVaultUnlockResult()
|
||||
.also {
|
||||
if (initUserCryptoMethod is InitUserCryptoMethod.Password) {
|
||||
// Save the master password hash.
|
||||
authSdkSource
|
||||
.hashPassword(
|
||||
email = email,
|
||||
password = initUserCryptoMethod.password,
|
||||
kdf = kdf,
|
||||
purpose = HashPurpose.LOCAL_AUTHORIZATION,
|
||||
)
|
||||
.onSuccess { passwordHash ->
|
||||
authDiskSource.storeMasterPasswordHash(
|
||||
userId = userId,
|
||||
passwordHash = passwordHash,
|
||||
)
|
||||
}
|
||||
}
|
||||
hashAndStoreMasterPassword(
|
||||
initUserCryptoMethod = initUserCryptoMethod,
|
||||
email = email,
|
||||
kdf = kdf,
|
||||
userId = userId,
|
||||
)
|
||||
if (it is VaultUnlockResult.Success) {
|
||||
clearInvalidUnlockCount(userId = userId)
|
||||
trustedDeviceManager
|
||||
@ -257,6 +247,43 @@ class VaultLockManagerImpl(
|
||||
.first()
|
||||
}
|
||||
|
||||
/**
|
||||
* Hashes a password and stores it as the master password hash for a given user.
|
||||
*/
|
||||
private suspend fun hashAndStoreMasterPassword(
|
||||
initUserCryptoMethod: InitUserCryptoMethod,
|
||||
email: String,
|
||||
kdf: Kdf,
|
||||
userId: String,
|
||||
) {
|
||||
if (initUserCryptoMethod is InitUserCryptoMethod.Password ||
|
||||
initUserCryptoMethod is InitUserCryptoMethod.MasterPasswordUnlock
|
||||
) {
|
||||
val password = when (initUserCryptoMethod) {
|
||||
is InitUserCryptoMethod.Password -> initUserCryptoMethod.password
|
||||
is InitUserCryptoMethod.MasterPasswordUnlock -> initUserCryptoMethod.password
|
||||
else -> throw IllegalStateException(
|
||||
"Invalid initUserCryptoMethod ${initUserCryptoMethod.logTag}.",
|
||||
)
|
||||
}
|
||||
|
||||
// Save the master password hash.
|
||||
authSdkSource
|
||||
.hashPassword(
|
||||
email = email,
|
||||
password = password,
|
||||
kdf = kdf,
|
||||
purpose = HashPurpose.LOCAL_AUTHORIZATION,
|
||||
)
|
||||
.onSuccess { passwordHash ->
|
||||
authDiskSource.storeMasterPasswordHash(
|
||||
userId = userId,
|
||||
passwordHash = passwordHash,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun waitUntilUnlocked(userId: String) {
|
||||
vaultUnlockDataStateFlow
|
||||
.map { vaultUnlockDataList ->
|
||||
|
||||
@ -44,6 +44,7 @@ import com.x8bit.bitwarden.data.vault.repository.util.logTag
|
||||
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkCipher
|
||||
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkFolder
|
||||
import com.x8bit.bitwarden.data.vault.repository.util.toSdkAccount
|
||||
import com.x8bit.bitwarden.data.vault.repository.util.toSdkMasterPasswordUnlock
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toFilteredList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@ -346,22 +347,31 @@ class VaultRepositoryImpl(
|
||||
?: return VaultUnlockResult.InvalidStateError(
|
||||
error = MissingPropertyException("User key"),
|
||||
)
|
||||
val activeAccount = authDiskSource.userState?.activeAccount
|
||||
val initUserCryptoMethod = activeAccount
|
||||
?.profile
|
||||
?.userDecryptionOptions
|
||||
?.masterPasswordUnlock
|
||||
?.let { masterPasswordUnlock ->
|
||||
InitUserCryptoMethod.MasterPasswordUnlock(
|
||||
password = masterPassword,
|
||||
masterPasswordUnlock = masterPasswordUnlock.toSdkMasterPasswordUnlock(),
|
||||
)
|
||||
}
|
||||
?: InitUserCryptoMethod.Password(
|
||||
password = masterPassword,
|
||||
userKey = userKey,
|
||||
)
|
||||
return this
|
||||
.unlockVaultForUser(
|
||||
userId = userId,
|
||||
initUserCryptoMethod = InitUserCryptoMethod.Password(
|
||||
password = masterPassword,
|
||||
userKey = userKey,
|
||||
),
|
||||
initUserCryptoMethod = initUserCryptoMethod,
|
||||
)
|
||||
.also {
|
||||
if (it is VaultUnlockResult.Success) {
|
||||
deriveTemporaryPinProtectedUserKeyIfNecessary(
|
||||
userId = userId,
|
||||
initUserCryptoMethod = InitUserCryptoMethod.Password(
|
||||
password = masterPassword,
|
||||
userKey = userKey,
|
||||
),
|
||||
initUserCryptoMethod = initUserCryptoMethod,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,15 @@
|
||||
package com.x8bit.bitwarden.data.vault.repository.util
|
||||
|
||||
import com.bitwarden.core.MasterPasswordUnlockData
|
||||
import com.bitwarden.network.model.MasterPasswordUnlockDataJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.util.toKdf
|
||||
|
||||
/**
|
||||
* Converts [MasterPasswordUnlockDataJson] to [MasterPasswordUnlockData]
|
||||
*/
|
||||
fun MasterPasswordUnlockDataJson.toSdkMasterPasswordUnlock(): MasterPasswordUnlockData =
|
||||
MasterPasswordUnlockData(
|
||||
kdf = kdf.toKdf(),
|
||||
masterKeyWrappedUserKey = masterKeyWrappedUserKey,
|
||||
salt = salt,
|
||||
)
|
||||
@ -6987,79 +6987,6 @@ class AuthRepositoryTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unlockVault uses user decryption options for KDF when init method is password`() =
|
||||
runTest {
|
||||
val successResponse = GET_TOKEN_WITH_ACCOUNT_KEYS_RESPONSE_SUCCESS
|
||||
coEvery {
|
||||
identityService.preLogin(email = EMAIL)
|
||||
} returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
coEvery {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
authModel = IdentityTokenAuthModel.MasterPassword(
|
||||
username = EMAIL,
|
||||
password = PASSWORD_HASH,
|
||||
),
|
||||
uniqueAppId = UNIQUE_APP_ID,
|
||||
)
|
||||
} returns successResponse.asSuccess()
|
||||
coEvery {
|
||||
vaultRepository.unlockVault(
|
||||
userId = USER_ID_1,
|
||||
email = EMAIL,
|
||||
kdf = ACCOUNT_2.profile.toSdkParams(),
|
||||
privateKey = successResponse.accountKeys!!
|
||||
.publicKeyEncryptionKeyPair
|
||||
.wrappedPrivateKey,
|
||||
signingKey = successResponse.accountKeys
|
||||
?.signatureKeyPair
|
||||
?.wrappedSigningKey,
|
||||
securityState = successResponse.accountKeys
|
||||
?.securityState
|
||||
?.securityState,
|
||||
initUserCryptoMethod = InitUserCryptoMethod.Password(
|
||||
password = PASSWORD,
|
||||
userKey = successResponse.key!!,
|
||||
),
|
||||
organizationKeys = null,
|
||||
)
|
||||
} returns VaultUnlockResult.Success
|
||||
coEvery { vaultRepository.syncIfNecessary() } just runs
|
||||
every {
|
||||
GET_TOKEN_WITH_ACCOUNT_KEYS_RESPONSE_SUCCESS.toUserState(
|
||||
previousUserState = null,
|
||||
environmentUrlData = EnvironmentUrlDataJson.DEFAULT_US,
|
||||
)
|
||||
} returns SINGLE_USER_STATE_1_WITH_DECRYPTION_OPTIONS
|
||||
|
||||
repository.login(email = EMAIL, password = PASSWORD)
|
||||
|
||||
coVerify {
|
||||
vaultRepository.unlockVault(
|
||||
userId = USER_ID_1,
|
||||
email = EMAIL,
|
||||
kdf = ACCOUNT_2.profile.toSdkParams(),
|
||||
privateKey = successResponse.accountKeys!!
|
||||
.publicKeyEncryptionKeyPair
|
||||
.wrappedPrivateKey,
|
||||
signingKey = successResponse.accountKeys
|
||||
?.signatureKeyPair
|
||||
?.wrappedSigningKey,
|
||||
securityState = successResponse.accountKeys
|
||||
?.securityState
|
||||
?.securityState,
|
||||
initUserCryptoMethod = InitUserCryptoMethod.Password(
|
||||
password = PASSWORD,
|
||||
userKey = successResponse.key!!,
|
||||
),
|
||||
organizationKeys = null,
|
||||
)
|
||||
vaultRepository.syncIfNecessary()
|
||||
settingsRepository.storeUserHasLoggedInValue(userId = USER_ID_1)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val FIXED_CLOCK: Clock = Clock.fixed(
|
||||
Instant.parse("2023-10-27T12:00:00Z"),
|
||||
|
||||
@ -9,10 +9,12 @@ import com.bitwarden.core.EnrollPinResponse
|
||||
import com.bitwarden.core.InitOrgCryptoRequest
|
||||
import com.bitwarden.core.InitUserCryptoMethod
|
||||
import com.bitwarden.core.InitUserCryptoRequest
|
||||
import com.bitwarden.core.MasterPasswordUnlockData
|
||||
import com.bitwarden.core.data.manager.realtime.RealtimeManager
|
||||
import com.bitwarden.core.data.util.asFailure
|
||||
import com.bitwarden.core.data.util.asSuccess
|
||||
import com.bitwarden.crypto.HashPurpose
|
||||
import com.bitwarden.crypto.Kdf
|
||||
import com.bitwarden.data.datasource.disk.base.FakeDispatcherManager
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
|
||||
@ -1001,6 +1003,7 @@ class VaultLockManagerTest {
|
||||
request = InitOrgCryptoRequest(organizationKeys = organizationKeys),
|
||||
)
|
||||
trustedDeviceManager.trustThisDeviceIfNecessary(userId = USER_ID)
|
||||
kdfManager.updateKdfToMinimumsIfNeeded(masterPassword)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1764,6 +1767,107 @@ class VaultLockManagerTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `unlockVault with initUserCryptoMethod masterPasswordUnlock success should hash and store master password`() =
|
||||
runTest {
|
||||
val kdf = MOCK_PROFILE.toSdkParams()
|
||||
val email = MOCK_PROFILE.email
|
||||
val masterPassword = "drowssap"
|
||||
val privateKey = "54321"
|
||||
val organizationKeys = mapOf("orgId1" to "orgKey1")
|
||||
val initUserCryptoMethod = InitUserCryptoMethod.MasterPasswordUnlock(
|
||||
password = masterPassword,
|
||||
masterPasswordUnlock = MasterPasswordUnlockData(
|
||||
kdf = mockk<Kdf>(relaxed = true),
|
||||
masterKeyWrappedUserKey = "mockKey",
|
||||
salt = "mockSalt",
|
||||
),
|
||||
)
|
||||
coEvery {
|
||||
vaultSdkSource.initializeCrypto(
|
||||
userId = USER_ID,
|
||||
request = InitUserCryptoRequest(
|
||||
userId = USER_ID,
|
||||
kdfParams = kdf,
|
||||
email = email,
|
||||
privateKey = privateKey,
|
||||
method = initUserCryptoMethod,
|
||||
signingKey = null,
|
||||
securityState = null,
|
||||
),
|
||||
)
|
||||
} returns InitializeCryptoResult.Success.asSuccess()
|
||||
coEvery {
|
||||
vaultSdkSource.initializeOrganizationCrypto(
|
||||
userId = USER_ID,
|
||||
request = InitOrgCryptoRequest(organizationKeys = organizationKeys),
|
||||
)
|
||||
} returns InitializeCryptoResult.Success.asSuccess()
|
||||
coEvery {
|
||||
trustedDeviceManager.trustThisDeviceIfNecessary(userId = USER_ID)
|
||||
} returns false.asSuccess()
|
||||
assertEquals(
|
||||
emptyList<VaultUnlockData>(),
|
||||
vaultLockManager.vaultUnlockDataStateFlow.value,
|
||||
)
|
||||
mutableVaultTimeoutStateFlow.value = VaultTimeout.ThirtyMinutes
|
||||
fakeAuthDiskSource.storeUserAutoUnlockKey(
|
||||
userId = USER_ID,
|
||||
userAutoUnlockKey = null,
|
||||
)
|
||||
|
||||
val result = vaultLockManager.unlockVault(
|
||||
userId = USER_ID,
|
||||
email = email,
|
||||
kdf = kdf,
|
||||
privateKey = privateKey,
|
||||
signingKey = null,
|
||||
securityState = null,
|
||||
initUserCryptoMethod = initUserCryptoMethod,
|
||||
organizationKeys = organizationKeys,
|
||||
)
|
||||
|
||||
assertEquals(VaultUnlockResult.Success, result)
|
||||
assertEquals(
|
||||
listOf(
|
||||
VaultUnlockData(
|
||||
userId = USER_ID,
|
||||
status = VaultUnlockData.Status.UNLOCKED,
|
||||
),
|
||||
),
|
||||
vaultLockManager.vaultUnlockDataStateFlow.value,
|
||||
)
|
||||
|
||||
fakeAuthDiskSource.assertUserAutoUnlockKey(
|
||||
userId = USER_ID,
|
||||
userAutoUnlockKey = null,
|
||||
)
|
||||
fakeAuthDiskSource.assertMasterPasswordHash(
|
||||
userId = USER_ID,
|
||||
passwordHash = "hashedPassword",
|
||||
)
|
||||
coVerify(exactly = 1) {
|
||||
vaultSdkSource.initializeCrypto(
|
||||
userId = USER_ID,
|
||||
request = InitUserCryptoRequest(
|
||||
userId = USER_ID,
|
||||
kdfParams = kdf,
|
||||
email = email,
|
||||
privateKey = privateKey,
|
||||
method = initUserCryptoMethod,
|
||||
signingKey = null,
|
||||
securityState = null,
|
||||
),
|
||||
)
|
||||
vaultSdkSource.initializeOrganizationCrypto(
|
||||
userId = USER_ID,
|
||||
request = InitOrgCryptoRequest(organizationKeys = organizationKeys),
|
||||
)
|
||||
trustedDeviceManager.trustThisDeviceIfNecessary(userId = USER_ID)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the verification call count for the given [mock] while leaving all other mocked
|
||||
* behavior in place.
|
||||
|
||||
@ -13,7 +13,9 @@ import com.bitwarden.data.manager.DispatcherManager
|
||||
import com.bitwarden.exporters.ExportFormat
|
||||
import com.bitwarden.fido.Fido2CredentialAutofillView
|
||||
import com.bitwarden.network.model.CipherTypeJson
|
||||
import com.bitwarden.network.model.MasterPasswordUnlockDataJson
|
||||
import com.bitwarden.network.model.SyncResponseJson
|
||||
import com.bitwarden.network.model.UserDecryptionOptionsJson
|
||||
import com.bitwarden.network.model.createMockCipher
|
||||
import com.bitwarden.network.model.createMockFolder
|
||||
import com.bitwarden.network.model.createMockOrganizationKeys
|
||||
@ -28,6 +30,7 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson
|
||||
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.util.toSdkParams
|
||||
import com.x8bit.bitwarden.data.platform.error.MissingPropertyException
|
||||
import com.x8bit.bitwarden.data.platform.error.NoActiveUserException
|
||||
@ -53,6 +56,7 @@ 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.toEncryptedSdkCipher
|
||||
import com.x8bit.bitwarden.data.vault.repository.util.toSdkMasterPasswordUnlock
|
||||
import com.x8bit.bitwarden.ui.vault.feature.verificationcode.util.createVerificationCodeItem
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
@ -748,6 +752,132 @@ class VaultRepositoryTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `unlockVaultWithMasterPassword with masterPasswordUnlock data should use MasterPasswordUnlock method`() =
|
||||
runTest {
|
||||
val userId = "mockId-1"
|
||||
val masterPassword = "mockPassword-1"
|
||||
val masterPasswordUnlockData = MOCK_MASTER_PASSWORD_UNLOCK_DATA
|
||||
.toSdkMasterPasswordUnlock()
|
||||
val userState = MOCK_USER_STATE.copy(
|
||||
accounts = mapOf(
|
||||
"mockId-1" to MOCK_ACCOUNT.copy(
|
||||
profile = MOCK_PROFILE.copy(
|
||||
userDecryptionOptions = UserDecryptionOptionsJson(
|
||||
hasMasterPassword = true,
|
||||
trustedDeviceUserDecryptionOptions = null,
|
||||
keyConnectorUserDecryptionOptions = null,
|
||||
masterPasswordUnlock = MOCK_MASTER_PASSWORD_UNLOCK_DATA,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
fakeAuthDiskSource.storeUserKey(
|
||||
userId = userId,
|
||||
userKey = "mockKey-1",
|
||||
)
|
||||
fakeAuthDiskSource.userState = userState
|
||||
fakeAuthDiskSource.storePrivateKey(userId = userId, privateKey = "mockPrivateKey-1")
|
||||
|
||||
coEvery {
|
||||
vaultLockManager.unlockVault(
|
||||
userId = userId,
|
||||
email = "email",
|
||||
kdf = MOCK_PROFILE.toSdkParams(),
|
||||
privateKey = "mockPrivateKey-1",
|
||||
signingKey = null,
|
||||
securityState = null,
|
||||
initUserCryptoMethod = InitUserCryptoMethod.MasterPasswordUnlock(
|
||||
password = masterPassword,
|
||||
masterPasswordUnlock = masterPasswordUnlockData,
|
||||
),
|
||||
organizationKeys = null,
|
||||
)
|
||||
} returns VaultUnlockResult.Success
|
||||
|
||||
val result = vaultRepository.unlockVaultWithMasterPassword(
|
||||
masterPassword = masterPassword,
|
||||
)
|
||||
|
||||
assertEquals(VaultUnlockResult.Success, result)
|
||||
coVerify {
|
||||
vaultLockManager.unlockVault(
|
||||
userId = userId,
|
||||
email = "email",
|
||||
kdf = MOCK_PROFILE.toSdkParams(),
|
||||
privateKey = "mockPrivateKey-1",
|
||||
signingKey = null,
|
||||
securityState = null,
|
||||
initUserCryptoMethod = InitUserCryptoMethod.MasterPasswordUnlock(
|
||||
password = masterPassword,
|
||||
masterPasswordUnlock = masterPasswordUnlockData,
|
||||
),
|
||||
organizationKeys = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `unlockVaultWithMasterPassword without masterPasswordUnlock data should use Password method`() =
|
||||
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(
|
||||
profile = MOCK_PROFILE.copy(
|
||||
userDecryptionOptions = null,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
fakeAuthDiskSource.userState = userState
|
||||
fakeAuthDiskSource.storePrivateKey(userId = userId, privateKey = "mockPrivateKey-1")
|
||||
fakeAuthDiskSource.storeUserKey(userId = userId, userKey = userKey)
|
||||
|
||||
coEvery {
|
||||
vaultLockManager.unlockVault(
|
||||
userId = userId,
|
||||
email = "email",
|
||||
kdf = MOCK_PROFILE.toSdkParams(),
|
||||
privateKey = "mockPrivateKey-1",
|
||||
signingKey = null,
|
||||
securityState = null,
|
||||
initUserCryptoMethod = InitUserCryptoMethod.Password(
|
||||
password = masterPassword,
|
||||
userKey = userKey,
|
||||
),
|
||||
organizationKeys = null,
|
||||
)
|
||||
} returns VaultUnlockResult.Success
|
||||
|
||||
val result = vaultRepository.unlockVaultWithMasterPassword(
|
||||
masterPassword = masterPassword,
|
||||
)
|
||||
|
||||
assertEquals(VaultUnlockResult.Success, result)
|
||||
coVerify {
|
||||
vaultLockManager.unlockVault(
|
||||
userId = userId,
|
||||
email = "email",
|
||||
kdf = MOCK_PROFILE.toSdkParams(),
|
||||
privateKey = "mockPrivateKey-1",
|
||||
signingKey = null,
|
||||
securityState = null,
|
||||
initUserCryptoMethod = InitUserCryptoMethod.Password(
|
||||
password = masterPassword,
|
||||
userKey = userKey,
|
||||
),
|
||||
organizationKeys = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `unlockVaultWithMasterPassword with VaultLockManager non-Success should unlock for the current user and return the error`() =
|
||||
@ -1615,3 +1745,9 @@ private val MOCK_USER_STATE = UserStateJson(
|
||||
"mockId-1" to MOCK_ACCOUNT,
|
||||
),
|
||||
)
|
||||
|
||||
private val MOCK_MASTER_PASSWORD_UNLOCK_DATA = MasterPasswordUnlockDataJson(
|
||||
salt = "mockSalt",
|
||||
kdf = MOCK_ACCOUNT.profile.toSdkParams().toKdfRequestModel(),
|
||||
masterKeyWrappedUserKey = "masterKeyWrappedUserKeyMock",
|
||||
)
|
||||
|
||||
@ -1,22 +1,29 @@
|
||||
package com.bitwarden.network.model
|
||||
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonNames
|
||||
|
||||
/**
|
||||
* Represents the data used to create the kdf settings.
|
||||
*/
|
||||
@Serializable
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
data class KdfJson(
|
||||
@SerialName("KdfType")
|
||||
@SerialName("kdfType")
|
||||
@JsonNames("KdfType")
|
||||
val kdfType: KdfTypeJson,
|
||||
|
||||
@SerialName("Iterations")
|
||||
@SerialName("iterations")
|
||||
@JsonNames("Iterations")
|
||||
val iterations: Int,
|
||||
|
||||
@SerialName("Memory")
|
||||
@SerialName("memory")
|
||||
@JsonNames("Memory")
|
||||
val memory: Int?,
|
||||
|
||||
@SerialName("Parallelism")
|
||||
@SerialName("parallelism")
|
||||
@JsonNames("Parallelism")
|
||||
val parallelism: Int?,
|
||||
)
|
||||
|
||||
@ -11,15 +11,17 @@ import kotlinx.serialization.json.JsonNames
|
||||
@Serializable
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
data class MasterPasswordUnlockDataJson(
|
||||
@SerialName("Salt")
|
||||
@SerialName("salt")
|
||||
@JsonNames("Salt")
|
||||
val salt: String,
|
||||
|
||||
@SerialName("Kdf")
|
||||
@SerialName("kdf")
|
||||
@JsonNames("Kdf")
|
||||
val kdf: KdfJson,
|
||||
|
||||
// TODO: PM-26397 this was done due to naming inconsistency server side,
|
||||
// should be cleaned up when server side is updated
|
||||
@SerialName("MasterKeyWrappedUserKey")
|
||||
@JsonNames("MasterKeyEncryptedUserKey")
|
||||
@SerialName("masterKeyWrappedUserKey")
|
||||
@JsonNames("masterKeyEncryptedUserKey", "MasterKeyEncryptedUserKey")
|
||||
val masterKeyWrappedUserKey: String,
|
||||
)
|
||||
|
||||
@ -49,7 +49,7 @@ data class SyncResponseJson(
|
||||
@SerialName("sends")
|
||||
val sends: List<Send>?,
|
||||
|
||||
@SerialName("UserDecryption")
|
||||
@SerialName("userDecryption")
|
||||
val userDecryption: UserDecryptionJson?,
|
||||
) {
|
||||
/**
|
||||
|
||||
@ -8,6 +8,6 @@ import kotlinx.serialization.Serializable
|
||||
*/
|
||||
@Serializable
|
||||
data class UserDecryptionJson(
|
||||
@SerialName("MasterPasswordUnlock")
|
||||
@SerialName("masterPasswordUnlock")
|
||||
val masterPasswordUnlock: MasterPasswordUnlockDataJson?,
|
||||
)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user