PageRenderTime 61ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/drupal/profiles/medienlabor/modules/contrib/aes/phpseclib/File/ASN1.php

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