Completed
Push — master ( 40b29f...7210ec )
by Florent
31:16 queued 19:22
created

Decrypter::checkCompleteHeader()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 8
rs 9.4286
cc 3
eloc 4
nc 3
nop 1
1
<?php
2
3
/*
4
 * The MIT License (MIT)
5
 *
6
 * Copyright (c) 2014-2015 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\ContentEncryption\ContentEncryptionInterface;
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\Behaviour\HasCheckerManager;
22
use Jose\Behaviour\HasCompressionManager;
23
use Jose\Behaviour\HasJWAManager;
24
use Jose\Behaviour\HasJWKFinderManager;
25
use Jose\Behaviour\HasKeyChecker;
26
use Jose\Behaviour\HasPayloadConverter;
27
use Jose\Checker\CheckerManagerInterface;
28
use Jose\Compression\CompressionManagerInterface;
29
use Jose\Finder\JWKFinderManagerInterface;
30
use Jose\Object\JWEInterface;
31
use Jose\Object\JWKInterface;
32
use Jose\Object\JWKSet;
33
use Jose\Object\JWKSetInterface;
34
use Jose\Payload\PayloadConverterManagerInterface;
35
36
/**
37
 */
38
final class Decrypter implements DecrypterInterface
39
{
40
    use HasKeyChecker;
41
    use HasJWAManager;
42
    use HasJWKFinderManager;
43
    use HasCheckerManager;
44
    use HasPayloadConverter;
45
    use HasCompressionManager;
46
47
    /**
48
     * Loader constructor.
49
     *
50
     * @param \Jose\Algorithm\JWAManagerInterface                      $jwa_manager
51
     * @param \Jose\Finder\JWKFinderManagerInterface                $jwk_finder_manager
52
     * @param \Jose\Payload\PayloadConverterManagerInterface $payload_converter_manager
53
     * @param \Jose\Compression\CompressionManagerInterface  $compression_manager
54
     * @param \Jose\Checker\CheckerManagerInterface          $checker_manager
55
     */
56
    public function __construct(
57
        JWAManagerInterface $jwa_manager,
58
        JWKFinderManagerInterface $jwk_finder_manager,
59
        PayloadConverterManagerInterface $payload_converter_manager,
60
        CompressionManagerInterface $compression_manager,
61
        CheckerManagerInterface $checker_manager)
62
    {
63
        $this->setJWAManager($jwa_manager);
64
        $this->setJWKFinderManager($jwk_finder_manager);
65
        $this->setPayloadConverter($payload_converter_manager);
66
        $this->setCompressionManager($compression_manager);
67
        $this->setCheckerManager($checker_manager);
68
    }
69
70
    /**
71
     * {@inheritdoc}
72
     */
73
    public function decrypt(JWEInterface &$jwe, JWKSetInterface $jwk_set = null)
74
    {
75
        $complete_header = $jwe->getHeaders();
76
77
        $this->checkCompleteHeader($complete_header);
78
79
        if (null === $jwk_set) {
80
            $jwk_set = $this->getKeysFromCompleteHeader(
81
                $complete_header,
82
                JWKFinderManagerInterface::KEY_TYPE_PRIVATE | JWKFinderManagerInterface::KEY_TYPE_DIRECT | JWKFinderManagerInterface::KEY_TYPE_SYMMETRIC
83
            );
84
        }
85
        $key_encryption_algorithm = $this->getKeyEncryptionAlgorithm($complete_header['alg']);
86
        $content_encryption_algorithm = $this->getContentEncryptionAlgorithm($complete_header['enc']);
87
88
        foreach ($jwk_set as $jwk) {
89
            if (!$this->checkKeyUsage($jwk, 'decryption')) {
90
                continue;
91
            }
92
            if (!$this->checkKeyAlgorithm($jwk, $key_encryption_algorithm->getAlgorithmName())) {
93
                continue;
94
            }
95
            try {
96
                $cek = $this->decryptCEK($key_encryption_algorithm, $content_encryption_algorithm, $jwk, $jwe->getEncryptedKey(), $complete_header);
97
98
                if (null !== $cek) {
99
                    if (true === $this->decryptPayload($jwe, $cek, $content_encryption_algorithm)) {
100
                        return true;
101
                    }
102
                }
103
            } catch (\InvalidArgumentException $e) {
104
                //We do nothing, we continue with other keys
105
            }
106
        }
107
108
        return false;
109
110
        //$this->getCheckerManager()->checkJWT($jwe);
111
    }
112
113
    /**
114
     * @param \Jose\Algorithm\JWAInterface                                           $key_encryption_algorithm
115
     * @param \Jose\Algorithm\ContentEncryption\ContentEncryptionInterface $content_encryption_algorithm
116
     * @param \Jose\Object\JWKInterface                                           $key
117
     * @param string|null                                                  $encrypted_cek
118
     * @param array                                                        $header
119
     *
120
     * @return string|null
121
     */
122
    private function decryptCEK(JWAInterface $key_encryption_algorithm, ContentEncryptionInterface $content_encryption_algorithm, JWKInterface $key, $encrypted_cek, array $header)
123
    {
124
        if ($key_encryption_algorithm instanceof DirectEncryptionInterface) {
125
            return $key_encryption_algorithm->getCEK($key, $header);
126
        } elseif ($key_encryption_algorithm instanceof KeyAgreementInterface) {
127
            return $key_encryption_algorithm->getAgreementKey($content_encryption_algorithm->getCEKSize(), $key, null, $header);
128
        } elseif ($key_encryption_algorithm instanceof KeyAgreementWrappingInterface) {
129
            return $key_encryption_algorithm->unwrapAgreementKey($key, $encrypted_cek, $content_encryption_algorithm->getCEKSize(), $header);
130
        } elseif ($key_encryption_algorithm instanceof KeyEncryptionInterface) {
131
            return $key_encryption_algorithm->decryptKey($key, $encrypted_cek, $header);
132
        } else {
133
            throw new \RuntimeException('Unsupported CEK generation');
134
        }
135
    }
136
137
    /**
138
     * @param \Jose\Object\JWEInterface                                           $jwe
139
     * @param string                                                       $cek
140
     * @param \Jose\Algorithm\ContentEncryption\ContentEncryptionInterface $content_encryption_algorithm
141
     *
142
     * @return \Jose\Object\JWEInterface
143
     */
144
    private function decryptPayload(JWEInterface &$jwe, $cek, $content_encryption_algorithm)
145
    {
146
        $payload = $content_encryption_algorithm->decryptContent(
147
            $jwe->getCiphertext(),
148
            $cek,
149
            $jwe->getIV(),
150
            $jwe->getAAD(),
151
            $jwe->getEncodedProtectedHeaders(),
152
            $jwe->getTag()
153
        );
154
155
        if (null === $payload) {
156
            return false;
157
        }
158
159
        if ($jwe->hasHeader('zip')) {
160
            $compression_method = $this->getCompressionMethod($jwe->getHeader('zip'));
161
            $payload = $compression_method->uncompress($payload);
162
            if (!is_string($payload)) {
163
                throw new \RuntimeException('Decompression failed');
164
            }
165
        }
166
167
        $payload = $this->getPayloadConverter()->convertStringToPayload($jwe->getHeaders(), $payload);
168
169
        $jwe = $jwe->withPayload($payload);
170
171
        return true;
172
    }
173
174
    /**
175
     * @param array $complete_header
176
     *
177
     * @throws \InvalidArgumentException
178
     */
179
    private function checkCompleteHeader(array $complete_header)
180
    {
181
        foreach (['enc', 'alg'] as $key) {
182
            if (!array_key_exists($key, $complete_header)) {
183
                throw new \InvalidArgumentException(sprintf("Parameters '%s' is missing.", $key));
184
            }
185
        }
186
    }
187
188
    /**
189
     * @param string $algorithm
190
     *
191
     * @return \Jose\Algorithm\KeyEncryption\DirectEncryptionInterface|\Jose\Algorithm\KeyEncryption\KeyEncryptionInterface|\Jose\Algorithm\KeyEncryption\KeyAgreementInterface|\Jose\Algorithm\KeyEncryption\KeyAgreementWrappingInterface
192
     */
193
    private function getKeyEncryptionAlgorithm($algorithm)
194
    {
195
        $key_encryption_algorithm = $this->getJWAManager()->getAlgorithm($algorithm);
196
        foreach ([
197
                     '\Jose\Algorithm\KeyEncryption\DirectEncryptionInterface',
198
                     '\Jose\Algorithm\KeyEncryption\KeyEncryptionInterface',
199
                     '\Jose\Algorithm\KeyEncryption\KeyAgreementInterface',
200
                     '\Jose\Algorithm\KeyEncryption\KeyAgreementWrappingInterface',
201
                 ] as $class) {
202
            if ($key_encryption_algorithm instanceof $class) {
203
                return $key_encryption_algorithm;
204
            }
205
        }
206
        throw new \RuntimeException(sprintf("The key encryption algorithm '%s' is not supported or not a key encryption algorithm instance.", $algorithm));
207
    }
208
209
    /**
210
     * @param $algorithm
211
     *
212
     * @return \Jose\Algorithm\ContentEncryption\ContentEncryptionInterface
213
     */
214
    private function getContentEncryptionAlgorithm($algorithm)
215
    {
216
        $content_encryption_algorithm = $this->getJWAManager()->getAlgorithm($algorithm);
217
        if (!$content_encryption_algorithm instanceof ContentEncryptionInterface) {
218
            throw new \RuntimeException("The algorithm '".$algorithm."' does not implement ContentEncryptionInterface.");
219
        }
220
221
        return $content_encryption_algorithm;
222
    }
223
224
    /**
225
     * @param string $method
226
     *
227
     * @throws \InvalidArgumentException
228
     *
229
     * @return \Jose\Compression\CompressionInterface
230
     */
231
    private function getCompressionMethod($method)
232
    {
233
        $compression_method = $this->getCompressionManager()->getCompressionAlgorithm($method);
234
        if (null === $compression_method) {
235
            throw new \InvalidArgumentException(sprintf("Compression method '%s' not supported"), $method);
236
        }
237
238
        return $compression_method;
239
    }
240
241
    /**
242
     * @param array $header
243
     * @param int   $key_type
244
     *
245
     * @return \Jose\Object\JWKSetInterface
246
     */
247
    private function getKeysFromCompleteHeader(array $header, $key_type)
248
    {
249
        $keys = $this->getJWKFinderManager()->findJWK($header, $key_type);
250
        $jwkset = new JWKSet($keys);
251
252
        return $jwkset;
253
    }
254
}
255