From f31ed0d0cdcb885063d94b4b064f5a48eb4088f5 Mon Sep 17 00:00:00 2001 From: Andrew Hutchings Date: Tue, 17 Feb 2026 10:35:54 +0000 Subject: [PATCH 1/4] Fix logic bug in TLSX_TCA_Find causing incorrect Trusted CA matching The while loop conditions in TLSX_TCA_Find were inverted, causing two bugs: the loop short-circuited on type match alone without checking the id content, and the XMEMCMP sense was reversed (continuing on match, stopping on mismatch). This meant any TCA entry with a matching type would be returned as a match regardless of whether the identifier actually matched. Restructure the loop to correctly require both type and id (size + content) to match before returning an entry, and to match any entry immediately for PRE_AGREED type. Add test_TLSX_TCA_Find unit test exercising exact match, mismatched id, and PRE_AGREED cases via memio handshake. --- src/tls.c | 9 ++- tests/api.c | 1 + tests/api/test_tls_ext.c | 128 +++++++++++++++++++++++++++++++++++++++ tests/api/test_tls_ext.h | 1 + 4 files changed, 137 insertions(+), 2 deletions(-) diff --git a/src/tls.c b/src/tls.c index 130660425bf..d6da6043787 100644 --- a/src/tls.c +++ b/src/tls.c @@ -2849,9 +2849,14 @@ static TCA* TLSX_TCA_Find(TCA *list, byte type, const byte* id, word16 idSz) { TCA* tca = list; - while (tca && tca->type != type && type != WOLFSSL_TRUSTED_CA_PRE_AGREED && - idSz != tca->idSz && !XMEMCMP(id, tca->id, idSz)) + while (tca) { + if (type == WOLFSSL_TRUSTED_CA_PRE_AGREED) + break; + if (tca->type == type && idSz == tca->idSz && + XMEMCMP(id, tca->id, idSz) == 0) + break; tca = tca->next; + } return tca; } diff --git a/tests/api.c b/tests/api.c index a6df4e14503..00155193448 100644 --- a/tests/api.c +++ b/tests/api.c @@ -32829,6 +32829,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_wolfSSL_DisableExtendedMasterSecret), TEST_DECL(test_certificate_authorities_certificate_request), TEST_DECL(test_certificate_authorities_client_hello), + TEST_DECL(test_TLSX_TCA_Find), TEST_DECL(test_wolfSSL_wolfSSL_UseSecureRenegotiation), TEST_DECL(test_wolfSSL_SCR_Reconnect), TEST_DECL(test_wolfSSL_SCR_check_enabled), diff --git a/tests/api/test_tls_ext.c b/tests/api/test_tls_ext.c index cf15058507d..6e1434b11b3 100644 --- a/tests/api/test_tls_ext.c +++ b/tests/api/test_tls_ext.c @@ -323,6 +323,134 @@ static int certificate_authorities_server_cb(WOLFSSL *ssl, void *_arg) { } #endif +/* Walk the TLSX list to find an extension by type. Avoids calling the + * WOLFSSL_LOCAL TLSX_Find which is not available in shared library builds. */ +static TLSX* test_TLSX_find_ext(TLSX* list, TLSX_Type type) +{ + while (list) { + if (list->type == type) + return list; + list = list->next; + } + return NULL; +} + +int test_TLSX_TCA_Find(void) +{ + EXPECT_DECLS; +#if defined(HAVE_TRUSTED_CA) && !defined(NO_SHA) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_WOLFSSL_CLIENT) + /* Two different 20-byte SHA1 ids */ + byte id_A[WC_SHA_DIGEST_SIZE]; + byte id_B[WC_SHA_DIGEST_SIZE]; + TLSX* ext; + + XMEMSET(id_A, 0xAA, sizeof(id_A)); + XMEMSET(id_B, 0xBB, sizeof(id_B)); + + /* Test 1: Exact match - same type and same id should match */ + { + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, + &ssl_s, wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + + /* Server has KEY_SHA1 with id_A */ + ExpectIntEQ(wolfSSL_UseTrustedCA(ssl_s, WOLFSSL_TRUSTED_CA_KEY_SHA1, + id_A, sizeof(id_A)), WOLFSSL_SUCCESS); + /* Client sends KEY_SHA1 with id_A (same) */ + ExpectIntEQ(wolfSSL_UseTrustedCA(ssl_c, WOLFSSL_TRUSTED_CA_KEY_SHA1, + id_A, sizeof(id_A)), WOLFSSL_SUCCESS); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* Server should have found a match and responded */ + ext = test_TLSX_find_ext(ssl_c ? ssl_c->extensions : NULL, + TLSX_TRUSTED_CA_KEYS); + ExpectNotNull(ext); + if (EXPECT_SUCCESS()) + ExpectIntEQ(ext->resp, 1); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + } + + /* Test 2: Same type, different id - should NOT match. + * This is the key test that exposes the logic bug in TLSX_TCA_Find + * where matching on type alone (without checking id content) causes + * a false positive. */ + { + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, + &ssl_s, wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + + /* Server has KEY_SHA1 with id_A */ + ExpectIntEQ(wolfSSL_UseTrustedCA(ssl_s, WOLFSSL_TRUSTED_CA_KEY_SHA1, + id_A, sizeof(id_A)), WOLFSSL_SUCCESS); + /* Client sends KEY_SHA1 with id_B (different!) */ + ExpectIntEQ(wolfSSL_UseTrustedCA(ssl_c, WOLFSSL_TRUSTED_CA_KEY_SHA1, + id_B, sizeof(id_B)), WOLFSSL_SUCCESS); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* Server should NOT have found a match - ids differ */ + ext = test_TLSX_find_ext(ssl_c ? ssl_c->extensions : NULL, + TLSX_TRUSTED_CA_KEYS); + ExpectNotNull(ext); + if (EXPECT_SUCCESS()) + ExpectIntEQ(ext->resp, 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + } + + /* Test 3: PRE_AGREED should match any server entry */ + { + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, + &ssl_s, wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + + /* Server has KEY_SHA1 with id_A */ + ExpectIntEQ(wolfSSL_UseTrustedCA(ssl_s, WOLFSSL_TRUSTED_CA_KEY_SHA1, + id_A, sizeof(id_A)), WOLFSSL_SUCCESS); + /* Client sends PRE_AGREED (no id needed) */ + ExpectIntEQ(wolfSSL_UseTrustedCA(ssl_c, WOLFSSL_TRUSTED_CA_PRE_AGREED, + NULL, 0), WOLFSSL_SUCCESS); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* Server should have matched (PRE_AGREED matches anything) */ + ext = test_TLSX_find_ext(ssl_c ? ssl_c->extensions : NULL, + TLSX_TRUSTED_CA_KEYS); + ExpectNotNull(ext); + if (EXPECT_SUCCESS()) + ExpectIntEQ(ext->resp, 1); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + } +#endif + return EXPECT_RESULT(); +} + int test_certificate_authorities_client_hello(void) { EXPECT_DECLS; #if !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ diff --git a/tests/api/test_tls_ext.h b/tests/api/test_tls_ext.h index 3506d02e967..2ec1da95c85 100644 --- a/tests/api/test_tls_ext.h +++ b/tests/api/test_tls_ext.h @@ -26,5 +26,6 @@ int test_tls_ems_downgrade(void); int test_wolfSSL_DisableExtendedMasterSecret(void); int test_certificate_authorities_certificate_request(void); int test_certificate_authorities_client_hello(void); +int test_TLSX_TCA_Find(void); #endif /* TESTS_API_TEST_TLS_EMS_H */ From 00de3f391892a5b68aa6d517f3ddb152f8e18f9d Mon Sep 17 00:00:00 2001 From: Andrew Hutchings Date: Tue, 17 Feb 2026 10:36:47 +0000 Subject: [PATCH 2/4] Use XMEMSET instead of memset in QUIC --- src/quic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/quic.c b/src/quic.c index 21f64903bbf..2d6e4a6c878 100644 --- a/src/quic.c +++ b/src/quic.c @@ -72,7 +72,7 @@ static QuicRecord *quic_record_make(WOLFSSL *ssl, qr = (QuicRecord*)XMALLOC(sizeof(*qr), ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); if (qr) { - memset(qr, 0, sizeof(*qr)); + XMEMSET(qr, 0, sizeof(*qr)); qr->level = level; if (level == wolfssl_encryption_early_data) { qr->capacity = qr->len = (word32)len; From 060a2b33959ee53fb5f3ece838281f62dcba751b Mon Sep 17 00:00:00 2001 From: Andrew Hutchings Date: Tue, 17 Feb 2026 10:39:41 +0000 Subject: [PATCH 3/4] Fix DTLS 1.3 unified header fixed bits mask DTLS13_FIXED_BITS_MASK used 0x111 (hex 273) instead of 0x7 (decimal 7, binary 111). Per RFC 9147 Section 4, the top 3 bits of the unified header flags byte must be 001. The incorrect hex value caused the mask to only check bit 5 instead of bits 5, 6, and 7, allowing bytes with bits 6 or 7 set to be misidentified as unified DTLS 1.3 headers. --- src/dtls13.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dtls13.c b/src/dtls13.c index 265ca7a6035..615423df783 100644 --- a/src/dtls13.c +++ b/src/dtls13.c @@ -93,7 +93,7 @@ typedef struct Dtls13RecordPlaintextHeader { #define DTLS13_SEQ_8_LEN 1 /* fixed bits mask to detect unified header */ -#define DTLS13_FIXED_BITS_MASK (0x111 << 5) +#define DTLS13_FIXED_BITS_MASK (0x7 << 5) /* fixed bits value to detect unified header */ #define DTLS13_FIXED_BITS (0x1 << 5) /* ConnectionID present bit in the unified header flags */ From 565c6aad49e9d5973ade4fc3fa760615b7857a52 Mon Sep 17 00:00:00 2001 From: Andrew Hutchings Date: Tue, 17 Feb 2026 10:46:33 +0000 Subject: [PATCH 4/4] Fix test building --- tests/api/test_tls_ext.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/api/test_tls_ext.c b/tests/api/test_tls_ext.c index 6e1434b11b3..001a7167ff3 100644 --- a/tests/api/test_tls_ext.c +++ b/tests/api/test_tls_ext.c @@ -323,6 +323,9 @@ static int certificate_authorities_server_cb(WOLFSSL *ssl, void *_arg) { } #endif +#if defined(HAVE_TRUSTED_CA) && !defined(NO_SHA) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_WOLFSSL_CLIENT) /* Walk the TLSX list to find an extension by type. Avoids calling the * WOLFSSL_LOCAL TLSX_Find which is not available in shared library builds. */ static TLSX* test_TLSX_find_ext(TLSX* list, TLSX_Type type) @@ -334,6 +337,7 @@ static TLSX* test_TLSX_find_ext(TLSX* list, TLSX_Type type) } return NULL; } +#endif int test_TLSX_TCA_Find(void) {