fix certificate date encoding

RFC 3280 requires in section
 - 4.1.2.5 Validity
 - 5.1.2.4 This Update
 - 5.1.2.5 Next Update
 - 5.1.2.6 Revoked Certificates
that dates are to be encoded as utcTime iff they are before 2050 and
as generalTime otherwise.

Currently, phpseclib does not respect this by always choosing generalTime.
Further, the format used interally to represent dates only keeps two digits,
so dates in 2050 and later cannot be represented in this format.

This patch fixes this by
 1. changing the interal format to be capable of unambiguously representing
    dates in 2050 or later (i.e. use four digits to represent the year),
 2. choosing between utcTime and generalTime accordingly.

Without this patch, openssl_x509_parse complains:
 Warning: openssl_x509_parse(): illegal ASN1 data type for timestamp
This commit is contained in:
Michael Braun 2014-03-19 14:04:55 +01:00
parent b77b26f692
commit 457f8fbb99
2 changed files with 37 additions and 17 deletions

View File

@ -162,7 +162,7 @@ class File_ASN1
* @access private
* @link http://php.net/class.datetime
*/
var $format = 'D, d M y H:i:s O';
var $format = 'D, d M Y H:i:s O';
/**
* Default date format

View File

@ -3116,6 +3116,28 @@ class File_X509
}
}
/**
* Helper function to build a time field according to RFC 3280 section
* - 4.1.2.5 Validity
* - 5.1.2.4 This Update
* - 5.1.2.5 Next Update
* - 5.1.2.6 Revoked Certificates
* by choosing utcTime iff year of date given is before 2050 and generalTime else.
*
* @param String $date in format date('D, d M Y H:i:s O')
* @access private
* @return Array
*/
function _timeField($date)
{
$year = @gmdate("Y", @strtotime($date)); // the same way ASN1.php parses this
if ($year < 2050) {
return Array('utcTime' => $date);
} else {
return Array('generalTime' => $date);
}
}
/**
* Sign an X.509 certificate
*
@ -3148,12 +3170,10 @@ class File_X509
$this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm;
if (!empty($this->startDate)) {
$this->currentCert['tbsCertificate']['validity']['notBefore']['generalTime'] = $this->startDate;
unset($this->currentCert['tbsCertificate']['validity']['notBefore']['utcTime']);
$this->currentCert['tbsCertificate']['validity']['notBefore'] = $this->_timeField($this->startDate);
}
if (!empty($this->endDate)) {
$this->currentCert['tbsCertificate']['validity']['notAfter']['generalTime'] = $this->endDate;
unset($this->currentCert['tbsCertificate']['validity']['notAfter']['utcTime']);
$this->currentCert['tbsCertificate']['validity']['notAfter'] = $this->_timeField($this->endDate);
}
if (!empty($this->serialNumber)) {
$this->currentCert['tbsCertificate']['serialNumber'] = $this->serialNumber;
@ -3175,8 +3195,8 @@ class File_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 = !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'));
$serialNumber = !empty($this->serialNumber) ? $this->serialNumber : new Math_BigInteger();
$this->currentCert = array(
@ -3187,8 +3207,8 @@ class File_X509
'signature' => array('algorithm' => $signatureAlgorithm),
'issuer' => false, // this is going to be overwritten later
'validity' => array(
'notBefore' => array('generalTime' => $startDate), // $this->setStartDate()
'notAfter' => array('generalTime' => $endDate) // $this->setEndDate()
'notBefore' => $this->_timeField($startDate), // $this->setStartDate()
'notAfter' => $this->_timeField($endDate) // $this->setEndDate()
),
'subject' => $subject->dn,
'subjectPublicKeyInfo' => $subjectPublicKey
@ -3367,7 +3387,7 @@ class File_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 = !empty($this->startDate) ? $this->startDate : @date('D, d M Y H:i:s O');
if (isset($crl->currentCert) && is_array($crl->currentCert) && isset($crl->currentCert['tbsCertList'])) {
$this->currentCert = $crl->currentCert;
@ -3380,7 +3400,7 @@ class File_X509
'version' => 'v2',
'signature' => array('algorithm' => $signatureAlgorithm),
'issuer' => false, // this is going to be overwritten later
'thisUpdate' => array('generalTime' => $thisUpdate) // $this->setStartDate()
'thisUpdate' => $this->_timeField($thisUpdate) // $this->setStartDate()
),
'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm),
'signature' => false // this is going to be overwritten later
@ -3389,10 +3409,10 @@ class File_X509
$tbsCertList = &$this->currentCert['tbsCertList'];
$tbsCertList['issuer'] = $issuer->dn;
$tbsCertList['thisUpdate'] = array('generalTime' => $thisUpdate);
$tbsCertList['thisUpdate'] = $this->_timeField($thisUpdate);
if (!empty($this->endDate)) {
$tbsCertList['nextUpdate'] = array('generalTime' => $this->endDate); // $this->setEndDate()
$tbsCertList['nextUpdate'] = $this->_timeField($this->endDate); // $this->setEndDate()
} else {
unset($tbsCertList['nextUpdate']);
}
@ -3515,7 +3535,7 @@ class File_X509
*/
function setStartDate($date)
{
$this->startDate = @date('D, d M y H:i:s O', @strtotime($date));
$this->startDate = @date('D, d M Y H:i:s O', @strtotime($date));
}
/**
@ -3539,7 +3559,7 @@ class File_X509
$temp = chr(FILE_ASN1_TYPE_GENERALIZED_TIME) . $asn1->_encodeLength(strlen($temp)) . $temp;
$this->endDate = new File_ASN1_Element($temp);
} else {
$this->endDate = @date('D, d M y H:i:s O', @strtotime($date));
$this->endDate = @date('D, d M Y H:i:s O', @strtotime($date));
}
}
@ -4213,7 +4233,7 @@ class File_X509
$i = count($rclist);
$rclist[] = array('userCertificate' => $serial,
'revocationDate' => array('generalTime' => @date('D, d M y H:i:s O')));
'revocationDate' => $this->_timeField(@date('D, d M Y H:i:s O')));
return $i;
}
@ -4233,7 +4253,7 @@ class File_X509
if (($i = $this->_revokedCertificate($rclist, $serial, true)) !== false) {
if (!empty($date)) {
$rclist[$i]['revocationDate'] = array('generalTime' => $date);
$rclist[$i]['revocationDate'] = $this->_timeField($date);
}
return true;