PageRenderTime 55ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/Sinner/Phpseclib/File/ASN1.php

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