Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/347.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
如何在python中从请求获取响应SSL证书?_Python_Http_Https_Request_Python Requests - Fatal编程技术网

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

如何在python中从请求获取响应SSL证书?,python,http,https,request,python-requests,Python,Http,Https,Request,Python Requests,正在尝试从中的响应获取SSL证书 做这件事的好方法是什么?这种方法虽然一点也不漂亮,但很有效: 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

正在尝试从中的响应获取SSL证书


做这件事的好方法是什么?

这种方法虽然一点也不漂亮,但很有效:

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

请求
故意将这样的低级内容包装起来。通常情况下,你唯一想做的事情就是。为此,只需传递
verify=True
。如果您想使用非标准的cacert包,也可以传递它。例如:

resp = requests.get('https://example.com', verify=True, cert=['/path/to/my/ca.crt'])
另外,
请求
主要是围绕其他库的一组包装器,主要是stdlib(或者,对于2.x,
httplib
)和

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

这就是其中之一。唯一能看到证书的对象是
http.client.HTTPSConnection
(或
urlib3.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
这是未经测试的,因此没有保证;您可能需要修补更多

此外,子类化和重写可能比monkeypatching更干净(特别是因为
HTTPAdapter
被设计为子类化)

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

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

resp.peercert
这将为您提供一个dict,其中包含
'subject'
'subjectAltName'
键,由
pyopenssl.WrappedSocket.getpeercert
返回。如果您想了解有关证书的更多信息,请尝试获取
OpenSSL.crypto.X509
对象。如果要获取整个对等证书链,请参阅

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

首先,非常完整

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

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
,它包含一个
元组
的对象

开始,非常完整。在跟踪建议的
连接关闭
问题时,我实际发现
peercert
中没有包含有关SSL证书的详细信息

我更深入地挖掘了连接和套接字信息,并提取了
self.sock.connection.get\u peer\u certificate()
函数,该函数包含以下重要功能:

  • get\u subject()
    用于CN
  • get\u notAfter()
    get\u notBefore()
    获取过期日期
  • get\u serial\u number()
    get\u signature\u algorithm()
请注意,只有在系统上安装了
pyopenssl
时,这些选项才可用。在引擎盖下,
urllib3
使用
pyopenssl
(如果可用),否则使用标准库的
ssl
模块。下面显示的
self.sock.connection
属性仅在
self.sock
urlib3.contrib.pyopenssl.WrappedSocket
时存在,而不是在它是
ssl.sslssocket
时存在。您可以使用
pip安装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))

谢谢大家的精彩回答

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

更新2019-02-12 请看一看我的项目的令人印象深刻的重写

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

独立代码段
为了检索证书的详细信息,如CN和到期日期,下面的脚本可以很好地使用。它还避免了一些错误,我认为这些错误是由于请求和urllib3的版本不正确/不兼容造成的:“AttributeError:‘SSLSocket’对象没有属性‘connection’”和“AttributeError:‘VerifiedHTTPSConnection’对象没有属性‘peer_certificate’”


此脚本需要Python3和pyOpenSSL。

这仅在连接仍处于活动状态时才起作用,这是不保证的。例如,如果另一端发送
连接:关闭
。或者如果您发送了其他请求(特别是在异步或线程上下文中使用此请求时)。或者,如果没有明显的原因,觉得urllib3就是这样。哇,这是非常完整的。谢谢!我会努力的,看看进展如何。再次感谢!谢谢,这真的很有帮助。不幸的是,当服务器使用
连接:close
并在数据传输后立即关闭连接时,我仍然无法获得“peercert”。我必须对
requests.packages.urllib3.connection.HTTPSConnection.connect进行修补,以便首先调用原始函数,然后立即获取
self.sock.getpeercert()
,然后将其传递到链上。Modern requests从2.24.0开始为具有ssl可用的系统中断此修补程序:非常感谢对答案的精彩添加+1.
#! /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)
from OpenSSL.SSL import Connection, Context, SSLv3_METHOD, TLSv1_2_METHOD
from datetime import datetime, time
import socket
host = 'www.google.com'
try:
    try:
        ssl_connection_setting = Context(SSLv3_METHOD)
    except ValueError:
        ssl_connection_setting = Context(TLSv1_2_METHOD)
    ssl_connection_setting.set_timeout(5)
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.connect((host, 443))
        c = Connection(ssl_connection_setting, s)
        c.set_tlsext_host_name(str.encode(host))
        c.set_connect_state()
        c.do_handshake()
        cert = c.get_peer_certificate()
        print("Is Expired: ", cert.has_expired())
        print("Issuer: ", cert.get_issuer())
        subject_list = cert.get_subject().get_components()
        cert_byte_arr_decoded = {}
        for item in subject_list:
            cert_byte_arr_decoded.update({item[0].decode('utf-8'): item[1].decode('utf-8')})
        print(cert_byte_arr_decoded)
        if len(cert_byte_arr_decoded) > 0:
            print("Subject: ", cert_byte_arr_decoded)
        if cert_byte_arr_decoded["CN"]:
            print("Common Name: ", cert_byte_arr_decoded["CN"])
        end_date = datetime.strptime(str(cert.get_notAfter().decode('utf-8')), "%Y%m%d%H%M%SZ")
        print("Not After (UTC Time): ", end_date)
        diff = end_date - datetime.now()
        print('Summary: "{}" SSL certificate expires on {} i.e. {} days.'.format(host, end_date, diff.days))
        c.shutdown()
        s.close()
except:
    print("Connection to {} failed.".format(host))