PM-19389: Handle encoding error when migrating biometric key (#4900)

This commit is contained in:
David Perez
2025-03-20 14:15:44 -05:00
committed by GitHub
parent 3eed1c1abe
commit 29371bdcb5
2 changed files with 58 additions and 15 deletions

View File

@@ -557,31 +557,46 @@ class VaultRepositoryImpl(
error = MissingPropertyException("Biometric key"),
)
val iv = authDiskSource.getUserBiometricInitVector(userId = userId)
val decryptedUserKey = iv
?.let {
try {
cipher
.doFinal(biometricsKey.toByteArray(Charsets.ISO_8859_1))
.decodeToString()
} catch (e: GeneralSecurityException) {
return VaultUnlockResult.BiometricDecodingError(error = e)
}
}
?: biometricsKey
val encryptedBiometricsKey = if (iv == null) {
// Attempting to setup an encrypted pin before unlocking, if this fails we send back
// the biometrics error and users will need to sign in another way and re-setup
// biometrics.
try {
cipher
.doFinal(biometricsKey.encodeToByteArray())
.toString(Charsets.ISO_8859_1)
} catch (e: GeneralSecurityException) {
return VaultUnlockResult.BiometricDecodingError(error = e)
}
} else {
null
}
return this
.unlockVaultForUser(
userId = userId,
initUserCryptoMethod = InitUserCryptoMethod.DecryptedKey(
decryptedUserKey = iv
?.let {
try {
cipher
.doFinal(biometricsKey.toByteArray(Charsets.ISO_8859_1))
.decodeToString()
} catch (e: GeneralSecurityException) {
return VaultUnlockResult.BiometricDecodingError(error = e)
}
}
?: biometricsKey,
decryptedUserKey = decryptedUserKey,
),
)
.also {
if (it is VaultUnlockResult.Success) {
if (iv == null) {
encryptedBiometricsKey?.let {
// If this key is present, we store it and the associated IV for future use
// since we want to migrate the user to a more secure form of biometrics.
authDiskSource.storeUserBiometricUnlockKey(
userId = userId,
biometricsKey = cipher
.doFinal(biometricsKey.encodeToByteArray())
.toString(Charsets.ISO_8859_1),
biometricsKey = it,
)
authDiskSource.storeUserBiometricInitVector(userId = userId, iv = cipher.iv)
}

View File

@@ -131,6 +131,7 @@ import org.junit.jupiter.api.Test
import retrofit2.HttpException
import java.io.File
import java.net.UnknownHostException
import java.security.GeneralSecurityException
import java.security.MessageDigest
import java.time.Clock
import java.time.Instant
@@ -1296,6 +1297,33 @@ class VaultRepositoryTest {
}
}
@Suppress("MaxLineLength")
@Test
fun `unlockVaultWithBiometrics with failure to encode biometrics key should return BiometricDecodingError`() =
runTest {
val userId = MOCK_USER_STATE.activeUserId
val biometricsKey = "asdf1234"
val error = GeneralSecurityException()
val cipher = mockk<Cipher> {
every { doFinal(any()) } throws error
}
fakeAuthDiskSource.userState = MOCK_USER_STATE
fakeAuthDiskSource.storeUserBiometricUnlockKey(
userId = userId,
biometricsKey = biometricsKey,
)
val result = vaultRepository.unlockVaultWithBiometrics(cipher = cipher)
assertEquals(
VaultUnlockResult.BiometricDecodingError(error = error),
result,
)
coVerify(exactly = 0) {
vaultSdkSource.derivePinProtectedUserKey(any(), any())
}
}
@Suppress("MaxLineLength")
@Test
fun `unlockVaultWithBiometrics with an IV and VaultLockManager Success should store the updated key and IV and unlock for the current user and return Success`() =