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