ssl.get_server_certificate 对于具有 SNI(服务器名称指示)的站点
ssl.get_server_certificate for sites with SNI (Server Name Indication)
我正在尝试获取 badssl.com 个子域(例如 https://expired.badssl.com)的服务器证书。
import ssl
ssl.get_server_certificate(('expired.badssl.com', 443))
但是在检查上面生成的证书时,我发现证书有
Identity: badssl-fallback-unknown-subdomain-or-no-sni
这意味着 SNI 失败了。如何获取badssl.com不同子域的服务器证书? (我正在使用 python 2.7.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))
搜索 "Python ssl.get_server_certificate SNI" 让我很容易找到这个答案。虽然OP自己的回答是正确的,但我想提供更多的见解以供将来参考。
对于一些 [hostname] 的休闲调用使用 Python 3.7:
ssl.get_server_certificate(("example.com", 443)
会抱怨以以下结尾的追溯:
ssl.SSLError: [SSL: TLSV1_ALERT_INTERNAL_ERROR] tlsv1 alert internal error (_ssl.c:1045)
做一些进一步的调查,利用 openssl s_client
实用程序,允许发现那些使 get_server_certificate
失败的相同 [hostname]s,也使休闲命令:
openssl s_client -showcerts -connect example.com:443
失败并出现此错误:
SSL23_GET_SERVER_HELLO:tlsv1 alert internal error:s23_clnt.c:802
请注意,错误消息类似于 python 代码返回的错误消息。
使用 -servername
开关成功了:
openssl s_client -showcerts -connect example.com:443 -servername example.com
得出的结论是,所调查的主机名指的是一个使用 SNI 的安全服务器(SNI 维基百科文章对这意味着什么给出了很好的解释)。
所以,再次切换到Python并查看get_server_certificate
方法,检查ssl模块源(here为了方便),你可以发现该函数包括这个调用:
context.wrap_socket(sock)
没有 server_hostname=hostname
键参数,这当然意味着 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)
我正在尝试获取 badssl.com 个子域(例如 https://expired.badssl.com)的服务器证书。
import ssl
ssl.get_server_certificate(('expired.badssl.com', 443))
但是在检查上面生成的证书时,我发现证书有
Identity: badssl-fallback-unknown-subdomain-or-no-sni
这意味着 SNI 失败了。如何获取badssl.com不同子域的服务器证书? (我正在使用 python 2.7.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))
搜索 "Python ssl.get_server_certificate SNI" 让我很容易找到这个答案。虽然OP自己的回答是正确的,但我想提供更多的见解以供将来参考。
对于一些 [hostname] 的休闲调用使用 Python 3.7:
ssl.get_server_certificate(("example.com", 443)
会抱怨以以下结尾的追溯:
ssl.SSLError: [SSL: TLSV1_ALERT_INTERNAL_ERROR] tlsv1 alert internal error (_ssl.c:1045)
做一些进一步的调查,利用 openssl s_client
实用程序,允许发现那些使 get_server_certificate
失败的相同 [hostname]s,也使休闲命令:
openssl s_client -showcerts -connect example.com:443
失败并出现此错误:
SSL23_GET_SERVER_HELLO:tlsv1 alert internal error:s23_clnt.c:802
请注意,错误消息类似于 python 代码返回的错误消息。
使用 -servername
开关成功了:
openssl s_client -showcerts -connect example.com:443 -servername example.com
得出的结论是,所调查的主机名指的是一个使用 SNI 的安全服务器(SNI 维基百科文章对这意味着什么给出了很好的解释)。
所以,再次切换到Python并查看get_server_certificate
方法,检查ssl模块源(here为了方便),你可以发现该函数包括这个调用:
context.wrap_socket(sock)
没有 server_hostname=hostname
键参数,这当然意味着 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)