PEP 476 – 为标准库 http 客户端默认启用证书验证
- 作者:
- Alex Gaynor <alex.gaynor at gmail.com>
- 状态:
- 最终
- 类型:
- 标准跟踪
- 创建:
- 2014-08-28
- Python 版本:
- 2.7.9, 3.4.3, 3.5
- 决议:
- Python-Dev 消息
摘要
目前,当标准库 http 客户端(urllib
、urllib2
、http
和 httplib
模块)遇到一个 https://
URL 时,它会将网络 HTTP 流量封装在 TLS 流中,这对于与这样的服务器通信是必要的。但是,在 TLS 握手过程中,它实际上不会检查服务器是否具有由任何信任根中的 CA 签名的 X509 证书,也不会验证所呈现的证书上的公用名(或主题备用名称)是否与请求的主机匹配。
未能执行这些检查意味着任何具有特权网络位置的人都可以轻松地对使用这两个 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_default_https_context
替换其对 ssl._create_stdlib_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)
也可以(尽管强烈不鼓励)通过在实现此 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,这将需要将 context
(SSLContext
) 参数移植到 httplib
,除了 PEP 466 中已移植的功能。
实现
- **已落地:** Issue 22366 向
urlib.request.urlopen
添加了context
参数。 - Issue 22417 实现此 PEP 的实质内容。
版权
此文档已置于公有领域。
来源: https://github.com/python/peps/blob/main/peps/pep-0476.rst
上次修改时间: 2023-09-09 17:39:29 GMT