Issue #3053956 by alexpott, neeravbm, aleevas, voleger, Berdir: Deprecate \Drupal\Component\Utility\Crypt::hashEquals() in favour of PHP's builtin hash_equals()

merge-requests/1119/head
catch 2019-06-10 14:28:03 +01:00
parent a3ce6e70ed
commit 17153a2605
15 changed files with 33 additions and 21 deletions

View File

@ -679,7 +679,7 @@ function drupal_valid_test_ua($new_prefix = NULL) {
$test_hmac = Crypt::hmacBase64($check_string, $key);
// Since we are making a local request a 600 second time window is allowed,
// and the HMAC must match.
if ($time_diff >= 0 && $time_diff <= 600 && Crypt::hashEquals($test_hmac, $hmac)) {
if ($time_diff >= 0 && $time_diff <= 600 && hash_equals($test_hmac, $hmac)) {
$test_prefix = $prefix;
}
else {

View File

@ -91,10 +91,13 @@ class Crypt {
* @return bool
* Returns TRUE when the two strings are equal, FALSE otherwise.
*
* @todo Deprecate in favor of hash_equals().
* https://www.drupal.org/node/3053956
* @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0.
* Use PHP's built-in hash_equals() function instead.
*
* @see https://www.drupal.org/node/3054488
*/
public static function hashEquals($known_string, $user_string) {
@trigger_error(__CLASS__ . '::hashEquals() is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use PHP\'s built-in hash_equals() function instead. See https://www.drupal.org/node/3054488', E_USER_DEPRECATED);
return hash_equals($known_string, $user_string);
}

View File

@ -87,7 +87,7 @@ class CsrfTokenGenerator {
return FALSE;
}
return Crypt::hashEquals($this->computeToken($seed, $value), $token);
return hash_equals($this->computeToken($seed, $value), $token);
}
/**

View File

@ -2,8 +2,6 @@
namespace Drupal\Core\Password;
use Drupal\Component\Utility\Crypt;
/**
* Secure password hashing functions based on the Portable PHP password
* hashing framework.
@ -248,8 +246,8 @@ class PhpassHashedPassword implements PasswordInterface {
return FALSE;
}
// Compare using hashEquals() instead of === to mitigate timing attacks.
return $computed_hash && Crypt::hashEquals($stored_hash, $computed_hash);
// Compare using hash_equals() instead of === to mitigate timing attacks.
return $computed_hash && hash_equals($stored_hash, $computed_hash);
}
/**

View File

@ -72,7 +72,7 @@ class ContextualController implements ContainerInjectionInterface {
$rendered = [];
foreach ($ids as $key => $id) {
if (!isset($tokens[$key]) || !Crypt::hashEquals($tokens[$key], Crypt::hmacBase64($id, Settings::getHashSalt() . \Drupal::service('private_key')->get()))) {
if (!isset($tokens[$key]) || !hash_equals($tokens[$key], Crypt::hmacBase64($id, Settings::getHashSalt() . \Drupal::service('private_key')->get()))) {
throw new BadRequestHttpException('Invalid contextual ID specified.');
}
$element = [

View File

@ -111,7 +111,7 @@ class ManagedFile extends FormElement {
elseif (\Drupal::currentUser()->isAnonymous()) {
$token = NestedArray::getValue($form_state->getUserInput(), array_merge($element['#parents'], ['file_' . $file->id(), 'fid_token']));
$file_hmac = Crypt::hmacBase64('file-' . $file->id(), \Drupal::service('private_key')->get() . Settings::getHashSalt());
if ($token === NULL || !Crypt::hashEquals($file_hmac, $token)) {
if ($token === NULL || !hash_equals($file_hmac, $token)) {
$force_default = TRUE;
break;
}

View File

@ -109,7 +109,7 @@ class ImageStyleDownloadController extends FileDownloadController {
// starts with styles/.
$valid = !empty($image_style) && $this->streamWrapperManager->isValidScheme($scheme);
if (!$this->config('image.settings')->get('allow_insecure_derivatives') || strpos(ltrim($target, '\/'), 'styles/') === 0) {
$valid &= Crypt::hashEquals($image_style->getPathToken($image_uri), $request->query->get(IMAGE_DERIVATIVE_TOKEN, ''));
$valid &= hash_equals($image_style->getPathToken($image_uri), $request->query->get(IMAGE_DERIVATIVE_TOKEN, ''));
}
if (!$valid) {
// Return a 404 (Page Not Found) rather than a 403 (Access Denied) as the

View File

@ -125,7 +125,7 @@ class OEmbedIframeController implements ContainerInjectionInterface {
// Hash the URL and max dimensions, and ensure it is equal to the hash
// parameter passed in the query string.
$hash = $this->iFrameUrlHelper->getHash($url, $max_width, $max_height);
if (!Crypt::hashEquals($hash, $request->query->get('hash', ''))) {
if (!hash_equals($hash, $request->query->get('hash', ''))) {
throw new AccessDeniedHttpException('This resource is not available');
}

View File

@ -187,7 +187,7 @@ class MediaLibraryState extends ParameterBag {
* The hashed parameters.
*/
public function isValidHash($hash) {
return Crypt::hashEquals($this->getHash(), $hash);
return hash_equals($this->getHash(), $hash);
}
/**

View File

@ -87,7 +87,7 @@ class EntityAutocompleteController extends ControllerBase {
$selection_settings = $this->keyValue->get($selection_settings_key, FALSE);
if ($selection_settings !== FALSE) {
$selection_settings_hash = Crypt::hmacBase64(serialize($selection_settings) . $target_type . $selection_handler, Settings::getHashSalt());
if (!Crypt::hashEquals($selection_settings_hash, $selection_settings_key)) {
if (!hash_equals($selection_settings_hash, $selection_settings_key)) {
// Disallow access when the selection settings hash does not match the
// passed-in key.
throw new AccessDeniedHttpException('Invalid selection settings key.');

View File

@ -2,7 +2,6 @@
namespace Drupal\toolbar\Controller;
use Drupal\Component\Utility\Crypt;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Controller\ControllerBase;
@ -49,7 +48,7 @@ class ToolbarController extends ControllerBase {
*/
public function checkSubTreeAccess($hash) {
$expected_hash = _toolbar_get_subtrees_hash()[0];
return AccessResult::allowedIf($this->currentUser()->hasPermission('access toolbar') && Crypt::hashEquals($expected_hash, $hash))->cachePerPermissions();
return AccessResult::allowedIf($this->currentUser()->hasPermission('access toolbar') && hash_equals($expected_hash, $hash))->cachePerPermissions();
}
}

View File

@ -3,7 +3,6 @@
namespace Drupal\user;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Component\Utility\Crypt;
use Drupal\Core\Entity\ContentEntityForm;
use Drupal\Core\Entity\EntityConstraintViolationListInterface;
use Drupal\Core\Entity\EntityRepositoryInterface;
@ -131,7 +130,7 @@ abstract class AccountForm extends ContentEntityForm {
// so it persists even on subsequent Ajax requests.
if (!$form_state->get('user_pass_reset') && ($token = $this->getRequest()->get('pass-reset-token'))) {
$session_key = 'pass_reset_' . $account->id();
$user_pass_reset = isset($_SESSION[$session_key]) && Crypt::hashEquals($_SESSION[$session_key], $token);
$user_pass_reset = isset($_SESSION[$session_key]) && hash_equals($_SESSION[$session_key], $token);
$form_state->set('user_pass_reset', $user_pass_reset);
}

View File

@ -226,7 +226,7 @@ class UserController extends ControllerBase {
$this->messenger()->addError($this->t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.'));
return $this->redirect('user.pass');
}
elseif ($user->isAuthenticated() && ($timestamp >= $user->getLastLoginTime()) && ($timestamp <= $current) && Crypt::hashEquals($hash, user_pass_rehash($user, $timestamp))) {
elseif ($user->isAuthenticated() && ($timestamp >= $user->getLastLoginTime()) && ($timestamp <= $current) && hash_equals($hash, user_pass_rehash($user, $timestamp))) {
user_login_finalize($user);
$this->logger->notice('User %name used one-time login link at time %timestamp.', ['%name' => $user->getDisplayName(), '%timestamp' => $timestamp]);
$this->messenger()->addStatus($this->t('You have just used your one-time login link. It is no longer necessary to use this link to log in. Please change your password.'));
@ -324,7 +324,7 @@ class UserController extends ControllerBase {
$account_data = $this->userData->get('user', $user->id());
if (isset($account_data['cancel_method']) && !empty($timestamp) && !empty($hashed_pass)) {
// Validate expiration and hashed password/login.
if ($timestamp <= $current && $current - $timestamp < $timeout && $user->id() && $timestamp >= $user->getLastLoginTime() && Crypt::hashEquals($hashed_pass, user_pass_rehash($user, $timestamp))) {
if ($timestamp <= $current && $current - $timestamp < $timeout && $user->id() && $timestamp >= $user->getLastLoginTime() && hash_equals($hashed_pass, user_pass_rehash($user, $timestamp))) {
$edit = [
'user_cancel_notify' => isset($account_data['cancel_notify']) ? $account_data['cancel_notify'] : $this->config('user.settings')->get('notify.status_canceled'),
];

View File

@ -40,7 +40,7 @@ catch (HttpExceptionInterface $e) {
if (Settings::get('rebuild_access', FALSE) ||
($request->query->get('token') && $request->query->get('timestamp') &&
((REQUEST_TIME - $request->query->get('timestamp')) < 300) &&
Crypt::hashEquals(Crypt::hmacBase64($request->query->get('timestamp'), Settings::get('hash_salt')), $request->query->get('token'))
hash_equals(Crypt::hmacBase64($request->query->get('timestamp'), Settings::get('hash_salt')), $request->query->get('token'))
)) {
// Clear user cache for all major platforms.
$user_caches = [

View File

@ -148,4 +148,17 @@ class CryptTest extends TestCase {
];
}
/**
* Legacy test of Drupal\Component\Utility\Crypt::hashEquals() method.
*
* @expectedDeprecation Drupal\Component\Utility\Crypt::hashEquals() is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use PHP's built-in hash_equals() function instead. See https://www.drupal.org/node/3054488
* @group legacy
*/
public function testHashEquals() {
$a_hash = Crypt::hashBase64('a');
$b_hash = Crypt::hashBase64('b');
$this->assertTrue(Crypt::hashEquals($a_hash, $a_hash));
$this->assertFalse(Crypt::hashEquals($a_hash, $b_hash));
}
}