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 // from openssl public keys
11 public $asnData = null;
13 private $parent = null;
15 public static $ASN_MARKERS = array(
16 'ASN_UNIVERSAL' => 0x00,
17 'ASN_APPLICATION' => 0x40,
18 'ASN_CONTEXT' => 0x80,
19 'ASN_PRIVATE' => 0xC0,
21 'ASN_PRIMITIVE' => 0x00,
22 'ASN_CONSTRUCTOR' => 0x20,
24 'ASN_LONG_LEN' => 0x80,
25 'ASN_EXTENSION_ID' => 0x1F,
29 public static $ASN_TYPES = array(
37 10 => 'ASN_ENUMERATED',
38 13 => 'ASN_RELATIVE_OID',
41 19 => 'ASN_PRINT_STR',
44 24 => 'ASN_GENERAL_TIME',
47 function __construct($v = false)
51 if (is_array($this->asnData)) {
52 foreach ($this->asnData as $key => $value) {
53 if (is_object($value)) {
54 $this->asnData[$key]->setParent($this);
58 if (is_object($this->asnData)) {
59 $this->asnData->setParent($this);
65 public function setParent($parent)
67 if (false !== $parent) {
68 $this->parent = $parent;
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.
77 public static function generateSubclasses()
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);
87 * Helper function for generateSubclasses()
89 public static function makeSubclass($name, $bit)
92 eval("class ".$name." extends ASN_BASE {}");
96 * This function reset's the internal cursor used for value iteration.
98 public function reset()
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.
108 public function __get($name)
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;
131 * Parse an ASN.1 binary string.
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.
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
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));
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
154 $length = ord($string[$p++]);
155 if (($length & ASN_LONG_LEN)==ASN_LONG_LEN){
157 for ($x=0; $x<($length & (ASN_LONG_LEN-1)); $x++){
158 $tempLength = ord($string[$p++]) + ($tempLength * 256);
160 $length = $tempLength;
162 $data = substr($string, $p, $length);
163 $parsed[] = self::parseASNData($type, $data, $level, $maxLevels);
171 * Parse an ASN.1 field value.
173 * This function takes a binary ASN.1 value and parses it according to it's specified type
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
181 public static function parseASNData($type, $data, $level, $maxLevels){
182 $type = $type%50; // strip out context
185 return new ASN_BASE($data);
187 return new ASN_BOOLEAN((bool)$data);
189 return new ASN_INTEGER(strtr(base64_encode($data),'+/','-_'));
191 return new ASN_BIT_STR(self::parseASNString($data, $level+1, $maxLevels));
193 return new ASN_OCTET_STR($data);
195 return new ASN_NULL(null);
197 return new ASN_REAL($data);
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);
204 return new ASN_SEQUENCE(self::parseASNString($data, $level+1, $maxLevels));
206 return new ASN_SET(self::parseASNString($data, $level+1, $maxLevels));
208 return new ASN_PRINT_STR($data);
210 return new ASN_IA5_STR($data);
212 return new ASN_UTC_TIME($data);
213 case ASN_GENERAL_TIME:
214 return new ASN_GENERAL_TIME($data);
216 return new ASN_OBJECT_ID(self::parseOID($data));
221 * Parse an ASN.1 OID value.
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)
228 * @param string $data The raw binary data string
229 * @return string The OID contained in $data
231 public static function parseOID($string){
232 $ret = floor(ord($string[0])/40).".";
233 $ret .= (ord($string[0]) % 40);
237 for ($i=1; $i<strlen($string); $i++){
238 $v = ord($string[$i]);
240 $build[] = ord($string[$i])-ASN_BIT;
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);
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;
252 $value = ((($build[$x] & (ASN_BIT-1)) >> $x) ^ ($build[$x+1] << (7 - $x) & 255)) * $mult;
257 $build = array(); // start over
266 public static function printASN($x, $indent=''){
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.'. ');
276 echo self::printASN($x->data, $indent.'. ');
278 } elseif (is_array($x)) {
280 echo self::printASN($d, $indent);
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";