Completed
Push — master ( 8281d7...a782ba )
by Florent
03:56
created

fromSerializationSignatureToFlattenedSerialization()   B

Complexity

Conditions 6
Paths 14

Size

Total Lines 23
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 23
rs 8.5907
cc 6
eloc 14
nc 14
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\Util;
13
14
use Jose\JSONSerializationModes;
15
16
final class Converter
17
{
18
    /**
19
     * This function will try to convert JWS/JWE from a serialization mode into an other.
20
     * It always returns an array:.
21
     *
22
     * @param array|string $input    The JWS/JWE to convert
23
     * @param string       $mode     Output mode
24
     *
25
     * @return string|string[]
26
     */
27
    public static function convert($input, $mode)
28
    {
29
        $prepared = self::getPreparedInput($input);
30
        switch ($mode) {
31
            case JSONSerializationModes::JSON_SERIALIZATION:
32
                return json_encode($prepared);
33
            case JSONSerializationModes::JSON_FLATTENED_SERIALIZATION:
34
                return self::fromSerializationToFlattenedSerialization($prepared);
35
            case JSONSerializationModes::JSON_COMPACT_SERIALIZATION:
36
                return self::fromSerializationToCompactSerialization($prepared);
37
            default:
38
                throw new \InvalidArgumentException(sprintf("The serialization method '%s' is not supported.", $mode));
39
        }
40
    }
41
42
    /**
43
     * This function will merge JWS or JWE. The result is always represents a JWS or JWE Json Serialization.
44
     * It accepts multiple arguments.
45
     *
46
     * @return array
47
     */
48
    public static function merge()
49
    {
50
        $inputs = [];
51
52
        //We convert all parameters into Json Serialization
53
        foreach (func_get_args() as $arg) {
54
            $inputs[] = json_decode(self::convert($arg, JSONSerializationModes::JSON_SERIALIZATION), true);
55
        }
56
57
        if (empty($inputs)) {
58
            throw new \InvalidArgumentException('Nothing to merge');
59
        }
60
        //We verify there is only JWS or JWE
61
        $type = null;
62
        foreach ($inputs as $input) {
63
            if (null === $type) {
64
                $type = self::getType($input);
65
            } else {
66
                $current = self::getType($input);
67
                if ($current !== $type) {
68
                    throw new \InvalidArgumentException('You cannot merge JWS and JWE');
69
                }
70
            }
71
        }
72
        switch ($type) {
73
            case 'JWS':
74
                return json_encode(self::mergeJWS($inputs));
75
            case 'JWE':
76
                return json_encode(self::mergeJWE($inputs));
77
            default:
78
                throw new \InvalidArgumentException('Unsupported input type');
79
        }
80
    }
81
82
    /**
83
     * @param array $inputs
84
     *
85
     * @return array
86
     */
87
    private static function mergeJWS($inputs)
88
    {
89
        $payload = null;
90
        //We verify if all common information are identical
91
        foreach ($inputs as $input) {
92
            if (null === $payload && array_key_exists('payload', $input)) {
93
                $payload = $input['payload'];
94
            } elseif (null !== $payload && array_key_exists('payload', $input)) {
95
                if ($payload !== $input['payload']) {
96
                    throw new \InvalidArgumentException('Unable to merge: a payload is not identical with other inputs');
97
                }
98
            }
99
        }
100
        //All good!
101
        $result = [];
102
        if (null !== $payload) {
103
            $result['payload'] = $payload;
104
        }
105
        $result['signatures'] = self::mergeSignatures($inputs);
106
107
        return $result;
108
    }
109
110
    /**
111
     * @param array $inputs
112
     *
113
     * @return array
114
     */
115
    private static function mergeSignatures($inputs)
116
    {
117
        $result = [];
118
        foreach ($inputs as $input) {
119
            foreach ($input['signatures'] as $recipient) {
120
                $temp = [];
121
                foreach (['header', 'protected', 'signature'] as $key) {
122
                    if (array_key_exists($key, $recipient)) {
123
                        $temp[$key] = $recipient[$key];
124
                    }
125
                }
126
                $result[] = $temp;
127
            }
128
        }
129
130
        return $result;
131
    }
132
133
    /**
134
     * @param array $inputs
135
     *
136
     * @return array
137
     */
138
    private static function mergeJWE($inputs)
139
    {
140
        foreach (['ciphertext', 'protected', 'unprotected', 'iv', 'aad', 'tag'] as $key) {
141
            $$key = null;
142
        }
143
        foreach ($inputs as $input) {
144
            foreach (['ciphertext', 'protected', 'unprotected', 'iv', 'aad', 'tag'] as $key) {
145
                if (null === $$key && array_key_exists($key, $input)) {
146
                    $$key = $input[$key];
147
                } elseif (null !== $$key && array_key_exists($key, $input)) {
148
                    if ($$key !== $input[$key]) {
149
                        throw new \InvalidArgumentException('Unable to merge: parameter "%s" is not identical with other inputs');
150
                    }
151
                }
152
            }
153
        }
154
        //All good!
155
        $result = [];
156
        foreach (['ciphertext', 'protected', 'unprotected', 'iv', 'aad', 'tag'] as $key) {
157
            if (null !== $$key) {
158
                $result[$key] = $$key;
159
            }
160
        }
161
        $result['recipients'] = self::mergeRecipients($inputs);
162
163
        return $result;
164
    }
165
166
    /**
167
     * @param array $inputs
168
     *
169
     * @return array
170
     */
171
    private static function mergeRecipients($inputs)
172
    {
173
        $result = [];
174
        foreach ($inputs as $input) {
175
            foreach ($input['recipients'] as $recipient) {
176
                $temp = [];
177
                foreach (['header', 'encrypted_key'] as $key) {
178
                    if (array_key_exists($key, $recipient)) {
179
                        $temp[$key] = $recipient[$key];
180
                    }
181
                }
182
                $result[] = $temp;
183
            }
184
        }
185
186
        return $result;
187
    }
188
189
    /**
190
     * @param array $input
191
     *
192
     * @return string|string[]
193
     */
194
    private static function fromSerializationToFlattenedSerialization($input)
195
    {
196
        if (array_key_exists('signatures', $input)) {
197
            return self::fromSerializationSignatureToFlattenedSerialization($input);
198
        }
199
200
        return self::fromSerializationRecipientToFlattenedSerialization($input);
201
    }
202
203
    /**
204
     * @param array $input
205
     *
206
     * @return string|string[]
207
     */
208
    private static function fromSerializationSignatureToFlattenedSerialization($input)
209
    {
210
        $signatures = [];
211
        foreach ($input['signatures'] as $signature) {
212
            $temp = [];
213
            if (!empty($input['payload'])) {
214
                $temp['payload'] = $input['payload'];
215
            }
216
            $temp['signature'] = $signature['signature'];
217
218
            foreach (['protected', 'header'] as $key) {
219
                if (array_key_exists($key, $signature)) {
220
                    $temp[$key] = $signature[$key];
221
                }
222
            }
223
            $signatures[] = json_encode($temp);
224
        }
225
        if (1 === count($signatures)) {
226
            $signatures = current($signatures);
227
        }
228
229
        return $signatures;
230
    }
231
232
    /**
233
     * @param array $input
234
     *
235
     * @return string|string[]
236
     */
237
    private static function fromSerializationRecipientToFlattenedSerialization($input)
238
    {
239
        $recipients = [];
240
        foreach ($input['recipients'] as $recipient) {
241
            $temp = [];
242
            foreach (['ciphertext', 'protected', 'unprotected', 'iv', 'aad', 'tag'] as $key) {
243
                if (array_key_exists($key, $input)) {
244
                    $temp[$key] = $input[$key];
245
                }
246
            }
247
            foreach (['header', 'encrypted_key'] as $key) {
248
                if (array_key_exists($key, $recipient)) {
249
                    $temp[$key] = $recipient[$key];
250
                }
251
            }
252
            $recipients[] = json_encode($temp);
253
        }
254
        if (1 === count($recipients)) {
255
            $recipients = current($recipients);
256
        }
257
258
        return $recipients;
259
    }
260
261
    /**
262
     * @param array $input
263
     *
264
     * @return string|string[]
265
     */
266
    private static function fromSerializationToCompactSerialization($input)
267
    {
268
        if (array_key_exists('signatures', $input)) {
269
            return self::fromSerializationSignatureToCompactSerialization($input);
270
        }
271
272
        return self::fromSerializationRecipientToCompactSerialization($input);
273
    }
274
275
    /**
276
     * @param array $input
277
     *
278
     * @return string|string[]
279
     */
280
    private static function fromSerializationSignatureToCompactSerialization($input)
281
    {
282
        $signatures = [];
283
        foreach ($input['signatures'] as $signature) {
284
            if (!array_key_exists('protected', $signature)) {
285
                throw new \InvalidArgumentException("Cannot convert into Compact Json Serialization: 'protected' parameter is missing");
286
            }
287
            if (array_key_exists('header', $signature)) {
288
                throw new \InvalidArgumentException("Cannot convert into Compact Json Serialization: 'header' parameter cannot be kept");
289
            }
290
            $temp = [
291
                $signature['protected'],
292
                isset($input['payload']) ? $input['payload'] : '',
293
                $signature['signature'],
294
            ];
295
            $signatures[] = implode('.', $temp);
296
        }
297
        if (1 === count($signatures)) {
298
            $signatures = current($signatures);
299
        }
300
301
        return $signatures;
302
    }
303
304
    /**
305
     * @param array $input
306
     *
307
     * @return string|string[]
308
     */
309
    private static function fromSerializationRecipientToCompactSerialization($input)
310
    {
311
        $recipients = [];
312
        foreach ($input['recipients'] as $recipient) {
313
            if (array_key_exists('header', $recipient)) {
314
                throw new \InvalidArgumentException("Cannot convert into Compact Json Serialization: 'header' parameter cannot be kept");
315
            }
316
            if (!array_key_exists('protected', $input)) {
317
                throw new \InvalidArgumentException("Cannot convert into Compact Json Serialization: 'protected' parameter is missing");
318
            }
319
            foreach (['unprotected', 'aad'] as $key) {
320
                if (array_key_exists($key, $input)) {
321
                    throw new \InvalidArgumentException(sprintf("Cannot convert into Compact Json Serialization: '%s' parameter cannot be kept", $key));
322
                }
323
            }
324
            $temp = [
325
                $input['protected'],
326
                array_key_exists('encrypted_key', $recipient) ? $recipient['encrypted_key'] : '',
327
                array_key_exists('iv', $input) ? $input['iv'] : '',
328
                $input['ciphertext'],
329
                array_key_exists('tag', $input) ? $input['tag'] : '',
330
            ];
331
            $recipients[] = implode('.', $temp);
332
        }
333
        if (1 === count($recipients)) {
334
            $recipients = current($recipients);
335
        }
336
337
        return $recipients;
338
    }
339
340
    /**
341
     * @param array $input
342
     *
343
     * @return string
344
     */
345
    public static function getType(array $input)
346
    {
347
        if (array_key_exists('signatures', $input)) {
348
            return 'JWS';
349
        } elseif (array_key_exists('ciphertext', $input)) {
350
            return 'JWE';
351
        } else {
352
            throw new \RuntimeException('Unsupported input');
353
        }
354
    }
355
356
    /**
357
     * @param $input
358
     *
359
     * @return array
360
     */
361
    private static function fromFlattenedSerializationRecipientToSerialization($input)
362
    {
363
        $recipient = [];
364
        foreach (['header', 'encrypted_key'] as $key) {
365
            if (array_key_exists($key, $input)) {
366
                $recipient[$key] = $input[$key];
367
            }
368
        }
369
        $recipients = [
370
            'ciphertext' => $input['ciphertext'],
371
            'recipients' => [$recipient],
372
        ];
373
        foreach (['ciphertext', 'protected', 'unprotected', 'iv', 'aad', 'tag'] as $key) {
374
            if (array_key_exists($key, $input)) {
375
                $recipients[$key] = $input[$key];
376
            }
377
        }
378
379
        return $recipients;
380
    }
381
382
    /**
383
     * @param $input
384
     *
385
     * @return array
386
     */
387
    private static function fromFlattenedSerializationSignatureToSerialization($input)
388
    {
389
        $signature = [
390
            'signature' => $input['signature'],
391
        ];
392
        foreach (['protected', 'header'] as $key) {
393
            if (array_key_exists($key, $input)) {
394
                $signature[$key] = $input[$key];
395
            }
396
        }
397
398
        $temp = [];
399
        if (!empty($input['payload'])) {
400
            $temp['payload'] = $input['payload'];
401
        }
402
        $temp['signatures'] = [$signature];
403
404
        return $temp;
405
    }
406
407
    /**
408
     * @param $input
409
     *
410
     * @return array
411
     */
412
    private static function fromCompactSerializationToSerialization($input)
413
    {
414
        $parts = explode('.', $input);
415
        switch (count($parts)) {
416
            case 3:
417
                return self::fromCompactSerializationSignatureToSerialization($parts);
418
            case 5:
419
                return self::fromCompactSerializationRecipientToSerialization($parts);
420
            default:
421
                throw new \InvalidArgumentException('Unsupported input');
422
        }
423
    }
424
425
    /**
426
     * @param array $parts
427
     *
428
     * @return array
429
     */
430
    private static function fromCompactSerializationRecipientToSerialization(array $parts)
431
    {
432
        $recipient = [];
433
        if (!empty($parts[1])) {
434
            $recipient['encrypted_key'] = $parts[1];
435
        }
436
437
        $recipients = [
438
            'recipients' => [$recipient],
439
        ];
440
        foreach ([3 => 'ciphertext', 0 => 'protected', 2 => 'iv', 4 => 'tag'] as $part => $key) {
441
            if (!empty($parts[$part])) {
442
                $recipients[$key] = $parts[$part];
443
            }
444
        }
445
446
        return $recipients;
447
    }
448
449
    /**
450
     * @param array $parts
451
     *
452
     * @return array
453
     */
454
    private static function fromCompactSerializationSignatureToSerialization(array $parts)
455
    {
456
        $temp = [];
457
458
        if (!empty($parts[1])) {
459
            $temp['payload'] = $parts[1];
460
        }
461
        $temp['signatures'] = [[
462
            'protected' => $parts[0],
463
            'signature' => $parts[2],
464
        ]];
465
466
        return $temp;
467
    }
468
469
    /**
470
     * @param string|array $input
471
     *
472
     * @return array
473
     */
474
    private static function getPreparedInput($input)
475
    {
476
        if (is_array($input)) {
477
            if (array_key_exists('signatures', $input) || array_key_exists('recipients', $input)) {
478
                return $input;
479
            }
480
            if (array_key_exists('signature', $input)) {
481
                return self::fromFlattenedSerializationSignatureToSerialization($input);
482
            }
483
            if (array_key_exists('ciphertext', $input)) {
484
                return self::fromFlattenedSerializationRecipientToSerialization($input);
485
            }
486
        } elseif (is_string($input)) {
487
            $json = json_decode($input, true);
488
            if (is_array($json)) {
489
                return self::getPreparedInput($json);
490
            }
491
492
            return self::fromCompactSerializationToSerialization($input);
493
        }
494
        throw new \InvalidArgumentException('Unsupported input');
495
    }
496
}
497