simplified the Prng code

This commit is contained in:
Fabien Potencier 2012-07-05 17:51:07 +02:00
parent e5dc7afe90
commit c0c89724b0
15 changed files with 33 additions and 320 deletions

View File

@ -1,68 +0,0 @@
<?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);
}
}
}

View File

@ -1,33 +0,0 @@
<?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());
}
}

View File

@ -1,43 +0,0 @@
<?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);
}
}
}

View File

@ -1,48 +0,0 @@
<?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;
}
}

View File

@ -1,57 +0,0 @@
<?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.');
}
}

View File

@ -87,7 +87,6 @@ class MainConfiguration implements ConfigurationInterface
{
$rootNode
->children()
->scalarNode('prng_seed_provider')->defaultNull()->end()
->arrayNode('acl')
->children()
->scalarNode('connection')

View File

@ -88,10 +88,6 @@ 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',

View File

@ -142,7 +142,7 @@
<!-- 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>%kernel.cache_dir%/prng.seed</argument>
<argument type="service" id="logger" on-invalid="ignore" />
</service>
</services>

View File

@ -2,7 +2,6 @@
$container->loadFromExtension('security', array(
'acl' => array(),
'prng_seed_provider' => 'custom_seed_provider',
'encoders' => array(
'JMS\FooBundle\Entity\User1' => 'plaintext',
'JMS\FooBundle\Entity\User2' => array(

View File

@ -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 prng-seed-provider="custom_seed_provider">
<config>
<acl />
<encoder class="JMS\FooBundle\Entity\User1" algorithm="plaintext" />

View File

@ -1,6 +1,5 @@
security:
acl: ~
prng_seed_provider: custom_seed_provider
encoders:
JMS\FooBundle\Entity\User1: plaintext
JMS\FooBundle\Entity\User2:

View File

@ -168,13 +168,6 @@ 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();

View File

@ -16,6 +16,7 @@ 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 Prng
@ -25,7 +26,7 @@ final class Prng
private $seed;
private $seedUpdated;
private $seedLastUpdatedAt;
private $seedProvider;
private $seedFile;
/**
* Constructor.
@ -33,12 +34,12 @@ final class Prng
* Be aware that a guessable seed will severely compromise the PRNG
* algorithm that is employed.
*
* @param SeedProviderInterface $provider
* @param LoggerInterface $logger
* @param string $seedFile
* @param LoggerInterface $logger
*/
public function __construct(SeedProviderInterface $provider = null, LoggerInterface $logger = null)
public function __construct($seedFile = null, LoggerInterface $logger = null)
{
$this->seedProvider = $provider;
$this->seedFile = $seedFile;
$this->logger = $logger;
// determine whether to use OpenSSL
@ -77,11 +78,16 @@ final class Prng
// initialize seed
if (null === $this->seed) {
if (null === $this->seedProvider) {
throw new \RuntimeException('You need to specify a custom seed provider.');
if (null === $this->seedFile) {
throw new \RuntimeException('You need to specify a file path to store the seed.');
}
list($this->seed, $this->seedLastUpdatedAt) = $this->seedProvider->loadSeed();
if (is_file($this->seedFile)) {
list($this->seed, $this->seedLastUpdatedAt) = $this->readSeed();
} else {
$this->seed = uniqid(mt_rand(), true);
$this->updateSeed();
}
}
$bytes = '';
@ -89,16 +95,23 @@ final class Prng
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;
}
$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;
}
}

View File

@ -1,37 +0,0 @@
<?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);
}

View File

@ -147,11 +147,11 @@ class PrngTest extends \PHPUnit_Framework_TestCase
$prngs = array();
// openssl with fallback
$prng = new Prng(new NullSeedProvider());
$prng = new Prng();
$prngs[] = array($prng);
// no-openssl with custom seed provider
$prng = new Prng(new NullSeedProvider());
$prng = new Prng(sys_get_temp_dir().'/_sf2.seed');
$this->disableOpenSsl($prng);
$prngs[] = array($prng);