[TASK] Use hash_equals for timing-safe comparison of hash-values
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / FormProtection / AbstractFormProtection.php
1 <?php
2 namespace TYPO3\CMS\Core\FormProtection;
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 * This class provides protection against cross-site request forgery (XSRF/CSRF)
22 * for forms.
23 *
24 * For documentation on how to use this class, please see the documentation of
25 * the corresponding subclasses
26 */
27 abstract class AbstractFormProtection
28 {
29 /**
30 * @var \Closure
31 */
32 protected $validationFailedCallback;
33
34 /**
35 * The session token which is used to be hashed during token generation.
36 *
37 * @var string
38 */
39 protected $sessionToken;
40
41 /**
42 * @return string
43 */
44 protected function getSessionToken()
45 {
46 if ($this->sessionToken === null) {
47 $this->sessionToken = $this->retrieveSessionToken();
48 }
49 return $this->sessionToken;
50 }
51
52 /**
53 * Frees as much memory as possible.
54 */
55 public function __destruct()
56 {
57 unset($this->sessionToken);
58 }
59
60 /**
61 * Deletes the session token and persists the (empty) token.
62 *
63 * This function is intended to be called when a user logs on or off.
64 */
65 public function clean()
66 {
67 unset($this->sessionToken);
68 $this->persistSessionToken();
69 }
70
71 /**
72 * Generates a token for a form by hashing the given parameters
73 * with the secret session token.
74 *
75 * Calling this function two times with the same parameters will create
76 * the same valid token during one user session.
77 *
78 * @param string $formName
79 * @param string $action
80 * @param string $formInstanceName
81 * @return string the 32-character hex ID of the generated token
82 * @throws \InvalidArgumentException
83 */
84 public function generateToken($formName, $action = '', $formInstanceName = '')
85 {
86 if ($formName == '') {
87 throw new \InvalidArgumentException('$formName must not be empty.', 1294586643);
88 }
89 $tokenId = GeneralUtility::hmac($formName . $action . $formInstanceName . $this->getSessionToken());
90 return $tokenId;
91 }
92
93 /**
94 * Checks whether the token $tokenId is valid in the form $formName with
95 * $formInstanceName.
96 *
97 * @param string $tokenId
98 * @param string $formName
99 * @param string $action
100 * @param string $formInstanceName
101 * @return bool
102 */
103 public function validateToken($tokenId, $formName, $action = '', $formInstanceName = '')
104 {
105 $validTokenId = GeneralUtility::hmac(((string)$formName . (string)$action) . (string)$formInstanceName . $this->getSessionToken());
106 if (hash_equals($validTokenId, (string)$tokenId)) {
107 $isValid = true;
108 } else {
109 $isValid = false;
110 }
111 if (!$isValid) {
112 $this->createValidationErrorMessage();
113 }
114 return $isValid;
115 }
116
117 /**
118 * Generates the random token which is used in the hash for the form tokens.
119 *
120 * @return string
121 */
122 protected function generateSessionToken()
123 {
124 return GeneralUtility::makeInstance(Random::class)->generateRandomHexString(64);
125 }
126
127 /**
128 * Creates or displays an error message telling the user that the submitted
129 * form token is invalid.
130 */
131 protected function createValidationErrorMessage()
132 {
133 if ($this->validationFailedCallback !== null) {
134 $this->validationFailedCallback->__invoke();
135 }
136 }
137
138 /**
139 * Retrieves the session token.
140 *
141 * @return string
142 */
143 abstract protected function retrieveSessionToken();
144
145 /**
146 * Saves the session token so that it can be used by a later incarnation
147 * of this class.
148 *
149 * @access private
150 */
151 abstract public function persistSessionToken();
152 }