Completed
Branch develop (8ddc4a)
by Florent
02:46
created

Encrypter::addRecipient()   B

Complexity

Conditions 4
Paths 6

Size

Total Lines 67
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 12
Bugs 4 Features 1
Metric Value
c 12
b 4
f 1
dl 0
loc 67
rs 8.8076
cc 4
eloc 39
nc 6
nop 3

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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,
0 ignored issues
show
Bug introduced by
It seems like $content_encryption_algorithm defined by $this->getContentEncryptionAlgorithm($jwe) on line 74 can be null; however, Jose\Encrypter::determineCEK() 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...
89
            $key_management_mode,
90
            $additional_headers
91
        );
92
93
        for($i = 0; $i < $jwe->countRecipients(); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
Consider avoiding function calls on each iteration of the for loop.

If you have a function call in the test part of a for loop, this function is executed on each iteration. Often such a function, can be moved to the initialization part and be cached.

// count() is called on each iteration
for ($i=0; $i < count($collection); $i++) { }

// count() is only called once
for ($i=0, $c=count($collection); $i<$c; $i++) { }
Loading history...
94
95
            $this->processRecipient(
96
                $jwe,
97
                $jwe->getRecipient($i),
98
                $cek,
99
                $content_encryption_algorithm,
0 ignored issues
show
Bug introduced by
It seems like $content_encryption_algorithm defined by $this->getContentEncryptionAlgorithm($jwe) on line 74 can be null; however, Jose\Encrypter::processRecipient() 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...
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);
0 ignored issues
show
Bug introduced by
It seems like $content_encryption_algorithm defined by $this->getContentEncryptionAlgorithm($jwe) on line 74 can be null; however, Jose\Encrypter::encryptJWE() 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...
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,
0 ignored issues
show
Bug introduced by
It seems like $key_encryption_algorithm defined by $this->findKeyEncryption...ithm($complete_headers) on line 141 can be null; however, Jose\Encrypter::getEncryptedKey() 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...
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,
0 ignored issues
show
Unused Code introduced by
The parameter $jwe is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
171
                                  ContentEncryptionAlgorithmInterface $content_encryption_algorithm,
172
                                  $key_management_mode,
173
                                  array &$additional_headers
0 ignored issues
show
Unused Code introduced by
The parameter $additional_headers is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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++) {
0 ignored issues
show
Performance Best Practice introduced by
Consider avoiding function calls on each iteration of the for loop.

If you have a function call in the test part of a for loop, this function is executed on each iteration. Often such a function, can be moved to the initialization part and be cached.

// count() is called on each iteration
for ($i=0; $i < count($collection); $i++) { }

// count() is only called once
for ($i=0, $c=count($collection); $i<$c; $i++) { }
Loading history...
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);
0 ignored issues
show
Bug introduced by
It seems like $key_encryption_algorithm defined by $this->findKeyEncryption...ithm($complete_headers) on line 302 can be null; however, Jose\Encrypter::checkKeys() 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...
Bug introduced by
It seems like $content_encryption_algorithm defined by $this->findContentEncryp...ithm($complete_headers) on line 305 can be null; however, Jose\Encrypter::checkKeys() 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...
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,
0 ignored issues
show
Bug introduced by
It seems like $key_encryption_algorithm defined by $this->findKeyEncryption...ithm($complete_headers) on line 302 can be null; however, Jose\Encrypter::getCEK() 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...
329
                $content_encryption_algorithm,
0 ignored issues
show
Bug introduced by
It seems like $content_encryption_algorithm defined by $this->findContentEncryp...ithm($complete_headers) on line 305 can be null; however, Jose\Encrypter::getCEK() 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...
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,
0 ignored issues
show
Bug introduced by
It seems like $key_encryption_algorithm defined by $this->findKeyEncryption...ithm($complete_headers) on line 302 can be null; however, Jose\Encrypter::computeRecipient() 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...
345
            $content_encryption_algorithm,
0 ignored issues
show
Bug introduced by
It seems like $content_encryption_algorithm defined by $this->findContentEncryp...ithm($complete_headers) on line 305 can be null; however, Jose\Encrypter::computeRecipient() 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...
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);
0 ignored issues
show
Bug introduced by
It seems like $content_encryption_algorithm defined by $this->findContentEncryp...ithm($complete_headers) on line 305 can be null; however, Jose\Encrypter::encryptJWE() 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...
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)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
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