[BUGFIX] Added space after template icon
[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\Core\Utility\GeneralUtility;
19 use TYPO3\CMS\Saltedpasswords\Salt\Pbkdf2Salt;
20
21 /**
22 * Testcase for Pbkdf2Salt
23 */
24 class Pbkdf2SaltTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
25 {
26 /**
27 * Keeps instance of object to test.
28 *
29 * @var Pbkdf2Salt
30 */
31 protected $objectInstance = null;
32
33 /**
34 * Sets up the fixtures for this testcase.
35 *
36 * @return void
37 */
38 protected function setUp()
39 {
40 $this->objectInstance = $this->getMockBuilder(Pbkdf2Salt::class)
41 ->setMethods(array('dummy'))
42 ->getMock();
43 // Speed up the tests by reducing the iteration count
44 $this->objectInstance->setHashCount(1000);
45 }
46
47 /**
48 * @test
49 */
50 public function hasCorrectBaseClass()
51 {
52 $hasCorrectBaseClass = get_class($this->objectInstance) === Pbkdf2Salt::class;
53 // XCLASS ?
54 if (!$hasCorrectBaseClass && false != get_parent_class($this->objectInstance)) {
55 $hasCorrectBaseClass = is_subclass_of($this->objectInstance, Pbkdf2Salt::class);
56 }
57 $this->assertTrue($hasCorrectBaseClass);
58 }
59
60 /**
61 * @test
62 */
63 public function nonZeroSaltLength()
64 {
65 $this->assertTrue($this->objectInstance->getSaltLength() > 0);
66 }
67
68 /**
69 * @test
70 */
71 public function emptyPasswordResultsInNullSaltedPassword()
72 {
73 $password = '';
74 $this->assertNull($this->objectInstance->getHashedPassword($password));
75 }
76
77 /**
78 * @test
79 */
80 public function nonEmptyPasswordResultsInNonNullSaltedPassword()
81 {
82 $password = 'a';
83 $this->assertNotNull($this->objectInstance->getHashedPassword($password));
84 }
85
86 /**
87 * @test
88 */
89 public function createdSaltedHashOfProperStructure()
90 {
91 $password = 'password';
92 $saltedHashPassword = $this->objectInstance->getHashedPassword($password);
93 $this->assertTrue($this->objectInstance->isValidSaltedPW($saltedHashPassword));
94 }
95
96 /**
97 * @test
98 */
99 public function createdSaltedHashOfProperStructureForCustomSaltWithoutSetting()
100 {
101 $password = 'password';
102 // custom salt without setting
103 $randomBytes = GeneralUtility::makeInstance(Random::class)->generateRandomBytes($this->objectInstance->getSaltLength());
104 $salt = $this->objectInstance->base64Encode($randomBytes, $this->objectInstance->getSaltLength());
105 $this->assertTrue($this->objectInstance->isValidSalt($salt));
106 $saltedHashPassword = $this->objectInstance->getHashedPassword($password, $salt);
107 $this->assertTrue($this->objectInstance->isValidSaltedPW($saltedHashPassword));
108 }
109
110 /**
111 * @test
112 */
113 public function createdSaltedHashOfProperStructureForMinimumHashCount()
114 {
115 $password = 'password';
116 $minHashCount = $this->objectInstance->getMinHashCount();
117 $this->objectInstance->setHashCount($minHashCount);
118 $saltedHashPassword = $this->objectInstance->getHashedPassword($password);
119 $this->assertTrue($this->objectInstance->isValidSaltedPW($saltedHashPassword));
120 // reset hashcount
121 $this->objectInstance->setHashCount(null);
122 }
123
124 /**
125 * Tests authentication procedure with alphabet characters.
126 *
127 * Checks if a "plain-text password" is every time mapped to the
128 * same "salted password hash" when using the same salt.
129 *
130 * @test
131 */
132 public function authenticationWithValidAlphaCharClassPassword()
133 {
134 $password = 'aEjOtY';
135 $saltedHashPassword = $this->objectInstance->getHashedPassword($password);
136 $this->assertTrue($this->objectInstance->checkPassword($password, $saltedHashPassword));
137 }
138
139 /**
140 * Tests authentication procedure with numeric characters.
141 *
142 * Checks if a "plain-text password" is every time mapped to the
143 * same "salted password hash" when using the same salt.
144 *
145 * @test
146 */
147 public function authenticationWithValidNumericCharClassPassword()
148 {
149 $password = '01369';
150 $saltedHashPassword = $this->objectInstance->getHashedPassword($password);
151 $this->assertTrue($this->objectInstance->checkPassword($password, $saltedHashPassword));
152 }
153
154 /**
155 * Tests authentication procedure with US-ASCII special characters.
156 *
157 * Checks if a "plain-text password" is every time mapped to the
158 * same "salted password hash" when using the same salt.
159 *
160 * @test
161 */
162 public function authenticationWithValidAsciiSpecialCharClassPassword()
163 {
164 $password = ' !"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~';
165 $saltedHashPassword = $this->objectInstance->getHashedPassword($password);
166 $this->assertTrue($this->objectInstance->checkPassword($password, $saltedHashPassword));
167 }
168
169 /**
170 * Tests authentication procedure with latin1 special characters.
171 *
172 * Checks if a "plain-text password" is every time mapped to the
173 * same "salted password hash" when using the same salt.
174 *
175 * @test
176 */
177 public function authenticationWithValidLatin1SpecialCharClassPassword()
178 {
179 $password = '';
180 for ($i = 160; $i <= 191; $i++) {
181 $password .= chr($i);
182 }
183 $password .= chr(215) . chr(247);
184 $saltedHashPassword = $this->objectInstance->getHashedPassword($password);
185 $this->assertTrue($this->objectInstance->checkPassword($password, $saltedHashPassword));
186 }
187
188 /**
189 * Tests authentication procedure with latin1 umlauts.
190 *
191 * Checks if a "plain-text password" is every time mapped to the
192 * same "salted password hash" when using the same salt.
193 *
194 * @test
195 */
196 public function authenticationWithValidLatin1UmlautCharClassPassword()
197 {
198 $password = '';
199 for ($i = 192; $i <= 214; $i++) {
200 $password .= chr($i);
201 }
202 for ($i = 216; $i <= 246; $i++) {
203 $password .= chr($i);
204 }
205 for ($i = 248; $i <= 255; $i++) {
206 $password .= chr($i);
207 }
208 $saltedHashPassword = $this->objectInstance->getHashedPassword($password);
209 $this->assertTrue($this->objectInstance->checkPassword($password, $saltedHashPassword));
210 }
211
212 /**
213 * @test
214 */
215 public function authenticationWithNonValidPassword()
216 {
217 $password = 'password';
218 $password1 = $password . 'INVALID';
219 $saltedHashPassword = $this->objectInstance->getHashedPassword($password);
220 $this->assertFalse($this->objectInstance->checkPassword($password1, $saltedHashPassword));
221 }
222
223 /**
224 * @test
225 */
226 public function passwordVariationsResultInDifferentHashes()
227 {
228 $pad = 'a';
229 $criticalPwLength = 0;
230 // We're using a constant salt.
231 $saltedHashPasswordCurrent = $salt = $this->objectInstance->getHashedPassword($pad);
232 for ($i = 0; $i <= 128; $i += 8) {
233 $password = str_repeat($pad, max($i, 1));
234 $saltedHashPasswordPrevious = $saltedHashPasswordCurrent;
235 $saltedHashPasswordCurrent = $this->objectInstance->getHashedPassword($password, $salt);
236 if ($i > 0 && $saltedHashPasswordPrevious === $saltedHashPasswordCurrent) {
237 $criticalPwLength = $i;
238 break;
239 }
240 }
241 $this->assertTrue($criticalPwLength == 0 || $criticalPwLength > 32, 'Duplicates of hashed passwords with plaintext password of length ' . $criticalPwLength . '+.');
242 }
243
244 /**
245 * @test
246 */
247 public function modifiedMinHashCount()
248 {
249 $minHashCount = $this->objectInstance->getMinHashCount();
250 $this->objectInstance->setMinHashCount($minHashCount - 1);
251 $this->assertTrue($this->objectInstance->getMinHashCount() < $minHashCount);
252 $this->objectInstance->setMinHashCount($minHashCount + 1);
253 $this->assertTrue($this->objectInstance->getMinHashCount() > $minHashCount);
254 }
255
256 /**
257 * @test
258 */
259 public function modifiedMaxHashCount()
260 {
261 $maxHashCount = $this->objectInstance->getMaxHashCount();
262 $this->objectInstance->setMaxHashCount($maxHashCount + 1);
263 $this->assertTrue($this->objectInstance->getMaxHashCount() > $maxHashCount);
264 $this->objectInstance->setMaxHashCount($maxHashCount - 1);
265 $this->assertTrue($this->objectInstance->getMaxHashCount() < $maxHashCount);
266 }
267
268 /**
269 * @test
270 */
271 public function modifiedHashCount()
272 {
273 $hashCount = $this->objectInstance->getHashCount();
274 $this->objectInstance->setMaxHashCount($hashCount + 1);
275 $this->objectInstance->setHashCount($hashCount + 1);
276 $this->assertTrue($this->objectInstance->getHashCount() > $hashCount);
277 $this->objectInstance->setMinHashCount($hashCount - 1);
278 $this->objectInstance->setHashCount($hashCount - 1);
279 $this->assertTrue($this->objectInstance->getHashCount() < $hashCount);
280 // reset hashcount
281 $this->objectInstance->setHashCount(null);
282 }
283
284 /**
285 * @test
286 */
287 public function updateNecessityForValidSaltedPassword()
288 {
289 $password = 'password';
290 $saltedHashPassword = $this->objectInstance->getHashedPassword($password);
291 $this->assertFalse($this->objectInstance->isHashUpdateNeeded($saltedHashPassword));
292 }
293
294 /**
295 * @test
296 */
297 public function updateNecessityForIncreasedHashcount()
298 {
299 $password = 'password';
300 $saltedHashPassword = $this->objectInstance->getHashedPassword($password);
301 $increasedHashCount = $this->objectInstance->getHashCount() + 1;
302 $this->objectInstance->setMaxHashCount($increasedHashCount);
303 $this->objectInstance->setHashCount($increasedHashCount);
304 $this->assertTrue($this->objectInstance->isHashUpdateNeeded($saltedHashPassword));
305 // reset hashcount
306 $this->objectInstance->setHashCount(null);
307 }
308
309 /**
310 * @test
311 */
312 public function updateNecessityForDecreasedHashcount()
313 {
314 $password = 'password';
315 $saltedHashPassword = $this->objectInstance->getHashedPassword($password);
316 $decreasedHashCount = $this->objectInstance->getHashCount() - 1;
317 $this->objectInstance->setMinHashCount($decreasedHashCount);
318 $this->objectInstance->setHashCount($decreasedHashCount);
319 $this->assertFalse($this->objectInstance->isHashUpdateNeeded($saltedHashPassword));
320 // reset hashcount
321 $this->objectInstance->setHashCount(null);
322 }
323
324 /**
325 * @test
326 */
327 public function isCompatibleWithPythonPasslibHashes()
328 {
329 $this->objectInstance->setMinHashCount(1000);
330 $passlibSaltedHash= '$pbkdf2-sha256$6400$.6UI/S.nXIk8jcbdHx3Fhg$98jZicV16ODfEsEZeYPGHU3kbrUrvUEXOPimVSQDD44';
331 $saltedHashPassword = $this->objectInstance->getHashedPassword('password', $passlibSaltedHash);
332
333 $this->assertSame($passlibSaltedHash, $saltedHashPassword);
334 }
335 }