From 457f8fbb996b3016c2075f4096421d6525b46e0d Mon Sep 17 00:00:00 2001 From: Michael Braun Date: Wed, 19 Mar 2014 14:04:55 +0100 Subject: [PATCH] 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 --- phpseclib/File/ASN1.php | 2 +- phpseclib/File/X509.php | 52 ++++++++++++++++++++++++++++------------- 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/phpseclib/File/ASN1.php b/phpseclib/File/ASN1.php index 8843569b..9c85083d 100644 --- a/phpseclib/File/ASN1.php +++ b/phpseclib/File/ASN1.php @@ -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 diff --git a/phpseclib/File/X509.php b/phpseclib/File/X509.php index de59c95e..19daff87 100644 --- a/phpseclib/File/X509.php +++ b/phpseclib/File/X509.php @@ -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;