diff --git a/src/Api/Auth/Models/Request/Accounts/PasswordRequestModel.cs b/src/Api/Auth/Models/Request/Accounts/PasswordRequestModel.cs index 8fa51e9f34..ab8c727852 100644 --- a/src/Api/Auth/Models/Request/Accounts/PasswordRequestModel.cs +++ b/src/Api/Auth/Models/Request/Accounts/PasswordRequestModel.cs @@ -1,7 +1,5 @@ -#nullable enable - -using System.ComponentModel.DataAnnotations; -using Bit.Api.KeyManagement.Models.Requests; +using System.ComponentModel.DataAnnotations; +using Bit.Core.KeyManagement.Models.Api.Request; namespace Bit.Api.Auth.Models.Request.Accounts; diff --git a/src/Api/KeyManagement/Models/Requests/MasterPasswordUnlockDataRequestModel.cs b/src/Api/KeyManagement/Models/Requests/MasterPasswordUnlockDataRequestModel.cs deleted file mode 100644 index ce7a2b343f..0000000000 --- a/src/Api/KeyManagement/Models/Requests/MasterPasswordUnlockDataRequestModel.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Bit.Core.KeyManagement.Models.Data; -using Bit.Core.Utilities; - -namespace Bit.Api.KeyManagement.Models.Requests; - -public class MasterPasswordUnlockDataRequestModel -{ - public required KdfRequestModel Kdf { get; init; } - [EncryptedString] public required string MasterKeyWrappedUserKey { get; init; } - [StringLength(256)] public required string Salt { get; init; } - - public MasterPasswordUnlockData ToData() - { - return new MasterPasswordUnlockData - { - Kdf = Kdf.ToData(), - MasterKeyWrappedUserKey = MasterKeyWrappedUserKey, - Salt = Salt - }; - } -} diff --git a/src/Core/Auth/Models/Api/Request/Accounts/RegisterFinishRequestModel.cs b/src/Core/Auth/Models/Api/Request/Accounts/RegisterFinishRequestModel.cs index bc84e9cf58..81b85387a6 100644 --- a/src/Core/Auth/Models/Api/Request/Accounts/RegisterFinishRequestModel.cs +++ b/src/Core/Auth/Models/Api/Request/Accounts/RegisterFinishRequestModel.cs @@ -1,6 +1,6 @@ using Bit.Core.Entities; using Bit.Core.Enums; -using Bit.Core.KeyManagement.Models.Data; +using Bit.Core.KeyManagement.Models.Api.Request; using Bit.Core.Utilities; namespace Bit.Core.Auth.Models.Api.Request.Accounts; @@ -21,8 +21,8 @@ public class RegisterFinishRequestModel : IValidatableObject public required string Email { get; set; } public string? EmailVerificationToken { get; set; } - public MasterPasswordAuthenticationData? MasterPasswordAuthentication { get; set; } - public MasterPasswordUnlockData? MasterPasswordUnlock { get; set; } + public MasterPasswordAuthenticationDataRequestModel? MasterPasswordAuthentication { get; set; } + public MasterPasswordUnlockDataRequestModel? MasterPasswordUnlock { get; set; } // PM-28143 - Remove property below (made optional during migration to MasterPasswordUnlockData) [StringLength(1000)] @@ -66,10 +66,10 @@ public class RegisterFinishRequestModel : IValidatableObject // 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. - MasterPasswordUnlockData.ThrowIfExistsAndNotMatchingAuthenticationData(MasterPasswordAuthentication, MasterPasswordUnlock); + MasterPasswordUnlockDataRequestModel.ThrowIfExistsAndNotMatchingAuthenticationData(MasterPasswordAuthentication, MasterPasswordUnlock); // PM-28143 - Remove line below - MasterPasswordAuthenticationData.ThrowIfExistsAndHashIsNotEqual(MasterPasswordAuthentication, MasterPasswordHash); + MasterPasswordAuthenticationDataRequestModel.ThrowIfExistsAndHashIsNotEqual(MasterPasswordAuthentication, MasterPasswordHash); var user = new User { @@ -122,10 +122,10 @@ public class RegisterFinishRequestModel : IValidatableObject public IEnumerable Validate(ValidationContext validationContext) { // PM-28143 - Remove line below - MasterPasswordUnlockData.ThrowIfExistsAndNotMatchingAuthenticationData(MasterPasswordAuthentication, MasterPasswordUnlock); + MasterPasswordUnlockDataRequestModel.ThrowIfExistsAndNotMatchingAuthenticationData(MasterPasswordAuthentication, MasterPasswordUnlock); // PM-28143 - Remove line below - MasterPasswordAuthenticationData.ThrowIfExistsAndHashIsNotEqual(MasterPasswordAuthentication, MasterPasswordHash); + MasterPasswordAuthenticationDataRequestModel.ThrowIfExistsAndHashIsNotEqual(MasterPasswordAuthentication, MasterPasswordHash); // PM-28143 - Remove line below var kdf = MasterPasswordUnlock?.Kdf.KdfType diff --git a/src/Api/KeyManagement/Models/Requests/MasterPasswordAuthenticationDataRequestModel.cs b/src/Core/KeyManagement/Models/Api/Request/MasterPasswordAuthenticationDataRequestModel.cs similarity index 56% rename from src/Api/KeyManagement/Models/Requests/MasterPasswordAuthenticationDataRequestModel.cs rename to src/Core/KeyManagement/Models/Api/Request/MasterPasswordAuthenticationDataRequestModel.cs index d65dc8fcb7..8a3283fc5b 100644 --- a/src/Api/KeyManagement/Models/Requests/MasterPasswordAuthenticationDataRequestModel.cs +++ b/src/Core/KeyManagement/Models/Api/Request/MasterPasswordAuthenticationDataRequestModel.cs @@ -1,7 +1,7 @@ using System.ComponentModel.DataAnnotations; using Bit.Core.KeyManagement.Models.Data; -namespace Bit.Api.KeyManagement.Models.Requests; +namespace Bit.Core.KeyManagement.Models.Api.Request; public class MasterPasswordAuthenticationDataRequestModel { @@ -18,4 +18,17 @@ public class MasterPasswordAuthenticationDataRequestModel Salt = Salt }; } + + public static void ThrowIfExistsAndHashIsNotEqual( + MasterPasswordAuthenticationDataRequestModel? authenticationData, + string? hash) + { + if (authenticationData != null && hash != null) + { + if (authenticationData.MasterPasswordAuthenticationHash != hash) + { + throw new Exception("Master password hash and hash are not equal."); + } + } + } } diff --git a/src/Core/KeyManagement/Models/Api/Request/MasterPasswordUnlockDataRequestModel.cs b/src/Core/KeyManagement/Models/Api/Request/MasterPasswordUnlockDataRequestModel.cs new file mode 100644 index 0000000000..a4baf01a19 --- /dev/null +++ b/src/Core/KeyManagement/Models/Api/Request/MasterPasswordUnlockDataRequestModel.cs @@ -0,0 +1,49 @@ +using System.ComponentModel.DataAnnotations; +using Bit.Core.KeyManagement.Models.Data; +using Bit.Core.Utilities; + +namespace Bit.Core.KeyManagement.Models.Api.Request; + +public class MasterPasswordUnlockDataRequestModel +{ + public required KdfRequestModel Kdf { get; init; } + [EncryptedString] public required string MasterKeyWrappedUserKey { get; init; } + [StringLength(256)] public required string Salt { get; init; } + + public MasterPasswordUnlockData ToData() + { + return new MasterPasswordUnlockData + { + Kdf = Kdf.ToData(), + MasterKeyWrappedUserKey = MasterKeyWrappedUserKey, + Salt = Salt + }; + } + + public static void ThrowIfExistsAndNotMatchingAuthenticationData( + MasterPasswordAuthenticationDataRequestModel? authenticationData, + MasterPasswordUnlockDataRequestModel? unlockData) + { + if (unlockData != null && authenticationData != null) + { + var matches = MatchesAuthenticationData( + unlockData, + authenticationData); + + if (!matches) + { + throw new Exception("KDF settings and salt must match between authentication and unlock data."); + } + } + } + + private static bool MatchesAuthenticationData( + MasterPasswordUnlockDataRequestModel unlockData, + MasterPasswordAuthenticationDataRequestModel authenticationData) + { + var kdfMatches = unlockData.Kdf.Equals(authenticationData.Kdf); + var saltMatches = unlockData.Salt == authenticationData.Salt; + + return kdfMatches && saltMatches; + } +} diff --git a/src/Api/KeyManagement/Models/Requests/KdfRequestModel.cs b/src/Core/KeyManagement/Models/Data/KdfRequestModel.cs similarity index 85% rename from src/Api/KeyManagement/Models/Requests/KdfRequestModel.cs rename to src/Core/KeyManagement/Models/Data/KdfRequestModel.cs index 904304a633..ae38490e00 100644 --- a/src/Api/KeyManagement/Models/Requests/KdfRequestModel.cs +++ b/src/Core/KeyManagement/Models/Data/KdfRequestModel.cs @@ -1,8 +1,7 @@ using System.ComponentModel.DataAnnotations; using Bit.Core.Enums; -using Bit.Core.KeyManagement.Models.Data; -namespace Bit.Api.KeyManagement.Models.Requests; +namespace Bit.Core.KeyManagement.Models.Data; public class KdfRequestModel { diff --git a/src/Core/KeyManagement/Models/Data/MasterPasswordAuthenticationData.cs b/src/Core/KeyManagement/Models/Data/MasterPasswordAuthenticationData.cs index be566aa4d0..25b864f0bd 100644 --- a/src/Core/KeyManagement/Models/Data/MasterPasswordAuthenticationData.cs +++ b/src/Core/KeyManagement/Models/Data/MasterPasswordAuthenticationData.cs @@ -3,6 +3,12 @@ using Bit.Core.Exceptions; namespace Bit.Core.KeyManagement.Models.Data; +/// +/// The data used for authentication of a master password. +/// +/// This data model does not have any validation, consider using MasterPasswordAuthenticationDataRequestModel +/// if validation is required. +/// public class MasterPasswordAuthenticationData { public required KdfSettings Kdf { get; init; } @@ -16,17 +22,4 @@ public class MasterPasswordAuthenticationData throw new BadRequestException("Invalid master password salt."); } } - - public static void ThrowIfExistsAndHashIsNotEqual( - MasterPasswordAuthenticationData? authenticationData, - string? hash) - { - if (authenticationData != null && hash != null) - { - if (authenticationData.MasterPasswordAuthenticationHash != hash) - { - throw new Exception("Master password hash and hash are not equal."); - } - } - } } diff --git a/src/Core/KeyManagement/Models/Data/MasterPasswordUnlockData.cs b/src/Core/KeyManagement/Models/Data/MasterPasswordUnlockData.cs index 609401b643..cb18ed2a78 100644 --- a/src/Core/KeyManagement/Models/Data/MasterPasswordUnlockData.cs +++ b/src/Core/KeyManagement/Models/Data/MasterPasswordUnlockData.cs @@ -16,31 +16,4 @@ public class MasterPasswordUnlockData throw new BadRequestException("Invalid master password salt."); } } - - public static void ThrowIfExistsAndNotMatchingAuthenticationData( - MasterPasswordAuthenticationData? authenticationData, - MasterPasswordUnlockData? unlockData) - { - if (unlockData != null && authenticationData != null) - { - var matches = MatchesAuthenticationData( - unlockData, - authenticationData); - - if (!matches) - { - throw new Exception("KDF settings and salt must match between authentication and unlock data."); - } - } - } - - private static bool MatchesAuthenticationData( - MasterPasswordUnlockData unlockData, - MasterPasswordAuthenticationData authenticationData) - { - var kdfMatches = unlockData.Kdf.Equals(authenticationData.Kdf); - var saltMatches = unlockData.Salt == authenticationData.Salt; - - return kdfMatches && saltMatches; - } } diff --git a/test/Identity.Test/Controllers/AccountsControllerTests.cs b/test/Identity.Test/Controllers/AccountsControllerTests.cs index e64e9b88fb..88899de80a 100644 --- a/test/Identity.Test/Controllers/AccountsControllerTests.cs +++ b/test/Identity.Test/Controllers/AccountsControllerTests.cs @@ -9,6 +9,7 @@ using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; +using Bit.Core.KeyManagement.Models.Api.Request; using Bit.Core.KeyManagement.Models.Data; using Bit.Core.Models.Data; using Bit.Core.Repositories; @@ -603,31 +604,28 @@ public class AccountsControllerTests : IDisposable string encryptedPrivateKey) { // Arrange: new-form model (MasterPasswordAuthenticationData + MasterPasswordUnlockData) + + var kdfData = new KdfRequestModel + { + KdfType = KdfType.Argon2id, + Iterations = AuthConstants.ARGON2_ITERATIONS.Default, + Memory = AuthConstants.ARGON2_MEMORY.Default, + Parallelism = AuthConstants.ARGON2_PARALLELISM.Default + }; + var newModel = new RegisterFinishRequestModel { Email = email, EmailVerificationToken = emailVerificationToken, - MasterPasswordAuthentication = new MasterPasswordAuthenticationData + MasterPasswordAuthentication = new MasterPasswordAuthenticationDataRequestModel { - Kdf = new KdfSettings - { - KdfType = KdfType.Argon2id, - Iterations = AuthConstants.ARGON2_ITERATIONS.Default, - Memory = AuthConstants.ARGON2_MEMORY.Default, - Parallelism = AuthConstants.ARGON2_PARALLELISM.Default - }, + Kdf = kdfData, MasterPasswordAuthenticationHash = masterPasswordHash, Salt = email // salt choice is not validated here during registration }, - MasterPasswordUnlock = new MasterPasswordUnlockData + MasterPasswordUnlock = new MasterPasswordUnlockDataRequestModel { - Kdf = new KdfSettings - { - KdfType = KdfType.Argon2id, - Iterations = AuthConstants.ARGON2_ITERATIONS.Default, - Memory = AuthConstants.ARGON2_MEMORY.Default, - Parallelism = AuthConstants.ARGON2_PARALLELISM.Default - }, + Kdf = kdfData, MasterKeyWrappedUserKey = masterKeyWrappedUserKey, Salt = email }, @@ -719,29 +717,29 @@ public class AccountsControllerTests : IDisposable string publicKey, string encryptedPrivateKey) { + var kdfData = new KdfRequestModel + { + KdfType = KdfType.Argon2id, + Iterations = AuthConstants.ARGON2_ITERATIONS.Default, + Memory = AuthConstants.ARGON2_MEMORY.Default, + Parallelism = AuthConstants.ARGON2_PARALLELISM.Default + }; + // Arrange: new-form model (MasterPasswordAuthenticationData + MasterPasswordUnlockData) var newModel = new RegisterFinishRequestModel { Email = email, OrgInviteToken = orgInviteToken, OrganizationUserId = organizationUserId, - MasterPasswordAuthentication = new MasterPasswordAuthenticationData + MasterPasswordAuthentication = new MasterPasswordAuthenticationDataRequestModel { - Kdf = new KdfSettings - { - KdfType = KdfType.PBKDF2_SHA256, - Iterations = AuthConstants.PBKDF2_ITERATIONS.Default - }, + Kdf = kdfData, MasterPasswordAuthenticationHash = masterPasswordHash, Salt = email }, - MasterPasswordUnlock = new MasterPasswordUnlockData + MasterPasswordUnlock = new MasterPasswordUnlockDataRequestModel { - Kdf = new KdfSettings - { - KdfType = KdfType.PBKDF2_SHA256, - Iterations = AuthConstants.PBKDF2_ITERATIONS.Default - }, + Kdf = kdfData, MasterKeyWrappedUserKey = masterKeyWrappedUserKey, Salt = email }, @@ -759,8 +757,10 @@ public class AccountsControllerTests : IDisposable OrgInviteToken = orgInviteToken, OrganizationUserId = organizationUserId, MasterPasswordHash = masterPasswordHash, - Kdf = KdfType.PBKDF2_SHA256, - KdfIterations = AuthConstants.PBKDF2_ITERATIONS.Default, + Kdf = kdfData.KdfType, + KdfIterations = kdfData.Iterations, + KdfMemory = kdfData.Memory, + KdfParallelism = kdfData.Parallelism, UserSymmetricKey = masterKeyWrappedUserKey, UserAsymmetricKeys = new KeysRequestModel { @@ -832,7 +832,7 @@ public class AccountsControllerTests : IDisposable string encryptedPrivateKey) { // Arrange: Provide only unlock-data KDF + key; leave root KDF fields null - var unlockKdf = new KdfSettings + var unlockKdf = new KdfRequestModel { KdfType = KdfType.PBKDF2_SHA256, Iterations = iterations @@ -842,14 +842,14 @@ public class AccountsControllerTests : IDisposable { Email = email, EmailVerificationToken = emailVerificationToken, - MasterPasswordAuthentication = new MasterPasswordAuthenticationData + MasterPasswordAuthentication = new MasterPasswordAuthenticationDataRequestModel { // present but not used by ToUser for KDF/Key - Kdf = new KdfSettings { KdfType = KdfType.Argon2id, Iterations = iterations }, + Kdf = unlockKdf, MasterPasswordAuthenticationHash = masterPasswordHash, Salt = email }, - MasterPasswordUnlock = new MasterPasswordUnlockData + MasterPasswordUnlock = new MasterPasswordUnlockDataRequestModel { Kdf = unlockKdf, MasterKeyWrappedUserKey = masterKeyWrappedUserKey, @@ -942,10 +942,10 @@ public class AccountsControllerTests : IDisposable { Email = email, EmailVerificationToken = emailVerificationToken, - MasterPasswordAuthentication = new MasterPasswordAuthenticationData + MasterPasswordAuthentication = new MasterPasswordAuthenticationDataRequestModel { // present but ToUser does not source KDF from here - Kdf = new KdfSettings { KdfType = KdfType.Argon2id, Iterations = iterations }, + Kdf = new KdfRequestModel { KdfType = KdfType.Argon2id, Iterations = iterations }, MasterPasswordAuthenticationHash = masterPasswordHash, Salt = email }, @@ -980,10 +980,10 @@ public class AccountsControllerTests : IDisposable { Email = email, EmailVerificationToken = emailVerificationToken, - MasterPasswordAuthentication = new MasterPasswordAuthenticationData + MasterPasswordAuthentication = new MasterPasswordAuthenticationDataRequestModel { // present but ToUser does not source iterations from here - Kdf = new KdfSettings { KdfType = kdfType, Iterations = AuthConstants.PBKDF2_ITERATIONS.Default }, + Kdf = new KdfRequestModel { KdfType = kdfType, Iterations = AuthConstants.PBKDF2_ITERATIONS.Default }, MasterPasswordAuthenticationHash = masterPasswordHash, Salt = email }, @@ -1018,9 +1018,9 @@ public class AccountsControllerTests : IDisposable { Email = email, EmailVerificationToken = emailVerificationToken, - MasterPasswordAuthentication = new MasterPasswordAuthenticationData + MasterPasswordAuthentication = new MasterPasswordAuthenticationDataRequestModel { - Kdf = new KdfSettings { KdfType = kdfType, Iterations = iterations }, + Kdf = new KdfRequestModel { KdfType = kdfType, Iterations = iterations }, MasterPasswordAuthenticationHash = masterPasswordHash, Salt = email }, diff --git a/test/IntegrationTestCommon/Factories/IdentityApplicationFactory.cs b/test/IntegrationTestCommon/Factories/IdentityApplicationFactory.cs index 4101553c01..830275af70 100644 --- a/test/IntegrationTestCommon/Factories/IdentityApplicationFactory.cs +++ b/test/IntegrationTestCommon/Factories/IdentityApplicationFactory.cs @@ -8,6 +8,7 @@ using Bit.Core; using Bit.Core.Auth.Models.Api.Request.Accounts; using Bit.Core.Entities; using Bit.Core.Enums; +using Bit.Core.KeyManagement.Models.Api.Request; using Bit.Core.KeyManagement.Models.Data; using Bit.Core.Services; using Bit.Identity; @@ -226,7 +227,7 @@ public class IdentityApplicationFactory : WebApplicationFactoryBase effectiveParallelism = AuthConstants.ARGON2_PARALLELISM.Default; } - var alignedKdf = new KdfSettings + var alignedKdf = new KdfRequestModel { KdfType = effectiveKdfType, Iterations = effectiveIterations, @@ -241,7 +242,7 @@ public class IdentityApplicationFactory : WebApplicationFactoryBase var masterKeyWrappedUserKey = !string.IsNullOrWhiteSpace(unlock.MasterKeyWrappedUserKey) ? unlock.MasterKeyWrappedUserKey : (string.IsNullOrWhiteSpace(requestModel.UserSymmetricKey) ? "user_symmetric_key" : requestModel.UserSymmetricKey); - requestModel.MasterPasswordUnlock = new MasterPasswordUnlockData + requestModel.MasterPasswordUnlock = new MasterPasswordUnlockDataRequestModel { Kdf = alignedKdf, MasterKeyWrappedUserKey = masterKeyWrappedUserKey, @@ -254,7 +255,7 @@ public class IdentityApplicationFactory : WebApplicationFactoryBase // Ensure registration uses the same hash the tests will provide at login. // PM-28143 - When MasterPasswordAuthenticationData is the only source of the auth hash, // stop overriding it from MasterPasswordHash and delete this whole reassignment block. - requestModel.MasterPasswordAuthentication = new MasterPasswordAuthenticationData + requestModel.MasterPasswordAuthentication = new MasterPasswordAuthenticationDataRequestModel { Kdf = alignedKdf, MasterPasswordAuthenticationHash = requestModel.MasterPasswordHash,