diff --git a/src/Core/KeyManagement/UserKey/Implementations/RotateUserAccountkeysCommand.cs b/src/Core/KeyManagement/UserKey/Implementations/RotateUserAccountkeysCommand.cs index fdc5c9286c..f973648a28 100644 --- a/src/Core/KeyManagement/UserKey/Implementations/RotateUserAccountkeysCommand.cs +++ b/src/Core/KeyManagement/UserKey/Implementations/RotateUserAccountkeysCommand.cs @@ -61,59 +61,85 @@ public class RotateUserAccountKeysCommand( user.RevisionDate = user.AccountRevisionDate = now; user.LastKeyRotationDate = now; user.SecurityStamp = Guid.NewGuid().ToString(); - var currentSignatureKeyPair = await _userSignatureKeyPairRepository.GetByUserIdAsync(user.Id); + List saveEncryptedDataActions = []; - if (!model.MasterPasswordUnlockData.ValidateForUser(user)) + UpdateAccountKeys(model, user, saveEncryptedDataActions); + UpdateUnlockMethods(model, user, saveEncryptedDataActions); + UpdateUserData(model, user, saveEncryptedDataActions); + + await _userRepository.UpdateUserKeyAndEncryptedDataV2Async(user, saveEncryptedDataActions); + await _pushService.PushLogOutAsync(user.Id); + return IdentityResult.Success; + } + + async Task IsUserV2UserAsync(User user) + { + ArgumentNullException.ThrowIfNull(user); + var currentSignatureKeyPair = await _userSignatureKeyPairRepository.GetByUserIdAsync(user.Id); + return currentSignatureKeyPair != null; + } + + async void ValidateRotationModelSignatureKeyPairForV2User(RotateUserAccountKeysData model, User user) + { + var currentSignatureKeyPair = await _userSignatureKeyPairRepository.GetByUserIdAsync(user.Id); + if (model.AccountKeys.SignatureKeyPairData == null) { - throw new InvalidOperationException("The provided master password unlock data is not valid for this user."); + throw new InvalidOperationException("The provided signing key data is null, but the user already has signing keys."); } + + if (model.AccountKeys.SignatureKeyPairData.VerifyingKey != currentSignatureKeyPair.VerifyingKey) + { + throw new InvalidOperationException("The provided signing key data does not match the user's current signing key data."); + } + if (string.IsNullOrEmpty(model.AccountKeys.PublicKeyEncryptionKeyPairData.SignedPublicKey)) + { + throw new InvalidOperationException("No signed public key provided, but the user already has a signature key pair."); + } + } + + void ValidateRotationModelSignatureKeyPairForV1UserAndUpgradeToV2(RotateUserAccountKeysData model, User user, List saveEncryptedDataActions) + { + if (model.AccountKeys.SignatureKeyPairData != null) + { + // user is upgrading + if (string.IsNullOrEmpty(model.AccountKeys.SignatureKeyPairData.VerifyingKey)) + { + throw new InvalidOperationException("The provided signing key data does not contain a valid verifying key."); + } + + if (string.IsNullOrEmpty(model.AccountKeys.SignatureKeyPairData.WrappedSigningKey)) + { + throw new InvalidOperationException("The provided signing key data does not contain a valid wrapped signing key."); + } + saveEncryptedDataActions.Add(_userSignatureKeyPairRepository.SetUserSignatureKeyPair(user.Id, model.AccountKeys.SignatureKeyPairData)); + user.SignedPublicKey = model.AccountKeys.PublicKeyEncryptionKeyPairData.SignedPublicKey; + } + } + + async void UpdateAccountKeys(RotateUserAccountKeysData model, User user, List saveEncryptedDataActions) + { + // Changing the public key encryption key pair is not supported during key rotation for now; so this ensures it is not accidentally changed if (model.AccountKeys.PublicKeyEncryptionKeyPairData.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 keypair is currently not supported during key rotation."); } + // Private key is re-wrapped with new user key by client + user.PrivateKey = model.UserKeyEncryptedAccountPrivateKey; - if (currentSignatureKeyPair != null) + + if (await IsUserV2UserAsync(user)) { - // user is already v2 user - if (model.AccountKeys.SignatureKeyPairData == null) - { - throw new InvalidOperationException("The provided signing key data is null, but the user already has signing keys."); - } - - if (model.AccountKeys.SignatureKeyPairData.VerifyingKey != currentSignatureKeyPair.VerifyingKey) - { - throw new InvalidOperationException("The provided signing key data does not match the user's current signing key data."); - } - if (string.IsNullOrEmpty(model.AccountKeys.PublicKeyEncryptionKeyPairData.SignedPublicKey)) - { - throw new InvalidOperationException("No signed public key provided, but the user already has a signature key pair."); - } + ValidateRotationModelSignatureKeyPairForV2User(model, user); } else { - if (model.AccountKeys.SignatureKeyPairData != null) - { - // user is upgrading - if (string.IsNullOrEmpty(model.AccountKeys.SignatureKeyPairData.VerifyingKey)) - { - throw new InvalidOperationException("The provided signing key data does not contain a valid verifying key."); - } - - if (string.IsNullOrEmpty(model.AccountKeys.SignatureKeyPairData.WrappedSigningKey)) - { - throw new InvalidOperationException("The provided signing key data does not contain a valid wrapped signing key."); - } - saveEncryptedDataActions.Add(_userSignatureKeyPairRepository.SetUserSignatureKeyPair(user.Id, model.AccountKeys.SignatureKeyPairData)); - user.SignedPublicKey = model.AccountKeys.PublicKeyEncryptionKeyPairData.SignedPublicKey; - } + ValidateRotationModelSignatureKeyPairForV1UserAndUpgradeToV2(model, user, saveEncryptedDataActions); } + } - user.Key = model.MasterPasswordUnlockData.MasterKeyEncryptedUserKey; - user.PrivateKey = model.UserKeyEncryptedAccountPrivateKey; - user.MasterPassword = _passwordHasher.HashPassword(user, model.MasterPasswordUnlockData.MasterKeyAuthenticationHash); - user.MasterPasswordHint = model.MasterPasswordUnlockData.MasterPasswordHint; - + void UpdateUserData(RotateUserAccountKeysData model, User user, List saveEncryptedDataActions) + { if (model.Ciphers.Any()) { saveEncryptedDataActions.Add(_cipherRepository.UpdateForKeyRotation(user.Id, model.Ciphers)); @@ -128,6 +154,18 @@ public class RotateUserAccountKeysCommand( { saveEncryptedDataActions.Add(_sendRepository.UpdateForKeyRotation(user.Id, model.Sends)); } + } + + 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()) { @@ -148,9 +186,5 @@ public class RotateUserAccountKeysCommand( { saveEncryptedDataActions.Add(_deviceRepository.UpdateKeysForRotationAsync(user.Id, model.DeviceKeys)); } - - await _userRepository.UpdateUserKeyAndEncryptedDataV2Async(user, saveEncryptedDataActions); - await _pushService.PushLogOutAsync(user.Id); - return IdentityResult.Success; } }