SNI实现多域名虚拟主机的SSL/TLS认证

直到现在,仍然有人认为只有独立 IP 的虚拟主机或 VPS 才能享有 SSL/TLS 连接服务。他们会一本正经地教导你,在 SSL 握手的过程中,根本不会传递域名这条信息,所以服务器端通常返回的是配置中的第一个可用证书;如果要使用多个证书呢,就只能配置不同的 SSL 端口或增加 IP 地址,或者可以花重金使用一个“多域名 SSL 证书”或一个“通配型证书”来达到相同效果。

嗯,这在过去是对的,但不适用于现在了,因为 SNI*(Server Name Indication)* 技术出现了*(实际上早就有了)*。换句话说,一个 IP 地址上可以为不同域名分配使用不同的 SSL 证书;这同时意味着,共享 IP 的虚拟主机也可实现 SSL/TLS 连接。

拓展阅读:《HTTPS的七个误解》

介绍

早期的 SSLv2 根据经典的公钥基础设施 PKI*(Public Key Infrastructure)* 设计,它默认认为:一台服务器*(或者说一个IP)*只会提供一个服务,所以在 SSL 握手时,服务器端可以确信客户端申请的是哪张证书。

但是让人万万没有想到的是,虚拟主机大力发展起来了,这就造成了一个 IP 会对应多个域名的情况。解决办法有一些,例如申请泛域名证书,对所有 *.yourdomain.com 的域名都可以认证,但如果你还有一个 yourdomain.net 的域名,那就不行了。

在 HTTP 协议中,请求的域名作为主机头*(Host)*放在 HTTP Header 中,所以服务器端知道应该把请求引向哪个域名,但是早期的 SSL 做不到这一点,因为在 SSL 握手的过程中,根本不会有 Host 的信息,所以服务器端通常返回的是配置中的第一个可用证书。因而一些较老的环境,可能会产生多域名分别配好了证书,但返回的始终是同一个。

既然问题的原因是在 SSL 握手时缺少主机头信息,那么补上就是了。

SNI*(Server Name Indication,意为“服务器名称指示”)* 定义在 RFC 4366,是一项用于改善 SSL/TLS 的技术,在 SSLv3/TLSv1 中被启用。它允许客户端在发起 SSL 握手请求时(具体说来,是客户端发出 SSL 请求中的 ClientHello 阶段),就提交请求的 Host 信息,使得服务器能够切换到正确的域并返回相应的证书。

要使用 SNI,需要客户端和服务器端同时满足条件,幸好对于现代浏览器来说,大部分都支持 SSLv3/TLSv1,所以都可以享受 SNI 带来的便利。

nginx / apache 服务器端实现

Nginx 和 Apache 服务端支持 SNI 参见:

《nginx 同一个IP上配置多个HTTPS主机》 《apache mod_gnutls实现多HTTPS虚拟主机》

支持SNI的浏览器、服务器、库

  • Internet Explorer 7 及更高版本(Windows Vista 及更高版本操作系统上的),Windows XP 的 Internet Explorer 总不支持,哪怕是 Internet Explorer 8。
  • Mozilla Firefox 2.0 及更高版本
  • Opera 8.0 及更高版本 (必须开启 TLS 1.1 协议)
  • Opera Mobile 至少是 10.1 beta 的 Android 版本
  • Google Chrome (Vista 或更高版本;XP 上的话要求 Chrome 6 及更高版本;OS X 10.5.7 及更高版本要求 Chrome 5.0.342.1 及更高版本)
  • Safari 2.1 及更高版本 (Mac OS X 10.5.6 及更高版本或 Windows Vista 及更高版本)
  • Konqueror/KDE 4.7 及更高版本
  • MobileSafari (在 Apple iOS 4.0 及更高版本的环境下的)
  • Android 默认浏览器 (在 Honeycomb 及更高版本的)
  • Windows Phone 7
  • MicroB (在 Maemo 下的)

支持SNI的服务器

  • Apache 2.2.12 及更高版本 使用 mod_ssl (或用试验性的 mod_gnutls 代替)
  • Cherokee 如果编译了 TLS 支持
  • 打了补丁的 lighttpd 1.4.x 或 1.5.x ,1.4.24+ 没打补丁就行
  • Nginx 在以 SNI 为支持的 OpenSSL 的陪同下
  • LiteSpeed 4.1 及更高版本
  • Pound 2.6 及更高版本
  • Apache Tomcat (Java 7) 及更高版本
  • Microsoft Internet Information Server (IIS) 8

支持SNI的库

  • Mozilla NSS 3.11.1 仅在客户端
  • OpenSSL
  • 0.9.8f (2007年10月11日发布) - 不缺省编译,可用设置选项 ‘–enable-tlsext’ 编译
  • 0.9.8j (2009年1月7日发布) 至 1.0.0 (2010年3月29日发布) – 缺省编译
  • GNU TLS
  • libcurl / cURL 至少 7.18.1 (2008年3月30日) 在 SNI 支持下编译一个 SSL/TLS 工具包
  • Python 3.2 (ssl, urllib 和 httplib 模块)
  • Qt 4.8
  • Oracle Java 7 JSSE

参考文档:http://blog.hesey.net/2012/02/sni-for-multi-domain-ssl-tls.html http://serverfault.com/questions/109800/multiple-ssl-domains-on-the-same-ip-address-and-same-port

在共享 IP 主机上添加 SSL 支持

按照一般步骤安装 SSL 证书【生成证书请求文件(顺便生成私钥)→到 SSL 服务商生成证书→合并证书扔进去】,然后修改下网站资源链接即可。只要主机面板*(如 cPanel)有相关选项,就应该没有内部技术问题(少数时候服务器不支持,此时应该联系主机商)*。

cPanel 下“SSL/TLS 管理器”中的“管理 SSL 主机”会提示道:

注意:您没有一个独立 IP。因此,当用户访问您任何一个 SSL 网站时,不支持 SNI 的浏览器很可能会向用户发出安全警告。Windows XP 上的 Microsoft Internet Explorer 是最广泛使用而不支持 SNI 的浏览器。

经实际测试,虚拟主机共享 IP 上首个安装 SSL 证书的人看起来有特权——整个 IP 都“安装上”那个证书了。可以推得也可以观察到,不支持 SNI 的浏览器在需要 SSL 证书时也将请求到这首个安装的证书。这对有“特权”的那个站*(即第一个安装 SSL 的站点)是好的,因为这非常好地兼容了不支持 SNI 的浏览器,使其总是不会发出警告,但主机上邻居们的 https 访问就悲催了。如果邻站安装了 SSL 证书,一个使用不支持 SNI 技术的浏览器去访问 https 站的访客将收到证书上域名不匹配的警告,即便忽略警告后访问的仍是目标站,但因证书不是预期的目标站的证书,此访客也存有中招中间人攻击的风险;更令人不安的是,若邻站没有安装 SSL 证书,使用不论支持 SNI 与否的浏览器去访问 https 站的访客将收到相同警告,忽略后会访问到有“特权”的那个站而非目标站(至少在 cPanel 主机如此)*,而此时“特权站长”除了能够发起中间人攻击外,更是可以十分方便省事地制作一个特别的网站,可以是各种跳转页,可以是恶作剧网站,更可以是……

如果 https ssl 网站报错

安全证书的名称无效或者与站点名称不匹配

或者

Incorrect certificate because this client doesn’t support SNI

最快的解决方式是把你的网站配置成 一个ip对应一个域名,如果非要一个ip对应多个域名,那配置在最前面的一个域名访问是正常的,其余域名都会有这个错误。