Completed
Push — v2.0.x ( 59a3d4...b06cab )
by Florent
03:22
created

Decrypter   B

Complexity

Total Complexity 28

Size/Duplication

Total Lines 216
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 17

Importance

Changes 15
Bugs 8 Features 2
Metric Value
wmc 28
c 15
b 8
f 2
lcom 1
cbo 17
dl 0
loc 216
rs 7.8571

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
A decryptUsingKey() 0 7 1
B decryptUsingKeySet() 0 32 6
B decryptCEK() 0 34 6
B decryptPayload() 0 30 5
A checkCompleteHeader() 0 8 3
A getKeyEncryptionAlgorithm() 0 10 2
A getContentEncryptionAlgorithm() 0 9 2
A getCompressionMethod() 0 9 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\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
        foreach ($jwe->getRecipients() as $recipient) {
78
            $complete_headers = array_merge(
79
                $jwe->getSharedProtectedHeaders(),
80
                $jwe->getSharedHeaders(),
81
                $recipient->getHeaders()
82
            );
83
            $this->checkCompleteHeader($complete_headers);
84
85
            $key_encryption_algorithm = $this->getKeyEncryptionAlgorithm($complete_headers);
86
            $content_encryption_algorithm = $this->getContentEncryptionAlgorithm($complete_headers);
87
88
            foreach ($jwk_set as $jwk) {
89
                try {
90
                    $this->checkKeyUsage($jwk, 'decryption');
91
                    $this->checkKeyAlgorithm($jwk, $key_encryption_algorithm->getAlgorithmName());
92
                    $cek = $this->decryptCEK($key_encryption_algorithm, $content_encryption_algorithm, $jwk, $recipient, $complete_headers);
93
                    if (null !== $cek) {
94
                        if (true === $this->decryptPayload($jwe, $cek, $content_encryption_algorithm, $complete_headers)) {
95
                            return true;
96
                        };
97
                    }
98
                } catch (\Exception $e) {
99
                    //We do nothing, we continue with other keys
100
                    continue;
101
                }
102
            }
103
        }
104
105
        return false;
106
    }
107
108
    /**
109
     * @param \Jose\Algorithm\JWAInterface                        $key_encryption_algorithm
110
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm
111
     * @param \Jose\Object\JWKInterface                           $key
112
     * @param \Jose\Object\RecipientInterface                     $recipient
113
     * @param array                                               $complete_headers
114
     *
115
     * @return null|string
116
     */
117
    private function decryptCEK(JWAInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, JWKInterface $key, RecipientInterface $recipient, array $complete_headers)
118
    {
119
        if ($key_encryption_algorithm instanceof DirectEncryptionInterface) {
120
            return $key_encryption_algorithm->getCEK($key);
121
        } elseif ($key_encryption_algorithm instanceof KeyAgreementInterface) {
122
            return $key_encryption_algorithm->getAgreementKey(
123
                $content_encryption_algorithm->getCEKSize(),
124
                $key,
125
                null,
126
                $complete_headers
127
            );
128
        } elseif ($key_encryption_algorithm instanceof KeyAgreementWrappingInterface) {
129
            return $key_encryption_algorithm->unwrapAgreementKey(
130
                $key,
131
                $recipient->getEncryptedKey(),
132
                $content_encryption_algorithm->getCEKSize(),
133
                $complete_headers
134
            );
135
        } elseif ($key_encryption_algorithm instanceof KeyEncryptionInterface) {
136
            return $key_encryption_algorithm->decryptKey(
137
                $key,
138
                $recipient->getEncryptedKey(),
139
                $complete_headers
140
            );
141
        } elseif ($key_encryption_algorithm instanceof KeyWrappingInterface) {
142
            return $key_encryption_algorithm->unwrapKey(
143
                $key,
144
                $recipient->getEncryptedKey(),
145
                $complete_headers
146
            );
147
        } else {
148
            throw new \InvalidArgumentException('Unsupported CEK generation');
149
        }
150
    }
151
152
    /**
153
     * @param \Jose\Object\JWEInterface                           $jwe
154
     * @param string                                              $cek
155
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm
156
     * @param array                                               $complete_headers
157
     *
158
     * @return bool
159
     */
160
    private function decryptPayload(JWEInterface &$jwe, $cek, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, array $complete_headers)
161
    {
162
        $payload = $content_encryption_algorithm->decryptContent(
163
            $jwe->getCiphertext(),
164
            $cek,
165
            $jwe->getIV(),
166
            $jwe->getAAD(),
167
            $jwe->getEncodedSharedProtectedHeaders(),
168
            $jwe->getTag()
169
        );
170
171
        if (null === $payload) {
172
            return false;
173
        }
174
175
        if (array_key_exists('zip', $complete_headers)) {
176
            $compression_method = $this->getCompressionMethod($complete_headers['zip']);
177
            $payload = $compression_method->uncompress($payload);
178
            if (!is_string($payload)) {
179
                throw new \InvalidArgumentException('Decompression failed');
180
            }
181
        }
182
183
        $jwe = $jwe->withContentEncryptionKey($cek);
184
185
        $decoded = json_decode($payload, true);
186
        $jwe = $jwe->withPayload(null === $decoded?$payload:$decoded);
187
188
        return true;
189
    }
190
191
    /**
192
     * @param array $complete_headers
193
     *
194
     * @throws \InvalidArgumentException
195
     */
196
    private function checkCompleteHeader(array $complete_headers)
197
    {
198
        foreach (['enc', 'alg'] as $key) {
199
            if (!array_key_exists($key, $complete_headers)) {
200
                throw new \InvalidArgumentException(sprintf("Parameters '%s' is missing.", $key));
201
            }
202
        }
203
    }
204
205
    /**
206
     * @param array $complete_headers
207
     *
208
     * @return \Jose\Algorithm\KeyEncryptionAlgorithmInterface
209
     */
210
    private function getKeyEncryptionAlgorithm(array $complete_headers)
211
    {
212
        $key_encryption_algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['alg']);
213
214
        if (!$key_encryption_algorithm instanceof KeyEncryptionAlgorithmInterface) {
215
            throw new \InvalidArgumentException(sprintf("The key encryption algorithm '%s' is not supported or does not implement KeyEncryptionAlgorithmInterface.", $complete_headers['alg']));
216
        }
217
218
        return $key_encryption_algorithm;
219
    }
220
221
    /**
222
     * @param array $complete_headers
223
     *
224
     * @return \Jose\Algorithm\ContentEncryptionAlgorithmInterface
225
     */
226
    private function getContentEncryptionAlgorithm(array $complete_headers)
227
    {
228
        $content_encryption_algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['enc']);
229
        if (!$content_encryption_algorithm instanceof ContentEncryptionAlgorithmInterface) {
230
            throw new \InvalidArgumentException(sprintf('The algorithm "%s" does not exist or does not implement ContentEncryptionInterface."', $complete_headers['enc']));
231
        }
232
233
        return $content_encryption_algorithm;
234
    }
235
236
    /**
237
     * @param string $method
238
     *
239
     * @throws \InvalidArgumentException
240
     *
241
     * @return \Jose\Compression\CompressionInterface
242
     */
243
    private function getCompressionMethod($method)
244
    {
245
        $compression_method = $this->getCompressionManager()->getCompressionAlgorithm($method);
246
        if (null === $compression_method) {
247
            throw new \InvalidArgumentException(sprintf("Compression method '%s' not supported"), $method);
248
        }
249
250
        return $compression_method;
251
    }
252
}
253