[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:
mp-bw 2023-05-01 09:47:00 -04:00 committed by GitHub
parent 4f0238122b
commit 0f417b8434
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 236 additions and 102 deletions

View file

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

View file

@ -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);
}
}
}

View file

@ -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"

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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>

View file

@ -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)
{

View file

@ -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
};

View file

@ -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);
}
}

View file

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

View file

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

View file

@ -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}";

View file

@ -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

View file

@ -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)

View file

@ -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;
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}
}

View file

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

View file

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

View file

@ -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;
}

View file

@ -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;
}