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

Encrypter::encrypt()   C

Complexity

Conditions 12
Paths 98

Size

Total Lines 74
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 1 Features 1
Metric Value
c 5
b 1
f 1
dl 0
loc 74
rs 5.3993
cc 12
eloc 39
nc 98
nop 6

How to fix   Long Method    Complexity   

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-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\JWAManagerInterface;
17
use Jose\Algorithm\KeyEncryption\DirectEncryptionInterface;
18
use Jose\Algorithm\KeyEncryption\KeyAgreementInterface;
19
use Jose\Algorithm\KeyEncryption\KeyAgreementWrappingInterface;
20
use Jose\Algorithm\KeyEncryption\KeyEncryptionInterface;
21
use Jose\Algorithm\KeyEncryption\KeyWrappingInterface;
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
31
/**
32
 */
33
final class Encrypter implements EncrypterInterface
0 ignored issues
show
Comprehensibility Best Practice introduced by
The type Jose\Encrypter has been defined more than once; this definition is ignored, only the first definition in src/Encrypter.old.php (L34-685) is considered.

This check looks for classes that have been defined more than once.

If you can, we would recommend to use standard object-oriented programming techniques. For example, to avoid multiple types, it might make sense to create a common interface, and then multiple, different implementations for that interface.

This also has the side-effect of providing you with better IDE auto-completion, static analysis and also better OPCode caching from PHP.

Loading history...
34
{
35
    use HasKeyChecker;
36
    use HasJWAManager;
37
    use HasCompressionManager;
38
39
    /**
40
     * Encrypter constructor.
41
     *
42
     * @param \Jose\Algorithm\JWAManagerInterface            $jwa_manager
43
     * @param \Jose\Compression\CompressionManagerInterface  $compression_manager
44
     */
45
    public function __construct(
46
        JWAManagerInterface $jwa_manager,
47
        CompressionManagerInterface $compression_manager)
48
    {
49
        $this->setJWAManager($jwa_manager);
50
        $this->setCompressionManager($compression_manager);
51
    }
52
53
    /**
54
     * @param \Jose\Object\JWEInterface      $jwe
55
     * @param \Jose\Object\JWKInterface      $recipient_key
56
     * @param \Jose\Object\JWKInterface|null $sender_key
57
     * @param array                          $recipient_headers
58
     *
59
     * @return \Jose\Object\JWEInterface
60
     */
61
    public function addRecipient(JWEInterface $jwe, JWKInterface $recipient_key, JWKInterface $sender_key = null, array $recipient_headers = [])
62
    {
63
        $complete_headers = array_merge(
64
            $jwe->getSharedProtectedHeaders(),
65
            $jwe->getSharedHeaders(),
66
            $recipient_headers
67
        );
68
69
        $key_encryption_algorithm = $this->findKeyEncryptionAlgorithm($complete_headers);
70
71
        // Content Encryption Algorithm
72
        $content_encryption_algorithm = $this->findContentEncryptionAlgorithm($complete_headers);
73
74
        if (null === $jwe->getCiphertext()) {
75
            // the content is not yet encrypted (no recipient)
76
77
            // CEK
78
            $content_encryption_key = $this->getCEK(
79
                $complete_headers,
80
                $key_encryption_algorithm,
0 ignored issues
show
Documentation introduced by
$key_encryption_algorithm is of type object<Jose\Algorithm\Ke...tionAlgorithmInterface>, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
81
                $content_encryption_algorithm,
0 ignored issues
show
Documentation introduced by
$content_encryption_algorithm is of type object<Jose\Algorithm\Co...tionAlgorithmInterface>, but the function expects a object<Jose\Algorithm\Ke...tionAlgorithmInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
82
                $recipient_key,
0 ignored issues
show
Documentation introduced by
$recipient_key is of type object<Jose\Object\JWKInterface>, but the function expects a object<Jose\Algorithm\Co...tionAlgorithmInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
83
                $sender_key
0 ignored issues
show
Bug introduced by
It seems like $sender_key defined by parameter $sender_key on line 61 can be null; however, Jose\Encrypter::getCEK() 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...
84
            );
85
            $jwe = $jwe->withContentEncryptionKey($content_encryption_key);
86
87
            // IV
88
            if (null !== $iv_size = $content_encryption_algorithm->getIVSize()) {
89
                $iv = $this->createIV($iv_size);
90
                $jwe = $jwe->withIV($iv);
91
            }
92
93
            // We encrypt the payload and get the tag
94
            $tag = null;
95
            $payload = $this->compressPayloadIfNeeded($jwe->getPayload(), $complete_headers);
0 ignored issues
show
Bug introduced by
The method compressPayloadIfNeeded() does not exist on Jose\Encrypter. Did you maybe mean compressPayload()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
96
            $ciphertext = $content_encryption_algorithm->encryptContent(
97
                $payload,
98
                $content_encryption_key,
99
                $jwe->getIV(),
100
                $jwe->getAAD(),
101
                $jwe->getEncodedSharedProtectedHeaders(),
102
                $tag
103
            );
104
            $jwe = $jwe->withCiphertext($ciphertext);
105
106
            // Tag
107
            if (null !== $tag) {
108
                $jwe = $jwe->withTag($tag);
109
            }
110
            // JWT Ciphertext
111
        } 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...
112
            // On vérifie le jey managmenet mode
113
114
            // On vérifie si le CEK est disponible.
115
            // - si dispo on quitte le if
116
            // - sinon on ne peut pas aller plus loin. Le JWE doit être décrypté avant
117
        }
118
119
        $recipient = new Recipient();
120
        $recipient = $recipient->withHeaders($recipient_headers);
121
122
        $encrypted_content_encryption_key = $this->getEncryptedKey(
123
            $complete_headers,
124
            $jwe->getContentEncryptionKey(),
125
            $key_encryption_algorithm,
0 ignored issues
show
Documentation introduced by
$key_encryption_algorithm is of type object<Jose\Algorithm\Ke...tionAlgorithmInterface>, but the function expects a object<Jose\Object\JWKInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
126
            $content_encryption_algorithm,
0 ignored issues
show
Documentation introduced by
$content_encryption_algorithm is of type object<Jose\Algorithm\Co...tionAlgorithmInterface>, but the function expects a null|object<Jose\Object\JWKInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
127
            $recipient_key,
0 ignored issues
show
Unused Code introduced by
The call to Encrypter::getEncryptedKey() has too many arguments starting with $recipient_key.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
128
            $sender_key
129
        );
130
        if (null !== $encrypted_content_encryption_key) {
131
            $recipient = $recipient->withEncryptedKey($encrypted_content_encryption_key);
132
        }
133
        $jwe = $jwe->addRecipient($recipient);
134
135
        return $jwe;
136
    }
137
138
    /**
139
     * @param string $payload
140
     * @param array  $complete_headers
141
     *
142
     * @return string
143
     */
144
    private function compressPayloadIfNeeded($payload, array $complete_headers)
145
    {
146
        if (!array_key_exists('zip', $complete_headers)) {
147
            return;
148
        }
149
150
        $compression_method = $this->getCompressionManager()->getCompressionAlgorithm($complete_headers['zip']);
151
        if (null === $compression_method) {
152
            throw new \RuntimeException(sprintf('Compression method "%s" not supported', $complete_headers['zip']));
153
        }
154
        $compressed_payload = $compression_method->compress($payload);
155
        if (!is_string($compressed_payload)) {
156
            throw new \RuntimeException('Compression failed.');
157
        }
158
159
        return $compressed_payload;
160
    }
161
162
    /**
163
     * @param array $complete_header
164
     *
165
     * @return \Jose\Algorithm\KeyEncryption\DirectEncryptionInterface|\Jose\Algorithm\KeyEncryption\KeyEncryptionInterface|\Jose\Algorithm\KeyEncryption\KeyAgreementInterface|\Jose\Algorithm\KeyEncryption\KeyAgreementWrappingInterface
166
     */
167
    private function getKeyEncryptionAlgorithm($complete_header)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
168
    {
169
        if (!array_key_exists('alg', $complete_header)) {
170
            throw new \InvalidArgumentException('Parameter "alg" is missing.');
171
        }
172
        $key_encryption_algorithm = $this->getJWAManager()->getAlgorithm($complete_header['alg']);
173
        foreach ([
174
                     '\Jose\Algorithm\KeyEncryption\DirectEncryptionInterface',
175
                     '\Jose\Algorithm\KeyEncryption\KeyEncryptionInterface',
176
                     '\Jose\Algorithm\KeyEncryption\KeyAgreementInterface',
177
                     '\Jose\Algorithm\KeyEncryption\KeyAgreementWrappingInterface',
178
                 ] as $class) {
179
            if ($key_encryption_algorithm instanceof $class) {
180
                return $key_encryption_algorithm;
181
            }
182
        }
183
        throw new \RuntimeException(sprintf('The key encryption algorithm "%s" is not supported or not a key encryption algorithm instance.', $complete_header['alg']));
184
    }
185
186
    /**
187
     * @param array $complete_headers
188
     *
189
     * @return \Jose\Algorithm\ContentEncryptionAlgorithmInterface
190
     */
191
    private function getContentEncryptionAlgorithm($complete_headers)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
192
    {
193
        if (!array_key_exists('enc', $complete_headers)) {
194
            throw new \InvalidArgumentException('Parameter "enc" is missing.');
195
        }
196
197
        $content_encryption_algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['enc']);
198
        if (!$content_encryption_algorithm instanceof ContentEncryptionAlgorithmInterface) {
199
            throw new \RuntimeException(sprintf('The algorithm "%s" is not enabled or does not implement ContentEncryptionAlgorithmInterface.', $complete_headers['enc']));
200
        }
201
202
        return $content_encryption_algorithm;
203
    }
204
205
    private function getEncryptedKey(array $complete_headers, $cek, KeyEncryptionAlgorithmInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, JWKInterface $recipient_key, JWKInterface $sender_key = null)
206
    {
207
        if ($key_encryption_algorithm instanceof KeyEncryptionInterface) {
208
            return $key_encryption_algorithm->encryptKey(
209
                    $recipient_key,
210
                    $cek,
211
                    $complete_headers
212
                );
213
        } elseif ($key_encryption_algorithm instanceof KeyWrappingInterface) {
214
            return $key_encryption_algorithm->wrapKey(
215
                $recipient_key,
216
                $cek,
217
                $complete_headers
218
            );
219
        } elseif ($key_encryption_algorithm instanceof KeyAgreementWrappingInterface) {
220
            if (!$sender_key instanceof JWKInterface) {
221
                throw new \RuntimeException('The sender key must be set using Key Agreement or Key Agreement with Wrapping algorithms.');
222
            }
223
            $additional_header_values = [];
224
            $jwt_cek = $key_encryption_algorithm->wrapAgreementKey($sender_key, $recipient_key, $cek, $content_encryption_algorithm->getCEKSize(), $complete_headers, $additional_header_values);
0 ignored issues
show
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...
225
            //$this->updateHeader($additional_header_values, $protected_header, $recipient_header, $serialization);
226
        } elseif ($key_encryption_algorithm instanceof KeyAgreementInterface) {
227
            $additional_header_values = [];
228
            $jwt_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 $sender_key defined by parameter $sender_key on line 205 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...
229
            //$this->updateHeader($additional_header_values, $protected_header, $recipient_header, $serialization);
230
        }
231
    }
232
233
    private function getCEK(array $complete_headers, KeyEncryptionAlgorithmInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, JWKInterface $recipient_key, JWKInterface $sender_key = null)
234
    {
235
        if ($key_encryption_algorithm instanceof KeyEncryptionInterface) {
236
            return $this->createCEK($content_encryption_algorithm->getCEKSize());
237
        } elseif ($key_encryption_algorithm instanceof KeyAgreementWrappingInterface) {
238
            return $this->createCEK($content_encryption_algorithm->getCEKSize());
239
        } elseif ($key_encryption_algorithm instanceof KeyAgreementInterface) {
240
            return $this->calculateAgreementKey($complete_headers, $key_encryption_algorithm, $content_encryption_algorithm, $recipient_key, $sender_key);
241
        } elseif ($key_encryption_algorithm instanceof DirectEncryptionInterface) {
242
            return $key_encryption_algorithm->getCEK($recipient_key);
243
        } else {
244
            throw new \RuntimeException('Unable to get key management mode.');
245
        }
246
    }
247
248
    /**
249
     * @param array $complete_headers
250
     *
251
     * @return \Jose\Algorithm\KeyEncryptionAlgorithmInterface
252
     */
253
    private function findKeyEncryptionAlgorithm(array $complete_headers)
254
    {
255
        if (!array_key_exists('alg', $complete_headers)) {
256
            throw new \InvalidArgumentException('Parameter "alg" is missing.');
257
        }
258
        $key_encryption_algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['alg']);
259
        if ($key_encryption_algorithm instanceof KeyEncryptionAlgorithmInterface) {
260
            return $key_encryption_algorithm;
261
        }
262
        throw new \RuntimeException(sprintf('The key encryption algorithm "%s" is not supported or not a key encryption algorithm instance.', $complete_headers['alg']));
263
    }
264
265
    /**
266
     * @param array $complete_headers
267
     *
268
     * @return \Jose\Algorithm\ContentEncryptionAlgorithmInterface
269
     */
270
    private function findContentEncryptionAlgorithm(array $complete_headers)
271
    {
272
        if (!array_key_exists('enc', $complete_headers)) {
273
            throw new \InvalidArgumentException('Parameter "enc" is missing.');
274
        }
275
276
        $content_encryption_algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['enc']);
277
        if (!$content_encryption_algorithm instanceof ContentEncryptionAlgorithmInterface) {
278
            throw new \RuntimeException(sprintf('The algorithm "%s" is not enabled or does not implement ContentEncryptionInterface.', $complete_headers['enc']));
279
        }
280
281
        return $content_encryption_algorithm;
282
    }
283
284
    /**
285
     * @param array                                               $complete_headers
286
     * @param \Jose\Algorithm\KeyEncryption\KeyAgreementInterface $key_encryption_algorithm
287
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm
288
     * @param \Jose\Object\JWKInterface                           $recipient_key
289
     * @param \Jose\Object\JWKInterface|null                      $sender_key
290
     *
291
     * @return string
292
     */
293
    private function calculateAgreementKey(array $complete_headers, KeyAgreementInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, JWKInterface $recipient_key, JWKInterface $sender_key = null)
294
    {
295
        if (!$sender_key instanceof JWKInterface) {
296
            throw new \RuntimeException('The sender key must be set using Key Agreement or Key Agreement with Wrapping algorithms.');
297
        }
298
        $additional_header_values = [];
299
        $cek = $key_encryption_algorithm->getAgreementKey($content_encryption_algorithm->getCEKSize(), $sender_key, $recipient_key, $complete_headers, $additional_header_values);
300
301
        return $cek;
302
    }
303
304
    /**
305
     * @param int $size
306
     *
307
     * @return string
308
     */
309
    private function createCEK($size)
310
    {
311
        return $this->generateRandomString($size / 8);
312
    }
313
314
    /**
315
     * @param int $size
316
     *
317
     * @return string
318
     */
319
    private function createIV($size)
320
    {
321
        return $this->generateRandomString($size / 8);
322
    }
323
324
    /**
325
     * @param int $length
326
     *
327
     * @return string
328
     */
329
    private function generateRandomString($length)
330
    {
331
        if (function_exists('random_bytes')) {
332
            return random_bytes($length);
333
        } else {
334
            return openssl_random_pseudo_bytes($length);
335
        }
336
    }
337
}
338