Python建立TLS连接触发AttributeError:'NoneType'无get_unverified_chain属性
解决Python TLS服务端启动时的AttributeError问题
我编写了一套用于服务端与客户端间建立TLS连接的简单Python代码,执行服务端脚本server.py时触发了AttributeError错误,以下是详细信息:
服务端脚本(server.py)
from socket import socket, AF_INET, SOCK_STREAM from ssl import SSLContext, PROTOCOL_TLS_SERVER ip = '127.0.0.1' port = 8443 context = SSLContext(PROTOCOL_TLS_SERVER) context.load_cert_chain('cert.pem', 'key.pem') with socket(AF_INET, SOCK_STREAM) as server: server.bind((ip, port)) server.listen(1) with context.wrap_socket(server, server_side=True) as tls: connection, address = tls.accept() print(f'Connected by {address}\n') data = connection.recv(1024) print(f'Client Says: {data}') connection.sendall(b"You're welcome")
客户端脚本(client.py)
from socket import create_connection from ssl import SSLContext, PROTOCOL_TLS_CLIENT hostname='example.org' ip = '127.0.0.1' port = 8443 context = SSLContext(PROTOCOL_TLS_CLIENT) context.load_verify_locations('cert.pem') with create_connection((ip, port)) as client: with context.wrap_socket(client, server_hostname=hostname) as tls: print(f'Using {tls.version()}\n') tls.sendall(b'Hello, world') data = tls.recv(1024) print(f'Server says: {data}')
证书生成命令
通过以下OpenSSL命令生成cert.pem和key.pem:
$ openssl req -new -x509 -days 365 -nodes -out cert.pem -keyout key.pem -subj "/C={two_char_country_code}/ST={state_name}/L={city_name}/O=UFF/CN=example.org/emailAddress=name@domain.com"
错误信息
执行python3 server.py时出现的错误栈:
Traceback (most recent call last): File "/Users/raze/Documents/Sample_Projects/Python_prac/python-tls/server.py", line 13, in <module> with context.wrap_socket(server, server_side=True) as tls: ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/site-packages/pip/_vendor/truststore/_api.py", line 122, in wrap_socket _verify_peercerts(ssl_sock, server_hostname=server_hostname) ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/site-packages/pip/_vendor/truststore/_api.py", line 330, in _verify_peercerts cert_bytes = _get_unverified_chain_bytes(sslobj) File "/Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/site-packages/pip/_vendor/truststore/_api.py", line 303, in _get_unverified_chain_bytes unverified_chain = sslobj.get_unverified_chain() or () # type: ignore[attr-defined] ~~~~~~~~~~~~~~~~~~~~~~~~~~~^^ File "/Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/ssl.py", line 1176, in get_unverified_chain chain = self._sslobj.get_unverified_chain() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AttributeError: 'NoneType' object has no attribute 'get_unverified_chain'
开发环境
- MacOS Ventura 13.2.1
- Python版本:3.14.0
解决方案
这个错误是由于pip依赖的truststore库对Python的ssl模块进行了猴子补丁,导致服务端的SSLContext.wrap_socket被替换成仅适配客户端场景的实现,调用了服务端socket不支持的客户端验证逻辑。
方法一:取消truststore的猴子补丁
在服务端脚本的最开头添加以下代码,恢复原生的ssl.SSLContext.wrap_socket方法:
# 取消truststore对ssl模块的猴子补丁,避免干扰服务端TLS逻辑 try: import ssl from pip._vendor.truststore._api import wrap_socket as truststore_wrap if ssl.SSLContext.wrap_socket == truststore_wrap: # 恢复原生的wrap_socket方法 ssl.SSLContext.wrap_socket = ssl.SSLContext._original_wrap_socket except ImportError: # 若未加载truststore则忽略 pass
修改后的完整服务端脚本:
# 取消truststore对ssl模块的猴子补丁,避免干扰服务端TLS逻辑 try: import ssl from pip._vendor.truststore._api import wrap_socket as truststore_wrap if ssl.SSLContext.wrap_socket == truststore_wrap: # 恢复原生的wrap_socket方法 ssl.SSLContext.wrap_socket = ssl.SSLContext._original_wrap_socket except ImportError: # 若未加载truststore则忽略 pass from socket import socket, AF_INET, SOCK_STREAM from ssl import SSLContext, PROTOCOL_TLS_SERVER ip = '127.0.0.1' port = 8443 context = SSLContext(PROTOCOL_TLS_SERVER) context.load_cert_chain('cert.pem', 'key.pem') with socket(AF_INET, SOCK_STREAM) as server: server.bind((ip, port)) server.listen(1) with context.wrap_socket(server, server_side=True) as tls: connection, address = tls.accept() print(f'Connected by {address}\n') data = connection.recv(1024) print(f'Client Says: {data}') connection.sendall(b"You're welcome")
方法二:使用原生ssl.wrap_socket替代SSLContext.wrap_socket
直接使用ssl.wrap_socket函数包装服务端socket,绕过被补丁的SSLContext.wrap_socket:
修改服务端脚本中的TLS包装部分:
import ssl with socket(AF_INET, SOCK_STREAM) as server: server.bind((ip, port)) server.listen(1) # 替换原来的context.wrap_socket调用 tls = ssl.wrap_socket(server, server_side=True, certfile='cert.pem', keyfile='key.pem') try: connection, address = tls.accept() print(f'Connected by {address}\n') data = connection.recv(1024) print(f'Client Says: {data}') connection.sendall(b"You're welcome") finally: tls.close()
方法三:隔离pip的truststore环境
创建独立的虚拟环境,避免pip的依赖干扰:
# 创建虚拟环境 python3 -m venv tls-env # 激活虚拟环境 source tls-env/bin/activate # 无需安装额外依赖,直接运行脚本 python server.py
内容的提问来源于stack exchange,提问作者Altaf
相关产品推荐
相关产品推荐

