ch4-tls

传输层安全协议(TLS)

网络基础 101,第四章

TLS 协议最初由 Netscape 开发,原名为 SSL(安全套接层),旨在为 Web 上的电子商务交易提供安全保障——这需要加密技术来保护客户的个人数据,同时需要认证和完整性保证来确保交易安全。为实现这一目标,SSL 协议被实现在应用层,直接运行于 TCP 之上(图 4-1),使得上层的协议(HTTP、邮件、即时通讯等)无需修改即可运行,同时为跨网络通信提供安全保障。

当 TLS 被正确使用时,第三方观察者只能推断出连接的两端、加密类型,以及数据传输的频率和大致数量,但无法读取或修改任何实际数据。 Figure 4-1. Transport Layer Security (TLS) 图 4-1. 传输层安全(TLS)

当 SSL 协议被 IETF 标准化后,更名为传输层安全(TLS)。虽然许多人仍习惯混用 TLS 和 SSL 这两个名称,但技术上它们是不同的——各自描述了协议的不同版本。

SSL 2.0 是该协议首个公开发布的版本,但由于发现多处安全漏洞,很快被 SSL 3.0 取代。由于 SSL 协议最初是 Netscape 的专有技术,IETF 成立了专门工作组对其进行标准化,最终于 1999 年 1 月发布 RFC 2246,即 TLS 1.0。此后,IETF 持续迭代该协议以修复安全漏洞并扩展其功能:TLS 1.1(RFC 4346)于 2006 年 4 月发布,TLS 1.2(RFC 5246)于 2008 年 8 月发布,TLS 1.3(RFC 8446)则于 2018 年 8 月正式发布。

务必注意:你的服务器应始终优先协商最新的稳定版 TLS 协议,以确保最佳的安全性、功能和性能。事实上,某些对性能要求极高的功能(如 HTTP/2 和 HTTP/3)明确要求使用 TLS 1.2 或更高版本,否则会中止连接。优秀的安全性与性能从来都是相辅相成的。

TLS 设计为运行在可靠的传输协议(如 TCP)之上。不过,它也被适配到数据报协议(如 UDP)上运行。**数据报传输层安全(DTLS)**协议(定义于 RFC 6347)基于 TLS 协议,能够在保持数据报传输模式的同时提供类似的安全保障。


加密、认证与完整性

TLS 协议旨在为运行于其上层的所有应用提供三项核心服务:加密认证数据完整性。技术上讲,并非每种场景都必须同时使用这三项服务——你可以选择接受未经真实性验证的证书,但务必清楚这样做带来的安全风险。实际上,任何安全的 Web 应用都应充分利用这三项服务。

加密(Encryption) 一种机制,用于混淆主机之间传输的内容。

认证(Authentication) 一种机制,用于验证所提供身份材料的有效性。

完整性(Integrity) 一种机制,用于检测消息篡改和伪造。

要建立加密安全的数据通道,通信双方必须就使用的密码套件和加密密钥达成一致。TLS 协议定义了明确的握手序列来完成这一交换,我们将在”TLS 握手”一节详细探讨。这一握手的精妙之处——也是 TLS 在实践中可行的原因——在于它使用了公钥密码学(又称非对称密码学),使得通信双方无需事先了解对方,就能在未经加密的通道上协商出共享密钥。

作为 TLS 握手的一部分,协议还允许双方认证彼此身份。在浏览器环境中,这一机制使客户端能够验证服务器的真实身份(例如,确认对方确实是你的银行,而非通过伪造名称或 IP 地址冒充目标的攻击者)。这一验证基于信任链的建立——详见”信任链与证书颁发机构”。此外,服务器也可以选择验证客户端身份——例如,公司代理服务器可以认证所有员工,每位员工都拥有公司签名的唯一证书。

最后,在加密和认证就位后,TLS 协议还提供自身的消息分帧机制,并为每条消息附加消息认证码(MAC)。MAC 算法是一种单向加密哈希函数(本质上是校验和),其密钥由通信双方协商确定。每当发送 TLS 记录时,都会生成并附加该消息的 MAC 值,接收方随后计算并验证收到的 MAC 值,以确保消息的完整性和真实性。

这三项机制共同构成了 Web 安全通信的基础。所有现代浏览器都支持多种密码套件,能够认证客户端和服务器,并对每条记录透明地执行完整性检查。


代理、中间设备、TLS 与 Web 新协议

HTTP 的可扩展性和成功催生了 Web 上丰富多样的代理和中间设备生态:缓存服务器、安全网关、Web 加速器、内容过滤器等等。在某些情况下我们知晓它们的存在(显式代理),而在另一些情况下它们对终端用户完全透明。

不幸的是,这些服务器的成功和普遍存在,给任何试图以任何方式偏离 HTTP/1.x 协议的人带来了问题:某些代理服务器可能只是中继它们无法解析的新 HTTP 扩展或替代传输格式;另一些可能在不应该的情况下继续盲目应用其逻辑;还有一些(如安全设备)可能在不存在恶意意图的情况下推断出恶意行为。

换句话说,在实践中,在 80 端口上偏离 HTTP/1.x 的既定语义往往导致部署不可靠:某些客户端没有问题,而另一些则出现不可预测且难以复现的故障——例如,同一客户端在不同网络间移动时可能表现出不同行为。

由于这些行为,WebSocket、HTTP/2、HTTP/3 等新协议和 HTTP 扩展不得不依赖建立 HTTPS 隧道来绕过中间代理,提供可靠的部署模型:加密隧道对所有中间设备混淆了数据。如果你曾疑惑为什么大多数 WebSocket 指南都告诉你使用 HTTPS 向移动客户端传输数据,这就是原因。


全面 HTTPS 化

未经加密的通信——通过 HTTP 和其他协议——会带来大量隐私、安全和完整性漏洞。这类交换容易被拦截、操纵和冒充,可能泄露用户凭证、浏览历史、身份信息和其他敏感数据。我们的应用需要通过 HTTPS 传输数据,来保护自身和用户免受这些威胁。

HTTPS 保护网站完整性 加密阻止入侵者篡改交换的数据——例如重写内容、注入不需要的恶意内容等。

HTTPS 保护用户隐私和安全 加密阻止入侵者窃听交换的数据。每个未受保护的请求都可能泄露用户的敏感信息,当这类数据在多个会话中聚合时,可用于去匿名化身份并泄露其他敏感信息。就用户而言,所有浏览活动都应被视为私密和敏感的。

HTTPS 解锁 Web 的强大功能 越来越多的新 Web 平台功能——如访问用户地理位置、拍照、录制视频、启用离线应用体验等——需要用户明确授权,而这反过来需要 HTTPS。HTTPS 提供的安全和完整性保证,是交付安全用户权限工作流和保护其偏好的关键组件。

为进一步强调这一点,互联网工程任务组(IETF)和互联网架构委员会(IAB)均已向开发者和协议设计者发布指导意见,强烈鼓励采用 HTTPS:

  • IETF:普遍监控是一种攻击
  • IAB:关于互联网机密性的声明

随着我们对互联网的依赖日益加深,每个人面临的风险和利害关系也在增长。因此,作为应用开发者和用户,确保通过全面启用 HTTPS 来保护自己,是我们的责任。


Let’s Encrypt

广泛采用 HTTPS 的一个常见障碍和阻碍,是需要向可信机构购买证书——详见”信任链与证书颁发机构”。Let’s Encrypt 项目于 2015 年启动,正是为解决这一特定问题:

“Let’s Encrypt 是一个免费、自动化、开放的证书颁发机构,由互联网安全研究组(ISRG)运营。Let’s Encrypt 和 ACME 协议的目标是:让你能够设置 HTTPS 服务器并自动获取浏览器信任的证书,无需任何人工干预。”

访问项目网站了解如何在你的站点上部署。现在没有任何限制——任何人都可以免费为其站点获取可信证书。


TLS 握手

在客户端和服务器能够通过 TLS 交换应用数据之前,必须先协商加密隧道:双方必须就 TLS 协议版本达成一致,选择密码套件,并在必要时验证证书。不幸的是,这些步骤中的每一步都需要客户端和服务器之间进行新的数据包往返(图 4-2),这为所有 TLS 连接增加了启动延迟。

Figure 4-2. TLS handshake protocol

图 4-2. TLS 握手协议

图 4-2 假设纽约和伦敦之间单向”光纤中的光速”延迟为 28 毫秒,与之前 TCP 连接建立示例相同;参见表 1-1。

0 毫秒 TLS 运行在可靠传输层(TCP)之上,这意味着我们必须首先完成 TCP 三次握手,这需要一次完整往返。

56 毫秒 TCP 连接建立后,客户端以明文发送若干参数,包括其运行的 TLS 协议版本、支持的密码套件列表,以及可能希望使用的其他 TLS 选项。

84 毫秒 服务器选择用于后续通信的 TLS 协议版本,从客户端提供的列表中决定密码套件,附加其证书,并将响应发回客户端。可选地,服务器还可以请求客户端证书,以及发送其他 TLS 扩展的参数。

112 毫秒 假设双方能够协商出共同版本和加密算法,且客户端对服务器提供的证书满意,客户端启动密钥交换(如 ECDHE),用于建立后续会话的对称密钥。

140 毫秒 服务器处理客户端发送的密钥交换参数,通过验证 MAC 检查消息完整性,并向客户端返回加密的 Finished 消息。

168 毫秒 客户端使用协商好的对称密钥解密消息,验证 MAC,如果一切正常,则隧道建立,现在可以发送应用数据。

如上述交换所示,新的 TLS 连接需要两次往返才能完成”完整握手”——这是坏消息。然而,在实践中,优化后的部署可以做得更好,实现稳定的 1-RTT TLS 握手

  • False Start 是 TLS 协议扩展,允许客户端和服务器在握手仅部分完成时就开始传输加密应用数据——即一旦发送 ChangeCipherSpec 和 Finished 消息,无需等待对方执行相同操作。这一优化将新 TLS 连接的握手开销减少到一次往返;详见”启用 TLS False Start”。

  • 如果客户端之前与服务器通信过,可以使用”简化握手”,这需要一次往返,同时允许双方通过复用之前协商的安全会话参数来降低 CPU 开销;详见”TLS 会话恢复”。

上述两种优化结合,使我们能够为新访客和回头客都提供稳定的 1-RTT TLS 握手,同时为基于先前协商会话参数恢复的会话节省计算开销。务必在部署中利用这些优化。

TLS 1.3 的设计目标之一是降低建立安全连接的延迟开销:新连接 1-RTT,恢复会话 0-RTT!


密钥交换与前向保密

由于各种历史和商业原因,RSA 握手曾是大多数 TLS 部署中的主导密钥交换机制:客户端生成对称密钥,用服务器公钥加密后发送给服务器,用作已建立会话的对称密钥。服务器则使用其私钥解密发送的对称密钥,密钥交换完成。从此刻起,客户端和服务器使用协商好的对称密钥加密会话。

RSA 握手可以工作,但存在一个关键弱点:同一对公钥-私钥既用于认证服务器,又用于加密发送给服务器的对称会话密钥。因此,如果攻击者获得服务器私钥并窃听交换,就能解密整个会话。更糟糕的是,即使攻击者当前没有私钥访问权限,仍可记录加密会话,待日后获得私钥时解密。

相比之下,Diffie-Hellman 密钥交换(特别是椭圆曲线 Diffie-Hellman 临时密钥交换,[[ECDHE]])允许客户端和服务器协商共享密钥,而无需在握手中显式传输:服务器私钥用于签名和验证握手,但建立的对称密钥从不离开客户端或服务器,即使被动攻击者拥有私钥访问权限也无法拦截。

好奇的读者可以查阅 Wikipedia 上关于 Diffie-Hellman 密钥交换的文章,了解该算法及其特性的详细信息。

最重要的是,Diffie-Hellman 密钥交换可用于降低过去通信会话被泄露的风险:我们可以为每次密钥交换生成新的”临时”(ephemeral)对称密钥,并丢弃之前的密钥。因此,由于临时密钥从不传输,且为每个新会话主动重新协商,最坏情况下攻击者可能入侵客户端或服务器并访问当前和未来会话的会话密钥。然而,仅知道私钥或当前临时密钥,并不能帮助攻击者解密任何先前会话!

结合使用 Diffie-Hellman 密钥交换和临时会话密钥,实现了**“完美前向保密”(PFS)**:长期密钥(如服务器私钥)的泄露不会泄露过去的会话密钥,也不会允许攻击者解密先前记录的会话。这是一个非常理想的特性!

因此,毫不奇怪,RSA 握手现正被积极淘汰:所有主流浏览器都优先选择启用前向保密的加密算法(即依赖 Diffie-Hellman 密钥交换),并且作为额外激励,某些协议优化仅在启用前向保密时才可用——例如通过 TLS False Start 实现 1-RTT 握手。

也就是说,查阅你的服务器文档,了解如何启用和部署前向保密!再次强调,优秀的安全性与性能从来都是相辅相成的。


公钥与对称密钥密码学的性能

公钥密码学仅在 TLS 隧道初始建立时使用:认证证书并执行密钥交换算法。

对称密钥密码学则使用建立的对称密钥加密会话中的所有后续通信。这样做主要是为了提升性能——公钥密码学的计算成本要高得多。为说明差异,如果你的计算机上安装了 OpenSSL,可以运行以下测试:

$> openssl speed ecdh
$> openssl speed aes

注意两个测试的单位不直接可比:椭圆曲线 Diffie-Hellman(ECDH)测试提供不同密钥大小的每秒操作数汇总表,而 AES 性能以每秒字节数衡量。尽管如此,很容易看出 ECDH 操作的计算成本要高得多。

确切的性能数字因使用的硬件、核心数、TLS 版本、服务器配置等因素而有显著差异。不要轻信过时的基准测试!始终在你自己的硬件上运行性能测试,并参考”降低计算开销”获取更多背景信息。


应用层协议协商(ALPN)

两个网络对等方可能希望使用自定义应用协议相互通信。解决这一问题的一种方法是预先确定协议,为其分配知名端口(如 HTTP 用 80 端口,TLS 用 443 端口),并配置所有客户端和服务器使用它。然而,在实践中,这是一个缓慢且不切实际的过程:每个端口分配都需要审批,更糟糕的是,防火墙和其他中间设备通常只允许 80 和 443 端口的流量。

因此,为便于自定义协议的部署,我们必须复用 80 或 443 端口,并使用额外机制来协商应用协议。80 端口保留给 HTTP,HTTP 规范为此提供了专门的 Upgrade 流程。然而,使用 Upgrade 可能增加额外的网络往返延迟,实践中在许多中间设备存在的情况下往往不可靠;详见”代理、中间设备、TLS 与 Web 新协议”。

想了解 HTTP Upgrade 工作流的实践示例,请翻阅至”升级到 HTTP/2”。

解决方案是——你猜对了——使用保留给基于 TLS 的安全 HTTPS 会话的 443 端口。使用端到端加密隧道对中间代理混淆数据,为部署新应用协议提供了快速可靠的方式。然而,我们仍需要另一种机制来协商 TLS 会话内将使用的协议。

应用层协议协商(ALPN),顾名思义,正是满足这一需求的 TLS 扩展。它扩展了 TLS 握手(图 4-2),允许对等方无需额外往返即可协商协议。具体流程如下:

  1. 客户端在 ClientHello 消息中附加新的 ProtocolNameList 字段,包含支持的应用协议列表。

  2. 服务器检查 ProtocolNameList 字段,并在 ServerHello 消息中返回 ProtocolName 字段,指示选定的协议。

服务器只能响应单个协议名称,如果不支持客户端请求的任何协议,可以选择中止连接。因此,一旦 TLS 握手完成,安全隧道即建立,客户端和服务器就应用协议达成一致;双方可以立即开始通过协商的协议交换消息。


服务器名称指示(SNI)

加密 TLS 隧道可以在任意两个 TCP 对等方之间建立:客户端只需知道对方的 IP 地址即可发起连接并执行 TLS 握手。然而,如果服务器希望在同一 IP 地址上托管多个独立站点,每个站点有自己的 TLS 证书,该怎么办?这是个陷阱问题;答案是——不借助额外机制无法实现。

为解决上述问题,**服务器名称指示(SNI)**扩展被引入 TLS 协议,允许客户端在 TLS 握手中指示其尝试连接的主机名。服务器可以检查 ClientHello 消息中发送的 SNI 主机名,选择适当的证书,并完成目标主机的 TLS 握手。


TLS、HTTP 与专用 IP

TLS+SNI 工作流与 HTTP 中的 Host 头广播相同,客户端指示请求站点的主机名:同一 IP 地址可能托管多个不同域名,SNI 和 Host 头都是区分它们所必需的。

不幸的是,某些旧版客户端(如 Windows XP 上运行的大多数 IE 版本、Android 2.2 等)不支持 SNI。因此,如果你需要为这些客户端提供 TLS,可能需要为每个主机分配专用 IP 地址。


TLS 会话恢复

完整 TLS 握手的额外延迟和计算成本,对所有需要安全通信的应用都构成了严重的性能损失。为帮助缓解部分成本,TLS 提供了一种机制,允许在多个连接之间恢复或共享协商好的密钥数据。


会话标识符

首个会话标识符(RFC 5246)恢复机制在 SSL 2.0 中引入,允许服务器在完整 TLS 协商过程中作为 ServerHello 消息的一部分,创建并发送 32 字节的会话标识符。有了会话 ID,客户端和服务器都可以存储先前协商的会话参数——以会话 ID 为键——并在后续会话中复用。

具体而言,客户端可以在 ClientHello 消息中包含会话 ID,向服务器表明它仍记得先前握手的协商密码套件和密钥,并能够复用。如果服务器能在其缓存中找到与广播 ID 关联的会话参数,则可以进行简化握手(图 4-3)。否则,需要完整的会话协商,将生成新的会话 ID。

Figure 4-3. Abbreviated TLS handshake protocol

图 4-3. 简化 TLS 握手协议

利用会话标识符,我们可以消除一次完整往返,以及用于协商共享密钥的公钥密码学开销。这使得安全连接能够快速建立,且不会损失安全性,因为我们复用的是先前协商的会话数据。

会话恢复对 HTTP/1.x 和 HTTP/2 部署都是重要的优化。简化握手消除了完整往返延迟,显著降低了双方的计算成本。

事实上,如果浏览器需要与同一主机建立多个连接(如使用 HTTP/1.x 时),它通常会故意等待首次 TLS 协商完成,再打开到同一服务器的额连接,以便这些连接可以被”恢复”并复用相同的会话参数。如果你曾查看网络追踪并疑惑为什么很少看到多个同主机 TLS 协商同时进行,这就是原因!

然而,会话标识符机制的一个实际限制是服务器需要为每个客户端创建和维护会话缓存。这在服务器端带来若干问题,对于每天看到数万甚至数百万唯一连接的服务器尤为明显:每个开放 TLS 连接消耗的内存、会话 ID 缓存和淘汰策略的需求,以及对拥有多台服务器的流行站点而言,理想情况下应使用共享 TLS 会话缓存以获得最佳性能,这带来了非平凡的部署挑战。

上述问题并非无法解决,许多高流量站点今天仍在成功使用会话标识符。但对于任何多服务器部署,会话标识符都需要仔细思考和系统架构设计,以确保会话缓存的良好运行。


会话票证

为解决服务器端 TLS 会话缓存部署的这一顾虑,“会话票证”(RFC 5077)替代机制被引入,它消除了服务器维护每客户端会话状态的需求。相反,如果客户端表明支持会话票证,服务器可以包含 New Session Ticket 记录,其中包含用仅服务器知晓的密钥加密的所有协商会话数据。

此会话票证随后由客户端存储,可以在后续会话的 ClientHello 消息中的 SessionTicket 扩展中包含。因此,所有会话数据仅存储在客户端,但票证仍是安全的,因为它用仅服务器知晓的密钥加密。

会话标识符和会话票证机制通常分别被称为会话缓存无状态恢复机制。无状态恢复的主要改进是消除了服务器端会话缓存,简化了部署——要求客户端在每次新连接时提供会话票证——直到票证过期。

实践中,在负载均衡服务器组中部署会话票证也需要仔细思考和系统架构设计:所有服务器必须用相同的会话密钥初始化,并需要额外机制来定期安全地在所有服务器间轮换共享密钥。


信任链与证书颁发机构

认证是建立每个 TLS 连接的组成部分。毕竟,与任何对等方(包括攻击者)通过加密隧道对话是可能的,除非我们能确定交谈的主机是我们信任的,否则所有加密工作可能都白费。要理解我们如何验证对等方身份,让我们考察 Alice 和 Bob 之间的简单认证工作流:

  1. Alice 和 Bob 各自生成自己的公钥和私钥。
  2. Alice 和 Bob 各自隐藏自己的私钥。
  3. Alice 与 Bob 分享她的公钥,Bob 也与 Alice 分享他的公钥。
  4. Alice 为 Bob 生成新消息,并用她的私钥签名。
  5. Bob 使用 Alice 的公钥验证提供的消息签名。

信任是上述交换的关键组件。具体而言,公钥加密允许我们使用发送者的公钥验证消息是否用正确的私钥签名,但批准发送者的决定仍基于信任。在上述交换中,Alice 和 Bob 可能在见面时交换了公钥,由于彼此很了解,他们确信交换未被冒充者破坏——也许他们甚至通过先前建立的另一个秘密(物理)握手验证了身份!

接下来,Alice 收到从未谋面的 Charlie 的消息,但 Charlie 声称是 Bob 的朋友。事实上,为证明与 Bob 的友谊,Charlie 让 Bob 用他的私钥签名自己的公钥,并将此签名随消息附上(图 4-4)。这种情况下,Alice 首先检查 Bob 对 Charlie 密钥的签名。她知道 Bob 的公钥,因此能够验证 Bob 确实签名了 Charlie 的密钥。由于她信任 Bob 验证 Charlie 的决定,她接受消息并对 Charlie 的消息执行类似的完整性检查,确保确实来自 Charlie。

Figure 4-4. Chain of trust for Alice, Bob, and Charlie

图 4-4. Alice、Bob 和 Charlie 的信任链

我们刚刚建立的是信任链:Alice 信任 Bob,Bob 信任 Charlie,通过传递信任,Alice 决定信任 Charlie。只要链中无人被攻破,这允许我们构建和扩展可信方列表。

Web 和浏览器中的认证遵循与上述完全相同的过程。这意味着此时你应该会问:你的浏览器信任谁,你使用浏览器时信任谁?这个问题至少有三个答案:

手动指定的证书 每个浏览器和操作系统都提供机制,让你手动导入任何信任的证书。如何获取证书并验证其完整性完全取决于你。

证书颁发机构 证书颁发机构(CA)是受信任的第三方,被证书主体(所有者)和依赖证书的一方共同信任。

浏览器和操作系统 每个操作系统和大多数浏览器都附带知名证书颁发机构列表。因此,你也信任这些软件的供应商来提供和维护可信方列表。

实践中,为每个网站存储和手动验证每个密钥是不切实际的(尽管如果你愿意,可以这样做)。因此,最常见的解决方案是使用**证书颁发机构(CA)**为我们完成这项工作(图 4-5):浏览器指定信任哪些 CA(根 CA),然后由 CA 负责验证其签名的每个站点,审计和验证这些证书未被滥用或泄露。如果任何带有 CA 证书的站点安全被攻破,则该 CA 也有责任吊销被泄露的证书。

Figure 4-5. CA signing of digital certificates

图 4-5. CA 对数字证书的签名

每个浏览器都允许你检查安全连接的信任链(图 4-6),通常通过点击 URL 旁的锁图标访问。

图 4-6. igvita.com 的信任链(Google Chrome)

  • igvita.com 证书由 StartCom Class 1 Primary Intermediate Server 签名。
  • StartCom Class 1 Primary Intermediate Server 证书由 StartCom Certification Authority 签名。
  • StartCom Certification Authority 是被认可的根证书颁发机构。

整个链的信任锚是根证书颁发机构,在上述案例中是 StartCom Certification Authority。每个浏览器都附带预初始化的可信证书颁发机构(“根”)列表,在此案例中,浏览器信任并能够验证 StartCom 根证书。因此,通过浏览器中的传递信任、浏览器厂商和 StartCom 证书颁发机构,我们将信任扩展到目标站点。


证书透明度

每个操作系统厂商和每个浏览器都提供默认信任的证书颁发机构的公开列表。用你喜欢的搜索引擎查找并调查这些列表。实践中,你会发现大多数系统依赖数百个可信证书颁发机构,这也是对该系统的常见抱怨:大量可信 CA 为浏览器中的信任链创造了巨大的攻击面。

好消息是,**证书透明度(Certificate Transparency)**项目正通过提供框架——公开日志——来解决这些缺陷,用于监控和审计所有新证书的颁发。访问项目网站了解更多信息。


证书吊销

偶尔,证书颁发者需要因多种可能原因吊销或使证书失效:证书私钥被泄露、证书颁发机构本身被泄露,或由于各种更良性的原因,如证书被取代、隶属关系变更等。为解决此问题,证书本身包含指示(图 4-7),说明如何检查其是否被吊销。因此,为确保信任链未被破坏,每个对等方在验证证书链时,可以按照嵌入的指示和签名检查每个证书的状态。

图 4-7. igvita.com 的 CRL 和 OCSP 指示(Google Chrome)


证书吊销列表(CRL)

证书吊销列表(CRL) 由 RFC 5280 定义,指定了检查每个证书状态的简单机制:每个证书颁发机构维护并定期发布吊销证书序列号列表。任何尝试验证证书的人都可以下载吊销列表,缓存它,并检查特定序列号是否存在——如果存在,则已被吊销。

此过程简单直接,但存在若干限制:

  • 吊销数量不断增长意味着 CRL 列表只会越来越长,每个客户端必须检索整个序列号列表。
  • 没有证书吊销即时通知机制——如果客户端在证书被吊销前缓存了 CRL,则 CRL 会将已吊销证书视为有效,直到缓存过期。
  • 从 CA 获取最新 CRL 列表可能阻塞证书验证,为 TLS 握手增加显著延迟。
  • CRL 获取可能因各种原因失败,这种情况下浏览器行为未定义。大多数浏览器将此类情况视为”软失败”,允许验证继续——是的,令人担忧。

在线证书状态协议(OCSP)

为解决 CRL 机制的部分限制,在线证书状态协议(OCSP) 由 RFC 2560 引入,提供实时检查证书状态的机制。与包含所有吊销序列号的 CRL 文件不同,OCSP 允许客户端在验证证书链时,直接向 CA 证书数据库查询特定序列号。

因此,OCSP 机制消耗更少带宽,能够提供实时验证。然而,执行实时 OCSP 查询的需求也带来了自身的问题:

  • CA 必须能够处理实时查询的负载。
  • CA 必须确保服务始终在线并在全球可用。
  • 实时 OCSP 请求可能损害客户端隐私,因为 CA 知道客户端正在访问哪些站点。
  • 客户端必须在验证证书链时阻塞等待 OCSP 请求。
  • 浏览器行为再次未定义,通常在网络超时或其他错误导致 OCSP 获取失败时导致”软失败”。

作为真实数据点,Firefox 遥测显示 OCSP 请求超时率高达 15%,成功时约为 TLS 握手增加 350 毫秒延迟。


OCSP 装订

由于上述原因,CRL 和 OCSP 吊销机制都不能提供我们应用所需的安全和性能保证。然而,不必绝望,因为 OCSP 装订(RFC 6066,“证书状态请求”扩展)通过允许服务器执行验证并将其作为 TLS 握手的一部分发送(“装订”)给客户端,解决了我们先前看到的大部分问题:

  • 不是客户端发起 OCSP 请求,而是服务器定期从 CA 检索签名和时间戳的 OCSP 响应。
  • 服务器随后将签名的 OCSP 响应附加(即”装订”)到 TLS 握手中,允许客户端验证证书和 CA 签名的附加 OCSP 吊销记录。

这种角色反转是安全的,因为装订记录由 CA 签名,可被客户端验证,并提供若干重要好处:

  • 客户端不泄露其浏览历史。
  • 客户端不必阻塞查询 OCSP 服务器。
  • 如果服务器选择加入(通过广播 OCSP “Must-Staple” 标志)且验证失败,客户端可以”硬失败”吊销处理。

简而言之,为获得最佳安全和性能保证,务必在服务器上配置和测试 OCSP 装订。


TLS 记录协议

与下面的 IP 或 TCP 层类似,TLS 会话中交换的所有数据也使用明确定义的协议进行分帧(图 4-8)。TLS 记录协议负责识别不同类型的消息(握手、警报或通过”内容类型”字段的数据),以及保护和验证每条消息的完整性。

Figure 4-8. TLS record structure

图 4-8. TLS 记录结构

传递应用数据的典型工作流如下:

  1. 记录协议接收应用数据。
  2. 接收的数据被分块:每条记录最大 2^14 字节,或 16 KB。
  3. 为每条记录添加消息认证码(MAC)或 HMAC。
  4. 每条记录中的数据使用协商的密码加密。

这些步骤完成后,加密数据被传递给 TCP 层传输。在接收端,对等方应用相同的工作流,但顺序相反:使用协商密码解密记录,验证 MAC,提取并将数据传递给上层的应用。

好消息是,上述所有工作都由 TLS 层本身处理,对大多数应用完全透明。然而,记录协议确实引入了一些我们需要了解的重要影响:

  • 最大 TLS 记录大小为 16 KB
  • 每条记录包含 5 字节头部、MAC(SSLv3、TLS 1.0、TLS 1.1 最多 20 字节,TLS 1.2 最多 32 字节),以及如果使用分组密码则包含填充。
  • 要解密和验证记录,必须拥有完整记录。

为你的应用选择正确的记录大小(如果有能力这样做)可能是重要的优化。小记录因记录分帧和 MAC 验证产生更大的 CPU 和字节开销,而大记录必须由 TCP 层交付和重组,然后才能被 TLS 层处理并交付给你的应用——跳至”优化 TLS 记录大小”了解完整详情。


TLS 优化实践

通过 TLS 部署应用需要一些额外工作,既在应用内(如将资源迁移到 HTTPS 以避免混合内容),也在负责通过 TLS 交付应用数据的基础设施配置上。调优良好的部署能在观察到的性能、用户体验和总体运营成本上产生巨大的积极差异。让我们深入探讨。


降低计算开销

建立和维护加密通道为双方引入了额外的计算成本。具体而言,首先是 TLS 握手期间使用的非对称(公钥)加密(见”TLS 握手”)。然后,一旦建立共享密钥,它就被用作对称密钥来加密所有 TLS 记录。

如我们先前所述,与对称密钥密码学相比,公钥密码学的计算成本要高得多,在 Web 早期通常需要额外硬件来执行”SSL 卸载”。好消息是,这已不再必要,曾经需要专用硬件的工作现在可以直接在 CPU 上完成。Facebook、Twitter、Google 等大型组织,为数十亿用户提供 TLS,都在软件和商用硬件上执行所有必要的 TLS 协商和计算。

“2010 年 1 月,Gmail 默认全面切换到 HTTPS。此前它作为选项引入,但现在所有用户都使用 HTTPS 来保护浏览器与 Google 之间的电子邮件,始终如此。为此,我们无需部署额外机器,也无需特殊硬件。在我们的生产前端机器上,SSL/TLS 仅占 CPU 负载的不到 1%,每连接内存不到 10 KB,网络开销不到 2%。许多人认为 SSL/TLS 占用大量 CPU 时间,我们希望上述数字(首次公开)有助于消除这一误解。

如果你现在停止阅读,只需记住一件事:SSL/TLS 不再计算昂贵。”

—— Adam Langley(Google)

“我们已使用硬件和软件负载均衡器大规模部署 TLS。我们发现,运行在商用 CPU 上的现代软件 TLS 实现速度足够快,能够处理繁重的 HTTPS 流量负载,无需诉诸专用加密硬件。我们使用运行在商用硬件上的软件提供所有 HTTPS 流量。”

—— Doug Beaver(Facebook)

“椭圆曲线 Diffie-Hellman(ECDHE)仅比 RSA 稍贵一点,对于同等安全级别……在实际部署中,我们发现启用和优先使用 ECDHE 密码套件实际上导致的 CPU 使用增加可以忽略不计。HTTP 长连接和会话恢复意味着大多数请求不需要完整握手,因此握手操作不会主导我们的 CPU 使用。我们发现 Twitter 75% 的客户端请求通过使用 ECDHE 建立的连接发送。剩余 25% 主要由尚不支持 ECDHE 密码套件的旧客户端组成。”

—— Jacob Hoffman-Andrews(Twitter)

要在自己的部署中获得最佳结果,充分利用 TLS 会话恢复——部署、测量并优化其成功率。消除在每次握手时执行昂贵公钥密码学操作的需求,将显著降低 TLS 的计算和延迟成本;没有理由在不必要的工作上花费 CPU 周期。

说到优化 CPU 周期,确保服务器使用最新版本的 TLS 库!除了安全改进,你通常还会看到性能收益。安全与性能相辅相成。


实现 1-RTT TLS 握手

未经优化的 TLS 部署可能轻易增加多次额外往返,为用户引入显著延迟——例如多 RTT 握手、缓慢且无效的证书吊销检查、需要多次往返的大 TLS 记录等。不要成为那样的站点,你可以做得更好。

调优良好的 TLS 部署应最多为协商 TLS 连接增加一次额外往返,无论新连接还是恢复连接,并避免所有其他延迟陷阱:配置会话恢复,启用前向保密以启用 TLS False Start。

为获得最佳端到端性能,务必审计应用使用的自有和第三方服务及服务器,包括 CDN 提供商。如需快速了解流行服务器和 CDN 的成绩单,请查看 istlsfastyet.com。


优化连接复用

最小化建立新 TCP+TLS 连接的延迟和计算开销的最佳方式是优化连接复用。这样做可以将设置成本分摊到多个请求上,为用户提供更快的体验。

验证服务器和代理配置是否设置为允许长连接,并审计连接超时设置。许多流行服务器设置激进的连接超时(如某些 Apache 版本默认 5 秒超时),强制大量不必要的重新协商。为获得最佳结果,使用日志和分析确定最佳超时值。


利用就近接入

正如我们在”延迟与带宽入门”中讨论的,我们可能无法让数据包传输得更快,但可以让它们传输更短的距离。通过将”边缘”服务器放置在离用户更近的位置(图 4-9),我们可以显著减少往返时间和 TCP 与 TLS 握手的总成本。

Figure 4-9. Early termination of client connections

图 4-9. 客户端连接的就近接入

实现这一点的简单方法是利用**内容分发网络(CDN)**的服务,其在全球维护边缘服务器池,或部署自己的边缘节点。通过允许用户与附近服务器终止连接,而非跨越海洋和大陆链路连接到你的源站,客户端获得”就近接入”的好处,往返时间更短。这一技术对静态和动态内容都同样有用且重要:静态内容可以由边缘服务器缓存和提供,而动态请求可以通过边缘到源站的已建立连接路由。


未缓存源站获取

使用 CDN 或代理服务器获取可能需要按用户定制或包含其他私有数据、因此不是边缘全局可缓存资源的资源的技术,通常称为**“未缓存源站获取”**。

虽然 CDN 在数据缓存于全球地理分布服务器时效果最佳,但未缓存源站获取仍提供非常重要的优化:客户端连接与附近服务器终止,可以显著降低握手延迟成本。CDN 或你自己的代理服务器可以维护到源站服务器的”热连接池”来中继数据,让你能够快速响应客户端。

事实上,作为额外的优化层,某些 CDN 提供商会在连接两端使用附近服务器!客户端连接在附近的 CDN 节点终止,然后该节点将请求中继到靠近源站的 CDN 节点,再路由到源站。CDN 网络内的跳数允许流量通过优化的 CDN 骨干网路由,有助于进一步降低客户端与源站服务器间的延迟。


配置会话缓存与无状态恢复

将连接终止在离用户更近的位置是一项在所有情况下都有助于降低用户延迟的优化,但再次强调,没有比不发送的比特更快的比特——发送更少的比特。启用 TLS 会话缓存和无状态恢复允许我们为回头客消除一次完整往返延迟,并降低计算开销。

TLS 会话缓存依赖的会话标识符在 SSL 2.0 中引入,得到大多数客户端和服务器的广泛支持。然而,如果你在服务器上配置 TLS,不要假设默认会启用会话支持。事实上,大多数服务器默认关闭会话支持更为常见——但你知道得更清楚!仔细检查并验证服务器、代理和 CDN 配置:

  • 多进程或多工作线程的服务器应使用共享会话缓存。
  • 共享会话缓存的大小应根据流量水平调整。
  • 应提供会话超时周期。
  • 在多服务器设置中,将相同客户端 IP 或相同 TLS 会话 ID 路由到相同服务器是提供良好会话缓存利用率的一种方式。
  • 当”粘性”负载均衡不可行时,应在不同服务器间使用共享缓存以提供良好的会话缓存利用率,并需要建立安全机制来共享和更新解密所提供会话票证的密钥。
  • 检查并监控 TLS 会话缓存统计以获得最佳性能。

实践中,为获得最佳结果,你应同时配置会话缓存和会话票证机制。这些机制协同工作,为新客户端和旧客户端提供最佳覆盖。


启用 TLS False Start

会话恢复提供两个重要好处:为回头客消除额外握手往返,并通过允许复用先前协商的会话参数来降低握手计算成本。然而,它无助于访客首次与服务器通信,或先前会话已过期的情况。

为获得两全其美——新访客和回头客都只需一次往返握手,以及回头客的计算节省——我们可以使用 TLS False Start,这是一种可选协议扩展,允许发送方在握手仅部分完成时发送应用数据(图 4-10)。

Figure 4-10. TLS handshake with False Start

图 4-10. 带 False Start 的 TLS 握手

False Start 不修改 TLS 握手协议本身,仅影响应用数据可以发送的协议时序。直观地说,一旦客户端发送了 ClientKeyExchange 记录,它就已知加密密钥,可以开始传输应用数据——握手的其余部分用于确认无人篡改握手记录,可以并行完成。因此,False Start 使我们能够将 TLS 握手保持为一次往返,无论执行完整握手还是简化握手。


部署 TLS False Start

由于 False Start 仅修改握手协议的时序,不需要更新 TLS 协议本身,可以单方面实现——即,客户端可以简单地更早开始传输加密应用数据。理论上是这样。

实践中,尽管 TLS False Start 应与所有现有 TLS 客户端和服务器向后兼容,但为所有 TLS 连接默认启用它被证明是有问题的,由于某些实现不佳的服务器。因此,所有现代浏览器都能使用 TLS False Start,但仅在服务器满足特定条件时才会这样做:

  • Chrome 和 Firefox 要求服务器握手中存在 ALPN 协议广播,且服务器选择的密码套件启用前向保密。
  • Safari 要求服务器选择的密码套件启用前向保密。

要在所有浏览器中启用 TLS False Start,服务器应通过 ALPN 扩展广播支持的协议列表——例如,“h2, http/1.1”——并配置为支持和优先使用启用前向保密的密码套件。


优化 TLS 记录大小

通过 TLS 交付的所有应用数据都在记录协议内传输(图 4-8)。每条记录的最大大小为 16 KB,根据选择的密码,每条记录将为头部、MAC 和可选填充增加 20 到 40 字节的开销。如果记录随后能放入单个 TCP 数据包,则还需添加 IP 和 TCP 开销:IP 头部 20 字节,无选项的 TCP 头部 20 字节。因此,每条记录可能有 60 到 100 字节的开销。对于线路上典型的 1,500 字节最大传输单元(MTU)大小,这种数据包结构转化为至少 6% 的分帧开销。

记录越小,分帧开销越高。然而,简单地将记录大小增加到最大值(16 KB)不一定是好主意。如果记录跨越多个 TCP 数据包,则 TLS 层必须等待所有 TCP 数据包到达才能解密数据(图 4-11)。如果任何 TCP 数据包丢失、重排序或因拥塞控制被节流,则 TLS 记录的各个片段必须缓冲后才能解码,导致额外延迟。实践中,这些延迟可能为偏好流式消费数据的浏览器创造显著瓶颈。

图 4-11. WireShark 捕获的 11,211 字节 TLS 记录拆分为 8 个 TCP 段

小记录产生开销,大记录产生延迟,没有”最佳”记录大小的单一值。相反,对于由浏览器消费的 Web 应用,最佳策略是基于 TCP 连接状态动态调整记录大小:

  • 当连接较新且 TCP 拥塞窗口较低,或连接已空闲一段时间(见慢启动重启)时,每个 TCP 数据包应恰好携带一个 TLS 记录,且 TLS 记录应占据 TCP 分配的最大段大小(MSS)。
  • 当连接拥塞窗口较大且传输大流(如流媒体视频)时,TLS 记录大小可以增加到跨越多个 TCP 数据包(最多 16KB),以减少客户端和服务器的分帧和 CPU 开销。

如果 TCP 连接已空闲,即使服务器上禁用了慢启动重启,发送新突发数据时最佳策略仍是减小记录大小:自上次传输以来条件可能已改变,我们的目标是最小化因丢包、重排序和重传导致的应用层缓冲概率。

使用动态策略为交互式流量提供最佳性能:小记录大小消除不必要的缓冲延迟,改善首次{HTML 字节、…、视频帧}时间,大记录大小通过最小化长流式传输的 TLS 开销来优化吞吐量。

为确定每种状态下的最佳记录大小,让我们从新的或空闲 TCP 连接的初始情况开始,希望避免 TLS 记录跨越多个 TCP 数据包:

  • 为 IPv4 分帧开销分配 20 字节,IPv6 分配 40 字节。
  • 为 TCP 分帧开销分配 20 字节。
  • 为 TCP 选项开销(时间戳、SACK)分配 40 字节。

假设常见的 1,500 字节起始 MTU,这为 IPv4 上的 TLS 记录留下 1,420 字节,为 IPv6 留下 1,400 字节。为面向未来,使用 IPv6 大小,即每条 TLS 记录 1,400 字节,如果 MTU 更低则按需调整。

接下来,关于记录大小应何时增加,以及连接空闲后何时重置的决定,可以基于预配置阈值:在传输 X KB 数据后将记录大小增加到最多 16 KB,在空闲 Y 毫秒后重置记录大小。

通常,配置 TLS 记录大小不是我们在应用层能控制的。相反,这通常是你的 TLS 服务器的设置,有时是编译时常数。查看服务器文档了解如何配置这些值的详细信息。


Google 的 TLS 优化实践

截至 2014 年初,Google 的服务器对前 1 MB 数据使用适合单个 TCP 段的小 TLS 记录,之后将记录大小增加到 16 KB 以优化吞吐量,空闲一秒后将记录大小重置回单个段——如此循环。

类似地,如果你的服务器处理大量 TLS 连接,最小化每连接内存使用可能是关键优化。默认情况下,OpenSSL 等流行库会为每连接分配最多 50 KB 内存,但与记录大小一样,值得查看文档或源代码了解如何调整此值。Google 的服务器将 OpenSSL 缓冲区减少到约 5 KB。


优化证书链

验证信任链需要浏览器遍历链,从站点证书开始,递归验证父证书直到到达可信根。因此,提供的链包含所有中间证书至关重要。如果遗漏任何证书,浏览器将被迫暂停验证过程并获取缺失证书,在此过程中添加额外的 DNS 查找、TCP 握手和 HTTP 请求。

浏览器如何知道从哪里获取缺失证书?每个子证书通常包含父证书的 URL。如果 URL 被省略且所需证书未包含,则验证将失败。

相反,不要包含不必要的证书,如证书链中的可信根——它们增加不必要的字节。回想一下,服务器证书链作为 TLS 握手的一部分发送,这很可能发生在处于慢启动算法早期阶段的新 TCP 连接上。如果证书链大小超过 TCP 初始拥塞窗口,我们将无意中为 TLS 握手添加额外往返:证书长度将溢出拥塞窗口,导致服务器停止并等待客户端 ACK 后才继续。

实践中,证书链的大小和深度在将初始拥塞窗口初始化为 4 个 TCP 段的旧 TCP 栈上是更大的顾虑和问题。对于较新的部署,初始拥塞窗口已提高到 10 个 TCP 段,对大多数证书链应该足够。

也就是说,验证服务器使用最新的 TCP 栈和设置,并优化和减小证书链大小。发送更少的字节总是好的、值得的优化


配置 OCSP 装订

每个新 TLS 连接都需要浏览器验证发送的证书链的签名。然而,还有一个关键步骤我们不能忘记:浏览器还需要验证证书未被吊销。

为验证证书状态,浏览器可以使用几种方法之一:证书吊销列表(CRL)、在线证书状态协议(OCSP)或 OCSP 装订。每种方法都有自己的限制,但 OCSP 装订 提供了迄今为止最佳的安全和性能保证——详见前面章节。务必配置服务器将(装订)来自 CA 的 OCSP 响应包含到提供的证书链中。这样做允许浏览器执行吊销检查而无需任何额外网络往返,并提供改进的安全保证。

  • OCSP 响应大小可能从 400 到 4,000 字节不等。将此响应装订到证书链会增加其大小——密切关注证书链的总大小,确保其不溢出新建 TCP 连接的初始拥塞窗口。
  • 当前 OCSP 装订实现仅允许包含单个 OCSP 响应,这意味着如果需要验证链中其他证书,浏览器可能不得不回退到另一种吊销机制——缩短证书链长度。未来,OCSP 多装订应该解决这一特定问题。

大多数流行服务器支持 OCSP 装订。查看相关文档了解支持和配置说明。类似地,如果使用或决定使用 CDN,检查其 TLS 栈是否支持并配置为使用 OCSP 装订。


启用 HTTP 严格传输安全(HSTS)

**HTTP 严格传输安全(HSTS)**是重要的安全策略机制,允许源站通过简单 HTTP 头向兼容浏览器声明访问规则——例如,"Strict-Transport-Security: max-age=31536000"。具体而言,它指示用户代理执行以下规则:

  • 到源站的所有请求都应通过 HTTPS 发送。这包括导航和所有其他同源子资源请求——例如,如果用户输入不带 https 前缀的 URL,用户代理应自动转换为 https 请求;如果页面包含非 https 资源引用,用户代理应自动转换为请求 https 版本。
  • 如果无法建立安全连接,不允许用户绕过警告请求 HTTP 版本——即,该源站是仅 HTTPS 的。
  • max-age 以秒为单位指定广告策略的生命周期(例如,max-age=31536000 等于广告策略的 365 天生命周期)。
  • includeSubdomains 指示策略应应用于当前源站的所有子域。

HSTS 将源站转换为仅 HTTPS 目标,并帮助保护应用免受各种被动和主动网络攻击。作为额外奖励,它还通过消除 HTTP 到 HTTPS 重定向的需要提供了不错的性能优化:客户端在发送前自动将所有请求重写为安全源!

在启用 HSTS 前务必彻底测试 TLS 部署。一旦策略被客户端缓存,无法协商 TLS 连接将导致硬失败——即,用户将看到浏览器错误页面,不允许继续。这一行为是明确且必要的设计选择,以防止网络攻击者诱骗客户端在没有 HTTPS 的情况下访问你的站点。


HSTS 预加载列表

HSTS 机制使得到源站的首次请求免受主动攻击保护——例如,恶意方可能降级客户端请求并阻止其注册 HSTS 策略。为解决此问题,大多数浏览器提供单独的 “HSTS 预加载列表” 机制,允许源站请求包含在浏览器附带的 HSTS 启用站点列表中。

一旦你对 HTTPS 部署有信心,考虑通过 hstspreload.org 提交站点到 HSTS 预加载列表。


升级站点内容至 HTTPS

为获得最佳安全和性能保证,站点实际使用 HTTPS 获取所有资源至关重要。否则,我们会遇到若干问题,损害两者,或更糟,破坏站点:

  • 混合”活动”内容(如通过 HTTP 交付的脚本和样式表)将被浏览器阻止,可能破坏站点功能。
  • 混合”被动”内容(如通过 HTTP 交付的图片、视频、音频等)会被获取,但允许攻击者观察和推断用户活动,并通过需要额外连接和握手来降低性能。

审计内容并更新资源和链接,包括第三方内容,以使用 HTTPS。**内容安全策略(CSP)**机制在此可以提供很大帮助,既用于识别 HTTPS 违规,也用于执行所需策略。

Content-Security-Policy: upgrade-insecure-requests
Content-Security-Policy-Report-Only: default-src https:; report-uri https://example.com/reporting/endpoint
  • 告诉浏览器将所有(自有和第三方)请求升级到 HTTPS。
  • 告诉浏览器向指定端点报告任何非 HTTPS 违规。

CSP 提供高度可配置的机制来控制允许使用哪些资产,以及如何及从何处获取。利用这些能力来保护你的站点和用户。


性能检查清单

作为应用开发者,我们被屏蔽在 TLS 协议的大部分复杂性之外——客户端和服务器代表我们完成大部分艰苦工作。然而,如我们在本章所见,这并不意味着我们可以忽视通过 TLS 交付应用的性能方面。调优服务器以启用关键 TLS 优化,并配置应用以启用客户端利用这些功能,会带来高回报:更快的握手、降低的延迟、更好的安全保证等。

考虑到这一点,以下是议程上的简短检查清单:

  • 从 TCP 获得最佳性能;参见”TCP 优化”。
  • 将 TLS 库升级到最新版本,并(重新)构建服务器以使用它们。
  • 启用并配置会话缓存和无状态恢复。
  • 监控会话缓存命中率并相应调整配置。
  • 配置前向保密密码以启用 TLS False Start。
  • 将 TLS 会话终止在离用户更近的位置,以最小化往返延迟。
  • 使用动态 TLS 记录大小优化延迟和吞吐量。
  • 审计并优化证书链大小。
  • 配置 OCSP 装订。
  • 配置 HSTS。
  • 配置 CSP 策略。
  • 启用 HTTP/2 和 HTTP/3;参见”HTTP/2”和”HTTP/3”。

测试与验证

最后,为验证和测试配置,你可以使用在线服务,如 SSL Labs 的 SSL 服务器测试,扫描公共服务器的常见配置和安全缺陷。此外,你应该熟悉 openssl 命令行接口,它将帮助你在本地检查服务器的整个握手和配置。

$> openssl s_client -state -CAfile root.ca.crt -connect example.com:443

CONNECTED(00000003)
SSL_connect:before/connect initialization
SSL_connect:SSLv2/v3 write client hello A
SSL_connect:SSLv3 read server hello A
depth=2 C = US, O = Internet Security Research Group, CN = ISRG Root X1
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = R3
verify return:1
depth=0 CN = example.com
verify return:1
SSL_connect:SSLv3 read server certificate A
SSL_connect:SSLv3 read server done A
SSL_connect:SSLv3 write client key exchange A
SSL_connect:SSLv3 write change cipher spec A
SSL_connect:SSLv3 write finished A
SSL_connect:SSLv3 flush data
SSL_connect:SSLv3 read finished A
---
Certificate chain
 0 s:CN = example.com
   i:C = US, O = Let's Encrypt, CN = R3
 1 s:C = US, O = Let's Encrypt, CN = R3
   i:C = US, O = Internet Security Research Group, CN = ISRG Root X1
---
Server certificate
-----BEGIN CERTIFICATE-----
... snip ...
---
No client certificate CA names sent
---
SSL handshake has read 3001 bytes and written 444 bytes
---
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
SSL-Session:
    Protocol  : TLSv1.3
    Cipher    : TLS_AES_256_GCM_SHA384
    Session-ID: 269349C84A4702EFA7...
    Session-ID-ctx:
    Resumption PSK: 1F5F5F33D50BE6228A...
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    Start Time: 1707643200
    Timeout   : 7200 (sec)
    Verify return code: 0 (ok)
    Extended master secret: no
    Max Early Data: 0
---
  • 客户端完成接收证书链的验证。
  • 接收的证书链(两张证书)。
  • 接收证书链的大小。
  • 为状态化 TLS 恢复颁发的会话标识符(TLS 1.3 中使用 PSK 恢复)。

在上述示例中,我们连接到 example.com 的默认 TLS 端口(443),并执行 TLS 握手。由于 s_client 不对已知根证书做假设,我们手动指定根证书路径,在本例中是示例域的 Let’s Encrypt 证书链。你的浏览器已有常见根证书,因此能够验证链,但 s_client 不做此类假设。尝试省略根证书,你会在日志中看到验证错误。

检查证书链显示服务器发送了两张证书,总计 3,001 字节。我们还可以看到协商的 TLS 会话变量——选择的协议、密码、密钥——并且可以看到服务器为当前会话颁发了会话标识符(在 TLS 1.3 中显示为 PSK),将来可能恢复。


« 返回目录

版权所有 © 2024 基于 Ilya Grigorik 原作修订。采用 CC BY-NC-ND 4.0 许可。