Issues (15)

src/LazyDecoder.php (1 issue)

Severity
1
<?php
2
/* Based on PHPSECLIB: https://github.com/phpseclib/phpseclib/blob/master/phpseclib/File/ASN1.php */
3
4
namespace vakata\asn1;
5
6
/**
7
 * A class handling ASN1 decoding.
8
 */
9
class LazyDecoder extends Decoder
10
{
11
    public function lazyDecodeHeader(array $header)
12
    {
13
        $this->reader->seek($header['content_start']);
14
        if ($header['class'] !== ASN1::CLASS_UNIVERSAL && $header['constructed']) {
15
            $header['children'] = $this->lazyParse(
16
                null,
17
                $header['length'] ? $header['start'] + $header['length'] - 1 : null,
18
                'header'
19
            );
20
        } elseif ($header['class'] === ASN1::CLASS_UNIVERSAL &&
21
            in_array($header['tag'], [ASN1::TYPE_SET, ASN1::TYPE_SEQUENCE])
22
        ) {
23
            $header['children'] = $this->lazyParse(
24
                null,
25
                $header['length'] ? $header['start'] + $header['length'] - 1 : null,
26
                'header'
27
            );
28
        } else {
29
            $header['value'] = $this->decode($header);
30
        }
31
        return $header;
32
    }
33
    public function lazyDecodeValue(array $header)
34
    {
35
        $this->reader->seek($header['content_start']);
36
        if ($header['class'] !== ASN1::CLASS_UNIVERSAL && $header['constructed']) {
37
            return $this->lazyParse(
38
                null,
39
                $header['length'] ? $header['start'] + $header['length'] - 1 : null,
40
                'value'
41
            );
42
        }
43
        if ($header['class'] === ASN1::CLASS_UNIVERSAL &&
44
            in_array($header['tag'], [ASN1::TYPE_SET, ASN1::TYPE_SEQUENCE])
45
        ) {
46
            return $this->lazyParse(
47
                null,
48
                $header['length'] ? $header['start'] + $header['length'] - 1 : null,
49
                'value'
50
            );
51
        }
52
        return $this->decode($header);
53
    }
54
    public function lazyParse($start = null, $max = null, string $mode = 'header')
55
    {
56
        if ($start !== null) {
57
            $this->reader->seek($start);
58
        }
59
        $skeleton = [];
60
        while (!$this->reader->eof() && ($max === null || $this->reader->pos() < $max)) {
61
            $header = $this->header();
62
            if ($header['class'] === 0 && $header['tag'] === 0) {
63
                if ($max === null) {
64
                    break;
65
                } else {
66
                    continue;
67
                }
68
            }
69
            if ($header['length'] === null) {
70
                $this->reader->readUntil(chr(0).chr(0));
71
                $header['length'] = $this->reader->pos() - $header['start'];
72
                $header['content_length'] = $this->reader->pos() - $header['content_start'];
73
            } else {
74
                if ($header['content_length'] > 0) {
75
                    $this->reader->bytes($header['content_length']);
76
                }
77
            }
78
            $skeleton[] = $header;
79
        }
80
        switch ($mode) {
81
            case 'value':
82
                return new LazyArray($skeleton, function ($v) { return $this->lazyDecodeValue($v); });
83
            case 'header':
84
            default:
85
                return new LazyArray($skeleton, function ($v) { return $this->lazyDecodeHeader($v); });
86
        }
87
    }
88
    public function structure($max = null)
89
    {
90
        return $this->lazyParse(0, null, 'header');
91
    }
92
    public function values($skeleton = null)
93
    {
94
        return $this->lazyParse(0, null, 'value');
95
    }
96
    public function map($map, $skeleton = null)
97
    {
98
        $null = null;
99
        if ($skeleton === null && $this->reader->pos() !== 0) {
100
            $this->reader->rewind();
101
        }
102
        $skeleton = $skeleton ?? $this->structure()[0] ?? null;
103
        if (!isset($skeleton)) {
104
            throw new ASN1Exception('No decoded data for map');
105
        }
106
        if ($skeleton['class'] !== ASN1::CLASS_UNIVERSAL) {
107
            if ($map['tag'] === ASN1::TYPE_CHOICE) {
108
                foreach ($map['children'] as $child) {
109
                    if (isset($child['name']) && (int)$skeleton['tag'] === (int)$child['name']) {
110
                        $map = $child;
111
                        if (isset($child['value']) && $child['value']) {
112
                            return $child['value'];
113
                        }
114
                        break;
115
                    }
116
                }
117
            }
118
        }
119
        if ($skeleton['class'] !== ASN1::CLASS_UNIVERSAL) {
120
            if (isset($map['implicit']) && $map['implicit']) {
121
                $skeleton['class'] = ASN1::CLASS_UNIVERSAL;
122
                $skeleton['tag'] = $map['tag'];
123
            } else {
124
                $skeleton = $skeleton['children'][0] ?? null;
125
            }
126
        }
127
        if ($map['tag'] === ASN1::TYPE_CHOICE) {
128
            foreach ($map['children'] as $child) {
129
                if ($skeleton['tag'] === $child['tag']) {
130
                    $map = $child;
131
                    if (isset($child['value']) && $child['value']) {
132
                        return $child['value'];
133
                    }
134
                    break;
135
                }
136
            }
137
        }
138
        if (in_array($map['tag'], [ASN1::TYPE_SEQUENCE, ASN1::TYPE_SET]) &&
139
            in_array($skeleton['tag'], [ASN1::TYPE_SEQUENCE, ASN1::TYPE_SET])) {
140
            $map['tag'] = $skeleton['tag'];
141
        }
142
        if ($map['tag'] === ASN1::TYPE_ANY && isset($skeleton['tag'])) {
143
            $map['tag'] = $skeleton['tag'];
144
        }
145
        if (!in_array($map['tag'], [ASN1::TYPE_ANY, ASN1::TYPE_ANY_RAW, ASN1::TYPE_ANY_SKIP, ASN1::TYPE_ANY_DER]) &&
146
            $map['tag'] !== $skeleton['tag']
147
        ) {
148
            if (!isset($map['optional']) || !$map['optional']) {
149
                throw new ASN1Exception('Decoded data does not match mapping - ' . $skeleton['tag']);
150
            }
151
            return $null;
152
        } else {
153
            switch ($map['tag']) {
154
                case ASN1::TYPE_ANY_DER:
155
                    $temp = $this->reader->chunk($skeleton['start'], $skeleton['length']);
156
                    return $temp;
157
                case ASN1::TYPE_ANY_SKIP:
158
                    return $null;
159
                case ASN1::TYPE_ANY_RAW:
160
                    return $skeleton['value'] ?? null;
161
                case ASN1::TYPE_SET:
162
                    if (isset($map['repeat'])) {
163
                        $mapRepeat = $map['repeat'];
164
                        $temp = $skeleton['children']->rawData();
165
                        return new LazyArray($temp, function ($v) use ($mapRepeat) {
166
                            return $this->map($mapRepeat, $this->lazyDecodeHeader($v));
167
                        });
168
                    } else {
169
                        if (!isset($map['children'])) {
170
                            return $null;
171
                        }
172
                        $temp = $skeleton['children'];
173
                        $result = [];
174
                        // named first
175
                        foreach ($map['children'] as $k => $v) {
176
                            if (isset($v['name'])) {
177
                                $result[$k] = null;
178
                                foreach ($temp as $kk => $vv) {
179
                                    if ($vv['class'] !== ASN1::CLASS_UNIVERSAL && (int)$v['name'] === $vv['tag']) {
180
                                        try {
181
                                            if (isset($v['implicit']) && $v['implicit']) {
182
                                                $vv['class'] = ASN1::CLASS_UNIVERSAL;
183
                                                $vv['tag'] = $map['tag'];
184
                                            } else {
185
                                                $vv = $vv['children'][0] ?? null;
186
                                            }
187
                                            $result[$k] = $this->map($v, $vv);
188
                                            $vv['map'] = $v;
189
                                            $result[$k] = $vv;
190
                                            unset($temp[$kk]);
191
                                            break;
192
                                        } catch (ASN1Exception $e) {
193
                                            // continue trying other children in case of failure
194
                                        }
195
                                    }
196
                                }
197
                                if ($result[$k] === null && (!isset($v['optional']) || !$v['optional'])) {
198
                                    throw new ASN1Exception('Missing tagged type - ' . $k);
199
                                }
200
                            }
201
                        }
202
                        foreach ($map['children'] as $k => $v) {
203
                            if (isset($v['name'])) {
204
                                continue;
205
                            }
206
                            $result[$k] = null;
207
                            foreach ($temp as $kk => $vv) {
208
                                if ($v['tag'] === $vv['tag'] ||
209
                                    in_array(
210
                                        $v['tag'],
211
                                        [
212
                                            ASN1::TYPE_ANY,
213
                                            ASN1::TYPE_ANY_DER,
214
                                            ASN1::TYPE_ANY_RAW,
215
                                            ASN1::TYPE_ANY_SKIP,
216
                                            ASN1::TYPE_CHOICE
217
                                        ]
218
                                    )
219
                                ) {
220
                                    try {
221
                                        $result[$k] = $this->map($v, $vv);
222
                                        $vv['map'] = $v;
223
                                        $result[$k] = $vv;
224
                                        unset($temp[$kk]);
225
                                        break;
226
                                    } catch (ASN1Exception $e) {
227
                                        $result[$k] = null;
228
                                    }
229
                                }
230
                            }
231
                            if ($result[$k] === null && (!isset($v['optional']) || !$v['optional'])) {
232
                                throw new ASN1Exception('Decoded data does not match mapping - ' . $k);
233
                            }
234
                        }
235
                        return new LazyArray($result, function ($v) {
236
                            return $v === null ? null : $this->map($v['map'], $this->lazyDecodeHeader($v));
237
                        });
238
                    }
239
                    break;
240
                case ASN1::TYPE_SEQUENCE:
241
                    if (isset($map['repeat'])) {
242
                        $mapRepeat = $map['repeat'];
243
                        $temp = $skeleton['children']->rawData();
244
                        return new LazyArray($temp, function ($v) use ($mapRepeat) {
245
                            return $this->map($mapRepeat, $this->lazyDecodeHeader($v));
246
                        });
247
                    } else {
248
                        if (!isset($map['children'])) {
249
                            return $null;
250
                        }
251
                        $result = [];
252
                        foreach ($skeleton['children'] as $vv) {
253
                            foreach ($map['children'] as $k => $v) {
254
                                if (isset($v['name']) && $vv['class'] !== ASN1::CLASS_UNIVERSAL &&
255
                                    (int)$v['name'] === $vv['tag']
256
                                ) {
257
                                    if (isset($v['implicit']) && $v['implicit']) {
258
                                        $vv['class'] = ASN1::CLASS_UNIVERSAL;
259
                                        $vv['tag'] = $map['tag'];
260
                                    } else {
261
                                        $vv = $vv['children'][0] ?? null;
262
                                    }
263
                                    $vv['map'] = $v;
264
                                    $result[$k] = $vv;
265
                                    unset($map['children'][$k]);
266
                                    break;
267
                                }
268
                                if (!isset($v['name']) &&
269
                                    (
270
                                        $v['tag'] === $vv['tag'] ||
271
                                        in_array(
272
                                            $v['tag'],
273
                                            [
274
                                                ASN1::TYPE_ANY,
275
                                                ASN1::TYPE_ANY_DER,
276
                                                ASN1::TYPE_ANY_RAW,
277
                                                ASN1::TYPE_ANY_SKIP,
278
                                                ASN1::TYPE_CHOICE
279
                                            ]
280
                                        )
281
                                    )
282
                                ) {
283
                                    try {
284
                                        $temp = $this->map($v, $vv);
0 ignored issues
show
The assignment to $temp is dead and can be removed.
Loading history...
285
                                        $vv['map'] = $v;
286
                                        $result[$k] = $vv;
287
                                        unset($map['children'][$k]);
288
                                        break;
289
                                    } catch (ASN1Exception $e) {
290
                                        // continue trying other children in case of failure
291
                                    }
292
                                }
293
                                if (!isset($v['optional']) || !$v['optional']) {
294
                                    throw new ASN1Exception('Missing type - ' . $k);
295
                                } else {
296
                                    $result[$k] = null;
297
                                    unset($map['children'][$k]);
298
                                }
299
                            }
300
                        }
301
                        return new LazyArray($result, function ($v) {
302
                            return $v === null ? null : $this->map($v['map'], $this->lazyDecodeHeader($v));
303
                        });
304
                    }
305
                    break;
306
                case ASN1::TYPE_OBJECT_IDENTIFIER:
307
                    $temp = isset($map['resolve']) && $map['resolve'] ?
308
                        ASN1::OIDtoText($skeleton['value']) :
309
                        $skeleton['value'];
310
                    return $temp;
311
                case ASN1::TYPE_OCTET_STRING:
312
                    if (isset($map['der']) && $map['der']) {
313
                        $temp = static::fromString($skeleton['value']);
314
                        $temp = isset($map['map']) ? $temp->map($map['map']) : $temp->values();
315
                        return $temp;
316
                    } else {
317
                        $temp = isset($map['raw']) && $map['raw'] ?
318
                            $skeleton['value'] :
319
                            base64_encode($skeleton['value']);
320
                        return $temp;
321
                    }
322
                    break;
323
                case ASN1::TYPE_INTEGER:
324
                    $base = isset($map['base']) && (int)$map['base'] ? (int)$map['base'] : 10;
325
                    if ($base < 3) {
326
                        $result = $skeleton['value'];
327
                    } else {
328
                        if (strlen($skeleton['value']) > 53 && $base === 16) {
329
                            $hex = '';
330
                            for ($i = strlen($skeleton['value']) - 4; $i >= 0; $i-=4) {
331
                                $hex .= dechex((int)bindec(substr($skeleton['value'], $i, 4)));
332
                            }
333
                            $result = strrev($hex);
334
                        } else {
335
                            $temp = base_convert($skeleton['value'], 2, $base);
336
                            if ($base === 10) {
337
                                $temp = (int)$temp;
338
                            }
339
                            $result = $temp;
340
                        }
341
                    }
342
                    if (isset($map['map']) && isset($map['map'][$result])) {
343
                        $result = $map['map'][$result];
344
                    }
345
                    return $result;
346
                case ASN1::TYPE_UTC_TIME:
347
                case ASN1::TYPE_GENERALIZED_TIME:
348
                    return $skeleton['value'];
349
                default:
350
                    $result = $skeleton['value'];
351
                    if (isset($map['map']) && isset($map['map'][$result])) {
352
                        $result = $map['map'][$result];
353
                    }
354
                    return $result;
355
            }
356
        }
357
    }
358
}
359