Failed Conditions
Push — master ( af51d1...73e249 )
by Florent
04:04
created

Encrypter::determineCEK()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 22
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 7
Bugs 0 Features 0
Metric Value
c 7
b 0
f 0
dl 0
loc 22
rs 8.6737
cc 5
eloc 17
nc 5
nop 4
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
17
final class Encrypter implements EncrypterInterface
18
{
19
    use Behaviour\HasKeyChecker;
20
    use Behaviour\HasJWAManager;
21
    use Behaviour\HasCompressionManager;
22
    use Behaviour\CommonCipheringMethods;
23
24
    /**
25
     * {@inheritdoc}
26
     */
27
    public static function createEncrypter(array $key_encryption_algorithms, array $content_encryption_algorithms, array $compression_methods = ['DEF', 'ZLIB', 'GZ'])
28
    {
29
        $encrypter = new self($key_encryption_algorithms, $content_encryption_algorithms, $compression_methods);
30
31
        return $encrypter;
32
    }
33
34
    /**
35
     * Decrypter constructor.
36
     *
37
     * @param string[]|\Jose\Algorithm\KeyEncryptionAlgorithmInterface[]     $key_encryption_algorithms
38
     * @param string[]|\Jose\Algorithm\ContentEncryptionAlgorithmInterface[] $content_encryption_algorithms
39
     * @param string[]|\Jose\Compression\CompressionInterface[]              $compression_methods
40
     */
41
    public function __construct(array $key_encryption_algorithms, array $content_encryption_algorithms, array $compression_methods)
42
    {
43
        $this->setKeyEncryptionAlgorithms($key_encryption_algorithms);
44
        $this->setContentEncryptionAlgorithms($content_encryption_algorithms);
45
        $this->setCompressionMethods($compression_methods);
46
        $this->setJWAManager(Factory\AlgorithmManagerFactory::createAlgorithmManager(array_merge($key_encryption_algorithms, $content_encryption_algorithms)));
47
        $this->setCompressionManager(Factory\CompressionManagerFactory::createCompressionManager($compression_methods));
48
    }
49
50
    /**
51
     * {@inheritdoc}
52
     */
53
    public function encrypt(Object\JWEInterface &$jwe)
54
    {
55
        Assertion::false($jwe->isEncrypted(), 'The JWE is already encrypted.');
56
        Assertion::greaterThan($jwe->countRecipients(), 0, 'The JWE does not contain recipient.');
57
58
        $content_encryption_algorithm = $this->getContentEncryptionAlgorithm($jwe);
59
        $compression_method = $this->getCompressionMethod($jwe);
60
        $key_management_mode = $this->getKeyManagementMode($jwe);
61
        $additional_headers = [];
62
        $cek = $this->determineCEK($jwe, $content_encryption_algorithm, $key_management_mode, $additional_headers);
63
64
        $nb_recipients = $jwe->countRecipients();
65
66
        for ($i = 0; $i < $nb_recipients; $i++) {
67
            $this->processRecipient($jwe, $jwe->getRecipient($i), $cek, $content_encryption_algorithm, $additional_headers);
68
        }
69
70
        if (!empty($additional_headers) && 1 === $jwe->countRecipients()) {
71
            $jwe = $jwe->withSharedProtectedHeaders(array_merge($jwe->getSharedProtectedHeaders(), $additional_headers));
72
        }
73
74
        $iv_size = $content_encryption_algorithm->getIVSize();
75
        $iv = $this->createIV($iv_size);
76
77
        $this->encryptJWE($jwe, $content_encryption_algorithm, $cek, $iv, $compression_method);
78
    }
79
80
    /**
81
     * @param \Jose\Object\JWEInterface                           $jwe
82
     * @param \Jose\Object\RecipientInterface                     $recipient
83
     * @param string                                              $cek
84
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm
85
     * @param array                                               $additional_headers
86
     */
87
    private function processRecipient(Object\JWEInterface $jwe, Object\RecipientInterface &$recipient, $cek, Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm, array &$additional_headers)
88
    {
89
        if (null === $recipient->getRecipientKey()) {
90
            return;
91
        }
92
        $complete_headers = array_merge($jwe->getSharedProtectedHeaders(), $jwe->getSharedHeaders(), $recipient->getHeaders());
93
        $key_encryption_algorithm = $this->findKeyEncryptionAlgorithm($complete_headers);
94
        $this->checkKeys($key_encryption_algorithm, $content_encryption_algorithm, $recipient->getRecipientKey());
0 ignored issues
show
Bug introduced by
It seems like $key_encryption_algorithm defined by $this->findKeyEncryption...ithm($complete_headers) on line 93 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...
95
        $encrypted_content_encryption_key = $this->getEncryptedKey($complete_headers, $cek, $key_encryption_algorithm, $content_encryption_algorithm, $additional_headers, $recipient->getRecipientKey());
0 ignored issues
show
Bug introduced by
It seems like $key_encryption_algorithm defined by $this->findKeyEncryption...ithm($complete_headers) on line 93 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...
96
        $recipient_headers = $recipient->getHeaders();
97
        if (!empty($additional_headers) && 1 !== $jwe->countRecipients()) {
98
            $recipient_headers = array_merge($recipient_headers, $additional_headers);
99
            $additional_headers = [];
100
        }
101
102
        $recipient = Object\Recipient::createRecipientFromLoadedJWE($recipient_headers, $encrypted_content_encryption_key);
103
    }
104
105
    /**
106
     * @param \Jose\Object\JWEInterface                           $jwe
107
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm
108
     * @param string                                              $key_management_mode
109
     * @param array                                               $additional_headers
110
     *
111
     * @return string
112
     */
113
    private function determineCEK(Object\JWEInterface $jwe, Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm, $key_management_mode, array &$additional_headers)
114
    {
115
        switch ($key_management_mode) {
116
            case Algorithm\KeyEncryption\KeyEncryptionInterface::MODE_ENCRYPT:
117
            case Algorithm\KeyEncryption\KeyEncryptionInterface::MODE_WRAP:
118
                return $this->createCEK($content_encryption_algorithm->getCEKSize());
119
            case Algorithm\KeyEncryption\KeyEncryptionInterface::MODE_AGREEMENT:
120
                Assertion::eq(1, $jwe->countRecipients(), 'Unable to encrypt for multiple recipients using key agreement algorithms.');
121
                $complete_headers = array_merge($jwe->getSharedProtectedHeaders(), $jwe->getSharedHeaders(), $jwe->getRecipient(0)->getHeaders());
122
                $algorithm = $this->findKeyEncryptionAlgorithm($complete_headers);
123
124
                return $algorithm->getAgreementKey($content_encryption_algorithm->getCEKSize(), $content_encryption_algorithm->getAlgorithmName(), $jwe->getRecipient(0)->getRecipientKey(), $complete_headers, $additional_headers);
125
            case Algorithm\KeyEncryption\KeyEncryptionInterface::MODE_DIRECT:
126
                Assertion::eq(1, $jwe->countRecipients(), 'Unable to encrypt for multiple recipients using key agreement algorithms.');
127
                Assertion::eq($jwe->getRecipient(0)->getRecipientKey()->get('kty'), 'oct', 'Wrong key type.');
128
                Assertion::true($jwe->getRecipient(0)->getRecipientKey()->has('k'), 'The key parameter "k" is missing.');
129
130
                return Base64Url::decode($jwe->getRecipient(0)->getRecipientKey()->get('k'));
131
            default:
132
                throw new \InvalidArgumentException(sprintf('Unsupported key management mode "%s".', $key_management_mode));
133
        }
134
    }
135
136
    /**
137
     * @param \Jose\Object\JWEInterface $jwe
138
     *
139
     * @return string
140
     */
141
    private function getKeyManagementMode(Object\JWEInterface $jwe)
142
    {
143
        $mode = null;
144
        $recipients = $jwe->getRecipients();
145
146
        foreach ($recipients as $recipient) {
147
            $complete_headers = array_merge($jwe->getSharedProtectedHeaders(), $jwe->getSharedHeaders(), $recipient->getHeaders());
148
            Assertion::keyExists($complete_headers, 'alg', 'Parameter "alg" is missing.');
149
150
            $key_encryption_algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['alg']);
151
            Assertion::isInstanceOf($key_encryption_algorithm, Algorithm\KeyEncryptionAlgorithmInterface::class, sprintf('The key encryption algorithm "%s" is not supported or not a key encryption algorithm instance.', $complete_headers['alg']));
152
153
            if (null === $mode) {
154
                $mode = $key_encryption_algorithm->getKeyManagementMode();
155
            } else {
156
                Assertion::true($this->areKeyManagementModesCompatible($mode, $key_encryption_algorithm->getKeyManagementMode()), 'Foreign key management mode forbidden.');
157
            }
158
        }
159
160
        return $mode;
161
    }
162
163
    /**
164
     * @param \Jose\Object\JWEInterface $jwe
165
     *
166
     * @return \Jose\Compression\CompressionInterface|null
167
     */
168
    private function getCompressionMethod(Object\JWEInterface $jwe)
169
    {
170
        $method = null;
171
        $nb_recipients = $jwe->countRecipients();
172
173
        for ($i = 0; $i < $nb_recipients; $i++) {
174
            $complete_headers = array_merge($jwe->getSharedProtectedHeaders(), $jwe->getSharedHeaders(), $jwe->getRecipient($i)->getHeaders());
175
            if (array_key_exists('zip', $complete_headers)) {
176
                if (null === $method) {
177
                    if (0 === $i) {
178
                        $method = $complete_headers['zip'];
179
                    } else {
180
                        throw new \InvalidArgumentException('Inconsistent "zip" parameter.');
181
                    }
182
                } else {
183
                    Assertion::eq($method, $complete_headers['zip'], 'Inconsistent "zip" parameter.');
184
                }
185
            } else {
186
                Assertion::eq(null, $method, 'Inconsistent "zip" parameter.');
187
            }
188
        }
189
190
        if (null === $method) {
191
            return;
192
        }
193
194
        $compression_method = $this->getCompressionManager()->getCompressionAlgorithm($method);
195
        Assertion::isInstanceOf($compression_method, Compression\CompressionInterface::class, sprintf('Compression method "%s" not supported.', $method));
196
197
        return $compression_method;
198
    }
199
200
    /**
201
     * @param \Jose\Object\JWEInterface $jwe
202
     *
203
     * @return \Jose\Algorithm\ContentEncryptionAlgorithmInterface
204
     */
205
    private function getContentEncryptionAlgorithm(Object\JWEInterface $jwe)
206
    {
207
        $algorithm = null;
208
209
        foreach ($jwe->getRecipients() as $recipient) {
210
            $complete_headers = array_merge($jwe->getSharedProtectedHeaders(), $jwe->getSharedHeaders(), $recipient->getHeaders());
211
            Assertion::keyExists($complete_headers, 'enc', 'Parameter "enc" is missing.');
212
            if (null === $algorithm) {
213
                $algorithm = $complete_headers['enc'];
214
            } else {
215
                Assertion::eq($algorithm, $complete_headers['enc'], 'Foreign content encryption algorithms are not allowed.');
216
            }
217
        }
218
219
        $content_encryption_algorithm = $this->getJWAManager()->getAlgorithm($algorithm);
220
        Assertion::isInstanceOf($content_encryption_algorithm, Algorithm\ContentEncryptionAlgorithmInterface::class, sprintf('The content encryption algorithm "%s" is not supported or not a content encryption algorithm instance.', $algorithm));
221
222
        return $content_encryption_algorithm;
223
    }
224
225
    /**
226
     * @param \Jose\Object\JWEInterface                           $jwe
227
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm
228
     * @param string                                              $cek
229
     * @param string                                              $iv
230
     * @param \Jose\Compression\CompressionInterface|null         $compression_method
231
     */
232
    private function encryptJWE(Object\JWEInterface &$jwe, Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm, $cek, $iv, Compression\CompressionInterface $compression_method = null)
233
    {
234
        if (!empty($jwe->getSharedProtectedHeaders())) {
235
            $jwe = $jwe->withEncodedSharedProtectedHeaders(Base64Url::encode(json_encode($jwe->getSharedProtectedHeaders())));
236
        }
237
238
        $tag = null;
239
        $payload = $this->preparePayload($jwe->getPayload(), $compression_method);
240
        $aad = null === $jwe->getAAD() ? null : Base64Url::encode($jwe->getAAD());
241
        $ciphertext = $content_encryption_algorithm->encryptContent($payload, $cek, $iv, $aad, $jwe->getEncodedSharedProtectedHeaders(), $tag);
242
        $jwe = $jwe->withCiphertext($ciphertext);
243
        $jwe = $jwe->withIV($iv);
244
245
        if (null !== $tag) {
246
            $jwe = $jwe->withTag($tag);
247
        }
248
    }
249
250
    /**
251
     * @param \Jose\Algorithm\KeyEncryptionAlgorithmInterface     $key_encryption_algorithm
252
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm
253
     * @param \Jose\Object\JWKInterface                           $recipient_key
254
     */
255
    private function checkKeys(Algorithm\KeyEncryptionAlgorithmInterface $key_encryption_algorithm, Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm, Object\JWKInterface $recipient_key)
256
    {
257
        $this->checkKeyUsage($recipient_key, 'encryption');
258
        if ('dir' !== $key_encryption_algorithm->getAlgorithmName()) {
259
            $this->checkKeyAlgorithm($recipient_key, $key_encryption_algorithm->getAlgorithmName());
260
        } else {
261
            $this->checkKeyAlgorithm($recipient_key, $content_encryption_algorithm->getAlgorithmName());
262
        }
263
    }
264
265
    /**
266
     * @param string $current
267
     * @param string $new
268
     *
269
     * @return bool
270
     */
271
    private function areKeyManagementModesCompatible($current, $new)
272
    {
273
        $agree = Algorithm\KeyEncryptionAlgorithmInterface::MODE_AGREEMENT;
274
        $dir = Algorithm\KeyEncryptionAlgorithmInterface::MODE_DIRECT;
275
        $enc = Algorithm\KeyEncryptionAlgorithmInterface::MODE_ENCRYPT;
276
        $wrap = Algorithm\KeyEncryptionAlgorithmInterface::MODE_WRAP;
277
        $supported_key_management_mode_combinations = [$enc.$enc     => true,$enc.$wrap    => true,$wrap.$enc    => true,$wrap.$wrap   => true,$agree.$agree => false,$agree.$dir   => false,$agree.$enc   => false,$agree.$wrap  => false,$dir.$agree   => false,$dir.$dir     => false,$dir.$enc     => false,$dir.$wrap    => false,$enc.$agree   => false,$enc.$dir     => false,$wrap.$agree  => false,$wrap.$dir    => false,];
278
279
        if (array_key_exists($current.$new, $supported_key_management_mode_combinations)) {
280
            return $supported_key_management_mode_combinations[$current.$new];
281
        }
282
283
        return false;
284
    }
285
286
    /**
287
     * @param string                                      $payload
288
     * @param \Jose\Compression\CompressionInterface|null $compression_method
289
     *
290
     * @return string
291
     */
292
    private function preparePayload($payload, Compression\CompressionInterface $compression_method = null)
293
    {
294
        $prepared = is_string($payload) ? $payload : json_encode($payload);
295
        Assertion::notNull($prepared, 'The payload is empty or cannot encoded into JSON.');
296
297
        if (null === $compression_method) {
298
            return $prepared;
299
        }
300
        $compressed_payload = $compression_method->compress($prepared);
301
        Assertion::string($compressed_payload, 'Compression failed.');
302
303
        return $compressed_payload;
304
    }
305
306
    /**
307
     * @param array                                               $complete_headers
308
     * @param string                                              $cek
309
     * @param \Jose\Algorithm\KeyEncryptionAlgorithmInterface     $key_encryption_algorithm
310
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm
311
     * @param \Jose\Object\JWKInterface                           $recipient_key
312
     * @param array                                               $additional_headers
313
     *
314
     * @return string|null
315
     */
316
    private function getEncryptedKey(array $complete_headers, $cek, Algorithm\KeyEncryptionAlgorithmInterface $key_encryption_algorithm, Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm, array &$additional_headers, Object\JWKInterface $recipient_key)
317
    {
318
        if ($key_encryption_algorithm instanceof Algorithm\KeyEncryption\KeyEncryptionInterface) {
319
            return $this->getEncryptedKeyFromKeyEncryptionAlgorithm($complete_headers, $cek, $key_encryption_algorithm, $recipient_key, $additional_headers);
320
        } elseif ($key_encryption_algorithm instanceof Algorithm\KeyEncryption\KeyWrappingInterface) {
321
            return $this->getEncryptedKeyFromKeyWrappingAlgorithm($complete_headers, $cek, $key_encryption_algorithm, $recipient_key, $additional_headers);
322
        } elseif ($key_encryption_algorithm instanceof Algorithm\KeyEncryption\KeyAgreementWrappingInterface) {
323
            return $this->getEncryptedKeyFromKeyAgreementAndKeyWrappingAlgorithm($complete_headers, $cek, $key_encryption_algorithm, $content_encryption_algorithm, $additional_headers, $recipient_key);
324
        }
325
    }
326
327
    /**
328
     * @param array                                                       $complete_headers
329
     * @param string                                                      $cek
330
     * @param \Jose\Algorithm\KeyEncryption\KeyAgreementWrappingInterface $key_encryption_algorithm
331
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface         $content_encryption_algorithm
332
     * @param array                                                       $additional_headers
333
     * @param \Jose\Object\JWKInterface                                   $recipient_key
334
     *
335
     * @return string
336
     */
337
    private function getEncryptedKeyFromKeyAgreementAndKeyWrappingAlgorithm(array $complete_headers, $cek, Algorithm\KeyEncryption\KeyAgreementWrappingInterface $key_encryption_algorithm, Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm, array &$additional_headers, Object\JWKInterface $recipient_key)
338
    {
339
        $jwt_cek = $key_encryption_algorithm->wrapAgreementKey($recipient_key, $cek, $content_encryption_algorithm->getCEKSize(), $complete_headers, $additional_headers);
340
341
        return $jwt_cek;
342
    }
343
344
    /**
345
     * @param array                                                $complete_headers
346
     * @param string                                               $cek
347
     * @param \Jose\Algorithm\KeyEncryption\KeyEncryptionInterface $key_encryption_algorithm
348
     * @param \Jose\Object\JWKInterface                            $recipient_key
349
     * @param array                                                $additional_headers
350
     *
351
     * @return string
352
     */
353
    private function getEncryptedKeyFromKeyEncryptionAlgorithm(array $complete_headers, $cek, Algorithm\KeyEncryption\KeyEncryptionInterface $key_encryption_algorithm, Object\JWKInterface $recipient_key, array &$additional_headers)
354
    {
355
        return $key_encryption_algorithm->encryptKey($recipient_key,  $cek, $complete_headers, $additional_headers);
356
    }
357
358
    /**
359
     * @param array                                              $complete_headers
360
     * @param string                                             $cek
361
     * @param \Jose\Algorithm\KeyEncryption\KeyWrappingInterface $key_encryption_algorithm
362
     * @param \Jose\Object\JWKInterface                          $recipient_key
363
     * @param array                                              $additional_headers
364
     *
365
     * @return string
366
     */
367
    private function getEncryptedKeyFromKeyWrappingAlgorithm(array $complete_headers, $cek, Algorithm\KeyEncryption\KeyWrappingInterface $key_encryption_algorithm, Object\JWKInterface $recipient_key, &$additional_headers)
368
    {
369
        return $key_encryption_algorithm->wrapKey($recipient_key, $cek, $complete_headers, $additional_headers);
370
    }
371
372
    /**
373
     * @param array $complete_headers
374
     *
375
     * @return \Jose\Algorithm\KeyEncryptionAlgorithmInterface
376
     */
377
    private function findKeyEncryptionAlgorithm(array $complete_headers)
378
    {
379
        Assertion::keyExists($complete_headers, 'alg', 'Parameter "alg" is missing.');
380
        $key_encryption_algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['alg']);
381
        Assertion::isInstanceOf($key_encryption_algorithm, Algorithm\KeyEncryptionAlgorithmInterface::class, sprintf('The key encryption algorithm "%s" is not supported or not a key encryption algorithm instance.', $complete_headers['alg']));
382
383
        return $key_encryption_algorithm;
384
    }
385
386
    /**
387
     * @param int $size
388
     *
389
     * @return string
390
     */
391
    private function createCEK($size)
392
    {
393
        return random_bytes($size / 8);
394
    }
395
396
    /**
397
     * @param int $size
398
     *
399
     * @return string
400
     */
401
    private function createIV($size)
402
    {
403
        return random_bytes($size / 8);
404
    }
405
}
406