具有SNI的站点的ssl.get_server_certificate(服务器名称指示)

ser*_*a99 6 python ssl ssl-certificate sni

我正在尝试获取badssl.com子域的服务器证书(例如https://expired.badssl.com).

import ssl
ssl.get_server_certificate(('expired.badssl.com', 443))
Run Code Online (Sandbox Code Playgroud)

但是在检查上面生成的证书时,我看到证书有

身份:badssl-fallback-unknown-subdomain-or-no-sni

这意味着SNI失败了.如何获取badssl.com的不同子域的服务器证书?(我使用的是python 2.7.12)

ser*_*a99 12

找到了答案.

import ssl
hostname = "expired.badssl.com"
port = 443
conn = ssl.create_connection((hostname, port))
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
sock = context.wrap_socket(conn, server_hostname=hostname)
certificate = ssl.DER_cert_to_PEM_cert(sock.getpeercert(True))
Run Code Online (Sandbox Code Playgroud)


wir*_*hin 9

搜索“ Python ssl.get_server_certificate SNI”使我很容易找到这个答案。尽管OP自己的回答是正确的,但我想提供更多的见识,以供将来参考。

对于某些[hostname],使用Python 3.7进行休闲调用:

ssl.get_server_certificate(("example.com", 443)
Run Code Online (Sandbox Code Playgroud)

将以结尾为结尾的抱怨:

ssl.SSLError: [SSL: TLSV1_ALERT_INTERNAL_ERROR] tlsv1 alert internal error (_ssl.c:1045)
Run Code Online (Sandbox Code Playgroud)

通过使用该openssl s_client实用工具进行进一步的调查,可以发现那些get_server_certificate失败的相同[主机名] 也产生了以下命令:

openssl s_client -showcerts -connect example.com:443
Run Code Online (Sandbox Code Playgroud)

失败并出现此错误:

SSL23_GET_SERVER_HELLO:tlsv1 alert internal error:s23_clnt.c:802
Run Code Online (Sandbox Code Playgroud)

请注意,错误消息类似于python代码返回的错误消息。

使用-servername开关可以达到目的:

openssl s_client -showcerts -connect example.com:443 -servername example.com
Run Code Online (Sandbox Code Playgroud)

得出的结论是,被调查的主机名是指使用SNI的安全服务器(有关SNI Wikipedia文章的含义,这是一个很好的解释)。

因此,再次切换到Python并查看该get_server_certificate方法,检查ssl模块源(为方便起见,在这里),您会发现该函数包括此调用:

context.wrap_socket(sock)
Run Code Online (Sandbox Code Playgroud)

如果没有server_hostname=hostnamekey参数,这当然意味着get_server_certificate不能用于查询SNI服务器。需要更多的努力:

hostname = "example.com"
port = 443

context = ssl.create_default_context()

with socket.create_connection((hostname, port)) as sock:
    with context.wrap_socket(sock, server_hostname=hostname) as sslsock:

        der_cert = sslsock.getpeercert(True)

        # from binary DER format to PEM
        pem_cert = ssl.DER_cert_to_PEM_cert(der_cert)          
        print(pem_cert)
Run Code Online (Sandbox Code Playgroud)

  • 谢谢,在 Python 3.6 中对我有用。额外感谢您的解释! (2认同)