您的详细信息是错误的(这不是加密本身的问题),但您的总体思路是正确的。
在SSL/TLS中,客户端首先连接到服务器并发送一条“ClientHello”消息,说明“我希望与你做一些 SSL”。这发生在 TCP 层之上,以及与 HTTP 相关的任何东西之下;只有在 SSL 隧道创建后,才会在 SSL 隧道中发送 HTTP 请求。SSL 隧道的创建是通过称为“握手”的过程完成的,其中 ClientHello 消息是第一步。
在握手期间,服务器将他的证书发送给客户端。证书包含服务器名称和他的公钥,并由证书颁发机构签名。只有在“验证”证书后,客户端才会接受使用公钥,该过程需要以下两个步骤:
- 客户端必须验证证书颁发机构对证书的签名。不过,它可能必须在此之前验证证书颁发机构自己的证书;该过程是递归的。证书验证充满了复杂的细节,我将在这里跳过,因为它们与本次讨论无关。
- 客户端必须检查预期的服务器名称是否确实出现在证书中。因为这就是证书的重点:将公钥(用于加密的数学对象)绑定到身份(服务器名称);只有在以某种方式验证公钥确实是服务器控制相应私钥的那个之后,证书颁发机构才会颁发证书(即签名) 。
一旦客户端合理地确定他拥有正确的公钥,他就可以完成握手:根据协商的密码套件,这需要非对称加密或数字签名;无论哪种方式,服务器都将使用他的私钥。在握手结束时,客户端和服务器最终都会获得一个共享的会话密钥,使用该密钥将进行更多的加密,这次是对称加密。
然后,也只有这样,“应用程序数据”才能通过隧道传输。对于 HTTPS,“应用程序数据”包含实际的 HTTP 请求。
现在,问题如下:客户端想要联系给定的服务器(名称如www.foo.com
),但该预期的服务器名称仅与 HTTP 请求一起发送到服务器。在 TCP 级别,仅存在 IP 地址。当服务器需要发送他的证书时,他只知道它是在某个 IP 地址下联系的,但它不知道客户端试图以什么名字来定位它。然而,服务器必须发送一个包含正确名称的证书。当服务器只为每个公共 IP 托管一个 HTTPS 服务器时,这很简单:只有一个可能的名称。由于多个 HTTPS 服务器共享相同的公共 IP,服务器必须进行一些占卜,这在一般情况下对于计算机来说是不可能的(对于占卜者来说已经相当困难了)。
可能的解决方案:
在证书中嵌入多个名称。该服务器只有一个证书,但它包含共享公共 IP 的所有 HTTPS 服务器的名称。这在 X.509 中作为Subject Alt Names扩展受到支持。这要求客户端浏览器以应有的方式实际支持标准,因此这不是给定的。该页面声称大多数浏览器都支持它,但我认为这至少需要进行一些广泛的测试(特别是当扩展中有多个名称并且这些名称包含“wildchar”时*
)。
在开始时通过额外的交换来增强协议。这称为STARTTLS选项。在 HTTP 上下文中,这意味着客户端将首先发送一个包含服务器名称的特殊 HTTP 请求,并使用一个特殊命令(即“STARTTLS”而不是普通的“GET”)来表明客户端参与的意愿在同一连接上立即进行 SSL/TLS 握手。这曾在某个时候为 HTTP 提出过,但被拒绝了,因为它不适用于现有的 HTTP 代理。STARTTLS 用于其他协议,例如 SSMTP 或 IMAPS,在这些协议中,实际连接始终可用。
让客户端在握手中尽早发送预期的服务器名称,例如通过在 ClientHello 消息中的某处走私它。这称为服务器名称指示。这就是 HTTPS 世界的目标。
SNI 最大的问题是 Windows XP 上的 IE 不支持它(它需要 IE 8+,但也需要 Vista 或更高版本;因为 IE 使用的实际 SSL 代码来自操作系统,而不是 IE 本身)。因此,您必须以某种方式诱使使用 WinXP 的客户端切换到 Firefox、Chrome 或其他了解 SNI 并执行 SSL 的浏览器,而不是依赖操作系统提供的实现。