ASN1 / X509: update to use DateTime instead of unix time

This commit is contained in:
terrafrost 2017-08-19 00:42:36 -05:00
parent 5c792f6bc1
commit 09c17b1a31
2 changed files with 131 additions and 17 deletions

View File

@ -554,7 +554,9 @@ class File_ASN1
break; break;
case FILE_ASN1_TYPE_UTC_TIME: case FILE_ASN1_TYPE_UTC_TIME:
case FILE_ASN1_TYPE_GENERALIZED_TIME: case FILE_ASN1_TYPE_GENERALIZED_TIME:
$current['content'] = $this->_decodeTime(substr($content, $content_pos), $tag); $current['content'] = class_exists('DateTime') ?
$this->_decodeDateTime(substr($content, $content_pos), $tag) :
$this->_decodeUnixTime(substr($content, $content_pos), $tag);
default: default:
} }
@ -788,10 +790,20 @@ class File_ASN1
return isset($this->oids[$decoded['content']]) ? $this->oids[$decoded['content']] : $decoded['content']; return isset($this->oids[$decoded['content']]) ? $this->oids[$decoded['content']] : $decoded['content'];
case FILE_ASN1_TYPE_UTC_TIME: case FILE_ASN1_TYPE_UTC_TIME:
case FILE_ASN1_TYPE_GENERALIZED_TIME: case FILE_ASN1_TYPE_GENERALIZED_TIME:
if (isset($mapping['implicit'])) { if (class_exists('DateTime')) {
$decoded['content'] = $this->_decodeTime($decoded['content'], $decoded['type']); if (isset($mapping['implicit'])) {
$decoded['content'] = $this->_decodeDateTime($decoded['content'], $decoded['type']);
}
if (!$decoded['content']) {
return false;
}
return $decoded['content']->format($this->format);
} else {
if (isset($mapping['implicit'])) {
$decoded['content'] = $this->_decodeUnixTime($decoded['content'], $decoded['type']);
}
return @date($this->format, $decoded['content']);
} }
return @date($this->format, $decoded['content']);
case FILE_ASN1_TYPE_BIT_STRING: case FILE_ASN1_TYPE_BIT_STRING:
if (isset($mapping['mapping'])) { if (isset($mapping['mapping'])) {
$offset = ord($decoded['content'][0]); $offset = ord($decoded['content'][0]);
@ -1040,7 +1052,12 @@ class File_ASN1
case FILE_ASN1_TYPE_GENERALIZED_TIME: case FILE_ASN1_TYPE_GENERALIZED_TIME:
$format = $mapping['type'] == FILE_ASN1_TYPE_UTC_TIME ? 'y' : 'Y'; $format = $mapping['type'] == FILE_ASN1_TYPE_UTC_TIME ? 'y' : 'Y';
$format.= 'mdHis'; $format.= 'mdHis';
$value = @gmdate($format, strtotime($source)) . 'Z'; if (!class_exists('DateTime')) {
$value = @gmdate($format, strtotime($source)) . 'Z';
} else {
$date = new DateTime($source, new DateTimeZone('GMT'));
$value = $date->format($format) . 'Z';
}
break; break;
case FILE_ASN1_TYPE_BIT_STRING: case FILE_ASN1_TYPE_BIT_STRING:
if (isset($mapping['mapping'])) { if (isset($mapping['mapping'])) {
@ -1202,7 +1219,7 @@ class File_ASN1
} }
/** /**
* BER-decode the time * BER-decode the time (using UNIX time)
* *
* Called by _decode_ber() and in the case of implicit tags asn1map(). * Called by _decode_ber() and in the case of implicit tags asn1map().
* *
@ -1211,7 +1228,7 @@ class File_ASN1
* @param int $tag * @param int $tag
* @return string * @return string
*/ */
function _decodeTime($content, $tag) function _decodeUnixTime($content, $tag)
{ {
/* UTCTime: /* UTCTime:
http://tools.ietf.org/html/rfc5280#section-4.1.2.5.1 http://tools.ietf.org/html/rfc5280#section-4.1.2.5.1
@ -1250,6 +1267,55 @@ class File_ASN1
return @$mktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year) + $timezone; return @$mktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year) + $timezone;
} }
/**
* BER-decode the time (using DateTime)
*
* Called by _decode_ber() and in the case of implicit tags asn1map().
*
* @access private
* @param string $content
* @param int $tag
* @return string
*/
function _decodeDateTime($content, $tag)
{
/* UTCTime:
http://tools.ietf.org/html/rfc5280#section-4.1.2.5.1
http://www.obj-sys.com/asn1tutorial/node15.html
GeneralizedTime:
http://tools.ietf.org/html/rfc5280#section-4.1.2.5.2
http://www.obj-sys.com/asn1tutorial/node14.html */
$format = 'YmdHis';
if ($tag == FILE_ASN1_TYPE_UTC_TIME) {
// 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];
}
$prefix = substr($content, 0, 2) >= 50 ? '19' : '20';
$content = $prefix . $content;
} elseif (strpos($content, '.') !== false) {
$format.= '.u';
}
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);
}
/** /**
* Set the time format * Set the time format
* *

View File

@ -2114,7 +2114,9 @@ class File_X509
} }
if (!isset($date)) { if (!isset($date)) {
$date = time(); $date = class_exists('DateTime') ?
new DateTime($date, new DateTimeZone(date_default_timezone_get())) :
time();
} }
$notBefore = $this->currentCert['tbsCertificate']['validity']['notBefore']; $notBefore = $this->currentCert['tbsCertificate']['validity']['notBefore'];
@ -2123,9 +2125,17 @@ class File_X509
$notAfter = $this->currentCert['tbsCertificate']['validity']['notAfter']; $notAfter = $this->currentCert['tbsCertificate']['validity']['notAfter'];
$notAfter = isset($notAfter['generalTime']) ? $notAfter['generalTime'] : $notAfter['utcTime']; $notAfter = isset($notAfter['generalTime']) ? $notAfter['generalTime'] : $notAfter['utcTime'];
if (class_exists('DateTime')) {
$notBefore = new DateTime($notBefore, new DateTimeZone(@date_default_timezone_get()));
$notAfter = new DateTime($notAfter, new DateTimeZone(@date_default_timezone_get()));
} else {
$notBefore = @strtotime($notBefore);
$notAfter = @strtotime($notAfter);
}
switch (true) { switch (true) {
case $date < @strtotime($notBefore): case $date < $notBefore:
case $date > @strtotime($notAfter): case $date > $notAfter:
return false; return false;
} }
@ -3385,7 +3395,15 @@ class File_X509
*/ */
function _timeField($date) function _timeField($date)
{ {
$year = @gmdate("Y", @strtotime($date)); // the same way ASN1.php parses this if (is_object($date) && strtolower(get_class($date)) == 'file_asn1_element') {
return $date;
}
if (!class_exists('DateTime')) {
$year = @gmdate("Y", @strtotime($date)); // the same way ASN1.php parses this
} else {
$dateObj = new DateTime($date, new DateTimeZone('GMT'));
$year = $dateObj->format('Y');
}
if ($year < 2050) { if ($year < 2050) {
return array('utcTime' => $date); return array('utcTime' => $date);
} else { } else {
@ -3450,8 +3468,16 @@ class File_X509
return false; return false;
} }
$startDate = !empty($this->startDate) ? $this->startDate : @date('D, d M Y H:i:s O'); if (!class_exists('DateTime')) {
$endDate = !empty($this->endDate) ? $this->endDate : @date('D, d M Y H:i:s O', strtotime('+1 year')); $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'));
} else {
$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');
}
if (!empty($this->serialNumber)) { if (!empty($this->serialNumber)) {
$serialNumber = $this->serialNumber; $serialNumber = $this->serialNumber;
} else { } else {
@ -3724,7 +3750,12 @@ class File_X509
$currentCert = isset($this->currentCert) ? $this->currentCert : null; $currentCert = isset($this->currentCert) ? $this->currentCert : null;
$signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject : null; $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject : null;
$thisUpdate = !empty($this->startDate) ? $this->startDate : @date('D, d M Y H:i:s O'); if (!class_exists('DateTime')) {
$thisUpdate = !empty($this->startDate) ? $this->startDate : @date('D, d M Y H:i:s O');
} else {
$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'])) { if (isset($crl->currentCert) && is_array($crl->currentCert) && isset($crl->currentCert['tbsCertList'])) {
$this->currentCert = $crl->currentCert; $this->currentCert = $crl->currentCert;
@ -3876,7 +3907,12 @@ class File_X509
*/ */
function setStartDate($date) function setStartDate($date)
{ {
$this->startDate = @date('D, d M Y H:i:s O', @strtotime($date)); if (class_exists('DateTime')) {
$date = new DateTime($date);
$this->startDate = $date->format('D, d M Y H:i:s O');
} else {
$this->startDate = @date('D, d M Y H:i:s O', @strtotime($date));
}
} }
/** /**
@ -3900,7 +3936,12 @@ class File_X509
$temp = chr(FILE_ASN1_TYPE_GENERALIZED_TIME) . $asn1->_encodeLength(strlen($temp)) . $temp; $temp = chr(FILE_ASN1_TYPE_GENERALIZED_TIME) . $asn1->_encodeLength(strlen($temp)) . $temp;
$this->endDate = new File_ASN1_Element($temp); $this->endDate = new File_ASN1_Element($temp);
} else { } else {
$this->endDate = @date('D, d M Y H:i:s O', @strtotime($date)); if (class_exists('DateTime')) {
$date = new DateTime($date);
$this->endDate = $date->format('D, d M Y H:i:s O');
} else {
$this->endDate = @date('D, d M Y H:i:s O', @strtotime($date));
}
} }
} }
@ -4640,9 +4681,16 @@ class File_X509
return false; return false;
} }
if (!class_exists('DateTime')) {
$revocationDate = @date('D, d M Y H:i:s O');
} else {
$revocationDate = new DateTime('now', new DateTimeZone(@date_default_timezone_get()));
$revocationDate = $revocationDate->format('D, d M Y H:i:s O');
}
$i = count($rclist); $i = count($rclist);
$rclist[] = array('userCertificate' => $serial, $rclist[] = array('userCertificate' => $serial,
'revocationDate' => $this->_timeField(@date('D, d M Y H:i:s O'))); 'revocationDate' => $this->_timeField($revocationDate));
return $i; return $i;
} }