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