[TASK] Mitigate argon2i hash issues
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Crypto / PasswordHashing / PasswordHashFactory.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Core\Crypto\PasswordHashing;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 use TYPO3\CMS\Core\Utility\GeneralUtility;
19
20 /**
21 * Factory class to find and return hash instances of given hashed passwords
22 * and to find and return default hash instances to hash new passwords.
23 */
24 class PasswordHashFactory
25 {
26 /**
27 * An instance of the salted hashing method.
28 * This member is set in the getSaltingInstance() function.
29 *
30 * @var PasswordHashInterface
31 * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
32 */
33 protected static $instance;
34
35 /**
36 * Find a hash class that handles given hash and return an instance of it.
37 *
38 * @param string $hash Given hash to find instance for
39 * @param string $mode 'FE' for frontend users, 'BE' for backend users
40 * @return PasswordHashInterface Object that can handle given hash
41 * @throws \LogicException
42 * @throws \InvalidArgumentException
43 * @throws InvalidPasswordHashException If no class was found that handles given hash
44 */
45 public function get(string $hash, string $mode): PasswordHashInterface
46 {
47 if ($mode !== 'FE' && $mode !== 'BE') {
48 throw new \InvalidArgumentException('Mode must be either \'FE\' or \'BE\', ' . $mode . ' given.', 1533948312);
49 }
50
51 $registeredHashClasses = static::getRegisteredSaltedHashingMethods();
52
53 if (empty($GLOBALS['TYPO3_CONF_VARS'][$mode]['passwordHashing']['className'])
54 || !isset($GLOBALS['TYPO3_CONF_VARS'][$mode]['passwordHashing']['options'])
55 || !is_array($GLOBALS['TYPO3_CONF_VARS'][$mode]['passwordHashing']['options'])
56 ) {
57 throw new \LogicException(
58 'passwordHashing configuration of ' . $mode . ' broken',
59 1533949053
60 );
61 }
62 $defaultHashClassName = $GLOBALS['TYPO3_CONF_VARS'][$mode]['passwordHashing']['className'];
63 $defaultHashOptions = (array)$GLOBALS['TYPO3_CONF_VARS'][$mode]['passwordHashing']['options'];
64
65 foreach ($registeredHashClasses as $className) {
66 if ($className === $defaultHashClassName) {
67 $hashInstance = GeneralUtility::makeInstance($className, $defaultHashOptions);
68 } else {
69 $hashInstance = GeneralUtility::makeInstance($className);
70 }
71 if (!$hashInstance instanceof PasswordHashInterface) {
72 throw new \LogicException('Class ' . $className . ' does not implement PasswordHashInterface', 1533818569);
73 }
74 if ($hashInstance->isAvailable() && $hashInstance->isValidSaltedPW($hash)) {
75 return $hashInstance;
76 }
77 }
78 // Do not add the hash to the exception to prevent information disclosure
79 throw new InvalidPasswordHashException(
80 'No implementation found to handle given hash. This happens if the stored hash uses a'
81 . ' mechanism not supported by current server. Follow the wiki link to fix this issue.',
82 1533818591
83 );
84 }
85
86 /**
87 * Determine configured default hash method and return an instance of the class representing it.
88 *
89 * @param string $mode 'FE' for frontend users, 'BE' for backend users
90 * @return PasswordHashInterface Class instance that is configured as default hash method
91 * @throws \InvalidArgumentException
92 * @throws \LogicException
93 * @throws InvalidPasswordHashException If configuration is broken
94 */
95 public function getDefaultHashInstance(string $mode): PasswordHashInterface
96 {
97 if ($mode !== 'FE' && $mode !== 'BE') {
98 throw new \InvalidArgumentException('Mode must be either \'FE\' or \'BE\', ' . $mode . ' given.', 1533820041);
99 }
100
101 if (empty($GLOBALS['TYPO3_CONF_VARS'][$mode]['passwordHashing']['className'])
102 || !isset($GLOBALS['TYPO3_CONF_VARS'][$mode]['passwordHashing']['options'])
103 || !is_array($GLOBALS['TYPO3_CONF_VARS'][$mode]['passwordHashing']['options'])
104 ) {
105 throw new \LogicException(
106 'passwordHashing configuration of ' . $mode . ' broken',
107 1533950622
108 );
109 }
110
111 $defaultHashClassName = $GLOBALS['TYPO3_CONF_VARS'][$mode]['passwordHashing']['className'];
112 $defaultHashOptions = $GLOBALS['TYPO3_CONF_VARS'][$mode]['passwordHashing']['options'];
113 $availableHashClasses = static::getRegisteredSaltedHashingMethods();
114
115 if (!in_array($defaultHashClassName, $availableHashClasses, true)) {
116 throw new InvalidPasswordHashException(
117 'Configured default hash method ' . $defaultHashClassName . ' is not registered',
118 1533820194
119 );
120 }
121 $hashInstance = GeneralUtility::makeInstance($defaultHashClassName, $defaultHashOptions);
122 if (!$hashInstance instanceof PasswordHashInterface) {
123 throw new \LogicException(
124 'Configured default hash method ' . $defaultHashClassName . ' is not an instance of PasswordHashInterface',
125 1533820281
126 );
127 }
128 if (!$hashInstance->isAvailable()) {
129 throw new InvalidPasswordHashException(
130 'Configured default hash method ' . $defaultHashClassName . ' is not available. If'
131 . ' the instance has just been upgraded, please log in to the standalone install tool'
132 . ' at typo3/install.php to fix this. Follow the wiki link for more details.',
133 1533822084
134 );
135 }
136 return $hashInstance;
137 }
138
139 /**
140 * Returns list of all registered hashing methods. Used eg. in
141 * extension configuration to select the default hashing method.
142 *
143 * @return array
144 * @throws \RuntimeException
145 */
146 public static function getRegisteredSaltedHashingMethods(): array
147 {
148 $saltMethods = $GLOBALS['TYPO3_CONF_VARS']['SYS']['availablePasswordHashAlgorithms'];
149 if (!is_array($saltMethods) || empty($saltMethods)) {
150 throw new \RuntimeException('No password hash methods configured', 1533948733);
151 }
152 if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/saltedpasswords']['saltMethods'])) {
153 trigger_error(
154 'Registering additional hash algorithms in $GLOBALS[\'TYPO3_CONF_VARS\'][\'SC_OPTIONS\'][\'ext/saltedpasswords\'][\'saltMethods\']'
155 . ' has been deprecated. Extend $GLOBALS[\'TYPO3_CONF_VARS\'][\'SYS\'][\'availablePasswordHashAlgorithms\'] instead',
156 E_USER_DEPRECATED
157 );
158 $configuredMethods = (array)$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/saltedpasswords']['saltMethods'];
159 if (!empty($configuredMethods)) {
160 $saltMethods = array_merge($saltMethods, $configuredMethods);
161 }
162 }
163 return $saltMethods;
164 }
165
166 /**
167 * Obtains a salting hashing method instance.
168 *
169 * This function will return an instance of a class that implements
170 * \TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashInterface
171 *
172 * Use parameter NULL to reset the factory!
173 *
174 * @param string|null $saltedHash Salted hashed password to determine the type of used method from or NULL to reset to the default type
175 * @param string $mode The TYPO3 mode (FE or BE) saltedpasswords shall be used for
176 * @return PasswordHashInterface|null An instance of salting hash method class or null if given hash is not supported
177 * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
178 */
179 public static function getSaltingInstance($saltedHash = '', $mode = TYPO3_MODE)
180 {
181 trigger_error(
182 'This method is obsolete and will be removed in TYPO3 v10. Use get() and getDefaultHashInstance() instead.',
183 E_USER_DEPRECATED
184 );
185 // Creating new instance when
186 // * no instance existing
187 // * a salted hash given to determine salted hashing method from
188 // * a NULL parameter given to reset instance back to default method
189 if (!is_object(self::$instance) || !empty($saltedHash) || $saltedHash === null) {
190 // Determine method by checking the given hash
191 if (!empty($saltedHash)) {
192 $result = self::determineSaltingHashingMethod($saltedHash, $mode);
193 if (!$result) {
194 self::$instance = null;
195 }
196 } else {
197 $classNameToUse = SaltedPasswordsUtility::getDefaultSaltingHashingMethod($mode);
198 self::$instance = GeneralUtility::makeInstance($classNameToUse);
199 }
200 }
201 return self::$instance;
202 }
203
204 /**
205 * Method tries to determine the salting hashing method used for given salt.
206 *
207 * Method implicitly sets the instance of the found method object in the class property when found.
208 *
209 * @param string $saltedHash
210 * @param string $mode (optional) The TYPO3 mode (FE or BE) saltedpasswords shall be used for
211 * @return bool TRUE, if salting hashing method has been found, otherwise FALSE
212 * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
213 */
214 public static function determineSaltingHashingMethod(string $saltedHash, $mode = TYPO3_MODE): bool
215 {
216 trigger_error(
217 'This method is obsolete and will be removed in TYPO3 v10.',
218 E_USER_DEPRECATED
219 );
220 $registeredMethods = static::getRegisteredSaltedHashingMethods();
221 $defaultClassName = SaltedPasswordsUtility::getDefaultSaltingHashingMethod($mode);
222 unset($registeredMethods[$defaultClassName]);
223 // place the default method first in the order
224 $registeredMethods = [$defaultClassName => $defaultClassName] + $registeredMethods;
225 $methodFound = false;
226 foreach ($registeredMethods as $method) {
227 $objectInstance = GeneralUtility::makeInstance($method);
228 if ($objectInstance instanceof PasswordHashInterface && $objectInstance->isAvailable()) {
229 $methodFound = $objectInstance->isValidSaltedPW($saltedHash);
230 if ($methodFound) {
231 self::$instance = $objectInstance;
232 break;
233 }
234 }
235 }
236 return $methodFound;
237 }
238
239 /**
240 * Method sets a custom salting hashing method class.
241 *
242 * @param string $resource Object resource to use (e.g. \TYPO3\CMS\Core\Crypto\PasswordHashing\BlowfishPasswordHash::class)
243 * @return PasswordHashInterface|null An instance of salting hashing method object or null
244 * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
245 */
246 public static function setPreferredHashingMethod(string $resource)
247 {
248 trigger_error('This method is obsolete and will be removed in TYPO3 v10.', E_USER_DEPRECATED);
249 self::$instance = null;
250 $objectInstance = GeneralUtility::makeInstance($resource);
251 if ($objectInstance instanceof PasswordHashInterface) {
252 self::$instance = $objectInstance;
253 }
254 return self::$instance;
255 }
256 }