diff options
author | Remy <relst@relst.nl> | 2015-10-24 21:10:26 +0200 |
---|---|---|
committer | Remy <relst@relst.nl> | 2015-10-24 21:10:26 +0200 |
commit | 20303ba5c649c814eeff3d98bb8262072acb6dd2 (patch) | |
tree | 0c8b6ff8b18bf47b9ae168f70f5b1db20fa98dc1 /functions | |
parent | 3e0a1dfae5ec9211cecc0f532de6cea2be9256fd (diff) | |
download | ssl-decoder-20303ba5c649c814eeff3d98bb8262072acb6dd2.zip ssl-decoder-20303ba5c649c814eeff3d98bb8262072acb6dd2.tar.gz ssl-decoder-20303ba5c649c814eeff3d98bb8262072acb6dd2.tar.bz2 |
version 3.0
Diffstat (limited to 'functions')
-rw-r--r-- | functions/connection.php | 297 | ||||
-rw-r--r-- | functions/crl.php | 163 | ||||
-rw-r--r-- | functions/json.php | 8 | ||||
-rw-r--r-- | functions/ocsp.php | 6 | ||||
-rw-r--r-- | functions/parse_certificate.php | 282 | ||||
-rw-r--r-- | functions/textual.php | 31 | ||||
-rw-r--r-- | functions/variables.php | 33 | ||||
-rw-r--r-- | functions/verify_certifitcate.php | 87 |
8 files changed, 699 insertions, 208 deletions
diff --git a/functions/connection.php b/functions/connection.php index b305495..84e3f78 100644 --- a/functions/connection.php +++ b/functions/connection.php @@ -15,6 +15,7 @@ // along with this program. If not, see <http://www.gnu.org/licenses/>. function submitCertToCT($chain, $ct_url) { + global $timeout; $ct_chain = array('chain' => []); foreach ($chain as $key => $value) { $string = $value['key']['certificate_pem']; @@ -33,6 +34,7 @@ function submitCertToCT($chain, $ct_url) { curl_setopt($ch, CURLOPT_NOBODY, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_FAILONERROR, false); + curl_setopt($ch, CURLOPT_MAXREDIRS, 5); curl_setopt($ch, CURLOPT_FRESH_CONNECT, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, true); @@ -81,6 +83,7 @@ function server_http_headers($host, $ip, $port){ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); + curl_setopt($ch, CURLOPT_MAXREDIRS, 5); if(curl_exec($ch) === false) { curl_close($ch); return false; @@ -113,7 +116,7 @@ function server_http_headers($host, $ip, $port){ function ssl_conn_ciphersuites($host, $ip, $port, $ciphersuites) { global $timeout; $old_error_reporting = error_reporting(); - error_reporting($old_error_reporting ^ E_WARNING); + error_reporting(0); $results = array(); foreach ($ciphersuites as $value) { $results[$value] = false; @@ -136,7 +139,9 @@ function ssl_conn_ciphersuites($host, $ip, $port, $ciphersuites) { } function test_heartbleed($ip, $port) { + //this uses an external python2 check to test for the heartblead vulnerability global $current_folder; + global $timeout; $exitstatus = 0; $output = 0; $cmdexitstatus = 0; @@ -147,6 +152,7 @@ function test_heartbleed($ip, $port) { # check if python2 is available exec("command -v python2 >/dev/null 2>&1", $cmdoutput, $cmdexitstatus); if ($cmdexitstatus != 1) { + //15 is a reasonable timeout. exec("timeout 15 python2 " . getcwd() . "/inc/heartbleed.py " . escapeshellcmd($ip) . " --json \"" . $tmpfile . "\" --threads 1 --port " . escapeshellcmd($port) . " --silent", $output, $exitstatus); if (file_exists($tmpfile)) { $json_data = json_decode(file_get_contents($tmpfile),true); @@ -166,11 +172,11 @@ function test_heartbleed($ip, $port) { } function heartbeat_test($host, $port) { - global $random_blurp, $timeout; + //this tests for the heartbeat protocol extension + global $random_blurp; + global $timeout; $result = 0; - //pre_dump('echo | timeout ' . $timeout . ' openssl s_client -connect ' . escapeshellcmd($host) . ':' . escapeshellcmd($port) . ' -servername ' . escapeshellcmd($host) . ' -tlsextdebug 2>&1 < /dev/null | awk -F\" \'/server extension/ {print $2}\''); - $output = shell_exec('echo | timeout ' . $timeout . ' openssl s_client -connect ' . escapeshellcmd($host) . ':' . escapeshellcmd($port) . ' -servername ' . escapeshellcmd($host) . ' -tlsextdebug 2>&1 </dev/null | awk -F\" \'/server extension/ {print $2}\''); $output = preg_replace("/[[:blank:]]+/"," ", $output); @@ -203,7 +209,6 @@ function conn_compression($host, $ip, $port) { } $exitstatus = 0; $output = 0; - //pre_dump('echo | timeout ' . $timeout . ' openssl s_client -servername "' . escapeshellcmd($host) . '" -connect "' . escapeshellcmd($ip) . ':' . escapeshellcmd($port) . '" -status -tlsextdebug 2>&1 | grep -qe "^Compression: NONE"'); exec('echo | timeout ' . $timeout . ' openssl s_client -servername "' . escapeshellcmd($host) . '" -connect "' . escapeshellcmd($ip) . ':' . escapeshellcmd($port) . '" -status -tlsextdebug 2>&1 | grep -qe "^Compression: NONE"', $output, $exitstatus); if ($exitstatus == 0) { $result = false; @@ -216,7 +221,7 @@ function conn_compression($host, $ip, $port) { function ssl_conn_protocols($host, $ip, $port) { global $timeout; $old_error_reporting = error_reporting(); - error_reporting($old_error_reporting ^ E_WARNING); + error_reporting(0); $results = array('sslv2' => false, 'sslv3' => false, 'tlsv1.0' => false, @@ -288,6 +293,180 @@ function ssl_conn_protocols($host, $ip, $port) { return $results; } +function get_ca_issuer_urls($raw_cert_data) { + $result = array(); + $authorityInfoAcces = explode("\n", openssl_x509_parse($raw_cert_data)['extensions']['authorityInfoAccess']); + if (openssl_x509_parse($raw_cert_data)['extensions']['authorityInfoAccess']) { + foreach ($authorityInfoAcces as $authorityInfoAccess) { + $crt_uris = explode("CA Issuers - URI:", $authorityInfoAccess); + foreach ($crt_uris as $key => $crt_uri) { + foreach (explode("\n", $crt_uri) as $crt_ur) { + if($crt_ur) { + if (strpos(strtolower($crt_ur), 'ocsp') === false) { + array_push($result, $crt_ur); + } + } + } + } + } + } + return $result; +} + +function get_ca_issuer_crt($raw_cert_data) { + //we save certs, so we might have the issuer already. + //first check that, otherwise get crt from authorityinfoaccess + global $timeout; + if (!is_dir('crt_hash')) { + mkdir('crt_hash'); + } + // filenames of saved certs are hashes of the asort full subject. + $sort_subject = openssl_x509_parse($raw_cert_data)['issuer']; + asort($sort_subject); + foreach ($sort_subject as $key => $value) { + $issuer_full = "/" . $key . "=" . $value . $issuer_full; + } + $crt_check_hash = hash("sha256", $issuer_full); + $crt_check_hash_folder = "crt_hash/"; + $crt_check_hash_file = $crt_check_hash_folder . $crt_check_hash . ".pem"; + if(file_exists($crt_check_hash_file)) { + //if we already have a PEM file where the subject matches this certs issuer + //it probably is the correct one. return that and be done with it. + $crt_data = file_get_contents($crt_check_hash_file); + $export_pem = ""; + openssl_x509_export($crt_data, $export_pem); + //make sure it is valid data. + if($export_pem) { + $crt_cn = openssl_x509_parse($crt_data)['name']; + //add start and end for more clarity since this is a copy-pastable thingy. + $return_crt = "#start " . $crt_cn . "\n" . $export_pem . "#end " . $crt_cn . "\n"; + return $return_crt; + } + } else { + $issuer_urls = get_ca_issuer_urls($raw_cert_data); + if($issuer_urls) { + foreach ($issuer_urls as $key => $ca_issuer_url) { + //if we don't have that cert saved, we check if there is a der file + //based on the issuer url hash. + $crt_hash = hash("sha256", $ca_issuer_url); + $crt_hash_folder = "crt_hash/"; + $crt_hash_file = $crt_hash_folder . $crt_hash . ".der"; + if (!file_exists($crt_hash_file)) { + //that file is not there, let's get it + if (0 === strpos($ca_issuer_url, 'http')) { + $fp = fopen ($crt_hash_file, 'w+'); + $ch = curl_init(($ca_issuer_url)); + curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); + curl_setopt($ch, CURLOPT_FILE, $fp); + curl_setopt($ch, CURLOPT_FAILONERROR, true); + curl_setopt($ch, CURLOPT_FRESH_CONNECT, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_MAXREDIRS, 5); + if(curl_exec($ch) === false) { + continue; + } + curl_close($ch); + if(stat($crt_hash_file)['size'] < 10 ) { + //probably a corrypt file. sould be at least +100KB. + unlink($crt_hash_file); + } + } + } else { + if (time()-filemtime($crt_hash_file) > 5 * 84600) { + // file older than 5 days. crt might have changed, retry. + $content_hash = sha1_file($crt_hash_file); + rename($crt_hash_file, $crt_hash_folder . $content_hash . "content_hash.der"); + get_ca_issuer_crt($raw_cert_data); + } + } + if (file_exists($crt_hash_file)) { + //we have a a der file, we need to convert it to pem and return it. + //dirty way to get pem from der... + $crt_data = "-----BEGIN CERTIFICATE-----\n" . wordwrap(base64_encode(file_get_contents($crt_hash_file)), 65, "\n", 1) . "\n-----END CERTIFICATE-----"; + $crt_cn = openssl_x509_parse($crt_data)['name']; + $export_pem = ""; + openssl_x509_export($crt_data, $export_pem); + //make sure it is valid data. + if($export_pem) { + $return_crt = "#start " . $crt_cn . "\n" . $export_pem . "\n#end " . $crt_cn . "\n"; + //add start and end for more clarity since this is a copy-pastable thingy. + $sort_subject = openssl_x509_parse($crt_data)['subject']; + asort($sort_subject); + foreach ($sort_subject as $key => $value) { + $name_full = "/" . $key . "=" . $value . $name_full; + } + $crt_hash = hash("sha256", $name_full); + $crt_hash_folder = "crt_hash/"; + $crt_hash_file = $crt_hash_folder . $crt_hash . ".pem"; + //if the chain is wrong and we got this certificate + //via the authorityinfoaccess, we might not get it as a + //regular cert via the check. so therefore we save this + //as well, via the same mechanism. + if(file_exists($crt_hash_file)) { + if (time()-filemtime($crt_hash_file) > 5 * 84600) { + // file older than 5 days. crt might have changed, retry. + $content_hash = sha1_file($crt_hash_file); + rename($crt_hash_file, $crt_hash_folder . $content_hash . "content_hash.pem"); + file_put_contents($crt_hash_file, $export_pem); + } + } else { + file_put_contents($crt_hash_file, $export_pem); + } + if(stat($crt_hash_file)['size'] < 10 ) { + //probably a corrypt file. sould be at least +100KB. + unlink($crt_hash_file); + } + } + } + return $return_crt; + } + } + } +} + + +function get_issuer_chain($raw_cert_data, $number=1, $result=null) { + global $max_chain_length; + if ($result['complete'] == 'yes') { + return $result; + } + if ($number > $max_chain_length) { + $result['complete'] == 'error'; + return $result; + } + $number += 1; + + if (!is_array($result)) { + $result = array('certs' => array(), 'complete' => 'false'); + } + + $sort_subject = openssl_x509_parse($raw_cert_data)['subject']; + asort($sort_subject); + foreach ($sort_subject as $key => $value) { + $subject_full = "/" . $key . "=" . $value . $subject_full; + } + $sort_issuer = openssl_x509_parse($raw_cert_data)['issuer']; + asort($sort_issuer); + foreach ($sort_issuer as $key => $value) { + $issuer_full = "/" . $key . "=" . $value . $issuer_full; + } + if($issuer_full == $subject_full && $result) { + $result['complete'] == 'yes'; + return $result; + } + $this_issuer = get_ca_issuer_crt($raw_cert_data); + if($this_issuer) { + array_push($result['certs'], $this_issuer); + $result = get_issuer_chain($this_issuer, $number, $result); + return $result; + } else { + return $result; + } + return $result; +} + function ssl_conn_metadata($data,$fastcheck=0) { global $random_blurp; global $current_folder; @@ -338,6 +517,59 @@ function ssl_conn_metadata($data,$fastcheck=0) { } echo "</td>"; echo "</tr>"; + + // correct chain + if ($fastcheck == 0 && $data["validation"]["status"] == "failed" && is_array($data["validation"]["correct_chain"])) { + echo "<tr>"; + echo "<td><strong>Correct Chain</strong></td>"; + echo "<td>"; + echo "<p><strong>The validation of this certificate failed. This might be because of an incorrect or incomplete CA chain. Based on the '<code>authorityInfoAccess</code>' extension and earlier saved certificates, the below result probably contains the correct CA Chain, in the correct order, for this certificate. The result also contains your certificate as the first one.</strong><br>"; + + echo "<p>This is our best guess at the correct ca signing chain: <br><ul>"; + foreach ($data['validation']['cns'] as $cn_key => $cn_value) { + foreach ($cn_value as $cnn_key => $cnn_value) { + echo "<span style='font-family: monospace;'><li>"; + if($cnn_key == 'cn') { + echo "Name.......: "; + echo htmlspecialchars($cnn_value); + echo "</li></span> "; + } + if ($cnn_key == 'issuer') { + echo "Issued by..: "; + echo htmlspecialchars($cnn_value); + echo "</li></span><br>"; + } + } + } + echo "</ul></p>"; + echo "<p>Click below to see the full chain output in PEM format, copy-pastable in most software.</p>"; + ?> + <div class="panel-group" id="accordion-correct-chain" role="tablist" aria-multiselectable="true"> + <div class="panel panel-default"> + <div class="panel-heading" role="tab" id="heading-correct-chain"> + <h4 class="panel-title"> + <a class="collapsed" data-toggle="collapse" data-parent="#accordion" href="#collapse-correct-chain" aria-expanded="false" aria-controls="collapse-correct-chain"> + Click to Open/Close + </a> + </h4> + </div> + <div id="collapse-correct-chain" class="panel-collapse collapse" role="tabpanel" aria-labelledby="heading-correct-chain"> + <div class="panel-body"> + <?php + echo "<pre>"; + foreach ($data['validation']['correct_chain'] as $cert) { + echo htmlspecialchars($cert); + echo "<br>"; + } + echo "</pre>"; + echo "</div>"; + echo "</div>"; + echo "</div>"; + echo "</div>"; + echo "</td>"; + echo "</tr>"; + } + // ip hostname port if ( $data["hostname"] ) { echo "<tr>"; @@ -351,7 +583,7 @@ function ssl_conn_metadata($data,$fastcheck=0) { echo "</td>"; echo "</tr>"; } - if($fastcheck == 0) { + if($fastcheck == 0) { // protocols echo "<tr>"; echo "<td>Protocols</td>"; @@ -603,12 +835,12 @@ function ssl_conn_metadata($data,$fastcheck=0) { echo "</table>"; } - - function ssl_conn_metadata_json($host, $ip, $port, $read_stream, $chain_data=null,$fastcheck=0) { $result = array(); global $random_blurp; global $current_folder; + global $timeout; + global $max_chain_length; $context = stream_context_get_params($read_stream); $context_meta = stream_context_get_options($read_stream)['ssl']['session_meta']; $cert_data = openssl_x509_parse($context["options"]["ssl"]["peer_certificate"])[0]; @@ -657,6 +889,53 @@ function ssl_conn_metadata_json($host, $ip, $port, $read_stream, $chain_data=nul } unlink('/tmp/verify_cert.' . $random_blurp . '.pem'); } + + //chain construction + if (isset($chain_data) && $factcheck == 0 && $result["validation"]["status"] == "failed") { + $return_chain = array(); + $export_pem = ""; + openssl_x509_export($chain_data[0], $export_pem); + $crt_cn = openssl_x509_parse($chain_data[0])['name']; + $export_pem = "#start " . $crt_cn . "\n" . $export_pem . "\n#end " . $crt_cn . "\n"; + array_push($return_chain, $export_pem); + $chain_length = count($chain_data); + $certificate_chain = array(); + if ($chain_length <= $max_chain_length) { + $issuer_crt = get_issuer_chain($chain_data[0]); + if (count($issuer_crt['certs']) >= 1) { + $issuercrts = array_unique($issuer_crt['certs']); + foreach ($issuercrts as $key => $value) { + array_push($return_chain, $value); + } + } + } + } + if(is_array($return_chain)) { + $return_chain = array_unique($return_chain); + } + if(count($return_chain) > 1) { + $result["validation"]["cns"] = array(); + $result["correct_chain"]["cns"] = array(); + $crt_cn = array(); + foreach ($return_chain as $retc_key => $retc_value) { + $issuer_full = ""; + $subject_full = ""; + $sort_issuer = openssl_x509_parse($retc_value)['issuer']; + $sort_subject = openssl_x509_parse($retc_value)['subject']; + asort($sort_subject); + foreach ($sort_subject as $sub_key => $sub_value) { + $subject_full = "/" . $sub_key . "=" . $sub_value . $subject_full; + } + asort($sort_issuer); + foreach ($sort_issuer as $iss_key => $iss_value) { + $issuer_full = "/" . $iss_key . "=" . $iss_value . $issuer_full; + } + $crt_cn['cn'] = $subject_full; + $crt_cn['issuer'] = $issuer_full; + array_push($result["validation"]["cns"], $crt_cn); + } + $result["validation"]["correct_chain"] = $return_chain; + } // hostname ip port $result["ip"] = $ip; if (filter_var(preg_replace('/[^A-Za-z0-9\.\:-]/', '', $ip), FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 )) { diff --git a/functions/crl.php b/functions/crl.php index 35e57a5..f371639 100644 --- a/functions/crl.php +++ b/functions/crl.php @@ -15,87 +15,88 @@ // along with this program. If not, see <http://www.gnu.org/licenses/>. function crl_verify($raw_cert_data, $verbose=true) { - global $random_blurp, $timeout; - $cert_data = openssl_x509_parse($raw_cert_data); - $cert_serial_nm = strtoupper(bcdechex($cert_data['serialNumber'])); - $crl_uris = []; - $crl_uri = explode("\nFull Name:\n ", $cert_data['extensions']['crlDistributionPoints']); - foreach ($crl_uri as $key => $uri) { - if (!empty($uri) ) { - $uri = explode("URI:", $uri); - foreach ($uri as $key => $crluri) { - if (!empty($crluri) ) { - $crl_uris[] = preg_replace('/\s+/', '', $crluri); - } - } + global $random_blurp, $timeout; + $cert_data = openssl_x509_parse($raw_cert_data); + $cert_serial_nm = strtoupper(bcdechex($cert_data['serialNumber'])); + $crl_uris = []; + $crl_uri = explode("\nFull Name:\n ", $cert_data['extensions']['crlDistributionPoints']); + foreach ($crl_uri as $key => $uri) { + if (!empty($uri) ) { + $uri = explode("URI:", $uri); + foreach ($uri as $key => $crluri) { + if (!empty($crluri) ) { + $crl_uris[] = preg_replace('/\s+/', '', $crluri); } + } } - foreach ($crl_uris as $key => $uri) { - if (!empty($uri)) { - if (0 === strpos($uri, 'http')) { - $fp = fopen ("/tmp/" . $random_blurp . "." . $key . ".crl", 'w+'); - $ch = curl_init(($uri)); - curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); - curl_setopt($ch, CURLOPT_FILE, $fp); - curl_setopt($ch, CURLOPT_FAILONERROR, true); - curl_setopt($ch, CURLOPT_FRESH_CONNECT, true); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); - if(curl_exec($ch) === false) - { - echo '<pre>Curl error: ' . htmlspecialchars(curl_error($ch)) ."</pre>"; - } - curl_close($ch); - if(stat("/tmp/" . $random_blurp . "." . escapeshellcmd($key) . ".crl")['size'] < 10 ) { - return false; - } - $crl_text = shell_exec("openssl crl -noout -text -inform der -in /tmp/" . $random_blurp . "." . escapeshellcmd($key) . ".crl 2>&1"); - - $crl_last_update = shell_exec("openssl crl -noout -lastupdate -inform der -in /tmp/" . $random_blurp . "." . escapeshellcmd($key) . ".crl"); - - $crl_next_update = shell_exec("openssl crl -noout -nextupdate -inform der -in /tmp/" . $random_blurp . "." . escapeshellcmd($key) . ".crl"); - - unlink("/tmp/" . $random_blurp . "." . escapeshellcmd($key) . ".crl"); - - if ( strpos($crl_text, "unable to load CRL") === 0 ) { - if ( $verbose ) { - $result = "<span class='text-danger glyphicon glyphicon-exclamation-sign'></span> - <span class='text-danger'>CRL invalid. (" . $uri . ")</span><br><pre> " . htmlspecialchars($crl_text) . "</pre>"; - return $result; - } else { - $result = "<span class='text-danger glyphicon glyphicon-remove'></span>"; - return $result; - } - } - - $crl_info = explode("Revoked Certificates:", $crl_text)[0]; - - $crl_certificates = explode("Revoked Certificates:", $crl_text)[1]; - - $crl_certificates = explode("Serial Number:", $crl_certificates); - $revcert = array('bla' => "die bla"); - foreach ($crl_certificates as $key => $revoked_certificate) { - if (!empty($revoked_certificate)) { - $revcert[str_replace(" ", "", explode("\n", $revoked_certificate)[0])] = str_replace(" Revocation Date: ", "", explode("\n", $revoked_certificate)[1]); - } - } - if( array_key_exists($cert_serial_nm, $revcert) ) { - if ( $verbose ) { - $result = "<span class='text-danger glyphicon glyphicon-exclamation-sign'></span> - <span class='text-danger'>REVOKED on " . $revcert[$cert_serial_nm] . ". " . $uri . "</span><br><pre> " . $crl_last_update . " " . $crl_next_update . "</pre>"; - } else { - $result = "<span class='text-danger glyphicon glyphicon-remove'></span>"; - } - } else { - if ( $verbose ) { - $result = "<span class='text-success glyphicon glyphicon-ok-sign'></span> <span class='text-success'> - " . $uri . "</span><br><pre> " . $crl_last_update . " " . $crl_next_update . "</pre>"; - } else { - $result = "<span class='text-success glyphicon glyphicon-ok'></span>"; - } - } - return $result; - } + } + foreach ($crl_uris as $key => $uri) { + if (!empty($uri)) { + if (0 === strpos($uri, 'http')) { + $fp = fopen ("/tmp/" . $random_blurp . "." . $key . ".crl", 'w+'); + $ch = curl_init(($uri)); + curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); + curl_setopt($ch, CURLOPT_FILE, $fp); + curl_setopt($ch, CURLOPT_FAILONERROR, true); + curl_setopt($ch, CURLOPT_FRESH_CONNECT, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_MAXREDIRS, 5); + if(curl_exec($ch) === false) { + echo '<pre>Curl error: ' . htmlspecialchars(curl_error($ch)) ."</pre>"; + } + curl_close($ch); + if(stat("/tmp/" . $random_blurp . "." . escapeshellcmd($key) . ".crl")['size'] < 10 ) { + unlink("/tmp/" . $random_blurp . "." . escapeshellcmd($key) . ".crl"); + return false; + } + $crl_text = shell_exec("timeout " . $timeout . " openssl crl -noout -text -inform der -in /tmp/" . $random_blurp . "." . escapeshellcmd($key) . ".crl 2>&1"); + + $crl_last_update = shell_exec("timeout " . $timeout . " openssl crl -noout -lastupdate -inform der -in /tmp/" . $random_blurp . "." . escapeshellcmd($key) . ".crl"); + + $crl_next_update = shell_exec("timeout " . $timeout . " openssl crl -noout -nextupdate -inform der -in /tmp/" . $random_blurp . "." . escapeshellcmd($key) . ".crl"); + + unlink("/tmp/" . $random_blurp . "." . escapeshellcmd($key) . ".crl"); + + if ( strpos($crl_text, "unable to load CRL") === 0 ) { + if ( $verbose ) { + $result = "<span class='text-danger glyphicon glyphicon-exclamation-sign'></span> - <span class='text-danger'>CRL invalid. (" . $uri . ")</span><br><pre> " . htmlspecialchars($crl_text) . "</pre>"; + return $result; + } else { + $result = "<span class='text-danger glyphicon glyphicon-remove'></span>"; + return $result; + } } + + $crl_info = explode("Revoked Certificates:", $crl_text)[0]; + + $crl_certificates = explode("Revoked Certificates:", $crl_text)[1]; + + $crl_certificates = explode("Serial Number:", $crl_certificates); + $revcert = array('bla' => "die bla"); + foreach ($crl_certificates as $key => $revoked_certificate) { + if (!empty($revoked_certificate)) { + $revcert[str_replace(" ", "", explode("\n", $revoked_certificate)[0])] = str_replace(" Revocation Date: ", "", explode("\n", $revoked_certificate)[1]); + } + } + if( array_key_exists($cert_serial_nm, $revcert) ) { + if ( $verbose ) { + $result = "<span class='text-danger glyphicon glyphicon-exclamation-sign'></span> - <span class='text-danger'>REVOKED on " . $revcert[$cert_serial_nm] . ". " . $uri . "</span><br><pre> " . $crl_last_update . " " . $crl_next_update . "</pre>"; + } else { + $result = "<span class='text-danger glyphicon glyphicon-remove'></span>"; + } + } else { + if ( $verbose ) { + $result = "<span class='text-success glyphicon glyphicon-ok-sign'></span> <span class='text-success'> - " . $uri . "</span><br><pre> " . $crl_last_update . " " . $crl_next_update . "</pre>"; + } else { + $result = "<span class='text-success glyphicon glyphicon-ok'></span>"; + } + } + return $result; + } } + } } @@ -111,7 +112,7 @@ function crl_verify_json($raw_cert_data) { $uri = explode("URI:", $uri); $uri = $uri[1]; if (isset($uri) ) { - $crl_uris[] = preg_replace('/\s+/', '', $uri); + $crl_uris[] = preg_replace('/\s+/', '', $uri); } } } @@ -135,12 +136,12 @@ function crl_verify_json($raw_cert_data) { if(stat("/tmp/" . $random_blurp . "." . escapeshellcmd($key) . ".crl")['size'] < 10 ) { $result[$crl_no]["error"] = "crl could not be retreived"; } - $crl_text = shell_exec("openssl crl -noout -text -inform der -in /tmp/" . $random_blurp . "." . escapeshellcmd($key) . ".crl 2>&1"); + $crl_text = shell_exec("timeout " . $timeout . " openssl crl -noout -text -inform der -in /tmp/" . $random_blurp . "." . escapeshellcmd($key) . ".crl 2>&1"); - $crl_last_update = shell_exec("openssl crl -noout -lastupdate -inform der -in /tmp/" . $random_blurp . "." . escapeshellcmd($key) . ".crl"); + $crl_last_update = shell_exec("timeout " . $timeout . " openssl crl -noout -lastupdate -inform der -in /tmp/" . $random_blurp . "." . escapeshellcmd($key) . ".crl"); $crl_last_update = explode("=", $crl_last_update)[1]; - $crl_next_update = shell_exec("openssl crl -noout -nextupdate -inform der -in /tmp/" . $random_blurp . "." . escapeshellcmd($key) . ".crl"); + $crl_next_update = shell_exec("timeout " . $timeout . " openssl crl -noout -nextupdate -inform der -in /tmp/" . $random_blurp . "." . escapeshellcmd($key) . ".crl"); $crl_next_update = explode("=", $crl_next_update)[1]; unlink("/tmp/" . $random_blurp . "." . escapeshellcmd($key) . ".crl"); diff --git a/functions/json.php b/functions/json.php index 3fae962..e25b885 100644 --- a/functions/json.php +++ b/functions/json.php @@ -18,6 +18,8 @@ function check_json($host,$ip,$port,$fastcheck=0) { global $timeout; global $max_chain_length; global $ct_urls; + $old_error_reporting = error_reporting(); + error_reporting(0); $data = []; $stream = stream_context_create (array("ssl" => array("capture_peer_cert" => true, @@ -51,11 +53,12 @@ function check_json($host,$ip,$port,$fastcheck=0) { $next = $chain_data[$key+1]; $prev = $chain_data[$key-1]; $chain_key = (string)$key+1; + $include_chain = false; if ($key == 0) { $data["connection"] = ssl_conn_metadata_json($host, $ip, $port, $read_stream, $chain_data, $fastcheck); - $data["chain"][$chain_key] = cert_parse_json($curr, $next, $host, true, $port); + $data["chain"][$chain_key] = cert_parse_json($curr, $next, $host, true, $port, $include_chain); } else { - $data["chain"][$chain_key] = cert_parse_json($curr, $next, null, false, $port); + $data["chain"][$chain_key] = cert_parse_json($curr, $next, null, false, $port, $include_chain); } // certificate transparency $data["certificate_transparency"] = []; @@ -78,6 +81,7 @@ function check_json($host,$ip,$port,$fastcheck=0) { return $data; } } + error_reporting($old_error_reporting); return $data; } diff --git a/functions/ocsp.php b/functions/ocsp.php index 8a5e54e..b9cefcb 100644 --- a/functions/ocsp.php +++ b/functions/ocsp.php @@ -15,6 +15,7 @@ // along with this program. If not, see <http://www.gnu.org/licenses/>. function ocsp_stapling($host, $ip, $port) { + //used openssl cli to check if host has enabled oscp stapling. global $timeout; if (filter_var(preg_replace('/[^A-Za-z0-9\.\:_-]/', '', $ip), FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { // ipv6 openssl tools are broken. (https://rt.openssl.org/Ticket/Display.html?id=1365&user=guest&pass=guest) @@ -50,6 +51,7 @@ function ocsp_stapling($host, $ip, $port) { } function ocsp_verify_json($raw_cert_data, $raw_next_cert_data, $ocsp_uri) { + //uses openssl cli to validate cert status with ocsp global $random_blurp, $timeout; $result = array(); $tmp_dir = '/tmp/'; @@ -65,8 +67,6 @@ function ocsp_verify_json($raw_cert_data, $raw_next_cert_data, $ocsp_uri) { // Some OCSP's want HTTP/1.1 but OpenSSL does not do that. Add Host header as workaround. $ocsp_host = parse_url($ocsp_uri, PHP_URL_HOST); - //pre_dump('openssl ocsp -no_nonce -CAfile '.$root_ca.' -issuer '.$isser_loc.' -cert '.$tmp_dir.$random_blurp.'.cert_client.pem -url "'. escapeshellcmd($ocsp_uri) . '" -header "HOST" "'. escapeshellcmd($ocsp_host) . '" 2>&1'); - $output = shell_exec('timeout ' . $timeout . ' | openssl ocsp -no_nonce -CAfile '.$root_ca.' -issuer '.$isser_loc .' -cert '.$tmp_dir.$random_blurp.'.cert_client.pem -url "'. escapeshellcmd($ocsp_uri) . '" -header "HOST" "'. escapeshellcmd($ocsp_host) . '" 2>&1'); $filter_output = shell_exec('timeout ' . $timeout . ' | openssl ocsp -no_nonce -CAfile '.$root_ca.' -issuer '.$isser_loc .' -cert '.$tmp_dir.$random_blurp.'.cert_client.pem -url "'. escapeshellcmd($ocsp_uri) . '" -header "HOST" "'. escapeshellcmd($ocsp_host) . '" 2>&1 | grep -v -e "to get local issuer certificate" -e "signer certificate not found" -e "Response Verify" -e "'. $tmp_dir.$random_blurp.'.cert_client.pem"'); @@ -105,7 +105,7 @@ function ocsp_verify_json($raw_cert_data, $raw_next_cert_data, $ocsp_uri) { $result["revocation_time"] = $lines["Revocation Time"]; } $result["ocsp_uri"] = $ocsp_uri; - + //remove temp files after use unlink($tmp_dir.$random_blurp.'.cert_client.pem'); unlink($tmp_dir.$random_blurp.'.cert_issuer.pem'); diff --git a/functions/parse_certificate.php b/functions/parse_certificate.php index d342e4b..28bdc80 100644 --- a/functions/parse_certificate.php +++ b/functions/parse_certificate.php @@ -15,10 +15,47 @@ // along with this program. If not, see <http://www.gnu.org/licenses/>. +function get_sans_from_csr($csr) { + global $random_blurp; + global $timeout; + //openssl_csr_get_subject doesn't support SAN names. + $filename = "/tmp/csr-" . $random_blurp . "-" . gen_uuid() . ".csr.pem"; + $write_csr = file_put_contents($filename, $csr); + if($write_csr !== FALSE) { + $openssl_csr_output = trim(shell_exec("timeout " . $timeout . " openssl req -noout -text -in " . $filename . " | grep -e 'DNS:' -e 'IP:'")); + } + unlink($filename); + if($openssl_csr_output) { + $sans = array(); + $csr_san_dns = explode("DNS:", $openssl_csr_output); + $csr_san_ip = explode("IP:", $openssl_csr_output); + if(count($csr_san_dns) > 1) { + foreach ($csr_san_dns as $key => $value) { + if($value) { + $san = trim(str_replace(",", "", str_replace("DNS:", "", $value))); + array_push($sans, $san); + } + } + } + if(count($csr_san_ip) > 1) { + foreach ($csr_san_ip as $key => $value) { + if($value) { + $san = trim(str_replace(",", "", str_replace("IP:", "", $value))); + array_push($sans, $san); + } + } + } + } + if(count($sans) >= 1) { + return $sans; + } +} + function csr_parse($data) { + //parses the json data from csr_parse_json() to a nice html page. echo "<table class='table table-striped table-bordered'>"; echo "<tr>"; - echo "<td colspan='2'><strong>Certificate Data</strong></td>"; + echo "<td colspan='2'><strong>Certificate Signing Request Data</strong></td>"; echo "</tr>"; foreach ($data['subject'] as $key => $value) { echo "<tr><td>"; @@ -73,6 +110,17 @@ function csr_parse($data) { } echo "</td></tr>\n"; } + + if($data['csr_sans']) { + echo "<tr><td>Subject Alternative Names</td><td><ul>"; + foreach ($data['csr_sans'] as $key => $value) { + echo "<span style='font-family:monospace;'><li>"; + echo htmlspecialchars($value); + echo "</li>"; + } + echo "</ul></td></tr>"; + } + echo "<tr><td>Public Key PEM ("; echo htmlspecialchars($data['details']['bits']); if ($data['details']['rsa']) { @@ -89,12 +137,18 @@ function csr_parse($data) { } echo ")</td><td><pre>"; echo htmlspecialchars($data['details']['key']); - echo "</pre></td>"; + echo "</pre></td></tr>"; + + echo "<tr><td>CSR PEM</td><td><pre>"; + echo htmlspecialchars($data['csr_pem']); + echo "</pre></td></tr>"; echo "</table>"; } function cert_parse($data) { + //parses the json data from cert_parse_json() to a nice html page. + //does output formatting based on some parts, like red if cert expired. if (is_array($data["warning"]) && count($data["warning"]) >= 1) { $data["warning"] = array_unique($data["warning"]); if (count($data["warning"]) == 1) { @@ -452,25 +506,27 @@ function cert_parse($data) { echo "<tr><td>OCSP</td><td>No OCSP URI found in certificate</td></tr>"; } } - echo "<tr>"; - echo "<td>Hostname Validation</td>"; - echo "<td>"; - // hostname validation - if ($data["hostname_in_san_or_cn"] == "true") { - echo "<span class='text-success glyphicon glyphicon-ok'></span>\n<span class='text-success'> - "; - echo htmlspecialchars($data['hostname_checked']); - echo " found in CN or SAN.</span>"; - } elseif ($data["hostname_in_san_or_cn"] == "false") { - echo '<span class="text-danger glyphicon glyphicon-remove"></span><span class="text-danger"> - '; - echo htmlspecialchars($data['hostname_checked']); - echo ' NOT found in CN or SAN.</span>'; - } elseif ($data["hostname_in_san_or_cn"] == "n/a; ca signing certificate") { - echo "Not applicable, this seems to be a CA signing certificate."; - } else { - echo "Not applicable, this seems to be a CA signing certificate."; + if(!empty($_GET['host'])) { + echo "<tr>"; + echo "<td>Hostname Validation</td>"; + echo "<td>"; + // hostname validation + if ($data["hostname_in_san_or_cn"] == "true") { + echo "<span class='text-success glyphicon glyphicon-ok'></span>\n<span class='text-success'> - "; + echo htmlspecialchars($data['hostname_checked']); + echo " found in CN or SAN.</span>"; + } elseif ($data["hostname_in_san_or_cn"] == "false") { + echo '<span class="text-danger glyphicon glyphicon-remove"></span><span class="text-danger"> - '; + echo htmlspecialchars($data['hostname_checked']); + echo ' NOT found in CN or SAN.</span>'; + } elseif ($data["hostname_in_san_or_cn"] == "n/a; ca signing certificate") { + echo "Not applicable, this seems to be a CA signing certificate."; + } else { + echo "Not applicable, this seems to be a CA signing certificate."; + } + echo "</td>"; + echo "</tr>"; } - echo "</td>"; - echo "</tr>"; // details echo "<tr>"; echo "<td colspan='2'><strong>Details</strong></td>"; @@ -553,11 +609,11 @@ function cert_parse($data) { echo "</td>"; echo "</tr>"; - if ($_GET['fastcheck'] == 0) { + if ($_GET['fastcheck'] == 0 && !empty($_GET['host'])) { echo "<tr>"; echo "<td>TLSA DNS </td>"; echo "<td>"; - if($data['tlsa']['error'] == 'none' && isset($data['tlsa'])) { + if($data['tlsa']['error'] == 'none' && !empty($data['tlsa'])) { echo "<table class='table table-striped'>"; foreach ($data["tlsa"] as $key => $value) { switch ($key) { @@ -746,6 +802,60 @@ function cert_parse($data) { echo "</div>"; echo "</td>"; echo "</tr>"; + + // correct chain + if (is_array($data["correct_chain"]["chain"])) { + echo "<tr>"; + echo "<td>Certificate Chain</td>"; + echo "<td>"; + echo "<p>We've constructed the certificate chain in the correct order of this certificate based on the '<code>authorityInfoAccess</code>' extension and earlier saved certificates. The result also contains this certificate as the first one.<br>"; + + echo "<p>This is our best guess at the correct CA Chain: <br><ul>"; + foreach ($data['correct_chain']['cns'] as $cn_key => $cn_value) { + foreach ($cn_value as $cnn_key => $cnn_value) { + echo "<span style='font-family: monospace;'><li>"; + if($cnn_key == 'cn') { + echo "Name.......: "; + echo htmlspecialchars($cnn_value); + echo "</li></span> "; + } + if ($cnn_key == 'issuer') { + echo "Issued by..: "; + echo htmlspecialchars($cnn_value); + echo "</li></span><br>"; + } + } + } + echo "</ul></p>"; + echo "<p>Click below to see the full chain output in PEM format, copy-pastable in most software.</p>"; + ?> + <div class="panel-group" id="accordion-correct-chain" role="tablist" aria-multiselectable="true"> + <div class="panel panel-default"> + <div class="panel-heading" role="tab" id="heading-correct-chain"> + <h4 class="panel-title"> + <a class="collapsed" data-toggle="collapse" data-parent="#accordion" href="#collapse-correct-chain" aria-expanded="false" aria-controls="collapse-correct-chain"> + Click to Open/Close + </a> + </h4> + </div> + <div id="collapse-correct-chain" class="panel-collapse collapse" role="tabpanel" aria-labelledby="heading-correct-chain"> + <div class="panel-body"> + <?php + echo "<pre>"; + foreach ($data['correct_chain']['chain'] as $cert) { + echo htmlspecialchars($cert); + echo "<br>"; + } + echo "</pre>"; + echo "</div>"; + echo "</div>"; + echo "</div>"; + echo "</div>"; + echo "</td>"; + echo "</tr>"; + } + + echo "<tr>"; echo "<td><a href='https://raymii.org/s/articles/HTTP_Public_Key_Pinning_Extension_HPKP.html'>SPKI Hash</a></td>"; echo "<td>"; @@ -792,29 +902,7 @@ function cert_parse($data) { - - - - -function csr_parse_json($csr) { - $result = array(); - if (strpos($csr, "BEGIN CERTIFICATE REQUEST") !== false) { - $cert_data = openssl_csr_get_public_key($csr); - $cert_details = openssl_pkey_get_details($cert_data); - $cert_key = $cert_details['key']; - $cert_subject = openssl_csr_get_subject($csr); - $result["subject"] = $cert_subject; - $result["key"] = $cert_key; - $result["details"] = $cert_details; - } elseif (strpos($csr, "BEGIN CERTIFICATE") !== false) { - $result = cert_parse_json($csr); - } else { - $result = array("error" => "data not valid csr"); - } - return $result; -} - -function cert_parse_json($raw_cert_data, $raw_next_cert_data=null, $host=null, $validate_hostname=false, $port="443") { +function cert_parse_json($raw_cert_data, $raw_next_cert_data=null, $host=null, $validate_hostname=false, $port="443", $include_chain=null) { global $random_blurp; global $ev_oids; global $timeout; @@ -953,6 +1041,78 @@ function cert_parse_json($raw_cert_data, $raw_next_cert_data=null, $host=null, $ $key_details = openssl_pkey_get_details(openssl_pkey_get_public($raw_cert_data)); $export_pem = ""; openssl_x509_export($raw_cert_data, $export_pem); + + // save pem. this because the reconstruct chain function works better + // this way. not all certs have authorityinfoaccess. We first check if + // we already have a matching cert. + if (!is_dir('crt_hash')) { + mkdir('crt_hash'); + } + // filenames of saved certs are hashes of the asort full subject. + $sort_subject = $cert_data['subject']; + asort($sort_subject); + foreach ($sort_subject as $key => $value) { + $name_full = "/" . $key . "=" . $value . $name_full; + } + $crt_hash = hash("sha256", $name_full); + $crt_hash_folder = "crt_hash/"; + $crt_hash_file = $crt_hash_folder . $crt_hash . ".pem"; + if(file_exists($crt_hash_file)) { + if (time()-filemtime($crt_hash_file) > 5 * 84600) { + // file older than 5 days. crt might have changed, retry. + $content_hash = sha1_file($crt_hash_file); + rename($crt_hash_file, $crt_hash_folder . $content_hash . "content_hash_save.pem"); + file_put_contents($crt_hash_file, $export_pem); + } + } else { + file_put_contents($crt_hash_file, $export_pem); + } + if(stat($crt_hash_file)['size'] < 10 ) { + //probably a corrupt file. sould be at least +100KB. + unlink($crt_hash_file); + } + + //chain reconstruction + if($include_chain && $raw_cert_data) { + $return_chain = array(); + $export_pem = ""; + openssl_x509_export($raw_cert_data, $export_pem); + $crt_cn = openssl_x509_parse($raw_cert_data)['name']; + $export_pem = "#start " . $crt_cn . "\n" . $export_pem . "\n#end " . $crt_cn . "\n"; + array_push($return_chain, $export_pem); + $certificate_chain = array(); + $issuer_crt = get_issuer_chain($raw_cert_data); + if (count($issuer_crt['certs']) >= 1) { + $issuercrts = array_unique($issuer_crt['certs']); + foreach ($issuercrts as $key => $value) { + array_push($return_chain, $value); + } + } + $return_chain = array_unique($return_chain); + if(count($return_chain) > 1) { + $result["correct_chain"]["cns"] = array(); + $crt_cn = array(); + foreach ($return_chain as $retc_key => $retc_value) { + $issuer_full = ""; + $subject_full = ""; + $sort_issuer = openssl_x509_parse($retc_value)['issuer']; + $sort_subject = openssl_x509_parse($retc_value)['subject']; + asort($sort_subject); + foreach ($sort_subject as $sub_key => $sub_value) { + $subject_full = "/" . $sub_key . "=" . $sub_value . $subject_full; + } + asort($sort_issuer); + foreach ($sort_issuer as $iss_key => $iss_value) { + $issuer_full = "/" . $iss_key . "=" . $iss_value . $issuer_full; + } + $crt_cn['cn'] = $subject_full; + $crt_cn['issuer'] = $issuer_full; + array_push($result["correct_chain"]["cns"], $crt_cn); + } + $result["correct_chain"]["chain"] = $return_chain; + } + } + //hashes $string = $export_pem; $pattern = '/-----(.*)-----/'; @@ -963,8 +1123,6 @@ function cert_parse_json($raw_cert_data, $raw_next_cert_data=null, $host=null, $ $replacement = ''; $export_pem_preg = preg_replace($pattern, $replacement, $string); $export_pem_preg = wordwrap($export_pem_preg, 77, "\n", TRUE); - //pre_dump("export preg: " . $export_pem_preg); - //pre_dump("end"); $result['hash']['md5'] = cert_hash('md5', $export_pem_preg); $result['hash']['sha1'] = cert_hash('sha1', $export_pem_preg); $result['hash']['sha256'] = cert_hash('sha256', $export_pem_preg); @@ -972,7 +1130,7 @@ function cert_parse_json($raw_cert_data, $raw_next_cert_data=null, $host=null, $ $result['hash']['sha512'] = cert_hash('sha512', $export_pem_preg); //TLSA check - if (isset($cert_data['subject']['CN']) && isset($host)) { + if (!empty($cert_data['subject']['CN']) && !empty($host)) { if ($validate_hostname == true) { $tlsa_record = shell_exec("timeout " . $timeout . " dig +short +dnssec +time=" . $timeout . " TLSA _" . escapeshellcmd($port) . "._tcp." . escapeshellcmd($host) . " 2>&1 | head -n 1"); if (!empty($tlsa_record)) { @@ -1061,6 +1219,34 @@ function cert_parse_json($raw_cert_data, $raw_next_cert_data=null, $host=null, $ +function csr_parse_json($csr) { + //if csr or cert is pasted in form tis function parses the csr or it send the cert to cert_parse. + global $random_blurp; + global $timeout; + $result = array(); + if (strpos($csr, "BEGIN CERTIFICATE REQUEST") !== false) { + $cert_data = openssl_csr_get_public_key($csr); + $cert_details = openssl_pkey_get_details($cert_data); + $cert_key = $cert_details['key']; + $cert_subject = openssl_csr_get_subject($csr); + $result["subject"] = $cert_subject; + $result["key"] = $cert_key; + $result["details"] = $cert_details; + if ($cert_details) { + $result["csr_pem"] = $csr; + $sans = get_sans_from_csr($csr); + if(count($sans) > 1) { + $result["csr_sans"] = $sans; + } + } + } elseif (strpos($csr, "BEGIN CERTIFICATE") !== false) { + $result = cert_parse_json($csr, null, null, null, null, true); + } else { + $result = array("error" => "data not valid csr"); + } + return $result; +} + diff --git a/functions/textual.php b/functions/textual.php index 0d05ded..0282aba 100644 --- a/functions/textual.php +++ b/functions/textual.php @@ -15,6 +15,7 @@ // along with this program. If not, see <http://www.gnu.org/licenses/>. function pre_dump($var) { + //this function is amazing whilst debugging. echo "<pre>"; var_dump($var); echo "</pre>"; @@ -33,30 +34,34 @@ function utf8encodeNestedArray($arr) { return $encoded_arr; } +//two helper functions to check if string starts or end with, from stack overflow. function startsWith($haystack, $needle) { - // search backwards starting from haystack length characters from the end - return $needle === "" || strrpos($haystack, $needle, -strlen($haystack)) !== FALSE; + // search backwards starting from haystack length characters from the end + return $needle === "" || strrpos($haystack, $needle, -strlen($haystack)) !== FALSE; } function endsWith($haystack, $needle) { - // search forward starting from end minus needle length characters - if(!empty($haystack)) { - return $needle === "" || strpos($haystack, $needle, strlen($haystack) - strlen($needle)) !== FALSE; - } + // search forward starting from end minus needle length characters + if(!empty($haystack)) { + return $needle === "" || strpos($haystack, $needle, strlen($haystack) - strlen($needle)) !== FALSE; + } } function get_current_folder(){ - $url = $_SERVER['REQUEST_URI']; - $parts = explode('/',$url); - $folder = ''; - for ($i = 0; $i < count($parts) - 1; $i++) { - $folder .= $parts[$i] . "/"; - } - return $folder; + //not current OS folder, but current web folder. + //used for relative links and css/js files + $url = $_SERVER['REQUEST_URI']; + $parts = explode('/',$url); + $folder = ''; + for ($i = 0; $i < count($parts) - 1; $i++) { + $folder .= $parts[$i] . "/"; + } + return $folder; } $current_folder = get_current_folder(); function gen_uuid() { + //from stack overflow. return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', // 32 bits for "time_low" mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), diff --git a/functions/variables.php b/functions/variables.php index 9e598e8..763360e 100644 --- a/functions/variables.php +++ b/functions/variables.php @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. -# timeout in seconds +# timeout in seconds, used globally (curl, shell commands, etc) $timeout = 2; # max chain length (big chain slows down checks) @@ -23,27 +23,31 @@ $max_chain_length = 10; # Don't change stuff down here. date_default_timezone_set('UTC'); -$version = 2.9; +$version = 3.0; -ini_set('default_socket_timeout', 2); +ini_set('default_socket_timeout', $timeout); -$random_blurp = rand(1000,99999); +//used for random filenames in /tmp in crl and ocsp checks +$random_blurp = rand(10,99999); // 2015-09-21 http://www.certificate-transparency.org/known-logs -$ct_urls = ["https://ct.ws.symantec.com", - "https://ct.googleapis.com/pilot", - "https://ct.googleapis.com/aviator", - "https://ct.googleapis.com/rocketeer", - "https://ct1.digicert-ct.com/log", - "https://ct.izenpe.com", - "https://ctlog.api.venafi.com", - "https://log.certly.io"]; +// $ct_urls = ["https://ct.ws.symantec.com", +// "https://ct.googleapis.com/pilot", +// "https://ct.googleapis.com/aviator", +// "https://ct.googleapis.com/rocketeer", +// "https://ct1.digicert-ct.com/log", +// "https://ct.izenpe.com", +// "https://ctlog.api.venafi.com", +// "https://log.certly.io"]; +$ct_urls = ["https://ct.googleapis.com/aviator"]; + # 2014-11-10 (nov) from wikipedia $ev_oids = array("1.3.6.1.4.1.34697.2.1", "1.3.6.1.4.1.34697.2.2", "1.3.6.1.4.1.34697.2.3", "1.3.6.1.4.1.34697.2.4", "1.2.40.0.17.1.22", "2.16.578.1.26.1.3.3", "1.3.6.1.4.1.17326.10.14.2.1.2", "1.3.6.1.4.1.17326.10.8.12.1.2", "1.3.6.1.4.1.6449.1.2.1.5.1", "2.16.840.1.114412.2.1", "2.16.840.1.114412.1.3.0.2", "2.16.528.1.1001.1.1.1.12.6.1.1.1", "2.16.840.1.114028.10.1.2", "0.4.0.2042.1.4", "0.4.0.2042.1.5", "1.3.6.1.4.1.13177.10.1.3.10", "1.3.6.1.4.1.14370.1.6", "1.3.6.1.4.1.4146.1.1", "2.16.840.1.114413.1.7.23.3", "1.3.6.1.4.1.14777.6.1.1", "2.16.792.1.2.1.1.5.7.1.9", "1.3.6.1.4.1.22234.2.5.2.3.1", "1.3.6.1.4.1.782.1.2.1.8.1", "1.3.6.1.4.1.8024.0.2.100.1.2", "1.2.392.200091.100.721.1", "2.16.840.1.114414.1.7.23.3", "1.3.6.1.4.1.23223.2", "1.3.6.1.4.1.23223.1.1.1", "2.16.756.1.83.21.0", "2.16.756.1.89.1.2.1.1", "2.16.840.1.113733.1.7.48.1", "2.16.840.1.114404.1.1.2.4.1", "2.16.840.1.113733.1.7.23.6", "1.3.6.1.4.1.6334.1.100.1", "2.16.840.1.114171.500.9", "1.3.6.1.4.1.36305.2"); function parse_hostname($u_hostname){ + # parses the URL and if no extea IP given, returns all A/AAAA records for that IP. # format raymii.org:1.2.34.56 should do SNI request to that ip. # parts[0]=host, parts[1]=ip $port = 0; @@ -87,6 +91,7 @@ function parse_hostname($u_hostname){ } function choose_endpoint($ips, $host, $port, $fastcheck) { + //if we detect multiple A/AAAA records, then show a page to choose the endpoint global $version; echo "<div id='page-content-wrapper'>\n"; echo "<div class='container-fluid'>\n"; @@ -100,6 +105,7 @@ function choose_endpoint($ips, $host, $port, $fastcheck) { echo "\">SSL Decoder</a></h1>\n"; echo "</div>\n"; } + //this div is hidden and only shown when an endpoint is choosen. echo "<div id='preloader'>\n"; echo "<p>\n"; echo "<img src=\""; @@ -112,7 +118,7 @@ function choose_endpoint($ips, $host, $port, $fastcheck) { echo "<div id='resultDiv'></div>\n"; echo "<div class='content' id='choose_endp'>\n<section id='choose_endpoint'>\n"; echo "<header>\n<h2>Multiple endpoints for " . htmlspecialchars($host) . "</h2>\n</header>\n"; - echo "<p>We've found multiple results for " . htmlspecialchars($host) . ". Please choose the host you want to scan from the list below:</p>\n<br>\n"; + echo "<p>We've found multiple A or AAAA records for " . htmlspecialchars($host) . ". Please choose the host you want to scan from the list below:</p>\n<br>\n"; echo "<ul>\n"; foreach ($ips as $ip) { echo "<li>"; @@ -121,6 +127,7 @@ function choose_endpoint($ips, $host, $port, $fastcheck) { echo "?host="; echo htmlspecialchars($host); echo ":"; + //ipv6 url's require [1234::5678] format if ($ip['type'] == 'A') { echo htmlspecialchars($ip['ip']); } elseif ($ip['type'] == 'AAAA') { diff --git a/functions/verify_certifitcate.php b/functions/verify_certifitcate.php index e639cff..4e23fc0 100644 --- a/functions/verify_certifitcate.php +++ b/functions/verify_certifitcate.php @@ -15,55 +15,58 @@ // along with this program. If not, see <http://www.gnu.org/licenses/>. function cert_hash($hash_alg, $raw_cert_to_hash) { + //returns the hash of the a certificate. Same as "openssl alg" cli. $cert_hash = hash($hash_alg, base64_decode($raw_cert_to_hash)); return $cert_hash; } function verify_certificate_hostname($raw_cert, $host) { - $cert_data = openssl_x509_parse($raw_cert); - if ($cert_data['subject']['CN']) { - $cert_host_names = []; - $cert_host_names[] = $cert_data['subject']['CN']; - if ($cert_data['extensions']['subjectAltName']) { - foreach ( explode("DNS:", $cert_data['extensions']['subjectAltName']) as $altName ) { - foreach (explode(",", $altName) as $key => $value) { - if ( !empty(str_replace(',', "", "$value"))) { - $cert_host_names[] = str_replace(" ", "", str_replace(',', "", "$value")); - } - } - } + //validates hostname to check with hostnames in certificate CN or subjectAltNames + $cert_data = openssl_x509_parse($raw_cert); + if ($cert_data['subject']['CN']) { + $cert_host_names = []; + $cert_host_names[] = $cert_data['subject']['CN']; + if ($cert_data['extensions']['subjectAltName']) { + foreach ( explode("DNS:", $cert_data['extensions']['subjectAltName']) as $altName ) { + foreach (explode(",", $altName) as $key => $value) { + if ( !empty(str_replace(',', "", "$value"))) { + $cert_host_names[] = str_replace(" ", "", str_replace(',', "", "$value")); + } } - foreach ($cert_host_names as $key => $hostname) { - if (strpos($hostname, "*.") === 0) { -// wildcard hostname from cert - if (explode(".", $host, 2)[1] == explode(".", $hostname, 2)[1] ) { -// split cert name and host name on . and compare everything after the first dot - return true; - } - } -// no wildcard, just regular match - if ($host == $hostname) { - return true; - } + } + } + foreach ($cert_host_names as $key => $hostname) { + if (strpos($hostname, "*.") === 0) { + // wildcard hostname from cert + if (explode(".", $host, 2)[1] == explode(".", $hostname, 2)[1] ) { + // split cert name and host name on . and compare everything after the first dot + return true; } -// no match - return false; + } + // no wildcard, just regular match + if ($host == $hostname) { + return true; + } } + // no match + return false; + } } function verify_cert_issuer_by_subject_hash($raw_cert_data, $raw_next_cert_data) { + //checks if the issuer of given cert is the same as the subject of the other cert, thus validating if cert 1 was signed by cert 2. global $random_blurp; + global $timeout; $tmp_dir = "/tmp/"; openssl_x509_export_to_file($raw_next_cert_data, $tmp_dir.$random_blurp.'.cert_issuer.pem'); openssl_x509_export_to_file($raw_cert_data, $tmp_dir.$random_blurp.'.cert_client.pem'); -//echo htmlspecialchars('openssl ocsp -no_nonce -CAfile '.$root_ca.' -issuer '.$tmp_dir.$random_blurp.'.cert_issuer.pem -cert '.$tmp_dir.$random_blurp.'.cert_client.pem -url "'. escapeshellcmd($ocsp_uri).'" 2>&1'); - - $cert_issuer_hash = shell_exec('openssl x509 -noout -issuer_hash -in '.$tmp_dir.$random_blurp.'.cert_client.pem 2>&1'); - $issuer_subject_hash = shell_exec('openssl x509 -noout -subject_hash -in '.$tmp_dir.$random_blurp.'.cert_issuer.pem 2>&1'); + $cert_issuer_hash = shell_exec('timeout ' . $timeout . ' openssl x509 -noout -issuer_hash -in '.$tmp_dir.$random_blurp.'.cert_client.pem 2>&1'); + $issuer_subject_hash = shell_exec('timeout ' . $timeout . ' openssl x509 -noout -subject_hash -in '.$tmp_dir.$random_blurp.'.cert_issuer.pem 2>&1'); + //remove those temp files. unlink($tmp_dir.$random_blurp.'.cert_client.pem'); unlink($tmp_dir.$random_blurp.'.cert_issuer.pem'); if ( $cert_issuer_hash == $issuer_subject_hash ) { @@ -75,21 +78,27 @@ function verify_cert_issuer_by_subject_hash($raw_cert_data, $raw_next_cert_data) function cert_signature_algorithm($raw_cert_data) { $cert_read = openssl_x509_read($raw_cert_data); + //if param 3 is FALSE, $out is filled with both the PEM file as wel all the contents of `openssl x509 -noout -text -in cert.pem. + //we use that to get the signature alg. openssl_x509_export($cert_read, $out, FALSE); $signature_algorithm = null; - if(preg_match('/^\s+Signature Algorithm:\s*(.*)\s*$/m', $out, $match)) $signature_algorithm = $match[1]; + if(preg_match('/^\s+Signature Algorithm:\s*(.*)\s*$/m', $out, $match)) { + $signature_algorithm = $match[1]; + } return($signature_algorithm); } function spki_hash($raw_cert_data) { - global $random_blurp; - $tmp_dir = '/tmp/'; - openssl_x509_export_to_file($raw_cert_data, $tmp_dir.$random_blurp.'.cert_client.pem'); - $output = shell_exec('openssl x509 -noout -in '.$tmp_dir.$random_blurp.'.cert_client.pem -pubkey | openssl asn1parse -noout -inform pem -out '.$tmp_dir.$random_blurp.'.public.key; openssl dgst -sha256 -binary '. $tmp_dir . $random_blurp . '.public.key | openssl enc -base64 2>&1'); - - unlink($tmp_dir.$random_blurp.'.cert_client.pem'); - unlink($tmp_dir.$random_blurp.'.public.key'); - return(trim(htmlspecialchars($output))); + global $timeout; + global $random_blurp; + $tmp_dir = '/tmp/'; + //below command returns the SPKI hash of a public key. + openssl_x509_export_to_file($raw_cert_data, $tmp_dir.$random_blurp.'.cert_client.pem'); + $output = shell_exec('timeout ' . $timeout . 'openssl x509 -noout -in '.$tmp_dir.$random_blurp.'.cert_client.pem -pubkey | openssl asn1parse -noout -inform pem -out '.$tmp_dir.$random_blurp.'.public.key; openssl dgst -sha256 -binary '. $tmp_dir . $random_blurp . '.public.key | openssl enc -base64 2>&1'); + //remove those files again. + unlink($tmp_dir.$random_blurp.'.cert_client.pem'); + unlink($tmp_dir.$random_blurp.'.public.key'); + return(trim(htmlspecialchars($output))); } ?>
\ No newline at end of file |