Completed
Push — v2.0.x ( c24837...59a3d4 )
by Florent
19:58
created

Encrypter::preparePayload()   B

Complexity

Conditions 6
Paths 10

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 22
rs 8.6737
cc 6
eloc 13
nc 10
nop 2
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 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
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
            if (!empty($jwe->getSharedProtectedHeaders())) {
78
                $jwe = $jwe->withEncodedSharedProtectedHeaders(Base64Url::encode(json_encode($jwe->getSharedProtectedHeaders())));
79
            }
80
81
            // CEK
82
            $content_encryption_key = $this->getCEK(
83
                $complete_headers,
84
                $key_encryption_algorithm,
85
                $content_encryption_algorithm,
86
                $recipient_key,
87
                $sender_key
88
            );
89
            $jwe = $jwe->withContentEncryptionKey($content_encryption_key);
90
91
            // IV
92
            if (null !== $iv_size = $content_encryption_algorithm->getIVSize()) {
93
                $iv = $this->createIV($iv_size);
94
                $jwe = $jwe->withIV($iv);
95
            }
96
97
            // We encrypt the payload and get the tag
98
            $tag = null;
99
            $payload = $this->preparePayload($jwe->getPayload(), $complete_headers);
100
101
            $ciphertext = $content_encryption_algorithm->encryptContent(
102
                $payload,
103
                $content_encryption_key,
104
                $jwe->getIV(),
105
                $jwe->getAAD(),
106
                $jwe->getEncodedSharedProtectedHeaders(),
107
                $tag
108
            );
109
            $jwe = $jwe->withCiphertext($ciphertext);
110
111
            // Tag
112
            if (null !== $tag) {
113
                $jwe = $jwe->withTag($tag);
114
            }
115
            // JWT Ciphertext
116
        } 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...
117
            // On vérifie le jey managmenet mode
118
119
            // On vérifie si le CEK est disponible.
120
            // - si dispo on quitte le if
121
            // - sinon on ne peut pas aller plus loin. Le JWE doit être décrypté avant
122
        }
123
124
        $recipient = new Recipient();
125
        $recipient = $recipient->withHeaders($recipient_headers);
126
127
        $encrypted_content_encryption_key = $this->getEncryptedKey(
128
            $complete_headers,
129
            $jwe->getContentEncryptionKey(),
130
            $key_encryption_algorithm,
131
            $content_encryption_algorithm,
132
            $recipient_key,
133
            $sender_key
134
        );
135
        if (null !== $encrypted_content_encryption_key) {
136
            $recipient = $recipient->withEncryptedKey($encrypted_content_encryption_key);
137
        }
138
        $jwe = $jwe->addRecipient($recipient);
139
140
        return $jwe;
141
    }
142
143
    /**
144
     * @param string $payload
145
     * @param array  $complete_headers
146
     *
147
     * @return string
148
     */
149
    private function preparePayload($payload, array $complete_headers)
150
    {
151
        $prepared = is_string($payload)?$payload:json_encode($payload);
152
153
        if(null === $prepared) {
154
            throw new \InvalidArgumentException('The payload is empty or cannot encoded into JSON.');
155
        }
156
        if (!array_key_exists('zip', $complete_headers)) {
157
            return $prepared;
158
        }
159
160
        $compression_method = $this->getCompressionManager()->getCompressionAlgorithm($complete_headers['zip']);
161
        if (null === $compression_method) {
162
            throw new \RuntimeException(sprintf('Compression method "%s" not supported', $complete_headers['zip']));
163
        }
164
        $compressed_payload = $compression_method->compress($prepared);
165
        if (!is_string($compressed_payload)) {
166
            throw new \RuntimeException('Compression failed.');
167
        }
168
169
        return $compressed_payload;
170
    }
171
172
    private function getEncryptedKey(array $complete_headers, $cek, KeyEncryptionAlgorithmInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, JWKInterface $recipient_key, JWKInterface $sender_key = null)
173
    {
174
        if ($key_encryption_algorithm instanceof KeyEncryptionInterface) {
175
            return $key_encryption_algorithm->encryptKey(
176
                    $recipient_key,
177
                    $cek,
178
                    $complete_headers
179
                );
180
        } elseif ($key_encryption_algorithm instanceof KeyWrappingInterface) {
181
            return $key_encryption_algorithm->wrapKey(
182
                $recipient_key,
183
                $cek,
184
                $complete_headers
185
            );
186
        } elseif ($key_encryption_algorithm instanceof KeyAgreementWrappingInterface) {
187
            if (!$sender_key instanceof JWKInterface) {
188
                throw new \RuntimeException('The sender key must be set using Key Agreement or Key Agreement with Wrapping algorithms.');
189
            }
190
            $additional_header_values = [];
191
            $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...
192
            //$this->updateHeader($additional_header_values, $protected_header, $recipient_header, $serialization);
193
        } elseif ($key_encryption_algorithm instanceof KeyAgreementInterface) {
194
            $additional_header_values = [];
195
            $jwt_cek = $key_encryption_algorithm->getAgreementKey($content_encryption_algorithm->getCEKSize(), $sender_key, $recipient_key, $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...
Bug introduced by
It seems like $sender_key defined by parameter $sender_key on line 172 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...
196
            //$this->updateHeader($additional_header_values, $protected_header, $recipient_header, $serialization);
197
        }
198
    }
199
200
    private function getCEK(array $complete_headers, KeyEncryptionAlgorithmInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, JWKInterface $recipient_key, JWKInterface $sender_key = null)
201
    {
202
        if ($key_encryption_algorithm instanceof KeyEncryptionInterface) {
203
            return $this->createCEK($content_encryption_algorithm->getCEKSize());
204
        } elseif ($key_encryption_algorithm instanceof KeyAgreementWrappingInterface) {
205
            return $this->createCEK($content_encryption_algorithm->getCEKSize());
206
        } elseif ($key_encryption_algorithm instanceof KeyAgreementInterface) {
207
            return $this->calculateAgreementKey($complete_headers, $key_encryption_algorithm, $content_encryption_algorithm, $recipient_key, $sender_key);
208
        } elseif ($key_encryption_algorithm instanceof DirectEncryptionInterface) {
209
            return $key_encryption_algorithm->getCEK($recipient_key);
210
        } else {
211
            throw new \RuntimeException('Unable to get key management mode.');
212
        }
213
    }
214
215
    /**
216
     * @param array $complete_headers
217
     *
218
     * @return \Jose\Algorithm\KeyEncryptionAlgorithmInterface
219
     */
220
    private function findKeyEncryptionAlgorithm(array $complete_headers)
221
    {
222
        if (!array_key_exists('alg', $complete_headers)) {
223
            throw new \InvalidArgumentException('Parameter "alg" is missing.');
224
        }
225
        $key_encryption_algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['alg']);
226
        if ($key_encryption_algorithm instanceof KeyEncryptionAlgorithmInterface) {
227
            return $key_encryption_algorithm;
228
        }
229
        throw new \InvalidArgumentException(sprintf('The key encryption algorithm "%s" is not supported or not a key encryption algorithm instance.', $complete_headers['alg']));
230
    }
231
232
    /**
233
     * @param array $complete_headers
234
     *
235
     * @return \Jose\Algorithm\ContentEncryptionAlgorithmInterface
236
     */
237
    private function findContentEncryptionAlgorithm(array $complete_headers)
238
    {
239
        if (!array_key_exists('enc', $complete_headers)) {
240
            throw new \InvalidArgumentException('Parameter "enc" is missing.');
241
        }
242
243
        $content_encryption_algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['enc']);
244
        if (!$content_encryption_algorithm instanceof ContentEncryptionAlgorithmInterface) {
245
            throw new \RuntimeException(sprintf('The algorithm "%s" is not enabled or does not implement ContentEncryptionInterface.', $complete_headers['enc']));
246
        }
247
248
        return $content_encryption_algorithm;
249
    }
250
251
    /**
252
     * @param array                                               $complete_headers
253
     * @param \Jose\Algorithm\KeyEncryption\KeyAgreementInterface $key_encryption_algorithm
254
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm
255
     * @param \Jose\Object\JWKInterface                           $recipient_key
256
     * @param \Jose\Object\JWKInterface|null                      $sender_key
257
     *
258
     * @return string
259
     */
260
    private function calculateAgreementKey(array $complete_headers, KeyAgreementInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, JWKInterface $recipient_key, JWKInterface $sender_key = null)
261
    {
262
        if (!$sender_key instanceof JWKInterface) {
263
            throw new \RuntimeException('The sender key must be set using Key Agreement or Key Agreement with Wrapping algorithms.');
264
        }
265
        $additional_header_values = [];
266
        $cek = $key_encryption_algorithm->getAgreementKey($content_encryption_algorithm->getCEKSize(), $sender_key, $recipient_key, $complete_headers, $additional_header_values);
267
268
        return $cek;
269
    }
270
271
    /**
272
     * @param int $size
273
     *
274
     * @return string
275
     */
276
    private function createCEK($size)
277
    {
278
        return $this->generateRandomString($size / 8);
279
    }
280
281
    /**
282
     * @param int $size
283
     *
284
     * @return string
285
     */
286
    private function createIV($size)
287
    {
288
        return $this->generateRandomString($size / 8);
289
    }
290
291
    /**
292
     * @param int $length
293
     *
294
     * @return string
295
     */
296
    private function generateRandomString($length)
297
    {
298
        if (function_exists('random_bytes')) {
299
            return random_bytes($length);
300
        } else {
301
            return openssl_random_pseudo_bytes($length);
302
        }
303
    }
304
}
305