HTTPS 与服务器名

一直以来都觉得用 HTTPS 的话,所有传输的信息都是密文传输的。正是因为如此,用 hosts 配合 HTTPS 来绕过墙访问一些敏感网站一直是一种看过去天衣无缝的完美方法:所有的程序都不需要修改,不需要知道如何使用代理,只要照常使用就可以了。唯一的缺陷是,hosts 的地址可能不时会失效,需要更换。而这唯一的缺陷,还可以用私设反向代理来解决。完美,毫无破绽。

提问

可当我对安全协议有一些了解之后,发现这件事情并没有那么简单。HTTPS 只是简单地将 HTTP 协议包裹进了 SSL/TLS,作为它们的应用数据。而 SSL/TLS 的协议流程,简单地来说可以分为握手和交换数据两大部分。而 HTTP 的数据,作为应用数据,显然是在交换数据阶段进行传送的,换句话说,握手这件事情,应该跟 HTTP 协议里的东西没有半毛钱关系。毕竟,握手的大多数内容都可以被认为是明文进行的嘛,如果握手的内容里有 HTTP 协议里的内容,不就泄密了?

说不定还真是如此呢。

我们知道 HTTPS 协议在连接服务器的时候,需要向服务器要求一个证书,服务器如果能返回和所访问的域名相匹配的证书,才能证明它真的是我们要找的服务器。这不,前几天 GitHub 的 HTTPS 就被劫持了,访问时出示了一个无效的证书,于是被浏览器拦下来了。

这就有一个问题了,既然证书是用来证明域名的,但是域名我们知道,实际上是由 HTTP 协议头中的 Host: 字段指出的,HTTP 协议的一切内容都是在握手结束之后才开始的,可证书又是在 SSL/TLS 的握手阶段由服务器提供的。那么服务器如何在发送证书之前,搞清楚客户端到底想要访问哪个域名,要求它出示哪张证书呢?

最简单最原始的解决方法就是:一个 IP 地址绑定一个证书,而这个 IP 地址专为指定的域名服务。这显然不算是个坏主意,即使多个域名绑定在一台机器上,在 IP 地址被认为还有很多的年代,一台机器配好几个 IP 也并不稀奇,分别给这不同的 IP 绑上不同的域名和不同的证书就可以了。

那么现在如果还想这么玩,大概就没那么容易了吧?毕竟 IPv4 的地址已经成了一种稀缺品。现在的凡人如果想要在一台机器上面、一个 IP 上面放多个不同的域名不同的网站,不得不使用基于名称的虚拟主机 (Name-based Virtual Host),著名的 Apache 当然支持。可是我们不小心发现,Apache 对基于名称的虚拟主机,甚至也支持 HTTPS!这果然是一件奇怪的事,它是怎么做到的呢?

回答

我发现这个问题后,有两个猜想,一是服务器将其所持有的所有证书都发过来,客户端自己挑选自己需要的那个来作为服务器的证明;二是客户端有某种办法将需要的域名在握手阶段就告知服务器端,以便服务器端做出选择。

第一种方法的弊病是显而易见的,如果证书很多的话,会浪费不少流量和时间,而且服务器所持有的证书也瞬间全部暴露了,这可没什么好处。但是第二种方法,在握手阶段告知就意味着域名会被明文传输,看起来似乎也不怎么妙啊。

我当然不可能是第一个提出这个问题的人,Stack Overflow 上面早已有类似的提问。而这种解决方法,正是上文所述的第二种——在要求服务器发送证书之前提前告知自己所请求的域名。这种技术叫做 SNI (Server Name Indication),是一个 SSL/TLS 的扩展。这个扩展也已经被 IETF 标准化为了 RFC 6066 的一部分。这种方法正是在最初的握手过程中明文提交域名以供服务器参考。

思考

我的疑问算是被回答了,但是这显然是一个很麻烦的事情。我们知道墙的屏蔽方法无非是——网址关键词、DNS 污染和 IP 丢包。其中网址关键词本可以使用 HTTPS 的方式来规避掉的,这也是 hosts 方式的技术基础。但如果域名在握手阶段就被提前暴露了,剩下的还有什么用意义呢?

RFC 6066 中也有提到服务器名的安全考虑,不过只是草草带过,并认为这并没有带来什么严重的安全问题。对于美国人民来说当然是没有的嘛。不过话说回来,证书里的域名也是明文保存的,所以确实可以说没有引入安全问题吧。只能祈祷墙不会嗅探到这种东西了?

Comments !

social