diff --git a/src/App/Pages/Accounts/LoginSsoPageViewModel.cs b/src/App/Pages/Accounts/LoginSsoPageViewModel.cs index ce07d99b4..a95906f25 100644 --- a/src/App/Pages/Accounts/LoginSsoPageViewModel.cs +++ b/src/App/Pages/Accounts/LoginSsoPageViewModel.cs @@ -211,33 +211,41 @@ namespace Bit.App.Pages 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) { - var pendingRequest = await _stateService.GetPendingAdminAuthRequestAsync(); - // 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 (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) { await _deviceTrustCryptoService.RemoveTrustedDeviceAsync(); 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(); - SsoAuthSuccessAction?.Invoke(); + StartSetPasswordAction?.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); 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 // have one and they aren't using key connector. // 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(); return; } - if (response.ForcePasswordReset) - { - UpdateTempPasswordAction?.Invoke(); - return; - } - _syncService.FullSyncAsync(true).FireAndForget(); SsoAuthSuccessAction?.Invoke(); } diff --git a/src/App/Pages/Accounts/RegisterPageViewModel.cs b/src/App/Pages/Accounts/RegisterPageViewModel.cs index f69453bad..52cca400c 100644 --- a/src/App/Pages/Accounts/RegisterPageViewModel.cs +++ b/src/App/Pages/Accounts/RegisterPageViewModel.cs @@ -178,7 +178,10 @@ namespace Bit.App.Pages Email = Email.Trim().ToLower(); var kdfConfig = new KdfConfig(KdfType.PBKDF2_SHA256, Constants.Pbkdf2Iterations, null, null); 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 (newPublicKey, newProtectedPrivateKey) = await _cryptoService.MakeKeyPairAsync(newUserKey); var request = new RegisterRequest diff --git a/src/App/Pages/Accounts/SetPasswordPageViewModel.cs b/src/App/Pages/Accounts/SetPasswordPageViewModel.cs index 2f0966786..76ebaab55 100644 --- a/src/App/Pages/Accounts/SetPasswordPageViewModel.cs +++ b/src/App/Pages/Accounts/SetPasswordPageViewModel.cs @@ -169,7 +169,8 @@ namespace Bit.App.Pages var masterPasswordHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, newMasterKey, HashPurpose.ServerAuthorization); 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 request = new SetPasswordRequest diff --git a/src/App/Pages/Accounts/TwoFactorPageViewModel.cs b/src/App/Pages/Accounts/TwoFactorPageViewModel.cs index 21d35dddc..a49b9fa02 100644 --- a/src/App/Pages/Accounts/TwoFactorPageViewModel.cs +++ b/src/App/Pages/Accounts/TwoFactorPageViewModel.cs @@ -326,27 +326,56 @@ namespace Bit.App.Pages if (decryptOptions?.TrustedDeviceOption != null) { - // 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 (result.ForcePasswordReset) - { - UpdateTempPasswordAction?.Invoke(); - } - else if (await _deviceTrustCryptoService.IsDeviceTrustedAsync()) + 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) { await _deviceTrustCryptoService.RemoveTrustedDeviceAsync(); 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 { - _syncService.FullSyncAsync(true).FireAndForget(); - await TwoFactorAuthSuccessAsync(); + await _stateService.SetPendingAdminAuthRequestAsync(null); + StartDeviceApprovalOptionsAction?.Invoke(); } } else @@ -361,16 +390,12 @@ namespace Bit.App.Pages // Note: TDE & Key connector are mutually exclusive org config options. 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(); return; } - if (result.ForcePasswordReset) - { - UpdateTempPasswordAction?.Invoke(); - return; - } - _syncService.FullSyncAsync(true).FireAndForget(); await TwoFactorAuthSuccessAsync(); } diff --git a/src/Core/Abstractions/IApiService.cs b/src/Core/Abstractions/IApiService.cs index 917389009..76dc38172 100644 --- a/src/Core/Abstractions/IApiService.cs +++ b/src/Core/Abstractions/IApiService.cs @@ -72,7 +72,7 @@ namespace Bit.Core.Abstractions Task PutOrganizationUserResetPasswordEnrollmentAsync(string orgId, string userId, OrganizationUserResetPasswordEnrollmentRequest request); Task GetMasterKeyFromKeyConnectorAsync(string keyConnectorUrl); - Task PostUserKeyToKeyConnector(string keyConnectorUrl, KeyConnectorUserKeyRequest request); + Task PostMasterKeyToKeyConnector(string keyConnectorUrl, KeyConnectorUserKeyRequest request); Task PostSetKeyConnectorKey(SetKeyConnectorKeyRequest request); Task PostConvertToKeyConnector(); Task PostLeaveOrganization(string id); diff --git a/src/Core/Abstractions/ICryptoService.cs b/src/Core/Abstractions/ICryptoService.cs index 4b9bf570a..9669bbfbc 100644 --- a/src/Core/Abstractions/ICryptoService.cs +++ b/src/Core/Abstractions/ICryptoService.cs @@ -26,7 +26,7 @@ namespace Bit.Core.Abstractions Task GetMasterKeyAsync(string userId = null); Task MakeMasterKeyAsync(string password, string email, KdfConfig kdfConfig); Task ClearMasterKeyAsync(string userId = null); - Task> EncryptUserKeyWithMasterKeyAsync(MasterKey masterKey); + Task> EncryptUserKeyWithMasterKeyAsync(MasterKey masterKey, UserKey userKey = null); Task DecryptUserKeyWithMasterKeyAsync(MasterKey masterKey, EncString encUserKey = null, string userId = null); Task> MakeDataEncKeyAsync(SymmetricCryptoKey key); Task HashMasterKeyAsync(string password, MasterKey key, HashPurpose hashPurpose = HashPurpose.ServerAuthorization); diff --git a/src/Core/Exceptions/MasterKeyNullException.cs b/src/Core/Exceptions/MasterKeyNullException.cs new file mode 100644 index 000000000..d7d8ff582 --- /dev/null +++ b/src/Core/Exceptions/MasterKeyNullException.cs @@ -0,0 +1,12 @@ +using System; +namespace Bit.Core.Exceptions +{ + public class MasterKeyNullException : Exception + { + public MasterKeyNullException() + : base("MasterKey is null.") + { + } + } +} + diff --git a/src/Core/Exceptions/UserAndMasterKeyNullException.cs b/src/Core/Exceptions/UserAndMasterKeysNullException.cs similarity index 100% rename from src/Core/Exceptions/UserAndMasterKeyNullException.cs rename to src/Core/Exceptions/UserAndMasterKeysNullException.cs diff --git a/src/Core/Exceptions/UserKeyNullException.cs b/src/Core/Exceptions/UserKeyNullException.cs new file mode 100644 index 000000000..f63d34382 --- /dev/null +++ b/src/Core/Exceptions/UserKeyNullException.cs @@ -0,0 +1,12 @@ +using System; +namespace Bit.Core.Exceptions +{ + public class UserKeyNullException : Exception + { + public UserKeyNullException() + : base("UserKey is null.") + { + } + } +} + diff --git a/src/Core/Models/Domain/AuthResult.cs b/src/Core/Models/Domain/AuthResult.cs index 8e48c7da7..8f3a6bc50 100644 --- a/src/Core/Models/Domain/AuthResult.cs +++ b/src/Core/Models/Domain/AuthResult.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Bit.Core.Enums; namespace Bit.Core.Models.Domain @@ -8,6 +9,8 @@ namespace Bit.Core.Models.Domain public bool TwoFactor { get; set; } public bool CaptchaNeeded => !string.IsNullOrWhiteSpace(CaptchaSiteKey); 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 ForcePasswordReset { get; set; } public Dictionary> TwoFactorProviders { get; set; } diff --git a/src/Core/Services/ApiService.cs b/src/Core/Services/ApiService.cs index 7734e5796..e2ae3ecd6 100644 --- a/src/Core/Services/ApiService.cs +++ b/src/Core/Services/ApiService.cs @@ -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()) { diff --git a/src/Core/Services/AuthService.cs b/src/Core/Services/AuthService.cs index f70d80a60..2fa087224 100644 --- a/src/Core/Services/AuthService.cs +++ b/src/Core/Services/AuthService.cs @@ -240,7 +240,13 @@ namespace Bit.Core.Services public async Task LogInSsoAsync(string code, string codeVerifier, string redirectUrl, string orgId) { 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 LogInTwoFactorAsync(TwoFactorProviderType twoFactorProvider, string twoFactorToken, diff --git a/src/Core/Services/CryptoService.cs b/src/Core/Services/CryptoService.cs index 0495a7096..856f0f777 100644 --- a/src/Core/Services/CryptoService.cs +++ b/src/Core/Services/CryptoService.cs @@ -6,6 +6,7 @@ using System.Text; using System.Threading.Tasks; using Bit.Core.Abstractions; using Bit.Core.Enums; +using Bit.Core.Exceptions; using Bit.Core.Models.Domain; using Bit.Core.Models.Response; using Bit.Core.Utilities; @@ -152,9 +153,13 @@ namespace Bit.Core.Services return _stateService.SetMasterKeyAsync(null, userId); } - public async Task> EncryptUserKeyWithMasterKeyAsync(MasterKey masterKey) + public async Task> 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)); } @@ -163,7 +168,7 @@ namespace Bit.Core.Services masterKey ??= await GetMasterKeyAsync(userId); if (masterKey == null) { - throw new Exception("No master key found."); + throw new MasterKeyNullException(); } if (encUserKey == null) diff --git a/src/Core/Services/KeyConnectorService.cs b/src/Core/Services/KeyConnectorService.cs index b80a446c5..9b0d55094 100644 --- a/src/Core/Services/KeyConnectorService.cs +++ b/src/Core/Services/KeyConnectorService.cs @@ -68,7 +68,7 @@ namespace Bit.Core.Services try { var keyConnectorRequest = new KeyConnectorUserKeyRequest(masterKey.EncKeyB64); - await _apiService.PostUserKeyToKeyConnector(organization.KeyConnectorUrl, keyConnectorRequest); + await _apiService.PostMasterKeyToKeyConnector(organization.KeyConnectorUrl, keyConnectorRequest); } catch (Exception e) { @@ -95,13 +95,15 @@ namespace Bit.Core.Services var keyConnectorRequest = new KeyConnectorUserKeyRequest(newMasterKey.EncKeyB64); 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); try { - await _apiService.PostUserKeyToKeyConnector(tokenResponse.KeyConnectorUrl, keyConnectorRequest); + await _apiService.PostMasterKeyToKeyConnector(tokenResponse.KeyConnectorUrl, keyConnectorRequest); } catch (Exception e) { @@ -115,7 +117,7 @@ namespace Bit.Core.Services EncryptedPrivateKey = newProtectedPrivateKey.EncryptedString }; var setPasswordRequest = new SetKeyConnectorKeyRequest( - newProtectedPrivateKey.EncryptedString, keys, tokenResponse.KdfConfig, orgId + newProtectedUserKey.EncryptedString, keys, tokenResponse.KdfConfig, orgId ); await _apiService.PostSetKeyConnectorKey(setPasswordRequest); }