故障定位与升级 / 有验证的HTTPS诊断发布 内容更新

ESP32 HTTPS 失败:时间、证书、域名和内存按顺序排查

完整IDF客户端保留证书和名称验证,以私有CA和真实本机TLS端点区分正确、错CA、错域名与有效期异常;私钥留在包外,板上无线、SNTP和TLS结果单列待测。

读完能做什么
得到保持CA、名称和有效期验证的单次HTTPS客户端,以及能实际执行的受控证书/本机诊断矩阵。
开始之前
先读所列前置,能使用固定IDF环境、保存同次源码/ELF/配置与原log;本文下载README含完整命令与恢复步骤。
所需器材
ESP32-S3-DevKitC-1 v1.1 / ESP32-S3-WROOM-1-N8R8、USB数据线与电脑、ESP-IDF5.5.5 / 固定Xtensa工具;本例不接外部负载、自有2.4GHz网络、NTP、LAN电脑;Python3.12/cryptography50.0.1/OpenSSL3.6.3
版本组合
固件基线:ESP-IDF5.5.5 / b774170ff46c393eeb5e495ea37936038d3f4f4f目标:DevKitC-1 v1.1 / WROOM-1-N8R8
适用范围

固定S3-DevKitC-1 v1.1 N8R8/IDF5.5.5,Flash8MiB/DIO80,不启用PSRAM;自己控制的LAN端点、2.4GHz接入点和NTP。默认配置关闭网络,电脑TLS夹具不等于板上TLS。

这篇验证到了哪一步

资料核对已通过
docs/esp32-evidence/maintenance-diagnostics-sources.json;29份固定SDK/子模块来源鲜取且与安装字节一致,具体阅读范围另列。验证日期:
构建 / 配置已通过
docs/esp32-evidence/maintenance-diagnostics-final-builds.json;本例两最终配置实际Exit0,输入快照、配置、app/ELF/map及日志SHA保留。对象:ESP32-S3 / N8R8计划版本:ESP-IDF5.5.5验证日期:
仿真不适用
仅执行适用主机逻辑、离线符号或电脑TLS;没有运行ESP32 panic/无线/TLS仿真。
本站实板待验证
没有本例实体烧录/运行/测量/恢复结果;observations.csv只有表头。

构建通过只说明指定环境中的代码或配置通过检查;实物功能、功耗和稳定性以对应的实板记录为准。

01 / 有验证的HTTPS诊断

先固定任务、器材与本轮证据范围

原计划R04不以insecure绕过报错。沿N03的HTTP客户端和N05的时间状态继续:先有链路,再取得同步结果和UTC,再验证信任链、DNS身份与有效期,最后区分TLS错误和HTTP应用状态。

本例生成自己的实验CA与SAN叶证书,私钥只在电脑私有目录;固件只嵌公开根。URL用自己的LAN IP路由,TLS_NAME保持esp32-lab.test并由common_name传给SNI/校名;它不自动修改HTTP Host,本例单站/health不依赖虚拟Host。

默认和网络链接两个最终app实际为190512B/888272B,均构建Exit0。真实电脑loopback TLS三场景和OpenSSL verify五场景通过;不是ESP32 Wi-Fi、SNTP、mbedTLS、板上峰值内存或目标时延测量。所有实板字段保持空白。

参考:[1] 固定esp_http_client.h:common_name/timeout/TLS错误API[2] 固定HTTP客户端实现:网络操作timeout与错误传播[3] 固定mbedTLS适配:hostname/SNI与名称验证

02 / 有验证的HTTPS诊断

构建与运行基线

安装官方IDF5.5.5并在新终端激活 export.sh / EIM 对应环境。作者复用已安装SDK,没有修改SDK源码。POSIX终端进入本例解压目录;PowerShell用等效变量/换行,不能直接照搬export语法。构建目录使用绝对路径,避免把对象写入网站仓库。

同一build保留app.bin、未剥离ELF、map、sdkconfig、dependencies.lock、源文件SHA和完整串口日志,不能仅保存一张panic截图或只下载来历不明bin。idf.py monitor退出Ctrl+]。若误刷或实验卡住,先停止输入、按RESET;必要时在BOOT配合RESET进入ROM下载模式并刷回已备份且分区匹配的正常固件。不要为排查修改eFuse或盲目erase_flash,NVS和其他分区可能有数据。

代码 / 配置
# POSIX shell · 配合本节前提逐项执行
export LAB_PROJECT="$PWD"
export LAB_BUILD="$HOME/esp32-labs/r04-safe"
python "$IDF_PATH/tools/idf.py" -C "$LAB_PROJECT" -B "$LAB_BUILD" -D SDKCONFIG="$LAB_BUILD/sdkconfig" build
# 确认板型、串口和备份后才自行执行;本文没有执行烧录。
python "$IDF_PATH/tools/idf.py" -C "$LAB_PROJECT" -B "$LAB_BUILD" -p YOUR_SERIAL_PORT flash monitor

参考:[1] 固定esp_http_client.h:common_name/timeout/TLS错误API[2] 固定HTTP客户端实现:网络操作timeout与错误传播[3] 固定mbedTLS适配:hostname/SNI与名称验证

03 / 有验证的HTTPS诊断

默认关闭与实验范围

默认SSID、URL、TLS_NAME为空,启动打印CONFIG_DISABLED,既不连接Wi-Fi也不请求HTTPS。config/network-link.defaults仅用文档网段192.0.2.1和假SSID做实际链接构建,不是联网通过记录。私有配置参考sdkconfig.private.example,不打印密码,不把它加入包/仓库。

本例自定义partitions.csv的factory槽为3MiB(offset0x10000),无OTA;Flash8MiB不代表app可无限增长。刷分区前先按E09备份,旧分区数据不能靠同名假设兼容。

本例只请求自己控制的私有LAN HTTPS端点;电脑本机夹具默认127.0.0.1,若ESP要访问,需显式绑定自己LAN接口并检查防火墙/网络隔离,不暴露到公网。URL中的IP用于路由,TLS_NAME固定为证书SAN中的DNS身份esp32-lab.test;common_name传入mbedTLS hostname,既用于SNI也用于名称验证。HTTP默认Host来自URL的IP,本例单站/health不依赖虚拟主机Host;需要虚拟站点时按服务端要求另行配置并验证,不把TLS名称配置误当HTTP Host已自动改写。

证书时间失败应先修UTC和同步链路;改时区只改变显示,不能修证书有效期。SNTP并非认证时间,本例2025≤UTC<2100只是粗门限,最终仍由证书notBefore/notAfter/签名/用途/名称验证作判定。不能把处于这个范围的任意年份称为正确时间。

参考:[1] 固定esp_http_client.h:common_name/timeout/TLS错误API[2] 固定HTTP客户端实现:网络操作timeout与错误传播[3] 固定mbedTLS适配:hostname/SNI与名称验证

04 / 有验证的HTTPS诊断

生成自己的受控证书,私钥留在包外

Python3.12、cryptography50.0.1;依赖写在tools/requirements.txt。为主机工具单独venv,不能覆盖IDF解释器。示例验证使用OpenSSL3.6.3,/usr/bin/openssl若显示LibreSSL不是本文同版CLI。证书按执行时UTC生成,根365天、叶30天、DNS SAN+serverAuth,使用RSA2048/SHA256;该配方只用于隔离实验,不是公共CA或生产证书管理系统。

输出目录必须全新,已存在拒绝;身份必须为小写ASCII DNS名,IP/非法标签在创建目录前拒绝。私有目录0700、三个私钥0600且不打印;Windows需另核ACL,POSIX mode不能代替Windows权限验收。下载中certs/lab_ca.pem只是作者本轮公开测试根,不含私钥。读者必须重生成自己的根/叶、复制公开根再构建;不能指望作者根为自己的服务签名。叶过期重签并部署,根轮换时客户端根和服务链也要协调;不要把过期样例或某年系统CA列表永久硬编码。

代码 / 配置
# POSIX shell · 配合本节前提逐项执行
python3.12 -m venv "$HOME/esp32-labs/r04-python"
export LAB_PY="$HOME/esp32-labs/r04-python/bin/python"
"$LAB_PY" -m pip install -r tools/requirements.txt
export LAB_PRIVATE="$HOME/esp32-labs/r04-private-$(date +%s)"
"$LAB_PY" tools/generate_lab.py --out "$LAB_PRIVATE" --name esp32-lab.test
cp "$LAB_PRIVATE/lab_ca.pem" certs/lab_ca.pem
# 只复制公开根证书,然后重新build;不复制server.key/lab_ca.key/wrong_ca.key。
"$LAB_PY" tools/fixture_tls.py --lab "$LAB_PRIVATE" --openssl /ABS/PATH/to/openssl3 --out /tmp/r04-fixture-NEW

参考:[1] 固定esp_http_client.h:common_name/timeout/TLS错误API[2] 固定HTTP客户端实现:网络操作timeout与错误传播[3] 固定mbedTLS适配:hostname/SNI与名称验证

05 / 有验证的HTTPS诊断

实际本机TLS夹具

夹具启动真实loopback HTTPS服务、限定超时和响应大小,依次执行正确CA+正确DNS(200)、错误CA拒绝、错误DNS拒绝;再用OpenSSL verify的-attime测试正确/错误CA/错误名/早于有效期/晚于有效期五项。它只向验证命令传epoch,不改系统时间。原始命令、返回码、公开证书SHA和服务log保留。PythonSSL/OpenSSL的错误码不等于ESP-IDF mbedTLS错误码,不能逐数值照抄到板上。

作者本轮8项实际通过,说明电脑上的受控TLS与X509条件能区分;没有验证ESP32射频、SNTP、mbedTLS堆、板上TLS或真实LAN。主机5项同源粗时钟门限和4项非法DNS CLI拒绝另计,不冒充握手。

参考:[1] 固定esp_http_client.h:common_name/timeout/TLS错误API[2] 固定HTTP客户端实现:网络操作timeout与错误传播[3] 固定mbedTLS适配:hostname/SNI与名称验证

06 / 有验证的HTTPS诊断

在自己的LAN启动端点并配置设备

URL必须https://;Wi-Fi仅一次10s应用连接窗口,断开标志与IP位一起判定。获得链路后创建SNTP信号量并显式sync_wait10s;该API等待与“init自动阻塞”不同。超时/粗UTC不合格停止HTTPS,修复后RESET,不降级HTTP或关闭验证。NVS初始化失败不擦除;Wi-Fi配置存RAM,事件组/驱动在程序结束后保留至RESET,避免回调引用已释放对象,也因此after_client_cleanup并不代表所有Wi-Fi内存已归还。

代码 / 配置
# POSIX shell · 配合本节前提逐项执行
"$LAB_PY" tools/tls_server.py --cert "$LAB_PRIVATE/server_cert.pem" --key "$LAB_PRIVATE/server.key" --bind YOUR_OWN_LAN_IP --port 8443
# 另一个已激活IDF的终端,进入本工程;文件中填自己的SSID/密码/LAN IP/DNS身份/NTP。
cp sdkconfig.private.example sdkconfig.private
export LAB_TLS_BUILD="$HOME/esp32-labs/r04-private-build"
python "$IDF_PATH/tools/idf.py" -C "$LAB_PROJECT" -B "$LAB_TLS_BUILD" -D SDKCONFIG="$LAB_TLS_BUILD/sdkconfig" -D 'SDKCONFIG_DEFAULTS=sdkconfig.defaults;sdkconfig.private' build
# 确认公开根来自同一私有lab后,自行flash/monitor该目录。

参考:[1] 固定esp_http_client.h:common_name/timeout/TLS错误API[2] 固定HTTP客户端实现:网络操作timeout与错误传播[3] 固定mbedTLS适配:hostname/SNI与名称验证

07 / 有验证的HTTPS诊断

单次HTTPS日志怎么读

先取internal_free/internal_largest/general_free,域有重叠不能相加。然后init/perform/取TLS错误与verify_flags/cleanup/再取快照。body最多打印前256B,总字节计数饱和防溢出;不记录私密业务内容,本地服务只返回固定夹具文本。日志tls_state表示get_and_clear_last_tls_error的读取返回状态,不是握手状态;HTTPS result才是perform返回码,tls_error/verify_flags是保存的原始TLS线索。HTTP缓冲各1024不等于TLS峰值内存仅2048B。5s是网络操作timeout,不是整个perform端到端5s保证,慢分块仍可更久;本例未实现全程总deadline。

disable_auto_redirect=true,避免自动跳到未审目标;本例单次请求后END,不是长期重连下载器。每次失败修一项并RESET重试,填写observations.csv的实测项。时间/证书状态与内存峰值要来自同次板上日志,不能用本机OpenSSL结果代填。面向普通互联网域名时优先用DNS URL和受维护的SDK证书bundle/更新流程,核对应版本与服务链;本实验私有根不覆盖互联网证书,不提供insecure fallback。

阶段需要保留的线索修复方向
LINK_NOT_READY无线/路由/IP窗口先网络,不把它叫证书错
TIME_NOT_READYsync返回码、UTC秒修NTP/UTC,不调时区糊过去
TLS名称/信任/有效期失败保存tls_error、verify_flags、实际SAN/链与当前UTC逐项修DNS身份、完整链、根和日期;保留verify
init/握手内存不足同caps的free/largest、配置、请求前后及峰值采集方案回R03核连续内部RAM;不盲删验证/换全局malloc
perform返回OK但HTTP404TLS成功与应用状态分开检查path/Host/服务,不能当TLS失败

参考:[1] 固定esp_http_client.h:common_name/timeout/TLS错误API[2] 固定HTTP客户端实现:网络操作timeout与错误传播[3] 固定mbedTLS适配:hostname/SNI与名称验证

08 / 有验证的HTTPS诊断

文件、许可与未验证项

应用/工具MIT;Pythoncryptography、OpenSSL、IDF按上游许可安装,不分发私钥/SDK/第三方二进制。空observations.csv保留实际UTC/SAN/错误/内存与恢复栏;没有板、网络或测流结果。固定依据:IDF5.5.5 esp_http_client.h/c、esp_tls_mbedtls.c、esp_tls_errors.h、esp_netif_sntp.h/c;cryptography50.0.1 X509 API与OpenSSL3.6 verify官方手册。不同版本错误位含义先查本版源码,禁止用“连接成功”省略身份与信任验证。

参考:[1] 固定esp_http_client.h:common_name/timeout/TLS错误API[2] 固定HTTP客户端实现:网络操作timeout与错误传播[3] 固定mbedTLS适配:hostname/SNI与名称验证

09 / 有验证的HTTPS诊断

完整客户端:同步前置、验证、错误日志与清理

下列是下载包中的完整同名文件;其余CMake、配置、锁、工具和空表在独立工程内,按README的相同版本构建。

代码 / 配置
// ---- main/main.c ----
/* SPDX-License-Identifier: MIT */
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "esp_wifi.h"
#include "esp_netif.h"
#include "esp_event.h"
#include "esp_netif_sntp.h"
#include "esp_http_client.h"
#include "esp_heap_caps.h"
#include "esp_timer.h"
#include "nvs_flash.h"
#include "tls_policy.h"
extern const char ca_start[] asm("_binary_lab_ca_pem_start");
static EventGroupHandle_t flags;
static const int IP_OK=1,LINK_FAIL=2;
static void network_event(void *unused,esp_event_base_t base,int32_t id,void *data){
 (void)unused;(void)data;
 if(base==WIFI_EVENT&&id==WIFI_EVENT_STA_START){esp_err_t e=esp_wifi_connect();if(e!=ESP_OK)xEventGroupSetBits(flags,LINK_FAIL);}
 else if(base==WIFI_EVENT&&id==WIFI_EVENT_STA_DISCONNECTED)xEventGroupSetBits(flags,LINK_FAIL);
 else if(base==IP_EVENT&&id==IP_EVENT_STA_GOT_IP)xEventGroupSetBits(flags,IP_OK);
}
typedef struct {size_t bytes,shown;} Body;
static esp_err_t http_event(esp_http_client_event_t *e){
 Body *b=e->user_data;
 if(e->event_id==HTTP_EVENT_ON_DATA&&e->data_len>0){
  size_t n=(size_t)e->data_len;if(n<=SIZE_MAX-b->bytes)b->bytes+=n;else b->bytes=SIZE_MAX;
  size_t show=256-b->shown;if(show>n)show=n;
  if(show){printf("BODY_PREFIX %.*s\n",(int)show,(const char*)e->data);b->shown+=show;}
 }
 return ESP_OK;
}
static void memory(const char *where){printf("MEM %s internal_free=%zu internal_largest=%zu general_free=%zu (overlapping caps, not a sum)\n",where,heap_caps_get_free_size(MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT),heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT),heap_caps_get_free_size(MALLOC_CAP_8BIT));}
void app_main(void){
 if(!CONFIG_HX_SSID[0]||!CONFIG_HX_HTTPS_URL[0]||!CONFIG_HX_TLS_NAME[0]){puts("CONFIG_DISABLED: no WiFi/TLS request; generate own lab CA and configure private LAN first");return;}
 if(strlen(CONFIG_HX_SSID)>31||strlen(CONFIG_HX_PASSWORD)>63||strncmp(CONFIG_HX_HTTPS_URL,"https://",8)||strstr(CONFIG_HX_SSID,"REPLACE")||strstr(CONFIG_HX_HTTPS_URL,"REPLACE")||strstr(CONFIG_HX_TLS_NAME,"REPLACE")){puts("CONFIG_INVALID: no request, credentials not printed");return;}
 esp_err_t e=nvs_flash_init();if(e!=ESP_OK){printf("NVS_INIT=%s; no erase\n",esp_err_to_name(e));return;}
 flags=xEventGroupCreate();if(!flags){puts("NO_MEMORY_EVENT_GROUP");return;}
 ESP_ERROR_CHECK(esp_netif_init());ESP_ERROR_CHECK(esp_event_loop_create_default());esp_netif_create_default_wifi_sta();
 ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT,ESP_EVENT_ANY_ID,network_event,NULL));ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT,IP_EVENT_STA_GOT_IP,network_event,NULL));
 wifi_init_config_t w=WIFI_INIT_CONFIG_DEFAULT();ESP_ERROR_CHECK(esp_wifi_init(&w));ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));wifi_config_t c={0};strlcpy((char*)c.sta.ssid,CONFIG_HX_SSID,sizeof c.sta.ssid);strlcpy((char*)c.sta.password,CONFIG_HX_PASSWORD,sizeof c.sta.password);ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA,&c));ESP_ERROR_CHECK(esp_wifi_start());
 EventBits_t bits=xEventGroupWaitBits(flags,IP_OK|LINK_FAIL,pdFALSE,pdFALSE,pdMS_TO_TICKS(10000));
 if(!(bits&IP_OK)||(bits&LINK_FAIL)){puts("LINK_NOT_READY: no TLS request; driver remains initialized, RESET after repair");return;}
 esp_sntp_config_t ntp=ESP_NETIF_SNTP_DEFAULT_CONFIG(CONFIG_HX_NTP_SERVER);ntp.wait_for_sync=true;
 ESP_ERROR_CHECK(esp_netif_sntp_init(&ntp));e=esp_netif_sntp_sync_wait(pdMS_TO_TICKS(10000));time_t utc=time(NULL);bool time_ok=tls_clock_ok(e==ESP_OK,(int64_t)utc);printf("CLOCK sync=%s utc_s=%"PRId64" plausible_and_synced=%u (not authenticated time)\n",esp_err_to_name(e),(int64_t)utc,time_ok);esp_netif_sntp_deinit();
 if(!time_ok){puts("TIME_NOT_READY: keep certificate verification, no HTTPS attempt; RESET after time/network repair");return;}
 Body body={0};esp_http_client_config_t h={.url=CONFIG_HX_HTTPS_URL,.common_name=CONFIG_HX_TLS_NAME,.cert_pem=ca_start,.skip_cert_common_name_check=false,.disable_auto_redirect=true,.timeout_ms=5000,.buffer_size=1024,.buffer_size_tx=1024,.event_handler=http_event,.user_data=&body};
 memory("before_client");esp_http_client_handle_t client=esp_http_client_init(&h);if(!client){puts("HTTP_CLIENT_INIT_FAILED");return;}
 int64_t start=esp_timer_get_time();e=esp_http_client_perform(client);int tls_error=0,verify_flags=0;esp_err_t saved=esp_http_client_get_and_clear_last_tls_error(client,&tls_error,&verify_flags);
 printf("HTTPS result=%s status=%d body_bytes=%zu elapsed_us=%"PRId64" tls_state=%s tls_error=%d verify_flags=0x%08x\n",esp_err_to_name(e),esp_http_client_get_status_code(client),body.bytes,esp_timer_get_time()-start,esp_err_to_name(saved),tls_error,(unsigned)verify_flags);
 ESP_ERROR_CHECK(esp_http_client_cleanup(client));memory("after_client_cleanup");puts("END single request; WiFi/event group remain initialized for inspection until RESET. No periodic retry or insecure fallback.");
}

参考:[1] 固定esp_http_client.h:common_name/timeout/TLS错误API[2] 固定HTTP客户端实现:网络操作timeout与错误传播[3] 固定mbedTLS适配:hostname/SNI与名称验证

10 / 有验证的HTTPS诊断

同源粗UTC前置:不能代替证书有效期

下列是下载包中的完整同名文件;其余CMake、配置、锁、工具和空表在独立工程内,按README的相同版本构建。

代码 / 配置
// ---- main/tls_policy.h ----
/* SPDX-License-Identifier: MIT */
#pragma once
#include <stdint.h>
#include <stdbool.h>
static inline bool tls_clock_ok(bool synced,int64_t utc){return synced&&utc>=1735689600LL&&utc<4102444800LL;}

参考:[1] 固定esp_http_client.h:common_name/timeout/TLS错误API[2] 固定HTTP客户端实现:网络操作timeout与错误传播[3] 固定mbedTLS适配:hostname/SNI与名称验证

11 / 有验证的HTTPS诊断

完整证书配方:只把公开根交给固件

下列是下载包中的完整同名文件;其余CMake、配置、锁、工具和空表在独立工程内,按README的相同版本构建。

代码 / 配置
# ---- tools/generate_lab.py ----
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
"""Generate a new private lab, never overwrite. Only lab_ca.pem belongs in firmware."""
import argparse,datetime,ipaddress,json,os,pathlib,hashlib,re
from cryptography import x509
from cryptography.x509.oid import NameOID,ExtendedKeyUsageOID
from cryptography.hazmat.primitives import hashes,serialization
from cryptography.hazmat.primitives.asymmetric import rsa
p=argparse.ArgumentParser();p.add_argument('--out',required=True);p.add_argument('--name',default='esp32-lab.test');a=p.parse_args();out=pathlib.Path(a.out)
label=r'[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?'
if len(a.name)>253 or not re.fullmatch(label+r'(?:\.'+label+r')*',a.name):p.error('valid lowercase ASCII DNS identity required')
try:ipaddress.ip_address(a.name)
except ValueError:pass
else:p.error('DNS identity required, not a numeric IP; IP routing URL is configured separately')
out.mkdir(mode=0o700,parents=True,exist_ok=False)
now=datetime.datetime.now(datetime.timezone.utc);start=now-datetime.timedelta(minutes=5)
def keyfile(name,key):
 f=out/name;f.write_bytes(key.private_bytes(serialization.Encoding.PEM,serialization.PrivateFormat.PKCS8,serialization.NoEncryption()));os.chmod(f,0o600)
def ca(label):
 key=rsa.generate_private_key(public_exponent=65537,key_size=2048);subject=x509.Name([x509.NameAttribute(NameOID.COMMON_NAME,label)])
 cert=x509.CertificateBuilder().subject_name(subject).issuer_name(subject).public_key(key.public_key()).serial_number(x509.random_serial_number()).not_valid_before(start).not_valid_after(now+datetime.timedelta(days=365)).add_extension(x509.BasicConstraints(ca=True,path_length=0),True).add_extension(x509.KeyUsage(True,False,False,False,False,True,True,False,False),True).add_extension(x509.SubjectKeyIdentifier.from_public_key(key.public_key()),False).sign(key,hashes.SHA256());return key,cert
root,cert=ca('Hixinwu generated local lab CA');wrong,wrong_cert=ca('Different local lab CA');server=rsa.generate_private_key(public_exponent=65537,key_size=2048);subject=x509.Name([x509.NameAttribute(NameOID.COMMON_NAME,a.name)])
leaf=x509.CertificateBuilder().subject_name(subject).issuer_name(cert.subject).public_key(server.public_key()).serial_number(x509.random_serial_number()).not_valid_before(start).not_valid_after(now+datetime.timedelta(days=30)).add_extension(x509.BasicConstraints(ca=False,path_length=None),True).add_extension(x509.SubjectAlternativeName([x509.DNSName(a.name)]),False).add_extension(x509.ExtendedKeyUsage([ExtendedKeyUsageOID.SERVER_AUTH]),False).add_extension(x509.KeyUsage(True,False,True,False,False,False,False,False,False),True).add_extension(x509.AuthorityKeyIdentifier.from_issuer_public_key(root.public_key()),False).sign(root,hashes.SHA256())
for name,obj in [('lab_ca.pem',cert),('wrong_ca.pem',wrong_cert),('server_cert.pem',leaf)]: (out/name).write_bytes(obj.public_bytes(serialization.Encoding.PEM))
keyfile('lab_ca.key',root);keyfile('server.key',server);keyfile('wrong_ca.key',wrong)
manifest={'generatedAt':now.isoformat(),'identity':a.name,'leafNotBefore':leaf.not_valid_before_utc.isoformat(),'leafNotAfter':leaf.not_valid_after_utc.isoformat(),'currentEpoch':int(now.timestamp()),'pastEpoch':int((start-datetime.timedelta(days=1)).timestamp()),'futureEpoch':int((now+datetime.timedelta(days=400)).timestamp()),'publicCertificates':{name:hashlib.sha256((out/name).read_bytes()).hexdigest() for name in ['lab_ca.pem','wrong_ca.pem','server_cert.pem']},'scope':'Generated test CA, not a system trust store. Private keys stay only in this private directory; do not put them in firmware, repo or ZIP.'}
(out/'public-manifest.json').write_text(json.dumps(manifest,indent=2)+'\n');print('Generated private lab at',out,'identity',a.name,'; copy ONLY lab_ca.pem into certs then rebuild. No private key printed.')

参考:[1] 固定esp_http_client.h:common_name/timeout/TLS错误API[2] 固定HTTP客户端实现:网络操作timeout与错误传播[3] 固定mbedTLS适配:hostname/SNI与名称验证

BACK TO THE SOURCE

资料来源与版本说明

保持原计划ID/slug/阶段和用途。固定上游与实际构建、适用主机/QEMU验证留证;没有把本机结果升级为实体运行结论。资料核对日期:。资料核对、程序构建与实板测试分别记录,不互相替代。

  1. 固定esp_http_client.h:common_name/timeout/TLS错误API
  2. 固定HTTP客户端实现:网络操作timeout与错误传播
  3. 固定mbedTLS适配:hostname/SNI与名称验证
  4. 固定SNTP配置:wait_for_sync与显式等待API
  5. 固定SNTP实现:信号量创建及等待
  6. 固定esp_tls_errors.h:本版TLS错误分类
  7. OpenSSL3.6官方verify:CAfile/verify_hostname/attime
  8. cryptography50.0.1官方X509 API与UTC证书日期
  9. IDF5.5.5 HTTP Client指南:HTTPS验证