PluginProbe
ManageWP Worker / 3.9.28
ManageWP Worker v3.9.28
4.9.38 4.9.37 4.9.36 4.9.35 4.9.34 3.8.7 3.8.8 3.9.0 3.9.1 3.9.10 3.9.11 3.9.12 3.9.13 3.9.14 3.9.15 3.9.16 3.9.17 3.9.18 3.9.19 3.9.2 3.9.20 3.9.21 3.9.22 3.9.23 3.9.24 All 73 releases
worker / lib / PHPSecLib / File / ASN1.php

ASN1.php in ManageWP Worker 3.9.28, at lib/PHPSecLib/File/ASN1.php

1,294 lines 51.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
3
4 /**
5 * Pure-PHP ASN.1 Parser
6 *
7 * PHP versions 4 and 5
8 *
9 * ASN.1 provides the semantics for data encoded using various schemes. The most commonly
10 * utilized scheme is DER or the "Distinguished Encoding Rules". PEM's are base64 encoded
11 * DER blobs.
12 *
13 * File_ASN1 decodes and encodes DER formatted messages and places them in a semantic context.
14 *
15 * Uses the 1988 ASN.1 syntax.
16 *
17 * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy
18 * of this software and associated documentation files (the "Software"), to deal
19 * in the Software without restriction, including without limitation the rights
20 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
21 * copies of the Software, and to permit persons to whom the Software is
22 * furnished to do so, subject to the following conditions:
23 *
24 * The above copyright notice and this permission notice shall be included in
25 * all copies or substantial portions of the Software.
26 *
27 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
28 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
29 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
30 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
31 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
32 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
33 * THE SOFTWARE.
34 *
35 * @category File
36 * @package File_ASN1
37 * @author Jim Wigginton <terrafrost@php.net>
38 * @copyright MMXII Jim Wigginton
39 * @license http://www.opensource.org/licenses/mit-license.html MIT License
40 * @link http://phpseclib.sourceforge.net
41 */
42
43 /**#@+
44 * Tag Classes
45 *
46 * @access private
47 * @link http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=12
48 */
49 define('FILE_ASN1_CLASS_UNIVERSAL', 0);
50 define('FILE_ASN1_CLASS_APPLICATION', 1);
51 define('FILE_ASN1_CLASS_CONTEXT_SPECIFIC', 2);
52 define('FILE_ASN1_CLASS_PRIVATE', 3);
53 /**#@-*/
54
55 /**#@+
56 * Tag Classes
57 *
58 * @access private
59 * @link http://www.obj-sys.com/asn1tutorial/node124.html
60 */
61 define('FILE_ASN1_TYPE_BOOLEAN', 1);
62 define('FILE_ASN1_TYPE_INTEGER', 2);
63 define('FILE_ASN1_TYPE_BIT_STRING', 3);
64 define('FILE_ASN1_TYPE_OCTET_STRING', 4);
65 define('FILE_ASN1_TYPE_NULL', 5);
66 define('FILE_ASN1_TYPE_OBJECT_IDENTIFIER',6);
67 //define('FILE_ASN1_TYPE_OBJECT_DESCRIPTOR',7);
68 //define('FILE_ASN1_TYPE_INSTANCE_OF', 8); // EXTERNAL
69 define('FILE_ASN1_TYPE_REAL', 9);
70 define('FILE_ASN1_TYPE_ENUMERATED', 10);
71 //define('FILE_ASN1_TYPE_EMBEDDED', 11);
72 define('FILE_ASN1_TYPE_UTF8_STRING', 12);
73 //define('FILE_ASN1_TYPE_RELATIVE_OID', 13);
74 define('FILE_ASN1_TYPE_SEQUENCE', 16); // SEQUENCE OF
75 define('FILE_ASN1_TYPE_SET', 17); // SET OF
76 /**#@-*/
77 /**#@+
78 * More Tag Classes
79 *
80 * @access private
81 * @link http://www.obj-sys.com/asn1tutorial/node10.html
82 */
83 define('FILE_ASN1_TYPE_NUMERIC_STRING', 18);
84 define('FILE_ASN1_TYPE_PRINTABLE_STRING',19);
85 define('FILE_ASN1_TYPE_TELETEX_STRING', 20); // T61String
86 define('FILE_ASN1_TYPE_VIDEOTEX_STRING', 21);
87 define('FILE_ASN1_TYPE_IA5_STRING', 22);
88 define('FILE_ASN1_TYPE_UTC_TIME', 23);
89 define('FILE_ASN1_TYPE_GENERALIZED_TIME',24);
90 define('FILE_ASN1_TYPE_GRAPHIC_STRING', 25);
91 define('FILE_ASN1_TYPE_VISIBLE_STRING', 26); // ISO646String
92 define('FILE_ASN1_TYPE_GENERAL_STRING', 27);
93 define('FILE_ASN1_TYPE_UNIVERSAL_STRING',28);
94 //define('FILE_ASN1_TYPE_CHARACTER_STRING',29);
95 define('FILE_ASN1_TYPE_BMP_STRING', 30);
96 /**#@-*/
97
98 /**#@+
99 * Tag Aliases
100 *
101 * These tags are kinda place holders for other tags.
102 *
103 * @access private
104 */
105 define('FILE_ASN1_TYPE_CHOICE', -1);
106 define('FILE_ASN1_TYPE_ANY', -2);
107 /**#@-*/
108
109 /**
110 * ASN.1 Element
111 *
112 * Bypass normal encoding rules in File_ASN1::encodeDER()
113 *
114 * @author Jim Wigginton <terrafrost@php.net>
115 * @version 0.3.0
116 * @access public
117 * @package File_ASN1
118 */
119 class File_ASN1_Element {
120 /**
121 * Raw element value
122 *
123 * @var String
124 * @access private
125 */
126 var $element;
127
128 /**
129 * Constructor
130 *
131 * @param String $encoded
132 * @return File_ASN1_Element
133 * @access public
134 */
135 function File_ASN1_Element($encoded)
136 {
137 $this->element = $encoded;
138 }
139 }
140
141 /**
142 * Pure-PHP ASN.1 Parser
143 *
144 * @author Jim Wigginton <terrafrost@php.net>
145 * @version 0.3.0
146 * @access public
147 * @package File_ASN1
148 */
149 class File_ASN1 {
150 /**
151 * ASN.1 object identifier
152 *
153 * @var Array
154 * @access private
155 * @link http://en.wikipedia.org/wiki/Object_identifier
156 */
157 var $oids = array();
158
159 /**
160 * Default date format
161 *
162 * @var String
163 * @access private
164 * @link http://php.net/class.datetime
165 */
166 var $format = 'D, d M y H:i:s O';
167
168 /**
169 * Default date format
170 *
171 * @var Array
172 * @access private
173 * @see File_ASN1::setTimeFormat()
174 * @see File_ASN1::asn1map()
175 * @link http://php.net/class.datetime
176 */
177 var $encoded;
178
179 /**
180 * Filters
181 *
182 * If the mapping type is FILE_ASN1_TYPE_ANY what do we actually encode it as?
183 *
184 * @var Array
185 * @access private
186 * @see File_ASN1::_encode_der()
187 */
188 var $filters;
189
190 /**
191 * Type mapping table for the ANY type.
192 *
193 * Structured or unknown types are mapped to a FILE_ASN1_Element.
194 * Unambiguous types get the direct mapping (int/real/bool).
195 * Others are mapped as a choice, with an extra indexing level.
196 *
197 * @var Array
198 * @access public
199 */
200 var $ANYmap = array(
201 FILE_ASN1_TYPE_BOOLEAN => true,
202 FILE_ASN1_TYPE_INTEGER => true,
203 FILE_ASN1_TYPE_BIT_STRING => 'bitString',
204 FILE_ASN1_TYPE_OCTET_STRING => 'octetString',
205 FILE_ASN1_TYPE_NULL => 'null',
206 FILE_ASN1_TYPE_OBJECT_IDENTIFIER => 'objectIdentifier',
207 FILE_ASN1_TYPE_REAL => true,
208 FILE_ASN1_TYPE_ENUMERATED => 'enumerated',
209 FILE_ASN1_TYPE_UTF8_STRING => 'utf8String',
210 FILE_ASN1_TYPE_NUMERIC_STRING => 'numericString',
211 FILE_ASN1_TYPE_PRINTABLE_STRING => 'printableString',
212 FILE_ASN1_TYPE_TELETEX_STRING => 'teletexString',
213 FILE_ASN1_TYPE_VIDEOTEX_STRING => 'videotexString',
214 FILE_ASN1_TYPE_IA5_STRING => 'ia5String',
215 FILE_ASN1_TYPE_UTC_TIME => 'utcTime',
216 FILE_ASN1_TYPE_GENERALIZED_TIME => 'generalTime',
217 FILE_ASN1_TYPE_GRAPHIC_STRING => 'graphicString',
218 FILE_ASN1_TYPE_VISIBLE_STRING => 'visibleString',
219 FILE_ASN1_TYPE_GENERAL_STRING => 'generalString',
220 FILE_ASN1_TYPE_UNIVERSAL_STRING => 'universalString',
221 //FILE_ASN1_TYPE_CHARACTER_STRING => 'characterString',
222 FILE_ASN1_TYPE_BMP_STRING => 'bmpString'
223 );
224
225 /**
226 * String type to character size mapping table.
227 *
228 * Non-convertable types are absent from this table.
229 * size == 0 indicates variable length encoding.
230 *
231 * @var Array
232 * @access public
233 */
234 var $stringTypeSize = array(
235 FILE_ASN1_TYPE_UTF8_STRING => 0,
236 FILE_ASN1_TYPE_BMP_STRING => 2,
237 FILE_ASN1_TYPE_UNIVERSAL_STRING => 4,
238 FILE_ASN1_TYPE_PRINTABLE_STRING => 1,
239 FILE_ASN1_TYPE_TELETEX_STRING => 1,
240 FILE_ASN1_TYPE_IA5_STRING => 1,
241 FILE_ASN1_TYPE_VISIBLE_STRING => 1,
242 );
243
244 /**
245 * Default Constructor.
246 *
247 * @access public
248 */
249 function File_ASN1()
250 {
251 static $static_init = null;
252 if (!$static_init) {
253 $static_init = true;
254 if (!class_exists('Math_BigInteger')) {
255 require_once('Math/BigInteger.php');
256 }
257 }
258 }
259
260 /**
261 * Parse BER-encoding
262 *
263 * Serves a similar purpose to openssl's asn1parse
264 *
265 * @param String $encoded
266 * @return Array
267 * @access public
268 */
269 function decodeBER($encoded)
270 {
271 if (is_object($encoded) && strtolower(get_class($encoded)) == 'file_asn1_element') {
272 $encoded = $encoded->element;
273 }
274
275 $this->encoded = $encoded;
276 return $this->_decode_ber($encoded);
277 }
278
279 /**
280 * Parse BER-encoding (Helper function)
281 *
282 * Sometimes we want to get the BER encoding of a particular tag. $start lets us do that without having to reencode.
283 * $encoded is passed by reference for the recursive calls done for FILE_ASN1_TYPE_BIT_STRING and
284 * FILE_ASN1_TYPE_OCTET_STRING. In those cases, the indefinite length is used.
285 *
286 * @param String $encoded
287 * @param Integer $start
288 * @return Array
289 * @access private
290 */
291 function _decode_ber(&$encoded, $start = 0)
292 {
293 $decoded = array();
294
295 while ( strlen($encoded) ) {
296 $current = array('start' => $start);
297
298 $type = ord($this->_string_shift($encoded));
299 $start++;
300
301 $constructed = ($type >> 5) & 1;
302
303 $tag = $type & 0x1F;
304 if ($tag == 0x1F) {
305 $tag = 0;
306 // process septets (since the eighth bit is ignored, it's not an octet)
307 do {
308 $loop = ord($encoded[0]) >> 7;
309 $tag <<= 7;
310 $tag |= ord($this->_string_shift($encoded)) & 0x7F;
311 $start++;
312 } while ( $loop );
313 }
314
315 // Length, as discussed in paragraph 8.1.3 of X.690-0207.pdf#page=13
316 $length = ord($this->_string_shift($encoded));
317 $start++;
318 if ( $length == 0x80 ) { // indefinite length
319 // "[A sender shall] use the indefinite form (see 8.1.3.6) if the encoding is constructed and is not all
320 // immediately available." -- paragraph 8.1.3.2.c
321 //if ( !$constructed ) {
322 // return false;
323 //}
324 $length = strlen($encoded);
325 } elseif ( $length & 0x80 ) { // definite length, long form
326 // technically, the long form of the length can be represented by up to 126 octets (bytes), but we'll only
327 // support it up to four.
328 $length&= 0x7F;
329 $temp = $this->_string_shift($encoded, $length);
330 // tags of indefinite length don't really have a header length; this length includes the tag
331 $current+= array('headerlength' => $length + 2);
332 $start+= $length;
333 extract(unpack('Nlength', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4)));
334 } else {
335 $current+= array('headerlength' => 2);
336 }
337
338 // End-of-content, see paragraphs 8.1.1.3, 8.1.3.2, 8.1.3.6, 8.1.5, and (for an example) 8.6.4.2
339 if (!$type && !$length) {
340 return $decoded;
341 }
342 $content = $this->_string_shift($encoded, $length);
343
344 /* Class is UNIVERSAL, APPLICATION, PRIVATE, or CONTEXT-SPECIFIC. The UNIVERSAL class is restricted to the ASN.1
345 built-in types. It defines an application-independent data type that must be distinguishable from all other
346 data types. The other three classes are user defined. The APPLICATION class distinguishes data types that
347 have a wide, scattered use within a particular presentation context. PRIVATE distinguishes data types within
348 a particular organization or country. CONTEXT-SPECIFIC distinguishes members of a sequence or set, the
349 alternatives of a CHOICE, or universally tagged set members. Only the class number appears in braces for this
350 data type; the term CONTEXT-SPECIFIC does not appear.
351
352 -- http://www.obj-sys.com/asn1tutorial/node12.html */
353 $class = ($type >> 6) & 3;
354 switch ($class) {
355 case FILE_ASN1_CLASS_APPLICATION:
356 case FILE_ASN1_CLASS_PRIVATE:
357 case FILE_ASN1_CLASS_CONTEXT_SPECIFIC:
358 $decoded[] = array(
359 'type' => $class,
360 'constant' => $tag,
361 'content' => $constructed ? $this->_decode_ber($content, $start) : $content,
362 'length' => $length + $start - $current['start']
363 ) + $current;
364 $start+= $length;
365 continue 2;
366 }
367
368 $current+= array('type' => $tag);
369
370 // decode UNIVERSAL tags
371 switch ($tag) {
372 case FILE_ASN1_TYPE_BOOLEAN:
373 // "The contents octets shall consist of a single octet." -- paragraph 8.2.1
374 //if (strlen($content) != 1) {
375 // return false;
376 //}
377 $current['content'] = (bool) ord($content[0]);
378 break;
379 case FILE_ASN1_TYPE_INTEGER:
380 case FILE_ASN1_TYPE_ENUMERATED:
381 $current['content'] = new Math_BigInteger($content, -256);
382 break;
383 case FILE_ASN1_TYPE_REAL: // not currently supported
384 return false;
385 case FILE_ASN1_TYPE_BIT_STRING:
386 // The initial octet shall encode, as an unsigned binary integer with bit 1 as the least significant bit,
387 // the number of unused bits in the final subsequent octet. The number shall be in the range zero to
388 // seven.
389 if (!$constructed) {
390 $current['content'] = $content;
391 } else {
392 $temp = $this->_decode_ber($content, $start);
393 $length-= strlen($content);
394 $last = count($temp) - 1;
395 for ($i = 0; $i < $last; $i++) {
396 // all subtags should be bit strings
397 //if ($temp[$i]['type'] != FILE_ASN1_TYPE_BIT_STRING) {
398 // return false;
399 //}
400 $current['content'].= substr($temp[$i]['content'], 1);
401 }
402 // all subtags should be bit strings
403 //if ($temp[$last]['type'] != FILE_ASN1_TYPE_BIT_STRING) {
404 // return false;
405 //}
406 $current['content'] = $temp[$last]['content'][0] . $current['content'] . substr($temp[$i]['content'], 1);
407 }
408 break;
409 case FILE_ASN1_TYPE_OCTET_STRING:
410 if (!$constructed) {
411 $current['content'] = $content;
412 } else {
413 $temp = $this->_decode_ber($content, $start);
414 $length-= strlen($content);
415 for ($i = 0, $size = count($temp); $i < $size; $i++) {
416 // all subtags should be octet strings
417 //if ($temp[$i]['type'] != FILE_ASN1_TYPE_OCTET_STRING) {
418 // return false;
419 //}
420 $current['content'].= $temp[$i]['content'];
421 }
422 // $length =
423 }
424 break;
425 case FILE_ASN1_TYPE_NULL:
426 // "The contents octets shall not contain any octets." -- paragraph 8.8.2
427 //if (strlen($content)) {
428 // return false;
429 //}
430 break;
431 case FILE_ASN1_TYPE_SEQUENCE:
432 case FILE_ASN1_TYPE_SET:
433 $current['content'] = $this->_decode_ber($content, $start);
434 break;
435 case FILE_ASN1_TYPE_OBJECT_IDENTIFIER:
436 $temp = ord($this->_string_shift($content));
437 $current['content'] = sprintf('%d.%d', floor($temp / 40), $temp % 40);
438 $valuen = 0;
439 // process septets
440 while (strlen($content)) {
441 $temp = ord($this->_string_shift($content));
442 $valuen <<= 7;
443 $valuen |= $temp & 0x7F;
444 if (~$temp & 0x80) {
445 $current['content'].= ".$valuen";
446 $valuen = 0;
447 }
448 }
449 // the eighth bit of the last byte should not be 1
450 //if ($temp >> 7) {
451 // return false;
452 //}
453 break;
454 /* Each character string type shall be encoded as if it had been declared:
455 [UNIVERSAL x] IMPLICIT OCTET STRING
456
457 -- X.690-0207.pdf#page=23 (paragraph 8.21.3)
458
459 Per that, we're not going to do any validation. If there are any illegal characters in the string,
460 we don't really care */
461 case FILE_ASN1_TYPE_NUMERIC_STRING:
462 // 0,1,2,3,4,5,6,7,8,9, and space
463 case FILE_ASN1_TYPE_PRINTABLE_STRING:
464 // Upper and lower case letters, digits, space, apostrophe, left/right parenthesis, plus sign, comma,
465 // hyphen, full stop, solidus, colon, equal sign, question mark
466 case FILE_ASN1_TYPE_TELETEX_STRING:
467 // The Teletex character set in CCITT's T61, space, and delete
468 // see http://en.wikipedia.org/wiki/Teletex#Character_sets
469 case FILE_ASN1_TYPE_VIDEOTEX_STRING:
470 // The Videotex character set in CCITT's T.100 and T.101, space, and delete
471 case FILE_ASN1_TYPE_VISIBLE_STRING:
472 // Printing character sets of international ASCII, and space
473 case FILE_ASN1_TYPE_IA5_STRING:
474 // International Alphabet 5 (International ASCII)
475 case FILE_ASN1_TYPE_GRAPHIC_STRING:
476 // All registered G sets, and space
477 case FILE_ASN1_TYPE_GENERAL_STRING:
478 // All registered C and G sets, space and delete
479 case FILE_ASN1_TYPE_UTF8_STRING:
480 // ????
481 case FILE_ASN1_TYPE_BMP_STRING:
482 $current['content'] = $content;
483 break;
484 case FILE_ASN1_TYPE_UTC_TIME:
485 case FILE_ASN1_TYPE_GENERALIZED_TIME:
486 $current['content'] = $this->_decodeTime($content, $tag);
487 default:
488
489 }
490
491 $start+= $length;
492 $decoded[] = $current + array('length' => $start - $current['start']);
493 }
494
495 return $decoded;
496 }
497
498 /**
499 * ASN.1 Decode
500 *
501 * Provides an ASN.1 semantic mapping ($mapping) from a parsed BER-encoding to a human readable format.
502 *
503 * @param Array $decoded
504 * @param Array $mapping
505 * @return Array
506 * @access public
507 */
508 function asn1map($decoded, $mapping)
509 {
510 if (isset($mapping['explicit'])) {
511 $decoded = $decoded['content'][0];
512 }
513
514 switch (true) {
515 case $mapping['type'] == FILE_ASN1_TYPE_ANY:
516 $intype = $decoded['type'];
517 if (isset($decoded['constant']) || !isset($this->ANYmap[$intype]) || ($this->encoded[$decoded['start']] & 0x20)) {
518 return new File_ASN1_Element(substr($this->encoded, $decoded['start'], $decoded['length']));
519 }
520 $inmap = $this->ANYmap[$intype];
521 if (is_string($inmap)) {
522 return array($inmap => $this->asn1map($decoded, array('type' => $intype) + $mapping));
523 }
524 break;
525 case $mapping['type'] == FILE_ASN1_TYPE_CHOICE:
526 foreach ($mapping['children'] as $key => $option) {
527 switch (true) {
528 case isset($option['constant']) && $option['constant'] == $decoded['constant']:
529 case !isset($option['constant']) && $option['type'] == $decoded['type']:
530 $value = $this->asn1map($decoded, $option);
531 break;
532 case !isset($option['constant']) && $option['type'] == FILE_ASN1_TYPE_CHOICE:
533 $v = $this->asn1map($decoded, $option);
534 if (isset($v)) {
535 $value = $v;
536 }
537 }
538 if (isset($value)) {
539 return array($key => $value);
540 }
541 }
542 return NULL;
543 case isset($mapping['implicit']):
544 case isset($mapping['explicit']):
545 case $decoded['type'] == $mapping['type']:
546 break;
547 default:
548 return NULL;
549 }
550
551 if (isset($mapping['implicit'])) {
552 $decoded['type'] = $mapping['type'];
553 }
554
555 switch ($decoded['type']) {
556 case FILE_ASN1_TYPE_SEQUENCE:
557 $map = array();
558
559 // ignore the min and max
560 if (isset($mapping['min']) && isset($mapping['max'])) {
561 $child = $mapping['children'];
562 foreach ($decoded['content'] as $content) {
563 if (($map[] = $this->asn1map($content, $child)) === NULL) {
564 return NULL;
565 }
566 }
567
568 return $map;
569 }
570
571 $n = count($decoded['content']);
572 $i = 0;
573
574 foreach ($mapping['children'] as $key => $child) {
575 $maymatch = $i < $n; // Match only existing input.
576 if ($maymatch) {
577 $temp = $decoded['content'][$i];
578
579 if ($child['type'] != FILE_ASN1_TYPE_CHOICE) {
580 // Get the mapping and input class & constant.
581 $childClass = $tempClass = FILE_ASN1_CLASS_UNIVERSAL;
582 $constant = NULL;
583 if (isset($temp['constant'])) {
584 $tempClass = isset($temp['class']) ? $temp['class'] : FILE_ASN1_CLASS_CONTEXT_SPECIFIC;
585 }
586 if (isset($child['class'])) {
587 $childClass = $child['class'];
588 $constant = $child['cast'];
589 }
590 elseif (isset($child['constant'])) {
591 $childClass = FILE_ASN1_CLASS_CONTEXT_SPECIFIC;
592 $constant = $child['constant'];
593 }
594
595 if (isset($constant) && isset($temp['constant'])) {
596 // Can only match if constants and class match.
597 $maymatch = $constant == $temp['constant'] && $childClass == $tempClass;
598 } else {
599 // Can only match if no constant expected and type matches or is generic.
600 $maymatch = !isset($child['constant']) && array_search($child['type'], array($temp['type'], FILE_ASN1_TYPE_ANY, FILE_ASN1_TYPE_CHOICE)) !== false;
601 }
602 }
603 }
604
605 if ($maymatch) {
606 // Attempt submapping.
607 $candidate = $this->asn1map($temp, $child);
608 $maymatch = $candidate !== NULL;
609 }
610
611 if ($maymatch) {
612 // Got the match: use it.
613 $map[$key] = $candidate;
614 $i++;
615 } elseif (isset($child['default'])) {
616 $map[$key] = $child['default']; // Use default.
617 } elseif (!isset($child['optional'])) {
618 return NULL; // Syntax error.
619 }
620 }
621
622 // Fail mapping if all input items have not been consumed.
623 return $i < $n? NULL: $map;
624
625 // the main diff between sets and sequences is the encapsulation of the foreach in another for loop
626 case FILE_ASN1_TYPE_SET:
627 $map = array();
628
629 // ignore the min and max
630 if (isset($mapping['min']) && isset($mapping['max'])) {
631 $child = $mapping['children'];
632 foreach ($decoded['content'] as $content) {
633 if (($map[] = $this->asn1map($content, $child)) === NULL) {
634 return NULL;
635 }
636 }
637
638 return $map;
639 }
640
641 for ($i = 0; $i < count($decoded['content']); $i++) {
642 $temp = $decoded['content'][$i];
643 $tempClass = FILE_ASN1_CLASS_UNIVERSAL;
644 if (isset($temp['constant'])) {
645 $tempClass = isset($temp['class']) ? $temp['class'] : FILE_ASN1_CLASS_CONTEXT_SPECIFIC;
646 }
647
648 foreach ($mapping['children'] as $key => $child) {
649 if (isset($map[$key])) {
650 continue;
651 }
652 $maymatch = true;
653 if ($child['type'] != FILE_ASN1_TYPE_CHOICE) {
654 $childClass = FILE_ASN1_CLASS_UNIVERSAL;
655 $constant = NULL;
656 if (isset($child['class'])) {
657 $childClass = $child['class'];
658 $constant = $child['cast'];
659 }
660 elseif (isset($child['constant'])) {
661 $childClass = FILE_ASN1_CLASS_CONTEXT_SPECIFIC;
662 $constant = $child['constant'];
663 }
664
665 if (isset($constant) && isset($temp['constant'])) {
666 // Can only match if constants and class match.
667 $maymatch = $constant == $temp['constant'] && $childClass == $tempClass;
668 } else {
669 // Can only match if no constant expected and type matches or is generic.
670 $maymatch = !isset($child['constant']) && array_search($child['type'], array($temp['type'], FILE_ASN1_TYPE_ANY, FILE_ASN1_TYPE_CHOICE)) !== false;
671 }
672 }
673
674 if ($maymatch) {
675 // Attempt submapping.
676 $candidate = $this->asn1map($temp, $child);
677 $maymatch = $candidate !== NULL;
678 }
679
680 if (!$maymatch) {
681 break;
682 }
683
684 // Got the match: use it.
685 $map[$key] = $candidate;
686 break;
687 }
688 }
689
690 foreach ($mapping['children'] as $key => $child) {
691 if (!isset($map[$key])) {
692 if (isset($child['default'])) {
693 $map[$key] = $child['default'];
694 } elseif (!isset($child['optional'])) {
695 return NULL;
696 }
697 }
698 }
699 return $map;
700 case FILE_ASN1_TYPE_OBJECT_IDENTIFIER:
701 return isset($this->oids[$decoded['content']]) ? $this->oids[$decoded['content']] : $decoded['content'];
702 case FILE_ASN1_TYPE_UTC_TIME:
703 case FILE_ASN1_TYPE_GENERALIZED_TIME:
704 if (isset($mapping['implicit'])) {
705 $decoded['content'] = $this->_decodeTime($decoded['content'], $decoded['type']);
706 }
707 return @date($this->format, $decoded['content']);
708 case FILE_ASN1_TYPE_BIT_STRING:
709 if (isset($mapping['mapping'])) {
710 $offset = ord($decoded['content'][0]);
711 $size = (strlen($decoded['content']) - 1) * 8 - $offset;
712 /*
713 From X.680-0207.pdf#page=46 (21.7):
714
715 "When a "NamedBitList" is used in defining a bitstring type ASN.1 encoding rules are free to add (or remove)
716 arbitrarily any trailing 0 bits to (or from) values that are being encoded or decoded. Application designers should
717 therefore ensure that different semantics are not associated with such values which differ only in the number of trailing
718 0 bits."
719 */
720 $bits = count($mapping['mapping']) == $size ? array() : array_fill(0, count($mapping['mapping']) - $size, false);
721 for ($i = strlen($decoded['content']) - 1; $i > 0; $i--) {
722 $current = ord($decoded['content'][$i]);
723 for ($j = $offset; $j < 8; $j++) {
724 $bits[] = (bool) ($current & (1 << $j));
725 }
726 $offset = 0;
727 }
728 $values = array();
729 $map = array_reverse($mapping['mapping']);
730 foreach ($map as $i => $value) {
731 if ($bits[$i]) {
732 $values[] = $value;
733 }
734 }
735 return $values;
736 }
737 case FILE_ASN1_TYPE_OCTET_STRING:
738 return base64_encode($decoded['content']);
739 case FILE_ASN1_TYPE_NULL:
740 return '';
741 case FILE_ASN1_TYPE_BOOLEAN:
742 return $decoded['content'];
743 case FILE_ASN1_TYPE_NUMERIC_STRING:
744 case FILE_ASN1_TYPE_PRINTABLE_STRING:
745 case FILE_ASN1_TYPE_TELETEX_STRING:
746 case FILE_ASN1_TYPE_VIDEOTEX_STRING:
747 case FILE_ASN1_TYPE_IA5_STRING:
748 case FILE_ASN1_TYPE_GRAPHIC_STRING:
749 case FILE_ASN1_TYPE_VISIBLE_STRING:
750 case FILE_ASN1_TYPE_GENERAL_STRING:
751 case FILE_ASN1_TYPE_UNIVERSAL_STRING:
752 case FILE_ASN1_TYPE_UTF8_STRING:
753 case FILE_ASN1_TYPE_BMP_STRING:
754 return $decoded['content'];
755 case FILE_ASN1_TYPE_INTEGER:
756 case FILE_ASN1_TYPE_ENUMERATED:
757 $temp = $decoded['content'];
758 if (isset($mapping['implicit'])) {
759 $temp = new Math_BigInteger($decoded['content'], -256);
760 }
761 if (isset($mapping['mapping'])) {
762 $temp = (int) $temp->toString();
763 return isset($mapping['mapping'][$temp]) ?
764 $mapping['mapping'][$temp] :
765 false;
766 }
767 return $temp;
768 }
769 }
770
771 /**
772 * ASN.1 Encode
773 *
774 * DER-encodes an ASN.1 semantic mapping ($mapping). Some libraries would probably call this function
775 * an ASN.1 compiler.
776 *
777 * @param String $source
778 * @param String $mapping
779 * @param Integer $idx
780 * @return String
781 * @access public
782 */
783 function encodeDER($source, $mapping)
784 {
785 $this->location = array();
786 return $this->_encode_der($source, $mapping);
787 }
788
789 /**
790 * ASN.1 Encode (Helper function)
791 *
792 * @param String $source
793 * @param String $mapping
794 * @param Integer $idx
795 * @return String
796 * @access private
797 */
798 function _encode_der($source, $mapping, $idx = NULL)
799 {
800 if (is_object($source) && strtolower(get_class($source)) == 'file_asn1_element') {
801 return $source->element;
802 }
803
804 // do not encode (implicitly optional) fields with value set to default
805 if (isset($mapping['default']) && $source === $mapping['default']) {
806 return '';
807 }
808
809 if (isset($idx)) {
810 $this->location[] = $idx;
811 }
812
813 $tag = $mapping['type'];
814
815 switch ($tag) {
816 case FILE_ASN1_TYPE_SET: // Children order is not important, thus process in sequence.
817 case FILE_ASN1_TYPE_SEQUENCE:
818 $tag|= 0x20; // set the constructed bit
819 $value = '';
820
821 // ignore the min and max
822 if (isset($mapping['min']) && isset($mapping['max'])) {
823 $child = $mapping['children'];
824
825 foreach ($source as $content) {
826 $temp = $this->_encode_der($content, $child);
827 if ($temp === false) {
828 return false;
829 }
830 $value.= $temp;
831 }
832 break;
833 }
834
835 foreach ($mapping['children'] as $key => $child) {
836 if (!isset($source[$key])) {
837 if (!isset($child['optional'])) {
838 return false;
839 }
840 continue;
841 }
842
843 $temp = $this->_encode_der($source[$key], $child, $key);
844 if ($temp === false) {
845 return false;
846 }
847
848 // An empty child encoding means it has been optimized out.
849 // Else we should have at least one tag byte.
850 if ($temp === '') {
851 continue;
852 }
853
854 // if isset($child['constant']) is true then isset($child['optional']) should be true as well
855 if (isset($child['constant'])) {
856 /*
857 From X.680-0207.pdf#page=58 (30.6):
858
859 "The tagging construction specifies explicit tagging if any of the following holds:
860 ...
861 c) the "Tag Type" alternative is used and the value of "TagDefault" for the module is IMPLICIT TAGS or
862 AUTOMATIC TAGS, but the type defined by "Type" is an untagged choice type, an untagged open type, or
863 an untagged "DummyReference" (see ITU-T Rec. X.683 | ISO/IEC 8824-4, 8.3)."
864 */
865 if (isset($child['explicit']) || $child['type'] == FILE_ASN1_TYPE_CHOICE) {
866 $subtag = chr((FILE_ASN1_CLASS_CONTEXT_SPECIFIC << 6) | 0x20 | $child['constant']);
867 $temp = $subtag . $this->_encodeLength(strlen($temp)) . $temp;
868 } else {
869 $subtag = chr((FILE_ASN1_CLASS_CONTEXT_SPECIFIC << 6) | (ord($temp[0]) & 0x20) | $child['constant']);
870 $temp = $subtag . substr($temp, 1);
871 }
872 }
873 $value.= $temp;
874 }
875 break;
876 case FILE_ASN1_TYPE_CHOICE:
877 $temp = false;
878
879 foreach ($mapping['children'] as $key => $child) {
880 if (!isset($source[$key])) {
881 continue;
882 }
883
884 $temp = $this->_encode_der($source[$key], $child, $key);
885 if ($temp === false) {
886 return false;
887 }
888
889 // An empty child encoding means it has been optimized out.
890 // Else we should have at least one tag byte.
891 if ($temp === '') {
892 continue;
893 }
894
895 $tag = ord($temp[0]);
896
897 // if isset($child['constant']) is true then isset($child['optional']) should be true as well
898 if (isset($child['constant'])) {
899 if (isset($child['explicit']) || $child['type'] == FILE_ASN1_TYPE_CHOICE) {
900 $subtag = chr((FILE_ASN1_CLASS_CONTEXT_SPECIFIC << 6) | 0x20 | $child['constant']);
901 $temp = $subtag . $this->_encodeLength(strlen($temp)) . $temp;
902 } else {
903 $subtag = chr((FILE_ASN1_CLASS_CONTEXT_SPECIFIC << 6) | (ord($temp[0]) & 0x20) | $child['constant']);
904 $temp = $subtag . substr($temp, 1);
905 }
906 }
907 }
908
909 if (isset($idx)) {
910 array_pop($this->location);
911 }
912
913 if ($temp && isset($mapping['cast'])) {
914 $temp[0] = chr(($mapping['class'] << 6) | ($tag & 0x20) | $mapping['cast']);
915 }
916
917 return $temp;
918 case FILE_ASN1_TYPE_INTEGER:
919 case FILE_ASN1_TYPE_ENUMERATED:
920 if (!isset($mapping['mapping'])) {
921 $value = $source->toBytes(true);
922 } else {
923 $value = array_search($source, $mapping['mapping']);
924 if ($value === false) {
925 return false;
926 }
927 $value = new Math_BigInteger($value);
928 $value = $value->toBytes(true);
929 if (!strlen($value)) {
930 $value = chr(0);
931 }
932 }
933 break;
934 case FILE_ASN1_TYPE_UTC_TIME:
935 case FILE_ASN1_TYPE_GENERALIZED_TIME:
936 $format = $mapping['type'] == FILE_ASN1_TYPE_UTC_TIME ? 'y' : 'Y';
937 $format.= 'mdHis';
938 $value = @gmdate($format, strtotime($source)) . 'Z';
939 break;
940 case FILE_ASN1_TYPE_BIT_STRING:
941 if (isset($mapping['mapping'])) {
942 $bits = array_fill(0, count($mapping['mapping']), 0);
943 $size = 0;
944 for ($i = 0; $i < count($mapping['mapping']); $i++) {
945 if (in_array($mapping['mapping'][$i], $source)) {
946 $bits[$i] = 1;
947 $size = $i;
948 }
949 }
950
951 $offset = 8 - (($size + 1) & 7);
952 $offset = $offset !== 8 ? $offset : 0;
953
954 $value = chr($offset);
955
956 for ($i = $size + 1; $i < count($mapping['mapping']); $i++) {
957 unset($bits[$i]);
958 }
959
960 $bits = implode('', array_pad($bits, $size + $offset + 1, 0));
961 $bytes = explode(' ', rtrim(chunk_split($bits, 8, ' ')));
962 foreach ($bytes as $byte) {
963 $value.= chr(bindec($byte));
964 }
965
966 break;
967 }
968 case FILE_ASN1_TYPE_OCTET_STRING:
969 /* The initial octet shall encode, as an unsigned binary integer with bit 1 as the least significant bit,
970 the number of unused bits in the final subsequent octet. The number shall be in the range zero to seven.
971
972 -- http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=16 */
973 $value = base64_decode($source);
974 break;
975 case FILE_ASN1_TYPE_OBJECT_IDENTIFIER:
976 $oid = preg_match('#(?:\d+\.)+#', $source) ? $source : array_search($source, $this->oids);
977 if ($oid === false) {
978 user_error('Invalid OID');
979 return false;
980 }
981 $value = '';
982 $parts = explode('.', $oid);
983 $value = chr(40 * $parts[0] + $parts[1]);
984 for ($i = 2; $i < count($parts); $i++) {
985 $temp = '';
986 if (!$parts[$i]) {
987 $temp = "\0";
988 } else {
989 while ($parts[$i]) {
990 $temp = chr(0x80 | ($parts[$i] & 0x7F)) . $temp;
991 $parts[$i] >>= 7;
992 }
993 $temp[strlen($temp) - 1] = $temp[strlen($temp) - 1] & chr(0x7F);
994 }
995 $value.= $temp;
996 }
997 break;
998 case FILE_ASN1_TYPE_ANY:
999 $loc = $this->location;
1000 if (isset($idx)) {
1001 array_pop($this->location);
1002 }
1003
1004 switch (true) {
1005 case !isset($source):
1006 return $this->_encode_der(NULL, array('type' => FILE_ASN1_TYPE_NULL) + $mapping);
1007 case is_int($source):
1008 case is_object($source) && strtolower(get_class($source)) == 'math_biginteger':
1009 return $this->_encode_der($source, array('type' => FILE_ASN1_TYPE_INTEGER) + $mapping);
1010 case is_float($source):
1011 return $this->_encode_der($source, array('type' => FILE_ASN1_TYPE_REAL) + $mapping);
1012 case is_bool($source):
1013 return $this->_encode_der($source, array('type' => FILE_ASN1_TYPE_BOOLEAN) + $mapping);
1014 case is_array($source) && count($source) == 1:
1015 $typename = implode('', array_keys($source));
1016 $outtype = array_search($typename, $this->ANYmap, true);
1017 if ($outtype !== false) {
1018 return $this->_encode_der($source[$typename], array('type' => $outtype) + $mapping);
1019 }
1020 }
1021
1022 $filters = $this->filters;
1023 foreach ($loc as $part) {
1024 if (!isset($filters[$part])) {
1025 $filters = false;
1026 break;
1027 }
1028 $filters = $filters[$part];
1029 }
1030 if ($filters === false) {
1031 user_error('No filters defined for ' . implode('/', $loc));
1032 return false;
1033 }
1034 return $this->_encode_der($source, $filters + $mapping);
1035 case FILE_ASN1_TYPE_NULL:
1036 $value = '';
1037 break;
1038 case FILE_ASN1_TYPE_NUMERIC_STRING:
1039 case FILE_ASN1_TYPE_TELETEX_STRING:
1040 case FILE_ASN1_TYPE_PRINTABLE_STRING:
1041 case FILE_ASN1_TYPE_UNIVERSAL_STRING:
1042 case FILE_ASN1_TYPE_UTF8_STRING:
1043 case FILE_ASN1_TYPE_BMP_STRING:
1044 case FILE_ASN1_TYPE_IA5_STRING:
1045 case FILE_ASN1_TYPE_VISIBLE_STRING:
1046 case FILE_ASN1_TYPE_VIDEOTEX_STRING:
1047 case FILE_ASN1_TYPE_GRAPHIC_STRING:
1048 case FILE_ASN1_TYPE_GENERAL_STRING:
1049 $value = $source;
1050 break;
1051 case FILE_ASN1_TYPE_BOOLEAN:
1052 $value = $source ? "\xFF" : "\x00";
1053 break;
1054 default:
1055 user_error('Mapping provides no type definition for ' . implode('/', $this->location));
1056 return false;
1057 }
1058
1059 if (isset($idx)) {
1060 array_pop($this->location);
1061 }
1062
1063 if (isset($mapping['cast'])) {
1064 $tag = ($mapping['class'] << 6) | ($tag & 0x20) | $mapping['cast'];
1065 }
1066
1067 return chr($tag) . $this->_encodeLength(strlen($value)) . $value;
1068 }
1069
1070 /**
1071 * DER-encode the length
1072 *
1073 * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See
1074 * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information.
1075 *
1076 * @access private
1077 * @param Integer $length
1078 * @return String
1079 */
1080 function _encodeLength($length)
1081 {
1082 if ($length <= 0x7F) {
1083 return chr($length);
1084 }
1085
1086 $temp = ltrim(pack('N', $length), chr(0));
1087 return pack('Ca*', 0x80 | strlen($temp), $temp);
1088 }
1089
1090 /**
1091 * BER-decode the time
1092 *
1093 * Called by _decode_ber() and in the case of implicit tags asn1map().
1094 *
1095 * @access private
1096 * @param String $content
1097 * @param Integer $tag
1098 * @return String
1099 */
1100 function _decodeTime($content, $tag)
1101 {
1102 /* UTCTime:
1103 http://tools.ietf.org/html/rfc5280#section-4.1.2.5.1
1104 http://www.obj-sys.com/asn1tutorial/node15.html
1105
1106 GeneralizedTime:
1107 http://tools.ietf.org/html/rfc5280#section-4.1.2.5.2
1108 http://www.obj-sys.com/asn1tutorial/node14.html */
1109
1110 $pattern = $tag == FILE_ASN1_TYPE_UTC_TIME ?
1111 '#(..)(..)(..)(..)(..)(..)(.*)#' :
1112 '#(....)(..)(..)(..)(..)(..).*([Z+-].*)$#';
1113
1114 preg_match($pattern, $content, $matches);
1115
1116 list(, $year, $month, $day, $hour, $minute, $second, $timezone) = $matches;
1117
1118 if ($tag == FILE_ASN1_TYPE_UTC_TIME) {
1119 $year = $year >= 50 ? "19$year" : "20$year";
1120 }
1121
1122 if ($timezone == 'Z') {
1123 $mktime = 'gmmktime';
1124 $timezone = 0;
1125 } elseif (preg_match('#([+-])(\d\d)(\d\d)#', $timezone, $matches)) {
1126 $mktime = 'gmmktime';
1127 $timezone = 60 * $matches[3] + 3600 * $matches[2];
1128 if ($matches[1] == '-') {
1129 $timezone = -$timezone;
1130 }
1131 } else {
1132 $mktime = 'mktime';
1133 $timezone = 0;
1134 }
1135
1136 return @$mktime($hour, $minute, $second, $month, $day, $year) + $timezone;
1137 }
1138
1139 /**
1140 * Set the time format
1141 *
1142 * Sets the time / date format for asn1map().
1143 *
1144 * @access public
1145 * @param String $format
1146 */
1147 function setTimeFormat($format)
1148 {
1149 $this->format = $format;
1150 }
1151
1152 /**
1153 * Load OIDs
1154 *
1155 * Load the relevant OIDs for a particular ASN.1 semantic mapping.
1156 *
1157 * @access public
1158 * @param Array $oids
1159 */
1160 function loadOIDs($oids)
1161 {
1162 $this->oids = $oids;
1163 }
1164
1165 /**
1166 * Load filters
1167 *
1168 * See File_X509, etc, for an example.
1169 *
1170 * @access public
1171 * @param Array $filters
1172 */
1173 function loadFilters($filters)
1174 {
1175 $this->filters = $filters;
1176 }
1177
1178 /**
1179 * String Shift
1180 *
1181 * Inspired by array_shift
1182 *
1183 * @param String $string
1184 * @param optional Integer $index
1185 * @return String
1186 * @access private
1187 */
1188 function _string_shift(&$string, $index = 1)
1189 {
1190 $substr = substr($string, 0, $index);
1191 $string = substr($string, $index);
1192 return $substr;
1193 }
1194
1195 /**
1196 * String type conversion
1197 *
1198 * This is a lazy conversion, dealing only with character size.
1199 * No real conversion table is used.
1200 *
1201 * @param String $in
1202 * @param optional Integer $from
1203 * @param optional Integer $to
1204 * @return String
1205 * @access public
1206 */
1207 function convert($in, $from = FILE_ASN1_TYPE_UTF8_STRING, $to = FILE_ASN1_TYPE_UTF8_STRING)
1208 {
1209 if (!isset($this->stringTypeSize[$from]) || !isset($this->stringTypeSize[$to])) {
1210 return false;
1211 }
1212 $insize = $this->stringTypeSize[$from];
1213 $outsize = $this->stringTypeSize[$to];
1214 $inlength = strlen($in);
1215 $out = '';
1216
1217 for ($i = 0; $i < $inlength;) {
1218 if ($inlength - $i < $insize) {
1219 return false;
1220 }
1221
1222 // Get an input character as a 32-bit value.
1223 $c = ord($in[$i++]);
1224 switch (true) {
1225 case $insize == 4:
1226 $c = ($c << 8) | ord($in[$i++]);
1227 $c = ($c << 8) | ord($in[$i++]);
1228 case $insize == 2:
1229 $c = ($c << 8) | ord($in[$i++]);
1230 case $insize == 1:
1231 break;
1232 case ($c & 0x80) == 0x00:
1233 break;
1234 case ($c & 0x40) == 0x00:
1235 return false;
1236 default:
1237 $bit = 6;
1238 do {
1239 if ($bit > 25 || $i >= $inlength || (ord($in[$i]) & 0xC0) != 0x80) {
1240 return false;
1241 }
1242 $c = ($c << 6) | (ord($in[$i++]) & 0x3F);
1243 $bit += 5;
1244 $mask = 1 << $bit;
1245 } while ($c & $bit);
1246 $c &= $mask - 1;
1247 break;
1248 }
1249
1250 // Convert and append the character to output string.
1251 $v = '';
1252 switch (true) {
1253 case $outsize == 4:
1254 $v .= chr($c & 0xFF);
1255 $c >>= 8;
1256 $v .= chr($c & 0xFF);
1257 $c >>= 8;
1258 case $outsize == 2:
1259 $v .= chr($c & 0xFF);
1260 $c >>= 8;
1261 case $outsize == 1:
1262 $v .= chr($c & 0xFF);
1263 $c >>= 8;
1264 if ($c) {
1265 return false;
1266 }
1267 break;
1268 case ($c & 0x80000000) != 0:
1269 return false;
1270 case $c >= 0x04000000:
1271 $v .= chr(0x80 | ($c & 0x3F));
1272 $c = ($c >> 6) | 0x04000000;
1273 case $c >= 0x00200000:
1274 $v .= chr(0x80 | ($c & 0x3F));
1275 $c = ($c >> 6) | 0x00200000;
1276 case $c >= 0x00010000:
1277 $v .= chr(0x80 | ($c & 0x3F));
1278 $c = ($c >> 6) | 0x00010000;
1279 case $c >= 0x00000800:
1280 $v .= chr(0x80 | ($c & 0x3F));
1281 $c = ($c >> 6) | 0x00000800;
1282 case $c >= 0x00000080:
1283 $v .= chr(0x80 | ($c & 0x3F));
1284 $c = ($c >> 6) | 0x000000C0;
1285 default:
1286 $v .= chr($c);
1287 break;
1288 }
1289 $out .= strrev($v);
1290 }
1291 return $out;
1292 }
1293 }
1294