Curl没有发送客户端证书。

10

我正在尝试发送一个简单的curl请求:

curl -k -i --key ./key.pem --cert ./cert.pem https://target_ip/whatever/

我遇到的问题是它没有发送任何证书。验证明显通过,否则我会看到诸如密钥不匹配等错误,但我可以在wireshark中看到证书未在TCP连接(围绕Client Hello)中被发送。像--verbose--cacert这样的开关也没有什么用处。
我能够通过postman成功地发送完全相同的证书。
我尝试从不同的来源发送相同的curl请求,例如我的WSL2 ubuntu、云中的debian容器、虚拟机等等。
有什么提示为什么它不发送证书吗?
编辑I - curl -v 的输出
*   Trying 52.xxx.xxx.xx:443...
* TCP_NODELAY set
* Connected to 52.xxx.xxx.xx (52.xxx.xxx.xx) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server accepted to use http/1.1
* Server certificate:
*  subject: CN=NGINXIngressController
*  start date: Aug 10 18:08:13 2020 GMT
*  expire date: Aug 10 18:08:13 2021 GMT
*  issuer: CN=NGINXIngressController
*  SSL certificate verify result: self signed certificate (18), continuing anyway.
> GET /whatever/ HTTP/1.1
> Host: custom.localhost.dev
> User-Agent: curl/7.68.0
> Accept: */*
> Authorization: Bearer  eyJ0...
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 403 Forbidden
HTTP/1.1 403 Forbidden
< Server: nginx/1.19.0
Server: nginx/1.19.0
< Date: Mon, 10 Aug 2020 22:23:24 GMT
Date: Mon, 10 Aug 2020 22:23:24 GMT
< Content-Type: text/html
Content-Type: text/html
< Content-Length: 153
Content-Length: 153
< Connection: keep-alive
Connection: keep-alive

<
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.19.0</center>
</body>
</html>
* Connection #0 to host 52.xxx.xxx.xx left intact

编辑二 - wireshark 抓包

匿名化 pcap 过于繁琐,这里只提供一些截图,请您查看所需内容。我已经在数据包中标出了没有看到证书发送的位置。请注意,我在我的 Windows 工作站上运行 postman,而 curl 在 WSL2 上运行,因此源地址有所不同。但其他 curl 主机的行为也是相同的。

Curl

Curl

Postman

Postman

编辑三 - 客户端 Hello 信息

Curl

Curl

Postman

Postman


“...我可以在Wireshark中看到…” - 你能否在Wireshark中看到服务器实际请求证书(即CertificateRequest)?URL是否与Postman完全相同?您能提供curl -v的调试输出吗? - Steffen Ullrich
2
@Trimack: "......由于在nginx服务器上ssl_verify_client optional_no_ca;是纯粹可选的,因此不需要请求证书。" - 使用此设置仍然会请求证书,但空证书将被接受作为响应。通常情况下,除非明确请求,否则客户端永远不会发送证书。 - Steffen Ullrich
冗长的输出清楚地显示服务器没有证书请求。这就是为什么客户端没有发送任何客户端证书的原因。您能否提供一个失败的curl连接和成功的postman连接的数据包捕获,以便可以看到差异在哪里? - Steffen Ullrich
Wireshark的输出显示使用了不同的IP,因此很可能使用了不同的服务器和不同的URL。其中一个服务器发送了CertificateRequest,而另一个则没有。对于没有CertificateRequest的服务器,TLS服务器响应的大小也比另一个服务器要大,这可能是由返回的不同证书引起的。问题很可能是服务器配置的差异,而不是curl与postman的区别。 - Steffen Ullrich
@SteffenUllrich,从服务器日志中可以看到他们正在访问相同的配置。他们还都使用IP请求,并使用相同的主机头来匹配目标虚拟服务器。我很快会上传新捕获的客户端Hello详细信息。我可以看到扩展中存在差异,但不知道哪个扩展会导致服务器请求证书以及为什么会有这些差异。 - Trimack
显示剩余4条评论
1个回答

8

ClientHello 显示了明显的差异:postman使用 server_name 扩展(SNI)提供预期的主机名,而 curl 没有。

这很可能会触发 Web 服务器配置中的不同部分:postman 触发访问特定虚拟主机,该虚拟主机作为 server_name 给出,而 curl 可能会遇到默认配置。假设仅在特定虚拟主机启用客户端证书,这解释了为什么服务器仅向 postman 发送 CertificateRequest 而不是向 curl 发送。

目前尚不清楚此主机名是什么,但从长度来看,它不能是 IP 地址。因此,即使声称是通过 https://target_ip/ 访问,即没有给出主机名,postman 仍然必须知道服务器的预期主机名。curl 无法从此 URL 推导出预期的主机名,因此无法设置 server_name。要让 curl 知道要设置 server_name 的主机名,并且仍然能够访问特定的 IP,请使用 --resolve 选项:

curl --resolve hostname:443:target_ip https://hostname/

非常感谢,你说得很准确。这里再提供一些进一步的解释:当客户端 Hello 到达时,Nginx 必须选择一个服务器块和证书来发出。此时它还没有 Host 标头,显然是根据 SNI 决定的,而 Postman 在 curl 不会自动填充。因此,对于 TLS 握手,配置(服务器块)与处理标头的 HTTP 的配置不同。 - Trimack
2
@Trimack:是的,这就是SNI的全部意义——在TLS握手中已经有了目标名称,因此可以基于请求的名称选择配置(包括证书),当在同一IP地址上有多个名称时。 - Steffen Ullrich

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接