mirror of
https://github.com/bitwarden/android.git
synced 2025-12-10 20:07:59 -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].
|
* 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].
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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) },
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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].
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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].
|
||||||
|
|||||||
@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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].
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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) }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user