mirror of
https://github.com/bitwarden/mobile
synced 2025-12-24 04:04:34 +00: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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user