-
-
Notifications
You must be signed in to change notification settings - Fork 121
Expand file tree
/
Copy pathPBES2AESKW.php
More file actions
159 lines (138 loc) · 5.09 KB
/
Copy pathPBES2AESKW.php
File metadata and controls
159 lines (138 loc) · 5.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
<?php
declare(strict_types=1);
namespace Jose\Component\Encryption\Algorithm\KeyEncryption;
use AESKW\A128KW;
use AESKW\A192KW;
use AESKW\A256KW;
use AESKW\Wrapper as WrapperInterface;
use InvalidArgumentException;
use Jose\Component\Core\JWK;
use Jose\Component\Core\Util\Base64UrlSafe;
use Override;
use RuntimeException;
use function in_array;
use function is_int;
use function is_string;
use function sprintf;
abstract readonly class PBES2AESKW implements KeyWrapping
{
public const DEFAULT_MAX_COUNT = 1_000_000;
public function __construct(
private readonly int $salt_size = 64,
private readonly int $nb_count = 4096,
private readonly int $max_count = self::DEFAULT_MAX_COUNT
) {
if (! interface_exists(WrapperInterface::class)) {
throw new RuntimeException('Please install "spomky-labs/aes-key-wrap" to use AES-KW algorithms');
}
}
#[Override]
public function allowedKeyTypes(): array
{
return ['oct'];
}
/**
* @param array<string, mixed> $completeHeader
* @param array<string, mixed> $additionalHeader
*/
#[Override]
public function wrapKey(JWK $key, string $cek, array $completeHeader, array &$additionalHeader): string
{
$password = $this->getKey($key);
$this->checkHeaderAlgorithm($completeHeader);
$wrapper = $this->getWrapper();
$hash_algorithm = $this->getHashAlgorithm();
$key_size = $this->getKeySize();
$salt = random_bytes($this->salt_size);
// We set header parameters
$additionalHeader['p2s'] = Base64UrlSafe::encodeUnpadded($salt);
$additionalHeader['p2c'] = $this->nb_count;
$derived_key = hash_pbkdf2(
$hash_algorithm,
$password,
$completeHeader['alg'] . "\x00" . $salt,
$this->nb_count,
$key_size,
true
);
return $wrapper::wrap($derived_key, $cek);
}
/**
* @param array<string, mixed> $completeHeader
*/
#[Override]
public function unwrapKey(JWK $key, string $encrypted_cek, array $completeHeader): string
{
$password = $this->getKey($key);
$this->checkHeaderAlgorithm($completeHeader);
$this->checkHeaderAdditionalParameters($completeHeader);
$wrapper = $this->getWrapper();
$hash_algorithm = $this->getHashAlgorithm();
$key_size = $this->getKeySize();
$p2s = $completeHeader['p2s'];
is_string($p2s) || throw new InvalidArgumentException('Invalid salt.');
$salt = $completeHeader['alg'] . "\x00" . Base64UrlSafe::decodeNoPadding($p2s);
$count = $completeHeader['p2c'];
is_int($count) || throw new InvalidArgumentException('Invalid counter.');
$derived_key = hash_pbkdf2($hash_algorithm, $password, $salt, $count, $key_size, true);
return $wrapper::unwrap($derived_key, $encrypted_cek);
}
#[Override]
public function getKeyManagementMode(): string
{
return self::MODE_WRAP;
}
protected function getKey(JWK $key): string
{
if (! in_array($key->get('kty'), $this->allowedKeyTypes(), true)) {
throw new InvalidArgumentException('Wrong key type.');
}
if (! $key->has('k')) {
throw new InvalidArgumentException('The key parameter "k" is missing.');
}
$k = $key->get('k');
if (! is_string($k)) {
throw new InvalidArgumentException('The key parameter "k" is invalid.');
}
return Base64UrlSafe::decodeNoPadding($k);
}
/**
* @param array<string, mixed> $header
*/
protected function checkHeaderAlgorithm(array $header): void
{
if (! isset($header['alg'])) {
throw new InvalidArgumentException('The header parameter "alg" is missing.');
}
if (! is_string($header['alg'])) {
throw new InvalidArgumentException('The header parameter "alg" is not valid.');
}
}
/**
* @param array<string, mixed> $header
*/
protected function checkHeaderAdditionalParameters(array $header): void
{
if (! isset($header['p2s'])) {
throw new InvalidArgumentException('The header parameter "p2s" is missing.');
}
if (! is_string($header['p2s'])) {
throw new InvalidArgumentException('The header parameter "p2s" is not valid.');
}
if (! isset($header['p2c'])) {
throw new InvalidArgumentException('The header parameter "p2c" is missing.');
}
if (! is_int($header['p2c']) || $header['p2c'] <= 0) {
throw new InvalidArgumentException('The header parameter "p2c" is not valid.');
}
if ($header['p2c'] > $this->max_count) {
throw new InvalidArgumentException(sprintf(
'The header parameter "p2c" is too large. The maximum allowed value is %d.',
$this->max_count
));
}
}
abstract protected function getWrapper(): A256KW|A128KW|A192KW;
abstract protected function getHashAlgorithm(): string;
abstract protected function getKeySize(): int;
}