From 7b358b1bbbe1f6008dfa06f3153b8211d8115f78 Mon Sep 17 00:00:00 2001 From: Matt Portune <59324545+mportune-bw@users.noreply.github.com> Date: Fri, 25 Sep 2020 21:14:10 -0400 Subject: [PATCH] biometric integrity check in iOS extensions (#1093) --- src/App/Pages/Accounts/LockPage.xaml | 2 +- src/App/Pages/Accounts/LockPageViewModel.cs | 17 ++++++- .../Controllers/LockPasswordViewController.cs | 46 +++++++++++++------ src/iOS.Core/Utilities/ThemeHelpers.cs | 10 ++++ 4 files changed, 59 insertions(+), 16 deletions(-) diff --git a/src/App/Pages/Accounts/LockPage.xaml b/src/App/Pages/Accounts/LockPage.xaml index 9e3bbc53a..2c9c5dcab 100644 --- a/src/App/Pages/Accounts/LockPage.xaml +++ b/src/App/Pages/Accounts/LockPage.xaml @@ -111,7 +111,7 @@ StyleClass="box-footer-label,text-danger,text-bold" IsVisible="{Binding BiometricIntegrityValid, Converter={StaticResource inverseBool}}" /> + IsVisible="{Binding BiometricButtonVisible}"> diff --git a/src/App/Pages/Accounts/LockPageViewModel.cs b/src/App/Pages/Accounts/LockPageViewModel.cs index b8aac7a6c..adcd0a960 100644 --- a/src/App/Pages/Accounts/LockPageViewModel.cs +++ b/src/App/Pages/Accounts/LockPageViewModel.cs @@ -33,6 +33,7 @@ namespace Bit.App.Pages private bool _pinLock; private bool _biometricLock; private bool _biometricIntegrityValid = true; + private bool _biometricButtonVisible = true; private string _biometricButtonText; private string _loggedInAsText; private string _lockedVerifyText; @@ -87,6 +88,12 @@ namespace Bit.App.Pages set => SetProperty(ref _biometricIntegrityValid, value); } + public bool BiometricButtonVisible + { + get => _biometricButtonVisible; + set => SetProperty(ref _biometricButtonVisible, value); + } + public string BiometricButtonText { get => _biometricButtonText; @@ -138,6 +145,13 @@ namespace Bit.App.Pages if (BiometricLock) { + BiometricIntegrityValid = await _biometricService.ValidateIntegrityAsync(); + if (!_biometricIntegrityValid) + { + BiometricButtonVisible = false; + return; + } + BiometricButtonVisible = true; BiometricButtonText = AppResources.UseBiometricsToUnlock; if (Device.RuntimePlatform == Device.iOS) { @@ -145,8 +159,7 @@ namespace Bit.App.Pages BiometricButtonText = supportsFace ? AppResources.UseFaceIDToUnlock : AppResources.UseFingerprintToUnlock; } - BiometricIntegrityValid = await _biometricService.ValidateIntegrityAsync(); - if (autoPromptBiometric & _biometricIntegrityValid) + if (autoPromptBiometric) { var tasks = Task.Run(async () => { diff --git a/src/iOS.Core/Controllers/LockPasswordViewController.cs b/src/iOS.Core/Controllers/LockPasswordViewController.cs index 12d026f87..ad6ec4ec0 100644 --- a/src/iOS.Core/Controllers/LockPasswordViewController.cs +++ b/src/iOS.Core/Controllers/LockPasswordViewController.cs @@ -22,9 +22,11 @@ namespace Bit.iOS.Core.Controllers private IStorageService _storageService; private IStorageService _secureStorageService; private IPlatformUtilsService _platformUtilsService; + private IBiometricService _biometricService; private Tuple _pinSet; private bool _pinLock; - private bool _fingerprintLock; + private bool _biometricLock; + private bool _biometricIntegrityValid = true; private int _invalidPinAttempts; public LockPasswordViewController(IntPtr handle) @@ -49,10 +51,12 @@ namespace Bit.iOS.Core.Controllers _storageService = ServiceContainer.Resolve("storageService"); _secureStorageService = ServiceContainer.Resolve("secureStorageService"); _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); + _biometricService = ServiceContainer.Resolve("biometricService"); _pinSet = _vaultTimeoutService.IsPinLockSetAsync().GetAwaiter().GetResult(); _pinLock = (_pinSet.Item1 && _vaultTimeoutService.PinProtectedKey != null) || _pinSet.Item2; - _fingerprintLock = _vaultTimeoutService.IsBiometricLockSetAsync().GetAwaiter().GetResult(); + _biometricLock = _vaultTimeoutService.IsBiometricLockSetAsync().GetAwaiter().GetResult() && + _cryptoService.HasKeyAsync().GetAwaiter().GetResult(); BaseNavItem.Title = _pinLock ? AppResources.VerifyPIN : AppResources.VerifyMasterPassword; BaseCancelButton.Title = AppResources.Cancel; @@ -80,12 +84,17 @@ namespace Bit.iOS.Core.Controllers base.ViewDidLoad(); - if (_fingerprintLock) + if (_biometricLock) { + _biometricIntegrityValid = _biometricService.ValidateIntegrityAsync().GetAwaiter().GetResult(); + if (!_biometricIntegrityValid) + { + return; + } var tasks = Task.Run(async () => { await Task.Delay(500); - NSRunLoop.Main.BeginInvokeOnMainThread(async () => await PromptFingerprintAsync()); + NSRunLoop.Main.BeginInvokeOnMainThread(async () => await PromptBiometricAsync()); }); } } @@ -93,7 +102,7 @@ namespace Bit.iOS.Core.Controllers public override void ViewDidAppear(bool animated) { base.ViewDidAppear(animated); - if (!_fingerprintLock) + if (!_biometricLock || !_biometricIntegrityValid) { MasterPasswordCell.TextField.BecomeFirstResponder(); } @@ -210,9 +219,9 @@ namespace Bit.iOS.Core.Controllers Success(); } - public async Task PromptFingerprintAsync() + public async Task PromptBiometricAsync() { - if (!_fingerprintLock) + if (!_biometricLock || !_biometricIntegrityValid) { return; } @@ -261,11 +270,22 @@ namespace Bit.iOS.Core.Controllers { if (indexPath.Row == 0) { - var biometricButtonText = _controller._deviceActionService.SupportsFaceBiometric() ? - AppResources.UseFaceIDToUnlock : AppResources.UseFingerprintToUnlock; var cell = new ExtendedUITableViewCell(); - cell.TextLabel.TextColor = ThemeHelpers.PrimaryColor; - cell.TextLabel.Text = biometricButtonText; + if (_controller._biometricIntegrityValid) + { + var biometricButtonText = _controller._deviceActionService.SupportsFaceBiometric() ? + AppResources.UseFaceIDToUnlock : AppResources.UseFingerprintToUnlock; + cell.TextLabel.TextColor = ThemeHelpers.PrimaryColor; + cell.TextLabel.Text = biometricButtonText; + } + else + { + cell.TextLabel.TextColor = ThemeHelpers.DangerColor; + cell.TextLabel.Font = ThemeHelpers.GetDangerFont(); + cell.TextLabel.Lines = 0; + cell.TextLabel.LineBreakMode = UILineBreakMode.WordWrap; + cell.TextLabel.Text = AppResources.BiometricInvalidated; + } return cell; } } @@ -279,7 +299,7 @@ namespace Bit.iOS.Core.Controllers public override nint NumberOfSections(UITableView tableView) { - return _controller._fingerprintLock ? 2 : 1; + return _controller._biometricLock ? 2 : 1; } public override nint RowsInSection(UITableView tableview, nint section) @@ -307,7 +327,7 @@ namespace Bit.iOS.Core.Controllers tableView.EndEditing(true); if (indexPath.Section == 1 && indexPath.Row == 0) { - var task = _controller.PromptFingerprintAsync(); + var task = _controller.PromptBiometricAsync(); return; } var cell = tableView.CellAt(indexPath); diff --git a/src/iOS.Core/Utilities/ThemeHelpers.cs b/src/iOS.Core/Utilities/ThemeHelpers.cs index 2f94ed8d2..5535057b1 100644 --- a/src/iOS.Core/Utilities/ThemeHelpers.cs +++ b/src/iOS.Core/Utilities/ThemeHelpers.cs @@ -11,6 +11,7 @@ namespace Bit.iOS.Core.Utilities public static UIColor BackgroundColor = Xamarin.Forms.Color.FromHex("#ffffff").ToUIColor(); public static UIColor MutedColor = Xamarin.Forms.Color.FromHex("#777777").ToUIColor(); public static UIColor SuccessColor = Xamarin.Forms.Color.FromHex("#00a65a").ToUIColor(); + public static UIColor DangerColor = Xamarin.Forms.Color.FromHex("dd4b39").ToUIColor(); public static UIColor PrimaryColor = Xamarin.Forms.Color.FromHex("#175DDC").ToUIColor(); public static UIColor TextColor = Xamarin.Forms.Color.FromHex("#000000").ToUIColor(); public static UIColor PlaceholderColor = Xamarin.Forms.Color.FromHex("#d0d0d0").ToUIColor(); @@ -55,6 +56,12 @@ namespace Bit.iOS.Core.Utilities UIButton.Appearance.TintColor = TextColor; UILabel.AppearanceWhenContainedIn(typeof(UITableViewHeaderFooterView)).TextColor = MutedColor; } + + public static UIFont GetDangerFont() + { + return Xamarin.Forms.Font.SystemFontOfSize(Xamarin.Forms.NamedSize.Small, + Xamarin.Forms.FontAttributes.Bold).ToUIFont(); + } private static void SetThemeVariables(string theme) { @@ -69,6 +76,7 @@ namespace Bit.iOS.Core.Utilities var whiteColor = Xamarin.Forms.Color.FromHex("#ffffff").ToUIColor(); MutedColor = Xamarin.Forms.Color.FromHex("#a3a3a3").ToUIColor(); SuccessColor = Xamarin.Forms.Color.FromHex("#00a65a").ToUIColor(); + DangerColor = Xamarin.Forms.Color.FromHex("ff3e24").ToUIColor(); BackgroundColor = Xamarin.Forms.Color.FromHex("#303030").ToUIColor(); SplashBackgroundColor = Xamarin.Forms.Color.FromHex("#222222").ToUIColor(); PrimaryColor = Xamarin.Forms.Color.FromHex("#52bdfb").ToUIColor(); @@ -85,6 +93,7 @@ namespace Bit.iOS.Core.Utilities var whiteColor = Xamarin.Forms.Color.FromHex("#ffffff").ToUIColor(); MutedColor = Xamarin.Forms.Color.FromHex("#a3a3a3").ToUIColor(); SuccessColor = Xamarin.Forms.Color.FromHex("#00a65a").ToUIColor(); + DangerColor = Xamarin.Forms.Color.FromHex("ff3e24").ToUIColor(); BackgroundColor = blackColor; SplashBackgroundColor = blackColor; PrimaryColor = Xamarin.Forms.Color.FromHex("#52bdfb").ToUIColor(); @@ -99,6 +108,7 @@ namespace Bit.iOS.Core.Utilities { MutedColor = Xamarin.Forms.Color.FromHex("#d8dee9").ToUIColor(); SuccessColor = Xamarin.Forms.Color.FromHex("#a3be8c").ToUIColor(); + DangerColor = Xamarin.Forms.Color.FromHex("bf616a").ToUIColor(); BackgroundColor = Xamarin.Forms.Color.FromHex("#3b4252").ToUIColor(); SplashBackgroundColor = Xamarin.Forms.Color.FromHex("#2e3440").ToUIColor(); PrimaryColor = Xamarin.Forms.Color.FromHex("#81a1c1").ToUIColor();