diff --git a/src/App/Pages/Vault/ViewPage.xaml.cs b/src/App/Pages/Vault/ViewPage.xaml.cs index 47436832c..36fe4715b 100644 --- a/src/App/Pages/Vault/ViewPage.xaml.cs +++ b/src/App/Pages/Vault/ViewPage.xaml.cs @@ -229,6 +229,12 @@ namespace Bit.App.Pages var selection = await DisplayActionSheet(AppResources.Options, AppResources.Cancel, AppResources.Delete, options.ToArray()); + + if (!await _vm.PromptPasswordAsync()) + { + return; + } + if (selection == AppResources.Delete) { if (await _vm.DeleteAsync()) diff --git a/src/App/Services/MobilePlatformUtilsService.cs b/src/App/Services/MobilePlatformUtilsService.cs index 219e2bd44..1875e2fe7 100644 --- a/src/App/Services/MobilePlatformUtilsService.cs +++ b/src/App/Services/MobilePlatformUtilsService.cs @@ -176,6 +176,11 @@ namespace Bit.App.Services var password = await _deviceActionService.DisplayPromptAync(AppResources.PasswordConfirmation, AppResources.PasswordConfirmationDesc, null, AppResources.Submit, AppResources.Cancel, password: true); + if (password == null) + { + return false; + } + var valid = await validator(password); if (!valid) diff --git a/src/App/Utilities/AppHelpers.cs b/src/App/Utilities/AppHelpers.cs index 20c06ca91..9eb08209f 100644 --- a/src/App/Utilities/AppHelpers.cs +++ b/src/App/Utilities/AppHelpers.cs @@ -82,7 +82,10 @@ namespace Bit.App.Utilities } else if (selection == AppResources.Edit) { - await page.Navigation.PushModalAsync(new NavigationPage(new AddEditPage(cipher.Id))); + if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync()) + { + await page.Navigation.PushModalAsync(new NavigationPage(new AddEditPage(cipher.Id))); + } } else if (selection == AppResources.CopyUsername) { diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index 3e4bcfc55..90cd12e22 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -41,6 +41,8 @@ public static string PreviousPageKey = "previousPage"; public static string InlineAutofillEnabledKey = "inlineAutofillEnabled"; public static string InvalidUnlockAttempts = "invalidUnlockAttempts"; + public static string PasswordRepromptAutofillKey = "passwordRepromptAutofillKey"; + public static string PasswordVerifiedAutofillKey = "passwordVerifiedAutofillKey"; public const int SelectFileRequestCode = 42; public const int SelectFilePermissionRequestCode = 43; public const int SaveFileRequestCode = 44; diff --git a/src/iOS.Autofill/CredentialProviderViewController.cs b/src/iOS.Autofill/CredentialProviderViewController.cs index 713e93c56..6f03d7cf0 100644 --- a/src/iOS.Autofill/CredentialProviderViewController.cs +++ b/src/iOS.Autofill/CredentialProviderViewController.cs @@ -79,6 +79,9 @@ namespace Bit.iOS.Autofill public override async void ProvideCredentialWithoutUserInteraction(ASPasswordCredentialIdentity credentialIdentity) { InitAppIfNeeded(); + var storageService = ServiceContainer.Resolve("storageService"); + await storageService.SaveAsync(Bit.Core.Constants.PasswordRepromptAutofillKey, false); + await storageService.SaveAsync(Bit.Core.Constants.PasswordVerifiedAutofillKey, false); if (!await IsAuthed() || await IsLocked()) { var err = new NSError(new NSString("ASExtensionErrorDomain"), @@ -87,7 +90,7 @@ namespace Bit.iOS.Autofill return; } _context.CredentialIdentity = credentialIdentity; - await ProvideCredentialAsync(); + await ProvideCredentialAsync(false); } public override async void PrepareInterfaceToProvideCredential(ASPasswordCredentialIdentity credentialIdentity) @@ -209,7 +212,7 @@ namespace Bit.iOS.Autofill }); } - private async Task ProvideCredentialAsync() + private async Task ProvideCredentialAsync(bool userInteraction = true) { var cipherService = ServiceContainer.Resolve("cipherService", true); Bit.Core.Models.Domain.Cipher cipher = null; @@ -229,6 +232,30 @@ namespace Bit.iOS.Autofill var storageService = ServiceContainer.Resolve("storageService"); var decCipher = await cipher.DecryptAsync(); + if (decCipher.Reprompt != Bit.Core.Enums.CipherRepromptType.None) + { + // Prompt for password using either the lock screen or dialog unless + // already verified the password. + if (!userInteraction) + { + await storageService.SaveAsync(Bit.Core.Constants.PasswordRepromptAutofillKey, true); + var err = new NSError(new NSString("ASExtensionErrorDomain"), + Convert.ToInt32(ASExtensionErrorCode.UserInteractionRequired), null); + ExtensionContext?.CancelRequest(err); + return; + } + else if (!await storageService.GetAsync(Bit.Core.Constants.PasswordVerifiedAutofillKey)) + { + var passwordRepromptService = ServiceContainer.Resolve("passwordRepromptService"); + if (!await passwordRepromptService.ShowPasswordPromptAsync()) + { + var err = new NSError(new NSString("ASExtensionErrorDomain"), + Convert.ToInt32(ASExtensionErrorCode.UserCanceled), null); + ExtensionContext?.CancelRequest(err); + return; + } + } + } string totpCode = null; var disableTotpCopy = await storageService.GetAsync(Bit.Core.Constants.DisableAutoTotpCopyKey); if (!disableTotpCopy.GetValueOrDefault(false)) @@ -248,7 +275,8 @@ namespace Bit.iOS.Autofill private async void CheckLock(Action notLockedAction) { - if (await IsLocked()) + var storageService = ServiceContainer.Resolve("storageService"); + if (await IsLocked() || await storageService.GetAsync(Bit.Core.Constants.PasswordRepromptAutofillKey)) { PerformSegue("lockPasswordSegue", this); } diff --git a/src/iOS.Autofill/LockPasswordViewController.cs b/src/iOS.Autofill/LockPasswordViewController.cs index ecadef71a..d1cf7e009 100644 --- a/src/iOS.Autofill/LockPasswordViewController.cs +++ b/src/iOS.Autofill/LockPasswordViewController.cs @@ -10,6 +10,7 @@ namespace Bit.iOS.Autofill { BiometricIntegrityKey = Bit.Core.Constants.iOSAutoFillBiometricIntegrityKey; DismissModalAction = Cancel; + autofillExtension = true; } public CredentialProviderViewController CPViewController { get; set; } diff --git a/src/iOS.Core/Controllers/LockPasswordViewController.cs b/src/iOS.Core/Controllers/LockPasswordViewController.cs index 4c0a59b9c..edb285e83 100644 --- a/src/iOS.Core/Controllers/LockPasswordViewController.cs +++ b/src/iOS.Core/Controllers/LockPasswordViewController.cs @@ -28,6 +28,9 @@ namespace Bit.iOS.Core.Controllers private bool _pinLock; private bool _biometricLock; private bool _biometricIntegrityValid = true; + private bool _passwordReprompt = false; + + protected bool autofillExtension = false; public LockPasswordViewController(IntPtr handle) : base(handle) @@ -44,7 +47,7 @@ namespace Bit.iOS.Core.Controllers public string BiometricIntegrityKey { get; set; } - public override void ViewDidLoad() + public override async void ViewDidLoad() { _vaultTimeoutService = ServiceContainer.Resolve("vaultTimeoutService"); _cryptoService = ServiceContainer.Resolve("cryptoService"); @@ -55,13 +58,25 @@ namespace Bit.iOS.Core.Controllers _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); _biometricService = ServiceContainer.Resolve("biometricService"); - _pinSet = _vaultTimeoutService.IsPinLockSetAsync().GetAwaiter().GetResult(); - _pinLock = (_pinSet.Item1 && _vaultTimeoutService.PinProtectedKey != null) || _pinSet.Item2; - _biometricLock = _vaultTimeoutService.IsBiometricLockSetAsync().GetAwaiter().GetResult() && - _cryptoService.HasKeyAsync().GetAwaiter().GetResult(); - _biometricIntegrityValid = _biometricService.ValidateIntegrityAsync(BiometricIntegrityKey).GetAwaiter() - .GetResult(); - + // We re-use the lock screen for autofill extension to verify master password + // when trying to access protected items. + if (autofillExtension && await _storageService.GetAsync(Bit.Core.Constants.PasswordRepromptAutofillKey)) + { + _passwordReprompt = true; + _pinSet = Tuple.Create(false, false); + _pinLock = false; + _biometricLock = false; + } + else + { + _pinSet = _vaultTimeoutService.IsPinLockSetAsync().GetAwaiter().GetResult(); + _pinLock = (_pinSet.Item1 && _vaultTimeoutService.PinProtectedKey != null) || _pinSet.Item2; + _biometricLock = _vaultTimeoutService.IsBiometricLockSetAsync().GetAwaiter().GetResult() && + _cryptoService.HasKeyAsync().GetAwaiter().GetResult(); + _biometricIntegrityValid = _biometricService.ValidateIntegrityAsync(BiometricIntegrityKey).GetAwaiter() + .GetResult(); + } + BaseNavItem.Title = _pinLock ? AppResources.VerifyPIN : AppResources.VerifyMasterPassword; BaseCancelButton.Title = AppResources.Cancel; BaseSubmitButton.Title = AppResources.Submit; @@ -199,7 +214,7 @@ namespace Bit.iOS.Core.Controllers _vaultTimeoutService.PinProtectedKey = await _cryptoService.EncryptAsync(key2.Key, pinKey); } await AppHelpers.ResetInvalidUnlockAttemptsAsync(); - await SetKeyAndContinueAsync(key2); + await SetKeyAndContinueAsync(key2, true); // Re-enable biometrics if (_biometricLock & !_biometricIntegrityValid) @@ -220,18 +235,22 @@ namespace Bit.iOS.Core.Controllers } } - private async Task SetKeyAndContinueAsync(SymmetricCryptoKey key) + private async Task SetKeyAndContinueAsync(SymmetricCryptoKey key, bool masterPassword = false) { var hasKey = await _cryptoService.HasKeyAsync(); if (!hasKey) { await _cryptoService.SetKeyAsync(key); } - DoContinue(); + DoContinue(masterPassword); } - private void DoContinue() + private async void DoContinue(bool masterPassword = false) { + if (masterPassword) + { + await _storageService.SaveAsync(Bit.Core.Constants.PasswordVerifiedAutofillKey, true); + } _vaultTimeoutService.BiometricLocked = false; MasterPasswordCell.TextField.ResignFirstResponder(); Success(); @@ -325,7 +344,15 @@ namespace Bit.iOS.Core.Controllers if (indexPath.Row == 0) { var cell = new ExtendedUITableViewCell(); - if (_controller._biometricIntegrityValid) + if (_controller._passwordReprompt) + { + cell.TextLabel.TextColor = ThemeHelpers.DangerColor; + cell.TextLabel.Font = ThemeHelpers.GetDangerFont(); + cell.TextLabel.Lines = 0; + cell.TextLabel.LineBreakMode = UILineBreakMode.WordWrap; + cell.TextLabel.Text = AppResources.PasswordConfirmationDesc; + } + else if (_controller._biometricIntegrityValid) { var biometricButtonText = _controller._deviceActionService.SupportsFaceBiometric() ? AppResources.UseFaceIDToUnlock : AppResources.UseFingerprintToUnlock; @@ -353,7 +380,7 @@ namespace Bit.iOS.Core.Controllers public override nint NumberOfSections(UITableView tableView) { - return _controller._biometricLock ? 2 : 1; + return _controller._biometricLock || _controller._passwordReprompt ? 2 : 1; } public override nint RowsInSection(UITableView tableview, nint section)