diff options
author | PiBa-NL <pba_2k3@yahoo.com> | 2013-11-21 23:14:30 +0100 |
---|---|---|
committer | PiBa-NL <pba_2k3@yahoo.com> | 2013-11-21 23:14:30 +0100 |
commit | 9824bac2ea71404e673d11fafbfd37f9a44dccc8 (patch) | |
tree | f727fb1c6da67f957f6fb3ac9afc6b814625b540 | |
parent | 82f495970898105fba33c472a6bc29799e361755 (diff) | |
download | pfsense-packages-9824bac2ea71404e673d11fafbfd37f9a44dccc8.tar.gz pfsense-packages-9824bac2ea71404e673d11fafbfd37f9a44dccc8.tar.bz2 pfsense-packages-9824bac2ea71404e673d11fafbfd37f9a44dccc8.zip |
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
-rw-r--r-- | config/haproxy-devel/haproxy.inc | 80 | ||||
-rw-r--r-- | config/haproxy-devel/haproxy.xml | 10 | ||||
-rwxr-xr-x | config/haproxy-devel/haproxy_global.php | 18 | ||||
-rw-r--r-- | config/haproxy-devel/haproxy_listeners.php | 7 | ||||
-rw-r--r-- | config/haproxy-devel/haproxy_listeners_edit.php | 29 | ||||
-rw-r--r-- | config/haproxy-devel/haproxy_pool_edit.php | 21 | ||||
-rw-r--r-- | config/haproxy-devel/haproxy_utils.inc | 244 | ||||
-rw-r--r-- | config/haproxy-devel/isCertSigner.inc | 361 |
8 files changed, 676 insertions, 94 deletions
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 "<select name=\"$name\" id=\"$name\" class=\"formselect\"$onchangeEvent>"; - foreach($keyvaluelist as $key => $desc){ - $selectedhtml = $key == $selected ? "selected" : ""; - echo "<option value=\"{$key}\" {$selectedhtml}>{$desc['name']}</option>"; - } - echo "</select>"; - } 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 @@ -100,6 +100,16 @@ <item>http://www.pfsense.com/packages/config/haproxy-devel/haproxy_xmlrpcsyncclient.inc</item> </additional_files_needed> <additional_files_needed> + <prefix>/usr/local/pkg/</prefix> + <chmod>077</chmod> + <item>http://www.pfsense.com/packages/config/haproxy-devel/isCertSigner.inc</item> + </additional_files_needed> + <additional_files_needed> + <prefix>/usr/local/pkg/</prefix> + <chmod>077</chmod> + <item>http://www.pfsense.com/packages/config/haproxy-devel/haproxy_utils.inc</item> + </additional_files_needed> + <additional_files_needed> <prefix>/usr/local/www/widgets/widgets/</prefix> <chmod>077</chmod> <item>http://www.pfsense.com/packages/config/haproxy-devel/haproxy.widget.php</item> 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) @@ -154,6 +158,20 @@ function enable_change(enable_change) { <div id="mainarea"> <table class="tabcont" width="100%" border="0" cellpadding="6" cellspacing="0"> <tr> + <td colspan="2" valign="top" class="listtopic">Recalculate certificate chain.</td> + </tr> + <tr> + <td width="22%" valign="top" class="vncell"> </td> + <td width="78%" class="vtable"> + <input type="hidden" name="calculate_certificate_chain" id="calculate_certificate_chain"> + <input type="button" class="formbtn" value="Recalculate certificate chains" onclick="$('calculate_certificate_chain').value='true';document.iform.submit();"> + <br/> + 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. + </td> + </tr> + + <tr> <td colspan="2" valign="top" class="listtopic">General settings</td> </tr> <tr> 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 '<img src="'.$img_cert.'" title="SSL offloading cert: '.$cert['descr'].'" alt="SSL offloading" border="0" height="16" width="16" />'; } - $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"); <?=$frontend['desc'];?> </td> <td class="listlr" ondblclick="document.location='haproxy_listeners_edit.php?id=<?=$frontendname;?>';"> - <?=$frontend['ipport'];?> + <?=str_replace(" "," ",$frontend['ipport']);?> </td> <td class="listlr" ondblclick="document.location='haproxy_listeners_edit.php?id=<?=$frontendname;?>';"> <?=$frontend['type']?> 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(); ?> <body link="#0000CC" vlink="#0000CC" alink="#0000CC"> @@ -526,11 +530,16 @@ include("head.inc"); <option value="disabled"<?php if($pconfig['status'] == "disabled") echo " SELECTED"; ?>>Disabled</option> </select> </td> - </tr> + </tr> <tr align="left"> <td width="22%" valign="top" class="vncell">Shared Frontend</td> <td width="78%" class="vtable" colspan="2"> + <?if (count($primaryfrontends)==0){ ?> + <b>At least 1 primary frontend is needed.</b><br/><br/> + <? } else{ ?> <input id="secondary" name="secondary" type="checkbox" value="yes" <?php if ($pconfig['secondary']=='yes') echo "checked"; ?> onclick="updatevisibility();"/> + <? } ?> + This can be used to host a second or more website on the same IP:Port combination.<br/> Use this setting to configure multiple backends/accesslists for a single frontend.<br/> All settings of which only 1 can exist will be hidden.<br/> The frontend settings will be merged into 1 set of frontend configuration. @@ -540,7 +549,6 @@ include("head.inc"); <td width="22%" valign="top" class="vncellreq">Primary frontend</td> <td width="78%" class="vtable" colspan="2"> <? - $primaryfrontends = get_haproxy_frontends($pconfig['name']); echo_html_select('primary_frontend',$primaryfrontends, $pconfig['primary_frontend'],"You must first create a 'primary' frontend.","updatevisibility();"); ?> </td> @@ -548,22 +556,9 @@ include("head.inc"); <tr class="haproxy_primary"> <td width="22%" valign="top" class="vncellreq">External address</td> <td width="78%" class="vtable"> - <select name="extaddr" class="formfld"> - <option value="" <?php if (!$pconfig['extaddr']) echo "selected"; ?>>Interface address</option> - <option value="localhost" <?php if ('localhost' == $pconfig['extaddr']) echo "selected"; ?>>Localhost</option> - <?php - if (is_array($config['virtualip']['vip'])): - foreach ($config['virtualip']['vip'] as $sn): - ?> - <option value="<?=$sn['subnet'];?>" <?php if ($sn['subnet'] == $pconfig['extaddr']) echo "selected"; ?>> - <?=htmlspecialchars("{$sn['subnet']} ({$sn['descr']})");?> - </option> - <?php - endforeach; - endif; + <? + echo_html_select('extaddr', $interfaces, $pconfig['extaddr']); ?> - <option value="any" <?php if($pconfig['extaddr'] == "any") echo "selected"; ?>>any</option> - </select> <br /> <span class="vexpl"> 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){ <td width="5%" class="listhdrr">Port</td> <td width="5%" class="listhdrr">SSL</td> <td width="8%" class="listhdrr">Weight</td> - <td width="5%" class="listhdr">Backup</td> + <td width="5%" class="listhdrr">Mode</td> <td width="15%" class="listhdr">Advanced</td> <td width="4%" class=""></td> </tr> @@ -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. </td> </tr> - - </table> - <br/> - <table width="100%" border="0" cellpadding="6" cellspacing="0"> + <tr><td> </td></tr> <tr> <td colspan="2" valign="top" class="listtopic">Health checking</td> </tr> @@ -611,9 +609,7 @@ FLUSH PRIVILEGES;</pre> Fill in the TCP portnumber the healthcheck should be performed on. </td> </tr> - </table> - <br/> - <table width="100%" border="0" cellpadding="6" cellspacing="0"> + <tr><td> </td></tr> <tr> <td colspan="2" valign="top" class="listtopic">Advanced settings</td> </tr> @@ -641,9 +637,7 @@ want the clients to see the failures. The number of attempts to reconnect is set by the 'retries' parameter.</div> </td> </tr> - </table> - <br/> <br/> - <table width="100%" border="0" cellpadding="6" cellspacing="0"> + <tr><td> </td></tr> <tr> <td colspan="2" valign="top" class="listtopic">Statistics</td> </tr> @@ -719,8 +713,7 @@ set by the 'retries' parameter.</div> Specify the refresh rate of the stats page in seconds, or specified time unit (us, ms, s, m, h, d). </td> </tr> - </table> - <table width="100%" border="0" cellpadding="6" cellspacing="0"> + <tr><td> </td></tr> <tr align="left"> <td width="22%" valign="top"> </td> <td width="78%"> 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 @@ +<?php +/* + haproxy_utils.php + part of pfSense (http://www.pfsense.com/) + Copyright (C) 2013 PiBa-NL + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ +/* + This file contains functions which are NOT specific to HAProxy and may/could/should + be moved to the general pfSense php library for possible easy use by other parts of pfSense +*/ + +function haproxy_interface_ip($interfacebindname,$userfriendly=false){ + $list = haproxy_get_bindable_interfaces(); + $item = $list[$interfacebindname]; + $result = $item['ip']; + if ($userfriendly && !$result) + $result = $item['name']; + return $result; +} +function haproxy_get_bindable_interfaces($ipv="ipv4,ipv6", $interfacetype="any,localhost,real,carp,ipalias"){ + // returns a list of ALL interface/IPs that can be used to bind a service to. + // filtered by the conditions given in the two filter parameters. + // result array includes: + // $bindable[key] can be stored and compared with previous setings + // $bindable[key]['ip'] the current IP (possibly changes for dhcp enabled interfaces..) + // $bindable[key]['description'] can be shown to user in a selection box + + global $config; + $ipverions = split(',',$ipv); + $interfacetypes= split(',',$interfacetype); + + $bindable = array(); + if (in_array("ipv4",$ipverions)){ + if (in_array('any',$interfacetypes)){ + $item = array(); + $item[ip] = '0.0.0.0'; + $item[name] = 'any (IPv4)'; + $bindable['any_ipv4'] = $item; + } + if (in_array('localhost',$interfacetypes)){ + $item = array(); + $item[ip] = '127.0.0.1'; + $item[name] = 'localhost (IPv4)'; + $bindable['localhost_ipv4'] = $item; + } + if (in_array('real',$interfacetypes)){ + foreach($config['interfaces'] as $if => $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 "<select name=\"$name\" id=\"$name\" class=\"formselect\"$onchangeEvent>"; + foreach($keyvaluelist as $key => $desc){ + $selectedhtml = $key == $selected ? "selected" : ""; + echo "<option value=\"{$key}\" {$selectedhtml}>{$desc['name']}</option>"; + } + echo "</select>"; + } 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 @@ +<?php +/** + * Is one pem encoded certificate the signer of another? + * + * The PHP openssl functionality is severely limited by the lack of a stable + * api and documentation that might as well have been encrypted itself. + * In particular the documention on openssl_verify() never explains where + * to get the actual signature to verify. The isCertSigner() function below + * will accept two PEM encoded certs as arguments and will return true if + * one certificate was used to sign the other. It only relies on the + * openssl_pkey_get_public() and openssl_public_decrypt() openssl functions, + * which should stay fairly stable. The ASN parsing code snippets were mostly + * borrowed from the horde project's smime.php. + * + * @author Mike Green <mikey at badpenguins dot com> + * @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.'; + } + } + } +?> +<HTML> +<BODY BGCOLOR="white"> +<HEAD> +<TITLE>Is This The Cert Signer?</TITLE> +<STYLE TYPE="text/css"> +textarea { + font-size: 11px; + } +</STYLE> +</HEAD> +<BODY> +<DIV ALIGN="center"> +<FORM NAME="check_certs" METHOD="post" action="<?= $_SERVER['PHP_SELF']; ?>"> +<TABLE BORDER="1" WIDTH="500px"> + <TR> + <TD><?= $answer; ?></TD> + </TR> + <TR> + <TH>Issuer Certificate (<A HREF="#" ONCLICK="javascript:document.check_certs.issuerPem.value='';">clear</A>)</TH> + </TR> + <TR> + <TD> + <TEXTAREA NAME="issuerPem" ROWS="20" COLS="70"><?= (isset($_POST['issuerPem'])) ? $_POST['issuerPem'] : ''; ?></TEXTAREA> + </TD> + </TR> + <TR> + <TH>Subject Certificate (<A HREF="#" ONCLICK="javascript:document.check_certs.subjectPem.value='';">clear</A>)</TH> + </TR> + <TR> + <TD> + <TEXTAREA NAME="subjectPem" ROWS="20" COLS="70"><?= (isset($_POST['subjectPem'])) ? $_POST['subjectPem'] : ''; ?></TEXTAREA> + </TD> + </TR> + <TR> + <TD ALIGN="right"> + (<A HREF="<?= $_SERVER['PHP_SELF']; ?>?viewSource">view source</A>) + <INPUT TYPE="submit" NAME="submit" VALUE="submit"> + </TD> + </TR> +</TABLE> +</FORM> +</DIV> +</BODY> +</HTML> + +*/ +?>
\ No newline at end of file |