如何检查域名是否拥有SSL证书?

15

是否可以获取域名(例如www.example.com)的HTTPS准备情况?

我需要验证一些URL,以确定它们是否具有SSL证书。我知道可以使用$_SERVER['HTTPS']来检查我们的服务器详细信息,但是如何为其他域名实现相同的功能呢?

非常感谢您的任何帮助。


1
尝试通过任何方法(如curl)连接到https://www.example.com - deceze
@deceze 我猜也是,但我怀疑这样可以吗?我的意思是这种检测方式正确吗? - Krishna Mohan
没有其他的方法。要么你可以通过SSL连接,要么就不行。唯一的方法是尝试一下。 - deceze
请注意,一些网站会从https重定向到http。 - usr1234567
谢谢 @deceze,我会研究一下的。 - Krishna Mohan
@deceze,我针对我的需求发布了一个答案。如果您有时间,请让我知道我的帖子是否有任何错误。谢谢 :) - Krishna Mohan
4个回答

20

最后,我得到了以下代码:

$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;
如果为域名启用了 HTTPS,则 var_dump($var)输出如下所示:
resource(4) of type (OpenSSL X.509) 

如果不存在,它会返回NULL

我已经检查了几个域名。它似乎工作正常。我希望它能帮助到某些人。


5
这种方法的危险在于您无法确定证书是否有效。许多服务器为某些域配置了SSL(使用自签名证书),如果没有匹配的域,这些服务器通常会返回第一个可用的证书(以支持不支持SNI的客户端)。 - Felix Sandström

15

此函数不仅会检查域名是否具有SSL证书,还会确认证书是否与请求的域名匹配。

最重要的部分是函数openssl_x509_parse,它解析证书并将所有细节作为数组返回。

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;
}

由于某些原因,我使用了一些变通方法,但是$namepart总是有4个元素而不是2个。 - brace110
如果证书有多个备用名称,例如在 https://example.com/ 上尝试时它将无法工作。 - the_nuts
我添加了以下代码以使其正常工作:if(!$res && strpos($cert['extensions']['subjectAltName']??'',"DNS:$domain") !== false) {return true;}(它无法处理altName为通配符的情况) - the_nuts
有没有办法下载证书并将其验证为 CA 列表之一? - Richard87

6

这里是我在github的某个地方找到的另一种解决方案(无法再次找到存储库...)

这是一种类似于接受答案的方法,但使用fsockopen测试我们是否可以在443端口上建立SSL连接。如果连接被拒绝,则该域名没有SSL证书。

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') );

为什么不直接使用以下代码 function has_ssl( $domain ) { return @fsockopen( 'ssl://' . $domain, 443, $errno, $errstr, 30 ); } - Matt
1
Matt,你是对的;fclose()行不是OP要求的一部分,因为它不会对正确的函数响应产生贡献。但我认为关闭远程套接字也是一个好习惯,以确保所有内容都被清理干净 :) - Philipp
这个答案不应该被使用。端口是打开的并不意味着那里有一个网站,也不意味着该网站拥有有效的HTTPS证书。 - vrijdenker

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接