From 127531f5aea1b9eaf6c466c6fba2fd4d57212051 Mon Sep 17 00:00:00 2001 From: Wojciech Nawalaniec Date: Wed, 14 Aug 2024 03:33:26 +0200 Subject: [PATCH] Upgrade to PHP 8 I've upgraded code and dependencies to PHP 8.1+. I decided to don't care about PHP 7 as it is unmaintained anymore, so changes are not bc. Although interfaces hasn't changed that much, I only added types where they were missing. I'm not sure to one change I've done to AesGcmEncryptingStream.php and AesGcmDecryptingStream.php. After switch to PHP 8, deprecation errors occurred because of StreamDecoratorTrait dynamic property declaration and I don't see any good solution to fix them for now. So I added #[\AllowDynamicProperties] to ignore them. They are not breaking anything and if there were in the future, there are tests covering it. --- .gitignore | 4 +- .travis.yml | 6 +- README.md | 4 +- composer.json | 11 +- phpunit.xml.dist | 15 +-- src/AesDecryptingStream.php | 52 ++++----- src/AesEncryptingStream.php | 44 +++---- src/AesGcmDecryptingStream.php | 53 +++------ src/AesGcmEncryptingStream.php | 50 +++----- src/Base64DecodingStream.php | 18 +-- src/Base64EncodingStream.php | 18 +-- src/Cbc.php | 31 ++--- src/CipherMethod.php | 6 - src/Ctr.php | 38 +++--- src/DecryptionFailedException.php | 4 +- src/Ecb.php | 13 +-- src/EncryptionFailedException.php | 4 +- src/HashingStream.php | 39 ++----- src/HexDecodingStream.php | 14 +-- src/HexEncodingStream.php | 16 +-- tests/AesDecryptingStreamTest.php | 110 +++++++----------- tests/AesEncryptingStreamTest.php | 155 +++++++++++-------------- tests/AesEncryptionStreamTestTrait.php | 96 ++++++++------- tests/AesGcmDecryptingStreamTest.php | 71 ++++++----- tests/AesGcmEncryptingStreamTest.php | 48 ++++---- tests/Base64DecodingStreamTest.php | 14 +-- tests/Base64EncodingStreamTest.php | 20 ++-- tests/CbcTest.php | 34 +++--- tests/CtrTest.php | 38 +++--- tests/EcbTest.php | 8 +- tests/ExceptionAssertions.php | 16 +++ tests/HashingStreamTest.php | 77 ++++++------ tests/HexDecodingStreamTest.php | 20 ++-- tests/HexEncodingStreamTest.php | 20 ++-- tests/RandomByteStream.php | 17 +-- 35 files changed, 514 insertions(+), 670 deletions(-) create mode 100644 tests/ExceptionAssertions.php diff --git a/.gitignore b/.gitignore index 6d0c746..1a01f3e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ composer.phar /vendor/ composer.lock - coverage-report/ - +.phpunit.result.cache +.idea diff --git a/.travis.yml b/.travis.yml index 608285c..74f148d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,9 @@ language: php php: - - 7.1 - - 7.2 - - 7.3 + - 8.1 + - 8.2 + - 8.3 env: - COMPOSER_OPTS="" diff --git a/README.md b/README.md index 46a7310..b63e1c9 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ $key = 'some-secret-password-here'; $inStream = new Stream(fopen('some-input-file', 'r')); // Any PSR-7 stream will be fine here $cipherTextStream = new AesEncryptingStream($inStream, $key, $cipherMethod); // Wrap the stream in an EncryptingStream -$cipherTextFile = Psr7\stream_for(fopen('encrypted.file', 'w')); +$cipherTextFile = Psr7\Utils::streamFor(fopen('encrypted.file', 'w')); Psr7\copy_to_stream($cipherTextStream, $cipherTextFile); // When you read from the encrypting stream, the data will be encrypted. // You'll also need to store the IV somewhere, because we'll need it later to decrypt the data. @@ -83,7 +83,7 @@ $cipherText = openssl_encrypt( $expectedHash = hash('sha256', $cipherText); $hashingDecorator = new Jsq\EncryptingStreams\HashingStream( - GuzzleHttp\Psr7\stream_for($cipherText), + GuzzleHttp\Psr7\Utils::streamFor($cipherText), $key, function ($hash) use ($expectedHash) { if ($hash !== $expectedHash) { diff --git a/composer.json b/composer.json index 82ddaa0..0e300c1 100644 --- a/composer.json +++ b/composer.json @@ -22,12 +22,15 @@ } }, "require": { - "php": ">=7.1", + "php": ">=8.1", "ext-openssl": "*", - "guzzlehttp/psr7": "~1.0", - "psr/http-message": "~1.0" + "guzzlehttp/psr7": "~2.6", + "psr/http-message": "~2.0" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^10.0" + }, + "scripts": { + "tests": "phpunit" } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 02e062a..033d9b0 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,17 +1,14 @@ - - + tests/ - - - + + src/ - - - + + diff --git a/src/AesDecryptingStream.php b/src/AesDecryptingStream.php index 66a8258..aa7947e 100644 --- a/src/AesDecryptingStream.php +++ b/src/AesDecryptingStream.php @@ -1,4 +1,5 @@ stream = $cipherText; - $this->key = $key; $this->cipherMethod = clone $cipherMethod; } - public function eof() + public function eof(): bool { return $this->cipherBuffer === '' && $this->stream->eof(); } @@ -75,14 +58,14 @@ public function read($length): string { if ($length > strlen($this->plainBuffer)) { $this->plainBuffer .= $this->decryptBlock( - self::BLOCK_SIZE * ceil(($length - strlen($this->plainBuffer)) / self::BLOCK_SIZE) + self::BLOCK_SIZE * (int) ceil(($length - strlen($this->plainBuffer)) / self::BLOCK_SIZE) ); } $data = substr($this->plainBuffer, 0, $length); $this->plainBuffer = substr($this->plainBuffer, $length); - return $data ? $data : ''; + return $data ?: ''; } public function seek($offset, $whence = SEEK_SET): void @@ -93,8 +76,7 @@ public function seek($offset, $whence = SEEK_SET): void $this->cipherMethod->seek(0, SEEK_SET); $this->stream->seek(0, SEEK_SET); } else { - throw new LogicException('AES encryption streams only support being' - . ' rewound, not arbitrary seeking.'); + throw new LogicException('AES encryption streams only support being rewound, not arbitrary seeking.'); } } @@ -124,9 +106,15 @@ private function decryptBlock(int $length): string ); if ($plaintext === false) { - throw new DecryptionFailedException("Unable to decrypt $cipherText with an initialization vector" - . " of {$this->cipherMethod->getCurrentIv()} using the {$this->cipherMethod->getOpenSslName()}" - . " algorithm. Please ensure you have provided the correct algorithm, initialization vector, and key."); + throw new DecryptionFailedException( + sprintf('Unable to decrypt %s with an initialization vector', $cipherText) + . sprintf( + ' of %s using the %s', + $this->cipherMethod->getCurrentIv(), + $this->cipherMethod->getOpenSslName() + ) + . " algorithm. Please ensure you have provided the correct algorithm, initialization vector, and key." + ); } $this->cipherMethod->update($cipherText); diff --git a/src/AesEncryptingStream.php b/src/AesEncryptingStream.php index 97dfcdb..9f33f13 100644 --- a/src/AesEncryptingStream.php +++ b/src/AesEncryptingStream.php @@ -1,4 +1,5 @@ stream = $plainText; - $this->key = $key; $this->cipherMethod = clone $cipherMethod; } @@ -45,7 +31,7 @@ public function getSize(): ?int { $plainTextSize = $this->stream->getSize(); - if ($this->cipherMethod->requiresPadding() && $plainTextSize !== null) { + if ($plainTextSize !== null && $this->cipherMethod->requiresPadding()) { // PKCS7 padding requires that between 1 and self::BLOCK_SIZE be // added to the plaintext to make it an even number of blocks. $padding = self::BLOCK_SIZE - $plainTextSize % self::BLOCK_SIZE; @@ -71,7 +57,7 @@ public function read($length): string $data = substr($this->buffer, 0, $length); $this->buffer = substr($this->buffer, $length); - return $data ? $data : ''; + return $data ?: ''; } public function seek($offset, $whence = SEEK_SET): void @@ -84,7 +70,7 @@ public function seek($offset, $whence = SEEK_SET): void if ($whence === SEEK_SET) { $this->buffer = ''; $wholeBlockOffset - = (int) ($offset / self::BLOCK_SIZE) * self::BLOCK_SIZE; + = (int)($offset / self::BLOCK_SIZE) * self::BLOCK_SIZE; $this->stream->seek($wholeBlockOffset); $this->cipherMethod->seek($wholeBlockOffset); $this->read($offset - $wholeBlockOffset); @@ -118,9 +104,15 @@ private function encryptBlock(int $length): string ); if ($cipherText === false) { - throw new EncryptionFailedException("Unable to encrypt data with an initialization vector" - . " of {$this->cipherMethod->getCurrentIv()} using the {$this->cipherMethod->getOpenSslName()}" - . " algorithm. Please ensure you have provided a valid algorithm and initialization vector."); + throw new EncryptionFailedException( + "Unable to encrypt data with an initialization vector" + . sprintf( + ' of %s using the %s', + $this->cipherMethod->getCurrentIv(), + $this->cipherMethod->getOpenSslName() + ) + . " algorithm. Please ensure you have provided a valid algorithm and initialization vector." + ); } $this->cipherMethod->update($cipherText); diff --git a/src/AesGcmDecryptingStream.php b/src/AesGcmDecryptingStream.php index e72a948..369c5e0 100644 --- a/src/AesGcmDecryptingStream.php +++ b/src/AesGcmDecryptingStream.php @@ -1,51 +1,32 @@ cipherText = $cipherText; - $this->key = $key; - $this->initializationVector = $initializationVector; - $this->tag = $tag; - $this->aad = $aad; - $this->tagLength = $tagLength; - $this->keySize = $keySize; } public function createStream(): StreamInterface { $plaintext = openssl_decrypt( - (string) $this->cipherText, - "aes-{$this->keySize}-gcm", + (string)$this->cipherText, + sprintf('aes-%d-gcm', $this->keySize), $this->key, OPENSSL_RAW_DATA, $this->initializationVector, @@ -54,12 +35,14 @@ public function createStream(): StreamInterface ); if ($plaintext === false) { - throw new DecryptionFailedException("Unable to decrypt data with an initialization vector" - . " of {$this->initializationVector} using the aes-{$this->keySize}-gcm algorithm. Please" - . " ensure you have provided a valid key size, initialization vector, and key."); + throw new DecryptionFailedException( + "Unable to decrypt data with an initialization vector" + . sprintf(' of %s using the aes-%d-gcm algorithm. Please', $this->initializationVector, $this->keySize) + . " ensure you have provided a valid key size, initialization vector, and key." + ); } - return Psr7\stream_for($plaintext); + return Utils::streamFor($plaintext); } public function isWritable(): bool diff --git a/src/AesGcmEncryptingStream.php b/src/AesGcmEncryptingStream.php index 6c9a572..419caad 100644 --- a/src/AesGcmEncryptingStream.php +++ b/src/AesGcmEncryptingStream.php @@ -1,49 +1,33 @@ plaintext = $plaintext; - $this->key = $key; - $this->initializationVector = $initializationVector; - $this->aad = $aad; - $this->tagLength = $tagLength; - $this->keySize = $keySize; } public function createStream(): StreamInterface { $cipherText = openssl_encrypt( - (string) $this->plaintext, - "aes-{$this->keySize}-gcm", + (string)$this->plaintext, + sprintf('aes-%d-gcm', $this->keySize), $this->key, OPENSSL_RAW_DATA, $this->initializationVector, @@ -53,12 +37,14 @@ public function createStream(): StreamInterface ); if ($cipherText === false) { - throw new EncryptionFailedException("Unable to encrypt data with an initialization vector" - . " of {$this->initializationVector} using the aes-{$this->keySize}-gcm algorithm. Please" - . " ensure you have provided a valid key size and initialization vector."); + throw new EncryptionFailedException( + "Unable to encrypt data with an initialization vector" + . sprintf(' of %s using the aes-%d-gcm algorithm. Please', $this->initializationVector, $this->keySize) + . " ensure you have provided a valid key size and initialization vector." + ); } - return Psr7\stream_for($cipherText); + return Utils::streamFor($cipherText); } public function getTag(): string diff --git a/src/Base64DecodingStream.php b/src/Base64DecodingStream.php index 8884fea..c81498c 100644 --- a/src/Base64DecodingStream.php +++ b/src/Base64DecodingStream.php @@ -1,4 +1,5 @@ stream = $stream; } public function getSize(): ?int @@ -28,9 +20,9 @@ public function getSize(): ?int return null; } - public function read($length): string + public function read(int $length): string { - $toRead = ceil($length / 3) * 4; + $toRead = (int)ceil($length / 3) * 4; $this->buffer .= base64_decode($this->stream->read($toRead)); $toReturn = substr($this->buffer, 0, $length); diff --git a/src/Base64EncodingStream.php b/src/Base64EncodingStream.php index a796688..5732317 100644 --- a/src/Base64EncodingStream.php +++ b/src/Base64EncodingStream.php @@ -1,4 +1,5 @@ stream = $stream; } public function getSize(): ?int @@ -28,12 +20,12 @@ public function getSize(): ?int $unencodedSize = $this->stream->getSize(); return $unencodedSize === null ? null - : (int) ceil($unencodedSize / 3) * 4; + : (int)ceil($unencodedSize / 3) * 4; } public function read($length): string { - $toRead = ceil($length / 4) * 3; + $toRead = (int)ceil($length / 4) * 3; $this->buffer .= base64_encode($this->stream->read($toRead)); $toReturn = substr($this->buffer, 0, $length); diff --git a/src/Cbc.php b/src/Cbc.php index 30f3bc4..fc2bf4a 100644 --- a/src/Cbc.php +++ b/src/Cbc.php @@ -1,4 +1,5 @@ baseIv = $this->iv = $iv; - $this->keySize = $keySize; - - if (strlen($iv) !== openssl_cipher_iv_length($this->getOpenSslName())) { + $this->iv = $this->baseIv; + if (strlen($this->baseIv) !== openssl_cipher_iv_length($this->getOpenSslName())) { throw new Iae('Invalid initialization vector'); } } public function getOpenSslName(): string { - return "aes-{$this->keySize}-cbc"; + return sprintf('aes-%d-cbc', $this->keySize); } public function getCurrentIv(): string @@ -53,8 +39,7 @@ public function seek(int $offset, int $whence = SEEK_SET): void if ($offset === 0 && $whence === SEEK_SET) { $this->iv = $this->baseIv; } else { - throw new LogicException('CBC initialization only support being' - . ' rewound, not arbitrary seeking.'); + throw new LogicException('CBC initialization only support being rewound, not arbitrary seeking.'); } } diff --git a/src/CipherMethod.php b/src/CipherMethod.php index 110014c..c0863a9 100644 --- a/src/CipherMethod.php +++ b/src/CipherMethod.php @@ -8,8 +8,6 @@ interface CipherMethod /** * Returns an identifier recognizable by `openssl_*` functions, such as * `aes-256-cbc` or `aes-128-ctr`. - * - * @return string */ public function getOpenSslName(): string; @@ -29,8 +27,6 @@ public function requiresPadding(): bool; * Adjust the return of this::getCurrentIv to reflect a seek performed on * the encryption stream using this IV object. * - * @param int $offset - * @param int $whence * * @throws LogicException Thrown if the requested seek is not supported by * this IV implementation. For example, a CBC IV @@ -42,8 +38,6 @@ public function seek(int $offset, int $whence = SEEK_SET): void; /** * Take account of the last cipher text block to adjust the return of * this::getCurrentIv - * - * @param string $cipherTextBlock */ public function update(string $cipherTextBlock): void; } \ No newline at end of file diff --git a/src/Ctr.php b/src/Ctr.php index 0d06465..3247cce 100644 --- a/src/Ctr.php +++ b/src/Ctr.php @@ -6,29 +6,24 @@ class Ctr implements CipherMethod { - const BLOCK_SIZE = 16; - const CTR_BLOCK_MAX = 65536; // maximum 16-bit unsigned integer value + public const BLOCK_SIZE = 16; + + public const CTR_BLOCK_MAX = 65536; // maximum 16-bit unsigned integer value /** * The hash initialization vector, stored as eight 16-bit words * @var int[] */ - private $iv; + private readonly array $iv; /** * The counter offset to add to the initialization vector * @var int[] */ - private $ctrOffset; - - /** - * @var int - */ - private $keySize; + private array $ctrOffset; - public function __construct(string $iv, int $keySize = 256) + public function __construct(string $iv, private readonly int $keySize = 256) { - $this->keySize = $keySize; if (strlen($iv) !== openssl_cipher_iv_length($this->getOpenSslName())) { throw new InvalidArgumentException('Invalid initialization vector'); } @@ -39,7 +34,7 @@ public function __construct(string $iv, int $keySize = 256) public function getOpenSslName(): string { - return "aes-{$this->keySize}-ctr"; + return sprintf('aes-%d-ctr', $this->keySize); } public function getCurrentIv(): string @@ -55,20 +50,19 @@ public function requiresPadding(): bool public function seek(int $offset, int $whence = SEEK_SET): void { if ($offset % self::BLOCK_SIZE !== 0) { - throw new LogicException('CTR initialization vectors only support ' - . ' seeking to indexes that are multiples of ' + throw new LogicException('CTR initialization vectors only support seeking to indexes that are multiples of ' . self::BLOCK_SIZE); } if ($whence === SEEK_SET) { $this->resetOffset(); - $this->incrementOffset($offset / self::BLOCK_SIZE); + $this->incrementOffset((int)ceil($offset / self::BLOCK_SIZE)); } elseif ($whence === SEEK_CUR) { if ($offset < 0) { throw new LogicException('Negative offsets are not supported.'); } - $this->incrementOffset($offset / self::BLOCK_SIZE); + $this->incrementOffset((int)ceil($offset / self::BLOCK_SIZE)); } else { throw new LogicException('Unrecognized whence.'); } @@ -76,24 +70,20 @@ public function seek(int $offset, int $whence = SEEK_SET): void public function update(string $cipherTextBlock): void { - $this->incrementOffset(strlen($cipherTextBlock) / self::BLOCK_SIZE); + $this->incrementOffset((int)ceil(strlen($cipherTextBlock) / self::BLOCK_SIZE)); } /** - * @param string $iv * @return int[] */ private function extractIvParts(string $iv): array { - return array_map(function ($part) { - return unpack('nnum', $part)['num']; - }, str_split($iv, 2)); + return array_map(fn($part) => unpack('nnum', $part)['num'], str_split($iv, 2)); } /** * @param int[] $baseIv * @param int[] $ctrOffset - * @return string */ private function calculateCurrentIv(array $baseIv, array $ctrOffset): string { @@ -105,9 +95,7 @@ private function calculateCurrentIv(array $baseIv, array $ctrOffset): string $iv[$i] = $sum % self::CTR_BLOCK_MAX; } - return implode(array_map(function ($ivBlock) { - return pack('n', $ivBlock); - }, $iv)); + return implode('', array_map(fn($ivBlock): string => pack('n', $ivBlock), $iv)); } private function incrementOffset(int $incrementBy): void diff --git a/src/DecryptionFailedException.php b/src/DecryptionFailedException.php index e59b1b5..1028a8f 100644 --- a/src/DecryptionFailedException.php +++ b/src/DecryptionFailedException.php @@ -1,4 +1,6 @@ keySize = $keySize; } public function getOpenSslName(): string { - return "aes-{$this->keySize}-ecb"; + return sprintf('aes-%d-ecb', $this->keySize); } public function getCurrentIv(): string diff --git a/src/EncryptionFailedException.php b/src/EncryptionFailedException.php index 11d68cd..99a83d9 100644 --- a/src/EncryptionFailedException.php +++ b/src/EncryptionFailedException.php @@ -1,4 +1,6 @@ stream = $stream; - $this->key = $key; + private readonly string $algorithm = 'sha256' + ) { $this->onComplete = $onComplete; - $this->algorithm = $algorithm; - $this->initializeHash(); } @@ -60,9 +45,10 @@ public function getHash(): ?string public function read($length): string { $read = $this->stream->read($length); - if (strlen($read) > 0) { + if ($read !== '') { hash_update($this->hashResource, $read); } + if ($this->stream->eof()) { $this->hash = hash_final($this->hashResource, true); if ($this->onComplete) { @@ -79,8 +65,7 @@ public function seek($offset, $whence = SEEK_SET): void $this->stream->seek($offset, $whence); $this->initializeHash(); } else { - throw new LogicException('AES encryption streams only support being' - . ' rewound, not arbitrary seeking.'); + throw new LogicException('AES encryption streams only support being rewound, not arbitrary seeking.'); } } @@ -90,7 +75,7 @@ private function initializeHash(): void $this->hashResource = hash_init( $this->algorithm, $this->key !== null ? HASH_HMAC : 0, - $this->key + $this->key ?? '' ); } } \ No newline at end of file diff --git a/src/HexDecodingStream.php b/src/HexDecodingStream.php index 24604d8..e2435e5 100644 --- a/src/HexDecodingStream.php +++ b/src/HexDecodingStream.php @@ -1,4 +1,5 @@ stream = $stream; } public function getSize(): ?int diff --git a/src/HexEncodingStream.php b/src/HexEncodingStream.php index 7aae87f..717ae00 100644 --- a/src/HexEncodingStream.php +++ b/src/HexEncodingStream.php @@ -1,4 +1,5 @@ stream = $stream; } public function getSize(): ?int @@ -33,7 +25,7 @@ public function getSize(): ?int public function read($length): string { - $this->buffer .= bin2hex($this->stream->read(ceil($length / 2))); + $this->buffer .= bin2hex($this->stream->read((int)ceil($length / 2))); $toReturn = substr($this->buffer, 0, $length); $this->buffer = substr($this->buffer, $length); diff --git a/tests/AesDecryptingStreamTest.php b/tests/AesDecryptingStreamTest.php index 096ca3d..88f3b53 100644 --- a/tests/AesDecryptingStreamTest.php +++ b/tests/AesDecryptingStreamTest.php @@ -1,30 +1,32 @@ getOpenSslName(), @@ -34,23 +36,18 @@ public function testStreamOutputSameAsOpenSSL( ); $this->assertSame( - (string) new AesDecryptingStream(Psr7\stream_for($cipherText), self::KEY, $iv), + (string)new AesDecryptingStream(Utils::streamFor($cipherText), self::KEY, $iv), $plainText ); } - /** - * @dataProvider cartesianJoinInputCipherMethodProvider - * - * @param StreamInterface $plainTextStream - * @param string $plainText - * @param CipherMethod $iv - */ + + #[DataProvider('cartesianJoinInputCipherMethodProvider')] public function testReportsSizeOfPlaintextWherePossible( StreamInterface $plainTextStream, string $plainText, CipherMethod $iv - ) { + ): void { $cipherText = openssl_encrypt( $plainText, $iv->getOpenSslName(), @@ -59,7 +56,7 @@ public function testReportsSizeOfPlaintextWherePossible( $iv->getCurrentIv() ); $deciphered = new AesDecryptingStream( - Psr7\stream_for($cipherText), + Utils::streamFor($cipherText), self::KEY, $iv ); @@ -71,18 +68,13 @@ public function testReportsSizeOfPlaintextWherePossible( } } - /** - * @dataProvider cartesianJoinInputCipherMethodProvider - * - * @param StreamInterface $plainTextStream - * @param string $plainText - * @param CipherMethod $iv - */ + + #[DataProvider('cartesianJoinInputCipherMethodProvider')] public function testSupportsReadingBeyondTheEndOfTheStream( StreamInterface $plainTextStream, string $plainText, CipherMethod $iv - ) { + ): void { $cipherText = openssl_encrypt( $plainText, $iv->getOpenSslName(), @@ -90,23 +82,18 @@ public function testSupportsReadingBeyondTheEndOfTheStream( OPENSSL_RAW_DATA, $iv->getCurrentIv() ); - $deciphered = new AesDecryptingStream(Psr7\stream_for($cipherText), self::KEY, $iv); + $deciphered = new AesDecryptingStream(Utils::streamFor($cipherText), self::KEY, $iv); $read = $deciphered->read(strlen($plainText) + AesDecryptingStream::BLOCK_SIZE); $this->assertSame($plainText, $read); } - /** - * @dataProvider cartesianJoinInputCipherMethodProvider - * - * @param StreamInterface $plainTextStream - * @param string $plainText - * @param CipherMethod $iv - */ + + #[DataProvider('cartesianJoinInputCipherMethodProvider')] public function testSupportsRewinding( StreamInterface $plainTextStream, string $plainText, CipherMethod $iv - ) { + ): void { $cipherText = openssl_encrypt( $plainText, $iv->getOpenSslName(), @@ -114,18 +101,14 @@ public function testSupportsRewinding( OPENSSL_RAW_DATA, $iv->getCurrentIv() ); - $deciphered = new AesDecryptingStream(Psr7\stream_for($cipherText), self::KEY, $iv); + $deciphered = new AesDecryptingStream(Utils::streamFor($cipherText), self::KEY, $iv); $firstBytes = $deciphered->read(256 * 2 + 3); $deciphered->rewind(); $this->assertSame($firstBytes, $deciphered->read(256 * 2 + 3)); } - /** - * @dataProvider cipherMethodProvider - * - * @param CipherMethod $iv - */ - public function testMemoryUsageRemainsConstant(CipherMethod $iv) + #[DataProvider('cipherMethodProvider')] + public function testMemoryUsageRemainsConstant(CipherMethod $iv): void { $memory = memory_get_usage(); @@ -140,7 +123,7 @@ public function testMemoryUsageRemainsConstant(CipherMethod $iv) $this->assertLessThanOrEqual($memory + 2 * self::MB, memory_get_usage()); } - public function testIsNotWritable() + public function testIsNotWritable(): void { $stream = new AesDecryptingStream( new RandomByteStream(124 * self::MB), @@ -151,11 +134,9 @@ public function testIsNotWritable() $this->assertFalse($stream->isWritable()); } - /** - * @expectedException \LogicException - */ - public function testDoesNotSupportArbitrarySeeking() + public function testDoesNotSupportArbitrarySeeking(): void { + $this->expectException(LogicException::class); $stream = new AesDecryptingStream( new RandomByteStream(124 * self::MB), 'foo', @@ -165,16 +146,12 @@ public function testDoesNotSupportArbitrarySeeking() $stream->seek(1); } - /** - * @dataProvider cipherMethodProvider - * - * @param CipherMethod $cipherMethod - */ + #[DataProvider('cipherMethodProvider')] public function testReturnsEmptyStringWhenSourceStreamEmpty( CipherMethod $cipherMethod - ) { + ): void { $stream = new AesDecryptingStream( - new AesEncryptingStream(Psr7\stream_for(''), self::KEY, clone $cipherMethod), + new AesEncryptingStream(Utils::streamFor(''), self::KEY, clone $cipherMethod), self::KEY, $cipherMethod ); @@ -183,19 +160,22 @@ public function testReturnsEmptyStringWhenSourceStreamEmpty( $this->assertSame($stream->read(self::MB), ''); } - public function testEmitsErrorWhenDecryptionFails() + public function testEmitsErrorWhenDecryptionFails(): void { // Capture the error in a custom handler to avoid PHPUnit's error trap - set_error_handler(function ($_, $message) use (&$error) { - $error = $message; - }); + $cipherText = Utils::streamFor(random_bytes(self::MB)); // not big fan of random in test but ok... + $cipherMethod = new Cbc(random_bytes(openssl_cipher_iv_length('aes-256-cbc'))); + $expectedException = new DecryptionFailedException( + sprintf('Unable to decrypt %s with an initialization vector', $cipherText) + . sprintf(' of %s using the %s', $cipherMethod->getCurrentIv(), $cipherMethod->getOpenSslName()) + . " algorithm. Please ensure you have provided the correct algorithm, initialization vector, and key." + ); // Trigger a decryption failure by attempting to decrypt gibberish // Not all cipher methods will balk (CTR, for example, will simply // decrypt gibberish into gibberish), so CBC is used. - $_ = (string) new AesDecryptingStream(new RandomByteStream(self::MB), self::KEY, - new Cbc(random_bytes(openssl_cipher_iv_length('aes-256-cbc')))); + $act = fn(): string => (string)new AesDecryptingStream($cipherText, self::KEY, $cipherMethod); - $this->assertRegExp("/DecryptionFailedException: Unable to decrypt/", $error); + $this->assertException($act, $expectedException); } } diff --git a/tests/AesEncryptingStreamTest.php b/tests/AesEncryptingStreamTest.php index 201c6e9..443f39d 100644 --- a/tests/AesEncryptingStreamTest.php +++ b/tests/AesEncryptingStreamTest.php @@ -1,30 +1,30 @@ assertSame( openssl_encrypt( $plainText, @@ -33,7 +33,7 @@ public function testStreamOutputSameAsOpenSSL( OPENSSL_RAW_DATA, $iv->getCurrentIv() ), - (string) new AesEncryptingStream( + (string)new AesEncryptingStream( $plainTextStream, self::KEY, $iv @@ -41,18 +41,12 @@ public function testStreamOutputSameAsOpenSSL( ); } - /** - * @dataProvider cartesianJoinInputCipherMethodProvider - * - * @param StreamInterface $plainTextStream - * @param string $plainText - * @param CipherMethod $iv - */ + #[DataProvider('cartesianJoinInputCipherMethodProvider')] public function testSupportsReadingBeyondTheEndOfTheStream( StreamInterface $plainTextStream, string $plainText, CipherMethod $iv - ) { + ): void { $cipherText = openssl_encrypt( $plainText, $iv->getOpenSslName(), @@ -65,18 +59,12 @@ public function testSupportsReadingBeyondTheEndOfTheStream( $this->assertSame('', $cipherStream->read(self::MB)); } - /** - * @dataProvider cartesianJoinInputCipherMethodProvider - * - * @param StreamInterface $plainTextStream - * @param string $plainText - * @param CipherMethod $iv - */ + #[DataProvider('cartesianJoinInputCipherMethodProvider')] public function testSupportsRewinding( StreamInterface $plainTextStream, string $plainText, CipherMethod $iv - ) { + ): void { if (!$plainTextStream->isSeekable()) { $this->markTestSkipped('Cannot rewind encryption streams whose plaintext is not seekable'); } else { @@ -87,32 +75,22 @@ public function testSupportsRewinding( } } - /** - * @dataProvider cartesianJoinInputCipherMethodProvider - * - * @param StreamInterface $plainTextStream - * @param string $plainText - * @param CipherMethod $iv - */ + #[DataProvider('cartesianJoinInputCipherMethodProvider')] public function testAccuratelyReportsSizeOfCipherText( StreamInterface $plainTextStream, string $plainText, CipherMethod $iv - ) { + ): void { if ($plainTextStream->getSize() === null) { $this->markTestSkipped('Cannot read size of ciphertext stream when plaintext stream size is unknown'); } else { $cipherText = new AesEncryptingStream($plainTextStream, 'foo', $iv); - $this->assertSame($cipherText->getSize(), strlen((string) $cipherText)); + $this->assertSame($cipherText->getSize(), strlen((string)$cipherText)); } } - /** - * @dataProvider cipherMethodProvider - * - * @param CipherMethod $cipherMethod - */ - public function testMemoryUsageRemainsConstant(CipherMethod $cipherMethod) + #[DataProvider('cipherMethodProvider')] + public function testMemoryUsageRemainsConstant(CipherMethod $cipherMethod): void { $memory = memory_get_usage(); @@ -130,7 +108,7 @@ public function testMemoryUsageRemainsConstant(CipherMethod $cipherMethod) $this->assertLessThanOrEqual($memory + 2 * self::MB, memory_get_usage()); } - public function testIsNotWritable() + public function testIsNotWritable(): void { $stream = new AesEncryptingStream( new RandomByteStream(124 * self::MB), @@ -141,16 +119,12 @@ public function testIsNotWritable() $this->assertFalse($stream->isWritable()); } - /** - * @dataProvider cipherMethodProvider - * - * @param CipherMethod $cipherMethod - */ + #[DataProvider('cipherMethodProvider')] public function testReturnsPaddedOrEmptyStringWhenSourceStreamEmpty( CipherMethod $cipherMethod - ){ + ): void { $stream = new AesEncryptingStream( - Psr7\stream_for(''), + Utils::streamFor(''), 'foo', $cipherMethod ); @@ -161,30 +135,21 @@ public function testReturnsPaddedOrEmptyStringWhenSourceStreamEmpty( $this->assertSame($stream->read(self::MB), ''); } - /** - * @dataProvider cipherMethodProvider - * - * @param CipherMethod $cipherMethod - * - * @expectedException \LogicException - */ - public function testDoesNotSupportSeekingFromEnd(CipherMethod $cipherMethod) + #[DataProvider('cipherMethodProvider')] + public function testDoesNotSupportSeekingFromEnd(CipherMethod $cipherMethod): void { - $stream = new AesEncryptingStream(Psr7\stream_for('foo'), 'foo', $cipherMethod); + $this->expectException(LogicException::class); + $stream = new AesEncryptingStream(Utils::streamFor('foo'), 'foo', $cipherMethod); $stream->seek(1, SEEK_END); } - /** - * @dataProvider seekableCipherMethodProvider - * - * @param CipherMethod $cipherMethod - */ + #[DataProvider('seekableCipherMethodProvider')] public function testSupportsSeekingFromCurrentPosition( CipherMethod $cipherMethod - ){ + ): void { $stream = new AesEncryptingStream( - Psr7\stream_for(random_bytes(2 * self::MB)), + Utils::streamFor(random_bytes(2 * self::MB)), 'foo', $cipherMethod ); @@ -193,27 +158,45 @@ public function testSupportsSeekingFromCurrentPosition( $stream->seek(-5, SEEK_CUR); $this->assertSame($lastFiveBytes, $stream->read(5)); } - public function testEmitsErrorWhenEncryptionFails() + + public function testEmitsErrorWhenEncryptionFails(): void { - // Capture the error in a custom handler to avoid PHPUnit's error trap - set_error_handler(function ($_, $message) use (&$error) { - $error = $message; - }); + $cipherMethod = new class implements CipherMethod { + public function getCurrentIv(): string + { + return 'iv'; + } + + public function getOpenSslName(): string + { + return 'aes-157-cbd'; + } + + public function requiresPadding(): bool + { + return false; + } + + public function update(string $cipherTextBlock): void + { + } + + public function seek(int $offset, int $whence = SEEK_SET): void + { + } + }; + $expectedException = new EncryptionFailedException( + "Unable to encrypt data with an initialization vector" + . sprintf(' of %s using the %s', $cipherMethod->getCurrentIv(), $cipherMethod->getOpenSslName()) + . " algorithm. Please ensure you have provided a valid algorithm and initialization vector." + ); // Trigger an openssl error by supplying an invalid key size - $_ = (string) new AesEncryptingStream(new RandomByteStream(self::MB), self::KEY, - new class implements CipherMethod { - public function getCurrentIv(): string { return 'iv'; } - - public function getOpenSslName(): string { return 'aes-157-cbd'; } - - public function requiresPadding(): bool { return false; } - - public function update(string $cipherTextBlock): void {} - - public function seek(int $offset, int $whence = SEEK_SET): void {} - }); + $act = fn(): string => @(string)new AesEncryptingStream( + new RandomByteStream(self::MB), self::KEY, + $cipherMethod + ); - $this->assertRegExp("/EncryptionFailedException: Unable to encrypt/", $error); + $this->assertException($act, $expectedException); } } diff --git a/tests/AesEncryptionStreamTestTrait.php b/tests/AesEncryptionStreamTestTrait.php index 7b32a72..6306927 100644 --- a/tests/AesEncryptionStreamTestTrait.php +++ b/tests/AesEncryptionStreamTestTrait.php @@ -1,29 +1,37 @@ unwrapProvider([$this, 'plainTextProvider']); + $plainTexts = self::unwrapProvider(self::plainTextProvider()); + $counter = count($plainTexts); - for ($i = 0; $i < count($plainTexts); $i++) { - for ($j = 0; $j < count($this->cipherMethodProvider()); $j++) { - $toReturn []= [ + for ($i = 0; $i < $counter; $i++) { + for ($j = 0; $j < count(self::cipherMethodProvider()); $j++) { + $toReturn [] = [ // Test each string with standard temp streams - Psr7\stream_for($plainTexts[$i]), + Utils::streamFor($plainTexts[$i]), $plainTexts[$i], - $this->cipherMethodProvider()[$j][0] + self::cipherMethodProvider()[$j][0] ]; - $toReturn []= [ + $toReturn [] = [ // Test each string with a stream that does not know its own size - Psr7\stream_for((function ($pt) { yield $pt; })($plainTexts[$i])), + Utils::streamFor( + (function ($pt) { + yield $pt; + })( + $plainTexts[$i] + ) + ), $plainTexts[$i], - $this->cipherMethodProvider()[$j][0] + self::cipherMethodProvider()[$j][0] ]; } } @@ -31,24 +39,31 @@ public function cartesianJoinInputCipherMethodProvider() return $toReturn; } - public function cartesianJoinInputKeySizeProvider() + public static function cartesianJoinInputKeySizeProvider(): array { $toReturn = []; - $plainTexts = $this->unwrapProvider([$this, 'plainTextProvider']); - $keySizes = $this->unwrapProvider([$this, 'keySizeProvider']); + $plainTexts = self::unwrapProvider(self::plainTextProvider()); + $keySizes = self::unwrapProvider(self::keySizeProvider()); + $counter = count($plainTexts); - for ($i = 0; $i < count($plainTexts); $i++) { + for ($i = 0; $i < $counter; $i++) { for ($j = 0; $j < count($keySizes); $j++) { - $toReturn []= [ + $toReturn [] = [ // Test each string with standard temp streams - Psr7\stream_for($plainTexts[$i]), + Utils::streamFor($plainTexts[$i]), $plainTexts[$i], $keySizes[$j], ]; - $toReturn []= [ + $toReturn [] = [ // Test each string with a stream that does not know its own size - Psr7\stream_for((function ($pt) { yield $pt; })($plainTexts[$i])), + Utils::streamFor( + (function ($pt) { + yield $pt; + })( + $plainTexts[$i] + ) + ), $plainTexts[$i], $keySizes[$j], ]; @@ -58,32 +73,34 @@ public function cartesianJoinInputKeySizeProvider() return $toReturn; } - public function cipherMethodProvider() + public static function cipherMethodProvider(): array { $toReturn = []; - foreach ($this->unwrapProvider([$this, 'keySizeProvider']) as $keySize) { - $toReturn []= [new Cbc( - random_bytes(openssl_cipher_iv_length('aes-256-cbc')), - $keySize - )]; - $toReturn []= [new Ctr( - random_bytes(openssl_cipher_iv_length('aes-256-ctr')), - $keySize - )]; - $toReturn []= [new Ecb($keySize)]; + foreach (self::unwrapProvider(self::keySizeProvider()) as $keySize) { + $toReturn [] = [ + new Cbc( + random_bytes(openssl_cipher_iv_length('aes-256-cbc')), + $keySize + ) + ]; + $toReturn [] = [ + new Ctr( + random_bytes(openssl_cipher_iv_length('aes-256-ctr')), + $keySize + ) + ]; + $toReturn [] = [new Ecb($keySize)]; } return $toReturn; } - public function seekableCipherMethodProvider() + public static function seekableCipherMethodProvider(): array { - return array_filter($this->cipherMethodProvider(), function (array $args) { - return !($args[0] instanceof Cbc); - }); + return array_filter(self::cipherMethodProvider(), fn(array $args): bool => !($args[0] instanceof Cbc)); } - public function keySizeProvider() + public static function keySizeProvider(): array { return [ [128], @@ -92,7 +109,8 @@ public function keySizeProvider() ]; } - public function plainTextProvider() { + public static function plainTextProvider(): array + { return [ ['The rain in Spain falls mainly on the plain.'], ['دست‌نوشته‌ها نمی‌سوزند'], @@ -104,10 +122,8 @@ public function plainTextProvider() { ]; } - private function unwrapProvider(callable $provider) + private static function unwrapProvider(array $provider): array { - return array_map(function (array $wrapped) { - return $wrapped[0]; - }, call_user_func($provider)); + return array_map(fn(array $wrapped) => $wrapped[0], $provider); } } diff --git a/tests/AesGcmDecryptingStreamTest.php b/tests/AesGcmDecryptingStreamTest.php index 48f8eae..fe09f03 100644 --- a/tests/AesGcmDecryptingStreamTest.php +++ b/tests/AesGcmDecryptingStreamTest.php @@ -1,30 +1,32 @@ 'bar']); $tag = null; $cipherText = openssl_encrypt( $plainText, - "aes-{$keySize}-gcm", + sprintf('aes-%d-gcm', $keySize), self::KEY, OPENSSL_RAW_DATA, $iv, @@ -32,9 +34,8 @@ public function testStreamOutputSameAsOpenSSL(StreamInterface $plainTextStream, $additionalData, 16 ); - $decryptingStream = new AesGcmDecryptingStream( - Psr7\stream_for($cipherText), + Utils::streamFor($cipherText), self::KEY, $iv, $tag, @@ -43,36 +44,50 @@ public function testStreamOutputSameAsOpenSSL(StreamInterface $plainTextStream, $keySize ); - $this->assertSame((string) $decryptingStream, $plainText); + $this->assertSame((string)$decryptingStream, $plainText); } - public function testIsNotWritable() + public function testIsNotWritable(): void { + $iv = random_bytes(openssl_cipher_iv_length('aes-256-gcm')); + $tag = null; $decryptingStream = new AesGcmDecryptingStream( - Psr7\stream_for(''), + Utils::streamFor( + openssl_encrypt( + '1234', + 'aes-256-gcm', + self::KEY, + OPENSSL_RAW_DATA, + $iv, + $tag + ) + ), self::KEY, - random_bytes(openssl_cipher_iv_length('aes-256-gcm')), - 'tag' + $iv, + $tag ); $this->assertFalse($decryptingStream->isWritable()); } - public function testEmitsErrorWhenDecryptionFails() + public function testEmitsErrorWhenDecryptionFails(): void { - // Capture the error in a custom handler to avoid PHPUnit's error trap - set_error_handler(function ($_, $message) use (&$error) { - $error = $message; - }); + $initializationVector = random_bytes(openssl_cipher_iv_length('aes-256-gcm')); + $keySize = 256; + $expectedException = new DecryptionFailedException( + "Unable to decrypt data with an initialization vector" + . sprintf(' of %s using the aes-%d-gcm algorithm. Please', $initializationVector, $keySize) + . " ensure you have provided a valid key size, initialization vector, and key." + ); - // Trigger a decryption failure by attempting to decrypt gibberish - $_ = (string) new AesGcmDecryptingStream( + $act = fn(): string => (string)new AesGcmDecryptingStream( new RandomByteStream(1024 * 1024), self::KEY, - random_bytes(openssl_cipher_iv_length('aes-256-gcm')), - 'tag' + $initializationVector, + 'tag', + keySize: $keySize ); - $this->assertRegExp("/DecryptionFailedException: Unable to decrypt/", $error); + $this->assertException($act, $expectedException); } } diff --git a/tests/AesGcmEncryptingStreamTest.php b/tests/AesGcmEncryptingStreamTest.php index a014ab0..49b3153 100644 --- a/tests/AesGcmEncryptingStreamTest.php +++ b/tests/AesGcmEncryptingStreamTest.php @@ -1,24 +1,21 @@ 'bar']); $tag = null; @@ -32,10 +29,10 @@ public function testStreamOutputSameAsOpenSSL(StreamInterface $plainTextStream, ); $this->assertSame( - (string) $encryptingStream, + (string)$encryptingStream, openssl_encrypt( $plainText, - "aes-{$keySize}-gcm", + sprintf('aes-%d-gcm', $keySize), self::KEY, OPENSSL_RAW_DATA, $iv, @@ -48,10 +45,10 @@ public function testStreamOutputSameAsOpenSSL(StreamInterface $plainTextStream, $this->assertSame($tag, $encryptingStream->getTag()); } - public function testIsNotWritable() + public function testIsNotWritable(): void { $decryptingStream = new AesGcmEncryptingStream( - Psr7\stream_for(''), + Utils::streamFor(''), self::KEY, random_bytes(openssl_cipher_iv_length('aes-256-gcm')) ); @@ -59,23 +56,26 @@ public function testIsNotWritable() $this->assertFalse($decryptingStream->isWritable()); } - public function testEmitsErrorWhenEncryptionFails() + public function testEmitsErrorWhenEncryptionFails(): void { - // Capture the error in a custom handler to avoid PHPUnit's error trap - set_error_handler(function ($_, $message) use (&$error) { - $error = $message; - }); + $initializationVector = random_bytes(openssl_cipher_iv_length('aes-256-gcm')); + $keySize = 157; + $expectedException = new EncryptionFailedException( + "Unable to encrypt data with an initialization vector" + . sprintf(' of %s using the aes-%d-gcm algorithm. Please', $initializationVector, $keySize) + . " ensure you have provided a valid key size and initialization vector." + ); // Trigger a decryption failure by attempting to decrypt gibberish - $_ = (string) new AesGcmEncryptingStream( + $act = fn(): string => @(string)new AesGcmEncryptingStream( new RandomByteStream(1024 * 1024), self::KEY, - random_bytes(openssl_cipher_iv_length('aes-256-gcm')), + $initializationVector, 'tag', 16, - 157 + $keySize ); - $this->assertRegExp("/EncryptionFailedException: Unable to encrypt/", $error); + $this->assertException($act, $expectedException); } } diff --git a/tests/Base64DecodingStreamTest.php b/tests/Base64DecodingStreamTest.php index cac908d..56a7b05 100644 --- a/tests/Base64DecodingStreamTest.php +++ b/tests/Base64DecodingStreamTest.php @@ -1,31 +1,31 @@ assertSame(base64_decode($stream), (string) $encodingStream); } - public function testShouldReportNullAsSize() + public function testShouldReportNullAsSize(): void { $encodingStream = new Base64DecodingStream( - Psr7\stream_for(base64_encode(random_bytes(1027))) + Utils::streamFor(base64_encode(random_bytes(1027))) ); $this->assertNull($encodingStream->getSize()); } - public function testMemoryUsageRemainsConstant() + public function testMemoryUsageRemainsConstant(): void { $memory = memory_get_usage(); diff --git a/tests/Base64EncodingStreamTest.php b/tests/Base64EncodingStreamTest.php index 4b79207..b30f44b 100644 --- a/tests/Base64EncodingStreamTest.php +++ b/tests/Base64EncodingStreamTest.php @@ -1,41 +1,39 @@ assertSame(base64_encode($bytes), (string) $encodingStream); } - public function testShouldReportSizeOfEncodedStream() + public function testShouldReportSizeOfEncodedStream(): void { $bytes = random_bytes(self::MB + 3); - $encodingStream = new Base64EncodingStream(Psr7\stream_for($bytes)); + $encodingStream = new Base64EncodingStream(Utils::streamFor($bytes)); $this->assertSame(strlen(base64_encode($bytes)), $encodingStream->getSize()); } - public function testShouldReportNullIfSizeOfSourceStreamUnknown() + public function testShouldReportNullIfSizeOfSourceStreamUnknown(): void { - $stream = new PumpStream(function ($length) { - return random_bytes($length); - }); + $stream = new PumpStream(fn($length): string => random_bytes($length)); $encodingStream = new Base64EncodingStream($stream); $this->assertNull($encodingStream->getSize()); } - public function testMemoryUsageRemainsConstant() + public function testMemoryUsageRemainsConstant(): void { $memory = memory_get_usage(); diff --git a/tests/CbcTest.php b/tests/CbcTest.php index ad0a9c3..7ee9f0c 100644 --- a/tests/CbcTest.php +++ b/tests/CbcTest.php @@ -1,17 +1,19 @@ assertSame('aes-256-cbc', (new Cbc($ivString))->getOpenSslName()); } - public function testShouldReturnInitialIvStringForCurrentIvBeforeUpdate() + public function testShouldReturnInitialIvStringForCurrentIvBeforeUpdate(): void { $ivString = random_bytes(openssl_cipher_iv_length('aes-256-cbc')); $iv = new Cbc($ivString); @@ -19,7 +21,7 @@ public function testShouldReturnInitialIvStringForCurrentIvBeforeUpdate() $this->assertSame($ivString, $iv->getCurrentIv()); } - public function testUpdateShouldSetCurrentIvToEndOfCipherBlock() + public function testUpdateShouldSetCurrentIvToEndOfCipherBlock(): void { $ivLength = openssl_cipher_iv_length('aes-256-cbc'); $ivString = random_bytes($ivLength); @@ -34,15 +36,13 @@ public function testUpdateShouldSetCurrentIvToEndOfCipherBlock() ); } - /** - * @expectedException \InvalidArgumentException - */ - public function testShouldThrowWhenIvOfInvalidLengthProvided() + public function testShouldThrowWhenIvOfInvalidLengthProvided(): void { + $this->expectException(InvalidArgumentException::class); new Cbc(random_bytes(openssl_cipher_iv_length('aes-256-cbc') + 1)); } - public function testShouldSupportSeekingToBeginning() + public function testShouldSupportSeekingToBeginning(): void { $ivString = random_bytes(openssl_cipher_iv_length('aes-256-cbc')); $iv = new Cbc($ivString); @@ -53,11 +53,9 @@ public function testShouldSupportSeekingToBeginning() $this->assertSame($ivString, $iv->getCurrentIv()); } - /** - * @expectedException \LogicException - */ - public function testShouldThrowWhenNonZeroOffsetProvidedToSeek() + public function testShouldThrowWhenNonZeroOffsetProvidedToSeek(): void { + $this->expectException(LogicException::class); $ivString = random_bytes(openssl_cipher_iv_length('aes-256-cbc')); $iv = new Cbc($ivString); $cipherTextBlock = random_bytes(1024); @@ -66,11 +64,9 @@ public function testShouldThrowWhenNonZeroOffsetProvidedToSeek() $iv->seek(1); } - /** - * @expectedException \LogicException - */ - public function testShouldThrowWhenSeekCurProvidedToSeek() + public function testShouldThrowWhenSeekCurProvidedToSeek(): void { + $this->expectException(LogicException::class); $ivString = random_bytes(openssl_cipher_iv_length('aes-256-cbc')); $iv = new Cbc($ivString); $cipherTextBlock = random_bytes(1024); @@ -79,11 +75,9 @@ public function testShouldThrowWhenSeekCurProvidedToSeek() $iv->seek(0, SEEK_CUR); } - /** - * @expectedException \LogicException - */ - public function testShouldThrowWhenSeekEndProvidedToSeek() + public function testShouldThrowWhenSeekEndProvidedToSeek(): void { + $this->expectException(LogicException::class); $ivString = random_bytes(openssl_cipher_iv_length('aes-256-cbc')); $iv = new Cbc($ivString); $cipherTextBlock = random_bytes(1024); diff --git a/tests/CtrTest.php b/tests/CtrTest.php index a4e0104..85ba812 100644 --- a/tests/CtrTest.php +++ b/tests/CtrTest.php @@ -1,17 +1,19 @@ assertSame('aes-256-ctr', (new Ctr($ivString))->getOpenSslName()); } - public function testShouldReturnInitialIvStringForCurrentIvBeforeUpdate() + public function testShouldReturnInitialIvStringForCurrentIvBeforeUpdate(): void { $ivString = random_bytes(openssl_cipher_iv_length('aes-256-ctr')); $iv = new Ctr($ivString); @@ -19,9 +21,9 @@ public function testShouldReturnInitialIvStringForCurrentIvBeforeUpdate() $this->assertSame($ivString, $iv->getCurrentIv()); } - public function testUpdateShouldSetIncrementIvByNumberOfBlocksProcessed() + public function testUpdateShouldSetIncrementIvByNumberOfBlocksProcessed(): void { - $ivString = $iv = hex2bin('deadbeefdeadbeefdeadbeefdeadbeee'); + $ivString = hex2bin('deadbeefdeadbeefdeadbeefdeadbeee'); $iv = new Ctr($ivString); $cipherTextBlock = random_bytes(Ctr::BLOCK_SIZE); @@ -33,15 +35,13 @@ public function testUpdateShouldSetIncrementIvByNumberOfBlocksProcessed() ); } - /** - * @expectedException \InvalidArgumentException - */ - public function testShouldThrowWhenIvOfInvalidLengthProvided() + public function testShouldThrowWhenIvOfInvalidLengthProvided(): void { + $this->expectException(InvalidArgumentException::class); new Ctr(random_bytes(openssl_cipher_iv_length('aes-256-ctr') + 1)); } - public function testShouldSupportSeekingToBeginning() + public function testShouldSupportSeekingToBeginning(): void { $ivString = random_bytes(openssl_cipher_iv_length('aes-256-ctr')); $iv = new Ctr($ivString); @@ -52,7 +52,7 @@ public function testShouldSupportSeekingToBeginning() $this->assertSame($ivString, $iv->getCurrentIv()); } - public function testShouldSupportSeekingFromCurrentPosition() + public function testShouldSupportSeekingFromCurrentPosition(): void { $ivString = random_bytes(openssl_cipher_iv_length('aes-256-ctr')); $iv = new Ctr($ivString); @@ -64,11 +64,9 @@ public function testShouldSupportSeekingFromCurrentPosition() $this->assertNotSame($updatedIv, $iv->getCurrentIv()); } - /** - * @expectedException \LogicException - */ - public function testShouldThrowWhenSeekOffsetNotDivisibleByBlockSize() + public function testShouldThrowWhenSeekOffsetNotDivisibleByBlockSize(): void { + $this->expectException(LogicException::class); $ivString = random_bytes(openssl_cipher_iv_length('aes-256-ctr')); $iv = new Ctr($ivString); $cipherTextBlock = random_bytes(1024); @@ -77,11 +75,9 @@ public function testShouldThrowWhenSeekOffsetNotDivisibleByBlockSize() $iv->seek(1); } - /** - * @expectedException \LogicException - */ - public function testShouldThrowWhenNegativeSeekCurProvidedToSeek() + public function testShouldThrowWhenNegativeSeekCurProvidedToSeek(): void { + $this->expectException(LogicException::class); $ivString = random_bytes(openssl_cipher_iv_length('aes-256-ctr')); $iv = new Ctr($ivString); $cipherTextBlock = random_bytes(1024); @@ -90,11 +86,9 @@ public function testShouldThrowWhenNegativeSeekCurProvidedToSeek() $iv->seek(Ctr::BLOCK_SIZE * -1, SEEK_CUR); } - /** - * @expectedException \LogicException - */ - public function testShouldThrowWhenSeekEndProvidedToSeek() + public function testShouldThrowWhenSeekEndProvidedToSeek(): void { + $this->expectException(LogicException::class); $ivString = random_bytes(openssl_cipher_iv_length('aes-256-ctr')); $iv = new Ctr($ivString); $cipherTextBlock = random_bytes(1024); diff --git a/tests/EcbTest.php b/tests/EcbTest.php index 5d6b35f..89bc6ce 100644 --- a/tests/EcbTest.php +++ b/tests/EcbTest.php @@ -5,12 +5,12 @@ class EcbTest extends TestCase { - public function testShouldReportCipherMethodOfECB() + public function testShouldReportCipherMethodOfECB(): void { $this->assertSame('aes-256-ecb', (new Ecb)->getOpenSslName()); } - public function testShouldReturnEmptyStringForCurrentIv() + public function testShouldReturnEmptyStringForCurrentIv(): void { $iv = new Ecb(); $this->assertEmpty($iv->getCurrentIv()); @@ -18,7 +18,7 @@ public function testShouldReturnEmptyStringForCurrentIv() $this->assertEmpty($iv->getCurrentIv()); } - public function testSeekShouldBeNoOp() + public function testSeekShouldBeNoOp(): void { $iv = new Ecb(); $baseIv = $iv->getCurrentIv(); @@ -26,7 +26,7 @@ public function testSeekShouldBeNoOp() $this->assertSame($baseIv, $iv->getCurrentIv()); } - public function testShouldReportThatPaddingIsRequired() + public function testShouldReportThatPaddingIsRequired(): void { $this->assertTrue((new Ecb)->requiresPadding()); } diff --git a/tests/ExceptionAssertions.php b/tests/ExceptionAssertions.php new file mode 100644 index 0000000..7cbac86 --- /dev/null +++ b/tests/ExceptionAssertions.php @@ -0,0 +1,16 @@ +expectExceptionObject($exception); + $act(); + } +} \ No newline at end of file diff --git a/tests/HashingStreamTest.php b/tests/HashingStreamTest.php index ff0c3da..3116614 100644 --- a/tests/HashingStreamTest.php +++ b/tests/HashingStreamTest.php @@ -1,24 +1,23 @@ assertSame(hash($algorithm, $toHash, true), $hash); }, $algorithm @@ -32,20 +31,16 @@ function ($hash) use ($toHash, $algorithm) { ); } - /** - * @dataProvider hmacAlgorithmProvider - * - * @param string $algorithm - */ + #[DataProvider('hmacAlgorithmProvider')] public function testAuthenticatedHashShouldMatchThatReturnedByHashMethod( - $algorithm - ) { + string $algorithm + ): void { $key = 'secret key'; $toHash = random_bytes(1025); $instance = new HashingStream( - Psr7\stream_for($toHash), + Utils::streamFor($toHash), $key, - function ($hash) use ($toHash, $key, $algorithm) { + function ($hash) use ($toHash, $key, $algorithm): void { $this->assertSame( hash_hmac($algorithm, $toHash, $key, true), $hash @@ -62,20 +57,16 @@ function ($hash) use ($toHash, $key, $algorithm) { ); } - /** - * @dataProvider hmacAlgorithmProvider - * - * @param string $algorithm - */ - public function testHashingStreamsCanBeRewound($algorithm) + #[DataProvider('hmacAlgorithmProvider')] + public function testHashingStreamsCanBeRewound(string $algorithm): void { $key = 'secret key'; $toHash = random_bytes(1025); $callCount = 0; $instance = new HashingStream( - Psr7\stream_for($toHash), + Utils::streamFor($toHash), $key, - function ($hash) use ($toHash, $key, $algorithm, &$callCount) { + function ($hash) use ($toHash, $key, $algorithm, &$callCount): void { ++$callCount; $this->assertSame( hash_hmac($algorithm, $toHash, $key, true), @@ -92,34 +83,36 @@ function ($hash) use ($toHash, $key, $algorithm, &$callCount) { $this->assertSame(2, $callCount); } - public function hmacAlgorithmProvider() + /** + * @return non-falsy-string[][] + */ + public static function hmacAlgorithmProvider(): array { $cryptoHashes = []; foreach (hash_algos() as $algo) { - // As of PHP 7.2, feeding a non-cryptographic hashing - // algorithm to `hash_init` will trigger an error, and - // feeding one to `hash_hmac` will cause the function to - // return `false`. - // cf https://www.php.net/manual/en/migration72.incompatible.php#migration72.incompatible.hash-functions - if (@hash_hmac($algo, 'data', 'secret key')) { - $cryptoHashes []= [$algo]; + // As of PHP 8.0, feeding a non-cryptographic hashing + // algorithm to `hash_init` will throw ValueError exception. + // cf https://www.php.net/manual/en/function.hash-hmac.php + try { + if (@hash_hmac($algo, 'data', 'secret key') !== '0') { + $cryptoHashes [] = [$algo]; + } + } catch (ValueError) { } } return $cryptoHashes; } - public function hashAlgorithmProvider() + public static function hashAlgorithmProvider(): array { - return array_map(function ($algo) { return [$algo]; }, hash_algos()); + return array_map(fn($algo): array => [$algo], hash_algos()); } - /** - * @expectedException \LogicException - */ - public function testDoesNotSupportArbitrarySeeking() + public function testDoesNotSupportArbitrarySeeking(): void { - $instance = new HashingStream(Psr7\stream_for(random_bytes(1025))); + $this->expectException(LogicException::class); + $instance = new HashingStream(Utils::streamFor(random_bytes(1025))); $instance->seek(1); } } diff --git a/tests/HexDecodingStreamTest.php b/tests/HexDecodingStreamTest.php index 2f96efa..ba10f6b 100644 --- a/tests/HexDecodingStreamTest.php +++ b/tests/HexDecodingStreamTest.php @@ -1,41 +1,39 @@ assertSame(hex2bin($stream), (string) $encodingStream); } - public function testShouldReportSizeOfDecodedStream() + public function testShouldReportSizeOfDecodedStream(): void { - $stream = Psr7\stream_for(bin2hex(random_bytes(1027))); + $stream = Utils::streamFor(bin2hex(random_bytes(1027))); $encodingStream = new HexDecodingStream($stream); $this->assertSame(strlen(hex2bin($stream)), $encodingStream->getSize()); } - public function testShouldReportNullIfSizeOfSourceStreamUnknown() + public function testShouldReportNullIfSizeOfSourceStreamUnknown(): void { - $stream = new PumpStream(function () { - return bin2hex(random_bytes(self::MB)); - }); + $stream = new PumpStream(fn(): string => bin2hex(random_bytes(self::MB))); $encodingStream = new HexDecodingStream($stream); $this->assertNull($encodingStream->getSize()); } - public function testMemoryUsageRemainsConstant() + public function testMemoryUsageRemainsConstant(): void { $memory = memory_get_usage(); diff --git a/tests/HexEncodingStreamTest.php b/tests/HexEncodingStreamTest.php index 6dbf2ba..0199e12 100644 --- a/tests/HexEncodingStreamTest.php +++ b/tests/HexEncodingStreamTest.php @@ -1,41 +1,39 @@ assertSame(bin2hex($bytes), (string) $encodingStream); } - public function testShouldReportSizeOfEncodedStream() + public function testShouldReportSizeOfEncodedStream(): void { $bytes = random_bytes(self::MB + 3); - $encodingStream = new HexEncodingStream(Psr7\stream_for($bytes)); + $encodingStream = new HexEncodingStream(Utils::streamFor($bytes)); $this->assertSame(strlen(bin2hex($bytes)), $encodingStream->getSize()); } - public function testShouldReportNullIfSizeOfSourceStreamUnknown() + public function testShouldReportNullIfSizeOfSourceStreamUnknown(): void { - $stream = new PumpStream(function ($length) { - return random_bytes($length); - }); + $stream = new PumpStream(fn($length): string => random_bytes($length)); $encodingStream = new HexEncodingStream($stream); $this->assertNull($encodingStream->getSize()); } - public function testMemoryUsageRemainsConstant() + public function testMemoryUsageRemainsConstant(): void { $memory = memory_get_usage(); diff --git a/tests/RandomByteStream.php b/tests/RandomByteStream.php index c762677..d86bdda 100644 --- a/tests/RandomByteStream.php +++ b/tests/RandomByteStream.php @@ -1,4 +1,5 @@ maxLength = $maxLength; - $this->stream = new PumpStream(function ($length) use (&$maxLength) { - $length = min($length, $maxLength); - $maxLength -= $length; + $this->stream = new PumpStream(function ($length) { + $length = min($length, $this->maxLength); + $this->maxLength -= $length; return $length > 0 ? random_bytes($length) : false; }); } - public function getSize() + public function getSize(): ?int { return $this->maxLength; }