首页
/ uv 的 TLS 证书管理:rustls 后端、自定义 CA 与不信任主机配置详解

uv 的 TLS 证书管理:rustls 后端、自定义 CA 与不信任主机配置详解

2026-09-04 18:45:39作者:裴锟轩Denise

uv 在与包索引及其他 HTTPS 服务器通信时使用 TLS,通过 X.509 证书验证服务器身份,防止连接被中间人截获。本篇以 uv 官方文档中的 TLS 证书主题为核心,完整覆盖 rustls TLS 后端与支持算法、系统证书切换、SSL_CERT_FILE/SSL_CERT_DIR/--cert 自定义 CA 配置、mTLS 客户端证书认证,以及 allow-insecure-host 跳过证书校验的设置方式,并逐条对照 uv 客户端 crate 的源码实现(crates/uv-client/src/tls.rscrates/uv-client/src/base_client.rs 等),说明每种配置在解析与客户端构建阶段的真实行为,帮助你在企业代理、私有 PyPI 镜像、自签名证书等场景下正确配置 uv 的证书信任链。

TLS 后端与支持的目标算法

uv 使用 rustls——一个用 Rust 编写的内存安全 TLS 实现——作为 TLS 后端,并以 aws-lc-rs 作为密码学提供者。这一点在客户端构建代码中可以直接确认:create_client 里通过 client_builder.tls_backend_rustls() 显式固定了后端,见 base_client.rs 中的客户端构造逻辑。

uv 支持的 X.509 证书签名算法如下:

  • ECDSA(P-256、P-384、P-521),搭配 SHA-256、SHA-384 或 SHA-512
  • Ed25519
  • RSA PKCS#1 v1.5(2048–8192 位),搭配 SHA-256、SHA-384 或 SHA-512
  • RSA-PSS(2048–8192 位),搭配 SHA-256、SHA-384 或 SHA-512

如果你的私有 CA 签发的根证书使用了上述范围之外的算法,rustls 将无法将其作为信任锚使用,这通常是“自定义证书配置后仍然校验失败”的根源之一。

默认证书源:内置的 Mozilla 根证书

默认情况下,uv 使用内置的 Mozilla 根证书(webpki 根证书集)来做 TLS 校验。源码中的实现非常直接:Certificates::webpki_roots() 直接返回 webpki_root_certs::TLS_SERVER_ROOT_CERTS,见 tls.rs。注释中还解释了为何选择 webpki-root-certs(返回 DER 字节的 CertificateDer)而非更省空间的 webpki-roots(预解析的 TrustAnchor):前者可以经由 reqwest 的 ClientBuilder::tls_certs_only 传入,从而让 reqwest 继续代管 ALPN、SNI、证书校验与 mTLS 配置。

在客户端构建时,证书源按如下优先级选择(见 base_client.rs):

  1. 存在自定义证书(--cert 参数或 SSL_CERT_FILE/SSL_CERT_DIR 环境变量)时,调用 tls_certs_only(custom_certs)只信任提供的证书;
  2. 否则若启用了系统证书(system-certs),不额外注入根证书,交由平台校验器处理;
  3. 否则回落到 Certificates::webpki_roots() 内置的 Mozilla 根证书。

使用系统证书存储

某些场景下你可能希望改用操作系统的原生证书存储,例如公司强制代理的 trust root 只存在于系统证书库中。uv 提供三种等价的方式开启系统证书:

  • 命令行标志 --system-certs(对应文档 CLI 参考
  • 环境变量 UV_SYSTEM_CERTS 设为 true(对应文档 环境变量参考)
  • uv.toml 中设置 system-certs = true(对应文档 设置参考)

优先级逻辑可以从设置解析代码中看到:CLI 标志 system-certs/no-system-certs 最先被判定,其次才是环境变量 UV_SYSTEM_CERTS,最后才是设置文件中的 system-certs,见 settings.rs

启用系统证书后,校验由 rustls 平台的 verifier 组件(rustls-platform-verifier,即委托操作系统证书校验器)完成,而不是 rustls 内置的 webpki 校验逻辑。从源码结构看,uv 在 system_certs 为真时不会调用 tls_certs_only,从而让 rustls 走其默认的“使用系统根存储”路径。

自定义 CA 证书

要信任私有 CA 或自签证书(而不跳过校验),推荐方式为:

  • 将环境变量 SSL_CERT_FILE 指向一个 PEM 编码的证书 bundle 文件(例如 certs.pemca-bundle.crt);
  • 或将 SSL_CERT_DIR 设置为一个或多个包含 PEM 证书的目录,多个条目用平台分隔符分隔(Unix 为 :,Windows 为 ;)。

对于单次 uv pip 调用,还可以直接传 --cert 参数指向 PEM bundle,该 bundle 会在这次调用中整体替换 uv 的默认证书源。

覆盖语义与容错行为

文档强调的关键语义在源码中有完整对应,值得逐条理解:

  • 完全覆盖而非追加:当 SSL_CERT_FILE/SSL_CERT_DIR 设置为非空值时,默认证书源被完全替换——只信任提供的证书。Certificates::from_env() 的注释明确写着“显式配置的文件或目录总是替换默认根证书,即使它缺失、不可访问或不含有效证书”,见 tls.rs。这意味着如果配置的路径不存在或其中没有有效证书,默认根证书也不会被信任,所有 HTTPS 连接都会失败——这是一个常见坑。
  • 路径解析规则SSL_CERT_DIR 的值通过 std::env::split_paths 按平台分隔符拆分为多个目录,逐个目录加载,见 tls.rs
  • 文件读取容错:证书通常带 .pem.crt.cer 扩展名,但 uv 会尝试读取提供目录中的任意常规文件;无法解析为 PEM 证书的文件会被忽略,符号链接会被解析,悬空符号链接被忽略。
  • 不支持 DER:DER 编码的证书文件不被支持,只能提供 PEM。
  • 无效证书的诊断信息:加载后每个候选证书都会尝试作为信任锚解析(anchor_from_trusted_cert),失败的证书会被过滤掉并产生带有证书主题、失败原因(如 malformed DER、不支持的关键扩展、无效的扩展密钥用途等)的告警,见 InvalidCertificateWarningfilter_invalid 实现,位于 tls.rstls.rs
  • 来源标识:内部用 CertificateSource 枚举区分 --certSSL_CERT_FILESSL_CERT_DIR 三种来源,告警信息会标明证书来自哪个来源,便于排查。
  • SSL_CERT_FILE 可以指向单张证书或多张证书的 bundleSSL_CERT_DIR 可以包含多个目录条目,uv 会加载每个目录中的所有有效证书。
  • 加载入口与优先级:在设置解析阶段,--cert 参数(custom_certificate_file)优先调用 Certificates::from_file,其结果存在时不再读取环境变量,见 settings.rs。与 from_env 不同,from_file 对无效路径或无有效证书的 bundle 会直接返回错误(而非告警忽略),保证 --cert 的失败是可显式感知的,见 tls.rs

相关的行为测试集中在 ssl_certs.rs,覆盖自定义 CA、链式证书等端到端场景;tls.rs 文件末尾的单测也验证了“显式文件/目录覆盖根证书”“缺失路径返回空集”等边界行为,见 tls.rs

客户端证书认证(mTLS)

如果索引服务器要求客户端证书认证,设置环境变量 SSL_CLIENT_CERT 指向一个 PEM 文件,文件内容为证书后跟私钥(同一文件内)。

源码实现为 read_identity:读取整个文件内容并交给 reqwest 的 Identity::from_pem 解析,见 tls.rs。在客户端构建时,若该环境变量存在则调用 client_builder.identity(...) 完成 mTLS 配置;若文件无效,会发出一次性告警(Ignoring invalid SSL_CLIENT_CERT)并继续以无客户端证书的方式构建客户端,见 base_client.rs

跳过证书校验:allow-insecure-host

当你确实需要信任自签名证书或干脆禁用某个主机的证书校验时,可以通过设置项 allow-insecure-host 让 uv 对指定主机跳过 HTTPS 证书验证。例如在 pyproject.toml 中添加:

[tool.uv]
allow-insecure-host = ["example.com"]

该选项接受主机名(如 localhost)或“主机名:端口”对(如 localhost:8080),且仅对 HTTPS 连接生效(HTTP 连接本身就没有加密,无需此配置)。官方文档同时提醒:由于缺少证书校验会带来被中间人攻击的风险,请仅在可信环境(如内网私有镜像)中使用。

源码中的主机匹配与双客户端机制

解析 allow-insecure-host 值的核心类型是 TrustedHost(位于 trusted_host.rs),从源码结构看它支持三种要素:

  • 可选的 scheme(https://http:// 前缀)
  • 主机名
  • 可选的端口

matches 方法按 scheme、端口、主机逐项比对目标 URL。此外,FromStr 实现中还额外支持通配符 *(解析为 TrustedHost::Wildcard,匹配所有主机),这一行为在 trusted_host.rs 的解析逻辑及其单测中可以确认。

运行时机制上,BaseClient 在构建时同时创建两个客户端:一个标准的安全客户端,一个通过 danger_accept_invalid_certs(true) 构建的“危险客户端”,见 base_client.rs。每次发起请求前,for_host 会用 disable_ssl 判断目标 URL 是否命中 allow_insecure_host 列表,命中则路由到危险客户端,否则走安全客户端,见 base_client.rs。因此该配置是逐主机精确生效的,不会全局放宽校验;同样的判断还会通过 git_http_settings 传递给 Git 依赖的 HTTP 拉取,保证 allow-insecure-host 对 Git 索引同样有效。

证书源与配置项速查

配置方式 作用 对应文档
默认(无配置) 使用内置 Mozilla 根证书(webpki 根证书集)
--system-certs / UV_SYSTEM_CERTS=true / system-certs = true 改用操作系统原生证书存储校验 CLI 参考、环境变量参考、设置参考
--cert <path>uv pip 单次调用) 以 PEM bundle 整体替换默认证书源,路径无效时显式报错 CLI 参考
SSL_CERT_FILE=<path> 以 PEM 文件或 bundle 完全替换默认信任锚 环境变量参考
SSL_CERT_DIR=<dir1>:<dir2> 从多个目录加载 PEM 证书,完全替换默认信任锚 环境变量参考
SSL_CLIENT_CERT=<path> mTLS:PEM 文件中包含证书+私钥 环境变量参考
allow-insecure-host = [...] 对指定主机(或 host:port)跳过 HTTPS 证书校验 设置参考

实践要点小结

  • 企业代理注入的根证书只存在于系统信任库时,优先使用 --system-certs,改动最小;
  • 需要在不改动系统环境的前提下信任私有 CA,使用 SSL_CERT_FILE 指向包含“私有 CA + 需要的公共 CA”的合并 bundle 最稳妥,因为该环境变量会完全覆盖默认根证书,只放私有 CA 会导致 PyPI 等公共索引校验失败;
  • 配置后出现“所有 HTTPS 请求都失败”且终端提示 No default certificates will be trusted 之类的告警,说明你配置的 SSL_CERT_FILE/SSL_CERT_DIR 路径无效或不含有效证书,这是覆盖语义的预期行为而非 bug;
  • 目录中的非 PEM 文件会被静默忽略,但无效 PEM 证书会产生包含证书主题与失败原因的诊断告警,排查时可重点关注 --verbose 输出;
  • allow-insecure-host 是“跳过校验”的最后手段,应严格限定到自签证书内网主机,避免配置过宽的主机匹配。
登录后查看全文
热门项目推荐
相关项目推荐