From bfcfd367dd169b8fcf52e9448d569aaaf860d155 Mon Sep 17 00:00:00 2001 From: Todd Martin <106564991+trmartin4@users.noreply.github.com> Date: Thu, 17 Aug 2023 15:19:35 -0400 Subject: [PATCH] Trusted Device Encryption feature (#2656) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [PM-1208] Add Device approval options screen. View model waiting for additional logic to be added. * [PM-1208] Add device related api endpoint. Add AccoundDecryptOptions model and property to user Account. * [PM-1208] Add continue button and not you option * [PM-1379] add DeviceTrustCryptoService with establish trust logic (#2535) * [PM-1379] add DeviceCryptoService with establish trust logic * PM-1379 update api location and other minor refactors * pm-1379 fix encoding * update trusted device keys api call to Put * [PM-1379] rename DeviceCryptoService to DeviceTrustCryptoService - refactors to prevent side effects * [PM-1379] rearrange methods in DeviceTrustCryptoService * [PM-1379] rearrange methods in abstraction * [PM-1379] deconstruct tuples * [PM-1379] remove extra tasks * [PM-2583] Answer auth request with mp field as null if doesn't have it. (#2609) * [PM-2287][PM-2289][PM-2293] Approval Options (#2608) * [PM-2293] Add AuthRequestType to PasswordlessLoginPage. * [PM-2293] Add Actions to ApproveWithDevicePage * [PM-2293] Change screen text based on AuthRequestType * [PM-2293] Refactor AuthRequestType enum. Add label. Remove unnecessary actions. * [PM-2293] Change boolean variable expression. * [PM-2293] Trust device after admin request login. * code format * [PM-2287] Add trust device to master password unlock. Change trust device method. Remove email from SSO login page. * [PM-2293] Fix state variable get set. * [PM-2287][PM-2289][PM-2293] Rename method * [PM-1201] Change timeout actions available based on hasMasterPassword (#2610) * [PM-1201] Change timeout actions available based on hasMasterPassword * [PM-2731] add user key and master key types * [PM-2713] add new state for new keys and obsolete old ones - UserKey - MasterKey - UserKeyMasterKey (enc UserKey from User Table) * [PM-271] add UserKey and MasterKey support to crypto service * [PM-2713] rename key hash to password hash & begin add methods to crypto service * [PM-2713] continue organizing crypto service * [PM-2713] more updates to crypto service * [PM-2713] add new pin methods to state service * [PM-2713] fix signature of GetUserKeyPin * [PM-2713] add make user key method to crypto service * [PM-2713] refresh pin key when setting user key * [PM-2713] use new MakeMasterKey method * [PM-2713] add toggle method to crypto service for keys * [PM-2713] converting calls to new crypto service api * [PM-2713] add migration for pin on lock screens * [PM-2713] more conversions to new crypto service api * [PM-2713] convert cipher service and others to crypto service api * [PM-2713] More conversions to crypto api * [PM-2713] use new crypto service api in auth service * [PM-2713] remove unused cached values in crypto service * [PM-2713] set decrypt and set user key in login helper * fix bad merge * Update crypto service api call to fix build * [PM-1208] Fix app resource file * [PM-1208] Fix merge * [PM-1208] Fix merge * [PM-2713] optimize async code in crypto service * [PM-2713] rename password hash to master key hash * [PM-2713] fix casting issues and pin * [PM-2713] remove extra comment * [PM-2713] remove broken casting * [PM-2297] Login with trusted device (Flow 2) (#2623) * [PM-2297] Add DecryptUserKeyWithDeviceKey method * [PM-2297] Add methods to DeviceTrustCryptoService update decryption options model * [PM-2297] Update account decryption options model * [PM-2297] Fix TrustedDeviceOption and DeviceResponse model. Change StateService device key get set to have default user id * [PM-2297] Update navigation to decryption options * [PM-2297] Add missing action navigations to iOS extensions * [PM-2297] Fix trust device bug/typo * [PM-2297] Fix model bug * [PM-2297] Fix state var crash * [PM-2297] Add trust device login logic to auth service * [PM-2297] Refactor auth service key connector code * [PM-2297] Remove reconciledOptions for deviceKey in state service * [PM-2297] Remove unnecessary user id params * [PM-2289] [PM-2293] TDE Login with device Admin Request (#2642) * [PM-2713] deconstruct new key pair * [PM-2713] rename PrivateKey methods to UserPrivateKey on crypto service * [PM-2713] rename PinLockEnum to PinLockType * [PM-2713] don't pass user key as param when encrypting * [PM-2713] rename toggle method, don't reset enc user key * [PM-2713] pr feedback * [PM-2713] PR feedback * [PM-2713] rename get pin lock type method * [PM-2713] revert feedback for build * [PM-2713] rename state methods * [PM-2713] combine makeDataEncKey methods * [PM-2713] consolidate attachment key creation - also fix ios files missed during symbol rename * [PM-2713] replace generic with inherited class * rename account keys to be more descriptive * [PM-2713] add auto unlock key to mobile * [PM-1208] Add TDE flows for new users (#2655) * [PM-1208] Create new user on SSO. Logout if not password is setup or has pending admin auth request. * [PM-1208] Fix new user UserKey decryption. * [PM-1208] Add new user continue to vault logic. Auto enrol user on continue. * [PM-1208] Trust device only if needed * [PM-1208] Add logic for New User SSO. * [PM-1208] Add logic for New User SSO (missing file). * [PM-2713] set user key on set password page * [PM-2713] set enc user key during kc onboarding * fix formatting * [PM-2713] make method async again - returning null from a task thats not async throws * [PM-2713] clear service cache when adding new account * Fix build after merge * [PM-3313] Fix Android SSO Login (#2663) * [PM-3313] Catch exception on AuthPendingRequest * [PM-3313] Fix lock timeout action if user doesn't have a master password. * code format * [PM-3313] Null email in Approval Options screen (#2664) * [PM-3313] Fix null email in approval options screen * [PM-3320][PM-3321] Fix labels and UI tweaks (#2666) * [PM-3320] Fix UI copy and remember me default ON. * [PM-3321] Fix UI on Log in with device screen. * [PM-3337] Fix admin request deny error (#2669) * [PM-3342] Not you button logs user out. (#2672) * [PM-3319] Check for admin request in Lock page (#2668) * [PM-3319] Ignore admin auth request when choosing mp as decryption option. * [PM-2289] Change header title based on auth request type (#2670) * [PM-2289] Change header title based on auth request type * [PM-3333] Check for purged admin auth requests (#2671) * [PM-3333] Check for purged admin auth requests Co-authored-by: Federico Maccaroni --------- Co-authored-by: Federico Maccaroni * [PM-3341] Vault Timeout Action not persisted correctly (#2673) * [PM-3341] Fix timeout action change when navigating * [PM-3357] Fix copy for Login Initiated (#2674) * [PM-3362] Fix auth request approval (#2675) * [PM-3362] Fix auth request approval * [PM-3362] Add new exception type * [PM-3102] Update Master password reprompt to be based on MP instead of Key Connector (#2653) * PM-3102 Added check to see if a user has master password set replacing previous usage of key connector. * PM-3102 Fix formatting * [PM-2713] Final merge from Key Migration branch to TDE Feature branch (#2667) * [PM-2713] add async to key connector service methods * [PM-2713] rename ephemeral pin key * add state for biometric key and accept UserKey instead of string for auto key * Get UserKey from bio state on unlock * PM-2713 Fix auto-migrating EncKeyEncrypted into MasterKey encrypted UserKey when requesting DecryptUserKeyWithMasterKeyAsync is called * renaming bio key and fix build * PM-3194 Fix biometrics button to be shown on upgrade when no UserKey is present yet * revert removal of key connector service from auth service * PM-2713 set user key when using KC * clear enc user key after migration * use is true for nullable bool * PR feedback, refactor kc service --------- Co-authored-by: Federico Maccaroni * Fix app fresh install user login with master password. (#2676) * [PM-3303] Fix biometric login after key migration (#2679) * [PM-3303] Add condition to biometric unlock * [PM-3381] Fix TDE login 2FA flow (#2678) * [PM-3381] Check for vault lock on 2FA screen * [PM-3381] Move logic to ViewModel * [PM-3381] Fix null vm error * [PM-3379] Fix key rotation on trusted device. (#2680) * [PM-3381] Update login flows (#2683) * [PM-3381] Update login flows * [PM-3381] Remove _authingWithSso parameter * PM-3385 Fix MP reprompt item level when no MP hash is stored like logging in with TDE. Also refactor code to be more maintainable (#2687) * PM-3386 Fix MP reprompt / OTP decision to be also based on the master key hash. (#2688) * PM-3450 Fix has master password with mp key hash check (#2689) * [PM-3394] Fix login with device for passwordless approvals (#2686) * set activeUserId to null when logging in a new account - Also stop the user key from being set in inactive accounts * get token for login with device if approving device doesn't have master key * add comment * simplify logic * check for route instead of using isAuthenticated - we don't clear the user id when logging in new account - this means we can't trust the state service, so we have to base our logic off the route in login with device * use authenticated auth request for tde login with device * [PM-3394] Add authingWithSso parameter to LoginPasswordlessRequestPage. * pr feedback * [PM-3394] Refactor condition Co-authored-by: Federico Maccaroni --------- Co-authored-by: André Bispo Co-authored-by: Federico Maccaroni * [PM-3462] Handle force password reset on mobile with TDE (#2694) * [PM-3462] Handle force password reset on mobile with TDE * [PM-3462] update references to refactored crypto method - fix kc bug, we were sending private key instead of user key to server - rename kc service method to be correct * [PM-3462] Update TwoFactorPage login logic * [PM-3462] Added pending admin request check to TwoFactorPage * [PM-3462] Added new exception types for null keys --------- Co-authored-by: André Bispo * [PM-1029] Fix Async suffix in ApiService. Add UserKeyNullExceptions. * [PM 3513] Fix passwordless 2fa login with device on mobile (#2700) * [PM-3513] Fix 2FA for normal login with device with users without mp * move _userKey --------- Co-authored-by: André Bispo * clear encrypted pin on logout (#2699) --------- Co-authored-by: André Bispo Co-authored-by: Jake Fink Co-authored-by: Federico Maccaroni --- src/Android/MainApplication.cs | 6 +- src/Android/Services/BiometricService.cs | 13 +- .../Abstractions/IPasswordRepromptService.cs | 5 +- src/App/Pages/Accounts/LockPage.xaml | 4 +- src/App/Pages/Accounts/LockPage.xaml.cs | 11 +- src/App/Pages/Accounts/LockPageViewModel.cs | 187 +-- .../Accounts/LoginApproveDevicePage.xaml | 76 + .../Accounts/LoginApproveDevicePage.xaml.cs | 64 + .../Accounts/LoginApproveDeviceViewModel.cs | 150 ++ src/App/Pages/Accounts/LoginPage.xaml.cs | 3 +- .../LoginPasswordlessRequestPage.xaml | 52 +- .../LoginPasswordlessRequestPage.xaml.cs | 5 +- .../LoginPasswordlessRequestViewModel.cs | 193 ++- src/App/Pages/Accounts/LoginSsoPage.xaml.cs | 9 + .../Pages/Accounts/LoginSsoPageViewModel.cs | 97 +- .../Pages/Accounts/RegisterPageViewModel.cs | 17 +- .../RemoveMasterPasswordPageViewModel.cs | 6 +- .../Accounts/SetPasswordPageViewModel.cs | 41 +- src/App/Pages/Accounts/TwoFactorPage.xaml.cs | 38 +- .../Pages/Accounts/TwoFactorPageViewModel.cs | 106 +- .../UpdateTempPasswordPageViewModel.cs | 16 +- .../Settings/ExportVaultPageViewModel.cs | 12 +- .../SettingsPage/SettingsPageViewModel.cs | 78 +- src/App/Pages/TabsPage.cs | 2 +- .../Pages/Vault/AttachmentsPageViewModel.cs | 2 +- .../Vault/AutofillCiphersPageViewModel.cs | 2 +- src/App/Pages/Vault/CipherAddEditPage.xaml.cs | 8 +- .../Pages/Vault/CipherDetailsPageViewModel.cs | 4 +- src/App/Pages/Vault/CiphersPageViewModel.cs | 4 +- .../Vault/OTPCipherSelectionPageViewModel.cs | 2 +- src/App/Resources/AppResources.Designer.cs | 137 +- src/App/Resources/AppResources.resx | 47 +- src/App/Services/BaseBiometricService.cs | 25 + .../Services/MobilePasswordRepromptService.cs | 17 +- .../AccountManagement/AccountsManager.cs | 1 + src/App/Utilities/AppHelpers.cs | 63 +- .../VerificationActionsFlowHelper.cs | 17 +- src/Core/Abstractions/IApiService.cs | 18 +- src/Core/Abstractions/IAuthService.cs | 10 +- src/Core/Abstractions/IBiometricService.cs | 1 + src/Core/Abstractions/ICryptoService.cs | 81 +- .../Abstractions/IDeviceTrustCryptoService.cs | 17 + src/Core/Abstractions/IKeyConnectorService.cs | 17 +- .../IPasswordResetEnrollmentService.cs | 12 + src/Core/Abstractions/IStateService.cs | 52 +- .../Abstractions/IUserVerificationService.cs | 1 + src/Core/Abstractions/IVaultTimeoutService.cs | 3 +- src/Core/Constants.cs | 20 +- src/Core/Enums/AuthRequestType.cs | 11 + src/Core/Enums/DeviceType.cs | 25 +- src/Core/Exceptions/MasterKeyNullException.cs | 12 + .../UserAndMasterKeysNullException.cs | 12 + src/Core/Exceptions/UserKeyNullException.cs | 12 + src/Core/Models/Domain/Account.cs | 11 +- .../Models/Domain/AccountDecryptionOptions.cs | 27 + src/Core/Models/Domain/AuthResult.cs | 5 +- .../Models/Domain/PendingAdminAuthRequest.cs | 10 + src/Core/Models/Domain/SymmetricCryptoKey.cs | 28 + .../Request/PasswordlessCreateLoginRequest.cs | 8 +- .../Request/PasswordlessLoginRequest.cs | 2 +- .../Request/TrustedDeviceKeysRequest.cs | 10 + src/Core/Models/Response/DeviceResponse.cs | 13 + .../Models/Response/IdentityTokenResponse.cs | 1 + src/Core/Services/ApiService.cs | 51 +- src/Core/Services/AuthService.cs | 227 ++- src/Core/Services/CipherService.cs | 37 +- src/Core/Services/CollectionService.cs | 2 +- src/Core/Services/CryptoService.cs | 1258 ++++++++++------- src/Core/Services/DeviceTrustCryptoService.cs | 134 ++ src/Core/Services/FolderService.cs | 5 +- src/Core/Services/KeyConnectorService.cs | 74 +- .../Services/PasswordGenerationService.cs | 4 +- .../PasswordResetEnrollmentService.cs | 62 + src/Core/Services/SendService.cs | 2 +- src/Core/Services/StateService.cs | 314 +++- src/Core/Services/SyncService.cs | 6 +- src/Core/Services/UserVerificationService.cs | 22 +- src/Core/Services/VaultTimeoutService.cs | 82 +- src/Core/Utilities/ServiceContainer.cs | 20 +- .../CredentialProviderViewController.cs | 29 +- src/iOS.Autofill/Utilities/AutofillHelpers.cs | 2 +- .../BaseLockPasswordViewController.cs | 162 ++- .../Controllers/LockPasswordViewController.cs | 197 +-- src/iOS.Core/Services/BiometricService.cs | 13 +- src/iOS.Core/Utilities/iOSCoreHelpers.cs | 6 +- src/iOS.Extension/LoadingViewController.cs | 28 +- src/iOS.Extension/LoginListViewController.cs | 2 +- .../LoadingViewController.cs | 27 +- test/Core.Test/Services/CipherServiceTests.cs | 6 +- test/Core.Test/Services/SendServiceTests.cs | 2 +- 90 files changed, 3348 insertions(+), 1365 deletions(-) create mode 100644 src/App/Pages/Accounts/LoginApproveDevicePage.xaml create mode 100644 src/App/Pages/Accounts/LoginApproveDevicePage.xaml.cs create mode 100644 src/App/Pages/Accounts/LoginApproveDeviceViewModel.cs create mode 100644 src/App/Services/BaseBiometricService.cs create mode 100644 src/Core/Abstractions/IDeviceTrustCryptoService.cs create mode 100644 src/Core/Abstractions/IPasswordResetEnrollmentService.cs create mode 100644 src/Core/Enums/AuthRequestType.cs create mode 100644 src/Core/Exceptions/MasterKeyNullException.cs create mode 100644 src/Core/Exceptions/UserAndMasterKeysNullException.cs create mode 100644 src/Core/Exceptions/UserKeyNullException.cs create mode 100644 src/Core/Models/Domain/AccountDecryptionOptions.cs create mode 100644 src/Core/Models/Domain/PendingAdminAuthRequest.cs create mode 100644 src/Core/Models/Request/TrustedDeviceKeysRequest.cs create mode 100644 src/Core/Models/Response/DeviceResponse.cs create mode 100644 src/Core/Services/DeviceTrustCryptoService.cs create mode 100644 src/Core/Services/PasswordResetEnrollmentService.cs diff --git a/src/Android/MainApplication.cs b/src/Android/MainApplication.cs index be1cf8174..cc2361821 100644 --- a/src/Android/MainApplication.cs +++ b/src/Android/MainApplication.cs @@ -68,9 +68,9 @@ namespace Bit.Droid ServiceContainer.Register("deleteAccountActionFlowExecutioner", deleteAccountActionFlowExecutioner); var verificationActionsFlowHelper = new VerificationActionsFlowHelper( - ServiceContainer.Resolve("keyConnectorService"), ServiceContainer.Resolve("passwordRepromptService"), - ServiceContainer.Resolve("cryptoService")); + ServiceContainer.Resolve("cryptoService"), + ServiceContainer.Resolve()); ServiceContainer.Register("verificationActionsFlowHelper", verificationActionsFlowHelper); var accountsManager = new AccountsManager( @@ -156,9 +156,9 @@ namespace Bit.Droid messagingService, broadcasterService); var autofillHandler = new AutofillHandler(stateService, messagingService, clipboardService, platformUtilsService, new LazyResolve()); - var biometricService = new BiometricService(stateService); var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService); var cryptoService = new CryptoService(stateService, cryptoFunctionService); + var biometricService = new BiometricService(stateService, cryptoService); var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService); ServiceContainer.Register(preferencesStorage); diff --git a/src/Android/Services/BiometricService.cs b/src/Android/Services/BiometricService.cs index 7a41b0319..fbca61cc0 100644 --- a/src/Android/Services/BiometricService.cs +++ b/src/Android/Services/BiometricService.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using Android.OS; using Android.Security.Keystore; +using Bit.App.Services; using Bit.Core.Abstractions; using Bit.Core.Services; using Java.Security; @@ -9,10 +10,8 @@ using Javax.Crypto; namespace Bit.Droid.Services { - public class BiometricService : IBiometricService + public class BiometricService : BaseBiometricService { - private readonly IStateService _stateService; - private const string KeyName = "com.8bit.bitwarden.biometric_integrity"; private const string KeyStoreName = "AndroidKeyStore"; @@ -24,14 +23,14 @@ namespace Bit.Droid.Services private readonly KeyStore _keystore; - public BiometricService(IStateService stateService) + public BiometricService(IStateService stateService, ICryptoService cryptoService) + : base(stateService, cryptoService) { - _stateService = stateService; _keystore = KeyStore.GetInstance(KeyStoreName); _keystore.Load(null); } - public async Task SetupBiometricAsync(string bioIntegritySrcKey = null) + public override async Task SetupBiometricAsync(string bioIntegritySrcKey = null) { if (Build.VERSION.SdkInt >= BuildVersionCodes.M) { @@ -41,7 +40,7 @@ namespace Bit.Droid.Services return true; } - public async Task IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null) + public override async Task IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null) { if (Build.VERSION.SdkInt < BuildVersionCodes.M) { diff --git a/src/App/Abstractions/IPasswordRepromptService.cs b/src/App/Abstractions/IPasswordRepromptService.cs index 579d9ab44..2490271c2 100644 --- a/src/App/Abstractions/IPasswordRepromptService.cs +++ b/src/App/Abstractions/IPasswordRepromptService.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using Bit.Core.Enums; namespace Bit.App.Abstractions { @@ -6,10 +7,8 @@ namespace Bit.App.Abstractions { string[] ProtectedFields { get; } - Task ShowPasswordPromptAsync(); + Task PromptAndCheckPasswordIfNeededAsync(CipherRepromptType repromptType = CipherRepromptType.Password); Task<(string password, bool valid)> ShowPasswordPromptAndGetItAsync(); - - Task Enabled(); } } 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..316dfbc74 100644 --- a/src/App/Pages/Accounts/LockPage.xaml.cs +++ b/src/App/Pages/Accounts/LockPage.xaml.cs @@ -20,13 +20,14 @@ namespace Bit.App.Pages private bool _promptedAfterResume; private bool _appeared; - public LockPage(AppOptions appOptions = null, bool autoPromptBiometric = true) + public LockPage(AppOptions appOptions = null, bool autoPromptBiometric = true, bool checkPendingAuthRequests = true) { _appOptions = appOptions; _autoPromptBiometric = autoPromptBiometric; InitializeComponent(); _broadcasterService = ServiceContainer.Resolve(); _vm = BindingContext as LockPageViewModel; + _vm.CheckPendingAuthRequests = checkPendingAuthRequests; _vm.Page = this; _vm.UnlockedAction = () => Device.BeginInvokeOnMainThread(async () => await UnlockedAsync()); @@ -44,7 +45,7 @@ namespace Bit.App.Pages { get { - if (_vm?.PinLock ?? false) + if (_vm?.PinEnabled ?? false) { return _pin; } @@ -54,7 +55,7 @@ namespace Bit.App.Pages public async Task PromptBiometricAfterResumeAsync() { - if (_vm.BiometricLock) + if (_vm.BiometricEnabled) { await Task.Delay(500); if (!_promptedAfterResume) @@ -91,13 +92,13 @@ namespace Bit.App.Pages _vm.FocusSecretEntry += PerformFocusSecretEntry; - if (!_vm.BiometricLock) + if (!_vm.BiometricEnabled) { RequestFocus(SecretEntry); } else { - if (_vm.UsingKeyConnector && !_vm.PinLock) + if (!_vm.HasMasterPassword && !_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 c56968933..3e6946c6a 100644 --- a/src/App/Pages/Accounts/LockPageViewModel.cs +++ b/src/App/Pages/Accounts/LockPageViewModel.cs @@ -27,27 +27,27 @@ namespace Bit.App.Pages private readonly IEnvironmentService _environmentService; private readonly IStateService _stateService; private readonly IBiometricService _biometricService; - private readonly IKeyConnectorService _keyConnectorService; + private readonly IUserVerificationService _userVerificationService; private readonly ILogger _logger; private readonly IWatchDeviceService _watchDeviceService; private readonly WeakEventManager _secretEntryFocusWeakEventManager = new WeakEventManager(); private readonly IPolicyService _policyService; private readonly IPasswordGenerationService _passwordGenerationService; - + private IDeviceTrustCryptoService _deviceTrustCryptoService; + private readonly ISyncService _syncService; private string _email; private string _masterPassword; private string _pin; private bool _showPassword; - private bool _pinLock; - private bool _biometricLock; + private PinLockType _pinStatus; + private bool _pinEnabled; + private bool _biometricEnabled; private bool _biometricIntegrityValid = true; private bool _biometricButtonVisible; - private bool _usingKeyConnector; + private bool _hasMasterPassword; private string _biometricButtonText; private string _loggedInAsText; private string _lockedVerifyText; - private bool _isPinProtected; - private bool _isPinProtectedWithKey; public LockPageViewModel() { @@ -60,11 +60,13 @@ namespace Bit.App.Pages _environmentService = ServiceContainer.Resolve("environmentService"); _stateService = ServiceContainer.Resolve("stateService"); _biometricService = ServiceContainer.Resolve("biometricService"); - _keyConnectorService = ServiceContainer.Resolve("keyConnectorService"); + _userVerificationService = ServiceContainer.Resolve(); _logger = ServiceContainer.Resolve("logger"); _watchDeviceService = ServiceContainer.Resolve(); _policyService = ServiceContainer.Resolve(); _passwordGenerationService = ServiceContainer.Resolve(); + _deviceTrustCryptoService = ServiceContainer.Resolve(); + _syncService = ServiceContainer.Resolve(); PageTitle = AppResources.VerifyMasterPassword; TogglePasswordCommand = new Command(TogglePassword); @@ -100,21 +102,21 @@ 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 + public bool HasMasterPassword { - get => _usingKeyConnector; + get => _hasMasterPassword; } - public bool BiometricLock + public bool BiometricEnabled { - get => _biometricLock; - set => SetProperty(ref _biometricLock, value); + get => _biometricEnabled; + set => SetProperty(ref _biometricEnabled, value); } public bool BiometricIntegrityValid @@ -147,6 +149,8 @@ namespace Bit.App.Pages set => SetProperty(ref _lockedVerifyText, value); } + public bool CheckPendingAuthRequests { get; set; } + public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; } public Command SubmitCommand { get; } @@ -162,18 +166,32 @@ 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(); - - // Users with key connector and without biometric or pin has no MP to unlock with - _usingKeyConnector = await _keyConnectorService.GetUsesKeyConnector(); - if (_usingKeyConnector && !(BiometricLock || PinLock)) + var pendingRequest = await _stateService.GetPendingAdminAuthRequestAsync(); + if (pendingRequest != null && CheckPendingAuthRequests) { await _vaultTimeoutService.LogOutAsync(); return; } + + _pinStatus = await _vaultTimeoutService.GetPinLockTypeAsync(); + + var ephemeralPinSet = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync() + ?? await _stateService.GetPinProtectedKeyAsync(); + PinEnabled = (_pinStatus == PinLockType.Transient && ephemeralPinSet != null) || + _pinStatus == PinLockType.Persistent; + BiometricEnabled = await _vaultTimeoutService.IsBiometricLockSetAsync() && await _biometricService.CanUseBiometricsUnlockAsync(); + + // Users without MP and without biometric or pin has no MP to unlock with + _hasMasterPassword = await _userVerificationService.HasMasterPasswordAsync(); + if (await _stateService.IsAuthenticatedAsync() + && !_hasMasterPassword + && !BiometricEnabled + && !PinEnabled) + { + await _vaultTimeoutService.LogOutAsync(); + return; + } + _email = await _stateService.GetEmailAsync(); if (string.IsNullOrWhiteSpace(_email)) { @@ -188,26 +206,18 @@ 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; } else { - if (_usingKeyConnector) - { - PageTitle = AppResources.UnlockVault; - LockedVerifyText = AppResources.VaultLockedIdentity; - } - else - { - PageTitle = AppResources.VerifyMasterPassword; - LockedVerifyText = AppResources.VaultLockedMasterPassword; - } + PageTitle = _hasMasterPassword ? AppResources.VerifyMasterPassword : AppResources.UnlockVault; + LockedVerifyText = _hasMasterPassword ? AppResources.VaultLockedMasterPassword : AppResources.VaultLockedIdentity; } - if (BiometricLock) + if (BiometricEnabled) { BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync(); if (!_biometricIntegrityValid) @@ -229,14 +239,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 +257,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 == PinLockType.Persistent) { - var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email, + userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyAsync(); + var oldEncryptedKey = await _stateService.GetPinProtectedAsync(); + oldPinProtected = oldEncryptedKey != null ? new EncString(oldEncryptedKey) : null; + } + else if (_pinStatus == PinLockType.Transient) + { + userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync(); + oldPinProtected = await _stateService.GetPinProtectedKeyAsync(); + } + + UserKey userKey; + if (oldPinProtected != null) + { + userKey = await _cryptoService.DecryptAndMigrateOldPinKeyAsync( + _pinStatus == PinLockType.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 SetUserKeyAndContinueAsync(userKey); } } catch @@ -295,19 +325,21 @@ namespace Bit.App.Pages } else { - var key = await _cryptoService.MakeKeyAsync(MasterPassword, _email, kdfConfig); - var storedKeyHash = await _cryptoService.GetKeyHashAsync(); + var masterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, _email, kdfConfig); + var storedKeyHash = await _cryptoService.GetMasterKeyHashAsync(); var passwordValid = false; MasterPasswordPolicyOptions enforcedMasterPasswordOptions = null; if (storedKeyHash != null) { - passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(MasterPassword, key); + // Offline unlock possible + passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(MasterPassword, masterKey); } else { + // Online unlock required await _deviceActionService.ShowLoadingAsync(AppResources.Loading); - var keyHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.ServerAuthorization); + var keyHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, masterKey, HashPurpose.ServerAuthorization); var request = new PasswordVerificationRequest(); request.MasterPasswordHash = keyHash; @@ -316,8 +348,8 @@ namespace Bit.App.Pages var response = await _apiService.PostAccountVerifyPasswordAsync(request); enforcedMasterPasswordOptions = response.MasterPasswordPolicy; passwordValid = true; - var localKeyHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.LocalAuthorization); - await _cryptoService.SetKeyHashAsync(localKeyHash); + var localKeyHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, masterKey, HashPurpose.LocalAuthorization); + await _cryptoService.SetMasterKeyHashAsync(localKeyHash); } catch (Exception e) { @@ -327,15 +359,6 @@ namespace Bit.App.Pages } if (passwordValid) { - if (_isPinProtected) - { - var protectedPin = await _stateService.GetProtectedPinAsync(); - var encKey = await _cryptoService.GetEncKeyAsync(key); - var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey); - var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, _email, kdfConfig); - await _stateService.SetPinProtectedKeyAsync(await _cryptoService.EncryptAsync(key.Key, pinKey)); - } - if (await RequirePasswordChangeAsync(enforcedMasterPasswordOptions)) { // Save the ForcePasswordResetReason to force a password reset after unlock @@ -345,10 +368,13 @@ namespace Bit.App.Pages MasterPassword = string.Empty; await AppHelpers.ResetInvalidUnlockAttemptsAsync(); - await SetKeyAndContinueAsync(key); + + var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey); + await _cryptoService.SetMasterKeyAsync(masterKey); + await SetUserKeyAndContinueAsync(userKey); // Re-enable biometrics - if (BiometricLock & !BiometricIntegrityValid) + if (BiometricEnabled & !BiometricIntegrityValid) { await _biometricService.SetupBiometricAsync(); } @@ -425,7 +451,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)); } @@ -433,32 +459,35 @@ 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) { - await DoContinueAsync(); + var userKey = await _cryptoService.GetBiometricUnlockKeyAsync(); + await SetUserKeyAndContinueAsync(userKey); } } - private async Task SetKeyAndContinueAsync(SymmetricCryptoKey key) + private async Task SetUserKeyAndContinueAsync(UserKey key) { - var hasKey = await _cryptoService.HasKeyAsync(); + var hasKey = await _cryptoService.HasUserKeyAsync(); if (!hasKey) { - await _cryptoService.SetKeyAsync(key); + await _cryptoService.SetUserKeyAsync(key); } + await _deviceTrustCryptoService.TrustDeviceIfNeededAsync(); await DoContinueAsync(); } private async Task DoContinueAsync() { + _syncService.FullSyncAsync(false).FireAndForget(); await _stateService.SetBiometricLockedAsync(false); _watchDeviceService.SyncDataToWatchAsync().FireAndForget(); _messagingService.Send("unlocked"); diff --git a/src/App/Pages/Accounts/LoginApproveDevicePage.xaml b/src/App/Pages/Accounts/LoginApproveDevicePage.xaml new file mode 100644 index 000000000..42edf6371 --- /dev/null +++ b/src/App/Pages/Accounts/LoginApproveDevicePage.xaml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + +