PEP 476 – 为标准库HTTP客户端默认启用证书验证
- 作者:
- Alex Gaynor <alex.gaynor at gmail.com>
- 状态:
- 最终版
- 类型:
- 标准跟踪
- 创建日期:
- 2014年8月28日
- Python 版本:
- 2.7.9, 3.4.3, 3.5
- 决议:
- Python-Dev 消息
摘要
目前,当标准库HTTP客户端(urllib、urllib2、http和httplib模块)遇到https:// URL时,它会像与此类服务器通信所需的那样,将网络HTTP流量包装在TLS流中。然而,在TLS握手期间,它不会实际检查服务器的X509证书是否由任何信任根中的CA签名,也不会验证所呈现证书上的通用名(或主题备用名)是否与请求的主机匹配。
未能执行这些检查意味着,任何具有特权网络位置的人都能够轻易地对使用这些HTTP客户端的Python应用程序执行中间人攻击,并随意更改流量。
本PEP提议默认启用Python HTTP客户端的X509证书签名验证以及主机名验证,但允许按每次调用选择退出。此更改将应用于Python 2.7、Python 3.4和Python 3.5。
基本原理
“HTTPS”中的“S”代表安全。当Python用户输入“HTTPS”时,他们期望的是安全连接,Python在提供此服务时应遵守合理的注意标准。目前我们未能做到这一点,这样做,看似简单的API正在误导用户。
当被问及此事时,许多Python用户表示他们不知道Python未能执行这些验证,并感到震惊。
requests(默认启用这些检查)的流行表明这些检查绝非负担过重,而且它被广泛推荐作为对标准库客户端的重大安全改进,这表明许多人期望他们的工具能达到更高的“默认安全”标准。
各种应用程序未能注意到Python在此事上的疏忽是导致定期分配CVE的原因 [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]。
技术细节
Python将在所有平台上使用系统提供的证书数据库。如果找不到此类数据库,将导致错误,用户需要明确指定位置来修复它。
这将通过添加一个新的ssl._create_default_https_context函数来实现,该函数与ssl.create_default_context相同。
http.client随后可以将其对ssl._create_stdlib_context的使用替换为ssl._create_default_https_context。
此外,ssl._create_stdlib_context将重命名为ssl._create_unverified_context(为了向后兼容,保留了一个别名)。
信任数据库
本PEP提议使用系统提供的证书数据库。之前的讨论曾建议捆绑Mozilla的证书数据库并默认使用它。出于几个原因,此提议被否决了:
- 使用平台信任数据库可以降低Python开发人员的维护负担——如果我们自带信任数据库,那么每次证书被撤销时都需要发布一个新版本。
- Linux供应商和其他下游厂商会解绑Mozilla证书,导致行为更加碎片化。
- 使用平台存储更容易处理诸如企业内部CA等情况。
OpenSSL也有两个环境变量,SSL_CERT_DIR和SSL_CERT_FILE,可用于将Python指向不同的证书数据库。
向后兼容性
这一改变将导致一些HTTPS连接看似“中断”,因为它们现在将在握手期间引发异常。
然而,这是具有误导性的,事实上这些连接目前正在静默失败,HTTPS URL表示对保密性和身份验证的期望。Python未实际验证用户请求已完成是一个错误,此外:“错误不应该静默传递。”
然而,需要访问带有自签名或不正确证书的服务器的用户可以通过提供带有自定义信任根或禁用验证的上下文来做到这一点(文档应强烈建议在可能的情况下使用前者)。用户还可以将必要的证书添加到系统信任存储中以全局信任它们。
Twisted的14.0版本也做了相同的更改,几乎没有遭到反对。
选择退出
对于希望在单个连接上选择退出证书验证的用户,他们可以通过向urllib.urlopen提供context参数来实现此目的。
import ssl
# This restores the same behavior as before.
context = ssl._create_unverified_context()
urllib.urlopen("https://no-valid-cert", context=context)
此外,**强烈不建议**通过猴子补丁(monkeypatching)本PEP所实现的Python版本中的ssl模块来全局禁用验证。
import ssl
try:
_create_unverified_https_context = ssl._create_unverified_context
except AttributeError:
# Legacy Python that doesn't verify HTTPS certificates by default
pass
else:
# Handle target environment that doesn't support HTTPS verification
ssl._create_default_https_context = _create_unverified_https_context
此指南主要针对希望在尚未支持HTTPS连接证书验证的遗留环境中采用本PEP实现的新版Python的系统管理员。例如,管理员可以通过在Python的标准操作环境中的sitecustomize.py中添加上述猴子补丁来选择退出。应用程序和库不应在整个进程中进行此更改(除非是为了响应系统管理员控制的配置设置)。
特别注重安全性的应用程序应始终提供显式应用程序定义的SSL上下文,而不是依赖底层 Python 实现的默认行为。
其他协议
本PEP仅提议对HTTP客户端要求此级别的验证,而不适用于SMTP等其他协议。
这是因为,由于浏览器执行的验证,HTTPS服务器中证书正确的比例很高,而对于其他协议,自签名或不正确证书则更为常见。值得注意的是,至少对于SMTP来说,这种情况似乎正在改变,将来应重新审查以考虑类似的PEP。
Python版本
本PEP描述了将在3.4.x、3.5和2.7.X分支上发生的更改。对于2.7.X,这将需要在httplib中回溯context(SSLContext)参数,以及PEP 466中已回溯的功能。
实施
- 已实现:Issue 22366为
urlib.request.urlopen添加了context参数。 - Issue 22417实现了本PEP的实质内容。
版权
本文档已进入公共领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0476.rst
上次修改时间:2025-02-01 08:59:27 GMT