moved the secure random class from JMSSecurityExtraBundle to Symfony (closes #3595)
This commit is contained in:
parent
bde2e26b69
commit
e5dc7afe90
@ -0,0 +1,68 @@
|
||||
<?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\Bridge\Doctrine\Security;
|
||||
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Symfony\Component\Security\Core\Util\SeedProviderInterface;
|
||||
|
||||
/**
|
||||
* Doctrine Seed Provider.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class DoctrineSeedProvider implements SeedProviderInterface
|
||||
{
|
||||
private $con;
|
||||
private $seedTableName;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Connection $con
|
||||
* @param string $tableName
|
||||
*/
|
||||
public function __construct(Connection $con, $tableName)
|
||||
{
|
||||
$this->con = $con;
|
||||
$this->seedTableName = $tableName;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadSeed()
|
||||
{
|
||||
$stmt = $this->con->executeQuery("SELECT seed, updated_at FROM {$this->seedTableName}");
|
||||
|
||||
if (false === $seed = $stmt->fetchColumn(0)) {
|
||||
throw new \RuntimeException('You need to initialize the generator by running the console command "init:prng".');
|
||||
}
|
||||
|
||||
$seedLastUpdatedAt = new \DateTime($stmt->fetchColumn(1));
|
||||
|
||||
return array($seed, $seedLastUpdatedAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function updateSeed($seed)
|
||||
{
|
||||
$params = array(':seed' => $seed, ':updatedAt' => new \DateTime());
|
||||
$types = array(':updatedAt' => Type::DATETIME);
|
||||
if (!$this->con->executeUpdate("UPDATE {$this->seedTableName} SET seed = :seed, updated_at = :updatedAt", $params, $types)) {
|
||||
$this->con->executeUpdate("INSERT INTO {$this->seedTableName} VALUES (:seed, :updatedAt)", $params, $types);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
<?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\Bridge\Doctrine\Security\EventListener;
|
||||
|
||||
use Symfony\Bridge\Doctrine\Security\PrngSchema;
|
||||
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;
|
||||
|
||||
/**
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class PrngSchemaListener
|
||||
{
|
||||
private $schema;
|
||||
|
||||
public function __construct(PrngSchema $schema)
|
||||
{
|
||||
$this->schema = $schema;
|
||||
}
|
||||
|
||||
public function postGenerateSchema(GenerateSchemaEventArgs $args)
|
||||
{
|
||||
$this->schema->addToSchema($args->getSchema());
|
||||
}
|
||||
}
|
43
src/Symfony/Bridge/Doctrine/Security/PrngSchema.php
Normal file
43
src/Symfony/Bridge/Doctrine/Security/PrngSchema.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?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\Bridge\Doctrine\Security;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
|
||||
/**
|
||||
* The DBAL schema that will be used if you choose the database-based seed provider.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
final class PrngSchema extends Schema
|
||||
{
|
||||
public function __construct($tableName)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$table = $this->createTable($tableName);
|
||||
$table->addColumn('seed', 'string', array(
|
||||
'length' => 88,
|
||||
'not_null' => true,
|
||||
));
|
||||
$table->addColumn('updated_at', 'datetime', array(
|
||||
'not_null' => true,
|
||||
));
|
||||
}
|
||||
|
||||
public function addToSchema(Schema $schema)
|
||||
{
|
||||
foreach ($this->getTables() as $table) {
|
||||
$schema->_addTable($table);
|
||||
}
|
||||
}
|
||||
}
|
@ -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\Bridge\Doctrine\Tests\Security;
|
||||
|
||||
use Symfony\Bridge\Doctrine\Security\DoctrineSeedProvider;
|
||||
use Symfony\Bridge\Doctrine\Security\PrngSchema;
|
||||
use Symfony\Component\Security\Core\Util\Prng;
|
||||
use Symfony\Component\Security\Tests\Core\Util\PrngTest;
|
||||
use Doctrine\DBAL\DriverManager;
|
||||
use Doctrine\DBAL\Connection;
|
||||
|
||||
class DoctrineSeedProviderTest extends PrngTest
|
||||
{
|
||||
public function getPrngs()
|
||||
{
|
||||
$con = DriverManager::getConnection(array(
|
||||
'driver' => 'pdo_sqlite',
|
||||
'memory' => true
|
||||
));
|
||||
|
||||
$schema = new PrngSchema('seed_table');
|
||||
foreach ($schema->toSql($con->getDatabasePlatform()) as $sql) {
|
||||
$con->executeQuery($sql);
|
||||
}
|
||||
$con->executeQuery("INSERT INTO seed_table VALUES (:seed, :updatedAt)", array(
|
||||
':seed' => base64_encode(hash('sha512', uniqid(mt_rand(), true), true)),
|
||||
':updatedAt' => date('Y-m-d H:i:s'),
|
||||
));
|
||||
|
||||
// no-openssl with database seed provider
|
||||
$prng = new Prng(new DoctrineSeedProvider($con, 'seed_table'));
|
||||
$this->disableOpenSsl($prng);
|
||||
|
||||
$prngs = parent::getPrngs();
|
||||
$prngs[] = array($prng);
|
||||
|
||||
return $prngs;
|
||||
}
|
||||
}
|
57
src/Symfony/Bundle/SecurityBundle/Command/InitPrngCommand.php
Executable file
57
src/Symfony/Bundle/SecurityBundle/Command/InitPrngCommand.php
Executable file
@ -0,0 +1,57 @@
|
||||
<?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\Bundle\SecurityBundle\Command;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
|
||||
/**
|
||||
* Initializes a custom PRNG seed provider.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class InitPrngCommand extends ContainerAwareCommand
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('init:prng');
|
||||
->addArgument('phrase', InputArgument::REQUIRED, 'A random string');
|
||||
->setDescription('Initialize a custom PRNG seed provider')
|
||||
->setHelp(<<<EOF
|
||||
The <info>%command.name%</info> command initializes a custom PRNG seed provider:
|
||||
|
||||
<info>php %command.full_name% ABCDE...</info>
|
||||
|
||||
The argument should be a random string, whatever comes to your mind right now.
|
||||
You do not need to remember it, it does not need to be cryptic, or long, and it
|
||||
will not be stored in a decipherable way. One restriction however, you should
|
||||
not let this be generated in an automated fashion.
|
||||
EOF
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
if (!$this->getContainer()->has('security.prng_seed_provider')) {
|
||||
throw new \RuntimeException('No seed provider has been configured under path "secure.prng".');
|
||||
}
|
||||
|
||||
$this->getContainer()->get('security.prng_seed_provider')->updateSeed(base64_encode(hash('sha512', $input->getArgument('phrase'), true)));
|
||||
|
||||
$output->writeln('The CSPRNG has been initialized successfully.');
|
||||
}
|
||||
}
|
@ -87,6 +87,7 @@ class MainConfiguration implements ConfigurationInterface
|
||||
{
|
||||
$rootNode
|
||||
->children()
|
||||
->scalarNode('prng_seed_provider')->defaultNull()->end()
|
||||
->arrayNode('acl')
|
||||
->children()
|
||||
->scalarNode('connection')
|
||||
|
@ -88,6 +88,10 @@ class SecurityExtension extends Extension
|
||||
$this->aclLoad($config['acl'], $container);
|
||||
}
|
||||
|
||||
if (null !== $config['prng_seed_provider']) {
|
||||
$container->setAlias('security.prng_seed_provider', $config['prng_seed_provider']);
|
||||
}
|
||||
|
||||
// add some required classes for compilation
|
||||
$this->addClassesToCompile(array(
|
||||
'Symfony\\Component\\Security\\Http\\Firewall',
|
||||
|
@ -138,5 +138,12 @@
|
||||
<argument type="service" id="security.context" />
|
||||
<argument type="service" id="security.encoder_factory" />
|
||||
</service>
|
||||
|
||||
<!-- Pseudorandom Number Generator -->
|
||||
<service id="security.prng" class="Symfony\Component\Security\Core\Util\Prng">
|
||||
<tag name="monolog.logger" channel="security" />
|
||||
<argument type="service" id="security.prng_seed_provider" on-invalid="ignore" />
|
||||
<argument type="service" id="logger" on-invalid="ignore" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
@ -45,6 +45,7 @@
|
||||
class="%security.authentication.rememberme.services.persistent.class%"
|
||||
parent="security.authentication.rememberme.services.abstract"
|
||||
abstract="true">
|
||||
<call method="setPrng"><argument type="service" id="security.prng" /></call>
|
||||
</service>
|
||||
|
||||
<service id="security.authentication.rememberme.services.simplehash"
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
$container->loadFromExtension('security', array(
|
||||
'acl' => array(),
|
||||
'prng_seed_provider' => 'custom_seed_provider',
|
||||
'encoders' => array(
|
||||
'JMS\FooBundle\Entity\User1' => 'plaintext',
|
||||
'JMS\FooBundle\Entity\User2' => array(
|
||||
|
@ -5,7 +5,7 @@
|
||||
xmlns:srv="http://symfony.com/schema/dic/services"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||
|
||||
<config>
|
||||
<config prng-seed-provider="custom_seed_provider">
|
||||
<acl />
|
||||
|
||||
<encoder class="JMS\FooBundle\Entity\User1" algorithm="plaintext" />
|
||||
|
@ -1,5 +1,6 @@
|
||||
security:
|
||||
acl: ~
|
||||
prng_seed_provider: custom_seed_provider
|
||||
encoders:
|
||||
JMS\FooBundle\Entity\User1: plaintext
|
||||
JMS\FooBundle\Entity\User2:
|
||||
|
@ -168,6 +168,13 @@ abstract class SecurityExtensionTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertEquals('foo', (string) $container->getAlias('security.acl.provider'));
|
||||
}
|
||||
|
||||
public function testSeedProvider()
|
||||
{
|
||||
$container = $this->getContainer('container1');
|
||||
|
||||
$this->assertEquals('custom_seed_provider', (string) $container->getAlias('security.prng_seed_provider'));
|
||||
}
|
||||
|
||||
protected function getContainer($file)
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
|
@ -9,6 +9,7 @@ CHANGELOG
|
||||
2.1.0
|
||||
-----
|
||||
|
||||
* added secure random number generator
|
||||
* [BC BREAK] The signature of ExceptionListener has changed
|
||||
* changed the HttpUtils constructor signature to take a UrlGenerator and a UrlMatcher instead of a Router
|
||||
* EncoderFactoryInterface::getEncoder() can now also take a class name as an argument
|
||||
|
@ -11,6 +11,8 @@
|
||||
|
||||
namespace Symfony\Component\Security\Core\Encoder;
|
||||
|
||||
use Symfony\Component\Security\Core\Util\String;
|
||||
|
||||
/**
|
||||
* 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 String::equals($password1, $password2);
|
||||
}
|
||||
}
|
||||
|
104
src/Symfony/Component/Security/Core/Util/Prng.php
Normal file
104
src/Symfony/Component/Security/Core/Util/Prng.php
Normal file
@ -0,0 +1,104 @@
|
||||
<?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 Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
final class Prng
|
||||
{
|
||||
private $logger;
|
||||
private $useOpenSsl;
|
||||
private $seed;
|
||||
private $seedUpdated;
|
||||
private $seedLastUpdatedAt;
|
||||
private $seedProvider;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* Be aware that a guessable seed will severely compromise the PRNG
|
||||
* algorithm that is employed.
|
||||
*
|
||||
* @param SeedProviderInterface $provider
|
||||
* @param LoggerInterface $logger
|
||||
*/
|
||||
public function __construct(SeedProviderInterface $provider = null, LoggerInterface $logger = null)
|
||||
{
|
||||
$this->seedProvider = $provider;
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the specified number of secure random bytes.
|
||||
*
|
||||
* @param integer $nbBytes
|
||||
* @return string
|
||||
*/
|
||||
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->seedProvider) {
|
||||
throw new \RuntimeException('You need to specify a custom seed provider.');
|
||||
}
|
||||
|
||||
list($this->seed, $this->seedLastUpdatedAt) = $this->seedProvider->loadSeed();
|
||||
}
|
||||
|
||||
$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));
|
||||
|
||||
if (!$this->seedUpdated && $this->seedLastUpdatedAt->getTimestamp() < time() - mt_rand(1, 10)) {
|
||||
if (null !== $this->seedProvider) {
|
||||
$this->seedProvider->updateSeed($this->seed);
|
||||
}
|
||||
|
||||
$this->seedUpdated = true;
|
||||
}
|
||||
}
|
||||
|
||||
return substr($bytes, 0, $nbBytes);
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* Seed Provider Interface.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
interface SeedProviderInterface
|
||||
{
|
||||
/**
|
||||
* Loads the initial seed.
|
||||
*
|
||||
* Whatever is returned from this method, it should not be guessable.
|
||||
*
|
||||
* @return array of the format array(string, DateTime) where string is the
|
||||
* initial seed, and DateTime is the last time it was updated
|
||||
*/
|
||||
function loadSeed();
|
||||
|
||||
/**
|
||||
* Updates the seed.
|
||||
*
|
||||
* @param string $seed
|
||||
*/
|
||||
function updateSeed($seed);
|
||||
}
|
48
src/Symfony/Component/Security/Core/Util/String.php
Normal file
48
src/Symfony/Component/Security/Core/Util/String.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 String
|
||||
{
|
||||
private final 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\Prng;
|
||||
|
||||
/**
|
||||
* Concrete implementation of the RememberMeServicesInterface which needs
|
||||
@ -30,6 +31,12 @@ use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
class PersistentTokenBasedRememberMeServices extends AbstractRememberMeServices
|
||||
{
|
||||
private $tokenProvider;
|
||||
private $prng;
|
||||
|
||||
public function setPrng(Prng $prng)
|
||||
{
|
||||
$this->prng = $prng;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the token provider
|
||||
@ -79,7 +86,7 @@ class PersistentTokenBasedRememberMeServices extends AbstractRememberMeServices
|
||||
}
|
||||
|
||||
$series = $persistentToken->getSeries();
|
||||
$tokenValue = $this->generateRandomValue();
|
||||
$tokenValue = $this->prng->nextBytes(64);
|
||||
$this->tokenProvider->updateToken($series, $tokenValue, new \DateTime());
|
||||
$request->attributes->set(self::COOKIE_ATTR_NAME,
|
||||
new Cookie(
|
||||
@ -101,8 +108,8 @@ class PersistentTokenBasedRememberMeServices extends AbstractRememberMeServices
|
||||
*/
|
||||
protected function onLoginSuccess(Request $request, Response $response, TokenInterface $token)
|
||||
{
|
||||
$series = $this->generateRandomValue();
|
||||
$tokenValue = $this->generateRandomValue();
|
||||
$series = $this->prng->nextBytes(64);
|
||||
$tokenValue = $this->prng->nextBytes(64);
|
||||
|
||||
$this->tokenProvider->createNewToken(
|
||||
new PersistentToken(
|
||||
@ -126,26 +133,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));
|
||||
}
|
||||
}
|
||||
|
179
src/Symfony/Component/Security/Tests/Core/Util/PrngTest.php
Executable file
179
src/Symfony/Component/Security/Tests/Core/Util/PrngTest.php
Executable file
@ -0,0 +1,179 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Security\Tests\Core\Util;
|
||||
|
||||
use Symfony\Component\Security\Core\Util\NullSeedProvider;
|
||||
use Symfony\Component\Security\Core\Util\PrngSchema;
|
||||
use Symfony\Component\Security\Core\Util\Prng;
|
||||
|
||||
class PrngTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* T1: Monobit test
|
||||
*
|
||||
* @dataProvider getPrngs
|
||||
*/
|
||||
public function testMonobit($prng)
|
||||
{
|
||||
$nbOnBits = substr_count($this->getBitSequence($prng, 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 getPrngs
|
||||
*/
|
||||
public function testPoker($prng)
|
||||
{
|
||||
$b = $this->getBitSequence($prng, 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 getPrngs
|
||||
*/
|
||||
public function testRun($prng)
|
||||
{
|
||||
$b = $this->getBitSequence($prng, 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 getPrngs
|
||||
*/
|
||||
public function testLongRun($prng)
|
||||
{
|
||||
$b = $this->getBitSequence($prng, 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 getPrngs
|
||||
*/
|
||||
public function testSerialCorrelation($prng)
|
||||
{
|
||||
$shift = rand(1, 5000);
|
||||
$b = $this->getBitSequence($prng, 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 getPrngs()
|
||||
{
|
||||
$prngs = array();
|
||||
|
||||
// openssl with fallback
|
||||
$prng = new Prng(new NullSeedProvider());
|
||||
$prngs[] = array($prng);
|
||||
|
||||
// no-openssl with custom seed provider
|
||||
$prng = new Prng(new NullSeedProvider());
|
||||
$this->disableOpenSsl($prng);
|
||||
$prngs[] = array($prng);
|
||||
|
||||
return $prngs;
|
||||
}
|
||||
|
||||
protected function disableOpenSsl($prng)
|
||||
{
|
||||
$ref = new \ReflectionProperty($prng, 'useOpenSsl');
|
||||
$ref->setAccessible(true);
|
||||
$ref->setValue($prng, false);
|
||||
}
|
||||
|
||||
private function getBitSequence($prng, $length)
|
||||
{
|
||||
$bitSequence = '';
|
||||
for ($i=0;$i<$length; $i+=40) {
|
||||
$value = unpack('H*', $prng->nextBytes(5));
|
||||
$value = str_pad(base_convert($value[1], 16, 2), 40, '0', STR_PAD_LEFT);
|
||||
$bitSequence .= $value;
|
||||
}
|
||||
|
||||
return substr($bitSequence, 0, $length);
|
||||
}
|
||||
}
|
14
src/Symfony/Component/Security/Tests/Core/Util/StringTest.php
Executable file
14
src/Symfony/Component/Security/Tests/Core/Util/StringTest.php
Executable file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Security\Tests\Core\Util;
|
||||
|
||||
use Symfony\Component\Security\Core\Util\String;
|
||||
|
||||
class StringTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testEquals()
|
||||
{
|
||||
$this->assertTrue(String::equals('password', 'password'));
|
||||
$this->assertFalse(String::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\Prng;
|
||||
|
||||
class PersistentTokenBasedRememberMeServicesTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
@ -318,7 +319,10 @@ class PersistentTokenBasedRememberMeServicesTest extends \PHPUnit_Framework_Test
|
||||
$userProvider = $this->getProvider();
|
||||
}
|
||||
|
||||
return new PersistentTokenBasedRememberMeServices(array($userProvider), 'fookey', 'fookey', $options, $logger);
|
||||
$r = new PersistentTokenBasedRememberMeServices(array($userProvider), 'fookey', 'fookey', $options, $logger);
|
||||
$r->setPrng(new Prng());
|
||||
|
||||
return $r;
|
||||
}
|
||||
|
||||
protected function getProvider()
|
||||
|
Reference in New Issue
Block a user