vakata /
asn1
| 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
Unused Code
introduced
by
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 |