Skip to content

Commit e76a1e5

Browse files
committed
Argon2 Tests
1 parent 990a796 commit e76a1e5

4 files changed

Lines changed: 271 additions & 23 deletions

File tree

‎src/security/CodeBeam.UltimateAuth.Security.Argon2/Argon2Options.cs‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
public sealed class Argon2Options
44
{
55
// OWASP recommended baseline
6-
public int MemorySizeKb { get; init; } = 64 * 1024; // 64 MB
7-
public int Iterations { get; init; } = 3;
8-
public int Parallelism { get; init; } = Environment.ProcessorCount;
6+
public int MemorySizeKb { get; set; } = 64 * 1024; // 64 MB
7+
public int Iterations { get; set; } = 3;
8+
public int Parallelism { get; set; } = Environment.ProcessorCount;
99

10-
public int SaltSize { get; init; } = 16;
11-
public int HashSize { get; init; } = 32;
10+
public int SaltSize { get; set; } = 16;
11+
public int HashSize { get; set; } = 32;
1212
}

‎src/security/CodeBeam.UltimateAuth.Security.Argon2/Argon2PasswordHasher.cs‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,11 @@ public bool Verify(PasswordHash hash, string secret)
4949
!int.TryParse(parts[2], out var parallelism))
5050
return false;
5151

52-
var salt = Convert.FromBase64String(parts[3]);
53-
var expectedHash = Convert.FromBase64String(parts[4]);
54-
5552
try
5653
{
54+
var salt = Convert.FromBase64String(parts[3]);
55+
var expectedHash = Convert.FromBase64String(parts[4]);
56+
5757
var argon2 = new Argon2id(Encoding.UTF8.GetBytes(secret))
5858
{
5959
Salt = salt,

‎tests/CodeBeam.UltimateAuth.Tests.Unit/Security/Argon2PasswordHasherTest.cs‎

Lines changed: 175 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,6 @@ namespace CodeBeam.UltimateAuth.Tests.Unit;
99

1010
public class Argon2PasswordHasherTests
1111
{
12-
private Argon2PasswordHasher CreateHasher()
13-
{
14-
var options = Options.Create(new Argon2Options());
15-
return new Argon2PasswordHasher(options);
16-
}
17-
1812
[Fact]
1913
public void Hash_Should_Return_Valid_PasswordHash()
2014
{
@@ -34,9 +28,7 @@ public void Hash_Should_Return_Valid_PasswordHash()
3428
public void Verify_Should_Return_True_For_Correct_Password()
3529
{
3630
var hasher = CreateHasher();
37-
3831
var hash = hasher.Hash("password123");
39-
4032
var result = hasher.Verify(hash, "password123");
4133

4234
result.Should().BeTrue();
@@ -46,9 +38,7 @@ public void Verify_Should_Return_True_For_Correct_Password()
4638
public void Verify_Should_Return_False_For_Wrong_Password()
4739
{
4840
var hasher = CreateHasher();
49-
5041
var hash = hasher.Hash("password123");
51-
5242
var result = hasher.Verify(hash, "wrong");
5343

5444
result.Should().BeFalse();
@@ -58,9 +48,7 @@ public void Verify_Should_Return_False_For_Wrong_Password()
5848
public void Verify_Should_Return_False_For_Invalid_Format()
5949
{
6050
var hasher = CreateHasher();
61-
6251
var invalid = PasswordHash.Create(PasswordAlgorithms.Argon2, "invalid");
63-
6452
var result = hasher.Verify(invalid, "password");
6553

6654
result.Should().BeFalse();
@@ -89,7 +77,6 @@ public void Hash_Should_Produce_Different_Hashes_For_Same_Password()
8977
public void Verify_Should_Use_Embedded_Salt_And_Parameters()
9078
{
9179
var hasher = CreateHasher();
92-
9380
var hash = hasher.Hash("password123");
9481

9582
var differentOptions = Options.Create(new Argon2Options
@@ -112,7 +99,6 @@ public void Verify_Should_Use_Embedded_Salt_And_Parameters()
11299
public void NeedsRehash_Should_Return_True_When_Parameters_Changed()
113100
{
114101
var hasher = CreateHasher();
115-
116102
var hash = hasher.Hash("password123");
117103

118104
var differentOptions = Options.Create(new Argon2Options
@@ -135,11 +121,185 @@ public void NeedsRehash_Should_Return_True_When_Parameters_Changed()
135121
public void NeedsRehash_Should_Return_False_When_Parameters_Match()
136122
{
137123
var hasher = CreateHasher();
124+
var hash = hasher.Hash("password123");
125+
var result = hasher.NeedsRehash(hash);
126+
127+
result.Should().BeFalse();
128+
}
129+
130+
[Theory]
131+
[InlineData("")]
132+
[InlineData(null)]
133+
public void Hash_Should_Throw_When_Password_Is_Null_Or_Empty(
134+
string? password)
135+
{
136+
var hasher = CreateHasher();
137+
var act = () => hasher.Hash(password!);
138138

139+
act.Should().Throw<UAuthValidationException>();
140+
}
141+
142+
[Theory]
143+
[InlineData("")]
144+
[InlineData(" ")]
145+
[InlineData(" ")]
146+
[InlineData(null)]
147+
public void Verify_Should_Return_False_When_Secret_Is_Invalid(
148+
string? secret)
149+
{
150+
var hasher = CreateHasher();
139151
var hash = hasher.Hash("password123");
152+
var result = hasher.Verify(hash, secret!);
140153

141-
var result = hasher.NeedsRehash(hash);
154+
result.Should().BeFalse();
155+
}
156+
157+
[Fact]
158+
public void Verify_Should_Return_False_When_Algorithm_Is_Not_Argon2()
159+
{
160+
var hasher = CreateHasher();
161+
162+
var hash = PasswordHash.Create("different-algorithm", "3.65536.1.c2FsdA==.aGFzaA==");
163+
164+
var result = hasher.Verify(hash, "password123");
142165

143166
result.Should().BeFalse();
144167
}
168+
169+
[Theory]
170+
[InlineData("invalid.65536.1.c2FsdA==.aGFzaA==")]
171+
[InlineData("3.invalid.1.c2FsdA==.aGFzaA==")]
172+
[InlineData("3.65536.invalid.c2FsdA==.aGFzaA==")]
173+
public void Verify_Should_Return_False_When_Parameters_Are_Invalid(string encoded)
174+
{
175+
var hasher = CreateHasher();
176+
var hash = PasswordHash.Create(PasswordAlgorithms.Argon2, encoded);
177+
var result = hasher.Verify(hash, "password123");
178+
179+
result.Should().BeFalse();
180+
}
181+
182+
[Theory]
183+
[InlineData("3.65536.1.NOT_BASE64.aGFzaA==")]
184+
[InlineData("3.65536.1.c2FsdA==.NOT_BASE64")]
185+
public void Verify_Should_Return_False_When_Hash_Contains_Invalid_Base64(
186+
string encoded)
187+
{
188+
var hasher = CreateHasher();
189+
var hash = PasswordHash.Create(PasswordAlgorithms.Argon2, encoded);
190+
var result = hasher.Verify(hash, "password123");
191+
192+
result.Should().BeFalse();
193+
}
194+
195+
[Fact]
196+
public void NeedsRehash_Should_Return_True_When_Algorithm_Is_Not_Argon2()
197+
{
198+
var hasher = CreateHasher();
199+
var hash = PasswordHash.Create("different-algorithm", "anything");
200+
201+
hasher.NeedsRehash(hash).Should().BeTrue();
202+
}
203+
204+
[Theory]
205+
[InlineData("invalid")]
206+
[InlineData("1.2.3")]
207+
[InlineData("1.2.3.4")]
208+
[InlineData("1.2.3.4.5.6")]
209+
public void NeedsRehash_Should_Return_True_When_Format_Is_Invalid(
210+
string encoded)
211+
{
212+
var hasher = CreateHasher();
213+
214+
var hash = PasswordHash.Create(
215+
PasswordAlgorithms.Argon2,
216+
encoded);
217+
218+
hasher.NeedsRehash(hash)
219+
.Should()
220+
.BeTrue();
221+
}
222+
223+
[Theory]
224+
[InlineData("invalid.65536.1.c2FsdA==.aGFzaA==")]
225+
[InlineData("3.invalid.1.c2FsdA==.aGFzaA==")]
226+
[InlineData("3.65536.invalid.c2FsdA==.aGFzaA==")]
227+
public void NeedsRehash_Should_Return_True_When_Parameters_Are_Invalid(
228+
string encoded)
229+
{
230+
var hasher = CreateHasher();
231+
232+
var hash = PasswordHash.Create(
233+
PasswordAlgorithms.Argon2,
234+
encoded);
235+
236+
hasher.NeedsRehash(hash)
237+
.Should()
238+
.BeTrue();
239+
}
240+
241+
[Fact]
242+
public void NeedsRehash_Should_Return_True_When_Iterations_Changed()
243+
{
244+
var hasher = CreateHasher();
245+
var hash = hasher.Hash("password123");
246+
247+
var differentHasher = CreateHasher(new Argon2Options
248+
{
249+
Iterations = 4
250+
});
251+
252+
differentHasher.NeedsRehash(hash)
253+
.Should()
254+
.BeTrue();
255+
}
256+
257+
[Fact]
258+
public void NeedsRehash_Should_Return_True_When_Memory_Size_Changed()
259+
{
260+
var hasher = CreateHasher();
261+
var hash = hasher.Hash("password123");
262+
263+
var differentHasher = CreateHasher(new Argon2Options
264+
{
265+
MemorySizeKb = 32 * 1024
266+
});
267+
268+
differentHasher.NeedsRehash(hash)
269+
.Should()
270+
.BeTrue();
271+
}
272+
273+
[Fact]
274+
public void NeedsRehash_Should_Return_True_When_Parallelism_Changed()
275+
{
276+
var hasher = CreateHasher();
277+
var hash = hasher.Hash("password123");
278+
279+
var differentParallelism =
280+
new Argon2Options().Parallelism == 1
281+
? 2
282+
: 1;
283+
284+
var differentHasher = CreateHasher(new Argon2Options
285+
{
286+
Parallelism = differentParallelism
287+
});
288+
289+
differentHasher.NeedsRehash(hash)
290+
.Should()
291+
.BeTrue();
292+
}
293+
294+
private static Argon2PasswordHasher CreateHasher()
295+
{
296+
return CreateHasher(new Argon2Options());
297+
}
298+
299+
private static Argon2PasswordHasher CreateHasher(
300+
Argon2Options options)
301+
{
302+
return new Argon2PasswordHasher(
303+
Options.Create(options));
304+
}
145305
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
using CodeBeam.UltimateAuth.Core.Abstractions;
2+
using CodeBeam.UltimateAuth.Security.Argon2;
3+
using FluentAssertions;
4+
using Microsoft.Extensions.DependencyInjection;
5+
using Microsoft.Extensions.Options;
6+
7+
namespace CodeBeam.UltimateAuth.Tests.Unit;
8+
9+
public sealed class Argon2ServiceCollectionExtensionsTests
10+
{
11+
[Fact]
12+
public void AddUltimateAuthArgon2_ShouldRegisterPasswordHasher()
13+
{
14+
var services = new ServiceCollection();
15+
services.AddUltimateAuthArgon2();
16+
17+
using var provider = services.BuildServiceProvider();
18+
19+
var hasher = provider.GetRequiredService<IUAuthPasswordHasher>();
20+
21+
hasher.Should().BeOfType<Argon2PasswordHasher>();
22+
}
23+
24+
[Fact]
25+
public void AddUltimateAuthArgon2_WithoutConfiguration_ShouldRegisterDefaultOptions()
26+
{
27+
var services = new ServiceCollection();
28+
29+
services.AddUltimateAuthArgon2();
30+
31+
using var provider = services.BuildServiceProvider();
32+
33+
var options = provider.GetRequiredService<IOptions<Argon2Options>>().Value;
34+
35+
options.Should().NotBeNull();
36+
options.Iterations.Should().Be(3);
37+
options.MemorySizeKb.Should().Be(64 * 1024);
38+
options.SaltSize.Should().Be(16);
39+
options.HashSize.Should().Be(32);
40+
}
41+
42+
[Fact]
43+
public void AddUltimateAuthArgon2_WithConfiguration_ShouldApplyConfiguration()
44+
{
45+
var services = new ServiceCollection();
46+
47+
services.AddUltimateAuthArgon2(options =>
48+
{
49+
options.Iterations = 7;
50+
options.MemorySizeKb = 32768;
51+
options.Parallelism = 2;
52+
options.SaltSize = 24;
53+
options.HashSize = 48;
54+
});
55+
56+
using var provider = services.BuildServiceProvider();
57+
var options = provider.GetRequiredService<IOptions<Argon2Options>>().Value;
58+
59+
options.Iterations.Should().Be(7);
60+
options.MemorySizeKb.Should().Be(32768);
61+
options.Parallelism.Should().Be(2);
62+
options.SaltSize.Should().Be(24);
63+
options.HashSize.Should().Be(48);
64+
}
65+
66+
[Fact]
67+
public void AddUltimateAuthArgon2_ConfiguredParameters_ShouldBeUsedByHasher()
68+
{
69+
var services = new ServiceCollection();
70+
71+
services.AddUltimateAuthArgon2(options =>
72+
{
73+
options.Iterations = 4;
74+
options.MemorySizeKb = 16384;
75+
options.Parallelism = 2;
76+
});
77+
78+
using var provider = services.BuildServiceProvider();
79+
var hasher = provider.GetRequiredService<IUAuthPasswordHasher>();
80+
var hash = hasher.Hash("Password123!");
81+
var parts = hash.Hash.Split('.');
82+
83+
parts.Should().HaveCount(5);
84+
parts[0].Should().Be("4");
85+
parts[1].Should().Be("16384");
86+
parts[2].Should().Be("2");
87+
}
88+
}

0 commit comments

Comments
 (0)