From 03bd98c528bcd1fbdfa602015f16924d4b589802 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Tue, 7 May 2024 10:04:19 +0200 Subject: [PATCH] [PasswordHasher] Make bcrypt nul byte hash test tolerant to PHP related failures --- Tests/Hasher/NativePasswordHasherTest.php | 36 ++++++++++++++++++--- Tests/Hasher/SodiumPasswordHasherTest.php | 38 ++++++++++++++++++++--- 2 files changed, 65 insertions(+), 9 deletions(-) diff --git a/Tests/Hasher/NativePasswordHasherTest.php b/Tests/Hasher/NativePasswordHasherTest.php index 4cf708b..9895910 100644 --- a/Tests/Hasher/NativePasswordHasherTest.php +++ b/Tests/Hasher/NativePasswordHasherTest.php @@ -98,16 +98,44 @@ public function testBcryptWithLongPassword() $this->assertTrue($hasher->verify($hasher->hash($plainPassword), $plainPassword)); } - public function testBcryptWithNulByte() + /** + * @requires PHP < 8.4 + */ + public function testBcryptWithNulByteWithNativePasswordHash() { $hasher = new NativePasswordHasher(null, null, 4, \PASSWORD_BCRYPT); $plainPassword = "a\0b"; - if (\PHP_VERSION_ID < 80218 || \PHP_VERSION_ID >= 80300 && \PHP_VERSION_ID < 80305) { - // password_hash() does not accept passwords containing NUL bytes since PHP 8.2.18 and 8.3.5 - $this->assertFalse($hasher->verify(password_hash($plainPassword, \PASSWORD_BCRYPT, ['cost' => 4]), $plainPassword)); + try { + $hash = password_hash($plainPassword, \PASSWORD_BCRYPT, ['cost' => 4]); + } catch (\Throwable $throwable) { + // we skip the test in case the current PHP version does not support NUL bytes in passwords + // with bcrypt + // + // @see https://github.com/php/php-src/commit/11f2568767660ffe92fbc6799800e01203aad73a + if (str_contains($throwable->getMessage(), 'Bcrypt password must not contain null character')) { + $this->markTestSkipped('password_hash() does not accept passwords containing NUL bytes.'); + } + + throw $throwable; } + if (null === $hash) { + // we also skip the test in case password_hash() returns null as + // implemented in security patches backports + // + // @see https://github.com/shivammathur/php-src-backports/commit/d22d9ebb29dce86edd622205dd1196a2796c08c7 + $this->markTestSkipped('password_hash() does not accept passwords containing NUL bytes.'); + } + + $this->assertTrue($hasher->verify($hash, $plainPassword)); + } + + public function testPasswordNulByteGracefullyHandled() + { + $hasher = new NativePasswordHasher(null, null, 4, \PASSWORD_BCRYPT); + $plainPassword = "a\0b"; + $this->assertTrue($hasher->verify($hasher->hash($plainPassword), $plainPassword)); } diff --git a/Tests/Hasher/SodiumPasswordHasherTest.php b/Tests/Hasher/SodiumPasswordHasherTest.php index 101c09f..2931635 100644 --- a/Tests/Hasher/SodiumPasswordHasherTest.php +++ b/Tests/Hasher/SodiumPasswordHasherTest.php @@ -73,17 +73,45 @@ public function testBcryptWithLongPassword() $this->assertTrue($hasher->verify((new NativePasswordHasher(null, null, 4, \PASSWORD_BCRYPT))->hash($plainPassword), $plainPassword)); } - public function testBcryptWithNulByte() + /** + * @requires PHP < 8.4 + */ + public function testBcryptWithNulByteWithNativePasswordHash() { $hasher = new SodiumPasswordHasher(null, null); $plainPassword = "a\0b"; - if (\PHP_VERSION_ID < 80218 || \PHP_VERSION_ID >= 80300 && \PHP_VERSION_ID < 80305) { - // password_hash() does not accept passwords containing NUL bytes since PHP 8.2.18 and 8.3.5 - $this->assertFalse($hasher->verify(password_hash($plainPassword, \PASSWORD_BCRYPT, ['cost' => 4]), $plainPassword)); + try { + $hash = password_hash($plainPassword, \PASSWORD_BCRYPT, ['cost' => 4]); + } catch (\Throwable $throwable) { + // we skip the test in case the current PHP version does not support NUL bytes in passwords + // with bcrypt + // + // @see https://github.com/php/php-src/commit/11f2568767660ffe92fbc6799800e01203aad73a + if (str_contains($throwable->getMessage(), 'Bcrypt password must not contain null character')) { + $this->markTestSkipped('password_hash() does not accept passwords containing NUL bytes.'); + } + + throw $throwable; } - $this->assertTrue($hasher->verify((new NativePasswordHasher(null, null, 4, \PASSWORD_BCRYPT))->hash($plainPassword), $plainPassword)); + if (null === $hash) { + // we also skip the test in case password_hash() returns null as + // implemented in security patches backports + // + // @see https://github.com/shivammathur/php-src-backports/commit/d22d9ebb29dce86edd622205dd1196a2796c08c7 + $this->markTestSkipped('password_hash() does not accept passwords containing NUL bytes.'); + } + + $this->assertTrue($hasher->verify($hash, $plainPassword)); + } + + public function testPasswordNulByteGracefullyHandled() + { + $hasher = new SodiumPasswordHasher(null, null); + $plainPassword = "a\0b"; + + $this->assertTrue($hasher->verify($hasher->hash($plainPassword), $plainPassword)); } public function testUserProvidedSaltIsNotUsed()