From 551a7bb1e657c12f45ac0a1f2ca851625723a63d Mon Sep 17 00:00:00 2001 From: chenjintao Date: Wed, 16 Sep 2026 15:32:22 +0800 Subject: [PATCH] Prefer X25519 for legacy WSS full handshakes Prioritize X25519 while retaining enabled curve fallbacks and log the negotiated ECDHE group. Extend ABI checks, crypto benchmarks and backend group probes, and document the updated APP package. --- artifacts/wss/README.md | 6 +- docs/talkingq_wss.md | 20 ++++-- .../lib/librws/src/rws_socketpriv.c | 30 ++++++++ tools/check_mbedtls_abi.py | 10 +-- tools/probe_wss_groups.py | 40 +++++++++++ tools/test_mbedtls_cert.c | 72 ++++++++++++++++++- tools/test_mbedtls_cert.py | 6 +- tools/test_wss_handshake.py | 19 ++--- tools/verify_wss_package.py | 8 ++- tools/wss_tls_host_config.h | 4 ++ 10 files changed, 189 insertions(+), 26 deletions(-) create mode 100644 tools/probe_wss_groups.py diff --git a/artifacts/wss/README.md b/artifacts/wss/README.md index b156324..3c29e36 100644 --- a/artifacts/wss/README.md +++ b/artifacts/wss/README.md @@ -1,9 +1,9 @@ # 旧板 WSS 联调包 -最新优化包为 `tls-speed/EG800AKCN_11LG-legacy-wss-fast.zip`,供无 GNSS 旧板联调,默认连接 `wss://banban.api.talkingq.com:443/ws`。默认音量仍为 100(已有保存值按原逻辑读取)。这是包含 NIST 运算加速内核和握手计时 APP 的完整多分区包;本次必须更新内核与 APP。 +最新优化包为 `full-handshake/EG800AKCN_11LG-legacy-wss-x25519.zip`,供无 GNSS 旧板联调,默认连接 `wss://banban.api.talkingq.com:443/ws`。默认音量仍为 100(已有保存值按原逻辑读取)。这是完整多分区包,沿用上一版 NIST 加速内核,APP 调整 WSS 的 ECDHE 曲线优先级为 X25519 优先,保留其他已启用曲线作为回退。每次仍执行完整握手,按键后建连。 -2026-09-16 14:20 板端已通过 TLS 校验与 HTTP 101,用户确认功能正常,但完整 TLS 握手约 9.6 秒。本次启用 mbedTLS NIST 快速模约减,并输出握手各阶段耗时。主机上同一证书链校验平均由 36.8 ms 降至 10.2 ms(各 20 次);不能直接换算板端耗时,优化包仍需上板计时。包校验与 SHA-256 见 `tls-speed/verification.json`。 +2026-09-16 14:38 板端已确认 NIST 加速后完整 TLS 握手降至 1820 ms,HTTP 101 正常。当前进一步优化:实际后台在 SDK 默认曲线顺序下选择 P-521,按新配置顺序选择 X25519;主机实际 SDK 每次新建 ECDH 平均 P-521 5.15 ms、X25519 1.55 ms。只代表运算基准,板端收益待验证。新增 `WSS TLS negotiated ECDHE group` 日志,预期值 9(X25519)。包校验与 SHA-256 见 `full-handshake/verification.json`。 -2026-09-16 11:32 复测:443 `/ws` 已返回 HTTP 101,TLS 和证书校验通过;8080 仍是明文 WS。`recv-fix/EG800AKCN_11LG-legacy-wss-recv.zip` 保留为已上板通过的回退版本。本目录原 `EG800AKCN_11LG-legacy-wss.zip` 缺少 ECDSA,`ecc-fix` 包存在接收误判,均仅用于历史对照。 +2026-09-16 11:32 复测:443 `/ws` 已返回 HTTP 101,TLS 和证书校验通过;8080 仍是明文 WS。`tls-speed/EG800AKCN_11LG-legacy-wss-fast.zip`(1820 ms)和 `recv-fix/EG800AKCN_11LG-legacy-wss-recv.zip` 均保留作已上板通过的回退版本。本目录原 `EG800AKCN_11LG-legacy-wss.zip` 缺少 ECDSA,`ecc-fix` 包存在接收误判,均仅用于历史对照。 原旧板包保留在 `../legacy-no-gnss/`。构建日志、探测 JSON、导出的公共证书和包体在此目录本地保存并被 Git 忽略。源码和详细步骤见 `../../docs/talkingq_wss.md`。 diff --git a/docs/talkingq_wss.md b/docs/talkingq_wss.md index 860808c..a5464b9 100644 --- a/docs/talkingq_wss.md +++ b/docs/talkingq_wss.md @@ -63,7 +63,19 @@ APP 通过 SDK 导出的 `mbedtls_ssl_handshake_step` 驱动同一 TLS 1.2 握 同一 Windows 主机上用实际 SDK crypto/X.509 源码验证相同证书链,各运行 20 次:关闭 NIST 优化平均 36.8 ms,开启后 10.2 ms,约 3.6 倍。两种配置均通过证书链、错误域名、不可信根、证书日期与 AES-GCM 检查。这是主机算法对比,不是板端总握手时间预测。可运行 `tools/test_mbedtls_cert.py --compare-nist` 复测;`tools/test_wss_handshake.py` 验证计时握手循环的状态推进、WANT、真实错误、超时与 tick 回绕。 -最新包是 `artifacts/wss/tls-speed/EG800AKCN_11LG-legacy-wss-fast.zip`,本次需要重新编译并烧录内核和 APP。`recv-fix` 包保留为已上板通过的回退版本。优化包仍需采集阶段日志确认实际收益;若主要耗时在网络等待,再评估会话恢复或提前连接,不能用主机基准推断网络影响。 +第一轮加速包是 `artifacts/wss/tls-speed/EG800AKCN_11LG-legacy-wss-fast.zip`,相对早期版本需更新内核和 APP。14:38 上板已确认 TLS 握手 1820 ms,其中证书校验 965 ms、服务器密钥交换 160 ms、客户端密钥交换 445 ms,HTTP 101 正常。 + +### 完整握手的曲线协商优化 + +用户要求每次完整握手且不提前建连。WSS 的 `ssl_conf.curve_list` 改为 X25519、P-256、P-384、P-521 优先,其他原已启用曲线继续作为回退;不启用会话恢复、不改变按键/休眠/提示音逻辑。SDK 没有向 APP 导出 `mbedtls_ssl_conf_curves`,因此采用其实现同样的配置字段赋值。ABI 检查扩展到该字段、握手参数及 ECDH 上下文,验证 ARMCC 内核与 GCC APP 的实际布局一致。协商日志直接读取已匹配的上下文,`WSS TLS negotiated ECDHE group: 9` 表示 X25519(P-256=3、P-384=4、P-521=5)。 + +`tools/probe_wss_groups.py` 用同一固件根 CA、TLS 1.2 和证书/域名校验进行独立完整握手,读取固件的曲线数组进行验证:实际后台在 SDK 默认顺序下选择 P-521,在新固件顺序下选择 X25519;密码套件均为 ECDHE-ECDSA-AES256-GCM-SHA384。证据见 `artifacts/wss/full-handshake/group-probe.json`。X25519 是标准的约 128 位安全强度密钥交换;P-521 的参数更大,单纯参数更大不等于更适合本设备。当前服务端叶证书是 P-256 ECDSA,证书链、有效期和域名校验保持必需。 + +主机实际 SDK 新建 ECDH(每组 20 次,重新生成临时密钥并计算共享密钥)平均:X25519 1.55 ms、P-256 1.90 ms、P-384 3.40 ms、P-521 5.15 ms;X25519 测试峰值额外堆 1144 字节,P-521 6376 字节(包含测试对端共享密钥校验的内存)。每次校验双方计算结果相同并检查内存释放。板端仍需实测,主要目标是降低客户端密钥交换开销,不能把基准比例套用到整个握手。 + +另对比 ECP window=4/5/6:证书校验约 10.35/10.20/10.45 ms,峰值分配约 11913/16009/18697 字节,收益不稳定,因此固件保留 window=4。可在 MSVC 开发者终端运行 `tools/test_mbedtls_cert.py --compare-windows` 复测。 + +最新包 `artifacts/wss/full-handshake/EG800AKCN_11LG-legacy-wss-x25519.zip` 沿用已验证的 NIST 内核,只更新 APP。当前工作区可 `build.bat app`、`build.bat firmware`;全新检出仍需先构建匹配的内核。旧 `tls-speed` 包保留,作为板端 1820 ms 的对照。 ## 根证书来源与验证 @@ -95,18 +107,18 @@ build.bat firmware 导出到下述路径后运行 `ql-cross-tool\win32\owtoolchain\Python\python.exe tools\verify_wss_package.py`,核对 ZIP CRC、APP/内核与当前构建的字节一致性、无 GNSS 入口以及内核 ECDSA/GCM/日期校验符号。SDK 有既有编译警告,不能仅凭外层批处理退出码认定成功。 -最新优化包位于 `artifacts/wss/tls-speed/EG800AKCN_11LG-legacy-wss-fast.zip`,标准输出为 `target/EG800AKCN_11LG/EG800AKCN_11LG.zip`,两者字节一致。这是完整多分区烧录包。本次新增内核 NIST 加速,需依次 `build.bat kernel`、`build.bat app`、`build.bat firmware`,不能仅更新 APP。运行 `ql-cross-tool\win32\owtoolchain\Python\python.exe tools\verify_wss_package.py` 校验默认最新包;也可显式传入该 ZIP 路径。 +最新优化包位于 `artifacts/wss/full-handshake/EG800AKCN_11LG-legacy-wss-x25519.zip`,标准输出为 `target/EG800AKCN_11LG/EG800AKCN_11LG.zip`,两者字节一致。这是完整多分区烧录包,内核与上一版 NIST 加速包字节一致。运行 `ql-cross-tool\win32\owtoolchain\Python\python.exe tools\verify_wss_package.py` 校验默认最新包;也可显式传入该 ZIP 路径。 `ecc-fix/EG800AKCN_11LG-legacy-wss-ecc.zip` 保留用于对照接收错误。原 `artifacts/wss/EG800AKCN_11LG-legacy-wss.zip` 存在缺失 ECDSA 的问题,请勿用于验证本次修复。旧板明文 WS 包仍保留在 `artifacts/legacy-no-gnss/`。 ## 上板验收 -443 `/ws` 已通过主机握手测试;ECC/RTC 和接收修复已在板端通过 TLS、HTTP 101,用户确认功能正常。本次 NIST 加速/计时包仍需上板检查: +443 `/ws` 已通过主机握手测试;ECC/RTC、接收修复和 NIST 加速已在板端通过 TLS、HTTP 101。本次 X25519 优先配置仍需上板检查: 1. 无 GNSS 旧板正常启动、蜂窝网络就绪、RTC 时间正确。 2. 先出现 `WSS root CA parsed successfully`,再出现 `WSS TLS handshake and certificate/hostname verification passed`。 3. 出现 `WebSocket upgrade verified: HTTP 101` 和 `ws connect okay`。 4. 服务器确认原设备鉴权成功,再测试收发音频、断网重连和休眠唤醒。 -5. 记录 `WSS TLS phase` 和 `WSS TLS handshake total`,同一网络环境多次对比原约 9.6 秒的 TLS 握手耗时。 +5. 确认 `WSS TLS negotiated ECDHE group: 9`;记录 `WSS TLS phase` 和 `WSS TLS handshake total`,同一网络环境多次对比上一版 1820 ms 的 TLS 握手耗时。 TLS 成功、WebSocket 成功、设备鉴权成功是三个不同阶段,不能只看其中一条日志就认定业务已跑通。 diff --git a/ql-application/threadx/interface/custom_app/lib/librws/src/rws_socketpriv.c b/ql-application/threadx/interface/custom_app/lib/librws/src/rws_socketpriv.c index 9159606..d8bb566 100644 --- a/ql-application/threadx/interface/custom_app/lib/librws/src/rws_socketpriv.c +++ b/ql-application/threadx/interface/custom_app/lib/librws/src/rws_socketpriv.c @@ -32,6 +32,7 @@ #ifdef LIBRWS_USING_MBED_TLS #include "mbedtls/base64.h" #include "mbedtls/sha1.h" +#include "mbedtls/ssl_internal.h" #endif @@ -1849,6 +1850,24 @@ void rws_debug(void *ctx, int level, const char *file, int line, const char *str LOG_I("%s", str ? str : NULL); } +/* Prefer a fast, widely supported full-handshake group on this MCU. + * Retain all groups enabled by the shared SDK profile as fallbacks. */ +static const mbedtls_ecp_group_id rws_tls_preferred_curves[] = { + MBEDTLS_ECP_DP_CURVE25519, + MBEDTLS_ECP_DP_SECP256R1, + MBEDTLS_ECP_DP_SECP384R1, + MBEDTLS_ECP_DP_SECP521R1, + MBEDTLS_ECP_DP_CURVE448, + MBEDTLS_ECP_DP_BP512R1, + MBEDTLS_ECP_DP_BP384R1, + MBEDTLS_ECP_DP_BP256R1, + MBEDTLS_ECP_DP_SECP224R1, + MBEDTLS_ECP_DP_SECP224K1, + MBEDTLS_ECP_DP_SECP192R1, + MBEDTLS_ECP_DP_SECP192K1, + MBEDTLS_ECP_DP_NONE +}; + static const char *rws_tls_phase_name(int state) { switch (state) { @@ -1875,6 +1894,13 @@ static int rws_tls_handshake_timed(mbedtls_ssl_context *ctx) break; if (ret != 0) ++retries; if (ctx->state != state) { + if (state == MBEDTLS_SSL_SERVER_KEY_EXCHANGE && ctx->handshake) { + mbedtls_ecp_group_id group = ctx->handshake->ecdh_ctx.grp_id; + LOG_I("WSS TLS negotiated ECDHE group: %d (X25519=%d, P256=%d, P384=%d, P521=%d)\n", + (int)group, (int)MBEDTLS_ECP_DP_CURVE25519, + (int)MBEDTLS_ECP_DP_SECP256R1, (int)MBEDTLS_ECP_DP_SECP384R1, + (int)MBEDTLS_ECP_DP_SECP521R1); + } LOG_I("WSS TLS phase %s (%d): elapsed=%u ms, calls=%u ms, retries=%u\n", rws_tls_phase_name(state), state, (unsigned int)(ql_rtos_get_systicks() - phase_started) * 5, @@ -1936,6 +1962,10 @@ int rws_ssl_conn(_rws_socket *s) ret = mbedtls_ssl_config_defaults(&ssl->ssl_conf, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT); if (ret != 0) goto exit; + /* Equivalent to mbedtls_ssl_conf_curves(): this SDK does not export its + setter to APP. The shared profile and ABI test cover curve_list. */ + ssl->ssl_conf.curve_list = rws_tls_preferred_curves; + LOG_I("WSS TLS full handshake: prefer X25519, P256, P384, P521\n"); mbedtls_ssl_conf_authmode(&ssl->ssl_conf, MBEDTLS_SSL_VERIFY_REQUIRED); mbedtls_ssl_conf_ca_chain(&ssl->ssl_conf, &ssl->cacert, NULL); mbedtls_ssl_conf_min_version(&ssl->ssl_conf, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3); diff --git a/tools/check_mbedtls_abi.py b/tools/check_mbedtls_abi.py index 1541c3a..8198d14 100644 --- a/tools/check_mbedtls_abi.py +++ b/tools/check_mbedtls_abi.py @@ -13,14 +13,16 @@ gcc = toolchain / 'gcc-arm-none-eabi/bin/arm-none-eabi-gcc.exe' objcopy = gcc.with_name('arm-none-eabi-objcopy.exe') armcc = toolchain / 'ARM_Compiler_5/bin/armcc.exe' types = ['ssl_context', 'ssl_config', 'net_context', 'x509_crt', 'pk_context', - 'entropy_context', 'ctr_drbg_context', 'ecp_group', 'ecp_point', 'mpi'] + 'entropy_context', 'ctr_drbg_context', 'ecp_group', 'ecp_point', 'mpi', + 'ssl_handshake_params', 'ecdh_context'] fields = {'ssl_context': ['conf', 'state', 'in_buf', 'out_buf', 'session', 'handshake', 'hostname'], - 'ssl_config': ['ca_chain', 'cert_profile', 'f_rng', 'p_rng'], - 'net_context': ['fd', 'cid'], 'x509_crt': ['pk', 'sig', 'next']} + 'ssl_config': ['ca_chain', 'cert_profile', 'f_rng', 'p_rng', 'curve_list'], + 'net_context': ['fd', 'cid'], 'x509_crt': ['pk', 'sig', 'next'], + 'ssl_handshake_params': ['ecdh_ctx'], 'ecdh_context': ['grp_id']} labels = ['sizeof(mbedtls_' + t + ')' for t in types] labels += ['offsetof(mbedtls_' + t + ', ' + f + ')' for t, fs in fields.items() for f in fs] source = output / 'tls_layout.c' -source.write_text('#include \n#include "mbedtls/ssl.h"\n' +source.write_text('#include \n#include "mbedtls/ssl_internal.h"\n' '#include "mbedtls/net_sockets.h"\n#include "mbedtls/entropy.h"\n' '#include "mbedtls/ctr_drbg.h"\n' 'const unsigned int talkingq_tls_layout[] = {\n' + ',\n'.join(labels) + '\n};\n') diff --git a/tools/probe_wss_groups.py b/tools/probe_wss_groups.py new file mode 100644 index 0000000..e6ca96c --- /dev/null +++ b/tools/probe_wss_groups.py @@ -0,0 +1,40 @@ +"""Probe TLS 1.2 full-handshake group negotiation with the firmware root CA.""" +import json +from pathlib import Path +import re +import subprocess + +root = Path(__file__).resolve().parents[1] +source = (root / 'ql-application/threadx/interface/custom_app/talkingq_root_ca.c').read_text() +pem = ''.join(json.loads('"' + value + '"') for value in re.findall(r'"(.*?)"', source) + if 'CERTIFICATE' in value or value.endswith(r'\n')) +output = root / 'artifacts/wss/full-handshake' +output.mkdir(parents=True, exist_ok=True) +ca = output / 'compiled-root.pem' +ca.write_text(pem) +openssl = Path('C:/Program Files/Git/usr/bin/openssl.exe') +groups = { + 'sdk_order': 'P-521:brainpoolP512r1:P-384:brainpoolP384r1:P-256:brainpoolP256r1:P-224:secp224k1:P-192:secp192k1:X25519:X448', + 'x25519_first': 'X25519:P-256:P-384:P-521', + 'p256_first': 'P-256:X25519:P-384:P-521', +} +firmware = (root / 'ql-application/threadx/interface/custom_app/lib/librws/src/rws_socketpriv.c').read_bytes().decode('latin1') +array = re.search(r'rws_tls_preferred_curves\[\]\s*=\s*\{(.*?)\}', firmware, re.S).group(1) +names = {'CURVE25519': 'X25519', 'CURVE448': 'X448', + 'SECP256R1': 'P-256', 'SECP384R1': 'P-384', 'SECP521R1': 'P-521', + 'SECP224R1': 'P-224', 'SECP192R1': 'P-192', + 'SECP224K1': 'secp224k1', 'SECP192K1': 'secp192k1', + 'BP512R1': 'brainpoolP512r1', 'BP384R1': 'brainpoolP384r1', 'BP256R1': 'brainpoolP256r1'} +groups['firmware_policy'] = ':'.join(names[name] for name in re.findall(r'MBEDTLS_ECP_DP_(\w+)', array) if name != 'NONE') +report = {} +for name, group_list in groups.items(): + result = subprocess.run([str(openssl), 's_client', '-connect', 'banban.api.talkingq.com:443', + '-servername', 'banban.api.talkingq.com', '-verify_hostname', 'banban.api.talkingq.com', + '-verify_return_error', '-CAfile', str(ca), '-no-CApath', '-no-CAstore', + '-tls1_2', '-no_ticket', '-groups', group_list, '-brief'], + input='', text=True, capture_output=True, timeout=20) + text = result.stdout + result.stderr + report[name] = {'groups': group_list, 'returncode': result.returncode, 'output': text} + print(name + ':\n' + text, flush=True) + assert result.returncode == 0 and 'Verification: OK' in text, name +(output / 'group-probe.json').write_text(json.dumps(report, indent=2)) diff --git a/tools/test_mbedtls_cert.c b/tools/test_mbedtls_cert.c index d1df778..58e9ab0 100644 --- a/tools/test_mbedtls_cert.c +++ b/tools/test_mbedtls_cert.c @@ -8,10 +8,26 @@ #include "mbedtls/platform_time.h" #include "mbedtls/gcm.h" #include "mbedtls/ssl_ciphersuites.h" +#include "mbedtls/ecdh.h" +#include "mbedtls/ctr_drbg.h" #include "talkingq_wss.h" -void *mbedtls_calloc(size_t n, size_t size) { return calloc(n, size); } -void mbedtls_free(void *p) { free(p); } +typedef union { size_t bytes; long double align; void *ptr; } alloc_header; +static size_t live_bytes, peak_bytes; +void *mbedtls_calloc(size_t n, size_t size) { + alloc_header *h; + size_t bytes; + if (size && n > (SIZE_MAX - sizeof(*h)) / size) return NULL; + bytes = n * size; + h = (alloc_header *)calloc(1, sizeof(*h) + bytes); + if (!h) return NULL; + h->bytes = bytes; live_bytes += bytes; + if (live_bytes > peak_bytes) peak_bytes = live_bytes; + return h + 1; +} +void mbedtls_free(void *p) { + if (p) { alloc_header *h = (alloc_header *)p - 1; live_bytes -= h->bytes; free(h); } +} static time_t test_time; static time_t read_test_time(time_t *p) { if (p) *p = test_time; return test_time; } mbedtls_time_t (*mbedtls_time)(mbedtls_time_t *) = read_test_time; @@ -26,6 +42,50 @@ int mbedtls_snprintf(char *buffer, size_t size, const char *format, ...) return result; } +/* Deterministic entropy is confined to this host benchmark, never firmware. */ +static int test_entropy(void *ctx, unsigned char *out, size_t n) +{ + size_t i; unsigned int *counter = (unsigned int *)ctx; + for (i = 0; i < n; ++i) out[i] = (unsigned char)(++*counter); + return 0; +} + +static void benchmark_ecdh(mbedtls_ecp_group_id id, const char *name) +{ + mbedtls_ecp_group peer, client; + mbedtls_ecp_point peer_q, client_q; + mbedtls_mpi peer_d, client_d, secret, expected; + mbedtls_ctr_drbg_context rng; + unsigned int counter = 0; + clock_t elapsed = 0, started; + size_t base; + int i; + mbedtls_ctr_drbg_init(&rng); + assert(mbedtls_ctr_drbg_seed(&rng, test_entropy, &counter, NULL, 0) == 0); + mbedtls_ecp_group_init(&peer); mbedtls_ecp_point_init(&peer_q); mbedtls_mpi_init(&peer_d); + assert(mbedtls_ecp_group_load(&peer, id) == 0); + assert(mbedtls_ecdh_gen_public(&peer, &peer_d, &peer_q, mbedtls_ctr_drbg_random, &rng) == 0); + base = live_bytes; peak_bytes = base; + for (i = 0; i < 20; ++i) { + mbedtls_ecp_group_init(&client); mbedtls_ecp_point_init(&client_q); + mbedtls_mpi_init(&client_d); mbedtls_mpi_init(&secret); mbedtls_mpi_init(&expected); + assert(mbedtls_ecp_group_load(&client, id) == 0); + started = clock(); + assert(mbedtls_ecdh_gen_public(&client, &client_d, &client_q, mbedtls_ctr_drbg_random, &rng) == 0); + assert(mbedtls_ecdh_compute_shared(&client, &secret, &peer_q, &client_d, mbedtls_ctr_drbg_random, &rng) == 0); + elapsed += clock() - started; + assert(mbedtls_ecdh_compute_shared(&peer, &expected, &client_q, &peer_d, mbedtls_ctr_drbg_random, &rng) == 0); + assert(mbedtls_mpi_cmp_mpi(&secret, &expected) == 0); + mbedtls_mpi_free(&expected); mbedtls_mpi_free(&secret); mbedtls_mpi_free(&client_d); + mbedtls_ecp_point_free(&client_q); mbedtls_ecp_group_free(&client); + assert(live_bytes == base); + } + printf("Host fresh ECDH %s: %.2f ms, extra peak heap %zu bytes (20 runs, includes peer check heap)\n", + name, 1000.0 * elapsed / CLOCKS_PER_SEC / 20, peak_bytes - base); + mbedtls_ecp_group_free(&peer); mbedtls_ecp_point_free(&peer_q); mbedtls_mpi_free(&peer_d); + mbedtls_ctr_drbg_free(&rng); +} + int main(int argc, char **argv) { mbedtls_x509_crt root, chain; @@ -63,12 +123,14 @@ int main(int argc, char **argv) printf("SDK server chain + hostname verify: %d, flags=0x%08x\n", ret, (unsigned int)flags); assert(ret == 0 && flags == 0); verify_started = clock(); + peak_bytes = live_bytes; for (i = 0; i < 20; ++i) { ret = mbedtls_x509_crt_verify(&chain, &root, NULL, "banban.api.talkingq.com", &flags, NULL, NULL, 0); assert(ret == 0 && flags == 0); } printf("Host certificate chain verify average: %.2f ms (20 runs)\n", 1000.0 * (clock() - verify_started) / CLOCKS_PER_SEC / 20); + printf("Host certificate verify peak heap: %zu bytes, ECP window: %d\n", peak_bytes, MBEDTLS_ECP_WINDOW_SIZE); mbedtls_ecp_group_init(&group); assert(mbedtls_ecp_group_load(&group, MBEDTLS_ECP_DP_SECP384R1) == 0); #ifdef MBEDTLS_ECP_NIST_OPTIM @@ -96,6 +158,12 @@ int main(int argc, char **argv) mbedtls_gcm_free(&gcm); mbedtls_x509_crt_free(&chain); mbedtls_x509_crt_free(&root); + assert(live_bytes == 0); + benchmark_ecdh(MBEDTLS_ECP_DP_CURVE25519, "X25519"); + benchmark_ecdh(MBEDTLS_ECP_DP_SECP256R1, "P-256"); + benchmark_ecdh(MBEDTLS_ECP_DP_SECP384R1, "P-384"); + benchmark_ecdh(MBEDTLS_ECP_DP_SECP521R1, "P-521"); + assert(live_bytes == 0); puts("PASS: SDK ECDSA certificate chain, hostname, trust, dates, TLS suite and AES-GCM vector"); return 0; } diff --git a/tools/test_mbedtls_cert.py b/tools/test_mbedtls_cert.py index c09ea75..7f10723 100644 --- a/tools/test_mbedtls_cert.py +++ b/tools/test_mbedtls_cert.py @@ -9,6 +9,7 @@ root = Path(__file__).resolve().parents[1] parser = argparse.ArgumentParser() parser.add_argument('chain', nargs='?', default=str(root / 'artifacts/wss/retest-443')) parser.add_argument('--compare-nist', action='store_true') +parser.add_argument('--compare-windows', action='store_true') args = parser.parse_args() output = root / 'artifacts/wss/tls-speed/host-cert' output.mkdir(parents=True, exist_ok=True) @@ -25,9 +26,12 @@ command = ['cl', '/nologo', '/O2', '/Gy', '/TC', '/D_CRT_SECURE_NO_WARNINGS', str(root / 'tools/test_mbedtls_cert.c'), str(root / 'ql-application/threadx/interface/custom_app/talkingq_root_ca.c')] command += [str(root / ('ql-kernel/threadx/quec_mbedtls/' + name + '.c')) for name in sources] -for variant in (['baseline', 'nist'] if args.compare_nist else ['nist']): +variants = ['window4', 'window5', 'window6'] if args.compare_windows else (['baseline', 'nist'] if args.compare_nist else ['nist']) +for variant in variants: exe = output / ('test_mbedtls_cert_' + variant + '.exe') flags = ['/DWSS_TEST_DISABLE_NIST_OPTIM'] if variant == 'baseline' else [] + if variant.startswith('window'): + flags += ['/DWSS_TEST_ECP_WINDOW=' + variant[-1]] subprocess.run(command + flags + ['/Fe' + str(exe), '/link', '/OPT:REF'], cwd=output, check=True) print('Certificate test variant:', variant, flush=True) subprocess.run([str(exe), str(chain)], check=True) diff --git a/tools/test_wss_handshake.py b/tools/test_wss_handshake.py index 3d5f637..0affc45 100644 --- a/tools/test_wss_handshake.py +++ b/tools/test_wss_handshake.py @@ -10,14 +10,7 @@ harness = r''' #include #include #include -enum { MBEDTLS_SSL_CLIENT_HELLO, MBEDTLS_SSL_SERVER_HELLO, - MBEDTLS_SSL_SERVER_CERTIFICATE, MBEDTLS_SSL_SERVER_KEY_EXCHANGE, - MBEDTLS_SSL_CLIENT_KEY_EXCHANGE, MBEDTLS_SSL_SERVER_FINISHED, - MBEDTLS_SSL_HANDSHAKE_OVER }; -#define MBEDTLS_ERR_SSL_WANT_READ -2 -#define MBEDTLS_ERR_SSL_WANT_WRITE -3 -#define MBEDTLS_ERR_SSL_TIMEOUT -4 -typedef struct { int state; } mbedtls_ssl_context; +#include "mbedtls/ssl_internal.h" typedef struct { int ret, next_state; unsigned int duration; } step; static step steps[16]; static int count, calls, sleeps; @@ -26,7 +19,7 @@ static void test_log(const char *fmt, ...) { (void)fmt; } #define LOG_I test_log static unsigned int ql_rtos_get_systicks(void) { return ticks; } static void rws_thread_sleep(unsigned int ms) { ticks += ms / 5; ++sleeps; } -static int mbedtls_ssl_handshake_step(mbedtls_ssl_context *ctx) { +int mbedtls_ssl_handshake_step(mbedtls_ssl_context *ctx) { step s; assert(ctx->state != MBEDTLS_SSL_HANDSHAKE_OVER); if (calls >= count) { ++calls; return MBEDTLS_ERR_SSL_WANT_READ; } @@ -36,11 +29,13 @@ static int mbedtls_ssl_handshake_step(mbedtls_ssl_context *ctx) { harness += body harness += r''' static void reset(mbedtls_ssl_context *ctx) { + memset(ctx, 0, sizeof(*ctx)); ctx->state = MBEDTLS_SSL_CLIENT_HELLO; count = calls = sleeps = 0; ticks = 0; memset(steps, 0, sizeof(steps)); } int main(void) { mbedtls_ssl_context ctx; + mbedtls_ssl_handshake_params handshake = {0}; reset(&ctx); steps[0] = (step){0, MBEDTLS_SSL_SERVER_HELLO, 1}; steps[1] = (step){MBEDTLS_ERR_SSL_WANT_READ, MBEDTLS_SSL_SERVER_HELLO, 0}; @@ -50,6 +45,11 @@ int main(void) { steps[5] = (step){0, MBEDTLS_SSL_SERVER_FINISHED, 5}; steps[6] = (step){0, MBEDTLS_SSL_HANDSHAKE_OVER, 1}; count = 7; assert(rws_tls_handshake_timed(&ctx) == 0 && calls == 7 && sleeps == 2 && ticks == 24); + reset(&ctx); ctx.handshake = &handshake; ctx.state = MBEDTLS_SSL_SERVER_KEY_EXCHANGE; + handshake.ecdh_ctx.grp_id = MBEDTLS_ECP_DP_CURVE25519; + steps[0] = (step){0, MBEDTLS_SSL_CLIENT_KEY_EXCHANGE, 1}; + steps[1] = (step){0, MBEDTLS_SSL_HANDSHAKE_OVER, 1}; count = 2; + assert(rws_tls_handshake_timed(&ctx) == 0 && calls == 2 && sleeps == 0); reset(&ctx); steps[0] = (step){-99, MBEDTLS_SSL_SERVER_CERTIFICATE, 1}; count = 1; assert(rws_tls_handshake_timed(&ctx) == -99 && calls == 1 && sleeps == 0); reset(&ctx); @@ -70,5 +70,6 @@ cfile = output / 'test_wss_handshake.c' cfile.write_text(harness, encoding='utf-8') exe = output / 'test_wss_handshake.exe' subprocess.run(['cl', '/nologo', '/W4', '/WX', '/TC', str(cfile), + '/I' + str(root / 'ql-application/threadx/common/include'), '/Fo' + str(output / 'test_wss_handshake.obj'), '/Fe' + str(exe)], check=True) subprocess.run([str(exe)], check=True) diff --git a/tools/verify_wss_package.py b/tools/verify_wss_package.py index 6e21c2e..e6fd1c8 100644 --- a/tools/verify_wss_package.py +++ b/tools/verify_wss_package.py @@ -8,7 +8,7 @@ import zipfile from pathlib import Path root = Path(__file__).resolve().parents[1] -package = Path(sys.argv[1]).resolve() if len(sys.argv) > 1 else root / 'artifacts/wss/tls-speed/EG800AKCN_11LG-legacy-wss-fast.zip' +package = Path(sys.argv[1]).resolve() if len(sys.argv) > 1 else root / 'artifacts/wss/full-handshake/EG800AKCN_11LG-legacy-wss-x25519.zip' output = package.parent output.mkdir(parents=True, exist_ok=True) objdump = root / 'ql-cross-tool/win32/owtoolchain/gcc-arm-none-eabi/bin/arm-none-eabi-objdump.exe' @@ -16,7 +16,8 @@ app = root / 'ql-application/threadx/build/app.elf' symbols = subprocess.check_output([str(objdump.with_name('arm-none-eabi-nm.exe')), str(app)], text=True) required = ['talkingq_parse_wss_url', 'talkingq_root_ca', 'rws_check_handshake', 'rws_ssl_conn', 'mbedtls_ssl_set_hostname', 'mbedtls_ssl_get_verify_result', 'mbedtls_ssl_conf_authmode', - 'mbedtls_ctr_drbg_random', 'mbedtls_sha1', 'mbedtls_base64_encode', 'mbedtls_ssl_handshake_step'] + 'mbedtls_ctr_drbg_random', 'mbedtls_sha1', 'mbedtls_base64_encode', 'mbedtls_ssl_handshake_step', + 'rws_tls_preferred_curves'] for symbol in required: assert re.search(r'\b' + symbol + r'$', symbols, re.M), 'Missing linked symbol: ' + symbol kernel_symbols = subprocess.check_output([str(objdump.with_name('arm-none-eabi-nm.exe')), @@ -33,7 +34,7 @@ assert '' in clock_body and '' no (output / 'x509-clock.disasm.txt').write_text(clock_body) report = {'package': package.name, 'sha256': hashlib.sha256(package.read_bytes()).hexdigest(), 'linked_symbols': required, 'kernel_symbols': kernel_required, - 'hardware_test': '2026-09-16 14:20 device log: TLS and HTTP 101 passed; NIST acceleration and handshake timings await device test', + 'hardware_test': '2026-09-16 14:38 device log: NIST TLS handshake 1820 ms and HTTP 101 passed; X25519 preference awaits device test', 'backend': '2026-09-16 11:32 CST: WSS 443 /ws HTTP 101; 8080 plaintext WS'} with zipfile.ZipFile(package) as archive: assert archive.testzip() is None, 'ZIP CRC failure' @@ -44,6 +45,7 @@ with zipfile.ZipFile(package) as archive: report[name] = {'bytes': len(data), 'sha256': hashlib.sha256(data).hexdigest()} assert b'wss://banban.api.talkingq.com:443/ws' in archive.read('customer_app.bin') assert b'WSS TLS handshake total=' in archive.read('customer_app.bin') + assert b'WSS TLS full handshake: prefer X25519' in archive.read('customer_app.bin') report['download_manifest'] = json.loads(archive.read('download.json')) printf_address = int(re.search(r'^([0-9a-fA-F]+) T printf$', symbols, re.M).group(1), 16) for symbol, binary, expected in [ diff --git a/tools/wss_tls_host_config.h b/tools/wss_tls_host_config.h index 17f49ac..b462c53 100644 --- a/tools/wss_tls_host_config.h +++ b/tools/wss_tls_host_config.h @@ -4,6 +4,10 @@ #ifdef WSS_TEST_DISABLE_NIST_OPTIM #undef MBEDTLS_ECP_NIST_OPTIM #endif +#ifdef WSS_TEST_ECP_WINDOW +#undef MBEDTLS_ECP_WINDOW_SIZE +#define MBEDTLS_ECP_WINDOW_SIZE WSS_TEST_ECP_WINDOW +#endif #undef MBEDTLS_HAVE_ASM #undef MBEDTLS_FS_IO #define QUECTEL_MBEDTLS_SUPPORT