Completed
Push — master ( 16bc05...356550 )
by sebastian
03:03
created

src/jwe/impl/JWE.php (8 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
     * @return string
176
     */
177
     private function getJWEEncryptedKey(EncryptionAlgorithm $alg, Key $recipient_public_key)
178
     {
179
        /**
180
         * When Key Wrapping, Key Encryption, or Key Agreement with Key
181
         * Wrapping are employed, encrypt the CEK to the recipient and let
182
         * the result be the JWE Encrypted Key.
183
         */
184
         $key_management_mode = $this->getKeyManagementMode($alg);
185
         switch($key_management_mode){
186
             case KeyManagementModeValues::KeyEncryption:
187
             case KeyManagementModeValues::KeyWrapping:
188
             case KeyManagementModeValues::KeyAgreementWithKeyWrapping:
0 ignored issues
show
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...
189
             {
190
                 return $alg->encrypt($recipient_public_key, $this->cek->getEncoded());
191
             }
192
             /**
193
              * When Direct Key Agreement or Direct Encryption are employed, let
194
              * the JWE Encrypted Key be the empty octet sequence.
195
              */
196
             default:
197
             return '';
198
         }
199
     }
200
201
    /**
202
     * Determine the Key Management Mode employed by the algorithm used
203
     * to determine the Content Encryption Key value.  (This is the
204
     * algorithm recorded in the "alg" (algorithm) Header Parameter of
205
     * the resulting JWE.)
206
     * @param EncryptionAlgorithm $alg
207
     * @return string
208
     */
209
    private function getKeyManagementMode(EncryptionAlgorithm $alg)
210
    {
211
        if($alg instanceof KeyEncryption)
212
            return KeyManagementModeValues::KeyEncryption;
213
        if($alg instanceof KeyWrapping)
214
            return KeyManagementModeValues::KeyWrapping;
215
        if($alg instanceof DirectKeyAgreement)
216
            return KeyManagementModeValues::DirectKeyAgreement;
217
        if($alg instanceof KeyAgreementWithKeyWrapping)
218
            return KeyManagementModeValues::KeyAgreementWithKeyWrapping;
219
        if($alg instanceof DirectEncryption)
220
            return KeyManagementModeValues::DirectEncryption;
221
    }
222
223
    /**
224
     * @return $this
225
     * @throws InvalidJWKAlgorithm
226
     * @throws InvalidKeyTypeAlgorithmException
227
     * @throws JWEInvalidRecipientKeyException
228
     * @throws JWEUnsupportedContentEncryptionAlgorithmException
229
     * @throws JWEUnsupportedKeyManagementAlgorithmException
230
     * @throws \Exception
231
     */
232
    private function encrypt()
233
    {
234
235
        if (is_null($this->jwk))
236
            throw new JWEInvalidRecipientKeyException;
237
238 View Code Duplication
        if($this->jwk->getAlgorithm()->getValue()!== $this->header->getAlgorithm()->getString())
0 ignored issues
show
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...
239
            throw new InvalidJWKAlgorithm
240
            (
241
                sprintf
242
                (
243
                    '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',
244
                    $this->jwk->getAlgorithm()->getValue(),
245
                    $this->header->getAlgorithm()->getString()
246
                )
247
            );
248
249
        $recipient_public_key     = $this->jwk->getKey(JSONWebKeyKeyOperationsValues::EncryptContent);
250
251
        $key_management_algorithm = KeyManagementAlgorithms_Registry::getInstance()->get($this->header->getAlgorithm()->getString());
252
253
        if (is_null($key_management_algorithm))
254
            throw new JWEUnsupportedKeyManagementAlgorithmException(sprintf('alg %s', $this->header->getAlgorithm()->getString()));
255
256
        if($key_management_algorithm->getKeyType() !== $recipient_public_key->getAlgorithm())
257
            throw new InvalidKeyTypeAlgorithmException
258
            (
259
                sprintf
260
                (
261
                    'key should be for alg %s, %s instead.',
262
                    $key_management_algorithm->getKeyType(),
263
                    $recipient_public_key->getAlgorithm()
264
                )
265
            );
266
267
        $content_encryption_algorithm = ContentEncryptionAlgorithms_Registry::getInstance()->get
268
        (
269
            $this->header->getEncryptionAlgorithm()->getString()
270
        );
271
272
        if (is_null($content_encryption_algorithm))
273
            throw new JWEUnsupportedContentEncryptionAlgorithmException
274
            (
275
                sprintf
276
                (
277
                    'enc %s',
278
                    $this->header->getEncryptionAlgorithm()->getString()
279
                )
280
            );
281
282
        $key_management_mode = $this->getKeyManagementMode($key_management_algorithm);
283
284
        $this->cek     = ContentEncryptionKeyFactory::build
285
        (
286
            $recipient_public_key,
287
            $key_management_mode,
288
            $content_encryption_algorithm
289
        );
290
291
        $this->enc_cek = $this->getJWEEncryptedKey($key_management_algorithm, $recipient_public_key);
292
293
        /**
294
         * Generate a random JWE Initialization Vector of the correct size
295
         * for the content encryption algorithm (if required for the
296
         * algorithm); otherwise, let the JWE Initialization Vector be the
297
         * empty octet sequence.
298
         */
299
        $this->iv      = '';
300
301
        if (!is_null($iv_size = $content_encryption_algorithm->getIVSize()))
302
        {
303
            $this->iv = $this->createIV($iv_size);
304
        }
305
        // We encrypt the payload and get the tag
306
        $jwt_shared_protected_header = JOSEHeaderSerializer::serialize($this->header);
307
308
        $payload = ($this->payload instanceof IJWSPayloadRawSpec) ? $this->payload->getRaw():'';
309
        $zip     = $this->header->getCompressionAlgorithm();
310
        /**
311
         * If a "zip" parameter was included, compress the plaintext using
312
         * the specified compression algorithm and let M be the octet
313
         * sequence representing the compressed plaintext; otherwise, let M
314
         * be the octet sequence representing the plaintext.
315
         */
316 View Code Duplication
        if(!is_null($zip))
0 ignored issues
show
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...
317
        {
318
            $compression__algorithm = CompressionAlgorithms_Registry::getInstance()->get($zip->getValue());
319
            $payload  = $compression__algorithm->compress($payload);
320
        }
321
322
        /**
323
         * Encrypt M using the CEK, the JWE Initialization Vector, and the
324
         * Additional Authenticated Data value using the specified content
325
         * encryption algorithm to create the JWE Ciphertext value and the
326
         * JWE Authentication Tag (which is the Authentication Tag output
327
         * from the encryption operation).
328
         */
329
        list($this->cipher_text, $this->tag) = $content_encryption_algorithm->encrypt
330
        (
331
            $payload,
332
            $this->cek->getEncoded(),
333
            $this->iv,
334
            $jwt_shared_protected_header
335
        );
336
337
        return $this;
338
    }
339
340
341
    /**
342
     * @param EncryptionAlgorithm $alg
343
     * @return null|Key
344
     * @throws JWEInvalidCompactFormatException
345
     * @throws InvalidKeyTypeAlgorithmException
346
     * @throws \Exception
347
     */
348
    private function decryptJWEEncryptedKey(EncryptionAlgorithm $alg){
349
350
        $key_management_mode   = $this->getKeyManagementMode($alg);
351
        $recipient_private_key = $this->jwk->getKey(JSONWebKeyKeyOperationsValues::DecryptContentAndValidateDecryption);
352
353
        if($alg->getKeyType() !== $recipient_private_key->getAlgorithm())
354
            throw new InvalidKeyTypeAlgorithmException
355
            (
356
                sprintf
357
                (
358
                    'key should be for alg %s, %s instead.',
359
                    $alg->getKeyType(),
360
                    $recipient_private_key->getAlgorithm()
361
                )
362
            );
363
364
        switch($key_management_mode){
365
            /**
366
             * When Key Wrapping, Key Encryption, or Key Agreement with Key
367
             * Wrapping are employed, decrypt the JWE Encrypted Key to produce
368
             * the CEK.  The CEK MUST have a length equal to that required for
369
             * the content encryption algorithm
370
             */
371
            case KeyManagementModeValues::KeyEncryption:
372
            case KeyManagementModeValues::KeyWrapping:
373
            case KeyManagementModeValues::KeyAgreementWithKeyWrapping:
0 ignored issues
show
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...
374
            {
375
376
                return ContentEncryptionKeyFactory::fromRaw($alg->decrypt($recipient_private_key, $this->enc_cek), $alg);
377
            }
378
            /**
379
             * When Direct Key Agreement or Direct Encryption are employed,
380
             * verify that the JWE Encrypted Key value is an empty octetsequence.
381
             * When Direct Encryption is employed, let the CEK be the shared
382
             * symmetric key.
383
             */
384
            case KeyManagementModeValues::DirectEncryption:
0 ignored issues
show
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...
385
            {
386
                if (!empty($this->enc_cek))
387
                    throw new JWEInvalidCompactFormatException('JWE Encrypted Key value is not an empty octetsequence.');
388
                return $recipient_private_key;
389
            }
390
            case KeyManagementModeValues::DirectKeyAgreement:
0 ignored issues
show
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...
391
            {
392
                if (!empty($this->enc_cek))
393
                    throw new JWEInvalidCompactFormatException('JWE Encrypted Key value is not an empty octetsequence.');
394
                throw new \Exception('unsupported Key Management Mode!');
395
            }
396
        }
397
        return null;
398
    }
399
400
    /**
401
     * @return $this
402
     * @throws InvalidJWKAlgorithm
403
     * @throws InvalidKeyTypeAlgorithmException
404
     * @throws JWEInvalidCompactFormatException
405
     * @throws JWEInvalidRecipientKeyException
406
     * @throws JWEUnsupportedContentEncryptionAlgorithmException
407
     * @throws JWEUnsupportedKeyManagementAlgorithmException
408
     * @throws \Exception
409
     */
410
    private function decrypt()
411
    {
412
        if (is_null($this->jwk))
413
            throw new JWEInvalidRecipientKeyException();
414
415
        if (!$this->should_decrypt) return $this;
416
417 View Code Duplication
        if($this->jwk->getAlgorithm()->getValue()!== $this->header->getAlgorithm()->getString())
0 ignored issues
show
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...
418
            throw new InvalidJWKAlgorithm
419
            (
420
                sprintf
421
                (
422
                    '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',
423
                    $this->jwk->getAlgorithm()->getValue(),
424
                    $this->header->getAlgorithm()->getString()
425
                )
426
            );
427
428
        $key_management_algorithm = KeyManagementAlgorithms_Registry::getInstance()->get
429
        (
430
            $this->header->getAlgorithm()->getString()
431
        );
432
433
        if (is_null($key_management_algorithm))
434
            throw new JWEUnsupportedKeyManagementAlgorithmException
435
            (
436
                sprintf
437
                (
438
                    'alg %s',
439
                    $this->header->getAlgorithm()->getString()
440
                )
441
            );
442
443
        $content_encryption_algorithm = ContentEncryptionAlgorithms_Registry::getInstance()->get
444
        (
445
            $this->header->getEncryptionAlgorithm()->getString()
446
        );
447
448
        if (is_null($content_encryption_algorithm))
449
            throw new JWEUnsupportedContentEncryptionAlgorithmException
450
            (
451
                sprintf
452
                (
453
                    'enc %s',
454
                    $this->header->getEncryptionAlgorithm()->getString()
455
                )
456
            );
457
458
        $this->cek = $this->decryptJWEEncryptedKey($key_management_algorithm);
459
460
        // We encrypt the payload and get the tag
461
        $jwt_shared_protected_header = JOSEHeaderSerializer::serialize($this->header);
462
463
        /**
464
         * Decrypt the JWE Cipher Text using the CEK, the JWE Initialization
465
         * Vector, the Additional Authenticated Data value, and the JWE
466
         * Authentication Tag (which is the Authentication Tag input to the
467
         * calculation) using the specified content encryption algorithm,
468
         * returning the decrypted plaintext and validating the JWE
469
         * Authentication Tag in the manner specified for the algorithm,
470
         * rejecting the input without emitting any decrypted output if the
471
         * JWE Authentication Tag is incorrect.
472
         */
473
        $plain_text = $content_encryption_algorithm->decrypt
474
        (
475
            $this->cipher_text,
476
            $this->cek->getEncoded(),
477
            $this->iv,
478
            $jwt_shared_protected_header,
479
            $this->tag
480
        );
481
482
        $zip     = $this->header->getCompressionAlgorithm();
483
        /**
484
         * If a "zip" parameter was included, uncompress the decrypted
485
         * plaintext using the specified compression algorithm.
486
         */
487 View Code Duplication
        if(!is_null($zip))
0 ignored issues
show
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...
488
        {
489
            $compression__algorithm = CompressionAlgorithms_Registry::getInstance()->get($zip->getValue());
490
            $plain_text = $compression__algorithm->uncompress($plain_text);
491
        }
492
493
        $this->setPayload(JWSPayloadFactory::build($plain_text));
494
        $this->should_decrypt = false;
495
496
        return $this;
497
    }
498
499
    /**
500
     * @return array
501
     */
502
    public function take()
503
    {
504
        return array(
505
            $this->header,
506
            $this->enc_cek,
507
            $this->iv,
508
            $this->cipher_text,
509
            $this->tag);
510
    }
511
512
    /**
513
     * @param IJWEJOSEHeader $header
514
     * @param IJWSPayloadSpec $payload
515
     * @return IJWE
516
     */
517
    public static function fromHeaderAndPayload(IJWEJOSEHeader $header, IJWSPayloadSpec $payload)
518
    {
519
        return new JWE($header, $payload);
520
    }
521
522
    /**
523
     * @param string $compact_serialization
524
     * @return IJWE
525
     * @throws JWEInvalidCompactFormatException
526
     * @access private
527
     */
528
    public static function fromCompactSerialization($compact_serialization)
529
    {
530
531
        list($header, $enc_cek, $iv, $cipher_text, $tag) = JWESerializer::deserialize($compact_serialization);
532
        $jwe     = new JWE($header);
533
        $jwe->iv = $iv;
534
        $jwe->tag = $tag;
535
        $jwe->enc_cek = $enc_cek;
536
        $jwe->cipher_text = $cipher_text;
537
        $jwe->should_decrypt = true;
538
        return $jwe;
539
    }
540
}