Skip to content

Commit c8eea5a

Browse files
committed
rewrite cryptography
1 parent 8e66530 commit c8eea5a

32 files changed

Lines changed: 827 additions & 889 deletions

phpstan-baseline.neon

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
parameters:
22
ignoreErrors:
33
-
4-
message: '#^Parameter \#1 \$key of class Patchlevel\\Hydrator\\Cryptography\\Cipher\\CipherKey constructor expects non\-empty\-string, string given\.$#'
5-
identifier: argument.type
4+
message: '#^Method Patchlevel\\Hydrator\\Cryptography\\Cipher\\OpensslCipher\:\:encrypt\(\) should return non\-empty\-string but returns string\.$#'
5+
identifier: return.type
66
count: 1
7-
path: src/Cryptography/Cipher/OpensslCipherKeyFactory.php
7+
path: src/Cryptography/Cipher/OpensslCipher.php
88

99
-
10-
message: '#^Parameter \#3 \$iv of class Patchlevel\\Hydrator\\Cryptography\\Cipher\\CipherKey constructor expects non\-empty\-string, string given\.$#'
10+
message: '#^Parameter \#1 \$key of class Patchlevel\\Hydrator\\Cryptography\\Cipher\\CipherKey constructor expects non\-empty\-string, string given\.$#'
1111
identifier: argument.type
1212
count: 1
1313
path: src/Cryptography/Cipher/OpensslCipherKeyFactory.php
1414

1515
-
16-
message: '#^Parameter \#2 \$data of method Patchlevel\\Hydrator\\Cryptography\\Cipher\\Cipher\:\:decrypt\(\) expects string, mixed given\.$#'
16+
message: '#^Parameter \#3 \$iv of class Patchlevel\\Hydrator\\Cryptography\\Cipher\\CipherKey constructor expects non\-empty\-string, string given\.$#'
1717
identifier: argument.type
1818
count: 1
19-
path: src/Cryptography/SensitiveDataPayloadCryptographer.php
19+
path: src/Cryptography/Cipher/OpensslCipherKeyFactory.php
2020

2121
-
2222
message: '#^Method Patchlevel\\Hydrator\\Guesser\\BuiltInGuesser\:\:guess\(\) has parameter \$type with generic class Symfony\\Component\\TypeInfo\\Type\\ObjectType but does not specify its types\: T$#'
@@ -120,12 +120,6 @@ parameters:
120120
count: 2
121121
path: tests/Unit/Normalizer/ArrayNormalizerTest.php
122122

123-
-
124-
message: '#^Parameter \#1 \$normalizer of class Patchlevel\\Hydrator\\Normalizer\\ArrayNormalizer constructor expects Patchlevel\\Hydrator\\Normalizer\\Normalizer, PHPUnit\\Framework\\MockObject\\MockObject given\.$#'
125-
identifier: argument.type
126-
count: 1
127-
path: tests/Unit/Normalizer/ArrayNormalizerTest.php
128-
129123
-
130124
message: '#^Cannot cast mixed to int\.$#'
131125
identifier: cast.int
@@ -137,9 +131,3 @@ parameters:
137131
identifier: cast.string
138132
count: 2
139133
path: tests/Unit/Normalizer/ArrayShapeNormalizerTest.php
140-
141-
-
142-
message: '#^Parameter \#1 \$normalizerMap of class Patchlevel\\Hydrator\\Normalizer\\ArrayShapeNormalizer constructor expects array\<Patchlevel\\Hydrator\\Normalizer\\Normalizer\>, array\<string, PHPUnit\\Framework\\MockObject\\MockObject\> given\.$#'
143-
identifier: argument.type
144-
count: 1
145-
path: tests/Unit/Normalizer/ArrayShapeNormalizerTest.php

phpstan.neon.dist

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,7 @@ services:
1515
class: Patchlevel\Hydrator\Tests\Architecture\FinalClassesTest
1616
tags:
1717
- phpat.test
18+
-
19+
class: Patchlevel\Hydrator\Tests\Architecture\ExceptionImplementsHydratorExceptionTest
20+
tags:
21+
- phpat.test

src/Attribute/SensitiveData.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
#[Attribute(Attribute::TARGET_PROPERTY)]
1111
final class SensitiveData
1212
{
13-
/** @var (callable(string, mixed):mixed)|null */
13+
/** @var (callable(string):mixed)|null */
1414
public readonly mixed $fallbackCallable;
1515

1616
public function __construct(
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Patchlevel\Hydrator\Cryptography;
6+
7+
use Patchlevel\Hydrator\Cryptography\Cipher\Cipher;
8+
use Patchlevel\Hydrator\Cryptography\Cipher\CipherKey;
9+
use Patchlevel\Hydrator\Cryptography\Cipher\CipherKeyFactory;
10+
use Patchlevel\Hydrator\Cryptography\Cipher\DecryptionFailed;
11+
use Patchlevel\Hydrator\Cryptography\Cipher\EncryptionFailed;
12+
use Patchlevel\Hydrator\Cryptography\Cipher\OpensslCipher;
13+
use Patchlevel\Hydrator\Cryptography\Cipher\OpensslCipherKeyFactory;
14+
use Patchlevel\Hydrator\Cryptography\Store\CipherKeyNotExists;
15+
use Patchlevel\Hydrator\Cryptography\Store\CipherKeyStore;
16+
17+
use function array_key_exists;
18+
use function is_array;
19+
20+
/**
21+
* @phpstan-type EncryptedDataV1 array{
22+
* __enc: 'v1',
23+
* data: non-empty-string,
24+
* method?: non-empty-string,
25+
* iv?: non-empty-string,
26+
* }
27+
*/
28+
final class BaseCryptographer implements Cryptographer
29+
{
30+
public function __construct(
31+
private readonly Cipher $cipher,
32+
private readonly CipherKeyStore $cipherKeyStore,
33+
private readonly CipherKeyFactory $cipherKeyFactory,
34+
) {
35+
}
36+
37+
/**
38+
* @return EncryptedDataV1
39+
*
40+
* @throws EncryptionFailed
41+
*/
42+
public function encrypt(string $subjectId, mixed $value): array
43+
{
44+
try {
45+
$cipherKey = $this->cipherKeyStore->get($subjectId);
46+
} catch (CipherKeyNotExists) {
47+
$cipherKey = ($this->cipherKeyFactory)();
48+
$this->cipherKeyStore->store($subjectId, $cipherKey);
49+
}
50+
51+
return [
52+
'__enc' => 'v1',
53+
'data' => $this->cipher->encrypt($cipherKey, $value),
54+
'method' => $cipherKey->method,
55+
'iv' => $cipherKey->iv,
56+
];
57+
}
58+
59+
/**
60+
* @param EncryptedDataV1 $encryptedData
61+
*
62+
* @throws CipherKeyNotExists
63+
* @throws DecryptionFailed
64+
*/
65+
public function decrypt(string $subjectId, mixed $encryptedData): mixed
66+
{
67+
$cipherKey = $this->cipherKeyStore->get($subjectId);
68+
69+
return $this->cipher->decrypt(
70+
new CipherKey(
71+
$cipherKey->key,
72+
$encryptedData['method'] ?? $cipherKey->method,
73+
$encryptedData['iv'] ?? $cipherKey->iv,
74+
),
75+
$encryptedData['data'],
76+
);
77+
}
78+
79+
public function supports(mixed $value): bool
80+
{
81+
return is_array($value) && array_key_exists('__enc', $value) && $value['__enc'] === 'v1';
82+
}
83+
84+
/** @param non-empty-string $method */
85+
public static function createWithOpenssl(
86+
CipherKeyStore $cryptoStore,
87+
string $method = OpensslCipherKeyFactory::DEFAULT_METHOD,
88+
): static {
89+
return new self(
90+
new OpensslCipher(),
91+
$cryptoStore,
92+
new OpensslCipherKeyFactory($method),
93+
);
94+
}
95+
}

src/Cryptography/Cipher/Cipher.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66

77
interface Cipher
88
{
9-
/** @throws EncryptionFailed */
9+
/**
10+
* @return non-empty-string
11+
*
12+
* @throws EncryptionFailed
13+
*/
1014
public function encrypt(CipherKey $key, mixed $data): string;
1115

1216
/** @throws DecryptionFailed */

src/Cryptography/Cipher/CreateCipherKeyFailed.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44

55
namespace Patchlevel\Hydrator\Cryptography\Cipher;
66

7+
use Patchlevel\Hydrator\HydratorException;
78
use RuntimeException;
89

9-
final class CreateCipherKeyFailed extends RuntimeException
10+
final class CreateCipherKeyFailed extends RuntimeException implements HydratorException
1011
{
1112
public function __construct()
1213
{

src/Cryptography/Cipher/DecryptionFailed.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44

55
namespace Patchlevel\Hydrator\Cryptography\Cipher;
66

7+
use Patchlevel\Hydrator\HydratorException;
78
use RuntimeException;
89

9-
final class DecryptionFailed extends RuntimeException
10+
final class DecryptionFailed extends RuntimeException implements HydratorException
1011
{
1112
public function __construct()
1213
{

src/Cryptography/Cipher/EncryptionFailed.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44

55
namespace Patchlevel\Hydrator\Cryptography\Cipher;
66

7+
use Patchlevel\Hydrator\HydratorException;
78
use RuntimeException;
89

9-
final class EncryptionFailed extends RuntimeException
10+
final class EncryptionFailed extends RuntimeException implements HydratorException
1011
{
1112
public function __construct()
1213
{

src/Cryptography/Cipher/MethodNotSupported.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44

55
namespace Patchlevel\Hydrator\Cryptography\Cipher;
66

7+
use Patchlevel\Hydrator\HydratorException;
78
use RuntimeException;
89

910
use function sprintf;
1011

11-
final class MethodNotSupported extends RuntimeException
12+
final class MethodNotSupported extends RuntimeException implements HydratorException
1213
{
1314
public function __construct(string $method)
1415
{

src/Cryptography/Cipher/OpensslCipher.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
final class OpensslCipher implements Cipher
1919
{
20+
/** @return non-empty-string */
2021
public function encrypt(CipherKey $key, mixed $data): string
2122
{
2223
$encryptedData = @openssl_encrypt(

0 commit comments

Comments
 (0)