Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/Core/KeyManagement/Kdf/KdfConstants.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
ο»Ώnamespace Bit.Core.KeyManagement.Kdf;

/// <summary>
/// These defaults are used by the prelogin enumeration-protection pool in
/// Identity/AccountsController. Changes here will affect prelogin responses
/// for non-existent users. See AccountsControllerTests for coverage.
/// </summary>
public static class KdfConstants
{
public static readonly RangeConstant PBKDF2_ITERATIONS = new(600_000, 2_000_000, 600_000);

public static readonly RangeConstant ARGON2_ITERATIONS = new(2, 10, 3);
public static readonly RangeConstant ARGON2_MEMORY = new(15, 1024, 64);
public static readonly RangeConstant ARGON2_ITERATIONS = new(2, 10, 6);
public static readonly RangeConstant ARGON2_MEMORY = new(15, 1024, 32);
public static readonly RangeConstant ARGON2_PARALLELISM = new(1, 16, 4);
}

Expand Down
10 changes: 9 additions & 1 deletion src/Identity/Controllers/AccountsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public class AccountsController : Controller
private readonly IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable> _registrationEmailVerificationTokenDataFactory;

private readonly byte[]? _defaultKdfHmacKey = null;
private static readonly List<UserKdfInformation> _defaultKdfResults =
internal static readonly List<UserKdfInformation> _defaultKdfResults =
[
// The first result (index 0) should always return the "normal" default.
new()
Expand All @@ -60,6 +60,14 @@ public class AccountsController : Controller
KdfIterations = 5_000,
},
new()
{
Kdf = KdfType.Argon2id,
KdfIterations = 3,
KdfMemory = 64,
KdfParallelism = 4,
},
// Mobile-friendly Argon2id default, tuned for iOS memory constraints.
new()
{
Kdf = KdfType.Argon2id,
KdfIterations = KdfConstants.ARGON2_ITERATIONS.Default,
Expand Down
42 changes: 37 additions & 5 deletions test/Identity.Test/Controllers/AccountsControllerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -207,11 +207,7 @@ public async Task PostPasswordPrelogin_WhenUserDoesNotExistAndDefaultKdfHmacKeyI

_userRepository.GetKdfInformationByEmailAsync(Arg.Any<string>()).Returns(Task.FromResult<UserKdfInformation?>(null));

var fieldInfo = typeof(AccountsController).GetField("_defaultKdfResults", BindingFlags.NonPublic | BindingFlags.Static);
if (fieldInfo == null)
throw new InvalidOperationException("Field '_defaultKdfResults' not found.");

var defaultKdfResults = (List<UserKdfInformation>)fieldInfo.GetValue(null)!;
var defaultKdfResults = AccountsController._defaultKdfResults;

var expectedIndex = GetExpectedKdfIndex(email, defaultKey, defaultKdfResults);
var expectedKdf = defaultKdfResults[expectedIndex];
Expand Down Expand Up @@ -1249,6 +1245,42 @@ public void RegisterFinishRequestModel_Validate_Throws_WhenAuthHashAndRootHashMi
Assert.Contains("MasterPasswordHash", mismatchResult.MemberNames);
}

[Fact]
public void DefaultKdfResults_ShouldContainExpectedEntries()
{
var pool = AccountsController._defaultKdfResults;

Assert.Equal(6, pool.Count);

// Index 0 β€” PBKDF2 600k (normal default)
Assert.Equal(KdfType.PBKDF2_SHA256, pool[0].Kdf);
Assert.Equal(600_000, pool[0].KdfIterations);

// Index 1 β€” PBKDF2 600k (weight duplicate)
Assert.Equal(KdfType.PBKDF2_SHA256, pool[1].Kdf);
Assert.Equal(600_000, pool[1].KdfIterations);

// Index 2 β€” PBKDF2 100k (historical)
Assert.Equal(KdfType.PBKDF2_SHA256, pool[2].Kdf);
Assert.Equal(100_000, pool[2].KdfIterations);

// Index 3 β€” PBKDF2 5k (historical)
Assert.Equal(KdfType.PBKDF2_SHA256, pool[3].Kdf);
Assert.Equal(5_000, pool[3].KdfIterations);

// Index 4 β€” Argon2id 3 iterations, 64MB, 4 parallelism (historical)
Assert.Equal(KdfType.Argon2id, pool[4].Kdf);
Assert.Equal(3, pool[4].KdfIterations);
Assert.Equal(64, pool[4].KdfMemory);
Assert.Equal(4, pool[4].KdfParallelism);

// Index 5 β€” Argon2id 6 iterations, 32MB, 4 parallelism
Assert.Equal(KdfType.Argon2id, pool[5].Kdf);
Assert.Equal(6, pool[5].KdfIterations);
Assert.Equal(32, pool[5].KdfMemory);
Assert.Equal(4, pool[5].KdfParallelism);
}

private void SetDefaultKdfHmacKey(byte[]? newKey)
{
var fieldInfo = typeof(AccountsController).GetField("_defaultKdfHmacKey", BindingFlags.NonPublic | BindingFlags.Instance);
Expand Down
Loading