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