mirror of
https://github.com/bitwarden/server
synced 2026-01-05 01:53:17 +00:00
[PM-3565] Enforce higher minimum KDF (#3304)
Extract KDF logic into a new Range class. Increase minimum iterations for PBKDF.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using System.Security.Claims;
|
||||
using Bit.Api.Auth.Models.Request.Accounts;
|
||||
using Bit.Api.Controllers;
|
||||
using Bit.Core;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Auth.Models.Api.Request.Accounts;
|
||||
@@ -111,14 +112,14 @@ public class AccountsControllerTests : IDisposable
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PostPrelogin_WhenUserDoesNotExist_ShouldDefaultToSha256And100000Iterations()
|
||||
public async Task PostPrelogin_WhenUserDoesNotExist_ShouldDefaultToPBKDF()
|
||||
{
|
||||
_userRepository.GetKdfInformationByEmailAsync(Arg.Any<string>()).Returns(Task.FromResult((UserKdfInformation)null));
|
||||
|
||||
var response = await _sut.PostPrelogin(new PreloginRequestModel { Email = "user@example.com" });
|
||||
|
||||
Assert.Equal(KdfType.PBKDF2_SHA256, response.Kdf);
|
||||
Assert.Equal(100000, response.KdfIterations);
|
||||
Assert.Equal(AuthConstants.PBKDF2_ITERATIONS.Default, response.KdfIterations);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -9,11 +9,11 @@ public class KdfRequestModelTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(KdfType.PBKDF2_SHA256, 1_000_000, null, null)] // Somewhere in the middle
|
||||
[InlineData(KdfType.PBKDF2_SHA256, 5000, null, null)] // Right on the lower boundary
|
||||
[InlineData(KdfType.PBKDF2_SHA256, 600_000, null, null)] // Right on the lower boundary
|
||||
[InlineData(KdfType.PBKDF2_SHA256, 2_000_000, null, null)] // Right on the upper boundary
|
||||
[InlineData(KdfType.Argon2id, 10, 500, 8)] // Somewhere in the middle
|
||||
[InlineData(KdfType.Argon2id, 1, 15, 1)] // Right on the lower boundary
|
||||
[InlineData(KdfType.Argon2id, 5000, 1024, 16)] // Right on the upper boundary
|
||||
[InlineData(KdfType.Argon2id, 5, 500, 8)] // Somewhere in the middle
|
||||
[InlineData(KdfType.Argon2id, 2, 15, 1)] // Right on the lower boundary
|
||||
[InlineData(KdfType.Argon2id, 10, 1024, 16)] // Right on the upper boundary
|
||||
public void Validate_IsValid(KdfType kdfType, int? kdfIterations, int? kdfMemory, int? kdfParallelism)
|
||||
{
|
||||
var model = new KdfRequestModel
|
||||
@@ -32,7 +32,7 @@ public class KdfRequestModelTests
|
||||
|
||||
[Theory]
|
||||
[InlineData(null, 350_000, null, null, 1)] // Although KdfType is nullable, it's marked as [Required]
|
||||
[InlineData(KdfType.PBKDF2_SHA256, 1000, null, null, 1)] // Too few iterations
|
||||
[InlineData(KdfType.PBKDF2_SHA256, 500_000, null, null, 1)] // Too few iterations
|
||||
[InlineData(KdfType.PBKDF2_SHA256, 2_000_001, null, null, 1)] // Too many iterations
|
||||
[InlineData(KdfType.Argon2id, 0, 30, 8, 1)] // Iterations must be greater than 0
|
||||
[InlineData(KdfType.Argon2id, 10, 14, 8, 1)] // Too little memory
|
||||
|
||||
69
test/Core.Test/ConstantsTests.cs
Normal file
69
test/Core.Test/ConstantsTests.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test;
|
||||
|
||||
public class ConstantsTests
|
||||
{
|
||||
public class RangeConstantTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_WithValidValues_SetsProperties()
|
||||
{
|
||||
// Arrange
|
||||
const int min = 0;
|
||||
const int max = 10;
|
||||
const int defaultValue = 5;
|
||||
|
||||
// Act
|
||||
var rangeConstant = new RangeConstant(min, max, defaultValue);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(min, rangeConstant.Min);
|
||||
Assert.Equal(max, rangeConstant.Max);
|
||||
Assert.Equal(defaultValue, rangeConstant.Default);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithInvalidValues_ThrowsArgumentOutOfRangeException()
|
||||
{
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => new RangeConstant(10, 0, 5));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithDefaultValueOutsideRange_ThrowsArgumentOutOfRangeException()
|
||||
{
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => new RangeConstant(0, 10, 20));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(5)]
|
||||
[InlineData(0)]
|
||||
[InlineData(10)]
|
||||
public void InsideRange_WithValidValues_ReturnsTrue(int number)
|
||||
{
|
||||
// Arrange
|
||||
var rangeConstant = new RangeConstant(0, 10, 5);
|
||||
|
||||
// Act
|
||||
bool result = rangeConstant.InsideRange(number);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(-1)]
|
||||
[InlineData(11)]
|
||||
public void InsideRange_WithInvalidValues_ReturnsFalse(int number)
|
||||
{
|
||||
// Arrange
|
||||
var rangeConstant = new RangeConstant(0, 10, 5);
|
||||
|
||||
// Act
|
||||
bool result = rangeConstant.InsideRange(number);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Bit.Core.Auth.Models.Api.Request.Accounts;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Auth.Models.Api.Request.Accounts;
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Auth.Services;
|
||||
using Bit.Core.Entities;
|
||||
@@ -64,14 +65,14 @@ public class AccountsControllerTests : IDisposable
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PostPrelogin_WhenUserDoesNotExist_ShouldDefaultToSha256And100000Iterations()
|
||||
public async Task PostPrelogin_WhenUserDoesNotExist_ShouldDefaultToPBKDF()
|
||||
{
|
||||
_userRepository.GetKdfInformationByEmailAsync(Arg.Any<string>()).Returns(Task.FromResult<UserKdfInformation>(null!));
|
||||
|
||||
var response = await _sut.PostPrelogin(new PreloginRequestModel { Email = "user@example.com" });
|
||||
|
||||
Assert.Equal(KdfType.PBKDF2_SHA256, response.Kdf);
|
||||
Assert.Equal(100000, response.KdfIterations);
|
||||
Assert.Equal(AuthConstants.PBKDF2_ITERATIONS.Default, response.KdfIterations);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user