3 // ASN.1 parsing library
4 // Attribution: http://www.krisbailey.com
6 // modified: Mike Macgrivin mike@macgirvin.com 6-oct-2010 to support Salmon auto-discovery
7 // modified: Tobias Diekershoff 28-jul-2016 adding an intval in line 162 to make PHP7 happy
8 // from openssl public keys
12 public $asnData = null;
14 private $parent = null;
16 public static $ASN_MARKERS = array(
17 'ASN_UNIVERSAL' => 0x00,
18 'ASN_APPLICATION' => 0x40,
19 'ASN_CONTEXT' => 0x80,
20 'ASN_PRIVATE' => 0xC0,
22 'ASN_PRIMITIVE' => 0x00,
23 'ASN_CONSTRUCTOR' => 0x20,
25 'ASN_LONG_LEN' => 0x80,
26 'ASN_EXTENSION_ID' => 0x1F,
30 public static $ASN_TYPES = array(
38 10 => 'ASN_ENUMERATED',
39 13 => 'ASN_RELATIVE_OID',
42 19 => 'ASN_PRINT_STR',
45 24 => 'ASN_GENERAL_TIME',
48 function __construct($v = false)
52 if (is_array($this->asnData)) {
53 foreach ($this->asnData as $key => $value) {
54 if (is_object($value)) {
55 $this->asnData[$key]->setParent($this);
59 if (is_object($this->asnData)) {
60 $this->asnData->setParent($this);
66 public function setParent($parent)
68 if (false !== $parent) {
69 $this->parent = $parent;
74 * This function will take the markers and types arrays and
75 * dynamically generate classes that extend this class for each one,
76 * and also define constants for them.
78 public static function generateSubclasses()
80 define('ASN_BASE', 0);
81 foreach (self::$ASN_MARKERS as $name => $bit)
82 self::makeSubclass($name, $bit);
83 foreach (self::$ASN_TYPES as $bit => $name)
84 self::makeSubclass($name, $bit);
88 * Helper function for generateSubclasses()
90 public static function makeSubclass($name, $bit)
93 eval("class ".$name." extends ASN_BASE {}");
97 * This function reset's the internal cursor used for value iteration.
99 public function reset()
105 * This function catches calls to get the value for the type, typeName, value, values, and data
106 * from the object. For type calls we just return the class name or the value of the constant that
107 * is named the same as the class.
109 public function __get($name)
111 if ('type' == $name) {
112 // int flag of the data type
113 return constant(get_class($this));
114 } elseif ('typeName' == $name) {
115 // name of the data type
116 return get_class($this);
117 } elseif ('value' == $name) {
118 // will always return one value and can be iterated over with:
119 // while ($v = $obj->value) { ...
120 // because $this->asnData["invalid key"] will return false
121 return is_array($this->asnData) ? $this->asnData[$this->cursor++] : $this->asnData;
122 } elseif ('values' == $name) {
123 // will always return an array
124 return is_array($this->asnData) ? $this->asnData : array($this->asnData);
125 } elseif ('data' == $name) {
126 // will always return the raw data
127 return $this->asnData;
132 * Parse an ASN.1 binary string.
134 * This function takes a binary ASN.1 string and parses it into it's respective
135 * pieces and returns it. It can optionally stop at any depth.
137 * @param string $string The binary ASN.1 String
138 * @param int $level The current parsing depth level
139 * @param int $maxLevel The max parsing depth level
140 * @return ASN_BASE The array representation of the ASN.1 data contained in $string
142 public static function parseASNString($string=false, $level=1, $maxLevels=false){
143 if (!class_exists('ASN_UNIVERSAL'))
144 self::generateSubclasses();
145 if ($level>$maxLevels && $maxLevels)
146 return array(new ASN_BASE($string));
148 $endLength = strlen($string);
149 $bigLength = $length = $type = $dtype = $p = 0;
150 while ($p<$endLength){
151 $type = ord($string[$p++]);
152 $dtype = ($type & 192) >> 6;
153 if ($type==0){ // if we are type 0, just continue
155 $length = ord($string[$p++]);
156 if (($length & ASN_LONG_LEN)==ASN_LONG_LEN){
158 for ($x=0; $x<($length & (ASN_LONG_LEN-1)); $x++){
159 $tempLength = ord($string[$p++]) + ($tempLength * 256);
161 $length = $tempLength;
163 $data = substr($string, $p, intval($length));
164 $parsed[] = self::parseASNData($type, $data, $level, $maxLevels);
172 * Parse an ASN.1 field value.
174 * This function takes a binary ASN.1 value and parses it according to it's specified type
176 * @param int $type The type of data being provided
177 * @param string $data The raw binary data string
178 * @param int $level The current parsing depth
179 * @param int $maxLevels The max parsing depth
180 * @return mixed The data that was parsed from the raw binary data string
182 public static function parseASNData($type, $data, $level, $maxLevels){
183 $type = $type%50; // strip out context
186 return new ASN_BASE($data);
188 return new ASN_BOOLEAN((bool)$data);
190 return new ASN_INTEGER(strtr(base64_encode($data),'+/','-_'));
192 return new ASN_BIT_STR(self::parseASNString($data, $level+1, $maxLevels));
194 return new ASN_OCTET_STR($data);
196 return new ASN_NULL(null);
198 return new ASN_REAL($data);
200 return new ASN_ENUMERATED(self::parseASNString($data, $level+1, $maxLevels));
201 case ASN_RELATIVE_OID: // I don't really know how this works and don't have an example :-)
202 // so, lets just return it ...
203 return new ASN_RELATIVE_OID($data);
205 return new ASN_SEQUENCE(self::parseASNString($data, $level+1, $maxLevels));
207 return new ASN_SET(self::parseASNString($data, $level+1, $maxLevels));
209 return new ASN_PRINT_STR($data);
211 return new ASN_IA5_STR($data);
213 return new ASN_UTC_TIME($data);
214 case ASN_GENERAL_TIME:
215 return new ASN_GENERAL_TIME($data);
217 return new ASN_OBJECT_ID(self::parseOID($data));
222 * Parse an ASN.1 OID value.
224 * This takes the raw binary string that represents an OID value and parses it into its
225 * dot notation form. example - 1.2.840.113549.1.1.5
226 * look up OID's here: http://www.oid-info.com/
227 * (the multi-byte OID section can be done in a more efficient way, I will fix it later)
229 * @param string $data The raw binary data string
230 * @return string The OID contained in $data
232 public static function parseOID($string){
233 $ret = floor(ord($string[0])/40).".";
234 $ret .= (ord($string[0]) % 40);
238 for ($i=1; $i<strlen($string); $i++){
239 $v = ord($string[$i]);
241 $build[] = ord($string[$i])-ASN_BIT;
243 // do the build here for multibyte values
244 $build[] = ord($string[$i])-ASN_BIT;
245 // you know, it seems there should be a better way to do this...
246 $build = array_reverse($build);
248 for ($x=0; $x<count($build); $x++){
249 $mult = $x==0?1:pow(256, $x);
250 if ($x+1==count($build)){
251 $value = ((($build[$x] & (ASN_BIT-1)) >> $x)) * $mult;
253 $value = ((($build[$x] & (ASN_BIT-1)) >> $x) ^ ($build[$x+1] << (7 - $x) & 255)) * $mult;
258 $build = array(); // start over
267 public static function printASN($x, $indent=''){
269 echo $indent.$x->typeName."\n";
270 if (ASN_NULL == $x->type) return;
271 if (is_array($x->data)) {
272 while ($d = $x->value) {
273 echo self::printASN($d, $indent.'. ');
277 echo self::printASN($x->data, $indent.'. ');
279 } elseif (is_array($x)) {
281 echo self::printASN($d, $indent);
284 if (preg_match('/[^[:print:]]/', $x)) // if we have non-printable characters that would
285 $x = base64_encode($x); // mess up the console, then print the base64 of them...
286 echo $indent.$x."\n";