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.
This commit is contained in:
chenjintao
2026-09-16 15:32:22 +08:00
parent 3a0714c735
commit 551a7bb1e6
10 changed files with 189 additions and 26 deletions
+6 -4
View File
@@ -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 <stddef.h>\n#include "mbedtls/ssl.h"\n'
source.write_text('#include <stddef.h>\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')
+40
View File
@@ -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))
+70 -2
View File
@@ -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;
}
+5 -1
View File
@@ -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)
+10 -9
View File
@@ -10,14 +10,7 @@ harness = r'''
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
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)
+5 -3
View File
@@ -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 '<mbedtls_platform_gmtime_r>' in clock_body and '<GetCurrentCompTime>' 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 [
+4
View File
@@ -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