From f5807e1d4e131ddb4c6de964e6620edb2b457a98 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Fri, 2 Mar 2018 07:25:57 -0600 Subject: [PATCH] X509: auto download intermediate certs --- phpseclib/File/X509.php | 153 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 151 insertions(+), 2 deletions(-) diff --git a/phpseclib/File/X509.php b/phpseclib/File/X509.php index ab3bd2bf..74afa4be 100644 --- a/phpseclib/File/X509.php +++ b/phpseclib/File/X509.php @@ -320,6 +320,14 @@ class File_X509 */ var $challenge; + /** + * Recursion Limit + * + * @var int + * @access private + */ + var $recur_limit = 5; + /** * Default Constructor. * @@ -2144,6 +2152,113 @@ class File_X509 return true; } + /** + * Fetches a URL + * + * @param string $url + * @access private + * @return bool|string + */ + function _fetchURL($url) + { + $parts = parse_url($url); + $data = ''; + switch ($parts['scheme']) { + case 'http': + $fsock = @fsockopen($parts['host'], isset($parts['port']) ? $parts['port'] : 80); + if (!$fsock) { + return false; + } + fputs($fsock, "GET $parts[path] HTTP/1.0\r\n"); + fputs($fsock, "Host: $parts[host]\r\n\r\n"); + $line = fgets($fsock, 1024); + if (strlen($line) < 3) { + return false; + } + preg_match('#HTTP/1.\d (\d{3})#', $line, $temp); + if ($temp[1] != '200') { + return false; + } + + // skip the rest of the headers in the http response + while (!feof($fsock) && fgets($fsock, 1024) != "\r\n") { + } + + while (!feof($fsock)) { + $data.= fread($fsock, 1024); + } + + break; + //case 'ftp': + //case 'ldap': + //default: + } + + return $data; + } + + /** + * Validates an intermediate cert as identified via authority info access extension + * + * See https://tools.ietf.org/html/rfc4325 for more info + * + * @param bool $caonly + * @param int $count + * @access private + * @return bool + */ + function _testForIntermediate($caonly, $count) + { + $opts = $this->getExtension('id-pe-authorityInfoAccess'); + if (!is_array($opts)) { + return false; + } + foreach ($opts as $opt) { + if ($opt['accessMethod'] == 'id-ad-caIssuers') { + // accessLocation is a GeneralName. GeneralName fields support stuff like email addresses, IP addresses, LDAP, + // etc, but we're only supporting URI's. URI's and LDAP are the only thing https://tools.ietf.org/html/rfc4325 + // discusses + if (isset($opt['accessLocation']['uniformResourceIdentifier'])) { + $url = $opt['accessLocation']['uniformResourceIdentifier']; + break; + } + } + } + + if (!isset($url)) { + return false; + } + + $cert = $this->_fetchURL($url); + if (!is_string($cert)) { + return false; + } + + $parent = new static(); + $parent->CAs = $this->CAs; + /* + "Conforming applications that support HTTP or FTP for accessing + certificates MUST be able to accept .cer files and SHOULD be able + to accept .p7c files." -- https://tools.ietf.org/html/rfc4325 + + A .p7c file is 'a "certs-only" CMS message as specified in RFC 2797" + + These are currently unsupported + */ + if (!is_array($parent->loadX509($cert))) { + return false; + } + + if (!$parent->_validateSignatureCountable($caonly, ++$count)) { + return false; + } + + $this->CAs[] = $parent->currentCert; + //$this->loadCA($cert); + + return true; + } + /** * Validate a signature * @@ -2160,11 +2275,30 @@ class File_X509 * @return mixed */ function validateSignature($caonly = true) + { + return $this->_validateSignatureCountable($caonly, 0); + } + + /** + * Validate a signature + * + * Performs said validation whilst keeping track of how many times validation method is called + * + * @param bool $caonly + * @param int $count + * @access private + * @return mixed + */ + function _validateSignatureCountable($caonly, $count) { if (!is_array($this->currentCert) || !isset($this->signatureSubject)) { return null; } + if ($count == $this->recur_limit) { + return false; + } + /* TODO: "emailAddress attribute values are not case-sensitive (e.g., "subscriber@example.com" is the same as "SUBSCRIBER@EXAMPLE.COM")." -- http://tools.ietf.org/html/rfc5280#section-4.1.2.6 @@ -2210,10 +2344,10 @@ class File_X509 } } if (count($this->CAs) == $i && $caonly) { - return false; + return $this->_testForIntermediate($caonly, $count) && $this->validateSignature($caonly); } } elseif (!isset($signingCert) || $caonly) { - return false; + return $this->_testForIntermediate($caonly, $count) && $this->validateSignature($caonly); } return $this->_validateSignature( $signingCert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'], @@ -2323,6 +2457,21 @@ class File_X509 return true; } + /** + * Sets the recursion limit + * + * When validating a signature it may be necessary to download intermediate certs from URI's. + * An intermediate cert that linked to itself would result in an infinite loop so to prevent + * that we set a recursion limit. A negative number means that there is no recursion limit. + * + * @param int $count + * @access public + */ + function setRecurLimit($count) + { + $this->recur_limit = $count; + } + /** * Reformat public keys *