Nginx禁用TLS1.0与1.1的尝试

作为一个开发者,自己的博客应该是一个追求现代化的网站(主要还是因为没什么 PV),所以我作出了一个愉快的的决定:禁用 TLS1.0 和 TLS1.1。

SSL/TLS 相关

什么是 SSL/TLS

传输层安全性协议(英语:Transport Layer Security,缩写作 TLS),及其前身安全套接层(Secure Sockets Layer,缩写作 SSL)是一种安全协议,目的是为互联网通信提供安全及数据完整性保障。网景公司(Netscape)在 1994 年推出首版网页浏览器,网景导航者时,推出 HTTPS 协议,以 SSL 进行加密,这是 SSL 的起源。IETF 将 SSL 进行标准化,1999 年公布第一版 TLS 标准文件。随后又公布 RFC 5246 (2008 年 8 月)与 RFC 6176 (2011 年 3 月)。在浏览器、电子邮件、即时通信、VoIP、网络传真等应用程序中,广泛支持这个协议。主要的网站,如 Google、Facebook 等也以这个协议来创建安全连线,发送数据。当前已成为互联网上保密通信的工业标准。

SSL/TLS 的版本

协议 发布时间 状态
SSL 1.0 未公布 未公布
SSL 2.0 1995 年 已于 2011 年弃用
SSL 3.0 1996 年 已于 2015 年弃用
TLS 1.0 1999 年 计划于 2020 年弃用
TLS 1.1 2006 年 计划于 2020 年弃用
TLS 1.2 2008 年
TLS 1.3 2018 年

为什么要禁用 TLS1.0、TLS1.1

SSL 由于以往发现的漏洞,已经被证实不安全。而 TLS1.0 与 SSL3.0 的区别实际上并不太多,并且 TLS1.0 可以通过某些方式被强制降级为 SSL3.0。

由此,支付卡行业安全标准委员会(PCI SSC)强制取消了支付卡行业对 TLS 1.0 的支持,同时强烈建议取消对 TLS 1.1 的支持。

苹果、谷歌、微软、Mozilla 也发表了声明,将于 2020 年初放弃对 TLS 1.1 和 TLS 1.0 的支持。其原因是这两个版本使用的是过时的算法和加密系统,经发现,这些算法和系统是十分脆弱的,比如 SHA-1 和 MD5。它们也缺乏像完美的前向保密性这样的现代特征,并且容易受到降级攻击的影响。

在 Nginx 上禁用 TLS1.0、TLS1.1

排查自己的网站

实际我想要禁用 TLS1.0 和 TLS1.1 的一大原因是,在SSL LabsMy SSL做的 SSL 检测时,发现我的某个博客启用了 TLS1.0 及配套的加密套件,导致评分直接掉到了 B 级。

由于本博客是托管在 Github Page 上的,启用的即 Github Page 的 TLS 配置,故不存在此问题。

开启TLS1.0将导致PCI DSS不合规

该博客原先支持了TLS1.0和TLS1.1

尝试在 Nginx 禁用 TLS1.0 和 TLS1.1

首先登陆自己的主机,查看 Nginx 版本

1
2
3
4
5
6
# nginx -V

nginx version: nginx/1.15.7
built by gcc 4.8.5 20150623 (Red Hat 4.8.5-36) (GCC)
built with OpenSSL 1.0.2k-fips 26 Jan 2017
TLS SNI support enabled

这个版本的 Nginx 默认支持的 TLS 版本,即为上述检测出来的 TLS1.0、TLS1.1、TLS1.2。

那么,如果要禁用 TLS1.0 及 TLS1.1 的话,只需要在 Nginx 配置文件中,在指定的域名上指定支持的 TLS 的版本即可。

我所指定的如下

1
2
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'TLS13+AESGCM+AES128:EECDH+AES128';

该配置方式仅作为演示用途,对于推荐的配置方式,可以在Cipherli.st获取。

保存 Nginx 配置后,重启 Nginx,通过 openssl 指令查询对应的 TLS1.0 版本是否仍旧有效:

1
2
3
4
5
6
7
8
9
# openssl s_client -connect mydomain.com:443 -tls1

CONNECTED(00000003)
depth=2 O = Digital Signature Trust Co., CN = DST Root CA X3
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
verify return:1
depth=0 CN = anotherdomain.com
verify return:1

然而发现仍旧返回了验证通过,这是怎么回事呢?

SNI 协议与 Nginx 的 default server

仔细看了上边命令的返回,我发现实际返回的域名和我测试所请求的域名并不一致。这是怎么回事呢?

原因是我在这个服务器上部署的域名和网站不止一个。在 HTTP 协议中,请求的域名作为主机头(Host)放在 HTTP Header 中,而在 SSL 握手的时候,SSL 拿不到 Host 的信息,这时服务端通常返回的是配置中第一个可用的证书。

这个问题是早期 SSL2.0 时就引入的一个问题,由此 SNI 协议就诞生了。SNI 协议允许客户端在发起 SSL 握手请求时,就提交请求的 Host 信息,使服务器能够切换到正确的域并返回相应的证书。

SNI(Server Name Indication)是为了解决一个服务器使用多个域名和证书的 SSL/TLS 扩展。一句话简述它的工作原理就是,在连接到服务器建立 SSL 连接之前先发送要访问站点的域名(Hostname),这样服务器根据这个域名返回一个合适的证书。目前,大多数操作系统和浏览器都已经很好地支持 SNI 扩展,OpenSSL 0.9.8 已经内置这一功能。

从上述 Nginx 命令中可以看出,我的 Nginx 是支持 SNI 协议的,而在上面没有返回正确证书的原因是,我的 openssl 命令并没有带上 SNI 协议查询。如果要带上 SNI 协议来查询的话,命令应该增加-servername 参数

1
2
3
4
5
6
7
8
9
10
11
# openssl s_client -servername mydomain.com -connect mydomain.com:443 -tls1

CONNECTED(00000003)
write:errno=104
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 0 bytes and written 0 bytes
---

可以看出,命令返回了错误信息,即 TLS1.0 确实被禁用了。

我们再回头看之前的错误中,还有一个问题。在客户端不支持 SNI 的情况下,服务端默认返回的是配置中第一个可用的证书。那么如果我们希望服务端返回特定的证书的话应该怎么办?

Nginx 有一个 default server 的概念。也就是说当出现不支持 SNI 协议的客户端时,将使用 default server 的配置进行验证。当没有配置 default server 的时候,Nginx 将使用找到的第一个配置文件中的配置(经过测试,应该是按照字母顺序排序的)。

我们需要在 Nginx 配置文件中,将指定的域名中加入 default_server,这样的话,在客户端不支持 SNI 时,Nginx 就会将该域名作为默认的配置。

1
2
3
4
server {
listen 443 ssl default_server;
...
}

为了以防万一,我把我所有的域名均禁用了 TLS1.0 和 TLS1.1。

再次通过 My SSL 检测后,评级上升到了 A。

评级上升为A级

显示我的域名已不再支持TLS1.0与TLS1.1

原本我还计划添加对 TLS1.3 的支持,但由于目前主流浏览器和客户端对 TLS1.3 的支持并不够完善,并且似乎我需要更新一些服务器配置,需要继续探究一下相关的知识,所以就没有在本次文章中写出。也许会在下一篇文章写吧!(Flag 已经立起来了)

参考资料