summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Pott2016-10-26 19:30:28 (GMT)
committerAlex Pott2016-10-26 19:33:05 (GMT)
commit2bccef8b365f6c27e6ce1444cfbad42722bddcad (patch)
treeb0760a5dcc759fe5aaff025ca404cd24236733ee
parentc225f3b9da8d62fb31a5555da017611824fb4166 (diff)
Issue #2763787 by pwolanin, nerdstein, rlhawk, YesCT, slasher13, tuutti, alexpott, therealssj, TravisCarden, dawehner, maximpodorov, klausi, catch, talhaparacha, Eric_A: Upgrade random_compat to latest version
-rw-r--r--composer.lock10
-rw-r--r--core/composer.json2
-rw-r--r--core/lib/Drupal/Component/Utility/Crypt.php41
-rw-r--r--core/lib/Drupal/Component/Utility/composer.json2
-rw-r--r--core/modules/system/system.install38
-rw-r--r--core/tests/Drupal/Tests/Component/Utility/CryptRandomFallbackTest.php71
-rw-r--r--core/tests/Drupal/Tests/Component/Utility/CryptTest.php2
7 files changed, 157 insertions, 9 deletions
diff --git a/composer.lock b/composer.lock
index 832979f..2ecd5c2 100644
--- a/composer.lock
+++ b/composer.lock
@@ -999,16 +999,16 @@
},
{
"name": "paragonie/random_compat",
- "version": "1.1.1",
+ "version": "v2.0.2",
"source": {
"type": "git",
"url": "https://github.com/paragonie/random_compat.git",
- "reference": "a208865a5aeffc2dbbef2a5b3409887272d93f32"
+ "reference": "088c04e2f261c33bed6ca5245491cfca69195ccf"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/paragonie/random_compat/zipball/a208865a5aeffc2dbbef2a5b3409887272d93f32",
- "reference": "a208865a5aeffc2dbbef2a5b3409887272d93f32",
+ "url": "https://api.github.com/repos/paragonie/random_compat/zipball/088c04e2f261c33bed6ca5245491cfca69195ccf",
+ "reference": "088c04e2f261c33bed6ca5245491cfca69195ccf",
"shasum": ""
},
"require": {
@@ -1043,7 +1043,7 @@
"pseudorandom",
"random"
],
- "time": "2015-12-01 02:52:15"
+ "time": "2016-04-03 06:00:07"
},
{
"name": "psr/http-message",
diff --git a/core/composer.json b/core/composer.json
index 53ab8fa..ba488e5 100644
--- a/core/composer.json
+++ b/core/composer.json
@@ -31,7 +31,7 @@
"symfony/psr-http-message-bridge": "v0.2",
"zendframework/zend-diactoros": "~1.1",
"composer/semver": "~1.0",
- "paragonie/random_compat": "~1.0",
+ "paragonie/random_compat": "^1.0|^2.0",
"asm89/stack-cors": "~1.0"
},
"require-dev": {
diff --git a/core/lib/Drupal/Component/Utility/Crypt.php b/core/lib/Drupal/Component/Utility/Crypt.php
index ace4ebd..6ebdc4a 100644
--- a/core/lib/Drupal/Component/Utility/Crypt.php
+++ b/core/lib/Drupal/Component/Utility/Crypt.php
@@ -19,7 +19,8 @@ class Crypt {
*
* In PHP 7 and up, this uses the built-in PHP function random_bytes().
* In older PHP versions, this uses the random_bytes() function provided by
- * the random_compat library.
+ * the random_compat library, or the fallback hash-based generator from Drupal
+ * 7.x.
*
* @param int $count
* The number of characters (bytes) to return in the string.
@@ -28,7 +29,43 @@ class Crypt {
* A randomly generated string.
*/
public static function randomBytes($count) {
- return random_bytes($count);
+ try {
+ return random_bytes($count);
+ }
+ catch (\Exception $e) {
+ // $random_state does not use drupal_static as it stores random bytes.
+ static $random_state, $bytes;
+ // If the compatibility library fails, this simple hash-based PRNG will
+ // generate a good set of pseudo-random bytes on any system.
+ // Note that it may be important that our $random_state is passed
+ // through hash() prior to being rolled into $output, that the two hash()
+ // invocations are different, and that the extra input into the first one
+ // - the microtime() - is prepended rather than appended. This is to avoid
+ // directly leaking $random_state via the $output stream, which could
+ // allow for trivial prediction of further "random" numbers.
+ if (strlen($bytes) < $count) {
+ // Initialize on the first call. The $_SERVER variable includes user and
+ // system-specific information that varies a little with each page.
+ if (!isset($random_state)) {
+ $random_state = print_r($_SERVER, TRUE);
+ if (function_exists('getmypid')) {
+ // Further initialize with the somewhat random PHP process ID.
+ $random_state .= getmypid();
+ }
+ $bytes = '';
+ // Ensure mt_rand() is reseeded before calling it the first time.
+ mt_srand();
+ }
+
+ do {
+ $random_state = hash('sha256', microtime() . mt_rand() . $random_state);
+ $bytes .= hash('sha256', mt_rand() . $random_state, TRUE);
+ } while (strlen($bytes) < $count);
+ }
+ $output = substr($bytes, 0, $count);
+ $bytes = substr($bytes, $count);
+ return $output;
+ }
}
/**
diff --git a/core/lib/Drupal/Component/Utility/composer.json b/core/lib/Drupal/Component/Utility/composer.json
index a23634f..13671ef 100644
--- a/core/lib/Drupal/Component/Utility/composer.json
+++ b/core/lib/Drupal/Component/Utility/composer.json
@@ -6,7 +6,7 @@
"license": "GPL-2.0+",
"require": {
"php": ">=5.5.9",
- "paragonie/random_compat": "~1.0",
+ "paragonie/random_compat": "^1.0|^2.0",
"drupal/core-render": "~8.2"
},
"autoload": {
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index cbdafbc..e8f45eb 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -257,6 +257,44 @@ function system_requirements($phase) {
$requirements['php_opcache']['title'] = t('PHP OPcode caching');
}
+ if ($phase != 'update') {
+ // Test whether we have a good source of random bytes.
+ $requirements['php_random_bytes'] = array(
+ 'title' => t('Random number generation'),
+ );
+ try {
+ $bytes = random_bytes(10);
+ if (strlen($bytes) != 10) {
+ throw new \Exception(t('Tried to generate 10 random bytes, generated @count', array('@count' => strlen($bytes))));
+ }
+ $requirements['php_random_bytes']['value'] = t('Successful');
+ }
+ catch (\Exception $e) {
+ // If /dev/urandom is not available on a UNIX-like system, check whether
+ // open_basedir restrictions are the cause.
+ $open_basedir_blocks_urandom = FALSE;
+ if (DIRECTORY_SEPARATOR === '/' && !@is_readable('/dev/urandom')) {
+ $open_basedir = ini_get('open_basedir');
+ if ($open_basedir) {
+ $open_basedir_paths = explode(PATH_SEPARATOR, $open_basedir);
+ $open_basedir_blocks_urandom = !array_intersect(array('/dev', '/dev/', '/dev/urandom'), $open_basedir_paths);
+ }
+ }
+ $args = array(
+ ':drupal-php' => 'https://www.drupal.org/docs/7/system-requirements/php#csprng',
+ '%exception_message' => $e->getMessage(),
+ );
+ if ($open_basedir_blocks_urandom) {
+ $requirements['php_random_bytes']['description'] = t('Drupal is unable to generate highly randomized numbers, which means certain security features like password reset URLs are not as secure as they should be. Instead, only a slow, less-secure fallback generator is available. The most likely cause is that open_basedir restrictions are in effect and /dev/urandom is not on the whitelist. See the <a href=":drupal-php">system requirements</a> page for more information. %exception_message', $args);
+ }
+ else {
+ $requirements['php_random_bytes']['description'] = t('Drupal is unable to generate highly randomized numbers, which means certain security features like password reset URLs are not as secure as they should be. Instead, only a slow, less-secure fallback generator is available. See the <a href=":drupal-php">system requirements</a> page for more information. %exception_message', $args);
+ }
+ $requirements['php_random_bytes']['value'] = t('Less secure');
+ $requirements['php_random_bytes']['severity'] = REQUIREMENT_ERROR;
+ }
+ }
+
if ($phase == 'install' || $phase == 'update') {
// Test for PDO (database).
$requirements['database_extensions'] = array(
diff --git a/core/tests/Drupal/Tests/Component/Utility/CryptRandomFallbackTest.php b/core/tests/Drupal/Tests/Component/Utility/CryptRandomFallbackTest.php
new file mode 100644
index 0000000..de00da6
--- /dev/null
+++ b/core/tests/Drupal/Tests/Component/Utility/CryptRandomFallbackTest.php
@@ -0,0 +1,71 @@
+<?php
+
+namespace Drupal\Tests\Component\Utility;
+
+use Drupal\Tests\UnitTestCase;
+use Drupal\Component\Utility\Crypt;
+
+/**
+ * Tests random byte generation fallback exception situations.
+ *
+ * @group Utility
+ *
+ * @runTestsInSeparateProcesses
+ *
+ * @coversDefaultClass \Drupal\Component\Utility\Crypt
+ */
+class CryptRandomFallbackTest extends UnitTestCase {
+
+ static protected $functionCalled = 0;
+
+ /**
+ * Allows the test to confirm that the namespaced random_bytes() was called.
+ */
+ public static function functionCalled() {
+ static::$functionCalled++;
+ }
+
+ /**
+ * Tests random byte generation using the fallback generator.
+ *
+ * If the call to random_bytes() throws an exception, Crypt::random_bytes()
+ * should still return a useful string of random bytes.
+ *
+ * @covers ::randomBytes
+ *
+ * @see \Drupal\Tests\Component\Utility\CryptTest::testRandomBytes()
+ */
+ public function testRandomBytesFallback() {
+ // This loop is a copy of
+ // \Drupal\Tests\Component\Utility\CryptTest::testRandomBytes().
+ for ($i = 0; $i < 10; $i++) {
+ $count = rand(10, 10000);
+ // Check that different values are being generated.
+ $this->assertNotEquals(Crypt::randomBytes($count), Crypt::randomBytes($count));
+ // Check the length.
+ $this->assertEquals($count, strlen(Crypt::randomBytes($count)));
+ }
+ $this->assertEquals(30, static::$functionCalled, 'The namespaced function was called the expected number of times.');
+ }
+
+}
+
+namespace Drupal\Component\Utility;
+
+use \Drupal\Tests\Component\Utility\CryptRandomFallbackTest;
+
+/**
+ * Defines a function in same namespace as Drupal\Component\Utility\Crypt.
+ *
+ * Forces throwing an exception in this test environment because the function
+ * in the namespace is used in preference to the global function.
+ *
+ * @param int $count
+ * Matches the global function definition.
+ *
+ * @throws \Exception
+ */
+function random_bytes($count) {
+ CryptRandomFallbackTest::functionCalled();
+ throw new \Exception($count);
+}
diff --git a/core/tests/Drupal/Tests/Component/Utility/CryptTest.php b/core/tests/Drupal/Tests/Component/Utility/CryptTest.php
index ff1e6cc..42d6015 100644
--- a/core/tests/Drupal/Tests/Component/Utility/CryptTest.php
+++ b/core/tests/Drupal/Tests/Component/Utility/CryptTest.php
@@ -18,6 +18,8 @@ class CryptTest extends UnitTestCase {
* Tests random byte generation.
*
* @covers ::randomBytes
+ *
+ * @see \Drupal\Tests\Component\Utility\CryptRandomFallbackTest::testRandomBytesFallback
*/
public function testRandomBytes() {
for ($i = 1; $i < 10; $i++) {