From 48a2ab360839e3d3869be8c00f4835947250684f Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 02:08:47 +0200 Subject: [PATCH 01/57] wolfcrypt: give the keywrap _ex test an Aes with the type's alignment --- wolfcrypt/test/test.c | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index ec68a86093..5e84c4cd02 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -23766,46 +23766,40 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t aeskeywrap_test(void) /* Drive wc_AesKeyWrap_ex/wc_AesKeyUnWrap_ex directly with a caller Aes; the * KAT loop above already covers every vector via the key-based wrappers. */ { - Aes* aes = (Aes*)XMALLOC(sizeof(Aes), HEAP_HINT, DYNAMIC_TYPE_AES); - if (aes == NULL) - return WC_TEST_RET_ENC_NC; + /* Aes must not come from XMALLOC here: struct Aes carries ALIGN16 + * members, so _Alignof(Aes) is 16 under the default --enable-aligndata, + * while malloc() only guarantees 8 on 32-bit targets. A local gets the + * type's alignment from the compiler. See wc_AesSetIV(), which clang + * lowers to an alignment-qualified NEON store on armv8-a+crypto. */ + Aes aes[1]; XMEMSET(output, 0, sizeof(output)); XMEMSET(plain, 0, sizeof(plain)); - if (wc_AesInit(aes, HEAP_HINT, devId) != 0) { - XFREE(aes, HEAP_HINT, DYNAMIC_TYPE_AES); + if (wc_AesInit(aes, HEAP_HINT, devId) != 0) return WC_TEST_RET_ENC_NC; - } if (wc_AesSetKey(aes, test_wrap[0].kek, test_wrap[0].kekLen, NULL, AES_ENCRYPTION) != 0) { wc_AesFree(aes); - XFREE(aes, HEAP_HINT, DYNAMIC_TYPE_AES); return WC_TEST_RET_ENC_NC; } wrapSz = wc_AesKeyWrap_ex(aes, test_wrap[0].data, test_wrap[0].dataLen, output, sizeof(output), NULL); wc_AesFree(aes); if ( (wrapSz < 0) || (wrapSz != (int)test_wrap[0].verifyLen) || - XMEMCMP(output, test_wrap[0].verify, test_wrap[0].verifyLen) != 0) { - XFREE(aes, HEAP_HINT, DYNAMIC_TYPE_AES); + XMEMCMP(output, test_wrap[0].verify, test_wrap[0].verifyLen) != 0) return WC_TEST_RET_ENC_NC; - } - if (wc_AesInit(aes, HEAP_HINT, devId) != 0) { - XFREE(aes, HEAP_HINT, DYNAMIC_TYPE_AES); + if (wc_AesInit(aes, HEAP_HINT, devId) != 0) return WC_TEST_RET_ENC_NC; - } if (wc_AesSetKey(aes, test_wrap[0].kek, test_wrap[0].kekLen, NULL, AES_DECRYPTION) != 0) { wc_AesFree(aes); - XFREE(aes, HEAP_HINT, DYNAMIC_TYPE_AES); return WC_TEST_RET_ENC_NC; } plainSz = wc_AesKeyUnWrap_ex(aes, output, (word32)wrapSz, plain, sizeof(plain), NULL); wc_AesFree(aes); - XFREE(aes, HEAP_HINT, DYNAMIC_TYPE_AES); if ( (plainSz < 0) || (plainSz != (int)test_wrap[0].dataLen) || XMEMCMP(plain, test_wrap[0].data, test_wrap[0].dataLen) != 0) return WC_TEST_RET_ENC_NC; From b46ff7ab609f57549e32531e9d031c094b8ef5ff Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 02:28:26 +0200 Subject: [PATCH 02/57] tests: drive the crafted SP vectors on the three qemu-user ARM lanes --- tests/unit-mcdc/test_sp_arm32_whitebox.c | 15 +++++++++++++++ tests/unit-mcdc/test_sp_arm64_whitebox.c | 15 +++++++++++++++ tests/unit-mcdc/test_sp_armthumb_whitebox.c | 15 +++++++++++++++ tests/unit-mcdc/test_sp_crafted_common.h | 14 +++++++++++--- 4 files changed, 56 insertions(+), 3 deletions(-) diff --git a/tests/unit-mcdc/test_sp_arm32_whitebox.c b/tests/unit-mcdc/test_sp_arm32_whitebox.c index ba9f646887..a0db3d7b10 100644 --- a/tests/unit-mcdc/test_sp_arm32_whitebox.c +++ b/tests/unit-mcdc/test_sp_arm32_whitebox.c @@ -119,6 +119,8 @@ #include "mcdc_fault_mutex.h" +#include "mcdc_fault_alloc.h" + #include #include #include @@ -129,6 +131,18 @@ static int wb_fail = 0; #define WB_NOTE(msg) do { printf(" [wb] %s\n", (msg)); } while (0) +/* Crafted-input driver shared with the SP host-backend white-boxes. The four + * ARM backends implement the SAME public API (sp_ecc_verify_, + * sp_ecc_sign_, sp_ecc_check_key_, sp_ModExp_, ...), so the same + * body drives them. It supplies two vectors this file's own drivers cannot: + * a verify whose public point is the Jacobian point at infinity (pZ == 0), + * which is the only way `(err == MP_OKAY) && sp__iszero_(p2->z)` goes + * true, and a sign with a zero private scalar against an all-zero hash, + * which makes s == 0 on EVERY attempt so the SP_ECC_MAX_SIG_GEN retry loop + * runs to exhaustion and leaves its `i > 0` operand false. Both are + * deterministic -- no RNG luck is involved, contrary to the note above. */ +#include "test_sp_crafted_common.h" + #if defined(WOLFSSL_HAVE_SP_ECC) || defined(WOLFSSL_HAVE_SP_RSA) || \ defined(WOLFSSL_HAVE_SP_DH) @@ -2041,6 +2055,7 @@ int main(void) wb_run_dh_gaps(); wb_run_residual_extra_all(); wb_run_mod_inv(); + wb_spc_all(); printf("done (%s)\n", wb_fail ? "with skips" : "ok"); #else diff --git a/tests/unit-mcdc/test_sp_arm64_whitebox.c b/tests/unit-mcdc/test_sp_arm64_whitebox.c index 21ab7ed8ca..a564ae241f 100644 --- a/tests/unit-mcdc/test_sp_arm64_whitebox.c +++ b/tests/unit-mcdc/test_sp_arm64_whitebox.c @@ -121,6 +121,8 @@ #include "mcdc_fault_mutex.h" +#include "mcdc_fault_alloc.h" + #include #include #include @@ -131,6 +133,18 @@ static int wb_fail = 0; #define WB_NOTE(msg) do { printf(" [wb] %s\n", (msg)); } while (0) +/* Crafted-input driver shared with the SP host-backend white-boxes. The four + * ARM backends implement the SAME public API (sp_ecc_verify_, + * sp_ecc_sign_, sp_ecc_check_key_, sp_ModExp_, ...), so the same + * body drives them. It supplies two vectors this file's own drivers cannot: + * a verify whose public point is the Jacobian point at infinity (pZ == 0), + * which is the only way `(err == MP_OKAY) && sp__iszero_(p2->z)` goes + * true, and a sign with a zero private scalar against an all-zero hash, + * which makes s == 0 on EVERY attempt so the SP_ECC_MAX_SIG_GEN retry loop + * runs to exhaustion and leaves its `i > 0` operand false. Both are + * deterministic -- no RNG luck is involved, contrary to the note above. */ +#include "test_sp_crafted_common.h" + #if defined(WOLFSSL_HAVE_SP_ECC) || defined(WOLFSSL_HAVE_SP_RSA) || \ defined(WOLFSSL_HAVE_SP_DH) @@ -1549,6 +1563,7 @@ int main(void) wb_run_rsa_dh_bounds(); wb_run_check_key_priv_all(); wb_run_mod_inv(); + wb_spc_all(); printf("done (%s)\n", wb_fail ? "with skips" : "ok"); #else diff --git a/tests/unit-mcdc/test_sp_armthumb_whitebox.c b/tests/unit-mcdc/test_sp_armthumb_whitebox.c index 225c319041..92e4eb916f 100644 --- a/tests/unit-mcdc/test_sp_armthumb_whitebox.c +++ b/tests/unit-mcdc/test_sp_armthumb_whitebox.c @@ -106,6 +106,8 @@ #include "mcdc_fault_mutex.h" +#include "mcdc_fault_alloc.h" + #include #include #include @@ -116,6 +118,18 @@ static int wb_fail = 0; #define WB_NOTE(msg) do { printf(" [wb] %s\n", (msg)); } while (0) +/* Crafted-input driver shared with the SP host-backend white-boxes. The four + * ARM backends implement the SAME public API (sp_ecc_verify_, + * sp_ecc_sign_, sp_ecc_check_key_, sp_ModExp_, ...), so the same + * body drives them. It supplies two vectors this file's own drivers cannot: + * a verify whose public point is the Jacobian point at infinity (pZ == 0), + * which is the only way `(err == MP_OKAY) && sp__iszero_(p2->z)` goes + * true, and a sign with a zero private scalar against an all-zero hash, + * which makes s == 0 on EVERY attempt so the SP_ECC_MAX_SIG_GEN retry loop + * runs to exhaustion and leaves its `i > 0` operand false. Both are + * deterministic -- no RNG luck is involved, contrary to the note above. */ +#include "test_sp_crafted_common.h" + #if defined(WOLFSSL_HAVE_SP_ECC) || defined(WOLFSSL_HAVE_SP_RSA) || \ defined(WOLFSSL_HAVE_SP_DH) @@ -1845,6 +1859,7 @@ int main(void) wb_run_gap_521(); wb_run_residual_extra_all(); wb_run_mod_inv(); + wb_spc_all(); printf("done (%s)\n", wb_fail ? "with skips" : "ok"); #else diff --git a/tests/unit-mcdc/test_sp_crafted_common.h b/tests/unit-mcdc/test_sp_crafted_common.h index 38f8ebd35b..ca5a047eed 100644 --- a/tests/unit-mcdc/test_sp_crafted_common.h +++ b/tests/unit-mcdc/test_sp_crafted_common.h @@ -211,9 +211,17 @@ * register quadruple that is identically zero and re-tests bit 0, so it * cannot terminate -- while appending a byte per iteration to a fixed * 0x208-byte stack buffer. It is not MC/DC-instrumented (it is assembly), - * so there is nothing to gain by driving it. See DEATHNOTE.md; the same - * defect is already recorded for sp_arm64.c. */ -#if defined(WOLFSSL_SP_X86_64_ASM) + * so there is nothing to gain by driving it. See DEATHNOTE.md. + * + * EVERY assembly backend has this shape, not just x86-64: the ARM lanes + * (sp_arm64.c, sp_arm32.c, sp_armthumb.c, sp_cortexm.c) hand-write the + * P-256 modular inverse for the same reason and the same defect is already + * recorded for sp_arm64.c. The guard therefore lists all of them -- a + * backend that reaches the `1` arm must have a C sp_256_mod_inv_(). Do + * NOT narrow this back to x86-64: the a == m vector does not return. */ +#if defined(WOLFSSL_SP_X86_64_ASM) || defined(WOLFSSL_SP_ARM64_ASM) || \ + defined(WOLFSSL_SP_ARM32_ASM) || defined(WOLFSSL_SP_ARM_THUMB_ASM) || \ + defined(WOLFSSL_SP_ARM_CORTEX_M_ASM) #define WB_SPC_MODINV_AM_256 0 #else #define WB_SPC_MODINV_AM_256 1 From 861512d0d52d26b17e53b81f9627f3a7a943bf1b Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 02:29:49 +0200 Subject: [PATCH 03/57] wolfcrypt: dsa.c key/parameter generation cleared mp_ints a failed mp_init_multi never built --- wolfcrypt/src/dsa.c | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/wolfcrypt/src/dsa.c b/wolfcrypt/src/dsa.c index ae1a1a7a8a..b40b0b974e 100644 --- a/wolfcrypt/src/dsa.c +++ b/wolfcrypt/src/dsa.c @@ -278,7 +278,16 @@ int wc_MakeDsaKey(WC_RNG *rng, DsaKey *dsa) if (err == MP_OKAY) #endif + { + /* Map an init failure to MP_INIT_E, the code the cleanup below uses to + * mean "nothing here was constructed". mp_init_multi() reports the + * backend's own error (MP_MEM from the heap backends) and, on failure, + * leaves every argument either cleared or never touched, so the + * cleanup must not mp_clear() any of them. */ err = mp_init_multi(&dsa->x, &dsa->y, tmpQ, NULL, NULL, NULL); + if (err != MP_OKAY) + err = MP_INIT_E; + } if (err == MP_OKAY) { do { @@ -320,7 +329,7 @@ int wc_MakeDsaKey(WC_RNG *rng, DsaKey *dsa) if (err == MP_OKAY) dsa->type = DSA_PRIVATE; - if (err != MP_OKAY) { + if ((err != MP_OKAY) && (err != WC_NO_ERR_TRACE(MP_INIT_E))) { mp_forcezero(&dsa->x); mp_clear(&dsa->y); } @@ -333,11 +342,13 @@ int wc_MakeDsaKey(WC_RNG *rng, DsaKey *dsa) #if defined(WOLFSSL_SMALL_STACK) && !defined(WOLFSSL_NO_MALLOC) XFREE(cBuf, dsa->heap, DYNAMIC_TYPE_TMP_BUFFER); if (tmpQ != NULL) { - mp_clear(tmpQ); + if (err != WC_NO_ERR_TRACE(MP_INIT_E)) + mp_clear(tmpQ); XFREE(tmpQ, dsa->heap, DYNAMIC_TYPE_TMP_BUFFER); } #else - mp_clear(tmpQ); + if (err != WC_NO_ERR_TRACE(MP_INIT_E)) + mp_clear(tmpQ); #endif return err; @@ -420,7 +431,15 @@ int wc_MakeDsaParameters(WC_RNG *rng, int modulus_size, DsaKey *dsa) if (err == MP_OKAY) #endif + { + /* Map an init failure to MP_INIT_E: the cleanup below already keys off + * that code to skip mp_clear() on objects the failed init never + * constructed, but mp_init_multi() returns the backend's own error + * (MP_MEM from the heap backends), so the guard never fired. */ err = mp_init_multi(tmp, tmp2, &dsa->p, &dsa->q, &dsa->g, 0); + if (err != MP_OKAY) + err = MP_INIT_E; + } if (err == MP_OKAY) err = mp_read_unsigned_bin(tmp2, buf, (word32)(msize - qsize)); From fc23316e8adb614424da11aaad536e98861a0d5e Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 09:28:35 +0200 Subject: [PATCH 04/57] tests: add a white-box MC/DC supplement for src/tls13.c --- tests/include.am | 1 + tests/unit-mcdc/test_tls13_whitebox.c | 121 ++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 tests/unit-mcdc/test_tls13_whitebox.c diff --git a/tests/include.am b/tests/include.am index 5e5ef55ef4..0c82436148 100644 --- a/tests/include.am +++ b/tests/include.am @@ -218,6 +218,7 @@ EXTRA_DIST += \ tests/unit-mcdc/test_sp_x86_64_fault_whitebox.c \ tests/unit-mcdc/test_sp_x86_64_whitebox.c \ tests/unit-mcdc/test_tfm_whitebox.c \ + tests/unit-mcdc/test_tls13_whitebox.c \ tests/unit-mcdc/test_tsp_fault_whitebox.c \ tests/unit-mcdc/test_tsp_whitebox.c \ tests/unit-mcdc/test_wc_encrypt_whitebox.c \ diff --git a/tests/unit-mcdc/test_tls13_whitebox.c b/tests/unit-mcdc/test_tls13_whitebox.c new file mode 100644 index 0000000000..da73bb45ec --- /dev/null +++ b/tests/unit-mcdc/test_tls13_whitebox.c @@ -0,0 +1,121 @@ +/* test_tls13_whitebox.c + * + * White-box MC/DC supplement for src/tls13.c. + * + * This is the FIRST white-box driver in the campaign that targets a src/ file + * rather than wolfcrypt/src/*.c. The build contract is identical (see + * tests/unit-mcdc/README.md): this TU #includes the target .c verbatim, is + * compiled with the exact flags the instrumented library used for it, and is + * linked against that variant's libwolfssl.a with the target's own object + * removed, so this TU supplies the single (instrumented) definition. + * + * WHY A WHITE-BOX IS NEEDED HERE, given that src/ has almost no mutable file + * scope variables and all state hangs off WOLFSSL / WOLFSSL_CTX: + * tls13.c has ~80 `static` functions. What this TU buys is not access to + * hidden state but the ability to call those helpers with ARGUMENT + * COMBINATIONS NO PUBLIC CALLER PRODUCES -- the defensive guards that every + * in-library caller has already excluded before the callee runs. Those guards + * are real conditions in the coverage map and are unreachable from tests/api + * without editing library source. + * + * Coverage from this binary is unioned with the tests/api variant coverage by + * source line:col by the campaign's aggregate.sh, which ORs the "independence + * shown" bit across binaries. llvm-cov derives independence PER BINARY, so + * every MC/DC pair below is completed WITHIN THIS FILE; nothing here leans on + * the API tests to supply the other half of a pair. + * + * main() always returns 0: the campaign treats a nonzero exit as a failed + * white-box and discards its coverage, so setup problems are printed as skips. + */ + +/* Pull tls13.c in verbatim so its file-static helpers are in scope and + * instrumented in THIS binary. tls13.c includes settings.h, which picks up + * user_settings.h via -DWOLFSSL_USER_SETTINGS. */ +#include + +#include + +#define WB_NOTE(msg) do { printf(" [wb] %s\n", (msg)); } while (0) + +/* The guard stack that encloses DecodeTls13SigAlg() in tls13.c: + * #if !defined(NO_TLS) && defined(WOLFSSL_TLS13) + * #ifndef WOLFCRYPT_ONLY + * #ifndef NO_CERTS + * #if !defined(NO_RSA) || defined(HAVE_ECC) || ... + * Reproduced verbatim so this file still compiles (as a no-op) on any build + * axis that does not compile the helper. */ +#if !defined(NO_TLS) && defined(WOLFSSL_TLS13) && !defined(WOLFCRYPT_ONLY) && \ + !defined(NO_CERTS) && \ + (!defined(NO_RSA) || defined(HAVE_ECC) || defined(HAVE_ED25519) || \ + defined(HAVE_ED448) || defined(HAVE_FALCON) || \ + defined(WOLFSSL_HAVE_MLDSA) || defined(WOLFSSL_HAVE_SLHDSA)) + #define WB_HAVE_DECODE_SIGALG +#endif + +/* ------------------------------------------------------------------------- * + * DecodeTls13SigAlg(): the two RSA-PSS minor-byte RANGE checks. + * + * if (input[1] >= RSA_PSS_RSAE_SHA256_MINOR && + * input[1] <= RSA_PSS_RSAE_SHA512_MINOR) [0x04 .. 0x06] + * else if (input[1] >= RSA_PSS_PSS_SHA256_MINOR && + * input[1] <= RSA_PSS_PSS_SHA512_MINOR) [0x09 .. 0x0B] + * + * DecodeTls13SigAlg is file-static and every in-library caller feeds it a + * signature algorithm that already passed the peer's advertised sig_algs + * negotiation, so the "major byte is 0x08 but the minor byte sits just outside + * a PSS range" combinations -- exactly the (T,F) halves of these two pairs -- + * never arrive from a handshake. Called directly here with all three vectors + * per decision: + * {0x08,0x05} -> (T,T) decision true + * {0x08,0x03} -> (F,-) decision false (short-circuits) pair for operand 0 + * {0x08,0x07} -> (T,F) decision false pair for operand 1 + * and the same shape one range up for the PSS-PSS check, which is only + * reached when the RSAE check is false. + * + * Pure function of a 2-byte buffer and two out-bytes: no WOLFSSL object, no + * allocation, no entropy. + * ------------------------------------------------------------------------- */ +#ifdef WB_HAVE_DECODE_SIGALG +static void wb_decode_tls13_sigalg(void) +{ + static const byte vec[6][2] = { + { NEW_SA_MAJOR, 0x05 }, /* RSAE range: T,T */ + { NEW_SA_MAJOR, 0x03 }, /* RSAE range: F,- ; PSS range: F,- */ + { NEW_SA_MAJOR, 0x07 }, /* RSAE range: T,F */ + { NEW_SA_MAJOR, 0x0A }, /* RSAE F,- then PSS range: T,T */ + { NEW_SA_MAJOR, 0x0C }, /* RSAE F,- then PSS range: T,F */ + { NEW_SA_MAJOR, 0x09 } /* PSS range lower edge: T,T */ + }; + byte input[2]; + byte hashAlgo; + byte hsType; + size_t i; + + for (i = 0; i < sizeof(vec) / sizeof(vec[0]); i++) { + input[0] = vec[i][0]; + input[1] = vec[i][1]; + hashAlgo = 0; + hsType = 0; + (void)DecodeTls13SigAlg(input, &hashAlgo, &hsType); + } + + WB_NOTE("DecodeTls13SigAlg: both PSS minor-byte range decisions driven " + "with both halves of each independence pair"); +} +#else +static void wb_decode_tls13_sigalg(void) +{ WB_NOTE("DecodeTls13SigAlg not compiled in this variant; skipped"); } +#endif + +int main(void) +{ + setvbuf(stdout, NULL, _IONBF, 0); + printf("tls13.c white-box MC/DC supplement\n"); + + wb_decode_tls13_sigalg(); + + printf("done\n"); + /* Always 0: a nonzero exit is scored as a failed white-box and its + * coverage is discarded. */ + return 0; +} From 0cf4dd8edb5be612216411d39592c6ca68f03e31 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 09:40:22 +0200 Subject: [PATCH 05/57] tests: reach the tls13.c key schedule, cookie and message-ordering guards from the white-box --- tests/unit-mcdc/test_tls13_whitebox.c | 649 ++++++++++++++++++++++++++ 1 file changed, 649 insertions(+) diff --git a/tests/unit-mcdc/test_tls13_whitebox.c b/tests/unit-mcdc/test_tls13_whitebox.c index da73bb45ec..9a9049a53e 100644 --- a/tests/unit-mcdc/test_tls13_whitebox.c +++ b/tests/unit-mcdc/test_tls13_whitebox.c @@ -107,12 +107,661 @@ static void wb_decode_tls13_sigalg(void) { WB_NOTE("DecodeTls13SigAlg not compiled in this variant; skipped"); } #endif + +/* ------------------------------------------------------------------------- * + * Shared fixture: one WOLFSSL built through the public API. Nothing here + * performs a handshake -- the object exists only so the static helpers below + * can be called with a STRUCTURALLY VALID ssl (the "all operands false" half + * of each guard) as well as with the degenerate arguments no in-library + * caller ever produces. No entropy is consumed beyond what wolfSSL_new() + * itself does, no certificate or key file is read, and no wall-clock or + * network behaviour is involved, so the binary is deterministic. + * + * A CLIENT method is used because wolfSSL_new() on a server WOLFSSL_CTX with + * no certificate loaded fails, and depending on the on-disk certs/ tree would + * make this TU sensitive to the runner's working directory. The one place + * below that needs server-side behaviour flips ssl->options.side for the + * duration of a single call and restores it -- see wb_create_cookie_ext_guards. + * ------------------------------------------------------------------------- */ +#if !defined(NO_TLS) && defined(WOLFSSL_TLS13) && !defined(WOLFCRYPT_ONLY) && \ + !defined(NO_WOLFSSL_CLIENT) + #define WB_HAVE_SSL_FIXTURE +#endif + +#ifdef WB_HAVE_SSL_FIXTURE +static WOLFSSL_CTX* wb_ctx_c = NULL; +static WOLFSSL* wb_ssl_c = NULL; + +/* The ssl argument the WB_ARRAYS_GUARD macro varies between vectors. Kept in a + * file-scope variable so one macro can drive helpers with different + * signatures without re-stating each call three times. */ +static WOLFSSL* wb_s = NULL; + +static int wb_fixture_setup(void) +{ + wb_ctx_c = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); + if (wb_ctx_c == NULL) + return 0; + wb_ssl_c = wolfSSL_new(wb_ctx_c); + if (wb_ssl_c == NULL) + return 0; + /* Every guard below reads ssl->arrays; a WOLFSSL that never got one is + * not a usable fixture. */ + if (wb_ssl_c->arrays == NULL) + return 0; + return 1; +} + +static void wb_fixture_teardown(void) +{ + if (wb_ssl_c != NULL) wolfSSL_free(wb_ssl_c); + if (wb_ctx_c != NULL) wolfSSL_CTX_free(wb_ctx_c); + wb_ssl_c = NULL; wb_ctx_c = NULL; +} + +/* Drive `if (ssl == NULL || ssl->arrays == NULL)` with all three vectors that + * unique-cause MC/DC needs, in THIS binary: + * + * ssl == NULL -> (T,-) decision true | pair for operand 0 + * ssl != NULL, arrays NULL -> (F,T) decision true | pair for operand 1 + * ssl != NULL, arrays set -> (F,F) decision false | shared partner + * + * EXPR must reference wb_s where the helper takes its WOLFSSL*. The third + * vector runs the helper's body for real; each helper below was chosen so that + * body is side-effect free on an un-negotiated WOLFSSL (specs.mac_algorithm is + * still 0, so the key schedule bails out with HASH_TYPE_E / BAD_FUNC_ARG + * before touching hsHashes or the record layer). Return values are + * deliberately ignored: the guard, not the outcome, is under test. + * + * arrays is nulled and restored rather than freed, so teardown is unaffected. + */ +#define WB_ARRAYS_GUARD(ssl, EXPR) \ + do { \ + Arrays* wb_saved = (ssl)->arrays; \ + wb_s = NULL; \ + (void)(EXPR); \ + wb_s = (ssl); \ + (ssl)->arrays = NULL; \ + (void)(EXPR); \ + (ssl)->arrays = wb_saved; \ + (void)(EXPR); \ + } while (0) + +/* ------------------------------------------------------------------------- * + * The TLS 1.3 key-schedule entry points and their `ssl == NULL || + * ssl->arrays == NULL` guards. + * + * DeriveBinderKey, DeriveBinderKeyResume, DeriveEarlyTrafficSecret, + * DeriveClient/ServerHandshakeSecret, DeriveClient/ServerTrafficSecret and + * DeriveExporterSecret are file-static; DeriveEarlySecret, + * DeriveHandshakeSecret and DeriveMasterSecret are WOLFSSL_LOCAL. Either way + * every in-library call site sits inside the handshake state machine, which + * cannot be entered at all without a WOLFSSL that already has its arrays -- + * FreeArrays() only runs once the handshake is complete and no key-schedule + * call follows it. So from tests/api the decision is *always* (F,F): both + * operands are stuck false and neither independence pair can ever be shown. + * Reaching (T,-) and (F,T) is precisely what compiling tls13.c into the test + * binary buys. + * ------------------------------------------------------------------------- */ +static void wb_key_schedule_null_guards(void) +{ + byte key[WC_MAX_DIGEST_SIZE]; + + if (wb_ssl_c == NULL) { + WB_NOTE("no ssl fixture; key-schedule guards skipped"); + return; + } + + XMEMSET(key, 0, sizeof(key)); + +#ifndef NO_PSK + WB_ARRAYS_GUARD(wb_ssl_c, DeriveBinderKey(wb_s, key)); +#endif +#if defined(HAVE_SESSION_TICKET) && \ + (!defined(NO_WOLFSSL_CLIENT) || !defined(NO_WOLFSSL_SERVER)) + WB_ARRAYS_GUARD(wb_ssl_c, DeriveBinderKeyResume(wb_s, key)); +#endif +#ifdef WOLFSSL_EARLY_DATA + WB_ARRAYS_GUARD(wb_ssl_c, + DeriveEarlyTrafficSecret(wb_s, key, WOLFSSL_CLIENT_END)); +#endif + WB_ARRAYS_GUARD(wb_ssl_c, DeriveClientHandshakeSecret(wb_s, key)); + WB_ARRAYS_GUARD(wb_ssl_c, DeriveServerHandshakeSecret(wb_s, key)); + WB_ARRAYS_GUARD(wb_ssl_c, DeriveClientTrafficSecret(wb_s, key)); + WB_ARRAYS_GUARD(wb_ssl_c, DeriveServerTrafficSecret(wb_s, key)); +#ifdef HAVE_KEYING_MATERIAL + WB_ARRAYS_GUARD(wb_ssl_c, DeriveExporterSecret(wb_s, key)); +#endif + WB_ARRAYS_GUARD(wb_ssl_c, DeriveEarlySecret(wb_s)); + WB_ARRAYS_GUARD(wb_ssl_c, DeriveHandshakeSecret(wb_s)); + WB_ARRAYS_GUARD(wb_ssl_c, DeriveMasterSecret(wb_s)); + + WB_NOTE("key-schedule ssl/arrays guards driven with all three vectors"); +} + +/* ------------------------------------------------------------------------- * + * BuildTls13HandshakeHmac(): `if (ssl == NULL || key == NULL || hash == NULL)` + * + * Three operands, so unique-cause MC/DC needs four vectors. The only two + * callers (SendTls13Finished / DoTls13Finished) pass ssl plus two automatic + * buffers, so all three operands are permanently false from tests/api. + * + * (T,-,-) ssl NULL + * (F,T,-) key NULL + * (F,F,T) hash NULL + * (F,F,F) all supplied -> body runs; specs.mac_algorithm is 0 on an + * un-negotiated WOLFSSL, so the switch takes `default:` and returns + * BAD_FUNC_ARG before dereferencing ssl->hsHashes. + * ------------------------------------------------------------------------- */ +static void wb_build_handshake_hmac_guard(void) +{ + byte key[WC_MAX_DIGEST_SIZE]; + byte hash[WC_MAX_DIGEST_SIZE]; + word32 hashSz = 0; + + if (wb_ssl_c == NULL) { + WB_NOTE("no ssl fixture; BuildTls13HandshakeHmac guard skipped"); + return; + } + + XMEMSET(key, 0, sizeof(key)); + XMEMSET(hash, 0, sizeof(hash)); + + (void)BuildTls13HandshakeHmac(NULL, key, hash, &hashSz); + (void)BuildTls13HandshakeHmac(wb_ssl_c, NULL, hash, &hashSz); + (void)BuildTls13HandshakeHmac(wb_ssl_c, key, NULL, &hashSz); + (void)BuildTls13HandshakeHmac(wb_ssl_c, key, hash, &hashSz); + + WB_NOTE("BuildTls13HandshakeHmac argument guard driven with all four " + "vectors"); +} + +/* ------------------------------------------------------------------------- * + * CreateCookieExt(): the two argument/state guards. + * + * if (hash == NULL || hashSz == 0) -> BAD_FUNC_ARG + * if (cookieSecret.buffer == NULL || cookieSecret.length == 0) + * -> COOKIE_ERROR + * + * Both callers (SendTls13ServerHello's HRR path and DoTls13ClientHello) reach + * CreateCookieExt only after the server has a cookie secret and a computed + * transcript hash, so from tests/api every operand of both decisions is stuck + * false. Driven here directly: + * + * hash guard: (NULL, 32) / (hash, 0) / (hash, 32) + * secret guard: no secret -> (T,-); secret with length forced to 0 -> (F,T); + * secret as installed -> (F,F), which runs the HMAC and the + * TLSX_Cookie_Use() that follows. + * + * The length field is forced to 0 and restored rather than freeing the + * buffer, so wolfSSL_free() still releases it exactly once. + * ------------------------------------------------------------------------- */ +#if defined(WOLFSSL_SEND_HRR_COOKIE) && !defined(NO_WOLFSSL_SERVER) +static void wb_create_cookie_ext_guards(void) +{ + static const byte secret[32] = { + 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07, + 0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f, + 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17, + 0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f + }; + byte hash[WC_SHA256_DIGEST_SIZE]; + TLSX* exts = NULL; + word32 savedLen; + int savedSide; + int ret; + + if (wb_ssl_c == NULL) { + WB_NOTE("no ssl fixture; CreateCookieExt guards skipped"); + return; + } + + XMEMSET(hash, 0x5a, sizeof(hash)); + + /* hash guard, all three vectors. The third falls through to the cookie + * secret guard, which on a fresh server is (T,-): no secret installed. */ + (void)CreateCookieExt(wb_ssl_c, NULL, (word16)sizeof(hash), &exts, + TLS13_BYTE, TLS_AES_128_GCM_SHA256); + (void)CreateCookieExt(wb_ssl_c, hash, 0, &exts, + TLS13_BYTE, TLS_AES_128_GCM_SHA256); + (void)CreateCookieExt(wb_ssl_c, hash, (word16)sizeof(hash), &exts, + TLS13_BYTE, TLS_AES_128_GCM_SHA256); + + /* wolfSSL_send_hrr_cookie() is server-only; the fixture is a client + * because a server WOLFSSL_CTX with no certificate cannot be instantiated. + * The side is flipped for exactly this call and restored immediately, so + * wolfSSL_free() still tears the object down along the client path. */ + savedSide = wb_ssl_c->options.side; + wb_ssl_c->options.side = WOLFSSL_SERVER_END; + ret = wolfSSL_send_hrr_cookie(wb_ssl_c, secret, (unsigned int)sizeof(secret)); + wb_ssl_c->options.side = savedSide; + + if (ret != WOLFSSL_SUCCESS || + wb_ssl_c->buffers.tls13CookieSecret.buffer == NULL) { + WB_NOTE("could not install a cookie secret; secret guard partial"); + TLSX_FreeAll(exts, wb_ssl_c->heap); + return; + } + + /* buffer non-NULL, length 0 -> (F,T) */ + savedLen = wb_ssl_c->buffers.tls13CookieSecret.length; + wb_ssl_c->buffers.tls13CookieSecret.length = 0; + (void)CreateCookieExt(wb_ssl_c, hash, (word16)sizeof(hash), &exts, + TLS13_BYTE, TLS_AES_128_GCM_SHA256); + wb_ssl_c->buffers.tls13CookieSecret.length = savedLen; + + /* buffer non-NULL, length non-zero -> (F,F); runs the HMAC + cookie use */ + (void)CreateCookieExt(wb_ssl_c, hash, (word16)sizeof(hash), &exts, + TLS13_BYTE, TLS_AES_128_GCM_SHA256); + + TLSX_FreeAll(exts, wb_ssl_c->heap); + + WB_NOTE("CreateCookieExt hash and cookie-secret guards driven with both " + "halves of each independence pair"); +} +#else +static void wb_create_cookie_ext_guards(void) +{ WB_NOTE("CreateCookieExt not compiled in this variant; skipped"); } +#endif + +#else /* !WB_HAVE_SSL_FIXTURE */ +static void wb_key_schedule_null_guards(void) +{ WB_NOTE("no WOLFSSL fixture on this build axis; skipped"); } +static void wb_build_handshake_hmac_guard(void) +{ WB_NOTE("no WOLFSSL fixture on this build axis; skipped"); } +static void wb_create_cookie_ext_guards(void) +{ WB_NOTE("no WOLFSSL fixture on this build axis; skipped"); } +#endif /* WB_HAVE_SSL_FIXTURE */ + + +/* ------------------------------------------------------------------------- * + * SanityCheckTls13MsgReceived(): the handshake-message ordering matrix. + * + * This file-static predicate is the densest single cluster of open conditions + * in tls13.c. It is a PURE function of ssl->options, ssl->msgsReceived, + * ssl->earlyData and ssl->certReqCtx -- it allocates nothing, reads no buffer + * and performs no crypto -- yet from tests/api most of its operand + * combinations are unreachable, because reaching a given message type at all + * means the state machine has already forced the very fields the decision + * tests. A handshake that is far enough along to deliver a Finished, for + * instance, cannot simultaneously have mutualAuth set and havePeerCert clear. + * + * Calling it directly with a synthesised WOLFSSL state is the only way to + * complete these pairs. Each vector below restores the fixture to a known base + * first (wb_sc_reset), so the vectors are order-independent and the fixture is + * handed back to wolfSSL_free() in its initial shape. + * ------------------------------------------------------------------------- */ +#ifdef WB_HAVE_SSL_FIXTURE +static void wb_sc_reset(void) +{ + WOLFSSL* ssl = wb_ssl_c; + + XMEMSET(&ssl->msgsReceived, 0, sizeof(ssl->msgsReceived)); + ssl->options.side = WOLFSSL_CLIENT_END; + ssl->options.clientState = NULL_STATE; + ssl->options.serverState = NULL_STATE; + ssl->options.connectState = CONNECT_BEGIN; + ssl->options.pskNegotiated = 0; + ssl->options.postHandshakeAuth = 0; + ssl->options.verifyPeer = 0; + ssl->options.verifyPostHandshake = 0; + ssl->options.mutualAuth = 0; + ssl->options.havePeerCert = 0; + ssl->options.havePeerVerify = 0; + ssl->options.dtls = 0; + ssl->options.downgrade = 0; + ssl->options.minDowngrade = 0; + ssl->certReqCtx = NULL; +#ifdef WOLFSSL_EARLY_DATA + ssl->earlyData = no_early_data; +#endif +} + +#define WB_SC(type) (void)SanityCheckTls13MsgReceived(wb_ssl_c, (byte)(type)) + +static void wb_sanity_check_client_hello(void) +{ +#ifndef NO_WOLFSSL_SERVER + WOLFSSL* ssl = wb_ssl_c; + + /* 2nd ClientHello duplicate check: + * got_client_hello == 1 && serverState != SERVER_HELLO_RETRY_REQUEST_COMPLETE + * Reached only on the server with clientState < CLIENT_HELLO_COMPLETE. A + * server that has seen one ClientHello has, by construction, either + * answered it (clientState advances) or emitted an HRR, so the (T,F) and + * (F,-) partners never coexist with (T,T) in one live handshake. */ + wb_sc_reset(); ssl->options.side = WOLFSSL_SERVER_END; + ssl->msgsReceived.got_client_hello = 1; /* (T,T) -> dup */ + WB_SC(client_hello); + + wb_sc_reset(); ssl->options.side = WOLFSSL_SERVER_END; /* (F,-) -> accept */ + WB_SC(client_hello); + + wb_sc_reset(); ssl->options.side = WOLFSSL_SERVER_END; + ssl->msgsReceived.got_client_hello = 1; + ssl->options.serverState = SERVER_HELLO_RETRY_REQUEST_COMPLETE; /* (T,F) */ + WB_SC(client_hello); +#endif +} + +static void wb_sanity_check_certificate(void) +{ + WOLFSSL* ssl = wb_ssl_c; + + /* Client-side ordering guard: + * side == CLIENT && serverState != SERVER_ENCRYPTED_EXTENSIONS_COMPLETE */ +#ifndef NO_WOLFSSL_CLIENT + wb_sc_reset(); /* (T,T) -> ooo */ + WB_SC(certificate); + + wb_sc_reset(); + ssl->options.serverState = SERVER_ENCRYPTED_EXTENSIONS_COMPLETE; /* (T,F) */ + WB_SC(certificate); +#endif + +#ifndef NO_WOLFSSL_SERVER + /* Server-side ordering guard: + * side == SERVER && clientState != CLIENT_HELLO_COMPLETE && + * serverState < SERVER_FINISHED_COMPLETE + * The (F,-,-) partner is the client vector just above, which also supplies + * the (T,F) half of the client guard -- one call, two pairs. */ + wb_sc_reset(); ssl->options.side = WOLFSSL_SERVER_END; /* (T,T,T) -> ooo */ + WB_SC(certificate); + + wb_sc_reset(); ssl->options.side = WOLFSSL_SERVER_END; + ssl->options.clientState = CLIENT_HELLO_COMPLETE; /* (T,F,-) */ + WB_SC(certificate); + + wb_sc_reset(); ssl->options.side = WOLFSSL_SERVER_END; + ssl->options.serverState = SERVER_FINISHED_COMPLETE; /* (T,T,F) */ + WB_SC(certificate); + + wb_sc_reset(); + ssl->options.serverState = SERVER_ENCRYPTED_EXTENSIONS_COMPLETE; + WB_SC(certificate); /* (F,-,-) */ +#endif +} + +#ifndef NO_WOLFSSL_CLIENT +static void wb_sanity_check_certificate_request(void) +{ + WOLFSSL* ssl = wb_ssl_c; + + /* Ordering guard: + * serverState != SERVER_ENCRYPTED_EXTENSIONS_COMPLETE && + * (serverState < SERVER_FINISHED_COMPLETE || + * clientState != CLIENT_FINISHED_COMPLETE) */ + wb_sc_reset(); /* (T,T,-) -> ooo */ + WB_SC(certificate_request); + + wb_sc_reset(); + ssl->options.serverState = SERVER_ENCRYPTED_EXTENSIONS_COMPLETE; /* (F,-,-) */ + WB_SC(certificate_request); + + wb_sc_reset(); + ssl->options.serverState = SERVER_FINISHED_COMPLETE; /* (T,F,T) -> ooo */ + WB_SC(certificate_request); + + /* (T,F,F): the post-handshake window. Also the only way to reach the + * post_handshake_auth guard below with its first two operands true. */ + wb_sc_reset(); + ssl->options.serverState = SERVER_FINISHED_COMPLETE; + ssl->options.clientState = CLIENT_FINISHED_COMPLETE; + ssl->options.postHandshakeAuth = 1; /* pha guard (T,T,F) */ + WB_SC(certificate_request); + + wb_sc_reset(); + ssl->options.serverState = SERVER_FINISHED_COMPLETE; + ssl->options.clientState = CLIENT_FINISHED_COMPLETE; + ssl->options.postHandshakeAuth = 0; /* pha guard (T,T,T) */ + WB_SC(certificate_request); + + /* Duplicate guard: + * got_certificate_request && clientState != CLIENT_FINISHED_COMPLETE */ + wb_sc_reset(); + ssl->options.serverState = SERVER_ENCRYPTED_EXTENSIONS_COMPLETE; + ssl->msgsReceived.got_certificate_request = 1; /* (T,T) -> dup */ + WB_SC(certificate_request); + + wb_sc_reset(); + ssl->options.serverState = SERVER_ENCRYPTED_EXTENSIONS_COMPLETE; + ssl->msgsReceived.got_certificate_request = 1; + ssl->options.clientState = CLIENT_FINISHED_COMPLETE; /* (T,F) */ + WB_SC(certificate_request); + /* (F,-) is any of the accepting vectors above. */ +} +#else +static void wb_sanity_check_certificate_request(void) { } +#endif + +static void wb_sanity_check_finished(void) +{ + WOLFSSL* ssl = wb_ssl_c; + CertReqCtx reqCtx; + + XMEMSET(&reqCtx, 0, sizeof(reqCtx)); + +#if !defined(NO_WOLFSSL_SERVER) && defined(WOLFSSL_EARLY_DATA) + /* Server early-data guard: + * earlyData == process_early_data && !dtls && !WOLFSSL_IS_QUIC(ssl) */ + wb_sc_reset(); + ssl->options.side = WOLFSSL_SERVER_END; + ssl->options.serverState = SERVER_FINISHED_COMPLETE; + ssl->options.clientState = CLIENT_HELLO_COMPLETE; + ssl->earlyData = process_early_data; /* (T,T,T) -> ooo */ + WB_SC(finished); + + wb_sc_reset(); + ssl->options.side = WOLFSSL_SERVER_END; + ssl->options.serverState = SERVER_FINISHED_COMPLETE; + ssl->options.clientState = CLIENT_HELLO_COMPLETE; /* (F,-,-) */ + WB_SC(finished); + + wb_sc_reset(); + ssl->options.side = WOLFSSL_SERVER_END; + ssl->options.serverState = SERVER_FINISHED_COMPLETE; + ssl->options.clientState = CLIENT_HELLO_COMPLETE; + ssl->earlyData = process_early_data; + ssl->options.dtls = 1; /* (T,F,-) */ + WB_SC(finished); + ssl->options.dtls = 0; +#endif + + /* The three peer-certificate guards. All are reached on the server with + * serverState >= SERVER_FINISHED_COMPLETE and clientState >= + * CLIENT_HELLO_COMPLETE, or on the client with serverState == + * SERVER_CERT_VERIFY_COMPLETE. pskNegotiated must be 0 for the block to be + * entered at all. */ +#define WB_SC_FIN_SERVER() \ + do { wb_sc_reset(); \ + ssl->options.side = WOLFSSL_SERVER_END; \ + ssl->options.serverState = SERVER_FINISHED_COMPLETE; \ + ssl->options.clientState = CLIENT_HELLO_COMPLETE; \ + } while (0) +#define WB_SC_FIN_CLIENT() \ + do { wb_sc_reset(); \ + ssl->options.side = WOLFSSL_CLIENT_END; \ + ssl->options.clientState = CLIENT_HELLO_COMPLETE; \ + ssl->options.serverState = SERVER_CERT_VERIFY_COMPLETE; \ + } while (0) + +#ifndef NO_WOLFSSL_SERVER + /* Guard 1: verifyPeer && + * (!verifyPostHandshake || + * (side == SERVER && certReqCtx != NULL)) && + * !got_certificate */ + WB_SC_FIN_SERVER(); ssl->options.verifyPeer = 1; /* (T,T,-,-,T) */ + WB_SC(finished); + + WB_SC_FIN_SERVER(); /* (F,-,-,-,-) */ + WB_SC(finished); + + WB_SC_FIN_SERVER(); ssl->options.verifyPeer = 1; + ssl->msgsReceived.got_certificate = 1; /* (T,T,-,-,F) */ + WB_SC(finished); + +#ifdef WOLFSSL_POST_HANDSHAKE_AUTH + WB_SC_FIN_SERVER(); ssl->options.verifyPeer = 1; + ssl->options.verifyPostHandshake = 1; + ssl->certReqCtx = &reqCtx; /* (T,F,T,T,T) */ + WB_SC(finished); + ssl->certReqCtx = NULL; + + WB_SC_FIN_SERVER(); ssl->options.verifyPeer = 1; + ssl->options.verifyPostHandshake = 1; /* (T,F,T,F,-) */ + WB_SC(finished); +#endif +#endif /* !NO_WOLFSSL_SERVER */ + +#ifndef NO_WOLFSSL_CLIENT +#ifdef WOLFSSL_POST_HANDSHAKE_AUTH + /* (T,F,F,-,-): a client never satisfies the side == SERVER operand. */ + WB_SC_FIN_CLIENT(); ssl->options.verifyPeer = 1; + ssl->options.verifyPostHandshake = 1; + ssl->msgsReceived.got_certificate = 1; + WB_SC(finished); +#endif +#endif + +#ifndef NO_WOLFSSL_SERVER + /* Guard 2: (mutualAuth || (side == CLIENT && verifyPeer)) && + * !havePeerCert */ + WB_SC_FIN_SERVER(); ssl->options.mutualAuth = 1; /* (T,-,-,T) */ + WB_SC(finished); + + WB_SC_FIN_SERVER(); ssl->options.mutualAuth = 1; + ssl->options.havePeerCert = 1; /* (T,-,-,F) */ + WB_SC(finished); + + WB_SC_FIN_SERVER(); /* (F,F,-,-) */ + WB_SC(finished); +#endif + +#ifndef NO_WOLFSSL_CLIENT + WB_SC_FIN_CLIENT(); ssl->options.verifyPeer = 1; + ssl->msgsReceived.got_certificate = 1; /* (F,T,T,T) */ + WB_SC(finished); + + WB_SC_FIN_CLIENT(); /* (F,T,F,-) */ + WB_SC(finished); +#endif + +#ifndef NO_WOLFSSL_SERVER + /* Guard 3: (mutualAuth || verifyPeer) && havePeerCert && !havePeerVerify */ + WB_SC_FIN_SERVER(); ssl->options.mutualAuth = 1; + ssl->options.havePeerCert = 1; /* (T,-,T,T) */ + WB_SC(finished); + + WB_SC_FIN_SERVER(); ssl->options.mutualAuth = 1; + ssl->options.havePeerCert = 1; + ssl->options.havePeerVerify = 1; /* (T,-,T,F) */ + WB_SC(finished); + + /* mutualAuth clear, verifyPeer set: guard 2's subexpression is false on + * the server, so guard 3 is reached with cond0 false and cond1 true. */ + WB_SC_FIN_SERVER(); ssl->options.verifyPeer = 1; + ssl->msgsReceived.got_certificate = 1; + ssl->options.havePeerCert = 1; /* (F,T,T,T) */ + WB_SC(finished); + + WB_SC_FIN_SERVER(); ssl->options.verifyPeer = 1; + ssl->msgsReceived.got_certificate = 1; /* (F,T,F,-) */ + WB_SC(finished); + + WB_SC_FIN_SERVER(); /* (F,F,-,-) */ + WB_SC(finished); +#endif + +#undef WB_SC_FIN_SERVER +#undef WB_SC_FIN_CLIENT + (void)reqCtx; +} + +#if defined(WOLFSSL_DTLS13) && !defined(WOLFSSL_NO_TLS12) +static void wb_sanity_check_hello_verify_request(void) +{ + WOLFSSL* ssl = wb_ssl_c; + + /* Ordering guard: + * serverState >= SERVER_HELLO_RETRY_REQUEST_COMPLETE || + * connectState != CLIENT_HELLO_SENT */ + wb_sc_reset(); ssl->options.dtls = 1; + ssl->options.serverState = SERVER_HELLO_RETRY_REQUEST_COMPLETE; /* (T,-) */ + WB_SC(hello_verify_request); + + wb_sc_reset(); ssl->options.dtls = 1; /* (F,T) */ + WB_SC(hello_verify_request); + + /* (F,F) falls through to the downgrade guard: + * !downgrade || minDowngrade < DTLSv1_2_MINOR */ + wb_sc_reset(); ssl->options.dtls = 1; + ssl->options.connectState = CLIENT_HELLO_SENT; /* (F,F); (T,-) */ + WB_SC(hello_verify_request); + + wb_sc_reset(); ssl->options.dtls = 1; + ssl->options.connectState = CLIENT_HELLO_SENT; + ssl->options.downgrade = 1; + ssl->options.minDowngrade = 0; /* (F,T) */ + WB_SC(hello_verify_request); + + wb_sc_reset(); ssl->options.dtls = 1; + ssl->options.connectState = CLIENT_HELLO_SENT; + ssl->options.downgrade = 1; + ssl->options.minDowngrade = DTLSv1_2_MINOR; /* (F,F) -> accept */ + WB_SC(hello_verify_request); + + wb_sc_reset(); +} +#else +static void wb_sanity_check_hello_verify_request(void) { } +#endif + +static void wb_sanity_check_msgs(void) +{ + if (wb_ssl_c == NULL) { + WB_NOTE("no ssl fixture; SanityCheckTls13MsgReceived skipped"); + return; + } + + wb_sanity_check_client_hello(); + wb_sanity_check_certificate(); + wb_sanity_check_certificate_request(); + wb_sanity_check_finished(); + wb_sanity_check_hello_verify_request(); + wb_sc_reset(); + + WB_NOTE("SanityCheckTls13MsgReceived ordering matrix driven"); +} +#undef WB_SC +#else +static void wb_sanity_check_msgs(void) +{ WB_NOTE("no WOLFSSL fixture on this build axis; skipped"); } +#endif /* WB_HAVE_SSL_FIXTURE */ + int main(void) { setvbuf(stdout, NULL, _IONBF, 0); printf("tls13.c white-box MC/DC supplement\n"); +#ifdef WB_HAVE_SSL_FIXTURE + if (wolfSSL_Init() != WOLFSSL_SUCCESS) { + WB_NOTE("wolfSSL_Init failed; fixture-based groups skipped"); + } + else if (!wb_fixture_setup()) { + WB_NOTE("could not build the WOLFSSL fixtures; groups skipped"); + } +#endif + wb_decode_tls13_sigalg(); + wb_key_schedule_null_guards(); + wb_build_handshake_hmac_guard(); + wb_create_cookie_ext_guards(); + wb_sanity_check_msgs(); + +#ifdef WB_HAVE_SSL_FIXTURE + wb_fixture_teardown(); + wolfSSL_Cleanup(); +#endif printf("done\n"); /* Always 0: a nonzero exit is scored as a failed white-box and its From 57cad5fa2873441ca25f25ef0ce0298c66840a96 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 09:45:53 +0200 Subject: [PATCH 06/57] tests: xmss white-box drives the forged-BDS and unsupported-idx_len MC/DC rows --- tests/unit-mcdc/test_wc_xmss_impl_whitebox.c | 257 ++++++++++++++++++- 1 file changed, 247 insertions(+), 10 deletions(-) diff --git a/tests/unit-mcdc/test_wc_xmss_impl_whitebox.c b/tests/unit-mcdc/test_wc_xmss_impl_whitebox.c index fd55d0fb12..93660d286e 100644 --- a/tests/unit-mcdc/test_wc_xmss_impl_whitebox.c +++ b/tests/unit-mcdc/test_wc_xmss_impl_whitebox.c @@ -254,12 +254,14 @@ static void wb_exhausted_index(WC_RNG* rng, const char* paramStr, int doSign) /* The FALSE row for this parameter set: one ordinary signature, so the * decision is evaluated with a valid index and the same idx_len. Under - * WOLFSSL_WC_XMSS_SMALL a height-40 signature recomputes every subtree and - * does not fit the campaign's TEST_TIMEOUT -- a timed-out white-box is - * scored as a SILENT SKIP and would lose the whole file -- so that one row - * is skipped there. wc_XmssKey_SigsLeft() is cheap in every build and - * still supplies the live-index row for wc_xmss_sigsleft()'s copy of the - * same macro. */ + * WOLFSSL_WC_XMSS_SMALL a height-40 signature recomputes every subtree, so + * it is a single signature per key here and the four-vector allocator + * sweep below (which has nothing to fail in the small build) is compiled + * out; the measured white-box runtime is in the run log. This row is what + * pairs IDX64_INVALID inside the small build's own wc_xmssmt_sign() + * (2455): its retired-index partner alone shows only the true side. + * wc_XmssKey_SigsLeft() is cheap in every build and still supplies the + * live-index row for wc_xmss_sigsleft()'s copy of the same macro. */ if (doSign) { sigSz = sigLen; if (wc_XmssKey_Sign(&key, sig, &sigSz, msg, (int)sizeof(msg)) != 0) { @@ -283,6 +285,7 @@ static void wb_exhausted_index(WC_RNG* rng, const char* paramStr, int doSign) * wc_xmssmt_sign() reach the guard with ret != 0. A short dense sweep -- * a vector count, not a clock -- covers the handful of allocations the * sign path makes. */ +#ifndef WOLFSSL_WC_XMSS_SMALL if (doSign) { int n; @@ -294,7 +297,22 @@ static void wb_exhausted_index(WC_RNG* rng, const char* paramStr, int doSign) mcdc_fa_disarm(); } mcdc_fa_restore(); + /* The first injected failure moves the XmssKey to WC_XMSS_STATE_BAD + * and wc_XmssKey_Sign()/_SigsLeft() then short-circuit in wc_xmss.c + * WITHOUT calling into this file - which silently dropped every + * retired-index row below (measured: the 64-bit arm of the macro at + * 2455/4117 was left unpaired in each non-small variant). The + * injected failures happen before any secret-key mutation, so the + * persisted key is still consistent; put the handle back in a good + * state so the rows that follow actually reach wc_xmss_impl.c. */ + key.state = WC_XMSS_STATE_OK; } +#else + /* wc_xmss_impl.c's only XMALLOC is the BDS-state allocation, which lives + * in the non-small block, so the injector has nothing to fail here: the + * sweep would only burn four height-40 recompute signatures. */ + (void)0; +#endif /* The TRUE row: retire the persisted index. Both entry points reload the * secret key through the read callback, so this is all that is needed. */ @@ -371,11 +389,7 @@ static void wb_run(void) #endif #if defined(WC_XMSS_SHA256) && (WOLFSSL_XMSS_MAX_HEIGHT >= 40) && \ (!defined(WOLFSSL_XMSS_MIN_HEIGHT) || (WOLFSSL_XMSS_MIN_HEIGHT <= 40)) -#ifdef WOLFSSL_WC_XMSS_SMALL - wb_exhausted_index(&rng, "XMSSMT-SHA2_40/8_256", 0); -#else wb_exhausted_index(&rng, "XMSSMT-SHA2_40/8_256", 1); -#endif #endif wc_FreeRng(&rng); @@ -1217,6 +1231,219 @@ static void wb_full_cycle_d1(void) } #endif /* !WOLFSSL_XMSS_VERIFY_ONLY && !WOLFSSL_WC_XMSS_SMALL */ +/******************************************** + * 3981-3983: wc_xmssmt_sign_next_idx()'s + * "if ((ret == 0) && (i > 0) && (updates > 0) && + * (idx_tree < ((XmssIdx)1 << (h - (hs * (i + 1))))) && + * (bds[alt_i].next < ((XmssIdx)1 << h)))" + * + * condIndex 0 ("ret == 0") and condIndex 4 ("bds[alt_i].next < (1 << h)") + * have no independence pair from ordinary signing: + * + * - condIndex 4: BdsState.next is a *sub*tree leaf counter. It is reset to 0 + * at a subtree boundary and wc_xmss_bds_update() itself stops incrementing + * it at (1 << sub_h), while the bound tested here is (1 << h) with h the + * FULL tree height. On every reachable signing vector next <= 2^sub_h <= + * 2^h, so the operand is true. It is only false when the value loaded out + * of the persisted secret key (a 24-bit big-endian field, wc_xmss_bds_ + * state_load() at 2729) is already >= 2^h - i.e. a corrupted/forged + * private key, which is exactly the case this defensive guard exists for. + * Here that state is forged directly: the BDS array is loaded from a COPY + * of a good secret key and every state's "next" is set to 1 << h before + * wc_xmssmt_sign_next_idx() is called. Memory-safe: "next" is read in + * exactly two places - this guard, and wc_xmss_bds_update()'s own + * "next < (1 << sub_h)" entry test, which the forged value turns into a + * no-op. It indexes nothing. + * + * - condIndex 0: reaching this guard with ret != 0 needs the *same* loop + * iteration's wc_xmss_bds_auth_path() (or wc_xmss_bds_treehash_updates()) + * to have failed; a failure in any earlier iteration leaves through the + * for-header instead. Both only fail on a NULL BDS sub-buffer or a digest + * failure. Forged the same way: bds[BDS_IDX(...)].keep is NULLed, so + * wc_xmss_bds_auth_path() takes its "(bds->keep == NULL) || + * (bds->authPath == NULL)" bail-out, sets state->ret = WC_FAILURE and + * returns before touching anything. + * + * Both forged rows are driven against the ORDINARY, all-operands-true row + * from a real wc_xmssmt_sign() at the same index, in this same binary, so + * the independence pairs are complete here and do not lean on any other + * build. Parameters are hand-built with the same per-layer geometry as + * XMSSMT-SHA2_40/8_256 (sub_h = 5, bds_k = 0, so updates = 2) but only + * h = 20 / d = 4, which keeps keygen and eight signatures cheap. + ********************************************/ +#if !defined(WOLFSSL_XMSS_VERIFY_ONLY) && !defined(WOLFSSL_WC_XMSS_SMALL) && \ + defined(WC_XMSS_SHA256) +static byte wb_ni_sk[16384]; +static byte wb_ni_skCopy[16384]; +static byte wb_ni_sig[16384]; + +static void wb_sign_next_idx_rows(void) +{ + XmssParams params; + XmssState state; + byte seed[3 * 32]; + byte pk[160]; + static const byte msg[] = "xmss whitebox next-idx message"; + int ret; + int k; + + wb_params_init(¶ms, WC_HASH_TYPE_SHA256, 32, 32, 20, 4, 4, 0); + if ((params.sk_len > (word32)sizeof(wb_ni_sk)) || + (params.sig_len > (word32)sizeof(wb_ni_sig))) { + WB_NOTE("next-idx rows: scratch too small; skipped"); + return; + } + if (wb_state_init(&state, ¶ms) != 0) { + WB_NOTE("next-idx rows: state init failed; skipped"); + return; + } + + XMEMSET(seed, 0x33, sizeof(seed)); + XMEMSET(wb_ni_sk, 0, sizeof(wb_ni_sk)); + XMEMSET(pk, 0, sizeof(pk)); + + ret = wc_xmssmt_keygen(&state, seed, wb_ni_sk, pk); + if (ret != 0) { + WB_NOTE("next-idx rows: keygen failed; skipped"); + wb_state_free(&state); + return; + } + + for (k = 0; k < 8; k++) { + BdsState* bds; + byte* wots_sigs; + int j; + + /* Row A - forged BDS state: every state's next is already past the + * full-tree bound, so condIndex 4 is false with condIndex 0..3 true. */ + XMEMCPY(wb_ni_skCopy, wb_ni_sk, sizeof(wb_ni_skCopy)); + bds = NULL; + wots_sigs = NULL; + if (wc_xmss_bds_state_alloc(¶ms, &bds, state.heap) == 0) { + if (wc_xmss_bds_state_load(&state, wb_ni_skCopy, bds, + &wots_sigs) == 0) { + for (j = 0; j < 2 * (int)params.d - 1; j++) { + bds[j].next = (word32)1U << params.h; + } + state.ret = 0; + (void)wc_xmssmt_sign_next_idx(&state, bds, (XmssIdx)k, + wots_sigs, wb_ni_skCopy); + state.ret = 0; + } + wc_xmss_bds_state_free(bds, state.heap); + } + + /* Row B - forged BDS state: the working state's keep buffer is NULL, + * so this iteration's wc_xmss_bds_auth_path() fails and the guard is + * reached with ret != 0 (condIndex 0 false). */ + XMEMCPY(wb_ni_skCopy, wb_ni_sk, sizeof(wb_ni_skCopy)); + bds = NULL; + wots_sigs = NULL; + if (wc_xmss_bds_state_alloc(¶ms, &bds, state.heap) == 0) { + if (wc_xmss_bds_state_load(&state, wb_ni_skCopy, bds, + &wots_sigs) == 0) { + bds[BDS_IDX((XmssIdx)k, 0, params.sub_h, params.d)].keep = + NULL; + state.ret = 0; + (void)wc_xmssmt_sign_next_idx(&state, bds, (XmssIdx)k, + wots_sigs, wb_ni_skCopy); + state.ret = 0; + } + wc_xmss_bds_state_free(bds, state.heap); + } + + /* Row C - the ordinary all-true row: a real signature at the same + * index, whose wc_xmssmt_sign() runs wc_xmssmt_sign_next_idx() + * unforged. */ + XMEMSET(wb_ni_sig, 0, sizeof(wb_ni_sig)); + ret = wc_xmssmt_sign(&state, msg, (word32)sizeof(msg), wb_ni_sk, + wb_ni_sig); + if (ret != 0) { + WB_NOTE("next-idx rows: ordinary sign failed"); + wb_fail = 1; + break; + } + } + + wb_state_free(&state); + WB_NOTE("next-idx forged-BDS rows exercised"); +} +#else +static void wb_sign_next_idx_rows(void) +{ + WB_NOTE("BDS signing path not compiled in; wb_sign_next_idx_rows " + "skipped"); +} +#endif /* !WOLFSSL_XMSS_VERIFY_ONLY && !WOLFSSL_WC_XMSS_SMALL && + * WC_XMSS_SHA256 */ + +/******************************************** + * 2455 (WOLFSSL_WC_XMSS_SMALL's wc_xmssmt_sign() only): + * "if ((ret == 0) && (WC_IDX_INVALID(idx, params->idx_len, params->h)))" + * condIndex 0's false side. The only assignment to ret before the guard is + * WC_IDX_DECODE's trailing "else { ret = NOT_COMPILED_IN; }", which fires + * only when params->idx_len is none of 3, 4, 5 and 8 - and every set in + * wc_xmss_alg[] uses one of those four, so no key can produce it. A + * hand-built parameter set with idx_len == 2 does, exactly as + * wb_full_cycle_d1() already does for wc_xmss_sigsleft()'s copy of the same + * macro. + * + * Memory-safe: before the guard the function only zeroes state->addr and + * copies idx_len (2) bytes sk->sig; sk_seed/pk_seed/sig_r are pointer + * arithmetic that is never dereferenced on this path, and with ret != 0 + * every later block is gated by "if (ret == 0)" down to "return ret", so + * nothing is allocated or hashed. The buffers are still sized past + * params.sk_len so even the unused interior pointers stay in-object. + * + * Only built for WOLFSSL_WC_XMSS_SMALL: the non-small wc_xmssmt_sign() + * (4041) allocates and loads the BDS state from sk *before* its own index + * check and decodes with xmss_idx_decode(), which has no NOT_COMPILED_IN + * arm - it would neither reach this decision nor be memory-safe with a + * forged parameter set. + ********************************************/ +#if defined(WOLFSSL_WC_XMSS_SMALL) && !defined(WOLFSSL_XMSS_VERIFY_ONLY) && \ + defined(WC_XMSS_SHA256) +static void wb_smallmt_bad_idx_len(void) +{ + XmssParams params; + XmssState state; + byte sk[1024]; + byte sig[1024]; + static const byte msg[] = "xmss whitebox bad idx_len message"; + int ret; + + wb_params_init(¶ms, WC_HASH_TYPE_SHA256, 32, 32, 4, 1, 2, 0); + if (params.sk_len > (word32)sizeof(sk)) { + WB_NOTE("bad idx_len row: scratch too small; skipped"); + return; + } + if (wb_state_init(&state, ¶ms) != 0) { + WB_NOTE("bad idx_len row: state init failed; skipped"); + return; + } + + XMEMSET(sk, 0, sizeof(sk)); + XMEMSET(sig, 0, sizeof(sig)); + + ret = wc_xmssmt_sign(&state, msg, (word32)sizeof(msg), sk, sig); + if (ret != WC_NO_ERR_TRACE(NOT_COMPILED_IN)) { + WB_NOTE("bad idx_len row: wc_xmssmt_sign did not reject an " + "unsupported idx_len"); + wb_fail = 1; + } + + wb_state_free(&state); + WB_NOTE("small-path unsupported idx_len row exercised"); +} +#else +static void wb_smallmt_bad_idx_len(void) +{ + WB_NOTE("small XMSS^MT signing path not compiled in; " + "wb_smallmt_bad_idx_len skipped"); +} +#endif /* WOLFSSL_WC_XMSS_SMALL && !WOLFSSL_XMSS_VERIFY_ONLY && + * WC_XMSS_SHA256 */ + #else /* WOLFSSL_HAVE_XMSS */ static void wb_hash_family_pairs(void) @@ -1243,6 +1470,14 @@ static void wb_full_cycle_d1(void) { WB_NOTE("WOLFSSL_HAVE_XMSS not compiled in; skipped"); } +static void wb_sign_next_idx_rows(void) +{ + WB_NOTE("WOLFSSL_HAVE_XMSS not compiled in; skipped"); +} +static void wb_smallmt_bad_idx_len(void) +{ + WB_NOTE("WOLFSSL_HAVE_XMSS not compiled in; skipped"); +} #endif /* WOLFSSL_HAVE_XMSS */ @@ -1256,6 +1491,8 @@ int main(void) wb_bds_auth_path(); wb_full_cycle_d2(); wb_full_cycle_d1(); + wb_sign_next_idx_rows(); + wb_smallmt_bad_idx_len(); printf("done (%s)\n", wb_fail ? "with failures" : "ok"); return 0; } From ebb85b7dc81a731434ee661c99e2feee4648ebaa Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 09:47:49 +0200 Subject: [PATCH 07/57] tests: lms MC/DC white-box for BDS auth-path state and the post-keygen SigsLeft guard --- tests/include.am | 6 + tests/unit-mcdc/test_lms_bds_whitebox.c | 741 ++++++++++++++++++++++ tests/unit-mcdc/test_lms_fault_whitebox.c | 109 +++- 3 files changed, 846 insertions(+), 10 deletions(-) create mode 100644 tests/unit-mcdc/test_lms_bds_whitebox.c diff --git a/tests/include.am b/tests/include.am index 0c82436148..b388c20290 100644 --- a/tests/include.am +++ b/tests/include.am @@ -164,6 +164,7 @@ EXTRA_DIST += \ tests/unit-mcdc/test_integer_whitebox.c \ tests/unit-mcdc/test_kdf_hash_fault_whitebox.c \ tests/unit-mcdc/test_kdf_whitebox.c \ + tests/unit-mcdc/test_lms_bds_whitebox.c \ tests/unit-mcdc/test_lms_fault_whitebox.c \ tests/unit-mcdc/test_lms_hash_fault_whitebox.c \ tests/unit-mcdc/test_logging_globalq_whitebox.c \ @@ -210,6 +211,8 @@ EXTRA_DIST += \ tests/unit-mcdc/test_sp_c32_whitebox.c \ tests/unit-mcdc/test_sp_c64_fault_whitebox.c \ tests/unit-mcdc/test_sp_c64_whitebox.c \ + tests/unit-mcdc/test_sp_cortexm_crafted_whitebox.c \ + tests/unit-mcdc/test_sp_cortexm_fault_whitebox.c \ tests/unit-mcdc/test_sp_cortexm_whitebox.c \ tests/unit-mcdc/test_sp_crafted_common.h \ tests/unit-mcdc/test_sp_fault_common.h \ @@ -217,6 +220,8 @@ EXTRA_DIST += \ tests/unit-mcdc/test_sp_int_whitebox.c \ tests/unit-mcdc/test_sp_x86_64_fault_whitebox.c \ tests/unit-mcdc/test_sp_x86_64_whitebox.c \ + tests/unit-mcdc/test_srp_whitebox.c \ + tests/unit-mcdc/test_tfm_fault_whitebox.c \ tests/unit-mcdc/test_tfm_whitebox.c \ tests/unit-mcdc/test_tls13_whitebox.c \ tests/unit-mcdc/test_tsp_fault_whitebox.c \ @@ -229,5 +234,6 @@ EXTRA_DIST += \ tests/unit-mcdc/test_wc_port_whitebox.c \ tests/unit-mcdc/test_wc_xmss_impl_whitebox.c \ tests/unit-mcdc/test_wolfentropy_whitebox.c \ + tests/unit-mcdc/test_wolfmath_whitebox.c \ tests/unit-mcdc/test_xmss_fault_whitebox.c \ tests/unit-mcdc/test_xmss_hash_fault_whitebox.c diff --git a/tests/unit-mcdc/test_lms_bds_whitebox.c b/tests/unit-mcdc/test_lms_bds_whitebox.c new file mode 100644 index 0000000000..86b80bd06f --- /dev/null +++ b/tests/unit-mcdc/test_lms_bds_whitebox.c @@ -0,0 +1,741 @@ +/* test_lms_bds_whitebox.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* + * MC/DC BDS-state white-box supplement for wolfcrypt/src/wc_lms_impl.c. + * + * WHAT THIS FILE ADDS OVER THE OTHER THREE LMS WHITE-BOXES + * ------------------------------------------------------- + * test_wc_lms_impl_whitebox.c / _gap.c drive the file-static helpers with + * bad arguments; test_lms_hash_fault_whitebox.c sweeps a *global* hash-call + * index over whole make_key / sign / verify / reload operations. That sweep + * is necessarily strided: one WOTS leaf is p * (2^w - 1) = 34 * 255 = 8670 + * primitive calls, so the handful of Merkle *interior* node hashes are one + * call in ~8700 and a strided sweep essentially never lands on them. The + * decisions that need `ret != 0` at an interior-node step therefore stayed + * open no matter how many sweep points were spent on them. + * + * This file replaces the search with arithmetic. Every target is reached by + * calling the static helper DIRECTLY with a hand-built LmsParams / + * LmsPrivState, and where a fault is needed the fault index is COMPUTED: + * + * L = mcdc_fh_seen() after one wc_lms_leaf_hash() (constant per leaf) + * C = mcdc_fh_seen() after one wc_lms_interior_hash() (constant per node) + * + * In wc_lms_treehash{,_init}() the loop body is exactly "leaf hash, then the + * carry chain of interior hashes", so leaf 0 occupies primitive calls + * [1 .. L], leaf 1 occupies [L+1 .. 2L], and the FIRST interior hash (i=1, + * h=1) occupies [2L+1 .. 2L+C]. Arming there fails that interior hash and + * nothing before it, which is precisely the state the `(ret == 0)` operand + * of the auth-path store needs. A small window around the computed index is + * swept so the vector survives a change in how many primitives one hash + * costs (raw-block vs WC_LMS_FULL_HASH vs SHAKE). + * + * wc_lms_compute_root() is small enough (one leaf hash + `height` node + * hashes) that its whole primitive range is swept DENSELY, which is what + * reaches the per-hash-family copies of the auth-path climb loop. + * + * TARGETS (campaign/reports/lms/GAPS.md keys, wc_lms_impl.c) + * 2109:...:0 wc_lms_treehash() `ret == 0` at the auth-path store + * (WOLFSSL_WC_LMS_SMALL arm) -- fault + * 2262:...:0 wc_lms_treehash_init() same, table-based arm -- fault + * 2397:...:2 wc_lms_treehash_update() `h <= params->height` -- direct call + * with a leaf index whose trailing-ones run exceeds the tree + * height; no in-tree caller can pass one, which is exactly why + * the guard exists and why only a white-box can falsify it. + * 2414:...:2 wc_lms_treehash_update() `!useRoot` -- direct call + * with q == 0 AND useRoot != 0 (real callers only ever pair + * useRoot=1 with q != 0), plus the all-true partner row in the + * same binary (q == 0, useRoot = 0). + * 2659:...:0 wc_lms_compute_root() SHAKE256 climb loop -- fault + * 2685:...:0 wc_lms_compute_root() SHA-256/192 climb loop -- fault + * 3359:...:1 wc_hss_update_auth_path() `i >= 0` -- direct call + * with levels == 1 and q == 0 so the loop runs off the bottom + * instead of taking the `break` in the q != 0 arm. + * 4120:...:0 wc_hss_verify() `ret == 0` -- public key + * whose encoded level count disagrees with the parameters. + * + * PAIRING (HARD RULE: MC/DC independence is computed per binary). Every + * rejecting vector above is issued together with its accepting partner in + * THIS program: the fault sweeps always run one DISARMED baseline first, the + * treehash_update rows are issued as a useRoot=0 / useRoot=1 pair over the + * same leaf range, and wc_hss_verify() is called twice (matching and + * mismatching level count). + * + * DETERMINISM: this file uses NO RNG at all -- no wc_InitRng, no + * wc_hss_make_key. Every input is a fixed byte pattern and every fault index + * is derived from measured, input-independent counts, so two runs of an + * unchanged tree produce byte-identical coverage. (The lms module's coverage + * is known to depend on RNG-driven key diversity across variants; nothing + * here pins or perturbs any RNG, so that diversity is untouched.) + * + * COST: no keygen and no signing. The most expensive driver computes 8 WOTS + * leaves; the whole program is a few hundred thousand SHA-256 blocks, orders + * of magnitude inside the campaign's 600 s TEST_TIMEOUT. + * + * VARIANTS (HARD RULE: must compile under every variant of the module): + * WOLFSSL_LMS_VERIFY_ONLY - keygen/signing static helpers are compiled + * out, so only the compute_root sweep and the + * wc_hss_verify rows are built. + * WOLFSSL_WC_LMS_SMALL - selects wc_lms_treehash(); the table-based + * treehash_init/update/update_auth_path drivers + * are compiled out (and vice versa). + * WOLFSSL_LMS_SHA256_192 / WOLFSSL_LMS_SHAKE256 - add a family row to the + * compute_root sweep table. + * main() always returns 0; setup problems are printed skips. + */ + +#include "mcdc_fault_hash.h" + +/* wc_lms_impl.c is #included AFTER the interposers are installed. */ +#include + +#include +#include + +static int wb_fail = 0; +#define WB_NOTE(msg) do { printf(" [wb] %s\n", (msg)); } while (0) + +#if defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_NO_LMS_SHA256_256) + +#define WB_HAVE_DRIVER 1 + +/* Signing-side static helpers (treehash / auth-path) exist only outside the + * verify-only build; the two treehash flavours are mutually exclusive. */ +#ifdef WOLFSSL_LMS_VERIFY_ONLY + #define WB_TREE_SMALL 0 + #define WB_TREE_TABLE 0 +#elif defined(WOLFSSL_WC_LMS_SMALL) + #define WB_TREE_SMALL 1 + #define WB_TREE_TABLE 0 +#else + #define WB_TREE_SMALL 0 + #define WB_TREE_TABLE 1 +#endif + +/* Largest hash length over all compiled families; sizes every fixed buffer. */ +#define WB_HLEN_MAX WC_SHA256_DIGEST_SIZE /* 32 */ +/* Winternitz w=8, wb=3: LMS_V = 2, ls = 0, p = hash_len + 2. */ +#define WB_WIDTH 8U +#define WB_LS 0U +#define WB_P_OF(hLen) ((word16)((hLen) + 2U)) + +/* Tree shape used by every driver here. Height 2 is the smallest that gives + * the carry chain more than one level (h = 1 and h = 2), which is all the + * targeted decisions need; keygen cost is 2^height WOTS leaves and each leaf + * is p * 255 hash calls, so height is the entire runtime budget. */ +#define WB_HEIGHT 2U +#define WB_ROOTLEVELS 2U +#define WB_CACHEBITS 2U +/* Generous fixed buffers: several drivers deliberately drive indices past + * what an in-tree caller would produce (that is the point of 2397), so the + * auth-path / root / stack buffers are sized well beyond the tree shape. */ +#define WB_NODES 32U + +typedef struct WbFam { + const char* name; + word16 lmsType; + word16 lmOtsType; + word16 hash_len; +} WbFam; + +static const WbFam wb_fams[] = { + { "sha256_256", LMS_SHA256_M32_H5, LMOTS_SHA256_N32_W8, + WC_SHA256_DIGEST_SIZE }, +#ifdef WOLFSSL_LMS_SHA256_192 + { "sha256_192", LMS_SHA256_M24_H5, LMOTS_SHA256_N24_W8, 24 }, +#endif +#ifdef WOLFSSL_LMS_SHAKE256 + { "shake256", LMS_SHAKE_M32_H5, LMOTS_SHAKE_N32_W8, + WC_SHA256_DIGEST_SIZE }, +#endif +}; +#define WB_NFAMS (sizeof(wb_fams) / sizeof(wb_fams[0])) + +/* Build a self-consistent LmsParams by hand. This TU never goes through + * wc_lms.c, so the values need only be internally consistent (see the same + * construction in test_wc_lms_impl_whitebox_gap.c). */ +static void wb_params(LmsParams* p, const WbFam* f, word8 levels, word8 height) +{ + XMEMSET(p, 0, sizeof(*p)); + p->levels = levels; + p->height = height; + p->width = (word8)WB_WIDTH; + p->ls = (word8)WB_LS; + p->p = WB_P_OF(f->hash_len); + p->lmsType = f->lmsType; + p->lmOtsType = f->lmOtsType; + p->hash_len = f->hash_len; + p->sig_len = 4U + + (word32)levels * LMS_SIG_LEN(height, p->p, p->hash_len) + + (word32)(levels - 1U) * LMS_PUBKEY_LEN(p->hash_len); +#ifndef WOLFSSL_WC_LMS_SMALL + p->rootLevels = (word8)WB_ROOTLEVELS; + p->cacheBits = (word8)WB_CACHEBITS; +#endif +} + +/* Mirrors wc_lmskey_state_init()/_free() in wc_lms.c (static in another TU). + * wc_InitSha256 / wc_InitShake256 are NOT interposed by mcdc_fault_hash.h, + * so this setup can never be faulted. */ +static int wb_state_init(LmsState* state, const LmsParams* params) +{ + int ret; + + XMEMSET(state, 0, sizeof(*state)); + state->params = params; + +#ifdef WOLFSSL_LMS_SHAKE256 + if (LMS_IS_SHAKE(params->lmOtsType)) { + ret = wc_InitShake256(LMS_STATE_SHAKE(state), NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_InitShake256(LMS_STATE_SHAKE_K(state), NULL, + INVALID_DEVID); + if (ret != 0) { + wc_Shake256_Free(LMS_STATE_SHAKE(state)); + } + } + return ret; + } +#endif + + ret = wc_InitSha256(LMS_STATE_HASH(state)); + if (ret == 0) { + ret = wc_InitSha256(LMS_STATE_HASH_K(state)); + if (ret != 0) { + wc_Sha256Free(LMS_STATE_HASH(state)); + } + } + return ret; +} + +static void wb_state_free(LmsState* state) +{ +#ifdef WOLFSSL_LMS_SHAKE256 + if (LMS_IS_SHAKE(state->params->lmOtsType)) { + wc_Shake256_Free(LMS_STATE_SHAKE_K(state)); + wc_Shake256_Free(LMS_STATE_SHAKE(state)); + return; + } +#endif + wc_Sha256Free(LMS_STATE_HASH_K(state)); + wc_Sha256Free(LMS_STATE_HASH(state)); +} + +/* Fixed, RNG-free inputs. */ +static const byte wb_id[LMS_I_LEN] = { + 0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7, + 0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF +}; +static byte wb_seed[WB_HLEN_MAX]; + +static void wb_init_seed(void) +{ + unsigned i; + for (i = 0; i < (unsigned)sizeof(wb_seed); i++) { + wb_seed[i] = (byte)(0x5A + i); + } +} + +/******************************************************************* + * 2659:...:0 (SHAKE256 arm) and 2685:...:0 (SHA-256/192 arm), plus the + * SHA-256/256 twin of the same loop: + * + * for (i = 0; (ret == 0) && (i < params->height - 1); i++) + * + * inside wc_lms_compute_root(). The `ret == 0` operand can only go false + * when the node hash of an EARLIER iteration failed, and the loop lives at + * the very end of a verify, behind ~4300 WOTS primitive calls -- out of + * reach of any strided sweep. + * + * wc_lms_compute_root() is a static, so it is called here on its own: one + * leaf hash followed by `height` node hashes, a handful of primitive calls + * in total, which lets the sweep be DENSE over [1 .. K]. Point n = C + 1 + * (C = primitive calls per node hash) fails the first loop iteration and + * nothing before it, giving the (ret != 0) row; the disarmed baseline that + * measured K is the accepting row, in this same binary. + * + * Each compiled hash family gets its own pass because wc_lms_compute_root() + * keeps a separate copy of the climb loop per family arm. + ******************************************************************/ +static void wb_compute_root_sweep(const WbFam* f) +{ + LmsParams params; + LmsState state; + byte kc[WB_HLEN_MAX]; + byte tc[WB_HLEN_MAX]; + byte path[WB_NODES * WB_HLEN_MAX]; + long k, n; + int ret; + + /* height 3 so the "all but last height" loop runs more than once. */ + wb_params(¶ms, f, 1, 3); + if (wb_state_init(&state, ¶ms) != 0) { + WB_NOTE("wb_state_init failed for compute_root sweep"); + wb_fail = 1; + return; + } + XMEMSET(state.buffer, 0x3C, sizeof(state.buffer)); + XMEMCPY(state.buffer, wb_id, LMS_I_LEN); + XMEMSET(kc, 0x11, sizeof(kc)); + XMEMSET(tc, 0, sizeof(tc)); + XMEMSET(path, 0x22, sizeof(path)); + + /* Disarmed baseline: the all-true row for every guard in the function, + * and the sweep length K. */ + mcdc_fh_disarm(); + ret = wc_lms_compute_root(&state, 1, kc, path, tc); + k = mcdc_fh_seen(); + if (ret != 0) { + WB_NOTE("baseline wc_lms_compute_root failed; sweep skipped"); + wb_fail = 1; + wb_state_free(&state); + return; + } + + for (n = 1; n <= k; n++) { + mcdc_fh_arm(n); + (void)wc_lms_compute_root(&state, 1, kc, path, tc); + mcdc_fh_disarm(); + } + + wb_state_free(&state); + printf(" [wb] compute_root sweep (%s): K=%ld, dense\n", f->name, k); +} + +/******************************************************************* + * 4120:...:0 wc_hss_verify(): if ((ret == 0) && (nspk + 1 != levels)) + * + * The `ret == 0` operand is false exactly when the immediately preceding + * check rejected the key: `if (levels != state->params->levels)`. Both rows + * are issued here, in this binary: + * + * A. encoded level count = params->levels + 1 -> ret != 0 at :4120, + * decision false with operand 0 false. + * B. encoded level count = params->levels, nspk deliberately wrong + * -> both operands true, decision true. + * + * Neither call reads past the 4-byte L field of the key or of the + * signature: wc_hss_verify() returns SIG_VERIFY_E before the chain walk. + ******************************************************************/ +static void wb_hss_verify_levels(const WbFam* f) +{ + LmsParams params; + LmsState state; + byte pub[HSS_PUBLIC_KEY_LEN(WB_HLEN_MAX)]; + byte sig[64]; + static const byte msg[] = "4120 hss_verify level-count message"; + int ret; + + wb_params(¶ms, f, 1, WB_HEIGHT); + if (wb_state_init(&state, ¶ms) != 0) { + WB_NOTE("wb_state_init failed for hss_verify level rows"); + wb_fail = 1; + return; + } + XMEMSET(pub, 0, sizeof(pub)); + XMEMSET(sig, 0, sizeof(sig)); + + /* Row A: level count in the public key disagrees with the parameters. */ + c32toa((word32)params.levels + 1U, pub); + c32toa(0, sig); + ret = wc_hss_verify(&state, pub, msg, (word32)sizeof(msg), sig, + (word32)sizeof(sig)); + if (ret == 0) { + WB_NOTE("hss_verify accepted a key with the wrong level count"); + wb_fail = 1; + } + + /* Row B: level count matches, nspk does not. */ + c32toa((word32)params.levels, pub); + c32toa(7, sig); + ret = wc_hss_verify(&state, pub, msg, (word32)sizeof(msg), sig, + (word32)sizeof(sig)); + if (ret == 0) { + WB_NOTE("hss_verify accepted a signature with the wrong nspk"); + wb_fail = 1; + } + + wb_state_free(&state); + WB_NOTE("4120 hss_verify level-count rows issued"); +} + +#if WB_TREE_SMALL || WB_TREE_TABLE +/* Primitive-call cost of one leaf hash (L) and one interior node hash (C). + * Both are input independent: the WOTS chain length is (2^width - 1) and the + * node hash is a single fixed-length hash, so measuring one of each is + * enough to locate any leaf/node boundary in the treehash loops. */ +static int wb_measure_lc(LmsState* state, long* pL, long* pC) +{ + byte temp[WB_HLEN_MAX]; + byte left[WB_HLEN_MAX]; + int ret; + + XMEMSET(temp, 0, sizeof(temp)); + XMEMSET(left, 0x77, sizeof(left)); + + XMEMCPY(state->buffer, wb_id, LMS_I_LEN); + mcdc_fh_disarm(); + ret = wc_lms_leaf_hash(state, wb_seed, 0, 4, temp); + *pL = mcdc_fh_seen(); + if (ret != 0) { + return ret; + } + + XMEMCPY(state->buffer, wb_id, LMS_I_LEN); + mcdc_fh_disarm(); + ret = wc_lms_interior_hash(state, left, 2, temp); + *pC = mcdc_fh_seen(); + mcdc_fh_disarm(); + return ret; +} +#endif /* WB_TREE_SMALL || WB_TREE_TABLE */ + +#if WB_TREE_TABLE +/******************************************************************* + * 2262:...:0 wc_lms_treehash_init(): + * if ((ret == 0) && (auth_path != NULL) && (((q >> h) ^ 0x1) == j)) + * + * inside the carry `while ((ret == 0) && ((j & 0x1) == 1))` loop. Reaching + * this decision with ret != 0 needs wc_lms_interior_hash() -- and NOT the + * leaf hash before it -- to fail, because a failed leaf hash keeps the while + * loop from being entered at all. + * + * The loop body is exactly "one leaf hash then the carry chain", so with L + * primitive calls per leaf and C per node hash, leaf 0 is [1 .. L], leaf 1 is + * [L+1 .. 2L], and the first interior hash is [2L+1 .. 2L+C]. The sweep runs + * a small window around that so it stays correct if a hash costs a different + * number of primitives (raw block vs WC_LMS_FULL_HASH vs SHAKE). + * + * The disarmed baseline call is the accepting partner: with q = 1 and + * height = 2, leaf i = 3 climbs to h = 1 with j = 1 and (q >> 1) ^ 1 == 1, + * so all three operands are true there. + ******************************************************************/ +static void wb_treehash_init_fault(const WbFam* f) +{ + LmsParams params; + LmsState state; + LmsPrivState priv; + byte auth_path[WB_NODES * WB_HLEN_MAX]; + byte stack_buf[WB_NODES * WB_HLEN_MAX]; + byte root_buf[WB_NODES * WB_HLEN_MAX]; + byte leaf_cache[WB_NODES * WB_HLEN_MAX]; + long L = 0, C = 0, n, lo, hi; + int ret; + + wb_params(¶ms, f, 1, WB_HEIGHT); + if (wb_state_init(&state, ¶ms) != 0) { + WB_NOTE("wb_state_init failed for treehash_init fault"); + wb_fail = 1; + return; + } + XMEMSET(auth_path, 0, sizeof(auth_path)); + XMEMSET(stack_buf, 0, sizeof(stack_buf)); + XMEMSET(root_buf, 0, sizeof(root_buf)); + XMEMSET(leaf_cache, 0, sizeof(leaf_cache)); + + if (wb_measure_lc(&state, &L, &C) != 0) { + WB_NOTE("leaf/interior hash cost measurement failed"); + wb_fail = 1; + wb_state_free(&state); + return; + } + + /* Accepting baseline (disarmed). */ + XMEMSET(&priv, 0, sizeof(priv)); + priv.auth_path = auth_path; + priv.stack.stack = stack_buf; + priv.root = root_buf; + priv.leaf.cache = leaf_cache; + mcdc_fh_disarm(); + ret = wc_lms_treehash_init(&state, &priv, wb_id, wb_seed, 1); + mcdc_fh_disarm(); + if (ret != 0) { + WB_NOTE("baseline wc_lms_treehash_init failed; fault window skipped"); + wb_fail = 1; + wb_state_free(&state); + return; + } + + lo = 2 * L + 1; + hi = 2 * L + 2 * C; + for (n = lo; n <= hi; n++) { + XMEMSET(&priv, 0, sizeof(priv)); + priv.auth_path = auth_path; + priv.stack.stack = stack_buf; + priv.root = root_buf; + priv.leaf.cache = leaf_cache; + mcdc_fh_arm(n); + (void)wc_lms_treehash_init(&state, &priv, wb_id, wb_seed, 1); + mcdc_fh_disarm(); + } + + wb_state_free(&state); + printf(" [wb] treehash_init fault window: L=%ld C=%ld n=[%ld..%ld]\n", + L, C, lo, hi); +} + +/******************************************************************* + * 2397:...:2 wc_lms_treehash_update(): + * if (useRoot && (h > params->height - params->rootLevels) && + * (h <= params->height)) + * 2414:...:2 wc_lms_treehash_update(): + * if ((ret == 0) && (q == 0) && (!useRoot) && + * (h > params->height - params->rootLevels) && ...) + * + * Three direct calls over the same fixed leaf range, all with q == 0: + * + * 1. useRoot = 0, leaves [0 .. 3] -> 2414 all-true row (accepting). + * 2. useRoot = 1, leaves [0 .. 3] -> 2414 with `!useRoot` false while + * ret == 0 and q == 0 hold (rejecting partner for cond 2), and 2397 + * all-true. + * 3. useRoot = 1, leaves [0 .. 7] -> leaf 7 has three trailing one bits, + * so the carry chain climbs to h = 3 on a height-2 tree and + * `h <= params->height` is FALSE with the two preceding operands true. + * No in-tree caller can produce that index (wc_hss_update_auth_path() + * derives max_idx from LMS_AUTH_PATH_IDX(), bounded by 2^height - 1), + * which is why the bound exists and why only a direct call can falsify + * it. Every buffer here is sized WB_NODES nodes, well past what the + * over-long climb indexes. + * + * leaf.idx starts at 0 with cacheBits = 2, so leaves 0..3 are served from + * the (zeroed) leaf cache -- the tree content is irrelevant to these + * decisions and this keeps calls 1 and 2 nearly free. + ******************************************************************/ +static void wb_treehash_update_roots(const WbFam* f) +{ + LmsParams params; + LmsState state; + LmsPrivState priv; + byte auth_path[WB_NODES * WB_HLEN_MAX]; + byte stack_buf[WB_NODES * WB_HLEN_MAX]; + byte root_buf[WB_NODES * WB_HLEN_MAX]; + byte leaf_cache[WB_NODES * WB_HLEN_MAX]; + int ret; + int i; + static const struct { word32 max_idx; int useRoot; const char* what; } + calls[] = { + { 3, 0, "q=0 useRoot=0 (2414 accepting row)" }, + { 3, 1, "q=0 useRoot=1 (2414 cond-2 rejecting row)" }, + { 7, 1, "leaf 7 on a height-2 tree (2397 cond-2 rejecting row)" }, + }; + + wb_params(¶ms, f, 1, WB_HEIGHT); + if (wb_state_init(&state, ¶ms) != 0) { + WB_NOTE("wb_state_init failed for treehash_update roots"); + wb_fail = 1; + return; + } + XMEMSET(auth_path, 0, sizeof(auth_path)); + XMEMSET(stack_buf, 0, sizeof(stack_buf)); + XMEMSET(root_buf, 0x66, sizeof(root_buf)); + XMEMSET(leaf_cache, 0x44, sizeof(leaf_cache)); + + mcdc_fh_disarm(); + for (i = 0; i < (int)(sizeof(calls) / sizeof(calls[0])); i++) { + XMEMSET(&priv, 0, sizeof(priv)); + priv.auth_path = auth_path; + priv.stack.stack = stack_buf; + priv.stack.offset = 0; + priv.root = root_buf; + priv.leaf.cache = leaf_cache; + priv.leaf.idx = 0; + priv.leaf.offset = 0; + + ret = wc_lms_treehash_update(&state, &priv, wb_id, wb_seed, 0, + calls[i].max_idx, 0, calls[i].useRoot); + if (ret != 0) { + printf(" [wb] treehash_update %s returned %d\n", calls[i].what, + ret); + wb_fail = 1; + } + } + + wb_state_free(&state); + WB_NOTE("2397/2414 treehash_update useRoot + over-long climb rows issued"); +} + +/******************************************************************* + * 3359:...:1 wc_hss_update_auth_path(): + * for (i = levels - 1; (ret == 0) && (i >= 0); i--) + * + * The `i >= 0` operand is only false when the loop runs off the bottom, and + * every level's arm for q != 0 ends in an unconditional `break`. So the loop + * reaches i == -1 only if EVERY level's q is zero -- which for the in-tree + * caller (wc_hss_sign(), after wc_lms_idx_inc()) means a raw index of 0, a + * state that call site can never present. + * + * A direct call with levels = 1 and a zeroed private key gives it: q == 0 + * skips the break arm, i drops to -1 and the loop condition is re-evaluated + * with ret == 0 and i >= 0 false. The i = 0 pass of the same call is the + * accepting row. The per-level LmsPrivState is given real buffers so the + * WOLFSSL_LMS_NO_SIGN_SMOOTHING spelling of the q == 0 arm (which calls + * wc_lms_treehash_init() instead of doing nothing) is equally safe. + ******************************************************************/ +static void wb_update_auth_path_bottom(const WbFam* f) +{ + LmsParams params; + LmsState state; + HssPrivKey pk; + byte priv_buf[LMS_MAX_LEVELS * LMS_PRIV_LEN(WB_HLEN_MAX)]; + byte priv_raw[HSS_PRIVATE_KEY_LEN(WB_HLEN_MAX)]; + byte auth_path[WB_NODES * WB_HLEN_MAX]; + byte stack_buf[WB_NODES * WB_HLEN_MAX]; + byte root_buf[WB_NODES * WB_HLEN_MAX]; + byte leaf_cache[WB_NODES * WB_HLEN_MAX]; + int ret; + + wb_params(¶ms, f, 1, WB_HEIGHT); + if (wb_state_init(&state, ¶ms) != 0) { + WB_NOTE("wb_state_init failed for update_auth_path bottom"); + wb_fail = 1; + return; + } + XMEMSET(&pk, 0, sizeof(pk)); + XMEMSET(priv_buf, 0, sizeof(priv_buf)); + XMEMSET(priv_raw, 0, sizeof(priv_raw)); + XMEMSET(auth_path, 0, sizeof(auth_path)); + XMEMSET(stack_buf, 0, sizeof(stack_buf)); + XMEMSET(root_buf, 0, sizeof(root_buf)); + XMEMSET(leaf_cache, 0, sizeof(leaf_cache)); + + pk.priv = priv_buf; + pk.state[0].auth_path = auth_path; + pk.state[0].stack.stack = stack_buf; + pk.state[0].root = root_buf; + pk.state[0].leaf.cache = leaf_cache; + + mcdc_fh_disarm(); + ret = wc_hss_update_auth_path(&state, &pk, priv_raw, 1); + if (ret != 0) { + WB_NOTE("wc_hss_update_auth_path(levels=1, q=0) failed"); + wb_fail = 1; + } + + wb_state_free(&state); + WB_NOTE("3359 update_auth_path run-off-the-bottom row issued"); +} +#endif /* WB_TREE_TABLE */ + +#if WB_TREE_SMALL +/******************************************************************* + * 2109:...:0 wc_lms_treehash() (WOLFSSL_WC_LMS_SMALL arm): + * if ((ret == 0) && (auth_path != NULL) && (((q >> h) ^ 0x1) == j)) + * + * Same shape and same arithmetic as the table-based treehash_init above: + * the first interior hash of the recompute treehash occupies primitive + * calls [2L+1 .. 2L+C]. The disarmed baseline (q = 1, height = 2, leaf + * i = 3 climbing to h = 1) is the accepting partner in this binary. + ******************************************************************/ +static void wb_treehash_small_fault(const WbFam* f) +{ + LmsParams params; + LmsState state; + byte auth_path[WB_NODES * WB_HLEN_MAX]; + byte pub[WB_HLEN_MAX]; + long L = 0, C = 0, n, lo, hi; + int ret; + + wb_params(¶ms, f, 1, WB_HEIGHT); + if (wb_state_init(&state, ¶ms) != 0) { + WB_NOTE("wb_state_init failed for small treehash fault"); + wb_fail = 1; + return; + } + XMEMSET(auth_path, 0, sizeof(auth_path)); + XMEMSET(pub, 0, sizeof(pub)); + + if (wb_measure_lc(&state, &L, &C) != 0) { + WB_NOTE("leaf/interior hash cost measurement failed"); + wb_fail = 1; + wb_state_free(&state); + return; + } + + mcdc_fh_disarm(); + ret = wc_lms_treehash(&state, wb_id, wb_seed, 1, auth_path, pub); + mcdc_fh_disarm(); + if (ret != 0) { + WB_NOTE("baseline wc_lms_treehash failed; fault window skipped"); + wb_fail = 1; + wb_state_free(&state); + return; + } + + lo = 2 * L + 1; + hi = 2 * L + 2 * C; + for (n = lo; n <= hi; n++) { + mcdc_fh_arm(n); + (void)wc_lms_treehash(&state, wb_id, wb_seed, 1, auth_path, pub); + mcdc_fh_disarm(); + } + + wb_state_free(&state); + printf(" [wb] small treehash fault window: L=%ld C=%ld n=[%ld..%ld]\n", + L, C, lo, hi); +} +#endif /* WB_TREE_SMALL */ + +#endif /* WOLFSSL_HAVE_LMS && !WOLFSSL_NO_LMS_SHA256_256 */ + +int main(void) +{ + /* Unbuffered: a TEST_TIMEOUT kill discards anything still in the stdio + * buffer, which reads as an empty log and no clue where it stopped. */ + setvbuf(stdout, NULL, _IONBF, 0); + printf("wc_lms_impl.c BDS-state white-box supplement\n"); + +#ifdef WB_HAVE_DRIVER + { + size_t i; + + wb_init_seed(); + + /* Family-specific: wc_lms_compute_root() keeps one copy of the + * auth-path climb loop per compiled hash family. */ + for (i = 0; i < WB_NFAMS; i++) { + wb_compute_root_sweep(&wb_fams[i]); + } + + /* Family-independent: the remaining targets are in code shared by + * every family, so the default SHA-256/256 family is enough. */ + wb_hss_verify_levels(&wb_fams[0]); +#if WB_TREE_TABLE + wb_treehash_init_fault(&wb_fams[0]); + wb_treehash_update_roots(&wb_fams[0]); + wb_update_auth_path_bottom(&wb_fams[0]); +#elif WB_TREE_SMALL + wb_treehash_small_fault(&wb_fams[0]); +#else + printf(" [wb] WOLFSSL_LMS_VERIFY_ONLY: treehash/auth-path " + "helpers not compiled; those rows are skipped\n"); +#endif + mcdc_fh_disarm(); + } +#else + printf(" [wb] LMS (SHA-256/256) not compiled in; nothing to do\n"); +#endif + + printf("done (%s)\n", wb_fail ? "with skips" : "ok"); + /* Setup problems are printed skips, never a non-zero exit: a non-zero + * exit makes the campaign discard this binary's whole coverage. */ + return 0; +} diff --git a/tests/unit-mcdc/test_lms_fault_whitebox.c b/tests/unit-mcdc/test_lms_fault_whitebox.c index efd29da869..9bb9c0be3e 100644 --- a/tests/unit-mcdc/test_lms_fault_whitebox.c +++ b/tests/unit-mcdc/test_lms_fault_whitebox.c @@ -96,6 +96,45 @@ static int wb_read_key(byte* priv, word32 privSz, void* context) * SigsLeft() and bails out with BAD_STATE_E/NOSIGS before ever touching the * rest of the buffer (wc_lms.c:1359, ahead of the wc_hss_reload_key() call), * so the remaining bytes are left zeroed. */ +#ifndef WOLFSSL_LMS_VERIFY_ONLY +/* 1258 (see wb_makekey_checks): the LmsKey whose Q counter the exhausting + * write callback below advances, or NULL to leave it alone. */ +static LmsKey* wb_exhaust_key = NULL; + +/* Write callback that saves the key and then advances the LIVE key's Q + * counter to the total leaf count, so wc_LmsKey_SigsLeft() reads the key as + * exhausted on the very next statement. + * + * wc_LmsKey_MakeKey() runs exactly this callback between wc_hss_make_key() + * and its "This should not happen" SigsLeft() guard, so this is the only + * point at which the state that guard defends against can be presented -- and + * presenting it is what an MC/DC pair for that decision requires. The Q value + * matches the parameter set used below (levels=1, height=5 -> 32 leaves); + * wc_hss_sigsleft() returns w64LT(q, 1 << (levels*height)), which is 0 for + * q == 32. */ +static int wb_write_key_exhaust(const byte* priv, word32 privSz, void* context) +{ + int rc = wb_write_key(priv, privSz, context); + + if ((rc == WC_LMS_RC_SAVED_TO_NV_MEMORY) && (wb_exhaust_key != NULL)) { + w64wrapper q = w64From32(0, (word32)1U << 5); + c64toa(&q, wb_exhaust_key->priv_raw); + } + return rc; +} + +/* Write callback that reports a failed NV write: wc_LmsKey_MakeKey() turns + * that into IO_FAILED_E, which is the only way ret is non-zero when the + * SigsLeft() guard at 1258 is reached. */ +static int wb_write_key_fail(const byte* priv, word32 privSz, void* context) +{ + (void)priv; + (void)privSz; + (void)context; + return -1; +} +#endif /* !WOLFSSL_LMS_VERIFY_ONLY */ + static int wb_read_exhausted(byte* priv, word32 privSz, void* context) { w64wrapper q; @@ -1087,14 +1126,25 @@ static void wb_sign_checks(WC_RNG* rng) * wc_LmsKey_MakeKey: 1163 (state!=PARMSET), 1195 (write_private_key==NULL), * 1208 (priv_data==NULL, only the FALSE/reuse row is uncovered). * - * 1261 if ((ret==0) && (wc_LmsKey_SigsLeft(key)==0)) -- PROVEN UNREACHABLE: - * wc_hss_make_key() (wc_lms_impl.c) always starts by zeroing Q via - * wc_lms_idx_zero() before it can fail, and wc_hss_sigsleft() (same file) - * with Q==0 is true for any params -- either the "levels*height>=64" - * shortcut forces ret=1 outright, or w64LT(0, 1<<(levels*height)) is true - * for any levels*height>=0. So SigsLeft()==0 can never hold directly after - * a successful wc_hss_make_key(), for any parameter set. No test call is - * possible; DEATHNOTE candidate (see task report), not closed here. + * 1258 if ((ret==0) && (wc_LmsKey_SigsLeft(key)==0)) -- the "This should not + * happen" guard on a freshly generated key. wc_hss_make_key() zeroes Q + * (wc_lms_idx_zero) before it can fail and wc_hss_sigsleft() is true for + * Q==0 under every parameter set, so the guard cannot fire on the value + * make_key itself leaves behind. An earlier pass concluded from that that + * the decision was unreachable. It is not: wc_LmsKey_MakeKey() calls the + * caller-supplied write_private_key() callback BETWEEN wc_hss_make_key() + * and this guard, and that callback is handed the key's own priv_raw + * buffer. Anything the NV-write step does to the stored index is therefore + * visible to the guard -- which is exactly the failure mode a guard placed + * after the write, rather than before it, exists to catch. + * + * Three rows, all in this binary: + * R1 write cb advances Q to the leaf count -> ret==0, SigsLeft()==0 + * -> decision TRUE (BAD_STATE_E, key->state = WC_LMS_STATE_NOSIGS) + * R2 write cb reports a failed NV write -> the I/O failure code + * -> decision FALSE on operand 0 + * R3 plain successful MakeKey -> ret==0, SigsLeft()!=0 + * -> decision FALSE on operand 1 ******************************************************************/ static void wb_makekey_checks(WC_RNG* rng) { @@ -1152,8 +1202,47 @@ static void wb_makekey_checks(WC_RNG* rng) } wc_LmsKey_Free(&key); - WB_NOTE("1163/1195/1208 MakeKey leaves closed (1261 unreachable, see " - "report)"); + /* R2: the NV write fails, so ret != 0 when 1258 is evaluated. */ + { + LmsKey keyIoFail; + + XMEMSET(&keyIoFail, 0, sizeof(keyIoFail)); + wc_LmsKey_Init(&keyIoFail, NULL, INVALID_DEVID); + wc_LmsKey_SetParameters(&keyIoFail, 1, 5, 8); + wc_LmsKey_SetWriteCb(&keyIoFail, wb_write_key_fail); + ret = wc_LmsKey_MakeKey(&keyIoFail, rng); + if (ret != WC_NO_ERR_TRACE(IO_FAILED_E)) { + WB_NOTE("MakeKey(write cb fails) did not report IO_FAILED_E"); + wb_fail = 1; + } + wc_LmsKey_Free(&keyIoFail); + } + + /* R1: the NV write succeeds but leaves the key's index at the leaf + * count, so SigsLeft() is 0 with ret still 0. */ + { + LmsKey keyExhaust; + + XMEMSET(&keyExhaust, 0, sizeof(keyExhaust)); + wc_LmsKey_Init(&keyExhaust, NULL, INVALID_DEVID); + wc_LmsKey_SetParameters(&keyExhaust, 1, 5, 8); + wc_LmsKey_SetWriteCb(&keyExhaust, wb_write_key_exhaust); + wb_exhaust_key = &keyExhaust; + ret = wc_LmsKey_MakeKey(&keyExhaust, rng); + wb_exhaust_key = NULL; + if (ret != WC_NO_ERR_TRACE(BAD_STATE_E)) { + WB_NOTE("MakeKey(exhausted after write) did not report " + "BAD_STATE_E"); + wb_fail = 1; + } + else if (keyExhaust.state != WC_LMS_STATE_NOSIGS) { + WB_NOTE("MakeKey(exhausted after write) left the wrong state"); + wb_fail = 1; + } + wc_LmsKey_Free(&keyExhaust); + } + + WB_NOTE("1163/1195/1208/1258 MakeKey leaves closed"); } /******************************************************************* From 276ad660f8babd258bed555b2a8e59c669930d0e Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 09:51:13 +0200 Subject: [PATCH 08/57] tests/unit-mcdc: add test_tfm_fault_whitebox.c closing 7 tfm.c MC/DC conditions --- tests/unit-mcdc/test_tfm_fault_whitebox.c | 493 ++++++++++++++++++++++ tests/unit-mcdc/test_tfm_whitebox.c | 15 +- 2 files changed, 505 insertions(+), 3 deletions(-) create mode 100644 tests/unit-mcdc/test_tfm_fault_whitebox.c diff --git a/tests/unit-mcdc/test_tfm_fault_whitebox.c b/tests/unit-mcdc/test_tfm_fault_whitebox.c new file mode 100644 index 0000000000..90966a3d3f --- /dev/null +++ b/tests/unit-mcdc/test_tfm_fault_whitebox.c @@ -0,0 +1,493 @@ +/* test_tfm_fault_whitebox.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* + * Second MC/DC white-box supplement for wolfcrypt/src/tfm.c (FASTMATH + * big-integer engine, bigint-tfm module). It complements + * test_tfm_whitebox.c, which reaches the file-static helpers; this TU targets + * the seven conditions that survived that driver, the tests/api + * DecisionCoverage extensions and the four native build variants, and which + * step 7 relabelled away from the (retired) "32-bit axis" residual class. + * + * Every section supplies BOTH halves of the targeted independence pair inside + * THIS binary - llvm-cov computes MC/DC per binary and the campaign only + * unions the "independence shown" bit by source line:col, so a rejection + * vector without its accepting partner in the same binary proves nothing. + * + * --------------------------------------------------------------------------- + * 1. fp_exptmod / fp_exptmod_ex / fp_exptmod_nct, negative-exponent branch + * 2927:0, 3022:0, 3120:0 `if ((err == 0) && (P->sign == FP_NEG))` + * + * The recorded recipe for these ("an mcdc_fault_alloc.h row hugging + * fp_exptmod_ex") and the model in test_tfm_whitebox.c's + * wb_TfmExptModDecisionCoverage ("call C: invmod fails (gcd=7), err==0 F") + * are BOTH wrong about where `err` comes from. The decision sits INSIDE + * + * err = fp_invmod(&tmp[0], &tmp[1], &tmp[0]); + * if (err == FP_OKAY) { + * ... + * err = _fp_exptmod_ct/_nct(...); + * if ((err == 0) && (P->sign == FP_NEG)) { err = fp_add(Y, P, Y); } + * } + * + * so an fp_invmod failure never REACHES line 2927 - it skips the whole + * block. The only `err` the decision can see is the exponentiation + * engine's, and that engine must fail with the invmod having SUCCEEDED. + * + * No injector is needed. Both engines open with + * if ((err = fp_montgomery_setup(P, &mp)) != FP_OKAY) return err; + * and fp_montgomery_setup rejects an even modulus outright + * (tfm.c:3480 `b = a->dp[0]; if ((b & 1) == 0) return FP_VAL;`). A + * negative modulus with an EVEN magnitude therefore reaches the engine and + * fails it, while fp_invmod - which for an even modulus dispatches to + * fp_invmod_slow (tfm.c:1284) - still succeeds whenever gcd(G,|P|) == 1. + * G = 3, X = -3, P = -14: invmod(3,14) = 5, montgomery_setup(14) = FP_VAL, + * so `err == 0` is FALSE with the decision reached. P = -7 (odd) is the + * accepting partner: engine succeeds, P->sign == FP_NEG, decision TRUE. + * + * 2. fp_to_unsigned_bin_len trailing-significance check + * 3988:0 `if ((i == a->used - 1) && ((a->dp[i] >> j) != 0))` + * + * Crafted input, no injector. `i` is the digit cursor the copy loop stopped + * at. Its three reachable shapes are selected purely by the requested + * output length c against a->used: + * c == DIGIT_BIT/8 with used == 1: the loop consumes the whole digit and + * exits on `i < a->used`, leaving i == a->used, so `i == a->used - 1` + * is FALSE (cond0 F, cond1 unevaluated - the rejecting half); + * c == 2 with used == 1 and a < 2^16: loop exits on x < 0 with i == 0 == + * used-1 and no significant bits left (cond0 T, cond1 F); + * c == DIGIT_BIT/8 with used == 2: i stops one digit short and the top + * digit is nonzero (cond0 T, cond1 T -> FP_VAL). + * + * 3. fp_isprime_ex Miller-Rabin error propagation + * 5200:0 `if ((err != FP_OKAY) || (res == FP_NO))` + * + * Crafted input, no injector. fp_prime_miller_rabin() forwards the return + * of fp_exptmod(b, r, a, y), whose modulus is the CANDIDATE a; fp_exptmod + * rejects `P->used > (FP_SIZE/2)` at tfm.c:2857. A candidate wider than + * FP_SIZE/2 digits that still survives the 256-entry trial-division loop + * above therefore makes the very first Miller-Rabin round return FP_VAL. + * A power of 1621 (the first prime ABOVE primes[FP_PRIME_SIZE-1] == 1619) + * is coprime to every table entry by construction, so it passes trial + * division for free. The accepting partners are ordinary calls on 1621 + * (prime: err FP_OKAY, res FP_YES) and on 1621*1627 (composite that also + * survives trial division: err FP_OKAY, res FP_NO -> cond1's own pair). + * + * 4. fp_isprime_ex trial-division error propagation + * 5188:0 `if (res != MP_OKAY || d == 0)` + * + * fp_mod_d() is fp_div_d(), whose only non-FP_OKAY returns are FP_VAL for a + * zero divisor (primes[] contains none) and FP_MEM from its + * WC_ALLOC_VAR_EX scratch. That allocation exists ONLY under + * WOLFSSL_SMALL_STACK (types.h:983), so this operand is closable in the + * small_stack variant and there alone: mcdc_fault_alloc.h + a one-shot + * fail-index sweep. Under the other variants the sweep is inert (no + * allocation is issued inside fp_isprime_ex) and the calls simply run to + * completion, which is harmless. The all-FALSE partner is the unarmed call + * in section 3. + * + * 5. mp_prime_is_prime_ex random-base rejection + * 5319:0 `if (fp_cmp_d(b, 2) != FP_GT || fp_cmp(b, c) != FP_LT)` + * + * The base b is read from wc_RNG_GenerateBlock(), so `b <= 2` is a lottery + * - for the smallest candidate this loop will accept (11 bits, baseSz 2, + * top byte masked to 3 bits) it is a 3-in-2048 draw, and ASIL-D evidence + * cannot rest on a lottery. This TU interposes wc_RNG_GenerateBlock with a + * SCRIPTED byte stream (the mcdc_seed_rng.h macro trick, but with a chosen + * script rather than a SHAKE squeeze, because the target range is too + * narrow to hit by seed search and a script is reproducible by + * inspection): + * draw 1 -> b = 1 (cond0 TRUE via fp_cmp_d == FP_LT) + * draw 2 -> b = 2 (cond0 TRUE via fp_cmp_d == FP_EQ) + * draw 3 -> b = 2047 (cond0 FALSE, cond1 TRUE: b >= c == 1619) + * draw 4+ -> b = 5 (both FALSE: a real Miller-Rabin round, t--) + * The post-script filler is a valid base, so the `continue` loop always + * terminates. Disarmed, the hook is a straight pass-through to the real + * wc_RNG_GenerateBlock. + * --------------------------------------------------------------------------- + * + * Crash safety: the crafted vectors are ordinary fp_int values built through + * the fp_* API; the faulted vectors fail a single allocation whose caller + * returns FP_MEM before initialising anything (WC_ALLOC_VAR_EX and + * fp_prime_miller_rabin's XMALLOC both return immediately on NULL), and the + * harness never dereferences a faulted call's output. Operands are rebuilt + * while DISARMED on every iteration. + * + * Determinism: no wall clock, no live entropy on any measured path - the RNG + * hook replaces the only randomness tfm.c consumes. + * + * It #includes tfm.c directly (like every other unit-mcdc white-box) to be the + * single instrumented definition; the library's tfm.o is trimmed from the + * archive at link time. main() always returns 0 - a nonzero exit makes the + * campaign discard the whole variant. + */ + +#include +#include + +/* Scripted RNG hook, declared while wc_RNG_GenerateBlock still means the real + * thing (random.h is already in above, so its prototype is not rewritten). */ +#ifndef WC_NO_RNG +static int mcdc_tfm_rng_block(WC_RNG* rng, byte* out, word32 sz); +#define wc_RNG_GenerateBlock(rng, out, sz) mcdc_tfm_rng_block((rng), (out), (sz)) +#endif + +#include + +#ifndef WC_NO_RNG +#undef wc_RNG_GenerateBlock +#endif + +#include "mcdc_fault_alloc.h" + +#include +#include + +#if defined(__GNUC__) || defined(__clang__) + #define WB_MAYBE_UNUSED __attribute__((unused)) +#else + #define WB_MAYBE_UNUSED +#endif + +static int wb_fail = 0; +#define WB_NOTE(msg) do { printf(" [wb] %s\n", (msg)); } while (0) + +/* ------------------------------------------------------------------------ */ +/* scripted RNG (section 5) */ +/* ------------------------------------------------------------------------ */ +#ifndef WC_NO_RNG + +static int mcdc_tfm_rng_armed = 0; +static word32 mcdc_tfm_rng_pos = 0; + +/* Two bytes per draw; see section 5 of the header comment. */ +static const byte mcdc_tfm_rng_script[] = { + 0x00, 0x01, /* b = 1 */ + 0x00, 0x02, /* b = 2 */ + 0x07, 0xff /* b = 2047 */ +}; + +WB_MAYBE_UNUSED static void mcdc_tfm_rng_arm(void) +{ + mcdc_tfm_rng_armed = 1; + mcdc_tfm_rng_pos = 0; +} + +WB_MAYBE_UNUSED static void mcdc_tfm_rng_disarm(void) +{ + mcdc_tfm_rng_armed = 0; + mcdc_tfm_rng_pos = 0; +} + +static int mcdc_tfm_rng_block(WC_RNG* rng, byte* out, word32 sz) +{ + word32 i; + + if (!mcdc_tfm_rng_armed) + return wc_RNG_GenerateBlock(rng, out, sz); + + for (i = 0; i < sz; i++) { + if (mcdc_tfm_rng_pos < (word32)sizeof(mcdc_tfm_rng_script)) { + out[i] = mcdc_tfm_rng_script[mcdc_tfm_rng_pos++]; + } + else { + /* filler: a valid base (0x0005 big-endian) so the loop ends */ + out[i] = (byte)((i + 1 == sz) ? 0x05 : 0x00); + } + } + return 0; +} + +#endif /* !WC_NO_RNG */ + +#if !defined(USE_FAST_MATH) + +int main(void) +{ + setvbuf(stdout, NULL, _IONBF, 0); + printf("tfm.c fault white-box: USE_FAST_MATH not defined, nothing to do\n"); + (void)wb_fail; + return 0; +} + +#else /* USE_FAST_MATH */ + +/* ------------------------------------------------------------------------ */ +/* 1. negative-exponent chain: err from the exponentiation engine (2927/3022/ */ +/* 3120 cond 0) */ +/* ------------------------------------------------------------------------ */ +#ifndef POSITIVE_EXP_ONLY +static void wb_exptmod_engine_failure(void) +{ + fp_int g, x, p, y; + + XMEMSET(&g, 0, sizeof(g)); + XMEMSET(&x, 0, sizeof(x)); + XMEMSET(&p, 0, sizeof(p)); + XMEMSET(&y, 0, sizeof(y)); + + /* --- accepting half: engine succeeds (err == 0 TRUE) and the modulus is + * negative (cond1 TRUE) -> the fp_add(Y, P, Y) fixup runs. --- */ + fp_set(&g, 3); fp_set(&x, 3); fp_setneg(&x); + fp_set(&p, 7); fp_setneg(&p); + printf(" [wb] fp_exptmod G=3 X=-3 P=-7 -> %d (expect 0)\n", + fp_exptmod(&g, &x, &p, &y)); + + fp_set(&g, 3); fp_set(&x, 3); fp_setneg(&x); + fp_set(&p, 7); fp_setneg(&p); + printf(" [wb] fp_exptmod_ex G=3 X=-3 P=-7 -> %d (expect 0)\n", + fp_exptmod_ex(&g, &x, x.used, &p, &y)); + + fp_set(&g, 3); fp_set(&x, 3); fp_setneg(&x); + fp_set(&p, 7); fp_setneg(&p); + printf(" [wb] fp_exptmod_nct G=3 X=-3 P=-7 -> %d (expect 0)\n", + fp_exptmod_nct(&g, &x, &p, &y)); + + /* --- rejecting half: |P| even, so fp_invmod still succeeds through + * fp_invmod_slow (gcd(3,14) == 1) but fp_montgomery_setup rejects the + * modulus inside _fp_exptmod_ct/_nct -> err == FP_VAL, cond0 FALSE. --- */ + fp_set(&g, 3); fp_set(&x, 3); fp_setneg(&x); + fp_set(&p, 14); fp_setneg(&p); + printf(" [wb] fp_exptmod G=3 X=-3 P=-14 -> %d (expect FP_VAL)\n", + fp_exptmod(&g, &x, &p, &y)); + + fp_set(&g, 3); fp_set(&x, 3); fp_setneg(&x); + fp_set(&p, 14); fp_setneg(&p); + printf(" [wb] fp_exptmod_ex G=3 X=-3 P=-14 -> %d (expect FP_VAL)\n", + fp_exptmod_ex(&g, &x, x.used, &p, &y)); + + fp_set(&g, 3); fp_set(&x, 3); fp_setneg(&x); + fp_set(&p, 14); fp_setneg(&p); + printf(" [wb] fp_exptmod_nct G=3 X=-3 P=-14 -> %d (expect FP_VAL)\n", + fp_exptmod_nct(&g, &x, &p, &y)); + + WB_NOTE("exptmod negative-exponent chain: engine-failure half exercised"); +} +#endif /* !POSITIVE_EXP_ONLY */ + +/* ------------------------------------------------------------------------ */ +/* 2. fp_to_unsigned_bin_len trailing check (3988 cond 0) */ +/* ------------------------------------------------------------------------ */ +#if DIGIT_BIT == 64 || DIGIT_BIT == 32 || DIGIT_BIT == 16 +static void wb_to_unsigned_bin_len_tail(void) +{ + fp_int a; + unsigned char buf[32]; + const int dbytes = (int)(DIGIT_BIT / 8); + + XMEMSET(&a, 0, sizeof(a)); + XMEMSET(buf, 0, sizeof(buf)); + + /* cond0 FALSE: the copy loop consumes every digit and stops on + * `i < a->used`, so i == a->used, one past `a->used - 1`. */ + fp_set(&a, 0x1234); + printf(" [wb] to_unsigned_bin_len(0x1234, %d) -> %d (expect 0, cond0 F)\n", + dbytes, fp_to_unsigned_bin_len(&a, buf, dbytes)); + + /* cond0 TRUE, cond1 FALSE: the loop stops on x < 0 with i == a->used - 1 + * and the remaining high bits of the current digit are zero. */ + fp_set(&a, 0x1234); + printf(" [wb] to_unsigned_bin_len(0x1234, 2) -> %d (expect 0, cond0 T" + " cond1 F)\n", fp_to_unsigned_bin_len(&a, buf, 2)); + + /* cond0 TRUE, cond1 TRUE: same stop, but a significant digit is left + * unwritten -> FP_VAL. */ + fp_set(&a, 1); + if (fp_mul_2d(&a, (int)DIGIT_BIT, &a) == FP_OKAY) + printf(" [wb] to_unsigned_bin_len(2^DIGIT_BIT, %d) -> %d (expect" + " FP_VAL, cond0 T cond1 T)\n", dbytes, + fp_to_unsigned_bin_len(&a, buf, dbytes)); + else + wb_fail = 1; + + WB_NOTE("fp_to_unsigned_bin_len trailing-significance vectors exercised"); +} +#endif /* DIGIT_BIT in {64,32,16} */ + +/* ------------------------------------------------------------------------ */ +/* 3./4. fp_isprime_ex (5200 cond 0, 5188 cond 0) */ +/* ------------------------------------------------------------------------ */ +#if !defined(NO_DH) || !defined(NO_DSA) || !defined(NO_RSA) || \ + defined(WOLFSSL_KEY_GEN) + +/* First prime strictly above primes[FP_PRIME_SIZE-1] (0x0653 == 1619), so any + * power of it survives the whole trial-division loop untouched. */ +#define WB_TFM_BIG_PRIME 1621 +#define WB_TFM_BIG_PRIME2 1627 + +/* a = WB_TFM_BIG_PRIME ^ k, k the smallest exponent with used > FP_SIZE/2. + * Returns 0 on success. */ +static int wb_build_oversized_candidate(fp_int* a) +{ + int guard = 0; + + fp_set(a, WB_TFM_BIG_PRIME); + while (a->used <= (FP_SIZE / 2)) { + if (fp_mul_d(a, WB_TFM_BIG_PRIME, a) != FP_OKAY) + return -1; + if (++guard > (FP_SIZE * DIGIT_BIT)) /* cannot loop forever */ + return -1; + } + return 0; +} + +static void wb_isprime_ex_vectors(void) +{ + fp_int a; + int res = 0; + int rc; + int n; + + XMEMSET(&a, 0, sizeof(a)); + + /* accepting half of BOTH decisions: a prime above the table. Every + * fp_mod_d returns MP_OKAY with d != 0 (5188 F,F) and every Miller-Rabin + * round returns FP_OKAY with res == FP_YES (5200 F,F). */ + fp_set(&a, WB_TFM_BIG_PRIME); + rc = fp_isprime_ex(&a, 8, &res); + printf(" [wb] fp_isprime_ex(1621) -> %d res %d (expect 0 / FP_YES)\n", + rc, res); + + /* 5188 cond1 TRUE: divisible by a table prime -> d == 0. */ + fp_set(&a, WB_TFM_BIG_PRIME); + if (fp_mul_d(&a, 3, &a) == FP_OKAY) + (void)fp_isprime_ex(&a, 8, &res); + + /* 5200 cond1 TRUE: composite that still survives trial division, so a + * Miller-Rabin round reports FP_NO with err == FP_OKAY. */ + fp_set(&a, WB_TFM_BIG_PRIME); + if (fp_mul_d(&a, WB_TFM_BIG_PRIME2, &a) == FP_OKAY) { + rc = fp_isprime_ex(&a, 8, &res); + printf(" [wb] fp_isprime_ex(1621*1627) -> %d res %d (expect 0 /" + " FP_NO)\n", rc, res); + } + + /* 5200 cond0 TRUE: candidate wider than FP_SIZE/2 digits. fp_exptmod + * inside the first Miller-Rabin round rejects it at tfm.c:2857 and + * fp_prime_miller_rabin forwards FP_VAL. */ + if (wb_build_oversized_candidate(&a) == 0) { + printf(" [wb] fp_isprime_ex(1621^k, used %d > FP_SIZE/2 %d) -> %d" + " (expect FP_VAL)\n", a.used, (int)(FP_SIZE / 2), + fp_isprime_ex(&a, 8, &res)); + } + else + wb_fail = 1; + + /* 5188 cond0 TRUE: the only failing return fp_mod_d has on this call site + * is FP_MEM out of fp_div_d's WC_ALLOC_VAR_EX, which exists only under + * WOLFSSL_SMALL_STACK. One-shot sweep so exactly one allocation fails per + * call and the rest of the run stays healthy; inert (a plain successful + * run) in variants where fp_isprime_ex allocates nothing. */ +#ifndef MCDC_FA_UNAVAILABLE + mcdc_fa_install(); + for (n = 1; n <= 4; n++) { + fp_set(&a, WB_TFM_BIG_PRIME); + mcdc_fa_arm_only(n); + (void)fp_isprime_ex(&a, 8, &res); + mcdc_fa_disarm(); + } + /* indices past the 256-entry trial-division loop land on fp_isprime_ex's + * own scratch and on fp_prime_miller_rabin's XMALLOC, a second (small + * stack only) route to 5200 cond0. */ + for (n = 255; n <= 262; n++) { + fp_set(&a, WB_TFM_BIG_PRIME); + mcdc_fa_arm_only(n); + (void)fp_isprime_ex(&a, 8, &res); + mcdc_fa_disarm(); + } + mcdc_fa_restore(); +#else + (void)n; + WB_NOTE("allocation lever unavailable; 5188:0 not attempted"); +#endif + + WB_NOTE("fp_isprime_ex trial-division / Miller-Rabin vectors exercised"); +} + +/* ------------------------------------------------------------------------ */ +/* 5. mp_prime_is_prime_ex random-base rejection (5319 cond 0) */ +/* ------------------------------------------------------------------------ */ +#if !defined(WC_NO_RNG) && !defined(FREESCALE_LTC_TFM) +static void wb_prime_is_prime_ex_base(void) +{ + WC_RNG rng; + mp_int a; + int res = 0; + int rc; + + XMEMSET(&a, 0, sizeof(a)); + + if (wc_InitRng(&rng) != 0) { + WB_NOTE("wc_InitRng failed; 5319:0 skipped"); + wb_fail = 1; + return; + } + + /* 1621: 11 bits -> baseSz 2, bitSz 3 (base[0] &= 7), c = a - 2 = 1619. + * The scripted stream walks b = 1, 2, 2047 and then a run of valid + * bases, so both operands of the base filter get both halves here. */ + fp_set(&a, WB_TFM_BIG_PRIME); + mcdc_tfm_rng_arm(); + rc = mp_prime_is_prime_ex(&a, 2, &res, &rng); + printf(" [wb] mp_prime_is_prime_ex(1621, scripted bases) -> %d res %d" + " (expect 0 / FP_YES)\n", rc, res); + mcdc_tfm_rng_disarm(); + + wc_FreeRng(&rng); + WB_NOTE("mp_prime_is_prime_ex scripted-base filter exercised"); +} +#endif /* !WC_NO_RNG && !FREESCALE_LTC_TFM */ + +#endif /* prime helpers compiled */ + +int main(void) +{ + setvbuf(stdout, NULL, _IONBF, 0); + printf("tfm.c fault white-box MC/DC supplement\n"); + + if (wolfCrypt_Init() != 0) { + printf(" wolfCrypt_Init failed; nothing measured\n"); + return 0; + } + +#ifndef POSITIVE_EXP_ONLY + wb_exptmod_engine_failure(); +#endif +#if DIGIT_BIT == 64 || DIGIT_BIT == 32 || DIGIT_BIT == 16 + wb_to_unsigned_bin_len_tail(); +#endif +#if !defined(NO_DH) || !defined(NO_DSA) || !defined(NO_RSA) || \ + defined(WOLFSSL_KEY_GEN) + wb_isprime_ex_vectors(); +#if !defined(WC_NO_RNG) && !defined(FREESCALE_LTC_TFM) + wb_prime_is_prime_ex_base(); +#endif +#endif + + (void)wolfCrypt_Cleanup(); + + printf("done (%s)\n", wb_fail ? "with skips" : "ok"); + /* Setup failures surface as skips, not failures: a nonzero exit makes the + * campaign discard this variant's coverage. */ + return 0; +} + +#endif /* USE_FAST_MATH */ diff --git a/tests/unit-mcdc/test_tfm_whitebox.c b/tests/unit-mcdc/test_tfm_whitebox.c index dd418c4db3..f977bcbf42 100644 --- a/tests/unit-mcdc/test_tfm_whitebox.c +++ b/tests/unit-mcdc/test_tfm_whitebox.c @@ -697,9 +697,17 @@ static void wb_TfmExptModDecisionCoverage(void) * point complete both operands' independence pairs: * call A: G=3, X=-3, P=7 (invmod succeeds: err==0 T; P ZPOS: F) * call B: G=3, X=-3, P=-7 (invmod succeeds: err==0 T; P NEG: T) - * call C: G=7, X=-3, P=-7 (invmod fails (gcd=7): err==0 F; P NEG: T) - * Pair (A,B) isolates the P->sign operand (err==0 held true); - * pair (B,C) isolates the err==0 operand (P->sign held negative). */ + * call C: G=7, X=-3, P=-7 (invmod fails (gcd=7)) + * Pair (A,B) isolates the P->sign operand (err==0 held true). + * + * CORRECTION (step 7): call C does NOT isolate the err==0 operand. The + * decision sits inside `if (err == FP_OKAY) { ... }`, so an fp_invmod + * failure skips the whole block and never reaches the test - `err` there + * can only come from _fp_exptmod_ct/_nct. Call C is kept because it does + * cover the invmod-failure return path, but the err==0 operand's FALSE + * half is closed in test_tfm_fault_whitebox.c, by an even |P| that lets + * fp_invmod succeed (via fp_invmod_slow) and then makes + * fp_montgomery_setup reject the modulus inside the engine. */ fp_set(&g, 3); fp_set(&x, 3); fp_setneg(&x); @@ -909,6 +917,7 @@ static void wb_entry_arg_guards(void) int main(void) { + setvbuf(stdout, NULL, _IONBF, 0); printf("tfm.c white-box MC/DC supplement\n"); #if !defined(USE_FAST_MATH) printf(" USE_FAST_MATH not defined; nothing to exercise\n"); From 40ceeed3dcb65f08f44456f3537cd30be5367c53 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 09:52:48 +0200 Subject: [PATCH 09/57] tests: white-box supplements for sp_cortexm.c on the m33mu lane --- .../test_sp_cortexm_crafted_whitebox.c | 128 ++++++++++++++++++ .../test_sp_cortexm_fault_whitebox.c | 84 ++++++++++++ 2 files changed, 212 insertions(+) create mode 100644 tests/unit-mcdc/test_sp_cortexm_crafted_whitebox.c create mode 100644 tests/unit-mcdc/test_sp_cortexm_fault_whitebox.c diff --git a/tests/unit-mcdc/test_sp_cortexm_crafted_whitebox.c b/tests/unit-mcdc/test_sp_cortexm_crafted_whitebox.c new file mode 100644 index 0000000000..cf08356f12 --- /dev/null +++ b/tests/unit-mcdc/test_sp_cortexm_crafted_whitebox.c @@ -0,0 +1,128 @@ +/* test_sp_cortexm_crafted_whitebox.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* + * MC/DC white-box supplement for the Cortex-M SP backend + * (wolfcrypt/src/sp_cortexm.c) -- crafted-input half. + * + * This is the fourth consumer of tests/unit-mcdc/test_sp_crafted_common.h, + * alongside sp_x86_64.c / sp_c64.c / sp_c32.c and the three qemu-user ARM + * lanes. All seven are implementations of ONE public API, so one body drives + * them; see that header for the full vector list and for the arguments behind + * the SP exclusion families A-G. + * + * It runs on the bare-metal m33mu lane through the lane white-box mechanism + * added in this pass (lib/lanes.sh forwards "lane_whitebox" for kind m33mu + * too; lanes/m33mu/entry.sh + harness/build-fw.sh WB_SRC mode relink the + * firmware with sp_cortexm.c's object replaced by this TU and main() routed + * here). Before that, sp_cortexm.c's only driver was the UNinstrumented + * lane_extra_source constructor in test_sp_cortexm_whitebox.c, which can call + * the file's external-linkage entry points but cannot supply an operand no + * public caller ever passes. + * + * WHAT IT CLOSES (sp_cortexm.c), both from the shared header's vectors: + * 42093:1 `for (i = SP_ECC_MAX_SIG_GEN; err == MP_OKAY && i > 0; i--)` in + * sp_ecc_sign_256() -- the `i > 0` operand's FALSE row. A zero + * private scalar with an all-zero hash makes s = (e + r*d)/k == 0 + * on EVERY attempt, so no attempt is accepted and the loop runs all + * SP_ECC_MAX_SIG_GEN times instead of leaving on `err`. r is + * non-zero and the iteration count is fixed, so nothing here + * depends on the RNG. + * 42989:0 and + * 42989:1 `if ((err == MP_OKAY) && sp_256_iszero_8(p2->z))` in + * sp_256_calc_vfy_point_8(). BOTH operands have to be driven in + * THIS one binary, which is why the TU turns + * WOLFSSL_SP_SMALL_STACK on below rather than leaving the `err` + * operand to the sibling fault white-box. For `A && B`, condition + * A's independence pair needs the decision's TRUE outcome, i.e. + * A = T *with* B = T -- and B is only ever true on the pZ == 0 + * vector, which the fault driver does not have. So: + * :1 a verify whose public point has pZ == 0. The point is then + * the Jacobian point at infinity and every step of the ladder + * keeps z == 0, so B goes true. + * :0 the shared header's allocation sweep over the same entry + * point (WB_SPC_EDGE_SIGNVERIFY, "no-op unless the variant + * sets WOLFSSL_SP_SMALL_STACK"): with the macro on, index 5 + * lets sp_ecc_verify_256's own two SP_ALLOC_VARs and + * sp_256_ecc_mulmod_base_8()'s two succeed and fails + * sp_256_ecc_mulmod_8()'s first, so `err` is MEMORY_E by the + * time the guard is evaluated. + * All of them are paired inside this binary by the ordinary sign/verify calls + * the same header makes just before them. + * + * NOT driven here: the `a == m` modular-inverse vector (verify with + * s == the curve order). test_sp_crafted_common.h already gates it off for + * every assembly backend, WOLFSSL_SP_ARM_CORTEX_M_ASM included -- + * sp_256_mod_inv_8() is hand-written Thumb assembly whose halving loop does + * not terminate on that input. See DEATHNOTE.md. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +/* Before ANY wolfSSL header, so sp_cortexm.c's own + * #ifdef WOLFSSL_SP_SMALL_STACK ... SP_ALLOC_VAR = XMALLOC + err + * arm of the SP_DECL_VAR/SP_ALLOC_VAR macro pair is the one compiled into this + * translation unit -- otherwise the SP temporaries are plain stack arrays, + * `err` is MP_OKAY from entry to exit and the shared header's allocation + * sweeps are inert. Sound for the same reasons spelled out in + * test_sp_arm_fault_common.h: the lane's white-box link contains exactly ONE + * copy of sp_cortexm.c -- this one -- so there is no ODR/ABI split with the + * rest of the firmware, no header or struct layout reacts to the macro, and it + * adds no decision to the file (SP_ALLOC_VAR/SP_FREE_VAR expand to + * single-condition ifs, which carry no MC/DC record), so the file's total + * stays 79 and the union with the other rows stays key-compatible. */ +#ifndef WOLFSSL_SP_SMALL_STACK + #define WOLFSSL_SP_SMALL_STACK +#endif + +#include + +#include + +#include +#include +#include +#include + +#include + +/* Pulls in mcdc_fault_alloc.h and the ecc/dh/random headers it needs itself, + * and defines wb_spc_all(). */ +#include "test_sp_crafted_common.h" + +int main(void) +{ + /* Unbuffered: on a timeout the process is killed and anything still + * buffered is lost, which reads as an empty log. */ + setvbuf(stdout, NULL, _IONBF, 0); + + printf("sp_cortexm.c crafted-input white-box supplement\n"); +#if defined(WOLFSSL_SP_ARM_CORTEX_M_ASM) + wb_spc_all(); + printf("done\n"); +#else + printf(" WOLFSSL_SP_ARM_CORTEX_M_ASM not defined; nothing to exercise\n"); +#endif + /* Always 0: a nonzero exit discards this white-box row's coverage. */ + return 0; +} diff --git a/tests/unit-mcdc/test_sp_cortexm_fault_whitebox.c b/tests/unit-mcdc/test_sp_cortexm_fault_whitebox.c new file mode 100644 index 0000000000..71e651a243 --- /dev/null +++ b/tests/unit-mcdc/test_sp_cortexm_fault_whitebox.c @@ -0,0 +1,84 @@ +/* test_sp_cortexm_fault_whitebox.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* + * MC/DC white-box supplement for the Cortex-M SP backend + * (wolfcrypt/src/sp_cortexm.c) -- heap-fault half. + * + * Sibling of test_sp_arm64_fault_whitebox.c / _arm32_ / _armthumb_ and shares + * their body (tests/unit-mcdc/test_sp_arm_fault_common.h); see that header for + * why the `err == MP_OKAY` operand of this file's success chains is otherwise + * dead by construction, why this TU (not a new lane variant) turns + * WOLFSSL_SP_SMALL_STACK on, and what the allocation sweep does. + * + * WHY THIS FILE EXISTS ONLY NOW + * ---------------------------- + * sp_cortexm.c is measured on the bare-metal m33mu lane, and until this pass + * that lane had no white-box mechanism at all: lib/lanes.sh forwarded a + * variant's "lane_whitebox" rows to kind `qemu-user` only, because those lanes + * can relink against libwolfssl.a with the involved object trimmed. m33mu is a + * single static firmware link with no archive, so the only driver it could + * carry was tests/unit-mcdc/test_sp_cortexm_whitebox.c riding along as a + * lane_extra_source -- an UNinstrumented TU that can call sp_cortexm.c's + * external-linkage entry points but cannot change how the file itself is + * compiled. Nothing it did could make an SP temporary allocation exist, let + * alone fail. + * + * The lane now supports white-boxes properly: harness/build-fw.sh has a + * WB_SRC mode that relinks the firmware from the base build's object manifest + * with sp_cortexm.c's object replaced by THIS TU (compiled with clang MC/DC), + * and main() routed here. That is what makes WOLFSSL_SP_SMALL_STACK -- defined + * below, before the #include -- take effect: this TU holds the one and only + * compiled copy of sp_cortexm.c in the image, so there is no ODR/ABI split + * with the rest of the firmware (the macro only changes function-local + * storage inside the file; no header and no struct layout reacts to it). + * + * WHAT IT CLOSES (all in sp_cortexm.c, all the `err == MP_OKAY` operand): + * 39304:0 39307:0 39310:0 sp_ecc_mulmod_add_256(), three consecutive + * 40819:0 40822:0 40825:0 sp_ecc_mulmod_base_add_256(), three consecutive + * `if ((err == MP_OKAY) && (!inMont))`. inMont is passed 0 so the second + * operand stays true and the sweep is what moves err. + * 42989:0 `if ((err == MP_OKAY) && sp_256_iszero_8(p2->z))` + * in sp_256_calc_vfy_point_8(); err arrives non-MP_OKAY when the + * sp_256_ecc_mulmod_8() immediately above it fails its SP allocation. + * The ordinary (accepting) arrival of each of those guards is produced by the + * same driver's unarmed preparation calls, in this same binary. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +/* Before ANY wolfSSL header, so sp_cortexm.c's own + * #ifdef WOLFSSL_SP_SMALL_STACK ... SP_ALLOC_VAR = XMALLOC + err + * arm of the SP_DECL_VAR/SP_ALLOC_VAR macro pair is the one compiled into this + * translation unit. No header reacts to this macro, so it changes nothing but + * function-local storage inside the file under test. */ +#ifndef WOLFSSL_SP_SMALL_STACK + #define WOLFSSL_SP_SMALL_STACK +#endif + +#include + +#include + +#define SP_ARM_FAULT_LABEL "sp_cortexm.c" +#include "test_sp_arm_fault_common.h" From ada925057bc8db62bb8fdd157a200d48980186cf Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 09:55:01 +0200 Subject: [PATCH 10/57] tests: add white-box MC/DC supplements for srp.c and wolfmath.c --- tests/unit-mcdc/test_srp_whitebox.c | 184 ++++++++++++++++++++ tests/unit-mcdc/test_wolfmath_whitebox.c | 213 +++++++++++++++++++++++ 2 files changed, 397 insertions(+) create mode 100644 tests/unit-mcdc/test_srp_whitebox.c create mode 100644 tests/unit-mcdc/test_wolfmath_whitebox.c diff --git a/tests/unit-mcdc/test_srp_whitebox.c b/tests/unit-mcdc/test_srp_whitebox.c new file mode 100644 index 0000000000..d54cc8becd --- /dev/null +++ b/tests/unit-mcdc/test_srp_whitebox.c @@ -0,0 +1,184 @@ +/* test_srp_whitebox.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* + * MC/DC hash-fault white-box supplement for wolfcrypt/src/srp.c. + * + * THE ONE OPEN CONDITION + * ---------------------- + * srp.c:1057 if (!r && ConstantCompare(proof, digest, (int)size) != 0) + * + * `r` at that point comes from SrpHashFinal() (:1049), and -- on the server + * side only -- from the two SrpHashUpdate() calls at :1055-:1056. Those are + * file-static dispatchers straight onto wc_ShaFinal/wc_Sha256Final/ + * wc_Sha384Final/wc_Sha512Final, which cannot fail on a live, initialised + * context. So every reachable call from tests/api arrives here with r == 0 + * and only ever shows the operand TRUE: the (T,T) row (a wrong proof, giving + * SRP_VERIFY_E) and the (T,F) row (a correct proof) are both there, but the + * idx0 independence pair -- (T,T) against (F,.) -- is not. + * + * mcdc_fault_hash.h is the campaign's lever for exactly this shape: it + * macro-interposes the hash primitives for THIS translation unit only, before + * srp.c is #included, so mcdc_fh_arm(1) makes the very next primitive call + * (and every later one) return BAD_FUNC_ARG. SrpHashFinal() then propagates + * that into `r`, and :1057 is evaluated with `!r` FALSE while the decision + * short-circuits -- the missing half. The unarmed (T,T) partner is driven in + * the SAME binary immediately before it, which is what MC/DC needs: llvm-cov + * computes independence per binary and the campaign only ORs the resulting + * bits by line:col. + * + * Note that wc_SrpVerifyPeersProof()'s SHA-256 proof hash is used here in its + * freshly initialised state (wc_SrpInit() runs SrpHashInit() on both proof + * contexts). No SRP handshake is needed to reach :1057 -- the decision does + * not depend on the session key, only on the digest/proof comparison -- and + * keeping the fixture to wc_SrpInit() makes every vector deterministic and + * cheap (no modexp, well inside TEST_TIMEOUT). tests/api/test_srp.c already + * carries the full handshake, including the corrupted-proof rejection. + * + * Build: compiled by run-mcdc-par.sh's white-box step with the SAME MC/DC + * CFLAGS, -DHAVE_CONFIG_H and -I as the instrumented library, then + * linked against that variant's libwolfssl.a with its srp.o removed (this TU + * supplies the instrumented srp.c). NOT part of the wolfSSL build; not + * registered in tests/api. See tests/unit-mcdc/README.md. + */ + +#include "mcdc_fault_hash.h" + +/* Pull srp.c in verbatim so the file-static SrpHash* dispatchers are in scope + * and instrumented in THIS binary, and so the macros above rewrite srp.c's + * own primitive call sites. */ +#include + +#include + +static int wb_fail = 0; +#define WB_NOTE(msg) do { printf(" [wb] %s\n", (msg)); } while (0) + +#if defined(WOLFCRYPT_HAVE_SRP) && !defined(NO_SHA256) && \ + defined(MCDC_FH_HAVE_SHA256) + +/* ------------------------------------------------------------------------ * + * srp.c:1057 if (!r && ConstantCompare(proof, digest, (int)size) != 0) + * + * vector A (unarmed, wrong proof) -> (T,T) decision TRUE -> SRP_VERIFY_E + * vector B (unarmed, right proof) -> (T,F) decision FALSE -> 0 + * vector C (armed, any proof) -> (F,.) decision FALSE -> BAD_FUNC_ARG + * + * A against C is the idx0 independence pair; A against B is idx1's (already + * shown by tests/api, repeated here so this binary stands on its own). + * ------------------------------------------------------------------------ */ +static void wb_verify_peers_proof_hash_fault(void) +{ + Srp srp; + byte proof[WC_SHA256_DIGEST_SIZE]; + byte expect[WC_SHA256_DIGEST_SIZE]; + int ret; + + /* The proof context is the one wc_SrpInit() built and nothing has been + * absorbed into it, so the digest wc_SrpVerifyPeersProof() computes is + * SHA-256 over the empty message. Derive it the same way, unarmed. */ + { + wc_Sha256 sha; + + XMEMSET(expect, 0, sizeof(expect)); + if (wc_InitSha256(&sha) != 0) { + WB_NOTE("wc_InitSha256 failed; skipping srp proof vectors"); + return; + } + ret = wc_Sha256Final(&sha, expect); + wc_Sha256Free(&sha); + if (ret != 0) { + WB_NOTE("wc_Sha256Final failed; skipping srp proof vectors"); + return; + } + } + + /* Vector A: r == 0, proof mismatches -> both operands TRUE. */ + XMEMSET(&srp, 0, sizeof(srp)); + if (wc_SrpInit(&srp, SRP_TYPE_SHA256, SRP_CLIENT_SIDE) != 0) { + WB_NOTE("wc_SrpInit failed; skipping srp proof vectors"); + return; + } + XMEMSET(proof, 0, sizeof(proof)); + proof[0] = (byte)(expect[0] ^ 0x01); + ret = wc_SrpVerifyPeersProof(&srp, proof, (word32)sizeof(proof)); + if (ret != WC_NO_ERR_TRACE(SRP_VERIFY_E)) { + WB_NOTE("wrong proof was not rejected with SRP_VERIFY_E"); + wb_fail = 1; + } + wc_SrpTerm(&srp); + + /* Vector B: r == 0, proof matches -> idx0 TRUE, idx1 FALSE. */ + XMEMSET(&srp, 0, sizeof(srp)); + if (wc_SrpInit(&srp, SRP_TYPE_SHA256, SRP_CLIENT_SIDE) != 0) { + WB_NOTE("wc_SrpInit failed; skipping matching-proof vector"); + } + else { + XMEMCPY(proof, expect, sizeof(proof)); + ret = wc_SrpVerifyPeersProof(&srp, proof, (word32)sizeof(proof)); + if (ret != 0) { + WB_NOTE("matching proof was not accepted"); + wb_fail = 1; + } + wc_SrpTerm(&srp); + } + + /* Vector C: SrpHashFinal() fails, so :1057 is reached with r != 0 and + * `!r` is FALSE -- the half no live context can produce. Arm for exactly + * this one call so nothing else in the binary is faulted. */ + XMEMSET(&srp, 0, sizeof(srp)); + if (wc_SrpInit(&srp, SRP_TYPE_SHA256, SRP_CLIENT_SIDE) != 0) { + WB_NOTE("wc_SrpInit failed; skipping hash-fault vector"); + return; + } + XMEMSET(proof, 0, sizeof(proof)); + mcdc_fh_arm(1); + ret = wc_SrpVerifyPeersProof(&srp, proof, (word32)sizeof(proof)); + mcdc_fh_disarm(); + if (ret != WC_NO_ERR_TRACE(MCDC_FH_ERR)) { + WB_NOTE("faulted SrpHashFinal did not propagate out of " + "wc_SrpVerifyPeersProof"); + wb_fail = 1; + } + wc_SrpTerm(&srp); + + WB_NOTE("wc_SrpVerifyPeersProof !r / ConstantCompare pairs exercised"); +} + +#else /* !(WOLFCRYPT_HAVE_SRP && !NO_SHA256 && MCDC_FH_HAVE_SHA256) */ + +static void wb_verify_peers_proof_hash_fault(void) +{ + WB_NOTE("SRP or SHA-256 not compiled in; srp proof vectors skipped"); +} + +#endif + +int main(void) +{ + setvbuf(stdout, NULL, _IONBF, 0); + printf("srp.c white-box supplement\n"); + wb_verify_peers_proof_hash_fault(); + printf("done (%s)\n", wb_fail ? "with skips" : "ok"); + /* Setup issues are surfaced as skips; a nonzero exit would make the + * campaign discard this variant's coverage. */ + return 0; +} diff --git a/tests/unit-mcdc/test_wolfmath_whitebox.c b/tests/unit-mcdc/test_wolfmath_whitebox.c new file mode 100644 index 0000000000..7d05a673d1 --- /dev/null +++ b/tests/unit-mcdc/test_wolfmath_whitebox.c @@ -0,0 +1,213 @@ +/* test_wolfmath_whitebox.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* + * MC/DC RNG-scripting white-box supplement for wolfcrypt/src/wolfmath.c. + * + * THE TWO OPEN CONDITIONS + * ----------------------- + * wolfmath.c:211 while ((ret == MP_OKAY) && (a->dp[a->used - 1] == 0)) + * + * This is mp_rand()'s "ensure the top digit is not zero" retry loop. Driven by + * a real RNG it is only ever evaluated once, as (T,F): ret is MP_OKAY because + * the preceding wc_RNG_GenerateBlock() succeeded, and the top digit is zero + * with probability 2^-64 (2^-32 on a 32-bit build), so: + * + * idx1 (a->dp[a->used - 1] == 0) has no TRUE row -- the loop body is never + * entered at all; + * idx0 (ret == MP_OKAY) has no FALSE row -- `ret` can only become non-OKAY + * INSIDE the loop body, from mp_get_rand_digit(), which is only reached + * once idx1 has already been TRUE. + * + * Both halves therefore hang off the same lever: the randomness mp_rand() + * consumes. Waiting for the real RNG to hand out an all-zero top digit is not + * an option for ASIL-D evidence (it is a 2^-64 lottery, and rule 3 of this + * campaign forbids evidence that depends on a live draw), so this TU scripts + * the stream instead. + * + * HOW -- MACRO INTERPOSITION ON wc_RNG_GenerateBlock() + * --------------------------------------------------- + * random.h is included and the hook declared FIRST, then wc_RNG_GenerateBlock + * is #defined to the hook, and only then is wolfmath.c #included. Both of + * wolfmath.c's call sites (mp_rand()'s block fill and mp_get_rand_digit()'s + * single-digit redraw) are rewritten; every other translation unit in the + * archive keeps the real function. The hook's own body sits after an #undef, + * so it still reaches the real RNG when the script is idle. + * + * PER-CONDITION VECTOR MAP (all three rows in THIS binary) + * -------------------------------------------------------- + * WB_RNG_FIXED block fill = 0xa5.. -> top digit != 0 + * loop evaluated once: (T,F) -> decision FALSE + * WB_RNG_ZERO block fill = 0x00.. -> top digit == 0 + * 1st evaluation: (T,T) -> decision TRUE, body runs + * mp_get_rand_digit()'s redraw is then refused, so + * 2nd evaluation: (F,.) -> decision FALSE + * WB_RNG_REAL pass-through, so an ordinary mp_rand() is also measured. + * + * (T,T) against (F,.) is idx0's independence pair; (T,T) against (T,F) is + * idx1's. Both are completed inside this binary, which is what MC/DC needs: + * llvm-cov computes independence per binary and the campaign only ORs the + * resulting bits by line:col. + * + * Build: compiled by run-mcdc-par.sh's white-box step with the SAME MC/DC + * CFLAGS, -DHAVE_CONFIG_H and -I as the instrumented library, then + * linked against that variant's libwolfssl.a with its wolfmath.o removed + * (this TU supplies the instrumented wolfmath.c). NOT part of the wolfSSL + * build; not registered in tests/api. See tests/unit-mcdc/README.md. + */ + +#include +#include + +/* Declare the hook explicitly rather than relying on the macro to rewrite + * random.h's own prototype: if anything drags random.h in first, the include + * guard skips that prototype, the hook is never declared, and every call site + * inside wolfmath.c fails to compile -- which the campaign scores as a SILENT + * SKIP (see the same note in mcdc_seed_rng.h). */ +static int wb_wm_rng_block(WC_RNG* rng, byte* out, word32 sz); + +#define wc_RNG_GenerateBlock(rng, out, sz) wb_wm_rng_block((rng), (out), (sz)) + +/* Pull wolfmath.c in verbatim so it is instrumented in THIS binary and sees + * the interposer above. */ +#include + +#undef wc_RNG_GenerateBlock + +#include + +static int wb_fail = 0; +#define WB_NOTE(msg) do { printf(" [wb] %s\n", (msg)); } while (0) + +#define WB_RNG_REAL 0 /* pass through to the real RNG */ +#define WB_RNG_FIXED 1 /* every draw is 0xa5.. (top digit non-zero) */ +#define WB_RNG_ZERO 2 /* first draw all-zero, every later draw fails */ + +static int wb_rng_mode = WB_RNG_REAL; +static int wb_rng_calls = 0; + +static int wb_wm_rng_block(WC_RNG* rng, byte* out, word32 sz) +{ + if (wb_rng_mode == WB_RNG_REAL) { + return wc_RNG_GenerateBlock(rng, out, sz); + } + + wb_rng_calls++; + + if (wb_rng_mode == WB_RNG_FIXED) { + XMEMSET(out, 0xa5, sz); + return 0; + } + + /* WB_RNG_ZERO: hand mp_rand() a block whose top digit is zero, then + * refuse the redraw mp_get_rand_digit() makes from inside the loop. */ + if (wb_rng_calls == 1) { + XMEMSET(out, 0, sz); + return 0; + } + return WC_NO_ERR_TRACE(RNG_FAILURE_E); +} + +#if !defined(WC_NO_RNG) && (!defined(NO_BIG_INT) || defined(WOLFSSL_SP_MATH)) + +static void wb_mp_rand_top_digit(void) +{ + mp_int a; + WC_RNG rng; + int ret; + /* Two digits is the smallest shape that still exercises the a->used + * indexing; mp_rand() rejects digits <= 0. */ + const int digits = 2; + + if (mp_init(&a) != MP_OKAY) { + WB_NOTE("mp_init failed; skipping mp_rand vectors"); + return; + } + if (wc_InitRng(&rng) != 0) { + WB_NOTE("wc_InitRng failed; skipping mp_rand vectors"); + mp_clear(&a); + return; + } + + /* Row 1 -- ordinary draw, pass-through: the loop is evaluated once and + * both operands come from the real RNG. */ + ret = mp_rand(&a, digits, &rng); + if (ret != MP_OKAY) { + WB_NOTE("mp_rand with the real RNG failed"); + wb_fail = 1; + } + + /* Row 2 -- (T,F): a scripted non-zero top digit, so the loop is entered + * zero times deterministically (the real-RNG row above cannot be relied + * on for this: it is a draw, not a vector). */ + wb_rng_mode = WB_RNG_FIXED; + wb_rng_calls = 0; + ret = mp_rand(&a, digits, &rng); + wb_rng_mode = WB_RNG_REAL; + if (ret != MP_OKAY) { + WB_NOTE("mp_rand with a scripted non-zero fill failed"); + wb_fail = 1; + } + + /* Row 3+4 -- (T,T) then (F,.): an all-zero block enters the loop, and the + * mp_get_rand_digit() redraw inside it is refused, so the very next + * evaluation of the same decision has ret != MP_OKAY. */ + wb_rng_mode = WB_RNG_ZERO; + wb_rng_calls = 0; + ret = mp_rand(&a, digits, &rng); + wb_rng_mode = WB_RNG_REAL; + if (ret != WC_NO_ERR_TRACE(RNG_FAILURE_E)) { + WB_NOTE("refused top-digit redraw did not propagate out of mp_rand"); + wb_fail = 1; + } + if (wb_rng_calls < 2) { + WB_NOTE("mp_rand never re-drew the top digit; loop was not entered"); + wb_fail = 1; + } + + wc_FreeRng(&rng); + mp_clear(&a); + + WB_NOTE("mp_rand top-digit retry loop pairs exercised"); +} + +#else + +static void wb_mp_rand_top_digit(void) +{ + /* Keep the interposer referenced so it is never an unused static in a + * variant that compiles mp_rand() out. */ + (void)&wb_wm_rng_block; + WB_NOTE("WC_NO_RNG or no big-int math; mp_rand vectors skipped"); +} + +#endif /* !WC_NO_RNG && (!NO_BIG_INT || WOLFSSL_SP_MATH) */ + +int main(void) +{ + setvbuf(stdout, NULL, _IONBF, 0); + printf("wolfmath.c white-box supplement\n"); + wb_mp_rand_top_digit(); + printf("done (%s)\n", wb_fail ? "with skips" : "ok"); + /* Setup issues are surfaced as skips; a nonzero exit would make the + * campaign discard this variant's coverage. */ + return 0; +} From 292c575d00f6be6a1fa1f4608be18defaaed9976 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 09:55:01 +0200 Subject: [PATCH 11/57] tests: drive the curve25519 blinding-rz and wolfentropy startup-noise MC/DC pairs --- tests/unit-mcdc/test_curve25519_whitebox.c | 120 ++++++++++++++++++++ tests/unit-mcdc/test_wolfentropy_whitebox.c | 96 ++++++++++++++++ 2 files changed, 216 insertions(+) diff --git a/tests/unit-mcdc/test_curve25519_whitebox.c b/tests/unit-mcdc/test_curve25519_whitebox.c index 82d01e5e9a..2ae5b854c0 100644 --- a/tests/unit-mcdc/test_curve25519_whitebox.c +++ b/tests/unit-mcdc/test_curve25519_whitebox.c @@ -51,10 +51,54 @@ * scope and instrumented in THIS binary. curve25519.c includes settings.h * (which picks up user_settings.h via -DWOLFSSL_USER_SETTINGS) and * curve25519.h itself. */ +/* ---- wc_RNG_GenerateBlock() interposer ---------------------------------- + * + * curve25519_smul_blind()'s blinding-value rejection loop (:279-:293) draws a + * fresh rz until it is acceptable. Its guard + * + * if ((i >= 0) || (rz[0] <= 0xec)) break; + * + * only takes "i >= 0" FALSE when EVERY byte of rz is 0xff -- a 2^-256 event + * that no seeded stream can be relied on to produce (and rule 3 of this + * campaign forbids evidence that depends on a live draw). Interposing + * wc_RNG_GenerateBlock() for THIS translation unit lets one scripted draw + * return 32 x 0xff, so the loop's first iteration evaluates the guard with + * i == -1, while the second (unscripted) draw ends the loop normally -- both + * halves of the idx0 pair in one call, and cnt never reaches + * WOLFSSL_CURVE25519_BLINDING_RAND_CNT, so no RNG_FAILURE_E bail-out. + * + * random.h is included and the hook declared FIRST so the macro never has to + * rewrite random.h's own prototype (see the same note in mcdc_seed_rng.h: an + * undeclared hook is a compile failure, which the campaign scores as a silent + * skip). The hook's body sits after the #undef, so it still reaches the real + * RNG when the script is idle. + */ +#include +#include + +static int wb_c25519_rng_block(WC_RNG* rng, byte* out, word32 sz); + +#define wc_RNG_GenerateBlock(rng, out, sz) wb_c25519_rng_block((rng), (out), (sz)) + #include +#undef wc_RNG_GenerateBlock + #include +/* Number of remaining draws to answer with all-0xff instead of real random. */ +static int wb_ff_draws = 0; + +static int wb_c25519_rng_block(WC_RNG* rng, byte* out, word32 sz) +{ + if (wb_ff_draws > 0) { + wb_ff_draws--; + XMEMSET(out, 0xff, sz); + return 0; + } + return wc_RNG_GenerateBlock(rng, out, sz); +} + static int wb_fail = 0; #define WB_NOTE(msg) do { printf(" [wb] %s\n", (msg)); } while (0) @@ -278,8 +322,83 @@ static void wb_generic_arg_guards(void) } #endif /* HAVE_CURVE25519 && WOLFSSL_CURVE25519_BLINDING */ +/* ------------------------------------------------------------------------- * + * curve25519.c:288 if ((i >= 0) || (rz[0] <= 0xec)) + * + * idx0 ("i >= 0") needs a draw whose every byte is 0xff, so the scan at + * :283-:286 falls off the bottom with i == -1. One scripted draw does that; + * the loop then goes round once more with a real draw, which breaks at some + * i >= 0 and gives the TRUE partner in the same call and the same binary. + * + * idx1 ("rz[0] <= 0xec") stays EXCLUDED and is not attempted here: reaching + * it at all requires i < 0, which the loop bound (i >= 0, not i >= 1) makes + * synonymous with rz[0] == 0xff, so the operand is unreachable-as-true. That + * is a product defect, filed in the campaign's DEATHNOTE.md; if the loop bound + * is ever corrected the exclusion must be withdrawn and BOTH operands + * re-measured from this same interposer. + * ------------------------------------------------------------------------- */ +#if defined(HAVE_CURVE25519) && defined(WOLFSSL_CURVE25519_BLINDING) && \ + !defined(FREESCALE_LTC_ECC) && !defined(WOLF_CRYPTO_CB_ONLY_CURVE25519) +static void wb_blind_rz_all_ff(void) +{ + byte pub[CURVE25519_KEYSIZE]; + byte priv[CURVE25519_KEYSIZE]; + WC_RNG rng; + int ret; + + if (wc_InitRng(&rng) != 0) { + WB_NOTE("wc_InitRng failed; skipping blinding rz vectors"); + return; + } + + XMEMSET(pub, 0, sizeof(pub)); + XMEMSET(priv, 0x5a, sizeof(priv)); + if (curve25519_priv_clamp(priv) != 0) { + WB_NOTE("curve25519_priv_clamp failed; skipping blinding rz vectors"); + wc_FreeRng(&rng); + return; + } + + /* Unscripted: every draw is real, so the guard is only ever evaluated + * with i >= 0 (idx0 TRUE, decision TRUE). */ + ret = wc_curve25519_make_pub_blind(CURVE25519_KEYSIZE, pub, + CURVE25519_KEYSIZE, priv, &rng); + if (ret != 0) { + WB_NOTE("wc_curve25519_make_pub_blind failed unscripted"); + wb_fail = 1; + } + + /* Scripted: exactly one all-0xff draw, so the first iteration evaluates + * the guard with i == -1 and rz[0] == 0xff -- (F,F), decision FALSE -- + * and the retry draws real bytes and breaks normally. */ + XMEMSET(pub, 0, sizeof(pub)); + wb_ff_draws = 1; + ret = wc_curve25519_make_pub_blind(CURVE25519_KEYSIZE, pub, + CURVE25519_KEYSIZE, priv, &rng); + if (wb_ff_draws != 0) { + WB_NOTE("scripted all-0xff draw was never consumed"); + wb_fail = 1; + wb_ff_draws = 0; + } + if (ret != 0) { + WB_NOTE("wc_curve25519_make_pub_blind failed after an all-0xff rz"); + wb_fail = 1; + } + + wc_FreeRng(&rng); + WB_NOTE("curve25519_smul_blind rz rejection-loop guard exercised"); +} +#else +static void wb_blind_rz_all_ff(void) +{ + (void)&wb_c25519_rng_block; + WB_NOTE("curve25519 blinding not compiled in; rz vectors skipped"); +} +#endif + int main(void) { + setvbuf(stdout, NULL, _IONBF, 0); printf("curve25519.c white-box supplement\n"); #ifndef HAVE_CURVE25519 printf(" HAVE_CURVE25519 not defined; nothing to exercise\n"); @@ -288,6 +407,7 @@ int main(void) wb_make_pub_nb(); wb_make_key_nb(); wb_generic_arg_guards(); + wb_blind_rz_all_ff(); printf("done (%s)\n", wb_fail ? "with skips" : "ok"); /* Setup failures are surfaced as skips, not test failures: the * campaign treats a nonzero exit as a failed variant and discards its diff --git a/tests/unit-mcdc/test_wolfentropy_whitebox.c b/tests/unit-mcdc/test_wolfentropy_whitebox.c index d012f2d644..b2a52b28ec 100644 --- a/tests/unit-mcdc/test_wolfentropy_whitebox.c +++ b/tests/unit-mcdc/test_wolfentropy_whitebox.c @@ -70,6 +70,51 @@ #include "mcdc_fault_mutex.h" +/* ---- SHA3-256 interposer, for the startup health test's noise fill ------- + * + * Entropy_GetNoise() is a file-static in wolfentropy.c itself, so it cannot be + * macro-interposed the way mcdc_fault_hash.h interposes primitives that live + * in another translation unit: a macro on its name renames the DEFINITION as + * well as the call sites and changes nothing. But every failure it can report + * originates in Entropy_MemUse(), whose only fallible operations are + * wc_Sha3_256_Update()/wc_Sha3_256_Final() on the shared conditioner -- and + * those DO come from sha3.o in the archive. Refusing one of them is therefore + * the reachable equivalent: Entropy_MemUse() -> Entropy_GetNoise() -> + * Entropy_HealthTest_Startup() propagates it into `ret` before the sample + * loop at :781 is ever evaluated. mcdc_fault_hash.h does not carry SHA-3 (no + * white-box has needed it before), so the wrapper is local to this TU. + * + * The wrapper is defined BEFORE the macro exists, so it still reaches the + * real primitive; ordering is load-bearing, exactly as in mcdc_fault_hash.h. + * + * libwolfssl_sources.h has to come first (mcdc_fault_mutex.h deliberately + * includes nothing, so no configuration has been read yet): without it + * WOLFSSL_SHA3 is undefined at this point, the whole interposer is + * preprocessed away, and wb_startup_noise_fail() below -- which is guarded on + * the same macro but sits AFTER wolfentropy.c has pulled settings.h in -- + * refers to a wb_sha3_refuse that does not exist. That is a compile failure, + * which the campaign scores as a SILENT SKIP. + */ +#include +#include +#include + +#ifdef WOLFSSL_SHA3 +#include + +static int wb_sha3_refuse = 0; + +static int wb_Sha3_256_Update(wc_Sha3* sha3, const byte* data, word32 len) +{ + if (wb_sha3_refuse) { + return WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + return wc_Sha3_256_Update(sha3, data, len); +} + +#define wc_Sha3_256_Update(s, d, l) wb_Sha3_256_Update((s), (d), (l)) +#endif /* WOLFSSL_SHA3 */ + #include #define MCDC_FM_IMPL @@ -280,6 +325,54 @@ static void wb_get_loop_early_exit(void) Entropy_Final(); } +/* Entropy_HealthTest_Startup()'s sample loop: + * "for (i = 0; (ret == 0) && (i < ENTROPY_INITIAL_COUNT); i++)" + * On a healthy host the loop always runs to completion, so idx0 ("ret == 0") + * only ever shows TRUE and the loop is only ever left through idx1. `ret` is + * assignable inside the loop (the two health tests at :782-:786), but real + * MemUse jitter never trips REP_CUTOFF/PROP_CUTOFF, and the noise buffer is + * filled by the file-static Entropy_GetNoise() which cannot be rigged + * directly. The reachable lever is the one thing Entropy_GetNoise() depends on + * from outside this file: refuse the conditioner's SHA3-256 update, so + * Entropy_MemUse() fails, Entropy_GetNoise() returns that error at :779, and + * the loop condition is evaluated once with ret != 0 -- idx0 FALSE, the + * missing half. The healthy call immediately before it (same binary) supplies + * (T,T) and, at i == ENTROPY_INITIAL_COUNT, (T,F). */ +#ifdef WOLFSSL_SHA3 +static void wb_startup_noise_fail(void) +{ + int ret; + + if (Entropy_Init() != 0) { + WB_NOTE("Entropy_Init failed; skipping startup noise-failure vector"); + return; + } + + /* Armed: the first conditioner update inside Entropy_MemUse() refuses, so + * Entropy_GetNoise() never fills the buffer. */ + wb_sha3_refuse = 1; + ret = Entropy_HealthTest_Startup(); + wb_sha3_refuse = 0; + if (ret == 0) { + WB_NOTE("refused SHA3 update did not fail the startup health test"); + wb_fail = 1; + } + + /* Unarmed partner in the same binary, which also leaves the health-test + * globals primed for whatever runs next. */ + if (Entropy_HealthTest_Startup() != 0) { + WB_NOTE("healthy startup health test failed (skip, not fail)"); + } + + Entropy_Final(); + + WB_NOTE("Entropy_HealthTest_Startup ret==0 pair exercised"); +} +#else +static void wb_startup_noise_fail(void) +{ WB_NOTE("WOLFSSL_SHA3 off; startup noise-failure vector skipped"); } +#endif /* WOLFSSL_SHA3 */ + #else /* !HAVE_ENTROPY_MEMUSE */ static void wb_repetition(void) @@ -292,6 +385,8 @@ static void wb_startup_retrigger(void) { WB_NOTE("HAVE_ENTROPY_MEMUSE not compiled in; skipped startup retrigger"); } static void wb_get_loop_early_exit(void) { WB_NOTE("HAVE_ENTROPY_MEMUSE not compiled in; skipped loop early exit"); } +static void wb_startup_noise_fail(void) +{ WB_NOTE("HAVE_ENTROPY_MEMUSE not compiled in; skipped startup noise fail"); } #endif /* HAVE_ENTROPY_MEMUSE */ @@ -364,6 +459,7 @@ int main(void) wb_proportion(); wb_startup_retrigger(); wb_get_loop_early_exit(); + wb_startup_noise_fail(); wb_entropy_get_mutex(); printf("done (%s)\n", wb_fail ? "with skips" : "ok"); /* Setup issues are surfaced as skips; a nonzero exit would make the From 5032dced35b9bcf9aa0619237e19d9fd39180dd1 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 09:57:31 +0200 Subject: [PATCH 12/57] tests: white-box for puf.c's GF(2^7) multiply zero-operand guard --- tests/unit-mcdc/test_puf_gf_whitebox.c | 95 ++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 tests/unit-mcdc/test_puf_gf_whitebox.c diff --git a/tests/unit-mcdc/test_puf_gf_whitebox.c b/tests/unit-mcdc/test_puf_gf_whitebox.c new file mode 100644 index 0000000000..bb5392b246 --- /dev/null +++ b/tests/unit-mcdc/test_puf_gf_whitebox.c @@ -0,0 +1,95 @@ +/* test_puf_gf_whitebox.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* + * MC/DC white-box supplement for wolfcrypt/src/puf.c -- the GF(2^7) helper. + * + * Closes puf.c 123:0, the last open condition in the module: + * + * static WC_INLINE byte gf_mul(byte a, byte b) + * { + * if (a == 0 || b == 0) + * return 0; + * + * `a == 0`'s independence pair needs the decision's TRUE outcome with `a` the + * deciding operand -- i.e. a == 0 -- beside the all-false row. The BCH decoder + * that reaches gf_mul() only multiplies syndrome/locator coefficients it has + * already tested for zero, so every call the KAT makes arrives with both + * operands non-zero and the decision is constant false. gf_mul() is + * file-static, so no caller outside puf.c can supply the missing row: it takes + * a TU that #includes puf.c. + * + * That is what this driver is. It rides the m33mu lane's white-box mechanism + * (variant "lane_whitebox"; lanes/m33mu/entry.sh + harness/build-fw.sh WB_SRC + * mode relink the firmware with puf.c's object replaced by this TU and main() + * routed here), which is also why it could not exist before: the lane's only + * driver used to be tests/unit-mcdc/test_puf_whitebox.c riding along as an + * UNinstrumented lane_extra_source, and that one can only call puf.c's public + * entry points. + * + * Three vectors, all in this one binary and all pure table arithmetic with no + * state, no allocation and no I/O: + * (a == 0, b != 0) -> decision true on the first operand + * (a != 0, b == 0) -> decision true on the second (already covered, kept so + * the pair is complete inside this binary) + * (a != 0, b != 0) -> the all-false row + * The same three are issued against gf_inv()'s single-condition guard so its + * zero arm is exercised here too. + */ + +#include + +#include + +#include + +int main(void) +{ + /* Unbuffered: on a timeout the process is killed and anything still + * buffered is lost, which reads as an empty log. */ + setvbuf(stdout, NULL, _IONBF, 0); + + printf("puf.c GF(2^7) white-box supplement\n"); + +#ifdef WOLFSSL_PUF + { + unsigned acc = 0; + int i; + + /* a == 0 with b non-zero: the row the BCH decoder never produces. */ + for (i = 1; i < 8; i++) { + acc += gf_mul(0, (byte)i); + acc += gf_mul((byte)i, 0); + acc += gf_mul((byte)i, (byte)(i + 1)); + acc += gf_inv((byte)i); + } + acc += gf_mul(0, 0); + acc += gf_inv(0); + printf(" [wb] gf_mul/gf_inv zero and non-zero operands issued (%u)\n", + acc); + } +#else + printf(" WOLFSSL_PUF not compiled; nothing to exercise\n"); +#endif + + /* Always 0: a nonzero exit discards this white-box row's coverage. */ + return 0; +} From 802b31cc7ac98646e8a4c6226b65f074dec9ce63 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 10:02:38 +0200 Subject: [PATCH 13/57] wolfcrypt: xmss exhausted-key index marker wrapped and re-enabled signing --- wolfcrypt/src/wc_xmss_impl.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/wolfcrypt/src/wc_xmss_impl.c b/wolfcrypt/src/wc_xmss_impl.c index 7cd27a90e4..688f19ba87 100644 --- a/wolfcrypt/src/wc_xmss_impl.c +++ b/wolfcrypt/src/wc_xmss_impl.c @@ -194,13 +194,17 @@ do { \ } while (0) /* Check whether index is valid. + * + * Written as "i >= 2^h - 1" for the same reason as IDX32_INVALID: the + * previous "((i + 1) >> (h - 32)) != 0" form wrapped at i == 2^64 - 1, the + * exhausted-key marker of an 8-byte index field (the h = 60 parameter sets). * * @param [in] i Index to check. * @param [in] c Count of bytes i was encoded in. * @param [in] h Full tree Height. */ #define IDX64_INVALID(i, c, h) \ - ((w64GetHigh32(w64Add32(i, 1, NULL)) >> ((h) - 32)) != 0) + w64GTE(i, w64Sub32(w64ShiftLeft(w64From32(0, 1), h), 1, NULL)) /* Set 64-bit index as hash address value for tree. * @@ -251,13 +255,19 @@ do { \ } while (0) /* Check whether 32-bit index is valid. + * + * Written as "i >= 2^h - 1" rather than "((i + 1) >> h) != 0": the two agree + * everywhere except at i == 0xFFFFFFFF, where i + 1 wraps to 0 and the shift + * form reports the index VALID. 0xFFFFFFFF is exactly the exhausted-key + * marker this file writes into a 4-byte index field, so the shift form let a + * retired key be signed with again. * * @param [in] i Index to check. * @param [in] c Count of bytes i was encoded in. * @param [in] h Full tree Height. */ #define IDX32_INVALID(i, c, h) \ - ((((i) + 1) >> (h)) != 0) + ((i) >= ((((word32)1U) << (h)) - 1U)) /* Set 32-bit index as hash address value for tree. * @@ -3714,13 +3724,17 @@ static void xmss_idx_decode(XmssIdx* idx, word8 c, const unsigned char* a) } /* Check whether index is valid. + * + * Written as "i >= 2^h - 1" rather than "((i + 1) >> h) != 0": with XmssIdx + * 32 bits wide (WOLFSSL_XMSS_MAX_HEIGHT <= 32) the increment wraps at the + * all-ones exhausted-key marker and the shift form reports it valid. * * @param [in] i Index to check. * @param [in] h Full tree Height. */ static int xmss_idx_invalid(XmssIdx i, word8 h) { - return ((i + 1) >> h) != 0; + return i >= ((((XmssIdx)1) << h) - 1); } /* Get tree and leaf index from index. From 5240ede97e8d739ae1a18d44e42bebc4041b440a Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 10:04:45 +0200 Subject: [PATCH 14/57] tests: complete the argument-guard vectors for the TLS 1.3-only public entry points --- tests/api/test_tls13.c | 106 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index cc81b5a119..3e2b0402e3 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -525,6 +525,112 @@ int test_tls13_apis(void) WC_NO_ERR_TRACE(BAD_FUNC_ARG)); #endif + /* The accepting vector for the whole guard: + * required == NULL || ssl == NULL || !IsAtLeastTLSv1_3(ssl->version) + * Every case above leaves at least one operand true, so none of the three + * operands had a partner vector with the decision false. A live TLS 1.3 + * object with a non-NULL out-parameter supplies it. */ +#ifndef NO_WOLFSSL_CLIENT + ExpectIntEQ(wolfSSL_key_update_response(clientSsl, &required), 0); +#endif + + /* The remaining TLS 1.3-only entry points guarded by + * ssl == NULL || !IsAtLeastTLSv1_3(ssl->version) + * (or the CTX equivalent). Each needs all three vectors -- NULL, a + * TLS 1.2 object, and a TLS 1.3 object -- for both operands to have an + * independence partner. Return values other than BAD_FUNC_ARG are + * intentional: these calls are made on objects that never handshook, so a + * side/state error is the correct success indication that the argument + * guard was passed. */ +#ifdef WOLFSSL_SEND_HRR_COOKIE + ExpectIntEQ(wolfSSL_disable_hrr_cookie(NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); +#ifndef NO_WOLFSSL_CLIENT +#ifndef WOLFSSL_NO_TLS12 + ExpectIntEQ(wolfSSL_disable_hrr_cookie(clientTls12Ssl), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); +#endif + ExpectIntEQ(wolfSSL_disable_hrr_cookie(clientSsl), + WC_NO_ERR_TRACE(SIDE_ERROR)); +#endif +#ifndef NO_WOLFSSL_SERVER + ExpectIntEQ(wolfSSL_disable_hrr_cookie(serverSsl), WOLFSSL_SUCCESS); +#endif +#endif /* WOLFSSL_SEND_HRR_COOKIE */ + +#ifdef HAVE_SUPPORTED_CURVES + ExpectIntEQ(wolfSSL_CTX_only_dhe_psk(NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); +#ifndef NO_WOLFSSL_CLIENT +#ifndef WOLFSSL_NO_TLS12 + ExpectIntEQ(wolfSSL_CTX_only_dhe_psk(clientTls12Ctx), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); +#endif + ExpectIntEQ(wolfSSL_CTX_only_dhe_psk(clientCtx), 0); +#endif + + ExpectIntEQ(wolfSSL_only_dhe_psk(NULL), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); +#ifndef NO_WOLFSSL_CLIENT +#ifndef WOLFSSL_NO_TLS12 + ExpectIntEQ(wolfSSL_only_dhe_psk(clientTls12Ssl), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); +#endif + ExpectIntEQ(wolfSSL_only_dhe_psk(clientSsl), 0); +#endif +#endif /* HAVE_SUPPORTED_CURVES */ + + /* wolfSSL_require_psk() already has its NULL and TLS 1.3 vectors in + * test_tls13_require_psk_apis(); only the TLS 1.2 middle vector -- the one + * that makes the version operand true on its own -- was missing. */ +#ifndef NO_WOLFSSL_CLIENT +#ifndef WOLFSSL_NO_TLS12 + ExpectIntEQ(wolfSSL_require_psk(clientTls12Ssl), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); +#endif + ExpectIntEQ(wolfSSL_require_psk(clientSsl), 0); +#endif + +#if !defined(NO_WOLFSSL_SERVER) && defined(HAVE_SESSION_TICKET) + ExpectIntEQ(wolfSSL_send_SessionTicket(NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); +#ifndef WOLFSSL_NO_TLS12 + ExpectIntEQ(wolfSSL_send_SessionTicket(serverTls12Ssl), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); +#endif + /* Never handshook, so the argument guard is passed and the handshake-state + * check rejects it. */ + ExpectIntEQ(wolfSSL_send_SessionTicket(serverSsl), + WC_NO_ERR_TRACE(NOT_READY_ERROR)); +#endif + +#ifdef WOLFSSL_EARLY_DATA + ExpectIntEQ(wolfSSL_get_early_data_status(NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); +#ifndef NO_WOLFSSL_CLIENT +#ifndef WOLFSSL_NO_TLS12 + ExpectIntEQ(wolfSSL_get_early_data_status(clientTls12Ssl), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); +#endif + ExpectIntEQ(wolfSSL_get_early_data_status(clientSsl), + WOLFSSL_EARLY_DATA_NOT_SENT); +#endif +#endif /* WOLFSSL_EARLY_DATA */ + + /* wolfSSL_get_cipher_name_by_hash(): + * hash == NULL || ssl == NULL || (ssl->suites == NULL && ssl->ctx == NULL) + * Only the first two operands are reachable: ssl->ctx is invariant non-NULL + * for a live WOLFSSL, so the parenthesised sub-expression is dead and both + * of its operands are recorded in campaign/db/exclusions.json. */ +#ifndef NO_PSK +#ifndef NO_WOLFSSL_CLIENT + ExpectNull(wolfSSL_get_cipher_name_by_hash(clientSsl, NULL)); + ExpectNull(wolfSSL_get_cipher_name_by_hash(NULL, "SHA256")); +#if !defined(NO_SHA256) && (defined(HAVE_AESGCM) || defined(HAVE_CHACHA)) + ExpectNotNull(wolfSSL_get_cipher_name_by_hash(clientSsl, "SHA256")); +#endif +#endif +#endif /* !NO_PSK */ + #if !defined(NO_CERTS) && defined(WOLFSSL_POST_HANDSHAKE_AUTH) ExpectIntEQ(wolfSSL_CTX_allow_post_handshake_auth(NULL), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); From b454a5baa45a870c687e43e989fa540e458e3d99 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 10:05:00 +0200 Subject: [PATCH 15/57] tests: close the last MC/DC conditions in dsa.c, eccsi.c and sakke.c fault white-boxes --- tests/unit-mcdc/test_dsa_fault_whitebox.c | 119 +++++++++++++- tests/unit-mcdc/test_eccsi_fault_whitebox.c | 165 +++++++++++++++++++- tests/unit-mcdc/test_sakke_fault_whitebox.c | 89 +++++++++++ 3 files changed, 366 insertions(+), 7 deletions(-) diff --git a/tests/unit-mcdc/test_dsa_fault_whitebox.c b/tests/unit-mcdc/test_dsa_fault_whitebox.c index 09f7ea57dc..d9095ccabe 100644 --- a/tests/unit-mcdc/test_dsa_fault_whitebox.c +++ b/tests/unit-mcdc/test_dsa_fault_whitebox.c @@ -129,6 +129,46 @@ static long wb_fmi_seen(void) { return wb_fmi_count; } #define mp_init_multi(a, b, c, d, e, f) \ wb_fm_init_multi((a), (b), (c), (d), (e), (f)) +/* ---- narrow, opt-in MEMORY_E fault on mp_prime_is_prime_ex ------------ + * wc_MakeDsaParameters' tmp2 cleanup guard + * + * if ((err not MP_INIT_E) && (err not MEMORY_E)) mp_clear(tmp2); + * + * can only take its `err not MEMORY_E` FALSE half with tmp2 ALLOCATED, and the + * only MEMORY_E the function assigns itself (the tmp/tmp2 XMALLOC guard at + * :426) is a short-circuit `||`: when it fires, tmp2 is NULL by construction + * and the guard is never reached. So that half needs a MEMORY_E arriving from + * DEEPER, after both allocations succeeded. + * + * mp_prime_is_prime_ex() at :466 is such a source in the product: the sp_int + * implementation propagates wc_RNG_GenerateBlock()'s error code VERBATIM out + * of its Miller-Rabin base draw (wolfcrypt/src/sp_int.c, the `err = + * wc_RNG_GenerateBlock(rng, (byte*)b->dp, baseSz); if (err != MP_OKAY) break;` + * in sp_prime_is_prime_ex), and wc_RNG_GenerateBlock returns MEMORY_E when its + * own WOLFSSL_SMALL_STACK scratch allocation fails. Injecting MEMORY_E here is + * therefore the same value the real code path produces, just without having to + * guess which of the several hundred allocations inside a 1024-bit parameter + * generation is the DRBG's (an index-walk that far in is neither cheap nor + * stable across builds). + * + * One-shot, and armed only around a dedicated wc_MakeDsaParameters call, so + * nothing else in the file sees it. dsa.c has exactly one direct call site. */ +static int wb_pip_mem = 0; + +static int wb_fm_prime_is_prime_ex(const mp_int* a, int t, int* result, + WC_RNG* rng) +{ + if (wb_pip_mem) { + wb_pip_mem = 0; + return MEMORY_E; + } + return mp_prime_is_prime_ex(MCDC_FM_MI(a), t, result, rng); +} + +#undef mp_prime_is_prime_ex +#define mp_prime_is_prime_ex(a, t, r, g) \ + wb_fm_prime_is_prime_ex((a), (t), (r), (g)) + #include #include "mcdc_fault_alloc.h" @@ -284,6 +324,75 @@ static void wb_crafted(WC_RNG* rng, DsaKey* key, const byte* digest, } } + /* ---- KEY-GENERATION MP_INIT_E cleanup halves: wc_MakeDsaKey 332 idx1 + * and wc_MakeDsaParameters 520 idx0 / 526 idx0 / 537 idx1. + * + * These were dead until wolfcrypt commit 1e8807b13. Neither key-generation + * entry point mapped a failed mp_init_multi() to MP_INIT_E -- mp_init_multi + * reports the BACKEND's code (MP_MEM from the heap backends) -- so + * wc_MakeDsaParameters' "err not MP_INIT_E" guards could never fire and + * wc_MakeDsaKey had no guard at all and ran mp_clear(tmpQ) on never- + * initialised storage (the SIGSEGV recorded in DEATHNOTE.md). Both sites + * now use the idiom wc_DsaSign_ex/wc_DsaVerify_ex already used, so failing + * THEIR mp_init_multi is the way into these halves and is crash-safe: the + * guards exist precisely to skip the clears. + * + * Each entry point performs exactly one mp_init_multi and it is the first + * one of the call (nothing ahead of it initialises an mp_int), so index 1 + * selects it in both cases. The accepting halves of the same guards come + * from the unarmed key/parameter generation in main(). */ + { + DsaKey ik; + XMEMSET(&ik, 0, sizeof(ik)); + if (wc_InitDsaKey(&ik) == 0) { + if (wc_DsaImportParamsRaw(&ik, kP, kQ, kG) == 0) { + wb_fmi_arm(1); + ret = wc_MakeDsaKey(rng, &ik); + wb_fmi_disarm(); + if (ret != WC_NO_ERR_TRACE(MP_INIT_E)) + printf(" [wb] MakeDsaKey init fault returned %d, not " + "MP_INIT_E: 332 idx1 NOT driven\n", ret); + } + wc_FreeDsaKey(&ik); + } + } + { + DsaKey pk; + XMEMSET(&pk, 0, sizeof(pk)); + if (wc_InitDsaKey(&pk) == 0) { + wb_fmi_arm(1); + ret = wc_MakeDsaParameters(rng, 1024, &pk); + wb_fmi_disarm(); + if (ret != WC_NO_ERR_TRACE(MP_INIT_E)) + printf(" [wb] MakeDsaParameters init fault returned %d, not " + "MP_INIT_E: 520/526/537 NOT driven\n", ret); + wc_FreeDsaKey(&pk); + } + } + + /* ---- wc_MakeDsaParameters 526 idx1 (`err not MEMORY_E` FALSE with tmp2 + * allocated). The function's OWN MEMORY_E (:426) is a short-circuit `||` + * over the tmp/tmp2 XMALLOCs, so whenever it fires tmp2 is NULL and the + * `if (tmp2 != NULL)` gate above 526 skips the guard entirely -- that path + * closes 520 idx1 (tmp allocated, tmp2 not) and can never close 526 idx1. + * The vector needs a MEMORY_E raised AFTER both allocations succeeded; + * mp_prime_is_prime_ex is such a source in the product (see the + * wb_fm_prime_is_prime_ex note). One-shot, fires on dsa.c's single direct + * call site at :466. */ + { + DsaKey pk; + XMEMSET(&pk, 0, sizeof(pk)); + if (wc_InitDsaKey(&pk) == 0) { + wb_pip_mem = 1; + ret = wc_MakeDsaParameters(rng, 1024, &pk); + wb_pip_mem = 0; + if (ret != WC_NO_ERR_TRACE(MEMORY_E)) + printf(" [wb] MakeDsaParameters MEMORY_E fault returned %d: " + "526 idx1 NOT driven\n", ret); + wc_FreeDsaKey(&pk); + } + } + /* ---- _DsaImportParamsRaw with trusted == 0: the (T,T) vector of * `err == MP_OKAY && !trusted` at :540. wc_DsaImportParamsRaw (used * everywhere else, including by the sweeps) hard-codes trusted = 1, so @@ -350,10 +459,14 @@ static void wb_crafted(WC_RNG* rng, DsaKey* key, const byte* digest, } else { /* Expected for every WOLFSSL_SP_INT_NEGATIVE-less build: - * sp_int.h then #defines mp_isneg(a) to the constant (0), - * so 887's second operand cannot be TRUE there at all. */ + * sp_int.h then #defines sp_isneg(a) to the constant (0), + * so 906's second operand cannot be TRUE there at all -- + * which is exactly why the module carries an + * `sp_negative` variant (-DWOLFSSL_SP_INT_NEGATIVE), the + * one build in which this vector is productive. */ printf(" [wb] no negative mp_int (set=%d sub=%d neg=%d " - "zero=%d): 887 idx1 skipped\n", + "zero=%d): 906 idx1 needs the sp_negative " + "variant\n", e1, e2, (int)mp_isneg(neg), (int)mp_iszero(neg)); } mp_clear(neg); diff --git a/tests/unit-mcdc/test_eccsi_fault_whitebox.c b/tests/unit-mcdc/test_eccsi_fault_whitebox.c index 8f40314002..37d9eed612 100644 --- a/tests/unit-mcdc/test_eccsi_fault_whitebox.c +++ b/tests/unit-mcdc/test_eccsi_fault_whitebox.c @@ -80,6 +80,75 @@ * (mp_free/mp_forcezero) are NOT interposed, so cleanup keeps working. */ #include "mcdc_fault_mp.h" +/* -------------------------------------------------------------------------- + * Value-forcing mp_addmod() interposer for eccsi_gen_sig()'s rejection loop + * (eccsi.c:1934): + * + * do { ... err = mp_mulmod(r, &key->ssk, &key->params.order, s); + * err = mp_addmod(he, s, &key->params.order, s); } + * while ((err == 0) && (mp_iszero(s) || (mp_cmp(s, he) == MP_EQ))); + * + * RFC 6507 step 4 rejects the candidate when s == 0 or s == HE. Both are + * ~2^-256 events on real entropy, so no amount of API driving reaches them, + * and a seeded RNG cannot force them either: s is the output of a modular + * multiply-and-add over an ephemeral scalar, not a value the generator hands + * out. Without one of the two rejecting draws the decision only ever records + * (T,F,F), which is a single vector -- that is why ALL THREE conditions were + * open, including `err == 0`, whose independence pair needs a vector where the + * loop actually REPEATS. + * + * eccsi.c has exactly two mp_addmod() call sites -- eccsi_make_pair() at 920 + * and this one at 1931 -- and only the second is reached from + * wc_SignEccsiHash(), so a one-shot armed immediately around a sign call needs + * no disambiguation. + * + * The modes are ONE-SHOT, so the loop's SECOND iteration computes a genuine s + * and terminates on real data: no retry loop here depends on a random draw + * going a particular way, and the accepting (T,F,F) row is produced by that + * same iteration in the same binary as the rejecting rows. + * + * Ordering is the load-bearing trick from mcdc_fault_mp.h: the wrapper is + * compiled while mp_addmod still names the (already interposed) real thing, + * and only then is the name redefined. eccsi.c is #included AFTER this block. + * ----------------------------------------------------------------------- */ +#define WB_EA_OFF 0 /* pass through */ +#define WB_EA_ZERO 1 /* succeed, but hand back s == 0 -> 1934 idx1 */ +#define WB_EA_EQ 2 /* succeed, but hand back s == he -> 1934 idx2 */ +#define WB_EA_FAIL 3 /* fail the add -> 1934 idx0 */ + +static int wb_ea_mode = WB_EA_OFF; + +MCDC_FM_MAYBE_UNUSED static int wb_ea_addmod(const mp_int* a, const mp_int* b, + const mp_int* m, mp_int* r) +{ + int mode = wb_ea_mode; + int err; + + if (mode != WB_EA_OFF) { + wb_ea_mode = WB_EA_OFF; /* one-shot */ + } + if (mode == WB_EA_FAIL) { + return MCDC_FM_ERR; + } + + err = mp_addmod(a, b, m, r); + if (err == 0) { + if (mode == WB_EA_ZERO) { + /* s = 0: RFC 6507's first rejection test. */ + mp_zero(r); + } + else if (mode == WB_EA_EQ) { + /* s = HE: the second rejection test. `a` IS he at the 1931 call + * site, so this needs no extra handle on the key. */ + err = mp_copy(a, r); + } + } + return err; +} + +#undef mp_addmod +#define mp_addmod(a, b, c, d) wb_ea_addmod((a), (b), (c), (d)) + #include #include "mcdc_fault_alloc.h" @@ -165,6 +234,81 @@ static int wb_mp_expired(void) printf(" [wb] mp sweep %s: K=%ld\n", (lbl), k_); \ } while (0) +/* ---- eccsi_gen_sig() 1934 rejection loop ------------------------------- + * while ((err == 0) && (mp_iszero(s) || (mp_cmp(s, he) == MP_EQ))); + * + * Three conditions, and before this vector set ALL THREE were open, because + * the loop had only ever been observed taking the single (T,F,F) exit: the two + * RFC 6507 step-4 rejections (s == 0, s == HE) are ~2^-256 draws, and `err==0` + * cannot show independence without a partner vector in which the decision is + * TRUE -- i.e. in which the loop actually repeats. + * + * The one-shot mp_addmod modes at the top of this file supply all of it: + * WB_EA_ZERO -> (T,T,-) TRUE, retries; the retry is the accepting (T,F,F) + * WB_EA_EQ -> (T,F,T) TRUE, likewise + * WB_EA_FAIL -> (F,-,-) FALSE + * + * Self-contained fixture on purpose: the shared fixture in wb_mp_sweeps() is + * driven through armed sweeps before the sign path is reached, and one of them + * currently leaves the key in a state wc_SetEccsiPair rejects (see the note + * there). A fresh key here means these vectors cannot be lost to that. + */ +static void wb_gen_sig_reject(WC_RNG* rng) +{ + EccsiKey k; + ecc_point* pvt = NULL; + mp_int ssk; + byte id[] = "eccsi-gensig@wolfssl.com"; + byte hash[WC_MAX_DIGEST_SIZE]; + byte hashSz = 0; + int ready = 0; + int mode; + + mcdc_fm_disarm(); + XMEMSET(&k, 0, sizeof(k)); + XMEMSET(&ssk, 0, sizeof(ssk)); + XMEMSET(hash, 0, sizeof(hash)); + + if (wc_InitEccsiKey(&k, NULL, INVALID_DEVID) != 0) { + WB_NOTE("gen_sig fixture: wc_InitEccsiKey failed; 1934 skipped"); + return; + } + pvt = wc_ecc_new_point_h(NULL); + if ((pvt != NULL) && (mp_init(&ssk) == 0) && + (wc_MakeEccsiKey(&k, rng) == 0) && + (wc_MakeEccsiPair(&k, rng, WC_HASH_TYPE_SHA256, id, + (word32)sizeof(id), &ssk, pvt) == 0) && + (wc_SetEccsiPair(&k, &ssk, pvt) == 0) && + (wc_HashEccsiId(&k, WC_HASH_TYPE_SHA256, id, (word32)sizeof(id), + pvt, hash, &hashSz) == 0) && + (wc_SetEccsiHash(&k, hash, hashSz) == 0)) { + ready = 1; + } + if (!ready) { + WB_NOTE("gen_sig fixture setup failed; 1934 skipped"); + wb_fail = 1; + } + else { + for (mode = WB_EA_ZERO; mode <= WB_EA_FAIL; mode++) { + byte sg[257]; + word32 z = (word32)sizeof(sg); + int e; + + XMEMSET(sg, 0, sizeof(sg)); + wb_ea_mode = mode; + e = wc_SignEccsiHash(&k, rng, WC_HASH_TYPE_SHA256, hash, + WC_SHA256_DIGEST_SIZE, sg, &z); + wb_ea_mode = WB_EA_OFF; + printf(" [wb] gen_sig reject mode %d -> %d\n", mode, e); + } + } + + mp_free(&ssk); + if (pvt != NULL) + wc_ecc_del_point_h(pvt, NULL); + wc_FreeEccsiKey(&k); +} + static void wb_mp_sweeps(WC_RNG* rng) { EccsiKey k; @@ -172,6 +316,7 @@ static void wb_mp_sweeps(WC_RNG* rng) mp_int ssk; byte id[] = "eccsi-mp-fault@wolfssl.com"; byte hash[WC_MAX_DIGEST_SIZE]; + byte hashSz = 0; byte sig[257]; word32 sigSz; int verified = 0; @@ -180,6 +325,10 @@ static void wb_mp_sweeps(WC_RNG* rng) wb_mp_t0 = time(NULL); mcdc_fm_disarm(); + /* Runs first, on its own fixture: see wb_gen_sig_reject(). */ + wb_gen_sig_reject(rng); + mcdc_fm_disarm(); + XMEMSET(&k, 0, sizeof(k)); XMEMSET(&ssk, 0, sizeof(ssk)); XMEMSET(hash, 0x5a, sizeof(hash)); @@ -219,8 +368,8 @@ static void wb_mp_sweeps(WC_RNG* rng) mcdc_fm_disarm(); if ((wc_SetEccsiPair(&k, &ssk, pvt) == 0) && (wc_HashEccsiId(&k, WC_HASH_TYPE_SHA256, id, (word32)sizeof(id), - pvt, hash, NULL) == 0) && - (wc_SetEccsiHash(&k, hash, WC_SHA256_DIGEST_SIZE) == 0)) { + pvt, hash, &hashSz) == 0) && + (wc_SetEccsiHash(&k, hash, hashSz) == 0)) { WB_MP_SWEEP("SignEccsiHash", 200, { byte s2[257]; @@ -242,8 +391,16 @@ static void wb_mp_sweeps(WC_RNG* rng) } } else { - WB_NOTE("SetEccsiPair/HashEccsiId/SetEccsiHash failed; sign+verify " - "sweeps skipped"); + /* Print WHICH step refused, so this never has to be bisected again: + * the original spelling passed hashSz = NULL to wc_HashEccsiId(), + * which rejects it with BAD_FUNC_ARG, and the SignEccsiHash / + * VerifyEccsiHash sweeps below were silently skipped on every run. */ + int e1 = wc_SetEccsiPair(&k, &ssk, pvt); + int e2 = wc_HashEccsiId(&k, WC_HASH_TYPE_SHA256, id, + (word32)sizeof(id), pvt, hash, &hashSz); + int e3 = wc_SetEccsiHash(&k, hash, hashSz); + printf(" [wb] SetEccsiPair=%d HashEccsiId=%d SetEccsiHash=%d; " + "sign+verify sweeps skipped\n", e1, e2, e3); } /* 196/202/208 eccsi_load_ecc_params(): diff --git a/tests/unit-mcdc/test_sakke_fault_whitebox.c b/tests/unit-mcdc/test_sakke_fault_whitebox.c index 0f4a48ddd1..5c66d87f6c 100644 --- a/tests/unit-mcdc/test_sakke_fault_whitebox.c +++ b/tests/unit-mcdc/test_sakke_fault_whitebox.c @@ -496,6 +496,39 @@ int main(int argc, char** argv) } WB_NOTE("wc_MakeSakkeKey fault sweep done"); + /* --- wc_MakeSakkeKey 543: the master-secret retry loop + * while ((err == 0) && mp_iszero(wc_ecc_key_get_priv(..))) + * Both conditions need the loop to actually RETRY, i.e. a drawn scalar + * that reduces to zero -- a ~2^-1024 event that no heap fault and no + * seeded RNG can produce (mp_rand() is served from another TU, so the + * seeded-RNG macro never reaches it). The value-forcing mp_rand() + * interposer at the top of this file is the lever; sakke.c has exactly + * one mp_rand() call site, and both modes are ONE-SHOT so the second + * iteration draws real entropy and the loop terminates on real data. + * + * WB_SR_ZERO -> (T,T) decision TRUE, retries; the retry supplies the + * accepting (T,F) row -> closes idx1 and gives idx0 + * the TRUE-decision partner it needs + * WB_SR_FAIL -> (F,-) decision FALSE -> closes idx0 + * Both rows land in THIS binary alongside the ordinary (T,F). --- */ + { + int mode; + for (mode = WB_SR_ZERO; mode <= WB_SR_FAIL; mode++) { + SakkeKey zk; + XMEMSET(&zk, 0, sizeof(zk)); + if (wc_InitSakkeKey_ex(&zk, 128, ECC_SAKKE_1, NULL, + INVALID_DEVID) == 0) { + int e2; + wb_sr_mode = mode; + e2 = wc_MakeSakkeKey(&zk, &rng); + wb_sr_mode = WB_SR_OFF; + printf(" [wb] MakeSakkeKey rand mode %d -> %d\n", + mode, e2); + wc_FreeSakkeKey(&zk); + } + } + } + /* --- wc_MakeSakkeRsk: sweeps its own success chain. rsk output only; * key state (public key + master secret) untouched, so reuse key. * Fresh rsk point per iteration. --- */ @@ -544,6 +577,62 @@ int main(int argc, char** argv) * err==0 false half. Where mp_add/mp_mul_d never allocate (fixed * sp_int), the sweep is a no-op and these stay justified residuals. */ { + /* sakke_tplmod 1518 idx0 (`err == 0` FALSE) -- the THIRD of the + * helper's three sequential reductions: + * + * err = mp_mul_d(a, 3, r); (1511) + * if ((err == 0) && (mp_cmp(r,m) != MP_LT)) sub; (1512) + * if ((err == 0) && (mp_cmp(r,m) != MP_LT)) sub; (1515) + * if ((err == 0) && (mp_cmp(r,m) != MP_LT)) sub; (1518) + * + * 1512 and 1515 get their err!=0 halves for free -- an earlier + * mp_sub failing carries err forward -- but 1518 needs the mp_sub + * at 1516 to have RUN and FAILED, which needs an input where the + * first two reductions both fire. Every in-product caller passes + * a < m, so 3a < 3m and the third check is only ever reached with + * err == 0; a direct call on the file-static helper with a == m + * makes all three fire (300 -> 200 -> 100 -> 0). + * + * The heap lever cannot do it: under a fixed sp_int backend + * mp_sub() never allocates, so it never fails. mcdc_fault_mp.h's + * lever can -- interposed mp_* call 1 is the mp_mul_d, 2 is the + * mp_sub at 1513 and 3 is the mp_sub at 1516, so arming index 3 + * fails exactly that one and 1518 is reached with err != 0. + * + * The accepting (T,T) row at 1518 is the unarmed a == m call right + * before it; the (T,F) row comes from the a=40,m=100 tplmod calls + * in the sweep below (300 -> 200 -> 100 -> stop). */ + { + mp_int a, m, r; + int e0, e1; + mp_init(&a); mp_init(&m); mp_init(&r); + mp_set(&a, 100); mp_set(&m, 100); + e1 = sakke_tplmod(&a, &m, &r); /* 1518 (T,T) */ + mcdc_fm_arm(3); + e0 = sakke_tplmod(&a, &m, &r); /* 1518 (F,-) */ + mcdc_fm_disarm(); + printf(" [wb] sakke_tplmod 1518 vectors: (T,T)=%d armed=%d\n", + e1, e0); + mp_free(&a); mp_free(&m); mp_free(&r); + } + + /* sakke_addmod 1490 idx0, same shape but one reduction: a direct + * armed call is the only way to reach it with err != 0. */ + { + mp_int a, b, m, r; + int e0; + mp_init(&a); mp_init(&b); mp_init(&m); mp_init(&r); + mp_set(&a, 60); mp_set(&b, 70); mp_set(&m, 100); + mcdc_fm_arm(1); + e0 = sakke_addmod(&a, &b, &m, &r); /* 1490 (F,-) */ + mcdc_fm_disarm(); + (void)sakke_addmod(&a, &b, &m, &r); /* 1490 (T,T) */ + mp_set(&a, 10); mp_set(&b, 20); + (void)sakke_addmod(&a, &b, &m, &r); /* 1490 (T,F) */ + printf(" [wb] sakke_addmod 1490 armed=%d\n", e0); + mp_free(&a); mp_free(&b); mp_free(&m); mp_free(&r); + } + for (n = 1; n <= SAKKE_K_SMALLMP; n++) { mp_int a, b, m, r; mp_init(&a); mp_init(&b); mp_init(&m); mp_init(&r); From da2e4b4e78ed99da14dd5649046442e7a23f29b6 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 10:05:05 +0200 Subject: [PATCH 16/57] wolfssl: keep the PEM no-start-line reason code out of the error trace path --- wolfssl/internal.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 7569a5cb2a..0fa681d9e0 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -7010,14 +7010,15 @@ struct SystemCryptoPolicy { do { \ (err) = wolfSSL_ERR_peek_last_error(); \ if (wolfSSL_ERR_GET_LIB(err) == WOLFSSL_ERR_LIB_PEM && \ - wolfSSL_ERR_GET_REASON(err) == -WOLFSSL_PEM_R_NO_START_LINE_E) { \ + wolfSSL_ERR_GET_REASON(err) == \ + -WC_NO_ERR_TRACE(WOLFSSL_PEM_R_NO_START_LINE_E)) { \ unsigned long peekErr; \ do { \ wc_RemoveErrorNode(-1); \ peekErr = wolfSSL_ERR_peek_last_error(); \ } while (wolfSSL_ERR_GET_LIB(peekErr) == WOLFSSL_ERR_LIB_PEM && \ wolfSSL_ERR_GET_REASON(peekErr) == \ - -WOLFSSL_PEM_R_NO_START_LINE_E); \ + -WC_NO_ERR_TRACE(WOLFSSL_PEM_R_NO_START_LINE_E)); \ } \ } while(0) #else From 490c67903049e92aadf76e28fec2973d51360c78 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 10:05:10 +0200 Subject: [PATCH 17/57] wolfssl: include chacha20_poly1305.h whenever the ChaCha20-Poly1305 suites are built --- wolfssl/internal.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 0fa681d9e0..844e739cda 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -53,7 +53,10 @@ #ifdef HAVE_POLY1305 #include #endif -#if defined(HAVE_CHACHA) && defined(HAVE_POLY1305) && defined(OPENSSL_EXTRA) +#if defined(HAVE_CHACHA) && defined(HAVE_POLY1305) + /* Not OPENSSL_EXTRA-only: the TLS record layer calls the persistent-key + * helpers wc_ChaCha20Poly1305_{Encrypt,Decrypt}_ex(), so this header has + * to be visible whenever the ChaCha20-Poly1305 suites are built. */ #include #endif #ifdef HAVE_ARIA From f14d4f215132469cd5b22464301b671a075c863f Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 10:12:06 +0200 Subject: [PATCH 18/57] tests: pin the flaky lms treehash_update ret operands with a computed node-hash fault --- tests/unit-mcdc/test_lms_bds_whitebox.c | 61 +++++++++++++++++++++---- 1 file changed, 53 insertions(+), 8 deletions(-) diff --git a/tests/unit-mcdc/test_lms_bds_whitebox.c b/tests/unit-mcdc/test_lms_bds_whitebox.c index 86b80bd06f..9dca3929d8 100644 --- a/tests/unit-mcdc/test_lms_bds_whitebox.c +++ b/tests/unit-mcdc/test_lms_bds_whitebox.c @@ -87,6 +87,25 @@ * is known to depend on RNG-driven key diversity across variants; nothing * here pins or perturbs any RNG, so that diversity is untouched.) * + * WHAT THIS FILE DELIBERATELY DOES NOT COVER + * ------------------------------------------ + * `2254:...:1` and `2414:...:4` are the SAME operand text, + * + * ((i >> (h-1)) != ((i + 1) >> (h - 1))) + * + * in wc_lms_treehash_init() and wc_lms_treehash_update(). It is a tautology + * at every evaluation, so no vector can pair it. Both sites reach it only + * from inside + * + * while ((ret == 0) && ((j & 0x1) == 1)) { ...; j >>= 1; h++; ... } + * + * whose k-th entry requires the pre-shift value i >> (k-1) to be odd. On + * arrival with a given h, bits 0 .. h-1 of i are therefore all set, i.e. + * i == (i >> (h-1)) * 2^(h-1) + (2^(h-1) - 1). Adding one carries out of + * bit h-1, so (i + 1) >> (h - 1) == (i >> (h - 1)) + 1: the two sides differ + * by exactly one on every evaluation and the `!=` is never false. Recorded + * in campaign/db/exclusions.json and EXCLUSIONS.md. + * * COST: no keygen and no signing. The most expensive driver computes 8 WOTS * leaves; the whole program is a few hundred thousand SHA-256 blocks, orders * of magnitude inside the campaign's 600 s TEST_TIMEOUT. @@ -500,8 +519,10 @@ static void wb_treehash_init_fault(const WbFam* f) * 2414:...:2 wc_lms_treehash_update(): * if ((ret == 0) && (q == 0) && (!useRoot) && * (h > params->height - params->rootLevels) && ...) + * 2414:...:0 and 2424:...:0 wc_lms_treehash_update(): the `ret == 0` + * operand of the same root copy and of the auth-path store below it. * - * Three direct calls over the same fixed leaf range, all with q == 0: + * Four direct calls over the same fixed leaf range, all with q == 0: * * 1. useRoot = 0, leaves [0 .. 3] -> 2414 all-true row (accepting). * 2. useRoot = 1, leaves [0 .. 3] -> 2414 with `!useRoot` false while @@ -516,9 +537,21 @@ static void wb_treehash_init_fault(const WbFam* f) * it. Every buffer here is sized WB_NODES nodes, well past what the * over-long climb indexes. * + * 4. useRoot = 0, leaves [0 .. 3], armed at primitive call 1 -> the two + * `ret == 0` operands (2414 cond 0, 2424 cond 0) go false, paired with + * call 1's all-true rows. The fault index needs no arithmetic here: + * leaves 0..3 are served from the leaf cache, so wc_lms_treehash_update + * issues NO primitive call until the carry chain of leaf 1 reaches + * wc_lms_interior_hash() -- primitive call 1 IS that node hash. + * These two conditions are the pair the campaign's 2026-08-11 flake + * hunt recorded as non-deterministic (they depend on where the global + * strided hash-fault sweep in test_lms_hash_fault_whitebox.c happens to + * land, which moves with the RNG-drawn key). This vector pins them. + * * leaf.idx starts at 0 with cacheBits = 2, so leaves 0..3 are served from * the (zeroed) leaf cache -- the tree content is irrelevant to these - * decisions and this keeps calls 1 and 2 nearly free. + * decisions, it keeps calls 1, 2 and 4 nearly free, and it is what makes + * call 4's fault index exact. ******************************************************************/ static void wb_treehash_update_roots(const WbFam* f) { @@ -531,11 +564,14 @@ static void wb_treehash_update_roots(const WbFam* f) byte leaf_cache[WB_NODES * WB_HLEN_MAX]; int ret; int i; - static const struct { word32 max_idx; int useRoot; const char* what; } - calls[] = { - { 3, 0, "q=0 useRoot=0 (2414 accepting row)" }, - { 3, 1, "q=0 useRoot=1 (2414 cond-2 rejecting row)" }, - { 7, 1, "leaf 7 on a height-2 tree (2397 cond-2 rejecting row)" }, + static const struct { + word32 max_idx; int useRoot; long arm; const char* what; + } calls[] = { + { 3, 0, 0, "q=0 useRoot=0 (2414/2424 accepting rows)" }, + { 3, 1, 0, "q=0 useRoot=1 (2414 cond-2 rejecting row)" }, + { 7, 1, 0, "leaf 7 on a height-2 tree (2397 cond-2 rejecting row)" }, + { 3, 0, 1, "q=0 useRoot=0, node hash faulted " + "(2414/2424 cond-0 rejecting rows)" }, }; wb_params(¶ms, f, 1, WB_HEIGHT); @@ -560,13 +596,22 @@ static void wb_treehash_update_roots(const WbFam* f) priv.leaf.idx = 0; priv.leaf.offset = 0; + if (calls[i].arm != 0) { + mcdc_fh_arm(calls[i].arm); + } ret = wc_lms_treehash_update(&state, &priv, wb_id, wb_seed, 0, calls[i].max_idx, 0, calls[i].useRoot); - if (ret != 0) { + mcdc_fh_disarm(); + if ((calls[i].arm == 0) && (ret != 0)) { printf(" [wb] treehash_update %s returned %d\n", calls[i].what, ret); wb_fail = 1; } + else if ((calls[i].arm != 0) && (ret == 0)) { + printf(" [wb] treehash_update %s did NOT propagate the faulted " + "node hash\n", calls[i].what); + wb_fail = 1; + } } wb_state_free(&state); From 77b1c9de0b20acb8bcbbc34e194348353fb43062 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 10:15:49 +0200 Subject: [PATCH 19/57] tests: pair tsp.c TspResponse_Verify cert!=NULL with a wrong-trusted-cert row --- tests/unit-mcdc/test_tsp_fault_whitebox.c | 25 +++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/unit-mcdc/test_tsp_fault_whitebox.c b/tests/unit-mcdc/test_tsp_fault_whitebox.c index 60873314c4..0a48ec130b 100644 --- a/tests/unit-mcdc/test_tsp_fault_whitebox.c +++ b/tests/unit-mcdc/test_tsp_fault_whitebox.c @@ -97,6 +97,18 @@ * tsp.c:1763 idx0,idx3 - Tsp_CheckSignerCert no-EKU / EKU-not-critical * tsp.c:1773 idx0,idx2 - Tsp_CheckSignerCert no-KU / KU-zero-bits * tsp.c:2162 idx0,idx1 - TspResponse_Verify cm != NULL + * tsp.c:2167 idx1 - TspResponse_Verify cert != NULL. The public + * entry points make this operand invariantly true: + * wc_TspResponse_Verify() rejects a NULL cert up + * front, and wc_TspResponse_VerifyWithCm() -- the + * only caller that passes cert == NULL -- passes a + * non-NULL cm, so the cm arm above consumes the + * ret == 0 arrival. Called directly (this TU has + * the static in scope) the operand pairs: cm == + * NULL with cert == NULL gives the decision's + * false row, and cm == NULL with a *different* + * trusted certificate gives its true row, both on + * the same verified token. * Plus allocation err-chain coverage (mcdc_fault_alloc.h fault sweep) over * wc_TspTstInfo_SignWithPkcs7()'s tstDer/attribs XMALLOC calls, and two cheap * bonus rows opportunistic with the above (tsp.c:939 SetNonce loop entry, @@ -1292,6 +1304,19 @@ static void wb_response_verify_cm(void) } } + /* 2167 idx1 true row: cm==NULL and a non-NULL trusted certificate that + * is NOT the signer. The token carries the TSA's own certificate, so + * wc_TspTstInfo_VerifyWithPKCS7() still returns 0 and the else-if is + * reached with ret==0; the pin comparison then fails on the length + * operand. Paired with the cert==NULL calls above, which reach the same + * else-if with ret==0 and take its false side on this very operand. */ + ret = TspResponse_Verify(&resp, client_cert_der_2048, + (word32)sizeof_client_cert_der_2048, NULL, NULL); + if (ret != WC_NO_ERR_TRACE(TSP_VERIFY_E)) { + WB_NOTE("TspResponse_Verify(wrong trusted cert) did not reject"); + wb_fail = 1; + } + /* 2162 idx1 true: ret==0 with cm != NULL. An empty (no trust anchors) * certificate manager still exercises the decision's TRUE row; the * ensuing chain verify is expected to fail (untrusted signer), which is From cbb22d3f572bae6477d42a956d5270a42dc4346f Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 10:17:15 +0200 Subject: [PATCH 20/57] tests: close sp_int.c randomised Miller-Rabin err operand with a pinned RNG --- tests/unit-mcdc/test_sp_int_whitebox.c | 83 +++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 9 deletions(-) diff --git a/tests/unit-mcdc/test_sp_int_whitebox.c b/tests/unit-mcdc/test_sp_int_whitebox.c index ec1c76f143..e6bc4ec3ed 100644 --- a/tests/unit-mcdc/test_sp_int_whitebox.c +++ b/tests/unit-mcdc/test_sp_int_whitebox.c @@ -37,12 +37,37 @@ * SMALL_STACK allocation-ceiling macros, and the 32-bit SP_WORD_SIZE axis). */ +/* settings.h FIRST, and before mcdc_seed_rng.h: that header decides whether it + * can build its SHAKE-256 stream by testing WOLFSSL_SHAKE256, and with no + * configuration in scope yet the test reads "no" and the header compiles + * itself down to inert stubs -- silently, with the driver still building and + * running. That is what happened on the first attempt at :19672:0 here. + * settings.h is include-guarded and idempotent, so pulling it in early costs + * nothing; sp_int.c below includes it again. */ +#include + +/* Deterministic RNG. Included BEFORE sp_int.c so its wc_RNG_GenerateBlock() + * call sites bind to the pinned SHAKE-256 stream while armed. Two things make + * this load-bearing for Class 23 below, not just cosmetic: + * - the stream is reproducible, so the fail-index sweep is reproducible; + * - the armed hook never enters the DRBG, so it makes NO allocation of its + * own. Under WOLFSSL_SMALL_STACK the real Hash_DRBG_Generate() does + * allocate, and one faulted allocation there puts the WC_RNG into its + * permanent DRBG_FAILED state -- after which every later sweep step dies + * at the first wc_RNG_GenerateBlock() instead of reaching the decision + * under test. That is exactly why the earlier real-entropy sweep never + * closed :19672:0. */ +#include "mcdc_seed_rng.h" + /* Pull sp_int.c in verbatim so its file-static helpers and the sp_int * struct's fields are in scope and instrumented in THIS binary. sp_int.c * includes settings.h (which picks up user_settings.h via * -DWOLFSSL_USER_SETTINGS) and sp_int.h itself. */ #include +#define MCDC_SR_IMPL +#include "mcdc_seed_rng.h" + #include "mcdc_fault_alloc.h" #include @@ -1630,6 +1655,11 @@ static void wb_gcd_r_small_b(void) * modular exponentiation it performs. Its false side (a composite rejected * on the result operand) has to be in the same binary. * ------------------------------------------------------------------------- */ +/* Pinned RNG seed for the randomised Miller-Rabin trial loop (Class 23). + * Recorded here because a seed that reaches a condition is a test vector: the + * next person has to be able to reproduce the result. */ +#define WB_PRIME_RNG_SEED 0x5eed0001UL + static void wb_prime_trial_alloc(void) { sp_int a; @@ -1709,15 +1739,13 @@ static void wb_prime_trial_alloc(void) (void)_sp_prime_random_trials(&a, 8, &res, &rng); wb_set_d(&a, (sp_int_digit)100160063ULL); (void)_sp_prime_random_trials(&a, 8, &res, &rng); - /* NOT CLOSED. The deterministic sibling's `err != MP_OKAY` operand - * closes on this sweep; this one does not, at any depth tried - * (n <= 30, 60, 120). The randomised loop draws a fresh candidate - * before every Miller-Rabin round and re-draws rejected ones, so - * the index of the round's own allocation is not a fixed offset - * from the arming point the way it is in _sp_prime_trials(). A - * pinned-seed RNG (mcdc_seed_rng.h) would make it one; it was not - * added for a single condition, and the condition is reported open - * rather than excluded -- it is not proven unreachable. */ + /* Real-entropy sweep. Kept for the rows it does reach (the + * wc_RNG_GenerateBlock() error break at :19666's neighbour), but it + * does NOT close :19672:0: the first faulted allocation lands inside + * Hash_DRBG_Generate() and leaves the WC_RNG permanently + * DRBG_FAILED, so every later index dies at the draw. Measured: for + * n >= 3 the call returns RNG_FAILURE_E with the RNG reporting a + * failure, never MP_MEM from the exponentiation. */ for (n = 1; n <= 30; n++) { wb_set_d(&a, (sp_int_digit)2147483647UL); mcdc_fa_arm_only(n); @@ -1726,6 +1754,43 @@ static void wb_prime_trial_alloc(void) } wc_FreeRng(&rng); } + + /* :19672:0 (`err != MP_OKAY` of the RANDOMISED trial loop), closed with a + * PINNED, allocation-free RNG on a fresh WC_RNG. + * + * PINNED INPUT (evidence): mcdc_seed_rng.h armed with seed 0x5eed0001; + * candidate a = 2^31 - 1 (prime) for the error rows and + * a = 100160063 = 10007 * 10009 (composite, both factors past the end of + * the small-prime table) for the *result == MP_NO row; trials = 8. + * + * With the armed hook the loop makes no allocation of its own before + * sp_prime_miller_rabin(), so allocation index 1 and 2 are this + * function's two ALLOC_SP_INT_ARRAYs (err set before the loop; the + * decision is not reached) and every index from 3 up lands inside the + * exponentiation, returning MP_MEM into `err` and breaking on THIS + * operand. The two un-armed calls above it in the same binary supply the + * (F,F) row (a prime, so the loop runs every round) and the (F,T) row (a + * composite, rejected on *result). */ + if (wc_InitRng(&rng) == 0) { + mcdc_sr_arm(WB_PRIME_RNG_SEED); + + wb_set_d(&a, (sp_int_digit)2147483647UL); + (void)_sp_prime_random_trials(&a, 8, &res, &rng); + wb_set_d(&a, (sp_int_digit)100160063ULL); + (void)_sp_prime_random_trials(&a, 8, &res, &rng); + + for (n = 1; n <= 24; n++) { + /* Rewind BEFORE arming: re-absorbing the seed allocates. */ + mcdc_sr_rewind(WB_PRIME_RNG_SEED); + wb_set_d(&a, (sp_int_digit)2147483647UL); + mcdc_fa_arm_only(n); + (void)_sp_prime_random_trials(&a, 8, &res, &rng); + mcdc_fa_disarm(); + } + + mcdc_sr_disarm(); + wc_FreeRng(&rng); + } #endif mcdc_fa_disarm(); From a0daf9a34a948ad0a797cfa28847149fec45e803 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 10:17:41 +0200 Subject: [PATCH 21/57] tests: record the xmss white-box exclusions and re-anchor its line references --- tests/unit-mcdc/test_wc_xmss_impl_whitebox.c | 38 +++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/tests/unit-mcdc/test_wc_xmss_impl_whitebox.c b/tests/unit-mcdc/test_wc_xmss_impl_whitebox.c index 93660d286e..aa8fed1214 100644 --- a/tests/unit-mcdc/test_wc_xmss_impl_whitebox.c +++ b/tests/unit-mcdc/test_wc_xmss_impl_whitebox.c @@ -35,6 +35,24 @@ * before use and freed after; the in-memory secret-key scratch buffer is * sized for the tall parameter set and roundtrips that would exceed it are * skipped cleanly. + * + * What this file deliberately does NOT cover - both are in the campaign's + * exclusion ledger (campaign/db/exclusions.json, + * EXCLUSIONS.md#condition-level-exclusions): + * + * 2465:2 and 4131:2 - the "c <= 4" operand of WC_IDX_INVALID's mixed + * 32/64-bit arm, i.e. "((c > 4) && IDX64_INVALID(..)) || ((c <= 4) && + * IDX32_INVALID(..))" with c = params->idx_len. It is the exact logical + * negation of the "c > 4" operand, which is evaluated on every arrival that + * gets past "ret == 0", so every vector that flips it flips "c > 4" too and + * no independence pair exists. It is not dead: it IS evaluated, and false, + * whenever c > 4 is true and IDX64_INVALID is false (a live height-40 key). + * The other four operands of both decisions ARE driven here - "ret == 0" + * false from a forged idx_len of 2, "c > 4"/IDX64_INVALID from live and + * retired XMSSMT-SHA2_40/8_256 keys, IDX32_INVALID from live and retired + * XMSSMT-SHA2_20/2_256 keys. Beware the index: these five conditions all + * share one macro-expansion location, and llvm-cov's export order is NOT + * source order there - index 2 is "c <= 4" and index 3 is IDX64_INVALID. */ #include @@ -1170,11 +1188,12 @@ static void wb_full_cycle_d1(void) /* wc_xmss_sigsleft(): line 4121's WC_IDX_INVALID true side, ret == 0 * true. Craft an sk whose encoded idx is exactly 2^h - 1 == 15 (the * smallest value for which (idx+1)>>h != 0) directly, rather than - * reusing the just-exhausted sk above: wc_xmssmt_sign()'s exhaustion - * handling XMEMSETs the index field to all-0xFF, which as an encoded - * 32-bit value (0xFFFFFFFF) wraps back to looking "valid" under - * IDX32_INVALID's "(idx+1)>>h" arithmetic (idx+1 overflows to 0) - a - * real quirk of that cleanup path, but not what this test is after. */ + * reusing the just-exhausted sk above, whose index field wc_xmssmt_sign() + * has XMEMSET to all-0xFF: 2^h - 1 is the smallest value the check must + * reject and is the one this test is after. (The all-0xFF marker used to + * read back as "valid" because IDX32_INVALID's "(idx+1)>>h" overflowed to + * 0 - a real defect, fixed in "wolfcrypt: xmss exhausted-key index marker + * wrapped and re-enabled signing" and recorded in DEATHNOTE.md.) */ if (exhausted) { byte idxSk[2048]; @@ -1232,7 +1251,8 @@ static void wb_full_cycle_d1(void) #endif /* !WOLFSSL_XMSS_VERIFY_ONLY && !WOLFSSL_WC_XMSS_SMALL */ /******************************************** - * 3981-3983: wc_xmssmt_sign_next_idx()'s + * 3995-3997 (3981-3983 before the exhausted-marker fix moved the file): + * wc_xmssmt_sign_next_idx()'s * "if ((ret == 0) && (i > 0) && (updates > 0) && * (idx_tree < ((XmssIdx)1 << (h - (hs * (i + 1))))) && * (bds[alt_i].next < ((XmssIdx)1 << h)))" @@ -1246,7 +1266,7 @@ static void wb_full_cycle_d1(void) * FULL tree height. On every reachable signing vector next <= 2^sub_h <= * 2^h, so the operand is true. It is only false when the value loaded out * of the persisted secret key (a 24-bit big-endian field, wc_xmss_bds_ - * state_load() at 2729) is already >= 2^h - i.e. a corrupted/forged + * state_load() at 2743) is already >= 2^h - i.e. a corrupted/forged * private key, which is exactly the case this defensive guard exists for. * Here that state is forged directly: the BDS array is loaded from a COPY * of a good secret key and every state's "next" is set to 1 << h before @@ -1378,7 +1398,7 @@ static void wb_sign_next_idx_rows(void) * WC_XMSS_SHA256 */ /******************************************** - * 2455 (WOLFSSL_WC_XMSS_SMALL's wc_xmssmt_sign() only): + * 2465 (WOLFSSL_WC_XMSS_SMALL's wc_xmssmt_sign() only): * "if ((ret == 0) && (WC_IDX_INVALID(idx, params->idx_len, params->h)))" * condIndex 0's false side. The only assignment to ret before the guard is * WC_IDX_DECODE's trailing "else { ret = NOT_COMPILED_IN; }", which fires @@ -1396,7 +1416,7 @@ static void wb_sign_next_idx_rows(void) * params.sk_len so even the unused interior pointers stay in-object. * * Only built for WOLFSSL_WC_XMSS_SMALL: the non-small wc_xmssmt_sign() - * (4041) allocates and loads the BDS state from sk *before* its own index + * (4055) allocates and loads the BDS state from sk *before* its own index * check and decodes with xmss_idx_decode(), which has no NOT_COMPILED_IN * arm - it would neither reach this decision nor be memory-safe with a * forged parameter set. From 93dacdac31f05ed942f32b8bb110f4fdbed759f7 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 10:20:40 +0200 Subject: [PATCH 22/57] tests: record the falcon depth-1 Babai clamp re-analysis in the white-box --- tests/unit-mcdc/test_falcon_whitebox.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/unit-mcdc/test_falcon_whitebox.c b/tests/unit-mcdc/test_falcon_whitebox.c index 318623a879..5f0c04c504 100644 --- a/tests/unit-mcdc/test_falcon_whitebox.c +++ b/tests/unit-mcdc/test_falcon_whitebox.c @@ -1326,10 +1326,25 @@ static void wb_sign_dyn_core_err(WC_RNG* rng) * ------------------------------------------------------------------ */ static void wb_residuals(void) { + /* Re-examined in step 7 (2026-08-20); still open, and the reasoning is in + * EXCLUSIONS.md so it is not redone. Two dead ends were ruled out for + * good: an all-zero (f, g) makes the denominator exactly 0, and 0/0 gives + * NaN, whose comparison makes the FIRST operand true -- a row that is + * already covered; and the wb_solve_ntru_babai_clamp trick above (256 + * keygens at logn = 3) does not transfer, because that clamp is at + * +-(2^31-1) on a RESCALED coefficient while this one is at +-(2^63-1) on + * an unscaled ratio, which at logn = 3 would need ||(f,g)(zeta)||^2 near + * 1e-15 -- excluded by the field-norm floor (the norm is a non-zero + * integer, so one tiny slot forces the others large). The norm floor is + * also why this is NOT an exclusion: at the production degrees it only + * bounds the slot below by about 2^-10000, i.e. it bounds |z| by nothing + * useful. */ WB_NOTE("residual: solve_NTRU_binary_depth1 !fpr_lt(z,+-2^63) halves: the " "Babai coefficient is bounded by sqrt(|F|^2+|G|^2)/sqrt(|f|^2+" "|g|^2) with |F|,|G| < 2^61 (2-word CRT limbs), so |z| >= 2^63 " - "needs both depth-1 field norms to nearly vanish at one FFT slot"); + "needs both depth-1 field norms to nearly vanish at one FFT slot; " + "x^n+1 is irreducible over Q so they never vanish exactly, but no " + "usable lower bound exists either -- see EXCLUSIONS.md"); WB_NOTE("residual: keygen f[u]/g[u] vs lim halves: lim is 1 << " "(falcon_max_fg_bits[logn] - 1), i.e. 32 at logn 9 and 16 at " "logn 10, while poly_small_mkgauss sums 1 << (10 - logn) draws of " From c29d3af380762b0b0784202b5a1cc24fc8dab25b Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 10:23:41 +0200 Subject: [PATCH 23/57] tests: drive wc_MlDsaKey_CheckKey s1/s2 range rows with a mutated private key --- tests/unit-mcdc/test_wc_mldsa_whitebox.c | 112 +++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/tests/unit-mcdc/test_wc_mldsa_whitebox.c b/tests/unit-mcdc/test_wc_mldsa_whitebox.c index 54e8f853d3..e5860b368a 100644 --- a/tests/unit-mcdc/test_wc_mldsa_whitebox.c +++ b/tests/unit-mcdc/test_wc_mldsa_whitebox.c @@ -1301,8 +1301,119 @@ static void wb_verify_invalid(void) } #endif +/* ------------------------------------------------------------------------- * + * wc_MlDsaKey_CheckKey()'s s1/s2 coefficient range check (wc_mldsa.c:12517, + * :12522, :12523). + * + * for (c = 0; c < (word32)(params->l * MLDSA_N); c++) { + * if (s1[c] < -eta || s1[c] > eta) { ret = PUBLIC_KEY_E; break; } + * } + * for (c = 0; (ret == 0) && (c < (word32)(params->k * MLDSA_N)); c++) { + * if (s2[c] < -eta || s2[c] > eta) { ret = PUBLIC_KEY_E; break; } + * } + * + * Every key the API can hand this function was either generated (s1/s2 are in + * range by construction) or decoded through mldsa_check_eta_range(), which + * rejects an out-of-range nibble/3-bit group before the key is marked set. So + * from tests/api the two `< -eta` operands only ever take their FALSE side and + * the s2 loop header's `ret == 0` operand only ever takes its TRUE side. + * + * The vector is a MUTATED private key blob: a good key is generated, then the + * first byte of the packed s1 (or s2) region of key->k is forced to 0xFF. The + * eta unpackers read `eta - t` from an unsigned bit field -- t is a 3-bit + * group for eta 2 and a nibble for eta 4 (mldsa_decode_eta_2_bits_c / + * mldsa_decode_eta_4_bits_c) -- so 0xFF decodes the first coefficient as + * 2 - 7 = -5 or 4 - 15 = -11, out of range on the LOW side for either + * parameter set. The un-mutated CheckKey call in the same binary supplies the + * all-false row of both `||` decisions and the `ret == 0` TRUE row; the s1 + * mutation supplies the s2 header's `ret == 0` FALSE row (the s1 loop has + * already set PUBLIC_KEY_E when that header is next evaluated). + * + * The HIGH side (`s1[c] > eta`) is NOT driven here and cannot be: the unpack + * is `eta - t` with t unsigned, so the decoded coefficient never exceeds eta. + * Both `> eta` operands are recorded in campaign/db/exclusions.json. + * ------------------------------------------------------------------------- */ +#if defined(WOLFSSL_HAVE_MLDSA) && defined(WOLFSSL_MLDSA_CHECK_KEY) && \ + !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) && \ + !defined(WOLFSSL_MLDSA_ASSIGN_KEY) && defined(WOLFSSL_MLDSA_PRIVATE_KEY) +static void wb_check_key_range(void) +{ + wc_MlDsaKey key; + byte seed[MLDSA_SEED_SZ]; + byte* kp; + byte* s1p; + byte* s2p; + byte savedS1; + byte savedS2; + int ret; +#ifndef WOLFSSL_NO_ML_DSA_44 + const int level = WC_ML_DSA_44; /* smallest set: fastest under cov */ +#elif !defined(WOLFSSL_NO_ML_DSA_65) + const int level = WC_ML_DSA_65; +#else + const int level = WC_ML_DSA_87; +#endif + + XMEMSET(seed, 0x27, sizeof(seed)); + + if (wc_MlDsaKey_Init(&key, NULL, INVALID_DEVID) != 0) { + WB_NOTE("CheckKey range rows skipped (init failed)"); + return; + } + if ((wc_MlDsaKey_SetParams(&key, level) != 0) || + (wc_MlDsaKey_MakeKeyFromSeed(&key, seed) != 0)) { + wc_MlDsaKey_Free(&key); + WB_NOTE("CheckKey range rows skipped (keygen unavailable)"); + return; + } + + /* All-false row of both range decisions, and the s2 header's ret == 0 + * true row: a well-formed key. */ + ret = wc_MlDsaKey_CheckKey(&key); + if (ret != 0) { + WB_NOTE("wc_MlDsaKey_CheckKey rejected a freshly generated key"); + } + + kp = (byte*)key.k; + s1p = kp + MLDSA_PUB_SEED_SZ + MLDSA_K_SZ + MLDSA_TR_SZ; + s2p = s1p + key.params->s1EncSz; + savedS1 = s1p[0]; + savedS2 = s2p[0]; + + /* s1[0] out of range on the low side: :12517 idx0 true, and the s2 loop + * header (:12522 idx0) is then evaluated with ret != 0. */ + s1p[0] = 0xFF; + ret = wc_MlDsaKey_CheckKey(&key); + if (ret == 0) { + WB_NOTE("CheckKey accepted an out-of-range s1 coefficient"); + } + s1p[0] = savedS1; + + /* s2[0] out of range: the s1 loop runs clean, the s2 header is true, and + * :12523 idx0 takes its true side. */ + s2p[0] = 0xFF; + ret = wc_MlDsaKey_CheckKey(&key); + if (ret == 0) { + WB_NOTE("CheckKey accepted an out-of-range s2 coefficient"); + } + s2p[0] = savedS2; + + wc_MlDsaKey_Free(&key); + WB_NOTE("CheckKey s1/s2 range rows exercised (12517, 12522, 12523)"); +} +#else +static void wb_check_key_range(void) +{ + WB_NOTE("CheckKey range rows skipped (not compiled in this variant)"); +} +#endif + int main(void) { + /* Unbuffered: on a timeout the process is killed and anything still + * buffered is lost, which reads as an empty log. */ + setvbuf(stdout, NULL, _IONBF, 0); + printf("wc_mldsa.c white-box MC/DC supplement\n"); #if !defined(WOLFSSL_HAVE_MLDSA) printf(" ML-DSA not enabled; nothing to exercise\n"); @@ -1339,6 +1450,7 @@ int main(void) wb_gen_lane_rows(); wb_arg_guards(); wb_verify_invalid(); + wb_check_key_range(); printf("done (%d note%s)\n", wb_notes, (wb_notes == 1) ? "" : "s"); return 0; #endif From aa5f47283f81a010de82d53db6c8c158cf1f92d8 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 10:41:46 +0200 Subject: [PATCH 24/57] tests: close ten pkcs7.c MC/DC conditions and make the seeded RNG header order-independent --- tests/unit-mcdc/mcdc_seed_rng.h | 9 + tests/unit-mcdc/test_pkcs7_arg_whitebox.c | 106 ++++++ tests/unit-mcdc/test_pkcs7_decode_whitebox.c | 372 +++++++++++++++++++ 3 files changed, 487 insertions(+) diff --git a/tests/unit-mcdc/mcdc_seed_rng.h b/tests/unit-mcdc/mcdc_seed_rng.h index 5befe207db..f84cdf473f 100644 --- a/tests/unit-mcdc/mcdc_seed_rng.h +++ b/tests/unit-mcdc/mcdc_seed_rng.h @@ -79,6 +79,15 @@ * implementation half for why that matters. */ +/* The availability test below reads feature macros, so the configuration has + * to be in scope before it runs. A driver that includes this header as its + * FIRST wolfSSL include otherwise evaluates the test against an empty macro + * environment, silently selects the inert stubs, and pins nothing -- which + * builds, links and runs, so the campaign scores it as a pass. Pull the + * switchboard in here rather than relying on every caller's include order. + * It is idempotent, so callers that already included it are unaffected. */ +#include + /* SHAKE-256 is gated positively by WOLFSSL_SHAKE256; there is no NO_SHA3. */ #if !defined(WOLFSSL_SHAKE256) || defined(WOLFSSL_NO_SHAKE256) || \ defined(WC_NO_RNG) diff --git a/tests/unit-mcdc/test_pkcs7_arg_whitebox.c b/tests/unit-mcdc/test_pkcs7_arg_whitebox.c index fdece5e43a..a403139a62 100644 --- a/tests/unit-mcdc/test_pkcs7_arg_whitebox.c +++ b/tests/unit-mcdc/test_pkcs7_arg_whitebox.c @@ -33,6 +33,17 @@ * vector is paired with the all-false vector inside this same file. The * accepting vector only has to make the guard evaluate false; failing * deeper in is fine and expected. + * + * ARGUED UNREACHABLE, do not re-open (also in the campaign's EXCLUSIONS.md + * and db/exclusions.json): + * + * :4183 cond 1 (`pkcs7->sidType != DEGENERATE_SID`). PKCS7_EncodeSigned's + * only assignment of a non-zero flatSignedAttribsSz is at :3836, inside + * the `if (pkcs7->sidType != DEGENERATE_SID)` block that opens at :3730. + * The enclosing `if (flatSignedAttribsSz > 0)` at :4180 therefore already + * implies sidType != DEGENERATE_SID: with a degenerate SID the attribute + * block never runs, flatSignedAttribsSz stays 0, and :4183 is not + * reached at all. The operand is constant-true where it is evaluated. */ #include @@ -1867,6 +1878,100 @@ static void wb_size_guards(void) } } +/* ------------------------------------------------------------------------- * + * wc_PKCS7_EncodeContentStream(): the trailing-pad gate + * :3413 `(cipherType != WC_CIPHER_NONE) && (totalSz == pkcs7->contentSz)` + * + * Both operands need rows that no public encode call produces. The only + * cipherType != WC_CIPHER_NONE callers are inside wc_PKCS7_EncryptContent() + * and are reached only when pkcs7->encodeStream is set, and every such call + * hands the whole content over in one piece, so totalSz always ends up equal + * to pkcs7->contentSz. Calling the (file-static) encoder directly supplies + * all three rows in this binary: + * + * (F,-) cipherType WC_CIPHER_NONE, the signed-bundle shape + * (T,T) AES-CBC with contentSz == inSz -> the pad block runs + * (T,F) AES-CBC with contentSz > inSz -> the read loop runs out of + * input first (`contentDataRead <= 0` breaks the do/while), so the + * pad block is skipped and the partial block is flushed as-is + * ------------------------------------------------------------------------- */ +#if !defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128) && \ + defined(ASN_BER_TO_DER) +static void wb_encode_content_stream_pad(void) +{ + static byte in[32]; + static byte out[512]; + byte key[16], iv[16]; + Aes aes; + wc_PKCS7* p; + int ret; + + XMEMSET(in, 0x41, sizeof(in)); + XMEMSET(key, 0x42, sizeof(key)); + XMEMSET(iv, 0x43, sizeof(iv)); + + WB_NOTE("wc_PKCS7_EncodeContentStream(): WC_CIPHER_NONE, pad gate short-" + "circuits on the first operand [:3413 cond 0 false]"); + p = wc_PKCS7_New(NULL, INVALID_DEVID); + if (p != NULL) { + if (wc_PKCS7_InitWithCert(p, NULL, 0) == 0) { + p->encodeStream = 1; + p->contentSz = (word32)sizeof(in); + ret = wc_PKCS7_EncodeContentStream(p, NULL, NULL, in, + (int)sizeof(in), out, WC_CIPHER_NONE); + WB_CHECK(ret == 0, ":3413 WC_CIPHER_NONE stream copy"); + } + wc_PKCS7_Free(p); + } + + WB_NOTE("wc_PKCS7_EncodeContentStream(): AES-CBC with the whole content" + " consumed, so the pad block runs [:3413 both operands true]"); + p = wc_PKCS7_New(NULL, INVALID_DEVID); + if (p != NULL) { + if (wc_PKCS7_InitWithCert(p, NULL, 0) == 0 && wc_AesInit(&aes, NULL, + INVALID_DEVID) == 0) { + if (wc_AesSetKey(&aes, key, (word32)sizeof(key), iv, + AES_ENCRYPTION) == 0) { + p->encodeStream = 1; + p->encryptOID = AES128CBCb; + p->contentSz = (word32)sizeof(in); + ret = wc_PKCS7_EncodeContentStream(p, NULL, &aes, in, + (int)sizeof(in), out, WC_CIPHER_AES_CBC); + WB_CHECK(ret == 0, ":3413 AES-CBC padded flush"); + } + wc_AesFree(&aes); + } + wc_PKCS7_Free(p); + } + + WB_NOTE("wc_PKCS7_EncodeContentStream(): AES-CBC whose declared contentSz" + " is larger than the input, so the read loop stops short and the" + " pad block is skipped [:3413 cond 1 false]"); + p = wc_PKCS7_New(NULL, INVALID_DEVID); + if (p != NULL) { + if (wc_PKCS7_InitWithCert(p, NULL, 0) == 0 && wc_AesInit(&aes, NULL, + INVALID_DEVID) == 0) { + if (wc_AesSetKey(&aes, key, (word32)sizeof(key), iv, + AES_ENCRYPTION) == 0) { + p->encodeStream = 1; + p->encryptOID = AES128CBCb; + p->contentSz = (word32)sizeof(in) * 2; + ret = wc_PKCS7_EncodeContentStream(p, NULL, &aes, in, + (int)sizeof(in), out, WC_CIPHER_AES_CBC); + WB_CHECK(ret == 0, ":3413 AES-CBC short read, no pad block"); + } + wc_AesFree(&aes); + } + wc_PKCS7_Free(p); + } +} +#else +static void wb_encode_content_stream_pad(void) +{ + WB_NOTE("no AES-CBC/BER-to-DER; EncodeContentStream pad gate skipped"); +} +#endif + int main(void) { setvbuf(stdout, NULL, _IONBF, 0); @@ -1885,6 +1990,7 @@ int main(void) wb_small_matrices(); wb_auth_encode_shapes(); wb_size_guards(); + wb_encode_content_stream_pad(); printf("done (%s)\n", wb_fail ? "with failures" : "ok"); /* Always return 0: a nonzero exit discards this variant's coverage diff --git a/tests/unit-mcdc/test_pkcs7_decode_whitebox.c b/tests/unit-mcdc/test_pkcs7_decode_whitebox.c index 172e773507..1dcb8bacb4 100644 --- a/tests/unit-mcdc/test_pkcs7_decode_whitebox.c +++ b/tests/unit-mcdc/test_pkcs7_decode_whitebox.c @@ -1283,6 +1283,33 @@ static void wb_octet_accum(word32 seedAccum) wc_PKCS7_FreeStream(&pkcs7); } +/* The "another OCTET STRING follows" branch, :6689. Its trailing operand needs + * an 0x04 tag whose length field is itself malformed -- a shape no encoder + * emits, since every OCTET STRING wolfSSL writes carries a well-formed length. + * Seeded directly, like wb_octet_accum() above, with currContRmnSz == 0 so the + * branch is entered on the first pass. */ +static int wb_octet_next_len(byte* in, word32 inSz) +{ + wc_PKCS7 pkcs7; + word32 idx = 0, tmpIdx = 0; + int ret; + + XMEMSET(&pkcs7, 0, sizeof(pkcs7)); + + if (wc_PKCS7_CreateStream(&pkcs7) != 0) { + return BAD_FUNC_ARG; + } + pkcs7.stream->currContSz = 0; + pkcs7.stream->currContRmnSz = 0; + pkcs7.stream->expected = 1; + pkcs7.stream->noContent = 0; + pkcs7.stream->maxLen = inSz; + + ret = wc_PKCS7_HandleOctetStrings(&pkcs7, in, inSz, &tmpIdx, &idx, 1); + wc_PKCS7_FreeStream(&pkcs7); + return ret; +} + static void wb_octet_accum_chains(void) { WB_NOTE("wc_PKCS7_HandleOctetStrings(): existing content buffer with a" @@ -1291,6 +1318,25 @@ static void wb_octet_accum_chains(void) WB_NOTE("wc_PKCS7_HandleOctetStrings(): existing content buffer with a" " non-zero accumulated size [:6817 trailing operand true]"); wb_octet_accum(4); + + { + static byte okLen[] = { 0x04, 0x02, 0xAA, 0xBB }; + static byte badLen[] = { 0x04 }; + int ret; + + WB_NOTE("wc_PKCS7_HandleOctetStrings(): a following OCTET STRING whose" + " length parses [:6689 trailing operand false]"); + ret = wb_octet_next_len(okLen, (word32)sizeof(okLen)); + WB_CHECK(ret != WC_NO_ERR_TRACE(ASN_PARSE_E), + ":6689 well-formed following OCTET STRING length"); + + WB_NOTE("wc_PKCS7_HandleOctetStrings(): a following OCTET STRING tag" + " with no length byte behind it [:6689 trailing operand" + " true]"); + ret = wb_octet_next_len(badLen, (word32)sizeof(badLen)); + WB_CHECK(ret == WC_NO_ERR_TRACE(ASN_PARSE_E), + ":6689 malformed following OCTET STRING length"); + } } #else static void wb_octet_accum_chains(void) @@ -1426,6 +1472,327 @@ static void wb_verify_outer_shapes(void) wbVsdVer[19] = 0x03; } +/* ------------------------------------------------------------------------- * + * Section 16: identity- and version-dispatch operands that neither a sweep + * nor any public encoder can produce, because no encoder in the tree ever + * emits the shape the operand tests. + * + * :6398 cond 1/2 wc_PKCS7_ParseSignerInfo()'s noDegenerate guard. The + * inner OR is `inSz == 0 || degenerate == 1`; both operands + * need a call with noDegenerate set, which the public + * decode path only ever makes with the *same* (inSz, + * degenerate) pair for a given bundle. Called directly with + * the three combinations instead. + * :5166 cond 1 wc_PKCS7_RsaVerify()'s `keyOID != RSAk && keyOID != + * :5298 cond 1 RSAPSSk` defence-in-depth guard, and the same guard in + * wc_PKCS7_RsaPssVerify(). The false row needs an + * RSASSA-PSS SubjectPublicKeyInfo (keyOID == RSAPSSk) in + * pkcs7->cert[], which no bundle this module builds carries; + * the true row needs a non-RSA-family cert in the same + * binary. Both are supplied here from certs/. + * :14106 cond 2 wc_PKCS7_ParseToRecipientInfoSet()'s BER marker test + * `ret == 0 && length == 0 && pkiMsg[(*idx)-1] == 0x80`. + * A zero-length *definite* outer SEQUENCE (`30 00`) is the + * only input that reaches the third operand with a false + * value; the `30 80` companion in the same binary supplies + * the true row. + * :14215 cond 4 the ECDSA arm of the envelopedData version dispatch, + * `publicKeyOID == ECDSAk && (version != 0 && ...)`. The + * false row needs an ECC signer key with version 0, the + * true row the same key with a version that is none of + * 0/2/3 -- one field of one hand-built header apart. + * ------------------------------------------------------------------------- */ + +/* ContentInfo/EnvelopedData header, parsed as far as the RecipientInfo SET. + * Padded well past MAX_OID_SZ + MAX_LENGTH_SZ so that the streaming + * wc_PKCS7_AddDataToStream() never has to ask for more input. */ +static byte wbRisHdr[96]; +/* the same prefix with a zero-length definite outer SEQUENCE, and with the + * indefinite-length marker, so :14106's third operand sees both values */ +static byte wbRisEmptyDef[96]; +static byte wbRisEmptyIndef[96]; + +static word32 wb_build_ris_hdr(byte* buf, word32 bufSz, byte version) +{ + word32 i = 0; + + XMEMSET(buf, 0, bufSz); + buf[i++] = 0x30; buf[i++] = 0x16; /* ContentInfo SEQUENCE */ + buf[i++] = 0x06; buf[i++] = 0x09; /* envelopedData OID */ + buf[i++] = 0x2A; buf[i++] = 0x86; buf[i++] = 0x48; buf[i++] = 0x86; + buf[i++] = 0xF7; buf[i++] = 0x0D; buf[i++] = 0x01; buf[i++] = 0x07; + buf[i++] = 0x03; + buf[i++] = 0xA0; buf[i++] = 0x09; /* [0] EXPLICIT */ + buf[i++] = 0x30; buf[i++] = 0x07; /* EnvelopedData SEQUENCE */ + buf[i++] = 0x02; buf[i++] = 0x01; buf[i++] = version; + buf[i++] = 0x31; buf[i++] = 0x02; /* RecipientInfo SET */ + buf[i++] = 0x30; buf[i++] = 0x00; + return bufSz; +} + +static int wb_parse_ris(byte* buf, word32 bufSz, word32 pubKeyOID) +{ + wc_PKCS7* p = wc_PKCS7_New(NULL, INVALID_DEVID); + word32 idx = 0; + int ret; + + if (p == NULL) { + return MEMORY_E; + } + if (wc_PKCS7_InitWithCert(p, NULL, 0) != 0) { + wc_PKCS7_Free(p); + return BAD_FUNC_ARG; + } + p->publicKeyOID = pubKeyOID; + ret = wc_PKCS7_ParseToRecipientInfoSet(p, buf, bufSz, &idx, ENVELOPED_DATA); + wc_PKCS7_Free(p); + return ret; +} + +static void wb_recipient_info_set_shapes(void) +{ + int ret; + + WB_NOTE("wc_PKCS7_ParseToRecipientInfoSet(): zero-length outer SEQUENCE," + " definite vs indefinite, isolates the 0x80 marker operand" + " [:14106 cond 2]"); + XMEMSET(wbRisEmptyDef, 0, sizeof(wbRisEmptyDef)); + wbRisEmptyDef[0] = 0x30; + wbRisEmptyDef[1] = 0x00; + ret = wb_parse_ris(wbRisEmptyDef, (word32)sizeof(wbRisEmptyDef), RSAk); + WB_CHECK(ret != 0, ":14106 definite zero-length SEQUENCE (marker false)"); + + XMEMSET(wbRisEmptyIndef, 0, sizeof(wbRisEmptyIndef)); + wbRisEmptyIndef[0] = 0x30; + wbRisEmptyIndef[1] = 0x80; + ret = wb_parse_ris(wbRisEmptyIndef, (word32)sizeof(wbRisEmptyIndef), RSAk); + WB_CHECK(ret != 0, ":14106 indefinite-length SEQUENCE (marker true)"); + +#ifdef HAVE_ECC + WB_NOTE("wc_PKCS7_ParseToRecipientInfoSet(): ECDSA signer key with" + " envelopedData version 0 and version 1 [:14215 cond 4]"); + (void)wb_build_ris_hdr(wbRisHdr, (word32)sizeof(wbRisHdr), 0x00); + ret = wb_parse_ris(wbRisHdr, (word32)sizeof(wbRisHdr), ECDSAk); + /* the success return is the RecipientInfo SET length, not 0 */ + WB_CHECK(ret > 0, ":14215 ECDSAk with version 0 is accepted"); + + (void)wb_build_ris_hdr(wbRisHdr, (word32)sizeof(wbRisHdr), 0x01); + ret = wb_parse_ris(wbRisHdr, (word32)sizeof(wbRisHdr), ECDSAk); + WB_CHECK(ret == WC_NO_ERR_TRACE(ASN_VERSION_E), + ":14215 ECDSAk with version 1 is rejected"); +#endif + + /* RSA companion rows, so the RSAk arm's operands are decided by the same + * two headers rather than only by real bundles. */ + (void)wb_build_ris_hdr(wbRisHdr, (word32)sizeof(wbRisHdr), 0x00); + ret = wb_parse_ris(wbRisHdr, (word32)sizeof(wbRisHdr), RSAk); + WB_CHECK(ret > 0, ":14215 RSAk with version 0 is accepted"); + (void)wb_build_ris_hdr(wbRisHdr, (word32)sizeof(wbRisHdr), 0x01); + ret = wb_parse_ris(wbRisHdr, (word32)sizeof(wbRisHdr), RSAk); + WB_CHECK(ret == WC_NO_ERR_TRACE(ASN_VERSION_E), + ":14215 RSAk with version 1 is rejected"); +} + +/* wc_PKCS7_ParseSignerInfo() with pkcs7->noDegenerate set, across the three + * (inSz, degenerate) combinations the inner OR needs. */ +static int wb_parse_signer_info_nodeg(byte* in, word32 inSz, int degenerate) +{ + wc_PKCS7 pkcs7; + word32 idx = 0; + byte* signedAttrib = NULL; + int signedAttribSz = 0; + int ret; + + XMEMSET(&pkcs7, 0, sizeof(pkcs7)); + pkcs7.version = 1; + pkcs7.noDegenerate = 1; + ret = wc_PKCS7_ParseSignerInfo(&pkcs7, in, inSz, &idx, degenerate, + &signedAttrib, &signedAttribSz); + wc_PKCS7_SignerInfoFree(&pkcs7); + return ret; +} + +static void wb_parse_signer_info_nodegenerate(void) +{ + int ret; + + WB_NOTE("wc_PKCS7_ParseSignerInfo(): noDegenerate matrix, each operand of" + " `inSz == 0 || degenerate == 1` isolated [:6398]"); + + /* (T,T,-): inSz == 0 decides the OR */ + ret = wb_parse_signer_info_nodeg(wbSiNoSeq, 0, 0); + WB_CHECK(ret == WC_NO_ERR_TRACE(PKCS7_NO_SIGNER_E), + ":6398 inSz == 0 with noDegenerate set"); + + /* (T,F,T): degenerate decides the OR */ + ret = wb_parse_signer_info_nodeg(wbSiNoSeq, (word32)sizeof(wbSiNoSeq), 1); + WB_CHECK(ret == WC_NO_ERR_TRACE(PKCS7_NO_SIGNER_E), + ":6398 degenerate == 1 with noDegenerate set"); + + /* (T,F,F): the guard does not fire and the parse runs on */ + ret = wb_parse_signer_info_nodeg(wbSiNoSeq, (word32)sizeof(wbSiNoSeq), 0); + WB_CHECK(ret != WC_NO_ERR_TRACE(PKCS7_NO_SIGNER_E), + ":6398 neither OR operand true, parse proceeds"); +} + +/* ------------------------------------------------------------------------- * + * The RSA-family SPKI guards. Both verifiers walk pkcs7->cert[] themselves, + * so the vector is just "put this DER in cert[0] and call". + * ------------------------------------------------------------------------- */ +#ifndef NO_RSA +static byte wbSpkiCert[2048]; + +static void wb_rsa_spki_guards(void) +{ + byte sig[256]; + byte hash[32]; + word32 certSz; + wc_PKCS7* p; + + XMEMSET(sig, 0x5A, sizeof(sig)); + XMEMSET(hash, 0x5B, sizeof(hash)); + + /* (a) keyOID is neither RSAk nor RSAPSSk: an ECDSA certificate. */ + certSz = wb_load_file("./certs/client-ecc-cert.der", wbSpkiCert, + (word32)sizeof(wbSpkiCert)); + if (certSz > 0) { + WB_NOTE("wc_PKCS7_RsaVerify(): non-RSA-family SPKI rejected" + " [:5166 both operands true]"); + p = wc_PKCS7_New(NULL, INVALID_DEVID); + if (p != NULL) { + if (wc_PKCS7_InitWithCert(p, NULL, 0) == 0) { + p->cert[0] = wbSpkiCert; + p->certSz[0] = certSz; + p->hashOID = SHA256h; + WB_CHECK(wc_PKCS7_RsaVerify(p, sig, (int)sizeof(sig), hash, + (word32)sizeof(hash)) != 0, + ":5166 ECDSA cert is skipped"); +#ifdef WC_RSA_PSS + WB_CHECK(wc_PKCS7_RsaPssVerify(p, sig, (int)sizeof(sig), hash, + (word32)sizeof(hash)) != 0, + ":5298 ECDSA cert is skipped"); +#endif + } + wc_PKCS7_Free(p); + } + } + + /* (b) keyOID == RSAPSSk: the second operand alone decides the guard. */ + certSz = wb_load_file("./certs/rsapss/client-rsapss.der", wbSpkiCert, + (word32)sizeof(wbSpkiCert)); + if (certSz > 0) { + WB_NOTE("wc_PKCS7_RsaVerify(): RSASSA-PSS SPKI accepted by the guard" + " [:5166 second operand false]"); + p = wc_PKCS7_New(NULL, INVALID_DEVID); + if (p != NULL) { + if (wc_PKCS7_InitWithCert(p, NULL, 0) == 0) { + p->cert[0] = wbSpkiCert; + p->certSz[0] = certSz; + p->hashOID = SHA256h; + /* the signature is garbage, so the call still fails -- but it + * fails *after* the guard, which is the point. */ + WB_CHECK(wc_PKCS7_RsaVerify(p, sig, (int)sizeof(sig), hash, + (word32)sizeof(hash)) != 0, + ":5166 RSAPSS cert passes the guard, signature fails"); +#ifdef WC_RSA_PSS + WB_CHECK(wc_PKCS7_RsaPssVerify(p, sig, (int)sizeof(sig), hash, + (word32)sizeof(hash)) != 0, + ":5298 RSAPSS cert passes the guard, signature fails"); +#endif + } + wc_PKCS7_Free(p); + } + } +} +#else +static void wb_rsa_spki_guards(void) +{ + WB_NOTE("NO_RSA; RSA-family SPKI guards skipped"); +} +#endif /* !NO_RSA */ + +/* ------------------------------------------------------------------------- * + * Section 17: the KTRI key-encryption-algorithm dispatch, :12114 + * `encOID != RSAk && encOID != RSAESOAEPk` + * Every KTRI this tree can *emit* carries rsaEncryption, which short-circuits + * on the first operand. The two rows the second operand needs are one byte + * apart from that: the last arc of the 9-byte algorithm OID inside the + * KeyTransRecipientInfo is rewritten in place -- 0x07 for id-RSAES-OAEP (the + * guard's false row) and 0x0A for id-RSASSA-PSS, which is in neither arm (the + * true row). Both replacements are the same length as rsaEncryption, so no + * enclosing ASN.1 length changes. + * ------------------------------------------------------------------------- */ +#if !defined(NO_RSA) && defined(USE_CERT_BUFFERS_2048) +static const byte wbRsaEncOid[] = { + 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 +}; + +static void wb_ktri_alg_call(byte* buf, word32 len) +{ + wc_PKCS7* p = wc_PKCS7_New(NULL, INVALID_DEVID); + static byte out[WB_SCRATCH_SZ]; + + if (p == NULL) { + return; + } + if (wc_PKCS7_InitWithCert(p, (byte*)client_cert_der_2048, + sizeof_client_cert_der_2048) == 0 && + wc_PKCS7_SetKey(p, (byte*)client_key_der_2048, + sizeof_client_key_der_2048) == 0) { + (void)wc_PKCS7_DecodeEnvelopedData(p, buf, len, out, sizeof(out)); + } + wc_PKCS7_Free(p); +} + +static void wb_ktri_key_alg_dispatch(void) +{ + word32 fullLen, i; + int found = -1; + + fullLen = wb_load_file("./certs/test/ktri-keyid-cms.msg", wbScratch, + sizeof(wbScratch)); + if (fullLen < sizeof(wbRsaEncOid)) { + WB_NOTE("ktri-keyid-cms.msg unavailable; KTRI algorithm dispatch" + " skipped"); + return; + } + /* the LAST rsaEncryption OID in the message is the KTRI's + * keyEncryptionAlgorithm (the earlier one is the certificate's SPKI) */ + for (i = 0; i + (word32)sizeof(wbRsaEncOid) <= fullLen; i++) { + if (XMEMCMP(wbScratch + i, wbRsaEncOid, sizeof(wbRsaEncOid)) == 0) { + found = (int)i; + } + } + if (found < 0) { + WB_NOTE("no rsaEncryption OID found in ktri-keyid-cms.msg; KTRI" + " algorithm dispatch skipped"); + return; + } + + WB_NOTE("wc_PKCS7_DecryptKtri(): rsaEncryption (first operand false)" + " [:12114]"); + wb_ktri_alg_call(wbScratch, fullLen); + + WB_NOTE("wc_PKCS7_DecryptKtri(): id-RSAES-OAEP (second operand false)" + " [:12114 cond 1]"); + wbScratch[(word32)found + 10] = 0x07; + wb_ktri_alg_call(wbScratch, fullLen); + + WB_NOTE("wc_PKCS7_DecryptKtri(): an OID in neither arm (both operands" + " true) [:12114]"); + wbScratch[(word32)found + 10] = 0x0A; + wb_ktri_alg_call(wbScratch, fullLen); + + wbScratch[(word32)found + 10] = 0x01; +} +#else +static void wb_ktri_key_alg_dispatch(void) +{ + WB_NOTE("NO_RSA or no 2048-bit cert buffers; KTRI algorithm dispatch" + " skipped"); +} +#endif + /* ------------------------------------------------------------------------- * * main -- always returns 0 so the campaign harness keeps this variant's * coverage even if an individual sub-section's build config disables it. @@ -1434,6 +1801,7 @@ int main(void) { int rngRet; + setvbuf(stdout, NULL, _IONBF, 0); printf("=== pkcs7 decode-chain white-box (Part 5) ===\n"); rngRet = wc_InitRng(&wbRng); @@ -1454,6 +1822,10 @@ int main(void) wb_octet_accum_chains(); wb_ecdsa_verify_results(); wb_verify_outer_shapes(); + wb_recipient_info_set_shapes(); + wb_parse_signer_info_nodegenerate(); + wb_rsa_spki_guards(); + wb_ktri_key_alg_dispatch(); if (rngRet == 0) { wc_FreeRng(&wbRng); From a209619d35ed8e3de2bdd38b86b76ce2c855830f Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 14:26:22 +0200 Subject: [PATCH 25/57] tests: add test_puf_gf_whitebox.c to EXTRA_DIST --- tests/include.am | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/include.am b/tests/include.am index b388c20290..2ec72a840b 100644 --- a/tests/include.am +++ b/tests/include.am @@ -185,6 +185,7 @@ EXTRA_DIST += \ tests/unit-mcdc/test_pkcs7_mutate_whitebox.c \ tests/unit-mcdc/test_pkcs7_whitebox.c \ tests/unit-mcdc/test_poly1305_whitebox.c \ + tests/unit-mcdc/test_puf_gf_whitebox.c \ tests/unit-mcdc/test_puf_whitebox.c \ tests/unit-mcdc/test_pwdbased_whitebox.c \ tests/unit-mcdc/test_random_fault_whitebox.c \ From de0f578907d1237d2ff8b7c47099a1959377235c Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 14:26:22 +0200 Subject: [PATCH 26/57] tests: asn.c MC/DC vectors for the extension, key and revocation decoders --- tests/unit-mcdc/test_asn_certgen_whitebox.c | 23 +++ tests/unit-mcdc/test_asn_ext_whitebox.c | 195 +++++++++++++++++- tests/unit-mcdc/test_asn_keys_whitebox.c | 135 ++++++++++++ .../unit-mcdc/test_asn_revocation_whitebox.c | 73 +++++++ tests/unit-mcdc/test_asn_whitebox.c | 1 + 5 files changed, 423 insertions(+), 4 deletions(-) diff --git a/tests/unit-mcdc/test_asn_certgen_whitebox.c b/tests/unit-mcdc/test_asn_certgen_whitebox.c index b3e1804b52..0126a6afbc 100644 --- a/tests/unit-mcdc/test_asn_certgen_whitebox.c +++ b/tests/unit-mcdc/test_asn_certgen_whitebox.c @@ -592,6 +592,17 @@ static void wb_set_name_ex(void) #endif /* ======================================================================== + * ARGUED UNREACHABLE, do not re-open (campaign EXCLUSIONS.md + + * db/exclusions.json): wc_SetSubjectRaw() :32713 cond 0 and wc_SetIssuerRaw() + * :32750 cond 0 (`decodedCert->subjectRaw` non-NULL). GetCertName() assigns + * cert->subjectRaw = &input[srcIdx] (asn.c:15513) on every path where the + * subject Name SEQUENCE parses, and DecodeCertInternal() only calls it once + * the template walk has succeeded; if either fails, DecodeCert() returns + * negative, wc_SetCert_LoadDer() propagates that, and the enclosing + * `if (ret >= 0)` is never entered. Cond 1 (subjectRawLen <= sizeof(CertName)) + * is NOT excluded -- a subject longer than sizeof(CertName) is constructible + * in principle, just not from any corpus certificate. + * * SECTION I: EncodeExtensions() direct call. * :~28792 if (cert->pathLenSet && ((keyUsage & KEYUSE_KEY_CERT_SIGN) || (!keyUsage))) * :~29126 else if ((output!=NULL) && (sz>maxSz)) @@ -629,6 +640,18 @@ static void wb_encode_extensions(void) WB_CHECK(sz > 0, ":28792 pathLenSet true, (keyUsage&CERT_SIGN)||!keyUsage both false"); + /* :28792 third operand (!keyUsage) true: pathLenSet with NO keyUsage at + * all, which is the case the `|| (!keyUsage)` arm exists for. Row (B) + * above is its false partner (keyUsage non-zero without KEY_CERT_SIGN), + * and both live in this binary. */ + WB_CHECK(wc_InitCert(&cert) == 0, "wc_InitCert (B2)"); + cert.isCA = 1; + cert.pathLenSet = 1; + cert.pathLen = 2; + cert.keyUsage = 0; + sz = EncodeExtensions(&cert, NULL, 0, 0); + WB_CHECK(sz > 0, ":28792 pathLenSet true, keyUsage == 0 (third operand)"); + /* :28792 pathLenSet false -> whole AND short-circuits false. */ WB_CHECK(wc_InitCert(&cert) == 0, "wc_InitCert (C)"); cert.isCA = 1; diff --git a/tests/unit-mcdc/test_asn_ext_whitebox.c b/tests/unit-mcdc/test_asn_ext_whitebox.c index 03a451c0fc..356f89e45a 100644 --- a/tests/unit-mcdc/test_asn_ext_whitebox.c +++ b/tests/unit-mcdc/test_asn_ext_whitebox.c @@ -88,10 +88,16 @@ * with certs/rsapss/server-rsapss.der but the mismatched-parameters * (false) arm would need byte-level PSS parameter surgery not attempted * here. - * - ParseCert() :23263-:23267 operands 2/3 (publicKey != NULL, - * pubKeySize > 0): once keyOID == RSAk and ParseCertRelative succeeds, - * GetCertKey always sets publicKey/pubKeySize together; only the - * all-true combination is reachable without editing library source. + * - ParseCert() :23442 operands 2/3 (publicKey != NULL, pubKeySize > 0): + * once keyOID == RSAk and ParseCertRelative succeeds, GetCertKey always + * sets publicKey/pubKeySize together; only the all-true combination is + * reachable without editing library source. Now FILED as an exclusion + * (campaign EXCLUSIONS.md + db/exclusions.json), together with the same + * arrival argument for ParseCertRelative()'s :24656 cond 1 and :24790 + * cond 0. The matching pubKeySize operands at :24656 cond 2 and :24790 + * cond 2 are deliberately NOT excluded: outside the RSA-family branch + * nothing validates the key body, so a zero-length SubjectPublicKeyInfo + * BIT STRING is arguably reachable there. * - DecodeCertInternal() :22815/:22821 2nd operand (issuer/subject != * NULL): both are initialised to NULL and assigned unconditionally * inside the single `if (ret == 0)` block at asn.c:22637 (:22683, @@ -1979,6 +1985,78 @@ static void wb_decode_cert_internal(void) FreeDecodedCert(&cert); } + /* --- trailing-data guard [:22749] --------------------------------- * + * `(ret == 0) && (!stopAtPubKey) && (!stopAfterPubKey) && + * (!cert->allowTrailing) && (cert->srcIdx != cert->maxIdx)` + * The decision's TRUE outcome needs a buffer that is longer than the + * certificate inside it, which no corpus file and no API caller + * supplies -- every loader hands DecodeCert() a buffer sized to the + * certificate. Calling the static decoder directly with one extra byte + * produces it, and the four other rows (one per operand) are driven + * against that same buffer so each operand alone decides the outcome. */ +#ifndef WOLFSSL_NO_ASN_STRICT + { + DecodedCert cert; + static byte trail[4096]; + int ret, crit; + word32 trailSz; + + if (origSz + 1 <= sizeof(trail)) { + XMEMCPY(trail, orig, origSz); + trail[origSz] = 0x00; + trailSz = (word32)origSz + 1; + + WB_NOTE("DecodeCertInternal(): one byte of trailing data" + " [:22749 all five operands true]"); + wc_InitDecodedCert(&cert, trail, trailSz, NULL); + ret = DecodeCertInternal(&cert, NO_VERIFY, &crit, NULL, 0, 0); + WB_CHECK(ret == WC_NO_ERR_TRACE(ASN_PARSE_E), + ":22749 trailing data rejected"); + FreeDecodedCert(&cert); + + WB_NOTE("DecodeCertInternal(): same buffer, stopAtPubKey" + " [:22749 second operand false]"); + wc_InitDecodedCert(&cert, trail, trailSz, NULL); + ret = DecodeCertInternal(&cert, NO_VERIFY, &crit, NULL, 1, 0); + /* the stopAtPubKey path returns the SubjectPublicKeyInfo offset, + * not 0 (asn.c:22964) */ + WB_CHECK(ret > 0, ":22749 stopAtPubKey ignores the trailer"); + FreeDecodedCert(&cert); + + WB_NOTE("DecodeCertInternal(): same buffer, stopAfterPubKey" + " [:22749 third operand false]"); + wc_InitDecodedCert(&cert, trail, trailSz, NULL); + ret = DecodeCertInternal(&cert, NO_VERIFY, &crit, NULL, 0, 1); + WB_CHECK(ret == 0, ":22749 stopAfterPubKey ignores the trailer"); + FreeDecodedCert(&cert); + + WB_NOTE("DecodeCertInternal(): same buffer with allowTrailing set," + " the TRUSTED CERTIFICATE shape [:22749 fourth operand" + " false]"); + wc_InitDecodedCert(&cert, trail, trailSz, NULL); + cert.allowTrailing = 1; + ret = DecodeCertInternal(&cert, NO_VERIFY, &crit, NULL, 0, 0); + WB_CHECK(ret == 0, ":22749 allowTrailing accepts the trailer"); + FreeDecodedCert(&cert); + + WB_NOTE("DecodeCertInternal(): buffer sized exactly to the" + " certificate [:22749 fifth operand false]"); + wc_InitDecodedCert(&cert, trail, (word32)origSz, NULL); + ret = DecodeCertInternal(&cert, NO_VERIFY, &crit, NULL, 0, 0); + WB_CHECK(ret == 0, ":22749 no trailing data"); + FreeDecodedCert(&cert); + + WB_NOTE("DecodeCertInternal(): truncated certificate, so the" + " guard is reached with ret already non-zero [:22749" + " leading operand false]"); + wc_InitDecodedCert(&cert, trail, (word32)(origSz / 2), NULL); + ret = DecodeCertInternal(&cert, NO_VERIFY, &crit, NULL, 0, 0); + WB_CHECK(ret != 0, ":22749 template walk failed first"); + FreeDecodedCert(&cert); + } + } +#endif /* !WOLFSSL_NO_ASN_STRICT */ + /* --- BEFORE/AFTER date propagation [:22608,:22609,:22620,:22621, * :22812] ------------------------------------------------------- */ { @@ -2456,6 +2534,70 @@ static void wb_decode_cert_req_version(void) WB_NOTE("version==0: version-check line executed with 2nd operand false " "(function may still fail later on the placeholder key material)"); (void)ret; + + /* The hand-built request above is minimal enough that the template walk + * can stop before the version guard, which leaves the guard's `ret == 0 + * && version <= MAX_X509_VERSION` row unproven. A real request settles + * it: certs/csr.signed.der parses cleanly, and the same bytes with the + * version INTEGER rewritten to 9 give the true row in this binary. */ + { + static byte realCsr[4096]; + static byte realCsrBad[4096]; + size_t realSz = 0; + word32 verIdx = 0, i; + + if (wb_load_file("./certs/csr.signed.der", realCsr, sizeof(realCsr), + &realSz) == 0 && realSz > 16) { + /* version INTEGER is the first element of the + * CertificationRequestInfo SEQUENCE, i.e. inside the first + * dozen bytes. */ + for (i = 0; i + 2 < 12; i++) { + if (realCsr[i] == 0x02 && realCsr[i + 1] == 0x01) { + verIdx = i + 2; + break; + } + } + WB_CHECK(verIdx != 0, "sanity: version INTEGER found in csr.signed.der"); + } + if (verIdx != 0) { + WB_NOTE("DecodeCertReq(): real request, version within range" + " [:23365 second operand false with ret == 0]"); + XMEMSET(&cert, 0, sizeof(cert)); + cert.source = realCsr; + cert.maxIdx = (word32)realSz; + ret = DecodeCertReq(&cert, &crit); + WB_CHECK(ret == 0, "csr.signed.der decodes"); + FreeDecodedCert(&cert); + + WB_NOTE("DecodeCertReq(): the same request with version 9" + " [:23365 both operands true]"); + XMEMCPY(realCsrBad, realCsr, realSz); + realCsrBad[verIdx] = 0x09; + XMEMSET(&cert, 0, sizeof(cert)); + cert.source = realCsrBad; + cert.maxIdx = (word32)realSz; + ret = DecodeCertReq(&cert, &crit); + WB_CHECK(ret == WC_NO_ERR_TRACE(ASN_PARSE_E), + "version 9 rejected"); + FreeDecodedCert(&cert); + + /* :23365's leading operand needs a request whose template walk + * fails outright, so the version guard is reached with ret + * already non-zero. Half the request is enough. */ + WB_NOTE("DecodeCertReq(): truncated request, template walk fails" + " [:23365 leading operand false]"); + XMEMSET(&cert, 0, sizeof(cert)); + cert.source = realCsr; + cert.maxIdx = (word32)(realSz / 2); + ret = DecodeCertReq(&cert, &crit); + WB_CHECK(ret != 0, "truncated request rejected"); + FreeDecodedCert(&cert); + } + else { + WB_NOTE("certs/csr.signed.der unavailable; real-request version" + " rows skipped"); + } + } } #else static void wb_decode_cert_req_version(void) { WB_NOTE("WOLFSSL_CERT_REQ off; skipped"); } @@ -2682,6 +2824,50 @@ static void wb_ext_error_propagation(void) #endif /* WOLFSSL_SUBJ_DIR_ATTR */ } +/* ------------------------------------------------------------------------- * + * SetDNSEntry(): the name-copy guard [:15067] + * `if (str != NULL && strLen > 0) XMEMCPY(dnsEntry_name, str, strLen);` + * + * Every production caller derives (str, strLen) from one parsed GeneralName, + * so the two are always consistent: a non-NULL pointer with a positive + * length. The guard exists for the degenerate combinations, and only a direct + * call can produce them. All three rows live in this binary: + * (F,-) str == NULL, strLen == 0 -> a zero-length entry is still created + * (T,T) a real string -> the copy runs + * (T,F) a real pointer, strLen 0 -> the copy is skipped + * ------------------------------------------------------------------------- */ +#ifndef WC_ASN_NO_HEAP +static void wb_set_dns_entry(void) +{ + DNS_entry* list = NULL; + static const char name[] = "example.com"; + int ret; + + WB_NOTE("SetDNSEntry(): str==NULL with strLen==0 [:15067 first operand" + " false]"); + ret = SetDNSEntry(NULL, NULL, NULL, NULL, 0, ASN_DNS_TYPE, &list); + WB_CHECK(ret == 0, ":15067 NULL/0 entry created"); + + WB_NOTE("SetDNSEntry(): real string, positive length [:15067 both" + " operands true]"); + ret = SetDNSEntry(NULL, NULL, NULL, name, (int)(sizeof(name) - 1), + ASN_DNS_TYPE, &list); + WB_CHECK(ret == 0, ":15067 name copied"); + + WB_NOTE("SetDNSEntry(): real string pointer with strLen==0 [:15067" + " second operand false]"); + ret = SetDNSEntry(NULL, NULL, NULL, name, 0, ASN_DNS_TYPE, &list); + WB_CHECK(ret == 0, ":15067 zero-length entry, copy skipped"); + + FreeAltNames(list, NULL); +} +#else +static void wb_set_dns_entry(void) +{ + WB_NOTE("WC_ASN_NO_HEAP build; SetDNSEntry name-copy guard skipped"); +} +#endif + int main(void) { setvbuf(stdout, NULL, _IONBF, 0); @@ -2717,6 +2903,7 @@ int main(void) wb_parse_cert_rsa_pubkey(); wb_get_decoded_cert_accessors(); wb_ext_error_propagation(); + wb_set_dns_entry(); printf("done (%s)\n", wb_fail ? "with failures" : "ok"); /* Always return 0: a nonzero exit discards this variant's coverage diff --git a/tests/unit-mcdc/test_asn_keys_whitebox.c b/tests/unit-mcdc/test_asn_keys_whitebox.c index 40f53b57a4..7c60f54026 100644 --- a/tests/unit-mcdc/test_asn_keys_whitebox.c +++ b/tests/unit-mcdc/test_asn_keys_whitebox.c @@ -728,6 +728,13 @@ static void wb_get_key_oid(void) { WB_NOTE("HAVE_PKCS8/12 off; wc_GetKeyOID skip /* ======================================================================== * Section A10: wc_EncryptPKCS8Key_ex() argument/salt/version checks. + * + * ARGUED UNREACHABLE, do not re-open (campaign EXCLUSIONS.md + + * db/exclusions.json): :10805 BOTH operands. GetAlgoV2() (asn.c:10713-10748) + * assigns *oid on every switch arm that returns 0 and leaves the caller's + * initialiser untouched only on the default arm, which returns ALGO_ID_E. + * encOid == NULL at :10805 therefore implies ret != 0, so the AND is never + * true: cond 1 has no true row and cond 0 has no (true, true) row. * :10724 key==NULL||outSz==NULL||password==NULL * :10731 ret==0 && (salt==NULL||saltSz==0) * :10735 ret==0 && version==PKCS5v2 @@ -737,6 +744,7 @@ static void wb_encrypt_pkcs8_key_ex(void) { byte key[16]; byte salt[8]; + static byte encOut[512]; word32 outSz; int ret; @@ -800,7 +808,50 @@ static void wb_encrypt_pkcs8_key_ex(void) PBES2, AES128CBCb, salt, sizeof(salt), 1000, 0, NULL, NULL); WB_CHECK(ret != 0, ":11531 both operands true (PBES2 dispatch)"); #endif + + /* :10799 third operand (`saltSz == 0`). Every public caller passes a + * salt pointer together with its real length, or neither; a non-NULL + * pointer with a zero length is the combination the OR's second operand + * exists for. The salt-provided call above is the row it pairs against. */ + WB_NOTE("wc_EncryptPKCS8Key_ex(): salt pointer with saltSz==0 [:10799" + " third operand]"); + outSz = 0; + ret = wc_EncryptPKCS8Key_ex(key, sizeof(key), NULL, &outSz, "pw", 2, + PKCS5, PBES1_SHA1_DES, 0, salt, 0, 1000, 0, NULL, NULL); + WB_CHECK(ret == WC_NO_ERR_TRACE(LENGTH_ONLY_E), + ":10799 salt != NULL with saltSz == 0 still generates a salt"); + + /* :11650 second operand's false row. The size-only calls above all set + * ret to LENGTH_ONLY_E at the preceding `out == NULL` branch, so they + * never reach this check with ret == 0; only a real encode with a large + * enough buffer does. PBES1 needs no RNG (the CBC IV is derived from the + * password), so this runs with rng == NULL. */ + WB_NOTE("EncryptContent(): full encode into a big-enough buffer [:11650" + " second operand false]"); + outSz = 0; + (void)EncryptContent(key, sizeof(key), NULL, &outSz, "pw", 2, PKCS5, + PBES1_SHA1_DES, 0, salt, sizeof(salt), 1000, 0, NULL, NULL); + if (outSz > 1 && outSz <= sizeof(encOut)) { + word32 room = outSz - 1; + + /* Both halves of :11650's second operand have to be in THIS binary: + * the too-small row lives in test_asn_fault_whitebox.c as well, but + * a pair completed across two binaries proves nothing. */ + ret = EncryptContent(key, sizeof(key), encOut, &room, "pw", 2, PKCS5, + PBES1_SHA1_DES, 0, salt, sizeof(salt), 1000, 0, NULL, NULL); + WB_CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), + ":11650 second operand true (one byte short)"); + + room = (word32)sizeof(encOut); + ret = EncryptContent(key, sizeof(key), encOut, &room, "pw", 2, PKCS5, + PBES1_SHA1_DES, 0, salt, sizeof(salt), 1000, 0, NULL, NULL); + WB_CHECK(ret > 0, ":11650 PBES1 encode succeeds with room to spare"); + } + else { + WB_NOTE("PBES1 size query out of range; :11650 row skipped"); + } } + #else static void wb_encrypt_pkcs8_key_ex(void) { WB_NOTE("HAVE_PKCS8/PWDBASED off; wc_EncryptPKCS8Key_ex skipped"); } #endif @@ -969,6 +1020,35 @@ static void wb_encrypt_content_pbes2(void) WB_CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), "out!=NULL, *outSz too small (11378 false, 11383 true)"); } + + /* :11466's second operand also needs the row where the buffer IS big + * enough, i.e. a completed encode. That is the only call in this section + * that gets past the size check, and it is also the only one that needs + * a WC_RNG: the PBES2 path draws the CBC IV before the check. The random + * bytes are written straight into the output and steer no decision, so + * the coverage this row produces is reproducible. */ + { + static byte bigOut[1024]; + word32 bigOutSz; + WC_RNG rng; + + outSz = 0; + (void)EncryptContentPBES2(input, sizeof(input), NULL, &outSz, "pw", 2, + AES128CBCb, salt, sizeof(salt), 1000, 0, NULL, NULL); + if (outSz > 0 && outSz <= sizeof(bigOut) && wc_InitRng(&rng) == 0) { + WB_NOTE("EncryptContentPBES2(): buffer large enough, encode runs" + " [:11466 second operand false]"); + bigOutSz = (word32)sizeof(bigOut); + ret = EncryptContentPBES2(input, sizeof(input), bigOut, &bigOutSz, + "pw", 2, AES128CBCb, salt, sizeof(salt), 1000, 0, &rng, + NULL); + WB_CHECK(ret > 0, ":11466 PBES2 encode completes"); + wc_FreeRng(&rng); + } + else { + WB_NOTE("no RNG or size out of range; :11466 encode row skipped"); + } + } #endif } #else @@ -1961,6 +2041,61 @@ static void wb_build_ecc_key_der(void) ret = wc_BuildEccKeyDer(&key, out, NULL, 1, 1); WB_CHECK(ret > 0, ":33566 2nd operand false (outLen==NULL, size check skipped)"); + /* :33780's leading operand (`ret == 0`) can only go false when the + * private-value export at :33772 itself errors, which no key that got + * this far normally does. A key decoded from a public-key SPKI has a dp + * and a public point but no private scalar, so the export fails while + * every earlier step succeeds. The successful build above is the row it + * pairs against. */ + WB_NOTE("wc_BuildEccKeyDer(): public-only key, private export fails" + " before the public-point export [:33780 leading operand]"); + { + ecc_key pubOnly; + word32 pubIdx = 0; + + if (wc_ecc_init(&pubOnly) == 0) { + if (wc_EccPublicKeyDecode(ecc_key_pub_der_256, &pubIdx, &pubOnly, + (word32)sizeof_ecc_key_pub_der_256) == 0) { + outLen = sizeof(out); + ret = wc_BuildEccKeyDer(&pubOnly, out, &outLen, 1, 1); + WB_CHECK(ret != 0, + ":33780 private-value export fails on a public-only" + " key"); + } + else { + WB_NOTE("ecc_key_pub_der_256 decode failed; :33780 row" + " skipped"); + } + wc_ecc_free(&pubOnly); + } + } + + /* :33537's second operand (`dataASN[ECCKEYASN_IDX_PARAMS].tag != 0`). + * Every ECC private key in certs_test.h carries the [0] parameters, so + * the operand only ever reads true. Building one with curveIn == 0 emits + * exactly the same structure minus the parameters, and decoding it in + * this binary supplies the false row. */ + WB_NOTE("wc_EccPrivateKeyDecode(): key DER built without the [0] curve" + " parameters [:33537 second operand false]"); + outLen = sizeof(out); + ret = wc_BuildEccKeyDer(&key, out, &outLen, 1, 0); + if (ret > 0) { + ecc_key noParams; + word32 npIdx = 0; + word32 derSz = (word32)ret; + + if (wc_ecc_init(&noParams) == 0) { + /* The decode cannot succeed without a curve to attach the key + * to; reaching the guard with the PARAMS tag clear is the + * point. */ + (void)wc_EccPrivateKeyDecode(out, &npIdx, &noParams, derSz); + wc_ecc_free(&noParams); + } + } + else { + WB_NOTE("wc_BuildEccKeyDer(curveIn==0) failed; :33537 row skipped"); + } + wc_ecc_free(&key); } diff --git a/tests/unit-mcdc/test_asn_revocation_whitebox.c b/tests/unit-mcdc/test_asn_revocation_whitebox.c index 02c53d4d1e..2cc15acbc4 100644 --- a/tests/unit-mcdc/test_asn_revocation_whitebox.c +++ b/tests/unit-mcdc/test_asn_revocation_whitebox.c @@ -50,6 +50,8 @@ #include +#include "mcdc_fault_alloc.h" + #include #include @@ -2548,8 +2550,78 @@ static void wb_make_crl_ex(void) { WB_NOTE("HAVE_OCSP off; skipped"); } static void wb_sign_crl(void) { WB_NOTE("HAVE_OCSP off; skipped"); } #endif /* HAVE_OCSP && !WOLFCRYPT_ONLY */ +/* ------------------------------------------------------------------------- * + * GetRevoked(): the cleanup guard [:37416] + * `if ((ret != 0) && (rc != NULL)) { ... free rc ... }` + * + * The second operand's false row needs a failure in which `rc` was never + * allocated, i.e. the RevokedCert XMALLOC at :37337 itself returning NULL. + * No input can produce that, so the allocator is faulted for exactly that + * one allocation. The paired true row is an ordinary parse failure with the + * allocation intact. + * ------------------------------------------------------------------------- */ +#ifndef CRL_STATIC_REVOKED_LIST +static void wb_get_revoked_cleanup(void) +{ + /* Revoked ::= SEQUENCE { userCertificate INTEGER, revocationDate Time } */ + static byte revoked[] = { + 0x30, 0x12, + 0x02, 0x01, 0x01, + 0x17, 0x0D, '2','5','0','1','0','1','0','0','0','0','0','0','Z' + }; + DecodedCRL dcrl; + word32 idx; + int ret; + + WB_NOTE("GetRevoked(): parse failure with the RevokedCert allocated" + " [:37416 both operands true]"); + XMEMSET(&dcrl, 0, sizeof(dcrl)); + idx = 0; + /* maxIdx cuts the entry in half, so GetASN_Items fails after the + * allocation has already succeeded. */ + ret = GetRevoked(NULL, revoked, &idx, &dcrl, (word32)sizeof(revoked) / 2); + WB_CHECK(ret != 0, ":37416 truncated Revoked entry rejected"); + + WB_NOTE("GetRevoked(): the RevokedCert allocation itself fails, so the" + " cleanup guard sees a NULL pointer [:37416 second operand" + " false]"); + mcdc_fa_install(); + mcdc_fa_disarm(); + mcdc_fa_arm_only(1); + XMEMSET(&dcrl, 0, sizeof(dcrl)); + idx = 0; + ret = GetRevoked(NULL, revoked, &idx, &dcrl, (word32)sizeof(revoked)); + mcdc_fa_disarm(); + mcdc_fa_restore(); + WB_CHECK(ret == WC_NO_ERR_TRACE(MEMORY_E), + ":37416 RevokedCert allocation failed"); + + WB_NOTE("GetRevoked(): a well-formed entry, so the guard's leading" + " operand is false [:37416]"); + XMEMSET(&dcrl, 0, sizeof(dcrl)); + idx = 0; + ret = GetRevoked(NULL, revoked, &idx, &dcrl, (word32)sizeof(revoked)); + WB_CHECK(ret == 0, ":37416 well-formed Revoked entry accepted"); + if (ret == 0) { + RevokedCert* rc = dcrl.certs; + while (rc != NULL) { + RevokedCert* next = rc->next; + XFREE(rc, dcrl.heap, DYNAMIC_TYPE_CRL); + rc = next; + } + dcrl.certs = NULL; + } +} +#else +static void wb_get_revoked_cleanup(void) +{ + WB_NOTE("CRL_STATIC_REVOKED_LIST; GetRevoked cleanup guard skipped"); +} +#endif + int main(void) { + setvbuf(stdout, NULL, _IONBF, 0); printf("asn.c revocation (OCSP/CRL) white-box MC/DC supplement\n"); wb_ocsp_decode_certid(); @@ -2569,6 +2641,7 @@ int main(void) wb_encode_crl_serial(); wb_make_crl_ex(); wb_sign_crl(); + wb_get_revoked_cleanup(); printf("done (%s)\n", wb_fail ? "with failures" : "ok"); /* Always return 0: a nonzero exit discards this variant's coverage diff --git a/tests/unit-mcdc/test_asn_whitebox.c b/tests/unit-mcdc/test_asn_whitebox.c index 7c0bbc9d1a..b75e4ce5b0 100644 --- a/tests/unit-mcdc/test_asn_whitebox.c +++ b/tests/unit-mcdc/test_asn_whitebox.c @@ -1215,6 +1215,7 @@ static void wb_asn1_print_all(void) { WB_NOTE("WOLFSSL_ASN_PRINT off; skipped"); int main(void) { + setvbuf(stdout, NULL, _IONBF, 0); printf("asn.c white-box MC/DC supplement\n"); wb_get_asn_tag(); From e4f36b66d662c12c6492c293564b577dcf3ab847 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 14:50:34 +0200 Subject: [PATCH 27/57] tests: close eight tls13.c legacy-version MC/DC conditions with mutated hello flights --- tests/api.c | 2 + tests/api/include.am | 2 + tests/api/test_tls13_bounds.c | 304 ++++++++++++++++++++++++++++++++++ tests/api/test_tls13_bounds.h | 40 +++++ 4 files changed, 348 insertions(+) create mode 100644 tests/api/test_tls13_bounds.c create mode 100644 tests/api/test_tls13_bounds.h diff --git a/tests/api.c b/tests/api.c index 2304f24f86..842f8b2575 100644 --- a/tests/api.c +++ b/tests/api.c @@ -309,6 +309,7 @@ #include #include #include +#include #if !defined(NO_CERTS) && defined(WOLFSSL_ASN_TEMPLATE) && defined(HAVE_ECC) #include #endif @@ -40276,6 +40277,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_wolfSSL_set_options), TEST_TLS13_DECLS, + TEST_TLS13_BOUNDS_DECLS, TEST_DECL(test_wolfSSL_tmp_dh), TEST_DECL(test_wolfSSL_tmp_dh_regression), diff --git a/tests/api/include.am b/tests/api/include.am index 5065e88b39..4966261035 100644 --- a/tests/api/include.am +++ b/tests/api/include.am @@ -138,6 +138,7 @@ tests_unit_test_SOURCES += tests/api/test_evp_pkey.c tests_unit_test_SOURCES += tests/api/test_certman.c # TLS 1.3 specific tests_unit_test_SOURCES += tests/api/test_tls13.c +tests_unit_test_SOURCES += tests/api/test_tls13_bounds.c endif EXTRA_DIST += tests/api/api.h @@ -256,4 +257,5 @@ EXTRA_DIST += tests/api/test_evp_cipher.h EXTRA_DIST += tests/api/test_evp_pkey.h EXTRA_DIST += tests/api/test_certman.h EXTRA_DIST += tests/api/test_tls13.h +EXTRA_DIST += tests/api/test_tls13_bounds.h diff --git a/tests/api/test_tls13_bounds.c b/tests/api/test_tls13_bounds.c new file mode 100644 index 0000000000..95c0888d61 --- /dev/null +++ b/tests/api/test_tls13_bounds.c @@ -0,0 +1,304 @@ +/* test_tls13_bounds.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* MC/DC vectors for src/tls13.c's protocol-version and length/boundary + * decisions (ISO 26262 Part 7, Track B). + * + * Every test here drives a real handshake through the tests/utils.c memio + * transport and mutates one field of one flight, so no WOLFSSL_LOCAL symbol is + * referenced and the file links in a shared build. Each vector is written to + * complete an independence PAIR with the ordinary handshakes the tls13 group + * already runs: for a decision "A && B" the ordinary handshake supplies the + * (A=1, B=0, D=0) row, so the vector here has to supply either (A=1, B=1, D=1) + * or (A=0, D=0) -- a rejection on its own proves nothing. + */ + +#include + +#ifdef NO_INLINE + #include +#else + #define WOLFSSL_MISC_INCLUDED + #include +#endif + +#include +#include +#include +#include +#include + +#if defined(WOLFSSL_TLS13) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_CERTS) && !defined(NO_FILESYSTEM) && \ + !defined(WOLFSSL_NO_TLS12) + +#define TLS13B_CH_BUF_SZ 4096 +#define TLS13B_REC_HDR_SZ 5 +#define TLS13B_HS_HDR_SZ 4 +/* record header (5) + handshake header (4) = offset of legacy_version */ +#define TLS13B_LEGACY_OFF (TLS13B_REC_HDR_SZ + TLS13B_HS_HDR_SZ) + +/* Copy the first complete TLS record out of a memio direction buffer. + * + * dir 0 is the server's inbox (client -> server), dir 1 the client's inbox. + * A single memio "message" can hold several concatenated records (the server + * writes ServerHello + CCS + the first encrypted flight in one go), so the + * record is sliced out using its own 2-byte length field rather than the + * message size. + * + * Returns 0 on success. */ +static int test_tls13b_take_record(struct test_memio_ctx* ctx, int dir, + byte* out, int out_cap, int* out_sz) +{ + const char* msg = NULL; + int msg_sz = 0; + int rec_sz; + + if (test_memio_get_message(ctx, dir, &msg, &msg_sz, 0) != 0) + return -1; + if (msg_sz < TLS13B_REC_HDR_SZ) + return -1; + rec_sz = TLS13B_REC_HDR_SZ + + (((int)(byte)msg[3] << 8) | (int)(byte)msg[4]); + if (rec_sz > msg_sz || rec_sz > out_cap) + return -1; + + XMEMCPY(out, msg, (size_t)rec_sz); + *out_sz = rec_sz; + return 0; +} + +/* Overwrite the legacy_version of the handshake message carried by a + * plaintext handshake record. hs_type guards against patching the wrong + * message. Returns 0 on success. */ +static int test_tls13b_set_legacy_version(byte* rec, int rec_sz, byte hs_type, + byte major, byte minor) +{ + if (rec_sz < TLS13B_LEGACY_OFF + 2) + return -1; + if (rec[0] != handshake) + return -1; + if (rec[TLS13B_REC_HDR_SZ] != hs_type) + return -1; + + rec[TLS13B_LEGACY_OFF] = major; + rec[TLS13B_LEGACY_OFF + 1] = minor; + return 0; +} + +/* Drive a TLS 1.3 client far enough to emit its ClientHello, rewrite the + * ClientHello's legacy_version to major.minor, replay it to a TLS 1.3-only + * server and require the given error. + * + * All three callers land in DoTls13ClientHello()'s legacy-version block + * (tls13.c ~7620-7665). */ +static int test_tls13b_ch_legacy_version(byte major, byte minor, int expErr) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte ch[TLS13B_CH_BUF_SZ]; + int ch_sz = 0; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + + /* Client emits the ClientHello and then blocks on the reply. */ + ExpectIntNE(wolfSSL_connect(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), + WOLFSSL_ERROR_WANT_READ); + + ExpectIntEQ(test_tls13b_take_record(&test_ctx, 0, ch, (int)sizeof(ch), + &ch_sz), 0); + ExpectIntEQ(test_tls13b_set_legacy_version(ch, ch_sz, client_hello, + major, minor), 0); + + test_memio_clear_buffer(&test_ctx, 0); + if (EXPECT_SUCCESS()) { + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)ch, + ch_sz), 0); + } + + ExpectIntNE(wolfSSL_accept(ssl_s), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_get_error(ssl_s, WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), + expErr); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} + +/* Run a TLS 1.3 handshake up to the server's first flight, rewrite the + * ServerHello's legacy_version to major.minor, feed the client that single + * record and require the given error. + * + * Lands in DoTls13ServerHello()'s downgrade block (tls13.c ~5432-5470). */ +static int test_tls13b_sh_legacy_version(byte major, byte minor, int expErr) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte sh[TLS13B_CH_BUF_SZ]; + int sh_sz = 0; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); + + ExpectIntNE(wolfSSL_connect(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), + WOLFSSL_ERROR_WANT_READ); + ExpectIntNE(wolfSSL_accept(ssl_s), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_get_error(ssl_s, WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), + WOLFSSL_ERROR_WANT_READ); + + ExpectIntEQ(test_tls13b_take_record(&test_ctx, 1, sh, (int)sizeof(sh), + &sh_sz), 0); + ExpectIntEQ(test_tls13b_set_legacy_version(sh, sh_sz, server_hello, + major, minor), 0); + + /* Drop the rest of the server flight: only the tampered ServerHello is + * replayed, so the client cannot get past the version check. */ + test_memio_clear_buffer(&test_ctx, 1); + if (EXPECT_SUCCESS()) { + ExpectIntEQ(test_memio_inject_message(&test_ctx, 1, (const char*)sh, + sh_sz), 0); + } + + ExpectIntNE(wolfSSL_connect(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), + expErr); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif /* guards */ + +/* tls13.c:7645 - "args->pv.major == SSLv3_MAJOR && args->pv.minor >= + * TLSv1_3_MINOR". A legacy_version of 0x0304 makes BOTH operands true, which + * is the decision-true row RFC 8446 4.2.1 lets a server abort on. It is the + * accepting partner for cond 0 (paired with the 0x0403 vector below) and for + * cond 1 (paired with every ordinary ClientHello, whose 0x0303 makes cond 1 + * false). */ +int test_tls13_ch_legacy_version_is_tls13(void) +{ +#if defined(WOLFSSL_TLS13) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_CERTS) && !defined(NO_FILESYSTEM) && \ + !defined(WOLFSSL_NO_TLS12) && \ + !defined(WOLFSSL_ALLOW_BAD_TLS_LEGACY_VERSION) + return test_tls13b_ch_legacy_version(SSLv3_MAJOR, TLSv1_3_MINOR, + WC_NO_ERR_TRACE(VERSION_ERROR)); +#else + return TEST_SKIPPED; +#endif +} + +/* tls13.c:7645 cond 0 false row, and tls13.c:7652 cond 0 true row. + * A legacy_version major of 4 is > SSLv3_MAJOR, so :7645 short-circuits false + * on its first operand and :7652's first operand is true on its own. The + * server has downgrade disabled, so the forced downgrade is refused with + * VERSION_ERROR. */ +int test_tls13_ch_legacy_version_major_above_ssl3(void) +{ +#if defined(WOLFSSL_TLS13) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_CERTS) && !defined(NO_FILESYSTEM) && \ + !defined(WOLFSSL_NO_TLS12) + return test_tls13b_ch_legacy_version(SSLv3_MAJOR + 1, TLSv1_2_MINOR, + WC_NO_ERR_TRACE(VERSION_ERROR)); +#else + return TEST_SKIPPED; +#endif +} + +/* tls13.c:7660 cond 1 true row - "args->pv.minor < TLSv1_2_MINOR" in the + * else-if that catches a pre-TLS1.2 legacy_version. 0x0301 (TLS 1.0) falls + * through :7645 and :7652 and makes this operand true; the ordinary + * ClientHello's 0x0303 is the false row. */ +int test_tls13_ch_legacy_version_below_tls12(void) +{ +#if defined(WOLFSSL_TLS13) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_CERTS) && !defined(NO_FILESYSTEM) && \ + !defined(WOLFSSL_NO_TLS12) + return test_tls13b_ch_legacy_version(SSLv3_MAJOR, TLSv1_MINOR, + WC_NO_ERR_TRACE(VERSION_ERROR)); +#else + return TEST_SKIPPED; +#endif +} + +/* tls13.c:5436 - "args->pv.major == ssl->version.major && args->pv.minor < + * TLSv1_2_MINOR" in DoTls13ServerHello(). A ServerHello legacy_version of + * 0x0302 makes both operands true (the decision-true row that cond 0 and + * cond 1 both need); the client has downgrade disabled so it then fails + * :5466 cond 1 ("args->pv.minor != tls12minor"), whose false row every + * ordinary handshake supplies. */ +int test_tls13_sh_legacy_version_below_tls12(void) +{ +#if defined(WOLFSSL_TLS13) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_CERTS) && !defined(NO_FILESYSTEM) && \ + !defined(WOLFSSL_NO_TLS12) + return test_tls13b_sh_legacy_version(SSLv3_MAJOR, TLSv1_1_MINOR, + WC_NO_ERR_TRACE(VERSION_ERROR)); +#else + return TEST_SKIPPED; +#endif +} + +/* tls13.c:5436 cond 0 false row and :5466 cond 0 true row. A ServerHello + * legacy_version major of 4 differs from the client's, so :5436 + * short-circuits false without evaluating its second operand and :5466's + * first operand is true on its own. */ +int test_tls13_sh_legacy_version_major_mismatch(void) +{ +#if defined(WOLFSSL_TLS13) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_CERTS) && !defined(NO_FILESYSTEM) && \ + !defined(WOLFSSL_NO_TLS12) + return test_tls13b_sh_legacy_version(SSLv3_MAJOR + 1, TLSv1_2_MINOR, + WC_NO_ERR_TRACE(VERSION_ERROR)); +#else + return TEST_SKIPPED; +#endif +} diff --git a/tests/api/test_tls13_bounds.h b/tests/api/test_tls13_bounds.h new file mode 100644 index 0000000000..5c3d569bb4 --- /dev/null +++ b/tests/api/test_tls13_bounds.h @@ -0,0 +1,40 @@ +/* test_tls13_bounds.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifndef WOLFCRYPT_TEST_TLS13_BOUNDS_H +#define WOLFCRYPT_TEST_TLS13_BOUNDS_H + +#include + +int test_tls13_ch_legacy_version_is_tls13(void); +int test_tls13_ch_legacy_version_major_above_ssl3(void); +int test_tls13_ch_legacy_version_below_tls12(void); +int test_tls13_sh_legacy_version_below_tls12(void); +int test_tls13_sh_legacy_version_major_mismatch(void); + +#define TEST_TLS13_BOUNDS_DECLS \ + TEST_DECL_GROUP("tls13", test_tls13_ch_legacy_version_is_tls13), \ + TEST_DECL_GROUP("tls13", test_tls13_ch_legacy_version_major_above_ssl3),\ + TEST_DECL_GROUP("tls13", test_tls13_ch_legacy_version_below_tls12), \ + TEST_DECL_GROUP("tls13", test_tls13_sh_legacy_version_below_tls12), \ + TEST_DECL_GROUP("tls13", test_tls13_sh_legacy_version_major_mismatch) + +#endif /* WOLFCRYPT_TEST_TLS13_BOUNDS_H */ From e6c30b8e0ce8d0dfe614c4966db60820c6db003b Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 14:58:15 +0200 Subject: [PATCH 28/57] tests: close pkcs7.c MC/DC :12036 cond 0 and :8237 cond 0 with seeded streaming states --- tests/unit-mcdc/test_pkcs7_decode_whitebox.c | 241 +++++++++++++++++++ 1 file changed, 241 insertions(+) diff --git a/tests/unit-mcdc/test_pkcs7_decode_whitebox.c b/tests/unit-mcdc/test_pkcs7_decode_whitebox.c index 1dcb8bacb4..31f054c24d 100644 --- a/tests/unit-mcdc/test_pkcs7_decode_whitebox.c +++ b/tests/unit-mcdc/test_pkcs7_decode_whitebox.c @@ -120,6 +120,43 @@ * inSz at :6963/:6965 and forced to defSz by * wc_PKCS7_SetMaxStream when it computes 0; it is * never 0 on arrival at VERIFY_STAGE3. + * + * Added by the 2026-08-20 streaming-state wave (Sections 18-19 below), both + * filed in campaign/db/exclusions.json: + * + * :7516 cond 2 -- family (4). stream->content is freed and NULLed at + * :7501-:7502 before VERIFY_STAGE3 runs, so it is + * non-NULL at :7516 only because the + * wc_PKCS7_HandleOctetStrings() call two statements + * above made it so. That function stores a non-NULL + * pointer there in exactly two places, and both set + * contentSz strictly positive in the same block: + * :6648, entered only under + * `if (pkcs7->content && pkcs7->contentSz > 0)` and + * followed at :6655 by + * `stream->contentSz = pkcs7->contentSz`; and :6821, + * followed at :6849 by + * `stream->contentSz += stream->expected`, in the + * `else` of `if (currContRmnSz == 0)` where every + * assignment of expected consistent with + * currContRmnSz > 0 is + * min(currContRmnSz, MAX_PKCS7_STREAM_BUFFER) >= 1. + * The only arm that grows contentSz without allocating + * (the streamOutCb branch at :6790) leaves content + * NULL, which drives cond 1 false, not cond 2. + * :12036 cond 1 -- family (3). The branch that reaches it, + * `stream->expected == MAX_SEQ_SZ`, is what bounds + * stream->length: wc_PKCS7_AddDataToStream() grows it + * only by min(inSz - rdSz, expected - length), and the + * largest expected on any path into + * WC_PKCS7_DECRYPT_KTRI_2 is + * MAX_OID_SZ + MAX_LENGTH_SZ + ASN_TAG_SZ == 38, at + * WC_PKCS7_INFOSET_STAGE1. :12034 has just assigned + * expected = sz + MAX_ALGO_SZ + ASN_TAG_SZ + + * MAX_LENGTH_SZ + 512, i.e. at least 538, so + * `length < expected` is constant true. Section 18's + * true row shows the ceiling: the most the guard can + * ever see is MAX_SEQ_SZ (6) against expected 542. */ #include @@ -1793,6 +1830,208 @@ static void wb_ktri_key_alg_dispatch(void) } #endif +/* ------------------------------------------------------------------------- * + * Section 18: wc_PKCS7_DecryptKtri()'s WC_PKCS7_DECRYPT_KTRI_2 "peek at the + * next SEQUENCE" block, :12036 cond 0 (`pkcs7->stream->length > 0`). + * + * The block is guarded by `pkcs7->stream->expected == MAX_SEQ_SZ`, and + * `expected` on the only path into this state is the RecipientInfo SET length + * that wc_PKCS7_ParseToRecipientInfoSet() stored at :14259 (EnvelopedData then + * copies it into stream->expected at :14424 as recipientSetSz). MAX_SEQ_SZ is + * 6, so the whole block is dead for every bundle this tree can emit -- a real + * KeyTransRecipientInfo SET is hundreds of bytes -- and the measured runs + * execute :12027-:12036 exactly zero times. Reaching it needs a SET whose + * length field says 6 while the recipient behind it is full size, which + * GetSet_ex(..., NO_USER_CHECK) at :14235 does accept; rather than build that + * whole bundle, the state is seeded directly here, which is the same shape. + * + * Both rows of cond 0 in one binary, on the state the guard actually reads: + * false row -- stream->length == 0, so wc_PKCS7_AddDataToStream() hands back + * the caller's buffer (:357) and the decision is false; the KTRI parse + * then runs on and fails in the IssuerAndSerialNumber walk. + * true row -- the stream is pre-loaded with 4 buffered bytes and fed a + * 2-byte chunk, so AddDataToStream() tops the internal buffer up to + * exactly MAX_SEQ_SZ (:379-:399) and returns it; stream->length is then 6 + * and the recomputed `expected` is sz + 538, so the decision is true and + * the call answers WC_PKCS7_WANT_READ_E. + * + * cond 1 (`stream->length < stream->expected`) has no false row and is filed + * in EXCLUSIONS.md: `expected` was just assigned `sz + MAX_ALGO_SZ + + * ASN_TAG_SZ + MAX_LENGTH_SZ + 512` (>= 538) two statements above, while + * stream->length can never exceed the largest `expected` ever passed to + * wc_PKCS7_AddDataToStream() on the way here -- MAX_OID_SZ + MAX_LENGTH_SZ + + * ASN_TAG_SZ == 38, at WC_PKCS7_INFOSET_STAGE1 -- because AddDataToStream only + * ever grows length by min(inSz - rdSz, expected - length). + * ------------------------------------------------------------------------- */ +#ifndef NO_PKCS7_STREAM +static int wb_ktri2_seq_peek(const byte* seed, word32 seedSz, + byte* in, word32 inSz) +{ + wc_PKCS7 pkcs7; + byte decryptedKey[MAX_ENCRYPTED_KEY_SZ]; + word32 decryptedKeySz = (word32)sizeof(decryptedKey); + word32 idx = 0; + int recipFound = 0; + int ret; + + XMEMSET(&pkcs7, 0, sizeof(pkcs7)); + XMEMSET(decryptedKey, 0, sizeof(decryptedKey)); + pkcs7.publicKeyOID = RSAk; + + if (wc_PKCS7_CreateStream(&pkcs7) != 0) { + return MEMORY_E; + } + if (seedSz > 0) { + if (wc_PKCS7_GrowStream(&pkcs7, MAX_SEQ_SZ) != 0) { + wc_PKCS7_FreeStream(&pkcs7); + return MEMORY_E; + } + XMEMCPY(pkcs7.stream->buffer, seed, seedSz); + pkcs7.stream->length = seedSz; + } + pkcs7.stream->idx = 0; + pkcs7.stream->expected = MAX_SEQ_SZ; + pkcs7.stream->varTwo = CMS_ISSUER_AND_SERIAL_NUMBER; /* sidType */ + pkcs7.stream->varThree = 0; /* version */ + wc_PKCS7_ChangeState(&pkcs7, WC_PKCS7_DECRYPT_KTRI_2); + + ret = wc_PKCS7_DecryptKtri(&pkcs7, in, inSz, &idx, decryptedKey, + &decryptedKeySz, &recipFound); + wc_PKCS7_FreeStream(&pkcs7); + return ret; +} + +static void wb_ktri2_seq_peek_rows(void) +{ + /* "30 04 AA BB CC DD": a 6-byte SEQUENCE, so GetSequence()/GetLength() + * succeed against a 6-byte view and the block reaches the guard. */ + static const byte seqSeed[] = { 0x30, 0x04, 0xAA, 0xBB }; + static byte seqTail[] = { 0xCC, 0xDD }; + static byte seqWhole[] = { 0x30, 0x04, 0xAA, 0xBB, 0xCC, 0xDD, + 0x00, 0x00 }; + int ret; + + WB_NOTE("wc_PKCS7_DecryptKtri(): KTRI_2 SEQUENCE peek with nothing" + " buffered [:12036 cond 0 false]"); + ret = wb_ktri2_seq_peek(NULL, 0, seqWhole, (word32)sizeof(seqWhole)); + WB_CHECK(ret != WC_NO_ERR_TRACE(WC_PKCS7_WANT_READ_E), + ":12036 stream->length == 0, guard does not fire"); + + WB_NOTE("wc_PKCS7_DecryptKtri(): KTRI_2 SEQUENCE peek with the internal" + " buffer topped up to MAX_SEQ_SZ [:12036 cond 0 true]"); + ret = wb_ktri2_seq_peek(seqSeed, (word32)sizeof(seqSeed), seqTail, + (word32)sizeof(seqTail)); + WB_CHECK(ret == WC_NO_ERR_TRACE(WC_PKCS7_WANT_READ_E), + ":12036 stream->length > 0 and short of the new expected"); +} +#else +static void wb_ktri2_seq_peek_rows(void) +{ + WB_NOTE("NO_PKCS7_STREAM; KTRI_2 SEQUENCE peek skipped"); +} +#endif /* !NO_PKCS7_STREAM */ + +/* ------------------------------------------------------------------------- * + * Section 19: PKCS7_VerifySignedData() WC_PKCS7_VERIFY_STAGE7, :8237 cond 0 + * (`ret == 0` ahead of the signature OCTET STRING tag test). + * + * This is NOT family (1): the statement between the state's + * wc_PKCS7_AddDataToStream() and the link is `if (idx >= pkiMsg2Sz) ret = + * BUFFER_E;` at :8232, which does write ret. The operand's false row therefore + * exists, and needs wc_PKCS7_ParseSignerInfo() to *succeed* while landing idx + * exactly on the end of the message -- a SignerInfo whose signatureAlgorithm + * is the last element in the buffer, with no signature behind it. No encoder + * in the tree emits that (signature is mandatory), and the truncation sweeps + * in Sections 1-8 and in test_pkcs7_mutate_whitebox.c cannot produce it either + * because cutting a real bundle there also invalidates the enclosing SEQUENCE + * and SET lengths, so the parse fails one stage earlier. + * + * So the stage is entered directly with a hand-built SignerInfo: + * SEQUENCE { INTEGER 1, SEQUENCE (IssuerAndSerialNumber, opaque to the + * parser -- it only records the bytes as the SID), AlgorithmIdentifier + * (sha-256), AlgorithmIdentifier (sha256WithRSAEncryption) } + * stream->expected is 0, so AddDataToStream() hands the buffer straight back + * at idx 0 (:357); stream->varThree is 1 so the `length > 0` operand of the + * enclosing :8230 guard holds, and stream->degenerate is 0. + * + * false row -- the buffer IS exactly the SignerInfo, so the parse leaves + * idx == pkiMsg2Sz, :8232 sets BUFFER_E and the decision is false. + * true row -- the same SignerInfo followed by a 4-byte OCTET STRING + * signature, so idx stops short, ret stays 0, the tag reads 0x04 and the + * decision is true. + * ------------------------------------------------------------------------- */ +#if !defined(NO_PKCS7_STREAM) && !defined(NO_RSA) && !defined(NO_SHA256) +/* offsets: [0..1] SignerInfo SEQ header, then version, SID, digestAlgorithm, + * signatureAlgorithm. 43 bytes total; the trailing 6 bytes are the OCTET + * STRING signature used only by the true row. */ +static byte wbStage7Si[] = { + 0x30, 0x29, /* SignerInfo SEQUENCE */ + 0x02, 0x01, 0x01, /* version 1 */ + 0x30, 0x06, /* IssuerAndSerial */ + 0x30, 0x00, /* issuer Name */ + 0x02, 0x02, 0x12, 0x34, /* serialNumber */ + 0x30, 0x0D, /* digestAlgorithm */ + 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, + 0x03, 0x04, 0x02, 0x01, /* id-sha256 */ + 0x05, 0x00, + 0x30, 0x0D, /* signatureAlgorithm */ + 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, + 0x0D, 0x01, 0x01, 0x0B, /* sha256WithRSA */ + 0x05, 0x00, + 0x04, 0x04, 0xDE, 0xAD, 0xBE, 0xEF /* signature (true row) */ +}; +#define WB_STAGE7_SI_ONLY 43 + +static int wb_stage7_signature(word32 inSz) +{ + wc_PKCS7* p = wc_PKCS7_New(NULL, INVALID_DEVID); + int ret; + + if (p == NULL) { + return MEMORY_E; + } + p->version = 1; + if (wc_PKCS7_CreateStream(p) != 0) { + wc_PKCS7_Free(p); + return MEMORY_E; + } + p->stream->expected = 0; /* AddDataToStream() returns `in` at idx 0 */ + p->stream->length = 0; + p->stream->idx = 0; + p->stream->varThree = 1; /* `length` operand of :8230 */ + p->stream->degenerate = 0; + p->stream->indefLen = 0; + wc_PKCS7_ChangeState(p, WC_PKCS7_VERIFY_STAGE7); + + ret = PKCS7_VerifySignedData(p, NULL, 0, wbStage7Si, inSz, NULL, 0); + wc_PKCS7_Free(p); + return ret; +} + +static void wb_stage7_signature_rows(void) +{ + int ret; + + WB_NOTE("PKCS7_VerifySignedData(): STAGE7 with the SignerInfo ending the" + " message [:8237 cond 0 false]"); + ret = wb_stage7_signature(WB_STAGE7_SI_ONLY); + WB_CHECK(ret == WC_NO_ERR_TRACE(BUFFER_E), + ":8232 idx == pkiMsg2Sz sets BUFFER_E before the tag test"); + + WB_NOTE("PKCS7_VerifySignedData(): STAGE7 with a signature OCTET STRING" + " behind the SignerInfo [:8237 cond 0 true]"); + ret = wb_stage7_signature((word32)sizeof(wbStage7Si)); + WB_CHECK(ret != WC_NO_ERR_TRACE(BUFFER_E), + ":8237 ret == 0 on arrival, the OCTET STRING tag is read"); +} +#else +static void wb_stage7_signature_rows(void) +{ + WB_NOTE("NO_PKCS7_STREAM/NO_RSA/NO_SHA256; STAGE7 signature-presence" + " rows skipped"); +} +#endif /* !NO_PKCS7_STREAM && !NO_RSA && !NO_SHA256 */ + /* ------------------------------------------------------------------------- * * main -- always returns 0 so the campaign harness keeps this variant's * coverage even if an individual sub-section's build config disables it. @@ -1826,6 +2065,8 @@ int main(void) wb_parse_signer_info_nodegenerate(); wb_rsa_spki_guards(); wb_ktri_key_alg_dispatch(); + wb_ktri2_seq_peek_rows(); + wb_stage7_signature_rows(); if (rngRet == 0) { wc_FreeRng(&wbRng); From 2f9e695011b03d6e217159c8707d45dfa9ff70ca Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 15:03:00 +0200 Subject: [PATCH 29/57] tests: drive tls13.c certificate fragment resume with an interrupted send --- tests/api/test_tls13_bounds.c | 184 ++++++++++++++++++++++++++++++++++ tests/api/test_tls13_bounds.h | 6 +- 2 files changed, 189 insertions(+), 1 deletion(-) diff --git a/tests/api/test_tls13_bounds.c b/tests/api/test_tls13_bounds.c index 95c0888d61..23c943a34b 100644 --- a/tests/api/test_tls13_bounds.c +++ b/tests/api/test_tls13_bounds.c @@ -205,6 +205,139 @@ static int test_tls13b_sh_legacy_version(byte major, byte minor, int expErr) wolfSSL_CTX_free(ctx_s); return EXPECT_RESULT(); } +/* --- WANT_WRITE resumption harness ----------------------------------------- + * + * SendTls13Certificate() only fragments (and only takes its ssl->fragOffset + * resume path) when a send is interrupted part way through a multi-record + * Certificate. test_memio's own simulate_want_write is all-or-nothing from the + * start of the flight, so instead a counting send callback is layered over + * test_memio_write_cb: write number tls13b_ww_at fails with WANT_WRITE once, + * every other write goes through. Sweeping tls13b_ww_at across the whole + * flight interrupts each record in turn, and the handshake is still required + * to complete, so the resume paths are exercised without a rejection vector. + */ +static int tls13b_ww_at = -1; +static int tls13b_ww_n = 0; + +static int test_tls13b_send_cb(WOLFSSL* ssl, char* buf, int sz, void* ctx) +{ + if (tls13b_ww_n++ == tls13b_ww_at) + return WOLFSSL_CBIO_ERR_WANT_WRITE; + return test_memio_write_cb(ssl, buf, sz, ctx); +} + +/* Build a memio pair whose server presents a certificate CHAIN (the default + * test_memio_setup loads the leaf only, which leaves certChainSz == 0 and the + * whole chain-walk block in SendTls13Certificate unreachable). + * + * The pre-made ctx_s makes test_memio_setup_ex skip its own certificate load + * AND its IO callback installation, so both are done here. */ +static int test_tls13b_setup_chain(struct test_memio_ctx* tc, + WOLFSSL_CTX** ctx_c, WOLFSSL_CTX** ctx_s, WOLFSSL** ssl_c, WOLFSSL** ssl_s, + int wantWriteSide) +{ + *ctx_s = wolfSSL_CTX_new(wolfTLSv1_3_server_method()); + if (*ctx_s == NULL) + return -1; + if (wolfSSL_CTX_use_PrivateKey_file(*ctx_s, svrKeyFile, CERT_FILETYPE) + != WOLFSSL_SUCCESS) + return -1; + if (wolfSSL_CTX_use_certificate_chain_file(*ctx_s, svrCertFile) + != WOLFSSL_SUCCESS) + return -1; + if (wolfSSL_CTX_load_verify_locations(*ctx_s, caCertFile, 0) + != WOLFSSL_SUCCESS) + return -1; + wolfSSL_SetIORecv(*ctx_s, test_memio_read_cb); + wolfSSL_SetIOSend(*ctx_s, wantWriteSide == 0 ? test_tls13b_send_cb + : test_memio_write_cb); + + if (test_memio_setup_ex(tc, ctx_c, ctx_s, ssl_c, ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method, + NULL, 0, NULL, 0, NULL, 0) != 0) + return -1; + + /* Per-SSL, not per-CTX: wolfSSL_new() has already copied ctx_c's + * callbacks into ssl_c by this point, so setting it on the CTX would be + * a no-op for this connection. */ + if (wantWriteSide == 1) + wolfSSL_SSLSetIOSend(*ssl_c, test_tls13b_send_cb); + return 0; +} + +/* One handshake with the server's write number 'at' interrupted by + * WANT_WRITE. Small max_fragment_length forces the Certificate across several + * records so at least one 'at' lands inside it. */ +static int test_tls13b_server_frag_round(int at) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + tls13b_ww_at = at; + tls13b_ww_n = 0; + + ExpectIntEQ(test_tls13b_setup_chain(&test_ctx, &ctx_c, &ctx_s, &ssl_c, + &ssl_s, 0), 0); +#ifdef HAVE_MAX_FRAGMENT + ExpectIntEQ(wolfSSL_UseMaxFragment(ssl_c, WOLFSSL_MFL_2_9), + WOLFSSL_SUCCESS); +#endif + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 64, NULL), 0); + + tls13b_ww_at = -1; + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} + +/* Same, with the client holding a certificate chain and the interrupt on the + * client's writes, so the client-side resume path in wolfSSL_connect_TLSv13() + * runs too. */ +static int test_tls13b_client_frag_round(int at) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + tls13b_ww_at = at; + tls13b_ww_n = 0; + + ExpectIntEQ(test_tls13b_setup_chain(&test_ctx, &ctx_c, &ctx_s, &ssl_c, + &ssl_s, 1), 0); + /* Ask for the client's certificate so the client also fragments one. + * client-cert.pem is its own issuer, so it has to be added to the + * server's store for the verify to succeed. */ + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, cliCertFile, 0), + WOLFSSL_SUCCESS); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_PEER, NULL); + ExpectIntEQ(wolfSSL_use_certificate_chain_file(ssl_c, cliCertFile), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_use_PrivateKey_file(ssl_c, cliKeyFile, CERT_FILETYPE), + WOLFSSL_SUCCESS); +#ifdef HAVE_MAX_FRAGMENT + ExpectIntEQ(wolfSSL_UseMaxFragment(ssl_c, WOLFSSL_MFL_2_9), + WOLFSSL_SUCCESS); +#endif + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 64, NULL), 0); + + tls13b_ww_at = -1; + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} #endif /* guards */ /* tls13.c:7645 - "args->pv.major == SSLv3_MAJOR && args->pv.minor >= @@ -302,3 +435,54 @@ int test_tls13_sh_legacy_version_major_mismatch(void) return TEST_SKIPPED; #endif } + +/* Interrupt every write of the server's flight in turn with WANT_WRITE while + * it sends a fragmented Certificate built from a real chain. + * + * Drives SendTls13Certificate()'s resume block: :10019 (certChainSz > 0 && + * fragOffset >= certSz + extSz[0]), :10061 cond 1 (the fragment loop leaving + * with length still > 0 because SendBuffered() answered WANT_WRITE, which is + * the row an uninterrupted send can never produce), :10143, :10156, :10187 and + * :9655, plus wolfSSL_accept_TLSv13()'s :16693 buffered-fragment resume. + * Every round must still complete the handshake, so each vector has its + * accepting partner in the same run. */ +int test_tls13_server_cert_fragment_want_write(void) +{ +#if defined(WOLFSSL_TLS13) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_CERTS) && !defined(NO_FILESYSTEM) && \ + !defined(WOLFSSL_NO_TLS12) + EXPECT_DECLS; + int at; + + for (at = 0; at < 16 && EXPECT_SUCCESS(); at++) + ExpectIntEQ(test_tls13b_server_frag_round(at), TEST_SUCCESS); + + return EXPECT_RESULT(); +#else + return TEST_SKIPPED; +#endif +} + +/* Client-side counterpart: the client sends its own fragmented Certificate + * and its writes are interrupted in turn, which drives + * wolfSSL_connect_TLSv13()'s :15347 buffered-fragment resume. */ +int test_tls13_client_cert_fragment_want_write(void) +{ +#if defined(WOLFSSL_TLS13) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_CERTS) && !defined(NO_FILESYSTEM) && \ + !defined(WOLFSSL_NO_TLS12) + EXPECT_DECLS; + int at; + + for (at = 0; at < 12 && EXPECT_SUCCESS(); at++) + ExpectIntEQ(test_tls13b_client_frag_round(at), TEST_SUCCESS); + + return EXPECT_RESULT(); +#else + return TEST_SKIPPED; +#endif +} diff --git a/tests/api/test_tls13_bounds.h b/tests/api/test_tls13_bounds.h index 5c3d569bb4..31ad1929d3 100644 --- a/tests/api/test_tls13_bounds.h +++ b/tests/api/test_tls13_bounds.h @@ -29,12 +29,16 @@ int test_tls13_ch_legacy_version_major_above_ssl3(void); int test_tls13_ch_legacy_version_below_tls12(void); int test_tls13_sh_legacy_version_below_tls12(void); int test_tls13_sh_legacy_version_major_mismatch(void); +int test_tls13_server_cert_fragment_want_write(void); +int test_tls13_client_cert_fragment_want_write(void); #define TEST_TLS13_BOUNDS_DECLS \ TEST_DECL_GROUP("tls13", test_tls13_ch_legacy_version_is_tls13), \ TEST_DECL_GROUP("tls13", test_tls13_ch_legacy_version_major_above_ssl3),\ TEST_DECL_GROUP("tls13", test_tls13_ch_legacy_version_below_tls12), \ TEST_DECL_GROUP("tls13", test_tls13_sh_legacy_version_below_tls12), \ - TEST_DECL_GROUP("tls13", test_tls13_sh_legacy_version_major_mismatch) + TEST_DECL_GROUP("tls13", test_tls13_sh_legacy_version_major_mismatch), \ + TEST_DECL_GROUP("tls13", test_tls13_server_cert_fragment_want_write), \ + TEST_DECL_GROUP("tls13", test_tls13_client_cert_fragment_want_write) #endif /* WOLFCRYPT_TEST_TLS13_BOUNDS_H */ From 152eb442e08fbd390ccddbcee07baac9812d0160 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 15:06:29 +0200 Subject: [PATCH 30/57] tests: close thirteen asn.c MC/DC conditions across five white-boxes --- tests/unit-mcdc/test_asn_cert_whitebox.c | 163 +++++++++++ tests/unit-mcdc/test_asn_certgen_whitebox.c | 209 ++++++++++++++ tests/unit-mcdc/test_asn_ext_whitebox.c | 34 +++ tests/unit-mcdc/test_asn_fault_whitebox.c | 269 ++++++++++++++++++ .../unit-mcdc/test_asn_revocation_whitebox.c | 103 +++++-- 5 files changed, 759 insertions(+), 19 deletions(-) diff --git a/tests/unit-mcdc/test_asn_cert_whitebox.c b/tests/unit-mcdc/test_asn_cert_whitebox.c index 6855dce24d..9c8b7b1f99 100644 --- a/tests/unit-mcdc/test_asn_cert_whitebox.c +++ b/tests/unit-mcdc/test_asn_cert_whitebox.c @@ -2334,6 +2334,143 @@ static Signer* wb_make_signer(const WbFix* fix) return signer; } +/* Build a certificate request whose serialNumber ATTRIBUTE is a single 0x00 + * byte, and run it through ParseCertRelative() as a CERTREQ_TYPE. Returns + * nothing: the point is the decisions evaluated on the way. */ +#ifdef WOLFSSL_CERT_REQ +static void wb_zero_serial_csr(void) +{ + /* Attribute ::= SEQUENCE { type OBJECT IDENTIFIER, values SET OF ANY } + * type = 2.5.4.5 (id-at-serialNumber), value = UTF8String { 0x00 }. + * UTF8String is one of DecodeCertReqAttrValue()'s accepted choices + * (strAttrChoice, asn.c:23082) and the decoder does not validate the + * code points, so a single NUL byte is carried through verbatim. */ + static const byte serialAttr[] = { + 0x30, 0x0A, + 0x06, 0x03, 0x55, 0x04, 0x05, + 0x31, 0x03, + 0x0C, 0x01, 0x00 + }; + byte* req = NULL; + Cert* cert = NULL; + DecodedCert* dc = NULL; + word32 sz; + word32 co, cl, lo, lw; + word32 infoOff, child, attrsOff = 0; + int n; + int ret; + const word32 cap = WB_FIX_DER_SZ; + + if (!wbKeysOk || !wbRngOk) { + return; + } + + req = (byte*)XMALLOC(cap, NULL, DYNAMIC_TYPE_TMP_BUFFER); + cert = (Cert*)XMALLOC(sizeof(Cert), NULL, DYNAMIC_TYPE_TMP_BUFFER); + dc = (DecodedCert*)XMALLOC(sizeof(DecodedCert), NULL, DYNAMIC_TYPE_DCERT); + if ((req == NULL) || (cert == NULL) || (dc == NULL)) { + XFREE(req, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(cert, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(dc, NULL, DYNAMIC_TYPE_DCERT); + return; + } + + ret = wc_InitCert(cert); + if (ret == 0) { + wb_fill_name(&cert->subject, "MCDC ZS CSR"); + cert->sigType = CTC_SHA256wRSA; + /* Non-empty attributes: the structural insert below needs the [0] + * set to have a content region of its own. */ + XSTRNCPY(cert->challengePw, "mcdc", CTC_NAME_SIZE); + ret = wc_MakeCertReq(cert, req, (int)cap, &wbKeyLeaf, NULL); + if (ret > 0) { + ret = 0; + } + } + if (ret == 0) { + ret = wc_SignCert(cert->bodySz, cert->sigType, req, (int)cap, + &wbKeyRoot, NULL, &wbRng); + if (ret > 0) { + sz = (word32)ret; + ret = 0; + } + else { + ret = -1; + } + } + WB_CHECK(ret == 0, "manufacture the challengePassword CSR"); + + /* CertificationRequest ::= SEQ { CertificationRequestInfo, ... }; + * CertificationRequestInfo ::= SEQ { version, subject, spki, [0] attrs } + * -- walk to the fourth child of the second-level SEQUENCE. */ + if (ret == 0) { + if (mcdc_der_hdr(req, sz, 0, &co, &cl, &lo, &lw) == 0) { + ret = -1; + } + else { + infoOff = co; + if (mcdc_der_hdr(req, sz, infoOff, &co, &cl, &lo, &lw) == 0) { + ret = -1; + } + else { + child = co; + for (n = 0; (ret == 0) && (n < 3); n++) { + if (mcdc_der_hdr(req, sz, child, &co, &cl, &lo, &lw) == 0) { + ret = -1; + } + else { + child = co + cl; + } + } + if ((ret == 0) && + (mcdc_der_hdr(req, sz, child, &co, &cl, &lo, &lw) + != 0) && + (req[child] == + (byte)(ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | + 0)) && (cl > 0)) { + attrsOff = co; + } + else { + ret = -1; + } + } + } + } + WB_CHECK(ret == 0, "located the CSR [0] attributes set"); + + if ((ret == 0) && (mcdc_der_grow(req, &sz, cap, attrsOff, serialAttr, + (word32)sizeof(serialAttr)) != 0)) { + WB_NOTE("splicing the serialNumber attribute was refused; " + ":24630 2nd-operand row skipped"); + ret = -1; + } + + if (ret == 0) { + wc_InitDecodedCert(dc, req, sz, NULL); + ret = ParseCertRelative(dc, CERTREQ_TYPE, NO_VERIFY, NULL, NULL); + /* The spliced attribute invalidates the signature, and a CSR is + * always self-verified, so the call ends in a signature failure -- + * long after :24622/:24630. What is asserted is that the serial + * attribute was picked up at all; without it the guard is not even + * entered. */ + WB_CHECK(dc->serialSz == 1 && dc->serial[0] == 0 && dc->isCSR == 1, + ":24630 2nd operand false (CSR with a zero serialNumber " + "attribute)"); + wc_FreeDecodedCert(dc); + } + + wc_SetCert_Free(cert); + XFREE(req, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(cert, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(dc, NULL, DYNAMIC_TYPE_DCERT); +} +#else +static void wb_zero_serial_csr(void) +{ + WB_NOTE("WOLFSSL_CERT_REQ off; :24630 2nd-operand row skipped"); +} +#endif + /* The fixture set. File-scope so the small_stack variant does not put ~80KB * of certificate DER on the stack. */ static WbFix wbRootA; /* self-signed CA, pathLen 1, keyCertSign */ @@ -2737,6 +2874,32 @@ static void wb_fixture_parse_matrix(void) wb_parse_one(&wbZeroSerialSubCA, CA_TYPE, VERIFY, cm, NULL); wb_parse_one(&wbZeroSerialSubCA, TRUSTED_PEER_TYPE, VERIFY, cm, NULL); + /* ---- :24630 `!isTrustAnchorLoad && !isCsr`, second operand -------- * + * A certificate can never take the isCsr arm and no certificate can + * make either of :24624's trailing operands false: DecodeCertInternal() + * (asn.c:23007) already rejects serial 0 for anything that is not + * `isCA && selfSigned`, and ParseCertRelative() returns immediately on a + * negative DecodeCert(), so `ret == 0` at :24622 implies both. + * + * A certificate REQUEST is the exception, and the only one. It is parsed + * by DecodeCertReq(), which does not carry that check, and it CAN carry a + * serial number: DecodeCertReqAttrValue()'s SERIAL_NUMBER_OID arm + * (asn.c:23145) copies a serialNumber ATTRIBUTE into cert->serial. A CSR + * whose attribute value is a single 0x00 byte therefore reaches :24622 + * with serialSz == 1, serial[0] == 0, isTrustAnchorLoad == 0 (the type is + * CERTREQ_TYPE) and isCsr == 1. + * + * wc_MakeCertReq() has no way to emit that attribute, so it is inserted + * structurally: the request is generated with a challengePassword (which + * makes the [0] attributes set non-empty, so it has a content region to + * insert into) and mcdc_der_grow() then splices an + * SEQUENCE { OID 2.5.4.5, SET { UTF8String 0x00 } } + * in at the START of that set's content, rewriting the enclosing + * CertificationRequest / CertificationRequestInfo / [0] lengths. The + * signature is invalidated by the edit, which does not matter: :24630 is + * evaluated well before the CERTREQ_TYPE ConfirmSignature() call. */ + wb_zero_serial_csr(); + /* ---- basicConstraints / keyUsage consistency [:24464] ------------- */ wb_parse_one(&wbLeafKuCertSign, CERT_TYPE, VERIFY, cm, NULL); wb_parse_one(&wbLeafKuCertSign, CA_TYPE, NO_VERIFY, cm, NULL); diff --git a/tests/unit-mcdc/test_asn_certgen_whitebox.c b/tests/unit-mcdc/test_asn_certgen_whitebox.c index 0126a6afbc..d7b980612f 100644 --- a/tests/unit-mcdc/test_asn_certgen_whitebox.c +++ b/tests/unit-mcdc/test_asn_certgen_whitebox.c @@ -1331,6 +1331,47 @@ static void wb_set_subject_issuer_raw(void) * BAD_FUNC_ARG guard); shown here for completeness of the wrapper. */ ret = wc_SetSubjectRaw(&cert, client_cert_der_2048, -1); WB_CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), "wc_SetSubjectRaw derSz<0 guard"); + + /* Second operand false: a subject Name whose raw encoding is longer than + * sizeof(CertName). No certificate in the corpus has one -- the whole + * struct is over a kilobyte -- and the generator cannot produce one + * either, because every CertName field is a fixed CTC_NAME_SIZE buffer. + * The guard is reached with such a length by loading a real certificate + * with wc_SetCert_LoadDer() (the same file-static wc_SetSubjectRaw() + * itself calls), writing the length into the cached DecodedCert, and + * then calling the public entry point with the SAME der pointer: the + * `cert->der != der` test at asn.c:32707 is false, so the reload is + * skipped, ret stays 0 and the enclosing `if (ret >= 0)` is entered with + * the oversized length in place. The XMEMCPY the guard protects is + * exactly what does NOT run, so nothing is overrun. */ + { + DecodedCert* dc; + + WB_CHECK(wc_InitCert(&cert) == 0, "wc_InitCert (raw C)"); + ret = wc_SetCert_LoadDer(&cert, client_cert_der_2048, + (word32)sizeof_client_cert_der_2048, INVALID_DEVID); + WB_CHECK(ret >= 0, "wc_SetCert_LoadDer (oversized-subject fixture)"); + if (ret >= 0) { + dc = (DecodedCert*)cert.decodedCert; + dc->subjectRawLen = (int)sizeof(CertName) + 1; + ret = wc_SetSubjectRaw(&cert, client_cert_der_2048, + (int)sizeof_client_cert_der_2048); + WB_CHECK(ret >= 0, + ":32713 2nd operand false (subjectRawLen > sizeof(CertName))"); + } + + WB_CHECK(wc_InitCert(&cert) == 0, "wc_InitCert (raw D)"); + ret = wc_SetCert_LoadDer(&cert, client_cert_der_2048, + (word32)sizeof_client_cert_der_2048, INVALID_DEVID); + if (ret >= 0) { + dc = (DecodedCert*)cert.decodedCert; + dc->subjectRawLen = (int)sizeof(CertName) + 1; + ret = wc_SetIssuerRaw(&cert, client_cert_der_2048, + (int)sizeof_client_cert_der_2048); + WB_CHECK(ret >= 0, + ":32750 2nd operand false (subjectRawLen > sizeof(CertName))"); + } + } } #else static void wb_set_subject_issuer_raw(void) @@ -2763,6 +2804,169 @@ static void wb_acert_general_names(void) #endif FreeAltNames(entries, NULL); } + +/* wc_ParseX509Acert()'s AttCertIssuer dispatch + * i_issuer = (dataASN[ACERT_IDX_ACINFO_ISSUER_V2].tag != 0) ? + * ACERT_IDX_ACINFO_ISSUER_V2 : ACERT_IDX_ACINFO_ISSUER_V1; + * ... + * if (i_issuer == ACERT_IDX_ACINFO_ISSUER_V2 && issuer_len > 0) { ... } + * [:40879] + * + * AcertASN declares the two issuer forms as one CHOICE group (asn.c:40594 and + * :40596): `[0] IMPLICIT V2Form` (tag 0xA0) and the bare `GeneralNames` + * SEQUENCE (tag 0x30). Both corpus attribute certificates carry the v2Form, + * so every existing vector arrives with i_issuer == ..._ISSUER_V2 and a + * non-empty content -- the decision is always true and neither operand has an + * independence pair. + * + * Two edits of certs/acert/acert.pem produce the two missing rows, and the + * unmodified certificate supplies the true row in the same binary: + * + * - retag the AttCertIssuer element from 0xA0 to 0x30. Nothing else moves + * (the tag is one byte and the length field is untouched), the CHOICE + * then matches the v1Form alternative, and cond 0 goes false. + * - delete the whole content of the [0] element with mcdc_der_shrink(), so + * the encoding still carries an `A0 00` -- tag present, length zero. The + * template records the tag, i_issuer stays ..._ISSUER_V2, and cond 1 goes + * false with cond 1's partner (the untouched cert) true. + * + * The AttCertIssuer element is located by walking the encoding rather than by + * scanning for 0xA0: Holder itself contains [0]/[1]/[2] members, so a linear + * search would find the wrong item. AttributeCertificate ::= SEQUENCE { + * acinfo, ... }, AttributeCertificateInfo ::= SEQUENCE { version, holder, + * issuer, ... }, so the issuer is the third child of the second child of the + * root. + */ +#if defined(WOLFSSL_ACERT) && defined(WOLFSSL_ASN_TEMPLATE) +/* Offset of the AttCertIssuer element, or 0 when the walk fails. */ +static word32 wb_acert_issuer_off(const byte* der, word32 sz) +{ + word32 co, cl, lo, lw; + word32 acinfo; + word32 child; + int n; + + if (mcdc_der_hdr(der, sz, 0, &co, &cl, &lo, &lw) == 0) { + return 0; /* AttributeCertificate SEQUENCE */ + } + acinfo = co; + if (mcdc_der_hdr(der, sz, acinfo, &co, &cl, &lo, &lw) == 0) { + return 0; /* AttributeCertificateInfo SEQUENCE */ + } + child = co; + for (n = 0; n < 2; n++) { /* skip version, then holder */ + if (mcdc_der_hdr(der, sz, child, &co, &cl, &lo, &lw) == 0) { + return 0; + } + child = co + cl; + } + if (mcdc_der_hdr(der, sz, child, &co, &cl, &lo, &lw) == 0) { + return 0; + } + return child; +} + +static void wb_acert_issuer_form(void) +{ + static const char* path = "./certs/acert/acert.pem"; + byte* pem; + long pemSz = 0; + DerBuffer* der = NULL; + byte edit[2048]; + word32 sz; + word32 issuerOff; + word32 co, cl, lo, lw; + int ret; + int v; + + WB_NOTE("wc_ParseX509Acert(): AttCertIssuer v2Form/v1Form dispatch " + "[:40879]"); + + pem = wb_read_file(path, &pemSz); + if (pem == NULL) { + WB_NOTE("corpus ACERT PEM not found at runtime cwd; skipping"); + return; + } + ret = wc_PemToDer(pem, pemSz, ACERT_TYPE, &der, NULL, NULL, NULL); + XFREE(pem, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if ((ret != 0) || (der == NULL)) { + WB_CHECK(0, "wc_PemToDer ACERT (issuer-form fixture)"); + if (der != NULL) { + FreeDer(&der); + } + return; + } + if (der->length > (word32)sizeof(edit)) { + WB_NOTE("acert larger than the edit buffer; skipped"); + FreeDer(&der); + return; + } + + issuerOff = wb_acert_issuer_off(der->buffer, der->length); + WB_CHECK(issuerOff != 0, "located the AttCertIssuer element"); + if (issuerOff == 0) { + FreeDer(&der); + return; + } + WB_CHECK(der->buffer[issuerOff] == + (byte)(ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | 0), + "corpus AttCertIssuer is the v2Form [0]"); + + /* v == 0: untouched (both operands true). + * v == 1: [0] retagged to SEQUENCE -> v1Form chosen, cond 0 false. + * v == 2: [0] emptied -> tag present, issuer_len == 0, cond 1 false. */ + for (v = 0; v < 3; v++) { + WC_DECLARE_VAR(acert, DecodedAcert, 1, 0); + + sz = der->length; + XMEMCPY(edit, der->buffer, sz); + if (v == 1) { + edit[issuerOff] = ASN_SEQUENCE | ASN_CONSTRUCTED; + } + else if (v == 2) { + if (mcdc_der_hdr(edit, sz, issuerOff, &co, &cl, &lo, &lw) == 0) { + WB_CHECK(0, "AttCertIssuer header re-read"); + break; + } + if (mcdc_der_shrink(edit, &sz, co, cl) != 0) { + WB_NOTE("emptying the AttCertIssuer was refused " + "(length width change); row skipped"); + continue; + } + } + +#ifdef WOLFSSL_SMALL_STACK + acert = (DecodedAcert*)XMALLOC(sizeof(DecodedAcert), NULL, + DYNAMIC_TYPE_DCERT); + if (acert == NULL) { + WB_CHECK(0, "alloc DecodedAcert (issuer-form fixture)"); + break; + } +#else + XMEMSET(acert, 0, sizeof(DecodedAcert)); +#endif + wc_InitDecodedAcert(acert, edit, sz, NULL); + ret = wc_ParseX509Acert(acert, NO_VERIFY); + if (v == 0) { + WB_CHECK(ret == 0, ":40879 both operands true (corpus v2Form)"); + } + else if (v == 1) { + WB_CHECK(ret == 0, + ":40879 1st operand false (v1Form GeneralNames)"); + } + else { + WB_CHECK(ret == 0, + ":40879 2nd operand false (empty v2Form, issuer_len 0)"); + } + wc_FreeDecodedAcert(acert); +#ifdef WOLFSSL_SMALL_STACK + XFREE(acert, NULL, DYNAMIC_TYPE_DCERT); +#endif + } + + FreeDer(&der); +} +#endif /* WOLFSSL_ACERT && WOLFSSL_ASN_TEMPLATE */ #else static void wb_decode_holder_issuer_guards(void) { @@ -2788,6 +2992,10 @@ static void wb_parse_acert_bad_dates(void) { WB_NOTE("wc_ParseX509Acert bad-date gates (no WOLFSSL_ACERT); skipped"); } +static void wb_acert_issuer_form(void) +{ + WB_NOTE("wc_ParseX509Acert issuer form (no WOLFSSL_ACERT); skipped"); +} #endif @@ -3136,6 +3344,7 @@ int main(void) wb_parse_acert_bad_dates(); wb_acert_rsapss_params(); wb_acert_general_names(); + wb_acert_issuer_form(); wb_pem_to_der_guards(); wb_encrypted_info_parse_guards(); diff --git a/tests/unit-mcdc/test_asn_ext_whitebox.c b/tests/unit-mcdc/test_asn_ext_whitebox.c index 356f89e45a..e1012e0e60 100644 --- a/tests/unit-mcdc/test_asn_ext_whitebox.c +++ b/tests/unit-mcdc/test_asn_ext_whitebox.c @@ -640,6 +640,17 @@ static void wb_permitted_excluded_lists(void) { WB_NOTE("IGNORE_NAME_CONSTRAINTS * :19366-:19368 URI-without-DNS-host rejection under uriConstraintsApply * :19392 subjectDnsName fallback len>0 && name!=NULL * :19414-:19415 critical + unsupported GeneralName form -> fail closed + * + * RESIDUAL (argued unreachable, recorded in EXCLUSIONS.md under + * "Condition-level exclusions"): the SECOND operand of the subjectDnsName + * fallback, `subjectDnsName.name != NULL`. subjectDnsName is XMEMSET to zero + * at the top of every nameTypes[] iteration and only three switch arms ever + * write it -- ASN_DNS_TYPE inside `cert->subjectCN != NULL`, ASN_RFC822_TYPE + * inside `cert->subjectEmail != NULL` and ASN_DIR_TYPE inside + * `cert->subjectRaw != NULL` -- each assigning the length and the pointer + * from the same object it has just tested. A non-zero .len therefore implies + * a non-NULL .name: the operand is fixed by the branch that reaches it and + * has no independence pair. * ------------------------------------------------------------------------- */ #ifndef IGNORE_NAME_CONSTRAINTS static void wb_confirm_name_constraints(void) @@ -736,6 +747,29 @@ static void wb_confirm_name_constraints(void) WB_CHECK(ConfirmNameConstraints(&signer, &cert) == 1, "no URI constraints in force (2nd operand false, skipped)"); + /* Same URI constraints in force, but the SAN is a dNSName. The + * nameTypes[] sweep reaches ASN_DNS_TYPE with cert->altNames holding an + * entry whose type matches, so the per-entry body IS entered with + * nameType != ASN_URI_TYPE -- the only shape that makes the URI check's + * FIRST operand false. Every vector above enters that body only for + * ASN_URI_TYPE, which pins it true. */ + XMEMSET(&signer, 0, sizeof(signer)); + XMEMSET(&cert, 0, sizeof(cert)); + signer.permittedNames = wb_mk_base(NULL, ".good.com", 9, ASN_URI_TYPE); + cert.isCA = 1; /* suppresses the subjectCN fallback */ + altName = wb_mk_dns("sub.good.com", 12, ASN_DNS_TYPE); + cert.altNames = altName; + WB_CHECK(ConfirmNameConstraints(&signer, &cert) == 1, + ":19505 1st operand false (dNSName SAN under URI constraints)"); + + /* The `subjectDnsName.name != NULL` operand of the subject fallback is a + * justified residual, not an untried row: subjectDnsName is memset to + * zero at the top of every nameTypes[] iteration and only three switch + * arms ever write it -- ASN_DNS_TYPE under `cert->subjectCN != NULL`, + * ASN_RFC822_TYPE under `cert->subjectEmail != NULL` and ASN_DIR_TYPE + * under `cert->subjectRaw != NULL` -- and each of those assigns the + * length and the pointer from the same object it has just tested. A + * non-zero .len therefore implies a non-NULL .name; see EXCLUSIONS.md. */ WB_NOTE("ConfirmNameConstraints(): subjectDnsName fallback len/name [:19392]"); /* subjectEmail present -> synthetic RFC822 name len>0 && name!=NULL, * both true, checked against an excluded email base. */ diff --git a/tests/unit-mcdc/test_asn_fault_whitebox.c b/tests/unit-mcdc/test_asn_fault_whitebox.c index acbf6594fd..da6d9b3999 100644 --- a/tests/unit-mcdc/test_asn_fault_whitebox.c +++ b/tests/unit-mcdc/test_asn_fault_whitebox.c @@ -1156,6 +1156,106 @@ static void wb_pem_to_der_entry_points(void) } #endif +/* ------------------------------------------------------------------------- * + * Section 18a: PemToDer()'s DES-EDE3-CBC trailing-pad trim (:26685). + * + * if (info->cipherType == WC_CIPHER_DES3) { + * if (der->length > DES_BLOCK_SIZE && + * (der->length % DES_BLOCK_SIZE) != 0) { + * padVal = der->buffer[der->length-1]; + * ... + * + * der->length here is the *base64-decoded body length* of the encrypted PEM, + * and the block is NOT guarded on wc_BufferKeyDecrypt()'s return value, so + * the three rows are chosen purely by how many bytes the body decodes to. + * No corpus PEM reaches it: every DES-EDE3-CBC key wolfSSL itself writes is + * a whole number of 8-byte blocks, which pins the second operand false, and + * section 18's encrypted fixture uses AES-128-CBC, which does not enter the + * DES3 arm at all. + * + * Three hand-built PEMs, all with the same header/IV and the same password + * callback, give the three rows in this one binary: + * 8 bytes -> der->length > DES_BLOCK_SIZE is false (cond 0 false) + * 20 bytes -> both operands true, the pad byte is read (decision true) + * 24 bytes -> length is a multiple of the block size (cond 1 false) + * The 20-byte body additionally makes wc_Des3_CbcDecryptWithKey() return + * BAD_LENGTH_E without touching the buffer (des3.c rejects a size that is + * not a multiple of DES_BLOCK_SIZE), so the byte the trim reads is the + * committed ciphertext byte 0x13 -- deterministic, and the same in every + * variant. The 8- and 24-byte bodies do decrypt, with a fixed password and a + * fixed salt, so their trim decisions are deterministic too. + * ------------------------------------------------------------------------- */ +#if defined(WOLFSSL_PEM_TO_DER) && defined(WOLFSSL_ENCRYPTED_KEYS) && \ + !defined(NO_DES3) && !defined(NO_WOLFSSL_SKIP_TRAILING_PAD) && \ + !defined(NO_PWDBASED) +static void wb_pem_des3_trailing_pad(void) +{ + /* Bodies are 0x00..0x07 (8 bytes), 0x00..0x13 (20) and 0x00..0x17 (24). */ + static const char pemDes3_8[] = + "-----BEGIN RSA PRIVATE KEY-----\n" + "Proc-Type: 4,ENCRYPTED\n" + "DEK-Info: DES-EDE3-CBC,0123456789ABCDEF\n" + "\n" + "AAECAwQFBgc=\n" + "-----END RSA PRIVATE KEY-----\n"; + static const char pemDes3_20[] = + "-----BEGIN RSA PRIVATE KEY-----\n" + "Proc-Type: 4,ENCRYPTED\n" + "DEK-Info: DES-EDE3-CBC,0123456789ABCDEF\n" + "\n" + "AAECAwQFBgcICQoLDA0ODxAREhM=\n" + "-----END RSA PRIVATE KEY-----\n"; + static const char pemDes3_24[] = + "-----BEGIN RSA PRIVATE KEY-----\n" + "Proc-Type: 4,ENCRYPTED\n" + "DEK-Info: DES-EDE3-CBC,0123456789ABCDEF\n" + "\n" + "AAECAwQFBgcICQoLDA0ODxAREhMUFRYX\n" + "-----END RSA PRIVATE KEY-----\n"; + static const char* shapes[3]; + static const char* names[3] = { + "8-byte body: der->length > DES_BLOCK_SIZE false (1st operand)", + "20-byte body: both operands true (pad byte read)", + "24-byte body: length is a block multiple (2nd operand false)" + }; + size_t i; + + WB_NOTE("PemToDer(): DES-EDE3-CBC trailing-pad trim [:26685]"); + + shapes[0] = pemDes3_8; + shapes[1] = pemDes3_20; + shapes[2] = pemDes3_24; + + for (i = 0; i < 3; i++) { + EncryptedInfo info; + DerBuffer* d = NULL; + int ret; + + XMEMSET(&info, 0, sizeof(info)); + info.passwd_cb = KeyPemToDerPassCb; + info.passwd_userdata = (void*)"password"; + + ret = PemToDer((const unsigned char*)shapes[i], + (long)XSTRLEN(shapes[i]), PRIVATEKEY_TYPE, &d, NULL, &info, + NULL); + /* Every one of these bodies is random data, so the parse that + * follows the decrypt always fails; what matters is that the trim + * block ran with the intended length. The 20-byte body cannot even + * be decrypted (not a block multiple), which is exactly why its + * second operand is true. */ + WB_CHECK(ret != 0 || d != NULL, names[i]); + if (d != NULL) { + FreeDer(&d); + } + } +} +#else +static void wb_pem_des3_trailing_pad(void) +{ + WB_NOTE("DES3/encrypted-keys/pad-trim not compiled in; skipped"); +} +#endif + /* ------------------------------------------------------------------------- * * Section 18b: the *remaining* PEM<->DER entry guards, each rejection vector * paired with its accepting vector in this same binary. @@ -2544,6 +2644,173 @@ static void wb_confirm_signature_dsa_sigsz(void) static void wb_confirm_signature_dsa_sigsz(void) { WB_NOTE("NO_DSA/HAVE_SELFTEST; skipped"); } #endif +/* ------------------------------------------------------------------------- * + * Section 24c: ConfirmSignature()'s RSA PKCS#1 v1.5 encode-match (:18466). + * + * if (encodedSigSz == verifySz && sigCtx->out != NULL && + * XMEMCMP(sigCtx->out, encodedSig, encodedSigSz) == 0) { ret = 0; } + * + * This is the SIG_STATE_CHECK arm of the classic (non-PSS) RSA case: the + * recovered DigestInfo is re-encoded locally and compared. All THREE operands + * were open, which says the decision had never once been true in this module + * -- no test in the asn/x509/certman groups performs a successful RSA + * certificate-signature verification. Loading a CA is not enough: for a + * self-signed CA_TYPE/TRUSTED_PEER_TYPE certificate ParseCertRelative() skips + * the whole ConfirmSignature() block (asn.c:24680), and the module's tests + * never present a leaf together with its issuer. + * + * Calling ConfirmSignature() directly with the corpus pair -- certs/ + * server-cert.der signed by certs/ca-cert.der with sha256WithRSAEncryption -- + * gives the all-true row with no RNG and no clock. Three more rows, all in + * this binary: + * + * cond 2 false: the same signature verified over a tbs whose first byte is + * flipped. The recovered DigestInfo is still 51 bytes and + * sigCtx->out is still set, so only the XMEMCMP differs. + * cond 0 false: HAVE_PK_CALLBACKS lets an RSA verify callback stand in for + * wc_RsaSSL_VerifyInline() (asn.c:18199). One that reports a + * 1-byte result makes encodedSigSz != verifySz. + * cond 1 false: the same callback reporting exactly the DigestInfo size but + * leaving *out untouched -- sigCtx->out is NULL-initialised at + * asn.c:17743 and only wc_RsaSSL_VerifyInline() ever assigns + * it, so this is the only producer of a matching size with a + * null pointer. + * ------------------------------------------------------------------------- */ +#if !defined(NO_RSA) && !defined(NO_CERTS) && !defined(NO_ASN_CRYPT) && \ + defined(HAVE_PK_CALLBACKS) && !defined(NO_SHA256) +static int wb_rsa_cb_ret = 0; + +static int wb_rsa_verify_stub(unsigned char* sig, unsigned int sigSz, + unsigned char** out, const unsigned char* keyDer, unsigned int keySz, + void* ctx) +{ + (void)sig; + (void)sigSz; + (void)keyDer; + (void)keySz; + (void)ctx; + (void)out; /* deliberately leaves *out as ConfirmSignature set it */ + return wb_rsa_cb_ret; +} + +static void wb_confirm_signature_rsa_encode(void) +{ + byte* caPem = NULL; + byte* leafPem = NULL; + long caSz = 0; + long leafSz = 0; + DecodedCert* ca = NULL; + DecodedCert* leaf = NULL; + byte* tbs = NULL; + word32 tbsSz; + byte digest[WC_SHA256_DIGEST_SIZE]; + byte encoded[MAX_ENCODED_CLASSIC_SIG_SZ]; + int encSz; + int ret; + + WB_NOTE("ConfirmSignature(): RSA PKCS#1 v1.5 encode match [:18466]"); + + caPem = wb_read_pem_file("./certs/ca-cert.der", &caSz); + leafPem = wb_read_pem_file("./certs/server-cert.der", &leafSz); + if ((caPem == NULL) || (leafPem == NULL)) { + WB_NOTE("certs/ca-cert.der or certs/server-cert.der not found; " + "encode-match rows skipped"); + XFREE(caPem, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(leafPem, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return; + } + + ca = (DecodedCert*)XMALLOC(sizeof(DecodedCert), NULL, DYNAMIC_TYPE_DCERT); + leaf = (DecodedCert*)XMALLOC(sizeof(DecodedCert), NULL, DYNAMIC_TYPE_DCERT); + if ((ca == NULL) || (leaf == NULL)) { + XFREE(ca, NULL, DYNAMIC_TYPE_DCERT); + XFREE(leaf, NULL, DYNAMIC_TYPE_DCERT); + XFREE(caPem, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(leafPem, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return; + } + + wc_InitDecodedCert(ca, caPem, (word32)caSz, NULL); + ret = ParseCert(ca, CA_TYPE, NO_VERIFY, NULL); + WB_CHECK(ret == 0, "parsed certs/ca-cert.der"); + wc_InitDecodedCert(leaf, leafPem, (word32)leafSz, NULL); + if (ret == 0) { + ret = ParseCert(leaf, CERT_TYPE, NO_VERIFY, NULL); + WB_CHECK(ret == 0, "parsed certs/server-cert.der"); + } + + if ((ret == 0) && (leaf->sigIndex > leaf->certBegin) && + (ca->publicKey != NULL) && (ca->pubKeySize > 0)) { + SignatureCtx sigCtx; + + tbsSz = leaf->sigIndex - leaf->certBegin; + + /* Row 1: the real pair -> all three operands true. */ + InitSignatureCtx(&sigCtx, NULL, INVALID_DEVID); + ret = ConfirmSignature(&sigCtx, leaf->source + leaf->certBegin, tbsSz, + ca->publicKey, ca->pubKeySize, ca->keyOID, leaf->signature, + leaf->sigLength, leaf->signatureOID, NULL, 0, NULL); + WB_CHECK(ret == 0, ":18466 all three operands true (valid signature)"); + FreeSignatureCtx(&sigCtx); + + /* Row 2: same signature, one tbs byte flipped -> only the XMEMCMP + * differs, so cond 0 and cond 1 stay true and cond 2 goes false. */ + tbs = (byte*)XMALLOC(tbsSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (tbs != NULL) { + XMEMCPY(tbs, leaf->source + leaf->certBegin, tbsSz); + tbs[tbsSz - 1] ^= 0xFFU; + InitSignatureCtx(&sigCtx, NULL, INVALID_DEVID); + ret = ConfirmSignature(&sigCtx, tbs, tbsSz, ca->publicKey, + ca->pubKeySize, ca->keyOID, leaf->signature, + leaf->sigLength, leaf->signatureOID, NULL, 0, NULL); + WB_CHECK(ret != 0, ":18466 3rd operand false (tampered tbs)"); + FreeSignatureCtx(&sigCtx); + XFREE(tbs, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + + /* Row 3: a verify callback that reports a 1-byte result -> the sizes + * disagree and cond 0 goes false. */ + wb_rsa_cb_ret = 1; + InitSignatureCtx(&sigCtx, NULL, INVALID_DEVID); + sigCtx.pkCbRsa = wb_rsa_verify_stub; + ret = ConfirmSignature(&sigCtx, leaf->source + leaf->certBegin, tbsSz, + ca->publicKey, ca->pubKeySize, ca->keyOID, leaf->signature, + leaf->sigLength, leaf->signatureOID, NULL, 0, NULL); + WB_CHECK(ret != 0, ":18466 1st operand false (size mismatch)"); + FreeSignatureCtx(&sigCtx); + + /* Row 4: the same callback reporting exactly the DigestInfo size the + * CHECK stage re-encodes, with *out left NULL -> cond 0 true, + * cond 1 false. */ + XMEMSET(digest, 0, sizeof(digest)); + encSz = (int)wc_EncodeSignature(encoded, digest, + WC_SHA256_DIGEST_SIZE, SHA256h); + WB_CHECK(encSz > 0, "sized the SHA-256 DigestInfo"); + wb_rsa_cb_ret = encSz; + InitSignatureCtx(&sigCtx, NULL, INVALID_DEVID); + sigCtx.pkCbRsa = wb_rsa_verify_stub; + ret = ConfirmSignature(&sigCtx, leaf->source + leaf->certBegin, tbsSz, + ca->publicKey, ca->pubKeySize, ca->keyOID, leaf->signature, + leaf->sigLength, leaf->signatureOID, NULL, 0, NULL); + WB_CHECK(ret != 0, ":18466 2nd operand false (out left NULL)"); + FreeSignatureCtx(&sigCtx); + wb_rsa_cb_ret = 0; + } + + wc_FreeDecodedCert(leaf); + wc_FreeDecodedCert(ca); + XFREE(leaf, NULL, DYNAMIC_TYPE_DCERT); + XFREE(ca, NULL, DYNAMIC_TYPE_DCERT); + XFREE(caPem, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(leafPem, NULL, DYNAMIC_TYPE_TMP_BUFFER); +} +#else +static void wb_confirm_signature_rsa_encode(void) +{ + WB_NOTE("RSA/PK-callbacks/SHA-256 not compiled in; :18466 rows skipped"); +} +#endif + /* ------------------------------------------------------------------------- * * Section 25: leading `ret == 0` operand of the encoders' "is the caller's * buffer big enough" guards. @@ -2783,6 +3050,7 @@ int main(void) wb_get_pubkey_der_from_cert_null_args(); wb_encrypted_info_get_null_args(); wb_pem_to_der_entry_points(); + wb_pem_des3_trailing_pad(); wb_pem_der_remaining_guards(); wb_parse_key_usage_str_null_args(); wb_cert_file_setters_null_args(); @@ -2797,6 +3065,7 @@ int main(void) wb_decode_cert_extensions_unknown_cb(); wb_parse_alloc_sweep(); wb_confirm_signature_dsa_sigsz(); + wb_confirm_signature_rsa_encode(); wb_encoder_size_guards(); wb_decode_dsa_asn1_sig_alloc(); diff --git a/tests/unit-mcdc/test_asn_revocation_whitebox.c b/tests/unit-mcdc/test_asn_revocation_whitebox.c index 2cc15acbc4..43c5991848 100644 --- a/tests/unit-mcdc/test_asn_revocation_whitebox.c +++ b/tests/unit-mcdc/test_asn_revocation_whitebox.c @@ -48,6 +48,11 @@ * (masking MC/DC is computed per binary, then ORed across binaries by key). */ +/* Installed BEFORE asn.c is #included: it interposes the two scratch-mp_int + * lifecycle macros so ParseCRL_Extensions()'s CRL-number branch can be made + * to fail its NEW_MP_INT_SIZE()/INIT_MP_INT_SIZE() pair. See section 12. */ +#include "mcdc_fault_mpint.h" + #include #include "mcdc_fault_alloc.h" @@ -1537,25 +1542,40 @@ static void wb_parse_crl_entry_extensions(void) { WB_NOTE("HAVE_CRL off or WOLFC * [:37284,:37285(idx 2,3),:37325,:37326,:37335,:37349,:37358,:37359,:37384, * :37407(idx1)] * - * Two adjacent conditions are ARGUED UNREACHABLE (not attempted below): - * - the CRL-number branch's "if (ret == 0 && (INIT_MP_INT_SIZE(...) != - * MP_OKAY))" guard: outside WOLFSSL_SMALL_STACK, DECL_MP_INT_SIZE_DYN - * stack-allocates `m` unconditionally (MP_INT_SIZE_CHECK_NULL is only - * defined under WOLFSSL_SMALL_STACK), so ret==0 never goes false here - * without a heap-allocation fault injector; INIT_MP_INT_SIZE() on that - * buffer is a plain mp_init() that cannot itself fail. Both operands - * are therefore compile-time constant in every variant this white-box - * runs under. - * - "if (ret == 0 && mp_toradix(m, dcrl->crlNumber, MP_RADIX_HEX) != - * MP_OKAY)": dcrl->crlNumber is CRL_MAX_NUM_HEX_STR_SZ bytes - * (CRL_MAX_NUM_SZ*2+1 = 41), which is exactly the space a maximum-size - * (20-byte, CRL_MAX_NUM_SZ) positive CRL number's hex-radix conversion - * needs -- verified empirically with a 20-byte value (0x7F followed by - * 19 bytes of 0xFF, the largest positive value the preceding size/sign - * checks admit): mp_toradix() still succeeds. ret==0 is also always - * true reaching this line (nothing between the two checks can set it - * nonzero without the residual above already applying), so this - * decision's operands are constant too. + * The CRL-number branch's scratch-mp_int guard + * NEW_MP_INT_SIZE(m, ...); + * #ifdef MP_INT_SIZE_CHECK_NULL + * if (m == NULL) ret = MEMORY_E; + * #endif + * if (ret == 0 && (INIT_MP_INT_SIZE(m, CRL_MAX_NUM_SZ * CHAR_BIT) + * != MP_OKAY)) ret = MP_INIT_E; [:37680] + * was previously recorded here as unreachable in both operands, on the + * grounds that outside WOLFSSL_SMALL_STACK `m` is stack storage that cannot + * be NULL and INIT_MP_INT_SIZE() on it cannot fail. Both halves of that are + * true of the PRODUCT, and neither is a reason the operands have no + * independence pair -- they are exactly what a fault injector exists for. + * mcdc_fault_mpint.h (included above, before asn.c) interposes both macros: + * - mcdc_fmi_arm_new(1) leaves `m` NULL and, because the header also + * defines MP_INT_SIZE_CHECK_NULL, compiles the caller's own NULL guard, + * so ret carries the memory-allocation error on arrival and cond 0 + * goes false; + * - mcdc_fmi_arm_init(1) makes INIT_MP_INT_SIZE() report MP_VAL, which is + * the decision's only true row and therefore the partner BOTH operands + * need; + * - the unarmed vector at the top of this section supplies cond 1's false + * row. All three are in this binary. + * + * The following guard + * if (ret == 0 && mp_toradix(m, (char*)dcrl->crlNumber, MP_RADIX_HEX) + * != MP_OKAY) [:37713] + * IS argued unreachable, and now from the source rather than empirically: + * mp_toradix() is sp_toradix() (sp_int.h:1437), which for MP_RADIX_HEX + * returns anything other than MP_OKAY only when `a` or `str` is NULL + * (sp_int.c:18962 and sp_tohex() at :18782 -- the conversion itself has no + * other failure path). `m` is non-NULL on every arrival (the NULL case sets + * ret at :37676, so cond 0 short-circuits) and dcrl->crlNumber is an array + * member of DecodedCRL, so the decision is never true and neither operand + * pairs. Recorded in EXCLUSIONS.md. * ------------------------------------------------------------------------- */ static word32 wb_build_crl_number_ext(byte* out, const byte* intContent, word32 intContentSz) @@ -1798,6 +1818,51 @@ static void wb_parse_crl_extensions(void) FreeDecodedCRL(&dcrl); } #endif /* !WC_ASN_UNKNOWN_EXT_CB */ + + /* :37680 -- the scratch-mp_int lifecycle guard. The valid small CRL + * number at the top of this function is the unarmed partner (cond 1 + * false); these two are the faulted rows. Both use the same fixture so + * nothing but the injector differs. */ + { + byte val = 0x05; + + sz = wb_build_crl_number_ext(extList, &val, 1); + + /* INIT_MP_INT_SIZE() reports failure -> the decision's only true + * row, which is the partner both operands need. */ + if (mcdc_fmi_init_available()) { + InitDecodedCRL(&dcrl, NULL); + mcdc_fmi_arm_init(1); + ret = ParseCRL_Extensions(&dcrl, extList, 0, sz); + mcdc_fmi_disarm(); + WB_CHECK(ret != 0 && dcrl.crlNumberSet == 0, + ":37680 both operands true (INIT_MP_INT_SIZE faulted)"); + FreeDecodedCRL(&dcrl); + } + else { + WB_NOTE("INIT_MP_INT_SIZE lever unavailable; :37680 true row " + "skipped"); + } + + /* NEW_MP_INT_SIZE() leaves `m` NULL, the caller's own guard sets + * ret = MEMORY_E, so cond 0 is false. Only available where + * DECL_MP_INT_SIZE_DYN declares an assignable pointer -- under + * WOLFSSL_SMALL_STACK the allocation is real and this row is + * contributed by the other variants. */ + if (mcdc_fmi_new_available()) { + InitDecodedCRL(&dcrl, NULL); + mcdc_fmi_arm_new(1); + ret = ParseCRL_Extensions(&dcrl, extList, 0, sz); + mcdc_fmi_disarm(); + WB_CHECK(ret != 0 && dcrl.crlNumberSet == 0, + ":37680 1st operand false (NEW_MP_INT_SIZE faulted)"); + FreeDecodedCRL(&dcrl); + } + else { + WB_NOTE("NEW_MP_INT_SIZE lever unavailable in this build; " + ":37680 1st-operand row skipped"); + } + } } #else static void wb_parse_crl_extensions(void) { WB_NOTE("HAVE_CRL/ASN_TEMPLATE off; ParseCRL_Extensions skipped"); } From 302542c8e635d02f5e25afe29247c2689e568e77 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 15:09:29 +0200 Subject: [PATCH 31/57] tests: add tls13 ECH handshakes and two version-negotiation vectors --- tests/api/test_tls13_bounds.c | 312 ++++++++++++++++++++++++++++++++++ tests/api/test_tls13_bounds.h | 10 +- 2 files changed, 321 insertions(+), 1 deletion(-) diff --git a/tests/api/test_tls13_bounds.c b/tests/api/test_tls13_bounds.c index 23c943a34b..4bd953b3a6 100644 --- a/tests/api/test_tls13_bounds.c +++ b/tests/api/test_tls13_bounds.c @@ -338,6 +338,85 @@ static int test_tls13b_client_frag_round(int at) wolfSSL_CTX_free(ctx_s); return EXPECT_RESULT(); } +/* Rewrite a ServerHello record so it carries a present-but-EMPTY extensions + * block. That is the only shape that reaches DoTls13ServerHello()'s + * post-negotiation code with args->totalExtSz == 0: a ServerHello with no + * extensions FIELD at all returns earlier, at the truncated-length branch. + * Returns the new record length. */ +static int test_tls13b_sh_empty_exts(byte* rec, int rec_sz) +{ + int off, sessIdSz, bodySz; + + if (rec_sz < TLS13B_LEGACY_OFF + 2 + RAN_LEN + 1) + return -1; + if (rec[0] != handshake || rec[TLS13B_REC_HDR_SZ] != server_hello) + return -1; + + off = TLS13B_LEGACY_OFF + OPAQUE16_LEN + RAN_LEN; + sessIdSz = rec[off]; + off += 1 + sessIdSz; + off += OPAQUE16_LEN + OPAQUE8_LEN; /* cipher suite + compression */ + if (off + OPAQUE16_LEN > rec_sz) + return -1; + + rec[off] = 0; + rec[off + 1] = 0; + off += OPAQUE16_LEN; + + bodySz = off - (TLS13B_REC_HDR_SZ + TLS13B_HS_HDR_SZ); + rec[6] = (byte)(bodySz >> 16); + rec[7] = (byte)(bodySz >> 8); + rec[8] = (byte)bodySz; + rec[3] = (byte)((off - TLS13B_REC_HDR_SZ) >> 8); + rec[4] = (byte)(off - TLS13B_REC_HDR_SZ); + return off; +} + +/* Find the first extension of the given type in a ClientHello record and + * return the offset of its body, or -1. */ +static int test_tls13b_ch_find_ext(const byte* rec, int rec_sz, word16 type, + int* bodySz) +{ + int off, sessIdSz, suiteSz, compSz, extEnd; + int extTotal; + + if (rec_sz < TLS13B_LEGACY_OFF + 2 + RAN_LEN + 1) + return -1; + if (rec[0] != handshake || rec[TLS13B_REC_HDR_SZ] != client_hello) + return -1; + + off = TLS13B_LEGACY_OFF + OPAQUE16_LEN + RAN_LEN; + sessIdSz = rec[off]; + off += 1 + sessIdSz; + if (off + OPAQUE16_LEN > rec_sz) + return -1; + suiteSz = ((int)rec[off] << 8) | rec[off + 1]; + off += OPAQUE16_LEN + suiteSz; + if (off + 1 > rec_sz) + return -1; + compSz = rec[off]; + off += 1 + compSz; + if (off + OPAQUE16_LEN > rec_sz) + return -1; + extTotal = ((int)rec[off] << 8) | rec[off + 1]; + off += OPAQUE16_LEN; + extEnd = off + extTotal; + if (extEnd > rec_sz) + return -1; + + while (off + 4 <= extEnd) { + word16 t = (word16)(((word16)rec[off] << 8) | rec[off + 1]); + int l = ((int)rec[off + 2] << 8) | rec[off + 3]; + if (off + 4 + l > extEnd) + return -1; + if (t == type) { + *bodySz = l; + return off + 4; + } + off += 4 + l; + } + return -1; +} #endif /* guards */ /* tls13.c:7645 - "args->pv.major == SSLv3_MAJOR && args->pv.minor >= @@ -486,3 +565,236 @@ int test_tls13_client_cert_fragment_want_write(void) return TEST_SKIPPED; #endif } + +/* tls13.c:5719 cond 0 - "args->totalExtSz > 0" in DoTls13ServerHello(). + * Every ordinary ServerHello supplies the (both operands true, decision true) + * row; this vector supplies the missing (cond 0 false) row by handing a + * downgrade-capable client a ServerHello whose extensions block is present but + * empty. The client then takes the !foundVersion downgrade path and arrives at + * :5719 with totalExtSz == 0. The handshake cannot complete (no key_share), so + * only the failure is asserted. */ +int test_tls13_sh_empty_extensions_block(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_CERTS) && !defined(NO_FILESYSTEM) && \ + !defined(WOLFSSL_NO_TLS12) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte sh[TLS13B_CH_BUF_SZ]; + int sh_sz = 0; + int new_sz = -1; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + /* SSLv23 client: downgrade enabled, which the !foundVersion branch needs. */ + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfSSLv23_client_method, wolfTLSv1_3_server_method), 0); + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); + + ExpectIntNE(wolfSSL_connect(ssl_c), WOLFSSL_SUCCESS); + ExpectIntNE(wolfSSL_accept(ssl_s), WOLFSSL_SUCCESS); + + ExpectIntEQ(test_tls13b_take_record(&test_ctx, 1, sh, (int)sizeof(sh), + &sh_sz), 0); + if (EXPECT_SUCCESS()) + new_sz = test_tls13b_sh_empty_exts(sh, sh_sz); + ExpectIntGT(new_sz, 0); + + test_memio_clear_buffer(&test_ctx, 1); + if (EXPECT_SUCCESS()) { + ExpectIntEQ(test_memio_inject_message(&test_ctx, 1, (const char*)sh, + new_sz), 0); + } + ExpectIntNE(wolfSSL_connect(ssl_c), WOLFSSL_SUCCESS); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* tls13.c:7425 cond 1 - "!IsAtLeastTLSv1_3(ssl->version)" in + * DoTls13SupportedVersions(). Ordinary handshakes give the (foundVersion set, + * version still 1.3, decision false) row. This vector rewrites the single + * version in the ClientHello's supported_versions extension from 0x0304 to + * 0x0303 - same length, so no other field moves - and hands it to a + * downgrade-capable server, which negotiates TLS 1.2 and makes the operand + * true with foundVersion still set. */ +int test_tls13_ch_supported_versions_tls12_only(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_CERTS) && !defined(NO_FILESYSTEM) && \ + !defined(WOLFSSL_NO_TLS12) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte ch[TLS13B_CH_BUF_SZ]; + int ch_sz = 0; + int svOff = -1, svSz = 0, i; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfSSLv23_server_method), 0); + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); + + ExpectIntNE(wolfSSL_connect(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(test_tls13b_take_record(&test_ctx, 0, ch, (int)sizeof(ch), + &ch_sz), 0); + if (EXPECT_SUCCESS()) + svOff = test_tls13b_ch_find_ext(ch, ch_sz, TLSX_SUPPORTED_VERSIONS, + &svSz); + ExpectIntGT(svOff, 0); + /* body is: 1-byte list length, then pairs */ + ExpectIntGE(svSz, 3); + if (EXPECT_SUCCESS()) { + for (i = svOff + 1; i + 1 < svOff + svSz; i += 2) { + if (ch[i] == SSLv3_MAJOR && ch[i + 1] == TLSv1_3_MINOR) + ch[i + 1] = TLSv1_2_MINOR; + } + } + + test_memio_clear_buffer(&test_ctx, 0); + if (EXPECT_SUCCESS()) { + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)ch, + ch_sz), 0); + } + /* The server downgrades; the TLS 1.3-only client cannot follow, so the + * handshake must not complete. */ + ExpectIntNE(wolfSSL_accept(ssl_s), WOLFSSL_SUCCESS); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* A complete Encrypted ClientHello handshake in the tls13 group. The group's + * existing tests never build an ECHConfig, so every ssl->ctx->echConfigs and + * echX operand in DoTls13ClientHello(), DoTls13HandShakeMsgType() and + * SendTls13ClientHello() sits on its "no ECH" row only. This supplies the + * accepting row for all of them; the ordinary handshakes supply the other. */ +int test_tls13_ech_accepted_handshake(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(HAVE_SNI) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_CERTS) && !defined(NO_FILESYSTEM) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + static const char pubName[] = "ech-public-name.com"; + static const char privName[] = "ech-private-name.com"; + byte configs[512]; + word32 configsLen = (word32)sizeof(configs); + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); + + /* The handshake reads ssl->ctx->echConfigs, so generating on the CTX + * after wolfSSL_new() is still in time for this connection. */ + ExpectIntEQ(wolfSSL_CTX_GenerateEchConfig(ctx_s, pubName, 0, 0, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_GetEchConfigs(ctx_s, configs, &configsLen), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_SetEchConfigs(ssl_c, configs, configsLen), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSNI(ssl_c, WOLFSSL_SNI_HOST_NAME, privName, + (word16)XSTRLEN(privName)), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSNI(ssl_s, WOLFSSL_SNI_HOST_NAME, privName, + (word16)XSTRLEN(privName)), WOLFSSL_SUCCESS); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 16, NULL), 0); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* Same handshake with the ECHConfig's public key corrupted, so the server + * cannot open the outer ClientHello and answers with retry_configs. That is + * the ECH-rejected arm of the same decisions - notably EchCheckAcceptance() + * and the acceptance-confirmation compare in DoTls13HandShakeMsgType(). */ +int test_tls13_ech_rejected_handshake(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(HAVE_SNI) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_CERTS) && !defined(NO_FILESYSTEM) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + static const char pubName[] = "ech-public-name.com"; + static const char privName[] = "ech-private-name.com"; + byte configs[512]; + word32 configsLen = (word32)sizeof(configs); + word16 idx; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); + + ExpectIntEQ(wolfSSL_CTX_GenerateEchConfig(ctx_s, pubName, 0, 0, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_GetEchConfigs(ctx_s, configs, &configsLen), + WOLFSSL_SUCCESS); + if (EXPECT_SUCCESS()) { + /* skip list length, version, length, config id, kem id and public + * key length to land on the first byte of the public key */ + idx = OPAQUE16_LEN + OPAQUE16_LEN + OPAQUE16_LEN + OPAQUE8_LEN + + OPAQUE16_LEN + OPAQUE16_LEN; + ExpectIntLT((word32)idx, configsLen); + if (EXPECT_SUCCESS()) + configs[idx] ^= 0xFF; + } + ExpectIntEQ(wolfSSL_SetEchConfigs(ssl_c, configs, configsLen), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSNI(ssl_c, WOLFSSL_SNI_HOST_NAME, privName, + (word16)XSTRLEN(privName)), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSNI(ssl_s, WOLFSSL_SNI_HOST_NAME, pubName, + (word16)XSTRLEN(pubName)), WOLFSSL_SUCCESS); + + /* The server cannot open the outer ClientHello, so it completes the + * handshake against the public name and returns retry_configs; RFC 9849 + * 6.1.7 then makes the client abort with ECH_REQUIRED_E. Both sides run + * their full ECH code paths first, which is the point of the vector. */ + ExpectIntNE(wolfSSL_connect(ssl_c), WOLFSSL_SUCCESS); + ExpectIntNE(wolfSSL_accept(ssl_s), WOLFSSL_SUCCESS); + ExpectIntNE(wolfSSL_connect(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), + WC_NO_ERR_TRACE(ECH_REQUIRED_E)); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_tls13_bounds.h b/tests/api/test_tls13_bounds.h index 31ad1929d3..aa7828f128 100644 --- a/tests/api/test_tls13_bounds.h +++ b/tests/api/test_tls13_bounds.h @@ -31,6 +31,10 @@ int test_tls13_sh_legacy_version_below_tls12(void); int test_tls13_sh_legacy_version_major_mismatch(void); int test_tls13_server_cert_fragment_want_write(void); int test_tls13_client_cert_fragment_want_write(void); +int test_tls13_sh_empty_extensions_block(void); +int test_tls13_ch_supported_versions_tls12_only(void); +int test_tls13_ech_accepted_handshake(void); +int test_tls13_ech_rejected_handshake(void); #define TEST_TLS13_BOUNDS_DECLS \ TEST_DECL_GROUP("tls13", test_tls13_ch_legacy_version_is_tls13), \ @@ -39,6 +43,10 @@ int test_tls13_client_cert_fragment_want_write(void); TEST_DECL_GROUP("tls13", test_tls13_sh_legacy_version_below_tls12), \ TEST_DECL_GROUP("tls13", test_tls13_sh_legacy_version_major_mismatch), \ TEST_DECL_GROUP("tls13", test_tls13_server_cert_fragment_want_write), \ - TEST_DECL_GROUP("tls13", test_tls13_client_cert_fragment_want_write) + TEST_DECL_GROUP("tls13", test_tls13_client_cert_fragment_want_write), \ + TEST_DECL_GROUP("tls13", test_tls13_sh_empty_extensions_block), \ + TEST_DECL_GROUP("tls13", test_tls13_ch_supported_versions_tls12_only), \ + TEST_DECL_GROUP("tls13", test_tls13_ech_accepted_handshake), \ + TEST_DECL_GROUP("tls13", test_tls13_ech_rejected_handshake) #endif /* WOLFCRYPT_TEST_TLS13_BOUNDS_H */ From 7df01a660f2a12ffc34aab96a0ac186a5ebeeade Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 15:13:07 +0200 Subject: [PATCH 32/57] tests: mutually authenticated tls13 handshakes for ecdsa, ed25519, ed448 and rsa --- tests/api/test_tls13_bounds.c | 119 ++++++++++++++++++++++++++++++++++ tests/api/test_tls13_bounds.h | 10 ++- 2 files changed, 128 insertions(+), 1 deletion(-) diff --git a/tests/api/test_tls13_bounds.c b/tests/api/test_tls13_bounds.c index 4bd953b3a6..054ccb4f8b 100644 --- a/tests/api/test_tls13_bounds.c +++ b/tests/api/test_tls13_bounds.c @@ -417,6 +417,62 @@ static int test_tls13b_ch_find_ext(const byte* rec, int rec_sz, word16 type, } return -1; } +/* A mutually authenticated TLS 1.3 handshake with a chosen key type on both + * ends. DoTls13CertificateVerify()'s peer-key / peerSigAlgo dispatch has one + * arm per algorithm and the group only ever ran the RSA one, so each arm's + * operands sat on a single row. */ +/* The ECC, Ed25519 and Ed448 client certificates in certs/ are self-signed, + * so cliCa is the client certificate itself rather than a separate CA. */ +static int test_tls13b_mutual_auth_round(const char* srvCa, + const char* srvCert, const char* srvKey, + const char* cliCa, const char* cliCert, const char* cliKey) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + + ExpectNotNull(ctx_c = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_c, srvCa, 0), + WOLFSSL_SUCCESS); + ExpectNotNull(ctx_s = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + ExpectIntEQ(wolfSSL_CTX_use_certificate_chain_file(ctx_s, srvCert), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx_s, srvKey, CERT_FILETYPE), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, cliCa, 0), + WOLFSSL_SUCCESS); + if (EXPECT_SUCCESS()) { + wolfSSL_CTX_set_verify(ctx_s, WOLFSSL_VERIFY_PEER | + WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + /* Pre-made CTXs make test_memio_setup_ex skip its own IO callback + * install as well as its cert load. */ + wolfSSL_SetIORecv(ctx_c, test_memio_read_cb); + wolfSSL_SetIOSend(ctx_c, test_memio_write_cb); + wolfSSL_SetIORecv(ctx_s, test_memio_read_cb); + wolfSSL_SetIOSend(ctx_s, test_memio_write_cb); + } + + ExpectIntEQ(test_memio_setup_ex(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method, + NULL, 0, NULL, 0, NULL, 0), 0); + ExpectIntEQ(wolfSSL_use_certificate_chain_file(ssl_c, cliCert), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_use_PrivateKey_file(ssl_c, cliKey, CERT_FILETYPE), + WOLFSSL_SUCCESS); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 16, NULL), 0); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} #endif /* guards */ /* tls13.c:7645 - "args->pv.major == SSLv3_MAJOR && args->pv.minor >= @@ -798,3 +854,66 @@ int test_tls13_ech_rejected_handshake(void) #endif return EXPECT_RESULT(); } + +/* ECDSA on both ends: drives DoTls13CertificateVerify()'s + * peerEccDsaKey / ecc_dsa_sa_algo arms. */ +int test_tls13_mutual_auth_ecdsa(void) +{ +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECC) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_CERTS) && !defined(NO_FILESYSTEM) + return test_tls13b_mutual_auth_round(caEccCertFile, eccCertFile, + eccKeyFile, cliEccCertFile, cliEccCertFile, cliEccKeyFile); +#else + return TEST_SKIPPED; +#endif +} + +/* Ed25519 on both ends: drives the peerEd25519Key / ed25519_sa_algo arms. */ +int test_tls13_mutual_auth_ed25519(void) +{ +#if defined(WOLFSSL_TLS13) && defined(HAVE_ED25519) && \ + defined(HAVE_ED25519_SIGN) && defined(HAVE_ED25519_VERIFY) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_ED25519_CLIENT_AUTH) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_CERTS) && !defined(NO_FILESYSTEM) + return test_tls13b_mutual_auth_round(caEdCertFile, edCertFile, + edKeyFile, cliEdCertFile, cliEdCertFile, cliEdKeyFile); +#else + return TEST_SKIPPED; +#endif +} + +/* Ed448 on both ends: drives the peerEd448Key / ed448_sa_algo arms. */ +int test_tls13_mutual_auth_ed448(void) +{ +#if defined(WOLFSSL_TLS13) && defined(HAVE_ED448) && \ + defined(HAVE_ED448_SIGN) && defined(HAVE_ED448_VERIFY) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_ED448_CLIENT_AUTH) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_CERTS) && !defined(NO_FILESYSTEM) + return test_tls13b_mutual_auth_round(caEd448CertFile, ed448CertFile, + ed448KeyFile, cliEd448CertFile, cliEd448CertFile, cliEd448KeyFile); +#else + return TEST_SKIPPED; +#endif +} + +/* RSA on both ends, mutually authenticated. The group already runs one-sided + * RSA handshakes; this adds the client-authenticating rows for the RSA arm of + * the same dispatch. */ +int test_tls13_mutual_auth_rsa(void) +{ +#if defined(WOLFSSL_TLS13) && !defined(NO_RSA) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_CERTS) && !defined(NO_FILESYSTEM) + return test_tls13b_mutual_auth_round(caCertFile, svrCertFile, + svrKeyFile, cliCertFile, cliCertFile, cliKeyFile); +#else + return TEST_SKIPPED; +#endif +} diff --git a/tests/api/test_tls13_bounds.h b/tests/api/test_tls13_bounds.h index aa7828f128..8f086607fa 100644 --- a/tests/api/test_tls13_bounds.h +++ b/tests/api/test_tls13_bounds.h @@ -35,6 +35,10 @@ int test_tls13_sh_empty_extensions_block(void); int test_tls13_ch_supported_versions_tls12_only(void); int test_tls13_ech_accepted_handshake(void); int test_tls13_ech_rejected_handshake(void); +int test_tls13_mutual_auth_ecdsa(void); +int test_tls13_mutual_auth_ed25519(void); +int test_tls13_mutual_auth_ed448(void); +int test_tls13_mutual_auth_rsa(void); #define TEST_TLS13_BOUNDS_DECLS \ TEST_DECL_GROUP("tls13", test_tls13_ch_legacy_version_is_tls13), \ @@ -47,6 +51,10 @@ int test_tls13_ech_rejected_handshake(void); TEST_DECL_GROUP("tls13", test_tls13_sh_empty_extensions_block), \ TEST_DECL_GROUP("tls13", test_tls13_ch_supported_versions_tls12_only), \ TEST_DECL_GROUP("tls13", test_tls13_ech_accepted_handshake), \ - TEST_DECL_GROUP("tls13", test_tls13_ech_rejected_handshake) + TEST_DECL_GROUP("tls13", test_tls13_ech_rejected_handshake), \ + TEST_DECL_GROUP("tls13", test_tls13_mutual_auth_ecdsa), \ + TEST_DECL_GROUP("tls13", test_tls13_mutual_auth_ed25519), \ + TEST_DECL_GROUP("tls13", test_tls13_mutual_auth_ed448), \ + TEST_DECL_GROUP("tls13", test_tls13_mutual_auth_rsa) #endif /* WOLFCRYPT_TEST_TLS13_BOUNDS_H */ From b16cde4bb1683c487aae3e9bcda46ce8c1fc9a0b Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 16:06:30 +0200 Subject: [PATCH 33/57] tests: white-box the tls13.c pointer-presence guards --- tests/include.am | 1 + tests/unit-mcdc/test_tls13_null_whitebox.c | 724 +++++++++++++++++++++ 2 files changed, 725 insertions(+) create mode 100644 tests/unit-mcdc/test_tls13_null_whitebox.c diff --git a/tests/include.am b/tests/include.am index 2ec72a840b..35b034c361 100644 --- a/tests/include.am +++ b/tests/include.am @@ -224,6 +224,7 @@ EXTRA_DIST += \ tests/unit-mcdc/test_srp_whitebox.c \ tests/unit-mcdc/test_tfm_fault_whitebox.c \ tests/unit-mcdc/test_tfm_whitebox.c \ + tests/unit-mcdc/test_tls13_null_whitebox.c \ tests/unit-mcdc/test_tls13_whitebox.c \ tests/unit-mcdc/test_tsp_fault_whitebox.c \ tests/unit-mcdc/test_tsp_whitebox.c \ diff --git a/tests/unit-mcdc/test_tls13_null_whitebox.c b/tests/unit-mcdc/test_tls13_null_whitebox.c new file mode 100644 index 0000000000..ec0325cb45 --- /dev/null +++ b/tests/unit-mcdc/test_tls13_null_whitebox.c @@ -0,0 +1,724 @@ +/* test_tls13_null_whitebox.c + * + * White-box MC/DC supplement for the POINTER-PRESENCE GUARDS of src/tls13.c. + * + * Companion to tests/unit-mcdc/test_tls13_whitebox.c, kept as a separate TU so + * the two can be extended independently; the campaign unions their coverage by + * source line:col exactly as it unions the variant builds. + * + * SCOPE. Every decision driven here is a NULL / presence check on a pointer. + * The campaign's disposition rule for that family is: + * + * - if the operand cannot vary even for a DIRECT caller, because a + * constructor or a callee postcondition fixes it, it is an entry in + * campaign/db/exclusions.json and NOT a test (e.g. `ssl->ctx != NULL`: + * wolfSSL_new() is the only constructor and rejects a NULL CTX); + * - if the operand cannot vary only because every IN-LIBRARY caller has + * already established it, it is reachable from a white-box and belongs + * here. That is what this file supplies. + * + * llvm-cov derives MC/DC independence PER BINARY, so for each decision below + * every row of its independence pairs -- including the "all operands false" + * row that the API tests also produce -- is driven inside THIS program. + * Nothing here leans on tests/api to complete a pair. + * + * DETERMINISM. No handshake, no network, no wall clock, no entropy beyond what + * wolfSSL_new() itself consumes. Every vector is a direct call with + * hand-supplied arguments, so consecutive runs are byte-identical. + * + * main() always returns 0: the campaign scores a nonzero exit as a failed + * white-box and discards its whole coverage, so setup problems print a skip. + */ + +/* Pull tls13.c in verbatim so its file-static helpers are in scope and + * instrumented in THIS binary. tls13.c includes settings.h, which picks up + * user_settings.h via -DWOLFSSL_USER_SETTINGS. */ +#include + +#include + +#define WB_NOTE(msg) do { printf(" [wb] %s\n", (msg)); } while (0) + +/* Every group below needs a live WOLFSSL built through the public API. A + * CLIENT method is used for the same reason as in test_tls13_whitebox.c: + * wolfSSL_new() on a server WOLFSSL_CTX with no certificate loaded returns + * NULL, and loading one would tie this TU to the runner's certs/ tree. The two + * groups that need server-side behaviour flip ssl->options.side for the + * duration of a single call and restore it. */ +#if !defined(NO_TLS) && defined(WOLFSSL_TLS13) && !defined(WOLFCRYPT_ONLY) && \ + !defined(NO_WOLFSSL_CLIENT) + #define WBN_HAVE_SSL_FIXTURE +#endif + +#ifdef WBN_HAVE_SSL_FIXTURE + +static WOLFSSL_CTX* wbn_ctx = NULL; +static WOLFSSL* wbn_ssl = NULL; + +static int wbn_fixture_setup(void) +{ + wbn_ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); + if (wbn_ctx == NULL) + return 0; + wbn_ssl = wolfSSL_new(wbn_ctx); + if (wbn_ssl == NULL) + return 0; + return 1; +} + +static void wbn_fixture_teardown(void) +{ + if (wbn_ssl != NULL) wolfSSL_free(wbn_ssl); + if (wbn_ctx != NULL) wolfSSL_CTX_free(wbn_ctx); + wbn_ssl = NULL; wbn_ctx = NULL; +} + + +/* ------------------------------------------------------------------------- * + * GROUP 1 -- FreeScv13Args() / FreeDcv13Args(), the `args` presence guards. + * + * FreeScv13Args: if (args && args->sigData) [tls13.c ~:10318] + * if (args != NULL && args->frag != NULL) [~:10328] + * FreeDcv13Args: if (args && args->sigData != NULL) [~:11678] + * + * Both are file-static cleanup helpers with a SINGLE in-library call site each + * (the tail of SendTls13CertificateVerify / DoTls13CertificateVerify), and + * without WOLFSSL_ASYNC_CRYPT -- which this module's option list deliberately + * excludes -- the argument there is `Scv13Args args[1]` / `Dcv13Args args[1]`, + * i.e. the address of a stack object. The `ssl->async->freeArgs = ...` + * registration that could supply a different pointer is inside + * #ifdef WOLFSSL_ASYNC_CRYPT and is not compiled. So from the library the + * leading operand is invariant true and the trailing one only ever takes the + * value the send path happened to leave behind. + * + * The pointer arguments are ordinary parameters, not object invariants: a + * direct caller may legitimately pass NULL (that is what the guard is for) and + * may present an args block with the buffer either allocated or not. Three + * calls give both halves of all three decisions' pairs: + * + * args == NULL -> (F,-) decision false + * args != NULL, buffers NULL -> (T,F) decision false + * args != NULL, buffers allocated -> (T,T) decision true + * + * The third vector hands the helper real XMALLOC'd blocks tagged with the same + * DYNAMIC_TYPE_* the send path uses, so the XFREE it performs is the correct + * one and the pointers are nulled by the helper itself -- no double free. + * ------------------------------------------------------------------------- */ +#if (!defined(NO_RSA) || defined(HAVE_ECC) || defined(HAVE_ED25519) || \ + defined(HAVE_ED448) || defined(HAVE_FALCON) || \ + defined(WOLFSSL_HAVE_MLDSA) || defined(WOLFSSL_HAVE_SLHDSA)) && \ + !defined(NO_CERTS) && \ + (!defined(NO_WOLFSSL_SERVER) || !defined(WOLFSSL_NO_CLIENT_AUTH)) + #define WBN_HAVE_SCV_ARGS +#endif +#if (!defined(NO_RSA) || defined(HAVE_ECC) || defined(HAVE_ED25519) || \ + defined(HAVE_ED448) || defined(HAVE_FALCON) || \ + defined(WOLFSSL_HAVE_MLDSA) || defined(WOLFSSL_HAVE_SLHDSA)) && \ + !defined(NO_CERTS) + #define WBN_HAVE_DCV_ARGS +#endif + +static void wbn_free_args_guards(void) +{ +#if defined(WBN_HAVE_SCV_ARGS) || defined(WBN_HAVE_DCV_ARGS) + if (wbn_ssl == NULL) { + WB_NOTE("no ssl fixture; Free*13Args guards skipped"); + return; + } +#endif + +#ifdef WBN_HAVE_SCV_ARGS + { + Scv13Args scv; + + /* (F,-) on both decisions. */ + FreeScv13Args(wbn_ssl, NULL); + + /* (T,F) on both decisions. */ + XMEMSET(&scv, 0, sizeof(scv)); + FreeScv13Args(wbn_ssl, &scv); + + /* (T,T) on both decisions. */ + XMEMSET(&scv, 0, sizeof(scv)); + scv.sigData = (byte*)XMALLOC(16, wbn_ssl->heap, + DYNAMIC_TYPE_SIGNATURE); + scv.frag = (byte*)XMALLOC(16, wbn_ssl->heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (scv.sigData == NULL || scv.frag == NULL) { + XFREE(scv.sigData, wbn_ssl->heap, DYNAMIC_TYPE_SIGNATURE); + XFREE(scv.frag, wbn_ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + WB_NOTE("FreeScv13Args: allocation failed; (T,T) row skipped"); + } + else { + FreeScv13Args(wbn_ssl, &scv); + } + } + WB_NOTE("FreeScv13Args: args/sigData and args/frag guards driven with " + "both halves of every pair"); +#else + WB_NOTE("FreeScv13Args not compiled in this variant; skipped"); +#endif + +#ifdef WBN_HAVE_DCV_ARGS + { + Dcv13Args dcv; + + /* (F,-) */ + FreeDcv13Args(wbn_ssl, NULL); + + /* (T,F) */ + XMEMSET(&dcv, 0, sizeof(dcv)); + FreeDcv13Args(wbn_ssl, &dcv); + + /* (T,T) */ + XMEMSET(&dcv, 0, sizeof(dcv)); + dcv.sigData = (byte*)XMALLOC(16, wbn_ssl->heap, + DYNAMIC_TYPE_SIGNATURE); + if (dcv.sigData == NULL) { + WB_NOTE("FreeDcv13Args: allocation failed; (T,T) row skipped"); + } + else { + FreeDcv13Args(wbn_ssl, &dcv); + } + } + WB_NOTE("FreeDcv13Args: args/sigData guard driven with both halves of " + "its pair"); +#else + WB_NOTE("FreeDcv13Args not compiled in this variant; skipped"); +#endif +} + + +/* ------------------------------------------------------------------------- * + * GROUP 2 -- DoTls13ServerHello()'s entry guard. + * + * if (ssl == NULL || ssl->arrays == NULL) [tls13.c ~:5366] + * + * Same shape, and the same argument, as the eleven key-schedule guards already + * driven from test_tls13_whitebox.c: DoTls13ServerHello is reached only from + * DoTls13HandShakeMsgType, which has dereferenced `ssl` many times over, and + * only while the handshake is in progress -- FreeArrays() runs after it. From + * tests/api the decision is permanently (F,F). + * + * ssl == NULL -> (T,-) decision true + * ssl != NULL, arrays NULL -> (F,T) decision true + * ssl != NULL, arrays set -> (F,F) decision false + * + * The third vector runs the handler's body for real; it is given helloSz = 1, + * which the very next statement rejects with BUFFER_ERROR ("Protocol version + * length check"), so no record state is touched. arrays is nulled and restored + * rather than freed, so teardown is unaffected. + * ------------------------------------------------------------------------- */ +static void wbn_do_server_hello_entry_guard(void) +{ + byte input[4]; + word32 idx; + byte extMsgType; + Arrays* saved; + + if (wbn_ssl == NULL) { + WB_NOTE("no ssl fixture; DoTls13ServerHello entry guard skipped"); + return; + } + + XMEMSET(input, 0, sizeof(input)); + + /* (T,-) */ + idx = 0; extMsgType = server_hello; + (void)DoTls13ServerHello(NULL, input, &idx, 1, &extMsgType); + + /* (F,T) */ + saved = wbn_ssl->arrays; + wbn_ssl->arrays = NULL; + idx = 0; extMsgType = server_hello; + (void)DoTls13ServerHello(wbn_ssl, input, &idx, 1, &extMsgType); + wbn_ssl->arrays = saved; + + /* (F,F) -- bails out at the helloSz < OPAQUE16_LEN check. */ + if (wbn_ssl->arrays != NULL) { + idx = 0; extMsgType = server_hello; + (void)DoTls13ServerHello(wbn_ssl, input, &idx, 1, &extMsgType); + WB_NOTE("DoTls13ServerHello: ssl/arrays entry guard driven with all " + "three vectors"); + } + else { + WB_NOTE("DoTls13ServerHello: fixture has no arrays; (F,F) row " + "skipped"); + } +} + + +/* ------------------------------------------------------------------------- * + * GROUP 3 -- EchHashHelloInner()'s argument guard. + * + * if (ssl == NULL || ech == NULL) [tls13.c ~:3843] + * + * File-static; every in-library caller reaches it with an ssl it has just + * dereferenced and an ech taken from a TLSX whose data pointer was NULL + * checked one line earlier, so the decision is permanently (F,F) from a + * handshake. + * + * The (F,F) vector runs the body: with a zeroed WOLFSSL_ECH whose + * innerClientHelloLen is 0, the client arm writes a 4-byte handshake header + * into the function's own falseHeader and hashes it, allocating ssl->hsHashesEch + * on the way (released by wolfSSL_free). No record layer, no key material. + * ------------------------------------------------------------------------- */ +#ifdef HAVE_ECH +static void wbn_ech_hash_hello_inner_guard(void) +{ + WOLFSSL_ECH ech; + byte inner[4]; + + if (wbn_ssl == NULL) { + WB_NOTE("no ssl fixture; EchHashHelloInner guard skipped"); + return; + } + + XMEMSET(&ech, 0, sizeof(ech)); + XMEMSET(inner, 0, sizeof(inner)); + ech.innerClientHello = inner; + ech.innerClientHelloLen = 0; + + /* (T,-) */ + (void)EchHashHelloInner(NULL, &ech); + /* (F,T) */ + (void)EchHashHelloInner(wbn_ssl, NULL); + /* (F,F) */ + (void)EchHashHelloInner(wbn_ssl, &ech); + + WB_NOTE("EchHashHelloInner: ssl/ech argument guard driven with all three " + "vectors"); +} +#else +static void wbn_ech_hash_hello_inner_guard(void) +{ WB_NOTE("HAVE_ECH off in this variant; EchHashHelloInner skipped"); } +#endif + + +/* ------------------------------------------------------------------------- * + * GROUP 4 -- TlsCheckCookie()'s cookie-secret guards. + * + * if ((primary.buffer == NULL || primary.length == 0) + * && (secondary.buffer == NULL || secondary.length == 0)) [~:7134] + * if (primary.buffer != NULL && primary.length > 0) [~:7155] + * if (ret == the cookie-mismatch code && secondary.buffer != NULL + * && secondary.length > 0) [~:7164] + * + * TlsCheckCookie is WOLFSSL_LOCAL and is called only from the HelloRetryRequest + * cookie path, which a server enters only once wolfSSL_send_hrr_cookie() has + * installed a primary secret -- so the "missing secret" arms and the + * secondary/verify-only rotation arms never both occur on one live WOLFSSL. + * The four (buffer, length) shapes are ordinary buffer state, not an object + * invariant, so a direct call can present each of them. + * + * Vectors (P = primary, S = secondary), all with a 64-byte cookie so the + * `cookieSz < specs.hash_size + macSz` length check passes on an + * un-negotiated WOLFSSL (specs.hash_size is still 0, macSz is the digest + * size): + * + * v1 P.buf NULL S.buf NULL :7134 (T,-,T,-) true + * v2 P.buf set, len 32 S.buf NULL :7134 (F,F,-,-) false + * v3 P.buf set, len 0 S.buf NULL :7134 (F,T,T,-) true + * v4 P.buf NULL S.buf set, len 32 :7134 (T,-,F,F) false + * v5 P.buf NULL S.buf set, len 0 :7134 (T,-,F,T) true + * v6 P.buf set, len 0 S.buf set, len 32 :7134 (F,T,F,F) false + * v7 P.buf set, len 32 S.buf set, len 0 :7134 (F,F,-,-) false + * + * which pairs :7134's four operands as (v1,v2), (v3,v2), (v1,v4), (v5,v4). + * The vectors that get past :7134 then pair :7155 as (v2 -> (T,T)) against + * (v4 -> (F,-)) and (v6 -> (T,F)), and :7164's length operand as + * (v4 -> (T,T,T)) against (v7 -> (T,T,F)); the MAC never matches a random + * cookie, so `ret` carries the cookie-mismatch code on entry to :7164 every + * time. + * + * The secret buffers are file-static arrays assigned into ssl->buffers and + * cleared again before return, so wolfSSL_free() never XFREEs them. + * ------------------------------------------------------------------------- */ +#if defined(WOLFSSL_SEND_HRR_COOKIE) && !defined(NO_WOLFSSL_SERVER) +static byte wbn_secret_pri[32]; +static byte wbn_secret_sec[32]; + +static void wbn_cookie_set(byte* pri, word32 priLen, byte* sec, word32 secLen) +{ + wbn_ssl->buffers.tls13CookieSecret.buffer = pri; + wbn_ssl->buffers.tls13CookieSecret.length = priLen; +#ifdef WOLFSSL_DTLS13 + wbn_ssl->buffers.tls13CookieSecretSecondary.buffer = sec; + wbn_ssl->buffers.tls13CookieSecretSecondary.length = secLen; +#else + (void)sec; (void)secLen; +#endif +} + +static void wbn_tls_check_cookie_guards(void) +{ + byte cookie[64]; + word32 i; + + if (wbn_ssl == NULL) { + WB_NOTE("no ssl fixture; TlsCheckCookie guards skipped"); + return; + } + + /* Fixed contents: the MAC comparison must fail identically every run. */ + for (i = 0; i < sizeof(cookie); i++) + cookie[i] = (byte)i; + for (i = 0; i < sizeof(wbn_secret_pri); i++) + wbn_secret_pri[i] = (byte)(0xA0 + i); + for (i = 0; i < sizeof(wbn_secret_sec); i++) + wbn_secret_sec[i] = (byte)(0x50 + i); + + wbn_cookie_set(NULL, 0, NULL, 0); /* v1 */ + (void)TlsCheckCookie(wbn_ssl, cookie, (word16)sizeof(cookie)); + + wbn_cookie_set(wbn_secret_pri, sizeof(wbn_secret_pri), NULL, 0); /* v2 */ + (void)TlsCheckCookie(wbn_ssl, cookie, (word16)sizeof(cookie)); + + wbn_cookie_set(wbn_secret_pri, 0, NULL, 0); /* v3 */ + (void)TlsCheckCookie(wbn_ssl, cookie, (word16)sizeof(cookie)); + +#ifdef WOLFSSL_DTLS13 + wbn_cookie_set(NULL, 0, wbn_secret_sec, sizeof(wbn_secret_sec)); /* v4 */ + (void)TlsCheckCookie(wbn_ssl, cookie, (word16)sizeof(cookie)); + + wbn_cookie_set(NULL, 0, wbn_secret_sec, 0); /* v5 */ + (void)TlsCheckCookie(wbn_ssl, cookie, (word16)sizeof(cookie)); + + wbn_cookie_set(wbn_secret_pri, 0, + wbn_secret_sec, sizeof(wbn_secret_sec)); /* v6 */ + (void)TlsCheckCookie(wbn_ssl, cookie, (word16)sizeof(cookie)); + + wbn_cookie_set(wbn_secret_pri, sizeof(wbn_secret_pri), + wbn_secret_sec, 0); /* v7 */ + (void)TlsCheckCookie(wbn_ssl, cookie, (word16)sizeof(cookie)); +#endif + + /* Detach the static secrets again so wolfSSL_free() has nothing to free. */ + wbn_cookie_set(NULL, 0, NULL, 0); + + WB_NOTE("TlsCheckCookie: primary/secondary cookie-secret guards driven " + "with both halves of every pair"); +} +#else +static void wbn_tls_check_cookie_guards(void) +{ WB_NOTE("WOLFSSL_SEND_HRR_COOKIE off in this variant; skipped"); } +#endif + + +/* ------------------------------------------------------------------------- * + * GROUP 5 -- SanityCheckTls13MsgReceived()'s DTLS 1.3 Connection ID arms. + * + * if (cidInfo == NULL || !cidInfo->negotiated) [tls13.c ~:14610] + * if (cidInfo->rx == NULL || cidInfo->rx->length == 0) [~:14623] + * if (cidInfo->tx == NULL || cidInfo->tx->length == 0) [~:14633] + * + * SanityCheckTls13MsgReceived is file-static and is a pure predicate over + * ssl->options / ssl->msgsReceived / ssl->dtlsCidInfo -- it has no side effect + * outside msgsReceived, which the CID arms do not touch, so it can be called + * repeatedly on one fixture. A live DTLS 1.3 peer only ever reaches these arms + * with a fully negotiated CIDInfo carrying non-empty ids, because the + * negotiation that allocates cidInfo is the same one that fills rx/tx; the + * "negotiated but empty" states RFC 9147 Section 9 tells the receiver to reject + * cannot be produced by wolfSSL as the peer. + * + * Vectors, all with options.dtls = 1 and handShakeState = HANDSHAKE_DONE so the + * two guards ahead of the rx/tx checks pass: + * + * cidInfo NULL -> :14610 (T,-) true + * cidInfo, negotiated 0 -> :14610 (F,T) true + * cidInfo, negotiated 1 -> :14610 (F,F) false, falls through + * with rx/tx NULL -> :14623/:14633 (T,-) true + * with rx/tx length 0 -> :14623/:14633 (F,T) true + * with rx/tx length 1 -> :14623/:14633 (F,F) false + * + * ConnectionID has a flexible array member, so each id is carved out of a byte + * buffer sized for the header plus one id byte. + * ------------------------------------------------------------------------- */ +#if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_DTLS_CID) +static void wbn_sanity_check_cid_guards(void) +{ + byte rxBuf[sizeof(ConnectionID) + 4]; + byte txBuf[sizeof(ConnectionID) + 4]; + ConnectionID* rx = (ConnectionID*)rxBuf; + ConnectionID* tx = (ConnectionID*)txBuf; + CIDInfo cid; + CIDInfo* savedCid; + byte savedState; + byte savedDtls; + + if (wbn_ssl == NULL) { + WB_NOTE("no ssl fixture; SanityCheckTls13MsgReceived CID arms " + "skipped"); + return; + } + + XMEMSET(rxBuf, 0, sizeof(rxBuf)); + XMEMSET(txBuf, 0, sizeof(txBuf)); + XMEMSET(&cid, 0, sizeof(cid)); + + savedCid = wbn_ssl->dtlsCidInfo; + savedDtls = (byte)wbn_ssl->options.dtls; + savedState = wbn_ssl->options.handShakeState; + + wbn_ssl->options.dtls = 1; + wbn_ssl->options.handShakeState = HANDSHAKE_DONE; + + /* :14610 (T,-) */ + wbn_ssl->dtlsCidInfo = NULL; + (void)SanityCheckTls13MsgReceived(wbn_ssl, request_connection_id); + + /* :14610 (F,T) */ + cid.negotiated = 0; + wbn_ssl->dtlsCidInfo = &cid; + (void)SanityCheckTls13MsgReceived(wbn_ssl, request_connection_id); + + /* From here on the decision at :14610 is (F,F) and the rx/tx arms run. */ + cid.negotiated = 1; + + /* :14623 (T,-) and :14633 (T,-) */ + cid.rx = NULL; + cid.tx = NULL; + (void)SanityCheckTls13MsgReceived(wbn_ssl, request_connection_id); + (void)SanityCheckTls13MsgReceived(wbn_ssl, new_connection_id); + + /* :14623 (F,T) and :14633 (F,T) */ + rx->length = 0; + tx->length = 0; + cid.rx = rx; + cid.tx = tx; + (void)SanityCheckTls13MsgReceived(wbn_ssl, request_connection_id); + (void)SanityCheckTls13MsgReceived(wbn_ssl, new_connection_id); + + /* :14623 (F,F) and :14633 (F,F) */ + rx->length = 1; + tx->length = 1; + (void)SanityCheckTls13MsgReceived(wbn_ssl, request_connection_id); + (void)SanityCheckTls13MsgReceived(wbn_ssl, new_connection_id); + + wbn_ssl->dtlsCidInfo = savedCid; + wbn_ssl->options.dtls = savedDtls; + wbn_ssl->options.handShakeState = savedState; + + WB_NOTE("SanityCheckTls13MsgReceived: CID negotiated/rx/tx guards driven " + "with both halves of every pair"); +} +#else +static void wbn_sanity_check_cid_guards(void) +{ WB_NOTE("DTLS 1.3 CID off in this variant; skipped"); } +#endif + + +/* ------------------------------------------------------------------------- * + * GROUP 6 -- SetupOcspResp()'s two presence chains. + * + * if (extension == NULL && side == WOLFSSL_CLIENT_END + * && options.handShakeDone + * && TLSX_Find(ssl->ctx->extensions, TLSX_STATUS_REQUEST) != NULL) + * [tls13.c ~:9769] + * if (SSL_CM(ssl) != NULL && SSL_CM(ssl)->ocsp_stapling != NULL + * && SSL_CM(ssl)->ocsp_stapling->statusCb != NULL) + * [~:9786] + * + * SetupOcspResp is file-static and is called from the Certificate send path. + * The first chain is the post-handshake-client-auth re-staple: it is true only + * for a CLIENT that has completed its handshake, no longer carries the + * status_request extension on ssl->extensions, and whose CTX still does. Every + * operand of it is ordinary object state that this file sets directly. + * + * SSL_CM(ssl) (operand 0 of :9786) is NOT driven here: ssl->ctx is invariant + * non-NULL for a live WOLFSSL and ctx->cm is allocated by wolfSSL_CTX_new(), + * so that operand has no false row for any caller, direct or not. It is an + * exclusions.json entry, not a test. + * + * Call sequence -- the order matters, because each call can change the state + * the next one reads: + * + * A no ssl ext, side SERVER :9769 (T,F,-,-) false + * B no ssl ext, side CLIENT, !handShakeDone :9769 (T,T,F,-) false + * C no ssl ext, side CLIENT, handShakeDone, + * CTX has no status_request :9769 (T,T,T,F) false + * -- enable status_request on the CTX -- + * D same, CTX now has it :9769 (T,T,T,T) true + * ... which creates the ssl extension, so D also reaches + * :9786 with stapling not yet enabled (T,F,-) false + * -- wolfSSL_CTX_EnableOCSPStapling(): cm->ocsp_stapling allocated -- + * E ssl ext now present :9769 (F,-,-,-) false + * :9786 (T,T,F) false + * -- statusCb installed -- + * F ssl ext present :9769 (F,-,-,-) false + * :9786 (T,T,T) true + * + * pairing :9769's four operands against D and :9786's operands 1 and 2 + * against F. Calls A..C and E return before touching the certificate, D and E + * stop at the "Certificate buffer not set!" check (this fixture has no + * certificate), and F returns through the status callback, which answers + * WOLFSSL_OCSP_STATUS_CB_NOACK -- so nothing here parses a certificate or + * performs an OCSP lookup. + * ------------------------------------------------------------------------- */ +#if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(NO_WOLFSSL_SERVER) && \ + defined(WOLFSSL_POST_HANDSHAKE_AUTH) +static int wbn_status_cb(WOLFSSL* ssl, void* arg) +{ + (void)ssl; (void)arg; + /* NOACK: TLSX_CSR_SetResponseWithStatusCB() maps it to 0 without touching + * ssl->ocspCsrResp, so no response buffer is required. */ + return WOLFSSL_OCSP_STATUS_CB_NOACK; +} + +static void wbn_setup_ocsp_resp_guards(void) +{ + byte savedSide; + byte savedDone; + + if (wbn_ssl == NULL || wbn_ctx == NULL) { + WB_NOTE("no ssl fixture; SetupOcspResp guards skipped"); + return; + } + if (TLSX_Find(wbn_ssl->extensions, TLSX_STATUS_REQUEST) != NULL) { + WB_NOTE("SetupOcspResp: fixture already carries status_request; " + "skipped"); + return; + } + + savedSide = wbn_ssl->options.side; + savedDone = wbn_ssl->options.handShakeDone; + + /* A */ + wbn_ssl->options.side = WOLFSSL_SERVER_END; + wbn_ssl->options.handShakeDone = 0; + (void)SetupOcspResp(wbn_ssl); + + /* B */ + wbn_ssl->options.side = WOLFSSL_CLIENT_END; + (void)SetupOcspResp(wbn_ssl); + + /* C */ + wbn_ssl->options.handShakeDone = 1; + (void)SetupOcspResp(wbn_ssl); + + /* D -- CTX now carries the request extension. */ + if (wolfSSL_CTX_UseOCSPStapling(wbn_ctx, WOLFSSL_CSR_OCSP, 0) + == WOLFSSL_SUCCESS) { + (void)SetupOcspResp(wbn_ssl); + } + else { + WB_NOTE("SetupOcspResp: CTX status_request unavailable; the all-true " + "row of the re-staple chain was not driven"); + } + + /* E -- cm->ocsp_stapling allocated, no callback yet. */ + if (wolfSSL_CTX_EnableOCSPStapling(wbn_ctx) == WOLFSSL_SUCCESS && + SSL_CM(wbn_ssl) != NULL && + SSL_CM(wbn_ssl)->ocsp_stapling != NULL) { + (void)SetupOcspResp(wbn_ssl); + + /* F -- the same assignment wolfSSL_CTX_set_tlsext_status_cb() makes; + * done directly so this group does not depend on the compat-layer + * entry point being compiled. */ + SSL_CM(wbn_ssl)->ocsp_stapling->statusCb = wbn_status_cb; + SSL_CM(wbn_ssl)->ocsp_stapling->statusCbArg = NULL; + (void)SetupOcspResp(wbn_ssl); + SSL_CM(wbn_ssl)->ocsp_stapling->statusCb = NULL; + } + else { + WB_NOTE("SetupOcspResp: OCSP stapling unavailable; the ocsp_stapling " + "and statusCb rows were not driven"); + } + + wbn_ssl->options.side = savedSide; + wbn_ssl->options.handShakeDone = savedDone; + + WB_NOTE("SetupOcspResp: post-handshake re-staple chain and the " + "ocsp_stapling/statusCb chain driven"); +} +#else +static void wbn_setup_ocsp_resp_guards(void) +{ WB_NOTE("SetupOcspResp not compiled in this variant; skipped"); } +#endif + + +/* ------------------------------------------------------------------------- * + * GROUP 7 -- BuildTls13Message()'s non-sizeOnly argument guard. + * + * else if (output == NULL || input == NULL) [tls13.c ~:3345] + * + * BuildTls13Message is WOLFSSL_LOCAL with seventeen in-library call sites, and + * every one of them that passes sizeOnly = 0 hands it a record buffer it has + * just reserved and a payload pointer into that same buffer, so from the whole + * library the decision is permanently (F,F) -- the `sizeOnly` sibling guard one + * line above (:3339) is the one that fires when a caller gets it wrong, and it + * is driven by the size probe in wolfssl_local_GetRecordSize(). + * + * output NULL -> (T,-) decision true + * output set, input NULL -> (F,T) decision true + * output set, input set -> (F,F) decision false + * + * The (F,F) vector is given outSz = 0, so control reaches the very next size + * check -- "Oops, want to write past output buffer size" -- and returns + * BUFFER_E before any record header is written or any AEAD state is touched. + * The fixture has no keys and none are needed. + * ------------------------------------------------------------------------- */ +static void wbn_build_message_arg_guard(void) +{ + byte out[8]; + byte in[8]; + + if (wbn_ssl == NULL) { + WB_NOTE("no ssl fixture; BuildTls13Message argument guard skipped"); + return; + } + + XMEMSET(out, 0, sizeof(out)); + XMEMSET(in, 0, sizeof(in)); + + /* (T,-) */ + (void)BuildTls13Message(wbn_ssl, NULL, 0, in, 0, application_data, 0, 0, 0); + /* (F,T) */ + (void)BuildTls13Message(wbn_ssl, out, 0, NULL, 0, application_data, 0, 0, 0); + /* (F,F) -- stops at the `args->sz > outSz` check with BUFFER_E. */ + (void)BuildTls13Message(wbn_ssl, out, 0, in, 0, application_data, 0, 0, 0); + + /* The probe wrote ssl->options.buildMsgState; put it back so nothing + * downstream inherits a half-built record state. */ + wbn_ssl->options.buildMsgState = BUILD_MSG_BEGIN; + + WB_NOTE("BuildTls13Message: output/input argument guard driven with all " + "three vectors"); +} + + +#endif /* WBN_HAVE_SSL_FIXTURE */ + +int main(void) +{ + setvbuf(stdout, NULL, _IONBF, 0); + printf("tls13.c white-box MC/DC supplement -- pointer-presence guards\n"); + +#ifdef WBN_HAVE_SSL_FIXTURE + if (wolfSSL_Init() != WOLFSSL_SUCCESS) { + WB_NOTE("wolfSSL_Init failed; all groups skipped"); + } + else if (!wbn_fixture_setup()) { + WB_NOTE("could not build the WOLFSSL fixture; all groups skipped"); + } + else { + wbn_free_args_guards(); + wbn_do_server_hello_entry_guard(); + wbn_ech_hash_hello_inner_guard(); + wbn_tls_check_cookie_guards(); + wbn_sanity_check_cid_guards(); + wbn_setup_ocsp_resp_guards(); + wbn_build_message_arg_guard(); + } + + wbn_fixture_teardown(); + wolfSSL_Cleanup(); +#else + WB_NOTE("no TLS 1.3 client on this build axis; nothing to drive"); +#endif + + printf("done\n"); + /* Always 0: a nonzero exit is scored as a failed white-box and its + * coverage is discarded. */ + return 0; +} From af04bae4d7da928a5556eef616f93a36460ad4ac Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 16:10:59 +0200 Subject: [PATCH 34/57] tests: drive the sizeOnly arm of BuildTls13Message's argument guard --- tests/unit-mcdc/test_tls13_null_whitebox.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/tests/unit-mcdc/test_tls13_null_whitebox.c b/tests/unit-mcdc/test_tls13_null_whitebox.c index ec0325cb45..c2bc26f123 100644 --- a/tests/unit-mcdc/test_tls13_null_whitebox.c +++ b/tests/unit-mcdc/test_tls13_null_whitebox.c @@ -640,7 +640,8 @@ static void wbn_setup_ocsp_resp_guards(void) /* ------------------------------------------------------------------------- * * GROUP 7 -- BuildTls13Message()'s non-sizeOnly argument guard. * - * else if (output == NULL || input == NULL) [tls13.c ~:3345] + * if (sizeOnly) { if (output || input) ... } [tls13.c ~:3339] + * else if (output == NULL || input == NULL) [~:3345] * * BuildTls13Message is WOLFSSL_LOCAL with seventeen in-library call sites, and * every one of them that passes sizeOnly = 0 hands it a record buffer it has @@ -671,6 +672,7 @@ static void wbn_build_message_arg_guard(void) XMEMSET(out, 0, sizeof(out)); XMEMSET(in, 0, sizeof(in)); + /* :3345, the sizeOnly == 0 arm. */ /* (T,-) */ (void)BuildTls13Message(wbn_ssl, NULL, 0, in, 0, application_data, 0, 0, 0); /* (F,T) */ @@ -678,12 +680,24 @@ static void wbn_build_message_arg_guard(void) /* (F,F) -- stops at the `args->sz > outSz` check with BUFFER_E. */ (void)BuildTls13Message(wbn_ssl, out, 0, in, 0, application_data, 0, 0, 0); + /* :3339, the sizeOnly == 1 arm -- `if (output || input)`. Its (F,F) row is + * the ordinary size probe, which returns args->sz without writing + * anything; the two true rows are the mistaken-caller shapes the guard + * exists to catch. Driven here as well so this decision does not depend on + * a size-probe caller existing in some other test file. */ + /* (T,-) */ + (void)BuildTls13Message(wbn_ssl, out, 0, NULL, 0, application_data, 0, 1, 0); + /* (F,T) */ + (void)BuildTls13Message(wbn_ssl, NULL, 0, in, 0, application_data, 0, 1, 0); + /* (F,F) */ + (void)BuildTls13Message(wbn_ssl, NULL, 0, NULL, 0, application_data, 0, 1, 0); + /* The probe wrote ssl->options.buildMsgState; put it back so nothing * downstream inherits a half-built record state. */ wbn_ssl->options.buildMsgState = BUILD_MSG_BEGIN; - WB_NOTE("BuildTls13Message: output/input argument guard driven with all " - "three vectors"); + WB_NOTE("BuildTls13Message: both output/input argument guards driven with " + "all three vectors each"); } From 4d5c2fd2331cc1088328cb1bbf05b57439ea4d5e Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 16:29:21 +0200 Subject: [PATCH 35/57] tests: close thirty-two tls13.c feature-flag MC/DC conditions with negotiated-feature handshakes --- tests/api.c | 2 + tests/api/include.am | 2 + tests/api/test_tls13_features.c | 1240 +++++++++++++++++++++++++++++++ tests/api/test_tls13_features.h | 68 ++ 4 files changed, 1312 insertions(+) create mode 100644 tests/api/test_tls13_features.c create mode 100644 tests/api/test_tls13_features.h diff --git a/tests/api.c b/tests/api.c index 842f8b2575..0587653c4c 100644 --- a/tests/api.c +++ b/tests/api.c @@ -310,6 +310,7 @@ #include #include #include +#include #if !defined(NO_CERTS) && defined(WOLFSSL_ASN_TEMPLATE) && defined(HAVE_ECC) #include #endif @@ -40278,6 +40279,7 @@ TEST_CASE testCases[] = { TEST_TLS13_DECLS, TEST_TLS13_BOUNDS_DECLS, + TEST_TLS13_FEATURES_DECLS, TEST_DECL(test_wolfSSL_tmp_dh), TEST_DECL(test_wolfSSL_tmp_dh_regression), diff --git a/tests/api/include.am b/tests/api/include.am index 4966261035..b4610b1735 100644 --- a/tests/api/include.am +++ b/tests/api/include.am @@ -139,6 +139,7 @@ tests_unit_test_SOURCES += tests/api/test_certman.c # TLS 1.3 specific tests_unit_test_SOURCES += tests/api/test_tls13.c tests_unit_test_SOURCES += tests/api/test_tls13_bounds.c +tests_unit_test_SOURCES += tests/api/test_tls13_features.c endif EXTRA_DIST += tests/api/api.h @@ -258,4 +259,5 @@ EXTRA_DIST += tests/api/test_evp_pkey.h EXTRA_DIST += tests/api/test_certman.h EXTRA_DIST += tests/api/test_tls13.h EXTRA_DIST += tests/api/test_tls13_bounds.h +EXTRA_DIST += tests/api/test_tls13_features.h diff --git a/tests/api/test_tls13_features.c b/tests/api/test_tls13_features.c new file mode 100644 index 0000000000..fda8a54385 --- /dev/null +++ b/tests/api/test_tls13_features.c @@ -0,0 +1,1240 @@ +/* test_tls13_features.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* MC/DC vectors for src/tls13.c's FEATURE-FLAG decisions (ISO 26262 Part 7, + * Track B): the rows that test ssl->options.* state, extension presence and + * PSK / ticket / early-data negotiation results. + * + * An ssl->options.X operand that is stuck on one row usually means no test + * ever turns X on -- a fixture gap, not a hard condition. Each test here + * negotiates a feature combination the tls13 group did not previously + * negotiate at all, and is paired with the ordinary handshakes that group + * already runs so that both rows of the independence pair exist in the same + * unit.test binary. + * + * Everything is driven over the tests/utils.c memio transport. The only + * non-WOLFSSL_API symbol used is BuildTls13Message(), which is declared + * WOLFSSL_TEST_VIS (exported for tests) and is already used the same way by + * test_tls13_zero_inner_content_type(), so this file links in a shared build. + */ + +#include + +#ifdef NO_INLINE + #include +#else + #define WOLFSSL_MISC_INCLUDED + #include +#endif + +#include +#include +#include +#include +#include + +#if defined(WOLFSSL_TLS13) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + +/* ------------------------------------------------------------------------- + * Optional client authentication: the server asks for a certificate but does + * not insist on one, and the client has none configured. + * ---------------------------------------------------------------------- */ + +/* wolfSSL_accept_TLSv13() TLS13_ACCEPT_FINISHED_DONE: + * + * if (!resuming && verifyPeer && !verifyPostHandshake && + * !havePeerCert && !failNoCert) + * peerAuthGood = 1; + * + * needs a server that set WOLFSSL_VERIFY_PEER *without* + * WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT and a client that answers the + * CertificateRequest with an empty Certificate. The group's other + * client-auth handshakes all supply a certificate (havePeerCert = 1), so + * this decision had never been true. + * + * The same handshake leaves the server with + * msgsReceived.got_certificate = 1 and got_certificate_verify = 0, which is + * the state test_tls13_feat_post_handshake_unexpected_msg() below needs. */ +int test_tls13_feat_optional_client_cert(void) +{ + EXPECT_DECLS; +#if !defined(NO_CERTS) && !defined(NO_FILESYSTEM) && !defined(NO_RSA) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + + /* Request a client certificate but accept the handshake without one. */ + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_PEER, NULL); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* The client had no certificate to send, so the server saw an empty + * Certificate message and no CertificateVerify, and still accepted. */ + ExpectIntEQ(ssl_s->msgsReceived.got_certificate, 1); + ExpectIntEQ(ssl_s->msgsReceived.got_certificate_verify, 0); + ExpectIntEQ(ssl_s->options.havePeerCert, 0); + ExpectIntEQ(ssl_s->options.peerAuthGood, 1); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* ------------------------------------------------------------------------- + * A handshake message of a type that is not legal after the handshake. + * ---------------------------------------------------------------------- */ + +/* DoTls13HandShakeMsgType(): + * + * if (handShakeState == HANDSHAKE_DONE && + * type != session_ticket && type != certificate_request && + * type != certificate && type != key_update && type != finished + * && type != request_connection_id && type != new_connection_id) + * + * The group already produces every "false" row of this chain: a client + * receiving a post-handshake NewSessionTicket, a post-handshake auth + * CertificateRequest / Certificate / Finished, a KeyUpdate, and the DTLS + * connection-id messages. What no test ever produced is the row where the + * whole chain is TRUE -- a post-handshake handshake message of some other + * type -- so none of the eight operands had a pair. + * + * CertificateVerify is the type that reaches the check: on a server, + * SanityCheckTls13MsgReceived() lets it through once the server has sent + * its Finished, has seen a ClientHello and a Certificate, and has not yet + * seen a CertificateVerify. Optional client authentication (the test above) + * leaves exactly that state. + * + * The message is built with the client's own application-data keys via + * BuildTls13Message() and handed to the server with wolfSSL_inject(), so + * this is a real record on the real connection, not a poke at internals. */ +int test_tls13_feat_post_handshake_unexpected_msg(void) +{ + EXPECT_DECLS; +#if !defined(NO_CERTS) && !defined(NO_FILESYSTEM) && !defined(NO_RSA) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + WOLFSSL_ALERT_HISTORY h; + /* handshake header for a 4-byte CertificateVerify body, plus a body the + * server never gets as far as parsing */ + byte hsMsg[8] = { certificate_verify, 0x00, 0x00, 0x04, + 0x08, 0x04, 0x00, 0x00 }; + byte record[128]; + char readBuf[16]; + int recordSz = 0; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(&h, 0, sizeof(h)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + + /* Optional client auth: server sees an empty Certificate and no + * CertificateVerify, so a later CertificateVerify is not a duplicate. */ + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_PEER, NULL); + /* Keep the post-handshake flight to just our injected message. */ + ExpectIntEQ(wolfSSL_no_ticket_TLSv13(ssl_s), 0); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(ssl_s->options.handShakeState, HANDSHAKE_DONE); + ExpectIntEQ(ssl_s->msgsReceived.got_certificate, 1); + ExpectIntEQ(ssl_s->msgsReceived.got_certificate_verify, 0); + + if (EXPECT_SUCCESS()) { + recordSz = BuildTls13Message(ssl_c, record, (int)sizeof(record), hsMsg, + (int)sizeof(hsMsg), handshake, 0, 0, 0); + ExpectIntGT(recordSz, 0); + } + if (EXPECT_SUCCESS()) { + ExpectIntEQ(wolfSSL_inject(ssl_s, record, recordSz), + WOLFSSL_SUCCESS); + } + + /* RFC 8446 Section 4.6: an unexpected handshake message after the + * handshake is a fatal unexpected_message alert. */ + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, (int)sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WC_NO_ERR_TRACE(OUT_OF_ORDER_E)); + ExpectIntEQ(wolfSSL_get_alert_history(ssl_s, &h), WOLFSSL_SUCCESS); + ExpectIntEQ(h.last_tx.code, unexpected_message); + ExpectIntEQ(h.last_tx.level, alert_fatal); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* ------------------------------------------------------------------------- + * External PSK negotiated in psk_ke mode -- no (EC)DHE, no key_share. + * ---------------------------------------------------------------------- */ + +#if !defined(NO_PSK) + +static const byte test_tls13_feat_psk[] = { + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f +}; +static const char test_tls13_feat_psk_id[] = "feat_psk_client"; + +static unsigned int test_tls13_feat_psk_client_cb(WOLFSSL* ssl, + const char* hint, char* identity, unsigned int id_max_len, + unsigned char* key, unsigned int key_max_len) +{ + (void)ssl; + (void)hint; + if (id_max_len <= XSTRLEN(test_tls13_feat_psk_id) || + key_max_len < sizeof(test_tls13_feat_psk)) + return 0; + XSTRNCPY(identity, test_tls13_feat_psk_id, id_max_len); + XMEMCPY(key, test_tls13_feat_psk, sizeof(test_tls13_feat_psk)); + return (unsigned int)sizeof(test_tls13_feat_psk); +} + +static unsigned int test_tls13_feat_psk_server_cb(WOLFSSL* ssl, + const char* id, unsigned char* key, unsigned int key_max_len) +{ + (void)ssl; + if (id == NULL || key_max_len < sizeof(test_tls13_feat_psk)) + return 0; + if (XSTRCMP(id, test_tls13_feat_psk_id) != 0) + return 0; + XMEMCPY(key, test_tls13_feat_psk, sizeof(test_tls13_feat_psk)); + return (unsigned int)sizeof(test_tls13_feat_psk); +} + +#endif /* !NO_PSK */ + +/* SetupPskKey() and CheckPreSharedKeys() both branch on whether the peer + * offered psk_dhe_ke and on whether a key_share entry is present: + * + * if (((modes & (1 << PSK_DHE_KE)) != 0) && !noPskDheKe && + * kse != NULL && kse->derived) [SetupPskKey] + * if (((modes & (1 << PSK_DHE_KE)) != 0 && !noPskDheKe && ext != NULL) + * || usingCertWithExternPsk) [CheckPreSharedKeys] + * + * Every PSK handshake in the group runs psk_dhe_ke, so the kse == NULL / + * ext == NULL rows were unreachable. wolfSSL_no_dhe_psk() on both ends + * negotiates plain psk_ke: no key_share is offered and preMasterSz is + * zeroed. The group's existing psk_dhe_ke handshakes are the partner row. */ +int test_tls13_feat_psk_ke_no_dhe(void) +{ + EXPECT_DECLS; +#if !defined(NO_PSK) && defined(HAVE_SUPPORTED_CURVES) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + const char appMsg[] = "psk_ke"; + char readBuf[sizeof(appMsg)]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_NONE, NULL); + wolfSSL_set_psk_client_callback(ssl_c, test_tls13_feat_psk_client_cb); + wolfSSL_set_psk_server_callback(ssl_s, test_tls13_feat_psk_server_cb); + + /* psk_ke only: the ClientHello carries no key_share extension. */ + ExpectIntEQ(wolfSSL_no_dhe_psk(ssl_c), 0); + ExpectIntEQ(wolfSSL_no_dhe_psk(ssl_s), 0); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(ssl_c->options.pskNegotiated, 1); + ExpectIntEQ(ssl_s->options.pskNegotiated, 1); + ExpectIntEQ(ssl_c->options.noPskDheKe, 1); + ExpectIntEQ(ssl_s->options.noPskDheKe, 1); + + ExpectIntEQ(wolfSSL_write(ssl_c, appMsg, (int)XSTRLEN(appMsg)), + (int)XSTRLEN(appMsg)); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, (int)sizeof(readBuf)), + (int)XSTRLEN(appMsg)); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* The rejecting partner of the vector above, for the + * + * else if (onlyPskDheKe || (failNoPSK && !resumption)) + * + * arms of CheckPreSharedKeys() and SetupPskKey(): a server that insists on + * forward secrecy (wolfSSL_only_dhe_psk) facing a client that offers + * psk_ke only. onlyPskDheKe had never been set on a live handshake -- the + * group only exercised it through the argument-validation API test. */ +int test_tls13_feat_psk_only_dhe_rejects_psk_ke(void) +{ + EXPECT_DECLS; +#if !defined(NO_PSK) && defined(HAVE_SUPPORTED_CURVES) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_NONE, NULL); + wolfSSL_set_psk_client_callback(ssl_c, test_tls13_feat_psk_client_cb); + wolfSSL_set_psk_server_callback(ssl_s, test_tls13_feat_psk_server_cb); + + ExpectIntEQ(wolfSSL_no_dhe_psk(ssl_c), 0); + ExpectIntEQ(wolfSSL_only_dhe_psk(ssl_s), 0); + + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_get_error(ssl_s, WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), + WC_NO_ERR_TRACE(PSK_KEY_ERROR)); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* wolfSSL_accept_TLSv13() TLS13_ACCEPT_FINISHED_DONE: + * + * if (!noTicketTls13 && ctx->ticketEncCb != NULL) + * SendTls13NewSessionTicket(ssl); + * + * HAVE_SESSION_TICKET installs a default ticket encryption callback, so + * ticketEncCb is non-NULL on every handshake the group runs and operand 1 + * had no false row. Clearing the callback keeps tickets enabled + * (noTicketTls13 stays 0, so operand 0 is still true) but leaves the server + * with no way to protect one, and it silently sends none. The group's + * ordinary ticketed handshakes are the partner row. */ +int test_tls13_feat_no_ticket_enc_cb(void) +{ + EXPECT_DECLS; +#if defined(HAVE_SESSION_TICKET) && !defined(NO_CERTS) && \ + !defined(NO_FILESYSTEM) && !defined(NO_RSA) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + + /* Tickets stay enabled; there is just no callback to encrypt one. */ + ExpectIntEQ(wolfSSL_CTX_set_TicketEncCb(ctx_s, NULL), WOLFSSL_SUCCESS); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(ssl_s->options.noTicketTls13, 0); + ExpectNull(ctx_s->ticketEncCb); + ExpectIntEQ(ssl_c->msgsReceived.got_session_ticket, 0); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + + +/* ------------------------------------------------------------------------- + * A PSK ClientHello that offers psk_dhe_ke but carries no key_share entry. + * ---------------------------------------------------------------------- */ + +/* CheckPreSharedKeys(): + * + * if (((modes & (1 << PSK_DHE_KE)) != 0 && !noPskDheKe && ext != NULL) + * || usingCertWithExternPsk) + * + * and SetupPskKey(): + * + * if (((modes & (1 << PSK_DHE_KE)) != 0) && !noPskDheKe && + * kse != NULL && kse->derived) + * + * both need a client that advertises psk_dhe_ke with an EMPTY key_share + * list. wolfSSL_NoKeyShares() produces exactly that (it is how the group + * forces a HelloRetryRequest), but until now it was only ever combined with + * cert_with_extern_psk, which takes the other arm of the || and hides the + * ext == NULL row. Here it is combined with a plain external PSK, so the + * left conjunct is false while usingCertWithExternPsk is also false and the + * decision comes out false -- the partner of + * test_tls13_cert_with_extern_psk_requires_key_share(). */ +int test_tls13_feat_psk_ke_empty_key_share(void) +{ + EXPECT_DECLS; +#if !defined(NO_PSK) && defined(HAVE_SUPPORTED_CURVES) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_NONE, NULL); + wolfSSL_set_psk_client_callback(ssl_c, test_tls13_feat_psk_client_cb); + wolfSSL_set_psk_server_callback(ssl_s, test_tls13_feat_psk_server_cb); + + /* psk_dhe_ke is still offered; the key_share list is empty. */ + ExpectIntEQ(wolfSSL_NoKeyShares(ssl_c), WOLFSSL_SUCCESS); + + /* CH1 has no key share, so the server answers with a HelloRetryRequest + * and the PSK is only bound on CH2. */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + ExpectIntEQ(ssl_c->options.pskNegotiated, 1); + ExpectIntEQ(ssl_s->options.pskNegotiated, 1); + ExpectIntEQ(ssl_s->msgsReceived.got_client_hello, 2); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* CheckPreSharedKeys(), on the branch taken when the ClientHello has no + * pre_shared_key extension at all: + * + * if (ssl->options.havePSK && ssl->options.failNoPSK) + * return PSK_MISSING_ERROR; + * + * havePSK is only set by installing an external-PSK callback, and every + * handshake in the group that installs one also sends a PSK, so operand 1 + * was only ever evaluated with failNoPSK set (the + * test_tls13_fail_if_no_psk_* family). This is the same server -- external + * PSK callback installed, so havePSK is true -- but with failNoPSK left off + * and a client that offers no PSK, so the server falls back to certificate + * authentication instead of refusing. */ +int test_tls13_feat_optional_psk_falls_back_to_cert(void) +{ + EXPECT_DECLS; +#if !defined(NO_PSK) && !defined(NO_CERTS) && !defined(NO_FILESYSTEM) && \ + !defined(NO_RSA) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + + /* Server accepts an external PSK but does not require one. The client + * has no PSK callback, so its ClientHello carries no pre_shared_key. */ + wolfSSL_set_psk_server_callback(ssl_s, test_tls13_feat_psk_server_cb); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(ssl_s->options.havePSK, 1); + ExpectIntEQ(ssl_s->options.failNoPSK, 0); + ExpectIntEQ(ssl_s->options.pskNegotiated, 0); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* ------------------------------------------------------------------------- + * Post-handshake client authentication, run to completion, with the + * status_request extension held on the CTX. + * ---------------------------------------------------------------------- */ + +#if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && defined(HAVE_OCSP) +static int test_tls13_feat_ocsp_io_cb(void* ioCtx, const char* url, int urlSz, + unsigned char* req, int reqSz, unsigned char** resp) +{ + (void)ioCtx; + (void)url; + (void)urlSz; + (void)req; + (void)reqSz; + *resp = NULL; + return 0; +} + +static void test_tls13_feat_ocsp_free_cb(void* ioCtx, unsigned char* resp) +{ + (void)ioCtx; + (void)resp; +} +#endif + +/* SetupOcspResp(): + * + * if (extension == NULL && side == WOLFSSL_CLIENT_END && + * handShakeDone && + * TLSX_Find(ssl->ctx->extensions, TLSX_STATUS_REQUEST) != NULL) + * + * is the post-handshake-auth path where the client has to rebuild the + * status_request extension it offered in its ClientHello, because that + * extension no longer lives on ssl->extensions. The group's existing PHA + * stapling test offers status_request with wolfSSL_UseOCSPStapling() on the + * *SSL*, so ssl->ctx->extensions is empty, the last operand is false and + * the decision was never true -- leaving all four operands unpaired. + * + * This test offers it with wolfSSL_CTX_UseOCSPStapling() instead, so the + * extension is on the CTX and the rebuild actually happens, and then runs + * the post-handshake authentication all the way to the server accepting the + * client certificate. */ +int test_tls13_feat_pha_ctx_status_request(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_POST_HANDSHAKE_AUTH) && \ + defined(HAVE_CERTIFICATE_STATUS_REQUEST) && defined(HAVE_OCSP) && \ + defined(KEEP_PEER_CERT) && \ + !defined(NO_CERTS) && !defined(NO_FILESYSTEM) && !defined(NO_RSA) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + WOLFSSL_X509* peer = NULL; + const char msg[] = "ping"; + char buf[8]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + + /* --- Client CTX ------------------------------------------------ */ + ExpectNotNull(ctx_c = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_c, caCertFile, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx_c, cliCertFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx_c, cliKeyFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_allow_post_handshake_auth(ctx_c), 0); + ExpectIntEQ(wolfSSL_CTX_EnableOCSPStapling(ctx_c), WOLFSSL_SUCCESS); + /* The whole point of this vector: status_request is offered from the + * CTX, so ssl->ctx->extensions holds it and the post-handshake rebuild + * in SetupOcspResp() finds it. */ + ExpectIntEQ(wolfSSL_CTX_UseOCSPStapling(ctx_c, WOLFSSL_CSR_OCSP, 0), + WOLFSSL_SUCCESS); + wolfSSL_SetIORecv(ctx_c, test_memio_read_cb); + wolfSSL_SetIOSend(ctx_c, test_memio_write_cb); + + /* --- Server CTX ------------------------------------------------ */ + ExpectNotNull(ctx_s = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx_s, svrCertFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx_s, svrKeyFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, caCertFile, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, + "./certs/client-ca.pem", 0), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_EnableOCSPStapling(ctx_s), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_SetOCSP_Cb(ctx_s, test_tls13_feat_ocsp_io_cb, + test_tls13_feat_ocsp_free_cb, NULL), WOLFSSL_SUCCESS); + wolfSSL_CTX_set_verify(ctx_s, WOLFSSL_VERIFY_NONE, NULL); + wolfSSL_SetIORecv(ctx_s, test_memio_read_cb); + wolfSSL_SetIOSend(ctx_s, test_memio_write_cb); + + /* --- SSL objects ----------------------------------------------- */ + ExpectNotNull(ssl_c = wolfSSL_new(ctx_c)); + wolfSSL_SetIOReadCtx(ssl_c, &test_ctx); + wolfSSL_SetIOWriteCtx(ssl_c, &test_ctx); + ExpectNotNull(ssl_s = wolfSSL_new(ctx_s)); + wolfSSL_SetIOReadCtx(ssl_s, &test_ctx); + wolfSSL_SetIOWriteCtx(ssl_s, &test_ctx); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectNull(wolfSSL_get_peer_certificate(ssl_s)); + + /* Trigger post-handshake authentication. */ + if (EXPECT_SUCCESS()) { + wolfSSL_set_verify(ssl_s, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + ExpectIntEQ(wolfSSL_request_certificate(ssl_s), WOLFSSL_SUCCESS); + } + + ExpectIntEQ(wolfSSL_write(ssl_s, msg, (int)sizeof(msg) - 1), + (int)sizeof(msg) - 1); + ExpectIntEQ(wolfSSL_read(ssl_c, buf, sizeof(buf) - 1), + (int)sizeof(msg) - 1); + + /* The client's reply carries Certificate (with the rebuilt staple), + * CertificateVerify and Finished ahead of the application data. */ + ExpectIntEQ(wolfSSL_write(ssl_c, msg, (int)sizeof(msg) - 1), + (int)sizeof(msg) - 1); + ExpectIntEQ(wolfSSL_read(ssl_s, buf, sizeof(buf) - 1), + (int)sizeof(msg) - 1); + + ExpectNotNull(peer = wolfSSL_get_peer_certificate(ssl_s)); + wolfSSL_X509_free(peer); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* ------------------------------------------------------------------------- + * A server that answers an external PSK with psk_ke, so the client's own + * key share is never used. + * ---------------------------------------------------------------------- */ + +/* SetupPskKey() runs on the CLIENT while it processes the ServerHello: + * + * if (((modes & (1 << PSK_DHE_KE)) != 0) && !noPskDheKe && + * kse != NULL && kse->derived) + * else if (onlyPskDheKe || (failNoPSK && !psk->resumption)) + * + * Reaching the kse rows needs a client that offered psk_dhe_ke (so the + * first two operands are true) whose key share was nevertheless not used. + * Every PSK handshake in the group had both ends agree on psk_dhe_ke, so + * kse was always a derived entry. Here only the SERVER is restricted to + * psk_ke (wolfSSL_no_dhe_psk on the server), which is a configuration the + * group never built: the client still offers psk_dhe_ke and a key share, + * the server confirms psk_ke, and the client's key share stays underived. + * + * `variant` selects what the client offered: + * 0 - a real key share (kse != NULL, kse->derived == 0) + * 1 - an empty key_share list via wolfSSL_NoKeyShares (kse == NULL) */ +static int test_tls13_feat_psk_ke_server_side(int variant) +{ + EXPECT_DECLS; +#if !defined(NO_PSK) && defined(HAVE_SUPPORTED_CURVES) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_NONE, NULL); + wolfSSL_set_psk_client_callback(ssl_c, test_tls13_feat_psk_client_cb); + wolfSSL_set_psk_server_callback(ssl_s, test_tls13_feat_psk_server_cb); + + /* Only the server refuses (EC)DHE with a PSK. */ + ExpectIntEQ(wolfSSL_no_dhe_psk(ssl_s), 0); + if (variant == 1) + ExpectIntEQ(wolfSSL_NoKeyShares(ssl_c), WOLFSSL_SUCCESS); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(ssl_c->options.pskNegotiated, 1); + ExpectIntEQ(ssl_s->options.pskNegotiated, 1); + ExpectIntEQ(ssl_s->options.noPskDheKe, 1); + /* The client offered psk_dhe_ke; only the server said no. */ + ExpectIntEQ(ssl_c->options.onlyPskDheKe, 0); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#else + (void)variant; +#endif + return EXPECT_RESULT(); +} + +int test_tls13_feat_psk_ke_server_key_share_unused(void) +{ + return test_tls13_feat_psk_ke_server_side(0); +} + +int test_tls13_feat_psk_ke_server_no_key_share(void) +{ + return test_tls13_feat_psk_ke_server_side(1); +} + +/* The same else-if, one operand further along: + * + * else if (onlyPskDheKe || (failNoPSK && !psk->resumption)) + * + * test_tls13_fail_if_no_psk_client_requires_dhe() supplies the row where a + * client with a mandatory EXTERNAL PSK refuses a psk_ke ServerHello + * (failNoPSK true, !psk->resumption true, decision true). Its partner -- + * failNoPSK still true but the PSK being a session-ticket RESUMPTION, which + * RFC 8446 exempts -- never existed, because no test combined + * wolfSSL_require_psk() on the client with a resumed session. */ +int test_tls13_feat_psk_ke_client_require_psk_resumption(void) +{ + EXPECT_DECLS; +#if defined(HAVE_SESSION_TICKET) && !defined(NO_PSK) && \ + defined(HAVE_SUPPORTED_CURVES) && \ + !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + !defined(NO_CERTS) && !defined(NO_FILESYSTEM) && !defined(NO_RSA) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + WOLFSSL_SESSION* sess = NULL; + struct test_memio_ctx test_ctx; + byte readBuf[16]; + + /* First connection: mint a session ticket. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + /* Drain the post-handshake NewSessionTicket. */ + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(ssl_s); + ssl_s = NULL; + + /* Second connection: resume with psk_ke while the CLIENT insists on a + * PSK. The resumption exemption means this must succeed. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); + wolfSSL_require_psk(ssl_c); + ExpectIntEQ(wolfSSL_no_dhe_psk(ssl_c), 0); + ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + ExpectIntEQ(ssl_c->options.resuming, 1); + ExpectIntEQ(ssl_s->options.resuming, 1); + ExpectIntEQ(ssl_c->options.failNoPSK, 1); + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + + +/* ------------------------------------------------------------------------- + * A TLS 1.3 HelloRetryRequest carrying a stateless cookie, accepted. + * ---------------------------------------------------------------------- */ + +/* The tail of DoTls13ClientHello() is one block guarded by + * ssl->options.sendCookie, and inside it three decisions branch on + * ssl->options.cookieGood: + * + * if (cookieGood && acceptState == TLS13_ACCEPT_FIRST_REPLY_DONE) + * if (cookieGood && serverState == SERVER_HELLO_RETRY_REQUEST_COMPLETE) + * if (!cookieGood && serverState != SERVER_HELLO_RETRY_REQUEST_COMPLETE) + * + * wolfSSL_send_hrr_cookie() had only ever been called by the argument-check + * test and by test_tls13_hrr_bad_cookie(), which corrupts the cookie so + * cookieGood never becomes 1. No test in the group had ever completed a + * TLS 1.3 handshake through an accepted cookie, so all six operands sat on + * one row. This drives both ClientHellos: CH1 with no key share (cookieGood + * still 0, the server emits the HelloRetryRequest and the cookie) and CH2 + * echoing that cookie back (cookieGood 1). */ +static int test_tls13_feat_hrr_cookie_round(int emptyKeyShare) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_SEND_HRR_COOKIE) && defined(HAVE_SUPPORTED_CURVES) && \ + !defined(NO_CERTS) && !defined(NO_FILESYSTEM) && !defined(NO_RSA) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + + /* Stateless HelloRetryRequest: the server puts a cookie in the HRR and + * validates the echo in CH2. NULL/0 lets it generate its own secret. */ + ExpectIntEQ(wolfSSL_send_hrr_cookie(ssl_s, NULL, 0), WOLFSSL_SUCCESS); + /* emptyKeyShare == 1: CH1 carries an empty key_share list, so the server + * is already committed to a HelloRetryRequest by the time the cookie + * block runs (serverState == SERVER_HELLO_RETRY_REQUEST_COMPLETE). + * emptyKeyShare == 0: CH1 carries a usable key share, so serverState is + * still NULL_STATE there and it is the cookie alone that forces the + * retry -- that is the only way :8188's decision comes out true. */ + if (emptyKeyShare) + ExpectIntEQ(wolfSSL_NoKeyShares(ssl_c), WOLFSSL_SUCCESS); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + ExpectIntEQ(ssl_s->options.sendCookie, 1); + ExpectIntEQ(ssl_s->options.cookieGood, 1); + ExpectIntEQ(ssl_s->msgsReceived.got_client_hello, 2); + ExpectIntEQ(ssl_c->msgsReceived.got_hello_retry_request, 1); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#else + (void)emptyKeyShare; +#endif + return EXPECT_RESULT(); +} + +int test_tls13_feat_hrr_cookie_handshake(void) +{ + return test_tls13_feat_hrr_cookie_round(1); +} + +int test_tls13_feat_hrr_cookie_forces_retry(void) +{ + return test_tls13_feat_hrr_cookie_round(0); +} + +/* ------------------------------------------------------------------------- + * Encrypted ClientHello: the feature-flag operands next to the ECH pointers. + * ---------------------------------------------------------------------- */ + +#if defined(HAVE_ECH) && defined(HAVE_SNI) && !defined(NO_CERTS) && \ + !defined(NO_FILESYSTEM) && !defined(NO_RSA) + +/* An ECH handshake that also does client authentication and delivers a + * session ticket, so the ECH guards in DoTls13CertificateRequest() and + * DoTls13NewSessionTicket() -- which the group's ECH tests never reach, + * because they neither request a certificate nor read the post-handshake + * ticket -- are evaluated too. + * + * Every one of those guards has the shape + * + * if (ssl->echConfigs != NULL && !ssl->options.disableECH && ) + * + * and `echEnabled == 0` supplies the row the group never had: the ECH + * configuration is present (operand 0 true) but ECH is switched off at + * run time, so operand 1 is false. wolfSSL_SetEchEnable() had never been + * called from the tls13 group at all. `echEnabled == 1` is the partner row + * on the same code path. */ +static int test_tls13_feat_ech_round(int echEnabled) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + static const char pubName[] = "ech-public-name.com"; + static const char privName[] = "ech-private-name.com"; + byte configs[512]; + word32 configsLen = (word32)sizeof(configs); + byte readBuf[16]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); + + /* Client authentication, so the server sends a CertificateRequest. */ + ExpectIntEQ(wolfSSL_use_certificate_file(ssl_c, cliCertFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_use_PrivateKey_file(ssl_c, cliKeyFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, cliCertFile, NULL), + WOLFSSL_SUCCESS); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_PEER, NULL); + + ExpectIntEQ(wolfSSL_CTX_GenerateEchConfig(ctx_s, pubName, 0, 0, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_GetEchConfigs(ctx_s, configs, &configsLen), + WOLFSSL_SUCCESS); + /* wolfSSL_SetEchEnable(ssl, 0) frees any ECHConfig already installed, so + * the switch has to be thrown BEFORE the configs are handed over for the + * (echConfigs != NULL && disableECH) state to exist at all. That is the + * state every `echConfigs != NULL && !disableECH && ...` guard in + * src/tls13.c needs in order to evaluate its second operand false. */ + if (!echEnabled) + wolfSSL_SetEchEnable(ssl_c, 0); + ExpectIntEQ(wolfSSL_SetEchConfigs(ssl_c, configs, configsLen), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSNI(ssl_c, WOLFSSL_SNI_HOST_NAME, privName, + (word16)XSTRLEN(privName)), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSNI(ssl_s, WOLFSSL_SNI_HOST_NAME, privName, + (word16)XSTRLEN(privName)), WOLFSSL_SUCCESS); + + ExpectNotNull(ssl_c->echConfigs); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + ExpectIntEQ(ssl_c->options.disableECH, echEnabled ? 0 : 1); + ExpectIntEQ(wolfSSL_GetEchStatus(ssl_c), echEnabled ? + WOLFSSL_ECH_STATUS_ACCEPTED : WOLFSSL_ECH_STATUS_NOT_OFFERED); + ExpectIntEQ(ssl_c->msgsReceived.got_certificate_request, 1); + ExpectIntEQ(ssl_s->options.havePeerCert, 1); + + /* Drain the post-handshake NewSessionTicket so DoTls13NewSessionTicket() + * runs with the ECH state still attached. */ + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(ssl_c->msgsReceived.got_session_ticket, 1); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif /* HAVE_ECH && HAVE_SNI && certs */ + +int test_tls13_feat_ech_full_handshake(void) +{ +#if defined(HAVE_ECH) && defined(HAVE_SNI) && !defined(NO_CERTS) && \ + !defined(NO_FILESYSTEM) && !defined(NO_RSA) + return test_tls13_feat_ech_round(1); +#else + return TEST_SKIPPED; +#endif +} + +int test_tls13_feat_ech_disabled_client(void) +{ +#if defined(HAVE_ECH) && defined(HAVE_SNI) && !defined(NO_CERTS) && \ + !defined(NO_FILESYSTEM) && !defined(NO_RSA) + return test_tls13_feat_ech_round(0); +#else + return TEST_SKIPPED; +#endif +} + +/* The server side of the same switch: + * + * DoTls13ClientHello(): if (ssl->ctx->echConfigs != NULL && + * !ssl->options.disableECH) + * SendTls13ServerHello(): the same pair + * + * The server holds an ECHConfig (operand 0 true) but has ECH switched off, + * so it never opens the client's outer ClientHello and answers against the + * public name. RFC 9849 6.1.7 then makes the client abort with + * ECH_REQUIRED_E once it sees the retry configs. The group's existing + * ECH-rejection vector corrupts the config's public key instead, which + * leaves disableECH false on both ends. */ +int test_tls13_feat_ech_disabled_server(void) +{ + EXPECT_DECLS; +#if defined(HAVE_ECH) && defined(HAVE_SNI) && !defined(NO_CERTS) && \ + !defined(NO_FILESYSTEM) && !defined(NO_RSA) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + static const char pubName[] = "ech-public-name.com"; + static const char privName[] = "ech-private-name.com"; + byte configs[512]; + word32 configsLen = (word32)sizeof(configs); + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); + + /* Client authentication as well, so the ECH-rejected client still runs + * DoTls13CertificateRequest() -- whose ECH guard sends a blank + * Certificate per RFC 9849 6.1.7 and is only ever true on this path. */ + ExpectIntEQ(wolfSSL_use_certificate_file(ssl_c, cliCertFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_use_PrivateKey_file(ssl_c, cliKeyFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, cliCertFile, NULL), + WOLFSSL_SUCCESS); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_PEER, NULL); + + ExpectIntEQ(wolfSSL_CTX_GenerateEchConfig(ctx_s, pubName, 0, 0, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_GetEchConfigs(ctx_s, configs, &configsLen), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_SetEchConfigs(ssl_c, configs, configsLen), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSNI(ssl_c, WOLFSSL_SNI_HOST_NAME, privName, + (word16)XSTRLEN(privName)), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSNI(ssl_s, WOLFSSL_SNI_HOST_NAME, pubName, + (word16)XSTRLEN(pubName)), WOLFSSL_SUCCESS); + + /* Server keeps its ECHConfig but refuses to use it. */ + wolfSSL_SetEchEnable(ssl_s, 0); + + ExpectIntNE(wolfSSL_connect(ssl_c), WOLFSSL_SUCCESS); + ExpectIntNE(wolfSSL_accept(ssl_s), WOLFSSL_SUCCESS); + ExpectIntNE(wolfSSL_connect(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), + WC_NO_ERR_TRACE(ECH_REQUIRED_E)); + ExpectIntEQ(ssl_s->options.disableECH, 1); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + + +/* CheckPreSharedKeys(): + * + * if (((modes & (1 << PSK_DHE_KE)) != 0 && !noPskDheKe && ext != NULL) + * || usingCertWithExternPsk) + * + * Operand 3 is only ever *evaluated* when the left conjunct is false, and + * every cert_with_extern_psk handshake the group runs has it true (the + * client offers psk_dhe_ke, the server does not set noPskDheKe and a + * key_share is present), so the || short-circuits before reaching it. + * A server that refuses (EC)DHE with a PSK makes the second conjunct false + * and leaves RFC 9973's own arm to carry the decision. */ +int test_tls13_feat_cert_with_extern_psk_psk_ke_server(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_CERT_WITH_EXTERN_PSK) && !defined(NO_PSK) && \ + defined(HAVE_SUPPORTED_CURVES) && \ + !defined(NO_CERTS) && !defined(NO_FILESYSTEM) && !defined(NO_RSA) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_NONE, NULL); + wolfSSL_set_psk_client_callback(ssl_c, test_tls13_feat_psk_client_cb); + wolfSSL_set_psk_server_callback(ssl_s, test_tls13_feat_psk_server_cb); + ExpectIntEQ(wolfSSL_set_cert_with_extern_psk(ssl_c, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_cert_with_extern_psk(ssl_s, 1), WOLFSSL_SUCCESS); + /* The server refuses psk_dhe_ke, so the left conjunct of the decision + * is false; RFC 9973's own arm then carries it and the handshake still + * completes with an (EC)DHE key share, as RFC 9973 Section 3 requires. */ + ExpectIntEQ(wolfSSL_no_dhe_psk(ssl_s), 0); + + /* DoTls13ClientHello() clears options.noPskDheKe again once RFC 9973's + * arm has taken the decision, so only the negotiated result is asserted + * here. */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + ExpectIntEQ(ssl_s->options.certWithExternPsk, 1); + ExpectIntEQ(ssl_c->options.certWithExternPsk, 1); + ExpectIntEQ(ssl_c->options.pskNegotiated, 1); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + + +/* DoTls13ServerHello(), inside the branch taken when the server chose a PSK: + * + * if (ssl->echConfigs != NULL && !ssl->options.disableECH && + * !ssl->options.echAccepted) + * return INVALID_PARAMETER; ("ECH rejected but server negotiated PSK") + * + * needs the two features together -- an ECH client whose ECH was rejected AND + * a server that answers with a pre_shared_key. The group ran ECH handshakes + * and PSK handshakes but never one of each, so this decision had never been + * true and none of its three operands paired. Here the server holds an + * ECHConfig but has ECH switched off, so it answers the outer ClientHello and + * negotiates the external PSK; the client must refuse rather than resume + * against an unauthenticated outer handshake. */ +static int test_tls13_feat_ech_psk_round(int mode) +{ + EXPECT_DECLS; +#if defined(HAVE_ECH) && defined(HAVE_SNI) && !defined(NO_PSK) && \ + !defined(NO_CERTS) && !defined(NO_FILESYSTEM) && !defined(NO_RSA) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + static const char pubName[] = "ech-public-name.com"; + static const char privName[] = "ech-private-name.com"; + byte configs[512]; + word32 configsLen = (word32)sizeof(configs); + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_NONE, NULL); + wolfSSL_set_psk_client_callback(ssl_c, test_tls13_feat_psk_client_cb); + wolfSSL_set_psk_server_callback(ssl_s, test_tls13_feat_psk_server_cb); + + ExpectIntEQ(wolfSSL_CTX_GenerateEchConfig(ctx_s, pubName, 0, 0, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_GetEchConfigs(ctx_s, configs, &configsLen), + WOLFSSL_SUCCESS); + /* mode 1 disables ECH on the client before the configs are installed, so + * echConfigs stays non-NULL while disableECH is set; mode 2 leaves the + * client's ECH on and switches the server's off. (An ECH handshake that + * is ACCEPTED and also negotiates a PSK is not a case wolfSSL supports -- + * it fails with INVALID_PARAMETER at this very guard -- so the operand + * that would need it is left open rather than asserted here.) */ + if (mode == 1) + wolfSSL_SetEchEnable(ssl_c, 0); + ExpectIntEQ(wolfSSL_SetEchConfigs(ssl_c, configs, configsLen), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSNI(ssl_c, WOLFSSL_SNI_HOST_NAME, privName, + (word16)XSTRLEN(privName)), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSNI(ssl_s, WOLFSSL_SNI_HOST_NAME, + mode == 2 ? pubName : privName, + (word16)XSTRLEN(mode == 2 ? pubName : privName)), WOLFSSL_SUCCESS); + + /* mode 2: server has the ECHConfig but refuses to use it, so ECH is + * rejected and the decision above comes out true. */ + if (mode == 2) + wolfSSL_SetEchEnable(ssl_s, 0); + + if (mode == 2) { + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + ExpectIntEQ(ssl_c->options.echAccepted, 0); + } + else { + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + ExpectIntEQ(ssl_c->options.pskNegotiated, 1); + ExpectIntEQ(ssl_c->options.echAccepted, mode == 0 ? 1 : 0); + ExpectIntEQ(ssl_c->options.disableECH, mode == 1 ? 1 : 0); + } + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#else + (void)mode; +#endif + return EXPECT_RESULT(); +} + +int test_tls13_feat_ech_psk_disabled_client(void) +{ + return test_tls13_feat_ech_psk_round(1); +} + +int test_tls13_feat_ech_rejected_with_psk(void) +{ + return test_tls13_feat_ech_psk_round(2); +} + +#else /* !WOLFSSL_TLS13 || !HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES */ + +int test_tls13_feat_optional_client_cert(void) +{ + return TEST_SKIPPED; +} +int test_tls13_feat_post_handshake_unexpected_msg(void) +{ + return TEST_SKIPPED; +} +int test_tls13_feat_psk_ke_no_dhe(void) +{ + return TEST_SKIPPED; +} +int test_tls13_feat_psk_only_dhe_rejects_psk_ke(void) +{ + return TEST_SKIPPED; +} +int test_tls13_feat_no_ticket_enc_cb(void) +{ + return TEST_SKIPPED; +} +int test_tls13_feat_psk_ke_empty_key_share(void) +{ + return TEST_SKIPPED; +} +int test_tls13_feat_optional_psk_falls_back_to_cert(void) +{ + return TEST_SKIPPED; +} +int test_tls13_feat_pha_ctx_status_request(void) +{ + return TEST_SKIPPED; +} +int test_tls13_feat_psk_ke_server_key_share_unused(void) +{ + return TEST_SKIPPED; +} +int test_tls13_feat_psk_ke_server_no_key_share(void) +{ + return TEST_SKIPPED; +} +int test_tls13_feat_psk_ke_client_require_psk_resumption(void) +{ + return TEST_SKIPPED; +} +int test_tls13_feat_hrr_cookie_handshake(void) +{ + return TEST_SKIPPED; +} +int test_tls13_feat_hrr_cookie_forces_retry(void) +{ + return TEST_SKIPPED; +} +int test_tls13_feat_cert_with_extern_psk_psk_ke_server(void) +{ + return TEST_SKIPPED; +} +int test_tls13_feat_ech_full_handshake(void) +{ + return TEST_SKIPPED; +} +int test_tls13_feat_ech_disabled_client(void) +{ + return TEST_SKIPPED; +} +int test_tls13_feat_ech_disabled_server(void) +{ + return TEST_SKIPPED; +} +int test_tls13_feat_ech_rejected_with_psk(void) +{ + return TEST_SKIPPED; +} +int test_tls13_feat_ech_psk_disabled_client(void) +{ + return TEST_SKIPPED; +} + +#endif diff --git a/tests/api/test_tls13_features.h b/tests/api/test_tls13_features.h new file mode 100644 index 0000000000..c29db7704a --- /dev/null +++ b/tests/api/test_tls13_features.h @@ -0,0 +1,68 @@ +/* test_tls13_features.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifndef WOLFCRYPT_TEST_TLS13_FEATURES_H +#define WOLFCRYPT_TEST_TLS13_FEATURES_H + +#include + +int test_tls13_feat_optional_client_cert(void); +int test_tls13_feat_post_handshake_unexpected_msg(void); +int test_tls13_feat_psk_ke_no_dhe(void); +int test_tls13_feat_psk_only_dhe_rejects_psk_ke(void); +int test_tls13_feat_no_ticket_enc_cb(void); +int test_tls13_feat_psk_ke_empty_key_share(void); +int test_tls13_feat_optional_psk_falls_back_to_cert(void); +int test_tls13_feat_pha_ctx_status_request(void); +int test_tls13_feat_psk_ke_server_key_share_unused(void); +int test_tls13_feat_psk_ke_server_no_key_share(void); +int test_tls13_feat_psk_ke_client_require_psk_resumption(void); +int test_tls13_feat_hrr_cookie_handshake(void); +int test_tls13_feat_hrr_cookie_forces_retry(void); +int test_tls13_feat_cert_with_extern_psk_psk_ke_server(void); +int test_tls13_feat_ech_full_handshake(void); +int test_tls13_feat_ech_disabled_client(void); +int test_tls13_feat_ech_disabled_server(void); +int test_tls13_feat_ech_rejected_with_psk(void); +int test_tls13_feat_ech_psk_disabled_client(void); + +#define TEST_TLS13_FEATURES_DECLS \ + TEST_DECL_GROUP("tls13", test_tls13_feat_optional_client_cert), \ + TEST_DECL_GROUP("tls13", test_tls13_feat_post_handshake_unexpected_msg), \ + TEST_DECL_GROUP("tls13", test_tls13_feat_psk_ke_no_dhe), \ + TEST_DECL_GROUP("tls13", test_tls13_feat_psk_only_dhe_rejects_psk_ke), \ + TEST_DECL_GROUP("tls13", test_tls13_feat_no_ticket_enc_cb), \ + TEST_DECL_GROUP("tls13", test_tls13_feat_psk_ke_empty_key_share), \ + TEST_DECL_GROUP("tls13", test_tls13_feat_optional_psk_falls_back_to_cert), \ + TEST_DECL_GROUP("tls13", test_tls13_feat_pha_ctx_status_request), \ + TEST_DECL_GROUP("tls13", test_tls13_feat_psk_ke_server_key_share_unused), \ + TEST_DECL_GROUP("tls13", test_tls13_feat_psk_ke_server_no_key_share), \ + TEST_DECL_GROUP("tls13", test_tls13_feat_psk_ke_client_require_psk_resumption), \ + TEST_DECL_GROUP("tls13", test_tls13_feat_hrr_cookie_handshake), \ + TEST_DECL_GROUP("tls13", test_tls13_feat_hrr_cookie_forces_retry), \ + TEST_DECL_GROUP("tls13", test_tls13_feat_cert_with_extern_psk_psk_ke_server), \ + TEST_DECL_GROUP("tls13", test_tls13_feat_ech_full_handshake), \ + TEST_DECL_GROUP("tls13", test_tls13_feat_ech_disabled_client), \ + TEST_DECL_GROUP("tls13", test_tls13_feat_ech_disabled_server), \ + TEST_DECL_GROUP("tls13", test_tls13_feat_ech_psk_disabled_client), \ + TEST_DECL_GROUP("tls13", test_tls13_feat_ech_rejected_with_psk) + +#endif /* WOLFCRYPT_TEST_TLS13_FEATURES_H */ From 8e90e6ab05649c913e52b5831ae177a0dfaac55d Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 17:50:43 +0200 Subject: [PATCH 36/57] tests: drop external tooling references from MC/DC test comments --- tests/api/test_aes.c | 2 +- tests/api/test_chacha.c | 2 +- tests/api/test_dh.c | 2 +- tests/api/test_ecc.c | 64 +++++++++---------- tests/api/test_hash.c | 2 +- tests/api/test_hmac.c | 4 +- tests/api/test_kdf.c | 4 +- tests/api/test_mldsa.c | 2 +- tests/api/test_mlkem.c | 2 +- tests/api/test_poly1305.c | 2 +- tests/api/test_port.c | 2 +- tests/api/test_random.c | 6 +- tests/api/test_rsa.c | 2 +- tests/api/test_sakke.c | 2 +- tests/api/test_tls13.c | 2 +- tests/api/test_wolfmath.c | 12 ++-- tests/include.am | 2 +- tests/unit-mcdc/README.md | 18 +++--- tests/unit-mcdc/mcdc_der_edit.h | 2 +- tests/unit-mcdc/mcdc_fault_alloc.h | 4 +- tests/unit-mcdc/mcdc_fault_hash.h | 8 +-- tests/unit-mcdc/mcdc_fault_mp.h | 4 +- tests/unit-mcdc/mcdc_fault_mpint.h | 4 +- tests/unit-mcdc/mcdc_fault_mutex.h | 2 +- tests/unit-mcdc/mcdc_seed_rng.h | 8 +-- tests/unit-mcdc/test_aes_whitebox.c | 18 +++--- tests/unit-mcdc/test_asn_cert_whitebox.c | 6 +- tests/unit-mcdc/test_asn_certgen_whitebox.c | 8 +-- tests/unit-mcdc/test_asn_ext_whitebox.c | 14 ++-- tests/unit-mcdc/test_asn_fault_whitebox.c | 4 +- tests/unit-mcdc/test_asn_keys_whitebox.c | 4 +- .../unit-mcdc/test_asn_revocation_whitebox.c | 18 +++--- tests/unit-mcdc/test_asn_whitebox.c | 6 +- .../test_chacha20_poly1305_whitebox.c | 6 +- tests/unit-mcdc/test_chacha_whitebox.c | 10 +-- tests/unit-mcdc/test_cmac_whitebox.c | 2 +- tests/unit-mcdc/test_curve25519_whitebox.c | 18 +++--- tests/unit-mcdc/test_dh_fault_whitebox.c | 12 ++-- tests/unit-mcdc/test_dsa_fault_whitebox.c | 12 ++-- tests/unit-mcdc/test_ecc_fault_whitebox.c | 2 +- tests/unit-mcdc/test_ecc_whitebox.c | 26 ++++---- tests/unit-mcdc/test_eccsi_fault_whitebox.c | 4 +- .../test_ed25519_hash_fault_whitebox.c | 4 +- tests/unit-mcdc/test_ed25519_whitebox.c | 12 ++-- .../test_ed448_hash_fault_whitebox.c | 4 +- tests/unit-mcdc/test_ed448_whitebox.c | 10 +-- tests/unit-mcdc/test_falcon_whitebox.c | 8 +-- .../test_frodokem_cryptocb_whitebox.c | 6 +- tests/unit-mcdc/test_frodokem_fault_common.h | 2 +- .../test_frodokem_mat_fault_whitebox.c | 2 +- .../test_frodokem_mat_hash_fault_whitebox.c | 4 +- tests/unit-mcdc/test_hpke_fault_whitebox.c | 2 +- tests/unit-mcdc/test_integer_fault_whitebox.c | 2 +- tests/unit-mcdc/test_integer_whitebox.c | 8 +-- .../unit-mcdc/test_kdf_hash_fault_whitebox.c | 2 +- tests/unit-mcdc/test_kdf_whitebox.c | 4 +- tests/unit-mcdc/test_lms_bds_whitebox.c | 10 +-- tests/unit-mcdc/test_lms_fault_whitebox.c | 12 ++-- .../unit-mcdc/test_lms_hash_fault_whitebox.c | 12 ++-- tests/unit-mcdc/test_logging_whitebox.c | 4 +- tests/unit-mcdc/test_memory_whitebox.c | 2 +- tests/unit-mcdc/test_mldsa_fault_whitebox.c | 4 +- .../test_mldsa_hash_fault_whitebox.c | 8 +-- tests/unit-mcdc/test_mlkem_fault_whitebox.c | 6 +- .../test_mlkem_poly_hash_fault_whitebox.c | 8 +-- tests/unit-mcdc/test_pkcs12_fault_whitebox.c | 12 ++-- tests/unit-mcdc/test_pkcs12_parse_whitebox.c | 2 +- tests/unit-mcdc/test_pkcs12_whitebox.c | 12 ++-- tests/unit-mcdc/test_pkcs7_arg_whitebox.c | 7 +- tests/unit-mcdc/test_pkcs7_decode_whitebox.c | 10 +-- tests/unit-mcdc/test_pkcs7_fault_whitebox.c | 6 +- tests/unit-mcdc/test_pkcs7_mutate_whitebox.c | 2 +- tests/unit-mcdc/test_pkcs7_whitebox.c | 10 +-- tests/unit-mcdc/test_poly1305_whitebox.c | 10 +-- tests/unit-mcdc/test_pwdbased_whitebox.c | 2 +- tests/unit-mcdc/test_random_fault_whitebox.c | 4 +- tests/unit-mcdc/test_random_whitebox.c | 14 ++-- tests/unit-mcdc/test_rsa_fault_whitebox.c | 6 +- tests/unit-mcdc/test_rsa_whitebox.c | 8 +-- tests/unit-mcdc/test_sakke_fault_whitebox.c | 6 +- tests/unit-mcdc/test_sha256_whitebox.c | 2 +- tests/unit-mcdc/test_she_whitebox.c | 2 +- tests/unit-mcdc/test_signature_whitebox.c | 4 +- .../test_slhdsa_hash_fault_whitebox.c | 12 ++-- tests/unit-mcdc/test_slhdsa_whitebox.c | 10 +-- tests/unit-mcdc/test_sp_arm32_whitebox.c | 2 +- tests/unit-mcdc/test_sp_arm_fault_common.h | 6 +- tests/unit-mcdc/test_sp_armthumb_whitebox.c | 4 +- tests/unit-mcdc/test_sp_c32_whitebox.c | 4 +- .../test_sp_cortexm_crafted_whitebox.c | 2 +- tests/unit-mcdc/test_sp_cortexm_whitebox.c | 2 +- tests/unit-mcdc/test_sp_crafted_common.h | 8 +-- tests/unit-mcdc/test_sp_fault_common.h | 4 +- tests/unit-mcdc/test_sp_int_fault_whitebox.c | 6 +- tests/unit-mcdc/test_sp_int_whitebox.c | 14 ++-- tests/unit-mcdc/test_sp_x86_64_whitebox.c | 20 +++--- tests/unit-mcdc/test_srp_whitebox.c | 8 +-- tests/unit-mcdc/test_tfm_fault_whitebox.c | 6 +- tests/unit-mcdc/test_tfm_whitebox.c | 10 +-- tests/unit-mcdc/test_tls13_null_whitebox.c | 8 +-- tests/unit-mcdc/test_tls13_whitebox.c | 6 +- tests/unit-mcdc/test_tsp_fault_whitebox.c | 10 +-- tests/unit-mcdc/test_tsp_whitebox.c | 8 +-- tests/unit-mcdc/test_wc_encrypt_whitebox.c | 4 +- tests/unit-mcdc/test_wc_lms_impl_whitebox.c | 2 +- .../unit-mcdc/test_wc_lms_impl_whitebox_gap.c | 10 +-- tests/unit-mcdc/test_wc_mldsa_whitebox.c | 6 +- tests/unit-mcdc/test_wc_mlkem_poly_whitebox.c | 10 +-- tests/unit-mcdc/test_wc_port_whitebox.c | 4 +- tests/unit-mcdc/test_wc_xmss_impl_whitebox.c | 10 +-- tests/unit-mcdc/test_wolfentropy_whitebox.c | 8 +-- tests/unit-mcdc/test_wolfmath_whitebox.c | 10 +-- tests/unit-mcdc/test_xmss_fault_whitebox.c | 6 +- .../unit-mcdc/test_xmss_hash_fault_whitebox.c | 6 +- 114 files changed, 407 insertions(+), 408 deletions(-) diff --git a/tests/api/test_aes.c b/tests/api/test_aes.c index 5e0696715d..bdf7999a3b 100644 --- a/tests/api/test_aes.c +++ b/tests/api/test_aes.c @@ -8520,7 +8520,7 @@ int test_wc_AesFeatureCoverage(void) * GCM/GMAC block works on all of them, so it only excludes HAVE_SELFTEST; the * CCM block additionally excludes old FIPS (its AAD-only case diverges there, * see the per-block note); the key-wrap block excludes all FIPS + self-test. - * The open MC/DC campaign builds are unaffected. */ + * The open MC/DC builds are unaffected. */ #if !defined(NO_AES) && defined(HAVE_AESGCM) && !defined(HAVE_SELFTEST) /* ---- AES-GCM streaming API: multi-chunk AAD and data ---- */ /* Uses a hardcoded 256-bit key, so requires AES-256. */ diff --git a/tests/api/test_chacha.c b/tests/api/test_chacha.c index 80a8758c38..edc23843be 100644 --- a/tests/api/test_chacha.c +++ b/tests/api/test_chacha.c @@ -73,7 +73,7 @@ int test_wc_Chacha_SetKey(void) /* misaligned key pointer: exercises the (wc_ptr_t)key % 4 realignment * decision in wc_Chacha_SetKey when XSTREAM_ALIGN is forced on (the - * xstream_align campaign variant). settings.h compiles XSTREAM_ALIGN out + * xstream_align variant). settings.h compiles XSTREAM_ALIGN out * by default on x86_64/i386/ia64 (NO_XSTREAM_ALIGN), so this call is a * harmless no-op realignment-free copy on every other build. */ { diff --git a/tests/api/test_dh.c b/tests/api/test_dh.c index 15f5a09c0b..df5a4caec1 100644 --- a/tests/api/test_dh.c +++ b/tests/api/test_dh.c @@ -39,7 +39,7 @@ * DH_MAX_SIZE expands to WC_BITS_FULL_BYTES(SP_INT_BITS), and * WC_BITS_FULL_BYTES(x) is defined as (WC_BITS_TO_BYTES(x) << 3) - i.e. it * returns SP_INT_BITS itself (rounded up to a byte multiple), NOT - * SP_INT_BITS/8 as its name suggests. With this campaign's SP_INT_BITS 4096, + * SP_INT_BITS/8 as its name suggests. With this suite's SP_INT_BITS 4096, * DH_MAX_SIZE is therefore 4096 (bytes!), not the 512 a caller would * reasonably expect. Passing that value as *privSz (a requested private-key * size, not just a buffer capacity) to wc_DhGenerateKeyPair overflows the diff --git a/tests/api/test_ecc.c b/tests/api/test_ecc.c index 0550d9adc2..f462916253 100644 --- a/tests/api/test_ecc.c +++ b/tests/api/test_ecc.c @@ -2712,12 +2712,12 @@ int test_wc_EccPrivateKeyToDer(void) /* * MC/DC wave 1 - decision-targeted negative/edge paths for wolfcrypt/src/ * ecc.c that the existing (already extensive) API tests above do not drive. - * Each block cites the GAPS.md line:col:cond it targets. No library source + * Each block cites the the uncovered-condition report line:col:cond it targets. No library source * is changed; every case is reached through the public wc_ecc_* API. * * Split into several functions (test_wc_EccDecisionCoverage{,2,3,4}) rather * than one large one: a single function covering this many independent - * decisions produced a stack-corrupting crash under this campaign's + * decisions produced a stack-corrupting crash under this suite's * -fcoverage-mcdc + -O0 combination (reproduced with gdb: a plain on-stack * mp_int's used/size fields were already garbage immediately after its own * mp_init(), and clearing it then walked off the end of its dp[] array and @@ -2747,7 +2747,7 @@ int test_wc_EccDecisionCoverage(void) #endif ExpectIntEQ(ret, 0); - /* ---- wc_ecc_set_curve: GAPS.md 1927 ---- + /* ---- wc_ecc_set_curve: the uncovered-condition report 1927 ---- * if (key == NULL || (keysize <= 0 && curve_id < 0)) * key==NULL true side is already exercised elsewhere (BAD_FUNC_ARG on a * NULL key is a common pattern); complete the compound's other operand @@ -2763,7 +2763,7 @@ int test_wc_EccDecisionCoverage(void) ExpectIntEQ(wc_ecc_set_curve(&key, KEY32, -1), 0); #endif - /* ---- wc_ecc_get_curve_id: GAPS.md 4317 ---- + /* ---- wc_ecc_get_curve_id: the uncovered-condition report 4317 ---- * if (wc_ecc_is_valid_idx(curve_idx) && curve_idx >= 0) * curve_idx == -1 makes wc_ecc_is_valid_idx() true (ECC_CUSTOM_IDX is * a valid "user-supplied params" index) but curve_idx>=0 false: the @@ -2774,14 +2774,14 @@ int test_wc_EccDecisionCoverage(void) ExpectIntEQ(wc_ecc_get_curve_id(key.idx), ECC_SECP256R1); #endif - /* ---- wc_ecc_get_curve_params: GAPS.md 4654 ---- + /* ---- wc_ecc_get_curve_params: the uncovered-condition report 4654 ---- * if (curve_idx >= 0 && curve_idx < (int)ECC_SET_COUNT) * both boundary violations (negative, and >= COUNT) plus a valid idx. */ ExpectNull(wc_ecc_get_curve_params(-1)); ExpectNull(wc_ecc_get_curve_params(1000000)); ExpectNotNull(wc_ecc_get_curve_params(key.idx)); - /* ---- wc_ecc_point_is_at_infinity: GAPS.md 5320 ---- + /* ---- wc_ecc_point_is_at_infinity: the uncovered-condition report 5320 ---- * if (mp_iszero(p->x) && mp_iszero(p->y)) * Unique-cause MC/DC for a 2-operand AND needs THREE vectors within * this same binary: (T,T), (F,T), (T,F) (the existing pointFns test's @@ -2805,7 +2805,7 @@ int test_wc_EccDecisionCoverage(void) wc_ecc_del_point(inf); } - /* ---- wc_ecc_gen_k: GAPS.md 5335 ---- + /* ---- wc_ecc_gen_k: the uncovered-condition report 5335 ---- * if (rng==NULL || size<0 || size+8>ECC_MAXSIZE_GEN || k==NULL || * order==NULL) * Exercise each operand's TRUE side individually against an otherwise @@ -2832,7 +2832,7 @@ int test_wc_EccDecisionCoverage(void) } #endif - /* ---- wc_ecc_init_id: GAPS.md 6479, 6483 ---- + /* ---- wc_ecc_init_id: the uncovered-condition report 6479, 6483 ---- * if (ret == 0 && (len < 0 || len > ECC_MAX_ID_LEN)) -> BUFFER_E * if (ret == 0 && id != NULL && len != 0) -> copy branch * Exercise: len<0, len>MAX, id==NULL (len!=0 skipped), len==0 (id!=NULL @@ -2855,7 +2855,7 @@ int test_wc_EccDecisionCoverage(void) XMEMSET(&idKey, 0, sizeof(idKey)); ExpectIntEQ(wc_ecc_init_id(&idKey, NULL, 0, NULL, INVALID_DEVID), 0); wc_ecc_free(&idKey); - /* id != NULL, len == 0: GAPS.md 6483's 3rd operand (len != 0) + /* id != NULL, len == 0: the uncovered-condition report 6483's 3rd operand (len != 0) * independence pair -- id!=NULL fixed TRUE across this call and * the all-true "copy" call below, len toggled 0 vs nonzero. */ XMEMSET(&idKey, 0, sizeof(idKey)); @@ -2868,7 +2868,7 @@ int test_wc_EccDecisionCoverage(void) } #endif - /* ---- wc_ecc_init_label: GAPS.md 6503, 6507 ---- + /* ---- wc_ecc_init_label: the uncovered-condition report 6503, 6507 ---- * if (key == NULL || label == NULL) * if (labelLen == 0 || labelLen > ECC_MAX_LABEL_LEN) */ #ifdef WOLF_PRIVATE_KEY_ID @@ -2898,7 +2898,7 @@ int test_wc_EccDecisionCoverage(void) #endif #if defined(HAVE_ECC_SIGN) && !defined(NO_ASN) - /* ---- wc_ecc_sign_hash / wc_ecc_sign_hash_ex: GAPS.md 6909, 7443 ---- + /* ---- wc_ecc_sign_hash / wc_ecc_sign_hash_ex: the uncovered-condition report 6909, 7443 ---- * if ((inlen > WC_MAX_DIGEST_SIZE) || (inlen < WC_MIN_DIGEST_SIZE_FOR_SIGN)) * The signVerify_hash test above already shows the ">MAX" true side; * complete the other operand with a too-short digest. */ @@ -2919,7 +2919,7 @@ int test_wc_EccDecisionCoverage(void) #endif /* wc_ecc_sign_hash() has its OWN copy of this length check (it does * not delegate to wc_ecc_sign_hash_ex() before running it), so - * GAPS.md 7443 (wc_ecc_sign_hash_ex's identical check) needs a + * the uncovered-condition report 7443 (wc_ecc_sign_hash_ex's identical check) needs a * direct call in the SAME test binary to independently show its own * MC/DC pair -- llvm-cov computes independence per-binary, so * showing the FALSE side via signVerify_hash's normal-length call @@ -2947,7 +2947,7 @@ int test_wc_EccDecisionCoverage(void) #endif /* HAVE_ECC_SIGN && !NO_ASN */ #if defined(HAVE_ECC_VERIFY) && defined(WOLFSSL_PUBLIC_MP) - /* ---- wc_ecc_verify_hash_ex: GAPS.md 9476 ---- + /* ---- wc_ecc_verify_hash_ex: the uncovered-condition report 9476 ---- * Same reasoning as wc_ecc_sign_hash_ex above: wc_ecc_verify_hash() * does not delegate through this check, so it needs its own direct * short-hash call in this binary. */ @@ -2970,7 +2970,7 @@ int test_wc_EccDecisionCoverage(void) } #endif - /* ---- wc_ecc_free: GAPS.md 8209 ---- + /* ---- wc_ecc_free: the uncovered-condition report 8209 ---- * if (key->deallocSet && key->dp != NULL) * Exercise the "deallocSet but dp already NULL" and "dp set but * deallocSet false" independence halves via wc_ecc_set_custom_curve @@ -3025,7 +3025,7 @@ int test_wc_EccDecisionCoverage2(void) #if defined(HAVE_ECC_VERIFY) && !defined(WOLFSSL_SP_MATH) && \ defined(WOLFSSL_PUBLIC_MP) - /* ---- wc_ecc_check_r_s_range (via wc_ecc_verify_hash_ex): GAPS.md + /* ---- wc_ecc_check_r_s_range (via wc_ecc_verify_hash_ex): the uncovered-condition report * 8939, 8942 ---- * if ((err == 0) && (mp_cmp(r, curve->order) != MP_LT)) -> r >= order * if ((err == 0) && (mp_cmp(s, curve->order) != MP_LT)) -> s >= order @@ -3056,7 +3056,7 @@ int test_wc_EccDecisionCoverage2(void) #endif /* ---- wc_ecc_import_point_der_ex / wc_ecc_export_point_der{,_compressed}: - * GAPS.md 9710, 9964, 9970, 9975, 9984, 10030, 10037, 10042 ---- */ + * the uncovered-condition report 9710, 9964, 9970, 9975, 9984, 10030, 10037, 10042 ---- */ #if defined(HAVE_ECC_KEY_EXPORT) && defined(HAVE_ECC_KEY_IMPORT) { ecc_point* point = NULL; @@ -3102,10 +3102,10 @@ int test_wc_EccDecisionCoverage2(void) { /* wc_ecc_export_point_der_compressed is WOLFSSL_LOCAL (hidden in a * shared library), so it is not linkable from the shared-library - * unit test; its own decision coverage is driven by the campaign's + * unit test; its own decision coverage is driven by the * ecc white-box (which includes ecc.c directly). The public * compressed export path wc_ecc_export_x963_ex(..., 1) is exercised - * here (GAPS.md 16058, the static wc_ecc_export_x963_compressed + * here (the uncovered-condition report 16058, the static wc_ecc_export_x963_compressed * helper). */ #ifdef HAVE_ECC_KEY_EXPORT { @@ -3124,7 +3124,7 @@ int test_wc_EccDecisionCoverage2(void) } #endif /* HAVE_ECC_KEY_EXPORT && HAVE_ECC_KEY_IMPORT */ - /* ---- wc_ecc_is_point: GAPS.md 10304, 10329, 10332, 10390, 10396, + /* ---- wc_ecc_is_point: the uncovered-condition report 10304, 10329, 10332, 10390, 10396, * 10403 ---- * Direct call (rather than through wc_ecc_point_is_on_curve) with a * point that is genuinely ON the curve (the generator) and the @@ -3191,7 +3191,7 @@ int test_wc_EccDecisionCoverage3(void) ExpectIntEQ(ret, 0); /* ---- wc_ecc_export_public_raw / wc_ecc_export_private_raw: - * GAPS.md 11477, 11484, 11538, 11548 ---- */ + * the uncovered-condition report 11477, 11484, 11538, 11548 ---- */ #if defined(HAVE_ECC_KEY_EXPORT) { byte qx[MAX_ECC_BYTES], qy[MAX_ECC_BYTES], d[MAX_ECC_BYTES]; @@ -3207,12 +3207,12 @@ int test_wc_EccDecisionCoverage3(void) &qyLen), WC_NO_ERR_TRACE(ECC_BAD_ARG_E)); wc_ecc_free(&noDpKey); - /* d != NULL but dLen == NULL: GAPS.md 11484 first operand. */ + /* d != NULL but dLen == NULL: the uncovered-condition report 11484 first operand. */ qxLen = sizeof(qx); qyLen = sizeof(qy); ExpectIntEQ(wc_ecc_export_private_raw(&key, qx, &qxLen, qy, &qyLen, d, NULL), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); - /* d != NULL, dLen != NULL, but key type is public-only: GAPS.md + /* d != NULL, dLen != NULL, but key type is public-only: the uncovered-condition report * 11484 second operand. */ { ecc_key pubOnly; @@ -3229,13 +3229,13 @@ int test_wc_EccDecisionCoverage3(void) ExpectIntEQ(wc_ecc_export_private_raw(&pubOnly, NULL, NULL, NULL, NULL, d, &dLen), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); - /* qx != NULL, qxLen == NULL: GAPS.md 11538 first operand. */ + /* qx != NULL, qxLen == NULL: the uncovered-condition report 11538 first operand. */ ExpectIntEQ(wc_ecc_export_private_raw(&key, qx, NULL, NULL, NULL, NULL, NULL), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); - /* qy != NULL, qyLen == NULL: GAPS.md 11548 first operand. */ + /* qy != NULL, qyLen == NULL: the uncovered-condition report 11548 first operand. */ ExpectIntEQ(wc_ecc_export_private_raw(&key, NULL, NULL, qy, NULL, NULL, NULL), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); - /* qx != NULL against a PRIVATEKEY_ONLY key: GAPS.md 11538 + /* qx != NULL against a PRIVATEKEY_ONLY key: the uncovered-condition report 11538 * second operand (type == ECC_PRIVATEKEY_ONLY). */ pubOnly.type = ECC_PRIVATEKEY_ONLY; qxbLen = sizeof(qxb); @@ -3250,7 +3250,7 @@ int test_wc_EccDecisionCoverage3(void) } #endif /* HAVE_ECC_KEY_EXPORT */ - /* ---- wc_ecc_rs_raw_to_sig: GAPS.md 12015 ---- */ + /* ---- wc_ecc_rs_raw_to_sig: the uncovered-condition report 12015 ---- */ { byte r[KEY32], s[KEY32], sig[ECC_MAX_SIG_SIZE]; word32 sigLen = sizeof(sig); @@ -3269,7 +3269,7 @@ int test_wc_EccDecisionCoverage3(void) NULL), WC_NO_ERR_TRACE(ECC_BAD_ARG_E)); } - /* ---- wc_ecc_import_private_key_ex: GAPS.md 11671 (_ecc_import_ + /* ---- wc_ecc_import_private_key_ex: the uncovered-condition report 11671 (_ecc_import_ * private_key_ex key==NULL||priv==NULL, reached via the public * wrapper's own identical pre-check, same independence pair) ---- */ #if defined(HAVE_ECC_KEY_IMPORT) @@ -3313,7 +3313,7 @@ int test_wc_EccDecisionCoverage4(void) #endif ExpectIntEQ(ret, 0); - /* ---- ecc_mul2add argument guard: GAPS.md 8446 ---- + /* ---- ecc_mul2add argument guard: the uncovered-condition report 8446 ---- * NOT closeable by any current variant, API or white-box: both bodies * of ecc_mul2add() (the argument-checked "normal" one at line ~8417 and * the Shamir/fixed-point-cache one at line ~13909 that supersedes it @@ -3324,13 +3324,13 @@ int test_wc_EccDecisionCoverage4(void) * exercises the unchecked Shamir body under the name ecc_mul2add) or * turns BOTH ECC_SHAMIR and FP_ECC OFF together (no_fp_shamir, per its * config_base's philosophy of flipping the FALSE side of both feature - * guards at once -- see modules.json's ecc notes), which compiles + * guards at once -- see the module registry's ecc notes), which compiles * *neither* body, making ecc_mul2add an undefined symbol there (link * failure, confirmed empirically). Reaching this decision needs a new, * not-yet-scaffolded variant: ECC_SHAMIR on + FP_ECC off. Classified as * a needs-variant residual; see RESIDUALS.md. */ - /* ---- wc_ecc_ctx_set_kdf_salt: GAPS.md 14607 ---- + /* ---- wc_ecc_ctx_set_kdf_salt: the uncovered-condition report 14607 ---- * if (ctx == NULL || (salt == NULL && sz != 0)) * ctx==NULL already the common BAD_FUNC_ARG idiom shown elsewhere; add * the salt==NULL/sz!=0 half here with a live ctx. */ @@ -3347,7 +3347,7 @@ int test_wc_EccDecisionCoverage4(void) } #endif - /* ---- wc_ecc_set_custom_curve: GAPS.md 16181 ---- */ + /* ---- wc_ecc_set_custom_curve: the uncovered-condition report 16181 ---- */ #if defined(WOLFSSL_CUSTOM_CURVES) { ecc_key ccKey2; @@ -3361,7 +3361,7 @@ int test_wc_EccDecisionCoverage4(void) } #endif - /* ---- wc_X963_KDF: GAPS.md 16217, 16221 ---- */ + /* ---- wc_X963_KDF: the uncovered-condition report 16217, 16221 ---- */ #ifdef HAVE_X963_KDF { byte secret[16]; diff --git a/tests/api/test_hash.c b/tests/api/test_hash.c index 3fc90da481..a3a4e7b2e5 100644 --- a/tests/api/test_hash.c +++ b/tests/api/test_hash.c @@ -999,7 +999,7 @@ int test_wc_HashDecisionCoverage(void) * these types, so hash->type is never one of them and the debug check * always intercepts here, making the arm unreachable in DEBUG builds * (non-DEBUG builds cover it). Not a value workaround -- the arm's - * coverage simply comes from non-DEBUG variants in the campaign union. */ + * coverage simply comes from non-DEBUG variants in the harness union. */ #ifndef DEBUG_WOLFSSL ExpectIntEQ(wc_HashUpdate(&hash, WC_HASH_TYPE_MD5_SHA, (byte*)"a", 1), WC_NO_ERR_TRACE(HASH_TYPE_E)); diff --git a/tests/api/test_hmac.c b/tests/api/test_hmac.c index 28e4a16697..fa84e01ac8 100644 --- a/tests/api/test_hmac.c +++ b/tests/api/test_hmac.c @@ -883,7 +883,7 @@ int test_wc_HmacSizeByType(void) /* The FIPS/self-test hmac's wc_HmacSizeByType returns HMAC_KAT_FIPS_E for any * type it doesn't accept (e.g. MD5 is not a FIPS HMAC type), and BAD_FUNC_ARG * differs too, so the size/invalid-type assertions here only hold on the open - * builds the campaign actually measures. Exclude the frozen modules whole. */ + * builds the harness actually measures. Exclude the frozen modules whole. */ #if !defined(NO_HMAC) && !defined(HAVE_SELFTEST) && !defined(HAVE_FIPS) #ifndef NO_MD5 ExpectIntEQ(wc_HmacSizeByType(WC_MD5), WC_MD5_DIGEST_SIZE); @@ -941,7 +941,7 @@ int test_wc_HmacCopy(void) /* wc_HmacCopy() is newer than the frozen FIPS/selftest wolfcrypt modules * (absent from the v4.1.0-stable hmac.h that cavp-selftest-v2 pins, and from * every frozen FIPS bundle), so skip it there to keep those builds warning- - * clean; the campaign measures MC/DC on non-FIPS variants regardless. */ + * clean; the harness measures MC/DC on non-FIPS variants regardless. */ #if !defined(NO_HMAC) && !defined(NO_SHA256) && \ !defined(HAVE_SELFTEST) && !defined(HAVE_FIPS) Hmac src; diff --git a/tests/api/test_kdf.c b/tests/api/test_kdf.c index c0f6dadb72..68a09be92d 100644 --- a/tests/api/test_kdf.c +++ b/tests/api/test_kdf.c @@ -99,7 +99,7 @@ int test_wc_KdfDecisionCoverage(void) * 14-byte HMAC_FIPS_MIN_KEY (returns HMAC_MIN_KEYLEN_E for the short keys * these probes use), the MD5/TLS1.0 PRF is disallowed, and several argument * checks differ, so these OPEN-build MC/DC assertions do not hold. kdf.c - * coverage is measured only in non-FIPS campaign variants, so skip the whole + * coverage is measured only in non-FIPS variants, so skip the whole * suite under FIPS/selftest. */ #if !defined(HAVE_FIPS) && !defined(HAVE_SELFTEST) #ifndef NO_KDF @@ -670,7 +670,7 @@ int test_wc_KdfFeatureCoverage(void) /* See test_wc_KdfDecisionCoverage: FIPS/selftest builds diverge on KDF * behavior (14-byte minimum HMAC key, MD5/TLS1.0 PRF disallowed, differing * argument checks), so skip this OPEN-build positive-path suite there; - * kdf.c is measured only in non-FIPS campaign variants. */ + * kdf.c is measured only in non-FIPS variants. */ #if !defined(HAVE_FIPS) && !defined(HAVE_SELFTEST) #ifndef NO_KDF diff --git a/tests/api/test_mldsa.c b/tests/api/test_mldsa.c index c7abe5f6dc..1b4016564c 100644 --- a/tests/api/test_mldsa.c +++ b/tests/api/test_mldsa.c @@ -30620,7 +30620,7 @@ int test_dilithium_hash(void) } /* ===================================================================== - * MC/DC coverage supplements (ISO 26262 per-module campaign). + * MC/DC coverage supplements (per-module MC/DC suite). * * test_wc_MldsaDecisionCoverage: one negative call per public-entry * argument / state check, each asserting the specific error, driving both diff --git a/tests/api/test_mlkem.c b/tests/api/test_mlkem.c index 759f25f644..a0403fd354 100644 --- a/tests/api/test_mlkem.c +++ b/tests/api/test_mlkem.c @@ -4089,7 +4089,7 @@ int test_wc_mlkem_decode_privkey_bad_pubhash(void) /****************************************************************************** - * MC/DC coverage supplements (ISO 26262 per-module campaign). + * MC/DC coverage supplements (per-module MC/DC suite). * * test_wc_MlkemDecisionCoverage: one negative call per argument-check / * short-buffer / key-state branch in the public ML-KEM API, each asserting the diff --git a/tests/api/test_poly1305.c b/tests/api/test_poly1305.c index a71de52417..3a4e6f0dd2 100644 --- a/tests/api/test_poly1305.c +++ b/tests/api/test_poly1305.c @@ -277,7 +277,7 @@ int test_wc_Poly1305_PadEncodeSizes(void) * of the (paddingLen > 0) decision. (paddingLen < WC_POLY1305_PAD_SZ is * structurally always true whenever paddingLen > 0 is true -- the mod-16 * formula bounds paddingLen to 0..15 -- so that operand's FALSE side is - * an unsatisfiable residual; see campaign RESIDUALS notes.) */ + * an unsatisfiable residual; an argued residual.) */ ExpectIntEQ(wc_Poly1305SetKey(&ctx, key, sizeof(key)), 0); ExpectIntEQ(wc_Poly1305_Pad(&ctx, 16), 0); diff --git a/tests/api/test_port.c b/tests/api/test_port.c index 9c1d13ba19..66e3c76f12 100644 --- a/tests/api/test_port.c +++ b/tests/api/test_port.c @@ -34,7 +34,7 @@ * Residuals left uncovered on purpose: wc_open_cloexec / wc_accept_cloexec's * "fd < 0 && errno == EINVAL" and "errno != ENOSYS && errno != EINVAL" arms are * the fallback for kernels without O_CLOEXEC / SOCK_CLOEXEC, unreachable on any - * host the campaign runs on. + * host the harness runs on. */ #ifndef SINGLE_THREADED diff --git a/tests/api/test_random.c b/tests/api/test_random.c index a4c3732050..b929cd9357 100644 --- a/tests/api/test_random.c +++ b/tests/api/test_random.c @@ -297,7 +297,7 @@ int test_wc_GenerateSeed(void) /* Different configurations have different paths and different errors or * no error at all. */ #ifdef TEST_WC_GENERATE_SEED_PARAMS - /* NOTE (GAPS.md residual, line ~5525 "os == NULL || output == NULL"): + /* NOTE (the uncovered-condition report residual, line ~5525 "os == NULL || output == NULL"): * TEST_WC_GENERATE_SEED_PARAMS is not defined by any variant in * configs/random/ today. Its header comment cites a real historical * bug -- the generic Linux getrandom()/dev-urandom wc_GenerateSeed() @@ -312,7 +312,7 @@ int test_wc_GenerateSeed(void) * configs/random/user_settings.base.h (none of this module's variants * select a different OS/HW entropy backend) would safely close this * residual; left undefined here since gap-closing tasks don't modify - * the shared campaign config headers -- flagged for the orchestrator. */ + * the shared suite config headers -- flagged for the orchestrator. */ /* Bad parameters. */ ExpectIntEQ(wc_GenerateSeed(NULL, NULL , 16), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); @@ -1088,7 +1088,7 @@ int test_wc_RNG_SeedCb(void) * skipped, since it is guarded by * "defined(HAVE_HASHDRBG) && !defined(CUSTOM_RAND_GENERATE_BLOCK)"). Not * gated on HAVE_HASHDRBG since this path is intentionally independent of - * it -- see configs/random/user_settings.custom_rand.h in the campaign for + * it -- see configs/random/user_settings.custom_rand.h in the harness for * why forcing both together is unsafe. */ int test_wc_RNG_CustomRandBlock(void) { diff --git a/tests/api/test_rsa.c b/tests/api/test_rsa.c index fa130e5bb4..ed930e74ec 100644 --- a/tests/api/test_rsa.c +++ b/tests/api/test_rsa.c @@ -1429,7 +1429,7 @@ int test_wc_RsaDecisionCoverage(void) * differ. The sibling key-gen/decision tests in this file (e.g. * test_wc_CheckProbablePrime, the RsaKeyGeneration group) exclude HAVE_SELFTEST * for the same reason; do so here too. HAVE_FIPS is intentionally left running: - * that (newer) module honours these decisions and the campaign gains coverage + * that (newer) module honours these decisions and the harness gains coverage * from it. */ #if !defined(NO_RSA) && defined(WOLFSSL_KEY_GEN) && \ !defined(WOLFSSL_RSA_PUBLIC_ONLY) && !defined(HAVE_SELFTEST) diff --git a/tests/api/test_sakke.c b/tests/api/test_sakke.c index 78d4a8d823..332c34100f 100644 --- a/tests/api/test_sakke.c +++ b/tests/api/test_sakke.c @@ -119,7 +119,7 @@ * wc_DeriveSakkeSSV()) first calls sakke_calc_a(), which calls * wc_HashInit_ex() -- itself rejecting an invalid/unsupported hashType with * BAD_FUNC_ARG/HASH_TYPE_E before sakke_hash_to_range() is ever reached. - * This is flagged for the DEATHNOTE rather than forced here; the tests below + * This is not forced here; the tests below * instead drive the reachable hashType-invalid path through * wc_HashInit_ex(), which is the only way sakke.c's hashType parameter can * be shown to gate an error from the public API. diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index 3e2b0402e3..179f08672c 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -620,7 +620,7 @@ int test_tls13_apis(void) * hash == NULL || ssl == NULL || (ssl->suites == NULL && ssl->ctx == NULL) * Only the first two operands are reachable: ssl->ctx is invariant non-NULL * for a live WOLFSSL, so the parenthesised sub-expression is dead and both - * of its operands are recorded in campaign/db/exclusions.json. */ + * of its operands are recorded in the exclusion record. */ #ifndef NO_PSK #ifndef NO_WOLFSSL_CLIENT ExpectNull(wolfSSL_get_cipher_name_by_hash(clientSsl, NULL)); diff --git a/tests/api/test_wolfmath.c b/tests/api/test_wolfmath.c index 49ea7f8f07..4a0b65bef6 100644 --- a/tests/api/test_wolfmath.c +++ b/tests/api/test_wolfmath.c @@ -247,7 +247,7 @@ int test_wc_SpIntSizeDecisionCoverage(void) * (sp_div_2d/sp_mod_2d/sp_mul_2d/sp_tohex/sp_exch/sp_2expt/sp_exptmod_ex) need * WOLFSSL_SP_MATH_ALL && !WOLFSSL_RSA_VERIFY_ONLY, the ct helpers * (sp_addmod_ct/sp_submod_ct/sp_div_2_mod_ct/sp_div_2) need HAVE_ECC, and - * sp_gcd needs !NO_RSA && WOLFSSL_KEY_GEN. This condition (which the campaign + * sp_gcd needs !NO_RSA && WOLFSSL_KEY_GEN. This condition (which the harness * sp-math config satisfies) guarantees every helper is compiled. */ #if defined(WOLFSSL_SP_MATH_ALL) && defined(WOLFSSL_PUBLIC_MP) && \ !defined(WOLFSSL_RSA_VERIFY_ONLY) && !defined(NO_RSA) && \ @@ -330,7 +330,7 @@ int test_wc_SpIntShiftDecisionCoverage(void) * (sp_div_2d/sp_mod_2d/sp_mul_2d/sp_tohex/sp_exch/sp_2expt/sp_exptmod_ex) need * WOLFSSL_SP_MATH_ALL && !WOLFSSL_RSA_VERIFY_ONLY, the ct helpers * (sp_addmod_ct/sp_submod_ct/sp_div_2_mod_ct/sp_div_2) need HAVE_ECC, and - * sp_gcd needs !NO_RSA && WOLFSSL_KEY_GEN. This condition (which the campaign + * sp_gcd needs !NO_RSA && WOLFSSL_KEY_GEN. This condition (which the harness * sp-math config satisfies) guarantees every helper is compiled. */ #if defined(WOLFSSL_SP_MATH_ALL) && defined(WOLFSSL_PUBLIC_MP) && \ !defined(WOLFSSL_RSA_VERIFY_ONLY) && !defined(NO_RSA) && \ @@ -392,7 +392,7 @@ int test_wc_SpIntDigitArithDecisionCoverage(void) * (sp_div_2d/sp_mod_2d/sp_mul_2d/sp_tohex/sp_exch/sp_2expt/sp_exptmod_ex) need * WOLFSSL_SP_MATH_ALL && !WOLFSSL_RSA_VERIFY_ONLY, the ct helpers * (sp_addmod_ct/sp_submod_ct/sp_div_2_mod_ct/sp_div_2) need HAVE_ECC, and - * sp_gcd needs !NO_RSA && WOLFSSL_KEY_GEN. This condition (which the campaign + * sp_gcd needs !NO_RSA && WOLFSSL_KEY_GEN. This condition (which the harness * sp-math config satisfies) guarantees every helper is compiled. */ #if defined(WOLFSSL_SP_MATH_ALL) && defined(WOLFSSL_PUBLIC_MP) && \ !defined(WOLFSSL_RSA_VERIFY_ONLY) && !defined(NO_RSA) && \ @@ -543,7 +543,7 @@ int test_wc_SpIntArithDecisionCoverage(void) * (sp_div_2d/sp_mod_2d/sp_mul_2d/sp_tohex/sp_exch/sp_2expt/sp_exptmod_ex) need * WOLFSSL_SP_MATH_ALL && !WOLFSSL_RSA_VERIFY_ONLY, the ct helpers * (sp_addmod_ct/sp_submod_ct/sp_div_2_mod_ct/sp_div_2) need HAVE_ECC, and - * sp_gcd needs !NO_RSA && WOLFSSL_KEY_GEN. This condition (which the campaign + * sp_gcd needs !NO_RSA && WOLFSSL_KEY_GEN. This condition (which the harness * sp-math config satisfies) guarantees every helper is compiled. */ #if defined(WOLFSSL_SP_MATH_ALL) && defined(WOLFSSL_PUBLIC_MP) && \ !defined(WOLFSSL_RSA_VERIFY_ONLY) && !defined(NO_RSA) && \ @@ -674,7 +674,7 @@ int test_wc_SpIntConvDecisionCoverage(void) * (sp_div_2d/sp_mod_2d/sp_mul_2d/sp_tohex/sp_exch/sp_2expt/sp_exptmod_ex) need * WOLFSSL_SP_MATH_ALL && !WOLFSSL_RSA_VERIFY_ONLY, the ct helpers * (sp_addmod_ct/sp_submod_ct/sp_div_2_mod_ct/sp_div_2) need HAVE_ECC, and - * sp_gcd needs !NO_RSA && WOLFSSL_KEY_GEN. This condition (which the campaign + * sp_gcd needs !NO_RSA && WOLFSSL_KEY_GEN. This condition (which the harness * sp-math config satisfies) guarantees every helper is compiled. */ #if defined(WOLFSSL_SP_MATH_ALL) && defined(WOLFSSL_PUBLIC_MP) && \ !defined(WOLFSSL_RSA_VERIFY_ONLY) && !defined(NO_RSA) && \ @@ -861,7 +861,7 @@ int test_wc_SpIntExptGcdDecisionCoverage(void) * (sp_div_2d/sp_mod_2d/sp_mul_2d/sp_tohex/sp_exch/sp_2expt/sp_exptmod_ex) need * WOLFSSL_SP_MATH_ALL && !WOLFSSL_RSA_VERIFY_ONLY, the ct helpers * (sp_addmod_ct/sp_submod_ct/sp_div_2_mod_ct/sp_div_2) need HAVE_ECC, and - * sp_gcd needs !NO_RSA && WOLFSSL_KEY_GEN. This condition (which the campaign + * sp_gcd needs !NO_RSA && WOLFSSL_KEY_GEN. This condition (which the harness * sp-math config satisfies) guarantees every helper is compiled. */ #if defined(WOLFSSL_SP_MATH_ALL) && defined(WOLFSSL_PUBLIC_MP) && \ !defined(WOLFSSL_RSA_VERIFY_ONLY) && !defined(NO_RSA) && \ diff --git a/tests/include.am b/tests/include.am index 35b034c361..c19499a3f6 100644 --- a/tests/include.am +++ b/tests/include.am @@ -114,7 +114,7 @@ DISTCLEANFILES+= tests/.libs/unit.test # directly and defines its own main(), so it cannot be a unit.test source (that # would duplicate main() and the included .c's symbols). They are compiled # standalone, one at a time, by the out-of-tree per-module MC/DC coverage -# campaign, which reads them from this directory. They are listed in EXTRA_DIST +# suite, which reads them from this directory. They are listed in EXTRA_DIST # only so the source-completeness check accounts for them and so they ship in # the dist tarball -- the same treatment tests/api/include.am gives its # non-compiled files. Do not move them to tests_unit_test_SOURCES. diff --git a/tests/unit-mcdc/README.md b/tests/unit-mcdc/README.md index e0f2641550..af7c66fb1d 100644 --- a/tests/unit-mcdc/README.md +++ b/tests/unit-mcdc/README.md @@ -5,7 +5,7 @@ This directory holds small, standalone white-box programs that raise **MC/DC** reaching decisions that are **structurally unreachable from the public API**. These are **not** part of the wolfSSL build and are **not** registered in -`tests/api`. They exist for the external ISO 26262 per-module coverage campaign +`tests/api`. They exist for the external ISO 26262 per-module coverage suite in `iso26262/mcdc-per-module/`. Nothing here changes library behaviour. ## Why a separate module @@ -24,16 +24,16 @@ MC/DC independence pair** in the same binary. ## How coverage is combined -llvm-cov computes MC/DC independence **per binary**. The campaign's +llvm-cov computes MC/DC independence **per binary**. The suite's `aggregate.sh` unions the "independence shown" bit **across binaries by source `line:col`**. So each pair must be completed *within the white-box binary itself* - it does not lean on the API tests to supply the other half. The white-box result is unioned in as an extra `"_wb"` ledger row, one per build variant, exactly like any other variant. -## Build contract (driven by `run-mcdc.sh`) +## Build contract -The campaign's `run-mcdc.sh` builds each file via `#include` with the **exact** +The external harness builds each file via `#include` with the **exact** compile flags the instrumented library used for that translation unit (captured from the real `libtool` command - struct layout and backend selection depend on `-DHAVE___UINT128_T`, `user_settings.h`, `-DWOLFSSL_TEST_STATIC_BUILD`, ...), then @@ -68,8 +68,8 @@ justified in `iso26262/mcdc-per-module/reports/aes/RESIDUALS.md`: `main()`, calls each unreachable helper with both halves of every targeted MC/DC pair. Keep every call memory-safe (short-circuits protect NULL derefs); surface setup failures as printed skips and **return 0** (a nonzero exit - makes the campaign discard the variant). -2. Point the campaign at it (a per-module white-box source path in - `db/modules.json`); `run-mcdc.sh`'s white-box step handles build/link/export. -3. Re-run `run-mcdc.sh ` then `aggregate.sh `; confirm the - targeted `line:col` keys leave `GAPS.md`. + makes the harness discard the variant). +2. Register it with the external harness as this module's white-box source; + its white-box step handles build, link and coverage export. +3. Re-measure the module and confirm the targeted `line:col` keys no longer + appear among its uncovered conditions. diff --git a/tests/unit-mcdc/mcdc_der_edit.h b/tests/unit-mcdc/mcdc_der_edit.h index 76f9180a8c..b07327b123 100644 --- a/tests/unit-mcdc/mcdc_der_edit.h +++ b/tests/unit-mcdc/mcdc_der_edit.h @@ -20,7 +20,7 @@ */ /* - * mcdc_der_edit.h -- structural DER edits for the per-module MC/DC campaign. + * mcdc_der_edit.h -- structural DER edits for the per-module MC/DC suite. * * PURPOSE * ------- diff --git a/tests/unit-mcdc/mcdc_fault_alloc.h b/tests/unit-mcdc/mcdc_fault_alloc.h index 44281aba18..5dc1e8461e 100644 --- a/tests/unit-mcdc/mcdc_fault_alloc.h +++ b/tests/unit-mcdc/mcdc_fault_alloc.h @@ -21,11 +21,11 @@ /* * mcdc_fault_alloc.h -- header-only, self-contained heap-fault injector for the - * per-module MC/DC campaign. + * per-module MC/DC suite. * * PURPOSE * ------- - * The dominant justified-residual class across the campaign is the FALSE half + * The dominant justified-residual class across the harness is the FALSE half * of success-chain guards shaped * * if ((err == MP_OKAY) && ) ... (drive on FALSE) diff --git a/tests/unit-mcdc/mcdc_fault_hash.h b/tests/unit-mcdc/mcdc_fault_hash.h index 5f135c3254..5ef7708c56 100644 --- a/tests/unit-mcdc/mcdc_fault_hash.h +++ b/tests/unit-mcdc/mcdc_fault_hash.h @@ -21,13 +21,13 @@ /* * mcdc_fault_hash.h -- header-only, self-contained HASH/BLOCK-CIPHER primitive - * fault injector for the per-module MC/DC campaign. It is the second lever + * fault injector for the per-module MC/DC suite. It is the second lever * beside mcdc_fault_alloc.h, and the ONLY one that works for the hash-based * signature/KEM engines. * * WHY A SECOND LEVER * ------------------ - * The dominant justified-residual class campaign-wide is the FALSE half of a + * The dominant justified-residual class suite-wide is the FALSE half of a * success chain: * * if ((ret == 0) && ) ... @@ -44,7 +44,7 @@ * * HOW IT WORKS -- MACRO INTERPOSITION * ----------------------------------- - * Every white-box TU in this campaign #includes the involved .c directly, and + * Every white-box TU in this suite #includes the involved .c directly, and * the harness links it against libwolfssl.a with only that one object trimmed. * The primitives above therefore still come from the archive and cannot be * replaced at link time -- but they CAN be replaced at preprocessing time, @@ -87,7 +87,7 @@ * PORTABILITY * ----------- * Each wrapper/macro pair is behind the same feature guard as the declaration - * it shadows, so a TU that includes this header builds under every campaign + * it shadows, so a TU that includes this header builds under every suite * variant (in a build where a primitive is compiled out, nothing is * interposed). Unused wrappers are ordinary unused static helpers. * diff --git a/tests/unit-mcdc/mcdc_fault_mp.h b/tests/unit-mcdc/mcdc_fault_mp.h index 0497060137..61cdf6e29e 100644 --- a/tests/unit-mcdc/mcdc_fault_mp.h +++ b/tests/unit-mcdc/mcdc_fault_mp.h @@ -21,7 +21,7 @@ /* * mcdc_fault_mp.h -- header-only big-integer (mp_*) fault injector for the - * per-module MC/DC campaign. Third lever, beside mcdc_fault_alloc.h (heap) and + * per-module MC/DC suite. Third lever, beside mcdc_fault_alloc.h (heap) and * mcdc_fault_hash.h (hash / block-cipher primitives). * * WHY @@ -229,7 +229,7 @@ MCDC_FM_MAYBE_UNUSED static int mcdc_fm_to_unsigned_bin_len(const mp_int* a, /* Some entry points are declared only under a feature condition by the * heapmath (integer.h) and fastmath (tfm.h) backends; sp_int.h declares all of * them. Wrapping one the active backend does not declare makes the including - * TU fail to build, which the campaign scores as a silent skip. */ + * TU fail to build, which the harness scores as a silent skip. */ #if defined(WOLFSSL_SP_MATH) || defined(WOLFSSL_SP_MATH_ALL) #define MCDC_FM_HAVE_READ_RADIX #define MCDC_FM_HAVE_PRIME_IS_PRIME_EX diff --git a/tests/unit-mcdc/mcdc_fault_mpint.h b/tests/unit-mcdc/mcdc_fault_mpint.h index e2487b8ead..322a1593a7 100644 --- a/tests/unit-mcdc/mcdc_fault_mpint.h +++ b/tests/unit-mcdc/mcdc_fault_mpint.h @@ -21,7 +21,7 @@ /* * mcdc_fault_mpint.h -- fault injector for the SCRATCH-mp_int LIFECYCLE macros - * NEW_MP_INT_SIZE() / INIT_MP_INT_SIZE(), for the per-module MC/DC campaign. + * NEW_MP_INT_SIZE() / INIT_MP_INT_SIZE(), for the per-module MC/DC suite. * Fourth lever, beside mcdc_fault_alloc.h (heap), mcdc_fault_hash.h (digest / * block-cipher primitives) and mcdc_fault_mp.h (big-integer computations). * @@ -97,7 +97,7 @@ * * AVAILABILITY (read the note in mcdc_seed_rng.h first: a conditionally * available header must still define its API UNCONDITIONALLY, or a TU that - * calls it fails to COMPILE under some variant -- which the campaign scores as + * calls it fails to COMPILE under some variant -- which the harness scores as * a silent skip rather than an error.) * * The INIT lever works under every backend: INIT_MP_INT_SIZE is an expression diff --git a/tests/unit-mcdc/mcdc_fault_mutex.h b/tests/unit-mcdc/mcdc_fault_mutex.h index 54fce2e910..2bac81bc5e 100644 --- a/tests/unit-mcdc/mcdc_fault_mutex.h +++ b/tests/unit-mcdc/mcdc_fault_mutex.h @@ -20,7 +20,7 @@ */ /* - * mcdc_fault_mutex.h -- mutex-fault injector for the per-module MC/DC campaign. + * mcdc_fault_mutex.h -- mutex-fault injector for the per-module MC/DC suite. * * PURPOSE * ------- diff --git a/tests/unit-mcdc/mcdc_seed_rng.h b/tests/unit-mcdc/mcdc_seed_rng.h index f84cdf473f..fc76088a94 100644 --- a/tests/unit-mcdc/mcdc_seed_rng.h +++ b/tests/unit-mcdc/mcdc_seed_rng.h @@ -20,7 +20,7 @@ */ /* - * mcdc_seed_rng.h -- deterministic RNG for the per-module MC/DC campaign. + * mcdc_seed_rng.h -- deterministic RNG for the per-module MC/DC suite. * * WHY THIS EXISTS * --------------- @@ -83,7 +83,7 @@ * to be in scope before it runs. A driver that includes this header as its * FIRST wolfSSL include otherwise evaluates the test against an empty macro * environment, silently selects the inert stubs, and pins nothing -- which - * builds, links and runs, so the campaign scores it as a pass. Pull the + * builds, links and runs, so the harness scores it as a pass. Pull the * switchboard in here rather than relying on every caller's include order. * It is idempotent, so callers that already included it are unaffected. */ #include @@ -108,7 +108,7 @@ static int mcdc_sr_active = 0; * before anything else drags random.h in; when it is not, the include * guard skips the prototype, the hook is never declared, and every call * site inside the .c under test fails with "use of undeclared - * identifier". That is a compile failure, which the campaign scores as a + * identifier". That is a compile failure, which the harness scores as a * SILENT SKIP -- the dh module read 107/173 with 6 of 12 variants * aggregating instead of 158/173, and still reported success. * @@ -220,7 +220,7 @@ int mcdc_sr_block(WC_RNG* rng, byte* out, word32 sz) #else /* MCDC_SR_UNAVAILABLE */ /* Inert stubs. Without these a TU that calls mcdc_sr_arm() fails to COMPILE in - * any variant lacking SHAKE -- and the campaign scores a white-box that fails + * any variant lacking SHAKE -- and the harness scores a white-box that fails * to compile as a SILENT SKIP, losing the whole file's coverage rather than * reporting an error. (Observed on the dh module: 12 variants aggregated * became 6, and dh.c read 107/173 instead of 158/173.) A header that is diff --git a/tests/unit-mcdc/test_aes_whitebox.c b/tests/unit-mcdc/test_aes_whitebox.c index 8c0d2209bd..b402a3148c 100644 --- a/tests/unit-mcdc/test_aes_whitebox.c +++ b/tests/unit-mcdc/test_aes_whitebox.c @@ -11,13 +11,13 @@ * MC/DC independence pair. * * Coverage from this binary is unioned with the tests/api variant coverage by - * source line:col in the per-module campaign (iso26262/mcdc-per-module): - * llvm-cov computes MC/DC independence PER BINARY, and the campaign's + * source line:col in the per-module suite: + * llvm-cov computes MC/DC independence PER BINARY, and the * aggregate.sh ORs the "independence shown" bit across binaries by key. That is * why every pair below is completed *within this file* rather than relying on * the API tests to supply the other half. * - * Build: compiled by run-mcdc.sh's white-box step with the SAME MC/DC CFLAGS, + * Build: compiled by the coverage runner's white-box step with the SAME MC/DC CFLAGS, * -DHAVE_CONFIG_H and -I as the instrumented library, then linked * against that variant's libwolfssl.a with its aes.o removed (this TU supplies * the instrumented aes.c). NOT part of the wolfSSL build; not registered in @@ -39,7 +39,7 @@ * Class 10 AArch64 CTR leftover-keystream loop (WOLFSSL_ARMASM, * __aarch64__, qemu-aarch64 lane only) .. exclusion demonstration * Classes 4, 5 and 10 only compile in the qemu-aarch64 emulator lane (see - * iso26262/mcdc-per-module campaign, db/lanes.json); on every other build + * iso26262/mcdc-per-module suite, db/lanes.json); on every other build * they reduce to a no-op stub so this file still compiles+runs natively. * The remaining union residuals are structurally uncoverable even here * (complementary-operand decisions where unique-cause MC/DC is unsatisfiable, @@ -47,7 +47,7 @@ * loop above, and AesCfbDecrypt_C's `ret == 0` loop guard: the only build * axis that compiles that block, WOLFSSL_ARMASM, also selects a * wc_AesEncrypt() with no failure path). Those stay justified in - * campaign/db/exclusions.json + EXCLUSIONS.md and reports/aes/RESIDUALS.md. + * the exclusion record + the exclusion record and reports/aes/RESIDUALS.md. */ /* Pull aes.c in verbatim so the file-static and WOLFSSL_LOCAL helpers below are @@ -275,7 +275,7 @@ static void wb_aesnew_common(void) /* ------------------------------------------------------------------------- * * Class 3: AES-NI internal pointer guards (WOLFSSL_AESNI). * - * When aes.c is compiled with AES-NI (the campaign's "aesni" variant), the + * When aes.c is compiled with AES-NI (the "aesni" variant), the * AES-NI code paths add file-static helpers whose NULL/size guards every public * caller pre-rejects, exactly like the classic GHASH guards: * @@ -601,7 +601,7 @@ static void wb_aarch64_gcm_ptr_guards(void) * iv == NULL, ivSz > 0 -> idx3 F (group false), idx5 T, idx6 T -> reject * and idx3 flips with idx5 in both, so neither row isolates idx5. The calls * below still run (they pair idx3/idx4/idx6); idx5 is recorded in - * campaign/db/exclusions.json. wc_AesGcmSetIV's idx5 ("ivFixed != NULL", + * the exclusion record. wc_AesGcmSetIV's idx5 ("ivFixed != NULL", * ~14858) is excluded on the identical argument against its idx3 * ("ivFixed == NULL"). * ------------------------------------------------------------------------- */ @@ -1123,7 +1123,7 @@ static void wb_ccm_aesni_dispatch(void) * so on arrival either left == 0 (left <= sz) or sz == 0 (left > sz), never * both non-zero. The loop below is a duplicate of that drain and its body is * dead. With the decision never true, neither operand has an independence - * pair, so both conditions are recorded in campaign/db/exclusions.json. The + * pair, so both conditions are recorded in the exclusion record. The * calls stay so the argument is demonstrated rather than only asserted. * ------------------------------------------------------------------------- */ #if defined(__aarch64__) && defined(WOLFSSL_ARMASM) && \ @@ -1196,7 +1196,7 @@ int main(void) wb_ccm_aesni_dispatch(); wb_aarch64_ctr_leftover(); printf("done (%s)\n", wb_fail ? "with skips" : "ok"); - /* Setup failures are surfaced as skips, not test failures: the campaign + /* Setup failures are surfaced as skips, not test failures: the harness * treats a nonzero exit as a failed variant and discards its coverage. */ return 0; #endif diff --git a/tests/unit-mcdc/test_asn_cert_whitebox.c b/tests/unit-mcdc/test_asn_cert_whitebox.c index 9c8b7b1f99..801eae97b1 100644 --- a/tests/unit-mcdc/test_asn_cert_whitebox.c +++ b/tests/unit-mcdc/test_asn_cert_whitebox.c @@ -1262,7 +1262,7 @@ static void wb_date_greater_than(void) * Section 15: wc_ValidateDateWithTime() [:15988,:16015] * * :15988 `sizeof(ltime)==sizeof(word32) && (sword32)ltime<0` -- on every - * build this campaign targets, time_t is 64-bit, so the 1st operand is a + * build this suite targets, time_t is 64-bit, so the 1st operand is a * compile-time false and the 2nd is never reached; its true side is not * reachable without a 32-bit time_t target (RESIDUAL, platform-gated, not * a fault-injection case). @@ -1273,7 +1273,7 @@ static void wb_date_greater_than(void) * the offset `i` lands on once the 6 GetTime() fields are consumed, so by * the time this line runs date[i] is always 'Z' on any successful parse. * Structurally dead code under the current ExtractDate() contract - * (candidate for the campaign DEATHNOTE) -- not attempted here. + * -- not attempted here. * ========================================================================= */ static void wb_validate_date_with_time(void) { @@ -3243,7 +3243,7 @@ int main(void) printf("done (%s)\n", wb_fail ? "with failures" : "ok"); /* Always return 0: a nonzero exit discards this variant's coverage - * entirely in the campaign harness. Failures are surfaced via the + * entirely in the test harness. Failures are surfaced via the * printed [FAIL] lines instead. */ (void)wb_fail; return 0; diff --git a/tests/unit-mcdc/test_asn_certgen_whitebox.c b/tests/unit-mcdc/test_asn_certgen_whitebox.c index d7b980612f..724df78990 100644 --- a/tests/unit-mcdc/test_asn_certgen_whitebox.c +++ b/tests/unit-mcdc/test_asn_certgen_whitebox.c @@ -42,7 +42,7 @@ * driven and paired for MC/DC. * * NOT COMPILED IN THIS MODULE'S CONFIG (verified against - * campaign/configs/asn/user_settings.base.h): + * suite/configs/asn/user_settings.base.h): * - WOLFSSL_ACERT: attribute-certificate parsing (ParseX509Acert, * DecodeAcertGeneralName(s), VerifyX509Acert, ...) is entirely gated * behind "#if defined(WOLFSSL_ACERT) && defined(WOLFSSL_ASN_TEMPLATE)" @@ -592,7 +592,7 @@ static void wb_set_name_ex(void) #endif /* ======================================================================== - * ARGUED UNREACHABLE, do not re-open (campaign EXCLUSIONS.md + + * ARGUED UNREACHABLE, do not re-open (suite the exclusion record + * db/exclusions.json): wc_SetSubjectRaw() :32713 cond 0 and wc_SetIssuerRaw() * :32750 cond 0 (`decodedCert->subjectRaw` non-NULL). GetCertName() assigns * cert->subjectRaw = &input[srcIdx] (asn.c:15513) on every path where the @@ -1963,7 +1963,7 @@ static void wb_rsa_public_key_decode_raw(void) * RESIDUAL: :40397/:40411/:40542's "badDate" arm is only taken when * CheckDate() actually reports the acert as expired/not-yet-valid. The * corpus certs (certs/acert/acert.pem, acert_ietf.pem) are fixed test - * vectors not guaranteed to straddle "today" for the life of this campaign, + * vectors not guaranteed to straddle "today" for the life of this suite, * so only the verify-mode short-circuits (NO_VERIFY / VERIFY_SKIP_DATE) * are driven below, not a genuine bad-date trigger; doing so safely would * need a deliberately-expired ACERT DER fixture, which is left for a @@ -3352,7 +3352,7 @@ int main(void) printf("done (%s)\n", wb_fail ? "with failures" : "ok"); /* Always return 0: a nonzero exit discards this variant's coverage - * entirely in the campaign harness. Failures are surfaced via the + * entirely in the test harness. Failures are surfaced via the * printed [FAIL] lines instead. */ (void)wb_fail; return 0; diff --git a/tests/unit-mcdc/test_asn_ext_whitebox.c b/tests/unit-mcdc/test_asn_ext_whitebox.c index e1012e0e60..0cc1e1aec9 100644 --- a/tests/unit-mcdc/test_asn_ext_whitebox.c +++ b/tests/unit-mcdc/test_asn_ext_whitebox.c @@ -21,7 +21,7 @@ /* * White-box MC/DC supplement for wolfcrypt/src/asn.c, "extensions" wave - * (Part 5 of the ISO 26262 MC/DC campaign): name-constraint matching, + * (Part 5 of the ISO 26262 MC/DC suite): name-constraint matching, * X.509 extension decoding, and the certificate/CSR decode core * (asn.c lines ~18537-23356 at the time this file was written). * @@ -33,7 +33,7 @@ * certificate -- the malformed/edge-case arms never fire from there. * * Coverage is unioned by source line:col with every other variant/whitebox - * in the per-module campaign; independence pairs are completed *within this + * in the per-module suite; independence pairs are completed *within this * binary*. * * Sections (asn.c line numbers as of this writing): @@ -92,7 +92,7 @@ * once keyOID == RSAk and ParseCertRelative succeeds, GetCertKey always * sets publicKey/pubKeySize together; only the all-true combination is * reachable without editing library source. Now FILED as an exclusion - * (campaign EXCLUSIONS.md + db/exclusions.json), together with the same + * (suite the exclusion record + db/exclusions.json), together with the same * arrival argument for ParseCertRelative()'s :24656 cond 1 and :24790 * cond 0. The matching pubKeySize operands at :24656 cond 2 and :24790 * cond 2 are deliberately NOT excluded: outside the RSA-family branch @@ -124,7 +124,7 @@ /* Some leading `ret == 0` operands in this file's decoders have no crafted * input that reaches them: the only preceding statement that can set ret is a * DECL_ASNGETDATA allocation or a hash call, neither of which fails on valid - * input. Those are driven with the campaign's heap-fault injector, which is + * input. Those are driven with the heap-fault injector, which is * only effective in the WOLFSSL_SMALL_STACK variant (where the ASN.1 data * arrays and wc_ShaHash()'s context are heap-allocated); the matching TRUE * rows are issued unarmed in the same binary. */ @@ -641,7 +641,7 @@ static void wb_permitted_excluded_lists(void) { WB_NOTE("IGNORE_NAME_CONSTRAINTS * :19392 subjectDnsName fallback len>0 && name!=NULL * :19414-:19415 critical + unsupported GeneralName form -> fail closed * - * RESIDUAL (argued unreachable, recorded in EXCLUSIONS.md under + * RESIDUAL (argued unreachable, recorded in the exclusion record under * "Condition-level exclusions"): the SECOND operand of the subjectDnsName * fallback, `subjectDnsName.name != NULL`. subjectDnsName is XMEMSET to zero * at the top of every nameTypes[] iteration and only three switch arms ever @@ -769,7 +769,7 @@ static void wb_confirm_name_constraints(void) * ASN_RFC822_TYPE under `cert->subjectEmail != NULL` and ASN_DIR_TYPE * under `cert->subjectRaw != NULL` -- and each of those assigns the * length and the pointer from the same object it has just tested. A - * non-zero .len therefore implies a non-NULL .name; see EXCLUSIONS.md. */ + * non-zero .len therefore implies a non-NULL .name; see the exclusion record. */ WB_NOTE("ConfirmNameConstraints(): subjectDnsName fallback len/name [:19392]"); /* subjectEmail present -> synthetic RFC822 name len>0 && name!=NULL, * both true, checked against an excluded email base. */ @@ -2941,7 +2941,7 @@ int main(void) printf("done (%s)\n", wb_fail ? "with failures" : "ok"); /* Always return 0: a nonzero exit discards this variant's coverage - * entirely in the campaign harness. Failures are surfaced via the + * entirely in the test harness. Failures are surfaced via the * printed [FAIL] lines instead. */ (void)wb_fail; return 0; diff --git a/tests/unit-mcdc/test_asn_fault_whitebox.c b/tests/unit-mcdc/test_asn_fault_whitebox.c index da6d9b3999..a9086aecc5 100644 --- a/tests/unit-mcdc/test_asn_fault_whitebox.c +++ b/tests/unit-mcdc/test_asn_fault_whitebox.c @@ -113,7 +113,7 @@ * caller passes a sigCpy of at least sigSz bytes, so both * mp_to_unsigned_bin() calls write inside the buffer and cannot fail. * - * GAPS.md rows in the deep certificate chain-verification internals + * the uncovered-condition report rows in the deep certificate chain-verification internals * (name-constraint enforcement, X.509 extension decoding/verification, * CRL/OCSP responder verification, ASN.1 dump/print) were left untouched by * this file -- they need a fully valid, parsed DecodedCert/Signer/chain @@ -3071,7 +3071,7 @@ int main(void) printf("done (%s)\n", wb_fail ? "with failures" : "ok"); /* Always return 0: a nonzero exit discards this variant's coverage - * entirely in the campaign harness. Failures are surfaced via the + * entirely in the test harness. Failures are surfaced via the * printed [FAIL] lines instead. */ (void)wb_fail; return 0; diff --git a/tests/unit-mcdc/test_asn_keys_whitebox.c b/tests/unit-mcdc/test_asn_keys_whitebox.c index 7c60f54026..bdb17f5e32 100644 --- a/tests/unit-mcdc/test_asn_keys_whitebox.c +++ b/tests/unit-mcdc/test_asn_keys_whitebox.c @@ -729,7 +729,7 @@ static void wb_get_key_oid(void) { WB_NOTE("HAVE_PKCS8/12 off; wc_GetKeyOID skip /* ======================================================================== * Section A10: wc_EncryptPKCS8Key_ex() argument/salt/version checks. * - * ARGUED UNREACHABLE, do not re-open (campaign EXCLUSIONS.md + + * ARGUED UNREACHABLE, do not re-open (suite the exclusion record + * db/exclusions.json): :10805 BOTH operands. GetAlgoV2() (asn.c:10713-10748) * assigns *oid on every switch arm that returns 0 and leaves the caller's * initialiser untouched only on the default arm, which returns ALGO_ID_E. @@ -2790,7 +2790,7 @@ int main(void) printf("done (%s)\n", wb_fail ? "with failures" : "ok"); /* Always return 0: a nonzero exit discards this variant's coverage - * entirely in the campaign harness. Failures are surfaced via the + * entirely in the test harness. Failures are surfaced via the * printed [FAIL] lines instead. */ (void)wb_fail; return 0; diff --git a/tests/unit-mcdc/test_asn_revocation_whitebox.c b/tests/unit-mcdc/test_asn_revocation_whitebox.c index 43c5991848..e9e1bb85ce 100644 --- a/tests/unit-mcdc/test_asn_revocation_whitebox.c +++ b/tests/unit-mcdc/test_asn_revocation_whitebox.c @@ -35,16 +35,16 @@ * helpers below, rather than typed-out byte arrays) plus a few real * certificate/key buffers borrowed from existing test fixtures. * - * NOTE on HAVE_OCSP_RESPONDER: the asn campaign's config_base + * NOTE on HAVE_OCSP_RESPONDER: the asn suite's config_base * (configs/asn/user_settings.base.h) never defines HAVE_OCSP_RESPONDER, so * EncodeCertID/EncodeSingleResponse/EncodeResponseData/EncodeBasicOcspResponse/ * OcspResponseEncode are compiled out for every variant of this module and - * GAPS.md carries no lines inside them -- this file does not attempt to + * the uncovered-condition report carries no lines inside them -- this file does not attempt to * cover that side and never needs it to build test input (all decode-side * buffers below are constructed by hand instead of via a round trip). * * Coverage is unioned by source line:col with the tests/api asn/ocsp run in - * the per-module campaign; every pair below is completed *within this file* + * the per-module suite; every pair below is completed *within this file* * (masking MC/DC is computed per binary, then ORed across binaries by key). */ @@ -286,7 +286,7 @@ static word32 wb_build_single_response(byte* out, * :35012/35013 if ((!AsnSkipDateCheck) && !XVALIDATE_DATE(nextDate, ..., ASN_AFTER, ...)) * :35021/35022 (WOLFSSL_OCSP_PARSE_STATUS) duplicate of :35006/35007 * AsnSkipDateCheck is a compile-time constant 0 unless - * WC_ASN_RUNTIME_DATE_CHECK_CONTROL is defined (not set for this campaign), + * WC_ASN_RUNTIME_DATE_CHECK_CONTROL is defined (not set for this suite), * so its "true" (skip) value is a structural residual here; only the * XVALIDATE_DATE operand is driven both ways. * ------------------------------------------------------------------------- */ @@ -1199,7 +1199,7 @@ static void wb_compare_ocsp_req_resp(void) * Section 11: ParseCRL_EntryExtensions() [:36841,:36842,:36854-:36856, * :36863,:36864,:36877,:36878,:36881,:36882,:36891,:36892,:36917,:36921, * :36935] - * WC_ASN_UNKNOWN_EXT_CB is active for this campaign (WOLFSSL_ASN_ALL pulls + * WC_ASN_UNKNOWN_EXT_CB is active for this suite (WOLFSSL_ASN_ALL pulls * in WOLFSSL_CUSTOM_OID + HAVE_OID_DECODING + WOLFSSL_ASN_TEMPLATE), so the * callback-dispatch branch is live, not compiled out. * ------------------------------------------------------------------------- */ @@ -1575,7 +1575,7 @@ static void wb_parse_crl_entry_extensions(void) { WB_NOTE("HAVE_CRL off or WOLFC * other failure path). `m` is non-NULL on every arrival (the NULL case sets * ret at :37676, so cond 0 short-circuits) and dcrl->crlNumber is an array * member of DecodedCRL, so the decision is never true and neither operand - * pairs. Recorded in EXCLUSIONS.md. + * pairs. Recorded in the exclusion record. * ------------------------------------------------------------------------- */ static word32 wb_build_crl_number_ext(byte* out, const byte* intContent, word32 intContentSz) @@ -1684,7 +1684,7 @@ static void wb_parse_crl_extensions(void) /* Duplicate CRL_NUMBER_OID extensions -> :37284/:37285 CRL_NUMBER_OID * term both true on the 2nd occurrence (WOLFSSL_NO_ASN_STRICT is not - * defined for this campaign, so strict duplicate rejection applies). */ + * defined for this suite, so strict duplicate rejection applies). */ InitDecodedCRL(&dcrl, NULL); { byte ext1[64], ext2[64]; @@ -1716,7 +1716,7 @@ static void wb_parse_crl_extensions(void) #ifndef WC_ASN_UNKNOWN_EXT_CB /* Only reachable as "handled==0" residual note when the callback - * feature is compiled out; this campaign has it on (see below), kept + * feature is compiled out; this suite has it on (see below), kept * here for portability to a variant that does not. */ InitDecodedCRL(&dcrl, NULL); { @@ -2710,7 +2710,7 @@ int main(void) printf("done (%s)\n", wb_fail ? "with failures" : "ok"); /* Always return 0: a nonzero exit discards this variant's coverage - * entirely in the campaign harness. Failures are surfaced via the + * entirely in the test harness. Failures are surfaced via the * printed [FAIL] lines instead. */ (void)wb_fail; return 0; diff --git a/tests/unit-mcdc/test_asn_whitebox.c b/tests/unit-mcdc/test_asn_whitebox.c index b75e4ce5b0..8f0fedf849 100644 --- a/tests/unit-mcdc/test_asn_whitebox.c +++ b/tests/unit-mcdc/test_asn_whitebox.c @@ -33,7 +33,7 @@ * minimal custom ASNItem templates. * * Coverage is unioned by source line:col with the tests/api asn/x509/... run - * in the per-module campaign; every pair below is completed *within this + * in the per-module suite; every pair below is completed *within this * file* (masking MC/DC is computed per binary). * * Sections (asn.c line numbers as of this writing): @@ -74,7 +74,7 @@ * itself cannot show a false outcome. * - GetOID() :7960 first operand (`ret == 0`): ret is a local initialised * to 0 at asn.c:7873 and assigned only inside `#ifdef ASN_DUMP_OID` - * (:7952), which settings.h never defines and no campaign variant sets. + * (:7952), which settings.h never defines and no suite variant sets. * Constant true. The other three operands are driven below with two * hand-computed wc_oid_sum() collisions. * - wc_Asn1_PrintAll() :39680 both operands: every path through @@ -1237,7 +1237,7 @@ int main(void) printf("done (%s)\n", wb_fail ? "with failures" : "ok"); /* Always return 0: a nonzero exit discards this variant's coverage - * entirely in the campaign harness. Failures are surfaced via the + * entirely in the test harness. Failures are surfaced via the * printed [FAIL] lines instead. */ (void)wb_fail; return 0; diff --git a/tests/unit-mcdc/test_chacha20_poly1305_whitebox.c b/tests/unit-mcdc/test_chacha20_poly1305_whitebox.c index a0f1d36517..fc1f3a758a 100644 --- a/tests/unit-mcdc/test_chacha20_poly1305_whitebox.c +++ b/tests/unit-mcdc/test_chacha20_poly1305_whitebox.c @@ -50,7 +50,7 @@ * guards (encrypt and decrypt arms), "len > 0" FALSE half: a zero-length * data update, valid but unused by the tests. * - * NOT closable, recorded in campaign/db/exclusions.json (three conditions, + * NOT closable, recorded in the exclusion record (three conditions, * one argument): * * :943 if (ret == 0 && aead->state == CHACHA20_POLY1305_STATE_AAD) @@ -80,7 +80,7 @@ * * No ciphertext or tag value is checked here: the AEAD KATs own correctness, * this file only drives the decisions. Every failure is reported as a skip and - * main() always returns 0 -- a non-zero exit makes the campaign discard the + * main() always returns 0 -- a non-zero exit makes the harness discard the * whole variant. */ @@ -320,7 +320,7 @@ int main(void) printf("chacha20_poly1305.c white-box MC/DC supplement\n"); wb_run(); printf("done (%s)\n", wb_fail ? "with skips" : "ok"); - /* Setup failures are surfaced as skips, not test failures: the campaign + /* Setup failures are surfaced as skips, not test failures: the harness * treats a nonzero exit as a failed variant and discards its coverage. */ return 0; } diff --git a/tests/unit-mcdc/test_chacha_whitebox.c b/tests/unit-mcdc/test_chacha_whitebox.c index 96f14366b1..18ff0d538b 100644 --- a/tests/unit-mcdc/test_chacha_whitebox.c +++ b/tests/unit-mcdc/test_chacha_whitebox.c @@ -30,10 +30,10 @@ * else { chacha_encrypt_x64(...); return 0; } * * Each of these is a single-condition branch (not a compound MC/DC decision: - * chacha.c's own db/modules.json-measured MC/DC total is unaffected by which + * chacha.c's own the module registry-measured MC/DC total is unaffected by which * of these paths a given build takes), so this white-box does not change the - * campaign's covered/total counts. It is kept anyway, matching the intel- - * dispatch technique used by the aes/sha3 white-boxes and this campaign's + * suite's covered/total counts. It is kept anyway, matching the intel- + * dispatch technique used by the aes/sha3 white-boxes and this suite's * poly1305 sibling, for FEATURE/branch-coverage evidence that the AVX2-false * sides (AVX1-only and the generic x64 fallback) are reachable and correct: * on an AVX2-capable CI host, cpuid_get_flags_ex()'s real detection always @@ -46,7 +46,7 @@ * before calling wc_Chacha_Process() makes it trust our forced value instead * of re-detecting. Crash-safety: we only ever CLEAR capability bits the real * host does not actually have removed either -- this host has both AVX1 and - * AVX2 hardware (see db/modules.json chacha notes), so forcing cpuidFlags to + * AVX2 hardware (see the module registry chacha notes), so forcing cpuidFlags to * "AVX1 only" or "neither" and letting the dispatch call the real * chacha_encrypt_avx1/chacha_encrypt_x64 asm is always safe: we never claim * a capability the CPU lacks, only hide one it has. @@ -307,7 +307,7 @@ int main(void) wb_chacha_predicate(); wb_chacha_avx512_dispatch(); printf("done (%s)\n", wb_fail ? "with skips" : "ok"); - /* Setup failures are surfaced as skips, not test failures: the campaign + /* Setup failures are surfaced as skips, not test failures: the harness * treats a nonzero exit as a failed variant and discards its coverage. */ return 0; #endif diff --git a/tests/unit-mcdc/test_cmac_whitebox.c b/tests/unit-mcdc/test_cmac_whitebox.c index dda9b09cd9..18112d60d9 100644 --- a/tests/unit-mcdc/test_cmac_whitebox.c +++ b/tests/unit-mcdc/test_cmac_whitebox.c @@ -197,7 +197,7 @@ int main(void) wb_cross_combos(); printf("done (%s)\n", wb_fail ? "with skips" : "ok"); /* Setup failures are surfaced as skips, not test failures: the - * campaign treats a nonzero exit as a failed variant and discards its + * suite treats a nonzero exit as a failed variant and discards its * coverage. */ return 0; #endif diff --git a/tests/unit-mcdc/test_curve25519_whitebox.c b/tests/unit-mcdc/test_curve25519_whitebox.c index 2ae5b854c0..ec378a765e 100644 --- a/tests/unit-mcdc/test_curve25519_whitebox.c +++ b/tests/unit-mcdc/test_curve25519_whitebox.c @@ -16,8 +16,8 @@ * helpers with both halves of each MC/DC independence pair. * * Coverage from this binary is unioned with the tests/api variant coverage - * by source line:col in the per-module campaign (iso26262/mcdc-per-module): - * llvm-cov computes MC/DC independence PER BINARY, and the campaign's + * by source line:col in the per-module suite: + * llvm-cov computes MC/DC independence PER BINARY, and the * aggregate.sh ORs the "independence shown" bit across binaries by key. * That is why every pair below is completed *within this file* rather than * relying on the API tests to supply the other half. @@ -25,7 +25,7 @@ * Only meaningful under WC_X25519_NONBLOCK (requires CURVE25519_SMALL, * per curve25519.c's own top-of-file doc comment); a no-op elsewhere. * - * Build: compiled by run-mcdc-par.sh's white-box step with the SAME MC/DC + * Build: compiled by the coverage runner's white-box step with the SAME MC/DC * CFLAGS, -DHAVE_CONFIG_H and -I as the instrumented library, * then linked against that variant's libwolfssl.a with its curve25519.o * removed (this TU supplies the instrumented curve25519.c). NOT part of @@ -40,11 +40,11 @@ * These are the only curve25519.c gaps confirmed structurally unreachable * through the public API: wc_curve25519_make_key() pre-validates key/rng * non-NULL identically before ever calling either static, and only enters - * either with ret==0 already true. See the campaign's RESIDUALS.md for + * either with ret==0 already true. See the RESIDUALS.md for * everything else (notably curve25519_smul_blind()'s RNG-retry loop, which * needs a controllable/mockable RNG to force its rare all-0xff/large-first- * byte draw and is left as a structural residual, matching the ecc.c - * campaign's Tonelli-Shanks precedent). + * suite's Tonelli-Shanks precedent). */ /* Pull curve25519.c in verbatim so the file-static helpers below are in @@ -60,7 +60,7 @@ * * only takes "i >= 0" FALSE when EVERY byte of rz is 0xff -- a 2^-256 event * that no seeded stream can be relied on to produce (and rule 3 of this - * campaign forbids evidence that depends on a live draw). Interposing + * suite forbids evidence that depends on a live draw). Interposing * wc_RNG_GenerateBlock() for THIS translation unit lets one scripted draw * return 32 x 0xff, so the loop's first iteration evaluates the guard with * i == -1, while the second (unscripted) draw ends the loop normally -- both @@ -69,7 +69,7 @@ * * random.h is included and the hook declared FIRST so the macro never has to * rewrite random.h's own prototype (see the same note in mcdc_seed_rng.h: an - * undeclared hook is a compile failure, which the campaign scores as a silent + * undeclared hook is a compile failure, which the harness scores as a silent * skip). The hook's body sits after the #undef, so it still reaches the real * RNG when the script is idle. */ @@ -333,7 +333,7 @@ static void wb_generic_arg_guards(void) * idx1 ("rz[0] <= 0xec") stays EXCLUDED and is not attempted here: reaching * it at all requires i < 0, which the loop bound (i >= 0, not i >= 1) makes * synonymous with rz[0] == 0xff, so the operand is unreachable-as-true. That - * is a product defect, filed in the campaign's DEATHNOTE.md; if the loop bound + * is a product defect; if the loop bound * is ever corrected the exclusion must be withdrawn and BOTH operands * re-measured from this same interposer. * ------------------------------------------------------------------------- */ @@ -410,7 +410,7 @@ int main(void) wb_blind_rz_all_ff(); printf("done (%s)\n", wb_fail ? "with skips" : "ok"); /* Setup failures are surfaced as skips, not test failures: the - * campaign treats a nonzero exit as a failed variant and discards its + * suite treats a nonzero exit as a failed variant and discards its * coverage. */ return 0; #endif diff --git a/tests/unit-mcdc/test_dh_fault_whitebox.c b/tests/unit-mcdc/test_dh_fault_whitebox.c index a8a77aa0d0..0cc3382d20 100644 --- a/tests/unit-mcdc/test_dh_fault_whitebox.c +++ b/tests/unit-mcdc/test_dh_fault_whitebox.c @@ -27,7 +27,7 @@ * file-static, full-capacity sp_int) is a cross-TU call into sp_int.c, and * mcdc_fault_alloc.h's XMALLOC hook only reaches allocations made BY dh.c * itself (see its header comment) - it cannot fail an sp_int.c internal - * allocation, and under this campaign's math backend (WOLFSSL_SP_MATH_ALL, + * allocation, and under this suite's math backend (WOLFSSL_SP_MATH_ALL, * SP_INT_BITS=4096, fixed-size sp_int, no heap growth) most of those calls * cannot fail at all except on a NULL argument that can never be NULL here. * This file therefore does not use mcdc_fault_alloc.h; instead it drives the @@ -71,7 +71,7 @@ * "oversized" buffer, whose LENGTH argument (never its dereferenced past-end * content) is what trips the size guard - no OOB read ever happens. * - * STRUCTURALLY UNSATISFIABLE (recorded in campaign/db/exclusions.json). + * STRUCTURALLY UNSATISFIABLE (recorded in the exclusion record). * Three dh.c conditions are not driven here because no input produces the * missing vector: * @@ -119,7 +119,7 @@ #include "mcdc_fault_mp.h" /* EXPERIMENT (2026-08-11 flake hunt): dh.c:3359 `(ret == 0) && (primeCheckCount)` - * was the last non-deterministic condition campaign-wide -- covered in 2 of 3 + * was the last non-deterministic condition suite-wide -- covered in 2 of 3 * sweeps of an unchanged tree. primeCheckCount counts rejected prime * candidates, so it is a direct function of the random draws. */ #include "mcdc_seed_rng.h" @@ -284,7 +284,7 @@ static void test_generate_keypair_null_guards(void) * dispatch above returns first): a p whose bit count is not 2048/3072/4096 * takes this generic path in every variant. 1408's mp_to_unsigned_bin(y,pub) * call is given the exact size it needs (never truncated) and only checks - * for NULL - its failure arm is unreachable (see DEATHNOTE note below), so + * for NULL - its failure arm is unreachable (), so * only 1408's ret==0 operand is exercised here (via the same cascade as * 1405's). */ static void test_generate_public_cascade(void) @@ -315,7 +315,7 @@ static void test_generate_public_cascade(void) /* 1405:1 - a modulus bigger than SP_INT_BITS makes sp_exptmod_ex's own * "m->used*2 >= SP_INT_DIGITS" guard fail deterministically (verified: * MP_EXPTMOD_E), with ret==0 still true entering the check. Self-built - * odd 6144-bit-ish value (no HAVE_FFDHE_6144 table in this campaign's + * odd 6144-bit-ish value (no HAVE_FFDHE_6144 table in this suite's * base config) - trusted=1 skips the (irrelevant) primality check. */ { byte bigp[768]; @@ -433,7 +433,7 @@ static void test_validate_and_pairwise(void) * candidate; a single flipped byte makes it composite with overwhelming * probability, giving isPrime==0 (2706's TRUE row) which sets ret= * DH_CHECK_PUB_E and cascades ret!=0 into 2710 (its FALSE row). mp_init() - * on &key->g itself cannot fail here (see DEATHNOTE note). */ + * on &key->g itself cannot fail here. */ static void test_setkey_primality(void) { WC_RNG rng; diff --git a/tests/unit-mcdc/test_dsa_fault_whitebox.c b/tests/unit-mcdc/test_dsa_fault_whitebox.c index d9095ccabe..e0d83fb634 100644 --- a/tests/unit-mcdc/test_dsa_fault_whitebox.c +++ b/tests/unit-mcdc/test_dsa_fault_whitebox.c @@ -80,8 +80,8 @@ * ./test_dsa_fault_whitebox baseline: unarmed valid ops only * ./test_dsa_fault_whitebox sweep baseline + the fault-index sweeps * (Two modes so the injector's contribution can be measured as a delta; the - * campaign's run_whitebox harness runs it with no args -- pass "sweep" there by - * default via argv, see the modules.json entry note.) + * suite's run_whitebox harness runs it with no args -- pass "sweep" there by + * default via argv, see the the module registry entry note.) */ /* Installed BEFORE dsa.c so its mp_* calls resolve to the fault wrappers -- @@ -304,7 +304,7 @@ static void wb_crafted(WC_RNG* rng, DsaKey* key, const byte* digest, /* ---- wc_MakeDsaParameters reached with err != MP_OKAY. 518's `err != * MP_OKAY` operand is only FALSE-able from a successful run (the baseline - * call) and TRUE-able from a failed one; nothing in the normal campaign + * call) and TRUE-able from a failed one; nothing in the normal suite * ever fails it, and the heap sweep cannot (see the MakeDsaParameters note * further down). One armed mp step is enough and stops before the * expensive prime search: index 1 is mp_read_unsigned_bin at :426, index 2 @@ -332,7 +332,7 @@ static void wb_crafted(WC_RNG* rng, DsaKey* key, const byte* digest, * reports the BACKEND's code (MP_MEM from the heap backends) -- so * wc_MakeDsaParameters' "err not MP_INIT_E" guards could never fire and * wc_MakeDsaKey had no guard at all and ran mp_clear(tmpQ) on never- - * initialised storage (the SIGSEGV recorded in DEATHNOTE.md). Both sites + * initialised storage (the SIGSEGV). Both sites * now use the idiom wc_DsaSign_ex/wc_DsaVerify_ex already used, so failing * THEIR mp_init_multi is the way into these halves and is crash-safe: the * guards exist precisely to skip the clears. @@ -665,7 +665,7 @@ static void wb_crafted(WC_RNG* rng, DsaKey* key, const byte* digest, int main(int argc, char** argv) { - /* Default action is the fault sweep so the campaign's run_whitebox harness + /* Default action is the fault sweep so the run_whitebox harness * (which runs this binary with NO arguments) gets full coverage. Pass * "baseline" to run only the unarmed valid ops (used to measure the * injector's contribution as a delta), or "probe" to print the @@ -714,7 +714,7 @@ int main(int argc, char** argv) /* Diagnostic: count the allocations each entry point performs, WITHOUT * failing any (arm a huge index so the counter advances but never * trips). Use these counts to choose each sweep's K -- see the header - * and the campaign fan-out recipe. Exits without sweeping. */ + * and the harness fan-out recipe. Exits without sweeping. */ int a = 0; byte s2[256]; XMEMSET(s2, 0, sizeof(s2)); mcdc_fa_arm(1000000); diff --git a/tests/unit-mcdc/test_ecc_fault_whitebox.c b/tests/unit-mcdc/test_ecc_fault_whitebox.c index 69aa5be47a..1444b17a98 100644 --- a/tests/unit-mcdc/test_ecc_fault_whitebox.c +++ b/tests/unit-mcdc/test_ecc_fault_whitebox.c @@ -1374,7 +1374,7 @@ int main(void) mcdc_fa_disarm(); mcdc_fa_restore(); printf("done (%s)\n", wb_fail ? "with skips" : "ok"); - /* Setup failures are surfaced as skips, not test failures: the campaign + /* Setup failures are surfaced as skips, not test failures: the harness * treats a nonzero exit as a failed variant and discards its coverage. */ return 0; #endif diff --git a/tests/unit-mcdc/test_ecc_whitebox.c b/tests/unit-mcdc/test_ecc_whitebox.c index 8f6e9d6466..85704605e7 100644 --- a/tests/unit-mcdc/test_ecc_whitebox.c +++ b/tests/unit-mcdc/test_ecc_whitebox.c @@ -13,13 +13,13 @@ * halves of each MC/DC independence pair. * * Coverage from this binary is unioned with the tests/api variant coverage by - * source line:col in the per-module campaign (iso26262/mcdc-per-module): - * llvm-cov computes MC/DC independence PER BINARY, and the campaign's + * source line:col in the per-module suite: + * llvm-cov computes MC/DC independence PER BINARY, and the * aggregate.sh ORs the "independence shown" bit across binaries by key. That * is why every pair below is completed *within this file* rather than * relying on the API tests to supply the other half. * - * Build: compiled by run-mcdc.sh's white-box step with the SAME MC/DC CFLAGS, + * Build: compiled by the coverage runner's white-box step with the SAME MC/DC CFLAGS, * -DHAVE_CONFIG_H and -I as the instrumented library, then linked * against that variant's libwolfssl.a with its ecc.o removed (this TU * supplies the instrumented ecc.c). NOT part of the wolfSSL build; not @@ -47,7 +47,7 @@ * with ecc.o removed, so the opt-in is local to the white-box binary: it does * not change the library under test, and it touches nothing outside ecc.c * (the macro appears in no header, so no shared type or layout moves). The - * campaign unions MC/DC per source line, which is exactly how a white-box is + * suite unions MC/DC per source line, which is exactly how a white-box is * meant to add rows the native variants cannot produce. */ #ifndef WOLFSSL_ECIES_STATIC_GCM_NONCE #define WOLFSSL_ECIES_STATIC_GCM_NONCE @@ -875,7 +875,7 @@ static void wb_projective_wrappers(void) mp_clear(&a); } -/* ECC_SHAMIR is unconditional in the base config (see modules.json "ecc" +/* ECC_SHAMIR is unconditional in the base config (see the module registry "ecc" * notes): under it, ecc_mul2add() is `static normal_ecc_mul2add()` (FP_ECC * on, the default) or the public `ecc_mul2add()` itself (no_fp_shamir, FP_ECC * off) -- select the same symbol the source itself would use. */ @@ -1658,7 +1658,7 @@ static void wb_arg_guards(void) /* The accepting vectors below need the real curve constants: a zero * modulus/order would make wc_ecc_gen_deterministic_k's RFC 6979 retry - * loop spin, and the campaign kills the variant on TEST_TIMEOUT. */ + * loop spin, and the harness kills the variant on TEST_TIMEOUT. */ { mp_int prime; mp_int order; @@ -1932,7 +1932,7 @@ static void wb_gap_pass2(void) /* (F,-): the only way to reach the guard with the first operand * FALSE is a NON-NULL pubOut (which leaves key->type alone) on a key * that is a full ECC_PRIVATEKEY. Every API caller passes NULL, which - * is why this row is missing from the campaign. */ + * is why this row is missing from the harness. */ if (pubPt != NULL) { key.type = ECC_PRIVATEKEY; (void)wc_ecc_make_pub(&key, pubPt); @@ -2008,7 +2008,7 @@ static void wb_gap_pass2(void) * The public import paths reduce/reject out-of-range coordinates before * building a point, so only a hand-built point reaches these with a * coordinate >= p. (The mp_isneg operand of each is a separate matter -- - * see the residual note in the campaign report.) */ + * see the residual note in the harness report.) */ { mp_int prime, af, bf; ecc_point* pt = wc_ecc_new_point(); @@ -2429,7 +2429,7 @@ static void wb_ecies_algos(void) /* (T,T,-) and (T,F,T): longer than one byte AND a compressed-point * prefix, once for each parity tag. Only an ECIES sender that chose - * point compression emits these, and nothing in the campaign does -- + * point compression emits these, and nothing in the harness does -- * which also made the 0x03 operand a COIN FLIP before this vector * existed: it was only covered when a random ephemeral key happened to * have an odd y, so the module's number moved between runs of an @@ -2510,7 +2510,7 @@ static const int wbCurveIds[] = { /* Curve availability is a RUNTIME question here, not a compile-time one: the * per-curve HAVE_ECCnnn macros are only set when a build hand-picks curves, - * and this campaign's configs take the HAVE_ALL_CURVES default instead -- so + * and this suite's configs take the HAVE_ALL_CURVES default instead -- so * guarding on them would silently reduce every sweep below to P-256 and leave * each `ecc_sets[idx].id == ` operand permanently TRUE. Asking the * table is correct for both kinds of build. */ @@ -3618,7 +3618,7 @@ static void wb_ecies_bad_kdf(void) * 5110:1 `if ((err == MP_OKAY) && checkInf)` * checkInf is a local initialized to the constant 1 and never * assigned again outside WOLFSSL_SE050 builds, so it cannot be FALSE - * in any variant this campaign compiles. + * in any variant this suite compiles. * * 5088:1 `x < mp_unsigned_bin_size(result->x)` * x is mp_unsigned_bin_size(curve->prime) and the guard only runs @@ -3689,7 +3689,7 @@ static void wb_ecies_bad_kdf(void) * NOT a residual -- COVERED by Class 33 below. The argument that * used to be filed here (MAX_ECC_BITS caps the scalars at 521 bits, * 66 bytes, well under KB_SIZE - 2 = 126) is false for this - * campaign's configs: they #define WOLFCRYPT_HAVE_SAKKE, which + * suite's configs: they #define WOLFCRYPT_HAVE_SAKKE, which * raises MAX_ECC_BITS to 1024 and puts the 128-byte ECC_SAKKE_1 * curve in ecc_sets[], while accel_fp_mul2add() uses a flat * `#define KB_SIZE 128` (accel_fp_mul() is 256 under the same @@ -3877,7 +3877,7 @@ int main(void) wb_ecies_bad_kdf(); wb_fp_mul2add_kb_size(); printf("done (%s)\n", wb_fail ? "with skips" : "ok"); - /* Setup failures are surfaced as skips, not test failures: the campaign + /* Setup failures are surfaced as skips, not test failures: the harness * treats a nonzero exit as a failed variant and discards its coverage. */ return 0; #endif diff --git a/tests/unit-mcdc/test_eccsi_fault_whitebox.c b/tests/unit-mcdc/test_eccsi_fault_whitebox.c index 37d9eed612..6738d45472 100644 --- a/tests/unit-mcdc/test_eccsi_fault_whitebox.c +++ b/tests/unit-mcdc/test_eccsi_fault_whitebox.c @@ -42,7 +42,7 @@ * the map guard sees err != 0. It #includes eccsi.c directly (like the sibling * test_eccsi_whitebox.c) so the file-static helpers are in scope. * - * Not driven here (justified, documented in test_eccsi_whitebox.c and GAPS.md): + * Not driven here (justified, documented in test_eccsi_whitebox.c and the uncovered-condition report): * - eccsi_load_ecc_params() 196/202/208 `err == 0` FALSE halves: reaching them * requires eccsi_load_order() / the a/b radix reads to fail. Those are * mp_read_radix() calls on fixed, static, in-struct sp_int members of known- @@ -64,7 +64,7 @@ * * Invocation: * ./test_eccsi_fault_whitebox default: full fault sweep (used by the - * campaign run_whitebox, no args) + * suite run_whitebox, no args) * ./test_eccsi_fault_whitebox baseline unarmed valid ops only (delta baseline) * ./test_eccsi_fault_whitebox probe per-target allocation-site counts */ diff --git a/tests/unit-mcdc/test_ed25519_hash_fault_whitebox.c b/tests/unit-mcdc/test_ed25519_hash_fault_whitebox.c index 4253f09e66..89d616ff41 100644 --- a/tests/unit-mcdc/test_ed25519_hash_fault_whitebox.c +++ b/tests/unit-mcdc/test_ed25519_hash_fault_whitebox.c @@ -34,7 +34,7 @@ * Every open "ret == 0" operand left in ed25519.c takes its `ret` from the * SHA-512 chain (ed25519_hash_init/update/final -> wc_InitSha512_ex / * wc_Sha512Update / wc_Sha512Final) and from nothing else. ed25519.c performs - * no allocation on these paths in this campaign's configs (WOLFSSL_SMALL_STACK + * no allocation on these paths in this suite's configs (WOLFSSL_SMALL_STACK * is unset, so WC_DECLARE_VAR/WC_ALLOC_VAR_EX are a plain stack object and a * no-op), so mcdc_fault_alloc.h has nothing to fault -- only a failing hash * primitive can break the chain. mcdc_fault_hash.h shadows Update/Final; @@ -103,7 +103,7 @@ * fixed sweep length. main() always returns 0 -- a nonzero exit would discard * the variant's whole coverage. * - * Build: compiled by the campaign's white-box step with the same MC/DC CFLAGS + * Build: compiled by the white-box step with the same MC/DC CFLAGS * as the instrumented library, then linked against that variant's * libwolfssl.a with ed25519.o removed. Not part of the wolfSSL build. */ diff --git a/tests/unit-mcdc/test_ed25519_whitebox.c b/tests/unit-mcdc/test_ed25519_whitebox.c index 6a42c2d573..3b2215cba9 100644 --- a/tests/unit-mcdc/test_ed25519_whitebox.c +++ b/tests/unit-mcdc/test_ed25519_whitebox.c @@ -17,17 +17,17 @@ * none of them ever construct the "in==NULL && inLen>0" combination or pass * key/hash as NULL. This translation unit reaches all three operands' TRUE * sides (and completes the FALSE-side pairing within this same binary, per - * the campaign's cross-binary MC/DC lesson) by compiling ed25519.c directly + * the cross-binary MC/DC lesson) by compiling ed25519.c directly * (#include) and calling the static helper directly. * * Coverage from this binary is unioned with the tests/api variant coverage - * by source line:col in the per-module campaign (iso26262/mcdc-per-module): - * llvm-cov computes MC/DC independence PER BINARY, and the campaign's + * by source line:col in the per-module suite: + * llvm-cov computes MC/DC independence PER BINARY, and the * aggregate.sh ORs the "independence shown" bit across binaries by key. That * is why every pair below is completed *within this file* rather than * relying on the API tests to supply the other half. * - * Build: compiled by run-mcdc-par.sh's white-box step with the SAME MC/DC + * Build: compiled by the coverage runner's white-box step with the SAME MC/DC * CFLAGS, -DHAVE_CONFIG_H and -I as the instrumented library, * then linked against that variant's libwolfssl.a with its ed25519.o * removed (this TU supplies the instrumented ed25519.c). NOT part of the @@ -38,7 +38,7 @@ * Class 1 ed25519_hash() key/in/hash NULL guard ............ 4 conditions * The only ed25519.c gap confirmed structurally unreachable through the * public API (every wrapper hard-codes non-NULL, well-formed arguments). - * See the campaign's RESIDUALS.md for everything else: the "ret==0" FALSE + * See the RESIDUALS.md for everything else: the "ret==0" FALSE * sides following a successful ed25519_hash() call (would need a mockable * malloc/wc_InitSha512Ex failure to force), and the WOLFSSL_CHECK_VER_FAULTS * redundant post-verify ConstantCompare (a deterministic double-call on the @@ -248,7 +248,7 @@ int main(void) wb_ed25519_verify_helper_key_guard(); printf("done (%s)\n", wb_fail ? "with skips" : "ok"); /* Setup failures are surfaced as skips, not test failures: the - * campaign treats a nonzero exit as a failed variant and discards its + * suite treats a nonzero exit as a failed variant and discards its * coverage. */ return 0; #endif diff --git a/tests/unit-mcdc/test_ed448_hash_fault_whitebox.c b/tests/unit-mcdc/test_ed448_hash_fault_whitebox.c index 2d8c950a54..968701f0f6 100644 --- a/tests/unit-mcdc/test_ed448_hash_fault_whitebox.c +++ b/tests/unit-mcdc/test_ed448_hash_fault_whitebox.c @@ -34,7 +34,7 @@ * Every open "(ret == 0)" operand left in ed448.c takes its `ret` from the * SHAKE256 chain (ed448_hash_init/update/final -> wc_InitShake256 / * wc_Shake256_Update / wc_Shake256_Final) and from nothing else. ed448.c - * performs no allocation on these paths in this campaign's configs + * performs no allocation on these paths in this suite's configs * (WOLFSSL_SMALL_STACK is unset, so WC_DECLARE_VAR/WC_ALLOC_VAR_EX are a plain * stack object and a no-op), so mcdc_fault_alloc.h has nothing to fault -- * only a failing hash primitive can break the chain. mcdc_fault_hash.h shadows @@ -102,7 +102,7 @@ * wall clock: fixed seeds, fixed message, fixed sweep length. main() always * returns 0 -- a nonzero exit would discard the variant's whole coverage. * - * Build: compiled by the campaign's white-box step with the same MC/DC CFLAGS + * Build: compiled by the white-box step with the same MC/DC CFLAGS * as the instrumented library, then linked against that variant's * libwolfssl.a with ed448.o removed. Not part of the wolfSSL build. */ diff --git a/tests/unit-mcdc/test_ed448_whitebox.c b/tests/unit-mcdc/test_ed448_whitebox.c index 45d09ef6d9..20a84f42a4 100644 --- a/tests/unit-mcdc/test_ed448_whitebox.c +++ b/tests/unit-mcdc/test_ed448_whitebox.c @@ -16,17 +16,17 @@ * buffer) -- none of them ever construct the "in == NULL && inLen > 0" * combination or pass key/hash as NULL. This translation unit reaches all * three operands' TRUE sides (and completes the FALSE-side pairing within this - * same binary, per the campaign's cross-binary MC/DC lesson) by compiling + * same binary, per the cross-binary MC/DC lesson) by compiling * ed448.c directly (#include) and calling the static helper directly. * * Coverage from this binary is unioned with the tests/api variant coverage by - * source line:col in the per-module campaign (iso26262/mcdc-per-module): - * llvm-cov computes MC/DC independence PER BINARY, and the campaign's + * source line:col in the per-module suite: + * llvm-cov computes MC/DC independence PER BINARY, and the * aggregate.sh ORs the "independence shown" bit across binaries by key. That * is why every pair below is completed *within this file* rather than relying * on the API tests to supply the other half. * - * Build: compiled by run-mcdc-par.sh's white-box step with the SAME MC/DC + * Build: compiled by the coverage runner's white-box step with the SAME MC/DC * CFLAGS, -DHAVE_CONFIG_H and -I as the instrumented library, then * linked against that variant's libwolfssl.a with its ed448.o removed (this TU * supplies the instrumented ed448.c). NOT part of the wolfSSL build; not @@ -276,7 +276,7 @@ int main(void) wb_ed448_hash(); wb_ed448_verify_key_null(); printf("done (%s)\n", wb_fail ? "with skips" : "ok"); - /* Setup failures are surfaced as skips, not test failures: the campaign + /* Setup failures are surfaced as skips, not test failures: the harness * treats a nonzero exit as a failed variant and discards its coverage. */ return 0; #endif diff --git a/tests/unit-mcdc/test_falcon_whitebox.c b/tests/unit-mcdc/test_falcon_whitebox.c index 5f0c04c504..91b617dc14 100644 --- a/tests/unit-mcdc/test_falcon_whitebox.c +++ b/tests/unit-mcdc/test_falcon_whitebox.c @@ -48,7 +48,7 @@ * a genuine mid-computation error (PRNG squeeze failure, bigint overflow, an * out-of-range coefficient the sampler bounds forbid, a degenerate/non-invertible * key) are documented as WB_NOTE residuals rather than forced unsafely. main() - * always returns 0 so the campaign keeps the variant. + * always returns 0 so the harness keeps the variant. */ #include @@ -1321,13 +1321,13 @@ static void wb_sign_dyn_core_err(WC_RNG* rng) * mid-computation error or a degenerate/forbidden operand, which cannot be * driven crash-safely from a white-box harness. Their opposite (normal) half is * covered above (mostly by the real round-trip). Each of these is carried as an - * EXCLUSIONS.md row with the source-level argument for why no satisfying vector + * the exclusion record row with the source-level argument for why no satisfying vector * exists. * ------------------------------------------------------------------ */ static void wb_residuals(void) { /* Re-examined in step 7 (2026-08-20); still open, and the reasoning is in - * EXCLUSIONS.md so it is not redone. Two dead ends were ruled out for + * the exclusion record so it is not redone. Two dead ends were ruled out for * good: an all-zero (f, g) makes the denominator exactly 0, and 0/0 gives * NaN, whose comparison makes the FIRST operand true -- a row that is * already covered; and the wb_solve_ntru_babai_clamp trick above (256 @@ -1344,7 +1344,7 @@ static void wb_residuals(void) "|g|^2) with |F|,|G| < 2^61 (2-word CRT limbs), so |z| >= 2^63 " "needs both depth-1 field norms to nearly vanish at one FFT slot; " "x^n+1 is irreducible over Q so they never vanish exactly, but no " - "usable lower bound exists either -- see EXCLUSIONS.md"); + "usable lower bound exists either -- see the exclusion record"); WB_NOTE("residual: keygen f[u]/g[u] vs lim halves: lim is 1 << " "(falcon_max_fg_bits[logn] - 1), i.e. 32 at logn 9 and 16 at " "logn 10, while poly_small_mkgauss sums 1 << (10 - logn) draws of " diff --git a/tests/unit-mcdc/test_frodokem_cryptocb_whitebox.c b/tests/unit-mcdc/test_frodokem_cryptocb_whitebox.c index 265da48cf1..cb074bbd88 100644 --- a/tests/unit-mcdc/test_frodokem_cryptocb_whitebox.c +++ b/tests/unit-mcdc/test_frodokem_cryptocb_whitebox.c @@ -36,9 +36,9 @@ * ... * #endif * - * so the operand needs THREE things at once that no existing campaign vector + * so the operand needs THREE things at once that no existing suite vector * supplies: the build must define WOLF_CRYPTO_CB (it is now set in - * campaign/configs/frodokem/user_settings.base.h), the key must carry a real + * suite/configs/frodokem/user_settings.base.h), the key must carry a real * devId, and a registered device must actually service the request rather than * declining with CRYPTOCB_UNAVAILABLE. Miss any one and `cbHandled` is a * constant 0 -- not merely undriven, but with no false side to drive. @@ -404,6 +404,6 @@ int main(void) #endif printf("done (%s)\n", wb_fail ? "with failures" : "ok"); - /* A non-zero exit makes the campaign discard this binary's coverage. */ + /* A non-zero exit makes the harness discard this binary's coverage. */ return 0; } diff --git a/tests/unit-mcdc/test_frodokem_fault_common.h b/tests/unit-mcdc/test_frodokem_fault_common.h index eacca96112..7eb20887ac 100644 --- a/tests/unit-mcdc/test_frodokem_fault_common.h +++ b/tests/unit-mcdc/test_frodokem_fault_common.h @@ -78,7 +78,7 @@ * ./wb.test default: baseline valid ops + the full fault sweep * ./wb.test baseline only the unarmed valid ops (measure sweep as a delta) * ./wb.test probe print per-entry-point allocation counts (sizes K) - * The campaign's run_whitebox harness runs the binary with NO args, so the + * The suite's run_whitebox harness runs the binary with NO args, so the * default action is the full sweep. */ diff --git a/tests/unit-mcdc/test_frodokem_mat_fault_whitebox.c b/tests/unit-mcdc/test_frodokem_mat_fault_whitebox.c index 5dd371b562..4f0985a45a 100644 --- a/tests/unit-mcdc/test_frodokem_mat_fault_whitebox.c +++ b/tests/unit-mcdc/test_frodokem_mat_fault_whitebox.c @@ -34,7 +34,7 @@ * AES-ECB over the aligned scratch takes a non-allocating path). The heap-fault * mock therefore closes NONE of the 13 here -- they need a primitive-return * fault mock instead. This driver still runs the mat file end to end (baseline - * coverage) and is kept so the campaign has a documented, reproducible negative + * coverage) and is kept so the harness has a documented, reproducible negative * result. See test_frodokem_fault_common.h for the full rationale. */ diff --git a/tests/unit-mcdc/test_frodokem_mat_hash_fault_whitebox.c b/tests/unit-mcdc/test_frodokem_mat_hash_fault_whitebox.c index 48eb4bfb9f..a0ac2226d3 100644 --- a/tests/unit-mcdc/test_frodokem_mat_hash_fault_whitebox.c +++ b/tests/unit-mcdc/test_frodokem_mat_hash_fault_whitebox.c @@ -73,7 +73,7 @@ * only set with qMask != 0xffff, and the 976/1344 sets run the identical code * with a bigger n at several times the cost. The larger sets still get their * baseline pass. Everything is bounded by a point budget AND a CPU deadline so - * the binary can never hit the campaign's 600 s TEST_TIMEOUT (a timeout is a + * the binary can never hit the 600 s TEST_TIMEOUT (a timeout is a * SILENT SKIP that would lose the whole file). */ @@ -462,7 +462,7 @@ int main(void) } printf("done (%s)\n", wb_fail ? "with failures" : "ok"); - /* A non-zero exit makes the campaign discard this binary's coverage. */ + /* A non-zero exit makes the harness discard this binary's coverage. */ return 0; } diff --git a/tests/unit-mcdc/test_hpke_fault_whitebox.c b/tests/unit-mcdc/test_hpke_fault_whitebox.c index 7a2921d9e6..bd10d8ede1 100644 --- a/tests/unit-mcdc/test_hpke_fault_whitebox.c +++ b/tests/unit-mcdc/test_hpke_fault_whitebox.c @@ -93,7 +93,7 @@ * ./test_hpke_fault_whitebox default: baseline + fault sweeps * ./test_hpke_fault_whitebox baseline only the unarmed valid ops * ./test_hpke_fault_whitebox probe print per-target allocation counts - * (No-arg default runs the sweep so the campaign's run_whitebox harness, which + * (No-arg default runs the sweep so the run_whitebox harness, which * runs the binary with no arguments, gets full coverage.) */ diff --git a/tests/unit-mcdc/test_integer_fault_whitebox.c b/tests/unit-mcdc/test_integer_fault_whitebox.c index dda5b13f6c..1798f87afc 100644 --- a/tests/unit-mcdc/test_integer_fault_whitebox.c +++ b/tests/unit-mcdc/test_integer_fault_whitebox.c @@ -53,7 +53,7 @@ * exactly one earlier op returns MP_MEM, so exactly one operand of one chain is * driven TRUE (short-circuiting the rest) per call. The unarmed baseline call * supplies the all-FALSE half of every pair in the SAME binary (llvm-cov - * computes MC/DC per binary; the campaign unions the "independence shown" bit + * computes MC/DC per binary; the harness unions the "independence shown" bit * across binaries by line:col). * * Which operands are alloc-closable: mp_abs (grows a fresh temp from NULL), diff --git a/tests/unit-mcdc/test_integer_whitebox.c b/tests/unit-mcdc/test_integer_whitebox.c index a8e155c2be..aac40073ee 100644 --- a/tests/unit-mcdc/test_integer_whitebox.c +++ b/tests/unit-mcdc/test_integer_whitebox.c @@ -10,10 +10,10 @@ * mp_prime_miller_rabin, mp_prime_is_divisible, s_is_power_of_two, bn_reverse). * This translation unit reaches them by compiling integer.c directly (#include) * and calling the static helpers with BOTH halves of each targeted MC/DC pair in this - * one binary (llvm-cov computes MC/DC per binary; the campaign unions the + * one binary (llvm-cov computes MC/DC per binary; the harness unions the * "independence shown" bit across binaries by line:col). * - * Build: compiled by run-mcdc.sh's white-box step with the SAME MC/DC CFLAGS + * Build: compiled by the coverage runner's white-box step with the SAME MC/DC CFLAGS * and -I as the instrumented library, then linked against that * variant's libwolfssl.a with its integer.o removed (this TU supplies the * instrumented integer.c). NOT part of the wolfSSL build; not registered in @@ -21,7 +21,7 @@ * * Every call is memory-safe (static helpers are handed initialized mp_ints and * in-range selectors); setup failures print a skip and return 0 (a nonzero - * exit makes the campaign discard the variant and its coverage). + * exit makes the harness discard the variant and its coverage). */ #include @@ -1227,7 +1227,7 @@ int main(void) #endif printf("done (%s)\n", wb_fail ? "with skips" : "ok"); /* Setup failures surface as skips, not failures: a nonzero exit makes the - * campaign discard this variant's coverage. */ + * suite discard this variant's coverage. */ return 0; #endif } diff --git a/tests/unit-mcdc/test_kdf_hash_fault_whitebox.c b/tests/unit-mcdc/test_kdf_hash_fault_whitebox.c index 97ae8fdbd7..37d2c0d771 100644 --- a/tests/unit-mcdc/test_kdf_hash_fault_whitebox.c +++ b/tests/unit-mcdc/test_kdf_hash_fault_whitebox.c @@ -95,7 +95,7 @@ * targets, with an #else stub, so this TU builds under kdf_default, * small_stack, ticket_nonce_malloc and crypto_cb alike. * - * Build: compiled by the campaign's white-box step with the same MC/DC CFLAGS + * Build: compiled by the white-box step with the same MC/DC CFLAGS * as the instrumented library, then linked against that variant's * libwolfssl.a with kdf.o removed. Not part of the wolfSSL build. */ diff --git a/tests/unit-mcdc/test_kdf_whitebox.c b/tests/unit-mcdc/test_kdf_whitebox.c index ed78089e6f..c8b3db03fa 100644 --- a/tests/unit-mcdc/test_kdf_whitebox.c +++ b/tests/unit-mcdc/test_kdf_whitebox.c @@ -29,7 +29,7 @@ * * 1. wc_Tls13_HKDF_Extract_ex() (~line 354) * if (prk == NULL || (ikm == NULL && ikmLen > 0)) - * A public entry point, but the campaign's group tests only ever call it + * A public entry point, but the group tests only ever call it * with a valid prk and a present ikm, so none of the three operands gets * a pair. All four call shapes are memory-safe: the guard short-circuits * before either pointer is read, and the accepted "ikm == NULL && @@ -76,7 +76,7 @@ * this file cannot do. They are closed in the sibling translation unit * tests/unit-mcdc/test_kdf_hash_fault_whitebox.c instead. * - * Build: compiled by the campaign's white-box step with the same MC/DC CFLAGS + * Build: compiled by the white-box step with the same MC/DC CFLAGS * as the instrumented library, then linked against that variant's * libwolfssl.a with kdf.o removed. Not part of the wolfSSL build. */ diff --git a/tests/unit-mcdc/test_lms_bds_whitebox.c b/tests/unit-mcdc/test_lms_bds_whitebox.c index 9dca3929d8..d464e2dcd2 100644 --- a/tests/unit-mcdc/test_lms_bds_whitebox.c +++ b/tests/unit-mcdc/test_lms_bds_whitebox.c @@ -53,7 +53,7 @@ * hashes) that its whole primitive range is swept DENSELY, which is what * reaches the per-hash-family copies of the auth-path climb loop. * - * TARGETS (campaign/reports/lms/GAPS.md keys, wc_lms_impl.c) + * TARGETS (suite/reports/lms/the uncovered-condition report keys, wc_lms_impl.c) * 2109:...:0 wc_lms_treehash() `ret == 0` at the auth-path store * (WOLFSSL_WC_LMS_SMALL arm) -- fault * 2262:...:0 wc_lms_treehash_init() same, table-based arm -- fault @@ -104,11 +104,11 @@ * i == (i >> (h-1)) * 2^(h-1) + (2^(h-1) - 1). Adding one carries out of * bit h-1, so (i + 1) >> (h - 1) == (i >> (h - 1)) + 1: the two sides differ * by exactly one on every evaluation and the `!=` is never false. Recorded - * in campaign/db/exclusions.json and EXCLUSIONS.md. + * in the exclusion record and the exclusion record. * * COST: no keygen and no signing. The most expensive driver computes 8 WOTS * leaves; the whole program is a few hundred thousand SHA-256 blocks, orders - * of magnitude inside the campaign's 600 s TEST_TIMEOUT. + * of magnitude inside the 600 s TEST_TIMEOUT. * * VARIANTS (HARD RULE: must compile under every variant of the module): * WOLFSSL_LMS_VERIFY_ONLY - keygen/signing static helpers are compiled @@ -543,7 +543,7 @@ static void wb_treehash_init_fault(const WbFam* f) * leaves 0..3 are served from the leaf cache, so wc_lms_treehash_update * issues NO primitive call until the carry chain of leaf 1 reaches * wc_lms_interior_hash() -- primitive call 1 IS that node hash. - * These two conditions are the pair the campaign's 2026-08-11 flake + * These two conditions are the pair the 2026-08-11 flake * hunt recorded as non-deterministic (they depend on where the global * strided hash-fault sweep in test_lms_hash_fault_whitebox.c happens to * land, which moves with the RNG-drawn key). This vector pins them. @@ -781,6 +781,6 @@ int main(void) printf("done (%s)\n", wb_fail ? "with skips" : "ok"); /* Setup problems are printed skips, never a non-zero exit: a non-zero - * exit makes the campaign discard this binary's whole coverage. */ + * exit makes the harness discard this binary's whole coverage. */ return 0; } diff --git a/tests/unit-mcdc/test_lms_fault_whitebox.c b/tests/unit-mcdc/test_lms_fault_whitebox.c index 9bb9c0be3e..20ddf961c9 100644 --- a/tests/unit-mcdc/test_lms_fault_whitebox.c +++ b/tests/unit-mcdc/test_lms_fault_whitebox.c @@ -37,7 +37,7 @@ * Keygen cost: every real wc_LmsKey_MakeKey/Reload below uses the smallest * mapped parameter set, levels=1 height=5 width=8 (WC_LMS_PARM_L1_H5_W8, 32 * leaves) -- the same set test_wc_lms_impl_whitebox.c uses for its per-family - * roundtrip. WOLFSSL_LMS_MAX_LEVELS is pinned to 2 by this module's campaign + * roundtrip. WOLFSSL_LMS_MAX_LEVELS is pinned to 2 by this module's suite * config, so no larger key is attempted here. * * VERIFY_ONLY: wc_LmsKey_SetLmsParm/SetParameters(_ex)/GetParameters(_ex), @@ -51,7 +51,7 @@ * whole group is behind one #ifndef with a skip stub. * * No allocation-fault sweep here: every uncovered decision in - * campaign/reports/lms/GAPS.md for wc_lms.c is a NULL/argument guard or a + * suite/reports/lms/the uncovered-condition report for wc_lms.c is a NULL/argument guard or a * state-machine check, not a post-XMALLOC error chain, so mcdc_fault_alloc.h * is not needed by this file. */ @@ -149,7 +149,7 @@ static int wb_read_exhausted(byte* priv, word32 privSz, void* context) /******************************************************************* * wc_LmsKey_InitId (665, 668, 674) / wc_LmsKey_InitLabel (698, 703). - * Only compiled when WOLF_PRIVATE_KEY_ID is set; this campaign's base + * Only compiled when WOLF_PRIVATE_KEY_ID is set; this suite's base * enables HAVE_PK_CALLBACKS, which settings.h auto-derives it from. * * 665: if ((key == NULL) || ((id == NULL) && (len != 0))) @@ -171,7 +171,7 @@ static int wb_read_exhausted(byte* priv, word32 privSz, void* context) * B=F (id==NULL, len!=0, ret==0) is UNREACHABLE: 665 already forces * ret=BAD_FUNC_ARG whenever id==NULL && len!=0, so "ret==0 && id==NULL" * can never coexist with len!=0. No test call issued for this row -- - * DEATHNOTE candidate, see task report. + * ******************************************************************/ #ifdef WOLF_PRIVATE_KEY_ID static void wb_initid(void) @@ -356,7 +356,7 @@ static void wb_setlmsparm_setparams(void) WB_NOTE("819 SetParameters state leaves closed"); /* --- 879 SetParameters_ex: only the ret==0 operand is flagged in - * GAPS.md, but its independence pair still needs the *wrong-state* row + * the uncovered-condition report, but its independence pair still needs the *wrong-state* row * held alongside it: for (ret==0) && (state!=INITED), the ret==0 * operand's own pair requires the OTHER operand pinned TRUE (masking * MC/DC on an AND chain -- pinning it FALSE, i.e. the plain success @@ -1411,7 +1411,7 @@ int main(void) printf("done (%s)\n", wb_fail ? "with failures" : "ok"); /* Setup/skip conditions are surfaced as notes, not process failures: - * the campaign discards a variant's whole coverage on non-zero exit. */ + * the harness discards a variant's whole coverage on non-zero exit. */ return 0; } diff --git a/tests/unit-mcdc/test_lms_hash_fault_whitebox.c b/tests/unit-mcdc/test_lms_hash_fault_whitebox.c index 73f8549024..45d25c5121 100644 --- a/tests/unit-mcdc/test_lms_hash_fault_whitebox.c +++ b/tests/unit-mcdc/test_lms_hash_fault_whitebox.c @@ -24,7 +24,7 @@ * * WHAT IS LEFT AFTER THE OTHER TWO LMS WHITE-BOXES * ------------------------------------------------ - * campaign/reports/lms/GAPS.md is dominated by ONE shape inside + * suite/reports/lms/the uncovered-condition report is dominated by ONE shape inside * wc_lms_impl.c's WOTS / Merkle / HSS engine: * * for (i = 0; (ret == 0) && (i < params->p); i++) ... @@ -34,7 +34,7 @@ * * The (ret == 0) operand only ever goes FALSE when an earlier step failed * *inside the same operation*. wc_lms_impl.c performs ZERO allocations - * (grep XMALLOC: none), so mcdc_fault_alloc.h -- the campaign's usual lever -- + * (grep XMALLOC: none), so mcdc_fault_alloc.h -- the usual lever -- * has nothing to fault here: `ret` in this file comes exclusively from * wc_Sha256HashBlock / wc_Sha256Update / wc_Sha256Final (and the SHAKE * equivalents). test_wc_lms_impl_whitebox_gap.c already closed everything that @@ -78,7 +78,7 @@ * a CPU-time deadline (WB_DEADLINE_S). The parameter set is the smallest that * still exercises the HSS multi-level machinery: levels=2, height=2 (16 * signatures total, subtree rollover after 4), Winternitz w=8, SHA-256/32. - * WOLFSSL_LMS_MAX_LEVELS is pinned to 2 by this module's campaign config, so + * WOLFSSL_LMS_MAX_LEVELS is pinned to 2 by this module's suite config, so * levels=2 is the maximum available. * * VARIANT COVERAGE (HARD RULE 3): WOLFSSL_LMS_VERIFY_ONLY compiles keygen and @@ -169,7 +169,7 @@ static const WbFamily wb_families[] = { * chosen so the total never exceeds WB_POINTS. */ #define WB_DENSE 48 #define WB_POINTS 192 -/* Hard CPU-time ceiling for the whole program, well under the campaign's +/* Hard CPU-time ceiling for the whole program, well under the * 600 s TEST_TIMEOUT even with variants running concurrently and even in the * (much slower) WOLFSSL_WC_LMS_SMALL recompute build. Every sweep tests it, so * the program degrades to fewer points rather than being killed -- a killed @@ -370,7 +370,7 @@ static int wb_do_make_key(void) * ~60 pinned keys inside one driver, multiplying a 10-minute module by * 30. Trading 40 real conditions for determinism on 3 is a bad deal, * so the two conditions above are recorded as known-flaky in the - * module residual note instead. See STEP6.md, flake hunt 2026-08-11. */ + * module residual note instead. See the working notes, flake hunt 2026-08-11. */ ret = wc_hss_make_key(&state, &wb_rng, wb_priv_raw, &wb_pk, wb_priv_data, wb_pub); wb_state_free(&state); @@ -660,6 +660,6 @@ int main(void) printf("done (%s)\n", wb_fail ? "with failures" : "ok"); /* Setup/skip conditions are notes, not process failures: a non-zero exit - * makes the campaign discard this binary's whole coverage. */ + * makes the harness discard this binary's whole coverage. */ return 0; } diff --git a/tests/unit-mcdc/test_logging_whitebox.c b/tests/unit-mcdc/test_logging_whitebox.c index 6c7d4523b9..b546c058ca 100644 --- a/tests/unit-mcdc/test_logging_whitebox.c +++ b/tests/unit-mcdc/test_logging_whitebox.c @@ -23,7 +23,7 @@ * White-box MC/DC supplement for wolfcrypt/src/logging.c -- CORE (non * OpenSSL-compat) decisions only. * - * The campaign's default build for this module measures 0 MC/DC on + * The suite's default build for this module measures 0 MC/DC on * logging.c's core decisions because every one of them lives behind a * debug/error-queue macro that the default variant does not enable * (DEBUG_WOLFSSL / WOLFSSL_DEBUG_CERTS for certificate logging, @@ -34,7 +34,7 @@ * tests/unit-mcdc/README.md's per-binary MC/DC contract). * * IMPORTANT SCOPE: OPENSSL_EXTRA is deliberately NEVER defined here. The - * per-module campaign excludes logging.c's OPENSSL_EXTRA-guarded (OpenSSL + * per-module suite excludes logging.c's OPENSSL_EXTRA-guarded (OpenSSL * compatibility) decisions from its MC/DC boundary; this file only targets * the CORE decisions. Where a core decision needs the same top-level guard * that also (independently) admits OPENSSL_EXTRA builds (e.g. the error diff --git a/tests/unit-mcdc/test_memory_whitebox.c b/tests/unit-mcdc/test_memory_whitebox.c index ea7937e760..82df762315 100644 --- a/tests/unit-mcdc/test_memory_whitebox.c +++ b/tests/unit-mcdc/test_memory_whitebox.c @@ -55,7 +55,7 @@ * Verified against a throwaway library built with: * ./configure --enable-usersettings --enable-static --disable-shared \ * --enable-staticmemory - * (CC=clang, CFLAGS/LDFLAGS carrying the campaign's + * (CC=clang, CFLAGS/LDFLAGS carrying the * -fprofile-instr-generate -fcoverage-mapping -fcoverage-mcdc, plus * -Wno-error=unused-function: wc_MemFailCount_AllocMem/FreeMem (memory.c * :150/:165) are `static` helpers whose only OTHER caller is the diff --git a/tests/unit-mcdc/test_mldsa_fault_whitebox.c b/tests/unit-mcdc/test_mldsa_fault_whitebox.c index a125b94de4..2f60e2746b 100644 --- a/tests/unit-mcdc/test_mldsa_fault_whitebox.c +++ b/tests/unit-mcdc/test_mldsa_fault_whitebox.c @@ -48,7 +48,7 @@ * buffer guard. * * These working-buffer allocations exist in every variant with the base - * campaign config (the WC_MLDSA_CACHE_* caching macros are OFF, so sign/verify + * suite config (the WC_MLDSA_CACHE_* caching macros are OFF, so sign/verify * XMALLOC their scratch on every call); they multiply under the small-memory * arms (WOLFSSL_MLDSA_SIGN_SMALL_MEM / _VERIFY_SMALL_MEM / * _SIGN_SMALL_MEM_PRECALC_A), whose per-column recompute blocks add extra @@ -86,7 +86,7 @@ * ./test_mldsa_fault_whitebox default: baseline + fault sweeps * ./test_mldsa_fault_whitebox baseline unarmed valid ops only (delta base) * ./test_mldsa_fault_whitebox probe print per-entry-point alloc counts - * (The campaign run_whitebox harness runs this binary with NO arguments, so the + * (The white-box harness runs this binary with NO arguments, so the * default action is the full sweep.) * * WHY WOLFSSL_SMALL_STACK IS FORCED BELOW diff --git a/tests/unit-mcdc/test_mldsa_hash_fault_whitebox.c b/tests/unit-mcdc/test_mldsa_hash_fault_whitebox.c index 835c7f49ba..57d53a2878 100644 --- a/tests/unit-mcdc/test_mldsa_hash_fault_whitebox.c +++ b/tests/unit-mcdc/test_mldsa_hash_fault_whitebox.c @@ -67,7 +67,7 @@ * needs key == NULL, which the public wrapper rejects before calling. The * static helper is in scope here and is called directly. * - * DOCUMENTED RESIDUALS (mirrored in campaign/db/exclusions.json): + * DOCUMENTED RESIDUALS (mirrored in the exclusion record): * - `for (; (ret == 0) && valid && (r < params->k); r++)` and * `if ((ret == 0) && valid)`: `valid` is assigned 1 unconditionally and is * only ever assigned 0 inside `#ifdef WOLFSSL_MLDSA_SIGN_CHECK_Y`, @@ -308,7 +308,7 @@ static void wb_sign_no_private(void) /* The partner row: the SAME entry point on a key that DOES have a private * key, so the operand is false with the decision reaching its other - * outcome. Nothing else in the campaign calls the seeded pre-hash signer, + * outcome. Nothing else in the harness calls the seeded pre-hash signer, * so without this the operand above has no vector to pair against. */ #ifndef WOLFSSL_MLDSA_NO_MAKE_KEY XMEMSET(&key, 0, sizeof(key)); @@ -442,7 +442,7 @@ static void wb_verify_ctx_hash_null(void) * two. An empty privateKey with no seed falls to asn.c:33992 and returns * ASN_PARSE_E. privKeyLen != 0 therefore holds on every arrival at both * else-ifs, and all three operands are recorded in - * campaign/db/exclusions.json rather than left open. The rows below stay: they + * the exclusion record rather than left open. The rows below stay: they * are what establishes the rejection, and they cost two decodes. * * The AlgorithmIdentifier carries id-ml-dsa-44 (2.16.840.1.101.3.4.3.17) @@ -667,7 +667,7 @@ int main(void) mcdc_fh_disarm(); printf("done (%s)\n", wb_fail ? "with failures" : "ok"); - /* A non-zero exit makes the campaign discard this binary's coverage. */ + /* A non-zero exit makes the harness discard this binary's coverage. */ return 0; } diff --git a/tests/unit-mcdc/test_mlkem_fault_whitebox.c b/tests/unit-mcdc/test_mlkem_fault_whitebox.c index 5d653244c1..ef38b3e662 100644 --- a/tests/unit-mcdc/test_mlkem_fault_whitebox.c +++ b/tests/unit-mcdc/test_mlkem_fault_whitebox.c @@ -50,11 +50,11 @@ * * SCOPE NOTE: wc_mlkem_poly.c's uncovered decisions are NOT addressed here. * Its only heap-allocation sites are all guarded by WOLFSSL_SMALL_STACK (which - * no ML-KEM campaign variant defines), and its remaining `(ret==0) && ...` loop + * no ML-KEM suite variant defines), and its remaining `(ret==0) && ...` loop * guards go non-zero only via a mid-loop PRF/XOF failure, not via an allocation * -- neither is reachable through a pass-through allocation fault. The bulk of * wc_mlkem_poly.c's residuals are AVX2 cpuid-dispatch and rejection-sampling - * data-path decisions (a separate, input-driven effort). See the campaign + * data-path decisions (a separate, input-driven effort). See the harness * report for the full accounting. * * It #includes wc_mlkem.c directly (like the other unit-mcdc white-boxes) to @@ -70,7 +70,7 @@ * ./test_mlkem_fault_whitebox full fault-index sweep (default) * ./test_mlkem_fault_whitebox baseline unarmed valid ops only (delta base) * ./test_mlkem_fault_whitebox probe print per-entry-point alloc counts - * (The campaign run_whitebox harness runs the binary with NO arguments, so the + * (The white-box harness runs the binary with NO arguments, so the * default action is the full sweep.) */ diff --git a/tests/unit-mcdc/test_mlkem_poly_hash_fault_whitebox.c b/tests/unit-mcdc/test_mlkem_poly_hash_fault_whitebox.c index d4807f2d1e..ad044e4d24 100644 --- a/tests/unit-mcdc/test_mlkem_poly_hash_fault_whitebox.c +++ b/tests/unit-mcdc/test_mlkem_poly_hash_fault_whitebox.c @@ -22,7 +22,7 @@ /* * MC/DC hash-fault white-box supplement for wolfcrypt/src/wc_mlkem_poly.c. * - * campaign/reports/mlkem/GAPS.md leaves ten residuals on this file and all ten + * suite/reports/mlkem/the uncovered-condition report leaves ten residuals on this file and all ten * are the FALSE half of a success chain or an optional-argument guard that the * public wc_MlKemKey_* API never produces: * @@ -40,7 +40,7 @@ * All four helpers are file-static (or WOLFSSL_LOCAL) and this TU #includes * wc_mlkem_poly.c, so each is called DIRECTLY with the operand combination the * API cannot produce. No key generation is performed at all, which keeps the - * whole binary well inside the campaign's wall-clock budget. + * whole binary well inside the wall-clock budget. * * 1. mlkem_hash512()'s three conditions need no injector: * - data2 == NULL and data2Len == 0 are simply passed in; @@ -72,7 +72,7 @@ * NOT REACHABLE HERE: under USE_INTEL_SPEEDUP mlkem_prf() writes the Keccak * state directly (sha3_block_bmi2 / sha3_block_avx2 / BlockSha3) and returns a * literal 0, so mlkem_get_noise_c()'s chain cannot be broken in the mlkem_avx2 - * variant. The six portable-C variants supply those rows and the campaign + * variant. The six portable-C variants supply those rows and the harness * unions by line:col. * * VARIANT COVERAGE (HARD RULE 2): every helper is behind the same #if the @@ -313,6 +313,6 @@ int main(void) mcdc_fh_disarm(); printf("done (%s)\n", wb_fail ? "with failures" : "ok"); - /* A non-zero exit makes the campaign discard this binary's coverage. */ + /* A non-zero exit makes the harness discard this binary's coverage. */ return 0; } diff --git a/tests/unit-mcdc/test_pkcs12_fault_whitebox.c b/tests/unit-mcdc/test_pkcs12_fault_whitebox.c index 4ac8481e6a..ab2a02a636 100644 --- a/tests/unit-mcdc/test_pkcs12_fault_whitebox.c +++ b/tests/unit-mcdc/test_pkcs12_fault_whitebox.c @@ -22,14 +22,14 @@ /* * MC/DC white-box supplement for wolfcrypt/src/pkcs12.c, closing the last * closable residual left after test_pkcs12_whitebox.c and - * test_pkcs12_parse_whitebox.c (GAPS.md: 58/65): PKCS12_CheckConstructedZero() + * test_pkcs12_parse_whitebox.c (the uncovered-condition report: 58/65): PKCS12_CheckConstructedZero() * * if (ret == 0 && GetObjectId(data, idx, &oid, oidIgnoreType, dataSz)) { * (pkcs12.c:1239) * * condition index 0 (`ret == 0`). * - * Both rows this condition needs already exist in the campaign -- just not + * Both rows this condition needs already exist in the harness -- just not * in the same binary. test_pkcs12_whitebox.c's wb_check_constructed_zero() * drives a valid chain (ret==0 entering 1239, GetObjectId succeeds -> * (T,F)=FALSE) and a chain truncated right after the outer SEQUENCE header @@ -57,7 +57,7 @@ * The function is static, so it is called directly (same idiom as the two * other pkcs12 white-boxes: #include pkcs12.c to reach file-static helpers). * - * The other six GAPS.md residuals are all structurally unreachable and are + * The other six the uncovered-condition report residuals are all structurally unreachable and are * deliberately NOT exercised here -- inventing a vector for a decision that * cannot occur would misrepresent the code as more exercised than it is. * Each was independently re-derived from the current source (not taken on @@ -113,9 +113,9 @@ * `ret < 0` check could see as false while the length-only comparison * that precedes it stays true. Dead code. * - * All six are logged as DEATHNOTE candidates by the caller; not repeated as + * All six are recorded by the caller; not repeated as * test code here. mcdc_fault_alloc.h is included for idiom consistency with - * the rest of the campaign's *_fault_whitebox.c files, but is unused: the + * the rest of the *_fault_whitebox.c files, but is unused: the * one closable residual here is a pure ASN decode-path decision, not an * allocation-failure guard. */ @@ -191,7 +191,7 @@ int main(void) printf("pkcs12.c fault white-box MC/DC supplement\n"); wb_check_zero_cond0(); printf("done (%s)\n", wb_fail ? "with failures" : "ok"); - /* Always return 0: a nonzero exit makes the campaign discard the whole + /* Always return 0: a nonzero exit makes the harness discard the whole * variant's coverage, including the parts that did succeed. */ return 0; } diff --git a/tests/unit-mcdc/test_pkcs12_parse_whitebox.c b/tests/unit-mcdc/test_pkcs12_parse_whitebox.c index ac64bc121f..d7d29f3ff8 100644 --- a/tests/unit-mcdc/test_pkcs12_parse_whitebox.c +++ b/tests/unit-mcdc/test_pkcs12_parse_whitebox.c @@ -505,7 +505,7 @@ int main(void) wb_d2i_eoc_skip(); wb_encrypted_zero_check(); printf("done (%s)\n", wb_fail ? "with skips" : "ok"); - /* Always return 0: a nonzero exit makes the campaign discard the whole + /* Always return 0: a nonzero exit makes the harness discard the whole * variant's coverage, including the parts that did succeed. */ return 0; } diff --git a/tests/unit-mcdc/test_pkcs12_whitebox.c b/tests/unit-mcdc/test_pkcs12_whitebox.c index 52fb3566fa..1b63a55609 100644 --- a/tests/unit-mcdc/test_pkcs12_whitebox.c +++ b/tests/unit-mcdc/test_pkcs12_whitebox.c @@ -8,11 +8,11 @@ * wrapper pre-validates). This translation unit compiles pkcs12.c directly * (#include) to reach its static helpers and calls them with both halves of * each targeted MC/DC independence pair. Heap-allocation failures use the - * shared campaign fault injector (mcdc_fault_alloc.h) to force a specific + * shared suite fault injector (mcdc_fault_alloc.h) to force a specific * XMALLOC call to return NULL deterministically. * * Coverage from this binary is unioned with the tests/api variant coverage by - * source line:col in the per-module campaign (iso26262/mcdc-per-module). + * source line:col in the per-module suite. * * Targeted residuals (pkcs12.c), by class: * Class 1 GetSignData() digest/salt alloc-failure guards ....... 2 conds @@ -38,7 +38,7 @@ * once GetLength has succeeded. Confirmed empirically (a totalSz small * enough to trip the overflow makes GetLength itself fail first, with a * BUFFER_E/ASN_PARSE_E return, never reaching this line with digest/salt - * already allocated). Logged in DEATHNOTE.md (Part 5 findings) as + * already allocated). Logged in the defect notes as * dead/simplify candidates; only the alloc-failure half is exercised here. * - wc_PKCS12_create_mac() kLen<0 (line ~599): every hash OID that * wc_OidGetHash() maps to a non-NONE wc_HashType is guarded in @@ -130,7 +130,7 @@ static void wb_free_signdata(WC_PKCS12* pkcs12) /* Class 1: GetSignData() digest/salt alloc-failure guards (pkcs12.c:445 * mac->digest==NULL; pkcs12.c:477 mac->salt==NULL -- the reachable half of * each `|| size+curIdx>totalSz` guard; see file header for why the size half - * is dead code, logged in DEATHNOTE.md). Both operands normally false (real + * is dead code). Both operands normally false (real * DER + successful alloc); the alloc-failure half is white-box only, reached * here with the shared fault injector on a static-function-direct call. */ static void wb_getsigndata(void) @@ -166,7 +166,7 @@ static void wb_getsigndata(void) mcdc_fa_restore(); WB_NOTE("GetSignData digest/salt alloc-failure pairs exercised " - "(size-overflow half is dead code, see file header / DEATHNOTE.md)"); + "(size-overflow half is dead code, see file header)"); } /* Class 2: wc_PKCS12_create_mac() NULL guard (pkcs12.c:546-547) and the @@ -651,7 +651,7 @@ int main(void) wb_check_constructed_zero(); wb_shroud_and_keybag(); printf("done (%s)\n", wb_fail ? "with skips" : "ok"); - /* Setup failures are surfaced as skips, not test failures: the campaign + /* Setup failures are surfaced as skips, not test failures: the harness * treats a nonzero exit as a failed variant and discards its coverage. */ return 0; } diff --git a/tests/unit-mcdc/test_pkcs7_arg_whitebox.c b/tests/unit-mcdc/test_pkcs7_arg_whitebox.c index a403139a62..2217b60a03 100644 --- a/tests/unit-mcdc/test_pkcs7_arg_whitebox.c +++ b/tests/unit-mcdc/test_pkcs7_arg_whitebox.c @@ -34,8 +34,7 @@ * accepting vector only has to make the guard evaluate false; failing * deeper in is fine and expected. * - * ARGUED UNREACHABLE, do not re-open (also in the campaign's EXCLUSIONS.md - * and db/exclusions.json): + * ARGUED UNREACHABLE, do not re-open: * * :4183 cond 1 (`pkcs7->sidType != DEGENERATE_SID`). PKCS7_EncodeSigned's * only assignment of a non-zero flatSignedAttribsSz is at :3836, inside @@ -1597,7 +1596,7 @@ static void wb_small_matrices(void) /* all-false baseline: the 3 operand-true rows above never pair against * a call that gets past the guard, so the guard's independence pairs * were never actually closed (mirrors the arg-guard trap noted in the - * campaign brief -- an operand-true-only batch without the baseline). */ + * suite brief -- an operand-true-only batch without the baseline). */ { wc_PKCS7 hp; int ret; @@ -1994,7 +1993,7 @@ int main(void) printf("done (%s)\n", wb_fail ? "with failures" : "ok"); /* Always return 0: a nonzero exit discards this variant's coverage - * entirely in the campaign harness. Failures are surfaced via the + * entirely in the test harness. Failures are surfaced via the * printed [FAIL] lines instead. */ (void)wb_fail; return 0; diff --git a/tests/unit-mcdc/test_pkcs7_decode_whitebox.c b/tests/unit-mcdc/test_pkcs7_decode_whitebox.c index 31f054c24d..973637116d 100644 --- a/tests/unit-mcdc/test_pkcs7_decode_whitebox.c +++ b/tests/unit-mcdc/test_pkcs7_decode_whitebox.c @@ -50,8 +50,8 @@ * The "ret == 0 && Get*(...)" idiom in this file produces a large number of * operands that have no MC/DC independence pair for a reason in the source, * not for want of a test. They fall into four provable families. Recording - * them here so the next pass does not re-spend effort on them; the campaign - * EXCLUSIONS.md carries the same arguments. + * them here so the next pass does not re-spend effort on them; the harness + * the exclusion record carries the same arguments. * * (1) LEADING OPERAND OF THE FIRST LINK IN A SWITCH CASE. `ret` is a local * initialised to 0, and the only statement before the link is a @@ -122,7 +122,7 @@ * never 0 on arrival at VERIFY_STAGE3. * * Added by the 2026-08-20 streaming-state wave (Sections 18-19 below), both - * filed in campaign/db/exclusions.json: + * filed in the exclusion record: * * :7516 cond 2 -- family (4). stream->content is freed and NULLed at * :7501-:7502 before VERIFY_STAGE3 runs, so it is @@ -1856,7 +1856,7 @@ static void wb_ktri_key_alg_dispatch(void) * the call answers WC_PKCS7_WANT_READ_E. * * cond 1 (`stream->length < stream->expected`) has no false row and is filed - * in EXCLUSIONS.md: `expected` was just assigned `sz + MAX_ALGO_SZ + + * in the exclusion record: `expected` was just assigned `sz + MAX_ALGO_SZ + * ASN_TAG_SZ + MAX_LENGTH_SZ + 512` (>= 538) two statements above, while * stream->length can never exceed the largest `expected` ever passed to * wc_PKCS7_AddDataToStream() on the way here -- MAX_OID_SZ + MAX_LENGTH_SZ + @@ -2033,7 +2033,7 @@ static void wb_stage7_signature_rows(void) #endif /* !NO_PKCS7_STREAM && !NO_RSA && !NO_SHA256 */ /* ------------------------------------------------------------------------- * - * main -- always returns 0 so the campaign harness keeps this variant's + * main -- always returns 0 so the test harness keeps this variant's * coverage even if an individual sub-section's build config disables it. * ------------------------------------------------------------------------- */ int main(void) diff --git a/tests/unit-mcdc/test_pkcs7_fault_whitebox.c b/tests/unit-mcdc/test_pkcs7_fault_whitebox.c index 5ceaaeba46..30020c27d5 100644 --- a/tests/unit-mcdc/test_pkcs7_fault_whitebox.c +++ b/tests/unit-mcdc/test_pkcs7_fault_whitebox.c @@ -1037,7 +1037,7 @@ static void wb_alloc_fault_encodestream(void) * Only compiled when the streaming state machine exists at all * (wc_PKCS7_CreateStream/GrowStream/FreeStream are themselves inside * `#ifndef NO_PKCS7_STREAM` in pkcs7.c, so they are simply not there to - * call under the no_stream campaign variant). + * call under the no_stream suite variant). * ------------------------------------------------------------------------- */ #ifndef NO_PKCS7_STREAM static void wb_growstream_bufsz(void) @@ -1080,7 +1080,7 @@ static void wb_growstream_bufsz(void) /* ------------------------------------------------------------------------- * * Section 11: wc_PKCS7_AddRecipient_KTRI() WOLFSSL_SMALL_STACK alloc-guard * `decoded == NULL || serial == NULL || keyAlgArray == NULL` [:9371]. This - * guard only exists in the WOLFSSL_SMALL_STACK build (small_stack campaign + * guard only exists in the WOLFSSL_SMALL_STACK build (small_stack suite * variant) -- in every other variant these three locals are plain stack * arrays and the line is not even compiled, so the sweep below is inert * (still safe) elsewhere. Allocation order is fixed by source order: serial @@ -1268,7 +1268,7 @@ int main(void) printf("done (%s)\n", wb_fail ? "with failures" : "ok"); /* Always return 0: a nonzero exit discards this variant's coverage - * entirely in the campaign harness. Failures are surfaced via the + * entirely in the test harness. Failures are surfaced via the * printed [FAIL] lines instead. */ (void)wb_fail; return 0; diff --git a/tests/unit-mcdc/test_pkcs7_mutate_whitebox.c b/tests/unit-mcdc/test_pkcs7_mutate_whitebox.c index 0978641e54..8bacdedfc1 100644 --- a/tests/unit-mcdc/test_pkcs7_mutate_whitebox.c +++ b/tests/unit-mcdc/test_pkcs7_mutate_whitebox.c @@ -1043,7 +1043,7 @@ int main(void) printf("done (%s)\n", wb_fail ? "with failures" : "ok"); /* Always return 0: a nonzero exit discards this variant's coverage - * entirely in the campaign harness. */ + * entirely in the test harness. */ (void)wb_fail; return 0; } diff --git a/tests/unit-mcdc/test_pkcs7_whitebox.c b/tests/unit-mcdc/test_pkcs7_whitebox.c index 8805fee507..e73af2a31d 100644 --- a/tests/unit-mcdc/test_pkcs7_whitebox.c +++ b/tests/unit-mcdc/test_pkcs7_whitebox.c @@ -23,7 +23,7 @@ * First white-box MC/DC supplement for wolfcrypt/src/pkcs7.c (Part 5). * * 17.8k lines, 245/1058 conditions covered by tests/api at the start of this - * file's existence -- the largest deficit left in the campaign after asn.c. + * file's existence -- the largest deficit left in the harness after asn.c. * Most of the file-static parsing/encoding helpers guard against argument * combinations that every public wrapper already rejects before calling in * (NULL/size cross-checks), or are internal state machines (streaming, @@ -32,8 +32,8 @@ * drives those helpers by hand. * * Coverage is unioned by source line:col with the tests/api pkcs7 run in the - * per-module campaign; only conditions NOT already shown by tests/api are - * targeted below (cross-checked against campaign/reports/pkcs7/GAPS.md at + * per-module suite; only conditions NOT already shown by tests/api are + * targeted below (cross-checked against suite/reports/pkcs7/the uncovered-condition report at * the time of writing). * * NOT covered here (residual, needs follow-up): @@ -46,7 +46,7 @@ * - wc_PKCS7_DecryptKtri/Kari/Kekri/Pwri/Ori internal chains past the * first ASN.1 element (need a valid partial RecipientInfo body). * - wc_PKCS7_AddRecipient_KTRI's WOLFSSL_SMALL_STACK alloc-fail guard - * (needs fault-injection, deferred technique per campaign notes). + * (needs fault-injection, deferred technique per suite notes). * - ML-DSA SignedData sign/verify (WC_PKCS7_HAVE_MLDSA not defined in * this module's config base -- WOLFSSL_HAVE_MLDSA is off). * - wc_PKCS7_CertMatchesSignerInfo's IssuerAndSerialNumber compare @@ -2061,7 +2061,7 @@ int main(void) printf("done (%s)\n", wb_fail ? "with failures" : "ok"); /* Always return 0: a nonzero exit discards this variant's coverage - * entirely in the campaign harness. Failures are surfaced via the + * entirely in the test harness. Failures are surfaced via the * printed [FAIL] lines instead. */ (void)wb_fail; return 0; diff --git a/tests/unit-mcdc/test_poly1305_whitebox.c b/tests/unit-mcdc/test_poly1305_whitebox.c index 0a77f364ff..525114a910 100644 --- a/tests/unit-mcdc/test_poly1305_whitebox.c +++ b/tests/unit-mcdc/test_poly1305_whitebox.c @@ -30,11 +30,11 @@ * else poly1305_*_avx(...); * * Each of these is a single-condition branch (not a compound MC/DC decision: - * poly1305.c's own db/modules.json-measured MC/DC total is unaffected by + * poly1305.c's own the module registry-measured MC/DC total is unaffected by * which of these paths a given build takes), so this white-box does not - * change the campaign's covered/total counts. It is kept anyway, matching + * change the covered/total counts. It is kept anyway, matching * the intel-dispatch technique used by the aes/sha3 white-boxes and this - * campaign's chacha sibling, for FEATURE/branch-coverage evidence that the + * suite's chacha sibling, for FEATURE/branch-coverage evidence that the * AVX2-false (AVX1-only) side is reachable and correct: on an AVX2-capable * CI host, cpuid_get_flags_ex()'s real detection always takes the AVX2 * branch through the public API, so tests/api alone never demonstrates the @@ -45,7 +45,7 @@ * WC_CPUID_INITIALIZER. Forcing intel_flags to a real (non-initializer) * value before calling wc_Poly1305SetKey() makes it trust our forced value * instead of re-detecting. Crash-safety: this host has real AVX1 hardware - * (see db/modules.json poly1305 notes), so forcing intel_flags to "AVX1 + * (see the module registry poly1305 notes), so forcing intel_flags to "AVX1 * only" and letting the dispatch call the real poly1305_*_avx asm is always * safe: we never claim a capability the CPU lacks, only hide one it has. * @@ -402,7 +402,7 @@ int main(void) wb_poly1305_avx512_dispatch(); wb_poly1305_setkey_guard(); printf("done (%s)\n", wb_fail ? "with skips" : "ok"); - /* Setup failures are surfaced as skips, not test failures: the campaign + /* Setup failures are surfaced as skips, not test failures: the harness * treats a nonzero exit as a failed variant and discards its coverage. */ return 0; #endif diff --git a/tests/unit-mcdc/test_pwdbased_whitebox.c b/tests/unit-mcdc/test_pwdbased_whitebox.c index 1280c307c2..5d05458ff3 100644 --- a/tests/unit-mcdc/test_pwdbased_whitebox.c +++ b/tests/unit-mcdc/test_pwdbased_whitebox.c @@ -71,7 +71,7 @@ * dead. An unarmed derivation is still driven below so this binary's own * MC/DC record carries the (T,T)/(T,F) rows next to the unreachable one. * - * Build: compiled by the campaign's white-box step with the same MC/DC CFLAGS + * Build: compiled by the white-box step with the same MC/DC CFLAGS * as the instrumented library, then linked against that variant's * libwolfssl.a with pwdbased.o removed. Not part of the wolfSSL build. */ diff --git a/tests/unit-mcdc/test_random_fault_whitebox.c b/tests/unit-mcdc/test_random_fault_whitebox.c index 651004613d..a40e0f6ff8 100644 --- a/tests/unit-mcdc/test_random_fault_whitebox.c +++ b/tests/unit-mcdc/test_random_fault_whitebox.c @@ -23,7 +23,7 @@ * ENTROPY / SEED-DERIVATION FAULT white-box supplement for * wolfcrypt/src/random.c. * - * This is the campaign's first RNG-failure injection driver. Two independent + * This is the first RNG-failure injection driver. Two independent * levers are combined here, and both generalise to any module that consumes * randomness -- see "REUSING THIS" at the bottom of this comment. * @@ -132,7 +132,7 @@ * generation, blinding, nonce derivation): install a staged/failing seed * callback and drive the module's public entry point twice. * - * Build: compiled by the campaign's white-box step with the same MC/DC CFLAGS + * Build: compiled by the white-box step with the same MC/DC CFLAGS * as the instrumented library, then linked against that variant's * libwolfssl.a with random.o removed. Not part of the wolfSSL build. */ diff --git a/tests/unit-mcdc/test_random_whitebox.c b/tests/unit-mcdc/test_random_whitebox.c index b466064d85..3cf934c904 100644 --- a/tests/unit-mcdc/test_random_whitebox.c +++ b/tests/unit-mcdc/test_random_whitebox.c @@ -22,7 +22,7 @@ /* White-box supplement for wolfcrypt/src/random.c. * * Two Hash_DRBG-core MC/DC leaves are structurally unreachable from the - * public wc_* API in this campaign, no matter what combination of public + * public wc_* API in this suite, no matter what combination of public * arguments a caller supplies: * * - Hash_gen()/Hash512_gen()'s "out != NULL && outSz != 0" guard around @@ -50,17 +50,17 @@ * sizeof(reseedCtr)) that always satisfy the guard, so the false side * needs a direct call with mismatched/zero lengths. * - * Two further GAPS.md residual classes remain justified SKIPS, deliberately - * NOT chased by this white-box (per the campaign's no-fault-injection + * Two further the uncovered-condition report residual classes remain justified SKIPS, deliberately + * NOT chased by this white-box (per the no-fault-injection * convention -- same class as the documented rsa/sp-math residuals): * * - Hash_gen()/Hash512_gen()'s "data == NULL || digest == NULL" XMALLOC * guard (only compiled under WOLFSSL_SMALL_STACK && * !WOLFSSL_SMALL_STACK_CACHE): reaching either operand's true side needs * the shared allocator to fail on one of two back-to-back XMALLOC() - * calls; this campaign injects no allocation-failure fault (same + * calls; this suite injects no allocation-failure fault (same * documented residual class as the rsa/sp-math allocation-failure - * branches -- see db/modules.json's "random" entry). + * branches -- see the module registry's "random" entry). * - Hash_DRBG_Init()/Hash512_DRBG_Init()'s chained * "Hash_df(...)==DRBG_SUCCESS && Hash_df(...)==DRBG_SUCCESS" (resp. * Hash512_df) compound: showing either operand's false side needs @@ -649,7 +649,7 @@ static void wb_hash512_gen_alloc_guard(void) * BUILD-AXIS GUARD: that chain compiles exactly ONE wc_GenerateSeed body, and * the other arms neither share this guard nor, in the CUSTOM_RAND_GENERATE_BLOCK * case, define wc_GenerateSeed at all (that variant would not even link this - * TU). The condition below excludes every arm reachable from this campaign's + * TU). The condition below excludes every arm reachable from this suite's * variant set and from a host build, so the NULL vectors are only compiled * where the guard that catches them is. * ------------------------------------------------------------------------ */ @@ -718,7 +718,7 @@ int main(void) wb_hash512_gen_alloc_guard(); printf("done (%s)\n", wb_fail ? "with skips" : "ok"); /* Setup failures are surfaced as skips, not test failures: the - * campaign treats a nonzero exit as a failed variant and discards its + * suite treats a nonzero exit as a failed variant and discards its * coverage. */ return 0; #endif diff --git a/tests/unit-mcdc/test_rsa_fault_whitebox.c b/tests/unit-mcdc/test_rsa_fault_whitebox.c index 82887f10bd..2e26ff05ea 100644 --- a/tests/unit-mcdc/test_rsa_fault_whitebox.c +++ b/tests/unit-mcdc/test_rsa_fault_whitebox.c @@ -52,7 +52,7 @@ * sweep simply finds fewer heap sites to fault and the targets run to * completion), which is why it is safe to wire as a normal whitebox entry that * every variant compiles. ==> wire it with the small_stack -D (see the - * modules.json note at the end of this file's commit message). + * the module registry note at the end of this file's commit message). * * It #includes rsa.c directly (like the sibling test_rsa_whitebox.c and the * other unit-mcdc white-boxes) to reach the file-static wc_CompareDiffPQ / @@ -72,7 +72,7 @@ * the RSA verify-decrypt padding comparisons (3584/3595/3631/4071/4074) and the * *_KeyDecodeRaw / CheckProbablePrime_ex argument guards (5293/5914/5996/6004). * - * STRUCTURALLY UNSATISFIABLE (recorded in campaign/db/exclusions.json; line + * STRUCTURALLY UNSATISFIABLE (recorded in the exclusion record; line * numbers are rsa.c's). None of these is "hard to reach" -- each is an * argument that the missing row does not exist: * @@ -125,7 +125,7 @@ * ./test_rsa_fault_whitebox default: full fault-index sweep * ./test_rsa_fault_whitebox baseline only the unarmed valid ops (delta base) * ./test_rsa_fault_whitebox probe per-entry-point allocation counts - * (Default is the sweep so the campaign's run_whitebox harness -- which runs the + * (Default is the sweep so the run_whitebox harness -- which runs the * binary with NO arguments -- gets full coverage.) */ diff --git a/tests/unit-mcdc/test_rsa_whitebox.c b/tests/unit-mcdc/test_rsa_whitebox.c index 79dd9f8428..ebb69c8d57 100644 --- a/tests/unit-mcdc/test_rsa_whitebox.c +++ b/tests/unit-mcdc/test_rsa_whitebox.c @@ -10,13 +10,13 @@ * calling the helpers with both halves of each MC/DC independence pair. * * Coverage from this binary is unioned with the tests/api variant coverage by - * source line:col in the per-module campaign (iso26262/mcdc-per-module): - * llvm-cov computes MC/DC independence PER BINARY, and the campaign's + * source line:col in the per-module suite: + * llvm-cov computes MC/DC independence PER BINARY, and the * aggregate.sh ORs the "independence shown" bit across binaries by key. That is * why every pair below is completed *within this file* rather than relying on * the API tests to supply the other half. * - * Build: compiled by run-mcdc.sh's white-box step with the SAME MC/DC CFLAGS, + * Build: compiled by the coverage runner's white-box step with the SAME MC/DC CFLAGS, * -DHAVE_CONFIG_H and -I as the instrumented library, then linked * against that variant's libwolfssl.a with its rsa.o removed (this TU supplies * the instrumented rsa.c). NOT part of the wolfSSL build; not registered in @@ -1000,7 +1000,7 @@ int main(void) wb_public_encrypt_size_guard(); wb_private_decrypt_outlen(); printf("done (%s)\n", wb_fail ? "with skips" : "ok"); - /* Setup failures are surfaced as skips, not test failures: the campaign + /* Setup failures are surfaced as skips, not test failures: the harness * treats a nonzero exit as a failed variant and discards its coverage. */ return 0; #endif diff --git a/tests/unit-mcdc/test_sakke_fault_whitebox.c b/tests/unit-mcdc/test_sakke_fault_whitebox.c index 5c66d87f6c..4c694fee4f 100644 --- a/tests/unit-mcdc/test_sakke_fault_whitebox.c +++ b/tests/unit-mcdc/test_sakke_fault_whitebox.c @@ -48,7 +48,7 @@ * sakke_compute_point_r are reachable for direct armed calls, and so * llvm-cov attributes the coverage to sakke.c's own decisions. * - * Targeted GAPS.md residuals (err==0 FALSE half unless noted): + * Targeted the uncovered-condition report residuals (err==0 FALSE half unless noted): * 411 sakke_mulmod_base_add "(err==0) && map" (non-SP build only) * 536 wc_MakeSakkeKey "(err==0) && mp_iszero(..)" cond 0 only; * cond 1 (mp_iszero true, random scalar == 0) is crypto-unreachable. @@ -75,7 +75,7 @@ * ./test_sakke_fault_whitebox default: full fault-index sweeps * ./test_sakke_fault_whitebox baseline unarmed valid ops only (delta base) * ./test_sakke_fault_whitebox probe print per-target allocation counts - * The campaign run_whitebox harness runs the binary with NO arguments, so the + * The white-box harness runs the binary with NO arguments, so the * default is the productive full sweep. */ @@ -469,7 +469,7 @@ int main(int argc, char** argv) #ifndef MCDC_FA_UNAVAILABLE /* ============================================================ * Fault-index sweeps. K values sized from the probe run (see the - * modules.json note); each K over-sweeps its target's allocation + * the module registry note); each K over-sweeps its target's allocation * count by a margin (over-sweeping is harmless -- once n exceeds the * site count the target simply runs to completion). Fresh key state * is (re)built while DISARMED for every group whose target mutates diff --git a/tests/unit-mcdc/test_sha256_whitebox.c b/tests/unit-mcdc/test_sha256_whitebox.c index 3786f014ee..29b3feac5d 100644 --- a/tests/unit-mcdc/test_sha256_whitebox.c +++ b/tests/unit-mcdc/test_sha256_whitebox.c @@ -254,7 +254,7 @@ int main(void) wb_intel_dispatch(); wb_update_transform_err(); printf("done (%s)\n", wb_fail ? "with skips" : "ok"); - /* Setup failures are surfaced as skips, not test failures: the campaign + /* Setup failures are surfaced as skips, not test failures: the harness * treats a nonzero exit as a failed variant and discards its coverage. */ return 0; #endif diff --git a/tests/unit-mcdc/test_she_whitebox.c b/tests/unit-mcdc/test_she_whitebox.c index a403adec8f..e710d6d3b3 100644 --- a/tests/unit-mcdc/test_she_whitebox.c +++ b/tests/unit-mcdc/test_she_whitebox.c @@ -63,7 +63,7 @@ * genuinely computed H_i, and either way the driver only inspects the returned * status, never the buffer. * - * Build: compiled by the campaign's white-box step with the same MC/DC CFLAGS + * Build: compiled by the white-box step with the same MC/DC CFLAGS * as the instrumented library, then linked against that variant's * libwolfssl.a with wc_she.o removed. Not part of the wolfSSL build. */ diff --git a/tests/unit-mcdc/test_signature_whitebox.c b/tests/unit-mcdc/test_signature_whitebox.c index 8b669c2f6e..9c11e74f89 100644 --- a/tests/unit-mcdc/test_signature_whitebox.c +++ b/tests/unit-mcdc/test_signature_whitebox.c @@ -75,11 +75,11 @@ * Nothing is read through plain_ptr on this vector -- the guard is FALSE, so * the XMEMCMP at line 313 is not executed. * - * If the campaign prefers not to admit interposition-driven evidence here, + * If the harness prefers not to admit interposition-driven evidence here, * the alternative is to EXCLUDE 311:21:311:42:1 (and, with it, the (T,F) * half only) on the argument above; V1/V2 still close idx0 on their own. * - * Build: compiled by the campaign's white-box step with the same MC/DC CFLAGS + * Build: compiled by the white-box step with the same MC/DC CFLAGS * as the instrumented library, then linked against that variant's * libwolfssl.a with signature.o removed. Not part of the wolfSSL build. */ diff --git a/tests/unit-mcdc/test_slhdsa_hash_fault_whitebox.c b/tests/unit-mcdc/test_slhdsa_hash_fault_whitebox.c index 3bd5e92373..b4f3e8dc41 100644 --- a/tests/unit-mcdc/test_slhdsa_hash_fault_whitebox.c +++ b/tests/unit-mcdc/test_slhdsa_hash_fault_whitebox.c @@ -22,7 +22,7 @@ /* * MC/DC hash-fault white-box supplement for wolfcrypt/src/wc_slhdsa.c. * - * campaign/reports/slhdsa/GAPS.md is almost entirely error propagation: + * suite/reports/slhdsa/the uncovered-condition report is almost entirely error propagation: * * if ((ret == 0) && (hdr != NULL)) -- PRF_msg / H_msg * if ((ret == 0) && (ctxSz > 0) && (ctx != NULL)) streaming chains @@ -41,7 +41,7 @@ * * WHERE THE INDEX HAS TO LAND * --------------------------- - * SLH-DSA sign is by far the most expensive operation in the campaign, so the + * SLH-DSA sign is by far the most expensive operation in the harness, so the * sweep is deliberately shaped: * * - a DENSE head (1..WB_DENSE) over every entry point. Almost all of the @@ -54,7 +54,7 @@ * densely than Sign, because verify is orders of magnitude cheaper. * * Every sweep also tests a CPU-time deadline, so the binary degrades to fewer - * points instead of being killed at the campaign's 600 s TEST_TIMEOUT -- a + * points instead of being killed at the 600 s TEST_TIMEOUT -- a * timeout is scored as a SILENT SKIP and would lose the whole file (HARD * RULE 2). * @@ -70,7 +70,7 @@ * slhdsakey_precompute_sha2_midstates()). * * NOT REACHABLE HERE (documented residuals, mirrored in - * campaign/db/exclusions.json): + * the exclusion record): * - `(ret == 0) && (n > 16)` / `(ret == 0) && (key->params->n > 16)`: the * second operand needs a category 3/5 parameter set, and this module's base * config compiles ONLY the 128-bit sets (WOLFSSL_SLHDSA_PARAM_NO_192/256 @@ -124,7 +124,7 @@ static int wb_fail = 0; #define WB_POINTS_VERIFY 128 #define WB_DEADLINE_S 170 -/* WALL clock, not clock(): the campaign runs several variants concurrently and +/* WALL clock, not clock(): the harness runs several variants concurrently and * TEST_TIMEOUT is 600 s of WALL time. Under that contention CPU time accrues * far slower than wall time, so a CPU-time budget would sail past the timeout * -- and a timed-out white-box is scored as a SILENT SKIP that loses the whole @@ -642,6 +642,6 @@ int main(void) #endif printf("done (%s)\n", wb_fail ? "with failures" : "ok"); - /* A non-zero exit makes the campaign discard this binary's coverage. */ + /* A non-zero exit makes the harness discard this binary's coverage. */ return 0; } diff --git a/tests/unit-mcdc/test_slhdsa_whitebox.c b/tests/unit-mcdc/test_slhdsa_whitebox.c index 6d404544be..f6bef839f8 100644 --- a/tests/unit-mcdc/test_slhdsa_whitebox.c +++ b/tests/unit-mcdc/test_slhdsa_whitebox.c @@ -23,12 +23,12 @@ * mid-sign. Called directly on a stack HashAddress. * * Coverage from this binary is unioned with the tests/api variant coverage by - * source line:col in the per-module campaign (iso26262/mcdc-per-module): + * source line:col in the per-module suite: * llvm-cov computes MC/DC independence PER BINARY, and aggregate.sh ORs the * "independence shown" bit across binaries by key. Every pair below is * therefore completed *within this file*. * - * Build: compiled by run-mcdc-par.sh's white-box step with the SAME MC/DC + * Build: compiled by the coverage runner's white-box step with the SAME MC/DC * CFLAGS, -DHAVE_CONFIG_H and -I as the instrumented library, then * linked against that variant's libwolfssl.a with its wc_slhdsa.o removed * (this TU supplies the instrumented wc_slhdsa.c). NOT part of the wolfSSL @@ -145,11 +145,11 @@ static void wb_ha_encode(void) } /* ------------------------------------------------------------------------- * - * SHA-2 message-hash static functions: gap-closing supplement (see GAPS.md). + * SHA-2 message-hash static functions: gap-closing supplement (see the uncovered-condition report). * * The tests/api DecisionCoverage additions close every arg-check reachable * from the public API. What's left needs either a real n>16 SHA-2 param - * (every campaign variant restricts to 128-bit only, see config_base's + * (every suite variant restricts to 128-bit only, see config_base's * notes) or a ctx/ctxSz combination the public API itself rejects before * ever reaching these static functions (ctx==NULL with ctxSz>0 is BAD_FUNC_ARG at * the wc_SlhDsaKey_Sign/Verify layer). Both are driven directly here. @@ -433,7 +433,7 @@ int main(void) wb_sign_internal_msg_argchecks(); #endif printf("done (%s)\n", wb_fail ? "with skips" : "ok"); - /* Setup failures are surfaced as skips, not test failures: the campaign + /* Setup failures are surfaced as skips, not test failures: the harness * treats a nonzero exit as a failed variant and discards its coverage. */ return 0; #endif diff --git a/tests/unit-mcdc/test_sp_arm32_whitebox.c b/tests/unit-mcdc/test_sp_arm32_whitebox.c index a0db3d7b10..1d51a83475 100644 --- a/tests/unit-mcdc/test_sp_arm32_whitebox.c +++ b/tests/unit-mcdc/test_sp_arm32_whitebox.c @@ -77,7 +77,7 @@ * This is a coverage-driving supplement, not a known-answer test: only "did * this fail outright" is checked, never a specific expected value. Coverage * from this binary is unioned with the tests/api variant coverage by source - * line:col in the per-module campaign (iso26262/mcdc-per-module). + * line:col in the per-module suite. * * Build: compiled by lanes/qemu-entry.sh's white-box step with the SAME MC/DC * cross CFLAGS (--target=arm-linux-gnueabihf, -DWOLFSSL_SP_ARM32_ASM, diff --git a/tests/unit-mcdc/test_sp_arm_fault_common.h b/tests/unit-mcdc/test_sp_arm_fault_common.h index 81536de948..91c9c09ea8 100644 --- a/tests/unit-mcdc/test_sp_arm_fault_common.h +++ b/tests/unit-mcdc/test_sp_arm_fault_common.h @@ -46,9 +46,9 @@ * mean a whole extra cross build + emulator pass per lane. * * The including TU therefore defines WOLFSSL_SP_SMALL_STACK for ITSELF, before - * it #includes the sp_arm*.c under test. That is sound for this campaign and + * it #includes the sp_arm*.c under test. That is sound for this suite and * cheaper than a variant: - * - the lane's white-box recipe (campaign/lanes/qemu-entry.sh) compiles the + * - the lane's white-box recipe (suite/lanes/qemu-entry.sh) compiles the * wb TU with the library's own captured compile line and links it against * libwolfssl.a with the target file's object REMOVED, so the wb binary * contains exactly one copy of sp_arm*.c -- this one -- and there is no @@ -365,7 +365,7 @@ static void wb_fa_curve(int curveId, int fieldSz, const char* label, * its first operand's false row) but it must NOT be driven through * P-256 on sp_arm64.c: sp_256_mod_inv_4() there is hand-written * AArch64 assembly whose loop does not terminate for a == m, and the - * white-box hung until TEST_TIMEOUT killed it -- which the campaign + * white-box hung until TEST_TIMEOUT killed it -- which the harness * records as a silent skip of the whole row. The C mod_inv bodies are * reached instead by the direct sweep in the ordinary white-boxes * (wb_run_mod_inv), which can pick the curves it calls. */ diff --git a/tests/unit-mcdc/test_sp_armthumb_whitebox.c b/tests/unit-mcdc/test_sp_armthumb_whitebox.c index 92e4eb916f..e5786c43d9 100644 --- a/tests/unit-mcdc/test_sp_armthumb_whitebox.c +++ b/tests/unit-mcdc/test_sp_armthumb_whitebox.c @@ -28,7 +28,7 @@ * (--target=arm-linux-gnueabihf -mthumb) and run under an ARM emulator * (qemu-arm). That is why this white-box is a LANE-only supplement (the * "qemu-armthumb" lane in db/lanes.json / the sp-arm-lanes "armthumb" variant - * in db/modules.json), never a native host build: the host x86-64 toolchain + * in the module registry), never a native host build: the host x86-64 toolchain * cannot even assemble the file. * * Unlike the asm-dispatch backends (sp_x86_64.c, the AArch64 armasm files), @@ -675,7 +675,7 @@ static void wb_mod_top_bit_odd(mp_int* m, int bits) * false; base=2 with a non-all-ones modulus forces the shape * operand false; base=2 with an all-ones modulus gives the * all-true baseline (self-contained -- not relying on real DH - * traffic elsewhere in the campaign for this size). + * traffic elsewhere in the harness for this size). * 3. Leading-zero-strip loop: base=1 gives a result of 1 (every byte * but the last is 0, closing the "out[i]==0" operand's both * sides in one call); base=0 gives an all-zero result (closing diff --git a/tests/unit-mcdc/test_sp_c32_whitebox.c b/tests/unit-mcdc/test_sp_c32_whitebox.c index f2fd036771..e40f24c6ef 100644 --- a/tests/unit-mcdc/test_sp_c32_whitebox.c +++ b/tests/unit-mcdc/test_sp_c32_whitebox.c @@ -68,9 +68,9 @@ * This is a coverage-driving supplement, not a known-answer test: only * "did this fail outright" is checked, never a specific expected value. * Coverage from this binary is unioned with the tests/api variant coverage - * by source line:col in the per-module campaign (iso26262/mcdc-per-module). + * by source line:col in the per-module suite. * - * Build: compiled by run-mcdc.sh's white-box step with the SAME MC/DC CFLAGS + * Build: compiled by the coverage runner's white-box step with the SAME MC/DC CFLAGS * (including -DSP_WORD_SIZE=32, which selects sp_c32.c's body), -DHAVE_CONFIG_H * and -I as the instrumented library, then linked against that * variant's libwolfssl.a with its sp_c32.o removed (this TU supplies the diff --git a/tests/unit-mcdc/test_sp_cortexm_crafted_whitebox.c b/tests/unit-mcdc/test_sp_cortexm_crafted_whitebox.c index cf08356f12..dbcca5769a 100644 --- a/tests/unit-mcdc/test_sp_cortexm_crafted_whitebox.c +++ b/tests/unit-mcdc/test_sp_cortexm_crafted_whitebox.c @@ -72,7 +72,7 @@ * s == the curve order). test_sp_crafted_common.h already gates it off for * every assembly backend, WOLFSSL_SP_ARM_CORTEX_M_ASM included -- * sp_256_mod_inv_8() is hand-written Thumb assembly whose halving loop does - * not terminate on that input. See DEATHNOTE.md. + * not terminate on that input. */ #ifdef HAVE_CONFIG_H diff --git a/tests/unit-mcdc/test_sp_cortexm_whitebox.c b/tests/unit-mcdc/test_sp_cortexm_whitebox.c index 7fbd3280ea..77a784c9c2 100644 --- a/tests/unit-mcdc/test_sp_cortexm_whitebox.c +++ b/tests/unit-mcdc/test_sp_cortexm_whitebox.c @@ -22,7 +22,7 @@ /* * MC/DC white-box supplement for the Cortex-M SP backend * (wolfcrypt/src/sp_cortexm.c), driven under the bare-metal m33mu emulator - * lane (campaign lane "m33mu", config configs/sp-arm-lanes/user_settings.cortexm.h). + * lane (the m33mu lane). * * LANE CONTRACT / WHY A CONSTRUCTOR (not the usual #include-the-.c whitebox): * The m33mu lane instruments sp_cortexm.c as its own clang TU and links it diff --git a/tests/unit-mcdc/test_sp_crafted_common.h b/tests/unit-mcdc/test_sp_crafted_common.h index ca5a047eed..e41474bd5e 100644 --- a/tests/unit-mcdc/test_sp_crafted_common.h +++ b/tests/unit-mcdc/test_sp_crafted_common.h @@ -126,7 +126,7 @@ * words in hand when the windowed loop's bit counter runs out, which is * the only vector of that loop's `i >= 0` operand. * - * NOT REACHABLE -- arguments, mirrored in EXCLUSIONS.md families A-G + * NOT REACHABLE -- arguments, mirrored in the exclusion record families A-G * ----------------------------------------------------------------- * A. `for (j=0; j_ecc_mulmod_stripe_. * Both bounds are compile-time arithmetic on the comb geometry, not @@ -211,7 +211,7 @@ * register quadruple that is identically zero and re-tests bit 0, so it * cannot terminate -- while appending a byte per iteration to a fixed * 0x208-byte stack buffer. It is not MC/DC-instrumented (it is assembly), - * so there is nothing to gain by driving it. See DEATHNOTE.md. + * so there is nothing to gain by driving it. * * EVERY assembly backend has this shape, not just x86-64: the ARM lanes * (sp_arm64.c, sp_arm32.c, sp_armthumb.c, sp_cortexm.c) hand-write the @@ -484,7 +484,7 @@ static void wb_spc_ecc_##BITS(void) \ key.pubkey.y, &one, &one, &smv, &res, NULL); \ } \ } -/* Three degenerate-operand vectors nothing else in the campaign produces. +/* Three degenerate-operand vectors nothing else in the harness produces. * * 1. sign with a zero private scalar and an all-zero hash. s is * (e + r*d) / k mod order, so e == 0 and d == 0 make s == 0 on EVERY @@ -822,7 +822,7 @@ static void wb_spc_all(void) { /* Referenced unconditionally: which of these the preprocessor leaves * with a live use depends on the variant, and an unused static is a - * warning this campaign's builds treat as noise to be avoided. */ + * warning this suite's builds treat as noise to be avoided. */ (void)wb_spc_digest; (void)wb_spc_zdigest; (void)wb_spc_make_modulus; diff --git a/tests/unit-mcdc/test_sp_fault_common.h b/tests/unit-mcdc/test_sp_fault_common.h index 57f9739bae..03b0a5a7de 100644 --- a/tests/unit-mcdc/test_sp_fault_common.h +++ b/tests/unit-mcdc/test_sp_fault_common.h @@ -28,7 +28,7 @@ * * if ((err == MP_OKAY) && ) * - * whose `err == MP_OKAY` operand has no false side in the campaign's builds. + * whose `err == MP_OKAY` operand has no false side in the builds. * The reason is not that the failure is hard to produce, it is that nothing in * the compiled code can produce it: SP_ALLOC_VAR is * @@ -47,7 +47,7 @@ * WOLFSSL_SP_SMALL_STACK. Elsewhere it runs the same operations with the * injector never armed, which costs one quick pass and keeps the file building * in every variant of the module (a white-box that fails to build is a silent - * skip, and the campaign has lost a module's evidence to that twice). + * skip, and the harness has lost a module's evidence to that twice). * * HOW * --- diff --git a/tests/unit-mcdc/test_sp_int_fault_whitebox.c b/tests/unit-mcdc/test_sp_int_fault_whitebox.c index a82556ff2d..b8d2c2aff1 100644 --- a/tests/unit-mcdc/test_sp_int_fault_whitebox.c +++ b/tests/unit-mcdc/test_sp_int_fault_whitebox.c @@ -38,7 +38,7 @@ * checked, whereas otherwise the temporaries are stack arrays and `err` cannot * change at all. * - * The campaign's sp-math module already builds a `small_stack` variant, so + * The suite's sp-math module already builds a `small_stack` variant, so * unlike the SP backends this needs no new configuration -- only this driver. * * METHOD @@ -76,7 +76,7 @@ * rather than evidence. The one exception is the invmod pair, which needs a * modulus of at least 1024 bits to select the division-based inverse. * - * Build: compiled by the campaign's white-box step with the same MC/DC CFLAGS + * Build: compiled by the white-box step with the same MC/DC CFLAGS * as the instrumented library, then linked against that variant's * libwolfssl.a with sp_int.o removed. Not part of the wolfSSL build. */ @@ -139,7 +139,7 @@ static const char* WB_M1024_EVEN = "00000000000000000000000000000000000000000000000000000000000062"; /* Which internal engines this configuration compiles. Mirrors sp_int.c's own - * guards so the TU builds under every campaign variant. */ + * guards so the TU builds under every suite variant. */ #if (defined(WOLFSSL_SP_MATH_ALL) && !defined(WOLFSSL_RSA_VERIFY_ONLY) && \ !defined(WOLFSSL_RSA_PUBLIC_ONLY)) || !defined(NO_DH) || \ defined(OPENSSL_ALL) diff --git a/tests/unit-mcdc/test_sp_int_whitebox.c b/tests/unit-mcdc/test_sp_int_whitebox.c index e6bc4ec3ed..cd3646c9b2 100644 --- a/tests/unit-mcdc/test_sp_int_whitebox.c +++ b/tests/unit-mcdc/test_sp_int_whitebox.c @@ -16,13 +16,13 @@ * the public API). * * Coverage from this binary is unioned with the tests/api variant coverage - * by source line:col in the per-module campaign: llvm-cov computes MC/DC - * independence PER BINARY, and the campaign's aggregate.sh ORs the + * by source line:col in the per-module suite: llvm-cov computes MC/DC + * independence PER BINARY, and the aggregate.sh ORs the * "independence shown" bit across binaries by key. That is why every pair * below is completed *within this file* rather than relying on the API * tests to supply the other half. * - * Build: compiled by run-mcdc-par.sh's white-box step with the SAME MC/DC + * Build: compiled by the coverage runner's white-box step with the SAME MC/DC * CFLAGS and -I as the instrumented library, then linked against * that variant's libwolfssl.a with its sp_int.o removed (this TU supplies * the instrumented sp_int.c). NOT part of the wolfSSL build; not registered @@ -194,7 +194,7 @@ static void wb_set_d(sp_int* a, sp_int_digit v) /* a = 2^bits, written straight into the digit array. * * sp_mul_2d() would be the natural way to build these operands, but it is not - * compiled in every campaign variant (the reduced backend drops it), and this + * compiled in every suite variant (the reduced backend drops it), and this * TU has to build under all of them. Returns MP_VAL when the requested width * does not fit the compile-time digit ceiling so callers can skip that row. */ static int wb_pow2(sp_int* a, int bits) @@ -649,7 +649,7 @@ static void wb_invmod_negative(void) * if ((err == MP_OKAY) && (!sp_iszero(y))) err = MP_VAL; * * sp_invmod() only selects _sp_invmod_div() for a modulus of at least 1024 - * bits, and the campaign's API tests only ever ask for an inverse that + * bits, and the API tests only ever ask for an inverse that * exists, so the loop's leftover is always zero there. Ask for the inverse * of a value that shares a factor with the modulus instead. * ------------------------------------------------------------------------- */ @@ -813,7 +813,7 @@ static void wb_sp_backend_dispatch_one(int bits) * `(mBits == W) && sp_isodd(m) && (bBits <= W) && (eBits <= W)` the * indices are mBits, bBits, eBits, then sp_isodd's two halves. Index 3 * is therefore `m->used != 0`, which sp_exptmod_ex() has already - * rejected via sp_iszero(m); see EXCLUSIONS.md. */ + * rejected via sp_iszero(m); see the exclusion record. */ if ((wb_pow2(&b, bits) == MP_OKAY) && (sp_add_d(&b, 5, &b) == MP_OKAY)) { _sp_init_size(&r, SP_INT_DIGITS); (void)sp_exptmod_ex(&b, &e, 1, &m, &r); @@ -1839,7 +1839,7 @@ int main(void) wb_gcd_r_small_b(); wb_prime_trial_alloc(); printf("done (%s)\n", wb_fail ? "with skips" : "ok"); - /* Setup failures are surfaced as skips, not test failures: the campaign + /* Setup failures are surfaced as skips, not test failures: the harness * treats a nonzero exit as a failed variant and discards its coverage. */ return 0; #endif diff --git a/tests/unit-mcdc/test_sp_x86_64_whitebox.c b/tests/unit-mcdc/test_sp_x86_64_whitebox.c index 81d184edf8..2f561414ee 100644 --- a/tests/unit-mcdc/test_sp_x86_64_whitebox.c +++ b/tests/unit-mcdc/test_sp_x86_64_whitebox.c @@ -32,7 +32,7 @@ * * * On any BMI2+ADX host (which is effectively every build/CI machine this - * campaign runs on) only the accelerated half of each such decision is ever + * suite runs on) only the accelerated half of each such decision is ever * taken by the ordinary tests/api-driven asm run, leaving the generic half * permanently uncovered -- roughly 374 decisions across the file. * @@ -48,11 +48,11 @@ * * Coverage from this binary is unioned with the tests/api variant coverage * (and with the normal, accelerated, asm run of this same file) by source - * line:col in the per-module campaign (iso26262/mcdc-per-module): - * llvm-cov computes MC/DC independence PER BINARY, and the campaign's + * line:col in the per-module suite: + * llvm-cov computes MC/DC independence PER BINARY, and the * aggregate.sh ORs the "independence shown" bit across binaries by key. * - * Build: compiled by run-mcdc.sh's white-box step with the SAME MC/DC CFLAGS, + * Build: compiled by the coverage runner's white-box step with the SAME MC/DC CFLAGS, * -DHAVE_CONFIG_H and -I as the instrumented library, then linked * against that variant's libwolfssl.a with its sp_x86_64.o removed (this TU * supplies the instrumented sp_x86_64.c). NOT part of the wolfSSL build; not @@ -110,7 +110,7 @@ * would require calling the avx2 variant directly while ALSO forcing * cpuid_get_flags() to report BMI2/ADX absent for that one call, which * doesn't correspond to any state the real dispatch logic can reach. These - * are left uncovered here and logged as a residual/DEATHNOTE class rather + * are left uncovered here and logged as a residual class rather * than driven via an impossible-state call. * * ------------------------------------------------------------------------- @@ -557,7 +557,7 @@ static void wb_run_rsa_free(void) * "Group 15" 3072-bit prime from memory risks a transcription error, and * generating one at runtime via wc_DhGenerateParams(3072) is a slow * probable-safe-prime search that would meaningfully slow this binary - * down for a size this campaign only asks for "if convenient". The + * down for a size this suite only asks for "if convenient". The * generic sp_ModExp_3072/sp_DhExp_3072 decisions are still covered via * the RSA-3072 path above (same underlying generic Montgomery modexp * routines), so 2048-bit alone still exercises the DH-specific @@ -1458,7 +1458,7 @@ static void wb_run_dispatch_521(void) /* ----------------------------------------------------------------------- * * SAKKE (1024-bit): sp_1024_div_16/from_bin/to_mp. Niche feature, almost - * certainly not enabled in this campaign's builds -- WB_NOTE-skip if not. + * certainly not enabled in this suite's builds -- WB_NOTE-skip if not. * ----------------------------------------------------------------------- */ static void wb_run_dispatch_1024(void) { @@ -1506,7 +1506,7 @@ static void wb_run_dispatch(void) * * sp_ecc_mulmod_add_256/384/521(): each has * if ((err == MP_OKAY) && (!inMont)) { ... sp__mod_mul_norm_ ... } - * repeated for x/y/z (the ~36-conditions-across-3-curves the campaign + * repeated for x/y/z (the ~36-conditions-across-3-curves the harness * counts), plus a final `if (map) { ... }`. Driving all 4 (inMont, map) * combinations with a real curve point (from wc_ecc_make_key_ex()) as both * the multiplicand and the point to add covers every operand of both @@ -1529,7 +1529,7 @@ static void wb_run_dispatch(void) * from the file-static p_mod array, visible in this TU because * sp_x86_64.c is #included, not linked); false via a real coordinate * (which is always < the modulus). - * privm is passed as NULL throughout (this campaign only needs the public- + * privm is passed as NULL throughout (this suite only needs the public- * point guards, not the private-scalar-matches-point path, which is * already exercised for real keys by wb_run_ecc()). * @@ -1915,7 +1915,7 @@ int main(void) /* The dispatch decisions in sp_x86_64.c are `IS_INTEL_BMI2(f) && * IS_INTEL_ADX(f)` (two conditions) plus single-condition * `IS_INTEL_MOVBE(f)` checks. CRITICAL: llvm-cov computes MC/DC - * independence PER BINARY, and the campaign only ORs the resulting + * independence PER BINARY, and the harness only ORs the resulting * covered-bit across binaries -- it does NOT reconstruct an independence * pair from vectors spread over different binaries. So THIS binary must * itself observe all three vectors of `A && B` (TT, FT, TF). The ordinary diff --git a/tests/unit-mcdc/test_srp_whitebox.c b/tests/unit-mcdc/test_srp_whitebox.c index d54cc8becd..3a61397265 100644 --- a/tests/unit-mcdc/test_srp_whitebox.c +++ b/tests/unit-mcdc/test_srp_whitebox.c @@ -35,14 +35,14 @@ * SRP_VERIFY_E) and the (T,F) row (a correct proof) are both there, but the * idx0 independence pair -- (T,T) against (F,.) -- is not. * - * mcdc_fault_hash.h is the campaign's lever for exactly this shape: it + * mcdc_fault_hash.h is the lever for exactly this shape: it * macro-interposes the hash primitives for THIS translation unit only, before * srp.c is #included, so mcdc_fh_arm(1) makes the very next primitive call * (and every later one) return BAD_FUNC_ARG. SrpHashFinal() then propagates * that into `r`, and :1057 is evaluated with `!r` FALSE while the decision * short-circuits -- the missing half. The unarmed (T,T) partner is driven in * the SAME binary immediately before it, which is what MC/DC needs: llvm-cov - * computes independence per binary and the campaign only ORs the resulting + * computes independence per binary and the harness only ORs the resulting * bits by line:col. * * Note that wc_SrpVerifyPeersProof()'s SHA-256 proof hash is used here in its @@ -53,7 +53,7 @@ * cheap (no modexp, well inside TEST_TIMEOUT). tests/api/test_srp.c already * carries the full handshake, including the corrupted-proof rejection. * - * Build: compiled by run-mcdc-par.sh's white-box step with the SAME MC/DC + * Build: compiled by the coverage runner's white-box step with the SAME MC/DC * CFLAGS, -DHAVE_CONFIG_H and -I as the instrumented library, then * linked against that variant's libwolfssl.a with its srp.o removed (this TU * supplies the instrumented srp.c). NOT part of the wolfSSL build; not @@ -179,6 +179,6 @@ int main(void) wb_verify_peers_proof_hash_fault(); printf("done (%s)\n", wb_fail ? "with skips" : "ok"); /* Setup issues are surfaced as skips; a nonzero exit would make the - * campaign discard this variant's coverage. */ + * suite discard this variant's coverage. */ return 0; } diff --git a/tests/unit-mcdc/test_tfm_fault_whitebox.c b/tests/unit-mcdc/test_tfm_fault_whitebox.c index 90966a3d3f..7a9e53215f 100644 --- a/tests/unit-mcdc/test_tfm_fault_whitebox.c +++ b/tests/unit-mcdc/test_tfm_fault_whitebox.c @@ -28,7 +28,7 @@ * step 7 relabelled away from the (retired) "32-bit axis" residual class. * * Every section supplies BOTH halves of the targeted independence pair inside - * THIS binary - llvm-cov computes MC/DC per binary and the campaign only + * THIS binary - llvm-cov computes MC/DC per binary and the harness only * unions the "independence shown" bit by source line:col, so a rejection * vector without its accepting partner in the same binary proves nothing. * @@ -137,7 +137,7 @@ * It #includes tfm.c directly (like every other unit-mcdc white-box) to be the * single instrumented definition; the library's tfm.o is trimmed from the * archive at link time. main() always returns 0 - a nonzero exit makes the - * campaign discard the whole variant. + * suite discard the whole variant. */ #include @@ -486,7 +486,7 @@ int main(void) printf("done (%s)\n", wb_fail ? "with skips" : "ok"); /* Setup failures surface as skips, not failures: a nonzero exit makes the - * campaign discard this variant's coverage. */ + * suite discard this variant's coverage. */ return 0; } diff --git a/tests/unit-mcdc/test_tfm_whitebox.c b/tests/unit-mcdc/test_tfm_whitebox.c index f977bcbf42..3401b339e5 100644 --- a/tests/unit-mcdc/test_tfm_whitebox.c +++ b/tests/unit-mcdc/test_tfm_whitebox.c @@ -12,9 +12,9 @@ * editing library source. This translation unit reaches them by compiling * tfm.c directly (#include) and calling the static helpers with BOTH halves of each * targeted pair in this one binary (llvm-cov computes MC/DC per binary; the - * campaign unions the "independence shown" bit across binaries by line:col). + * suite unions the "independence shown" bit across binaries by line:col). * - * Build: compiled by run-mcdc.sh's white-box step with the SAME MC/DC CFLAGS + * Build: compiled by the coverage runner's white-box step with the SAME MC/DC CFLAGS * and -I as the instrumented library, then linked against that * variant's libwolfssl.a with its tfm.o removed (this TU supplies the * instrumented tfm.c). NOT part of the wolfSSL build; not registered in @@ -22,7 +22,7 @@ * * Every call is memory-safe (static helpers are handed initialized fp_ints and * in-range selectors); setup failures print a skip and return 0 (a nonzero - * exit makes the campaign discard the variant and its coverage). + * exit makes the harness discard the variant and its coverage). */ #include @@ -803,7 +803,7 @@ static void wb_TfmExptModDecisionCoverage(void) /* ------------------------------------------------------------------------ * Public-entry ARGUMENT-GUARD residuals. * - * campaign/reports/bigint-tfm/GAPS.md lists several multi-operand OR guards at + * suite/reports/bigint-tfm/the uncovered-condition report lists several multi-operand OR guards at * the top of public entry points whose operands the ordinary tests only ever * present all-false (they always pass valid arguments), so no operand's * independence pair is shown. Each is closed here by calling the entry point @@ -944,7 +944,7 @@ int main(void) wb_entry_arg_guards(); printf("done (%s)\n", wb_fail ? "with skips" : "ok"); /* Setup failures surface as skips, not failures: a nonzero exit makes the - * campaign discard this variant's coverage. */ + * suite discard this variant's coverage. */ return 0; #endif } diff --git a/tests/unit-mcdc/test_tls13_null_whitebox.c b/tests/unit-mcdc/test_tls13_null_whitebox.c index c2bc26f123..6ba3b52b3b 100644 --- a/tests/unit-mcdc/test_tls13_null_whitebox.c +++ b/tests/unit-mcdc/test_tls13_null_whitebox.c @@ -3,15 +3,15 @@ * White-box MC/DC supplement for the POINTER-PRESENCE GUARDS of src/tls13.c. * * Companion to tests/unit-mcdc/test_tls13_whitebox.c, kept as a separate TU so - * the two can be extended independently; the campaign unions their coverage by + * the two can be extended independently; the harness unions their coverage by * source line:col exactly as it unions the variant builds. * * SCOPE. Every decision driven here is a NULL / presence check on a pointer. - * The campaign's disposition rule for that family is: + * The suite's disposition rule for that family is: * * - if the operand cannot vary even for a DIRECT caller, because a * constructor or a callee postcondition fixes it, it is an entry in - * campaign/db/exclusions.json and NOT a test (e.g. `ssl->ctx != NULL`: + * the exclusion record and NOT a test (e.g. `ssl->ctx != NULL`: * wolfSSL_new() is the only constructor and rejects a NULL CTX); * - if the operand cannot vary only because every IN-LIBRARY caller has * already established it, it is reachable from a white-box and belongs @@ -26,7 +26,7 @@ * wolfSSL_new() itself consumes. Every vector is a direct call with * hand-supplied arguments, so consecutive runs are byte-identical. * - * main() always returns 0: the campaign scores a nonzero exit as a failed + * main() always returns 0: the harness scores a nonzero exit as a failed * white-box and discards its whole coverage, so setup problems print a skip. */ diff --git a/tests/unit-mcdc/test_tls13_whitebox.c b/tests/unit-mcdc/test_tls13_whitebox.c index 9a9049a53e..00ba2bdd85 100644 --- a/tests/unit-mcdc/test_tls13_whitebox.c +++ b/tests/unit-mcdc/test_tls13_whitebox.c @@ -2,7 +2,7 @@ * * White-box MC/DC supplement for src/tls13.c. * - * This is the FIRST white-box driver in the campaign that targets a src/ file + * This is the FIRST white-box driver in the harness that targets a src/ file * rather than wolfcrypt/src/*.c. The build contract is identical (see * tests/unit-mcdc/README.md): this TU #includes the target .c verbatim, is * compiled with the exact flags the instrumented library used for it, and is @@ -19,12 +19,12 @@ * without editing library source. * * Coverage from this binary is unioned with the tests/api variant coverage by - * source line:col by the campaign's aggregate.sh, which ORs the "independence + * source line:col by the aggregate.sh, which ORs the "independence * shown" bit across binaries. llvm-cov derives independence PER BINARY, so * every MC/DC pair below is completed WITHIN THIS FILE; nothing here leans on * the API tests to supply the other half of a pair. * - * main() always returns 0: the campaign treats a nonzero exit as a failed + * main() always returns 0: the harness treats a nonzero exit as a failed * white-box and discards its coverage, so setup problems are printed as skips. */ diff --git a/tests/unit-mcdc/test_tsp_fault_whitebox.c b/tests/unit-mcdc/test_tsp_fault_whitebox.c index 0a48ec130b..0c255f820c 100644 --- a/tests/unit-mcdc/test_tsp_fault_whitebox.c +++ b/tests/unit-mcdc/test_tsp_fault_whitebox.c @@ -24,7 +24,7 @@ * * tests/api/test_tsp.c and tests/unit-mcdc/test_tsp_whitebox.c together drive * the module's happy paths and most static-helper argument guards. The - * campaign's GAPS.md still lists a residual set of MC/DC independence pairs + * suite's the uncovered-condition report still lists a residual set of MC/DC independence pairs * as unshown in the union of all variant runs; this file targets those, * providing BOTH rows of each pair in this one binary (MC/DC independence is * computed per binary, not merged across separately-compiled #include @@ -71,7 +71,7 @@ * tsa_cert_der_2048/tsa_key_der_2048 (real RSA signature over real * SignedAttributes). * - * DEATHNOTE claim check (asn_tsp.c:1433, condition index 0, + * dead-condition claim check (asn_tsp.c:1433, condition index 0, * `GetASNTag(signers, &idx, &tag, signersSz) < 0`, inside * TspCheckOneSignerInfo()'s `while ((ret == 0) && (idx < signersSz))` loop): * CONFIRMED dead at this call site. GetASNTag() only fails when @@ -86,7 +86,7 @@ * test_tsp_whitebox.c's header for why that would be unnecessary anyway: * none of the residuals here are in one of its file-static helpers). * - * Targeted residuals, by GAPS.md line (17 NULL/argument-guard conditions): + * Targeted residuals, by the uncovered-condition report line (17 NULL/argument-guard conditions): * tsp.c:797 idx0,idx1 - SetGenTimeAsTime ts==NULL / ValidateGmtime * tsp.c:1033 idx3,idx5 - SetFromRequest policySz==0 / serialSz==0 * tsp.c:1175 idx4 - CheckRequest nonce content mismatch @@ -125,7 +125,7 @@ * GeneralName (no mock needed) * tsp.c:2135 idx2 - TspResponse_Verify tokenSz==0 with a token * - * STRUCTURALLY UNSATISFIABLE (recorded in campaign/db/exclusions.json): + * STRUCTURALLY UNSATISFIABLE (recorded in the exclusion record): * - tsp.c:2167 idx3 `pkcs7->verifyCert == NULL`: this else-if only runs * with ret == 0, and wc_TspTstInfo_VerifyWithPKCS7() sets ret to * TSP_VERIFY_E whenever pkcs7->verifyCert is NULL, so the operand is @@ -1358,7 +1358,7 @@ int main(void) wb_response_verify_cm(); printf("done (%s)\n", wb_fail ? "with skips" : "ok"); - /* Always return 0: a nonzero exit makes the campaign discard the whole + /* Always return 0: a nonzero exit makes the harness discard the whole * variant's coverage, including the parts that did succeed. */ (void)wb_fail; return 0; diff --git a/tests/unit-mcdc/test_tsp_whitebox.c b/tests/unit-mcdc/test_tsp_whitebox.c index 8dde3c27a6..db75385860 100644 --- a/tests/unit-mcdc/test_tsp_whitebox.c +++ b/tests/unit-mcdc/test_tsp_whitebox.c @@ -38,7 +38,7 @@ * Class 9 wc_TspTstInfo_Encode() accuracy micros!=0 ............... 1 cond * Class 10 TspCheckSigningCertAttr() cert-hash mismatch ............ 1 cond * Class 11 TspCheckOneSignerInfo() SignerInfo SET walk ............. 4 conds - * Total newly exercised: 30 conditions (of 58 in the campaign's GAPS.md). + * Total newly exercised: 30 conditions (of 58 in the uncovered-condition report). * * Documented residuals (not exercised here; time-boxed out of this pass - * each needs either a fault only reachable through a platform-specific @@ -55,7 +55,7 @@ * fixture with "no EKU extension at all" or "EKU present but not * critical" was found in certs_test.h within this pass's time budget * (tsa_bad_ku_cert_der_2048 and tsa_extra_eku_cert_der_2048 cover other - * operands of the same two decisions, already outside GAPS.md). + * operands of the same two decisions, already outside the uncovered-condition report). * - tsp.c:1854 wc_TspTstInfo_VerifyWithPKCS7() contentType-OID mismatch, * tsp.c:2162/:2167/:2179/:2188/:2230 TspResponse_Verify()'s cm/cert/ * contentSz/cleanup decisions past a successful token verify: all @@ -73,7 +73,7 @@ * technique as test_pkcs12_parse_whitebox.c's Class 3), but the ASN * template's exact byte offsets were not worked out in this pass. * - * STRUCTURALLY UNSATISFIABLE (recorded in campaign/db/exclusions.json): + * STRUCTURALLY UNSATISFIABLE (recorded in the exclusion record): * - asn_tsp.c:1021 idx1 `length >= 2`, idx2 `length <= 5`. Defence in depth * behind the ASN.1 template. GetASN_Items() stores the item's FULL length * (asn.c:1948) before stepping over a BIT STRING's unused-bits byte, then @@ -702,7 +702,7 @@ int main(void) wb_check_signing_cert_attr(); wb_check_one_signer_info(); printf("done (%s)\n", wb_fail ? "with skips" : "ok"); - /* Always return 0: a nonzero exit makes the campaign discard the whole + /* Always return 0: a nonzero exit makes the harness discard the whole * variant's coverage, including the parts that did succeed. */ return 0; } diff --git a/tests/unit-mcdc/test_wc_encrypt_whitebox.c b/tests/unit-mcdc/test_wc_encrypt_whitebox.c index 906b077bd2..9d1ef7734c 100644 --- a/tests/unit-mcdc/test_wc_encrypt_whitebox.c +++ b/tests/unit-mcdc/test_wc_encrypt_whitebox.c @@ -60,7 +60,7 @@ * if (version == PKCS5v2 || version == PKCS12v1) * desIv = cbcIv; * Both operands need three rows in one binary, and the row that never - * occurs in the campaign's API-level runs is the all-false one, because + * occurs in the API-level runs is the all-false one, because * the PBE_*_DES ids are only ever reached with a PKCS#12 or PKCS#5 v2.0 * encoding. The third version value has to be one the version switch * above still accepts, otherwise ret != 0 and the cipher switch is never @@ -76,7 +76,7 @@ * both inside the range wc_PBKDF1() just wrote, so no uninitialised or * out-of-bounds read. Input is 32 bytes, a whole number of DES blocks. * - * Build: compiled by the campaign's white-box step with the same MC/DC CFLAGS + * Build: compiled by the white-box step with the same MC/DC CFLAGS * as the instrumented library, then linked against that variant's * libwolfssl.a with wc_encrypt.o removed. Not part of the wolfSSL build. */ diff --git a/tests/unit-mcdc/test_wc_lms_impl_whitebox.c b/tests/unit-mcdc/test_wc_lms_impl_whitebox.c index 2c2ac291fc..e0359e1e96 100644 --- a/tests/unit-mcdc/test_wc_lms_impl_whitebox.c +++ b/tests/unit-mcdc/test_wc_lms_impl_whitebox.c @@ -236,7 +236,7 @@ int main(void) printf("wc_lms_impl.c white-box supplement\n"); wb_run(); printf("done (%s)\n", wb_fail ? "with failures" : "ok"); - /* Setup/skip conditions are surfaced as notes, not failures: the campaign + /* Setup/skip conditions are surfaced as notes, not failures: the harness * discards a variant on nonzero exit. Genuine logic mismatches set * wb_fail; return 0 regardless so a family-unavailable skip is not a * variant-killer, but print the state above. */ diff --git a/tests/unit-mcdc/test_wc_lms_impl_whitebox_gap.c b/tests/unit-mcdc/test_wc_lms_impl_whitebox_gap.c index 3b311db1f5..6258dea18e 100644 --- a/tests/unit-mcdc/test_wc_lms_impl_whitebox_gap.c +++ b/tests/unit-mcdc/test_wc_lms_impl_whitebox_gap.c @@ -24,8 +24,8 @@ * This build variant does NOT define WOLFSSL_WC_LMS_SMALL, WOLFSSL_LMS_SHAKE256 * or WOLFSSL_LMS_SHA256_192, so the code paths guarded by those macros are dead * in this translation unit; the corresponding decisions belong to other - * campaign variants and are skipped here (see notes below and the final - * per-line residual list in the task report). + * suite variants and are skipped here (see notes below and the final + * per-line residual list in the notes). * * This white-box #includes wc_lms_impl.c directly so it can call file-static * helpers (wc_lms_treehash_init/update, wc_lmots_q_expand, ...) and the @@ -56,7 +56,7 @@ static int wb_fail = 0; /* Tree height for the two drivers that build a real multi-level HSS key. * Keygen cost is 2^h OTS keys per subtree per level, so height 5 made those * two setups alone take ~320s under MC/DC instrumentation -- over the - * campaign's 600s TEST_TIMEOUT once variants run concurrently, which scored + * suite's 600s TEST_TIMEOUT once variants run concurrently, which scored * the whole file as a skip and cost every decision in it. * * wb_make_params() bypasses wc_LmsKey_SetParameters() and lmsType is only @@ -1066,7 +1066,7 @@ static void wb_hss_full_cycle(void) {} int main(void) { - /* Unbuffered: if a driver overruns the campaign's TEST_TIMEOUT the + /* Unbuffered: if a driver overruns the TEST_TIMEOUT the * harness SIGKILLs this process, and anything still sitting in stdio's * buffer is lost -- which reports as an empty log and no clue where it * stopped. */ @@ -1088,7 +1088,7 @@ int main(void) printf("done (%s)\n", wb_fail ? "with skips" : "ok"); /* Setup failures are surfaced as skips (printed notes + wb_fail), not - * process failures: the campaign discards a variant's whole coverage + * process failures: the harness discards a variant's whole coverage * on non-zero exit, so this always returns 0. */ return 0; #endif diff --git a/tests/unit-mcdc/test_wc_mldsa_whitebox.c b/tests/unit-mcdc/test_wc_mldsa_whitebox.c index e5860b368a..41409587cc 100644 --- a/tests/unit-mcdc/test_wc_mldsa_whitebox.c +++ b/tests/unit-mcdc/test_wc_mldsa_whitebox.c @@ -44,7 +44,7 @@ * Crash-safety: all inputs are bounded, fixed-size stack arrays sized to * MLDSA_N coefficients; no helper is handed a short/NULL buffer it would * dereference past. On any unexpected result we print a note and continue; - * the binary always returns 0 so the campaign keeps the variant. + * the binary always returns 0 so the harness keeps the variant. */ /* SAVE_VECTOR_REGISTERS2() gates every SIMD dispatch in this file. In a @@ -1198,7 +1198,7 @@ static void wb_arg_guards(void) * if ((ret == 0) && valid) { ... } * * guards: `valid` goes false when a decoded hint is malformed, a norm check - * fails, or the recomputed commitment differs. A campaign that only ever + * fails, or the recomputed commitment differs. A suite that only ever * verifies signatures it just produced sees valid == 1 at every one of them, * so the operand is undriven -- and a genuinely corrupt signature is the * ordinary, in-spec way to drive it. @@ -1331,7 +1331,7 @@ static void wb_verify_invalid(void) * * The HIGH side (`s1[c] > eta`) is NOT driven here and cannot be: the unpack * is `eta - t` with t unsigned, so the decoded coefficient never exceeds eta. - * Both `> eta` operands are recorded in campaign/db/exclusions.json. + * Both `> eta` operands are recorded in the exclusion record. * ------------------------------------------------------------------------- */ #if defined(WOLFSSL_HAVE_MLDSA) && defined(WOLFSSL_MLDSA_CHECK_KEY) && \ !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) && \ diff --git a/tests/unit-mcdc/test_wc_mlkem_poly_whitebox.c b/tests/unit-mcdc/test_wc_mlkem_poly_whitebox.c index bdf4d904e7..28bdd08779 100644 --- a/tests/unit-mcdc/test_wc_mlkem_poly_whitebox.c +++ b/tests/unit-mcdc/test_wc_mlkem_poly_whitebox.c @@ -37,12 +37,12 @@ * - mlkem_ntt / mlkem_invntt / mlkem_csubq_c: exercised per-variant so each of * the four code-size arms (default / WOLFSSL_MLKEM_SMALL / * WOLFSSL_MLKEM_NO_LARGE_CODE / WOLFSSL_MLKEM_NTT_UNROLL) gets its reduction - * and butterfly loops driven when the campaign rebuilds this TU per arm. + * and butterfly loops driven when the harness rebuilds this TU per arm. * * This TU #includes wc_mlkem_poly.c so those static helpers are in scope, then calls * each with both halves of every targeted pair on tiny fixed-size buffers. * Memory-safe by construction (all buffers are MLKEM_N sword16 / bounded byte - * arrays); prints skips and returns 0 on any unexpected result so the campaign + * arrays); prints skips and returns 0 on any unexpected result so the harness * keeps the variant. */ @@ -355,7 +355,7 @@ static void wb_transform(void) * module's intel-dispatch skip). * - The USE_INTEL_SPEEDUP AVX2 rejection-sampling while-loops: USE_INTEL_SPEEDUP * is OFF by default and only compiled with the separate `--enable-intelasm` - * axis, which this campaign build does not use. + * axis, which this suite build does not use. * - `(ret == 0) && ...` chain guards in mlkem_gen_matrix_c/_i and * mlkem_get_noise_c: ret can only go non-zero via a mid-chain PRF/hash * failure, which is not selectable without corrupting library state. @@ -526,7 +526,7 @@ static const int wb_kem_types[] = { /* One key generation only: the matrix generators and their rejection-sampling * loops all hang off wc_MlKemKey_MakeKey(), and keeping the per-pass work to a * single keygen is what lets the lane sweep below afford ~100 passes inside the - * campaign's wall-clock budget. */ + * suite's wall-clock budget. */ static void wb_run_keygen(WC_RNG* rng, int type) { MlKemKey key; @@ -881,7 +881,7 @@ int main(void) wb_rej_uniform(); wb_transform(); if (wb_fail) { - /* Do not fail the campaign variant on a behavioural surprise; the + /* Do not fail the harness variant on a behavioural surprise; the * coverage is still valid. Report and exit 0. */ printf(" [wb] note: one or more sanity checks were unexpected\n"); } diff --git a/tests/unit-mcdc/test_wc_port_whitebox.c b/tests/unit-mcdc/test_wc_port_whitebox.c index e794159b33..54b82eb7c9 100644 --- a/tests/unit-mcdc/test_wc_port_whitebox.c +++ b/tests/unit-mcdc/test_wc_port_whitebox.c @@ -34,7 +34,7 @@ * * wc_accept_cloexec()'s guard `if (errno != ENOSYS && errno != EINVAL)` * (wc_port.c ~:5684) can only see errno == ENOSYS on a kernel that does not - * implement accept4(). Every host this campaign runs on does, so the first + * implement accept4(). Every host this suite runs on does, so the first * operand has no reachable independence pair from the outside. * * The white-box TU #includes wc_port.c directly, so accept4() can be @@ -296,7 +296,7 @@ int main(void) wb_strnstr(); wb_cloexec_wrappers(); printf(" [wb] failures: %d\n", wb_fail); - /* Always 0: a non-zero exit makes the campaign harness discard the + /* Always 0: a non-zero exit makes the test harness discard the * whole variant rather than record its coverage. */ return 0; } diff --git a/tests/unit-mcdc/test_wc_xmss_impl_whitebox.c b/tests/unit-mcdc/test_wc_xmss_impl_whitebox.c index aa8fed1214..6239fc996a 100644 --- a/tests/unit-mcdc/test_wc_xmss_impl_whitebox.c +++ b/tests/unit-mcdc/test_wc_xmss_impl_whitebox.c @@ -36,9 +36,9 @@ * sized for the tall parameter set and roundtrips that would exceed it are * skipped cleanly. * - * What this file deliberately does NOT cover - both are in the campaign's - * exclusion ledger (campaign/db/exclusions.json, - * EXCLUSIONS.md#condition-level-exclusions): + * What this file deliberately does NOT cover - both are in the + * exclusion ledger (the exclusion record, + * the exclusion record#condition-level-exclusions): * * 2465:2 and 4131:2 - the "c <= 4" operand of WC_IDX_INVALID's mixed * 32/64-bit arm, i.e. "((c > 4) && IDX64_INVALID(..)) || ((c <= 4) && @@ -216,7 +216,7 @@ static void wb_param_roundtrip(WC_RNG* rng, const char* paramStr) * * `(idx_len > 4)` and `(idx_len <= 4)` are exact logical complements of one * parameter, so the second of them has no independence pair by construction; - * that residual is recorded in campaign/db/exclusions.json. + * that residual is recorded in the exclusion record. */ static void wb_exhausted_index(WC_RNG* rng, const char* paramStr, int doSign) { @@ -1193,7 +1193,7 @@ static void wb_full_cycle_d1(void) * reject and is the one this test is after. (The all-0xFF marker used to * read back as "valid" because IDX32_INVALID's "(idx+1)>>h" overflowed to * 0 - a real defect, fixed in "wolfcrypt: xmss exhausted-key index marker - * wrapped and re-enabled signing" and recorded in DEATHNOTE.md.) */ + * wrapped and re-enabled signing".) */ if (exhausted) { byte idxSk[2048]; diff --git a/tests/unit-mcdc/test_wolfentropy_whitebox.c b/tests/unit-mcdc/test_wolfentropy_whitebox.c index b2a52b28ec..4cb9eb36e6 100644 --- a/tests/unit-mcdc/test_wolfentropy_whitebox.c +++ b/tests/unit-mcdc/test_wolfentropy_whitebox.c @@ -45,7 +45,7 @@ * The higher-level Entropy_Init()/wc_Entropy_OnDemandTest()/wc_Entropy_Get() * exercise the MemUse/GetSample/GetNoise/Condition path best-effort; any * setup failure is reported as a skip (return 0), never a test failure, so - * the campaign never discards the variant's coverage. + * the harness never discards the variant's coverage. * * wc_Entropy_Get() itself has two decisions whose operands reference the * SAME file-static health-test state, but are not independently selectable @@ -93,7 +93,7 @@ * preprocessed away, and wb_startup_noise_fail() below -- which is guarded on * the same macro but sits AFTER wolfentropy.c has pulled settings.h in -- * refers to a wb_sha3_refuse that does not exist. That is a compile failure, - * which the campaign scores as a SILENT SKIP. + * which the harness scores as a SILENT SKIP. */ #include #include @@ -237,7 +237,7 @@ static void wb_collect_path(void) * -- is never shown from tests/api. Both globals are file-static: drive * each operand's independence pair directly, holding the other operand * false (the "both false" side is already exercised by every other - * steady-state call in this campaign, e.g. wb_collect_path() above). */ + * steady-state call in this suite, e.g. wb_collect_path() above). */ static void wb_startup_retrigger(void) { int ret; @@ -463,6 +463,6 @@ int main(void) wb_entropy_get_mutex(); printf("done (%s)\n", wb_fail ? "with skips" : "ok"); /* Setup issues are surfaced as skips; a nonzero exit would make the - * campaign discard this variant's coverage. */ + * suite discard this variant's coverage. */ return 0; } diff --git a/tests/unit-mcdc/test_wolfmath_whitebox.c b/tests/unit-mcdc/test_wolfmath_whitebox.c index 7d05a673d1..bd4f8c8752 100644 --- a/tests/unit-mcdc/test_wolfmath_whitebox.c +++ b/tests/unit-mcdc/test_wolfmath_whitebox.c @@ -40,7 +40,7 @@ * Both halves therefore hang off the same lever: the randomness mp_rand() * consumes. Waiting for the real RNG to hand out an all-zero top digit is not * an option for ASIL-D evidence (it is a 2^-64 lottery, and rule 3 of this - * campaign forbids evidence that depends on a live draw), so this TU scripts + * suite forbids evidence that depends on a live draw), so this TU scripts * the stream instead. * * HOW -- MACRO INTERPOSITION ON wc_RNG_GenerateBlock() @@ -64,10 +64,10 @@ * * (T,T) against (F,.) is idx0's independence pair; (T,T) against (T,F) is * idx1's. Both are completed inside this binary, which is what MC/DC needs: - * llvm-cov computes independence per binary and the campaign only ORs the + * llvm-cov computes independence per binary and the harness only ORs the * resulting bits by line:col. * - * Build: compiled by run-mcdc-par.sh's white-box step with the SAME MC/DC + * Build: compiled by the coverage runner's white-box step with the SAME MC/DC * CFLAGS, -DHAVE_CONFIG_H and -I as the instrumented library, then * linked against that variant's libwolfssl.a with its wolfmath.o removed * (this TU supplies the instrumented wolfmath.c). NOT part of the wolfSSL @@ -80,7 +80,7 @@ /* Declare the hook explicitly rather than relying on the macro to rewrite * random.h's own prototype: if anything drags random.h in first, the include * guard skips that prototype, the hook is never declared, and every call site - * inside wolfmath.c fails to compile -- which the campaign scores as a SILENT + * inside wolfmath.c fails to compile -- which the harness scores as a SILENT * SKIP (see the same note in mcdc_seed_rng.h). */ static int wb_wm_rng_block(WC_RNG* rng, byte* out, word32 sz); @@ -208,6 +208,6 @@ int main(void) wb_mp_rand_top_digit(); printf("done (%s)\n", wb_fail ? "with skips" : "ok"); /* Setup issues are surfaced as skips; a nonzero exit would make the - * campaign discard this variant's coverage. */ + * suite discard this variant's coverage. */ return 0; } diff --git a/tests/unit-mcdc/test_xmss_fault_whitebox.c b/tests/unit-mcdc/test_xmss_fault_whitebox.c index 01e553b430..27c0c07a19 100644 --- a/tests/unit-mcdc/test_xmss_fault_whitebox.c +++ b/tests/unit-mcdc/test_xmss_fault_whitebox.c @@ -26,7 +26,7 @@ * * None of these decisions need a real XMSS keypair. Every guard chain here * is closed with the SMALLEST built-in parameter set (XMSS-SHA2_10_256, - * height 10 - available under every campaign variant since + * height 10 - available under every suite variant since * WOLFSSL_XMSS_MIN_HEIGHT defaults to 10) purely for its XmssParams fields * (sig_len/pk_len/sk_len); wc_XmssKey_SetParamStr() never generates keys, so * it costs nothing. key->state is then poked directly (this file #includes @@ -57,7 +57,7 @@ * This #includes wc_xmss.c directly (like the sibling wc_xmss_impl.c * white-box) so key->state and the other private fields are reachable. * - * Invocation: no arguments; runs the full sweep (the campaign's + * Invocation: no arguments; runs the full sweep (the * run_whitebox harness invokes the binary with none). */ @@ -908,7 +908,7 @@ static void wb_import_pub_raw_guard(void) * 907: same function's * "if (labelLen == 0 || labelLen > XMSS_MAX_LABEL_LEN)" * WOLF_PRIVATE_KEY_ID is auto-enabled by settings.h whenever - * HAVE_PK_CALLBACKS is set (true for every campaign variant here), so this + * HAVE_PK_CALLBACKS is set (true for every suite variant here), so this * is not a dead gate in practice. ********************************************/ static void wb_init_id_label(void) diff --git a/tests/unit-mcdc/test_xmss_hash_fault_whitebox.c b/tests/unit-mcdc/test_xmss_hash_fault_whitebox.c index 27f8956b69..fe7f511c58 100644 --- a/tests/unit-mcdc/test_xmss_hash_fault_whitebox.c +++ b/tests/unit-mcdc/test_xmss_hash_fault_whitebox.c @@ -22,7 +22,7 @@ /* * MC/DC hash-fault white-box supplement for wolfcrypt/src/wc_xmss_impl.c. * - * campaign/reports/xmss/GAPS.md is entirely error-propagation: + * suite/reports/xmss/the uncovered-condition report is entirely error-propagation: * * for (i = 1; (ret == 0) && (i < params->wots_len); i++) -- WOTS+ chain * for (i = 0; (ret == 0) && (i < params->d); i++) -- subtree loops @@ -91,7 +91,7 @@ static int wb_fail = 0; #define WB_POINTS 192 #define WB_DEADLINE_S 170 -/* WALL clock, not clock(): the campaign runs several variants concurrently and +/* WALL clock, not clock(): the harness runs several variants concurrently and * TEST_TIMEOUT is 600 s of WALL time. Under that contention CPU time accrues * far slower than wall time, so a CPU-time budget would sail past the timeout * -- and a timed-out white-box is scored as a SILENT SKIP that loses the whole @@ -450,6 +450,6 @@ int main(void) #endif printf("done (%s)\n", wb_fail ? "with failures" : "ok"); - /* A non-zero exit makes the campaign discard this binary's coverage. */ + /* A non-zero exit makes the harness discard this binary's coverage. */ return 0; } From b844d337afb984007fd7faf87d7d5690112f317d Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 19:11:32 +0200 Subject: [PATCH 37/57] tests: fix tls13 test guard scope and register the new files with cmake --- CMakeLists.txt | 2 ++ tests/api/test_tls13_features.c | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 08ae2c8b8a..5c477a27f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4550,6 +4550,8 @@ if(WOLFSSL_EXAMPLES) tests/api/test_evp_pkey.c tests/api/test_certman.c tests/api/test_tls13.c + tests/api/test_tls13_bounds.c + tests/api/test_tls13_features.c tests/srp.c tests/suites.c tests/w64wrapper.c diff --git a/tests/api/test_tls13_features.c b/tests/api/test_tls13_features.c index fda8a54385..7528e26d0e 100644 --- a/tests/api/test_tls13_features.c +++ b/tests/api/test_tls13_features.c @@ -486,7 +486,13 @@ int test_tls13_feat_optional_psk_falls_back_to_cert(void) * status_request extension held on the CTX. * ---------------------------------------------------------------------- */ -#if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && defined(HAVE_OCSP) +/* Guard must match test_tls13_feat_pha_ctx_status_request() below exactly: these + * are its only callers, and a narrower guard leaves them defined-but-unused + * under -Werror=unused-function. */ +#if defined(WOLFSSL_POST_HANDSHAKE_AUTH) && \ + defined(HAVE_CERTIFICATE_STATUS_REQUEST) && defined(HAVE_OCSP) && \ + defined(KEEP_PEER_CERT) && \ + !defined(NO_CERTS) && !defined(NO_FILESYSTEM) && !defined(NO_RSA) static int test_tls13_feat_ocsp_io_cb(void* ioCtx, const char* url, int urlSz, unsigned char* req, int reqSz, unsigned char** resp) { From 0545aca768bbbbfa7420d3b20d2d7ca11180fc2b Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 20:43:39 +0200 Subject: [PATCH 38/57] tests: guard the ech round's session-ticket assertion on HAVE_SESSION_TICKET --enable-ech alone leaves session tickets off, so the server sends nothing after the handshake and got_session_ticket stays 0. --- tests/api/test_tls13_features.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/api/test_tls13_features.c b/tests/api/test_tls13_features.c index 7528e26d0e..9e10aa844b 100644 --- a/tests/api/test_tls13_features.c +++ b/tests/api/test_tls13_features.c @@ -911,10 +911,15 @@ static int test_tls13_feat_ech_round(int echEnabled) ExpectIntEQ(ssl_s->options.havePeerCert, 1); /* Drain the post-handshake NewSessionTicket so DoTls13NewSessionTicket() - * runs with the ECH state still attached. */ + * runs with the ECH state still attached. A build can enable ECH without + * session tickets (--enable-ech alone leaves "Session Ticket: no"), and + * then the server sends nothing after the handshake: the read still + * reports WANT_READ, but there is no ticket to have received. */ ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); +#ifdef HAVE_SESSION_TICKET ExpectIntEQ(ssl_c->msgsReceived.got_session_ticket, 1); +#endif wolfSSL_free(ssl_c); wolfSSL_CTX_free(ctx_c); From 1e8d00bf48ee851ac23103a06b3a7fbe2771a543 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 20:47:03 +0200 Subject: [PATCH 39/57] tests: add three tls extension test files to the tls group Empty skeletons registered with automake, cmake and the api test table so cases can be added without further build-system changes. --- CMakeLists.txt | 3 +++ tests/api.c | 6 ++++++ tests/api/include.am | 6 ++++++ tests/api/test_tls_bounds.c | 36 ++++++++++++++++++++++++++++++++++++ tests/api/test_tls_bounds.h | 30 ++++++++++++++++++++++++++++++ tests/api/test_tls_msgtype.c | 36 ++++++++++++++++++++++++++++++++++++ tests/api/test_tls_msgtype.h | 30 ++++++++++++++++++++++++++++++ tests/api/test_tls_parse.c | 36 ++++++++++++++++++++++++++++++++++++ tests/api/test_tls_parse.h | 30 ++++++++++++++++++++++++++++++ 9 files changed, 213 insertions(+) create mode 100644 tests/api/test_tls_bounds.c create mode 100644 tests/api/test_tls_bounds.h create mode 100644 tests/api/test_tls_msgtype.c create mode 100644 tests/api/test_tls_msgtype.h create mode 100644 tests/api/test_tls_parse.c create mode 100644 tests/api/test_tls_parse.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 5c477a27f4..331b51f5af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4510,6 +4510,9 @@ if(WOLFSSL_EXAMPLES) tests/api/test_evp.c tests/api/test_tls_ext.c tests/api/test_tls.c + tests/api/test_tls_bounds.c + tests/api/test_tls_msgtype.c + tests/api/test_tls_parse.c tests/api/test_session.c tests/api/test_x509.c tests/api/test_asn.c diff --git a/tests/api.c b/tests/api.c index 0587653c4c..76778a0ab7 100644 --- a/tests/api.c +++ b/tests/api.c @@ -268,6 +268,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -40682,6 +40685,9 @@ TEST_CASE testCases[] = { TEST_DECL(test_ocsp_responder), TEST_DECL(test_wolfIO_DecodeUrl_crlf_reject), TEST_TLS_DECLS, + TEST_TLS_BOUNDS_DECLS, + TEST_TLS_MSGTYPE_DECLS, + TEST_TLS_PARSE_DECLS, TEST_SESSION_DECLS, TEST_DECL(test_wc_DhSetNamedKey), TEST_DECL(test_DhAgree_rejects_p_minus_1), diff --git a/tests/api/include.am b/tests/api/include.am index b4610b1735..849414b107 100644 --- a/tests/api/include.am +++ b/tests/api/include.am @@ -81,6 +81,9 @@ tests_unit_test_SOURCES += tests/api/test_ocsp.c tests_unit_test_SOURCES += tests/api/test_evp.c tests_unit_test_SOURCES += tests/api/test_tls_ext.c tests_unit_test_SOURCES += tests/api/test_tls.c +tests_unit_test_SOURCES += tests/api/test_tls_bounds.c +tests_unit_test_SOURCES += tests/api/test_tls_msgtype.c +tests_unit_test_SOURCES += tests/api/test_tls_parse.c tests_unit_test_SOURCES += tests/api/test_session.c # Certs tests_unit_test_SOURCES += tests/api/test_x509.c @@ -218,6 +221,9 @@ EXTRA_DIST += tests/api/create_x500_unique_id_certs.py EXTRA_DIST += tests/api/test_evp.h EXTRA_DIST += tests/api/test_tls_ext.h EXTRA_DIST += tests/api/test_tls.h +EXTRA_DIST += tests/api/test_tls_bounds.h +EXTRA_DIST += tests/api/test_tls_msgtype.h +EXTRA_DIST += tests/api/test_tls_parse.h EXTRA_DIST += tests/api/test_session.h EXTRA_DIST += tests/api/test_x509.h EXTRA_DIST += tests/api/test_asn.h diff --git a/tests/api/test_tls_bounds.c b/tests/api/test_tls_bounds.c new file mode 100644 index 0000000000..0a08384de8 --- /dev/null +++ b/tests/api/test_tls_bounds.c @@ -0,0 +1,36 @@ +/* test_tls_bounds.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include +#include +#include +#include + +#include +#include + +/* Placeholder so the translation unit is never empty and the group always has + * at least one member. Replace when the first real case lands. */ +int test_tls_bounds_placeholder(void) +{ + EXPECT_DECLS; + return EXPECT_RESULT(); +} diff --git a/tests/api/test_tls_bounds.h b/tests/api/test_tls_bounds.h new file mode 100644 index 0000000000..247186c802 --- /dev/null +++ b/tests/api/test_tls_bounds.h @@ -0,0 +1,30 @@ +/* test_tls_bounds.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifndef TESTS_API_TEST_TLS_BOUNDS_H +#define TESTS_API_TEST_TLS_BOUNDS_H + +int test_tls_bounds_placeholder(void); + +#define TEST_TLS_BOUNDS_DECLS \ + TEST_DECL_GROUP("tls", test_tls_bounds_placeholder) + +#endif /* TESTS_API_TEST_TLS_BOUNDS_H */ diff --git a/tests/api/test_tls_msgtype.c b/tests/api/test_tls_msgtype.c new file mode 100644 index 0000000000..1d661b0719 --- /dev/null +++ b/tests/api/test_tls_msgtype.c @@ -0,0 +1,36 @@ +/* test_tls_msgtype.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include +#include +#include +#include + +#include +#include + +/* Placeholder so the translation unit is never empty and the group always has + * at least one member. Replace when the first real case lands. */ +int test_tls_msgtype_placeholder(void) +{ + EXPECT_DECLS; + return EXPECT_RESULT(); +} diff --git a/tests/api/test_tls_msgtype.h b/tests/api/test_tls_msgtype.h new file mode 100644 index 0000000000..892a677c48 --- /dev/null +++ b/tests/api/test_tls_msgtype.h @@ -0,0 +1,30 @@ +/* test_tls_msgtype.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifndef TESTS_API_TEST_TLS_MSGTYPE_H +#define TESTS_API_TEST_TLS_MSGTYPE_H + +int test_tls_msgtype_placeholder(void); + +#define TEST_TLS_MSGTYPE_DECLS \ + TEST_DECL_GROUP("tls", test_tls_msgtype_placeholder) + +#endif /* TESTS_API_TEST_TLS_MSGTYPE_H */ diff --git a/tests/api/test_tls_parse.c b/tests/api/test_tls_parse.c new file mode 100644 index 0000000000..ff0fcb1ebe --- /dev/null +++ b/tests/api/test_tls_parse.c @@ -0,0 +1,36 @@ +/* test_tls_parse.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include +#include +#include +#include + +#include +#include + +/* Placeholder so the translation unit is never empty and the group always has + * at least one member. Replace when the first real case lands. */ +int test_tls_parse_placeholder(void) +{ + EXPECT_DECLS; + return EXPECT_RESULT(); +} diff --git a/tests/api/test_tls_parse.h b/tests/api/test_tls_parse.h new file mode 100644 index 0000000000..cfe58d8d93 --- /dev/null +++ b/tests/api/test_tls_parse.h @@ -0,0 +1,30 @@ +/* test_tls_parse.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifndef TESTS_API_TEST_TLS_PARSE_H +#define TESTS_API_TEST_TLS_PARSE_H + +int test_tls_parse_placeholder(void); + +#define TEST_TLS_PARSE_DECLS \ + TEST_DECL_GROUP("tls", test_tls_parse_placeholder) + +#endif /* TESTS_API_TEST_TLS_PARSE_H */ From fa088cb3bb1abd1bde1147590c27a831f4aea00a Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 21:25:53 +0200 Subject: [PATCH 40/57] tests: close tls.c argument-guard, CSR/CSR2 and TLS 1.2 MAC MC/DC conditions Covers TLSX_UseSNI/UseALPN/UseMaxFragment/UseCertificateStatusRequest(V2) argument guards, TLSX_SupportExtensions, TLSX_CSR2_InitRequests/ ForceRequest, TLSX_CSR_GetRequest_ex, wolfSSL_make_eap_keys, wolfSSL_SetTlsHmacInner, BuildTlsHandshakeHash, TLS_hmac's size-overflow and constant-time dispatch, and the ALPN/Cookie extension size guards. --- tests/api/test_tls_bounds.c | 813 +++++++++++++++++++++++++++++++++++- tests/api/test_tls_bounds.h | 33 +- 2 files changed, 841 insertions(+), 5 deletions(-) diff --git a/tests/api/test_tls_bounds.c b/tests/api/test_tls_bounds.c index 0a08384de8..4df603d4a6 100644 --- a/tests/api/test_tls_bounds.c +++ b/tests/api/test_tls_bounds.c @@ -27,10 +27,817 @@ #include #include -/* Placeholder so the translation unit is never empty and the group always has - * at least one member. Replace when the first real case lands. */ -int test_tls_bounds_placeholder(void) +/* ---------------------------------------------------------------------- */ +/* TLSX_UseSNI - the extensions list argument is always &ssl->extensions or + * &ctx->extensions (wolfSSL_UseSNI / wolfSSL_CTX_UseSNI), or &ech->extensions + * (the ECH echo path in TLSX_SNI_Parse) - always the address of a struct + * member, never NULL. The "extensions == NULL" half of the guard has no + * reachable caller and is excluded (argued in the report, not retested here). + * The "data == NULL" half, the host-name-length guard, and the duplicate-type + * removal in the linked list are all reachable through wolfSSL_UseSNI(). */ +#if defined(HAVE_SNI) && !defined(NO_WOLFSSL_CLIENT) +int test_TLSX_UseSNI_bounds(void) { EXPECT_DECLS; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + char longName[WOLFSSL_HOST_NAME_MAX + 1]; + + XMEMSET(longName, 'a', sizeof(longName) - 1); + longName[sizeof(longName) - 1] = '\0'; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + + /* data == NULL -> BAD_FUNC_ARG (extensions is always non-NULL here). */ + ExpectIntEQ(wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, NULL, 8), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* type == HOST_NAME && size >= MAX -> BAD_LENGTH_E. */ + ExpectIntEQ(wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, longName, + (word16)XSTRLEN(longName)), WC_NO_ERR_TRACE(BAD_LENGTH_E)); + + /* type != HOST_NAME: the length guard is skipped regardless of size (the + * BAD_LENGTH_E path is not taken), independence for the first operand of + * the (type == HOST_NAME) && (size >= MAX) guard. WOLFSSL_SNI_HOST_NAME + * is the only type TLSX_SNI_New() accepts, so this then fails later, + * inside TLSX_SNI_New()'s own type switch, with MEMORY_E - a different + * and later guard than the one under test here. */ + ExpectIntEQ(wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME + 1, longName, + (word16)XSTRLEN(longName)), WC_NO_ERR_TRACE(MEMORY_E)); + + /* type == HOST_NAME && size < MAX -> accepted; also the first insert, + * so the duplicate-type list walk has nothing to match yet. */ + ExpectIntEQ(wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, "a.example.com", + 13), WOLFSSL_SUCCESS); + + /* A second insert: the walk finds sni->next->type == type on the first + * node and removes the duplicate. + * + * "sni->next && sni->next->type == type" (the duplicate-removal check) + * is not driven for independence anywhere in this file: WOLFSSL_SNI_ + * HOST_NAME is the only type TLSX_SNI_New() will ever construct (its + * switch on sni->type frees and rejects anything else), so every node + * that ever exists in the list has type == WOLFSSL_SNI_HOST_NAME, and + * "type" here (the argument of the call reaching this loop) must also + * be WOLFSSL_SNI_HOST_NAME or TLSX_UseSNI() would already have returned + * MEMORY_E from TLSX_SNI_New() before the loop is reached. So whenever + * "sni->next" is true, "sni->next->type == type" is true too - the loop + * always removes the one prior node on its first check and breaks, + * which also means a second loop iteration (where sni->next could be + * NULL) is never reached. Both operands are pinned to true on every + * real execution; excluded (family 4: contradicted by the callee + * postcondition of TLSX_SNI_New()). */ + ExpectIntEQ(wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, "c.example.com", + 13), WOLFSSL_SUCCESS); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + return EXPECT_RESULT(); +} +#endif + +/* ---------------------------------------------------------------------- */ +/* TLSX_UseALPN - extensions is always &ssl->extensions (wolfSSL_UseALPN()); + * unreachable-NULL, excluded. data == NULL: wolfSSL_UseALPN() only calls + * TLSX_UseALPN() with tokens produced by XSTRTOK(), which are never NULL + * inside the "while (token[idx] != NULL)" loop, so the only way to reach + * TLSX_UseALPN() with a NULL data pointer at all is to call it directly. */ +#if defined(HAVE_ALPN) && !defined(NO_WOLFSSL_CLIENT) +int test_TLSX_UseALPN_bounds(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + + ExpectIntEQ(wolfSSL_UseALPN(ssl, (char*)"http/1.1", 8, + WOLFSSL_ALPN_FAILED_ON_MISMATCH), WOLFSSL_SUCCESS); + +#ifdef WOLFSSL_TEST_STATIC_BUILD + /* No wrapper can pass data == NULL; extensions == NULL is likewise + * unreachable through any caller (always &ssl->extensions), and is + * exercised here only to document that the guard exists, not to claim + * it as a caller-reachable pair. */ + ExpectIntEQ(TLSX_UseALPN(&ssl->extensions, NULL, 4, + WOLFSSL_ALPN_FAILED_ON_MISMATCH, ssl->heap), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); +#endif + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + return EXPECT_RESULT(); +} +#endif + +/* ---------------------------------------------------------------------- */ +/* TLSX_UseMaxFragment - extensions always &ssl->extensions / &ctx->extensions; + * unreachable-NULL, excluded. mfl < MIN and mfl > MAX are both reachable + * through the public wrapper with an out-of-range code, alongside a valid + * in-range call. */ +#if defined(HAVE_MAX_FRAGMENT) && !defined(NO_WOLFSSL_CLIENT) +int test_TLSX_UseMaxFragment_bounds(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + + /* mfl < WOLFSSL_MFL_MIN (0 is below WOLFSSL_MFL_2_9 == 1). */ + ExpectIntEQ(wolfSSL_UseMaxFragment(ssl, WOLFSSL_MFL_DISABLED), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* mfl > WOLFSSL_MFL_MAX (WOLFSSL_MFL_2_8 == 6 is the maximum code). */ + ExpectIntEQ(wolfSSL_UseMaxFragment(ssl, (byte)(WOLFSSL_MFL_MAX + 1)), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* In range: accepted. */ + ExpectIntEQ(wolfSSL_UseMaxFragment(ssl, WOLFSSL_MFL_2_11), + WOLFSSL_SUCCESS); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + return EXPECT_RESULT(); +} +#endif + +/* ---------------------------------------------------------------------- */ +/* TLSX_UseCertificateStatusRequest - extensions is always &ssl->extensions / + * &ctx->extensions (wolfSSL_UseOCSPStapling() / _CTX_); unreachable-NULL, + * excluded. status_type != WOLFSSL_CSR_OCSP is reachable directly: the + * wrapper passes the caller's status_type straight through with no + * validation of its own. */ +#if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(NO_WOLFSSL_CLIENT) +int test_TLSX_UseCertificateStatusRequest_bounds(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + + /* status_type != WOLFSSL_CSR_OCSP. */ + ExpectIntEQ(wolfSSL_UseOCSPStapling(ssl, WOLFSSL_CSR_OCSP + 1, 0), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* Valid request. */ + ExpectIntEQ(wolfSSL_UseOCSPStapling(ssl, WOLFSSL_CSR_OCSP, + WOLFSSL_CSR_OCSP_USE_NONCE), WOLFSSL_SUCCESS); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + return EXPECT_RESULT(); +} +#endif + +/* ---------------------------------------------------------------------- */ +/* TLSX_UseCertificateStatusRequestV2 - same pattern as V1 above. */ +#if defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) && !defined(NO_WOLFSSL_CLIENT) +int test_TLSX_UseCertificateStatusRequestV2_bounds(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + + /* Neither WOLFSSL_CSR2_OCSP nor WOLFSSL_CSR2_OCSP_MULTI: both operands + * of "status_type != OCSP && status_type != OCSP_MULTI" true. */ + ExpectIntEQ(wolfSSL_UseOCSPStaplingV2(ssl, + WOLFSSL_CSR2_OCSP_MULTI + 1, 0), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* status_type == OCSP_MULTI: first operand true (MULTI != OCSP), second + * operand false (MULTI == MULTI) - independence for the second operand, + * paired against the invalid-type call above. */ + ExpectIntEQ(wolfSSL_UseOCSPStaplingV2(ssl, WOLFSSL_CSR2_OCSP_MULTI, + WOLFSSL_CSR2_OCSP_USE_NONCE), WOLFSSL_SUCCESS); + + /* status_type == OCSP: first operand false, short-circuits past the + * second - independence for the first operand. */ + ExpectIntEQ(wolfSSL_UseOCSPStaplingV2(ssl, WOLFSSL_CSR2_OCSP, + WOLFSSL_CSR2_OCSP_USE_NONCE), WOLFSSL_SUCCESS); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + return EXPECT_RESULT(); +} +#endif + +/* ---------------------------------------------------------------------- */ +/* TLSX_SupportExtensions() - "return ssl && (IsTLS(ssl) || + * ssl->version.major == DTLS_MAJOR);" reached through the WOLFSSL_TEST_VIS + * TLSX_WriteRequest(), which calls it as its leading guard. + * + * With WOLFSSL_DTLS defined (as it is in this build), IsTLS() itself already + * returns true for ssl->version.major == DTLS_MAJOR (it has its own + * "#ifdef WOLFSSL_DTLS if (ssl->version.major == DTLS_MAJOR) return 1;" + * check). So the third operand here can only be evaluated (IsTLS() false) + * when major is neither a valid TLS major/minor pair nor DTLS_MAJOR - and in + * that same case the third operand's own check of major == DTLS_MAJOR is + * false too. The third operand's true side is therefore contradicted by + * IsTLS()'s postcondition in this build and is excluded (family 4); only its + * (always-false-when-reached) value is exercised below, alongside the first + * and second operands. */ +#if !defined(NO_WOLFSSL_CLIENT) +int test_TLSX_SupportExtensions_bounds(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte out[512]; + word32 offset = 0; + + /* ssl == NULL: the leading operand alone decides the result. */ + ExpectIntEQ(TLSX_WriteRequest(NULL, out, client_hello, &offset), 0); + + /* A live ssl object: IsTLS(ssl) true (real major/minor), so ssl && + * IsTLS(ssl) is already true and the third operand is never reached - + * independence for the first and second operands. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + offset = 0; + ExpectIntEQ(TLSX_WriteRequest(ssl, out, client_hello, &offset), 0); + + /* Corrupt ssl->version.major to a value IsTLS() does not recognize + * (neither SSLv3_MAJOR nor DTLS_MAJOR): IsTLS(ssl) is now false, and the + * third operand - checking that very same field for DTLS_MAJOR - is + * false too. This is the only way to make IsTLS(ssl) false at all: every + * ssl created through a real method sets a recognized major/minor. */ + ssl->version.major = 0; + offset = 0; + ExpectIntEQ(TLSX_WriteRequest(ssl, out, client_hello, &offset), 0); + ssl->version.major = SSLv3_MAJOR; + ssl->version.minor = TLSv1_2_MINOR; + + /* TLSX_WriteRequest()'s own leading guard is + * "!TLSX_SupportExtensions(ssl) || output == NULL": a supported ssl with + * output == NULL exercises the second operand independently of the + * ssl == NULL call above. */ + offset = 0; + ExpectIntEQ(TLSX_WriteRequest(ssl, NULL, client_hello, &offset), 0); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + return EXPECT_RESULT(); +} +#endif + +/* ---------------------------------------------------------------------- */ +/* TLSX_EarlyData_Use() - "extension->val = (WOLFSSL_IS_QUIC(ssl) && + * is_response && maxSz > 0) ? WOLFSSL_MAX_32BIT : maxSz;" - this build has no + * WOLFSSL_QUIC, so WOLFSSL_IS_QUIC() expands to the literal 0: the first + * operand is a compile-time constant false in every translation unit of this + * binary. Because of the left-to-right && short circuit, is_response and + * maxSz > 0 can never be evaluated either - all three operands of this + * decision are unreachable in this configuration. Excluded (family 3: fixed + * by the branch/config that reaches it). + * No test body: nothing to drive. */ + +/* ---------------------------------------------------------------------- */ +/* TLSX_CSR2_InitRequests(), TLSX_CSR2_ForceRequest(), TLSX_CSR_GetRequest_ex() + * are WOLFSSL_LOCAL with no public wrapper; each is unit-tested directly by + * building the minimal extension/context state each one dereferences. */ +#if defined(WOLFSSL_TEST_STATIC_BUILD) && \ + defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) && !defined(NO_CERTS) +int test_TLSX_CSR2_InitRequests_bounds(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + DecodedCert cert; + TLSX* ext; + CertificateStatusRequestItemV2* csr2; + + XMEMSET(&cert, 0, sizeof(cert)); + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + + ExpectIntEQ(TLSX_UseCertificateStatusRequestV2(&ssl->extensions, + WOLFSSL_CSR2_OCSP, 0, ssl->heap, ssl->devId), WOLFSSL_SUCCESS); + ext = TLSX_Find(ssl->extensions, TLSX_STATUS_REQUEST_V2); + ExpectNotNull(ext); + + if (ext != NULL) { + csr2 = (CertificateStatusRequestItemV2*)ext->data; + + /* isPeer == 0: "!isPeer" true, short-circuits past requests != 0. */ + ExpectIntEQ(TLSX_CSR2_InitRequests(ssl->extensions, &cert, 0, + ssl->heap), 0); + + /* isPeer == 1, requests == 0: both operands false, falls through to + * build the request from the (empty) DecodedCert. */ + ExpectIntEQ(TLSX_CSR2_InitRequests(ssl->extensions, &cert, 1, + ssl->heap), 0); + ExpectIntEQ(csr2->requests, 1); + + /* isPeer == 1, requests != 0: "!isPeer" false, "requests != 0" + * true - independence for the second operand. */ + ExpectIntEQ(TLSX_CSR2_InitRequests(ssl->extensions, &cert, 1, + ssl->heap), 0); + ExpectIntEQ(csr2->requests, 1); + } + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + return EXPECT_RESULT(); +} + +int test_TLSX_CSR2_ForceRequest_bounds(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + TLSX* ext; + CertificateStatusRequestItemV2* csr2; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + + ExpectIntEQ(TLSX_UseCertificateStatusRequestV2(&ssl->extensions, + WOLFSSL_CSR2_OCSP, 0, ssl->heap, ssl->devId), WOLFSSL_SUCCESS); + ext = TLSX_Find(ssl->extensions, TLSX_STATUS_REQUEST_V2); + ExpectNotNull(ext); + + if (ext != NULL) { + csr2 = (CertificateStatusRequestItemV2*)ext->data; + + /* ocspEnabled false: whole AND is false regardless of requests. */ + SSL_CM(ssl)->ocspEnabled = 0; + csr2->requests = 1; + ExpectIntEQ(TLSX_CSR2_ForceRequest(ssl), WC_NO_ERR_TRACE(OCSP_LOOKUP_FAIL)); + + /* ocspEnabled true, requests == 0: first operand true, second + * false - independence for the second operand's false side. */ + SSL_CM(ssl)->ocspEnabled = 1; + csr2->requests = 0; + ExpectIntEQ(TLSX_CSR2_ForceRequest(ssl), WC_NO_ERR_TRACE(OCSP_LOOKUP_FAIL)); + + /* ocspEnabled true, requests >= 1: both operands true, so the + * lookup runs. No responder is configured on this CertManager, so + * CheckOcspRequest() reports BAD_FUNC_ARG on its own ocsp == NULL + * guard rather than reaching the network. */ + csr2->requests = 1; + ExpectIntEQ(TLSX_CSR2_ForceRequest(ssl), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + } + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + return EXPECT_RESULT(); +} +#endif + +#if defined(WOLFSSL_TEST_STATIC_BUILD) && \ + defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(WOLFSSL_NO_TLS12) && \ + !defined(NO_WOLFSSL_CLIENT) +int test_TLSX_CSR_GetRequest_ex_bounds(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + + /* A TLS 1.2-only method keeps IsAtLeastTLSv1_3(csr->ssl->version) false, + * so the idx == 0 branch below is deterministic without negotiating a + * version first. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + + /* csr == NULL: no TLSX_STATUS_REQUEST extension present at all. */ + ExpectNull(TLSX_CSR_GetRequest_ex(ssl->extensions, 0)); + + /* csr != NULL, csr->ssl == NULL - reachable only by calling the + * WOLFSSL_LOCAL constructor directly with a NULL ssl; the public + * wrapper (wolfSSL_UseOCSPStapling -> TLSX_UseCertificateStatusRequest) + * always forwards a live ssl pointer, so this half needs the direct + * call too. */ + ExpectIntEQ(TLSX_UseCertificateStatusRequest(&ssl->extensions, + WOLFSSL_CSR_OCSP, 0, NULL, ssl->heap, ssl->devId), + WOLFSSL_SUCCESS); + ExpectNull(TLSX_CSR_GetRequest_ex(ssl->extensions, 0)); + + /* csr != NULL, csr->ssl != NULL. */ + TLSX_Remove(&ssl->extensions, TLSX_STATUS_REQUEST, ssl->heap); + ExpectIntEQ(TLSX_UseCertificateStatusRequest(&ssl->extensions, + WOLFSSL_CSR_OCSP, 0, ssl, ssl->heap, ssl->devId), + WOLFSSL_SUCCESS); + /* Pre-TLS1.3 ssl: idx == 0 returns the sole ocsp[0] slot. */ + ExpectNotNull(TLSX_CSR_GetRequest_ex(ssl->extensions, 0)); + ExpectNull(TLSX_CSR_GetRequest_ex(ssl->extensions, 1)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + return EXPECT_RESULT(); +} +#endif + + +/* ---------------------------------------------------------------------- */ +/* wolfSSL_make_eap_keys() - "ssl == NULL || ssl->arrays == NULL". ssl->arrays + * is allocated lazily by the handshake and is still NULL on a freshly + * created object, so both operands are reachable without completing a + * handshake at all. */ +#if defined(WOLFSSL_HAVE_PRF) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(WOLFSSL_NO_TLS12) && defined(WOLFSSL_TEST_STATIC_BUILD) +int test_wolfSSL_make_eap_keys_bounds(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte key[32]; + + /* ssl == NULL. */ + ExpectIntEQ(wolfSSL_make_eap_keys(NULL, key, sizeof(key), "label"), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + + /* ssl != NULL, ssl->arrays != NULL: wolfSSL_new() -> ReinitSSL() already + * allocated it, so this is the default state. */ + ExpectNotNull(ssl->arrays); + ssl->specs.mac_algorithm = sha256_mac; + ExpectIntEQ(wolfSSL_make_eap_keys(ssl, key, sizeof(key), "label"), 0); + + /* ssl != NULL, ssl->arrays == NULL: FreeArrays() is what clears it in + * real use, once the handshake has finished with the randoms/master + * secret and no longer needs them - called directly here (WOLFSSL_LOCAL) + * rather than running a full handshake just to reach the same state. */ + FreeArrays(ssl, 0); + ExpectNull(ssl->arrays); + ExpectIntEQ(wolfSSL_make_eap_keys(ssl, key, sizeof(key), "label"), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + return EXPECT_RESULT(); +} +#endif + +/* ---------------------------------------------------------------------- */ +/* wolfSSL_SetTlsHmacInner() - "ssl == NULL || inner == NULL", then + * "content == dtls12_cid || (ssl->options.dtls && DtlsGetCidTxSize(ssl) > + * 0)". Both are public (WOLFSSL_API) and reachable directly. */ +#if !defined(NO_WOLFSSL_CLIENT) && !defined(WOLFSSL_AEAD_ONLY) +int test_wolfSSL_SetTlsHmacInner_bounds(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte inner[WOLFSSL_TLS_HMAC_INNER_SZ]; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + + /* ssl == NULL. */ + ExpectIntEQ(wolfSSL_SetTlsHmacInner(NULL, inner, 10, application_data, 0), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* ssl != NULL, inner == NULL. */ + ExpectIntEQ(wolfSSL_SetTlsHmacInner(ssl, NULL, 10, application_data, 0), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* content == dtls12_cid: true on its own, regardless of ssl->options. + * dtls, on a plain TLS object. */ + ExpectIntEQ(wolfSSL_SetTlsHmacInner(ssl, inner, 10, dtls12_cid, 0), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* content != dtls12_cid, ssl->options.dtls == 0: both operands of the + * inner OR are false without evaluating DtlsGetCidTxSize() at all. */ + ExpectIntEQ(wolfSSL_SetTlsHmacInner(ssl, inner, 10, application_data, 0), + 0); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + +#if defined(WOLFSSL_DTLS) && defined(WOLFSSL_DTLS_CID) && \ + defined(WOLFSSL_DTLS13) + /* content != dtls12_cid, ssl->options.dtls == 1, no CID negotiated: + * independence for the second operand while the third is false. */ + ctx = NULL; + ssl = NULL; + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_SetTlsHmacInner(ssl, inner, 10, application_data, 0), + 0); + + /* content != dtls12_cid, ssl->options.dtls == 1, a tx CID configured: + * independence for the third operand. */ + ExpectIntEQ(wolfSSL_dtls_cid_use(ssl), WOLFSSL_SUCCESS); + { + byte cid[4] = { 1, 2, 3, 4 }; + ExpectIntEQ(wolfSSL_dtls_cid_set(ssl, cid, sizeof(cid)), + WOLFSSL_SUCCESS); + } + if (DtlsGetCidTxSize(ssl) > 0) { + ExpectIntEQ(wolfSSL_SetTlsHmacInner(ssl, inner, 10, application_data, + 0), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + } + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + + return EXPECT_RESULT(); +} +#endif + +/* ---------------------------------------------------------------------- */ +/* BuildTlsHandshakeHash() - the leading 4-operand NULL/size guard has no + * public wrapper, so it is unit-tested directly (WOLFSSL_TEST_STATIC_BUILD). + * The mac_algorithm <= sha256_mac || mac_algorithm == blake2b_mac branch + * (choosing the SHA-256 handshake hash) only needs its second operand: + * mac_algorithm <= sha256_mac already has an independence pair elsewhere in + * the suite; blake2b_mac (8) is above sha256_mac (4) in enum wc_MACAlgorithm + * ordering regardless of whether HAVE_BLAKE2B is built, so setting + * specs.mac_algorithm to it exercises the SHA-256 path through the second + * operand without requiring BLAKE2b support. */ +#if defined(WOLFSSL_TEST_STATIC_BUILD) && !defined(WOLFSSL_NO_TLS12) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_SHA256) +int test_BuildTlsHandshakeHash_bounds(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte hash[WC_MAX_DIGEST_SIZE]; + word32 hashLen; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(InitHandshakeHashes(ssl), 0); + + /* ssl == NULL. */ + hashLen = sizeof(hash); + ExpectIntEQ(BuildTlsHandshakeHash(NULL, hash, &hashLen), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* hash == NULL. */ + hashLen = sizeof(hash); + ExpectIntEQ(BuildTlsHandshakeHash(ssl, NULL, &hashLen), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* hashLen == NULL. */ + ExpectIntEQ(BuildTlsHandshakeHash(ssl, hash, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* *hashLen < HSHASH_SZ. */ + hashLen = 1; + ExpectIntEQ(BuildTlsHandshakeHash(ssl, hash, &hashLen), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* All valid, mac_algorithm <= sha256_mac (sha256_mac itself): the + * already-covered independence pair for the first operand. */ + ssl->specs.mac_algorithm = sha256_mac; + hashLen = sizeof(hash); + ExpectIntEQ(BuildTlsHandshakeHash(ssl, hash, &hashLen), 0); + ExpectIntEQ(hashLen, WC_SHA256_DIGEST_SIZE); + + /* mac_algorithm == blake2b_mac: first operand false (8 > sha256_mac's + * 4), second operand true - still routes to the SHA-256 hash object + * (populated regardless of the negotiated MAC, for exactly this kind of + * lookup), not to an unbuilt BLAKE2b one. */ + ssl->specs.mac_algorithm = blake2b_mac; + hashLen = sizeof(hash); + ExpectIntEQ(BuildTlsHandshakeHash(ssl, hash, &hashLen), 0); + ExpectIntEQ(hashLen, WC_SHA256_DIGEST_SIZE); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + return EXPECT_RESULT(); +} +#endif + +/* ---------------------------------------------------------------------- */ +/* TLS_hmac() / Hmac_UpdateFinal_CT() - driven directly through ssl->hmac(), + * which InitSSL() points at TLS_hmac() by default whenever TLS 1.2 (or + * older) CBC-MAC support is built, before any handshake runs (see the + * existing test_tls_hmac_size_overflow() in test_hmac.c for the same + * pattern). No live connection is needed: the size-overflow guard and the + * verify/padSz dispatch are pure argument checks over ssl->specs and the + * caller-supplied lengths. */ +#if !defined(NO_HMAC) && !defined(WOLFSSL_AEAD_ONLY) && !defined(NO_TLS) && \ + defined(NO_OLD_TLS) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_SHA256) +int test_TLS_hmac_bounds(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte digest[WC_MAX_DIGEST_SIZE]; + byte in[256]; + + XMEMSET(in, 0xAA, sizeof(in)); + XMEMSET(digest, 0, sizeof(digest)); + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectNotNull(ssl->hmac); + + if (EXPECT_SUCCESS()) { + ssl->specs.mac_algorithm = sha256_mac; + ssl->specs.hash_size = WC_SHA256_DIGEST_SIZE; + + /* "verify && padSz >= 0" (first occurrence, guards the size-overflow + * pre-check): verify == 0 - first operand false, independence + * against the verify == 1 calls below. Neither the pre-check nor + * the second occurrence at line ~1465 are reached; the plain + * wc_HmacUpdate/Final path below runs instead. */ + ExpectIntEQ(ssl->hmac(ssl, digest, in, 50, 10, application_data, 0, + PEER_ORDER), 0); + + /* verify == 1, padSz == -1: second operand false - independence for + * the second operand of the same guard, and for the second + * occurrence at line ~1465 (both are skipped, same as above). */ + ExpectIntEQ(ssl->hmac(ssl, digest, in, 50, -1, application_data, 1, + PEER_ORDER), 0); + + /* verify == 1, padSz == 10: both operands true. Reaches the + * overflow pre-check with ordinary values (no overflow) and then + * the constant-time verify path (Hmac_UpdateFinal_CT(), and within + * it Hmac_OuterHash() - both only reachable from here). */ + ExpectIntEQ(ssl->hmac(ssl, digest, in, 50, 10, application_data, 1, + PEER_ORDER), 0); + + /* Overflow in the first addition (sz + hashSz): sz alone is already + * within 32 of the word32 max. */ + ExpectIntEQ(ssl->hmac(ssl, digest, in, + (word32)(WOLFSSL_MAX_32BIT - 10), 0, application_data, 1, + PEER_ORDER), WC_NO_ERR_TRACE(BUFFER_E)); + + /* First addition safe (sz + hashSz == WOLFSSL_MAX_32BIT - 5), but + * adding padSz overflows - independence for the second addition, + * with the first false. */ + ExpectIntEQ(ssl->hmac(ssl, digest, in, + (word32)(WOLFSSL_MAX_32BIT - 32 - 5), 10, + application_data, 1, PEER_ORDER), + WC_NO_ERR_TRACE(BUFFER_E)); + + /* First two additions land exactly on WOLFSSL_MAX_32BIT (sz + + * hashSz(32) + padSz(0)), so neither overflows, but the final "+ 1" + * does - independence for the third addition, with the first two + * false. */ + ExpectIntEQ(ssl->hmac(ssl, digest, in, + (word32)(WOLFSSL_MAX_32BIT - 32), 0, application_data, 1, + PEER_ORDER), WC_NO_ERR_TRACE(BUFFER_E)); + +#ifdef WOLFSSL_TEST_STATIC_BUILD + /* Hmac_UpdateFinal_CT()'s own "macLen <= 0" guard: force hash_size + * to 0 for this call only. wc_HmacSetKey() accepts a zero-length + * key (RFC 2104 permits an empty key), so ret stays 0 and this + * still reaches the constant-time path with macLen == 0. + * + * The guard's other half, "macLen > sizeof(hmac->innerHash)" + * (innerHash is WC_MAX_DIGEST_SIZE bytes), is excluded: macLen is + * always ssl->specs.hash_size or TRUNCATED_HMAC_SZ, and hash_size + * is also the key length wc_HmacSetKey() reads out of + * ssl->keys.*_write_MAC_secret, a fixed WC_MAX_DIGEST_SIZE-byte + * array - so any hash_size big enough to take this branch would + * already have made wc_HmacSetKey() (immediately above, in the same + * call) read out of bounds. Every real MAC algorithm's digest size + * fits within WC_MAX_DIGEST_SIZE by construction, so this half is + * contradicted by that shared field's own bound (family 4). */ + ssl->specs.hash_size = 0; + ExpectIntEQ(ssl->hmac(ssl, digest, in, 50, 0, application_data, 1, + PEER_ORDER), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ssl->specs.hash_size = WC_SHA256_DIGEST_SIZE; +#endif + } + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + return EXPECT_RESULT(); +} +#endif + +/* ---------------------------------------------------------------------- */ +/* TLSX_ALPN_GetSize() (reached through the WOLFSSL_TEST_VIS + * TLSX_GetRequestSize()) - "alpnSz == 0 && extension->data != NULL" is the + * 16-bit overflow guard on the accumulated ALPN protocol list size. The only + * producer of a TLSX_APPLICATION_LAYER_PROTOCOL extension is TLSX_UseALPN(), + * which always supplies a non-NULL ALPN entry before pushing - so + * extension->data is never NULL while this extension exists, and the second + * operand is true on every real execution that reaches it (which only + * happens when the first operand is already true, i.e. on overflow). + * Excluded (family 4: contradicted by TLSX_UseALPN()'s own postcondition); + * only the first operand is driven here. WOLFSSL_MAX_ALPN_NUMBER (257) + * entries of the maximum WOLFSSL_MAX_ALPN_PROTO_NAME_LEN (255) push the + * running total past 0xFFFF (257 * 256 + 2 == 65794). */ +#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(HAVE_ALPN) && \ + !defined(NO_WOLFSSL_CLIENT) +int test_TLSX_ALPN_GetSize_overflow(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + char name[WOLFSSL_MAX_ALPN_PROTO_NAME_LEN]; + word32 len; + int i; + + XMEMSET(name, 'a', sizeof(name)); + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + + /* alpnSz != 0 (no overflow): first operand false, short-circuits past + * the second - independence for the first operand, paired against the + * overflow case below. */ + len = 0; + ExpectIntEQ(wolfSSL_UseALPN(ssl, (char*)"http/1.1", 8, + WOLFSSL_ALPN_FAILED_ON_MISMATCH), WOLFSSL_SUCCESS); + ExpectIntEQ(TLSX_GetRequestSize(ssl, client_hello, &len), 0); + + wolfSSL_free(ssl); + ssl = NULL; + ExpectNotNull(ssl = wolfSSL_new(ctx)); + + for (i = 0; EXPECT_SUCCESS() && i < WOLFSSL_MAX_ALPN_NUMBER; i++) { + ExpectIntEQ(TLSX_UseALPN(&ssl->extensions, name, sizeof(name), + WOLFSSL_ALPN_FAILED_ON_MISMATCH, ssl->heap), + WOLFSSL_SUCCESS); + } + len = 0; + ExpectIntEQ(TLSX_GetRequestSize(ssl, client_hello, &len), + WC_NO_ERR_TRACE(LENGTH_ERROR)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + return EXPECT_RESULT(); +} +#endif + +/* ---------------------------------------------------------------------- */ +/* TLSX_Cookie_GetSize() / TLSX_Cookie_Write() - "msgType == client_hello || + * msgType == hello_retry_request", reached through the WOLFSSL_TEST_VIS + * TLSX_GetRequestSize()/TLSX_WriteRequest() by passing the message type + * directly: the dispatch inside TLSX_GetSize()/TLSX_Write() only looks at + * the msgType argument (and, once the extension already exists, + * extension->resp for the "is this message type getting a response-only + * extension" skip), not at which top-level wrapper made the call. */ +#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(WOLFSSL_TLS13) && \ + defined(WOLFSSL_SEND_HRR_COOKIE) && !defined(NO_WOLFSSL_CLIENT) +int test_TLSX_Cookie_bounds(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + TLSX* ext; + byte cookieData[4] = { 1, 2, 3, 4 }; + byte out[64]; + word32 offset; + word32 pLen; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + + ExpectIntEQ(TLSX_Cookie_Use(ssl, cookieData, sizeof(cookieData), NULL, 0, + 0, &ssl->extensions), 0); + + /* msgType == client_hello: first operand true. isRequest is true for + * client_hello, so the extension is included regardless of resp. Drives + * both TLSX_Cookie_GetSize() (via TLSX_GetRequestSize()) and + * TLSX_Cookie_Write() (via TLSX_WriteRequest()) - the two functions have + * the same guard at the same relative position, reached the same way. */ + pLen = 0; + ExpectIntEQ(TLSX_GetRequestSize(ssl, client_hello, &pLen), 0); + ExpectTrue(pLen > OPAQUE16_LEN); + offset = 0; + ExpectIntEQ(TLSX_WriteRequest(ssl, out, client_hello, &offset), 0); + ExpectTrue(offset > OPAQUE16_LEN); + + /* msgType == hello_retry_request: second operand true, first false - + * independence for the second operand. hello_retry_request is not + * "isRequest" (only client_hello/certificate_request are), so resp must + * be set for TLSX_GetSize()/TLSX_Write() to not skip the extension. */ + ext = TLSX_Find(ssl->extensions, TLSX_COOKIE); + ExpectNotNull(ext); + if (ext != NULL) + ext->resp = 1; + pLen = 0; + ExpectIntEQ(TLSX_GetRequestSize(ssl, hello_retry_request, &pLen), 0); + ExpectTrue(pLen > OPAQUE16_LEN); + offset = 0; + ExpectIntEQ(TLSX_WriteRequest(ssl, out, hello_retry_request, &offset), 0); + ExpectTrue(offset > OPAQUE16_LEN); + + /* msgType == server_hello: both operands false - independence for the + * second operand's false side, paired against the hello_retry_request + * case above (both with resp == 1, so the extension is not skipped + * before TLSX_Cookie_GetSize()/_Write() run). */ + pLen = 0; + ExpectIntEQ(TLSX_GetRequestSize(ssl, server_hello, &pLen), + WC_NO_ERR_TRACE(SANITY_MSG_E)); + offset = 0; + ExpectIntEQ(TLSX_WriteRequest(ssl, out, server_hello, &offset), + WC_NO_ERR_TRACE(SANITY_MSG_E)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); return EXPECT_RESULT(); } +#endif diff --git a/tests/api/test_tls_bounds.h b/tests/api/test_tls_bounds.h index 247186c802..f9b3927adc 100644 --- a/tests/api/test_tls_bounds.h +++ b/tests/api/test_tls_bounds.h @@ -22,9 +22,38 @@ #ifndef TESTS_API_TEST_TLS_BOUNDS_H #define TESTS_API_TEST_TLS_BOUNDS_H -int test_tls_bounds_placeholder(void); +int test_TLSX_UseSNI_bounds(void); +int test_TLSX_UseALPN_bounds(void); +int test_TLSX_UseMaxFragment_bounds(void); +int test_TLSX_UseCertificateStatusRequest_bounds(void); +int test_TLSX_UseCertificateStatusRequestV2_bounds(void); +int test_TLSX_SupportExtensions_bounds(void); +int test_TLSX_CSR2_InitRequests_bounds(void); +int test_TLSX_CSR2_ForceRequest_bounds(void); +int test_TLSX_CSR_GetRequest_ex_bounds(void); +int test_wolfSSL_make_eap_keys_bounds(void); +int test_wolfSSL_SetTlsHmacInner_bounds(void); +int test_BuildTlsHandshakeHash_bounds(void); +int test_TLS_hmac_bounds(void); +int test_TLSX_ALPN_GetSize_overflow(void); +int test_TLSX_Cookie_bounds(void); #define TEST_TLS_BOUNDS_DECLS \ - TEST_DECL_GROUP("tls", test_tls_bounds_placeholder) + TEST_DECL_GROUP("tls", test_TLSX_UseSNI_bounds), \ + TEST_DECL_GROUP("tls", test_TLSX_UseALPN_bounds), \ + TEST_DECL_GROUP("tls", test_TLSX_UseMaxFragment_bounds), \ + TEST_DECL_GROUP("tls", test_TLSX_UseCertificateStatusRequest_bounds),\ + TEST_DECL_GROUP("tls", \ + test_TLSX_UseCertificateStatusRequestV2_bounds), \ + TEST_DECL_GROUP("tls", test_TLSX_SupportExtensions_bounds), \ + TEST_DECL_GROUP("tls", test_TLSX_CSR2_InitRequests_bounds), \ + TEST_DECL_GROUP("tls", test_TLSX_CSR2_ForceRequest_bounds), \ + TEST_DECL_GROUP("tls", test_TLSX_CSR_GetRequest_ex_bounds), \ + TEST_DECL_GROUP("tls", test_wolfSSL_make_eap_keys_bounds), \ + TEST_DECL_GROUP("tls", test_wolfSSL_SetTlsHmacInner_bounds), \ + TEST_DECL_GROUP("tls", test_BuildTlsHandshakeHash_bounds), \ + TEST_DECL_GROUP("tls", test_TLS_hmac_bounds), \ + TEST_DECL_GROUP("tls", test_TLSX_ALPN_GetSize_overflow), \ + TEST_DECL_GROUP("tls", test_TLSX_Cookie_bounds) #endif /* TESTS_API_TEST_TLS_BOUNDS_H */ From 1dbb5d7de4f3fc34fef340be76549451ad8b6986 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 21:28:24 +0200 Subject: [PATCH 41/57] tests: cover the tls extension per-message-type gates in TLSX_Parse One case per extension, each parsing the same crafted record in every message type RFC 8446 4.2 permits for it and in one it does not. Also puts the existing extension tests in test_tls_ext.c into the tls group; they were registered without one and ran only in a full sweep. --- tests/api/test_tls_ext.h | 21 + tests/api/test_tls_msgtype.c | 1157 +++++++++++++++++++++++++++++++++- tests/api/test_tls_msgtype.h | 70 +- 3 files changed, 1241 insertions(+), 7 deletions(-) diff --git a/tests/api/test_tls_ext.h b/tests/api/test_tls_ext.h index 40041c0533..c5ffbe16b3 100644 --- a/tests/api/test_tls_ext.h +++ b/tests/api/test_tls_ext.h @@ -56,4 +56,25 @@ int test_wolfSSL_custom_ext_resumption_fallback(void); int test_wolfSSL_custom_ext_ticket_fallback(void); int test_wolfSSL_custom_ext_add_null(void); +#define TEST_TLS_EXT_DECLS \ + TEST_DECL_GROUP("tls", test_tls_ems_downgrade), \ + TEST_DECL_GROUP("tls", test_tls_ems_resumption_downgrade), \ + TEST_DECL_GROUP("tls", test_tls_ems_resumption_server_downgrade), \ + TEST_DECL_GROUP("tls", test_scr_verify_data_mismatch), \ + TEST_DECL_GROUP("tls", test_scr_no_renegotiation_option), \ + TEST_DECL_GROUP("tls", test_helloRequest_no_renegotiation_option), \ + TEST_DECL_GROUP("tls", test_helloRequest_advertise_only_refused), \ + TEST_DECL_GROUP("tls", test_tls13_hrr_cipher_suite_mismatch), \ + TEST_DECL_GROUP("tls", test_tls13_ticket_age_out_of_window), \ + TEST_DECL_GROUP("tls", test_certificate_authorities_certificate_request), \ + TEST_DECL_GROUP("tls", test_certificate_authorities_client_hello), \ + TEST_DECL_GROUP("tls", test_TLSX_TCA_Find), \ + TEST_DECL_GROUP("tls", test_TLSX_SNI_GetSize_overflow), \ + TEST_DECL_GROUP("tls", test_TLSX_ECH_msg_type_validation), \ + TEST_DECL_GROUP("tls", test_TLSX_CSR2_tls13_msg_type_validation), \ + TEST_DECL_GROUP("tls", test_TLSX_SRTP_msg_type_validation), \ + TEST_DECL_GROUP("tls", test_TLSX_ALPN_server_response_count), \ + TEST_DECL_GROUP("tls", test_TLSX_SupportedCurve_empty_or_unsupported), \ + TEST_DECL_GROUP("tls", test_TLSX_PointFormat_uncompressed_required) + #endif /* TESTS_API_TEST_TLS_EXT_H */ diff --git a/tests/api/test_tls_msgtype.c b/tests/api/test_tls_msgtype.c index 1d661b0719..3274b07c55 100644 --- a/tests/api/test_tls_msgtype.c +++ b/tests/api/test_tls_msgtype.c @@ -1,4 +1,4 @@ -/* test_tls_msgtype.h +/* test_tls_msgtype.c * * Copyright (C) 2006-2026 wolfSSL Inc. * @@ -27,10 +27,1159 @@ #include #include -/* Placeholder so the translation unit is never empty and the group always has - * at least one member. Replace when the first real case lands. */ -int test_tls_msgtype_placeholder(void) +/* This file drives TLSX_Parse() (src/tls.c) directly with hand-built + * extension records to exercise the per-extension "not permitted in this + * message" gates from RFC 8446 Section 4.2, plus the argument validation and + * ClientHello-consistency checks at the top and bottom of the same function. + * TLSX_Parse() is WOLFSSL_TEST_VIS, so it is callable here without a + * WOLFSSL_TEST_STATIC_BUILD guard. */ + +#if !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) + +/* Build one extension record (2-byte type, 2-byte length, N zero data + * bytes) into buf and return its total length. Content is all-zero: gates + * are checked before an extension's data is interpreted, so the exact bytes + * only need to satisfy the minimum-size gate, not be semantically valid. */ +static word16 build_ext(byte* buf, word16 type, word16 dataSz) +{ + buf[0] = (byte)(type >> 8); + buf[1] = (byte)type; + buf[2] = (byte)(dataSz >> 8); + buf[3] = (byte)dataSz; + if (dataSz > 0) + XMEMSET(buf + 4, 0, dataSz); + return (word16)(4 + dataSz); +} + +#endif /* !NO_WOLFSSL_CLIENT && !NO_TLS && HAVE_TLS_EXTENSIONS */ + +/* ---- TLSX_Parse() argument validation ------------------------------- */ +/* if (!ssl || !input || (isRequest && !suites)) return BAD_FUNC_ARG; */ +int test_tls_msgtype_arg_guard(void) +{ + EXPECT_DECLS; +#if !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte dummy[4] = { 0 }; + Suites suites; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + + /* !ssl */ + ExpectIntEQ(TLSX_Parse(NULL, dummy, 0, finished, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* !input, ssl valid */ + ExpectIntEQ(TLSX_Parse(ssl, NULL, 4, finished, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* baseline: ssl and input valid, isRequest false (finished), no + * extensions - accepted. */ + ExpectIntEQ(TLSX_Parse(ssl, dummy, 0, finished, NULL), 0); + /* isRequest true (client_hello), suites missing */ + ExpectIntEQ(TLSX_Parse(ssl, dummy, 0, client_hello, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* isRequest true (client_hello), suites present - accepted. */ + ExpectIntEQ(TLSX_Parse(ssl, dummy, 0, client_hello, &suites), 0); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* ---- duplicate Pre-Shared Key extension in one ClientHello ----------- */ +/* if (msgType == client_hello && pskDone) return PSK_KEY_ERROR; */ +#if defined(WOLFSSL_TLS13) && (defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)) +/* A minimal, structurally valid ClientHello pre_shared_key body: one + * zero-length identity (6 bytes: 2-byte identities-length of a single + * 6-byte entry, 2-byte identity length of 0, 4-byte age of 0) and one + * binder sized at the SHA-256 digest length (33 bytes: 1-byte binder length + * of 32, 32 zero binder bytes). See TLSX_PreSharedKey_Parse_ClientHello() / + * MIN_PSK_ID_LEN / MIN_PSK_BINDERS_LEN. */ +static const byte psk_ch_body[] = { + 0x00, 0x06, /* identities len */ + 0x00, 0x00, /* identity len = 0 */ + 0x00, 0x00, 0x00, 0x00, /* ticket age = 0 */ + 0x00, 0x21, /* binders len = 33 */ + 0x20, /* binder len = 32 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 /* binder data */ +}; +#endif + +int test_tls_msgtype_psk_duplicate(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && (defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[64]; + word16 len, pskExtLen; + Suites suites; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + + /* op0 false: not a ClientHello, pskDone is never even examined. */ + len = build_ext(buf, 0xfff0, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, finished, NULL), 0); + + /* op0 true, op1 false: single ClientHello extension, pskDone stays 0 + * through the only loop iteration. */ + len = build_ext(buf, 0xfff0, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, client_hello, &suites), 0); + + /* op0 true, op1 true: a valid pre_shared_key extension sets pskDone, + * then a second extension record in the same ClientHello re-enters the + * loop with pskDone already set. */ + pskExtLen = build_ext(buf, TLSX_PRE_SHARED_KEY, (word16)sizeof(psk_ch_body)); + XMEMCPY(buf + 4, psk_ch_body, sizeof(psk_ch_body)); + buf[pskExtLen] = 0x00; /* one trailing byte so offset < length again */ + len = (word16)(pskExtLen + 1); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(PSK_KEY_ERROR)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* ---- Certificate-message extension must have been offered ------------ */ +/* if (msgType == certificate && IsAtLeastTLSv1_3(ssl->version) && + * TLSX_Find(ssl->extensions, type) == NULL && + * (ssl->ctx == NULL || TLSX_Find(ssl->ctx->extensions, type) == NULL)) */ +int test_tls_msgtype_certificate_ext_offered(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_MAX_FRAGMENT) && !defined(WOLFSSL_NO_TLS12) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[8]; + word16 len; + WOLFSSL_CTX* savedCtx; + + /* op0 false: not a Certificate message. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + len = build_ext(buf, TLSX_MAX_FRAGMENT_LENGTH, 0); + ExpectIntNE(TLSX_Parse(ssl, buf, len, finished, NULL), + WC_NO_ERR_TRACE(UNSUPPORTED_EXTENSION)); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* Baseline reject (op0..op4 all "true", via the ctx->extensions arm): + * Certificate message, TLS 1.3, type not offered anywhere. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + len = build_ext(buf, TLSX_MAX_FRAGMENT_LENGTH, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, certificate, NULL), + WC_NO_ERR_TRACE(UNSUPPORTED_EXTENSION)); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* op1 false: Certificate message, but not TLS 1.3. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + len = build_ext(buf, TLSX_MAX_FRAGMENT_LENGTH, 0); + ExpectIntNE(TLSX_Parse(ssl, buf, len, certificate, NULL), + WC_NO_ERR_TRACE(UNSUPPORTED_EXTENSION)); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* op2 false: the type was offered at the ssl level. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_UseMaxFragment(ssl, WOLFSSL_MFL_2_9), WOLFSSL_SUCCESS); + len = build_ext(buf, TLSX_MAX_FRAGMENT_LENGTH, 0); + ExpectIntNE(TLSX_Parse(ssl, buf, len, certificate, NULL), + WC_NO_ERR_TRACE(UNSUPPORTED_EXTENSION)); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* op3 false, op4 false: not offered at the ssl level, but the ctx has + * it - so the type was still offered (CTX-level extensions apply to + * every ssl created from this ctx). */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectIntEQ(wolfSSL_CTX_UseMaxFragment(ctx, WOLFSSL_MFL_2_9), WOLFSSL_SUCCESS); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + len = build_ext(buf, TLSX_MAX_FRAGMENT_LENGTH, 0); + ExpectIntNE(TLSX_Parse(ssl, buf, len, certificate, NULL), + WC_NO_ERR_TRACE(UNSUPPORTED_EXTENSION)); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* op3 true: ctx forcibly NULL (restored before free). */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + len = build_ext(buf, TLSX_MAX_FRAGMENT_LENGTH, 0); + savedCtx = ssl->ctx; + ssl->ctx = NULL; + ExpectIntEQ(TLSX_Parse(ssl, buf, len, certificate, NULL), + WC_NO_ERR_TRACE(UNSUPPORTED_EXTENSION)); + ssl->ctx = savedCtx; + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* ---- RFC 8446 4.2 "not permitted in this message" per-extension gates */ + +int test_tls_msgtype_sni_tls13(void) +{ + EXPECT_DECLS; +#if defined(HAVE_SNI) && defined(WOLFSSL_TLS13) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[16]; + word16 len; + Suites suites; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + + len = build_ext(buf, TLSX_SERVER_NAME, WOLFSSL_SNI_MIN_SIZE_CLIENT); + ExpectIntNE(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_SERVER_NAME, 0); + ExpectIntNE(TLSX_Parse(ssl, buf, len, encrypted_extensions, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_SERVER_NAME, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, finished, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +int test_tls_msgtype_sni_tls12(void) +{ + EXPECT_DECLS; +#if defined(HAVE_SNI) && !defined(WOLFSSL_NO_TLS12) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[16]; + word16 len; + Suites suites; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + + len = build_ext(buf, TLSX_SERVER_NAME, WOLFSSL_SNI_MIN_SIZE_CLIENT); + ExpectIntNE(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_SERVER_NAME, WOLFSSL_SNI_MIN_SIZE_SERVER); + ExpectIntNE(TLSX_Parse(ssl, buf, len, server_hello, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_SERVER_NAME, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, finished, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +int test_tls_msgtype_tca(void) +{ + EXPECT_DECLS; +#if !defined(WOLFSSL_NO_TLS12) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[16]; + word16 len; + Suites suites; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + + len = build_ext(buf, TLSX_TRUSTED_CA_KEYS, WOLFSSL_TCA_MIN_SIZE_CLIENT); + ExpectIntNE(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_TRUSTED_CA_KEYS, WOLFSSL_TCA_MIN_SIZE_SERVER); + ExpectIntNE(TLSX_Parse(ssl, buf, len, server_hello, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_TRUSTED_CA_KEYS, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, finished, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +int test_tls_msgtype_mfl_tls13(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[16]; + word16 len; + Suites suites; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + + len = build_ext(buf, TLSX_MAX_FRAGMENT_LENGTH, WOLFSSL_MFL_MIN_SIZE_CLIENT); + ExpectIntNE(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_MAX_FRAGMENT_LENGTH, 0); + ExpectIntNE(TLSX_Parse(ssl, buf, len, encrypted_extensions, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_MAX_FRAGMENT_LENGTH, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, finished, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +int test_tls_msgtype_mfl_tls12(void) +{ + EXPECT_DECLS; +#if !defined(WOLFSSL_NO_TLS12) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[16]; + word16 len; + Suites suites; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + + len = build_ext(buf, TLSX_MAX_FRAGMENT_LENGTH, WOLFSSL_MFL_MIN_SIZE_CLIENT); + ExpectIntNE(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_MAX_FRAGMENT_LENGTH, WOLFSSL_MFL_MIN_SIZE_SERVER); + ExpectIntNE(TLSX_Parse(ssl, buf, len, server_hello, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_MAX_FRAGMENT_LENGTH, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, finished, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +int test_tls_msgtype_supported_groups_tls13(void) +{ + EXPECT_DECLS; +#if defined(HAVE_SUPPORTED_CURVES) && defined(WOLFSSL_TLS13) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[16]; + word16 len; + Suites suites; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + + len = build_ext(buf, TLSX_SUPPORTED_GROUPS, WOLFSSL_EC_MIN_SIZE_CLIENT); + ExpectIntNE(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_SUPPORTED_GROUPS, 0); + ExpectIntNE(TLSX_Parse(ssl, buf, len, encrypted_extensions, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_SUPPORTED_GROUPS, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, finished, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +int test_tls_msgtype_point_formats(void) +{ + EXPECT_DECLS; +#if !defined(WOLFSSL_NO_TLS12) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[16]; + word16 len; + Suites suites; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + + len = build_ext(buf, TLSX_EC_POINT_FORMATS, WOLFSSL_PF_MIN_SIZE_CLIENT); + ExpectIntNE(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_EC_POINT_FORMATS, WOLFSSL_PF_MIN_SIZE_SERVER); + ExpectIntNE(TLSX_Parse(ssl, buf, len, server_hello, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_EC_POINT_FORMATS, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, finished, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +int test_tls_msgtype_csr_tls13(void) +{ + EXPECT_DECLS; +#if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && defined(WOLFSSL_TLS13) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[16]; + word16 len; + Suites suites; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + /* Offer status_request in our own ClientHello first, so the + * Certificate-message extension is recognized as one we asked for by + * the check earlier in TLSX_Parse() (RFC 8446 4.4.2). */ + ExpectIntEQ(wolfSSL_UseOCSPStapling(ssl, WOLFSSL_CSR_OCSP, 0), + WOLFSSL_SUCCESS); + + len = build_ext(buf, TLSX_STATUS_REQUEST, WOLFSSL_CSR_MIN_SIZE_CLIENT); + ExpectIntNE(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_STATUS_REQUEST, 0); + ExpectIntNE(TLSX_Parse(ssl, buf, len, certificate_request, &suites), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_STATUS_REQUEST, 0); + ExpectIntNE(TLSX_Parse(ssl, buf, len, certificate, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_STATUS_REQUEST, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, finished, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +int test_tls_msgtype_csr_tls12(void) +{ + EXPECT_DECLS; +#if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(WOLFSSL_NO_TLS12) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[16]; + word16 len; + Suites suites; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + + len = build_ext(buf, TLSX_STATUS_REQUEST, WOLFSSL_CSR_MIN_SIZE_CLIENT); + ExpectIntNE(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_STATUS_REQUEST, WOLFSSL_CSR_MIN_SIZE_SERVER); + ExpectIntNE(TLSX_Parse(ssl, buf, len, server_hello, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_STATUS_REQUEST, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, finished, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +int test_tls_msgtype_csr2_tls12(void) +{ + EXPECT_DECLS; +#if defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) && !defined(WOLFSSL_NO_TLS12) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[16]; + word16 len; + Suites suites; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + + len = build_ext(buf, TLSX_STATUS_REQUEST_V2, WOLFSSL_CSR2_MIN_SIZE_CLIENT); + ExpectIntNE(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_STATUS_REQUEST_V2, WOLFSSL_CSR2_MIN_SIZE_SERVER); + ExpectIntNE(TLSX_Parse(ssl, buf, len, server_hello, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_STATUS_REQUEST_V2, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, finished, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +int test_tls_msgtype_extms(void) +{ + EXPECT_DECLS; +#if defined(HAVE_EXTENDED_MASTER) && !defined(WOLFSSL_NO_TLS12) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[8]; + word16 len; + Suites suites; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + + len = build_ext(buf, HELLO_EXT_EXTMS, 0); + ExpectIntNE(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, HELLO_EXT_EXTMS, 0); + ExpectIntNE(TLSX_Parse(ssl, buf, len, server_hello, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, HELLO_EXT_EXTMS, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, finished, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +int test_tls_msgtype_renegotiation_info(void) +{ + EXPECT_DECLS; +#if defined(HAVE_SECURE_RENEGOTIATION) && !defined(WOLFSSL_NO_TLS12) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[8]; + word16 len; + Suites suites; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + + len = build_ext(buf, TLSX_RENEGOTIATION_INFO, WOLFSSL_SCR_MIN_SIZE_CLIENT); + ExpectIntNE(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_RENEGOTIATION_INFO, WOLFSSL_SCR_MIN_SIZE_SERVER); + ExpectIntNE(TLSX_Parse(ssl, buf, len, server_hello, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_RENEGOTIATION_INFO, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, finished, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +int test_tls_msgtype_session_ticket_tls12(void) +{ + EXPECT_DECLS; +#if defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_TLS12) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[8]; + word16 len; + Suites suites; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + + len = build_ext(buf, TLSX_SESSION_TICKET, 0); + ExpectIntNE(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_SESSION_TICKET, 0); + ExpectIntNE(TLSX_Parse(ssl, buf, len, server_hello, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_SESSION_TICKET, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, finished, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +int test_tls_msgtype_alpn_tls13(void) +{ + EXPECT_DECLS; +#if defined(HAVE_ALPN) && defined(WOLFSSL_TLS13) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[16]; + word16 len; + Suites suites; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + + len = build_ext(buf, TLSX_APPLICATION_LAYER_PROTOCOL, + WOLFSSL_ALPN_MIN_SIZE_CLIENT); + ExpectIntNE(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_APPLICATION_LAYER_PROTOCOL, 0); + ExpectIntNE(TLSX_Parse(ssl, buf, len, encrypted_extensions, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_APPLICATION_LAYER_PROTOCOL, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, finished, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +int test_tls_msgtype_alpn_tls12(void) +{ + EXPECT_DECLS; +#if defined(HAVE_ALPN) && !defined(WOLFSSL_NO_TLS12) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[16]; + word16 len; + Suites suites; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + + len = build_ext(buf, TLSX_APPLICATION_LAYER_PROTOCOL, + WOLFSSL_ALPN_MIN_SIZE_CLIENT); + ExpectIntNE(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_APPLICATION_LAYER_PROTOCOL, + WOLFSSL_ALPN_MIN_SIZE_SERVER); + ExpectIntNE(TLSX_Parse(ssl, buf, len, server_hello, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_APPLICATION_LAYER_PROTOCOL, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, finished, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +int test_tls_msgtype_sigalgs_tls13(void) +{ + EXPECT_DECLS; +#if !defined(NO_CERTS) && !defined(WOLFSSL_NO_SIGALG) && \ + defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[16]; + word16 len; + Suites suites; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + + len = build_ext(buf, TLSX_SIGNATURE_ALGORITHMS, WOLFSSL_SA_MIN_SIZE_CLIENT); + ExpectIntNE(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_SIGNATURE_ALGORITHMS, 0); + ExpectIntNE(TLSX_Parse(ssl, buf, len, certificate_request, &suites), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_SIGNATURE_ALGORITHMS, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, finished, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +int test_tls_msgtype_etm(void) +{ + EXPECT_DECLS; +#if defined(HAVE_ENCRYPT_THEN_MAC) && !defined(WOLFSSL_AEAD_ONLY) && \ + !defined(WOLFSSL_NO_TLS12) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[8]; + word16 len; + Suites suites; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + + len = build_ext(buf, TLSX_ENCRYPT_THEN_MAC, WOLFSSL_ETM_MIN_SIZE_CLIENT); + ExpectIntNE(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_ENCRYPT_THEN_MAC, WOLFSSL_ETM_MIN_SIZE_SERVER); + ExpectIntNE(TLSX_Parse(ssl, buf, len, server_hello, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_ENCRYPT_THEN_MAC, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, finished, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +int test_tls_msgtype_supported_versions(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[8]; + word16 len; + Suites suites; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + + len = build_ext(buf, TLSX_SUPPORTED_VERSIONS, 0); + ExpectIntNE(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_SUPPORTED_VERSIONS, 0); + ExpectIntNE(TLSX_Parse(ssl, buf, len, server_hello, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_SUPPORTED_VERSIONS, 0); + ExpectIntNE(TLSX_Parse(ssl, buf, len, hello_retry_request, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_SUPPORTED_VERSIONS, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, finished, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +int test_tls_msgtype_cookie(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[8]; + word16 len; + Suites suites; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + + len = build_ext(buf, TLSX_COOKIE, WOLFSSL_CKE_MIN_SIZE_CLIENT); + ExpectIntNE(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_COOKIE, WOLFSSL_CKE_MIN_SIZE_SERVER); + ExpectIntNE(TLSX_Parse(ssl, buf, len, hello_retry_request, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_COOKIE, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, finished, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +int test_tls_msgtype_psk(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && (defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[8]; + word16 len; + Suites suites; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + + len = build_ext(buf, TLSX_PRE_SHARED_KEY, WOLFSSL_PSK_MIN_SIZE_CLIENT); + ExpectIntNE(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_PRE_SHARED_KEY, WOLFSSL_PSK_MIN_SIZE_SERVER); + ExpectIntNE(TLSX_Parse(ssl, buf, len, server_hello, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_PRE_SHARED_KEY, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, finished, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +int test_tls_msgtype_cert_with_extern_psk(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_CERT_WITH_EXTERN_PSK) && \ + !defined(NO_PSK) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[8]; + word16 len; + Suites suites; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + /* TLSX_CertWithExternPsk_Parse() only records the ClientHello offer - + * and only answers a ServerHello with anything but EXT_NOT_ALLOWED - + * once the server has opted in. */ + ExpectIntEQ(wolfSSL_set_cert_with_extern_psk(ssl, 1), WOLFSSL_SUCCESS); + + len = build_ext(buf, TLSX_CERT_WITH_EXTERN_PSK, 0); + ExpectIntNE(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_CERT_WITH_EXTERN_PSK, 0); + ExpectIntNE(TLSX_Parse(ssl, buf, len, server_hello, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_CERT_WITH_EXTERN_PSK, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, finished, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +int test_tls_msgtype_early_data(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_EARLY_DATA) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[8]; + word16 len; + Suites suites; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + + len = build_ext(buf, TLSX_EARLY_DATA, 0); + ExpectIntNE(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_EARLY_DATA, 4); + ExpectIntNE(TLSX_Parse(ssl, buf, len, session_ticket, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_EARLY_DATA, 0); + ExpectIntNE(TLSX_Parse(ssl, buf, len, encrypted_extensions, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_EARLY_DATA, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, finished, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +int test_tls_msgtype_sigalgs_cert(void) +{ + EXPECT_DECLS; +#if !defined(NO_CERTS) && !defined(WOLFSSL_NO_SIGALG) && \ + defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[16]; + word16 len; + Suites suites; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + + len = build_ext(buf, TLSX_SIGNATURE_ALGORITHMS_CERT, WOLFSSL_SA_MIN_SIZE_CLIENT); + ExpectIntNE(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_SIGNATURE_ALGORITHMS_CERT, 0); + ExpectIntNE(TLSX_Parse(ssl, buf, len, certificate_request, &suites), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_SIGNATURE_ALGORITHMS_CERT, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, finished, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +int test_tls_msgtype_key_share(void) +{ + EXPECT_DECLS; +#if defined(HAVE_SUPPORTED_CURVES) && defined(WOLFSSL_TLS13) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[8]; + word16 len; + Suites suites; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + + len = build_ext(buf, TLSX_KEY_SHARE, WOLFSSL_KS_MIN_SIZE_CLIENT); + ExpectIntNE(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_KEY_SHARE, WOLFSSL_KS_MIN_SIZE_SERVER); + ExpectIntNE(TLSX_Parse(ssl, buf, len, server_hello, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_KEY_SHARE, WOLFSSL_KS_MIN_SIZE_SERVER); + ExpectIntNE(TLSX_Parse(ssl, buf, len, hello_retry_request, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_KEY_SHARE, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, finished, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +int test_tls_msgtype_client_cert_type_tls13(void) +{ + EXPECT_DECLS; +#if defined(HAVE_RPK) && defined(WOLFSSL_TLS13) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[8]; + word16 len; + Suites suites; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + + len = build_ext(buf, TLSX_CLIENT_CERTIFICATE_TYPE, WOLFSSL_CCT_MIN_SIZE_CLIENT); + ExpectIntNE(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_CLIENT_CERTIFICATE_TYPE, 0); + ExpectIntNE(TLSX_Parse(ssl, buf, len, encrypted_extensions, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_CLIENT_CERTIFICATE_TYPE, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, finished, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +int test_tls_msgtype_client_cert_type_tls12(void) +{ + EXPECT_DECLS; +#if defined(HAVE_RPK) && !defined(WOLFSSL_NO_TLS12) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[8]; + word16 len; + Suites suites; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + + len = build_ext(buf, TLSX_CLIENT_CERTIFICATE_TYPE, WOLFSSL_CCT_MIN_SIZE_CLIENT); + ExpectIntNE(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_CLIENT_CERTIFICATE_TYPE, WOLFSSL_CCT_MIN_SIZE_SERVER); + ExpectIntNE(TLSX_Parse(ssl, buf, len, server_hello, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_CLIENT_CERTIFICATE_TYPE, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, finished, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +int test_tls_msgtype_server_cert_type_tls13(void) +{ + EXPECT_DECLS; +#if defined(HAVE_RPK) && defined(WOLFSSL_TLS13) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[8]; + word16 len; + Suites suites; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + + len = build_ext(buf, TLSX_SERVER_CERTIFICATE_TYPE, WOLFSSL_SCT_MIN_SIZE_CLIENT); + ExpectIntNE(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_SERVER_CERTIFICATE_TYPE, 0); + ExpectIntNE(TLSX_Parse(ssl, buf, len, encrypted_extensions, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_SERVER_CERTIFICATE_TYPE, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, finished, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +int test_tls_msgtype_server_cert_type_tls12(void) +{ + EXPECT_DECLS; +#if defined(HAVE_RPK) && !defined(WOLFSSL_NO_TLS12) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[8]; + word16 len; + Suites suites; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + + len = build_ext(buf, TLSX_SERVER_CERTIFICATE_TYPE, WOLFSSL_SCT_MIN_SIZE_CLIENT); + ExpectIntNE(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_SERVER_CERTIFICATE_TYPE, WOLFSSL_SCT_MIN_SIZE_SERVER); + ExpectIntNE(TLSX_Parse(ssl, buf, len, server_hello, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_SERVER_CERTIFICATE_TYPE, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, finished, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +int test_tls_msgtype_connection_id(void) { EXPECT_DECLS; +#if defined(WOLFSSL_DTLS_CID) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[8]; + word16 len; + Suites suites; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + + len = build_ext(buf, TLSX_CONNECTION_ID, WOLFSSL_CID_MIN_SIZE_CLIENT); + ExpectIntNE(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_CONNECTION_ID, WOLFSSL_CID_MIN_SIZE_SERVER); + ExpectIntNE(TLSX_Parse(ssl, buf, len, server_hello, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_CONNECTION_ID, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, finished, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +int test_tls_msgtype_ech(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[8]; + word16 len; + Suites suites; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + + len = build_ext(buf, TLSX_ECH, WOLFSSL_ECH_MIN_SIZE_CLIENT); + ExpectIntNE(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_ECH, 0); + ExpectIntNE(TLSX_Parse(ssl, buf, len, encrypted_extensions, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_ECH, WOLFSSL_ECH_MIN_SIZE_SERVER); + ExpectIntNE(TLSX_Parse(ssl, buf, len, hello_retry_request, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + len = build_ext(buf, TLSX_ECH, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, finished, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif return EXPECT_RESULT(); } diff --git a/tests/api/test_tls_msgtype.h b/tests/api/test_tls_msgtype.h index 892a677c48..e07153ace6 100644 --- a/tests/api/test_tls_msgtype.h +++ b/tests/api/test_tls_msgtype.h @@ -22,9 +22,73 @@ #ifndef TESTS_API_TEST_TLS_MSGTYPE_H #define TESTS_API_TEST_TLS_MSGTYPE_H -int test_tls_msgtype_placeholder(void); +int test_tls_msgtype_arg_guard(void); +int test_tls_msgtype_psk_duplicate(void); +int test_tls_msgtype_certificate_ext_offered(void); +int test_tls_msgtype_sni_tls13(void); +int test_tls_msgtype_sni_tls12(void); +int test_tls_msgtype_tca(void); +int test_tls_msgtype_mfl_tls13(void); +int test_tls_msgtype_mfl_tls12(void); +int test_tls_msgtype_supported_groups_tls13(void); +int test_tls_msgtype_point_formats(void); +int test_tls_msgtype_csr_tls13(void); +int test_tls_msgtype_csr_tls12(void); +int test_tls_msgtype_csr2_tls12(void); +int test_tls_msgtype_extms(void); +int test_tls_msgtype_renegotiation_info(void); +int test_tls_msgtype_session_ticket_tls12(void); +int test_tls_msgtype_alpn_tls13(void); +int test_tls_msgtype_alpn_tls12(void); +int test_tls_msgtype_sigalgs_tls13(void); +int test_tls_msgtype_etm(void); +int test_tls_msgtype_supported_versions(void); +int test_tls_msgtype_cookie(void); +int test_tls_msgtype_psk(void); +int test_tls_msgtype_cert_with_extern_psk(void); +int test_tls_msgtype_early_data(void); +int test_tls_msgtype_sigalgs_cert(void); +int test_tls_msgtype_key_share(void); +int test_tls_msgtype_client_cert_type_tls13(void); +int test_tls_msgtype_client_cert_type_tls12(void); +int test_tls_msgtype_server_cert_type_tls13(void); +int test_tls_msgtype_server_cert_type_tls12(void); +int test_tls_msgtype_connection_id(void); +int test_tls_msgtype_ech(void); -#define TEST_TLS_MSGTYPE_DECLS \ - TEST_DECL_GROUP("tls", test_tls_msgtype_placeholder) +#define TEST_TLS_MSGTYPE_DECLS \ + TEST_DECL_GROUP("tls", test_tls_msgtype_arg_guard), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_psk_duplicate), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_certificate_ext_offered), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_sni_tls13), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_sni_tls12), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_tca), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_mfl_tls13), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_mfl_tls12), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_supported_groups_tls13), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_point_formats), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_csr_tls13), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_csr_tls12), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_csr2_tls12), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_extms), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_renegotiation_info), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_session_ticket_tls12), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_alpn_tls13), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_alpn_tls12), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_sigalgs_tls13), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_etm), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_supported_versions), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_cookie), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_psk), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_cert_with_extern_psk), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_early_data), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_sigalgs_cert), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_key_share), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_client_cert_type_tls13), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_client_cert_type_tls12), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_server_cert_type_tls13), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_server_cert_type_tls12), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_connection_id), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_ech) #endif /* TESTS_API_TEST_TLS_MSGTYPE_H */ From eaac75d9e37cb3b5e9c9ebdcb8794249adb12e56 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 21:56:56 +0200 Subject: [PATCH 42/57] tests: add TLSX extension parser tests for tls.c Covers ALPN, trusted CA keys, client/server certificate type, cookie, encrypt-then-mac, max fragment length, truncated HMAC, session ticket, secure renegotiation, supported versions, signature algorithms, certificate status request (v1/v2), EC point formats/supported curves, SNI, and the cipher-suite/curve compatibility check, driven directly through TLSX_Parse() and TLSX_ParseVersion() with hand-built extension bodies. --- tests/api/test_tls_parse.c | 1632 +++++++++++++++++++++++++++++++++++- tests/api/test_tls_parse.h | 34 +- 2 files changed, 1659 insertions(+), 7 deletions(-) diff --git a/tests/api/test_tls_parse.c b/tests/api/test_tls_parse.c index ff0fcb1ebe..26ac2a6b15 100644 --- a/tests/api/test_tls_parse.c +++ b/tests/api/test_tls_parse.c @@ -1,4 +1,4 @@ -/* test_tls_parse.h +/* test_tls_parse.c * * Copyright (C) 2006-2026 wolfSSL Inc. * @@ -27,10 +27,1634 @@ #include #include -/* Placeholder so the translation unit is never empty and the group always has - * at least one member. Replace when the first real case lands. */ -int test_tls_parse_placeholder(void) +/* Helper to build a server-side WOLFSSL_CTX with a certificate/key loaded, + * as required for wolfSSL_new() to succeed on a server context. + */ +static WOLFSSL_CTX* test_tls_parse_server_ctx(WOLFSSL_METHOD* method) +{ + WOLFSSL_CTX* ctx = NULL; + + if (method == NULL) + return NULL; + + ctx = wolfSSL_CTX_new(method); + if (ctx == NULL) + return NULL; + +#if !defined(NO_CERTS) && !defined(NO_FILESYSTEM) && !defined(NO_RSA) + if (wolfSSL_CTX_use_certificate_file(ctx, svrCertFile, CERT_FILETYPE) + != WOLFSSL_SUCCESS || + wolfSSL_CTX_use_PrivateKey_file(ctx, svrKeyFile, CERT_FILETYPE) + != WOLFSSL_SUCCESS) { + wolfSSL_CTX_free(ctx); + return NULL; + } +#else + wolfSSL_CTX_free(ctx); + return NULL; +#endif + + return ctx; +} + +/* TLSX_Parse() walks a concatenated list of type(2)/length(2)/body + * extensions, exactly as they appear on the wire -- not a single + * extension's body on its own. This wraps one extension's body with that + * header into 'out', which must be at least bodyLen + 4 bytes, and returns + * the total length. + */ +static word16 test_tls_parse_build_ext(byte* out, word16 outCap, + word16 type, const byte* body, word16 bodyLen) +{ + /* A body that doesn't fit the caller's buffer is a fixture bug: fail + * loudly and locally rather than silently overrunning the stack. */ + if ((word32)bodyLen + 4 > outCap) { + fprintf(stderr, "test_tls_parse_build_ext: body of %u bytes does " + "not fit a %u byte buffer\n", (unsigned)bodyLen, + (unsigned)outCap); + abort(); + } + out[0] = (byte)(type >> 8); + out[1] = (byte)(type & 0xFF); + out[2] = (byte)(bodyLen >> 8); + out[3] = (byte)(bodyLen & 0xFF); + if (bodyLen > 0 && body != NULL) + XMEMCPY(out + 4, body, bodyLen); + return (word16)(4 + bodyLen); +} + +/* A small counting allocator used to force a single, targeted malloc + * failure. Installed narrowly around the call under test and restored + * immediately after, so it never affects unrelated allocations. + */ +#if defined(WOLFSSL_TEST_STATIC_BUILD) && !defined(NO_TLS) +static int tls_parse_fail_after = -1; +static int tls_parse_alloc_seen = 0; + +static void* tls_parse_fail_malloc(size_t size) +{ + if (tls_parse_fail_after >= 0) { + if (tls_parse_alloc_seen == tls_parse_fail_after) { + tls_parse_alloc_seen++; + return NULL; + } + tls_parse_alloc_seen++; + } + return malloc(size); +} + +static void tls_parse_fail_free(void* ptr) +{ + free(ptr); +} + +static void* tls_parse_fail_realloc(void* ptr, size_t size) +{ + return realloc(ptr, size); +} +#endif /* WOLFSSL_TEST_STATIC_BUILD && !NO_TLS */ + +/* ---- ALPN --------------------------------------------------------------- */ +/* RFC 7301: covers the TLSX_APPLICATION_LAYER_PROTOCOL parse helpers that + * TLSX_Parse() reaches for both the client_hello (isRequest) and + * server_hello (response) directions. + */ +int test_TLSX_ALPN_parse(void) { EXPECT_DECLS; +#if defined(HAVE_ALPN) && defined(HAVE_TLS_EXTENSIONS) && !defined(NO_TLS) && \ + !defined(NO_WOLFSSL_CLIENT) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte ext[300]; + word16 extLen; + + /* TLSX_ALPN_New(), reached through TLSX_UseALPN(): an over-length name + * is rejected; a normal one is accepted. (A NULL name is rejected by + * TLSX_UseALPN()'s own guard before TLSX_ALPN_New() is reached at all.) + */ +#ifdef WOLFSSL_TEST_STATIC_BUILD + { + TLSX* extensions = NULL; + byte tooLong[WOLFSSL_MAX_ALPN_PROTO_NAME_LEN + 1]; + XMEMSET(tooLong, 'a', sizeof(tooLong)); + + ExpectIntEQ(TLSX_UseALPN(&extensions, tooLong, sizeof(tooLong), 0, + NULL), WC_NO_ERR_TRACE(MEMORY_E)); + ExpectNull(extensions); + ExpectIntEQ(TLSX_UseALPN(&extensions, "http/1.1", 8, 0, NULL), + WOLFSSL_SUCCESS); + ExpectNotNull(extensions); + TLSX_FreeAll(extensions, NULL); + } +#endif + + /* TLSX_ALPN_GetRequest(): each NULL argument independently. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + void* data = NULL; + word16 dataSz = 0; + + ExpectIntEQ(TLSX_ALPN_GetRequest(NULL, &data, &dataSz), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_UseALPN(ssl, (char*)"http/1.1", 8, + WOLFSSL_ALPN_CONTINUE_ON_MISMATCH), WOLFSSL_SUCCESS); + ExpectIntEQ(TLSX_ALPN_GetRequest(ssl->extensions, NULL, &dataSz), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(TLSX_ALPN_GetRequest(ssl->extensions, &data, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(TLSX_ALPN_GetRequest(ssl->extensions, &data, &dataSz), + WOLFSSL_ALPN_NOT_FOUND); + } + wolfSSL_free(ssl); + ssl = NULL; + wolfSSL_CTX_free(ctx); + ctx = NULL; + + /* TLSX_ALPN_ParseAndSet() response direction and TLSX_ALPN_Find()/ + * ALPN_find_match(), all reached through TLSX_Parse(). The client + * configures a multi-entry ALPN list; the crafted server_hello + * response searches it, exercising exact match, same-length mismatch + * and different-length mismatch. + */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_UseALPN(ssl, (char*)"aa", 2, WOLFSSL_ALPN_FAILED_ON_MISMATCH), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseALPN(ssl, (char*)"bb", 2, + WOLFSSL_ALPN_CONTINUE_ON_MISMATCH), WOLFSSL_SUCCESS); + { + /* mismatched content, same length as "bb": exercises the + * XSTRNCMP() half of the search without an early length miss. */ + const byte respSameLen[] = { 0x00, 0x03, 0x02, 'z', 'z' }; + /* mismatched, longer than any configured name: every configured + * entry is rejected on length before content is compared. */ + const byte respDiffLen[] = { 0x00, 0x04, 0x03, 'z', 'z', 'z' }; + /* server_hello response, exact match on "bb". Run last: a + * successful match rewrites the ALPN list (marks the negotiated + * entry), so it must not run before the mismatch cases above. */ + const byte respExact[] = { 0x00, 0x03, 0x02, 'b', 'b' }; + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), + TLSXT_APPLICATION_LAYER_PROTOCOL, respSameLen, + (word16)sizeof(respSameLen)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, server_hello, NULL), 0); + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), + TLSXT_APPLICATION_LAYER_PROTOCOL, respDiffLen, + (word16)sizeof(respDiffLen)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, server_hello, NULL), 0); + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), + TLSXT_APPLICATION_LAYER_PROTOCOL, respExact, + (word16)sizeof(respExact)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, server_hello, NULL), 0); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* Response direction, nothing configured on this client at all: + * ALPN_find_match()'s extension == NULL path. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + const byte resp[] = { 0x00, 0x03, 0x02, 'h', 'i' }; + extLen = test_tls_parse_build_ext(ext, sizeof(ext), + TLSXT_APPLICATION_LAYER_PROTOCOL, resp, + (word16)sizeof(resp)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, server_hello, NULL), + WC_NO_ERR_TRACE(UNSUPPORTED_EXTENSION)); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* Response direction, two protocol names instead of the one RFC 7301 + * Section 3.1 allows in a response. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_UseALPN(ssl, (char*)"xx", 2, + WOLFSSL_ALPN_CONTINUE_ON_MISMATCH), WOLFSSL_SUCCESS); + if (ssl != NULL) { + const byte twoEntries[] = { 0x00, 0x06, 0x02, 'x', 'x', 0x02, 'y', 'y' }; + extLen = test_tls_parse_build_ext(ext, sizeof(ext), + TLSXT_APPLICATION_LAYER_PROTOCOL, twoEntries, + (word16)sizeof(twoEntries)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, server_hello, NULL), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* Request direction: server parses and stores a client_hello ALPN + * list. Exercises TLSX_ALPN_ParseAndSet()'s length bookkeeping (shared + * with the response direction) on the request side, including the + * per-entry wlen == 0 and length-overflow checks and the overall list + * size mismatch. + */ + ctx = test_tls_parse_server_ctx(wolfTLSv1_2_server_method()); + ExpectNotNull(ctx); + ssl = NULL; + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + Suites* suites = (Suites*)WOLFSSL_SUITES(ssl); + /* list size claims 5 bytes, only 3 are present. */ + const byte badListLen[] = { 0x00, 0x05, 0x02, 'h', 'i' }; + /* first entry's own length prefix is 0. */ + const byte zeroWlen[] = { 0x00, 0x01, 0x00 }; + /* first entry's length prefix (5) runs past the list. */ + const byte overWlen[] = { 0x00, 0x03, 0x05, 'h', 'i' }; + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), + TLSXT_APPLICATION_LAYER_PROTOCOL, badListLen, + (word16)sizeof(badListLen)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), + TLSXT_APPLICATION_LAYER_PROTOCOL, zeroWlen, + (word16)sizeof(zeroWlen)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), + TLSXT_APPLICATION_LAYER_PROTOCOL, overWlen, + (word16)sizeof(overWlen)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + ctx = test_tls_parse_server_ctx(wolfTLSv1_2_server_method()); + ExpectNotNull(ctx); + ssl = NULL; + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + const byte req[] = { 0x00, 0x03, 0x02, 'h', 'i' }; + Suites* suites = (Suites*)WOLFSSL_SUITES(ssl); + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), + TLSXT_APPLICATION_LAYER_PROTOCOL, req, (word16)sizeof(req)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), 0); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* ---- Trusted CA Keys ------------------------------------------------------ + * RFC 6066 Section 6. TLSX_TCA_New()'s per-type id validation, TLSX_TCA_Find() + * and the bounds checks in TLSX_TCA_Parse(), all server side. + */ +int test_TLSX_TCA_parse(void) +{ + EXPECT_DECLS; +#if defined(HAVE_TRUSTED_CA) && defined(HAVE_TLS_EXTENSIONS) && \ + !defined(NO_TLS) && !defined(NO_WOLFSSL_SERVER) && !defined(NO_SHA) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte ext[64]; + word16 extLen; + + /* TLSX_TCA_New(): SHA1 id of the wrong size is rejected; the right + * size is accepted. Also the X509 name id case (any idSz > 0). + */ +#ifdef WOLFSSL_TEST_STATIC_BUILD + { + TLSX* extensions = NULL; + byte id[WC_SHA_DIGEST_SIZE + 1]; + XMEMSET(id, 0x11, sizeof(id)); + + ExpectIntEQ(TLSX_UseTrustedCA(&extensions, + WOLFSSL_TRUSTED_CA_KEY_SHA1, id, WC_SHA_DIGEST_SIZE + 1, + NULL), WC_NO_ERR_TRACE(MEMORY_E)); + ExpectNull(extensions); + ExpectIntEQ(TLSX_UseTrustedCA(&extensions, + WOLFSSL_TRUSTED_CA_KEY_SHA1, id, WC_SHA_DIGEST_SIZE, + NULL), WOLFSSL_SUCCESS); + ExpectNotNull(extensions); + TLSX_FreeAll(extensions, NULL); + extensions = NULL; + + ExpectIntEQ(TLSX_UseTrustedCA(&extensions, + WOLFSSL_TRUSTED_CA_X509_NAME, id, 0, NULL), + WC_NO_ERR_TRACE(MEMORY_E)); + ExpectNull(extensions); + ExpectIntEQ(TLSX_UseTrustedCA(&extensions, + WOLFSSL_TRUSTED_CA_X509_NAME, id, 1, NULL), + WOLFSSL_SUCCESS); + ExpectNotNull(extensions); + TLSX_FreeAll(extensions, NULL); + } +#endif + + /* TLSX_TCA_Parse(), server side: not configured (no-op success), a + * length-prefix mismatch, and a well-formed list that TLSX_TCA_Find() + * matches by a pre-agreed entry (no id comparison needed). + */ + ctx = test_tls_parse_server_ctx(wolfTLSv1_2_server_method()); + ExpectNotNull(ctx); + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + /* No trusted_ca_keys configured on the server: skip, success. + * An empty list (list length 0) is the shortest body that still + * satisfies the extension's minimum-size gate. */ + Suites* suites = (Suites*)WOLFSSL_SUITES(ssl); + const byte empty[] = { 0x00, 0x00 }; + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_TRUSTED_CA_KEYS, empty, + (word16)sizeof(empty)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), 0); + } + wolfSSL_free(ssl); + ssl = NULL; + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + Suites* suites = (Suites*)WOLFSSL_SUITES(ssl); + /* list length (1) does not match actual body (3 bytes follow). */ + const byte badLen[] = { 0x00, 0x01, 0x00, 0x00, 0x00 }; + /* trusted_ca_keys list: one entry, type pre_agreed(0), matches the + * configured entry via TLSX_TCA_Find()'s type-only comparison. */ + const byte good[] = { 0x00, 0x01, WOLFSSL_TRUSTED_CA_PRE_AGREED }; + + ExpectIntEQ(wolfSSL_UseTrustedCA(ssl, WOLFSSL_TRUSTED_CA_PRE_AGREED, + NULL, 0), WOLFSSL_SUCCESS); + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_TRUSTED_CA_KEYS, badLen, + (word16)sizeof(badLen)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_TRUSTED_CA_KEYS, good, + (word16)sizeof(good)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), 0); + } + wolfSSL_free(ssl); + ssl = NULL; + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + Suites* suites = (Suites*)WOLFSSL_SUITES(ssl); + byte id[WC_SHA_DIGEST_SIZE]; + /* X509 name id: id length prefix claims more than remains. */ + const byte truncated[] = { + 0x00, 0x05, /* list length */ + WOLFSSL_TRUSTED_CA_X509_NAME, 0x00, 0x03, 0xAA, 0xBB + /* idSz=3 claimed, only 2 bytes remain: offset+idSz > length. */ + }; + const byte fits[] = { + 0x00, 0x05, + WOLFSSL_TRUSTED_CA_X509_NAME, 0x00, 0x02, 0xAA, 0xBB + }; + + XMEMSET(id, 0x33, sizeof(id)); + ExpectIntEQ(wolfSSL_UseTrustedCA(ssl, WOLFSSL_TRUSTED_CA_KEY_SHA1, + id, sizeof(id)), WOLFSSL_SUCCESS); + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_TRUSTED_CA_KEYS, + truncated, (word16)sizeof(truncated)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_TRUSTED_CA_KEYS, fits, + (word16)sizeof(fits)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), 0); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* Client side, response direction, trusted_ca_keys never requested: + * unsupported extension. */ +#ifndef NO_WOLFSSL_CLIENT + ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method()); + ExpectNotNull(ctx); + ssl = NULL; + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + extLen = test_tls_parse_build_ext(ext, sizeof(ext), + TLSXT_TRUSTED_CA_KEYS, NULL, 0); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, server_hello, NULL), + WC_NO_ERR_TRACE(UNSUPPORTED_EXTENSION)); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif +#endif + return EXPECT_RESULT(); +} + +/* ---- Client/Server Certificate Type (RFC 7250, raw public keys) --------- */ +int test_TLSX_certtype_parse(void) +{ + EXPECT_DECLS; +#if defined(HAVE_RPK) && defined(HAVE_TLS_EXTENSIONS) && !defined(NO_TLS) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(WOLFSSL_NO_TLS12) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte ext[16]; + word16 extLen; + + /* IsCertTypeListed(): value present vs. absent in an offered list, + * reached through TLSX_ClientCertificateType_Parse()'s client-side + * (server_hello) branch. The offered-type bookkeeping is set directly, + * mirroring what the write side records after a real ClientHello is + * built; only the received value's parse is under test here. + */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + const byte respX509[] = { WOLFSSL_CERT_TYPE_X509 }; + const byte respRpk[] = { WOLFSSL_CERT_TYPE_RPK }; + + ssl->options.rpkState.sending_ClientCertTypeCnt = 1; + ssl->options.rpkState.sending_ClientCertTypes[0] = + WOLFSSL_CERT_TYPE_X509; + + /* offered X509, server confirms X509: listed. */ + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_CLIENT_CERTIFICATE, + respX509, (word16)sizeof(respX509)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, server_hello, NULL), 0); + + /* offered X509, server claims RPK: not listed, rejected. */ + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_CLIENT_CERTIFICATE, + respRpk, (word16)sizeof(respRpk)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, server_hello, NULL), + WC_NO_ERR_TRACE(UNSUPPORTED_EXTENSION)); + + /* A count past MAX_CLIENT_CERT_TYPE_CNT is treated as not listed + * without reading past the fixed-size offered-types array. */ + ssl->options.rpkState.sending_ClientCertTypeCnt = 5; + extLen = test_tls_parse_build_ext(ext, sizeof(ext), + TLSXT_CLIENT_CERTIFICATE, respX509, (word16)sizeof(respX509)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, server_hello, NULL), + WC_NO_ERR_TRACE(UNSUPPORTED_EXTENSION)); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* ---- Cookie (RFC 8446 4.2.2 / DTLS 1.3 HelloRetryRequest) ---------------- */ +int test_TLSX_Cookie_parse(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + !defined(NO_TLS) && !defined(NO_WOLFSSL_CLIENT) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte ext[16]; + word16 extLen; + + /* client_hello direction with a previously stored cookie: length/value + * mismatch is rejected, a match is accepted. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + Suites* suites = (Suites*)WOLFSSL_SUITES(ssl); + const byte hrr[] = { 0x00, 0x03, 0xAA, 0xBB, 0xCC }; + const byte chBad[] = { 0x00, 0x03, 0xAA, 0xBB, 0xFF }; + const byte chGood[] = { 0x00, 0x03, 0xAA, 0xBB, 0xCC }; + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_COOKIE, hrr, + (word16)sizeof(hrr)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, hello_retry_request, NULL), + 0); + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_COOKIE, chBad, + (word16)sizeof(chBad)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), + WC_NO_ERR_TRACE(HRR_COOKIE_ERROR)); + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_COOKIE, chGood, + (word16)sizeof(chGood)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), 0); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + +#ifdef WOLFSSL_DTLS13 + /* DTLS 1.3: a cookie in client_hello with none stored yet and no + * matching extension is accepted (a different SSL instance may have + * issued it). */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + Suites* suites = (Suites*)WOLFSSL_SUITES(ssl); + const byte ch[] = { 0x00, 0x01, 0xAA }; + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_COOKIE, ch, + (word16)sizeof(ch)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), 0); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif +#endif + return EXPECT_RESULT(); +} + +/* ---- Encrypt-Then-Mac (RFC 7366) ----------------------------------------- */ +int test_TLSX_EncryptThenMac_parse(void) +{ + EXPECT_DECLS; +#if defined(HAVE_ENCRYPT_THEN_MAC) && !defined(WOLFSSL_AEAD_ONLY) && \ + !defined(NO_TLS) && !defined(NO_WOLFSSL_CLIENT) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte ext[8]; + word16 extLen; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + /* server_hello, empty body: accepted. */ + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_ENCRYPT_THEN_MAC, NULL, + 0); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, server_hello, NULL), 0); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* ---- Maximum Fragment Length (RFC 6066 Section 4) ------------------------ */ +int test_TLSX_MFL_parse(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MAX_FRAGMENT) && !defined(WOLFSSL_OLD_UNSUPPORTED_EXTENSION) \ + && !defined(NO_TLS) && !defined(NO_WOLFSSL_CLIENT) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte ext[8]; + word16 extLen; + + /* Client did not request MFL: any server_hello response is flagged as + * an unrequested extension before the value is even looked at. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + const byte resp[] = { WOLFSSL_MFL_2_9 }; + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_MAX_FRAGMENT_LENGTH, + resp, (word16)sizeof(resp)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, server_hello, NULL), + WC_NO_ERR_TRACE(UNSUPPORTED_EXTENSION)); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* Client requested MFL_2_9: a mismatching echo is rejected, the same + * value is accepted. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + const byte mismatch[] = { WOLFSSL_MFL_2_10 }; + const byte match[] = { WOLFSSL_MFL_2_9 }; + + ExpectIntEQ(wolfSSL_UseMaxFragment(ssl, WOLFSSL_MFL_2_9), + WOLFSSL_SUCCESS); + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_MAX_FRAGMENT_LENGTH, + mismatch, (word16)sizeof(mismatch)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, server_hello, NULL), + WC_NO_ERR_TRACE(UNKNOWN_MAX_FRAG_LEN_E)); + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_MAX_FRAGMENT_LENGTH, + match, (word16)sizeof(match)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, server_hello, NULL), 0); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* ---- Truncated HMAC (RFC 6066 Section 7, deprecated) --------------------- */ +int test_TLSX_THM_parse(void) +{ + EXPECT_DECLS; +#if defined(HAVE_TRUNCATED_HMAC) && !defined(NO_TLS) && \ + !defined(NO_WOLFSSL_CLIENT) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte ext[8]; + word16 extLen; + + /* Truncated HMAC is only ever dispatched to on client_hello (the + * server side of the extension); the response direction is not + * reachable through TLSX_Parse() at all. */ + ExpectNotNull(ctx = test_tls_parse_server_ctx(wolfTLSv1_2_server_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + Suites* suites = (Suites*)WOLFSSL_SUITES(ssl); + byte dummy = 0; + + /* Non-empty body is invalid: extension_data MUST be empty. */ + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_TRUNCATED_HMAC, &dummy, + 1); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + + /* Empty body, valid: enables the extension for this connection. */ + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_TRUNCATED_HMAC, NULL, + 0); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), 0); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* ---- Session Ticket (RFC 5077 / RFC 8446 4.6.1) -------------------------- */ +int test_TLSX_SessionTicket_parse(void) +{ + EXPECT_DECLS; +#if defined(HAVE_SESSION_TICKET) && !defined(NO_TLS) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(WOLFSSL_NO_TLS12) && \ + !defined(NO_WOLFSSL_CLIENT) && defined(HAVE_SSL_MEMIO_TESTS_DEPENDENCIES) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + WOLFSSL* ssl2 = NULL; + byte ext[600]; + word16 extLen; + + ctx = test_tls_parse_server_ctx(wolfTLSv1_2_server_method()); + ExpectNotNull(ctx); + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + Suites* suites = (Suites*)WOLFSSL_SUITES(ssl); + /* Too short to be a real ticket: DoClientTicket() rejects it + * without attempting decryption. */ + const byte tooShort[] = { 0x01, 0x02, 0x03, 0x04 }; + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_SESSION_TICKET, + tooShort, (word16)sizeof(tooShort)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), 0); + ExpectIntEQ(ssl->options.rejectTicket, 1); + } + wolfSSL_free(ssl); + ssl = NULL; + + /* A genuine ticket, decryptable by the ctx that issued it, but + * presented to a connection whose negotiated version is older than + * the ticket's: the ticket must be rejected with a version mismatch, + * not just a generic reject. Uses its own ctx pair (test_memio_setup() + * only wires up its I/O callbacks on a ctx it creates itself). */ + { + struct test_memio_ctx memio; + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx2 = NULL; + WOLFSSL* ssl_c = NULL; + byte ticket[512]; + word32 ticketSz = (word32)sizeof(ticket); + + XMEMSET(&memio, 0, sizeof(memio)); + ExpectIntEQ(test_memio_setup(&memio, &ctx_c, &ctx2, &ssl_c, &ssl, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + if (ssl_c != NULL) + ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl, 10, NULL), 0); + ExpectIntEQ(wolfSSL_get_SessionTicket(ssl_c, ticket, &ticketSz), + WOLFSSL_SUCCESS); + ExpectIntGT(ticketSz, 0); + + if (EXPECT_SUCCESS() && ticketSz > 0) { + ExpectNotNull(ssl2 = wolfSSL_new(ctx2)); + if (ssl2 != NULL) { + Suites* suites = (Suites*)WOLFSSL_SUITES(ssl2); + /* Force this connection to look like it negotiated a + * version older than the ticket's original session. */ + ssl2->version.minor = SSLv3_MINOR; + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_SESSION_TICKET, + ticket, (word16)ticketSz); + /* The internal VERSION_ERROR is caught and downgraded to a + * non-fatal reject, same as any other undecryptable + * ticket -- it does not propagate out of TLSX_Parse(). */ + ExpectIntEQ(TLSX_Parse(ssl2, ext, extLen, client_hello, + suites), 0); + ExpectIntEQ(ssl2->options.rejectTicket, 1); + } + } + wolfSSL_free(ssl2); + wolfSSL_free(ssl); + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx2); + wolfSSL_CTX_free(ctx_c); + ssl = NULL; + } + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* ---- Secure Renegotiation (RFC 5746) ------------------------------------- + * ret starts at SECURE_RENEGOTIATION_E and is only touched by + * wolfSSL_UseSecureRenegotiation()'s single allocation; forcing that + * allocation to fail is the only way to observe a third value. + */ +int test_TLSX_SecureRenegotiation_parse(void) +{ + EXPECT_DECLS; +#if defined(HAVE_SECURE_RENEGOTIATION) && !defined(NO_TLS) && \ + !defined(NO_WOLFSSL_SERVER) && defined(WOLFSSL_TEST_STATIC_BUILD) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte ext[8 + 2 * TLS_FINISHED_SZ]; + word16 extLen; + wolfSSL_Malloc_cb prevM = NULL; + wolfSSL_Free_cb prevF = NULL; + wolfSSL_Realloc_cb prevR = NULL; + + ctx = test_tls_parse_server_ctx(wolfTLSv1_2_server_method()); + ExpectNotNull(ctx); + + /* ret stays SECURE_RENEGOTIATION_E: secure_renegotiation is already + * set up and enabled (as if a prior renegotiation_info round trip had + * already completed on this connection), so the allocating branch is + * skipped, and so are the "not yet enabled" / verify-data branches + * further down the if/else-if chain -- ret is simply never touched. */ + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + Suites* suites = (Suites*)WOLFSSL_SUITES(ssl); + const byte arm[] = { 0x00 }; + /* neither 0 (the "not yet enabled" trigger) nor TLS_FINISHED_SZ. */ + const byte req[] = { 0xFF }; + + ExpectIntEQ(wolfSSL_UseSecureRenegotiation(ssl), WOLFSSL_SUCCESS); + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_RENEGOTIATION_INFO, + arm, (word16)sizeof(arm)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), 0); + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_RENEGOTIATION_INFO, + req, (word16)sizeof(req)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), + WC_NO_ERR_TRACE(SECURE_RENEGOTIATION_E)); + } + wolfSSL_free(ssl); + ssl = NULL; + + /* ret == 0: a normal first-time allocation succeeds. */ + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + Suites* suites = (Suites*)WOLFSSL_SUITES(ssl); + const byte req[] = { 0x00 }; + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_RENEGOTIATION_INFO, + req, (word16)sizeof(req)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), 0); + } + wolfSSL_free(ssl); + ssl = NULL; + + /* Force the one allocation inside TLSX_UseSecureRenegotiation() to + * fail, so ret picks up a third, distinct value. */ + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + Suites* suites = (Suites*)WOLFSSL_SUITES(ssl); + const byte req[] = { 0x00 }; + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_RENEGOTIATION_INFO, + req, (word16)sizeof(req)); + + ExpectIntEQ(wolfSSL_GetAllocators(&prevM, &prevF, &prevR), 0); + ExpectIntEQ(wolfSSL_SetAllocators(tls_parse_fail_malloc, + tls_parse_fail_free, tls_parse_fail_realloc), 0); + tls_parse_alloc_seen = 0; + tls_parse_fail_after = 0; + + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), + WC_NO_ERR_TRACE(MEMORY_E)); + + tls_parse_fail_after = -1; + (void)wolfSSL_SetAllocators(prevM, prevF, prevR); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* Client side response direction: *input == 2*TLS_FINISHED_SZ, but the + * declared extension length disagrees with it -- length is the + * second, independent half of that check. */ + ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method()); + ExpectNotNull(ctx); + ssl = NULL; + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + byte full[1 + 2 * TLS_FINISHED_SZ]; + /* declares the full double-verify-data size, but is one byte + * short of it. */ + byte shortBody[2 * TLS_FINISHED_SZ]; + + XMEMSET(full, 0, sizeof(full)); + full[0] = 2 * TLS_FINISHED_SZ; + XMEMSET(shortBody, 0, sizeof(shortBody)); + shortBody[0] = 2 * TLS_FINISHED_SZ; + + ExpectIntEQ(wolfSSL_UseSecureRenegotiation(ssl), WOLFSSL_SUCCESS); + ssl->secure_renegotiation->enabled = 1; + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_RENEGOTIATION_INFO, + shortBody, (word16)sizeof(shortBody)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, server_hello, NULL), + WC_NO_ERR_TRACE(SECURE_RENEGOTIATION_E)); + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_RENEGOTIATION_INFO, + full, (word16)sizeof(full)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, server_hello, NULL), 0); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* ---- Supported Versions (RFC 8446 4.2.1) --------------------------------- */ +int test_TLSX_SupportedVersions_parse(void) +{ + EXPECT_DECLS; + /* TLSX_Parse()'s own per-extension dispatch never reaches + * TLSX_SupportedVersions_Parse(): supported_versions is scanned and + * consumed by TLSX_ParseVersion() in an earlier pass (it must be known + * before any other extension can be interpreted), and the main loop's + * TLSX_SUPPORTED_VERSIONS case only re-validates the message type. + * WOLFSSL_LOCAL: called directly (guarded). */ +#if defined(WOLFSSL_TLS13) && !defined(NO_TLS) && !defined(NO_WOLFSSL_SERVER) \ + && !defined(NO_WOLFSSL_CLIENT) && defined(WOLFSSL_TEST_STATIC_BUILD) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte ext[8 + MAX_SV_EXT_LEN]; + word16 extLen; + int found; + + /* client_hello direction, server side: the three independent ways the + * body can fail the initial length sanity check. */ + ctx = test_tls_parse_server_ctx(wolfTLSv1_3_server_method()); + ExpectNotNull(ctx); + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + /* length (2) < OPAQUE8_LEN + OPAQUE16_LEN (3): too short. */ + const byte tooShort[] = { 0x02, 0x03 }; + /* even length: fails (length & 1) != 1. list len byte says 2, but + * total length here is even (4). */ + const byte evenLen[] = { 0x02, 0x03, 0x04, 0x00 }; + /* well-formed, single TLS 1.3 entry. */ + const byte good[] = { 0x02, 0x03, TLSv1_3_MINOR }; + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_SUPPORTED_VERSIONS, + tooShort, (word16)sizeof(tooShort)); + ExpectIntEQ(TLSX_ParseVersion(ssl, ext, extLen, client_hello, + &found), WC_NO_ERR_TRACE(BUFFER_ERROR)); + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_SUPPORTED_VERSIONS, + evenLen, (word16)sizeof(evenLen)); + ExpectIntEQ(TLSX_ParseVersion(ssl, ext, extLen, client_hello, + &found), WC_NO_ERR_TRACE(BUFFER_ERROR)); + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_SUPPORTED_VERSIONS, + good, (word16)sizeof(good)); + ExpectIntEQ(TLSX_ParseVersion(ssl, ext, extLen, client_hello, + &found), 0); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* An over-long list also fails on its own: odd total length (so the + * parity check already passes) but still over MAX_SV_EXT_LEN. */ + ctx = test_tls_parse_server_ctx(wolfTLSv1_3_server_method()); + ExpectNotNull(ctx); + ssl = NULL; + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + byte body[MAX_SV_EXT_LEN + 2]; + word16 i; + + body[0] = (byte)(MAX_SV_EXT_LEN + 1); + for (i = 1; i < sizeof(body); i += 2) { + body[i] = SSLv3_MAJOR; + body[i + 1] = TLSv1_3_MINOR; + } + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_SUPPORTED_VERSIONS, + body, (word16)sizeof(body)); + ExpectIntEQ(TLSX_ParseVersion(ssl, ext, extLen, client_hello, + &found), WC_NO_ERR_TRACE(BUFFER_ERROR)); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* server_hello / hello_retry_request direction, client side. */ + ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); + ExpectNotNull(ctx); + ssl = NULL; + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + const byte v[] = { SSLv3_MAJOR, TLSv1_3_MINOR }; + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_SUPPORTED_VERSIONS, v, + (word16)sizeof(v)); + ExpectIntEQ(TLSX_ParseVersion(ssl, ext, extLen, server_hello, + &found), 0); + } + wolfSSL_free(ssl); + ssl = NULL; + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + const byte v[] = { SSLv3_MAJOR, TLSv1_3_MINOR }; + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_SUPPORTED_VERSIONS, v, + (word16)sizeof(v)); + ExpectIntEQ(TLSX_ParseVersion(ssl, ext, extLen, hello_retry_request, + &found), 0); + } + wolfSSL_free(ssl); + ssl = NULL; + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + /* Neither client_hello, server_hello nor hello_retry_request. */ + const byte v[] = { SSLv3_MAJOR, TLSv1_3_MINOR }; + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_SUPPORTED_VERSIONS, v, + (word16)sizeof(v)); + ExpectIntEQ(TLSX_ParseVersion(ssl, ext, extLen, finished, &found), + WC_NO_ERR_TRACE(SANITY_MSG_E)); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* Downgrade bookkeeping: ssl->options.downgrade set and the connection + * already sitting at TLS 1.2 minor -- vs. either being false. */ + ctx = wolfSSL_CTX_new(wolfSSLv23_client_method()); + ExpectNotNull(ctx); + ssl = NULL; + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + const byte v[] = { SSLv3_MAJOR, TLSv1_3_MINOR }; + ssl->options.downgrade = 1; + ssl->version.minor = TLSv1_2_MINOR; + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_SUPPORTED_VERSIONS, v, + (word16)sizeof(v)); + ExpectIntEQ(TLSX_ParseVersion(ssl, ext, extLen, server_hello, + &found), 0); + } + wolfSSL_free(ssl); + ssl = NULL; + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + /* downgrade left false and version.minor left at its default + * (TLS 1.3): the "no upgrade allowed" check right after this one + * would otherwise reject a version.minor that was forced down + * without downgrade being set. */ + const byte v[] = { SSLv3_MAJOR, TLSv1_3_MINOR }; + ssl->options.downgrade = 0; + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_SUPPORTED_VERSIONS, v, + (word16)sizeof(v)); + ExpectIntEQ(TLSX_ParseVersion(ssl, ext, extLen, server_hello, + &found), 0); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* ---- Signature Algorithms (RFC 8446 4.2.3) ------------------------------- */ +int test_TLSX_SignatureAlgorithms_parse(void) +{ + EXPECT_DECLS; +#if !defined(NO_CERTS) && !defined(WOLFSSL_NO_SIGALG) && !defined(NO_TLS) && \ + !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte ext[16]; + word16 extLen; + + ctx = test_tls_parse_server_ctx(wolfTLSv1_2_server_method()); + ExpectNotNull(ctx); + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + Suites* suites = (Suites*)WOLFSSL_SUITES(ssl); + /* length (3) < OPAQUE16_LEN*2 (4): too short. */ + const byte tooShort[] = { 0x00, 0x02, 0x04 }; + /* odd overall length: fails (length & 1) != 0. */ + const byte oddLen[] = { 0x00, 0x02, 0x04, 0x03, 0x00 }; + /* well-formed, one algorithm. */ + const byte good[] = { 0x00, 0x02, 0x04, 0x03 }; + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_SIGNATURE_ALGORITHMS, + tooShort, (word16)sizeof(tooShort)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_SIGNATURE_ALGORITHMS, + oddLen, (word16)sizeof(oddLen)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_SIGNATURE_ALGORITHMS, + good, (word16)sizeof(good)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), 0); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + +#if !defined(NO_RSA) && defined(HAVE_TLS_EXTENSIONS) && defined(WOLFSSL_TLS13) + /* SignatureAlgorithmsCert: same length checks, separate extension. + * Only dispatched to on a TLS 1.3+ connection. */ + ctx = test_tls_parse_server_ctx(wolfTLSv1_3_server_method()); + ExpectNotNull(ctx); + ssl = NULL; + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + Suites* suites = (Suites*)WOLFSSL_SUITES(ssl); + const byte tooShort[] = { 0x00, 0x02, 0x04 }; + /* long enough, but an odd total length. */ + const byte oddLen[] = { 0x00, 0x02, 0x04, 0x03, 0x00 }; + const byte good[] = { 0x00, 0x02, 0x04, 0x03 }; + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), + TLSXT_SIGNATURE_ALGORITHMS_CERT, tooShort, + (word16)sizeof(tooShort)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), + TLSXT_SIGNATURE_ALGORITHMS_CERT, oddLen, + (word16)sizeof(oddLen)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), + TLSXT_SIGNATURE_ALGORITHMS_CERT, good, + (word16)sizeof(good)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), 0); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* TLSX_SignatureAlgorithms_MapPss(): rsa_pss_sa_algo entries whose + * second byte is within [pss_sha256, pss_sha512] vs. just above it, + * both already >= pss_sha256. Reached via the plain SignatureAlgorithms + * extension's TLS 1.3 PSS-with-SHA remap. */ +#ifdef WOLFSSL_TLS13 + ctx = test_tls_parse_server_ctx(wolfTLSv1_3_server_method()); + ExpectNotNull(ctx); + ssl = NULL; + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + Suites* suites = (Suites*)WOLFSSL_SUITES(ssl); + const byte inRange[] = { 0x00, 0x02, rsa_pss_sa_algo, pss_sha512 }; + const byte aboveRange[] = { + 0x00, 0x02, rsa_pss_sa_algo, (byte)(pss_sha512 + 1) + }; + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_SIGNATURE_ALGORITHMS, + inRange, (word16)sizeof(inRange)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), 0); + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_SIGNATURE_ALGORITHMS, + aboveRange, (word16)sizeof(aboveRange)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), 0); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif +#endif +#endif + return EXPECT_RESULT(); +} + +/* ---- Certificate Status Request / v2 (RFC 6066 8, RFC 6961) -------------- */ +int test_TLSX_CSR_parse(void) +{ + EXPECT_DECLS; +#if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(NO_TLS) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_WOLFSSL_CLIENT) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte ext[32]; + word16 extLen; + + /* Server side: not able to staple (no OCSP stapling enabled) skips + * rather than failing. */ + ctx = test_tls_parse_server_ctx(wolfTLSv1_2_server_method()); + ExpectNotNull(ctx); + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + Suites* suites = (Suites*)WOLFSSL_SUITES(ssl); + const byte req[] = { + WOLFSSL_CSR_OCSP, + 0x00, 0x00, /* responder_id_list, empty */ + 0x00, 0x00 /* request_extensions, empty */ + }; + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_STATUS_REQUEST, req, + (word16)sizeof(req)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), 0); + } + wolfSSL_free(ssl); + ssl = NULL; + + /* Same request, but with OCSP stapling enabled on the ctx: taken. */ + ExpectIntEQ(wolfSSL_CTX_EnableOCSPStapling(ctx), WOLFSSL_SUCCESS); + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + Suites* suites = (Suites*)WOLFSSL_SUITES(ssl); + const byte req[] = { + WOLFSSL_CSR_OCSP, + 0x00, 0x00, + 0x00, 0x00 + }; + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_STATUS_REQUEST, req, + (word16)sizeof(req)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), 0); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + +#if defined(WOLFSSL_TLS13) && defined(HAVE_SSL_MEMIO_TESTS_DEPENDENCIES) + /* Client side, TLS 1.3 certificate direction (RFC 8446 4.4.2): the OCSP + * response TLV's status_type and length bookkeeping. */ + ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); + ExpectNotNull(ctx); + ssl = NULL; + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + const byte respBadType[] = { + 0x00, /* status_type: not WOLFSSL_CSR_OCSP */ + 0x00, 0x00, 0x00 /* 24-bit response length: 0 */ + }; + + ExpectIntEQ(wolfSSL_UseOCSPStapling(ssl, WOLFSSL_CSR_OCSP, 0), + WOLFSSL_SUCCESS); + /* This synthetic parse never ran a real handshake, so the + * negotiated-TLS-1.3 flag was never set by version negotiation. */ + ssl->options.tls1_3 = 1; + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_STATUS_REQUEST, + respBadType, (word16)sizeof(respBadType)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, certificate, NULL), + WC_NO_ERR_TRACE(BAD_CERTIFICATE_STATUS_ERROR)); + } + wolfSSL_free(ssl); + ssl = NULL; + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + /* Too short even for the status_type byte and the 24-bit response + * length: rejected before the status_type byte is looked at. */ + const byte tooShort[] = { 0x00, 0x00, 0x00 }; + + ExpectIntEQ(wolfSSL_UseOCSPStapling(ssl, WOLFSSL_CSR_OCSP, 0), + WOLFSSL_SUCCESS); + ssl->options.tls1_3 = 1; + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_STATUS_REQUEST, + tooShort, (word16)sizeof(tooShort)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, certificate, NULL), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + } + wolfSSL_free(ssl); + ssl = NULL; + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + const byte respGood[] = { + WOLFSSL_CSR_OCSP, + 0x00, 0x00, 0x01, + 0xAA + }; + ExpectIntEQ(wolfSSL_UseOCSPStapling(ssl, WOLFSSL_CSR_OCSP, 0), + WOLFSSL_SUCCESS); + ssl->options.tls1_3 = 1; + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_STATUS_REQUEST, + respGood, (word16)sizeof(respGood)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, certificate, NULL), + 0); + } + wolfSSL_free(ssl); + ssl = NULL; + + /* The response buffer allocation: response_idx is always 0 at this + * point in a synthetic parse (no prior certificate chain was + * processed), so a real handshake is not needed to force a memory + * failure at that one allocation. */ +#ifdef WOLFSSL_TEST_STATIC_BUILD + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + const byte respGood[] = { + WOLFSSL_CSR_OCSP, + 0x00, 0x00, 0x01, + 0xAA + }; + wolfSSL_Malloc_cb prevM = NULL; + wolfSSL_Free_cb prevF = NULL; + wolfSSL_Realloc_cb prevR = NULL; + + ExpectIntEQ(wolfSSL_UseOCSPStapling(ssl, WOLFSSL_CSR_OCSP, 0), + WOLFSSL_SUCCESS); + ssl->options.tls1_3 = 1; + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_STATUS_REQUEST, + respGood, (word16)sizeof(respGood)); + + ExpectIntEQ(wolfSSL_GetAllocators(&prevM, &prevF, &prevR), 0); + ExpectIntEQ(wolfSSL_SetAllocators(tls_parse_fail_malloc, + tls_parse_fail_free, tls_parse_fail_realloc), 0); + tls_parse_alloc_seen = 0; + tls_parse_fail_after = 0; + + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, certificate, NULL), + WC_NO_ERR_TRACE(MEMORY_ERROR)); + + tls_parse_fail_after = -1; + (void)wolfSSL_SetAllocators(prevM, prevF, prevR); + } + wolfSSL_free(ssl); + ssl = NULL; +#endif + wolfSSL_CTX_free(ctx); +#endif /* WOLFSSL_TLS13 && HAVE_SSL_MEMIO_TESTS_DEPENDENCIES */ +#endif + +#if defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) && !defined(NO_TLS) && \ + !defined(NO_WOLFSSL_SERVER) + { + WOLFSSL_CTX* ctx2 = test_tls_parse_server_ctx( + wolfTLSv1_2_server_method()); + WOLFSSL* ssl2 = NULL; + byte ext2[16]; + word16 ext2Len; + /* One status_request_v2 entry: type OCSP, empty responder_id_list + * and empty request_extensions. */ + const byte req[] = { + 0x00, 0x07, /* overall list length */ + WOLFSSL_CSR2_OCSP, + 0x00, 0x04, /* this entry's length */ + 0x00, 0x00, /* responder_id_list, empty */ + 0x00, 0x00 /* request_extensions, empty */ + }; + + ExpectNotNull(ctx2); + + /* No OCSP stapling enabled: this entry is skipped (continue). */ + if (ctx2 != NULL) + ExpectNotNull(ssl2 = wolfSSL_new(ctx2)); + if (ssl2 != NULL) { + Suites* suites = (Suites*)WOLFSSL_SUITES(ssl2); + ext2Len = test_tls_parse_build_ext(ext2, sizeof(ext2), TLSXT_STATUS_REQUEST_V2, + req, (word16)sizeof(req)); + ExpectIntEQ(TLSX_Parse(ssl2, ext2, ext2Len, client_hello, + suites), 0); + } + wolfSSL_free(ssl2); + ssl2 = NULL; + + /* OCSP stapling enabled: the entry is used. */ + ExpectIntEQ(wolfSSL_CTX_EnableOCSPStapling(ctx2), WOLFSSL_SUCCESS); + if (ctx2 != NULL) + ExpectNotNull(ssl2 = wolfSSL_new(ctx2)); + if (ssl2 != NULL) { + Suites* suites = (Suites*)WOLFSSL_SUITES(ssl2); + ext2Len = test_tls_parse_build_ext(ext2, sizeof(ext2), TLSXT_STATUS_REQUEST_V2, + req, (word16)sizeof(req)); + ExpectIntEQ(TLSX_Parse(ssl2, ext2, ext2Len, client_hello, + suites), 0); + } + wolfSSL_free(ssl2); + wolfSSL_CTX_free(ctx2); + } +#endif + return EXPECT_RESULT(); +} + +/* ---- EC Point Formats (RFC 8422 5.1.2) ----------------------------------- */ +int test_TLSX_PointFormat_parse(void) +{ + EXPECT_DECLS; +#if defined(HAVE_SUPPORTED_CURVES) && !defined(NO_TLS) && \ + !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte ext[16]; + word16 extLen; + + ctx = test_tls_parse_server_ctx(wolfTLSv1_2_server_method()); + ExpectNotNull(ctx); + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + Suites* suites = (Suites*)WOLFSSL_SUITES(ssl); + /* list length byte (2) disagrees with the actual body length (2 + * total, 1 format byte). */ + const byte badLen[] = { 0x02, WOLFSSL_EC_PF_UNCOMPRESSED }; + /* well formed: one format, uncompressed. */ + const byte good[] = { 0x01, WOLFSSL_EC_PF_UNCOMPRESSED }; + + /* length (0) < ENUM_LEN (1). */ + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_EC_POINT_FORMATS, NULL, + 0); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_EC_POINT_FORMATS, + badLen, (word16)sizeof(badLen)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_EC_POINT_FORMATS, + good, (word16)sizeof(good)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), 0); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + +#if defined(WOLFSSL_TLS13) && defined(HAVE_SUPPORTED_CURVES) && \ + !defined(WOLFSSL_NO_SERVER_GROUPS_EXT) && !defined(NO_TLS) && \ + !defined(NO_WOLFSSL_SERVER) && defined(WOLFSSL_TEST_STATIC_BUILD) + /* TLSX_SupportedCurve_Preferred(): checkSupported gating and the + * TLSX_IsGroupSupported() result, driven by a raw (unfiltered) offered + * groups list -- TLSX_SupportedCurve_Parse() records whatever the peer + * sent, support is only checked later by this function. A TLS 1.2 + * server ssl is used so RFC 8446 9.2's "KeyShare requires + * SupportedGroups and vice-versa" check does not apply; the function + * under test does not itself depend on the negotiated version. */ + { + WOLFSSL_CTX* ctxp = test_tls_parse_server_ctx( + wolfTLSv1_2_server_method()); + WOLFSSL* sslp = NULL; + + ExpectNotNull(ctxp); + if (ctxp != NULL) + ExpectNotNull(sslp = wolfSSL_new(ctxp)); +#if !defined(NO_DH) && !defined(WOLFSSL_NO_TLS12) + if (sslp != NULL) { + Suites* suites = (Suites*)WOLFSSL_SUITES(sslp); + /* An FFDHE codepoint this build does not have enabled (only + * WOLFSSL_FFDHE_2048 is): recorded with its real group id + * per RFC 7919 Section 4, but TLSX_IsGroupSupported() is false + * for it -- unlike a non-FFDHE unrecognised id, which is + * recorded as an empty (data == NULL) restriction instead. */ + const byte groups[] = { 0x00, 0x02, 0x01, 0x01 }; /* FFDHE 3072 */ + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_SUPPORTED_GROUPS, + groups, (word16)sizeof(groups)); + ExpectIntEQ(TLSX_Parse(sslp, ext, extLen, client_hello, suites), + 0); + + ExpectIntEQ(TLSX_SupportedCurve_Preferred(sslp, 0), + WOLFSSL_FFDHE_3072); + ExpectIntEQ(TLSX_SupportedCurve_Preferred(sslp, 1), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + } +#endif + wolfSSL_free(sslp); + sslp = NULL; + if (ctxp != NULL) + ExpectNotNull(sslp = wolfSSL_new(ctxp)); + if (sslp != NULL) { +#ifdef HAVE_CURVE25519 + Suites* suites = (Suites*)WOLFSSL_SUITES(sslp); + const byte groups[] = { 0x00, 0x02, 0x00, 0x1D }; /* X25519 */ + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_SUPPORTED_GROUPS, + groups, (word16)sizeof(groups)); + ExpectIntEQ(TLSX_Parse(sslp, ext, extLen, client_hello, suites), + 0); + ExpectIntEQ(TLSX_SupportedCurve_Preferred(sslp, 1), + WOLFSSL_ECC_X25519); +#endif + } + wolfSSL_free(sslp); + wolfSSL_CTX_free(ctxp); + } +#endif + return EXPECT_RESULT(); +} + +/* ---- Server Name Indication (RFC 6066 Section 3) ------------------------- */ +int test_TLSX_SNI_parse(void) +{ + EXPECT_DECLS; +#if defined(HAVE_SNI) && !defined(NO_TLS) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte ext[64]; + word16 extLen; + + ctx = test_tls_parse_server_ctx(wolfTLSv1_2_server_method()); + ExpectNotNull(ctx); + + /* server_name_list length disagreements: total-length mismatch and + * zero-length list, then a well-formed one-entry list. SNI must be + * configured on the server for the match logic below it to run. */ + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + Suites* suites = (Suites*)WOLFSSL_SUITES(ssl); + const char* host = "example.com"; + /* list length (5) does not match the 6 bytes that actually follow + * it. */ + const byte lenMismatch[] = { + 0x00, 0x05, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA + }; + /* [server_name_list length(2)][name_type(1)][name length(2)][name] */ + byte good[2 + 1 + 2 + 11]; + word16 off = 2; + + ExpectIntEQ(wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, host, + (word16)XSTRLEN(host)), WOLFSSL_SUCCESS); + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_SERVER_NAME, + lenMismatch, (word16)sizeof(lenMismatch)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + + good[off++] = WOLFSSL_SNI_HOST_NAME; + good[off++] = 0x00; good[off++] = 11; + XMEMCPY(good + off, host, 11); + off += 11; + good[0] = 0x00; good[1] = (byte)(off - 2); /* list length */ + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_SERVER_NAME, good, off); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), 0); + } + wolfSSL_free(ssl); + ssl = NULL; + + /* Host-name length disagreements within an otherwise valid list: the + * declared name length runs past the extension body, and a + * zero-length name. */ + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + Suites* suites = (Suites*)WOLFSSL_SUITES(ssl); + const char* host = "example.com"; + byte truncated[2 + 1 + 2 + 11]; + byte zeroName[2 + 1 + 2]; + word16 off; + + ExpectIntEQ(wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, host, + (word16)XSTRLEN(host)), WOLFSSL_SUCCESS); + + off = 2; + truncated[off++] = WOLFSSL_SNI_HOST_NAME; + /* claim a name length of 12 (one more than provided). */ + truncated[off++] = 0x00; truncated[off++] = 12; + XMEMCPY(truncated + off, host, 11); + off += 11; + truncated[0] = 0x00; truncated[1] = (byte)(off - 2); + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_SERVER_NAME, truncated, + off); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + + off = 2; + zeroName[off++] = WOLFSSL_SNI_HOST_NAME; + zeroName[off++] = 0x00; zeroName[off++] = 0x00; + zeroName[0] = 0x00; zeroName[1] = (byte)(off - 2); + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_SERVER_NAME, zeroName, + off); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* Client side response direction: SNI configured (extension and its + * data both present) accepts the empty echo; a non-empty echo is + * rejected. */ + ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method()); + ExpectNotNull(ctx); + ssl = NULL; + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + const char* host = "example.com"; + byte extra = 0; + + ExpectIntEQ(wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, host, + (word16)XSTRLEN(host)), WOLFSSL_SUCCESS); + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_SERVER_NAME, NULL, 0); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, server_hello, NULL), 0); + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_SERVER_NAME, &extra, 1); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, server_hello, NULL), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* Client side, SNI not requested at all: response is an unsupported + * extension. */ + ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method()); + ExpectNotNull(ctx); + ssl = NULL; + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_SERVER_NAME, NULL, 0); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, server_hello, NULL), + WC_NO_ERR_TRACE(UNSUPPORTED_EXTENSION)); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* ---- TLSX_ValidateSupportedCurves() ------------------------------------- + * Cipher-suite/curve compatibility check used while building a ServerHello. + * WOLFSSL_LOCAL: called directly (guarded), with a supported_groups list + * built by parsing a raw ClientHello extension body -- that path records + * whatever the peer sent without filtering for local support, which is + * what lets a specific curve be selected precisely for this test. + */ +int test_TLSX_ValidateSupportedCurves(void) +{ + EXPECT_DECLS; +#if (defined(HAVE_ECC) || defined(HAVE_CURVE25519) || \ + defined(HAVE_CURVE448)) && defined(HAVE_SUPPORTED_CURVES) && \ + !defined(NO_TLS) && !defined(NO_WOLFSSL_SERVER) && \ + defined(WOLFSSL_TEST_STATIC_BUILD) && defined(HAVE_CURVE25519) && \ + defined(HAVE_CURVE448) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + word32 oid; + byte ext[16]; + word16 extLen; + + ctx = test_tls_parse_server_ctx(wolfTLSv1_2_server_method()); + ExpectNotNull(ctx); + + /* first != {ECC_BYTE, ECDHE_PSK_BYTE, CHACHA_BYTE}: no restriction, + * independent of any configured groups. */ + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + ExpectIntEQ(TLSX_ValidateSupportedCurves(ssl, 0x00, 0x00, &oid), 1); + } + wolfSSL_free(ssl); + ssl = NULL; + + /* first == ECC_BYTE, with a supported_groups list offering X25519 + * then X448: exercises the X25519/X448 default-case defOid reset + * (second entry is not the one that set defOid). */ + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + Suites* suites = (Suites*)WOLFSSL_SUITES(ssl); + const byte groups[] = { + 0x00, 0x04, + 0x00, 0x1D, /* X25519 */ + 0x00, 0x1E /* X448 */ + }; + ssl->eccTempKeySz = 0; + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_SUPPORTED_GROUPS, + groups, (word16)sizeof(groups)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), 0); + /* second == some value not matched by any explicit ECC_BYTE case + * (no ECDHE_ECDSA/ECDHE_RSA cipher id): falls to "default". */ + ExpectIntEQ(TLSX_ValidateSupportedCurves(ssl, ECC_BYTE, 0xFF, &oid), + 1); + } + wolfSSL_free(ssl); + ssl = NULL; + + /* Single-entry X25519 list: defOid gets set to X25519 by the first + * curve seen, and the default-case reset then fires for that very + * entry (oid == defOid). */ + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + Suites* suites = (Suites*)WOLFSSL_SUITES(ssl); + const byte groups[] = { 0x00, 0x02, 0x00, 0x1D }; + ssl->eccTempKeySz = 0; + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_SUPPORTED_GROUPS, + groups, (word16)sizeof(groups)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), 0); + ExpectIntEQ(TLSX_ValidateSupportedCurves(ssl, ECC_BYTE, 0xFF, &oid), + 1); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif return EXPECT_RESULT(); } diff --git a/tests/api/test_tls_parse.h b/tests/api/test_tls_parse.h index cfe58d8d93..42d4e82ca4 100644 --- a/tests/api/test_tls_parse.h +++ b/tests/api/test_tls_parse.h @@ -22,9 +22,37 @@ #ifndef TESTS_API_TEST_TLS_PARSE_H #define TESTS_API_TEST_TLS_PARSE_H -int test_tls_parse_placeholder(void); +int test_TLSX_ALPN_parse(void); +int test_TLSX_TCA_parse(void); +int test_TLSX_certtype_parse(void); +int test_TLSX_Cookie_parse(void); +int test_TLSX_EncryptThenMac_parse(void); +int test_TLSX_MFL_parse(void); +int test_TLSX_THM_parse(void); +int test_TLSX_SessionTicket_parse(void); +int test_TLSX_SecureRenegotiation_parse(void); +int test_TLSX_SupportedVersions_parse(void); +int test_TLSX_SignatureAlgorithms_parse(void); +int test_TLSX_CSR_parse(void); +int test_TLSX_PointFormat_parse(void); +int test_TLSX_SNI_parse(void); +int test_TLSX_ValidateSupportedCurves(void); -#define TEST_TLS_PARSE_DECLS \ - TEST_DECL_GROUP("tls", test_tls_parse_placeholder) +#define TEST_TLS_PARSE_DECLS \ + TEST_DECL_GROUP("tls", test_TLSX_ALPN_parse), \ + TEST_DECL_GROUP("tls", test_TLSX_TCA_parse), \ + TEST_DECL_GROUP("tls", test_TLSX_certtype_parse), \ + TEST_DECL_GROUP("tls", test_TLSX_Cookie_parse), \ + TEST_DECL_GROUP("tls", test_TLSX_EncryptThenMac_parse), \ + TEST_DECL_GROUP("tls", test_TLSX_MFL_parse), \ + TEST_DECL_GROUP("tls", test_TLSX_THM_parse), \ + TEST_DECL_GROUP("tls", test_TLSX_SessionTicket_parse), \ + TEST_DECL_GROUP("tls", test_TLSX_SecureRenegotiation_parse), \ + TEST_DECL_GROUP("tls", test_TLSX_SupportedVersions_parse), \ + TEST_DECL_GROUP("tls", test_TLSX_SignatureAlgorithms_parse), \ + TEST_DECL_GROUP("tls", test_TLSX_CSR_parse), \ + TEST_DECL_GROUP("tls", test_TLSX_PointFormat_parse), \ + TEST_DECL_GROUP("tls", test_TLSX_SNI_parse), \ + TEST_DECL_GROUP("tls", test_TLSX_ValidateSupportedCurves) #endif /* TESTS_API_TEST_TLS_PARSE_H */ From 2d51e22709c0032ddc718573759401d12caceaab Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 22:59:52 +0200 Subject: [PATCH 43/57] tests: cover SNI, pre-shared-key, cookie and trusted-CA parsing in tls.c Adds direct TLSX_Parse()-driven tests for TLSX_SNI_Parse's cacheOnly/ checkPublic/ECH-publicName match logic and TLSX_SNI_Find, the RFC 8446 4.2.11 pre_shared_key identity/binder walk and server-side identity selection/resumption checks, TLSX_Cookie_Parse's DTLS 1.3 and echoed- cookie comparison, and TLSX_TCA_Parse/TLSX_TCA_Find/TLSX_TCA_New. --- tests/api/test_tls_msgtype.c | 1237 ++++++++++++++++++++++++++++++++++ tests/api/test_tls_msgtype.h | 32 +- 2 files changed, 1268 insertions(+), 1 deletion(-) diff --git a/tests/api/test_tls_msgtype.c b/tests/api/test_tls_msgtype.c index 3274b07c55..fbe35bfd88 100644 --- a/tests/api/test_tls_msgtype.c +++ b/tests/api/test_tls_msgtype.c @@ -52,6 +52,22 @@ static word16 build_ext(byte* buf, word16 type, word16 dataSz) return (word16)(4 + dataSz); } +/* Build one extension record (2-byte type, 2-byte length, then bodyLen + * bytes copied verbatim from body) into buf and return its total length. + * Unlike build_ext(), the data is caller-supplied, for extensions whose + * gates require structurally meaningful content rather than zero bytes. */ +static word16 build_ext_with_body(byte* buf, word16 type, const byte* body, + word16 bodyLen) +{ + buf[0] = (byte)(type >> 8); + buf[1] = (byte)type; + buf[2] = (byte)(bodyLen >> 8); + buf[3] = (byte)bodyLen; + if (bodyLen > 0) + XMEMCPY(buf + 4, body, bodyLen); + return (word16)(4 + bodyLen); +} + #endif /* !NO_WOLFSSL_CLIENT && !NO_TLS && HAVE_TLS_EXTENSIONS */ /* ---- TLSX_Parse() argument validation ------------------------------- */ @@ -1183,3 +1199,1224 @@ int test_tls_msgtype_ech(void) #endif return EXPECT_RESULT(); } + +#if defined(HAVE_SNI) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +/* Builds an SNI extension body: 2-byte list length, 1-byte name type + * (WOLFSSL_SNI_HOST_NAME), 2-byte name length, name bytes. Returns the + * total body length. */ +static word16 build_sni_body(byte* buf, const char* host) +{ + word16 hostLen = (word16)XSTRLEN(host); + word16 listLen = (word16)(ENUM_LEN + OPAQUE16_LEN + hostLen); + + buf[0] = (byte)(listLen >> 8); + buf[1] = (byte)listLen; + buf[2] = WOLFSSL_SNI_HOST_NAME; + buf[3] = (byte)(hostLen >> 8); + buf[4] = (byte)hostLen; + XMEMCPY(buf + 5, host, hostLen); + return (word16)(5 + hostLen); +} +#endif /* HAVE_SNI && !NO_WOLFSSL_CLIENT && !NO_TLS */ + +/* ---- TLSX_SNI_Find() -------------------------------------------------- */ +/* while (sni && sni->type != type) sni = sni->next; */ +int test_tls_msgtype_sni_find(void) +{ + EXPECT_DECLS; +#if defined(HAVE_SNI) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + const char* host = "example.com"; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, host, + (word16)XSTRLEN(host)), WOLFSSL_SUCCESS); + + /* sni != NULL, sni->type == type: the loop body is never entered - the + * only list entry is found immediately. */ + ExpectIntEQ(wolfSSL_SNI_Status(ssl, WOLFSSL_SNI_HOST_NAME), + WOLFSSL_SNI_NO_MATCH); + + /* sni != NULL, sni->type != type: one non-matching iteration advances + * to sni->next, which is NULL, ending the loop without a match. There + * is only one SNI name type, so a type the list does not hold is the + * only way to exercise this. */ + ExpectIntEQ(wolfSSL_SNI_Status(ssl, (byte)(WOLFSSL_SNI_HOST_NAME + 1)), + 0); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* ---- TLSX_SNI_Parse(): client-side response gate ---------------------- */ +/* !isRequest branch: if (!extension || !extension->data) + * return TLSX_HandleUnsupportedExtension(ssl); */ +int test_tls_msgtype_sni_parse_response_gate(void) +{ + EXPECT_DECLS; +#if defined(HAVE_SNI) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + !defined(WOLFSSL_NO_TLS12) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + TLSX* extension = NULL; + const char* host = "example.com"; + byte buf[8]; + word16 len; + + /* extension found, but its data was cleared: the client must still + * treat a ServerHello SNI response as unsolicited rather than + * dereference a NULL SNI list. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, host, + (word16)XSTRLEN(host)), WOLFSSL_SUCCESS); + ExpectNotNull(extension = TLSX_Find(ssl->extensions, TLSX_SERVER_NAME)); + if (extension != NULL) + extension->data = NULL; + + len = build_ext(buf, TLSX_SERVER_NAME, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, server_hello, NULL), + WC_NO_ERR_TRACE(UNSUPPORTED_EXTENSION)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* ---- TLSX_SNI_Parse(): server-side list-length gate -------------------- */ +/* if (length != OPAQUE16_LEN + size || size == 0) return BUFFER_ERROR; + * A record this short is already rejected by TLSX_Parse()'s own minimum- + * size gate (WOLFSSL_SNI_MIN_SIZE_CLIENT) before TLSX_SNI_Parse() is ever + * called, so this exercises that outer gate rather than the size == 0 + * check specifically - both return BUFFER_ERROR either way. */ +int test_tls_msgtype_sni_parse_size_gates(void) +{ + EXPECT_DECLS; +#if defined(HAVE_SNI) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + const char* host = "srv.example"; + byte buf[16]; + byte zeroSize[OPAQUE16_LEN] = { 0x00, 0x00 }; + word16 len; + Suites suites; + + /* An empty server_name_list is rejected as malformed. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + ExpectIntEQ(wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, host, + (word16)XSTRLEN(host)), WOLFSSL_SUCCESS); + + len = build_ext_with_body(buf, TLSX_SERVER_NAME, zeroSize, + (word16)sizeof(zeroSize)); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +#if defined(HAVE_SNI) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +/* SNI receive callback used to force cacheOnly in TLSX_SNI_Parse() when no + * SNI has been configured on the SSL object. */ +static int sni_recv_cb(WOLFSSL* ssl, int* ret, void* arg) +{ + (void)ssl; (void)ret; (void)arg; + return 0; +} +#endif + +/* ---- TLSX_SNI_Parse(): forced-keep (cacheOnly) path -------------------- */ +/* if (!cacheOnly && !checkPublic && !(sni = TLSX_SNI_Find(...))) + * return 0; + * matched = cacheOnly || (...); + * if (matched || ...) { ... } */ +int test_tls_msgtype_sni_parse_cacheonly(void) +{ + EXPECT_DECLS; +#if defined(HAVE_SNI) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + !defined(WOLFSSL_NO_TLS12) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + TLSX* extension = NULL; + const char* host = "example.com"; + byte sniBody[24]; + byte buf[32]; + word16 sniLen, len; + Suites suites; + + /* extension found (wolfSSL_UseSNI was called) but its data was + * cleared, and a servername callback is registered: cacheOnly is + * forced on, so the extension is silently kept without a real match + * even though the type wasn't actually configured any more. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + wolfSSL_CTX_set_servername_callback(ctx, sni_recv_cb); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, host, + (word16)XSTRLEN(host)), WOLFSSL_SUCCESS); + ExpectNotNull(extension = TLSX_Find(ssl->extensions, TLSX_SERVER_NAME)); + if (extension != NULL) + extension->data = NULL; + + sniLen = build_sni_body(sniBody, "test.example"); + len = build_ext_with_body(buf, TLSX_SERVER_NAME, sniBody, sniLen); + XMEMSET(&suites, 0, sizeof(suites)); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, client_hello, &suites), 0); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* ---- TLSX_SNI_Parse(): real match / mismatch, options ------------------ */ +/* if (!cacheOnly && !checkPublic && !(sni = TLSX_SNI_Find(...))) return 0; + * - TLSX_SNI_New() only ever constructs an SNI object of type + * WOLFSSL_SNI_HOST_NAME (any other type hits its "invalid type" + * branch and fails), and the incoming record's type is rejected + * earlier in this function unless it is also WOLFSSL_SNI_HOST_NAME. + * So whenever this line is reached with cacheOnly and checkPublic + * both false (i.e. extension->data was non-NULL to begin with), + * TLSX_SNI_Find() is guaranteed to find that single entry, so this + * "not using this type of SNI" return is not reachable that way. + * if (!cacheOnly && sni != NULL && sni->status != WOLFSSL_SNI_NO_MATCH) + * return 0; + * matched = cacheOnly || (hostName != NULL && XSTRLEN(hostName) == size && + * XSTRNCMP(hostName, ..., size) == 0); + * if (!matched && checkPublic) return 0; + * if (matched || (sni != NULL && (sni->options & ANSWER_ON_MISMATCH))) {...} + * else if ((sni == NULL) || !(sni->options & CONTINUE_ON_MISMATCH)) { + * ... return UNKNOWN_SNI_HOST_NAME_E; } */ +int test_tls_msgtype_sni_parse_match(void) +{ + EXPECT_DECLS; +#if defined(HAVE_SNI) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + !defined(NO_WOLFSSL_SERVER) && defined(WOLFSSL_TLS13) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte sniBody[24]; + byte buf[32]; + word16 sniLen, len; + Suites suites; + const char* configured = "a.example"; + + /* M1: configured host matches the ClientHello's host exactly. sni is + * found (not the mismatched-type case below), its status starts at + * WOLFSSL_SNI_NO_MATCH, and every hostName comparison operand is true, + * so the extension is installed and the response is queued. Reusing + * this ssl for a second, identical parse also exercises the "already + * resolved" skip: the second call's sni->status is no longer + * WOLFSSL_SNI_NO_MATCH, so it returns immediately. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, configured, + (word16)XSTRLEN(configured)), WOLFSSL_SUCCESS); + XMEMSET(&suites, 0, sizeof(suites)); + sniLen = build_sni_body(sniBody, configured); + len = build_ext_with_body(buf, TLSX_SERVER_NAME, sniBody, sniLen); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, client_hello, &suites), 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, client_hello, &suites), 0); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* M2: configured host does not match, and differs in length, so + * XSTRLEN(hostName) == size is false (masking XSTRNCMP). No mismatch + * options are set, so the handshake is aborted. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, configured, + (word16)XSTRLEN(configured)), WOLFSSL_SUCCESS); + XMEMSET(&suites, 0, sizeof(suites)); + sniLen = build_sni_body(sniBody, "bb.example2"); + len = build_ext_with_body(buf, TLSX_SERVER_NAME, sniBody, sniLen); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(UNKNOWN_SNI_HOST_NAME_E)); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* M3: configured host does not match, but is the same length, so + * XSTRLEN(hostName) == size is true and XSTRNCMP(...) == 0 is false. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, configured, + (word16)XSTRLEN(configured)), WOLFSSL_SUCCESS); + XMEMSET(&suites, 0, sizeof(suites)); + sniLen = build_sni_body(sniBody, "b.example"); + len = build_ext_with_body(buf, TLSX_SERVER_NAME, sniBody, sniLen); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(UNKNOWN_SNI_HOST_NAME_E)); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* M4: mismatch, but WOLFSSL_SNI_ANSWER_ON_MISMATCH is set on the + * configured name - the handshake proceeds with a fake match instead + * of aborting. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, configured, + (word16)XSTRLEN(configured)), WOLFSSL_SUCCESS); + wolfSSL_SNI_SetOptions(ssl, WOLFSSL_SNI_HOST_NAME, + WOLFSSL_SNI_ANSWER_ON_MISMATCH); + XMEMSET(&suites, 0, sizeof(suites)); + sniLen = build_sni_body(sniBody, "bb.example2"); + len = build_ext_with_body(buf, TLSX_SERVER_NAME, sniBody, sniLen); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, client_hello, &suites), 0); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* M5: mismatch, but WOLFSSL_SNI_CONTINUE_ON_MISMATCH is set - the + * handshake continues without installing a response or aborting. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, configured, + (word16)XSTRLEN(configured)), WOLFSSL_SUCCESS); + wolfSSL_SNI_SetOptions(ssl, WOLFSSL_SNI_HOST_NAME, + WOLFSSL_SNI_CONTINUE_ON_MISMATCH); + XMEMSET(&suites, 0, sizeof(suites)); + sniLen = build_sni_body(sniBody, "bb.example2"); + len = build_ext_with_body(buf, TLSX_SERVER_NAME, sniBody, sniLen); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, client_hello, &suites), 0); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + +#endif + return EXPECT_RESULT(); +} + +/* ---- TLSX_SNI_Parse(): outer SNI vs. ECH config publicName ------------- */ +/* checkPublic is only ever set when an ECH extension is already attached + * to ssl->extensions and no SNI was configured for this SSL/CTX; the outer + * SNI is then matched against every configured ECH config's publicName + * instead of a locally configured host name. + * if (XSTRLEN(workingConfig->publicName) == size && + * XSTRNCMP(workingConfig->publicName, ..., size) == 0) matched = 1; + * if (!matched && checkPublic) return 0; */ +int test_tls_msgtype_sni_parse_ech_public(void) +{ + EXPECT_DECLS; +#if defined(HAVE_SNI) && defined(WOLFSSL_TLS13) && defined(HAVE_ECH) && \ + defined(WOLFSSL_TEST_STATIC_BUILD) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + WOLFSSL_EchConfig echConfig; + WOLFSSL_ECH* ech = NULL; + char publicName[] = "pub.example"; + byte sniBody[24]; + byte buf[32]; + word16 sniLen, len; + Suites suites; + + /* Outer SNI equals the ECH config's publicName: the while loop's + * XSTRLEN/XSTRNCMP operands are both true, matched is set, and the + * response is installed onto ech->extensions instead of + * ssl->extensions. No local SNI is configured, so checkPublic is what + * drove this parse at all. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&echConfig, 0, sizeof(echConfig)); + echConfig.publicName = publicName; + ExpectNotNull(ech = (WOLFSSL_ECH*)XMALLOC(sizeof(WOLFSSL_ECH), ssl->heap, + DYNAMIC_TYPE_TMP_BUFFER)); + if (ech != NULL) { + XMEMSET(ech, 0, sizeof(WOLFSSL_ECH)); + ech->echConfig = &echConfig; + ExpectIntEQ(TLSX_Push(&ssl->extensions, TLSX_ECH, ech, ssl->heap), 0); + } + XMEMSET(&suites, 0, sizeof(suites)); + sniLen = build_sni_body(sniBody, publicName); + len = build_ext_with_body(buf, TLSX_SERVER_NAME, sniBody, sniLen); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, client_hello, &suites), 0); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* Outer SNI is the same length as the publicName but differs in + * content: XSTRLEN(...) == size is true, XSTRNCMP(...) == 0 is false, + * so the loop does not match. checkPublic then makes the mismatch a + * silent no-op instead of an alert - unlike a locally configured SNI + * mismatch, which aborts the handshake. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&echConfig, 0, sizeof(echConfig)); + echConfig.publicName = publicName; + ExpectNotNull(ech = (WOLFSSL_ECH*)XMALLOC(sizeof(WOLFSSL_ECH), ssl->heap, + DYNAMIC_TYPE_TMP_BUFFER)); + if (ech != NULL) { + XMEMSET(ech, 0, sizeof(WOLFSSL_ECH)); + ech->echConfig = &echConfig; + ExpectIntEQ(TLSX_Push(&ssl->extensions, TLSX_ECH, ech, ssl->heap), 0); + } + XMEMSET(&suites, 0, sizeof(suites)); + sniLen = build_sni_body(sniBody, "pub.examplx"); + len = build_ext_with_body(buf, TLSX_SERVER_NAME, sniBody, sniLen); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, client_hello, &suites), 0); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* ---- TLSX_PreSharedKey_Parse_ClientHello(): identity list --------------- */ +/* if (len < MIN_PSK_ID_LEN || length - idx < len) return BUFFER_E; + * ... + * if (len < OPAQUE16_LEN + identityLen + OPAQUE32_LEN || + * identityLen > MAX_PSK_ID_LEN) return BUFFER_E; */ +int test_tls_msgtype_psk_ch_id_gates(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && (defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[MAX_PSK_ID_LEN + 64]; + byte body[MAX_PSK_ID_LEN + 48]; + word16 len; + Suites suites; + + /* op0 true: identities length (5) is below MIN_PSK_ID_LEN (6). Padded + * to 4 bytes total so the earlier "room for both length fields" check + * (length - idx < 2*OPAQUE16_LEN) passes and this is the check that + * fires. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + { + byte shortLen[2 * OPAQUE16_LEN] = { 0x00, 0x05, 0x00, 0x00 }; + len = build_ext_with_body(buf, TLSX_PRE_SHARED_KEY, shortLen, + (word16)sizeof(shortLen)); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(BUFFER_E)); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* op0 false, op1 true: identities length (6) meets MIN_PSK_ID_LEN, but + * fewer than 6 bytes actually follow in the extension. (op0 false, + * op1 false is the psk_duplicate test's valid ClientHello body.) */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + { + byte truncated[4] = { 0x00, 0x06, 0x00, 0x00 }; + len = build_ext_with_body(buf, TLSX_PRE_SHARED_KEY, truncated, + (word16)sizeof(truncated)); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(BUFFER_E)); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* Per-identity op0 true: identities length (6) is consistent with the + * outer check, but the single identity inside it claims a 10-byte + * identity plus age though only 4 bytes remain for them. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + { + byte b[8] = { 0x00, 0x06, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00 }; + len = build_ext_with_body(buf, TLSX_PRE_SHARED_KEY, b, + (word16)sizeof(b)); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(BUFFER_E)); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* Per-identity op0 false, op1 true: identityLen is one more than + * MAX_PSK_ID_LEN, with enough buffer supplied to hold it in full. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + { + word16 idLen = MAX_PSK_ID_LEN + 1; + word16 identitiesLen = (word16)(OPAQUE16_LEN + idLen + OPAQUE32_LEN); + word16 idx = 0; + + body[idx++] = (byte)(identitiesLen >> 8); + body[idx++] = (byte)identitiesLen; + body[idx++] = (byte)(idLen >> 8); + body[idx++] = (byte)idLen; + XMEMSET(body + idx, 0x41, idLen); + idx = (word16)(idx + idLen); + body[idx++] = 0; body[idx++] = 0; body[idx++] = 0; body[idx++] = 0; + len = build_ext_with_body(buf, TLSX_PRE_SHARED_KEY, body, idx); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(BUFFER_E)); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +#if defined(WOLFSSL_TLS13) && (defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +/* A two-identity ClientHello pre_shared_key body: two distinct identities + * (so TLSX_PreSharedKey_Use() creates two list entries instead of + * deduplicating on identical content) and two SHA-256-sized binders. */ +static const byte psk_ch_body_two[] = { + 0x00, 0x0D, /* identities len */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* identity #1 (empty) */ + 0x00, 0x01, 0xBB, 0x00, 0x00, 0x00, 0x00, /* identity #2 (1 byte) */ + 0x00, 0x42, /* binders len = 66 */ + 0x20, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0x20, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0 +}; +#endif + +/* ---- TLSX_PreSharedKey_Parse(): server-selected identity index --------- */ +/* for (; list != NULL && idx > 0; idx--) list = list->next; */ +int test_tls_msgtype_psk_sh_index(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && (defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + TLSX* extension = NULL; + PreSharedKey* list = NULL; + byte chBuf[96]; + byte shBuf[8]; + byte idxBody[OPAQUE16_LEN]; + word16 chLen, shLen; + Suites suites; + + /* op0 true, op1 true (continue) then op0 true, op1 false (stop): two + * identities on the list, server selects index 1 (the second). */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + chLen = build_ext_with_body(chBuf, TLSX_PRE_SHARED_KEY, psk_ch_body_two, + (word16)sizeof(psk_ch_body_two)); + ExpectIntEQ(TLSX_Parse(ssl, chBuf, chLen, client_hello, &suites), 0); + ExpectNotNull(extension = TLSX_Find(ssl->extensions, TLSX_PRE_SHARED_KEY)); + if (ssl != NULL && ssl->session != NULL && ssl->ctx != NULL) + ssl->session->version = ssl->ctx->method->version; + idxBody[0] = 0x00; idxBody[1] = 0x01; /* choose index 1 */ + shLen = build_ext_with_body(shBuf, TLSX_PRE_SHARED_KEY, idxBody, + (word16)sizeof(idxBody)); + ExpectIntEQ(TLSX_Parse(ssl, shBuf, shLen, server_hello, NULL), 0); + if (extension != NULL) + list = (PreSharedKey*)extension->data; + ExpectNotNull(list); + if (list != NULL) { + ExpectIntEQ(list->chosen, 0); + ExpectNotNull(list->next); + if (list->next != NULL) + ExpectIntEQ(list->next->chosen, 1); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* op0 false: a single identity, server selects index 1 - the loop + * runs out of list (masking op1) before idx reaches 0. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + chLen = build_ext_with_body(chBuf, TLSX_PRE_SHARED_KEY, psk_ch_body, + (word16)sizeof(psk_ch_body)); + ExpectIntEQ(TLSX_Parse(ssl, chBuf, chLen, client_hello, &suites), 0); + idxBody[0] = 0x00; idxBody[1] = 0x01; /* index 1, out of range */ + shLen = build_ext_with_body(shBuf, TLSX_PRE_SHARED_KEY, idxBody, + (word16)sizeof(idxBody)); + ExpectIntEQ(TLSX_Parse(ssl, shBuf, shLen, server_hello, NULL), + WC_NO_ERR_TRACE(PSK_KEY_ERROR)); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* ---- TLSX_PreSharedKey_Parse(): resumed-session consistency ------------ */ +/* if (ssl->options.cipherSuite0 != ssl->session->cipherSuite0 || + * ssl->options.cipherSuite != ssl->session->cipherSuite || + * ssl->session->version.major != ssl->ctx->method->version.major || + * ssl->session->version.minor != ssl->ctx->method->version.minor) + * return PSK_KEY_ERROR; */ +int test_tls_msgtype_psk_sh_resumption(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && (defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte chBuf[64]; + byte shBuf[8]; + static const byte idxBody[OPAQUE16_LEN] = { 0x00, 0x00 }; + word16 chLen, shLen; + Suites suites; + + shLen = build_ext_with_body(shBuf, TLSX_PRE_SHARED_KEY, idxBody, + (word16)sizeof(idxBody)); + + /* Baseline: cipherSuite0/cipherSuite/version all match - success. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + chLen = build_ext_with_body(chBuf, TLSX_PRE_SHARED_KEY, psk_ch_body, + (word16)sizeof(psk_ch_body)); + ExpectIntEQ(TLSX_Parse(ssl, chBuf, chLen, client_hello, &suites), 0); + if (ssl != NULL && ssl->session != NULL && ssl->ctx != NULL) + ssl->session->version = ssl->ctx->method->version; + ExpectIntEQ(TLSX_Parse(ssl, shBuf, shLen, server_hello, NULL), 0); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* op0 true: cipherSuite0 mismatch. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + chLen = build_ext_with_body(chBuf, TLSX_PRE_SHARED_KEY, psk_ch_body, + (word16)sizeof(psk_ch_body)); + ExpectIntEQ(TLSX_Parse(ssl, chBuf, chLen, client_hello, &suites), 0); + if (ssl != NULL && ssl->session != NULL && ssl->ctx != NULL) { + ssl->session->version = ssl->ctx->method->version; + ssl->session->cipherSuite0 = 1; + } + ExpectIntEQ(TLSX_Parse(ssl, shBuf, shLen, server_hello, NULL), + WC_NO_ERR_TRACE(PSK_KEY_ERROR)); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* op1 true: cipherSuite mismatch. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + chLen = build_ext_with_body(chBuf, TLSX_PRE_SHARED_KEY, psk_ch_body, + (word16)sizeof(psk_ch_body)); + ExpectIntEQ(TLSX_Parse(ssl, chBuf, chLen, client_hello, &suites), 0); + if (ssl != NULL && ssl->session != NULL && ssl->ctx != NULL) { + ssl->session->version = ssl->ctx->method->version; + ssl->session->cipherSuite = 1; + } + ExpectIntEQ(TLSX_Parse(ssl, shBuf, shLen, server_hello, NULL), + WC_NO_ERR_TRACE(PSK_KEY_ERROR)); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* op2 true: session version.major mismatch. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + chLen = build_ext_with_body(chBuf, TLSX_PRE_SHARED_KEY, psk_ch_body, + (word16)sizeof(psk_ch_body)); + ExpectIntEQ(TLSX_Parse(ssl, chBuf, chLen, client_hello, &suites), 0); + if (ssl != NULL && ssl->session != NULL && ssl->ctx != NULL) { + ssl->session->version = ssl->ctx->method->version; + ssl->session->version.major = (byte)(ssl->session->version.major + 1); + } + ExpectIntEQ(TLSX_Parse(ssl, shBuf, shLen, server_hello, NULL), + WC_NO_ERR_TRACE(PSK_KEY_ERROR)); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* op3 true: session version.minor mismatch. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + chLen = build_ext_with_body(chBuf, TLSX_PRE_SHARED_KEY, psk_ch_body, + (word16)sizeof(psk_ch_body)); + ExpectIntEQ(TLSX_Parse(ssl, chBuf, chLen, client_hello, &suites), 0); + if (ssl != NULL && ssl->session != NULL && ssl->ctx != NULL) { + ssl->session->version = ssl->ctx->method->version; + ssl->session->version.minor = (byte)(ssl->session->version.minor + 1); + } + ExpectIntEQ(TLSX_Parse(ssl, shBuf, shLen, server_hello, NULL), + WC_NO_ERR_TRACE(PSK_KEY_ERROR)); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* ---- TLSX_PreSharedKey_Parse_ClientHello(): binder list ----------------- */ +/* if (len < MIN_PSK_BINDERS_LEN || length - idx < len) return BUFFER_E; + * while (list != NULL && len > 0) { + * if (list->binderLen < WC_SHA256_DIGEST_SIZE || + * list->binderLen > WC_MAX_DIGEST_SIZE) return BUFFER_E; + * ... + * } + * if (list != NULL || len != 0) return BUFFER_E; */ +int test_tls_msgtype_psk_ch_binder_gates(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && (defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[128]; + byte body[128]; + word16 len, idx; + + /* op0 true: binders length (10) is below MIN_PSK_BINDERS_LEN (33). One + * valid identity precedes it so the identity list itself is accepted. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + { + Suites suites; + static const byte shortBinders[] = { + 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 1 identity */ + 0x00, 0x0A, /* binders len = 10 */ + 0,0,0,0,0,0,0,0,0,0 /* 10 filler bytes */ + }; + XMEMSET(&suites, 0, sizeof(suites)); + len = build_ext_with_body(buf, TLSX_PRE_SHARED_KEY, shortBinders, + (word16)sizeof(shortBinders)); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(BUFFER_E)); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* op0 false, op1 true: binders length (33) meets MIN_PSK_BINDERS_LEN, + * but far fewer bytes actually remain in the extension. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + { + Suites suites; + static const byte truncatedBinders[] = { + 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 1 identity */ + 0x00, 0x21, /* binders len = 33 */ + 0,0,0,0,0 /* only 5 remain */ + }; + XMEMSET(&suites, 0, sizeof(suites)); + len = build_ext_with_body(buf, TLSX_PRE_SHARED_KEY, truncatedBinders, + (word16)sizeof(truncatedBinders)); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(BUFFER_E)); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* Per-binder op0 true: the binder length byte (10) is below + * WC_SHA256_DIGEST_SIZE (32). The declared binders length (34) still + * needs to cover the length byte plus filler so the outer binders- + * length gate passes and this check is the one that fires. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + { + Suites suites; + + idx = 0; + body[idx++] = 0x00; body[idx++] = 0x06; /* identities len */ + body[idx++] = 0x00; body[idx++] = 0x00; /* identityLen = 0 */ + body[idx++] = 0; body[idx++] = 0; body[idx++] = 0; body[idx++] = 0; + body[idx++] = 0x00; body[idx++] = 0x22; /* binders len = 34 */ + body[idx++] = 10; /* binderLen = 10 (< 32) */ + XMEMSET(body + idx, 0, 33); + idx = (word16)(idx + 33); + XMEMSET(&suites, 0, sizeof(suites)); + len = build_ext_with_body(buf, TLSX_PRE_SHARED_KEY, body, idx); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(BUFFER_E)); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* Per-binder op1 true (op0 false): the binder length byte (100) is + * above WC_MAX_DIGEST_SIZE (64). The declared binders length (34) only + * needs to cover the length byte itself plus filler - the check fires + * before any binder bytes are read. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + { + Suites suites; + + idx = 0; + body[idx++] = 0x00; body[idx++] = 0x06; /* identities len */ + body[idx++] = 0x00; body[idx++] = 0x00; /* identityLen = 0 */ + body[idx++] = 0; body[idx++] = 0; body[idx++] = 0; body[idx++] = 0; + body[idx++] = 0x00; body[idx++] = 0x22; /* binders len = 34 */ + body[idx++] = 100; /* binderLen = 100 (> 64) */ + XMEMSET(body + idx, 0, 33); + idx = (word16)(idx + 33); + XMEMSET(&suites, 0, sizeof(suites)); + len = build_ext_with_body(buf, TLSX_PRE_SHARED_KEY, body, idx); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(BUFFER_E)); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* while(list!=NULL && len>0) op1 (len>0), and the trailing "list != + * NULL" gate: two identities, but only one binder - after consuming + * it, len reaches 0 while list still points at the second identity. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + { + Suites suites; + static const byte twoIdOneBinder[] = { + 0x00, 0x0D, /* identities len = 13 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* identity #1, age = 0 */ + 0x00, 0x01, 0xBB, 0x00, 0x00, 0x00, 0x00, /* identity #2 (1 byte), age = 0 */ + 0x00, 0x21, /* binders len = 33 */ + 0x20, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0 + }; + XMEMSET(&suites, 0, sizeof(suites)); + len = build_ext_with_body(buf, TLSX_PRE_SHARED_KEY, twoIdOneBinder, + (word16)sizeof(twoIdOneBinder)); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(BUFFER_E)); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* Trailing "len != 0" gate: one identity but two binders' worth of + * data - list runs out (becomes NULL) while len still has a second + * binder's length left over. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + { + Suites suites; + static const byte oneIdTwoBinders[] = { + 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 1 identity */ + 0x00, 0x42, /* binders len = 66 */ + 0x20, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0x20, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0 + }; + XMEMSET(&suites, 0, sizeof(suites)); + len = build_ext_with_body(buf, TLSX_PRE_SHARED_KEY, oneIdTwoBinders, + (word16)sizeof(oneIdTwoBinders)); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(BUFFER_E)); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* ---- TLSX_Cookie_Parse() ------------------------------------------------ */ +/* This function is only ever reached through TLSX_Parse()'s own extension + * dispatch (src/tls.c, the TLSX_COOKIE case), which already requires + * IsAtLeastTLSv1_3(ssl->version) and msgType being client_hello or + * hello_retry_request before calling it - both are argued as exclusions in + * the campaign report rather than tested here: + * if (msgType != client_hello && msgType != hello_retry_request) {...} + * - the caller's identical check makes this always false. + * if (ssl->options.dtls && IsAtLeastTLSv1_3(ssl->version)) + * - the second operand is always true here for the same reason; + * only the dtls operand can vary. + * + * if (cookie->len != len || XMEMCMP(cookie->data, input + idx, len) != 0) { + * ... return HRR_COOKIE_ERROR; } */ +int test_tls_msgtype_cookie_parse_gates(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[24]; + byte body[16]; + word16 len; + Suites suites; + +#if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_DTLS) + /* dtls operand true: no Cookie extension configured yet, and the SSL + * object is DTLS 1.3 - the cookie is accepted and stored rather than + * rejected with HRR_COOKIE_ERROR. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + body[0] = 0x00; body[1] = 0x04; body[2] = 1; body[3] = 2; body[4] = 3; + body[5] = 4; + len = build_ext_with_body(buf, TLSX_COOKIE, body, 6); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, client_hello, &suites), 0); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + + /* dtls operand false: same setup, but a plain (non-DTLS) TLS 1.3 + * client - HRR_COOKIE_ERROR because no HelloRetryRequest was sent. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + body[0] = 0x00; body[1] = 0x04; body[2] = 1; body[3] = 2; body[4] = 3; + body[5] = 4; + len = build_ext_with_body(buf, TLSX_COOKIE, body, 6); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(HRR_COOKIE_ERROR)); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + +#if defined(WOLFSSL_TEST_STATIC_BUILD) + /* An existing Cookie extension (as if this SSL object had already + * sent a HelloRetryRequest cookie) is compared against a second + * ClientHello's cookie. */ + { + static const byte seedCookie[4] = { 1, 2, 3, 4 }; + + /* op0 true: the echoed cookie's length does not match. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(TLSX_Cookie_Use(ssl, seedCookie, sizeof(seedCookie), + NULL, 0, 1, &ssl->extensions), 0); + XMEMSET(&suites, 0, sizeof(suites)); + body[0] = 0x00; body[1] = 0x02; body[2] = 1; body[3] = 2; + len = build_ext_with_body(buf, TLSX_COOKIE, body, 4); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, client_hello, &suites), + WC_NO_ERR_TRACE(HRR_COOKIE_ERROR)); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* op0 false, op1 false: length and content both match - the + * cookie is accepted and the request-seen flag is cleared. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(TLSX_Cookie_Use(ssl, seedCookie, sizeof(seedCookie), + NULL, 0, 1, &ssl->extensions), 0); + XMEMSET(&suites, 0, sizeof(suites)); + body[0] = 0x00; body[1] = 0x04; + body[2] = 1; body[3] = 2; body[4] = 3; body[5] = 4; + len = build_ext_with_body(buf, TLSX_COOKIE, body, 6); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, client_hello, &suites), 0); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + } +#endif /* WOLFSSL_TEST_STATIC_BUILD */ +#endif + return EXPECT_RESULT(); +} + +/* ---- TLSX_TCA_Parse(): gates -------------------------------------------- */ +/* !isRequest branch: if (!extension || !extension->data) + * return TLSX_HandleUnsupportedExtension(ssl); + * server branch: if (!extension || !extension->data) return 0; + * X509_NAME branch: if ((offset > length) || (idSz > length - offset)) + * return BUFFER_ERROR; + * - offset > length is unreachable here: the immediately preceding + * check (offset + OPAQUE16_LEN > length) guarantees offset <= length + * after the OPAQUE16_LEN advance, so this operand never pairs + * (excluded in the campaign report, not tested here). */ +int test_tls_msgtype_tca_parse_gates(void) +{ + EXPECT_DECLS; +#if defined(HAVE_TRUSTED_CA) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_TLS) && \ + !defined(WOLFSSL_NO_TLS12) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + TLSX* extension = NULL; + const byte id[] = { 1, 2, 3, 4 }; + byte buf[16]; + word16 len; + + /* 3131 op0 true: no TCA configured at all - the client must treat an + * unsolicited response as unsupported. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + len = build_ext(buf, TLSX_TRUSTED_CA_KEYS, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, server_hello, NULL), + WC_NO_ERR_TRACE(UNSUPPORTED_EXTENSION)); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* 3131 op1 true: TCA configured, but its data was cleared - same + * outcome via the other operand. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_UseTrustedCA(ssl, WOLFSSL_TRUSTED_CA_X509_NAME, + id, (word32)sizeof(id)), WOLFSSL_SUCCESS); + ExpectNotNull(extension = TLSX_Find(ssl->extensions, + TLSX_TRUSTED_CA_KEYS)); + if (extension != NULL) + extension->data = NULL; + len = build_ext(buf, TLSX_TRUSTED_CA_KEYS, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, server_hello, NULL), + WC_NO_ERR_TRACE(UNSUPPORTED_EXTENSION)); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* 3131 both false: TCA configured normally, empty response body - + * accepted, response flag set. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_UseTrustedCA(ssl, WOLFSSL_TRUSTED_CA_X509_NAME, + id, (word32)sizeof(id)), WOLFSSL_SUCCESS); + len = build_ext(buf, TLSX_TRUSTED_CA_KEYS, 0); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, server_hello, NULL), 0); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* 3145 op1 true: server side, TCA configured but its data was + * cleared - "not enabled at server side" is taken, not a crash. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_UseTrustedCA(ssl, WOLFSSL_TRUSTED_CA_X509_NAME, + id, (word32)sizeof(id)), WOLFSSL_SUCCESS); + ExpectNotNull(extension = TLSX_Find(ssl->extensions, + TLSX_TRUSTED_CA_KEYS)); + if (extension != NULL) + extension->data = NULL; + /* A ClientHello TCA extension must be at least WOLFSSL_TCA_MIN_SIZE_CLIENT + * bytes to pass TLSX_Parse()'s own minimum-size gate; the body content + * is irrelevant here since extension->data == NULL returns before the + * body is ever read. */ + len = build_ext(buf, TLSX_TRUSTED_CA_KEYS, WOLFSSL_TCA_MIN_SIZE_CLIENT); + { + Suites suites; + XMEMSET(&suites, 0, sizeof(suites)); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, client_hello, &suites), 0); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* ---- TLSX_TCA_Find() ----------------------------------------------------- */ +/* if (tca->type == type && idSz == tca->idSz && + * XMEMCMP(id, tca->id, idSz) == 0) break; */ +int test_tls_msgtype_tca_find(void) +{ + EXPECT_DECLS; +#if defined(HAVE_TRUSTED_CA) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_TLS) && !defined(NO_SHA) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[24]; + byte body[16]; + word16 len, entryLen; + Suites suites; + const byte sha1Id[WC_SHA_DIGEST_SIZE] = { + 0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11, + 0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11 + }; + const byte idA[] = { 'A','A','A','A' }; + const byte idB[] = { 'B','B','B','B' }; + const byte idAX[] = { 'A','A','A','A','X' }; + + /* op0 false: the configured entry is CERT_SHA1, the query is + * X509_NAME - tca->type != type on the only list entry. Each body is + * a 2-byte list length followed by one type + idSz + id entry. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_UseTrustedCA(ssl, WOLFSSL_TRUSTED_CA_CERT_SHA1, + sha1Id, sizeof(sha1Id)), WOLFSSL_SUCCESS); + XMEMSET(&suites, 0, sizeof(suites)); + entryLen = (word16)(1 + OPAQUE16_LEN + sizeof(idA)); + body[0] = (byte)(entryLen >> 8); body[1] = (byte)entryLen; + body[2] = WOLFSSL_TRUSTED_CA_X509_NAME; + body[3] = 0x00; body[4] = (byte)sizeof(idA); + XMEMCPY(body + 5, idA, sizeof(idA)); + len = build_ext_with_body(buf, TLSX_TRUSTED_CA_KEYS, body, + (word16)(2 + entryLen)); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, client_hello, &suites), 0); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* op0 true, op1 false: type matches, but the query's idSz (5) + * differs from the configured entry's idSz (4). */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_UseTrustedCA(ssl, WOLFSSL_TRUSTED_CA_X509_NAME, + idA, (word32)sizeof(idA)), WOLFSSL_SUCCESS); + XMEMSET(&suites, 0, sizeof(suites)); + entryLen = (word16)(1 + OPAQUE16_LEN + sizeof(idAX)); + body[0] = (byte)(entryLen >> 8); body[1] = (byte)entryLen; + body[2] = WOLFSSL_TRUSTED_CA_X509_NAME; + body[3] = 0x00; body[4] = (byte)sizeof(idAX); + XMEMCPY(body + 5, idAX, sizeof(idAX)); + len = build_ext_with_body(buf, TLSX_TRUSTED_CA_KEYS, body, + (word16)(2 + entryLen)); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, client_hello, &suites), 0); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* op0 true, op1 true, op2 false: type and length match, content + * does not. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_UseTrustedCA(ssl, WOLFSSL_TRUSTED_CA_X509_NAME, + idA, (word32)sizeof(idA)), WOLFSSL_SUCCESS); + XMEMSET(&suites, 0, sizeof(suites)); + entryLen = (word16)(1 + OPAQUE16_LEN + sizeof(idB)); + body[0] = (byte)(entryLen >> 8); body[1] = (byte)entryLen; + body[2] = WOLFSSL_TRUSTED_CA_X509_NAME; + body[3] = 0x00; body[4] = (byte)sizeof(idB); + XMEMCPY(body + 5, idB, sizeof(idB)); + len = build_ext_with_body(buf, TLSX_TRUSTED_CA_KEYS, body, + (word16)(2 + entryLen)); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, client_hello, &suites), 0); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* op0 true, op1 true, op2 true: exact match - found on the first + * (only) list entry. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_UseTrustedCA(ssl, WOLFSSL_TRUSTED_CA_X509_NAME, + idA, (word32)sizeof(idA)), WOLFSSL_SUCCESS); + XMEMSET(&suites, 0, sizeof(suites)); + entryLen = (word16)(1 + OPAQUE16_LEN + sizeof(idA)); + body[0] = (byte)(entryLen >> 8); body[1] = (byte)entryLen; + body[2] = WOLFSSL_TRUSTED_CA_X509_NAME; + body[3] = 0x00; body[4] = (byte)sizeof(idA); + XMEMCPY(body + 5, idA, sizeof(idA)); + len = build_ext_with_body(buf, TLSX_TRUSTED_CA_KEYS, body, + (word16)(2 + entryLen)); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, client_hello, &suites), 0); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +#if defined(HAVE_TRUSTED_CA) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +/* A small counting allocator used to force a single, targeted malloc + * failure inside TLSX_TCA_New(). Installed narrowly around the call under + * test and restored immediately after. */ +static int tca_fail_after = -1; +static int tca_alloc_seen = 0; + +static void* tca_fail_malloc(size_t size) +{ + if (tca_fail_after >= 0) { + if (tca_alloc_seen == tca_fail_after) { + tca_alloc_seen++; + return NULL; + } + tca_alloc_seen++; + } + return malloc(size); +} + +static void tca_fail_free(void* ptr) +{ + free(ptr); +} + +static void* tca_fail_realloc(void* ptr, size_t size) +{ + return realloc(ptr, size); +} +#endif /* HAVE_TRUSTED_CA && !NO_WOLFSSL_CLIENT && !NO_TLS */ + +/* ---- TLSX_TCA_New(): id allocation failure ------------------------------ */ +/* KEY_SHA1/CERT_SHA1: if (idSz == WC_SHA_DIGEST_SIZE && + * (tca->id = XMALLOC(idSz, ...))) {...} + * X509_NAME: if (idSz > 0 && + * (tca->id = XMALLOC(idSz, ...))) {...} + * In both cases the length operand's pair is already covered elsewhere; + * only the allocation succeeding vs. failing is exercised here. */ +int test_tls_msgtype_tca_new_alloc(void) +{ + EXPECT_DECLS; +#if defined(HAVE_TRUSTED_CA) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + !defined(NO_SHA) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + wolfSSL_Malloc_cb prevM = NULL; + wolfSSL_Free_cb prevF = NULL; + wolfSSL_Realloc_cb prevR = NULL; + const byte sha1Id[WC_SHA_DIGEST_SIZE] = { + 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22, + 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22 + }; + const byte nameId[] = { 5, 6, 7, 8 }; + + /* CERT_SHA1: the TCA struct itself (allocation #0) succeeds, the id + * buffer (allocation #1) fails. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_GetAllocators(&prevM, &prevF, &prevR), 0); + ExpectIntEQ(wolfSSL_SetAllocators(tca_fail_malloc, tca_fail_free, + tca_fail_realloc), 0); + tca_alloc_seen = 0; + tca_fail_after = 1; + ExpectIntEQ(wolfSSL_UseTrustedCA(ssl, WOLFSSL_TRUSTED_CA_CERT_SHA1, + sha1Id, sizeof(sha1Id)), WC_NO_ERR_TRACE(MEMORY_E)); + tca_fail_after = -1; + (void)wolfSSL_SetAllocators(prevM, prevF, prevR); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* X509_NAME: same shape - struct allocation succeeds, id buffer + * allocation fails. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_GetAllocators(&prevM, &prevF, &prevR), 0); + ExpectIntEQ(wolfSSL_SetAllocators(tca_fail_malloc, tca_fail_free, + tca_fail_realloc), 0); + tca_alloc_seen = 0; + tca_fail_after = 1; + ExpectIntEQ(wolfSSL_UseTrustedCA(ssl, WOLFSSL_TRUSTED_CA_X509_NAME, + nameId, (word32)sizeof(nameId)), WC_NO_ERR_TRACE(MEMORY_E)); + tca_fail_after = -1; + (void)wolfSSL_SetAllocators(prevM, prevF, prevR); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* ---- TLSX_PreSharedKey_Write(): server's chosen identity ---------------- */ +/* for (i=0; list != NULL && !list->chosen; i++) list = list->next; */ +int test_tls_msgtype_psk_write_chosen(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && (defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(WOLFSSL_TEST_STATIC_BUILD) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + TLSX* extension = NULL; + PreSharedKey* pskA = NULL; + PreSharedKey* pskB = NULL; + byte identityA[] = { 0xAA }; + byte identityB[] = { 0xBB }; + byte output[32]; + word16 offset; + + /* op0 true, op1 true (continue) then op0 true, op1 false (stop): two + * identities, the second one chosen. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(TLSX_PreSharedKey_Use(&ssl->extensions, identityA, + (word16)sizeof(identityA), 0, no_mac, 0, 0, 1, &pskA, + ssl->heap), 0); + ExpectIntEQ(TLSX_PreSharedKey_Use(&ssl->extensions, identityB, + (word16)sizeof(identityB), 0, no_mac, 0, 0, 1, &pskB, + ssl->heap), 0); + if (pskB != NULL) + pskB->chosen = 1; + ExpectNotNull(extension = TLSX_Find(ssl->extensions, TLSX_PRE_SHARED_KEY)); + if (extension != NULL) + extension->resp = 1; + offset = 0; + ExpectIntEQ(TLSX_WriteResponse(ssl, output, server_hello, &offset), 0); + ExpectIntGT(offset, 0); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* op0 false: a single, unchosen identity - the loop runs off the end + * of the list before finding a chosen entry. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(TLSX_PreSharedKey_Use(&ssl->extensions, identityA, + (word16)sizeof(identityA), 0, no_mac, 0, 0, 1, &pskA, + ssl->heap), 0); + ExpectNotNull(extension = TLSX_Find(ssl->extensions, TLSX_PRE_SHARED_KEY)); + if (extension != NULL) + extension->resp = 1; + offset = 0; + ExpectIntEQ(TLSX_WriteResponse(ssl, output, server_hello, &offset), + WC_NO_ERR_TRACE(BUILD_MSG_ERROR)); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_tls_msgtype.h b/tests/api/test_tls_msgtype.h index e07153ace6..69fcc48060 100644 --- a/tests/api/test_tls_msgtype.h +++ b/tests/api/test_tls_msgtype.h @@ -55,6 +55,21 @@ int test_tls_msgtype_server_cert_type_tls13(void); int test_tls_msgtype_server_cert_type_tls12(void); int test_tls_msgtype_connection_id(void); int test_tls_msgtype_ech(void); +int test_tls_msgtype_sni_find(void); +int test_tls_msgtype_sni_parse_response_gate(void); +int test_tls_msgtype_sni_parse_size_gates(void); +int test_tls_msgtype_sni_parse_cacheonly(void); +int test_tls_msgtype_sni_parse_match(void); +int test_tls_msgtype_sni_parse_ech_public(void); +int test_tls_msgtype_psk_ch_id_gates(void); +int test_tls_msgtype_psk_ch_binder_gates(void); +int test_tls_msgtype_psk_sh_index(void); +int test_tls_msgtype_psk_sh_resumption(void); +int test_tls_msgtype_cookie_parse_gates(void); +int test_tls_msgtype_tca_parse_gates(void); +int test_tls_msgtype_tca_find(void); +int test_tls_msgtype_tca_new_alloc(void); +int test_tls_msgtype_psk_write_chosen(void); #define TEST_TLS_MSGTYPE_DECLS \ TEST_DECL_GROUP("tls", test_tls_msgtype_arg_guard), \ @@ -89,6 +104,21 @@ int test_tls_msgtype_ech(void); TEST_DECL_GROUP("tls", test_tls_msgtype_server_cert_type_tls13), \ TEST_DECL_GROUP("tls", test_tls_msgtype_server_cert_type_tls12), \ TEST_DECL_GROUP("tls", test_tls_msgtype_connection_id), \ - TEST_DECL_GROUP("tls", test_tls_msgtype_ech) + TEST_DECL_GROUP("tls", test_tls_msgtype_ech), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_sni_find), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_sni_parse_response_gate), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_sni_parse_size_gates), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_sni_parse_cacheonly), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_sni_parse_match), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_sni_parse_ech_public), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_psk_ch_id_gates), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_psk_ch_binder_gates), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_psk_sh_index), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_psk_sh_resumption), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_cookie_parse_gates), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_tca_parse_gates), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_tca_find), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_tca_new_alloc), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_psk_write_chosen) #endif /* TESTS_API_TEST_TLS_MSGTYPE_H */ From 3e047f17b2beae3a99db608be06e91768bb96f51 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 23:00:51 +0200 Subject: [PATCH 44/57] tests: cover supported groups and key-share negotiation in tls.c --- tests/api/test_tls_parse.c | 1447 ++++++++++++++++++++++++++++++++++++ tests/api/test_tls_parse.h | 12 +- 2 files changed, 1458 insertions(+), 1 deletion(-) diff --git a/tests/api/test_tls_parse.c b/tests/api/test_tls_parse.c index 26ac2a6b15..56607db5f9 100644 --- a/tests/api/test_tls_parse.c +++ b/tests/api/test_tls_parse.c @@ -26,6 +26,9 @@ #include #include +#ifndef NO_DH +#include +#endif /* Helper to build a server-side WOLFSSL_CTX with a certificate/key loaded, * as required for wolfSSL_new() to succeed on a server context. @@ -114,6 +117,66 @@ static void* tls_parse_fail_realloc(void* ptr, size_t size) } #endif /* WOLFSSL_TEST_STATIC_BUILD && !NO_TLS */ +#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(WOLFSSL_TLS13) && \ + defined(HAVE_SUPPORTED_CURVES) +/* Pushes a single-entry supported_groups restriction directly, bypassing + * TLSX_UseSupportedCurve()'s TLSX_IsGroupSupported() gate -- needed to name + * a group id this build does not itself recognise, the same as a peer's + * raw wire value would. */ +static int test_tls_parse_push_curve(TLSX** extensions, WOLFSSL* ssl, + word16 name) +{ + SupportedCurve* curve = (SupportedCurve*)XMALLOC(sizeof(SupportedCurve), + ssl->heap, DYNAMIC_TYPE_TLSX); + if (curve == NULL) + return WC_NO_ERR_TRACE(MEMORY_E); + curve->name = name; + curve->next = NULL; + return TLSX_Push(extensions, TLSX_SUPPORTED_GROUPS, curve, ssl->heap); +} + +/* Builds and pushes a minimal key share entry -- a peer offer that was + * never processed into a real key -- for tests that only need the + * bookkeeping fields (group, ke) a negotiation helper looks at. */ +static KeyShareEntry* test_tls_parse_push_kse(TLSX** extensions, WOLFSSL* ssl, + word16 group) +{ + KeyShareEntry* kse = (KeyShareEntry*)XMALLOC(sizeof(KeyShareEntry), + ssl->heap, DYNAMIC_TYPE_TLSX); + if (kse == NULL) + return NULL; + XMEMSET(kse, 0, sizeof(*kse)); + kse->group = group; + kse->ke = (byte*)XMALLOC(1, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY); + if (kse->ke == NULL) { + XFREE(kse, ssl->heap, DYNAMIC_TYPE_TLSX); + return NULL; + } + kse->ke[0] = 0xAA; + kse->keLen = 1; + if (TLSX_Push(extensions, TLSX_KEY_SHARE, kse, ssl->heap) != 0) { + XFREE(kse->ke, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY); + XFREE(kse, ssl->heap, DYNAMIC_TYPE_TLSX); + return NULL; + } + return kse; +} + +/* TLSX_KeyShare_FreeAll() is not visible outside src/tls.c even as + * WOLFSSL_LOCAL; releasing a standalone (not already list-linked) entry + * built directly for a test goes through the generic TLSX_FreeAll() + * instead, via a throwaway one-node extension list. */ +static void test_tls_parse_free_kse(WOLFSSL* ssl, KeyShareEntry* kse) +{ + TLSX* extensions = NULL; + if (kse == NULL) + return; + if (TLSX_Push(&extensions, TLSX_KEY_SHARE, kse, ssl->heap) != 0) + return; + TLSX_FreeAll(extensions, ssl->heap); +} +#endif /* WOLFSSL_TEST_STATIC_BUILD && WOLFSSL_TLS13 && HAVE_SUPPORTED_CURVES */ + /* ---- ALPN --------------------------------------------------------------- */ /* RFC 7301: covers the TLSX_APPLICATION_LAYER_PROTOCOL parse helpers that * TLSX_Parse() reaches for both the client_hello (isRequest) and @@ -1446,6 +1509,47 @@ int test_TLSX_PointFormat_parse(void) wolfSSL_CTX_free(ctxp); } #endif + +#if defined(HAVE_SUPPORTED_CURVES) && !defined(NO_TLS) && \ + !defined(NO_WOLFSSL_SERVER) && defined(WOLFSSL_TEST_STATIC_BUILD) + /* TLSX_PointFormat_ValidateResponse(): reached while sizing/writing a + * ServerHello, through the WOLFSSL_LOCAL TLSX_GetResponseSize() rather + * than TLSX_Parse() (there is no wire input on this side). A cipher + * suite whose first byte is ECDHE_PSK_BYTE takes the same "already + * covered by the peer's key exchange, do not also send point formats" + * path as ECC_BYTE/CHACHA_BYTE. */ + { + WOLFSSL_CTX* ctxr = test_tls_parse_server_ctx( + wolfTLSv1_2_server_method()); + WOLFSSL* sslr = NULL; + + ExpectNotNull(ctxr); + if (ctxr != NULL) + ExpectNotNull(sslr = wolfSSL_new(ctxr)); + if (sslr != NULL) { + word16 length = 0; + + ExpectIntEQ(TLSX_UsePointFormat(&sslr->extensions, + WOLFSSL_EC_PF_UNCOMPRESSED, sslr->heap), + WOLFSSL_SUCCESS); + { + TLSX* pf = TLSX_Find(sslr->extensions, TLSX_EC_POINT_FORMATS); + ExpectNotNull(pf); + if (pf != NULL) + pf->resp = 1; + } + sslr->options.cipherSuite0 = ECDHE_PSK_BYTE; + ExpectIntEQ(TLSX_GetResponseSize(sslr, server_hello, &length), + 0); + /* Early return leaves the suppression semaphore untouched, so + * the extension is still included (unlike a cipher suite that + * falls all the way through to the TURN_ON() at the end). */ + ExpectIntGT(length, 0); + } + wolfSSL_free(sslr); + wolfSSL_CTX_free(ctxr); + } +#endif return EXPECT_RESULT(); } @@ -1614,6 +1718,19 @@ int test_TLSX_ValidateSupportedCurves(void) wolfSSL_free(ssl); ssl = NULL; + /* first == ECDHE_PSK_BYTE: takes the same restriction-lookup branch as + * ECC_BYTE/CHACHA_BYTE. No supported_groups configured: still no + * restriction, but by way of the extension == NULL check instead of + * skipping the lookup outright. */ + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + ExpectIntEQ(TLSX_ValidateSupportedCurves(ssl, ECDHE_PSK_BYTE, 0x00, + &oid), 1); + } + wolfSSL_free(ssl); + ssl = NULL; + /* first == ECC_BYTE, with a supported_groups list offering X25519 * then X448: exercises the X25519/X448 default-case defOid reset * (second entry is not the one that set defOid). */ @@ -1654,7 +1771,1337 @@ int test_TLSX_ValidateSupportedCurves(void) 1); } wolfSSL_free(ssl); + ssl = NULL; + + /* eccTempKeySz larger than any real curve size: the "set default", + * "current" and "next" bookkeeping (all gated on eccTempKeySz <= + * octets, or == for current) never fires for any offered curve, so + * ecdhCurveOID is never resolved away from 0 -- the ephemeral-suite + * rejection at the very end is reached with *ecdhCurveOID still 0 + * (index 0), but a "default" (non-ECDHE_ECDSA/RSA) second byte never + * sets ephmSuite (index 1 false). */ + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + Suites* suites = (Suites*)WOLFSSL_SUITES(ssl); + const byte groups[] = { 0x00, 0x02, 0x00, 0x17 }; /* secp256r1 */ + ssl->eccTempKeySz = 100; + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_SUPPORTED_GROUPS, + groups, (word16)sizeof(groups)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), 0); + ExpectIntEQ(TLSX_ValidateSupportedCurves(ssl, ECC_BYTE, 0xFF, &oid), + 1); + } + wolfSSL_free(ssl); + ssl = NULL; + + /* Same eccTempKeySz, but second is a real ECDHE_ECDSA suite id: + * ephmSuite is now set (index 1 true) while *ecdhCurveOID is still 0 + * (index 0 true, same as above) -- the suite is rejected outright. */ + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + Suites* suites = (Suites*)WOLFSSL_SUITES(ssl); + const byte groups[] = { 0x00, 0x02, 0x00, 0x17 }; + ssl->eccTempKeySz = 100; + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_SUPPORTED_GROUPS, + groups, (word16)sizeof(groups)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), 0); + ExpectIntEQ(TLSX_ValidateSupportedCurves(ssl, ECC_BYTE, + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, &oid), 0); + } + wolfSSL_free(ssl); + ssl = NULL; + + /* eccTempKeySz back to the default (0) with the same real ECDHE_ECDSA + * suite id: *ecdhCurveOID does resolve away from 0 this time (via the + * "next highest strength" fallback), so the rejection at the end is + * never reached (index 0 false, index 1 held true as above). */ + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + Suites* suites = (Suites*)WOLFSSL_SUITES(ssl); + const byte groups[] = { 0x00, 0x02, 0x00, 0x17 }; + ssl->eccTempKeySz = 0; + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_SUPPORTED_GROUPS, + groups, (word16)sizeof(groups)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), 0); + ExpectIntEQ(TLSX_ValidateSupportedCurves(ssl, ECC_BYTE, + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, &oid), 1); + } + wolfSSL_free(ssl); + ssl = NULL; + + /* X25519 alone, eccTempKeySz larger than its octets: the "set default" + * assignment at the top of the loop body does not fire for this entry + * (same eccTempKeySz <= octets gate as the 5963 case above), so defOid + * is still 0 -- not equal to this entry's own (non-zero) oid -- when + * the default-case reset check runs. */ + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + Suites* suites = (Suites*)WOLFSSL_SUITES(ssl); + const byte groups[] = { 0x00, 0x02, 0x00, 0x1D }; /* X25519 */ + ssl->eccTempKeySz = 100; + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_SUPPORTED_GROUPS, + groups, (word16)sizeof(groups)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), 0); + ExpectIntEQ(TLSX_ValidateSupportedCurves(ssl, ECC_BYTE, 0xFF, &oid), + 1); + } + wolfSSL_free(ssl); + ssl = NULL; + + /* X448 alone, same oversized eccTempKeySz shape: the X448 counterpart + * of the X25519 case just above. */ + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + Suites* suites = (Suites*)WOLFSSL_SUITES(ssl); + const byte groups[] = { 0x00, 0x02, 0x00, 0x1E }; /* X448 */ + ssl->eccTempKeySz = 100; + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_SUPPORTED_GROUPS, + groups, (word16)sizeof(groups)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), 0); + ExpectIntEQ(TLSX_ValidateSupportedCurves(ssl, ECC_BYTE, 0xFF, &oid), + 1); + } + wolfSSL_free(ssl); + ssl = NULL; + + /* X448 alone: defOid gets set to X448 by the first (and only) curve + * seen, and the default-case reset then fires for that very entry + * (oid == defOid) -- the X448 counterpart of the X25519-alone case + * above. */ + if (ctx != NULL) + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + Suites* suites = (Suites*)WOLFSSL_SUITES(ssl); + const byte groups[] = { 0x00, 0x02, 0x00, 0x1E }; /* X448 */ + ssl->eccTempKeySz = 0; + extLen = test_tls_parse_build_ext(ext, sizeof(ext), TLSXT_SUPPORTED_GROUPS, + groups, (word16)sizeof(groups)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), 0); + ExpectIntEQ(TLSX_ValidateSupportedCurves(ssl, ECC_BYTE, 0xFF, &oid), + 1); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* ---- Supported Groups (RFC 8422 Section 5.1.1 / RFC 8446 4.2.7) --------- */ +/* TLSX_SupportedCurve_Parse() is dispatched to only for client_hello (and, + * on TLS 1.3, encrypted_extensions) through TLSX_Parse(); the server_hello + * direction it itself validates is never reached that way, so it is called + * directly here (WOLFSSL_LOCAL). */ +int test_TLSX_SupportedGroups_parse(void) +{ + EXPECT_DECLS; +#if defined(HAVE_SUPPORTED_CURVES) && !defined(NO_TLS) && \ + !defined(NO_WOLFSSL_CLIENT) && defined(WOLFSSL_TEST_STATIC_BUILD) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + /* secp256r1: a real, locally supported curve. */ + const byte goodBody[] = { 0x00, 0x02, 0x00, 0x17 }; + + /* server_hello direction, pre-TLS-1.3: rejected before the body is + * looked at (this build does not define WOLFSSL_ALLOW_SERVER_SC_EXT). */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + TLSX* extensions = NULL; + ExpectIntEQ(TLSX_SupportedCurve_Parse(ssl, goodBody, + (word16)sizeof(goodBody), 0, &extensions), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + ExpectNull(extensions); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* server_hello direction, TLS 1.3: the version half of the guard no + * longer applies, so parsing proceeds. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + TLSX* extensions = NULL; + ExpectIntEQ(TLSX_SupportedCurve_Parse(ssl, goodBody, + (word16)sizeof(goodBody), 0, &extensions), 0); + TLSX_FreeAll(extensions, NULL); + extensions = NULL; + + /* A single FFDHE-range group id this build has no key size table + * for (only WOLFSSL_FFDHE_2048 is compiled in): tolerated as + * BAD_FUNC_ARG from TLSX_UseSupportedCurve(), and on the response + * direction (isRequest == 0) the RFC 7919 restriction bookkeeping + * further down is skipped entirely -- it exists only for a server + * recording what a client offered. */ + { + const byte ffdheUnknown[] = { 0x00, 0x02, 0x01, 0x05 }; + ExpectIntEQ(TLSX_SupportedCurve_Parse(ssl, ffdheUnknown, + (word16)sizeof(ffdheUnknown), 0, &extensions), 0); + ExpectNull(extensions); + } + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* client_hello direction is never checked against the version; a TLS + * 1.2 connection reaches the same body parsing as above. Also the + * first, "accept whatever the peer wants" shape (no local restriction + * configured): a single well-known, supported curve is recorded. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + TLSX* extensions = NULL; + + ExpectIntEQ(TLSX_SupportedCurve_Parse(ssl, goodBody, + (word16)sizeof(goodBody), 1, &extensions), 0); + TLSX_FreeAll(extensions, NULL); + extensions = NULL; + + /* Odd total length fails the OPAQUE16_LEN modulus check (the + * length-too-short half is exercised elsewhere already). */ + { + const byte oddLen[] = { 0x00, 0x01, 0x17 }; + ExpectIntEQ(TLSX_SupportedCurve_Parse(ssl, oddLen, + (word16)sizeof(oddLen), 1, &extensions), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + ExpectNull(extensions); + } + + /* Same unknown FFDHE id, as a client_hello (isRequest == 1): + * recorded as an explicit (non-empty) restriction so DHE suite + * selection still sees the offer. */ + { + const byte ffdheUnknown[] = { 0x00, 0x02, 0x01, 0x05 }; + ExpectIntEQ(TLSX_SupportedCurve_Parse(ssl, ffdheUnknown, + (word16)sizeof(ffdheUnknown), 1, &extensions), 0); + ExpectNotNull(extensions); + } + TLSX_FreeAll(extensions, NULL); + extensions = NULL; + + /* A single group id above the FFDHE range entirely: also + * BAD_FUNC_ARG, but not FFDHE, so no restriction is recorded for + * it specifically -- yet since it is the only (unsupported) group + * offered, the list is still empty afterwards and the "record an + * empty restriction" fallback fires. */ + { + const byte aboveFfdhe[] = { 0x00, 0x02, 0x02, 0x58 }; + ExpectIntEQ(TLSX_SupportedCurve_Parse(ssl, aboveFfdhe, + (word16)sizeof(aboveFfdhe), 1, &extensions), 0); + ExpectNotNull(extensions); + } + TLSX_FreeAll(extensions, NULL); + extensions = NULL; + +#ifdef WOLFSSL_TEST_STATIC_BUILD + /* First entry is a real, supported curve, but its allocation is + * forced to fail: a distinct error (neither WOLFSSL_SUCCESS nor + * BAD_FUNC_ARG) that aborts the scan immediately. */ + { + wolfSSL_Malloc_cb prevM = NULL; + wolfSSL_Free_cb prevF = NULL; + wolfSSL_Realloc_cb prevR = NULL; + + ExpectIntEQ(wolfSSL_GetAllocators(&prevM, &prevF, &prevR), 0); + ExpectIntEQ(wolfSSL_SetAllocators(tls_parse_fail_malloc, + tls_parse_fail_free, tls_parse_fail_realloc), 0); + tls_parse_alloc_seen = 0; + tls_parse_fail_after = 0; + + ExpectIntEQ(TLSX_SupportedCurve_Parse(ssl, goodBody, + (word16)sizeof(goodBody), 1, &extensions), + WC_NO_ERR_TRACE(MEMORY_E)); + + tls_parse_fail_after = -1; + (void)wolfSSL_SetAllocators(prevM, prevF, prevR); + ExpectNull(extensions); + } +#endif + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* Second shape: a local restriction is already configured (as if from + * wolfSSL_CTX_set1_groups_list()), so parsing intersects the peer's + * list against it instead of accepting it outright. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + TLSX* extensions = NULL; + const byte noMatch261[] = { 0x00, 0x02, 0x01, 0x05 }; + const byte noMatch100[] = { 0x00, 0x02, 0x00, 0x64 }; + const byte noMatch600[] = { 0x00, 0x02, 0x02, 0x58 }; + const byte noMatch256[] = { 0x00, 0x02, 0x01, 0x00 }; + + /* client_hello, offered group in the FFDHE range but not one this + * build knows: no match against the local restriction, but the + * restriction bookkeeping records it anyway. */ + ExpectIntEQ(TLSX_UseSupportedCurve(&extensions, + WOLFSSL_ECC_SECP256R1, ssl->heap, ssl->options.side), + WOLFSSL_SUCCESS); + ExpectIntEQ(TLSX_SupportedCurve_Parse(ssl, noMatch261, + (word16)sizeof(noMatch261), 1, &extensions), 0); + TLSX_FreeAll(extensions, NULL); + extensions = NULL; + + /* client_hello, offered group below the FFDHE range: not FFDHE, + * no match, nothing recorded for it -- no common curve, and this + * connection is not TLS 1.3, so that is a hard error. */ + ExpectIntEQ(TLSX_UseSupportedCurve(&extensions, + WOLFSSL_ECC_SECP256R1, ssl->heap, ssl->options.side), + WOLFSSL_SUCCESS); + ExpectIntEQ(TLSX_SupportedCurve_Parse(ssl, noMatch100, + (word16)sizeof(noMatch100), 1, &extensions), + WC_NO_ERR_TRACE(ECC_CURVE_ERROR)); + TLSX_FreeAll(extensions, NULL); + extensions = NULL; + + /* client_hello, offered group above the FFDHE range: same as + * below-range, not FFDHE. */ + ExpectIntEQ(TLSX_UseSupportedCurve(&extensions, + WOLFSSL_ECC_SECP256R1, ssl->heap, ssl->options.side), + WOLFSSL_SUCCESS); + ExpectIntEQ(TLSX_SupportedCurve_Parse(ssl, noMatch600, + (word16)sizeof(noMatch600), 1, &extensions), + WC_NO_ERR_TRACE(ECC_CURVE_ERROR)); + TLSX_FreeAll(extensions, NULL); + extensions = NULL; + + /* client_hello, offered group is WOLFSSL_FFDHE_2048 (0x0100): in + * range, but this build does support it, so the "unsupported + * FFDHE codepoint" restriction bookkeeping does not apply to it + * either -- it just is not in the local restriction's curve list. */ + ExpectIntEQ(TLSX_UseSupportedCurve(&extensions, + WOLFSSL_ECC_SECP256R1, ssl->heap, ssl->options.side), + WOLFSSL_SUCCESS); + ExpectIntEQ(TLSX_SupportedCurve_Parse(ssl, noMatch256, + (word16)sizeof(noMatch256), 1, &extensions), + WC_NO_ERR_TRACE(ECC_CURVE_ERROR)); + TLSX_FreeAll(extensions, NULL); + extensions = NULL; + +#ifdef WOLFSSL_TEST_STATIC_BUILD + /* Offered group matches the local restriction, but recording it + * in the intersection list is forced to fail: a non-zero ret + * reaches the "no common curve" check below without commonCurves + * ever becoming non-NULL through the normal path. */ + { + wolfSSL_Malloc_cb prevM = NULL; + wolfSSL_Free_cb prevF = NULL; + wolfSSL_Realloc_cb prevR = NULL; + + ExpectIntEQ(TLSX_UseSupportedCurve(&extensions, + WOLFSSL_ECC_SECP256R1, ssl->heap, + ssl->options.side), WOLFSSL_SUCCESS); + + ExpectIntEQ(wolfSSL_GetAllocators(&prevM, &prevF, &prevR), 0); + ExpectIntEQ(wolfSSL_SetAllocators(tls_parse_fail_malloc, + tls_parse_fail_free, tls_parse_fail_realloc), 0); + tls_parse_alloc_seen = 0; + tls_parse_fail_after = 0; + + ExpectIntEQ(TLSX_SupportedCurve_Parse(ssl, goodBody, + (word16)sizeof(goodBody), 1, &extensions), + WC_NO_ERR_TRACE(MEMORY_E)); + + tls_parse_fail_after = -1; + (void)wolfSSL_SetAllocators(prevM, prevF, prevR); + TLSX_FreeAll(extensions, NULL); + } +#endif + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* Same restriction shape, response direction: needs a TLS 1.3 + * connection to get past the version guard tested first (a TLS 1.2 + * connection would be rejected before the body is even looked at). + * The FFDHE restriction bookkeeping is for a server reading a + * ClientHello only, so it is skipped regardless of the offered + * group. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + TLSX* extensions = NULL; + const byte noMatch261[] = { 0x00, 0x02, 0x01, 0x05 }; + + ExpectIntEQ(TLSX_UseSupportedCurve(&extensions, + WOLFSSL_ECC_SECP256R1, ssl->heap, ssl->options.side), + WOLFSSL_SUCCESS); + ExpectIntEQ(TLSX_SupportedCurve_Parse(ssl, noMatch261, + (word16)sizeof(noMatch261), 0, &extensions), 0); + TLSX_FreeAll(extensions, NULL); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* ---- Key Share negotiation (RFC 8446 4.2.8) ------------------------------ + * TLSX_KeyShare_Choose(), TLSX_KeyShare_Setup() and + * TLSX_KeyShare_Parse_ClientHello() are WOLFSSL_LOCAL: called directly here + * (guarded), with a client list built by hand for the shapes that would be + * awkward to reach through a real handshake. + */ +int test_TLSX_KeyShare_negotiate(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_SUPPORTED_CURVES) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_WOLFSSL_CLIENT) && \ + defined(WOLFSSL_TEST_STATIC_BUILD) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + + /* TLSX_KeyShare_Choose(): ssl == NULL / wrong side argument guard. */ + { + KeyShareEntry* kse = NULL; + byte searched = 0; + + ExpectIntEQ(TLSX_KeyShare_Choose(NULL, NULL, 0, 0, &kse, &searched), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + } + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + KeyShareEntry* kse = NULL; + byte searched = 0; + /* Right type, wrong side: a client-side ssl. */ + ExpectIntEQ(TLSX_KeyShare_Choose(ssl, ssl->extensions, 0, 0, &kse, + &searched), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + ExpectNotNull(ctx = test_tls_parse_server_ctx(wolfTLSv1_3_server_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + KeyShareEntry* kse = NULL; + byte searched = 0; + /* Both false: right side, no KeyShare extension at all -- an empty + * candidate list, not an error. */ + ExpectIntEQ(TLSX_KeyShare_Choose(ssl, ssl->extensions, 0, 0, &kse, + &searched), 0); + ExpectIntEQ(searched, 1); + ExpectNull(kse); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* TLSX_KeyShare_Choose(): extension->resp == 1 means a server key share + * was already chosen (e.g. after a HelloRetryRequest) -- outside of + * async key generation this is state that should not recur. */ + ExpectNotNull(ctx = test_tls_parse_server_ctx(wolfTLSv1_3_server_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + TLSX* extension; + KeyShareEntry* kse = NULL; + byte searched = 0; + + ExpectIntEQ(TLSX_Push(&ssl->extensions, TLSX_KEY_SHARE, NULL, + ssl->heap), 0); + extension = TLSX_Find(ssl->extensions, TLSX_KEY_SHARE); + ExpectNotNull(extension); + if (extension != NULL) + extension->resp = 1; + ExpectIntEQ(TLSX_KeyShare_Choose(ssl, ssl->extensions, 0, 0, &kse, + &searched), WC_NO_ERR_TRACE(INCOMPLETE_DATA)); + + /* Same extension, resp == 0: falls through to the normal search + * instead (an empty list here too, since data is NULL). */ + if (extension != NULL) + extension->resp = 0; + searched = 0; + ExpectIntEQ(TLSX_KeyShare_Choose(ssl, ssl->extensions, 0, 0, &kse, + &searched), 0); + ExpectIntEQ(searched, 1); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + +#ifdef WOLFSSL_HAVE_MLKEM + /* TLSX_KeyShare_Choose(): a client-offered group above WOLFSSL_ECC_MAX + * is only kept as a candidate when it is a recognised PQC or PQC + * hybrid group id -- anything else in that numeric space is skipped. + * All three group ids below are recorded as a matching + * supported_groups entry directly (bypassing the "is this build + * capable of it" gate TLSX_UseSupportedCurve() would apply), exactly + * as TLSX_SupportedCurve_Parse() would record whatever id a peer + * offered. */ + { + static const word16 groupIds[] = { + WOLFSSL_ML_KEM_512, /* pure PQC: !IS_PQC is false */ + WOLFSSL_SECP256R1MLKEM768,/* hybrid: !IS_PQC true, !IS_HYBRID false */ + 0xBEEF /* neither: both operands true */ + }; + size_t i; + + for (i = 0; i < sizeof(groupIds) / sizeof(groupIds[0]); i++) { + KeyShareEntry* kse = NULL; + byte searched = 0; + + ExpectNotNull(ctx = test_tls_parse_server_ctx( + wolfTLSv1_3_server_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + ExpectIntEQ(test_tls_parse_push_curve(&ssl->extensions, ssl, + groupIds[i]), 0); + ExpectNotNull(test_tls_parse_push_kse(&ssl->extensions, ssl, + groupIds[i])); + ExpectIntEQ(TLSX_KeyShare_Choose(ssl, ssl->extensions, 0, 0, + &kse, &searched), 0); + ExpectIntEQ(searched, 1); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + } + } +#endif /* WOLFSSL_HAVE_MLKEM */ + + /* TLSX_KeyShare_Setup(): the same ssl == NULL / wrong side guard as + * Choose(), on a different public entry point. */ + ExpectIntEQ(TLSX_KeyShare_Setup(NULL, NULL), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + ExpectIntEQ(TLSX_KeyShare_Setup(ssl, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + ExpectNotNull(ctx = test_tls_parse_server_ctx(wolfTLSv1_3_server_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + /* Right side, right type: falls through to the "no KeyShare + * extension yet" state check instead of the argument guard. */ + ExpectIntEQ(TLSX_KeyShare_Setup(ssl, NULL), + WC_NO_ERR_TRACE(BAD_STATE_E)); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* TLSX_KeyShare_Parse_ClientHello(): the list-length prefix and the + * MAX_EXT_DATA_LEN bound. Both are checked against the 'length' + * argument before any byte past the 2-byte prefix is read, so an + * over-large 'length' is exercised without actually allocating an + * extension body anywhere near that size. */ + ExpectNotNull(ctx = test_tls_parse_server_ctx(wolfTLSv1_3_server_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + TLSX* extensions = NULL; + /* declares a 5-entry list length but length says only 10 bytes of + * body follow (short by OPAQUE16_LEN). */ + const byte lenMismatch[] = { 0x00, 0x05 }; + /* declared length matches, but MAX_EXT_DATA_LEN - HELLO_EXT_SZ is + * exceeded; only the first 2 bytes are ever read. */ + const byte overLarge[] = { 0xFF, 0xFD }; + /* an empty list: both checks pass. */ + const byte empty[] = { 0x00, 0x00 }; + + ExpectIntEQ(TLSX_KeyShare_Parse_ClientHello(ssl, lenMismatch, 10, + &extensions), WC_NO_ERR_TRACE(BUFFER_ERROR)); + TLSX_FreeAll(extensions, NULL); + extensions = NULL; + + ExpectIntEQ(TLSX_KeyShare_Parse_ClientHello(ssl, overLarge, 65535, + &extensions), WC_NO_ERR_TRACE(BUFFER_ERROR)); + TLSX_FreeAll(extensions, NULL); + extensions = NULL; + + ExpectIntEQ(TLSX_KeyShare_Parse_ClientHello(ssl, empty, + (word16)sizeof(empty), &extensions), 0); + TLSX_FreeAll(extensions, NULL); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* TLSX_KeyShare_Parse(), server_hello direction: "not in the list sent + * if there isn't a private key". A group that was offered (both + * supported_groups and key_share list it) is guaranteed a non-NULL + * KeyShareEntry by TLSX_KeyShareEntry_Parse()'s own postcondition (it + * only returns a length equal to the input's when it also produced an + * entry), so keyShareEntry itself is never NULL here; only the + * key/privKey half is under test. */ +#if !defined(NO_DH) && defined(HAVE_FFDHE_2048) + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + byte body[2 + 2 + 256]; + word16 off = 0; + + body[off++] = (byte)(WOLFSSL_FFDHE_2048 >> 8); + body[off++] = (byte)(WOLFSSL_FFDHE_2048 & 0xFF); + body[off++] = 0x01; body[off++] = 0x00; /* keLen == 256 */ + XMEMSET(body + off, 0, 256); /* 0: never a valid DH public value */ + off += 256; + + ExpectIntEQ(test_tls_parse_push_curve(&ssl->extensions, ssl, + WOLFSSL_FFDHE_2048), 0); + /* client's own offer: no key generated for it (as if the server + * chose a group the client never actually built a key for -- not + * how a real client behaves, but the field state under test). */ + ExpectNotNull(test_tls_parse_push_kse(&ssl->extensions, ssl, + WOLFSSL_FFDHE_2048)); + + ExpectIntEQ(TLSX_KeyShare_Parse(ssl, body, off, server_hello), + WC_NO_ERR_TRACE(BAD_KEY_SHARE_DATA)); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* Same shape, but the client's entry already has a (fully initialised, + * so freeing it later is safe) DH key object -- key != NULL alone is + * enough for the gate to pass, independent of privKey. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + byte body[2 + 2 + 256]; + word16 off = 0; + KeyShareEntry* kse; + DhKey* dhKey = NULL; + + body[off++] = (byte)(WOLFSSL_FFDHE_2048 >> 8); + body[off++] = (byte)(WOLFSSL_FFDHE_2048 & 0xFF); + body[off++] = 0x01; body[off++] = 0x00; + XMEMSET(body + off, 0, 256); + off += 256; + + ExpectIntEQ(test_tls_parse_push_curve(&ssl->extensions, ssl, + WOLFSSL_FFDHE_2048), 0); + kse = test_tls_parse_push_kse(&ssl->extensions, ssl, + WOLFSSL_FFDHE_2048); + ExpectNotNull(kse); + ExpectNotNull(dhKey = (DhKey*)XMALLOC(sizeof(DhKey), ssl->heap, + DYNAMIC_TYPE_DH)); + if (dhKey != NULL) + ExpectIntEQ(wc_InitDhKey_ex(dhKey, ssl->heap, INVALID_DEVID), 0); + if (kse != NULL) + kse->key = dhKey; + + /* key != NULL, privKey == NULL: gate passes; the derivation itself + * then rejects the all-zero peer public value (never a valid DH + * public key) before privKey's absence would even matter. */ + ExpectIntEQ(TLSX_KeyShare_Parse(ssl, body, off, server_hello), + WC_NO_ERR_TRACE(PEER_KEY_ERROR)); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* Same shape, but the client's entry already has a private key (as a + * real one would by the time a ServerHello arrives): the gate passes + * and parsing proceeds to deriving the secret, which then fails on + * the all-zero peer value above instead. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + byte body[2 + 2 + 256]; + word16 off = 0; + KeyShareEntry* kse; + + body[off++] = (byte)(WOLFSSL_FFDHE_2048 >> 8); + body[off++] = (byte)(WOLFSSL_FFDHE_2048 & 0xFF); + body[off++] = 0x01; body[off++] = 0x00; + XMEMSET(body + off, 0, 256); + off += 256; + + ExpectIntEQ(test_tls_parse_push_curve(&ssl->extensions, ssl, + WOLFSSL_FFDHE_2048), 0); + kse = test_tls_parse_push_kse(&ssl->extensions, ssl, + WOLFSSL_FFDHE_2048); + ExpectNotNull(kse); + if (kse != NULL) { + kse->privKey = (byte*)XMALLOC(1, ssl->heap, + DYNAMIC_TYPE_PRIVATE_KEY); + ExpectNotNull(kse->privKey); + if (kse->privKey != NULL) { + kse->privKey[0] = 0x01; + kse->privKeyLen = 1; + kse->keyLen = 1; + } + } + + ExpectIntEQ(TLSX_KeyShare_Parse(ssl, body, off, server_hello), + WC_NO_ERR_TRACE(PEER_KEY_ERROR)); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif /* !NO_DH && HAVE_FFDHE_2048 */ +#endif + return EXPECT_RESULT(); +} + +/* ---- Key Share key generation (RFC 8446 4.2.8) -------------------------- + * TLSX_KeyShare_GenKey() dispatches by group to the per-algorithm Gen*Key() + * helpers, all WOLFSSL_LOCAL, called directly on a hand-built KeyShareEntry + * (a real handshake would need a full ClientHello round trip to reach the + * same pubKey/privKey states). Entries are heap-allocated and released via + * test_tls_parse_free_kse(), matching what TLSX_KeyShare_FreeAll() (not + * itself visible here) expects to own. + */ +int test_TLSX_KeyShare_gen(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_TEST_STATIC_BUILD) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + +#if !defined(NO_DH) && defined(HAVE_FFDHE_2048) + /* TLSX_KeyShare_GenDhKey(): "no key material yet" is true when either + * buffer is missing. A generation from a completely fresh entry hits + * both; pre-seeding one buffer while leaving the other NULL isolates + * each half. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + KeyShareEntry* kse = NULL; + + /* Fresh: pubKey == NULL && privKey == NULL. */ + ExpectNotNull(kse = (KeyShareEntry*)XMALLOC(sizeof(KeyShareEntry), + ssl->heap, DYNAMIC_TYPE_TLSX)); + if (kse != NULL) { + XMEMSET(kse, 0, sizeof(*kse)); + kse->group = WOLFSSL_FFDHE_2048; + ExpectIntEQ(TLSX_KeyShare_GenKey(ssl, kse), 0); + ExpectNotNull(kse->pubKey); + ExpectNotNull(kse->privKey); + + /* Same entry, called again: both buffers already present, the + * whole generation block (and the two allocation guards + * inside it) is skipped entirely. */ + ExpectIntEQ(TLSX_KeyShare_GenKey(ssl, kse), 0); + + test_tls_parse_free_kse(ssl, kse); + } + + /* pubKey missing, privKey pre-seeded: the outer guard is true from + * pubKey alone; the privKey allocation guard is then false. */ + ExpectNotNull(kse = (KeyShareEntry*)XMALLOC(sizeof(KeyShareEntry), + ssl->heap, DYNAMIC_TYPE_TLSX)); + if (kse != NULL) { + XMEMSET(kse, 0, sizeof(*kse)); + kse->group = WOLFSSL_FFDHE_2048; + kse->privKey = (byte*)XMALLOC(128, ssl->heap, + DYNAMIC_TYPE_PRIVATE_KEY); + ExpectNotNull(kse->privKey); + ExpectIntEQ(TLSX_KeyShare_GenKey(ssl, kse), 0); + ExpectNotNull(kse->pubKey); + test_tls_parse_free_kse(ssl, kse); + } + + /* privKey missing, pubKey pre-seeded (sized for the FFDHE 2048 + * prime): the outer guard is true from privKey alone; the pubKey + * allocation guard is then false. */ + ExpectNotNull(kse = (KeyShareEntry*)XMALLOC(sizeof(KeyShareEntry), + ssl->heap, DYNAMIC_TYPE_TLSX)); + if (kse != NULL) { + XMEMSET(kse, 0, sizeof(*kse)); + kse->group = WOLFSSL_FFDHE_2048; + kse->pubKey = (byte*)XMALLOC(256, ssl->heap, + DYNAMIC_TYPE_PUBLIC_KEY); + ExpectNotNull(kse->pubKey); + ExpectIntEQ(TLSX_KeyShare_GenKey(ssl, kse), 0); + ExpectNotNull(kse->privKey); + test_tls_parse_free_kse(ssl, kse); + } + +#ifdef WOLFSSL_TEST_STATIC_BUILD + /* "ret == 0" itself: force some allocation inside the shared DH + * key setup (the params/key object itself, ahead of either + * buffer's own allocation) to fail, so ret is non-zero by the + * time the pubKey/privKey allocation guards are reached and + * neither one fires. The exact allocation count spent on key + * object setup before either buffer is not part of this + * function's contract, so a small range of failure points is + * tried; harmless if a given one instead lands after a guard + * already ran (that attempt just contributes nothing new). */ + { + int fa; + for (fa = 0; fa <= 12; fa++) { + wolfSSL_Malloc_cb prevM = NULL; + wolfSSL_Free_cb prevF = NULL; + wolfSSL_Realloc_cb prevR = NULL; + + ExpectNotNull(kse = (KeyShareEntry*)XMALLOC( + sizeof(KeyShareEntry), ssl->heap, + DYNAMIC_TYPE_TLSX)); + if (kse == NULL) + break; + XMEMSET(kse, 0, sizeof(*kse)); + kse->group = WOLFSSL_FFDHE_2048; + + ExpectIntEQ(wolfSSL_GetAllocators(&prevM, &prevF, &prevR), 0); + ExpectIntEQ(wolfSSL_SetAllocators(tls_parse_fail_malloc, + tls_parse_fail_free, tls_parse_fail_realloc), 0); + tls_parse_alloc_seen = 0; + tls_parse_fail_after = fa; + + (void)TLSX_KeyShare_GenKey(ssl, kse); + + tls_parse_fail_after = -1; + (void)wolfSSL_SetAllocators(prevM, prevF, prevR); + + test_tls_parse_free_kse(ssl, kse); + } + } +#endif + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif /* !NO_DH && HAVE_FFDHE_2048 */ + +#ifdef HAVE_CURVE25519 + /* TLSX_KeyShare_GenX25519Key(): "ret == 0 && pubKey == NULL". A fresh + * entry gives both true; a second call on the same (now fully + * populated) entry gives pubKey == NULL false while ret stays 0. + * Clearing ssl->rng first forces the key generation itself to fail + * (WC_RNG* rng == NULL is rejected before anything else), giving + * ret == 0 false without needing a malformed group or corrupt state. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + KeyShareEntry* kse = NULL; + WC_RNG* savedRng = ssl->rng; + + ExpectNotNull(kse = (KeyShareEntry*)XMALLOC(sizeof(KeyShareEntry), + ssl->heap, DYNAMIC_TYPE_TLSX)); + if (kse != NULL) { + XMEMSET(kse, 0, sizeof(*kse)); + kse->group = WOLFSSL_ECC_X25519; + ssl->rng = NULL; + ExpectIntEQ(TLSX_KeyShare_GenKey(ssl, kse), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectNull(kse->pubKey); + ssl->rng = savedRng; + test_tls_parse_free_kse(ssl, kse); + } + + ExpectNotNull(kse = (KeyShareEntry*)XMALLOC(sizeof(KeyShareEntry), + ssl->heap, DYNAMIC_TYPE_TLSX)); + if (kse != NULL) { + XMEMSET(kse, 0, sizeof(*kse)); + kse->group = WOLFSSL_ECC_X25519; + ExpectIntEQ(TLSX_KeyShare_GenKey(ssl, kse), 0); + ExpectNotNull(kse->pubKey); + ExpectIntEQ(TLSX_KeyShare_GenKey(ssl, kse), 0); + test_tls_parse_free_kse(ssl, kse); + } + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif /* HAVE_CURVE25519 */ + +#ifdef HAVE_CURVE448 + /* TLSX_KeyShare_GenX448Key(): same shape as X25519 above. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + KeyShareEntry* kse = NULL; + WC_RNG* savedRng = ssl->rng; + + ExpectNotNull(kse = (KeyShareEntry*)XMALLOC(sizeof(KeyShareEntry), + ssl->heap, DYNAMIC_TYPE_TLSX)); + if (kse != NULL) { + XMEMSET(kse, 0, sizeof(*kse)); + kse->group = WOLFSSL_ECC_X448; + ssl->rng = NULL; + ExpectIntEQ(TLSX_KeyShare_GenKey(ssl, kse), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectNull(kse->pubKey); + ssl->rng = savedRng; + test_tls_parse_free_kse(ssl, kse); + } + + ExpectNotNull(kse = (KeyShareEntry*)XMALLOC(sizeof(KeyShareEntry), + ssl->heap, DYNAMIC_TYPE_TLSX)); + if (kse != NULL) { + XMEMSET(kse, 0, sizeof(*kse)); + kse->group = WOLFSSL_ECC_X448; + ExpectIntEQ(TLSX_KeyShare_GenKey(ssl, kse), 0); + ExpectNotNull(kse->pubKey); + ExpectIntEQ(TLSX_KeyShare_GenKey(ssl, kse), 0); + test_tls_parse_free_kse(ssl, kse); + } + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif /* HAVE_CURVE448 */ + +#if defined(HAVE_ECC) && defined(HAVE_ECC_KEY_EXPORT) + /* TLSX_KeyShare_GenEccKey(): only the "ret == 0" half of "ret == 0 && + * pubKey == NULL" is open (the pubKey half already has coverage + * elsewhere); force it false the same way as the Curve25519/X448 + * cases above. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + KeyShareEntry* kse = NULL; + WC_RNG* savedRng = ssl->rng; + + ExpectNotNull(kse = (KeyShareEntry*)XMALLOC(sizeof(KeyShareEntry), + ssl->heap, DYNAMIC_TYPE_TLSX)); + if (kse != NULL) { + XMEMSET(kse, 0, sizeof(*kse)); + kse->group = WOLFSSL_ECC_SECP256R1; + ssl->rng = NULL; + ExpectIntEQ(TLSX_KeyShare_GenKey(ssl, kse), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectNull(kse->pubKey); + ssl->rng = savedRng; + test_tls_parse_free_kse(ssl, kse); + } + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif /* HAVE_ECC && HAVE_ECC_KEY_EXPORT */ +#endif + return EXPECT_RESULT(); +} + +/* ---- Key Share free/size/write (RFC 8446 4.2.8) -------------------------- */ +int test_TLSX_KeyShare_freesizewrite(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_SUPPORTED_CURVES) && \ + !defined(NO_DH) && defined(HAVE_FFDHE_2048) && \ + defined(WOLFSSL_TEST_STATIC_BUILD) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + + /* TLSX_KeyShare_FreeAll(): "privKey != NULL && privKeyLen > 0" gates + * zeroing an FFDHE entry's private key before it is freed. All four + * combinations are driven directly on a standalone extension list + * (not tied to ssl->extensions), each freed by the same call under + * test. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + int i; + for (i = 0; i < 4; i++) { + TLSX* extensions = NULL; + KeyShareEntry* kse = NULL; + + ExpectNotNull(kse = test_tls_parse_push_kse(&extensions, ssl, + WOLFSSL_FFDHE_2048)); + if (kse != NULL) { + switch (i) { + case 0: /* privKey == NULL, privKeyLen == 0 */ + break; + case 1: /* privKey != NULL, privKeyLen == 0 */ + kse->privKey = (byte*)XMALLOC(1, ssl->heap, + DYNAMIC_TYPE_PRIVATE_KEY); + ExpectNotNull(kse->privKey); + break; + case 2: /* privKey != NULL, privKeyLen > 0 */ + kse->privKey = (byte*)XMALLOC(4, ssl->heap, + DYNAMIC_TYPE_PRIVATE_KEY); + ExpectNotNull(kse->privKey); + kse->privKeyLen = 4; + break; + case 3: /* privKey == NULL, privKeyLen > 0 */ + kse->privKeyLen = 4; + break; + } + } + TLSX_FreeAll(extensions, ssl->heap); + } + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* TLSX_KeyShare_GetSize() / TLSX_KeyShare_Write(): "!isRequest && + * pubKey == NULL" -- a request-direction (client_hello) list always + * writes every entry regardless of pubKey; a response-direction + * (server_hello) list skips any entry without one. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + word32 reqLen; + word16 respLen; + byte out[64]; + word32 reqOff; + word16 respOff; + KeyShareEntry* kse; + + /* pubKey == NULL: response direction skips it (0 bytes); request + * direction still writes it (pubKeyLen == 0, so just the header). + * resp must be set for the response direction to consider this + * extension at all (TLSX_GetSize()'s own, outer "only marked + * extensions are sent back" rule) -- otherwise it would be + * skipped one level up, before ever reaching the pubKey check + * under test. */ + ExpectNotNull(kse = test_tls_parse_push_kse(&ssl->extensions, ssl, + WOLFSSL_FFDHE_2048)); + (void)kse; + { + TLSX* ext = TLSX_Find(ssl->extensions, TLSX_KEY_SHARE); + ExpectNotNull(ext); + if (ext != NULL) + ext->resp = 1; + } + + respLen = 0; + ExpectIntEQ(TLSX_GetResponseSize(ssl, server_hello, &respLen), 0); + respOff = 0; + XMEMSET(out, 0, sizeof(out)); + ExpectIntEQ(TLSX_WriteResponse(ssl, out, server_hello, &respOff), 0); + + reqLen = 0; + ExpectIntEQ(TLSX_GetRequestSize(ssl, client_hello, &reqLen), 0); + ExpectIntGT(reqLen, 0); + reqOff = 0; + XMEMSET(out, 0, sizeof(out)); + ExpectIntEQ(TLSX_WriteRequest(ssl, out, client_hello, &reqOff), 0); + ExpectIntGT(reqOff, 0); + } + wolfSSL_free(ssl); + ssl = NULL; + + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + word16 respLen; + byte out[64]; + word16 respOff; + KeyShareEntry* kse = NULL; + + /* pubKey != NULL: response direction includes it too. */ + ExpectNotNull(kse = test_tls_parse_push_kse(&ssl->extensions, ssl, + WOLFSSL_FFDHE_2048)); + if (kse != NULL) { + kse->pubKey = (byte*)XMALLOC(4, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY); + ExpectNotNull(kse->pubKey); + kse->pubKeyLen = 4; + } + { + TLSX* ext = TLSX_Find(ssl->extensions, TLSX_KEY_SHARE); + ExpectNotNull(ext); + if (ext != NULL) + ext->resp = 1; + } + + respLen = 0; + ExpectIntEQ(TLSX_GetResponseSize(ssl, server_hello, &respLen), 0); + ExpectIntGT(respLen, 0); + respOff = 0; + XMEMSET(out, 0, sizeof(out)); + ExpectIntEQ(TLSX_WriteResponse(ssl, out, server_hello, &respOff), 0); + ExpectIntGT(respOff, 0); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* ---- Key Share secret derivation (RFC 8446 4.2.8, TLS 1.2 legacy DH) ---- + * TLSX_KeyShare_ProcessDh(), TLSX_KeyShare_ProcessX25519_ex() and + * TLSX_KeyShare_ProcessEcc_ex() are all fully static; TLSX_KeyShare_ + * DeriveSecret() (WOLFSSL_LOCAL) reaches them by dispatching on whatever + * single entry is in the KeyShare extension, so it is used here as the + * entry point, with a hand-built entry standing in for what a real + * handshake would have produced by this point. + */ +int test_TLSX_KeyShare_process(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_SUPPORTED_CURVES) && \ + defined(WOLFSSL_TEST_STATIC_BUILD) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + +#ifdef HAVE_CURVE25519 + /* TLSX_KeyShare_ProcessX25519_ex(): "ret == 0 && key == NULL" -- our + * own side's key. A peer public value is needed either way; borrow + * one from a throwaway key pair generated for this test alone. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + KeyShareEntry* peer = NULL; + KeyShareEntry* target; + + ExpectNotNull(peer = (KeyShareEntry*)XMALLOC(sizeof(KeyShareEntry), + ssl->heap, DYNAMIC_TYPE_TLSX)); + if (peer != NULL) { + XMEMSET(peer, 0, sizeof(*peer)); + peer->group = WOLFSSL_ECC_X25519; + ExpectIntEQ(TLSX_KeyShare_GenKey(ssl, peer), 0); + } + + target = test_tls_parse_push_kse(&ssl->extensions, ssl, + WOLFSSL_ECC_X25519); + ExpectNotNull(target); + if (target != NULL && peer != NULL && peer->pubKey != NULL) { + XFREE(target->ke, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY); + target->ke = (byte*)XMALLOC(peer->pubKeyLen, ssl->heap, + DYNAMIC_TYPE_PUBLIC_KEY); + ExpectNotNull(target->ke); + if (target->ke != NULL) { + XMEMCPY(target->ke, peer->pubKey, peer->pubKeyLen); + target->keLen = (word16)peer->pubKeyLen; + } + } + /* target->key stays NULL: our own side never generated a key. */ + ExpectIntEQ(TLSX_KeyShare_DeriveSecret(ssl), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + test_tls_parse_free_kse(ssl, peer); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* Same shape, but our own side has a real key too: both operands + * false, and the exchange actually completes. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + KeyShareEntry* peer = NULL; + KeyShareEntry* target; + + ExpectNotNull(peer = (KeyShareEntry*)XMALLOC(sizeof(KeyShareEntry), + ssl->heap, DYNAMIC_TYPE_TLSX)); + if (peer != NULL) { + XMEMSET(peer, 0, sizeof(*peer)); + peer->group = WOLFSSL_ECC_X25519; + ExpectIntEQ(TLSX_KeyShare_GenKey(ssl, peer), 0); + } + + target = test_tls_parse_push_kse(&ssl->extensions, ssl, + WOLFSSL_ECC_X25519); + ExpectNotNull(target); + if (target != NULL) { + ExpectIntEQ(TLSX_KeyShare_GenKey(ssl, target), 0); + if (peer != NULL && peer->pubKey != NULL) { + XFREE(target->ke, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY); + target->ke = (byte*)XMALLOC(peer->pubKeyLen, ssl->heap, + DYNAMIC_TYPE_PUBLIC_KEY); + ExpectNotNull(target->ke); + if (target->ke != NULL) { + XMEMCPY(target->ke, peer->pubKey, peer->pubKeyLen); + target->keLen = (word16)peer->pubKeyLen; + } + } + } + ExpectIntEQ(TLSX_KeyShare_DeriveSecret(ssl), 0); + + test_tls_parse_free_kse(ssl, peer); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* "ret == 0" itself: an invalid peer value is rejected before our own + * key is ever looked at, independent of whether one was generated. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + KeyShareEntry* target; + + target = test_tls_parse_push_kse(&ssl->extensions, ssl, + WOLFSSL_ECC_X25519); + ExpectNotNull(target); + if (target != NULL) { + /* wrong length for a Curve25519 public value: rejected by + * wc_curve25519_check_public() before the peer key is even + * imported. */ + XFREE(target->ke, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY); + target->ke = (byte*)XMALLOC(4, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY); + ExpectNotNull(target->ke); + if (target->ke != NULL) { + XMEMSET(target->ke, 0, 4); + target->keLen = 4; + } + } + ExpectIntEQ(TLSX_KeyShare_DeriveSecret(ssl), + WC_NO_ERR_TRACE(ECC_PEERKEY_ERROR)); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif /* HAVE_CURVE25519 */ + +#if defined(HAVE_ECC) && defined(HAVE_ECC_KEY_EXPORT) + /* TLSX_KeyShare_ProcessEcc_ex(): same "ret == 0 && key == NULL" shape, + * for a plain named ECC curve instead of X25519. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + KeyShareEntry* peer = NULL; + KeyShareEntry* target; + + ExpectNotNull(peer = (KeyShareEntry*)XMALLOC(sizeof(KeyShareEntry), + ssl->heap, DYNAMIC_TYPE_TLSX)); + if (peer != NULL) { + XMEMSET(peer, 0, sizeof(*peer)); + peer->group = WOLFSSL_ECC_SECP256R1; + ExpectIntEQ(TLSX_KeyShare_GenKey(ssl, peer), 0); + } + + target = test_tls_parse_push_kse(&ssl->extensions, ssl, + WOLFSSL_ECC_SECP256R1); + ExpectNotNull(target); + if (target != NULL && peer != NULL && peer->pubKey != NULL) { + XFREE(target->ke, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY); + target->ke = (byte*)XMALLOC(peer->pubKeyLen, ssl->heap, + DYNAMIC_TYPE_PUBLIC_KEY); + ExpectNotNull(target->ke); + if (target->ke != NULL) { + XMEMCPY(target->ke, peer->pubKey, peer->pubKeyLen); + target->keLen = (word16)peer->pubKeyLen; + } + } + ExpectIntEQ(TLSX_KeyShare_DeriveSecret(ssl), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + test_tls_parse_free_kse(ssl, peer); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + KeyShareEntry* peer = NULL; + KeyShareEntry* target; + + ExpectNotNull(peer = (KeyShareEntry*)XMALLOC(sizeof(KeyShareEntry), + ssl->heap, DYNAMIC_TYPE_TLSX)); + if (peer != NULL) { + XMEMSET(peer, 0, sizeof(*peer)); + peer->group = WOLFSSL_ECC_SECP256R1; + ExpectIntEQ(TLSX_KeyShare_GenKey(ssl, peer), 0); + } + + target = test_tls_parse_push_kse(&ssl->extensions, ssl, + WOLFSSL_ECC_SECP256R1); + ExpectNotNull(target); + if (target != NULL) { + ExpectIntEQ(TLSX_KeyShare_GenKey(ssl, target), 0); + if (peer != NULL && peer->pubKey != NULL) { + XFREE(target->ke, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY); + target->ke = (byte*)XMALLOC(peer->pubKeyLen, ssl->heap, + DYNAMIC_TYPE_PUBLIC_KEY); + ExpectNotNull(target->ke); + if (target->ke != NULL) { + XMEMCPY(target->ke, peer->pubKey, peer->pubKeyLen); + target->keLen = (word16)peer->pubKeyLen; + } + } + } + ExpectIntEQ(TLSX_KeyShare_DeriveSecret(ssl), 0); + + test_tls_parse_free_kse(ssl, peer); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* "ret == 0" itself: an invalid (wrong-length) peer value is rejected + * while importing it, before our own key is ever looked at. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + KeyShareEntry* target; + + target = test_tls_parse_push_kse(&ssl->extensions, ssl, + WOLFSSL_ECC_SECP256R1); + ExpectNotNull(target); + if (target != NULL) { + XFREE(target->ke, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY); + target->ke = (byte*)XMALLOC(4, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY); + ExpectNotNull(target->ke); + if (target->ke != NULL) { + XMEMSET(target->ke, 0, 4); + target->keLen = 4; + } + } + ExpectIntEQ(TLSX_KeyShare_DeriveSecret(ssl), + WC_NO_ERR_TRACE(ECC_PEERKEY_ERROR)); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif /* HAVE_ECC && HAVE_ECC_KEY_EXPORT */ + +#if !defined(NO_DH) && defined(HAVE_FFDHE_2048) + /* TLSX_KeyShare_ProcessDh(): "ret == 0 && dhKeySz > preMasterSz" -- a + * raw Diffie-Hellman agreement can legitimately produce a shared value + * shorter than the prime's byte length (a leading zero byte), which is + * then re-padded; a value with no leading zero byte does not need it. + * Both are reached deterministically by fixing our own private + * exponent at 1, so the derived secret is exactly the peer's public + * value (chosen well below the prime either way, so no modular + * reduction occurs). */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + KeyShareEntry* target; + byte* ke = NULL; + + target = test_tls_parse_push_kse(&ssl->extensions, ssl, + WOLFSSL_FFDHE_2048); + ExpectNotNull(target); + if (target != NULL) { + XFREE(target->ke, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY); + ExpectNotNull(ke = (byte*)XMALLOC(256, ssl->heap, + DYNAMIC_TYPE_PUBLIC_KEY)); + target->ke = ke; + if (ke != NULL) { + /* peer public value 2, left-padded to 256 bytes: a + * leading zero byte. */ + XMEMSET(ke, 0, 256); + ke[255] = 0x02; + target->keLen = 256; + } + target->privKey = (byte*)XMALLOC(1, ssl->heap, + DYNAMIC_TYPE_PRIVATE_KEY); + ExpectNotNull(target->privKey); + if (target->privKey != NULL) { + target->privKey[0] = 0x01; + target->keyLen = 1; + } + } + + ExpectIntEQ(TLSX_KeyShare_DeriveSecret(ssl), 0); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + KeyShareEntry* target; + byte* ke = NULL; + + target = test_tls_parse_push_kse(&ssl->extensions, ssl, + WOLFSSL_FFDHE_2048); + ExpectNotNull(target); + if (target != NULL) { + XFREE(target->ke, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY); + ExpectNotNull(ke = (byte*)XMALLOC(256, ssl->heap, + DYNAMIC_TYPE_PUBLIC_KEY)); + target->ke = ke; + if (ke != NULL) { + /* peer public value 2^2040, left-padded to 256 bytes: no + * leading zero byte. */ + XMEMSET(ke, 0, 256); + ke[0] = 0x01; + target->keLen = 256; + } + target->privKey = (byte*)XMALLOC(1, ssl->heap, + DYNAMIC_TYPE_PRIVATE_KEY); + ExpectNotNull(target->privKey); + if (target->privKey != NULL) { + target->privKey[0] = 0x01; + target->keyLen = 1; + } + } + + ExpectIntEQ(TLSX_KeyShare_DeriveSecret(ssl), 0); + } + wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); +#endif /* !NO_DH && HAVE_FFDHE_2048 */ #endif return EXPECT_RESULT(); } diff --git a/tests/api/test_tls_parse.h b/tests/api/test_tls_parse.h index 42d4e82ca4..1d2d8bcd29 100644 --- a/tests/api/test_tls_parse.h +++ b/tests/api/test_tls_parse.h @@ -37,6 +37,11 @@ int test_TLSX_CSR_parse(void); int test_TLSX_PointFormat_parse(void); int test_TLSX_SNI_parse(void); int test_TLSX_ValidateSupportedCurves(void); +int test_TLSX_SupportedGroups_parse(void); +int test_TLSX_KeyShare_negotiate(void); +int test_TLSX_KeyShare_gen(void); +int test_TLSX_KeyShare_freesizewrite(void); +int test_TLSX_KeyShare_process(void); #define TEST_TLS_PARSE_DECLS \ TEST_DECL_GROUP("tls", test_TLSX_ALPN_parse), \ @@ -53,6 +58,11 @@ int test_TLSX_ValidateSupportedCurves(void); TEST_DECL_GROUP("tls", test_TLSX_CSR_parse), \ TEST_DECL_GROUP("tls", test_TLSX_PointFormat_parse), \ TEST_DECL_GROUP("tls", test_TLSX_SNI_parse), \ - TEST_DECL_GROUP("tls", test_TLSX_ValidateSupportedCurves) + TEST_DECL_GROUP("tls", test_TLSX_ValidateSupportedCurves), \ + TEST_DECL_GROUP("tls", test_TLSX_SupportedGroups_parse), \ + TEST_DECL_GROUP("tls", test_TLSX_KeyShare_negotiate), \ + TEST_DECL_GROUP("tls", test_TLSX_KeyShare_gen), \ + TEST_DECL_GROUP("tls", test_TLSX_KeyShare_freesizewrite), \ + TEST_DECL_GROUP("tls", test_TLSX_KeyShare_process) #endif /* TESTS_API_TEST_TLS_PARSE_H */ From 55150d08cbb76bc69b9ef799ff33f261b27e1d0b Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 23:13:09 +0200 Subject: [PATCH 45/57] tests: cover OCSP stapling, extension population and msgType dispatch in tls.c Adds direct-call and memio-driven tests for the CSR/CSR2 OCSP write and size paths, ProcessChainOCSPRequest, TLSX_PopulateExtensions/ PopulateSupportedGroups, the TLSX_Write/GetSize msgType dispatchers (SupportedVersions, EncryptThenMac, Client/ServerCertificateType, SecureRenegotiation), and the TLSX_SessionTicket_Parse REJECT/ VERSION_ERROR false-false row. --- tests/api/test_tls_bounds.c | 1803 +++++++++++++++++++++++++++++++++++ tests/api/test_tls_bounds.h | 31 +- 2 files changed, 1833 insertions(+), 1 deletion(-) diff --git a/tests/api/test_tls_bounds.c b/tests/api/test_tls_bounds.c index 4df603d4a6..eb4a68e4f5 100644 --- a/tests/api/test_tls_bounds.c +++ b/tests/api/test_tls_bounds.c @@ -27,6 +27,35 @@ #include #include +/* c32to24() (wolfcrypt/src/misc.c) is only externally linkable when NO_INLINE + * is defined; this build inlines it into each translation unit that already + * needs it, so it is not visible here. Same 3-byte big-endian length write, + * spelled out locally. */ +static void test_tls_bounds_c32to24(word32 in, byte* out) +{ + out[0] = (byte)(in >> 16); + out[1] = (byte)(in >> 8); + out[2] = (byte)in; +} + +#if !defined(NO_WOLFSSL_SERVER) && !defined(NO_CERTS) && !defined(NO_RSA) && \ + !defined(NO_FILESYSTEM) +/* SetSSL_CTX() (InitSSL()'s caller) fails wolfSSL_new() with NO_PRIVATE_KEY + * for a server-side ssl with no certificate/key and no PSK/anon/cert-setup-cb + * fallback, so every server-side ssl created only to unit-test a WOLFSSL_LOCAL + * function directly (never running a real handshake) still needs a loaded + * cert/key to get past wolfSSL_new() at all. */ +static int test_tls_bounds_load_server_cert(WOLFSSL_CTX* ctx) +{ + EXPECT_DECLS; + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx, "./certs/server-cert.pem", + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx, "./certs/server-key.pem", + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + return EXPECT_RESULT(); +} +#endif + /* ---------------------------------------------------------------------- */ /* TLSX_UseSNI - the extensions list argument is always &ssl->extensions or * &ctx->extensions (wolfSSL_UseSNI / wolfSSL_CTX_UseSNI), or &ech->extensions @@ -841,3 +870,1777 @@ int test_TLSX_Cookie_bounds(void) return EXPECT_RESULT(); } #endif + +/* ---------------------------------------------------------------------- */ +/* TLSX_CSR_GetSize_ex() / TLSX_CSR_Write_ex() - both share the same + * response-side shape: + * if (!isRequest && csr->ssl != NULL && IsAtLeastTLSv1_3(csr->ssl->version)) { + * if (csr->ssl != NULL && SSL_CM(csr->ssl) != NULL && + * SSL_CM(csr->ssl)->ocsp_stapling != NULL && + * SSL_CM(csr->ssl)->ocsp_stapling->statusCb != NULL) { + * + * } + * responses[idx] directly> + * } + * + * In TLSX_CSR_Write_ex() specifically, the leading "!isRequest" operand of + * the outer decision is unreachable as false: the function's own + * "#ifndef NO_WOLFSSL_CLIENT if (isRequest) { ...; return (int)offset; }" + * block (present in this build) always returns before this line whenever + * isRequest is true, so control only ever reaches the response-side "if" + * with isRequest already false. Excluded (family 3: fixed by the branch + * that reaches it). TLSX_CSR_GetSize_ex()'s twin block has no such + * unconditional return ahead of it (the request-side "if (isRequest) {...}" + * just falls through), so its !isRequest/csr->ssl!=NULL operands are + * independently reachable there - which is exactly why the worklist only + * carries TLSX_CSR_GetSize_ex's third operand (the version check) and not + * its first two. + * + * The nested decision's leading "csr->ssl != NULL" (in both functions) is + * likewise fixed true: it re-tests the same csr->ssl pointer the enclosing + * "if" already required to be non-NULL two lines above, with no assignment + * to csr->ssl in between. Excluded (family 3) in both functions. + */ +#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(WOLFSSL_TLS13) && \ + defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_WOLFSSL_CLIENT) && defined(HAVE_OCSP) +static int test_TLSX_CSR_write_getsize_status_cb(WOLFSSL* ssl, void* arg) +{ + (void)ssl; (void)arg; + return WOLFSSL_OCSP_STATUS_CB_OK; +} + +int test_TLSX_CSR_write_getsize_bounds(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + TLSX* ext = NULL; + CertificateStatusRequest* csr = NULL; + WOLFSSL_CERT_MANAGER* origCM = NULL; + ProtocolVersion origVersion; + byte out[OPAQUE8_LEN + OPAQUE24_LEN + 8]; + word16 sz; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + ExpectIntEQ(test_tls_bounds_load_server_cert(ctx), TEST_SUCCESS); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(TLSX_UseCertificateStatusRequest(&ssl->extensions, + WOLFSSL_CSR_OCSP, 0, ssl, ssl->heap, ssl->devId), + WOLFSSL_SUCCESS); + ExpectNotNull(ext = TLSX_Find(ssl->extensions, TLSX_STATUS_REQUEST)); + if (ext != NULL) + csr = (CertificateStatusRequest*)ext->data; + ExpectNotNull(csr); + + if (csr != NULL) { + origVersion = ssl->version; + + /* Outer decision, operand 1 (csr->ssl != NULL): false side. Short- + * circuits before the version check (csr->ssl->version) is ever + * dereferenced, so this is safe even though ssl is TLS 1.3. Only + * TLSX_CSR_Write_ex() needs this pair (TLSX_CSR_GetSize_ex()'s + * operand 1 is already covered elsewhere). */ + csr->ssl = NULL; + ExpectIntEQ(TLSX_CSR_Write_ex(csr, out, 0, 0), 0); + csr->ssl = ssl; + + /* Outer decision, operand 2 (IsAtLeastTLSv1_3): false side, with + * operand 1 true (csr->ssl == ssl, unchanged). Paired against the + * TLS 1.3 calls below (operand 2 true). */ + ssl->version.major = SSLv3_MAJOR; + ssl->version.minor = TLSv1_2_MINOR; + ExpectIntEQ(TLSX_CSR_GetSize_ex(csr, 0, 0), 0); + ExpectIntEQ(TLSX_CSR_Write_ex(csr, out, 0, 0), 0); + ssl->version = origVersion; + + /* Nested decision, operand 1 (SSL_CM(csr->ssl) != NULL): false + * side. Outer decision is true throughout (TLS 1.3, csr->ssl set), + * so this also covers operand 2 of the outer decision (true side). */ + origCM = ssl->ctx->cm; + ssl->ctx->cm = NULL; + ExpectIntEQ(TLSX_CSR_GetSize_ex(csr, 0, 0), + OPAQUE8_LEN + OPAQUE24_LEN); + ExpectIntEQ(TLSX_CSR_Write_ex(csr, out, 0, 0), + OPAQUE8_LEN + OPAQUE24_LEN); + ssl->ctx->cm = origCM; + + /* Nested decision, operand 2 (ocsp_stapling != NULL): false side, + * operand 1 now true (cm restored, no stapling object allocated + * yet). */ + ExpectIntEQ(TLSX_CSR_GetSize_ex(csr, 0, 0), + OPAQUE8_LEN + OPAQUE24_LEN); + ExpectIntEQ(TLSX_CSR_Write_ex(csr, out, 0, 0), + OPAQUE8_LEN + OPAQUE24_LEN); + + /* Nested decision, operand 3 (statusCb != NULL): false side, + * operands 1 and 2 now true (stapling object allocated, no + * callback registered yet). */ + ExpectIntEQ(wolfSSL_CTX_EnableOCSPStapling(ctx), WOLFSSL_SUCCESS); + ExpectIntEQ(TLSX_CSR_GetSize_ex(csr, 0, 0), + OPAQUE8_LEN + OPAQUE24_LEN); + ExpectIntEQ(TLSX_CSR_Write_ex(csr, out, 0, 0), + OPAQUE8_LEN + OPAQUE24_LEN); + + /* Nested decision, all operands true: routes through + * TLSX_CSR_WriteWithStatusCB() / the status-callback size path. + * TLSX_CSR_WriteWithStatusCB()'s own leading NULL guards + * ("ssl == NULL || SSL_CM(ssl) == NULL" and "ocsp == NULL || + * ocsp->statusCb == NULL") are unreachable as true: it has exactly + * one caller (this line), which has just proven all four of those + * facts true. Excluded (family 1). Its response==NULL/respSz==0 + * guard is not established by the caller and is driven below. */ + ExpectIntEQ(wolfSSL_CTX_set_tlsext_status_cb(ctx, + test_TLSX_CSR_write_getsize_status_cb), WOLFSSL_SUCCESS); + + /* response == NULL (respSz == 0 too, both untouched): operand 0 + * of the response/respSz guard true. */ + ssl->ocspCsrResp[0].buffer = NULL; + ssl->ocspCsrResp[0].length = 0; + ExpectIntEQ(TLSX_CSR_Write_ex(csr, out, 0, 0), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* response != NULL, respSz == 0: operand 0 false, operand 1 true - + * independence for operand 1. */ + ssl->ocspCsrResp[0].buffer = out; + ssl->ocspCsrResp[0].length = 0; + ExpectIntEQ(TLSX_CSR_Write_ex(csr, out, 0, 0), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* response != NULL, respSz > 0: both operands false - the write + * succeeds. Also exercises TLSX_CSR_GetSize_ex()'s status-callback + * size path (not itself an open condition, but the same call site). */ + ssl->ocspCsrResp[0].length = 4; + sz = TLSX_CSR_GetSize_ex(csr, 0, 0); + ExpectIntEQ(sz, OPAQUE8_LEN + OPAQUE24_LEN + 4); + ExpectIntEQ(TLSX_CSR_Write_ex(csr, out, 0, 0), + OPAQUE8_LEN + OPAQUE24_LEN + 4); + + ssl->ocspCsrResp[0].buffer = NULL; + ssl->ocspCsrResp[0].length = 0; + } + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + return EXPECT_RESULT(); +} +#endif + +/* ---------------------------------------------------------------------- */ +/* TLSX_CSR_SetResponseWithStatusCB() - unlike TLSX_CSR_WriteWithStatusCB(), + * this one is WOLFSSL_LOCAL (not static-in-file), so it is unit-tested + * directly rather than only through its one real caller (SetupOcspResp() in + * tls13.c, which - like TLSX_CSR_Write_ex() above - already guarantees + * ssl/SSL_CM(ssl)/ocsp_stapling/statusCb are non-NULL before calling it). + * Called directly, none of those preconditions are established, so all four + * operands are independently reachable here. */ +#if defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_SERVER) && \ + defined(HAVE_CERTIFICATE_STATUS_REQUEST) && defined(HAVE_OCSP) +int test_TLSX_CSR_SetResponseWithStatusCB_bounds(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + WOLFSSL_CERT_MANAGER* origCM = NULL; + + /* ssl == NULL: first operand of "ssl == NULL || SSL_CM(ssl) == NULL". */ + ExpectIntEQ(TLSX_CSR_SetResponseWithStatusCB(NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + ExpectIntEQ(test_tls_bounds_load_server_cert(ctx), TEST_SUCCESS); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + + /* ssl != NULL, SSL_CM(ssl) == NULL: second operand true, first false - + * independence for the second operand. */ + origCM = ssl->ctx->cm; + ssl->ctx->cm = NULL; + ExpectIntEQ(TLSX_CSR_SetResponseWithStatusCB(ssl), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ssl->ctx->cm = origCM; + + /* ssl != NULL, SSL_CM(ssl) != NULL, ocsp == NULL (no stapling object + * allocated yet): both operands of the first guard false, first + * operand of the second guard ("ocsp == NULL") true. */ + ExpectIntEQ(TLSX_CSR_SetResponseWithStatusCB(ssl), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* ocsp != NULL, statusCb == NULL: second operand of the second guard + * true, first false - independence for that operand. */ + ExpectIntEQ(wolfSSL_CTX_EnableOCSPStapling(ctx), WOLFSSL_SUCCESS); + ExpectIntEQ(TLSX_CSR_SetResponseWithStatusCB(ssl), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* Both guards false: reaches the callback itself. No status_request + * extension is present on ssl, so the callback's WOLFSSL_OCSP_STATUS_CB_OK + * finds nothing in ssl->ocspCsrResp to ack and returns cleanly. */ + ExpectIntEQ(wolfSSL_CTX_set_tlsext_status_cb(ctx, + test_TLSX_CSR_write_getsize_status_cb), WOLFSSL_SUCCESS); + ExpectIntEQ(TLSX_CSR_SetResponseWithStatusCB(ssl), 0); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + return EXPECT_RESULT(); +} +#endif + +/* ---------------------------------------------------------------------- */ +/* ProcessChainOCSPRequest() - walks ssl->buffers.certChain (each entry a + * 3-byte length followed by a DER certificate) and, for each one, builds + * and checks an OCSP request against SSL_CM(ssl)->ocsp_stapling. Response + * bytes below are the same "resp_cert_unknown" fixture used by + * tests/api/test_ocsp.c (generated by create_ocsp_test_blobs.py for + * certs/ocsp/intermediate1-ca-cert.pem against certs/ocsp/root-ca-cert.pem), + * copied here under a local name so this file does not pull in + * test_ocsp_test_blobs.h's non-static globals into a second translation + * unit. */ +#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(WOLFSSL_TLS13) && \ + !defined(NO_WOLFSSL_SERVER) && defined(WOLFSSL_TLS_OCSP_MULTI) && \ + defined(HAVE_CERTIFICATE_STATUS_REQUEST) && defined(HAVE_OCSP) && \ + !defined(NO_RSA) && !defined(NO_SHA256) +static const unsigned char csrocsp_resp_unknown[] = { + 0x30, 0x82, 0x07, 0x29, 0x0a, 0x01, 0x00, 0xa0, 0x82, 0x07, 0x22, 0x30, + 0x82, 0x07, 0x1e, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, + 0x01, 0x01, 0x04, 0x82, 0x07, 0x0f, 0x30, 0x82, 0x07, 0x0b, 0x30, 0x82, + 0x01, 0x00, 0xa1, 0x81, 0x9b, 0x30, 0x81, 0x98, 0x31, 0x0b, 0x30, 0x09, + 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, + 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, 0x0a, 0x57, 0x61, 0x73, 0x68, + 0x69, 0x6e, 0x67, 0x74, 0x6f, 0x6e, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, + 0x55, 0x04, 0x07, 0x0c, 0x07, 0x53, 0x65, 0x61, 0x74, 0x74, 0x6c, 0x65, + 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x07, 0x77, + 0x6f, 0x6c, 0x66, 0x53, 0x53, 0x4c, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, + 0x55, 0x04, 0x0b, 0x0c, 0x0b, 0x45, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x65, + 0x72, 0x69, 0x6e, 0x67, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, + 0x03, 0x0c, 0x0f, 0x77, 0x6f, 0x6c, 0x66, 0x53, 0x53, 0x4c, 0x20, 0x72, + 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x09, + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16, 0x11, 0x66, + 0x61, 0x63, 0x74, 0x73, 0x40, 0x77, 0x6f, 0x6c, 0x66, 0x73, 0x73, 0x6c, + 0x2e, 0x63, 0x6f, 0x6d, 0x18, 0x0f, 0x32, 0x30, 0x32, 0x36, 0x30, 0x36, + 0x32, 0x34, 0x31, 0x36, 0x33, 0x37, 0x30, 0x35, 0x5a, 0x30, 0x4f, 0x30, + 0x4d, 0x30, 0x38, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, + 0x04, 0x14, 0x7a, 0x34, 0xec, 0xb3, 0x2b, 0x4f, 0x1b, 0xa2, 0x72, 0x22, + 0x92, 0xa8, 0x4c, 0xc0, 0x12, 0xc7, 0x7a, 0x56, 0x9e, 0x20, 0x04, 0x14, + 0x73, 0xb0, 0x1c, 0xa4, 0x2f, 0x82, 0xcb, 0xcf, 0x47, 0xa5, 0x38, 0xd7, + 0xb0, 0x04, 0x82, 0x3a, 0x7e, 0x72, 0x15, 0x21, 0x02, 0x01, 0x01, 0x82, + 0x00, 0x18, 0x0f, 0x32, 0x30, 0x32, 0x36, 0x30, 0x36, 0x32, 0x34, 0x31, + 0x36, 0x33, 0x37, 0x30, 0x35, 0x5a, 0x30, 0x0b, 0x06, 0x09, 0x2a, 0x86, + 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x03, 0x82, 0x01, 0x01, 0x00, + 0x1c, 0x64, 0xcb, 0x8a, 0x8e, 0x9c, 0x63, 0x4e, 0xd5, 0x6d, 0xbc, 0xac, + 0x92, 0x78, 0x02, 0xf2, 0xb5, 0x40, 0x93, 0x38, 0x5d, 0x28, 0x6c, 0x05, + 0x57, 0x81, 0xf3, 0x8b, 0xd7, 0x0e, 0xbb, 0xfc, 0xee, 0x63, 0x9a, 0x4a, + 0x02, 0x04, 0xd9, 0xa2, 0x61, 0x0f, 0x8e, 0x44, 0xb6, 0x48, 0xf4, 0xfe, + 0x23, 0xab, 0xd6, 0x0c, 0x92, 0xe3, 0x8e, 0xcf, 0x36, 0xf1, 0x7b, 0x6c, + 0xf7, 0x99, 0xb0, 0x83, 0xa0, 0xbd, 0x66, 0x98, 0x02, 0xb0, 0x4b, 0x7a, + 0xf7, 0x77, 0x6e, 0x47, 0xa4, 0xd0, 0x27, 0x2e, 0xa0, 0xbd, 0xb0, 0xa7, + 0xfa, 0xb6, 0x8d, 0x84, 0xef, 0x3a, 0x38, 0xa3, 0x6b, 0x26, 0x73, 0xc0, + 0xd6, 0xef, 0x2f, 0xf9, 0x1b, 0xef, 0x01, 0x2c, 0x4e, 0x36, 0x8d, 0x9b, + 0x45, 0x58, 0xb3, 0x97, 0x46, 0x9f, 0xb5, 0xd2, 0x5a, 0x7d, 0xa6, 0x47, + 0x37, 0xd6, 0xa4, 0x3b, 0x61, 0x97, 0x20, 0xd3, 0x32, 0x0e, 0x7f, 0xac, + 0x76, 0x62, 0x19, 0xab, 0x74, 0x71, 0x7b, 0x89, 0x75, 0xfa, 0x3f, 0x89, + 0xe7, 0xf2, 0x55, 0xeb, 0x32, 0xce, 0xe2, 0x55, 0x98, 0x0b, 0x67, 0x9a, + 0x94, 0x48, 0x95, 0x8e, 0xa3, 0x61, 0x8a, 0x4c, 0x2e, 0xe9, 0xbe, 0x65, + 0xe5, 0x7c, 0x9e, 0x5f, 0xcc, 0xeb, 0x74, 0xee, 0xb2, 0x59, 0x5a, 0x03, + 0xa0, 0xbd, 0xcf, 0x06, 0x95, 0x6b, 0x34, 0x47, 0x19, 0x7e, 0xd0, 0xb4, + 0xcc, 0xf6, 0xb3, 0xdc, 0x46, 0x74, 0x0a, 0x9a, 0x28, 0x57, 0xba, 0x46, + 0x6f, 0xfc, 0x24, 0xcd, 0x82, 0x20, 0x1a, 0x1c, 0x74, 0x0c, 0x37, 0x8e, + 0x22, 0x1f, 0x00, 0x9c, 0x66, 0x4f, 0xf5, 0xbb, 0xeb, 0xe1, 0x7f, 0x0e, + 0xb0, 0x39, 0xde, 0xd8, 0xf2, 0x56, 0xe0, 0xc2, 0xa2, 0x95, 0xbe, 0xad, + 0x9c, 0x10, 0x56, 0x28, 0x8e, 0x50, 0x79, 0x2f, 0xb6, 0xbf, 0x31, 0x6e, + 0x71, 0x53, 0x13, 0x63, 0xa0, 0x82, 0x04, 0xf1, 0x30, 0x82, 0x04, 0xed, + 0x30, 0x82, 0x04, 0xe9, 0x30, 0x82, 0x03, 0xd1, 0xa0, 0x03, 0x02, 0x01, + 0x02, 0x02, 0x01, 0x63, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x81, 0x98, 0x31, 0x0b, + 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, + 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, 0x0a, 0x57, 0x61, + 0x73, 0x68, 0x69, 0x6e, 0x67, 0x74, 0x6f, 0x6e, 0x31, 0x10, 0x30, 0x0e, + 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x07, 0x53, 0x65, 0x61, 0x74, 0x74, + 0x6c, 0x65, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, + 0x07, 0x77, 0x6f, 0x6c, 0x66, 0x53, 0x53, 0x4c, 0x31, 0x14, 0x30, 0x12, + 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x0b, 0x45, 0x6e, 0x67, 0x69, 0x6e, + 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, + 0x55, 0x04, 0x03, 0x0c, 0x0f, 0x77, 0x6f, 0x6c, 0x66, 0x53, 0x53, 0x4c, + 0x20, 0x72, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x31, 0x20, 0x30, 0x1e, + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16, + 0x11, 0x66, 0x61, 0x63, 0x74, 0x73, 0x40, 0x77, 0x6f, 0x6c, 0x66, 0x73, + 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x1e, 0x17, 0x0d, 0x32, 0x36, + 0x30, 0x36, 0x31, 0x31, 0x32, 0x31, 0x34, 0x34, 0x33, 0x34, 0x5a, 0x17, + 0x0d, 0x32, 0x39, 0x30, 0x33, 0x30, 0x37, 0x32, 0x31, 0x34, 0x34, 0x33, + 0x34, 0x5a, 0x30, 0x81, 0x98, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, + 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, + 0x55, 0x04, 0x08, 0x0c, 0x0a, 0x57, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, + 0x74, 0x6f, 0x6e, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x07, + 0x0c, 0x07, 0x53, 0x65, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x31, 0x10, 0x30, + 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x07, 0x77, 0x6f, 0x6c, 0x66, + 0x53, 0x53, 0x4c, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0b, + 0x0c, 0x0b, 0x45, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x65, 0x72, 0x69, 0x6e, + 0x67, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x0f, + 0x77, 0x6f, 0x6c, 0x66, 0x53, 0x53, 0x4c, 0x20, 0x72, 0x6f, 0x6f, 0x74, + 0x20, 0x43, 0x41, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x09, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16, 0x11, 0x66, 0x61, 0x63, 0x74, + 0x73, 0x40, 0x77, 0x6f, 0x6c, 0x66, 0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, + 0x6d, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, + 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xab, 0x2c, + 0xb4, 0x2f, 0x1d, 0x06, 0x09, 0xef, 0x4e, 0x29, 0x86, 0x84, 0x7e, 0xcc, + 0xbf, 0xa6, 0x79, 0x7c, 0xf0, 0xc0, 0xc1, 0x64, 0x25, 0x8c, 0x75, 0xb7, + 0x10, 0x05, 0xca, 0x48, 0x27, 0x0c, 0x0e, 0x32, 0x1c, 0xb0, 0xfe, 0x99, + 0x85, 0x39, 0xb6, 0xb9, 0xa2, 0xf7, 0x27, 0xff, 0x6d, 0x3c, 0x8c, 0x16, + 0x73, 0x29, 0x21, 0x7f, 0x8b, 0xa6, 0x54, 0x71, 0x90, 0xad, 0xcc, 0x05, + 0xb9, 0x9f, 0x15, 0xc7, 0x0a, 0x3f, 0x5f, 0x69, 0xf4, 0x0a, 0x5f, 0x8c, + 0x71, 0xb5, 0x2c, 0xbf, 0x66, 0xe2, 0x03, 0x9a, 0x32, 0xf4, 0xd2, 0xec, + 0x2a, 0x89, 0x4b, 0xf9, 0x35, 0x88, 0x14, 0x33, 0x47, 0x4e, 0x2e, 0x05, + 0x79, 0x01, 0xed, 0x64, 0x36, 0x76, 0xb9, 0xf8, 0x85, 0xcd, 0x01, 0x88, + 0xac, 0xc5, 0xb2, 0xb1, 0x59, 0xb8, 0xcd, 0x5a, 0xf4, 0x09, 0x09, 0x38, + 0x9b, 0xda, 0x5a, 0xcf, 0xce, 0x78, 0x99, 0x1f, 0x49, 0x3d, 0x41, 0xd6, + 0x06, 0x7c, 0x52, 0x99, 0xc8, 0x97, 0xd1, 0xb3, 0x80, 0x3a, 0xa2, 0x4f, + 0x36, 0xc4, 0xc5, 0x96, 0x30, 0x77, 0x31, 0x38, 0xc8, 0x70, 0xcc, 0xe1, + 0x67, 0x06, 0xb3, 0x2b, 0x2f, 0x93, 0xb5, 0x69, 0xcf, 0x83, 0x7e, 0x88, + 0x53, 0x9b, 0x0f, 0x46, 0x21, 0x4c, 0xd6, 0x05, 0x36, 0x44, 0x99, 0x60, + 0x68, 0x47, 0xe5, 0x32, 0x01, 0x12, 0xd4, 0x10, 0x73, 0xae, 0x9a, 0x34, + 0x94, 0xfa, 0x6e, 0xb8, 0x58, 0x4f, 0x7b, 0x5b, 0x8a, 0x92, 0x97, 0xad, + 0xfd, 0x97, 0xb9, 0x75, 0xca, 0xc2, 0xd4, 0x45, 0x7d, 0x17, 0x6b, 0xcd, + 0x2f, 0xf3, 0x63, 0x7a, 0x0e, 0x30, 0xb5, 0x0b, 0xa9, 0xd9, 0xa6, 0x7c, + 0x74, 0x60, 0x9d, 0xcc, 0x09, 0x03, 0x43, 0xf1, 0x0f, 0x90, 0xd3, 0xb7, + 0xfe, 0x6c, 0x9f, 0xd9, 0xcd, 0x78, 0x4b, 0x15, 0xae, 0x8c, 0x5b, 0xf9, + 0x99, 0x81, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x3a, 0x30, + 0x82, 0x01, 0x36, 0x30, 0x0c, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x04, 0x05, + 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, + 0x04, 0x16, 0x04, 0x14, 0x73, 0xb0, 0x1c, 0xa4, 0x2f, 0x82, 0xcb, 0xcf, + 0x47, 0xa5, 0x38, 0xd7, 0xb0, 0x04, 0x82, 0x3a, 0x7e, 0x72, 0x15, 0x21, + 0x30, 0x81, 0xc5, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x81, 0xbd, 0x30, + 0x81, 0xba, 0x80, 0x14, 0x73, 0xb0, 0x1c, 0xa4, 0x2f, 0x82, 0xcb, 0xcf, + 0x47, 0xa5, 0x38, 0xd7, 0xb0, 0x04, 0x82, 0x3a, 0x7e, 0x72, 0x15, 0x21, + 0xa1, 0x81, 0x9e, 0xa4, 0x81, 0x9b, 0x30, 0x81, 0x98, 0x31, 0x0b, 0x30, + 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, + 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, 0x0a, 0x57, 0x61, 0x73, + 0x68, 0x69, 0x6e, 0x67, 0x74, 0x6f, 0x6e, 0x31, 0x10, 0x30, 0x0e, 0x06, + 0x03, 0x55, 0x04, 0x07, 0x0c, 0x07, 0x53, 0x65, 0x61, 0x74, 0x74, 0x6c, + 0x65, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x07, + 0x77, 0x6f, 0x6c, 0x66, 0x53, 0x53, 0x4c, 0x31, 0x14, 0x30, 0x12, 0x06, + 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x0b, 0x45, 0x6e, 0x67, 0x69, 0x6e, 0x65, + 0x65, 0x72, 0x69, 0x6e, 0x67, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, + 0x04, 0x03, 0x0c, 0x0f, 0x77, 0x6f, 0x6c, 0x66, 0x53, 0x53, 0x4c, 0x20, + 0x72, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x31, 0x20, 0x30, 0x1e, 0x06, + 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16, 0x11, + 0x66, 0x61, 0x63, 0x74, 0x73, 0x40, 0x77, 0x6f, 0x6c, 0x66, 0x73, 0x73, + 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x82, 0x01, 0x63, 0x30, 0x0b, 0x06, 0x03, + 0x55, 0x1d, 0x0f, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x32, 0x06, + 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x26, 0x30, + 0x24, 0x30, 0x22, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, + 0x01, 0x86, 0x16, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x31, 0x32, + 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x3a, 0x32, 0x32, 0x32, 0x32, + 0x30, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, + 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x36, 0xb1, 0x86, + 0xd6, 0x72, 0xf8, 0xe7, 0x6a, 0xae, 0x43, 0xfb, 0xc0, 0xed, 0xf1, 0x36, + 0x64, 0x8a, 0xda, 0x8e, 0x5f, 0xf7, 0xc4, 0xad, 0x64, 0xc8, 0x29, 0x03, + 0x74, 0x58, 0xb0, 0x9e, 0xee, 0x41, 0x89, 0x5b, 0x2a, 0x12, 0xf4, 0x82, + 0xa4, 0x03, 0xa5, 0xf0, 0xdf, 0xa2, 0x84, 0xcb, 0x2b, 0xb3, 0x16, 0x0f, + 0xdc, 0xcf, 0xcc, 0x56, 0x99, 0x61, 0xa9, 0xf9, 0x3d, 0x3a, 0x7e, 0xe4, + 0x12, 0x43, 0xc3, 0xb1, 0x4f, 0x58, 0x26, 0x79, 0xe7, 0xe4, 0x0d, 0xa5, + 0x88, 0x3d, 0x79, 0x33, 0xa1, 0x09, 0x7d, 0x78, 0xaf, 0xbd, 0x59, 0x71, + 0x11, 0x54, 0x4a, 0xcc, 0xd6, 0xd2, 0x6d, 0x1f, 0x88, 0x27, 0xac, 0xd5, + 0xbf, 0x75, 0xfc, 0xc3, 0x05, 0x0b, 0xcd, 0xc8, 0x0e, 0x72, 0x41, 0x1d, + 0xd8, 0x68, 0x62, 0xbe, 0x94, 0xd7, 0x60, 0xbe, 0x05, 0x4a, 0x42, 0x9c, + 0x50, 0xb7, 0x45, 0x71, 0x6d, 0x83, 0x9a, 0xef, 0x08, 0x5c, 0x41, 0xdb, + 0xc8, 0x62, 0x33, 0x3c, 0x69, 0xa2, 0x8a, 0xb4, 0x0f, 0xdb, 0x65, 0xc4, + 0xb7, 0x92, 0x0a, 0x76, 0xf7, 0x55, 0x06, 0x77, 0x8c, 0xff, 0x8c, 0x84, + 0x84, 0xd9, 0xdd, 0x46, 0x11, 0x2a, 0x2d, 0x27, 0x96, 0xa7, 0xf5, 0x47, + 0xc1, 0x43, 0x4b, 0xfe, 0x53, 0xd8, 0xbe, 0x16, 0x94, 0x36, 0x0a, 0xd4, + 0xbe, 0xc3, 0x6c, 0x9b, 0x0c, 0x52, 0x31, 0x4a, 0xeb, 0x62, 0xb4, 0x81, + 0x4b, 0x2d, 0xf7, 0xf1, 0x65, 0xc1, 0xee, 0x36, 0x79, 0x19, 0xf7, 0xab, + 0x16, 0xf8, 0x38, 0xd2, 0xea, 0x87, 0x8d, 0xf8, 0xf9, 0x14, 0x82, 0xdb, + 0x67, 0xb6, 0x94, 0xa8, 0x55, 0x0b, 0x90, 0x6b, 0xaf, 0xb0, 0xe9, 0x42, + 0x64, 0x42, 0x6d, 0x2c, 0xe3, 0xf1, 0xb6, 0xe0, 0xf9, 0x58, 0xed, 0x69, + 0x66, 0x62, 0x98, 0xdc, 0x5a, 0x7b, 0xfa, 0x35, 0x5a, 0x23, 0x84, 0x91, + 0x0e, +}; + +enum { + CSROCSP_MODE_UNKNOWN = 0, + CSROCSP_MODE_LOOKUP_FAIL, + CSROCSP_MODE_WANT_READ +}; + +static int test_ProcessChainOCSPRequest_io_cb(void* ctx, const char* url, + int urlSz, unsigned char* req, int reqSz, unsigned char** respBuf) +{ + int mode = *(int*)ctx; + static const unsigned char garbage[] = { 0xFF, 0x00, 0x11, 0x22 }; + (void)url; (void)urlSz; (void)req; (void)reqSz; + + switch (mode) { + case CSROCSP_MODE_UNKNOWN: + *respBuf = (unsigned char*)csrocsp_resp_unknown; + return (int)sizeof(csrocsp_resp_unknown); + case CSROCSP_MODE_LOOKUP_FAIL: + *respBuf = (unsigned char*)garbage; + return (int)sizeof(garbage); + case CSROCSP_MODE_WANT_READ: + default: + return WOLFSSL_CBIO_ERR_WANT_READ; + } +} + +/* Common setup for the "one real, matching chain entry" vectors: server1 + * (leaf) + intermediate1 (the chain entry ProcessChainOCSPRequest() will + * process) issued off root-ca, matching the identity the csrocsp_resp_* + * fixtures above were generated against. */ +static int test_ProcessChainOCSPRequest_setup(WOLFSSL_CTX** pctx, + WOLFSSL** pssl, CertificateStatusRequest** pcsr, int* mode) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + TLSX* ext = NULL; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + ExpectIntEQ(wolfSSL_CTX_use_certificate_chain_file(ctx, + "./certs/ocsp/server1-chain-noroot.pem"), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx, + "./certs/ocsp/server1-key.pem", WOLFSSL_FILETYPE_PEM), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx, + "./certs/ocsp/root-ca-cert.pem", NULL), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_EnableOCSPStapling(ctx), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_EnableOCSP(ctx, WOLFSSL_OCSP_URL_OVERRIDE | + WOLFSSL_OCSP_NO_NONCE), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_SetOCSP_OverrideURL(ctx, "http://dummy.test"), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_SetOCSP_Cb(ctx, test_ProcessChainOCSPRequest_io_cb, + NULL, (void*)mode), WOLFSSL_SUCCESS); + + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + ExpectIntEQ(TLSX_UseCertificateStatusRequest(&ssl->extensions, + WOLFSSL_CSR_OCSP, 0, ssl, ssl->heap, ssl->devId), + WOLFSSL_SUCCESS); + ExpectNotNull(ext = TLSX_Find(ssl->extensions, TLSX_STATUS_REQUEST)); + if (ext != NULL) + *pcsr = (CertificateStatusRequest*)ext->data; + } + + *pctx = ctx; + *pssl = ssl; + return EXPECT_RESULT(); +} + +int test_ProcessChainOCSPRequest_bounds(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + CertificateStatusRequest* csr = NULL; + int mode; + byte garbageThenValid[8 + 3 + 4096]; + byte intermediate1Der[4096]; + word32 interLen = 0; + XFILE f = XBADFILE; + DerBuffer fakeChain; + + /* chain == NULL (both certChain and certificate unset): the leading + * "chain && chain->buffer" guard is false at its first operand, the + * whole walk is skipped, and the function returns success with + * nothing done. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + ExpectIntEQ(test_tls_bounds_load_server_cert(ctx), TEST_SUCCESS); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + TLSX* ext = NULL; + ExpectIntEQ(TLSX_UseCertificateStatusRequest(&ssl->extensions, + WOLFSSL_CSR_OCSP, 0, ssl, ssl->heap, ssl->devId), + WOLFSSL_SUCCESS); + ExpectNotNull(ext = TLSX_Find(ssl->extensions, TLSX_STATUS_REQUEST)); + if (ext != NULL) + csr = (CertificateStatusRequest*)ext->data; + /* A certificate had to be loaded for wolfSSL_new() to succeed (see + * test_tls_bounds_load_server_cert()); clear both buffers back to + * NULL so ProcessChainOCSPRequest() sees exactly the "chain == + * NULL" state under test. */ + ssl->buffers.certChain = NULL; + ssl->buffers.certificate = NULL; + ExpectIntEQ(ProcessChainOCSPRequest(ssl), 0); + } + wolfSSL_free(ssl); + ssl = NULL; + wolfSSL_CTX_free(ctx); + ctx = NULL; + + /* chain != NULL, chain->buffer == NULL: second operand false, + * independence from the chain == NULL vector above (first operand). */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + ExpectIntEQ(test_tls_bounds_load_server_cert(ctx), TEST_SUCCESS); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + TLSX* ext = NULL; + ExpectIntEQ(TLSX_UseCertificateStatusRequest(&ssl->extensions, + WOLFSSL_CSR_OCSP, 0, ssl, ssl->heap, ssl->devId), + WOLFSSL_SUCCESS); + ExpectNotNull(ext = TLSX_Find(ssl->extensions, TLSX_STATUS_REQUEST)); + if (ext != NULL) + csr = (CertificateStatusRequest*)ext->data; + XMEMSET(&fakeChain, 0, sizeof(fakeChain)); + ssl->buffers.certChain = &fakeChain; + ExpectIntEQ(ProcessChainOCSPRequest(ssl), 0); + ssl->buffers.certChain = NULL; + } + wolfSSL_free(ssl); + ssl = NULL; + wolfSSL_CTX_free(ctx); + ctx = NULL; + + /* while-loop operand 0 (ret == 0) false side: a two-entry chain whose + * first entry is not a certificate at all, so CreateOcspRequest() fails + * with a hard ASN.1 parse error - not one of the soft-fail codes the + * loop body resets to 0 - while its second (valid) entry is still + * unconsumed, forcing the while condition's re-check to see ret != 0 + * with more chain data pending. */ + ExpectNotNull(f = XFOPEN("./certs/ocsp/intermediate1-ca-cert.der", "rb")); + if (f != XBADFILE) { + interLen = (word32)XFREAD(intermediate1Der, 1, sizeof(intermediate1Der), + f); + XFCLOSE(f); + } + ExpectIntGT(interLen, 0); + ExpectIntLT(interLen, sizeof(intermediate1Der)); + { + word32 idx = 0; + static const byte notACert[] = { 0xFF, 0xFF, 0xFF, 0xFF }; + + test_tls_bounds_c32to24(sizeof(notACert), garbageThenValid + idx); + idx += OPAQUE24_LEN; + XMEMCPY(garbageThenValid + idx, notACert, sizeof(notACert)); + idx += sizeof(notACert); + + test_tls_bounds_c32to24(interLen, garbageThenValid + idx); + idx += OPAQUE24_LEN; + XMEMCPY(garbageThenValid + idx, intermediate1Der, interLen); + idx += interLen; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + ExpectIntEQ(test_tls_bounds_load_server_cert(ctx), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_EnableOCSPStapling(ctx), WOLFSSL_SUCCESS); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + TLSX* ext = NULL; + ExpectIntEQ(TLSX_UseCertificateStatusRequest(&ssl->extensions, + WOLFSSL_CSR_OCSP, 0, ssl, ssl->heap, ssl->devId), + WOLFSSL_SUCCESS); + ExpectNotNull(ext = TLSX_Find(ssl->extensions, + TLSX_STATUS_REQUEST)); + if (ext != NULL) + csr = (CertificateStatusRequest*)ext->data; + + XMEMSET(&fakeChain, 0, sizeof(fakeChain)); + fakeChain.buffer = garbageThenValid; + fakeChain.length = idx; + ssl->buffers.certChain = &fakeChain; + + /* Not zero (ASN parse error) and not one of ProcessChainOCSPRequest's + * own soft-fail codes, so it propagates as the loop's exit ret. */ + ExpectIntNE(ProcessChainOCSPRequest(ssl), 0); + ExpectIntNE(ProcessChainOCSPRequest(ssl), + WC_NO_ERR_TRACE(OCSP_CERT_UNKNOWN)); + ExpectIntNE(ProcessChainOCSPRequest(ssl), + WC_NO_ERR_TRACE(OCSP_LOOKUP_FAIL)); + ExpectIntNE(ProcessChainOCSPRequest(ssl), + WC_NO_ERR_TRACE(OCSP_NO_URL)); + + ssl->buffers.certChain = NULL; + } + wolfSSL_free(ssl); + ssl = NULL; + wolfSSL_CTX_free(ctx); + ctx = NULL; + } + + /* One real, matching chain entry (intermediate1): the while loop enters + * (operand 0 and operand 1 both true) and, after processing the single + * entry, exits normally with pos == chain->length (operand 1 false, + * operand 0 still true) - independence for operand 1, paired against + * the forced-error vector above (operand 0 false). Run three times with + * a different responder outcome each time (a fresh CertManager each + * time, so no cached status from one run leaks into the next), driving + * the three-way soft-fail classification. */ + mode = CSROCSP_MODE_UNKNOWN; + ExpectIntEQ(test_ProcessChainOCSPRequest_setup(&ctx, &ssl, &csr, &mode), + TEST_SUCCESS); + if (csr != NULL) { + /* ret is OCSP_CERT_UNKNOWN: first operand of the three-way OR. */ + ExpectIntEQ(ProcessChainOCSPRequest(ssl), 0); + } + wolfSSL_free(ssl); + ssl = NULL; + wolfSSL_CTX_free(ctx); + ctx = NULL; + csr = NULL; + + mode = CSROCSP_MODE_LOOKUP_FAIL; + ExpectIntEQ(test_ProcessChainOCSPRequest_setup(&ctx, &ssl, &csr, &mode), + TEST_SUCCESS); + if (csr != NULL) { + /* ret is OCSP_LOOKUP_FAIL (garbage response bytes fail to decode): + * second operand true, first false - independence for the second + * operand. */ + ExpectIntEQ(ProcessChainOCSPRequest(ssl), 0); + } + wolfSSL_free(ssl); + ssl = NULL; + wolfSSL_CTX_free(ctx); + ctx = NULL; + csr = NULL; + + /* ret is OCSP_NO_URL: reached without any responder call at all, by + * leaving the override URL unset and using a chain (server-cert.pem + + * ca-cert.pem) whose certificates carry no OCSP responder AIA URL. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + ExpectIntEQ(wolfSSL_CTX_use_certificate_chain_file(ctx, + "./certs/server-cert.pem"), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx, + "./certs/server-key.pem", WOLFSSL_FILETYPE_PEM), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx, + "./certs/ca-cert.pem", NULL), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_EnableOCSPStapling(ctx), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_EnableOCSP(ctx, WOLFSSL_OCSP_NO_NONCE), + WOLFSSL_SUCCESS); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + TLSX* ext = NULL; + ExpectIntEQ(TLSX_UseCertificateStatusRequest(&ssl->extensions, + WOLFSSL_CSR_OCSP, 0, ssl, ssl->heap, ssl->devId), + WOLFSSL_SUCCESS); + ExpectNotNull(ext = TLSX_Find(ssl->extensions, TLSX_STATUS_REQUEST)); + if (ext != NULL) + csr = (CertificateStatusRequest*)ext->data; + ExpectNotNull(ssl->buffers.certChain); + if (csr != NULL) { + /* ret is OCSP_NO_URL: third operand true, first and second + * false - independence for the third operand. */ + ExpectIntEQ(ProcessChainOCSPRequest(ssl), 0); + } + } + wolfSSL_free(ssl); + ssl = NULL; + wolfSSL_CTX_free(ctx); + ctx = NULL; + csr = NULL; + + /* ret is OCSP_WANT_READ (via WOLFSSL_CBIO_ERR_WANT_READ from the I/O + * callback): none of the three - the baseline all-false vector paired + * against each of the three above. */ + mode = CSROCSP_MODE_WANT_READ; + ExpectIntEQ(test_ProcessChainOCSPRequest_setup(&ctx, &ssl, &csr, &mode), + TEST_SUCCESS); + if (csr != NULL) { + ExpectIntEQ(ProcessChainOCSPRequest(ssl), + WC_NO_ERR_TRACE(OCSP_WANT_READ)); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + return EXPECT_RESULT(); +} +#endif + +/* ---------------------------------------------------------------------- */ +/* TLSX_PopulateExtensions() - called directly (WOLFSSL_LOCAL), the same way + * SendClientHello()/SendTls13ClientHello() call it before any handshake + * bytes are produced, so a bare freshly-created ssl is exactly the state a + * real caller reaches this function with. + * + * "16880:17:16880:60:0" - "if (!isServer && ssl->options.postHandshakeAuth)" + * - is not covered here and is excluded rather than driven: it sits inside + * "if (!isServer && IsAtLeastTLSv1_3(ssl->version)) { ... }" (opened many + * lines above, never re-assigning isServer in between), so reaching this + * line at all already requires isServer == 0. Its own "!isServer" operand + * is therefore fixed true on every execution that reaches it - excluded + * (family 3: fixed by the branch that reaches it). */ +#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(WOLFSSL_TLS13) && \ + !defined(NO_WOLFSSL_CLIENT) && defined(HAVE_SUPPORTED_CURVES) && \ + (defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)) && !defined(NO_PSK) && \ + defined(HAVE_AESGCM) && !defined(NO_AES) && !defined(NO_SHA256) +static unsigned int test_TLSX_PopulateExtensions_psk_cb(WOLFSSL* ssl, + const char* hint, char* identity, unsigned int id_max_len, + unsigned char* key, unsigned int key_max_len) +{ + int mode = *(int*)wolfSSL_get_psk_callback_ctx(ssl); + (void)hint; (void)key_max_len; + + XSTRNCPY(identity, "id", id_max_len); + key[0] = 0x01; + + /* mode 0: > MAX_PSK_KEY_LEN and not equal to USE_HW_PSK - both operands true. + * mode 1: > MAX_PSK_KEY_LEN and equal to USE_HW_PSK - operand 0 true, operand + * 1 false. + * mode 2: <= MAX_PSK_KEY_LEN - operand 0 false, short-circuits. */ + switch (mode) { + case 0: + return MAX_PSK_KEY_LEN + 1; + case 1: + return (unsigned int)WC_NO_ERR_TRACE(USE_HW_PSK); + default: + return 4; + } +} + +static unsigned int test_TLSX_PopulateExtensions_psk_tls13_cb(WOLFSSL* ssl, + const char* hint, char* identity, unsigned int id_max_len, + unsigned char* key, unsigned int key_max_len, const char** ciphersuite) +{ + (void)ssl; (void)hint; (void)key_max_len; + XSTRNCPY(identity, "id", id_max_len); + key[0] = 0x01; + *ciphersuite = "TLS13-AES128-GCM-SHA256"; + return 4; +} + +int test_TLSX_PopulateExtensions_bounds(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + int mode; + + /* Operand 1 of "!ssl->options.userCurves && !ssl->ctx->userCurves": + * ssl->options.userCurves inherits from ctx->userCurves at wolfSSL_new() + * (InitSSL()), so a ctx with a user curve list makes both true unless + * the ssl-level copy is forced back to 0 - no public caller can produce + * "ctx->userCurves set, ssl->options.userCurves clear" any other way. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectIntEQ(wolfSSL_CTX_UseSupportedCurve(ctx, WOLFSSL_ECC_SECP256R1), + WOLFSSL_SUCCESS); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + ExpectIntEQ(ssl->options.userCurves, 1); + ssl->options.userCurves = 0; + ExpectIntEQ(TLSX_PopulateExtensions(ssl, 0), 0); + ExpectNull(TLSX_Find(ssl->extensions, TLSX_SUPPORTED_GROUPS)); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* The point-format decision's four leaf conditions are + * "(!IsAtLeastTLSv1_3(ssl->version) || ssl->options.downgrade) && + * TLSX_Find(ssl->ctx->extensions, TLSX_EC_POINT_FORMATS) == NULL && + * TLSX_Find(ssl->extensions, TLSX_EC_POINT_FORMATS) == NULL" + * (operand indices 0-3); the worklist's ":2" is the ctx-level Find, not + * the ssl-level one (index 3, already paired elsewhere). TLS 1.2 keeps + * operand 0 true throughout; ssl->extensions is left without the + * extension in both vectors below so operand 3 stays fixed true while + * only the ctx-level Find (operand 2) is flipped. */ +#if defined(HAVE_ECC) || defined(HAVE_CURVE25519) || defined(HAVE_CURVE448) + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + /* ctx-level Find != NULL: operand 2 false. */ + ExpectIntEQ(TLSX_UsePointFormat(&ssl->ctx->extensions, + WOLFSSL_EC_PF_UNCOMPRESSED, ssl->ctx->heap), + WOLFSSL_SUCCESS); + ExpectIntEQ(TLSX_PopulateExtensions(ssl, 0), 0); + ExpectNull(TLSX_Find(ssl->extensions, TLSX_EC_POINT_FORMATS)); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* ctx-level Find == NULL (default, fresh ctx): operand 2 true - + * independence for operand 2, paired against the vector above. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(TLSX_PopulateExtensions(ssl, 0), 0); + ExpectNotNull(TLSX_Find(ssl->extensions, TLSX_EC_POINT_FORMATS)); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + + /* Operand 1 of "ssl->options.resuming && ssl->session->namedGroup != 0": + * resuming fixed true, namedGroup flipped. A default (non-resuming) call + * is the false/false pair already exercised in ordinary handshake tests + * elsewhere; this isolates the second operand specifically. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + ssl->options.resuming = 1; + ssl->session->namedGroup = 0; + ExpectIntEQ(TLSX_PopulateExtensions(ssl, 0), 0); + ssl->session->namedGroup = WOLFSSL_ECC_SECP256R1; + ExpectIntEQ(TLSX_PopulateExtensions(ssl, 0), 0); + ssl->options.resuming = 0; + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* Operand 1 of "ssl->options.resuming && ssl->session->ticketLen > 0": + * resuming fixed true, ticketLen flipped. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + byte ticket[4] = { 1, 2, 3, 4 }; + + ssl->options.resuming = 1; + ssl->session->ticketLen = 0; + ExpectIntEQ(TLSX_PopulateExtensions(ssl, 0), 0); + + XMEMCPY(ssl->session->ticket, ticket, sizeof(ticket)); + ssl->session->ticketLen = sizeof(ticket); + /* SetCipherSpecs(ssl) (reached because ticketLen > 0) needs a + * suite it recognizes; a fresh session's cipherSuite0/cipherSuite + * default to 0, which is not one. */ + ssl->session->cipherSuite0 = TLS13_BYTE; + ssl->session->cipherSuite = TLS_AES_128_GCM_SHA256; + ExpectIntEQ(TLSX_PopulateExtensions(ssl, 0), 0); + ssl->options.resuming = 0; + ssl->session->ticketLen = 0; + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* Operand 1 of "client_psk_cb != NULL || client_psk_tls13_cb != NULL": + * operand 0 fixed false (no client_psk_cb), operand 1 flipped true by + * registering a tls13 callback that reports "no key available" (a + * 0-length key), which OPENSSL_EXTRA would treat specially but this + * build (no OPENSSL_EXTRA) routes through the same + * "> MAX_PSK_KEY_LEN" guard as any other size, taking its false side. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + wolfSSL_set_psk_client_tls13_callback(ssl, + test_TLSX_PopulateExtensions_psk_tls13_cb); + ExpectIntEQ(TLSX_PopulateExtensions(ssl, 0), 0); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* Both operands of "psk_keySz > MAX_PSK_KEY_LEN" and "not equal to USE_HW_PSK", + * reached through the plain client_psk_cb path. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + + mode = 0; + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + wolfSSL_set_psk_client_callback(ssl, + test_TLSX_PopulateExtensions_psk_cb); + wolfSSL_set_psk_callback_ctx(ssl, &mode); + /* Both operands true: PSK_KEY_ERROR (no OPENSSL_EXTRA in this + * build, so the 0-length special case does not apply). */ + ExpectIntEQ(TLSX_PopulateExtensions(ssl, 0), + WC_NO_ERR_TRACE(PSK_KEY_ERROR)); + } + wolfSSL_free(ssl); + + mode = 1; + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + wolfSSL_set_psk_client_callback(ssl, + test_TLSX_PopulateExtensions_psk_cb); + wolfSSL_set_psk_callback_ctx(ssl, &mode); + /* Operand 0 true, operand 1 false - independence for operand 1. */ + ExpectIntEQ(TLSX_PopulateExtensions(ssl, 0), 0); + } + wolfSSL_free(ssl); + + mode = 2; + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + wolfSSL_set_psk_client_callback(ssl, + test_TLSX_PopulateExtensions_psk_cb); + wolfSSL_set_psk_callback_ctx(ssl, &mode); + /* Operand 0 false - independence for operand 0. */ + ExpectIntEQ(TLSX_PopulateExtensions(ssl, 0), 0); + } + wolfSSL_free(ssl); + + wolfSSL_CTX_free(ctx); + return EXPECT_RESULT(); +} +#endif + +/* ---------------------------------------------------------------------- */ +/* TLSX_PopulateSupportedGroups() is static-in-file, reached only through + * TLSX_PopulateExtensions(). Its ML-KEM blocks all share + * "IsAtLeastTLSv1_3(ssl->version) && TLSX_IsMlKemGroupSupported(ssl->options.side)"; + * the worklist carries only the second operand (the first already has a + * pair elsewhere). TLSX_IsMlKemGroupSupported() is: + * if (side == WOLFSSL_CLIENT_END) return ; + * else if (side == WOLFSSL_SERVER_END) return ; + * else return ; + * and this build has both WOLFSSL_HAVE_MLKEM_CLIENT_SUPPORT and + * WOLFSSL_HAVE_MLKEM_SERVER_SUPPORT defined (neither + * WOLFSSL_MLKEM_NO_MAKE_KEY/_NO_DECAPSULATE/_NO_ENCAPSULATE is set) - so + * every one of its three branches returns 1, for every possible byte value + * of ssl->options.side, not just WOLFSSL_CLIENT_END/_SERVER_END/_NEITHER_END. + * Confirmed by direct call: TLSX_IsMlKemGroupSupported(WOLFSSL_NEITHER_END) + * returns 1 here, not 0. There is no side value - real or corrupted - that + * makes this operand false in this build. Excluded (family 4: contradicted + * by TLSX_IsMlKemGroupSupported()'s own postcondition under this build's + * feature set). */ +#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(WOLFSSL_TLS13) && \ + !defined(NO_WOLFSSL_CLIENT) && defined(HAVE_SUPPORTED_CURVES) && \ + defined(WOLFSSL_HAVE_MLKEM_CLIENT_SUPPORT) && !defined(WOLFSSL_NO_ML_KEM) && \ + !defined(NO_DH) && defined(HAVE_FFDHE_2048) +int test_TLSX_PopulateSupportedGroups_bounds(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + + /* Default client: side == WOLFSSL_CLIENT_END, so + * TLSX_IsMlKemGroupSupported() is true for all four ML-KEM blocks + * (its only reachable value in this build - see above). */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(TLSX_PopulateExtensions(ssl, 0), 0); + wolfSSL_free(ssl); + ssl = NULL; + wolfSSL_CTX_free(ctx); + + /* "2048/8 >= minDhKeySz && 2048/8 <= maxDhKeySz": both operands. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + + /* Default range: both operands true. */ + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(TLSX_PopulateExtensions(ssl, 0), 0); + wolfSSL_free(ssl); + ssl = NULL; + + /* minDhKeySz raised above 2048 bits: first operand false. */ + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + ExpectIntEQ(wolfSSL_SetMinDhKey_Sz(ssl, 4096), WOLFSSL_SUCCESS); + ExpectIntEQ(TLSX_PopulateExtensions(ssl, 0), 0); + } + wolfSSL_free(ssl); + ssl = NULL; + + /* maxDhKeySz lowered below 2048 bits, minDhKeySz left at its default + * (below 2048 bits): first operand true, second false - independence + * for the second operand. */ + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + ExpectIntEQ(wolfSSL_SetMaxDhKey_Sz(ssl, 1024), WOLFSSL_SUCCESS); + ExpectIntEQ(TLSX_PopulateExtensions(ssl, 0), 0); + } + wolfSSL_free(ssl); + + wolfSSL_CTX_free(ctx); + return EXPECT_RESULT(); +} +#endif + +/* ---------------------------------------------------------------------- */ +/* TLSX_CSR_Parse() (client, TLS 1.3, receiving a CertificateStatus + * response) and TLSX_CSR2_Parse() / TLSX_CSR_Parse() (server, parsing a + * status_request(_v2) request from a ClientHello) - reached through the + * WOLFSSL_TEST_VIS TLSX_Parse(). */ +#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(WOLFSSL_TLS13) && \ + defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) +static int test_TLSX_CSR_Parse_fail_after = -1; +static int test_TLSX_CSR_Parse_alloc_seen = 0; + +static void* test_TLSX_CSR_Parse_fail_malloc(size_t size) +{ + if (test_TLSX_CSR_Parse_fail_after >= 0) { + if (test_TLSX_CSR_Parse_alloc_seen == test_TLSX_CSR_Parse_fail_after) { + test_TLSX_CSR_Parse_alloc_seen++; + return NULL; + } + test_TLSX_CSR_Parse_alloc_seen++; + } + return malloc(size); +} + +static void test_TLSX_CSR_Parse_fail_free(void* ptr) +{ + free(ptr); +} + +static void* test_TLSX_CSR_Parse_fail_realloc(void* ptr, size_t size) +{ + return realloc(ptr, size); +} + +int test_TLSX_CSR_Parse_bounds(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte ext[16]; + word16 extLen; + wolfSSL_Malloc_cb prevM = NULL; + wolfSSL_Free_cb prevF = NULL; + wolfSSL_Realloc_cb prevR = NULL; + + /* Server side, isRequest == true: "SSL_CM(ssl) == NULL || + * !SSL_CM(ssl)->ocspStaplingEnabled". A single status_request entry + * (status_type == OCSP, empty responder_id_list and request_extensions) + * with SSL_CM(ssl) forced NULL - first operand true. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_server_method())); + ExpectIntEQ(test_tls_bounds_load_server_cert(ctx), TEST_SUCCESS); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + Suites* suites = (Suites*)WOLFSSL_SUITES(ssl); + WOLFSSL_CERT_MANAGER* origCM = ssl->ctx->cm; + + /* type(2) + len(2) + status_type(1) + responder_id_list len(2)=0 + + * request_ext len(2)=0 */ + ext[0] = (byte)(TLSXT_STATUS_REQUEST >> 8); + ext[1] = (byte)(TLSXT_STATUS_REQUEST & 0xFF); + ext[2] = 0; ext[3] = 5; /* extension body length */ + ext[4] = WOLFSSL_CSR_OCSP; + ext[5] = 0; ext[6] = 0; + ext[7] = 0; ext[8] = 0; + extLen = 4 + 5; + + ssl->ctx->cm = NULL; + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), 0); + ssl->ctx->cm = origCM; + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* Client side, isRequest == false, TLS 1.3: the internal ticket-style + * malloc for csr->responses[idx].buffer. + * Operand 0 (ret == 0) of "if (ret == 0 && + * csr->responses[ssl->response_idx].buffer == NULL)": forced to true + * by failing that allocation (operand 1 held true), then to false by + * pushing ssl->response_idx out of range so an earlier guard already + * set ret != 0 before this line is reached - independence for operand + * 0, with operand 1's own pair already covered elsewhere. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + /* type(2) + length(2) + status_type(1) + resp_length(3) + 4 bytes + * of (fake) response data. */ + byte body[HELLO_EXT_TYPE_SZ + OPAQUE16_LEN + OPAQUE8_LEN + + OPAQUE24_LEN + 4]; + word16 bodyLen; + + ExpectIntEQ(TLSX_UseCertificateStatusRequest(&ssl->extensions, + WOLFSSL_CSR_OCSP, 0, ssl, ssl->heap, ssl->devId), + WOLFSSL_SUCCESS); + ssl->options.tls1_3 = 1; + + body[0] = (byte)(TLSXT_STATUS_REQUEST >> 8); + body[1] = (byte)(TLSXT_STATUS_REQUEST & 0xFF); + body[2] = 0; + body[3] = OPAQUE8_LEN + OPAQUE24_LEN + 4; + body[4] = WOLFSSL_CSR_OCSP; + test_tls_bounds_c32to24(4, body + 4 + OPAQUE8_LEN); + XMEMSET(body + 4 + OPAQUE8_LEN + OPAQUE24_LEN, 0xAB, 4); + bodyLen = 4 + OPAQUE8_LEN + OPAQUE24_LEN + 4; + + /* ret == 0, forced allocation failure: operand 0 true, operand 1 + * (buffer == NULL) true - overall true, ret becomes MEMORY_ERROR. */ + ssl->response_idx = 0; + ExpectIntEQ(wolfSSL_GetAllocators(&prevM, &prevF, &prevR), 0); + ExpectIntEQ(wolfSSL_SetAllocators(test_TLSX_CSR_Parse_fail_malloc, + test_TLSX_CSR_Parse_fail_free, + test_TLSX_CSR_Parse_fail_realloc), 0); + test_TLSX_CSR_Parse_alloc_seen = 0; + test_TLSX_CSR_Parse_fail_after = 0; + ExpectIntEQ(TLSX_Parse(ssl, body, bodyLen, certificate, NULL), + WC_NO_ERR_TRACE(MEMORY_ERROR)); + test_TLSX_CSR_Parse_fail_after = -1; + (void)wolfSSL_SetAllocators(prevM, prevF, prevR); + + /* response_idx out of range: an earlier guard sets ret == + * BAD_FUNC_ARG before this decision is reached at all - operand 0 + * false, independence from the vector above. */ + ssl->response_idx = 1 + MAX_CHAIN_DEPTH; + ExpectIntEQ(TLSX_Parse(ssl, body, bodyLen, certificate, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ssl->response_idx = 0; + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + return EXPECT_RESULT(); +} +#endif + +#if defined(WOLFSSL_TEST_STATIC_BUILD) && \ + defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) && !defined(NO_WOLFSSL_SERVER) +int test_TLSX_CSR2_Parse_bounds(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte ext[9]; + WOLFSSL_CERT_MANAGER* origCM = NULL; + + /* Server side: "SSL_CM(ssl) == NULL || !SSL_CM(ssl)->ocspStaplingEnabled", + * SSL_CM(ssl) forced NULL - first operand true. One status_request_v2 + * entry (status_type == OCSP, empty responder_id_list/request_extensions). */ + ext[0] = 0; ext[1] = 7; /* overall list size */ + ext[2] = WOLFSSL_CSR2_OCSP; /* status_type */ + ext[3] = 0; ext[4] = 4; /* inner request length */ + ext[5] = 0; ext[6] = 0; /* responder_id_list length */ + ext[7] = 0; ext[8] = 0; /* request_extensions length */ + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_server_method())); + ExpectIntEQ(test_tls_bounds_load_server_cert(ctx), TEST_SUCCESS); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + Suites* suites = (Suites*)WOLFSSL_SUITES(ssl); + byte full[9 + 4]; + word16 fullLen; + + full[0] = (byte)(TLSXT_STATUS_REQUEST_V2 >> 8); + full[1] = (byte)(TLSXT_STATUS_REQUEST_V2 & 0xFF); + full[2] = 0; full[3] = sizeof(ext); + XMEMCPY(full + 4, ext, sizeof(ext)); + fullLen = 4 + sizeof(ext); + + origCM = ssl->ctx->cm; + ssl->ctx->cm = NULL; + ExpectIntEQ(TLSX_Parse(ssl, full, fullLen, client_hello, suites), 0); + ssl->ctx->cm = origCM; + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + return EXPECT_RESULT(); +} +#endif + +/* ---------------------------------------------------------------------- */ +/* TLSX_WriteRequest() / TLSX_GetRequestSize() - the "ssl->ctx && + * ssl->ctx->extensions" merge guard. msgType values other than client_hello + * and certificate_request skip the whole leading block (including the + * SSL_CM(ssl) dereference that a NULL ssl->ctx could not survive), landing + * directly on this guard, so ssl->ctx can safely be forced NULL only for + * such a msgType. */ +#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(WOLFSSL_TLS13) && \ + !defined(NO_WOLFSSL_CLIENT) +int test_TLSX_ext_dispatch_ctx_extensions_bounds(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + WOLFSSL_CTX* savedCtx = NULL; + byte out[64]; + word32 offset; + word32 len; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + /* ssl->ctx == NULL: first operand false. server_hello is neither + * client_hello nor certificate_request, so the leading blocks that + * dereference ssl->ctx are skipped entirely before this line. */ + savedCtx = ssl->ctx; + ssl->ctx = NULL; + len = 0; + ExpectIntEQ(TLSX_GetRequestSize(ssl, server_hello, &len), 0); + offset = 0; + ExpectIntEQ(TLSX_WriteRequest(ssl, out, server_hello, &offset), 0); + ssl->ctx = savedCtx; + + /* ssl->ctx != NULL, ssl->ctx->extensions == NULL (default): second + * operand false, first true. Still server_hello: with HAVE_ECH + * defined (as it is in this build), msgType == client_hello takes + * a wholly different "if (!ssl->options.disableECH && msgType == + * client_hello) { ... } else { }" branch that never + * reaches this guard at all - server_hello is what actually lands + * on it, for every vector here. */ + len = 0; + ExpectIntEQ(TLSX_GetRequestSize(ssl, server_hello, &len), 0); + offset = 0; + ExpectIntEQ(TLSX_WriteRequest(ssl, out, server_hello, &offset), 0); + + /* ssl->ctx != NULL, ssl->ctx->extensions != NULL: both true. A + * renegotiation-info extension with NULL data writes just its + * length byte. server_hello has isRequest == false, so it also + * needs resp marked or TLSX_GetSize()/TLSX_Write()'s own + * "!isRequest && !extension->resp" guard skips it before ever + * reaching the ssl->ctx merge under test. */ + { + TLSX* ext; + ExpectIntEQ(TLSX_Push(&ssl->ctx->extensions, + TLSX_RENEGOTIATION_INFO, NULL, ssl->ctx->heap), 0); + ExpectNotNull(ext = TLSX_Find(ssl->ctx->extensions, + TLSX_RENEGOTIATION_INFO)); + if (ext != NULL) + ext->resp = 1; + } + len = 0; + ExpectIntEQ(TLSX_GetRequestSize(ssl, server_hello, &len), 0); + ExpectTrue(len > 0); + offset = 0; + ExpectIntEQ(TLSX_WriteRequest(ssl, out, server_hello, &offset), 0); + ExpectTrue(offset > 0); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + return EXPECT_RESULT(); +} +#endif + +/* ---------------------------------------------------------------------- */ +/* TLSX_WriteRequest()'s and TLSX_GetRequestSize()'s extended-master-secret + * tails - both "msgType == client_hello && ssl->options.haveEMS && + * (!IsAtLeastTLSv1_3(ssl->version) || ssl->options.downgrade)" - only the + * second operand (haveEMS) is open on each; the worklist's ":1" suffix. The + * first (msgType) and third (version/downgrade) already have pairs + * elsewhere. Both tails sit after the HAVE_ECH branch merges back together, + * so client_hello reaches them the same way in either function. */ +#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(HAVE_EXTENDED_MASTER) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(WOLFSSL_NO_TLS12) +int test_TLSX_WriteRequest_ems_bounds(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte out[64]; + word32 offset; + word32 len; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + /* haveEMS == 0 (HAVE_EXTENDED_MASTER defaults it to 1; forced off + * here): second operand false. */ + ssl->options.haveEMS = 0; + len = 0; + ExpectIntEQ(TLSX_GetRequestSize(ssl, client_hello, &len), 0); + offset = 0; + ExpectIntEQ(TLSX_WriteRequest(ssl, out, client_hello, &offset), 0); + + /* haveEMS == 1: second operand true - independence for the second + * operand, first and third held at the same values as above. */ + ssl->options.haveEMS = 1; + len = 0; + ExpectIntEQ(TLSX_GetRequestSize(ssl, client_hello, &len), 0); + ExpectTrue(len >= HELLO_EXT_SZ); + offset = 0; + ExpectIntEQ(TLSX_WriteRequest(ssl, out, client_hello, &offset), 0); + ExpectTrue(offset > 0); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + return EXPECT_RESULT(); +} +#endif + +/* ---------------------------------------------------------------------- */ +/* TLSX_WriteRequest()'s trailing length-prefix guard - + * "offset > OPAQUE16_LEN || msgType != client_hello". */ +#if defined(WOLFSSL_TEST_STATIC_BUILD) && !defined(NO_WOLFSSL_CLIENT) +int test_TLSX_WriteRequest_length_prefix_bounds(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte out[64]; + word32 offset; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + /* offset == OPAQUE16_LEN (nothing written), msgType == client_hello: + * both operands false - the length prefix is left unwritten. A + * fixed TLS 1.2 method with EMS explicitly off, and the + * empty renegotiation_info InitSSL() advertises by default for + * every client (SetupClientSecureRenegotiation()) removed, keeps + * this ssl from writing anything else of its own accord. */ + ssl->options.haveEMS = 0; + TLSX_Remove(&ssl->extensions, TLSX_RENEGOTIATION_INFO, ssl->heap); + offset = 0; + ExpectIntEQ(TLSX_WriteRequest(ssl, out, client_hello, &offset), + 0); + ExpectIntEQ(offset, OPAQUE16_LEN); + + /* offset == OPAQUE16_LEN, msgType == certificate_request: second + * operand true, first false - independence for the second + * operand. */ + offset = 0; +#if defined(WOLFSSL_TLS13) && !defined(NO_CERTS) + ExpectIntEQ(TLSX_WriteRequest(ssl, out, certificate_request, &offset), + 0); +#endif + + /* offset > OPAQUE16_LEN, msgType == client_hello: first operand + * true - independence for the first operand. A pushed + * renegotiation-info extension guarantees at least one byte gets + * written. */ + ExpectIntEQ(TLSX_Push(&ssl->extensions, TLSX_RENEGOTIATION_INFO, + NULL, ssl->heap), 0); + offset = 0; + ExpectIntEQ(TLSX_WriteRequest(ssl, out, client_hello, &offset), 0); + ExpectTrue(offset > OPAQUE16_LEN); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + return EXPECT_RESULT(); +} +#endif + +/* ---------------------------------------------------------------------- */ +/* TLSX_WriteResponse() - "TLSX_SupportExtensions(ssl) && output" (its own + * independence pair, separate from TLSX_WriteRequest()'s call to the same + * predicate) and the extended-master-secret tail + * "ssl->options.haveEMS && msgType == server_hello && + * !IsAtLeastTLSv1_3(ssl->version)" (operands 1 and 2; operand 0 already + * has a pair elsewhere). TLSX_GetResponseSize() has the identical EMS tail + * one call earlier and is driven by the same three vectors. Both are + * WOLFSSL_LOCAL, reachable directly in this static build. */ +#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(HAVE_EXTENDED_MASTER) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(WOLFSSL_NO_TLS12) +int test_TLSX_WriteResponse_bounds(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte out[64]; + word16 offset; + word16 len; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_server_method())); + ExpectIntEQ(test_tls_bounds_load_server_cert(ctx), TEST_SUCCESS); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + /* TLSX_SupportExtensions(ssl) false (corrupted version.major, the + * only way to make it false - see test_TLSX_SupportExtensions_bounds + * above), output != NULL: first operand false. */ + ssl->version.major = 0; + offset = 0; + ExpectIntEQ(TLSX_WriteResponse(ssl, out, server_hello, &offset), 0); + ExpectIntEQ(offset, 0); + ssl->version.major = SSLv3_MAJOR; + ssl->version.minor = TLSv1_2_MINOR; + + /* TLSX_SupportExtensions(ssl) true, output == NULL: second operand + * false, first true - independence for the second operand. */ + offset = 0; + ExpectIntEQ(TLSX_WriteResponse(ssl, NULL, server_hello, &offset), 0); + ExpectIntEQ(offset, 0); + + /* Both true, haveEMS && server_hello && !TLS1.3: all three EMS + * operands true. */ + ssl->options.haveEMS = 1; + len = 0; + ExpectIntEQ(TLSX_GetResponseSize(ssl, server_hello, &len), 0); + ExpectTrue(len >= HELLO_EXT_SZ); + offset = 0; + ExpectIntEQ(TLSX_WriteResponse(ssl, out, server_hello, &offset), 0); + ExpectTrue(offset > 0); + + /* haveEMS true, msgType != server_hello: EMS operand 1 false - + * independence for operand 1 (operand 0 held true). */ + len = 0; + ExpectIntEQ(TLSX_GetResponseSize(ssl, encrypted_extensions, &len), 0); + offset = 0; + ExpectIntEQ(TLSX_WriteResponse(ssl, out, encrypted_extensions, + &offset), 0); + + /* haveEMS true, msgType == server_hello, TLS 1.3: EMS operand 2 + * false - independence for operand 2 (operands 0 and 1 held true). */ + ssl->version.major = SSLv3_MAJOR; + ssl->version.minor = TLSv1_3_MINOR; + ssl->options.tls1_3 = 1; + len = 0; + ExpectIntEQ(TLSX_GetResponseSize(ssl, server_hello, &len), 0); + offset = 0; + ExpectIntEQ(TLSX_WriteResponse(ssl, out, server_hello, &offset), 0); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + return EXPECT_RESULT(); +} +#endif + +/* ---------------------------------------------------------------------- */ +/* The msgType splits shared by TLSX_SupportedVersions_GetSize()/_Write(), + * TLSX_EncryptThenMac_GetSize()/_Write(), TLSX_ClientCertificateType_GetSize()/ + * _Write() and TLSX_ServerCertificateType_GetSize()/_Write() - all static- + * in-file, reached only through the generic TLSX_GetSize()/TLSX_Write() + * dispatch inside the WOLFSSL_TEST_VIS TLSX_GetRequestSize()/ + * TLSX_WriteRequest(). Those two only use msgType to decide which top-level + * semaphore bits to set before the dispatch; the dispatch itself passes + * msgType straight through to each extension's own handler regardless of + * which wrapper made the call (see test_TLSX_Cookie_bounds() above for the + * same pattern). One ssl populated with all four extensions and driven + * through client_hello/server_hello/hello_retry_request/encrypted_extensions + * therefore exercises every open condition in this group in one binary. */ +#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(WOLFSSL_TLS13) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(WOLFSSL_NO_TLS12) && \ + defined(HAVE_ENCRYPT_THEN_MAC) && !defined(WOLFSSL_AEAD_ONLY) && \ + defined(HAVE_RPK) +int test_TLSX_ext_msgtype_dispatch_bounds(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + /* TLSX_PopulateExtensions() on a TLS 1.3 client also builds a real + * KeyShare (possibly a large post-quantum/hybrid group) and signature + * algorithm list, so this needs to be sized well past a single + * extension's record, not just the two pushed-by-hand ones under + * test - undersizing this the way an early pass did with a 16-byte + * buffer for a 29-byte record corrupts the stack. */ + byte out[8192]; + word32 offset; + word32 len; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + /* SupportedVersions and EncryptThenMac come from the normal + * client-side extension population; ClientCertificateType/ + * ServerCertificateType are pushed directly (their real Use() + * paths need a loaded RPK certificate, which is not needed to + * reach the GetSize/Write msgType split under test). */ + ExpectIntEQ(TLSX_PopulateExtensions(ssl, 0), 0); + ExpectNotNull(TLSX_Find(ssl->extensions, TLSX_SUPPORTED_VERSIONS)); + ExpectNotNull(TLSX_Find(ssl->extensions, TLSX_ENCRYPT_THEN_MAC)); + + ssl->options.rpkState.sending_ClientCertTypeCnt = 1; + ssl->options.rpkState.sending_ClientCertTypes[0] = 0; + ExpectIntEQ(TLSX_Push(&ssl->extensions, TLSX_CLIENT_CERTIFICATE_TYPE, + ssl, ssl->heap), 0); + ssl->options.rpkState.sending_ServerCertTypeCnt = 1; + ssl->options.rpkState.sending_ServerCertTypes[0] = 0; + ExpectIntEQ(TLSX_Push(&ssl->extensions, TLSX_SERVER_CERTIFICATE_TYPE, + ssl, ssl->heap), 0); + + /* TLSX_GetSize()/TLSX_Write() skip any extension with resp == 0 + * outright for every msgType that is not client_hello/ + * certificate_request (their own leading "!isRequest && + * !extension->resp" guard) - server_hello, hello_retry_request and + * encrypted_extensions all fall in that set. Each is marked resp + * only for the call(s) that actually exercise it: ServerHello + * needs all four; hello_retry_request only means anything to + * SupportedVersions/EncryptThenMac (ClientCertificateType/ + * ServerCertificateType's own GetSize() has no case for it at all, + * and unlike TLSX_CSR_GetSize_ex() it does not clamp a negative + * SANITY_MSG_E before folding it into the word32 running total, + * so marking it resp for a msgType it was never meant to answer + * corrupts TLSX_GetSize()'s own accumulator - not the thing under + * test here); encrypted_extensions only means anything to + * ClientCertificateType/ServerCertificateType. */ + { + TLSX* svExt = TLSX_Find(ssl->extensions, TLSX_SUPPORTED_VERSIONS); + TLSX* etmExt = TLSX_Find(ssl->extensions, TLSX_ENCRYPT_THEN_MAC); + TLSX* cctExt = TLSX_Find(ssl->extensions, + TLSX_CLIENT_CERTIFICATE_TYPE); + TLSX* sctExt = TLSX_Find(ssl->extensions, + TLSX_SERVER_CERTIFICATE_TYPE); + ExpectNotNull(svExt); + ExpectNotNull(etmExt); + ExpectNotNull(cctExt); + ExpectNotNull(sctExt); + + /* msgType == client_hello: SupportedVersions/ClientCertificateType/ + * ServerCertificateType all take their "if (msgType == + * client_hello)" branch (not part of any open condition here); + * EncryptThenMac's "msgType != client_hello" operand false. */ + len = 0; + ExpectIntEQ(TLSX_GetRequestSize(ssl, client_hello, &len), 0); + offset = 0; + ExpectIntEQ(TLSX_WriteRequest(ssl, out, client_hello, &offset), + 0); + + /* msgType == server_hello: SupportedVersions/ + * ClientCertificateType/ServerCertificateType operand 0 true; + * EncryptThenMac operand 0 true, operand 1 false. */ + if (svExt != NULL) svExt->resp = 1; + if (etmExt != NULL) etmExt->resp = 1; + if (cctExt != NULL) cctExt->resp = 1; + if (sctExt != NULL) sctExt->resp = 1; + len = 0; + ExpectIntEQ(TLSX_GetRequestSize(ssl, server_hello, &len), 0); + offset = 0; + ExpectIntEQ(TLSX_WriteRequest(ssl, out, server_hello, &offset), + 0); + if (cctExt != NULL) cctExt->resp = 0; + if (sctExt != NULL) sctExt->resp = 0; + + /* msgType == hello_retry_request: SupportedVersions operand 0 + * false, operand 1 true; EncryptThenMac both operands true - + * SANITY_MSG_E, which TLSX_GetSize()/TLSX_Write() propagate up. + * ClientCertificateType/ServerCertificateType left resp == 0 + * (skipped) for exactly the reason in the comment above. */ + len = 0; + ExpectIntEQ(TLSX_GetRequestSize(ssl, hello_retry_request, &len), + WC_NO_ERR_TRACE(SANITY_MSG_E)); + offset = 0; + ExpectIntEQ(TLSX_WriteRequest(ssl, out, hello_retry_request, + &offset), WC_NO_ERR_TRACE(SANITY_MSG_E)); + if (svExt != NULL) svExt->resp = 0; + if (etmExt != NULL) etmExt->resp = 0; + } + + /* msgType == encrypted_extensions: ClientCertificateType/ + * ServerCertificateType operand 0 false, operand 1 true. */ + { + TLSX* cctExt = TLSX_Find(ssl->extensions, + TLSX_CLIENT_CERTIFICATE_TYPE); + TLSX* sctExt = TLSX_Find(ssl->extensions, + TLSX_SERVER_CERTIFICATE_TYPE); + if (cctExt != NULL) cctExt->resp = 1; + if (sctExt != NULL) sctExt->resp = 1; + } + len = 0; + ExpectIntEQ(TLSX_GetRequestSize(ssl, encrypted_extensions, &len), 0); + offset = 0; + ExpectIntEQ(TLSX_WriteRequest(ssl, out, encrypted_extensions, + &offset), 0); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + /* The "both false" baseline row for each of the three OR-shaped msgType + * splits above (SupportedVersions/ClientCertificateType/ + * ServerCertificateType) still needs one more vector each: a msgType + * that is neither of the two the extension recognizes. server_hello/ + * hello_retry_request/encrypted_extensions above only ever showed one + * extension's operands both false as a side effect of some OTHER + * extension being absent or skipped from that same call - never this + * extension, reached with resp honored, on a call of its own. Each + * pushed alone (fresh ssl, so no cross-extension interaction) with an + * unused msgType supplies it. ClientCertificateType/ + * ServerCertificateType's TLSX_GetSize() case does not clamp the + * SANITY_MSG_E their GetSize() returns here (unlike TLSX_Write()'s + * case, which returns a word16 that is always 0 in this branch) before + * folding it into the running total, so TLSX_GetRequestSize() itself + * comes back BUFFER_E rather than SANITY_MSG_E - a preexisting + * TLSX_GetSize() quirk, not the thing under test, and not asserted on + * beyond "some error". */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + TLSX* ext = NULL; + /* TLSX_SetSupportedVersions() is static-in-file; its whole body is + * "return TLSX_Push(extensions, TLSX_SUPPORTED_VERSIONS, data, + * heap);" with data == ssl, called directly here instead. */ + ExpectIntEQ(TLSX_Push(&ssl->extensions, TLSX_SUPPORTED_VERSIONS, ssl, + ssl->heap), 0); + ExpectNotNull(ext = TLSX_Find(ssl->extensions, + TLSX_SUPPORTED_VERSIONS)); + if (ext != NULL) + ext->resp = 1; + len = 0; + ExpectIntEQ(TLSX_GetRequestSize(ssl, encrypted_extensions, &len), + WC_NO_ERR_TRACE(SANITY_MSG_E)); + offset = 0; + ExpectIntEQ(TLSX_WriteRequest(ssl, out, encrypted_extensions, + &offset), WC_NO_ERR_TRACE(SANITY_MSG_E)); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + TLSX* ext = NULL; + ssl->options.rpkState.sending_ClientCertTypeCnt = 1; + ExpectIntEQ(TLSX_Push(&ssl->extensions, TLSX_CLIENT_CERTIFICATE_TYPE, + ssl, ssl->heap), 0); + ExpectNotNull(ext = TLSX_Find(ssl->extensions, + TLSX_CLIENT_CERTIFICATE_TYPE)); + if (ext != NULL) + ext->resp = 1; + len = 0; + ExpectIntNE(TLSX_GetRequestSize(ssl, hello_retry_request, &len), 0); + offset = 0; + ExpectIntEQ(TLSX_WriteRequest(ssl, out, hello_retry_request, + &offset), 0); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + TLSX* ext = NULL; + ssl->options.rpkState.sending_ServerCertTypeCnt = 1; + ExpectIntEQ(TLSX_Push(&ssl->extensions, TLSX_SERVER_CERTIFICATE_TYPE, + ssl, ssl->heap), 0); + ExpectNotNull(ext = TLSX_Find(ssl->extensions, + TLSX_SERVER_CERTIFICATE_TYPE)); + if (ext != NULL) + ext->resp = 1; + len = 0; + ExpectIntNE(TLSX_GetRequestSize(ssl, hello_retry_request, &len), 0); + offset = 0; + ExpectIntEQ(TLSX_WriteRequest(ssl, out, hello_retry_request, + &offset), 0); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + return EXPECT_RESULT(); +} +#endif + +/* ---------------------------------------------------------------------- */ +/* TLSX_SecureRenegotiation_Write() - "data && data->enabled && + * data->verifySet". Only the first operand is open; a NULL data pointer is + * the "HAVE_SERVER_RENEGOTIATION_INFO only" empty-extension shape the + * function's own comment describes, produced here directly with + * TLSX_Push() rather than through a real renegotiation handshake. */ +#if defined(WOLFSSL_TEST_STATIC_BUILD) && \ + (defined(HAVE_SECURE_RENEGOTIATION) || defined(HAVE_SERVER_RENEGOTIATION_INFO)) && \ + !defined(NO_WOLFSSL_CLIENT) +int test_TLSX_SecureRenegotiation_Write_bounds(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + /* Sized from the vector: prefix(2) + type(2) + len(2) + reneg length + * byte(1) + client_verify_data(TLS_FINISHED_SZ==12) == 19 bytes for + * the enabled/verifySet case below. */ + byte out[32]; + word32 offset; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + /* data == NULL: first operand false. InitSSL() already advertises + * an empty renegotiation_info by default for every client + * (SetupClientSecureRenegotiation(), with real, non-NULL data) - + * remove it first so this vector's own NULL-data push is the only + * TLSX_RENEGOTIATION_INFO node on the list. */ + ssl->options.haveEMS = 0; + TLSX_Remove(&ssl->extensions, TLSX_RENEGOTIATION_INFO, ssl->heap); + ExpectIntEQ(TLSX_Push(&ssl->extensions, TLSX_RENEGOTIATION_INFO, + NULL, ssl->heap), 0); + offset = 0; + ExpectIntEQ(TLSX_WriteRequest(ssl, out, client_hello, &offset), 0); + ExpectIntEQ(offset, OPAQUE16_LEN + HELLO_EXT_TYPE_SZ + OPAQUE16_LEN + + OPAQUE8_LEN); + } + wolfSSL_free(ssl); + ssl = NULL; + + /* data != NULL, enabled and verifySet both true: first operand true - + * independence for the first operand. wolfSSL_UseSecureRenegotiation() + * allocates data; verifySet/enabled are then set directly (only ever + * set true together, by a real Finished exchange in the non-test + * path). */ + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + TLSX* ext = NULL; + SecureRenegotiation* data; + + ssl->options.haveEMS = 0; + ExpectIntEQ(wolfSSL_UseSecureRenegotiation(ssl), WOLFSSL_SUCCESS); + ExpectNotNull(ext = TLSX_Find(ssl->extensions, TLSX_RENEGOTIATION_INFO)); + if (ext != NULL) { + data = (SecureRenegotiation*)ext->data; + data->enabled = 1; + data->verifySet = 1; + offset = 0; + ExpectIntEQ(TLSX_WriteRequest(ssl, out, client_hello, &offset), + 0); + ExpectIntEQ(offset, OPAQUE16_LEN + HELLO_EXT_TYPE_SZ + + OPAQUE16_LEN + OPAQUE8_LEN + TLS_FINISHED_SZ); + } + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + return EXPECT_RESULT(); +} +#endif + +/* ---------------------------------------------------------------------- */ +/* TLSX_SessionTicket_Parse() (server, TLS 1.2), the "ret is REJECT or + * VERSION_ERROR" branch after "ret = DoClientTicket(...)". The + * false/false row - ret is neither value - needs a DoClientTicket() return + * that is none of WOLFSSL_TICKET_RET_OK/_CREATE/_REJECT or VERSION_ERROR. + * DoDecryptTicket() has exactly one such path: when the registered + * SessionTicketEncCb reports success but writes an out-of-range outLen, + * DoDecryptTicket() returns BAD_TICKET_KEY_CB_SZ directly - none of the + * four TicketEncRet values and unrelated to VERSION_ERROR - and + * DoClientTicket() passes that straight back up unchanged (it does not + * reach WOLFSSL_TICKET_RET_REJECT; every REJECT return in DoDecryptTicket() + * is a distinct, earlier guard). TLSX_SessionTicket_Parse() then falls to + * its own "else if (ret < 0)" tail, leaving ret untouched, so the negative + * value propagates out of TLSX_Parse() and fails the handshake - a real, + * protocol-level row, not a callee postcondition that collapses it into + * REJECT. So: covered, not excluded. */ +#if defined(HAVE_SSL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_TLS12) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_WOLFSSL_CLIENT) +static int test_TLSX_SessionTicket_ff_enc_cb(WOLFSSL* ssl, + byte key_name[WOLFSSL_TICKET_NAME_SZ], byte iv[WOLFSSL_TICKET_IV_SZ], + byte mac[WOLFSSL_TICKET_MAC_SZ], int enc, byte* ticket, int inLen, + int* outLen, void* userCtx) +{ + int i; + (void)ssl; (void)userCtx; + + if (enc) { + XMEMSET(key_name, 0x11, WOLFSSL_TICKET_NAME_SZ); + XMEMSET(iv, 0x22, WOLFSSL_TICKET_IV_SZ); + XMEMSET(mac, 0x33, WOLFSSL_TICKET_MAC_SZ); + /* DoCreateTicket()'s own sanity check rejects an encrypt callback + * that leaves the internal ticket bytes unchanged, so this has to + * actually transform them, not just report success. */ + for (i = 0; i < inLen; i++) + ticket[i] = (byte)(ticket[i] ^ 0xA5); + *outLen = inLen; + } + else { + /* Report success with an out-of-range outLen: DoDecryptTicket()'s + * own "outLen > inLen || outLen < WOLFSSL_INTERNAL_TICKET_LEN" + * guard turns this into BAD_TICKET_KEY_CB_SZ. */ + *outLen = 0; + } + return WOLFSSL_TICKET_RET_OK; +} + +static int test_TLSX_SessionTicket_ff_ctx_ready(WOLFSSL_CTX* ctx) +{ + EXPECT_DECLS; + ExpectIntEQ(wolfSSL_CTX_set_TicketEncCb(ctx, + test_TLSX_SessionTicket_ff_enc_cb), WOLFSSL_SUCCESS); + return EXPECT_RESULT(); +} + +int test_TLSX_SessionTicket_Parse_falsefalse_bounds(void) +{ + EXPECT_DECLS; + test_ssl_memio_ctx test_ctx; + WOLFSSL_SESSION* sess = NULL; + + /* First connection: issue a ticket (enc == 1 path only). */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + test_ctx.c_cb.method = wolfTLSv1_2_client_method; + test_ctx.s_cb.method = wolfTLSv1_2_server_method; + test_ctx.s_cb.ctx_ready = test_TLSX_SessionTicket_ff_ctx_ready; + ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_UseSessionTicket(test_ctx.c_ssl), WOLFSSL_SUCCESS); + ExpectIntEQ(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), + TEST_SUCCESS); + ExpectNotNull(sess = wolfSSL_get1_session(test_ctx.c_ssl)); + test_ssl_memio_cleanup(&test_ctx); + + /* Second connection: present that ticket back (enc == 0 path), which + * the callback now deliberately corrupts. */ + if (sess != NULL) { + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + test_ctx.c_cb.method = wolfTLSv1_2_client_method; + test_ctx.s_cb.method = wolfTLSv1_2_server_method; + test_ctx.s_cb.ctx_ready = test_TLSX_SessionTicket_ff_ctx_ready; + ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_set_session(test_ctx.c_ssl, sess), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), + TEST_FAIL); + test_ssl_memio_cleanup(&test_ctx); + } + wolfSSL_SESSION_free(sess); + + return EXPECT_RESULT(); +} +#else +int test_TLSX_SessionTicket_Parse_falsefalse_bounds(void) +{ + return TEST_SKIPPED; +} +#endif diff --git a/tests/api/test_tls_bounds.h b/tests/api/test_tls_bounds.h index f9b3927adc..c3e4354365 100644 --- a/tests/api/test_tls_bounds.h +++ b/tests/api/test_tls_bounds.h @@ -37,6 +37,20 @@ int test_BuildTlsHandshakeHash_bounds(void); int test_TLS_hmac_bounds(void); int test_TLSX_ALPN_GetSize_overflow(void); int test_TLSX_Cookie_bounds(void); +int test_TLSX_CSR_write_getsize_bounds(void); +int test_TLSX_CSR_SetResponseWithStatusCB_bounds(void); +int test_ProcessChainOCSPRequest_bounds(void); +int test_TLSX_PopulateExtensions_bounds(void); +int test_TLSX_PopulateSupportedGroups_bounds(void); +int test_TLSX_CSR_Parse_bounds(void); +int test_TLSX_CSR2_Parse_bounds(void); +int test_TLSX_ext_dispatch_ctx_extensions_bounds(void); +int test_TLSX_WriteRequest_ems_bounds(void); +int test_TLSX_WriteRequest_length_prefix_bounds(void); +int test_TLSX_WriteResponse_bounds(void); +int test_TLSX_ext_msgtype_dispatch_bounds(void); +int test_TLSX_SecureRenegotiation_Write_bounds(void); +int test_TLSX_SessionTicket_Parse_falsefalse_bounds(void); #define TEST_TLS_BOUNDS_DECLS \ TEST_DECL_GROUP("tls", test_TLSX_UseSNI_bounds), \ @@ -54,6 +68,21 @@ int test_TLSX_Cookie_bounds(void); TEST_DECL_GROUP("tls", test_BuildTlsHandshakeHash_bounds), \ TEST_DECL_GROUP("tls", test_TLS_hmac_bounds), \ TEST_DECL_GROUP("tls", test_TLSX_ALPN_GetSize_overflow), \ - TEST_DECL_GROUP("tls", test_TLSX_Cookie_bounds) + TEST_DECL_GROUP("tls", test_TLSX_Cookie_bounds), \ + TEST_DECL_GROUP("tls", test_TLSX_CSR_write_getsize_bounds), \ + TEST_DECL_GROUP("tls", \ + test_TLSX_CSR_SetResponseWithStatusCB_bounds), \ + TEST_DECL_GROUP("tls", test_ProcessChainOCSPRequest_bounds), \ + TEST_DECL_GROUP("tls", test_TLSX_PopulateExtensions_bounds), \ + TEST_DECL_GROUP("tls", test_TLSX_PopulateSupportedGroups_bounds), \ + TEST_DECL_GROUP("tls", test_TLSX_CSR_Parse_bounds), \ + TEST_DECL_GROUP("tls", test_TLSX_CSR2_Parse_bounds), \ + TEST_DECL_GROUP("tls", test_TLSX_ext_dispatch_ctx_extensions_bounds),\ + TEST_DECL_GROUP("tls", test_TLSX_WriteRequest_ems_bounds), \ + TEST_DECL_GROUP("tls", test_TLSX_WriteRequest_length_prefix_bounds),\ + TEST_DECL_GROUP("tls", test_TLSX_WriteResponse_bounds), \ + TEST_DECL_GROUP("tls", test_TLSX_ext_msgtype_dispatch_bounds), \ + TEST_DECL_GROUP("tls", test_TLSX_SecureRenegotiation_Write_bounds), \ + TEST_DECL_GROUP("tls", test_TLSX_SessionTicket_Parse_falsefalse_bounds) #endif /* TESTS_API_TEST_TLS_BOUNDS_H */ From aad6589d71edf13944bad3abf325a5621d06c1e0 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 20 Aug 2026 23:25:33 +0200 Subject: [PATCH 46/57] tests: define tls bounds tests unconditionally and fix link visibility The public test_*() entry points in test_tls_bounds.c were defined inside their #if regions while api.c registers them unconditionally, so any build without the feature failed to link. Move each guard into the function body with a TEST_SKIPPED #else, and name the guards that cover a file-scope helper plus its test(s) so region and body cannot drift apart. Add WOLFSSL_TEST_STATIC_BUILD to the two guards over regions that call the WOLFSSL_LOCAL DtlsGetCidTxSize() and TLSX_ALPN_GetRequest(). --- tests/api/test_tls_bounds.c | 264 ++++++++++++++++++++++++------------ tests/api/test_tls_parse.c | 4 +- 2 files changed, 179 insertions(+), 89 deletions(-) diff --git a/tests/api/test_tls_bounds.c b/tests/api/test_tls_bounds.c index eb4a68e4f5..dbeade21d3 100644 --- a/tests/api/test_tls_bounds.c +++ b/tests/api/test_tls_bounds.c @@ -27,6 +27,41 @@ #include #include +/* Named guards for the regions below: each covers a file-scope helper or + * fixture plus the test(s) that use it, so the condition is written once + * and the region and the in-body guard cannot drift apart. */ +#if defined(WOLFSSL_TEST_STATIC_BUILD) && \ + defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) && !defined(NO_CERTS) + #define TEST_TLS_BOUNDS_CSR2_REQUESTS +#endif +#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(WOLFSSL_TLS13) && \ + defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_WOLFSSL_CLIENT) && defined(HAVE_OCSP) + #define TEST_TLS_BOUNDS_CSR_STATUS_CB +#endif +#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(WOLFSSL_TLS13) && \ + !defined(NO_WOLFSSL_SERVER) && defined(WOLFSSL_TLS_OCSP_MULTI) && \ + defined(HAVE_CERTIFICATE_STATUS_REQUEST) && defined(HAVE_OCSP) && \ + !defined(NO_RSA) && !defined(NO_SHA256) + #define TEST_TLS_BOUNDS_OCSP_CHAIN +#endif +#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(WOLFSSL_TLS13) && \ + !defined(NO_WOLFSSL_CLIENT) && defined(HAVE_SUPPORTED_CURVES) && \ + (defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)) && !defined(NO_PSK) && \ + defined(HAVE_AESGCM) && !defined(NO_AES) && !defined(NO_SHA256) + #define TEST_TLS_BOUNDS_POPULATE_EXT +#endif +#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(WOLFSSL_TLS13) && \ + defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) + #define TEST_TLS_BOUNDS_CSR_PARSE +#endif +#if defined(HAVE_SSL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_TLS12) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_WOLFSSL_CLIENT) + #define TEST_TLS_BOUNDS_SESSION_TICKET_FF +#endif + /* c32to24() (wolfcrypt/src/misc.c) is only externally linkable when NO_INLINE * is defined; this build inlines it into each translation unit that already * needs it, so it is not visible here. Same 3-byte big-endian length write, @@ -64,9 +99,9 @@ static int test_tls_bounds_load_server_cert(WOLFSSL_CTX* ctx) * reachable caller and is excluded (argued in the report, not retested here). * The "data == NULL" half, the host-name-length guard, and the duplicate-type * removal in the linked list are all reachable through wolfSSL_UseSNI(). */ -#if defined(HAVE_SNI) && !defined(NO_WOLFSSL_CLIENT) int test_TLSX_UseSNI_bounds(void) { +#if defined(HAVE_SNI) && !defined(NO_WOLFSSL_CLIENT) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -123,8 +158,10 @@ int test_TLSX_UseSNI_bounds(void) wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); return EXPECT_RESULT(); -} +#else + return TEST_SKIPPED; #endif +} /* ---------------------------------------------------------------------- */ /* TLSX_UseALPN - extensions is always &ssl->extensions (wolfSSL_UseALPN()); @@ -132,9 +169,9 @@ int test_TLSX_UseSNI_bounds(void) * TLSX_UseALPN() with tokens produced by XSTRTOK(), which are never NULL * inside the "while (token[idx] != NULL)" loop, so the only way to reach * TLSX_UseALPN() with a NULL data pointer at all is to call it directly. */ -#if defined(HAVE_ALPN) && !defined(NO_WOLFSSL_CLIENT) int test_TLSX_UseALPN_bounds(void) { +#if defined(HAVE_ALPN) && !defined(NO_WOLFSSL_CLIENT) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -158,17 +195,19 @@ int test_TLSX_UseALPN_bounds(void) wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); return EXPECT_RESULT(); -} +#else + return TEST_SKIPPED; #endif +} /* ---------------------------------------------------------------------- */ /* TLSX_UseMaxFragment - extensions always &ssl->extensions / &ctx->extensions; * unreachable-NULL, excluded. mfl < MIN and mfl > MAX are both reachable * through the public wrapper with an out-of-range code, alongside a valid * in-range call. */ -#if defined(HAVE_MAX_FRAGMENT) && !defined(NO_WOLFSSL_CLIENT) int test_TLSX_UseMaxFragment_bounds(void) { +#if defined(HAVE_MAX_FRAGMENT) && !defined(NO_WOLFSSL_CLIENT) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -191,8 +230,10 @@ int test_TLSX_UseMaxFragment_bounds(void) wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); return EXPECT_RESULT(); -} +#else + return TEST_SKIPPED; #endif +} /* ---------------------------------------------------------------------- */ /* TLSX_UseCertificateStatusRequest - extensions is always &ssl->extensions / @@ -200,9 +241,9 @@ int test_TLSX_UseMaxFragment_bounds(void) * excluded. status_type != WOLFSSL_CSR_OCSP is reachable directly: the * wrapper passes the caller's status_type straight through with no * validation of its own. */ -#if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(NO_WOLFSSL_CLIENT) int test_TLSX_UseCertificateStatusRequest_bounds(void) { +#if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(NO_WOLFSSL_CLIENT) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -221,14 +262,16 @@ int test_TLSX_UseCertificateStatusRequest_bounds(void) wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); return EXPECT_RESULT(); -} +#else + return TEST_SKIPPED; #endif +} /* ---------------------------------------------------------------------- */ /* TLSX_UseCertificateStatusRequestV2 - same pattern as V1 above. */ -#if defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) && !defined(NO_WOLFSSL_CLIENT) int test_TLSX_UseCertificateStatusRequestV2_bounds(void) { +#if defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) && !defined(NO_WOLFSSL_CLIENT) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -256,8 +299,10 @@ int test_TLSX_UseCertificateStatusRequestV2_bounds(void) wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); return EXPECT_RESULT(); -} +#else + return TEST_SKIPPED; #endif +} /* ---------------------------------------------------------------------- */ /* TLSX_SupportExtensions() - "return ssl && (IsTLS(ssl) || @@ -274,9 +319,9 @@ int test_TLSX_UseCertificateStatusRequestV2_bounds(void) * IsTLS()'s postcondition in this build and is excluded (family 4); only its * (always-false-when-reached) value is exercised below, alongside the first * and second operands. */ -#if !defined(NO_WOLFSSL_CLIENT) int test_TLSX_SupportExtensions_bounds(void) { +#if !defined(NO_WOLFSSL_CLIENT) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -316,8 +361,10 @@ int test_TLSX_SupportExtensions_bounds(void) wolfSSL_CTX_free(ctx); return EXPECT_RESULT(); -} +#else + return TEST_SKIPPED; #endif +} /* ---------------------------------------------------------------------- */ /* TLSX_EarlyData_Use() - "extension->val = (WOLFSSL_IS_QUIC(ssl) && @@ -334,10 +381,9 @@ int test_TLSX_SupportExtensions_bounds(void) /* TLSX_CSR2_InitRequests(), TLSX_CSR2_ForceRequest(), TLSX_CSR_GetRequest_ex() * are WOLFSSL_LOCAL with no public wrapper; each is unit-tested directly by * building the minimal extension/context state each one dereferences. */ -#if defined(WOLFSSL_TEST_STATIC_BUILD) && \ - defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) && !defined(NO_CERTS) int test_TLSX_CSR2_InitRequests_bounds(void) { +#ifdef TEST_TLS_BOUNDS_CSR2_REQUESTS EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -378,10 +424,14 @@ int test_TLSX_CSR2_InitRequests_bounds(void) wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); return EXPECT_RESULT(); +#else + return TEST_SKIPPED; +#endif } int test_TLSX_CSR2_ForceRequest_bounds(void) { +#ifdef TEST_TLS_BOUNDS_CSR2_REQUESTS EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -421,14 +471,16 @@ int test_TLSX_CSR2_ForceRequest_bounds(void) wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); return EXPECT_RESULT(); -} +#else + return TEST_SKIPPED; #endif +} +int test_TLSX_CSR_GetRequest_ex_bounds(void) +{ #if defined(WOLFSSL_TEST_STATIC_BUILD) && \ defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(WOLFSSL_NO_TLS12) && \ !defined(NO_WOLFSSL_CLIENT) -int test_TLSX_CSR_GetRequest_ex_bounds(void) -{ EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -464,8 +516,10 @@ int test_TLSX_CSR_GetRequest_ex_bounds(void) wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); return EXPECT_RESULT(); -} +#else + return TEST_SKIPPED; #endif +} /* ---------------------------------------------------------------------- */ @@ -473,10 +527,10 @@ int test_TLSX_CSR_GetRequest_ex_bounds(void) * is allocated lazily by the handshake and is still NULL on a freshly * created object, so both operands are reachable without completing a * handshake at all. */ -#if defined(WOLFSSL_HAVE_PRF) && !defined(NO_WOLFSSL_CLIENT) && \ - !defined(WOLFSSL_NO_TLS12) && defined(WOLFSSL_TEST_STATIC_BUILD) int test_wolfSSL_make_eap_keys_bounds(void) { +#if defined(WOLFSSL_HAVE_PRF) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(WOLFSSL_NO_TLS12) && defined(WOLFSSL_TEST_STATIC_BUILD) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -507,16 +561,18 @@ int test_wolfSSL_make_eap_keys_bounds(void) wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); return EXPECT_RESULT(); -} +#else + return TEST_SKIPPED; #endif +} /* ---------------------------------------------------------------------- */ /* wolfSSL_SetTlsHmacInner() - "ssl == NULL || inner == NULL", then * "content == dtls12_cid || (ssl->options.dtls && DtlsGetCidTxSize(ssl) > * 0)". Both are public (WOLFSSL_API) and reachable directly. */ -#if !defined(NO_WOLFSSL_CLIENT) && !defined(WOLFSSL_AEAD_ONLY) int test_wolfSSL_SetTlsHmacInner_bounds(void) { +#if !defined(NO_WOLFSSL_CLIENT) && !defined(WOLFSSL_AEAD_ONLY) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -546,8 +602,10 @@ int test_wolfSSL_SetTlsHmacInner_bounds(void) wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); -#if defined(WOLFSSL_DTLS) && defined(WOLFSSL_DTLS_CID) && \ - defined(WOLFSSL_DTLS13) +/* DtlsGetCidTxSize() is WOLFSSL_LOCAL, so this block only builds when the + * test links against the static library. */ +#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(WOLFSSL_DTLS) && \ + defined(WOLFSSL_DTLS_CID) && defined(WOLFSSL_DTLS13) /* content != dtls12_cid, ssl->options.dtls == 1, no CID negotiated: * independence for the second operand while the third is false. */ ctx = NULL; @@ -575,8 +633,10 @@ int test_wolfSSL_SetTlsHmacInner_bounds(void) #endif return EXPECT_RESULT(); -} +#else + return TEST_SKIPPED; #endif +} /* ---------------------------------------------------------------------- */ /* BuildTlsHandshakeHash() - the leading 4-operand NULL/size guard has no @@ -588,10 +648,10 @@ int test_wolfSSL_SetTlsHmacInner_bounds(void) * ordering regardless of whether HAVE_BLAKE2B is built, so setting * specs.mac_algorithm to it exercises the SHA-256 path through the second * operand without requiring BLAKE2b support. */ -#if defined(WOLFSSL_TEST_STATIC_BUILD) && !defined(WOLFSSL_NO_TLS12) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(NO_SHA256) int test_BuildTlsHandshakeHash_bounds(void) { +#if defined(WOLFSSL_TEST_STATIC_BUILD) && !defined(WOLFSSL_NO_TLS12) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_SHA256) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -640,8 +700,10 @@ int test_BuildTlsHandshakeHash_bounds(void) wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); return EXPECT_RESULT(); -} +#else + return TEST_SKIPPED; #endif +} /* ---------------------------------------------------------------------- */ /* TLS_hmac() / Hmac_UpdateFinal_CT() - driven directly through ssl->hmac(), @@ -651,10 +713,10 @@ int test_BuildTlsHandshakeHash_bounds(void) * pattern). No live connection is needed: the size-overflow guard and the * verify/padSz dispatch are pure argument checks over ssl->specs and the * caller-supplied lengths. */ -#if !defined(NO_HMAC) && !defined(WOLFSSL_AEAD_ONLY) && !defined(NO_TLS) && \ - defined(NO_OLD_TLS) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_SHA256) int test_TLS_hmac_bounds(void) { +#if !defined(NO_HMAC) && !defined(WOLFSSL_AEAD_ONLY) && !defined(NO_TLS) && \ + defined(NO_OLD_TLS) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_SHA256) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -741,8 +803,10 @@ int test_TLS_hmac_bounds(void) wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); return EXPECT_RESULT(); -} +#else + return TEST_SKIPPED; #endif +} /* ---------------------------------------------------------------------- */ /* TLSX_ALPN_GetSize() (reached through the WOLFSSL_TEST_VIS @@ -757,10 +821,10 @@ int test_TLS_hmac_bounds(void) * only the first operand is driven here. WOLFSSL_MAX_ALPN_NUMBER (257) * entries of the maximum WOLFSSL_MAX_ALPN_PROTO_NAME_LEN (255) push the * running total past 0xFFFF (257 * 256 + 2 == 65794). */ -#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(HAVE_ALPN) && \ - !defined(NO_WOLFSSL_CLIENT) int test_TLSX_ALPN_GetSize_overflow(void) { +#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(HAVE_ALPN) && \ + !defined(NO_WOLFSSL_CLIENT) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -797,8 +861,10 @@ int test_TLSX_ALPN_GetSize_overflow(void) wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); return EXPECT_RESULT(); -} +#else + return TEST_SKIPPED; #endif +} /* ---------------------------------------------------------------------- */ /* TLSX_Cookie_GetSize() / TLSX_Cookie_Write() - "msgType == client_hello || @@ -808,10 +874,10 @@ int test_TLSX_ALPN_GetSize_overflow(void) * the msgType argument (and, once the extension already exists, * extension->resp for the "is this message type getting a response-only * extension" skip), not at which top-level wrapper made the call. */ -#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(WOLFSSL_TLS13) && \ - defined(WOLFSSL_SEND_HRR_COOKIE) && !defined(NO_WOLFSSL_CLIENT) int test_TLSX_Cookie_bounds(void) { +#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(WOLFSSL_TLS13) && \ + defined(WOLFSSL_SEND_HRR_COOKIE) && !defined(NO_WOLFSSL_CLIENT) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -868,8 +934,10 @@ int test_TLSX_Cookie_bounds(void) wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); return EXPECT_RESULT(); -} +#else + return TEST_SKIPPED; #endif +} /* ---------------------------------------------------------------------- */ /* TLSX_CSR_GetSize_ex() / TLSX_CSR_Write_ex() - both share the same @@ -901,17 +969,17 @@ int test_TLSX_Cookie_bounds(void) * "if" already required to be non-NULL two lines above, with no assignment * to csr->ssl in between. Excluded (family 3) in both functions. */ -#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(WOLFSSL_TLS13) && \ - defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(NO_WOLFSSL_SERVER) && \ - !defined(NO_WOLFSSL_CLIENT) && defined(HAVE_OCSP) +#ifdef TEST_TLS_BOUNDS_CSR_STATUS_CB static int test_TLSX_CSR_write_getsize_status_cb(WOLFSSL* ssl, void* arg) { (void)ssl; (void)arg; return WOLFSSL_OCSP_STATUS_CB_OK; } +#endif int test_TLSX_CSR_write_getsize_bounds(void) { +#ifdef TEST_TLS_BOUNDS_CSR_STATUS_CB EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -1023,8 +1091,10 @@ int test_TLSX_CSR_write_getsize_bounds(void) wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); return EXPECT_RESULT(); -} +#else + return TEST_SKIPPED; #endif +} /* ---------------------------------------------------------------------- */ /* TLSX_CSR_SetResponseWithStatusCB() - unlike TLSX_CSR_WriteWithStatusCB(), @@ -1034,10 +1104,9 @@ int test_TLSX_CSR_write_getsize_bounds(void) * ssl/SSL_CM(ssl)/ocsp_stapling/statusCb are non-NULL before calling it). * Called directly, none of those preconditions are established, so all four * operands are independently reachable here. */ -#if defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_SERVER) && \ - defined(HAVE_CERTIFICATE_STATUS_REQUEST) && defined(HAVE_OCSP) int test_TLSX_CSR_SetResponseWithStatusCB_bounds(void) { +#ifdef TEST_TLS_BOUNDS_CSR_STATUS_CB EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -1081,8 +1150,10 @@ int test_TLSX_CSR_SetResponseWithStatusCB_bounds(void) wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); return EXPECT_RESULT(); -} +#else + return TEST_SKIPPED; #endif +} /* ---------------------------------------------------------------------- */ /* ProcessChainOCSPRequest() - walks ssl->buffers.certChain (each entry a @@ -1094,10 +1165,7 @@ int test_TLSX_CSR_SetResponseWithStatusCB_bounds(void) * copied here under a local name so this file does not pull in * test_ocsp_test_blobs.h's non-static globals into a second translation * unit. */ -#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(WOLFSSL_TLS13) && \ - !defined(NO_WOLFSSL_SERVER) && defined(WOLFSSL_TLS_OCSP_MULTI) && \ - defined(HAVE_CERTIFICATE_STATUS_REQUEST) && defined(HAVE_OCSP) && \ - !defined(NO_RSA) && !defined(NO_SHA256) +#ifdef TEST_TLS_BOUNDS_OCSP_CHAIN static const unsigned char csrocsp_resp_unknown[] = { 0x30, 0x82, 0x07, 0x29, 0x0a, 0x01, 0x00, 0xa0, 0x82, 0x07, 0x22, 0x30, 0x82, 0x07, 0x1e, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, @@ -1323,9 +1391,11 @@ static int test_ProcessChainOCSPRequest_setup(WOLFSSL_CTX** pctx, *pssl = ssl; return EXPECT_RESULT(); } +#endif int test_ProcessChainOCSPRequest_bounds(void) { +#ifdef TEST_TLS_BOUNDS_OCSP_CHAIN EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -1539,8 +1609,10 @@ int test_ProcessChainOCSPRequest_bounds(void) wolfSSL_CTX_free(ctx); return EXPECT_RESULT(); -} +#else + return TEST_SKIPPED; #endif +} /* ---------------------------------------------------------------------- */ /* TLSX_PopulateExtensions() - called directly (WOLFSSL_LOCAL), the same way @@ -1555,10 +1627,7 @@ int test_ProcessChainOCSPRequest_bounds(void) * line at all already requires isServer == 0. Its own "!isServer" operand * is therefore fixed true on every execution that reaches it - excluded * (family 3: fixed by the branch that reaches it). */ -#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(WOLFSSL_TLS13) && \ - !defined(NO_WOLFSSL_CLIENT) && defined(HAVE_SUPPORTED_CURVES) && \ - (defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)) && !defined(NO_PSK) && \ - defined(HAVE_AESGCM) && !defined(NO_AES) && !defined(NO_SHA256) +#ifdef TEST_TLS_BOUNDS_POPULATE_EXT static unsigned int test_TLSX_PopulateExtensions_psk_cb(WOLFSSL* ssl, const char* hint, char* identity, unsigned int id_max_len, unsigned char* key, unsigned int key_max_len) @@ -1593,9 +1662,11 @@ static unsigned int test_TLSX_PopulateExtensions_psk_tls13_cb(WOLFSSL* ssl, *ciphersuite = "TLS13-AES128-GCM-SHA256"; return 4; } +#endif int test_TLSX_PopulateExtensions_bounds(void) { +#ifdef TEST_TLS_BOUNDS_POPULATE_EXT EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -1751,8 +1822,10 @@ int test_TLSX_PopulateExtensions_bounds(void) wolfSSL_CTX_free(ctx); return EXPECT_RESULT(); -} +#else + return TEST_SKIPPED; #endif +} /* ---------------------------------------------------------------------- */ /* TLSX_PopulateSupportedGroups() is static-in-file, reached only through @@ -1773,12 +1846,12 @@ int test_TLSX_PopulateExtensions_bounds(void) * makes this operand false in this build. Excluded (family 4: contradicted * by TLSX_IsMlKemGroupSupported()'s own postcondition under this build's * feature set). */ +int test_TLSX_PopulateSupportedGroups_bounds(void) +{ #if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(WOLFSSL_TLS13) && \ !defined(NO_WOLFSSL_CLIENT) && defined(HAVE_SUPPORTED_CURVES) && \ defined(WOLFSSL_HAVE_MLKEM_CLIENT_SUPPORT) && !defined(WOLFSSL_NO_ML_KEM) && \ !defined(NO_DH) && defined(HAVE_FFDHE_2048) -int test_TLSX_PopulateSupportedGroups_bounds(void) -{ EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -1823,17 +1896,17 @@ int test_TLSX_PopulateSupportedGroups_bounds(void) wolfSSL_CTX_free(ctx); return EXPECT_RESULT(); -} +#else + return TEST_SKIPPED; #endif +} /* ---------------------------------------------------------------------- */ /* TLSX_CSR_Parse() (client, TLS 1.3, receiving a CertificateStatus * response) and TLSX_CSR2_Parse() / TLSX_CSR_Parse() (server, parsing a * status_request(_v2) request from a ClientHello) - reached through the * WOLFSSL_TEST_VIS TLSX_Parse(). */ -#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(WOLFSSL_TLS13) && \ - defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(NO_WOLFSSL_CLIENT) && \ - !defined(NO_WOLFSSL_SERVER) +#ifdef TEST_TLS_BOUNDS_CSR_PARSE static int test_TLSX_CSR_Parse_fail_after = -1; static int test_TLSX_CSR_Parse_alloc_seen = 0; @@ -1858,9 +1931,11 @@ static void* test_TLSX_CSR_Parse_fail_realloc(void* ptr, size_t size) { return realloc(ptr, size); } +#endif int test_TLSX_CSR_Parse_bounds(void) { +#ifdef TEST_TLS_BOUNDS_CSR_PARSE EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -1955,13 +2030,15 @@ int test_TLSX_CSR_Parse_bounds(void) wolfSSL_CTX_free(ctx); return EXPECT_RESULT(); -} +#else + return TEST_SKIPPED; #endif +} -#if defined(WOLFSSL_TEST_STATIC_BUILD) && \ - defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) && !defined(NO_WOLFSSL_SERVER) int test_TLSX_CSR2_Parse_bounds(void) { +#if defined(WOLFSSL_TEST_STATIC_BUILD) && \ + defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) && !defined(NO_WOLFSSL_SERVER) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -2000,8 +2077,10 @@ int test_TLSX_CSR2_Parse_bounds(void) wolfSSL_CTX_free(ctx); return EXPECT_RESULT(); -} +#else + return TEST_SKIPPED; #endif +} /* ---------------------------------------------------------------------- */ /* TLSX_WriteRequest() / TLSX_GetRequestSize() - the "ssl->ctx && @@ -2010,10 +2089,10 @@ int test_TLSX_CSR2_Parse_bounds(void) * SSL_CM(ssl) dereference that a NULL ssl->ctx could not survive), landing * directly on this guard, so ssl->ctx can safely be forced NULL only for * such a msgType. */ -#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(WOLFSSL_TLS13) && \ - !defined(NO_WOLFSSL_CLIENT) int test_TLSX_ext_dispatch_ctx_extensions_bounds(void) { +#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(WOLFSSL_TLS13) && \ + !defined(NO_WOLFSSL_CLIENT) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -2074,8 +2153,10 @@ int test_TLSX_ext_dispatch_ctx_extensions_bounds(void) wolfSSL_CTX_free(ctx); return EXPECT_RESULT(); -} +#else + return TEST_SKIPPED; #endif +} /* ---------------------------------------------------------------------- */ /* TLSX_WriteRequest()'s and TLSX_GetRequestSize()'s extended-master-secret @@ -2085,10 +2166,10 @@ int test_TLSX_ext_dispatch_ctx_extensions_bounds(void) * first (msgType) and third (version/downgrade) already have pairs * elsewhere. Both tails sit after the HAVE_ECH branch merges back together, * so client_hello reaches them the same way in either function. */ -#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(HAVE_EXTENDED_MASTER) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(WOLFSSL_NO_TLS12) int test_TLSX_WriteRequest_ems_bounds(void) { +#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(HAVE_EXTENDED_MASTER) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(WOLFSSL_NO_TLS12) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -2121,15 +2202,17 @@ int test_TLSX_WriteRequest_ems_bounds(void) wolfSSL_CTX_free(ctx); return EXPECT_RESULT(); -} +#else + return TEST_SKIPPED; #endif +} /* ---------------------------------------------------------------------- */ /* TLSX_WriteRequest()'s trailing length-prefix guard - * "offset > OPAQUE16_LEN || msgType != client_hello". */ -#if defined(WOLFSSL_TEST_STATIC_BUILD) && !defined(NO_WOLFSSL_CLIENT) int test_TLSX_WriteRequest_length_prefix_bounds(void) { +#if defined(WOLFSSL_TEST_STATIC_BUILD) && !defined(NO_WOLFSSL_CLIENT) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -2175,8 +2258,10 @@ int test_TLSX_WriteRequest_length_prefix_bounds(void) wolfSSL_CTX_free(ctx); return EXPECT_RESULT(); -} +#else + return TEST_SKIPPED; #endif +} /* ---------------------------------------------------------------------- */ /* TLSX_WriteResponse() - "TLSX_SupportExtensions(ssl) && output" (its own @@ -2187,10 +2272,10 @@ int test_TLSX_WriteRequest_length_prefix_bounds(void) * has a pair elsewhere). TLSX_GetResponseSize() has the identical EMS tail * one call earlier and is driven by the same three vectors. Both are * WOLFSSL_LOCAL, reachable directly in this static build. */ -#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(HAVE_EXTENDED_MASTER) && \ - !defined(NO_WOLFSSL_SERVER) && !defined(WOLFSSL_NO_TLS12) int test_TLSX_WriteResponse_bounds(void) { +#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(HAVE_EXTENDED_MASTER) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(WOLFSSL_NO_TLS12) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -2250,8 +2335,10 @@ int test_TLSX_WriteResponse_bounds(void) wolfSSL_CTX_free(ctx); return EXPECT_RESULT(); -} +#else + return TEST_SKIPPED; #endif +} /* ---------------------------------------------------------------------- */ /* The msgType splits shared by TLSX_SupportedVersions_GetSize()/_Write(), @@ -2266,12 +2353,12 @@ int test_TLSX_WriteResponse_bounds(void) * same pattern). One ssl populated with all four extensions and driven * through client_hello/server_hello/hello_retry_request/encrypted_extensions * therefore exercises every open condition in this group in one binary. */ +int test_TLSX_ext_msgtype_dispatch_bounds(void) +{ #if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(WOLFSSL_TLS13) && \ !defined(NO_WOLFSSL_CLIENT) && !defined(WOLFSSL_NO_TLS12) && \ defined(HAVE_ENCRYPT_THEN_MAC) && !defined(WOLFSSL_AEAD_ONLY) && \ defined(HAVE_RPK) -int test_TLSX_ext_msgtype_dispatch_bounds(void) -{ EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -2473,8 +2560,10 @@ int test_TLSX_ext_msgtype_dispatch_bounds(void) wolfSSL_CTX_free(ctx); return EXPECT_RESULT(); -} +#else + return TEST_SKIPPED; #endif +} /* ---------------------------------------------------------------------- */ /* TLSX_SecureRenegotiation_Write() - "data && data->enabled && @@ -2482,11 +2571,11 @@ int test_TLSX_ext_msgtype_dispatch_bounds(void) * the "HAVE_SERVER_RENEGOTIATION_INFO only" empty-extension shape the * function's own comment describes, produced here directly with * TLSX_Push() rather than through a real renegotiation handshake. */ +int test_TLSX_SecureRenegotiation_Write_bounds(void) +{ #if defined(WOLFSSL_TEST_STATIC_BUILD) && \ (defined(HAVE_SECURE_RENEGOTIATION) || defined(HAVE_SERVER_RENEGOTIATION_INFO)) && \ !defined(NO_WOLFSSL_CLIENT) -int test_TLSX_SecureRenegotiation_Write_bounds(void) -{ EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -2544,8 +2633,10 @@ int test_TLSX_SecureRenegotiation_Write_bounds(void) wolfSSL_CTX_free(ctx); return EXPECT_RESULT(); -} +#else + return TEST_SKIPPED; #endif +} /* ---------------------------------------------------------------------- */ /* TLSX_SessionTicket_Parse() (server, TLS 1.2), the "ret is REJECT or @@ -2563,9 +2654,7 @@ int test_TLSX_SecureRenegotiation_Write_bounds(void) * value propagates out of TLSX_Parse() and fails the handshake - a real, * protocol-level row, not a callee postcondition that collapses it into * REJECT. So: covered, not excluded. */ -#if defined(HAVE_SSL_MEMIO_TESTS_DEPENDENCIES) && \ - defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_TLS12) && \ - !defined(NO_WOLFSSL_SERVER) && !defined(NO_WOLFSSL_CLIENT) +#ifdef TEST_TLS_BOUNDS_SESSION_TICKET_FF static int test_TLSX_SessionTicket_ff_enc_cb(WOLFSSL* ssl, byte key_name[WOLFSSL_TICKET_NAME_SZ], byte iv[WOLFSSL_TICKET_IV_SZ], byte mac[WOLFSSL_TICKET_MAC_SZ], int enc, byte* ticket, int inLen, @@ -2601,9 +2690,11 @@ static int test_TLSX_SessionTicket_ff_ctx_ready(WOLFSSL_CTX* ctx) test_TLSX_SessionTicket_ff_enc_cb), WOLFSSL_SUCCESS); return EXPECT_RESULT(); } +#endif int test_TLSX_SessionTicket_Parse_falsefalse_bounds(void) { +#ifdef TEST_TLS_BOUNDS_SESSION_TICKET_FF EXPECT_DECLS; test_ssl_memio_ctx test_ctx; WOLFSSL_SESSION* sess = NULL; @@ -2637,10 +2728,7 @@ int test_TLSX_SessionTicket_Parse_falsefalse_bounds(void) wolfSSL_SESSION_free(sess); return EXPECT_RESULT(); -} #else -int test_TLSX_SessionTicket_Parse_falsefalse_bounds(void) -{ return TEST_SKIPPED; -} #endif +} diff --git a/tests/api/test_tls_parse.c b/tests/api/test_tls_parse.c index 56607db5f9..79aa15deae 100644 --- a/tests/api/test_tls_parse.c +++ b/tests/api/test_tls_parse.c @@ -185,7 +185,9 @@ static void test_tls_parse_free_kse(WOLFSSL* ssl, KeyShareEntry* kse) int test_TLSX_ALPN_parse(void) { EXPECT_DECLS; -#if defined(HAVE_ALPN) && defined(HAVE_TLS_EXTENSIONS) && !defined(NO_TLS) && \ +/* TLSX_ALPN_GetRequest() below is WOLFSSL_LOCAL: needs the static library. */ +#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(HAVE_ALPN) && \ + defined(HAVE_TLS_EXTENSIONS) && !defined(NO_TLS) && \ !defined(NO_WOLFSSL_CLIENT) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; From af6393b17241233ab114e41ce847b1513154e55e Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 21 Aug 2026 01:34:57 +0200 Subject: [PATCH 47/57] tests: mark the tls bounds helpers as possibly unused Their callers sit behind differing feature guards, so a configuration that compiles none of them in must not warn. --- tests/api/test_tls_bounds.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/api/test_tls_bounds.c b/tests/api/test_tls_bounds.c index dbeade21d3..41f1af64da 100644 --- a/tests/api/test_tls_bounds.c +++ b/tests/api/test_tls_bounds.c @@ -62,10 +62,21 @@ #define TEST_TLS_BOUNDS_SESSION_TICKET_FF #endif +/* Each helper below is called only from test bodies whose feature guards differ + * from one another, so no single condition describes "some caller is compiled + * in" -- a shared build, for instance, compiles out every WOLFSSL_TEST_STATIC_BUILD + * body at once. Mark them instead of trying to track the union by hand. */ +#if defined(__GNUC__) || defined(__clang__) + #define TEST_TLS_BOUNDS_UNUSED __attribute__((unused)) +#else + #define TEST_TLS_BOUNDS_UNUSED +#endif + /* c32to24() (wolfcrypt/src/misc.c) is only externally linkable when NO_INLINE * is defined; this build inlines it into each translation unit that already * needs it, so it is not visible here. Same 3-byte big-endian length write, * spelled out locally. */ +TEST_TLS_BOUNDS_UNUSED static void test_tls_bounds_c32to24(word32 in, byte* out) { out[0] = (byte)(in >> 16); @@ -80,6 +91,7 @@ static void test_tls_bounds_c32to24(word32 in, byte* out) * fallback, so every server-side ssl created only to unit-test a WOLFSSL_LOCAL * function directly (never running a real handshake) still needs a loaded * cert/key to get past wolfSSL_new() at all. */ +TEST_TLS_BOUNDS_UNUSED static int test_tls_bounds_load_server_cert(WOLFSSL_CTX* ctx) { EXPECT_DECLS; From ce67d8730c71f7a6eb6eaf8d98405cde0b64f31e Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 21 Aug 2026 01:38:20 +0200 Subject: [PATCH 48/57] tests: skip the oversize psk key case where the library does not reject it OPENSSL_EXTRA compiles out the PSK_KEY_ERROR assignment, so the call returns 0 and continues with a key size larger than its buffer. --- tests/api/test_tls_bounds.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/api/test_tls_bounds.c b/tests/api/test_tls_bounds.c index 41f1af64da..a10316573d 100644 --- a/tests/api/test_tls_bounds.c +++ b/tests/api/test_tls_bounds.c @@ -1797,18 +1797,21 @@ int test_TLSX_PopulateExtensions_bounds(void) * reached through the plain client_psk_cb path. */ ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); +#ifndef OPENSSL_EXTRA + /* An over-long key is rejected with PSK_KEY_ERROR. Under OPENSSL_EXTRA the + * assignment is compiled out and the handshake carries on with a key size + * larger than the buffer holding it, so this case is not driven there. */ mode = 0; ExpectNotNull(ssl = wolfSSL_new(ctx)); if (ssl != NULL) { wolfSSL_set_psk_client_callback(ssl, test_TLSX_PopulateExtensions_psk_cb); wolfSSL_set_psk_callback_ctx(ssl, &mode); - /* Both operands true: PSK_KEY_ERROR (no OPENSSL_EXTRA in this - * build, so the 0-length special case does not apply). */ ExpectIntEQ(TLSX_PopulateExtensions(ssl, 0), WC_NO_ERR_TRACE(PSK_KEY_ERROR)); } wolfSSL_free(ssl); +#endif mode = 1; ExpectNotNull(ssl = wolfSSL_new(ctx)); From 07aa9d14074363037c88e574c88c2103a439ce9e Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 21 Aug 2026 01:41:54 +0200 Subject: [PATCH 49/57] tests: expect the ffdhe group when the build supports it TLSX_SupportedCurve_Preferred returns the first entry when the group is in the supported set, and BAD_FUNC_ARG only when it is not. --- tests/api/test_tls_parse.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/api/test_tls_parse.c b/tests/api/test_tls_parse.c index 79aa15deae..62e9a82ace 100644 --- a/tests/api/test_tls_parse.c +++ b/tests/api/test_tls_parse.c @@ -1486,8 +1486,15 @@ int test_TLSX_PointFormat_parse(void) ExpectIntEQ(TLSX_SupportedCurve_Preferred(sslp, 0), WOLFSSL_FFDHE_3072); +#ifdef HAVE_FFDHE_3072 + /* The group is in the supported set here, so the first entry is + * returned rather than skipped. */ + ExpectIntEQ(TLSX_SupportedCurve_Preferred(sslp, 1), + WOLFSSL_FFDHE_3072); +#else ExpectIntEQ(TLSX_SupportedCurve_Preferred(sslp, 1), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); +#endif } #endif wolfSSL_free(sslp); From 506c5468799e1902981ac74cf4c51e093bdd2d1f Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 21 Aug 2026 06:38:33 +0200 Subject: [PATCH 50/57] tests: guard the tls extension tests on the features they actually use Adds the missing WOLFSSL_TLS13 and WOLFSSL_SEND_HRR_COOKIE terms, keeps the shared record builders available to the TLS 1.2 cases, and marks helpers whose callers sit behind differing guards as possibly unused. --- tests/api/test_tls_msgtype.c | 31 ++++++++++++++++++++++++------- tests/api/test_tls_parse.c | 15 +++++++++++++-- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/tests/api/test_tls_msgtype.c b/tests/api/test_tls_msgtype.c index fbe35bfd88..a6004b096b 100644 --- a/tests/api/test_tls_msgtype.c +++ b/tests/api/test_tls_msgtype.c @@ -27,6 +27,13 @@ #include #include +#if defined(__GNUC__) || defined(__clang__) + #define TEST_TLS_MSGTYPE_UNUSED __attribute__((unused)) +#else + #define TEST_TLS_MSGTYPE_UNUSED +#endif + + /* This file drives TLSX_Parse() (src/tls.c) directly with hand-built * extension records to exercise the per-extension "not permitted in this * message" gates from RFC 8446 Section 4.2, plus the argument validation and @@ -41,6 +48,7 @@ * bytes) into buf and return its total length. Content is all-zero: gates * are checked before an extension's data is interpreted, so the exact bytes * only need to satisfy the minimum-size gate, not be semantically valid. */ +TEST_TLS_MSGTYPE_UNUSED static word16 build_ext(byte* buf, word16 type, word16 dataSz) { buf[0] = (byte)(type >> 8); @@ -56,6 +64,7 @@ static word16 build_ext(byte* buf, word16 type, word16 dataSz) * bytes copied verbatim from body) into buf and return its total length. * Unlike build_ext(), the data is caller-supplied, for extensions whose * gates require structurally meaningful content rather than zero bytes. */ +TEST_TLS_MSGTYPE_UNUSED static word16 build_ext_with_body(byte* buf, word16 type, const byte* body, word16 bodyLen) { @@ -75,7 +84,7 @@ static word16 build_ext_with_body(byte* buf, word16 type, const byte* body, int test_tls_msgtype_arg_guard(void) { EXPECT_DECLS; -#if !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte dummy[4] = { 0 }; @@ -1200,10 +1209,12 @@ int test_tls_msgtype_ech(void) return EXPECT_RESULT(); } -#if defined(HAVE_SNI) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(HAVE_SNI) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_TLS) /* Builds an SNI extension body: 2-byte list length, 1-byte name type * (WOLFSSL_SNI_HOST_NAME), 2-byte name length, name bytes. Returns the * total body length. */ +TEST_TLS_MSGTYPE_UNUSED static word16 build_sni_body(byte* buf, const char* host) { word16 hostLen = (word16)XSTRLEN(host); @@ -1224,7 +1235,8 @@ static word16 build_sni_body(byte* buf, const char* host) int test_tls_msgtype_sni_find(void) { EXPECT_DECLS; -#if defined(HAVE_SNI) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(HAVE_SNI) && defined(WOLFSSL_TLS13) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; const char* host = "example.com"; @@ -1297,7 +1309,8 @@ int test_tls_msgtype_sni_parse_response_gate(void) int test_tls_msgtype_sni_parse_size_gates(void) { EXPECT_DECLS; -#if defined(HAVE_SNI) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(HAVE_SNI) && defined(WOLFSSL_TLS13) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; const char* host = "srv.example"; @@ -1324,9 +1337,11 @@ int test_tls_msgtype_sni_parse_size_gates(void) return EXPECT_RESULT(); } -#if defined(HAVE_SNI) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(HAVE_SNI) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_TLS) /* SNI receive callback used to force cacheOnly in TLSX_SNI_Parse() when no * SNI has been configured on the SSL object. */ +TEST_TLS_MSGTYPE_UNUSED static int sni_recv_cb(WOLFSSL* ssl, int* ret, void* arg) { (void)ssl; (void)ret; (void)arg; @@ -1398,7 +1413,8 @@ int test_tls_msgtype_sni_parse_cacheonly(void) int test_tls_msgtype_sni_parse_match(void) { EXPECT_DECLS; -#if defined(HAVE_SNI) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ +#if defined(HAVE_SNI) && defined(WOLFSSL_TLS13) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ !defined(NO_WOLFSSL_SERVER) && defined(WOLFSSL_TLS13) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -2016,7 +2032,8 @@ int test_tls_msgtype_psk_ch_binder_gates(void) int test_tls_msgtype_cookie_parse_gates(void) { EXPECT_DECLS; -#if defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[24]; diff --git a/tests/api/test_tls_parse.c b/tests/api/test_tls_parse.c index 62e9a82ace..b03d901f1d 100644 --- a/tests/api/test_tls_parse.c +++ b/tests/api/test_tls_parse.c @@ -28,6 +28,13 @@ #include #ifndef NO_DH #include + +#if defined(__GNUC__) || defined(__clang__) + #define TEST_TLS_PARSE_UNUSED __attribute__((unused)) +#else + #define TEST_TLS_PARSE_UNUSED +#endif + #endif /* Helper to build a server-side WOLFSSL_CTX with a certificate/key loaded, @@ -94,6 +101,7 @@ static word16 test_tls_parse_build_ext(byte* out, word16 outCap, static int tls_parse_fail_after = -1; static int tls_parse_alloc_seen = 0; +TEST_TLS_PARSE_UNUSED static void* tls_parse_fail_malloc(size_t size) { if (tls_parse_fail_after >= 0) { @@ -106,11 +114,13 @@ static void* tls_parse_fail_malloc(size_t size) return malloc(size); } +TEST_TLS_PARSE_UNUSED static void tls_parse_fail_free(void* ptr) { free(ptr); } +TEST_TLS_PARSE_UNUSED static void* tls_parse_fail_realloc(void* ptr, size_t size) { return realloc(ptr, size); @@ -1908,8 +1918,9 @@ int test_TLSX_ValidateSupportedCurves(void) int test_TLSX_SupportedGroups_parse(void) { EXPECT_DECLS; -#if defined(HAVE_SUPPORTED_CURVES) && !defined(NO_TLS) && \ - !defined(NO_WOLFSSL_CLIENT) && defined(WOLFSSL_TEST_STATIC_BUILD) +#if defined(HAVE_SUPPORTED_CURVES) && defined(WOLFSSL_TLS13) && \ + !defined(NO_TLS) && !defined(NO_WOLFSSL_CLIENT) && \ + defined(WOLFSSL_TEST_STATIC_BUILD) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; /* secp256r1: a real, locally supported curve. */ From 63c038177bd4c8029e84cc0b899f898c95a9f316 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 21 Aug 2026 07:15:59 +0200 Subject: [PATCH 51/57] tests: guard the tls extension tests on server side and auth availability The server-side parse paths are compiled out by NO_WOLFSSL_SERVER, and a TLS 1.2 server context cannot be created with no RSA and no ECC. --- tests/api/test_tls_msgtype.c | 16 ++++++++++++---- tests/api/test_tls_parse.c | 11 +++++++---- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/tests/api/test_tls_msgtype.c b/tests/api/test_tls_msgtype.c index a6004b096b..13865321b8 100644 --- a/tests/api/test_tls_msgtype.c +++ b/tests/api/test_tls_msgtype.c @@ -262,8 +262,11 @@ int test_tls_msgtype_certificate_ext_offered(void) int test_tls_msgtype_sni_tls13(void) { EXPECT_DECLS; +/* Drives TLSX_SNI_Parse's isRequest path, which is server-side code and + * is compiled out by NO_WOLFSSL_SERVER. */ #if defined(HAVE_SNI) && defined(WOLFSSL_TLS13) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_TLS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[16]; @@ -1236,7 +1239,8 @@ int test_tls_msgtype_sni_find(void) { EXPECT_DECLS; #if defined(HAVE_SNI) && defined(WOLFSSL_TLS13) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_TLS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; const char* host = "example.com"; @@ -1309,8 +1313,11 @@ int test_tls_msgtype_sni_parse_response_gate(void) int test_tls_msgtype_sni_parse_size_gates(void) { EXPECT_DECLS; +/* Drives TLSX_SNI_Parse's isRequest path, which is server-side code and + * is compiled out by NO_WOLFSSL_SERVER. */ #if defined(HAVE_SNI) && defined(WOLFSSL_TLS13) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_TLS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; const char* host = "srv.example"; @@ -1414,7 +1421,8 @@ int test_tls_msgtype_sni_parse_match(void) { EXPECT_DECLS; #if defined(HAVE_SNI) && defined(WOLFSSL_TLS13) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_TLS) && \ !defined(NO_WOLFSSL_SERVER) && defined(WOLFSSL_TLS13) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; diff --git a/tests/api/test_tls_parse.c b/tests/api/test_tls_parse.c index b03d901f1d..4d8c9f6322 100644 --- a/tests/api/test_tls_parse.c +++ b/tests/api/test_tls_parse.c @@ -715,7 +715,7 @@ int test_TLSX_THM_parse(void) { EXPECT_DECLS; #if defined(HAVE_TRUNCATED_HMAC) && !defined(NO_TLS) && \ - !defined(NO_WOLFSSL_CLIENT) + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte ext[8]; @@ -1111,7 +1111,8 @@ int test_TLSX_SignatureAlgorithms_parse(void) { EXPECT_DECLS; #if !defined(NO_CERTS) && !defined(WOLFSSL_NO_SIGALG) && !defined(NO_TLS) && \ - !defined(NO_WOLFSSL_SERVER) + !defined(NO_WOLFSSL_SERVER) && \ + (!defined(NO_RSA) || defined(HAVE_ECC)) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte ext[16]; @@ -1424,7 +1425,8 @@ int test_TLSX_PointFormat_parse(void) { EXPECT_DECLS; #if defined(HAVE_SUPPORTED_CURVES) && !defined(NO_TLS) && \ - !defined(NO_WOLFSSL_SERVER) + !defined(NO_WOLFSSL_SERVER) && \ + (!defined(NO_RSA) || defined(HAVE_ECC)) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte ext[16]; @@ -1576,7 +1578,8 @@ int test_TLSX_PointFormat_parse(void) int test_TLSX_SNI_parse(void) { EXPECT_DECLS; -#if defined(HAVE_SNI) && !defined(NO_TLS) && !defined(NO_WOLFSSL_SERVER) +#if defined(HAVE_SNI) && !defined(NO_TLS) && !defined(NO_WOLFSSL_SERVER) && \ + (!defined(NO_RSA) || defined(HAVE_ECC)) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte ext[64]; From 438c673f39a7ab792314cd773a33ddbdbd752383 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 21 Aug 2026 07:24:43 +0200 Subject: [PATCH 52/57] tests: mark the tls parse server-context helper as possibly unused Configurations without a server or without RSA and ECC compile out every caller. --- tests/api/test_tls_parse.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/api/test_tls_parse.c b/tests/api/test_tls_parse.c index 4d8c9f6322..9f39440e1c 100644 --- a/tests/api/test_tls_parse.c +++ b/tests/api/test_tls_parse.c @@ -40,6 +40,7 @@ /* Helper to build a server-side WOLFSSL_CTX with a certificate/key loaded, * as required for wolfSSL_new() to succeed on a server context. */ +TEST_TLS_PARSE_UNUSED static WOLFSSL_CTX* test_tls_parse_server_ctx(WOLFSSL_METHOD* method) { WOLFSSL_CTX* ctx = NULL; From 9874fa7e06017ed4265678312b81057622acb96f Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 21 Aug 2026 07:33:44 +0200 Subject: [PATCH 53/57] tests: define the tls parse unused marker outside the dh guard It was placed inside #ifndef NO_DH, so a build without DH left it undefined where the helpers use it. --- tests/api/test_tls_parse.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/api/test_tls_parse.c b/tests/api/test_tls_parse.c index 9f39440e1c..a82de76580 100644 --- a/tests/api/test_tls_parse.c +++ b/tests/api/test_tls_parse.c @@ -28,15 +28,16 @@ #include #ifndef NO_DH #include +#endif +/* Several helpers below are called only from test bodies whose feature guards + * differ, so a configuration can compile in none of their callers. */ #if defined(__GNUC__) || defined(__clang__) #define TEST_TLS_PARSE_UNUSED __attribute__((unused)) #else #define TEST_TLS_PARSE_UNUSED #endif -#endif - /* Helper to build a server-side WOLFSSL_CTX with a certificate/key loaded, * as required for wolfSSL_new() to succeed on a server context. */ From 3911d99e9768ec02ff965e867495f4912a161608 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 21 Aug 2026 08:57:54 +0200 Subject: [PATCH 54/57] tests: guard the tls extension tests on extension and tls 1.2 availability Adds the HAVE_TLS_EXTENSIONS, per-extension and WOLFSSL_NO_TLS12 terms the bodies need, and releases the SNI list a test detaches from its extension. --- tests/api/test_tls_bounds.c | 113 +++++++++------- tests/api/test_tls_msgtype.c | 248 +++++++++++++++++++++-------------- tests/api/test_tls_parse.c | 96 +++++++------- 3 files changed, 266 insertions(+), 191 deletions(-) diff --git a/tests/api/test_tls_bounds.c b/tests/api/test_tls_bounds.c index a10316573d..09ac5e72c7 100644 --- a/tests/api/test_tls_bounds.c +++ b/tests/api/test_tls_bounds.c @@ -113,7 +113,9 @@ static int test_tls_bounds_load_server_cert(WOLFSSL_CTX* ctx) * removal in the linked list are all reachable through wolfSSL_UseSNI(). */ int test_TLSX_UseSNI_bounds(void) { -#if defined(HAVE_SNI) && !defined(NO_WOLFSSL_CLIENT) +#if defined(HAVE_SNI) && !defined(NO_WOLFSSL_CLIENT) && \ + defined(HAVE_TLS_EXTENSIONS) && \ + !defined(WOLFSSL_NO_TLS12) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -183,7 +185,9 @@ int test_TLSX_UseSNI_bounds(void) * TLSX_UseALPN() with a NULL data pointer at all is to call it directly. */ int test_TLSX_UseALPN_bounds(void) { -#if defined(HAVE_ALPN) && !defined(NO_WOLFSSL_CLIENT) +#if defined(HAVE_ALPN) && !defined(NO_WOLFSSL_CLIENT) && \ + defined(HAVE_TLS_EXTENSIONS) && \ + !defined(WOLFSSL_NO_TLS12) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -219,7 +223,9 @@ int test_TLSX_UseALPN_bounds(void) * in-range call. */ int test_TLSX_UseMaxFragment_bounds(void) { -#if defined(HAVE_MAX_FRAGMENT) && !defined(NO_WOLFSSL_CLIENT) +#if defined(HAVE_MAX_FRAGMENT) && !defined(NO_WOLFSSL_CLIENT) && \ + defined(HAVE_TLS_EXTENSIONS) && \ + !defined(WOLFSSL_NO_TLS12) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -255,7 +261,9 @@ int test_TLSX_UseMaxFragment_bounds(void) * validation of its own. */ int test_TLSX_UseCertificateStatusRequest_bounds(void) { -#if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(NO_WOLFSSL_CLIENT) +#if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(NO_WOLFSSL_CLIENT) && \ + defined(HAVE_TLS_EXTENSIONS) && \ + !defined(WOLFSSL_NO_TLS12) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -283,7 +291,9 @@ int test_TLSX_UseCertificateStatusRequest_bounds(void) /* TLSX_UseCertificateStatusRequestV2 - same pattern as V1 above. */ int test_TLSX_UseCertificateStatusRequestV2_bounds(void) { -#if defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) && !defined(NO_WOLFSSL_CLIENT) +#if defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) && !defined(NO_WOLFSSL_CLIENT) && \ + defined(HAVE_TLS_EXTENSIONS) && \ + !defined(WOLFSSL_NO_TLS12) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -333,7 +343,9 @@ int test_TLSX_UseCertificateStatusRequestV2_bounds(void) * and second operands. */ int test_TLSX_SupportExtensions_bounds(void) { -#if !defined(NO_WOLFSSL_CLIENT) +#if !defined(NO_WOLFSSL_CLIENT) && \ + defined(HAVE_TLS_EXTENSIONS) && \ + !defined(WOLFSSL_NO_TLS12) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -395,7 +407,9 @@ int test_TLSX_SupportExtensions_bounds(void) * building the minimal extension/context state each one dereferences. */ int test_TLSX_CSR2_InitRequests_bounds(void) { -#ifdef TEST_TLS_BOUNDS_CSR2_REQUESTS +#if defined(TEST_TLS_BOUNDS_CSR2_REQUESTS) && \ + defined(HAVE_TLS_EXTENSIONS) && \ + !defined(WOLFSSL_NO_TLS12) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -443,7 +457,9 @@ int test_TLSX_CSR2_InitRequests_bounds(void) int test_TLSX_CSR2_ForceRequest_bounds(void) { -#ifdef TEST_TLS_BOUNDS_CSR2_REQUESTS +#if defined(TEST_TLS_BOUNDS_CSR2_REQUESTS) && \ + defined(HAVE_TLS_EXTENSIONS) && \ + !defined(WOLFSSL_NO_TLS12) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -490,9 +506,8 @@ int test_TLSX_CSR2_ForceRequest_bounds(void) int test_TLSX_CSR_GetRequest_ex_bounds(void) { -#if defined(WOLFSSL_TEST_STATIC_BUILD) && \ - defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(WOLFSSL_NO_TLS12) && \ - !defined(NO_WOLFSSL_CLIENT) +#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(WOLFSSL_NO_TLS12) && !defined(NO_WOLFSSL_CLIENT) && \ + defined(HAVE_TLS_EXTENSIONS) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -584,7 +599,8 @@ int test_wolfSSL_make_eap_keys_bounds(void) * 0)". Both are public (WOLFSSL_API) and reachable directly. */ int test_wolfSSL_SetTlsHmacInner_bounds(void) { -#if !defined(NO_WOLFSSL_CLIENT) && !defined(WOLFSSL_AEAD_ONLY) +#if !defined(NO_WOLFSSL_CLIENT) && !defined(WOLFSSL_AEAD_ONLY) && \ + !defined(WOLFSSL_NO_TLS12) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -727,8 +743,9 @@ int test_BuildTlsHandshakeHash_bounds(void) * caller-supplied lengths. */ int test_TLS_hmac_bounds(void) { -#if !defined(NO_HMAC) && !defined(WOLFSSL_AEAD_ONLY) && !defined(NO_TLS) && \ - defined(NO_OLD_TLS) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_SHA256) +#if !defined(NO_HMAC) && !defined(WOLFSSL_AEAD_ONLY) && !defined(NO_TLS) && defined(NO_OLD_TLS) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_SHA256) && \ + defined(HAVE_TLS_EXTENSIONS) && \ + !defined(WOLFSSL_NO_TLS12) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -835,8 +852,9 @@ int test_TLS_hmac_bounds(void) * running total past 0xFFFF (257 * 256 + 2 == 65794). */ int test_TLSX_ALPN_GetSize_overflow(void) { -#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(HAVE_ALPN) && \ - !defined(NO_WOLFSSL_CLIENT) +#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(HAVE_ALPN) && !defined(NO_WOLFSSL_CLIENT) && \ + defined(HAVE_TLS_EXTENSIONS) && \ + !defined(WOLFSSL_NO_TLS12) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -888,8 +906,8 @@ int test_TLSX_ALPN_GetSize_overflow(void) * extension" skip), not at which top-level wrapper made the call. */ int test_TLSX_Cookie_bounds(void) { -#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(WOLFSSL_TLS13) && \ - defined(WOLFSSL_SEND_HRR_COOKIE) && !defined(NO_WOLFSSL_CLIENT) +#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(WOLFSSL_TLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) && !defined(NO_WOLFSSL_CLIENT) && \ + defined(HAVE_TLS_EXTENSIONS) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -991,7 +1009,8 @@ static int test_TLSX_CSR_write_getsize_status_cb(WOLFSSL* ssl, void* arg) int test_TLSX_CSR_write_getsize_bounds(void) { -#ifdef TEST_TLS_BOUNDS_CSR_STATUS_CB +#if defined(TEST_TLS_BOUNDS_CSR_STATUS_CB) && \ + defined(HAVE_TLS_EXTENSIONS) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -1118,7 +1137,8 @@ int test_TLSX_CSR_write_getsize_bounds(void) * operands are independently reachable here. */ int test_TLSX_CSR_SetResponseWithStatusCB_bounds(void) { -#ifdef TEST_TLS_BOUNDS_CSR_STATUS_CB +#if defined(TEST_TLS_BOUNDS_CSR_STATUS_CB) && \ + defined(HAVE_TLS_EXTENSIONS) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -1407,7 +1427,8 @@ static int test_ProcessChainOCSPRequest_setup(WOLFSSL_CTX** pctx, int test_ProcessChainOCSPRequest_bounds(void) { -#ifdef TEST_TLS_BOUNDS_OCSP_CHAIN +#if defined(TEST_TLS_BOUNDS_OCSP_CHAIN) && \ + defined(HAVE_TLS_EXTENSIONS) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -1678,7 +1699,9 @@ static unsigned int test_TLSX_PopulateExtensions_psk_tls13_cb(WOLFSSL* ssl, int test_TLSX_PopulateExtensions_bounds(void) { -#ifdef TEST_TLS_BOUNDS_POPULATE_EXT +#if defined(TEST_TLS_BOUNDS_POPULATE_EXT) && \ + defined(HAVE_TLS_EXTENSIONS) && \ + !defined(WOLFSSL_NO_TLS12) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -1863,10 +1886,8 @@ int test_TLSX_PopulateExtensions_bounds(void) * feature set). */ int test_TLSX_PopulateSupportedGroups_bounds(void) { -#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(WOLFSSL_TLS13) && \ - !defined(NO_WOLFSSL_CLIENT) && defined(HAVE_SUPPORTED_CURVES) && \ - defined(WOLFSSL_HAVE_MLKEM_CLIENT_SUPPORT) && !defined(WOLFSSL_NO_ML_KEM) && \ - !defined(NO_DH) && defined(HAVE_FFDHE_2048) +#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && defined(HAVE_SUPPORTED_CURVES) && defined(WOLFSSL_HAVE_MLKEM_CLIENT_SUPPORT) && !defined(WOLFSSL_NO_ML_KEM) && !defined(NO_DH) && defined(HAVE_FFDHE_2048) && \ + defined(HAVE_TLS_EXTENSIONS) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -1950,7 +1971,9 @@ static void* test_TLSX_CSR_Parse_fail_realloc(void* ptr, size_t size) int test_TLSX_CSR_Parse_bounds(void) { -#ifdef TEST_TLS_BOUNDS_CSR_PARSE +#if defined(TEST_TLS_BOUNDS_CSR_PARSE) && \ + defined(HAVE_TLS_EXTENSIONS) && \ + !defined(WOLFSSL_NO_TLS12) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -2052,8 +2075,9 @@ int test_TLSX_CSR_Parse_bounds(void) int test_TLSX_CSR2_Parse_bounds(void) { -#if defined(WOLFSSL_TEST_STATIC_BUILD) && \ - defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) && !defined(NO_WOLFSSL_SERVER) +#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) && !defined(NO_WOLFSSL_SERVER) && \ + defined(HAVE_TLS_EXTENSIONS) && \ + !defined(WOLFSSL_NO_TLS12) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -2106,8 +2130,8 @@ int test_TLSX_CSR2_Parse_bounds(void) * such a msgType. */ int test_TLSX_ext_dispatch_ctx_extensions_bounds(void) { -#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(WOLFSSL_TLS13) && \ - !defined(NO_WOLFSSL_CLIENT) +#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && \ + defined(HAVE_TLS_EXTENSIONS) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -2183,8 +2207,8 @@ int test_TLSX_ext_dispatch_ctx_extensions_bounds(void) * so client_hello reaches them the same way in either function. */ int test_TLSX_WriteRequest_ems_bounds(void) { -#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(HAVE_EXTENDED_MASTER) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(WOLFSSL_NO_TLS12) +#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(HAVE_EXTENDED_MASTER) && !defined(NO_WOLFSSL_CLIENT) && !defined(WOLFSSL_NO_TLS12) && \ + defined(HAVE_TLS_EXTENSIONS) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -2227,7 +2251,9 @@ int test_TLSX_WriteRequest_ems_bounds(void) * "offset > OPAQUE16_LEN || msgType != client_hello". */ int test_TLSX_WriteRequest_length_prefix_bounds(void) { -#if defined(WOLFSSL_TEST_STATIC_BUILD) && !defined(NO_WOLFSSL_CLIENT) +#if defined(WOLFSSL_TEST_STATIC_BUILD) && !defined(NO_WOLFSSL_CLIENT) && \ + defined(HAVE_TLS_EXTENSIONS) && \ + !defined(WOLFSSL_NO_TLS12) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -2289,8 +2315,8 @@ int test_TLSX_WriteRequest_length_prefix_bounds(void) * WOLFSSL_LOCAL, reachable directly in this static build. */ int test_TLSX_WriteResponse_bounds(void) { -#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(HAVE_EXTENDED_MASTER) && \ - !defined(NO_WOLFSSL_SERVER) && !defined(WOLFSSL_NO_TLS12) +#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(HAVE_EXTENDED_MASTER) && !defined(NO_WOLFSSL_SERVER) && !defined(WOLFSSL_NO_TLS12) && \ + defined(HAVE_TLS_EXTENSIONS) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -2370,10 +2396,8 @@ int test_TLSX_WriteResponse_bounds(void) * therefore exercises every open condition in this group in one binary. */ int test_TLSX_ext_msgtype_dispatch_bounds(void) { -#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(WOLFSSL_TLS13) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(WOLFSSL_NO_TLS12) && \ - defined(HAVE_ENCRYPT_THEN_MAC) && !defined(WOLFSSL_AEAD_ONLY) && \ - defined(HAVE_RPK) +#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && !defined(WOLFSSL_NO_TLS12) && defined(HAVE_ENCRYPT_THEN_MAC) && !defined(WOLFSSL_AEAD_ONLY) && defined(HAVE_RPK) && \ + defined(HAVE_TLS_EXTENSIONS) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -2588,9 +2612,9 @@ int test_TLSX_ext_msgtype_dispatch_bounds(void) * TLSX_Push() rather than through a real renegotiation handshake. */ int test_TLSX_SecureRenegotiation_Write_bounds(void) { -#if defined(WOLFSSL_TEST_STATIC_BUILD) && \ - (defined(HAVE_SECURE_RENEGOTIATION) || defined(HAVE_SERVER_RENEGOTIATION_INFO)) && \ - !defined(NO_WOLFSSL_CLIENT) +#if defined(WOLFSSL_TEST_STATIC_BUILD) && (defined(HAVE_SECURE_RENEGOTIATION) || defined(HAVE_SERVER_RENEGOTIATION_INFO)) && !defined(NO_WOLFSSL_CLIENT) && \ + defined(HAVE_TLS_EXTENSIONS) && \ + !defined(WOLFSSL_NO_TLS12) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -2709,7 +2733,8 @@ static int test_TLSX_SessionTicket_ff_ctx_ready(WOLFSSL_CTX* ctx) int test_TLSX_SessionTicket_Parse_falsefalse_bounds(void) { -#ifdef TEST_TLS_BOUNDS_SESSION_TICKET_FF +#if defined(TEST_TLS_BOUNDS_SESSION_TICKET_FF) && \ + !defined(WOLFSSL_NO_TLS12) EXPECT_DECLS; test_ssl_memio_ctx test_ctx; WOLFSSL_SESSION* sess = NULL; diff --git a/tests/api/test_tls_msgtype.c b/tests/api/test_tls_msgtype.c index 13865321b8..64fa7bffa3 100644 --- a/tests/api/test_tls_msgtype.c +++ b/tests/api/test_tls_msgtype.c @@ -44,6 +44,25 @@ #if !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ defined(HAVE_TLS_EXTENSIONS) +/* TLSX_SNI_Free() is file-static in src/tls.c. A test that detaches an SNI + * list from its extension has to release it the same way: the host name, then + * the node. */ +TEST_TLS_MSGTYPE_UNUSED +static void test_tls_msgtype_free_sni(void* p, void* heap) +{ + SNI* sni = (SNI*)p; + + while (sni != NULL) { + SNI* next = sni->next; + + if (sni->type == WOLFSSL_SNI_HOST_NAME) + XFREE(sni->data.host_name, heap, DYNAMIC_TYPE_TLSX); + XFREE(sni, heap, DYNAMIC_TYPE_TLSX); + sni = next; + } +} + + /* Build one extension record (2-byte type, 2-byte length, N zero data * bytes) into buf and return its total length. Content is all-zero: gates * are checked before an extension's data is interpreted, so the exact bytes @@ -84,7 +103,8 @@ static word16 build_ext_with_body(byte* buf, word16 type, const byte* body, int test_tls_msgtype_arg_guard(void) { EXPECT_DECLS; -#if defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte dummy[4] = { 0 }; @@ -140,8 +160,8 @@ static const byte psk_ch_body[] = { int test_tls_msgtype_psk_duplicate(void) { EXPECT_DECLS; -#if defined(WOLFSSL_TLS13) && (defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(WOLFSSL_TLS13) && (defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[64]; @@ -184,8 +204,8 @@ int test_tls_msgtype_psk_duplicate(void) int test_tls_msgtype_certificate_ext_offered(void) { EXPECT_DECLS; -#if defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ - defined(HAVE_MAX_FRAGMENT) && !defined(WOLFSSL_NO_TLS12) +#if defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && defined(HAVE_MAX_FRAGMENT) && !defined(WOLFSSL_NO_TLS12) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[8]; @@ -264,9 +284,8 @@ int test_tls_msgtype_sni_tls13(void) EXPECT_DECLS; /* Drives TLSX_SNI_Parse's isRequest path, which is server-side code and * is compiled out by NO_WOLFSSL_SERVER. */ -#if defined(HAVE_SNI) && defined(WOLFSSL_TLS13) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ - !defined(NO_TLS) +#if defined(HAVE_SNI) && defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[16]; @@ -296,8 +315,8 @@ int test_tls_msgtype_sni_tls13(void) int test_tls_msgtype_sni_tls12(void) { EXPECT_DECLS; -#if defined(HAVE_SNI) && !defined(WOLFSSL_NO_TLS12) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(HAVE_SNI) && !defined(WOLFSSL_NO_TLS12) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[16]; @@ -327,7 +346,8 @@ int test_tls_msgtype_sni_tls12(void) int test_tls_msgtype_tca(void) { EXPECT_DECLS; -#if !defined(WOLFSSL_NO_TLS12) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if !defined(WOLFSSL_NO_TLS12) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[16]; @@ -357,7 +377,8 @@ int test_tls_msgtype_tca(void) int test_tls_msgtype_mfl_tls13(void) { EXPECT_DECLS; -#if defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[16]; @@ -387,7 +408,8 @@ int test_tls_msgtype_mfl_tls13(void) int test_tls_msgtype_mfl_tls12(void) { EXPECT_DECLS; -#if !defined(WOLFSSL_NO_TLS12) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if !defined(WOLFSSL_NO_TLS12) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[16]; @@ -417,8 +439,8 @@ int test_tls_msgtype_mfl_tls12(void) int test_tls_msgtype_supported_groups_tls13(void) { EXPECT_DECLS; -#if defined(HAVE_SUPPORTED_CURVES) && defined(WOLFSSL_TLS13) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(HAVE_SUPPORTED_CURVES) && defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[16]; @@ -448,7 +470,8 @@ int test_tls_msgtype_supported_groups_tls13(void) int test_tls_msgtype_point_formats(void) { EXPECT_DECLS; -#if !defined(WOLFSSL_NO_TLS12) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if !defined(WOLFSSL_NO_TLS12) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[16]; @@ -478,8 +501,8 @@ int test_tls_msgtype_point_formats(void) int test_tls_msgtype_csr_tls13(void) { EXPECT_DECLS; -#if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && defined(WOLFSSL_TLS13) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[16]; @@ -517,8 +540,8 @@ int test_tls_msgtype_csr_tls13(void) int test_tls_msgtype_csr_tls12(void) { EXPECT_DECLS; -#if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(WOLFSSL_NO_TLS12) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(WOLFSSL_NO_TLS12) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[16]; @@ -548,8 +571,8 @@ int test_tls_msgtype_csr_tls12(void) int test_tls_msgtype_csr2_tls12(void) { EXPECT_DECLS; -#if defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) && !defined(WOLFSSL_NO_TLS12) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) && !defined(WOLFSSL_NO_TLS12) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[16]; @@ -579,8 +602,8 @@ int test_tls_msgtype_csr2_tls12(void) int test_tls_msgtype_extms(void) { EXPECT_DECLS; -#if defined(HAVE_EXTENDED_MASTER) && !defined(WOLFSSL_NO_TLS12) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(HAVE_EXTENDED_MASTER) && !defined(WOLFSSL_NO_TLS12) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[8]; @@ -610,8 +633,8 @@ int test_tls_msgtype_extms(void) int test_tls_msgtype_renegotiation_info(void) { EXPECT_DECLS; -#if defined(HAVE_SECURE_RENEGOTIATION) && !defined(WOLFSSL_NO_TLS12) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(HAVE_SECURE_RENEGOTIATION) && !defined(WOLFSSL_NO_TLS12) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[8]; @@ -641,8 +664,8 @@ int test_tls_msgtype_renegotiation_info(void) int test_tls_msgtype_session_ticket_tls12(void) { EXPECT_DECLS; -#if defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_TLS12) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_TLS12) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[8]; @@ -672,8 +695,8 @@ int test_tls_msgtype_session_ticket_tls12(void) int test_tls_msgtype_alpn_tls13(void) { EXPECT_DECLS; -#if defined(HAVE_ALPN) && defined(WOLFSSL_TLS13) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(HAVE_ALPN) && defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[16]; @@ -704,8 +727,8 @@ int test_tls_msgtype_alpn_tls13(void) int test_tls_msgtype_alpn_tls12(void) { EXPECT_DECLS; -#if defined(HAVE_ALPN) && !defined(WOLFSSL_NO_TLS12) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(HAVE_ALPN) && !defined(WOLFSSL_NO_TLS12) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[16]; @@ -737,8 +760,8 @@ int test_tls_msgtype_alpn_tls12(void) int test_tls_msgtype_sigalgs_tls13(void) { EXPECT_DECLS; -#if !defined(NO_CERTS) && !defined(WOLFSSL_NO_SIGALG) && \ - defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if !defined(NO_CERTS) && !defined(WOLFSSL_NO_SIGALG) && defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[16]; @@ -768,8 +791,8 @@ int test_tls_msgtype_sigalgs_tls13(void) int test_tls_msgtype_etm(void) { EXPECT_DECLS; -#if defined(HAVE_ENCRYPT_THEN_MAC) && !defined(WOLFSSL_AEAD_ONLY) && \ - !defined(WOLFSSL_NO_TLS12) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(HAVE_ENCRYPT_THEN_MAC) && !defined(WOLFSSL_AEAD_ONLY) && !defined(WOLFSSL_NO_TLS12) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[8]; @@ -799,7 +822,8 @@ int test_tls_msgtype_etm(void) int test_tls_msgtype_supported_versions(void) { EXPECT_DECLS; -#if defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[8]; @@ -832,7 +856,8 @@ int test_tls_msgtype_supported_versions(void) int test_tls_msgtype_cookie(void) { EXPECT_DECLS; -#if defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[8]; @@ -862,8 +887,8 @@ int test_tls_msgtype_cookie(void) int test_tls_msgtype_psk(void) { EXPECT_DECLS; -#if defined(WOLFSSL_TLS13) && (defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(WOLFSSL_TLS13) && (defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[8]; @@ -893,8 +918,8 @@ int test_tls_msgtype_psk(void) int test_tls_msgtype_cert_with_extern_psk(void) { EXPECT_DECLS; -#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_CERT_WITH_EXTERN_PSK) && \ - !defined(NO_PSK) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_CERT_WITH_EXTERN_PSK) && !defined(NO_PSK) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[8]; @@ -928,8 +953,8 @@ int test_tls_msgtype_cert_with_extern_psk(void) int test_tls_msgtype_early_data(void) { EXPECT_DECLS; -#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_EARLY_DATA) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_EARLY_DATA) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[8]; @@ -962,8 +987,8 @@ int test_tls_msgtype_early_data(void) int test_tls_msgtype_sigalgs_cert(void) { EXPECT_DECLS; -#if !defined(NO_CERTS) && !defined(WOLFSSL_NO_SIGALG) && \ - defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if !defined(NO_CERTS) && !defined(WOLFSSL_NO_SIGALG) && defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[16]; @@ -993,8 +1018,8 @@ int test_tls_msgtype_sigalgs_cert(void) int test_tls_msgtype_key_share(void) { EXPECT_DECLS; -#if defined(HAVE_SUPPORTED_CURVES) && defined(WOLFSSL_TLS13) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(HAVE_SUPPORTED_CURVES) && defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[8]; @@ -1027,8 +1052,8 @@ int test_tls_msgtype_key_share(void) int test_tls_msgtype_client_cert_type_tls13(void) { EXPECT_DECLS; -#if defined(HAVE_RPK) && defined(WOLFSSL_TLS13) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(HAVE_RPK) && defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[8]; @@ -1058,8 +1083,8 @@ int test_tls_msgtype_client_cert_type_tls13(void) int test_tls_msgtype_client_cert_type_tls12(void) { EXPECT_DECLS; -#if defined(HAVE_RPK) && !defined(WOLFSSL_NO_TLS12) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(HAVE_RPK) && !defined(WOLFSSL_NO_TLS12) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[8]; @@ -1089,8 +1114,8 @@ int test_tls_msgtype_client_cert_type_tls12(void) int test_tls_msgtype_server_cert_type_tls13(void) { EXPECT_DECLS; -#if defined(HAVE_RPK) && defined(WOLFSSL_TLS13) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(HAVE_RPK) && defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[8]; @@ -1120,8 +1145,8 @@ int test_tls_msgtype_server_cert_type_tls13(void) int test_tls_msgtype_server_cert_type_tls12(void) { EXPECT_DECLS; -#if defined(HAVE_RPK) && !defined(WOLFSSL_NO_TLS12) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(HAVE_RPK) && !defined(WOLFSSL_NO_TLS12) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[8]; @@ -1151,7 +1176,8 @@ int test_tls_msgtype_server_cert_type_tls12(void) int test_tls_msgtype_connection_id(void) { EXPECT_DECLS; -#if defined(WOLFSSL_DTLS_CID) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(WOLFSSL_DTLS_CID) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[8]; @@ -1181,8 +1207,8 @@ int test_tls_msgtype_connection_id(void) int test_tls_msgtype_ech(void) { EXPECT_DECLS; -#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[8]; @@ -1238,9 +1264,8 @@ static word16 build_sni_body(byte* buf, const char* host) int test_tls_msgtype_sni_find(void) { EXPECT_DECLS; -#if defined(HAVE_SNI) && defined(WOLFSSL_TLS13) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ - !defined(NO_TLS) +#if defined(HAVE_SNI) && defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; const char* host = "example.com"; @@ -1274,11 +1299,12 @@ int test_tls_msgtype_sni_find(void) int test_tls_msgtype_sni_parse_response_gate(void) { EXPECT_DECLS; -#if defined(HAVE_SNI) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ - !defined(WOLFSSL_NO_TLS12) +#if defined(HAVE_SNI) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && !defined(WOLFSSL_NO_TLS12) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; TLSX* extension = NULL; + void* savedExtData = NULL; const char* host = "example.com"; byte buf[8]; word16 len; @@ -1291,13 +1317,21 @@ int test_tls_msgtype_sni_parse_response_gate(void) ExpectIntEQ(wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, host, (word16)XSTRLEN(host)), WOLFSSL_SUCCESS); ExpectNotNull(extension = TLSX_Find(ssl->extensions, TLSX_SERVER_NAME)); - if (extension != NULL) + if (extension != NULL) { + savedExtData = extension->data; extension->data = NULL; + } len = build_ext(buf, TLSX_SERVER_NAME, 0); ExpectIntEQ(TLSX_Parse(ssl, buf, len, server_hello, NULL), WC_NO_ERR_TRACE(UNSUPPORTED_EXTENSION)); + if (extension != NULL) { + if (extension->data == NULL) + extension->data = savedExtData; + else if (extension->data != savedExtData) + test_tls_msgtype_free_sni(savedExtData, ssl->heap); + } wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); #endif @@ -1315,9 +1349,8 @@ int test_tls_msgtype_sni_parse_size_gates(void) EXPECT_DECLS; /* Drives TLSX_SNI_Parse's isRequest path, which is server-side code and * is compiled out by NO_WOLFSSL_SERVER. */ -#if defined(HAVE_SNI) && defined(WOLFSSL_TLS13) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ - !defined(NO_TLS) +#if defined(HAVE_SNI) && defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; const char* host = "srv.example"; @@ -1364,11 +1397,12 @@ static int sni_recv_cb(WOLFSSL* ssl, int* ret, void* arg) int test_tls_msgtype_sni_parse_cacheonly(void) { EXPECT_DECLS; -#if defined(HAVE_SNI) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ - !defined(WOLFSSL_NO_TLS12) +#if defined(HAVE_SNI) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && !defined(WOLFSSL_NO_TLS12) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; TLSX* extension = NULL; + void* savedExtData = NULL; const char* host = "example.com"; byte sniBody[24]; byte buf[32]; @@ -1385,14 +1419,22 @@ int test_tls_msgtype_sni_parse_cacheonly(void) ExpectIntEQ(wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, host, (word16)XSTRLEN(host)), WOLFSSL_SUCCESS); ExpectNotNull(extension = TLSX_Find(ssl->extensions, TLSX_SERVER_NAME)); - if (extension != NULL) + if (extension != NULL) { + savedExtData = extension->data; extension->data = NULL; + } sniLen = build_sni_body(sniBody, "test.example"); len = build_ext_with_body(buf, TLSX_SERVER_NAME, sniBody, sniLen); XMEMSET(&suites, 0, sizeof(suites)); ExpectIntEQ(TLSX_Parse(ssl, buf, len, client_hello, &suites), 0); + if (extension != NULL) { + if (extension->data == NULL) + extension->data = savedExtData; + else if (extension->data != savedExtData) + test_tls_msgtype_free_sni(savedExtData, ssl->heap); + } wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); #endif @@ -1420,10 +1462,8 @@ int test_tls_msgtype_sni_parse_cacheonly(void) int test_tls_msgtype_sni_parse_match(void) { EXPECT_DECLS; -#if defined(HAVE_SNI) && defined(WOLFSSL_TLS13) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ - !defined(NO_TLS) && \ - !defined(NO_WOLFSSL_SERVER) && defined(WOLFSSL_TLS13) +#if defined(HAVE_SNI) && defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && !defined(NO_TLS) && !defined(NO_WOLFSSL_SERVER) && defined(WOLFSSL_TLS13) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte sniBody[24]; @@ -1526,9 +1566,8 @@ int test_tls_msgtype_sni_parse_match(void) int test_tls_msgtype_sni_parse_ech_public(void) { EXPECT_DECLS; -#if defined(HAVE_SNI) && defined(WOLFSSL_TLS13) && defined(HAVE_ECH) && \ - defined(WOLFSSL_TEST_STATIC_BUILD) && !defined(NO_WOLFSSL_CLIENT) && \ - !defined(NO_WOLFSSL_SERVER) && !defined(NO_TLS) +#if defined(HAVE_SNI) && defined(WOLFSSL_TLS13) && defined(HAVE_ECH) && defined(WOLFSSL_TEST_STATIC_BUILD) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; WOLFSSL_EchConfig echConfig; @@ -1596,8 +1635,8 @@ int test_tls_msgtype_sni_parse_ech_public(void) int test_tls_msgtype_psk_ch_id_gates(void) { EXPECT_DECLS; -#if defined(WOLFSSL_TLS13) && (defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(WOLFSSL_TLS13) && (defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[MAX_PSK_ID_LEN + 64]; @@ -1703,8 +1742,8 @@ static const byte psk_ch_body_two[] = { int test_tls_msgtype_psk_sh_index(void) { EXPECT_DECLS; -#if defined(WOLFSSL_TLS13) && (defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(WOLFSSL_TLS13) && (defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; TLSX* extension = NULL; @@ -1770,8 +1809,8 @@ int test_tls_msgtype_psk_sh_index(void) int test_tls_msgtype_psk_sh_resumption(void) { EXPECT_DECLS; -#if defined(WOLFSSL_TLS13) && (defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(WOLFSSL_TLS13) && (defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte chBuf[64]; @@ -1874,8 +1913,8 @@ int test_tls_msgtype_psk_sh_resumption(void) int test_tls_msgtype_psk_ch_binder_gates(void) { EXPECT_DECLS; -#if defined(WOLFSSL_TLS13) && (defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(WOLFSSL_TLS13) && (defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[128]; @@ -2040,8 +2079,8 @@ int test_tls_msgtype_psk_ch_binder_gates(void) int test_tls_msgtype_cookie_parse_gates(void) { EXPECT_DECLS; -#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[24]; @@ -2129,12 +2168,12 @@ int test_tls_msgtype_cookie_parse_gates(void) int test_tls_msgtype_tca_parse_gates(void) { EXPECT_DECLS; -#if defined(HAVE_TRUSTED_CA) && !defined(NO_WOLFSSL_CLIENT) && \ - !defined(NO_WOLFSSL_SERVER) && !defined(NO_TLS) && \ - !defined(WOLFSSL_NO_TLS12) +#if defined(HAVE_TRUSTED_CA) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && !defined(NO_TLS) && !defined(WOLFSSL_NO_TLS12) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; TLSX* extension = NULL; + void* savedExtData = NULL; const byte id[] = { 1, 2, 3, 4 }; byte buf[16]; word16 len; @@ -2157,11 +2196,15 @@ int test_tls_msgtype_tca_parse_gates(void) id, (word32)sizeof(id)), WOLFSSL_SUCCESS); ExpectNotNull(extension = TLSX_Find(ssl->extensions, TLSX_TRUSTED_CA_KEYS)); - if (extension != NULL) + if (extension != NULL) { + savedExtData = extension->data; extension->data = NULL; + } len = build_ext(buf, TLSX_TRUSTED_CA_KEYS, 0); ExpectIntEQ(TLSX_Parse(ssl, buf, len, server_hello, NULL), WC_NO_ERR_TRACE(UNSUPPORTED_EXTENSION)); + if (extension != NULL) + extension->data = savedExtData; wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); @@ -2184,8 +2227,10 @@ int test_tls_msgtype_tca_parse_gates(void) id, (word32)sizeof(id)), WOLFSSL_SUCCESS); ExpectNotNull(extension = TLSX_Find(ssl->extensions, TLSX_TRUSTED_CA_KEYS)); - if (extension != NULL) + if (extension != NULL) { + savedExtData = extension->data; extension->data = NULL; + } /* A ClientHello TCA extension must be at least WOLFSSL_TCA_MIN_SIZE_CLIENT * bytes to pass TLSX_Parse()'s own minimum-size gate; the body content * is irrelevant here since extension->data == NULL returns before the @@ -2196,6 +2241,8 @@ int test_tls_msgtype_tca_parse_gates(void) XMEMSET(&suites, 0, sizeof(suites)); ExpectIntEQ(TLSX_Parse(ssl, buf, len, client_hello, &suites), 0); } + if (extension != NULL) + extension->data = savedExtData; wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); #endif @@ -2208,8 +2255,9 @@ int test_tls_msgtype_tca_parse_gates(void) int test_tls_msgtype_tca_find(void) { EXPECT_DECLS; -#if defined(HAVE_TRUSTED_CA) && !defined(NO_WOLFSSL_CLIENT) && \ - !defined(NO_WOLFSSL_SERVER) && !defined(NO_TLS) && !defined(NO_SHA) +#if defined(HAVE_TRUSTED_CA) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && !defined(NO_TLS) && !defined(NO_SHA) && \ + defined(HAVE_TLS_EXTENSIONS) && \ + !defined(WOLFSSL_NO_TLS12) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[24]; @@ -2340,8 +2388,9 @@ static void* tca_fail_realloc(void* ptr, size_t size) int test_tls_msgtype_tca_new_alloc(void) { EXPECT_DECLS; -#if defined(HAVE_TRUSTED_CA) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ - !defined(NO_SHA) +#if defined(HAVE_TRUSTED_CA) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && !defined(NO_SHA) && \ + defined(HAVE_TLS_EXTENSIONS) && \ + !defined(WOLFSSL_NO_TLS12) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; wolfSSL_Malloc_cb prevM = NULL; @@ -2393,9 +2442,8 @@ int test_tls_msgtype_tca_new_alloc(void) int test_tls_msgtype_psk_write_chosen(void) { EXPECT_DECLS; -#if defined(WOLFSSL_TLS13) && (defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ - defined(WOLFSSL_TEST_STATIC_BUILD) +#if defined(WOLFSSL_TLS13) && (defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && defined(WOLFSSL_TEST_STATIC_BUILD) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; TLSX* extension = NULL; diff --git a/tests/api/test_tls_parse.c b/tests/api/test_tls_parse.c index a82de76580..2389310af8 100644 --- a/tests/api/test_tls_parse.c +++ b/tests/api/test_tls_parse.c @@ -198,9 +198,8 @@ int test_TLSX_ALPN_parse(void) { EXPECT_DECLS; /* TLSX_ALPN_GetRequest() below is WOLFSSL_LOCAL: needs the static library. */ -#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(HAVE_ALPN) && \ - defined(HAVE_TLS_EXTENSIONS) && !defined(NO_TLS) && \ - !defined(NO_WOLFSSL_CLIENT) +#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(HAVE_ALPN) && defined(HAVE_TLS_EXTENSIONS) && !defined(NO_TLS) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(WOLFSSL_NO_TLS12) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte ext[300]; @@ -389,8 +388,8 @@ int test_TLSX_ALPN_parse(void) int test_TLSX_TCA_parse(void) { EXPECT_DECLS; -#if defined(HAVE_TRUSTED_CA) && defined(HAVE_TLS_EXTENSIONS) && \ - !defined(NO_TLS) && !defined(NO_WOLFSSL_SERVER) && !defined(NO_SHA) +#if defined(HAVE_TRUSTED_CA) && defined(HAVE_TLS_EXTENSIONS) && !defined(NO_TLS) && !defined(NO_WOLFSSL_SERVER) && !defined(NO_SHA) && \ + !defined(WOLFSSL_NO_TLS12) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte ext[64]; @@ -581,8 +580,8 @@ int test_TLSX_certtype_parse(void) int test_TLSX_Cookie_parse(void) { EXPECT_DECLS; -#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ - !defined(NO_TLS) && !defined(NO_WOLFSSL_CLIENT) +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) && !defined(NO_TLS) && !defined(NO_WOLFSSL_CLIENT) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte ext[16]; @@ -640,8 +639,9 @@ int test_TLSX_Cookie_parse(void) int test_TLSX_EncryptThenMac_parse(void) { EXPECT_DECLS; -#if defined(HAVE_ENCRYPT_THEN_MAC) && !defined(WOLFSSL_AEAD_ONLY) && \ - !defined(NO_TLS) && !defined(NO_WOLFSSL_CLIENT) +#if defined(HAVE_ENCRYPT_THEN_MAC) && !defined(WOLFSSL_AEAD_ONLY) && !defined(NO_TLS) && !defined(NO_WOLFSSL_CLIENT) && \ + defined(HAVE_TLS_EXTENSIONS) && \ + !defined(WOLFSSL_NO_TLS12) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte ext[8]; @@ -665,8 +665,9 @@ int test_TLSX_EncryptThenMac_parse(void) int test_TLSX_MFL_parse(void) { EXPECT_DECLS; -#if defined(HAVE_MAX_FRAGMENT) && !defined(WOLFSSL_OLD_UNSUPPORTED_EXTENSION) \ - && !defined(NO_TLS) && !defined(NO_WOLFSSL_CLIENT) +#if defined(HAVE_MAX_FRAGMENT) && !defined(WOLFSSL_OLD_UNSUPPORTED_EXTENSION) && !defined(NO_TLS) && !defined(NO_WOLFSSL_CLIENT) && \ + defined(HAVE_TLS_EXTENSIONS) && \ + !defined(WOLFSSL_NO_TLS12) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte ext[8]; @@ -716,8 +717,9 @@ int test_TLSX_MFL_parse(void) int test_TLSX_THM_parse(void) { EXPECT_DECLS; -#if defined(HAVE_TRUNCATED_HMAC) && !defined(NO_TLS) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) +#if defined(HAVE_TRUNCATED_HMAC) && !defined(NO_TLS) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + defined(HAVE_TLS_EXTENSIONS) && \ + !defined(WOLFSSL_NO_TLS12) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte ext[8]; @@ -753,9 +755,8 @@ int test_TLSX_THM_parse(void) int test_TLSX_SessionTicket_parse(void) { EXPECT_DECLS; -#if defined(HAVE_SESSION_TICKET) && !defined(NO_TLS) && \ - !defined(NO_WOLFSSL_SERVER) && !defined(WOLFSSL_NO_TLS12) && \ - !defined(NO_WOLFSSL_CLIENT) && defined(HAVE_SSL_MEMIO_TESTS_DEPENDENCIES) +#if defined(HAVE_SESSION_TICKET) && !defined(NO_TLS) && !defined(NO_WOLFSSL_SERVER) && !defined(WOLFSSL_NO_TLS12) && !defined(NO_WOLFSSL_CLIENT) && defined(HAVE_SSL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; WOLFSSL* ssl2 = NULL; @@ -840,8 +841,9 @@ int test_TLSX_SessionTicket_parse(void) int test_TLSX_SecureRenegotiation_parse(void) { EXPECT_DECLS; -#if defined(HAVE_SECURE_RENEGOTIATION) && !defined(NO_TLS) && \ - !defined(NO_WOLFSSL_SERVER) && defined(WOLFSSL_TEST_STATIC_BUILD) +#if defined(HAVE_SECURE_RENEGOTIATION) && !defined(NO_TLS) && !defined(NO_WOLFSSL_SERVER) && defined(WOLFSSL_TEST_STATIC_BUILD) && \ + defined(HAVE_TLS_EXTENSIONS) && \ + !defined(WOLFSSL_NO_TLS12) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte ext[8 + 2 * TLS_FINISHED_SZ]; @@ -966,8 +968,9 @@ int test_TLSX_SupportedVersions_parse(void) * before any other extension can be interpreted), and the main loop's * TLSX_SUPPORTED_VERSIONS case only re-validates the message type. * WOLFSSL_LOCAL: called directly (guarded). */ -#if defined(WOLFSSL_TLS13) && !defined(NO_TLS) && !defined(NO_WOLFSSL_SERVER) \ - && !defined(NO_WOLFSSL_CLIENT) && defined(WOLFSSL_TEST_STATIC_BUILD) +#if defined(WOLFSSL_TLS13) && !defined(NO_TLS) && !defined(NO_WOLFSSL_SERVER) && !defined(NO_WOLFSSL_CLIENT) && defined(WOLFSSL_TEST_STATIC_BUILD) && \ + defined(HAVE_TLS_EXTENSIONS) && \ + !defined(WOLFSSL_NO_TLS12) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte ext[8 + MAX_SV_EXT_LEN]; @@ -1112,9 +1115,9 @@ int test_TLSX_SupportedVersions_parse(void) int test_TLSX_SignatureAlgorithms_parse(void) { EXPECT_DECLS; -#if !defined(NO_CERTS) && !defined(WOLFSSL_NO_SIGALG) && !defined(NO_TLS) && \ - !defined(NO_WOLFSSL_SERVER) && \ - (!defined(NO_RSA) || defined(HAVE_ECC)) +#if !defined(NO_CERTS) && !defined(WOLFSSL_NO_SIGALG) && !defined(NO_TLS) && !defined(NO_WOLFSSL_SERVER) && (!defined(NO_RSA) || defined(HAVE_ECC)) && \ + defined(HAVE_TLS_EXTENSIONS) && \ + !defined(WOLFSSL_NO_TLS12) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte ext[16]; @@ -1222,8 +1225,9 @@ int test_TLSX_SignatureAlgorithms_parse(void) int test_TLSX_CSR_parse(void) { EXPECT_DECLS; -#if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(NO_TLS) && \ - !defined(NO_WOLFSSL_SERVER) && !defined(NO_WOLFSSL_CLIENT) +#if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(NO_TLS) && !defined(NO_WOLFSSL_SERVER) && !defined(NO_WOLFSSL_CLIENT) && \ + defined(HAVE_TLS_EXTENSIONS) && \ + !defined(WOLFSSL_NO_TLS12) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte ext[32]; @@ -1426,9 +1430,9 @@ int test_TLSX_CSR_parse(void) int test_TLSX_PointFormat_parse(void) { EXPECT_DECLS; -#if defined(HAVE_SUPPORTED_CURVES) && !defined(NO_TLS) && \ - !defined(NO_WOLFSSL_SERVER) && \ - (!defined(NO_RSA) || defined(HAVE_ECC)) +#if defined(HAVE_SUPPORTED_CURVES) && !defined(NO_TLS) && !defined(NO_WOLFSSL_SERVER) && (!defined(NO_RSA) || defined(HAVE_ECC)) && \ + defined(HAVE_TLS_EXTENSIONS) && \ + !defined(WOLFSSL_NO_TLS12) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte ext[16]; @@ -1580,8 +1584,9 @@ int test_TLSX_PointFormat_parse(void) int test_TLSX_SNI_parse(void) { EXPECT_DECLS; -#if defined(HAVE_SNI) && !defined(NO_TLS) && !defined(NO_WOLFSSL_SERVER) && \ - (!defined(NO_RSA) || defined(HAVE_ECC)) +#if defined(HAVE_SNI) && !defined(NO_TLS) && !defined(NO_WOLFSSL_SERVER) && (!defined(NO_RSA) || defined(HAVE_ECC)) && \ + defined(HAVE_TLS_EXTENSIONS) && \ + !defined(WOLFSSL_NO_TLS12) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte ext[64]; @@ -1718,11 +1723,9 @@ int test_TLSX_SNI_parse(void) int test_TLSX_ValidateSupportedCurves(void) { EXPECT_DECLS; -#if (defined(HAVE_ECC) || defined(HAVE_CURVE25519) || \ - defined(HAVE_CURVE448)) && defined(HAVE_SUPPORTED_CURVES) && \ - !defined(NO_TLS) && !defined(NO_WOLFSSL_SERVER) && \ - defined(WOLFSSL_TEST_STATIC_BUILD) && defined(HAVE_CURVE25519) && \ - defined(HAVE_CURVE448) +#if (defined(HAVE_ECC) || defined(HAVE_CURVE25519) || defined(HAVE_CURVE448)) && defined(HAVE_SUPPORTED_CURVES) && !defined(NO_TLS) && !defined(NO_WOLFSSL_SERVER) && defined(WOLFSSL_TEST_STATIC_BUILD) && defined(HAVE_CURVE25519) && defined(HAVE_CURVE448) && \ + defined(HAVE_TLS_EXTENSIONS) && \ + !defined(WOLFSSL_NO_TLS12) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; word32 oid; @@ -1923,9 +1926,9 @@ int test_TLSX_ValidateSupportedCurves(void) int test_TLSX_SupportedGroups_parse(void) { EXPECT_DECLS; -#if defined(HAVE_SUPPORTED_CURVES) && defined(WOLFSSL_TLS13) && \ - !defined(NO_TLS) && !defined(NO_WOLFSSL_CLIENT) && \ - defined(WOLFSSL_TEST_STATIC_BUILD) +#if defined(HAVE_SUPPORTED_CURVES) && defined(WOLFSSL_TLS13) && !defined(NO_TLS) && !defined(NO_WOLFSSL_CLIENT) && defined(WOLFSSL_TEST_STATIC_BUILD) && \ + defined(HAVE_TLS_EXTENSIONS) && \ + !defined(WOLFSSL_NO_TLS12) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; /* secp256r1: a real, locally supported curve. */ @@ -2176,9 +2179,8 @@ int test_TLSX_SupportedGroups_parse(void) int test_TLSX_KeyShare_negotiate(void) { EXPECT_DECLS; -#if defined(WOLFSSL_TLS13) && defined(HAVE_SUPPORTED_CURVES) && \ - !defined(NO_WOLFSSL_SERVER) && !defined(NO_WOLFSSL_CLIENT) && \ - defined(WOLFSSL_TEST_STATIC_BUILD) +#if defined(WOLFSSL_TLS13) && defined(HAVE_SUPPORTED_CURVES) && !defined(NO_WOLFSSL_SERVER) && !defined(NO_WOLFSSL_CLIENT) && defined(WOLFSSL_TEST_STATIC_BUILD) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -2472,7 +2474,8 @@ int test_TLSX_KeyShare_negotiate(void) int test_TLSX_KeyShare_gen(void) { EXPECT_DECLS; -#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_TEST_STATIC_BUILD) +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_TEST_STATIC_BUILD) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -2692,9 +2695,8 @@ int test_TLSX_KeyShare_gen(void) int test_TLSX_KeyShare_freesizewrite(void) { EXPECT_DECLS; -#if defined(WOLFSSL_TLS13) && defined(HAVE_SUPPORTED_CURVES) && \ - !defined(NO_DH) && defined(HAVE_FFDHE_2048) && \ - defined(WOLFSSL_TEST_STATIC_BUILD) +#if defined(WOLFSSL_TLS13) && defined(HAVE_SUPPORTED_CURVES) && !defined(NO_DH) && defined(HAVE_FFDHE_2048) && defined(WOLFSSL_TEST_STATIC_BUILD) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -2834,8 +2836,8 @@ int test_TLSX_KeyShare_freesizewrite(void) int test_TLSX_KeyShare_process(void) { EXPECT_DECLS; -#if defined(WOLFSSL_TLS13) && defined(HAVE_SUPPORTED_CURVES) && \ - defined(WOLFSSL_TEST_STATIC_BUILD) +#if defined(WOLFSSL_TLS13) && defined(HAVE_SUPPORTED_CURVES) && defined(WOLFSSL_TEST_STATIC_BUILD) && \ + defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; From e29880378afff333d87f1e0d8d0ffa004dc57bb9 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 21 Aug 2026 09:07:12 +0200 Subject: [PATCH 55/57] tests: mark every static helper in the tls extension tests as possibly unused Their callers sit behind differing feature guards, so any configuration can compile in none of them. --- tests/api/test_tls_bounds.c | 10 ++++++++++ tests/api/test_tls_msgtype.c | 3 +++ tests/api/test_tls_parse.c | 4 ++++ 3 files changed, 17 insertions(+) diff --git a/tests/api/test_tls_bounds.c b/tests/api/test_tls_bounds.c index 09ac5e72c7..9c6fee4c9a 100644 --- a/tests/api/test_tls_bounds.c +++ b/tests/api/test_tls_bounds.c @@ -1000,6 +1000,7 @@ int test_TLSX_Cookie_bounds(void) * to csr->ssl in between. Excluded (family 3) in both functions. */ #ifdef TEST_TLS_BOUNDS_CSR_STATUS_CB +TEST_TLS_BOUNDS_UNUSED static int test_TLSX_CSR_write_getsize_status_cb(WOLFSSL* ssl, void* arg) { (void)ssl; (void)arg; @@ -1361,6 +1362,7 @@ enum { CSROCSP_MODE_WANT_READ }; +TEST_TLS_BOUNDS_UNUSED static int test_ProcessChainOCSPRequest_io_cb(void* ctx, const char* url, int urlSz, unsigned char* req, int reqSz, unsigned char** respBuf) { @@ -1385,6 +1387,7 @@ static int test_ProcessChainOCSPRequest_io_cb(void* ctx, const char* url, * (leaf) + intermediate1 (the chain entry ProcessChainOCSPRequest() will * process) issued off root-ca, matching the identity the csrocsp_resp_* * fixtures above were generated against. */ +TEST_TLS_BOUNDS_UNUSED static int test_ProcessChainOCSPRequest_setup(WOLFSSL_CTX** pctx, WOLFSSL** pssl, CertificateStatusRequest** pcsr, int* mode) { @@ -1661,6 +1664,7 @@ int test_ProcessChainOCSPRequest_bounds(void) * is therefore fixed true on every execution that reaches it - excluded * (family 3: fixed by the branch that reaches it). */ #ifdef TEST_TLS_BOUNDS_POPULATE_EXT +TEST_TLS_BOUNDS_UNUSED static unsigned int test_TLSX_PopulateExtensions_psk_cb(WOLFSSL* ssl, const char* hint, char* identity, unsigned int id_max_len, unsigned char* key, unsigned int key_max_len) @@ -1685,6 +1689,7 @@ static unsigned int test_TLSX_PopulateExtensions_psk_cb(WOLFSSL* ssl, } } +TEST_TLS_BOUNDS_UNUSED static unsigned int test_TLSX_PopulateExtensions_psk_tls13_cb(WOLFSSL* ssl, const char* hint, char* identity, unsigned int id_max_len, unsigned char* key, unsigned int key_max_len, const char** ciphersuite) @@ -1946,6 +1951,7 @@ int test_TLSX_PopulateSupportedGroups_bounds(void) static int test_TLSX_CSR_Parse_fail_after = -1; static int test_TLSX_CSR_Parse_alloc_seen = 0; +TEST_TLS_BOUNDS_UNUSED static void* test_TLSX_CSR_Parse_fail_malloc(size_t size) { if (test_TLSX_CSR_Parse_fail_after >= 0) { @@ -1958,11 +1964,13 @@ static void* test_TLSX_CSR_Parse_fail_malloc(size_t size) return malloc(size); } +TEST_TLS_BOUNDS_UNUSED static void test_TLSX_CSR_Parse_fail_free(void* ptr) { free(ptr); } +TEST_TLS_BOUNDS_UNUSED static void* test_TLSX_CSR_Parse_fail_realloc(void* ptr, size_t size) { return realloc(ptr, size); @@ -2694,6 +2702,7 @@ int test_TLSX_SecureRenegotiation_Write_bounds(void) * protocol-level row, not a callee postcondition that collapses it into * REJECT. So: covered, not excluded. */ #ifdef TEST_TLS_BOUNDS_SESSION_TICKET_FF +TEST_TLS_BOUNDS_UNUSED static int test_TLSX_SessionTicket_ff_enc_cb(WOLFSSL* ssl, byte key_name[WOLFSSL_TICKET_NAME_SZ], byte iv[WOLFSSL_TICKET_IV_SZ], byte mac[WOLFSSL_TICKET_MAC_SZ], int enc, byte* ticket, int inLen, @@ -2722,6 +2731,7 @@ static int test_TLSX_SessionTicket_ff_enc_cb(WOLFSSL* ssl, return WOLFSSL_TICKET_RET_OK; } +TEST_TLS_BOUNDS_UNUSED static int test_TLSX_SessionTicket_ff_ctx_ready(WOLFSSL_CTX* ctx) { EXPECT_DECLS; diff --git a/tests/api/test_tls_msgtype.c b/tests/api/test_tls_msgtype.c index 64fa7bffa3..0e883a0039 100644 --- a/tests/api/test_tls_msgtype.c +++ b/tests/api/test_tls_msgtype.c @@ -2355,6 +2355,7 @@ int test_tls_msgtype_tca_find(void) static int tca_fail_after = -1; static int tca_alloc_seen = 0; +TEST_TLS_MSGTYPE_UNUSED static void* tca_fail_malloc(size_t size) { if (tca_fail_after >= 0) { @@ -2367,11 +2368,13 @@ static void* tca_fail_malloc(size_t size) return malloc(size); } +TEST_TLS_MSGTYPE_UNUSED static void tca_fail_free(void* ptr) { free(ptr); } +TEST_TLS_MSGTYPE_UNUSED static void* tca_fail_realloc(void* ptr, size_t size) { return realloc(ptr, size); diff --git a/tests/api/test_tls_parse.c b/tests/api/test_tls_parse.c index 2389310af8..6b0c9e637c 100644 --- a/tests/api/test_tls_parse.c +++ b/tests/api/test_tls_parse.c @@ -75,6 +75,7 @@ static WOLFSSL_CTX* test_tls_parse_server_ctx(WOLFSSL_METHOD* method) * header into 'out', which must be at least bodyLen + 4 bytes, and returns * the total length. */ +TEST_TLS_PARSE_UNUSED static word16 test_tls_parse_build_ext(byte* out, word16 outCap, word16 type, const byte* body, word16 bodyLen) { @@ -135,6 +136,7 @@ static void* tls_parse_fail_realloc(void* ptr, size_t size) * TLSX_UseSupportedCurve()'s TLSX_IsGroupSupported() gate -- needed to name * a group id this build does not itself recognise, the same as a peer's * raw wire value would. */ +TEST_TLS_PARSE_UNUSED static int test_tls_parse_push_curve(TLSX** extensions, WOLFSSL* ssl, word16 name) { @@ -150,6 +152,7 @@ static int test_tls_parse_push_curve(TLSX** extensions, WOLFSSL* ssl, /* Builds and pushes a minimal key share entry -- a peer offer that was * never processed into a real key -- for tests that only need the * bookkeeping fields (group, ke) a negotiation helper looks at. */ +TEST_TLS_PARSE_UNUSED static KeyShareEntry* test_tls_parse_push_kse(TLSX** extensions, WOLFSSL* ssl, word16 group) { @@ -178,6 +181,7 @@ static KeyShareEntry* test_tls_parse_push_kse(TLSX** extensions, WOLFSSL* ssl, * WOLFSSL_LOCAL; releasing a standalone (not already list-linked) entry * built directly for a test goes through the generic TLSX_FreeAll() * instead, via a throwaway one-node extension list. */ +TEST_TLS_PARSE_UNUSED static void test_tls_parse_free_kse(WOLFSSL* ssl, KeyShareEntry* kse) { TLSX* extensions = NULL; From 67c3983689890d96059d41a34cc16c76fd573263 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 21 Aug 2026 12:43:48 +0200 Subject: [PATCH 56/57] tests: load an ecc server certificate when rsa is unavailable Also guards the sni free helper on HAVE_SNI and gives the request-direction key share entry a public key before it is written. --- tests/api/test_tls_msgtype.c | 2 ++ tests/api/test_tls_parse.c | 29 +++++++++++++++++++++++++---- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/tests/api/test_tls_msgtype.c b/tests/api/test_tls_msgtype.c index 0e883a0039..9c3529e045 100644 --- a/tests/api/test_tls_msgtype.c +++ b/tests/api/test_tls_msgtype.c @@ -47,6 +47,7 @@ /* TLSX_SNI_Free() is file-static in src/tls.c. A test that detaches an SNI * list from its extension has to release it the same way: the host name, then * the node. */ +#ifdef HAVE_SNI TEST_TLS_MSGTYPE_UNUSED static void test_tls_msgtype_free_sni(void* p, void* heap) { @@ -61,6 +62,7 @@ static void test_tls_msgtype_free_sni(void* p, void* heap) sni = next; } } +#endif /* HAVE_SNI */ /* Build one extension record (2-byte type, 2-byte length, N zero data diff --git a/tests/api/test_tls_parse.c b/tests/api/test_tls_parse.c index 6b0c9e637c..0f2f17d005 100644 --- a/tests/api/test_tls_parse.c +++ b/tests/api/test_tls_parse.c @@ -53,10 +53,20 @@ static WOLFSSL_CTX* test_tls_parse_server_ctx(WOLFSSL_METHOD* method) if (ctx == NULL) return NULL; -#if !defined(NO_CERTS) && !defined(NO_FILESYSTEM) && !defined(NO_RSA) - if (wolfSSL_CTX_use_certificate_file(ctx, svrCertFile, CERT_FILETYPE) +#if !defined(NO_CERTS) && !defined(NO_FILESYSTEM) && \ + (!defined(NO_RSA) || defined(HAVE_ECC)) + #ifndef NO_RSA + const char* certFile = svrCertFile; + const char* keyFile = svrKeyFile; + #else + /* An ECC-only build has no RSA server certificate to load. */ + const char* certFile = eccCertFile; + const char* keyFile = eccKeyFile; + #endif + + if (wolfSSL_CTX_use_certificate_file(ctx, certFile, CERT_FILETYPE) != WOLFSSL_SUCCESS || - wolfSSL_CTX_use_PrivateKey_file(ctx, svrKeyFile, CERT_FILETYPE) + wolfSSL_CTX_use_PrivateKey_file(ctx, keyFile, CERT_FILETYPE) != WOLFSSL_SUCCESS) { wolfSSL_CTX_free(ctx); return NULL; @@ -2768,7 +2778,6 @@ int test_TLSX_KeyShare_freesizewrite(void) * under test. */ ExpectNotNull(kse = test_tls_parse_push_kse(&ssl->extensions, ssl, WOLFSSL_FFDHE_2048)); - (void)kse; { TLSX* ext = TLSX_Find(ssl->extensions, TLSX_KEY_SHARE); ExpectNotNull(ext); @@ -2782,6 +2791,18 @@ int test_TLSX_KeyShare_freesizewrite(void) XMEMSET(out, 0, sizeof(out)); ExpectIntEQ(TLSX_WriteResponse(ssl, out, server_hello, &respOff), 0); + /* The request direction never reaches the pubKey test -- isRequest + * short-circuits it -- but it does copy pubKey unconditionally, so + * give the entry one before writing a ClientHello. */ + if (kse != NULL) { + kse->pubKey = (byte*)XMALLOC(2, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY); + ExpectNotNull(kse->pubKey); + if (kse->pubKey != NULL) { + XMEMSET(kse->pubKey, 0, 2); + kse->pubKeyLen = 2; + } + } + reqLen = 0; ExpectIntEQ(TLSX_GetRequestSize(ssl, client_hello, &reqLen), 0); ExpectIntGT(reqLen, 0); From fbfbdc6b9009ef8e750bb6741411dafc1cd6bcbf Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 21 Aug 2026 13:08:31 +0200 Subject: [PATCH 57/57] tests: guard two ssl dereferences that run after a failed allocation EXPECT_DECLS records the failure and carries on, so the field access ran on a null pointer. --- tests/api/test_tls_bounds.c | 12 +++++++----- tests/api/test_tls_msgtype.c | 12 +++++++----- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/tests/api/test_tls_bounds.c b/tests/api/test_tls_bounds.c index 9c6fee4c9a..d1cb4ef0d4 100644 --- a/tests/api/test_tls_bounds.c +++ b/tests/api/test_tls_bounds.c @@ -368,11 +368,13 @@ int test_TLSX_SupportExtensions_bounds(void) * third operand - checking that very same field for DTLS_MAJOR - is * false too. This is the only way to make IsTLS(ssl) false at all: every * ssl created through a real method sets a recognized major/minor. */ - ssl->version.major = 0; - offset = 0; - ExpectIntEQ(TLSX_WriteRequest(ssl, out, client_hello, &offset), 0); - ssl->version.major = SSLv3_MAJOR; - ssl->version.minor = TLSv1_2_MINOR; + if (ssl != NULL) { + ssl->version.major = 0; + offset = 0; + ExpectIntEQ(TLSX_WriteRequest(ssl, out, client_hello, &offset), 0); + ssl->version.major = SSLv3_MAJOR; + ssl->version.minor = TLSv1_2_MINOR; + } /* TLSX_WriteRequest()'s own leading guard is * "!TLSX_SupportExtensions(ssl) || output == NULL": a supported ssl with diff --git a/tests/api/test_tls_msgtype.c b/tests/api/test_tls_msgtype.c index 9c3529e045..fcdb64dafd 100644 --- a/tests/api/test_tls_msgtype.c +++ b/tests/api/test_tls_msgtype.c @@ -268,11 +268,13 @@ int test_tls_msgtype_certificate_ext_offered(void) ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); ExpectNotNull(ssl = wolfSSL_new(ctx)); len = build_ext(buf, TLSX_MAX_FRAGMENT_LENGTH, 0); - savedCtx = ssl->ctx; - ssl->ctx = NULL; - ExpectIntEQ(TLSX_Parse(ssl, buf, len, certificate, NULL), - WC_NO_ERR_TRACE(UNSUPPORTED_EXTENSION)); - ssl->ctx = savedCtx; + if (ssl != NULL) { + savedCtx = ssl->ctx; + ssl->ctx = NULL; + ExpectIntEQ(TLSX_Parse(ssl, buf, len, certificate, NULL), + WC_NO_ERR_TRACE(UNSUPPORTED_EXTENSION)); + ssl->ctx = savedCtx; + } wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); #endif