1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-13 14:53:18 +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

@@ -14,6 +14,7 @@ using Bit.Core.Enums;
using Bit.App.Pages;
using Bit.App.Models;
using Xamarin.Forms;
using Bit.Core.Services;
namespace Bit.iOS.Core.Controllers
{
@@ -29,10 +30,9 @@ namespace Bit.iOS.Core.Controllers
private IPlatformUtilsService _platformUtilsService;
private IBiometricService _biometricService;
private IKeyConnectorService _keyConnectorService;
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;
@@ -96,25 +96,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;
}
@@ -143,7 +146,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) =>
@@ -151,7 +154,7 @@ namespace Bit.iOS.Core.Controllers
CheckPasswordAsync().GetAwaiter().GetResult();
return true;
};
if (_pinLock)
if (_pinEnabled)
{
MasterPasswordCell.TextField.KeyboardType = UIKeyboardType.NumberPad;
}
@@ -165,7 +168,7 @@ namespace Bit.iOS.Core.Controllers
base.ViewDidLoad();
if (_biometricLock)
if (_biometricEnabled)
{
if (!_biometricIntegrityValid)
{
@@ -186,13 +189,13 @@ 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();
}
@@ -204,7 +207,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;
@@ -214,33 +217,53 @@ namespace Bit.iOS.Core.Controllers
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
var inputtedValue = MasterPasswordCell.TextField.Text;
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
@@ -260,33 +283,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
{
@@ -303,12 +320,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)
@@ -335,12 +352,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);
}
@@ -360,7 +377,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);
}
@@ -369,7 +386,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) =>
{
@@ -444,7 +461,7 @@ namespace Bit.iOS.Core.Controllers
public override nint NumberOfSections(UITableView tableView)
{
return (!_controller._biometricUnlockOnly && _controller._biometricLock) ||
return (!_controller._biometricUnlockOnly && _controller._biometricEnabled) ||
_controller._passwordReprompt
? 2
: 1;