Completed
Push — master ( 861caf...658c4f )
by Florent
02:20
created

Encrypter::findContentEncryptionAlgorithm()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 13
rs 9.4285
cc 3
eloc 7
nc 3
nop 1
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 Base64Url\Base64Url;
15
use Jose\Algorithm\ContentEncryptionAlgorithmInterface;
16
use Jose\Algorithm\JWAManagerInterface;
17
use Jose\Algorithm\KeyEncryption\DirectEncryptionInterface;
18
use Jose\Algorithm\KeyEncryption\KeyAgreementInterface;
19
use Jose\Algorithm\KeyEncryption\KeyAgreementWrappingInterface;
20
use Jose\Algorithm\KeyEncryption\KeyEncryptionInterface;
21
use Jose\Algorithm\KeyEncryption\KeyWrappingInterface;
22
use Jose\Algorithm\KeyEncryptionAlgorithmInterface;
23
use Jose\Behaviour\HasCompressionManager;
24
use Jose\Behaviour\HasJWAManager;
25
use Jose\Behaviour\HasKeyChecker;
26
use Jose\Compression\CompressionManagerInterface;
27
use Jose\Object\JWEInterface;
28
use Jose\Object\JWKInterface;
29
use Jose\Object\Recipient;
30
use Jose\Util\StringUtil;
31
32
/**
33
 */
34
final class Encrypter implements EncrypterInterface
35
{
36
    use HasKeyChecker;
37
    use HasJWAManager;
38
    use HasCompressionManager;
39
40
    /**
41
     * Encrypter constructor.
42
     *
43
     * @param \Jose\Algorithm\JWAManagerInterface           $jwa_manager
44
     * @param \Jose\Compression\CompressionManagerInterface $compression_manager
45
     */
46
    public function __construct(
47
        JWAManagerInterface $jwa_manager,
48
        CompressionManagerInterface $compression_manager)
49
    {
50
        $this->setJWAManager($jwa_manager);
51
        $this->setCompressionManager($compression_manager);
52
    }
53
54
    /**
55
     * {@inheritdoc}
56
     */
57
    public function addRecipient(JWEInterface &$jwe, JWKInterface $recipient_key, JWKInterface $sender_key = null, array $recipient_headers = [])
58
    {
59
        $complete_headers = array_merge(
60
            $jwe->getSharedProtectedHeaders(),
61
            $jwe->getSharedHeaders(),
62
            $recipient_headers
63
        );
64
65
        // Key Encryption Algorithm
66
        $key_encryption_algorithm = $this->findKeyEncryptionAlgorithm($complete_headers);
67
68
        // Content Encryption Algorithm
69
        $content_encryption_algorithm = $this->findContentEncryptionAlgorithm($complete_headers);
70
71
        // We check keys (usage and algorithm if restrictions are set)
72
        $this->checkKeys($key_encryption_algorithm, $recipient_key, $sender_key);
73
74
        if (null === $jwe->getCiphertext()) {
75
            // the content is not yet encrypted (no recipient)
76
77
            $this->encryptJWE($jwe, $complete_headers, $key_encryption_algorithm, $content_encryption_algorithm, $recipient_key, $sender_key);
78
        } else {
79
            if (0 === $jwe->countRecipients()) {
80
                throw new \InvalidArgumentException('Invalid JWE. The payload is encrypted but no recipient is available.');
81
            }
82
            if (null === $jwe->getContentEncryptionKey()) {
83
                throw new \InvalidArgumentException('Unable to add a recipient. The JWE must be decrypted first.');
84
            }
85
            $current_key_management_mode = $this->getCurrentKeyManagementMode($jwe);
86
87
            if (false === $this->areKeyManagementModesCompatible($current_key_management_mode, $key_encryption_algorithm->getKeyManagementMode())) {
88
                throw new \InvalidArgumentException('Foreign key management mode forbidden.');
89
            }
90
        }
91
92
        $recipient = $this->computeRecipient(
93
            $jwe,
94
            $key_encryption_algorithm,
95
            $content_encryption_algorithm,
96
            $complete_headers,
97
            $recipient_headers,
98
            $recipient_key,
99
            $sender_key
100
        );
101
102
        $jwe = $jwe->addRecipient($recipient);
103
    }
104
105
    /**
106
     * @param \Jose\Object\JWEInterface                           $jwe
107
     * @param array                                               $complete_headers
108
     * @param \Jose\Algorithm\KeyEncryptionAlgorithmInterface     $key_encryption_algorithm
109
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm
110
     * @param \Jose\Object\JWKInterface                           $recipient_key
111
     * @param \Jose\Object\JWKInterface|null                      $sender_key
112
     */
113
    private function encryptJWE(JWEInterface &$jwe,
114
                                array $complete_headers,
115
                                KeyEncryptionAlgorithmInterface $key_encryption_algorithm,
116
                                ContentEncryptionAlgorithmInterface $content_encryption_algorithm,
117
                                JWKInterface $recipient_key,
118
                                JWKInterface $sender_key = null
119
    ) {
120
121
        if (!empty($jwe->getSharedProtectedHeaders())) {
122
            $jwe = $jwe->withEncodedSharedProtectedHeaders(Base64Url::encode(json_encode($jwe->getSharedProtectedHeaders())));
123
        }
124
125
        // CEK
126
        $content_encryption_key = $this->getCEK(
127
            $complete_headers,
128
            $key_encryption_algorithm,
129
            $content_encryption_algorithm,
130
            $recipient_key,
131
            $sender_key
132
        );
133
        $jwe = $jwe->withContentEncryptionKey($content_encryption_key);
134
135
        // IV
136
        if (null !== $iv_size = $content_encryption_algorithm->getIVSize()) {
137
            $iv = $this->createIV($iv_size);
138
            $jwe = $jwe->withIV($iv);
139
        }
140
141
        // We encrypt the payload and get the tag
142
        $tag = null;
143
        $payload = $this->preparePayload($jwe->getPayload(), $complete_headers);
144
145
        $ciphertext = $content_encryption_algorithm->encryptContent(
146
            $payload,
147
            $content_encryption_key,
148
            $jwe->getIV(),
149
            $jwe->getAAD(),
150
            $jwe->getEncodedSharedProtectedHeaders(),
151
            $tag
152
        );
153
        $jwe = $jwe->withCiphertext($ciphertext);
154
155
        // Tag
156
        if (null !== $tag) {
157
            $jwe = $jwe->withTag($tag);
158
        }
159
    }
160
161
    /**
162
     * @param \Jose\Algorithm\KeyEncryptionAlgorithmInterface $algorithm
163
     * @param \Jose\Object\JWKInterface                       $recipient_key
164
     * @param \Jose\Object\JWKInterface|null                  $sender_key
165
     */
166
    private function checkKeys(KeyEncryptionAlgorithmInterface $algorithm, JWKInterface $recipient_key, JWKInterface $sender_key = null)
167
    {
168
        $this->checkKeyUsage($recipient_key, 'encryption');
169
        $this->checkKeyAlgorithm($recipient_key, $algorithm->getAlgorithmName());
170
        if ($sender_key instanceof JWKInterface) {
171
            $this->checkKeyUsage($sender_key, 'encryption');
172
            $this->checkKeyAlgorithm($sender_key, $algorithm->getAlgorithmName());
173
        }
174
    }
175
176
    /**
177
     * @param \Jose\Object\JWEInterface $jwe
178
     *
179
     * @return string
180
     */
181
    private function getCurrentKeyManagementMode(JWEInterface $jwe)
182
    {
183
        $complete_headers = array_merge(
184
            $jwe->getSharedProtectedHeaders(),
185
            $jwe->getSharedHeaders(),
186
            $jwe->getRecipient(0)->getHeaders()
187
        );
188
        $key_encryption_algorithm = $this->findKeyEncryptionAlgorithm($complete_headers);
189
190
        return $key_encryption_algorithm->getKeyManagementMode();
191
    }
192
193
    /**
194
     * @param \Jose\Object\JWEInterface                           $jwe
195
     * @param \Jose\Algorithm\KeyEncryptionAlgorithmInterface     $key_encryption_algorithm
196
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm
197
     * @param array                                               $complete_headers
198
     * @param array                                               $recipient_headers
199
     * @param \Jose\Object\JWKInterface                           $recipient_key
200
     * @param \Jose\Object\JWKInterface|null                      $sender_key
201
     *
202
     * @return \Jose\Object\RecipientInterface
203
     */
204
    private function computeRecipient(JWEInterface $jwe, KeyEncryptionAlgorithmInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, array $complete_headers, array $recipient_headers, JWKInterface $recipient_key, JWKInterface $sender_key = null)
205
    {
206
        $recipient = new Recipient();
207
        $recipient = $recipient->withHeaders($recipient_headers);
208
209
        $additional_headers = [];
210
        $encrypted_content_encryption_key = $this->getEncryptedKey(
211
            $complete_headers,
212
            $jwe->getContentEncryptionKey(),
213
            $key_encryption_algorithm,
214
            $content_encryption_algorithm,
215
            $additional_headers,
216
            $recipient_key,
217
            $sender_key
218
        );
219
        if (!empty($additional_headers)) {
220
            foreach ($additional_headers as $key => $value) {
221
                $recipient = $recipient->withHeader($key, $value);
222
            }
223
        }
224
        if (null !== $encrypted_content_encryption_key) {
225
            $recipient = $recipient->withEncryptedKey($encrypted_content_encryption_key);
226
        }
227
228
        return $recipient;
229
    }
230
231
    /**
232
     * @param string $current
233
     * @param string $new
234
     *
235
     * @return bool
236
     */
237
    private function areKeyManagementModesCompatible($current, $new)
238
    {
239
        $agree = KeyEncryptionAlgorithmInterface::MODE_AGREEMENT;
240
        $dir = KeyEncryptionAlgorithmInterface::MODE_DIRECT;
241
        $enc = KeyEncryptionAlgorithmInterface::MODE_ENCRYPT;
242
        $wrap = KeyEncryptionAlgorithmInterface::MODE_WRAP;
243
244
        $supported_key_management_mode_combinations = [
245
            $agree.$enc   => true,
246
            $agree.$wrap  => true,
247
            $dir.$enc     => true,
248
            $dir.$wrap    => true,
249
            $enc.$enc     => true,
250
            $enc.$wrap    => true,
251
            $wrap.$enc    => true,
252
            $wrap.$wrap   => true,
253
            $agree.$agree => false,
254
            $agree.$dir   => false,
255
            $dir.$agree   => false,
256
            $dir.$dir     => false,
257
            $enc.$agree   => false,
258
            $enc.$dir     => false,
259
            $wrap.$agree  => false,
260
            $wrap.$dir    => false,
261
        ];
262
263
        if (array_key_exists($current.$new, $supported_key_management_mode_combinations)) {
264
            return $supported_key_management_mode_combinations[$current.$new];
265
        }
266
267
        return false;
268
    }
269
270
    /**
271
     * @param string $payload
272
     * @param array  $complete_headers
273
     *
274
     * @return string
275
     */
276
    private function preparePayload($payload, array $complete_headers)
277
    {
278
        $prepared = is_string($payload) ? $payload : json_encode($payload);
279
280
        if (null === $prepared) {
281
            throw new \InvalidArgumentException('The payload is empty or cannot encoded into JSON.');
282
        }
283
        if (!array_key_exists('zip', $complete_headers)) {
284
            return $prepared;
285
        }
286
287
        $compression_method = $this->getCompressionManager()->getCompressionAlgorithm($complete_headers['zip']);
288
        if (null === $compression_method) {
289
            throw new \RuntimeException(sprintf('Compression method "%s" not supported', $complete_headers['zip']));
290
        }
291
        $compressed_payload = $compression_method->compress($prepared);
292
        if (!is_string($compressed_payload)) {
293
            throw new \RuntimeException('Compression failed.');
294
        }
295
296
        return $compressed_payload;
297
    }
298
299
    /**
300
     * @param array                                               $complete_headers
301
     * @param string                                              $cek
302
     * @param \Jose\Algorithm\KeyEncryptionAlgorithmInterface     $key_encryption_algorithm
303
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm
304
     * @param \Jose\Object\JWKInterface                           $recipient_key
305
     * @param \Jose\Object\JWKInterface|null                      $sender_key
306
     * @param array                                               $additional_headers
307
     *
308
     * @return string|null
309
     */
310
    private function getEncryptedKey(array $complete_headers, $cek, KeyEncryptionAlgorithmInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, array &$additional_headers, JWKInterface $recipient_key, JWKInterface $sender_key = null)
311
    {
312
        if ($key_encryption_algorithm instanceof KeyEncryptionInterface) {
313
            return $this->getEncryptedKeyFroKeyEncryptionAlgorithm($complete_headers, $cek, $key_encryption_algorithm, $recipient_key);
314
        } elseif ($key_encryption_algorithm instanceof KeyWrappingInterface) {
315
            return $this->getEncryptedKeyFroKeyWrappingAlgorithm($complete_headers, $cek, $key_encryption_algorithm, $recipient_key);
316
        } elseif ($key_encryption_algorithm instanceof KeyAgreementWrappingInterface) {
317
            return $this->getEncryptedKeyFroKeyAgreementAndKeyWrappingAlgorithm($complete_headers, $cek, $key_encryption_algorithm, $content_encryption_algorithm, $additional_headers, $recipient_key, $sender_key);
318
        } elseif ($key_encryption_algorithm instanceof KeyAgreementInterface) {
319
            return $this->getEncryptedKeyFroKeyAgreementAlgorithm($complete_headers, $key_encryption_algorithm, $content_encryption_algorithm, $additional_headers, $recipient_key, $sender_key);
320
        }
321
    }
322
323
    /**
324
     * @param array                                               $complete_headers
325
     * @param \Jose\Algorithm\KeyEncryption\KeyAgreementInterface $key_encryption_algorithm
326
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm
327
     * @param array                                               $additional_headers
328
     * @param \Jose\Object\JWKInterface                           $recipient_key
329
     * @param \Jose\Object\JWKInterface|null                      $sender_key
330
     *
331
     * @return mixed
332
     */
333
    private function getEncryptedKeyFroKeyAgreementAlgorithm(array $complete_headers, KeyAgreementInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, array &$additional_headers, JWKInterface $recipient_key, JWKInterface $sender_key = null)
334
    {
335
        if (!$sender_key instanceof JWKInterface) {
336
            throw new \RuntimeException('The sender key must be set using Key Agreement or Key Agreement with Wrapping algorithms.');
337
        }
338
        $jwt_cek = $key_encryption_algorithm->getAgreementKey($content_encryption_algorithm->getCEKSize(), $sender_key, $recipient_key, $complete_headers, $additional_headers);
339
340
        return $jwt_cek;
341
    }
342
343
    /**
344
     * @param array                                                       $complete_headers
345
     * @param string                                                      $cek
346
     * @param \Jose\Algorithm\KeyEncryption\KeyAgreementWrappingInterface $key_encryption_algorithm
347
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface         $content_encryption_algorithm
348
     * @param array                                                       $additional_headers
349
     * @param \Jose\Object\JWKInterface                                   $recipient_key
350
     * @param \Jose\Object\JWKInterface|null                              $sender_key
351
     *
352
     * @return string
353
     */
354
    private function getEncryptedKeyFroKeyAgreementAndKeyWrappingAlgorithm(array $complete_headers, $cek, KeyAgreementWrappingInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, array &$additional_headers, JWKInterface $recipient_key, JWKInterface $sender_key = null)
355
    {
356
        if (!$sender_key instanceof JWKInterface) {
357
            throw new \RuntimeException('The sender key must be set using Key Agreement or Key Agreement with Wrapping algorithms.');
358
        }
359
        $jwt_cek = $key_encryption_algorithm->wrapAgreementKey($sender_key, $recipient_key, $cek, $content_encryption_algorithm->getCEKSize(), $complete_headers, $additional_headers);
360
361
        return $jwt_cek;
362
    }
363
364
    /**
365
     * @param array                                                $complete_headers
366
     * @param string                                               $cek
367
     * @param \Jose\Algorithm\KeyEncryption\KeyEncryptionInterface $key_encryption_algorithm
368
     * @param \Jose\Object\JWKInterface                            $recipient_key
369
     *
370
     * @return string
371
     */
372
    private function getEncryptedKeyFroKeyEncryptionAlgorithm(array $complete_headers, $cek, KeyEncryptionInterface $key_encryption_algorithm, JWKInterface $recipient_key)
373
    {
374
        return $key_encryption_algorithm->encryptKey(
375
            $recipient_key,
376
            $cek,
377
            $complete_headers
378
        );
379
    }
380
381
    /**
382
     * @param array                                              $complete_headers
383
     * @param string                                             $cek
384
     * @param \Jose\Algorithm\KeyEncryption\KeyWrappingInterface $key_encryption_algorithm
385
     * @param \Jose\Object\JWKInterface                          $recipient_key
386
     *
387
     * @return string
388
     */
389
    private function getEncryptedKeyFroKeyWrappingAlgorithm(array $complete_headers, $cek, KeyWrappingInterface $key_encryption_algorithm, JWKInterface $recipient_key)
390
    {
391
        return $key_encryption_algorithm->wrapKey(
392
            $recipient_key,
393
            $cek,
394
            $complete_headers
395
        );
396
    }
397
398
    /**
399
     * @param array                                               $complete_headers
400
     * @param \Jose\Algorithm\KeyEncryptionAlgorithmInterface     $key_encryption_algorithm
401
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm
402
     * @param \Jose\Object\JWKInterface                           $recipient_key
403
     * @param \Jose\Object\JWKInterface|null                      $sender_key
404
     *
405
     * @return string
406
     */
407
    private function getCEK(array $complete_headers, KeyEncryptionAlgorithmInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, JWKInterface $recipient_key, JWKInterface $sender_key = null)
408
    {
409
        if ($key_encryption_algorithm instanceof KeyEncryptionInterface) {
410
            return $this->createCEK($content_encryption_algorithm->getCEKSize());
411
        } elseif ($key_encryption_algorithm instanceof KeyWrappingInterface) {
412
            return $this->createCEK($content_encryption_algorithm->getCEKSize());
413
        } elseif ($key_encryption_algorithm instanceof KeyAgreementWrappingInterface) {
414
            return $this->createCEK($content_encryption_algorithm->getCEKSize());
415
        } elseif ($key_encryption_algorithm instanceof KeyAgreementInterface) {
416
            return $this->calculateAgreementKey($complete_headers, $key_encryption_algorithm, $content_encryption_algorithm, $recipient_key, $sender_key);
417
        } elseif ($key_encryption_algorithm instanceof DirectEncryptionInterface) {
418
            return $key_encryption_algorithm->getCEK($recipient_key);
419
        } else {
420
            throw new \RuntimeException('Unable to get key management mode.');
421
        }
422
    }
423
424
    /**
425
     * @param array $complete_headers
426
     *
427
     * @return \Jose\Algorithm\KeyEncryptionAlgorithmInterface
428
     */
429
    private function findKeyEncryptionAlgorithm(array $complete_headers)
430
    {
431
        if (!array_key_exists('alg', $complete_headers)) {
432
            throw new \InvalidArgumentException('Parameter "alg" is missing.');
433
        }
434
        $key_encryption_algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['alg']);
435
        if ($key_encryption_algorithm instanceof KeyEncryptionAlgorithmInterface) {
436
            return $key_encryption_algorithm;
437
        }
438
        throw new \InvalidArgumentException(sprintf('The key encryption algorithm "%s" is not supported or not a key encryption algorithm instance.', $complete_headers['alg']));
439
    }
440
441
    /**
442
     * @param array $complete_headers
443
     *
444
     * @return \Jose\Algorithm\ContentEncryptionAlgorithmInterface
445
     */
446
    private function findContentEncryptionAlgorithm(array $complete_headers)
447
    {
448
        if (!array_key_exists('enc', $complete_headers)) {
449
            throw new \InvalidArgumentException('Parameter "enc" is missing.');
450
        }
451
452
        $content_encryption_algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['enc']);
453
        if (!$content_encryption_algorithm instanceof ContentEncryptionAlgorithmInterface) {
454
            throw new \RuntimeException(sprintf('The algorithm "%s" is not enabled or does not implement ContentEncryptionInterface.', $complete_headers['enc']));
455
        }
456
457
        return $content_encryption_algorithm;
458
    }
459
460
    /**
461
     * @param array                                               $complete_headers
462
     * @param \Jose\Algorithm\KeyEncryption\KeyAgreementInterface $key_encryption_algorithm
463
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm
464
     * @param \Jose\Object\JWKInterface                           $recipient_key
465
     * @param \Jose\Object\JWKInterface|null                      $sender_key
466
     *
467
     * @return string
468
     */
469
    private function calculateAgreementKey(array $complete_headers, KeyAgreementInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, JWKInterface $recipient_key, JWKInterface $sender_key = null)
470
    {
471
        if (!$sender_key instanceof JWKInterface) {
472
            throw new \RuntimeException('The sender key must be set using Key Agreement or Key Agreement with Wrapping algorithms.');
473
        }
474
        $additional_header_values = [];
475
        $cek = $key_encryption_algorithm->getAgreementKey($content_encryption_algorithm->getCEKSize(), $sender_key, $recipient_key, $complete_headers, $additional_header_values);
476
477
        return $cek;
478
    }
479
480
    /**
481
     * @param int $size
482
     *
483
     * @return string
484
     */
485
    private function createCEK($size)
486
    {
487
        return StringUtil::generateRandomBytes($size / 8);
488
    }
489
490
    /**
491
     * @param int $size
492
     *
493
     * @return string
494
     */
495
    private function createIV($size)
496
    {
497
        return StringUtil::generateRandomBytes($size / 8);
498
    }
499
}
500