Failed Conditions
Push — master ( 20ac4f...66e9c8 )
by Florent
03:13
created

Encrypter   D

Complexity

Total Complexity 48

Size/Duplication

Total Lines 482
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 20

Importance

Changes 26
Bugs 9 Features 4
Metric Value
wmc 48
c 26
b 9
f 4
lcom 1
cbo 20
dl 0
loc 482
rs 4.5032

19 Methods

Rating   Name   Duplication   Size   Complexity  
A createEncrypter() 0 6 1
A __construct() 0 14 1
B encrypt() 0 50 4
B processRecipient() 0 44 4
B determineCEK() 0 25 5
A getKeyManagementMode() 0 21 3
B getCompressionMethod() 0 31 6
A getContentEncryptionAlgorithm() 0 23 3
B encryptJWE() 0 31 4
A checkKeys() 0 9 2
A areKeyManagementModesCompatible() 0 15 2
A preparePayload() 0 14 3
A getEncryptedKey() 0 12 4
A getEncryptedKeyFromKeyAgreementAndKeyWrappingAlgorithm() 0 6 1
A getEncryptedKeyFromKeyEncryptionAlgorithm() 0 9 1
A getEncryptedKeyFromKeyWrappingAlgorithm() 0 9 1
A findKeyEncryptionAlgorithm() 0 9 1
A createCEK() 0 4 1
A createIV() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like Encrypter often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Encrypter, and based on these observations, apply Extract Interface, too.

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