From 33791a03acd24debc36a15aa50cb3bac6fc8294d Mon Sep 17 00:00:00 2001 From: Matt Portune <59324545+mportune-bw@users.noreply.github.com> Date: Wed, 9 Jun 2021 10:03:05 -0400 Subject: [PATCH] track failed unlock attempts in storage (#1421) --- src/App/Pages/Accounts/LockPageViewModel.cs | 15 ++++-- src/App/Pages/Accounts/LoginPageViewModel.cs | 2 + .../Pages/Accounts/LoginSsoPageViewModel.cs | 2 + src/App/Services/MobileStorageService.cs | 1 + src/App/Utilities/AppHelpers.cs | 15 ++++++ src/Core/Constants.cs | 1 + .../Controllers/LockPasswordViewController.cs | 53 +++++++++++++++++-- 7 files changed, 82 insertions(+), 7 deletions(-) diff --git a/src/App/Pages/Accounts/LockPageViewModel.cs b/src/App/Pages/Accounts/LockPageViewModel.cs index b4c6df68d..fcd148858 100644 --- a/src/App/Pages/Accounts/LockPageViewModel.cs +++ b/src/App/Pages/Accounts/LockPageViewModel.cs @@ -8,6 +8,7 @@ using Bit.Core.Models.Domain; using Bit.Core.Utilities; using System; using System.Threading.Tasks; +using Bit.App.Utilities; using Bit.Core.Models.Request; using Xamarin.Forms; @@ -37,7 +38,6 @@ namespace Bit.App.Pages private string _biometricButtonText; private string _loggedInAsText; private string _lockedVerifyText; - private int _invalidPinAttempts = 0; private Tuple _pinSet; public LockPageViewModel() @@ -208,6 +208,7 @@ namespace Bit.App.Pages if (!failed) { Pin = string.Empty; + await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await SetKeyAndContinueAsync(key); } } @@ -217,6 +218,7 @@ namespace Bit.App.Pages kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000)); failed = false; Pin = string.Empty; + await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await SetKeyAndContinueAsync(key); } } @@ -226,8 +228,8 @@ namespace Bit.App.Pages } if (failed) { - _invalidPinAttempts++; - if (_invalidPinAttempts >= 5) + var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync(); + if (invalidUnlockAttempts >= 5) { _messagingService.Send("logout"); return; @@ -278,6 +280,7 @@ namespace Bit.App.Pages _vaultTimeoutService.PinProtectedKey = await _cryptoService.EncryptAsync(key.Key, pinKey); } MasterPassword = string.Empty; + await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await SetKeyAndContinueAsync(key); // Re-enable biometrics @@ -288,6 +291,12 @@ namespace Bit.App.Pages } else { + var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync(); + if (invalidUnlockAttempts >= 5) + { + _messagingService.Send("logout"); + return; + } await _platformUtilsService.ShowDialogAsync(AppResources.InvalidMasterPassword, AppResources.AnErrorHasOccurred); } diff --git a/src/App/Pages/Accounts/LoginPageViewModel.cs b/src/App/Pages/Accounts/LoginPageViewModel.cs index 5b0c5b5ba..29c99304e 100644 --- a/src/App/Pages/Accounts/LoginPageViewModel.cs +++ b/src/App/Pages/Accounts/LoginPageViewModel.cs @@ -6,6 +6,7 @@ using Bit.Core.Exceptions; using Bit.Core.Utilities; using System; using System.Threading.Tasks; +using Bit.App.Utilities; using Xamarin.Forms; namespace Bit.App.Pages @@ -125,6 +126,7 @@ namespace Bit.App.Pages { await _storageService.RemoveAsync(Keys_RememberedEmail); } + await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await _deviceActionService.HideLoadingAsync(); if (response.TwoFactor) { diff --git a/src/App/Pages/Accounts/LoginSsoPageViewModel.cs b/src/App/Pages/Accounts/LoginSsoPageViewModel.cs index 9a733091f..24c3ea763 100644 --- a/src/App/Pages/Accounts/LoginSsoPageViewModel.cs +++ b/src/App/Pages/Accounts/LoginSsoPageViewModel.cs @@ -5,6 +5,7 @@ using Bit.Core.Abstractions; using Bit.Core.Utilities; using System; using System.Threading.Tasks; +using Bit.App.Utilities; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.Domain; @@ -182,6 +183,7 @@ namespace Bit.App.Pages try { var response = await _authService.LogInSsoAsync(code, codeVerifier, redirectUri); + await AppHelpers.ResetInvalidUnlockAttemptsAsync(); if (RememberOrgIdentifier) { await _storageService.SaveAsync(Keys_RememberedOrgIdentifier, OrgIdentifier); diff --git a/src/App/Services/MobileStorageService.cs b/src/App/Services/MobileStorageService.cs index b43550a8e..d1fb15cc5 100644 --- a/src/App/Services/MobileStorageService.cs +++ b/src/App/Services/MobileStorageService.cs @@ -39,6 +39,7 @@ namespace Bit.App.Services Constants.iOSExtensionBiometricIntegrityKey, Constants.EnvironmentUrlsKey, Constants.InlineAutofillEnabledKey, + Constants.InvalidUnlockAttempts, }; private readonly HashSet _migrateToPreferences = new HashSet diff --git a/src/App/Utilities/AppHelpers.cs b/src/App/Utilities/AppHelpers.cs index 2366a4120..20c06ca91 100644 --- a/src/App/Utilities/AppHelpers.cs +++ b/src/App/Utilities/AppHelpers.cs @@ -440,5 +440,20 @@ namespace Bit.App.Utilities } return previousPage; } + + public static async Task IncrementInvalidUnlockAttemptsAsync() + { + var storageService = ServiceContainer.Resolve("storageService"); + var invalidUnlockAttempts = await storageService.GetAsync(Constants.InvalidUnlockAttempts); + invalidUnlockAttempts++; + await storageService.SaveAsync(Constants.InvalidUnlockAttempts, invalidUnlockAttempts); + return invalidUnlockAttempts; + } + + public static async Task ResetInvalidUnlockAttemptsAsync() + { + var storageService = ServiceContainer.Resolve("storageService"); + await storageService.RemoveAsync(Constants.InvalidUnlockAttempts); + } } } diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index 3ef3529f6..3e4bcfc55 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -40,6 +40,7 @@ public static string EventCollectionKey = "eventCollection"; public static string PreviousPageKey = "previousPage"; public static string InlineAutofillEnabledKey = "inlineAutofillEnabled"; + public static string InvalidUnlockAttempts = "invalidUnlockAttempts"; public const int SelectFileRequestCode = 42; public const int SelectFilePermissionRequestCode = 43; public const int SaveFileRequestCode = 44; diff --git a/src/iOS.Core/Controllers/LockPasswordViewController.cs b/src/iOS.Core/Controllers/LockPasswordViewController.cs index a23cfcb15..4c0a59b9c 100644 --- a/src/iOS.Core/Controllers/LockPasswordViewController.cs +++ b/src/iOS.Core/Controllers/LockPasswordViewController.cs @@ -8,6 +8,7 @@ using Bit.App.Abstractions; using Bit.Core.Abstractions; using Bit.Core.Utilities; using System.Threading.Tasks; +using Bit.App.Utilities; using Bit.Core.Models.Domain; using Bit.Core.Enums; @@ -27,7 +28,6 @@ namespace Bit.iOS.Core.Controllers private bool _pinLock; private bool _biometricLock; private bool _biometricIntegrityValid = true; - private int _invalidPinAttempts; public LockPasswordViewController(IntPtr handle) : base(handle) @@ -144,6 +144,7 @@ namespace Bit.iOS.Core.Controllers failed = decPin != inputtedValue; if (!failed) { + await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await SetKeyAndContinueAsync(key); } } @@ -152,6 +153,7 @@ namespace Bit.iOS.Core.Controllers var key2 = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email, kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000)); failed = false; + await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await SetKeyAndContinueAsync(key2); } } @@ -161,10 +163,10 @@ namespace Bit.iOS.Core.Controllers } if (failed) { - _invalidPinAttempts++; - if (_invalidPinAttempts >= 5) + var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync(); + if (invalidUnlockAttempts >= 5) { - Cancel?.Invoke(); + await LogOutAsync(); return; } InvalidValue(); @@ -196,6 +198,7 @@ namespace Bit.iOS.Core.Controllers kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000)); _vaultTimeoutService.PinProtectedKey = await _cryptoService.EncryptAsync(key2.Key, pinKey); } + await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await SetKeyAndContinueAsync(key2); // Re-enable biometrics @@ -206,6 +209,12 @@ namespace Bit.iOS.Core.Controllers } else { + var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync(); + if (invalidUnlockAttempts >= 5) + { + await LogOutAsync(); + return; + } InvalidValue(); } } @@ -256,6 +265,42 @@ namespace Bit.iOS.Core.Controllers }); PresentViewController(alert, true, null); } + + private async Task LogOutAsync() + { + var syncService = ServiceContainer.Resolve("syncService"); + var tokenService = ServiceContainer.Resolve("tokenService"); + var settingsService = ServiceContainer.Resolve("settingsService"); + var cipherService = ServiceContainer.Resolve("cipherService"); + var folderService = ServiceContainer.Resolve("folderService"); + var collectionService = ServiceContainer.Resolve("collectionService"); + var passwordGenerationService = ServiceContainer.Resolve( + "passwordGenerationService"); + var stateService = ServiceContainer.Resolve("stateService"); + var searchService = ServiceContainer.Resolve("searchService"); + var authService = ServiceContainer.Resolve("authService"); + + var userId = await _userService.GetUserIdAsync(); + await Task.WhenAll( + syncService.SetLastSyncAsync(DateTime.MinValue), + tokenService.ClearTokenAsync(), + _cryptoService.ClearKeysAsync(), + _userService.ClearAsync(), + settingsService.ClearAsync(userId), + cipherService.ClearAsync(userId), + folderService.ClearAsync(userId), + collectionService.ClearAsync(userId), + passwordGenerationService.ClearAsync(), + _vaultTimeoutService.ClearAsync(), + stateService.PurgeAsync(), + _deviceActionService.ClearCacheAsync()); + _vaultTimeoutService.BiometricLocked = true; + searchService.ClearIndex(); + authService.LogOut(() => + { + Cancel?.Invoke(); + }); + } public class TableSource : ExtendedUITableViewSource {