Friendica Communications Platform (please note that this is a clone of the repository at github, issues are handled there) https://friendi.ca
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

291 lines
8.2 KiB

  1. <?php
  2. // ASN.1 parsing library
  3. // Attribution: http://www.krisbailey.com
  4. // license: unknown
  5. // modified: Mike Macgrivin mike@macgirvin.com 6-oct-2010 to support Salmon auto-discovery
  6. // from openssl public keys
  7. class ASN_BASE {
  8. public $asnData = null;
  9. private $cursor = 0;
  10. private $parent = null;
  11. public static $ASN_MARKERS = array(
  12. 'ASN_UNIVERSAL' => 0x00,
  13. 'ASN_APPLICATION' => 0x40,
  14. 'ASN_CONTEXT' => 0x80,
  15. 'ASN_PRIVATE' => 0xC0,
  16. 'ASN_PRIMITIVE' => 0x00,
  17. 'ASN_CONSTRUCTOR' => 0x20,
  18. 'ASN_LONG_LEN' => 0x80,
  19. 'ASN_EXTENSION_ID' => 0x1F,
  20. 'ASN_BIT' => 0x80,
  21. );
  22. public static $ASN_TYPES = array(
  23. 1 => 'ASN_BOOLEAN',
  24. 2 => 'ASN_INTEGER',
  25. 3 => 'ASN_BIT_STR',
  26. 4 => 'ASN_OCTET_STR',
  27. 5 => 'ASN_NULL',
  28. 6 => 'ASN_OBJECT_ID',
  29. 9 => 'ASN_REAL',
  30. 10 => 'ASN_ENUMERATED',
  31. 13 => 'ASN_RELATIVE_OID',
  32. 48 => 'ASN_SEQUENCE',
  33. 49 => 'ASN_SET',
  34. 19 => 'ASN_PRINT_STR',
  35. 22 => 'ASN_IA5_STR',
  36. 23 => 'ASN_UTC_TIME',
  37. 24 => 'ASN_GENERAL_TIME',
  38. );
  39. function __construct($v = false)
  40. {
  41. if (false !== $v) {
  42. $this->asnData = $v;
  43. if (is_array($this->asnData)) {
  44. foreach ($this->asnData as $key => $value) {
  45. if (is_object($value)) {
  46. $this->asnData[$key]->setParent($this);
  47. }
  48. }
  49. } else {
  50. if (is_object($this->asnData)) {
  51. $this->asnData->setParent($this);
  52. }
  53. }
  54. }
  55. }
  56. public function setParent($parent)
  57. {
  58. if (false !== $parent) {
  59. $this->parent = $parent;
  60. }
  61. }
  62. /**
  63. * This function will take the markers and types arrays and
  64. * dynamically generate classes that extend this class for each one,
  65. * and also define constants for them.
  66. */
  67. public static function generateSubclasses()
  68. {
  69. define('ASN_BASE', 0);
  70. foreach (self::$ASN_MARKERS as $name => $bit)
  71. self::makeSubclass($name, $bit);
  72. foreach (self::$ASN_TYPES as $bit => $name)
  73. self::makeSubclass($name, $bit);
  74. }
  75. /**
  76. * Helper function for generateSubclasses()
  77. */
  78. public static function makeSubclass($name, $bit)
  79. {
  80. define($name, $bit);
  81. eval("class ".$name." extends ASN_BASE {}");
  82. }
  83. /**
  84. * This function reset's the internal cursor used for value iteration.
  85. */
  86. public function reset()
  87. {
  88. $this->cursor = 0;
  89. }
  90. /**
  91. * This function catches calls to get the value for the type, typeName, value, values, and data
  92. * from the object. For type calls we just return the class name or the value of the constant that
  93. * is named the same as the class.
  94. */
  95. public function __get($name)
  96. {
  97. if ('type' == $name) {
  98. // int flag of the data type
  99. return constant(get_class($this));
  100. } elseif ('typeName' == $name) {
  101. // name of the data type
  102. return get_class($this);
  103. } elseif ('value' == $name) {
  104. // will always return one value and can be iterated over with:
  105. // while ($v = $obj->value) { ...
  106. // because $this->asnData["invalid key"] will return false
  107. return is_array($this->asnData) ? $this->asnData[$this->cursor++] : $this->asnData;
  108. } elseif ('values' == $name) {
  109. // will always return an array
  110. return is_array($this->asnData) ? $this->asnData : array($this->asnData);
  111. } elseif ('data' == $name) {
  112. // will always return the raw data
  113. return $this->asnData;
  114. }
  115. }
  116. /**
  117. * Parse an ASN.1 binary string.
  118. *
  119. * This function takes a binary ASN.1 string and parses it into it's respective
  120. * pieces and returns it. It can optionally stop at any depth.
  121. *
  122. * @param string $string The binary ASN.1 String
  123. * @param int $level The current parsing depth level
  124. * @param int $maxLevel The max parsing depth level
  125. * @return ASN_BASE The array representation of the ASN.1 data contained in $string
  126. */
  127. public static function parseASNString($string=false, $level=1, $maxLevels=false){
  128. if (!class_exists('ASN_UNIVERSAL'))
  129. self::generateSubclasses();
  130. if ($level>$maxLevels && $maxLevels)
  131. return array(new ASN_BASE($string));
  132. $parsed = array();
  133. $endLength = strlen($string);
  134. $bigLength = $length = $type = $dtype = $p = 0;
  135. while ($p<$endLength){
  136. $type = ord($string[$p++]);
  137. $dtype = ($type & 192) >> 6;
  138. if ($type==0){ // if we are type 0, just continue
  139. } else {
  140. $length = ord($string[$p++]);
  141. if (($length & ASN_LONG_LEN)==ASN_LONG_LEN){
  142. $tempLength = 0;
  143. for ($x=0; $x<($length & (ASN_LONG_LEN-1)); $x++){
  144. $tempLength = ord($string[$p++]) + ($tempLength * 256);
  145. }
  146. $length = $tempLength;
  147. }
  148. $data = substr($string, $p, $length);
  149. $parsed[] = self::parseASNData($type, $data, $level, $maxLevels);
  150. $p = $p + $length;
  151. }
  152. }
  153. return $parsed;
  154. }
  155. /**
  156. * Parse an ASN.1 field value.
  157. *
  158. * This function takes a binary ASN.1 value and parses it according to it's specified type
  159. *
  160. * @param int $type The type of data being provided
  161. * @param string $data The raw binary data string
  162. * @param int $level The current parsing depth
  163. * @param int $maxLevels The max parsing depth
  164. * @return mixed The data that was parsed from the raw binary data string
  165. */
  166. public static function parseASNData($type, $data, $level, $maxLevels){
  167. $type = $type%50; // strip out context
  168. switch ($type){
  169. default:
  170. return new ASN_BASE($data);
  171. case ASN_BOOLEAN:
  172. return new ASN_BOOLEAN((bool)$data);
  173. case ASN_INTEGER:
  174. return new ASN_INTEGER(strtr(base64_encode($data),'+/','-_'));
  175. case ASN_BIT_STR:
  176. return new ASN_BIT_STR(self::parseASNString($data, $level+1, $maxLevels));
  177. case ASN_OCTET_STR:
  178. return new ASN_OCTET_STR($data);
  179. case ASN_NULL:
  180. return new ASN_NULL(null);
  181. case ASN_REAL:
  182. return new ASN_REAL($data);
  183. case ASN_ENUMERATED:
  184. return new ASN_ENUMERATED(self::parseASNString($data, $level+1, $maxLevels));
  185. case ASN_RELATIVE_OID: // I don't really know how this works and don't have an example :-)
  186. // so, lets just return it ...
  187. return new ASN_RELATIVE_OID($data);
  188. case ASN_SEQUENCE:
  189. return new ASN_SEQUENCE(self::parseASNString($data, $level+1, $maxLevels));
  190. case ASN_SET:
  191. return new ASN_SET(self::parseASNString($data, $level+1, $maxLevels));
  192. case ASN_PRINT_STR:
  193. return new ASN_PRINT_STR($data);
  194. case ASN_IA5_STR:
  195. return new ASN_IA5_STR($data);
  196. case ASN_UTC_TIME:
  197. return new ASN_UTC_TIME($data);
  198. case ASN_GENERAL_TIME:
  199. return new ASN_GENERAL_TIME($data);
  200. case ASN_OBJECT_ID:
  201. return new ASN_OBJECT_ID(self::parseOID($data));
  202. }
  203. }
  204. /**
  205. * Parse an ASN.1 OID value.
  206. *
  207. * This takes the raw binary string that represents an OID value and parses it into its
  208. * dot notation form. example - 1.2.840.113549.1.1.5
  209. * look up OID's here: http://www.oid-info.com/
  210. * (the multi-byte OID section can be done in a more efficient way, I will fix it later)
  211. *
  212. * @param string $data The raw binary data string
  213. * @return string The OID contained in $data
  214. */
  215. public static function parseOID($string){
  216. $ret = floor(ord($string[0])/40).".";
  217. $ret .= (ord($string[0]) % 40);
  218. $build = array();
  219. $cs = 0;
  220. for ($i=1; $i<strlen($string); $i++){
  221. $v = ord($string[$i]);
  222. if ($v>127){
  223. $build[] = ord($string[$i])-ASN_BIT;
  224. } elseif ($build){
  225. // do the build here for multibyte values
  226. $build[] = ord($string[$i])-ASN_BIT;
  227. // you know, it seems there should be a better way to do this...
  228. $build = array_reverse($build);
  229. $num = 0;
  230. for ($x=0; $x<count($build); $x++){
  231. $mult = $x==0?1:pow(256, $x);
  232. if ($x+1==count($build)){
  233. $value = ((($build[$x] & (ASN_BIT-1)) >> $x)) * $mult;
  234. } else {
  235. $value = ((($build[$x] & (ASN_BIT-1)) >> $x) ^ ($build[$x+1] << (7 - $x) & 255)) * $mult;
  236. }
  237. $num += $value;
  238. }
  239. $ret .= ".".$num;
  240. $build = array(); // start over
  241. } else {
  242. $ret .= ".".$v;
  243. $build = array();
  244. }
  245. }
  246. return $ret;
  247. }
  248. public static function printASN($x, $indent=''){
  249. if (is_object($x)) {
  250. echo $indent.$x->typeName."\n";
  251. if (ASN_NULL == $x->type) return;
  252. if (is_array($x->data)) {
  253. while ($d = $x->value) {
  254. echo self::printASN($d, $indent.'. ');
  255. }
  256. $x->reset();
  257. } else {
  258. echo self::printASN($x->data, $indent.'. ');
  259. }
  260. } elseif (is_array($x)) {
  261. foreach ($x as $d) {
  262. echo self::printASN($d, $indent);
  263. }
  264. } else {
  265. if (preg_match('/[^[:print:]]/', $x)) // if we have non-printable characters that would
  266. $x = base64_encode($x); // mess up the console, then print the base64 of them...
  267. echo $indent.$x."\n";
  268. }
  269. }
  270. }