1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* |
4
|
|
|
* The MIT License (MIT) |
5
|
|
|
* |
6
|
|
|
* Copyright (c) 2014-2016 Spomky-Labs |
7
|
|
|
* |
8
|
|
|
* This software may be modified and distributed under the terms |
9
|
|
|
* of the MIT license. See the LICENSE file for details. |
10
|
|
|
*/ |
11
|
|
|
|
12
|
|
|
namespace Jose; |
13
|
|
|
|
14
|
|
|
use Assert\Assertion; |
15
|
|
|
use Base64Url\Base64Url; |
16
|
|
|
use Jose\Algorithm\ContentEncryptionAlgorithmInterface; |
17
|
|
|
use Jose\Algorithm\KeyEncryption\KeyAgreementWrappingInterface; |
18
|
|
|
use Jose\Algorithm\KeyEncryption\KeyEncryptionInterface; |
19
|
|
|
use Jose\Algorithm\KeyEncryption\KeyWrappingInterface; |
20
|
|
|
use Jose\Algorithm\KeyEncryptionAlgorithmInterface; |
21
|
|
|
use Jose\Behaviour\CommonCipheringMethods; |
22
|
|
|
use Jose\Behaviour\HasCompressionManager; |
23
|
|
|
use Jose\Behaviour\HasJWAManager; |
24
|
|
|
use Jose\Behaviour\HasKeyChecker; |
25
|
|
|
use Jose\Behaviour\HasLogger; |
26
|
|
|
use Jose\Compression\CompressionInterface; |
27
|
|
|
use Jose\Factory\AlgorithmManagerFactory; |
28
|
|
|
use Jose\Factory\CompressionManagerFactory; |
29
|
|
|
use Jose\Object\JWEInterface; |
30
|
|
|
use Jose\Object\JWKInterface; |
31
|
|
|
use Jose\Object\Recipient; |
32
|
|
|
use Jose\Object\RecipientInterface; |
33
|
|
|
use Psr\Log\LoggerInterface; |
34
|
|
|
use Psr\Log\LogLevel; |
35
|
|
|
|
36
|
|
|
final class Encrypter implements EncrypterInterface |
37
|
|
|
{ |
38
|
|
|
use HasKeyChecker; |
39
|
|
|
use HasJWAManager; |
40
|
|
|
use HasCompressionManager; |
41
|
|
|
use HasLogger; |
42
|
|
|
use CommonCipheringMethods; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* {@inheritdoc} |
46
|
|
|
*/ |
47
|
|
|
public static function createEncrypter(array $key_encryption_algorithms, array $content_encryption_algorithms, array $compression_methods = ['DEF', 'ZLIB', 'GZ'], LoggerInterface $logger = null) |
48
|
|
|
{ |
49
|
|
|
return new self($key_encryption_algorithms, $content_encryption_algorithms, $compression_methods, $logger); |
|
|
|
|
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* Decrypter constructor. |
54
|
|
|
* |
55
|
|
|
* @param string[]|\Jose\Algorithm\JWAInterface[] $key_encryption_algorithms |
56
|
|
|
* @param string[]|\Jose\Algorithm\JWAInterface[] $content_encryption_algorithms |
57
|
|
|
* @param string[]|\Jose\Compression\CompressionInterface[] $compression_methods |
58
|
|
|
* @param \Psr\Log\LoggerInterface|null $logger |
59
|
|
|
*/ |
60
|
|
|
public function __construct( |
61
|
|
|
array $key_encryption_algorithms, |
62
|
|
|
array $content_encryption_algorithms, |
63
|
|
|
array $compression_methods, |
64
|
|
|
LoggerInterface $logger = null |
65
|
|
|
) { |
66
|
|
|
$this->setKeyEncryptionAlgorithms($key_encryption_algorithms); |
|
|
|
|
67
|
|
|
$this->setContentEncryptionAlgorithms($content_encryption_algorithms); |
|
|
|
|
68
|
|
|
$this->setCompressionMethods($compression_methods); |
69
|
|
|
$this->setJWAManager(AlgorithmManagerFactory::createAlgorithmManager(array_merge( |
70
|
|
|
$key_encryption_algorithms, |
71
|
|
|
$content_encryption_algorithms |
72
|
|
|
))); |
73
|
|
|
$this->setCompressionManager(CompressionManagerFactory::createCompressionManager($compression_methods)); |
74
|
|
|
|
75
|
|
|
if (null !== $logger) { |
76
|
|
|
$this->setLogger($logger); |
77
|
|
|
} |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* {@inheritdoc} |
82
|
|
|
*/ |
83
|
|
|
public function encrypt(JWEInterface &$jwe) |
84
|
|
|
{ |
85
|
|
|
$this->log(LogLevel::INFO, 'Trying to encrypt the JWE object', ['jwe' => $jwe]); |
86
|
|
|
Assertion::false($jwe->isEncrypted(), 'The JWE is already encrypted.'); |
87
|
|
|
Assertion::greaterThan($jwe->countRecipients(), 0, 'The JWE does not contain recipient.'); |
88
|
|
|
|
89
|
|
|
// Content Encryption Algorithm |
90
|
|
|
$this->log(LogLevel::DEBUG, 'Trying to find the content encryption algorithm'); |
91
|
|
|
$content_encryption_algorithm = $this->getContentEncryptionAlgorithm($jwe); |
92
|
|
|
$this->log(LogLevel::DEBUG, 'The content encryption algorithm has been found', ['content_encryption_algorithm', $content_encryption_algorithm]); |
93
|
|
|
|
94
|
|
|
// Compression Method |
95
|
|
|
$this->log(LogLevel::DEBUG, 'Trying to find the compression method (if needed)'); |
96
|
|
|
$compression_method = $this->getCompressionMethod($jwe); |
97
|
|
|
$this->log(LogLevel::DEBUG, 'The compression method search is finished', ['compression_method', $compression_method]); |
98
|
|
|
|
99
|
|
|
// Key Management Mode |
100
|
|
|
$this->log(LogLevel::DEBUG, 'Trying to find the key management mode'); |
101
|
|
|
$key_management_mode = $this->getKeyManagementMode($jwe); |
102
|
|
|
$this->log(LogLevel::DEBUG, 'The key management mode has been found', ['key_management_mode', $key_management_mode]); |
103
|
|
|
|
104
|
|
|
// Additional Headers |
105
|
|
|
$additional_headers = []; |
106
|
|
|
|
107
|
|
|
// CEK |
108
|
|
|
$this->log(LogLevel::DEBUG, 'Trying to determine the content encryption key (CEK)'); |
109
|
|
|
$cek = $this->determineCEK( |
110
|
|
|
$jwe, |
111
|
|
|
$content_encryption_algorithm, |
112
|
|
|
$key_management_mode, |
113
|
|
|
$additional_headers |
114
|
|
|
); |
115
|
|
|
$this->log(LogLevel::DEBUG, 'The content encryption key has been determined', ['cek', $cek]); |
116
|
|
|
|
117
|
|
|
$nb_recipients = $jwe->countRecipients(); |
118
|
|
|
|
119
|
|
|
$this->log(LogLevel::DEBUG, 'Trying to encrypt the content encryption key (CEK) for all recipients'); |
120
|
|
|
for ($i = 0; $i < $nb_recipients; $i++) { |
121
|
|
|
$this->log(LogLevel::DEBUG, 'Processing with recipient #{index}', ['index', $i]); |
122
|
|
|
$this->processRecipient( |
123
|
|
|
$jwe, |
124
|
|
|
$jwe->getRecipient($i), |
125
|
|
|
$cek, |
126
|
|
|
$content_encryption_algorithm, |
127
|
|
|
$additional_headers |
128
|
|
|
); |
129
|
|
|
$this->log(LogLevel::DEBUG, 'Processing done'); |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
if (!empty($additional_headers) && 1 === $jwe->countRecipients()) { |
133
|
|
|
$this->log(LogLevel::DEBUG, 'Additional headers will be added to the shared protected headers', ['additional_headers' => $additional_headers]); |
134
|
|
|
$jwe = $jwe->withSharedProtectedHeaders(array_merge( |
135
|
|
|
$jwe->getSharedProtectedHeaders(), |
136
|
|
|
$additional_headers |
137
|
|
|
)); |
138
|
|
|
$this->log(LogLevel::DEBUG, 'Additional headers added'); |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
// IV |
142
|
|
|
$this->log(LogLevel::DEBUG, 'Creating Initialization Vector (IV)'); |
143
|
|
|
$iv_size = $content_encryption_algorithm->getIVSize(); |
144
|
|
|
$iv = $this->createIV($iv_size); |
145
|
|
|
$this->log(LogLevel::DEBUG, 'Initialization Vector (IV) creation done', ['iv' => $iv]); |
146
|
|
|
|
147
|
|
|
$this->log(LogLevel::DEBUG, 'Trying to encrypt the JWE object '); |
148
|
|
|
$this->encryptJWE($jwe, $content_encryption_algorithm, $cek, $iv, $compression_method); |
149
|
|
|
$this->log(LogLevel::DEBUG, 'JWE object encryption done.'); |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* @param \Jose\Object\JWEInterface $jwe |
154
|
|
|
* @param \Jose\Object\RecipientInterface $recipient |
155
|
|
|
* @param string $cek |
156
|
|
|
* @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm |
157
|
|
|
* @param array $additional_headers |
158
|
|
|
*/ |
159
|
|
|
private function processRecipient(JWEInterface $jwe, |
160
|
|
|
RecipientInterface &$recipient, |
161
|
|
|
$cek, |
162
|
|
|
ContentEncryptionAlgorithmInterface $content_encryption_algorithm, |
163
|
|
|
array &$additional_headers |
164
|
|
|
) { |
165
|
|
|
if (null === $recipient->getRecipientKey()) { |
166
|
|
|
$this->log(LogLevel::WARNING, 'The recipient key is not set. Aborting.'); |
167
|
|
|
return; |
168
|
|
|
} |
169
|
|
|
$complete_headers = array_merge( |
170
|
|
|
$jwe->getSharedProtectedHeaders(), |
171
|
|
|
$jwe->getSharedHeaders(), |
172
|
|
|
$recipient->getHeaders() |
173
|
|
|
); |
174
|
|
|
|
175
|
|
|
$this->log(LogLevel::DEBUG, 'Trying to find the key encryption algorithm'); |
176
|
|
|
$key_encryption_algorithm = $this->findKeyEncryptionAlgorithm($complete_headers); |
177
|
|
|
$this->log(LogLevel::DEBUG, 'The key encryption algorithm has been found', ['key_encryption_algorithm' => $key_encryption_algorithm]); |
178
|
|
|
|
179
|
|
|
// We check keys (usage and algorithm if restrictions are set) |
180
|
|
|
$this->log(LogLevel::DEBUG, 'Checking recipient key usage'); |
181
|
|
|
$this->checkKeys( |
182
|
|
|
$key_encryption_algorithm, |
183
|
|
|
$content_encryption_algorithm, |
184
|
|
|
$recipient->getRecipientKey() |
185
|
|
|
); |
186
|
|
|
$this->log(LogLevel::DEBUG, 'Recipient key usage checks done'); |
187
|
|
|
|
188
|
|
|
$this->log(LogLevel::DEBUG, 'Trying to compute the content encryption key'); |
189
|
|
|
$encrypted_content_encryption_key = $this->getEncryptedKey( |
190
|
|
|
$complete_headers, |
191
|
|
|
$cek, |
192
|
|
|
$key_encryption_algorithm, |
193
|
|
|
$content_encryption_algorithm, |
194
|
|
|
$additional_headers, |
195
|
|
|
$recipient->getRecipientKey() |
196
|
|
|
); |
197
|
|
|
$this->log(LogLevel::DEBUG, 'Content encryption key computation done', ['encrypted_content_encryption_key' => $encrypted_content_encryption_key]); |
198
|
|
|
|
199
|
|
|
$recipient_headers = $recipient->getHeaders(); |
200
|
|
|
if (!empty($additional_headers) && 1 !== $jwe->countRecipients()) { |
201
|
|
|
$recipient_headers = array_merge( |
202
|
|
|
$recipient_headers, |
203
|
|
|
$additional_headers |
204
|
|
|
); |
205
|
|
|
$additional_headers = []; |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
$recipient = Recipient::createRecipientFromLoadedJWE($recipient_headers, $encrypted_content_encryption_key); |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
/** |
212
|
|
|
* @param \Jose\Object\JWEInterface $jwe |
213
|
|
|
* @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm |
214
|
|
|
* @param string $key_management_mode |
215
|
|
|
* @param array $additional_headers |
216
|
|
|
* |
217
|
|
|
* @return string |
218
|
|
|
*/ |
219
|
|
|
private function determineCEK(JWEInterface $jwe, |
220
|
|
|
ContentEncryptionAlgorithmInterface $content_encryption_algorithm, |
221
|
|
|
$key_management_mode, |
222
|
|
|
array &$additional_headers |
223
|
|
|
) { |
224
|
|
|
switch ($key_management_mode) { |
225
|
|
|
case KeyEncryptionInterface::MODE_ENCRYPT: |
226
|
|
|
case KeyEncryptionInterface::MODE_WRAP: |
227
|
|
|
return $this->createCEK($content_encryption_algorithm->getCEKSize()); |
228
|
|
|
case KeyEncryptionInterface::MODE_AGREEMENT: |
229
|
|
|
Assertion::eq(1, $jwe->countRecipients(), 'Unable to encrypt for multiple recipients using key agreement algorithms.'); |
230
|
|
|
|
231
|
|
|
$complete_headers = array_merge( |
232
|
|
|
$jwe->getSharedProtectedHeaders(), |
233
|
|
|
$jwe->getSharedHeaders(), |
234
|
|
|
$jwe->getRecipient(0)->getHeaders() |
235
|
|
|
); |
236
|
|
|
$algorithm = $this->findKeyEncryptionAlgorithm($complete_headers); |
237
|
|
|
|
238
|
|
|
return $algorithm->getAgreementKey($content_encryption_algorithm->getCEKSize(), $content_encryption_algorithm->getAlgorithmName(), $jwe->getRecipient(0)->getRecipientKey(), $complete_headers, $additional_headers); |
239
|
|
|
case KeyEncryptionInterface::MODE_DIRECT: |
240
|
|
|
Assertion::eq(1, $jwe->countRecipients(), 'Unable to encrypt for multiple recipients using key agreement algorithms.'); |
241
|
|
|
|
242
|
|
|
Assertion::eq($jwe->getRecipient(0)->getRecipientKey()->get('kty'), 'oct', 'Wrong key type.'); |
243
|
|
|
Assertion::true($jwe->getRecipient(0)->getRecipientKey()->has('k'), 'The key parameter "k" is missing.'); |
244
|
|
|
|
245
|
|
|
return Base64Url::decode($jwe->getRecipient(0)->getRecipientKey()->get('k')); |
246
|
|
|
default: |
247
|
|
|
throw new \InvalidArgumentException(sprintf('Unsupported key management mode "%s".', $key_management_mode)); |
248
|
|
|
} |
249
|
|
|
} |
250
|
|
|
|
251
|
|
|
/** |
252
|
|
|
* @param \Jose\Object\JWEInterface $jwe |
253
|
|
|
* |
254
|
|
|
* @return string |
255
|
|
|
*/ |
256
|
|
|
private function getKeyManagementMode(JWEInterface $jwe) |
257
|
|
|
{ |
258
|
|
|
$mode = null; |
259
|
|
|
$recipients = $jwe->getRecipients(); |
260
|
|
|
|
261
|
|
|
foreach ($recipients as $recipient) { |
262
|
|
|
$complete_headers = array_merge( |
263
|
|
|
$jwe->getSharedProtectedHeaders(), |
264
|
|
|
$jwe->getSharedHeaders(), |
265
|
|
|
$recipient->getHeaders() |
266
|
|
|
); |
267
|
|
|
Assertion::keyExists($complete_headers, 'alg', 'Parameter "alg" is missing.'); |
268
|
|
|
|
269
|
|
|
$key_encryption_algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['alg']); |
270
|
|
|
Assertion::isInstanceOf($key_encryption_algorithm, KeyEncryptionAlgorithmInterface::class, sprintf('The key encryption algorithm "%s" is not supported or not a key encryption algorithm instance.', $complete_headers['alg'])); |
271
|
|
|
|
272
|
|
|
if (null === $mode) { |
273
|
|
|
$mode = $key_encryption_algorithm->getKeyManagementMode(); |
274
|
|
|
} else { |
275
|
|
|
Assertion::true( |
276
|
|
|
$this->areKeyManagementModesCompatible($mode, $key_encryption_algorithm->getKeyManagementMode()), |
277
|
|
|
'Foreign key management mode forbidden.' |
278
|
|
|
); |
279
|
|
|
} |
280
|
|
|
} |
281
|
|
|
|
282
|
|
|
return $mode; |
283
|
|
|
} |
284
|
|
|
|
285
|
|
|
/** |
286
|
|
|
* @param \Jose\Object\JWEInterface $jwe |
287
|
|
|
* |
288
|
|
|
* @return \Jose\Compression\CompressionInterface|null |
289
|
|
|
*/ |
290
|
|
|
private function getCompressionMethod(JWEInterface $jwe) |
291
|
|
|
{ |
292
|
|
|
$method = null; |
293
|
|
|
$nb_recipients = $jwe->countRecipients(); |
294
|
|
|
|
295
|
|
|
for ($i = 0; $i < $nb_recipients; $i++) { |
296
|
|
|
$complete_headers = array_merge( |
297
|
|
|
$jwe->getSharedProtectedHeaders(), |
298
|
|
|
$jwe->getSharedHeaders(), |
299
|
|
|
$jwe->getRecipient($i)->getHeaders() |
300
|
|
|
); |
301
|
|
|
if (array_key_exists('zip', $complete_headers)) { |
302
|
|
|
if (null === $method) { |
303
|
|
|
if (0 === $i) { |
304
|
|
|
$method = $complete_headers['zip']; |
305
|
|
|
} else { |
306
|
|
|
throw new \InvalidArgumentException('Inconsistent "zip" parameter.'); |
307
|
|
|
} |
308
|
|
|
} else { |
309
|
|
|
Assertion::eq($method, $complete_headers['zip'], 'Inconsistent "zip" parameter.'); |
310
|
|
|
} |
311
|
|
|
} else { |
312
|
|
|
Assertion::eq(null, $method, 'Inconsistent "zip" parameter.'); |
313
|
|
|
} |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
if (null === $method) { |
317
|
|
|
return; |
318
|
|
|
} |
319
|
|
|
|
320
|
|
|
$compression_method = $this->getCompressionManager()->getCompressionAlgorithm($method); |
321
|
|
|
Assertion::isInstanceOf($compression_method, CompressionInterface::class, sprintf('Compression method "%s" not supported.', $method)); |
322
|
|
|
|
323
|
|
|
return $compression_method; |
324
|
|
|
} |
325
|
|
|
|
326
|
|
|
/** |
327
|
|
|
* @param \Jose\Object\JWEInterface $jwe |
328
|
|
|
* |
329
|
|
|
* @return \Jose\Algorithm\ContentEncryptionAlgorithmInterface |
330
|
|
|
*/ |
331
|
|
|
private function getContentEncryptionAlgorithm(JWEInterface $jwe) |
332
|
|
|
{ |
333
|
|
|
$algorithm = null; |
334
|
|
|
|
335
|
|
|
foreach ($jwe->getRecipients() as $recipient) { |
336
|
|
|
$complete_headers = array_merge( |
337
|
|
|
$jwe->getSharedProtectedHeaders(), |
338
|
|
|
$jwe->getSharedHeaders(), |
339
|
|
|
$recipient->getHeaders() |
340
|
|
|
); |
341
|
|
|
Assertion::keyExists($complete_headers, 'enc', 'Parameter "enc" is missing.'); |
342
|
|
|
if (null === $algorithm) { |
343
|
|
|
$algorithm = $complete_headers['enc']; |
344
|
|
|
} else { |
345
|
|
|
Assertion::eq($algorithm, $complete_headers['enc'], 'Foreign content encryption algorithms are not allowed.'); |
346
|
|
|
} |
347
|
|
|
} |
348
|
|
|
|
349
|
|
|
$content_encryption_algorithm = $this->getJWAManager()->getAlgorithm($algorithm); |
350
|
|
|
Assertion::isInstanceOf($content_encryption_algorithm, ContentEncryptionAlgorithmInterface::class, sprintf('The content encryption algorithm "%s" is not supported or not a content encryption algorithm instance.', $algorithm)); |
351
|
|
|
|
352
|
|
|
return $content_encryption_algorithm; |
353
|
|
|
} |
354
|
|
|
|
355
|
|
|
/** |
356
|
|
|
* @param \Jose\Object\JWEInterface $jwe |
357
|
|
|
* @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm |
358
|
|
|
* @param string $cek |
359
|
|
|
* @param string $iv |
360
|
|
|
* @param \Jose\Compression\CompressionInterface|null $compression_method |
361
|
|
|
*/ |
362
|
|
|
private function encryptJWE(JWEInterface &$jwe, |
363
|
|
|
ContentEncryptionAlgorithmInterface $content_encryption_algorithm, |
364
|
|
|
$cek, |
365
|
|
|
$iv, |
366
|
|
|
CompressionInterface $compression_method = null |
367
|
|
|
) { |
368
|
|
|
if (!empty($jwe->getSharedProtectedHeaders())) { |
369
|
|
|
$jwe = $jwe->withEncodedSharedProtectedHeaders(Base64Url::encode(json_encode($jwe->getSharedProtectedHeaders()))); |
370
|
|
|
} |
371
|
|
|
|
372
|
|
|
// We encrypt the payload and get the tag |
373
|
|
|
$tag = null; |
374
|
|
|
$payload = $this->preparePayload($jwe->getPayload(), $compression_method); |
375
|
|
|
|
376
|
|
|
$ciphertext = $content_encryption_algorithm->encryptContent( |
377
|
|
|
$payload, |
378
|
|
|
$cek, |
379
|
|
|
$iv, |
380
|
|
|
null === $jwe->getAAD() ? null : Base64Url::encode($jwe->getAAD()), |
381
|
|
|
$jwe->getEncodedSharedProtectedHeaders(), |
382
|
|
|
$tag |
383
|
|
|
); |
384
|
|
|
|
385
|
|
|
$jwe = $jwe->withCiphertext($ciphertext); |
386
|
|
|
$jwe = $jwe->withIV($iv); |
387
|
|
|
|
388
|
|
|
// Tag |
389
|
|
|
if (null !== $tag) { |
390
|
|
|
$jwe = $jwe->withTag($tag); |
391
|
|
|
} |
392
|
|
|
} |
393
|
|
|
|
394
|
|
|
/** |
395
|
|
|
* @param \Jose\Algorithm\KeyEncryptionAlgorithmInterface $key_encryption_algorithm |
396
|
|
|
* @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm |
397
|
|
|
* @param \Jose\Object\JWKInterface $recipient_key |
398
|
|
|
*/ |
399
|
|
|
private function checkKeys(KeyEncryptionAlgorithmInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, JWKInterface $recipient_key) |
400
|
|
|
{ |
401
|
|
|
$this->checkKeyUsage($recipient_key, 'encryption'); |
402
|
|
|
if ('dir' !== $key_encryption_algorithm->getAlgorithmName()) { |
403
|
|
|
$this->checkKeyAlgorithm($recipient_key, $key_encryption_algorithm->getAlgorithmName()); |
404
|
|
|
} else { |
405
|
|
|
$this->checkKeyAlgorithm($recipient_key, $content_encryption_algorithm->getAlgorithmName()); |
406
|
|
|
} |
407
|
|
|
} |
408
|
|
|
|
409
|
|
|
/** |
410
|
|
|
* @param string $current |
411
|
|
|
* @param string $new |
412
|
|
|
* |
413
|
|
|
* @return bool |
414
|
|
|
*/ |
415
|
|
|
private function areKeyManagementModesCompatible($current, $new) |
416
|
|
|
{ |
417
|
|
|
$agree = KeyEncryptionAlgorithmInterface::MODE_AGREEMENT; |
418
|
|
|
$dir = KeyEncryptionAlgorithmInterface::MODE_DIRECT; |
419
|
|
|
$enc = KeyEncryptionAlgorithmInterface::MODE_ENCRYPT; |
420
|
|
|
$wrap = KeyEncryptionAlgorithmInterface::MODE_WRAP; |
421
|
|
|
|
422
|
|
|
$supported_key_management_mode_combinations = [ |
423
|
|
|
$enc.$enc => true, |
424
|
|
|
$enc.$wrap => true, |
425
|
|
|
$wrap.$enc => true, |
426
|
|
|
$wrap.$wrap => true, |
427
|
|
|
$agree.$agree => false, |
428
|
|
|
$agree.$dir => false, |
429
|
|
|
$agree.$enc => false, |
430
|
|
|
$agree.$wrap => false, |
431
|
|
|
$dir.$agree => false, |
432
|
|
|
$dir.$dir => false, |
433
|
|
|
$dir.$enc => false, |
434
|
|
|
$dir.$wrap => false, |
435
|
|
|
$enc.$agree => false, |
436
|
|
|
$enc.$dir => false, |
437
|
|
|
$wrap.$agree => false, |
438
|
|
|
$wrap.$dir => false, |
439
|
|
|
]; |
440
|
|
|
|
441
|
|
|
if (array_key_exists($current.$new, $supported_key_management_mode_combinations)) { |
442
|
|
|
return $supported_key_management_mode_combinations[$current.$new]; |
443
|
|
|
} |
444
|
|
|
|
445
|
|
|
return false; |
446
|
|
|
} |
447
|
|
|
|
448
|
|
|
/** |
449
|
|
|
* @param string $payload |
450
|
|
|
* @param \Jose\Compression\CompressionInterface|null $compression_method |
451
|
|
|
* |
452
|
|
|
* @return string |
453
|
|
|
*/ |
454
|
|
|
private function preparePayload($payload, CompressionInterface $compression_method = null) |
455
|
|
|
{ |
456
|
|
|
$prepared = is_string($payload) ? $payload : json_encode($payload); |
457
|
|
|
|
458
|
|
|
Assertion::notNull($prepared, 'The payload is empty or cannot encoded into JSON.'); |
459
|
|
|
|
460
|
|
|
if (null === $compression_method) { |
461
|
|
|
return $prepared; |
462
|
|
|
} |
463
|
|
|
$compressed_payload = $compression_method->compress($prepared); |
464
|
|
|
Assertion::string($compressed_payload, 'Compression failed.'); |
465
|
|
|
|
466
|
|
|
return $compressed_payload; |
467
|
|
|
} |
468
|
|
|
|
469
|
|
|
/** |
470
|
|
|
* @param array $complete_headers |
471
|
|
|
* @param string $cek |
472
|
|
|
* @param \Jose\Algorithm\KeyEncryptionAlgorithmInterface $key_encryption_algorithm |
473
|
|
|
* @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm |
474
|
|
|
* @param \Jose\Object\JWKInterface $recipient_key |
475
|
|
|
* @param array $additional_headers |
476
|
|
|
* |
477
|
|
|
* @return string|null |
478
|
|
|
*/ |
479
|
|
|
private function getEncryptedKey(array $complete_headers, $cek, KeyEncryptionAlgorithmInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, array &$additional_headers, JWKInterface $recipient_key) |
480
|
|
|
{ |
481
|
|
|
if ($key_encryption_algorithm instanceof KeyEncryptionInterface) { |
482
|
|
|
return $this->getEncryptedKeyFromKeyEncryptionAlgorithm($complete_headers, $cek, $key_encryption_algorithm, $recipient_key, $additional_headers); |
483
|
|
|
} elseif ($key_encryption_algorithm instanceof KeyWrappingInterface) { |
484
|
|
|
return $this->getEncryptedKeyFromKeyWrappingAlgorithm($complete_headers, $cek, $key_encryption_algorithm, $recipient_key, $additional_headers); |
485
|
|
|
} elseif ($key_encryption_algorithm instanceof KeyAgreementWrappingInterface) { |
486
|
|
|
return $this->getEncryptedKeyFromKeyAgreementAndKeyWrappingAlgorithm($complete_headers, $cek, $key_encryption_algorithm, $content_encryption_algorithm, $additional_headers, $recipient_key); |
487
|
|
|
} |
488
|
|
|
|
489
|
|
|
// Using KeyAgreementInterface or DirectEncryptionInterface, the encrypted key is an empty string |
490
|
|
|
} |
491
|
|
|
|
492
|
|
|
/** |
493
|
|
|
* @param array $complete_headers |
494
|
|
|
* @param string $cek |
495
|
|
|
* @param \Jose\Algorithm\KeyEncryption\KeyAgreementWrappingInterface $key_encryption_algorithm |
496
|
|
|
* @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm |
497
|
|
|
* @param array $additional_headers |
498
|
|
|
* @param \Jose\Object\JWKInterface $recipient_key |
499
|
|
|
* |
500
|
|
|
* @return string |
501
|
|
|
*/ |
502
|
|
|
private function getEncryptedKeyFromKeyAgreementAndKeyWrappingAlgorithm(array $complete_headers, $cek, KeyAgreementWrappingInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, array &$additional_headers, JWKInterface $recipient_key) |
503
|
|
|
{ |
504
|
|
|
$jwt_cek = $key_encryption_algorithm->wrapAgreementKey($recipient_key, $cek, $content_encryption_algorithm->getCEKSize(), $complete_headers, $additional_headers); |
505
|
|
|
|
506
|
|
|
return $jwt_cek; |
507
|
|
|
} |
508
|
|
|
|
509
|
|
|
/** |
510
|
|
|
* @param array $complete_headers |
511
|
|
|
* @param string $cek |
512
|
|
|
* @param \Jose\Algorithm\KeyEncryption\KeyEncryptionInterface $key_encryption_algorithm |
513
|
|
|
* @param \Jose\Object\JWKInterface $recipient_key |
514
|
|
|
* @param array $additional_headers |
515
|
|
|
* |
516
|
|
|
* @return string |
517
|
|
|
*/ |
518
|
|
|
private function getEncryptedKeyFromKeyEncryptionAlgorithm(array $complete_headers, $cek, KeyEncryptionInterface $key_encryption_algorithm, JWKInterface $recipient_key, array &$additional_headers) |
519
|
|
|
{ |
520
|
|
|
return $key_encryption_algorithm->encryptKey( |
521
|
|
|
$recipient_key, |
522
|
|
|
$cek, |
523
|
|
|
$complete_headers, |
524
|
|
|
$additional_headers |
525
|
|
|
); |
526
|
|
|
} |
527
|
|
|
|
528
|
|
|
/** |
529
|
|
|
* @param array $complete_headers |
530
|
|
|
* @param string $cek |
531
|
|
|
* @param \Jose\Algorithm\KeyEncryption\KeyWrappingInterface $key_encryption_algorithm |
532
|
|
|
* @param \Jose\Object\JWKInterface $recipient_key |
533
|
|
|
* @param array $additional_headers |
534
|
|
|
* |
535
|
|
|
* @return string |
536
|
|
|
*/ |
537
|
|
|
private function getEncryptedKeyFromKeyWrappingAlgorithm(array $complete_headers, $cek, KeyWrappingInterface $key_encryption_algorithm, JWKInterface $recipient_key, &$additional_headers) |
538
|
|
|
{ |
539
|
|
|
return $key_encryption_algorithm->wrapKey( |
540
|
|
|
$recipient_key, |
541
|
|
|
$cek, |
542
|
|
|
$complete_headers, |
543
|
|
|
$additional_headers |
544
|
|
|
); |
545
|
|
|
} |
546
|
|
|
|
547
|
|
|
/** |
548
|
|
|
* @param array $complete_headers |
549
|
|
|
* |
550
|
|
|
* @return \Jose\Algorithm\KeyEncryptionAlgorithmInterface |
551
|
|
|
*/ |
552
|
|
|
private function findKeyEncryptionAlgorithm(array $complete_headers) |
553
|
|
|
{ |
554
|
|
|
Assertion::keyExists($complete_headers, 'alg', 'Parameter "alg" is missing.'); |
555
|
|
|
|
556
|
|
|
$key_encryption_algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['alg']); |
557
|
|
|
Assertion::isInstanceOf($key_encryption_algorithm, KeyEncryptionAlgorithmInterface::class, sprintf('The key encryption algorithm "%s" is not supported or not a key encryption algorithm instance.', $complete_headers['alg'])); |
558
|
|
|
|
559
|
|
|
return $key_encryption_algorithm; |
560
|
|
|
} |
561
|
|
|
|
562
|
|
|
/** |
563
|
|
|
* @param int $size |
564
|
|
|
* |
565
|
|
|
* @return string |
566
|
|
|
*/ |
567
|
|
|
private function createCEK($size) |
568
|
|
|
{ |
569
|
|
|
return random_bytes($size / 8); |
570
|
|
|
} |
571
|
|
|
|
572
|
|
|
/** |
573
|
|
|
* @param int $size |
574
|
|
|
* |
575
|
|
|
* @return string |
576
|
|
|
*/ |
577
|
|
|
private function createIV($size) |
578
|
|
|
{ |
579
|
|
|
return random_bytes($size / 8); |
580
|
|
|
} |
581
|
|
|
} |
582
|
|
|
|
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.