merged branch fabpot/prng (PR #4763)
This PR was merged into the master branch. Commits -------aecc9b1
fixed tests when OpenSsl is not enabled in PHP, renamed a missnamed test, added missing license doc blocksca567b5
fixed CS5cdf696
added a SecureRandomInterface234f725
rename String to StringUtils5849855
moved the secure random dep for remember me as a constructor argument248703f
renamed Prng to SecureRandomc0c8972
simplified the Prng codee5dc7af
moved the secure random class from JMSSecurityExtraBundle to Symfony (closes #3595) Discussion ---------- [2.2][Security] Add a PRNG (closes #3595) As per #3595, I have moved the secure random class from JMSSecurityExtraBundle to Symfony. It has more impact than I expected ;) As you will see, the implementation has been refactored a bit. The most notable change is that Doctrine support has been moved to the bridge with the addition of a proper Doctrine seed provider (Doctrine is not a special case anymore). The Doctrine configuration has been moved to the DoctrineBundle: doctrine/DoctrineBundle#91 schmittjoh/JMSSecurityExtraBundle#65 removes the code that has been moved. --------------------------------------------------------------------------- by Seldaek at 2012-07-05T13:26:01Z I'm all for more security features, and both the String class & the Prng class for wrapping openssl make a lot of sense IMO, but I fail to see the use of the rest. If we just want a seed to have a fallback in case openssl is missing, I'd rather have a secret in the config.yml than a million classes to store the same secret in the DB. Maybe I'm missing something though? /cc @schmittjoh --------------------------------------------------------------------------- by schmittjoh at 2012-07-05T16:32:10Z Having the configuration in different places (SecurityBundle & DoctrineBundle) feels a bit weird. I would prefer an approach similar to ACL, or the user provider/firewall section with factories. The latter being a bit more work to implement and the former potentially asking for complaints about too tight coupling to Doctrine. Regarding testing, we probably need to move the disableOpenSsl method to the SecureRandom class in order to allow OpenSSL to be disabled for testing and we also need to change the byte generation algorithm to produce the same output for the same starting seed. I agree that it does not make sense to introduce an interface for SecureRandom as only the seed providers should be replaced. As for the seed itself, it is constantly updated and does not stay the same as in the beginning. Thus, we need a provider that we can write to, and not only read from. I'm also not sure about using OpenSSL on Windows as I have read enough resources which claimed that the entropy on Windows is not always good (including OpenSSL docs). Always using the custom seed provider at least always ensured proper entropy even if OpenSSL's speed issues have been fixed in newer PHP versions. --------------------------------------------------------------------------- by stof at 2012-07-05T16:44:24Z @schmittjoh everything is in SecurityBundle now as it does not use a database anymore --------------------------------------------------------------------------- by stof at 2012-07-05T16:44:59Z and there is no seed provider anymore either --------------------------------------------------------------------------- by schmittjoh at 2012-07-05T16:53:39Z Not having a seed provider is not such a good idea, but having a file-based seed provider is. --------------------------------------------------------------------------- by Seldaek at 2012-07-05T17:01:18Z @schmittjoh why would you need to replace the seed provider? Don't you think that people serious about security to the point that they would want a stronger seed provider would enable openssl instead? --------------------------------------------------------------------------- by stof at 2012-07-05T17:06:50Z Well, what I meant is that there is no interchangeable provider anymore. The Prng class uses the file directly. And btw, I think the Prng class should be mockable for tests, so it should either have an interface or not be final (I vote for adding an interface) --------------------------------------------------------------------------- by jalliot at 2012-07-09T18:46:12Z @fabpot @schmittjoh What about using more fallbacks for `openssl_random_pseudo_bytes` like in @Seldaek's post ["Unpredictable hashes for humans"](http://seld.be/notes/unpredictable-hashes-for-humans)? Trying `mcrypt_create_iv` first might also be faster. --------------------------------------------------------------------------- by Seldaek at 2012-07-10T08:52:46Z @jalliot I think mcrypt should be after if you make it use /dev/urandom, not 100% sure but openssl is probably higher quality than urandom. --------------------------------------------------------------------------- by schmittjoh at 2012-07-10T09:12:07Z The fallback algorithm that I added should be enough (it passes the statistical randomness tests). On Tue, Jul 10, 2012 at 10:52 AM, Jordi Boggiano < reply@reply.github.com > wrote: > @jalliot I think mcrypt should be after if you make it use /dev/urandom, > not 100% sure but openssl is probably higher quality than urandom. > > --- > Reply to this email directly or view it on GitHub: > https://github.com/symfony/symfony/pull/4763#issuecomment-6870145 > --------------------------------------------------------------------------- by stof at 2012-10-13T17:20:06Z @fabpot please send a PR to the doc so that this can be merged 😃 --------------------------------------------------------------------------- by stof at 2012-10-13T17:22:08Z hmm, actually, some comments have not been taken into account yet so it is not ready to be merged --------------------------------------------------------------------------- by stof at 2012-10-27T07:14:43Z you forgot the SecureRandom file --------------------------------------------------------------------------- by fabpot at 2012-10-27T08:49:54Z I think I've addressed all the comments. If everyone agree with the current implementation, I'm going to start updating the documentation. --------------------------------------------------------------------------- by fabpot at 2012-10-27T10:51:15Z I've fixed the remaining CS issues. --------------------------------------------------------------------------- by fabpot at 2012-10-28T07:00:31Z Documentation is here: symfony/symfony-docs#1858
This commit is contained in:
commit
d21584ec68
@ -138,5 +138,12 @@
|
||||
<argument type="service" id="security.context" />
|
||||
<argument type="service" id="security.encoder_factory" />
|
||||
</service>
|
||||
|
||||
<!-- Pseudorandom Number Generator -->
|
||||
<service id="security.secure_random" class="Symfony\Component\Security\Core\Util\SecureRandom">
|
||||
<tag name="monolog.logger" channel="security" />
|
||||
<argument>%kernel.cache_dir%/secure_random.seed</argument>
|
||||
<argument type="service" id="logger" on-invalid="ignore" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
@ -45,6 +45,12 @@
|
||||
class="%security.authentication.rememberme.services.persistent.class%"
|
||||
parent="security.authentication.rememberme.services.abstract"
|
||||
abstract="true">
|
||||
<argument type="collection" /> <!-- User Providers -->
|
||||
<argument /> <!-- Shared Token Key -->
|
||||
<argument /> <!-- Shared Provider Key -->
|
||||
<argument type="collection" /> <!-- Options -->
|
||||
<argument type="service" id="logger" on-invalid="null" />
|
||||
<argument type="service" id="security.secure_random" />
|
||||
</service>
|
||||
|
||||
<service id="security.authentication.rememberme.services.simplehash"
|
||||
|
@ -4,7 +4,8 @@ CHANGELOG
|
||||
2.2.0
|
||||
-----
|
||||
|
||||
* Added PBKDF2 Password encoder
|
||||
* added secure random number generator
|
||||
* added PBKDF2 Password encoder
|
||||
|
||||
2.1.0
|
||||
-----
|
||||
|
@ -11,6 +11,8 @@
|
||||
|
||||
namespace Symfony\Component\Security\Core\Encoder;
|
||||
|
||||
use Symfony\Component\Security\Core\Util\StringUtils;
|
||||
|
||||
/**
|
||||
* BasePasswordEncoder is the base class for all password encoders.
|
||||
*
|
||||
@ -77,15 +79,6 @@ abstract class BasePasswordEncoder implements PasswordEncoderInterface
|
||||
*/
|
||||
protected function comparePasswords($password1, $password2)
|
||||
{
|
||||
if (strlen($password1) !== strlen($password2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = 0;
|
||||
for ($i = 0; $i < strlen($password1); $i++) {
|
||||
$result |= ord($password1[$i]) ^ ord($password2[$i]);
|
||||
}
|
||||
|
||||
return 0 === $result;
|
||||
return StringUtils::equals($password1, $password2);
|
||||
}
|
||||
}
|
||||
|
114
src/Symfony/Component/Security/Core/Util/SecureRandom.php
Normal file
114
src/Symfony/Component/Security/Core/Util/SecureRandom.php
Normal file
@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Security\Core\Util;
|
||||
|
||||
use Symfony\Component\HttpKernel\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* A secure random number generator implementation.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
final class SecureRandom implements SecureRandomInterface
|
||||
{
|
||||
private $logger;
|
||||
private $useOpenSsl;
|
||||
private $seed;
|
||||
private $seedUpdated;
|
||||
private $seedLastUpdatedAt;
|
||||
private $seedFile;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* Be aware that a guessable seed will severely compromise the PRNG
|
||||
* algorithm that is employed.
|
||||
*
|
||||
* @param string $seedFile
|
||||
* @param LoggerInterface $logger
|
||||
*/
|
||||
public function __construct($seedFile = null, LoggerInterface $logger = null)
|
||||
{
|
||||
$this->seedFile = $seedFile;
|
||||
$this->logger = $logger;
|
||||
|
||||
// determine whether to use OpenSSL
|
||||
if (defined('PHP_WINDOWS_VERSION_BUILD') && version_compare(PHP_VERSION, '5.3.4', '<')) {
|
||||
$this->useOpenSsl = false;
|
||||
} elseif (!function_exists('openssl_random_pseudo_bytes')) {
|
||||
if (null !== $this->logger) {
|
||||
$this->logger->notice('It is recommended that you enable the "openssl" extension for random number generation.');
|
||||
}
|
||||
$this->useOpenSsl = false;
|
||||
} else {
|
||||
$this->useOpenSsl = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function nextBytes($nbBytes)
|
||||
{
|
||||
// try OpenSSL
|
||||
if ($this->useOpenSsl) {
|
||||
$bytes = openssl_random_pseudo_bytes($nbBytes, $strong);
|
||||
|
||||
if (false !== $bytes && true === $strong) {
|
||||
return $bytes;
|
||||
}
|
||||
|
||||
if (null !== $this->logger) {
|
||||
$this->logger->info('OpenSSL did not produce a secure random number.');
|
||||
}
|
||||
}
|
||||
|
||||
// initialize seed
|
||||
if (null === $this->seed) {
|
||||
if (null === $this->seedFile) {
|
||||
throw new \RuntimeException('You need to specify a file path to store the seed.');
|
||||
}
|
||||
|
||||
if (is_file($this->seedFile)) {
|
||||
list($this->seed, $this->seedLastUpdatedAt) = $this->readSeed();
|
||||
} else {
|
||||
$this->seed = uniqid(mt_rand(), true);
|
||||
$this->updateSeed();
|
||||
}
|
||||
}
|
||||
|
||||
$bytes = '';
|
||||
while (strlen($bytes) < $nbBytes) {
|
||||
static $incr = 1;
|
||||
$bytes .= hash('sha512', $incr++.$this->seed.uniqid(mt_rand(), true).$nbBytes, true);
|
||||
$this->seed = base64_encode(hash('sha512', $this->seed.$bytes.$nbBytes, true));
|
||||
$this->updateSeed();
|
||||
}
|
||||
|
||||
return substr($bytes, 0, $nbBytes);
|
||||
}
|
||||
|
||||
private function readSeed()
|
||||
{
|
||||
return json_decode(file_get_contents($this->seedFile));
|
||||
}
|
||||
|
||||
private function updateSeed()
|
||||
{
|
||||
if (!$this->seedUpdated && $this->seedLastUpdatedAt < time() - mt_rand(1, 10)) {
|
||||
file_put_contents($this->seedFile, json_encode(array($this->seed, microtime(true))));
|
||||
}
|
||||
|
||||
$this->seedUpdated = true;
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Security\Core\Util;
|
||||
|
||||
use Symfony\Component\HttpKernel\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Interface that needs to be implemented by all secure random number generators.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
interface SecureRandomInterface
|
||||
{
|
||||
/**
|
||||
* Generates the specified number of secure random bytes.
|
||||
*
|
||||
* @param integer $nbBytes
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function nextBytes($nbBytes);
|
||||
}
|
48
src/Symfony/Component/Security/Core/Util/StringUtils.php
Normal file
48
src/Symfony/Component/Security/Core/Util/StringUtils.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Security\Core\Util;
|
||||
|
||||
/**
|
||||
* String utility functions.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
final class StringUtils
|
||||
{
|
||||
final private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two strings.
|
||||
*
|
||||
* This method implements a constant-time algorithm to compare strings.
|
||||
*
|
||||
* @param string $str1 The first string
|
||||
* @param string $str2 The second string
|
||||
*
|
||||
* @return Boolean true if the two strings are the same, false otherwise
|
||||
*/
|
||||
public static function equals($str1, $str2)
|
||||
{
|
||||
if (strlen($str1) !== $c = strlen($str2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = 0;
|
||||
for ($i = 0; $i < $c; $i++) {
|
||||
$result |= ord($str1[$i]) ^ ord($str2[$i]);
|
||||
}
|
||||
|
||||
return 0 === $result;
|
||||
}
|
||||
}
|
@ -19,6 +19,7 @@ use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Component\Security\Core\Exception\CookieTheftException;
|
||||
use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentToken;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Util\SecureRandomInterface;
|
||||
|
||||
/**
|
||||
* Concrete implementation of the RememberMeServicesInterface which needs
|
||||
@ -30,6 +31,24 @@ use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
class PersistentTokenBasedRememberMeServices extends AbstractRememberMeServices
|
||||
{
|
||||
private $tokenProvider;
|
||||
private $secureRandom;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $userProviders
|
||||
* @param string $key
|
||||
* @param string $providerKey
|
||||
* @param array $options
|
||||
* @param LoggerInterface $logger
|
||||
* @param SecureRandomInterface $secureRandom
|
||||
*/
|
||||
public function __construct(array $userProviders, $key, $providerKey, array $options = array(), LoggerInterface $logger = null, SecureRandomInterface $secureRandom)
|
||||
{
|
||||
parent::__construct($userProviders, $key, $providerKey, $options, $logger);
|
||||
|
||||
$this->secureRandom = $secureRandom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the token provider
|
||||
@ -79,7 +98,7 @@ class PersistentTokenBasedRememberMeServices extends AbstractRememberMeServices
|
||||
}
|
||||
|
||||
$series = $persistentToken->getSeries();
|
||||
$tokenValue = $this->generateRandomValue();
|
||||
$tokenValue = $this->secureRandom->nextBytes(64);
|
||||
$this->tokenProvider->updateToken($series, $tokenValue, new \DateTime());
|
||||
$request->attributes->set(self::COOKIE_ATTR_NAME,
|
||||
new Cookie(
|
||||
@ -101,8 +120,8 @@ class PersistentTokenBasedRememberMeServices extends AbstractRememberMeServices
|
||||
*/
|
||||
protected function onLoginSuccess(Request $request, Response $response, TokenInterface $token)
|
||||
{
|
||||
$series = $this->generateRandomValue();
|
||||
$tokenValue = $this->generateRandomValue();
|
||||
$series = $this->secureRandom->nextBytes(64);
|
||||
$tokenValue = $this->secureRandom->nextBytes(64);
|
||||
|
||||
$this->tokenProvider->createNewToken(
|
||||
new PersistentToken(
|
||||
@ -126,26 +145,4 @@ class PersistentTokenBasedRememberMeServices extends AbstractRememberMeServices
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a cryptographically strong random value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function generateRandomValue()
|
||||
{
|
||||
if (function_exists('openssl_random_pseudo_bytes')) {
|
||||
$bytes = openssl_random_pseudo_bytes(64, $strong);
|
||||
|
||||
if (true === $strong && false !== $bytes) {
|
||||
return base64_encode($bytes);
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $this->logger) {
|
||||
$this->logger->warn('Could not produce a cryptographically strong random value. Please install/update the OpenSSL extension.');
|
||||
}
|
||||
|
||||
return base64_encode(hash('sha512', uniqid(mt_rand(), true), true));
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,14 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Security\Tests\Core\Util
|
||||
{
|
||||
use Symfony\Component\Security\Core\Util\ClassUtils;
|
||||
|
205
src/Symfony/Component/Security/Tests/Core/Util/SecureRandomTest.php
Executable file
205
src/Symfony/Component/Security/Tests/Core/Util/SecureRandomTest.php
Executable file
@ -0,0 +1,205 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Security\Tests\Core\Util;
|
||||
|
||||
use Symfony\Component\Security\Core\Util\NullSeedProvider;
|
||||
use Symfony\Component\Security\Core\Util\SecureRandom;
|
||||
|
||||
class SecureRandomTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* T1: Monobit test
|
||||
*
|
||||
* @dataProvider getSecureRandoms
|
||||
*/
|
||||
public function testMonobit($secureRandom)
|
||||
{
|
||||
$nbOnBits = substr_count($this->getBitSequence($secureRandom, 20000), '1');
|
||||
$this->assertTrue($nbOnBits > 9654 && $nbOnBits < 10346, 'Monobit test failed, number of turned on bits: '.$nbOnBits);
|
||||
}
|
||||
|
||||
/**
|
||||
* T2: Chi-square test with 15 degrees of freedom (chi-Quadrat-Anpassungstest)
|
||||
*
|
||||
* @dataProvider getSecureRandoms
|
||||
*/
|
||||
public function testPoker($secureRandom)
|
||||
{
|
||||
$b = $this->getBitSequence($secureRandom, 20000);
|
||||
$c = array();
|
||||
for ($i = 0; $i <= 15; $i++) {
|
||||
$c[$i] = 0;
|
||||
}
|
||||
|
||||
for ($j = 1; $j <= 5000; $j++) {
|
||||
$k = 4 * $j - 1;
|
||||
$c[8 * $b[$k - 3] + 4 * $b[$k - 2] + 2 * $b[$k - 1] + $b[$k]] += 1;
|
||||
}
|
||||
|
||||
$f = 0;
|
||||
for ($i = 0; $i <= 15; $i++) {
|
||||
$f += $c[$i] * $c[$i];
|
||||
}
|
||||
|
||||
$Y = 16/5000 * $f - 5000;
|
||||
|
||||
$this->assertTrue($Y > 1.03 && $Y < 57.4, 'Poker test failed, Y = '.$Y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run test
|
||||
*
|
||||
* @dataProvider getSecureRandoms
|
||||
*/
|
||||
public function testRun($secureRandom)
|
||||
{
|
||||
$b = $this->getBitSequence($secureRandom, 20000);
|
||||
|
||||
$runs = array();
|
||||
for ($i = 1; $i <= 6; $i++) {
|
||||
$runs[$i] = 0;
|
||||
}
|
||||
|
||||
$addRun = function($run) use (&$runs) {
|
||||
if ($run > 6) {
|
||||
$run = 6;
|
||||
}
|
||||
|
||||
$runs[$run] += 1;
|
||||
};
|
||||
|
||||
$currentRun = 0;
|
||||
$lastBit = null;
|
||||
for ($i = 0; $i < 20000; $i++) {
|
||||
if ($lastBit === $b[$i]) {
|
||||
$currentRun += 1;
|
||||
} else {
|
||||
if ($currentRun > 0) {
|
||||
$addRun($currentRun);
|
||||
}
|
||||
|
||||
$lastBit = $b[$i];
|
||||
$currentRun = 0;
|
||||
}
|
||||
}
|
||||
if ($currentRun > 0) {
|
||||
$addRun($currentRun);
|
||||
}
|
||||
|
||||
$this->assertTrue($runs[1] > 2267 && $runs[1] < 2733, 'Runs of length 1 outside of defined interval: '.$runs[1]);
|
||||
$this->assertTrue($runs[2] > 1079 && $runs[2] < 1421, 'Runs of length 2 outside of defined interval: '.$runs[2]);
|
||||
$this->assertTrue($runs[3] > 502 && $runs[3] < 748, 'Runs of length 3 outside of defined interval: '.$runs[3]);
|
||||
$this->assertTrue($runs[4] > 233 && $runs[4] < 402, 'Runs of length 4 outside of defined interval: '.$runs[4]);
|
||||
$this->assertTrue($runs[5] > 90 && $runs[5] < 223, 'Runs of length 5 outside of defined interval: '.$runs[5]);
|
||||
$this->assertTrue($runs[6] > 90 && $runs[6] < 233, 'Runs of length 6 outside of defined interval: '.$runs[6]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Long-run test
|
||||
*
|
||||
* @dataProvider getSecureRandoms
|
||||
*/
|
||||
public function testLongRun($secureRandom)
|
||||
{
|
||||
$b = $this->getBitSequence($secureRandom, 20000);
|
||||
|
||||
$longestRun = 0;
|
||||
$currentRun = $lastBit = null;
|
||||
for ($i = 0; $i < 20000; $i++) {
|
||||
if ($lastBit === $b[$i]) {
|
||||
$currentRun += 1;
|
||||
} else {
|
||||
if ($currentRun > $longestRun) {
|
||||
$longestRun = $currentRun;
|
||||
}
|
||||
$lastBit = $b[$i];
|
||||
$currentRun = 0;
|
||||
}
|
||||
}
|
||||
if ($currentRun > $longestRun) {
|
||||
$longestRun = $currentRun;
|
||||
}
|
||||
|
||||
$this->assertTrue($longestRun < 34, 'Failed longest run test: '.$longestRun);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serial Correlation (Autokorrelationstest)
|
||||
*
|
||||
* @dataProvider getSecureRandoms
|
||||
*/
|
||||
public function testSerialCorrelation($secureRandom)
|
||||
{
|
||||
$shift = rand(1, 5000);
|
||||
$b = $this->getBitSequence($secureRandom, 20000);
|
||||
|
||||
$Z = 0;
|
||||
for ($i = 0; $i < 5000; $i++) {
|
||||
$Z += $b[$i] === $b[$i + $shift] ? 1 : 0;
|
||||
}
|
||||
|
||||
$this->assertTrue($Z > 2326 && $Z < 2674, 'Failed serial correlation test: '.$Z);
|
||||
}
|
||||
|
||||
public function getSecureRandoms()
|
||||
{
|
||||
$secureRandoms = array();
|
||||
|
||||
// openssl
|
||||
$secureRandom = new SecureRandom();
|
||||
// only add if openssl is indeed present
|
||||
if ($this->hasOpenSsl($secureRandom)) {
|
||||
$secureRandoms[] = array($secureRandom);
|
||||
} else {
|
||||
$this->markTestSkipped('OpenSSL is not available');
|
||||
}
|
||||
|
||||
// no-openssl with custom seed provider
|
||||
$secureRandom = new SecureRandom(sys_get_temp_dir().'/_sf2.seed');
|
||||
$this->disableOpenSsl($secureRandom);
|
||||
$secureRandoms[] = array($secureRandom);
|
||||
|
||||
return $secureRandoms;
|
||||
}
|
||||
|
||||
protected function disableOpenSsl($secureRandom)
|
||||
{
|
||||
$ref = new \ReflectionProperty($secureRandom, 'useOpenSsl');
|
||||
$ref->setAccessible(true);
|
||||
$ref->setValue($secureRandom, false);
|
||||
$ref->setAccessible(false);
|
||||
}
|
||||
|
||||
protected function hasOpenSsl($secureRandom)
|
||||
{
|
||||
$ref = new \ReflectionProperty($secureRandom, 'useOpenSsl');
|
||||
$ref->setAccessible(true);
|
||||
|
||||
$ret = $ref->getValue($secureRandom);
|
||||
|
||||
$ref->setAccessible(false);
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
private function getBitSequence($secureRandom, $length)
|
||||
{
|
||||
$bitSequence = '';
|
||||
for ($i = 0; $i < $length; $i += 40) {
|
||||
$value = unpack('H*', $secureRandom->nextBytes(5));
|
||||
$value = str_pad(base_convert($value[1], 16, 2), 40, '0', STR_PAD_LEFT);
|
||||
$bitSequence .= $value;
|
||||
}
|
||||
|
||||
return substr($bitSequence, 0, $length);
|
||||
}
|
||||
}
|
23
src/Symfony/Component/Security/Tests/Core/Util/StringUtilsTest.php
Executable file
23
src/Symfony/Component/Security/Tests/Core/Util/StringUtilsTest.php
Executable file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Security\Tests\Core\Util;
|
||||
|
||||
use Symfony\Component\Security\Core\Util\StringUtils;
|
||||
|
||||
class StringUtilsTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testEquals()
|
||||
{
|
||||
$this->assertTrue(StringUtils::equals('password', 'password'));
|
||||
$this->assertFalse(StringUtils::equals('password', 'foo'));
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ use Symfony\Component\HttpFoundation\ResponseHeaderBag;
|
||||
use Symfony\Component\Security\Http\RememberMe\PersistentTokenBasedRememberMeServices;
|
||||
use Symfony\Component\Security\Core\Exception\TokenNotFoundException;
|
||||
use Symfony\Component\Security\Core\Exception\CookieTheftException;
|
||||
use Symfony\Component\Security\Core\Util\SecureRandom;
|
||||
|
||||
class PersistentTokenBasedRememberMeServicesTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
@ -318,7 +319,7 @@ class PersistentTokenBasedRememberMeServicesTest extends \PHPUnit_Framework_Test
|
||||
$userProvider = $this->getProvider();
|
||||
}
|
||||
|
||||
return new PersistentTokenBasedRememberMeServices(array($userProvider), 'fookey', 'fookey', $options, $logger);
|
||||
return new PersistentTokenBasedRememberMeServices(array($userProvider), 'fookey', 'fookey', $options, $logger, new SecureRandom());
|
||||
}
|
||||
|
||||
protected function getProvider()
|
||||
|
Reference in New Issue
Block a user