Completed
Push — master ( 7210ec...8e6e06 )
by Florent
08:52
created

Decrypter::getContentEncryptionAlgorithm()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 9
rs 9.6667
cc 2
eloc 5
nc 2
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($jwe->getHeaders());
78
79
        if (null === $jwk_set) {
80
            $jwk_set = $this->getKeysFromCompleteHeader(
81
                $jwe->getHeaders(),
82
                JWKFinderManagerInterface::KEY_TYPE_PRIVATE | JWKFinderManagerInterface::KEY_TYPE_DIRECT | JWKFinderManagerInterface::KEY_TYPE_SYMMETRIC
83
            );
84
        }
85
        $key_encryption_algorithm = $this->getKeyEncryptionAlgorithm($jwe->getHeader('alg'));
86
        $content_encryption_algorithm = $this->getContentEncryptionAlgorithm($jwe->getHeader('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(), $jwe->getHeaders());
97
98
                if (null !== $cek) {
99
                    if (true === $this->decryptPayload($jwe, $cek, $content_encryption_algorithm)) {
100
                        $this->getCheckerManager()->checkJWT($jwe);
101
                        return true;
102
                    }
103
                }
104
            } catch (\InvalidArgumentException $e) {
105
                //We do nothing, we continue with other keys
106
            }
107
        }
108
109
        return false;
110
    }
111
112
    /**
113
     * @param \Jose\Algorithm\JWAInterface                                           $key_encryption_algorithm
114
     * @param \Jose\Algorithm\ContentEncryption\ContentEncryptionInterface $content_encryption_algorithm
115
     * @param \Jose\Object\JWKInterface                                           $key
116
     * @param string|null                                                  $encrypted_cek
117
     * @param array                                                        $header
118
     *
119
     * @return string|null
120
     */
121
    private function decryptCEK(JWAInterface $key_encryption_algorithm, ContentEncryptionInterface $content_encryption_algorithm, JWKInterface $key, $encrypted_cek, array $header)
122
    {
123
        if ($key_encryption_algorithm instanceof DirectEncryptionInterface) {
124
            return $key_encryption_algorithm->getCEK($key, $header);
125
        } elseif ($key_encryption_algorithm instanceof KeyAgreementInterface) {
126
            return $key_encryption_algorithm->getAgreementKey($content_encryption_algorithm->getCEKSize(), $key, null, $header);
127
        } elseif ($key_encryption_algorithm instanceof KeyAgreementWrappingInterface) {
128
            return $key_encryption_algorithm->unwrapAgreementKey($key, $encrypted_cek, $content_encryption_algorithm->getCEKSize(), $header);
129
        } elseif ($key_encryption_algorithm instanceof KeyEncryptionInterface) {
130
            return $key_encryption_algorithm->decryptKey($key, $encrypted_cek, $header);
131
        } else {
132
            throw new \RuntimeException('Unsupported CEK generation');
133
        }
134
    }
135
136
    /**
137
     * @param \Jose\Object\JWEInterface                                           $jwe
138
     * @param string                                                       $cek
139
     * @param \Jose\Algorithm\ContentEncryption\ContentEncryptionInterface $content_encryption_algorithm
140
     *
141
     * @return \Jose\Object\JWEInterface
142
     */
143
    private function decryptPayload(JWEInterface &$jwe, $cek, $content_encryption_algorithm)
144
    {
145
        $payload = $content_encryption_algorithm->decryptContent(
146
            $jwe->getCiphertext(),
147
            $cek,
148
            $jwe->getIV(),
149
            $jwe->getAAD(),
150
            $jwe->getEncodedProtectedHeaders(),
151
            $jwe->getTag()
152
        );
153
154
        if (null === $payload) {
155
            return false;
156
        }
157
158
        if ($jwe->hasHeader('zip')) {
159
            $compression_method = $this->getCompressionMethod($jwe->getHeader('zip'));
160
            $payload = $compression_method->uncompress($payload);
161
            if (!is_string($payload)) {
162
                throw new \RuntimeException('Decompression failed');
163
            }
164
        }
165
166
        $payload = $this->getPayloadConverter()->convertStringToPayload($jwe->getHeaders(), $payload);
167
168
        $jwe = $jwe->withPayload($payload);
169
170
        return true;
171
    }
172
173
    /**
174
     * @param array $complete_header
175
     *
176
     * @throws \InvalidArgumentException
177
     */
178
    private function checkCompleteHeader(array $complete_header)
179
    {
180
        foreach (['enc', 'alg'] as $key) {
181
            if (!array_key_exists($key, $complete_header)) {
182
                throw new \InvalidArgumentException(sprintf("Parameters '%s' is missing.", $key));
183
            }
184
        }
185
    }
186
187
    /**
188
     * @param string $algorithm
189
     *
190
     * @return \Jose\Algorithm\KeyEncryption\DirectEncryptionInterface|\Jose\Algorithm\KeyEncryption\KeyEncryptionInterface|\Jose\Algorithm\KeyEncryption\KeyAgreementInterface|\Jose\Algorithm\KeyEncryption\KeyAgreementWrappingInterface
191
     */
192
    private function getKeyEncryptionAlgorithm($algorithm)
193
    {
194
        $key_encryption_algorithm = $this->getJWAManager()->getAlgorithm($algorithm);
195
        foreach ([
196
                     '\Jose\Algorithm\KeyEncryption\DirectEncryptionInterface',
197
                     '\Jose\Algorithm\KeyEncryption\KeyEncryptionInterface',
198
                     '\Jose\Algorithm\KeyEncryption\KeyAgreementInterface',
199
                     '\Jose\Algorithm\KeyEncryption\KeyAgreementWrappingInterface',
200
                 ] as $class) {
201
            if ($key_encryption_algorithm instanceof $class) {
202
                return $key_encryption_algorithm;
203
            }
204
        }
205
        throw new \RuntimeException(sprintf("The key encryption algorithm '%s' is not supported or not a key encryption algorithm instance.", $algorithm));
206
    }
207
208
    /**
209
     * @param $algorithm
210
     *
211
     * @return \Jose\Algorithm\ContentEncryption\ContentEncryptionInterface
212
     */
213
    private function getContentEncryptionAlgorithm($algorithm)
214
    {
215
        $content_encryption_algorithm = $this->getJWAManager()->getAlgorithm($algorithm);
216
        if (!$content_encryption_algorithm instanceof ContentEncryptionInterface) {
217
            throw new \RuntimeException("The algorithm '".$algorithm."' does not implement ContentEncryptionInterface.");
218
        }
219
220
        return $content_encryption_algorithm;
221
    }
222
223
    /**
224
     * @param string $method
225
     *
226
     * @throws \InvalidArgumentException
227
     *
228
     * @return \Jose\Compression\CompressionInterface
229
     */
230
    private function getCompressionMethod($method)
231
    {
232
        $compression_method = $this->getCompressionManager()->getCompressionAlgorithm($method);
233
        if (null === $compression_method) {
234
            throw new \InvalidArgumentException(sprintf("Compression method '%s' not supported"), $method);
235
        }
236
237
        return $compression_method;
238
    }
239
240
    /**
241
     * @param array $header
242
     * @param int   $key_type
243
     *
244
     * @return \Jose\Object\JWKSetInterface
245
     */
246
    private function getKeysFromCompleteHeader(array $header, $key_type)
247
    {
248
        $keys = $this->getJWKFinderManager()->findJWK($header, $key_type);
249
        $jwkset = new JWKSet($keys);
250
251
        return $jwkset;
252
    }
253
}
254