Merge branch '2.0'

This commit is contained in:
terrafrost 2016-06-26 20:43:33 -05:00
commit a490e06b48
2 changed files with 120 additions and 38 deletions

View File

@ -225,14 +225,15 @@ class ASN1
*
* @param string $encoded
* @param int $start
* @param int $encoded_pos
* @return array
* @access private
*/
function _decode_ber($encoded, $start = 0)
function _decode_ber($encoded, $start = 0, $encoded_pos = 0)
{
$current = array('start' => $start);
$type = ord($this->_string_shift($encoded));
$type = ord($encoded[$encoded_pos++]);
$start++;
$constructed = ($type >> 5) & 1;
@ -244,23 +245,24 @@ class ASN1
do {
$loop = ord($encoded[0]) >> 7;
$tag <<= 7;
$tag |= ord($this->_string_shift($encoded)) & 0x7F;
$tag |= ord($encoded[$encoded_pos++]) & 0x7F;
$start++;
} while ($loop);
}
// Length, as discussed in paragraph 8.1.3 of X.690-0207.pdf#page=13
$length = ord($this->_string_shift($encoded));
$length = ord($encoded[$encoded_pos++]);
$start++;
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
// immediately available." -- paragraph 8.1.3.2.c
$length = strlen($encoded);
$length = strlen($encoded) - $encoded_pos;
} 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
// support it up to four.
$length&= 0x7F;
$temp = $this->_string_shift($encoded, $length);
$temp = substr($encoded, $encoded_pos, $length);
$encoded_pos += $length;
// tags of indefinte length don't really have a header length; this length includes the tag
$current+= array('headerlength' => $length + 2);
$start+= $length;
@ -269,11 +271,12 @@ class ASN1
$current+= array('headerlength' => 2);
}
if ($length > strlen($encoded)) {
if ($length > (strlen($encoded) - $encoded_pos)) {
return false;
}
$content = $this->_string_shift($encoded, $length);
$content = substr($encoded, $encoded_pos, $length);
$content_pos = 0;
// at this point $length can be overwritten. it's only accurate for definite length things as is
@ -306,7 +309,7 @@ class ASN1
$temp = $this->_decode_ber($content, $start);
$length = $temp['length'];
// end-of-content octets - see paragraph 8.1.5
if (substr($content, $length, 2) == "\0\0") {
if (substr($content, $content_pos + $length, 2) == "\0\0") {
$length+= 2;
$start+= $length;
$newcontent[] = $temp;
@ -315,7 +318,7 @@ class ASN1
$start+= $length;
$remainingLength-= $length;
$newcontent[] = $temp;
$this->_string_shift($content, $length);
$content_pos += $length;
}
return array(
@ -339,11 +342,11 @@ class ASN1
//if (strlen($content) != 1) {
// return false;
//}
$current['content'] = (bool) ord($content[0]);
$current['content'] = (bool) ord($content[$content_pos]);
break;
case self::TYPE_INTEGER:
case self::TYPE_ENUMERATED:
$current['content'] = new BigInteger($content, -256);
$current['content'] = new BigInteger(substr($content, $content_pos), -256);
break;
case self::TYPE_REAL: // not currently supported
return false;
@ -352,10 +355,10 @@ class ASN1
// the number of unused bits in the final subsequent octet. The number shall be in the range zero to
// seven.
if (!$constructed) {
$current['content'] = $content;
$current['content'] = substr($content, $content_pos);
} else {
$temp = $this->_decode_ber($content, $start);
$length-= strlen($content);
$temp = $this->_decode_ber($content, $start, $content_pos);
$length-= (strlen($content) - $content_pos);
$last = count($temp) - 1;
for ($i = 0; $i < $last; $i++) {
// all subtags should be bit strings
@ -373,13 +376,13 @@ class ASN1
break;
case self::TYPE_OCTET_STRING:
if (!$constructed) {
$current['content'] = $content;
$current['content'] = substr($content, $content_pos);
} else {
$current['content'] = '';
$length = 0;
while (substr($content, 0, 2) != "\0\0") {
$temp = $this->_decode_ber($content, $length + $start);
$this->_string_shift($content, $temp['length']);
while (substr($content, $content_pos, 2) != "\0\0") {
$temp = $this->_decode_ber($content, $length + $start, $content_pos);
$content_pos += $temp['length'];
// all subtags should be octet strings
//if ($temp['type'] != self::TYPE_OCTET_STRING) {
// return false;
@ -387,7 +390,7 @@ class ASN1
$current['content'].= $temp['content'];
$length+= $temp['length'];
}
if (substr($content, 0, 2) == "\0\0") {
if (substr($content, $content_pos, 2) == "\0\0") {
$length+= 2; // +2 for the EOC
}
}
@ -402,26 +405,28 @@ class ASN1
case self::TYPE_SET:
$offset = 0;
$current['content'] = array();
while (strlen($content)) {
$content_len = strlen($content);
while ($content_pos < $content_len) {
// 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") {
if (!isset($current['headerlength']) && substr($content, $content_pos, 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']);
$temp = $this->_decode_ber($content, $start + $offset, $content_pos);
$content_pos += $temp['length'];
$current['content'][] = $temp;
$offset+= $temp['length'];
}
break;
case self::TYPE_OBJECT_IDENTIFIER:
$temp = ord($this->_string_shift($content));
$temp = ord($content[$content_pos++]);
$current['content'] = sprintf('%d.%d', floor($temp / 40), $temp % 40);
$valuen = 0;
// process septets
while (strlen($content)) {
$temp = ord($this->_string_shift($content));
$content_len = strlen($content);
while ($content_pos < $content_len) {
$temp = ord($content[$content_pos++]);
$valuen <<= 7;
$valuen |= $temp & 0x7F;
if (~$temp & 0x80) {
@ -462,11 +467,11 @@ class ASN1
case self::TYPE_UTF8_STRING:
// ????
case self::TYPE_BMP_STRING:
$current['content'] = $content;
$current['content'] = substr($content, $content_pos);
break;
case self::TYPE_UTC_TIME:
case self::TYPE_GENERALIZED_TIME:
$current['content'] = $this->_decodeTime($content, $tag);
$current['content'] = $this->_decodeTime(substr($content, $content_pos), $tag);
default:
}

View File

@ -1487,7 +1487,9 @@ class X509
$this->signatureSubject = substr($cert, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']);
$this->_mapInExtensions($x509, 'tbsCertificate/extensions', $asn1);
if ($this->_isSubArrayValid($x509, 'tbsCertificate/extensions')) {
$this->_mapInExtensions($x509, 'tbsCertificate/extensions', $asn1);
}
$this->_mapInDNs($x509, 'tbsCertificate/issuer/rdnSequence', $asn1);
$this->_mapInDNs($x509, 'tbsCertificate/subject/rdnSequence', $asn1);
@ -1591,9 +1593,9 @@ class X509
*/
function _mapInExtensions(&$root, $path, $asn1)
{
$extensions = &$this->_subArray($root, $path);
$extensions = &$this->_subArrayUnchecked($root, $path);
if (is_array($extensions)) {
if ($extensions) {
for ($i = 0; $i < count($extensions); $i++) {
$id = $extensions[$i]['extnId'];
$value = &$extensions[$i]['extnValue'];
@ -1725,7 +1727,7 @@ class X509
if ($mapped !== false) {
$values[$j] = $mapped;
}
if ($id == 'pkcs-9-at-extensionRequest') {
if ($id == 'pkcs-9-at-extensionRequest' && $this->_isSubArrayValid($values, $j)) {
$this->_mapInExtensions($values, $j, $asn1);
}
} elseif ($map) {
@ -3244,11 +3246,18 @@ class X509
$this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']);
$this->_mapInDNs($crl, 'tbsCertList/issuer/rdnSequence', $asn1);
$this->_mapInExtensions($crl, 'tbsCertList/crlExtensions', $asn1);
$rclist = &$this->_subArray($crl, 'tbsCertList/revokedCertificates');
if (is_array($rclist)) {
foreach ($rclist as $i => $extension) {
$this->_mapInExtensions($rclist, "$i/crlEntryExtensions", $asn1);
if ($this->_isSubArrayValid($crl, 'tbsCertList/crlExtensions')) {
$this->_mapInExtensions($crl, 'tbsCertList/crlExtensions', $asn1);
}
if ($this->_isSubArrayValid($crl, 'tbsCertList/revokedCertificates')) {
$rclist_ref = &$this->_subArrayUnchecked($crl, 'tbsCertList/revokedCertificates');
if ($rclist_ref) {
$rclist = $crl['tbsCertList']['revokedCertificates'];
foreach ($rclist as $i => $extension) {
if ($this->_isSubArrayValid($rclist, "$i/crlEntryExtensions", $asn1)) {
$this->_mapInExtensions($rclist_ref, "$i/crlEntryExtensions", $asn1);
}
}
}
}
@ -3867,6 +3876,74 @@ class X509
$this->caFlag = true;
}
/**
* Check for validity of subarray
*
* This is intended for use in conjunction with _subArrayUnchecked(),
* implementing the checks included in _subArray() but without copying
* a potentially large array by passing its reference by-value to is_array().
*
* @param array $root
* @param string $path
* @return boolean
* @access private
*/
function _isSubArrayValid($root, $path)
{
if (!is_array($root)) {
return false;
}
foreach (explode('/', $path) as $i) {
if (!is_array($root)) {
return false;
}
if (!isset($root[$i])) {
return true;
}
$root = $root[$i];
}
return true;
}
/**
* Get a reference to a subarray
*
* This variant of _subArray() does no is_array() checking,
* so $root should be checked with _isSubArrayValid() first.
*
* This is here for performance reasons:
* Passing a reference (i.e. $root) by-value (i.e. to is_array())
* creates a copy. If $root is an especially large array, this is expensive.
*
* @param array $root
* @param string $path absolute path with / as component separator
* @param bool $create optional
* @access private
* @return array|false
*/
function &_subArrayUnchecked(&$root, $path, $create = false)
{
$false = false;
foreach (explode('/', $path) as $i) {
if (!isset($root[$i])) {
if (!$create) {
return $false;
}
$root[$i] = array();
}
$root = &$root[$i];
}
return $root;
}
/**
* Get a reference to a subarray
*