Completed
Push — master ( 35fc1b...16bc05 )
by sebastian
03:06
created

JWE::encrypt()   D

Complexity

Conditions 9
Paths 13

Size

Total Lines 107
Code Lines 47

Duplication

Lines 15
Ratio 14.02 %

Importance

Changes 6
Bugs 0 Features 3
Metric Value
c 6
b 0
f 3
dl 15
loc 107
rs 4.8196
cc 9
eloc 47
nc 13
nop 0

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Copyright 2015 OpenStack Foundation
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 * http://www.apache.org/licenses/LICENSE-2.0
8
 * Unless required by applicable law or agreed to in writing, software
9
 * distributed under the License is distributed on an "AS IS" BASIS,
10
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
 * See the License for the specific language governing permissions and
12
 * limitations under the License.
13
 **/
14
15
namespace jwe\impl;
16
17
use jwa\cryptographic_algorithms\ContentEncryptionAlgorithms_Registry;
18
use jwa\cryptographic_algorithms\EncryptionAlgorithm;
19
use jwa\cryptographic_algorithms\exceptions\InvalidKeyTypeAlgorithmException;
20
use jwa\cryptographic_algorithms\key_management\modes\DirectEncryption;
21
use jwa\cryptographic_algorithms\key_management\modes\DirectKeyAgreement;
22
use jwa\cryptographic_algorithms\key_management\modes\KeyAgreementWithKeyWrapping;
23
use jwa\cryptographic_algorithms\key_management\modes\KeyEncryption;
24
use jwa\cryptographic_algorithms\key_management\modes\KeyWrapping;
25
use jwa\cryptographic_algorithms\KeyManagementAlgorithms_Registry;
26
use jwe\exceptions\JWEInvalidCompactFormatException;
27
use jwe\exceptions\JWEInvalidRecipientKeyException;
28
use jwe\exceptions\JWEUnsupportedContentEncryptionAlgorithmException;
29
use jwe\exceptions\JWEUnsupportedKeyManagementAlgorithmException;
30
use jwe\compression_algorithms\CompressionAlgorithms_Registry;
31
use jwe\IJWEJOSEHeader;
32
use jwe\IJWE;
33
use jwe\KeyManagementModeValues;
34
use jwk\exceptions\InvalidJWKAlgorithm;
35
use jwk\IJWK;
36
use jwk\JSONWebKeyKeyOperationsValues;
37
use jws\IJWSPayloadRawSpec;
38
use jws\IJWSPayloadSpec;
39
use jws\payloads\JWSPayloadFactory;
40
use jwt\utils\JOSEHeaderSerializer;
41
use security\Key;
42
43
/**
44
 * Class JWE
45
 * @package jwe\impl
46
 * @access private
47
 */
48
final class JWE implements IJWE, IJWESnapshot
49
{
50
51
    /**
52
     * @var IJWK
53
     */
54
    private $jwk = null;
55
56
    /**
57
     * @var IJWSPayloadSpec
58
     */
59
    private $payload = null;
60
61
    /**
62
     * @var IJWEJOSEHeader
63
     */
64
    private $header;
65
66
    /**
67
     * @var Key
68
     */
69
    private $cek = null;
70
71
    /**
72
     * @var string
73
     */
74
    private $tag = null;
75
76
    /**
77
     * @var string
78
     */
79
    private $cipher_text = null;
80
81
    /**
82
     * @var string
83
     */
84
    private $iv;
85
86
    /**
87
     * @var string
88
     */
89
    private $enc_cek = null;
90
91
    private $should_decrypt = false;
92
93
    /**
94
     * @param IJWEJOSEHeader $header
95
     * @param IJWSPayloadSpec $payload
96
     */
97
    protected function __construct(IJWEJOSEHeader $header, IJWSPayloadSpec $payload = null)
98
    {
99
        $this->header = $header;
100
        if(!is_null($payload))
101
            $this->setPayload($payload);
102
    }
103
104
    /**
105
     * @param IJWK $recipient_key
106
     * @return $this
107
     */
108
    public function setRecipientKey(IJWK $recipient_key)
109
    {
110
        $this->jwk = $recipient_key;
111
        return $this;
112
    }
113
114
    /**
115
     * @param IJWSPayloadSpec $payload
116
     * @return $this
117
     */
118
    public function setPayload(IJWSPayloadSpec $payload)
119
    {
120
        $this->payload = $payload;
121
        return $this;
122
    }
123
124
    /**
125
     * @param int $size
126
     * @return String
127
     */
128
    protected function createIV($size)
129
    {
130
        return IVFactory::build($size);
131
    }
132
133
    /**
134
     * @throws JWEInvalidRecipientKeyException
135
     * @throws JWEUnsupportedContentEncryptionAlgorithmException
136
     * @throws JWEUnsupportedKeyManagementAlgorithmException
137
     * @return string
138
     */
139
    public function toCompactSerialization()
140
    {
141
        return JWESerializer::serialize($this->encrypt());
142
    }
143
144
    /**
145
     * @return mixed
146
     * @throws JWEInvalidRecipientKeyException
147
     * @throws JWEUnsupportedContentEncryptionAlgorithmException
148
     * @throws JWEUnsupportedKeyManagementAlgorithmException
149
     */
150
    public function getPlainText()
151
    {
152
        if ($this->should_decrypt)
153
        {
154
            $this->decrypt();
155
        }
156
157
        if (is_null($this->payload))
158
            $this->payload = JWSPayloadFactory::build('');
159
160
        return ($this->payload instanceof IJWSPayloadRawSpec) ? $this->payload->getRaw():'';
161
    }
162
163
    /**
164
     * @return IJWEJOSEHeader
165
     */
166
    public function getJOSEHeader()
167
    {
168
        return $this->header;
169
    }
170
171
172
    /***
173
     * @param EncryptionAlgorithm $alg
174
     * @param Key $recipient_public_key
175
     * @param Key $cek
176
     * @return string
177
     */
178
     private function getJWEEncryptedKey(EncryptionAlgorithm $alg, Key $recipient_public_key, Key $cek)
0 ignored issues
show
Unused Code introduced by
The parameter $cek is not used and could be removed.

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

Loading history...
179
     {
180
        /**
181
         * When Key Wrapping, Key Encryption, or Key Agreement with Key
182
         * Wrapping are employed, encrypt the CEK to the recipient and let
183
         * the result be the JWE Encrypted Key.
184
         */
185
         $key_management_mode = $this->getKeyManagementMode($alg);
186
         switch($key_management_mode){
187
             case KeyManagementModeValues::KeyEncryption:
188
             case KeyManagementModeValues::KeyWrapping:
189
             case KeyManagementModeValues::KeyAgreementWithKeyWrapping:
0 ignored issues
show
Coding Style introduced by
CASE statements must be defined using a colon

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
190
             {
191
                 return $alg->encrypt($recipient_public_key, $this->cek->getEncoded());
192
             }
193
             break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
194
             /**
195
              * When Direct Key Agreement or Direct Encryption are employed, let
196
              * the JWE Encrypted Key be the empty octet sequence.
197
              */
198
             default:
199
                 return '';
200
             break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
201
         }
202
     }
203
204
    /**
205
     * Determine the Key Management Mode employed by the algorithm used
206
     * to determine the Content Encryption Key value.  (This is the
207
     * algorithm recorded in the "alg" (algorithm) Header Parameter of
208
     * the resulting JWE.)
209
     * @param EncryptionAlgorithm $alg
210
     * @return string
211
     */
212
    private function getKeyManagementMode(EncryptionAlgorithm $alg)
213
    {
214
        if($alg instanceof KeyEncryption)
215
            return KeyManagementModeValues::KeyEncryption;
216
        if($alg instanceof KeyWrapping)
217
            return KeyManagementModeValues::KeyWrapping;
218
        if($alg instanceof DirectKeyAgreement)
219
            return KeyManagementModeValues::DirectKeyAgreement;
220
        if($alg instanceof KeyAgreementWithKeyWrapping)
221
            return KeyManagementModeValues::KeyAgreementWithKeyWrapping;
222
        if($alg instanceof DirectEncryption)
223
            return KeyManagementModeValues::DirectEncryption;
224
    }
225
226
    /**
227
     * @return $this
228
     * @throws InvalidJWKAlgorithm
229
     * @throws InvalidKeyTypeAlgorithmException
230
     * @throws JWEInvalidRecipientKeyException
231
     * @throws JWEUnsupportedContentEncryptionAlgorithmException
232
     * @throws JWEUnsupportedKeyManagementAlgorithmException
233
     * @throws \Exception
234
     */
235
    private function encrypt()
236
    {
237
238
        if (is_null($this->jwk))
239
            throw new JWEInvalidRecipientKeyException;
240
241 View Code Duplication
        if($this->jwk->getAlgorithm()->getValue()!== $this->header->getAlgorithm()->getString())
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
242
            throw new InvalidJWKAlgorithm
243
            (
244
                sprintf
245
                (
246
                    'mismatch between algorithm intended for use with the key %s and the cryptographic algorithm used to encrypt or determine the value of the CEK %s',
247
                    $this->jwk->getAlgorithm()->getValue(),
248
                    $this->header->getAlgorithm()->getString()
249
                )
250
            );
251
252
        $recipient_public_key     = $this->jwk->getKey(JSONWebKeyKeyOperationsValues::EncryptContent);
253
254
        $key_management_algorithm = KeyManagementAlgorithms_Registry::getInstance()->get($this->header->getAlgorithm()->getString());
255
256
        if (is_null($key_management_algorithm))
257
            throw new JWEUnsupportedKeyManagementAlgorithmException(sprintf('alg %s', $this->header->getAlgorithm()->getString()));
258
259
        if($key_management_algorithm->getKeyType() !== $recipient_public_key->getAlgorithm())
260
            throw new InvalidKeyTypeAlgorithmException
261
            (
262
                sprintf
263
                (
264
                    'key should be for alg %s, %s instead.',
265
                    $key_management_algorithm->getKeyType(),
266
                    $recipient_public_key->getAlgorithm()
267
                )
268
            );
269
270
        $content_encryption_algorithm = ContentEncryptionAlgorithms_Registry::getInstance()->get
271
        (
272
            $this->header->getEncryptionAlgorithm()->getString()
273
        );
274
275
        if (is_null($content_encryption_algorithm))
276
            throw new JWEUnsupportedContentEncryptionAlgorithmException
277
            (
278
                sprintf
279
                (
280
                    'enc %s',
281
                    $this->header->getEncryptionAlgorithm()->getString()
282
                )
283
            );
284
285
        $key_management_mode = $this->getKeyManagementMode($key_management_algorithm);
286
287
        $this->cek     = ContentEncryptionKeyFactory::build
288
        (
289
            $recipient_public_key,
290
            $key_management_mode,
291
            $content_encryption_algorithm
292
        );
293
294
        $this->enc_cek = $this->getJWEEncryptedKey($key_management_algorithm, $recipient_public_key, $this->cek);
295
296
        /**
297
         * Generate a random JWE Initialization Vector of the correct size
298
         * for the content encryption algorithm (if required for the
299
         * algorithm); otherwise, let the JWE Initialization Vector be the
300
         * empty octet sequence.
301
         */
302
        $this->iv      = '';
303
304
        if (!is_null($iv_size = $content_encryption_algorithm->getIVSize()))
305
        {
306
            $this->iv = $this->createIV($iv_size);
307
        }
308
        // We encrypt the payload and get the tag
309
        $jwt_shared_protected_header = JOSEHeaderSerializer::serialize($this->header);
310
311
        $payload = ($this->payload instanceof IJWSPayloadRawSpec) ? $this->payload->getRaw():'';
312
        $zip     = $this->header->getCompressionAlgorithm();
313
        /**
314
         * If a "zip" parameter was included, compress the plaintext using
315
         * the specified compression algorithm and let M be the octet
316
         * sequence representing the compressed plaintext; otherwise, let M
317
         * be the octet sequence representing the plaintext.
318
         */
319 View Code Duplication
        if(!is_null($zip))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
320
        {
321
            $compression__algorithm = CompressionAlgorithms_Registry::getInstance()->get($zip->getValue());
322
            $payload  = $compression__algorithm->compress($payload);
323
        }
324
325
        /**
326
         * Encrypt M using the CEK, the JWE Initialization Vector, and the
327
         * Additional Authenticated Data value using the specified content
328
         * encryption algorithm to create the JWE Ciphertext value and the
329
         * JWE Authentication Tag (which is the Authentication Tag output
330
         * from the encryption operation).
331
         */
332
        list($this->cipher_text, $this->tag) = $content_encryption_algorithm->encrypt
333
        (
334
            $payload,
335
            $this->cek->getEncoded(),
336
            $this->iv,
337
            $jwt_shared_protected_header
338
        );
339
340
        return $this;
341
    }
342
343
344
    /**
345
     * @param EncryptionAlgorithm $alg
346
     * @return null|Key
347
     * @throws JWEInvalidCompactFormatException
348
     * @throws InvalidKeyTypeAlgorithmException
349
     * @throws \Exception
350
     */
351
    private function decryptJWEEncryptedKey(EncryptionAlgorithm $alg){
352
353
        $key_management_mode   = $this->getKeyManagementMode($alg);
354
        $recipient_private_key = $this->jwk->getKey(JSONWebKeyKeyOperationsValues::DecryptContentAndValidateDecryption);
355
356
        if($alg->getKeyType() !== $recipient_private_key->getAlgorithm())
357
            throw new InvalidKeyTypeAlgorithmException
358
            (
359
                sprintf
360
                (
361
                    'key should be for alg %s, %s instead.',
362
                    $alg->getKeyType(),
363
                    $recipient_private_key->getAlgorithm()
364
                )
365
            );
366
367
        switch($key_management_mode){
368
            /**
369
             * When Key Wrapping, Key Encryption, or Key Agreement with Key
370
             * Wrapping are employed, decrypt the JWE Encrypted Key to produce
371
             * the CEK.  The CEK MUST have a length equal to that required for
372
             * the content encryption algorithm
373
             */
374
            case KeyManagementModeValues::KeyEncryption:
375
            case KeyManagementModeValues::KeyWrapping:
376
            case KeyManagementModeValues::KeyAgreementWithKeyWrapping:
0 ignored issues
show
Coding Style introduced by
CASE statements must be defined using a colon

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
377
            {
378
379
                return ContentEncryptionKeyFactory::fromRaw($alg->decrypt($recipient_private_key, $this->enc_cek), $alg);
380
            }
381
            break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
382
            /**
383
             * When Direct Key Agreement or Direct Encryption are employed,
384
             * verify that the JWE Encrypted Key value is an empty octetsequence.
385
             * When Direct Encryption is employed, let the CEK be the shared
386
             * symmetric key.
387
             */
388
            case KeyManagementModeValues::DirectEncryption:
389
                if(!empty($this->enc_cek))
390
                    throw new JWEInvalidCompactFormatException('JWE Encrypted Key value is not an empty octetsequence.');
391
                return $recipient_private_key;
392
            break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
393
            case KeyManagementModeValues::DirectKeyAgreement:
394
                if(!empty($this->enc_cek))
395
                    throw new JWEInvalidCompactFormatException('JWE Encrypted Key value is not an empty octetsequence.');
396
                throw new \Exception('unsupported Key Management Mode!');
397
            break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
398
        }
399
        return null;
400
    }
401
402
    /**
403
     * @return $this
404
     * @throws InvalidJWKAlgorithm
405
     * @throws InvalidKeyTypeAlgorithmException
406
     * @throws JWEInvalidCompactFormatException
407
     * @throws JWEInvalidRecipientKeyException
408
     * @throws JWEUnsupportedContentEncryptionAlgorithmException
409
     * @throws JWEUnsupportedKeyManagementAlgorithmException
410
     * @throws \Exception
411
     */
412
    private function decrypt()
413
    {
414
        if (is_null($this->jwk))
415
            throw new JWEInvalidRecipientKeyException();
416
417
        if (!$this->should_decrypt) return $this;
418
419 View Code Duplication
        if($this->jwk->getAlgorithm()->getValue()!== $this->header->getAlgorithm()->getString())
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
420
            throw new InvalidJWKAlgorithm
421
            (
422
                sprintf
423
                (
424
                    'mismatch between algorithm intended for use with the key %s and the cryptographic algorithm used to encrypt or determine the value of the CEK %s',
425
                    $this->jwk->getAlgorithm()->getValue(),
426
                    $this->header->getAlgorithm()->getString()
427
                )
428
            );
429
430
        $key_management_algorithm = KeyManagementAlgorithms_Registry::getInstance()->get
431
        (
432
            $this->header->getAlgorithm()->getString()
433
        );
434
435
        if (is_null($key_management_algorithm))
436
            throw new JWEUnsupportedKeyManagementAlgorithmException
437
            (
438
                sprintf
439
                (
440
                    'alg %s',
441
                    $this->header->getAlgorithm()->getString()
442
                )
443
            );
444
445
        $content_encryption_algorithm = ContentEncryptionAlgorithms_Registry::getInstance()->get
446
        (
447
            $this->header->getEncryptionAlgorithm()->getString()
448
        );
449
450
        if (is_null($content_encryption_algorithm))
451
            throw new JWEUnsupportedContentEncryptionAlgorithmException
452
            (
453
                sprintf
454
                (
455
                    'enc %s',
456
                    $this->header->getEncryptionAlgorithm()->getString()
457
                )
458
            );
459
460
        $this->cek = $this->decryptJWEEncryptedKey($key_management_algorithm);
461
462
        // We encrypt the payload and get the tag
463
        $jwt_shared_protected_header = JOSEHeaderSerializer::serialize($this->header);
464
465
        /**
466
         * Decrypt the JWE Cipher Text using the CEK, the JWE Initialization
467
         * Vector, the Additional Authenticated Data value, and the JWE
468
         * Authentication Tag (which is the Authentication Tag input to the
469
         * calculation) using the specified content encryption algorithm,
470
         * returning the decrypted plaintext and validating the JWE
471
         * Authentication Tag in the manner specified for the algorithm,
472
         * rejecting the input without emitting any decrypted output if the
473
         * JWE Authentication Tag is incorrect.
474
         */
475
        $plain_text = $content_encryption_algorithm->decrypt
476
        (
477
            $this->cipher_text,
478
            $this->cek->getEncoded(),
479
            $this->iv,
480
            $jwt_shared_protected_header,
481
            $this->tag
482
        );
483
484
        $zip     = $this->header->getCompressionAlgorithm();
485
        /**
486
         * If a "zip" parameter was included, uncompress the decrypted
487
         * plaintext using the specified compression algorithm.
488
         */
489 View Code Duplication
        if(!is_null($zip))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
490
        {
491
            $compression__algorithm = CompressionAlgorithms_Registry::getInstance()->get($zip->getValue());
492
            $plain_text = $compression__algorithm->uncompress($plain_text);
493
        }
494
495
        $this->setPayload(JWSPayloadFactory::build($plain_text));
496
        $this->should_decrypt = false;
497
498
        return $this;
499
    }
500
501
    /**
502
     * @return array
503
     */
504
    public function take()
505
    {
506
        return array(
507
            $this->header,
508
            $this->enc_cek,
509
            $this->iv,
510
            $this->cipher_text,
511
            $this->tag);
512
    }
513
514
    /**
515
     * @param IJWEJOSEHeader $header
516
     * @param IJWSPayloadSpec $payload
517
     * @return IJWE
518
     */
519
    public static function fromHeaderAndPayload(IJWEJOSEHeader $header, IJWSPayloadSpec $payload)
520
    {
521
        return new JWE($header, $payload);
522
    }
523
524
    /**
525
     * @param string $compact_serialization
526
     * @return IJWE
527
     * @throws JWEInvalidCompactFormatException
528
     * @access private
529
     */
530
    public static function fromCompactSerialization($compact_serialization)
531
    {
532
533
        list($header, $enc_cek, $iv, $cipher_text, $tag) = JWESerializer::deserialize($compact_serialization);
534
        $jwe     = new JWE($header);
535
        $jwe->iv = $iv;
536
        $jwe->tag = $tag;
537
        $jwe->enc_cek = $enc_cek;
538
        $jwe->cipher_text = $cipher_text;
539
        $jwe->should_decrypt = true;
540
        return $jwe;
541
    }
542
}