如何从Python中的请求响应的SSL证书?

问题描述 投票:12回答:5

试图从在requests响应的SSL证书。

什么是做到这一点的好办法?

python http https request python-requests
5个回答
15
投票

requests刻意包装了低层次的东西是这样的。通常情况下,你要做的唯一一件事情就是verify that the certs are valid。要做到这一点,只是通过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.rawurllib3.response.HTTPResponse),但在许多情况下,这是不可能的。

这是其中的案例之一。有史以来看到证书的唯一对象是一个http.client.HTTPSConnection(或urllib3.connectionpool.VerifiedHTTPSConnection,但是这只是一个前的子类)和ssl.SSLSocket,既不那些由当时的请求返回不复存在。 (正如其名称connectionpool所暗示的,HTTPSConnection对象存储在一个池,并且可以尽快它的完成重复使用;所述SSLSocketHTTPSConnection的成员)

所以,你需要修补的东西,所以你可以复制数据环比上涨。这可能是这么简单:

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

这是未经测试,所以不能保证;您可能需要修补不止于此。

此外,子类化和压倒一切很可能会超过的Monkeypatching(尤其是因为HTTPAdapter被设计为子类)清洁剂。

或者,甚至更好,分叉urllib3requests,修改你的叉子,并(如果你认为这是合法有用)提交上游引入请求。

不管怎样,现在,从你的代码,你可以这样做:

resp.peercert

这会给你的字典用'subject''subjectAltName'键,通过pyopenssl.WrappedSocket.getpeercert返回。如果改为需要有关证书的详细信息,请Christophe Vandeplas's variant of this answer,让你得到一个OpenSSL.crypto.X509对象。如果你想获得整个对证书链,见GoldenStake's answer

当然,你也可能要带齐必要的验证证书的信息传递,但是这是更容易,因为它已经通过顶层。


9
投票

首先,abarnert's answer非常齐全。虽然追connection-close的建议Kalkran问题,我居然发现peercert没有包含有关SSL证书的详细信息。

我挖的连接和插座信息更深提取包含了像大功能self.sock.connection.get_peer_certificate()功能:

  • get_subject()为CN
  • get_notAfter()get_notBefore()的到期日期
  • get_serial_number()get_signature_algorithm()用于加密相关的技术细节
  • ...

请注意,这些只适用,如果你已经pyopenssl您的系统上安装。引擎盖下,urllib3使用pyopenssl如果它是可用的和标准库的ssl模块除外。如果self.sock.connectionself.sock,而不是如果它是一个urllib3.contrib.pyopenssl.WrappedSocket只以下所示的ssl.SSLSocket属性存在。你可以用pyopenssl安装pip install 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))

3
投票

这一点,虽然没有漂亮可言,工作原理:

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

3
投票

感谢大家的真棒答案。

它帮助我在工程师的回答这个问题:

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

UPDATE 2019-02-12

请看看Cert Human: SSL Certificates for Humanshttps://github.com/neozenith/get-ca-pylifehackjim项目令人印象深刻重写。

我现在已经存档的原始资料库。

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)

2
投票

首先,abarnert's answer非常齐全

不过,我想补充一点的情况下,你要寻找的同行证书链,你需要修补又一段代码

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

© www.soinside.com 2019 - 2024. All rights reserved.