You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在TLS通信时在客户端与服务端打印协商的TLS版本

实现客户端(urllib3)与服务端(CherryPy)协商TLS版本的日志打印

我们需要在客户端(基于urllib3)和服务端(基于CherryPy)的日志中,打印TLS握手协商出的具体版本。以下是具体实现方案:

客户端(urllib3)实现

urllib3的连接对象在完成TLS握手后,可通过底层socket的version()方法获取协商的TLS版本。在发送请求后,直接从连接实例中提取该信息并打印即可。

修改后的客户端代码示例:

self.__connection = ProxyManager(proxy_url=proxy_url,
                                 proxy_headers=self.__get_proxy_headers(),
                                 proxy_ssl_context=self.__get_proxy_ssl_context(proxy_url),
                                 cert_file=cert_file,
                                 key_file=cert_key,
                                 key_password=key_pwd,
                                 cert_reqs=cert_reqs,
                                 ca_certs=ca_cert,
                                 timeout=self._get_timeout(connect_timeout=self.__connect_timeout,
                                                           read_timeout=self.__read_timeout),
                                 ssl_context=self.__get_ssl_context(),
                                 retries=False)

def __get_ssl_context():
    ssl_context = create_urllib3_context(ssl_version=ssl.PROTOCOL_TLS_CLIENT,
                                         ciphers=constants.SSL_TLS_CIPHER_SUITE)
    ssl_context.set_ecdh_curve(constants.SSL_TLS_CURVE)
    ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2
    return ssl_context

# 发送请求后打印协商的TLS版本
def send_request_and_log_tls_version(self, url, method="GET", **kwargs):
    response = self.__connection.request(method, url, **kwargs)
    # 获取协商的TLS版本
    tls_version = self.__connection.sock.version()
    print(f"客户端协商的TLS版本: {tls_version}")
    # 可替换为项目日志组件,如logging.info(f"客户端协商的TLS版本: {tls_version}")
    return response

说明:

  • 调用self.__connection.sock.version()可直接获取版本标识,返回值如TLSv1.2或TLSv1.3
  • 可根据项目日志规范替换print为对应日志库调用

服务端(CherryPy)实现

CherryPy的SSL适配器在完成socket包裹后,可通过socket对象获取协商的TLS版本。提供两种实现方案:

方案1:重写SSLAdapter的wrap_socket方法

在TLS握手完成后立即打印版本,适合全局监控:

cheroot.server.ssl_adapters['ssl-password-adapter'] = SSLPasswordAdapter

class SSLPasswordAdapter(BuiltinSSLAdapter):
    def __init__(self, certificate, private_key, certificate_chain=None, ciphers=None):
        self.certificate = certificate
        self.private_key = private_key
        self.certificate_chain = certificate_chain
        self.ciphers = ciphers

        self.context = ssl.create_default_context(
            purpose=ssl.Purpose.CLIENT_AUTH,
            cafile=certificate_chain,
        )

        self.context.minimum_version = ssl.TLSVersion.TLSv1_2
        self.context.load_cert_chain(certificate, private_key, self._password)
        
        if self.ciphers is not None:
            self.context.set_ciphers(ciphers)
        else:
            self.context.set_ciphers(constants.SSL_TLS_CIPHER_SUITE)
            self.context.set_ecdh_curve(constants.SSL_TLS_CURVE)

    def wrap_socket(self, sock, server_side=False, **kwargs):
        wrapped_sock = super().wrap_socket(sock, server_side=True, **kwargs)
        # 获取协商的TLS版本
        tls_version = wrapped_sock.version()
        print(f"服务端与客户端协商的TLS版本: {tls_version}")
        # 可替换为项目日志组件,如logging.info(f"服务端与客户端协商的TLS版本: {tls_version}")
        return wrapped_sock

方案2:通过CherryPy请求钩子获取

在请求处理阶段获取版本,可关联具体请求上下文:

import cherrypy

def log_tls_version():
    # 获取当前请求的底层socket
    sock = cherrypy.request.rfile.raw._sock
    if hasattr(sock, 'version'):
        tls_version = sock.version()
        print(f"服务端处理请求时协商的TLS版本: {tls_version}")
        # 可替换为项目日志组件,如logging.info(f"服务端处理请求时协商的TLS版本: {tls_version}")

# 注册请求钩子,在请求开始时执行
cherrypy.tools.log_tls = cherrypy.Tool('on_start_resource', log_tls_version)

# 启用该工具
cherrypy.config.update({'tools.log_tls.on': True})

说明:

  • 若version()方法无法调用,可尝试sock._sslobj.version()(适配部分Python/CherryPy版本)
  • 方案1侧重全局握手监控,方案2可结合请求参数做精细化日志记录

内容的提问来源于stack exchange,提问作者dev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 17:57:07