如何从python中的请求获取响应SSL证书?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16903528/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-18 23:57:29  来源:igfitidea点击:

How to get response SSL certificate from requests in python?

pythonhttphttpsrequestpython-requests

提问by Juan Carlos Coto

Trying to get the SSL certificate from a response in requests.

尝试从requests.

What is a good way to do this?

有什么好的方法可以做到这一点?

采纳答案by abarnert

requestsdeliberately wraps up low-level stuff like this. Normally, the only thing you want to do is to verify that the certs are valid. To do that, just pass verify=True. If you want to use a non-standard cacert bundle, you can pass that too. For example:

requests故意包装这样的低级东西。通常,您唯一要做的就是验证证书是否有效。要做到这一点,只需通过verify=True。如果你想使用一个非标准的 cacert 包,你也可以通过它。例如:

resp = requests.get('https://example.com', verify=True, cert=['/path/to/my/ca.crt'])

Also, requestsis primarily a set of wrappers around other libraries, mostly urllib3and the stdlib's http.client(or, for 2.x, httplib) and ssl.

另外,requests主要是一组各地的图书馆,主要是包装的urllib3和STDLIB的http.client(或者,对于2.x中httplib)和ssl

Sometimes, the answer is just to get at the lower-level objects (e.g., resp.rawis the urllib3.response.HTTPResponse), but in many cases that's impossible.

有时,答案只是获取较低级别的对象(例如,resp.rawurllib3.response.HTTPResponse),但在许多情况下这是不可能的。

And this is one of those cases. The only objects that ever see the certs are an http.client.HTTPSConnection(or a urllib3.connectionpool.VerifiedHTTPSConnection, but that's just a subclass of the former) and an ssl.SSLSocket, and neither of those exist anymore by the time the request returns. (As the name connectionpoolimplies, the HTTPSConnectionobject is stored in a pool, and may be reused as soon as it's done; the SSLSocketis a member of the HTTPSConnection.)

这就是其中一种情况。唯一能看到证书的对象是 an http.client.HTTPSConnection(或 a urllib3.connectionpool.VerifiedHTTPSConnection,但这只是前者的子类) 和 an ssl.SSLSocket,并且在请求返回时这两个对象都不存在了。(connectionpool顾名思义,HTTPSConnection对象存储在池中,一旦完成就可以重用;它SSLSocket是 的成员HTTPSConnection。)

So, you need to patch things so you can copy the data up the chain. It may be as simple as this:

因此,您需要修补一些东西,以便您可以将数据复制到链上。它可能像这样简单:

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

That's untested, so no guarantees; you may need to patch more than that.

这是未经测试的,所以不能保证;您可能需要修补更多。

Also, subclassing and overriding would probably be cleaner than monkeypatching (especially since HTTPAdapterwas designed to be subclassed).

此外,子类化和覆盖可能比猴子补丁更干净(特别是因为它HTTPAdapter被设计为子类化)。

Or, even better, forking urllib3and requests, modifying your fork, and (if you think this is legitimately useful) submitting pull requests upstream.

或者,更好的是,分叉urllib3requests修改您的分叉,并且(如果您认为这合法有用)向上游提交拉取请求。

Anyway, now, from your code, you can do this:

无论如何,现在,从您的代码中,您可以执行以下操作:

resp.peercert

This will give you a dict with 'subject'and 'subjectAltName'keys, as returned by pyopenssl.WrappedSocket.getpeercert. If you instead want more information about the cert, try Christophe Vandeplas's variant of this answerthat lets you get an OpenSSL.crypto.X509object. If you want to get the entire peer certificate chain, see GoldenStake's answer.

这将为您提供一个带有'subject''subjectAltName'键的字典,由pyopenssl.WrappedSocket.getpeercert. 如果您想要更多关于证书的信息,请尝试Christophe Vandeplas 的这个答案的变体,它可以让您获得一个OpenSSL.crypto.X509对象。如果您想获取整个对等证书链,请参阅GoldenStake 的回答

Of course you may also want to pass along all the information necessary to verify the cert, but that's even easier, because it already passes through the top level.

当然,您可能还想传递验证证书所需的所有信息,但这更容易,因为它已经通过了顶层。

回答by t-8ch

This, although not pretty at all, works:

这虽然一点也不漂亮,但有效:

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())

回答by GoldenStake

To start, abarnert's answeris very complete

首先,abarnert的回答很完整

But I would like to add, that in the case you're looking for the peer cert chain, you would need to patch yet another piece of code

但我想补充一点,如果您正在寻找对等证书链,则需要修补另一段代码

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

after that you can call it in a very similiar manner as the accepted answer

之后,您可以以与接受的答案非常相似的方式调用它

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

you will get resp.peercertchainwhich contains a tupleof OpenSSL.crypto.X509objects

你会得到resp.peercertchain其中包含tupleOpenSSL.crypto.X509对象

回答by Christophe Vandeplas

To start, abarnert's answeris very complete. While chasing the proposed connection-closeissue of KalkranI actually discovered that the peercertdidn't contain detailed information about the SSL Certificate.

首先,abarnert 的回答非常完整。在追逐Kalkran的提议connection-close问题时,我实际上发现该问题不包含有关 SSL 证书的详细信息。peercert

I dug deeper in the connection and socket info and extracted the self.sock.connection.get_peer_certificate()function which contains great functions like:

我深入研究了连接和套接字信息,并提取了self.sock.connection.get_peer_certificate()包含以下强大功能的函数:

  • get_subject()for CN
  • get_notAfter()and get_notBefore()for expiration dates
  • get_serial_number()and get_signature_algorithm()for crypto related technical details
  • ...
  • get_subject()中文
  • get_notAfter()get_notBefore()到期日
  • get_serial_number()以及get_signature_algorithm()与加密相关的技术细节
  • ...

Note that these are only available if you have pyopensslinstalled on your system. Under the hood, urllib3uses pyopensslif it's available and the standard library's sslmodule otherwise. The self.sock.connectionattribute shown below only exists if self.sockis a urllib3.contrib.pyopenssl.WrappedSocket, not if it's a ssl.SSLSocket. You can install pyopensslwith pip install pyopenssl.

请注意,这些只有pyopenssl在您的系统上安装后才可用。引擎盖下,urllib3使用pyopenssl,如果它是可用的和标准库的ssl模块除外。下面self.sock.connection显示的属性仅在self.sock是 a时才存在,如果是 aurllib3.contrib.pyopenssl.WrappedSocket则不存在ssl.SSLSocket。您可以pyopenssl使用pip install pyopenssl.

Once that's done, the code becomes:

完成后,代码变为:

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

You will be able to access the result easily:

您将能够轻松访问结果:

r = requests.get('https://yourdomain.tld', timeout=0.1)
print('Expires on: {}'.format(r.peer_certificate.get_notAfter()))
print(dir(r.peer_certificate))

If, like me, you want to ignore SSL Certificate warnings just add the following in the top of the file and do not SSL verify:

如果像我一样,您想忽略 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))

回答by Josh Peak

Thanks for everyone's awesome answers.

感谢大家的精彩答案。

It helped me over engineer an answer to this question:

它帮助我过度设计了这个问题的答案:

How to add a custom CA Root certificate to the CA Store used by Python in Windows?

如何将自定义 CA Root 证书添加到 Python 在 Windows 中使用的 CA Store?

UPDATE 2019-02-12

更新 2019-02-12

Please take a look at Cert Human: SSL Certificates for Humansfor an impressive rewrite of my https://github.com/neozenith/get-ca-pyproject by lifehackjim.

I have archived the original repository now.

请看一下Cert Human: SSL Certificates for Humans对我的https://github.com/neozenith/get-ca-py项目由lifehackjim进行的令人印象深刻的重写。

我现在已经归档了原始存储库。

Stand alone snippet

独立片段

#! /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)