Failed Conditions
Push — master ( 0ecb40...d7fc29 )
by Florent
03:02
created

Encrypter::createCEK()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
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 Assert\Assertion;
15
use Base64Url\Base64Url;
16
17
final class Encrypter implements EncrypterInterface
18
{
19
    use Behaviour\HasKeyChecker;
20
    use Behaviour\HasJWAManager;
21
    use Behaviour\HasCompressionManager;
22
    use Behaviour\CommonCipheringMethods;
23
    use Behaviour\EncrypterTrait;
24
25
    /**
26
     * {@inheritdoc}
27
     */
28
    public static function createEncrypter(array $key_encryption_algorithms, array $content_encryption_algorithms, array $compression_methods = ['DEF', 'ZLIB', 'GZ'])
29
    {
30
        $encrypter = new self($key_encryption_algorithms, $content_encryption_algorithms, $compression_methods);
31
32
        return $encrypter;
33
    }
34
35
    /**
36
     * Decrypter constructor.
37
     *
38
     * @param string[]|\Jose\Algorithm\KeyEncryptionAlgorithmInterface[]     $key_encryption_algorithms
39
     * @param string[]|\Jose\Algorithm\ContentEncryptionAlgorithmInterface[] $content_encryption_algorithms
40
     * @param string[]|\Jose\Compression\CompressionInterface[]              $compression_methods
41
     */
42
    public function __construct(array $key_encryption_algorithms, array $content_encryption_algorithms, array $compression_methods)
43
    {
44
        $this->setKeyEncryptionAlgorithms($key_encryption_algorithms);
45
        $this->setContentEncryptionAlgorithms($content_encryption_algorithms);
46
        $this->setCompressionMethods($compression_methods);
47
        $this->setJWAManager(Factory\AlgorithmManagerFactory::createAlgorithmManager(array_merge($key_encryption_algorithms, $content_encryption_algorithms)));
48
        $this->setCompressionManager(Factory\CompressionManagerFactory::createCompressionManager($compression_methods));
49
    }
50
51
    /**
52
     * {@inheritdoc}
53
     */
54
    public function encrypt(Object\JWEInterface &$jwe)
55
    {
56
        Assertion::false($jwe->isEncrypted(), 'The JWE is already encrypted.');
57
        Assertion::greaterThan($jwe->countRecipients(), 0, 'The JWE does not contain recipient.');
58
        $additional_headers = [];
59
        $nb_recipients = $jwe->countRecipients();
60
        $this->prepareEncryptionProcess($jwe, $content_encryption_algorithm, $compression_method, $key_management_mode, $cek, $additional_headers);
61
62
        for ($i = 0; $i < $nb_recipients; $i++) {
63
            $this->processRecipient($jwe, $jwe->getRecipient($i), $cek, $content_encryption_algorithm, $additional_headers);
0 ignored issues
show
Documentation introduced by
$content_encryption_algorithm is of type null, but the function expects a object<Jose\Algorithm\Co...tionAlgorithmInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
64
        }
65
66
        if (!empty($additional_headers) && 1 === $jwe->countRecipients()) {
67
            $jwe = $jwe->withSharedProtectedHeaders(array_merge($jwe->getSharedProtectedHeaders(), $additional_headers));
68
        }
69
70
        $iv_size = $content_encryption_algorithm->getIVSize();
0 ignored issues
show
Bug introduced by
The method getIVSize cannot be called on $content_encryption_algorithm (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
71
        $iv = $this->createIV($iv_size);
72
73
        $this->encryptJWE($jwe, $content_encryption_algorithm, $cek, $iv, $compression_method);
0 ignored issues
show
Documentation introduced by
$content_encryption_algorithm is of type null, but the function expects a object<Jose\Algorithm\Co...tionAlgorithmInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
74
    }
75
76
    /**
77
     * @param \Jose\Object\JWEInterface                           $jwe
78
     * @param \Jose\Object\RecipientInterface                     $recipient
79
     * @param string                                              $cek
80
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm
81
     * @param array                                               $additional_headers
82
     */
83
    private function processRecipient(Object\JWEInterface $jwe, Object\RecipientInterface &$recipient, $cek, Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm, array &$additional_headers)
84
    {
85
        if (null === $recipient->getRecipientKey()) {
86
            return;
87
        }
88
        $complete_headers = array_merge($jwe->getSharedProtectedHeaders(), $jwe->getSharedHeaders(), $recipient->getHeaders());
89
        $key_encryption_algorithm = $this->findKeyEncryptionAlgorithm($complete_headers);
90
        $this->checkKeys($key_encryption_algorithm, $content_encryption_algorithm, $recipient->getRecipientKey());
0 ignored issues
show
Bug introduced by
It seems like $key_encryption_algorithm defined by $this->findKeyEncryption...ithm($complete_headers) on line 89 can be null; however, Jose\Encrypter::checkKeys() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
91
        $encrypted_content_encryption_key = $this->getEncryptedKey($complete_headers, $cek, $key_encryption_algorithm, $content_encryption_algorithm, $additional_headers, $recipient->getRecipientKey());
0 ignored issues
show
Bug introduced by
It seems like $key_encryption_algorithm defined by $this->findKeyEncryption...ithm($complete_headers) on line 89 can be null; however, Jose\Encrypter::getEncryptedKey() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
92
        $recipient_headers = $recipient->getHeaders();
93
        if (!empty($additional_headers) && 1 !== $jwe->countRecipients()) {
94
            $recipient_headers = array_merge($recipient_headers, $additional_headers);
95
            $additional_headers = [];
96
        }
97
98
        $recipient = Object\Recipient::createRecipientFromLoadedJWE($recipient_headers, $encrypted_content_encryption_key);
99
    }
100
101
    /**
102
     * @param \Jose\Object\JWEInterface                           $jwe
103
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm
104
     * @param string                                              $cek
105
     * @param string                                              $iv
106
     * @param \Jose\Compression\CompressionInterface|null         $compression_method
107
     */
108
    private function encryptJWE(Object\JWEInterface &$jwe, Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm, $cek, $iv, Compression\CompressionInterface $compression_method = null)
109
    {
110
        if (!empty($jwe->getSharedProtectedHeaders())) {
111
            $jwe = $jwe->withEncodedSharedProtectedHeaders(Base64Url::encode(json_encode($jwe->getSharedProtectedHeaders())));
112
        }
113
114
        $tag = null;
115
        $payload = $this->preparePayload($jwe->getPayload(), $compression_method);
116
        $aad = null === $jwe->getAAD() ? null : Base64Url::encode($jwe->getAAD());
117
        $ciphertext = $content_encryption_algorithm->encryptContent($payload, $cek, $iv, $aad, $jwe->getEncodedSharedProtectedHeaders(), $tag);
118
        $jwe = $jwe->withCiphertext($ciphertext);
119
        $jwe = $jwe->withIV($iv);
120
121
        if (null !== $tag) {
122
            $jwe = $jwe->withTag($tag);
123
        }
124
    }
125
126
    /**
127
     * @param \Jose\Algorithm\KeyEncryptionAlgorithmInterface     $key_encryption_algorithm
128
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm
129
     * @param \Jose\Object\JWKInterface                           $recipient_key
130
     */
131
    private function checkKeys(Algorithm\KeyEncryptionAlgorithmInterface $key_encryption_algorithm, Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm, Object\JWKInterface $recipient_key)
132
    {
133
        $this->checkKeyUsage($recipient_key, 'encryption');
134
        if ('dir' !== $key_encryption_algorithm->getAlgorithmName()) {
135
            $this->checkKeyAlgorithm($recipient_key, $key_encryption_algorithm->getAlgorithmName());
136
        } else {
137
            $this->checkKeyAlgorithm($recipient_key, $content_encryption_algorithm->getAlgorithmName());
138
        }
139
    }
140
141
    /**
142
     * @param string                                      $payload
143
     * @param \Jose\Compression\CompressionInterface|null $compression_method
144
     *
145
     * @return string
146
     */
147
    private function preparePayload($payload, Compression\CompressionInterface $compression_method = null)
148
    {
149
        $prepared = is_string($payload) ? $payload : json_encode($payload);
150
        Assertion::notNull($prepared, 'The payload is empty or cannot encoded into JSON.');
151
152
        if (null === $compression_method) {
153
            return $prepared;
154
        }
155
        $compressed_payload = $compression_method->compress($prepared);
156
        Assertion::string($compressed_payload, 'Compression failed.');
157
158
        return $compressed_payload;
159
    }
160
161
    /**
162
     * @param array                                               $complete_headers
163
     * @param string                                              $cek
164
     * @param \Jose\Algorithm\KeyEncryptionAlgorithmInterface     $key_encryption_algorithm
165
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm
166
     * @param \Jose\Object\JWKInterface                           $recipient_key
167
     * @param array                                               $additional_headers
168
     *
169
     * @return string|null
170
     */
171
    private function getEncryptedKey(array $complete_headers, $cek, Algorithm\KeyEncryptionAlgorithmInterface $key_encryption_algorithm, Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm, array &$additional_headers, Object\JWKInterface $recipient_key)
172
    {
173
        if ($key_encryption_algorithm instanceof Algorithm\KeyEncryption\KeyEncryptionInterface) {
174
            return $this->getEncryptedKeyFromKeyEncryptionAlgorithm($complete_headers, $cek, $key_encryption_algorithm, $recipient_key, $additional_headers);
175
        } elseif ($key_encryption_algorithm instanceof Algorithm\KeyEncryption\KeyWrappingInterface) {
176
            return $this->getEncryptedKeyFromKeyWrappingAlgorithm($complete_headers, $cek, $key_encryption_algorithm, $recipient_key, $additional_headers);
177
        } elseif ($key_encryption_algorithm instanceof Algorithm\KeyEncryption\KeyAgreementWrappingInterface) {
178
            return $this->getEncryptedKeyFromKeyAgreementAndKeyWrappingAlgorithm($complete_headers, $cek, $key_encryption_algorithm, $content_encryption_algorithm, $additional_headers, $recipient_key);
179
        }
180
    }
181
182
    /**
183
     * @param array                                                       $complete_headers
184
     * @param string                                                      $cek
185
     * @param \Jose\Algorithm\KeyEncryption\KeyAgreementWrappingInterface $key_encryption_algorithm
186
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface         $content_encryption_algorithm
187
     * @param array                                                       $additional_headers
188
     * @param \Jose\Object\JWKInterface                                   $recipient_key
189
     *
190
     * @return string
191
     */
192
    private function getEncryptedKeyFromKeyAgreementAndKeyWrappingAlgorithm(array $complete_headers, $cek, Algorithm\KeyEncryption\KeyAgreementWrappingInterface $key_encryption_algorithm, Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm, array &$additional_headers, Object\JWKInterface $recipient_key)
193
    {
194
        $jwt_cek = $key_encryption_algorithm->wrapAgreementKey($recipient_key, $cek, $content_encryption_algorithm->getCEKSize(), $complete_headers, $additional_headers);
195
196
        return $jwt_cek;
197
    }
198
199
    /**
200
     * @param array                                                $complete_headers
201
     * @param string                                               $cek
202
     * @param \Jose\Algorithm\KeyEncryption\KeyEncryptionInterface $key_encryption_algorithm
203
     * @param \Jose\Object\JWKInterface                            $recipient_key
204
     * @param array                                                $additional_headers
205
     *
206
     * @return string
207
     */
208
    private function getEncryptedKeyFromKeyEncryptionAlgorithm(array $complete_headers, $cek, Algorithm\KeyEncryption\KeyEncryptionInterface $key_encryption_algorithm, Object\JWKInterface $recipient_key, array &$additional_headers)
209
    {
210
        return $key_encryption_algorithm->encryptKey($recipient_key,  $cek, $complete_headers, $additional_headers);
211
    }
212
213
    /**
214
     * @param array                                              $complete_headers
215
     * @param string                                             $cek
216
     * @param \Jose\Algorithm\KeyEncryption\KeyWrappingInterface $key_encryption_algorithm
217
     * @param \Jose\Object\JWKInterface                          $recipient_key
218
     * @param array                                              $additional_headers
219
     *
220
     * @return string
221
     */
222
    private function getEncryptedKeyFromKeyWrappingAlgorithm(array $complete_headers, $cek, Algorithm\KeyEncryption\KeyWrappingInterface $key_encryption_algorithm, Object\JWKInterface $recipient_key, &$additional_headers)
223
    {
224
        return $key_encryption_algorithm->wrapKey($recipient_key, $cek, $complete_headers, $additional_headers);
225
    }
226
227
    /**
228
     * @param array $complete_headers
229
     *
230
     * @return \Jose\Algorithm\KeyEncryptionAlgorithmInterface
231
     */
232
    private function findKeyEncryptionAlgorithm(array $complete_headers)
233
    {
234
        Assertion::keyExists($complete_headers, 'alg', 'Parameter "alg" is missing.');
235
        $key_encryption_algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['alg']);
236
        Assertion::isInstanceOf($key_encryption_algorithm, Algorithm\KeyEncryptionAlgorithmInterface::class, sprintf('The key encryption algorithm "%s" is not supported or not a key encryption algorithm instance.', $complete_headers['alg']));
237
238
        return $key_encryption_algorithm;
239
    }
240
241
    /**
242
     * @param int $size
243
     *
244
     * @return string
245
     */
246
    private function createIV($size)
247
    {
248
        return random_bytes($size / 8);
249
    }
250
}
251