Failed Conditions
Push — master ( 7c0052...43ee2a )
by Florent
02:17
created

JWELoader   C

Complexity

Total Complexity 40

Size/Duplication

Total Lines 301
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 17

Importance

Changes 0
Metric Value
wmc 40
lcom 2
cbo 17
dl 0
loc 301
rs 6.6773
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
A load() 0 4 1
A getKeyEncryptionAlgorithmManager() 0 4 1
A getContentEncryptionAlgorithmManager() 0 4 1
A getCompressionMethodManager() 0 4 1
A decryptUsingKey() 0 7 1
B decryptUsingKeySet() 0 24 4
B decryptRecipientKey() 0 29 5
A checkRecipients() 0 6 2
A checkPayload() 0 6 2
A checkJWKSet() 0 6 2
B decryptCEK() 0 16 6
A decryptPayload() 0 9 3
A decompressIfNeeded() 0 12 3
A checkCompleteHeader() 0 8 3
A getKeyEncryptionAlgorithm() 0 9 2
A getContentEncryptionAlgorithm() 0 9 2

How to fix   Complexity   

Complex Class

Complex classes like JWELoader 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 JWELoader, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * The MIT License (MIT)
7
 *
8
 * Copyright (c) 2014-2017 Spomky-Labs
9
 *
10
 * This software may be modified and distributed under the terms
11
 * of the MIT license.  See the LICENSE file for details.
12
 */
13
14
namespace Jose\Component\Encryption;
15
16
use Jose\Component\Checker\HeaderCheckerManager;
17
use Jose\Component\Core\AlgorithmManager;
18
use Jose\Component\Core\JWKSet;
19
use Jose\Component\Encryption\Compression\CompressionMethodManager;
20
use Base64Url\Base64Url;
21
use Jose\Component\Core\AlgorithmInterface;
22
use Jose\Component\Core\JWK;
23
use Jose\Component\Core\Util\KeyChecker;
24
use Jose\Component\Encryption\Algorithm\ContentEncryptionAlgorithmInterface;
25
use Jose\Component\Encryption\Algorithm\KeyEncryption\DirectEncryptionInterface;
26
use Jose\Component\Encryption\Algorithm\KeyEncryption\KeyAgreementInterface;
27
use Jose\Component\Encryption\Algorithm\KeyEncryption\KeyAgreementWrappingInterface;
28
use Jose\Component\Encryption\Algorithm\KeyEncryption\KeyEncryptionInterface;
29
use Jose\Component\Encryption\Algorithm\KeyEncryption\KeyWrappingInterface;
30
use Jose\Component\Encryption\Algorithm\KeyEncryptionAlgorithmInterface;
31
use Jose\Component\Encryption\Serializer\JWESerializerManager;
32
33
/**
34
 * Class JWELoader.
35
 */
36
final class JWELoader
37
{
38
    /**
39
     * @var HeaderCheckerManager
40
     */
41
    private $headerCheckerManager;
42
43
    /**
44
     * @var AlgorithmManager
45
     */
46
    private $keyEncryptionAlgorithmManager;
47
48
    /**
49
     * @var AlgorithmManager
50
     */
51
    private $contentEncryptionAlgorithmManager;
52
53
    /**
54
     * @var CompressionMethodManager
55
     */
56
    private $compressionMethodManager;
57
58
    /**
59
     * @var JWESerializerManager
60
     */
61
    private $serializerManager;
62
63
    /**
64
     * JWELoader constructor.
65
     *
66
     * @param AlgorithmManager         $keyEncryptionAlgorithmManager
67
     * @param AlgorithmManager         $contentEncryptionAlgorithmManager
68
     * @param CompressionMethodManager $compressionMethodManager
69
     * @param HeaderCheckerManager     $headerCheckerManager
70
     * @param JWESerializerManager     $serializerManager
71
     */
72
    public function __construct(AlgorithmManager $keyEncryptionAlgorithmManager, AlgorithmManager $contentEncryptionAlgorithmManager, CompressionMethodManager $compressionMethodManager, HeaderCheckerManager $headerCheckerManager, JWESerializerManager $serializerManager)
73
    {
74
        $this->keyEncryptionAlgorithmManager = $keyEncryptionAlgorithmManager;
75
        $this->contentEncryptionAlgorithmManager = $contentEncryptionAlgorithmManager;
76
        $this->compressionMethodManager = $compressionMethodManager;
77
        $this->headerCheckerManager = $headerCheckerManager;
78
        $this->serializerManager = $serializerManager;
79
    }
80
81
    /**
82
     * @param string      $input
83
     * @param string|null $serializer
84
     *
85
     * @return JWE
86
     */
87
    public function load(string $input, ?string &$serializer = null): JWE
88
    {
89
        return $this->serializerManager->unserialize($input, $serializer);
90
    }
91
92
    /**
93
     * @return AlgorithmManager
94
     */
95
    public function getKeyEncryptionAlgorithmManager(): AlgorithmManager
96
    {
97
        return $this->keyEncryptionAlgorithmManager;
98
    }
99
100
    /**
101
     * @return AlgorithmManager
102
     */
103
    public function getContentEncryptionAlgorithmManager(): AlgorithmManager
104
    {
105
        return $this->contentEncryptionAlgorithmManager;
106
    }
107
108
    /**
109
     * @return CompressionMethodManager
110
     */
111
    public function getCompressionMethodManager(): CompressionMethodManager
112
    {
113
        return $this->compressionMethodManager;
114
    }
115
116
    /**
117
     * @param JWE      $jwe            A JWE object to decrypt
118
     * @param JWK      $jwk            The key used to decrypt the input
119
     * @param null|int $recipientIndex If the JWE has been decrypted, an integer that represents the ID of the recipient is set
120
     *
121
     * @return JWE
122
     */
123
    public function decryptUsingKey(JWE $jwe, JWK $jwk, ?int &$recipientIndex = null): JWE
124
    {
125
        $jwkset = JWKSet::createFromKeys([$jwk]);
126
        $jwe = $this->decryptUsingKeySet($jwe, $jwkset, $recipientIndex);
127
128
        return $jwe;
129
    }
130
131
    /**
132
     * @param JWE      $jwe            A JWE object to decrypt
133
     * @param JWKSet   $jwkset         The key set used to decrypt the input
134
     * @param null|int $recipientIndex If the JWE has been decrypted, an integer that represents the ID of the recipient is set
135
     *
136
     * @return JWE
137
     */
138
    public function decryptUsingKeySet(JWE $jwe, JWKSet $jwkset, ?int &$recipientIndex = null): JWE
139
    {
140
        $this->checkJWKSet($jwkset);
141
        $this->checkPayload($jwe);
142
        $this->checkRecipients($jwe);
143
144
        $nb_recipients = $jwe->countRecipients();
145
146
        for ($i = 0; $i < $nb_recipients; ++$i) {
147
            try {
148
                $this->headerCheckerManager->check($jwe, $i);
149
            } catch (\Exception $e) {
150
                continue;
151
            }
152
            $plaintext = $this->decryptRecipientKey($jwe, $jwkset, $i);
153
            if (null !== $plaintext) {
154
                $recipientIndex = $i;
155
156
                return $jwe->withPayload($plaintext);
157
            }
158
        }
159
160
        throw new \InvalidArgumentException('Unable to decrypt the JWE.');
161
    }
162
163
    /**
164
     * @param JWE    $jwe
165
     * @param JWKSet $jwkset
166
     * @param int    $i
167
     *
168
     * @return string|null
169
     */
170
    private function decryptRecipientKey(JWE $jwe, JWKSet $jwkset, int $i): ?string
171
    {
172
        $recipient = $jwe->getRecipient($i);
173
        $complete_headers = array_merge($jwe->getSharedProtectedHeaders(), $jwe->getSharedHeaders(), $recipient->getHeaders());
174
        $this->checkCompleteHeader($complete_headers);
175
176
        $key_encryption_algorithm = $this->getKeyEncryptionAlgorithm($complete_headers);
177
        $content_encryption_algorithm = $this->getContentEncryptionAlgorithm($complete_headers);
178
179
        foreach ($jwkset as $jwk) {
180
            try {
181
                KeyChecker::checkKeyUsage($jwk, 'decryption');
0 ignored issues
show
Bug introduced by
It seems like $jwk defined by $jwk on line 179 can be null; however, Jose\Component\Core\Util...hecker::checkKeyUsage() does not accept null, maybe add an additional type check?

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:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
182
                if ('dir' !== $key_encryption_algorithm->name()) {
183
                    KeyChecker::checkKeyAlgorithm($jwk, $key_encryption_algorithm->name());
0 ignored issues
show
Bug introduced by
It seems like $jwk defined by $jwk on line 179 can be null; however, Jose\Component\Core\Util...er::checkKeyAlgorithm() does not accept null, maybe add an additional type check?

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:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
184
                } else {
185
                    KeyChecker::checkKeyAlgorithm($jwk, $content_encryption_algorithm->name());
0 ignored issues
show
Bug introduced by
It seems like $jwk defined by $jwk on line 179 can be null; however, Jose\Component\Core\Util...er::checkKeyAlgorithm() does not accept null, maybe add an additional type check?

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:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
186
                }
187
                $cek = $this->decryptCEK($key_encryption_algorithm, $content_encryption_algorithm, $jwk, $recipient, $complete_headers);
0 ignored issues
show
Bug introduced by
It seems like $jwk defined by $jwk on line 179 can be null; however, Jose\Component\Encryption\JWELoader::decryptCEK() does not accept null, maybe add an additional type check?

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:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
188
                if (null !== $cek) {
189
                    return $this->decryptPayload($jwe, $cek, $content_encryption_algorithm, $complete_headers);
190
                }
191
            } catch (\Exception $e) {
192
                //We do nothing, we continue with other keys
193
                continue;
194
            }
195
        }
196
197
        return null;
198
    }
199
200
    /**
201
     * @param JWE $jwe
202
     */
203
    private function checkRecipients(JWE $jwe)
204
    {
205
        if (0 === $jwe->countRecipients()) {
206
            throw new \InvalidArgumentException('The JWE does not contain any recipient.');
207
        }
208
    }
209
210
    /**
211
     * @param JWE $jwe
212
     */
213
    private function checkPayload(JWE $jwe)
214
    {
215
        if (null !== $jwe->getPayload()) {
216
            throw new \InvalidArgumentException('The JWE is already decrypted.');
217
        }
218
    }
219
220
    /**
221
     * @param JWKSet $jwkset
222
     */
223
    private function checkJWKSet(JWKSet $jwkset)
224
    {
225
        if (0 === $jwkset->count()) {
226
            throw new \InvalidArgumentException('No key in the key set.');
227
        }
228
    }
229
230
    /**
231
     * @param AlgorithmInterface                  $key_encryption_algorithm
232
     * @param ContentEncryptionAlgorithmInterface $content_encryption_algorithm
233
     * @param JWK                                 $key
234
     * @param Recipient                           $recipient
235
     * @param array                               $complete_headers
236
     *
237
     * @return null|string
238
     */
239
    private function decryptCEK(AlgorithmInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, JWK $key, Recipient $recipient, array $complete_headers): ?string
240
    {
241
        if ($key_encryption_algorithm instanceof DirectEncryptionInterface) {
242
            return $key_encryption_algorithm->getCEK($key);
243
        } elseif ($key_encryption_algorithm instanceof KeyAgreementInterface) {
244
            return $key_encryption_algorithm->getAgreementKey($content_encryption_algorithm->getCEKSize(), $content_encryption_algorithm->name(), $key, $complete_headers);
245
        } elseif ($key_encryption_algorithm instanceof KeyAgreementWrappingInterface) {
246
            return $key_encryption_algorithm->unwrapAgreementKey($key, $recipient->getEncryptedKey(), $content_encryption_algorithm->getCEKSize(), $complete_headers);
247
        } elseif ($key_encryption_algorithm instanceof KeyEncryptionInterface) {
248
            return $key_encryption_algorithm->decryptKey($key, $recipient->getEncryptedKey(), $complete_headers);
249
        } elseif ($key_encryption_algorithm instanceof KeyWrappingInterface) {
250
            return $key_encryption_algorithm->unwrapKey($key, $recipient->getEncryptedKey(), $complete_headers);
251
        } else {
252
            throw new \InvalidArgumentException('Unsupported CEK generation');
253
        }
254
    }
255
256
    /**
257
     * @param JWE                                 $jwe
258
     * @param string                              $cek
259
     * @param ContentEncryptionAlgorithmInterface $content_encryption_algorithm
260
     * @param array                               $complete_headers
261
     *
262
     * @return string
263
     */
264
    private function decryptPayload(JWE $jwe, string $cek, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, array $complete_headers): string
265
    {
266
        $payload = $content_encryption_algorithm->decryptContent($jwe->getCiphertext(), $cek, $jwe->getIV(), null === $jwe->getAAD() ? null : Base64Url::encode($jwe->getAAD()), $jwe->getEncodedSharedProtectedHeaders(), $jwe->getTag());
267
        if (null === $payload) {
268
            throw new \RuntimeException('Unable to decrypt the JWE.');
269
        }
270
271
        return $this->decompressIfNeeded($payload, $complete_headers);
272
    }
273
274
    /**
275
     * @param string $payload
276
     * @param array  $complete_headers
277
     *
278
     * @return string
279
     */
280
    private function decompressIfNeeded(string $payload, array $complete_headers): string
281
    {
282
        if (array_key_exists('zip', $complete_headers)) {
283
            $compression_method = $this->compressionMethodManager->get($complete_headers['zip']);
284
            $payload = $compression_method->uncompress($payload);
285
            if (!is_string($payload)) {
286
                throw new \InvalidArgumentException('Decompression failed');
287
            }
288
        }
289
290
        return $payload;
291
    }
292
293
    /**
294
     * @param array $complete_headers
295
     *
296
     * @throws \InvalidArgumentException
297
     */
298
    private function checkCompleteHeader(array $complete_headers)
299
    {
300
        foreach (['enc', 'alg'] as $key) {
301
            if (!array_key_exists($key, $complete_headers)) {
302
                throw new \InvalidArgumentException(sprintf("Parameters '%s' is missing.", $key));
303
            }
304
        }
305
    }
306
307
    /**
308
     * @param array $complete_headers
309
     *
310
     * @return KeyEncryptionAlgorithmInterface
311
     */
312
    private function getKeyEncryptionAlgorithm(array $complete_headers): KeyEncryptionAlgorithmInterface
313
    {
314
        $key_encryption_algorithm = $this->keyEncryptionAlgorithmManager->get($complete_headers['alg']);
315
        if (!$key_encryption_algorithm instanceof KeyEncryptionAlgorithmInterface) {
316
            throw new \InvalidArgumentException(sprintf('The key encryption algorithm "%s" is not supported or does not implement KeyEncryptionAlgorithmInterface.', $complete_headers['alg']));
317
        }
318
319
        return $key_encryption_algorithm;
320
    }
321
322
    /**
323
     * @param array $complete_headers
324
     *
325
     * @return ContentEncryptionAlgorithmInterface
326
     */
327
    private function getContentEncryptionAlgorithm(array $complete_headers): ContentEncryptionAlgorithmInterface
328
    {
329
        $content_encryption_algorithm = $this->contentEncryptionAlgorithmManager->get($complete_headers['enc']);
330
        if (!$content_encryption_algorithm instanceof ContentEncryptionAlgorithmInterface) {
331
            throw new \InvalidArgumentException(sprintf('The key encryption algorithm "%s" is not supported or does not implement ContentEncryptionInterface.', $complete_headers['enc']));
332
        }
333
334
        return $content_encryption_algorithm;
335
    }
336
}
337