LoginSignup
3
1

More than 3 years have passed since last update.

ssl.get_server_certificateで証明書を取得できない場合の対処

Posted at

概要

Python でSSL証明書の情報を確認したい場合、以下のようなコードが紹介されていることが多い。

import ssl
import OpenSSL # required pyopenssl

cert = ssl.get_server_certificate(('www.google.com', 443))
x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)

試してみると、多くの場合はこの方法で正常に情報が取得できたが、正常に情報が取得できないケースがあったため、調査を実施した。

実際のところ invoke (Fabric 2) でローカルの openssl コマンド叩けばうまくいったけれど、何故 Python でやるとうまくいかなかったのかが疑問だったので。

原因

Server Name Indication(SNI) の指定がないため。
openssl s_client コマンドで言えば -servername が未指定のため。

IPアドレス直打ちの場合などにアクセスさせないためや、1つのサーバー上で複数のドメイン管理をしている場合などにSNI設定なしでアクセスすると別設定の証明書を返してきたり、そもそも返さなかったりする。

わざと openssl コマンドで -servername example.com など全く別のサーバーネームを与えた場合に同様の事象が再現した。

対処

原因がSNIと分かれば同じような質問があった。 結果としては、ssl.get_server_certificate は使わず、その中身と同じようなことを自分で書いてやる必要がある。

import socket
import ssl
import OpenSSL

def get_server_certificate(hostname):
    context = ssl.create_default_context()
    with socket.create_connection((hostname, 443)) as sock:
        with context.wrap_socket(sock, server_hostname=hostname) as sslsock:
            der_cert = sslsock.getpeercert(True)
            return ssl.DER_cert_to_PEM_cert(der_cert)  

cert = get_server_certificate('www.google.com')
x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1