Skip to content

Commit b56032b

Browse files
committed
Implement OpenSslEncryptParameterOutTypeExtension
1 parent 65be2b2 commit b56032b

File tree

3 files changed

+157
-0
lines changed

3 files changed

+157
-0
lines changed

conf/config.neon

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1569,6 +1569,11 @@ services:
15691569
tags:
15701570
- phpstan.dynamicFunctionThrowTypeExtension
15711571

1572+
-
1573+
class: PHPStan\Type\Php\OpenSslEncryptParameterOutTypeExtension
1574+
tags:
1575+
- phpstan.functionParameterOutTypeExtension
1576+
15721577
-
15731578
class: PHPStan\Type\Php\ParseStrParameterOutTypeExtension
15741579
tags:
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Php;
4+
5+
use PhpParser\Node\Expr\FuncCall;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Reflection\FunctionReflection;
8+
use PHPStan\Reflection\ParameterReflection;
9+
use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
10+
use PHPStan\Type\FunctionParameterOutTypeExtension;
11+
use PHPStan\Type\NullType;
12+
use PHPStan\Type\StringType;
13+
use PHPStan\Type\Type;
14+
use PHPStan\Type\TypeCombinator;
15+
use PHPStan\Type\TypeTraverser;
16+
use PHPStan\Type\UnionType;
17+
use function in_array;
18+
use function openssl_get_cipher_methods;
19+
use function strtolower;
20+
use function substr;
21+
22+
final class OpenSslEncryptParameterOutTypeExtension implements FunctionParameterOutTypeExtension
23+
{
24+
25+
public function isFunctionSupported(FunctionReflection $functionReflection, ParameterReflection $parameter): bool
26+
{
27+
return $functionReflection->getName() === 'openssl_encrypt' && $parameter->getName() === 'tag';
28+
}
29+
30+
public function getParameterOutTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $funcCall, ParameterReflection $parameter, Scope $scope): ?Type
31+
{
32+
$args = $funcCall->getArgs();
33+
$cipherArg = $args[1] ?? null;
34+
35+
if ($cipherArg === null) {
36+
return null;
37+
}
38+
39+
$cipherType = $scope->getType($cipherArg->value);
40+
41+
return TypeTraverser::map($cipherType, static function (Type $type, callable $traverse): Type {
42+
if ($type instanceof UnionType) {
43+
return $traverse($type);
44+
}
45+
46+
$tagTypes = [];
47+
48+
foreach ($type->getConstantStrings() as $cipherType) {
49+
$cipher = strtolower($cipherType->getValue());
50+
$mode = substr($cipher, -3);
51+
52+
if (!in_array($cipher, openssl_get_cipher_methods(), true)) {
53+
$tagTypes[] = new NullType();
54+
continue;
55+
}
56+
57+
if (in_array($mode, ['gcm', 'ccm'], true)) {
58+
$tagTypes[] = TypeCombinator::intersect(
59+
new StringType(),
60+
new AccessoryNonEmptyStringType(),
61+
);
62+
continue;
63+
}
64+
65+
$tagTypes[] = new NullType();
66+
}
67+
68+
if ($tagTypes === []) {
69+
return TypeCombinator::addNull(TypeCombinator::intersect(
70+
new StringType(),
71+
new AccessoryNonEmptyStringType(),
72+
));
73+
}
74+
75+
return TypeCombinator::union(...$tagTypes);
76+
});
77+
}
78+
79+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace OpenSslEncrypt;
6+
7+
use function PHPStan\Testing\assertType;
8+
9+
class Foo
10+
{
11+
public function testStringCipher(string $cipher): void
12+
{
13+
openssl_encrypt('data', $cipher, random_bytes(32), OPENSSL_RAW_DATA, random_bytes(16), $tag);
14+
assertType('non-empty-string|null', $tag);
15+
}
16+
17+
public function testUnknownCipher(): void
18+
{
19+
openssl_encrypt('data', 'aes-256-cde', random_bytes(32), OPENSSL_RAW_DATA, random_bytes(16), $tag);
20+
assertType('null', $tag);
21+
22+
openssl_encrypt('data', 'abc-256-gcm', random_bytes(32), OPENSSL_RAW_DATA, random_bytes(16), $tag);
23+
assertType('null', $tag);
24+
25+
openssl_encrypt('data', 'abc-256-ccm', random_bytes(32), OPENSSL_RAW_DATA, random_bytes(16), $tag);
26+
assertType('null', $tag);
27+
}
28+
29+
public function testAeadCipher(): void
30+
{
31+
$cipher = 'aes-256-gcm';
32+
openssl_encrypt('data', $cipher, random_bytes(32), OPENSSL_RAW_DATA, random_bytes(16), $tag);
33+
assertType('non-empty-string', $tag);
34+
35+
$cipher = 'aes-256-ccm';
36+
openssl_encrypt('data', $cipher, random_bytes(32), OPENSSL_RAW_DATA, random_bytes(16), $tag);
37+
assertType('non-empty-string', $tag);
38+
}
39+
40+
public function testNonAeadCipher(): void
41+
{
42+
$cipher = 'aes-256-cbc';
43+
openssl_encrypt('data', $cipher, random_bytes(32), OPENSSL_RAW_DATA, random_bytes(16), $tag);
44+
assertType('null', $tag);
45+
}
46+
47+
/**
48+
* @param 'aes-256-ctr'|'aes-256-gcm' $cipher
49+
*/
50+
public function testMixedAeadAndNonAeadCiphers(string $cipher): void
51+
{
52+
openssl_encrypt('data', $cipher, random_bytes(32), OPENSSL_RAW_DATA, random_bytes(16), $tag);
53+
assertType('non-empty-string|null', $tag);
54+
}
55+
56+
/**
57+
* @param 'aes-256-cbc'|'aes-256-ctr' $cipher
58+
*/
59+
public function testMixedTwoNonAeadCiphers(string $cipher): void
60+
{
61+
openssl_encrypt('data', $cipher, random_bytes(32), OPENSSL_RAW_DATA, random_bytes(16), $tag);
62+
assertType('non-empty-string|null', $tag);
63+
}
64+
65+
/**
66+
* @param 'aes-256-gcm'|'aes-256-ccm' $cipher
67+
*/
68+
public function testMixedTwoAeadCiphers(string $cipher): void
69+
{
70+
openssl_encrypt('data', $cipher, random_bytes(32), OPENSSL_RAW_DATA, random_bytes(16), $tag);
71+
assertType('non-empty-string|null', $tag);
72+
}
73+
}

0 commit comments

Comments
 (0)