ASN1: rewrite _decode_der

this rewrite makes phpseclib better able to handle indef lengths,
which had previously been untested.
This commit is contained in:
terrafrost 2014-08-25 10:12:56 -05:00
parent 7696cbf826
commit e258e001fa

View File

@ -272,7 +272,8 @@ class File_ASN1
} }
$this->encoded = $encoded; $this->encoded = $encoded;
return $this->_decode_ber($encoded); // encapsulate in an array for BC with the old decodeBER
return array($this->_decode_ber($encoded));
} }
/** /**
@ -287,11 +288,8 @@ class File_ASN1
* @return Array * @return Array
* @access private * @access private
*/ */
function _decode_ber(&$encoded, $start = 0) function _decode_ber($encoded, $start = 0)
{ {
$decoded = array();
while ( strlen($encoded) ) {
$current = array('start' => $start); $current = array('start' => $start);
$type = ord($this->_string_shift($encoded)); $type = ord($this->_string_shift($encoded));
@ -317,16 +315,13 @@ class File_ASN1
if ( $length == 0x80 ) { // indefinite length if ( $length == 0x80 ) { // indefinite length
// "[A sender shall] use the indefinite form (see 8.1.3.6) if the encoding is constructed and is not all // "[A sender shall] use the indefinite form (see 8.1.3.6) if the encoding is constructed and is not all
// immediately available." -- paragraph 8.1.3.2.c // immediately available." -- paragraph 8.1.3.2.c
//if ( !$constructed ) {
// return false;
//}
$length = strlen($encoded); $length = strlen($encoded);
} elseif ( $length & 0x80 ) { // definite length, long form } elseif ( $length & 0x80 ) { // definite length, long form
// technically, the long form of the length can be represented by up to 126 octets (bytes), but we'll only // technically, the long form of the length can be represented by up to 126 octets (bytes), but we'll only
// support it up to four. // support it up to four.
$length&= 0x7F; $length&= 0x7F;
$temp = $this->_string_shift($encoded, $length); $temp = $this->_string_shift($encoded, $length);
// tags of indefinite length don't really have a header length; this length includes the tag // tags of indefinte length don't really have a header length; this length includes the tag
$current+= array('headerlength' => $length + 2); $current+= array('headerlength' => $length + 2);
$start+= $length; $start+= $length;
extract(unpack('Nlength', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4))); extract(unpack('Nlength', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4)));
@ -334,12 +329,10 @@ class File_ASN1
$current+= array('headerlength' => 2); $current+= array('headerlength' => 2);
} }
// End-of-content, see paragraphs 8.1.1.3, 8.1.3.2, 8.1.3.6, 8.1.5, and (for an example) 8.6.4.2
if (!$type && !$length) {
return $decoded;
}
$content = $this->_string_shift($encoded, $length); $content = $this->_string_shift($encoded, $length);
// at this point $length can be overwritten. it's only accurate for definite length things as is
/* Class is UNIVERSAL, APPLICATION, PRIVATE, or CONTEXT-SPECIFIC. The UNIVERSAL class is restricted to the ASN.1 /* Class is UNIVERSAL, APPLICATION, PRIVATE, or CONTEXT-SPECIFIC. The UNIVERSAL class is restricted to the ASN.1
built-in types. It defines an application-independent data type that must be distinguishable from all other built-in types. It defines an application-independent data type that must be distinguishable from all other
data types. The other three classes are user defined. The APPLICATION class distinguishes data types that data types. The other three classes are user defined. The APPLICATION class distinguishes data types that
@ -354,14 +347,24 @@ class File_ASN1
case FILE_ASN1_CLASS_APPLICATION: case FILE_ASN1_CLASS_APPLICATION:
case FILE_ASN1_CLASS_PRIVATE: case FILE_ASN1_CLASS_PRIVATE:
case FILE_ASN1_CLASS_CONTEXT_SPECIFIC: case FILE_ASN1_CLASS_CONTEXT_SPECIFIC:
$decoded[] = array( $newcontent = $this->_decode_ber($content, $start);
$length = $newcontent['length'];
if (substr($content, $length, 2) == "\0\0") {
$length+= 2;
}
$start+= $length;
return array(
'type' => $class, 'type' => $class,
'constant' => $tag, 'constant' => $tag,
'content' => $constructed ? $this->_decode_ber($content, $start) : $content, // the array encapsulation is for BC with the old format
'length' => $length + $start - $current['start'] 'content' => array($newcontent),
// the only time when $content['headerlength'] isn't defined is when the length is indefinite.
// the absence of $content['headerlength'] is how we know if something is indefinite or not.
// technically, it could be defined to be 2 and then another indicator could be used but whatever.
'length' => $start - $current['start']
) + $current; ) + $current;
$start+= $length;
continue 2;
} }
$current+= array('type' => $tag); $current+= array('type' => $tag);
@ -409,16 +412,21 @@ class File_ASN1
if (!$constructed) { if (!$constructed) {
$current['content'] = $content; $current['content'] = $content;
} else { } else {
$temp = $this->_decode_ber($content, $start); $current['content'] = '';
$length-= strlen($content); $length = 0;
for ($i = 0, $size = count($temp); $i < $size; $i++) { while (substr($content, 0, 2) != "\0\0") {
$temp = $this->_decode_ber($content, $length + $start);
$this->_string_shift($content, $temp['length']);
// all subtags should be octet strings // all subtags should be octet strings
//if ($temp[$i]['type'] != FILE_ASN1_TYPE_OCTET_STRING) { //if ($temp['type'] != FILE_ASN1_TYPE_OCTET_STRING) {
// return false; // return false;
//} //}
$current['content'].= $temp[$i]['content']; $current['content'].= $temp['content'];
$length+= $temp['length'];
}
if (substr($content, 0, 2) == "\0\0") {
$length+= 2; // +2 for the EOC
} }
// $length =
} }
break; break;
case FILE_ASN1_TYPE_NULL: case FILE_ASN1_TYPE_NULL:
@ -429,7 +437,20 @@ class File_ASN1
break; break;
case FILE_ASN1_TYPE_SEQUENCE: case FILE_ASN1_TYPE_SEQUENCE:
case FILE_ASN1_TYPE_SET: case FILE_ASN1_TYPE_SET:
$current['content'] = $this->_decode_ber($content, $start); $offset = 0;
$current['content'] = array();
while (strlen($content)) {
// if indefinite length construction was used and we have an end-of-content string next
// see paragraphs 8.1.1.3, 8.1.3.2, 8.1.3.6, 8.1.5, and (for an example) 8.6.4.2
if (!isset($current['headerlength']) && substr($content, 0, 2) == "\0\0") {
$length = $offset + 2; // +2 for the EOC
break 2;
}
$temp = $this->_decode_ber($content, $start + $offset);
$this->_string_shift($content, $temp['length']);
$current['content'][] = $temp;
$offset+= $temp['length'];
}
break; break;
case FILE_ASN1_TYPE_OBJECT_IDENTIFIER: case FILE_ASN1_TYPE_OBJECT_IDENTIFIER:
$temp = ord($this->_string_shift($content)); $temp = ord($this->_string_shift($content));
@ -484,18 +505,16 @@ class File_ASN1
case FILE_ASN1_TYPE_GENERALIZED_TIME: case FILE_ASN1_TYPE_GENERALIZED_TIME:
$current['content'] = $this->_decodeTime($content, $tag); $current['content'] = $this->_decodeTime($content, $tag);
default: default:
} }
$start+= $length; $start+= $length;
$decoded[] = $current + array('length' => $start - $current['start']);
}
return $decoded; // ie. length is the length of the full TLV encoding - it's not just the length of the value
return $current + array('length' => $start - $current['start']);
} }
/** /**
* ASN.1 Decode * ASN.1 Map
* *
* Provides an ASN.1 semantic mapping ($mapping) from a parsed BER-encoding to a human readable format. * Provides an ASN.1 semantic mapping ($mapping) from a parsed BER-encoding to a human readable format.
* *
@ -805,16 +824,6 @@ class File_ASN1
return $this->_encode_der($source, $mapping, null, $special); return $this->_encode_der($source, $mapping, null, $special);
} }
/**
* ASN.1 Encode (Helper function)
*
* @param String $source
* @param Array $mapping
* @param Integer $idx
* @param Array $special
* @return String
* @access private
*/
/** /**
* ASN.1 Encode (Helper function) * ASN.1 Encode (Helper function)
* *