diff --git a/src/Core/KeyManagement/Queries/GetMinimumClientVersionForUserQuery.cs b/src/Core/KeyManagement/Queries/GetMinimumClientVersionForUserQuery.cs index 49cc46a54c..fe6020ab5a 100644 --- a/src/Core/KeyManagement/Queries/GetMinimumClientVersionForUserQuery.cs +++ b/src/Core/KeyManagement/Queries/GetMinimumClientVersionForUserQuery.cs @@ -21,5 +21,3 @@ public class GetMinimumClientVersionForUserQuery(IIsV2EncryptionUserQuery isV2En return null; } } - - diff --git a/src/Core/KeyManagement/Queries/Interfaces/IGetMinimumClientVersionForUserQuery.cs b/src/Core/KeyManagement/Queries/Interfaces/IGetMinimumClientVersionForUserQuery.cs index 896a473d69..01deb460f1 100644 --- a/src/Core/KeyManagement/Queries/Interfaces/IGetMinimumClientVersionForUserQuery.cs +++ b/src/Core/KeyManagement/Queries/Interfaces/IGetMinimumClientVersionForUserQuery.cs @@ -6,5 +6,3 @@ public interface IGetMinimumClientVersionForUserQuery { Task Run(User? user); } - - diff --git a/src/Core/KeyManagement/Queries/Interfaces/IIsV2EncryptionUserQuery.cs b/src/Core/KeyManagement/Queries/Interfaces/IIsV2EncryptionUserQuery.cs index 6b644a2dc7..38c0e10b44 100644 --- a/src/Core/KeyManagement/Queries/Interfaces/IIsV2EncryptionUserQuery.cs +++ b/src/Core/KeyManagement/Queries/Interfaces/IIsV2EncryptionUserQuery.cs @@ -6,5 +6,3 @@ public interface IIsV2EncryptionUserQuery { Task Run(User user); } - - diff --git a/src/Core/KeyManagement/Queries/IsV2EncryptionUserQuery.cs b/src/Core/KeyManagement/Queries/IsV2EncryptionUserQuery.cs index 74a203004c..ea64d5a20a 100644 --- a/src/Core/KeyManagement/Queries/IsV2EncryptionUserQuery.cs +++ b/src/Core/KeyManagement/Queries/IsV2EncryptionUserQuery.cs @@ -29,5 +29,3 @@ public class IsV2EncryptionUserQuery(IUserSignatureKeyPairRepository userSignatu }; } } - - diff --git a/src/Core/KeyManagement/UserKey/Implementations/RotateUserAccountkeysCommand.cs b/src/Core/KeyManagement/UserKey/Implementations/RotateUserAccountkeysCommand.cs deleted file mode 100644 index 6e5708f667..0000000000 --- a/src/Core/KeyManagement/UserKey/Implementations/RotateUserAccountkeysCommand.cs +++ /dev/null @@ -1,265 +0,0 @@ -// FIXME: Update this file to be null safe and then delete the line below -#nullable disable - -using Bit.Core.Auth.Repositories; -using Bit.Core.Entities; -using Bit.Core.Enums; -using Bit.Core.KeyManagement.Models.Data; -using Bit.Core.KeyManagement.Repositories; -using Bit.Core.KeyManagement.Utilities; -using Bit.Core.Platform.Push; -using Bit.Core.Repositories; -using Bit.Core.Services; -using Bit.Core.Tools.Repositories; -using Bit.Core.Vault.Repositories; -using Microsoft.AspNetCore.Identity; - -namespace Bit.Core.KeyManagement.UserKey.Implementations; - -/// -public class RotateUserAccountKeysCommand : IRotateUserAccountKeysCommand -{ - private readonly IUserService _userService; - private readonly IUserRepository _userRepository; - private readonly ICipherRepository _cipherRepository; - private readonly IFolderRepository _folderRepository; - private readonly ISendRepository _sendRepository; - private readonly IEmergencyAccessRepository _emergencyAccessRepository; - private readonly IOrganizationUserRepository _organizationUserRepository; - private readonly IDeviceRepository _deviceRepository; - private readonly IPushNotificationService _pushService; - private readonly IdentityErrorDescriber _identityErrorDescriber; - private readonly IWebAuthnCredentialRepository _credentialRepository; - private readonly IPasswordHasher _passwordHasher; - private readonly IUserSignatureKeyPairRepository _userSignatureKeyPairRepository; - private readonly IFeatureService _featureService; - - /// - /// Instantiates a new - /// - /// Master password hash validation - /// Updates user keys and re-encrypted data if needed - /// Provides a method to update re-encrypted cipher data - /// Provides a method to update re-encrypted folder data - /// Provides a method to update re-encrypted send data - /// Provides a method to update re-encrypted emergency access data - /// Provides a method to update re-encrypted organization user data - /// Provides a method to update re-encrypted device keys - /// Hashes the new master password - /// Logs out user from other devices after successful rotation - /// Provides a password mismatch error if master password hash validation fails - /// Provides a method to update re-encrypted WebAuthn keys - /// Provides a method to update re-encrypted signature keys - public RotateUserAccountKeysCommand(IUserService userService, IUserRepository userRepository, - ICipherRepository cipherRepository, IFolderRepository folderRepository, ISendRepository sendRepository, - IEmergencyAccessRepository emergencyAccessRepository, IOrganizationUserRepository organizationUserRepository, - IDeviceRepository deviceRepository, - IPasswordHasher passwordHasher, - IPushNotificationService pushService, IdentityErrorDescriber errors, IWebAuthnCredentialRepository credentialRepository, - IUserSignatureKeyPairRepository userSignatureKeyPairRepository, - IFeatureService featureService) - { - _userService = userService; - _userRepository = userRepository; - _cipherRepository = cipherRepository; - _folderRepository = folderRepository; - _sendRepository = sendRepository; - _emergencyAccessRepository = emergencyAccessRepository; - _organizationUserRepository = organizationUserRepository; - _deviceRepository = deviceRepository; - _pushService = pushService; - _identityErrorDescriber = errors; - _credentialRepository = credentialRepository; - _passwordHasher = passwordHasher; - _userSignatureKeyPairRepository = userSignatureKeyPairRepository; - _featureService = featureService; - } - - /// - public async Task RotateUserAccountKeysAsync(User user, RotateUserAccountKeysData model) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - if (!await _userService.CheckPasswordAsync(user, model.OldMasterKeyAuthenticationHash)) - { - return IdentityResult.Failed(_identityErrorDescriber.PasswordMismatch()); - } - - var now = DateTime.UtcNow; - user.RevisionDate = user.AccountRevisionDate = now; - user.LastKeyRotationDate = now; - user.SecurityStamp = Guid.NewGuid().ToString(); - - List saveEncryptedDataActions = []; - - await UpdateAccountKeysAsync(model, user, saveEncryptedDataActions); - UpdateUnlockMethods(model, user, saveEncryptedDataActions); - UpdateUserData(model, user, saveEncryptedDataActions); - - await _userRepository.UpdateUserKeyAndEncryptedDataV2Async(user, saveEncryptedDataActions); - await _pushService.PushLogOutAsync(user.Id); - return IdentityResult.Success; - } - - public async Task RotateV2AccountKeysAsync(RotateUserAccountKeysData model, User user, List saveEncryptedDataActions) - { - ValidateV2Encryption(model); - await ValidateVerifyingKeyUnchangedAsync(model, user); - - saveEncryptedDataActions.Add(_userSignatureKeyPairRepository.UpdateForKeyRotation(user.Id, model.AccountKeys.SignatureKeyPairData)); - user.SignedPublicKey = model.AccountKeys.PublicKeyEncryptionKeyPairData.SignedPublicKey; - user.SecurityState = model.AccountKeys.SecurityStateData!.SecurityState; - user.SecurityVersion = model.AccountKeys.SecurityStateData.SecurityVersion; - } - - public void UpgradeV1ToV2Keys(RotateUserAccountKeysData model, User user, List saveEncryptedDataActions) - { - ValidateV2Encryption(model); - saveEncryptedDataActions.Add(_userSignatureKeyPairRepository.SetUserSignatureKeyPair(user.Id, model.AccountKeys.SignatureKeyPairData)); - user.SignedPublicKey = model.AccountKeys.PublicKeyEncryptionKeyPairData.SignedPublicKey; - user.SecurityState = model.AccountKeys.SecurityStateData!.SecurityState; - user.SecurityVersion = model.AccountKeys.SecurityStateData.SecurityVersion; - } - - public async Task UpdateAccountKeysAsync(RotateUserAccountKeysData model, User user, List saveEncryptedDataActions) - { - ValidatePublicKeyEncryptionKeyPairUnchanged(model, user); - - if (IsV2EncryptionUserAsync(user)) - { - await RotateV2AccountKeysAsync(model, user, saveEncryptedDataActions); - } - else if (model.AccountKeys.SignatureKeyPairData != null) - { - UpgradeV1ToV2Keys(model, user, saveEncryptedDataActions); - } - else - { - if (EncryptionParsing.GetEncryptionType(model.AccountKeys.PublicKeyEncryptionKeyPairData.WrappedPrivateKey) != EncryptionType.AesCbc256_HmacSha256_B64) - { - throw new InvalidOperationException("The provided account private key was not wrapped with AES-256-CBC-HMAC"); - } - // V1 user to V1 user rotation needs to further changes, the private key was re-encrypted. - } - - // Private key is re-wrapped with new user key by client - user.PrivateKey = model.AccountKeys.PublicKeyEncryptionKeyPairData.WrappedPrivateKey; - } - - public void UpdateUserData(RotateUserAccountKeysData model, User user, List saveEncryptedDataActions) - { - // The revision date has to be updated so that de-synced clients don't accidentally post over the re-encrypted data - // with an old-user key-encrypted copy - var now = DateTime.UtcNow; - - if (model.Ciphers.Any()) - { - var ciphersWithUpdatedDate = model.Ciphers.ToList().Select(c => { c.RevisionDate = now; return c; }); - saveEncryptedDataActions.Add(_cipherRepository.UpdateForKeyRotation(user.Id, ciphersWithUpdatedDate)); - } - - if (model.Folders.Any()) - { - var foldersWithUpdatedDate = model.Folders.ToList().Select(f => { f.RevisionDate = now; return f; }); - saveEncryptedDataActions.Add(_folderRepository.UpdateForKeyRotation(user.Id, foldersWithUpdatedDate)); - } - - if (model.Sends.Any()) - { - var sendsWithUpdatedDate = model.Sends.ToList().Select(s => { s.RevisionDate = now; return s; }); - saveEncryptedDataActions.Add(_sendRepository.UpdateForKeyRotation(user.Id, sendsWithUpdatedDate)); - } - } - - void UpdateUnlockMethods(RotateUserAccountKeysData model, User user, List saveEncryptedDataActions) - { - if (!model.MasterPasswordUnlockData.ValidateForUser(user)) - { - throw new InvalidOperationException("The provided master password unlock data is not valid for this user."); - } - // Update master password authentication & unlock - user.Key = model.MasterPasswordUnlockData.MasterKeyEncryptedUserKey; - user.MasterPassword = _passwordHasher.HashPassword(user, model.MasterPasswordUnlockData.MasterKeyAuthenticationHash); - user.MasterPasswordHint = model.MasterPasswordUnlockData.MasterPasswordHint; - - if (model.EmergencyAccesses.Any()) - { - saveEncryptedDataActions.Add(_emergencyAccessRepository.UpdateForKeyRotation(user.Id, model.EmergencyAccesses)); - } - - if (model.OrganizationUsers.Any()) - { - saveEncryptedDataActions.Add(_organizationUserRepository.UpdateForKeyRotation(user.Id, model.OrganizationUsers)); - } - - if (model.WebAuthnKeys.Any()) - { - saveEncryptedDataActions.Add(_credentialRepository.UpdateKeysForRotationAsync(user.Id, model.WebAuthnKeys)); - } - - if (model.DeviceKeys.Any()) - { - saveEncryptedDataActions.Add(_deviceRepository.UpdateKeysForRotationAsync(user.Id, model.DeviceKeys)); - } - } - - private bool IsV2EncryptionUserAsync(User user) - { - // Returns whether the user is a V2 user based on the private key's encryption type. - ArgumentNullException.ThrowIfNull(user); - var isPrivateKeyEncryptionV2 = EncryptionParsing.GetEncryptionType(user.PrivateKey) == EncryptionType.XChaCha20Poly1305_B64; - return isPrivateKeyEncryptionV2; - } - - private async Task ValidateVerifyingKeyUnchangedAsync(RotateUserAccountKeysData model, User user) - { - var currentSignatureKeyPair = await _userSignatureKeyPairRepository.GetByUserIdAsync(user.Id) ?? throw new InvalidOperationException("User does not have a signature key pair."); - if (model.AccountKeys.SignatureKeyPairData.VerifyingKey != currentSignatureKeyPair!.VerifyingKey) - { - throw new InvalidOperationException("The provided verifying key does not match the user's current verifying key."); - } - } - - private static void ValidatePublicKeyEncryptionKeyPairUnchanged(RotateUserAccountKeysData model, User user) - { - var publicKey = model.AccountKeys.PublicKeyEncryptionKeyPairData.PublicKey; - if (publicKey != user.PublicKey) - { - throw new InvalidOperationException("The provided account public key does not match the user's current public key, and changing the account asymmetric key pair is currently not supported during key rotation."); - } - } - - private static void ValidateV2Encryption(RotateUserAccountKeysData model) - { - if (model.AccountKeys.SignatureKeyPairData == null) - { - throw new InvalidOperationException("Signature key pair data is required for V2 encryption."); - } - if (EncryptionParsing.GetEncryptionType(model.AccountKeys.SignatureKeyPairData.WrappedSigningKey) != EncryptionType.XChaCha20Poly1305_B64) - { - throw new InvalidOperationException("The provided signing key data is not wrapped with XChaCha20-Poly1305."); - } - if (string.IsNullOrEmpty(model.AccountKeys.SignatureKeyPairData.VerifyingKey)) - { - throw new InvalidOperationException("The provided signature key pair data does not contain a valid verifying key."); - } - - if (EncryptionParsing.GetEncryptionType(model.AccountKeys.PublicKeyEncryptionKeyPairData.WrappedPrivateKey) != EncryptionType.XChaCha20Poly1305_B64) - { - throw new InvalidOperationException("The provided private key encryption key is not wrapped with XChaCha20-Poly1305."); - } - if (string.IsNullOrEmpty(model.AccountKeys.PublicKeyEncryptionKeyPairData.SignedPublicKey)) - { - throw new InvalidOperationException("No signed public key provided, but the user already has a signature key pair."); - } - if (model.AccountKeys.SecurityStateData == null || string.IsNullOrEmpty(model.AccountKeys.SecurityStateData.SecurityState)) - { - throw new InvalidOperationException("No signed security state provider for V2 user"); - } - } - - // Parsing moved to Bit.Core.KeyManagement.Utilities.EncryptionParsing -} diff --git a/src/Core/KeyManagement/UserKey/Implementations/RotateUserAccountKeysCommand.cs b/src/Core/KeyManagement/UserKey/Implementations/tmp_Rotate.cs similarity index 100% rename from src/Core/KeyManagement/UserKey/Implementations/RotateUserAccountKeysCommand.cs rename to src/Core/KeyManagement/UserKey/Implementations/tmp_Rotate.cs diff --git a/src/Core/KeyManagement/Utilities/EncryptionParsing.cs b/src/Core/KeyManagement/Utilities/EncryptionParsing.cs index f288cfd99a..269ff51228 100644 --- a/src/Core/KeyManagement/Utilities/EncryptionParsing.cs +++ b/src/Core/KeyManagement/Utilities/EncryptionParsing.cs @@ -25,5 +25,3 @@ public static class EncryptionParsing throw new ArgumentException("Invalid encryption type string."); } } - - diff --git a/src/Identity/IdentityServer/RequestValidators/BaseRequestValidator.cs b/src/Identity/IdentityServer/RequestValidators/BaseRequestValidator.cs index ae6aaed2bd..54e432de84 100644 --- a/src/Identity/IdentityServer/RequestValidators/BaseRequestValidator.cs +++ b/src/Identity/IdentityServer/RequestValidators/BaseRequestValidator.cs @@ -111,7 +111,7 @@ public abstract class BaseRequestValidator where T : class } else { - // 1. We need to check if the user's master password hash is correct. + // 1. We need to check if the user is legitimate via the appropriate mechanism through. var valid = await ValidateContextAsync(context, validatorContext); var user = validatorContext.User; if (!valid) @@ -122,10 +122,11 @@ public abstract class BaseRequestValidator where T : class return; } - // 1.5 We need to check now the version number + // 1.5 Now check the version number of the client. Do this after ValidateContextAsync so that + // we prevent account enumeration. If we were to do this before we would validate that a given user + // could exist await ValidateClientVersionAsync(context, validatorContext); - // 2. Decide if this user belongs to an organization that requires SSO. validatorContext.SsoRequired = await RequireSsoLoginAsync(user, request.GrantType); if (validatorContext.SsoRequired) diff --git a/test/Core.Test/KeyManagement/Queries/IsV2EncryptionUserQueryTests.cs b/test/Core.Test/KeyManagement/Queries/IsV2EncryptionUserQueryTests.cs index 67c3601b81..a3e91bb6ef 100644 --- a/test/Core.Test/KeyManagement/Queries/IsV2EncryptionUserQueryTests.cs +++ b/test/Core.Test/KeyManagement/Queries/IsV2EncryptionUserQueryTests.cs @@ -61,5 +61,3 @@ public class IsV2EncryptionUserQueryTests await Assert.ThrowsAsync(async () => await sut.Run(user)); } } - - diff --git a/test/Identity.IntegrationTest/Login/ClientVersionGateTests.cs b/test/Identity.IntegrationTest/Login/ClientVersionGateTests.cs index 54f005688e..642148b685 100644 --- a/test/Identity.IntegrationTest/Login/ClientVersionGateTests.cs +++ b/test/Identity.IntegrationTest/Login/ClientVersionGateTests.cs @@ -115,5 +115,3 @@ public class ClientVersionGateTests : IClassFixture databaseContext.SaveChanges(); } } - - diff --git a/test/Identity.Test/IdentityServer/RequestValidators/ClientVersionValidatorTests.cs b/test/Identity.Test/IdentityServer/RequestValidators/ClientVersionValidatorTests.cs index dc491596fe..45fd26169a 100644 --- a/test/Identity.Test/IdentityServer/RequestValidators/ClientVersionValidatorTests.cs +++ b/test/Identity.Test/IdentityServer/RequestValidators/ClientVersionValidatorTests.cs @@ -51,5 +51,3 @@ public class ClientVersionValidatorTests Assert.True(ok); } } - -