aboutsummaryrefslogtreecommitdiffstats
path: root/library/asn1.php
blob: 6ab8c6210669b0728e6f3f34d173b76851038790 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
<?php

// ASN.1 parsing library
// Attribution: http://www.krisbailey.com
// license: unknown
// modified: Mike Macgrivin mike@macgirvin.com 6-oct-2010 to support Salmon auto-discovery
// from openssl public keys


class ASN_BASE {
	public $asnData = null;
	private $cursor = 0;
	private $parent = null;
	
	public static $ASN_MARKERS = array(
		'ASN_UNIVERSAL'		=> 0x00,
		'ASN_APPLICATION'	=> 0x40,
		'ASN_CONTEXT'		=> 0x80,
		'ASN_PRIVATE'		=> 0xC0,

		'ASN_PRIMITIVE'		=> 0x00,
		'ASN_CONSTRUCTOR'	=> 0x20,

		'ASN_LONG_LEN'		=> 0x80,
		'ASN_EXTENSION_ID'	=> 0x1F,
		'ASN_BIT'		=> 0x80,
	);
	
	public static $ASN_TYPES = array(
		1	=> 'ASN_BOOLEAN',
		2	=> 'ASN_INTEGER',
		3	=> 'ASN_BIT_STR',
		4	=> 'ASN_OCTET_STR',
		5	=> 'ASN_NULL',
		6	=> 'ASN_OBJECT_ID',
		9	=> 'ASN_REAL',
		10	=> 'ASN_ENUMERATED',
		13	=> 'ASN_RELATIVE_OID',
		48	=> 'ASN_SEQUENCE',
		49	=> 'ASN_SET',
		19	=> 'ASN_PRINT_STR',
		22	=> 'ASN_IA5_STR',
		23	=> 'ASN_UTC_TIME',
		24	=> 'ASN_GENERAL_TIME',
	);

	function __construct($v = false)
	{
		if (false !== $v) {
			$this->asnData = $v;
			if (is_array($this->asnData)) {
				foreach ($this->asnData as $key => $value) {
					if (is_object($value)) {
						$this->asnData[$key]->setParent($this);
					}
				}
			} else {
				if (is_object($this->asnData)) {
					$this->asnData->setParent($this);
				}
			}
		}
	}
	
	public function setParent($parent)
	{
		if (false !== $parent) {
			$this->parent = $parent;
		}
	}
	
	/**
	 * This function will take the markers and types arrays and
	 * dynamically generate classes that extend this class for each one,
	 * and also define constants for them.
	 */
	public static function generateSubclasses()
	{
		define('ASN_BASE', 0);
		foreach (self::$ASN_MARKERS as $name => $bit)
			self::makeSubclass($name, $bit);
		foreach (self::$ASN_TYPES as $bit => $name)
			self::makeSubclass($name, $bit);
	}
	
	/**
	 * Helper function for generateSubclasses()
	 */
	public static function makeSubclass($name, $bit)
	{
		define($name, $bit);
		eval("class ".$name." extends ASN_BASE {}");
	}
	
	/**
	 * This function reset's the internal cursor used for value iteration.
	 */
	public function reset()
	{
		$this->cursor = 0;
	}
	
	/**
	 * This function catches calls to get the value for the type, typeName, value, values, and data
	 * from the object.  For type calls we just return the class name or the value of the constant that
	 * is named the same as the class.
	 */
	public function __get($name)
	{
		if ('type' == $name) {
			// int flag of the data type
			return constant(get_class($this));
		} elseif ('typeName' == $name) {
			// name of the data type
			return get_class($this);
		} elseif ('value' == $name) {
			// will always return one value and can be iterated over with:
			// while ($v = $obj->value) { ...
			// because $this->asnData["invalid key"] will return false
			return is_array($this->asnData) ? $this->asnData[$this->cursor++] : $this->asnData;
		} elseif ('values' == $name) {
			// will always return an array
			return is_array($this->asnData) ? $this->asnData : array($this->asnData);
		} elseif ('data' == $name) {
			// will always return the raw data
			return $this->asnData;
		}
	}

	/**
	 * Parse an ASN.1 binary string.
	 * 
	 * This function takes a binary ASN.1 string and parses it into it's respective
	 * pieces and returns it.  It can optionally stop at any depth.
	 *
	 * @param	string	$string		The binary ASN.1 String
	 * @param	int	$level		The current parsing depth level
	 * @param	int	$maxLevel	The max parsing depth level
	 * @return	ASN_BASE	The array representation of the ASN.1 data contained in $string
	 */
	public static function parseASNString($string=false, $level=1, $maxLevels=false){
		if (!class_exists('ASN_UNIVERSAL'))
			self::generateSubclasses();
		if ($level>$maxLevels && $maxLevels)
			return array(new ASN_BASE($string));
		$parsed = array();
		$endLength = strlen($string);
		$bigLength = $length = $type = $dtype = $p = 0;
		while ($p<$endLength){
			$type = ord($string[$p++]);
			$dtype = ($type & 192) >> 6;
			if ($type==0){ // if we are type 0, just continue
			} else {
				$length = ord($string[$p++]);
				if (($length & ASN_LONG_LEN)==ASN_LONG_LEN){
					$tempLength = 0;
					for ($x=0; $x<($length & (ASN_LONG_LEN-1)); $x++){
						$tempLength = ord($string[$p++]) + ($tempLength * 256);
					}
					$length = $tempLength;
				}
				$data = substr($string, $p, intval($length));
				$parsed[] = self::parseASNData($type, $data, $level, $maxLevels);
				$p = $p + $length;
			}
		}
		return $parsed;
	}

	/**
	 * Parse an ASN.1 field value.
	 * 
	 * This function takes a binary ASN.1 value and parses it according to it's specified type
	 *
	 * @param	int	$type		The type of data being provided
	 * @param	string	$data		The raw binary data string
	 * @param	int	$level		The current parsing depth
	 * @param	int	$maxLevels	The max parsing depth
	 * @return	mixed	The data that was parsed from the raw binary data string
	 */
	public static function parseASNData($type, $data, $level, $maxLevels){
		$type = $type%50; // strip out context
		switch ($type){
			default:
				return new ASN_BASE($data);
			case ASN_BOOLEAN:
				return new ASN_BOOLEAN((bool)$data);
			case ASN_INTEGER:
				return new ASN_INTEGER(strtr(base64_encode($data),'+/','-_'));
			case ASN_BIT_STR:
				return new ASN_BIT_STR(self::parseASNString($data, $level+1, $maxLevels));
			case ASN_OCTET_STR:
				return new ASN_OCTET_STR($data);
			case ASN_NULL:
				return new ASN_NULL(null);
			case ASN_REAL:
				return new ASN_REAL($data);
			case ASN_ENUMERATED:
				return new ASN_ENUMERATED(self::parseASNString($data, $level+1, $maxLevels));
			case ASN_RELATIVE_OID: // I don't really know how this works and don't have an example :-)
						// so, lets just return it ...
				return new ASN_RELATIVE_OID($data);
			case ASN_SEQUENCE:
				return new ASN_SEQUENCE(self::parseASNString($data, $level+1, $maxLevels));
			case ASN_SET:
				return new ASN_SET(self::parseASNString($data, $level+1, $maxLevels));
			case ASN_PRINT_STR:
				return new ASN_PRINT_STR($data);
			case ASN_IA5_STR:
				return new ASN_IA5_STR($data);
			case ASN_UTC_TIME:
				return new ASN_UTC_TIME($data);
			case ASN_GENERAL_TIME:
				return new ASN_GENERAL_TIME($data);
			case ASN_OBJECT_ID:
				return new ASN_OBJECT_ID(self::parseOID($data));
		}
	}

	/**
	 * Parse an ASN.1 OID value.
	 * 
	 * This takes the raw binary string that represents an OID value and parses it into its
	 * dot notation form.  example - 1.2.840.113549.1.1.5
	 * look up OID's here: http://www.oid-info.com/
	 * (the multi-byte OID section can be done in a more efficient way, I will fix it later)
	 *
	 * @param	string	$data		The raw binary data string
	 * @return	string	The OID contained in $data
	 */
	public static function parseOID($string){
		$ret = floor(ord($string[0])/40).".";
		$ret .= (ord($string[0]) % 40);
		$build = array();
		$cs = 0;	
		
		for ($i=1; $i<strlen($string); $i++){
			$v = ord($string[$i]);
			if ($v>127){
				$build[] = ord($string[$i])-ASN_BIT;
			} elseif ($build){
				// do the build here for multibyte values
				$build[] = ord($string[$i])-ASN_BIT;
				// you know, it seems there should be a better way to do this...
				$build = array_reverse($build);
				$num = 0;
				for ($x=0; $x<count($build); $x++){
					$mult = $x==0?1:pow(256, $x);
					if ($x+1==count($build)){
						$value = ((($build[$x] & (ASN_BIT-1)) >> $x)) * $mult;
					} else {
						$value = ((($build[$x] & (ASN_BIT-1)) >> $x) ^ ($build[$x+1] << (7 - $x) & 255)) * $mult;
					}
					$num += $value;
				}
				$ret .= ".".$num;
				$build = array(); // start over
			} else {
				$ret .= ".".$v;
				$build = array();
			}
		}
		return $ret;
	}
	
	public static function printASN($x, $indent=''){
		if (is_object($x)) {
			echo $indent.$x->typeName."\n";
			if (ASN_NULL == $x->type) return;
			if (is_array($x->data)) {
				while ($d = $x->value) {
					echo self::printASN($d, $indent.'.  ');
				}
				$x->reset();
			} else {
				echo self::printASN($x->data, $indent.'.  ');
			}
		} elseif (is_array($x)) {
			foreach ($x as $d) {
				echo self::printASN($d, $indent);
			}
		} else {
			if (preg_match('/[^[:print:]]/', $x))	// if we have non-printable characters that would
				$x = base64_encode($x);		// mess up the console, then print the base64 of them...
			echo $indent.$x."\n";
		}
	}

	
}