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\JWAManagerInterface; |
18
|
|
|
use Jose\Algorithm\KeyEncryption\DirectEncryptionInterface; |
19
|
|
|
use Jose\Algorithm\KeyEncryption\KeyAgreementInterface; |
20
|
|
|
use Jose\Algorithm\KeyEncryption\KeyAgreementWrappingInterface; |
21
|
|
|
use Jose\Algorithm\KeyEncryption\KeyEncryptionInterface; |
22
|
|
|
use Jose\Algorithm\KeyEncryption\KeyWrappingInterface; |
23
|
|
|
use Jose\Algorithm\KeyEncryptionAlgorithmInterface; |
24
|
|
|
use Jose\Behaviour\HasCompressionManager; |
25
|
|
|
use Jose\Behaviour\HasJWAManager; |
26
|
|
|
use Jose\Behaviour\HasKeyChecker; |
27
|
|
|
use Jose\Behaviour\HasLogger; |
28
|
|
|
use Jose\Compression\CompressionInterface; |
29
|
|
|
use Jose\Compression\CompressionManagerInterface; |
30
|
|
|
use Jose\Object\JWEInterface; |
31
|
|
|
use Jose\Object\JWKInterface; |
32
|
|
|
use Jose\Object\Recipient; |
33
|
|
|
use Jose\Object\RecipientInterface; |
34
|
|
|
use Jose\Util\StringUtil; |
35
|
|
|
use Psr\Log\LoggerInterface; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
*/ |
39
|
|
|
final class Encrypter implements EncrypterInterface |
40
|
|
|
{ |
41
|
|
|
use HasKeyChecker; |
42
|
|
|
use HasJWAManager; |
43
|
|
|
use HasCompressionManager; |
44
|
|
|
use HasLogger; |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* Encrypter constructor. |
48
|
|
|
* |
49
|
|
|
* @param \Jose\Algorithm\JWAManagerInterface $jwa_manager |
50
|
|
|
* @param \Jose\Compression\CompressionManagerInterface $compression_manager |
51
|
|
|
* @param \Psr\Log\LoggerInterface|null $logger |
52
|
|
|
*/ |
53
|
|
|
public function __construct( |
54
|
|
|
JWAManagerInterface $jwa_manager, |
55
|
|
|
CompressionManagerInterface $compression_manager, |
56
|
|
|
LoggerInterface $logger = null |
57
|
|
|
) { |
58
|
|
|
$this->setJWAManager($jwa_manager); |
59
|
|
|
$this->setCompressionManager($compression_manager); |
60
|
|
|
|
61
|
|
|
if (null !== $logger) { |
62
|
|
|
$this->setLogger($logger); |
63
|
|
|
} |
64
|
|
|
} |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* {@inheritdoc} |
68
|
|
|
*/ |
69
|
|
|
public function encrypt(JWEInterface &$jwe) |
70
|
|
|
{ |
71
|
|
|
Assertion::greaterThan($jwe->countRecipients(), 0, 'The JWE does not contain recipient.'); |
72
|
|
|
|
73
|
|
|
// Content Encryption Algorithm |
74
|
|
|
$content_encryption_algorithm = $this->getContentEncryptionAlgorithm($jwe); |
75
|
|
|
|
76
|
|
|
// Compression Method |
77
|
|
|
$compression_method = $this->getCompressionMethod($jwe); |
78
|
|
|
|
79
|
|
|
// Key Management Mode |
80
|
|
|
$key_management_mode = $this->getKeyManagementMode($jwe); |
81
|
|
|
|
82
|
|
|
// Additional Headers |
83
|
|
|
$additional_headers = []; |
84
|
|
|
|
85
|
|
|
// CEK |
86
|
|
|
$cek = $this->determineCEK( |
87
|
|
|
$jwe, |
88
|
|
|
$content_encryption_algorithm, |
|
|
|
|
89
|
|
|
$key_management_mode, |
90
|
|
|
$additional_headers |
91
|
|
|
); |
92
|
|
|
|
93
|
|
|
for($i = 0; $i < $jwe->countRecipients(); $i++) { |
|
|
|
|
94
|
|
|
|
95
|
|
|
$this->processRecipient( |
96
|
|
|
$jwe, |
97
|
|
|
$jwe->getRecipient($i), |
98
|
|
|
$cek, |
99
|
|
|
$content_encryption_algorithm, |
|
|
|
|
100
|
|
|
$additional_headers |
101
|
|
|
); |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
if (!empty($additional_headers) && 1 === $jwe->countRecipients()) { |
105
|
|
|
$jwe = $jwe->withSharedProtectedHeaders(array_merge( |
106
|
|
|
$jwe->getSharedProtectedHeaders(), |
107
|
|
|
$additional_headers |
108
|
|
|
)); |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
// IV |
112
|
|
|
if (null !== $iv_size = $content_encryption_algorithm->getIVSize()) { |
113
|
|
|
$iv = $this->createIV($iv_size); |
114
|
|
|
$jwe = $jwe->withIV($iv); |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
$jwe = $jwe ->withContentEncryptionKey($cek); |
118
|
|
|
|
119
|
|
|
$this->encryptJWE($jwe, $content_encryption_algorithm, $compression_method); |
|
|
|
|
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
/** |
123
|
|
|
* @param \Jose\Object\JWEInterface $jwe |
124
|
|
|
* @param \Jose\Object\RecipientInterface $recipient |
125
|
|
|
* @param string $cek |
126
|
|
|
* @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm |
127
|
|
|
* @param array $additional_headers |
128
|
|
|
*/ |
129
|
|
|
private function processRecipient(JWEInterface $jwe, |
130
|
|
|
RecipientInterface &$recipient, |
131
|
|
|
$cek, |
132
|
|
|
ContentEncryptionAlgorithmInterface $content_encryption_algorithm, |
133
|
|
|
array &$additional_headers |
134
|
|
|
) { |
135
|
|
|
$complete_headers = array_merge( |
136
|
|
|
$jwe->getSharedProtectedHeaders(), |
137
|
|
|
$jwe->getSharedHeaders(), |
138
|
|
|
$recipient->getHeaders() |
139
|
|
|
); |
140
|
|
|
|
141
|
|
|
$key_encryption_algorithm = $this->findKeyEncryptionAlgorithm($complete_headers); |
142
|
|
|
|
143
|
|
|
$encrypted_content_encryption_key = $this->getEncryptedKey( |
144
|
|
|
$complete_headers, |
145
|
|
|
$cek, |
146
|
|
|
$key_encryption_algorithm, |
|
|
|
|
147
|
|
|
$content_encryption_algorithm, |
148
|
|
|
$additional_headers, |
149
|
|
|
$recipient->getRecipientKey() |
150
|
|
|
); |
151
|
|
|
|
152
|
|
|
if (!empty($additional_headers) && 1 !== $jwe->countRecipients()) { |
153
|
|
|
foreach ($additional_headers as $key => $value) { |
154
|
|
|
$recipient = $recipient->withHeader($key, $value); |
155
|
|
|
} |
156
|
|
|
} |
157
|
|
|
if (null !== $encrypted_content_encryption_key) { |
158
|
|
|
$recipient = $recipient->withEncryptedKey($encrypted_content_encryption_key); |
159
|
|
|
} |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
/** |
163
|
|
|
* @param \Jose\Object\JWEInterface $jwe |
164
|
|
|
* @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm |
165
|
|
|
* @param string $key_management_mode |
166
|
|
|
* @param array $additional_headers |
167
|
|
|
* |
168
|
|
|
* @return string |
169
|
|
|
*/ |
170
|
|
|
private function determineCEK(JWEInterface $jwe, |
|
|
|
|
171
|
|
|
ContentEncryptionAlgorithmInterface $content_encryption_algorithm, |
172
|
|
|
$key_management_mode, |
173
|
|
|
array &$additional_headers |
|
|
|
|
174
|
|
|
) { |
175
|
|
|
switch($key_management_mode) { |
176
|
|
|
case KeyEncryptionInterface::MODE_ENCRYPT: |
177
|
|
|
case KeyEncryptionInterface::MODE_WRAP: |
178
|
|
|
return $this->createCEK($content_encryption_algorithm->getCEKSize()); |
179
|
|
|
/*case KeyEncryptionInterface::MODE_AGREEMENT: |
180
|
|
|
Assertion::eq(1, $jwe->countRecipients(), 'Unable to encrypt for multiple recipients using key agreement algorithms.'); |
181
|
|
|
|
182
|
|
|
return ; |
183
|
|
|
case KeyEncryptionInterface::MODE_DIRECT: |
184
|
|
|
Assertion::eq(1, $jwe->countRecipients(), 'Unable to encrypt for multiple recipients using key agreement algorithms.'); |
185
|
|
|
|
186
|
|
|
Assertion::eq($key->get('kty'), 'oct', 'Wrong key type.'); |
187
|
|
|
Assertion::true($key->has('k'), 'The key parameter "k" is missing.'); |
188
|
|
|
|
189
|
|
|
return Base64Url::decode($key->get('k'));*/ |
190
|
|
|
default: |
191
|
|
|
throw new \InvalidArgumentException(sprintf('Unsupported key management mode "%s".', $key_management_mode)); |
192
|
|
|
} |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
/** |
196
|
|
|
* @param \Jose\Object\JWEInterface $jwe |
197
|
|
|
* |
198
|
|
|
* @return string |
199
|
|
|
*/ |
200
|
|
|
private function getKeyManagementMode(JWEInterface $jwe) |
201
|
|
|
{ |
202
|
|
|
$mode = null; |
203
|
|
|
|
204
|
|
|
foreach($jwe->getRecipients() as $recipient) { |
205
|
|
|
$complete_headers = array_merge( |
206
|
|
|
$jwe->getSharedProtectedHeaders(), |
207
|
|
|
$jwe->getSharedHeaders(), |
208
|
|
|
$recipient->getHeaders() |
209
|
|
|
); |
210
|
|
|
Assertion::keyExists($complete_headers, 'alg', 'Parameter "alg" is missing.'); |
211
|
|
|
|
212
|
|
|
$key_encryption_algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['alg']); |
213
|
|
|
Assertion::isInstanceOf($key_encryption_algorithm, KeyEncryptionAlgorithmInterface::class, sprintf('The algorithm "%s" is not enabled or does not implement KeyEncryptionAlgorithmInterface.', $complete_headers['alg'])); |
214
|
|
|
|
215
|
|
|
if (null === $mode) { |
216
|
|
|
$mode = $key_encryption_algorithm->getKeyManagementMode(); |
217
|
|
|
} else { |
218
|
|
|
Assertion::true( |
219
|
|
|
$this->areKeyManagementModesCompatible($mode, $key_encryption_algorithm->getKeyManagementMode()), |
220
|
|
|
'incompatible key management modes are not allowed.' |
221
|
|
|
); |
222
|
|
|
} |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
return $mode; |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
/** |
229
|
|
|
* @param \Jose\Object\JWEInterface $jwe |
230
|
|
|
* |
231
|
|
|
* @return \Jose\Algorithm\ContentEncryptionAlgorithmInterface |
232
|
|
|
*/ |
233
|
|
|
private function getCompressionMethod(JWEInterface $jwe) |
234
|
|
|
{ |
235
|
|
|
$method = null; |
236
|
|
|
|
237
|
|
|
for($i = 0; $i < $jwe->countRecipients(); $i++) { |
|
|
|
|
238
|
|
|
$complete_headers = array_merge( |
239
|
|
|
$jwe->getSharedProtectedHeaders(), |
240
|
|
|
$jwe->getSharedHeaders(), |
241
|
|
|
$jwe->getRecipient($i)->getHeaders() |
242
|
|
|
); |
243
|
|
|
Assertion::keyExists($complete_headers, 'zip', 'Parameter "enc" is missing.'); |
244
|
|
|
if (null === $method) { |
245
|
|
|
if (0 === $i) { |
246
|
|
|
$method = $complete_headers['zip']; |
247
|
|
|
} else { |
248
|
|
|
throw new \InvalidArgumentException('Inconsistent "zip" parameter.'); |
249
|
|
|
} |
250
|
|
|
} else { |
251
|
|
|
Assertion::eq($method, $complete_headers['enc'], 'Foreign compression methods are not allowed.'); |
252
|
|
|
} |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
$compression_method = $this->getCompressionManager()->getCompressionAlgorithm($method); |
256
|
|
|
Assertion::isInstanceOf($compression_method, CompressionInterface::class, sprintf('The compression method "%s" is not enabled or does not implement ContentEncryptionInterface.', $method)); |
257
|
|
|
|
258
|
|
|
return $compression_method; |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
/** |
262
|
|
|
* @param \Jose\Object\JWEInterface $jwe |
263
|
|
|
* |
264
|
|
|
* @return \Jose\Algorithm\ContentEncryptionAlgorithmInterface |
265
|
|
|
*/ |
266
|
|
|
private function getContentEncryptionAlgorithm(JWEInterface $jwe) |
267
|
|
|
{ |
268
|
|
|
$algorithm = null; |
269
|
|
|
|
270
|
|
|
foreach($jwe->getRecipients() as $recipient) { |
271
|
|
|
$complete_headers = array_merge( |
272
|
|
|
$jwe->getSharedProtectedHeaders(), |
273
|
|
|
$jwe->getSharedHeaders(), |
274
|
|
|
$recipient->getHeaders() |
275
|
|
|
); |
276
|
|
|
Assertion::keyExists($complete_headers, 'enc', 'Parameter "enc" is missing.'); |
277
|
|
|
if (null === $algorithm) { |
278
|
|
|
$algorithm = $complete_headers['enc']; |
279
|
|
|
} else { |
280
|
|
|
Assertion::eq($algorithm, $complete_headers['enc'], 'Foreign content encryption algorithms are not allowed.'); |
281
|
|
|
} |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
$content_encryption_algorithm = $this->getJWAManager()->getAlgorithm($algorithm); |
285
|
|
|
Assertion::isInstanceOf($content_encryption_algorithm, ContentEncryptionAlgorithmInterface::class, sprintf('The algorithm "%s" is not enabled or does not implement ContentEncryptionAlgorithmInterface.', $algorithm)); |
286
|
|
|
|
287
|
|
|
return $content_encryption_algorithm; |
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
/** |
291
|
|
|
* {@inheritdoc} |
292
|
|
|
*/ |
293
|
|
|
public function addRecipient(JWEInterface &$jwe, JWKInterface $recipient_key, array $recipient_headers = []) |
294
|
|
|
{ |
295
|
|
|
$complete_headers = array_merge( |
296
|
|
|
$jwe->getSharedProtectedHeaders(), |
297
|
|
|
$jwe->getSharedHeaders(), |
298
|
|
|
$recipient_headers |
299
|
|
|
); |
300
|
|
|
|
301
|
|
|
// Key Encryption Algorithm |
302
|
|
|
$key_encryption_algorithm = $this->findKeyEncryptionAlgorithm($complete_headers); |
303
|
|
|
|
304
|
|
|
// Content Encryption Algorithm |
305
|
|
|
$content_encryption_algorithm = $this->findContentEncryptionAlgorithm($complete_headers); |
306
|
|
|
|
307
|
|
|
// Compression Method (if any) |
308
|
|
|
$compression_method = $this->findCompressionMethod($complete_headers); |
309
|
|
|
|
310
|
|
|
// We check keys (usage and algorithm if restrictions are set) |
311
|
|
|
$this->checkKeys($key_encryption_algorithm, $content_encryption_algorithm, $recipient_key); |
|
|
|
|
312
|
|
|
|
313
|
|
|
$additional_headers = []; |
314
|
|
|
if (null !== $jwe->getCiphertext()) { |
315
|
|
|
Assertion::greaterThan($jwe->countRecipients(), 0, 'Invalid JWE. The payload is encrypted but no recipient is available.'); |
316
|
|
|
Assertion::notNull($jwe->getContentEncryptionKey(), 'Unable to add a recipient. The JWE must be decrypted first.'); |
317
|
|
|
|
318
|
|
|
$current_key_management_mode = $this->getCurrentKeyManagementMode($jwe); |
319
|
|
|
|
320
|
|
|
Assertion::true( |
321
|
|
|
$this->areKeyManagementModesCompatible($current_key_management_mode, $key_encryption_algorithm->getKeyManagementMode()), |
322
|
|
|
'Foreign key management mode forbidden.' |
323
|
|
|
); |
324
|
|
|
} else { |
325
|
|
|
// CEK |
326
|
|
|
$content_encryption_key = $this->getCEK( |
327
|
|
|
$complete_headers, |
328
|
|
|
$key_encryption_algorithm, |
|
|
|
|
329
|
|
|
$content_encryption_algorithm, |
|
|
|
|
330
|
|
|
$additional_headers, |
331
|
|
|
$recipient_key |
332
|
|
|
); |
333
|
|
|
$jwe = $jwe->withContentEncryptionKey($content_encryption_key); |
334
|
|
|
|
335
|
|
|
// IV |
336
|
|
|
if (null !== $iv_size = $content_encryption_algorithm->getIVSize()) { |
337
|
|
|
$iv = $this->createIV($iv_size); |
338
|
|
|
$jwe = $jwe->withIV($iv); |
339
|
|
|
} |
340
|
|
|
} |
341
|
|
|
|
342
|
|
|
$recipient = $this->computeRecipient( |
343
|
|
|
$jwe, |
344
|
|
|
$key_encryption_algorithm, |
|
|
|
|
345
|
|
|
$content_encryption_algorithm, |
|
|
|
|
346
|
|
|
$complete_headers, |
347
|
|
|
$additional_headers, |
348
|
|
|
$recipient_headers, |
349
|
|
|
$recipient_key |
350
|
|
|
); |
351
|
|
|
|
352
|
|
|
$jwe = $jwe->addRecipient($recipient); |
353
|
|
|
|
354
|
|
|
if (null === $jwe->getCiphertext()) { |
355
|
|
|
// the content is not yet encrypted (no recipient) |
356
|
|
|
|
357
|
|
|
$this->encryptJWE($jwe, $content_encryption_algorithm, $compression_method); |
|
|
|
|
358
|
|
|
} |
359
|
|
|
} |
360
|
|
|
|
361
|
|
|
/** |
362
|
|
|
* @param \Jose\Object\JWEInterface $jwe |
363
|
|
|
* @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm |
364
|
|
|
* @param \Jose\Compression\CompressionInterface|null $compression_method |
365
|
|
|
*/ |
366
|
|
|
private function encryptJWE(JWEInterface &$jwe, |
367
|
|
|
ContentEncryptionAlgorithmInterface $content_encryption_algorithm, |
368
|
|
|
CompressionInterface $compression_method = null |
369
|
|
|
) { |
370
|
|
|
if (!empty($jwe->getSharedProtectedHeaders())) { |
371
|
|
|
$jwe = $jwe->withEncodedSharedProtectedHeaders(Base64Url::encode(json_encode($jwe->getSharedProtectedHeaders()))); |
372
|
|
|
} |
373
|
|
|
|
374
|
|
|
// We encrypt the payload and get the tag |
375
|
|
|
$tag = null; |
376
|
|
|
$payload = $this->preparePayload($jwe->getPayload(), $compression_method); |
377
|
|
|
|
378
|
|
|
$ciphertext = $content_encryption_algorithm->encryptContent( |
379
|
|
|
$payload, |
380
|
|
|
$jwe->getContentEncryptionKey(), |
381
|
|
|
$jwe->getIV(), |
382
|
|
|
$jwe->getAAD(), |
383
|
|
|
$jwe->getEncodedSharedProtectedHeaders(), |
384
|
|
|
$tag |
385
|
|
|
); |
386
|
|
|
$jwe = $jwe->withCiphertext($ciphertext); |
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 \Jose\Object\JWEInterface $jwe |
411
|
|
|
* |
412
|
|
|
* @return string |
413
|
|
|
*/ |
414
|
|
|
private function getCurrentKeyManagementMode(JWEInterface $jwe) |
415
|
|
|
{ |
416
|
|
|
$complete_headers = array_merge( |
417
|
|
|
$jwe->getSharedProtectedHeaders(), |
418
|
|
|
$jwe->getSharedHeaders(), |
419
|
|
|
$jwe->getRecipient(0)->getHeaders() |
420
|
|
|
); |
421
|
|
|
$key_encryption_algorithm = $this->findKeyEncryptionAlgorithm($complete_headers); |
422
|
|
|
|
423
|
|
|
return $key_encryption_algorithm->getKeyManagementMode(); |
424
|
|
|
} |
425
|
|
|
|
426
|
|
|
/** |
427
|
|
|
* @param \Jose\Object\JWEInterface $jwe |
428
|
|
|
* @param \Jose\Algorithm\KeyEncryptionAlgorithmInterface $key_encryption_algorithm |
429
|
|
|
* @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm |
430
|
|
|
* @param array $complete_headers |
431
|
|
|
* @param array $additional_headers |
432
|
|
|
* @param array $recipient_headers |
433
|
|
|
* @param \Jose\Object\JWKInterface $recipient_key |
434
|
|
|
* |
435
|
|
|
* @return \Jose\Object\RecipientInterface |
436
|
|
|
*/ |
437
|
|
|
private function computeRecipient(JWEInterface $jwe, KeyEncryptionAlgorithmInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, array $complete_headers, array &$additional_headers, array $recipient_headers, JWKInterface $recipient_key) |
438
|
|
|
{ |
439
|
|
|
$recipient = new Recipient(); |
440
|
|
|
$recipient = $recipient->withHeaders($recipient_headers); |
441
|
|
|
|
442
|
|
|
$encrypted_content_encryption_key = $this->getEncryptedKey( |
443
|
|
|
$complete_headers, |
444
|
|
|
$jwe->getContentEncryptionKey(), |
445
|
|
|
$key_encryption_algorithm, |
446
|
|
|
$content_encryption_algorithm, |
447
|
|
|
$additional_headers, |
448
|
|
|
$recipient_key |
449
|
|
|
); |
450
|
|
|
if (!empty($additional_headers)) { |
451
|
|
|
foreach ($additional_headers as $key => $value) { |
452
|
|
|
$recipient = $recipient->withHeader($key, $value); |
453
|
|
|
} |
454
|
|
|
} |
455
|
|
|
if (null !== $encrypted_content_encryption_key) { |
456
|
|
|
$recipient = $recipient->withEncryptedKey($encrypted_content_encryption_key); |
457
|
|
|
} |
458
|
|
|
|
459
|
|
|
return $recipient; |
460
|
|
|
} |
461
|
|
|
|
462
|
|
|
/** |
463
|
|
|
* @param string $current |
464
|
|
|
* @param string $new |
465
|
|
|
* |
466
|
|
|
* @return bool |
467
|
|
|
*/ |
468
|
|
|
private function areKeyManagementModesCompatible($current, $new) |
469
|
|
|
{ |
470
|
|
|
$agree = KeyEncryptionAlgorithmInterface::MODE_AGREEMENT; |
471
|
|
|
$dir = KeyEncryptionAlgorithmInterface::MODE_DIRECT; |
472
|
|
|
$enc = KeyEncryptionAlgorithmInterface::MODE_ENCRYPT; |
473
|
|
|
$wrap = KeyEncryptionAlgorithmInterface::MODE_WRAP; |
474
|
|
|
|
475
|
|
|
$supported_key_management_mode_combinations = [ |
476
|
|
|
$enc.$enc => true, |
477
|
|
|
$enc.$wrap => true, |
478
|
|
|
$wrap.$enc => true, |
479
|
|
|
$wrap.$wrap => true, |
480
|
|
|
$agree.$agree => false, |
481
|
|
|
$agree.$dir => false, |
482
|
|
|
$agree.$enc => false, |
483
|
|
|
$agree.$wrap => false, |
484
|
|
|
$dir.$agree => false, |
485
|
|
|
$dir.$dir => false, |
486
|
|
|
$dir.$enc => false, |
487
|
|
|
$dir.$wrap => false, |
488
|
|
|
$enc.$agree => false, |
489
|
|
|
$enc.$dir => false, |
490
|
|
|
$wrap.$agree => false, |
491
|
|
|
$wrap.$dir => false, |
492
|
|
|
]; |
493
|
|
|
|
494
|
|
|
if (array_key_exists($current.$new, $supported_key_management_mode_combinations)) { |
495
|
|
|
return $supported_key_management_mode_combinations[$current.$new]; |
496
|
|
|
} |
497
|
|
|
|
498
|
|
|
return false; |
499
|
|
|
} |
500
|
|
|
|
501
|
|
|
/** |
502
|
|
|
* @param string $payload |
503
|
|
|
* @param \Jose\Compression\CompressionInterface|null $compression_method |
504
|
|
|
* |
505
|
|
|
* @return string |
506
|
|
|
*/ |
507
|
|
|
private function preparePayload($payload, CompressionInterface $compression_method = null) |
508
|
|
|
{ |
509
|
|
|
$prepared = is_string($payload) ? $payload : json_encode($payload); |
510
|
|
|
|
511
|
|
|
Assertion::notNull($prepared, 'The payload is empty or cannot encoded into JSON.'); |
512
|
|
|
|
513
|
|
|
if (null === $compression_method) { |
514
|
|
|
return $prepared; |
515
|
|
|
} |
516
|
|
|
$compressed_payload = $compression_method->compress($prepared); |
517
|
|
|
Assertion::string($compressed_payload, 'Compression failed.'); |
518
|
|
|
|
519
|
|
|
return $compressed_payload; |
520
|
|
|
} |
521
|
|
|
|
522
|
|
|
/** |
523
|
|
|
* @param array $complete_headers |
524
|
|
|
* |
525
|
|
|
* @return \Jose\Compression\CompressionInterface|null |
526
|
|
|
*/ |
527
|
|
|
private function findCompressionMethod(array $complete_headers) |
528
|
|
|
{ |
529
|
|
|
if (!array_key_exists('zip', $complete_headers)) { |
530
|
|
|
return; |
531
|
|
|
} |
532
|
|
|
|
533
|
|
|
$compression_method = $this->getCompressionManager()->getCompressionAlgorithm($complete_headers['zip']); |
534
|
|
|
Assertion::isInstanceOf($compression_method, CompressionInterface::class, sprintf('Compression method "%s" not supported', $complete_headers['zip'])); |
535
|
|
|
|
536
|
|
|
return $compression_method; |
537
|
|
|
} |
538
|
|
|
|
539
|
|
|
/** |
540
|
|
|
* @param array $complete_headers |
541
|
|
|
* @param string $cek |
542
|
|
|
* @param \Jose\Algorithm\KeyEncryptionAlgorithmInterface $key_encryption_algorithm |
543
|
|
|
* @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm |
544
|
|
|
* @param \Jose\Object\JWKInterface $recipient_key |
545
|
|
|
* @param array $additional_headers |
546
|
|
|
* |
547
|
|
|
* @return string|null |
548
|
|
|
*/ |
549
|
|
|
private function getEncryptedKey(array $complete_headers, $cek, KeyEncryptionAlgorithmInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, array &$additional_headers, JWKInterface $recipient_key) |
550
|
|
|
{ |
551
|
|
|
if ($key_encryption_algorithm instanceof KeyEncryptionInterface) { |
552
|
|
|
return $this->getEncryptedKeyFromKeyEncryptionAlgorithm($complete_headers, $cek, $key_encryption_algorithm, $recipient_key, $additional_headers); |
553
|
|
|
} elseif ($key_encryption_algorithm instanceof KeyWrappingInterface) { |
554
|
|
|
return $this->getEncryptedKeyFromKeyWrappingAlgorithm($complete_headers, $cek, $key_encryption_algorithm, $recipient_key, $additional_headers); |
555
|
|
|
} elseif ($key_encryption_algorithm instanceof KeyAgreementWrappingInterface) { |
556
|
|
|
return $this->getEncryptedKeyFromKeyAgreementAndKeyWrappingAlgorithm($complete_headers, $cek, $key_encryption_algorithm, $content_encryption_algorithm, $additional_headers, $recipient_key); |
557
|
|
|
} |
558
|
|
|
|
559
|
|
|
// Using KeyAgreementInterface or DirectEncryptionInterface, the encrypted key is an empty string |
560
|
|
|
} |
561
|
|
|
|
562
|
|
|
/** |
563
|
|
|
* @param array $complete_headers |
564
|
|
|
* @param \Jose\Algorithm\KeyEncryption\KeyAgreementInterface $key_encryption_algorithm |
565
|
|
|
* @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm |
566
|
|
|
* @param array $additional_headers |
567
|
|
|
* @param \Jose\Object\JWKInterface $recipient_key |
568
|
|
|
* |
569
|
|
|
* @return mixed |
570
|
|
|
*/ |
571
|
|
|
private function getEncryptedKeyFromKeyAgreementAlgorithm(array $complete_headers, KeyAgreementInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, array &$additional_headers, JWKInterface $recipient_key) |
|
|
|
|
572
|
|
|
{ |
573
|
|
|
$jwt_cek = $key_encryption_algorithm->getAgreementKey( |
574
|
|
|
$content_encryption_algorithm->getCEKSize(), |
575
|
|
|
$content_encryption_algorithm->getAlgorithmName(), |
576
|
|
|
$recipient_key, |
577
|
|
|
$complete_headers, |
578
|
|
|
$additional_headers |
579
|
|
|
); |
580
|
|
|
|
581
|
|
|
return $jwt_cek; |
582
|
|
|
} |
583
|
|
|
|
584
|
|
|
/** |
585
|
|
|
* @param array $complete_headers |
586
|
|
|
* @param string $cek |
587
|
|
|
* @param \Jose\Algorithm\KeyEncryption\KeyAgreementWrappingInterface $key_encryption_algorithm |
588
|
|
|
* @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm |
589
|
|
|
* @param array $additional_headers |
590
|
|
|
* @param \Jose\Object\JWKInterface $recipient_key |
591
|
|
|
* |
592
|
|
|
* @return string |
593
|
|
|
*/ |
594
|
|
|
private function getEncryptedKeyFromKeyAgreementAndKeyWrappingAlgorithm(array $complete_headers, $cek, KeyAgreementWrappingInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, array &$additional_headers, JWKInterface $recipient_key) |
595
|
|
|
{ |
596
|
|
|
$jwt_cek = $key_encryption_algorithm->wrapAgreementKey($recipient_key, $cek, $content_encryption_algorithm->getCEKSize(), $complete_headers, $additional_headers); |
597
|
|
|
|
598
|
|
|
return $jwt_cek; |
599
|
|
|
} |
600
|
|
|
|
601
|
|
|
/** |
602
|
|
|
* @param array $complete_headers |
603
|
|
|
* @param string $cek |
604
|
|
|
* @param \Jose\Algorithm\KeyEncryption\KeyEncryptionInterface $key_encryption_algorithm |
605
|
|
|
* @param \Jose\Object\JWKInterface $recipient_key |
606
|
|
|
* @param array $additional_headers |
607
|
|
|
* |
608
|
|
|
* @return string |
609
|
|
|
*/ |
610
|
|
|
private function getEncryptedKeyFromKeyEncryptionAlgorithm(array $complete_headers, $cek, KeyEncryptionInterface $key_encryption_algorithm, JWKInterface $recipient_key, array &$additional_headers) |
611
|
|
|
{ |
612
|
|
|
return $key_encryption_algorithm->encryptKey( |
613
|
|
|
$recipient_key, |
614
|
|
|
$cek, |
615
|
|
|
$complete_headers, |
616
|
|
|
$additional_headers |
617
|
|
|
); |
618
|
|
|
} |
619
|
|
|
|
620
|
|
|
/** |
621
|
|
|
* @param array $complete_headers |
622
|
|
|
* @param string $cek |
623
|
|
|
* @param \Jose\Algorithm\KeyEncryption\KeyWrappingInterface $key_encryption_algorithm |
624
|
|
|
* @param \Jose\Object\JWKInterface $recipient_key |
625
|
|
|
* @param array $additional_headers |
626
|
|
|
* |
627
|
|
|
* @return string |
628
|
|
|
*/ |
629
|
|
|
private function getEncryptedKeyFromKeyWrappingAlgorithm(array $complete_headers, $cek, KeyWrappingInterface $key_encryption_algorithm, JWKInterface $recipient_key, &$additional_headers) |
630
|
|
|
{ |
631
|
|
|
return $key_encryption_algorithm->wrapKey( |
632
|
|
|
$recipient_key, |
633
|
|
|
$cek, |
634
|
|
|
$complete_headers, |
635
|
|
|
$additional_headers |
636
|
|
|
); |
637
|
|
|
} |
638
|
|
|
|
639
|
|
|
/** |
640
|
|
|
* @param array $complete_headers |
641
|
|
|
* @param \Jose\Algorithm\KeyEncryptionAlgorithmInterface $key_encryption_algorithm |
642
|
|
|
* @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm |
643
|
|
|
* @param array $additional_header_values |
644
|
|
|
* @param \Jose\Object\JWKInterface $recipient_key |
645
|
|
|
* |
646
|
|
|
* @return string |
647
|
|
|
*/ |
648
|
|
|
private function getCEK(array $complete_headers, KeyEncryptionAlgorithmInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, array &$additional_header_values, JWKInterface $recipient_key) |
649
|
|
|
{ |
650
|
|
|
if ($key_encryption_algorithm instanceof KeyEncryptionInterface) { |
651
|
|
|
return $this->createCEK($content_encryption_algorithm->getCEKSize()); |
652
|
|
|
} elseif ($key_encryption_algorithm instanceof KeyWrappingInterface) { |
653
|
|
|
return $this->createCEK($content_encryption_algorithm->getCEKSize()); |
654
|
|
|
} elseif ($key_encryption_algorithm instanceof KeyAgreementWrappingInterface) { |
655
|
|
|
return $this->createCEK($content_encryption_algorithm->getCEKSize()); |
656
|
|
|
} elseif ($key_encryption_algorithm instanceof KeyAgreementInterface) { |
657
|
|
|
return $this->calculateAgreementKey($complete_headers, $key_encryption_algorithm, $content_encryption_algorithm, $additional_header_values, $recipient_key); |
658
|
|
|
} elseif ($key_encryption_algorithm instanceof DirectEncryptionInterface) { |
659
|
|
|
return $key_encryption_algorithm->getCEK($recipient_key); |
660
|
|
|
} else { |
661
|
|
|
throw new \RuntimeException('Unable to get key management mode.'); |
662
|
|
|
} |
663
|
|
|
} |
664
|
|
|
|
665
|
|
|
/** |
666
|
|
|
* @param array $complete_headers |
667
|
|
|
* |
668
|
|
|
* @return \Jose\Algorithm\KeyEncryptionAlgorithmInterface |
669
|
|
|
*/ |
670
|
|
|
private function findKeyEncryptionAlgorithm(array $complete_headers) |
671
|
|
|
{ |
672
|
|
|
Assertion::keyExists($complete_headers, 'alg', 'Parameter "alg" is missing.'); |
673
|
|
|
|
674
|
|
|
$key_encryption_algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['alg']); |
675
|
|
|
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'])); |
676
|
|
|
|
677
|
|
|
return $key_encryption_algorithm; |
678
|
|
|
} |
679
|
|
|
|
680
|
|
|
/** |
681
|
|
|
* @param array $complete_headers |
682
|
|
|
* |
683
|
|
|
* @return \Jose\Algorithm\ContentEncryptionAlgorithmInterface |
684
|
|
|
*/ |
685
|
|
|
private function findContentEncryptionAlgorithm(array $complete_headers) |
686
|
|
|
{ |
687
|
|
|
Assertion::keyExists($complete_headers, 'enc', 'Parameter "enc" is missing.'); |
688
|
|
|
|
689
|
|
|
|
690
|
|
|
$content_encryption_algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['enc']); |
691
|
|
|
Assertion::isInstanceOf($content_encryption_algorithm, ContentEncryptionAlgorithmInterface::class, sprintf('The algorithm "%s" is not enabled or does not implement ContentEncryptionInterface.', $complete_headers['enc'])); |
692
|
|
|
|
693
|
|
|
return $content_encryption_algorithm; |
694
|
|
|
} |
695
|
|
|
|
696
|
|
|
/** |
697
|
|
|
* @param array $complete_headers |
698
|
|
|
* @param \Jose\Algorithm\KeyEncryption\KeyAgreementInterface $key_encryption_algorithm |
699
|
|
|
* @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm |
700
|
|
|
* @param array $additional_header_values |
701
|
|
|
* @param \Jose\Object\JWKInterface $recipient_key |
702
|
|
|
* |
703
|
|
|
* @return string |
704
|
|
|
*/ |
705
|
|
|
private function calculateAgreementKey(array $complete_headers, KeyAgreementInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, array &$additional_header_values, JWKInterface $recipient_key) |
706
|
|
|
{ |
707
|
|
|
$cek = $key_encryption_algorithm->getAgreementKey( |
708
|
|
|
$content_encryption_algorithm->getCEKSize(), |
709
|
|
|
$content_encryption_algorithm->getAlgorithmName(), |
710
|
|
|
$recipient_key, |
711
|
|
|
$complete_headers, |
712
|
|
|
$additional_header_values |
713
|
|
|
); |
714
|
|
|
|
715
|
|
|
return $cek; |
716
|
|
|
} |
717
|
|
|
|
718
|
|
|
/** |
719
|
|
|
* @param int $size |
720
|
|
|
* |
721
|
|
|
* @return string |
722
|
|
|
*/ |
723
|
|
|
private function createCEK($size) |
724
|
|
|
{ |
725
|
|
|
return StringUtil::generateRandomBytes($size / 8); |
726
|
|
|
} |
727
|
|
|
|
728
|
|
|
/** |
729
|
|
|
* @param int $size |
730
|
|
|
* |
731
|
|
|
* @return string |
732
|
|
|
*/ |
733
|
|
|
private function createIV($size) |
734
|
|
|
{ |
735
|
|
|
return StringUtil::generateRandomBytes($size / 8); |
736
|
|
|
} |
737
|
|
|
} |
738
|
|
|
|
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: