參考:
- rfc 5246、rfc 6347
- https://www.cnblogs.com/VideoCloudTech/p/14744210.html
- https://support.f5.com/csp/article/K73646361
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]
3.4 ClientHello 新增 Cookie
通過 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)) 消息。