Fixes for password reprompt (#1416)

This commit is contained in:
Oscar Hinton 2021-06-10 17:57:18 +02:00 committed by GitHub
parent 33791a03ac
commit 2b8dbde923
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 90 additions and 18 deletions

View file

@ -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())

View file

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

View file

@ -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)
{

View file

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

View file

@ -79,6 +79,9 @@ namespace Bit.iOS.Autofill
public override async void ProvideCredentialWithoutUserInteraction(ASPasswordCredentialIdentity credentialIdentity)
{
InitAppIfNeeded();
var storageService = ServiceContainer.Resolve<IStorageService>("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<ICipherService>("cipherService", true);
Bit.Core.Models.Domain.Cipher cipher = null;
@ -229,6 +232,30 @@ namespace Bit.iOS.Autofill
var storageService = ServiceContainer.Resolve<IStorageService>("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<bool>(Bit.Core.Constants.PasswordVerifiedAutofillKey))
{
var passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("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<bool?>(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<IStorageService>("storageService");
if (await IsLocked() || await storageService.GetAsync<bool>(Bit.Core.Constants.PasswordRepromptAutofillKey))
{
PerformSegue("lockPasswordSegue", this);
}

View file

@ -10,6 +10,7 @@ namespace Bit.iOS.Autofill
{
BiometricIntegrityKey = Bit.Core.Constants.iOSAutoFillBiometricIntegrityKey;
DismissModalAction = Cancel;
autofillExtension = true;
}
public CredentialProviderViewController CPViewController { get; set; }

View file

@ -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<IVaultTimeoutService>("vaultTimeoutService");
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
@ -55,13 +58,25 @@ namespace Bit.iOS.Core.Controllers
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_biometricService = ServiceContainer.Resolve<IBiometricService>("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<bool>(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)