WebRTC(十二):DTLS

发布于:2025-06-29 ⋅ 阅读:(15) ⋅ 点赞:(0)

在 WebRTC 中的作用

DTLS(Datagram Transport Layer Security)是 TLS 的 UDP 版本,在 WebRTC 中用于:

  • 安全协商加密密钥
  • 对等验证(基于 X.509 证书 + fingerprint)
  • 为 SRTP/SRTCP 提供密钥材料

WebRTC 不直接用 DTLS 传输音视频,而是通过它协商出密钥后,用于 SRTP/SRTCP 加密 RTP/RTCP 流。

与 SRTP 的关系

在 WebRTC 中,音视频数据加密流程如下:

[RTP/RTCP] ─► SRTP/SRTCP ─► UDP Socket
                   ▲
             Key 来源于 DTLS
  • DTLS:协商密钥(SDES 替代方案)
  • SRTP:使用 DTLS 派生出的密钥加密 RTP
  • SRTCP:同理用于 RTCP 加密

DTLS 本质上是提供 密钥协商、身份认证、消息完整性保护

握手流程详解

流程图

Client                  Server
  | ------ ClientHello ------> |
  | <--- HelloVerifyRequest -- |
  | ------ ClientHello ------> |  (带 cookie)
  | <-------- ServerHello -----|
  | <----- Certificate --------|
  | <- ServerKeyExchange (可选)
  | <- CertificateRequest (可选)
  | <---- ServerHelloDone -----|
  | ---- Certificate (可选) -->|
  | ---- ClientKeyExchange --> |
  | ---- CertificateVerify --> |
  | ---- ChangeCipherSpec -->  |
  | ---- Finished -----------> |
  | <--- ChangeCipherSpec ---- |
  | <-------- Finished --------|

ClientHello(第一次)

客户端发起握手,发送:

  • 协议版本(如 DTLS 1.2)
  • 随机数(Random)
  • 支持的加密套件(cipher suites)
  • 会话 ID(Session ID)
  • 扩展(如 SNI)

HelloVerifyRequest(服务器防攻击措施)

  • 目的:防止 DoS 攻击(攻击者伪造 IP,消耗服务器资源)
  • 内容:服务端返回一个 cookie 给客户端,要求客户端再发送一次 ClientHello 并带上这个 cookie
  • 此时没有任何状态被保留(无状态机制)

ClientHello(第二次,带 Cookie)

  • 客户端重新发起 ClientHello,并附上 cookie
  • 服务端验证 cookie 成功后,进入真正的握手过程

ServerHello

  • 服务端选择协议版本、加密算法
  • 提供其 Random、Session ID、压缩方法、扩展信息

Certificate(服务端证书)

  • 服务端发送其证书(如 X.509),用于客户端验证其身份

ServerKeyExchange(可选)

  • 如果所选加密套件是临时密钥交换(如 ECDHE),会发送这个报文,包含公钥参数等

CertificateRequest(可选)

  • 如果需要客户端身份认证,服务端请求客户端提供证书

ServerHelloDone

  • 服务端完成所有 hello 消息,提示客户端可以继续下一步

Certificate(客户端,若服务端请求)

  • 客户端发送证书用于身份认证(如 Mutual TLS)

ClientKeyExchange

  • 客户端发送密钥交换材料(如 DH 公钥、RSA 加密的 pre-master secret)

CertificateVerify(可选)

  • 如果客户端提供了证书,此报文包含对前面握手消息的签名,用于验证客户端拥有私钥

ChangeCipherSpec

  • 客户端通知服务端:接下来所有数据都将使用协商好的加密套件加密

Finished

  • 客户端发送已加密的 Finished 消息,包含所有握手摘要的摘要(验证握手完整性)

服务端 ChangeCipherSpec + Finished

  • 服务端也切换加密,并发送加密的 Finished 消息

特有的机制

特性 说明
消息序列号(Message Sequence) 每条握手消息都有编号,用于乱序重组
重传机制 如果对方没有应答,会重传握手消息(无 ACK)
记录层编号(Epoch/Sequence Number) 每个阶段有不同的 epochseq,用于包重放保护
HelloVerifyRequest 防止 IP 伪造攻击,DTLS 专属机制

重传机制(Retransmission)

  • 为什么需要?
    UDP 不保证报文到达和顺序,DTLS 握手报文可能丢失,必须实现重传保证握手完成。
  • 机制内容:
    • DTLS 对握手消息做序列号标记;
    • 如果客户端或服务器在超时时间内未收到预期消息,会重传之前的握手消息;
    • 重传报文必须被对端识别为重复消息并正确处理,不导致状态错误。

消息序列号(Message Sequence Number)

  • 用途:
    防止握手消息乱序,保证握手阶段状态同步。
  • 特点:
    • 每个握手消息分配序列号,递增;
    • 接收端按序列号处理消息,忽略重复或乱序消息。

握手消息片段化(Fragmentation)

  • 为什么需要?
    UDP 报文大小有限,握手消息可能大于单个 UDP 报文大小。
  • 机制内容:
    • 握手消息可以被分割成多个片段(Fragment)发送;
    • 接收端负责重组片段,直到完整消息;
    • 每个片段带有片段偏移和长度信息。

Cookie 机制(HelloVerifyRequest)

  • 目的:
    防止DoS 攻击(反射放大攻击),避免服务器资源被恶意请求耗尽。
  • 流程:
    • 客户端第一次发送 ClientHello,服务器不直接响应握手,而是回复一个带有 Cookie 的 HelloVerifyRequest
    • 客户端必须带上该 Cookie 重新发送 ClientHello
    • 服务器验证 Cookie 合法后才继续握手流程。

序列号和重放保护(Replay Protection)

  • 内容:
    • DTLS 为每条记录分配序列号(Record Sequence Number),确保记录不会被重放;
    • 接收端检测重复序列号,丢弃重放包;
    • 与 TLS 不同的是,DTLS 要在无连接环境下实现此功能。

无连接的记录层(Record Layer)

  • DTLS 使用和 TLS 类似的记录层结构,但消息通过 UDP 发送,没有连接状态;
  • 每条记录带有协议版本、内容类型、长度和序列号字段。

时间戳和定时器

  • DTLS 需要在握手时设置超时定时器,管理重传和超时处理;
  • 这在 TLS(基于 TCP)中不需要,因为 TCP 已经保证了可靠传输。

支持 MTU 探测与路径 MTU 发现

  • 由于 UDP 报文大小限制,DTLS 可以基于握手分片检测 MTU,以防止 IP 层分片导致性能下降。

Fingerprint(证书指纹)

DTLS Fingerprint 是对参与 DTLS 握手的 X.509 证书进行哈希计算(例如 SHA-256),生成的一串唯一字符串。

WebRTC 中不会使用 CA 机构来验证证书,而是通过交换证书指纹(fingerprint)来验证对等端。

  • SDP 协商中携带 fingerprint
  • 格式示例:
a=fingerprint:sha-256 7F:B8:33:...:9D

验证流程:

  1. 浏览器生成 DTLS 证书(X.509);
  2. 提取 fingerprint,加入 SDP;
  3. 对端在接收 DTLS 握手证书时,校验是否与 fingerprint 匹配;
  4. 若不匹配,则拒绝连接。

这确保了对端身份未被中间人篡改。

OpenSSL DTLS

#include <openssl/ssl.h>
#include <openssl/err.h>

SSL_CTX *init_dtls_server_ctx() {
    const SSL_METHOD *method = DTLS_server_method();
    SSL_CTX *ctx = SSL_CTX_new(method);

    // 加载服务器证书和私钥
    SSL_CTX_use_certificate_file(ctx, "server_cert.pem", SSL_FILETYPE_PEM);
    SSL_CTX_use_PrivateKey_file(ctx, "server_key.pem", SSL_FILETYPE_PEM);

    // 设置握手重传超时和最大重试次数
    // DTLS 有内置超时机制,OpenSSL自动处理重传
    // 不用手动实现重传逻辑

    return ctx;
}

void dtls_handshake(SSL *ssl, BIO *bio) {
    // 设置BIO为无连接UDP模式
    SSL_set_bio(ssl, bio, bio);

    // 阻塞握手直到成功或失败
    int ret = SSL_accept(ssl);
    if (ret <= 0) {
        int err = SSL_get_error(ssl, ret);
        if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
            // 需要继续读写,可能是等待重传
            // 事件循环继续
        } else {
            // 握手失败处理
        }
    }
    // 握手成功后,调用SSL_read/SSL_write进行加密数据收发
}

DTLS 与 TLS 的关键区别

项目 TLS DTLS
传输层 TCP UDP
顺序 有序 无序
重传 不需要 需要(自实现)
报文格式 流式 数据报(data-gram)
拒绝服务防护 有 HelloVerifyRequest
应用场景 HTTPS WebRTC / VoIP / SRTP / IoT