Completed
Push — master ( 636cdd...8c476c )
by Florent
02:36
created

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