From 9824bac2ea71404e673d11fafbfd37f9a44dccc8 Mon Sep 17 00:00:00 2001 From: PiBa-NL Date: Thu, 21 Nov 2013 23:14:30 +0100 Subject: haproxy-devel -better IPv6 support -use certificate chains where available -new interface selections to listen on instead of only wan,VIPs,any,local -option to recalculate certificate chain links -show shared frontend option only when another primary frontend is present --- config/haproxy-devel/haproxy.inc | 80 ++---- config/haproxy-devel/haproxy.xml | 10 + config/haproxy-devel/haproxy_global.php | 18 ++ config/haproxy-devel/haproxy_listeners.php | 7 +- config/haproxy-devel/haproxy_listeners_edit.php | 29 +- config/haproxy-devel/haproxy_pool_edit.php | 21 +- config/haproxy-devel/haproxy_utils.inc | 244 ++++++++++++++++ config/haproxy-devel/isCertSigner.inc | 361 ++++++++++++++++++++++++ 8 files changed, 676 insertions(+), 94 deletions(-) create mode 100644 config/haproxy-devel/haproxy_utils.inc create mode 100644 config/haproxy-devel/isCertSigner.inc (limited to 'config/haproxy-devel') diff --git a/config/haproxy-devel/haproxy.inc b/config/haproxy-devel/haproxy.inc index 912f1fb3..4da961de 100644 --- a/config/haproxy-devel/haproxy.inc +++ b/config/haproxy-devel/haproxy.inc @@ -31,6 +31,7 @@ require_once("functions.inc"); require_once("pkg-utils.inc"); require_once("notices.inc"); +require_once("haproxy_utils.inc"); require_once("haproxy_xmlrpcsyncclient.inc"); $d_haproxyconfdirty_path = $g['varrun_path'] . "/haproxy.conf.dirty"; @@ -107,7 +108,7 @@ $a_closetypes['forceclose'] = array('name' => 'forceclose', 'syntax' => 'forcecl function haproxy_custom_php_deinstall_command() { exec("cd /var/db/pkg && pkg_delete `ls | grep haproxy`"); - exec("rm /usr/local/pkg/haproxy.inc"); + exec("rm /usr/local/pkg/haproxy*"); exec("rm /usr/local/www/haproxy*"); exec("rm /usr/local/etc/rc.d/haproxy.sh"); exec("rm /etc/devd/haproxy.conf"); @@ -592,6 +593,13 @@ function haproxy_writeconf($configfile) { $ssl_crt=" crt /var/etc/{$backend['name']}.{$backend['port']}.crt"; $cert = lookup_cert($backend['ssloffloadcert']); $certcontent = base64_decode($cert['crt'])."\r\n".base64_decode($cert['prv']); + + $certchaincontent = ca_chain($cert); + if ($certchaincontent != "") { + $certcontent .= "\r\n" . $certchaincontent; + } + unset($certchaincontent); + file_put_contents("/var/etc/{$backend['name']}.{$backend['port']}.crt", $certcontent); unset($certcontent); }else{ @@ -657,14 +665,12 @@ function haproxy_writeconf($configfile) { $listenip = ""; // Process and add bind directives for ports - foreach($ports as $port) { - if($port) { - if($bind['extaddr'] == "any") - $listenip .= "\tbind\t\t\t0.0.0.0:{$port} {$ssl_info} {$advanced_bind}\n"; - elseif($bind['extaddr']) - $listenip .= "\tbind\t\t\t{$bind['extaddr']}:{$port} {$ssl_info} {$advanced_bind}\n"; - else - $listenip .= "\tbind\t\t\t" . get_current_wan_address('wan') . ":{$port} {$ssl_info} {$advanced_bind}\n"; + $ip = haproxy_interface_ip($bind['extaddr']); + if ($ip){ + foreach($ports as $port) { + if($port) { + $listenip .= "\tbind\t\t\t$ip:{$port} {$ssl_info} {$advanced_bind}\n"; + } } } @@ -1039,16 +1045,11 @@ function get_primaryfrontend($frontend) { return $mainfrontend; } -function get_frontend_ipport($frontend) { +function get_frontend_ipport($frontend,$userfriendly=false) { $mainfrontend = get_primaryfrontend($frontend); - if($mainfrontend['extaddr'] == "any") - $result = "0.0.0.0"; - elseif ($mainfrontend['extaddr'] == "localhost") - $result = "127.0.0.1"; - elseif($mainfrontend['extaddr']) - $result = $mainfrontend['extaddr']; - else - $result = get_current_wan_address('wan'); + $result = haproxy_interface_ip($mainfrontend['extaddr'],$userfriendly); + if ($userfriendly and is_ipaddrv6($result)) + $result = "[{$result}]"; return $result . ":" . $mainfrontend['port']; } @@ -1099,7 +1100,7 @@ function get_haproxy_frontends($excludeitem="") { return $result; } -function get_frontent_acls($frontend) { +function get_frontend_acls($frontend) { $result = array(); $a_acl = &$frontend['ha_acls']['item']; if (is_array($a_acl)) @@ -1123,51 +1124,10 @@ function get_frontent_acls($frontend) { return $result; } -function phparray_to_javascriptarray_recursive($nestID, $path, $items, $nodeName, $includeitems) { - $offset = str_repeat(' ',$nestID); - $itemName = "item$nestID"; - echo "{$offset}$nodeName = {};\n"; - if (is_array($items)) - foreach ($items as $key => $item) - { - if (in_array($path.'/'.$key, $includeitems)) - $subpath = $path.'/'.$key; - else - $subpath = $path.'/*'; - if (in_array($subpath, $includeitems) || in_array($path.'/*', $includeitems)) { - if (is_array($item)) { - $subNodeName = "item$nestID"; - phparray_to_javascriptarray_recursive($nestID+1, $subpath, $items[$key], $subNodeName, $includeitems); - echo "{$offset}{$nodeName}['{$key}'] = $itemName;\n"; - } else - echo "{$offset}{$nodeName}['$key'] = '$item';\n"; - } - } -} - -function phparray_to_javascriptarray($items, $javaMapName, $includeitems) { - phparray_to_javascriptarray_recursive(1,'',$items, $javaMapName, $includeitems); -} - function haproxy_escapestring($configurationsting) { $result = str_replace('\\', '\\\\', $configurationsting); $result = str_replace(' ', '\\ ', $result); return str_replace('#', '\\#', $result); } -function echo_html_select($name, $keyvaluelist, $selected, $listEmptyMessage="", $onchangeEvent="") { - if (count($keyvaluelist)>0){ - if ($onchangeEvent != "") - $onchangeEvent .= " onchange=$onchangeEvent"; - echo ""; - } else { - echo $listEmptyMessage; - } -} - ?> diff --git a/config/haproxy-devel/haproxy.xml b/config/haproxy-devel/haproxy.xml index bfd7f437..f7bac704 100644 --- a/config/haproxy-devel/haproxy.xml +++ b/config/haproxy-devel/haproxy.xml @@ -99,6 +99,16 @@ 077 http://www.pfsense.com/packages/config/haproxy-devel/haproxy_xmlrpcsyncclient.inc + + /usr/local/pkg/ + 077 + http://www.pfsense.com/packages/config/haproxy-devel/isCertSigner.inc + + + /usr/local/pkg/ + 077 + http://www.pfsense.com/packages/config/haproxy-devel/haproxy_utils.inc + /usr/local/www/widgets/widgets/ 077 diff --git a/config/haproxy-devel/haproxy_global.php b/config/haproxy-devel/haproxy_global.php index ff8d1280..0ff0e10e 100755 --- a/config/haproxy-devel/haproxy_global.php +++ b/config/haproxy-devel/haproxy_global.php @@ -31,6 +31,7 @@ require_once("guiconfig.inc"); require_once("haproxy.inc"); +require_once("haproxy_utils.inc"); require_once("globals.inc"); if (!is_array($config['installedpackages']['haproxy'])) @@ -41,6 +42,9 @@ if ($_POST) { unset($input_errors); $pconfig = $_POST; + if ($_POST['calculate_certificate_chain']) { + haproxy_recalculate_certifcate_chain(); + } else if ($_POST['apply']) { $result = haproxy_check_and_run($savemsg, true); if ($result) @@ -153,6 +157,20 @@ function enable_change(enable_change) {
+ + + + + + + + diff --git a/config/haproxy-devel/haproxy_listeners.php b/config/haproxy-devel/haproxy_listeners.php index 6d9c9dc1..397cef5c 100644 --- a/config/haproxy-devel/haproxy_listeners.php +++ b/config/haproxy-devel/haproxy_listeners.php @@ -32,6 +32,7 @@ require_once("guiconfig.inc"); require_once("haproxy.inc"); require_once("certs.inc"); +require_once("haproxy_utils.inc"); if (!is_array($config['installedpackages']['haproxy']['ha_backends']['item'])) { $config['installedpackages']['haproxy']['ha_backends']['item'] = array(); @@ -125,7 +126,7 @@ include("head.inc"); $a_frontend_grouped = array(); foreach($a_frontend as &$frontend2) { - $ipport = get_frontend_ipport($frontend2); + $ipport = get_frontend_ipport($frontend2, true); $frontend2['ipport'] = $ipport; $a_frontend_grouped[$ipport][] = $frontend2; } @@ -156,7 +157,7 @@ include("head.inc"); echo 'SSL offloading'; } - $acls = get_frontent_acls($frontend); + $acls = get_frontend_acls($frontend); $isaclset = ""; foreach ($acls as $acl) { $isaclset .= " " . $acl['descr']; @@ -179,7 +180,7 @@ include("head.inc"); - + @@ -548,22 +556,9 @@ include("head.inc"); - + @@ -534,10 +535,7 @@ foreach($simplefields as $field){ NOTE: paste text into this box that you would like to pass thru. Applied to the backend section. - -
Recalculate certificate chain.
  + + +
+ This can be required after certificates have been created or imported. As pfSense 2.1.0 currently does not + always keep track of these dependencies which might be required to create a proper certificate chain when using SSLoffloading. +
General settings
- + diff --git a/config/haproxy-devel/haproxy_listeners_edit.php b/config/haproxy-devel/haproxy_listeners_edit.php index d37444c0..98fba74a 100644 --- a/config/haproxy-devel/haproxy_listeners_edit.php +++ b/config/haproxy-devel/haproxy_listeners_edit.php @@ -32,6 +32,7 @@ require("guiconfig.inc"); require_once("haproxy.inc"); +require_once("haproxy_utils.inc"); /* Compatibility function for pfSense 2.0 */ if (!function_exists("cert_get_purpose")) { @@ -274,6 +275,9 @@ if (!$id) $pgtitle = "HAProxy: Frontend: Edit"; include("head.inc"); + +$primaryfrontends = get_haproxy_frontends($pconfig['name']); +$interfaces = haproxy_get_bindable_interfaces(); ?> @@ -526,11 +530,16 @@ include("head.inc");
Shared Frontend + + At least 1 primary frontend is needed.

+ onclick="updatevisibility();"/> + + This can be used to host a second or more website on the same IP:Port combination.
Use this setting to configure multiple backends/accesslists for a single frontend.
All settings of which only 1 can exist will be hidden.
The frontend settings will be merged into 1 set of frontend configuration. @@ -540,7 +549,6 @@ include("head.inc");
Primary frontend
External address -
If you want this rule to apply to another IP address than the IP address of the interface chosen above, diff --git a/config/haproxy-devel/haproxy_pool_edit.php b/config/haproxy-devel/haproxy_pool_edit.php index a7a56b1c..92f4177e 100644 --- a/config/haproxy-devel/haproxy_pool_edit.php +++ b/config/haproxy-devel/haproxy_pool_edit.php @@ -31,6 +31,7 @@ require("guiconfig.inc"); require_once("haproxy.inc"); +require_once("haproxy_utils.inc"); $d_haproxyconfdirty_path = $g['varrun_path'] . "/haproxy.conf.dirty"; @@ -137,7 +138,7 @@ if ($_POST) { if (preg_match("/[^a-zA-Z0-9\.\-_]/", $server_name)) $input_errors[] = "The field 'Name' contains invalid characters."; - if (preg_match("/[^a-zA-Z0-9\.\-_]/", $server_address)) + if (!is_ipaddr($server_address)) $input_errors[] = "The field 'Address' contains invalid characters."; if (!preg_match("/.{2,}/", $server_name)) @@ -350,7 +351,7 @@ foreach($simplefields as $field){
Port SSL WeightBackupMode Advanced
-
- + @@ -611,9 +609,7 @@ FLUSH PRIVILEGES; Fill in the TCP portnumber the healthcheck should be performed on. -
 
Health checking
-
- + @@ -641,9 +637,7 @@ want the clients to see the failures. The number of attempts to reconnect is set by the 'retries' parameter. -
 
Advanced settings
-
 
- + @@ -719,8 +713,7 @@ set by the 'retries' parameter. Specify the refresh rate of the stats page in seconds, or specified time unit (us, ms, s, m, h, d). -
 
Statistics
- +
 
  diff --git a/config/haproxy-devel/haproxy_utils.inc b/config/haproxy-devel/haproxy_utils.inc new file mode 100644 index 00000000..e826f530 --- /dev/null +++ b/config/haproxy-devel/haproxy_utils.inc @@ -0,0 +1,244 @@ + $ifdetail) { + if (!isset($ifdetail['enable'])) + continue; + if (!isset($ifdetail['ipaddr'])) + continue; + $item = array(); + $item[ip] = get_interface_ip($if); + $item[name] = $ifdetail['descr'].' address (IPv4)'; + $bindable[$if.'_ipv4'] = $item; + } + } + if (in_array('carp',$interfacetypes)){ + $carplist = get_configured_carp_interface_list(); + foreach ($carplist as $carpif => $carpip){ + if (is_ipaddrv4($carpip)){ + $item = array(); + $item['ip'] = $carpip; + $item['name'] = $carpip." (".get_vip_descr($carpip).")"; + $bindable[$carpip] = $item; + } + } + + } + if (in_array('ipalias',$interfacetypes)){ + $aliaslist = get_configured_ip_aliases_list(); + foreach ($aliaslist as $aliasip => $aliasif){ + if (is_ipaddrv4($aliasip)){ + $item = array(); + $item['ip'] = $aliasip; + $item['name'] = $aliasip." (".get_vip_descr($aliasip).")"; + $bindable[$aliasip.'_ipv4'] = $item; + } + } + } + } + if (in_array("ipv6",$ipverions)){ + if (in_array('any',$interfacetypes)){ + $item = array(); + $item[ip] = '::'; + $item[name] = 'any (IPv6)'; + $bindable['any_ipv6'] = $item; + } + if (in_array('localhost',$interfacetypes)){ + $item = array(); + $item[ip] = '::1'; + $item[name] = 'localhost (IPv6)'; + $bindable['localhost_ipv6'] = $item; + } + if (in_array('real',$interfacetypes)){ + foreach($config['interfaces'] as $if => $ifdetail) { + if (!isset($ifdetail['enable'])) + continue; + if (!isset($ifdetail['ipaddrv6'])) + continue; + $item = array(); + $item[ip] = get_interface_ipv6($if); + $item[name] = $ifdetail['descr'].' address (IPv6)'; + $bindable[$if.'_ipv6'] = $item; + } + } + if (in_array('carp',$interfacetypes)){ + $carplist = get_configured_carp_interface_list(); + foreach ($carplist as $carpif => $carpip){ + if (is_ipaddrv6($carpip)){ + $item = array(); + $item['ip'] = $carpip; + $item['name'] = $carpip." (".get_vip_descr($carpip).")"; + $bindable[$carpip] = $item; + } + } + + } + if (in_array('ipalias',$interfacetypes)){ + $aliaslist = get_configured_ip_aliases_list(); + foreach ($aliaslist as $aliasip => $aliasif){ + if (is_ipaddrv6($aliasip)){ + $item = array(); + $item['ip'] = $aliasip; + $item['name'] = $aliasip." (".get_vip_descr($aliasip).")"; + $bindable[$aliasip] = $item; + } + } + } + } + return $bindable; +} + +function haproxy_cert_signed_by($cert, $signedbycert) { + // uses function isCertSigner(a,b) from isCertSigner.inc to check if $cert was signed by $signedbycert + // returns true if it is + return isCertSigner(base64_decode($cert['crt']), base64_decode($signedbycert['crt'])); +} +function haproxy_get_certificates(){ + global $config; + $allcerts = array(); + foreach($config['cert'] as &$cert) + $allcerts[] = &$cert; + foreach($config['ca'] as &$cert) + $allcerts[] = &$cert; + return $allcerts; +} +function haproxy_recalculate_certifcate_chain(){ + // and set "selfsigned" for certificates that where used to sign themselves + // recalculate the "caref" for all certificates where it is currently unkown. + + $allcertificates = haproxy_get_certificates(); + $items_recalculated = 0; + foreach($allcertificates as &$cert){ + $recalculate=false; + if (!isset($cert['selfsigned'])){ + if (!isset($cert['caref'])) + $recalculate=true; + else { + $ca = lookup_ca($cert['caref']); + if (!$ca) + $recalculate=true; + } + } + if ($recalculate){ + foreach($allcertificates as &$signedbycert){ + if(haproxy_cert_signed_by($cert, $signedbycert)){ + if ($cert['refid'] == $signedbycert['refid']){ + $cert['selfsigned'] = true; + } else { + $cert['caref'] = $signedbycert['refid']; + } + $items_recalculated++; + } + } + } + } + if ($items_recalculated > 0) + write_config("Recalculated $items_recalculated certificate chains."); +} + +function phparray_to_javascriptarray_recursive($nestID, $path, $items, $nodeName, $includeitems) { + $offset = str_repeat(' ',$nestID); + $itemName = "item$nestID"; + echo "{$offset}$nodeName = {};\n"; + if (is_array($items)) + foreach ($items as $key => $item) + { + if (in_array($path.'/'.$key, $includeitems)) + $subpath = $path.'/'.$key; + else + $subpath = $path.'/*'; + if (in_array($subpath, $includeitems) || in_array($path.'/*', $includeitems)) { + if (is_array($item)) { + $subNodeName = "item$nestID"; + phparray_to_javascriptarray_recursive($nestID+1, $subpath, $items[$key], $subNodeName, $includeitems); + echo "{$offset}{$nodeName}['{$key}'] = $itemName;\n"; + } else + echo "{$offset}{$nodeName}['$key'] = '$item';\n"; + } + } +} +function phparray_to_javascriptarray($items, $javaMapName, $includeitems) { + phparray_to_javascriptarray_recursive(1,'',$items, $javaMapName, $includeitems); +} + +function echo_html_select($name, $keyvaluelist, $selected, $listEmptyMessage="", $onchangeEvent="") { + if (count($keyvaluelist)>0){ + if ($onchangeEvent != "") + $onchangeEvent .= " onchange=$onchangeEvent"; + echo ""; + } else { + echo $listEmptyMessage; + } +} + +?> \ No newline at end of file diff --git a/config/haproxy-devel/isCertSigner.inc b/config/haproxy-devel/isCertSigner.inc new file mode 100644 index 00000000..d964129f --- /dev/null +++ b/config/haproxy-devel/isCertSigner.inc @@ -0,0 +1,361 @@ + + * @copyright Copyright (c) 2010, Mike Green + * @license http://opensource.org/licenses/gpl-2.0.php GPLv2 + */ + +/** + * If viewSource is in the request string, show the source, luke. + */ +if (isset($_REQUEST['viewSource'])) { + die(highlight_file(__FILE__)); + } + +/** + * Extract signature from der encoded cert. + * Expects x509 der encoded certificate consisting of a section container + * containing 2 sections and a bitstream. The bitstream contains the + * original encrypted signature, encrypted by the public key of the issuing + * signer. + * @param string $der + * @return string on success + * @return bool false on failures + */ +function extractSignature($der=false) { + if (strlen($der) < 5) { return false; } + // skip container sequence + $der = substr($der,4); + // now burn through two sequences and the return the final bitstream + while(strlen($der) > 1) { + $class = ord($der[0]); + $classHex = dechex($class); + switch($class) { + // BITSTREAM + case 0x03: + $len = ord($der[1]); + $bytes = 0; + if ($len & 0x80) { + $bytes = $len & 0x0f; + $len = 0; + for ($i = 0; $i < $bytes; $i++) { + $len = ($len << 8) | ord($der[$i + 2]); + } + } + return substr($der,3 + $bytes, $len); + break; + // SEQUENCE + case 0x30: + $len = ord($der[1]); + $bytes = 0; + if ($len & 0x80) { + $bytes = $len & 0x0f; + $len = 0; + for($i = 0; $i < $bytes; $i++) { + $len = ($len << 8) | ord($der[$i + 2]); + } + } + $contents = substr($der, 2 + $bytes, $len); + $der = substr($der,2 + $bytes + $len); + break; + default: + return false; + break; + } + } + return false; + } + +/** + * Get signature algorithm oid from der encoded signature data. + * Expects decrypted signature data from a certificate in der format. + * This ASN1 data should contain the following structure: + * SEQUENCE + * SEQUENCE + * OID (signature algorithm) + * NULL + * OCTET STRING (signature hash) + * @return bool false on failures + * @return string oid + */ +function getSignatureAlgorithmOid($der=null) { + // Validate this is the der we need... + if (!is_string($der) or strlen($der) < 5) { return false; } + $bit_seq1 = 0; + $bit_seq2 = 2; + $bit_oid = 4; + if (ord($der[$bit_seq1]) !== 0x30) { + die('Invalid DER passed to getSignatureAlgorithmOid()'); + } + if (ord($der[$bit_seq2]) !== 0x30) { + die('Invalid DER passed to getSignatureAlgorithmOid()'); + } + if (ord($der[$bit_oid]) !== 0x06) { + die('Invalid DER passed to getSignatureAlgorithmOid'); + } + // strip out what we don't need and get the oid + $der = substr($der,$bit_oid); + // Get the oid + $len = ord($der[1]); + $bytes = 0; + if ($len & 0x80) { + $bytes = $len & 0x0f; + $len = 0; + for ($i = 0; $i < $bytes; $i++) { + $len = ($len << 8) | ord($der[$i + 2]); + } + } + $oid_data = substr($der, 2 + $bytes, $len); + // Unpack the OID + $oid = floor(ord($oid_data[0]) / 40); + $oid .= '.' . ord($oid_data[0]) % 40; + $value = 0; + $i = 1; + while ($i < strlen($oid_data)) { + $value = $value << 7; + $value = $value | (ord($oid_data[$i]) & 0x7f); + if (!(ord($oid_data[$i]) & 0x80)) { + $oid .= '.' . $value; + $value = 0; + } + $i++; + } + return $oid; + } + +/** + * Get signature hash from der encoded signature data. + * Expects decrypted signature data from a certificate in der format. + * This ASN1 data should contain the following structure: + * SEQUENCE + * SEQUENCE + * OID (signature algorithm) + * NULL + * OCTET STRING (signature hash) + * @return bool false on failures + * @return string hash + */ +function getSignatureHash($der=null) { + // Validate this is the der we need... + if (!is_string($der) or strlen($der) < 5) { return false; } + if (ord($der[0]) !== 0x30) { + die('Invalid DER passed to getSignatureHash()'); + } + // strip out the container sequence + $der = substr($der,2); + if (ord($der[0]) !== 0x30) { + die('Invalid DER passed to getSignatureHash()'); + } + // Get the length of the first sequence so we can strip it out. + $len = ord($der[1]); + $bytes = 0; + if ($len & 0x80) { + $bytes = $len & 0x0f; + $len = 0; + for ($i = 0; $i < $bytes; $i++) { + $len = ($len << 8) | ord($der[$i + 2]); + } + } + $der = substr($der, 2 + $bytes + $len); + // Now we should have an octet string + if (ord($der[0]) !== 0x04) { + die('Invalid DER passed to getSignatureHash()'); + } + $len = ord($der[1]); + $bytes = 0; + if ($len & 0x80) { + $bytes = $len & 0x0f; + $len = 0; + for ($i = 0; $i < $bytes; $i++) { + $len = ($len << 8) | ord($der[$i + 2]); + } + } + return bin2hex(substr($der, 2 + $bytes, $len)); + } + +/** + * Determine if one cert was used to sign another + * Note that more than one CA cert can give a positive result, some certs + * re-issue signing certs after having only changed the expiration dates. + * @param string $cert - PEM encoded cert + * @param string $caCert - PEM encoded cert that possibly signed $cert + * @return bool + */ +function isCertSigner($certPem=null,$caCertPem=null) { + if (!function_exists('openssl_pkey_get_public')) { + die('Need the openssl_pkey_get_public() function.'); + } + if (!function_exists('openssl_public_decrypt')) { + die('Need the openssl_public_decrypt() function.'); + } + if (!function_exists('hash')) { + die('Need the php hash() function.'); + } + if (empty($certPem) or empty($caCertPem)) { return false; } + // Convert the cert to der for feeding to extractSignature. + $certDer = pemToDer($certPem); + if (!is_string($certDer)) { die('invalid certPem'); } + // Grab the encrypted signature from the der encoded cert. + $encryptedSig = extractSignature($certDer); + if (!is_string($encryptedSig)) { + die('Failed to extract encrypted signature from certPem.'); + } + // Extract the public key from the ca cert, which is what has + // been used to encrypt the signature in the cert. + $pubKey = openssl_pkey_get_public($caCertPem); + if ($pubKey === false) { + die('Failed to extract the public key from the ca cert.'); + } + // Attempt to decrypt the encrypted signature using the CA's public + // key, returning the decrypted signature in $decryptedSig. If + // it can't be decrypted, this ca was not used to sign it for sure... + $rc = openssl_public_decrypt($encryptedSig,$decryptedSig,$pubKey); + if ($rc === false) { return false; } + // We now have the decrypted signature, which is der encoded + // asn1 data containing the signature algorithm and signature hash. + // Now we need what was originally hashed by the issuer, which is + // the original DER encoded certificate without the issuer and + // signature information. + $origCert = stripSignerAsn($certDer); + if ($origCert === false) { + die('Failed to extract unsigned cert.'); + } + // Get the oid of the signature hash algorithm, which is required + // to generate our own hash of the original cert. This hash is + // what will be compared to the issuers hash. + $oid = getSignatureAlgorithmOid($decryptedSig); + if ($oid === false) { + die('Failed to determine the signature algorithm.'); + } + switch($oid) { + case '1.2.840.113549.2.2': $algo = 'md2'; break; + case '1.2.840.113549.2.4': $algo = 'md4'; break; + case '1.2.840.113549.2.5': $algo = 'md5'; break; + case '1.3.14.3.2.18': $algo = 'sha'; break; + case '1.3.14.3.2.26': $algo = 'sha1'; break; + case '2.16.840.1.101.3.4.2.1': $algo = 'sha256'; break; + case '2.16.840.1.101.3.4.2.2': $algo = 'sha384'; break; + case '2.16.840.1.101.3.4.2.3': $algo = 'sha512'; break; + default: + die('Unknown signature hash algorithm oid: ' . $oid); + break; + } + // Get the issuer generated hash from the decrypted signature. + $decryptedHash = getSignatureHash($decryptedSig); + // Ok, hash the original unsigned cert with the same algorithm + // and if it matches $decryptedHash we have a winner. + $certHash = hash($algo,$origCert); + return ($decryptedHash === $certHash); + } + +/** + * Convert pem encoded certificate to DER encoding + * @return string $derEncoded on success + * @return bool false on failures + */ +function pemToDer($pem=null) { + if (!is_string($pem)) { return false; } + $cert_split = preg_split('/(-----((BEGIN)|(END)) CERTIFICATE-----)/',$pem); + if (!isset($cert_split[1])) { return false; } + return base64_decode($cert_split[1]); + } + +/** + * Obtain der cert with issuer and signature sections stripped. + * @param string $der - der encoded certificate + * @return string $der on success + * @return bool false on failures. + */ +function stripSignerAsn($der=null) { + if (!is_string($der) or strlen($der) < 8) { return false; } + $bit = 4; + $len = ord($der[($bit + 1)]); + $bytes = 0; + if ($len & 0x80) { + $bytes = $len & 0x0f; + $len = 0; + for($i = 0; $i < $bytes; $i++) { + $len = ($len << 8) | ord($der[$bit + $i + 2]); + } + } + return substr($der,4,$len + 4); + } +/* +/ ** + * HTML form starts here... + * / + +$answer = 'Enter PEM Encoded Certificates for the Issuer and Subject ' + . 'and click Submit. Include the entire certificates, including ' + . 'the BEGIN CERTIFICATE and END CERTIFICATE lines.'; + +if (isset($_POST['subjectPem']) and isset($_POST['issuerPem'])) { + if (strlen($_POST['subjectPem']) > 0 and strlen($_POST['issuerPem']) > 0) { + $rc = isCertSigner($_POST['subjectPem'],$_POST['issuerPem']); + if ($rc === true) { + $answer = 'The issuer cert DID sign the subject cert.'; + } else { + $answer = 'The issuer cert DID NOT sign the subject cert.'; + } + } + } +?> + + + +Is This The Cert Signer? + + + +
+
+ + + + + + + + + + + + + + + + + + + +
Issuer Certificate (clear)
+ +
Subject Certificate (clear)
+ +
+ (view source) + +
+
+
+ + + +*/ +?> \ No newline at end of file -- cgit v1.2.3