[PM-3726] prevent legacy user login (#2769)
* [PM-3726] prevent legacy user login * [PM-3726] prevent unlock or auto key migration if legacy user * [PM-3726] add legacy checks to lock page and refactor * [PM-3726] rethrow exception from pin * formatting * [PM-3726] add changes to LockViewController, consolidate logout calls * formatting * [PM-3726] pr feedback * generate resx * formatting
This commit is contained in:
parent
8b9658d2c5
commit
c4f6ae9077
@ -7,6 +7,7 @@ using Bit.App.Utilities;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Models.Request;
|
||||
using Bit.Core.Services;
|
||||
@ -72,11 +73,12 @@ namespace Bit.App.Pages
|
||||
TogglePasswordCommand = new Command(TogglePassword);
|
||||
SubmitCommand = new Command(async () => await SubmitAsync());
|
||||
|
||||
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
|
||||
{
|
||||
AllowAddAccountRow = true,
|
||||
AllowActiveAccountSelection = true
|
||||
};
|
||||
AccountSwitchingOverlayViewModel =
|
||||
new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
|
||||
{
|
||||
AllowAddAccountRow = true,
|
||||
AllowActiveAccountSelection = true
|
||||
};
|
||||
}
|
||||
|
||||
public string MasterPassword
|
||||
@ -155,8 +157,12 @@ namespace Bit.App.Pages
|
||||
|
||||
public Command SubmitCommand { get; }
|
||||
public Command TogglePasswordCommand { get; }
|
||||
|
||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
||||
public string PasswordVisibilityAccessibilityText => ShowPassword
|
||||
? AppResources.PasswordIsVisibleTapToHide
|
||||
: AppResources.PasswordIsNotVisibleTapToShow;
|
||||
|
||||
public Action UnlockedAction { get; set; }
|
||||
public event Action<int?> FocusSecretEntry
|
||||
{
|
||||
@ -178,8 +184,9 @@ namespace Bit.App.Pages
|
||||
var ephemeralPinSet = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync()
|
||||
?? await _stateService.GetPinProtectedKeyAsync();
|
||||
PinEnabled = (_pinStatus == PinLockType.Transient && ephemeralPinSet != null) ||
|
||||
_pinStatus == PinLockType.Persistent;
|
||||
BiometricEnabled = await _vaultTimeoutService.IsBiometricLockSetAsync() && await _biometricService.CanUseBiometricsUnlockAsync();
|
||||
_pinStatus == PinLockType.Persistent;
|
||||
|
||||
BiometricEnabled = await IsBiometricsEnabledAsync();
|
||||
|
||||
// Users without MP and without biometric or pin has no MP to unlock with
|
||||
_hasMasterPassword = await _userVerificationService.HasMasterPasswordAsync();
|
||||
@ -214,7 +221,9 @@ namespace Bit.App.Pages
|
||||
else
|
||||
{
|
||||
PageTitle = _hasMasterPassword ? AppResources.VerifyMasterPassword : AppResources.UnlockVault;
|
||||
LockedVerifyText = _hasMasterPassword ? AppResources.VaultLockedMasterPassword : AppResources.VaultLockedIdentity;
|
||||
LockedVerifyText = _hasMasterPassword
|
||||
? AppResources.VaultLockedMasterPassword
|
||||
: AppResources.VaultLockedIdentity;
|
||||
}
|
||||
|
||||
if (BiometricEnabled)
|
||||
@ -233,11 +242,32 @@ namespace Bit.App.Pages
|
||||
BiometricButtonText = supportsFace ? AppResources.UseFaceIDToUnlock :
|
||||
AppResources.UseFingerprintToUnlock;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SubmitAsync()
|
||||
{
|
||||
ShowPassword = false;
|
||||
try
|
||||
{
|
||||
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
|
||||
if (PinEnabled)
|
||||
{
|
||||
await UnlockWithPinAsync(kdfConfig);
|
||||
}
|
||||
else
|
||||
{
|
||||
await UnlockWithMasterPasswordAsync(kdfConfig);
|
||||
}
|
||||
|
||||
}
|
||||
catch (LegacyUserException)
|
||||
{
|
||||
await HandleLegacyUserAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UnlockWithPinAsync(KdfConfig kdfConfig)
|
||||
{
|
||||
if (PinEnabled && string.IsNullOrWhiteSpace(Pin))
|
||||
{
|
||||
@ -246,6 +276,84 @@ namespace Bit.App.Pages
|
||||
AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
var failed = true;
|
||||
try
|
||||
{
|
||||
EncString userKeyPin;
|
||||
EncString oldPinProtected;
|
||||
switch (_pinStatus)
|
||||
{
|
||||
case PinLockType.Persistent:
|
||||
{
|
||||
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyAsync();
|
||||
var oldEncryptedKey = await _stateService.GetPinProtectedAsync();
|
||||
oldPinProtected = oldEncryptedKey != null ? new EncString(oldEncryptedKey) : null;
|
||||
break;
|
||||
}
|
||||
case PinLockType.Transient:
|
||||
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync();
|
||||
oldPinProtected = await _stateService.GetPinProtectedKeyAsync();
|
||||
break;
|
||||
case PinLockType.Disabled:
|
||||
default:
|
||||
throw new Exception("Pin is disabled");
|
||||
}
|
||||
|
||||
UserKey userKey;
|
||||
if (oldPinProtected != null)
|
||||
{
|
||||
userKey = await _cryptoService.DecryptAndMigrateOldPinKeyAsync(
|
||||
_pinStatus == PinLockType.Transient,
|
||||
Pin,
|
||||
_email,
|
||||
kdfConfig,
|
||||
oldPinProtected
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
userKey = await _cryptoService.DecryptUserKeyWithPinAsync(
|
||||
Pin,
|
||||
_email,
|
||||
kdfConfig,
|
||||
userKeyPin
|
||||
);
|
||||
}
|
||||
|
||||
var protectedPin = await _stateService.GetProtectedPinAsync();
|
||||
var decryptedPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), userKey);
|
||||
failed = decryptedPin != Pin;
|
||||
if (!failed)
|
||||
{
|
||||
Pin = string.Empty;
|
||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||
await SetUserKeyAndContinueAsync(userKey);
|
||||
}
|
||||
}
|
||||
catch (LegacyUserException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch
|
||||
{
|
||||
failed = true;
|
||||
}
|
||||
if (failed)
|
||||
{
|
||||
var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
|
||||
if (invalidUnlockAttempts >= 5)
|
||||
{
|
||||
_messagingService.Send("logout");
|
||||
return;
|
||||
}
|
||||
await _platformUtilsService.ShowDialogAsync(AppResources.InvalidPIN,
|
||||
AppResources.AnErrorHasOccurred);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UnlockWithMasterPasswordAsync(KdfConfig kdfConfig)
|
||||
{
|
||||
if (!PinEnabled && string.IsNullOrWhiteSpace(MasterPassword))
|
||||
{
|
||||
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
|
||||
@ -254,142 +362,78 @@ namespace Bit.App.Pages
|
||||
return;
|
||||
}
|
||||
|
||||
ShowPassword = false;
|
||||
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
|
||||
|
||||
if (PinEnabled)
|
||||
var masterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, _email, kdfConfig);
|
||||
if (await _cryptoService.IsLegacyUserAsync(masterKey))
|
||||
{
|
||||
var failed = true;
|
||||
throw new LegacyUserException();
|
||||
}
|
||||
|
||||
var storedKeyHash = await _cryptoService.GetMasterKeyHashAsync();
|
||||
var passwordValid = false;
|
||||
MasterPasswordPolicyOptions enforcedMasterPasswordOptions = null;
|
||||
|
||||
if (storedKeyHash != null)
|
||||
{
|
||||
// Offline unlock possible
|
||||
passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(MasterPassword, masterKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Online unlock required
|
||||
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
|
||||
var keyHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, masterKey,
|
||||
HashPurpose.ServerAuthorization);
|
||||
var request = new PasswordVerificationRequest();
|
||||
request.MasterPasswordHash = keyHash;
|
||||
|
||||
try
|
||||
{
|
||||
EncString userKeyPin = null;
|
||||
EncString oldPinProtected = null;
|
||||
if (_pinStatus == PinLockType.Persistent)
|
||||
{
|
||||
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyAsync();
|
||||
var oldEncryptedKey = await _stateService.GetPinProtectedAsync();
|
||||
oldPinProtected = oldEncryptedKey != null ? new EncString(oldEncryptedKey) : null;
|
||||
}
|
||||
else if (_pinStatus == PinLockType.Transient)
|
||||
{
|
||||
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync();
|
||||
oldPinProtected = await _stateService.GetPinProtectedKeyAsync();
|
||||
}
|
||||
|
||||
UserKey userKey;
|
||||
if (oldPinProtected != null)
|
||||
{
|
||||
userKey = await _cryptoService.DecryptAndMigrateOldPinKeyAsync(
|
||||
_pinStatus == PinLockType.Transient,
|
||||
Pin,
|
||||
_email,
|
||||
kdfConfig,
|
||||
oldPinProtected
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
userKey = await _cryptoService.DecryptUserKeyWithPinAsync(
|
||||
Pin,
|
||||
_email,
|
||||
kdfConfig,
|
||||
userKeyPin
|
||||
);
|
||||
}
|
||||
|
||||
var protectedPin = await _stateService.GetProtectedPinAsync();
|
||||
var decryptedPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), userKey);
|
||||
failed = decryptedPin != Pin;
|
||||
if (!failed)
|
||||
{
|
||||
Pin = string.Empty;
|
||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||
await SetUserKeyAndContinueAsync(userKey);
|
||||
}
|
||||
var response = await _apiService.PostAccountVerifyPasswordAsync(request);
|
||||
enforcedMasterPasswordOptions = response.MasterPasswordPolicy;
|
||||
passwordValid = true;
|
||||
var localKeyHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, masterKey,
|
||||
HashPurpose.LocalAuthorization);
|
||||
await _cryptoService.SetMasterKeyHashAsync(localKeyHash);
|
||||
}
|
||||
catch
|
||||
catch (Exception e)
|
||||
{
|
||||
failed = true;
|
||||
System.Diagnostics.Debug.WriteLine(">>> {0}: {1}", e.GetType(), e.StackTrace);
|
||||
}
|
||||
if (failed)
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
}
|
||||
|
||||
if (passwordValid)
|
||||
{
|
||||
if (await RequirePasswordChangeAsync(enforcedMasterPasswordOptions))
|
||||
{
|
||||
var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
|
||||
if (invalidUnlockAttempts >= 5)
|
||||
{
|
||||
_messagingService.Send("logout");
|
||||
return;
|
||||
}
|
||||
await _platformUtilsService.ShowDialogAsync(AppResources.InvalidPIN,
|
||||
AppResources.AnErrorHasOccurred);
|
||||
// Save the ForcePasswordResetReason to force a password reset after unlock
|
||||
await _stateService.SetForcePasswordResetReasonAsync(
|
||||
ForcePasswordResetReason.WeakMasterPasswordOnLogin);
|
||||
}
|
||||
|
||||
MasterPassword = string.Empty;
|
||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||
|
||||
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
|
||||
await _cryptoService.SetMasterKeyAsync(masterKey);
|
||||
await SetUserKeyAndContinueAsync(userKey);
|
||||
|
||||
// Re-enable biometrics
|
||||
if (BiometricEnabled & !BiometricIntegrityValid)
|
||||
{
|
||||
await _biometricService.SetupBiometricAsync();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var masterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, _email, kdfConfig);
|
||||
var storedKeyHash = await _cryptoService.GetMasterKeyHashAsync();
|
||||
var passwordValid = false;
|
||||
MasterPasswordPolicyOptions enforcedMasterPasswordOptions = null;
|
||||
|
||||
if (storedKeyHash != null)
|
||||
var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
|
||||
if (invalidUnlockAttempts >= 5)
|
||||
{
|
||||
// Offline unlock possible
|
||||
passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(MasterPassword, masterKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Online unlock required
|
||||
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
|
||||
var keyHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, masterKey, HashPurpose.ServerAuthorization);
|
||||
var request = new PasswordVerificationRequest();
|
||||
request.MasterPasswordHash = keyHash;
|
||||
|
||||
try
|
||||
{
|
||||
var response = await _apiService.PostAccountVerifyPasswordAsync(request);
|
||||
enforcedMasterPasswordOptions = response.MasterPasswordPolicy;
|
||||
passwordValid = true;
|
||||
var localKeyHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, masterKey, HashPurpose.LocalAuthorization);
|
||||
await _cryptoService.SetMasterKeyHashAsync(localKeyHash);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(">>> {0}: {1}", e.GetType(), e.StackTrace);
|
||||
}
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
}
|
||||
if (passwordValid)
|
||||
{
|
||||
if (await RequirePasswordChangeAsync(enforcedMasterPasswordOptions))
|
||||
{
|
||||
// Save the ForcePasswordResetReason to force a password reset after unlock
|
||||
await _stateService.SetForcePasswordResetReasonAsync(
|
||||
ForcePasswordResetReason.WeakMasterPasswordOnLogin);
|
||||
}
|
||||
|
||||
MasterPassword = string.Empty;
|
||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||
|
||||
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
|
||||
await _cryptoService.SetMasterKeyAsync(masterKey);
|
||||
await SetUserKeyAndContinueAsync(userKey);
|
||||
|
||||
// Re-enable biometrics
|
||||
if (BiometricEnabled & !BiometricIntegrityValid)
|
||||
{
|
||||
await _biometricService.SetupBiometricAsync();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
|
||||
if (invalidUnlockAttempts >= 5)
|
||||
{
|
||||
_messagingService.Send("logout");
|
||||
return;
|
||||
}
|
||||
await _platformUtilsService.ShowDialogAsync(AppResources.InvalidMasterPassword,
|
||||
AppResources.AnErrorHasOccurred);
|
||||
_messagingService.Send("logout");
|
||||
return;
|
||||
}
|
||||
await _platformUtilsService.ShowDialogAsync(AppResources.InvalidMasterPassword,
|
||||
AppResources.AnErrorHasOccurred);
|
||||
}
|
||||
}
|
||||
|
||||
@ -452,26 +496,36 @@ namespace Bit.App.Pages
|
||||
{
|
||||
ShowPassword = !ShowPassword;
|
||||
var secret = PinEnabled ? Pin : MasterPassword;
|
||||
_secretEntryFocusWeakEventManager.RaiseEvent(string.IsNullOrEmpty(secret) ? 0 : secret.Length, nameof(FocusSecretEntry));
|
||||
_secretEntryFocusWeakEventManager.RaiseEvent(string.IsNullOrEmpty(secret) ? 0 : secret.Length,
|
||||
nameof(FocusSecretEntry));
|
||||
}
|
||||
|
||||
public async Task PromptBiometricAsync()
|
||||
{
|
||||
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync();
|
||||
BiometricButtonVisible = BiometricIntegrityValid;
|
||||
if (!BiometricEnabled || !BiometricIntegrityValid)
|
||||
try
|
||||
{
|
||||
return;
|
||||
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync();
|
||||
BiometricButtonVisible = BiometricIntegrityValid;
|
||||
if (!BiometricEnabled || !BiometricIntegrityValid)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
|
||||
PinEnabled ? AppResources.PIN : AppResources.MasterPassword,
|
||||
() => _secretEntryFocusWeakEventManager.RaiseEvent((int?)null, nameof(FocusSecretEntry)),
|
||||
!PinEnabled && !HasMasterPassword);
|
||||
|
||||
await _stateService.SetBiometricLockedAsync(!success);
|
||||
if (success)
|
||||
{
|
||||
var userKey = await _cryptoService.GetBiometricUnlockKeyAsync();
|
||||
await SetUserKeyAndContinueAsync(userKey);
|
||||
}
|
||||
}
|
||||
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
|
||||
PinEnabled ? AppResources.PIN : AppResources.MasterPassword,
|
||||
() => _secretEntryFocusWeakEventManager.RaiseEvent((int?)null, nameof(FocusSecretEntry)),
|
||||
!PinEnabled && !HasMasterPassword);
|
||||
await _stateService.SetBiometricLockedAsync(!success);
|
||||
if (success)
|
||||
catch (LegacyUserException)
|
||||
{
|
||||
var userKey = await _cryptoService.GetBiometricUnlockKeyAsync();
|
||||
await SetUserKeyAndContinueAsync(userKey);
|
||||
await HandleLegacyUserAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@ -494,5 +548,29 @@ namespace Bit.App.Pages
|
||||
_messagingService.Send("unlocked");
|
||||
UnlockedAction?.Invoke();
|
||||
}
|
||||
|
||||
private async Task<bool> IsBiometricsEnabledAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _vaultTimeoutService.IsBiometricLockSetAsync() &&
|
||||
await _biometricService.CanUseBiometricsUnlockAsync();
|
||||
}
|
||||
catch (LegacyUserException)
|
||||
{
|
||||
await HandleLegacyUserAsync();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private async Task HandleLegacyUserAsync()
|
||||
{
|
||||
// Legacy users must migrate on web vault.
|
||||
await _platformUtilsService.ShowDialogAsync(AppResources.EncryptionKeyMigrationRequiredDescriptionLong,
|
||||
AppResources.AnErrorHasOccurred,
|
||||
AppResources.Ok);
|
||||
await _vaultTimeoutService.LogOutAsync();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -248,6 +248,14 @@ namespace Bit.App.Pages
|
||||
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
|
||||
if (response.RequiresEncryptionKeyMigration)
|
||||
{
|
||||
// Legacy users must migrate on web vault.
|
||||
await _platformUtilsService.ShowDialogAsync(AppResources.EncryptionKeyMigrationRequiredDescriptionLong, AppResources.AnErrorHasOccurred,
|
||||
AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.TwoFactor)
|
||||
{
|
||||
StartTwoFactorAction?.Invoke();
|
||||
|
11653
src/App/Resources/AppResources.Designer.cs
generated
11653
src/App/Resources/AppResources.Designer.cs
generated
File diff suppressed because it is too large
Load Diff
@ -954,6 +954,9 @@ Scanning will happen automatically.</value>
|
||||
<data name="UpdateKey" xml:space="preserve">
|
||||
<value>You cannot use this feature until you update your encryption key.</value>
|
||||
</data>
|
||||
<data name="EncryptionKeyMigrationRequiredDescriptionLong" xml:space="preserve">
|
||||
<value>Encryption key migration required. Please login through the web vault to update your encryption key.</value>
|
||||
</data>
|
||||
<data name="LearnMore" xml:space="preserve">
|
||||
<value>Learn more</value>
|
||||
</data>
|
||||
|
@ -13,6 +13,7 @@ namespace Bit.Core.Abstractions
|
||||
Task RefreshKeysAsync();
|
||||
Task SetUserKeyAsync(UserKey userKey, string userId = null);
|
||||
Task<UserKey> GetUserKeyAsync(string userId = null);
|
||||
Task<bool> IsLegacyUserAsync(MasterKey masterKey = null, string userId = null);
|
||||
Task<UserKey> GetUserKeyWithLegacySupportAsync(string userId = null);
|
||||
Task<bool> HasUserKeyAsync(string userId = null);
|
||||
Task<bool> HasEncryptedUserKeyAsync(string userId = null);
|
||||
|
11
src/Core/Exceptions/LegacyUserException.cs
Normal file
11
src/Core/Exceptions/LegacyUserException.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System;
|
||||
namespace Bit.Core.Exceptions
|
||||
{
|
||||
public class LegacyUserException : Exception
|
||||
{
|
||||
public LegacyUserException()
|
||||
: base("Legacy users must migrate on web vault.")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ namespace Bit.Core.Models.Domain
|
||||
[Obsolete("Use AccountDecryptionOptions to determine if the user does not have a MP")]
|
||||
public bool ResetMasterPassword { get; set; }
|
||||
public bool ForcePasswordReset { get; set; }
|
||||
public bool RequiresEncryptionKeyMigration { get; set; }
|
||||
public Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProviders { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -477,6 +477,17 @@ namespace Bit.Core.Services
|
||||
}
|
||||
|
||||
var tokenResponse = response.TokenResponse;
|
||||
if (localHashedPassword != null && tokenResponse.Key == null)
|
||||
{
|
||||
// Only check for legacy if there is no key on token
|
||||
if (await _cryptoService.IsLegacyUserAsync(masterKey))
|
||||
{
|
||||
// Legacy users must migrate on web vault;
|
||||
result.RequiresEncryptionKeyMigration = true;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
result.ResetMasterPassword = tokenResponse.ResetMasterPassword;
|
||||
result.ForcePasswordReset = tokenResponse.ForcePasswordReset;
|
||||
_masterPasswordPolicy = tokenResponse.MasterPasswordPolicy;
|
||||
|
@ -62,6 +62,16 @@ namespace Bit.Core.Services
|
||||
return _stateService.GetUserKeyAsync(userId);
|
||||
}
|
||||
|
||||
public async Task<bool> IsLegacyUserAsync(MasterKey masterKey = null, string userId = null)
|
||||
{
|
||||
masterKey ??= await GetMasterKeyAsync(userId);
|
||||
if (masterKey == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return await ValidateUserKeyAsync(new UserKey(masterKey.Key));
|
||||
}
|
||||
|
||||
public async Task<UserKey> GetUserKeyWithLegacySupportAsync(string userId = null)
|
||||
{
|
||||
var userKey = await GetUserKeyAsync(userId);
|
||||
@ -997,6 +1007,31 @@ namespace Bit.Core.Services
|
||||
return keyCreator(key);
|
||||
}
|
||||
|
||||
private async Task<bool> ValidateUserKeyAsync(UserKey key, string userId = null)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var encPrivateKey = await _stateService.GetPrivateKeyEncryptedAsync(userId);
|
||||
if (encPrivateKey == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var privateKey = await DecryptToBytesAsync(new EncString(encPrivateKey), key);
|
||||
await _cryptoFunctionService.RsaExtractPublicKeyAsync(privateKey);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private class EncryptedObject
|
||||
{
|
||||
public byte[] Iv { get; set; }
|
||||
@ -1019,6 +1054,10 @@ namespace Bit.Core.Services
|
||||
|
||||
// Decrypt
|
||||
var masterKey = new MasterKey(Convert.FromBase64String(oldKey));
|
||||
if (await IsLegacyUserAsync(masterKey, userId))
|
||||
{
|
||||
throw new LegacyUserException();
|
||||
}
|
||||
var encryptedUserKey = await _stateService.GetEncKeyEncryptedAsync(userId);
|
||||
if (encryptedUserKey == null)
|
||||
{
|
||||
@ -1058,6 +1097,10 @@ namespace Bit.Core.Services
|
||||
kdfConfig,
|
||||
oldPinKey
|
||||
);
|
||||
if (await IsLegacyUserAsync(masterKey))
|
||||
{
|
||||
throw new LegacyUserException();
|
||||
}
|
||||
var encUserKey = await _stateService.GetEncKeyEncryptedAsync();
|
||||
var userKey = await DecryptUserKeyWithMasterKeyAsync(
|
||||
masterKey,
|
||||
|
@ -2,6 +2,8 @@
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Domain;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
@ -67,14 +69,23 @@ namespace Bit.Core.Services
|
||||
|
||||
if (!await _cryptoService.HasUserKeyAsync(userId))
|
||||
{
|
||||
if (!await _cryptoService.HasAutoUnlockKeyAsync(userId))
|
||||
try
|
||||
{
|
||||
return true;
|
||||
if (!await _cryptoService.HasAutoUnlockKeyAsync(userId))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (userId != null && await _stateService.GetActiveUserIdAsync() != userId)
|
||||
{
|
||||
await _cryptoService.SetUserKeyAsync(await _cryptoService.GetAutoUnlockKeyAsync(userId),
|
||||
userId);
|
||||
}
|
||||
}
|
||||
if (userId != null && await _stateService.GetActiveUserIdAsync() != userId)
|
||||
catch (LegacyUserException)
|
||||
{
|
||||
await _cryptoService.SetUserKeyAsync(await _cryptoService.GetAutoUnlockKeyAsync(userId), userId);
|
||||
await LogOutAsync(false, userId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Check again to verify auto key was set
|
||||
|
@ -7,6 +7,7 @@ using Bit.App.Resources;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
@ -120,8 +121,7 @@ namespace Bit.iOS.Core.Controllers
|
||||
_pinEnabled = (_pinStatus == PinLockType.Transient && ephemeralPinSet != null) ||
|
||||
_pinStatus == PinLockType.Persistent;
|
||||
|
||||
_biometricEnabled = await _vaultTimeoutService.IsBiometricLockSetAsync()
|
||||
&& await _biometricService.CanUseBiometricsUnlockAsync();
|
||||
_biometricEnabled = await IsBiometricsEnabledAsync();
|
||||
_hasMasterPassword = await _userVerificationService.HasMasterPasswordAsync();
|
||||
_biometricUnlockOnly = !_hasMasterPassword && _biometricEnabled && !_pinEnabled;
|
||||
|
||||
@ -260,99 +260,26 @@ namespace Bit.iOS.Core.Controllers
|
||||
&&
|
||||
kdfConfig.Memory > Constants.MaximumArgon2IdMemoryBeforeExtensionCrashing
|
||||
&&
|
||||
!await _platformUtilsService.ShowDialogAsync(AppResources.UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve, AppResources.Warning, AppResources.Continue, AppResources.Cancel))
|
||||
!await _platformUtilsService.ShowDialogAsync(
|
||||
AppResources.UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve,
|
||||
AppResources.Warning, AppResources.Continue, AppResources.Cancel))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_pinEnabled)
|
||||
{
|
||||
var failed = true;
|
||||
try
|
||||
{
|
||||
EncString userKeyPin = null;
|
||||
EncString oldPinProtected = null;
|
||||
if (_pinStatus == PinLockType.Persistent)
|
||||
{
|
||||
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyAsync();
|
||||
var oldEncryptedKey = await _stateService.GetPinProtectedAsync();
|
||||
oldPinProtected = oldEncryptedKey != null ? new EncString(oldEncryptedKey) : null;
|
||||
}
|
||||
else if (_pinStatus == PinLockType.Transient)
|
||||
{
|
||||
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync();
|
||||
oldPinProtected = await _stateService.GetPinProtectedKeyAsync();
|
||||
}
|
||||
|
||||
UserKey userKey;
|
||||
if (oldPinProtected != null)
|
||||
{
|
||||
userKey = await _cryptoService.DecryptAndMigrateOldPinKeyAsync(
|
||||
_pinStatus == PinLockType.Transient,
|
||||
inputtedValue,
|
||||
email,
|
||||
kdfConfig,
|
||||
oldPinProtected
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
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(userKey);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
failed = true;
|
||||
}
|
||||
if (failed)
|
||||
{
|
||||
await HandleFailedCredentialsAsync();
|
||||
}
|
||||
await UnlockWithPinAsync(inputtedValue, email, kdfConfig);
|
||||
}
|
||||
else
|
||||
{
|
||||
var masterKey = await _cryptoService.MakeMasterKeyAsync(inputtedValue, email, kdfConfig);
|
||||
|
||||
var storedPasswordHash = await _cryptoService.GetMasterKeyHashAsync();
|
||||
if (storedPasswordHash == null)
|
||||
{
|
||||
var oldKey = await _secureStorageService.GetAsync<string>("oldKey");
|
||||
if (masterKey.KeyB64 == oldKey)
|
||||
{
|
||||
var localPasswordHash = await _cryptoService.HashMasterKeyAsync(inputtedValue, masterKey, HashPurpose.LocalAuthorization);
|
||||
await _secureStorageService.RemoveAsync("oldKey");
|
||||
await _cryptoService.SetMasterKeyHashAsync(localPasswordHash);
|
||||
}
|
||||
}
|
||||
var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(inputtedValue, masterKey);
|
||||
if (passwordValid)
|
||||
{
|
||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||
|
||||
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
|
||||
await _cryptoService.SetMasterKeyAsync(masterKey);
|
||||
await SetKeyAndContinueAsync(userKey, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
await HandleFailedCredentialsAsync();
|
||||
}
|
||||
await UnlockWithMasterPasswordAsync(inputtedValue, email, kdfConfig);
|
||||
}
|
||||
}
|
||||
catch (LegacyUserException)
|
||||
{
|
||||
await HandleLegacyUserAsync();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_checkingPassword = false;
|
||||
@ -370,22 +297,127 @@ namespace Bit.iOS.Core.Controllers
|
||||
InvalidValue();
|
||||
}
|
||||
|
||||
private async Task UnlockWithPinAsync(string inputPin, string email, KdfConfig kdfConfig)
|
||||
{
|
||||
var failed = true;
|
||||
try
|
||||
{
|
||||
EncString userKeyPin = null;
|
||||
EncString oldPinProtected = null;
|
||||
if (_pinStatus == PinLockType.Persistent)
|
||||
{
|
||||
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyAsync();
|
||||
var oldEncryptedKey = await _stateService.GetPinProtectedAsync();
|
||||
oldPinProtected = oldEncryptedKey != null ? new EncString(oldEncryptedKey) : null;
|
||||
}
|
||||
else if (_pinStatus == PinLockType.Transient)
|
||||
{
|
||||
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync();
|
||||
oldPinProtected = await _stateService.GetPinProtectedKeyAsync();
|
||||
}
|
||||
|
||||
UserKey userKey;
|
||||
if (oldPinProtected != null)
|
||||
{
|
||||
userKey = await _cryptoService.DecryptAndMigrateOldPinKeyAsync(
|
||||
_pinStatus == PinLockType.Transient,
|
||||
inputPin,
|
||||
email,
|
||||
kdfConfig,
|
||||
oldPinProtected
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
userKey = await _cryptoService.DecryptUserKeyWithPinAsync(
|
||||
inputPin,
|
||||
email,
|
||||
kdfConfig,
|
||||
userKeyPin
|
||||
);
|
||||
}
|
||||
|
||||
var protectedPin = await _stateService.GetProtectedPinAsync();
|
||||
var decryptedPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), userKey);
|
||||
failed = decryptedPin != inputPin;
|
||||
if (!failed)
|
||||
{
|
||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||
await SetKeyAndContinueAsync(userKey);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
failed = true;
|
||||
}
|
||||
|
||||
if (failed)
|
||||
{
|
||||
await HandleFailedCredentialsAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UnlockWithMasterPasswordAsync(string inputPassword, string email, KdfConfig kdfConfig)
|
||||
{
|
||||
var masterKey = await _cryptoService.MakeMasterKeyAsync(inputPassword, email, kdfConfig);
|
||||
if (await _cryptoService.IsLegacyUserAsync(masterKey))
|
||||
{
|
||||
throw new LegacyUserException();
|
||||
}
|
||||
|
||||
var storedPasswordHash = await _cryptoService.GetMasterKeyHashAsync();
|
||||
if (storedPasswordHash == null)
|
||||
{
|
||||
var oldKey = await _secureStorageService.GetAsync<string>("oldKey");
|
||||
if (masterKey.KeyB64 == oldKey)
|
||||
{
|
||||
var localPasswordHash =
|
||||
await _cryptoService.HashMasterKeyAsync(inputPassword, masterKey,
|
||||
HashPurpose.LocalAuthorization);
|
||||
await _secureStorageService.RemoveAsync("oldKey");
|
||||
await _cryptoService.SetMasterKeyHashAsync(localPasswordHash);
|
||||
}
|
||||
}
|
||||
|
||||
var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(inputPassword, masterKey);
|
||||
if (passwordValid)
|
||||
{
|
||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||
|
||||
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
|
||||
await _cryptoService.SetMasterKeyAsync(masterKey);
|
||||
await SetKeyAndContinueAsync(userKey, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
await HandleFailedCredentialsAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task PromptBiometricAsync()
|
||||
{
|
||||
if (!_biometricEnabled || !_biometricIntegrityValid)
|
||||
try
|
||||
{
|
||||
return;
|
||||
}
|
||||
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
|
||||
_pinEnabled ? AppResources.PIN : AppResources.MasterPassword,
|
||||
() => MasterPasswordCell.TextField.BecomeFirstResponder(),
|
||||
!_pinEnabled && !_hasMasterPassword);
|
||||
if (!_biometricEnabled || !_biometricIntegrityValid)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _stateService.SetBiometricLockedAsync(!success);
|
||||
if (success)
|
||||
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
|
||||
_pinEnabled ? AppResources.PIN : AppResources.MasterPassword,
|
||||
() => MasterPasswordCell.TextField.BecomeFirstResponder(),
|
||||
!_pinEnabled && !_hasMasterPassword);
|
||||
|
||||
await _stateService.SetBiometricLockedAsync(!success);
|
||||
if (success)
|
||||
{
|
||||
var userKey = await _cryptoService.GetBiometricUnlockKeyAsync();
|
||||
await SetKeyAndContinueAsync(userKey);
|
||||
}
|
||||
}
|
||||
catch (LegacyUserException)
|
||||
{
|
||||
var userKey = await _cryptoService.GetBiometricUnlockKeyAsync();
|
||||
await SetKeyAndContinueAsync(userKey);
|
||||
await HandleLegacyUserAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@ -437,6 +469,29 @@ namespace Bit.iOS.Core.Controllers
|
||||
await _biometricService.SetupBiometricAsync(BiometricIntegritySourceKey);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> IsBiometricsEnabledAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _vaultTimeoutService.IsBiometricLockSetAsync() &&
|
||||
await _biometricService.CanUseBiometricsUnlockAsync();
|
||||
}
|
||||
catch (LegacyUserException)
|
||||
{
|
||||
await HandleLegacyUserAsync();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private async Task HandleLegacyUserAsync()
|
||||
{
|
||||
// Legacy users must migrate on web vault.
|
||||
await _platformUtilsService.ShowDialogAsync(AppResources.EncryptionKeyMigrationRequiredDescriptionLong,
|
||||
AppResources.AnErrorHasOccurred,
|
||||
AppResources.Ok);
|
||||
await _vaultTimeoutService.LogOutAsync();
|
||||
}
|
||||
|
||||
private void InvalidValue()
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user