From 2891bcf66155b5e1c9060ce87f79ef97dfb17144 Mon Sep 17 00:00:00 2001 From: Vasil Rangelov Date: Tue, 30 May 2023 18:27:10 +0300 Subject: [PATCH 1/5] Made PHPUnit tests pass in PHP 8.0, PHP 8.1 and PHP 8.2, by supporting PHPUnit 10, PHPUnit 8 and PHPUnit 5, complete with config files, one of which is explicitly picked by phpunit.sh based on the PHPUnit version previously determined based on the PHP version. This required dropping support for PHPUnit 4, but that is OK, as PHPUnit 5 still supports PHP 5.6, that being the earliest PHP version supported by this library. Also fixed the broken exception message assertions in two of the tests, so that tests now pass. --- .travis.yml | 5 +- composer.json | 3 +- test/phpunit-10.xml | 9 ++ test/phpunit-5.xml | 7 ++ test/phpunit-8.xml | 7 ++ test/phpunit.sh | 21 ++--- test/phpunit.xml | 7 -- test/unit/BackwardsCompatibilityTest.php | 37 ++++---- test/unit/CoreTest.php | 7 +- test/unit/CryptoTest.php | 68 ++++----------- test/unit/CtrModeTest.php | 29 +++---- test/unit/EncodingTest.php | 15 ++-- test/unit/FileTest.php | 105 +++++++++-------------- test/unit/KeyTest.php | 9 +- test/unit/LegacyDecryptTest.php | 13 ++- test/unit/PasswordTest.php | 13 ++- test/unit/RuntimeTestTest.php | 4 +- 17 files changed, 148 insertions(+), 211 deletions(-) create mode 100644 test/phpunit-10.xml create mode 100644 test/phpunit-5.xml create mode 100644 test/phpunit-8.xml delete mode 100644 test/phpunit.xml diff --git a/.travis.yml b/.travis.yml index b0a8565..6050aba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,6 +18,10 @@ matrix: env: USE_PSALM=1 - php: "8.0" env: USE_PSALM=1 + - php: "8.1" + env: USE_PSALM=1 + - php: "8.2" + env: USE_PSALM=1 - php: "nightly" env: USE_PSALM=1 - php: "hhvm" @@ -25,7 +29,6 @@ matrix: allow_failures: - php: "nightly" - php: "hhvm" - - php: "8.0" install: - composer install - curl -LSs https://box-project.github.io/box2/installer.php | php diff --git a/composer.json b/composer.json index 025f38d..7f15ba0 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,8 @@ "php": ">=5.6.0" }, "require-dev": { - "phpunit/phpunit": "^4|^5|^6|^7|^8|^9" + "phpunit/phpunit": "^5|^6|^7|^8|^9|^10", + "yoast/phpunit-polyfills": "^2.0.0" }, "bin": [ "bin/generate-defuse-key" diff --git a/test/phpunit-10.xml b/test/phpunit-10.xml new file mode 100644 index 0000000..712a8cd --- /dev/null +++ b/test/phpunit-10.xml @@ -0,0 +1,9 @@ + + + + ../src + + + + + diff --git a/test/phpunit-5.xml b/test/phpunit-5.xml new file mode 100644 index 0000000..f76c419 --- /dev/null +++ b/test/phpunit-5.xml @@ -0,0 +1,7 @@ + + + + ../src + + + diff --git a/test/phpunit-8.xml b/test/phpunit-8.xml new file mode 100644 index 0000000..f76c419 --- /dev/null +++ b/test/phpunit-8.xml @@ -0,0 +1,7 @@ + + + + ../src + + + diff --git a/test/phpunit.sh b/test/phpunit.sh index 187696a..4de7df6 100755 --- a/test/phpunit.sh +++ b/test/phpunit.sh @@ -33,19 +33,12 @@ if [ "$clean" -eq 1 ]; then fi # Let's grab the latest release and its signature +phpunitversion=$([ $PHP_VERSION -ge 80100 ] && echo "10" || ([ $PHP_VERSION -ge 70200 ] && echo "8" || echo "5")) if [ ! -f phpunit.phar ]; then - if [[ $PHP_VERSION -ge 50600 ]]; then - wget -O phpunit.phar https://phar.phpunit.de/phpunit-5.7.phar - else - wget -O phpunit.phar https://phar.phpunit.de/phpunit-4.8.phar - fi + wget -O phpunit.phar https://phar.phpunit.de/phpunit-$phpunitversion.phar fi if [ ! -f phpunit.phar.asc ]; then - if [[ $PHP_VERSION -ge 50600 ]]; then - wget -O phpunit.phar.asc https://phar.phpunit.de/phpunit-5.7.phar.asc - else - wget -O phpunit.phar.asc https://phar.phpunit.de/phpunit-4.8.phar.asc - fi + wget -O phpunit.phar.asc https://phar.phpunit.de/phpunit-$phpunitversion.phar.asc fi # What are the major/minor versions? @@ -56,19 +49,19 @@ gpg --verify phpunit.phar.asc phpunit.phar if [ $? -eq 0 ]; then echo if [ "$2" -eq "1" ]; then - COVERAGE1_ARGS="--coverage-clover=$parentdir/coverage1.xml -c $parentdir/test/phpunit.xml" - COVERAGE2_ARGS="--coverage-clover=$parentdir/coverage2.xml -c $parentdir/test/phpunit.xml" + COVERAGE1_ARGS="--coverage-clover=$parentdir/coverage1.xml" + COVERAGE2_ARGS="--coverage-clover=$parentdir/coverage2.xml" else COVERAGE1_ARGS="" COVERAGE2_ARGS="" fi echo -e "\033[33mBegin Unit Testing\033[0m" # Run the test suite with normal func_overload. - php -d mbstring.func_overload=0 phpunit.phar $COVERAGE1_ARGS --bootstrap "$parentdir/$1" "$parentdir/test/unit" && \ + php -d mbstring.func_overload=0 phpunit.phar -c "$parentdir/test/phpunit-$phpunitversion.xml" $COVERAGE1_ARGS --bootstrap "$parentdir/$1" "$parentdir/test/unit" && \ # Run the test suite again with funky func_overload. # This is deprecated in PHP 7 and PHPUnit is no longer compatible with the options. if [[ $PHP_VERSION -le 50600 ]]; then - php -d mbstring.func_overload=7 phpunit.phar $COVERAGE2_ARGS --bootstrap "$parentdir/$1" "$parentdir/test/unit" + php -d mbstring.func_overload=7 phpunit.phar -c "$parentdir/test/phpunit-$phpunitversion.xml" $COVERAGE2_ARGS --bootstrap "$parentdir/$1" "$parentdir/test/unit" fi EXITCODE=$? # Cleanup diff --git a/test/phpunit.xml b/test/phpunit.xml deleted file mode 100644 index 2808abe..0000000 --- a/test/phpunit.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - ../src - - - diff --git a/test/unit/BackwardsCompatibilityTest.php b/test/unit/BackwardsCompatibilityTest.php index 5556f97..3abe5f2 100644 --- a/test/unit/BackwardsCompatibilityTest.php +++ b/test/unit/BackwardsCompatibilityTest.php @@ -3,25 +3,22 @@ use \Defuse\Crypto\Crypto; use \Defuse\Crypto\Encoding; use \Defuse\Crypto\Key; +use Yoast\PHPUnitPolyfills\TestCases\TestCase; -class BackwardsCompatibilityTest extends PHPUnit_Framework_TestCase +class BackwardsCompatibilityTest extends TestCase { - /* helper function to create a key with raw bytes */ - public function keyHelper($rawkey) { - $key = Key::createNewRandomKey(); - $func = function ($bytes) { - $this->key_bytes = $bytes; - }; - $helper = $func->bindTo($key,$key); - $helper($rawkey); - return $key; - } + /* helper function to create a key with raw bytes */ + public function keyHelper($rawkey) { + $key = Key::createNewRandomKey(); + $func = function ($bytes) { + $this->key_bytes = $bytes; + }; + $helper = $func->bindTo($key,$key); + $helper($rawkey); + return $key; + } - /** - * @expectedException \Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException - * @expectedExceptionMessage invalid hex encoding - */ public function testDecryptLegacyWithWrongMethodStraightUpHex() { $cipher = Encoding::hexToBin( @@ -34,6 +31,9 @@ public function testDecryptLegacyWithWrongMethodStraightUpHex() '00000000000000000000000000000000' ); + $this->expectException(\Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException::class); + $this->expectExceptionMessage('invalid hex encoding'); + /* Make it try to parse the binary as hex. */ $plain = Crypto::decrypt( $cipher, @@ -45,10 +45,6 @@ public function testDecryptLegacyWithWrongMethodStraightUpHex() ); } - /** - * @expectedException \Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException - * @expectedExceptionMessage Bad version header - */ public function testDecryptLegacyWithWrongMethodStraightUpBinary() { $cipher = Encoding::hexToBin( @@ -61,6 +57,9 @@ public function testDecryptLegacyWithWrongMethodStraightUpBinary() '00000000000000000000000000000000' ); + $this->expectException(\Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException::class); + $this->expectExceptionMessage('Bad version header'); + /* This time, treat the binary as binary. */ $plain = Crypto::decrypt( $cipher, diff --git a/test/unit/CoreTest.php b/test/unit/CoreTest.php index 6466bc3..692b2fa 100644 --- a/test/unit/CoreTest.php +++ b/test/unit/CoreTest.php @@ -1,8 +1,9 @@ expectException(\InvalidArgumentException::class); Core::ourSubstr('abc', 0, -1); } diff --git a/test/unit/CryptoTest.php b/test/unit/CryptoTest.php index e94153d..2341d2c 100644 --- a/test/unit/CryptoTest.php +++ b/test/unit/CryptoTest.php @@ -3,9 +3,10 @@ use \Defuse\Crypto\Core; use \Defuse\Crypto\Crypto; use \Defuse\Crypto\Key; +use Yoast\PHPUnitPolyfills\TestCases\TestCase; use Defuse\Crypto\Exception as Ex; -class CryptoTest extends PHPUnit_Framework_TestCase +class CryptoTest extends TestCase { # Test for issue #165 -- encrypting then decrypting empty string fails. public function testEmptyString() @@ -23,6 +24,7 @@ public function testEmptyString() // We can't runtime-test the password stuff because it runs PBKDF2. public function testEncryptDecryptWithPassword() { + $this->expectNotToPerformAssertions(); $data = "EnCrYpT EvErYThInG\x00\x00"; $password = 'password'; @@ -93,137 +95,105 @@ public function testEncryptDecryptWithPassword() } } - /** - * @expectedException \Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException - */ public function testDecryptRawAsHex() { $ciphertext = Crypto::encryptWithPassword('testdata', 'password', true); + $this->expectException(\Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException::class); Crypto::decryptWithPassword($ciphertext, 'password', false); } - /** - * @expectedException \Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException - */ public function testDecryptHexAsRaw() { $ciphertext = Crypto::encryptWithPassword('testdata', 'password', false); + $this->expectException(\Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException::class); Crypto::decryptWithPassword($ciphertext, 'password', true); } - /** - * @expectedException \TypeError - */ public function testEncryptTypeErrorA() { $key = Key::createNewRandomKey(); + $this->expectException(\TypeError::class); Crypto::encrypt(3, $key, false); } - /** - * @expectedException \TypeError - */ public function testEncryptTypeErrorB() { + $this->expectException(\TypeError::class); Crypto::encrypt("plaintext", 3, false); } - /** - * @expectedException \TypeError - */ public function testEncryptTypeErrorC() { $key = Key::createNewRandomKey(); + $this->expectException(\TypeError::class); Crypto::encrypt("plaintext", $key, 3); } - /** - * @expectedException \TypeError - */ public function testEncryptWithPasswordTypeErrorA() { + $this->expectException(\TypeError::class); Crypto::encryptWithPassword(3, "password", false); } - /** - * @expectedException \TypeError - */ public function testEncryptWithPasswordTypeErrorB() { + $this->expectException(\TypeError::class); Crypto::encryptWithPassword("plaintext", 3, false); } - /** - * @expectedException \TypeError - */ public function testEncryptWithPasswordTypeErrorC() { + $this->expectException(\TypeError::class); Crypto::encryptWithPassword("plaintext", "password", 3); } - /** - * @expectedException \TypeError - */ public function testDecryptTypeErrorA() { $key = Key::createNewRandomKey(); + $this->expectException(\TypeError::class); Crypto::decrypt(3, $key, false); } - /** - * @expectedException \TypeError - */ public function testDecryptTypeErrorB() { + $this->expectException(\TypeError::class); Crypto::decrypt("ciphertext", 3, false); } - /** - * @expectedException \TypeError - */ public function testDecryptTypeErrorC() { $key = Key::createNewRandomKey(); + $this->expectException(\TypeError::class); Crypto::decrypt("ciphertext", $key, 3); } - /** - * @expectedException \TypeError - */ public function testDecryptWithPasswordTypeErrorA() { + $this->expectException(\TypeError::class); Crypto::decryptWithPassword(3, "password", false); } - /** - * @expectedException \TypeError - */ public function testDecryptWithPasswordTypeErrorB() { + $this->expectException(\TypeError::class); Crypto::decryptWithPassword("ciphertext", 3, false); } - /** - * @expectedException \TypeError - */ public function testDecryptWithPasswordTypeErrorC() { + $this->expectException(\TypeError::class); Crypto::decryptWithPassword("ciphertext", "password", 3); } - /** - * @expectedException \TypeError - */ public function testLegacyDecryptTypeErrorA() { + $this->expectException(\TypeError::class); Crypto::legacyDecrypt(3, "key"); } - /** - * @expectedException \TypeError - */ public function testLegacyDecryptTypeErrorB() { + $this->expectException(\TypeError::class); Crypto::legacyDecrypt("ciphertext", 3); } diff --git a/test/unit/CtrModeTest.php b/test/unit/CtrModeTest.php index f4ab297..72e3abd 100644 --- a/test/unit/CtrModeTest.php +++ b/test/unit/CtrModeTest.php @@ -1,10 +1,11 @@ expectException(\Defuse\Crypto\Exception\EnvironmentIsBrokenException::class); \Defuse\Crypto\Core::incrementCounter( str_repeat("\x00", 16), -1 ); } - /** - * @expectedException \Defuse\Crypto\Exception\EnvironmentIsBrokenException - */ public function testIncrementByZero() { + $this->expectException(\Defuse\Crypto\Exception\EnvironmentIsBrokenException::class); \Defuse\Crypto\Core::incrementCounter( str_repeat("\x00", 16), 0 ); } - public function allNonZeroByteValuesProvider() + public static function allNonZeroByteValuesProvider() { $all_bytes = []; for ($i = 1; $i <= 0xff; $i++) { @@ -159,43 +156,37 @@ public function allNonZeroByteValuesProvider() /** * @dataProvider allNonZeroByteValuesProvider - * @expectedException \Defuse\Crypto\Exception\EnvironmentIsBrokenException */ public function testIncrementCausingOverflowInFirstByte($lsb) { /* Smallest value that will overflow. */ $increment = (PHP_INT_MAX - $lsb) + 1; $start = str_repeat("\x00", 15) . chr($lsb); + $this->expectException(\Defuse\Crypto\Exception\EnvironmentIsBrokenException::class); \Defuse\Crypto\Core::incrementCounter($start, $increment); } - /** - * @expectedException \Defuse\Crypto\Exception\EnvironmentIsBrokenException - */ public function testIncrementWithShortIvLength() { + $this->expectException(\Defuse\Crypto\Exception\EnvironmentIsBrokenException::class); \Defuse\Crypto\Core::incrementCounter( str_repeat("\x00", 15), 1 ); } - /** - * @expectedException \Defuse\Crypto\Exception\EnvironmentIsBrokenException - */ public function testIncrementWithLongIvLength() { + $this->expectException(\Defuse\Crypto\Exception\EnvironmentIsBrokenException::class); \Defuse\Crypto\Core::incrementCounter( str_repeat("\x00", 17), 1 ); } - /** - * @expectedException \Defuse\Crypto\Exception\EnvironmentIsBrokenException - */ public function testIncrementByNonInteger() { + $this->expectException(\Defuse\Crypto\Exception\EnvironmentIsBrokenException::class); \Defuse\Crypto\Core::incrementCounter( str_repeat("\x00", 16), 1.0 diff --git a/test/unit/EncodingTest.php b/test/unit/EncodingTest.php index b707b82..4e1e274 100644 --- a/test/unit/EncodingTest.php +++ b/test/unit/EncodingTest.php @@ -2,8 +2,9 @@ use \Defuse\Crypto\Encoding; use \Defuse\Crypto\Core; +use Yoast\PHPUnitPolyfills\TestCases\TestCase; -class EncodingTest extends PHPUnit_Framework_TestCase +class EncodingTest extends TestCase { public function testEncodeDecodeEquivalency() { @@ -46,10 +47,6 @@ public function testEncodeDecodeEquivalencyTwoBytes() } } - /** - * @expectedException \Defuse\Crypto\Exception\BadFormatException - * @expectedExceptionMessage checksum doesn't match - */ public function testIncorrectChecksum() { $header = Core::secureRandom(Core::HEADER_VERSION_SIZE); @@ -65,13 +62,11 @@ public function testIncorrectChecksum() $str[2*Encoding::SERIALIZE_HEADER_BYTES + 6] = 'f'; $str[2*Encoding::SERIALIZE_HEADER_BYTES + 7] = 'f'; $str[2*Encoding::SERIALIZE_HEADER_BYTES + 8] = 'f'; + $this->expectException(\Defuse\Crypto\Exception\BadFormatException::class); + $this->expectExceptionMessage("checksum doesn't match"); Encoding::loadBytesFromChecksummedAsciiSafeString($header, $str); } - /** - * @expectedException \Defuse\Crypto\Exception\BadFormatException - * @expectedExceptionMessage not a hex string - */ public function testBadHexEncoding() { $header = Core::secureRandom(Core::HEADER_VERSION_SIZE); @@ -80,6 +75,8 @@ public function testBadHexEncoding() Core::secureRandom(Core::KEY_BYTE_SIZE) ); $str[0] = 'Z'; + $this->expectException(\Defuse\Crypto\Exception\BadFormatException::class); + $this->expectExceptionMessage('not a hex string'); Encoding::loadBytesFromChecksummedAsciiSafeString($header, $str); } diff --git a/test/unit/FileTest.php b/test/unit/FileTest.php index 324c7bb..59d89ae 100644 --- a/test/unit/FileTest.php +++ b/test/unit/FileTest.php @@ -1,14 +1,15 @@ key = Key::createNewRandomKey(); } - public function tearDown() + public function tear_down() { array_map('unlink', glob(self::$TEMP_DIR . '/*')); rmdir(self::$TEMP_DIR); @@ -149,29 +150,27 @@ public function testResourceToResourceWithPassword($srcFile) 'Original file mismatches the result of encrypt and decrypt'); } - /** - * @expectedException \Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException - * @expectedExceptionMessage Input file is too small to have been created by this library. - */ public function testDecryptBadMagicNumber() { $junk = self::$TEMP_DIR . '/junk'; file_put_contents($junk, 'This file does not have the right magic number.'); + $this->expectException(\Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException::class); + $this->expectExceptionMessage('Input file is too small to have been created by this library.'); File::decryptFile($junk, self::$TEMP_DIR . '/unjunked', $this->key); } /** * @dataProvider garbageCiphertextProvider - * @expectedException \Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException */ public function testDecryptGarbage($ciphertext) { $junk = self::$TEMP_DIR . '/junk'; file_put_contents($junk, $ciphertext); + $this->expectException(\Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException::class); File::decryptFile($junk, self::$TEMP_DIR . '/unjunked', $this->key); } - public function garbageCiphertextProvider() + public static function garbageCiphertextProvider() { $ciphertexts = [ [str_repeat('this is not anything that can be decrypted.', 100)], @@ -189,12 +188,10 @@ public function testDecryptEmptyFile() { $junk = self::$TEMP_DIR . '/junk'; file_put_contents($junk, ''); + $this->expectException(\Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException::class); File::decryptFile($junk, self::$TEMP_DIR . '/unjunked', $this->key); } - /** - * @expectedException \Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException - */ public function testDecryptTruncatedCiphertext() { // This tests for issue #115 on GitHub. @@ -209,6 +206,7 @@ public function testDecryptTruncatedCiphertext() $truncated = substr($ciphertext, 0, 64); file_put_contents($truncated_path, $truncated); + $this->expectException(\Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException::class); File::decryptFile($truncated_path, $plaintext_path, $this->key); } @@ -243,10 +241,6 @@ public function testEncryptWithFileDecryptWithCrypto() $this->assertSame($plaintext, $plaintext_decrypted); } - /** - * @expectedException \Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException - * @excpectedExceptionMessage Message Authentication failure; tampering detected. - */ public function testExtraData() { $src = self::$FILE_DIR . '/wat-gigantic-duck.jpg'; @@ -256,6 +250,8 @@ public function testExtraData() file_put_contents($dest, str_repeat('A', 2048), FILE_APPEND); + $this->expectException(\Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException::class); + $this->expectExceptionMessage('Integrity check failed.'); File::decryptFile($dest, $dest . '.jpg', $this->key); } @@ -265,122 +261,98 @@ public function testFileCreateRandomKey() $this->assertInstanceOf('\Defuse\Crypto\Key', $result); } - /** - * @expectedException \Defuse\Crypto\Exception\IOException - * @expectedExceptionMessage No such file or directory - */ public function testBadSourcePathEncrypt() { + $this->expectException(\Defuse\Crypto\Exception\IOException::class); + $this->expectExceptionMessage('No such file or directory'); File::encryptFile('./i-do-not-exist', 'output-file', $this->key); } - /** - * @expectedException \Defuse\Crypto\Exception\IOException - * @expectedExceptionMessage No such file or directory - */ public function testBadSourcePathDecrypt() { + $this->expectException(\Defuse\Crypto\Exception\IOException::class); + $this->expectExceptionMessage('No such file or directory'); File::decryptFile('./i-do-not-exist', 'output-file', $this->key); } - /** - * @expectedException \Defuse\Crypto\Exception\IOException - * @expectedExceptionMessage No such file or directory - */ public function testBadSourcePathEncryptWithPassword() { + $this->expectException(\Defuse\Crypto\Exception\IOException::class); + $this->expectExceptionMessage('No such file or directory'); File::encryptFileWithPassword('./i-do-not-exist', 'output-file', 'password'); } - /** - * @expectedException \Defuse\Crypto\Exception\IOException - * @expectedExceptionMessage No such file or directory - */ public function testBadSourcePathDecryptWithPassword() { + $this->expectException(\Defuse\Crypto\Exception\IOException::class); + $this->expectExceptionMessage('No such file or directory'); File::decryptFileWithPassword('./i-do-not-exist', 'output-file', 'password'); } - /** - * @expectedException \Defuse\Crypto\Exception\IOException - * @expectedExceptionMessage Is a directory - */ public function testBadDestinationPathEncrypt() { $src = self::$FILE_DIR . '/wat-gigantic-duck.jpg'; + $this->expectException(\Defuse\Crypto\Exception\IOException::class); + $this->expectExceptionMessage('Is a directory'); File::encryptFile($src, './', $this->key); } - /** - * @expectedException \Defuse\Crypto\Exception\IOException - * @expectedExceptionMessage Is a directory - */ public function testBadDestinationPathDecrypt() { $src = self::$FILE_DIR . '/wat-gigantic-duck.jpg'; + $this->expectException(\Defuse\Crypto\Exception\IOException::class); + $this->expectExceptionMessage('Is a directory'); File::decryptFile($src, './', $this->key); } - /** - * @expectedException \Defuse\Crypto\Exception\IOException - * @expectedExceptionMessage Is a directory - */ public function testBadDestinationPathEncryptWithPassword() { $src = self::$FILE_DIR . '/wat-gigantic-duck.jpg'; + $this->expectException(\Defuse\Crypto\Exception\IOException::class); + $this->expectExceptionMessage('Is a directory'); File::encryptFileWithPassword($src, './', 'password'); } - /** - * @expectedException \Defuse\Crypto\Exception\IOException - * @expectedExceptionMessage Is a directory - */ public function testBadDestinationPathDecryptWithPassword() { $src = self::$FILE_DIR . '/wat-gigantic-duck.jpg'; + $this->expectException(\Defuse\Crypto\Exception\IOException::class); + $this->expectExceptionMessage('Is a directory'); File::decryptFileWithPassword($src, './', 'password'); } - /** - * @expectedException \Defuse\Crypto\Exception\IOException - * @expectedExceptionMessage must be a resource - */ public function testNonResourceInputEncrypt() { $resource = fopen('php://memory', 'wb'); + $this->expectException(\Defuse\Crypto\Exception\IOException::class); + $this->expectExceptionMessage('must be a resource'); File::encryptResource('not a resource', $resource, $this->key); fclose($resource); } - /** - * @expectedException \Defuse\Crypto\Exception\IOException - * @expectedExceptionMessage must be a resource - */ public function testNonResourceOutputEncrypt() { $resource = fopen('php://memory', 'wb'); + $this->expectException(\Defuse\Crypto\Exception\IOException::class); + $this->expectExceptionMessage('must be a resource'); File::encryptResource($resource, 'not a resource', $this->key); fclose($resource); } - /** - * @expectedException \Defuse\Crypto\Exception\IOException - * @expectedExceptionMessage must be a resource - */ public function testNonResourceInputDecrypt() { $resource = fopen('php://memory', 'wb'); + $this->expectException(\Defuse\Crypto\Exception\IOException::class); + $this->expectExceptionMessage('must be a resource'); File::decryptResource('not a resource', $resource, $this->key); fclose($resource); } - /** - * @expectedException \Defuse\Crypto\Exception\IOException - * @expectedExceptionMessage must be a resource - */ public function testNonResourceOutputDecrypt() { $resource = fopen('php://memory', 'wb'); + $this->expectException(\Defuse\Crypto\Exception\IOException::class); + $this->expectExceptionMessage('must be a resource'); File::decryptResource($resource, 'not a resource', $this->key); fclose($resource); } @@ -396,14 +368,15 @@ public function testNonFileResourceDecrypt() $output = fopen('php://memory', 'wb'); try { File::decryptResource($stdin, $output, $this->key); - } catch (Exception $ex) { + } catch (\Exception $ex) { fclose($output); fclose($stdin); + $this->expectException(\Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException::class); throw $ex; } } - public function fileToFileProvider() + public static function fileToFileProvider() { $data = []; diff --git a/test/unit/KeyTest.php b/test/unit/KeyTest.php index 03c9d84..d64df35 100644 --- a/test/unit/KeyTest.php +++ b/test/unit/KeyTest.php @@ -2,8 +2,9 @@ use \Defuse\Crypto\Core; use \Defuse\Crypto\Key; +use Yoast\PHPUnitPolyfills\TestCases\TestCase; -class KeyTest extends PHPUnit_Framework_TestCase +class KeyTest extends TestCase { public function testCreateNewRandomKey() { @@ -19,15 +20,13 @@ public function testSaveAndLoadKey() $this->assertSame($key1->getRawBytes(), $key2->getRawBytes()); } - /** - * @expectedException \Defuse\Crypto\Exception\BadFormatException - * @excpectedExceptionMessage key version header - */ public function testIncorrectHeader() { $key = Key::createNewRandomKey(); $str = $key->saveToAsciiSafeString(); $str[0] = 'f'; + $this->expectException(\Defuse\Crypto\Exception\BadFormatException::class); + $this->expectExceptionMessage('Invalid header.'); Key::loadFromAsciiSafeString($str); } } diff --git a/test/unit/LegacyDecryptTest.php b/test/unit/LegacyDecryptTest.php index 59832b8..e379b34 100644 --- a/test/unit/LegacyDecryptTest.php +++ b/test/unit/LegacyDecryptTest.php @@ -3,8 +3,9 @@ use \Defuse\Crypto\Core; use \Defuse\Crypto\Crypto; use \Defuse\Crypto\Encoding; +use Yoast\PHPUnitPolyfills\TestCases\TestCase; -class LegacyDecryptTest extends PHPUnit_Framework_TestCase +class LegacyDecryptTest extends TestCase { public function testDecryptLegacyCiphertext() { @@ -24,9 +25,6 @@ public function testDecryptLegacyCiphertext() $this->assertSame($plain, 'This is a test message'); } - /** - * @expectedException \Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException - */ public function testDecryptLegacyCiphertextWrongKey() { $cipher = Encoding::hexToBin( @@ -37,6 +35,7 @@ public function testDecryptLegacyCiphertextWrongKey() '024b5e2009106870f1db25d8b85fd01f' ); + $this->expectException(\Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException::class); $plain = Crypto::legacyDecrypt( $cipher, "\x01\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F" @@ -44,13 +43,11 @@ public function testDecryptLegacyCiphertextWrongKey() ); } - /** - * @expectedException \Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException - * @expectedExceptionMessage short - */ public function testLegacyDecryptTooShort() { $too_short = str_repeat("a", Core::LEGACY_MAC_BYTE_SIZE); + $this->expectException(\Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException::class); + $this->expectExceptionMessage('short'); Crypto::legacyDecrypt($too_short, "0123456789ABCDEF"); } diff --git a/test/unit/PasswordTest.php b/test/unit/PasswordTest.php index a1c2370..38bffae 100644 --- a/test/unit/PasswordTest.php +++ b/test/unit/PasswordTest.php @@ -1,8 +1,9 @@ assertSame($key1->getRawBytes(), $key2->getRawBytes()); } - /** - * @expectedException \Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException - */ public function testKeyProtectedByPasswordWrong() { $pkey = KeyProtectedByPassword::createRandomPasswordProtectedKey('rightpassword'); + $this->expectException(\Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException::class); $key1 = $pkey->unlockKey('wrongpassword'); } @@ -47,19 +46,16 @@ public function testChangePassword() /** * Check that changing the password actually changes the password. - * @expectedException \Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException */ function testPasswordActuallyChanges() { $pkey1 = KeyProtectedByPassword::createRandomPasswordProtectedKey('password'); $pkey1->changePassword('password', 'new password'); + $this->expectException(\Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException::class); $pkey1->unlockKey('password'); } - /** - * @expectedException \Defuse\Crypto\Exception\BadFormatException - */ function testMalformedLoad() { $pkey1 = KeyProtectedByPassword::createRandomPasswordProtectedKey('password'); @@ -67,6 +63,7 @@ function testMalformedLoad() $pkey1_enc_ascii[0] = "\xFF"; + $this->expectException(\Defuse\Crypto\Exception\BadFormatException::class); KeyProtectedByPassword::loadFromAsciiSafeString($pkey1_enc_ascii); } } diff --git a/test/unit/RuntimeTestTest.php b/test/unit/RuntimeTestTest.php index 7790323..39d9cc1 100644 --- a/test/unit/RuntimeTestTest.php +++ b/test/unit/RuntimeTestTest.php @@ -1,11 +1,13 @@ expectNotToPerformAssertions(); RuntimeTests::runtimeTest(); } } From c03bf479ac246fd62203bf71f353b6a2486ec9aa Mon Sep 17 00:00:00 2001 From: Taylor Hornby Date: Thu, 15 Jun 2023 18:23:32 -0600 Subject: [PATCH 2/5] Make the tests work on Travis-CI. --- .github/workflows/ci.yml | 107 ---------------------------------- .travis.yml | 21 ++++--- src/Core.php | 5 +- src/File.php | 39 ++++++++++++- test/phpunit-10.xml | 2 +- test/unit/CryptoTest.php | 1 - test/unit/FileTest.php | 6 -- test/unit/RuntimeTestTest.php | 1 - 8 files changed, 56 insertions(+), 126 deletions(-) delete mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 4cb9d7f..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,107 +0,0 @@ -name: CI - -on: [push] - -jobs: - old: - name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }} - runs-on: ${{ matrix.operating-system }} - strategy: - matrix: - operating-system: ['ubuntu-16.04'] - php-versions: ['5.4', '5.5', '5.6', '7.0'] - phpunit-versions: ['7.5.20'] - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-versions }} - extensions: mbstring, intl - ini-values: post_max_size=256M, max_execution_time=180 - tools: psalm, phpunit:${{ matrix.phpunit-versions }} - - - name: Fix permissions - run: sudo chmod -R 0777 . - - - name: Install dependencies - run: composer self-update --1; composer install - - - name: PHPUnit tests - uses: php-actions/phpunit@v2 - with: - memory_limit: 256M - - moderate: - name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }} - runs-on: ${{ matrix.operating-system }} - strategy: - matrix: - operating-system: ['ubuntu-latest'] - php-versions: ['7.1', '7.2', '7.3'] - phpunit-versions: ['latest'] - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-versions }} - extensions: mbstring, intl, sodium - ini-values: post_max_size=256M, max_execution_time=180 - tools: psalm, phpunit:${{ matrix.phpunit-versions }} - - - name: Fix permissions - run: sudo chmod -R 0777 . - - - name: Install dependencies - run: composer install - - - name: PHPUnit tests - uses: php-actions/phpunit@v2 - timeout-minutes: 30 - with: - memory_limit: 256M - - modern: - name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }} - runs-on: ${{ matrix.operating-system }} - strategy: - matrix: - operating-system: ['ubuntu-latest'] - php-versions: ['7.4', '8.0'] - phpunit-versions: ['latest'] - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-versions }} - extensions: mbstring, intl, sodium - ini-values: post_max_size=256M, max_execution_time=180 - tools: psalm, phpunit:${{ matrix.phpunit-versions }} - - - name: Fix permissions - run: sudo chmod -R 0777 . - - - name: Install dependencies - run: composer install - - - name: PHPUnit tests - uses: php-actions/phpunit@v2 - timeout-minutes: 30 - with: - memory_limit: 256M - - - name: Install Psalm - if: contains(['7.4', '8.0'], ${{ matrix.php-version }}) - run: composer require --dev vimeo/psalm:^4 - - - name: Static Analysis - if: contains(['7.4', '8.0'], ${{ matrix.php-version }}) - run: vendor/bin/psalm diff --git a/.travis.yml b/.travis.yml index 6050aba..32f1f95 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,11 +9,11 @@ matrix: - php: "7.0" env: USE_PSALM=0 - php: "7.1" - env: USE_PSALM=1 + env: USE_PSALM=0 - php: "7.2" - env: USE_PSALM=1 + env: USE_PSALM=0 - php: "7.3" - env: USE_PSALM=1 + env: USE_PSALM=0 - php: "7.4" env: USE_PSALM=1 - php: "8.0" @@ -29,18 +29,23 @@ matrix: allow_failures: - php: "nightly" - php: "hhvm" + # Travis-CI's 8.2 is currently broken, see: + # https://github.com/defuse/php-encryption/pull/506#issuecomment-1594084107 + - php: "8.2" install: - composer install - - curl -LSs https://box-project.github.io/box2/installer.php | php - - mkdir ~/box - - mv box.phar ~/box/box before_script: - echo "xdebug.mode = coverage" > extra_php_config.ini - phpenv config-add extra_php_config.ini script: - ./test.sh - - PATH=$PATH:~/box/ make -C dist/ build-phar - - ./test.sh dist/defuse-crypto.phar +# - mkdir /tmp/box +# - chmod 755 /tmp/box +# - curl -LSs https://github.com/box-project/box/releases/download/4.3.8/box.phar -o /tmp/box/box +# - chmod 755 /tmp/box/box +# - PATH="$PATH:/tmp/box/" which box +# - PATH="$PATH:/tmp/box/" make -C dist/ build-phar +# - ./test.sh dist/defuse-crypto.phar - if [[ $USE_PSALM -eq 1 ]]; then composer require --with-all-dependencies --dev "vimeo/psalm:dev-master"; fi - if [[ $USE_PSALM -eq 1 ]]; then composer install; fi - if [[ $USE_PSALM -eq 1 ]]; then vendor/bin/psalm; fi diff --git a/src/Core.php b/src/Core.php index 2f3a319..337ad58 100644 --- a/src/Core.php +++ b/src/Core.php @@ -100,7 +100,10 @@ public static function secureRandom($octets) { self::ensureFunctionExists('random_bytes'); try { - return \random_bytes($octets); + if ($octets == 0) { + return ""; + } + return \random_bytes(max(1, $octets)); } catch (\Exception $ex) { throw new Ex\EnvironmentIsBrokenException( 'Your system does not have a secure random number generator.' diff --git a/src/File.php b/src/File.php index 3a64c4a..ad2eac1 100644 --- a/src/File.php +++ b/src/File.php @@ -196,7 +196,9 @@ private static function encryptFileInternal($inputFilename, $outputFilename, Key } /* Open the input file. */ + self::removePHPUnitErrorHandler(); $if = @\fopen($inputFilename, 'rb'); + self::restorePHPUnitErrorHandler(); if ($if === false) { throw new Ex\IOException( 'Cannot open input file for encrypting: ' . @@ -209,7 +211,9 @@ private static function encryptFileInternal($inputFilename, $outputFilename, Key } /* Open the output file. */ + self::removePHPUnitErrorHandler(); $of = @\fopen($outputFilename, 'wb'); + self::restorePHPUnitErrorHandler(); if ($of === false) { \fclose($if); throw new Ex\IOException( @@ -265,7 +269,9 @@ private static function decryptFileInternal($inputFilename, $outputFilename, Key } /* Open the input file. */ + self::removePHPUnitErrorHandler(); $if = @\fopen($inputFilename, 'rb'); + self::restorePHPUnitErrorHandler(); if ($if === false) { throw new Ex\IOException( 'Cannot open input file for decrypting: ' . @@ -279,7 +285,9 @@ private static function decryptFileInternal($inputFilename, $outputFilename, Key } /* Open the output file. */ + self::removePHPUnitErrorHandler(); $of = @\fopen($outputFilename, 'wb'); + self::restorePHPUnitErrorHandler(); if ($of === false) { \fclose($if); throw new Ex\IOException( @@ -770,9 +778,38 @@ private static function getLastErrorMessage() { $error = error_get_last(); if ($error === null) { - return '[no PHP error]'; + return '[no PHP error, or you have a custom error handler set]'; } else { return $error['message']; } } + + /** + * PHPUnit sets an error handler, which prevents getLastErrorMessage() from working, + * because error_get_last does not work when custom handlers are set. + * + * This is a workaround, which should be a no-op in production deployments, to make + * getLastErrorMessage() return the error messages that the PHPUnit tests expect. + * + * If, in a production deployment, a custom error handler is set, the exception + * handling will still work as usual, but the error messages will be confusing. + * + * @return void + */ + private static function removePHPUnitErrorHandler() { + if (defined('PHPUNIT_COMPOSER_INSTALL') || defined('__PHPUNIT_PHAR__')) { + set_error_handler(null); + } + } + + /** + * Undoes what removePHPUnitErrorHandler did. + * + * @return void + */ + private static function restorePHPUnitErrorHandler() { + if (defined('PHPUNIT_COMPOSER_INSTALL') || defined('__PHPUNIT_PHAR__')) { + restore_error_handler(); + } + } } diff --git a/test/phpunit-10.xml b/test/phpunit-10.xml index 712a8cd..c1808f0 100644 --- a/test/phpunit-10.xml +++ b/test/phpunit-10.xml @@ -1,4 +1,4 @@ - + ../src diff --git a/test/unit/CryptoTest.php b/test/unit/CryptoTest.php index 2341d2c..ea81fb8 100644 --- a/test/unit/CryptoTest.php +++ b/test/unit/CryptoTest.php @@ -24,7 +24,6 @@ public function testEmptyString() // We can't runtime-test the password stuff because it runs PBKDF2. public function testEncryptDecryptWithPassword() { - $this->expectNotToPerformAssertions(); $data = "EnCrYpT EvErYThInG\x00\x00"; $password = 'password'; diff --git a/test/unit/FileTest.php b/test/unit/FileTest.php index 59d89ae..98fb17b 100644 --- a/test/unit/FileTest.php +++ b/test/unit/FileTest.php @@ -181,9 +181,6 @@ public static function garbageCiphertextProvider() return $ciphertexts; } - /** - * @expectedException \Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException - */ public function testDecryptEmptyFile() { $junk = self::$TEMP_DIR . '/junk'; @@ -357,9 +354,6 @@ public function testNonResourceOutputDecrypt() fclose($resource); } - /** - * @expectedException \Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException - */ public function testNonFileResourceDecrypt() { /* This should behave equivalently to an empty file. Calling fstat() on diff --git a/test/unit/RuntimeTestTest.php b/test/unit/RuntimeTestTest.php index 39d9cc1..78325e6 100644 --- a/test/unit/RuntimeTestTest.php +++ b/test/unit/RuntimeTestTest.php @@ -7,7 +7,6 @@ class RuntimeTestTest extends TestCase { public function testRuntimeTest() { - $this->expectNotToPerformAssertions(); RuntimeTests::runtimeTest(); } } From 28ed7b873c82ab5b81bae35f178c5867d8c189fd Mon Sep 17 00:00:00 2001 From: Qasim Abdullah <89213175+qasim-at-tci@users.noreply.github.com> Date: Fri, 16 Jun 2023 18:55:50 +0500 Subject: [PATCH 3/5] use focal for 8.2 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 32f1f95..adbad46 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,6 +22,7 @@ matrix: env: USE_PSALM=1 - php: "8.2" env: USE_PSALM=1 + dist: focal - php: "nightly" env: USE_PSALM=1 - php: "hhvm" From 9d2df09406157ea3bdaa1671bd072a9775199510 Mon Sep 17 00:00:00 2001 From: Qasim Abdullah <89213175+qasim-at-tci@users.noreply.github.com> Date: Fri, 16 Jun 2023 19:02:10 +0500 Subject: [PATCH 4/5] remove 8.2 from allow failures --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index adbad46..0aa111b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,7 +32,7 @@ matrix: - php: "hhvm" # Travis-CI's 8.2 is currently broken, see: # https://github.com/defuse/php-encryption/pull/506#issuecomment-1594084107 - - php: "8.2" + #- php: "8.2" install: - composer install before_script: From 5d3269ff9cf0700280f33b5b7acdec9f3c120db9 Mon Sep 17 00:00:00 2001 From: Taylor Hornby Date: Fri, 16 Jun 2023 15:42:07 -0600 Subject: [PATCH 5/5] Throw an exception whenever a zero or negative amount of random bytes is requested; note random_bytes fails when you pass in 0 --- src/Core.php | 8 +++++--- test/unit/CoreTest.php | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/Core.php b/src/Core.php index 337ad58..bd99a1a 100644 --- a/src/Core.php +++ b/src/Core.php @@ -98,11 +98,13 @@ public static function incrementCounter($ctr, $inc) */ public static function secureRandom($octets) { + if ($octets <= 0) { + throw new Ex\CryptoException( + 'A zero or negative amount of random bytes was requested.' + ); + } self::ensureFunctionExists('random_bytes'); try { - if ($octets == 0) { - return ""; - } return \random_bytes(max(1, $octets)); } catch (\Exception $ex) { throw new Ex\EnvironmentIsBrokenException( diff --git a/test/unit/CoreTest.php b/test/unit/CoreTest.php index 692b2fa..69cfe08 100644 --- a/test/unit/CoreTest.php +++ b/test/unit/CoreTest.php @@ -127,4 +127,24 @@ public function testOurSubstrLengthIsMax() { $this->assertSame('bc', Core::ourSubstr('abc', 1, 500)); } + + public function testSecureRandomZeroLength() + { + $this->expectException(\Defuse\Crypto\Exception\CryptoException::class); + $this->expectExceptionMessage('zero or negative'); + Core::secureRandom(0); + } + + public function testSecureRandomNegativeLength() + { + $this->expectException(\Defuse\Crypto\Exception\CryptoException::class); + $this->expectExceptionMessage('zero or negative'); + Core::secureRandom(-1); + } + + public function testSecureRandomPositiveLength() + { + $x = Core::secureRandom(10); + $this->assertSame(10, strlen($x)); + } }