# 证书与指纹 · 防中间人的关键

> DTLS 握手用自签名证书，可自签名怎么防中间人？靠指纹——把证书的哈希写进 SDP 的 a=fingerprint 行，握手后比对远端证书的真实指纹。这一章读 rtc_certificate.h，讲清证书生成、指纹如何把 DTLS 安全和信令绑定，以及 setup 角色谁当 DTLS 客户端。

- 作者：David（道雾轩）
- 专栏：音视频与 WebRTC 详解（https://daiw.org/manual/webrtc.md）
- 最后更新：2026-07-14
- 原文：https://daiw.org/manual/webrtc/certificates-fingerprints
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# 证书与指纹 · 防中间人的关键

[DTLS 握手](https://daiw.org/manual/webrtc/dtls-handshake)要用证书。但 WebRTC 的证书是**自签名**的——没有 CA（证书颁发机构）背书。那怎么防止中间人偷偷换掉证书、冒充对端？答案藏在一个巧妙的设计里：**指纹 + 信令**。这一章讲清这个 WebRTC 安全的关键一环。

## 自签名证书：RtcCertificate

每个 [`PeerConnection`](https://daiw.org/manual/webrtc/peer-connection-object) 用一张 **`RtcCertificate`**（`rtc_base/rtc_certificate.h`）做 DTLS 握手。它包装了一个 `SSLIdentity`（证书 + 私钥）：

```cpp
// rtc_base/rtc_certificate.h(节选)
class RTCCertificate final : ... {
  const SSLCertificate& GetSSLCertificate() const;
  const SSLCertChain& GetSSLCertificateChain() const;
  // 由一个 SSLIdentity(证书 + 私钥)构造
};
```

这张证书是**自签名**的——WebRTC 自己生成一对密钥、自己签一张证书，不找 CA。可自签名证书人人都能造，中间人也能造一张自己的，那安全从何谈起？

## 指纹：把证书哈希写进 SDP

关键机制来了。虽然证书自签名，但它的**指纹（fingerprint）**——即证书内容的哈希（如 SHA-256）——是**唯一**的、伪造不了「既是这张证书、指纹又对得上」。WebRTC 的做法：

<Callout type="info">
  **把本地证书的指纹，写进 [SDP](https://daiw.org/manual/webrtc/sdp-and-jsep) 的 `a=fingerprint:` 行，随[信令](https://daiw.org/manual/webrtc/call-lifecycle)发给对端。** 于是对端在[协商](https://daiw.org/manual/webrtc/offer-answer-handler)阶段就知道了「我将要握手的那张证书，指纹应该是 X」。等 [DTLS 握手](https://daiw.org/manual/webrtc/dtls-handshake)时，对端拿到你**实际出示的证书**，算一遍它的指纹，和 SDP 里承诺的 X **比对**：

  - **指纹一致** → 握手的这一方，正是[信令](https://daiw.org/manual/webrtc/call-lifecycle)里那一方。安全。
  - **指纹不符** → 有人在中间掉了包、换了证书。**握手中止**，连接失败。

  这正是 `g3doc/dtls_transport.md` 说的：握手产出「a fingerprint of the remote certificate which is **compared to the one given in the SDP `a=fingerprint:` line**」。
</Callout>

## 安全的根基：信令可信

这个设计有个深刻的前提，值得点透：

<Callout type="info">
  **DTLS 的防中间人，建立在「[信令](https://daiw.org/manual/webrtc/call-lifecycle)本身是可信的」之上。** 指纹是随 SDP 走信令传的。如果**信令通道**被中间人完全控制，他可以把双方的指纹都换成自己的，然后分别和两端握手，做成一个「双面中间人」。所以 WebRTC 的媒体安全，**最终依赖信令的安全**——这就是为什么真实部署里，信令通道几乎总是走 [HTTPS/WSS](https://daiw.org/manual/webrtc/call-lifecycle)（加密 + 服务器身份认证）。

  换个角度看，这是一个漂亮的**信任传递**：你信任信令服务器（靠 HTTPS 的 CA 体系），信令带来对端的指纹，指纹又在 DTLS 握手时验证媒体通路的对端——**信任从「有 CA 的信令」传递到了「无 CA 的媒体」**。自签名证书省去了给每个 WebRTC 端点签发 CA 证书的巨大麻烦，而指纹 + 可信信令补上了安全，设计得很经济。
</Callout>

## setup 角色：谁当 DTLS 客户端

[DTLS 握手](https://daiw.org/manual/webrtc/dtls-handshake)是有「客户端 / 服务端」之分的（谁先发 ClientHello）。这个角色也在 [SDP](https://daiw.org/manual/webrtc/sdp-and-jsep) 里协商，靠 `a=setup:` 行：

- **`a=setup:actpass`** —— offer 方常用，表示「主动或被动都行，你定」。
- **`a=setup:active`** —— 我当 DTLS **客户端**（主动发起握手）。
- **`a=setup:passive`** —— 我当 DTLS **服务端**（等对方发起）。

通常 answer 方选 `active` 当客户端。这和 [ICE 的 controlling/controlled 角色](https://daiw.org/manual/webrtc/ice-controller)是**独立**的两回事——一个定「DTLS 谁先握手」，一个定「ICE 谁拍板选路」，别混淆。

## 小结与下一章

WebRTC 的 DTLS 用**自签名证书**（`RtcCertificate`，包装 `SSLIdentity`），靠**指纹**防中间人：证书哈希写进 [SDP 的 `a=fingerprint:`](https://daiw.org/manual/webrtc/sdp-and-jsep) 随[信令](https://daiw.org/manual/webrtc/call-lifecycle)发出，[握手](https://daiw.org/manual/webrtc/dtls-handshake)时比对远端证书的真实指纹，不符则中止。这套安全的根基是**信令可信**（故信令走 HTTPS/WSS）——信任从「有 CA 的信令」传递到「无 CA 的媒体」。DTLS 客户端 / 服务端角色由 `a=setup:` 协商，与 [ICE 角色](https://daiw.org/manual/webrtc/ice-controller)无关。

握手成功、身份也验明了，接下来就是把握手产出的密钥材料，变成 [SRTP](https://daiw.org/manual/webrtc/srtp-encryption) 真正能用的密钥。下一章讲这个「导出」。👉 [DTLS-SRTP 密钥导出](https://daiw.org/manual/webrtc/dtls-srtp-key-export)。

## 参考资料

- [1] libwebrtc 源码.`rtc_base/rtc_certificate.h`（`RTCCertificate`、`SSLIdentity`、`GetSSLCertificate`）。
- [2] libwebrtc 源码.`g3doc/dtls_transport.md`（指纹比对 SDP）、`rtc_base/rtc_certificate_generator.h`（证书生成）。
- [3] IETF RFC 8122「SDP 里的证书指纹」、RFC 5763「DTLS-SRTP 建立」。
