use native biomatrics on Android

This commit is contained in:
Kyle Spearrin 2019-10-23 09:11:48 -04:00
parent aed3ec5474
commit 4b989b01e9
11 changed files with 199 additions and 52 deletions

View File

@ -8,9 +8,11 @@ using Android.App;
using Android.App.Assist;
using Android.Content;
using Android.Content.PM;
using Android.Hardware.Biometrics;
using Android.Nfc;
using Android.OS;
using Android.Provider;
using Android.Runtime;
using Android.Support.V4.App;
using Android.Support.V4.Content;
using Android.Text;
@ -28,6 +30,7 @@ using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Bit.Droid.Autofill;
using Plugin.CurrentActivity;
using Plugin.Fingerprint;
namespace Bit.Droid.Services
{
@ -335,11 +338,72 @@ namespace Bit.Droid.Services
Application.Context.PackageName, 0).VersionCode.ToString();
}
public bool SupportsFaceId()
public bool SupportsFaceBiometric()
{
return false;
}
public Task<bool> SupportsFaceBiometricAsync()
{
return Task.FromResult(SupportsFaceBiometric());
}
public async Task<bool> BiometricAvailableAsync()
{
if(UseNativeBiometric())
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var manager = activity.GetSystemService(Context.BiometricService) as BiometricManager;
return manager.CanAuthenticate() == BiometricCode.Success;
}
else
{
try
{
return await CrossFingerprint.Current.IsAvailableAsync();
}
catch
{
return false;
}
}
}
public bool UseNativeBiometric()
{
return (int)Build.VERSION.SdkInt >= 28;
}
public Task<bool> AuthenticateBiometricAsync(string text = null)
{
if(string.IsNullOrWhiteSpace(text))
{
text = AppResources.BiometricDirection;
}
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
using(var builder = new BiometricPrompt.Builder(activity))
{
builder.SetTitle(text);
builder.SetConfirmationRequired(false);
builder.SetNegativeButton(AppResources.Cancel, activity.MainExecutor,
new DialogInterfaceOnClickListener
{
Clicked = () => { }
});
var prompt = builder.Build();
var result = new TaskCompletionSource<bool>();
prompt.Authenticate(new CancellationSignal(), activity.MainExecutor,
new BiometricAuthenticationCallback
{
Success = authResult => result.TrySetResult(true),
Failed = () => result.TrySetResult(false),
Help = (helpCode, helpString) => { }
});
return result.Task;
}
}
public bool SupportsNfc()
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
@ -715,5 +779,41 @@ namespace Bit.Droid.Services
Context.ClipboardService) as Android.Content.ClipboardManager;
clipboardManager.Text = text;
}
private class BiometricAuthenticationCallback : BiometricPrompt.AuthenticationCallback
{
public Action<BiometricPrompt.AuthenticationResult> Success { get; set; }
public Action Failed { get; set; }
public Action<BiometricAcquiredStatus, Java.Lang.ICharSequence> Help { get; set; }
public override void OnAuthenticationSucceeded(BiometricPrompt.AuthenticationResult authResult)
{
base.OnAuthenticationSucceeded(authResult);
Success?.Invoke(authResult);
}
public override void OnAuthenticationFailed()
{
base.OnAuthenticationFailed();
Failed?.Invoke();
}
public override void OnAuthenticationHelp([GeneratedEnum] BiometricAcquiredStatus helpCode,
Java.Lang.ICharSequence helpString)
{
base.OnAuthenticationHelp(helpCode, helpString);
Help?.Invoke(helpCode, helpString);
}
}
private class DialogInterfaceOnClickListener : Java.Lang.Object, IDialogInterfaceOnClickListener
{
public Action Clicked { get; set; }
public void OnClick(IDialogInterface dialog, int which)
{
Clicked?.Invoke();
}
}
}
}

View File

@ -20,7 +20,11 @@ namespace Bit.App.Abstractions
string okButtonText = null, string cancelButtonText = null, bool numericKeyboard = false,
bool autofocus = true);
void RateApp();
bool SupportsFaceId();
bool SupportsFaceBiometric();
Task<bool> SupportsFaceBiometricAsync();
Task<bool> BiometricAvailableAsync();
bool UseNativeBiometric();
Task<bool> AuthenticateBiometricAsync(string text = null);
bool SupportsNfc();
bool SupportsCamera();
bool SupportsAutofillService();

View File

@ -126,7 +126,8 @@ namespace Bit.App.Pages
if(FingerprintLock)
{
FingerprintButtonText = _deviceActionService.SupportsFaceId() ? AppResources.UseFaceIDToUnlock :
var supportsFace = await _deviceActionService.SupportsFaceBiometricAsync();
FingerprintButtonText = supportsFace ? AppResources.UseFaceIDToUnlock :
AppResources.UseFingerprintToUnlock;
if(autoPromptFingerprint)
{
@ -266,7 +267,7 @@ namespace Bit.App.Pages
{
return;
}
var success = await _platformUtilsService.AuthenticateFingerprintAsync(null,
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
PinLock ? AppResources.PIN : AppResources.MasterPassword, () =>
{
var page = Page as LockPage;

View File

@ -142,8 +142,8 @@ namespace Bit.App.Pages
var fingerprintName = AppResources.Fingerprint;
if(Device.RuntimePlatform == Device.iOS)
{
fingerprintName = _deviceActionService.SupportsFaceId() ?
AppResources.FaceID : AppResources.TouchID;
var supportsFace = await _deviceActionService.SupportsFaceBiometricAsync();
fingerprintName = supportsFace ? AppResources.FaceID : AppResources.TouchID;
}
if(item.Name == string.Format(AppResources.UnlockWith, fingerprintName))
{

View File

@ -62,7 +62,7 @@ namespace Bit.App.Pages
public async Task InitAsync()
{
_supportsFingerprint = await _platformUtilsService.SupportsFingerprintAsync();
_supportsFingerprint = await _platformUtilsService.SupportsBiometricAsync();
var lastSync = await _syncService.GetLastSyncAsync();
if(lastSync != null)
{
@ -255,9 +255,9 @@ namespace Bit.App.Pages
{
_fingerprint = false;
}
else if(await _platformUtilsService.SupportsFingerprintAsync())
else if(await _platformUtilsService.SupportsBiometricAsync())
{
_fingerprint = await _platformUtilsService.AuthenticateFingerprintAsync(null,
_fingerprint = await _platformUtilsService.AuthenticateBiometricAsync(null,
_deviceActionService.DeviceType == Core.Enums.DeviceType.Android ? "." : null);
}
if(_fingerprint == current)
@ -328,8 +328,8 @@ namespace Bit.App.Pages
var fingerprintName = AppResources.Fingerprint;
if(Device.RuntimePlatform == Device.iOS)
{
fingerprintName = _deviceActionService.SupportsFaceId() ?
AppResources.FaceID : AppResources.TouchID;
fingerprintName = _deviceActionService.SupportsFaceBiometric() ? AppResources.FaceID :
AppResources.TouchID;
}
var item = new SettingsPageListItem
{

View File

@ -537,6 +537,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Use biometrics to verify..
/// </summary>
public static string BiometricDirection {
get {
return ResourceManager.GetString("BiometricDirection", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Bitwarden.
/// </summary>

View File

@ -1578,4 +1578,7 @@
<data name="LoginExpired" xml:space="preserve">
<value>Your login session has expired.</value>
</data>
<data name="BiometricDirection" xml:space="preserve">
<value>Use biometrics to verify.</value>
</data>
</root>

View File

@ -197,45 +197,45 @@ namespace Bit.App.Services
return await Clipboard.GetTextAsync();
}
public async Task<bool> SupportsFingerprintAsync()
public async Task<bool> SupportsBiometricAsync()
{
try
return await _deviceActionService.BiometricAvailableAsync();
}
public async Task<bool> AuthenticateBiometricAsync(string text = null, string fallbackText = null,
Action fallback = null)
{
if(_deviceActionService.UseNativeBiometric())
{
return await CrossFingerprint.Current.IsAvailableAsync();
return await _deviceActionService.AuthenticateBiometricAsync(text);
}
catch
else
{
try
{
if(text == null)
{
var supportsFace = await _deviceActionService.SupportsFaceBiometricAsync();
text = supportsFace ? AppResources.FaceIDDirection : AppResources.FingerprintDirection;
}
var fingerprintRequest = new AuthenticationRequestConfiguration(text)
{
CancelTitle = AppResources.Cancel,
FallbackTitle = fallbackText
};
var result = await CrossFingerprint.Current.AuthenticateAsync(fingerprintRequest);
if(result.Authenticated)
{
return true;
}
else if(result.Status == FingerprintAuthenticationResultStatus.FallbackRequested)
{
fallback?.Invoke();
}
}
catch { }
return false;
}
}
public async Task<bool> AuthenticateFingerprintAsync(string text = null, string fallbackText = null,
Action fallback = null)
{
try
{
if(text == null)
{
text = _deviceActionService.SupportsFaceId() ? AppResources.FaceIDDirection :
AppResources.FingerprintDirection;
}
var fingerprintRequest = new AuthenticationRequestConfiguration(text)
{
CancelTitle = AppResources.Cancel,
FallbackTitle = fallbackText
};
var result = await CrossFingerprint.Current.AuthenticateAsync(fingerprintRequest);
if(result.Authenticated)
{
return true;
}
else if(result.Status == FingerprintAuthenticationResultStatus.FallbackRequested)
{
fallback?.Invoke();
}
}
catch { }
return false;
}
}
}

View File

@ -26,7 +26,7 @@ namespace Bit.Core.Abstractions
void ShowToast(string type, string title, string[] text, Dictionary<string, object> options = null);
bool SupportsU2f();
bool SupportsDuo();
Task<bool> SupportsFingerprintAsync();
Task<bool> AuthenticateFingerprintAsync(string text = null, string fallbackText = null, Action fallback = null);
Task<bool> SupportsBiometricAsync();
Task<bool> AuthenticateBiometricAsync(string text = null, string fallbackText = null, Action fallback = null);
}
}

View File

@ -216,7 +216,7 @@ namespace Bit.iOS.Core.Controllers
{
return;
}
var success = await _platformUtilsService.AuthenticateFingerprintAsync(null,
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
_pinLock ? AppResources.PIN : AppResources.MasterPassword,
() => MasterPasswordCell.TextField.BecomeFirstResponder());
_lockService.FingerprintLocked = !success;
@ -261,7 +261,7 @@ namespace Bit.iOS.Core.Controllers
{
if(indexPath.Row == 0)
{
var fingerprintButtonText = _controller._deviceActionService.SupportsFaceId() ?
var fingerprintButtonText = _controller._deviceActionService.SupportsFaceBiometric() ?
AppResources.UseFaceIDToUnlock : AppResources.UseFingerprintToUnlock;
var cell = new ExtendedUITableViewCell();
cell.TextLabel.TextColor = ThemeHelpers.PrimaryColor;

View File

@ -15,6 +15,7 @@ using Foundation;
using LocalAuthentication;
using MobileCoreServices;
using Photos;
using Plugin.Fingerprint;
using UIKit;
using Xamarin.Forms;
@ -243,18 +244,47 @@ namespace Bit.iOS.Core.Services
Device.OpenUri(new Uri(uri));
}
public bool SupportsFaceId()
public bool SupportsFaceBiometric()
{
if(SystemMajorVersion() < 11)
{
return false;
}
var context = new LAContext();
if(!context.CanEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, out NSError e))
using(var context = new LAContext())
{
if(!context.CanEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, out var e))
{
return false;
}
return context.BiometryType == LABiometryType.FaceId;
}
}
public Task<bool> SupportsFaceBiometricAsync()
{
return Task.FromResult(SupportsFaceBiometric());
}
public async Task<bool> BiometricAvailableAsync()
{
try
{
return await CrossFingerprint.Current.IsAvailableAsync();
}
catch
{
return false;
}
return context.BiometryType == LABiometryType.FaceId;
}
public bool UseNativeBiometric()
{
return false;
}
public Task<bool> AuthenticateBiometricAsync(string text = null)
{
throw new NotSupportedException();
}
public bool SupportsNfc()