参考来源:
书籍:深入浅出https-从原理到实战(作者:虞卫东)
抓包分析文件可下载,来自github上的作者上传资源
会话恢复机制的背景
当客户端和服务器端握手成功,建立了一个完整的 TLS 连接,只要客户端和服务器端
不主动关闭该连接,HTTPS 的应用层数据请求就一直受该 TLS 连接保护,一旦客户端和
服务器端关闭该连接,那么客户端下次访问 HTTPS 网站的时候就要进行一次新的连接,
造成了极大的网络延迟,并消耗客户端和服务器端的运算能力。
有没有一种机制能够复用先前的 TLS 连接呢?或者说能否恢复先前的 TLS 会话呢?
在 TLS/SSL 协议中,可以使用会话恢复机制。
一个完整的会话(Session)包括什么呢?
握手协议完成后,服务器端会在内存中保存会话信息,包括如下部分。
◎ 会话标识符(session identifier):每个会话都有唯一编号。
◎ 证书(peer certificate):对端的证书,一般情况下都为空。
◎ 压缩算法(compression method):一般不启用。
◎ 密码套件(cipher spec):客户端和服务器端协商出的密码套件。
◎ 主密钥(master secret):每个会话会保存一个主密钥,注意不是预备主密钥。
◎ 会话可恢复标识(is resumable):表示某个会话是否可恢复。
通过服务器保存的会话信息,最终能够生成 TLS 记录层协议所需要的加密参数
(security parameters),从而能够保护应用层的数据。
Session ID 的工作原理
在了解会话恢复之前,先回顾一下完整握手过程:
◎ 客户端发送 Client Hello 消息,其中传递的 Session ID 值为空。
◎ 服务器端检查客户端的 Session ID 值,如果该值为空,则进行完整的握手。生成一个新的 Session ID,该值通过服务器端的 Server Hello 消息传递给客户端。
◎ 客户端接收到服务器端的 Session ID 值后,会记录在内存中,也就是说客户端仅仅在内存中保存一个 Session ID 值。
◎ 服务器端和客户端完整处理 Finished 消息后,代表一个完整的会话结束,服务器端将会话信息保存 Session Cache 中,健值就是 Session ID,健值对应的内容就是会话信息。
基于 Session ID 的会话恢复处理流程
基于流程,描述下客户端和服务器端是如何处理的。
◎ 客户端再次请求相同的网站,如果该网站对应的 Session ID 值不为空,则 Client
Hello 消息附带该值。
◎ 服务器接收到该请求后,检查 Session Cache 是否能够匹配健值为 Session ID 的会
话,如果没有或者不可恢复会话,则进行完整的握手协议,同时生成一个新的
Session ID 返回给客户端。
◎ 服务器端如果能够恢复本次连接,则直接发送 ChangeCipherSpec 和 Finished 子消息,不进行密码协商,因为主密钥存在于 Session Cache 中。
◎ 最终客户端也发送 ChangeCipherSpec 和 Finished 子消息,表示会话恢复成功。基于 Session ID 的会话恢复主要由服务器端存储会话信息,该方式很早以前就存在于
TLS/SSL 协议中,大部分客户端和服务器端都支持这种恢复方式。
会话恢复需要注意的点:
◎ 即使客户端和服务器端能够恢复出上次连接的主密钥,客户端和服务器端最终生
成的密钥块和先前的密钥块是不一样的,主要原因就在于通过 PRF 生成密钥块的
时候,客户端和服务器端的随机数不同于前一次连接,这也有效地增强了安全性。
◎ 在恢复会话完成后,也要校验客户端和服务器端的 Finished 消息,避免握手消息
篡改。
◎ 恢复会话的时候,本次连接协商出的密码套件必须和会话中的密码套件是一致
的,否则就要进行完整的握手。
◎ 是否恢复成功取决于客户端和服务器端,即使存在可以恢复的会话,服务器也可
以要求进行完整的握手。
◎ 会话中并不保存扩展信息,所以每个扩展必须充分考虑会话恢复的情况。
◎ Session ID 是明文传输的,服务器 Session ID 不应该包含隐私数据,Session ID 也
很容易篡改或者伪造,不过有 Finished 消息的存在,一般不会遇到攻击。
客户端可以发送的 Session ID 来源
1)上一次完成握手后客户端记录的 Session ID
这种情况很好理解,比如用户间隔一段时间再次访问某个该网站,客户端就可以传递该 Session ID。
2)客户端使用另外一条连接正在使用的 Session ID
现代浏览器一般允许同时有多个连接请求,以便进行并行处理,某一条连接完成生成
一个 Session ID 后,客户端另外一条连接就可以发送该 Session ID,也就是相同时间点发送的连接可以包含同样的 Session ID。
3)客户端可以使用当前连接的 Session ID
只要客户端接收到服务器端 Server Hello 消息的 Session ID,就可以在下个连接中立刻发送该 Session ID。
Session ID 的优缺点
1)Session ID 会话恢复的好处
◎ 减少网络延迟,通过交互图可以看出完整握手协议需要两个 RTT(一次消息往
返),而简短的握手则减少了一个 RTT。
◎ 减少了客户端和服务器端的负载,握手协议耗时的操作在于密码学的运算,尤其
是密钥协商需要消耗大量的 CPU 运算,而简单的握手并不需要进行密钥协商。
2)Session ID 会话恢复的缺点
◎ 由服务器存储会话信息,这极大地限制了服务器的扩展能力,为了避免占用太多
的内存,要充分考虑会话的生命有效期。
◎ TLS/SSL 协议只是规定 Session Cache 的存储方式,没有考虑如何实现 Session
Cache。
大部分 Web 服务器都是基于底层的 OpenSSL 库实现的 Session Cache,没有考虑多个
主机共享 Session Cache 的问题。
总体来说,服务器存储和不支持分布式 Session Cache 极大限制了会话恢复效果,所
以接下来介绍一种新的会话恢复方式,那就是 SessionTicket。
第二种会话恢复方式-SessionTicket介绍
SessionTicket 是另外一种会话恢复方式,解决了 Session ID 会话恢复存在的缺点,是
一种更好的会话恢复方式。
SessionTicket 的处理标准定义在 RFC 5077 中,在 TLS/SSL 协议中,SessionTicket 以
TLS 扩展的方式完成会话恢复,SessionTicket 扩展的实现定义在 RFC 4507 上。
SessionTicket 的应用场景
SessionTicket 主要解决 Session ID 会话恢复存在的问题,如果遇到以下问题,那么特
别适合使用 SessionTicket。
◎ Session ID 会话信息存储在服务器端,对于大型 HTTPS 网站来说,占用的内存量
非常大,是非常大的开销。
◎ HTTPS 网站提供者希望会话信息的生命周期更长一点,尽量使用简短的握手。
◎ HTTPS 网站提供者希望会话信息能够跨主机访问,Session ID 会话恢复显然不能。
◎ 嵌入式的服务器没有太多的内存存储会话信息。
如果遇到以上问题,那么使用 SessionTicket 显然是合适的
SessionTicket 的交互流程
SessionTicket 从应用的角度来看,原理很简单,服务器将会话信息加密后以票据
(ticket)的方式发送给客户端,服务器本身不存储会话信息。客户端接收到票据后将其存
储到内存中,如果想恢复会话,则下一次连接的是将票据发送给服务器端,服务器端解密
后,如果确认无误则表示可以进行会话恢复,完成了一次简短的握手。
相对于 Session ID 的恢复来说,有两点的改变:
◎ 会话信息由客户端保存。
◎ 会话信息需要由服务器端解密,客户端不参与解密过程,只负责存储和传输。
SessionTicket 在具体实现的时候,其实有多种情况,接下来一一说明。
1.基于Session Ticket进行完整的握手
(1)对于一次新连接,如果期望服务器支持 SessionTicket 会话恢复,则在客户端 Client
Hello 消息中包括一个空的 SessionTicket TLS 扩展。
(2)如果服务器支持 SessionTicket 会话恢复,服务器的 Server Hello 消息中也包括一
个空的 SessionTicket TLS 扩展。
(3)服务器端对会话信息进行加密保护,生成一个票据,然后在 NewSessionTicket 子
消息中发送该票据,NewSessionTicket 子消息是握手协议的一个独立子消息。由于是完整
的握手,其他的一些子消息也会正常处理。
(4)客户端收到 NewSessionTicket 子消息后,将票据存储起来,以便下次使用。
2.基于 SessionTicket 进行简短的握手
(1)客户端存储了一个票据,如果希望恢复会话,则在客户端的 Client Hello 消息中
包括一个非空的 SessionTicket TLS 扩展。
(2)服务器端接收到非空票据后,对票据进行解密校验,如果可以恢复则在服务器
Server Hello 消息中发送一个空的 SessionTicket TLS 扩展。
(3)由于是简短握手,所以 Certificate 和 ServerKeyExchange 等子消息不发送,接下
来发送一个 NewSessionTicket 子消息来更新票据,票据也是有有效期的。
(4)客户端和服务器端接着校验 Finished 子消息则表示简单握手完成,顺利完成会话
恢复。
3.服务器不支持 SessionTicket 的交互流程
客户端发送了非空的 SessionTicket TLS 扩展后,服务器可以选择不支持会话恢复,也
不生成新的票据,为了达到这个目的,可以在服务器端 Server Hello 消息中不响应
SessionTicket TLS 扩展,也不发送 NewSessionTicket 子消息。
4.服务器校验票据失败的流程
客户端发送了非空的 SessionTicket TLS 扩展后,服务器校验失败后,可以重新生成新
的票据支持 SessionTicket,该流程和基于 SessionTicket 进行完整握手的流程差不多,只是
不发送 NewSessionTicket 消息,大概流程如下:
SessionTicket TLS 扩展
理解了 SessionTicket 的交互流程,再理解 SessionTicket TLS 扩展就很容易了。
(1)如果客户端想获得一个票据,可以在客户端 Client Hello 消息中发送一个空的
SessionTicket TLS 扩展。
(2)如果服务器端不想支持 SessionTicket 会话恢复,客户端 Client Hello 消息中不发
送 SessionTicket TLS 扩展即可。
(3)如果服务器端支持生成票据,不管客户端发送的 SessionTicket TLS 扩展是不是为
空,服务器都会发送 NewSessionTicket 子消息,该消息包含一个票据。
(4)服务器端没有接收到客户端的 SessionTicket TLS 扩展,不用进行任何 SessionTicket
处理。
NewSessionTicket 握手子消息
该消息必须在 ChangeCipherSpec 协议发送之前发送,如果服务器端 Server Hello 消
息包含 SessionTicket TLS 扩展,则必须发送该消息;如果服务器端 Server Hello 消息不
包含 SessionTicket TLS 扩展,则不能发送该消息,表示客户端或者服务器端不想使用
SessionTicket 会话恢复机制。
由于该消息也是握手协议的一部分,Finished 子消息校验消息完整性的时候也必须包
含 NewSessionTicket 子消息。
如果服务器端成功校验客户端发送的票据,必须重新生成一个票据,然后通过
NewSessionTicket 子消息发送新票据,客户端在下一次连接的时候应该发送新的票据。
和标准的 TLS/SSL 协议相比,握手协议新增了几个子消息,比如上一节讲解的
CertificateStatus 子消息。
NewSessionTicket 子消息中包含最重要的元素就是票据,票据也有生命周期,服务器
端应该校验票据的有效期,过期的票据不能用于进行会话恢复。
票据的生成完全由服务器端控制,客户端只是传输票据,不涉及票据的解密。
不同的 Web 服务器,票据生成使用的算法也不尽相同,但在实现的时候一定要注意
安全性,一旦票据加密的密钥被破解,则失去了前向安全性。
对于读者来说,了解会话恢复的原理很重要。能够正确通过 Nginx 或者 Apache 部署
即可,会话恢复是提升 HTTPS 性能非常关键的解决方案,也是 HTTPS 的一个重要知识点。
两种会话恢复方式如何共存
基于 Session ID 和 SessionTicket 的会话恢复会不会同时有效果?会不会有冲突?两者
之间的关系比较复杂,简单做下说明。
(1)如果服务器想使用 SessionTicket 机制,那么服务器 Server Hello 可以不发送
session_id。
(2)如果服务器不想使用 SessionTicket 机制,那么不在 Server Hello 消息中包含
SessionTicket 扩展即可,此时应该生成 session_id 发送给客户端。
(3)对于客户端来说,SessionTicket 恢复的优先级应该更高,如果服务器端同时发送
了票据和 session_id,客户端应该不使用 ServerHello.session_id。
(4)如果服务器端同时发送了票据和 ServerHello.session_id,为了方便切换两种会话
恢复方式,客户端应该同时发送票据和 session_id。服务器端接收后,如果在 Session Cache
中存在 session_id,则响应同样的 session_id 给客户端,同时也发送票据给客户端。
总体来说,如何混用两种会话方式取决于客户端和服务器端的实现。