Go 1.12 TLS 1.3 简单测试

《TLS 1.3 当前(2018.10)支持与部署之现状》中,我们提到 Go 将在 1.12 中支持 TLS 1.3. 作为一个 Gopher, 终于在前几天盼来了 golang 1.12 的发布。

但是从 release 日志看,本次对选择性的部分支持 TLS 1.3, 且默认处于关闭状态:

Go 1.12 adds opt-in support for TLS 1.3 in the crypto/tls package as specified by RFC 8446. It can be enabled by adding the value tls13=1 to the GODEBUG environment variable. It will be enabled by default in Go 1.13.

如果要开启 TLS 1.3, 需要设置环境变量:GODEBUG=tls13=1.

本次发布的 TLS 1.3 的 cipher suite 无法配置,也不支持 0-RTT 模式:

TLS 1.3 cipher suites are not configurable. All supported cipher suites are safe, and if PreferServerCipherSuites is set in Config the preference order is based on the available hardware.

Early data (also called “0-RTT mode”) is not currently supported as a client or server.

要知道,没有 0-RTT 的 TLS 1.3 是没有灵魂的,对本次版本的失望那是肯定的。但是依然在第一时间升级了 Go 版本,简单测试了一下国内网络环境下 TLS 1.3 与 1.2 的握手延迟。如果对 TLS 1.3 握手延迟还不太熟悉,可以参见拙文《TLS1.3/QUIC 是怎样做到 0-RTT 的》 以及 TLS 1.3 Handshake Protocol.

测试代码

需要说明的是,这个测试是不严谨,里面没有考虑 cipher suite 以及 early data 的差异。测试结果定性意义大于定量意义。

测试结果

测试使用的目标服务器地址是 blog.cloudflare.com:443, 我的网络环境下,ping 延迟为 226 ms (1-RTT).

从结果看,有如下结论:

  1. TLS 1.3 平均比 TLS 1.2 建立连接的延迟低约 1-RTT, 跟理论分析是吻合的。但是,
  2. 从 max 项可以看出,部分时候国内到目标服务器网络不稳定带来的波动比 TLS 本身协议优化的 RTT 大的多。因此,稳定高延迟的网络链路有时候比低延迟高抖动的网络更有实际意义。
  3. TLS 建立在 TCP 基础上,TCP 的握手延迟在 TLS 层面是优化不掉的,或者说不是 TLS 的管辖范围,因此,在允许的情况下,尽量复用连接。

TLS 1.3 当前(2018.10)支持与部署之现状

今年8月10日,历经三年有余,TLS 1.3 最终版本终于得以发布——RFC 8446. 关于 RFC 的详细介绍可以进一步阅读 A Detailed Look at RFC 8446 (a.k.a. TLS 1.3). TLS 1.3 因为其在握手延迟以及安全性上的改进 (可以参考拙文《TLS1.3/QUIC 是怎样做到 0-RTT 的》),毫不夸张的说,这是一件将深刻而长远影响互联网发展的技术里程碑。那么将 TLS 1.3 尽快平滑应用到线上环境无疑是一件势在必行的事情了。

在我日常的工作中,对于 TLS 1.3 的支持和部署主要关注两个层面: 编程语言(Go, Java)、 API gateway (Nginx) 和浏览器. 下面分别介绍一下这三个层面 TLS 1.3 的支持部署现状。(因为 RFC 8446 已经发布,因此本文中说的支持如无特殊说明,都是指对最终版本 RFC 8446 的支持。)

编程语言对 TLS 1.3 的支持

Go 方面,官方在 TLS 1.3 draft 阶段一直没有跟进。因此,有一个关于对 TLS 1.3 支持的 issue 从 2015 年 open 至今都没有关闭:crypto/tls: add support for TLS 1.3. 其实 Go 发布版本和改进标准库的效率还是挺高的,对于 TLS 1.3 上的“不作为”更多是因为 Go 在兼容性上的承诺导致其并适合在最终版发布前实现互不兼容的 draft 方案。

而 Go 1.11 的发布时间(2018.08.24)与 RFC 8446 的发布时间比较接近,没有足够时间实现并发布该特性。从 golang-dev 小组讨论 Re: crypto/tls and TLS 1.3 看,由于 1.11 没有实现 TLS 1.3 ,那么 1.12 实现 TLS 1.3 基本是板上钉钉的事了:

The key scheduling fact is that the Go 1.11 feature freeze is just a week away, so we decided that it would be too rushed to merge the 1.3 patches for it. I definitely aim to have TLS 1.3 in Go 1.12.

根据惯例, Go 1.12 的发布时间将会是 2019.02~03. 如果期间你实在想用 Go 编程测试 TLS 1.3, 可以尝试使用 CloudFlare 的 tls-tris 库。根据 Go net/http 标准库维护者 Brad Fitzpatrick 的消息,这个库将会被合并到标准库作为 Go 官方 TLS 1.3 的实现。因此,如果你不得不用这个库干一些生成环境的活也大可放心,即使日后升级 Go 1.12, 接口兼容性还是有保证的。

Java 方面,由于 Java 11 出生时间好(2018.09.25), 因此是出生就支持 TLS 1.3 RFC 8446, 具体可以参见 JEP 332: Transport Layer Security (TLS) 1.3. Java 11 是 LTS 版本,因此,如果有条件升级到 11, 推荐使用 Java 11 实现的 TLS 1.3 以及配套的 HttpClient;如果生产环境暂无法升级 Java 版本,推荐使用 OkHttp. 关于 Java Http Client 选型可以参见Java HTTP 组件库选型看这篇就够了

Nginx 对 TLS 1.3 的支持

准确讲应该是 Nginx 所使用 SSL lib 对 TLS 1.3 的支持。在这方面,Boring SSL 跟进速度飞快,在 RFC 发布后第4天实现了对最终版本的支持。OpenSSL 虽然很早就跟进了 draft 的实现,但是对最终版本的支持需要 1.1.1-pre9 及以后的版本:

The OpenSSL git master branch (and the 1.1.1-pre9 beta version) contain our development TLSv1.3 code which is based on the final version of RFC8446 and can be used for testing purposes (i.e. it is not for production use). Earlier beta versions of OpenSSL 1.1.1 implemented draft versions of the standard. Those versions contained the macro TLS1_3_VERSION_DRAFT_TXT in the tls1.h header file which identified the specific draft version that was implemented. This macro has been removed from 1.1.1-pre9 and the current master branch.

TLSv1.3 is enabled by default in the latest development versions (there is no need to explicitly enable it). To disable it at compile time you must use the “no-tls1_3” option to “config” or “Configure”.

Although the latest 1.1.1 versions support the final standard version, other applications that support TLSv1.3 may still be using older draft versions. This is a common source of interoperability problems. If two peers supporting different TLSv1.3 draft versions attempt to communicate then they will fall back to TLSv1.2.

而第一个 OpenSSL 1.1.1 release 是在 2018.09.11, 因此如果你跟我一样是 OpenSSL 的死忠粉,当前阶段 Nginx 支持 TSL 1.3 的最佳方式是 Nginx 1.15.5 + OpenSSL 1.1.1. 而这种脏活、苦活、累活当然是交给 Docker 解决了:从源代码编译 nginx docker 镜像开启 TLS 1.3,项目地址可以参见 docker-nginx.

配置 Nginx 支持 TLS 1.3 需要注意一点:默认情况下 Nginx 因为安全原因,没有开启 TLS 1.3 0-RTT,可以通过添加 ssl_early_data on; 指令开启 0-RTT. 完整配置可以参考 nginx.conf.

浏览器对 TLS 1.3 的支持

当前阶段,Chrome 69 和 Firefox 62 都只支持到 draft 28, 而 draft 28 与最终版本是不兼容的。因此,要测试体验 TLS 1.3 final 需要使用 Chrome Beta 测试版。然后在 chrome://flags/#tls13-variant 开启 TLS 1.3 final:

扩展阅读

QUIC 存在 UDP 反射 DDoS 攻击漏洞吗?

今年年初,360信息安全部发布了一篇关于利用 UDP 反射 DDoS 的分析报告:Memcache UDP反射放大攻击技术分析。报告一出,引起了业界的普遍关注。根据文中所述,光是Qrator Labs 在 medium.com 上 批露的一次DDoS攻击看,其攻击流量峰值达到 480Gbps。而360信息安全团队本身也监测和确认有更大的攻击已经实际发生,只是未被公开报道。

而就在这个这个事件纰漏没多久,我把博客升级为支持基于 UDP 的 QUIC 协议来改善小站的访问体验:本站开启支持 QUIC 的方法与配置。本着小站没几人访问的蜜汁自信,当时也没太纠结 QUIC 是否也存在 UDP 反射漏洞。前几天,看到著名博主,阮一峰同学网站被 DDoS 攻击,心里咯噔一下:出来混迟早是要还的,还是填坑为安吧。

什么是 UDP 反射 DDoS 攻击

简单讲,就是攻击者利用IP网络不做真实源地址检查的“设计缺陷“,向提供基于 UDP 服务的服务器发送伪造源地址(一般为被攻击者的主机IP)的 UDP 报文请求,使得这些 UDP 报文的响应数据都会发送给被攻击者主机,这种攻击我们称之为 UDP 反射 DDoS 攻击。

之所以要通过被利用的服务器反射流量到被攻击的服务器,是因为被利用的服务器一般存在流量放大效应。即一个伪造IP的 UDP 请求发送到到被利用服务器后,被利用服务器会发送比请求更多的数据到被攻击者服务器。

被利用服务器输出流量与输入流量的比值我们称之为放大系数。这个系数与被利用服务器所提供的 UDP 服务有关。之前提到的利用 Memcache 漏洞的 DRDoS 攻击,可以获得稳定的 60000 倍放大系数。而我们日常使用的 DNS 则可以轻松的获得 50 倍的放大系数。

由放大系数反推,我们可以知道,如果一个 UDP 服务被利用以后,放大系数小于等于1的话,则不存在利用价值,因为这个时候,只从带宽流量方面考虑的话,还不如直接利用攻击主机对被攻击服务器进行攻击效率高。

QUIC 存在 UDP 反射攻击漏洞吗

按照蛤乎惯例,照顾猴急的同学,先给结论:可以。

QUIC 主要通过以下机制来解决该问题:

  1. 对于首次发起建立 QUIC 连接的客户端,服务端要求其初始化的 hello 数据包必须完全填充。这个包在 IPv4 下一般是 1370 字节,在 IPv6 下是 1350 字节。在 QUIC 协议中,服务器和客户端数据交互的基本单位是就是 UDP 数据包,而一个全填充的数据包已经达到了数据包大小的上限,因此服务器的响应数据包一定是小于等于这个 hello 数据包的。显然,放大系数小于等于1. 因此,新连接的建立没有反射利用的价值。
  2. 建立 QUIC 连接后,客户端发出的数据包就不会全填充了。这个时候,如果被 UDP 反射利用,放大系数是大于1的。因此, QUIC 引入了源地址token (source address token):在成功建立 QUIC 连接后,服务器会用发放一个源地址token给客户端,并要求客户端在后续的数据包中带上这个token;服务器只对源地址token有效的数据包进行处理。源地址token中一般包含客户端的源地址和服务器的时间。因此这是一个客户端证明其IP所有权的凭证。
  3. 由于源地址token可能会被网络中的攻击者嗅探收集,因此 QUIC 设计了一套源地址token的过期和刷新机制。另一方面,每次客户端发送的数据包如果都带上源地址token的话,不仅客户端流量大,服务器验证token也是额外的开销,使得协议延迟变高。因此 QUIC 协议允许客户端按照一个动态策略选择是否在数据包中夹带源地址token:服务器端收集和统计源地址的数据包,当统计到源地址数据包交互响应异常的数量超过阈值时,要求该源地址的客户端必须夹带源地址token, 对于无法提供合法源地址的token的请求进行 reject 处理。

扩展阅读