1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-21 02:33:36 +00:00

[PM-3462] Handle force password reset on mobile with TDE (#2694)

* [PM-3462] Handle force password reset on mobile with TDE

* [PM-3462] update references to refactored crypto method
- fix kc bug, we were sending private key instead of user key to server
- rename kc service method to be correct

* [PM-3462] Update TwoFactorPage login logic

* [PM-3462] Added pending admin request check to TwoFactorPage

* [PM-3462] Added new exception types for null keys

---------

Co-authored-by: André Bispo <abispo@bitwarden.com>
This commit is contained in:
Jake Fink
2023-08-16 14:38:50 -04:00
committed by GitHub
parent 001e08e347
commit 79c2e4016b
14 changed files with 129 additions and 56 deletions

View File

@@ -211,33 +211,41 @@ namespace Bit.App.Pages
return; return;
} }
// Trusted device option is sent regardless if this is a trusted device or not
// If it is trusted, it will have the necessary keys
if (decryptOptions?.TrustedDeviceOption != null) if (decryptOptions?.TrustedDeviceOption != null)
{ {
var pendingRequest = await _stateService.GetPendingAdminAuthRequestAsync(); if (await _deviceTrustCryptoService.IsDeviceTrustedAsync())
// If user doesn't have a MP, but has reset password permission, they must set a MP
if (!decryptOptions.HasMasterPassword &&
decryptOptions.TrustedDeviceOption.HasManageResetPasswordPermission)
{
StartSetPasswordAction?.Invoke();
}
else if (response.ForcePasswordReset)
{
UpdateTempPasswordAction?.Invoke();
}
else if (await _deviceTrustCryptoService.IsDeviceTrustedAsync())
{ {
// If we have a device key but no keys on server, we need to remove the device key
if (decryptOptions.TrustedDeviceOption.EncryptedPrivateKey == null && decryptOptions.TrustedDeviceOption.EncryptedUserKey == null) if (decryptOptions.TrustedDeviceOption.EncryptedPrivateKey == null && decryptOptions.TrustedDeviceOption.EncryptedUserKey == null)
{ {
await _deviceTrustCryptoService.RemoveTrustedDeviceAsync(); await _deviceTrustCryptoService.RemoveTrustedDeviceAsync();
StartDeviceApprovalOptionsAction?.Invoke(); StartDeviceApprovalOptionsAction?.Invoke();
return;
} }
else // If user doesn't have a MP, but has reset password permission, they must set a MP
if (!decryptOptions.HasMasterPassword &&
decryptOptions.TrustedDeviceOption.HasManageResetPasswordPermission)
{ {
_syncService.FullSyncAsync(true).FireAndForget(); StartSetPasswordAction?.Invoke();
SsoAuthSuccessAction?.Invoke(); return;
} }
// Update temp password only if the device is trusted and therefore has a decrypted User Key set
if (response.ForcePasswordReset)
{
UpdateTempPasswordAction?.Invoke();
return;
}
// Device is trusted and has keys, so we can decrypt
_syncService.FullSyncAsync(true).FireAndForget();
SsoAuthSuccessAction?.Invoke();
return;
} }
else if (pendingRequest != null)
// Check for pending Admin Auth requests before navigating to device approval options
var pendingRequest = await _stateService.GetPendingAdminAuthRequestAsync();
if (pendingRequest != null)
{ {
var authRequest = await _authService.GetPasswordlessLoginRequestByIdAsync(pendingRequest.Id); var authRequest = await _authService.GetPasswordlessLoginRequestByIdAsync(pendingRequest.Id);
if (authRequest?.RequestApproved == true) if (authRequest?.RequestApproved == true)
@@ -268,18 +276,14 @@ namespace Bit.App.Pages
// In the standard, non TDE case, a user must set password if they don't // In the standard, non TDE case, a user must set password if they don't
// have one and they aren't using key connector. // have one and they aren't using key connector.
// Note: TDE & Key connector are mutually exclusive org config options. // Note: TDE & Key connector are mutually exclusive org config options.
if (response.ResetMasterPassword || (decryptOptions?.RequireSetPassword ?? false)) if (response.ResetMasterPassword || (decryptOptions?.RequireSetPassword == true))
{ {
// TODO: We need to look into how to handle this when Org removes TDE
// Will we have the User Key by now to set a new password?
StartSetPasswordAction?.Invoke(); StartSetPasswordAction?.Invoke();
return; return;
} }
if (response.ForcePasswordReset)
{
UpdateTempPasswordAction?.Invoke();
return;
}
_syncService.FullSyncAsync(true).FireAndForget(); _syncService.FullSyncAsync(true).FireAndForget();
SsoAuthSuccessAction?.Invoke(); SsoAuthSuccessAction?.Invoke();
} }

View File

@@ -178,7 +178,10 @@ namespace Bit.App.Pages
Email = Email.Trim().ToLower(); Email = Email.Trim().ToLower();
var kdfConfig = new KdfConfig(KdfType.PBKDF2_SHA256, Constants.Pbkdf2Iterations, null, null); var kdfConfig = new KdfConfig(KdfType.PBKDF2_SHA256, Constants.Pbkdf2Iterations, null, null);
var newMasterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, Email, kdfConfig); var newMasterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, Email, kdfConfig);
var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(newMasterKey); var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(
newMasterKey,
await _cryptoService.MakeUserKeyAsync()
);
var hashedPassword = await _cryptoService.HashMasterKeyAsync(MasterPassword, newMasterKey); var hashedPassword = await _cryptoService.HashMasterKeyAsync(MasterPassword, newMasterKey);
var (newPublicKey, newProtectedPrivateKey) = await _cryptoService.MakeKeyPairAsync(newUserKey); var (newPublicKey, newProtectedPrivateKey) = await _cryptoService.MakeKeyPairAsync(newUserKey);
var request = new RegisterRequest var request = new RegisterRequest

View File

@@ -169,7 +169,8 @@ namespace Bit.App.Pages
var masterPasswordHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, newMasterKey, HashPurpose.ServerAuthorization); var masterPasswordHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, newMasterKey, HashPurpose.ServerAuthorization);
var localMasterPasswordHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, newMasterKey, HashPurpose.LocalAuthorization); var localMasterPasswordHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, newMasterKey, HashPurpose.LocalAuthorization);
var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(newMasterKey); var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(newMasterKey,
await _cryptoService.GetUserKeyAsync() ?? await _cryptoService.MakeUserKeyAsync());
var (newPublicKey, newProtectedPrivateKey) = await _cryptoService.MakeKeyPairAsync(newUserKey); var (newPublicKey, newProtectedPrivateKey) = await _cryptoService.MakeKeyPairAsync(newUserKey);
var request = new SetPasswordRequest var request = new SetPasswordRequest

View File

@@ -326,27 +326,56 @@ namespace Bit.App.Pages
if (decryptOptions?.TrustedDeviceOption != null) if (decryptOptions?.TrustedDeviceOption != null)
{ {
// If user doesn't have a MP, but has reset password permission, they must set a MP if (await _deviceTrustCryptoService.IsDeviceTrustedAsync())
if (!decryptOptions.HasMasterPassword &&
decryptOptions.TrustedDeviceOption.HasManageResetPasswordPermission)
{
StartSetPasswordAction?.Invoke();
}
else if (result.ForcePasswordReset)
{
UpdateTempPasswordAction?.Invoke();
}
else if (await _deviceTrustCryptoService.IsDeviceTrustedAsync())
{ {
// If we have a device key but no keys on server, we need to remove the device key
if (decryptOptions.TrustedDeviceOption.EncryptedPrivateKey == null && decryptOptions.TrustedDeviceOption.EncryptedUserKey == null) if (decryptOptions.TrustedDeviceOption.EncryptedPrivateKey == null && decryptOptions.TrustedDeviceOption.EncryptedUserKey == null)
{ {
await _deviceTrustCryptoService.RemoveTrustedDeviceAsync(); await _deviceTrustCryptoService.RemoveTrustedDeviceAsync();
StartDeviceApprovalOptionsAction?.Invoke(); StartDeviceApprovalOptionsAction?.Invoke();
return;
}
// If user doesn't have a MP, but has reset password permission, they must set a MP
if (!decryptOptions.HasMasterPassword &&
decryptOptions.TrustedDeviceOption.HasManageResetPasswordPermission)
{
StartSetPasswordAction?.Invoke();
return;
}
// Update temp password only if the device is trusted and therefore has a decrypted User Key set
if (result.ForcePasswordReset)
{
UpdateTempPasswordAction?.Invoke();
return;
}
// Device is trusted and has keys, so we can decrypt
_syncService.FullSyncAsync(true).FireAndForget();
await TwoFactorAuthSuccessAsync();
return;
}
// Check for pending Admin Auth requests before navigating to device approval options
var pendingRequest = await _stateService.GetPendingAdminAuthRequestAsync();
if (pendingRequest != null)
{
var authRequest = await _authService.GetPasswordlessLoginRequestByIdAsync(pendingRequest.Id);
if (authRequest?.RequestApproved == true)
{
var authResult = await _authService.LogInPasswordlessAsync(true, await _stateService.GetActiveUserEmailAsync(), authRequest.RequestAccessCode, pendingRequest.Id, pendingRequest.PrivateKey, authRequest.Key, authRequest.MasterPasswordHash);
if (authResult == null && await _stateService.IsAuthenticatedAsync())
{
await Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync(
() => _platformUtilsService.ShowToast("info", null, AppResources.LoginApproved));
await _stateService.SetPendingAdminAuthRequestAsync(null);
_syncService.FullSyncAsync(true).FireAndForget();
await TwoFactorAuthSuccessAsync();
}
} }
else else
{ {
_syncService.FullSyncAsync(true).FireAndForget(); await _stateService.SetPendingAdminAuthRequestAsync(null);
await TwoFactorAuthSuccessAsync(); StartDeviceApprovalOptionsAction?.Invoke();
} }
} }
else else
@@ -361,16 +390,12 @@ namespace Bit.App.Pages
// Note: TDE & Key connector are mutually exclusive org config options. // Note: TDE & Key connector are mutually exclusive org config options.
if (result.ResetMasterPassword || (decryptOptions?.RequireSetPassword ?? false)) if (result.ResetMasterPassword || (decryptOptions?.RequireSetPassword ?? false))
{ {
// TODO: We need to look into how to handle this when Org removes TDE
// Will we have the User Key by now to set a new password?
StartSetPasswordAction?.Invoke(); StartSetPasswordAction?.Invoke();
return; return;
} }
if (result.ForcePasswordReset)
{
UpdateTempPasswordAction?.Invoke();
return;
}
_syncService.FullSyncAsync(true).FireAndForget(); _syncService.FullSyncAsync(true).FireAndForget();
await TwoFactorAuthSuccessAsync(); await TwoFactorAuthSuccessAsync();
} }

View File

@@ -72,7 +72,7 @@ namespace Bit.Core.Abstractions
Task PutOrganizationUserResetPasswordEnrollmentAsync(string orgId, string userId, Task PutOrganizationUserResetPasswordEnrollmentAsync(string orgId, string userId,
OrganizationUserResetPasswordEnrollmentRequest request); OrganizationUserResetPasswordEnrollmentRequest request);
Task<KeyConnectorUserKeyResponse> GetMasterKeyFromKeyConnectorAsync(string keyConnectorUrl); Task<KeyConnectorUserKeyResponse> GetMasterKeyFromKeyConnectorAsync(string keyConnectorUrl);
Task PostUserKeyToKeyConnector(string keyConnectorUrl, KeyConnectorUserKeyRequest request); Task PostMasterKeyToKeyConnector(string keyConnectorUrl, KeyConnectorUserKeyRequest request);
Task PostSetKeyConnectorKey(SetKeyConnectorKeyRequest request); Task PostSetKeyConnectorKey(SetKeyConnectorKeyRequest request);
Task PostConvertToKeyConnector(); Task PostConvertToKeyConnector();
Task PostLeaveOrganization(string id); Task PostLeaveOrganization(string id);

View File

@@ -26,7 +26,7 @@ namespace Bit.Core.Abstractions
Task<MasterKey> GetMasterKeyAsync(string userId = null); Task<MasterKey> GetMasterKeyAsync(string userId = null);
Task<MasterKey> MakeMasterKeyAsync(string password, string email, KdfConfig kdfConfig); Task<MasterKey> MakeMasterKeyAsync(string password, string email, KdfConfig kdfConfig);
Task ClearMasterKeyAsync(string userId = null); Task ClearMasterKeyAsync(string userId = null);
Task<Tuple<UserKey, EncString>> EncryptUserKeyWithMasterKeyAsync(MasterKey masterKey); Task<Tuple<UserKey, EncString>> EncryptUserKeyWithMasterKeyAsync(MasterKey masterKey, UserKey userKey = null);
Task<UserKey> DecryptUserKeyWithMasterKeyAsync(MasterKey masterKey, EncString encUserKey = null, string userId = null); Task<UserKey> DecryptUserKeyWithMasterKeyAsync(MasterKey masterKey, EncString encUserKey = null, string userId = null);
Task<Tuple<SymmetricCryptoKey, EncString>> MakeDataEncKeyAsync(SymmetricCryptoKey key); Task<Tuple<SymmetricCryptoKey, EncString>> MakeDataEncKeyAsync(SymmetricCryptoKey key);
Task<string> HashMasterKeyAsync(string password, MasterKey key, HashPurpose hashPurpose = HashPurpose.ServerAuthorization); Task<string> HashMasterKeyAsync(string password, MasterKey key, HashPurpose hashPurpose = HashPurpose.ServerAuthorization);

View File

@@ -0,0 +1,12 @@
using System;
namespace Bit.Core.Exceptions
{
public class MasterKeyNullException : Exception
{
public MasterKeyNullException()
: base("MasterKey is null.")
{
}
}
}

View File

@@ -0,0 +1,12 @@
using System;
namespace Bit.Core.Exceptions
{
public class UserKeyNullException : Exception
{
public UserKeyNullException()
: base("UserKey is null.")
{
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using Bit.Core.Enums; using Bit.Core.Enums;
namespace Bit.Core.Models.Domain namespace Bit.Core.Models.Domain
@@ -8,6 +9,8 @@ namespace Bit.Core.Models.Domain
public bool TwoFactor { get; set; } public bool TwoFactor { get; set; }
public bool CaptchaNeeded => !string.IsNullOrWhiteSpace(CaptchaSiteKey); public bool CaptchaNeeded => !string.IsNullOrWhiteSpace(CaptchaSiteKey);
public string CaptchaSiteKey { get; set; } public string CaptchaSiteKey { get; set; }
// TODO: PM-3287 - Remove after 3 releases of backwards compatibility - Target release 2023.12
[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 Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProviders { get; set; } public Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProviders { get; set; }

View File

@@ -541,7 +541,7 @@ namespace Bit.Core.Services
} }
} }
public async Task PostUserKeyToKeyConnector(string keyConnectorUrl, KeyConnectorUserKeyRequest request) public async Task PostMasterKeyToKeyConnector(string keyConnectorUrl, KeyConnectorUserKeyRequest request)
{ {
using (var requestMessage = new HttpRequestMessage()) using (var requestMessage = new HttpRequestMessage())
{ {

View File

@@ -240,7 +240,13 @@ namespace Bit.Core.Services
public async Task<AuthResult> LogInSsoAsync(string code, string codeVerifier, string redirectUrl, string orgId) public async Task<AuthResult> LogInSsoAsync(string code, string codeVerifier, string redirectUrl, string orgId)
{ {
SelectedTwoFactorProviderType = null; SelectedTwoFactorProviderType = null;
return await LogInHelperAsync(null, null, null, code, codeVerifier, redirectUrl, null, orgId: orgId); var result = await LogInHelperAsync(null, null, null, code, codeVerifier, redirectUrl, null, orgId: orgId);
if (result.ForcePasswordReset)
{
await _stateService.SetForcePasswordResetReasonAsync(ForcePasswordResetReason.AdminForcePasswordReset);
}
return result;
} }
public async Task<AuthResult> LogInTwoFactorAsync(TwoFactorProviderType twoFactorProvider, string twoFactorToken, public async Task<AuthResult> LogInTwoFactorAsync(TwoFactorProviderType twoFactorProvider, string twoFactorToken,

View File

@@ -6,6 +6,7 @@ using System.Text;
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; using Bit.Core.Models.Domain;
using Bit.Core.Models.Response; using Bit.Core.Models.Response;
using Bit.Core.Utilities; using Bit.Core.Utilities;
@@ -152,9 +153,13 @@ namespace Bit.Core.Services
return _stateService.SetMasterKeyAsync(null, userId); return _stateService.SetMasterKeyAsync(null, userId);
} }
public async Task<Tuple<UserKey, EncString>> EncryptUserKeyWithMasterKeyAsync(MasterKey masterKey) public async Task<Tuple<UserKey, EncString>> EncryptUserKeyWithMasterKeyAsync(MasterKey masterKey, UserKey userKey = null)
{ {
var userKey = await GetUserKeyAsync() ?? await MakeUserKeyAsync(); userKey ??= await GetUserKeyAsync();
if (userKey == null)
{
throw new UserKeyNullException();
}
return await BuildProtectedSymmetricKeyAsync(masterKey, userKey.Key, keyBytes => new UserKey(keyBytes)); return await BuildProtectedSymmetricKeyAsync(masterKey, userKey.Key, keyBytes => new UserKey(keyBytes));
} }
@@ -163,7 +168,7 @@ namespace Bit.Core.Services
masterKey ??= await GetMasterKeyAsync(userId); masterKey ??= await GetMasterKeyAsync(userId);
if (masterKey == null) if (masterKey == null)
{ {
throw new Exception("No master key found."); throw new MasterKeyNullException();
} }
if (encUserKey == null) if (encUserKey == null)

View File

@@ -68,7 +68,7 @@ namespace Bit.Core.Services
try try
{ {
var keyConnectorRequest = new KeyConnectorUserKeyRequest(masterKey.EncKeyB64); var keyConnectorRequest = new KeyConnectorUserKeyRequest(masterKey.EncKeyB64);
await _apiService.PostUserKeyToKeyConnector(organization.KeyConnectorUrl, keyConnectorRequest); await _apiService.PostMasterKeyToKeyConnector(organization.KeyConnectorUrl, keyConnectorRequest);
} }
catch (Exception e) catch (Exception e)
{ {
@@ -95,13 +95,15 @@ namespace Bit.Core.Services
var keyConnectorRequest = new KeyConnectorUserKeyRequest(newMasterKey.EncKeyB64); var keyConnectorRequest = new KeyConnectorUserKeyRequest(newMasterKey.EncKeyB64);
await _cryptoService.SetMasterKeyAsync(newMasterKey); await _cryptoService.SetMasterKeyAsync(newMasterKey);
var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(newMasterKey); var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(
newMasterKey,
await _cryptoService.MakeUserKeyAsync());
await _cryptoService.SetUserKeyAsync(newUserKey); await _cryptoService.SetUserKeyAsync(newUserKey);
try try
{ {
await _apiService.PostUserKeyToKeyConnector(tokenResponse.KeyConnectorUrl, keyConnectorRequest); await _apiService.PostMasterKeyToKeyConnector(tokenResponse.KeyConnectorUrl, keyConnectorRequest);
} }
catch (Exception e) catch (Exception e)
{ {
@@ -115,7 +117,7 @@ namespace Bit.Core.Services
EncryptedPrivateKey = newProtectedPrivateKey.EncryptedString EncryptedPrivateKey = newProtectedPrivateKey.EncryptedString
}; };
var setPasswordRequest = new SetKeyConnectorKeyRequest( var setPasswordRequest = new SetKeyConnectorKeyRequest(
newProtectedPrivateKey.EncryptedString, keys, tokenResponse.KdfConfig, orgId newProtectedUserKey.EncryptedString, keys, tokenResponse.KdfConfig, orgId
); );
await _apiService.PostSetKeyConnectorKey(setPasswordRequest); await _apiService.PostSetKeyConnectorKey(setPasswordRequest);
} }