diff --git a/ChangeLog b/ChangeLog index b5deda5f49..bbf71e6e33 100644 --- a/ChangeLog +++ b/ChangeLog @@ -36,6 +36,8 @@ phpMyAdmin - ChangeLog - issue Fix PHP warning on GIS visualization when there is only one GIS column - issue #17728 Some select HTML tags will now have the correct UI style - issue #17734 PHP deprecations will only be shown when in a development environment +- issue #17369 Fix server error when blowfish_secret is not exactly 32 bytes long +- issue #17736 Add utf8mb3 as an alias of utf8 on the charset description page 5.2.0 (2022-05-10) - issue #16521 Upgrade Bootstrap to version 5 diff --git a/config.sample.inc.php b/config.sample.inc.php index 34b6a9dabf..2fca0b5bc4 100644 --- a/config.sample.inc.php +++ b/config.sample.inc.php @@ -10,8 +10,8 @@ declare(strict_types=1); /** - * This is needed for cookie based authentication to encrypt password in - * cookie. Needs to be 32 chars long. + * This is needed for cookie based authentication to encrypt the cookie. + * Needs to be a 32-bytes long string of random bytes. See FAQ 2.10. */ $cfg['blowfish_secret'] = ''; /* YOU MUST FILL IN THIS FOR COOKIE AUTH! */ diff --git a/doc/config.rst b/doc/config.rst index 5594823827..20366d140d 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -1888,6 +1888,8 @@ Generic settings A secret key used to encrypt/decrypt the URL query string. Should be 32 bytes long. + .. seealso:: :ref:`faq2_10` + Cookie authentication options ----------------------------- @@ -1896,13 +1898,33 @@ Cookie authentication options :type: string :default: ``''`` - The "cookie" auth\_type uses AES algorithm to encrypt the password. If you - are using the "cookie" auth\_type, enter here a random passphrase of your - choice. It will be used internally by the AES algorithm: you won’t be - prompted for this passphrase. + The "cookie" auth\_type uses the :term:`Sodium` extension to encrypt the cookies (see :term:`Cookie`). If you are + using the "cookie" auth\_type, enter here a generated string of random bytes to be used as an encryption key. It + will be used internally by the :term:`Sodium` extension: you won't be prompted for this encryption key. - The secret should be 32 characters long. Using shorter will lead to weaker security - of encrypted cookies, using longer will cause no harm. + Since a binary string is usually not printable, it can be converted into a hexadecimal representation (using a + function like `sodium_bin2hex `_) and then used in the configuration file. For + example: + + .. code-block:: php + + // The string is a hexadecimal representation of a 32-bytes long string of random bytes. + $cfg['blowfish_secret'] = sodium_hex2bin('f16ce59f45714194371b48fe362072dc3b019da7861558cd4ad29e4d6fb13851'); + + Using a binary string is recommended. However, if all 32 bytes of the string are visible + characters, then a function like `sodium_bin2hex `_ is not required. For + example: + + .. code-block:: php + + // A string of 32 characters. + $cfg['blowfish_secret'] = 'JOFw435365IScA&Q!cDugr!lSfuAz*OW'; + + .. warning:: + + The encryption key must be 32 bytes long. If it is longer than the length of bytes, only the first 32 bytes will + be used, and if it is shorter, a new temporary key will be automatically generated for you. However, this + temporary key will only last for the duration of the session. .. note:: @@ -1910,11 +1932,21 @@ Cookie authentication options Blowfish algorithm was originally used to do the encryption. .. versionchanged:: 3.1.0 + Since version 3.1.0 phpMyAdmin can generate this on the fly, but it makes a bit weaker security as this generated secret is stored in session and furthermore it makes impossible to recall user name from cookie. + .. versionchanged:: 5.2.0 + + Since version 5.2.0, phpMyAdmin uses the + `sodium\_crypto\_secretbox `_ and + `sodium\_crypto\_secretbox\_open `_ PHP functions to encrypt + and decrypt cookies, respectively. + + .. seealso:: :ref:`faq2_10` + .. config:option:: $cfg['CookieSameSite'] :type: string @@ -3809,8 +3841,8 @@ following example shows two of them: .. code-block:: php `_ :term:`PHP` function. Since this function returns a binary string, +the returned value should be converted to printable format before being able to copy it. + +For example, the :config:option:`$cfg['blowfish_secret']` configuration directive requires a 32-bytes long string. The +following command can be used to generate a hexadecimal representation of this string. + +.. code-block:: sh + + php -r 'echo bin2hex(random_bytes(32)) . PHP_EOL;' + +The above example will output something similar to: + +.. code-block:: sh + + f16ce59f45714194371b48fe362072dc3b019da7861558cd4ad29e4d6fb13851 + +And then this hexadecimal value can be used in the configuration file. + +.. code-block:: php + + $cfg['blowfish_secret'] = sodium_hex2bin('f16ce59f45714194371b48fe362072dc3b019da7861558cd4ad29e4d6fb13851'); + +The `sodium_hex2bin `_ is used here to convert the hexadecimal value back to the +binary format. + .. _faqlimitations: Known limitations diff --git a/doc/glossary.rst b/doc/glossary.rst index 87eb074a27..8e328ae25d 100644 --- a/doc/glossary.rst +++ b/doc/glossary.rst @@ -335,6 +335,11 @@ From Wikipedia, the free encyclopedia .. seealso:: + Sodium + The Sodium PHP extension. + + .. seealso:: `PHP manual for Sodium extension `_ + Storage Engines MySQL can use several different formats for storing data on disk, these are called storage engines or table types. phpMyAdmin allows a user to diff --git a/doc/setup.rst b/doc/setup.rst index c2b7b78928..1f2563edfc 100644 --- a/doc/setup.rst +++ b/doc/setup.rst @@ -587,8 +587,8 @@ simple configuration may look like this: .. code-block:: xml+php cfg->set('blowfish_secret', Util::generateRandom(32)); + $this->cfg->set('blowfish_secret', sodium_crypto_secretbox_keygen()); } return [ @@ -345,55 +348,21 @@ class ServerConfigChecks ): void { // $cfg['blowfish_secret'] // it's required for 'cookie' authentication - if (! $cookieAuthUsed) { - return; - } - - if ($blowfishSecretSet) { - // 'cookie' auth used, blowfish_secret was generated - SetupIndex::messagesSet( - 'notice', - 'blowfish_secret_created', - Descriptions::get('blowfish_secret'), - Sanitize::sanitizeMessage(__( - 'You didn\'t have blowfish secret set and have enabled ' - . '[kbd]cookie[/kbd] authentication, so a key was automatically ' - . 'generated for you. It is used to encrypt cookies; you don\'t need to ' - . 'remember it.' - )) - ); - - return; - } - - $blowfishWarnings = []; - // check length - if (strlen($blowfishSecret) < 32) { - // too short key - $blowfishWarnings[] = __('Key is too short, it should have at least 32 characters.'); - } - - // check used characters - $hasDigits = (bool) preg_match('/\d/', $blowfishSecret); - $hasChars = (bool) preg_match('/\S/', $blowfishSecret); - $hasNonword = (bool) preg_match('/\W/', $blowfishSecret); - if (! $hasDigits || ! $hasChars || ! $hasNonword) { - $blowfishWarnings[] = Sanitize::sanitizeMessage( - __( - 'Key should contain letters, numbers [em]and[/em] special characters.' - ) - ); - } - - if (empty($blowfishWarnings)) { + if (! $cookieAuthUsed || ! $blowfishSecretSet) { return; } + // 'cookie' auth used, blowfish_secret was generated SetupIndex::messagesSet( - 'error', - 'blowfish_warnings' . count($blowfishWarnings), + 'notice', + 'blowfish_secret_created', Descriptions::get('blowfish_secret'), - implode('
', $blowfishWarnings) + Sanitize::sanitizeMessage(__( + 'You didn\'t have blowfish secret set and have enabled ' + . '[kbd]cookie[/kbd] authentication, so a key was automatically ' + . 'generated for you. It is used to encrypt cookies; you don\'t need to ' + . 'remember it.' + )) ); } diff --git a/libraries/classes/Config/Settings.php b/libraries/classes/Config/Settings.php index c5ee9ede3d..62d344c516 100644 --- a/libraries/classes/Config/Settings.php +++ b/libraries/classes/Config/Settings.php @@ -118,10 +118,9 @@ final class Settings public $AllowThirdPartyFraming; /** - * The 'cookie' auth_type uses AES algorithm to encrypt the password. If - * at least one server configuration uses 'cookie' auth_type, enter here a - * pass phrase that will be used by AES. The maximum length seems to be 46 - * characters. + * The 'cookie' auth_type uses the Sodium extension to encrypt the cookies. If at least one server configuration + * uses 'cookie' auth_type, enter here a generated string of random bytes to be used as an encryption key. The + * encryption key must be 32 bytes long. * * @var string */ diff --git a/libraries/classes/Controllers/HomeController.php b/libraries/classes/Controllers/HomeController.php index 2b7a30226c..22b3990735 100644 --- a/libraries/classes/Controllers/HomeController.php +++ b/libraries/classes/Controllers/HomeController.php @@ -318,19 +318,23 @@ class HomeController extends AbstractController * Check if user does not have defined blowfish secret and it is being used. */ if (! empty($_SESSION['encryption_key'])) { - if (empty($GLOBALS['cfg']['blowfish_secret'])) { + $encryptionKeyLength = mb_strlen($GLOBALS['cfg']['blowfish_secret'], '8bit'); + if ($encryptionKeyLength < SODIUM_CRYPTO_SECRETBOX_KEYBYTES) { $this->errors[] = [ 'message' => __( - 'The configuration file now needs a secret passphrase (blowfish_secret).' + 'The configuration file needs a valid key for cookie encryption.' + . ' A temporary key was automatically generated for you.' + . ' Please refer to the [doc@cfg_blowfish_secret]documentation[/doc].' ), 'severity' => 'warning', ]; - } elseif (mb_strlen($GLOBALS['cfg']['blowfish_secret'], '8bit') !== SODIUM_CRYPTO_SECRETBOX_KEYBYTES) { + } elseif ($encryptionKeyLength > SODIUM_CRYPTO_SECRETBOX_KEYBYTES) { $this->errors[] = [ 'message' => sprintf( __( - 'The secret passphrase in configuration (blowfish_secret) is not the correct length.' - . ' It should be %d bytes long.' + 'The cookie encryption key in the configuration file is longer than necessary.' + . ' It should only be %d bytes long.' + . ' Please refer to the [doc@cfg_blowfish_secret]documentation[/doc].' ), SODIUM_CRYPTO_SECRETBOX_KEYBYTES ), diff --git a/libraries/classes/Plugins/Auth/AuthenticationCookie.php b/libraries/classes/Plugins/Auth/AuthenticationCookie.php index b4a0b0bfde..c468223232 100644 --- a/libraries/classes/Plugins/Auth/AuthenticationCookie.php +++ b/libraries/classes/Plugins/Auth/AuthenticationCookie.php @@ -592,11 +592,21 @@ class AuthenticationCookie extends AuthenticationPlugin */ private function getEncryptionSecret(): string { + /** @var mixed $key */ $key = $GLOBALS['cfg']['blowfish_secret'] ?? null; - if (is_string($key) && mb_strlen($key, '8bit') === SODIUM_CRYPTO_SECRETBOX_KEYBYTES) { + if (! is_string($key)) { + return $this->getSessionEncryptionSecret(); + } + + $length = mb_strlen($key, '8bit'); + if ($length === SODIUM_CRYPTO_SECRETBOX_KEYBYTES) { return $key; } + if ($length > SODIUM_CRYPTO_SECRETBOX_KEYBYTES) { + return mb_substr($key, 0, SODIUM_CRYPTO_SECRETBOX_KEYBYTES, '8bit'); + } + return $this->getSessionEncryptionSecret(); } @@ -605,6 +615,7 @@ class AuthenticationCookie extends AuthenticationPlugin */ private function getSessionEncryptionSecret(): string { + /** @var mixed $key */ $key = $_SESSION['encryption_key'] ?? null; if (is_string($key) && mb_strlen($key, '8bit') === SODIUM_CRYPTO_SECRETBOX_KEYBYTES) { return $key; diff --git a/libraries/classes/Setup/ConfigGenerator.php b/libraries/classes/Setup/ConfigGenerator.php index 189c8c9260..9d69a5bab1 100644 --- a/libraries/classes/Setup/ConfigGenerator.php +++ b/libraries/classes/Setup/ConfigGenerator.php @@ -15,12 +15,18 @@ use function count; use function gmdate; use function implode; use function is_array; +use function is_string; +use function mb_strlen; use function preg_replace; +use function sodium_bin2hex; +use function sodium_crypto_secretbox_keygen; +use function sprintf; use function str_contains; use function strtr; use function var_export; use const DATE_RFC1123; +use const SODIUM_CRYPTO_SECRETBOX_KEYBYTES; /** * Config file generation class @@ -92,6 +98,12 @@ class ConfigGenerator */ private static function getVarExport($var_name, $var_value, string $eol) { + if ($var_name === 'blowfish_secret') { + $secret = self::getBlowfishSecretKey($var_value); + + return sprintf('$cfg[\'blowfish_secret\'] = \sodium_hex2bin(\'%s\');%s', sodium_bin2hex($secret), $eol); + } + if (! is_array($var_value) || empty($var_value)) { return "\$cfg['" . $var_name . "'] = " . var_export($var_value, true) . ';' . $eol; @@ -197,4 +209,18 @@ class ConfigGenerator return $ret; } + + /** + * @param mixed $key + * + * @psalm-return non-empty-string + */ + private static function getBlowfishSecretKey($key): string + { + if (is_string($key) && mb_strlen($key, '8bit') === SODIUM_CRYPTO_SECRETBOX_KEYBYTES) { + return $key; + } + + return sodium_crypto_secretbox_keygen(); + } } diff --git a/libraries/config.default.php b/libraries/config.default.php index 3ecb83faa2..2c3acd51c1 100644 --- a/libraries/config.default.php +++ b/libraries/config.default.php @@ -100,10 +100,9 @@ $cfg['TranslationWarningThreshold'] = 80; $cfg['AllowThirdPartyFraming'] = false; /** - * The 'cookie' auth_type uses AES algorithm to encrypt the password. If - * at least one server configuration uses 'cookie' auth_type, enter here a - * pass phrase that will be used by AES. The maximum length seems to be 46 - * characters. + * The 'cookie' auth_type uses the Sodium extension to encrypt the cookies. If at least one server configuration + * uses 'cookie' auth_type, enter here a generated string of random bytes to be used as an encryption key. The + * encryption key must be 32 bytes long. * * @global string $cfg['blowfish_secret'] */ diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 2ce6efc17c..b112a79cb8 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -7145,6 +7145,11 @@ parameters: count: 1 path: libraries/classes/Setup/ConfigGenerator.php + - + message: "#^Method PhpMyAdmin\\\\Setup\\\\ConfigGenerator\\:\\:getBlowfishSecretKey\\(\\) should return non\\-empty\\-string but returns string\\.$#" + count: 2 + path: libraries/classes/Setup/ConfigGenerator.php + - message: "#^Method PhpMyAdmin\\\\Setup\\\\ConfigGenerator\\:\\:getServerPart\\(\\) has parameter \\$servers with no value type specified in iterable type array\\.$#" count: 1 diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 2468ad1990..007c6d5445 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -2151,8 +2151,7 @@ $GLOBALS['cfg']['TranslationWarningThreshold'] $GLOBALS['language_stats'] - - $GLOBALS['cfg']['blowfish_secret'] + $this->config->get('ShowGitRevision') ?? true $this->config->get('TempDir') @@ -4331,10 +4330,6 @@ - - $GLOBALS['cfg']['blowfish_secret'] - $GLOBALS['cfg']['blowfish_secret'] - $matches[1] @@ -9061,13 +9056,11 @@ $_SESSION['browser_access_time'][$key] - + $GLOBALS['pma_auth_server'] $captchaSiteVerifyURL $captchaSiteVerifyURL $key - $key - $key $password $serverCookie $serverCookie @@ -12516,6 +12509,10 @@ + + $key + sodium_crypto_secretbox_keygen() + $conf['Servers'] @@ -12533,6 +12530,9 @@ $v $v + + non-empty-string + self::getServerPart($cf, $eol, $conf['Servers']) @@ -14172,40 +14172,24 @@ - - $_SESSION['messages'] + $_SESSION['messages']['error'] - $_SESSION['messages']['error'] - $_SESSION['messages']['notice'] $_SESSION['messages']['notice'] - + $_SESSION['messages']['error'] - $_SESSION['messages']['error'] - $_SESSION['messages']['notice'] $_SESSION['messages']['notice'] - - $_SESSION[$this->sessionID]['AllowArbitraryServer'] + $_SESSION[$this->sessionID]['AllowArbitraryServer'] $_SESSION[$this->sessionID]['BZipDump'] - $_SESSION[$this->sessionID]['BZipDump'] - $_SESSION[$this->sessionID]['GZipDump'] $_SESSION[$this->sessionID]['GZipDump'] $_SESSION[$this->sessionID]['LoginCookieStore'] - $_SESSION[$this->sessionID]['LoginCookieStore'] - $_SESSION[$this->sessionID]['LoginCookieValidity'] $_SESSION[$this->sessionID]['LoginCookieValidity'] $_SESSION[$this->sessionID]['SaveDir'] - $_SESSION[$this->sessionID]['SaveDir'] - $_SESSION[$this->sessionID]['Servers'] - $_SESSION[$this->sessionID]['Servers'] $_SESSION[$this->sessionID]['Servers'] $_SESSION[$this->sessionID]['TempDir'] - $_SESSION[$this->sessionID]['TempDir'] $_SESSION[$this->sessionID]['ZipDump'] - $_SESSION[$this->sessionID]['ZipDump'] - $_SESSION[$this->sessionID]['blowfish_secret'] $_SESSION[$this->sessionID] diff --git a/psalm.xml b/psalm.xml index edcb269d77..6baa7fe39f 100644 --- a/psalm.xml +++ b/psalm.xml @@ -42,6 +42,7 @@ AllowThirdPartyFraming: bool|'sameorigin', ArbitraryServerRegexp: string, AvailableCharsets: string[], + blowfish_secret: string, BrowseMarkerEnable: bool, BrowsePointerEnable: bool, BrowseMIME: bool, diff --git a/scripts/create-release.sh b/scripts/create-release.sh index 9585461b38..b147732436 100755 --- a/scripts/create-release.sh +++ b/scripts/create-release.sh @@ -262,7 +262,7 @@ restore_vendor_folder() { echo 'No backup to restore' exit 1; fi - rm -rf ./vendor + rm -r ./vendor mv "${TEMP_FOLDER}/vendor" ./vendor rmdir "${TEMP_FOLDER}" } @@ -284,7 +284,7 @@ delete_phpunit_sandbox() { echo 'No phpunit sandbox to delete' exit 1; fi - rm -rf "${TEMP_PHPUNIT_FOLDER}" + rm -r "${TEMP_PHPUNIT_FOLDER}" } security_checkup() { @@ -425,16 +425,16 @@ fi echo "* Removing unneeded files" # Remove developer information -rm -rf .github CODE_OF_CONDUCT.md DCO +rm -r .github CODE_OF_CONDUCT.md DCO # Testsuite setup -rm -f .scrutinizer.yml .weblate codecov.yml +rm .scrutinizer.yml .weblate codecov.yml # Remove Doctum config file -rm -f test/doctum-config.php +rm test/doctum-config.php # Remove readme for github -rm -f README.rst +rm README.rst if [ -f ./scripts/console ]; then # Update the vendors to have the dev vendors @@ -468,15 +468,18 @@ do PACKAGES_VERSIONS="$PACKAGES_VERSIONS $PACKAGES:$PKG_VERSION" done -echo "Installing composer packages '$PACKAGES_VERSIONS'" +echo "* Installing composer packages '$PACKAGES_VERSIONS'" composer require --no-interaction --optimize-autoloader --update-no-dev $PACKAGES_VERSIONS +echo "* Running a security checkup" security_checkup +echo "* Cleaning up vendor folders" mv composer.json.backup composer.json cleanup_composer_vendors +echo "* Running a security checkup" security_checkup if [ $do_tag -eq 1 ] ; then echo "* Commiting composer.lock" @@ -491,8 +494,8 @@ fi # Remove git metadata rm .git -find . -name .gitignore -print0 | xargs -0 -r rm -f -find . -name .gitattributes -print0 | xargs -0 -r rm -f +find . -name .gitignore -print0 | xargs -0 -r rm +find . -name .gitattributes -print0 | xargs -0 -r rm if [ $do_test -eq 1 ] ; then # Move the folder out and install dev vendors @@ -505,7 +508,7 @@ if [ $do_test -eq 1 ] ; then test_ret=$? if [ $do_ci -eq 1 ] ; then cd ../.. - rm -rf $workdir + rm -r $workdir git worktree prune if [ "$branch" = "ci" ] ; then git branch -D ci @@ -520,7 +523,7 @@ if [ $do_test -eq 1 ] ; then # Generate an normal autoload (this is just a security, because normally the vendor folder will be restored) composer dump-autoload # Remove libs installed for testing - rm -rf build + rm -r build delete_phpunit_sandbox restore_vendor_folder fi @@ -531,6 +534,7 @@ cd .. # Prepare all kits for kit in $KITS ; do + echo "* Building kit: $kit" # Copy all files name=phpMyAdmin-$version-$kit cp -r phpMyAdmin-$version $name @@ -543,27 +547,27 @@ for kit in $KITS ; do if [ $kit != source ] ; then echo "* Removing source files" # Testsuite - rm -rf test/ + rm -r test/ # Template test files rm -r templates/test/ rm phpunit.xml.* build.xml - rm -f .editorconfig .browserslistrc .eslintignore .jshintrc .eslintrc.json .stylelintrc.json psalm.xml psalm-baseline.xml phpstan.neon.dist phpstan-baseline.neon phpcs.xml.dist jest.config.js infection.json.dist - # Gettext po files - rm -rf po/ + rm .editorconfig .browserslistrc .eslintignore .jshintrc .eslintrc.json .stylelintrc.json psalm.xml psalm-baseline.xml phpstan.neon.dist phpstan-baseline.neon phpcs.xml.dist jest.config.js infection.json.dist + # Gettext po files (if they where not removed by ./scripts/lang-cleanup.sh) + rm -rf po # Documentation source code mv doc/html htmldoc - rm -rf doc + rm -r doc mkdir doc mv htmldoc doc/html rm doc/html/.buildinfo doc/html/objects.inv - rm -rf node_modules + rm -r node_modules # Remove bin files for non source version # https://github.com/phpmyadmin/phpmyadmin/issues/16033 - rm -rf vendor/bin + rm -r vendor/bin fi # Remove developer scripts - rm -rf scripts + rm -r scripts # Remove possible tmp folder rm -rf tmp @@ -604,11 +608,11 @@ for kit in $KITS ; do # Cleanup rm -f $name.tar # Remove directory with current dist set - rm -rf $name + rm -r $name done # Cleanup -rm -rf phpMyAdmin-${version} +rm -r phpMyAdmin-${version} git worktree prune # Signing of files with default GPG key @@ -657,7 +661,7 @@ if [ $do_tag -eq 1 ] ; then git rm --force composer.lock git commit -s -m "Removing composer.lock" cd ../.. - rm -rf $workdir + rm -r $workdir git worktree prune fi diff --git a/test/classes/Config/ServerConfigChecksTest.php b/test/classes/Config/ServerConfigChecksTest.php index 9e64d14e60..2d26e221b5 100644 --- a/test/classes/Config/ServerConfigChecksTest.php +++ b/test/classes/Config/ServerConfigChecksTest.php @@ -11,6 +11,10 @@ use ReflectionException; use ReflectionProperty; use function array_keys; +use function mb_strlen; +use function str_repeat; + +use const SODIUM_CRYPTO_SECRETBOX_KEYBYTES; /** * @covers \PhpMyAdmin\Config\ServerConfigChecks @@ -100,8 +104,10 @@ class ServerConfigChecksTest extends AbstractTestCase ); } - public function testBlowfishCreate(): void + public function testBlowfish(): void { + $_SESSION[$this->sessionID] = []; + $_SESSION[$this->sessionID]['blowfish_secret'] = null; $_SESSION[$this->sessionID]['Servers'] = [ '1' => [ 'host' => 'localhost', @@ -110,7 +116,6 @@ class ServerConfigChecksTest extends AbstractTestCase 'AllowRoot' => false, ], ]; - $_SESSION[$this->sessionID]['AllowArbitraryServer'] = false; $_SESSION[$this->sessionID]['LoginCookieValidity'] = -1; $_SESSION[$this->sessionID]['LoginCookieStore'] = 0; @@ -123,28 +128,73 @@ class ServerConfigChecksTest extends AbstractTestCase $configChecker = new ServerConfigChecks($GLOBALS['ConfigFile']); $configChecker->performConfigChecks(); - $this->assertEquals( - ['blowfish_secret_created'], - array_keys($_SESSION['messages']['notice']) - ); - - $this->assertArrayNotHasKey('error', $_SESSION['messages']); + /** + * @var mixed $secret + * @psalm-suppress TypeDoesNotContainType + */ + $secret = $_SESSION[$this->sessionID]['blowfish_secret'] ?? ''; + $this->assertIsString($secret); + $this->assertSame(SODIUM_CRYPTO_SECRETBOX_KEYBYTES, mb_strlen($secret, '8bit')); + $messages = $_SESSION['messages'] ?? null; + $this->assertIsArray($messages); + $this->assertArrayHasKey('notice', $messages); + $this->assertIsArray($messages['notice']); + $this->assertArrayHasKey('blowfish_secret_created', $messages['notice']); + $this->assertArrayNotHasKey('error', $messages); } - public function testBlowfish(): void + public function testBlowfishWithInvalidSecret(): void { - $_SESSION[$this->sessionID]['blowfish_secret'] = 'sec'; - + $_SESSION[$this->sessionID] = []; + $_SESSION[$this->sessionID]['blowfish_secret'] = str_repeat('a', SODIUM_CRYPTO_SECRETBOX_KEYBYTES + 1); $_SESSION[$this->sessionID]['Servers'] = [ '1' => [ 'host' => 'localhost', + 'ssl' => true, 'auth_type' => 'cookie', + 'AllowRoot' => false, ], ]; $configChecker = new ServerConfigChecks($GLOBALS['ConfigFile']); $configChecker->performConfigChecks(); - $this->assertArrayHasKey('blowfish_warnings2', $_SESSION['messages']['error']); + /** + * @var mixed $secret + * @psalm-suppress TypeDoesNotContainType + */ + $secret = $_SESSION[$this->sessionID]['blowfish_secret'] ?? ''; + $this->assertIsString($secret); + $this->assertSame(SODIUM_CRYPTO_SECRETBOX_KEYBYTES, mb_strlen($secret, '8bit')); + $messages = $_SESSION['messages'] ?? null; + $this->assertIsArray($messages); + $this->assertArrayHasKey('notice', $messages); + $this->assertIsArray($messages['notice']); + $this->assertArrayHasKey('blowfish_secret_created', $messages['notice']); + $this->assertArrayNotHasKey('error', $messages); + } + + public function testBlowfishWithValidSecret(): void + { + $_SESSION[$this->sessionID] = []; + $_SESSION[$this->sessionID]['blowfish_secret'] = str_repeat('a', SODIUM_CRYPTO_SECRETBOX_KEYBYTES); + $_SESSION[$this->sessionID]['Servers'] = ['1' => ['host' => 'localhost', 'auth_type' => 'cookie']]; + + $configChecker = new ServerConfigChecks($GLOBALS['ConfigFile']); + $configChecker->performConfigChecks(); + + /** + * @var mixed $secret + * @psalm-suppress TypeDoesNotContainType + */ + $secret = $_SESSION[$this->sessionID]['blowfish_secret'] ?? ''; + $this->assertIsString($secret); + $this->assertSame(SODIUM_CRYPTO_SECRETBOX_KEYBYTES, mb_strlen($secret, '8bit')); + $messages = $_SESSION['messages'] ?? null; + $this->assertIsArray($messages); + $this->assertArrayHasKey('notice', $messages); + $this->assertIsArray($messages['notice']); + $this->assertArrayNotHasKey('blowfish_secret_created', $messages['notice']); + $this->assertArrayNotHasKey('error', $messages); } } diff --git a/test/classes/Setup/ConfigGeneratorTest.php b/test/classes/Setup/ConfigGeneratorTest.php index 7aaaa10ec0..985feb404d 100644 --- a/test/classes/Setup/ConfigGeneratorTest.php +++ b/test/classes/Setup/ConfigGeneratorTest.php @@ -10,6 +10,13 @@ use PhpMyAdmin\Tests\AbstractTestCase; use PhpMyAdmin\Version; use ReflectionClass; +use function explode; +use function hex2bin; +use function mb_strlen; +use function str_repeat; + +use const SODIUM_CRYPTO_SECRETBOX_KEYBYTES; + /** * @covers \PhpMyAdmin\Setup\ConfigGenerator */ @@ -115,6 +122,29 @@ class ConfigGeneratorTest extends AbstractTestCase ); } + public function testGetVarExportForBlowfishSecret(): void + { + $reflection = new ReflectionClass(ConfigGenerator::class); + $method = $reflection->getMethod('getVarExport'); + $method->setAccessible(true); + + $this->assertEquals( + '$cfg[\'blowfish_secret\'] = \sodium_hex2bin(\'' + . '6161616161616161616161616161616161616161616161616161616161616161\');' . "\n", + $method->invoke(null, 'blowfish_secret', str_repeat('a', SODIUM_CRYPTO_SECRETBOX_KEYBYTES), "\n") + ); + + /** @var string $actual */ + $actual = $method->invoke(null, 'blowfish_secret', 'invalid secret', "\n"); + $this->assertStringStartsWith('$cfg[\'blowfish_secret\'] = \sodium_hex2bin(\'', $actual); + $this->assertStringEndsWith('\');' . "\n", $actual); + $pieces = explode('\'', $actual); + $this->assertCount(5, $pieces); + $binaryString = hex2bin($pieces[3]); + $this->assertIsString($binaryString); + $this->assertSame(SODIUM_CRYPTO_SECRETBOX_KEYBYTES, mb_strlen($binaryString, '8bit')); + } + /** * Test for ConfigGenerator::isZeroBasedArray */