DTLS 协议

参考:

1. 概述

tls 协议是基于 tcp 的协议,dtls 协议在 tls 协议的基础上,扩充了对 udp 的支持。本篇文章主要记录自己对 dtls 的理解,以及在 webrtc 中的应用。

2. dtls 协议结构

dtls 目前只有 1.0 和 1.2 版本,分别对应 tls 1.1 和 1.2 版本,webrtc 中主要使用的是 1.2 版本。
与 tls 一样,dtls 协议分为 record protocol(记录协议)、handshake protocol(握手协议)、alert protocol(警告协议) 和 change cipher spec protocol(改变密码规约协议):

2.1 record protocol(记录协议)

record protocol 的具体实现是 record layer,其可以看作一个容器,里面装载了3种上层协议。根据装载的数据类型,可以分为:

  • DTLSPlaintext,即明文数据,握手过程中大部分都是明文数据
  • DTLSCompressed,即压缩数据,没有看到过
  • DTLSCiphertext,即加密数据,在 ChangeCipherSpec 后都是这种数据

2.2 handshake protocol(握手协议)

dtls 握手过程中,除了前面所说的 handshake protocol,change cipher spec protocol(改变密码规约协议)也会出现在握手中。

3. 针对 udp 做的改变

3.1 record layer 的头部结构

对于所有消息,record layer 中定义了如下头部字段:

  • type(tls),描述上层消息类型(change_cipher_spec(20), alert(21), handshake(22), application_data(23))
  • version(tls),协议版本
  • epoch(new),密码规约变更后增 1,即 ChangeCipherSpec 后增 1,一般从 0 开始
  • sequence number(new),每个 epoch 中递增,一般从 0 开始,用于接收端检查丢包和乱序
  • length(tls),上层应用消息的长度,单位字节

3.2 handshake 中的 Fragment 字段

handshake 时,新增 Fragment 字段,如果握手消息超过 MTU 大小,则可以通过 Fragment 字段将消息分段发送:

  • Fragment offset,本分段在总消息中的长度偏移
  • Fragment length,本分段的长度

3.3 handshake 中的 message sequence 字段

handshake 时,新增 message sequence 字段,一般从 0 开始,每发送一个 handshake 消息增 1,重传的 handshake 消息中 message sequence 不变:

         Client                             Server
         ------                             ------
         ClientHello (seq=0)  ------>
                                 X<-- HelloVerifyRequest (seq=0)
                                                 (lost)
         [Timer Expires]

         ClientHello (seq=0)  ------>
         (retransmit)
                              <------ HelloVerifyRequest (seq=0)
         ClientHello (seq=1)  ------>
         (with cookie)
                              <------        ServerHello (seq=1)
                              <------        Certificate (seq=2)
                              <------    ServerHelloDone (seq=3)
         [Rest of handshake]

通过 Cookie 信息防止 DoS 攻击:

   The exchange is shown below:

      Client                                   Server
      ------                                   ------
      ClientHello           ------>

                            <----- HelloVerifyRequest
                                   (contains cookie)

      ClientHello           ------>
      (with cookie)

      [Rest of handshake]

   DTLS therefore modifies the ClientHello message to add the cookie
   value.

   struct {
     ProtocolVersion client_version;
     Random random;
     SessionID session_id;
     opaque cookie<0..2^8-1>;                             // New field
     CipherSuite cipher_suites<2..2^16-1>;
           CompressionMethod compression_methods<1..2^8-1>;
   } ClientHello;

3.5 重传

dtls 新增了两个序列号,一个在 record layer 头部中,一个在 handshake 消息的头部中:

  • record layer 头部的序列号(sequence number),所有 dtls 包都会携带,并且重传包中也会增1
  • handshake 头部的序列号(message sequence),只有握手时才会有,重传时不会变化

dtls 为握手过程中的不同包定义了 flight 的概念:

   Client                                          Server
   ------                                          ------
   ClientHello             -------->                           Flight 1
                           <-------    HelloVerifyRequest      Flight 2
   ClientHello             -------->                           Flight 3
                                              ServerHello    \
                                             Certificate*     \
                                       ServerKeyExchange*      Flight 4
                                      CertificateRequest*     /
                           <--------      ServerHelloDone    /
   Certificate*                                              \
   ClientKeyExchange                                          \
   CertificateVerify*                                          Flight 5
   [ChangeCipherSpec]                                         /
   Finished                -------->                         /
                                       [ChangeCipherSpec]    \ Flight 6
                           <--------             Finished    /
               Figure 1. Message Flights for Full Handshake

flight 是一个抽象的概念,表示多个包的组合在收发状态机中统一的处理过程(主要用作统一重传):

                      +-----------+
                      | PREPARING |
                +---> |           | <--------------------+
                |     |           |                      |
                |     +-----------+                      |
                |           |                            |
                |           | Buffer next flight         |
                |           |                            |
                |          \|/                           |
                |     +-----------+                      |
                |     |           |                      |
                |     |  SENDING  |<------------------+  |
                |     |           |                   |  | Send
                |     +-----------+                   |  | HelloRequest
        Receive |           |                         |  |
           next |           | Send flight             |  | or
         flight |  +--------+                         |  |
                |  |        | Set retransmit timer    |  | Receive
                |  |       \|/                        |  | HelloRequest
                |  |  +-----------+                   |  | Send
                |  |  |           |                   |  | ClientHello
                +--)--|  WAITING  |-------------------+  |
                |  |  |           |   Timer expires   |  |
                |  |  +-----------+                   |  |
                |  |         |                        |  |
                |  |         |                        |  |
                |  |         +------------------------+  |
                |  |                Read retransmit      |
        Receive |  |                                     |
           last |  |                                     |
         flight |  |                                     |
                |  |                                     |
               \|/\|/                                    |
                                                         |
            +-----------+                                |
            |           |                                |
            | FINISHED  | -------------------------------+
            |           |
            +-----------+
                 |  /|\
                 |   |
                 |   |
                 +---+

              Read retransmit
           Retransmit last flight

          Figure 3. DTLS Timeout and Retransmission State Machine

对于客户端:

  • 初始进入 preparing 状态,发送的 ClientHello 属于第 1 个 flight,将此 flight 缓存到队列中,进入 sending 状态
  • 取出第 1 个 flight 发送出去,进入 waiting 状态等待
  • 接收到第 2 个 flight(ServerHello 等) 后,进入 preparing 状态,等待上层处理完后发送第 3 个 flight(ClientFinished 等),然后进入 waiting 继续等待
  • 接收到第 4 个 flight(ServerFinished 等) 后,进入 finished 状态,握手完成,但是依然需要等待 2MSL 时间后再退出状态机,因为可能需要重传最后一个 flight
  • 在 waiting 状态,会启用递增定时器,超时后进入 sending 状态重发整个 flight 消息,然后重设递增定时器
  • 经过 n 此重传后,握手失败,返回错误

对于服务端:

  • 接收到第 1 个 flight(ClientHello) 后进入 preparing 状态,等待上层处理完后发送第 2 个 flight(ServerHello 等),然后进入 waiting 继续等待
  • 接收到第 3 个 flight(ClientFinished 等) 后,进入 preparing 状态,等待上层处理完后发送第 4 个 flight(ServerFinished 等) 握手完成,进入 finished 状态
  • 进入 finished 状态后,依然需要等待 2MSL 时间后再退出状态机,因为可能需要重传最后一个 flight
  • 在 waiting 状态,会启用递增定时器,超时后进入 sending 状态重发整个 flight 消息,然后重设递增定时器
  • 经过 n 此重传后,握手失败,返回错误

另外注意,HelloVerifyRequest 消息不会重传(rfc6347 3.2.1 节)。

3.5.1 超时时间

在 rfc6347 4.2.4.1 节中,推荐的默认初始超时时间为 t=1s,每次一个 flight 超时,更新定时器为 t=2t,在经过 60s 的总超时时间后,dtls 握手失败。
默认的超时时间可能不太合理(超时间隔太长),因此在 openssl 1.1.1b 版本开始,可以通过 DTLS_set_timer_cb() 函数自由设置超时时间。

4. dtls 握手消息

dtls 握手类似 tls,流程如下:

   Client                                          Server
   ------                                          ------
   ClientHello             -------->                      
                           <-------    HelloVerifyRequest 
   ClientHello             -------->                        
                                              ServerHello 
                                             Certificate*     
                                       ServerKeyExchange*      
                                      CertificateRequest*     
                           <--------      ServerHelloDone    
   Certificate*                                              
   ClientKeyExchange                                          
   CertificateVerify*                                     
   [ChangeCipherSpec]                                         
   Finished                -------->                         
                                       [ChangeCipherSpec]    
                           <--------             Finished    
        Figure 1. Message Flights for Full Handshake

4.1 ClientHello

  • Version:客户端支持的 dtls 版本为 2.0
  • Random:客户端随机数(用于生成最终密钥)
  • Cipher Suites:客户端支持的加密套件
  • Extension:客户端支持的扩展

4.2 ServerHello

  • Version:协商支持的 dtls 版本为 2.0
  • Random:服务端随机数(用于生成最终密钥)
  • Cipher Suite:协商支持的加密套件(主要是协商出密钥生成的方式,如 RSA、ECDHE)
  • Extension:协商支持的扩展

4.2.1 Cipher Suite 加密套件

Cipher Suite 遵循 IANA 标准,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA 的含义如下:

  • Protocol:这里为 TLS 协议
  • Key Exchange Algorithm:这里为 ECDHE 密钥交换算法
  • Authentication Algorithm:这里为 ECDSA 算法,即服务端需要计算签名时(Certificate、ServerKeyExchange)使用的默认签名算法,此项可能为空(密钥交换算法为 RSA 时可能为空,为空时签名算法与密钥交换算法相同)
  • Encryption Algorithm:这里为 AES_256_CBC 加密算法,即最终对称密钥加密使用的加密方式
  • Hash Algorithm:这里为 sha1,即服务端需要计算摘要时(Certificate、ServerKeyExchange)使用的默认 hash 算法

4.2.2 use_srtp extension

虽然 cipher suite 中协商了对称加密算法,但是 webrtc 通过 dtls 握手后实际使用的对称加密方式是通过 srtp 协议实现的,在 use_srtp 扩展中协商了 srtp 使用的加密方式:

4.3 ServerCertificate


虽然 cipher suite 中协商了摘要和签名算法,但是根据 ClientHello 中的 signature_algorithms extension(签名算法扩展),这里证书摘要和签名使用了 ecdsa-with-SHA256 算法。
证书中携带有证书对应的公钥,私钥保存在 server 端。注意在 ecdhe 密钥交换算法下,此密钥对与 ecdhe 生成的密钥对是独立的,证书中的密钥对只用来校验证书。
webrtc 中都是自签名私有证书,需要通过 sdp 中的 a=fingerprint 来验证证书是否有效。

4.3.1 fingerprint

当 webrtc 客户端生成证书后(webrtc 需要双向验证,所以两端都会生成证书),会将整个证书做一次 hash(包括证书的签名),得到的 hash 结果长度是固定的:

a=fingerprint:sha-256 60:DF:D2:8E:AC:1B:7C:DB:DA:36:39:57:AA:DD:1A:C2:43:58:36:09:80:56:CA:18:F4:85:6B:9F:B1:8F:78:13

其中,sha-256 说明了具体 hash 算法。
当另一端收到指纹和对端的证书后,对证书使用相同的 hash 算法做一次 hash,比较结果就能知道证书是否有效。

4.4 ServerKeyExchange


此消息用来将 server 端使用的公钥,发送给 client 端,此处需要区分两种不同密钥协商算法:

  • RSA:server 端可以不发送此消息,因为 RSA 算法使用的公钥已经在 ServerCertificate 中描述
  • DH:server 端通过此消息将 dh 算法每次握手随机生成的密钥对中的公钥发送给客户端

4.4.1 DH 算法下的冒充问题

此消息对公钥使用了 ecdsa_secp256r1_sha256 摘要签名算法进行了签名,签名密钥使用的是 ServerCertificate 证书中公钥对应的私钥进行签名的,客户端通过 server 证书中的公钥来验证此签名。
如果有中间人监听了上一次握手的消息,截取了 server 证书,下一次冒充 server 端进行握手的时候,虽然 client 端的证书校验能够通过,但是 ServerKeyExchange 却通过不了,因为冒充者没有证书对应的私钥。

4.5 CertificateRequest


webrtc 通信双方都需要验证对方的身份,所以这里会请求对方的证书。
这里 Signature Hash Algorithms 字段列出了本端支持的证书摘要和签名算法。

4.6 ServerHelloDone


此消息表明 server 端第一次握手消息已结束。

4.7 ClientCertificate

客户端会校验 server 端的证书,然后发送 client 端的证书给服务端。
client 证书中同样会携带证书密钥对中的公钥。

4.8 ClientKeyExchange

此消息用来将 client 使用的公钥,发送给 server 端,对于不同密钥协商算法,此消息有不同的含义:

  • RSA:使用 server 端 RSA 公钥(证书中),对 premaster secret 加密发送给 server 端
  • DH:客户端会将 DH 算法每次随机生成的密钥对中的公钥通过此消息发送给 server 端

4.9 CertificateVerify

只有服务端向客户端请求证书后,才会有此消息。
此消息是对客户端发送 CertificateVerify 之前所有收到和发送的握手信息(从 ClientHello 开始,不包括 ClientHello && HelloVerifyRequest 消息对)做的摘要签名,用于向服务端证明自己拥有 ClientKeyExchange 对应的私钥。

4.9.1 DH 算法下的冒充问题

此消息中签名密钥使用的是 ClientCertificate 证书中公钥对应的私钥进行签名的,server 端通过 client 证书中的公钥来验证此签名。
如果有中间人监听了上一次握手的消息,截取了 client 证书,下一次冒充 client 端进行握手的时候,虽然 server 端的证书校验能够通过,但是 CertificateVerify 却通过不了,因为冒充者没有证书对应的私钥。

4.10 ChangeCipherSpec

从此消息之后,客户端/服务端发送的 record 消息,除了头部后面都是加密的报文。

4.11 Finished


使用计算的对称加密密钥将前面的消息合起来计算消息认证码,客户端和服务端通过此 finished 消息校验两者计算的对称加密密钥是否是相同的,校验成功则 dtls 握手完全结束。

5. 安全性讨论

dtls 需要解决经典的 3 个安全问题,分别是窃听、篡改和冒充问题。

5.1 窃听问题

虽然握手过程是明文的,但是第三方没法获取最终的加密密钥,也就无法破解加密后的消息。

5.2 篡改问题

虽然握手过程是明文的,如果篡改了握手消息,握手将会失败。如果篡改了对称加密的消息,消息认证码校验将会失败。

5.3 冒充问题

如果假设了信令通道(传递 sdp 的信道)是安全的,那么第三方无法提供虚假证书,因为证书摘要需要与 sdp 中的指纹对应起来。
如果证书被监听然后盗用,根据前面讨论的 DH 算法下的冒充问题,如果发生了冒充(提供了正确的证书),然而在 ServerKeyExchange 和 ClientCertificate 消息中的签名校验也会失败。

6. 生成 srtp 密钥

具体生成过程没有详细研究,可以参考 https://blog.csdn.net/VideoCloudTech/article/details/116521237 这篇文章和 srs 中的源码。

7. dtls 连接关闭

dtls 连接关闭时,需要发送 alert(warning(1), close_notify(0)) 消息。

posted @ 2022-03-08 20:42  小夕nike  阅读(3175)  评论(0编辑  收藏  举报