错误信息
invalid peer certificate: UnknownIssuer
也可能输出为
- InvalidCertificate(UnknownIssuer)
reqwest 会把它打印在错误来源链的末尾:“error sending request for url (…): client error (Connect): invalid peer certificate: UnknownIssuer”;Debug 输出显示为 InvalidCertificate(UnknownIssuer)。ureq 打印的是“io: invalid peer certificate: UnknownIssuer”。基于 rustls 构建的原生二进制程序(pnpm 12 和许多 CLI)显示的也是同样的文本。
rustls (via reqwest, ureq, hyper-rustls, tonic) — rustls::Error::InvalidCertificate(CertificateError::UnknownIssuer)
含义
rustls 无法为服务器证书构建出通往任一已配置信任锚的证书链。UnknownIssuer 是 rustls 对所有路径构建失败统一使用的错误码:私有 CA 或代理 CA、缺失的中间证书,或者自签名证书。哪些根证书被视为受信任,取决于 crate 的构建方式:平台验证器(操作系统证书存储)、rustls-native-certs(启动时加载的操作系统根证书)或 webpki-roots(编译进程序的 Mozilla 列表)。
原因
公司 CA 或私有 CA
常见服务器的证书链终止于一个不在已配置信任锚之列的根证书,这在内部服务和执行 TLS 检查的代理中很常见。
客户端只信任编译进程序的公共根证书
常见使用 webpki-roots 构建的 crate 完全忽略操作系统的证书存储,因此 IT 部门安装的根证书永远不会生效。reqwest 0.13 默认的平台验证器则确实会使用操作系统的证书存储(我们的复现在运行 update-ca-certificates 之后证实了这一点)。
服务器没有发送中间证书
偶见rustls 不会去获取缺失的中间证书;即使根证书受信任,连接只发送叶子证书的服务器也会报出 UnknownIssuer。
服务器使用自签名证书
偶见rustls 对自签名证书同样报告 UnknownIssuer;要么在该客户端中把这张证书作为根证书信任,要么更换它。
修复方法
- 1.
在客户端中添加根证书
ClientBuilder::add_root_certificate 会添加一个信任锚,同时保留完整的验证(名称、日期)。
rust // Trust a private root for this client (in addition to the default roots) let pem = std::fs::read("corp-root-ca.pem")?; let client = reqwest::Client::builder() .add_root_certificate(reqwest::Certificate::from_pem(&pem)?) .build()?; - 2.
将根证书安装到操作系统的证书存储
使用平台验证器(reqwest 0.13 的默认设置)时,操作系统证书存储中的根证书会被信任;在 Linux 上,运行 update-ca-certificates 就够了。
bash # reqwest 0.13 verifies with the platform verifier: install the root in the OS store sudo cp corp-root-ca.pem /usr/local/share/ca-certificates/corp-root-ca.crt sudo update-ca-certificates - 3.
检查编译进程序的是哪种根证书来源
cargo tree 会显示使用的是 webpki-roots 还是 rustls-native-certs;如果必须使用操作系统的证书存储,就切换 Cargo 特性。
bash # Which root source is compiled into this binary? (a crate that is absent reports no match) cargo tree -e features -i rustls-platform-verifier # OS verifier (reqwest 0.13 default) cargo tree -e features -i rustls-native-certs # OS roots loaded at start-up cargo tree -e features -i webpki-roots # Mozilla roots compiled in, OS store ignored - 4.
服务器管理员:发送中间证书
如果握手中只有一张证书,而它的颁发者是中间 CA,就说明服务器配置不完整。
bash openssl s_client -connect api.example.com:443 -servername api.example.com -showcerts </dev/null 2>/dev/null \ | grep -E '^ *[0-9]+ s:|^ *i:'
相关错误
invalid peer certificate: certificate expired: verification timerustls 0.23 (via reqwest, ureq, hyper-rustls)invalid peer certificate: certificate not valid for name "api.example.com"; cer…rustls 0.23 (via reqwest, ureq, hyper-rustls)Error: ERR_PNPM_RESOLVING_NPM_RESOLVER_NETWORK_ERRORpnpm 12 (native binary, rustls)x509: certificate signed by unknown authorityGo crypto/x509