From 340ee0cd2d7bf33316238fed8e9ab2e459d676ce Mon Sep 17 00:00:00 2001 From: Patrick Monnerat Date: Tue, 23 Oct 2012 13:37:51 +0200 Subject: [PATCH] ASN1/X509: latch effective type of ANY fields as an additional indexing level. --- phpseclib/File/ASN1.php | 93 ++++++++++++++++++++++++++++++++--------- phpseclib/File/X509.php | 50 +++++++++++++++------- 2 files changed, 107 insertions(+), 36 deletions(-) diff --git a/phpseclib/File/ASN1.php b/phpseclib/File/ASN1.php index 8c409e93..5ff2bdf2 100644 --- a/phpseclib/File/ASN1.php +++ b/phpseclib/File/ASN1.php @@ -195,6 +195,41 @@ class File_ASN1 { */ var $filters; + /** + * Type mapping table for the ANY type. + * + * Structured or unknown types are mapped to a FILE_ASN1_Element. + * Unambiguous types get the direct mapping (int/real/bool). + * Others are mapped as a choice, with an extra indexing level. + * + * @var Array + * @access private + */ + var $ANYmap = array( + FILE_ASN1_TYPE_BOOLEAN => true, + FILE_ASN1_TYPE_INTEGER => true, + FILE_ASN1_TYPE_BIT_STRING => 'bitString', + FILE_ASN1_TYPE_OCTET_STRING => 'octetString', + FILE_ASN1_TYPE_NULL => 'null', + FILE_ASN1_TYPE_OBJECT_IDENTIFIER => 'objectIdentifier', + FILE_ASN1_TYPE_REAL => true, + FILE_ASN1_TYPE_ENUMERATED => 'enumerated', + FILE_ASN1_TYPE_UTF8_STRING => 'utf8String', + FILE_ASN1_TYPE_NUMERIC_STRING => 'numericString', + FILE_ASN1_TYPE_PRINTABLE_STRING => 'printableString', + FILE_ASN1_TYPE_TELETEX_STRING => 'teletexString', + FILE_ASN1_TYPE_VIDEOTEX_STRING => 'videotexString', + FILE_ASN1_TYPE_IA5_STRING => 'ia5String', + FILE_ASN1_TYPE_UTC_TIME => 'utcTime', + FILE_ASN1_TYPE_GENERALIZED_TIME => 'generalTime', + FILE_ASN1_TYPE_GRAPHIC_STRING => 'graphicString', + FILE_ASN1_TYPE_VISIBLE_STRING => 'visibleString', + FILE_ASN1_TYPE_GENERAL_STRING => 'generalString', + FILE_ASN1_TYPE_UNIVERSAL_STRING => 'universalString', + //FILE_ASN1_TYPE_CHARACTER_STRING => 'characterString', + FILE_ASN1_TYPE_BMP_STRING => 'bmpString' + ); + /** * Parse BER-encoding * @@ -444,6 +479,16 @@ class File_ASN1 { } switch (true) { + case $mapping['type'] == FILE_ASN1_TYPE_ANY: + $intype = $decoded['type']; + if (isset($decoded['constant']) || !isset($this->ANYmap[$intype]) || ($this->encoded[$decoded['start']] & 0x20)) { + return new File_ASN1_Element(substr($this->encoded, $decoded['start'], $decoded['length'])); + } + $inmap = $this->ANYmap[$intype]; + if (is_string($inmap)) { + return array($inmap => $this->asn1map($decoded, array('type' => $intype) + $mapping)); + } + break; case $mapping['type'] == FILE_ASN1_TYPE_CHOICE: foreach ($mapping['children'] as $key => $option) { switch (true) { @@ -459,7 +504,6 @@ class File_ASN1 { case isset($mapping['implicit']): case isset($mapping['explicit']): case $decoded['type'] == $mapping['type']: - case $mapping['type'] == FILE_ASN1_TYPE_ANY: break; default: return NULL; @@ -469,14 +513,6 @@ class File_ASN1 { $decoded['type'] = $mapping['type']; } - if ($mapping['type'] == FILE_ASN1_TYPE_ANY) { - if ($decoded['type'] == FILE_ASN1_TYPE_SEQUENCE || $decoded['type'] == FILE_ASN1_TYPE_SET) { - // return $this->encode_der($decoded['content']); - //return serialize($decoded['content']); - return substr($this->encoded, $decoded['start'], $decoded['length']); - } - } - switch ($decoded['type']) { case FILE_ASN1_TYPE_SEQUENCE: $map = array(); @@ -920,14 +956,31 @@ class File_ASN1 { } break; case FILE_ASN1_TYPE_ANY: - if (!isset($source)) { - if (isset($idx)) { - array_pop($this->location); - } - return $this->_encode_der(NULL, array('type' => FILE_ASN1_TYPE_NULL)); + $loc = $this->location; + if (isset($idx)) { + array_pop($this->location); } + + switch (true) { + case !isset($source): + return $this->_encode_der(NULL, array('type' => FILE_ASN1_TYPE_NULL) + $mapping); + case is_int($source): + case is_object($source) && strtolower(get_class($source)) == 'math_biginteger': + return $this->_encode_der($source, array('type' => FILE_ASN1_TYPE_INTEGER) + $mapping); + case is_float($source): + return $this->_encode_der($source, array('type' => FILE_ASN1_TYPE_REAL) + $mapping); + case is_bool($source): + return $this->_encode_der($source, array('type' => FILE_ASN1_TYPE_BOOLEAN) + $mapping); + case is_array($source) && count($source) == 1: + $typename = implode('', array_keys($source)); + $outtype = array_search($typename, $this->ANYmap, true); + if ($outtype !== false) { + return $this->_encode_der($source[$typename], array('type' => $outtype) + $mapping); + } + } + $filters = $this->filters; - foreach ($this->location as $part) { + foreach ($loc as $part) { if (!isset($filters[$part])) { $filters = false; break; @@ -935,13 +988,10 @@ class File_ASN1 { $filters = $filters[$part]; } if ($filters === false) { - user_error('No filters defined for ' . implode('/', $this->location), E_USER_NOTICE); + user_error('No filters defined for ' . implode('/', $loc), E_USER_NOTICE); return false; } - if (isset($idx)) { - array_pop($this->location); - } - return $this->_encode_der($source, $filters); + return $this->_encode_der($source, $filters + $mapping); case FILE_ASN1_TYPE_NULL: $value = ''; break; @@ -953,6 +1003,9 @@ class File_ASN1 { case FILE_ASN1_TYPE_BMP_STRING: case FILE_ASN1_TYPE_IA5_STRING: case FILE_ASN1_TYPE_VISIBLE_STRING: + case FILE_ASN1_TYPE_VIDEOTEX_STRING: + case FILE_ASN1_TYPE_GRAPHIC_STRING: + case FILE_ASN1_TYPE_GENERAL_STRING: $value = $source; break; case FILE_ASN1_TYPE_BOOLEAN: diff --git a/phpseclib/File/X509.php b/phpseclib/File/X509.php index 2a60550a..f0835491 100644 --- a/phpseclib/File/X509.php +++ b/phpseclib/File/X509.php @@ -2033,10 +2033,11 @@ class File_X509 { * * @param String $propName * @param Mixed $propValue + * @param String $type optional * @access public * @return Boolean */ - function setDNProp($propName, $propValue) + function setDNProp($propName, $propValue, $type = 'utf8String') { if (empty($this->dn)) { $this->dn = array('rdnSequence' => array()); @@ -2046,13 +2047,17 @@ class File_X509 { return false; } - foreach ((array) $propValue as $v) + foreach ((array) $propValue as $v) { + if (!is_array($v) && isset($type)) { + $v = array($type => $v); + } $this->dn['rdnSequence'][] = array( array( 'type' => $propName, 'value'=> $v ) ); + } return true; } @@ -2088,10 +2093,12 @@ class File_X509 { * Get Distinguished Name properties * * @param String $propName + * @param Array $dn optional + * @param Boolean $withType optional * @return Mixed * @access public */ - function getDNProp($propName, $dn = NULL) + function getDNProp($propName, $dn = NULL, $withType = false) { if (!isset($dn)) { $dn = $this->dn; @@ -2109,7 +2116,11 @@ class File_X509 { $result = array(); for ($i = 0; $i < count($dn); $i++) { if ($dn[$i][0]['type'] == $propName) { - $result[] = $dn[$i][0]['value']; + $v = $dn[$i][0]['value']; + if (!$withType && is_array($v) && count($v) == 1) { + $v = array_pop($v); + } + $result[] = $v; } } @@ -2121,10 +2132,11 @@ class File_X509 { * * @param Mixed $dn * @param Boolean $merge optional + * @param String $type optional * @access public * @return Boolean */ - function setDN($dn, $merge = false) + function setDN($dn, $merge = false, $type = 'utf8String') { if (!$merge) { $this->dn = NULL; @@ -2137,8 +2149,8 @@ class File_X509 { } // handles stuff generated by openssl_x509_parse() - foreach ($dn as $type => $value) { - if (!$this->setDNProp($type, $value)) { + foreach ($dn as $prop => $value) { + if (!$this->setDNProp($prop, $value, $type)) { return false; } } @@ -2148,9 +2160,9 @@ class File_X509 { // handles everything else $results = preg_split('#((?:^|, |/)(?:C=|O=|OU=|CN=|L=|ST=|SN=|postalCode=|streetAddress=|emailAddress=|serialNumber=|organizationalUnitName=|title=|description=|role=|x500UniqueIdentifier=))#', $dn, -1, PREG_SPLIT_DELIM_CAPTURE); for ($i = 1; $i < count($results); $i+=2) { - $type = trim($results[$i], ', =/'); + $prop = trim($results[$i], ', =/'); $value = $results[$i + 1]; - if (!$this->setDNProp($type, $value)) { + if (!$this->setDNProp($prop, $value, $type)) { return false; } } @@ -2162,6 +2174,7 @@ class File_X509 { * Get the Distinguished Name for a certificates subject * * @param Boolean $string optional + * @param Array $dn optional * @access public * @return Boolean */ @@ -2177,11 +2190,11 @@ class File_X509 { $start = true; foreach ($dn['rdnSequence'] as $field) { - $type = $field[0]['type']; + $prop = $field[0]['type']; $value = $field[0]['value']; $delim = ', '; - switch ($type) { + switch ($prop) { case 'id-at-countryName': $desc = 'C='; break; @@ -2209,12 +2222,15 @@ class File_X509 { break; default: $delim = '/'; - $desc = preg_replace('#.+-([^-]+)$#', '$1', $type) . '='; + $desc = preg_replace('#.+-([^-]+)$#', '$1', $prop) . '='; } if (!$start) { $output.= $delim; } + if (is_array($value) && count($value) == 1) { + $value = array_pop($value); // Always strip data type. + } $output.= $desc . $value; $start = false; } @@ -2254,28 +2270,30 @@ class File_X509 { * Get an individual Distinguished Name property for a certificates issuer * * @param String $propName + * @param Boolean $withType optional * @access public * @return Mixed */ - function getIssuerDNProp($propName) + function getIssuerDNProp($propName, $withType = false) { if (!isset($this->currentCert) || !is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) { return false; } - return $this->getDNProp($propName, $this->currentCert['tbsCertificate']['issuer']); + return $this->getDNProp($propName, $this->currentCert['tbsCertificate']['issuer'], $withType); } /** * Alias of getDNProp() * * @param String $propName + * @param Boolean $withType optional * @access public * @return Mixed */ - function getSubjectDNProp($propName) + function getSubjectDNProp($propName, $withType = false) { - return $this->getDNProp($propName); + return $this->getDNProp($propName, NULL, $withType); } /**