Failed Conditions
Push — v7 ( d6049e...ebb162 )
by Florent
02:07
created

JWELoader   C

Complexity

Total Complexity 39

Size/Duplication

Total Lines 297
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 17

Importance

Changes 0
Metric Value
wmc 39
lcom 2
cbo 17
dl 0
loc 297
rs 6.5773
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
A load() 0 4 1
A decryptUsingKey() 0 9 1
A decryptUsingKeySet() 0 19 3
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
A getKeyEncryptionAlgorithmManager() 0 4 1
A getContentEncryptionAlgorithmManager() 0 4 1
A getCompressionMethodManager() 0 4 1
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
     *
84
     * @return JWE
85
     */
86
    public function load(string $input): JWE
87
    {
88
        return $this->serializerManager->unserialize($input);
89
    }
90
91
    /**
92
     * @return AlgorithmManager
93
     */
94
    public function getKeyEncryptionAlgorithmManager(): AlgorithmManager
95
    {
96
        return $this->keyEncryptionAlgorithmManager;
97
    }
98
99
    /**
100
     * @return AlgorithmManager
101
     */
102
    public function getContentEncryptionAlgorithmManager(): AlgorithmManager
103
    {
104
        return $this->contentEncryptionAlgorithmManager;
105
    }
106
107
    /**
108
     * @return CompressionMethodManager
109
     */
110
    public function getCompressionMethodManager(): CompressionMethodManager
111
    {
112
        return $this->compressionMethodManager;
113
    }
114
115
    /**
116
     * @param JWE      $jwe            A JWE object to decrypt
117
     * @param JWK      $jwk            The key used to decrypt the input
118
     * @param null|int $recipientIndex If the JWE has been decrypted, an integer that represents the ID of the recipient is set
119
     *
120
     * @return JWE
121
     */
122
    public function decryptUsingKey(JWE $jwe, JWK $jwk, ?int &$recipientIndex = null): JWE
123
    {
124
        $jwkset = JWKSet::createFromKeys([$jwk]);
125
126
        $jwe = $this->decryptUsingKeySet($jwe, $jwkset, $recipientIndex);
127
        $this->headerCheckerManager->check($jwe, $recipientIndex);
128
129
        return $jwe;
130
    }
131
132
    /**
133
     * @param JWE      $jwe            A JWE object to decrypt
134
     * @param JWKSet   $jwkset         The key set used to decrypt the input
135
     * @param null|int $recipientIndex If the JWE has been decrypted, an integer that represents the ID of the recipient is set
136
     *
137
     * @return JWE
138
     */
139
    public function decryptUsingKeySet(JWE $jwe, JWKSet $jwkset, ?int &$recipientIndex = null): JWE
140
    {
141
        $this->checkJWKSet($jwkset);
142
        $this->checkPayload($jwe);
143
        $this->checkRecipients($jwe);
144
145
        $nb_recipients = $jwe->countRecipients();
146
147
        for ($i = 0; $i < $nb_recipients; ++$i) {
148
            $plaintext = $this->decryptRecipientKey($jwe, $jwkset, $i);
149
            if (null !== $plaintext) {
150
                $recipientIndex = $i;
151
152
                return $jwe->withPayload($plaintext);
153
            }
154
        }
155
156
        throw new \InvalidArgumentException('Unable to decrypt the JWE.');
157
    }
158
159
    /**
160
     * @param JWE    $jwe
161
     * @param JWKSet $jwkset
162
     * @param int    $i
163
     *
164
     * @return string|null
165
     */
166
    private function decryptRecipientKey(JWE $jwe, JWKSet $jwkset, int $i): ?string
167
    {
168
        $recipient = $jwe->getRecipient($i);
169
        $complete_headers = array_merge($jwe->getSharedProtectedHeaders(), $jwe->getSharedHeaders(), $recipient->getHeaders());
170
        $this->checkCompleteHeader($complete_headers);
171
172
        $key_encryption_algorithm = $this->getKeyEncryptionAlgorithm($complete_headers);
173
        $content_encryption_algorithm = $this->getContentEncryptionAlgorithm($complete_headers);
174
175
        foreach ($jwkset as $jwk) {
176
            try {
177
                KeyChecker::checkKeyUsage($jwk, 'decryption');
0 ignored issues
show
Bug introduced by
It seems like $jwk defined by $jwk on line 175 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...
178
                if ('dir' !== $key_encryption_algorithm->name()) {
179
                    KeyChecker::checkKeyAlgorithm($jwk, $key_encryption_algorithm->name());
0 ignored issues
show
Bug introduced by
It seems like $jwk defined by $jwk on line 175 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...
180
                } else {
181
                    KeyChecker::checkKeyAlgorithm($jwk, $content_encryption_algorithm->name());
0 ignored issues
show
Bug introduced by
It seems like $jwk defined by $jwk on line 175 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...
182
                }
183
                $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 175 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...
184
                if (null !== $cek) {
185
                    return $this->decryptPayload($jwe, $cek, $content_encryption_algorithm, $complete_headers);
186
                }
187
            } catch (\Exception $e) {
188
                //We do nothing, we continue with other keys
189
                continue;
190
            }
191
        }
192
193
        return null;
194
    }
195
196
    /**
197
     * @param JWE $jwe
198
     */
199
    private function checkRecipients(JWE $jwe)
200
    {
201
        if (0 === $jwe->countRecipients()) {
202
            throw new \InvalidArgumentException('The JWE does not contain any recipient.');
203
        }
204
    }
205
206
    /**
207
     * @param JWE $jwe
208
     */
209
    private function checkPayload(JWE $jwe)
210
    {
211
        if (null !== $jwe->getPayload()) {
212
            throw new \InvalidArgumentException('The JWE is already decrypted.');
213
        }
214
    }
215
216
    /**
217
     * @param JWKSet $jwkset
218
     */
219
    private function checkJWKSet(JWKSet $jwkset)
220
    {
221
        if (0 === $jwkset->count()) {
222
            throw new \InvalidArgumentException('No key in the key set.');
223
        }
224
    }
225
226
    /**
227
     * @param AlgorithmInterface                  $key_encryption_algorithm
228
     * @param ContentEncryptionAlgorithmInterface $content_encryption_algorithm
229
     * @param JWK                                 $key
230
     * @param Recipient                           $recipient
231
     * @param array                               $complete_headers
232
     *
233
     * @return null|string
234
     */
235
    private function decryptCEK(AlgorithmInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, JWK $key, Recipient $recipient, array $complete_headers): ?string
236
    {
237
        if ($key_encryption_algorithm instanceof DirectEncryptionInterface) {
238
            return $key_encryption_algorithm->getCEK($key);
239
        } elseif ($key_encryption_algorithm instanceof KeyAgreementInterface) {
240
            return $key_encryption_algorithm->getAgreementKey($content_encryption_algorithm->getCEKSize(), $content_encryption_algorithm->name(), $key, $complete_headers);
241
        } elseif ($key_encryption_algorithm instanceof KeyAgreementWrappingInterface) {
242
            return $key_encryption_algorithm->unwrapAgreementKey($key, $recipient->getEncryptedKey(), $content_encryption_algorithm->getCEKSize(), $complete_headers);
243
        } elseif ($key_encryption_algorithm instanceof KeyEncryptionInterface) {
244
            return $key_encryption_algorithm->decryptKey($key, $recipient->getEncryptedKey(), $complete_headers);
245
        } elseif ($key_encryption_algorithm instanceof KeyWrappingInterface) {
246
            return $key_encryption_algorithm->unwrapKey($key, $recipient->getEncryptedKey(), $complete_headers);
247
        } else {
248
            throw new \InvalidArgumentException('Unsupported CEK generation');
249
        }
250
    }
251
252
    /**
253
     * @param JWE                                 $jwe
254
     * @param string                              $cek
255
     * @param ContentEncryptionAlgorithmInterface $content_encryption_algorithm
256
     * @param array                               $complete_headers
257
     *
258
     * @return string
259
     */
260
    private function decryptPayload(JWE $jwe, string $cek, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, array $complete_headers): string
261
    {
262
        $payload = $content_encryption_algorithm->decryptContent($jwe->getCiphertext(), $cek, $jwe->getIV(), null === $jwe->getAAD() ? null : Base64Url::encode($jwe->getAAD()), $jwe->getEncodedSharedProtectedHeaders(), $jwe->getTag());
263
        if (null === $payload) {
264
            throw new \RuntimeException('Unable to decrypt the JWE.');
265
        }
266
267
        return $this->decompressIfNeeded($payload, $complete_headers);
268
    }
269
270
    /**
271
     * @param string $payload
272
     * @param array  $complete_headers
273
     *
274
     * @return string
275
     */
276
    private function decompressIfNeeded(string $payload, array $complete_headers): string
277
    {
278
        if (array_key_exists('zip', $complete_headers)) {
279
            $compression_method = $this->compressionMethodManager->get($complete_headers['zip']);
280
            $payload = $compression_method->uncompress($payload);
281
            if (!is_string($payload)) {
282
                throw new \InvalidArgumentException('Decompression failed');
283
            }
284
        }
285
286
        return $payload;
287
    }
288
289
    /**
290
     * @param array $complete_headers
291
     *
292
     * @throws \InvalidArgumentException
293
     */
294
    private function checkCompleteHeader(array $complete_headers)
295
    {
296
        foreach (['enc', 'alg'] as $key) {
297
            if (!array_key_exists($key, $complete_headers)) {
298
                throw new \InvalidArgumentException(sprintf("Parameters '%s' is missing.", $key));
299
            }
300
        }
301
    }
302
303
    /**
304
     * @param array $complete_headers
305
     *
306
     * @return KeyEncryptionAlgorithmInterface
307
     */
308
    private function getKeyEncryptionAlgorithm(array $complete_headers): KeyEncryptionAlgorithmInterface
309
    {
310
        $key_encryption_algorithm = $this->keyEncryptionAlgorithmManager->get($complete_headers['alg']);
311
        if (!$key_encryption_algorithm instanceof KeyEncryptionAlgorithmInterface) {
312
            throw new \InvalidArgumentException(sprintf('The key encryption algorithm "%s" is not supported or does not implement KeyEncryptionAlgorithmInterface.', $complete_headers['alg']));
313
        }
314
315
        return $key_encryption_algorithm;
316
    }
317
318
    /**
319
     * @param array $complete_headers
320
     *
321
     * @return ContentEncryptionAlgorithmInterface
322
     */
323
    private function getContentEncryptionAlgorithm(array $complete_headers): ContentEncryptionAlgorithmInterface
324
    {
325
        $content_encryption_algorithm = $this->contentEncryptionAlgorithmManager->get($complete_headers['enc']);
326
        if (!$content_encryption_algorithm instanceof ContentEncryptionAlgorithmInterface) {
327
            throw new \InvalidArgumentException(sprintf('The key encryption algorithm "%s" is not supported or does not implement ContentEncryptionInterface.', $complete_headers['enc']));
328
        }
329
330
        return $content_encryption_algorithm;
331
    }
332
}
333