aboutsummaryrefslogtreecommitdiffstats
path: root/config/haproxy-devel/isCertSigner.inc
diff options
context:
space:
mode:
Diffstat (limited to 'config/haproxy-devel/isCertSigner.inc')
-rw-r--r--config/haproxy-devel/isCertSigner.inc361
1 files changed, 361 insertions, 0 deletions
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