Skip to content

Commit

Permalink
[PM-3726] prevent legacy user login (#2769)
Browse files Browse the repository at this point in the history
* [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
  • Loading branch information
jlf0dev authored Sep 20, 2023
1 parent 8b9658d commit c4f6ae9
Show file tree
Hide file tree
Showing 11 changed files with 2,146 additions and 4,431 deletions.
364 changes: 221 additions & 143 deletions src/App/Pages/Accounts/LockPageViewModel.cs

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions src/App/Pages/Accounts/LoginPageViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,14 @@ await _platformUtilsService.ShowDialogAsync(

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();
Expand Down
5,871 changes: 1,682 additions & 4,189 deletions src/App/Resources/AppResources.Designer.cs

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions src/App/Resources/AppResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
1 change: 1 addition & 0 deletions src/Core/Abstractions/ICryptoService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public interface ICryptoService
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);
Expand Down
11 changes: 11 additions & 0 deletions src/Core/Exceptions/LegacyUserException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;
namespace Bit.Core.Exceptions
{
public class LegacyUserException : Exception
{
public LegacyUserException()
: base("Legacy users must migrate on web vault.")
{
}
}
}
1 change: 1 addition & 0 deletions src/Core/Models/Domain/AuthResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class AuthResult
[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; }
}
}
11 changes: 11 additions & 0 deletions src/Core/Services/AuthService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,17 @@ private async Task<AuthResult> LogInHelperAsync(string email, string hashedPassw
}

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;
Expand Down
43 changes: 43 additions & 0 deletions src/Core/Services/CryptoService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,16 @@ public Task<UserKey> GetUserKeyAsync(string userId = null)
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);
Expand Down Expand Up @@ -997,6 +1007,31 @@ private async Task<TKey> MakeKeyAsync<TKey>(string password, string salt, KdfCon
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; }
Expand All @@ -1019,6 +1054,10 @@ private async Task MigrateAutoAndBioKeysIfNeededAsync(string userId = null)

// 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)
{
Expand Down Expand Up @@ -1058,6 +1097,10 @@ public async Task<UserKey> DecryptAndMigrateOldPinKeyAsync(
kdfConfig,
oldPinKey
);
if (await IsLegacyUserAsync(masterKey))
{
throw new LegacyUserException();
}
var encUserKey = await _stateService.GetEncKeyEncryptedAsync();
var userKey = await DecryptUserKeyWithMasterKeyAsync(
masterKey,
Expand Down
19 changes: 15 additions & 4 deletions src/Core/Services/VaultTimeoutService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -67,14 +69,23 @@ public async Task<bool> IsLockedAsync(string userId = null)

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
Expand Down
Loading

0 comments on commit c4f6ae9

Please sign in to comment.