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