Completed
Push — master ( d8c018...19ac78 )
by Florent
06:38
created

Loader::convert()   C

Complexity

Conditions 7
Paths 6

Size

Total Lines 24
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 1
Metric Value
c 3
b 1
f 1
dl 0
loc 24
rs 6.7272
cc 7
eloc 16
nc 6
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 Assert\Assertion;
15
use Jose\Behaviour\HasLogger;
16
use Jose\Object\JWEInterface;
17
use Jose\Object\JWKInterface;
18
use Jose\Object\JWKSet;
19
use Jose\Object\JWKSetInterface;
20
use Jose\Object\JWSInterface;
21
use Jose\Util\JWELoader;
22
use Jose\Util\JWSLoader;
23
use Psr\Log\LoggerInterface;
24
use Psr\Log\LogLevel;
25
26
/**
27
 * Class able to load JWS or JWE.
28
 * JWS object can also be verified.
29
 */
30
final class Loader implements LoaderInterface
31
{
32
    use HasLogger;
33
34
    /**
35
     * {@inheritdoc}
36
     */
37
    public function loadAndDecryptUsingKey($input, JWKInterface $jwk, array $allowed_key_encryption_algorithms, array $allowed_content_encryption_algorithms, &$recipient_index = null, LoggerInterface $logger = null)
38
    {
39
        $jwk_set = new JWKSet();
40
        $jwk_set->addKey($jwk);
41
42
        return $this->loadAndDecrypt($input, $jwk_set, $allowed_key_encryption_algorithms, $allowed_content_encryption_algorithms, $recipient_index, $logger);
43
    }
44
45
    /**
46
     * {@inheritdoc}
47
     */
48
    public function loadAndDecryptUsingKeySet($input, JWKSetInterface $jwk_set, array $allowed_key_encryption_algorithms, array $allowed_content_encryption_algorithms, &$recipient_index = null, LoggerInterface $logger = null)
49
    {
50
        return $this->loadAndDecrypt($input, $jwk_set, $allowed_key_encryption_algorithms, $allowed_content_encryption_algorithms, $recipient_index, $logger);
51
    }
52
53
    /**
54
     * {@inheritdoc}
55
     */
56
    public function loadAndVerifySignatureUsingKey($input, JWKInterface $jwk, array $allowed_algorithms, &$signature_index = null, LoggerInterface $logger = null)
57
    {
58
        $jwk_set = new JWKSet();
59
        $jwk_set->addKey($jwk);
60
61
        return $this->loadAndVerifySignature($input, $jwk_set, $allowed_algorithms, null, $signature_index, $logger);
62
    }
63
64
    /**
65
     * {@inheritdoc}
66
     */
67
    public function loadAndVerifySignatureUsingKeySet($input, JWKSetInterface $jwk_set, array $allowed_algorithms, &$signature_index = null, LoggerInterface $logger = null)
68
    {
69
        return $this->loadAndVerifySignature($input, $jwk_set, $allowed_algorithms, null, $signature_index, $logger);
70
    }
71
72
    /**
73
     * {@inheritdoc}
74
     */
75
    public function loadAndVerifySignatureUsingKeyAndDetachedPayload($input, JWKInterface $jwk, array $allowed_algorithms, $detached_payload, &$signature_index = null, LoggerInterface $logger = null)
76
    {
77
        $jwk_set = new JWKSet();
78
        $jwk_set->addKey($jwk);
79
80
        return $this->loadAndVerifySignature($input, $jwk_set, $allowed_algorithms, $detached_payload, $signature_index, $logger);
81
    }
82
83
    /**
84
     * {@inheritdoc}
85
     */
86
    public function loadAndVerifySignatureUsingKeySetAndDetachedPayload($input, JWKSetInterface $jwk_set, array $allowed_algorithms, $detached_payload, &$signature_index = null, LoggerInterface $logger = null)
87
    {
88
        return $this->loadAndVerifySignature($input, $jwk_set, $allowed_algorithms, $detached_payload, $signature_index, $logger);
89
    }
90
91
    /**
92
     * @param string                        $input
93
     * @param \Jose\Object\JWKSetInterface  $jwk_set
94
     * @param array                         $allowed_key_encryption_algorithms
95
     * @param array                         $allowed_content_encryption_algorithms
96
     * @param null|int                      $recipient_index
97
     * @param \Psr\Log\LoggerInterface|null $logger
98
     *
99
     * @return \Jose\Object\JWEInterface
100
     */
101
    private function loadAndDecrypt($input, JWKSetInterface $jwk_set, array $allowed_key_encryption_algorithms, array $allowed_content_encryption_algorithms, &$recipient_index = null, LoggerInterface $logger = null)
102
    {
103
        $jwt = $this->load($input);
104
        Assertion::isInstanceOf($jwt, JWEInterface::class, 'The input is not a valid JWE');
105
        $decrypted = Decrypter::createDecrypter($allowed_key_encryption_algorithms, $allowed_content_encryption_algorithms, ['DEF', 'ZLIB', 'GZ'], $logger);
106
107
        $decrypted->decryptUsingKeySet($jwt, $jwk_set, $recipient_index);
0 ignored issues
show
Bug introduced by
It seems like $jwt defined by $this->load($input) on line 103 can also be of type object<Jose\Object\JWSInterface>; however, Jose\Decrypter::decryptUsingKeySet() does only seem to accept object<Jose\Object\JWEInterface>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
108
109
        return $jwt;
110
    }
111
112
    /**
113
     * @param string                        $input
114
     * @param \Jose\Object\JWKSetInterface  $jwk_set
115
     * @param array                         $allowed_algorithms
116
     * @param string|null                   $detached_payload
117
     * @param null|int                      $signature_index
118
     * @param \Psr\Log\LoggerInterface|null $logger
119
     *
120
     * @return \Jose\Object\JWSInterface
121
     */
122
    private function loadAndVerifySignature($input, JWKSetInterface $jwk_set, array $allowed_algorithms, $detached_payload = null, &$signature_index = null, LoggerInterface $logger = null)
123
    {
124
        $jwt = $this->load($input);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->load($input); of type Jose\Object\JWSInterface|Jose\Object\JWEInterface adds the type Jose\Object\JWEInterface to the return on line 130 which is incompatible with the return type documented by Jose\Loader::loadAndVerifySignature of type Jose\Object\JWSInterface.
Loading history...
125
        Assertion::isInstanceOf($jwt, JWSInterface::class, 'The input is not a valid JWS.');
126
        $verifier = Verifier::createVerifier($allowed_algorithms, $logger);
127
128
        $verifier->verifyWithKeySet($jwt, $jwk_set, $detached_payload, $signature_index);
0 ignored issues
show
Bug introduced by
It seems like $jwt defined by $this->load($input) on line 124 can also be of type object<Jose\Object\JWEInterface>; however, Jose\Verifier::verifyWithKeySet() does only seem to accept object<Jose\Object\JWSInterface>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
129
130
        return $jwt;
131
    }
132
133
    /**
134
     * {@inheritdoc}
135
     */
136
    public function load($input)
137
    {
138
        $this->log(LogLevel::INFO, 'Trying to load the input.', ['input' => $input]);
139
        $json = $this->convert($input);
140
        if (array_key_exists('signatures', $json)) {
141
            $this->log(LogLevel::DEBUG, 'The input is a JWS.', ['json' => $json]);
142
143
            return JWSLoader::loadSerializedJsonJWS($json);
144
        }
145
        if (array_key_exists('recipients', $json)) {
146
            $this->log(LogLevel::DEBUG, 'The input is a JWE.', ['json' => $json]);
147
148
            return JWELoader::loadSerializedJsonJWE($json);
149
        }
150
    }
151
152
    /**
153
     * @param string $input
154
     *
155
     * @return array
156
     */
157
    private function convert($input)
158
    {
159
        if (is_array($data = json_decode($input, true))) {
160
            if (array_key_exists('signatures', $data) || array_key_exists('recipients', $data)) {
161
                $this->log(LogLevel::DEBUG, 'The input seems to be a JWS or a JWE.');
162
163
                return $data;
164
            } elseif (array_key_exists('signature', $data)) {
165
                $this->log(LogLevel::DEBUG, 'The input seems to be a flattened JWS.');
166
167
                return $this->fromFlattenedSerializationSignatureToSerialization($data);
168
            } elseif (array_key_exists('ciphertext', $data)) {
169
                $this->log(LogLevel::DEBUG, 'The input seems to be a flattened JWE.');
170
171
                return $this->fromFlattenedSerializationRecipientToSerialization($data);
172
            }
173
        } elseif (is_string($input)) {
174
            $this->log(LogLevel::DEBUG, 'The input may be a compact JWS or JWE.');
175
176
            return $this->fromCompactSerializationToSerialization($input);
177
        }
178
        $this->log(LogLevel::ERROR, 'The input is not supported');
179
        throw new \InvalidArgumentException('Unsupported input');
180
    }
181
182
    /**
183
     * @param $input
184
     *
185
     * @return array
186
     */
187
    private function fromFlattenedSerializationRecipientToSerialization($input)
188
    {
189
        $recipient = [];
190
        foreach (['header', 'encrypted_key'] as $key) {
191
            if (array_key_exists($key, $input)) {
192
                $recipient[$key] = $input[$key];
193
            }
194
        }
195
        $recipients = [
196
            'ciphertext' => $input['ciphertext'],
197
            'recipients' => [$recipient],
198
        ];
199
        foreach (['protected', 'unprotected', 'iv', 'aad', 'tag'] as $key) {
200
            if (array_key_exists($key, $input)) {
201
                $recipients[$key] = $input[$key];
202
            }
203
        }
204
205
        $this->log(LogLevel::INFO, 'JWE in Flattened JSON Serialization mode loaded.');
206
207
        return $recipients;
208
    }
209
210
    /**
211
     * @param $input
212
     *
213
     * @return array
214
     */
215
    private function fromFlattenedSerializationSignatureToSerialization($input)
216
    {
217
        $signature = [
218
            'signature' => $input['signature'],
219
        ];
220
        foreach (['protected', 'header'] as $key) {
221
            if (array_key_exists($key, $input)) {
222
                $signature[$key] = $input[$key];
223
            }
224
        }
225
226
        $temp = [];
227
        if (!empty($input['payload'])) {
228
            $temp['payload'] = $input['payload'];
229
        }
230
        $temp['signatures'] = [$signature];
231
232
        $this->log(LogLevel::INFO, 'JWS in Flattened JSON Serialization mode loaded.');
233
234
        return $temp;
235
    }
236
237
    /**
238
     * @param string $input
239
     *
240
     * @return array
241
     */
242
    private function fromCompactSerializationToSerialization($input)
243
    {
244
        $parts = explode('.', $input);
245
        switch (count($parts)) {
246
            case 3:
247
                $this->log(LogLevel::DEBUG, 'The input seems to be a compact JWS.');
248
249
                return $this->fromCompactSerializationSignatureToSerialization($parts);
250
            case 5:
251
                $this->log(LogLevel::DEBUG, 'The input seems to be a compact JWE.');
252
253
                return $this->fromCompactSerializationRecipientToSerialization($parts);
254
            default:
255
                throw new \InvalidArgumentException('Unsupported input');
256
        }
257
    }
258
259
    /**
260
     * @param array $parts
261
     *
262
     * @return array
263
     */
264
    private function fromCompactSerializationRecipientToSerialization(array $parts)
265
    {
266
        $recipient = [];
267
        if (!empty($parts[1])) {
268
            $recipient['encrypted_key'] = $parts[1];
269
        }
270
271
        $recipients = [
272
            'recipients' => [$recipient],
273
        ];
274
        foreach ([0 => 'protected', 2 => 'iv', 3 => 'ciphertext', 4 => 'tag'] as $part => $key) {
275
            if (!empty($parts[$part])) {
276
                $recipients[$key] = $parts[$part];
277
            }
278
        }
279
280
        $this->log(LogLevel::INFO, 'JWE in Compact JSON Serialization mode loaded.');
281
282
        return $recipients;
283
    }
284
285
    /**
286
     * @param array $parts
287
     *
288
     * @return array
289
     */
290
    private function fromCompactSerializationSignatureToSerialization(array $parts)
291
    {
292
        $temp = [];
293
294
        if (!empty($parts[1])) {
295
            $temp['payload'] = $parts[1];
296
        }
297
        $temp['signatures'] = [[
298
            'protected' => $parts[0],
299
            'signature' => $parts[2],
300
        ]];
301
302
        $this->log(LogLevel::INFO, 'JWS in Compact JSON Serialization mode loaded.');
303
304
        return $temp;
305
    }
306
}
307