TLS1.3系列文章(2):TLS1.3握手之密钥交换阶段

1,862 阅读8分钟

这是我参与8月更文挑战的第2天,活动详情查看:8月更文挑战

8月份更文挑战主题:TLS1.3协议
TLS.png
TLS1.3系列文章(2):TLS1.3握手之密钥交换阶段

TLS1.3握手消息

这里先对SSL/TLS协议做一个简单的介绍。SSL/TLS协议严格的说位于OSI-7层模型传输层(TCP, UDP)协议之上、应用层之下的会话层

20210911103119.png

SSL/TLS协议是一个分层协议,本身可以分为上下两层:

  • 下层为TLS记录层协议(record layer protocal)
  • 上层为TLS握手层协议(handshake layer protocal)

TLS协议的组成如下:

20210911103135.png

文中主要介绍其中的握手协议部分。TLS1.3完整握手流程如下:

tls1.3-fullhandshake.png

对上图中的特殊符号说明:

参数说明
+当前消息中需要重点关注的扩展载荷
*可选载荷
{}使用[sender]_handshake_traffic_secret衍生密钥加密的消息
[]使用[sender]_application_traffic_secret_N衍生密钥加密的消息

tls1.3的握手流程可以分成下面的三个阶段,下面按照这个三个阶段介绍包括的握手消息。

密钥交换阶段(Key Exchange): 协商加密算法以及共享密钥信息,此阶段完成后,所有消息都需要加密。

服务端参数阶段(Server Parameters): 协商其他握手阶段参数。

认证阶段(Authentication):协商双方认证、密钥确认以及握手报文校验。

http-TLS1.3.png

TLS1.3支持三种密钥交换方式:

1️⃣ ECDHE算法 :包括有限域的、椭圆曲线域的DH算法

2️⃣ PSK-only : 预共享密钥方式

3️⃣ PSK + ECDHE 算法 :DH算法+预共享密钥方式

下文中,主要是对这三个阶段中涉及的消息进行介绍。

2.1 密钥交换阶段

密钥交换阶段主要两个报文(消息)交互:ClientHello和ServerHello。下面对这两个报文的内容进行说明:

2.1.1 ClientHello消息

ClientHello的发送场景有两种:①客户端首次与服务端建立TLS连接时;②当服务端通过HelloRetryRequest消息明确要求客户端发送ClientHello消息时。需要注意的是:当服务端使用HelloRetryRequest消息要求客户端发送ClientHello载荷时,客户端必须使用与之前相同的ClientHello消息,除了下面几种场景外,不得有任何修改:

  • 当HelloRetryRequest消息中已经包括一个 "key_share" 而非提供了一个key_share列表时,需要在新的ClientHello中采用指定组进行密钥交换;
  • 如果之前的ClientHello附带了 "early_data" 扩展载荷,那么需要将其删除;因为HelloRetryRequest消息之后不允许使用0-RTT的方式;
  • 在HelloRetryRequest消息中存在 "cookie" 扩展载荷;
  • 填充的长度有修改的时候;
  • 需要重新计算 "pre_shared_key""obfuscated_ticket_age" 、以及删除部分服务端不支持的加密套件时;
  • 未来某些扩展载荷可能修改的情况下也需要修改ClientHello消息内容。

如果客户端和服务端已经使用以前的TLS版本(如TLS1.2及以前的版本)建立了TLS链接,那么即使服务端又收到了TLS1.3的连接请求,仍然不得使用TLS1.3进行协商。

ClientHello消息结构定义如下:

图片.png

通过Wireshark抓包,显示ClientHello消息报文内容如下:

image-20220709165236090.png

  • legacy_version

legacy_version在wireshark中实际显示为:verson。该字段用来TLS1.2以及更早TLS的版本协商,此字段填充的是客户端支持的最高版本号(很多实现中默认支持低版本tls)。目前TLS版本号定义如下:

TLS版本verson取值
TLS1.30x0304
TLS1.20x0303
TLS1.10x0302
TLS1.00x0301

为了彻底解决TLS版本协商的问题,TLS1.3中"supported_version"扩展载荷。该载荷中列出了客户端所支持的所有TLS版本,其中0x0304是目前现阶段最高的版本号(即TLS1.3); 在TLS1.3的ClientHello中,version字段填充的是0x0303,而在"supported_version"扩展载荷中填充“0x0304”,因此来表明当前是tls1.3的协商流程。

"supported_version"扩展载荷填充时,是按照发送端想要的版本优先级进行排序的。默认情况下是越安全的版本优先级越高:

image-20220719232735030.png

  • random

32字节的随机数,主要用于密钥的制作。

  • legacy_session_id

在TLS1.3之前的版本,该字段用于TLS会话的快速恢复;但是在TLS1.3中,会话快速恢复功能被“pre_shared keys”替换。

  • cipher_suites

发送端支持的加密套件。按照发送端自己想要的优先级进行降序排列,接收到对于不识别或者不支持的加密套件应该忽略后继续处理后续的套件。在wireshark中格式如下:

image-20220719233734922.png

这里再说一下加密套件(CipherSuite)的格式:

TLS——密钥交换算法——签名算法——WITH——加密算法——摘要算法

其中密钥交换算法和签名算法可以合二为一。

上图中服务端选择的算法套件是:TLS_RSA_WITH_AES_256_GCM_SHA384 (0xc030), 它的意思是:

  • 密钥交换算法采用:RSA
  • 签名算法采用:RSA
  • 加密算法采用:AES对称算法,密钥长度为256bit, 模式为:GCM。
  • 摘要算法采用:SHA384
  • legacy_compression_methods

TLS1.3中强制规定该字段必须填充为0。

  • extensions

为了照顾版本兼容性,在TLS1.3中将一些重要的功能载荷移到了扩展载荷中。扩展载荷在TLS1.3的ClientHello消息中是必须包括的(至少需要包括“supported_versions”)。

image-20220719234845584.png

2.1.2 ServerHello消息

在客户端和服务端可以就ClientHello消息中的参数(比如版本号,算法套件,密钥协商方式等等)达成一致的情况下,服务端发送ServerHello消息作为ClientHello消息的响应。

ServerHello的结构定义如下:

图片.png

通过Wireshark抓包,显示的ServerHello报文格式如下:

image-20220709165619564.png

  • legacy_version

在ClientHello消息中已经对该字段有了简单的介绍。这里再次强调下:该字段主要用于TLS1.3版本之前的TLS协商,在TLS1.3中该字段固定填充为0x0303; 除此之外,TLS1.3新增了“supported_versions”扩展载荷,用来标签客户端支持TLS1.3标准

  • random

32字节的服务端随机数。需要特别注意的是:当TLS1.3的服务端确定使用TLS1.2标准、甚至更低的TLS版本进行协商时,需要在random的最后8字节填充降级信息。当采用TLS1.2时,填充“DOWNGRD\x01”; 当采用TLS1.1或者更低时,填充“DOWNGRD\x00”。

  • legacy_session_id_echo

在tls1.3中,无论服务端是否复用sessionid对应的会话,在serverhello中都必须填充clienthello中的session_id值。如果客户端收到的session_id与自己发送的session_id不一致的话需要立即终止TLS协商。

  • legacy_compression_method

占用1个字节,在tls1.3中,该字段必须填充0。

  • extensions

扩展载荷列表。ServerHello消息中只能包含一些用于加密算法和版本协商的扩展载荷。在TLS1.3的ServerHello中,必须要支持的是“supported_version"扩展载荷;除此之外,现阶段的ServerHello消息还可能包括”pre_shared_key"或者"key_share",也有可能是两个载荷同时包括(当使用PSK+ECDH方式进行协商时)。其他的扩展载荷则是通过EncryptedExtensions消息单独发送。

Server端通过对ClientHello报文的处理,并通过ServerHello载荷来确定了双方采用的tls版本、算法套件信息、采用的密钥协商算法等信息。如果使用(EC)DHE方式进行密钥交换,则ServerHello包含一个 "key_share " 扩展,以及选择的DH组信息,此外还包含服务端的DH算法计算的公钥信息。 如果使用PSK密钥 建立,那么ServerHello包含一个 "pre_shared_key " 扩展,表明客户端提供的哪一个 PSK被选中。

image-20220709173935748.png

2.1.3 HelloRetryRequest消息

场景:在客户端和服务端就某些参数协商成功的前提下,但Client Hello消息包含的信息不足,无法继续进行TLS握手时,服务端发送HelloRetryRequtest消息作为对ClientHello的响应。

HelloRetryRequest消息与ServerHello消息拥有相同的格式,且version,session_id, cipher_suites,compression等字段含义也相同,不过在RFC8443中仍然将其定义为两种消息。

服务端发送的HelloRetryRequest的扩展载荷中必须“supported_versions"类型。除此之外,它也应该包含供客户端生成正确的ClientHello消息的最少扩展载荷集。和ServerHello消息一样,除了”cookie"扩展消息外,HelloRetryRequest中不得包含ClientHello中不存在的扩展载荷。

客户端在收到HelloRetryRequest消息后,客户端必须检查legacy_version, legacy_session_id_echo, cipher_suite,以及legacy_compression_method字段;之后首先通过“supported_version"载荷确定TLS采用的版本信息,然后继续处理其他扩展载荷信息。如果HelloRetryRequest不会导致ClientHello的任何更改,客户端必须使用“illegal_parameter”警报中止握手。如果客户端在同一连接中接收到第二个HelloRetryRequest(即ClientHello本身响应于HelloRetryRequest),则它必须用“unexpected_message”警报中止握手。

否则, 客户端必须处理HelloRetryRequest中的所有扩展,并发送第二个更新的ClientHello。RFC8446规范中定义的HelloRetryRequest扩展包括:

  • supported_version
  • cookie
  • key_share

客户端如果收到一个没有在ClientHello中包含的密码套件,则应该立即终止握手。 如果发送了HelloRetryRequest消息,则服务器端再次收到更新过的CLientHello消息时,必须确保ClientHello中的密码套件与HelloRetryRequest中的是一致的。客户端收到ServerHello后必须检查ServerHello中提供的密码套件是否与HelloRetryRequest中的密码套件相同,如果不相同,则将产生“illegal_parameter”告警,并且终止握手。

此外,客户端在收到HelloRetryRequest消息后,对于更新后的ClientHello消息,客户端不再需要提供所选密码套件之外的哈希算法相关联的任何预共享密钥。这种情况下,客户端可以避免在第二个ClientHello中计算多余的hash转换结构(因为这部分算法理论上不会再次被选中,因此完全没有必要进行计算)。

\