diff options
-rw-r--r-- | library/asn1.php | 303 |
1 files changed, 303 insertions, 0 deletions
diff --git a/library/asn1.php b/library/asn1.php new file mode 100644 index 000000000..5b1ad3b8f --- /dev/null +++ b/library/asn1.php @@ -0,0 +1,303 @@ +<?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, $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(accum($data)); +// return new ASN_INTEGER(ord($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"; + } + } + + +} + + +function accum($s) { + $len = strlen($s); + $result = ''; + for ($i=0; $i < $len; $i++) { + $cur = substr($s,$i,1); + $result .= bin2hex($cur); + } + return $result; +} + |