PM-2575 Fixed extension freeze when using the return button on the keyboard when unlocking the extension. Also added way to prevent multiple executions of checking the password and logging exceptions. (#2568)

This commit is contained in:
Federico Maccaroni 2023-06-13 22:38:08 +02:00 committed by GitHub
parent 1332ef7b43
commit 98705e443f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 99 additions and 84 deletions

View File

@ -6435,7 +6435,7 @@ namespace Bit.App.Resources {
}
/// <summary>
/// Looks up a localized string similar to Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve.
/// Looks up a localized string similar to Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve..
/// </summary>
public static string UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve {
get {

View File

@ -2635,6 +2635,6 @@ Do you want to switch to this account?</value>
<value>Master password re-prompt help</value>
</data>
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve</value>
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve.</value>
</data>
</root>

View File

@ -1,5 +1,6 @@
using System;
using Bit.App.Controls;
using Bit.Core.Utilities;
using Bit.iOS.Core.Utilities;
using UIKit;
@ -44,7 +45,7 @@ namespace Bit.iOS.Autofill
partial void SubmitButton_Activated(UIBarButtonItem sender)
{
var task = CheckPasswordAsync();
CheckPasswordAsync().FireAndForget();
}
partial void CancelButton_Activated(UIBarButtonItem sender)

View File

@ -36,6 +36,7 @@ namespace Bit.iOS.Core.Controllers
private bool _passwordReprompt = false;
private bool _usesKeyConnector;
private bool _biometricUnlockOnly = false;
private bool _checkingPassword;
protected bool autofillExtension = false;
@ -154,7 +155,7 @@ namespace Bit.iOS.Core.Controllers
MasterPasswordCell.TextField.ReturnKeyType = UIReturnKeyType.Go;
MasterPasswordCell.TextField.ShouldReturn += (UITextField tf) =>
{
CheckPasswordAsync().GetAwaiter().GetResult();
CheckPasswordAsync().FireAndForget();
return true;
};
if (_pinLock)
@ -208,108 +209,121 @@ namespace Bit.iOS.Core.Controllers
MasterPasswordCell.TextField.BecomeFirstResponder();
}
}
protected async Task CheckPasswordAsync()
{
if (string.IsNullOrWhiteSpace(MasterPasswordCell.TextField.Text))
{
var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred,
string.Format(AppResources.ValidationFieldRequired,
_pinLock ? AppResources.PIN : AppResources.MasterPassword),
AppResources.Ok);
PresentViewController(alert, true, null);
return;
}
var email = await _stateService.GetEmailAsync();
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
var inputtedValue = MasterPasswordCell.TextField.Text;
// HACK: iOS extensions have constrained memory, given how it works Argon2Id, it's likely to crash
// the extension depending on the argon2id memory configured.
// So, we warn the user and advise to decrease the configured memory letting them the option to continue, if wanted.
if (kdfConfig.Type == KdfType.Argon2id
&&
kdfConfig.Memory > Constants.MaximumArgon2IdMemoryBeforeExtensionCrashing
&&
!await _platformUtilsService.ShowDialogAsync(AppResources.UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve, AppResources.Warning, AppResources.Continue, AppResources.Cancel))
if (_checkingPassword)
{
return;
}
_checkingPassword = true;
if (_pinLock)
try
{
var failed = true;
try
if (string.IsNullOrWhiteSpace(MasterPasswordCell.TextField.Text))
{
if (_isPinProtected)
var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred,
string.Format(AppResources.ValidationFieldRequired,
_pinLock ? AppResources.PIN : AppResources.MasterPassword),
AppResources.Ok);
PresentViewController(alert, true, null);
return;
}
var email = await _stateService.GetEmailAsync();
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
var inputtedValue = MasterPasswordCell.TextField.Text;
// HACK: iOS extensions have constrained memory, given how it works Argon2Id, it's likely to crash
// the extension depending on the argon2id memory configured.
// So, we warn the user and advise to decrease the configured memory letting them the option to continue, if wanted.
if (kdfConfig.Type == KdfType.Argon2id
&&
kdfConfig.Memory > Constants.MaximumArgon2IdMemoryBeforeExtensionCrashing
&&
!await _platformUtilsService.ShowDialogAsync(AppResources.UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve, AppResources.Warning, AppResources.Continue, AppResources.Cancel))
{
return;
}
if (_pinLock)
{
var failed = true;
try
{
var key = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, 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 != inputtedValue;
if (!failed)
if (_isPinProtected)
{
var key = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, 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 != inputtedValue;
if (!failed)
{
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key);
}
}
else
{
var key2 = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email,
kdfConfig);
failed = false;
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key);
await SetKeyAndContinueAsync(key2);
}
}
else
catch
{
var key2 = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email,
kdfConfig);
failed = false;
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key2);
failed = true;
}
}
catch
{
failed = true;
}
if (failed)
{
await HandleFailedCredentialsAsync();
}
}
else
{
var key2 = await _cryptoService.MakeKeyAsync(inputtedValue, email, kdfConfig);
var storedKeyHash = await _cryptoService.GetKeyHashAsync();
if (storedKeyHash == null)
{
var oldKey = await _secureStorageService.GetAsync<string>("oldKey");
if (key2.KeyB64 == oldKey)
if (failed)
{
var localKeyHash = await _cryptoService.HashPasswordAsync(inputtedValue, key2, HashPurpose.LocalAuthorization);
await _secureStorageService.RemoveAsync("oldKey");
await _cryptoService.SetKeyHashAsync(localKeyHash);
await HandleFailedCredentialsAsync();
}
}
var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(inputtedValue, key2);
if (passwordValid)
{
if (_isPinProtected)
{
var protectedPin = await _stateService.GetProtectedPinAsync();
var encKey = await _cryptoService.GetEncKeyAsync(key2);
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, email,
kdfConfig);
await _stateService.SetPinProtectedKeyAsync(await _cryptoService.EncryptAsync(key2.Key, pinKey));
}
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key2, true);
}
else
{
await HandleFailedCredentialsAsync();
var key2 = await _cryptoService.MakeKeyAsync(inputtedValue, email, kdfConfig);
var storedKeyHash = await _cryptoService.GetKeyHashAsync();
if (storedKeyHash == null)
{
var oldKey = await _secureStorageService.GetAsync<string>("oldKey");
if (key2.KeyB64 == oldKey)
{
var localKeyHash = await _cryptoService.HashPasswordAsync(inputtedValue, key2, HashPurpose.LocalAuthorization);
await _secureStorageService.RemoveAsync("oldKey");
await _cryptoService.SetKeyHashAsync(localKeyHash);
}
}
var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(inputtedValue, key2);
if (passwordValid)
{
if (_isPinProtected)
{
var protectedPin = await _stateService.GetProtectedPinAsync();
var encKey = await _cryptoService.GetEncKeyAsync(key2);
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, email,
kdfConfig);
await _stateService.SetPinProtectedKeyAsync(await _cryptoService.EncryptAsync(key2.Key, pinKey));
}
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key2, true);
}
else
{
await HandleFailedCredentialsAsync();
}
}
}
finally
{
_checkingPassword = false;
}
}
private async Task HandleFailedCredentialsAsync()