TLS 协议分析 (六) handshake 协议扩展
5.11. handshake — Finished
在 ChangeCipherSpec 消息之后,应该立即发送 Finished 消息,来确认密钥交换和认证过程已经成功了。ChangeCipherSpec 必须在其它握手消息和 Finished 消息之间。
Finished 消息是第一条用刚刚协商出来的参数保护的消息。接收方必须确认 Finished 消息的内容是正确的。一旦某一方发送了,并且确认了对端发来的 Finished 消息,就可以开始在连接上发送和接收应用数据了。
消息结构:
Hash 表示握手消息的 hash。hash 函数是前文 PRF 的 hash 函数。或者 CipherSuite 规定的用于 Finished 计算的 hash 函数。
在 TLS 的之前版本中,verify_data 总是 12 字节。在 TLS 1.2 中,这取决于 CipherSuite。如果 CipherSuite 没有显式规定 verify_data_length ,就当成 12 字节处理。将来的 CipherSuite 可能会规定别的长度,但是不能小于 12 字节。
Finished 消息必须跟在 ChangeCipherSpec 消息之后,如果顺序错乱,就是 fatal error.
handshake_message 的内容包含从 ClientHello 开始,直到 本条 Finished 之前的所有消息,只包含 handshake 层的消息体,不包含 record 层的几个消息头字段。包括 CertificateVerify 消息。同时,对客户端和服务器来说,handshake_message 的内容不同, 后发送者必须包含前发送者的 Finished 消息。
注意:ChangeCipherSpec 消息,alert,和其它的 record 类型不是握手消息,不包含在 hash 计算中。同时,HelloRequest 消息也不算在内。
5.12. handshake — NewSessionTicket
SessionTicket 定义在 RFC5077 标准里面,2008 年发布。
SessionTicket 是一种不需要服务器端状态的,恢复 TLS session 的方式。SessionTicket 可以用于任何 CipherSuite。 TLS 1.0, TLS 1.1, TLS 1.2 都适用。
在下面这些场景下,尤其有用:
用户量巨大,session id 的方式耗费服务器内存过多服务器希望长时间缓存 session 服务器有多台,不希望服务器间有共享状态服务器内存不足客户端在 ClientHello 中设置一个 SessionTicket 扩展来标识自己支持 SessionTicket。如果客户端本地没有存之前收到的 ticket,就把这个扩展设为空。
如果服务器希望使用 SessionTicket 机制,服务器把本地的 session 状态存入一个 ticket 中,ticket 会被加密,并被 MAC 保护,无法篡改,加密和算 MAC 用的 key 只有服务器知道。加密并 MAC 过的 ticket 用 NewSessionTicket 消息分发给客户端,NewSessionTicket 消息应该在 ChangeCipherSpec 消息之前,在服务器验证通过客户端的 Finished 消息之后发送。
客户端把收到的 ticket 和 master secret 等其它与当前 session 有关的参数一起,缓存起来。单客户端希望恢复会话时,就把 ticket 包含在 ClientHello 的 SessionTicket 扩展中发给服务器。服务器收到后,解密 ticket,算 MAC 确认 ticket 没有被篡改过,然后从解密的内容里面,获取 session 状态,用来恢复会话。如果服务器成功地验证了 ticket,可以在 ServerHello 之后返回一个 NewSessionTicket 消息来更新 ticket。
显然,这种情况下,相比完整握手,可以省掉 1 个 RTT。如下图:
如果服务器不能,或者不想使用客户端发来的 ticket,那服务器可以忽略 ticket,启动一个完整的握手流程。
如果服务器此时不希望下发新的 ticket,那就可以不回复 SessionTicket 扩展,或者不回复 NewSessionTicket 消息。此时除了 ClientHello 里面的 SessionTicket 扩展,就和一般的 TLS 流程一样了。
如果服务器拒绝收到的 ticket,服务器可能仍然希望在完整的握手之后,下发新的 ticket。此时流程和全新 ticket 生成下发的区别,就是 ClientHello 的 SessionTicket 不是空的。
NewSessionTicket 消息服务器在握手过程中,发 ChangeCipherSpec 之前发送 NewSessionTicket 消息。如果服务器在 ServerHello 中包含了一个 SessionTicket 扩展,那就必须发送 NewSessionTicket 消息。如果服务器没有包含 SessionTicket 扩展,那绝对不能发送 NewSessionTicket 消息。如果服务器在包含了 SessionTicket 扩展之后,不想发送 ticket,那可以发送一个长度为 0 的 NewSessionTicket 消息。
在完整握手的情况下,客户端必须在确认服务器的 Finished 消息正确之后,才能认为 NewSessionTicket 里面的 ticket 合法。
服务器可以 NewSessionTicket 消息中更新 ticket。
ticket_lifetime_hint 字段包含一个服务器的提示,提示客户端本 ticket 应该存多长时间就失效。单位是秒,网络字节序。当时间到期时,客户端应该删掉 ticket 和关联的状态。客户端也可以提前删除。服务器端也可以提前认为 ticket 失效。
SessionTicket 和 Session ID 之间的关系比较繁琐。感兴趣的自行去看 RFC 吧。
对于客户端来说,ticket 就是一块二进制 buffer,客户端并不管里面的内容。所以 ticket 具体怎么加密加 MAC 服务器可以为所欲为,无需顾及客户端的感受。
RFC5077 中推荐了一种 ticket 的加密保护方法:服务器使用 2 个 key,一个 aes-128-cbc 的 key,一个 HMAC-SHA-256 的 key。
ticket 的格式像这样:
其中,key_name 用来标识一组 key,这样服务器端就可以使用多组 key。
加密过程,首先随机生成 IV,然后用 aes-128-cbc 加密 session 的序列化结果,然后用 HMAC-SHA-256 对 key_name,IV,encrypted_data 的长度(2 字节),encrypted_data 计算 MAC。最好把各个字段填入上面 ticket 结构体。显然,此处是 Encrypt-then-MAC 的方式,是最安全的。
实际在 openssl 中的 session,用 asn1 格式序列化保存了下面这些字段:
6. ChangeCipherSpec 协议
ChangeCipherSpec 用来通知对端,开始启用协商好的 Connection State 做对称加密,内容只有 1 个字节。这个协议是冗余的,在 TLS 1.3 里面直接被删除了。
changeCipherSpec 协议抓包:
7. Alert 协议
一种返回码机制,简单
其中 level 是等级,不同等级要求不同的处理。
其中有一种:close_notify,用来通知对端,我不会再发送更多数据了。这个可以让对端主动 close fd,这样可以减少我方 tcp timewait 状态的 socket 量。
alert 协议:
8. application data 协议
application data 协议,就是把应用数据直接输入 record 层,做分段,算 MAC,加密,传输。抓包举例如下:
本文转自微信后台团队,如有侵犯,请联系我们立即删除
OpenIMgithub 开源地址:
https://github.com/OpenIMSDK/Open-IM-Server
OpenIM 官网 : https://www.rentsoft.cn
**OpenIM 官方论坛: ** https://forum.rentsoft.cn/
更多技术文章:
开源 OpenIM:高性能、可伸缩、易扩展的即时通讯架构https://forum.rentsoft.cn/thread/3
【OpenIM 原创】简单轻松入门 一文讲解 WebRTC 实现 1 对 1 音视频通信原理https://forum.rentsoft.cn/thread/4
【OpenIM 原创】开源 OpenIM:轻量、高效、实时、可靠、低成本的消息模型https://forum.rentsoft.cn/thread/1
评论