Skip to content

Commit

Permalink
Implement OpenSslEncryptParameterOutTypeExtension
Browse files Browse the repository at this point in the history
  • Loading branch information
paulbalandan committed Jan 7, 2025
1 parent 65be2b2 commit 430dfeb
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 0 deletions.
5 changes: 5 additions & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -1569,6 +1569,11 @@ services:
tags:
- phpstan.dynamicFunctionThrowTypeExtension

-
class: PHPStan\Type\Php\OpenSslEncryptParameterOutTypeExtension
tags:
- phpstan.functionParameterOutTypeExtension

-
class: PHPStan\Type\Php\ParseStrParameterOutTypeExtension
tags:
Expand Down
79 changes: 79 additions & 0 deletions src/Type/Php/OpenSslEncryptParameterOutTypeExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Php;

use PhpParser\Node\Expr\FuncCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\ParameterReflection;
use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
use PHPStan\Type\FunctionParameterOutTypeExtension;
use PHPStan\Type\NullType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\TypeTraverser;
use PHPStan\Type\UnionType;
use function in_array;
use function openssl_get_cipher_methods;
use function strtolower;
use function substr;

final class OpenSslEncryptParameterOutTypeExtension implements FunctionParameterOutTypeExtension
{

public function isFunctionSupported(FunctionReflection $functionReflection, ParameterReflection $parameter): bool
{
return $functionReflection->getName() === 'openssl_encrypt' && $parameter->getName() === 'tag';
}

public function getParameterOutTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $funcCall, ParameterReflection $parameter, Scope $scope): ?Type
{
$args = $funcCall->getArgs();
$cipherArg = $args[1] ?? null;

if ($cipherArg === null) {
return null;
}

$cipherType = $scope->getType($cipherArg->value);

return TypeTraverser::map($cipherType, static function (Type $type, callable $traverse): Type {
if ($type instanceof UnionType) {
return $traverse($type);
}

$tagTypes = [];

foreach ($type->getConstantStrings() as $cipherType) {
$cipher = strtolower($cipherType->getValue());
$mode = substr($cipher, -3);

if (!in_array($cipher, openssl_get_cipher_methods(), true)) {
$tagTypes[] = new NullType();
continue;
}

if (in_array($mode, ['gcm', 'ccm'], true)) {
$tagTypes[] = TypeCombinator::intersect(
new StringType(),
new AccessoryNonEmptyStringType(),
);
continue;
}

$tagTypes[] = new NullType();
}

if ($tagTypes === []) {
return TypeCombinator::addNull(TypeCombinator::intersect(
new StringType(),
new AccessoryNonEmptyStringType(),
));
}

return TypeCombinator::union(...$tagTypes);
});
}

}
73 changes: 73 additions & 0 deletions tests/PHPStan/Analyser/nsrt/openssl-encrypt.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

declare(strict_types = 1);

namespace OpenSslEncrypt;

use function PHPStan\Testing\assertType;

class Foo
{
public function testStringCipher(string $cipher): void
{
openssl_encrypt('data', $cipher, random_bytes(32), OPENSSL_RAW_DATA, random_bytes(16), $tag);
assertType('non-empty-string|null', $tag);
}

public function testUnknownCipher(): void
{
openssl_encrypt('data', 'aes-256-cde', random_bytes(32), OPENSSL_RAW_DATA, random_bytes(16), $tag);
assertType('null', $tag);

openssl_encrypt('data', 'abc-256-gcm', random_bytes(32), OPENSSL_RAW_DATA, random_bytes(16), $tag);
assertType('null', $tag);

openssl_encrypt('data', 'abc-256-ccm', random_bytes(32), OPENSSL_RAW_DATA, random_bytes(16), $tag);
assertType('null', $tag);
}

public function testAeadCipher(): void
{
$cipher = 'aes-256-gcm';
openssl_encrypt('data', $cipher, random_bytes(32), OPENSSL_RAW_DATA, random_bytes(16), $tag);
assertType('non-empty-string', $tag);

$cipher = 'aes-256-ccm';
openssl_encrypt('data', $cipher, random_bytes(32), OPENSSL_RAW_DATA, random_bytes(16), $tag);
assertType('non-empty-string', $tag);
}

public function testNonAeadCipher(): void
{
$cipher = 'aes-256-cbc';
openssl_encrypt('data', $cipher, random_bytes(32), OPENSSL_RAW_DATA, random_bytes(16), $tag);
assertType('null', $tag);
}

/**
* @param 'aes-256-ctr'|'aes-256-gcm' $cipher
*/
public function testMixedAeadAndNonAeadCiphers(string $cipher): void
{
openssl_encrypt('data', $cipher, random_bytes(32), OPENSSL_RAW_DATA, random_bytes(16), $tag);
assertType('non-empty-string|null', $tag);
}

/**
* @param 'aes-256-cbc'|'aes-256-ctr' $cipher
*/
public function testMixedTwoNonAeadCiphers(string $cipher): void
{
openssl_encrypt('data', $cipher, random_bytes(32), OPENSSL_RAW_DATA, random_bytes(16), $tag);
assertType('null', $tag);
}

/**
* @param 'aes-256-gcm'|'aes-256-ccm' $cipher
*/
public function testMixedTwoAeadCiphers(string $cipher): void
{
openssl_encrypt('data', $cipher, random_bytes(32), OPENSSL_RAW_DATA, random_bytes(16), $tag);
assertType('non-empty-string', $tag);
}
}

0 comments on commit 430dfeb

Please sign in to comment.