1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-15 07:43:37 +00:00

[PM-2713] add migration for pin on lock screens

This commit is contained in:
Jacob Fink
2023-07-18 21:23:50 -04:00
parent bdfe806846
commit 7c664f58b3
12 changed files with 363 additions and 215 deletions

View File

@@ -46,7 +46,7 @@
<StackLayout StyleClass="box"> <StackLayout StyleClass="box">
<Grid <Grid
StyleClass="box-row" StyleClass="box-row"
IsVisible="{Binding PinLock}" IsVisible="{Binding PinEnabled}"
Padding="0, 10, 0, 0"> Padding="0, 10, 0, 0">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
@@ -89,7 +89,7 @@
<Grid <Grid
x:Name="_passwordGrid" x:Name="_passwordGrid"
StyleClass="box-row" StyleClass="box-row"
IsVisible="{Binding PinLock, Converter={StaticResource inverseBool}}" IsVisible="{Binding PinEnabled, Converter={StaticResource inverseBool}}"
Padding="0, 10, 0, 0"> Padding="0, 10, 0, 0">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />

View File

@@ -44,7 +44,7 @@ namespace Bit.App.Pages
{ {
get get
{ {
if (_vm?.PinLock ?? false) if (_vm?.PinEnabled ?? false)
{ {
return _pin; return _pin;
} }
@@ -54,7 +54,7 @@ namespace Bit.App.Pages
public async Task PromptBiometricAfterResumeAsync() public async Task PromptBiometricAfterResumeAsync()
{ {
if (_vm.BiometricLock) if (_vm.BiometricEnabled)
{ {
await Task.Delay(500); await Task.Delay(500);
if (!_promptedAfterResume) if (!_promptedAfterResume)
@@ -91,13 +91,13 @@ namespace Bit.App.Pages
_vm.FocusSecretEntry += PerformFocusSecretEntry; _vm.FocusSecretEntry += PerformFocusSecretEntry;
if (!_vm.BiometricLock) if (!_vm.BiometricEnabled)
{ {
RequestFocus(SecretEntry); RequestFocus(SecretEntry);
} }
else else
{ {
if (_vm.UsingKeyConnector && !_vm.PinLock) if (_vm.UsingKeyConnector && !_vm.PinEnabled)
{ {
_passwordGrid.IsVisible = false; _passwordGrid.IsVisible = false;
_unlockButton.IsVisible = false; _unlockButton.IsVisible = false;

View File

@@ -38,16 +38,15 @@ namespace Bit.App.Pages
private string _masterPassword; private string _masterPassword;
private string _pin; private string _pin;
private bool _showPassword; private bool _showPassword;
private bool _pinLock; private PinLockEnum _pinStatus;
private bool _biometricLock; private bool _pinEnabled;
private bool _biometricEnabled;
private bool _biometricIntegrityValid = true; private bool _biometricIntegrityValid = true;
private bool _biometricButtonVisible; private bool _biometricButtonVisible;
private bool _usingKeyConnector; private bool _usingKeyConnector;
private string _biometricButtonText; private string _biometricButtonText;
private string _loggedInAsText; private string _loggedInAsText;
private string _lockedVerifyText; private string _lockedVerifyText;
private bool _isPinProtected;
private bool _isPinProtectedWithKey;
public LockPageViewModel() public LockPageViewModel()
{ {
@@ -100,10 +99,10 @@ namespace Bit.App.Pages
}); });
} }
public bool PinLock public bool PinEnabled
{ {
get => _pinLock; get => _pinEnabled;
set => SetProperty(ref _pinLock, value); set => SetProperty(ref _pinEnabled, value);
} }
public bool UsingKeyConnector public bool UsingKeyConnector
@@ -111,10 +110,10 @@ namespace Bit.App.Pages
get => _usingKeyConnector; get => _usingKeyConnector;
} }
public bool BiometricLock public bool BiometricEnabled
{ {
get => _biometricLock; get => _biometricEnabled;
set => SetProperty(ref _biometricLock, value); set => SetProperty(ref _biometricEnabled, value);
} }
public bool BiometricIntegrityValid public bool BiometricIntegrityValid
@@ -162,14 +161,18 @@ namespace Bit.App.Pages
public async Task InitAsync() public async Task InitAsync()
{ {
(_isPinProtected, _isPinProtectedWithKey) = await _vaultTimeoutService.IsPinLockSetAsync(); _pinStatus = await _vaultTimeoutService.IsPinLockSetAsync();
PinLock = (_isPinProtected && await _stateService.GetPinProtectedKeyAsync() != null) ||
_isPinProtectedWithKey; var ephemeralPinSet = await _stateService.GetUserKeyPinEphemeralAsync()
BiometricLock = await _vaultTimeoutService.IsBiometricLockSetAsync() && await _cryptoService.HasKeyAsync(); ?? await _stateService.GetPinProtectedKeyAsync();
PinEnabled = (_pinStatus == PinLockEnum.Transient && ephemeralPinSet != null) ||
_pinStatus == PinLockEnum.Persistent;
BiometricEnabled = await _vaultTimeoutService.IsBiometricLockSetAsync() && await _cryptoService.HasEncryptedUserKeyAsync();
// Users with key connector and without biometric or pin has no MP to unlock with // Users with key connector and without biometric or pin has no MP to unlock with
_usingKeyConnector = await _keyConnectorService.GetUsesKeyConnector(); _usingKeyConnector = await _keyConnectorService.GetUsesKeyConnector();
if (_usingKeyConnector && !(BiometricLock || PinLock)) if (_usingKeyConnector && !(BiometricEnabled || PinEnabled))
{ {
await _vaultTimeoutService.LogOutAsync(); await _vaultTimeoutService.LogOutAsync();
return; return;
@@ -188,7 +191,7 @@ namespace Bit.App.Pages
} }
var webVaultHostname = CoreHelpers.GetHostname(webVault); var webVaultHostname = CoreHelpers.GetHostname(webVault);
LoggedInAsText = string.Format(AppResources.LoggedInAsOn, _email, webVaultHostname); LoggedInAsText = string.Format(AppResources.LoggedInAsOn, _email, webVaultHostname);
if (PinLock) if (PinEnabled)
{ {
PageTitle = AppResources.VerifyPIN; PageTitle = AppResources.VerifyPIN;
LockedVerifyText = AppResources.VaultLockedPIN; LockedVerifyText = AppResources.VaultLockedPIN;
@@ -207,7 +210,7 @@ namespace Bit.App.Pages
} }
} }
if (BiometricLock) if (BiometricEnabled)
{ {
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync(); BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync();
if (!_biometricIntegrityValid) if (!_biometricIntegrityValid)
@@ -229,14 +232,14 @@ namespace Bit.App.Pages
public async Task SubmitAsync() public async Task SubmitAsync()
{ {
if (PinLock && string.IsNullOrWhiteSpace(Pin)) if (PinEnabled && string.IsNullOrWhiteSpace(Pin))
{ {
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
string.Format(AppResources.ValidationFieldRequired, AppResources.PIN), string.Format(AppResources.ValidationFieldRequired, AppResources.PIN),
AppResources.Ok); AppResources.Ok);
return; return;
} }
if (!PinLock && string.IsNullOrWhiteSpace(MasterPassword)) if (!PinEnabled && string.IsNullOrWhiteSpace(MasterPassword))
{ {
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword), string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword),
@@ -247,34 +250,54 @@ namespace Bit.App.Pages
ShowPassword = false; ShowPassword = false;
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile)); var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
if (PinLock) if (PinEnabled)
{ {
var failed = true; var failed = true;
try try
{ {
if (_isPinProtected) EncString userKeyPin = null;
EncString oldPinProtected = null;
if (_pinStatus == PinLockEnum.Persistent)
{ {
var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email, userKeyPin = await _stateService.GetUserKeyPinAsync();
var oldEncryptedKey = await _stateService.GetPinProtectedAsync();
oldPinProtected = oldEncryptedKey != null ? new EncString(oldEncryptedKey) : null;
}
else if (_pinStatus == PinLockEnum.Transient)
{
userKeyPin = await _stateService.GetUserKeyPinEphemeralAsync();
oldPinProtected = await _stateService.GetPinProtectedKeyAsync();
}
UserKey userKey;
if (oldPinProtected != null)
{
userKey = await _cryptoService.DecryptAndMigrateOldPinKeyAsync(
_pinStatus == PinLockEnum.Transient,
Pin,
_email,
kdfConfig, kdfConfig,
await _stateService.GetPinProtectedKeyAsync()); oldPinProtected
var encKey = await _cryptoService.GetEncKeyAsync(key); );
var protectedPin = await _stateService.GetProtectedPinAsync();
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
failed = decPin != Pin;
if (!failed)
{
Pin = string.Empty;
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key);
}
} }
else else
{ {
var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email, kdfConfig); userKey = await _cryptoService.DecryptUserKeyWithPinAsync(
failed = false; Pin,
_email,
kdfConfig,
userKeyPin
);
}
var protectedPin = await _stateService.GetProtectedPinAsync();
var decryptedPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), userKey);
failed = decryptedPin != Pin;
if (!failed)
{
Pin = string.Empty; Pin = string.Empty;
await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key); await SetKeyAndContinueAsync(userKey);
} }
} }
catch catch
@@ -302,10 +325,12 @@ namespace Bit.App.Pages
if (storedKeyHash != null) if (storedKeyHash != null)
{ {
// Offline unlock possible
passwordValid = await _cryptoService.CompareAndUpdatePasswordHashAsync(MasterPassword, masterKey); passwordValid = await _cryptoService.CompareAndUpdatePasswordHashAsync(MasterPassword, masterKey);
} }
else else
{ {
// Online unlock required
await _deviceActionService.ShowLoadingAsync(AppResources.Loading); await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
var keyHash = await _cryptoService.HashPasswordAsync(MasterPassword, masterKey, HashPurpose.ServerAuthorization); var keyHash = await _cryptoService.HashPasswordAsync(MasterPassword, masterKey, HashPurpose.ServerAuthorization);
var request = new PasswordVerificationRequest(); var request = new PasswordVerificationRequest();
@@ -327,16 +352,6 @@ namespace Bit.App.Pages
} }
if (passwordValid) if (passwordValid)
{ {
// TODO(Jake): Update this to use new PinKeyEphemeral
if (_isPinProtected)
{
var protectedPin = await _stateService.GetProtectedPinAsync();
var encKey = await _cryptoService.GetEncKeyAsync(masterKey);
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
var pinKey = await _cryptoService.MakePinKeyAsync(decPin, _email, kdfConfig);
await _stateService.SetPinProtectedKeyAsync(await _cryptoService.EncryptAsync(masterKey.Key, pinKey));
}
if (await RequirePasswordChangeAsync(enforcedMasterPasswordOptions)) if (await RequirePasswordChangeAsync(enforcedMasterPasswordOptions))
{ {
// Save the ForcePasswordResetReason to force a password reset after unlock // Save the ForcePasswordResetReason to force a password reset after unlock
@@ -346,10 +361,13 @@ namespace Bit.App.Pages
MasterPassword = string.Empty; MasterPassword = string.Empty;
await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(masterKey);
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
await _cryptoService.SetMasterKeyAsync(masterKey);
await SetKeyAndContinueAsync(userKey);
// Re-enable biometrics // Re-enable biometrics
if (BiometricLock & !BiometricIntegrityValid) if (BiometricEnabled & !BiometricIntegrityValid)
{ {
await _biometricService.SetupBiometricAsync(); await _biometricService.SetupBiometricAsync();
} }
@@ -426,7 +444,7 @@ namespace Bit.App.Pages
public void TogglePassword() public void TogglePassword()
{ {
ShowPassword = !ShowPassword; ShowPassword = !ShowPassword;
var secret = PinLock ? Pin : MasterPassword; var secret = PinEnabled ? Pin : MasterPassword;
_secretEntryFocusWeakEventManager.RaiseEvent(string.IsNullOrEmpty(secret) ? 0 : secret.Length, nameof(FocusSecretEntry)); _secretEntryFocusWeakEventManager.RaiseEvent(string.IsNullOrEmpty(secret) ? 0 : secret.Length, nameof(FocusSecretEntry));
} }
@@ -434,12 +452,12 @@ namespace Bit.App.Pages
{ {
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync(); BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync();
BiometricButtonVisible = BiometricIntegrityValid; BiometricButtonVisible = BiometricIntegrityValid;
if (!BiometricLock || !BiometricIntegrityValid) if (!BiometricEnabled || !BiometricIntegrityValid)
{ {
return; return;
} }
var success = await _platformUtilsService.AuthenticateBiometricAsync(null, var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
PinLock ? AppResources.PIN : AppResources.MasterPassword, PinEnabled ? AppResources.PIN : AppResources.MasterPassword,
() => _secretEntryFocusWeakEventManager.RaiseEvent((int?)null, nameof(FocusSecretEntry))); () => _secretEntryFocusWeakEventManager.RaiseEvent((int?)null, nameof(FocusSecretEntry)));
await _stateService.SetBiometricLockedAsync(!success); await _stateService.SetBiometricLockedAsync(!success);
if (success) if (success)
@@ -448,7 +466,6 @@ namespace Bit.App.Pages
} }
} }
// TODO(Jake): Update to store UserKey
private async Task SetKeyAndContinueAsync(UserKey key) private async Task SetKeyAndContinueAsync(UserKey key)
{ {
var hasKey = await _cryptoService.HasUserKeyAsync(); var hasKey = await _cryptoService.HasUserKeyAsync();

View File

@@ -139,7 +139,7 @@ namespace Bit.App.Pages
} }
var pinSet = await _vaultTimeoutService.IsPinLockSetAsync(); var pinSet = await _vaultTimeoutService.IsPinLockSetAsync();
_pin = pinSet.Item1 || pinSet.Item2; _pin = pinSet != PinLockEnum.Disabled;
_biometric = await _vaultTimeoutService.IsBiometricLockSetAsync(); _biometric = await _vaultTimeoutService.IsBiometricLockSetAsync();
_screenCaptureAllowed = await _stateService.GetScreenCaptureAllowedAsync(); _screenCaptureAllowed = await _stateService.GetScreenCaptureAllowedAsync();

View File

@@ -13,6 +13,7 @@ namespace Bit.Core.Abstractions
Task SetUserKeyAsync(UserKey userKey, string userId = null); Task SetUserKeyAsync(UserKey userKey, string userId = null);
Task<UserKey> GetUserKeyAsync(string userId = null); Task<UserKey> GetUserKeyAsync(string userId = null);
Task<bool> HasUserKeyAsync(string userId = null); Task<bool> HasUserKeyAsync(string userId = null);
Task<bool> HasEncryptedUserKeyAsync(string userId = null);
Task<UserKey> MakeUserKeyAsync(); Task<UserKey> MakeUserKeyAsync();
Task ClearUserKeyAsync(string userId = null); Task ClearUserKeyAsync(string userId = null);
Task SetMasterKeyEncryptedUserKeyAsync(string value, string userId = null); Task SetMasterKeyEncryptedUserKeyAsync(string value, string userId = null);
@@ -38,7 +39,8 @@ namespace Bit.Core.Abstractions
Task<Tuple<string, EncString>> MakeKeyPairAsync(SymmetricCryptoKey key = null); Task<Tuple<string, EncString>> MakeKeyPairAsync(SymmetricCryptoKey key = null);
Task ClearKeyPairAsync(bool memoryOnly = false, string userId = null); Task ClearKeyPairAsync(bool memoryOnly = false, string userId = null);
Task<PinKey> MakePinKeyAsync(string pin, string salt, KdfConfig config); Task<PinKey> MakePinKeyAsync(string pin, string salt, KdfConfig config);
// Task<UserKey> DecryptUserKeyWithPin(string pin, string salt, KdfConfig kdfConfig, EncString pinProtectedUserKey = null); Task<UserKey> DecryptUserKeyWithPinAsync(string pin, string salt, KdfConfig kdfConfig, EncString pinProtectedUserKey = null);
Task<MasterKey> DecryptMasterKeyWithPinAsync(string pin, string salt, KdfConfig kdfConfig, EncString pinProtectedMasterKey = null);
Task<SymmetricCryptoKey> MakeSendKeyAsync(byte[] keyMaterial); Task<SymmetricCryptoKey> MakeSendKeyAsync(byte[] keyMaterial);
// TODO(Jake): This isn't used, delete? // TODO(Jake): This isn't used, delete?
Task ClearKeysAsync(string userId = null); Task ClearKeysAsync(string userId = null);
@@ -52,6 +54,7 @@ namespace Bit.Core.Abstractions
Task<EncString> EncryptAsync(byte[] plainValue, SymmetricCryptoKey key = null); Task<EncString> EncryptAsync(byte[] plainValue, SymmetricCryptoKey key = null);
Task<EncString> EncryptAsync(string plainValue, SymmetricCryptoKey key = null); Task<EncString> EncryptAsync(string plainValue, SymmetricCryptoKey key = null);
Task<EncByteArray> EncryptToBytesAsync(byte[] plainValue, SymmetricCryptoKey key = null); Task<EncByteArray> EncryptToBytesAsync(byte[] plainValue, SymmetricCryptoKey key = null);
Task<UserKey> DecryptAndMigrateOldPinKeyAsync(bool masterPasswordOnRestart, string pin, string email, KdfConfig kdfConfig, EncString oldPinKey);

View File

@@ -44,7 +44,7 @@ namespace Bit.Core.Abstractions
Task<bool> CanAccessPremiumAsync(string userId = null); Task<bool> CanAccessPremiumAsync(string userId = null);
Task<string> GetProtectedPinAsync(string userId = null); Task<string> GetProtectedPinAsync(string userId = null);
Task SetPersonalPremiumAsync(bool value, string userId = null); Task SetPersonalPremiumAsync(bool value, string userId = null);
Task<string> GetUserKeyPinAsync(string userId = null); Task<EncString> GetUserKeyPinAsync(string userId = null);
Task SetUserKeyPinAsync(EncString value, string userId = null); Task SetUserKeyPinAsync(EncString value, string userId = null);
Task<EncString> GetUserKeyPinEphemeralAsync(string userId = null); Task<EncString> GetUserKeyPinEphemeralAsync(string userId = null);
Task SetUserKeyPinEphemeralAsync(EncString value, string userId = null); Task SetUserKeyPinEphemeralAsync(EncString value, string userId = null);

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Services;
namespace Bit.Core.Abstractions namespace Bit.Core.Abstractions
{ {
@@ -16,7 +17,7 @@ namespace Bit.Core.Abstractions
Task<bool> ShouldLockAsync(string userId = null); Task<bool> ShouldLockAsync(string userId = null);
Task<bool> IsLoggedOutByTimeoutAsync(string userId = null); Task<bool> IsLoggedOutByTimeoutAsync(string userId = null);
Task<bool> ShouldLogOutByTimeoutAsync(string userId = null); Task<bool> ShouldLogOutByTimeoutAsync(string userId = null);
Task<Tuple<bool, bool>> IsPinLockSetAsync(string userId = null); Task<PinLockEnum> IsPinLockSetAsync(string userId = null);
Task<bool> IsBiometricLockSetAsync(string userId = null); Task<bool> IsBiometricLockSetAsync(string userId = null);
Task LockAsync(bool allowSoftLock = false, bool userInitiated = false, string userId = null); Task LockAsync(bool allowSoftLock = false, bool userInitiated = false, string userId = null);
Task LogOutAsync(bool userInitiated = true, string userId = null); Task LogOutAsync(bool userInitiated = true, string userId = null);

View File

@@ -73,6 +73,11 @@ namespace Bit.Core.Services
return await GetUserKeyAsync(userId) != null; return await GetUserKeyAsync(userId) != null;
} }
public async Task<bool> HasEncryptedUserKeyAsync(string userId = null)
{
return await _stateService.GetUserKeyMasterKeyAsync(userId) != null;
}
public async Task<UserKey> MakeUserKeyAsync() public async Task<UserKey> MakeUserKeyAsync()
{ {
return new UserKey(await _cryptoFunctionService.RandomBytesAsync(64)); return new UserKey(await _cryptoFunctionService.RandomBytesAsync(64));
@@ -418,18 +423,39 @@ namespace Bit.Core.Services
await clearDeprecatedPinKeysAsync(userId); await clearDeprecatedPinKeysAsync(userId);
} }
// public async Task<UserKey> DecryptUserKeyWithPin(string pin, string salt, KdfConfig kdfConfig, EncString pinProtectedUserKey = null) public async Task<UserKey> DecryptUserKeyWithPinAsync(string pin, string salt, KdfConfig kdfConfig, EncString pinProtectedUserKey = null)
// { {
// pinProtectedUserKey ??= await _stateService.GetUserKeyPinAsync(); pinProtectedUserKey ??= await _stateService.GetUserKeyPinAsync();
// pinProtectedUserKey ??= await _stateService.GetUserKeyPinEphemeralAsync(); pinProtectedUserKey ??= await _stateService.GetUserKeyPinEphemeralAsync();
// if (pinProtectedUserKey == null) if (pinProtectedUserKey == null)
// { {
// throw new Exception("No PIN protected user key found."); throw new Exception("No PIN protected user key found.");
// } }
// var pinKey = await MakePinKeyAsync(pin, salt, kdfConfig); var pinKey = await MakePinKeyAsync(pin, salt, kdfConfig);
// var userKey = await DecryptToBytesAsync(pinProtectedUserKey, pinKey); var userKey = await DecryptToBytesAsync(pinProtectedUserKey, pinKey);
// return new UserKey(userKey); return new UserKey(userKey);
// } }
// Only for migration purposes
public async Task<MasterKey> DecryptMasterKeyWithPinAsync(
string pin,
string salt,
KdfConfig kdfConfig,
EncString pinProtectedMasterKey = null)
{
if (pinProtectedMasterKey == null)
{
var pinProtectedMasterKeyString = await _stateService.GetPinProtectedAsync();
if (pinProtectedMasterKeyString == null)
{
throw new Exception("No PIN protected master key found.");
}
pinProtectedMasterKey = new EncString(pinProtectedMasterKeyString);
}
var pinKey = await MakePinKeyAsync(pin, salt, kdfConfig);
var masterKey = await DecryptToBytesAsync(pinProtectedMasterKey, pinKey);
return new MasterKey(masterKey);
}
public async Task<SymmetricCryptoKey> MakeSendKeyAsync(byte[] keyMaterial) public async Task<SymmetricCryptoKey> MakeSendKeyAsync(byte[] keyMaterial)
{ {
@@ -932,10 +958,52 @@ namespace Bit.Core.Services
public SymmetricCryptoKey Key { get; set; } public SymmetricCryptoKey Key { get; set; }
} }
// --LEGACY METHODS-- // --MIGRATION METHODS--
// We previously used the master key for additional keys, but now we use the user key. // We previously used the master key for additional keys, but now we use the user key.
// These methods support migrating the old keys to the new ones. // These methods support migrating the old keys to the new ones.
public async Task<UserKey> DecryptAndMigrateOldPinKeyAsync(
bool masterPasswordOnRestart,
string pin,
string email,
KdfConfig kdfConfig,
EncString oldPinKey)
{
// Decrypt
var masterKey = await DecryptMasterKeyWithPinAsync(
pin,
email,
kdfConfig,
oldPinKey
);
var encUserKey = await _stateService.GetEncKeyEncryptedAsync();
var userKey = await DecryptUserKeyWithMasterKeyAsync(
masterKey,
new EncString(encUserKey)
);
// Migrate
var pinKey = await MakePinKeyAsync(pin, email, kdfConfig);
var pinProtectedKey = await EncryptAsync(userKey.Key, pinKey);
if (masterPasswordOnRestart)
{
await _stateService.SetPinProtectedKeyAsync(null);
await _stateService.SetUserKeyPinEphemeralAsync(pinProtectedKey);
}
else
{
await _stateService.SetPinProtectedAsync(null);
await _stateService.SetUserKeyPinAsync(pinProtectedKey);
// We previously only set the protected pin if MP on Restart was enabled
// now we set it regardless
var encPin = await EncryptAsync(pin, userKey);
await _stateService.SetProtectedPinAsync(encPin.EncryptedString);
}
return userKey;
}
public async Task clearDeprecatedPinKeysAsync(string userId = null) public async Task clearDeprecatedPinKeysAsync(string userId = null)
{ {
await _stateService.SetPinProtectedAsync(null); await _stateService.SetPinProtectedAsync(null);

View File

@@ -396,9 +396,9 @@ namespace Bit.Core.Services
} }
// TODO(Jake): Does this need to be secure storage? // TODO(Jake): Does this need to be secure storage?
public async Task<string> GetUserKeyPinAsync(string userId = null) public async Task<EncString> GetUserKeyPinAsync(string userId = null)
{ {
return await _storageMediatorService.GetAsync<string>(Constants.UserKeyPinKey(userId), false); return new EncString(await _storageMediatorService.GetAsync<string>(Constants.UserKeyPinKey(userId), false));
} }
// TODO(Jake): Does this need to be secure storage? // TODO(Jake): Does this need to be secure storage?

View File

@@ -7,6 +7,13 @@ using Bit.Core.Models.Domain;
namespace Bit.Core.Services namespace Bit.Core.Services
{ {
public enum PinLockEnum
{
Disabled,
Persistent,
Transient
}
public class VaultTimeoutService : IVaultTimeoutService public class VaultTimeoutService : IVaultTimeoutService
{ {
private readonly ICryptoService _cryptoService; private readonly ICryptoService _cryptoService;
@@ -165,11 +172,13 @@ namespace Bit.Core.Services
if (await _keyConnectorService.GetUsesKeyConnector()) if (await _keyConnectorService.GetUsesKeyConnector())
{ {
var (isPinProtected, isPinProtectedWithKey) = await IsPinLockSetAsync(userId); var pinStatus = await IsPinLockSetAsync(userId);
var pinLock = (isPinProtected && await _stateService.GetPinProtectedKeyAsync(userId) != null) || var ephemeralPinSet = await _stateService.GetUserKeyPinEphemeralAsync()
isPinProtectedWithKey; ?? await _stateService.GetPinProtectedKeyAsync();
var pinEnabled = (pinStatus == PinLockEnum.Transient && ephemeralPinSet != null) ||
pinStatus == PinLockEnum.Persistent;
if (!pinLock && !await IsBiometricLockSetAsync()) if (!pinEnabled && !await IsBiometricLockSetAsync())
{ {
await LogOutAsync(userInitiated, userId); await LogOutAsync(userInitiated, userId);
return; return;
@@ -218,11 +227,26 @@ namespace Bit.Core.Services
await _tokenService.ToggleTokensAsync(); await _tokenService.ToggleTokensAsync();
} }
public async Task<Tuple<bool, bool>> IsPinLockSetAsync(string userId = null) public async Task<PinLockEnum> IsPinLockSetAsync(string userId = null)
{ {
var protectedPin = await _stateService.GetProtectedPinAsync(userId); // we can't depend on only the protected pin being set because old
var pinProtectedKey = await _stateService.GetPinProtectedAsync(userId); // versions only used it for MP on Restart
return new Tuple<bool, bool>(protectedPin != null, pinProtectedKey != null); var pinIsEnabled = await _stateService.GetProtectedPinAsync(userId);
var userKeyPin = await _stateService.GetUserKeyPinAsync(userId);
var oldUserKeyPin = await _stateService.GetPinProtectedAsync(userId);
if (userKeyPin != null || oldUserKeyPin != null)
{
return PinLockEnum.Persistent;
}
else if (pinIsEnabled != null && userKeyPin == null && oldUserKeyPin == null)
{
return PinLockEnum.Transient;
}
else
{
return PinLockEnum.Disabled;
}
} }
public async Task<bool> IsBiometricLockSetAsync(string userId = null) public async Task<bool> IsBiometricLockSetAsync(string userId = null)

View File

@@ -8,11 +8,13 @@ using Bit.App.Utilities;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Bit.iOS.Core.Utilities; using Bit.iOS.Core.Utilities;
using Bit.iOS.Core.Views; using Bit.iOS.Core.Views;
using Foundation; using Foundation;
using UIKit; using UIKit;
using Xamarin.Essentials;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.iOS.Core.Controllers namespace Bit.iOS.Core.Controllers
@@ -28,10 +30,9 @@ namespace Bit.iOS.Core.Controllers
private IBiometricService _biometricService; private IBiometricService _biometricService;
private IKeyConnectorService _keyConnectorService; private IKeyConnectorService _keyConnectorService;
private IAccountsManager _accountManager; private IAccountsManager _accountManager;
private bool _isPinProtected; private PinLockEnum _pinStatus;
private bool _isPinProtectedWithKey; private bool _pinEnabled;
private bool _pinLock; private bool _biometricEnabled;
private bool _biometricLock;
private bool _biometricIntegrityValid = true; private bool _biometricIntegrityValid = true;
private bool _passwordReprompt = false; private bool _passwordReprompt = false;
private bool _usesKeyConnector; private bool _usesKeyConnector;
@@ -85,7 +86,7 @@ namespace Bit.iOS.Core.Controllers
} }
public abstract UITableView TableView { get; } public abstract UITableView TableView { get; }
public override async void ViewDidLoad() public override async void ViewDidLoad()
{ {
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService"); _vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
@@ -103,25 +104,28 @@ namespace Bit.iOS.Core.Controllers
if (autofillExtension && await _stateService.GetPasswordRepromptAutofillAsync()) if (autofillExtension && await _stateService.GetPasswordRepromptAutofillAsync())
{ {
_passwordReprompt = true; _passwordReprompt = true;
_isPinProtected = false; _pinStatus = PinLockEnum.Disabled;
_isPinProtectedWithKey = false; _pinEnabled = false;
_pinLock = false; _biometricEnabled = false;
_biometricLock = false;
} }
else else
{ {
(_isPinProtected, _isPinProtectedWithKey) = await _vaultTimeoutService.IsPinLockSetAsync(); _pinStatus = await _vaultTimeoutService.IsPinLockSetAsync();
_pinLock = (_isPinProtected && await _stateService.GetPinProtectedKeyAsync() != null) ||
_isPinProtectedWithKey; var ephemeralPinSet = await _stateService.GetUserKeyPinEphemeralAsync()
_biometricLock = await _vaultTimeoutService.IsBiometricLockSetAsync() && ?? await _stateService.GetPinProtectedKeyAsync();
await _cryptoService.HasKeyAsync(); _pinEnabled = (_pinStatus == PinLockEnum.Transient && ephemeralPinSet != null) ||
_pinStatus == PinLockEnum.Persistent;
_biometricEnabled = await _vaultTimeoutService.IsBiometricLockSetAsync()
&& await _cryptoService.HasEncryptedUserKeyAsync();
_biometricIntegrityValid = _biometricIntegrityValid =
await _platformUtilsService.IsBiometricIntegrityValidAsync(BiometricIntegritySourceKey); await _platformUtilsService.IsBiometricIntegrityValidAsync(BiometricIntegritySourceKey);
_usesKeyConnector = await _keyConnectorService.GetUsesKeyConnector(); _usesKeyConnector = await _keyConnectorService.GetUsesKeyConnector();
_biometricUnlockOnly = _usesKeyConnector && _biometricLock && !_pinLock; _biometricUnlockOnly = _usesKeyConnector && _biometricEnabled && !_pinEnabled;
} }
if (_pinLock) if (_pinEnabled)
{ {
BaseNavItem.Title = AppResources.VerifyPIN; BaseNavItem.Title = AppResources.VerifyPIN;
} }
@@ -150,7 +154,7 @@ namespace Bit.iOS.Core.Controllers
if (!_biometricUnlockOnly) if (!_biometricUnlockOnly)
{ {
MasterPasswordCell.Label.Text = _pinLock ? AppResources.PIN : AppResources.MasterPassword; MasterPasswordCell.Label.Text = _pinEnabled ? AppResources.PIN : AppResources.MasterPassword;
MasterPasswordCell.TextField.SecureTextEntry = true; MasterPasswordCell.TextField.SecureTextEntry = true;
MasterPasswordCell.TextField.ReturnKeyType = UIReturnKeyType.Go; MasterPasswordCell.TextField.ReturnKeyType = UIReturnKeyType.Go;
MasterPasswordCell.TextField.ShouldReturn += (UITextField tf) => MasterPasswordCell.TextField.ShouldReturn += (UITextField tf) =>
@@ -158,7 +162,7 @@ namespace Bit.iOS.Core.Controllers
CheckPasswordAsync().FireAndForget(); CheckPasswordAsync().FireAndForget();
return true; return true;
}; };
if (_pinLock) if (_pinEnabled)
{ {
MasterPasswordCell.TextField.KeyboardType = UIKeyboardType.NumberPad; MasterPasswordCell.TextField.KeyboardType = UIKeyboardType.NumberPad;
} }
@@ -177,7 +181,7 @@ namespace Bit.iOS.Core.Controllers
base.ViewDidLoad(); base.ViewDidLoad();
if (_biometricLock) if (_biometricEnabled)
{ {
if (!_biometricIntegrityValid) if (!_biometricIntegrityValid)
{ {
@@ -198,18 +202,18 @@ namespace Bit.iOS.Core.Controllers
// Users with key connector and without biometric or pin has no MP to unlock with // Users with key connector and without biometric or pin has no MP to unlock with
if (_usesKeyConnector) if (_usesKeyConnector)
{ {
if (!(_pinLock || _biometricLock) || if (!(_pinEnabled || _biometricEnabled) ||
(_biometricLock && !_biometricIntegrityValid)) (_biometricEnabled && !_biometricIntegrityValid))
{ {
PromptSSO(); PromptSSO();
} }
} }
else if (!_biometricLock || !_biometricIntegrityValid) else if (!_biometricEnabled || !_biometricIntegrityValid)
{ {
MasterPasswordCell.TextField.BecomeFirstResponder(); MasterPasswordCell.TextField.BecomeFirstResponder();
} }
} }
protected async Task CheckPasswordAsync() protected async Task CheckPasswordAsync()
{ {
if (_checkingPassword) if (_checkingPassword)
@@ -224,7 +228,7 @@ namespace Bit.iOS.Core.Controllers
{ {
var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred, var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred,
string.Format(AppResources.ValidationFieldRequired, string.Format(AppResources.ValidationFieldRequired,
_pinLock ? AppResources.PIN : AppResources.MasterPassword), _pinEnabled ? AppResources.PIN : AppResources.MasterPassword),
AppResources.Ok); AppResources.Ok);
PresentViewController(alert, true, null); PresentViewController(alert, true, null);
return; return;
@@ -246,33 +250,53 @@ namespace Bit.iOS.Core.Controllers
return; return;
} }
if (_pinLock) if (_pinEnabled)
{ {
var failed = true; var failed = true;
try try
{ {
if (_isPinProtected) EncString userKeyPin = null;
EncString oldPinProtected = null;
if (_pinStatus == PinLockEnum.Persistent)
{ {
var key = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email, userKeyPin = await _stateService.GetUserKeyPinAsync();
var oldEncryptedKey = await _stateService.GetPinProtectedAsync();
oldPinProtected = oldEncryptedKey != null ? new EncString(oldEncryptedKey) : null;
}
else if (_pinStatus == PinLockEnum.Transient)
{
userKeyPin = await _stateService.GetUserKeyPinEphemeralAsync();
oldPinProtected = await _stateService.GetPinProtectedKeyAsync();
}
UserKey userKey;
if (oldPinProtected != null)
{
userKey = await _cryptoService.DecryptAndMigrateOldPinKeyAsync(
_pinStatus == PinLockEnum.Transient,
inputtedValue,
email,
kdfConfig, kdfConfig,
await _stateService.GetPinProtectedKeyAsync()); oldPinProtected
var encKey = await _cryptoService.GetEncKeyAsync(key); );
var protectedPin = await _stateService.GetProtectedPinAsync();
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
failed = decPin != inputtedValue;
if (!failed)
{
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key);
}
} }
else else
{ {
var key2 = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email, userKey = await _cryptoService.DecryptUserKeyWithPinAsync(
kdfConfig); inputtedValue,
failed = false; email,
kdfConfig,
userKeyPin
);
}
var protectedPin = await _stateService.GetProtectedPinAsync();
var decryptedPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), userKey);
failed = decryptedPin != inputtedValue;
if (!failed)
{
await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key2); await SetKeyAndContinueAsync(userKey);
} }
} }
catch catch
@@ -286,33 +310,27 @@ namespace Bit.iOS.Core.Controllers
} }
else else
{ {
var key2 = await _cryptoService.MakeKeyAsync(inputtedValue, email, kdfConfig); var masterKey = await _cryptoService.MakeMasterKeyAsync(inputtedValue, email, kdfConfig);
var storedKeyHash = await _cryptoService.GetKeyHashAsync(); var storedPasswordHash = await _cryptoService.GetPasswordHashAsync();
if (storedKeyHash == null) if (storedPasswordHash == null)
{ {
var oldKey = await _secureStorageService.GetAsync<string>("oldKey"); var oldKey = await _secureStorageService.GetAsync<string>("oldKey");
if (key2.KeyB64 == oldKey) if (masterKey.KeyB64 == oldKey)
{ {
var localKeyHash = await _cryptoService.HashPasswordAsync(inputtedValue, key2, HashPurpose.LocalAuthorization); var localPasswordHash = await _cryptoService.HashPasswordAsync(inputtedValue, masterKey, HashPurpose.LocalAuthorization);
await _secureStorageService.RemoveAsync("oldKey"); await _secureStorageService.RemoveAsync("oldKey");
await _cryptoService.SetKeyHashAsync(localKeyHash); await _cryptoService.SetPasswordHashAsync(localPasswordHash);
} }
} }
var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(inputtedValue, key2); var passwordValid = await _cryptoService.CompareAndUpdatePasswordHashAsync(inputtedValue, masterKey);
if (passwordValid) if (passwordValid)
{ {
if (_isPinProtected)
{
var protectedPin = await _stateService.GetProtectedPinAsync();
var encKey = await _cryptoService.GetEncKeyAsync(key2);
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, email,
kdfConfig);
await _stateService.SetPinProtectedKeyAsync(await _cryptoService.EncryptAsync(key2.Key, pinKey));
}
await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key2, true);
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
await _cryptoService.SetMasterKeyAsync(masterKey);
await SetKeyAndContinueAsync(userKey, true);
} }
else else
{ {
@@ -339,12 +357,12 @@ namespace Bit.iOS.Core.Controllers
public async Task PromptBiometricAsync() public async Task PromptBiometricAsync()
{ {
if (!_biometricLock || !_biometricIntegrityValid) if (!_biometricEnabled || !_biometricIntegrityValid)
{ {
return; return;
} }
var success = await _platformUtilsService.AuthenticateBiometricAsync(null, var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
_pinLock ? AppResources.PIN : AppResources.MasterPassword, _pinEnabled ? AppResources.PIN : AppResources.MasterPassword,
() => MasterPasswordCell.TextField.BecomeFirstResponder()); () => MasterPasswordCell.TextField.BecomeFirstResponder());
await _stateService.SetBiometricLockedAsync(!success); await _stateService.SetBiometricLockedAsync(!success);
if (success) if (success)
@@ -371,12 +389,12 @@ namespace Bit.iOS.Core.Controllers
PresentViewController(loginController, true, null); PresentViewController(loginController, true, null);
} }
private async Task SetKeyAndContinueAsync(SymmetricCryptoKey key, bool masterPassword = false) private async Task SetKeyAndContinueAsync(UserKey userKey, bool masterPassword = false)
{ {
var hasKey = await _cryptoService.HasKeyAsync(); var hasKey = await _cryptoService.HasUserKeyAsync();
if (!hasKey) if (!hasKey)
{ {
await _cryptoService.SetKeyAsync(key); await _cryptoService.SetUserKeyAsync(userKey);
} }
DoContinue(masterPassword); DoContinue(masterPassword);
} }
@@ -396,7 +414,7 @@ namespace Bit.iOS.Core.Controllers
private async Task EnableBiometricsIfNeeded() private async Task EnableBiometricsIfNeeded()
{ {
// Re-enable biometrics if initial use // Re-enable biometrics if initial use
if (_biometricLock & !_biometricIntegrityValid) if (_biometricEnabled & !_biometricIntegrityValid)
{ {
await _biometricService.SetupBiometricAsync(BiometricIntegritySourceKey); await _biometricService.SetupBiometricAsync(BiometricIntegritySourceKey);
} }
@@ -405,7 +423,7 @@ namespace Bit.iOS.Core.Controllers
private void InvalidValue() private void InvalidValue()
{ {
var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred, var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred,
string.Format(null, _pinLock ? AppResources.PIN : AppResources.InvalidMasterPassword), string.Format(null, _pinEnabled ? AppResources.PIN : AppResources.InvalidMasterPassword),
AppResources.Ok, (a) => AppResources.Ok, (a) =>
{ {
@@ -490,7 +508,7 @@ namespace Bit.iOS.Core.Controllers
return 0; return 0;
} }
return (!controller._biometricUnlockOnly && controller._biometricLock) || return (!controller._biometricUnlockOnly && controller._biometricEnabled) ||
controller._passwordReprompt controller._passwordReprompt
? 2 ? 2
: 1; : 1;

View File

@@ -14,6 +14,7 @@ using Bit.Core.Enums;
using Bit.App.Pages; using Bit.App.Pages;
using Bit.App.Models; using Bit.App.Models;
using Xamarin.Forms; using Xamarin.Forms;
using Bit.Core.Services;
namespace Bit.iOS.Core.Controllers namespace Bit.iOS.Core.Controllers
{ {
@@ -29,10 +30,9 @@ namespace Bit.iOS.Core.Controllers
private IPlatformUtilsService _platformUtilsService; private IPlatformUtilsService _platformUtilsService;
private IBiometricService _biometricService; private IBiometricService _biometricService;
private IKeyConnectorService _keyConnectorService; private IKeyConnectorService _keyConnectorService;
private bool _isPinProtected; private PinLockEnum _pinStatus;
private bool _isPinProtectedWithKey; private bool _pinEnabled;
private bool _pinLock; private bool _biometricEnabled;
private bool _biometricLock;
private bool _biometricIntegrityValid = true; private bool _biometricIntegrityValid = true;
private bool _passwordReprompt = false; private bool _passwordReprompt = false;
private bool _usesKeyConnector; private bool _usesKeyConnector;
@@ -96,25 +96,28 @@ namespace Bit.iOS.Core.Controllers
if (autofillExtension && await _stateService.GetPasswordRepromptAutofillAsync()) if (autofillExtension && await _stateService.GetPasswordRepromptAutofillAsync())
{ {
_passwordReprompt = true; _passwordReprompt = true;
_isPinProtected = false; _pinStatus = PinLockEnum.Disabled;
_isPinProtectedWithKey = false; _pinEnabled = false;
_pinLock = false; _biometricEnabled = false;
_biometricLock = false;
} }
else else
{ {
(_isPinProtected, _isPinProtectedWithKey) = await _vaultTimeoutService.IsPinLockSetAsync(); _pinStatus = await _vaultTimeoutService.IsPinLockSetAsync();
_pinLock = (_isPinProtected && await _stateService.GetPinProtectedKeyAsync() != null) ||
_isPinProtectedWithKey; var ephemeralPinSet = await _stateService.GetUserKeyPinEphemeralAsync()
_biometricLock = await _vaultTimeoutService.IsBiometricLockSetAsync() && ?? await _stateService.GetPinProtectedKeyAsync();
await _cryptoService.HasKeyAsync(); _pinEnabled = (_pinStatus == PinLockEnum.Transient && ephemeralPinSet != null) ||
_pinStatus == PinLockEnum.Persistent;
_biometricEnabled = await _vaultTimeoutService.IsBiometricLockSetAsync()
&& await _cryptoService.HasEncryptedUserKeyAsync();
_biometricIntegrityValid = _biometricIntegrityValid =
await _platformUtilsService.IsBiometricIntegrityValidAsync(BiometricIntegritySourceKey); await _platformUtilsService.IsBiometricIntegrityValidAsync(BiometricIntegritySourceKey);
_usesKeyConnector = await _keyConnectorService.GetUsesKeyConnector(); _usesKeyConnector = await _keyConnectorService.GetUsesKeyConnector();
_biometricUnlockOnly = _usesKeyConnector && _biometricLock && !_pinLock; _biometricUnlockOnly = _usesKeyConnector && _biometricEnabled && !_pinEnabled;
} }
if (_pinLock) if (_pinEnabled)
{ {
BaseNavItem.Title = AppResources.VerifyPIN; BaseNavItem.Title = AppResources.VerifyPIN;
} }
@@ -143,7 +146,7 @@ namespace Bit.iOS.Core.Controllers
if (!_biometricUnlockOnly) if (!_biometricUnlockOnly)
{ {
MasterPasswordCell.Label.Text = _pinLock ? AppResources.PIN : AppResources.MasterPassword; MasterPasswordCell.Label.Text = _pinEnabled ? AppResources.PIN : AppResources.MasterPassword;
MasterPasswordCell.TextField.SecureTextEntry = true; MasterPasswordCell.TextField.SecureTextEntry = true;
MasterPasswordCell.TextField.ReturnKeyType = UIReturnKeyType.Go; MasterPasswordCell.TextField.ReturnKeyType = UIReturnKeyType.Go;
MasterPasswordCell.TextField.ShouldReturn += (UITextField tf) => MasterPasswordCell.TextField.ShouldReturn += (UITextField tf) =>
@@ -151,7 +154,7 @@ namespace Bit.iOS.Core.Controllers
CheckPasswordAsync().GetAwaiter().GetResult(); CheckPasswordAsync().GetAwaiter().GetResult();
return true; return true;
}; };
if (_pinLock) if (_pinEnabled)
{ {
MasterPasswordCell.TextField.KeyboardType = UIKeyboardType.NumberPad; MasterPasswordCell.TextField.KeyboardType = UIKeyboardType.NumberPad;
} }
@@ -165,7 +168,7 @@ namespace Bit.iOS.Core.Controllers
base.ViewDidLoad(); base.ViewDidLoad();
if (_biometricLock) if (_biometricEnabled)
{ {
if (!_biometricIntegrityValid) if (!_biometricIntegrityValid)
{ {
@@ -186,13 +189,13 @@ namespace Bit.iOS.Core.Controllers
// Users with key connector and without biometric or pin has no MP to unlock with // Users with key connector and without biometric or pin has no MP to unlock with
if (_usesKeyConnector) if (_usesKeyConnector)
{ {
if (!(_pinLock || _biometricLock) || if (!(_pinEnabled || _biometricEnabled) ||
(_biometricLock && !_biometricIntegrityValid)) (_biometricEnabled && !_biometricIntegrityValid))
{ {
PromptSSO(); PromptSSO();
} }
} }
else if (!_biometricLock || !_biometricIntegrityValid) else if (!_biometricEnabled || !_biometricIntegrityValid)
{ {
MasterPasswordCell.TextField.BecomeFirstResponder(); MasterPasswordCell.TextField.BecomeFirstResponder();
} }
@@ -204,7 +207,7 @@ namespace Bit.iOS.Core.Controllers
{ {
var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred, var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred,
string.Format(AppResources.ValidationFieldRequired, string.Format(AppResources.ValidationFieldRequired,
_pinLock ? AppResources.PIN : AppResources.MasterPassword), _pinEnabled ? AppResources.PIN : AppResources.MasterPassword),
AppResources.Ok); AppResources.Ok);
PresentViewController(alert, true, null); PresentViewController(alert, true, null);
return; return;
@@ -214,33 +217,53 @@ namespace Bit.iOS.Core.Controllers
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile)); var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
var inputtedValue = MasterPasswordCell.TextField.Text; var inputtedValue = MasterPasswordCell.TextField.Text;
if (_pinLock) if (_pinEnabled)
{ {
var failed = true; var failed = true;
try try
{ {
if (_isPinProtected) EncString userKeyPin = null;
EncString oldPinProtected = null;
if (_pinStatus == PinLockEnum.Persistent)
{ {
var key = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email, userKeyPin = await _stateService.GetUserKeyPinAsync();
var oldEncryptedKey = await _stateService.GetPinProtectedAsync();
oldPinProtected = oldEncryptedKey != null ? new EncString(oldEncryptedKey) : null;
}
else if (_pinStatus == PinLockEnum.Transient)
{
userKeyPin = await _stateService.GetUserKeyPinEphemeralAsync();
oldPinProtected = await _stateService.GetPinProtectedKeyAsync();
}
UserKey userKey;
if (oldPinProtected != null)
{
userKey = await _cryptoService.DecryptAndMigrateOldPinKeyAsync(
_pinStatus == PinLockEnum.Transient,
inputtedValue,
email,
kdfConfig, kdfConfig,
await _stateService.GetPinProtectedKeyAsync()); oldPinProtected
var encKey = await _cryptoService.GetEncKeyAsync(key); );
var protectedPin = await _stateService.GetProtectedPinAsync();
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
failed = decPin != inputtedValue;
if (!failed)
{
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key);
}
} }
else else
{ {
var key2 = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email, userKey = await _cryptoService.DecryptUserKeyWithPinAsync(
kdfConfig); inputtedValue,
failed = false; email,
kdfConfig,
userKeyPin
);
}
var protectedPin = await _stateService.GetProtectedPinAsync();
var decryptedPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), userKey);
failed = decryptedPin != inputtedValue;
if (!failed)
{
await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key2); await SetKeyAndContinueAsync(userKey);
} }
} }
catch catch
@@ -260,33 +283,27 @@ namespace Bit.iOS.Core.Controllers
} }
else else
{ {
var key2 = await _cryptoService.MakeKeyAsync(inputtedValue, email, kdfConfig); var masterKey = await _cryptoService.MakeMasterKeyAsync(inputtedValue, email, kdfConfig);
var storedKeyHash = await _cryptoService.GetKeyHashAsync(); var storedPasswordHash = await _cryptoService.GetPasswordHashAsync();
if (storedKeyHash == null) if (storedPasswordHash == null)
{ {
var oldKey = await _secureStorageService.GetAsync<string>("oldKey"); var oldKey = await _secureStorageService.GetAsync<string>("oldKey");
if (key2.KeyB64 == oldKey) if (masterKey.KeyB64 == oldKey)
{ {
var localKeyHash = await _cryptoService.HashPasswordAsync(inputtedValue, key2, HashPurpose.LocalAuthorization); var localPasswordHash = await _cryptoService.HashPasswordAsync(inputtedValue, masterKey, HashPurpose.LocalAuthorization);
await _secureStorageService.RemoveAsync("oldKey"); await _secureStorageService.RemoveAsync("oldKey");
await _cryptoService.SetKeyHashAsync(localKeyHash); await _cryptoService.SetPasswordHashAsync(localPasswordHash);
} }
} }
var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(inputtedValue, key2); var passwordValid = await _cryptoService.CompareAndUpdatePasswordHashAsync(inputtedValue, masterKey);
if (passwordValid) if (passwordValid)
{ {
if (_isPinProtected)
{
var protectedPin = await _stateService.GetProtectedPinAsync();
var encKey = await _cryptoService.GetEncKeyAsync(key2);
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, email,
kdfConfig);
await _stateService.SetPinProtectedKeyAsync(await _cryptoService.EncryptAsync(key2.Key, pinKey));
}
await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key2, true);
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
await _cryptoService.SetMasterKeyAsync(masterKey);
await SetKeyAndContinueAsync(userKey, true);
} }
else else
{ {
@@ -303,12 +320,12 @@ namespace Bit.iOS.Core.Controllers
public async Task PromptBiometricAsync() public async Task PromptBiometricAsync()
{ {
if (!_biometricLock || !_biometricIntegrityValid) if (!_biometricEnabled || !_biometricIntegrityValid)
{ {
return; return;
} }
var success = await _platformUtilsService.AuthenticateBiometricAsync(null, var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
_pinLock ? AppResources.PIN : AppResources.MasterPassword, _pinEnabled ? AppResources.PIN : AppResources.MasterPassword,
() => MasterPasswordCell.TextField.BecomeFirstResponder()); () => MasterPasswordCell.TextField.BecomeFirstResponder());
await _stateService.SetBiometricLockedAsync(!success); await _stateService.SetBiometricLockedAsync(!success);
if (success) if (success)
@@ -335,12 +352,12 @@ namespace Bit.iOS.Core.Controllers
PresentViewController(loginController, true, null); PresentViewController(loginController, true, null);
} }
private async Task SetKeyAndContinueAsync(SymmetricCryptoKey key, bool masterPassword = false) private async Task SetKeyAndContinueAsync(UserKey userKey, bool masterPassword = false)
{ {
var hasKey = await _cryptoService.HasKeyAsync(); var hasKey = await _cryptoService.HasUserKeyAsync();
if (!hasKey) if (!hasKey)
{ {
await _cryptoService.SetKeyAsync(key); await _cryptoService.SetUserKeyAsync(userKey);
} }
DoContinue(masterPassword); DoContinue(masterPassword);
} }
@@ -360,7 +377,7 @@ namespace Bit.iOS.Core.Controllers
private async Task EnableBiometricsIfNeeded() private async Task EnableBiometricsIfNeeded()
{ {
// Re-enable biometrics if initial use // Re-enable biometrics if initial use
if (_biometricLock & !_biometricIntegrityValid) if (_biometricEnabled & !_biometricIntegrityValid)
{ {
await _biometricService.SetupBiometricAsync(BiometricIntegritySourceKey); await _biometricService.SetupBiometricAsync(BiometricIntegritySourceKey);
} }
@@ -369,7 +386,7 @@ namespace Bit.iOS.Core.Controllers
private void InvalidValue() private void InvalidValue()
{ {
var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred, var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred,
string.Format(null, _pinLock ? AppResources.PIN : AppResources.InvalidMasterPassword), string.Format(null, _pinEnabled ? AppResources.PIN : AppResources.InvalidMasterPassword),
AppResources.Ok, (a) => AppResources.Ok, (a) =>
{ {
@@ -444,7 +461,7 @@ namespace Bit.iOS.Core.Controllers
public override nint NumberOfSections(UITableView tableView) public override nint NumberOfSections(UITableView tableView)
{ {
return (!_controller._biometricUnlockOnly && _controller._biometricLock) || return (!_controller._biometricUnlockOnly && _controller._biometricEnabled) ||
_controller._passwordReprompt _controller._passwordReprompt
? 2 ? 2
: 1; : 1;