Skip to content

Commit 6a1a675

Browse files
committed
Resolve #87 - Add unit tests for credential verifier & correct implementation
1 parent fc99cad commit 6a1a675

3 files changed

Lines changed: 242 additions & 7 deletions

File tree

CSF.Security/PBKDF2CredentialVerifier.cs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
// THE SOFTWARE.
2626
using System;
2727
using System.Security.Cryptography;
28+
using System.Linq;
2829

2930
namespace CSF.Security
3031
{
@@ -92,15 +93,12 @@ public virtual bool Verify(TEnteredCredentials enteredCredentials, TStoredCreden
9293
throw new ArgumentNullException(nameof(storedCredentials));
9394
}
9495

95-
var salt = storedCredentials.GetSaltAsByteArray();
96+
var storedSalt = storedCredentials.GetSaltAsByteArray();
9697
var storedKey = storedCredentials.GetKeyAsByteArray();
98+
var enteredPassword = enteredCredentials.GetPasswordAsByteArray();
9799

98-
var password = enteredCredentials.GetPasswordAsByteArray();
99-
100-
var utility = GetHashingUtility(password, salt);
101-
var generatedKey = utility.GetBytes(storedKey.Length);
102-
103-
return generatedKey == storedKey;
100+
var generatedKey = CreateKey(enteredPassword, storedSalt, storedKey.Length);
101+
return Enumerable.SequenceEqual(generatedKey, storedKey);
104102
}
105103

106104
/// <summary>
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
//
2+
// TestPBKDF2CredentialVerifier.cs
3+
//
4+
// Author:
5+
// Craig Fowler <craig@craigfowler.me.uk>
6+
//
7+
// Copyright (c) 2016 Craig Fowler
8+
//
9+
// Permission is hereby granted, free of charge, to any person obtaining a copy
10+
// of this software and associated documentation files (the "Software"), to deal
11+
// in the Software without restriction, including without limitation the rights
12+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
// copies of the Software, and to permit persons to whom the Software is
14+
// furnished to do so, subject to the following conditions:
15+
//
16+
// The above copyright notice and this permission notice shall be included in
17+
// all copies or substantial portions of the Software.
18+
//
19+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
// THE SOFTWARE.
26+
using System;
27+
using CSF.Security;
28+
using NUnit.Framework;
29+
using Moq;
30+
31+
namespace Test.CSF.Security
32+
{
33+
[TestFixture]
34+
public class TestPBKDF2CredentialVerifier
35+
{
36+
#region constants
37+
38+
private readonly byte[]
39+
PASSWORD_ONE = new byte[] { 10, 20, 30, 40, 50 },
40+
PASSWORD_TWO = new byte[] { 20, 10, 30, 40, 50 },
41+
SALT_ONE = new byte[] { 11, 21, 31, 41, 51, 61, 71, 81 },
42+
SALT_TWO = new byte[] { 12, 22, 32, 42, 52, 62, 72, 82 },
43+
PASSWORD_ONE_SALT_ONE_ITER_100_KEY = new byte[] { 58, 57, 26, 90, 209, 115, 230, 94, 207, 30 };
44+
45+
#endregion
46+
47+
#region tests
48+
49+
[Test]
50+
public void CreateKey_creates_same_key_with_same_password_and_salt_across_two_instances()
51+
{
52+
// Act
53+
var resultOne = CreateKeyInNewInstance(SALT_ONE, PASSWORD_ONE, 10, 100);
54+
var resultTwo = CreateKeyInNewInstance(SALT_ONE, PASSWORD_ONE, 10, 100);
55+
56+
// Assert
57+
CollectionAssert.AreEqual(resultOne, resultTwo);
58+
}
59+
60+
[Test]
61+
public void CreateKey_creates_different_key_with_different_password_and_same_salt()
62+
{
63+
// Act
64+
var resultOne = CreateKeyInNewInstance(SALT_ONE, PASSWORD_ONE, 10, 100);
65+
var resultTwo = CreateKeyInNewInstance(SALT_ONE, PASSWORD_TWO, 10, 100);
66+
67+
// Assert
68+
Assert.IsFalse(Object.Equals(resultOne, resultTwo), "Results are not equal");
69+
}
70+
71+
[Test]
72+
public void CreateKey_creates_different_key_with_same_password_and_different_salt()
73+
{
74+
// Act
75+
var resultOne = CreateKeyInNewInstance(SALT_ONE, PASSWORD_ONE, 10, 100);
76+
var resultTwo = CreateKeyInNewInstance(SALT_TWO, PASSWORD_ONE, 10, 100);
77+
78+
// Assert
79+
Assert.IsFalse(Object.Equals(resultOne, resultTwo), "Results are not equal");
80+
}
81+
82+
[Test]
83+
public void CreateKey_creates_same_key_with_same_password_and_salt_but_different_iteration_counts()
84+
{
85+
// Act
86+
var resultOne = CreateKeyInNewInstance(SALT_ONE, PASSWORD_ONE, 10, 100);
87+
var resultTwo = CreateKeyInNewInstance(SALT_ONE, PASSWORD_ONE, 10, 105);
88+
89+
// Assert
90+
Assert.IsFalse(Object.Equals(resultOne, resultTwo), "Results are not equal");
91+
}
92+
93+
[TestCase(10)]
94+
[TestCase(20)]
95+
public void CreateKey_creates_key_of_correct_byte_length(int keyLength)
96+
{
97+
// Act
98+
var result = CreateKeyInNewInstance(SALT_ONE, PASSWORD_ONE, keyLength, 100);
99+
100+
// Assert
101+
Assert.AreEqual(keyLength, result.Length);
102+
}
103+
104+
[TestCase(10)]
105+
[TestCase(20)]
106+
public void CreateRandomSalt_creates_salt_of_correct_byte_length(int saltLength)
107+
{
108+
// Arrange
109+
var sut = new StubVerifier(100);
110+
111+
// Act
112+
var result = sut.CreateRandomSalt(saltLength);
113+
114+
// Assert
115+
Assert.AreEqual(saltLength, result.Length);
116+
}
117+
118+
[Test]
119+
public void Verify_returns_true_for_correct_password_salt_and_iteration_count()
120+
{
121+
// Arrange
122+
var stored = new Mock<StubStoredCredentials>();
123+
stored.Setup(x => x.GetKeyAsByteArray()).Returns(PASSWORD_ONE_SALT_ONE_ITER_100_KEY);
124+
stored.Setup(x => x.GetSaltAsByteArray()).Returns(SALT_ONE);
125+
126+
var entered = new Mock<StubEnteredCredentials>();
127+
entered.Setup(x => x.GetPasswordAsByteArray()).Returns(PASSWORD_ONE);
128+
129+
var sut = new StubVerifier(100);
130+
131+
// Act
132+
var result = sut.Verify(entered.Object, stored.Object);
133+
134+
// Assert
135+
Assert.IsTrue(result);
136+
}
137+
138+
[Test]
139+
public void Verify_returns_false_for_correct_password_and_iteration_count_but_wrong_salt()
140+
{
141+
// Arrange
142+
var stored = new Mock<StubStoredCredentials>();
143+
stored.Setup(x => x.GetKeyAsByteArray()).Returns(PASSWORD_ONE_SALT_ONE_ITER_100_KEY);
144+
stored.Setup(x => x.GetSaltAsByteArray()).Returns(SALT_TWO);
145+
146+
var entered = new Mock<StubEnteredCredentials>();
147+
entered.Setup(x => x.GetPasswordAsByteArray()).Returns(PASSWORD_ONE);
148+
149+
var sut = new StubVerifier(100);
150+
151+
// Act
152+
var result = sut.Verify(entered.Object, stored.Object);
153+
154+
// Assert
155+
Assert.IsFalse(result);
156+
}
157+
158+
[Test]
159+
public void Verify_returns_false_for_correct_salt_and_iteration_count_but_wrong_password()
160+
{
161+
// Arrange
162+
var stored = new Mock<StubStoredCredentials>();
163+
stored.Setup(x => x.GetKeyAsByteArray()).Returns(PASSWORD_ONE_SALT_ONE_ITER_100_KEY);
164+
stored.Setup(x => x.GetSaltAsByteArray()).Returns(SALT_ONE);
165+
166+
var entered = new Mock<StubEnteredCredentials>();
167+
entered.Setup(x => x.GetPasswordAsByteArray()).Returns(PASSWORD_TWO);
168+
169+
var sut = new StubVerifier(100);
170+
171+
// Act
172+
var result = sut.Verify(entered.Object, stored.Object);
173+
174+
// Assert
175+
Assert.IsFalse(result);
176+
}
177+
178+
[Test]
179+
public void Verify_returns_false_for_correct_password_and_salt_but_wrong_iteration_count()
180+
{
181+
// Arrange
182+
var stored = new Mock<StubStoredCredentials>();
183+
stored.Setup(x => x.GetKeyAsByteArray()).Returns(PASSWORD_ONE_SALT_ONE_ITER_100_KEY);
184+
stored.Setup(x => x.GetSaltAsByteArray()).Returns(SALT_ONE);
185+
186+
var entered = new Mock<StubEnteredCredentials>();
187+
entered.Setup(x => x.GetPasswordAsByteArray()).Returns(PASSWORD_ONE);
188+
189+
var sut = new StubVerifier(105);
190+
191+
// Act
192+
var result = sut.Verify(entered.Object, stored.Object);
193+
194+
// Assert
195+
Assert.IsFalse(result);
196+
}
197+
198+
#endregion
199+
200+
#region methods
201+
202+
private byte[] CreateKeyInNewInstance(byte[] salt, byte[] password, int length, int iterationCount)
203+
{
204+
var sut = new StubVerifier(iterationCount);
205+
return sut.CreateKey(password, salt, length);
206+
}
207+
208+
private bool VerifyInNewInstance(StubEnteredCredentials entered, StubStoredCredentials stored, int iterationCount)
209+
{
210+
var sut = new StubVerifier(iterationCount);
211+
return sut.Verify(entered, stored);
212+
}
213+
214+
#endregion
215+
216+
#region contained types
217+
218+
public class StubStoredCredentials : Base64KeyAndSalt {}
219+
220+
public class StubEnteredCredentials : ICredentialsWithPassword
221+
{
222+
public virtual byte[] GetPasswordAsByteArray()
223+
{
224+
throw new NotImplementedException();
225+
}
226+
}
227+
228+
public class StubVerifier : PBKDF2CredentialVerifier<StubEnteredCredentials,StubStoredCredentials>
229+
{
230+
public StubVerifier(int iterationCount) : base(iterationCount) {}
231+
}
232+
233+
#endregion
234+
}
235+
}
236+

Test.CSF/Test.CSF.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
<Compile Include="Collections\Legacy\TestEventBoundSetWrapper.cs" />
9696
<Compile Include="Collections\Legacy\TestIesiCollectionExtensions.cs" />
9797
<Compile Include="Collections\Legacy\TestReferenceSet.cs" />
98+
<Compile Include="Security\TestPBKDF2CredentialVerifier.cs" />
9899
</ItemGroup>
99100
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
100101
<ItemGroup>

0 commit comments

Comments
 (0)