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