Completed
Push — v2.0.x ( 8e74ea...23a51c )
by Florent
02:24
created

Decrypter::checkRecipients()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 6
rs 9.4285
cc 2
eloc 3
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 Jose\Algorithm\ContentEncryptionAlgorithmInterface;
15
use Jose\Algorithm\JWAInterface;
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\HasCheckerManager;
24
use Jose\Behaviour\HasCompressionManager;
25
use Jose\Behaviour\HasJWAManager;
26
use Jose\Behaviour\HasKeyChecker;
27
use Jose\Checker\CheckerManagerInterface;
28
use Jose\Compression\CompressionManagerInterface;
29
use Jose\Object\JWEInterface;
30
use Jose\Object\JWKInterface;
31
use Jose\Object\JWKSet;
32
use Jose\Object\JWKSetInterface;
33
use Jose\Object\RecipientInterface;
34
35
/**
36
 */
37
final class Decrypter implements DecrypterInterface
38
{
39
    use HasKeyChecker;
40
    use HasJWAManager;
41
    use HasCheckerManager;
42
    use HasCompressionManager;
43
44
    /**
45
     * Loader constructor.
46
     *
47
     * @param \Jose\Algorithm\JWAManagerInterface           $jwa_manager
48
     * @param \Jose\Compression\CompressionManagerInterface $compression_manager
49
     * @param \Jose\Checker\CheckerManagerInterface         $checker_manager
50
     */
51
    public function __construct(
52
        JWAManagerInterface $jwa_manager,
53
        CompressionManagerInterface $compression_manager,
54
        CheckerManagerInterface $checker_manager)
55
    {
56
        $this->setJWAManager($jwa_manager);
57
        $this->setCompressionManager($compression_manager);
58
        $this->setCheckerManager($checker_manager);
59
    }
60
61
    /**
62
     * {@inheritdoc}
63
     */
64
    public function decryptUsingKey(JWEInterface &$jwe, JWKInterface $jwk)
65
    {
66
        $jwk_set = new JWKSet();
67
        $jwk_set = $jwk_set->addKey($jwk);
68
69
        return $this->decryptUsingKeySet($jwe, $jwk_set);
70
    }
71
72
    /**
73
     * {@inheritdoc}
74
     */
75
    public function decryptUsingKeySet(JWEInterface &$jwe, JWKSetInterface $jwk_set)
76
    {
77
        $this->checkJWKSet($jwk_set);
78
        $this->checkPayload($jwe);
79
        $this->checkRecipients($jwe);
80
81
        for ($i = 0; $i < $jwe->countRecipients(); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
Consider avoiding function calls on each iteration of the for loop.

If you have a function call in the test part of a for loop, this function is executed on each iteration. Often such a function, can be moved to the initialization part and be cached.

// count() is called on each iteration
for ($i=0; $i < count($collection); $i++) { }

// count() is only called once
for ($i=0, $c=count($collection); $i<$c; $i++) { }
Loading history...
82
            $recipient = $jwe->getRecipient($i);
83
            $complete_headers = array_merge(
84
                $jwe->getSharedProtectedHeaders(),
85
                $jwe->getSharedHeaders(),
86
                $recipient->getHeaders()
87
            );
88
            $this->checkCompleteHeader($complete_headers);
89
90
            $key_encryption_algorithm = $this->getKeyEncryptionAlgorithm($complete_headers);
91
            $content_encryption_algorithm = $this->getContentEncryptionAlgorithm($complete_headers);
92
93
            foreach ($jwk_set as $jwk) {
94
                try {
95
                    $this->checkKeyUsage($jwk, 'decryption');
96
                    $this->checkKeyAlgorithm($jwk, $key_encryption_algorithm->getAlgorithmName());
97
                    $cek = $this->decryptCEK($key_encryption_algorithm, $content_encryption_algorithm, $jwk, $recipient, $complete_headers);
98
                    if (null !== $cek) {
99
                        if (true === $this->decryptPayload($jwe, $cek, $content_encryption_algorithm, $complete_headers)) {
100
                            $this->getCheckerManager()->checkJWT($jwe);
101
102
                            return $i;
103
                        };
104
                    }
105
                } catch (\Exception $e) {
106
                    //We do nothing, we continue with other keys
107
                    continue;
108
                }
109
            }
110
        }
111
112
        return false;
113
    }
114
115
    /**
116
     * @param \Jose\Object\JWEInterface $jwe
117
     */
118
    private function checkRecipients(JWEInterface $jwe)
119
    {
120
        if (0 === $jwe->countRecipients()) {
121
            throw new \InvalidArgumentException('The JWE does not contain any recipient.');
122
        }
123
    }
124
125
    /**
126
     * @param \Jose\Object\JWEInterface $jwe
127
     */
128
    private function checkPayload(JWEInterface $jwe)
129
    {
130
        if (null !== $jwe->getPayload()) {
131
            throw new \InvalidArgumentException('The JWE is already decrypted.');
132
        }
133
    }
134
135
    /**
136
     * @param \Jose\Object\JWKSetInterface $jwk_set
137
     */
138
    private function checkJWKSet(JWKSetInterface $jwk_set)
139
    {
140
        if (0 === count($jwk_set)) {
141
            throw new \InvalidArgumentException('No key in the key set.');
142
        }
143
    }
144
145
    /**
146
     * @param \Jose\Algorithm\JWAInterface                        $key_encryption_algorithm
147
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm
148
     * @param \Jose\Object\JWKInterface                           $key
149
     * @param \Jose\Object\RecipientInterface                     $recipient
150
     * @param array                                               $complete_headers
151
     *
152
     * @return null|string
153
     */
154
    private function decryptCEK(JWAInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, JWKInterface $key, RecipientInterface $recipient, array $complete_headers)
155
    {
156
        if ($key_encryption_algorithm instanceof DirectEncryptionInterface) {
157
            return $key_encryption_algorithm->getCEK($key);
158
        } elseif ($key_encryption_algorithm instanceof KeyAgreementInterface) {
159
            return $key_encryption_algorithm->getAgreementKey(
160
                $content_encryption_algorithm->getCEKSize(),
161
                $key,
162
                null,
163
                $complete_headers
164
            );
165
        } elseif ($key_encryption_algorithm instanceof KeyAgreementWrappingInterface) {
166
            return $key_encryption_algorithm->unwrapAgreementKey(
167
                $key,
168
                $recipient->getEncryptedKey(),
169
                $content_encryption_algorithm->getCEKSize(),
170
                $complete_headers
171
            );
172
        } elseif ($key_encryption_algorithm instanceof KeyEncryptionInterface) {
173
            return $key_encryption_algorithm->decryptKey(
174
                $key,
175
                $recipient->getEncryptedKey(),
176
                $complete_headers
177
            );
178
        } elseif ($key_encryption_algorithm instanceof KeyWrappingInterface) {
179
            return $key_encryption_algorithm->unwrapKey(
180
                $key,
181
                $recipient->getEncryptedKey(),
182
                $complete_headers
183
            );
184
        } else {
185
            throw new \InvalidArgumentException('Unsupported CEK generation');
186
        }
187
    }
188
189
    /**
190
     * @param \Jose\Object\JWEInterface                           $jwe
191
     * @param string                                              $cek
192
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm
193
     * @param array                                               $complete_headers
194
     *
195
     * @return bool
196
     */
197
    private function decryptPayload(JWEInterface &$jwe, $cek, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, array $complete_headers)
198
    {
199
        $payload = $content_encryption_algorithm->decryptContent(
200
            $jwe->getCiphertext(),
201
            $cek,
202
            $jwe->getIV(),
203
            $jwe->getAAD(),
204
            $jwe->getEncodedSharedProtectedHeaders(),
205
            $jwe->getTag()
206
        );
207
208
        if (null === $payload) {
209
            return false;
210
        }
211
212
        if (array_key_exists('zip', $complete_headers)) {
213
            $compression_method = $this->getCompressionMethod($complete_headers['zip']);
214
            $payload = $compression_method->uncompress($payload);
215
            if (!is_string($payload)) {
216
                throw new \InvalidArgumentException('Decompression failed');
217
            }
218
        }
219
220
        $jwe = $jwe->withContentEncryptionKey($cek);
221
222
        $decoded = json_decode($payload, true);
223
        $jwe = $jwe->withPayload(null === $decoded ? $payload : $decoded);
224
225
        return true;
226
    }
227
228
    /**
229
     * @param array $complete_headers
230
     *
231
     * @throws \InvalidArgumentException
232
     */
233
    private function checkCompleteHeader(array $complete_headers)
234
    {
235
        foreach (['enc', 'alg'] as $key) {
236
            if (!array_key_exists($key, $complete_headers)) {
237
                throw new \InvalidArgumentException(sprintf("Parameters '%s' is missing.", $key));
238
            }
239
        }
240
    }
241
242
    /**
243
     * @param array $complete_headers
244
     *
245
     * @return \Jose\Algorithm\KeyEncryptionAlgorithmInterface
246
     */
247
    private function getKeyEncryptionAlgorithm(array $complete_headers)
248
    {
249
        $key_encryption_algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['alg']);
250
251
        if (!$key_encryption_algorithm instanceof KeyEncryptionAlgorithmInterface) {
252
            throw new \InvalidArgumentException(sprintf("The key encryption algorithm '%s' is not supported or does not implement KeyEncryptionAlgorithmInterface.", $complete_headers['alg']));
253
        }
254
255
        return $key_encryption_algorithm;
256
    }
257
258
    /**
259
     * @param array $complete_headers
260
     *
261
     * @return \Jose\Algorithm\ContentEncryptionAlgorithmInterface
262
     */
263
    private function getContentEncryptionAlgorithm(array $complete_headers)
264
    {
265
        $content_encryption_algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['enc']);
266
        if (!$content_encryption_algorithm instanceof ContentEncryptionAlgorithmInterface) {
267
            throw new \InvalidArgumentException(sprintf('The algorithm "%s" does not exist or does not implement ContentEncryptionInterface."', $complete_headers['enc']));
268
        }
269
270
        return $content_encryption_algorithm;
271
    }
272
273
    /**
274
     * @param string $method
275
     *
276
     * @throws \InvalidArgumentException
277
     *
278
     * @return \Jose\Compression\CompressionInterface
279
     */
280
    private function getCompressionMethod($method)
281
    {
282
        $compression_method = $this->getCompressionManager()->getCompressionAlgorithm($method);
283
        if (null === $compression_method) {
284
            throw new \InvalidArgumentException(sprintf("Compression method '%s' not supported"), $method);
285
        }
286
287
        return $compression_method;
288
    }
289
}
290