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

src/jwe/impl/JWE.php (5 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
     * @param Key $cek
176
     * @return string
177
     */
178
     private function getJWEEncryptedKey(EncryptionAlgorithm $alg, Key $recipient_public_key, Key $cek)
0 ignored issues
show
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:
190
             {
191
                 return $alg->encrypt($recipient_public_key, $this->cek->getEncoded());
192
             }
193
             break;
0 ignored issues
show
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
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())
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))
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:
377
            {
378
379
                return ContentEncryptionKeyFactory::fromRaw($alg->decrypt($recipient_private_key, $this->enc_cek), $alg);
380
            }
381
            break;
0 ignored issues
show
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
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;
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())
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))
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
}