|
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\Object\JWEInterface; |
|
15
|
|
|
use Jose\Object\JWKInterface; |
|
16
|
|
|
use Jose\Object\JWKSet; |
|
17
|
|
|
use Jose\Object\JWKSetInterface; |
|
18
|
|
|
use Jose\Object\JWSInterface; |
|
19
|
|
|
|
|
20
|
|
|
/** |
|
21
|
|
|
* Class able to load JWS or JWE. |
|
22
|
|
|
* JWS object can also be verified. |
|
23
|
|
|
*/ |
|
24
|
|
|
final class Loader |
|
25
|
|
|
{ |
|
26
|
|
|
/** |
|
27
|
|
|
* @param $input |
|
28
|
|
|
* @param JWKInterface $jwk |
|
29
|
|
|
* @param array $allowed_key_encryption_algorithms |
|
30
|
|
|
* @param array $allowed_content_encryption_algorithms |
|
31
|
|
|
* @param int|null $recipient_index |
|
32
|
|
|
* @return JWEInterface |
|
33
|
|
|
*/ |
|
34
|
|
|
public function loadAndDecryptUsingKey($input, JWKInterface $jwk, array $allowed_key_encryption_algorithms, array $allowed_content_encryption_algorithms, ?int &$recipient_index = null): JWEInterface |
|
35
|
|
|
{ |
|
36
|
|
|
$jwk_set = new JWKSet(); |
|
37
|
|
|
$jwk_set->addKey($jwk); |
|
38
|
|
|
|
|
39
|
|
|
return $this->loadAndDecrypt($input, $jwk_set, $allowed_key_encryption_algorithms, $allowed_content_encryption_algorithms, $recipient_index); |
|
40
|
|
|
} |
|
41
|
|
|
|
|
42
|
|
|
/** |
|
43
|
|
|
* @param $input |
|
44
|
|
|
* @param JWKSetInterface $jwk_set |
|
45
|
|
|
* @param array $allowed_key_encryption_algorithms |
|
46
|
|
|
* @param array $allowed_content_encryption_algorithms |
|
47
|
|
|
* @param int|null $recipient_index |
|
48
|
|
|
* @return JWEInterface |
|
49
|
|
|
*/ |
|
50
|
|
|
public function loadAndDecryptUsingKeySet($input, JWKSetInterface $jwk_set, array $allowed_key_encryption_algorithms, array $allowed_content_encryption_algorithms, ?int &$recipient_index = null): JWEInterface |
|
51
|
|
|
{ |
|
52
|
|
|
return $this->loadAndDecrypt($input, $jwk_set, $allowed_key_encryption_algorithms, $allowed_content_encryption_algorithms, $recipient_index); |
|
53
|
|
|
} |
|
54
|
|
|
|
|
55
|
|
|
/** |
|
56
|
|
|
* @param $input |
|
57
|
|
|
* @param JWKInterface $jwk |
|
58
|
|
|
* @param array $allowed_algorithms |
|
59
|
|
|
* @param int|null $signature_index |
|
60
|
|
|
* @return JWSInterface |
|
61
|
|
|
*/ |
|
62
|
|
|
public function loadAndVerifySignatureUsingKey($input, JWKInterface $jwk, array $allowed_algorithms, ?int &$signature_index = null): JWSInterface |
|
63
|
|
|
{ |
|
64
|
|
|
$jwk_set = new JWKSet(); |
|
65
|
|
|
$jwk_set->addKey($jwk); |
|
66
|
|
|
|
|
67
|
|
|
return $this->loadAndVerifySignature($input, $jwk_set, $allowed_algorithms, null, $signature_index); |
|
68
|
|
|
} |
|
69
|
|
|
|
|
70
|
|
|
/** |
|
71
|
|
|
* @param $input |
|
72
|
|
|
* @param JWKSetInterface $jwk_set |
|
73
|
|
|
* @param array $allowed_algorithms |
|
74
|
|
|
* @param int|null $signature_index |
|
75
|
|
|
* @return JWSInterface |
|
76
|
|
|
*/ |
|
77
|
|
|
public function loadAndVerifySignatureUsingKeySet($input, JWKSetInterface $jwk_set, array $allowed_algorithms, ?int &$signature_index = null): JWSInterface |
|
78
|
|
|
{ |
|
79
|
|
|
return $this->loadAndVerifySignature($input, $jwk_set, $allowed_algorithms, null, $signature_index); |
|
80
|
|
|
} |
|
81
|
|
|
|
|
82
|
|
|
/** |
|
83
|
|
|
* @param $input |
|
84
|
|
|
* @param JWKInterface $jwk |
|
85
|
|
|
* @param array $allowed_algorithms |
|
86
|
|
|
* @param string $detached_payload |
|
87
|
|
|
* @param int|null $signature_index |
|
88
|
|
|
* @return JWSInterface |
|
89
|
|
|
*/ |
|
90
|
|
|
public function loadAndVerifySignatureUsingKeyAndDetachedPayload($input, JWKInterface $jwk, array $allowed_algorithms, string $detached_payload, ?int &$signature_index = null): JWSInterface |
|
91
|
|
|
{ |
|
92
|
|
|
$jwk_set = new JWKSet(); |
|
93
|
|
|
$jwk_set->addKey($jwk); |
|
94
|
|
|
|
|
95
|
|
|
return $this->loadAndVerifySignature($input, $jwk_set, $allowed_algorithms, $detached_payload, $signature_index); |
|
96
|
|
|
} |
|
97
|
|
|
|
|
98
|
|
|
/** |
|
99
|
|
|
* @param $input |
|
100
|
|
|
* @param JWKSetInterface $jwk_set |
|
101
|
|
|
* @param array $allowed_algorithms |
|
102
|
|
|
* @param string $detached_payload |
|
103
|
|
|
* @param int|null $signature_index |
|
104
|
|
|
* @return JWSInterface |
|
105
|
|
|
*/ |
|
106
|
|
|
public function loadAndVerifySignatureUsingKeySetAndDetachedPayload($input, JWKSetInterface $jwk_set, array $allowed_algorithms, string $detached_payload, ?int &$signature_index = null): JWSInterface |
|
107
|
|
|
{ |
|
108
|
|
|
return $this->loadAndVerifySignature($input, $jwk_set, $allowed_algorithms, $detached_payload, $signature_index); |
|
109
|
|
|
} |
|
110
|
|
|
|
|
111
|
|
|
/** |
|
112
|
|
|
* @param string $input |
|
113
|
|
|
* @param JWKSetInterface $jwk_set |
|
114
|
|
|
* @param array $allowed_key_encryption_algorithms |
|
115
|
|
|
* @param array $allowed_content_encryption_algorithms |
|
116
|
|
|
* @param null|int $recipient_index |
|
117
|
|
|
* |
|
118
|
|
|
* @return JWEInterface |
|
119
|
|
|
*/ |
|
120
|
|
|
private function loadAndDecrypt(string $input, JWKSetInterface $jwk_set, array $allowed_key_encryption_algorithms, array $allowed_content_encryption_algorithms, ?int &$recipient_index = null): JWEInterface |
|
121
|
|
|
{ |
|
122
|
|
|
$jwt = $this->load($input); |
|
123
|
|
|
if (!$jwt instanceof JWEInterface) { |
|
124
|
|
|
throw new \InvalidArgumentException('The input is not a valid JWE.'); |
|
125
|
|
|
} |
|
126
|
|
|
$decrypted = Decrypter::createDecrypter($allowed_key_encryption_algorithms, $allowed_content_encryption_algorithms, ['DEF', 'ZLIB', 'GZ']); |
|
127
|
|
|
|
|
128
|
|
|
$decrypted->decryptUsingKeySet($jwt, $jwk_set, $recipient_index); |
|
129
|
|
|
|
|
130
|
|
|
return $jwt; |
|
131
|
|
|
} |
|
132
|
|
|
|
|
133
|
|
|
/** |
|
134
|
|
|
* @param string $input |
|
135
|
|
|
* @param JWKSetInterface $jwk_set |
|
136
|
|
|
* @param array $allowed_algorithms |
|
137
|
|
|
* @param string|null $detached_payload |
|
138
|
|
|
* @param null|int $signature_index |
|
139
|
|
|
* |
|
140
|
|
|
* @return JWSInterface |
|
141
|
|
|
*/ |
|
142
|
|
|
private function loadAndVerifySignature(string $input, JWKSetInterface $jwk_set, array $allowed_algorithms, string $detached_payload = null, ?int &$signature_index = null): JWSInterface |
|
143
|
|
|
{ |
|
144
|
|
|
$jwt = $this->load($input); |
|
145
|
|
|
if (!$jwt instanceof JWSInterface) { |
|
146
|
|
|
throw new \InvalidArgumentException('The input is not a valid JWS.'); |
|
147
|
|
|
} |
|
148
|
|
|
$verifier = Verifier::createVerifier($allowed_algorithms); |
|
149
|
|
|
|
|
150
|
|
|
$verifier->verifyWithKeySet($jwt, $jwk_set, $detached_payload, $signature_index); |
|
|
|
|
|
|
151
|
|
|
|
|
152
|
|
|
return $jwt; |
|
153
|
|
|
} |
|
154
|
|
|
|
|
155
|
|
|
/** |
|
156
|
|
|
* @param string $input |
|
157
|
|
|
* @return JWEInterface|JWSInterface |
|
158
|
|
|
*/ |
|
159
|
|
|
public function load(string $input) |
|
160
|
|
|
{ |
|
161
|
|
|
$json = $this->convert($input); |
|
162
|
|
|
if (array_key_exists('signatures', $json)) { |
|
163
|
|
|
return Util\JWSLoader::loadSerializedJsonJWS($json); |
|
164
|
|
|
} |
|
165
|
|
|
if (array_key_exists('recipients', $json)) { |
|
166
|
|
|
return Util\JWELoader::loadSerializedJsonJWE($json); |
|
167
|
|
|
} |
|
168
|
|
|
} |
|
169
|
|
|
|
|
170
|
|
|
/** |
|
171
|
|
|
* @param string $input |
|
172
|
|
|
* |
|
173
|
|
|
* @return array |
|
174
|
|
|
*/ |
|
175
|
|
|
private function convert(string $input): array |
|
176
|
|
|
{ |
|
177
|
|
|
if (is_array($data = json_decode($input, true))) { |
|
178
|
|
|
if (array_key_exists('signatures', $data) || array_key_exists('recipients', $data)) { |
|
179
|
|
|
return $data; |
|
180
|
|
|
} elseif (array_key_exists('signature', $data)) { |
|
181
|
|
|
return $this->fromFlattenedSerializationSignatureToSerialization($data); |
|
182
|
|
|
} elseif (array_key_exists('ciphertext', $data)) { |
|
183
|
|
|
return $this->fromFlattenedSerializationRecipientToSerialization($data); |
|
184
|
|
|
} |
|
185
|
|
|
} elseif (is_string($input)) { |
|
186
|
|
|
return $this->fromCompactSerializationToSerialization($input); |
|
187
|
|
|
} |
|
188
|
|
|
throw new \InvalidArgumentException('Unsupported input'); |
|
189
|
|
|
} |
|
190
|
|
|
|
|
191
|
|
|
/** |
|
192
|
|
|
* @param array $input |
|
193
|
|
|
* @return array |
|
194
|
|
|
*/ |
|
195
|
|
|
private function fromFlattenedSerializationRecipientToSerialization(array $input): array |
|
196
|
|
|
{ |
|
197
|
|
|
$recipient = []; |
|
198
|
|
|
foreach (['header', 'encrypted_key'] as $key) { |
|
199
|
|
|
if (array_key_exists($key, $input)) { |
|
200
|
|
|
$recipient[$key] = $input[$key]; |
|
201
|
|
|
} |
|
202
|
|
|
} |
|
203
|
|
|
$recipients = [ |
|
204
|
|
|
'ciphertext' => $input['ciphertext'], |
|
205
|
|
|
'recipients' => [$recipient], |
|
206
|
|
|
]; |
|
207
|
|
|
foreach (['protected', 'unprotected', 'iv', 'aad', 'tag'] as $key) { |
|
208
|
|
|
if (array_key_exists($key, $input)) { |
|
209
|
|
|
$recipients[$key] = $input[$key]; |
|
210
|
|
|
} |
|
211
|
|
|
} |
|
212
|
|
|
|
|
213
|
|
|
return $recipients; |
|
214
|
|
|
} |
|
215
|
|
|
|
|
216
|
|
|
/** |
|
217
|
|
|
* @param array $input |
|
218
|
|
|
* @return array |
|
219
|
|
|
*/ |
|
220
|
|
|
private function fromFlattenedSerializationSignatureToSerialization(array $input): array |
|
221
|
|
|
{ |
|
222
|
|
|
$signature = [ |
|
223
|
|
|
'signature' => $input['signature'], |
|
224
|
|
|
]; |
|
225
|
|
|
foreach (['protected', 'header'] as $key) { |
|
226
|
|
|
if (array_key_exists($key, $input)) { |
|
227
|
|
|
$signature[$key] = $input[$key]; |
|
228
|
|
|
} |
|
229
|
|
|
} |
|
230
|
|
|
|
|
231
|
|
|
$temp = []; |
|
232
|
|
|
if (!empty($input['payload'])) { |
|
233
|
|
|
$temp['payload'] = $input['payload']; |
|
234
|
|
|
} |
|
235
|
|
|
$temp['signatures'] = [$signature]; |
|
236
|
|
|
|
|
237
|
|
|
return $temp; |
|
238
|
|
|
} |
|
239
|
|
|
|
|
240
|
|
|
/** |
|
241
|
|
|
* @param string $input |
|
242
|
|
|
* |
|
243
|
|
|
* @return array |
|
244
|
|
|
*/ |
|
245
|
|
|
private function fromCompactSerializationToSerialization($input) |
|
246
|
|
|
{ |
|
247
|
|
|
$parts = explode('.', $input); |
|
248
|
|
|
switch (count($parts)) { |
|
249
|
|
|
case 3: |
|
250
|
|
|
return $this->fromCompactSerializationSignatureToSerialization($parts); |
|
251
|
|
|
case 5: |
|
252
|
|
|
return $this->fromCompactSerializationRecipientToSerialization($parts); |
|
253
|
|
|
default: |
|
254
|
|
|
throw new \InvalidArgumentException('Unsupported input'); |
|
255
|
|
|
} |
|
256
|
|
|
} |
|
257
|
|
|
|
|
258
|
|
|
/** |
|
259
|
|
|
* @param array $parts |
|
260
|
|
|
* |
|
261
|
|
|
* @return array |
|
262
|
|
|
*/ |
|
263
|
|
|
private function fromCompactSerializationRecipientToSerialization(array $parts): array |
|
264
|
|
|
{ |
|
265
|
|
|
$recipient = []; |
|
266
|
|
|
if (!empty($parts[1])) { |
|
267
|
|
|
$recipient['encrypted_key'] = $parts[1]; |
|
268
|
|
|
} |
|
269
|
|
|
|
|
270
|
|
|
$recipients = [ |
|
271
|
|
|
'recipients' => [$recipient], |
|
272
|
|
|
]; |
|
273
|
|
|
foreach ([0 => 'protected', 2 => 'iv', 3 => 'ciphertext', 4 => 'tag'] as $part => $key) { |
|
274
|
|
|
if (!empty($parts[$part])) { |
|
275
|
|
|
$recipients[$key] = $parts[$part]; |
|
276
|
|
|
} |
|
277
|
|
|
} |
|
278
|
|
|
|
|
279
|
|
|
return $recipients; |
|
280
|
|
|
} |
|
281
|
|
|
|
|
282
|
|
|
/** |
|
283
|
|
|
* @param array $parts |
|
284
|
|
|
* |
|
285
|
|
|
* @return array |
|
286
|
|
|
*/ |
|
287
|
|
|
private function fromCompactSerializationSignatureToSerialization(array $parts): array |
|
288
|
|
|
{ |
|
289
|
|
|
$temp = []; |
|
290
|
|
|
|
|
291
|
|
|
if (!empty($parts[1])) { |
|
292
|
|
|
$temp['payload'] = $parts[1]; |
|
293
|
|
|
} |
|
294
|
|
|
$temp['signatures'] = [[ |
|
295
|
|
|
'protected' => $parts[0], |
|
296
|
|
|
'signature' => $parts[2], |
|
297
|
|
|
]]; |
|
298
|
|
|
|
|
299
|
|
|
return $temp; |
|
300
|
|
|
} |
|
301
|
|
|
} |
|
302
|
|
|
|
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.