MaxSem has uploaded a new change for review. ( 
https://gerrit.wikimedia.org/r/405374 )

Change subject: DO NOT MERGE: clean up CryptRand for PHP7
......................................................................

DO NOT MERGE: clean up CryptRand for PHP7

This kills various shims and b/c hacks and makes this class a
thin wrapper around random_bytes() - so thin that one might question
if it's even needed.

Change-Id: Ic30376a90e66d8f00dab86e7e6466fb3a750b87d
---
M includes/ServiceWiring.php
M includes/installer/Installer.php
M includes/installer/i18n/en.json
M includes/installer/i18n/qqq.json
M includes/libs/CryptRand.php
M includes/utils/MWCryptRand.php
6 files changed, 30 insertions(+), 334 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/core 
refs/changes/74/405374/1

diff --git a/includes/ServiceWiring.php b/includes/ServiceWiring.php
index 79e5b84..3a04514 100644
--- a/includes/ServiceWiring.php
+++ b/includes/ServiceWiring.php
@@ -178,27 +178,8 @@
                return new WatchedItemQueryService( 
$services->getDBLoadBalancer() );
        },
 
-       'CryptRand' => function ( MediaWikiServices $services ) {
-               $secretKey = $services->getMainConfig()->get( 'SecretKey' );
+       'CryptRand' => function () {
                return new CryptRand(
-                       [
-                               // To try vary the system information of the 
state a bit more
-                               // by including the system's hostname into the 
state
-                               'wfHostname',
-                               // It's mostly worthless but throw the wiki's 
id into the data
-                               // for a little more variance
-                               'wfWikiID',
-                               // If we have a secret key set then throw it 
into the state as well
-                               function () use ( $secretKey ) {
-                                       return $secretKey ?: '';
-                               }
-                       ],
-                       // The config file is likely the most often edited file 
we know should
-                       // be around so include its stat info into the state.
-                       // The constant with its location will almost always be 
defined, as
-                       // WebStart.php defines MW_CONFIG_FILE to 
$IP/LocalSettings.php unless
-                       // being configured with MW_CONFIG_CALLBACK (e.g. the 
installer).
-                       defined( 'MW_CONFIG_FILE' ) ? [ MW_CONFIG_FILE ] : [],
                        LoggerFactory::getInstance( 'CryptRand' )
                );
        },
diff --git a/includes/installer/Installer.php b/includes/installer/Installer.php
index 5ea9bfe..dd44b51 100644
--- a/includes/installer/Installer.php
+++ b/includes/installer/Installer.php
@@ -1517,21 +1517,9 @@
        protected function doGenerateKeys( $keys ) {
                $status = Status::newGood();
 
-               $strong = true;
                foreach ( $keys as $name => $length ) {
-                       $secretKey = MWCryptRand::generateHex( $length, true );
-                       if ( !MWCryptRand::wasStrong() ) {
-                               $strong = false;
-                       }
-
+                       $secretKey = MWCryptRand::generateHex( $length );
                        $this->setVar( $name, $secretKey );
-               }
-
-               if ( !$strong ) {
-                       $names = array_keys( $keys );
-                       $names = preg_replace( '/^(.*)$/', '\$$1', $names );
-                       global $wgLang;
-                       $status->warning( 'config-insecure-keys', 
$wgLang->listToText( $names ), count( $names ) );
                }
 
                return $status;
diff --git a/includes/installer/i18n/en.json b/includes/installer/i18n/en.json
index 6d4c485..dd93f18 100644
--- a/includes/installer/i18n/en.json
+++ b/includes/installer/i18n/en.json
@@ -292,7 +292,6 @@
        "config-install-interwiki-exists": "<strong>Warning:</strong> The 
interwiki table seems to already have entries.\nSkipping default list.",
        "config-install-stats": "Initializing statistics",
        "config-install-keys": "Generating secret keys",
-       "config-insecure-keys": "<strong>Warning:</strong> {{PLURAL:$2|A secure 
key|Secure keys}} ($1) generated during installation {{PLURAL:$2|is|are}} not 
completely safe. Consider changing {{PLURAL:$2|it|them}} manually.",
        "config-install-updates": "Prevent running unneeded updates",
        "config-install-updates-failed": "<strong>Error:</strong> Inserting 
update keys into tables failed with the following error: $1",
        "config-install-sysop": "Creating administrator user account",
diff --git a/includes/installer/i18n/qqq.json b/includes/installer/i18n/qqq.json
index 2fc95ce..f287eee 100644
--- a/includes/installer/i18n/qqq.json
+++ b/includes/installer/i18n/qqq.json
@@ -312,7 +312,6 @@
        "config-install-interwiki-exists": "Error notice during the 
installation saying that one of the database tables is already set up, so it's 
continuing without taking that step.",
        "config-install-stats": 
"*{{msg-mw|Config-install-database}}\n*{{msg-mw|Config-install-tables}}\n*{{msg-mw|Config-install-schema}}\n*{{msg-mw|Config-install-user}}\n*{{msg-mw|Config-install-interwiki}}\n*{{msg-mw|Config-install-stats}}\n*{{msg-mw|Config-install-keys}}\n*{{msg-mw|Config-install-sysop}}\n*{{msg-mw|Config-install-mainpage}}",
        "config-install-keys": 
"*{{msg-mw|Config-install-database}}\n*{{msg-mw|Config-install-tables}}\n*{{msg-mw|Config-install-schema}}\n*{{msg-mw|Config-install-user}}\n*{{msg-mw|Config-install-interwiki}}\n*{{msg-mw|Config-install-stats}}\n*{{msg-mw|Config-install-keys}}\n*{{msg-mw|Config-install-sysop}}\n*{{msg-mw|Config-install-mainpage}}",
-       "config-insecure-keys": "Parameters:\n* $1 - A list of names of the 
secret keys that were generated.\n* $2 - the number of items in the list $1, to 
be used with PLURAL.",
        "config-install-updates": "Message indicating that the updatelog table 
is filled with keys of updates that won't be run when running database 
updates.\n\nSee 
also:\n*{{msg-mw|Config-install-database}}\n*{{msg-mw|Config-install-tables}}\n*{{msg-mw|Config-install-interwiki}}\n*{{msg-mw|Config-install-stats}}\n*{{msg-mw|Config-install-keys}}\n*{{msg-mw|Config-install-updates}}\n*{{msg-mw|Config-install-schema}}\n*{{msg-mw|Config-install-user}}\n*{{msg-mw|Config-install-sysop}}\n*{{msg-mw|Config-install-mainpage}}",
        "config-install-updates-failed": "Used as error message. Parameters:\n* 
$1 - detailed error message",
        "config-install-sysop": "Message indicates that the administrator user 
account is being created\n\nSee 
also:\n*{{msg-mw|Config-install-database}}\n*{{msg-mw|Config-install-tables}}\n*{{msg-mw|Config-install-schema}}\n*{{msg-mw|Config-install-user}}\n*{{msg-mw|Config-install-interwiki}}\n*{{msg-mw|Config-install-stats}}\n*{{msg-mw|Config-install-keys}}\n*{{msg-mw|Config-install-sysop}}\n*{{msg-mw|Config-install-mainpage}}",
diff --git a/includes/libs/CryptRand.php b/includes/libs/CryptRand.php
index 859d58b..0b716ba 100644
--- a/includes/libs/CryptRand.php
+++ b/includes/libs/CryptRand.php
@@ -27,183 +27,58 @@
 
 class CryptRand {
        /**
-        * Minimum number of iterations we want to make in our drift 
calculations.
+        * @deprecated since 1.31, unused
         */
        const MIN_ITERATIONS = 1000;
 
        /**
-        * Number of milliseconds we want to spend generating each separate byte
-        * of the final generated bytes.
-        * This is used in combination with the hash length to determine the 
duration
-        * we should spend doing drift calculations.
+        * @deprecated since 1.31, unused
         */
        const MSEC_PER_BYTE = 0.5;
-
-       /**
-        * A boolean indicating whether the previous random generation was done 
using
-        * cryptographically strong random number generator or not.
-        */
-       protected $strong = null;
-
-       /**
-        * List of functions to call to generate some random state
-        *
-        * @var callable[]
-        */
-       protected $randomFuncs = [];
-
-       /**
-        * List of files to generate some random state from
-        *
-        * @var string[]
-        */
-       protected $randomFiles = [];
 
        /**
         * @var LoggerInterface
         */
        protected $logger;
 
-       public function __construct( array $randomFuncs, array $randomFiles, 
LoggerInterface $logger ) {
-               $this->randomFuncs = $randomFuncs;
-               $this->randomFiles = $randomFiles;
+       public function __construct( LoggerInterface $logger ) {
                $this->logger = $logger;
        }
 
        /**
         * Initialize an initial random state based off of whatever we can find
+        *
+        * @deprecated since 1.31, unused and does nothing
+        *
         * @return string
         */
        protected function initialRandomState() {
-               // $_SERVER contains a variety of unstable user and system 
specific information
-               // It'll vary a little with each page, and vary even more with 
separate users
-               // It'll also vary slightly across different machines
-               $state = serialize( $_SERVER );
 
-               // Try to gather a little entropy from the different php rand 
sources
-               $state .= rand() . uniqid( mt_rand(), true );
-
-               // Include some information about the filesystem's current 
state in the random state
-               $files = $this->randomFiles;
-
-               // We know this file is here so grab some info about ourselves
-               $files[] = __FILE__;
-
-               // We must also have a parent folder, and with the usual file 
structure, a grandparent
-               $files[] = __DIR__;
-               $files[] = dirname( __DIR__ );
-
-               foreach ( $files as $file ) {
-                       MediaWiki\suppressWarnings();
-                       $stat = stat( $file );
-                       MediaWiki\restoreWarnings();
-                       if ( $stat ) {
-                               // stat() duplicates data into numeric and 
string keys so kill off all the numeric ones
-                               foreach ( $stat as $k => $v ) {
-                                       if ( is_numeric( $k ) ) {
-                                               unset( $k );
-                                       }
-                               }
-                               // The absolute filename itself will differ 
from install to install so don't leave it out
-                               $path = realpath( $file );
-                               if ( $path !== false ) {
-                                       $state .= $path;
-                               } else {
-                                       $state .= $file;
-                               }
-                               $state .= implode( '', $stat );
-                       } else {
-                               // The fact that the file isn't there is worth 
at least a
-                               // minuscule amount of entropy.
-                               $state .= '0';
-                       }
-               }
-
-               // Try and make this a little more unstable by including the 
varying process
-               // id of the php process we are running inside of if we are 
able to access it
-               if ( function_exists( 'getmypid' ) ) {
-                       $state .= getmypid();
-               }
-
-               // If available try to increase the instability of the data by 
throwing in
-               // the precise amount of memory that we happen to be using at 
the moment.
-               if ( function_exists( 'memory_get_usage' ) ) {
-                       $state .= memory_get_usage( true );
-               }
-
-               foreach ( $this->randomFuncs as $randomFunc ) {
-                       $state .= call_user_func( $randomFunc );
-               }
-
-               return $state;
+               return '';
        }
 
        /**
         * Randomly hash data while mixing in clock drift data for randomness
+        *
+        * @deprecated since 1.31, unused and does nothing
         *
         * @param string $data The data to randomly hash.
         * @return string The hashed bytes
         * @author Tim Starling
         */
        protected function driftHash( $data ) {
-               // Minimum number of iterations (to avoid slow operations 
causing the
-               // loop to gather little entropy)
-               $minIterations = self::MIN_ITERATIONS;
-               // Duration of time to spend doing calculations (in seconds)
-               $duration = ( self::MSEC_PER_BYTE / 1000 ) * 
MWCryptHash::hashLength();
-               // Create a buffer to use to trigger memory operations
-               $bufLength = 10000000;
-               $buffer = str_repeat( ' ', $bufLength );
-               $bufPos = 0;
-
-               // Iterate for $duration seconds or at least $minIterations 
number of iterations
-               $iterations = 0;
-               $startTime = microtime( true );
-               $currentTime = $startTime;
-               while ( $iterations < $minIterations || $currentTime - 
$startTime < $duration ) {
-                       // Trigger some memory writing to trigger some bus 
activity
-                       // This may create variance in the time between 
iterations
-                       $bufPos = ( $bufPos + 13 ) % $bufLength;
-                       $buffer[$bufPos] = ' ';
-                       // Add the drift between this iteration and the last in 
as entropy
-                       $nextTime = microtime( true );
-                       $delta = (int)( ( $nextTime - $currentTime ) * 1000000 
);
-                       $data .= $delta;
-                       // Every 100 iterations hash the data and entropy
-                       if ( $iterations % 100 === 0 ) {
-                               $data = sha1( $data );
-                       }
-                       $currentTime = $nextTime;
-                       $iterations++;
-               }
-               $timeTaken = $currentTime - $startTime;
-               $data = MWCryptHash::hash( $data );
-
-               $this->logger->debug( "Clock drift calculation " .
-                       "(time-taken=" . ( $timeTaken * 1000 ) . "ms, " .
-                       "iterations=$iterations, " .
-                       "time-per-iteration=" . ( $timeTaken / $iterations * 
1e6 ) . "us)" );
-
-               return $data;
+               return '';
        }
 
        /**
         * Return a rolling random state initially build using data from 
unstable sources
+        *
+        * @deprecated since 1.31, unused and does nothing
+        *
         * @return string A new weak random state
         */
        protected function randomState() {
-               static $state = null;
-               if ( is_null( $state ) ) {
-                       // Initialize the state with whatever unstable data we 
can find
-                       // It's important that this data is hashed right 
afterwards to prevent
-                       // it from being leaked into the output stream
-                       $state = MWCryptHash::hash( $this->initialRandomState() 
);
-               }
-               // Generate a new random state based on the initial random 
state or previous
-               // random state by combining it with clock drift
-               $state = $this->driftHash( $state );
-
-               return $state;
+               throw new Exception( __CLASS__ . ' now returns guaranteed 
cryptographically secure random, no weak cryptography is needed' );
        }
 
        /**
@@ -211,14 +86,12 @@
         * random bytes generation in the previously run generate* call
         * was cryptographically strong.
         *
+        * @deprecated since 1.31, always returns true
+        *
         * @return bool Returns true if the source was strong, false if not.
         */
        public function wasStrong() {
-               if ( is_null( $this->strong ) ) {
-                       throw new RuntimeException( __METHOD__ . ' called 
before generation of random data' );
-               }
-
-               return $this->strong;
+               return true;
        }
 
        /**
@@ -228,148 +101,11 @@
         * was cryptographically strong.
         *
         * @param int $bytes The number of bytes of random data to generate
-        * @param bool $forceStrong Pass true if you want generate to prefer 
cryptographically
-        *                          strong sources of entropy even if reading 
from them may steal
-        *                          more entropy from the system than optimal.
         * @return string Raw binary random data
         */
-       public function generate( $bytes, $forceStrong = false ) {
+       public function generate( $bytes ) {
                $bytes = floor( $bytes );
-               static $buffer = '';
-               if ( is_null( $this->strong ) ) {
-                       // Set strength to false initially until we know what 
source data is coming from
-                       $this->strong = true;
-               }
-
-               if ( strlen( $buffer ) < $bytes ) {
-                       // If available make use of PHP 7's random_bytes
-                       // On Linux, getrandom syscall will be used if 
available.
-                       // On Windows CryptGenRandom will always be used
-                       // On other platforms, /dev/urandom will be used.
-                       // Avoids polyfills from before php 7.0
-                       // All error situations will throw Exceptions and or 
Errors
-                       if ( PHP_VERSION_ID >= 70000
-                               || ( defined( 'HHVM_VERSION_ID' ) && 
HHVM_VERSION_ID >= 31101 )
-                       ) {
-                               $rem = $bytes - strlen( $buffer );
-                               $buffer .= random_bytes( $rem );
-                       }
-                       if ( strlen( $buffer ) >= $bytes ) {
-                               $this->strong = true;
-                       }
-               }
-
-               if ( strlen( $buffer ) < $bytes ) {
-                       // If available make use of mcrypt_create_iv URANDOM 
source to generate randomness
-                       // On unix-like systems this reads from /dev/urandom 
but does it without any buffering
-                       // and bypasses openbasedir restrictions, so it's 
preferable to reading directly
-                       // On Windows starting in PHP 5.3.0 Windows' native 
CryptGenRandom is used to generate
-                       // entropy so this is also preferable to just trying to 
read urandom because it may work
-                       // on Windows systems as well.
-                       if ( function_exists( 'mcrypt_create_iv' ) ) {
-                               $rem = $bytes - strlen( $buffer );
-                               $iv = mcrypt_create_iv( $rem, 
MCRYPT_DEV_URANDOM );
-                               if ( $iv === false ) {
-                                       $this->logger->debug( "mcrypt_create_iv 
returned false." );
-                               } else {
-                                       $buffer .= $iv;
-                                       $this->logger->debug( "mcrypt_create_iv 
generated " . strlen( $iv ) .
-                                               " bytes of randomness." );
-                               }
-                       }
-               }
-
-               if ( strlen( $buffer ) < $bytes ) {
-                       if ( function_exists( 'openssl_random_pseudo_bytes' ) ) 
{
-                               $rem = $bytes - strlen( $buffer );
-                               $openssl_bytes = openssl_random_pseudo_bytes( 
$rem, $openssl_strong );
-                               if ( $openssl_bytes === false ) {
-                                       $this->logger->debug( 
"openssl_random_pseudo_bytes returned false." );
-                               } else {
-                                       $buffer .= $openssl_bytes;
-                                       $this->logger->debug( 
"openssl_random_pseudo_bytes generated " .
-                                               strlen( $openssl_bytes ) . " 
bytes of " .
-                                               ( $openssl_strong ? "strong" : 
"weak" ) . " randomness." );
-                               }
-                               if ( strlen( $buffer ) >= $bytes ) {
-                                       // openssl tells us if the random 
source was strong, if some of our data was generated
-                                       // using it use it's say on whether the 
randomness is strong
-                                       $this->strong = !!$openssl_strong;
-                               }
-                       }
-               }
-
-               // Only read from urandom if we can control the buffer size or 
were passed forceStrong
-               if ( strlen( $buffer ) < $bytes &&
-                       ( function_exists( 'stream_set_read_buffer' ) || 
$forceStrong )
-               ) {
-                       $rem = $bytes - strlen( $buffer );
-                       if ( !function_exists( 'stream_set_read_buffer' ) && 
$forceStrong ) {
-                               $this->logger->debug( "Was forced to read from 
/dev/urandom " .
-                                       "without control over the buffer size." 
);
-                       }
-                       // /dev/urandom is generally considered the best 
possible commonly
-                       // available random source, and is available on most 
*nix systems.
-                       MediaWiki\suppressWarnings();
-                       $urandom = fopen( "/dev/urandom", "rb" );
-                       MediaWiki\restoreWarnings();
-
-                       // Attempt to read all our random data from urandom
-                       // php's fread always does buffered reads based on the 
stream's chunk_size
-                       // so in reality it will usually read more than the 
amount of data we're
-                       // asked for and not storing that risks depleting the 
system's random pool.
-                       // If stream_set_read_buffer is available set the 
chunk_size to the amount
-                       // of data we need. Otherwise read 8k, php's default 
chunk_size.
-                       if ( $urandom ) {
-                               // php's default chunk_size is 8k
-                               $chunk_size = 1024 * 8;
-                               if ( function_exists( 'stream_set_read_buffer' 
) ) {
-                                       // If possible set the chunk_size to 
the amount of data we need
-                                       stream_set_read_buffer( $urandom, $rem 
);
-                                       $chunk_size = $rem;
-                               }
-                               $random_bytes = fread( $urandom, max( 
$chunk_size, $rem ) );
-                               $buffer .= $random_bytes;
-                               fclose( $urandom );
-                               $this->logger->debug( "/dev/urandom generated " 
. strlen( $random_bytes ) .
-                                       " bytes of randomness." );
-
-                               if ( strlen( $buffer ) >= $bytes ) {
-                                       // urandom is always strong, set to 
true if all our data was generated using it
-                                       $this->strong = true;
-                               }
-                       } else {
-                               $this->logger->debug( "/dev/urandom could not 
be opened." );
-                       }
-               }
-
-               // If we cannot use or generate enough data from a secure source
-               // use this loop to generate a good set of pseudo random data.
-               // This works by initializing a random state using a pile of 
unstable data
-               // and continually shoving it through a hash along with a 
variable salt.
-               // We hash the random state with more salt to avoid the state 
from leaking
-               // out and being used to predict the /randomness/ that follows.
-               if ( strlen( $buffer ) < $bytes ) {
-                       $this->logger->debug( __METHOD__ .
-                               ": Falling back to using a pseudo random state 
to generate randomness." );
-               }
-               while ( strlen( $buffer ) < $bytes ) {
-                       $buffer .= MWCryptHash::hmac( $this->randomState(), 
strval( mt_rand() ) );
-                       // This code is never really cryptographically strong, 
if we use it
-                       // at all, then set strong to false.
-                       $this->strong = false;
-               }
-
-               // Once the buffer has been filled up with enough random data 
to fulfill
-               // the request shift off enough data to handle the request and 
leave the
-               // unused portion left inside the buffer for the next request 
for random data
-               $generated = substr( $buffer, 0, $bytes );
-               $buffer = substr( $buffer, $bytes );
-
-               $this->logger->debug( strlen( $buffer ) .
-                       " bytes of randomness leftover in the buffer." );
-
-               return $generated;
+               return random_bytes( $bytes );
        }
 
        /**
@@ -379,18 +115,15 @@
         * was cryptographically strong.
         *
         * @param int $chars The number of hex chars of random data to generate
-        * @param bool $forceStrong Pass true if you want generate to prefer 
cryptographically
-        *                          strong sources of entropy even if reading 
from them may steal
-        *                          more entropy from the system than optimal.
         * @return string Hexadecimal random data
         */
-       public function generateHex( $chars, $forceStrong = false ) {
+       public function generateHex( $chars ) {
                // hex strings are 2x the length of raw binary so we divide the 
length in half
                // odd numbers will result in a .5 that leads the generate() 
being 1 character
                // short, so we use ceil() to ensure that we always have enough 
bytes
                $bytes = ceil( $chars / 2 );
                // Generate the data and then convert it to a hex string
-               $hex = bin2hex( $this->generate( $bytes, $forceStrong ) );
+               $hex = bin2hex( $this->generate( $bytes ) );
 
                // A bit of paranoia here, the caller asked for a specific 
length of string
                // here, and it's possible (eg when given an odd number) that 
we may actually
diff --git a/includes/utils/MWCryptRand.php b/includes/utils/MWCryptRand.php
index 5818958..697b5bf 100644
--- a/includes/utils/MWCryptRand.php
+++ b/includes/utils/MWCryptRand.php
@@ -39,10 +39,12 @@
         * random bytes generation in the previously run generate* call
         * was cryptographically strong.
         *
+        * @deprecated since 1.31, always returns true
+        *
         * @return bool Returns true if the source was strong, false if not.
         */
        public static function wasStrong() {
-               return self::singleton()->wasStrong();
+               return true;
        }
 
        /**
@@ -52,13 +54,10 @@
         * was cryptographically strong.
         *
         * @param int $bytes The number of bytes of random data to generate
-        * @param bool $forceStrong Pass true if you want generate to prefer 
cryptographically
-        *                          strong sources of entropy even if reading 
from them may steal
-        *                          more entropy from the system than optimal.
         * @return string Raw binary random data
         */
-       public static function generate( $bytes, $forceStrong = false ) {
-               return self::singleton()->generate( $bytes, $forceStrong );
+       public static function generate( $bytes ) {
+               return self::singleton()->generate( $bytes );
        }
 
        /**
@@ -68,12 +67,9 @@
         * was cryptographically strong.
         *
         * @param int $chars The number of hex chars of random data to generate
-        * @param bool $forceStrong Pass true if you want generate to prefer 
cryptographically
-        *                          strong sources of entropy even if reading 
from them may steal
-        *                          more entropy from the system than optimal.
         * @return string Hexadecimal random data
         */
-       public static function generateHex( $chars, $forceStrong = false ) {
-               return self::singleton()->generateHex( $chars, $forceStrong );
+       public static function generateHex( $chars ) {
+               return self::singleton()->generateHex( $chars );
        }
 }

-- 
To view, visit https://gerrit.wikimedia.org/r/405374
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ic30376a90e66d8f00dab86e7e6466fb3a750b87d
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: MaxSem <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to