From: Alexandre Daubois <alex.daubois@gmail.com>
Date: Tue, 7 May 2024 10:04:19 +0200
Subject: [PasswordHasher] Make bcrypt nul byte hash test tolerant to PHP
 related failures

Origin: upstream, https://github.com/symfony/symfony/commit/30de18b659e331828df1618b6734fe4348a9ae63
---
 .../Tests/Hasher/NativePasswordHasherTest.php      | 36 +++++++++++++++++---
 .../Tests/Hasher/SodiumPasswordHasherTest.php      | 38 +++++++++++++++++++---
 2 files changed, 65 insertions(+), 9 deletions(-)

diff --git a/src/Symfony/Component/PasswordHasher/Tests/Hasher/NativePasswordHasherTest.php b/src/Symfony/Component/PasswordHasher/Tests/Hasher/NativePasswordHasherTest.php
index 4cf708b..9895910 100644
--- a/src/Symfony/Component/PasswordHasher/Tests/Hasher/NativePasswordHasherTest.php
+++ b/src/Symfony/Component/PasswordHasher/Tests/Hasher/NativePasswordHasherTest.php
@@ -98,16 +98,44 @@ class NativePasswordHasherTest extends TestCase
         $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/src/Symfony/Component/PasswordHasher/Tests/Hasher/SodiumPasswordHasherTest.php b/src/Symfony/Component/PasswordHasher/Tests/Hasher/SodiumPasswordHasherTest.php
index 101c09f..2931635 100644
--- a/src/Symfony/Component/PasswordHasher/Tests/Hasher/SodiumPasswordHasherTest.php
+++ b/src/Symfony/Component/PasswordHasher/Tests/Hasher/SodiumPasswordHasherTest.php
@@ -73,17 +73,45 @@ class SodiumPasswordHasherTest extends TestCase
         $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()
