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 傍受では、ピア証明書が中間者によってこのホスト名用に生成された証明書である場合があります。別の経路で観察した証明書と比較するときは、この違いを保ってください。

関連記事