[TASK] Unit tests: Do not rely on CacheManager instance
[Packages/TYPO3.CMS.git] / typo3 / sysext / saltedpasswords / Tests / Unit / Salt / Pbkdf2SaltTest.php
1 <?php
2 namespace TYPO3\CMS\Saltedpasswords\Tests\Unit\Salt;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Crypto\Random;
18 use TYPO3\CMS\Saltedpasswords\Salt\Pbkdf2Salt;
19 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
20
21 /**
22 * Test case
23 */
24 class Pbkdf2SaltTest extends UnitTestCase
25 {
26 /**
27 * Keeps instance of object to test.
28 *
29 * @var Pbkdf2Salt
30 */
31 protected $subject = null;
32
33 /**
34 * Sets up the fixtures for this testcase.
35 */
36 protected function setUp()
37 {
38 $this->subject = new Pbkdf2Salt();
39 // Speed up the tests by reducing the iteration count
40 $this->subject->setHashCount(1000);
41 $this->subject->setMinHashCount(1000);
42 $this->subject->setMaxHashCount(10000000);
43 }
44
45 /**
46 * @test
47 */
48 public function nonZeroSaltLength()
49 {
50 $this->assertTrue($this->subject->getSaltLength() > 0);
51 }
52
53 /**
54 * @test
55 */
56 public function emptyPasswordResultsInNullSaltedPassword()
57 {
58 $password = '';
59 $this->assertNull($this->subject->getHashedPassword($password));
60 }
61
62 /**
63 * @test
64 */
65 public function nonEmptyPasswordResultsInNonNullSaltedPassword()
66 {
67 $password = 'a';
68 $this->assertNotNull($this->subject->getHashedPassword($password));
69 }
70
71 /**
72 * @test
73 */
74 public function createdSaltedHashOfProperStructure()
75 {
76 $password = 'password';
77 $saltedHashPassword = $this->subject->getHashedPassword($password);
78 $this->assertTrue($this->subject->isValidSaltedPW($saltedHashPassword));
79 }
80
81 /**
82 * @test
83 */
84 public function createdSaltedHashOfProperStructureForCustomSaltWithoutSetting()
85 {
86 $password = 'password';
87 // custom salt without setting
88 $randomBytes = (new Random())->generateRandomBytes($this->subject->getSaltLength());
89 $salt = $this->subject->base64Encode($randomBytes, $this->subject->getSaltLength());
90 $this->assertTrue($this->subject->isValidSalt($salt));
91 $saltedHashPassword = $this->subject->getHashedPassword($password, $salt);
92 $this->assertTrue($this->subject->isValidSaltedPW($saltedHashPassword));
93 }
94
95 /**
96 * @test
97 */
98 public function createdSaltedHashOfProperStructureForMinimumHashCount()
99 {
100 $password = 'password';
101 $minHashCount = $this->subject->getMinHashCount();
102 $this->subject->setHashCount($minHashCount);
103 $saltedHashPassword = $this->subject->getHashedPassword($password);
104 $this->assertTrue($this->subject->isValidSaltedPW($saltedHashPassword));
105 // reset hashcount
106 $this->subject->setHashCount(null);
107 }
108
109 /**
110 * Tests authentication procedure with fixed password and fixed (pre-generated) hash.
111 *
112 * Checks if a "plain-text password" is every time mapped to the
113 * same "salted password hash" when using the same fixed salt.
114 *
115 * @test
116 */
117 public function authenticationWithValidAlphaCharClassPasswordAndFixedHash()
118 {
119 $password = 'password';
120 $saltedHashPassword = '$pbkdf2-sha256$1000$woPhT0yoWm3AXJXSjuxJ3w$iZ6EvTulMqXlzr0NO8z5EyrklFcJk5Uw2Fqje68FfaQ';
121 $this->assertTrue($this->subject->checkPassword($password, $saltedHashPassword));
122 }
123
124 /**
125 * Tests that authentication procedure fails with broken hash to compare to
126 *
127 * @test
128 */
129 public function authenticationFailsWithBrokenHash()
130 {
131 $password = 'password';
132 $saltedHashPassword = '$pbkdf2-sha256$1000$woPhT0yoWm3AXJXSjuxJ3w$iZ6EvTulMqXlzr0NO8z5EyrklFcJk5Uw2Fqje68Ffa';
133 $this->assertFalse($this->subject->checkPassword($password, $saltedHashPassword));
134 }
135
136 /**
137 * Tests authentication procedure with alphabet characters.
138 *
139 * Checks if a "plain-text password" is every time mapped to the
140 * same "salted password hash" when using the same salt.
141 *
142 * @test
143 */
144 public function authenticationWithValidAlphaCharClassPassword()
145 {
146 $password = 'aEjOtY';
147 $saltedHashPassword = $this->subject->getHashedPassword($password);
148 $this->assertTrue($this->subject->checkPassword($password, $saltedHashPassword));
149 }
150
151 /**
152 * Tests authentication procedure with numeric characters.
153 *
154 * Checks if a "plain-text password" is every time mapped to the
155 * same "salted password hash" when using the same salt.
156 *
157 * @test
158 */
159 public function authenticationWithValidNumericCharClassPassword()
160 {
161 $password = '01369';
162 $saltedHashPassword = $this->subject->getHashedPassword($password);
163 $this->assertTrue($this->subject->checkPassword($password, $saltedHashPassword));
164 }
165
166 /**
167 * Tests authentication procedure with US-ASCII special characters.
168 *
169 * Checks if a "plain-text password" is every time mapped to the
170 * same "salted password hash" when using the same salt.
171 *
172 * @test
173 */
174 public function authenticationWithValidAsciiSpecialCharClassPassword()
175 {
176 $password = ' !"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~';
177 $saltedHashPassword = $this->subject->getHashedPassword($password);
178 $this->assertTrue($this->subject->checkPassword($password, $saltedHashPassword));
179 }
180
181 /**
182 * Tests authentication procedure with latin1 special characters.
183 *
184 * Checks if a "plain-text password" is every time mapped to the
185 * same "salted password hash" when using the same salt.
186 *
187 * @test
188 */
189 public function authenticationWithValidLatin1SpecialCharClassPassword()
190 {
191 $password = '';
192 for ($i = 160; $i <= 191; $i++) {
193 $password .= chr($i);
194 }
195 $password .= chr(215) . chr(247);
196 $saltedHashPassword = $this->subject->getHashedPassword($password);
197 $this->assertTrue($this->subject->checkPassword($password, $saltedHashPassword));
198 }
199
200 /**
201 * Tests authentication procedure with latin1 umlauts.
202 *
203 * Checks if a "plain-text password" is every time mapped to the
204 * same "salted password hash" when using the same salt.
205 *
206 * @test
207 */
208 public function authenticationWithValidLatin1UmlautCharClassPassword()
209 {
210 $password = '';
211 for ($i = 192; $i <= 214; $i++) {
212 $password .= chr($i);
213 }
214 for ($i = 216; $i <= 246; $i++) {
215 $password .= chr($i);
216 }
217 for ($i = 248; $i <= 255; $i++) {
218 $password .= chr($i);
219 }
220 $saltedHashPassword = $this->subject->getHashedPassword($password);
221 $this->assertTrue($this->subject->checkPassword($password, $saltedHashPassword));
222 }
223
224 /**
225 * @test
226 */
227 public function authenticationWithNonValidPassword()
228 {
229 $password = 'password';
230 $password1 = $password . 'INVALID';
231 $saltedHashPassword = $this->subject->getHashedPassword($password);
232 $this->assertFalse($this->subject->checkPassword($password1, $saltedHashPassword));
233 }
234
235 /**
236 * @test
237 */
238 public function passwordVariationsResultInDifferentHashes()
239 {
240 $pad = 'a';
241 $criticalPwLength = 0;
242 // We're using a constant salt.
243 $saltedHashPasswordCurrent = $salt = $this->subject->getHashedPassword($pad);
244 for ($i = 0; $i <= 128; $i += 8) {
245 $password = str_repeat($pad, max($i, 1));
246 $saltedHashPasswordPrevious = $saltedHashPasswordCurrent;
247 $saltedHashPasswordCurrent = $this->subject->getHashedPassword($password, $salt);
248 if ($i > 0 && $saltedHashPasswordPrevious === $saltedHashPasswordCurrent) {
249 $criticalPwLength = $i;
250 break;
251 }
252 }
253 $this->assertTrue($criticalPwLength == 0 || $criticalPwLength > 32, 'Duplicates of hashed passwords with plaintext password of length ' . $criticalPwLength . '+.');
254 }
255
256 /**
257 * @test
258 */
259 public function modifiedMinHashCount()
260 {
261 $minHashCount = $this->subject->getMinHashCount();
262 $this->subject->setMinHashCount($minHashCount - 1);
263 $this->assertTrue($this->subject->getMinHashCount() < $minHashCount);
264 $this->subject->setMinHashCount($minHashCount + 1);
265 $this->assertTrue($this->subject->getMinHashCount() > $minHashCount);
266 }
267
268 /**
269 * @test
270 */
271 public function modifiedMaxHashCount()
272 {
273 $maxHashCount = $this->subject->getMaxHashCount();
274 $this->subject->setMaxHashCount($maxHashCount + 1);
275 $this->assertTrue($this->subject->getMaxHashCount() > $maxHashCount);
276 $this->subject->setMaxHashCount($maxHashCount - 1);
277 $this->assertTrue($this->subject->getMaxHashCount() < $maxHashCount);
278 }
279
280 /**
281 * @test
282 */
283 public function modifiedHashCount()
284 {
285 $hashCount = $this->subject->getHashCount();
286 $this->subject->setMaxHashCount($hashCount + 1);
287 $this->subject->setHashCount($hashCount + 1);
288 $this->assertTrue($this->subject->getHashCount() > $hashCount);
289 $this->subject->setMinHashCount($hashCount - 1);
290 $this->subject->setHashCount($hashCount - 1);
291 $this->assertTrue($this->subject->getHashCount() < $hashCount);
292 // reset hashcount
293 $this->subject->setHashCount(null);
294 }
295
296 /**
297 * @test
298 */
299 public function updateNecessityForValidSaltedPassword()
300 {
301 $password = 'password';
302 $saltedHashPassword = $this->subject->getHashedPassword($password);
303 $this->assertFalse($this->subject->isHashUpdateNeeded($saltedHashPassword));
304 }
305
306 /**
307 * @test
308 */
309 public function updateNecessityForIncreasedHashcount()
310 {
311 $password = 'password';
312 $saltedHashPassword = $this->subject->getHashedPassword($password);
313 $increasedHashCount = $this->subject->getHashCount() + 1;
314 $this->subject->setMaxHashCount($increasedHashCount);
315 $this->subject->setHashCount($increasedHashCount);
316 $this->assertTrue($this->subject->isHashUpdateNeeded($saltedHashPassword));
317 // reset hashcount
318 $this->subject->setHashCount(null);
319 }
320
321 /**
322 * @test
323 */
324 public function updateNecessityForDecreasedHashcount()
325 {
326 $password = 'password';
327 $saltedHashPassword = $this->subject->getHashedPassword($password);
328 $decreasedHashCount = $this->subject->getHashCount() - 1;
329 $this->subject->setMinHashCount($decreasedHashCount);
330 $this->subject->setHashCount($decreasedHashCount);
331 $this->assertFalse($this->subject->isHashUpdateNeeded($saltedHashPassword));
332 // reset hashcount
333 $this->subject->setHashCount(null);
334 }
335
336 /**
337 * @test
338 */
339 public function isCompatibleWithPythonPasslibHashes()
340 {
341 $passlibSaltedHash= '$pbkdf2-sha256$6400$.6UI/S.nXIk8jcbdHx3Fhg$98jZicV16ODfEsEZeYPGHU3kbrUrvUEXOPimVSQDD44';
342 $saltedHashPassword = $this->subject->getHashedPassword('password', $passlibSaltedHash);
343
344 $this->assertSame($passlibSaltedHash, $saltedHashPassword);
345 }
346 }