Completed
Push — master ( 3d3a90...756792 )
by Florent
05:31 queued 02:57
created

Encrypter::generateRandomString()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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