在 Python 中通过 HTTP 代理读取 TLS 对端证书
先用 Python 的 HTTPConnection 隧道支持建立连接,再用会校验证书的 SSL 上下文包裹套接字。
ssl.get_server_certificate 不会与 HTTP 代理协商。先建立隧道,然后用目标主机名执行 TLS,并获取对端证书。
对于无需认证的 HTTP 代理,使用 HTTP 代理列表中某个候选的主机和端口:
from contextlib import closing
import http.client
import ssl
hostname = "example.com"
context = ssl.create_default_context()
with closing(http.client.HTTPConnection(
"proxy.example.net", 8080, timeout=10
)) as connection:
connection.set_tunnel(hostname, 443)
connection.connect()
with context.wrap_socket(
connection.sock, server_hostname=hostname
) as tls:
certificate_der = tls.getpeercert(binary_form=True)
certificate_pem = ssl.DER_cert_to_PEM_cert(certificate_der)
HTTPConnection 负责处理 CONNECT 请求及其响应。随后 SSL 上下文在校验通过后才返回证书,校验内容包括证书链与目标身份。这次握手不需要向目标发送 HTTP GET。
如果环境使用已批准的私有 CA,应把该 CA 加载进上下文,而不是关闭校验。证书链被拒绝会让上面展示的握手失败。
getpeercert 返回的是对端的叶子证书,而不是自动导出的完整证书链。在受信任的 TLS 拦截下,对端证书可能是中间人为该主机名生成的证书。将它与通过其他路径观察到的证书比较时,要保留这一区别。