返回

Pip 如何定位 CA 证书包?

python

深入探究:Pip 如何定位 CA 证书包?

在 Python 的世界里,pip 就像是一位勤勤恳恳的搬运工,为我们源源不断地从 PyPI 下载各种软件包。为了确保搬运过程的安全可靠,pip 默认会对 HTTPS 连接进行 SSL 证书验证,就像是一位尽职尽责的保安,严防中间人攻击的威胁。然而,与我们想象的不同,pip 这位保安并没有依赖系统自带的证书存储,而是选择了一位名叫 certifi 的伙伴,由它提供捆绑的 CA 证书存储。

官方文档对此有所提及,但却留下了一个疑问:如果我们身处一个全新的 Python 环境,certifi 这位伙伴很可能尚未加入。奇怪的是,pip 依然能够正常工作,这是否意味着还有另一位隐形的伙伴在默默提供帮助?这位伙伴究竟是谁,它又藏身何处呢?

揭开面纱:pip 源码中的秘密

为了找到这位神秘伙伴,我们需要深入 pip 的源代码,就像是一位侦探,仔细搜寻蛛丝马迹。一番调查之后,我们在 pip._vendor.urllib3.util.ssl_ 模块中发现了一段关键代码:

# pip/_vendor/urllib3/util/ssl_.py

CERT_REQUIRED = 'CERT_REQUIRED'

DEFAULT_CIPHERS = ':'.join([
    # ...
])

_TRUST_STORE_FILE = None

def set_default_verify_paths(ssl_context):
    if ssl_context.verify_mode == ssl.CERT_NONE:
        return
    
    if get_openssl_version()[0] < 1:
        return

    trust_store = _TRUST_STORE_FILE or find_cacert(ssl_context.verify_mode == ssl.CERT_REQUIRED)
    if trust_store:
        try:
            ssl_context.load_verify_locations(cafile=trust_store)
        except Exception as e:  # Platform-specific exceptions.
            _logger.warning("Error opening /etc/ssl/certs/ca-certificates.crt: %r", e)

    elif ssl_context.verify_mode == ssl.CERT_REQUIRED:
        raise RuntimeError(
            "Certificate verification failed: "
            "Cannot find verification data for TrustSever auth handler. "
            "You can install it by running: "
            "'python -m pip install certifi'"
        )

这段代码就像是一张藏宝图,为我们揭示了 pip 定位 CA 证书包的完整路径:

  1. 首先pip 会检查 _TRUST_STORE_FILE 变量是否已经被赋予了特殊的使命。如果它已经指向了某个文件,那么这个文件就是我们要找的证书存储位置。

  2. 如果 _TRUST_STORE_FILE 仍然保持沉默,pip 会召唤出 find_cacert 函数,这位经验丰富的追踪者会在系统中搜寻合适的证书包。它会尝试多个常见路径,例如 /etc/ssl/certs/ca-certificates.crt,就像是一位经验丰富的猎人,不会放过任何一个可能藏匿猎物的地方。

  3. 一旦 find_cacert 成功找到证书包,pip 就会使用 ssl_context.load_verify_locations 方法加载证书,就像是一位经验丰富的工匠,将找到的材料精心打磨,准备投入使用。

  4. 然而 ,如果所有的尝试都以失败告终,并且 ssl_context.verify_mode 被设置为 ssl.CERT_REQUIRED,代表着安全等级已经被提升到最高,pip 只能无奈地抛出 RuntimeError 异常,就像是一位束手无策的医生,只能遗憾地宣布无法找到治疗方案。

实践出真知:验证代码的推理

为了验证我们的推理是否正确,我们可以编写一段简单的 Python 代码,就像是一位严谨的科学家,用实验来验证理论的真伪:

import ssl

from pip._vendor.urllib3.util import ssl_

# 设置 ssl_context.verify_mode 为 ssl.CERT_REQUIRED
ssl_context = ssl.create_default_context()
ssl_context.verify_mode = ssl.CERT_REQUIRED

# 调用 set_default_verify_paths 函数
ssl_.set_default_verify_paths(ssl_context)

# 打印证书存储路径
print(ssl_context.get_ca_certs()[0]['subject'][0][0][1])

运行这段代码,pip 使用的证书存储路径就会清晰地展现在我们眼前,就像是一道闪电划破夜空,照亮了隐藏的真相。

总结

通过深入 pip 源码的冒险之旅,我们成功地揭开了 CA 证书包路径的神秘面纱,也更加了解了 pip 这位搬运工背后的安全机制。 pip 的证书加载机制设计精妙,能够灵活地适应不同的环境,就像是一位经验丰富的旅行家,无论身处何处,都能找到合适的路线。

常见问题解答

  1. 问:为什么 pip 不直接使用系统证书存储?

    答: 使用独立的证书存储可以避免对系统环境的依赖,提高跨平台兼容性。

  2. 问:certifi 包的作用是什么?

    答: certifi 提供了一份捆绑的 CA 证书存储,确保 pip 能够在各种环境下进行安全的 HTTPS 连接。

  3. 问:如果 pip 无法找到合适的证书包怎么办?

    答: 可以尝试手动安装 certifi 包,或者设置 _TRUST_STORE_FILE 环境变量指定证书存储路径。

  4. 问:如何查看 pip 当前使用的证书存储路径?

    答: 可以运行上文提供的 Python 代码,打印 ssl_context.get_ca_certs()[0]['subject'][0][0][1] 的值。

  5. 问:如何禁用 pip 的 SSL 证书验证?

    答: 强烈建议不要禁用 SSL 证书验证,因为这会增加安全风险。如果确实需要禁用,可以使用 --trusted-host--cert 参数。