ssl - 是否可以识别 TLS 信息。在请求响应中?

标签 ssl python-requests python-3.6

我正在使用 python 的请求模块。我可以获得服务器的响应头和应用层数据:

import requests
r = requests.get('https://yahoo.com')
print(r.url)  

我的问题:请求是否允许检索传输层数据(服务器的 TLS 选择版本、密码套件等?)。

最佳答案

这是一个有效的快速丑陋猴子补丁版本:

import requests
from requests.packages.urllib3.connection import VerifiedHTTPSConnection

SOCK = None

_orig_connect = requests.packages.urllib3.connection.VerifiedHTTPSConnection.connect

def _connect(self):
    global SOCK
    _orig_connect(self)
    SOCK = self.sock

requests.packages.urllib3.connection.VerifiedHTTPSConnection.connect = _connect

requests.get('https://yahoo.com')
tlscon = SOCK.connection
print 'Cipher is %s/%s' % (tlscon.get_cipher_name(), tlscon.get_cipher_version())
print 'Remote certificates: %s' % (tlscon.get_peer_certificate())
print 'Protocol version: %s' % tlscon.get_protocol_version_name()

这会产生:

Cipher is ECDHE-RSA-AES128-GCM-SHA256/TLSv1.2
Remote certificates: <OpenSSL.crypto.X509 object at 0x10c60e310>
Protocol version: TLSv1.2

然而,这很糟糕,因为猴子修补并依赖于一个唯一的全局变量,这也意味着您无法检查在重定向步骤等处发生了什么。

也许可以通过一些可以变成传输适配器的工作来获取底层连接作为请求的属性(可能是 session 或其他东西)。但这可能会造成泄漏,因为在当前的实现中,底层套接字会被尽快丢弃(参见 How to get the underlying socket when using Python requests )。

更新,现在使用传输适配器

这是有效的,并且符合框架(没有全局变量,应该处理重定向等。虽然代理可能需要做一些事情,比如为 proxy_manager_for 添加覆盖),但是这是更多的代码。

import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.connectionpool import HTTPSConnectionPool
from requests.packages.urllib3.poolmanager import PoolManager


class InspectedHTTPSConnectionPool(HTTPSConnectionPool):
    @property
    def inspector(self):
        return self._inspector

    @inspector.setter
    def inspector(self, inspector):
        self._inspector = inspector

    def _validate_conn(self, conn):
        r = super(InspectedHTTPSConnectionPool, self)._validate_conn(conn)
        if self.inspector:
            self.inspector(self.host, self.port, conn)

        return r


class InspectedPoolManager(PoolManager):
    @property
    def inspector(self):
        return self._inspector

    @inspector.setter
    def inspector(self, inspector):
        self._inspector = inspector

    def _new_pool(self, scheme, host, port):
        if scheme != 'https':
            return super(InspectedPoolManager, self)._new_pool(scheme, host, port)

        kwargs = self.connection_pool_kw
        if scheme == 'http':
            kwargs = self.connection_pool_kw.copy()
            for kw in SSL_KEYWORDS:
                kwargs.pop(kw, None)

        pool = InspectedHTTPSConnectionPool(host, port, **kwargs)
        pool.inspector = self.inspector
        return pool


class TLSInspectorAdapter(HTTPAdapter):
    def __init__(self, inspector):
        self._inspector = inspector
        super(TLSInspectorAdapter, self).__init__()

    def init_poolmanager(self, connections, maxsize, block=False, **pool_kwargs):
        self.poolmanager = InspectedPoolManager(num_pools=connections, maxsize=maxsize, block=block, strict=True, **pool_kwargs)
        self.poolmanager.inspector = self._inspector


def connection_inspector(host, port, connection):
    print 'host is %s' % host
    print 'port is %s' % port
    print 'connection is %s' % connection
    sock = connection.sock
    sock_connection = sock.connection
    print 'socket is %s' % sock
    print 'Protocol version: %s' % sock_connection.get_protocol_version_name()
    print 'Cipher is %s/%s' % (sock_connection.get_cipher_name(), sock_connection.get_cipher_version())
    print 'Remote certificate: %s' % sock.getpeercert()



url = 'https://yahoo.com'
s = requests.Session()
s.mount(url, TLSInspectorAdapter(connection_inspector))
r = s.get(url)

是的,socketconnection在命名上有很多混淆:requests使用了一个“连接池”,它有一组连接,实际上是,对于 HTTPS,一个 PyOpenSSL WrappedSocket,它本身有一个底层真实的 TLS 连接(即 PyOpenSSL Connection 对象)。因此 connection_inspector 中出现了奇怪的形式。

但这会返回预期的结果:

host is yahoo.com
port is 443
connection is <requests.packages.urllib3.connection.VerifiedHTTPSConnection object at 0x10bb372d0>
socket is <requests.packages.urllib3.contrib.pyopenssl.WrappedSocket object at 0x10bb37410>
Protocol version: TLSv1.2
Cipher is ECDHE-RSA-AES128-GCM-SHA256/TLSv1.2
Remote certificate: {'subjectAltName': [('DNS', '*.www.yahoo.com'), ('DNS', 'add.my.yahoo.com'), ('DNS', '*.amp.yimg.com'), ('DNS', 'au.yahoo.com'), ('DNS', 'be.yahoo.com'), ('DNS', 'br.yahoo.com'), ('DNS', 'ca.my.yahoo.com'), ('DNS', 'ca.rogers.yahoo.com'), ('DNS', 'ca.yahoo.com'), ('DNS', 'ddl.fp.yahoo.com'), ('DNS', 'de.yahoo.com'), ('DNS', 'en-maktoob.yahoo.com'), ('DNS', 'espanol.yahoo.com'), ('DNS', 'es.yahoo.com'), ('DNS', 'fr-be.yahoo.com'), ('DNS', 'fr-ca.rogers.yahoo.com'), ('DNS', 'frontier.yahoo.com'), ('DNS', 'fr.yahoo.com'), ('DNS', 'gr.yahoo.com'), ('DNS', 'hk.yahoo.com'), ('DNS', 'hsrd.yahoo.com'), ('DNS', 'ideanetsetter.yahoo.com'), ('DNS', 'id.yahoo.com'), ('DNS', 'ie.yahoo.com'), ('DNS', 'in.yahoo.com'), ('DNS', 'it.yahoo.com'), ('DNS', 'maktoob.yahoo.com'), ('DNS', 'malaysia.yahoo.com'), ('DNS', 'mbp.yimg.com'), ('DNS', 'my.yahoo.com'), ('DNS', 'nz.yahoo.com'), ('DNS', 'ph.yahoo.com'), ('DNS', 'qc.yahoo.com'), ('DNS', 'ro.yahoo.com'), ('DNS', 'se.yahoo.com'), ('DNS', 'sg.yahoo.com'), ('DNS', 'tw.yahoo.com'), ('DNS', 'uk.yahoo.com'), ('DNS', 'us.yahoo.com'), ('DNS', 'verizon.yahoo.com'), ('DNS', 'vn.yahoo.com'), ('DNS', 'www.yahoo.com'), ('DNS', 'yahoo.com'), ('DNS', 'za.yahoo.com')], 'subject': ((('commonName', u'*.www.yahoo.com'),),)}

其他想法:

  1. 如果像 https://stackoverflow.com/a/22253656/6368697 中那样进行猴子修补,您可能会删除很多代码。基本上 poolmanager.pool_classes_by_scheme['http'] = MyHTTPConnectionPool;但这仍然是猴子修补,令人遗憾的是 PoolManager 没有为 pool_classes_by_scheme 变量提供一个很好的 API 以便能够轻松覆盖它
  2. PyOpenSSL ssl_context 可能能够保存将在 TLS 握手期间调用的回调并获取基础数据;然后在 init_poolmanager 中,您只需要在调用父类(super class)之前在 kwargs 中设置 ssl_context;这个例子在 https://gist.github.com/aiguofer/1eb881ccf199d4aaa2097d87f93ace6a <= 或者可能不是,因为实际上该结构来自 ssl.create_default_contextssl 远不如 PyOpenSSL 强大,我看不出有什么办法使用 ssl 添加回调,它们存在于 PyOpenSSL 中。 YMMV。

附言:

  1. 一旦你发现你有 _validate_conn,你可以在它获得正确的连接对象时覆盖它,生活就更容易了
  2. 特别是如果你正确地在顶部导入,你需要使用在请求中分发的 urllib3 包,而不是“真正的”urllib3 否则你会得到很多奇怪的错误,因为两者中的相同方法不会有相同的签名...

关于ssl - 是否可以识别 TLS 信息。在请求响应中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55431014/

相关文章:

ajax - ajax 应用程序中 Internet Explorer 11 的保活问题

mongodb - 如何在mongodb中配置ssl?

python - 使用请求将文件上传到 python-eve

Python + SOAP : The message with Action \'\' cannot be processed at the receiver, 由于 EndpointDispatcher 的 ContractFilter 不匹配

python - pycrypto 适用于 python2.7 而不是 python3.6

Python 3.6 : Floor division of a complex number

SSL 部分加密

java - Android 6(棉花糖)的 SSLHandshakeException SSLProtocolException

Python请求在localhost返回504

python - Python 中的正则表达式用于匹配可能包含或不包含某些部分的模式