Completed
Push — v2.0.x ( c7c6d1...6644e2 )
by Florent
05:06
created

getEncryptedKeyFroKeyAgreementAndKeyWrappingAlgorithm()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 9
rs 9.6666
cc 2
eloc 5
nc 2
nop 7
1
<?php
2
3
/*
4
 * The MIT License (MIT)
5
 *
6
 * Copyright (c) 2014-2016 Spomky-Labs
7
 *
8
 * This software may be modified and distributed under the terms
9
 * of the MIT license.  See the LICENSE file for details.
10
 */
11
12
namespace Jose;
13
14
use Base64Url\Base64Url;
15
use Jose\Algorithm\ContentEncryptionAlgorithmInterface;
16
use Jose\Algorithm\JWAManagerInterface;
17
use Jose\Algorithm\KeyEncryption\DirectEncryptionInterface;
18
use Jose\Algorithm\KeyEncryption\KeyAgreementInterface;
19
use Jose\Algorithm\KeyEncryption\KeyAgreementWrappingInterface;
20
use Jose\Algorithm\KeyEncryption\KeyEncryptionInterface;
21
use Jose\Algorithm\KeyEncryption\KeyWrappingInterface;
22
use Jose\Algorithm\KeyEncryptionAlgorithmInterface;
23
use Jose\Behaviour\HasCompressionManager;
24
use Jose\Behaviour\HasJWAManager;
25
use Jose\Behaviour\HasKeyChecker;
26
use Jose\Compression\CompressionManagerInterface;
27
use Jose\Object\JWEInterface;
28
use Jose\Object\JWKInterface;
29
use Jose\Object\Recipient;
30
31
/**
32
 */
33
final class Encrypter implements EncrypterInterface
34
{
35
    use HasKeyChecker;
36
    use HasJWAManager;
37
    use HasCompressionManager;
38
39
    /**
40
     * Encrypter constructor.
41
     *
42
     * @param \Jose\Algorithm\JWAManagerInterface           $jwa_manager
43
     * @param \Jose\Compression\CompressionManagerInterface $compression_manager
44
     */
45
    public function __construct(
46
        JWAManagerInterface $jwa_manager,
47
        CompressionManagerInterface $compression_manager)
48
    {
49
        $this->setJWAManager($jwa_manager);
50
        $this->setCompressionManager($compression_manager);
51
    }
52
53
    /**
54
     * @param \Jose\Object\JWEInterface      $jwe
55
     * @param \Jose\Object\JWKInterface      $recipient_key
56
     * @param \Jose\Object\JWKInterface|null $sender_key
57
     * @param array                          $recipient_headers
58
     *
59
     * @return \Jose\Object\JWEInterface
60
     */
61
    public function addRecipient(JWEInterface $jwe, JWKInterface $recipient_key, JWKInterface $sender_key = null, array $recipient_headers = [])
62
    {
63
        $complete_headers = array_merge(
64
            $jwe->getSharedProtectedHeaders(),
65
            $jwe->getSharedHeaders(),
66
            $recipient_headers
67
        );
68
69
        $key_encryption_algorithm = $this->findKeyEncryptionAlgorithm($complete_headers);
70
71
        $this->checkKeyUsage($recipient_key, 'encryption');
72
        $this->checkKeyAlgorithm($recipient_key, $key_encryption_algorithm->getAlgorithmName());
73
        if ($sender_key instanceof JWKInterface) {
74
            $this->checkKeyUsage($sender_key, 'encryption');
75
            $this->checkKeyAlgorithm($sender_key, $key_encryption_algorithm->getAlgorithmName());
76
        }
77
78
        // Content Encryption Algorithm
79
        $content_encryption_algorithm = $this->findContentEncryptionAlgorithm($complete_headers);
80
81
        if (null === $jwe->getCiphertext()) {
82
            // the content is not yet encrypted (no recipient)
83
84
            if (!empty($jwe->getSharedProtectedHeaders())) {
85
                $jwe = $jwe->withEncodedSharedProtectedHeaders(Base64Url::encode(json_encode($jwe->getSharedProtectedHeaders())));
86
            }
87
88
            // CEK
89
            $content_encryption_key = $this->getCEK(
90
                $complete_headers,
91
                $key_encryption_algorithm,
92
                $content_encryption_algorithm,
93
                $recipient_key,
94
                $sender_key
95
            );
96
            $jwe = $jwe->withContentEncryptionKey($content_encryption_key);
97
98
            // IV
99
            if (null !== $iv_size = $content_encryption_algorithm->getIVSize()) {
100
                $iv = $this->createIV($iv_size);
101
                $jwe = $jwe->withIV($iv);
102
            }
103
104
            // We encrypt the payload and get the tag
105
            $tag = null;
106
            $payload = $this->preparePayload($jwe->getPayload(), $complete_headers);
107
108
            $ciphertext = $content_encryption_algorithm->encryptContent(
109
                $payload,
110
                $content_encryption_key,
111
                $jwe->getIV(),
112
                $jwe->getAAD(),
113
                $jwe->getEncodedSharedProtectedHeaders(),
114
                $tag
115
            );
116
            $jwe = $jwe->withCiphertext($ciphertext);
117
118
            // Tag
119
            if (null !== $tag) {
120
                $jwe = $jwe->withTag($tag);
121
            }
122
123
            $recipient = $this->computeRecipient(
124
                $jwe,
125
                $key_encryption_algorithm,
126
                $content_encryption_algorithm,
127
                $complete_headers,
128
                $recipient_headers,
129
                $recipient_key,
130
                $sender_key
131
            );
132
133
            $jwe = $jwe->addRecipient($recipient);
134
        } else {
135
            if (0 === $jwe->countRecipients()) {
136
                throw new \InvalidArgumentException('Invalid JWE. The payload is encrypted but no recipient is available.');
137
            }
138
            if (null === $jwe->getContentEncryptionKey()) {
139
                throw new \InvalidArgumentException('Unable to add a recipient. The JWE must be decrypted first.');
140
            }
141
            $current_key_management_mode = $this->getCurrentKeyManagementMode($jwe);
142
143
            if (false === $this->areKeyManagementModesCompatible($current_key_management_mode, $key_encryption_algorithm->getKeyManagementMode())) {
144
                throw new \InvalidArgumentException('Foreign key management mode forbidden.');
145
            }
146
147
            $recipient = $this->computeRecipient(
148
                $jwe,
149
                $key_encryption_algorithm,
150
                $content_encryption_algorithm,
151
                $complete_headers,
152
                $recipient_headers,
153
                $recipient_key,
154
                $sender_key
155
            );
156
157
            $jwe = $jwe->addRecipient($recipient);
158
        }
159
160
        return $jwe;
161
    }
162
163
    /**
164
     * @param \Jose\Object\JWEInterface $jwe
165
     *
166
     * @return string
167
     */
168
    private function getCurrentKeyManagementMode(JWEInterface $jwe)
169
    {
170
        $complete_headers = array_merge(
171
            $jwe->getSharedProtectedHeaders(),
172
            $jwe->getSharedHeaders(),
173
            $jwe->getRecipient(0)->getHeaders()
174
        );
175
        $key_encryption_algorithm = $this->findKeyEncryptionAlgorithm($complete_headers);
176
177
        return $key_encryption_algorithm->getKeyManagementMode();
178
    }
179
180
    /**
181
     * @param \Jose\Object\JWEInterface                           $jwe
182
     * @param \Jose\Algorithm\KeyEncryptionAlgorithmInterface     $key_encryption_algorithm
183
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm
184
     * @param array                                               $complete_headers
185
     * @param array                                               $recipient_headers
186
     * @param \Jose\Object\JWKInterface                           $recipient_key
187
     * @param \Jose\Object\JWKInterface|null                      $sender_key
188
     *
189
     * @return \Jose\Object\RecipientInterface
190
     */
191
    private function computeRecipient(JWEInterface $jwe, KeyEncryptionAlgorithmInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, array $complete_headers, array $recipient_headers, JWKInterface $recipient_key, JWKInterface $sender_key = null)
192
    {
193
        $recipient = new Recipient();
194
        $recipient = $recipient->withHeaders($recipient_headers);
195
196
        $additional_headers = [];
197
        $encrypted_content_encryption_key = $this->getEncryptedKey(
198
            $complete_headers,
199
            $jwe->getContentEncryptionKey(),
200
            $key_encryption_algorithm,
201
            $content_encryption_algorithm,
202
            $additional_headers,
203
            $recipient_key,
204
            $sender_key
205
        );
206
        if (!empty($additional_headers)) {
207
            foreach ($additional_headers as $key => $value) {
208
                $recipient = $recipient->withHeader($key, $value);
209
            }
210
        }
211
        if (null !== $encrypted_content_encryption_key) {
212
            $recipient = $recipient->withEncryptedKey($encrypted_content_encryption_key);
213
        }
214
215
        return $recipient;
216
    }
217
218
    /**
219
     * @param string $current
220
     * @param string $new
221
     *
222
     * @return bool
223
     */
224
    private function areKeyManagementModesCompatible($current, $new)
225
    {
226
        $agree = KeyEncryptionAlgorithmInterface::MODE_AGREEMENT;
227
        $dir = KeyEncryptionAlgorithmInterface::MODE_DIRECT;
228
        $enc = KeyEncryptionAlgorithmInterface::MODE_ENCRYPT;
229
        $wrap = KeyEncryptionAlgorithmInterface::MODE_WRAP;
230
231
        $supported_key_management_mode_combinations = [
232
            $agree.$enc => true,
233
            $agree.$wrap => true,
234
            $dir.$enc => true,
235
            $dir.$wrap => true,
236
            $enc.$enc => true,
237
            $enc.$wrap => true,
238
            $wrap.$enc => true,
239
            $wrap.$wrap => true,
240
            $agree.$agree => false,
241
            $agree.$dir => false,
242
            $dir.$agree => false,
243
            $dir.$dir => false,
244
            $enc.$agree => false,
245
            $enc.$dir => false,
246
            $wrap.$agree => false,
247
            $wrap.$dir => false,
248
        ];
249
250
        if (array_key_exists($current.$new, $supported_key_management_mode_combinations)) {
251
            return $supported_key_management_mode_combinations[$current.$new];
252
        }
253
254
        return false;
255
    }
256
257
    /**
258
     * @param string $payload
259
     * @param array  $complete_headers
260
     *
261
     * @return string
262
     */
263
    private function preparePayload($payload, array $complete_headers)
264
    {
265
        $prepared = is_string($payload) ? $payload : json_encode($payload);
266
267
        if (null === $prepared) {
268
            throw new \InvalidArgumentException('The payload is empty or cannot encoded into JSON.');
269
        }
270
        if (!array_key_exists('zip', $complete_headers)) {
271
            return $prepared;
272
        }
273
274
        $compression_method = $this->getCompressionManager()->getCompressionAlgorithm($complete_headers['zip']);
275
        if (null === $compression_method) {
276
            throw new \RuntimeException(sprintf('Compression method "%s" not supported', $complete_headers['zip']));
277
        }
278
        $compressed_payload = $compression_method->compress($prepared);
279
        if (!is_string($compressed_payload)) {
280
            throw new \RuntimeException('Compression failed.');
281
        }
282
283
        return $compressed_payload;
284
    }
285
286
    /**
287
     * @param array                                               $complete_headers
288
     * @param string                                              $cek
289
     * @param \Jose\Algorithm\KeyEncryptionAlgorithmInterface     $key_encryption_algorithm
290
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm
291
     * @param \Jose\Object\JWKInterface                           $recipient_key
292
     * @param \Jose\Object\JWKInterface|null                      $sender_key
293
     * @param array                                               $additional_headers
294
     *
295
     * @return string|null
296
     */
297
    private function getEncryptedKey(array $complete_headers, $cek, KeyEncryptionAlgorithmInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, array &$additional_headers, JWKInterface $recipient_key, JWKInterface $sender_key = null)
298
    {
299
        if ($key_encryption_algorithm instanceof KeyEncryptionInterface) {
300
301
            return $this->getEncryptedKeyFroKeyEncryptionAlgorithm($complete_headers, $cek, $key_encryption_algorithm, $recipient_key);
302
        } elseif ($key_encryption_algorithm instanceof KeyWrappingInterface) {
303
304
            return $this->getEncryptedKeyFroKeyWrappingAlgorithm($complete_headers, $cek, $key_encryption_algorithm, $recipient_key);
305
        } elseif ($key_encryption_algorithm instanceof KeyAgreementWrappingInterface) {
306
307
            return $this->getEncryptedKeyFroKeyAgreementAndKeyWrappingAlgorithm($complete_headers, $cek, $key_encryption_algorithm, $content_encryption_algorithm, $additional_headers, $recipient_key, $sender_key);
308
        } elseif ($key_encryption_algorithm instanceof KeyAgreementInterface) {
309
310
            return $this->getEncryptedKeyFroKeyAgreementAlgorithm($complete_headers, $key_encryption_algorithm, $content_encryption_algorithm, $additional_headers, $recipient_key, $sender_key);
311
        }
312
    }
313
314
    /**
315
     * @param array                                               $complete_headers
316
     * @param \Jose\Algorithm\KeyEncryption\KeyAgreementInterface $key_encryption_algorithm
317
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm
318
     * @param array                                               $additional_headers
319
     * @param \Jose\Object\JWKInterface                           $recipient_key
320
     * @param \Jose\Object\JWKInterface|null                      $sender_key
321
     *
322
     * @return mixed
323
     */
324
    private function getEncryptedKeyFroKeyAgreementAlgorithm(array $complete_headers, KeyAgreementInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, array &$additional_headers, JWKInterface $recipient_key, JWKInterface $sender_key = null)
325
    {
326
        if (!$sender_key instanceof JWKInterface) {
327
            throw new \RuntimeException('The sender key must be set using Key Agreement or Key Agreement with Wrapping algorithms.');
328
        }
329
        $jwt_cek = $key_encryption_algorithm->getAgreementKey($content_encryption_algorithm->getCEKSize(), $sender_key, $recipient_key, $complete_headers, $additional_headers);
330
331
        return $jwt_cek;
332
    }
333
334
    /**
335
     * @param array                                                       $complete_headers
336
     * @param string                                                      $cek
337
     * @param \Jose\Algorithm\KeyEncryption\KeyAgreementWrappingInterface $key_encryption_algorithm
338
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface         $content_encryption_algorithm
339
     * @param array                                                       $additional_headers
340
     * @param \Jose\Object\JWKInterface                                   $recipient_key
341
     * @param \Jose\Object\JWKInterface|null                              $sender_key
342
     *
343
     * @return string
344
     */
345
    private function getEncryptedKeyFroKeyAgreementAndKeyWrappingAlgorithm(array $complete_headers, $cek, KeyAgreementWrappingInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, array &$additional_headers, JWKInterface $recipient_key, JWKInterface $sender_key = null)
346
    {
347
        if (!$sender_key instanceof JWKInterface) {
348
            throw new \RuntimeException('The sender key must be set using Key Agreement or Key Agreement with Wrapping algorithms.');
349
        }
350
        $jwt_cek = $key_encryption_algorithm->wrapAgreementKey($sender_key, $recipient_key, $cek, $content_encryption_algorithm->getCEKSize(), $complete_headers, $additional_headers);
351
352
        return $jwt_cek;
353
    }
354
355
    /**
356
     * @param array                                                $complete_headers
357
     * @param string                                               $cek
358
     * @param \Jose\Algorithm\KeyEncryption\KeyEncryptionInterface $key_encryption_algorithm
359
     * @param \Jose\Object\JWKInterface                            $recipient_key
360
     *
361
     * @return string
362
     */
363
    private function getEncryptedKeyFroKeyEncryptionAlgorithm(array $complete_headers, $cek, KeyEncryptionInterface $key_encryption_algorithm, JWKInterface $recipient_key)
364
    {
365
        return $key_encryption_algorithm->encryptKey(
366
            $recipient_key,
367
            $cek,
368
            $complete_headers
369
        );
370
    }
371
372
    /**
373
     * @param array                                              $complete_headers
374
     * @param string                                             $cek
375
     * @param \Jose\Algorithm\KeyEncryption\KeyWrappingInterface $key_encryption_algorithm
376
     * @param \Jose\Object\JWKInterface                          $recipient_key
377
     *
378
     * @return string
379
     */
380
    private function getEncryptedKeyFroKeyWrappingAlgorithm(array $complete_headers, $cek, KeyWrappingInterface $key_encryption_algorithm, JWKInterface $recipient_key)
381
    {
382
        return $key_encryption_algorithm->wrapKey(
383
            $recipient_key,
384
            $cek,
385
            $complete_headers
386
        );
387
    }
388
389
    /**
390
     * @param array                                               $complete_headers
391
     * @param \Jose\Algorithm\KeyEncryptionAlgorithmInterface     $key_encryption_algorithm
392
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm
393
     * @param \Jose\Object\JWKInterface                           $recipient_key
394
     * @param \Jose\Object\JWKInterface|null                      $sender_key
395
     *
396
     * @return string
397
     */
398
    private function getCEK(array $complete_headers, KeyEncryptionAlgorithmInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, JWKInterface $recipient_key, JWKInterface $sender_key = null)
399
    {
400
        if ($key_encryption_algorithm instanceof KeyEncryptionInterface) {
401
            return $this->createCEK($content_encryption_algorithm->getCEKSize());
402
        } elseif ($key_encryption_algorithm instanceof KeyAgreementWrappingInterface) {
403
            return $this->createCEK($content_encryption_algorithm->getCEKSize());
404
        } elseif ($key_encryption_algorithm instanceof KeyAgreementInterface) {
405
            return $this->calculateAgreementKey($complete_headers, $key_encryption_algorithm, $content_encryption_algorithm, $recipient_key, $sender_key);
406
        } elseif ($key_encryption_algorithm instanceof DirectEncryptionInterface) {
407
            return $key_encryption_algorithm->getCEK($recipient_key);
408
        } else {
409
            throw new \RuntimeException('Unable to get key management mode.');
410
        }
411
    }
412
413
    /**
414
     * @param array $complete_headers
415
     *
416
     * @return \Jose\Algorithm\KeyEncryptionAlgorithmInterface
417
     */
418
    private function findKeyEncryptionAlgorithm(array $complete_headers)
419
    {
420
        if (!array_key_exists('alg', $complete_headers)) {
421
            throw new \InvalidArgumentException('Parameter "alg" is missing.');
422
        }
423
        $key_encryption_algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['alg']);
424
        if ($key_encryption_algorithm instanceof KeyEncryptionAlgorithmInterface) {
425
            return $key_encryption_algorithm;
426
        }
427
        throw new \InvalidArgumentException(sprintf('The key encryption algorithm "%s" is not supported or not a key encryption algorithm instance.', $complete_headers['alg']));
428
    }
429
430
    /**
431
     * @param array $complete_headers
432
     *
433
     * @return \Jose\Algorithm\ContentEncryptionAlgorithmInterface
434
     */
435
    private function findContentEncryptionAlgorithm(array $complete_headers)
436
    {
437
        if (!array_key_exists('enc', $complete_headers)) {
438
            throw new \InvalidArgumentException('Parameter "enc" is missing.');
439
        }
440
441
        $content_encryption_algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['enc']);
442
        if (!$content_encryption_algorithm instanceof ContentEncryptionAlgorithmInterface) {
443
            throw new \RuntimeException(sprintf('The algorithm "%s" is not enabled or does not implement ContentEncryptionInterface.', $complete_headers['enc']));
444
        }
445
446
        return $content_encryption_algorithm;
447
    }
448
449
    /**
450
     * @param array                                               $complete_headers
451
     * @param \Jose\Algorithm\KeyEncryption\KeyAgreementInterface $key_encryption_algorithm
452
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm
453
     * @param \Jose\Object\JWKInterface                           $recipient_key
454
     * @param \Jose\Object\JWKInterface|null                      $sender_key
455
     *
456
     * @return string
457
     */
458
    private function calculateAgreementKey(array $complete_headers, KeyAgreementInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, JWKInterface $recipient_key, JWKInterface $sender_key = null)
459
    {
460
        if (!$sender_key instanceof JWKInterface) {
461
            throw new \RuntimeException('The sender key must be set using Key Agreement or Key Agreement with Wrapping algorithms.');
462
        }
463
        $additional_header_values = [];
464
        $cek = $key_encryption_algorithm->getAgreementKey($content_encryption_algorithm->getCEKSize(), $sender_key, $recipient_key, $complete_headers, $additional_header_values);
465
466
        return $cek;
467
    }
468
469
    /**
470
     * @param int $size
471
     *
472
     * @return string
473
     */
474
    private function createCEK($size)
475
    {
476
        return $this->generateRandomString($size / 8);
477
    }
478
479
    /**
480
     * @param int $size
481
     *
482
     * @return string
483
     */
484
    private function createIV($size)
485
    {
486
        return $this->generateRandomString($size / 8);
487
    }
488
489
    /**
490
     * @param int $length
491
     *
492
     * @return string
493
     */
494
    private function generateRandomString($length)
495
    {
496
        if (function_exists('random_bytes')) {
497
            return random_bytes($length);
498
        } else {
499
            return openssl_random_pseudo_bytes($length);
500
        }
501
    }
502
}
503