在物联网设备通信中,安全性是首要考虑因素。想象一下,你家的智能门锁如果被黑客入侵,后果不堪设想。传统的单向认证(比如我们访问HTTPS网站)只验证服务器身份,而双向认证(Mutual TLS)则要求通信双方都验证彼此的身份。
我在实际项目中遇到过这样的场景:两个ESP32设备需要通过Wi-Fi传输敏感数据。如果只做单向认证,攻击者可以伪造其中一个设备进行中间人攻击。而双向认证就像两个人在见面时互相检查身份证,确保"你确实是你说的人"。
双向认证的核心是三个文件:
首先确保你的开发环境已经安装:
我习惯在项目目录下创建certs文件夹存放证书,这样结构清晰:
bash复制mkdir -p esp32_mtls/certs
cd esp32_mtls/certs
很多开发者最头疼的就是证书生成,其实用这个脚本就能搞定。新建gen_certs.sh:
bash复制#!/bin/bash
# 生成CA证书
openssl genrsa -out ca.key 2048
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt -subj "/C=CN/ST=JS/L=NJ/O=ESP32/OU=IOT/CN=ESP32 CA"
# 生成服务器证书
openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr -subj "/C=CN/ST=JS/L=NJ/O=ESP32/OU=IOT/CN=esp32-server"
openssl x509 -req -days 3650 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt
# 生成客户端证书
openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr -subj "/C=CN/ST=JS/L=NJ/O=ESP32/OU=IOT/CN=esp32-client"
openssl x509 -req -days 3650 -in client.csr -CA ca.crt -CAkey ca.key -CAserial ca.srl -out client.crt
# 清理临时文件
rm *.csr *.srl
运行脚本后你会得到6个关键文件:
注意:实际生产环境应该使用更复杂的主题信息,这里为演示做了简化
我用的是ESP-IDF自带的https_server示例,具体路径:
$IDF_PATH/examples/protocols/http_server/simple
关键修改步骤:
cmake复制idf_component_register(SRCS "main.c"
INCLUDE_DIRS "."
EMBED_TXTFILES "server.crt" "server.key" "ca.crt")
c复制extern const uint8_t server_cert_start[] asm("_binary_server_crt_start");
extern const uint8_t server_cert_end[] asm("_binary_server_crt_end");
extern const uint8_t server_key_start[] asm("_binary_server_key_start");
extern const uint8_t ca_cert_start[] asm("_binary_ca_crt_start");
httpd_ssl_config_t conf = {
.cacert_pem = ca_cert_start,
.cacert_len = sizeof(ca_cert_start),
.prvtkey_pem = server_key_start,
.prvtkey_len = sizeof(server_key_start),
.cert_chain_pem = server_cert_start,
.cert_chain_len = sizeof(server_cert_start),
// 启用双向认证
.client_verify_cert_pem = ca_cert_start,
.client_verify_cert_len = sizeof(ca_cert_start),
.client_verify_mode = SSL_VERIFY_REQUIRED,
};
我遇到过几个典型问题:
启动服务端后,记下日志中的IP地址,客户端连接时会用到:
code复制I (1234) example: Starting server on port: 443
I (1235) example: Server listening on https://192.168.1.100
使用esp_http_client示例进行修改:
$IDF_PATH/examples/protocols/esp_http_client
关键配置结构体:
c复制esp_http_client_config_t config = {
.url = "https://192.168.1.100/api",
.cert_pem = (const char *)ca_cert_start, // 验证服务端的CA证书
.client_cert_pem = (const char *)client_crt_start, // 客户端证书
.client_key_pem = (const char *)client_key_start, // 客户端私钥
.skip_cert_common_name_check = false, // 严格校验CN字段
.timeout_ms = 5000,
};
我发现最可靠的方式是直接编译进固件:
cmake复制idf_component_register(SRCS "esp_http_client_example.c"
INCLUDE_DIRS "."
EMBED_TXTFILES "ca.crt" "client.crt" "client.key")
c复制extern const uint8_t ca_crt_start[] asm("_binary_ca_crt_start");
extern const uint8_t client_crt_start[] asm("_binary_client_crt_start");
extern const uint8_t client_key_start[] asm("_binary_client_key_start");
正常情况应该看到:
code复制I (1587) esp_https_client: Connection established...
I (1597) esp_https_client: SSL handshake successful
I (1607) example: HTTPS Status = 200
如果握手失败,常见错误包括:
经过实测,我有几个优化心得:
在ESP32-WROOM-32D上测试,完整握手过程约需800ms,会话复用后可降至200ms以内。
在实际部署时,有几个安全建议:
我曾遇到过一个坑:设备量产后才发现CA证书有效期只设了1年。建议生产环境CA证书至少设置10年有效期,终端设备证书可以设为1年。