通过SSL连接向转发HTTP代理发送CONNECT请求?

15

我正在编写一个HTTP代理,但是在了解如何通过TLS进行CONNECT请求时遇到了一些问题。为了更好地了解情况,我正在使用Apache进行实验,以观察它如何与客户端交互。这是来自我的默认虚拟主机。

NameVirtualHost *:443
<VirtualHost>
  ServerName example.com
  DocumentRoot htdocs/example.com  
  ProxyRequests On
  AllowConnect 22
  SSLEngine on
  SSLCertificateFile /root/ssl/example.com-startssl.pem
  SSLCertificateKeyFile /root/ssl/example.com-startssl.key
  SSLCertificateChainFile /root/ssl/sub.class1.server.ca.pem
  SSLStrictSNIVHostCheck off
</VirtualHost>

Apache和我的客户端之间的对话如下所示。

a. 客户端连接到 example.com:443,并在TLS握手中发送example.com

b. 客户端发送HTTP请求。

CONNECT 192.168.1.1:22 HTTP/1.1
Host: example.com
Proxy-Connection: Keep-Alive

c. Apache报告 HTTP/1.1 400 Bad Request。Apache错误日志显示:

Hostname example.com provided via SNI and hostname 192.168.1.1
provided via HTTP are different. 

看起来Apache除了检查是否存在Host头部外,不会对其进行进一步的检查。即使客户端发送Host: foo,我也会得到相同的失败行为。如果我在没有TLS的情况下向example.com:80发出HTTP请求,那么Apache将连接我到192.168.1.1:22。

我不完全理解这种行为。 CONNECT请求有什么问题吗?我似乎找不到相关的RFC部分来解释所有这些。


1
SNI 上面的意思是握手中发送的主机名,而不是主机头。正如我在下面的答案中所写的,混合 SSL 和 CONNECT 代理并不典型。看起来 Apache 根本没有预料到这一点,因为它进行了证书验证。您可以尝试在 Apache 中使用 SSLStrictSNIVHostCheck off - eckes
4个回答

41

不清楚您是想将Apache Httpd用作代理服务器,这可能解释了您收到的400状态代码。

CONNECT由客户端使用,并发送到代理服务器(可能是Apache Httpd,但通常不是),而不是目标Web服务器。

在客户端和代理服务器之间建立TLS连接之前,客户端与代理服务器使用CONNECT。客户端(C)连接到代理(P)proxy.example.com并发送此请求(包括空行):

C->P: CONNECT www.example.com:443 HTTP/1.1
C->P: Host: www.example.com:443
C->P:

代理服务器打开一个TCP连接到 www.example.com:443 (P-S),并以200状态码响应客户端请求,接受该请求:

P->C: 200 OK
P->C: 

然后,客户端和代理(C-P)之间的连接保持打开状态。代理服务器将 C-P 连接中的所有内容转发到 P-S 并返回。客户端通过启动该通道上的 TLS 握手将其活动(P-S)连接升级为 SSL/TLS 连接。由于现在一切都被中继到服务器,就好像 TLS 交换是直接与 www.example.com:443 进行的一样。

代理在握手过程中没有任何作用(因此也不涉及 SNI)。TLS 握手实际上是在客户端和最终服务器之间直接进行的。

如果您正在编写代理服务器,则要允许客户端连接到 HTTPS 服务器,您需要读取 CONNECT 请求,从代理到最终服务器建立连接(在 CONNECT 请求中给出),向客户端发送 200 OK 的回复,然后将从客户端读取的所有内容转发到服务器,并反之亦然。

RFC 2616CONNECT 视为建立简单隧道的一种方式(实际上确实如此)。在 RFC 2817 中有更多相关内容,但 RFC 2817 的其余部分(在非代理 HTTP 连接中升级到 TLS)很少使用。

看起来您正在尝试让客户端(C)与代理(P)之间的连接使用 TLS。这很好,但是客户端不会使用 CONNECT 连接到外部 Web 服务器(除非它也是连接到 HTTPS 服务器)。


1
  1. 想要了解的是,当客户端可以直接使用SSL与终端服务器通信时,为什么会使用HTTP“CONNECT”?无论是“CONNECT”还是SSL,它们都将通过配置的代理进行遍历。
  2. 另外,在“CONNECT”请求中,客户端在哪个头字段中指定中间代理服务器地址?
- Sandeep
@ Sandeep,代理没有标题,客户端通过套接字目录连接到代理。这就是代理的作用。 - Cholthi Paul Ttiopic
那么,通过CONNECT方法,客户端的任何https数据都不会传递到中间代理的应用程序级别?只是在代理的TCP级别上进行评估,并直接中继到远程服务器? - zzinny

4

3

来自RFC 2616(第14.23节):

Host请求头字段指定正在请求的资源的Internet主机和端口号,这些信息源自用户或引用资源(通常是HTTP URL,如3.2.2节所述)给出的原始URI。 Host字段值必须表示原始URL给出的源服务器或网关的命名权限。

我的理解是,您需要将CONNECT行中的地址复制到HOST行中。 总之,资源的地址是192.168.1.1,通过example.com连接并不会从RFC的角度改变任何东西。


根据第5.2节,“2.如果请求URI不是绝对URI,并且请求包括Host头字段,则主机由Host头字段值确定。” 对于CONNECT,请求URI不是绝对URI(第5.1.2节)。 - sigjuice
@sigjuice...所以5.2版本并不适用(你为什么要提到它?) - Eugene Mayevski 'Callback
@sigjuice 你把错误的变量(5.1和5.2节)引入了方程式中。至于Apache-最有可能他们在证书管理中使用主机头,而不是过多关注RFCs。 - Eugene Mayevski 'Callback
如果我通过非TLS HTTP/1.1连接发送CONNECT到80端口,主机头似乎仍然无关紧要。我可以说“Host: abc”,Apache仍然会连接到22端口。对我来说,这看起来像是违反了5.2的规定。 - sigjuice
1
@sigjuice:我理解14.23节的方式是,必须使用“Host”头来指示所请求资源的主机。使用“CONNECT”不属于“Host”头允许您选择哪个虚拟主机应处理“CONNECT”的类别:所请求的资源仍将是客户端的最终目标。这也与第14.23节中指定的代理服务器的非“CONNECT”使用“Host”一致。我只是认为没有设想过基于名称选择代理主机本身。 - Bruno
显示剩余2条评论

2
在TLS(https)中很少看到CONNECT方法。实际上,我不知道有哪个客户端会这样做(如果有的话,我很想知道是谁,因为我认为这是一个很好的功能)。
通常情况下,客户端使用http(纯tcp)连接到代理,并将CONNECT方法(和主机头)发送到host:443。然后代理将建立与终端点的透明连接,然后客户端通过发送SSL握手来保护数据的“端对端”。
CONNECT方法并没有真正被指定,在HTTP RFC中只是保留了它。但通常它非常简单,因此是可互操作的。该方法指定主机[:端口]。Host: 头可以简单地被忽略。可能需要一些额外的代理身份验证标头。当连接体开始时,代理不再需要进行任何解析(一些代理会这样做,因为它们检查有效的SSL握手)。

1
顺便提一下:Chrome支持与代理服务器的SSL连接:http://www.chromium.org/developers/design-documents/secure-web-proxy - eckes

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