ASN1.php 60 KB

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