1
0
mirror of https://github.com/bitwarden/mobile.git synced 2025-03-02 03:31:07 +01:00

[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:
Jake Fink 2023-09-20 15:56:51 -04:00 committed by GitHub
parent 8b9658d2c5
commit c4f6ae9077
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 5040 additions and 7325 deletions

View File

@ -7,6 +7,7 @@ using Bit.App.Utilities;
using Bit.Core; using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
using Bit.Core.Models.Request; using Bit.Core.Models.Request;
using Bit.Core.Services; using Bit.Core.Services;
@ -72,11 +73,12 @@ namespace Bit.App.Pages
TogglePasswordCommand = new Command(TogglePassword); TogglePasswordCommand = new Command(TogglePassword);
SubmitCommand = new Command(async () => await SubmitAsync()); SubmitCommand = new Command(async () => await SubmitAsync());
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger) AccountSwitchingOverlayViewModel =
{ new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
AllowAddAccountRow = true, {
AllowActiveAccountSelection = true AllowAddAccountRow = true,
}; AllowActiveAccountSelection = true
};
} }
public string MasterPassword public string MasterPassword
@ -155,8 +157,12 @@ namespace Bit.App.Pages
public Command SubmitCommand { get; } public Command SubmitCommand { get; }
public Command TogglePasswordCommand { get; } public Command TogglePasswordCommand { get; }
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye; 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 Action UnlockedAction { get; set; }
public event Action<int?> FocusSecretEntry public event Action<int?> FocusSecretEntry
{ {
@ -178,8 +184,9 @@ namespace Bit.App.Pages
var ephemeralPinSet = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync() var ephemeralPinSet = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync()
?? await _stateService.GetPinProtectedKeyAsync(); ?? await _stateService.GetPinProtectedKeyAsync();
PinEnabled = (_pinStatus == PinLockType.Transient && ephemeralPinSet != null) || PinEnabled = (_pinStatus == PinLockType.Transient && ephemeralPinSet != null) ||
_pinStatus == PinLockType.Persistent; _pinStatus == PinLockType.Persistent;
BiometricEnabled = await _vaultTimeoutService.IsBiometricLockSetAsync() && await _biometricService.CanUseBiometricsUnlockAsync();
BiometricEnabled = await IsBiometricsEnabledAsync();
// Users without MP and without biometric or pin has no MP to unlock with // Users without MP and without biometric or pin has no MP to unlock with
_hasMasterPassword = await _userVerificationService.HasMasterPasswordAsync(); _hasMasterPassword = await _userVerificationService.HasMasterPasswordAsync();
@ -214,7 +221,9 @@ namespace Bit.App.Pages
else else
{ {
PageTitle = _hasMasterPassword ? AppResources.VerifyMasterPassword : AppResources.UnlockVault; PageTitle = _hasMasterPassword ? AppResources.VerifyMasterPassword : AppResources.UnlockVault;
LockedVerifyText = _hasMasterPassword ? AppResources.VaultLockedMasterPassword : AppResources.VaultLockedIdentity; LockedVerifyText = _hasMasterPassword
? AppResources.VaultLockedMasterPassword
: AppResources.VaultLockedIdentity;
} }
if (BiometricEnabled) if (BiometricEnabled)
@ -233,11 +242,32 @@ namespace Bit.App.Pages
BiometricButtonText = supportsFace ? AppResources.UseFaceIDToUnlock : BiometricButtonText = supportsFace ? AppResources.UseFaceIDToUnlock :
AppResources.UseFingerprintToUnlock; AppResources.UseFingerprintToUnlock;
} }
} }
} }
public async Task SubmitAsync() 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)) if (PinEnabled && string.IsNullOrWhiteSpace(Pin))
{ {
@ -246,6 +276,84 @@ namespace Bit.App.Pages
AppResources.Ok); AppResources.Ok);
return; 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)) if (!PinEnabled && string.IsNullOrWhiteSpace(MasterPassword))
{ {
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
@ -254,142 +362,78 @@ namespace Bit.App.Pages
return; return;
} }
ShowPassword = false; var masterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, _email, kdfConfig);
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile)); if (await _cryptoService.IsLegacyUserAsync(masterKey))
if (PinEnabled)
{ {
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 try
{ {
EncString userKeyPin = null; var response = await _apiService.PostAccountVerifyPasswordAsync(request);
EncString oldPinProtected = null; enforcedMasterPasswordOptions = response.MasterPasswordPolicy;
if (_pinStatus == PinLockType.Persistent) passwordValid = true;
{ var localKeyHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, masterKey,
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyAsync(); HashPurpose.LocalAuthorization);
var oldEncryptedKey = await _stateService.GetPinProtectedAsync(); await _cryptoService.SetMasterKeyHashAsync(localKeyHash);
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);
}
} }
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(); // Save the ForcePasswordResetReason to force a password reset after unlock
if (invalidUnlockAttempts >= 5) await _stateService.SetForcePasswordResetReasonAsync(
{ ForcePasswordResetReason.WeakMasterPasswordOnLogin);
_messagingService.Send("logout"); }
return;
} MasterPassword = string.Empty;
await _platformUtilsService.ShowDialogAsync(AppResources.InvalidPIN, await AppHelpers.ResetInvalidUnlockAttemptsAsync();
AppResources.AnErrorHasOccurred);
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
await _cryptoService.SetMasterKeyAsync(masterKey);
await SetUserKeyAndContinueAsync(userKey);
// Re-enable biometrics
if (BiometricEnabled & !BiometricIntegrityValid)
{
await _biometricService.SetupBiometricAsync();
} }
} }
else else
{ {
var masterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, _email, kdfConfig); var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
var storedKeyHash = await _cryptoService.GetMasterKeyHashAsync(); if (invalidUnlockAttempts >= 5)
var passwordValid = false;
MasterPasswordPolicyOptions enforcedMasterPasswordOptions = null;
if (storedKeyHash != null)
{ {
// Offline unlock possible _messagingService.Send("logout");
passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(MasterPassword, masterKey); return;
}
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);
} }
await _platformUtilsService.ShowDialogAsync(AppResources.InvalidMasterPassword,
AppResources.AnErrorHasOccurred);
} }
} }
@ -452,26 +496,36 @@ namespace Bit.App.Pages
{ {
ShowPassword = !ShowPassword; ShowPassword = !ShowPassword;
var secret = PinEnabled ? Pin : MasterPassword; 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() public async Task PromptBiometricAsync()
{ {
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync(); try
BiometricButtonVisible = BiometricIntegrityValid;
if (!BiometricEnabled || !BiometricIntegrityValid)
{ {
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, catch (LegacyUserException)
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 HandleLegacyUserAsync();
await SetUserKeyAndContinueAsync(userKey);
} }
} }
@ -494,5 +548,29 @@ namespace Bit.App.Pages
_messagingService.Send("unlocked"); _messagingService.Send("unlocked");
UnlockedAction?.Invoke(); 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();
}
} }
} }

View File

@ -248,6 +248,14 @@ namespace Bit.App.Pages
await _deviceActionService.HideLoadingAsync(); 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) if (response.TwoFactor)
{ {
StartTwoFactorAction?.Invoke(); StartTwoFactorAction?.Invoke();

File diff suppressed because it is too large Load Diff

View File

@ -954,6 +954,9 @@ Scanning will happen automatically.</value>
<data name="UpdateKey" xml:space="preserve"> <data name="UpdateKey" xml:space="preserve">
<value>You cannot use this feature until you update your encryption key.</value> <value>You cannot use this feature until you update your encryption key.</value>
</data> </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"> <data name="LearnMore" xml:space="preserve">
<value>Learn more</value> <value>Learn more</value>
</data> </data>

View File

@ -13,6 +13,7 @@ namespace Bit.Core.Abstractions
Task RefreshKeysAsync(); Task RefreshKeysAsync();
Task SetUserKeyAsync(UserKey userKey, string userId = null); Task SetUserKeyAsync(UserKey userKey, string userId = null);
Task<UserKey> GetUserKeyAsync(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<UserKey> GetUserKeyWithLegacySupportAsync(string userId = null);
Task<bool> HasUserKeyAsync(string userId = null); Task<bool> HasUserKeyAsync(string userId = null);
Task<bool> HasEncryptedUserKeyAsync(string userId = null); Task<bool> HasEncryptedUserKeyAsync(string userId = null);

View 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.")
{
}
}
}

View File

@ -13,6 +13,7 @@ namespace Bit.Core.Models.Domain
[Obsolete("Use AccountDecryptionOptions to determine if the user does not have a MP")] [Obsolete("Use AccountDecryptionOptions to determine if the user does not have a MP")]
public bool ResetMasterPassword { get; set; } public bool ResetMasterPassword { get; set; }
public bool ForcePasswordReset { get; set; } public bool ForcePasswordReset { get; set; }
public bool RequiresEncryptionKeyMigration { get; set; }
public Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProviders { get; set; } public Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProviders { get; set; }
} }
} }

View File

@ -477,6 +477,17 @@ namespace Bit.Core.Services
} }
var tokenResponse = response.TokenResponse; 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.ResetMasterPassword = tokenResponse.ResetMasterPassword;
result.ForcePasswordReset = tokenResponse.ForcePasswordReset; result.ForcePasswordReset = tokenResponse.ForcePasswordReset;
_masterPasswordPolicy = tokenResponse.MasterPasswordPolicy; _masterPasswordPolicy = tokenResponse.MasterPasswordPolicy;

View File

@ -62,6 +62,16 @@ namespace Bit.Core.Services
return _stateService.GetUserKeyAsync(userId); 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) public async Task<UserKey> GetUserKeyWithLegacySupportAsync(string userId = null)
{ {
var userKey = await GetUserKeyAsync(userId); var userKey = await GetUserKeyAsync(userId);
@ -997,6 +1007,31 @@ namespace Bit.Core.Services
return keyCreator(key); 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 private class EncryptedObject
{ {
public byte[] Iv { get; set; } public byte[] Iv { get; set; }
@ -1019,6 +1054,10 @@ namespace Bit.Core.Services
// Decrypt // Decrypt
var masterKey = new MasterKey(Convert.FromBase64String(oldKey)); var masterKey = new MasterKey(Convert.FromBase64String(oldKey));
if (await IsLegacyUserAsync(masterKey, userId))
{
throw new LegacyUserException();
}
var encryptedUserKey = await _stateService.GetEncKeyEncryptedAsync(userId); var encryptedUserKey = await _stateService.GetEncKeyEncryptedAsync(userId);
if (encryptedUserKey == null) if (encryptedUserKey == null)
{ {
@ -1058,6 +1097,10 @@ namespace Bit.Core.Services
kdfConfig, kdfConfig,
oldPinKey oldPinKey
); );
if (await IsLegacyUserAsync(masterKey))
{
throw new LegacyUserException();
}
var encUserKey = await _stateService.GetEncKeyEncryptedAsync(); var encUserKey = await _stateService.GetEncKeyEncryptedAsync();
var userKey = await DecryptUserKeyWithMasterKeyAsync( var userKey = await DecryptUserKeyWithMasterKeyAsync(
masterKey, masterKey,

View File

@ -2,6 +2,8 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Domain;
namespace Bit.Core.Services namespace Bit.Core.Services
{ {
@ -67,14 +69,23 @@ namespace Bit.Core.Services
if (!await _cryptoService.HasUserKeyAsync(userId)) 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 // Check again to verify auto key was set

View File

@ -7,6 +7,7 @@ using Bit.App.Resources;
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
@ -120,8 +121,7 @@ namespace Bit.iOS.Core.Controllers
_pinEnabled = (_pinStatus == PinLockType.Transient && ephemeralPinSet != null) || _pinEnabled = (_pinStatus == PinLockType.Transient && ephemeralPinSet != null) ||
_pinStatus == PinLockType.Persistent; _pinStatus == PinLockType.Persistent;
_biometricEnabled = await _vaultTimeoutService.IsBiometricLockSetAsync() _biometricEnabled = await IsBiometricsEnabledAsync();
&& await _biometricService.CanUseBiometricsUnlockAsync();
_hasMasterPassword = await _userVerificationService.HasMasterPasswordAsync(); _hasMasterPassword = await _userVerificationService.HasMasterPasswordAsync();
_biometricUnlockOnly = !_hasMasterPassword && _biometricEnabled && !_pinEnabled; _biometricUnlockOnly = !_hasMasterPassword && _biometricEnabled && !_pinEnabled;
@ -260,99 +260,26 @@ namespace Bit.iOS.Core.Controllers
&& &&
kdfConfig.Memory > Constants.MaximumArgon2IdMemoryBeforeExtensionCrashing 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; return;
} }
if (_pinEnabled) if (_pinEnabled)
{ {
var failed = true; await UnlockWithPinAsync(inputtedValue, email, kdfConfig);
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();
}
} }
else else
{ {
var masterKey = await _cryptoService.MakeMasterKeyAsync(inputtedValue, email, kdfConfig); await UnlockWithMasterPasswordAsync(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();
}
} }
} }
catch (LegacyUserException)
{
await HandleLegacyUserAsync();
}
finally finally
{ {
_checkingPassword = false; _checkingPassword = false;
@ -370,22 +297,127 @@ namespace Bit.iOS.Core.Controllers
InvalidValue(); 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() public async Task PromptBiometricAsync()
{ {
if (!_biometricEnabled || !_biometricIntegrityValid) try
{ {
return; if (!_biometricEnabled || !_biometricIntegrityValid)
} {
var success = await _platformUtilsService.AuthenticateBiometricAsync(null, return;
_pinEnabled ? AppResources.PIN : AppResources.MasterPassword, }
() => MasterPasswordCell.TextField.BecomeFirstResponder(),
!_pinEnabled && !_hasMasterPassword);
await _stateService.SetBiometricLockedAsync(!success); var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
if (success) _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 HandleLegacyUserAsync();
await SetKeyAndContinueAsync(userKey);
} }
} }
@ -437,6 +469,29 @@ namespace Bit.iOS.Core.Controllers
await _biometricService.SetupBiometricAsync(BiometricIntegritySourceKey); 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() private void InvalidValue()
{ {