SSL/TLS
SSL(Secure Sockets Layer,安全套接层)协议最初由 Netscape(网景公司)在 1994 年设计并开发,为了给 HTTP 提供一个安全的传输层。
到 1999 年,因为 SSL 应用广泛,已经成为 Internet 上的事实标准。所以 IETF(Internet Engineering Task Force,互联网工程任务组)在 SSL 的基础上将其标准化为 TLS(Transport Layer Security,传输层安全协议)协议。
目前,我们最常见的是 TLS v1.2 版本,而最新的 v1.3(2018 年,RFC8446)版本有望成为有史以来最安全,但也最复杂的 TLS 协议。
TLS 1.2
TLS 协议是 CIAA 网络安全模型的具体实现,基于混合加密方案和 PKI/CA 数字证书技术制定了一套安全通信协议标准,所以理解 TLS 协议之前需要先对 CIAA 安全模型有一定的认识。
TLS 主要由 2 个部分组成:
TLS 记录数据(TLS Record):是一种数据结构,用于将应用层协议的数据分割成多个 TLS Records。数据结构的概览如下图所示:
TLS 握手协议(TLS Handshake):是一种协议,用于在 TLS 通信的初始阶段进行身份验证和协商安全参数。协议处理流程如下图所示:
协议交互抓包示例如下图所示:
client_hello
当 TCP 连接建立后,TLS 握手的第一步由 Client 发起,发送 ClientHello Msg 到 Server。
Client Hello Msg 包含以下内容:
Client 支持的 TLS 版本。
Client 支持的 Cipher Suites(加密套件候选列表)。
Compression Methods(压缩算法候选列表)。
Extensions(扩展字段)。
Client Random(随机数),用于后续的对称密钥协商。
可选的 Session ID,用于支持 TLS 会话恢复。如果提供了,那么 Server 会复用对应的握手信息,避免短时间内重复握手。
server_hello + server_certificate + sever_hello_done
Server 收到了 ClientHello Msg 后,比对 Server 支持的 TLS 版本和 Cipher Suites,然会回复 ServerHello Msg,以此来完成 Cipher Suites 协商阶段。
Server Hello Msg 包含以下内容:
Server 所能支持的最高 TLS 版本。
Server 选择使用的 Cipher Suites(加密套件)。例如:Nginx 的 ssl_ciphers HIGH!MD5; 配置项。
Server 选择使用的 Compression Method(压缩算法)。
Server Random(随机数),用于后续的对称密钥协商。
可选的 Session ID,用于支持 TLS 会话恢复。如果提供了,则 Client 会复用当前握手的信息,避免短时间内重复握手。
可选的 Session ID 会话恢复,是指在一次完整协商的连接断开时,Client 和 Server 都会将 TLS Session 协商的安全参数保留一段时间,用于后续的会话恢复,起到类似 Cache 的功能。希望恢复会话的 Client 需要将相应的 Session ID 放入 Client Hello Msg 发出。如果 Server 愿意恢复会话,则将相同的 Session ID 放入 Server Hello Msg 返回。
Server Certificate Msg 包含以下内容:
Server 的 CA 证书,该证书由 CA 签发,是一个由 CA 背书的 Server “公钥“。
在双向安全认证场景中,Client 也需要提供它的 CA 证书,还需要包含 Client Certificate Request(客户端证书请求)。
Server Hello Done Msg:向 Client 发送 Server Hello Done 表示 Hello 协商阶段完成。
Certificate authentication
Client 收到 Server Hello Msg 后,会对收到的 Server CA 证书进行合法性验证。只有通过验证才会进行后续通信,否则根据错误情况不同做出提示和操作,合法性验证的内容包括:
Trusted Certificate Path(证书链的可信性)。
Revocation(证书是否已经吊销):有 2 种方式:离线 CRL 验证、在线 OCSP 验证。不同的 Client 会采用不同的方式。
Expiry Date(有效期):证书是否在有效时间范围内。
Domain(域名):核查证书域名是否与当前的访问域名匹配。
client_key_exchange + change_cipher_spec + encrypted_handshake_message
Client 完成了 Server CA 证书的合法性验证之后,就可以从 Server CA 证书中得到了 Server Public Key,继而开始非对称加密行为。具体采用的非对称加密算法由 Server 选择的 Cipher Suites 决定,例如:ECDHE。
Client Key Exchange Msg:内含了 Client 本地运算生成的 Pre-Master 随机数,并用 Server Public Key 加密,然后发送给 Server,再由 Server Private Key 解密。
此时,Client 就获得了混合加密方案所需要的全部通信密钥信息,包括:
在 C/S 协商阶段获得的 2 个明文随机数 random_C 和 random_S。
Client 自己生成的加密 Pre-Master 随机数。
用于对称加密的 Master Secret 对称密钥,Client 通过 Fuc(random_C, random_S, Pre-Master) 计算得到。使用 3 个随机数来计算,一方面为了防止 “random_C” 被中间人猜出,另一方也增加 Master Secret 的随机性。
用于非对称加密的 Server Public Key。
Change Cipher Spec Msg:Client 通知 Server 后续的通信都采用协商的通信密钥和加密算法进行加密通信。
Encrypted Handshake Msg / Finished Msg:TLS v1.2 采用了 MAC(Message authentication code,消息认证码)来确保 Handshake 流程未被篡改。具体的行为是:对 Client 之前已经发送过的所有 Msgs,再次使用协商好的 Master Secret 对称密钥进行 HASH 计算得到数字摘要,最后发送给 Server 用于最后的安全握手验证。
change_cipher_spec + encrypted_handshake_message
Server 收到 Client Key Exchange Msg 后,使用 Server Private Key 解密得到 Client 的 Pre-Master 随机数,并基于之前交换的两个明文随机数 random_C 和 random_S,同样可以计算出协商 Master Secret 对称密钥。
Server 收到 Encrypted Handshake Msg 后,同样通过协商好的加密算法进行解密,然后再对 Server 收到的所有 Msgs 使用同样的 Master Secret 对称密钥进行一次数字摘要计算。
最后通过对比数据签名,来最终验证数据的完整性。如果失败,则触发致命的 Alert 异常:Bad Record MAC(Message authentication code,消息认证码),表示安全通道建立过程中有恶意篡改行为。
Change Cipher Spec Msg:验证通过之后,Server 同样发送该消息以告知 Client 后续的通信都采用协商的通信密钥与算法进行加密通信。
Encrypted Handshake Msg / Finished Msg:Server 也会以同样的方式计算出数字签名并发送给 Client。
至此,Client 和 Server 双方都拥有了合法的 Master Secret 对成密钥,接下来进入到业务数据的对称加密安全传输阶段。整个过程通常需要几百毫秒。
TLS 1.3
相对于 TLS v1.2 而言,v1.3 是迄今为止最大的一次改动,主要的改进目的如下:
更强的安全性:进一步加密握手流程、改善跨协议攻击的弹性、删除不安全的加密算法。
更快的访问速度:减少握手等待时间。
引入的新特性如下:
引入了新的 PSK 密钥协商机制。
支持 0-RTT 数据传输,在建立连接时节省了往返时间。
废弃了过时的 3DES、RC4、AES-CBC 等加密组件,以及 SHA1、MD5 等哈希算法。
对 Server Hello Msg 之后的所有握手消息采取了加密操作,可见明文大大减少。
不再允许对加密报文进行压缩、不再允许双方发起重协商。
DSA 证书不再允许使用。
目前最新的 Chrome 和 Firefox 都已支持 TLS 1.3,但需要手动开启。下面是各大浏览器的 TLS 1.3 支持情况:
更强的安全性
加密了整个 TLS Handshake 握手流程
TLS v1.2 中的 Handshake 流程并没有实现完全加密,协商加密算法类型和随机数阶段、以及握手结束(Encrypted Handshake Msg / Finished Msg)都是明文的,通过对称 MAC(Message authentication code,消息认证码)来确保握手未被篡改。
这种疏忽导致了许多备受瞩目的安全漏洞(e.g. FREAK、LogJam etc.),所以在 TLS v1.3 中,会对整个握手进行非对称加密。
以最简单的 FREAK 攻击为例,它利用了以下 2 个漏洞:
许多浏览器和服务器仍然支持 20 世纪 90 年代的弱密码(称为 Export 密码)。
TLS v1.2 用于协商使用哪种加密算法的握手部分没有加密。
使得 FREAK 中间人可以篡改 Client 和 Server 最终选用的 Supported ciphers(加密套件),让双方都降级了加密强度(HIGH => LOW => EXPORT)。然后中间人就可以暴力破解 Encrypted Handshake Message 得到 3 个随机数,并计算出 Master Secret 对称密钥信息 ,最终就可以伪造了彼此的 Finished Message,即 MAC 值。如下图所示。
在 TLS v1.3 中,这种类型的安全降级攻击是不可能的,因为整个握手流程都进行了加密,同时 v1.3 还删除了那些不安全的加密算法,包括:
• RSA 密钥传输:不支持前向安全性。
• CBC 模式密码:易受 BEAST 和 Lucky 13 攻击。
• RC4 流密码:在 HTTPS 中使用并不安全。
• SHA-1 哈希函数:建议以 SHA-2 取而代之。
• 任意 Diffie-Hellman 组:CVE-2016-0701 漏洞。
• 输出密码:易受 FREAK 和 LogJam 攻击。
• 等等。
使用支持向前保密的临时 Diffie-Hellman 替代 RSA 加密算法
RSA 非对称加密算法是由 Rivest,Shamir 和 Adelman 在 1977 年发现的,一直都被视为是密码学领域的重大成就之一。但现在看来,RSA 存在不满足前向保密(Forward Secret)的严重缺陷。即:如果中间人记录存储了加密对话数据,后面假如某一天中间人通过 Heartbleed(心脏出血)之类的技术窃取了 Server 的 RSA Private Key,那么中间人依然可以将对话解密。
因此,TLS 1.3 移除了 RSA,而仅采用了临时 Diffie-Hellman 作为唯一的秘钥交换机制。
临时 Diffie-Hellman 由 Diffie 和 Hellman 在 1976 年发明,要求 Client 和 Server 都创建一对非对称密钥,并且都交换彼此的 Public Key,一旦收到了对方的 Public Key,那么就会与自己的 Private Key 进行组合,最后以相同的 Pre-Master Secret 值作为结尾。
所谓 “临时”,指的是在每个 TLS 会话中,协商密钥所使用的 Pre-Master Secret 参数都是临时生成的,可以实现每个会话的密钥唯一。因此,即使在以后的时间里,攻击者获得了以前的临时密钥,也无法利用这些密钥来破解之前或之后的会话。即使一个密钥被泄漏,也不会影响其他会话的安全性。
更快的访问速度
在 Web 领域,传输延迟(Transmission Latency)是重要的性能指标之一,低延迟意味着更流畅的页面加载以及更快的 API 响应速度。而一个完整的 HTTPS 连接的建立大概需要以下 4 步:
DNS 查询
TCP 握手(1 RTT)
TLS v1.2 握手 (2 RTT)
建立 HTTP 连接(1 RTT)
可见在 TLS v1.2 中,新建一个完整的 HTTPS 连接最少需要 4 个 RTT(Round-Trip Time 往返时延),而重连则可以通过 TLS 的会话恢复机制节省 1 个 RTT。
• TLS v1.2 新建连接握手流程:
• TLS v1.2 会话恢复流程(指定了 Hello Msg 的 Session ID):
在 TLS v1.3 中,由于仅支持向前保密的临时 Diffie-Hellman 对称加密算法,所以 Client 可以在一条 Msg 中就完成 Diffie-Hellman 密钥共享,即:只需要一次往返( 1-RTT )就可以完成握手。
• TLS v1.3 新建连接握手流程:
另外,TLS v1.3 在会话恢复时,Client 会将 Server 发送过来的 Session Ticket 进行计算,组成一个新的 PSK (PreSharedKey,预共享密钥)。Client 将 PSK 缓存在本地。会话恢复时,在 Client Hello Msg 带上 PSK 扩展,同时通过之前 Client 发送的 Finished Msg 计算出 Resumption Secret(恢复密钥)。通过该密钥加密数据发送给 Server,然后 Server 就会从 Session Ticket 中算出 PSK,使用它来解密刚才发过来的加密数据。至此完成了该 0-RTT 会话恢复的过程。
• TLS v1.3 0-RTT 会话恢复流程:
升级 OpenSSL 1.1.1 支持 TLS 1.3
并非所有的 Client 和 Server 都支持相同版本的 TLS,因此大多数 Server 都会同时支持多个版本,并且进行协商。TLS 的版本协商非常简单。Client 会通知 Server 它支持的协议的最新版本,Server 则会回复支持的协议版本,如果存在交集则协商成功。否则,连接失败。虽然版本协商的过程很简单,但事实证明,很多连接场景并未能正确地实现这一功能,从而导致安全事故。
OpenSSL 最新的 1.1.1 版本提供了 TLS 1.3 的支持,而且和 1.1.0 版本完全兼容。在特定的 Linux 发行版中,可能需要手动安装。
以 CentOS7 为例,检查是否开启了 TLS 1.3:
$ openssl s_client --help | grep tls1_3
如果没有,则需要手动安装:
下载 OpenSSL 1.1.1 版本
$ cd /opt
$ wget https://github.com/openssl/openssl/archive/OpenSSL_1_1_1-stable.zip
$ unzip OpenSSL_1_1_1-stable.zip
编译安装
$ ./config enable-tls1_3 --prefix=/usr/local/openssl
$ make && make install
配置
$ mv /usr/bin/openssl /usr/bin/openssl.old
$ mv /usr/lib64/openssl /usr/lib64/openssl.old
$ mv /usr/lib64/libssl.so /usr/lib64/libssl.so.old
$ ln -s /usr/local/openssl/bin/openssl /usr/bin/openssl
$ ln -s /usr/local/openssl/include/openssl /usr/include/openssl
$ ln -s /usr/local/openssl/lib/libssl.so /usr/lib64/libssl.so
$ echo "/usr/local/openssl/lib" >> /etc/ld.so.conf
$ ldconfig -v
查看版本并验证
$ openssl version
$ openssl s_client --help | grep tls1_3
HTTPS
HTTPS 就是 HTTP 与 TLS 的组合,本质为 HTTP over SSL/TLS。在以往的文章中,我们已经分别介绍了 HTTP 和 TLS 协议,所以在这里主要关注 HTTPS 的 2 种安全认证方式,并梳理 HTTPS 连接建立流程。
测试网站是否开启了 TLS 1.3:
$ git clone --depth 1 https://github.com/drwetter/testssl.sh.git
$ cd testssl.sh
$ ./testssl.sh -p
Testing protocols via sockets except NPN+ALPN
SSLv2 not offered (OK)
SSLv3 not offered (OK)
TLS 1 offered
TLS 1.1 offered
TLS 1.2 offered (OK)
TLS 1.3 offered (OK): draft 28, draft 27, draft 26
NPN/SPDY h2, spdy/3.1, http/1.1 (advertised)
ALPN/HTTP2 h2, spdy/3.1, http/1.1 (offered)
HTTPS 单向认证
Client 向 Server 发送 TLS 协议版本号、加密算法种类、随机数等信息。
Server 给 Client 返回 TLS 协议版本号、加密算法种类、随机数等信息,同时也返回 Server 的 CA 证书。
Client 使用 Server 返回的信息验证 CA 证书的合法性,包括:
– 证书是否过期。
– 签发证书的 CA 机构是否可靠。
– 安装的 CA 公钥证书是否能正确解开 CA 证书并返回数字签名。
– CA 证书上的 DN(域名)是否和 Server 的实际域名相匹配。
– 验证通过后,将继续进行通信,否则,终止通信。
Client 向 Server 发送自己所能支持的对称加密算法,供 Server 进行选择。
Server 端在 Client 提供的加密方案中选择加密程度最高的方式。
Server 将选择好的加密方案通过明文方式返回给 Client。
Client 接收到 Server 返回的加密方式后,使用该加密方式生成产生 Pre-Master 随机码,使用 Server Public Key 进行加密,并发送至 Server。
Server 收到 Client 发来的加密信息后,使用自己的 Private Key 进行解密,获取 Pre-Master 随机码,并计算出 Master 对称密钥。
在接下来的会话中,Server 和 Client 将会使用 Master 对称密钥进行对称加密,保证通信过程中信息的安全。
HTTPS 双向认证
双向认证和单向认证类似,主要是额外增加了 Server 对 Client 的认证。如下图红色部分。
TLS 的 SNI 扩展
SNI
早期的 SSLv2 根据经典的 PKI 标准进行设计,它默认认为一台 HTTP Server(或者说一个 IP 地址)只会提供一个服务(只有一个 Domain Name),所以在 SSL 握手时,Client 无需指明具体的 Domain Name,Server 就会把默认的 CA 证书返回。
然而在 Apache 等 HTTP Server 中应用了 VirtualHosts 之后,就出现了一个 IP 会对应多个 Domain Name 的情况。为了支持 VirtualHosts,HTTP/1.1 Header 协议增加了 Host 字段,相应的 TLS 也需要类似的手段,否则会出现 “公共名称不匹配错误(Unable to communicate securely with peer: requested domain name does not match the server's certificate.)”。即:虽然 Client 到达了 HTTP Server 的正确 IP 地址,但由于 CA 证书上的 Domain Name 与 Web 的 Domain Name 不匹配导致无法建立安全连接。
为了解决这个问题,TLS 在 v1.0 版本中增加了 SNI(Server Name Indication,服务器名称指示,RFC 4366)扩展,它包含在 TLS Hello 握手流程中,以确保 Client 能够指定要访问的 Domain Name被托管在相同的 HTTP Server 中,通过基于 Hostname 的 VirtualHosts 对外提供服务。此时。,如果 TLS 的 SNI 扩展指定为 https://www.example.com,那么 HTTP Server 就会返回 https://www.example.com 的 CA 证书,继而建立正确的安全连接。
• 发出 SNI:
SSLKEYLOGFILE=ssl_log.txt curl
--cacert ~/ca_01.pem
--resolve www.app1.com172.18.22.68
-X GET "https://www.app1.com:443/"
-H 'Content-type: application/json'
-H 'Accept: application/json'
-H 'host: www.app1.com'
• 抓包示例:
• Client Hello 的 SNI Extensions:
ESNI(加密的 SNI)
SNI 作为 TLS Client Hello Msg 的 扩展字段,这意味着在 TLS v1.2 中,SNI 是明文的。也就是说,任何监视 Client 和 Server 之间连接的攻击者都可以读取到 SNI 信息,并以此了解到 Client 正在与哪个 Domain Name 建立 HTTPS 连接,即便攻击者无法解密进一步的通信内容,但攻击者仍然可以利用 SNI 信息,例如:建立一个钓鱼网站来欺骗用户。
由此,就提出了 ESNI(加密的 SNI),通过加密 client_hello 消息的 SNI 部分(仅此部分),来保护 SNI 的私密性,确保攻击者无法监视到 SNI 明文信息。另外,ESNI 的加密密钥必须以其他方式进行传输。
具体而言,HTTP Server 会在其 DNS 记录中添加一个用于 ESNI 的 Public Key。这样,当 Client 查找到正确的 HTTP Server IP 地址时,同时也能得到对应的 Public Key。
浏览器向 DNS Server 发送 Domain Name 查询,以查询 HTTP Server 的 IP 地址。
DNS 响应 HTTP Server IP 地址以及 ESNI Public Key。
浏览器向指定的 IP 地址发送 TLS client_hello 消息,并使用 ESNI Public Key 对 SNI 部分进行加密。
HTTP Server 根据 SNI 指示返回指定的 CA 证书。
TLS 握手继续进行。
但需要注意的是,即表如此 ESNI 也并非是绝对安全的,因为常规的 DNS 通信未加密,存在 “地址簿伪装“ 攻击风险。即使安装了 ESNI,攻击者仍然可以查看用户正在查询的 DNS 记录,并确定他们正在访问哪些网站。
所以更进一步的,还可能需要安全的 DNS 方案,常见的有:
• 基于 TLS 的 DNS;
• 基于 HTTPS 的 DNS;
• DNSSEC;
• 等等
升级 curl 支持 HTTP2 与 TLS 1.3
编译安装
安装编译环境:
$ yum -y groupinstall "Development Tools"
$ yum -y install libev libev-devel zlib zlib-devel openssl openssl-devel git
安装 OpenSSL:
$ mkdir /var/tmp
$ cd /var/tmp
$ wget https://openssl.org/source/openssl-1.0.2.tar.gz
$ tar -zxf openssl-1.0.2.tar.gz
$ cd openssl-1.0.2
$ mkdir /opt/openssl
$ ./config --prefix=/opt/openssl
$ make
$ make test
$ make install
安装 nghttp2:
$ git clone https://github.com/tatsuhiro-t/nghttp2.git
$ cd nghttp2
$ autoreconf -i
$ automake
$ autoconf
$ ./configure
$ make
$ make install
$ echo '/usr/local/lib' > /etc/ld.so.conf.d/custom-libs.conf
$ ldconfig
$ ldconfig -p| grep libnghttp2
安装 curl:
$ cd /var/tmp
$ git clone https://github.com/bagder/curl.git
$ cd curl
$ ./buildconf
$ ./configure --with-ssl=/opt/openssl --with-nghttp2=/usr/local --disable-file --without-pic --disable-shared
$ make
验证:
$ /var/tmp/curl/src/curl --version
curl 7.70.0-DEV (x86_64-unknown-linux-gnu) libcurl/7.70.0-DEV OpenSSL/1.0.2o nghttp2/1.41.0-DEV
Release-Date: [unreleased]
Protocols: dict ftp ftps gopher http https imap imaps pop3 pop3s rtsp smb smbs smtp smtps telnet tftp
Features: AsynchDNS HTTP2 HTTPS-proxy IPv6 Largefile NTLM NTLM_WB SSL TLS-SRP UnixSockets
curl 从 7.52.0 版本开始也已经支持 TLS 1.3 了,curl 7.61.0 及以上在 TLS 握手过程中协商 TLS 版本时,curl 默认使用 TLS 1.3,但也取决于 curl 正在使用的 TLS 库及其版本,例如:要求 OpenSSL 1.1.1 版本以上。
curl 常用选项
curl [options] [URL...]
• -A/--user-agent:设置用户代理发送给服务器。
• -e/--referer:来源网址。
• --cacert:CA 证书(SSL)。
• -k/--insecure:允许忽略证书进行 SSL 连接。
• --compressed:要求返回是压缩的格式。
• -H/--header:自定义首部信息传递给服务器。
• -i:显示页面内容,包括报文首部信息。
• -I/--head:只显示响应报文首部信息。
• -D/--dump-header:将 URL 的 header 信息存放在指定文件中。
• --basic:使用 HTTP 基本认证。
• -u/--user user[:password]:设置服务器的用户和密码。
• -L:如果有 3xx 响应码,重新发请求到新位置。
• -O:使用 URL 中默认的文件名保存文件到本地。
• -o:将网络文件保存为指定的文件中。
• --limit-rate:设置传输速度。
• -0/--http1.0:数字 0,使用 HTTP 1.0。
• -v/--verbose:更详细。
• -C:选项可对文件使用断点续传功能。
• -c/--cookie-jar:将 URL 中 Cookie 存放在指定文件中。
• -x/--proxy proxyhost[:port]:指定代理服务器地址。
• -X/--request:向服务器发送指定请求方法。
• -U/--proxy-user :代理服务器用户和密码。
• -T:选项可将指定的本地文件上传到 FTP 服务器上。
• --data/-d:方式指定使用 POST 方式传递数据。
• -b name=data:从服务器响应 set-cookie 得到值,返回给服务器。
curl 指令使用 SNI
由上文可知,没有 SNI 的情况下,服务器无法预知客户端到底请求的是哪一个域名的服务。SNI 的 TLS 扩展通过发送虚拟域名做为 TLS 协商的一部分修正了这个问题,在 Client Hello 阶段,通过 SNI 扩展,将域名信息提前告诉服务器,服务器根据域名取得对应的证书返回给客户端已完成校验过程。 curl 7.18.1+ & openssl 0.9.8j+ 的组合就可以支持 SNI 了:
curl
--cacert /root/CA/nginx1.com/cacert.pem
-X GET "https://webserver.com:8443/"
-H 'Content-type: application/json'
-H 'Accept: application/json'
-H 'host: nginx1.com'
如果没有配置 DNS 解析的话可以使用 curl 7.21.3 支持的 --resolve 参数:
curl
--cacert /root/CA/nginx1.com/cacert.pem
--resolve webserver.com127.0.0.1
-X GET "https://webserver.com:8443/"
-H 'Content-type: application/json'
-H 'Accept: application/json'
-H 'host: nginx1.com'
--resolve 主要用于直接定位到 IP 地址进行访问,对于一个 Domain Name 有多个服务器(多个不同的 IP)的服务来说,这个参数可以指定的访问某个设备。
审核编辑:刘清
全部0条评论
快来发表一下你的评论吧 !