mirror of
https://github.com/bitwarden/android.git
synced 2025-12-10 09:56:45 -06:00
[PM-23290] Migrate PIN unlock keys to PinProtectedUserKeyEnvelope (#6024)
This commit is contained in:
parent
d5912a5dc3
commit
afeeb494da
@ -216,25 +216,59 @@ interface AuthDiskSource : AppIdProvider {
|
||||
/**
|
||||
* Retrieves a pin-protected user key for the given [userId].
|
||||
*/
|
||||
@Deprecated(
|
||||
message = "Use getPinProtectedUserKeyEnvelope instead.",
|
||||
replaceWith = ReplaceWith("getPinProtectedUserKeyEnvelope"),
|
||||
)
|
||||
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].
|
||||
*
|
||||
* When [inMemoryOnly] is `true`, the value will only be available via a call to
|
||||
* [getPinProtectedUserKey] during the current app session.
|
||||
*/
|
||||
@Deprecated(
|
||||
message = "Use storePinProtectedUserKeyEnvelope instead.",
|
||||
replaceWith = ReplaceWith("storePinProtectedUserKeyEnvelope"),
|
||||
)
|
||||
fun storePinProtectedUserKey(
|
||||
userId: String,
|
||||
pinProtectedUserKey: String?,
|
||||
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].
|
||||
*/
|
||||
@Deprecated(
|
||||
message = "Use getPinProtectedUserKeyEnvelopeFlow instead.",
|
||||
replaceWith = ReplaceWith("getPinProtectedUserKeyEnvelopeFlow"),
|
||||
)
|
||||
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].
|
||||
*/
|
||||
|
||||
@ -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_PRIVATE_KEY = "encPrivateKey"
|
||||
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 ORGANIZATIONS_KEY = "organizations"
|
||||
private const val ORGANIZATION_KEYS_KEY = "encOrgKeys"
|
||||
@ -67,6 +68,7 @@ class AuthDiskSourceImpl(
|
||||
AuthDiskSource {
|
||||
|
||||
private val inMemoryPinProtectedUserKeys = mutableMapOf<String, String?>()
|
||||
private val inMemoryPinProtectedUserKeyEnvelopes = mutableMapOf<String, String?>()
|
||||
private val mutableShouldUseKeyConnectorFlowMap =
|
||||
mutableMapOf<String, MutableSharedFlow<Boolean?>>()
|
||||
private val mutableOrganizationsFlowMap =
|
||||
@ -82,6 +84,8 @@ class AuthDiskSourceImpl(
|
||||
mutableMapOf<String, MutableSharedFlow<String?>>()
|
||||
private val mutablePinProtectedUserKeyFlowMap =
|
||||
mutableMapOf<String, MutableSharedFlow<String?>>()
|
||||
private val mutablePinProtectedUserKeyEnvelopeFlowMap =
|
||||
mutableMapOf<String, MutableSharedFlow<String?>>()
|
||||
private val mutableUserStateFlow = bufferedMutableSharedFlow<UserStateJson?>(replay = 1)
|
||||
|
||||
override var userState: UserStateJson?
|
||||
@ -142,6 +146,7 @@ class AuthDiskSourceImpl(
|
||||
storeUserKey(userId = userId, userKey = null)
|
||||
storeUserAutoUnlockKey(userId = userId, userAutoUnlockKey = null)
|
||||
storePinProtectedUserKey(userId = userId, pinProtectedUserKey = null)
|
||||
storePinProtectedUserKeyEnvelope(userId = userId, pinProtectedUserKeyEnvelope = null)
|
||||
storeEncryptedPin(userId = userId, encryptedPin = null)
|
||||
storePrivateKey(userId = userId, privateKey = null)
|
||||
storeAccountKeys(userId = userId, accountKeys = null)
|
||||
@ -329,10 +334,24 @@ class AuthDiskSourceImpl(
|
||||
getMutableBiometricUnlockKeyFlow(userId)
|
||||
.onSubscription { emit(getUserBiometricUnlockKey(userId = userId)) }
|
||||
|
||||
@Deprecated(
|
||||
"Use getPinProtectedUserKeyEnvelope instead.",
|
||||
replaceWith = ReplaceWith("getPinProtectedUserKeyEnvelope"),
|
||||
)
|
||||
override fun getPinProtectedUserKey(userId: String): String? =
|
||||
inMemoryPinProtectedUserKeys[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(
|
||||
userId: String,
|
||||
pinProtectedUserKey: String?,
|
||||
@ -347,10 +366,32 @@ class AuthDiskSourceImpl(
|
||||
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?> =
|
||||
getMutablePinProtectedUserKeyFlow(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? =
|
||||
getString(key = TWO_FACTOR_TOKEN_KEY.appendIdentifier(email))
|
||||
|
||||
@ -579,6 +620,12 @@ class AuthDiskSourceImpl(
|
||||
bufferedMutableSharedFlow(replay = 1)
|
||||
}
|
||||
|
||||
private fun getMutablePinProtectedUserKeyEnvelopeFlow(
|
||||
userId: String,
|
||||
): MutableSharedFlow<String?> = mutablePinProtectedUserKeyEnvelopeFlowMap.getOrPut(userId) {
|
||||
bufferedMutableSharedFlow(replay = 1)
|
||||
}
|
||||
|
||||
private fun migrateAccountTokens() {
|
||||
userState
|
||||
?.accounts
|
||||
|
||||
@ -85,7 +85,8 @@ class UserLogoutManagerImpl(
|
||||
// Save any data that will still need to be retained after otherwise clearing all dat
|
||||
val vaultTimeoutInMinutes = settingsDiskSource.getVaultTimeoutInMinutes(userId = userId)
|
||||
val vaultTimeoutAction = settingsDiskSource.getVaultTimeoutAction(userId = userId)
|
||||
val pinProtectedUserKey = authDiskSource.getPinProtectedUserKey(userId = userId)
|
||||
val pinProtectedUserKeyEnvelope = authDiskSource
|
||||
.getPinProtectedUserKeyEnvelope(userId = userId)
|
||||
|
||||
switchUserIfAvailable(
|
||||
currentUserId = userId,
|
||||
@ -107,9 +108,9 @@ class UserLogoutManagerImpl(
|
||||
vaultTimeoutAction = vaultTimeoutAction,
|
||||
)
|
||||
}
|
||||
authDiskSource.storePinProtectedUserKey(
|
||||
authDiskSource.storePinProtectedUserKeyEnvelope(
|
||||
userId = userId,
|
||||
pinProtectedUserKey = pinProtectedUserKey,
|
||||
pinProtectedUserKeyEnvelope = pinProtectedUserKeyEnvelope,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -156,7 +156,7 @@ class UserStateManagerImpl(
|
||||
private fun getVaultUnlockType(
|
||||
userId: String,
|
||||
): VaultUnlockType = authDiskSource
|
||||
.getPinProtectedUserKey(userId = userId)
|
||||
.getPinProtectedUserKeyEnvelope(userId = userId)
|
||||
?.let { VaultUnlockType.PIN }
|
||||
?: VaultUnlockType.MASTER_PASSWORD
|
||||
}
|
||||
|
||||
@ -1295,8 +1295,8 @@ class AuthRepositoryImpl(
|
||||
?.activeAccount
|
||||
?.profile
|
||||
?: return ValidatePinResult.Error(error = NoActiveUserException())
|
||||
val pinProtectedUserKey = authDiskSource
|
||||
.getPinProtectedUserKey(userId = activeAccount.userId)
|
||||
val pinProtectedUserKeyEnvelope = authDiskSource
|
||||
.getPinProtectedUserKeyEnvelope(userId = activeAccount.userId)
|
||||
?: return ValidatePinResult.Error(
|
||||
error = MissingPropertyException("Pin Protected User Key"),
|
||||
)
|
||||
@ -1304,7 +1304,7 @@ class AuthRepositoryImpl(
|
||||
.validatePin(
|
||||
userId = activeAccount.userId,
|
||||
pin = pin,
|
||||
pinProtectedUserKey = pinProtectedUserKey,
|
||||
pinProtectedUserKey = pinProtectedUserKeyEnvelope,
|
||||
)
|
||||
.fold(
|
||||
onSuccess = { ValidatePinResult.Success(isValid = it) },
|
||||
|
||||
@ -29,6 +29,7 @@ import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
@ -293,7 +294,11 @@ class SettingsRepositoryImpl(
|
||||
?.let { userId ->
|
||||
authDiskSource
|
||||
.getPinProtectedUserKeyFlow(userId)
|
||||
.map { it != null }
|
||||
.combine(
|
||||
authDiskSource.getPinProtectedUserKeyEnvelopeFlow(userId),
|
||||
) { pinProtectedUserKey, pinProtectedUserKeyEnvelope ->
|
||||
pinProtectedUserKey != null || pinProtectedUserKeyEnvelope != null
|
||||
}
|
||||
}
|
||||
?: flowOf(false)
|
||||
|
||||
@ -403,7 +408,7 @@ class SettingsRepositoryImpl(
|
||||
?.userDecryptionOptions
|
||||
?.hasMasterPassword != false
|
||||
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
|
||||
// The timeout action cannot be "lock" if you do not have master password, pin, or
|
||||
// biometrics unlock enabled.
|
||||
@ -527,21 +532,27 @@ class SettingsRepositoryImpl(
|
||||
val userId = activeUserId ?: return
|
||||
unconfinedScope.launch {
|
||||
vaultSdkSource
|
||||
.derivePinKey(
|
||||
.enrollPin(
|
||||
userId = userId,
|
||||
pin = pin,
|
||||
)
|
||||
.fold(
|
||||
onSuccess = { derivePinKeyResponse ->
|
||||
onSuccess = { enrollPinResponse ->
|
||||
authDiskSource.apply {
|
||||
storeEncryptedPin(
|
||||
userId = userId,
|
||||
encryptedPin = derivePinKeyResponse.encryptedPin,
|
||||
encryptedPin = enrollPinResponse.userKeyEncryptedPin,
|
||||
)
|
||||
storePinProtectedUserKeyEnvelope(
|
||||
userId = userId,
|
||||
pinProtectedUserKeyEnvelope =
|
||||
enrollPinResponse.pinProtectedUserKeyEnvelope,
|
||||
inMemoryOnly = shouldRequireMasterPasswordOnRestart,
|
||||
)
|
||||
// Remove any legacy pin protected user keys.
|
||||
storePinProtectedUserKey(
|
||||
userId = userId,
|
||||
pinProtectedUserKey = derivePinKeyResponse.pinProtectedUserKey,
|
||||
inMemoryOnly = shouldRequireMasterPasswordOnRestart,
|
||||
pinProtectedUserKey = null,
|
||||
)
|
||||
}
|
||||
},
|
||||
@ -561,6 +572,10 @@ class SettingsRepositoryImpl(
|
||||
userId = userId,
|
||||
encryptedPin = null,
|
||||
)
|
||||
authDiskSource.storePinProtectedUserKeyEnvelope(
|
||||
userId = userId,
|
||||
pinProtectedUserKeyEnvelope = null,
|
||||
)
|
||||
authDiskSource.storePinProtectedUserKey(
|
||||
userId = userId,
|
||||
pinProtectedUserKey = null,
|
||||
|
||||
@ -2,7 +2,7 @@ package com.x8bit.bitwarden.data.vault.datasource.sdk
|
||||
|
||||
import com.bitwarden.collections.Collection
|
||||
import com.bitwarden.collections.CollectionView
|
||||
import com.bitwarden.core.DerivePinKeyResponse
|
||||
import com.bitwarden.core.EnrollPinResponse
|
||||
import com.bitwarden.core.InitOrgCryptoRequest
|
||||
import com.bitwarden.core.InitUserCryptoMethod
|
||||
import com.bitwarden.core.InitUserCryptoRequest
|
||||
@ -75,30 +75,26 @@ interface VaultSdkSource {
|
||||
): Result<DeriveKeyConnectorResult>
|
||||
|
||||
/**
|
||||
* Derives a "pin key" from the given [pin] for the given [userId]. This can be used to later
|
||||
* unlock their vault via a call to [initializeCrypto] with [InitUserCryptoMethod.Pin].
|
||||
* Protects the current user key with the provided PIN. This can be used to later unlock
|
||||
* their vault via a call to [initializeCrypto] with [InitUserCryptoMethod.PinEnvelope].
|
||||
*
|
||||
* This should only be called after a successful call to [initializeCrypto] for the associated
|
||||
* user.
|
||||
*/
|
||||
suspend fun derivePinKey(
|
||||
suspend fun enrollPin(
|
||||
userId: String,
|
||||
pin: String,
|
||||
): Result<DerivePinKeyResponse>
|
||||
): Result<EnrollPinResponse>
|
||||
|
||||
/**
|
||||
* Derives a pin-protected user key from the given [encryptedPin] for the given [userId]. This
|
||||
* value must be derived from a previous call to [derivePinKey] with a plaintext PIN. This can
|
||||
* be used to later unlock their vault via a call to [initializeCrypto] with
|
||||
* [InitUserCryptoMethod.Pin].
|
||||
*
|
||||
* This should only be called after a successful call to [initializeCrypto] for the associated
|
||||
* user.
|
||||
* Protects the current user key with the provided PIN. The result can be stored and later
|
||||
* used to initialize another client instance by using the PIN and the PIN key with
|
||||
* [initializeCrypto]. The provided pin is encrypted with the user key.
|
||||
*/
|
||||
suspend fun derivePinProtectedUserKey(
|
||||
suspend fun enrollPinWithEncryptedPin(
|
||||
userId: String,
|
||||
encryptedPin: String,
|
||||
): Result<String>
|
||||
): Result<EnrollPinResponse>
|
||||
|
||||
/**
|
||||
* Validate the user pin using the [pinProtectedUserKey].
|
||||
|
||||
@ -4,7 +4,7 @@ import com.bitwarden.collections.Collection
|
||||
import com.bitwarden.collections.CollectionView
|
||||
import com.bitwarden.core.DeriveKeyConnectorException
|
||||
import com.bitwarden.core.DeriveKeyConnectorRequest
|
||||
import com.bitwarden.core.DerivePinKeyResponse
|
||||
import com.bitwarden.core.EnrollPinResponse
|
||||
import com.bitwarden.core.InitOrgCryptoRequest
|
||||
import com.bitwarden.core.InitUserCryptoRequest
|
||||
import com.bitwarden.core.UpdateKdfResponse
|
||||
@ -109,24 +109,24 @@ class VaultSdkSourceImpl(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun derivePinKey(
|
||||
override suspend fun enrollPin(
|
||||
userId: String,
|
||||
pin: String,
|
||||
): Result<DerivePinKeyResponse> =
|
||||
): Result<EnrollPinResponse> =
|
||||
runCatchingWithLogs {
|
||||
getClient(userId = userId)
|
||||
.crypto()
|
||||
.derivePinKey(pin = pin)
|
||||
.enrollPin(pin = pin)
|
||||
}
|
||||
|
||||
override suspend fun derivePinProtectedUserKey(
|
||||
override suspend fun enrollPinWithEncryptedPin(
|
||||
userId: String,
|
||||
encryptedPin: String,
|
||||
): Result<String> =
|
||||
): Result<EnrollPinResponse> =
|
||||
runCatchingWithLogs {
|
||||
getClient(userId = userId)
|
||||
.crypto()
|
||||
.derivePinUserKey(encryptedPin = encryptedPin)
|
||||
.enrollPinWithEncryptedPin(encryptedPin = encryptedPin)
|
||||
}
|
||||
|
||||
override suspend fun validatePin(
|
||||
|
||||
@ -239,6 +239,7 @@ class VaultLockManagerImpl(
|
||||
trustedDeviceManager
|
||||
.trustThisDeviceIfNecessary(userId = userId)
|
||||
updateKdfIfNeeded(initUserCryptoMethod)
|
||||
migratePinProtectedUserKeyIfNeeded(userId = userId)
|
||||
setVaultToUnlocked(userId = userId)
|
||||
} else {
|
||||
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
|
||||
* if this new value is greater than [MAXIMUM_INVALID_UNLOCK_ATTEMPTS].
|
||||
|
||||
@ -360,11 +360,27 @@ class VaultRepositoryImpl(
|
||||
): VaultUnlockResult {
|
||||
val userId = activeUserId
|
||||
?: return VaultUnlockResult.InvalidStateError(error = NoActiveUserException())
|
||||
|
||||
return authDiskSource.getPinProtectedUserKeyEnvelope(userId = userId)
|
||||
?.let { pinProtectedUserKeyEnvelope ->
|
||||
this.unlockVaultForUser(
|
||||
userId = userId,
|
||||
initUserCryptoMethod = InitUserCryptoMethod.PinEnvelope(
|
||||
pin = pin,
|
||||
pinProtectedUserKeyEnvelope = pinProtectedUserKeyEnvelope,
|
||||
),
|
||||
)
|
||||
}
|
||||
?: 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"),
|
||||
)
|
||||
return this.unlockVaultForUser(
|
||||
|
||||
this.unlockVaultForUser(
|
||||
userId = userId,
|
||||
initUserCryptoMethod = InitUserCryptoMethod.Pin(
|
||||
pin = pin,
|
||||
@ -372,6 +388,7 @@ class VaultRepositoryImpl(
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun generateTotp(
|
||||
cipherId: String,
|
||||
@ -502,17 +519,29 @@ class VaultRepositoryImpl(
|
||||
*/
|
||||
private suspend fun deriveTemporaryPinProtectedUserKeyIfNecessary(userId: String) {
|
||||
val encryptedPin = authDiskSource.getEncryptedPin(userId = userId) ?: return
|
||||
val existingPinProtectedUserKey = authDiskSource.getPinProtectedUserKey(userId = userId)
|
||||
if (existingPinProtectedUserKey != null) return
|
||||
val existingPinProtectedUserKeyEnvelope = authDiskSource
|
||||
.getPinProtectedUserKeyEnvelope(
|
||||
userId = userId,
|
||||
)
|
||||
if (existingPinProtectedUserKeyEnvelope != null) return
|
||||
vaultSdkSource
|
||||
.derivePinProtectedUserKey(
|
||||
.enrollPinWithEncryptedPin(
|
||||
userId = userId,
|
||||
encryptedPin = encryptedPin,
|
||||
)
|
||||
.onSuccess { pinProtectedUserKey ->
|
||||
.onSuccess { enrollPinResponse ->
|
||||
authDiskSource.storeEncryptedPin(
|
||||
userId = userId,
|
||||
encryptedPin = enrollPinResponse.userKeyEncryptedPin,
|
||||
)
|
||||
authDiskSource.storePinProtectedUserKeyEnvelope(
|
||||
userId = userId,
|
||||
pinProtectedUserKeyEnvelope = enrollPinResponse.pinProtectedUserKeyEnvelope,
|
||||
inMemoryOnly = true,
|
||||
)
|
||||
authDiskSource.storePinProtectedUserKey(
|
||||
userId = userId,
|
||||
pinProtectedUserKey = pinProtectedUserKey,
|
||||
pinProtectedUserKey = null,
|
||||
inMemoryOnly = true,
|
||||
)
|
||||
}
|
||||
|
||||
@ -331,6 +331,7 @@ class AuthDiskSourceTest {
|
||||
assertNull(authDiskSource.getUserBiometricInitVector(userId = userId))
|
||||
assertNull(authDiskSource.getUserBiometricUnlockKey(userId = userId))
|
||||
assertNull(authDiskSource.getPinProtectedUserKey(userId = userId))
|
||||
assertNull(authDiskSource.getPinProtectedUserKeyEnvelope(userId = userId))
|
||||
assertNull(authDiskSource.getInvalidUnlockAttempts(userId = userId))
|
||||
assertNull(authDiskSource.getUserKey(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
|
||||
fun `storePinProtectedUserKey should update result flow from getPinProtectedUserKeyFlow`() =
|
||||
runTest {
|
||||
|
||||
@ -65,6 +65,9 @@ class FakeAuthDiskSource : AuthDiskSource {
|
||||
private val storedShowImportLogins = mutableMapOf<String, Boolean?>()
|
||||
private val storedLastLockTimestampState = mutableMapOf<String, Instant?>()
|
||||
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
|
||||
set(value) {
|
||||
@ -89,11 +92,19 @@ class FakeAuthDiskSource : AuthDiskSource {
|
||||
storedBiometricInitVectors.remove(userId)
|
||||
storedBiometricKeys.remove(userId)
|
||||
storedOrganizationKeys.remove(userId)
|
||||
storedPinProtectedUserKeyEnvelopes.remove(userId)
|
||||
|
||||
mutableShouldUseKeyConnectorFlowMap.remove(userId)
|
||||
mutableOrganizationsFlowMap.remove(userId)
|
||||
mutablePoliciesFlowMap.remove(userId)
|
||||
mutableAccountTokensFlowMap.remove(userId)
|
||||
mutablePinProtectedUserKeyEnvelopesFlowMap.remove(userId)
|
||||
}
|
||||
|
||||
private fun getMutablePinProtectedUserKeyEnvelopeFlow(
|
||||
userId: String,
|
||||
): MutableSharedFlow<String?> = mutablePinProtectedUserKeyEnvelopesFlowMap.getOrPut(userId) {
|
||||
bufferedMutableSharedFlow(replay = 1)
|
||||
}
|
||||
|
||||
override fun getShouldUseKeyConnectorFlow(
|
||||
@ -170,9 +181,17 @@ class FakeAuthDiskSource : AuthDiskSource {
|
||||
storedUserAutoUnlockKeys[userId] = userAutoUnlockKey
|
||||
}
|
||||
|
||||
@Deprecated(
|
||||
"Use getPinProtectedUserKeyEnvelope instead.",
|
||||
replaceWith = ReplaceWith("getPinProtectedUserKeyEnvelope"),
|
||||
)
|
||||
override fun getPinProtectedUserKey(userId: String): String? =
|
||||
storedPinProtectedUserKeys[userId]?.first
|
||||
|
||||
@Deprecated(
|
||||
"Use storePinProtectedUserKeyEnvelope instead.",
|
||||
replaceWith = ReplaceWith("storePinProtectedUserKeyEnvelope"),
|
||||
)
|
||||
override fun storePinProtectedUserKey(
|
||||
userId: String,
|
||||
pinProtectedUserKey: String?,
|
||||
@ -182,6 +201,10 @@ class FakeAuthDiskSource : AuthDiskSource {
|
||||
getMutablePinProtectedUserKeyFlow(userId).tryEmit(pinProtectedUserKey)
|
||||
}
|
||||
|
||||
@Deprecated(
|
||||
"Use getPinProtectedUserKeyEnvelopeFlow instead.",
|
||||
replaceWith = ReplaceWith("getPinProtectedUserKeyEnvelopeFlow"),
|
||||
)
|
||||
override fun getPinProtectedUserKeyFlow(userId: String): Flow<String?> =
|
||||
getMutablePinProtectedUserKeyFlow(userId)
|
||||
.onSubscription {
|
||||
@ -331,6 +354,24 @@ class FakeAuthDiskSource : AuthDiskSource {
|
||||
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].
|
||||
*/
|
||||
@ -427,6 +468,20 @@ class FakeAuthDiskSource : AuthDiskSource {
|
||||
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].
|
||||
*/
|
||||
|
||||
@ -34,7 +34,12 @@ import java.time.ZonedDateTime
|
||||
class UserLogoutManagerTest {
|
||||
private val authDiskSource: AuthDiskSource = mockk {
|
||||
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 { clearData(any()) } just runs
|
||||
}
|
||||
@ -137,8 +142,9 @@ class UserLogoutManagerTest {
|
||||
every {
|
||||
settingsDiskSource.getVaultTimeoutAction(userId = userId)
|
||||
} returns vaultTimeoutAction
|
||||
|
||||
every {
|
||||
authDiskSource.getPinProtectedUserKey(userId = userId)
|
||||
authDiskSource.getPinProtectedUserKeyEnvelope(userId = userId)
|
||||
} returns pinProtectedUserKey
|
||||
|
||||
userLogoutManager.softLogout(userId = userId, reason = LogoutReason.Timeout)
|
||||
@ -164,9 +170,9 @@ class UserLogoutManagerTest {
|
||||
userId = userId,
|
||||
vaultTimeoutAction = vaultTimeoutAction,
|
||||
)
|
||||
authDiskSource.storePinProtectedUserKey(
|
||||
authDiskSource.storePinProtectedUserKeyEnvelope(
|
||||
userId = userId,
|
||||
pinProtectedUserKey = pinProtectedUserKey,
|
||||
pinProtectedUserKeyEnvelope = pinProtectedUserKey,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -186,7 +192,7 @@ class UserLogoutManagerTest {
|
||||
settingsDiskSource.getVaultTimeoutAction(userId = userId)
|
||||
} returns vaultTimeoutAction
|
||||
every {
|
||||
authDiskSource.getPinProtectedUserKey(userId = userId)
|
||||
authDiskSource.getPinProtectedUserKeyEnvelope(userId = userId)
|
||||
} returns pinProtectedUserKey
|
||||
|
||||
userLogoutManager.softLogout(userId = userId, reason = LogoutReason.Timeout)
|
||||
@ -206,9 +212,9 @@ class UserLogoutManagerTest {
|
||||
userId = userId,
|
||||
vaultTimeoutAction = vaultTimeoutAction,
|
||||
)
|
||||
authDiskSource.storePinProtectedUserKey(
|
||||
authDiskSource.storePinProtectedUserKeyEnvelope(
|
||||
userId = userId,
|
||||
pinProtectedUserKey = pinProtectedUserKey,
|
||||
pinProtectedUserKeyEnvelope = pinProtectedUserKey,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -222,15 +228,15 @@ class UserLogoutManagerTest {
|
||||
val pinProtectedUserKey = "pinProtectedUserKey"
|
||||
|
||||
every { authDiskSource.userState } returns MULTI_USER_STATE
|
||||
every {
|
||||
authDiskSource.getPinProtectedUserKeyEnvelope(userId)
|
||||
} returns pinProtectedUserKey
|
||||
every {
|
||||
settingsDiskSource.getVaultTimeoutInMinutes(userId = userId)
|
||||
} returns vaultTimeoutInMinutes
|
||||
every {
|
||||
settingsDiskSource.getVaultTimeoutAction(userId = userId)
|
||||
} returns vaultTimeoutAction
|
||||
every {
|
||||
authDiskSource.getPinProtectedUserKey(userId = userId)
|
||||
} returns pinProtectedUserKey
|
||||
|
||||
userLogoutManager.softLogout(userId = userId, reason = LogoutReason.SecurityStamp)
|
||||
|
||||
@ -249,9 +255,9 @@ class UserLogoutManagerTest {
|
||||
userId = userId,
|
||||
vaultTimeoutAction = vaultTimeoutAction,
|
||||
)
|
||||
authDiskSource.storePinProtectedUserKey(
|
||||
authDiskSource.storePinProtectedUserKeyEnvelope(
|
||||
userId = userId,
|
||||
pinProtectedUserKey = pinProtectedUserKey,
|
||||
pinProtectedUserKeyEnvelope = pinProtectedUserKey,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,13 +92,13 @@ class UserStateManagerTest {
|
||||
)
|
||||
|
||||
fakeAuthDiskSource.apply {
|
||||
storePinProtectedUserKey(
|
||||
storePinProtectedUserKeyEnvelope(
|
||||
userId = USER_ID_1,
|
||||
pinProtectedUserKey = "pinProtectedUseKey",
|
||||
pinProtectedUserKeyEnvelope = "pinProtectedUseKey",
|
||||
)
|
||||
storePinProtectedUserKey(
|
||||
storePinProtectedUserKeyEnvelope(
|
||||
userId = USER_ID_2,
|
||||
pinProtectedUserKey = "pinProtectedUseKey",
|
||||
pinProtectedUserKeyEnvelope = "pinProtectedUseKey",
|
||||
)
|
||||
userState = MULTI_USER_STATE
|
||||
}
|
||||
@ -137,13 +137,13 @@ class UserStateManagerTest {
|
||||
)
|
||||
|
||||
fakeAuthDiskSource.apply {
|
||||
storePinProtectedUserKey(
|
||||
storePinProtectedUserKeyEnvelope(
|
||||
userId = USER_ID_1,
|
||||
pinProtectedUserKey = null,
|
||||
pinProtectedUserKeyEnvelope = null,
|
||||
)
|
||||
storePinProtectedUserKey(
|
||||
storePinProtectedUserKeyEnvelope(
|
||||
userId = USER_ID_2,
|
||||
pinProtectedUserKey = null,
|
||||
pinProtectedUserKeyEnvelope = null,
|
||||
)
|
||||
storeOrganizations(
|
||||
userId = USER_ID_1,
|
||||
|
||||
@ -6353,9 +6353,9 @@ class AuthRepositoryTest {
|
||||
val pinProtectedUserKey = "pinProtectedUserKey"
|
||||
val error = Throwable("Fail!")
|
||||
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
|
||||
fakeAuthDiskSource.storePinProtectedUserKey(
|
||||
fakeAuthDiskSource.storePinProtectedUserKeyEnvelope(
|
||||
userId = SINGLE_USER_STATE_1.activeUserId,
|
||||
pinProtectedUserKey = pinProtectedUserKey,
|
||||
pinProtectedUserKeyEnvelope = pinProtectedUserKey,
|
||||
)
|
||||
coEvery {
|
||||
vaultSdkSource.validatePin(
|
||||
@ -6387,9 +6387,9 @@ class AuthRepositoryTest {
|
||||
val pin = "PIN"
|
||||
val pinProtectedUserKey = "pinProtectedUserKey"
|
||||
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
|
||||
fakeAuthDiskSource.storePinProtectedUserKey(
|
||||
fakeAuthDiskSource.storePinProtectedUserKeyEnvelope(
|
||||
userId = SINGLE_USER_STATE_1.activeUserId,
|
||||
pinProtectedUserKey = pinProtectedUserKey,
|
||||
pinProtectedUserKeyEnvelope = pinProtectedUserKey,
|
||||
)
|
||||
coEvery {
|
||||
vaultSdkSource.validatePin(
|
||||
@ -6421,9 +6421,9 @@ class AuthRepositoryTest {
|
||||
val pin = "PIN"
|
||||
val pinProtectedUserKey = "pinProtectedUserKey"
|
||||
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
|
||||
fakeAuthDiskSource.storePinProtectedUserKey(
|
||||
fakeAuthDiskSource.storePinProtectedUserKeyEnvelope(
|
||||
userId = SINGLE_USER_STATE_1.activeUserId,
|
||||
pinProtectedUserKey = pinProtectedUserKey,
|
||||
pinProtectedUserKeyEnvelope = pinProtectedUserKey,
|
||||
)
|
||||
coEvery {
|
||||
vaultSdkSource.validatePin(
|
||||
|
||||
@ -3,7 +3,7 @@ package com.x8bit.bitwarden.data.platform.repository
|
||||
import android.view.autofill.AutofillManager
|
||||
import app.cash.turbine.test
|
||||
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.util.asFailure
|
||||
import com.bitwarden.core.data.util.asSuccess
|
||||
@ -202,9 +202,9 @@ class SettingsRepositoryTest {
|
||||
|
||||
// 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.
|
||||
fakeAuthDiskSource.storePinProtectedUserKey(
|
||||
fakeAuthDiskSource.storePinProtectedUserKeyEnvelope(
|
||||
userId = USER_ID,
|
||||
pinProtectedUserKey = "pinProtectedKey",
|
||||
pinProtectedUserKeyEnvelope = "pinProtectedKey",
|
||||
)
|
||||
fakeSettingsDiskSource.apply {
|
||||
storeVaultTimeoutInMinutes(
|
||||
@ -924,19 +924,19 @@ class SettingsRepositoryTest {
|
||||
@Test
|
||||
fun `storeUnlockPin when the master password on restart is required should only save an encrypted PIN to disk`() {
|
||||
val pin = "1234"
|
||||
val encryptedPin = "encryptedPin"
|
||||
val pinProtectedUserKey = "pinProtectedUserKey"
|
||||
val derivePinKeyResponse = DerivePinKeyResponse(
|
||||
pinProtectedUserKey = pinProtectedUserKey,
|
||||
encryptedPin = encryptedPin,
|
||||
val userKeyEncryptedPin = "encryptedPin"
|
||||
val pinProtectedUserKeyEnvelope = "pinProtectedUserKeyEnvelope"
|
||||
val enrollResponse = EnrollPinResponse(
|
||||
pinProtectedUserKeyEnvelope = pinProtectedUserKeyEnvelope,
|
||||
userKeyEncryptedPin = userKeyEncryptedPin,
|
||||
)
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
coEvery {
|
||||
vaultSdkSource.derivePinKey(
|
||||
vaultSdkSource.enrollPin(
|
||||
userId = USER_ID,
|
||||
pin = pin,
|
||||
)
|
||||
} returns derivePinKeyResponse.asSuccess()
|
||||
} returns enrollResponse.asSuccess()
|
||||
|
||||
settingsRepository.storeUnlockPin(
|
||||
pin = pin,
|
||||
@ -946,16 +946,16 @@ class SettingsRepositoryTest {
|
||||
fakeAuthDiskSource.apply {
|
||||
assertEncryptedPin(
|
||||
userId = USER_ID,
|
||||
encryptedPin = encryptedPin,
|
||||
encryptedPin = userKeyEncryptedPin,
|
||||
)
|
||||
assertPinProtectedUserKey(
|
||||
assertPinProtectedUserKeyEnvelope(
|
||||
userId = USER_ID,
|
||||
pinProtectedUserKey = pinProtectedUserKey,
|
||||
pinProtectedUserKeyEnvelope = pinProtectedUserKeyEnvelope,
|
||||
inMemoryOnly = true,
|
||||
)
|
||||
}
|
||||
coVerify {
|
||||
vaultSdkSource.derivePinKey(
|
||||
vaultSdkSource.enrollPin(
|
||||
userId = USER_ID,
|
||||
pin = pin,
|
||||
)
|
||||
@ -966,19 +966,19 @@ class SettingsRepositoryTest {
|
||||
@Test
|
||||
fun `storeUnlockPin when the master password on restart is not required should save all PIN data to disk`() {
|
||||
val pin = "1234"
|
||||
val encryptedPin = "encryptedPin"
|
||||
val pinProtectedUserKey = "pinProtectedUserKey"
|
||||
val derivePinKeyResponse = DerivePinKeyResponse(
|
||||
pinProtectedUserKey = pinProtectedUserKey,
|
||||
encryptedPin = encryptedPin,
|
||||
val userKeyEncryptedPin = "encryptedPin"
|
||||
val pinProtectedUserKeyEnvelope = "pinProtectedUserKeyEnvelope"
|
||||
val enrollResponse = EnrollPinResponse(
|
||||
pinProtectedUserKeyEnvelope = pinProtectedUserKeyEnvelope,
|
||||
userKeyEncryptedPin = userKeyEncryptedPin,
|
||||
)
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
coEvery {
|
||||
vaultSdkSource.derivePinKey(
|
||||
vaultSdkSource.enrollPin(
|
||||
userId = USER_ID,
|
||||
pin = pin,
|
||||
)
|
||||
} returns derivePinKeyResponse.asSuccess()
|
||||
} returns enrollResponse.asSuccess()
|
||||
|
||||
settingsRepository.storeUnlockPin(
|
||||
pin = pin,
|
||||
@ -988,16 +988,16 @@ class SettingsRepositoryTest {
|
||||
fakeAuthDiskSource.apply {
|
||||
assertEncryptedPin(
|
||||
userId = USER_ID,
|
||||
encryptedPin = encryptedPin,
|
||||
encryptedPin = userKeyEncryptedPin,
|
||||
)
|
||||
assertPinProtectedUserKey(
|
||||
assertPinProtectedUserKeyEnvelope(
|
||||
userId = USER_ID,
|
||||
pinProtectedUserKey = pinProtectedUserKey,
|
||||
pinProtectedUserKeyEnvelope = pinProtectedUserKeyEnvelope,
|
||||
inMemoryOnly = false,
|
||||
)
|
||||
}
|
||||
coVerify {
|
||||
vaultSdkSource.derivePinKey(
|
||||
vaultSdkSource.enrollPin(
|
||||
userId = USER_ID,
|
||||
pin = pin,
|
||||
)
|
||||
@ -1013,9 +1013,9 @@ class SettingsRepositoryTest {
|
||||
userId = USER_ID,
|
||||
encryptedPin = "encryptedPin",
|
||||
)
|
||||
storePinProtectedUserKey(
|
||||
storePinProtectedUserKeyEnvelope(
|
||||
userId = USER_ID,
|
||||
pinProtectedUserKey = "pinProtectedUserKey",
|
||||
pinProtectedUserKeyEnvelope = "pinProtectedUserKeyEnvelope",
|
||||
)
|
||||
}
|
||||
|
||||
@ -1026,9 +1026,9 @@ class SettingsRepositoryTest {
|
||||
userId = USER_ID,
|
||||
encryptedPin = null,
|
||||
)
|
||||
assertPinProtectedUserKey(
|
||||
assertPinProtectedUserKeyEnvelope(
|
||||
userId = USER_ID,
|
||||
pinProtectedUserKey = null,
|
||||
pinProtectedUserKeyEnvelope = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ import com.bitwarden.collections.Collection
|
||||
import com.bitwarden.collections.CollectionView
|
||||
import com.bitwarden.core.DeriveKeyConnectorException
|
||||
import com.bitwarden.core.DeriveKeyConnectorRequest
|
||||
import com.bitwarden.core.DerivePinKeyResponse
|
||||
import com.bitwarden.core.EnrollPinResponse
|
||||
import com.bitwarden.core.InitOrgCryptoRequest
|
||||
import com.bitwarden.core.InitUserCryptoRequest
|
||||
import com.bitwarden.core.MasterPasswordAuthenticationData
|
||||
@ -339,14 +339,14 @@ class VaultSdkSourceTest {
|
||||
}
|
||||
|
||||
@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 pin = "pin"
|
||||
val expectedResult = mockk<DerivePinKeyResponse>()
|
||||
val expectedResult = mockk<EnrollPinResponse>()
|
||||
coEvery {
|
||||
clientCrypto.derivePinKey(pin = pin)
|
||||
clientCrypto.enrollPin(pin = pin)
|
||||
} returns expectedResult
|
||||
val result = vaultSdkSource.derivePinKey(
|
||||
val result = vaultSdkSource.enrollPin(
|
||||
userId = userId,
|
||||
pin = pin,
|
||||
)
|
||||
@ -355,21 +355,21 @@ class VaultSdkSourceTest {
|
||||
result,
|
||||
)
|
||||
coVerify {
|
||||
clientCrypto.derivePinKey(pin)
|
||||
clientCrypto.enrollPin(pin)
|
||||
}
|
||||
coVerify { sdkClientManager.getOrCreateClient(userId = userId) }
|
||||
}
|
||||
|
||||
@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 {
|
||||
val userId = "userId"
|
||||
val encryptedPin = "encryptedPin"
|
||||
val expectedResult = "pinProtectedUserKey"
|
||||
val expectedResult = mockk<EnrollPinResponse>()
|
||||
coEvery {
|
||||
clientCrypto.derivePinUserKey(encryptedPin = encryptedPin)
|
||||
clientCrypto.enrollPinWithEncryptedPin(encryptedPin = encryptedPin)
|
||||
} returns expectedResult
|
||||
val result = vaultSdkSource.derivePinProtectedUserKey(
|
||||
val result = vaultSdkSource.enrollPinWithEncryptedPin(
|
||||
userId = userId,
|
||||
encryptedPin = encryptedPin,
|
||||
)
|
||||
@ -378,7 +378,7 @@ class VaultSdkSourceTest {
|
||||
result,
|
||||
)
|
||||
coVerify {
|
||||
clientCrypto.derivePinUserKey(encryptedPin = encryptedPin)
|
||||
clientCrypto.enrollPinWithEncryptedPin(encryptedPin = encryptedPin)
|
||||
}
|
||||
coVerify { sdkClientManager.getOrCreateClient(userId = userId) }
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import app.cash.turbine.test
|
||||
import com.bitwarden.core.EnrollPinResponse
|
||||
import com.bitwarden.core.InitOrgCryptoRequest
|
||||
import com.bitwarden.core.InitUserCryptoMethod
|
||||
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
|
||||
* behavior in place.
|
||||
|
||||
@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.vault.repository
|
||||
import app.cash.turbine.test
|
||||
import com.bitwarden.collections.CollectionView
|
||||
import com.bitwarden.core.DateTime
|
||||
import com.bitwarden.core.EnrollPinResponse
|
||||
import com.bitwarden.core.InitUserCryptoMethod
|
||||
import com.bitwarden.core.data.repository.model.DataState
|
||||
import com.bitwarden.core.data.util.asFailure
|
||||
@ -233,7 +234,7 @@ class VaultRepositoryTest {
|
||||
)
|
||||
}
|
||||
coVerify(exactly = 0) {
|
||||
vaultSdkSource.derivePinProtectedUserKey(any(), any())
|
||||
vaultSdkSource.enrollPinWithEncryptedPin(any(), any())
|
||||
}
|
||||
fakeAuthDiskSource.apply {
|
||||
assertBiometricsKey(
|
||||
@ -271,7 +272,7 @@ class VaultRepositoryTest {
|
||||
result,
|
||||
)
|
||||
coVerify(exactly = 0) {
|
||||
vaultSdkSource.derivePinProtectedUserKey(any(), any())
|
||||
vaultSdkSource.enrollPinWithEncryptedPin(any(), any())
|
||||
}
|
||||
}
|
||||
|
||||
@ -298,7 +299,7 @@ class VaultRepositoryTest {
|
||||
result,
|
||||
)
|
||||
coVerify(exactly = 0) {
|
||||
vaultSdkSource.derivePinProtectedUserKey(any(), any())
|
||||
vaultSdkSource.enrollPinWithEncryptedPin(any(), any())
|
||||
}
|
||||
}
|
||||
|
||||
@ -353,7 +354,7 @@ class VaultRepositoryTest {
|
||||
)
|
||||
}
|
||||
coVerify(exactly = 0) {
|
||||
vaultSdkSource.derivePinProtectedUserKey(any(), any())
|
||||
vaultSdkSource.enrollPinWithEncryptedPin(any(), any())
|
||||
}
|
||||
}
|
||||
|
||||
@ -364,7 +365,11 @@ class VaultRepositoryTest {
|
||||
val userId = MOCK_USER_STATE.activeUserId
|
||||
val encryptedPin = "encryptedPin"
|
||||
val privateKey = "mockPrivateKey-1"
|
||||
val pinProtectedUserKey = "pinProtectedUserkey"
|
||||
val pinProtectedUserKeyEnvelope = "pinProtectedUserKeyEnvelope"
|
||||
val enrollResponse = EnrollPinResponse(
|
||||
pinProtectedUserKeyEnvelope = pinProtectedUserKeyEnvelope,
|
||||
userKeyEncryptedPin = encryptedPin,
|
||||
)
|
||||
val biometricsKey = "asdf1234"
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
val encryptedBytes = byteArrayOf(1, 1)
|
||||
@ -374,11 +379,11 @@ class VaultRepositoryTest {
|
||||
every { iv } returns initVector
|
||||
}
|
||||
coEvery {
|
||||
vaultSdkSource.derivePinProtectedUserKey(
|
||||
vaultSdkSource.enrollPinWithEncryptedPin(
|
||||
userId = userId,
|
||||
encryptedPin = encryptedPin,
|
||||
)
|
||||
} returns pinProtectedUserKey.asSuccess()
|
||||
} returns enrollResponse.asSuccess()
|
||||
coEvery {
|
||||
vaultLockManager.unlockVault(
|
||||
userId = userId,
|
||||
@ -403,6 +408,11 @@ class VaultRepositoryTest {
|
||||
pinProtectedUserKey = null,
|
||||
inMemoryOnly = true,
|
||||
)
|
||||
storePinProtectedUserKeyEnvelope(
|
||||
userId = userId,
|
||||
pinProtectedUserKeyEnvelope = null,
|
||||
inMemoryOnly = true,
|
||||
)
|
||||
}
|
||||
|
||||
val result = vaultRepository.unlockVaultWithBiometrics(cipher = cipher)
|
||||
@ -410,7 +420,12 @@ class VaultRepositoryTest {
|
||||
assertEquals(VaultUnlockResult.Success, result)
|
||||
fakeAuthDiskSource.assertPinProtectedUserKey(
|
||||
userId = userId,
|
||||
pinProtectedUserKey = pinProtectedUserKey,
|
||||
pinProtectedUserKey = null,
|
||||
inMemoryOnly = true,
|
||||
)
|
||||
fakeAuthDiskSource.assertPinProtectedUserKeyEnvelope(
|
||||
userId = userId,
|
||||
pinProtectedUserKeyEnvelope = pinProtectedUserKeyEnvelope,
|
||||
inMemoryOnly = true,
|
||||
)
|
||||
coVerify {
|
||||
@ -428,7 +443,7 @@ class VaultRepositoryTest {
|
||||
)
|
||||
}
|
||||
coEvery {
|
||||
vaultSdkSource.derivePinProtectedUserKey(
|
||||
vaultSdkSource.enrollPinWithEncryptedPin(
|
||||
userId = userId,
|
||||
encryptedPin = encryptedPin,
|
||||
)
|
||||
@ -603,9 +618,15 @@ class VaultRepositoryTest {
|
||||
runTest {
|
||||
val userId = "mockId-1"
|
||||
val mockVaultUnlockResult = VaultUnlockResult.Success
|
||||
val userKeyEncryptedPin = "encryptedPin"
|
||||
val pinProtectedUserKeyEnvelope = "pinProtectedUserKeyEnvelope"
|
||||
val enrollResponse = EnrollPinResponse(
|
||||
pinProtectedUserKeyEnvelope = pinProtectedUserKeyEnvelope,
|
||||
userKeyEncryptedPin = userKeyEncryptedPin,
|
||||
)
|
||||
coEvery {
|
||||
vaultSdkSource.derivePinProtectedUserKey(any(), any())
|
||||
} returns "pinProtectedUserKey".asSuccess()
|
||||
vaultSdkSource.enrollPinWithEncryptedPin(any(), any())
|
||||
} returns enrollResponse.asSuccess()
|
||||
prepareStateForUnlocking(unlockResult = mockVaultUnlockResult)
|
||||
fakeAuthDiskSource.apply {
|
||||
storeEncryptedPin(
|
||||
@ -617,6 +638,11 @@ class VaultRepositoryTest {
|
||||
pinProtectedUserKey = null,
|
||||
inMemoryOnly = true,
|
||||
)
|
||||
storePinProtectedUserKeyEnvelope(
|
||||
userId = userId,
|
||||
pinProtectedUserKeyEnvelope = null,
|
||||
inMemoryOnly = true,
|
||||
)
|
||||
}
|
||||
|
||||
val result = vaultRepository.unlockVaultWithMasterPassword(
|
||||
@ -642,7 +668,7 @@ class VaultRepositoryTest {
|
||||
organizationKeys = createMockOrganizationKeys(number = 1),
|
||||
)
|
||||
}
|
||||
coVerify(exactly = 0) { vaultSdkSource.derivePinProtectedUserKey(any(), any()) }
|
||||
coVerify(exactly = 0) { vaultSdkSource.enrollPinWithEncryptedPin(any(), any()) }
|
||||
}
|
||||
|
||||
@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`() =
|
||||
runTest {
|
||||
val userId = "mockId-1"
|
||||
val encryptedPin = "encryptedPin"
|
||||
val pinProtectedUserKey = "pinProtectedUserkey"
|
||||
val pinProtectedUserKey = "pinProtectedUserkeyEnvelope"
|
||||
val encryptedPin = "userKeyEncryptedPin"
|
||||
val enrollResponse = EnrollPinResponse(
|
||||
pinProtectedUserKeyEnvelope = pinProtectedUserKey,
|
||||
userKeyEncryptedPin = encryptedPin,
|
||||
)
|
||||
val mockVaultUnlockResult = VaultUnlockResult.Success
|
||||
coEvery {
|
||||
vaultSdkSource.derivePinProtectedUserKey(
|
||||
vaultSdkSource.enrollPinWithEncryptedPin(
|
||||
userId = userId,
|
||||
encryptedPin = encryptedPin,
|
||||
)
|
||||
} returns pinProtectedUserKey.asSuccess()
|
||||
} returns enrollResponse.asSuccess()
|
||||
prepareStateForUnlocking(unlockResult = mockVaultUnlockResult)
|
||||
fakeAuthDiskSource.apply {
|
||||
storeEncryptedPin(
|
||||
@ -670,6 +700,11 @@ class VaultRepositoryTest {
|
||||
pinProtectedUserKey = null,
|
||||
inMemoryOnly = true,
|
||||
)
|
||||
storePinProtectedUserKeyEnvelope(
|
||||
userId = userId,
|
||||
pinProtectedUserKeyEnvelope = null,
|
||||
inMemoryOnly = true,
|
||||
)
|
||||
}
|
||||
|
||||
val result = vaultRepository.unlockVaultWithMasterPassword(
|
||||
@ -682,7 +717,12 @@ class VaultRepositoryTest {
|
||||
)
|
||||
fakeAuthDiskSource.assertPinProtectedUserKey(
|
||||
userId = userId,
|
||||
pinProtectedUserKey = pinProtectedUserKey,
|
||||
pinProtectedUserKey = null,
|
||||
inMemoryOnly = true,
|
||||
)
|
||||
fakeAuthDiskSource.assertPinProtectedUserKeyEnvelope(
|
||||
userId = userId,
|
||||
pinProtectedUserKeyEnvelope = pinProtectedUserKey,
|
||||
inMemoryOnly = true,
|
||||
)
|
||||
coVerify {
|
||||
@ -701,7 +741,7 @@ class VaultRepositoryTest {
|
||||
)
|
||||
}
|
||||
coEvery {
|
||||
vaultSdkSource.derivePinProtectedUserKey(
|
||||
vaultSdkSource.enrollPinWithEncryptedPin(
|
||||
userId = userId,
|
||||
encryptedPin = encryptedPin,
|
||||
)
|
||||
@ -762,6 +802,10 @@ class VaultRepositoryTest {
|
||||
userId = "mockId-1",
|
||||
pinProtectedUserKey = null,
|
||||
)
|
||||
fakeAuthDiskSource.storePinProtectedUserKeyEnvelope(
|
||||
userId = "mockId-1",
|
||||
pinProtectedUserKeyEnvelope = null,
|
||||
)
|
||||
fakeAuthDiskSource.storePrivateKey(
|
||||
userId = "mockId-1",
|
||||
privateKey = "mockPrivateKey-1",
|
||||
@ -780,6 +824,10 @@ class VaultRepositoryTest {
|
||||
userId = "mockId-1",
|
||||
pinProtectedUserKey = "mockKey-1",
|
||||
)
|
||||
fakeAuthDiskSource.storePinProtectedUserKeyEnvelope(
|
||||
userId = "mockId-1",
|
||||
pinProtectedUserKeyEnvelope = null,
|
||||
)
|
||||
fakeAuthDiskSource.storePrivateKey(
|
||||
userId = "mockId-1",
|
||||
privateKey = null,
|
||||
@ -801,6 +849,41 @@ class VaultRepositoryTest {
|
||||
|
||||
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(
|
||||
mockVaultUnlockResult,
|
||||
result,
|
||||
@ -844,9 +927,9 @@ class VaultRepositoryTest {
|
||||
privateKey = "mockPrivateKey-1",
|
||||
signingKey = null,
|
||||
securityState = null,
|
||||
initUserCryptoMethod = InitUserCryptoMethod.Pin(
|
||||
initUserCryptoMethod = InitUserCryptoMethod.PinEnvelope(
|
||||
pin = "1234",
|
||||
pinProtectedUserKey = "mockKey-1",
|
||||
pinProtectedUserKeyEnvelope = "mockKey-1",
|
||||
),
|
||||
organizationKeys = createMockOrganizationKeys(number = 1),
|
||||
)
|
||||
@ -1432,6 +1515,10 @@ class VaultRepositoryTest {
|
||||
userId = userId,
|
||||
pinProtectedUserKey = "mockKey-1",
|
||||
)
|
||||
fakeAuthDiskSource.storePinProtectedUserKeyEnvelope(
|
||||
userId = userId,
|
||||
pinProtectedUserKeyEnvelope = "mockKey-1",
|
||||
)
|
||||
fakeAuthDiskSource.storeOrganizationKeys(
|
||||
userId = userId,
|
||||
organizationKeys = createMockOrganizationKeys(number = 1),
|
||||
@ -1471,6 +1558,23 @@ class VaultRepositoryTest {
|
||||
organizationKeys = createMockOrganizationKeys(number = 1),
|
||||
)
|
||||
} 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
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user