diff --git a/src/Core/Abstractions/IUserVerificationMediatorService.cs b/src/Core/Abstractions/IUserVerificationMediatorService.cs index c2fa5431a..873d297bd 100644 --- a/src/Core/Abstractions/IUserVerificationMediatorService.cs +++ b/src/Core/Abstractions/IUserVerificationMediatorService.cs @@ -9,6 +9,6 @@ namespace Bit.Core.Abstractions Task ShouldPerformMasterPasswordRepromptAsync(Fido2UserVerificationOptions options); Task<(bool CanPerfom, bool IsUnlocked)> PerformOSUnlockAsync(); Task<(bool canPerformUnlockWithPin, bool pinVerified)> VerifyPinCodeAsync(); - Task<(bool canPerformUnlockWithMasterPassword, bool mpVerified)> VerifyMasterPasswordAsync(); + Task<(bool canPerformUnlockWithMasterPassword, bool mpVerified)> VerifyMasterPasswordAsync(bool isMasterPasswordReprompt); } } diff --git a/src/Core/Services/UserVerification/Fido2UserVerificationRequiredServiceStrategy.cs b/src/Core/Services/UserVerification/Fido2UserVerificationRequiredServiceStrategy.cs index 592803045..4aca3fe73 100644 --- a/src/Core/Services/UserVerification/Fido2UserVerificationRequiredServiceStrategy.cs +++ b/src/Core/Services/UserVerification/Fido2UserVerificationRequiredServiceStrategy.cs @@ -40,7 +40,7 @@ namespace Bit.Core.Services.UserVerification return pinVerified; } - var (canPerformUnlockWithMasterPassword, mpVerified) = await _userVerificationMediatorService.VerifyMasterPasswordAsync(); + var (canPerformUnlockWithMasterPassword, mpVerified) = await _userVerificationMediatorService.VerifyMasterPasswordAsync(false); if (canPerformUnlockWithMasterPassword) { return mpVerified; diff --git a/src/Core/Services/UserVerification/UserVerificationMediatorService.cs b/src/Core/Services/UserVerification/UserVerificationMediatorService.cs index 3fc13be1b..5864969d1 100644 --- a/src/Core/Services/UserVerification/UserVerificationMediatorService.cs +++ b/src/Core/Services/UserVerification/UserVerificationMediatorService.cs @@ -10,6 +10,8 @@ namespace Bit.Core.Services.UserVerification { public class UserVerificationMediatorService : IUserVerificationMediatorService { + private const byte MAX_ATTEMPTS = 5; + private readonly IPlatformUtilsService _platformUtilsService; private readonly IPasswordRepromptService _passwordRepromptService; private readonly IUserPinService _userPinService; @@ -44,7 +46,8 @@ namespace Bit.Core.Services.UserVerification await options.OnNeedUITask(); } - return await _passwordRepromptService.PromptAndCheckPasswordIfNeededAsync(Enums.CipherRepromptType.Password); + var (canPerformMP, mpVerified) = await VerifyMasterPasswordAsync(true); + return canPerformMP && mpVerified; } if (!_fido2UserVerificationStrategies.TryGetValue(options.UserVerificationPreference, out var userVerificationServiceStrategy)) @@ -95,43 +98,71 @@ namespace Bit.Core.Services.UserVerification public async Task<(bool canPerformUnlockWithPin, bool pinVerified)> VerifyPinCodeAsync() { - if (!await _userPinService.IsPinLockEnabledAsync()) + return await VerifyWithAttemptsAsync(async () => { - return (false, false); - } + if (!await _userPinService.IsPinLockEnabledAsync()) + { + return (false, false); + } - var pin = await _deviceActionService.DisplayPromptAync(AppResources.EnterPIN, - AppResources.VerifyPIN, null, AppResources.Ok, AppResources.Cancel, password: true); - if (pin is null) - { - // cancelled by the user - return (true, false); - } + var pin = await _deviceActionService.DisplayPromptAync(AppResources.EnterPIN, + AppResources.VerifyPIN, null, AppResources.Ok, AppResources.Cancel, password: true); + if (pin is null) + { + // cancelled by the user + return (true, false); + } - try - { - var isVerified = await _userPinService.VerifyPinAsync(pin); - return (true, isVerified); - } - catch (SymmetricCryptoKey.ArgumentKeyNullException) - { - return (true, false); - } - catch (SymmetricCryptoKey.InvalidKeyOperationException) - { - return (true, false); - } + try + { + var isVerified = await _userPinService.VerifyPinAsync(pin); + return (true, isVerified); + } + catch (SymmetricCryptoKey.ArgumentKeyNullException) + { + return (true, false); + } + catch (SymmetricCryptoKey.InvalidKeyOperationException) + { + return (true, false); + } + }); } - public async Task<(bool canPerformUnlockWithMasterPassword, bool mpVerified)> VerifyMasterPasswordAsync() + public async Task<(bool canPerformUnlockWithMasterPassword, bool mpVerified)> VerifyMasterPasswordAsync(bool isMasterPasswordReprompt) { - if (!await _userVerificationService.HasMasterPasswordAsync(true)) + return await VerifyWithAttemptsAsync(async () => { - return (false, false); - } + if (!await _userVerificationService.HasMasterPasswordAsync(true)) + { + return (false, false); + } - var (_, isValid) = await _platformUtilsService.ShowPasswordDialogAndGetItAsync(AppResources.MasterPassword, string.Empty, _userVerificationService.VerifyMasterPasswordAsync); - return (true, isValid); + var title = isMasterPasswordReprompt ? AppResources.PasswordConfirmation : AppResources.MasterPassword; + var body = isMasterPasswordReprompt ? AppResources.PasswordConfirmationDesc : string.Empty; + + var (_, isValid) = await _platformUtilsService.ShowPasswordDialogAndGetItAsync(title, body, _userVerificationService.VerifyMasterPasswordAsync); + return (true, isValid); + }); + } + + private async Task<(bool canPerform, bool isVerified)> VerifyWithAttemptsAsync(Func> verifyAsync) + { + byte attempts = 0; + do + { + var (canPerform, verified) = await verifyAsync(); + if (!canPerform) + { + return (false, false); + } + if (verified) + { + return (true, true); + } + } while (attempts++ < MAX_ATTEMPTS); + + return (true, false); } } }