diff --git a/phpseclib/File/ASN1.php b/phpseclib/File/ASN1.php index 504dbe8b..1da046e8 100644 --- a/phpseclib/File/ASN1.php +++ b/phpseclib/File/ASN1.php @@ -25,6 +25,8 @@ namespace phpseclib\File; use phpseclib\File\ASN1\Element; use phpseclib\Math\BigInteger; +use DateTime; +use DateTimeZone; /** * Pure-PHP ASN.1 Parser @@ -707,7 +709,7 @@ class ASN1 if (isset($mapping['implicit'])) { $decoded['content'] = $this->_decodeTime($decoded['content'], $decoded['type']); } - return @date($this->format, $decoded['content']); + return $decoded['content'] ? $decoded['content']->format($this->format) : false; case self::TYPE_BIT_STRING: if (isset($mapping['mapping'])) { $offset = ord($decoded['content'][0]); @@ -956,7 +958,8 @@ class ASN1 case self::TYPE_GENERALIZED_TIME: $format = $mapping['type'] == self::TYPE_UTC_TIME ? 'y' : 'Y'; $format.= 'mdHis'; - $value = @gmdate($format, strtotime($source)) . 'Z'; + $date = new DateTime($source, new DateTimeZone('GMT')); + $value = $date->format($format) . 'Z'; break; case self::TYPE_BIT_STRING: if (isset($mapping['mapping'])) { @@ -1137,33 +1140,32 @@ class ASN1 http://tools.ietf.org/html/rfc5280#section-4.1.2.5.2 http://www.obj-sys.com/asn1tutorial/node14.html */ - $pattern = $tag == self::TYPE_UTC_TIME ? - '#^(..)(..)(..)(..)(..)(..)?(.*)$#' : - '#(....)(..)(..)(..)(..)(..).*([Z+-].*)$#'; - - preg_match($pattern, $content, $matches); - - list(, $year, $month, $day, $hour, $minute, $second, $timezone) = $matches; + $format = 'YmdHis'; if ($tag == self::TYPE_UTC_TIME) { - $year = $year >= 50 ? "19$year" : "20$year"; - } - - if ($timezone == 'Z') { - $mktime = 'gmmktime'; - $timezone = 0; - } elseif (preg_match('#([+-])(\d\d)(\d\d)#', $timezone, $matches)) { - $mktime = 'gmmktime'; - $timezone = 60 * $matches[3] + 3600 * $matches[2]; - if ($matches[1] == '-') { - $timezone = -$timezone; + // https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=28 says "the seconds + // element shall always be present" but none-the-less I've seen X509 certs where it isn't and if the + // browsers parse it phpseclib ought to too + if (preg_match('#^(\d{10})(Z|[+-]\d{4})$#', $content, $matches)) { + $content = $matches[1] . '00' . $matches[2]; } - } else { - $mktime = 'mktime'; - $timezone = 0; + $prefix = substr($content, 0, 2) >= 50 ? '19' : '20'; + $content = $prefix . $content; + } elseif (strpos($content, '.') !== false) { + $format.= '.u'; } - return @$mktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year) + $timezone; + if ($content[strlen($content) - 1] == 'Z') { + $content = substr($content, 0, -1) . '+0000'; + } + + if (strpos($content, '-') !== false || strpos($content, '+') !== false) { + $format.= 'O'; + } + + // error supression isn't necessary as of PHP 7.0: + // http://php.net/manual/en/migration70.other-changes.php + return @DateTime::createFromFormat($format, $content); } /** diff --git a/phpseclib/File/X509.php b/phpseclib/File/X509.php index 9a70457b..caa9726c 100644 --- a/phpseclib/File/X509.php +++ b/phpseclib/File/X509.php @@ -31,6 +31,8 @@ use phpseclib\Crypt\Random; use phpseclib\Crypt\RSA; use phpseclib\File\ASN1\Element; use phpseclib\Math\BigInteger; +use DateTime; +use DateTimeZone; /** * Pure-PHP X.509 Parser @@ -2082,7 +2084,7 @@ class X509 } if (!isset($date)) { - $date = time(); + $date = new DateTime($date, new DateTimeZone(date_default_timezone_get())); } $notBefore = $this->currentCert['tbsCertificate']['validity']['notBefore']; @@ -2092,8 +2094,8 @@ class X509 $notAfter = isset($notAfter['generalTime']) ? $notAfter['generalTime'] : $notAfter['utcTime']; switch (true) { - case $date < @strtotime($notBefore): - case $date > @strtotime($notAfter): + case $date < new DateTime($notBefore, new DateTimeZone(@date_default_timezone_get())): + case $date > new DateTime($notAfter, new DateTimeZone(@date_default_timezone_get())): return false; } @@ -3338,7 +3340,11 @@ class X509 */ function _timeField($date) { - $year = @gmdate("Y", @strtotime($date)); // the same way ASN1.php parses this + if ($date instanceof Element) { + return $date; + } + $dateObj = new DateTime($date, new DateTimeZone('GMT')); + $year = $dateObj->format('Y'); // the same way ASN1.php parses this if ($year < 2050) { return array('utcTime' => $date); } else { @@ -3403,8 +3409,12 @@ class X509 return false; } - $startDate = !empty($this->startDate) ? $this->startDate : @date('D, d M Y H:i:s O'); - $endDate = !empty($this->endDate) ? $this->endDate : @date('D, d M Y H:i:s O', strtotime('+1 year')); + $startDate = new DateTime('now', new DateTimeZone(@date_default_timezone_get())); + $startDate = !empty($this->startDate) ? $this->startDate : $startDate->format('D, d M Y H:i:s O'); + + $endDate = new DateTime('+1 year', new DateTimeZone(@date_default_timezone_get())); + $endDate = !empty($this->endDate) ? $this->endDate : $endDate->format('D, d M Y H:i:s O'); + /* "The serial number MUST be a positive integer" "Conforming CAs MUST NOT use serialNumber values longer than 20 octets." -- https://tools.ietf.org/html/rfc5280#section-4.1.2.2 @@ -3672,7 +3682,9 @@ class X509 $currentCert = isset($this->currentCert) ? $this->currentCert : null; $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject : null; - $thisUpdate = !empty($this->startDate) ? $this->startDate : @date('D, d M Y H:i:s O'); + + $thisUpdate = new DateTime('now', new DateTimeZone(@date_default_timezone_get())); + $thisUpdate = !empty($this->startDate) ? $this->startDate : $thisUpdate->format('D, d M Y H:i:s O'); if (isset($crl->currentCert) && is_array($crl->currentCert) && isset($crl->currentCert['tbsCertList'])) { $this->currentCert = $crl->currentCert; @@ -3823,7 +3835,11 @@ class X509 */ function setStartDate($date) { - $this->startDate = @date('D, d M Y H:i:s O', @strtotime($date)); + if (!is_object($date) || !is_a($date, 'DateTime')) { + $date = new DateTime($date); + } + + $this->startDate = $date->format('D, d M Y H:i:s O'); } /** @@ -3847,7 +3863,11 @@ class X509 $temp = chr(ASN1::TYPE_GENERALIZED_TIME) . $asn1->_encodeLength(strlen($temp)) . $temp; $this->endDate = new Element($temp); } else { - $this->endDate = @date('D, d M Y H:i:s O', @strtotime($date)); + if (!is_object($date) || !is_a($date, 'DateTime')) { + $date = new DateTime($date); + } + + $this->endDate = $date->format('D, d M Y H:i:s O'); } } @@ -4577,8 +4597,9 @@ class X509 } $i = count($rclist); + $revocationDate = new DateTime('now', new DateTimeZone(@date_default_timezone_get())); $rclist[] = array('userCertificate' => $serial, - 'revocationDate' => $this->_timeField(@date('D, d M Y H:i:s O'))); + 'revocationDate' => $this->_timeField($revocationDate->format('D, d M Y H:i:s O'))); return $i; }