From dc95a6fec6d44e1dfcd7ab5895d6e68bf71676bb Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Mon, 8 Apr 2019 17:54:59 +0200 Subject: [PATCH] [Security] Fix argon2 availability checks --- .../DependencyInjection/SecurityExtension.php | 2 +- .../UserPasswordEncoderCommandTest.php | 8 ++-- .../Core/Encoder/Argon2iPasswordEncoder.php | 14 +++--- .../Core/Encoder/Argon2idPasswordEncoder.php | 21 +++++---- .../Encoder/Argon2iPasswordEncoderTest.php | 45 ++++++++++++++++--- 5 files changed, 62 insertions(+), 28 deletions(-) diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index b8b3358c02..dd7fe75cc8 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -571,7 +571,7 @@ class SecurityExtension extends Extension implements PrependExtensionInterface } throw new InvalidConfigurationException('Argon2i algorithm is not supported. Install the libsodium extension or use BCrypt instead.'); - } elseif (\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) { + } elseif (!\defined('PASSWORD_ARGON2I') && Argon2idPasswordEncoder::isDefaultSodiumAlgorithm()) { @trigger_error('Configuring an encoder based on the "argon2i" algorithm while only "argon2id" is supported is deprecated since Symfony 4.3, use "argon2id" instead.', E_USER_DEPRECATED); } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/UserPasswordEncoderCommandTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/UserPasswordEncoderCommandTest.php index 80d3348124..54d355cf0f 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/UserPasswordEncoderCommandTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/UserPasswordEncoderCommandTest.php @@ -73,7 +73,7 @@ class UserPasswordEncoderCommandTest extends WebTestCase public function testEncodePasswordArgon2i() { - if (!Argon2iPasswordEncoder::isSupported() || \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) { + if (!Argon2iPasswordEncoder::isSupported() || !\defined('PASSWORD_ARGON2I') && Argon2idPasswordEncoder::isDefaultSodiumAlgorithm()) { $this->markTestSkipped('Argon2i algorithm not available.'); } $this->setupArgon2i(); @@ -95,7 +95,7 @@ class UserPasswordEncoderCommandTest extends WebTestCase public function testEncodePasswordArgon2id() { if (!Argon2idPasswordEncoder::isSupported()) { - $this->markTestSkipped('Argon2i algorithm not available.'); + $this->markTestSkipped('Argon2id algorithm not available.'); } $this->setupArgon2id(); $this->passwordEncoderCommandTester->execute([ @@ -107,7 +107,7 @@ class UserPasswordEncoderCommandTest extends WebTestCase $output = $this->passwordEncoderCommandTester->getDisplay(); $this->assertContains('Password encoding succeeded', $output); - $encoder = new Argon2iPasswordEncoder(); + $encoder = new Argon2idPasswordEncoder(); preg_match('# Encoded password\s+(\$argon2id?\$[\w,=\$+\/]+={0,2})\s+#', $output, $matches); $hash = $matches[1]; $this->assertTrue($encoder->isPasswordValid($hash, 'password', null)); @@ -175,7 +175,7 @@ class UserPasswordEncoderCommandTest extends WebTestCase public function testEncodePasswordArgon2iOutput() { - if (!Argon2iPasswordEncoder::isSupported() || \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) { + if (!Argon2iPasswordEncoder::isSupported() || !\defined('PASSWORD_ARGON2I') && Argon2idPasswordEncoder::isDefaultSodiumAlgorithm()) { $this->markTestSkipped('Argon2id algorithm not available.'); } diff --git a/src/Symfony/Component/Security/Core/Encoder/Argon2iPasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/Argon2iPasswordEncoder.php index 1694e8fd65..b1add53763 100644 --- a/src/Symfony/Component/Security/Core/Encoder/Argon2iPasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/Argon2iPasswordEncoder.php @@ -48,11 +48,11 @@ class Argon2iPasswordEncoder extends BasePasswordEncoder implements SelfSaltingE if (\PHP_VERSION_ID >= 70200 && \defined('PASSWORD_ARGON2I')) { return $this->encodePasswordNative($raw, \PASSWORD_ARGON2I); } elseif (\function_exists('sodium_crypto_pwhash_str')) { - if (0 === strpos($hash = $this->encodePasswordSodiumFunction($raw), Argon2idPasswordEncoder::HASH_PREFIX)) { + if (Argon2idPasswordEncoder::isDefaultSodiumAlgorithm()) { @trigger_error(sprintf('Using "%s" while only the "argon2id" algorithm is supported is deprecated since Symfony 4.3, use "%s" instead.', __CLASS__, Argon2idPasswordEncoder::class), E_USER_DEPRECATED); } - return $hash; + return $this->encodePasswordSodiumFunction($raw); } if (\extension_loaded('libsodium')) { return $this->encodePasswordSodiumExtension($raw); @@ -70,12 +70,12 @@ class Argon2iPasswordEncoder extends BasePasswordEncoder implements SelfSaltingE return false; } - if (\PHP_VERSION_ID >= 70200 && \defined('PASSWORD_ARGON2I')) { - // If $encoded was created via "sodium_crypto_pwhash_str()", the hashing algorithm may be "argon2id" instead of "argon2i" - if ($isArgon2id = (0 === strpos($encoded, Argon2idPasswordEncoder::HASH_PREFIX))) { - @trigger_error(sprintf('Calling "%s()" with a password hashed using argon2id is deprecated since Symfony 4.3, use "%s" instead.', __METHOD__, Argon2idPasswordEncoder::class), E_USER_DEPRECATED); - } + // If $encoded was created via "sodium_crypto_pwhash_str()", the hashing algorithm may be "argon2id" instead of "argon2i" + if ($isArgon2id = (0 === strpos($encoded, Argon2idPasswordEncoder::HASH_PREFIX))) { + @trigger_error(sprintf('Calling "%s()" with a password hashed using argon2id is deprecated since Symfony 4.3, use "%s" instead.', __METHOD__, Argon2idPasswordEncoder::class), E_USER_DEPRECATED); + } + if (\PHP_VERSION_ID >= 70200 && \defined('PASSWORD_ARGON2I')) { // Remove the right part of the OR in 5.0 if (\defined('PASSWORD_ARGON2I') || $isArgon2id && \defined('PASSWORD_ARGON2ID')) { return password_verify($raw, $encoded); diff --git a/src/Symfony/Component/Security/Core/Encoder/Argon2idPasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/Argon2idPasswordEncoder.php index 7dc924bb15..a201bca36b 100644 --- a/src/Symfony/Component/Security/Core/Encoder/Argon2idPasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/Argon2idPasswordEncoder.php @@ -46,18 +46,11 @@ class Argon2idPasswordEncoder extends BasePasswordEncoder implements SelfSalting if (\defined('PASSWORD_ARGON2ID')) { return $this->encodePasswordNative($raw, \PASSWORD_ARGON2ID); } - if (!\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) { + if (!self::isDefaultSodiumAlgorithm()) { throw new LogicException('Algorithm "argon2id" is not supported. Please install the libsodium extension or upgrade to PHP 7.3+.'); } - $hash = \sodium_crypto_pwhash_str( - $raw, - \SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE, - \SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE - ); - \sodium_memzero($raw); - - return $hash; + return $this->encodePasswordSodiumFunction($raw); } /** @@ -82,4 +75,14 @@ class Argon2idPasswordEncoder extends BasePasswordEncoder implements SelfSalting throw new LogicException('Algorithm "argon2id" is not supported. Please install the libsodium extension or upgrade to PHP 7.3+.'); } + + /** + * @internal + */ + public static function isDefaultSodiumAlgorithm() + { + return \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13') + && \defined('SODIUM_CRYPTO_PWHASH_ALG_DEFAULT') + && \SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13 === \SODIUM_CRYPTO_PWHASH_ALG_DEFAULT; + } } diff --git a/src/Symfony/Component/Security/Core/Tests/Encoder/Argon2iPasswordEncoderTest.php b/src/Symfony/Component/Security/Core/Tests/Encoder/Argon2iPasswordEncoderTest.php index 93917c5b59..88f10b446d 100644 --- a/src/Symfony/Component/Security/Core/Tests/Encoder/Argon2iPasswordEncoderTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Encoder/Argon2iPasswordEncoderTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Security\Core\Tests\Encoder; use PHPUnit\Framework\TestCase; +use Symfony\Component\Security\Core\Encoder\Argon2idPasswordEncoder; use Symfony\Component\Security\Core\Encoder\Argon2iPasswordEncoder; /** @@ -21,15 +22,12 @@ class Argon2iPasswordEncoderTest extends TestCase { const PASSWORD = 'password'; - protected function setUp() - { - if (!Argon2iPasswordEncoder::isSupported() || \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) { - $this->markTestSkipped('Argon2i algorithm is not supported.'); - } - } - public function testValidationWithConfig() { + if (!Argon2iPasswordEncoder::isSupported() || Argon2idPasswordEncoder::isDefaultSodiumAlgorithm()) { + $this->markTestSkipped('Argon2i algorithm is not supported.'); + } + $encoder = new Argon2iPasswordEncoder(8, 4, 1); $result = $encoder->encodePassword(self::PASSWORD, null); $this->assertTrue($encoder->isPasswordValid($result, self::PASSWORD, null)); @@ -38,6 +36,10 @@ class Argon2iPasswordEncoderTest extends TestCase public function testValidation() { + if (!Argon2iPasswordEncoder::isSupported() || Argon2idPasswordEncoder::isDefaultSodiumAlgorithm()) { + $this->markTestSkipped('Argon2i algorithm is not supported.'); + } + $encoder = new Argon2iPasswordEncoder(); $result = $encoder->encodePassword(self::PASSWORD, null); $this->assertTrue($encoder->isPasswordValid($result, self::PASSWORD, null)); @@ -49,12 +51,20 @@ class Argon2iPasswordEncoderTest extends TestCase */ public function testEncodePasswordLength() { + if (!Argon2iPasswordEncoder::isSupported() || Argon2idPasswordEncoder::isDefaultSodiumAlgorithm()) { + $this->markTestSkipped('Argon2i algorithm is not supported.'); + } + $encoder = new Argon2iPasswordEncoder(); $encoder->encodePassword(str_repeat('a', 4097), 'salt'); } public function testCheckPasswordLength() { + if (!Argon2iPasswordEncoder::isSupported() || Argon2idPasswordEncoder::isDefaultSodiumAlgorithm()) { + $this->markTestSkipped('Argon2i algorithm is not supported.'); + } + $encoder = new Argon2iPasswordEncoder(); $result = $encoder->encodePassword(str_repeat('a', 4096), null); $this->assertFalse($encoder->isPasswordValid($result, str_repeat('a', 4097), null)); @@ -63,8 +73,29 @@ class Argon2iPasswordEncoderTest extends TestCase public function testUserProvidedSaltIsNotUsed() { + if (!Argon2iPasswordEncoder::isSupported() || Argon2idPasswordEncoder::isDefaultSodiumAlgorithm()) { + $this->markTestSkipped('Argon2i algorithm is not supported.'); + } + $encoder = new Argon2iPasswordEncoder(); $result = $encoder->encodePassword(self::PASSWORD, 'salt'); $this->assertTrue($encoder->isPasswordValid($result, self::PASSWORD, 'anotherSalt')); } + + /** + * @group legacy + * @exectedDeprecation Using "Symfony\Component\Security\Core\Encoder\Argon2iPasswordEncoder" while only the "argon2id" algorithm is supported is deprecated since Symfony 4.3, use "Symfony\Component\Security\Core\Encoder\Argon2idPasswordEncoder" instead. + * @exectedDeprecation Calling "Symfony\Component\Security\Core\Encoder\Argon2iPasswordEncoder::isPasswordValid()" with a password hashed using argon2id is deprecated since Symfony 4.3, use "Symfony\Component\Security\Core\Encoder\Argon2idPasswordEncoder" instead. + */ + public function testEncodeWithArgon2idSupportOnly() + { + if (!Argon2iPasswordEncoder::isSupported() || !Argon2idPasswordEncoder::isDefaultSodiumAlgorithm()) { + $this->markTestSkipped('Argon2id algorithm not available.'); + } + + $encoder = new Argon2iPasswordEncoder(); + $result = $encoder->encodePassword(self::PASSWORD, null); + $this->assertTrue($encoder->isPasswordValid($result, self::PASSWORD, null)); + $this->assertFalse($encoder->isPasswordValid($result, 'anotherPassword', null)); + } }