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;
case FILE_ASN1_TYPE_UTC_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:
}
@ -788,10 +790,20 @@ class File_ASN1
return isset($this->oids[$decoded['content']]) ? $this->oids[$decoded['content']] : $decoded['content'];
case FILE_ASN1_TYPE_UTC_TIME:
case FILE_ASN1_TYPE_GENERALIZED_TIME:
if (class_exists('DateTime')) {
if (isset($mapping['implicit'])) {
$decoded['content'] = $this->_decodeTime($decoded['content'], $decoded['type']);
$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']);
}
case FILE_ASN1_TYPE_BIT_STRING:
if (isset($mapping['mapping'])) {
$offset = ord($decoded['content'][0]);
@ -1040,7 +1052,12 @@ class File_ASN1
case FILE_ASN1_TYPE_GENERALIZED_TIME:
$format = $mapping['type'] == FILE_ASN1_TYPE_UTC_TIME ? 'y' : 'Y';
$format.= 'mdHis';
if (!class_exists('DateTime')) {
$value = @gmdate($format, strtotime($source)) . 'Z';
} else {
$date = new DateTime($source, new DateTimeZone('GMT'));
$value = $date->format($format) . 'Z';
}
break;
case FILE_ASN1_TYPE_BIT_STRING:
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().
*
@ -1211,7 +1228,7 @@ class File_ASN1
* @param int $tag
* @return string
*/
function _decodeTime($content, $tag)
function _decodeUnixTime($content, $tag)
{
/* UTCTime:
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;
}
/**
* 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
*

View File

@ -2114,7 +2114,9 @@ class File_X509
}
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'];
@ -2123,9 +2125,17 @@ class File_X509
$notAfter = $this->currentCert['tbsCertificate']['validity']['notAfter'];
$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) {
case $date < @strtotime($notBefore):
case $date > @strtotime($notAfter):
case $date < $notBefore:
case $date > $notAfter:
return false;
}
@ -3385,7 +3395,15 @@ class File_X509
*/
function _timeField($date)
{
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) {
return array('utcTime' => $date);
} else {
@ -3450,8 +3468,16 @@ class File_X509
return false;
}
if (!class_exists('DateTime')) {
$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)) {
$serialNumber = $this->serialNumber;
} else {
@ -3724,7 +3750,12 @@ class File_X509
$currentCert = isset($this->currentCert) ? $this->currentCert : null;
$signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject : null;
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'])) {
$this->currentCert = $crl->currentCert;
@ -3876,8 +3907,13 @@ class File_X509
*/
function setStartDate($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));
}
}
/**
* Set certificate end date
@ -3899,10 +3935,15 @@ class File_X509
$asn1 = new File_ASN1();
$temp = chr(FILE_ASN1_TYPE_GENERALIZED_TIME) . $asn1->_encodeLength(strlen($temp)) . $temp;
$this->endDate = new File_ASN1_Element($temp);
} else {
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));
}
}
}
/**
* Set Serial Number
@ -4640,9 +4681,16 @@ class File_X509
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);
$rclist[] = array('userCertificate' => $serial,
'revocationDate' => $this->_timeField(@date('D, d M Y H:i:s O')));
'revocationDate' => $this->_timeField($revocationDate));
return $i;
}