背景
在 ssllabs 中可以测试域名的 SSL 安全等级:
影响这个测试等级的最主要因素就是密钥套件,在接入阿里云 CDN 的所有域名中,绝大多数域名评级都是 A,但是有少数域名为了兼容一些老浏览器或者客户端,需要支持比如 RC4 这样的加密算法,这样就导致评级为 B,但用户体验更重要,这就需要为这些对密钥套件有特殊需求的域名特殊配置密钥套件。
另外,当我们调试 https 时,比如抓包分析数据包时,发现应用数据都是加密的,无法分析 HTTP 协议的问题,但是如果我们有私钥,那就有办法可以通过 wireshark 来配置解密抓包信息。但是这个有个前提条件是密钥交换算法是 RSA,如果使用 ECDHE 即使有私钥也是无法解密抓包文件的,这就是所谓的 PFS(Perfect Forward Secrecy)。所以当我们想调试 HTTPS 里面的明文信息时,临时调整一下服务器的密钥套件列表中密钥交换算法仅包含 RSA 即可,调试完成之后再恢复成 PFS 密钥套件,这样就很方便的调试 HTTPS 了。
原理
密钥套件也称加密套件、密码套件,是 SSL 中最核心的算法套件,称之为套件是因为其中包含了密钥交换算法、签名算法、加密算法和摘要算法,由两个字节表示。以下是 TLS 密钥套件的语法:
在 Client Hello 中有密钥套件列表,表示客户端所支持的密钥套件:
服务端只能从 Client Hello 的密钥套件列表中选择一个做为本次会话的密钥套件:
那服务端又是怎么选择这个密钥套件呢?这就涉及到密钥套件选择算法了,在 openssl 里面密钥套件的选择与几个因素有关:
- 服务端配置的密钥套件列表。
- 客户端支持的密钥套件列表。
- 服务端密钥套件列表优先还是客户端密钥套件列表优先。
- 已选择的 SSL 协议版本。
- 证书所支持的算法。
如果服务端密钥套件列表优先,则挨个从服务端配置的密钥套件列表中选择一个密钥套件是证书所支持的、已选择的 SSL 协议版本所支持的,并且在客户端密钥套件列表的密钥套件,如果是客户端密钥套件列表优化也是类似,一般都是配置服务端密钥套件优先。
其中最关键的就是服务端配置的密钥套件列表,下面先来看看 openssl 中密钥套件的表达方式:
openssl ciphers 'ALL' -V
上面的协议版本就是该密钥套件支持的协议最低版本,比如 ECDHE-RSA-AES128-GCM-SHA256 的协议版本是 TLSv1.2,那如果握手时选择了 TLSv1.1、TLSv1.0、SSLv3 这些版本则不能使用该密钥套件。
密钥套件之间用4个分隔符之一分开——冒号、分号、逗号或者空格,一般使用冒号。如下密钥套件列表中包含了所有特殊字符:
EECDH+AESGCM:+EECDH+AES256:ALL:-RSA+3DES:!DH:!PSK:!KRB5:!MD5:!RC4:!aNULL:!EXP:!LOW:!SSLV2:!NULL
其说明如下:
EECDH+AESGCM:同时包含EECDH和AESGCM算法的密钥套件,+号相当于『与』操作。
密钥套件前面+号:将该算法移到算法列表的末尾。这个选项不会添加任何新的算法,它只是紧紧的移动匹配的已经存在的算法。
密钥套件前面-号:从算法列表中删除该算法。但是可以通过后面的选项将一个或所有的算法可以被再次添加。
密钥套件前面!号:从算法列表中删除该算法。删除了的算法将不会再出现。
通过 openssl ciphers 指令列出来的顺序就是服务端的密钥套件列表顺序:
$openssl ciphers 'EECDH+AESGCM:+EECDH+AES256:ALL:-RSA+3DES:!DH:!PSK:!KRB5:!MD5:!RC4:!aNULL:!EXP:!LOW:!SSLV2:!NULL' -V
0xC0,0x2F - ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(128) Mac=AEAD
0xC0,0x2B - ECDHE-ECDSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AESGCM(128) Mac=AEAD
0xC0,0x30 - ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(256) Mac=AEAD
0xC0,0x2C - ECDHE-ECDSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AESGCM(256) Mac=AEAD
0xC0,0x28 - ECDHE-RSA-AES256-SHA384 TLSv1.2 Kx=ECDH Au=RSA Enc=AES(256) Mac=SHA384
0xC0,0x24 - ECDHE-ECDSA-AES256-SHA384 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AES(256) Mac=SHA384
0xC0,0x14 - ECDHE-RSA-AES256-SHA SSLv3 Kx=ECDH Au=RSA Enc=AES(256) Mac=SHA1
0xC0,0x0A - ECDHE-ECDSA-AES256-SHA SSLv3 Kx=ECDH Au=ECDSA Enc=AES(256) Mac=SHA1
0xC0,0x32 - ECDH-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH/RSA Au=ECDH Enc=AESGCM(256) Mac=AEAD
0xC0,0x2E - ECDH-ECDSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH/ECDSA Au=ECDH Enc=AESGCM(256) Mac=AEAD
0xC0,0x2A - ECDH-RSA-AES256-SHA384 TLSv1.2 Kx=ECDH/RSA Au=ECDH Enc=AES(256) Mac=SHA384
0xC0,0x26 - ECDH-ECDSA-AES256-SHA384 TLSv1.2 Kx=ECDH/ECDSA Au=ECDH Enc=AES(256) Mac=SHA384
0xC0,0x0F - ECDH-RSA-AES256-SHA SSLv3 Kx=ECDH/RSA Au=ECDH Enc=AES(256) Mac=SHA1
0xC0,0x05 - ECDH-ECDSA-AES256-SHA SSLv3 Kx=ECDH/ECDSA Au=ECDH Enc=AES(256) Mac=SHA1
0x00,0x9D - AES256-GCM-SHA384 TLSv1.2 Kx=RSA Au=RSA Enc=AESGCM(256) Mac=AEAD
0x00,0x3D - AES256-SHA256 TLSv1.2 Kx=RSA Au=RSA Enc=AES(256) Mac=SHA256
0x00,0x35 - AES256-SHA SSLv3 Kx=RSA Au=RSA Enc=AES(256) Mac=SHA1
0x00,0x84 - CAMELLIA256-SHA SSLv3 Kx=RSA Au=RSA Enc=Camellia(256) Mac=SHA1
0xC0,0x27 - ECDHE-RSA-AES128-SHA256 TLSv1.2 Kx=ECDH Au=RSA Enc=AES(128) Mac=SHA256
0xC0,0x23 - ECDHE-ECDSA-AES128-SHA256 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AES(128) Mac=SHA256
0xC0,0x13 - ECDHE-RSA-AES128-SHA SSLv3 Kx=ECDH Au=RSA Enc=AES(128) Mac=SHA1
0xC0,0x09 - ECDHE-ECDSA-AES128-SHA SSLv3 Kx=ECDH Au=ECDSA Enc=AES(128) Mac=SHA1
0xC0,0x12 - ECDHE-RSA-DES-CBC3-SHA SSLv3 Kx=ECDH Au=RSA Enc=3DES(168) Mac=SHA1
0xC0,0x08 - ECDHE-ECDSA-DES-CBC3-SHA SSLv3 Kx=ECDH Au=ECDSA Enc=3DES(168) Mac=SHA1
0xC0,0x31 - ECDH-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH/RSA Au=ECDH Enc=AESGCM(128) Mac=AEAD
0xC0,0x2D - ECDH-ECDSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH/ECDSA Au=ECDH Enc=AESGCM(128) Mac=AEAD
0xC0,0x29 - ECDH-RSA-AES128-SHA256 TLSv1.2 Kx=ECDH/RSA Au=ECDH Enc=AES(128) Mac=SHA256
0xC0,0x25 - ECDH-ECDSA-AES128-SHA256 TLSv1.2 Kx=ECDH/ECDSA Au=ECDH Enc=AES(128) Mac=SHA256
0xC0,0x0E - ECDH-RSA-AES128-SHA SSLv3 Kx=ECDH/RSA Au=ECDH Enc=AES(128) Mac=SHA1
0xC0,0x04 - ECDH-ECDSA-AES128-SHA SSLv3 Kx=ECDH/ECDSA Au=ECDH Enc=AES(128) Mac=SHA1
0xC0,0x0D - ECDH-RSA-DES-CBC3-SHA SSLv3 Kx=ECDH/RSA Au=ECDH Enc=3DES(168) Mac=SHA1
0xC0,0x03 - ECDH-ECDSA-DES-CBC3-SHA SSLv3 Kx=ECDH/ECDSA Au=ECDH Enc=3DES(168) Mac=SHA1
0x00,0x9C - AES128-GCM-SHA256 TLSv1.2 Kx=RSA Au=RSA Enc=AESGCM(128) Mac=AEAD
0x00,0x3C - AES128-SHA256 TLSv1.2 Kx=RSA Au=RSA Enc=AES(128) Mac=SHA256
0x00,0x2F - AES128-SHA SSLv3 Kx=RSA Au=RSA Enc=AES(128) Mac=SHA1
0x00,0x96 - SEED-SHA SSLv3 Kx=RSA Au=RSA Enc=SEED(128) Mac=SHA1
0x00,0x41 - CAMELLIA128-SHA SSLv3 Kx=RSA Au=RSA Enc=Camellia(128) Mac=SHA1
0x00,0x07 - IDEA-CBC-SHA SSLv3 Kx=RSA Au=RSA Enc=IDEA(128) Mac=SHA1
实现
在 Tengine 中有两个指令跟密钥套件有关:
ssl_ciphers EECDH+AESGCM:+EECDH+AES256:ALL:-RSA+3DES:!DH:!PSK:!KRB5:!MD5:!RC4:!aNULL:!EXP:!LOW:!SSLV2:!NULL;
ssl_prefer_server_ciphers on;
其中,ssl_ciphers 是配置服务器支持的密钥套件列表,ssl_prefer_server_ciphers 为 on 则指定从服务器支持的密钥套件列表中的优先顺序来选择密钥套件,为 off 则表示从客户端支持的密钥套件列表中的优先顺序来选择密钥套件。
ssl_prefer_server_ciphers 配置为 on 是必须的,因为如果不设置为 on,那意味着优先从客户端支持的密钥套件列表中选择加密套件,这样的话可能导致 SSL 的性能比较低,而且可能导致被攻击。
另外,我们知道 ssl_ciphers 指令是静态配置,想要修改服务器支持的密钥套件列表的话,必须 reload Tengine,对于单台服务器或者少量服务器这么操作是可以的,但是对于阿里云 CDN 上万台机器的集群来 reload Tengine 的话,这么做就不现实了,所以我们也实现了 ssl 密钥套件列表的动态配置。
如下图,可以在阿里云 CDN 管理后台管理系统上根据域名来调整密钥套件。(由于密钥套件的修改是比较底层的配置,非专业人员配置容易出问题,所以没有开放到 CDN 用户控制台。)