Completed
Push — v2.0.x ( 7a58b6 )
by Florent
24:58
created

Encrypter::getCEK()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 14
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 14
rs 8.8571
cc 5
eloc 11
nc 5
nop 6
1
<?php
2
3
/*
4
 * The MIT License (MIT)
5
 *
6
 * Copyright (c) 2014-2015 Spomky-Labs
7
 *
8
 * This software may be modified and distributed under the terms
9
 * of the MIT license.  See the LICENSE file for details.
10
 */
11
12
namespace Jose;
13
14
use Base64Url\Base64Url;
15
use Jose\Algorithm\ContentEncryptionAlgorithmInterface;
16
use Jose\Algorithm\EncryptionInterface;
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\KeyEncryptionAlgorithmInterface;
23
use Jose\Behaviour\HasCompressionManager;
24
use Jose\Behaviour\HasJWAManager;
25
use Jose\Behaviour\HasKeyChecker;
26
use Jose\Compression\CompressionManagerInterface;
27
use Jose\Object\JWEInterface;
28
use Jose\Object\JWKInterface;
29
use Jose\Object\Recipient;
30
use Jose\Util\Converter;
31
32
/**
33
 */
34
final class Encrypter implements EncrypterInterface
35
{
36
    use HasKeyChecker;
37
    use HasJWAManager;
38
    use HasCompressionManager;
39
40
    /**
41
     * Encrypter constructor.
42
     *
43
     * @param \Jose\Algorithm\JWAManagerInterface            $jwa_manager
44
     * @param \Jose\Compression\CompressionManagerInterface  $compression_manager
45
     */
46
    public function __construct(
47
        JWAManagerInterface $jwa_manager,
48
        CompressionManagerInterface $compression_manager)
49
    {
50
        $this->setJWAManager($jwa_manager);
51
        $this->setCompressionManager($compression_manager);
52
    }
53
54
    /**
55
     * @param array|\Jose\Object\JWKInterface|\Jose\Object\JWKSetInterface|\Jose\Object\JWTInterface|string $input
56
     * @param array                                                                                         $instructions
57
     * @param array                                                                                         $shared_protected_header
58
     * @param array                                                                                         $shared_unprotected_header
59
     * @param string                                                                                        $serialization
60
     * @param null                                                                                          $aad
61
     *
62
     * @return string
63
     */
64
    /*public function encrypt($input, array $instructions, $serialization, array $shared_protected_header = [], array $shared_unprotected_header = [], $aad = null)
65
    {
66
        $additional_header = [];
67
        $this->checkSerializationMode($serialization);
68
        $input = $this->getPayloadConverter()->convertPayloadToString($additional_header, $input);
69
        $this->checkInstructions($instructions, $serialization);
70
        if (!empty($shared_unprotected_header) && JSONSerializationModes::JSON_COMPACT_SERIALIZATION === $serialization) {
71
            throw new \InvalidArgumentException('Cannot create Compact Json Serialization representation: shared unprotected header cannot be kept');
72
        }
73
        if (!empty($aad) && JSONSerializationModes::JSON_COMPACT_SERIALIZATION === $serialization) {
74
            throw new \InvalidArgumentException('Cannot create Compact Json Serialization representation: AAD cannot be kept');
75
        }
76
77
        $protected_header = array_merge($shared_protected_header, $additional_header);
78
79
        // We check if key management mode is OK
80
        $key_management_mode = $this->getKeyManagementMode($instructions, $protected_header, $shared_unprotected_header);
81
82
        // We get the content encryption algorithm
83
        $content_encryption_algorithm = $this->getContentEncryptionAlgorithm($instructions, $protected_header, $shared_unprotected_header);
84
85
        // CEK
86
        $cek = $this->determineCEK($key_management_mode, $instructions, $protected_header, $shared_unprotected_header, $content_encryption_algorithm->getCEKSize());
87
88
        $recipients = ['recipients' => []];
89
        foreach ($instructions as $instruction) {
90
            $recipients['recipients'][] = $this->computeRecipient($instruction, $protected_header, $shared_unprotected_header, $cek, $content_encryption_algorithm->getCEKSize(), $serialization);
91
        }
92
93
        // We prepare the payload and compress it if required
94
        $compression_method = $this->findCompressionMethod($instructions, $protected_header, $shared_unprotected_header);
95
        $this->compressPayload($input, $compression_method);
96
97
        // We compute the initialization vector
98
        $iv = null;
99
        if (null !== $iv_size = $content_encryption_algorithm->getIVSize()) {
100
            $iv = $this->createIV($iv_size);
101
        }
102
103
        // JWT Shared protected header
104
        $jwt_shared_protected_header = Base64Url::encode(json_encode($protected_header));
105
106
        // We encrypt the payload and get the tag
107
        $tag = null;
108
        $ciphertext = $content_encryption_algorithm->encryptContent($input, $cek, $iv, $aad, $jwt_shared_protected_header, $tag);
109
110
        // JWT Ciphertext
111
        $jwt_ciphertext = Base64Url::encode($ciphertext);
112
113
        // JWT AAD
114
        $jwt_aad = null === $aad ? null : Base64Url::encode($aad);
115
116
        // JWT Tag
117
        $jwt_tag = null === $tag ? null : Base64Url::encode($tag);
118
119
        // JWT IV
120
        $jwt_iv = null === $iv ? '' : Base64Url::encode($iv);
121
122
        $values = [
123
            'ciphertext'  => $jwt_ciphertext,
124
            'protected'   => $jwt_shared_protected_header,
125
            'unprotected' => $shared_unprotected_header,
126
            'iv'          => $jwt_iv,
127
            'tag'         => $jwt_tag,
128
            'aad'         => $jwt_aad,
129
        ];
130
        foreach ($values as $key => $value) {
131
            if (!empty($value)) {
132
                $recipients[$key] = $value;
133
            }
134
        }
135
136
        return Converter::convert($recipients, $serialization);
137
    }*/
138
139
    /**
140
     * @param \Jose\Object\EncryptionInstructionInterface $instruction
141
     * @param                                             $protected_header
142
     * @param                                             $unprotected_header
143
     * @param string                                      $cek
144
     * @param int                                         $cek_size
145
     * @param string                                      $serialization
146
     *
147
     * @throws \Exception
148
     *
149
     * @return array
150
     */
151
    /*private function computeRecipient(EncryptionInstructionInterface $instruction, &$protected_header, $unprotected_header, $cek, $cek_size, $serialization)
152
    {
153
        if (!$this->checkKeyUsage($instruction->getRecipientKey(), 'encryption')) {
154
            throw new \InvalidArgumentException('Key cannot be used to encrypt');
155
        }
156
157
        $recipient_header = $instruction->getRecipientUnprotectedHeader();
158
        $complete_header = array_merge($protected_header, $unprotected_header, $recipient_header);
159
160
        $key_encryption_algorithm = $this->getKeyEncryptionAlgorithm($complete_header);
161
162
        if (!$this->checkKeyAlgorithm($instruction->getRecipientKey(), $key_encryption_algorithm->getAlgorithmName())) {
163
            throw new \InvalidArgumentException(sprintf('Key is only allowed for algorithm "%s".', $instruction->getRecipientKey()->get('alg')));
164
        }
165
166
        $jwt_cek = null;
167
        if ($key_encryption_algorithm instanceof KeyEncryptionInterface) {
168
            $jwt_cek = Base64Url::encode($key_encryption_algorithm->encryptKey($instruction->getRecipientKey(), $cek, $protected_header));
169
        } elseif ($key_encryption_algorithm instanceof KeyAgreementWrappingInterface) {
170
            if (null === $instruction->getSenderKey()) {
171
                throw new \RuntimeException('The sender key must be set using Key Agreement or Key Agreement with Wrapping algorithms.');
172
            }
173
            $additional_header_values = [];
174
            $jwt_cek = Base64Url::encode($key_encryption_algorithm->wrapAgreementKey($instruction->getSenderKey(), $instruction->getRecipientKey(), $cek, $cek_size, $complete_header, $additional_header_values));
175
            $this->updateHeader($additional_header_values, $protected_header, $recipient_header, $serialization);
176
        } elseif ($key_encryption_algorithm instanceof KeyAgreementInterface) {
177
            $additional_header_values = [];
178
            $jwt_cek = Base64Url::encode($key_encryption_algorithm->getAgreementKey($cek_size, $instruction->getSenderKey(), $instruction->getRecipientKey(), $complete_header, $additional_header_values));
179
            $this->updateHeader($additional_header_values, $protected_header, $recipient_header, $serialization);
180
        }
181
182
        $result = [];
183
        if (null !== $jwt_cek) {
184
            $result['encrypted_key'] = $jwt_cek;
185
        }
186
        if (!empty($recipient_header)) {
187
            $result['header'] = $recipient_header;
188
        }
189
190
        return $result;
191
    }*/
192
193
    /**
194
     * @param array  $additional_header_values
195
     * @param array  $protected_header
196
     * @param array  $recipient_header
197
     * @param string $serialization
198
     */
199
    /*private function updateHeader(array $additional_header_values, array &$protected_header, array &$recipient_header, $serialization)
200
    {
201
        if (JSONSerializationModes::JSON_COMPACT_SERIALIZATION === $serialization) {
202
            $protected_header = array_merge($protected_header, $additional_header_values);
203
        } else {
204
            $recipient_header = array_merge($recipient_header, $additional_header_values);
205
        }
206
    }*/
207
208
    /**
209
     * @param \Jose\Object\EncryptionInstructionInterface[] $instructions
210
     * @param array                                         $protected_header
211
     * @param array                                         $unprotected_header
212
     *
213
     * @return string
214
     */
215
    /*private function getKeyManagementMode(array $instructions, $protected_header, $unprotected_header)
216
    {
217
        $mode = null;
218
        foreach ($instructions as $instruction) {
219
            $recipient_header = $instruction->getRecipientUnprotectedHeader();
220
            $complete_header = array_merge($protected_header, $unprotected_header, $recipient_header);
221
222
            $temp = $this->getKeyManagementMode2($complete_header);
223
            if (null === $mode) {
224
                $mode = $temp;
225
            } else {
226
                if (!$this->areKeyManagementModeAuthorized($mode, $temp)) {
227
                    throw new \RuntimeException('Foreign key management mode forbidden.');
228
                }
229
            }
230
        }
231
232
        return $mode;
233
    }*/
234
235
    /**
236
     * @param string $mode1
237
     * @param string $mode2
238
     *
239
     * @return bool
240
     */
241
    /*private function areKeyManagementModeAuthorized($mode1, $mode2)
242
    {
243
        switch ($mode1.$mode2) {
244
            case 'encenc':
245
            case 'encwrap':
246
            case 'wrapenc':
247
            case 'wrapwrap':
248
            case 'agreeagree':
249
            case 'dirdir':
250
                return true;
251
            default:
252
                return false;
253
        }
254
    }*/
255
256
    /**
257
     * @param array $complete_header
258
     *
259
     * @return string
260
     */
261
    /*private function getKeyManagementMode2($complete_header)
262
    {
263
        $key_encryption_algorithm = $this->getKeyEncryptionAlgorithm($complete_header);
264
265
        if ($key_encryption_algorithm instanceof KeyEncryptionInterface) {
266
            return 'enc';
267
        } elseif ($key_encryption_algorithm instanceof KeyAgreementWrappingInterface) {
268
            return 'wrap';
269
        } elseif ($key_encryption_algorithm instanceof KeyAgreementInterface) {
270
            return 'agree';
271
        } elseif ($key_encryption_algorithm instanceof DirectEncryptionInterface) {
272
            return 'dir';
273
        } else {
274
            throw new \RuntimeException('Unable to get key management mode.');
275
        }
276
    }*/
277
278
    /**
279
     * @param string                                        $key_management_mode
280
     * @param \Jose\Object\EncryptionInstructionInterface[] $instructions
281
     * @param array                                         $protected_header
282
     * @param array                                         $unprotected_header
283
     * @param int                                           $cek_size
284
     *
285
     * @return string
286
     */
287
    /*private function determineCEK($key_management_mode, array $instructions, $protected_header, $unprotected_header, $cek_size)
288
    {
289
        switch ($key_management_mode) {
290
            case 'enc':
291
            case 'wrap':
292
                return $this->createCEK($cek_size);
293
            case 'dir':
294
                return $this->getDirectKey($instructions, $protected_header, $unprotected_header);
295
            case 'agree':
296
                return $this->getAgreementKey($instructions, $protected_header, $unprotected_header, $cek_size);
297
            default:
298
                throw new \RuntimeException('Unable to get CEK (unsupported key management mode).');
299
        }
300
    }*/
301
302
    /**
303
     * @param \Jose\Object\EncryptionInstructionInterface[] $instructions
304
     * @param array                                         $protected_header
305
     * @param array                                         $unprotected_header
306
     *
307
     * @return string
308
     */
309
    /*private function getDirectKey(array $instructions, $protected_header, $unprotected_header)
310
    {
311
        $cek = null;
312
        foreach ($instructions as $instruction) {
313
            $recipient_header = $instruction->getRecipientUnprotectedHeader();
314
            $complete_header = array_merge($protected_header, $unprotected_header, $recipient_header);
315
316
            $key_encryption_algorithm = $this->getKeyEncryptionAlgorithm($complete_header);
317
            if (!$key_encryption_algorithm instanceof DirectEncryptionInterface) {
318
                throw new \RuntimeException('The key encryption algorithm is not an instance of DirectEncryptionInterface');
319
            }
320
321
            $temp = $key_encryption_algorithm->getCEK($instruction->getRecipientKey(), $complete_header);
322
            if (null === $cek) {
323
                $cek = $temp;
324
            } else {
325
                if ($cek !== $temp) {
326
                    throw new \RuntimeException('Foreign CEK forbidden using direct key.');
327
                }
328
            }
329
        }
330
331
        return $cek;
332
    }*/
333
334
    /**
335
     * @param \Jose\Object\EncryptionInstructionInterface[] $instructions
336
     * @param array                                         $protected_header
337
     * @param array                                         $unprotected_header
338
     * @param int                                           $cek_size
339
     *
340
     * @return string
341
     */
342
    /*private function getAgreementKey(array $instructions, $protected_header, $unprotected_header, $cek_size)
343
    {
344
        $cek = null;
345
        foreach ($instructions as $instruction) {
346
            $recipient_header = $instruction->getRecipientUnprotectedHeader();
347
            $complete_header = array_merge($protected_header, $unprotected_header, $recipient_header);
348
349
            $key_encryption_algorithm = $this->getKeyEncryptionAlgorithm($complete_header);
350
351
            if (!$key_encryption_algorithm instanceof KeyAgreementInterface) {
352
                throw new \RuntimeException('The key encryption algorithm is not an instance of KeyAgreementInterface');
353
            }
354
355
            if (null === $instruction->getSenderKey()) {
356
                throw new \RuntimeException('The sender key must be set using Key Agreement or Key Agreement with Wrapping algorithms.');
357
            }
358
            $additional_header_values = [];
359
            $temp = $key_encryption_algorithm->getAgreementKey($cek_size, $instruction->getSenderKey(), $instruction->getRecipientKey(), $complete_header, $additional_header_values);
360
            if (null === $cek) {
361
                $cek = $temp;
362
            } else {
363
                if ($cek !== $temp) {
364
                    throw new \RuntimeException('Foreign CEK forbidden using direct key agreement.');
365
                }
366
            }
367
        }
368
369
        return $cek;
370
    }*/
371
372
    /**
373
     * @param string $payload
374
     * @param array  $complete_headers
375
     */
376
    private function compressPayload(&$payload, array $complete_headers)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
377
    {
378
        if (!array_key_exists('zip', $complete_headers)) {
379
            return;
380
        }
381
382
        $compression_method = $this->getCompressionManager()->getCompressionAlgorithm($complete_headers['zip']);
383
        if (null === $compression_method) {
384
            throw new \RuntimeException(sprintf('Compression method "%s" not supported', $complete_headers['zip']));
385
        }
386
        $payload = $compression_method->compress($payload);
387
        if (!is_string($payload)) {
388
            throw new \RuntimeException('Compression failed.');
389
        }
390
    }
391
392
    /**
393
     * @param array $complete_headers
394
     *
395
     * @return string|null
396
     */
397
    private function findCompressionMethod($complete_headers)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
398
    {
399
        if (array_key_exists('zip', $complete_headers) && $method !== $complete_headers['zip']) {
0 ignored issues
show
Bug introduced by
The variable $method does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
400
            throw new \RuntimeException('Foreign compression method forbidden');
401
        }
402
403
        return $method;
404
    }
405
406
    /**
407
     * @param string $method
408
     *
409
     * @return \Jose\Compression\CompressionInterface
410
     */
411
    private function getCompressionMethod($method)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
412
    {
413
        $compression_method = $this->getCompressionManager()->getCompressionAlgorithm($method);
414
        if (null === $compression_method) {
415
            throw new \RuntimeException(sprintf('Compression method "%s" not supported', $method));
416
        }
417
418
        return $compression_method;
419
    }
420
421
    /**
422
     * @param array $complete_header
423
     *
424
     * @return \Jose\Algorithm\KeyEncryption\DirectEncryptionInterface|\Jose\Algorithm\KeyEncryption\KeyEncryptionInterface|\Jose\Algorithm\KeyEncryption\KeyAgreementInterface|\Jose\Algorithm\KeyEncryption\KeyAgreementWrappingInterface
425
     */
426
    private function getKeyEncryptionAlgorithm($complete_header)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
427
    {
428
        if (!array_key_exists('alg', $complete_header)) {
429
            throw new \InvalidArgumentException('Parameter "alg" is missing.');
430
        }
431
        $key_encryption_algorithm = $this->getJWAManager()->getAlgorithm($complete_header['alg']);
432
        foreach ([
433
                     '\Jose\Algorithm\KeyEncryption\DirectEncryptionInterface',
434
                     '\Jose\Algorithm\KeyEncryption\KeyEncryptionInterface',
435
                     '\Jose\Algorithm\KeyEncryption\KeyAgreementInterface',
436
                     '\Jose\Algorithm\KeyEncryption\KeyAgreementWrappingInterface',
437
                 ] as $class) {
438
            if ($key_encryption_algorithm instanceof $class) {
439
                return $key_encryption_algorithm;
440
            }
441
        }
442
        throw new \RuntimeException(sprintf('The key encryption algorithm "%s" is not supported or not a key encryption algorithm instance.', $complete_header['alg']));
443
    }
444
445
    /**
446
     * @param \Jose\Object\EncryptionInstructionInterface[] $instructions
447
     * @param array                                         $protected_header
448
     * @param array                                         $unprotected_header
449
     *
450
     * @return \Jose\Algorithm\ContentEncryption\ContentEncryptionInterface
451
     */
452
    private function getContentEncryptionAlgorithm(array $instructions, array $protected_header = [], array $unprotected_header = [])
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
453
    {
454
        $algorithm = null;
455
        foreach ($instructions as $instruction) {
456
            $recipient_header = $instruction->getRecipientUnprotectedHeader();
457
            $complete_header = array_merge($protected_header, $unprotected_header, $recipient_header);
458
            if (!array_key_exists('enc', $complete_header)) {
459
                throw new \InvalidArgumentException('Parameter "enc" is missing.');
460
            }
461
            if (null === $algorithm) {
462
                $algorithm = $complete_header['enc'];
463
            } else {
464
                if ($algorithm !== $complete_header['enc']) {
465
                    throw new \InvalidArgumentException('Foreign "enc" parameter forbidden.');
466
                }
467
            }
468
        }
469
470
        $content_encryption_algorithm = $this->getJWAManager()->getAlgorithm($algorithm);
471
        if (!$content_encryption_algorithm instanceof ContentEncryptionInterface) {
0 ignored issues
show
Bug introduced by
The class Jose\ContentEncryptionInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
472
            throw new \RuntimeException(sprintf('The algorithm "%s" is not enabled or does not implement ContentEncryptionInterface.', $algorithm));
473
        }
474
475
        return $content_encryption_algorithm;
476
    }
477
478
    /**
479
     * @param int $size
480
     *
481
     * @return string
482
     */
483
    private function createCEK($size)
484
    {
485
        return $this->generateRandomString($size / 8);
486
    }
487
488
    /**
489
     * @param int $size
490
     *
491
     * @return string
492
     */
493
    private function createIV($size)
494
    {
495
        return $this->generateRandomString($size / 8);
496
    }
497
498
    /**
499
     * @param int $length
500
     *
501
     * @return string
502
     */
503
    private function generateRandomString($length)
504
    {
505
        if (function_exists('random_bytes')) {
506
            return random_bytes($length);
507
        } else {
508
            return openssl_random_pseudo_bytes($length);
509
        }
510
    }
511
512
    /**
513
     * @param \Jose\Object\JWEInterface      $jwe
514
     * @param \Jose\Object\JWKInterface      $recipient_key
515
     * @param \Jose\Object\JWKInterface|null $sender_key
516
     * @param array                          $recipient_headers
517
     *
518
     * @return \Jose\Object\JWEInterface
519
     */
520
    public function addRecipient(JWEInterface $jwe, JWKInterface $recipient_key, JWKInterface $sender_key = null, array $recipient_headers = [])
521
    {
522
        $complete_headers = array_merge(
523
            $jwe->getSharedProtectedHeaders(),
524
            $jwe->getSharedHeaders(),
525
            $recipient_headers
526
        );
527
528
        $key_encryption_algorithm = $this->findKeyEncryptionAlgorithm($complete_headers);
529
        $key_management_mode = $key_encryption_algorithm->getKeyManagementMode();
530
531
        if (null === $jwe->getCiphertext()) {
532
            // the content is not yet encrypted (no recipient)
533
534
            $content_encryption_algorithm = $this->findContentEncryptionAlgorithm($complete_headers);
535
536
            // CEK
537
            $cek = $this->getCEK(
538
                $key_management_mode,
539
                $complete_headers,
540
                $key_encryption_algorithm,
541
                $content_encryption_algorithm,
542
                $recipient_key,
543
                $sender_key
544
            );
545
546
            $jwt_cek = $this->getEncryptedKey($cek, $key_encryption_algorithm);
0 ignored issues
show
Bug introduced by
The call to getEncryptedKey() misses a required argument $recipient_key.

This check looks for function calls that miss required arguments.

Loading history...
Unused Code introduced by
$jwt_cek is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
547
548
        } else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
549
550
        }
551
552
        // Si déjà chiffré
553
        // - récupérer le key management mode
554
        // - vérifier le key management mode
555
        // -
556
        // Si pas chiffré
557
        // - déterminer le CEK
558
        // - définir le CEK dans le JWE
559
        // - chiffrer
560
        // - insérer le ciphertextt
561
562
        // We get the content encryption algorithm
563
564
565
566
        $recipient = new Recipient();
567
        $recipient = $recipient->withHeaders($recipient_headers);
568
569
        // We encrypt the payload and get the tag
570
        $tag = null;
571
        $ciphertext = $content_encryption_algorithm->encryptContent($input, $cek, $iv, $aad, $jwt_shared_protected_header, $tag);
0 ignored issues
show
Bug introduced by
The variable $content_encryption_algorithm does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $input does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $cek does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $iv does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $aad does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $jwt_shared_protected_header does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
572
573
        $jwe = $jwe->withCiphertext(Base64Url::encode($ciphertext));
574
        $jwe = $jwe->withAAD(null === $aad ? null : Base64Url::encode($aad));
575
        $jwe = $jwe->withTag(null === $tag ? null : Base64Url::encode($tag));
576
        $jwe = $jwe->withIV(null === $iv ? '' : Base64Url::encode($iv));
577
        $jwe = $jwe->addRecipient($recipient);
578
579
        return $jwe;
580
    }
581
582
    private function getEncryptedKey($cek, $key_encryption_algorithm, JWKInterface $recipient_key, JWKInterface $sender_key = null)
583
    {
584
        if ($key_encryption_algorithm instanceof KeyEncryptionInterface) {
585
            return Base64Url::encode($key_encryption_algorithm->encryptKey($recipient_key, $cek, $protected_header));
586
        } elseif ($key_encryption_algorithm instanceof KeyAgreementWrappingInterface) {
587
            if (!$sender_key instanceof JWKInterface) {
588
                throw new \RuntimeException('The sender key must be set using Key Agreement or Key Agreement with Wrapping algorithms.');
589
            }
590
            $additional_header_values = [];
591
            $jwt_cek = Base64Url::encode($key_encryption_algorithm->wrapAgreementKey($sender_key, $recipient_key, $cek, $cek_size, $complete_header, $additional_header_values));
0 ignored issues
show
Bug introduced by
The variable $cek_size does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $complete_header does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Unused Code introduced by
$jwt_cek is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
592
            //$this->updateHeader($additional_header_values, $protected_header, $recipient_header, $serialization);
593
        } elseif ($key_encryption_algorithm instanceof KeyAgreementInterface) {
594
            $additional_header_values = [];
595
            $jwt_cek = Base64Url::encode($key_encryption_algorithm->getAgreementKey($cek_size, $sender_key, $recipient_key, $complete_header, $additional_header_values));
0 ignored issues
show
Bug introduced by
It seems like $sender_key defined by parameter $sender_key on line 582 can be null; however, Jose\Algorithm\KeyEncryp...face::getAgreementKey() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
Unused Code introduced by
$jwt_cek is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
596
            //$this->updateHeader($additional_header_values, $protected_header, $recipient_header, $serialization);
597
        }
598
    }
599
600
    private function getCEK($key_management_mode, array $complete_headers, KeyEncryptionAlgorithmInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, JWKInterface $recipient_key, JWKInterface $sender_key = null)
601
    {
602
        switch ($key_management_mode) {
603
            case 'enc':
604
            case 'wrap':
605
                return $this->createCEK($content_encryption_algorithm->getCEKSize());
606
            case 'dir':
607
                return $key_encryption_algorithm->getCEK($recipient_key);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Jose\Algorithm\KeyEncryptionAlgorithmInterface as the method getCEK() does only exist in the following implementations of said interface: Jose\Algorithm\KeyEncryption\Dir.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
608
            case 'agree':
609
                return $this->calculateAgreementKey($complete_headers, $key_encryption_algorithm, $content_encryption_algorithm, $recipient_key, $sender_key);
610
            default:
611
                throw new \RuntimeException('Unable to get CEK (unsupported key management mode).');
612
        }
613
    }
614
615
    private function getKeyManagementModeOfAlgorithm(EncryptionInterface $algorithm)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
616
    {
617
        if ($algorithm instanceof KeyEncryptionInterface) {
618
            return 'enc';
619
        } elseif ($algorithm instanceof KeyAgreementWrappingInterface) {
620
            return 'wrap';
621
        } elseif ($algorithm instanceof KeyAgreementInterface) {
622
            return 'agree';
623
        } elseif ($algorithm instanceof DirectEncryptionInterface) {
624
            return 'dir';
625
        } else {
626
            throw new \RuntimeException('Unable to get key management mode.');
627
        }
628
    }
629
630
    /**
631
     * @param array $complete_headers
632
     *
633
     * @return \Jose\Algorithm\KeyEncryptionAlgorithmInterface
634
     */
635
    private function findKeyEncryptionAlgorithm(array $complete_headers)
636
    {
637
        if (!array_key_exists('alg', $complete_headers)) {
638
            throw new \InvalidArgumentException('Parameter "alg" is missing.');
639
        }
640
        $key_encryption_algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['alg']);
641
        if ($key_encryption_algorithm instanceof KeyEncryptionAlgorithmInterface) {
642
            return $key_encryption_algorithm;
643
        }
644
        throw new \RuntimeException(sprintf('The key encryption algorithm "%s" is not supported or not a key encryption algorithm instance.', $complete_headers['alg']));
645
    }
646
647
    /**
648
     * @param array $complete_headers
649
     *
650
     * @return \Jose\Algorithm\ContentEncryptionAlgorithmInterface
651
     */
652
    private function findContentEncryptionAlgorithm(array $complete_headers)
653
    {
654
        if (!array_key_exists('enc', $complete_headers)) {
655
            throw new \InvalidArgumentException('Parameter "enc" is missing.');
656
        }
657
658
        $content_encryption_algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['enc']);
659
        if (!$content_encryption_algorithm instanceof ContentEncryptionAlgorithmInterface) {
660
            throw new \RuntimeException(sprintf('The algorithm "%s" is not enabled or does not implement ContentEncryptionInterface.', $complete_headers['enc']));
661
        }
662
663
        return $content_encryption_algorithm;
664
    }
665
666
    /**
667
     * @param array                                               $complete_headers
668
     * @param \Jose\Algorithm\KeyEncryptionAlgorithmInterface     $key_encryption_algorithm
669
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm
670
     * @param \Jose\Object\JWKInterface                           $recipient_key
671
     * @param \Jose\Object\JWKInterface|null                      $sender_key
672
     *
673
     * @return string
674
     */
675
    private function calculateAgreementKey(array $complete_headers, KeyEncryptionAlgorithmInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, JWKInterface $recipient_key, JWKInterface $sender_key = null)
676
    {
677
        if (!$sender_key instanceof JWKInterface) {
678
            throw new \RuntimeException('The sender key must be set using Key Agreement or Key Agreement with Wrapping algorithms.');
679
        }
680
        $additional_header_values = [];
681
        $cek = $key_encryption_algorithm->getAgreementKey($content_encryption_algorithm->getCEKSize(), $sender_key, $recipient_key, $complete_headers, $additional_header_values);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Jose\Algorithm\KeyEncryptionAlgorithmInterface as the method getAgreementKey() does only exist in the following implementations of said interface: Jose\Algorithm\KeyEncryption\ECDHES.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
682
683
        return $cek;
684
    }
685
}
686