uv 的 TLS 证书管理:rustls 后端、自定义 CA 与不信任主机配置详解
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.rs、crates/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):
- 存在自定义证书(
--cert参数或SSL_CERT_FILE/SSL_CERT_DIR环境变量)时,调用tls_certs_only(custom_certs),只信任提供的证书; - 否则若启用了系统证书(
system-certs),不额外注入根证书,交由平台校验器处理; - 否则回落到
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.pem、ca-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、不支持的关键扩展、无效的扩展密钥用途等)的告警,见InvalidCertificateWarning与filter_invalid实现,位于 tls.rs 与 tls.rs。 - 来源标识:内部用
CertificateSource枚举区分--cert、SSL_CERT_FILE、SSL_CERT_DIR三种来源,告警信息会标明证书来自哪个来源,便于排查。 SSL_CERT_FILE可以指向单张证书或多张证书的 bundle;SSL_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是“跳过校验”的最后手段,应严格限定到自签证书内网主机,避免配置过宽的主机匹配。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00