mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 07:05:35 +03:00
[PM-1817] Expand biometric integrity checks to the account level (#2498)
* Change bio integrity validation to work at account-level * biometric state migration * fix account bio valid key storage location during migration * comment clarification * fix for iOS extensions not using custom avatar color
This commit is contained in:
parent
4f0238122b
commit
0f417b8434
22 changed files with 236 additions and 102 deletions
|
@ -21,6 +21,7 @@ using Bit.App.Utilities;
|
|||
using Bit.App.Pages;
|
||||
using Bit.App.Utilities.AccountManagement;
|
||||
using Bit.App.Controls;
|
||||
using Bit.Core.Enums;
|
||||
#if !FDROID
|
||||
using Android.Gms.Security;
|
||||
#endif
|
||||
|
@ -147,7 +148,7 @@ namespace Bit.Droid
|
|||
var storageMediatorService = new StorageMediatorService(mobileStorageService, secureStorageService, preferencesStorage);
|
||||
var stateService = new StateService(mobileStorageService, secureStorageService, storageMediatorService, messagingService);
|
||||
var stateMigrationService =
|
||||
new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService);
|
||||
new StateMigrationService(DeviceType.Android, liteDbStorage, preferencesStorage, secureStorageService);
|
||||
var clipboardService = new ClipboardService(stateService);
|
||||
var deviceActionService = new DeviceActionService(stateService, messagingService);
|
||||
var fileService = new FileService(stateService, broadcasterService);
|
||||
|
@ -155,7 +156,7 @@ namespace Bit.Droid
|
|||
messagingService, broadcasterService);
|
||||
var autofillHandler = new AutofillHandler(stateService, messagingService, clipboardService,
|
||||
platformUtilsService, new LazyResolve<IEventService>());
|
||||
var biometricService = new BiometricService();
|
||||
var biometricService = new BiometricService(stateService);
|
||||
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
|
||||
var cryptoService = new CryptoService(stateService, cryptoFunctionService);
|
||||
var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService);
|
||||
|
|
|
@ -4,7 +4,6 @@ using Android.OS;
|
|||
using Android.Security.Keystore;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Java.Security;
|
||||
using Javax.Crypto;
|
||||
|
||||
|
@ -12,6 +11,8 @@ namespace Bit.Droid.Services
|
|||
{
|
||||
public class BiometricService : IBiometricService
|
||||
{
|
||||
private readonly IStateService _stateService;
|
||||
|
||||
private const string KeyName = "com.8bit.bitwarden.biometric_integrity";
|
||||
|
||||
private const string KeyStoreName = "AndroidKeyStore";
|
||||
|
@ -23,28 +24,28 @@ namespace Bit.Droid.Services
|
|||
|
||||
private readonly KeyStore _keystore;
|
||||
|
||||
public BiometricService()
|
||||
public BiometricService(IStateService stateService)
|
||||
{
|
||||
_stateService = stateService;
|
||||
_keystore = KeyStore.GetInstance(KeyStoreName);
|
||||
_keystore.Load(null);
|
||||
}
|
||||
|
||||
public Task<bool> SetupBiometricAsync(string bioIntegrityKey = null)
|
||||
public async Task<bool> SetupBiometricAsync(string bioIntegritySrcKey = null)
|
||||
{
|
||||
// bioIntegrityKey used in iOS only
|
||||
if (Build.VERSION.SdkInt >= BuildVersionCodes.M)
|
||||
{
|
||||
CreateKey();
|
||||
await CreateKeyAsync(bioIntegritySrcKey);
|
||||
}
|
||||
|
||||
return Task.FromResult(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
public Task<bool> ValidateIntegrityAsync(string bioIntegrityKey = null)
|
||||
public async Task<bool> IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null)
|
||||
{
|
||||
if (Build.VERSION.SdkInt < BuildVersionCodes.M)
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
|
@ -55,7 +56,7 @@ namespace Bit.Droid.Services
|
|||
|
||||
if (key == null || cipher == null)
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
cipher.Init(CipherMode.EncryptMode, key);
|
||||
|
@ -63,25 +64,32 @@ namespace Bit.Droid.Services
|
|||
catch (KeyPermanentlyInvalidatedException e)
|
||||
{
|
||||
// Biometric has changed
|
||||
return Task.FromResult(false);
|
||||
await ClearStateAsync(bioIntegritySrcKey);
|
||||
return false;
|
||||
}
|
||||
catch (UnrecoverableKeyException e)
|
||||
{
|
||||
// Biometric was disabled and re-enabled
|
||||
return Task.FromResult(false);
|
||||
await ClearStateAsync(bioIntegritySrcKey);
|
||||
return false;
|
||||
}
|
||||
catch (InvalidKeyException e)
|
||||
{
|
||||
// Fallback for old bitwarden users without a key
|
||||
LoggerHelper.LogEvenIfCantBeResolved(e);
|
||||
CreateKey();
|
||||
await CreateKeyAsync(bioIntegritySrcKey);
|
||||
}
|
||||
|
||||
return Task.FromResult(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void CreateKey()
|
||||
private async Task CreateKeyAsync(string bioIntegritySrcKey = null)
|
||||
{
|
||||
bioIntegritySrcKey ??= Core.Constants.BiometricIntegritySourceKey;
|
||||
await _stateService.SetSystemBiometricIntegrityState(bioIntegritySrcKey,
|
||||
await GetStateAsync(bioIntegritySrcKey));
|
||||
await _stateService.SetAccountBiometricIntegrityValidAsync(bioIntegritySrcKey);
|
||||
|
||||
try
|
||||
{
|
||||
var keyGen = KeyGenerator.GetInstance(KeyAlgorithm, KeyStoreName);
|
||||
|
@ -101,5 +109,16 @@ namespace Bit.Droid.Services
|
|||
LoggerHelper.LogEvenIfCantBeResolved(e);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> GetStateAsync(string bioIntegritySrcKey)
|
||||
{
|
||||
return await _stateService.GetSystemBiometricIntegrityState(bioIntegritySrcKey) ??
|
||||
Guid.NewGuid().ToString();
|
||||
}
|
||||
|
||||
private async Task ClearStateAsync(string bioIntegritySrcKey)
|
||||
{
|
||||
await _stateService.SetSystemBiometricIntegrityState(bioIntegritySrcKey, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -137,7 +137,7 @@
|
|||
</StackLayout>
|
||||
<StackLayout Padding="10, 0">
|
||||
<Label
|
||||
Text="{u:I18n BiometricInvalidated}"
|
||||
Text="{u:I18n AccountBiometricInvalidated}"
|
||||
StyleClass="box-footer-label,text-danger,text-bold"
|
||||
IsVisible="{Binding BiometricIntegrityValid, Converter={StaticResource inverseBool}}" />
|
||||
<Button Text="{Binding BiometricButtonText}" Clicked="Biometric_Clicked"
|
||||
|
|
|
@ -209,7 +209,7 @@ namespace Bit.App.Pages
|
|||
|
||||
if (BiometricLock)
|
||||
{
|
||||
BiometricIntegrityValid = await _biometricService.ValidateIntegrityAsync();
|
||||
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync();
|
||||
if (!_biometricIntegrityValid)
|
||||
{
|
||||
BiometricButtonVisible = false;
|
||||
|
@ -431,7 +431,8 @@ namespace Bit.App.Pages
|
|||
|
||||
public async Task PromptBiometricAsync()
|
||||
{
|
||||
BiometricIntegrityValid = await _biometricService.ValidateIntegrityAsync();
|
||||
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync();
|
||||
BiometricButtonVisible = BiometricIntegrityValid;
|
||||
if (!BiometricLock || !BiometricIntegrityValid)
|
||||
{
|
||||
return;
|
||||
|
|
8
src/App/Resources/AppResources.Designer.cs
generated
8
src/App/Resources/AppResources.Designer.cs
generated
|
@ -970,18 +970,18 @@ namespace Bit.App.Resources {
|
|||
/// <summary>
|
||||
/// Looks up a localized string similar to Biometric unlock disabled pending verification of master password..
|
||||
/// </summary>
|
||||
public static string BiometricInvalidated {
|
||||
public static string AccountBiometricInvalidated {
|
||||
get {
|
||||
return ResourceManager.GetString("BiometricInvalidated", resourceCulture);
|
||||
return ResourceManager.GetString("AccountBiometricInvalidated", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Biometric unlock for autofill disabled pending verification of master password..
|
||||
/// </summary>
|
||||
public static string BiometricInvalidatedExtension {
|
||||
public static string AccountBiometricInvalidatedExtension {
|
||||
get {
|
||||
return ResourceManager.GetString("BiometricInvalidatedExtension", resourceCulture);
|
||||
return ResourceManager.GetString("AccountBiometricInvalidatedExtension", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1752,11 +1752,11 @@ Scanning will happen automatically.</value>
|
|||
<value>Do you really want to send to the trash?</value>
|
||||
<comment>Confirmation alert message when soft-deleting a cipher.</comment>
|
||||
</data>
|
||||
<data name="BiometricInvalidated" xml:space="preserve">
|
||||
<value>Biometric unlock disabled pending verification of master password.</value>
|
||||
<data name="AccountBiometricInvalidated" xml:space="preserve">
|
||||
<value>Biometric unlock for this account is disabled pending verification of master password.</value>
|
||||
</data>
|
||||
<data name="BiometricInvalidatedExtension" xml:space="preserve">
|
||||
<value>Biometric unlock for autofill disabled pending verification of master password.</value>
|
||||
<data name="AccountBiometricInvalidatedExtension" xml:space="preserve">
|
||||
<value>Autofill biometric unlock for this account is disabled pending verification of master password.</value>
|
||||
</data>
|
||||
<data name="EnableSyncOnRefresh" xml:space="preserve">
|
||||
<value>Allow sync on refresh</value>
|
||||
|
|
|
@ -6,6 +6,7 @@ using Bit.App.Models;
|
|||
using Bit.App.Resources;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Utilities;
|
||||
using Plugin.Fingerprint;
|
||||
using Plugin.Fingerprint.Abstractions;
|
||||
using Xamarin.Essentials;
|
||||
|
@ -226,6 +227,20 @@ namespace Bit.App.Services
|
|||
}
|
||||
}
|
||||
|
||||
public async Task<bool> IsBiometricIntegrityValidAsync(string bioIntegritySrcKey = null)
|
||||
{
|
||||
bioIntegritySrcKey ??= Core.Constants.BiometricIntegritySourceKey;
|
||||
|
||||
var biometricService = ServiceContainer.Resolve<IBiometricService>();
|
||||
if (!await biometricService.IsSystemBiometricIntegrityValidAsync(bioIntegritySrcKey))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var stateService = ServiceContainer.Resolve<IStateService>();
|
||||
return await stateService.IsAccountBiometricIntegrityValidAsync(bioIntegritySrcKey);
|
||||
}
|
||||
|
||||
public async Task<bool> AuthenticateBiometricAsync(string text = null, string fallbackText = null,
|
||||
Action fallback = null)
|
||||
{
|
||||
|
|
|
@ -22,14 +22,14 @@ namespace Bit.App.Services
|
|||
Constants.PushRegisteredTokenKey,
|
||||
Constants.LastBuildKey,
|
||||
Constants.ClearCiphersCacheKey,
|
||||
Constants.BiometricIntegrityKey,
|
||||
Constants.BiometricIntegritySourceKey,
|
||||
Constants.iOSExtensionActiveUserIdKey,
|
||||
Constants.iOSAutoFillClearCiphersCacheKey,
|
||||
Constants.iOSAutoFillBiometricIntegrityKey,
|
||||
Constants.iOSAutoFillBiometricIntegritySourceKey,
|
||||
Constants.iOSExtensionClearCiphersCacheKey,
|
||||
Constants.iOSExtensionBiometricIntegrityKey,
|
||||
Constants.iOSExtensionBiometricIntegritySourceKey,
|
||||
Constants.iOSShareExtensionClearCiphersCacheKey,
|
||||
Constants.iOSShareExtensionBiometricIntegrityKey,
|
||||
Constants.iOSShareExtensionBiometricIntegritySourceKey,
|
||||
Constants.RememberedEmailKey,
|
||||
Constants.RememberedOrgIdentifierKey
|
||||
};
|
||||
|
|
|
@ -4,7 +4,7 @@ namespace Bit.Core.Abstractions
|
|||
{
|
||||
public interface IBiometricService
|
||||
{
|
||||
Task<bool> SetupBiometricAsync(string bioIntegrityKey = null);
|
||||
Task<bool> ValidateIntegrityAsync(string bioIntegrityKey = null);
|
||||
Task<bool> SetupBiometricAsync(string bioIntegritySrcKey = null);
|
||||
Task<bool> IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ namespace Bit.Core.Abstractions
|
|||
bool SupportsFido2();
|
||||
bool SupportsDuo();
|
||||
Task<bool> SupportsBiometricAsync();
|
||||
Task<bool> IsBiometricIntegrityValidAsync(string bioIntegritySrcKey = null);
|
||||
Task<bool> AuthenticateBiometricAsync(string text = null, string fallbackText = null, Action fallback = null);
|
||||
long GetActiveTime();
|
||||
}
|
||||
|
|
|
@ -31,6 +31,10 @@ namespace Bit.Core.Abstractions
|
|||
Task SetBiometricUnlockAsync(bool? value, string userId = null);
|
||||
Task<bool> GetBiometricLockedAsync(string userId = null);
|
||||
Task SetBiometricLockedAsync(bool value, string userId = null);
|
||||
Task<string> GetSystemBiometricIntegrityState(string bioIntegritySrcKey);
|
||||
Task SetSystemBiometricIntegrityState(string bioIntegritySrcKey, string systemBioIntegrityState);
|
||||
Task<bool> IsAccountBiometricIntegrityValidAsync(string bioIntegritySrcKey, string userId = null);
|
||||
Task SetAccountBiometricIntegrityValidAsync(string bioIntegritySrcKey, string userId = null);
|
||||
Task<bool> CanAccessPremiumAsync(string userId = null);
|
||||
Task SetPersonalPremiumAsync(bool value, string userId = null);
|
||||
Task<string> GetProtectedPinAsync(string userId = null);
|
||||
|
|
|
@ -18,13 +18,13 @@
|
|||
public const string LastBuildKey = "lastBuild";
|
||||
public const string AddSitePromptShownKey = "addSitePromptShown";
|
||||
public const string ClearCiphersCacheKey = "clearCiphersCache";
|
||||
public const string BiometricIntegrityKey = "biometricIntegrityState";
|
||||
public const string BiometricIntegritySourceKey = "biometricIntegritySource";
|
||||
public const string iOSAutoFillClearCiphersCacheKey = "iOSAutoFillClearCiphersCache";
|
||||
public const string iOSAutoFillBiometricIntegrityKey = "iOSAutoFillBiometricIntegrityState";
|
||||
public const string iOSAutoFillBiometricIntegritySourceKey = "iOSAutoFillBiometricIntegritySource";
|
||||
public const string iOSExtensionClearCiphersCacheKey = "iOSExtensionClearCiphersCache";
|
||||
public const string iOSExtensionBiometricIntegrityKey = "iOSExtensionBiometricIntegrityState";
|
||||
public const string iOSExtensionBiometricIntegritySourceKey = "iOSExtensionBiometricIntegritySource";
|
||||
public const string iOSShareExtensionClearCiphersCacheKey = "iOSShareExtensionClearCiphersCache";
|
||||
public const string iOSShareExtensionBiometricIntegrityKey = "iOSShareExtensionBiometricIntegrityState";
|
||||
public const string iOSShareExtensionBiometricIntegritySourceKey = "iOSShareExtensionBiometricIntegritySource";
|
||||
public const string iOSExtensionActiveUserIdKey = "iOSExtensionActiveUserId";
|
||||
public const string EventCollectionKey = "eventCollection";
|
||||
public const string RememberedEmailKey = "rememberedEmail";
|
||||
|
@ -110,6 +110,8 @@
|
|||
public static string ProtectedPinKey(string userId) => $"protectedPin_{userId}";
|
||||
public static string LastSyncKey(string userId) => $"lastSync_{userId}";
|
||||
public static string BiometricUnlockKey(string userId) => $"biometricUnlock_{userId}";
|
||||
public static string AccountBiometricIntegrityValidKey(string userId, string systemBioIntegrityState) =>
|
||||
$"accountBiometricIntegrityValid_{userId}_{systemBioIntegrityState}";
|
||||
public static string ApprovePasswordlessLoginsKey(string userId) => $"approvePasswordlessLogins_{userId}";
|
||||
public static string UsernameGenOptionsKey(string userId) => $"usernameGenerationOptions_{userId}";
|
||||
public static string PushLastRegistrationDateKey(string userId) => $"pushLastRegistrationDate_{userId}";
|
||||
|
|
|
@ -13,8 +13,9 @@ namespace Bit.Core.Services
|
|||
{
|
||||
public class StateMigrationService : IStateMigrationService
|
||||
{
|
||||
private const int StateVersion = 4;
|
||||
private const int StateVersion = 5;
|
||||
|
||||
private readonly DeviceType _deviceType;
|
||||
private readonly IStorageService _preferencesStorageService;
|
||||
private readonly IStorageService _liteDbStorageService;
|
||||
private readonly IStorageService _secureStorageService;
|
||||
|
@ -27,9 +28,10 @@ namespace Bit.Core.Services
|
|||
Secure,
|
||||
}
|
||||
|
||||
public StateMigrationService(IStorageService liteDbStorageService, IStorageService preferenceStorageService,
|
||||
IStorageService secureStorageService)
|
||||
public StateMigrationService(DeviceType deviceType, IStorageService liteDbStorageService,
|
||||
IStorageService preferenceStorageService, IStorageService secureStorageService)
|
||||
{
|
||||
_deviceType = deviceType;
|
||||
_liteDbStorageService = liteDbStorageService;
|
||||
_preferencesStorageService = preferenceStorageService;
|
||||
_secureStorageService = secureStorageService;
|
||||
|
@ -79,6 +81,9 @@ namespace Bit.Core.Services
|
|||
case 3:
|
||||
await MigrateFrom3To4Async();
|
||||
break;
|
||||
case 4:
|
||||
await MigrateFrom4To5Async();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -442,6 +447,10 @@ namespace Bit.Core.Services
|
|||
// Removal of old state data will happen organically as state is rebuilt in app
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region v4 to v5 Migration
|
||||
|
||||
private class V4Keys
|
||||
{
|
||||
internal static string VaultTimeoutKey(string userId) => $"vaultTimeout_{userId}";
|
||||
|
@ -450,6 +459,87 @@ namespace Bit.Core.Services
|
|||
internal const string ThemeKey = "theme";
|
||||
internal const string AutoDarkThemeKey = "autoDarkTheme";
|
||||
internal const string DisableFaviconKey = "disableFavicon";
|
||||
internal const string BiometricIntegrityKey = "biometricIntegrityState";
|
||||
internal const string iOSAutoFillBiometricIntegrityKey = "iOSAutoFillBiometricIntegrityState";
|
||||
internal const string iOSExtensionBiometricIntegrityKey = "iOSExtensionBiometricIntegrityState";
|
||||
internal const string iOSShareExtensionBiometricIntegrityKey = "iOSShareExtensionBiometricIntegrityState";
|
||||
}
|
||||
|
||||
private async Task MigrateFrom4To5Async()
|
||||
{
|
||||
var bioIntegrityState = await GetValueAsync<string>(Storage.Prefs, V4Keys.BiometricIntegrityKey);
|
||||
var iOSAutofillBioIntegrityState =
|
||||
await GetValueAsync<string>(Storage.Prefs, V4Keys.iOSAutoFillBiometricIntegrityKey);
|
||||
var iOSExtensionBioIntegrityState =
|
||||
await GetValueAsync<string>(Storage.Prefs, V4Keys.iOSExtensionBiometricIntegrityKey);
|
||||
var iOSShareExtensionBioIntegrityState =
|
||||
await GetValueAsync<string>(Storage.Prefs, V4Keys.iOSShareExtensionBiometricIntegrityKey);
|
||||
|
||||
if (_deviceType == DeviceType.Android && string.IsNullOrWhiteSpace(bioIntegrityState))
|
||||
{
|
||||
bioIntegrityState = Guid.NewGuid().ToString();
|
||||
}
|
||||
|
||||
await SetValueAsync(Storage.Prefs, V5Keys.BiometricIntegritySourceKey, bioIntegrityState);
|
||||
|
||||
if (_deviceType == DeviceType.iOS)
|
||||
{
|
||||
await SetValueAsync(Storage.Prefs, V5Keys.iOSAutoFillBiometricIntegritySourceKey,
|
||||
iOSAutofillBioIntegrityState);
|
||||
await SetValueAsync(Storage.Prefs, V5Keys.iOSExtensionBiometricIntegritySourceKey,
|
||||
iOSExtensionBioIntegrityState);
|
||||
await SetValueAsync(Storage.Prefs, V5Keys.iOSShareExtensionBiometricIntegritySourceKey,
|
||||
iOSShareExtensionBioIntegrityState);
|
||||
}
|
||||
|
||||
var state = await GetValueAsync<State>(Storage.LiteDb, Constants.StateKey);
|
||||
if (state?.Accounts is null)
|
||||
{
|
||||
// No accounts available, update stored version and exit
|
||||
await SetLastStateVersionAsync(5);
|
||||
return;
|
||||
}
|
||||
|
||||
// build integrity keys for existing users
|
||||
foreach (var account in state.Accounts.Where(a => a.Value?.Profile?.UserId != null))
|
||||
{
|
||||
var userId = account.Value.Profile.UserId;
|
||||
|
||||
await SetValueAsync(Storage.LiteDb,
|
||||
V5Keys.AccountBiometricIntegrityValidKey(userId, bioIntegrityState), true);
|
||||
|
||||
if (_deviceType == DeviceType.iOS)
|
||||
{
|
||||
await SetValueAsync(Storage.LiteDb,
|
||||
V5Keys.AccountBiometricIntegrityValidKey(userId, iOSAutofillBioIntegrityState), true);
|
||||
await SetValueAsync(Storage.LiteDb,
|
||||
V5Keys.AccountBiometricIntegrityValidKey(userId, iOSExtensionBioIntegrityState), true);
|
||||
await SetValueAsync(Storage.LiteDb,
|
||||
V5Keys.AccountBiometricIntegrityValidKey(userId, iOSShareExtensionBioIntegrityState), true);
|
||||
}
|
||||
}
|
||||
|
||||
// Update stored version
|
||||
await SetLastStateVersionAsync(5);
|
||||
|
||||
// Remove old data
|
||||
await RemoveValueAsync(Storage.Prefs, V4Keys.BiometricIntegrityKey);
|
||||
await RemoveValueAsync(Storage.Prefs, V4Keys.iOSAutoFillBiometricIntegrityKey);
|
||||
await RemoveValueAsync(Storage.Prefs, V4Keys.iOSExtensionBiometricIntegrityKey);
|
||||
await RemoveValueAsync(Storage.Prefs, V4Keys.iOSShareExtensionBiometricIntegrityKey);
|
||||
}
|
||||
|
||||
private class V5Keys
|
||||
{
|
||||
internal const string BiometricIntegritySourceKey = "biometricIntegritySource";
|
||||
internal const string iOSAutoFillBiometricIntegritySourceKey = "iOSAutoFillBiometricIntegritySource";
|
||||
internal const string iOSExtensionBiometricIntegritySourceKey = "iOSExtensionBiometricIntegritySource";
|
||||
|
||||
internal const string iOSShareExtensionBiometricIntegritySourceKey =
|
||||
"iOSShareExtensionBiometricIntegritySource";
|
||||
|
||||
internal static string AccountBiometricIntegrityValidKey(string userId, string systemBioIntegrityState) =>
|
||||
$"accountBiometricIntegrityValid_{userId}_{systemBioIntegrityState}";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
|
|
@ -272,6 +272,36 @@ namespace Bit.Core.Services
|
|||
await SaveAccountAsync(account, reconciledOptions);
|
||||
}
|
||||
|
||||
public async Task<string> GetSystemBiometricIntegrityState(string bioIntegritySrcKey)
|
||||
{
|
||||
return await GetValueAsync<string>(bioIntegritySrcKey, await GetDefaultStorageOptionsAsync());
|
||||
}
|
||||
|
||||
public async Task SetSystemBiometricIntegrityState(string bioIntegritySrcKey, string systemBioIntegrityState)
|
||||
{
|
||||
await SetValueAsync(bioIntegritySrcKey, systemBioIntegrityState, await GetDefaultStorageOptionsAsync());
|
||||
}
|
||||
|
||||
public async Task<bool> IsAccountBiometricIntegrityValidAsync(string bioIntegritySrcKey, string userId = null)
|
||||
{
|
||||
var systemBioIntegrityState = await GetSystemBiometricIntegrityState(bioIntegritySrcKey);
|
||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||
await GetDefaultStorageOptionsAsync());
|
||||
return await GetValueAsync<bool?>(
|
||||
Constants.AccountBiometricIntegrityValidKey(reconciledOptions.UserId, systemBioIntegrityState),
|
||||
reconciledOptions) ?? false;
|
||||
}
|
||||
|
||||
public async Task SetAccountBiometricIntegrityValidAsync(string bioIntegritySrcKey, string userId = null)
|
||||
{
|
||||
var systemBioIntegrityState = await GetSystemBiometricIntegrityState(bioIntegritySrcKey);
|
||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||
await GetDefaultStorageOptionsAsync());
|
||||
await SetValueAsync(
|
||||
Constants.AccountBiometricIntegrityValidKey(reconciledOptions.UserId, systemBioIntegrityState),
|
||||
true, reconciledOptions);
|
||||
}
|
||||
|
||||
public async Task<bool> CanAccessPremiumAsync(string userId = null)
|
||||
{
|
||||
if (userId == null)
|
||||
|
|
|
@ -15,7 +15,7 @@ namespace Bit.iOS.Autofill
|
|||
public LockPasswordViewController(IntPtr handle)
|
||||
: base(handle)
|
||||
{
|
||||
BiometricIntegrityKey = Bit.Core.Constants.iOSAutoFillBiometricIntegrityKey;
|
||||
BiometricIntegritySourceKey = Bit.Core.Constants.iOSAutoFillBiometricIntegritySourceKey;
|
||||
DismissModalAction = Cancel;
|
||||
autofillExtension = true;
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ namespace Bit.iOS.Core.Controllers
|
|||
public FormEntryTableViewCell MasterPasswordCell { get; set; } = new FormEntryTableViewCell(
|
||||
AppResources.MasterPassword, buttonsConfig: FormEntryTableViewCell.ButtonsConfig.One);
|
||||
|
||||
public string BiometricIntegrityKey { get; set; }
|
||||
public string BiometricIntegritySourceKey { get; set; }
|
||||
|
||||
public UITableViewCell BiometricCell
|
||||
{
|
||||
|
@ -77,7 +77,7 @@ namespace Bit.iOS.Core.Controllers
|
|||
cell.TextLabel.Font = ThemeHelpers.GetDangerFont();
|
||||
cell.TextLabel.Lines = 0;
|
||||
cell.TextLabel.LineBreakMode = UILineBreakMode.WordWrap;
|
||||
cell.TextLabel.Text = AppResources.BiometricInvalidatedExtension;
|
||||
cell.TextLabel.Text = AppResources.AccountBiometricInvalidatedExtension;
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
|
@ -114,7 +114,8 @@ namespace Bit.iOS.Core.Controllers
|
|||
_isPinProtectedWithKey;
|
||||
_biometricLock = await _vaultTimeoutService.IsBiometricLockSetAsync() &&
|
||||
await _cryptoService.HasKeyAsync();
|
||||
_biometricIntegrityValid = await _biometricService.ValidateIntegrityAsync(BiometricIntegrityKey);
|
||||
_biometricIntegrityValid =
|
||||
await _platformUtilsService.IsBiometricIntegrityValidAsync(BiometricIntegritySourceKey);
|
||||
_usesKeyConnector = await _keyConnectorService.GetUsesKeyConnector();
|
||||
_biometricUnlockOnly = _usesKeyConnector && _biometricLock && !_pinLock;
|
||||
}
|
||||
|
@ -371,7 +372,7 @@ namespace Bit.iOS.Core.Controllers
|
|||
// Re-enable biometrics if initial use
|
||||
if (_biometricLock & !_biometricIntegrityValid)
|
||||
{
|
||||
await _biometricService.SetupBiometricAsync(BiometricIntegrityKey);
|
||||
await _biometricService.SetupBiometricAsync(BiometricIntegritySourceKey);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ namespace Bit.iOS.Core.Controllers
|
|||
public FormEntryTableViewCell MasterPasswordCell { get; set; } = new FormEntryTableViewCell(
|
||||
AppResources.MasterPassword, buttonsConfig: FormEntryTableViewCell.ButtonsConfig.One);
|
||||
|
||||
public string BiometricIntegrityKey { get; set; }
|
||||
public string BiometricIntegritySourceKey { get; set; }
|
||||
|
||||
public UITableViewCell BiometricCell
|
||||
{
|
||||
|
@ -74,7 +74,7 @@ namespace Bit.iOS.Core.Controllers
|
|||
cell.TextLabel.Font = ThemeHelpers.GetDangerFont();
|
||||
cell.TextLabel.Lines = 0;
|
||||
cell.TextLabel.LineBreakMode = UILineBreakMode.WordWrap;
|
||||
cell.TextLabel.Text = AppResources.BiometricInvalidatedExtension;
|
||||
cell.TextLabel.Text = AppResources.AccountBiometricInvalidatedExtension;
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
|
@ -108,7 +108,8 @@ namespace Bit.iOS.Core.Controllers
|
|||
_isPinProtectedWithKey;
|
||||
_biometricLock = await _vaultTimeoutService.IsBiometricLockSetAsync() &&
|
||||
await _cryptoService.HasKeyAsync();
|
||||
_biometricIntegrityValid = await _biometricService.ValidateIntegrityAsync(BiometricIntegrityKey);
|
||||
_biometricIntegrityValid =
|
||||
await _platformUtilsService.IsBiometricIntegrityValidAsync(BiometricIntegritySourceKey);
|
||||
_usesKeyConnector = await _keyConnectorService.GetUsesKeyConnector();
|
||||
_biometricUnlockOnly = _usesKeyConnector && _biometricLock && !_pinLock;
|
||||
}
|
||||
|
@ -361,7 +362,7 @@ namespace Bit.iOS.Core.Controllers
|
|||
// Re-enable biometrics if initial use
|
||||
if (_biometricLock & !_biometricIntegrityValid)
|
||||
{
|
||||
await _biometricService.SetupBiometricAsync(BiometricIntegrityKey);
|
||||
await _biometricService.SetupBiometricAsync(BiometricIntegritySourceKey);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,29 +7,30 @@ namespace Bit.iOS.Core.Services
|
|||
{
|
||||
public class BiometricService : IBiometricService
|
||||
{
|
||||
private IStorageService _storageService;
|
||||
private IStateService _stateService;
|
||||
|
||||
public BiometricService(IStorageService storageService)
|
||||
public BiometricService(IStateService stateService)
|
||||
{
|
||||
_storageService = storageService;
|
||||
_stateService = stateService;
|
||||
}
|
||||
|
||||
public async Task<bool> SetupBiometricAsync(string bioIntegrityKey = null)
|
||||
public async Task<bool> SetupBiometricAsync(string bioIntegritySrcKey = null)
|
||||
{
|
||||
if (bioIntegrityKey == null)
|
||||
if (bioIntegritySrcKey == null)
|
||||
{
|
||||
bioIntegrityKey = Bit.Core.Constants.BiometricIntegrityKey;
|
||||
bioIntegritySrcKey = Bit.Core.Constants.BiometricIntegritySourceKey;
|
||||
}
|
||||
var state = GetState();
|
||||
if (state != null)
|
||||
{
|
||||
await _storageService.SaveAsync(bioIntegrityKey, ToBase64(state));
|
||||
await _stateService.SetSystemBiometricIntegrityState(bioIntegritySrcKey, ToBase64(state));
|
||||
await _stateService.SetAccountBiometricIntegrityValidAsync(bioIntegritySrcKey);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> ValidateIntegrityAsync(string bioIntegrityKey = null)
|
||||
public async Task<bool> IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null)
|
||||
{
|
||||
var state = GetState();
|
||||
if (state == null)
|
||||
|
@ -38,18 +39,14 @@ namespace Bit.iOS.Core.Services
|
|||
return true;
|
||||
}
|
||||
|
||||
if (bioIntegrityKey == null)
|
||||
if (bioIntegritySrcKey == null)
|
||||
{
|
||||
bioIntegrityKey = Bit.Core.Constants.BiometricIntegrityKey;
|
||||
bioIntegritySrcKey = Bit.Core.Constants.BiometricIntegritySourceKey;
|
||||
}
|
||||
var oldState = await _storageService.GetAsync<string>(bioIntegrityKey);
|
||||
if (oldState == null)
|
||||
var savedState = await _stateService.GetSystemBiometricIntegrityState(bioIntegritySrcKey);
|
||||
if (savedState != null)
|
||||
{
|
||||
oldState = await GetMigratedIntegrityState(bioIntegrityKey);
|
||||
}
|
||||
if (oldState != null)
|
||||
{
|
||||
return FromBase64(oldState).Equals(state);
|
||||
return FromBase64(savedState).Equals(state);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -72,35 +69,5 @@ namespace Bit.iOS.Core.Services
|
|||
var bytes = System.Convert.FromBase64String(data);
|
||||
return NSData.FromArray(bytes);
|
||||
}
|
||||
|
||||
private async Task<string> GetMigratedIntegrityState(string bioIntegrityKey)
|
||||
{
|
||||
var legacyKey = "biometricState";
|
||||
if (bioIntegrityKey == Bit.Core.Constants.iOSAutoFillBiometricIntegrityKey)
|
||||
{
|
||||
legacyKey = "autofillBiometricState";
|
||||
}
|
||||
else if (bioIntegrityKey == Bit.Core.Constants.iOSExtensionBiometricIntegrityKey)
|
||||
{
|
||||
legacyKey = "extensionBiometricState";
|
||||
}
|
||||
|
||||
// Original values are pulled from DB since the legacy keys were never defined in _preferenceStorageKeys
|
||||
var integrityState = await _storageService.GetAsync<string>(legacyKey);
|
||||
if (integrityState != null)
|
||||
{
|
||||
// Save original value to pref storage with new key
|
||||
await _storageService.SaveAsync(bioIntegrityKey, integrityState);
|
||||
|
||||
// Remove value from DB storage with legacy key
|
||||
await _storageService.RemoveAsync(legacyKey);
|
||||
|
||||
// Return value as if it was always in pref storage
|
||||
return integrityState;
|
||||
}
|
||||
|
||||
// Return null since the state was never set
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,8 @@ namespace Bit.iOS.Core.Utilities
|
|||
}
|
||||
|
||||
var avatarImageSource = new AvatarImageSource(await _stateService.GetActiveUserIdAsync(),
|
||||
await _stateService.GetNameAsync(), await _stateService.GetEmailAsync());
|
||||
await _stateService.GetNameAsync(), await _stateService.GetEmailAsync(),
|
||||
await _stateService.GetAvatarColorAsync());
|
||||
using (var avatarUIImage = await avatarImageSource.GetNativeImageAsync())
|
||||
{
|
||||
return avatarUIImage?.ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal) ?? UIImage.GetSystemImage(DEFAULT_SYSTEM_AVATAR_IMAGE);
|
||||
|
|
|
@ -10,6 +10,7 @@ using Bit.App.Services;
|
|||
using Bit.App.Utilities;
|
||||
using Bit.App.Utilities.AccountManagement;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.iOS.Core.Services;
|
||||
|
@ -105,13 +106,13 @@ namespace Bit.iOS.Core.Utilities
|
|||
var storageMediatorService = new StorageMediatorService(mobileStorageService, secureStorageService, preferencesStorage);
|
||||
var stateService = new StateService(mobileStorageService, secureStorageService, storageMediatorService, messagingService);
|
||||
var stateMigrationService =
|
||||
new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService);
|
||||
new StateMigrationService(DeviceType.iOS, liteDbStorage, preferencesStorage, secureStorageService);
|
||||
var deviceActionService = new DeviceActionService();
|
||||
var fileService = new FileService(stateService, messagingService);
|
||||
var clipboardService = new ClipboardService(stateService);
|
||||
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, clipboardService,
|
||||
messagingService, broadcasterService);
|
||||
var biometricService = new BiometricService(mobileStorageService);
|
||||
var biometricService = new BiometricService(stateService);
|
||||
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
|
||||
var cryptoService = new CryptoService(stateService, cryptoFunctionService);
|
||||
var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService);
|
||||
|
|
|
@ -9,7 +9,7 @@ namespace Bit.iOS.Extension
|
|||
public LockPasswordViewController(IntPtr handle)
|
||||
: base(handle)
|
||||
{
|
||||
BiometricIntegrityKey = Bit.Core.Constants.iOSExtensionBiometricIntegrityKey;
|
||||
BiometricIntegritySourceKey = Bit.Core.Constants.iOSExtensionBiometricIntegritySourceKey;
|
||||
DismissModalAction = Cancel;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,14 +13,14 @@ namespace Bit.iOS.ShareExtension
|
|||
|
||||
public LockPasswordViewController()
|
||||
{
|
||||
BiometricIntegrityKey = Bit.Core.Constants.iOSShareExtensionBiometricIntegrityKey;
|
||||
BiometricIntegritySourceKey = Bit.Core.Constants.iOSShareExtensionBiometricIntegritySourceKey;
|
||||
DismissModalAction = Cancel;
|
||||
}
|
||||
|
||||
public LockPasswordViewController(IntPtr handle)
|
||||
: base(handle)
|
||||
{
|
||||
BiometricIntegrityKey = Bit.Core.Constants.iOSShareExtensionBiometricIntegrityKey;
|
||||
BiometricIntegritySourceKey = Bit.Core.Constants.iOSShareExtensionBiometricIntegritySourceKey;
|
||||
DismissModalAction = Cancel;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue