错误信息
curl: (60) SSL: no alternative certificate subject name matches target hostname 'api.example.com'
也可能输出为
- curl: (60) SSL: no alternative certificate subject name matches target host name 'api.example.com'
curl 8.x 打印“target hostname”;curl 7.x 打印的是“target host name”。引号中的主机就是 URL 中写的内容。非常旧的 curl 版本(7.62.0 之前)对这项检查使用退出码 51。
curl (OpenSSL backend) — CURLE_PEER_FAILED_VERIFICATION, hostname check in lib/vtls/openssl.c
含义
证书链是受信任的,但证书主题备用名称(SAN)扩展中的 DNS 名称(或 IP 地址)没有一个与 URL 中的主机匹配。curl 只拿 URL 的主机与 SAN 条目比较;只要存在 SAN 扩展,主体的通用名称就会被忽略。末尾引号中的名称就是要求 curl 访问的主机。
原因
证书中没有列出这个名称
常见为 example.com 签发的证书并不覆盖 www.example.com 或 api.example.com,除非每个名称都在 SAN 列表中。新的子域名和新环境(staging、eu.)需要重新签发证书。
URL 使用了 IP 地址、localhost 或内部别名
常见无论使用 https://10.0.0.5 还是 https://localhost 访问,或者使用 docker-compose 服务名,都能到达正确的服务器,但请求的是证书签发时从未包含的名称。IP 地址只与 IP 类型的 SAN 条目匹配,从不与 DNS 类型的条目匹配。
服务器返回了另一个网站的证书
偶见在共享 IP 上,证书根据 SNI 选择。如果请求的名称没有匹配的 server 块,服务器会用默认证书响应,而该证书上写的是另一个网站。
以为通配符覆盖的范围比实际更大
偶见*.example.com 覆盖 api.example.com,但不覆盖 example.com,也不覆盖 v2.api.example.com:通配符只代表恰好一个标签。
修复方法
- 1.
将 SAN 列表与 URL 比对
打印服务器所提供证书的 subjectAltName 扩展。修复方法有两种:换用列出该名称的证书,或者让 URL 使用已列出的名称之一。
bash # Names the served certificate actually covers echo | openssl s_client -connect api.example.com:443 -servername api.example.com 2>/dev/null \ | openssl x509 -noout -subject -ext subjectAltName - 2.
重新签发证书,包含所有正在使用的名称
把所有主机名(如果客户端通过 IP 连接,还有 IP 地址)都申请为 SAN 条目。只把名称放进通用名称,对当前任何客户端都不起作用。
bash # ACME: list every name the certificate must cover sudo certbot certonly --nginx -d example.com -d www.example.com -d api.example.com # Private CA: put all names into subjectAltName (CN is ignored for matching) openssl req -new -key server.key -out server.csr -subj "/CN=api.example.com" \ -addext "subjectAltName=DNS:api.example.com,DNS:www.example.com,IP:10.0.0.5" - 3.
保留名称,只改地址
当你必须访问某个特定地址(负载均衡器节点、隧道、预发布环境的 IP)时,--resolve 或 --connect-to 会保留 URL 中的主机,因此 SNI 和主机名检查仍然使用证书上的名称。
bash # Keep the certificate's name in the URL and steer the connection to another address curl --resolve api.example.com:443:10.0.0.5 https://api.example.com/ curl --connect-to api.example.com:443:lb-internal.example.net:443 https://api.example.com/ - 4.
服务器所有者:检查 SNI 路由
如果带 SNI 和不带 SNI 的请求得到的证书不同,就为该名称添加一个 server 块(或 Ingress 规则),并确保客户端访问真实主机名时拿到的不是默认证书。
bash # Compare what the server returns with and without SNI openssl s_client -connect 10.0.0.5:443 -servername api.example.com </dev/null 2>/dev/null | openssl x509 -noout -subject openssl s_client -connect 10.0.0.5:443 -noservername </dev/null 2>/dev/null | openssl x509 -noout -subject
相关错误
x509: certificate is valid for example.com, not api.example.comGo crypto/x509Hostname/IP does not match certificate's altnames: Host: api.example.com. is no…Node.js tls.checkServerIdentity()[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: Hostname mismatch,…Python ssl module (OpenSSL host check)No subject alternative DNS name matching api.example.com found.Java (JSSE HostnameChecker)fatal: unable to access 'https://git.example.com/team/app.git/': SSL: certifica…git over HTTPS with libcurl-gnutls (Debian, Ubuntu)