From 1300fece5f00b1032ba61198e702737143c6dd7d Mon Sep 17 00:00:00 2001 From: Ashura Date: Wed, 14 Feb 2018 16:32:53 +0100 Subject: [PATCH] [Security] Add configuration for Argon2i encryption --- .../DependencyInjection/MainConfiguration.php | 3 + .../DependencyInjection/SecurityExtension.php | 6 +- .../CompleteConfigurationTest.php | 57 +++++++++++++++++-- .../Fixtures/php/argon2i_encoder.php | 14 ++--- .../Fixtures/xml/argon2i_encoder.xml | 24 ++++---- .../Fixtures/yml/argon2i_encoder.yml | 14 ++--- .../Bundle/SecurityBundle/composer.json | 2 +- .../Core/Encoder/Argon2iPasswordEncoder.php | 23 +++++++- .../Security/Core/Encoder/EncoderFactory.php | 6 +- .../Encoder/Argon2iPasswordEncoderTest.php | 8 +++ 10 files changed, 118 insertions(+), 39 deletions(-) diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php index a8c1cd52a5..55dd12157c 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php @@ -385,6 +385,9 @@ class MainConfiguration implements ConfigurationInterface ->max(31) ->defaultValue(13) ->end() + ->scalarNode('memory_cost')->defaultNull()->end() + ->scalarNode('time_cost')->defaultNull()->end() + ->scalarNode('threads')->defaultNull()->end() ->scalarNode('id')->end() ->end() ->end() diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index e3ab18570e..3b56ac54dd 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -523,7 +523,11 @@ class SecurityExtension extends Extension return array( 'class' => 'Symfony\Component\Security\Core\Encoder\Argon2iPasswordEncoder', - 'arguments' => array(), + 'arguments' => array( + $config['memory_cost'], + $config['time_cost'], + $config['threads'], + ), ); } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php index cedc5dfa06..9302e5377e 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php @@ -285,6 +285,9 @@ abstract class CompleteConfigurationTest extends TestCase 'key_length' => 40, 'ignore_case' => false, 'cost' => 13, + 'memory_cost' => null, + 'time_cost' => null, + 'threads' => null, ), 'JMS\FooBundle\Entity\User3' => array( 'algorithm' => 'md5', @@ -294,6 +297,9 @@ abstract class CompleteConfigurationTest extends TestCase 'encode_as_base64' => true, 'iterations' => 5000, 'cost' => 13, + 'memory_cost' => null, + 'time_cost' => null, + 'threads' => null, ), 'JMS\FooBundle\Entity\User4' => new Reference('security.encoder.foo'), 'JMS\FooBundle\Entity\User5' => array( @@ -307,16 +313,57 @@ abstract class CompleteConfigurationTest extends TestCase )), $container->getDefinition('security.encoder_factory.generic')->getArguments()); } - public function testArgon2iEncoder() + public function testEncodersWithLibsodium() { if (!Argon2iPasswordEncoder::isSupported()) { $this->markTestSkipped('Argon2i algorithm is not supported.'); } - $this->assertSame(array(array('JMS\FooBundle\Entity\User7' => array( - 'class' => 'Symfony\Component\Security\Core\Encoder\Argon2iPasswordEncoder', - 'arguments' => array(), - ))), $this->getContainer('argon2i_encoder')->getDefinition('security.encoder_factory.generic')->getArguments()); + $container = $this->getContainer('argon2i_encoder'); + + $this->assertEquals(array(array( + 'JMS\FooBundle\Entity\User1' => array( + 'class' => 'Symfony\Component\Security\Core\Encoder\PlaintextPasswordEncoder', + 'arguments' => array(false), + ), + 'JMS\FooBundle\Entity\User2' => array( + 'algorithm' => 'sha1', + 'encode_as_base64' => false, + 'iterations' => 5, + 'hash_algorithm' => 'sha512', + 'key_length' => 40, + 'ignore_case' => false, + 'cost' => 13, + 'memory_cost' => null, + 'time_cost' => null, + 'threads' => null, + ), + 'JMS\FooBundle\Entity\User3' => array( + 'algorithm' => 'md5', + 'hash_algorithm' => 'sha512', + 'key_length' => 40, + 'ignore_case' => false, + 'encode_as_base64' => true, + 'iterations' => 5000, + 'cost' => 13, + 'memory_cost' => null, + 'time_cost' => null, + 'threads' => null, + ), + 'JMS\FooBundle\Entity\User4' => new Reference('security.encoder.foo'), + 'JMS\FooBundle\Entity\User5' => array( + 'class' => 'Symfony\Component\Security\Core\Encoder\Pbkdf2PasswordEncoder', + 'arguments' => array('sha1', false, 5, 30), + ), + 'JMS\FooBundle\Entity\User6' => array( + 'class' => 'Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder', + 'arguments' => array(15), + ), + 'JMS\FooBundle\Entity\User7' => array( + 'class' => 'Symfony\Component\Security\Core\Encoder\Argon2iPasswordEncoder', + 'arguments' => array(256, 1, 2), + ), + )), $container->getDefinition('security.encoder_factory.generic')->getArguments()); } public function testRememberMeThrowExceptionsDefault() diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/argon2i_encoder.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/argon2i_encoder.php index d315f88170..ec40e29de1 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/argon2i_encoder.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/argon2i_encoder.php @@ -1,18 +1,14 @@ load('container1.php', $container); + $container->loadFromExtension('security', array( 'encoders' => array( 'JMS\FooBundle\Entity\User7' => array( 'algorithm' => 'argon2i', - ), - ), - 'providers' => array( - 'default' => array('id' => 'foo'), - ), - 'firewalls' => array( - 'main' => array( - 'form_login' => false, - 'http_basic' => null, + 'memory_cost' => 256, + 'time_cost' => 1, + 'threads' => 2, ), ), )); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/argon2i_encoder.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/argon2i_encoder.xml index d9ff22ab82..83794384ab 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/argon2i_encoder.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/argon2i_encoder.xml @@ -1,18 +1,16 @@ - + - - + + + - + + + - - - - - - + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/argon2i_encoder.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/argon2i_encoder.yml index 70381f8d85..6abd4d0798 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/argon2i_encoder.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/argon2i_encoder.yml @@ -1,12 +1,10 @@ +imports: + - { resource: container1.yml } + security: encoders: JMS\FooBundle\Entity\User7: algorithm: argon2i - - providers: - default: { id: foo } - - firewalls: - main: - form_login: false - http_basic: ~ + memory_cost: 256 + time_cost: 1 + threads: 2 diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index 2eff4d845d..ab4635b04f 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -18,7 +18,7 @@ "require": { "php": "^7.1.3", "ext-xml": "*", - "symfony/security": "~3.4|~4.0", + "symfony/security": "~4.1", "symfony/dependency-injection": "^3.4.3|^4.0.3", "symfony/http-kernel": "~3.4|~4.0" }, diff --git a/src/Symfony/Component/Security/Core/Encoder/Argon2iPasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/Argon2iPasswordEncoder.php index 2aa011c7cb..195fad6a23 100644 --- a/src/Symfony/Component/Security/Core/Encoder/Argon2iPasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/Argon2iPasswordEncoder.php @@ -17,9 +17,30 @@ use Symfony\Component\Security\Core\Exception\BadCredentialsException; * Argon2iPasswordEncoder uses the Argon2i hashing algorithm. * * @author Zan Baldwin + * @author Dominik Müller */ class Argon2iPasswordEncoder extends BasePasswordEncoder implements SelfSaltingEncoderInterface { + private $config = array(); + + /** + * Argon2iPasswordEncoder constructor. + * + * @param int|null $memoryCost memory usage of the algorithm + * @param int|null $timeCost number of iterations + * @param int|null $threads number of parallel threads + */ + public function __construct(int $memoryCost = null, int $timeCost = null, int $threads = null) + { + if (\defined('PASSWORD_ARGON2I')) { + $this->config = array( + 'memory_cost' => $memoryCost ?? \PASSWORD_ARGON2_DEFAULT_MEMORY_COST, + 'time_cost' => $timeCost ?? \PASSWORD_ARGON2_DEFAULT_TIME_COST, + 'threads' => $threads ?? \PASSWORD_ARGON2_DEFAULT_THREADS, + ); + } + } + public static function isSupported() { if (\defined('PASSWORD_ARGON2I')) { @@ -81,7 +102,7 @@ class Argon2iPasswordEncoder extends BasePasswordEncoder implements SelfSaltingE private function encodePasswordNative($raw) { - return password_hash($raw, \PASSWORD_ARGON2I); + return password_hash($raw, \PASSWORD_ARGON2I, $this->config); } private function encodePasswordSodiumFunction($raw) diff --git a/src/Symfony/Component/Security/Core/Encoder/EncoderFactory.php b/src/Symfony/Component/Security/Core/Encoder/EncoderFactory.php index 38caa81a05..e65c30feaa 100644 --- a/src/Symfony/Component/Security/Core/Encoder/EncoderFactory.php +++ b/src/Symfony/Component/Security/Core/Encoder/EncoderFactory.php @@ -111,7 +111,11 @@ class EncoderFactory implements EncoderFactoryInterface case 'argon2i': return array( 'class' => Argon2iPasswordEncoder::class, - 'arguments' => array(), + 'arguments' => array( + $config['memory_cost'], + $config['time_cost'], + $config['threads'], + ), ); } diff --git a/src/Symfony/Component/Security/Core/Tests/Encoder/Argon2iPasswordEncoderTest.php b/src/Symfony/Component/Security/Core/Tests/Encoder/Argon2iPasswordEncoderTest.php index 70f2142ec3..cdb4f8767a 100644 --- a/src/Symfony/Component/Security/Core/Tests/Encoder/Argon2iPasswordEncoderTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Encoder/Argon2iPasswordEncoderTest.php @@ -28,6 +28,14 @@ class Argon2iPasswordEncoderTest extends TestCase } } + public function testValidationWithConfig() + { + $encoder = new Argon2iPasswordEncoder(4, 4, 1); + $result = $encoder->encodePassword(self::PASSWORD, null); + $this->assertTrue($encoder->isPasswordValid($result, self::PASSWORD, null)); + $this->assertFalse($encoder->isPasswordValid($result, 'anotherPassword', null)); + } + public function testValidation() { $encoder = new Argon2iPasswordEncoder();