Example #1
0
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];
    if (filter_var(preg_replace('/[^A-Za-z0-9\\.\\:-]/', '', $ip), FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
        $result["warning"][] = "You are testing an IPv6 host. Due to <a href=\"https://rt.openssl.org/Ticket/Display.html?id=1365&user=guest&pass=guest\">bugs</a> in OpenSSL's command line tools the results will be inaccurate. Known incorrect are OCSP Stapling, TLS_FALLBACK_SCSV and SSL Compression results, others may also be incorrect.";
    }
    $result["checked_hostname"] = $host;
    //chain
    if (isset($context_meta)) {
        if (isset($chain_data)) {
            $chain_length = count($chain_data);
            $certificate_chain = array();
            if ($chain_length <= 10) {
                for ($i = 0; $i < $chain_length; $i++) {
                    if (openssl_x509_parse($chain_data[$i])['issuer']['CN'] && openssl_x509_parse($chain_data[$i])['subject']['CN']) {
                        $result["chain"][$i]["name"] = openssl_x509_parse($chain_data[$i])['subject']['CN'];
                        $result["chain"][$i]["issuer"] = openssl_x509_parse($chain_data[$i])['issuer']['CN'];
                        $export_pem = "";
                        openssl_x509_export($chain_data[$i], $export_pem);
                        array_push($certificate_chain, $export_pem);
                        if (openssl_x509_parse($chain_data[$i])['issuer']['CN'] == openssl_x509_parse($chain_data[$i + 1])['subject']['CN']) {
                            continue;
                        } else {
                            if ($i != $chain_length - 1) {
                                $result["chain"][$i]["error"] = "Issuer does not match the next certificate CN. Chain order is probably wrong.";
                                $result["warning"][] = "Issuer does not match the next certificate CN. Chain order is probably wrong.";
                            }
                        }
                    }
                }
            }
            // chain validation
            file_put_contents('/tmp/verify_cert.' . $random_blurp . '.pem', implode("\n", array_reverse($certificate_chain)) . PHP_EOL, FILE_APPEND);
            $verify_output = 0;
            $verify_exit_code = 0;
            $verify_exec = exec(escapeshellcmd('openssl verify -verbose -purpose any -CAfile ' . getcwd() . '/cacert.pem /tmp/verify_cert.' . $random_blurp . '.pem') . "| grep -v OK", $verify_output, $verify_exit_code);
            if ($verify_exit_code != 1) {
                $result["validation"]["status"] = "failed";
                $result["validation"]["error"] = "Error: Validating certificate chain failed: " . str_replace('/tmp/verify_cert.' . $random_blurp . '.pem: ', '', implode("\n", $verify_output));
                $result["warning"][] = "Validating certificate chain failed. Probably non-trusted root/self signed certificate, or the chain order is wrong.";
            } else {
                $result["validation"]["status"] = "success";
            }
            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)) {
            $addr = inet_pton(preg_replace('/[^A-Za-z0-9\\.\\:-]/', '', $ip));
            $unpack = unpack('H*hex', $addr);
            $hex = $unpack['hex'];
            $arpa = implode('.', array_reverse(str_split($hex))) . '.ip6.arpa';
            if (!empty(dns_get_record($arpa, DNS_PTR)[0]["target"])) {
                $result["hostname"] = dns_get_record($arpa, DNS_PTR)[0]["target"];
            } else {
                $result["hostname"] = "{$host} (No PTR available).";
            }
        } elseif (filter_var(preg_replace('/[^A-Za-z0-9\\.\\:-]/', '', $ip), FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
            if (!empty(gethostbyaddr(preg_replace('/[^A-Za-z0-9\\.\\:-]/', '', $ip)))) {
                $result["hostname"] = gethostbyaddr(preg_replace('/[^A-Za-z0-9\\.\\:-]/', '', $ip));
            } else {
                $result["hostname"] = "{$host} (No PTR available).";
            }
        } else {
            $result["hostname"] = "{$host} (No PTR available).";
        }
        $result["port"] = $port;
        if ($fastcheck == 0) {
            //heartbleed
            $result['heartbleed'] = test_heartbleed($ip, $port);
            if ($result['heartbleed'] == "vulnerable") {
                $result["warning"][] = 'Vulnerable to the Heartbleed bug. Please update your OpenSSL ASAP!';
            }
            // compression
            $compression = conn_compression($host, $ip, $port);
            if ($compression == false) {
                $result["compression"] = false;
            } else {
                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)
                    $result["warning"][] = 'SSL compression not tested because of <a href="https://rt.openssl.org/Ticket/Display.html?id=1365&user=guest&pass=guest">bugs</a> in the OpenSSL tools and IPv6.';
                } else {
                    $result["compression"] = true;
                    $result["warning"][] = 'SSL compression enabled. Please disable to prevent attacks like CRIME.';
                }
            }
            // protocols
            $result["protocols"] = array_reverse(ssl_conn_protocols($host, $ip, $port));
            foreach ($result["protocols"] as $key => $value) {
                if ($value == true) {
                    if ($key == "sslv2") {
                        $result["warning"][] = 'SSLv2 supported. Please disable ASAP and upgrade to a newer protocol like TLSv1.2.';
                    }
                    if ($key == "sslv3") {
                        $result["warning"][] = 'SSLv3 supported. Please disable and upgrade to a newer protocol like TLSv1.2.';
                    }
                } else {
                    if ($key == "tlsv1.2") {
                        $result["warning"][] = 'TLSv1.2 unsupported. Please enable TLSv1.2.';
                    }
                }
            }
            // ciphersuites
            $ciphersuites_to_test = array('ECDHE-RSA-AES256-GCM-SHA384', 'ECDHE-ECDSA-AES256-GCM-SHA384', 'ECDHE-RSA-AES256-SHA384', 'ECDHE-ECDSA-AES256-SHA384', 'ECDHE-RSA-AES256-SHA', 'ECDHE-ECDSA-AES256-SHA', 'SRP-DSS-AES-256-CBC-SHA', 'SRP-RSA-AES-256-CBC-SHA', 'SRP-AES-256-CBC-SHA', 'DH-DSS-AES256-GCM-SHA384', 'DHE-DSS-AES256-GCM-SHA384', 'DH-RSA-AES256-GCM-SHA384', 'DHE-RSA-AES256-GCM-SHA384', 'DHE-RSA-AES256-SHA256', 'DHE-DSS-AES256-SHA256', 'DH-RSA-AES256-SHA256', 'DH-DSS-AES256-SHA256', 'DHE-RSA-AES256-SHA', 'DHE-DSS-AES256-SHA', 'DH-RSA-AES256-SHA', 'DH-DSS-AES256-SHA', 'DHE-RSA-CAMELLIA256-SHA', 'DHE-DSS-CAMELLIA256-SHA', 'DH-RSA-CAMELLIA256-SHA', 'DH-DSS-CAMELLIA256-SHA', 'ECDH-RSA-AES256-GCM-SHA384', 'ECDH-ECDSA-AES256-GCM-SHA384', 'ECDH-RSA-AES256-SHA384', 'ECDH-ECDSA-AES256-SHA384', 'ECDH-RSA-AES256-SHA', 'ECDH-ECDSA-AES256-SHA', 'AES256-GCM-SHA384', 'AES256-SHA256', 'AES256-SHA', 'CAMELLIA256-SHA', 'PSK-AES256-CBC-SHA', 'ECDHE-RSA-AES128-GCM-SHA256', 'ECDHE-ECDSA-AES128-GCM-SHA256', 'ECDHE-RSA-AES128-SHA256', 'ECDHE-ECDSA-AES128-SHA256', 'ECDHE-RSA-AES128-SHA', 'ECDHE-ECDSA-AES128-SHA', 'SRP-DSS-AES-128-CBC-SHA', 'SRP-RSA-AES-128-CBC-SHA', 'SRP-AES-128-CBC-SHA', 'DH-DSS-AES128-GCM-SHA256', 'DHE-DSS-AES128-GCM-SHA256', 'DH-RSA-AES128-GCM-SHA256', 'DHE-RSA-AES128-GCM-SHA256', 'DHE-RSA-AES128-SHA256', 'DHE-DSS-AES128-SHA256', 'DH-RSA-AES128-SHA256', 'DH-DSS-AES128-SHA256', 'DHE-RSA-AES128-SHA', 'DHE-DSS-AES128-SHA', 'DH-RSA-AES128-SHA', 'DH-DSS-AES128-SHA', 'DHE-RSA-SEED-SHA', 'DHE-DSS-SEED-SHA', 'DH-RSA-SEED-SHA', 'DH-DSS-SEED-SHA', 'DHE-RSA-CAMELLIA128-SHA', 'DHE-DSS-CAMELLIA128-SHA', 'DH-RSA-CAMELLIA128-SHA', 'DH-DSS-CAMELLIA128-SHA', 'ECDH-RSA-AES128-GCM-SHA256', 'ECDH-ECDSA-AES128-GCM-SHA256', 'ECDH-RSA-AES128-SHA256', 'ECDH-ECDSA-AES128-SHA256', 'ECDH-RSA-AES128-SHA', 'ECDH-ECDSA-AES128-SHA', 'AES128-GCM-SHA256', 'AES128-SHA256', 'AES128-SHA', 'SEED-SHA', 'CAMELLIA128-SHA', 'IDEA-CBC-SHA', 'PSK-AES128-CBC-SHA', 'ECDHE-RSA-RC4-SHA', 'ECDHE-ECDSA-RC4-SHA', 'ECDH-RSA-RC4-SHA', 'ECDH-ECDSA-RC4-SHA', 'RC4-SHA', 'RC4-MD5', 'PSK-RC4-SHA', 'ECDHE-RSA-DES-CBC3-SHA', 'ECDHE-ECDSA-DES-CBC3-SHA', 'SRP-DSS-3DES-EDE-CBC-SHA', 'SRP-RSA-3DES-EDE-CBC-SHA', 'SRP-3DES-EDE-CBC-SHA', 'EDH-RSA-DES-CBC3-SHA', 'EDH-DSS-DES-CBC3-SHA', 'DH-RSA-DES-CBC3-SHA', 'DH-DSS-DES-CBC3-SHA', 'ECDH-RSA-DES-CBC3-SHA', 'ECDH-ECDSA-DES-CBC3-SHA', 'DES-CBC3-SHA', 'PSK-3DES-EDE-CBC-SHA', 'EDH-RSA-DES-CBC-SHA', 'EDH-DSS-DES-CBC-SHA', 'DH-RSA-DES-CBC-SHA', 'DH-DSS-DES-CBC-SHA', 'DES-CBC-SHA', 'EXP-EDH-RSA-DES-CBC-SHA', 'EXP-EDH-DSS-DES-CBC-SHA', 'EXP-DH-RSA-DES-CBC-SHA', 'EXP-DH-DSS-DES-CBC-SHA', 'EXP-DES-CBC-SHA', 'EXP-RC2-CBC-MD5', 'EXP-RC4-MD5', 'ECDHE-RSA-NULL-SHA', 'ECDHE-ECDSA-NULL-SHA', 'AECDH-NULL-SHA', 'ECDH-RSA-NULL-SHA', 'ECDH-ECDSA-NULL-SHA', 'NULL-SHA256', 'NULL-SHA', 'NULL-MD5');
            $tested_ciphersuites = ssl_conn_ciphersuites($host, $ip, $port, $ciphersuites_to_test);
            $result["supported_ciphersuites"] = array();
            foreach ($tested_ciphersuites as $key => $value) {
                if ($value == true) {
                    $result["supported_ciphersuites"][] = $key;
                }
            }
            // tls_fallback_scsv
            $fallback = tls_fallback_scsv($host, $ip, $port);
            if ($fallback['protocol_count'] == 1) {
                $result["tls_fallback_scsv"] = "Only 1 protocol enabled, fallback not possible, TLS_FALLBACK_SCSV not required.";
            } else {
                if ($fallback['tls_fallback_scsv_support'] == 1) {
                    $result["tls_fallback_scsv"] = "supported";
                } else {
                    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)
                        $result["warning"][] = 'TLS_FALLBACK_SCSV not tested because of <a href="https://rt.openssl.org/Ticket/Display.html?id=1365&user=guest&pass=guest">bugs</a> in the OpenSSL tools and IPv6.';
                    } else {
                        $result["tls_fallback_scsv"] = "unsupported";
                        $result["warning"][] = "TLS_FALLBACK_SCSV unsupported. Please upgrade OpenSSL to enable. This offers downgrade attack protection.";
                    }
                }
            }
            //hsts
            $headers = server_http_headers($host, $ip, $port);
            if ($headers["strict-transport-security"]) {
                if (is_array($headers["strict-transport-security"])) {
                    $result["strict_sransport-security"] = substr($headers["strict-transport-security"][0], 0, 50);
                } else {
                    $result["strict_transport_security"] = substr($headers["strict-transport-security"], 0, 50);
                }
            } else {
                $result["strict_transport_security"] = 'not set';
                $result["warning"][] = "HTTP Strict Transport Security not set.";
            }
            //hpkp
            if ($headers["public-key-pins"]) {
                if (is_array($headers["public-key-pins"])) {
                    $result["public_key_pins"] = substr($headers["public-key-pins"][0], 0, 255);
                } else {
                    $result["public_key_pins"] = substr($headers["public-key-pins"], 0, 255);
                }
            } else {
                $result["public_key_pins"] = 'not set';
            }
            if ($headers["public-key-pins-report-only"]) {
                if (is_array($headers["public-key-pins-report-only"])) {
                    $result["public_key_pins_report_only"] = substr($headers["public-key-pins-report-only"][0], 0, 255);
                } else {
                    $result["public_key_pins_report_only"] = substr($headers["public-key-pins-report-only"], 0, 255);
                }
            }
            // ocsp stapling
            $stapling = ocsp_stapling($host, $ip, $port);
            if ($stapling["working"] == 1) {
                $result["ocsp_stapling"] = $stapling;
            } else {
                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)
                    $result["warning"][] = 'OCSP Stapling not tested because of <a href="https://rt.openssl.org/Ticket/Display.html?id=1365&user=guest&pass=guest">bugs</a> in the OpenSSL tools and IPv6.';
                } else {
                    $result["ocsp_stapling"] = "not set";
                    $result["warning"][] = "OCSP Stapling not enabled.";
                }
            }
            $result["heartbeat"] = heartbeat_test($host, $port);
        }
        $result["openssl_version"] = shell_exec("openssl version");
        $result["datetime_rfc2822"] = shell_exec("date --rfc-2822");
    }
    return $result;
}
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;
    $result = array();
    $cert_data = openssl_x509_parse($raw_cert_data);
    if (isset($raw_next_cert_data)) {
        $next_cert_data = openssl_x509_parse($raw_next_cert_data);
    }
    $today = date("Y-m-d");
    //cert
    if (isset($cert_data)) {
        // purposes
        $purposes = array();
        foreach ($cert_data['purposes'] as $key => $purpose) {
            $purposes[$purpose[2]]["ca"] = $purpose[1];
            $purposes[$purpose[2]]["general"] = $purpose[0];
        }
        unset($cert_data['purposes']);
        $cert_data['purposes'] = $purposes;
        $result["cert_data"] = $cert_data;
    }
    // valid from
    if (!empty($result['cert_data']['validFrom_time_t'])) {
        if ($today < date(DATE_RFC2822, $result['cert_data']['validFrom_time_t'])) {
            $result['cert_issued_in_future'] = false;
        } else {
            $result['cert_issued_in_future'] = true;
            $result['warning'][] = "Certificate issue date is in the future: " . date(DATE_RFC2822, $data['cert_data']['validFrom_time_t']);
        }
    }
    // expired
    if (!empty($cert_data['validTo_time_t'])) {
        if ($today > date(DATE_RFC2822, $cert_data['validFrom_time_t']) || strtotime($today) < strtotime(date(DATE_RFC2822, $cert_data['validTo_time_t']))) {
            $result['cert_expired'] = false;
        } else {
            $result['cert_expired'] = true;
            $result['warning'][] = "Certificate expired! Expiration date: " . date(DATE_RFC2822, $cert_data['validTo_time_t']);
        }
    }
    // almost expired
    if (!empty($cert_data['validTo_time_t'])) {
        $certExpiryDate = strtotime(date(DATE_RFC2822, $cert_data['validTo_time_t']));
        $certExpiryDiff = $certExpiryDate - strtotime($today);
        if ($certExpiryDiff < 2592000) {
            $result['cert_expires_in_less_than_thirty_days'] = true;
            $result['warning'][] = "Certificate expires in " . round($certExpiryDiff / 84600) . " days!. Expiration date: " . date(DATE_RFC2822, $certExpiryDate);
        } else {
            $result['cert_expires_in_less_than_thirty_days'] = false;
        }
    }
    if (array_search(explode("Policy: ", explode("\n", $cert_data['extensions']['certificatePolicies'])[0])[1], $ev_oids)) {
        $result["validation_type"] = "extended";
    } else {
        if (isset($cert_data['subject']['O'])) {
            $result["validation_type"] = "organization";
        } else {
            if (isset($cert_data['subject']['CN'])) {
                $result["validation_type"] = "domain";
            }
        }
    }
    // issuer
    if ($raw_next_cert_data) {
        if (verify_cert_issuer_by_subject_hash($raw_cert_data, $raw_next_cert_data)) {
            $result["issuer_valid"] = true;
        } else {
            $result["issuer_valid"] = false;
            $result['warning'][] = "Provided certificate issuer does not match issuer in certificate. Sent chain order wrong.";
        }
    }
    // crl
    if (isset($cert_data['extensions']['crlDistributionPoints'])) {
        $result["crl"] = crl_verify_json($raw_cert_data);
        if (is_array($result["crl"])) {
            foreach ($result["crl"] as $key => $value) {
                if ($value["status"] == "revoked") {
                    $result['warning'][] = "Certificate revoked on CRL: " . $value['crl_uri'] . ". Revocation time: " . $value['revoked_on'] . ".";
                }
            }
        }
    } else {
        $result["crl"] = "No CRL URI found in certificate";
    }
    // ocsp
    if (isset($cert_data['extensions']['authorityInfoAccess'])) {
        $ocsp_uris = explode("OCSP - URI:", $cert_data['extensions']['authorityInfoAccess']);
        unset($ocsp_uris[0]);
        if (isset($ocsp_uris)) {
            if (isset($raw_next_cert_data)) {
                foreach ($ocsp_uris as $key => $ocsp_uri) {
                    $ocsp_uri = explode("\n", $ocsp_uri)[0];
                    $ocsp_uri = explode(" ", $ocsp_uri)[0];
                    $result["ocsp"]["{$key}"] = ocsp_verify_json($raw_cert_data, $raw_next_cert_data, $ocsp_uri);
                    if ($result['ocsp'][$key]["status"] == "revoked") {
                        $result['warning'][] = "Certificate revoked on OCSP: " . $result['ocsp'][$key]['ocsp_uri'] . ". Revocation time: " . $result['ocsp'][$key]['revocation_time'] . ".";
                    } elseif ($result['ocsp'][$key]["status"] == "unknown") {
                        $result['warning'][] = "OCSP error on: " . $result['ocsp'][$key]['ocsp_uri'] . ".";
                    }
                }
            } else {
                $result["ocsp"] = "No issuer cert provided. Unable to send OCSP request.";
            }
        } else {
            $result["ocsp"] = "No OCSP URI found in certificate";
        }
    } else {
        $result["ocsp"] = "No OCSP URI found in certificate";
    }
    // hostname validation
    if ($validate_hostname == true) {
        $result["hostname_checked"] = $host;
        if (isset($cert_data['subject']['CN'])) {
            if (verify_certificate_hostname($raw_cert_data, $host)) {
                $result["hostname_in_san_or_cn"] = "true";
            } else {
                $result["hostname_in_san_or_cn"] = "false";
                $result['warning'][] = "Hostname " . $host . " not found in certificate.";
            }
        }
    } else {
        $result["hostname_in_san_or_cn"] = "n/a; ca signing certificate";
    }
    //serial number
    if (isset($cert_data['serialNumber'])) {
        $serial = [];
        $sn = str_split(strtoupper(bcdechex($cert_data['serialNumber'])), 2);
        $sn_len = count($sn);
        foreach ($sn as $key => $s) {
            $serial[] = htmlspecialchars($s);
            if ($key != $sn_len - 1) {
                $serial[] = ":";
            }
        }
        $result["serialNumber"] = implode("", $serial);
    }
    // key details
    $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 = '/-----(.*)-----/';
    $replacement = '';
    $string = preg_replace($pattern, $replacement, $string);
    $pattern = '/\\n/';
    $replacement = '';
    $export_pem_preg = preg_replace($pattern, $replacement, $string);
    $export_pem_preg = wordwrap($export_pem_preg, 77, "\n", TRUE);
    $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);
    $result['hash']['sha384'] = cert_hash('sha384', $export_pem_preg);
    $result['hash']['sha512'] = cert_hash('sha512', $export_pem_preg);
    //TLSA check
    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)) {
                $tlsa = explode(" ", $tlsa_record, 4);
                $pattern = '/ /';
                $replacement = '';
                $result['tlsa']['tlsa_hash'] = trim(strtolower(preg_replace($pattern, $replacement, $tlsa[3])));
                $result['tlsa']['tlsa_usage'] = $tlsa[0];
                $result['tlsa']['tlsa_selector'] = $tlsa[1];
                $result['tlsa']['tlsa_matching_type'] = $tlsa[2];
                $result['tlsa']['error'] = 'none';
            } else {
                $result['tlsa']['error'] = 'No TLSA record found.';
                $result['tlsa']['example'] = '_' . htmlspecialchars($port) . '._tcp.' . htmlspecialchars($host) . ' IN TLSA 3 0 1 ' . $result['hash']['sha256'] . ';';
            }
        } else {
            $result['tlsa']['error'] = 'CA certificate, TLSA not applicable.';
        }
    }
    if (isset($key_details['rsa'])) {
        $result["key"]["type"] = "rsa";
        $result["key"]["bits"] = $key_details['bits'];
        if ($key_details['bits'] < 2048) {
            $result['warning'][] = $key_details['bits'] . " bit RSA key is not safe. Upgrade to at least 4096 bits.";
        }
        // weak debian key check
        $bin_modulus = $key_details['rsa']['n'];
        # blacklist format requires sha1sum of output from "openssl x509 -noout -modulus" including the Modulus= and newline.
        # create the blacklist:
        # https://packages.debian.org/source/squeeze/openssl-blacklist
        # svn co svn://svn.debian.org/pkg-openssl/openssl-blacklist/
        # find openssl-blacklist/trunk/blacklists/ -iname "*.db" -exec cat {} >> unsorted_blacklist.db \;
        # sort -u unsorted_blacklist.db > debian_blacklist.db
        $mod_sha1sum = sha1("Modulus=" . strtoupper(bin2hex($bin_modulus)) . "\n");
        $blacklist_file = fopen('inc/debian_blacklist.db', 'r');
        $key_in_blacklist = false;
        while (($buffer = fgets($blacklist_file)) !== false) {
            if (strpos($buffer, $mod_sha1sum) !== false) {
                $key_in_blacklist = true;
                break;
            }
        }
        fclose($blacklist_file);
        if ($key_in_blacklist == true) {
            $result["key"]["weak_debian_rsa_key"] = "true";
            $result['warning'][] = "Weak debian key found. Remove this key right now and create a new one.";
        }
    } else {
        if (isset($key_details['dsa'])) {
            $result["key"]["type"] = "dsa";
            $result["key"]["bits"] = $key_details['bits'];
        } else {
            if (isset($key_details['dh'])) {
                $result["key"]["type"] = "dh";
                $result["key"]["bits"] = $key_details['bits'];
            } else {
                if (isset($key_details['ec'])) {
                    $result["key"]["type"] = "ecdsa";
                    $result["key"]["bits"] = $key_details['bits'];
                } else {
                    $result["key"]["type"] = "unknown";
                    $result["key"]["bits"] = $key_details['bits'];
                }
            }
        }
    }
    // signature algorithm
    $result["key"]["signature_algorithm"] = cert_signature_algorithm($raw_cert_data);
    if ($result["key"]["signature_algorithm"] == "sha1WithRSAEncryption") {
        $result['warning'][] = "SHA-1 certificate. Upgrade (re-issue) to SHA-256 or better.";
    }
    if (isset($export_pem)) {
        $result["key"]["certificate_pem"] = $export_pem;
    }
    if (isset($key_details['key'])) {
        $result["key"]["public_key_pem"] = $key_details['key'];
        $result["key"]["spki_hash"] = spki_hash($export_pem);
    }
    return $result;
}