ASN1,X509: add support for ip addresses in subjaltname

(among other places)
This commit is contained in:
terrafrost 2013-10-09 14:25:52 -05:00
parent 9c5563503e
commit 9b53c45f04
2 changed files with 125 additions and 22 deletions

View File

@ -500,12 +500,15 @@ class File_ASN1 {
* *
* Provides an ASN.1 semantic mapping ($mapping) from a parsed BER-encoding to a human readable format. * Provides an ASN.1 semantic mapping ($mapping) from a parsed BER-encoding to a human readable format.
* *
* "Special" mappings may be applied on a per tag-name basis via $special.
*
* @param Array $decoded * @param Array $decoded
* @param Array $mapping * @param Array $mapping
* @param Array $special
* @return Array * @return Array
* @access public * @access public
*/ */
function asn1map($decoded, $mapping) function asn1map($decoded, $mapping, $special = array())
{ {
if (isset($mapping['explicit'])) { if (isset($mapping['explicit'])) {
$decoded = $decoded['content'][0]; $decoded = $decoded['content'][0];
@ -519,7 +522,7 @@ class File_ASN1 {
} }
$inmap = $this->ANYmap[$intype]; $inmap = $this->ANYmap[$intype];
if (is_string($inmap)) { if (is_string($inmap)) {
return array($inmap => $this->asn1map($decoded, array('type' => $intype) + $mapping)); return array($inmap => $this->asn1map($decoded, array('type' => $intype) + $mapping, $special));
} }
break; break;
case $mapping['type'] == FILE_ASN1_TYPE_CHOICE: case $mapping['type'] == FILE_ASN1_TYPE_CHOICE:
@ -527,15 +530,18 @@ class File_ASN1 {
switch (true) { switch (true) {
case isset($option['constant']) && $option['constant'] == $decoded['constant']: case isset($option['constant']) && $option['constant'] == $decoded['constant']:
case !isset($option['constant']) && $option['type'] == $decoded['type']: case !isset($option['constant']) && $option['type'] == $decoded['type']:
$value = $this->asn1map($decoded, $option); $value = $this->asn1map($decoded, $option, $special);
break; break;
case !isset($option['constant']) && $option['type'] == FILE_ASN1_TYPE_CHOICE: case !isset($option['constant']) && $option['type'] == FILE_ASN1_TYPE_CHOICE:
$v = $this->asn1map($decoded, $option); $v = $this->asn1map($decoded, $option, $special);
if (isset($v)) { if (isset($v)) {
$value = $v; $value = $v;
} }
} }
if (isset($value)) { if (isset($value)) {
if (isset($special[$key])) {
$value = $special[$key]($value);
}
return array($key => $value); return array($key => $value);
} }
} }
@ -560,7 +566,7 @@ class File_ASN1 {
if (isset($mapping['min']) && isset($mapping['max'])) { if (isset($mapping['min']) && isset($mapping['max'])) {
$child = $mapping['children']; $child = $mapping['children'];
foreach ($decoded['content'] as $content) { foreach ($decoded['content'] as $content) {
if (($map[] = $this->asn1map($content, $child)) === NULL) { if (($map[] = $this->asn1map($content, $child, $special)) === NULL) {
return NULL; return NULL;
} }
} }
@ -604,12 +610,15 @@ class File_ASN1 {
if ($maymatch) { if ($maymatch) {
// Attempt submapping. // Attempt submapping.
$candidate = $this->asn1map($temp, $child); $candidate = $this->asn1map($temp, $child, $special);
$maymatch = $candidate !== NULL; $maymatch = $candidate !== NULL;
} }
if ($maymatch) { if ($maymatch) {
// Got the match: use it. // Got the match: use it.
if (isset($special[$key])) {
$candidate = $special[$key]($candidate);
}
$map[$key] = $candidate; $map[$key] = $candidate;
$i++; $i++;
} elseif (isset($child['default'])) { } elseif (isset($child['default'])) {
@ -630,7 +639,7 @@ class File_ASN1 {
if (isset($mapping['min']) && isset($mapping['max'])) { if (isset($mapping['min']) && isset($mapping['max'])) {
$child = $mapping['children']; $child = $mapping['children'];
foreach ($decoded['content'] as $content) { foreach ($decoded['content'] as $content) {
if (($map[] = $this->asn1map($content, $child)) === NULL) { if (($map[] = $this->asn1map($content, $child, $special)) === NULL) {
return NULL; return NULL;
} }
} }
@ -673,7 +682,7 @@ class File_ASN1 {
if ($maymatch) { if ($maymatch) {
// Attempt submapping. // Attempt submapping.
$candidate = $this->asn1map($temp, $child); $candidate = $this->asn1map($temp, $child, $special);
$maymatch = $candidate !== NULL; $maymatch = $candidate !== NULL;
} }
@ -682,6 +691,9 @@ class File_ASN1 {
} }
// Got the match: use it. // Got the match: use it.
if (isset($special[$key])) {
$candidate = $special[$key]($candidate);
}
$map[$key] = $candidate; $map[$key] = $candidate;
break; break;
} }
@ -774,18 +786,30 @@ class File_ASN1 {
* DER-encodes an ASN.1 semantic mapping ($mapping). Some libraries would probably call this function * DER-encodes an ASN.1 semantic mapping ($mapping). Some libraries would probably call this function
* an ASN.1 compiler. * an ASN.1 compiler.
* *
* "Special" mappings can be applied via $special.
*
* @param String $source * @param String $source
* @param String $mapping * @param String $mapping
* @param Integer $idx * @param Integer $idx
* @return String * @return String
* @access public * @access public
*/ */
function encodeDER($source, $mapping) function encodeDER($source, $mapping, $special = array())
{ {
$this->location = array(); $this->location = array();
return $this->_encode_der($source, $mapping); return $this->_encode_der($source, $mapping, NULL, $special);
} }
/**
* ASN.1 Encode (Helper function)
*
* @param String $source
* @param Array $mapping
* @param Integer $idx
* @param Array $special
* @return String
* @access private
*/
/** /**
* ASN.1 Encode (Helper function) * ASN.1 Encode (Helper function)
* *
@ -795,7 +819,7 @@ class File_ASN1 {
* @return String * @return String
* @access private * @access private
*/ */
function _encode_der($source, $mapping, $idx = NULL) function _encode_der($source, $mapping, $idx = NULL, $special = array())
{ {
if (is_object($source) && strtolower(get_class($source)) == 'file_asn1_element') { if (is_object($source) && strtolower(get_class($source)) == 'file_asn1_element') {
return $source->element; return $source->element;
@ -807,6 +831,9 @@ class File_ASN1 {
} }
if (isset($idx)) { if (isset($idx)) {
if (isset($special[$idx])) {
$source = $special[$idx]($source);
}
$this->location[] = $idx; $this->location[] = $idx;
} }
@ -823,7 +850,7 @@ class File_ASN1 {
$child = $mapping['children']; $child = $mapping['children'];
foreach ($source as $content) { foreach ($source as $content) {
$temp = $this->_encode_der($content, $child); $temp = $this->_encode_der($content, $child, NULL, $special);
if ($temp === false) { if ($temp === false) {
return false; return false;
} }
@ -840,7 +867,7 @@ class File_ASN1 {
continue; continue;
} }
$temp = $this->_encode_der($source[$key], $child, $key); $temp = $this->_encode_der($source[$key], $child, $key, $special);
if ($temp === false) { if ($temp === false) {
return false; return false;
} }
@ -881,7 +908,7 @@ class File_ASN1 {
continue; continue;
} }
$temp = $this->_encode_der($source[$key], $child, $key); $temp = $this->_encode_der($source[$key], $child, $key, $special);
if ($temp === false) { if ($temp === false) {
return false; return false;
} }
@ -1003,19 +1030,19 @@ class File_ASN1 {
switch (true) { switch (true) {
case !isset($source): case !isset($source):
return $this->_encode_der(NULL, array('type' => FILE_ASN1_TYPE_NULL) + $mapping); return $this->_encode_der(NULL, array('type' => FILE_ASN1_TYPE_NULL) + $mapping, NULL, $special);
case is_int($source): case is_int($source):
case is_object($source) && strtolower(get_class($source)) == 'math_biginteger': case is_object($source) && strtolower(get_class($source)) == 'math_biginteger':
return $this->_encode_der($source, array('type' => FILE_ASN1_TYPE_INTEGER) + $mapping); return $this->_encode_der($source, array('type' => FILE_ASN1_TYPE_INTEGER) + $mapping, NULL, $special);
case is_float($source): case is_float($source):
return $this->_encode_der($source, array('type' => FILE_ASN1_TYPE_REAL) + $mapping); return $this->_encode_der($source, array('type' => FILE_ASN1_TYPE_REAL) + $mapping, NULL, $special);
case is_bool($source): case is_bool($source):
return $this->_encode_der($source, array('type' => FILE_ASN1_TYPE_BOOLEAN) + $mapping); return $this->_encode_der($source, array('type' => FILE_ASN1_TYPE_BOOLEAN) + $mapping, NULL, $special);
case is_array($source) && count($source) == 1: case is_array($source) && count($source) == 1:
$typename = implode('', array_keys($source)); $typename = implode('', array_keys($source));
$outtype = array_search($typename, $this->ANYmap, true); $outtype = array_search($typename, $this->ANYmap, true);
if ($outtype !== false) { if ($outtype !== false) {
return $this->_encode_der($source[$typename], array('type' => $outtype) + $mapping); return $this->_encode_der($source[$typename], array('type' => $outtype) + $mapping, NULL, $special);
} }
} }
@ -1031,7 +1058,7 @@ class File_ASN1 {
user_error('No filters defined for ' . implode('/', $loc)); user_error('No filters defined for ' . implode('/', $loc));
return false; return false;
} }
return $this->_encode_der($source, $filters + $mapping); return $this->_encode_der($source, $filters + $mapping, NULL, $special);
case FILE_ASN1_TYPE_NULL: case FILE_ASN1_TYPE_NULL:
$value = ''; $value = '';
break; break;

View File

@ -1562,7 +1562,7 @@ class File_X509 {
corresponding to the extension type identified by extnID */ corresponding to the extension type identified by extnID */
$map = $this->_getMapping($id); $map = $this->_getMapping($id);
if (!is_bool($map)) { if (!is_bool($map)) {
$mapped = $asn1->asn1map($decoded[0], $map); $mapped = $asn1->asn1map($decoded[0], $map, array('iPAddress' => array($this, '_decodeIP')));
$value = $mapped === false ? $decoded[0] : $mapped; $value = $mapped === false ? $decoded[0] : $mapped;
if ($id == 'id-ce-certificatePolicies') { if ($id == 'id-ce-certificatePolicies') {
@ -1644,7 +1644,7 @@ class File_X509 {
unset($extensions[$i]); unset($extensions[$i]);
} }
} else { } else {
$temp = $asn1->encodeDER($value, $map); $temp = $asn1->encodeDER($value, $map, array('iPAddress' => array($this, '_encodeIP')));
$value = base64_encode($temp); $value = base64_encode($temp);
} }
} }
@ -2178,6 +2178,36 @@ class File_X509 {
} }
} }
/**
* Decodes an IP address
*
* Takes in a base64 encoded "blob" and returns a human readable IP address
*
* @param String $ip
* @access private
* @return String
*/
function _decodeIP($ip)
{
$ip = base64_decode($ip);
list(, $ip) = unpack('N', $ip);
return long2ip($ip);
}
/**
* Encodes an IP address
*
* Takes a human readable IP address into a base64-encoded "blob"
*
* @param String $ip
* @access private
* @return String
*/
function _encodeIP($ip)
{
return base64_encode(pack('N', ip2long($ip)));
}
/** /**
* "Normalizes" a Distinguished Name property * "Normalizes" a Distinguished Name property
* *
@ -3203,6 +3233,21 @@ class File_X509 {
array_map(array('File_X509', '_dnsName'), $subject->domains)); array_map(array('File_X509', '_dnsName'), $subject->domains));
} }
if (isset($subject->ipAddresses) && count($subject->ipAddresses)) {
// should an IP address appear as the CN if no domain name is specified? idk
//$ips = count($subject->domains) ? $subject->ipAddresses : array_slice($subject->ipAddresses, 1);
$ipAddresses = array();
foreach ($subject->ipAddresses as $ipAddress) {
$encoded = $subject->_ipAddress($ipAddress);
if ($encoded !== false) {
$ipAddresses[] = $encoded;
}
}
if (count($ipAddresses)) {
$this->setExtension('id-ce-subjectAltName', $ipAddresses);
}
}
if ($this->caFlag) { if ($this->caFlag) {
$keyUsage = $this->getExtension('id-ce-keyUsage'); $keyUsage = $this->getExtension('id-ce-keyUsage');
if (!$keyUsage) { if (!$keyUsage) {
@ -4098,6 +4143,23 @@ class File_X509 {
$this->setDNProp('id-at-commonName', $this->domains[0]); $this->setDNProp('id-at-commonName', $this->domains[0]);
} }
/**
* Set the IP Addresses's which the cert is to be valid for
*
* @access public
* @param String $ipAddress optional
*/
function setIPAddress()
{
$this->ipAddresses = func_get_args();
/*
if (!isset($this->domains)) {
$this->removeDNProp('id-at-commonName');
$this->setDNProp('id-at-commonName', $this->ipAddresses[0]);
}
*/
}
/** /**
* Helper function to build domain array * Helper function to build domain array
* *
@ -4110,6 +4172,20 @@ class File_X509 {
return array('dNSName' => $domain); return array('dNSName' => $domain);
} }
/**
* Helper function to build IP Address array
*
* (IPv6 is not currently supported)
*
* @access private
* @param String $address
* @return Array
*/
function _iPAddress($address)
{
return array('iPAddress' => $address);
}
/** /**
* Get the index of a revoked certificate. * Get the index of a revoked certificate.
* *