Completed
Push — master ( 658c4f...a94a5c )
by Florent
02:18
created

Decrypter::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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