rustls 0.23 (via reqwest, ureq, hyper-rustls)Rust
错误信息
invalid peer certificate: certificate not valid for name "api.example.com"; certificate is only valid for DnsName("www.example.com")
期望的名称和证书提供的名称列表会根据每次失败的情况填入;IP 地址显示为 IpAddress(…)。早期的 rustls 版本只打印变体名称(“invalid peer certificate: NotValidForName”)。
rustls 0.23 (via reqwest, ureq, hyper-rustls) — CertificateError::NotValidForNameContext
含义
证书链是受信任的,但客户端所连接的名称不在证书的主题备用名称(SAN)中。rustls 0.23 会把两者都列出来:期望的名称,以及证书提供的每一个名称(DnsName、IpAddress 条目),因此不匹配之处一目了然。
原因
证书没有覆盖这个名称
常见SAN 列表中缺少另一个子域名、裸域名或改了名的服务;通配符只覆盖一级标签。
通过 IP 或内部别名连接
常见服务名称(Kubernetes、docker-compose)或直接使用的地址,而证书从来没有为它们签发过。
服务器发送了默认证书
偶见代理没有为所请求的名称做配置,于是提供了另一个站点的证书。
修复方法
- 1.
将证书提供的名称与 URL 对比
错误消息中已经列出了这些名称;用 openssl 可以确认服务器实际发送的内容。
bash echo | openssl s_client -connect api.example.com:443 -servername api.example.com 2>/dev/null \ | openssl x509 -noout -ext subjectAltName - 2.
覆盖地址,而不是名称
reqwest 的 ClientBuilder::resolve 会把主机名固定解析到某个套接字地址,因此验证时仍使用证书上的名称。
rust // Keep the certificate's name in the URL and send the connection to a specific address let addr: std::net::SocketAddr = "10.0.0.5:443".parse()?; let client = reqwest::Client::builder().resolve("www.example.com", addr).build()?; let resp = client.get("https://www.example.com/health").send().await?; - 3.
重新签发包含所有在用名称的证书
把缺少的 DNS 名称(或 IP 条目)添加到证书中。
bash openssl req -new -key server.key -out server.csr -subj "/CN=api.example.com" \ -addext "subjectAltName=DNS:api.example.com,DNS:www.example.com"