- id-at-organizationalUnitName was misnamed as id-at-dnQualifier

- make it so CA's can't be loaded if the keyusage extension doesn't permit their being loaded
- implement validateURL() function stub
- add support for a few more DN attributes
- add removeDNProp(), getDNProp() and setDomain()
- fixed some issues preventing new certs from being signed

git-svn-id: http://phpseclib.svn.sourceforge.net/svnroot/phpseclib/trunk@213 21d32557-59b3-4da0-833f-c5933fad653e
This commit is contained in:
Jim Wigginton 2012-04-22 06:00:55 +00:00
parent 392ff50c00
commit 3dd9e2b318

View File

@ -1029,17 +1029,17 @@ class File_X509 {
'2.5.4.7' => 'id-at-localityName', '2.5.4.7' => 'id-at-localityName',
'2.5.4.8' => 'id-at-stateOrProvinceName', '2.5.4.8' => 'id-at-stateOrProvinceName',
'2.5.4.10' => 'id-at-organizationName', '2.5.4.10' => 'id-at-organizationName',
'2.5.4.11' => 'id-at-dnQualifier', '2.5.4.11' => 'id-at-organizationalUnitName',
'2.5.4.12' => 'id-at-title', '2.5.4.12' => 'id-at-title',
'2.5.4.46' => 'id-at-dnQualifier', '2.5.4.46' => 'id-at-dnQualifier',
'2.5.4.6' => 'id-at-countryName', '2.5.4.6' => 'id-at-countryName',
'2.5.4.5' => 'id-at-serialNumber', '2.5.4.5' => 'id-at-serialNumber',
'2.5.4.65' => 'id-at-pseudonym', '2.5.4.65' => 'id-at-pseudonym',
'2.5.4.17' => 'id-at-postalCode',
'2.5.4.9' => 'id-at-streetAddress',
'0.9.2342.19200300.100.1.25' => 'id-domainComponent', '0.9.2342.19200300.100.1.25' => 'id-domainComponent',
'1.2.840.113549.1.9' => 'pkcs-9', '1.2.840.113549.1.9' => 'pkcs-9',
'1.2.840.113549.1.9.1' => 'id-emailAddress', '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' => 'id-ce',
'2.5.29.35' => 'id-ce-authorityKeyIdentifier', '2.5.29.35' => 'id-ce-authorityKeyIdentifier',
'2.5.29.14' => 'id-ce-subjectKeyIdentifier', '2.5.29.14' => 'id-ce-subjectKeyIdentifier',
@ -1249,6 +1249,7 @@ class File_X509 {
$key = $this->_reformatKey($x509['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'], $key); $key = $this->_reformatKey($x509['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'], $key);
$this->currentCert = $x509; $this->currentCert = $x509;
$this->dn = $x509['tbsCertificate']['subject'];
return $x509; return $x509;
} }
@ -1419,23 +1420,96 @@ class File_X509 {
* *
* @param String $cert * @param String $cert
* @access public * @access public
* @return Boolean
*/ */
function loadCA($cert) function loadCA($cert)
{ {
$this->CAs[] = $this->loadX509($cert); /* From RFC5280 "PKIX Certificate and CRL Profile":
If the keyUsage extension is present, then the subject public key
MUST NOT be used to verify signatures on certificates or CRLs unless
the corresponding keyCertSign or cRLSign bit is set. */
$cert = $this->loadX509($cert);
if (!$cert) {
return false;
}
$keyUsage = $x509->getExtension('id-ce-keyUsage');
if ($keyUsage && !in_array('keyCertSign', $keyUsage)) {
return false;
}
$this->CAs[] = $cert;
unset($this->currentCert); unset($this->currentCert);
unset($this->signatureSubject); unset($this->signatureSubject);
return true;
} }
/** /**
* Validate an X.509 certificate against a URL * Validate an X.509 certificate against a URL
* *
* From RFC2818 "HTTP over TLS":
*
* Matching is performed using the matching rules specified by
* [RFC2459]. If more than one identity of a given type is present in
* the certificate (e.g., more than one dNSName name, a match in any one
* of the set is considered acceptable.) Names may contain the wildcard
* character * which is considered to match any single domain name
* component or component fragment. E.g., *.a.com matches foo.a.com but
* not bar.foo.a.com. f*.com matches foo.com but not bar.com.
*
* @param String $url * @param String $url
* @access public * @access public
* @return Boolean * @return Boolean
*/ */
function validateURL($url) function validateURL($url)
{ {
if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) {
return false;
}
$components = parse_url($url);
if (!isset($components['host'])) {
return false;
}
if ($names = $this->getExtension('id-ce-subjectAltName')) {
foreach ($names as $key => $value) {
$value = str_replace(array('.', '*'), array('\.', '[^.]*'), $value);
switch ($key) {
case 'dNSName':
/* From RFC2818 "HTTP over TLS":
If a subjectAltName extension of type dNSName is present, that MUST
be used as the identity. Otherwise, the (most specific) Common Name
field in the Subject field of the certificate MUST be used. Although
the use of the Common Name is existing practice, it is deprecated and
Certification Authorities are encouraged to use the dNSName instead. */
if (preg_match('#^' . $value . '$#', $components['host'])) {
return true;
}
break;
case 'iPAddress':
/* From RFC2818 "HTTP over TLS":
In some cases, the URI is specified as an IP address rather than a
hostname. In this case, the iPAddress subjectAltName must be present
in the certificate and must exactly match the IP in the URI. */
if (preg_match('#(?:\d{1-3}\.){4}#', $components['host'] . '.') && preg_match('#^' . $value . '$#', $components['host'])) {
return true;
}
}
}
return false;
}
if ($value = $this->getDNProp('id-at-commonName')) {
$value = str_replace(array('.', '*'), array('\.', '[^.]*'), $value[0]);
return preg_match('#^' . $value . '$#', $components['host']);
}
return false;
} }
/** /**
@ -1680,6 +1754,33 @@ class File_X509 {
case 'streetaddress': case 'streetaddress':
$type = 'id-at-streetAddress'; $type = 'id-at-streetAddress';
break; break;
case 'id-at-name':
case 'name':
$type = 'id-at-name';
case 'id-at-givenname':
case 'givenname':
$type = 'id-at-givenName';
break;
case 'id-at-surname':
case 'surname':
$type = 'id-at-surname';
break;
case 'id-at-initials':
case 'initials':
$type = 'id-at-initials';
break;
case 'id-at-generationqualifier':
case 'generationqualifier':
$type = 'id-at-generationQualifier';
break;
case 'id-at-organizationalunitname':
case 'organizationalunitname':
$type = 'id-at-organizationalUnitName';
break;
case 'id-at-pseudonym':
case 'pseudonym':
$type = 'id-at-pseudonym';
break;
default: default:
return false; return false;
} }
@ -1694,6 +1795,53 @@ class File_X509 {
return true; return true;
} }
/**
* Remove Distinguished Name properties
*
* @param String $propName
* @access public
*/
function removeDNProp($propName)
{
if (empty($this->dn)) {
return;
}
$dn = &$this->dn['rdnSequence'];
$size = count($dn);
for ($i = 0; $i < $size; $i++) {
if ($dn[$i][0]['type'] == $propName) {
unset($dn[$i]);
}
}
$dn = array_values($dn);
}
/**
* Get Distinguished Name properties
*
* @param String $propName
* @return Mixed
* @access public
*/
function getDNProp($propName)
{
if (empty($this->dn)) {
return false;
}
$dn = $this->dn['rdnSequence'];
$result = array();
for ($i = 0; $i < $size; $i++) {
if ($dn[$i][0]['type'] == $propName) {
$result[] = $propName;
}
}
return $result;
}
/** /**
* Set a Distinguished Name * Set a Distinguished Name
* *
@ -1741,7 +1889,7 @@ class File_X509 {
function getDN($string = false, $dn = NULL) function getDN($string = false, $dn = NULL)
{ {
if (!isset($dn)) { if (!isset($dn)) {
$dn = $this->currentCert['tbsCertificate']['subject']; $dn = $this->dn;
} }
if (!$string) { if (!$string) {
@ -1809,7 +1957,6 @@ class File_X509 {
* *
* @param Object $key * @param Object $key
* @access public * @access public
* @return Boolean
*/ */
function setPrivateKey($key) function setPrivateKey($key)
{ {
@ -1817,17 +1964,14 @@ class File_X509 {
} }
/** /**
* Get the public key * Gets the public key
* *
* Keys need to be Crypt_RSA objects
*
* @param Object $key
* @access public * @access public
* @return Boolean * @return Object
*/ */
function getPublicKey($key) function getPublicKey()
{ {
$this->publicKey = $key; //return
} }
/** /**
@ -1908,6 +2052,9 @@ class File_X509 {
$signatureSubject = $this->signatureSubject; $signatureSubject = $this->signatureSubject;
if (isset($subject->currentCert) && is_array($subject->currentCert) && isset($subject->currentCert['tbsCertificate'])) { if (isset($subject->currentCert) && is_array($subject->currentCert) && isset($subject->currentCert['tbsCertificate'])) {
$this->currentCert['tbsCertificate']['signature']['algorithm'] =
$this->currentCert['signatureAlgorithm']['algorithm'] =
$signatureAlgorithm;
$this->currentCert = $subject->currentCert; $this->currentCert = $subject->currentCert;
if (!empty($this->startDate)) { if (!empty($this->startDate)) {
$this->currentCert['tbsCertificate']['validity']['notBefore']['utcTime'] = $this->startDate; $this->currentCert['tbsCertificate']['validity']['notBefore']['utcTime'] = $this->startDate;
@ -1927,21 +2074,24 @@ class File_X509 {
$this->currentCert['tbsCertificate']['subjectPublicKeyInfo'] = $subjectPublicKey; $this->currentCert['tbsCertificate']['subjectPublicKeyInfo'] = $subjectPublicKey;
} }
$this->removeExtension('id-ce-authorityKeyIdentifier'); $this->removeExtension('id-ce-authorityKeyIdentifier');
if (isset($subject->domains)) {
$this->removeExtension('id-ce-subjectAltName');
}
} else { } else {
if (!isset($subject->publicKey)) { if (!isset($subject->publicKey)) {
return false; return false;
} }
$startDate = empty($this->startDate) ? $this->startDate : @date('M j H:i:s Y T'); $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')); $endDate = !empty($this->endDate) ? $this->endDate : @date('M j H:i:s Y T', strtotime('+1 year'));
$serialNumber = empty($this->serialNumber) ? $this->serialNumber : "\0"; $serialNumber = !empty($this->serialNumber) ? $this->serialNumber : new Math_BigInteger();
$this->currentCert = array( $this->currentCert = array(
'tbsCertificate' => 'tbsCertificate' =>
array( array(
'version' => 'v3', 'version' => 'v3',
'serialNumber' => $serialNumber, // $this->setserialNumber() 'serialNumber' => $serialNumber, // $this->setserialNumber()
'signature' => $signatureAlgorithm, 'signature' => array('algorithm' => $signatureAlgorithm),
'issuer' => false, // this is going to be overwritten later 'issuer' => false, // this is going to be overwritten later
'validity' => array( 'validity' => array(
'notBefore' => array('utcTime' => $startDate), // $this->setStartDate() 'notBefore' => array('utcTime' => $startDate), // $this->setStartDate()
@ -1950,7 +2100,7 @@ class File_X509 {
'subject' => $subject->dn, 'subject' => $subject->dn,
'subjectPublicKeyInfo' => $subjectPublicKey 'subjectPublicKeyInfo' => $subjectPublicKey
), ),
'signatureAlgorithm' => $signatureAlgorithm, 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm),
'signature' => false // this is going to be overwritten later 'signature' => false // this is going to be overwritten later
); );
} }
@ -1986,6 +2136,18 @@ class File_X509 {
); );
} }
if (isset($subject->domains) && count($subject->domains) > 1) {
$this->currentCert['tbsCertificate']['extensions'][] = array(
'extnId' => 'id-ce-subjectAltName',
'critical' => false,
'extnValue' => array()
);
$last = count($this->currentCert['tbsCertificate']['extensions']) - 1;
foreach ($subject->domains as $domain) {
$this->currentCert['tbsCertificate']['extensions'][$last]['extnValue'][] = array('dNSName' => $domain);
}
}
// resync $this->signatureSubject // resync $this->signatureSubject
$this->loadX509($this->saveX509($this->currentCert)); $this->loadX509($this->saveX509($this->currentCert));
@ -2022,7 +2184,6 @@ class File_X509 {
$key->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1); $key->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
$this->currentCert['signature'] = base64_encode("\0" . $key->sign($this->signatureSubject)); $this->currentCert['signature'] = base64_encode("\0" . $key->sign($this->signatureSubject));
return $this->currentCert; return $this->currentCert;
} }
default: default:
@ -2152,7 +2313,7 @@ class File_X509 {
* Sets the authority key identifier * Sets the authority key identifier
* *
* This is used by the id-ce-authorityKeyIdentifier and the id-ce-subjectKeyIdentifier extensions. * This is used by the id-ce-authorityKeyIdentifier and the id-ce-subjectKeyIdentifier extensions.
* *
* @param String $value * @param String $value
* @access public * @access public
*/ */
@ -2167,8 +2328,8 @@ class File_X509 {
/** /**
* Format a public key as appropriate * Format a public key as appropriate
* *
* @access public * @access private
* @return Array * @return Array
*/ */
function _formatSubjectPublicKey() function _formatSubjectPublicKey()
@ -2187,4 +2348,17 @@ class File_X509 {
return false; return false;
} }
} }
/**
* Set the domain name's which the cert is to be valid for
*
* @access public
* @return Array
*/
function setDomain()
{
$this->domains = func_get_args();
$this->removeDNProp('id-at-commonName');
$this->setDNProp('id-at-commonName', $this->domains[0]);
}
} }