[Security] Fix argon2 availability checks

This commit is contained in:
Robin Chalas 2019-04-08 17:54:59 +02:00
parent d935f40ed0
commit dc95a6fec6
5 changed files with 62 additions and 28 deletions

View File

@ -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);
}

View File

@ -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.');
}

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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));
}
}