![为什么在使用“openssl s_client”连接 Apache 时会返回 400“错误请求”错误,即使它发送了 TLS SNI](https://linux22.com/image/758765/%E4%B8%BA%E4%BB%80%E4%B9%88%E5%9C%A8%E4%BD%BF%E7%94%A8%E2%80%9Copenssl%20s_client%E2%80%9D%E8%BF%9E%E6%8E%A5%20Apache%20%E6%97%B6%E4%BC%9A%E8%BF%94%E5%9B%9E%20400%E2%80%9C%E9%94%99%E8%AF%AF%E8%AF%B7%E6%B1%82%E2%80%9D%E9%94%99%E8%AF%AF%EF%BC%8C%E5%8D%B3%E4%BD%BF%E5%AE%83%E5%8F%91%E9%80%81%E4%BA%86%20TLS%20SNI.png)
我有一个网站,可通过 HTTP over TLS 访问。它是多宿主的,因此主机名有多个 A DNS 条目。此外,同一组 IP 地址上有多个网站,具有多个主机名,因此所有这些都是在 Apache 2 中使用几个 NameVirtualHost 设置的。
使用浏览器或 WebDAV 客户端等访问网站非常方便。
但是当我尝试使用“openssl s_client”进行连接时,我无论如何都无法让它工作。Apache 向我抛出 400“错误请求”错误。Apache 选择了正确的 X.509 证书,因此 SNI 必须由 openssl 正确设置。当我在“GET”行上按下“enter”时,甚至在我添加任何标头之前,就会发生这种情况。我使用的 HTTP 语法(基本上是“GET / HTTP/1.1\nHost: hostname”适用于网站的非 TLS 版本(仅重定向到 TLS 版本),所以这应该不是问题。相同的 HTTP 语法也适用于连接到例如www.google.com通过 TLS(端口 443),所以它再次看起来是正确的。
Apache 服务器的日志显示请求末尾有一个换行符,这可能是一个提示。日志条目转到默认尽管 Apache 为所连接的主机名发送了 X.509 证书,但它与默认值不同,即该 IP/端口的 VirtualHost。
我已经使用 wireshark 检查过 ClientHello 中是否设置了 SNI。
以下是一个示例会话:
输入
$ openssl s_client -connect hostname:443
输出
depth=2 O = Digital Signature Trust Co., CN = DST Root CA X3
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
verify return:1
depth=0 CN = hostname
verify return:1
---
Certificate chain
0 s:CN = hostname
i:C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
1 s:C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
i:O = Digital Signature Trust Co., CN = DST Root CA X3
---
Server certificate
-----BEGIN CERTIFICATE-----
(...)
-----END CERTIFICATE-----
subject=CN = hostname
issuer=C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
---
No client certificate CA names sent
Peer signing digest: SHA256
Peer signature type: RSA-PSS
Server Temp Key: X25519, 253 bits
---
SSL handshake has read 3121 bytes and written 388 bytes
Verification: OK
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server public key is 2048 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)
---
---
Post-Handshake New Session Ticket arrived:
SSL-Session:
Protocol : TLSv1.3
Cipher : TLS_AES_256_GCM_SHA384
Session-ID: FBFD6172E3C659D4071B68A36BD5983A8571906C00190A745B5DAA1359D4087C
Session-ID-ctx:
Resumption PSK: 21957A3C267DAA25CE2D7F6BFD3C3593CCFA1E6FC45C4DC5D0CE35C29175F7E9BBDD59EE6C213DC53B8291B7BD055238
PSK identity: None
PSK identity hint: None
SRP username: None
TLS session ticket lifetime hint: 300 (seconds)
TLS session ticket:
0000 - 18 4b 25 ae 49 61 e8 f3-73 b5 bb 3b 50 2f 1d c4 .K%.Ia..s..;P/..
0010 - 7e 02 e9 41 01 9e ed 8b-a1 b6 ec 48 4d 12 11 f2 ~..A.......HM...
Start Time: 1602857943
Timeout : 7200 (sec)
Verify return code: 0 (ok)
Extended master secret: no
Max Early Data: 0
---
read R BLOCK
---
Post-Handshake New Session Ticket arrived:
SSL-Session:
Protocol : TLSv1.3
Cipher : TLS_AES_256_GCM_SHA384
Session-ID: 627DF7810556DB2918484F393E66DEA7BF22DD3A42288B47E46FCDCDEF176125
Session-ID-ctx:
Resumption PSK: 851978077C93284CB3D69B541B77844A48D1CFC8800F60B6F7CF3D297911D0D93E0E935017B78777E2BD51A29E13329F
PSK identity: None
PSK identity hint: None
SRP username: None
TLS session ticket lifetime hint: 300 (seconds)
TLS session ticket:
0000 - c0 1a 60 23 36 80 bf 27-83 0e 1f 26 4b 04 f2 53 ..`#6..'...&K..S
0010 - a0 8a 2d 6e 41 44 17 c7-9b 11 5d dc 7f 08 71 fc ..-nAD....]...q.
Start Time: 1602857943
Timeout : 7200 (sec)
Verify return code: 0 (ok)
Extended master secret: no
Max Early Data: 0
---
read R BLOCK
输入(注意第一个行后没有空行)
GET / HTTP/1.1
输出
Date: Fri, 16 Oct 2020 14:19:11 GMT
Server: Apache/2.4.38 (Debian)
Content-Length: 322
Connection: close
Content-Type: text/html; charset=iso-8859-1
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>400 Bad Request</title>
</head><body>
<h1>Bad Request</h1>
<p>Your browser sent a request that this server could not understand.<br />
</p>
<hr>
<address>Apache/2.4.38 (Debian) Server at default_hostname Port 443</address>
</body></html>
closed
笔记:错误消息提到的是default_hostname,而不是hostname!
相应的 Apache 日志条目是:
default_hostname:443 1.2.3.4 - - [16/Oct/2020:16:19:11 +0200] "GET / HTTP/1.1\n" 400 3279 "-" "-"
请注意,default_hostname 不是主机名,并且 Apache 发送了正确的服务器证书,因此它已识别出正确的 VirtualHost!如果我不使用 SNI 进行连接(openssl s_client -connect 4.3.2.1:443),那么它会发送 default_hostname 的服务器证书。
答案1
...“获取/HTTP/1.1 \n”400 3279“-”“-”
HTTP 请求结尾的行应该是\r\n
而不是\n
。尝试使用-crlf
选项s_client
将 Enter 翻译成\r\n
而不是\n
。
此外,服务器可能对请求的完整发送时间有所限制,因此可能不够快,无法直接输入所有内容。请尝试从文本文件中剪切并粘贴预先创建的请求。
答案2
您确实发送了一个格式错误的请求。HTTP/1.1 要求存在 Host: 标头,并且对于 SNI,Host: 标头的内容必须与 SNI 主机名匹配。
再试一次,这次提供正确的 Host: 标头。