[PM-23290] Migrate PIN unlock keys to PinProtectedUserKeyEnvelope (#6024)

This commit is contained in:
André Bispo 2025-10-20 18:31:12 +01:00 committed by GitHub
parent d5912a5dc3
commit afeeb494da
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 645 additions and 137 deletions

View File

@ -216,25 +216,59 @@ interface AuthDiskSource : AppIdProvider {
/** /**
* Retrieves a pin-protected user key for the given [userId]. * Retrieves a pin-protected user key for the given [userId].
*/ */
@Deprecated(
message = "Use getPinProtectedUserKeyEnvelope instead.",
replaceWith = ReplaceWith("getPinProtectedUserKeyEnvelope"),
)
fun getPinProtectedUserKey(userId: String): String? fun getPinProtectedUserKey(userId: String): String?
/**
* Retrieves a pin-protected user key envelope for the given [userId].
*/
fun getPinProtectedUserKeyEnvelope(userId: String): String?
/** /**
* Stores a pin-protected user key for the given [userId]. * Stores a pin-protected user key for the given [userId].
* *
* When [inMemoryOnly] is `true`, the value will only be available via a call to * When [inMemoryOnly] is `true`, the value will only be available via a call to
* [getPinProtectedUserKey] during the current app session. * [getPinProtectedUserKey] during the current app session.
*/ */
@Deprecated(
message = "Use storePinProtectedUserKeyEnvelope instead.",
replaceWith = ReplaceWith("storePinProtectedUserKeyEnvelope"),
)
fun storePinProtectedUserKey( fun storePinProtectedUserKey(
userId: String, userId: String,
pinProtectedUserKey: String?, pinProtectedUserKey: String?,
inMemoryOnly: Boolean = false, inMemoryOnly: Boolean = false,
) )
/**
* Stores a pin-protected user key envelope for the given [userId].
*
* When [inMemoryOnly] is `true`, the value will only be available via a call to
* [getPinProtectedUserKeyEnvelope] during the current app session.
*/
fun storePinProtectedUserKeyEnvelope(
userId: String,
pinProtectedUserKeyEnvelope: String?,
inMemoryOnly: Boolean = false,
)
/** /**
* Retrieves a flow for the pin-protected user key for the given [userId]. * Retrieves a flow for the pin-protected user key for the given [userId].
*/ */
@Deprecated(
message = "Use getPinProtectedUserKeyEnvelopeFlow instead.",
replaceWith = ReplaceWith("getPinProtectedUserKeyEnvelopeFlow"),
)
fun getPinProtectedUserKeyFlow(userId: String): Flow<String?> fun getPinProtectedUserKeyFlow(userId: String): Flow<String?>
/**
* Retrieves a flow for the pin-protected user key envelope for the given [userId].
*/
fun getPinProtectedUserKeyEnvelopeFlow(userId: String): Flow<String?>
/** /**
* Gets a two-factor auth token using a user's [email]. * Gets a two-factor auth token using a user's [email].
*/ */

View File

@ -37,6 +37,7 @@ private const val INVALID_UNLOCK_ATTEMPTS_KEY = "invalidUnlockAttempts"
private const val MASTER_KEY_ENCRYPTION_USER_KEY = "masterKeyEncryptedUserKey" private const val MASTER_KEY_ENCRYPTION_USER_KEY = "masterKeyEncryptedUserKey"
private const val MASTER_KEY_ENCRYPTION_PRIVATE_KEY = "encPrivateKey" private const val MASTER_KEY_ENCRYPTION_PRIVATE_KEY = "encPrivateKey"
private const val PIN_PROTECTED_USER_KEY_KEY = "pinKeyEncryptedUserKey" private const val PIN_PROTECTED_USER_KEY_KEY = "pinKeyEncryptedUserKey"
private const val PIN_PROTECTED_USER_KEY_KEY_ENVELOPE = "pinKeyEncryptedUserKeyEnvelope"
private const val ENCRYPTED_PIN_KEY = "protectedPin" private const val ENCRYPTED_PIN_KEY = "protectedPin"
private const val ORGANIZATIONS_KEY = "organizations" private const val ORGANIZATIONS_KEY = "organizations"
private const val ORGANIZATION_KEYS_KEY = "encOrgKeys" private const val ORGANIZATION_KEYS_KEY = "encOrgKeys"
@ -67,6 +68,7 @@ class AuthDiskSourceImpl(
AuthDiskSource { AuthDiskSource {
private val inMemoryPinProtectedUserKeys = mutableMapOf<String, String?>() private val inMemoryPinProtectedUserKeys = mutableMapOf<String, String?>()
private val inMemoryPinProtectedUserKeyEnvelopes = mutableMapOf<String, String?>()
private val mutableShouldUseKeyConnectorFlowMap = private val mutableShouldUseKeyConnectorFlowMap =
mutableMapOf<String, MutableSharedFlow<Boolean?>>() mutableMapOf<String, MutableSharedFlow<Boolean?>>()
private val mutableOrganizationsFlowMap = private val mutableOrganizationsFlowMap =
@ -82,6 +84,8 @@ class AuthDiskSourceImpl(
mutableMapOf<String, MutableSharedFlow<String?>>() mutableMapOf<String, MutableSharedFlow<String?>>()
private val mutablePinProtectedUserKeyFlowMap = private val mutablePinProtectedUserKeyFlowMap =
mutableMapOf<String, MutableSharedFlow<String?>>() mutableMapOf<String, MutableSharedFlow<String?>>()
private val mutablePinProtectedUserKeyEnvelopeFlowMap =
mutableMapOf<String, MutableSharedFlow<String?>>()
private val mutableUserStateFlow = bufferedMutableSharedFlow<UserStateJson?>(replay = 1) private val mutableUserStateFlow = bufferedMutableSharedFlow<UserStateJson?>(replay = 1)
override var userState: UserStateJson? override var userState: UserStateJson?
@ -142,6 +146,7 @@ class AuthDiskSourceImpl(
storeUserKey(userId = userId, userKey = null) storeUserKey(userId = userId, userKey = null)
storeUserAutoUnlockKey(userId = userId, userAutoUnlockKey = null) storeUserAutoUnlockKey(userId = userId, userAutoUnlockKey = null)
storePinProtectedUserKey(userId = userId, pinProtectedUserKey = null) storePinProtectedUserKey(userId = userId, pinProtectedUserKey = null)
storePinProtectedUserKeyEnvelope(userId = userId, pinProtectedUserKeyEnvelope = null)
storeEncryptedPin(userId = userId, encryptedPin = null) storeEncryptedPin(userId = userId, encryptedPin = null)
storePrivateKey(userId = userId, privateKey = null) storePrivateKey(userId = userId, privateKey = null)
storeAccountKeys(userId = userId, accountKeys = null) storeAccountKeys(userId = userId, accountKeys = null)
@ -329,10 +334,24 @@ class AuthDiskSourceImpl(
getMutableBiometricUnlockKeyFlow(userId) getMutableBiometricUnlockKeyFlow(userId)
.onSubscription { emit(getUserBiometricUnlockKey(userId = userId)) } .onSubscription { emit(getUserBiometricUnlockKey(userId = userId)) }
@Deprecated(
"Use getPinProtectedUserKeyEnvelope instead.",
replaceWith = ReplaceWith("getPinProtectedUserKeyEnvelope"),
)
override fun getPinProtectedUserKey(userId: String): String? = override fun getPinProtectedUserKey(userId: String): String? =
inMemoryPinProtectedUserKeys[userId] inMemoryPinProtectedUserKeys[userId]
?: getString(key = PIN_PROTECTED_USER_KEY_KEY.appendIdentifier(userId)) ?: getString(key = PIN_PROTECTED_USER_KEY_KEY.appendIdentifier(userId))
override fun getPinProtectedUserKeyEnvelope(userId: String): String? =
inMemoryPinProtectedUserKeyEnvelopes[userId]
?: getString(
key = PIN_PROTECTED_USER_KEY_KEY_ENVELOPE.appendIdentifier(userId),
)
@Deprecated(
"Use storePinProtectedUserKeyEnvelope instead.",
replaceWith = ReplaceWith("storePinProtectedUserKeyEnvelope"),
)
override fun storePinProtectedUserKey( override fun storePinProtectedUserKey(
userId: String, userId: String,
pinProtectedUserKey: String?, pinProtectedUserKey: String?,
@ -347,10 +366,32 @@ class AuthDiskSourceImpl(
getMutablePinProtectedUserKeyFlow(userId).tryEmit(pinProtectedUserKey) getMutablePinProtectedUserKeyFlow(userId).tryEmit(pinProtectedUserKey)
} }
override fun storePinProtectedUserKeyEnvelope(
userId: String,
pinProtectedUserKeyEnvelope: String?,
inMemoryOnly: Boolean,
) {
inMemoryPinProtectedUserKeyEnvelopes[userId] = pinProtectedUserKeyEnvelope
if (inMemoryOnly) return
putString(
key = PIN_PROTECTED_USER_KEY_KEY_ENVELOPE.appendIdentifier(userId),
value = pinProtectedUserKeyEnvelope,
)
getMutablePinProtectedUserKeyEnvelopeFlow(userId).tryEmit(pinProtectedUserKeyEnvelope)
}
@Deprecated(
"Use getPinProtectedUserKeyEnvelopeFlow instead.",
replaceWith = ReplaceWith("getPinProtectedUserKeyEnvelopeFlow"),
)
override fun getPinProtectedUserKeyFlow(userId: String): Flow<String?> = override fun getPinProtectedUserKeyFlow(userId: String): Flow<String?> =
getMutablePinProtectedUserKeyFlow(userId) getMutablePinProtectedUserKeyFlow(userId)
.onSubscription { emit(getPinProtectedUserKey(userId = userId)) } .onSubscription { emit(getPinProtectedUserKey(userId = userId)) }
override fun getPinProtectedUserKeyEnvelopeFlow(userId: String): Flow<String?> =
getMutablePinProtectedUserKeyEnvelopeFlow(userId)
.onSubscription { emit(getPinProtectedUserKeyEnvelope(userId = userId)) }
override fun getTwoFactorToken(email: String): String? = override fun getTwoFactorToken(email: String): String? =
getString(key = TWO_FACTOR_TOKEN_KEY.appendIdentifier(email)) getString(key = TWO_FACTOR_TOKEN_KEY.appendIdentifier(email))
@ -579,6 +620,12 @@ class AuthDiskSourceImpl(
bufferedMutableSharedFlow(replay = 1) bufferedMutableSharedFlow(replay = 1)
} }
private fun getMutablePinProtectedUserKeyEnvelopeFlow(
userId: String,
): MutableSharedFlow<String?> = mutablePinProtectedUserKeyEnvelopeFlowMap.getOrPut(userId) {
bufferedMutableSharedFlow(replay = 1)
}
private fun migrateAccountTokens() { private fun migrateAccountTokens() {
userState userState
?.accounts ?.accounts

View File

@ -85,7 +85,8 @@ class UserLogoutManagerImpl(
// Save any data that will still need to be retained after otherwise clearing all dat // Save any data that will still need to be retained after otherwise clearing all dat
val vaultTimeoutInMinutes = settingsDiskSource.getVaultTimeoutInMinutes(userId = userId) val vaultTimeoutInMinutes = settingsDiskSource.getVaultTimeoutInMinutes(userId = userId)
val vaultTimeoutAction = settingsDiskSource.getVaultTimeoutAction(userId = userId) val vaultTimeoutAction = settingsDiskSource.getVaultTimeoutAction(userId = userId)
val pinProtectedUserKey = authDiskSource.getPinProtectedUserKey(userId = userId) val pinProtectedUserKeyEnvelope = authDiskSource
.getPinProtectedUserKeyEnvelope(userId = userId)
switchUserIfAvailable( switchUserIfAvailable(
currentUserId = userId, currentUserId = userId,
@ -107,9 +108,9 @@ class UserLogoutManagerImpl(
vaultTimeoutAction = vaultTimeoutAction, vaultTimeoutAction = vaultTimeoutAction,
) )
} }
authDiskSource.storePinProtectedUserKey( authDiskSource.storePinProtectedUserKeyEnvelope(
userId = userId, userId = userId,
pinProtectedUserKey = pinProtectedUserKey, pinProtectedUserKeyEnvelope = pinProtectedUserKeyEnvelope,
) )
} }

View File

@ -156,7 +156,7 @@ class UserStateManagerImpl(
private fun getVaultUnlockType( private fun getVaultUnlockType(
userId: String, userId: String,
): VaultUnlockType = authDiskSource ): VaultUnlockType = authDiskSource
.getPinProtectedUserKey(userId = userId) .getPinProtectedUserKeyEnvelope(userId = userId)
?.let { VaultUnlockType.PIN } ?.let { VaultUnlockType.PIN }
?: VaultUnlockType.MASTER_PASSWORD ?: VaultUnlockType.MASTER_PASSWORD
} }

View File

@ -1295,8 +1295,8 @@ class AuthRepositoryImpl(
?.activeAccount ?.activeAccount
?.profile ?.profile
?: return ValidatePinResult.Error(error = NoActiveUserException()) ?: return ValidatePinResult.Error(error = NoActiveUserException())
val pinProtectedUserKey = authDiskSource val pinProtectedUserKeyEnvelope = authDiskSource
.getPinProtectedUserKey(userId = activeAccount.userId) .getPinProtectedUserKeyEnvelope(userId = activeAccount.userId)
?: return ValidatePinResult.Error( ?: return ValidatePinResult.Error(
error = MissingPropertyException("Pin Protected User Key"), error = MissingPropertyException("Pin Protected User Key"),
) )
@ -1304,7 +1304,7 @@ class AuthRepositoryImpl(
.validatePin( .validatePin(
userId = activeAccount.userId, userId = activeAccount.userId,
pin = pin, pin = pin,
pinProtectedUserKey = pinProtectedUserKey, pinProtectedUserKey = pinProtectedUserKeyEnvelope,
) )
.fold( .fold(
onSuccess = { ValidatePinResult.Success(isValid = it) }, onSuccess = { ValidatePinResult.Success(isValid = it) },

View File

@ -29,6 +29,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
@ -293,7 +294,11 @@ class SettingsRepositoryImpl(
?.let { userId -> ?.let { userId ->
authDiskSource authDiskSource
.getPinProtectedUserKeyFlow(userId) .getPinProtectedUserKeyFlow(userId)
.map { it != null } .combine(
authDiskSource.getPinProtectedUserKeyEnvelopeFlow(userId),
) { pinProtectedUserKey, pinProtectedUserKeyEnvelope ->
pinProtectedUserKey != null || pinProtectedUserKeyEnvelope != null
}
} }
?: flowOf(false) ?: flowOf(false)
@ -403,7 +408,7 @@ class SettingsRepositoryImpl(
?.userDecryptionOptions ?.userDecryptionOptions
?.hasMasterPassword != false ?.hasMasterPassword != false
val timeoutAction = settingsDiskSource.getVaultTimeoutAction(userId = userId) val timeoutAction = settingsDiskSource.getVaultTimeoutAction(userId = userId)
val hasPin = authDiskSource.getPinProtectedUserKey(userId = userId) != null val hasPin = authDiskSource.getPinProtectedUserKeyEnvelope(userId = userId) != null
val hasBiometrics = authDiskSource.getUserBiometricUnlockKey(userId = userId) != null val hasBiometrics = authDiskSource.getUserBiometricUnlockKey(userId = userId) != null
// The timeout action cannot be "lock" if you do not have master password, pin, or // The timeout action cannot be "lock" if you do not have master password, pin, or
// biometrics unlock enabled. // biometrics unlock enabled.
@ -527,21 +532,27 @@ class SettingsRepositoryImpl(
val userId = activeUserId ?: return val userId = activeUserId ?: return
unconfinedScope.launch { unconfinedScope.launch {
vaultSdkSource vaultSdkSource
.derivePinKey( .enrollPin(
userId = userId, userId = userId,
pin = pin, pin = pin,
) )
.fold( .fold(
onSuccess = { derivePinKeyResponse -> onSuccess = { enrollPinResponse ->
authDiskSource.apply { authDiskSource.apply {
storeEncryptedPin( storeEncryptedPin(
userId = userId, userId = userId,
encryptedPin = derivePinKeyResponse.encryptedPin, encryptedPin = enrollPinResponse.userKeyEncryptedPin,
) )
storePinProtectedUserKeyEnvelope(
userId = userId,
pinProtectedUserKeyEnvelope =
enrollPinResponse.pinProtectedUserKeyEnvelope,
inMemoryOnly = shouldRequireMasterPasswordOnRestart,
)
// Remove any legacy pin protected user keys.
storePinProtectedUserKey( storePinProtectedUserKey(
userId = userId, userId = userId,
pinProtectedUserKey = derivePinKeyResponse.pinProtectedUserKey, pinProtectedUserKey = null,
inMemoryOnly = shouldRequireMasterPasswordOnRestart,
) )
} }
}, },
@ -561,6 +572,10 @@ class SettingsRepositoryImpl(
userId = userId, userId = userId,
encryptedPin = null, encryptedPin = null,
) )
authDiskSource.storePinProtectedUserKeyEnvelope(
userId = userId,
pinProtectedUserKeyEnvelope = null,
)
authDiskSource.storePinProtectedUserKey( authDiskSource.storePinProtectedUserKey(
userId = userId, userId = userId,
pinProtectedUserKey = null, pinProtectedUserKey = null,

View File

@ -2,7 +2,7 @@ package com.x8bit.bitwarden.data.vault.datasource.sdk
import com.bitwarden.collections.Collection import com.bitwarden.collections.Collection
import com.bitwarden.collections.CollectionView import com.bitwarden.collections.CollectionView
import com.bitwarden.core.DerivePinKeyResponse import com.bitwarden.core.EnrollPinResponse
import com.bitwarden.core.InitOrgCryptoRequest import com.bitwarden.core.InitOrgCryptoRequest
import com.bitwarden.core.InitUserCryptoMethod import com.bitwarden.core.InitUserCryptoMethod
import com.bitwarden.core.InitUserCryptoRequest import com.bitwarden.core.InitUserCryptoRequest
@ -75,30 +75,26 @@ interface VaultSdkSource {
): Result<DeriveKeyConnectorResult> ): Result<DeriveKeyConnectorResult>
/** /**
* Derives a "pin key" from the given [pin] for the given [userId]. This can be used to later * Protects the current user key with the provided PIN. This can be used to later unlock
* unlock their vault via a call to [initializeCrypto] with [InitUserCryptoMethod.Pin]. * their vault via a call to [initializeCrypto] with [InitUserCryptoMethod.PinEnvelope].
* *
* This should only be called after a successful call to [initializeCrypto] for the associated * This should only be called after a successful call to [initializeCrypto] for the associated
* user. * user.
*/ */
suspend fun derivePinKey( suspend fun enrollPin(
userId: String, userId: String,
pin: String, pin: String,
): Result<DerivePinKeyResponse> ): Result<EnrollPinResponse>
/** /**
* Derives a pin-protected user key from the given [encryptedPin] for the given [userId]. This * Protects the current user key with the provided PIN. The result can be stored and later
* value must be derived from a previous call to [derivePinKey] with a plaintext PIN. This can * used to initialize another client instance by using the PIN and the PIN key with
* be used to later unlock their vault via a call to [initializeCrypto] with * [initializeCrypto]. The provided pin is encrypted with the user key.
* [InitUserCryptoMethod.Pin].
*
* This should only be called after a successful call to [initializeCrypto] for the associated
* user.
*/ */
suspend fun derivePinProtectedUserKey( suspend fun enrollPinWithEncryptedPin(
userId: String, userId: String,
encryptedPin: String, encryptedPin: String,
): Result<String> ): Result<EnrollPinResponse>
/** /**
* Validate the user pin using the [pinProtectedUserKey]. * Validate the user pin using the [pinProtectedUserKey].

View File

@ -4,7 +4,7 @@ import com.bitwarden.collections.Collection
import com.bitwarden.collections.CollectionView import com.bitwarden.collections.CollectionView
import com.bitwarden.core.DeriveKeyConnectorException import com.bitwarden.core.DeriveKeyConnectorException
import com.bitwarden.core.DeriveKeyConnectorRequest import com.bitwarden.core.DeriveKeyConnectorRequest
import com.bitwarden.core.DerivePinKeyResponse import com.bitwarden.core.EnrollPinResponse
import com.bitwarden.core.InitOrgCryptoRequest import com.bitwarden.core.InitOrgCryptoRequest
import com.bitwarden.core.InitUserCryptoRequest import com.bitwarden.core.InitUserCryptoRequest
import com.bitwarden.core.UpdateKdfResponse import com.bitwarden.core.UpdateKdfResponse
@ -109,24 +109,24 @@ class VaultSdkSourceImpl(
} }
} }
override suspend fun derivePinKey( override suspend fun enrollPin(
userId: String, userId: String,
pin: String, pin: String,
): Result<DerivePinKeyResponse> = ): Result<EnrollPinResponse> =
runCatchingWithLogs { runCatchingWithLogs {
getClient(userId = userId) getClient(userId = userId)
.crypto() .crypto()
.derivePinKey(pin = pin) .enrollPin(pin = pin)
} }
override suspend fun derivePinProtectedUserKey( override suspend fun enrollPinWithEncryptedPin(
userId: String, userId: String,
encryptedPin: String, encryptedPin: String,
): Result<String> = ): Result<EnrollPinResponse> =
runCatchingWithLogs { runCatchingWithLogs {
getClient(userId = userId) getClient(userId = userId)
.crypto() .crypto()
.derivePinUserKey(encryptedPin = encryptedPin) .enrollPinWithEncryptedPin(encryptedPin = encryptedPin)
} }
override suspend fun validatePin( override suspend fun validatePin(

View File

@ -239,6 +239,7 @@ class VaultLockManagerImpl(
trustedDeviceManager trustedDeviceManager
.trustThisDeviceIfNecessary(userId = userId) .trustThisDeviceIfNecessary(userId = userId)
updateKdfIfNeeded(initUserCryptoMethod) updateKdfIfNeeded(initUserCryptoMethod)
migratePinProtectedUserKeyIfNeeded(userId = userId)
setVaultToUnlocked(userId = userId) setVaultToUnlocked(userId = userId)
} else { } else {
incrementInvalidUnlockCount(userId = userId) incrementInvalidUnlockCount(userId = userId)
@ -275,6 +276,39 @@ class VaultLockManagerImpl(
) )
} }
/**
* Migrates the PIN-protected user key for the given user if needed.
*
* If an encrypted PIN exists and no PIN-protected user key envelope is present,
* enrolls the PIN with the encrypted PIN and stores the resulting envelope.
* Optionally marks the envelope as in-memory only if the PIN-protected user key is not present.
*
* @param userId The ID of the user for whom to migrate the PIN-protected user key.
*/
private suspend fun migratePinProtectedUserKeyIfNeeded(userId: String) {
val encryptedPin = authDiskSource.getEncryptedPin(userId) ?: return
if (authDiskSource.getPinProtectedUserKeyEnvelope(userId) != null) return
val inMemoryOnly = authDiskSource.getPinProtectedUserKey(userId) == null
vaultSdkSource.enrollPinWithEncryptedPin(userId, encryptedPin)
.onSuccess { enrollPinResponse ->
authDiskSource.storeEncryptedPin(
userId = userId,
encryptedPin = enrollPinResponse.userKeyEncryptedPin,
)
authDiskSource.storePinProtectedUserKeyEnvelope(
userId = userId,
pinProtectedUserKeyEnvelope = enrollPinResponse.pinProtectedUserKeyEnvelope,
inMemoryOnly = inMemoryOnly,
)
authDiskSource.storePinProtectedUserKey(
userId = userId,
pinProtectedUserKey = null,
inMemoryOnly = inMemoryOnly,
)
}
}
/** /**
* Increments the stored invalid unlock count for the given [userId] and automatically logs out * Increments the stored invalid unlock count for the given [userId] and automatically logs out
* if this new value is greater than [MAXIMUM_INVALID_UNLOCK_ATTEMPTS]. * if this new value is greater than [MAXIMUM_INVALID_UNLOCK_ATTEMPTS].

View File

@ -360,17 +360,34 @@ class VaultRepositoryImpl(
): VaultUnlockResult { ): VaultUnlockResult {
val userId = activeUserId val userId = activeUserId
?: return VaultUnlockResult.InvalidStateError(error = NoActiveUserException()) ?: return VaultUnlockResult.InvalidStateError(error = NoActiveUserException())
val pinProtectedUserKey = authDiskSource.getPinProtectedUserKey(userId = userId)
?: return VaultUnlockResult.InvalidStateError( return authDiskSource.getPinProtectedUserKeyEnvelope(userId = userId)
error = MissingPropertyException("Pin protected key"), ?.let { pinProtectedUserKeyEnvelope ->
) this.unlockVaultForUser(
return this.unlockVaultForUser( userId = userId,
userId = userId, initUserCryptoMethod = InitUserCryptoMethod.PinEnvelope(
initUserCryptoMethod = InitUserCryptoMethod.Pin( pin = pin,
pin = pin, pinProtectedUserKeyEnvelope = pinProtectedUserKeyEnvelope,
pinProtectedUserKey = pinProtectedUserKey, ),
), )
) }
?: run {
// This is needed to support unlocking with a legacy pin protected user key.
// Once the vault is unlocked, the user's pin protected user key is migrated to
// a pin protected user key envelope.
val pinProtectedUserKey = authDiskSource.getPinProtectedUserKey(userId = userId)
?: return VaultUnlockResult.InvalidStateError(
error = MissingPropertyException("Pin protected key"),
)
this.unlockVaultForUser(
userId = userId,
initUserCryptoMethod = InitUserCryptoMethod.Pin(
pin = pin,
pinProtectedUserKey = pinProtectedUserKey,
),
)
}
} }
override suspend fun generateTotp( override suspend fun generateTotp(
@ -502,17 +519,29 @@ class VaultRepositoryImpl(
*/ */
private suspend fun deriveTemporaryPinProtectedUserKeyIfNecessary(userId: String) { private suspend fun deriveTemporaryPinProtectedUserKeyIfNecessary(userId: String) {
val encryptedPin = authDiskSource.getEncryptedPin(userId = userId) ?: return val encryptedPin = authDiskSource.getEncryptedPin(userId = userId) ?: return
val existingPinProtectedUserKey = authDiskSource.getPinProtectedUserKey(userId = userId) val existingPinProtectedUserKeyEnvelope = authDiskSource
if (existingPinProtectedUserKey != null) return .getPinProtectedUserKeyEnvelope(
userId = userId,
)
if (existingPinProtectedUserKeyEnvelope != null) return
vaultSdkSource vaultSdkSource
.derivePinProtectedUserKey( .enrollPinWithEncryptedPin(
userId = userId, userId = userId,
encryptedPin = encryptedPin, encryptedPin = encryptedPin,
) )
.onSuccess { pinProtectedUserKey -> .onSuccess { enrollPinResponse ->
authDiskSource.storeEncryptedPin(
userId = userId,
encryptedPin = enrollPinResponse.userKeyEncryptedPin,
)
authDiskSource.storePinProtectedUserKeyEnvelope(
userId = userId,
pinProtectedUserKeyEnvelope = enrollPinResponse.pinProtectedUserKeyEnvelope,
inMemoryOnly = true,
)
authDiskSource.storePinProtectedUserKey( authDiskSource.storePinProtectedUserKey(
userId = userId, userId = userId,
pinProtectedUserKey = pinProtectedUserKey, pinProtectedUserKey = null,
inMemoryOnly = true, inMemoryOnly = true,
) )
} }

View File

@ -331,6 +331,7 @@ class AuthDiskSourceTest {
assertNull(authDiskSource.getUserBiometricInitVector(userId = userId)) assertNull(authDiskSource.getUserBiometricInitVector(userId = userId))
assertNull(authDiskSource.getUserBiometricUnlockKey(userId = userId)) assertNull(authDiskSource.getUserBiometricUnlockKey(userId = userId))
assertNull(authDiskSource.getPinProtectedUserKey(userId = userId)) assertNull(authDiskSource.getPinProtectedUserKey(userId = userId))
assertNull(authDiskSource.getPinProtectedUserKeyEnvelope(userId = userId))
assertNull(authDiskSource.getInvalidUnlockAttempts(userId = userId)) assertNull(authDiskSource.getInvalidUnlockAttempts(userId = userId))
assertNull(authDiskSource.getUserKey(userId = userId)) assertNull(authDiskSource.getUserKey(userId = userId))
assertNull(authDiskSource.getUserAutoUnlockKey(userId = userId)) assertNull(authDiskSource.getUserAutoUnlockKey(userId = userId))
@ -825,6 +826,63 @@ class AuthDiskSourceTest {
) )
} }
@Test
@Suppress("MaxLineLength")
fun `storePinProtectedUserKeyEnvelope should update result flow from getPinProtectedUserKeyEnvelopeFlow`() =
runTest {
val topSecretKey = "topsecret"
val mockUserId = "mockUserId"
authDiskSource.getPinProtectedUserKeyEnvelopeFlow(mockUserId).test {
assertNull(awaitItem())
authDiskSource.storePinProtectedUserKeyEnvelope(
userId = mockUserId,
pinProtectedUserKeyEnvelope = topSecretKey,
)
assertEquals(topSecretKey, awaitItem())
}
}
@Test
fun `getPinProtectedUserKeyEnvelope should pull from SharedPreferences`() {
val pinProtectedUserKeyEnvelopeBaseKey =
"bwPreferencesStorage:pinKeyEncryptedUserKeyEnvelope"
val mockUserId = "mockUserId"
val mockPinProtectedUserKeyEnvelope = "mockPinProtectedUserKeyEnvelope"
fakeSharedPreferences
.edit {
putString(
"${pinProtectedUserKeyEnvelopeBaseKey}_$mockUserId",
mockPinProtectedUserKeyEnvelope,
)
}
val actual = authDiskSource.getPinProtectedUserKeyEnvelope(userId = mockUserId)
assertEquals(
mockPinProtectedUserKeyEnvelope,
actual,
)
}
@Test
fun `storePinProtectedUserKeyEnvelope should pull from SharedPreferences`() {
val pinProtectedUserKeyEnvelopeBaseKey =
"bwPreferencesStorage:pinKeyEncryptedUserKeyEnvelope"
val mockUserId = "mockUserId"
val mockPinProtectedUserKeyEnvelope = "mockPinProtectedUserKeyEnvelope"
authDiskSource.storePinProtectedUserKeyEnvelope(
userId = mockUserId,
pinProtectedUserKeyEnvelope = mockPinProtectedUserKeyEnvelope,
)
val actual = fakeSharedPreferences
.getString(
"${pinProtectedUserKeyEnvelopeBaseKey}_$mockUserId",
null,
)
assertEquals(
mockPinProtectedUserKeyEnvelope,
actual,
)
}
@Test @Test
fun `storePinProtectedUserKey should update result flow from getPinProtectedUserKeyFlow`() = fun `storePinProtectedUserKey should update result flow from getPinProtectedUserKeyFlow`() =
runTest { runTest {

View File

@ -65,6 +65,9 @@ class FakeAuthDiskSource : AuthDiskSource {
private val storedShowImportLogins = mutableMapOf<String, Boolean?>() private val storedShowImportLogins = mutableMapOf<String, Boolean?>()
private val storedLastLockTimestampState = mutableMapOf<String, Instant?>() private val storedLastLockTimestampState = mutableMapOf<String, Instant?>()
private val storedAccountKeys = mutableMapOf<String, AccountKeysJson?>() private val storedAccountKeys = mutableMapOf<String, AccountKeysJson?>()
private val storedPinProtectedUserKeyEnvelopes = mutableMapOf<String, Pair<String?, Boolean>>()
private val mutablePinProtectedUserKeyEnvelopesFlowMap =
mutableMapOf<String, MutableSharedFlow<String?>>()
override var userState: UserStateJson? = null override var userState: UserStateJson? = null
set(value) { set(value) {
@ -89,11 +92,19 @@ class FakeAuthDiskSource : AuthDiskSource {
storedBiometricInitVectors.remove(userId) storedBiometricInitVectors.remove(userId)
storedBiometricKeys.remove(userId) storedBiometricKeys.remove(userId)
storedOrganizationKeys.remove(userId) storedOrganizationKeys.remove(userId)
storedPinProtectedUserKeyEnvelopes.remove(userId)
mutableShouldUseKeyConnectorFlowMap.remove(userId) mutableShouldUseKeyConnectorFlowMap.remove(userId)
mutableOrganizationsFlowMap.remove(userId) mutableOrganizationsFlowMap.remove(userId)
mutablePoliciesFlowMap.remove(userId) mutablePoliciesFlowMap.remove(userId)
mutableAccountTokensFlowMap.remove(userId) mutableAccountTokensFlowMap.remove(userId)
mutablePinProtectedUserKeyEnvelopesFlowMap.remove(userId)
}
private fun getMutablePinProtectedUserKeyEnvelopeFlow(
userId: String,
): MutableSharedFlow<String?> = mutablePinProtectedUserKeyEnvelopesFlowMap.getOrPut(userId) {
bufferedMutableSharedFlow(replay = 1)
} }
override fun getShouldUseKeyConnectorFlow( override fun getShouldUseKeyConnectorFlow(
@ -170,9 +181,17 @@ class FakeAuthDiskSource : AuthDiskSource {
storedUserAutoUnlockKeys[userId] = userAutoUnlockKey storedUserAutoUnlockKeys[userId] = userAutoUnlockKey
} }
@Deprecated(
"Use getPinProtectedUserKeyEnvelope instead.",
replaceWith = ReplaceWith("getPinProtectedUserKeyEnvelope"),
)
override fun getPinProtectedUserKey(userId: String): String? = override fun getPinProtectedUserKey(userId: String): String? =
storedPinProtectedUserKeys[userId]?.first storedPinProtectedUserKeys[userId]?.first
@Deprecated(
"Use storePinProtectedUserKeyEnvelope instead.",
replaceWith = ReplaceWith("storePinProtectedUserKeyEnvelope"),
)
override fun storePinProtectedUserKey( override fun storePinProtectedUserKey(
userId: String, userId: String,
pinProtectedUserKey: String?, pinProtectedUserKey: String?,
@ -182,6 +201,10 @@ class FakeAuthDiskSource : AuthDiskSource {
getMutablePinProtectedUserKeyFlow(userId).tryEmit(pinProtectedUserKey) getMutablePinProtectedUserKeyFlow(userId).tryEmit(pinProtectedUserKey)
} }
@Deprecated(
"Use getPinProtectedUserKeyEnvelopeFlow instead.",
replaceWith = ReplaceWith("getPinProtectedUserKeyEnvelopeFlow"),
)
override fun getPinProtectedUserKeyFlow(userId: String): Flow<String?> = override fun getPinProtectedUserKeyFlow(userId: String): Flow<String?> =
getMutablePinProtectedUserKeyFlow(userId) getMutablePinProtectedUserKeyFlow(userId)
.onSubscription { .onSubscription {
@ -331,6 +354,24 @@ class FakeAuthDiskSource : AuthDiskSource {
storedLastLockTimestampState[userId] = lastLockTimestamp storedLastLockTimestampState[userId] = lastLockTimestamp
} }
override fun getPinProtectedUserKeyEnvelope(userId: String): String? =
storedPinProtectedUserKeyEnvelopes[userId]?.first
override fun storePinProtectedUserKeyEnvelope(
userId: String,
pinProtectedUserKeyEnvelope: String?,
inMemoryOnly: Boolean,
) {
storedPinProtectedUserKeyEnvelopes[userId] = pinProtectedUserKeyEnvelope to inMemoryOnly
getMutablePinProtectedUserKeyEnvelopeFlow(userId).tryEmit(pinProtectedUserKeyEnvelope)
}
override fun getPinProtectedUserKeyEnvelopeFlow(userId: String): Flow<String?> =
getMutablePinProtectedUserKeyEnvelopeFlow(userId)
.onSubscription {
emit(getPinProtectedUserKeyEnvelope(userId))
}
/** /**
* Assert the the [isTdeLoginComplete] was stored successfully using the [userId]. * Assert the the [isTdeLoginComplete] was stored successfully using the [userId].
*/ */
@ -427,6 +468,20 @@ class FakeAuthDiskSource : AuthDiskSource {
assertEquals(pinProtectedUserKey to inMemoryOnly, storedPinProtectedUserKeys[userId]) assertEquals(pinProtectedUserKey to inMemoryOnly, storedPinProtectedUserKeys[userId])
} }
/**
* Assert that the [pinProtectedUserKeyEnvelope] was stored successfully using the [userId].
*/
fun assertPinProtectedUserKeyEnvelope(
userId: String,
pinProtectedUserKeyEnvelope: String?,
inMemoryOnly: Boolean = false,
) {
assertEquals(
pinProtectedUserKeyEnvelope to inMemoryOnly,
storedPinProtectedUserKeyEnvelopes[userId],
)
}
/** /**
* Assert the the [organizationKeys] was stored successfully using the [userId]. * Assert the the [organizationKeys] was stored successfully using the [userId].
*/ */

View File

@ -34,7 +34,12 @@ import java.time.ZonedDateTime
class UserLogoutManagerTest { class UserLogoutManagerTest {
private val authDiskSource: AuthDiskSource = mockk { private val authDiskSource: AuthDiskSource = mockk {
every { storeAccountTokens(userId = any(), accountTokens = null) } just runs every { storeAccountTokens(userId = any(), accountTokens = null) } just runs
every { storePinProtectedUserKey(userId = any(), pinProtectedUserKey = any()) } just runs every {
storePinProtectedUserKeyEnvelope(
userId = any(),
pinProtectedUserKeyEnvelope = any(),
)
} just runs
every { userState = any() } just runs every { userState = any() } just runs
every { clearData(any()) } just runs every { clearData(any()) } just runs
} }
@ -137,8 +142,9 @@ class UserLogoutManagerTest {
every { every {
settingsDiskSource.getVaultTimeoutAction(userId = userId) settingsDiskSource.getVaultTimeoutAction(userId = userId)
} returns vaultTimeoutAction } returns vaultTimeoutAction
every { every {
authDiskSource.getPinProtectedUserKey(userId = userId) authDiskSource.getPinProtectedUserKeyEnvelope(userId = userId)
} returns pinProtectedUserKey } returns pinProtectedUserKey
userLogoutManager.softLogout(userId = userId, reason = LogoutReason.Timeout) userLogoutManager.softLogout(userId = userId, reason = LogoutReason.Timeout)
@ -164,9 +170,9 @@ class UserLogoutManagerTest {
userId = userId, userId = userId,
vaultTimeoutAction = vaultTimeoutAction, vaultTimeoutAction = vaultTimeoutAction,
) )
authDiskSource.storePinProtectedUserKey( authDiskSource.storePinProtectedUserKeyEnvelope(
userId = userId, userId = userId,
pinProtectedUserKey = pinProtectedUserKey, pinProtectedUserKeyEnvelope = pinProtectedUserKey,
) )
} }
} }
@ -186,7 +192,7 @@ class UserLogoutManagerTest {
settingsDiskSource.getVaultTimeoutAction(userId = userId) settingsDiskSource.getVaultTimeoutAction(userId = userId)
} returns vaultTimeoutAction } returns vaultTimeoutAction
every { every {
authDiskSource.getPinProtectedUserKey(userId = userId) authDiskSource.getPinProtectedUserKeyEnvelope(userId = userId)
} returns pinProtectedUserKey } returns pinProtectedUserKey
userLogoutManager.softLogout(userId = userId, reason = LogoutReason.Timeout) userLogoutManager.softLogout(userId = userId, reason = LogoutReason.Timeout)
@ -206,9 +212,9 @@ class UserLogoutManagerTest {
userId = userId, userId = userId,
vaultTimeoutAction = vaultTimeoutAction, vaultTimeoutAction = vaultTimeoutAction,
) )
authDiskSource.storePinProtectedUserKey( authDiskSource.storePinProtectedUserKeyEnvelope(
userId = userId, userId = userId,
pinProtectedUserKey = pinProtectedUserKey, pinProtectedUserKeyEnvelope = pinProtectedUserKey,
) )
} }
} }
@ -222,15 +228,15 @@ class UserLogoutManagerTest {
val pinProtectedUserKey = "pinProtectedUserKey" val pinProtectedUserKey = "pinProtectedUserKey"
every { authDiskSource.userState } returns MULTI_USER_STATE every { authDiskSource.userState } returns MULTI_USER_STATE
every {
authDiskSource.getPinProtectedUserKeyEnvelope(userId)
} returns pinProtectedUserKey
every { every {
settingsDiskSource.getVaultTimeoutInMinutes(userId = userId) settingsDiskSource.getVaultTimeoutInMinutes(userId = userId)
} returns vaultTimeoutInMinutes } returns vaultTimeoutInMinutes
every { every {
settingsDiskSource.getVaultTimeoutAction(userId = userId) settingsDiskSource.getVaultTimeoutAction(userId = userId)
} returns vaultTimeoutAction } returns vaultTimeoutAction
every {
authDiskSource.getPinProtectedUserKey(userId = userId)
} returns pinProtectedUserKey
userLogoutManager.softLogout(userId = userId, reason = LogoutReason.SecurityStamp) userLogoutManager.softLogout(userId = userId, reason = LogoutReason.SecurityStamp)
@ -249,9 +255,9 @@ class UserLogoutManagerTest {
userId = userId, userId = userId,
vaultTimeoutAction = vaultTimeoutAction, vaultTimeoutAction = vaultTimeoutAction,
) )
authDiskSource.storePinProtectedUserKey( authDiskSource.storePinProtectedUserKeyEnvelope(
userId = userId, userId = userId,
pinProtectedUserKey = pinProtectedUserKey, pinProtectedUserKeyEnvelope = pinProtectedUserKey,
) )
} }
} }

View File

@ -92,13 +92,13 @@ class UserStateManagerTest {
) )
fakeAuthDiskSource.apply { fakeAuthDiskSource.apply {
storePinProtectedUserKey( storePinProtectedUserKeyEnvelope(
userId = USER_ID_1, userId = USER_ID_1,
pinProtectedUserKey = "pinProtectedUseKey", pinProtectedUserKeyEnvelope = "pinProtectedUseKey",
) )
storePinProtectedUserKey( storePinProtectedUserKeyEnvelope(
userId = USER_ID_2, userId = USER_ID_2,
pinProtectedUserKey = "pinProtectedUseKey", pinProtectedUserKeyEnvelope = "pinProtectedUseKey",
) )
userState = MULTI_USER_STATE userState = MULTI_USER_STATE
} }
@ -137,13 +137,13 @@ class UserStateManagerTest {
) )
fakeAuthDiskSource.apply { fakeAuthDiskSource.apply {
storePinProtectedUserKey( storePinProtectedUserKeyEnvelope(
userId = USER_ID_1, userId = USER_ID_1,
pinProtectedUserKey = null, pinProtectedUserKeyEnvelope = null,
) )
storePinProtectedUserKey( storePinProtectedUserKeyEnvelope(
userId = USER_ID_2, userId = USER_ID_2,
pinProtectedUserKey = null, pinProtectedUserKeyEnvelope = null,
) )
storeOrganizations( storeOrganizations(
userId = USER_ID_1, userId = USER_ID_1,

View File

@ -6353,9 +6353,9 @@ class AuthRepositoryTest {
val pinProtectedUserKey = "pinProtectedUserKey" val pinProtectedUserKey = "pinProtectedUserKey"
val error = Throwable("Fail!") val error = Throwable("Fail!")
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1 fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
fakeAuthDiskSource.storePinProtectedUserKey( fakeAuthDiskSource.storePinProtectedUserKeyEnvelope(
userId = SINGLE_USER_STATE_1.activeUserId, userId = SINGLE_USER_STATE_1.activeUserId,
pinProtectedUserKey = pinProtectedUserKey, pinProtectedUserKeyEnvelope = pinProtectedUserKey,
) )
coEvery { coEvery {
vaultSdkSource.validatePin( vaultSdkSource.validatePin(
@ -6387,9 +6387,9 @@ class AuthRepositoryTest {
val pin = "PIN" val pin = "PIN"
val pinProtectedUserKey = "pinProtectedUserKey" val pinProtectedUserKey = "pinProtectedUserKey"
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1 fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
fakeAuthDiskSource.storePinProtectedUserKey( fakeAuthDiskSource.storePinProtectedUserKeyEnvelope(
userId = SINGLE_USER_STATE_1.activeUserId, userId = SINGLE_USER_STATE_1.activeUserId,
pinProtectedUserKey = pinProtectedUserKey, pinProtectedUserKeyEnvelope = pinProtectedUserKey,
) )
coEvery { coEvery {
vaultSdkSource.validatePin( vaultSdkSource.validatePin(
@ -6421,9 +6421,9 @@ class AuthRepositoryTest {
val pin = "PIN" val pin = "PIN"
val pinProtectedUserKey = "pinProtectedUserKey" val pinProtectedUserKey = "pinProtectedUserKey"
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1 fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
fakeAuthDiskSource.storePinProtectedUserKey( fakeAuthDiskSource.storePinProtectedUserKeyEnvelope(
userId = SINGLE_USER_STATE_1.activeUserId, userId = SINGLE_USER_STATE_1.activeUserId,
pinProtectedUserKey = pinProtectedUserKey, pinProtectedUserKeyEnvelope = pinProtectedUserKey,
) )
coEvery { coEvery {
vaultSdkSource.validatePin( vaultSdkSource.validatePin(

View File

@ -3,7 +3,7 @@ package com.x8bit.bitwarden.data.platform.repository
import android.view.autofill.AutofillManager import android.view.autofill.AutofillManager
import app.cash.turbine.test import app.cash.turbine.test
import com.bitwarden.authenticatorbridge.util.generateSecretKey import com.bitwarden.authenticatorbridge.util.generateSecretKey
import com.bitwarden.core.DerivePinKeyResponse import com.bitwarden.core.EnrollPinResponse
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
import com.bitwarden.core.data.util.asFailure import com.bitwarden.core.data.util.asFailure
import com.bitwarden.core.data.util.asSuccess import com.bitwarden.core.data.util.asSuccess
@ -202,9 +202,9 @@ class SettingsRepositoryTest {
// Updating the Vault settings values and calling setDefaultsIfNecessary again has no // Updating the Vault settings values and calling setDefaultsIfNecessary again has no
// effect on the currently stored values since we have a way to unlock the vault. // effect on the currently stored values since we have a way to unlock the vault.
fakeAuthDiskSource.storePinProtectedUserKey( fakeAuthDiskSource.storePinProtectedUserKeyEnvelope(
userId = USER_ID, userId = USER_ID,
pinProtectedUserKey = "pinProtectedKey", pinProtectedUserKeyEnvelope = "pinProtectedKey",
) )
fakeSettingsDiskSource.apply { fakeSettingsDiskSource.apply {
storeVaultTimeoutInMinutes( storeVaultTimeoutInMinutes(
@ -924,19 +924,19 @@ class SettingsRepositoryTest {
@Test @Test
fun `storeUnlockPin when the master password on restart is required should only save an encrypted PIN to disk`() { fun `storeUnlockPin when the master password on restart is required should only save an encrypted PIN to disk`() {
val pin = "1234" val pin = "1234"
val encryptedPin = "encryptedPin" val userKeyEncryptedPin = "encryptedPin"
val pinProtectedUserKey = "pinProtectedUserKey" val pinProtectedUserKeyEnvelope = "pinProtectedUserKeyEnvelope"
val derivePinKeyResponse = DerivePinKeyResponse( val enrollResponse = EnrollPinResponse(
pinProtectedUserKey = pinProtectedUserKey, pinProtectedUserKeyEnvelope = pinProtectedUserKeyEnvelope,
encryptedPin = encryptedPin, userKeyEncryptedPin = userKeyEncryptedPin,
) )
fakeAuthDiskSource.userState = MOCK_USER_STATE fakeAuthDiskSource.userState = MOCK_USER_STATE
coEvery { coEvery {
vaultSdkSource.derivePinKey( vaultSdkSource.enrollPin(
userId = USER_ID, userId = USER_ID,
pin = pin, pin = pin,
) )
} returns derivePinKeyResponse.asSuccess() } returns enrollResponse.asSuccess()
settingsRepository.storeUnlockPin( settingsRepository.storeUnlockPin(
pin = pin, pin = pin,
@ -946,16 +946,16 @@ class SettingsRepositoryTest {
fakeAuthDiskSource.apply { fakeAuthDiskSource.apply {
assertEncryptedPin( assertEncryptedPin(
userId = USER_ID, userId = USER_ID,
encryptedPin = encryptedPin, encryptedPin = userKeyEncryptedPin,
) )
assertPinProtectedUserKey( assertPinProtectedUserKeyEnvelope(
userId = USER_ID, userId = USER_ID,
pinProtectedUserKey = pinProtectedUserKey, pinProtectedUserKeyEnvelope = pinProtectedUserKeyEnvelope,
inMemoryOnly = true, inMemoryOnly = true,
) )
} }
coVerify { coVerify {
vaultSdkSource.derivePinKey( vaultSdkSource.enrollPin(
userId = USER_ID, userId = USER_ID,
pin = pin, pin = pin,
) )
@ -966,19 +966,19 @@ class SettingsRepositoryTest {
@Test @Test
fun `storeUnlockPin when the master password on restart is not required should save all PIN data to disk`() { fun `storeUnlockPin when the master password on restart is not required should save all PIN data to disk`() {
val pin = "1234" val pin = "1234"
val encryptedPin = "encryptedPin" val userKeyEncryptedPin = "encryptedPin"
val pinProtectedUserKey = "pinProtectedUserKey" val pinProtectedUserKeyEnvelope = "pinProtectedUserKeyEnvelope"
val derivePinKeyResponse = DerivePinKeyResponse( val enrollResponse = EnrollPinResponse(
pinProtectedUserKey = pinProtectedUserKey, pinProtectedUserKeyEnvelope = pinProtectedUserKeyEnvelope,
encryptedPin = encryptedPin, userKeyEncryptedPin = userKeyEncryptedPin,
) )
fakeAuthDiskSource.userState = MOCK_USER_STATE fakeAuthDiskSource.userState = MOCK_USER_STATE
coEvery { coEvery {
vaultSdkSource.derivePinKey( vaultSdkSource.enrollPin(
userId = USER_ID, userId = USER_ID,
pin = pin, pin = pin,
) )
} returns derivePinKeyResponse.asSuccess() } returns enrollResponse.asSuccess()
settingsRepository.storeUnlockPin( settingsRepository.storeUnlockPin(
pin = pin, pin = pin,
@ -988,16 +988,16 @@ class SettingsRepositoryTest {
fakeAuthDiskSource.apply { fakeAuthDiskSource.apply {
assertEncryptedPin( assertEncryptedPin(
userId = USER_ID, userId = USER_ID,
encryptedPin = encryptedPin, encryptedPin = userKeyEncryptedPin,
) )
assertPinProtectedUserKey( assertPinProtectedUserKeyEnvelope(
userId = USER_ID, userId = USER_ID,
pinProtectedUserKey = pinProtectedUserKey, pinProtectedUserKeyEnvelope = pinProtectedUserKeyEnvelope,
inMemoryOnly = false, inMemoryOnly = false,
) )
} }
coVerify { coVerify {
vaultSdkSource.derivePinKey( vaultSdkSource.enrollPin(
userId = USER_ID, userId = USER_ID,
pin = pin, pin = pin,
) )
@ -1013,9 +1013,9 @@ class SettingsRepositoryTest {
userId = USER_ID, userId = USER_ID,
encryptedPin = "encryptedPin", encryptedPin = "encryptedPin",
) )
storePinProtectedUserKey( storePinProtectedUserKeyEnvelope(
userId = USER_ID, userId = USER_ID,
pinProtectedUserKey = "pinProtectedUserKey", pinProtectedUserKeyEnvelope = "pinProtectedUserKeyEnvelope",
) )
} }
@ -1026,9 +1026,9 @@ class SettingsRepositoryTest {
userId = USER_ID, userId = USER_ID,
encryptedPin = null, encryptedPin = null,
) )
assertPinProtectedUserKey( assertPinProtectedUserKeyEnvelope(
userId = USER_ID, userId = USER_ID,
pinProtectedUserKey = null, pinProtectedUserKeyEnvelope = null,
) )
} }
} }

View File

@ -4,7 +4,7 @@ import com.bitwarden.collections.Collection
import com.bitwarden.collections.CollectionView import com.bitwarden.collections.CollectionView
import com.bitwarden.core.DeriveKeyConnectorException import com.bitwarden.core.DeriveKeyConnectorException
import com.bitwarden.core.DeriveKeyConnectorRequest import com.bitwarden.core.DeriveKeyConnectorRequest
import com.bitwarden.core.DerivePinKeyResponse import com.bitwarden.core.EnrollPinResponse
import com.bitwarden.core.InitOrgCryptoRequest import com.bitwarden.core.InitOrgCryptoRequest
import com.bitwarden.core.InitUserCryptoRequest import com.bitwarden.core.InitUserCryptoRequest
import com.bitwarden.core.MasterPasswordAuthenticationData import com.bitwarden.core.MasterPasswordAuthenticationData
@ -339,14 +339,14 @@ class VaultSdkSourceTest {
} }
@Test @Test
fun `derivePinKey should call SDK and return a Result with the correct data`() = runBlocking { fun `enrollPin should call SDK and return a Result with the correct data`() = runBlocking {
val userId = "userId" val userId = "userId"
val pin = "pin" val pin = "pin"
val expectedResult = mockk<DerivePinKeyResponse>() val expectedResult = mockk<EnrollPinResponse>()
coEvery { coEvery {
clientCrypto.derivePinKey(pin = pin) clientCrypto.enrollPin(pin = pin)
} returns expectedResult } returns expectedResult
val result = vaultSdkSource.derivePinKey( val result = vaultSdkSource.enrollPin(
userId = userId, userId = userId,
pin = pin, pin = pin,
) )
@ -355,21 +355,21 @@ class VaultSdkSourceTest {
result, result,
) )
coVerify { coVerify {
clientCrypto.derivePinKey(pin) clientCrypto.enrollPin(pin)
} }
coVerify { sdkClientManager.getOrCreateClient(userId = userId) } coVerify { sdkClientManager.getOrCreateClient(userId = userId) }
} }
@Test @Test
fun `derivePinProtectedUserKey should call SDK and return a Result with the correct data`() = fun `enrollPinWithEncryptedPin should call SDK and return a Result with the correct data`() =
runBlocking { runBlocking {
val userId = "userId" val userId = "userId"
val encryptedPin = "encryptedPin" val encryptedPin = "encryptedPin"
val expectedResult = "pinProtectedUserKey" val expectedResult = mockk<EnrollPinResponse>()
coEvery { coEvery {
clientCrypto.derivePinUserKey(encryptedPin = encryptedPin) clientCrypto.enrollPinWithEncryptedPin(encryptedPin = encryptedPin)
} returns expectedResult } returns expectedResult
val result = vaultSdkSource.derivePinProtectedUserKey( val result = vaultSdkSource.enrollPinWithEncryptedPin(
userId = userId, userId = userId,
encryptedPin = encryptedPin, encryptedPin = encryptedPin,
) )
@ -378,7 +378,7 @@ class VaultSdkSourceTest {
result, result,
) )
coVerify { coVerify {
clientCrypto.derivePinUserKey(encryptedPin = encryptedPin) clientCrypto.enrollPinWithEncryptedPin(encryptedPin = encryptedPin)
} }
coVerify { sdkClientManager.getOrCreateClient(userId = userId) } coVerify { sdkClientManager.getOrCreateClient(userId = userId) }
} }

View File

@ -5,6 +5,7 @@ import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import app.cash.turbine.test import app.cash.turbine.test
import com.bitwarden.core.EnrollPinResponse
import com.bitwarden.core.InitOrgCryptoRequest import com.bitwarden.core.InitOrgCryptoRequest
import com.bitwarden.core.InitUserCryptoMethod import com.bitwarden.core.InitUserCryptoMethod
import com.bitwarden.core.InitUserCryptoRequest import com.bitwarden.core.InitUserCryptoRequest
@ -1635,6 +1636,134 @@ class VaultLockManagerTest {
} }
} }
@Suppress("MaxLineLength")
@Test
fun `unlockVault with initializeCrypto success should migrate pinProtectedUserKey`() =
runTest {
val kdf = MOCK_PROFILE.toSdkParams()
val email = MOCK_PROFILE.email
val masterPassword = "drowssap"
val userKey = "12345"
val privateKey = "54321"
val organizationKeys = mapOf("orgId1" to "orgKey1")
val userKeyEncryptedPin = "encryptedPin"
val pinProtectedUserKeyEnvelope = "pinProtectedUserKeyEnvelope"
val enrollResponse = EnrollPinResponse(
pinProtectedUserKeyEnvelope = pinProtectedUserKeyEnvelope,
userKeyEncryptedPin = userKeyEncryptedPin,
)
coEvery {
vaultSdkSource.initializeCrypto(
userId = USER_ID,
request = InitUserCryptoRequest(
userId = USER_ID,
kdfParams = kdf,
email = email,
privateKey = privateKey,
method = InitUserCryptoMethod.Password(
password = masterPassword,
userKey = userKey,
),
signingKey = null,
securityState = null,
),
)
} returns InitializeCryptoResult.Success.asSuccess()
coEvery {
vaultSdkSource.initializeOrganizationCrypto(
userId = USER_ID,
request = InitOrgCryptoRequest(organizationKeys = organizationKeys),
)
} returns InitializeCryptoResult.Success.asSuccess()
coEvery {
vaultSdkSource.enrollPinWithEncryptedPin(
userId = USER_ID,
encryptedPin = userKeyEncryptedPin,
)
} returns enrollResponse.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,
)
fakeAuthDiskSource.storeEncryptedPin(
userId = USER_ID,
encryptedPin = userKeyEncryptedPin,
)
fakeAuthDiskSource.storePinProtectedUserKey(
userId = USER_ID,
pinProtectedUserKey = userKeyEncryptedPin,
)
val result = vaultLockManager.unlockVault(
userId = USER_ID,
email = email,
kdf = kdf,
privateKey = privateKey,
signingKey = null,
securityState = null,
initUserCryptoMethod = InitUserCryptoMethod.Password(
password = masterPassword,
userKey = userKey,
),
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",
)
fakeAuthDiskSource.assertPinProtectedUserKeyEnvelope(
userId = USER_ID,
pinProtectedUserKeyEnvelope = pinProtectedUserKeyEnvelope,
inMemoryOnly = false,
)
coVerify(exactly = 1) {
vaultSdkSource.initializeCrypto(
userId = USER_ID,
request = InitUserCryptoRequest(
userId = USER_ID,
kdfParams = kdf,
email = email,
privateKey = privateKey,
method = InitUserCryptoMethod.Password(
password = masterPassword,
userKey = userKey,
),
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 * Resets the verification call count for the given [mock] while leaving all other mocked
* behavior in place. * behavior in place.

View File

@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.vault.repository
import app.cash.turbine.test import app.cash.turbine.test
import com.bitwarden.collections.CollectionView import com.bitwarden.collections.CollectionView
import com.bitwarden.core.DateTime import com.bitwarden.core.DateTime
import com.bitwarden.core.EnrollPinResponse
import com.bitwarden.core.InitUserCryptoMethod import com.bitwarden.core.InitUserCryptoMethod
import com.bitwarden.core.data.repository.model.DataState import com.bitwarden.core.data.repository.model.DataState
import com.bitwarden.core.data.util.asFailure import com.bitwarden.core.data.util.asFailure
@ -233,7 +234,7 @@ class VaultRepositoryTest {
) )
} }
coVerify(exactly = 0) { coVerify(exactly = 0) {
vaultSdkSource.derivePinProtectedUserKey(any(), any()) vaultSdkSource.enrollPinWithEncryptedPin(any(), any())
} }
fakeAuthDiskSource.apply { fakeAuthDiskSource.apply {
assertBiometricsKey( assertBiometricsKey(
@ -271,7 +272,7 @@ class VaultRepositoryTest {
result, result,
) )
coVerify(exactly = 0) { coVerify(exactly = 0) {
vaultSdkSource.derivePinProtectedUserKey(any(), any()) vaultSdkSource.enrollPinWithEncryptedPin(any(), any())
} }
} }
@ -298,7 +299,7 @@ class VaultRepositoryTest {
result, result,
) )
coVerify(exactly = 0) { coVerify(exactly = 0) {
vaultSdkSource.derivePinProtectedUserKey(any(), any()) vaultSdkSource.enrollPinWithEncryptedPin(any(), any())
} }
} }
@ -353,7 +354,7 @@ class VaultRepositoryTest {
) )
} }
coVerify(exactly = 0) { coVerify(exactly = 0) {
vaultSdkSource.derivePinProtectedUserKey(any(), any()) vaultSdkSource.enrollPinWithEncryptedPin(any(), any())
} }
} }
@ -364,7 +365,11 @@ class VaultRepositoryTest {
val userId = MOCK_USER_STATE.activeUserId val userId = MOCK_USER_STATE.activeUserId
val encryptedPin = "encryptedPin" val encryptedPin = "encryptedPin"
val privateKey = "mockPrivateKey-1" val privateKey = "mockPrivateKey-1"
val pinProtectedUserKey = "pinProtectedUserkey" val pinProtectedUserKeyEnvelope = "pinProtectedUserKeyEnvelope"
val enrollResponse = EnrollPinResponse(
pinProtectedUserKeyEnvelope = pinProtectedUserKeyEnvelope,
userKeyEncryptedPin = encryptedPin,
)
val biometricsKey = "asdf1234" val biometricsKey = "asdf1234"
fakeAuthDiskSource.userState = MOCK_USER_STATE fakeAuthDiskSource.userState = MOCK_USER_STATE
val encryptedBytes = byteArrayOf(1, 1) val encryptedBytes = byteArrayOf(1, 1)
@ -374,11 +379,11 @@ class VaultRepositoryTest {
every { iv } returns initVector every { iv } returns initVector
} }
coEvery { coEvery {
vaultSdkSource.derivePinProtectedUserKey( vaultSdkSource.enrollPinWithEncryptedPin(
userId = userId, userId = userId,
encryptedPin = encryptedPin, encryptedPin = encryptedPin,
) )
} returns pinProtectedUserKey.asSuccess() } returns enrollResponse.asSuccess()
coEvery { coEvery {
vaultLockManager.unlockVault( vaultLockManager.unlockVault(
userId = userId, userId = userId,
@ -403,6 +408,11 @@ class VaultRepositoryTest {
pinProtectedUserKey = null, pinProtectedUserKey = null,
inMemoryOnly = true, inMemoryOnly = true,
) )
storePinProtectedUserKeyEnvelope(
userId = userId,
pinProtectedUserKeyEnvelope = null,
inMemoryOnly = true,
)
} }
val result = vaultRepository.unlockVaultWithBiometrics(cipher = cipher) val result = vaultRepository.unlockVaultWithBiometrics(cipher = cipher)
@ -410,7 +420,12 @@ class VaultRepositoryTest {
assertEquals(VaultUnlockResult.Success, result) assertEquals(VaultUnlockResult.Success, result)
fakeAuthDiskSource.assertPinProtectedUserKey( fakeAuthDiskSource.assertPinProtectedUserKey(
userId = userId, userId = userId,
pinProtectedUserKey = pinProtectedUserKey, pinProtectedUserKey = null,
inMemoryOnly = true,
)
fakeAuthDiskSource.assertPinProtectedUserKeyEnvelope(
userId = userId,
pinProtectedUserKeyEnvelope = pinProtectedUserKeyEnvelope,
inMemoryOnly = true, inMemoryOnly = true,
) )
coVerify { coVerify {
@ -428,7 +443,7 @@ class VaultRepositoryTest {
) )
} }
coEvery { coEvery {
vaultSdkSource.derivePinProtectedUserKey( vaultSdkSource.enrollPinWithEncryptedPin(
userId = userId, userId = userId,
encryptedPin = encryptedPin, encryptedPin = encryptedPin,
) )
@ -603,9 +618,15 @@ class VaultRepositoryTest {
runTest { runTest {
val userId = "mockId-1" val userId = "mockId-1"
val mockVaultUnlockResult = VaultUnlockResult.Success val mockVaultUnlockResult = VaultUnlockResult.Success
val userKeyEncryptedPin = "encryptedPin"
val pinProtectedUserKeyEnvelope = "pinProtectedUserKeyEnvelope"
val enrollResponse = EnrollPinResponse(
pinProtectedUserKeyEnvelope = pinProtectedUserKeyEnvelope,
userKeyEncryptedPin = userKeyEncryptedPin,
)
coEvery { coEvery {
vaultSdkSource.derivePinProtectedUserKey(any(), any()) vaultSdkSource.enrollPinWithEncryptedPin(any(), any())
} returns "pinProtectedUserKey".asSuccess() } returns enrollResponse.asSuccess()
prepareStateForUnlocking(unlockResult = mockVaultUnlockResult) prepareStateForUnlocking(unlockResult = mockVaultUnlockResult)
fakeAuthDiskSource.apply { fakeAuthDiskSource.apply {
storeEncryptedPin( storeEncryptedPin(
@ -617,6 +638,11 @@ class VaultRepositoryTest {
pinProtectedUserKey = null, pinProtectedUserKey = null,
inMemoryOnly = true, inMemoryOnly = true,
) )
storePinProtectedUserKeyEnvelope(
userId = userId,
pinProtectedUserKeyEnvelope = null,
inMemoryOnly = true,
)
} }
val result = vaultRepository.unlockVaultWithMasterPassword( val result = vaultRepository.unlockVaultWithMasterPassword(
@ -642,7 +668,7 @@ class VaultRepositoryTest {
organizationKeys = createMockOrganizationKeys(number = 1), organizationKeys = createMockOrganizationKeys(number = 1),
) )
} }
coVerify(exactly = 0) { vaultSdkSource.derivePinProtectedUserKey(any(), any()) } coVerify(exactly = 0) { vaultSdkSource.enrollPinWithEncryptedPin(any(), any()) }
} }
@Suppress("MaxLineLength") @Suppress("MaxLineLength")
@ -650,15 +676,19 @@ class VaultRepositoryTest {
fun `unlockVaultWithMasterPassword with VaultLockManager Success and a stored encrypted pin should unlock for the current user, derive a new pin-protected key, and return Success`() = fun `unlockVaultWithMasterPassword with VaultLockManager Success and a stored encrypted pin should unlock for the current user, derive a new pin-protected key, and return Success`() =
runTest { runTest {
val userId = "mockId-1" val userId = "mockId-1"
val encryptedPin = "encryptedPin" val pinProtectedUserKey = "pinProtectedUserkeyEnvelope"
val pinProtectedUserKey = "pinProtectedUserkey" val encryptedPin = "userKeyEncryptedPin"
val enrollResponse = EnrollPinResponse(
pinProtectedUserKeyEnvelope = pinProtectedUserKey,
userKeyEncryptedPin = encryptedPin,
)
val mockVaultUnlockResult = VaultUnlockResult.Success val mockVaultUnlockResult = VaultUnlockResult.Success
coEvery { coEvery {
vaultSdkSource.derivePinProtectedUserKey( vaultSdkSource.enrollPinWithEncryptedPin(
userId = userId, userId = userId,
encryptedPin = encryptedPin, encryptedPin = encryptedPin,
) )
} returns pinProtectedUserKey.asSuccess() } returns enrollResponse.asSuccess()
prepareStateForUnlocking(unlockResult = mockVaultUnlockResult) prepareStateForUnlocking(unlockResult = mockVaultUnlockResult)
fakeAuthDiskSource.apply { fakeAuthDiskSource.apply {
storeEncryptedPin( storeEncryptedPin(
@ -670,6 +700,11 @@ class VaultRepositoryTest {
pinProtectedUserKey = null, pinProtectedUserKey = null,
inMemoryOnly = true, inMemoryOnly = true,
) )
storePinProtectedUserKeyEnvelope(
userId = userId,
pinProtectedUserKeyEnvelope = null,
inMemoryOnly = true,
)
} }
val result = vaultRepository.unlockVaultWithMasterPassword( val result = vaultRepository.unlockVaultWithMasterPassword(
@ -682,7 +717,12 @@ class VaultRepositoryTest {
) )
fakeAuthDiskSource.assertPinProtectedUserKey( fakeAuthDiskSource.assertPinProtectedUserKey(
userId = userId, userId = userId,
pinProtectedUserKey = pinProtectedUserKey, pinProtectedUserKey = null,
inMemoryOnly = true,
)
fakeAuthDiskSource.assertPinProtectedUserKeyEnvelope(
userId = userId,
pinProtectedUserKeyEnvelope = pinProtectedUserKey,
inMemoryOnly = true, inMemoryOnly = true,
) )
coVerify { coVerify {
@ -701,7 +741,7 @@ class VaultRepositoryTest {
) )
} }
coEvery { coEvery {
vaultSdkSource.derivePinProtectedUserKey( vaultSdkSource.enrollPinWithEncryptedPin(
userId = userId, userId = userId,
encryptedPin = encryptedPin, encryptedPin = encryptedPin,
) )
@ -762,6 +802,10 @@ class VaultRepositoryTest {
userId = "mockId-1", userId = "mockId-1",
pinProtectedUserKey = null, pinProtectedUserKey = null,
) )
fakeAuthDiskSource.storePinProtectedUserKeyEnvelope(
userId = "mockId-1",
pinProtectedUserKeyEnvelope = null,
)
fakeAuthDiskSource.storePrivateKey( fakeAuthDiskSource.storePrivateKey(
userId = "mockId-1", userId = "mockId-1",
privateKey = "mockPrivateKey-1", privateKey = "mockPrivateKey-1",
@ -780,6 +824,10 @@ class VaultRepositoryTest {
userId = "mockId-1", userId = "mockId-1",
pinProtectedUserKey = "mockKey-1", pinProtectedUserKey = "mockKey-1",
) )
fakeAuthDiskSource.storePinProtectedUserKeyEnvelope(
userId = "mockId-1",
pinProtectedUserKeyEnvelope = null,
)
fakeAuthDiskSource.storePrivateKey( fakeAuthDiskSource.storePrivateKey(
userId = "mockId-1", userId = "mockId-1",
privateKey = null, privateKey = null,
@ -801,6 +849,41 @@ class VaultRepositoryTest {
val result = vaultRepository.unlockVaultWithPin(pin = "1234") val result = vaultRepository.unlockVaultWithPin(pin = "1234")
assertEquals(
mockVaultUnlockResult,
result,
)
coVerify {
vaultLockManager.unlockVault(
userId = userId,
email = "email",
kdf = MOCK_PROFILE.toSdkParams(),
privateKey = "mockPrivateKey-1",
signingKey = null,
securityState = null,
initUserCryptoMethod = InitUserCryptoMethod.PinEnvelope(
pin = "1234",
pinProtectedUserKeyEnvelope = "mockKey-1",
),
organizationKeys = createMockOrganizationKeys(number = 1),
)
}
}
@Suppress("MaxLineLength")
@Test
fun `unlockVaultWithPin with PinProtectedUserKeyEnvelope null and VaultLockManager Success should unlock with pin for the current user and return Success`() =
runTest {
val userId = "mockId-1"
val mockVaultUnlockResult = VaultUnlockResult.Success
prepareStateForUnlocking(unlockResult = mockVaultUnlockResult)
fakeAuthDiskSource.storePinProtectedUserKeyEnvelope(
userId = userId,
pinProtectedUserKeyEnvelope = null,
)
val result = vaultRepository.unlockVaultWithPin(pin = "1234")
assertEquals( assertEquals(
mockVaultUnlockResult, mockVaultUnlockResult,
result, result,
@ -844,9 +927,9 @@ class VaultRepositoryTest {
privateKey = "mockPrivateKey-1", privateKey = "mockPrivateKey-1",
signingKey = null, signingKey = null,
securityState = null, securityState = null,
initUserCryptoMethod = InitUserCryptoMethod.Pin( initUserCryptoMethod = InitUserCryptoMethod.PinEnvelope(
pin = "1234", pin = "1234",
pinProtectedUserKey = "mockKey-1", pinProtectedUserKeyEnvelope = "mockKey-1",
), ),
organizationKeys = createMockOrganizationKeys(number = 1), organizationKeys = createMockOrganizationKeys(number = 1),
) )
@ -1432,6 +1515,10 @@ class VaultRepositoryTest {
userId = userId, userId = userId,
pinProtectedUserKey = "mockKey-1", pinProtectedUserKey = "mockKey-1",
) )
fakeAuthDiskSource.storePinProtectedUserKeyEnvelope(
userId = userId,
pinProtectedUserKeyEnvelope = "mockKey-1",
)
fakeAuthDiskSource.storeOrganizationKeys( fakeAuthDiskSource.storeOrganizationKeys(
userId = userId, userId = userId,
organizationKeys = createMockOrganizationKeys(number = 1), organizationKeys = createMockOrganizationKeys(number = 1),
@ -1471,6 +1558,23 @@ class VaultRepositoryTest {
organizationKeys = createMockOrganizationKeys(number = 1), organizationKeys = createMockOrganizationKeys(number = 1),
) )
} returns unlockResult } returns unlockResult
// PIN ENVELOPE unlock
coEvery {
vaultLockManager.unlockVault(
userId = userId,
email = "email",
kdf = MOCK_PROFILE.toSdkParams(),
privateKey = "mockPrivateKey-1",
signingKey = null,
securityState = null,
initUserCryptoMethod = InitUserCryptoMethod.PinEnvelope(
pin = mockPin,
pinProtectedUserKeyEnvelope = "mockKey-1",
),
organizationKeys = createMockOrganizationKeys(number = 1),
)
} returns unlockResult
} }
//endregion Helper functions //endregion Helper functions
} }