4、设备身份认证:双向认证机制、TLS 1.3握手流程、证书链验证、OCSP Stapling
设备身份认证,说白了就是解决一个问题:我怎么知道对面那个“设备”是真的?
在IoT场景里,这个问题比Web场景棘手得多。Web上你只要验证服务器证书就行,客户端是谁无所谓。但设备不一样——你既要确认云端是正经云端,也要确认接入的设备不是被篡改过的冒牌货。这就是双向认证的由来。
我早年做过一个智能门锁项目,当时只做了单向认证,结果被人在中间插了一个伪造的网关,所有开锁指令都被截走了。嗯,从那以后,我对双向认证再也不敢马虎。
4.1 双向认证机制
双向认证(Mutual TLS,简称mTLS),就是客户端和服务端各自出示证书,互相验明正身。
流程其实不复杂:
- 客户端发起连接:Client Hello,带上支持的密码套件。
- 服务端回应:Server Hello,选好密码套件,并发送自己的证书链。
- 客户端验证服务端证书:检查证书链、有效期、吊销状态。
- 服务端请求客户端证书:发送CertificateRequest消息。
- 客户端发送自己的证书链:并附带签名验证数据。
- 服务端验证客户端证书:同样检查证书链、吊销状态。
- 密钥协商完成:后续通信使用对称加密。
关键区别:单向认证只验证服务器,双向认证两边都验证。在IoT中,设备端证书通常预置在安全芯片或TEE中,私钥不可导出。
我个人习惯,在设备出厂前就把设备证书和根CA证书一起烧录进安全元件。这样设备第一次上电就能直接发起mTLS连接,不需要额外的注册流程。
4.2 TLS 1.3握手流程
TLS 1.3相比1.2,握手速度提升了一大截。1.2需要两次往返(2-RTT),1.3只需要一次(1-RTT),如果复用会话甚至可以0-RTT。
我画了一张流程图,你看一眼就明白了:
TLS 1.3有几个关键变化:
- 移除了不安全的密码套件:RSA密钥交换、CBC模式等全被砍掉。
- 强制使用前向安全:只能用ECDHE或DHE。
- 0-RTT模式:如果之前连接过,可以带上早期数据一起发。但要注意重放攻击。
注意:0-RTT虽然快,但有重放风险。在IoT场景中,如果设备频繁发送开锁、转账等敏感指令,建议禁用0-RTT,或者使用额外的防重放机制。
4.3 证书链验证
证书链验证,就是一层一层往上查,直到找到你信任的根CA。
举个例子:设备证书是由中间CA1签发的,中间CA1又是由根CA签发的。验证时,客户端拿着设备证书,先看签发者是谁,找到中间CA1证书,再看中间CA1的签发者,找到根CA证书。如果根CA在信任列表里,并且所有签名都正确,那就通过。
验证步骤其实很机械:
- 检查有效期:当前时间是否在证书的validFrom和validTo之间。
- 检查签名:用父证书的公钥验证子证书的签名。
- 检查基本约束:CA证书必须标记为CA=true,路径长度不能超限。
- 检查密钥用法:证书的Key Usage必须允许当前用途(比如数字签名)。
- 检查吊销状态:通过CRL或OCSP确认证书未被吊销。
我的经验:在嵌入式设备上,证书链验证很吃资源。我曾经在一个只有256KB RAM的设备上做验证,发现解析证书链时内存爆了。后来我用了证书链裁剪——只保留必要的中间证书,把根证书预置在固件里,才把内存降下来。
证书链验证的典型结构:
4.4 OCSP Stapling
证书吊销检查,传统做法是客户端自己去OCSP服务器查。但这样有几个问题:
- 性能差:每次连接都要多一次HTTP请求。
- 隐私泄露:OCSP服务器能知道谁在访问哪个网站。
- 单点故障:OCSP服务器挂了,客户端可能拒绝连接。
OCSP Stapling就是解决这些问题的。服务端自己定期去OCSP服务器拉取一个“已签名的时间戳证明”,然后在TLS握手时直接把这个证明塞给客户端。客户端验证这个证明的签名,就知道证书当前没被吊销。
流程是这样的:
- 服务端定时拉取:每隔一段时间(比如1小时),服务端向OCSP服务器请求当前证书的状态。
- OCSP服务器返回:返回一个签名的OCSP响应,包含证书序列号、状态(good/revoked/unknown)、时间戳。
- 服务端缓存:把OCSP响应缓存起来。
- TLS握手时附带:在Certificate消息后,服务端把缓存的OCSP响应发给客户端。
- 客户端验证:客户端用OCSP响应签发者的公钥验证签名,确认证书状态。
核心优势:客户端不需要额外网络请求,握手速度更快,隐私更好,而且OCSP服务器挂了也不影响已有连接。
我曾经遇到过一个坑:某个设备固件里的OCSP响应缓存时间设置得太长(24小时),结果证书被吊销后,设备还继续用旧缓存连接了将近一天。后来我把缓存时间改成了1小时,并且加了一个“强制刷新”机制——如果连接失败,立即重新拉取OCSP响应。
OCSP Stapling的配置示例(Nginx):
server {
listen 443 ssl;
ssl_certificate /etc/ssl/certs/server.crt;
ssl_certificate_key /etc/ssl/private/server.key;
# 启用OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
# OCSP响应签发者证书链
ssl_trusted_certificate /etc/ssl/certs/ca-chain.crt;
# OCSP服务器地址
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
}
在IoT设备端,验证OCSP Stapling的代码片段:
// 伪代码:验证OCSP Stapling响应
int verify_ocsp_stapling(X509 *server_cert,
const unsigned char *ocsp_resp,
size_t ocsp_len) {
OCSP_RESPONSE *resp = d2i_OCSP_RESPONSE(NULL, &ocsp_resp, ocsp_len);
if (!resp) return -1;
// 检查响应状态
int status = OCSP_response_status(resp);
if (status != OCSP_RESPONSE_STATUS_SUCCESSFUL) return -2;
// 获取基本响应
OCSP_BASICRESP *basic = OCSP_response_get1_basic(resp);
if (!basic) return -3;
// 验证签名(使用信任的CA证书)
if (!OCSP_basic_verify(basic, NULL, trusted_store, 0)) return -4;
// 检查证书状态
OCSP_CERTID *cid = OCSP_cert_to_id(NULL, server_cert, issuer_cert);
int cert_status = OCSP_resp_find_status(basic, cid, NULL, NULL, NULL, NULL);
OCSP_CERTID_free(cid);
OCSP_BASICRESP_free(basic);
OCSP_RESPONSE_free(resp);
return (cert_status == V_OCSP_CERTSTATUS_GOOD) ? 0 : -5;
}
注意:OCSP Stapling的响应有过期时间。如果服务端没有及时刷新,客户端拿到的可能是过期的状态。建议在客户端也做一层兜底——如果OCSP响应过期超过一定阈值,就主动发起OCSP请求。
最后总结一下这四块内容的关系:
| 概念 | 作用 | IoT场景要点 |
|---|---|---|
| 双向认证 | 设备和服务端互相验证身份 | 设备证书预置在安全芯片中,私钥不可导出 |
| TLS 1.3握手 | 建立加密通道,1-RTT完成 | 0-RTT慎用,注意重放攻击 |
| 证书链验证 | 逐级信任验证,直到根CA | 嵌入式设备注意内存限制,可裁剪证书链 |
| OCSP Stapling | 服务端代查证书吊销状态 | 缓存时间要合理,建议1小时以内 |
嗯,这四块内容,说白了就是设备身份认证的四个支柱。少了任何一个,你的安全体系都有漏洞。我在实际项目中见过太多“只做了单向认证”或者“没做吊销检查”的案例,结果都被攻击者钻了空子。你想想看,设备身份都保不住,后面的密钥轮换、安全通信还有什么意义?