Failed Conditions
Push — v7 ( 838dcb...318c5f )
by Florent
04:36
created

Decrypter::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 6
nc 1
nop 3
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
use Jose\Algorithm\ContentEncryptionAlgorithmInterface;
17
use Jose\Algorithm\JWAInterface;
18
use Jose\Algorithm\JWAManager;
19
use Jose\Algorithm\KeyEncryption\DirectEncryptionInterface;
20
use Jose\Algorithm\KeyEncryption\KeyAgreementInterface;
21
use Jose\Algorithm\KeyEncryption\KeyAgreementWrappingInterface;
22
use Jose\Algorithm\KeyEncryption\KeyEncryptionInterface;
23
use Jose\Algorithm\KeyEncryption\KeyWrappingInterface;
24
use Jose\Algorithm\KeyEncryptionAlgorithmInterface;
25
use Jose\Compression\CompressionInterface;
26
use Jose\Object\JWEInterface;
27
use Jose\Object\JWKInterface;
28
use Jose\Object\JWKSet;
29
use Jose\Object\JWKSetInterface;
30
use Jose\Object\RecipientInterface;
31
32
final class Decrypter
33
{
34
    /**
35
     * @var JWAManager
36
     */
37
    private $jwaManager;
38
39
    use Behaviour\HasKeyChecker;
40
    use Behaviour\HasCompressionManager;
41
    use Behaviour\CommonCipheringMethods;
42
43
    /**
44
     * @param array $key_encryption_algorithms
45
     * @param array $content_encryption_algorithms
46
     * @param array $compression_methods
47
     * @return Decrypter
48
     */
49
    public static function createDecrypter(array $key_encryption_algorithms, array $content_encryption_algorithms, array $compression_methods = ['DEF', 'ZLIB', 'GZ']): Decrypter
50
    {
51
        $decrypter = new self($key_encryption_algorithms, $content_encryption_algorithms, $compression_methods);
52
53
        return $decrypter;
54
    }
55
56
    /**
57
     * Decrypter constructor.
58
     *
59
     * @param string[]|KeyEncryptionAlgorithmInterface[]     $key_encryption_algorithms
60
     * @param string[]|ContentEncryptionAlgorithmInterface[] $content_encryption_algorithms
61
     * @param string[]|CompressionInterface[]              $compression_methods
62
     */
63
    public function __construct(array $key_encryption_algorithms, array $content_encryption_algorithms, array $compression_methods)
64
    {
65
        $this->setKeyEncryptionAlgorithms($key_encryption_algorithms);
66
        $this->setContentEncryptionAlgorithms($content_encryption_algorithms);
67
        $this->setCompressionMethods($compression_methods);
68
        $this->jwaManager = Factory\AlgorithmManagerFactory::createAlgorithmManager(array_merge($key_encryption_algorithms, $content_encryption_algorithms));
69
        $this->setCompressionManager(Factory\CompressionManagerFactory::createCompressionManager($compression_methods));
70
    }
71
72
    /**
73
     * @param JWEInterface $jwe
74
     * @param JWKInterface $jwk
75
     * @param int|null $recipient_index
76
     */
77
    public function decryptUsingKey(JWEInterface &$jwe, JWKInterface $jwk, ?int &$recipient_index = null)
78
    {
79
        $jwk_set = new JWKSet();
80
        $jwk_set->addKey($jwk);
81
82
        $this->decryptUsingKeySet($jwe, $jwk_set, $recipient_index);
83
    }
84
85
    /**
86
     * @param JWEInterface $jwe
87
     * @param JWKSetInterface $jwk_set
88
     * @param int|null $recipient_index
89
     */
90
    public function decryptUsingKeySet(JWEInterface &$jwe, JWKSetInterface $jwk_set, ?int &$recipient_index = null)
91
    {
92
        $this->checkJWKSet($jwk_set);
93
        $this->checkPayload($jwe);
94
        $this->checkRecipients($jwe);
95
96
        $nb_recipients = $jwe->countRecipients();
97
98
        for ($i = 0; $i < $nb_recipients; $i++) {
99
            if (is_int($result = $this->decryptRecipientKey($jwe, $jwk_set, $i))) {
100
                $recipient_index = $result;
101
102
                return;
103
            }
104
        }
105
106
        throw new \InvalidArgumentException('Unable to decrypt the JWE.');
107
    }
108
109
    /**
110
     * @param JWEInterface    $jwe
111
     * @param JWKSetInterface $jwk_set
112
     * @param int             $i
113
     *
114
     * @return int|null
115
     */
116
    private function decryptRecipientKey(JWEInterface &$jwe, JWKSetInterface $jwk_set, int $i): ?int
117
    {
118
        $recipient = $jwe->getRecipient($i);
119
        $complete_headers = array_merge($jwe->getSharedProtectedHeaders(), $jwe->getSharedHeaders(), $recipient->getHeaders());
120
        $this->checkCompleteHeader($complete_headers);
121
122
        $key_encryption_algorithm = $this->getKeyEncryptionAlgorithm($complete_headers);
123
        $content_encryption_algorithm = $this->getContentEncryptionAlgorithm($complete_headers);
124
125
        foreach ($jwk_set as $jwk) {
126
            try {
127
                $this->checkKeyUsage($jwk, 'decryption');
128
                if ('dir' !== $key_encryption_algorithm->getAlgorithmName()) {
129
                    $this->checkKeyAlgorithm($jwk, $key_encryption_algorithm->getAlgorithmName());
130
                } else {
131
                    $this->checkKeyAlgorithm($jwk, $content_encryption_algorithm->getAlgorithmName());
132
                }
133
                $cek = $this->decryptCEK($key_encryption_algorithm, $content_encryption_algorithm, $jwk, $recipient, $complete_headers);
134
                if (null !== $cek) {
135
                    if (true === $this->decryptPayload($jwe, $cek, $content_encryption_algorithm, $complete_headers)) {
136
                        return $i;
137
                    }
138
                }
139
            } catch (\Exception $e) {
140
                //We do nothing, we continue with other keys
141
                continue;
142
            }
143
        }
144
145
        return null;
146
    }
147
148
    /**
149
     * @param JWEInterface $jwe
150
     */
151
    private function checkRecipients(JWEInterface $jwe)
152
    {
153
        Assertion::greaterThan($jwe->countRecipients(), 0, 'The JWE does not contain any recipient.');
154
    }
155
156
    /**
157
     * @param JWEInterface $jwe
158
     */
159
    private function checkPayload(JWEInterface $jwe)
160
    {
161
        Assertion::true(null === $jwe->getPayload(), 'The JWE is already decrypted.');
162
    }
163
164
    /**
165
     * @param JWKSetInterface $jwk_set
166
     */
167
    private function checkJWKSet(JWKSetInterface $jwk_set)
168
    {
169
        Assertion::greaterThan(count($jwk_set), 0, 'No key in the key set.');
170
    }
171
172
    /**
173
     * @param JWAInterface                        $key_encryption_algorithm
174
     * @param ContentEncryptionAlgorithmInterface $content_encryption_algorithm
175
     * @param JWKInterface                           $key
176
     * @param RecipientInterface                     $recipient
177
     * @param array                                               $complete_headers
178
     *
179
     * @return string
180
     */
181
    private function decryptCEK(JWAInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, JWKInterface $key, RecipientInterface $recipient, array $complete_headers): string
182
    {
183
        if ($key_encryption_algorithm instanceof DirectEncryptionInterface) {
184
            return $key_encryption_algorithm->getCEK($key);
185
        } elseif ($key_encryption_algorithm instanceof KeyAgreementInterface) {
186
            return $key_encryption_algorithm->getAgreementKey($content_encryption_algorithm->getCEKSize(), $content_encryption_algorithm->getAlgorithmName(), $key, $complete_headers);
187
        } elseif ($key_encryption_algorithm instanceof KeyAgreementWrappingInterface) {
188
            return $key_encryption_algorithm->unwrapAgreementKey($key, $recipient->getEncryptedKey(), $content_encryption_algorithm->getCEKSize(), $complete_headers);
189
        } elseif ($key_encryption_algorithm instanceof KeyEncryptionInterface) {
190
            return $key_encryption_algorithm->decryptKey($key, $recipient->getEncryptedKey(), $complete_headers);
191
        } elseif ($key_encryption_algorithm instanceof KeyWrappingInterface) {
192
            return $key_encryption_algorithm->unwrapKey($key, $recipient->getEncryptedKey(), $complete_headers);
193
        } else {
194
            throw new \InvalidArgumentException('Unsupported CEK generation');
195
        }
196
    }
197
198
    /**
199
     * @param JWEInterface                           $jwe
200
     * @param string                                              $cek
201
     * @param ContentEncryptionAlgorithmInterface $content_encryption_algorithm
202
     * @param array                                               $complete_headers
203
     *
204
     * @return bool
205
     */
206
    private function decryptPayload(JWEInterface &$jwe, string $cek, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, array $complete_headers): bool
207
    {
208
        $payload = $content_encryption_algorithm->decryptContent($jwe->getCiphertext(), $cek, $jwe->getIV(), null === $jwe->getAAD() ? null : Base64Url::encode($jwe->getAAD()), $jwe->getEncodedSharedProtectedHeaders(), $jwe->getTag());
209
        if (null === $payload) {
210
            return false;
211
        }
212
213
        $this->decompressIfNeeded($payload, $complete_headers);
214
        $decoded = json_decode($payload, true);
215
        $jwe = $jwe->withPayload(null === $decoded ? $payload : $decoded);
216
217
        return true;
218
    }
219
220
    /**
221
     * @param string $payload
222
     * @param array  $complete_headers
223
     */
224
    private function decompressIfNeeded(string &$payload, array $complete_headers)
225
    {
226
        if (array_key_exists('zip', $complete_headers)) {
227
            $compression_method = $this->getCompressionMethod($complete_headers['zip']);
228
            $payload = $compression_method->uncompress($payload);
229
            Assertion::string($payload, 'Decompression failed');
230
        }
231
    }
232
233
    /**
234
     * @param array $complete_headers
235
     *
236
     * @throws \InvalidArgumentException
237
     */
238
    private function checkCompleteHeader(array $complete_headers)
239
    {
240
        foreach (['enc', 'alg'] as $key) {
241
            Assertion::keyExists($complete_headers, $key, sprintf("Parameters '%s' is missing.", $key));
242
        }
243
    }
244
245
    /**
246
     * @param array $complete_headers
247
     *
248
     * @return KeyEncryptionAlgorithmInterface
249
     */
250
    private function getKeyEncryptionAlgorithm(array $complete_headers): KeyEncryptionAlgorithmInterface
251
    {
252
        $key_encryption_algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['alg']);
253
        Assertion::isInstanceOf($key_encryption_algorithm, KeyEncryptionAlgorithmInterface::class, sprintf('The key encryption algorithm "%s" is not supported or does not implement KeyEncryptionAlgorithmInterface.', $complete_headers['alg']));
254
255
        return $key_encryption_algorithm;
256
    }
257
258
    /**
259
     * @param array $complete_headers
260
     *
261
     * @return ContentEncryptionAlgorithmInterface
262
     */
263
    private function getContentEncryptionAlgorithm(array $complete_headers): ContentEncryptionAlgorithmInterface
264
    {
265
        $content_encryption_algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['enc']);
266
        Assertion::isInstanceOf($content_encryption_algorithm, ContentEncryptionAlgorithmInterface::class, sprintf('The key encryption algorithm "%s" is not supported or does not implement ContentEncryptionInterface.', $complete_headers['enc']));
267
268
        return $content_encryption_algorithm;
269
    }
270
271
    /**
272
     * @param string $method
273
     *
274
     * @throws \InvalidArgumentException
275
     *
276
     * @return CompressionInterface
277
     */
278
    private function getCompressionMethod(string $method): CompressionInterface
279
    {
280
        $compression_method = $this->getCompressionManager()->getCompressionAlgorithm($method);
281
        Assertion::notNull($compression_method, sprintf('Compression method "%s" not supported', $method));
282
283
        return $compression_method;
284
    }
285
286
    /**
287
     * @return JWAManager
288
     */
289
    protected function getJWAManager(): JWAManager
290
    {
291
        return $this->jwaManager;
292
    }
293
}
294