diff --git a/src/App/Pages/Accounts/LockPage.xaml b/src/App/Pages/Accounts/LockPage.xaml index f34a8b284..dabcb9fd6 100644 --- a/src/App/Pages/Accounts/LockPage.xaml +++ b/src/App/Pages/Accounts/LockPage.xaml @@ -46,7 +46,7 @@ @@ -89,7 +89,7 @@ diff --git a/src/App/Pages/Accounts/LockPage.xaml.cs b/src/App/Pages/Accounts/LockPage.xaml.cs index f5a7914a5..d62b4c6b2 100644 --- a/src/App/Pages/Accounts/LockPage.xaml.cs +++ b/src/App/Pages/Accounts/LockPage.xaml.cs @@ -44,7 +44,7 @@ namespace Bit.App.Pages { get { - if (_vm?.PinLock ?? false) + if (_vm?.PinEnabled ?? false) { return _pin; } @@ -54,7 +54,7 @@ namespace Bit.App.Pages public async Task PromptBiometricAfterResumeAsync() { - if (_vm.BiometricLock) + if (_vm.BiometricEnabled) { await Task.Delay(500); if (!_promptedAfterResume) @@ -91,13 +91,13 @@ namespace Bit.App.Pages _vm.FocusSecretEntry += PerformFocusSecretEntry; - if (!_vm.BiometricLock) + if (!_vm.BiometricEnabled) { RequestFocus(SecretEntry); } else { - if (_vm.UsingKeyConnector && !_vm.PinLock) + if (_vm.UsingKeyConnector && !_vm.PinEnabled) { _passwordGrid.IsVisible = false; _unlockButton.IsVisible = false; diff --git a/src/App/Pages/Accounts/LockPageViewModel.cs b/src/App/Pages/Accounts/LockPageViewModel.cs index 49231be77..6bb3aef27 100644 --- a/src/App/Pages/Accounts/LockPageViewModel.cs +++ b/src/App/Pages/Accounts/LockPageViewModel.cs @@ -38,16 +38,15 @@ namespace Bit.App.Pages private string _masterPassword; private string _pin; private bool _showPassword; - private bool _pinLock; - private bool _biometricLock; + private PinLockEnum _pinStatus; + private bool _pinEnabled; + private bool _biometricEnabled; private bool _biometricIntegrityValid = true; private bool _biometricButtonVisible; private bool _usingKeyConnector; private string _biometricButtonText; private string _loggedInAsText; private string _lockedVerifyText; - private bool _isPinProtected; - private bool _isPinProtectedWithKey; public LockPageViewModel() { @@ -100,10 +99,10 @@ namespace Bit.App.Pages }); } - public bool PinLock + public bool PinEnabled { - get => _pinLock; - set => SetProperty(ref _pinLock, value); + get => _pinEnabled; + set => SetProperty(ref _pinEnabled, value); } public bool UsingKeyConnector @@ -111,10 +110,10 @@ namespace Bit.App.Pages get => _usingKeyConnector; } - public bool BiometricLock + public bool BiometricEnabled { - get => _biometricLock; - set => SetProperty(ref _biometricLock, value); + get => _biometricEnabled; + set => SetProperty(ref _biometricEnabled, value); } public bool BiometricIntegrityValid @@ -162,14 +161,18 @@ namespace Bit.App.Pages public async Task InitAsync() { - (_isPinProtected, _isPinProtectedWithKey) = await _vaultTimeoutService.IsPinLockSetAsync(); - PinLock = (_isPinProtected && await _stateService.GetPinProtectedKeyAsync() != null) || - _isPinProtectedWithKey; - BiometricLock = await _vaultTimeoutService.IsBiometricLockSetAsync() && await _cryptoService.HasKeyAsync(); + _pinStatus = await _vaultTimeoutService.IsPinLockSetAsync(); + + var ephemeralPinSet = await _stateService.GetUserKeyPinEphemeralAsync() + ?? 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 _usingKeyConnector = await _keyConnectorService.GetUsesKeyConnector(); - if (_usingKeyConnector && !(BiometricLock || PinLock)) + if (_usingKeyConnector && !(BiometricEnabled || PinEnabled)) { await _vaultTimeoutService.LogOutAsync(); return; @@ -188,7 +191,7 @@ namespace Bit.App.Pages } var webVaultHostname = CoreHelpers.GetHostname(webVault); LoggedInAsText = string.Format(AppResources.LoggedInAsOn, _email, webVaultHostname); - if (PinLock) + if (PinEnabled) { PageTitle = AppResources.VerifyPIN; LockedVerifyText = AppResources.VaultLockedPIN; @@ -207,7 +210,7 @@ namespace Bit.App.Pages } } - if (BiometricLock) + if (BiometricEnabled) { BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync(); if (!_biometricIntegrityValid) @@ -229,14 +232,14 @@ namespace Bit.App.Pages public async Task SubmitAsync() { - if (PinLock && string.IsNullOrWhiteSpace(Pin)) + if (PinEnabled && string.IsNullOrWhiteSpace(Pin)) { await Page.DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired, AppResources.PIN), AppResources.Ok); return; } - if (!PinLock && string.IsNullOrWhiteSpace(MasterPassword)) + if (!PinEnabled && string.IsNullOrWhiteSpace(MasterPassword)) { await Page.DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword), @@ -247,34 +250,54 @@ namespace Bit.App.Pages ShowPassword = false; var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile)); - if (PinLock) + if (PinEnabled) { var failed = true; 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, - await _stateService.GetPinProtectedKeyAsync()); - 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); - } + oldPinProtected + ); } else { - var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email, kdfConfig); - failed = false; + userKey = await _cryptoService.DecryptUserKeyWithPinAsync( + 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; await AppHelpers.ResetInvalidUnlockAttemptsAsync(); - await SetKeyAndContinueAsync(key); + await SetKeyAndContinueAsync(userKey); } } catch @@ -302,10 +325,12 @@ namespace Bit.App.Pages if (storedKeyHash != null) { + // Offline unlock possible passwordValid = await _cryptoService.CompareAndUpdatePasswordHashAsync(MasterPassword, masterKey); } else { + // Online unlock required await _deviceActionService.ShowLoadingAsync(AppResources.Loading); var keyHash = await _cryptoService.HashPasswordAsync(MasterPassword, masterKey, HashPurpose.ServerAuthorization); var request = new PasswordVerificationRequest(); @@ -327,16 +352,6 @@ namespace Bit.App.Pages } 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)) { // Save the ForcePasswordResetReason to force a password reset after unlock @@ -346,10 +361,13 @@ namespace Bit.App.Pages MasterPassword = string.Empty; await AppHelpers.ResetInvalidUnlockAttemptsAsync(); - await SetKeyAndContinueAsync(masterKey); + + var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey); + await _cryptoService.SetMasterKeyAsync(masterKey); + await SetKeyAndContinueAsync(userKey); // Re-enable biometrics - if (BiometricLock & !BiometricIntegrityValid) + if (BiometricEnabled & !BiometricIntegrityValid) { await _biometricService.SetupBiometricAsync(); } @@ -426,7 +444,7 @@ namespace Bit.App.Pages public void TogglePassword() { ShowPassword = !ShowPassword; - var secret = PinLock ? Pin : MasterPassword; + var secret = PinEnabled ? Pin : MasterPassword; _secretEntryFocusWeakEventManager.RaiseEvent(string.IsNullOrEmpty(secret) ? 0 : secret.Length, nameof(FocusSecretEntry)); } @@ -434,12 +452,12 @@ namespace Bit.App.Pages { BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync(); BiometricButtonVisible = BiometricIntegrityValid; - if (!BiometricLock || !BiometricIntegrityValid) + if (!BiometricEnabled || !BiometricIntegrityValid) { return; } var success = await _platformUtilsService.AuthenticateBiometricAsync(null, - PinLock ? AppResources.PIN : AppResources.MasterPassword, + PinEnabled ? AppResources.PIN : AppResources.MasterPassword, () => _secretEntryFocusWeakEventManager.RaiseEvent((int?)null, nameof(FocusSecretEntry))); await _stateService.SetBiometricLockedAsync(!success); if (success) @@ -448,7 +466,6 @@ namespace Bit.App.Pages } } - // TODO(Jake): Update to store UserKey private async Task SetKeyAndContinueAsync(UserKey key) { var hasKey = await _cryptoService.HasUserKeyAsync(); diff --git a/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs b/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs index 26530d9a8..f6f2c8a3d 100644 --- a/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs +++ b/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs @@ -139,7 +139,7 @@ namespace Bit.App.Pages } var pinSet = await _vaultTimeoutService.IsPinLockSetAsync(); - _pin = pinSet.Item1 || pinSet.Item2; + _pin = pinSet != PinLockEnum.Disabled; _biometric = await _vaultTimeoutService.IsBiometricLockSetAsync(); _screenCaptureAllowed = await _stateService.GetScreenCaptureAllowedAsync(); diff --git a/src/Core/Abstractions/ICryptoService.cs b/src/Core/Abstractions/ICryptoService.cs index 72f34867a..6b10b8ce1 100644 --- a/src/Core/Abstractions/ICryptoService.cs +++ b/src/Core/Abstractions/ICryptoService.cs @@ -13,6 +13,7 @@ namespace Bit.Core.Abstractions Task SetUserKeyAsync(UserKey userKey, string userId = null); Task GetUserKeyAsync(string userId = null); Task HasUserKeyAsync(string userId = null); + Task HasEncryptedUserKeyAsync(string userId = null); Task MakeUserKeyAsync(); Task ClearUserKeyAsync(string userId = null); Task SetMasterKeyEncryptedUserKeyAsync(string value, string userId = null); @@ -38,7 +39,8 @@ namespace Bit.Core.Abstractions Task> MakeKeyPairAsync(SymmetricCryptoKey key = null); Task ClearKeyPairAsync(bool memoryOnly = false, string userId = null); Task MakePinKeyAsync(string pin, string salt, KdfConfig config); - // Task DecryptUserKeyWithPin(string pin, string salt, KdfConfig kdfConfig, EncString pinProtectedUserKey = null); + Task DecryptUserKeyWithPinAsync(string pin, string salt, KdfConfig kdfConfig, EncString pinProtectedUserKey = null); + Task DecryptMasterKeyWithPinAsync(string pin, string salt, KdfConfig kdfConfig, EncString pinProtectedMasterKey = null); Task MakeSendKeyAsync(byte[] keyMaterial); // TODO(Jake): This isn't used, delete? Task ClearKeysAsync(string userId = null); @@ -52,6 +54,7 @@ namespace Bit.Core.Abstractions Task EncryptAsync(byte[] plainValue, SymmetricCryptoKey key = null); Task EncryptAsync(string plainValue, SymmetricCryptoKey key = null); Task EncryptToBytesAsync(byte[] plainValue, SymmetricCryptoKey key = null); + Task DecryptAndMigrateOldPinKeyAsync(bool masterPasswordOnRestart, string pin, string email, KdfConfig kdfConfig, EncString oldPinKey); diff --git a/src/Core/Abstractions/IStateService.cs b/src/Core/Abstractions/IStateService.cs index ec40dd5ef..714c7345b 100644 --- a/src/Core/Abstractions/IStateService.cs +++ b/src/Core/Abstractions/IStateService.cs @@ -44,7 +44,7 @@ namespace Bit.Core.Abstractions Task CanAccessPremiumAsync(string userId = null); Task GetProtectedPinAsync(string userId = null); Task SetPersonalPremiumAsync(bool value, string userId = null); - Task GetUserKeyPinAsync(string userId = null); + Task GetUserKeyPinAsync(string userId = null); Task SetUserKeyPinAsync(EncString value, string userId = null); Task GetUserKeyPinEphemeralAsync(string userId = null); Task SetUserKeyPinEphemeralAsync(EncString value, string userId = null); diff --git a/src/Core/Abstractions/IVaultTimeoutService.cs b/src/Core/Abstractions/IVaultTimeoutService.cs index c74001b17..529806983 100644 --- a/src/Core/Abstractions/IVaultTimeoutService.cs +++ b/src/Core/Abstractions/IVaultTimeoutService.cs @@ -1,6 +1,7 @@ using System; using System.Threading.Tasks; using Bit.Core.Enums; +using Bit.Core.Services; namespace Bit.Core.Abstractions { @@ -16,7 +17,7 @@ namespace Bit.Core.Abstractions Task ShouldLockAsync(string userId = null); Task IsLoggedOutByTimeoutAsync(string userId = null); Task ShouldLogOutByTimeoutAsync(string userId = null); - Task> IsPinLockSetAsync(string userId = null); + Task IsPinLockSetAsync(string userId = null); Task IsBiometricLockSetAsync(string userId = null); Task LockAsync(bool allowSoftLock = false, bool userInitiated = false, string userId = null); Task LogOutAsync(bool userInitiated = true, string userId = null); diff --git a/src/Core/Services/CryptoService.cs b/src/Core/Services/CryptoService.cs index 8d30afeb0..d5ed33e04 100644 --- a/src/Core/Services/CryptoService.cs +++ b/src/Core/Services/CryptoService.cs @@ -73,6 +73,11 @@ namespace Bit.Core.Services return await GetUserKeyAsync(userId) != null; } + public async Task HasEncryptedUserKeyAsync(string userId = null) + { + return await _stateService.GetUserKeyMasterKeyAsync(userId) != null; + } + public async Task MakeUserKeyAsync() { return new UserKey(await _cryptoFunctionService.RandomBytesAsync(64)); @@ -418,18 +423,39 @@ namespace Bit.Core.Services await clearDeprecatedPinKeysAsync(userId); } - // public async Task DecryptUserKeyWithPin(string pin, string salt, KdfConfig kdfConfig, EncString pinProtectedUserKey = null) - // { - // pinProtectedUserKey ??= await _stateService.GetUserKeyPinAsync(); - // pinProtectedUserKey ??= await _stateService.GetUserKeyPinEphemeralAsync(); - // if (pinProtectedUserKey == null) - // { - // throw new Exception("No PIN protected user key found."); - // } - // var pinKey = await MakePinKeyAsync(pin, salt, kdfConfig); - // var userKey = await DecryptToBytesAsync(pinProtectedUserKey, pinKey); - // return new UserKey(userKey); - // } + public async Task DecryptUserKeyWithPinAsync(string pin, string salt, KdfConfig kdfConfig, EncString pinProtectedUserKey = null) + { + pinProtectedUserKey ??= await _stateService.GetUserKeyPinAsync(); + pinProtectedUserKey ??= await _stateService.GetUserKeyPinEphemeralAsync(); + if (pinProtectedUserKey == null) + { + throw new Exception("No PIN protected user key found."); + } + var pinKey = await MakePinKeyAsync(pin, salt, kdfConfig); + var userKey = await DecryptToBytesAsync(pinProtectedUserKey, pinKey); + return new UserKey(userKey); + } + + // Only for migration purposes + public async Task 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 MakeSendKeyAsync(byte[] keyMaterial) { @@ -932,10 +958,52 @@ namespace Bit.Core.Services 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. // These methods support migrating the old keys to the new ones. + public async Task 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) { await _stateService.SetPinProtectedAsync(null); diff --git a/src/Core/Services/StateService.cs b/src/Core/Services/StateService.cs index 7eb54919c..076ffd8e6 100644 --- a/src/Core/Services/StateService.cs +++ b/src/Core/Services/StateService.cs @@ -396,9 +396,9 @@ namespace Bit.Core.Services } // TODO(Jake): Does this need to be secure storage? - public async Task GetUserKeyPinAsync(string userId = null) + public async Task GetUserKeyPinAsync(string userId = null) { - return await _storageMediatorService.GetAsync(Constants.UserKeyPinKey(userId), false); + return new EncString(await _storageMediatorService.GetAsync(Constants.UserKeyPinKey(userId), false)); } // TODO(Jake): Does this need to be secure storage? diff --git a/src/Core/Services/VaultTimeoutService.cs b/src/Core/Services/VaultTimeoutService.cs index 917664917..ad7cb0ce4 100644 --- a/src/Core/Services/VaultTimeoutService.cs +++ b/src/Core/Services/VaultTimeoutService.cs @@ -7,6 +7,13 @@ using Bit.Core.Models.Domain; namespace Bit.Core.Services { + public enum PinLockEnum + { + Disabled, + Persistent, + Transient + } + public class VaultTimeoutService : IVaultTimeoutService { private readonly ICryptoService _cryptoService; @@ -165,11 +172,13 @@ namespace Bit.Core.Services if (await _keyConnectorService.GetUsesKeyConnector()) { - var (isPinProtected, isPinProtectedWithKey) = await IsPinLockSetAsync(userId); - var pinLock = (isPinProtected && await _stateService.GetPinProtectedKeyAsync(userId) != null) || - isPinProtectedWithKey; + var pinStatus = await IsPinLockSetAsync(userId); + var ephemeralPinSet = await _stateService.GetUserKeyPinEphemeralAsync() + ?? 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); return; @@ -218,11 +227,26 @@ namespace Bit.Core.Services await _tokenService.ToggleTokensAsync(); } - public async Task> IsPinLockSetAsync(string userId = null) + public async Task IsPinLockSetAsync(string userId = null) { - var protectedPin = await _stateService.GetProtectedPinAsync(userId); - var pinProtectedKey = await _stateService.GetPinProtectedAsync(userId); - return new Tuple(protectedPin != null, pinProtectedKey != null); + // we can't depend on only the protected pin being set because old + // versions only used it for MP on Restart + 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 IsBiometricLockSetAsync(string userId = null) diff --git a/src/iOS.Core/Controllers/BaseLockPasswordViewController.cs b/src/iOS.Core/Controllers/BaseLockPasswordViewController.cs index 0edccc732..bebc7e925 100644 --- a/src/iOS.Core/Controllers/BaseLockPasswordViewController.cs +++ b/src/iOS.Core/Controllers/BaseLockPasswordViewController.cs @@ -8,11 +8,13 @@ using Bit.App.Utilities; using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Models.Domain; +using Bit.Core.Services; using Bit.Core.Utilities; using Bit.iOS.Core.Utilities; using Bit.iOS.Core.Views; using Foundation; using UIKit; +using Xamarin.Essentials; using Xamarin.Forms; namespace Bit.iOS.Core.Controllers @@ -28,10 +30,9 @@ namespace Bit.iOS.Core.Controllers private IBiometricService _biometricService; private IKeyConnectorService _keyConnectorService; private IAccountsManager _accountManager; - private bool _isPinProtected; - private bool _isPinProtectedWithKey; - private bool _pinLock; - private bool _biometricLock; + private PinLockEnum _pinStatus; + private bool _pinEnabled; + private bool _biometricEnabled; private bool _biometricIntegrityValid = true; private bool _passwordReprompt = false; private bool _usesKeyConnector; @@ -85,7 +86,7 @@ namespace Bit.iOS.Core.Controllers } public abstract UITableView TableView { get; } - + public override async void ViewDidLoad() { _vaultTimeoutService = ServiceContainer.Resolve("vaultTimeoutService"); @@ -103,25 +104,28 @@ namespace Bit.iOS.Core.Controllers if (autofillExtension && await _stateService.GetPasswordRepromptAutofillAsync()) { _passwordReprompt = true; - _isPinProtected = false; - _isPinProtectedWithKey = false; - _pinLock = false; - _biometricLock = false; + _pinStatus = PinLockEnum.Disabled; + _pinEnabled = false; + _biometricEnabled = false; } else { - (_isPinProtected, _isPinProtectedWithKey) = await _vaultTimeoutService.IsPinLockSetAsync(); - _pinLock = (_isPinProtected && await _stateService.GetPinProtectedKeyAsync() != null) || - _isPinProtectedWithKey; - _biometricLock = await _vaultTimeoutService.IsBiometricLockSetAsync() && - await _cryptoService.HasKeyAsync(); + _pinStatus = await _vaultTimeoutService.IsPinLockSetAsync(); + + var ephemeralPinSet = await _stateService.GetUserKeyPinEphemeralAsync() + ?? await _stateService.GetPinProtectedKeyAsync(); + _pinEnabled = (_pinStatus == PinLockEnum.Transient && ephemeralPinSet != null) || + _pinStatus == PinLockEnum.Persistent; + + _biometricEnabled = await _vaultTimeoutService.IsBiometricLockSetAsync() + && await _cryptoService.HasEncryptedUserKeyAsync(); _biometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync(BiometricIntegritySourceKey); _usesKeyConnector = await _keyConnectorService.GetUsesKeyConnector(); - _biometricUnlockOnly = _usesKeyConnector && _biometricLock && !_pinLock; + _biometricUnlockOnly = _usesKeyConnector && _biometricEnabled && !_pinEnabled; } - if (_pinLock) + if (_pinEnabled) { BaseNavItem.Title = AppResources.VerifyPIN; } @@ -150,7 +154,7 @@ namespace Bit.iOS.Core.Controllers if (!_biometricUnlockOnly) { - MasterPasswordCell.Label.Text = _pinLock ? AppResources.PIN : AppResources.MasterPassword; + MasterPasswordCell.Label.Text = _pinEnabled ? AppResources.PIN : AppResources.MasterPassword; MasterPasswordCell.TextField.SecureTextEntry = true; MasterPasswordCell.TextField.ReturnKeyType = UIReturnKeyType.Go; MasterPasswordCell.TextField.ShouldReturn += (UITextField tf) => @@ -158,7 +162,7 @@ namespace Bit.iOS.Core.Controllers CheckPasswordAsync().FireAndForget(); return true; }; - if (_pinLock) + if (_pinEnabled) { MasterPasswordCell.TextField.KeyboardType = UIKeyboardType.NumberPad; } @@ -177,7 +181,7 @@ namespace Bit.iOS.Core.Controllers base.ViewDidLoad(); - if (_biometricLock) + if (_biometricEnabled) { 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 if (_usesKeyConnector) { - if (!(_pinLock || _biometricLock) || - (_biometricLock && !_biometricIntegrityValid)) + if (!(_pinEnabled || _biometricEnabled) || + (_biometricEnabled && !_biometricIntegrityValid)) { PromptSSO(); } } - else if (!_biometricLock || !_biometricIntegrityValid) + else if (!_biometricEnabled || !_biometricIntegrityValid) { MasterPasswordCell.TextField.BecomeFirstResponder(); } } - + protected async Task CheckPasswordAsync() { if (_checkingPassword) @@ -224,7 +228,7 @@ namespace Bit.iOS.Core.Controllers { var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired, - _pinLock ? AppResources.PIN : AppResources.MasterPassword), + _pinEnabled ? AppResources.PIN : AppResources.MasterPassword), AppResources.Ok); PresentViewController(alert, true, null); return; @@ -246,33 +250,53 @@ namespace Bit.iOS.Core.Controllers return; } - if (_pinLock) + if (_pinEnabled) { var failed = true; 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, - await _stateService.GetPinProtectedKeyAsync()); - 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); - } + oldPinProtected + ); } else { - var key2 = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email, - kdfConfig); - failed = false; + userKey = await _cryptoService.DecryptUserKeyWithPinAsync( + inputtedValue, + 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 SetKeyAndContinueAsync(key2); + await SetKeyAndContinueAsync(userKey); } } catch @@ -286,33 +310,27 @@ namespace Bit.iOS.Core.Controllers } else { - var key2 = await _cryptoService.MakeKeyAsync(inputtedValue, email, kdfConfig); + var masterKey = await _cryptoService.MakeMasterKeyAsync(inputtedValue, email, kdfConfig); - var storedKeyHash = await _cryptoService.GetKeyHashAsync(); - if (storedKeyHash == null) + var storedPasswordHash = await _cryptoService.GetPasswordHashAsync(); + if (storedPasswordHash == null) { var oldKey = await _secureStorageService.GetAsync("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 _cryptoService.SetKeyHashAsync(localKeyHash); + await _cryptoService.SetPasswordHashAsync(localPasswordHash); } } - var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(inputtedValue, key2); + var passwordValid = await _cryptoService.CompareAndUpdatePasswordHashAsync(inputtedValue, masterKey); 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 SetKeyAndContinueAsync(key2, true); + + var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey); + await _cryptoService.SetMasterKeyAsync(masterKey); + await SetKeyAndContinueAsync(userKey, true); } else { @@ -339,12 +357,12 @@ namespace Bit.iOS.Core.Controllers public async Task PromptBiometricAsync() { - if (!_biometricLock || !_biometricIntegrityValid) + if (!_biometricEnabled || !_biometricIntegrityValid) { return; } var success = await _platformUtilsService.AuthenticateBiometricAsync(null, - _pinLock ? AppResources.PIN : AppResources.MasterPassword, + _pinEnabled ? AppResources.PIN : AppResources.MasterPassword, () => MasterPasswordCell.TextField.BecomeFirstResponder()); await _stateService.SetBiometricLockedAsync(!success); if (success) @@ -371,12 +389,12 @@ namespace Bit.iOS.Core.Controllers 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) { - await _cryptoService.SetKeyAsync(key); + await _cryptoService.SetUserKeyAsync(userKey); } DoContinue(masterPassword); } @@ -396,7 +414,7 @@ namespace Bit.iOS.Core.Controllers private async Task EnableBiometricsIfNeeded() { // Re-enable biometrics if initial use - if (_biometricLock & !_biometricIntegrityValid) + if (_biometricEnabled & !_biometricIntegrityValid) { await _biometricService.SetupBiometricAsync(BiometricIntegritySourceKey); } @@ -405,7 +423,7 @@ namespace Bit.iOS.Core.Controllers private void InvalidValue() { 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) => { @@ -490,7 +508,7 @@ namespace Bit.iOS.Core.Controllers return 0; } - return (!controller._biometricUnlockOnly && controller._biometricLock) || + return (!controller._biometricUnlockOnly && controller._biometricEnabled) || controller._passwordReprompt ? 2 : 1; diff --git a/src/iOS.Core/Controllers/LockPasswordViewController.cs b/src/iOS.Core/Controllers/LockPasswordViewController.cs index 8db432c46..9d4019a47 100644 --- a/src/iOS.Core/Controllers/LockPasswordViewController.cs +++ b/src/iOS.Core/Controllers/LockPasswordViewController.cs @@ -14,6 +14,7 @@ using Bit.Core.Enums; using Bit.App.Pages; using Bit.App.Models; using Xamarin.Forms; +using Bit.Core.Services; namespace Bit.iOS.Core.Controllers { @@ -29,10 +30,9 @@ namespace Bit.iOS.Core.Controllers private IPlatformUtilsService _platformUtilsService; private IBiometricService _biometricService; private IKeyConnectorService _keyConnectorService; - private bool _isPinProtected; - private bool _isPinProtectedWithKey; - private bool _pinLock; - private bool _biometricLock; + private PinLockEnum _pinStatus; + private bool _pinEnabled; + private bool _biometricEnabled; private bool _biometricIntegrityValid = true; private bool _passwordReprompt = false; private bool _usesKeyConnector; @@ -96,25 +96,28 @@ namespace Bit.iOS.Core.Controllers if (autofillExtension && await _stateService.GetPasswordRepromptAutofillAsync()) { _passwordReprompt = true; - _isPinProtected = false; - _isPinProtectedWithKey = false; - _pinLock = false; - _biometricLock = false; + _pinStatus = PinLockEnum.Disabled; + _pinEnabled = false; + _biometricEnabled = false; } else { - (_isPinProtected, _isPinProtectedWithKey) = await _vaultTimeoutService.IsPinLockSetAsync(); - _pinLock = (_isPinProtected && await _stateService.GetPinProtectedKeyAsync() != null) || - _isPinProtectedWithKey; - _biometricLock = await _vaultTimeoutService.IsBiometricLockSetAsync() && - await _cryptoService.HasKeyAsync(); + _pinStatus = await _vaultTimeoutService.IsPinLockSetAsync(); + + var ephemeralPinSet = await _stateService.GetUserKeyPinEphemeralAsync() + ?? await _stateService.GetPinProtectedKeyAsync(); + _pinEnabled = (_pinStatus == PinLockEnum.Transient && ephemeralPinSet != null) || + _pinStatus == PinLockEnum.Persistent; + + _biometricEnabled = await _vaultTimeoutService.IsBiometricLockSetAsync() + && await _cryptoService.HasEncryptedUserKeyAsync(); _biometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync(BiometricIntegritySourceKey); _usesKeyConnector = await _keyConnectorService.GetUsesKeyConnector(); - _biometricUnlockOnly = _usesKeyConnector && _biometricLock && !_pinLock; + _biometricUnlockOnly = _usesKeyConnector && _biometricEnabled && !_pinEnabled; } - if (_pinLock) + if (_pinEnabled) { BaseNavItem.Title = AppResources.VerifyPIN; } @@ -143,7 +146,7 @@ namespace Bit.iOS.Core.Controllers if (!_biometricUnlockOnly) { - MasterPasswordCell.Label.Text = _pinLock ? AppResources.PIN : AppResources.MasterPassword; + MasterPasswordCell.Label.Text = _pinEnabled ? AppResources.PIN : AppResources.MasterPassword; MasterPasswordCell.TextField.SecureTextEntry = true; MasterPasswordCell.TextField.ReturnKeyType = UIReturnKeyType.Go; MasterPasswordCell.TextField.ShouldReturn += (UITextField tf) => @@ -151,7 +154,7 @@ namespace Bit.iOS.Core.Controllers CheckPasswordAsync().GetAwaiter().GetResult(); return true; }; - if (_pinLock) + if (_pinEnabled) { MasterPasswordCell.TextField.KeyboardType = UIKeyboardType.NumberPad; } @@ -165,7 +168,7 @@ namespace Bit.iOS.Core.Controllers base.ViewDidLoad(); - if (_biometricLock) + if (_biometricEnabled) { 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 if (_usesKeyConnector) { - if (!(_pinLock || _biometricLock) || - (_biometricLock && !_biometricIntegrityValid)) + if (!(_pinEnabled || _biometricEnabled) || + (_biometricEnabled && !_biometricIntegrityValid)) { PromptSSO(); } } - else if (!_biometricLock || !_biometricIntegrityValid) + else if (!_biometricEnabled || !_biometricIntegrityValid) { MasterPasswordCell.TextField.BecomeFirstResponder(); } @@ -204,7 +207,7 @@ namespace Bit.iOS.Core.Controllers { var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired, - _pinLock ? AppResources.PIN : AppResources.MasterPassword), + _pinEnabled ? AppResources.PIN : AppResources.MasterPassword), AppResources.Ok); PresentViewController(alert, true, null); return; @@ -214,33 +217,53 @@ namespace Bit.iOS.Core.Controllers var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile)); var inputtedValue = MasterPasswordCell.TextField.Text; - if (_pinLock) + if (_pinEnabled) { var failed = true; 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, - await _stateService.GetPinProtectedKeyAsync()); - 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); - } + oldPinProtected + ); } else { - var key2 = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email, - kdfConfig); - failed = false; + userKey = await _cryptoService.DecryptUserKeyWithPinAsync( + inputtedValue, + 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 SetKeyAndContinueAsync(key2); + await SetKeyAndContinueAsync(userKey); } } catch @@ -260,33 +283,27 @@ namespace Bit.iOS.Core.Controllers } else { - var key2 = await _cryptoService.MakeKeyAsync(inputtedValue, email, kdfConfig); + var masterKey = await _cryptoService.MakeMasterKeyAsync(inputtedValue, email, kdfConfig); - var storedKeyHash = await _cryptoService.GetKeyHashAsync(); - if (storedKeyHash == null) + var storedPasswordHash = await _cryptoService.GetPasswordHashAsync(); + if (storedPasswordHash == null) { var oldKey = await _secureStorageService.GetAsync("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 _cryptoService.SetKeyHashAsync(localKeyHash); + await _cryptoService.SetPasswordHashAsync(localPasswordHash); } } - var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(inputtedValue, key2); + var passwordValid = await _cryptoService.CompareAndUpdatePasswordHashAsync(inputtedValue, masterKey); 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 SetKeyAndContinueAsync(key2, true); + + var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey); + await _cryptoService.SetMasterKeyAsync(masterKey); + await SetKeyAndContinueAsync(userKey, true); } else { @@ -303,12 +320,12 @@ namespace Bit.iOS.Core.Controllers public async Task PromptBiometricAsync() { - if (!_biometricLock || !_biometricIntegrityValid) + if (!_biometricEnabled || !_biometricIntegrityValid) { return; } var success = await _platformUtilsService.AuthenticateBiometricAsync(null, - _pinLock ? AppResources.PIN : AppResources.MasterPassword, + _pinEnabled ? AppResources.PIN : AppResources.MasterPassword, () => MasterPasswordCell.TextField.BecomeFirstResponder()); await _stateService.SetBiometricLockedAsync(!success); if (success) @@ -335,12 +352,12 @@ namespace Bit.iOS.Core.Controllers 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) { - await _cryptoService.SetKeyAsync(key); + await _cryptoService.SetUserKeyAsync(userKey); } DoContinue(masterPassword); } @@ -360,7 +377,7 @@ namespace Bit.iOS.Core.Controllers private async Task EnableBiometricsIfNeeded() { // Re-enable biometrics if initial use - if (_biometricLock & !_biometricIntegrityValid) + if (_biometricEnabled & !_biometricIntegrityValid) { await _biometricService.SetupBiometricAsync(BiometricIntegritySourceKey); } @@ -369,7 +386,7 @@ namespace Bit.iOS.Core.Controllers private void InvalidValue() { 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) => { @@ -444,7 +461,7 @@ namespace Bit.iOS.Core.Controllers public override nint NumberOfSections(UITableView tableView) { - return (!_controller._biometricUnlockOnly && _controller._biometricLock) || + return (!_controller._biometricUnlockOnly && _controller._biometricEnabled) || _controller._passwordReprompt ? 2 : 1;