15

Is is possible to get the details like if a domain (say www.example.com) is HTTPS ready?

I need to validate some URLs, whether they have SSL certificate or not. I know, by using $_SERVER['HTTPS'] we can check our server details. but how can I achieve the same for other domains.

Any help would be much appreciated.

Krishna Mohan
  • 1,503
  • 3
  • 22
  • 28

4 Answers4

20

Finally, I've ended up with the following my code:

$stream = stream_context_create (array("ssl" => array("capture_peer_cert" => true)));
$read = fopen("https://www.example.com", "rb", false, $stream);
$cont = stream_context_get_params($read);
$var = ($cont["options"]["ssl"]["peer_certificate"]);
$result = (!is_null($var)) ? true : false;

If HTTPS is enabled for a domain, var_dump($var) gives the output as shown:

resource(4) of type (OpenSSL X.509) 

If it doesn't exist it returns NULL.

I've checked a few domains. It seems to be working fine. I hope it will help someone.

Angel Politis
  • 10,955
  • 14
  • 48
  • 66
Krishna Mohan
  • 1,503
  • 3
  • 22
  • 28
  • 5
    The danger with this approach is that you don't know if the certificate is valid or not. Many servers has SSL configured for some domains (ur using self signed certs) and it's common that these servers return the first available certificate if no domain match is made (to support clients without SNI support). – Felix Sandström Nov 17 '16 at 10:17
15

This function will not only check if the domain has a SSL certificate, but also confirms, if the certificate matches the requested domain.

The most important part is the function openssl_x509_parse which parses the certificate and returns all the details as array.

function has_ssl( $domain ) {
    $res = false;
    $stream = @stream_context_create( array( 'ssl' => array( 'capture_peer_cert' => true ) ) );
    $socket = @stream_socket_client( 'ssl://' . $domain . ':443', $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $stream );

    // If we got a ssl certificate we check here, if the certificate domain
    // matches the website domain.
    if ( $socket ) {
        $cont = stream_context_get_params( $socket );
        $cert_ressource = $cont['options']['ssl']['peer_certificate'];
        $cert = openssl_x509_parse( $cert_ressource );

        // Expected name has format "/CN=*.yourdomain.com"
        $namepart = explode( '=', $cert['name'] );

        // We want to correctly confirm the certificate even 
        // for subdomains like "www.yourdomain.com"
        if ( count( $namepart ) == 2 ) {
            $cert_domain = trim( $namepart[1], '*. ' );
            $check_domain = substr( $domain, -strlen( $cert_domain ) );
            $res = ($cert_domain == $check_domain);
        }
    }

    return $res;
}
Philipp
  • 10,240
  • 8
  • 59
  • 71
  • Worked with some work around, for some reason $namepart always had 4 elements instead of 2. – brace110 Feb 28 '17 at 11:11
  • It doesn't work if the certificate has multiple alternative names, for example try it on https://example.com/ – the_nuts May 16 '17 at 09:07
  • I added this to make it work: `if(!$res && strpos($cert['extensions']['subjectAltName']??'',"DNS:$domain") !== false) {return true;}` (it doesn't handle the case where an altName is a wildcard) – the_nuts May 16 '17 at 09:20
  • Any way we can download the certificate and validate it towards a list of CA's? – Richard87 Mar 18 '19 at 11:29
6

Here is a different solution that I found somewhere on github (cannot find the repo again...)

It's a similar approach to the accepted answer, but uses fsockopen to test, if we can establish a SSL connection on port 443. If the connection is refused then the domain does not have a ssl certificate.

function has_ssl( $domain ) {
    $ssl_check = @fsockopen( 'ssl://' . $domain, 443, $errno, $errstr, 30 );
    $res = !! $ssl_check;
    if ( $ssl_check ) { fclose( $ssl_check ); }
    return $res;
}

// Test it:
print_r( has_ssl('google.com') );
Philipp
  • 10,240
  • 8
  • 59
  • 71
  • Why not just `function has_ssl( $domain ) { return @fsockopen( 'ssl://' . $domain, 443, $errno, $errstr, 30 ); }`? – Matt Jun 13 '17 at 13:37
  • 1
    Matt, you are right; the `fclose()` line is not part of the OP requirement as it does not contribute to the correct function response. But I think it's good practice also to close the remote socket again just to be sure it's all cleaned up :) – Philipp Jan 27 '20 at 15:29
  • This answer shouldn't be used. The fact that a port is open doesn't mean that there's a website there and that the website has a valid HTTPS certificate. – vrijdenker Jan 18 '23 at 10:51
1

There's no way to check if the certificate is secure or not using PHP alone without API (green padlock)

But you can use this, which use a API: https://github.com/spatie/ssl-certificate

cs95
  • 379,657
  • 97
  • 704
  • 746
WtrndMOB
  • 29
  • 2