ESP32开发:ESP-TLS简介
ESP-TLS 组件提供简化 API 接口,用于访问常用 TLS 功能,支持如 CA 认证验证、SNI、ALPN 协商和非阻塞连接等常见场景。本文 https_request 例程为例,分析ESP-TLS的使用过程。
ESP-TLS使用简介
https_request 例程是一个HTTPS请求演示,展示了怎么使用ESP-TLS库来建立HTTPS的通信过程,请发送HTTP GET请求。
其中ESP-TLS 库是 ESP-IDF 对底层 TLS (mbedTLS 或者wolfSSL) 做的一层封装,提供了更高层、更简洁的接口,方便快速发起 HTTPS 请求,屏蔽了较多底层细节。用户可以设置选择底层的TLS库,默认情况下,使用mbedTLS 库。
一、ESP-TLS 简介
ESP-TLS 组件提供简化 API 接口,用于访问常用 TLS 功能,支持如 CA 认证验证、SNI、ALPN 协商和非阻塞连接等常见场景,相关配置可在数据结构体 esp_tls_cfg_t 中指定。配置完成后,使用以下 API 进行 TLS 通信:
esp_tls_init():初始化 TLS 连接句柄。
esp_tls_conn_new_sync():开启新的阻塞式 TLS 连接。
esp_tls_conn_new_async():开启新的非阻塞式 TLS 连接。
esp_tls_conn_read():读取 TLS 层之上的应用数据。
esp_tls_conn_write():将应用数据写入 TLS 连接。
esp_tls_conn_destroy():释放连接。
任何应用层协议,如 HTTP1、HTTP2 等,均可调用 ESP-TLS 组件接口实现。
1. ESP-TLS 组件的树形结构
├── esp_tls.c
├── esp_tls.h
├── esp_tls_mbedtls.c
├── esp_tls_wolfssl.c
└── private_include
├── esp_tls_mbedtls.h
└── esp_tls_wolfssl.h
ESP-TLS 组件文件 esp-tls/esp_tls.h 包含该组件的公共 API 头文件。在 ESP-TLS 组件内部,为了实现安全会话功能,会使用 MbedTLS 和 WolfSSL 两个 SSL/TLS 库中的其中一个进行安全会话的建立,与 MbedTLS 相关的 API 存放在 esp-tls/private_include/esp_tls_mbedtls.h,而与 WolfSSL 相关的 API 存放在 esp-tls/private_include/esp_tls_wolfssl.h。
2. TLS 服务器验证
ESP-TLS 在客户端提供了多种验证 TLS 服务器的选项,如验证对端服务器的服务器证书、或使用预共享密钥验证服务器。用户应在 esp_tls_cfg_t 结构体中选择以下任一选项完成 TLS 服务器验证,若未做选择,则客户端默认在 TLS 连接创建时,会返回错误。
TLS中有以下几种验证方式:
-
cacert_buf 和 cacert_bytes:以缓冲区的形式向
esp_tls_cfg_t结构体提供 CA 证书,ESP-TLS 将使用缓冲区中的 CA 证书验证服务器。注意,须在esp_tls_cfg_t结构体中设置以下变量:cacert_buf- 指针,指向包含 CA 证书的缓冲区。cacert_bytes- CA 证书大小(以字节为单位)。
-
use_global_ca_store:
global_ca_store把CA证书存放在全局存储区,只需要初始化一次,后续所有的TLS连接,只需要在的esp_tls_cfg_t结构体中设置use_global_ca_store = true,就能使用该CA验证 ESP-TLS 连接的服务器。 -
crt_bundle_attach:ESP x509 证书包 API 提供了便捷的服务器验证方法,即打包一组自定义的 x509 根证书,用于 TLS 服务器验证,详情请参阅 ESP x509 证书包。
-
psk_hint_key:要使用预共享密钥验证服务器,必须在 ESP-TLS menuconfig 中启用 CONFIG_ESP_TLS_PSK_VERIFICATION,然后向结构体
esp_tls_cfg_t提供指向 PSK 提示和密钥的指针。若未选择有关服务器验证的其他选项,ESP-TLS 将仅用 PSK 验证服务器。 -
跳过服务器验证:该选项并不安全,仅供测试使用。在 ESP-TLS menuconfig 中启用 CONFIG_ESP_TLS_INSECURE 和 CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY 可启用该选项,此时,若未在
esp_tls_cfg_t结构体选择其他服务器验证选项,ESP-TLS 将默认跳过服务器验证。
2.1 全局 CA Store 和 普通方式(缓冲区配置 CA)的区别
1. 缓冲区配置(每次手动配置 CA)
做法:在每一次 TLS 连接时,把证书传给 esp_tls_cfg_t:
Cesp_tls_cfg_t cfg = {
.cacert_pem_buf = server_root_cert_pem_start,
.cacert_pem_bytes = server_root_cert_pem_end - server_root_cert_pem_start,
};
📌 特点:
- 每个连接都会单独从 PEM 解析证书(
mbedtls_x509_crt_parse())。 - 如果你在多个地方频繁创建 HTTPS 连接,会有 重复解析 + 内存开销。
- 灵活,可以针对不同服务器传不同的根 CA。
2. 全局 CA Store
做法:在程序启动时,先加载一次全局根证书:
Cesp_tls_set_global_ca_store(server_root_cert_pem_start,
server_root_cert_pem_end - server_root_cert_pem_start);
esp_tls_cfg_t cfg = {
.use_global_ca_store = true,
};
📌 特点:
- 证书只在第一次解析并存进 RAM,后续所有连接复用。
- 减少多次解析同一个证书的开销(CPU 和堆内存)。
- 适合你只使用一套根 CA 的情况。
3. 核心区别对比
| 对比点 | 普通方式:手动配置 CA | 全局 CA Store |
|---|---|---|
| 证书加载 | 每次连接时都加载/解析 PEM | 程序启动时加载一次 |
| 内存占用 | 每个连接可有独立副本 | 所有连接共享一份证书链 |
| CPU 开销 | 每次连接时都要 PEM → X.509 解析 | 只解析一次,后续直接复用 |
| 灵活性 | 可以为每个服务器配置不同 CA | 所有连接共用同一份全局 CA |
| 适用场景 | 多服务、多根证书环境 | 单一或少量固定根 CA,频繁新建连接 |
2.2 使用ESP x509 证书包
在mbedtls 配置中开启 CONFIG_MBEDTLS_CERTIFICATE_BUNDLE = 1 时,SDK 会把一份常见的 公共 CA 证书集合(根证书 bundle)编译到固件里。
- 当你调用
https_get_request_using_crt_bundle()时,实际上是使用esp_crt_bundle_attach自动加载那份全局内置根证书集合。
3. SNI(服务器名称指示)
SNI 是 TLS 协议的一个扩展,它能让客户端在 TLS 握手过程中,主动指定要连接的主机名。当服务器通过同一个 IP 地址托管多个域名时,客户端必须使用这一功能才能成功建立连接。
如何确保 SNI 正常工作:
- 使用 HTTPS 连接时,ESP-TLS 默认启用 SNI,无需额外配置。
- 若需手动指定 SNI 主机名,请使用
esp_tls_cfg_t中的common_name字段进行设置,确保在握手过程中向服务器发送正确的主机名。 common_name的值必须与服务器证书中的通用名称 (CN, Common Name) 完全匹配。- 需将
skip_common_name字段设置为false,确保服务器证书能通过主机名完成正确验证。这是 SNI 正常运行的必要条件。
4. ESP-TLS 服务器证书选择回调
使用 MbedTLS 协议栈时,ESP-TLS 组件支持设置服务器证书选择回调函数。此时,在服务器握手期间可选择使用哪个服务器证书,该回调可获取客户端发送的 “Client Hello” 消息中提供的 TLS 扩展(ALPN、SPI 等),并基于此选择传输哪个服务器证书给客户端。要启用此功能,请在 ESP-TLS menuconfig 中启用 CONFIG_ESP_TLS_SERVER_CERT_SELECT_HOOK。
证书选择回调可在结构体 esp_tls_cfg_t 中配置,具体如下:
int cert_selection_callback(mbedtls_ssl_context *ssl)
{
/* 回调应执行的代码 */
return 0;
}
esp_tls_cfg_t cfg = {
cert_select_cb = cert_section_callback,
};
5. 客户端会话票据
ESP-TLS 支持客户端会话恢复,可以在后续与同一服务器连接时避免完整的 TLS 握手,节省时间和资源。该功能在 ESP-TLS 使用 MbedTLS 作为底层协议栈时可用。
会话恢复机制在不同 TLS 版本中略有差异:
- TLS 1.2:通过会话 ID(由 TLS 协议栈内部管理)或会话票据(参见 RFC 5077)实现会话恢复。ESP-TLS 主要采用会话票据机制,从而显式控制应用程序。
- TLS 1.3:会话恢复完全依赖会话票据实现。服务器会在主握手完成后,通过 “NewSessionTicket” 消息发送票据。与 TLS 1.2 不同,这些票据可以在会话期间的任意时刻发送,无需在握手后立即完成。
要启用和使用客户端会话票据的步骤如下:
-
启用 Kconfig 选项 CONFIG_ESP_TLS_CLIENT_SESSION_TICKETS。
-
在成功建立 TLS 连接(并完成握手)后,使用
esp_tls_get_client_session()获取会话票据。- 对于 TLS 1.3:会话票据可能在握手后由服务器随时发送,因此应用程序应定期或在特定的应用层交互之后调用
esp_tls_get_client_session(),确保获取最新的票据。TLS 协议栈接收并处理的每个新票据都会覆盖之前的票据,用于后续的会话恢复。
- 对于 TLS 1.3:会话票据可能在握手后由服务器随时发送,因此应用程序应定期或在特定的应用层交互之后调用
-
将此会话票据安全地存储起来。
-
后续连接同一服务器时,将存储的会话票据填入
esp_tls_cfg_t::client_session字段。 -
在不再需要客户端会话或获取新会话前,应调用
esp_tls_free_client_session()释放客户端会话上下文。
#include "esp_tls.h"
// 全局或持久存储客户端会话
esp_tls_client_session_t *saved_session = NULL;
void connect_to_server(bool use_saved_session_arg) {
esp_tls_cfg_t cfg = {0}; // 按需初始化配置参数
// ... 设置其他 cfg 成员,如 cacert_buf, common_name 等
if (use_saved_session_arg && saved_session) {
cfg.client_session = saved_session;
// ESP_LOGI(TAG, "Attempting connection with saved session ticket.");
} else {
// ESP_LOGI(TAG, "Attempting connection without a saved session ticket (full handshake).");
}
esp_tls_t *tls = esp_tls_init();
if (!tls) {
// ESP_LOGE(TAG, "Failed to initialize ESP-TLS handle.");
return;
}
if (esp_tls_conn_http_new_sync("https://your-server.com", &cfg, tls) == 1) {
// ESP_LOGI(TAG, "Connection successful.");
// 每次连接成功后,都要尝试获取/更新最新的会话票据。
// 无论本次是新握手还是通过会话恢复连接,更新票据都是有益的。
// 特别是对 TLS 1.3 而言,服务器可能会在握手完成后下发新票据。
if (saved_session) {
esp_tls_free_client_session(saved_session); // 释放之前的票据
saved_session = NULL;
}
saved_session = esp_tls_get_client_session(tls);
if (saved_session) {
// ESP_LOGI(TAG, "Successfully retrieved/updated client session ticket.");
} else {
// ESP_LOGW(TAG, "Failed to get client session ticket even after a successful connection.");
}
// 执行 TLS 通信...
}
esp_tls_conn_destroy(tls);
}
备注
- 从服务器获取的会话票据通常有有效期,期限由服务器决定。
- 当尝试使用存储的票据进行连接时,如果服务器判定该票据无效(例如过期或被拒绝),ESP-TLS 会自动尝试执行完整的 TLS 握手来建立连接。在这种情况下,应用程序无需实现额外的重试逻辑。只有当会话恢复和后续的完整握手均失败时,才会报告连接失败。
- 当不再需要
esp_tls_client_session_t上下文时,或在获取并存储新的会话票据前,应调用esp_tls_free_client_session()释放会话。 - 对于 TLS 1.3,服务器在一次连接中可能多次发送新会话票据 NewSessionTicket。每次成功调用
esp_tls_get_client_session()都会返回由底层 TLS 协议栈处理的最新票据上下文。如果应用程序要使用最新的票据进行会话恢复,则需由应用程序负责管理并更新其存储的会话信息。
6. TLS 加密套件
ESP-TLS 支持在客户端模式下设置加密套件列表,TLS 密码套件列表用于向服务器传递所支持的密码套件信息,用户可以根据自己需求增减加密套件,且适用于任何 TLS 协议栈配置。如果服务器支持列表中的任一密码套件,则 TLS 连接成功,反之连接失败。
连接客户端时,在 esp_tls_cfg_t 结构体中设置 ciphersuites_list 的步骤如下:
/* 加密套件列表必须以 0 结尾,并且在整个 TLS 连接期间,加密套件的内存地址空间有效 */
static const int ciphersuites_list[] = {MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, MBEDTLS_TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, 0};
esp_tls_cfg_t cfg = {
.ciphersuites_list = ciphersuites_list,
};
ESP-TLS 不会检查 ciphersuites_list 的有效性,因此需调用 esp_tls_get_ciphersuites_list() 获取 TLS 协议栈中支持的加密套件列表,并检查设置的加密套件是否在支持的加密套件列表中。
备注:
- 此功能仅在 MbedTLS 协议栈中有效。
7. TLS 协议版本
ESP-TLS 能够为 TLS 连接设置相应的 TLS 协议版本,指定版本将用于建立专用 TLS 连接。也就是说,在运行时不同的 TLS 连接可以配置到 TLS 1.2、TLS 1.3 等不同协议版本。
备注
- 目前,仅在 MbedTLS 作为 ESP-TLS 的底层 SSL/TLS 协议栈时支持此功能。
要在 ESP-TLS 中设置 TLS 协议版本,请设置 esp_tls_cfg_t::tls_version,从 esp_tls_proto_ver_t 中选择所需版本。如未指定协议版本字段,将默认根据服务器要求建立 TLS 连接。
ESP-TLS 连接的协议版本可按如下方式配置:
#include "esp_tls.h"
esp_tls_cfg_t cfg = {
.tls_version = ESP_TLS_VER_TLS_1_2,
};
二、HTTPS_REQUEST例程
在TLS握手阶段,客户端( ESP32)会验证服务器证书,主要包括:
- 有效期:检查
Not Before和Not After字段- 当前时间必须在这个时间区间内,否则认为“尚未生效”或“已过期”。
- 签名:用根 CA 公钥验证服务器证书签名。
- 域名:证书中的 CN / SAN 必须与访问的主机名匹配。
这三步任何一步失败,TLS 都会拒绝连接。
1. 时间同步
在进行TLS连接时,需要同步系统时间,否则在证书验证阶段会验证失败。
通过如下指令可以查看当前服务器的CA根证书的有效期:
virtual-machine:~/https_request/main$ openssl x509 -in server_root_cert.pem -noout -dates
notBefore=Jun 4 11:04:38 2015 GMT
notAfter=Jun 4 11:04:38 2035 GMT
ESP32的系统时间不能超过这个证书的有效期,如果没有同步时间,则会报错:
“The certificate validity starts in the future”
导致证书验证失败。
因此,在TLS连接之前,需要先通过SNTP/NTP 同步系统时间。
⚠️ 取消证书到期检查
如果你不想同步时间,可以在配置中跳过证书有效期检查,之后TLS的握手时就不会检查证书有效性,后续的流程能够正常进行。
在ESP32的配置中,取消 MBEDTLS_HAVE_TIME_DATE 到期检查。
2. 证书
在HTTPS_REQUEST例程中,使用到server_root_cert.pem 根证书,该证书用来验证 www.howsmyssl.com 服务器的身份。
把该根CA证书和服务器证书,以及服务器的根CA证书进行对比:
可以看到, www.howsmyssl.com 服务器证书 由 中间证书颁发机构 Let’s Encrypt 签发,而中间证书R10 又由根证书颁发机构签发。最后侧的图示中,展示了完整的证书签发链。在本例程中的证书就是根CA证书,作为证书信任锚点,来验证整个证书链,确保ESP32 所连接的服务器身份正确。
3. 例程中的内容
1. 服务器验证设置
在本例程中,演示了多个配置情况:
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE && CONFIG_EXAMPLE_USING_ESP_TLS_MBEDTLS
https_get_request_using_crt_bundle(); // 使用证书包,不需要配置根证书。
#endif
ESP_LOGI(TAG, "Minimum free heap size: %" PRIu32 " bytes", esp_get_minimum_free_heap_size());
printf("mbedtls time: %lld\n", mbedtls_time(NULL));
https_get_request_using_cacert_buf(); // 缓冲区配置根证书
// https_get_request_using_global_ca_store(); // 把CA证书存放在全局存储区
// https_get_request_using_specified_ciphersuites(); // 配置加密套件
2.客户端使用会话Ticket的示例
#ifdef CONFIG_EXAMPLE_CLIENT_SESSION_TICKETS
static void https_get_request_to_local_server(const char* url)
{
ESP_LOGI(TAG, "https_request to local server");
esp_tls_cfg_t cfg = {
.cacert_buf = (const unsigned char *) local_server_cert_pem_start,
.cacert_bytes = local_server_cert_pem_end - local_server_cert_pem_start,
.skip_common_name = true,
};
save_client_session = true;
https_get_request(cfg, url, LOCAL_SRV_REQUEST);
}
static void https_get_request_using_already_saved_session(const char *url)
{
ESP_LOGI(TAG, "https_request using saved client session");
esp_tls_cfg_t cfg = {
.client_session = tls_client_session,
};
https_get_request(cfg, url, LOCAL_SRV_REQUEST);
esp_tls_free_client_session(tls_client_session);
save_client_session = false;
tls_client_session = NULL;
}
#endif
三、文档来源
更多推荐




所有评论(0)