[PM-23278] Upgrade user KDF settings to minimums (#5955)

Co-authored-by: David Perez <david@livefront.com>
This commit is contained in:
André Bispo
2025-10-09 08:49:22 +01:00
committed by GitHub
parent 44c373a354
commit d98ff6478f
35 changed files with 1573 additions and 23 deletions

View File

@@ -5,6 +5,7 @@ import com.bitwarden.network.model.DeleteAccountRequestJson
import com.bitwarden.network.model.NetworkResult
import com.bitwarden.network.model.ResetPasswordRequestJson
import com.bitwarden.network.model.SetPasswordRequestJson
import com.bitwarden.network.model.UpdateKdfJsonRequest
import com.bitwarden.network.model.VerifyOtpRequestJson
import retrofit2.http.Body
import retrofit2.http.HTTP
@@ -36,6 +37,12 @@ internal interface AuthenticatedAccountsApi {
@POST("/accounts/request-otp")
suspend fun requestOtp(): NetworkResult<Unit>
/**
* Update the KDF settings for the current account.
*/
@POST("/accounts/kdf")
suspend fun updateKdf(@Body body: UpdateKdfJsonRequest): NetworkResult<Unit>
@POST("/accounts/verify-otp")
suspend fun verifyOtp(
@Body body: VerifyOtpRequestJson,

View File

@@ -0,0 +1,19 @@
package com.bitwarden.network.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* Represents the data used to authenticate with the master password.
*/
@Serializable
data class MasterPasswordAuthenticationDataJson(
@SerialName("Kdf")
val kdf: KdfJson,
@SerialName("MasterPasswordAuthenticationHash")
val masterPasswordAuthenticationHash: String,
@SerialName("Salt")
val salt: String,
)

View File

@@ -0,0 +1,25 @@
package com.bitwarden.network.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* Represents the request body used to update the user's kdf settings.
*/
@Serializable
data class UpdateKdfJsonRequest(
@SerialName("authenticationData")
val authenticationData: MasterPasswordAuthenticationDataJson,
@SerialName("key")
val key: String,
@SerialName("masterPasswordHash")
val masterPasswordHash: String,
@SerialName("newMasterPasswordHash")
val newMasterPasswordHash: String,
@SerialName("unlockData")
val unlockData: MasterPasswordUnlockDataJson,
)

View File

@@ -8,6 +8,7 @@ import com.bitwarden.network.model.ResendEmailRequestJson
import com.bitwarden.network.model.ResendNewDeviceOtpRequestJson
import com.bitwarden.network.model.ResetPasswordRequestJson
import com.bitwarden.network.model.SetPasswordRequestJson
import com.bitwarden.network.model.UpdateKdfJsonRequest
import com.bitwarden.network.model.VerificationCodeResponseJson
import com.bitwarden.network.model.VerificationOtpResponseJson
@@ -115,4 +116,9 @@ interface AccountsService {
accessToken: String,
masterKey: String,
): Result<Unit>
/**
* Update the KDF settings for the current account.
*/
suspend fun updateKdf(body: UpdateKdfJsonRequest): Result<Unit>
}

View File

@@ -16,6 +16,7 @@ import com.bitwarden.network.model.ResendEmailRequestJson
import com.bitwarden.network.model.ResendNewDeviceOtpRequestJson
import com.bitwarden.network.model.ResetPasswordRequestJson
import com.bitwarden.network.model.SetPasswordRequestJson
import com.bitwarden.network.model.UpdateKdfJsonRequest
import com.bitwarden.network.model.VerificationCodeResponseJson
import com.bitwarden.network.model.VerificationOtpResponseJson
import com.bitwarden.network.model.VerifyOtpRequestJson
@@ -209,4 +210,9 @@ internal class AccountsServiceImpl(
body = KeyConnectorMasterKeyRequestJson(masterKey = masterKey),
)
.toResult()
override suspend fun updateKdf(body: UpdateKdfJsonRequest): Result<Unit> =
authenticatedAccountsApi
.updateKdf(body)
.toResult()
}

View File

@@ -6,15 +6,19 @@ import com.bitwarden.network.api.AuthenticatedKeyConnectorApi
import com.bitwarden.network.api.UnauthenticatedAccountsApi
import com.bitwarden.network.api.UnauthenticatedKeyConnectorApi
import com.bitwarden.network.base.BaseServiceTest
import com.bitwarden.network.model.KdfJson
import com.bitwarden.network.model.KdfTypeJson
import com.bitwarden.network.model.KeyConnectorKeyRequestJson
import com.bitwarden.network.model.KeyConnectorMasterKeyResponseJson
import com.bitwarden.network.model.MasterPasswordAuthenticationDataJson
import com.bitwarden.network.model.MasterPasswordUnlockDataJson
import com.bitwarden.network.model.PasswordHintResponseJson
import com.bitwarden.network.model.RegisterRequestJson
import com.bitwarden.network.model.ResendEmailRequestJson
import com.bitwarden.network.model.ResendNewDeviceOtpRequestJson
import com.bitwarden.network.model.ResetPasswordRequestJson
import com.bitwarden.network.model.SetPasswordRequestJson
import com.bitwarden.network.model.UpdateKdfJsonRequest
import com.bitwarden.network.model.VerificationCodeResponseJson
import com.bitwarden.network.model.VerificationOtpResponseJson
import kotlinx.coroutines.test.runTest
@@ -291,6 +295,25 @@ class AccountsServiceTest : BaseServiceTest() {
}
@Test
fun `updateKdf success should return Success`() = runTest {
val response = MockResponse().setResponseCode(200)
server.enqueue(response)
val result = service.updateKdf(body = UPDATE_KDF_REQUEST)
assertTrue(result.isSuccess)
}
@Test
fun `updateKdf failure should return Failure`() = runTest {
val response = MockResponse().setResponseCode(400)
server.enqueue(response)
val result = service.updateKdf(body = UPDATE_KDF_REQUEST)
assertTrue(result.isFailure)
}
fun `resendNewDeviceOtp with 400 response is Error`() = runTest {
val response = MockResponse().setResponseCode(400).setBody(INVALID_JSON)
server.enqueue(response)
@@ -318,3 +341,29 @@ private const val INVALID_JSON = """
"validationErrors": null
}
"""
private val UPDATE_KDF_REQUEST = UpdateKdfJsonRequest(
authenticationData = MasterPasswordAuthenticationDataJson(
kdf = KdfJson(
kdfType = KdfTypeJson.PBKDF2_SHA256,
iterations = 7,
memory = 1,
parallelism = 2,
),
masterPasswordAuthenticationHash = "mockMasterPasswordHash",
salt = "mockSalt",
),
key = "mockKey",
masterPasswordHash = "mockMasterPasswordHash",
newMasterPasswordHash = "mockNewMasterPasswordHash",
unlockData = MasterPasswordUnlockDataJson(
kdf = KdfJson(
kdfType = KdfTypeJson.PBKDF2_SHA256,
iterations = 7,
memory = 1,
parallelism = 2,
),
masterKeyWrappedUserKey = "mockMasterPasswordKey",
salt = "mockSalt",
),
)