写点什么

TLS 协议分析 (六) handshake 协议扩展

用户头像
OpenIM
关注
发布于: 2 小时前

5.11. handshake — Finished

在 ChangeCipherSpec 消息之后,应该立即发送 Finished 消息,来确认密钥交换和认证过程已经成功了。ChangeCipherSpec 必须在其它握手消息和 Finished 消息之间。


Finished 消息是第一条用刚刚协商出来的参数保护的消息。接收方必须确认 Finished 消息的内容是正确的。一旦某一方发送了,并且确认了对端发来的 Finished 消息,就可以开始在连接上发送和接收应用数据了。


消息结构:


struct {    opaque verify_data[verify_data_length];} Finished;
verify_data PRF(master_secret, finished_label,Hash(handshake_messages)) [0..verify_data_length-1];
finished_label 对客户端发的Finished消息来说,固定是字符串 "client finished". 对服务器发的Finished消息来说,固定是字符串 "server finished".1.2.3.4.5.6.7.8.9.10.11.
复制代码


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 消息之后发送。


Client                                               ServerClientHello(empty SessionTicket extension)------->                                                ServerHello                            (empty SessionTicket extension)                                               Certificate*                                         ServerKeyExchange*                                        CertificateRequest*                             <--------      ServerHelloDoneCertificate*ClientKeyExchangeCertificateVerify*[ChangeCipherSpec]Finished                     -------->                                           NewSessionTicket                                         [ChangeCipherSpec]                             <--------             FinishedApplication Data             <------->     Application Data
Figure 1: Message flow for full handshake issuing new session ticket1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.
复制代码


客户端把收到的 ticket 和 master secret 等其它与当前 session 有关的参数一起,缓存起来。单客户端希望恢复会话时,就把 ticket 包含在 ClientHello 的 SessionTicket 扩展中发给服务器。服务器收到后,解密 ticket,算 MAC 确认 ticket 没有被篡改过,然后从解密的内容里面,获取 session 状态,用来恢复会话。如果服务器成功地验证了 ticket,可以在 ServerHello 之后返回一个 NewSessionTicket 消息来更新 ticket。


显然,这种情况下,相比完整握手,可以省掉 1 个 RTT。如下图:


Client                                                ServerClientHello(SessionTicket extension)      -------->                                                 ServerHello                             (empty SessionTicket extension)                                            NewSessionTicket                                          [ChangeCipherSpec]                              <--------             Finished[ChangeCipherSpec]Finished                      -------->Application Data              <------->     Application Data  Figure 2: Message flow for abbreviated handshake using new                        session ticket1.2.3.4.5.6.7.8.9.10.11.12.13.
复制代码


如果服务器不能,或者不想使用客户端发来的 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 失效。


struct {    uint32 ticket_lifetime_hint;    opaque ticket<0..2^16-1>;} NewSessionTicket;1.2.3.4.
复制代码


SessionTicket 和 Session ID 之间的关系比较繁琐。感兴趣的自行去看 RFC 吧。


对于客户端来说,ticket 就是一块二进制 buffer,客户端并不管里面的内容。所以 ticket 具体怎么加密加 MAC 服务器可以为所欲为,无需顾及客户端的感受。


RFC5077 中推荐了一种 ticket 的加密保护方法:服务器使用 2 个 key,一个 aes-128-cbc 的 key,一个 HMAC-SHA-256 的 key。


ticket 的格式像这样:


struct {    opaque key_name[16];    opaque iv[16];    opaque encrypted_state<0..2^16-1>;    opaque mac[32];} ticket;1.2.3.4.5.6.
复制代码


其中,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 格式序列化保存了下面这些字段:


typedef struct ssl_session_asn1_st {    ASN1_INTEGER version;    ASN1_INTEGER ssl_version;    ASN1_OCTET_STRING cipher;    ASN1_OCTET_STRING master_key;    ASN1_OCTET_STRING session_id;    ASN1_OCTET_STRING session_id_context;    ASN1_INTEGER time;    ASN1_INTEGER timeout;    ASN1_INTEGER verify_result;    ASN1_OCTET_STRING tlsext_hostname;    ASN1_INTEGER tlsext_tick_lifetime;    ASN1_OCTET_STRING tlsext_tick;} SSL_SESSION_ASN1;1.2.3.4.5.6.7.8.9.10.11.12.13.14.
复制代码

6. ChangeCipherSpec 协议

ChangeCipherSpec 用来通知对端,开始启用协商好的 Connection State 做对称加密,内容只有 1 个字节。这个协议是冗余的,在 TLS 1.3 里面直接被删除了。


changeCipherSpec 协议抓包:


7. Alert 协议

一种返回码机制,简单


  enum { warning(1), fatal(2), (255) } AlertLevel;
struct { AlertLevel level; AlertDescription description; } Alert;1.2.3.4.5.6.
复制代码


其中 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

用户头像

OpenIM

关注

还未添加个人签名 2021.08.30 加入

还未添加个人简介

评论

发布
暂无评论
TLS协议分析 (六) handshake协议扩展