597aeaaf3827a10570ca26caeee39d6c774895c0
[Packages/TYPO3.CMS.git] / typo3 / sysext / saltedpasswords / Tests / Unit / Salts / BlowfishSaltTest.php
1 <?php
2 namespace TYPO3\CMS\Saltedpasswords\Tests\Unit\Salts;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2009-2011 Marcus Krause <marcus#exp2009@t3sec.info>
8 * All rights reserved
9 *
10 * This script is part of the TYPO3 project. The TYPO3 project is
11 * free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * The GNU General Public License can be found at
17 * http://www.gnu.org/copyleft/gpl.html.
18 * A copy is found in the textfile GPL.txt and important notices to the license
19 * from the author is found in LICENSE.txt distributed with these scripts.
20 *
21 *
22 * This script is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * This copyright notice MUST APPEAR in all copies of the script!
28 ***************************************************************/
29 /**
30 * Contains class "tx_saltedpasswords_salts_blowfish"
31 * that provides Blowfish salted hashing.
32 */
33 /**
34 * Testcases for class tx_saltedpasswords_salts_blowfish.
35 *
36 * @author Marcus Krause <marcus#exp2009@t3sec.info>
37 * @package TYPO3
38 * @subpackage tx_saltedpasswords
39 */
40 class BlowfishSaltTest extends \tx_phpunit_testcase {
41
42 /**
43 * Keeps instance of object to test.
44 *
45 * @var \TYPO3\CMS\Saltedpasswords\Salt\BlowfishSalt
46 */
47 protected $objectInstance = NULL;
48
49 /**
50 * Sets up the fixtures for this testcase.
51 *
52 * @return void
53 */
54 public function setUp() {
55 $this->objectInstance = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Saltedpasswords\\Salt\\BlowfishSalt');
56 }
57
58 /**
59 * Tears down objects and settings created in this testcase.
60 *
61 * @return void
62 */
63 public function tearDown() {
64 unset($this->objectInstance);
65 }
66
67 /**
68 * Marks tests as skipped if the blowfish method is not available.
69 *
70 * @return void
71 */
72 protected function skipTestIfBlowfishIsNotAvailable() {
73 if (!CRYPT_BLOWFISH) {
74 $this->markTestSkipped('Blowfish is not supported on your platform.');
75 }
76 }
77
78 /**
79 * @test
80 */
81 public function hasCorrectBaseClass() {
82 $hasCorrectBaseClass = 0 === strcmp('TYPO3\\CMS\\Saltedpasswords\\Salt\\BlowfishSalt', get_class($this->objectInstance)) ? TRUE : FALSE;
83 // XCLASS ?
84 if (!$hasCorrectBaseClass && FALSE != get_parent_class($this->objectInstance)) {
85 $hasCorrectBaseClass = is_subclass_of($this->objectInstance, 'TYPO3\\CMS\\Saltedpasswords\\Salt\\BlowfishSalt');
86 }
87 $this->assertTrue($hasCorrectBaseClass);
88 }
89
90 /**
91 * @test
92 */
93 public function nonZeroSaltLength() {
94 $this->assertTrue($this->objectInstance->getSaltLength() > 0);
95 }
96
97 /**
98 * @test
99 */
100 public function emptyPasswordResultsInNullSaltedPassword() {
101 $password = '';
102 $this->assertNull($this->objectInstance->getHashedPassword($password));
103 }
104
105 /**
106 * @test
107 */
108 public function nonEmptyPasswordResultsInNonNullSaltedPassword() {
109 $this->skipTestIfBlowfishIsNotAvailable();
110 $password = 'a';
111 $this->assertNotNull($this->objectInstance->getHashedPassword($password));
112 }
113
114 /**
115 * @test
116 */
117 public function createdSaltedHashOfProperStructure() {
118 $this->skipTestIfBlowfishIsNotAvailable();
119 $password = 'password';
120 $saltedHashPassword = $this->objectInstance->getHashedPassword($password);
121 $this->assertTrue($this->objectInstance->isValidSaltedPW($saltedHashPassword));
122 }
123
124 /**
125 * @test
126 */
127 public function createdSaltedHashOfProperStructureForCustomSaltWithoutSetting() {
128 $this->skipTestIfBlowfishIsNotAvailable();
129 $password = 'password';
130 // custom salt without setting
131 $randomBytes = \TYPO3\CMS\Core\Utility\GeneralUtility::generateRandomBytes($this->objectInstance->getSaltLength());
132 $salt = $this->objectInstance->base64Encode($randomBytes, $this->objectInstance->getSaltLength());
133 $this->assertTrue($this->objectInstance->isValidSalt($salt));
134 $saltedHashPassword = $this->objectInstance->getHashedPassword($password, $salt);
135 $this->assertTrue($this->objectInstance->isValidSaltedPW($saltedHashPassword));
136 }
137
138 /**
139 * @test
140 */
141 public function createdSaltedHashOfProperStructureForMaximumHashCount() {
142 $this->skipTestIfBlowfishIsNotAvailable();
143 $password = 'password';
144 $maxHashCount = $this->objectInstance->getMaxHashCount();
145 $this->objectInstance->setHashCount($maxHashCount);
146 $saltedHashPassword = $this->objectInstance->getHashedPassword($password);
147 $this->assertTrue($this->objectInstance->isValidSaltedPW($saltedHashPassword));
148 // reset hashcount
149 $this->objectInstance->setHashCount(NULL);
150 }
151
152 /**
153 * @test
154 */
155 public function createdSaltedHashOfProperStructureForMinimumHashCount() {
156 $this->skipTestIfBlowfishIsNotAvailable();
157 $password = 'password';
158 $minHashCount = $this->objectInstance->getMinHashCount();
159 $this->objectInstance->setHashCount($minHashCount);
160 $saltedHashPassword = $this->objectInstance->getHashedPassword($password);
161 $this->assertTrue($this->objectInstance->isValidSaltedPW($saltedHashPassword));
162 // reset hashcount
163 $this->objectInstance->setHashCount(NULL);
164 }
165
166 /**
167 * Tests authentication procedure with alphabet characters.
168 *
169 * Checks if a "plain-text password" is everytime mapped to the
170 * same "salted password hash" when using the same salt.
171 *
172 * @test
173 */
174 public function authenticationWithValidAlphaCharClassPassword() {
175 $this->skipTestIfBlowfishIsNotAvailable();
176 $password = 'aEjOtY';
177 $saltedHashPassword = $this->objectInstance->getHashedPassword($password);
178 $this->assertTrue($this->objectInstance->checkPassword($password, $saltedHashPassword));
179 }
180
181 /**
182 * Tests authentication procedure with numeric characters.
183 *
184 * Checks if a "plain-text password" is everytime mapped to the
185 * same "salted password hash" when using the same salt.
186 *
187 * @test
188 */
189 public function authenticationWithValidNumericCharClassPassword() {
190 $this->skipTestIfBlowfishIsNotAvailable();
191 $password = '01369';
192 $saltedHashPassword = $this->objectInstance->getHashedPassword($password);
193 $this->assertTrue($this->objectInstance->checkPassword($password, $saltedHashPassword));
194 }
195
196 /**
197 * Tests authentication procedure with US-ASCII special characters.
198 *
199 * Checks if a "plain-text password" is everytime mapped to the
200 * same "salted password hash" when using the same salt.
201 *
202 * @test
203 */
204 public function authenticationWithValidAsciiSpecialCharClassPassword() {
205 $this->skipTestIfBlowfishIsNotAvailable();
206 $password = ' !"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~';
207 $saltedHashPassword = $this->objectInstance->getHashedPassword($password);
208 $this->assertTrue($this->objectInstance->checkPassword($password, $saltedHashPassword));
209 }
210
211 /**
212 * Tests authentication procedure with latin1 special characters.
213 *
214 * Checks if a "plain-text password" is everytime mapped to the
215 * same "salted password hash" when using the same salt.
216 *
217 * @test
218 */
219 public function authenticationWithValidLatin1SpecialCharClassPassword() {
220 $this->skipTestIfBlowfishIsNotAvailable();
221 $password = '';
222 for ($i = 160; $i <= 191; $i++) {
223 $password .= chr($i);
224 }
225 $password .= chr(215) . chr(247);
226 $saltedHashPassword = $this->objectInstance->getHashedPassword($password);
227 $this->assertTrue($this->objectInstance->checkPassword($password, $saltedHashPassword));
228 }
229
230 /**
231 * Tests authentication procedure with latin1 umlauts.
232 *
233 * Checks if a "plain-text password" is everytime mapped to the
234 * same "salted password hash" when using the same salt.
235 *
236 * @test
237 */
238 public function authenticationWithValidLatin1UmlautCharClassPassword() {
239 $this->skipTestIfBlowfishIsNotAvailable();
240 $password = '';
241 for ($i = 192; $i <= 214; $i++) {
242 $password .= chr($i);
243 }
244 for ($i = 216; $i <= 246; $i++) {
245 $password .= chr($i);
246 }
247 for ($i = 248; $i <= 255; $i++) {
248 $password .= chr($i);
249 }
250 $saltedHashPassword = $this->objectInstance->getHashedPassword($password);
251 $this->assertTrue($this->objectInstance->checkPassword($password, $saltedHashPassword));
252 }
253
254 /**
255 * @test
256 */
257 public function authenticationWithNonValidPassword() {
258 $this->skipTestIfBlowfishIsNotAvailable();
259 $password = 'password';
260 $password1 = $password . 'INVALID';
261 $saltedHashPassword = $this->objectInstance->getHashedPassword($password);
262 $this->assertFalse($this->objectInstance->checkPassword($password1, $saltedHashPassword));
263 }
264
265 /**
266 * @test
267 */
268 public function passwordVariationsResultInDifferentHashes() {
269 $this->skipTestIfBlowfishIsNotAvailable();
270 $pad = 'a';
271 $password = '';
272 $criticalPwLength = 0;
273 // We're using a constant salt.
274 $saltedHashPasswordPrevious = ($saltedHashPasswordCurrent = ($salt = $this->objectInstance->getHashedPassword($pad)));
275 for ($i = 0; $i <= 128; $i += 8) {
276 $password = str_repeat($pad, max($i, 1));
277 $saltedHashPasswordPrevious = $saltedHashPasswordCurrent;
278 $saltedHashPasswordCurrent = $this->objectInstance->getHashedPassword($password, $salt);
279 if ($i > 0 && 0 == strcmp($saltedHashPasswordPrevious, $saltedHashPasswordCurrent)) {
280 $criticalPwLength = $i;
281 break;
282 }
283 }
284 $this->assertTrue($criticalPwLength == 0 || $criticalPwLength > 32, 'Duplicates of hashed passwords with plaintext password of length ' . $criticalPwLength . '+.');
285 }
286
287 /**
288 * @test
289 */
290 public function modifiedMinHashCount() {
291 $minHashCount = $this->objectInstance->getMinHashCount();
292 $this->objectInstance->setMinHashCount($minHashCount - 1);
293 $this->assertTrue($this->objectInstance->getMinHashCount() < $minHashCount);
294 $this->objectInstance->setMinHashCount($minHashCount + 1);
295 $this->assertTrue($this->objectInstance->getMinHashCount() > $minHashCount);
296 }
297
298 /**
299 * @test
300 */
301 public function modifiedMaxHashCount() {
302 $maxHashCount = $this->objectInstance->getMaxHashCount();
303 $this->objectInstance->setMaxHashCount($maxHashCount + 1);
304 $this->assertTrue($this->objectInstance->getMaxHashCount() > $maxHashCount);
305 $this->objectInstance->setMaxHashCount($maxHashCount - 1);
306 $this->assertTrue($this->objectInstance->getMaxHashCount() < $maxHashCount);
307 }
308
309 /**
310 * @test
311 */
312 public function modifiedHashCount() {
313 $hashCount = $this->objectInstance->getHashCount();
314 $this->objectInstance->setMaxHashCount($hashCount + 1);
315 $this->objectInstance->setHashCount($hashCount + 1);
316 $this->assertTrue($this->objectInstance->getHashCount() > $hashCount);
317 $this->objectInstance->setMinHashCount($hashCount - 1);
318 $this->objectInstance->setHashCount($hashCount - 1);
319 $this->assertTrue($this->objectInstance->getHashCount() < $hashCount);
320 // reset hashcount
321 $this->objectInstance->setHashCount(NULL);
322 }
323
324 /**
325 * @test
326 */
327 public function updateNecessityForValidSaltedPassword() {
328 $this->skipTestIfBlowfishIsNotAvailable();
329 $password = 'password';
330 $saltedHashPassword = $this->objectInstance->getHashedPassword($password);
331 $this->assertFalse($this->objectInstance->isHashUpdateNeeded($saltedHashPassword));
332 }
333
334 /**
335 * @test
336 */
337 public function updateNecessityForIncreasedHashcount() {
338 $password = 'password';
339 $saltedHashPassword = $this->objectInstance->getHashedPassword($password);
340 $increasedHashCount = $this->objectInstance->getHashCount() + 1;
341 $this->objectInstance->setMaxHashCount($increasedHashCount);
342 $this->objectInstance->setHashCount($increasedHashCount);
343 $this->assertTrue($this->objectInstance->isHashUpdateNeeded($saltedHashPassword));
344 // reset hashcount
345 $this->objectInstance->setHashCount(NULL);
346 }
347
348 /**
349 * @test
350 */
351 public function updateNecessityForDecreasedHashcount() {
352 $this->skipTestIfBlowfishIsNotAvailable();
353 $password = 'password';
354 $saltedHashPassword = $this->objectInstance->getHashedPassword($password);
355 $decreasedHashCount = $this->objectInstance->getHashCount() - 1;
356 $this->objectInstance->setMinHashCount($decreasedHashCount);
357 $this->objectInstance->setHashCount($decreasedHashCount);
358 $this->assertFalse($this->objectInstance->isHashUpdateNeeded($saltedHashPassword));
359 // reset hashcount
360 $this->objectInstance->setHashCount(NULL);
361 }
362
363 }
364
365
366 ?>