diff --git a/CHANGELOG.md b/CHANGELOG.md index 7315827..9fae2b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Refactored `\ncc\Objects > PackageLock` + - Updated `defuse\php-encryption` to version 2.4.0 ## [1.0.2] - 2023-06-29 diff --git a/src/ncc/ThirdParty/defuse/php-encryption/Core.php b/src/ncc/ThirdParty/defuse/php-encryption/Core.php index 8eba7bf..8b9f03e 100644 --- a/src/ncc/ThirdParty/defuse/php-encryption/Core.php +++ b/src/ncc/ThirdParty/defuse/php-encryption/Core.php @@ -4,6 +4,7 @@ namespace ncc\Defuse\Crypto; use ncc\Defuse\Crypto\Exception as Ex; + final class Core { const HEADER_VERSION_SIZE = 4; @@ -98,9 +99,14 @@ final class Core */ 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 { - return \random_bytes($octets); + return \random_bytes(max(1, $octets)); } catch (\Exception $ex) { throw new Ex\EnvironmentIsBrokenException( 'Your system does not have a secure random number generator.' @@ -285,7 +291,7 @@ final class Core { static $exists = null; if ($exists === null) { - $exists = \extension_loaded('mbstring') && \ini_get('mbstring.func_overload') !== false && (int)\ini_get('mbstring.func_overload') & MB_OVERLOAD_STRING; + $exists = \extension_loaded('mbstring') && \function_exists('mb_strlen'); } if ($exists) { $length = \mb_strlen($str, '8bit'); @@ -311,7 +317,7 @@ final class Core { static $exists = null; if ($exists === null) { - $exists = \extension_loaded('mbstring') && \ini_get('mbstring.func_overload') !== false && (int)\ini_get('mbstring.func_overload') & MB_OVERLOAD_STRING; + $exists = \extension_loaded('mbstring') && \function_exists('mb_substr'); } // This is required to make mb_substr behavior identical to substr. @@ -381,7 +387,15 @@ final class Core * * @return string A $key_length-byte key derived from the password and salt. */ - public static function pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false) + public static function pbkdf2( + $algorithm, + #[\SensitiveParameter] + $password, + $salt, + $count, + $key_length, + $raw_output = false + ) { // Type checks: if (! \is_string($algorithm)) { diff --git a/src/ncc/ThirdParty/defuse/php-encryption/Crypto.php b/src/ncc/ThirdParty/defuse/php-encryption/Crypto.php index 6bed553..38dbbde 100644 --- a/src/ncc/ThirdParty/defuse/php-encryption/Crypto.php +++ b/src/ncc/ThirdParty/defuse/php-encryption/Crypto.php @@ -10,8 +10,11 @@ class Crypto * Encrypts a string with a Key. * * @param string $plaintext - * @param Key $key - * @param bool $raw_binary + * @param Key $key + * @param bool $raw_binary + * + * @throws Ex\EnvironmentIsBrokenException + * @throws \TypeError * * @return string */ @@ -45,11 +48,19 @@ class Crypto * * @param string $plaintext * @param string $password - * @param bool $raw_binary + * @param bool $raw_binary + * + * @throws Ex\EnvironmentIsBrokenException + * @throws \TypeError * * @return string */ - public static function encryptWithPassword($plaintext, $password, $raw_binary = false) + public static function encryptWithPassword( + $plaintext, + #[\SensitiveParameter] + $password, + $raw_binary = false + ) { if (!\is_string($plaintext)) { throw new \TypeError( @@ -124,7 +135,12 @@ class Crypto * * @return string */ - public static function decryptWithPassword($ciphertext, $password, $raw_binary = false) + public static function decryptWithPassword( + $ciphertext, + #[\SensitiveParameter] + $password, + $raw_binary = false + ) { if (!\is_string($ciphertext)) { throw new \TypeError( @@ -160,7 +176,11 @@ class Crypto * * @return string */ - public static function legacyDecrypt($ciphertext, $key) + public static function legacyDecrypt( + $ciphertext, + #[\SensitiveParameter] + $key + ) { if (!\is_string($ciphertext)) { throw new \TypeError( @@ -372,7 +392,13 @@ class Crypto * * @return string */ - protected static function plainEncrypt($plaintext, $key, $iv) + protected static function plainEncrypt( + $plaintext, + #[\SensitiveParameter] + $key, + #[\SensitiveParameter] + $iv + ) { Core::ensureConstantExists('OPENSSL_RAW_DATA'); Core::ensureFunctionExists('openssl_encrypt'); @@ -402,7 +428,14 @@ class Crypto * * @return string */ - protected static function plainDecrypt($ciphertext, $key, $iv, $cipherMethod) + protected static function plainDecrypt( + $ciphertext, + #[\SensitiveParameter] + $key, + #[\SensitiveParameter] + $iv, + $cipherMethod + ) { Core::ensureConstantExists('OPENSSL_RAW_DATA'); Core::ensureFunctionExists('openssl_decrypt'); @@ -431,7 +464,12 @@ class Crypto * * @return bool */ - protected static function verifyHMAC($expected_hmac, $message, $key) + protected static function verifyHMAC( + $expected_hmac, + $message, + #[\SensitiveParameter] + $key + ) { $message_hmac = \hash_hmac(Core::HASH_FUNCTION_NAME, $message, $key, true); return Core::hashEquals($message_hmac, $expected_hmac); diff --git a/src/ncc/ThirdParty/defuse/php-encryption/DerivedKeys.php b/src/ncc/ThirdParty/defuse/php-encryption/DerivedKeys.php index 4dd1d61..a657717 100644 --- a/src/ncc/ThirdParty/defuse/php-encryption/DerivedKeys.php +++ b/src/ncc/ThirdParty/defuse/php-encryption/DerivedKeys.php @@ -4,7 +4,7 @@ namespace ncc\Defuse\Crypto; /** * Class DerivedKeys - * @package ncc\Defuse\Crypto + * @package Defuse\Crypto */ final class DerivedKeys { diff --git a/src/ncc/ThirdParty/defuse/php-encryption/Encoding.php b/src/ncc/ThirdParty/defuse/php-encryption/Encoding.php index 33e126e..0186785 100644 --- a/src/ncc/ThirdParty/defuse/php-encryption/Encoding.php +++ b/src/ncc/ThirdParty/defuse/php-encryption/Encoding.php @@ -175,7 +175,11 @@ final class Encoding * * @return string */ - public static function saveBytesToChecksummedAsciiSafeString($header, $bytes) + public static function saveBytesToChecksummedAsciiSafeString( + $header, + #[\SensitiveParameter] + $bytes + ) { // Headers must be a constant length to prevent one type's header from // being a prefix of another type's header, leading to ambiguity. @@ -207,7 +211,11 @@ final class Encoding * * @return string */ - public static function loadBytesFromChecksummedAsciiSafeString($expected_header, $string) + public static function loadBytesFromChecksummedAsciiSafeString( + $expected_header, + #[\SensitiveParameter] + $string + ) { // Headers must be a constant length to prevent one type's header from // being a prefix of another type's header, leading to ambiguity. diff --git a/src/ncc/ThirdParty/defuse/php-encryption/File.php b/src/ncc/ThirdParty/defuse/php-encryption/File.php index 5424154..b735d6a 100644 --- a/src/ncc/ThirdParty/defuse/php-encryption/File.php +++ b/src/ncc/ThirdParty/defuse/php-encryption/File.php @@ -38,7 +38,12 @@ final class File * @throws Ex\EnvironmentIsBrokenException * @throws Ex\IOException */ - public static function encryptFileWithPassword($inputFilename, $outputFilename, $password) + public static function encryptFileWithPassword( + $inputFilename, + $outputFilename, + #[\SensitiveParameter] + $password + ) { self::encryptFileInternal( $inputFilename, @@ -81,7 +86,12 @@ final class File * @throws Ex\IOException * @throws Ex\WrongKeyOrModifiedCiphertextException */ - public static function decryptFileWithPassword($inputFilename, $outputFilename, $password) + public static function decryptFileWithPassword( + $inputFilename, + $outputFilename, + #[\SensitiveParameter] + $password + ) { self::decryptFileInternal( $inputFilename, @@ -125,7 +135,12 @@ final class File * @throws Ex\IOException * @throws Ex\WrongKeyOrModifiedCiphertextException */ - public static function encryptResourceWithPassword($inputHandle, $outputHandle, $password) + public static function encryptResourceWithPassword( + $inputHandle, + $outputHandle, + #[\SensitiveParameter] + $password + ) { self::encryptResourceInternal( $inputHandle, @@ -169,7 +184,12 @@ final class File * @throws Ex\IOException * @throws Ex\WrongKeyOrModifiedCiphertextException */ - public static function decryptResourceWithPassword($inputHandle, $outputHandle, $password) + public static function decryptResourceWithPassword( + $inputHandle, + $outputHandle, + #[\SensitiveParameter] + $password + ) { self::decryptResourceInternal( $inputHandle, @@ -196,7 +216,9 @@ final class File } /* 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 +231,9 @@ final class File } /* Open the output file. */ + self::removePHPUnitErrorHandler(); $of = @\fopen($outputFilename, 'wb'); + self::restorePHPUnitErrorHandler(); if ($of === false) { \fclose($if); throw new Ex\IOException( @@ -265,7 +289,9 @@ final class File } /* 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 +305,9 @@ final class File } /* Open the output file. */ + self::removePHPUnitErrorHandler(); $of = @\fopen($outputFilename, 'wb'); + self::restorePHPUnitErrorHandler(); if ($of === false) { \fclose($if); throw new Ex\IOException( @@ -770,9 +798,38 @@ final class File { $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/src/ncc/ThirdParty/defuse/php-encryption/Key.php b/src/ncc/ThirdParty/defuse/php-encryption/Key.php index c0bbdde..1353095 100644 --- a/src/ncc/ThirdParty/defuse/php-encryption/Key.php +++ b/src/ncc/ThirdParty/defuse/php-encryption/Key.php @@ -41,7 +41,11 @@ final class Key * * @return Key */ - public static function loadFromAsciiSafeString($saved_key_string, $do_not_trim = false) + public static function loadFromAsciiSafeString( + #[\SensitiveParameter] + $saved_key_string, + $do_not_trim = false + ) { if (!$do_not_trim) { $saved_key_string = Encoding::trimTrailingWhitespace($saved_key_string); @@ -82,7 +86,10 @@ final class Key * * @throws Ex\EnvironmentIsBrokenException */ - private function __construct($bytes) + private function __construct( + #[\SensitiveParameter] + $bytes + ) { Core::ensureTrue( Core::ourStrlen($bytes) === self::KEY_BYTE_SIZE, diff --git a/src/ncc/ThirdParty/defuse/php-encryption/KeyOrPassword.php b/src/ncc/ThirdParty/defuse/php-encryption/KeyOrPassword.php index c65a6e1..8f33dfd 100644 --- a/src/ncc/ThirdParty/defuse/php-encryption/KeyOrPassword.php +++ b/src/ncc/ThirdParty/defuse/php-encryption/KeyOrPassword.php @@ -39,7 +39,10 @@ final class KeyOrPassword * * @return KeyOrPassword */ - public static function createFromPassword($password) + public static function createFromPassword( + #[\SensitiveParameter] + $password + ) { return new KeyOrPassword(self::SECRET_TYPE_PASSWORD, $password); } @@ -133,7 +136,11 @@ final class KeyOrPassword * @param int $secret_type * @param mixed $secret (either a Key or a password string) */ - private function __construct($secret_type, $secret) + private function __construct( + $secret_type, + #[\SensitiveParameter] + $secret + ) { // The constructor is private, so these should never throw. if ($secret_type === self::SECRET_TYPE_KEY) { diff --git a/src/ncc/ThirdParty/defuse/php-encryption/KeyProtectedByPassword.php b/src/ncc/ThirdParty/defuse/php-encryption/KeyProtectedByPassword.php index 696934a..b90c7b4 100644 --- a/src/ncc/ThirdParty/defuse/php-encryption/KeyProtectedByPassword.php +++ b/src/ncc/ThirdParty/defuse/php-encryption/KeyProtectedByPassword.php @@ -22,7 +22,10 @@ final class KeyProtectedByPassword * * @return KeyProtectedByPassword */ - public static function createRandomPasswordProtectedKey($password) + public static function createRandomPasswordProtectedKey( + #[\SensitiveParameter] + $password + ) { $inner_key = Key::createNewRandomKey(); /* The password is hashed as a form of poor-man's domain separation @@ -47,7 +50,10 @@ final class KeyProtectedByPassword * * @return KeyProtectedByPassword */ - public static function loadFromAsciiSafeString($saved_key_string) + public static function loadFromAsciiSafeString( + #[\SensitiveParameter] + $saved_key_string + ) { $encrypted_key = Encoding::loadBytesFromChecksummedAsciiSafeString( self::PASSWORD_KEY_CURRENT_VERSION, @@ -82,7 +88,10 @@ final class KeyProtectedByPassword * @param string $password * @return Key */ - public function unlockKey($password) + public function unlockKey( + #[\SensitiveParameter] + $password + ) { try { $inner_key_encoded = Crypto::decryptWithPassword( @@ -115,7 +124,12 @@ final class KeyProtectedByPassword * * @return KeyProtectedByPassword */ - public function changePassword($current_password, $new_password) + public function changePassword( + #[\SensitiveParameter] + $current_password, + #[\SensitiveParameter] + $new_password + ) { $inner_key = $this->unlockKey($current_password); /* The password is hashed as a form of poor-man's domain separation diff --git a/src/ncc/ThirdParty/defuse/php-encryption/README.md b/src/ncc/ThirdParty/defuse/php-encryption/README.md index 84e1185..0e76317 100644 --- a/src/ncc/ThirdParty/defuse/php-encryption/README.md +++ b/src/ncc/ThirdParty/defuse/php-encryption/README.md @@ -1,7 +1,7 @@ php-encryption =============== -[![Build Status](https://travis-ci.org/defuse/php-encryption.svg?branch=master)](https://travis-ci.org/defuse/php-encryption) +![Build Status](https://app.travis-ci.com/defuse/php-encryption.svg?branch=master) [![codecov](https://codecov.io/gh/defuse/php-encryption/branch/master/graph/badge.svg)](https://codecov.io/gh/defuse/php-encryption) [![Latest Stable Version](https://poser.pugx.org/defuse/php-encryption/v/stable)](https://packagist.org/packages/defuse/php-encryption) [![License](https://poser.pugx.org/defuse/php-encryption/license)](https://packagist.org/packages/defuse/php-encryption) @@ -15,10 +15,10 @@ This is a library for encrypting data with a key or password in PHP. **It requires PHP 5.6 or newer and OpenSSL 1.0.1 or newer.** We recommend using a version of PHP that [still has security support](https://www.php.net/supported-versions.php), which at the time of -writing means PHP 7.3 or later. Using this library with an unsupported +writing means PHP 8.0 or later. Using this library with an unsupported version of PHP could lead to security vulnerabilities. -The current version of `php-encryption` is v2.3.1. This library is expected to +The current version of `php-encryption` is v2.4.0. This library is expected to remain stable and supported by its authors with security and bugfixes until at least January 1st, 2024. @@ -99,23 +99,26 @@ a formal audit, please [contact Taylor Hornby](https://defuse.ca/contact.htm). Public Keys ------------ -The GnuPG public key used to sign current and older releases is available in -[dist/signingkey.asc](https://github.com/defuse/php-encryption/raw/master/dist/signingkey.asc). Its fingerprint is: - -``` -2FA6 1D8D 99B9 2658 6BAC 3D53 385E E055 A129 1538 -``` - -You can verify it against Taylor Hornby's [contact -page](https://defuse.ca/contact.htm) and -[twitter](https://twitter.com/DefuseSec/status/723741424253059074). - -Due to the old key expiring, new releases will be signed with a new public key -available in [dist/signingkey-new.asc](https://github.com/defuse/php-encryption/raw/master/dist/signingkey-new.asc). Its fingerprint is: +The GnuPG public key used to sign the current and new releases is available in +[dist/signingkey-new.asc](https://github.com/defuse/php-encryption/raw/master/dist/signingkey-new.asc). Its fingerprint is: ``` 6DD6 E677 0281 5846 FC85 25A3 DD2E 507F 7BDB 1669 ``` +You can verify it against Taylor Hornby's [contact +page](https://defuse.ca/contact.htm) and +[twitter](https://twitter.com/DefuseSec/status/1670840796743081984). + +Older releases were signed with a (now-expired) available in +[dist/signingkey-old.asc](https://github.com/defuse/php-encryption/raw/master/dist/signingkey-old.asc). The old key's fingerprint is: + +``` +2FA6 1D8D 99B9 2658 6BAC 3D53 385E E055 A129 1538 +``` + +The old key's fingerprint can be verified against Taylor Hornby's [contact page](https://defuse.ca/contact.htm) and +[twitter](https://twitter.com/DefuseSec/status/723741424253059074). + A signature of this new key by the old key is available in [dist/signingkey-new.asc.sig](https://github.com/defuse/php-encryption/raw/master/dist/signingkey-new.asc.sig). diff --git a/src/ncc/ThirdParty/defuse/php-encryption/RuntimeTests.php b/src/ncc/ThirdParty/defuse/php-encryption/RuntimeTests.php index c44654c..d855766 100644 --- a/src/ncc/ThirdParty/defuse/php-encryption/RuntimeTests.php +++ b/src/ncc/ThirdParty/defuse/php-encryption/RuntimeTests.php @@ -1,4 +1,24 @@