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 拦截下,对端证书可能是中间人为该主机名生成的证书。将它与通过其他路径观察到的证书比较时,要保留这一区别。

相关文章