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, var selection = await DisplayActionSheet(AppResources.Options, AppResources.Cancel,
AppResources.Delete, options.ToArray()); AppResources.Delete, options.ToArray());
if (!await _vm.PromptPasswordAsync())
{
return;
}
if (selection == AppResources.Delete) if (selection == AppResources.Delete)
{ {
if (await _vm.DeleteAsync()) if (await _vm.DeleteAsync())

View file

@ -176,6 +176,11 @@ namespace Bit.App.Services
var password = await _deviceActionService.DisplayPromptAync(AppResources.PasswordConfirmation, var password = await _deviceActionService.DisplayPromptAync(AppResources.PasswordConfirmation,
AppResources.PasswordConfirmationDesc, null, AppResources.Submit, AppResources.Cancel, password: true); AppResources.PasswordConfirmationDesc, null, AppResources.Submit, AppResources.Cancel, password: true);
if (password == null)
{
return false;
}
var valid = await validator(password); var valid = await validator(password);
if (!valid) if (!valid)

View file

@ -82,7 +82,10 @@ namespace Bit.App.Utilities
} }
else if (selection == AppResources.Edit) 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) else if (selection == AppResources.CopyUsername)
{ {

View file

@ -41,6 +41,8 @@
public static string PreviousPageKey = "previousPage"; public static string PreviousPageKey = "previousPage";
public static string InlineAutofillEnabledKey = "inlineAutofillEnabled"; public static string InlineAutofillEnabledKey = "inlineAutofillEnabled";
public static string InvalidUnlockAttempts = "invalidUnlockAttempts"; public static string InvalidUnlockAttempts = "invalidUnlockAttempts";
public static string PasswordRepromptAutofillKey = "passwordRepromptAutofillKey";
public static string PasswordVerifiedAutofillKey = "passwordVerifiedAutofillKey";
public const int SelectFileRequestCode = 42; public const int SelectFileRequestCode = 42;
public const int SelectFilePermissionRequestCode = 43; public const int SelectFilePermissionRequestCode = 43;
public const int SaveFileRequestCode = 44; public const int SaveFileRequestCode = 44;

View file

@ -79,6 +79,9 @@ namespace Bit.iOS.Autofill
public override async void ProvideCredentialWithoutUserInteraction(ASPasswordCredentialIdentity credentialIdentity) public override async void ProvideCredentialWithoutUserInteraction(ASPasswordCredentialIdentity credentialIdentity)
{ {
InitAppIfNeeded(); 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()) if (!await IsAuthed() || await IsLocked())
{ {
var err = new NSError(new NSString("ASExtensionErrorDomain"), var err = new NSError(new NSString("ASExtensionErrorDomain"),
@ -87,7 +90,7 @@ namespace Bit.iOS.Autofill
return; return;
} }
_context.CredentialIdentity = credentialIdentity; _context.CredentialIdentity = credentialIdentity;
await ProvideCredentialAsync(); await ProvideCredentialAsync(false);
} }
public override async void PrepareInterfaceToProvideCredential(ASPasswordCredentialIdentity credentialIdentity) 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); var cipherService = ServiceContainer.Resolve<ICipherService>("cipherService", true);
Bit.Core.Models.Domain.Cipher cipher = null; Bit.Core.Models.Domain.Cipher cipher = null;
@ -229,6 +232,30 @@ namespace Bit.iOS.Autofill
var storageService = ServiceContainer.Resolve<IStorageService>("storageService"); var storageService = ServiceContainer.Resolve<IStorageService>("storageService");
var decCipher = await cipher.DecryptAsync(); 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; string totpCode = null;
var disableTotpCopy = await storageService.GetAsync<bool?>(Bit.Core.Constants.DisableAutoTotpCopyKey); var disableTotpCopy = await storageService.GetAsync<bool?>(Bit.Core.Constants.DisableAutoTotpCopyKey);
if (!disableTotpCopy.GetValueOrDefault(false)) if (!disableTotpCopy.GetValueOrDefault(false))
@ -248,7 +275,8 @@ namespace Bit.iOS.Autofill
private async void CheckLock(Action notLockedAction) 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); PerformSegue("lockPasswordSegue", this);
} }

View file

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

View file

@ -28,6 +28,9 @@ namespace Bit.iOS.Core.Controllers
private bool _pinLock; private bool _pinLock;
private bool _biometricLock; private bool _biometricLock;
private bool _biometricIntegrityValid = true; private bool _biometricIntegrityValid = true;
private bool _passwordReprompt = false;
protected bool autofillExtension = false;
public LockPasswordViewController(IntPtr handle) public LockPasswordViewController(IntPtr handle)
: base(handle) : base(handle)
@ -44,7 +47,7 @@ namespace Bit.iOS.Core.Controllers
public string BiometricIntegrityKey { get; set; } public string BiometricIntegrityKey { get; set; }
public override void ViewDidLoad() public override async void ViewDidLoad()
{ {
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService"); _vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService"); _cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
@ -55,13 +58,25 @@ namespace Bit.iOS.Core.Controllers
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"); _platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_biometricService = ServiceContainer.Resolve<IBiometricService>("biometricService"); _biometricService = ServiceContainer.Resolve<IBiometricService>("biometricService");
_pinSet = _vaultTimeoutService.IsPinLockSetAsync().GetAwaiter().GetResult(); // We re-use the lock screen for autofill extension to verify master password
_pinLock = (_pinSet.Item1 && _vaultTimeoutService.PinProtectedKey != null) || _pinSet.Item2; // when trying to access protected items.
_biometricLock = _vaultTimeoutService.IsBiometricLockSetAsync().GetAwaiter().GetResult() && if (autofillExtension && await _storageService.GetAsync<bool>(Bit.Core.Constants.PasswordRepromptAutofillKey))
_cryptoService.HasKeyAsync().GetAwaiter().GetResult(); {
_biometricIntegrityValid = _biometricService.ValidateIntegrityAsync(BiometricIntegrityKey).GetAwaiter() _passwordReprompt = true;
.GetResult(); _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; BaseNavItem.Title = _pinLock ? AppResources.VerifyPIN : AppResources.VerifyMasterPassword;
BaseCancelButton.Title = AppResources.Cancel; BaseCancelButton.Title = AppResources.Cancel;
BaseSubmitButton.Title = AppResources.Submit; BaseSubmitButton.Title = AppResources.Submit;
@ -199,7 +214,7 @@ namespace Bit.iOS.Core.Controllers
_vaultTimeoutService.PinProtectedKey = await _cryptoService.EncryptAsync(key2.Key, pinKey); _vaultTimeoutService.PinProtectedKey = await _cryptoService.EncryptAsync(key2.Key, pinKey);
} }
await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key2); await SetKeyAndContinueAsync(key2, true);
// Re-enable biometrics // Re-enable biometrics
if (_biometricLock & !_biometricIntegrityValid) 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(); var hasKey = await _cryptoService.HasKeyAsync();
if (!hasKey) if (!hasKey)
{ {
await _cryptoService.SetKeyAsync(key); 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; _vaultTimeoutService.BiometricLocked = false;
MasterPasswordCell.TextField.ResignFirstResponder(); MasterPasswordCell.TextField.ResignFirstResponder();
Success(); Success();
@ -325,7 +344,15 @@ namespace Bit.iOS.Core.Controllers
if (indexPath.Row == 0) if (indexPath.Row == 0)
{ {
var cell = new ExtendedUITableViewCell(); 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() ? var biometricButtonText = _controller._deviceActionService.SupportsFaceBiometric() ?
AppResources.UseFaceIDToUnlock : AppResources.UseFingerprintToUnlock; AppResources.UseFaceIDToUnlock : AppResources.UseFingerprintToUnlock;
@ -353,7 +380,7 @@ namespace Bit.iOS.Core.Controllers
public override nint NumberOfSections(UITableView tableView) 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) public override nint RowsInSection(UITableView tableview, nint section)