TLS 1.3 在安全性和效率上相比 TLS 1.2 有重大改进,其中 CertificateVerify 消息是验证服务器(和客户端)拥有证书私钥的关键机制。本文深入解析其工作原理,特别是握手摘要(Transcript)在其中的作用。
CertificateVerify 的核心作用#
在 TLS 握手过程中,服务器需要证明自己拥有与证书对应的私钥。直接发送私钥显然不可能,于是 TLS 协议设计了 CertificateVerify 消息:
服务器使用私钥对握手摘要进行签名,客户端通过公钥验证签名,从而确认服务器拥有对应的私钥。
这一机制确保了:
- 身份认证:证书与私钥匹配
- 完整性保护:握手消息未被篡改
- 不可抵赖:签名无法伪造
握手摘要(Transcript)是什么?#
握手摘要是一个 哈希链,包含从 ClientHello 到 CertificateVerify 之前的所有握手消息的哈希值。在 TLS 1.3 中,这个摘要用于:
- CertificateVerify 签名:作为签名的输入
- Finished 消息验证:验证整个握手的完整性
握手摘要的计算#
TLS 1.3 使用 HKDF-Extract 构建握手摘要:
1
|
Transcript = Hash(Handshake Messages)
|
这里的 “Handshake Messages” 包含:
- ClientHello
- ServerHello
- EncryptedExtensions
- Certificate
- CertificateRequest (如果有)
实际查看握手消息#
使用 OpenSSL 可以查看完整的握手消息:
1
2
3
|
# 查看 TLS 1.3 握手详情
$ openssl s_client -connect cloudflare.com:443 -tls1_3 -debug 2>&1 | \
grep -E "Handshake message| CertificateVerify" | head -20
|
输出示例显示握手消息按顺序处理:
1
2
3
4
5
6
|
Handshake message: client_hello
Handshake message: server_hello
Handshake message: encrypted_extensions
Handshake message: certificate
Handshake message: certificate_verify
Handshake message: finished
|
CertificateVerify 消息结构#
消息格式#
1
2
3
4
|
struct {
SignatureScheme algorithm;
opaque signature<0..2^16-1>;
} CertificateVerify;
|
- algorithm:签名算法(如 ECDSA+SHA256, RSA+SHA256)
- signature:对握手摘要的签名
签名内容#
CertificateVerify 的签名输入是:
1
|
Transcript-Hash(Handshake Messages, Certificate)
|
具体来说,是对以下内容的签名:
1
2
|
TLS 1.3, CertificateVerify
Transcript-Hash value
|
在 RFC 8446 中定义:
1
2
3
4
5
6
7
8
9
10
|
const (
// ... other constants
TB_CERTIFICATE_VERIFY = 100
)
// ...
certificate_verify =
SignatureScheme algorithm;
opaque signature<0..2^16-1>;
|
不同签名算法的处理#
TLS 1.3 支持多种签名算法,主要分为两类:
ECDSA 系列#
1
2
3
4
5
|
# 查看服务器支持的签名算法
$ openssl s_client -connect cloudflare.com:443 -tls1_3 2>&1 | grep -E "Peer signing|Signature"
Peer signing digest: SHA256
Peer signature type: ECDSA
|
对于 ECDSA 证书:
- 计算握手摘要的 SHA-256 或 SHA-384 哈希
- 使用 ECDSA 私钥对该哈希签名
- 签名结果为 (r, s) 对序列化
RSA-PSS#
对于 RSA-PSS 证书:
1
2
3
4
|
# RSA-PSS 签名算法的连接示例
$ openssl s_client -connect example.com:443 -tls1_3 \
-sigalgs "RSA-PSS+SHA256:RSA-PSS+SHA384" 2>&1 | \
grep -E "Cipher|Signature"
|
RSA-PSS 是 TLS 1.3 推荐的 RSA 签名方案,相比传统 PKCS#1 v1.5 更安全。
实际验证过程#
查看证书详情#
首先查看服务器证书和签名算法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
$ openssl s_client -connect cloudflare.com:443 -tls1_3 2>&1 | \
grep -A5 "Server certificate"
Server certificate
-----BEGIN CERTIFICATE-----
MIID+jCCA5+gAwIBAgIRAKScWO9psGAiEyjsqQ14hIUwCgYIKoZIzj0EAwIwOzEL
MAkGA1UEBhMCVVMxHjAcBgNVBAoTFUdvb2dsZSBUcnVzdCBTZXJ2aWNlczEMMAoG
A1UEAxMDV0UxMB4XDTI2MDUxMDIxMTQwNloXDTI2MDgwODIyMTQwMlowGTEXMBUG
A1UEAxMOY2xvdWRmbGFyZS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQy
2/xUV3ysZxMwyNx1cbiFMRfU90PryGBSNpT7byCs+89lVT0dqt7v5qcpycOIc9gS
...
-----END CERTIFICATE-----
subject=CN = cloudflare.com
issuer=C = US, O = Google Trust Services, CN = WE1
|
验证签名算法#
1
2
3
4
5
6
7
8
9
|
# 详细查看证书信息
$ openssl x509 -in /dev/stdin -noout -text <<< "$(openssl s_client -connect cloudflare.com:443 -tls1_3 2>/dev/null | openssl x509)" | grep -A2 "Subject Public Key"
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (256 bit)
ASN.1 OID: prime256v1
NIST Curve: P-256
Signature Algorithm: ecdsa-with-SHA256
|
手动模拟验证#
虽然无法直接获取服务器的私钥进行验证,但可以用私钥模拟 CertificateVerify 的生成过程:
1
2
3
4
5
6
7
8
9
10
11
12
|
# 生成测试私钥和证书
$ openssl ecparam -genkey -prime256v1 -out test.key
# 生成自签名证书
$ openssl req -new -x509 -key test.key -out test.crt -days 365 \
-subj "/CN=Test Server"
# 提取证书公钥
$ openssl x509 -in test.crt -noout -pubkey > test.pubkey
# 模拟签名过程(仅为示例)
$ echo "test transcript hash" | openssl dgst -sha256 -sign test.key
|
安全性分析#
握手摘要的安全作用#
- 防止重放攻击:每次握手生成不同的摘要,无法复用
- 防止中间人篡改:任何握手消息的修改都会导致摘要变化
- 前向安全:即使长期私钥泄露,历史握手仍安全
常见攻击向量#
1. 证书私钥泄露#
如果服务器私钥泄露,攻击者可以伪造 CertificateVerify。但现代证书通常有较短的有效期,降低风险。
2. 弱签名算法#
TLS 1.3 已废弃弱算法:
| 不推荐使用 |
推荐使用 |
| RSA-PKCS1 v1.5 |
RSA-PSS |
| ECDSA (SHA-1) |
ECDSA (SHA-256/384) |
| RSA (1024-bit) |
RSA (2048-bit+) |
验证服务器配置#
检查服务器是否使用安全的签名算法:
1
2
3
4
5
|
# 检查支持的签名算法
$ openssl s_client -connect cloudflare.com:443 -tls1_3 -showcerts 2>&1 | \
openssl x509 -noout -text | grep "Signature Algorithm"
Signature Algorithm: ecdsa-with-SHA256
|
排查 CertificateVerify 失败#
常见错误#
使用 Wireshark 抓包时,可能遇到以下错误:
1
2
|
CertificateVerify: Failed
Signature verification error
|
排查步骤#
- 确认证书链完整:
1
|
$ openssl s_client -connect example.com:443 -showcerts 2>&1
|
- 检查握手摘要:
使用 Wireshark 过滤器:
1
|
tls.handshake.type == 15 // CertificateVerify
|
- 验证签名算法兼容性:
1
2
3
|
# 指定签名算法连接
$ openssl s_client -connect example.com:443 -tls1_3 \
-sigalgs "ECDSA+SHA256" 2>&1
|
与 TLS 1.2 的对比#
| 特性 |
TLS 1.2 |
TLS 1.3 |
| CertificateVerify 时机 |
证书之后、Finished 之前 |
证书之后、Finished 之前 |
| 签名输入 |
握手消息特定字段 |
完整握手摘要 |
| 传输加密 |
明文 |
加密(ServerHello 后已建立密钥) |
| 支持的算法 |
RSA, DSA, ECDSA |
仅强签名算法 |
| 0-RTT 支持 |
不支持 |
支持 |
实际配置建议#
Nginx 配置#
确保服务器使用强签名算法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
server {
listen 443 ssl http2;
server_name example.com;
# 启用 TLS 1.3
ssl_protocols TLSv1.3;
# 优先使用强密码套件
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers on;
# 证书和密钥
ssl_certificate /path/to/server.crt;
ssl_certificate_key /path/to/server.key;
}
|
验证配置#
1
2
3
4
5
6
7
|
# 测试服务器签名算法
$ openssl s_client -connect example.com:443 -tls1_3 2>&1 | \
grep "Peer signing"
# 使用指定算法测试
$ openssl s_client -connect example.com:443 -tls1_3 \
-sigalgs "ECDSA+SHA256" 2>&1 | grep "Cipher"
|
TLS 1.3 的 CertificateVerify 机制是握手中关键的安全环节:
- 握手摘要确保签名的上下文完整,防止篡改
- 加密传输CertificateVerify 消息,提升安全性
- 强签名算法要求(RSA-PSS、ECDSA)提升抗攻击能力
理解这一机制有助于:
- 排查 TLS 握手故障
- 正确配置服务器
- 评估证书安全性
参考来源#