requests
の応答からSSL証明書を取得しようとしています。
これを行う良い方法は何ですか?
requests
は、このような低レベルのものを意図的にラップします。通常、実行したいのは 証明書が有効であることを確認する だけです。これを行うには、verify=True
を渡します。非標準のcacertバンドルを使用する場合は、それも渡すことができます。例えば:
resp = requests.get('https://example.com', verify=True, cert=['/path/to/my/ca.crt'])
また、requests
は主に他のライブラリのラッパーのセットであり、ほとんどが urllib3
とstdlibの http.client
(または2.xの場合はhttplib
です。 )および ssl
。
時には、答えは下位レベルのオブジェクトを取得することです(たとえば、resp.raw
はurllib3.response.HTTPResponse
です)。しかし、多くの場合、それは不可能です。
そして、これはそれらのケースの1つです。これまでに証明書を見る唯一のオブジェクトはhttp.client.HTTPSConnection
(またはurllib3.connectionpool.VerifiedHTTPSConnection
ですが、それは前者のサブクラスにすぎません)とssl.SSLSocket
であり、それらのいずれも現時点では存在しませんリクエストが返されます。 (connectionpool
という名前が意味するように、HTTPSConnection
オブジェクトはプールに格納され、それが完了するとすぐに再利用できます。SSLSocket
はHTTPSConnection
のメンバーです。)
したがって、チェーンにデータをコピーできるように、パッチを適用する必要があります。これは次のように簡単な場合があります。
HTTPResponse = requests.packages.urllib3.response.HTTPResponse
orig_HTTPResponse__init__ = HTTPResponse.__init__
def new_HTTPResponse__init__(self, *args, **kwargs):
orig_HTTPResponse__init__(self, *args, **kwargs)
try:
self.peercert = self._connection.sock.getpeercert()
except AttributeError:
pass
HTTPResponse.__init__ = new_HTTPResponse__init__
HTTPAdapter = requests.adapters.HTTPAdapter
orig_HTTPAdapter_build_response = HTTPAdapter.build_response
def new_HTTPAdapter_build_response(self, request, resp):
response = orig_HTTPAdapter_build_response(self, request, resp)
try:
response.peercert = resp.peercert
except AttributeError:
pass
return response
HTTPAdapter.build_response = new_HTTPAdapter_build_response
これはテストされていないため、保証はありません。それ以上のパッチが必要になる場合があります。
また、サブクラス化とオーバーライドは、モンキーパッチングよりもクリーンだと思われます(特にHTTPAdapter
はサブクラス化するように設計されているため)。
または、urllib3
とrequests
をフォークしてフォークを変更し、(これが正当に役立つと思われる場合は)アップストリームにプルリクエストを送信することもできます。
とにかく、今、あなたのコードから、あなたはこれを行うことができます:
resp.peercert
これにより、'subject'
から返される'subjectAltName'
キーとpyopenssl.WrappedSocket.getpeercert
キーを含む辞書が得られます。代わりに証明書に関する詳細情報が必要な場合は、 この回答のクリストフヴァンデプラスの変形 を試してください。これにより、OpenSSL.crypto.X509
オブジェクトを取得できます。ピア証明書チェーン全体を取得する場合は、 GoldenStakeの回答 を参照してください。
もちろん、証明書の検証に必要なすべての情報を渡すこともできますが、すでに最上位レベルを通過しているため、さらに簡単です。
はじめに、 abarnertの回答 は非常に完全です。 Kalkran の提案された_connection-close
_問題を追跡しているときに、peercert
にSSLに関する詳細情報が含まれていないことが実際にわかりました証明書。
接続とソケット情報をさらに掘り下げ、次のような優れた関数を含むself.sock.connection.get_peer_certificate()
関数を抽出しました。
get_subject()
get_notAfter()
およびget_notBefore()
(有効期限)get_serial_number()
およびget_signature_algorithm()
(暗号関連の技術詳細)これらは、システムにpyopenssl
がインストールされている場合にのみ使用できることに注意してください。内部では、_urllib3
_は、使用可能な場合はpyopenssl
を使用し、それ以外の場合は標準ライブラリのssl
モジュールを使用します。以下に示す_self.sock.connection
_属性は、_self.sock
_が_urllib3.contrib.pyopenssl.WrappedSocket
_の場合にのみ存在し、_ssl.SSLSocket
_の場合には存在しません。 _pip install pyopenssl
_を使用してpyopenssl
をインストールできます。
これが完了すると、コードは次のようになります。
_import requests
HTTPResponse = requests.packages.urllib3.response.HTTPResponse
orig_HTTPResponse__init__ = HTTPResponse.__init__
def new_HTTPResponse__init__(self, *args, **kwargs):
orig_HTTPResponse__init__(self, *args, **kwargs)
try:
self.peer_certificate = self._connection.peer_certificate
except AttributeError:
pass
HTTPResponse.__init__ = new_HTTPResponse__init__
HTTPAdapter = requests.adapters.HTTPAdapter
orig_HTTPAdapter_build_response = HTTPAdapter.build_response
def new_HTTPAdapter_build_response(self, request, resp):
response = orig_HTTPAdapter_build_response(self, request, resp)
try:
response.peer_certificate = resp.peer_certificate
except AttributeError:
pass
return response
HTTPAdapter.build_response = new_HTTPAdapter_build_response
HTTPSConnection = requests.packages.urllib3.connection.HTTPSConnection
orig_HTTPSConnection_connect = HTTPSConnection.connect
def new_HTTPSConnection_connect(self):
orig_HTTPSConnection_connect(self)
try:
self.peer_certificate = self.sock.connection.get_peer_certificate()
except AttributeError:
pass
HTTPSConnection.connect = new_HTTPSConnection_connect
_
結果に簡単にアクセスできます:
_r = requests.get('https://yourdomain.tld', timeout=0.1)
print('Expires on: {}'.format(r.peer_certificate.get_notAfter()))
print(dir(r.peer_certificate))
_
私のように、SSL証明書の警告を無視したい場合は、ファイルの先頭に以下を追加し、SSL検証は行わないでください。
_from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
r = requests.get('https://yourdomain.tld', timeout=0.1, verify=False)
print(dir(r.peer_certificate))
_
みんなの素晴らしい答えをありがとう。
それは私にこの質問への答えを設計する上で役立ちました:
Python)で使用されるCAストアにカスタムCAルート証明書を追加する方法
Cert Human:SSL Certificates for Humans を見てください-私の印象的な書き換え https://github.com/neozenith/get-ca-py プロジェクト- lifehackjim 。
元のリポジトリをアーカイブしました。
#! /usr/bin/env python
# -*- coding: utf-8 -*-
"""
Get Certificates from a request and dump them.
"""
import argparse
import sys
import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
"""
Inspired by the answers from this Stackoverflow question:
https://stackoverflow.com/questions/16903528/how-to-get-response-ssl-certificate-from-requests-in-python
What follows is a series of patching the low level libraries in requests.
"""
"""
https://stackoverflow.com/a/47931103/622276
"""
sock_requests = requests.packages.urllib3.contrib.pyopenssl.WrappedSocket
def new_getpeercertchain(self, *args, **kwargs):
x509 = self.connection.get_peer_cert_chain()
return x509
sock_requests.getpeercertchain = new_getpeercertchain
"""
https://stackoverflow.com/a/16904808/622276
"""
HTTPResponse = requests.packages.urllib3.response.HTTPResponse
orig_HTTPResponse__init__ = HTTPResponse.__init__
def new_HTTPResponse__init__(self, *args, **kwargs):
orig_HTTPResponse__init__(self, *args, **kwargs)
try:
self.peercertchain = self._connection.sock.getpeercertchain()
except AttributeError:
pass
HTTPResponse.__init__ = new_HTTPResponse__init__
HTTPAdapter = requests.adapters.HTTPAdapter
orig_HTTPAdapter_build_response = HTTPAdapter.build_response
def new_HTTPAdapter_build_response(self, request, resp):
response = orig_HTTPAdapter_build_response(self, request, resp)
try:
response.peercertchain = resp.peercertchain
except AttributeError:
pass
return response
HTTPAdapter.build_response = new_HTTPAdapter_build_response
"""
Attempt to wrap in a somewhat usable CLI
"""
def cli(args):
parser = argparse.ArgumentParser(description="Request any URL and dump the certificate chain")
parser.add_argument("url", metavar="URL", type=str, nargs=1, help="Valid https URL to be handled by requests")
verify_parser = parser.add_mutually_exclusive_group(required=False)
verify_parser.add_argument("--verify", dest="verify", action="store_true", help="Explicitly set SSL verification")
verify_parser.add_argument(
"--no-verify", dest="verify", action="store_false", help="Explicitly disable SSL verification"
)
parser.set_defaults(verify=True)
return vars(parser.parse_args(args))
def dump_pem(cert, outfile="ca-chain.crt"):
"""Use the CN to dump certificate to PEM format"""
PyOpenSSL = requests.packages.urllib3.contrib.pyopenssl
pem_data = PyOpenSSL.OpenSSL.crypto.dump_certificate(PyOpenSSL.OpenSSL.crypto.FILETYPE_PEM, cert)
issuer = cert.get_issuer().get_components()
print(pem_data.decode("utf-8"))
with open(outfile, "a") as output:
for part in issuer:
output.write(part[0].decode("utf-8"))
output.write("=")
output.write(part[1].decode("utf-8"))
output.write(",\t")
output.write("\n")
output.write(pem_data.decode("utf-8"))
if __name__ == "__main__":
cli_args = cli(sys.argv[1:])
url = cli_args["url"][0]
req = requests.get(url, verify=cli_args["verify"])
for cert in req.peercertchain:
dump_pem(cert)
これはまったくきれいではありませんが、うまくいきます:
import requests
req = requests.get('https://httpbin.org')
pool = req.connection.poolmanager.connection_from_url('https://httpbin.org')
conn = pool.pool.get()
# get() removes it from the pool, so put it back in
pool.pool.put(conn)
print(conn.sock.getpeercert())
まず、 abarnertの回答 は非常に完全です
ただし、ピア証明書チェーンを探している場合は、さらに別のコードにパッチを適用する必要があることを付け加えておきます。
import requests
sock_requests = requests.packages.urllib3.contrib.pyopenssl.WrappedSocket
def new_getpeercertchain(self,*args, **kwargs):
x509 = self.connection.get_peer_cert_chain()
return x509
sock_requests.getpeercertchain = new_getpeercertchain
その後、あなたはそれを受け入れられた答えと同じように呼び出すことができます
HTTPResponse = requests.packages.urllib3.response.HTTPResponse
orig_HTTPResponse__init__ = HTTPResponse.__init__
def new_HTTPResponse__init__(self, *args, **kwargs):
orig_HTTPResponse__init__(self, *args, **kwargs)
try:
self.peercertchain = self._connection.sock.getpeercertchain()
except AttributeError:
pass
HTTPResponse.__init__ = new_HTTPResponse__init__
HTTPAdapter = requests.adapters.HTTPAdapter
orig_HTTPAdapter_build_response = HTTPAdapter.build_response
def new_HTTPAdapter_build_response(self, request, resp):
response = orig_HTTPAdapter_build_response(self, request, resp)
try:
response.peercertchain = resp.peercertchain
except AttributeError:
pass
return response
HTTPAdapter.build_response = new_HTTPAdapter_build_response
resp.peercertchain
オブジェクトのTuple
を含むOpenSSL.crypto.X509
を取得します