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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user