@@ -50,8 +50,6 @@ class Security implements SecurityInterface
5050 protected $ hash ;
5151
5252 /**
53- * The CSRF Cookie instance.
54- *
5553 * @var Cookie
5654 */
5755 protected $ cookie ;
@@ -85,17 +83,8 @@ class Security implements SecurityInterface
8583 */
8684 private ?string $ hashInCookie = null ;
8785
88- /**
89- * Security Config
90- */
9186 protected SecurityConfig $ config ;
9287
93- /**
94- * Constructor.
95- *
96- * Stores our configuration and fires off the init() method to setup
97- * initial state.
98- */
9988 public function __construct (SecurityConfig $ config )
10089 {
10190 $ this ->config = $ config ;
@@ -115,28 +104,12 @@ public function __construct(SecurityConfig $config)
115104 $ this ->hashInCookie = $ this ->request ->getCookie ($ this ->cookieName );
116105
117106 $ this ->restoreHash ();
107+
118108 if ($ this ->hash === null ) {
119109 $ this ->generateHash ();
120110 }
121111 }
122112
123- private function isCSRFCookie (): bool
124- {
125- return $ this ->config ->csrfProtection === self ::CSRF_PROTECTION_COOKIE ;
126- }
127-
128- private function configureSession (): void
129- {
130- $ this ->session = service ('session ' );
131- }
132-
133- private function configureCookie (CookieConfig $ cookie ): void
134- {
135- $ cookiePrefix = $ cookie ->prefix ;
136- $ this ->cookieName = $ cookiePrefix . $ this ->rawCookieName ;
137- Cookie::setDefaults ($ cookie );
138- }
139-
140113 public function verify (RequestInterface $ request )
141114 {
142115 $ method = $ request ->getMethod ();
@@ -173,8 +146,96 @@ public function verify(RequestInterface $request)
173146 return $ this ;
174147 }
175148
149+ public function getHash (): ?string
150+ {
151+ return $ this ->config ->tokenRandomize ? $ this ->randomize ($ this ->hash ) : $ this ->hash ;
152+ }
153+
154+ public function getTokenName (): string
155+ {
156+ return $ this ->config ->tokenName ;
157+ }
158+
159+ public function getHeaderName (): string
160+ {
161+ return $ this ->config ->headerName ;
162+ }
163+
164+ public function getCookieName (): string
165+ {
166+ return $ this ->config ->cookieName ;
167+ }
168+
169+ public function shouldRedirect (): bool
170+ {
171+ return $ this ->config ->redirect ;
172+ }
173+
174+ public function generateHash (): string
175+ {
176+ $ this ->hash = bin2hex (random_bytes (static ::CSRF_HASH_BYTES ));
177+
178+ if ($ this ->isCSRFCookie ()) {
179+ $ this ->saveHashInCookie ();
180+ } else {
181+ // Session based CSRF protection
182+ $ this ->saveHashInSession ();
183+ }
184+
185+ return $ this ->hash ;
186+ }
187+
176188 /**
177- * Remove token in POST or JSON request data
189+ * Randomize hash to avoid BREACH attacks.
190+ */
191+ protected function randomize (string $ hash ): string
192+ {
193+ $ keyBinary = random_bytes (static ::CSRF_HASH_BYTES );
194+ $ hashBinary = hex2bin ($ hash );
195+
196+ if ($ hashBinary === false ) {
197+ throw new LogicException ('$hash is invalid: ' . $ hash );
198+ }
199+
200+ return bin2hex (($ hashBinary ^ $ keyBinary ) . $ keyBinary );
201+ }
202+
203+ /**
204+ * Derandomize the token.
205+ *
206+ * @throws InvalidArgumentException
207+ */
208+ protected function derandomize (#[SensitiveParameter] string $ token ): string
209+ {
210+ $ key = substr ($ token , -static ::CSRF_HASH_BYTES * 2 );
211+ $ value = substr ($ token , 0 , static ::CSRF_HASH_BYTES * 2 );
212+
213+ try {
214+ return bin2hex ((string ) hex2bin ($ value ) ^ (string ) hex2bin ($ key ));
215+ } catch (ErrorException $ e ) {
216+ throw new InvalidArgumentException ($ e ->getMessage (), $ e ->getCode (), $ e );
217+ }
218+ }
219+
220+ private function isCSRFCookie (): bool
221+ {
222+ return $ this ->config ->csrfProtection === self ::CSRF_PROTECTION_COOKIE ;
223+ }
224+
225+ private function configureSession (): void
226+ {
227+ $ this ->session = service ('session ' );
228+ }
229+
230+ private function configureCookie (CookieConfig $ cookie ): void
231+ {
232+ $ cookiePrefix = $ cookie ->prefix ;
233+ $ this ->cookieName = $ cookiePrefix . $ this ->rawCookieName ;
234+ Cookie::setDefaults ($ cookie );
235+ }
236+
237+ /**
238+ * Remove token in POST, JSON, or form-encoded data to prevent it from being accidentally leaked.
178239 */
179240 private function removeTokenInRequest (IncomingRequest $ request ): void
180241 {
@@ -277,87 +338,6 @@ private function isNonEmptyTokenString(mixed $token): bool
277338 return is_string ($ token ) && $ token !== '' ;
278339 }
279340
280- /**
281- * Returns the CSRF Token.
282- */
283- public function getHash (): ?string
284- {
285- return $ this ->config ->tokenRandomize ? $ this ->randomize ($ this ->hash ) : $ this ->hash ;
286- }
287-
288- /**
289- * Randomize hash to avoid BREACH attacks.
290- *
291- * @params string $hash CSRF hash
292- *
293- * @return string CSRF token
294- */
295- protected function randomize (string $ hash ): string
296- {
297- $ keyBinary = random_bytes (static ::CSRF_HASH_BYTES );
298- $ hashBinary = hex2bin ($ hash );
299-
300- if ($ hashBinary === false ) {
301- throw new LogicException ('$hash is invalid: ' . $ hash );
302- }
303-
304- return bin2hex (($ hashBinary ^ $ keyBinary ) . $ keyBinary );
305- }
306-
307- /**
308- * Derandomize the token.
309- *
310- * @params string $token CSRF token
311- *
312- * @return string CSRF hash
313- *
314- * @throws InvalidArgumentException "hex2bin(): Hexadecimal input string must have an even length"
315- */
316- protected function derandomize (#[SensitiveParameter] string $ token ): string
317- {
318- $ key = substr ($ token , -static ::CSRF_HASH_BYTES * 2 );
319- $ value = substr ($ token , 0 , static ::CSRF_HASH_BYTES * 2 );
320-
321- try {
322- return bin2hex ((string ) hex2bin ($ value ) ^ (string ) hex2bin ($ key ));
323- } catch (ErrorException $ e ) {
324- // "hex2bin(): Hexadecimal input string must have an even length"
325- throw new InvalidArgumentException ($ e ->getMessage (), $ e ->getCode (), $ e );
326- }
327- }
328-
329- /**
330- * Returns the CSRF Token Name.
331- */
332- public function getTokenName (): string
333- {
334- return $ this ->config ->tokenName ;
335- }
336-
337- /**
338- * Returns the CSRF Header Name.
339- */
340- public function getHeaderName (): string
341- {
342- return $ this ->config ->headerName ;
343- }
344-
345- /**
346- * Returns the CSRF Cookie Name.
347- */
348- public function getCookieName (): string
349- {
350- return $ this ->config ->cookieName ;
351- }
352-
353- /**
354- * Check if request should be redirect on failure.
355- */
356- public function shouldRedirect (): bool
357- {
358- return $ this ->config ->redirect ;
359- }
360-
361341 /**
362342 * Restore hash from Session or Cookie
363343 */
@@ -373,23 +353,6 @@ private function restoreHash(): void
373353 }
374354 }
375355
376- /**
377- * Generates (Regenerates) the CSRF Hash.
378- */
379- public function generateHash (): string
380- {
381- $ this ->hash = bin2hex (random_bytes (static ::CSRF_HASH_BYTES ));
382-
383- if ($ this ->isCSRFCookie ()) {
384- $ this ->saveHashInCookie ();
385- } else {
386- // Session based CSRF protection
387- $ this ->saveHashInSession ();
388- }
389-
390- return $ this ->hash ;
391- }
392-
393356 private function isHashInCookie (): bool
394357 {
395358 if ($ this ->hashInCookie === null ) {
0 commit comments