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; }