Skip to content

Commit d966423

Browse files
authored
[PM-23280] Use masterPasswordUnlock KDF settings on vault unlock (#6026)
1 parent f7cbcd2 commit d966423

File tree

3 files changed

+124
-1
lines changed

3 files changed

+124
-1
lines changed

app/src/main/kotlin/com/x8bit/bitwarden/data/auth/datasource/sdk/util/KdfExtensions.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import com.bitwarden.network.model.KdfJson
55
import com.bitwarden.network.model.KdfTypeJson
66
import com.bitwarden.network.model.KdfTypeJson.ARGON2_ID
77
import com.bitwarden.network.model.KdfTypeJson.PBKDF2_SHA256
8+
import com.x8bit.bitwarden.data.auth.util.KdfParamsConstants.DEFAULT_ARGON2_MEMORY
9+
import com.x8bit.bitwarden.data.auth.util.KdfParamsConstants.DEFAULT_ARGON2_PARALLELISM
810

911
/**
1012
* Convert a [Kdf] to a [KdfTypeJson].
@@ -34,3 +36,16 @@ fun Kdf.toKdfRequestModel(): KdfJson =
3436
parallelism = null,
3537
)
3638
}
39+
40+
/**
41+
* Convert a [KdfJson] to a [Kdf].
42+
*/
43+
fun KdfJson.toKdf(): Kdf =
44+
when (this.kdfType) {
45+
ARGON2_ID -> Kdf.Argon2id(
46+
iterations = iterations.toUInt(),
47+
memory = memory?.toUInt() ?: DEFAULT_ARGON2_MEMORY.toUInt(),
48+
parallelism = parallelism?.toUInt() ?: DEFAULT_ARGON2_PARALLELISM.toUInt(),
49+
)
50+
PBKDF2_SHA256 -> Kdf.Pbkdf2(iterations = iterations.toUInt())
51+
}

app/src/main/kotlin/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryImpl.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
5151
import com.x8bit.bitwarden.data.auth.datasource.network.model.DeviceDataModel
5252
import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSource
5353
import com.x8bit.bitwarden.data.auth.datasource.sdk.util.toInt
54+
import com.x8bit.bitwarden.data.auth.datasource.sdk.util.toKdf
5455
import com.x8bit.bitwarden.data.auth.datasource.sdk.util.toKdfTypeJson
5556
import com.x8bit.bitwarden.data.auth.manager.AuthRequestManager
5657
import com.x8bit.bitwarden.data.auth.manager.KdfManager
@@ -2046,10 +2047,20 @@ class AuthRepositoryImpl(
20462047
initUserCryptoMethod: InitUserCryptoMethod,
20472048
): VaultUnlockResult {
20482049
val userId = accountProfile.userId
2050+
val kdfParams = (initUserCryptoMethod as? InitUserCryptoMethod.Password)
2051+
?.let {
2052+
accountProfile
2053+
.userDecryptionOptions
2054+
?.masterPasswordUnlock
2055+
?.kdf
2056+
?.toKdf()
2057+
}
2058+
?: accountProfile.toSdkParams()
2059+
20492060
return vaultRepository.unlockVault(
20502061
userId = userId,
20512062
email = accountProfile.email,
2052-
kdf = accountProfile.toSdkParams(),
2063+
kdf = kdfParams,
20532064
privateKey = privateKey,
20542065
signingKey = signingKey,
20552066
securityState = securityState,

app/src/test/kotlin/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryTest.kt

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import com.bitwarden.network.model.GetTokenResponseJson
3030
import com.bitwarden.network.model.IdentityTokenAuthModel
3131
import com.bitwarden.network.model.KdfTypeJson
3232
import com.bitwarden.network.model.KeyConnectorMasterKeyResponseJson
33+
import com.bitwarden.network.model.MasterPasswordUnlockDataJson
3334
import com.bitwarden.network.model.OrganizationAutoEnrollStatusResponseJson
3435
import com.bitwarden.network.model.OrganizationKeysResponseJson
3536
import com.bitwarden.network.model.OrganizationType
@@ -76,6 +77,7 @@ import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength.LEVEL
7677
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength.LEVEL_2
7778
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength.LEVEL_3
7879
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength.LEVEL_4
80+
import com.x8bit.bitwarden.data.auth.datasource.sdk.util.toKdfRequestModel
7981
import com.x8bit.bitwarden.data.auth.manager.AuthRequestManager
8082
import com.x8bit.bitwarden.data.auth.manager.KdfManager
8183
import com.x8bit.bitwarden.data.auth.manager.KeyConnectorManager
@@ -6985,6 +6987,79 @@ class AuthRepositoryTest {
69856987
)
69866988
}
69876989

6990+
@Test
6991+
fun `unlockVault uses user decryption options for KDF when init method is password`() =
6992+
runTest {
6993+
val successResponse = GET_TOKEN_WITH_ACCOUNT_KEYS_RESPONSE_SUCCESS
6994+
coEvery {
6995+
identityService.preLogin(email = EMAIL)
6996+
} returns PRE_LOGIN_SUCCESS.asSuccess()
6997+
coEvery {
6998+
identityService.getToken(
6999+
email = EMAIL,
7000+
authModel = IdentityTokenAuthModel.MasterPassword(
7001+
username = EMAIL,
7002+
password = PASSWORD_HASH,
7003+
),
7004+
uniqueAppId = UNIQUE_APP_ID,
7005+
)
7006+
} returns successResponse.asSuccess()
7007+
coEvery {
7008+
vaultRepository.unlockVault(
7009+
userId = USER_ID_1,
7010+
email = EMAIL,
7011+
kdf = ACCOUNT_2.profile.toSdkParams(),
7012+
privateKey = successResponse.accountKeys!!
7013+
.publicKeyEncryptionKeyPair
7014+
.wrappedPrivateKey,
7015+
signingKey = successResponse.accountKeys
7016+
?.signatureKeyPair
7017+
?.wrappedSigningKey,
7018+
securityState = successResponse.accountKeys
7019+
?.securityState
7020+
?.securityState,
7021+
initUserCryptoMethod = InitUserCryptoMethod.Password(
7022+
password = PASSWORD,
7023+
userKey = successResponse.key!!,
7024+
),
7025+
organizationKeys = null,
7026+
)
7027+
} returns VaultUnlockResult.Success
7028+
coEvery { vaultRepository.syncIfNecessary() } just runs
7029+
every {
7030+
GET_TOKEN_WITH_ACCOUNT_KEYS_RESPONSE_SUCCESS.toUserState(
7031+
previousUserState = null,
7032+
environmentUrlData = EnvironmentUrlDataJson.DEFAULT_US,
7033+
)
7034+
} returns SINGLE_USER_STATE_1_WITH_DECRYPTION_OPTIONS
7035+
7036+
repository.login(email = EMAIL, password = PASSWORD)
7037+
7038+
coVerify {
7039+
vaultRepository.unlockVault(
7040+
userId = USER_ID_1,
7041+
email = EMAIL,
7042+
kdf = ACCOUNT_2.profile.toSdkParams(),
7043+
privateKey = successResponse.accountKeys!!
7044+
.publicKeyEncryptionKeyPair
7045+
.wrappedPrivateKey,
7046+
signingKey = successResponse.accountKeys
7047+
?.signatureKeyPair
7048+
?.wrappedSigningKey,
7049+
securityState = successResponse.accountKeys
7050+
?.securityState
7051+
?.securityState,
7052+
initUserCryptoMethod = InitUserCryptoMethod.Password(
7053+
password = PASSWORD,
7054+
userKey = successResponse.key!!,
7055+
),
7056+
organizationKeys = null,
7057+
)
7058+
vaultRepository.syncIfNecessary()
7059+
settingsRepository.storeUserHasLoggedInValue(userId = USER_ID_1)
7060+
}
7061+
}
7062+
69887063
companion object {
69897064
private val FIXED_CLOCK: Clock = Clock.fixed(
69907065
Instant.parse("2023-10-27T12:00:00Z"),
@@ -7175,6 +7250,28 @@ class AuthRepositoryTest {
71757250
),
71767251
)
71777252

7253+
private val MOCK_MASTER_PASSWORD_UNLOCK_DATA = MasterPasswordUnlockDataJson(
7254+
salt = "mockSalt",
7255+
kdf = ACCOUNT_2.profile.toSdkParams().toKdfRequestModel(),
7256+
masterKeyWrappedUserKey = "masterKeyWrappedUserKeyMock",
7257+
)
7258+
7259+
private val SINGLE_USER_STATE_1_WITH_DECRYPTION_OPTIONS = UserStateJson(
7260+
activeUserId = USER_ID_1,
7261+
accounts = mapOf(
7262+
USER_ID_1 to ACCOUNT_1.copy(
7263+
profile = ACCOUNT_1.profile.copy(
7264+
userDecryptionOptions = UserDecryptionOptionsJson(
7265+
hasMasterPassword = true,
7266+
keyConnectorUserDecryptionOptions = null,
7267+
trustedDeviceUserDecryptionOptions = null,
7268+
masterPasswordUnlock = MOCK_MASTER_PASSWORD_UNLOCK_DATA,
7269+
),
7270+
),
7271+
),
7272+
),
7273+
)
7274+
71787275
private val SINGLE_USER_STATE_2 = UserStateJson(
71797276
activeUserId = USER_ID_2,
71807277
accounts = mapOf(

0 commit comments

Comments
 (0)