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();