From cc895d059812bf0b97b625e568b6edb083237a23 Mon Sep 17 00:00:00 2001 From: Patrick Pimentel Date: Mon, 29 Dec 2025 13:39:08 -0500 Subject: [PATCH] fix(register): [PM-27084] Account Register Uses New Data Types - Added more tests. --- .../Accounts/RegisterFinishRequestModel.cs | 8 - .../Controllers/AccountsControllerTests.cs | 147 +++++++++++++++++- 2 files changed, 146 insertions(+), 9 deletions(-) diff --git a/src/Core/Auth/Models/Api/Request/Accounts/RegisterFinishRequestModel.cs b/src/Core/Auth/Models/Api/Request/Accounts/RegisterFinishRequestModel.cs index 81b85387a6..1379fc30fc 100644 --- a/src/Core/Auth/Models/Api/Request/Accounts/RegisterFinishRequestModel.cs +++ b/src/Core/Auth/Models/Api/Request/Accounts/RegisterFinishRequestModel.cs @@ -63,14 +63,6 @@ public class RegisterFinishRequestModel : IValidatableObject public User ToUser() { - // PM-28143 - Remove line below - // When we process this request to a user object, check if the unlock and authentication - // data has been passed through, and if so they should have matching values. - MasterPasswordUnlockDataRequestModel.ThrowIfExistsAndNotMatchingAuthenticationData(MasterPasswordAuthentication, MasterPasswordUnlock); - - // PM-28143 - Remove line below - MasterPasswordAuthenticationDataRequestModel.ThrowIfExistsAndHashIsNotEqual(MasterPasswordAuthentication, MasterPasswordHash); - var user = new User { Email = Email, diff --git a/test/Identity.Test/Controllers/AccountsControllerTests.cs b/test/Identity.Test/Controllers/AccountsControllerTests.cs index 88899de80a..7c338e141f 100644 --- a/test/Identity.Test/Controllers/AccountsControllerTests.cs +++ b/test/Identity.Test/Controllers/AccountsControllerTests.cs @@ -1,4 +1,5 @@ -using System.Reflection; +using System.ComponentModel.DataAnnotations; +using System.Reflection; using System.Text; using Bit.Core; using Bit.Core.Auth.Models.Api.Request.Accounts; @@ -1040,6 +1041,150 @@ public class AccountsControllerTests : IDisposable Assert.Equal("MasterKeyWrappedUserKey couldn't be found on either the MasterPasswordUnlockData or the UserSymmetricKey property passed in.", ex.Message); } + [Theory, BitAutoData] + public void RegisterFinishRequestModel_Validate_Throws_WhenUnlockAndAuthDataMismatch( + string email, + string authHash, + string masterKeyWrappedUserKey, + string publicKey, + string encryptedPrivateKey) + { + // Arrange: authentication and unlock have different KDF and/or salt + var authKdf = new KdfRequestModel + { + KdfType = KdfType.PBKDF2_SHA256, + Iterations = AuthConstants.PBKDF2_ITERATIONS.Default + }; + var unlockKdf = new KdfRequestModel + { + KdfType = KdfType.Argon2id, + Iterations = AuthConstants.ARGON2_ITERATIONS.Default, + Memory = AuthConstants.ARGON2_MEMORY.Default, + Parallelism = AuthConstants.ARGON2_PARALLELISM.Default + }; + + var model = new RegisterFinishRequestModel + { + Email = email, + MasterPasswordAuthentication = new MasterPasswordAuthenticationDataRequestModel + { + Kdf = authKdf, + MasterPasswordAuthenticationHash = authHash, + Salt = email + }, + MasterPasswordUnlock = new MasterPasswordUnlockDataRequestModel + { + Kdf = unlockKdf, + MasterKeyWrappedUserKey = masterKeyWrappedUserKey, + Salt = email + }, + UserAsymmetricKeys = new KeysRequestModel + { + PublicKey = publicKey, + EncryptedPrivateKey = encryptedPrivateKey + } + }; + + var ctx = new ValidationContext(model); + + // Act & Assert + var ex = Assert.Throws(() => model.Validate(ctx).ToList()); + Assert.Equal("KDF settings and salt must match between authentication and unlock data.", ex.Message); + } + + [Theory, BitAutoData] + public void RegisterFinishRequestModel_Validate_Throws_WhenSaltMismatch( + string email, + string authHash, + string masterKeyWrappedUserKey, + string publicKey, + string encryptedPrivateKey) + { + var unlockKdf = new KdfRequestModel + { + KdfType = KdfType.Argon2id, + Iterations = AuthConstants.ARGON2_ITERATIONS.Default, + Memory = AuthConstants.ARGON2_MEMORY.Default, + Parallelism = AuthConstants.ARGON2_PARALLELISM.Default + }; + + var model = new RegisterFinishRequestModel + { + Email = email, + MasterPasswordAuthentication = new MasterPasswordAuthenticationDataRequestModel + { + Kdf = unlockKdf, + MasterPasswordAuthenticationHash = authHash, + Salt = email + }, + MasterPasswordUnlock = new MasterPasswordUnlockDataRequestModel + { + Kdf = unlockKdf, + MasterKeyWrappedUserKey = masterKeyWrappedUserKey, + // Intentionally different salt to force mismatch + Salt = email + ".mismatch" + }, + UserAsymmetricKeys = new KeysRequestModel + { + PublicKey = publicKey, + EncryptedPrivateKey = encryptedPrivateKey + } + }; + + var ctx = new ValidationContext(model); + + // Act & Assert + var ex = Assert.Throws(() => model.Validate(ctx).ToList()); + Assert.Equal("KDF settings and salt must match between authentication and unlock data.", ex.Message); + } + + [Theory, BitAutoData] + public void RegisterFinishRequestModel_Validate_Throws_WhenAuthHashAndRootHashMismatch( + string email, + string authHash, + string differentRootHash, + string masterKeyWrappedUserKey, + string publicKey, + string encryptedPrivateKey) + { + // Arrange: same KDF/salt, but authentication hash differs from legacy root hash + var kdf = new KdfRequestModel + { + KdfType = KdfType.PBKDF2_SHA256, + Iterations = AuthConstants.PBKDF2_ITERATIONS.Default + }; + + var model = new RegisterFinishRequestModel + { + Email = email, + MasterPasswordAuthentication = new MasterPasswordAuthenticationDataRequestModel + { + Kdf = kdf, + MasterPasswordAuthenticationHash = authHash, + Salt = email + }, + MasterPasswordUnlock = new MasterPasswordUnlockDataRequestModel + { + Kdf = kdf, + MasterKeyWrappedUserKey = masterKeyWrappedUserKey, + Salt = email + }, + // Intentionally set the legacy field to a different value to trigger the throw + MasterPasswordHash = differentRootHash, + UserAsymmetricKeys = new KeysRequestModel + { + PublicKey = publicKey, + EncryptedPrivateKey = encryptedPrivateKey + } + }; + + var ctx = new ValidationContext(model); + + // Act & Assert + var ex = Assert.Throws(() => model.Validate(ctx).ToList()); + Assert.Equal("Master password hash and hash are not equal.", ex.Message); + } + private void SetDefaultKdfHmacKey(byte[]? newKey) { var fieldInfo = typeof(AccountsController).GetField("_defaultKdfHmacKey", BindingFlags.NonPublic | BindingFlags.Instance);