mirror of
https://github.com/bitwarden/android.git
synced 2025-01-12 11:17:30 +03:00
Invalidate biometric on change (#1026)
* Initial working version for Android * Add a fallback for when upgrading from older app version. * Ensure biometric validity is re-checked on focus * Only setup biometric integrity key if biometric is turned on. * Fix styling according to comments * Fallback for Android 5. * Improve comment * Add boilerplate for iOS * Change BiometricService to public * Untested iOS implementation. * Convert IBiometricService to async. Fix code style for iOS. * Base64 NSData. * Review comments for Android BiometricService. * Rename methods in BiometricService to append Async * Ensure we wait for async SetupBiometricAsync. * Update BiometricService.cs Co-authored-by: Kyle Spearrin <kspearrin@users.noreply.github.com>
This commit is contained in:
parent
39de2c1d25
commit
ae28de4159
12 changed files with 218 additions and 20 deletions
|
@ -133,6 +133,7 @@
|
||||||
<Compile Include="MainActivity.cs" />
|
<Compile Include="MainActivity.cs" />
|
||||||
<Compile Include="Resources\Resource.designer.cs" />
|
<Compile Include="Resources\Resource.designer.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
<Compile Include="Services\BiometricService.cs" />
|
||||||
<Compile Include="Services\CryptoPrimitiveService.cs" />
|
<Compile Include="Services\CryptoPrimitiveService.cs" />
|
||||||
<Compile Include="Services\DeviceActionService.cs" />
|
<Compile Include="Services\DeviceActionService.cs" />
|
||||||
<Compile Include="Services\LocalizeService.cs" />
|
<Compile Include="Services\LocalizeService.cs" />
|
||||||
|
|
|
@ -98,6 +98,7 @@ namespace Bit.Droid
|
||||||
broadcasterService, () => ServiceContainer.Resolve<IEventService>("eventService"));
|
broadcasterService, () => ServiceContainer.Resolve<IEventService>("eventService"));
|
||||||
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, messagingService,
|
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, messagingService,
|
||||||
broadcasterService);
|
broadcasterService);
|
||||||
|
var biometricService = new BiometricService();
|
||||||
|
|
||||||
ServiceContainer.Register<IBroadcasterService>("broadcasterService", broadcasterService);
|
ServiceContainer.Register<IBroadcasterService>("broadcasterService", broadcasterService);
|
||||||
ServiceContainer.Register<IMessagingService>("messagingService", messagingService);
|
ServiceContainer.Register<IMessagingService>("messagingService", messagingService);
|
||||||
|
@ -108,6 +109,7 @@ namespace Bit.Droid
|
||||||
ServiceContainer.Register<IStorageService>("secureStorageService", secureStorageService);
|
ServiceContainer.Register<IStorageService>("secureStorageService", secureStorageService);
|
||||||
ServiceContainer.Register<IDeviceActionService>("deviceActionService", deviceActionService);
|
ServiceContainer.Register<IDeviceActionService>("deviceActionService", deviceActionService);
|
||||||
ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService);
|
ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService);
|
||||||
|
ServiceContainer.Register<IBiometricService>("biometricService", biometricService);
|
||||||
|
|
||||||
// Push
|
// Push
|
||||||
#if FDROID
|
#if FDROID
|
||||||
|
|
90
src/Android/Services/BiometricService.cs
Normal file
90
src/Android/Services/BiometricService.cs
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Android.OS;
|
||||||
|
using Android.Security.Keystore;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Java.Security;
|
||||||
|
using Javax.Crypto;
|
||||||
|
|
||||||
|
namespace Bit.Droid.Services
|
||||||
|
{
|
||||||
|
public class BiometricService : IBiometricService
|
||||||
|
{
|
||||||
|
private const string KeyName = "com.8bit.bitwarden.biometric_integrity";
|
||||||
|
|
||||||
|
private const string KeyStoreName = "AndroidKeyStore";
|
||||||
|
|
||||||
|
private const string KeyAlgorithm = KeyProperties.KeyAlgorithmAes;
|
||||||
|
private const string BlockMode = KeyProperties.BlockModeCbc;
|
||||||
|
private const string EncryptionPadding = KeyProperties.EncryptionPaddingPkcs7;
|
||||||
|
private const string Transformation = KeyAlgorithm + "/" + BlockMode + "/" + EncryptionPadding;
|
||||||
|
|
||||||
|
private readonly KeyStore _keystore;
|
||||||
|
|
||||||
|
public BiometricService()
|
||||||
|
{
|
||||||
|
_keystore = KeyStore.GetInstance(KeyStoreName);
|
||||||
|
_keystore.Load(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> SetupBiometricAsync()
|
||||||
|
{
|
||||||
|
if (Build.VERSION.SdkInt >= BuildVersionCodes.M)
|
||||||
|
{
|
||||||
|
CreateKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> ValidateIntegrityAsync()
|
||||||
|
{
|
||||||
|
if (Build.VERSION.SdkInt < BuildVersionCodes.M)
|
||||||
|
{
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
_keystore.Load(null);
|
||||||
|
IKey key = _keystore.GetKey(KeyName, null);
|
||||||
|
Cipher cipher = Cipher.GetInstance(Transformation);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
cipher.Init(CipherMode.EncryptMode, key);
|
||||||
|
}
|
||||||
|
catch (KeyPermanentlyInvalidatedException e)
|
||||||
|
{
|
||||||
|
// Biometric has changed
|
||||||
|
return Task.FromResult(false);
|
||||||
|
}
|
||||||
|
catch (UnrecoverableKeyException e)
|
||||||
|
{
|
||||||
|
// Biometric was disabled and re-enabled
|
||||||
|
return Task.FromResult(false);
|
||||||
|
}
|
||||||
|
catch (InvalidKeyException e)
|
||||||
|
{
|
||||||
|
// Fallback for old bitwarden users without a key
|
||||||
|
CreateKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateKey()
|
||||||
|
{
|
||||||
|
KeyGenerator keyGen = KeyGenerator.GetInstance(KeyAlgorithm, KeyStoreName);
|
||||||
|
KeyGenParameterSpec keyGenSpec =
|
||||||
|
new KeyGenParameterSpec.Builder(KeyName, KeyStorePurpose.Encrypt | KeyStorePurpose.Decrypt)
|
||||||
|
.SetBlockModes(BlockMode)
|
||||||
|
.SetEncryptionPaddings(EncryptionPadding)
|
||||||
|
.SetUserAuthenticationRequired(true)
|
||||||
|
.Build();
|
||||||
|
keyGen.Init(keyGenSpec);
|
||||||
|
keyGen.GenerateKey();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -106,8 +106,12 @@
|
||||||
Margin="0, 10, 0, 0" />
|
Margin="0, 10, 0, 0" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout Padding="10, 0">
|
<StackLayout Padding="10, 0">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n BiometricInvalidated}"
|
||||||
|
StyleClass="box-footer-label,text-danger,text-bold"
|
||||||
|
IsVisible="{Binding BiometricIntegrityValid, Converter={StaticResource inverseBool}}" />
|
||||||
<Button Text="{Binding BiometricButtonText}" Clicked="Biometric_Clicked"
|
<Button Text="{Binding BiometricButtonText}" Clicked="Biometric_Clicked"
|
||||||
IsVisible="{Binding BiometricLock}"></Button>
|
IsVisible="{Binding BiometricLock}" IsEnabled="{Binding BiometricIntegrityValid}"></Button>
|
||||||
<Button Text="{u:I18n LogOut}" Clicked="LogOut_Clicked"></Button>
|
<Button Text="{u:I18n LogOut}" Clicked="LogOut_Clicked"></Button>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
|
|
|
@ -24,11 +24,13 @@ namespace Bit.App.Pages
|
||||||
private readonly IStorageService _secureStorageService;
|
private readonly IStorageService _secureStorageService;
|
||||||
private readonly IEnvironmentService _environmentService;
|
private readonly IEnvironmentService _environmentService;
|
||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
|
private readonly IBiometricService _biometricService;
|
||||||
|
|
||||||
private string _email;
|
private string _email;
|
||||||
private bool _showPassword;
|
private bool _showPassword;
|
||||||
private bool _pinLock;
|
private bool _pinLock;
|
||||||
private bool _biometricLock;
|
private bool _biometricLock;
|
||||||
|
private bool _biometricIntegrityValid = true;
|
||||||
private string _biometricButtonText;
|
private string _biometricButtonText;
|
||||||
private string _loggedInAsText;
|
private string _loggedInAsText;
|
||||||
private string _lockedVerifyText;
|
private string _lockedVerifyText;
|
||||||
|
@ -47,6 +49,7 @@ namespace Bit.App.Pages
|
||||||
_secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService");
|
_secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService");
|
||||||
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
|
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
|
||||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||||
|
_biometricService = ServiceContainer.Resolve<IBiometricService>("biometricService");
|
||||||
|
|
||||||
PageTitle = AppResources.VerifyMasterPassword;
|
PageTitle = AppResources.VerifyMasterPassword;
|
||||||
TogglePasswordCommand = new Command(TogglePassword);
|
TogglePasswordCommand = new Command(TogglePassword);
|
||||||
|
@ -75,6 +78,12 @@ namespace Bit.App.Pages
|
||||||
set => SetProperty(ref _biometricLock, value);
|
set => SetProperty(ref _biometricLock, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool BiometricIntegrityValid
|
||||||
|
{
|
||||||
|
get => _biometricIntegrityValid;
|
||||||
|
set => SetProperty(ref _biometricIntegrityValid, value);
|
||||||
|
}
|
||||||
|
|
||||||
public string BiometricButtonText
|
public string BiometricButtonText
|
||||||
{
|
{
|
||||||
get => _biometricButtonText;
|
get => _biometricButtonText;
|
||||||
|
@ -133,7 +142,8 @@ namespace Bit.App.Pages
|
||||||
BiometricButtonText = supportsFace ? AppResources.UseFaceIDToUnlock :
|
BiometricButtonText = supportsFace ? AppResources.UseFaceIDToUnlock :
|
||||||
AppResources.UseFingerprintToUnlock;
|
AppResources.UseFingerprintToUnlock;
|
||||||
}
|
}
|
||||||
if (autoPromptBiometric)
|
BiometricIntegrityValid = await _biometricService.ValidateIntegrityAsync();
|
||||||
|
if (autoPromptBiometric & _biometricIntegrityValid)
|
||||||
{
|
{
|
||||||
var tasks = Task.Run(async () =>
|
var tasks = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
|
@ -238,6 +248,12 @@ namespace Bit.App.Pages
|
||||||
}
|
}
|
||||||
MasterPassword = string.Empty;
|
MasterPassword = string.Empty;
|
||||||
await SetKeyAndContinueAsync(key);
|
await SetKeyAndContinueAsync(key);
|
||||||
|
|
||||||
|
// Re-enable biometrics
|
||||||
|
if (BiometricLock & !BiometricIntegrityValid)
|
||||||
|
{
|
||||||
|
await _biometricService.SetupBiometricAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -267,7 +283,8 @@ namespace Bit.App.Pages
|
||||||
|
|
||||||
public async Task PromptBiometricAsync()
|
public async Task PromptBiometricAsync()
|
||||||
{
|
{
|
||||||
if (!BiometricLock)
|
BiometricIntegrityValid = await _biometricService.ValidateIntegrityAsync();
|
||||||
|
if (!BiometricLock || !BiometricIntegrityValid)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ namespace Bit.App.Pages
|
||||||
private readonly IVaultTimeoutService _vaultTimeoutService;
|
private readonly IVaultTimeoutService _vaultTimeoutService;
|
||||||
private readonly IStorageService _storageService;
|
private readonly IStorageService _storageService;
|
||||||
private readonly ISyncService _syncService;
|
private readonly ISyncService _syncService;
|
||||||
|
private readonly IBiometricService _biometricService;
|
||||||
|
|
||||||
private bool _supportsBiometric;
|
private bool _supportsBiometric;
|
||||||
private bool _pin;
|
private bool _pin;
|
||||||
|
@ -60,6 +61,7 @@ namespace Bit.App.Pages
|
||||||
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||||
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||||
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
|
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
|
||||||
|
_biometricService = ServiceContainer.Resolve<IBiometricService>("biometricService");
|
||||||
|
|
||||||
GroupedItems = new ExtendedObservableCollection<SettingsPageListGroup>();
|
GroupedItems = new ExtendedObservableCollection<SettingsPageListGroup>();
|
||||||
PageTitle = AppResources.Settings;
|
PageTitle = AppResources.Settings;
|
||||||
|
@ -306,6 +308,7 @@ namespace Bit.App.Pages
|
||||||
}
|
}
|
||||||
if (_biometric)
|
if (_biometric)
|
||||||
{
|
{
|
||||||
|
await _biometricService.SetupBiometricAsync();
|
||||||
await _storageService.SaveAsync(Constants.BiometricUnlockKey, true);
|
await _storageService.SaveAsync(Constants.BiometricUnlockKey, true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -59,46 +59,46 @@
|
||||||
: using a System.ComponentModel.TypeConverter
|
: using a System.ComponentModel.TypeConverter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
-->
|
-->
|
||||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
<xsd:element name="root" msdata:IsDataSet="true">
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:choice maxOccurs="unbounded">
|
<xsd:choice maxOccurs="unbounded">
|
||||||
<xsd:element name="metadata">
|
<xsd:element name="metadata">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:sequence>
|
<xsd:sequence>
|
||||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
</xsd:sequence>
|
</xsd:sequence>
|
||||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
<xsd:attribute name="type" type="xsd:string"/>
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
<xsd:attribute ref="xml:space"/>
|
<xsd:attribute ref="xml:space" />
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
</xsd:element>
|
</xsd:element>
|
||||||
<xsd:element name="assembly">
|
<xsd:element name="assembly">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:attribute name="alias" type="xsd:string"/>
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
<xsd:attribute name="name" type="xsd:string"/>
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
</xsd:element>
|
</xsd:element>
|
||||||
<xsd:element name="data">
|
<xsd:element name="data">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:sequence>
|
<xsd:sequence>
|
||||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
</xsd:sequence>
|
</xsd:sequence>
|
||||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
<xsd:attribute ref="xml:space"/>
|
<xsd:attribute ref="xml:space" />
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
</xsd:element>
|
</xsd:element>
|
||||||
<xsd:element name="resheader">
|
<xsd:element name="resheader">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:sequence>
|
<xsd:sequence>
|
||||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
</xsd:sequence>
|
</xsd:sequence>
|
||||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
</xsd:element>
|
</xsd:element>
|
||||||
</xsd:choice>
|
</xsd:choice>
|
||||||
|
@ -1674,4 +1674,7 @@
|
||||||
<value>Do you really want to send to the bin?</value>
|
<value>Do you really want to send to the bin?</value>
|
||||||
<comment>Confirmation alert message when soft-deleting a cipher.</comment>
|
<comment>Confirmation alert message when soft-deleting a cipher.</comment>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="BiometricInvalidated" xml:space="preserve">
|
||||||
|
<value>Biometric change detected, login using Master Password to enable again.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
|
@ -1674,6 +1674,9 @@
|
||||||
<value>Do you really want to send to the trash?</value>
|
<value>Do you really want to send to the trash?</value>
|
||||||
<comment>Confirmation alert message when soft-deleting a cipher.</comment>
|
<comment>Confirmation alert message when soft-deleting a cipher.</comment>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="BiometricInvalidated" xml:space="preserve">
|
||||||
|
<value>Biometric change detected, login using Master Password to enable again.</value>
|
||||||
|
</data>
|
||||||
<data name="EnableSyncOnRefresh" xml:space="preserve">
|
<data name="EnableSyncOnRefresh" xml:space="preserve">
|
||||||
<value>Enable sync on refresh</value>
|
<value>Enable sync on refresh</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
10
src/Core/Abstractions/IBiometricService.cs
Normal file
10
src/Core/Abstractions/IBiometricService.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Bit.Core.Abstractions
|
||||||
|
{
|
||||||
|
public interface IBiometricService
|
||||||
|
{
|
||||||
|
Task<bool> SetupBiometricAsync();
|
||||||
|
Task<bool> ValidateIntegrityAsync();
|
||||||
|
}
|
||||||
|
}
|
62
src/iOS.Core/Services/BiometricService.cs
Normal file
62
src/iOS.Core/Services/BiometricService.cs
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Foundation;
|
||||||
|
using LocalAuthentication;
|
||||||
|
|
||||||
|
namespace Bit.iOS.Core.Services
|
||||||
|
{
|
||||||
|
public class BiometricService : IBiometricService
|
||||||
|
{
|
||||||
|
private IStorageService _storageService;
|
||||||
|
|
||||||
|
public BiometricService(IStorageService storageService)
|
||||||
|
{
|
||||||
|
_storageService = storageService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> SetupBiometricAsync()
|
||||||
|
{
|
||||||
|
var state = GetState();
|
||||||
|
await _storageService.SaveAsync("biometricState", ToBase64(state));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ValidateIntegrityAsync()
|
||||||
|
{
|
||||||
|
var oldState = await _storageService.GetAsync<string>("biometricState");
|
||||||
|
if (oldState == null)
|
||||||
|
{
|
||||||
|
// Fallback for upgraded devices
|
||||||
|
await SetupBiometricAsync();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var state = GetState();
|
||||||
|
|
||||||
|
return FromBase64(oldState) == state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private NSData GetState()
|
||||||
|
{
|
||||||
|
var context = new LAContext();
|
||||||
|
context.CanEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, out _);
|
||||||
|
|
||||||
|
return context.EvaluatedPolicyDomainState;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ToBase64(NSData data)
|
||||||
|
{
|
||||||
|
return System.Convert.ToBase64String(data.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
private NSData FromBase64(string data)
|
||||||
|
{
|
||||||
|
var bytes = System.Convert.FromBase64String(data);
|
||||||
|
return NSData.FromArray(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -55,6 +55,7 @@ namespace Bit.iOS.Core.Utilities
|
||||||
var deviceActionService = new DeviceActionService(mobileStorageService, messagingService);
|
var deviceActionService = new DeviceActionService(mobileStorageService, messagingService);
|
||||||
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, messagingService,
|
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, messagingService,
|
||||||
broadcasterService);
|
broadcasterService);
|
||||||
|
var biometricService = new BiometricService(mobileStorageService);
|
||||||
|
|
||||||
ServiceContainer.Register<IBroadcasterService>("broadcasterService", broadcasterService);
|
ServiceContainer.Register<IBroadcasterService>("broadcasterService", broadcasterService);
|
||||||
ServiceContainer.Register<IMessagingService>("messagingService", messagingService);
|
ServiceContainer.Register<IMessagingService>("messagingService", messagingService);
|
||||||
|
@ -65,6 +66,7 @@ namespace Bit.iOS.Core.Utilities
|
||||||
ServiceContainer.Register<IStorageService>("secureStorageService", secureStorageService);
|
ServiceContainer.Register<IStorageService>("secureStorageService", secureStorageService);
|
||||||
ServiceContainer.Register<IDeviceActionService>("deviceActionService", deviceActionService);
|
ServiceContainer.Register<IDeviceActionService>("deviceActionService", deviceActionService);
|
||||||
ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService);
|
ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService);
|
||||||
|
ServiceContainer.Register<IBiometricService>("biometricService", biometricService);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Bootstrap(Func<Task> postBootstrapFunc = null)
|
public static void Bootstrap(Func<Task> postBootstrapFunc = null)
|
||||||
|
|
|
@ -163,6 +163,7 @@
|
||||||
<Compile Include="Renderers\CustomTabbedRenderer.cs" />
|
<Compile Include="Renderers\CustomTabbedRenderer.cs" />
|
||||||
<Compile Include="Renderers\CustomViewCellRenderer.cs" />
|
<Compile Include="Renderers\CustomViewCellRenderer.cs" />
|
||||||
<Compile Include="Renderers\HybridWebViewRenderer.cs" />
|
<Compile Include="Renderers\HybridWebViewRenderer.cs" />
|
||||||
|
<Compile Include="Services\BiometricService.cs" />
|
||||||
<Compile Include="Services\DeviceActionService.cs" />
|
<Compile Include="Services\DeviceActionService.cs" />
|
||||||
<Compile Include="Utilities\ASHelpers.cs" />
|
<Compile Include="Utilities\ASHelpers.cs" />
|
||||||
<Compile Include="Utilities\Dialogs.cs" />
|
<Compile Include="Utilities\Dialogs.cs" />
|
||||||
|
|
Loading…
Reference in a new issue