0%

dtls1.2跟tls1.2的不同

备忘。

1. 问题

UDP不可靠,这个引起两个问题

  1. tls是按顺序加解密的,两端维持有一个seq number,乱了的话,就解密不出来了;这个seq在tls中不是随数据包发送的,而是在两端各自维护。还有种情况是使用流加密,流加密的状态也是在两端维护的。这里导致记录层数据包之间是有隐含关联的。
  2. tls握手是以消息已经排好顺序为前提的,如果乱了就会认为握手不正确。比如该server发送 ServerHello,直接发送了 Certificate, client就会认为是 unexpected message,直接终止连接。

2. 处理

2.1. 针对记录层数据包之间的关联性

  1. DTLS不使用流加密
  2. DTLS将seq number放到每个数据包,随数据包一起发送,这样对端就能直接取出seq来解密

2.2. 针对握手阶段消息的顺序

  1. 数据包可能丢失—–>DTLS加上了握手消息重传机制
  2. 数据包可能重新排序—–>DTLS给握手过程中的每个消息都加上seq number,如果是期望的下一个消息就处理,如果不是就先缓存
  3. 握手数据包大小可能超过IP数据包分片大小(通常<1500)—–>DTLS允许一个握手数据包分成几个记录层数据包,保证每个记录层数据包都在PMTU之内。记录层数据包不会分成几个IP包;但几个记录层数据包可以在一个IP包中。

2.3. 重放检测

还有个TLS没有的问题,就是DTLS可能会收到重复的数据包,DTLS提供了一种可选的重放检查机制”bitmap”来检查重放,简单来说就是一个类似滑动有效窗口一样的范围,左侧是按顺序收到的最新的一个有效消息对应的seq number,右侧是DTLS认为有效的seq number,落在其中的消息都认为有效,都可以解密,落在窗口右侧的都可以解密校验MAC,校验通过就向右滑动窗口。

2.4. 无状态检查

这个其实DTLS和TLS(1.3)都有,但DTLS是必须的。

UDP没有连接,也没有三次握手,导致应用可以伪造数据包一直发送ClientHello,如果server每次收到ClientHello都建立握手状态,然后返回ServerHello,会导致server端内存很快耗尽。DTLS解决办法是收到 ClientHello 后,返回一个 HelloRetryRequest 的消息,该消息中带有一段”cookie”,其中存放 Cookie = HMAC(Secret, Client-IP, Client-Parameters),这是对client IP和加密参数的一个校验值,Secret 只有server才知道,这样就能方式client伪造。然后client再发送一个带上cookie的 ClientHello,server会再次验证该cookie是否是同一个client发来的,如果不是,server就拒绝连接。在验证第二个ClientHello有效之前,server不会保存握手状态,这样就可以防止client发起DDoS攻击。

3. 超时重传机制

这里重点介绍下这个超时重传,也就是解决UDP数据包可能丢失的问题。

首先要知道,DTLS只保证握手阶段数据包的可靠传输,应用数据是不保证的——当然DTLS保证即使应用数据包乱序和丢失,也能正常解密收到的数据。

DTLS将握手分为几个”flight”,每个”flight”其实是一端发送的一组消息,这组数据可以连续发出去,中间不会再收取对端的消息。每个”flight”当成一个整体,如果重传,就重传这一组消息。如下划分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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划分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Client Server
------ ------

ClientHello --------> Flight 1

ServerHello \
[ChangeCipherSpec] Flight 2
<-------- Finished /

[ChangeCipherSpec] \Flight 3
Finished --------> /

Figure 2. Message Flights for Session-Resuming Handshake
(No Cookie Exchange)

发送和重传的详细过程例子:client发送”flight 3”消息后,就建立个定时器,如果一段时间没有收到全部的 “flight 4” 消息,client就认为需要重传”flight 3”。server发送完”flight 4”后,也会建立个定时器,如果收到了client重传过来的”flight 3”,就会重传”flight 4”。client在收到全部的”flight 4”后会清理掉”flight 3”的定时器,server在收到全部的”flight 5”消息后,会清理掉”flight 4”的定时器。
如果是最后一个flight,比如完整握手中server发送的”flight 6”,发送完成后,同样需要建立一个定时器,最少是默认MSL的两倍,用来等待 client “flight 5”的重传包或者是应用数据。如果超过了2*MSL或者等来了client发来的应用数据,server就可以清理掉该定时器。会话恢复中则是client的 “flight 3” 建立定时器。

以上的过程可以统一到下边这个状态机中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
              +-----------+
| 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

因为是client先发送数据,所以client一开始进入的是”PREPARING”状态,server一开始进入的是”WAITING”状态。

PS: 重传消息的seq number不变。

4. 其他一些东西

  1. 最开始的 ClientHello 和 HelloVerifyRequest 不包含在最后 CertificateVerify 和 Finished 中HMAC的计算里
  2. 虽然一个握手消息会分成几个记录层数据包,但计算 CertificateVerify 和 Finished 中的hash的时候,仍会将分开的握手消息分片组成一个完整的握手消息去计算,毕竟计算握手副本hash的时候,用的只是握手数据,不包括记录层。
  3. 超时重传定时器的值,建议从1s开始,依次*2,最大值不少于 60s。
  4. 告警信息不会重传。
  5. epoch是每次加密状态变换的时候递增。

参考:

  1. dtls1.2-rfc6347
  2. tls1.2-rfc5246