diff options
-rw-r--r-- | config/haproxy-devel/haproxy.xml | 5 | ||||
-rw-r--r-- | config/haproxy-devel/haproxy_utils.inc | 29 | ||||
-rw-r--r-- | config/haproxy-devel/isCertSigner.inc | 361 |
3 files changed, 26 insertions, 369 deletions
diff --git a/config/haproxy-devel/haproxy.xml b/config/haproxy-devel/haproxy.xml index f7bac704..299d76c6 100644 --- a/config/haproxy-devel/haproxy.xml +++ b/config/haproxy-devel/haproxy.xml @@ -102,11 +102,6 @@ <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> diff --git a/config/haproxy-devel/haproxy_utils.inc b/config/haproxy-devel/haproxy_utils.inc index e826f530..940c816f 100644 --- a/config/haproxy-devel/haproxy_utils.inc +++ b/config/haproxy-devel/haproxy_utils.inc @@ -31,6 +31,8 @@ be moved to the general pfSense php library for possible easy use by other parts of pfSense */ +require_once("config.inc"); + function haproxy_interface_ip($interfacebindname,$userfriendly=false){ $list = haproxy_get_bindable_interfaces(); $item = $list[$interfacebindname]; @@ -153,11 +155,32 @@ function haproxy_get_bindable_interfaces($ipv="ipv4,ipv6", $interfacetype="any,l return $bindable; } +function haproxy_get_cert_extensions($crt){ + $cert = openssl_x509_parse(base64_decode($crt['crt'])); + return $cert['extensions']; +} + +function haproxy_get_cert_authoritykeyidentifier($cert) +{ + $certextension = haproxy_get_cert_extensions($cert); + $lines = preg_split('/[\n]+/',$certextension['authorityKeyIdentifier']); + return substr($lines[0],6);// cut off the starting string 'keyid:' +} +function haproxy_get_cert_subjectKeyIdentifier($cert) +{ + $certextension = haproxy_get_cert_extensions($cert); + $lines = preg_split('/[\n]+/',$certextension['subjectKeyIdentifier']); + return $lines[0]; +} + 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'])); + // checks if $cert was signed by $signedbycert + // this does NOT validate a proper signature but only checks if the extension properties match. + $authoritykeyid = haproxy_get_cert_authoritykeyidentifier($cert); + $subjectid = haproxy_get_cert_subjectKeyIdentifier($signedbycert); + return $authoritykeyid == $subjectid; } + function haproxy_get_certificates(){ global $config; $allcerts = array(); diff --git a/config/haproxy-devel/isCertSigner.inc b/config/haproxy-devel/isCertSigner.inc deleted file mode 100644 index d964129f..00000000 --- a/config/haproxy-devel/isCertSigner.inc +++ /dev/null @@ -1,361 +0,0 @@ -<?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 |