- make it so an array returned by loadX509() can be reloaded by loadX509()

- validateDate() didn't work
- add postalCode and streetAddress as supported DN attributes
- add getDN()
- split setKey() out into setPrivateKey() and setPublicKey()
- add sign(), setStartDate(), setEndDate(), setSerialNumber(), removeExtension(), getExtension() and getExtensions()

git-svn-id: http://phpseclib.svn.sourceforge.net/svnroot/phpseclib/trunk@209 21d32557-59b3-4da0-833f-c5933fad653e
This commit is contained in:
Jim Wigginton 2012-04-15 17:17:16 +00:00
parent 09f4bef2f1
commit 3f9aa1ad6a

View File

@ -117,12 +117,20 @@ class File_X509 {
var $dn = array('rdnSequence' => array());
/**
* Public or private key
* Public key
*
* @var String
* @access private
*/
var $key;
var $publicKey;
/**
* Private key
*
* @var String
* @access private
*/
var $privateKey;
/**
* Object identifiers for X.509 certificates
@ -149,6 +157,41 @@ class File_X509 {
*/
var $currentCert;
/**
* The signature subject
*
* There's no guarantee File_X509 is going to reencode an X.509 cert in the same way it was originally
* encoded so we take save the portion of the original cert that the signature would have made for.
*
* @var String
* @access private
*/
var $signatureSubject;
/**
* Certificate Start Date
*
* @var String
* @access private
*/
var $startDate;
/**
* Certificate End Date
*
* @var String
* @access private
*/
var $endDate;
/**
* Serial Number
*
* @var String
* @access private
*/
var $serialNumber;
/**
* Default Constructor.
*
@ -984,6 +1027,8 @@ class File_X509 {
'0.9.2342.19200300.100.1.25' => 'id-domainComponent',
'1.2.840.113549.1.9' => 'pkcs-9',
'1.2.840.113549.1.9.1' => 'id-emailAddress',
'2.5.4.17' => 'id-at-postalCode',
'2.5.4.9' => 'id-at-streetAddress',
'2.5.29' => 'id-ce',
'2.5.29.35' => 'id-ce-authorityKeyIdentifier',
'2.5.29.14' => 'id-ce-subjectKeyIdentifier',
@ -1112,12 +1157,20 @@ class File_X509 {
/**
* Load X.509 certificate
*
* Returns an associative array describing the X.509 cert or a false if the cert failed to load
*
* @param String $cert
* @access public
* @return Array
* @return Mixed
*/
function loadX509($cert)
{
if (is_array($cert) && isset($cert['tbsCertificate'])) {
$this->currentCert = $cert;
unset($this->signatureSubject);
return false;
}
$asn1 = new File_ASN1();
/*
@ -1198,6 +1251,10 @@ class File_X509 {
*/
function saveX509($cert)
{
if (!is_array($cert) || !isset($cert['tbsCertificate'])) {
return false;
}
switch ($cert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm']) {
case 'rsaEncryption':
$cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'] =
@ -1389,8 +1446,8 @@ class File_X509 {
}
switch (true) {
case time() < @strtotime($this->currentCert['tbsCertificate']['notBefore']):
case time() > @strtotime($this->currentCert['tbsCertificate']['notAfter']):
case time() < @strtotime($this->currentCert['tbsCertificate']['validity']['notBefore']):
case time() > @strtotime($this->currentCert['tbsCertificate']['validity']['notAfter']):
return false;
}
@ -1409,7 +1466,7 @@ class File_X509 {
*/
function validateSignature($options = 0)
{
if (!is_array($this->currentCert)) {
if (!is_array($this->currentCert) || !isset($this->signatureSubject)) {
return false;
}
@ -1552,7 +1609,7 @@ class File_X509 {
case 'id-at-organizationname':
case 'organizationname':
case 'o':
$type = 'id-at-organizationname';
$type = 'id-at-organizationName';
break;
case 'id-at-dnqualifier':
case 'dnqualifier':
@ -1585,6 +1642,14 @@ class File_X509 {
case 'serialnumber':
$type = 'id-at-serialNumber';
break;
case 'id-at-postalcode':
case 'postalcode':
$type = 'id-at-postalCode';
break;
case 'id-at-streetaddress':
case 'streetaddress':
$type = 'id-at-streetAddress';
break;
default:
return false;
}
@ -1608,8 +1673,13 @@ class File_X509 {
*/
function setDN($dn)
{
// handles stuff generated by openssl_x509_parse()
if (is_array($dn)) {
if (isset($dn['rdnSequence'])) {
$this->dn = $dn;
return true;
}
// handles stuff generated by openssl_x509_parse()
foreach ($dn as $type => $value) {
if (!$this->setDNProp($type, $value)) {
return false;
@ -1619,9 +1689,9 @@ class File_X509 {
}
// handles everything else
$results = preg_split('#((?:^|, )(?:C=|O=|OU=|CN=))#', $dn, -1, PREG_SPLIT_DELIM_CAPTURE);
$results = preg_split('#((?:^|, |/)(?:C=|O=|OU=|CN=|L=|ST=|postalCode=|streetAddress=|emailAddress=|serialNumber=))#', $dn, -1, PREG_SPLIT_DELIM_CAPTURE);
for ($i = 1; $i < count($results); $i+=2) {
$type = trim($results[$i], ', =');
$type = trim($results[$i], ', =/');
$value = $results[$i + 1];
if (!$this->setDNProp($type, $value)) {
return false;
@ -1632,7 +1702,88 @@ class File_X509 {
}
/**
* Set public or private key
* Get the Distinguished Name for a certificates subject
*
* @param Boolean $string optional
* @access public
* @return Boolean
*/
function getDN($string = false)
{
if (!$string) {
return $this->currentCert['tbsCertificate']['subject'];
}
$start = true;
foreach ($this->currentCert['tbsCertificate']['subject']['rdnSequence'] as $field) {
$type = $field[0]['type'];
$value = $field[0]['value'];
$delim = ', ';
switch ($type) {
case 'id-at-countryName':
$desc = 'C=';
break;
case 'id-at-stateOrProvinceName':
$desc = 'ST=';
break;
case 'id-at-organizationName':
$desc = 'O=';
break;
case 'id-at-dnQualifier':
$desc = 'OU=';
break;
case 'id-at-commonName':
$desc = 'CN=';
break;
case 'id-at-localityName':
$desc = 'L=';
break;
default:
$delim = '/';
$desc = preg_replace('#.+-([^-]+)$#', '$1', $type) . '=';
}
if (!$start) {
$output.= $delim;
}
$output.= $desc . $value;
$start = false;
}
return $output;
}
/**
* Set public key
*
* Key needs to be a Crypt_RSA object
*
* @param Object $key
* @access public
* @return Boolean
*/
function setPublicKey($key)
{
$this->publicKey = $key;
}
/**
* Set private key
*
* Key needs to be a Crypt_RSA object
*
* @param Object $key
* @access public
* @return Boolean
*/
function setPrivateKey($key)
{
$this->privateKey = $key;
}
/**
* Get the public key
*
* Keys need to be Crypt_RSA objects
*
@ -1640,9 +1791,9 @@ class File_X509 {
* @access public
* @return Boolean
*/
function setKey($key)
function getPublicKey($key)
{
$this->key = $key;
$this->publicKey = $key;
}
/**
@ -1685,14 +1836,224 @@ class File_X509 {
if (!class_exists('Crypt_RSA')) {
require_once('Crypt/RSA.php');
}
$this->key = new Crypt_RSA();
$this->key->loadKey($key);
$this->publicKey = new Crypt_RSA();
$this->publicKey->loadKey($key);
default:
$this->key = NULL;
$this->publicKey = NULL;
}
$this->currentCert = $csr;
return $csr;
}
/**
* Sign an X.509 certificate
*
* $issuer's private key needs to be loaded.
* $subject can be either an existing X.509 cert (if you want to resign it),
* a CSR or something with the DN and public key explicitly set.
*
* @param Crypt_X509 $issuer
* @param Crypt_X509 $subject
* @param String $signatureAlgorithm optional
* @access public
* @return Mixed
*/
function sign($issuer, $subject, $signatureAlgorithm = 'sha1WithRSAEncryption')
{
if (!is_object($issuer->privateKey) || !is_array($issuer->dn)) {
return false;
}
$currentCert = $this->currentCert;
$signatureSubject = $this->signatureSubject;
if (is_array($subject->currentCert) && isset($subject->currentCert['tbsCertificate'])) {
$this->currentCert = $subject->currentCert;
if (!empty($this->startDate)) {
$this->currentCert['tbsCertificate']['validity']['notBefore']['utcTime'] = $this->startDate;
unset($this->currentCert['tbsCertificate']['validity']['notBefore']['generalTime']);
}
if (!empty($this->endDate)) {
$this->currentCert['tbsCertificate']['validity']['notAfter']['utcTime'] = $this->endDate;
unset($this->currentCert['tbsCertificate']['validity']['notAfter']['generalTime']);
}
if (!empty($this->dn)) {
$this->currentCert['tbsCertificate']['subject'] = $this->dn;
}
$this->removeExtension('id-ce-authorityKeyIdentifier');
} else {
$startDate = empty($this->startDate) ? $this->startDate : @date('M j H:i:s Y T');
$endDate = empty($this->endDate) ? $this->endDate : @date('M j H:i:s Y T', strtotime('+1 year'));
$serialNumber = empty($this->serialNumber) ? $this->serialNumber : "\0";
$this->currentCert = array(
'tbsCertificate' =>
array(
'version' => 'v3',
'serialNumber' => $this->serialNumber, // $this->setserialNumber()
'signature' => $signatureAlgorithm,
'issuer' => false, // this is going to be overwritten later
'validity' => array(
'notBefore' => array('utcTime' => $this->startDate), // $this->setStartDate()
'notAfter' => array('utcTime' => $this->endDate) // $this->setEndDate()
),
'subject' => $subject->dn,
'subjectPublicKeyInfo' => $subject->publicKey->getPublicKey()
),
'signatureAlgorithm' => $signatureAlgorithm,
'signature' => false // this is going to be overwritten later
);
}
$this->currentCert['tbsCertificate']['issuer'] = $issuer->dn;
$this->loadX509($this->saveX509($this->currentCert));
$result = $this->_sign($issuer->privateKey, $signatureAlgorithm);
$this->currentCert = $currentCert;
$this->signatureSubject = $signatureSubject;
return $result;
}
/**
* X.509 certificate signing helper function.
*
* @param Object $key
* @param Crypt_X509 $subject
* @param String $signatureAlgorithm
* @access public
* @return Mixed
*/
function _sign($key, $signatureAlgorithm)
{
switch (strtolower(get_class($key))) {
case 'crypt_rsa':
switch ($signatureAlgorithm) {
case 'md2WithRSAEncryption':
case 'md5WithRSAEncryption':
case 'sha1WithRSAEncryption':
case 'sha224WithRSAEncryption':
case 'sha256WithRSAEncryption':
case 'sha384WithRSAEncryption':
case 'sha512WithRSAEncryption':
$key->setHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm));
$key->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
$this->currentCert['signature'] = base64_encode("\0" . $key->sign($this->signatureSubject));
return $this->currentCert;
}
default:
return false;
}
}
/**
* Set certificate start date
*
* @param String $date
* @access public
*/
function setStartDate($date)
{
$this->startDate = @date('M j H:i:s Y T', @strtotime($date));
}
/**
* Set certificate end date
*
* @param String $date
* @access public
*/
function setEndDate($date)
{
$this->endDate = @date('M j H:i:s Y T', @strtotime($date));
}
/**
* Set Serial Number
*
* @param String $serial
* @access public
*/
function setSerialNumber($serial)
{
$this->serialNumber = $serial;
}
/**
* Remove an Extension
*
* @param String $id
* @access public
* @return Boolean
*/
function removeExtension($id)
{
if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) {
return false;
}
$result = false;
$extensions = &$this->currentCert['tbsCertificate']['extensions'];
foreach ($extensions as $key => $value) {
if ($value['extnId'] == $id) {
unset($extensions[$key]);
$result = true;
}
}
$extensions = array_values($extensions);
return $result;
}
/**
* Get an Extension
*
* Returns the extension if it exists and false if not
*
* @param String $id
* @access public
* @return Mixed
*/
function getExtension($id)
{
if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) {
return false;
}
foreach ($this->currentCert['tbsCertificate']['extensions'] as $key => $value) {
if ($value['extnId'] == $id) {
return $value['extnValue'];
}
}
return false;
}
/**
* Returns a list of all extensions in use
*
* @access public
* @return Array
*/
function getExtensions()
{
if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) {
return false;
}
$extensions = array();
foreach ($this->currentCert['tbsCertificate']['extensions'] as $extension) {
$extensions[] = $extension['extnId'];
}
return $extensions;
}
}