1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-18 09:13:15 +00:00

[PM-2713] add migration for pin on lock screens

This commit is contained in:
Jacob Fink
2023-07-18 21:23:50 -04:00
parent bdfe806846
commit 7c664f58b3
12 changed files with 363 additions and 215 deletions

View File

@@ -8,11 +8,13 @@ using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Domain;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Bit.iOS.Core.Utilities;
using Bit.iOS.Core.Views;
using Foundation;
using UIKit;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace Bit.iOS.Core.Controllers
@@ -28,10 +30,9 @@ namespace Bit.iOS.Core.Controllers
private IBiometricService _biometricService;
private IKeyConnectorService _keyConnectorService;
private IAccountsManager _accountManager;
private bool _isPinProtected;
private bool _isPinProtectedWithKey;
private bool _pinLock;
private bool _biometricLock;
private PinLockEnum _pinStatus;
private bool _pinEnabled;
private bool _biometricEnabled;
private bool _biometricIntegrityValid = true;
private bool _passwordReprompt = false;
private bool _usesKeyConnector;
@@ -85,7 +86,7 @@ namespace Bit.iOS.Core.Controllers
}
public abstract UITableView TableView { get; }
public override async void ViewDidLoad()
{
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
@@ -103,25 +104,28 @@ namespace Bit.iOS.Core.Controllers
if (autofillExtension && await _stateService.GetPasswordRepromptAutofillAsync())
{
_passwordReprompt = true;
_isPinProtected = false;
_isPinProtectedWithKey = false;
_pinLock = false;
_biometricLock = false;
_pinStatus = PinLockEnum.Disabled;
_pinEnabled = false;
_biometricEnabled = false;
}
else
{
(_isPinProtected, _isPinProtectedWithKey) = await _vaultTimeoutService.IsPinLockSetAsync();
_pinLock = (_isPinProtected && await _stateService.GetPinProtectedKeyAsync() != null) ||
_isPinProtectedWithKey;
_biometricLock = await _vaultTimeoutService.IsBiometricLockSetAsync() &&
await _cryptoService.HasKeyAsync();
_pinStatus = await _vaultTimeoutService.IsPinLockSetAsync();
var ephemeralPinSet = await _stateService.GetUserKeyPinEphemeralAsync()
?? await _stateService.GetPinProtectedKeyAsync();
_pinEnabled = (_pinStatus == PinLockEnum.Transient && ephemeralPinSet != null) ||
_pinStatus == PinLockEnum.Persistent;
_biometricEnabled = await _vaultTimeoutService.IsBiometricLockSetAsync()
&& await _cryptoService.HasEncryptedUserKeyAsync();
_biometricIntegrityValid =
await _platformUtilsService.IsBiometricIntegrityValidAsync(BiometricIntegritySourceKey);
_usesKeyConnector = await _keyConnectorService.GetUsesKeyConnector();
_biometricUnlockOnly = _usesKeyConnector && _biometricLock && !_pinLock;
_biometricUnlockOnly = _usesKeyConnector && _biometricEnabled && !_pinEnabled;
}
if (_pinLock)
if (_pinEnabled)
{
BaseNavItem.Title = AppResources.VerifyPIN;
}
@@ -150,7 +154,7 @@ namespace Bit.iOS.Core.Controllers
if (!_biometricUnlockOnly)
{
MasterPasswordCell.Label.Text = _pinLock ? AppResources.PIN : AppResources.MasterPassword;
MasterPasswordCell.Label.Text = _pinEnabled ? AppResources.PIN : AppResources.MasterPassword;
MasterPasswordCell.TextField.SecureTextEntry = true;
MasterPasswordCell.TextField.ReturnKeyType = UIReturnKeyType.Go;
MasterPasswordCell.TextField.ShouldReturn += (UITextField tf) =>
@@ -158,7 +162,7 @@ namespace Bit.iOS.Core.Controllers
CheckPasswordAsync().FireAndForget();
return true;
};
if (_pinLock)
if (_pinEnabled)
{
MasterPasswordCell.TextField.KeyboardType = UIKeyboardType.NumberPad;
}
@@ -177,7 +181,7 @@ namespace Bit.iOS.Core.Controllers
base.ViewDidLoad();
if (_biometricLock)
if (_biometricEnabled)
{
if (!_biometricIntegrityValid)
{
@@ -198,18 +202,18 @@ namespace Bit.iOS.Core.Controllers
// Users with key connector and without biometric or pin has no MP to unlock with
if (_usesKeyConnector)
{
if (!(_pinLock || _biometricLock) ||
(_biometricLock && !_biometricIntegrityValid))
if (!(_pinEnabled || _biometricEnabled) ||
(_biometricEnabled && !_biometricIntegrityValid))
{
PromptSSO();
}
}
else if (!_biometricLock || !_biometricIntegrityValid)
else if (!_biometricEnabled || !_biometricIntegrityValid)
{
MasterPasswordCell.TextField.BecomeFirstResponder();
}
}
protected async Task CheckPasswordAsync()
{
if (_checkingPassword)
@@ -224,7 +228,7 @@ namespace Bit.iOS.Core.Controllers
{
var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred,
string.Format(AppResources.ValidationFieldRequired,
_pinLock ? AppResources.PIN : AppResources.MasterPassword),
_pinEnabled ? AppResources.PIN : AppResources.MasterPassword),
AppResources.Ok);
PresentViewController(alert, true, null);
return;
@@ -246,33 +250,53 @@ namespace Bit.iOS.Core.Controllers
return;
}
if (_pinLock)
if (_pinEnabled)
{
var failed = true;
try
{
if (_isPinProtected)
EncString userKeyPin = null;
EncString oldPinProtected = null;
if (_pinStatus == PinLockEnum.Persistent)
{
var key = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email,
userKeyPin = await _stateService.GetUserKeyPinAsync();
var oldEncryptedKey = await _stateService.GetPinProtectedAsync();
oldPinProtected = oldEncryptedKey != null ? new EncString(oldEncryptedKey) : null;
}
else if (_pinStatus == PinLockEnum.Transient)
{
userKeyPin = await _stateService.GetUserKeyPinEphemeralAsync();
oldPinProtected = await _stateService.GetPinProtectedKeyAsync();
}
UserKey userKey;
if (oldPinProtected != null)
{
userKey = await _cryptoService.DecryptAndMigrateOldPinKeyAsync(
_pinStatus == PinLockEnum.Transient,
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);
}
oldPinProtected
);
}
else
{
var key2 = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email,
kdfConfig);
failed = false;
userKey = await _cryptoService.DecryptUserKeyWithPinAsync(
inputtedValue,
email,
kdfConfig,
userKeyPin
);
}
var protectedPin = await _stateService.GetProtectedPinAsync();
var decryptedPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), userKey);
failed = decryptedPin != inputtedValue;
if (!failed)
{
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key2);
await SetKeyAndContinueAsync(userKey);
}
}
catch
@@ -286,33 +310,27 @@ namespace Bit.iOS.Core.Controllers
}
else
{
var key2 = await _cryptoService.MakeKeyAsync(inputtedValue, email, kdfConfig);
var masterKey = await _cryptoService.MakeMasterKeyAsync(inputtedValue, email, kdfConfig);
var storedKeyHash = await _cryptoService.GetKeyHashAsync();
if (storedKeyHash == null)
var storedPasswordHash = await _cryptoService.GetPasswordHashAsync();
if (storedPasswordHash == null)
{
var oldKey = await _secureStorageService.GetAsync<string>("oldKey");
if (key2.KeyB64 == oldKey)
if (masterKey.KeyB64 == oldKey)
{
var localKeyHash = await _cryptoService.HashPasswordAsync(inputtedValue, key2, HashPurpose.LocalAuthorization);
var localPasswordHash = await _cryptoService.HashPasswordAsync(inputtedValue, masterKey, HashPurpose.LocalAuthorization);
await _secureStorageService.RemoveAsync("oldKey");
await _cryptoService.SetKeyHashAsync(localKeyHash);
await _cryptoService.SetPasswordHashAsync(localPasswordHash);
}
}
var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(inputtedValue, key2);
var passwordValid = await _cryptoService.CompareAndUpdatePasswordHashAsync(inputtedValue, masterKey);
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);
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
await _cryptoService.SetMasterKeyAsync(masterKey);
await SetKeyAndContinueAsync(userKey, true);
}
else
{
@@ -339,12 +357,12 @@ namespace Bit.iOS.Core.Controllers
public async Task PromptBiometricAsync()
{
if (!_biometricLock || !_biometricIntegrityValid)
if (!_biometricEnabled || !_biometricIntegrityValid)
{
return;
}
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
_pinLock ? AppResources.PIN : AppResources.MasterPassword,
_pinEnabled ? AppResources.PIN : AppResources.MasterPassword,
() => MasterPasswordCell.TextField.BecomeFirstResponder());
await _stateService.SetBiometricLockedAsync(!success);
if (success)
@@ -371,12 +389,12 @@ namespace Bit.iOS.Core.Controllers
PresentViewController(loginController, true, null);
}
private async Task SetKeyAndContinueAsync(SymmetricCryptoKey key, bool masterPassword = false)
private async Task SetKeyAndContinueAsync(UserKey userKey, bool masterPassword = false)
{
var hasKey = await _cryptoService.HasKeyAsync();
var hasKey = await _cryptoService.HasUserKeyAsync();
if (!hasKey)
{
await _cryptoService.SetKeyAsync(key);
await _cryptoService.SetUserKeyAsync(userKey);
}
DoContinue(masterPassword);
}
@@ -396,7 +414,7 @@ namespace Bit.iOS.Core.Controllers
private async Task EnableBiometricsIfNeeded()
{
// Re-enable biometrics if initial use
if (_biometricLock & !_biometricIntegrityValid)
if (_biometricEnabled & !_biometricIntegrityValid)
{
await _biometricService.SetupBiometricAsync(BiometricIntegritySourceKey);
}
@@ -405,7 +423,7 @@ namespace Bit.iOS.Core.Controllers
private void InvalidValue()
{
var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred,
string.Format(null, _pinLock ? AppResources.PIN : AppResources.InvalidMasterPassword),
string.Format(null, _pinEnabled ? AppResources.PIN : AppResources.InvalidMasterPassword),
AppResources.Ok, (a) =>
{
@@ -490,7 +508,7 @@ namespace Bit.iOS.Core.Controllers
return 0;
}
return (!controller._biometricUnlockOnly && controller._biometricLock) ||
return (!controller._biometricUnlockOnly && controller._biometricEnabled) ||
controller._passwordReprompt
? 2
: 1;