From 125274935ef280100f1c685dfe74fd909d3a9cdb Mon Sep 17 00:00:00 2001 From: Kareem Date: Thu, 30 Apr 2026 16:09:39 -0700 Subject: [PATCH 1/5] Ensure post_handshake_auth extension was sent before accepting post-handshake CertificateRequest message as per RFC 8446 4.6.2. Thanks to Xiangdong Li for the report. --- src/tls13.c | 15 ++++++++++ tests/api/test_tls13.c | 65 ++++++++++++++++++++++++++++++++++++++++++ tests/api/test_tls13.h | 2 ++ 3 files changed, 82 insertions(+) diff --git a/src/tls13.c b/src/tls13.c index 794e3e068d..41d80e94bb 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -13163,6 +13163,21 @@ static int SanityCheckTls13MsgReceived(WOLFSSL* ssl, byte type) WOLFSSL_ERROR_VERBOSE(OUT_OF_ORDER_E); return OUT_OF_ORDER_E; } + /* RFC 8446 4.6.2: A client that receives a post-handshake + * CertificateRequest message without having sent the + * "post_handshake_auth" extension MUST send an + * "unexpected_message" fatal alert. The caller of + * SanityCheckTls13MsgReceived() converts OUT_OF_ORDER_E into an + * unexpected_message alert. */ + if (ssl->options.serverState >= SERVER_FINISHED_COMPLETE && + ssl->options.clientState == CLIENT_FINISHED_COMPLETE && + !ssl->options.postHandshakeAuth) { + WOLFSSL_MSG("Post-handshake CertificateRequest received " + "without having sent post_handshake_auth " + "extension"); + WOLFSSL_ERROR_VERBOSE(OUT_OF_ORDER_E); + return OUT_OF_ORDER_E; + } #endif #if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK) /* Server's authenticating with PSK must not send this. */ diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index 4d9abc71c0..de924e3052 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -5531,6 +5531,71 @@ int test_tls13_zero_inner_content_type(void) return EXPECT_RESULT(); } +/* RFC 8446 Section 4.6.2: A client that receives a post-handshake + * CertificateRequest message without having sent the "post_handshake_auth" + * extension MUST send an "unexpected_message" fatal alert. + * + * This test completes a TLS 1.3 handshake in which the client never enabled + * post-handshake auth (so no extension was sent in the ClientHello), then + * forces the server to transmit a post-handshake CertificateRequest anyway. + * The client must reject the message with an unexpected_message fatal + * alert. */ +int test_tls13_post_handshake_auth_no_ext(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_POST_HANDSHAKE_AUTH) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + 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; + char readBuf[8]; + + 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); + /* Keep the post-handshake byte stream simple by suppressing the server's + * NewSessionTicket so the only post-handshake record from the server is + * the CertificateRequest under test. */ + ExpectIntEQ(wolfSSL_no_ticket_TLSv13(ssl_s), 0); + + /* Intentionally do NOT call wolfSSL_allow_post_handshake_auth() on the + * client so the post_handshake_auth extension is omitted from the + * ClientHello. */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* The server's wolfSSL_request_certificate() refuses to send a + * post-handshake CertificateRequest unless its postHandshakeAuth flag is + * set (normally set when parsing the client's extension). To exercise + * the receive-side check we are testing, simulate a server that sends + * one anyway by toggling the flag directly. */ + if (ssl_s != NULL) + ssl_s->options.postHandshakeAuth = 1; + ExpectIntEQ(wolfSSL_request_certificate(ssl_s), WOLFSSL_SUCCESS); + + /* The client must reject the unsolicited CertificateRequest. */ + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, (int)sizeof(readBuf)), + WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WC_NO_ERR_TRACE(OUT_OF_ORDER_E)); + + /* And the client must transmit a fatal unexpected_message alert. */ + ExpectIntEQ(wolfSSL_get_alert_history(ssl_c, &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(); +} + /* Test that a TLS 1.3-capable client rejects downgrade sentinels in a * downgraded ServerHello random for both TLS 1.2 and TLS 1.1-or-lower. */ int test_tls13_downgrade_sentinel(void) diff --git a/tests/api/test_tls13.h b/tests/api/test_tls13.h index ae48d6ebbf..01b20029f9 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -58,6 +58,7 @@ int test_tls13_corrupted_finished(void); int test_tls13_peerauth_failsafe(void); int test_tls13_hrr_bad_cookie(void); int test_tls13_zero_inner_content_type(void); +int test_tls13_post_handshake_auth_no_ext(void); int test_tls13_downgrade_sentinel(void); int test_tls13_serverhello_bad_cipher_suites(void); int test_tls13_cert_with_extern_psk_apis(void); @@ -109,6 +110,7 @@ int test_tls13_cipher_fuzz_aes128_ccm_8_sha256(void); TEST_DECL_GROUP("tls13", test_tls13_peerauth_failsafe), \ TEST_DECL_GROUP("tls13", test_tls13_hrr_bad_cookie), \ TEST_DECL_GROUP("tls13", test_tls13_zero_inner_content_type), \ + TEST_DECL_GROUP("tls13", test_tls13_post_handshake_auth_no_ext), \ TEST_DECL_GROUP("tls13", test_tls13_downgrade_sentinel), \ TEST_DECL_GROUP("tls13", test_tls13_serverhello_bad_cipher_suites), \ TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_apis), \ From 2aacc18788f8633ea937efca257ea676128e4e0f Mon Sep 17 00:00:00 2001 From: Kareem Date: Thu, 30 Apr 2026 16:23:56 -0700 Subject: [PATCH 2/5] Enable all-zero shared secret check for curve448/25519 by default. Change macro to opt-out macro WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK. Add unit test. Thanks to Xiangdong Li for the report. --- .wolfssl_known_macro_extras | 2 +- tests/api/test_curve25519.c | 45 +++++++++++++++++++++++++++++++++++++ tests/api/test_curve25519.h | 2 ++ tests/api/test_curve448.c | 42 ++++++++++++++++++++++++++++++++++ tests/api/test_curve448.h | 2 ++ wolfcrypt/src/curve25519.c | 10 ++++----- wolfcrypt/src/curve448.c | 5 +++-- 7 files changed, 100 insertions(+), 8 deletions(-) diff --git a/.wolfssl_known_macro_extras b/.wolfssl_known_macro_extras index 583422bea4..67c0fa3983 100644 --- a/.wolfssl_known_macro_extras +++ b/.wolfssl_known_macro_extras @@ -755,7 +755,6 @@ WOLFSSL_ECC_BLIND_K WOLFSSL_ECC_GEN_REJECT_SAMPLING WOLFSSL_ECC_NO_SMALL_STACK WOLFSSL_ECC_SIGALG_PARAMS_NULL_ALLOWED -WOLFSSL_ECDHX_SHARED_NOT_ZERO WOLFSSL_ECDSA_MATCH_HASH WOLFSSL_ECDSA_SET_K_ONE_LOOP WOLFSSL_EC_POINT_CMP_JACOBIAN @@ -832,6 +831,7 @@ WOLFSSL_NO_DEL_HANDLE WOLFSSL_NO_DER_TO_PEM WOLFSSL_NO_DH186 WOLFSSL_NO_DTLS_SIZE_CHECK +WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK WOLFSSL_NO_ETM_ALERT WOLFSSL_NO_FENCE WOLFSSL_NO_ISSUERHASH_TDPEER diff --git a/tests/api/test_curve25519.c b/tests/api/test_curve25519.c index e6521c89db..4108ad0d34 100644 --- a/tests/api/test_curve25519.c +++ b/tests/api/test_curve25519.c @@ -353,6 +353,51 @@ int test_wc_curve25519_shared_secret_ex(void) return EXPECT_RESULT(); } /* END test_wc_curve25519_shared_secret_ex */ +/* + * Testing that wc_curve25519_shared_secret_ex rejects an all-zero shared + * secret (RFC 7748 section 6.1). This is the default behavior; users that + * need the legacy behavior can opt out with WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK. + */ +int test_wc_curve25519_shared_secret_zero_check(void) +{ + EXPECT_DECLS; +#if defined(HAVE_CURVE25519) && defined(HAVE_CURVE25519_KEY_IMPORT) && \ + !defined(WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK) + curve25519_key private_key; + curve25519_key public_key; + WC_RNG rng; + byte out[CURVE25519_KEYSIZE]; + word32 outLen = sizeof(out); + /* All-zero public key is a low-order point that yields an all-zero + * shared secret for any private key. */ + byte zero_pub[CURVE25519_KEYSIZE]; + + XMEMSET(&rng, 0, sizeof(WC_RNG)); + XMEMSET(zero_pub, 0, sizeof(zero_pub)); + + ExpectIntEQ(wc_curve25519_init(&private_key), 0); + ExpectIntEQ(wc_curve25519_init(&public_key), 0); + ExpectIntEQ(wc_InitRng(&rng), 0); +#ifdef WOLFSSL_CURVE25519_BLINDING + ExpectIntEQ(wc_curve25519_set_rng(&private_key, &rng), 0); +#endif + + ExpectIntEQ(wc_curve25519_make_key(&rng, CURVE25519_KEYSIZE, &private_key), + 0); + ExpectIntEQ(wc_curve25519_import_public_ex(zero_pub, sizeof(zero_pub), + &public_key, EC25519_LITTLE_ENDIAN), 0); + + ExpectIntEQ(wc_curve25519_shared_secret_ex(&private_key, &public_key, out, + &outLen, EC25519_BIG_ENDIAN), + WC_NO_ERR_TRACE(ECC_OUT_OF_RANGE_E)); + + DoExpectIntEQ(wc_FreeRng(&rng), 0); + wc_curve25519_free(&private_key); + wc_curve25519_free(&public_key); +#endif + return EXPECT_RESULT(); +} /* END test_wc_curve25519_shared_secret_zero_check */ + /* * Testing wc_curve25519_make_pub */ diff --git a/tests/api/test_curve25519.h b/tests/api/test_curve25519.h index 3d14bb53e3..c65ccb34f8 100644 --- a/tests/api/test_curve25519.h +++ b/tests/api/test_curve25519.h @@ -30,6 +30,7 @@ int test_wc_curve25519_export_key_raw(void); int test_wc_curve25519_export_key_raw_ex(void); int test_wc_curve25519_make_key(void); int test_wc_curve25519_shared_secret_ex(void); +int test_wc_curve25519_shared_secret_zero_check(void); int test_wc_curve25519_make_pub(void); int test_wc_curve25519_export_public_ex(void); int test_wc_curve25519_export_private_raw_ex(void); @@ -45,6 +46,7 @@ int test_wc_Curve25519KeyToDer_oneasymkey_version(void); TEST_DECL_GROUP("curve25519", test_wc_curve25519_export_key_raw_ex), \ TEST_DECL_GROUP("curve25519", test_wc_curve25519_make_key), \ TEST_DECL_GROUP("curve25519", test_wc_curve25519_shared_secret_ex), \ + TEST_DECL_GROUP("curve25519", test_wc_curve25519_shared_secret_zero_check),\ TEST_DECL_GROUP("curve25519", test_wc_curve25519_make_pub), \ TEST_DECL_GROUP("curve25519", test_wc_curve25519_export_public_ex), \ TEST_DECL_GROUP("curve25519", test_wc_curve25519_export_private_raw_ex), \ diff --git a/tests/api/test_curve448.c b/tests/api/test_curve448.c index bfc025d29b..2f0f652701 100644 --- a/tests/api/test_curve448.c +++ b/tests/api/test_curve448.c @@ -116,6 +116,48 @@ int test_wc_curve448_shared_secret_ex(void) return EXPECT_RESULT(); } /* END test_wc_curve448_shared_secret_ex */ +/* + * Testing that wc_curve448_shared_secret_ex rejects an all-zero shared + * secret (RFC 7748 section 6.2). This is the default behavior; users that + * need the legacy behavior can opt out with WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK. + */ +int test_wc_curve448_shared_secret_zero_check(void) +{ + EXPECT_DECLS; +#if defined(HAVE_CURVE448) && defined(HAVE_CURVE448_KEY_IMPORT) && \ + defined(HAVE_CURVE448_SHARED_SECRET) && \ + !defined(WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK) + curve448_key private_key; + curve448_key public_key; + WC_RNG rng; + byte out[CURVE448_KEY_SIZE]; + word32 outLen = sizeof(out); + /* All-zero public key is a low-order point that yields an all-zero + * shared secret for any private key. */ + byte zero_pub[CURVE448_PUB_KEY_SIZE]; + + XMEMSET(&rng, 0, sizeof(WC_RNG)); + XMEMSET(zero_pub, 0, sizeof(zero_pub)); + + ExpectIntEQ(wc_curve448_init(&private_key), 0); + ExpectIntEQ(wc_curve448_init(&public_key), 0); + ExpectIntEQ(wc_InitRng(&rng), 0); + + ExpectIntEQ(wc_curve448_make_key(&rng, CURVE448_KEY_SIZE, &private_key), 0); + ExpectIntEQ(wc_curve448_import_public_ex(zero_pub, sizeof(zero_pub), + &public_key, EC448_LITTLE_ENDIAN), 0); + + ExpectIntEQ(wc_curve448_shared_secret_ex(&private_key, &public_key, out, + &outLen, EC448_BIG_ENDIAN), + WC_NO_ERR_TRACE(ECC_OUT_OF_RANGE_E)); + + DoExpectIntEQ(wc_FreeRng(&rng), 0); + wc_curve448_free(&private_key); + wc_curve448_free(&public_key); +#endif + return EXPECT_RESULT(); +} /* END test_wc_curve448_shared_secret_zero_check */ + /* * Testing test_wc_curve448_export_public_ex */ diff --git a/tests/api/test_curve448.h b/tests/api/test_curve448.h index 93af7809fb..3ff1b4eed7 100644 --- a/tests/api/test_curve448.h +++ b/tests/api/test_curve448.h @@ -26,6 +26,7 @@ int test_wc_curve448_make_key(void); int test_wc_curve448_shared_secret_ex(void); +int test_wc_curve448_shared_secret_zero_check(void); int test_wc_curve448_export_public_ex(void); int test_wc_curve448_export_private_raw_ex(void); int test_wc_curve448_export_key_raw(void); @@ -39,6 +40,7 @@ int test_wc_Curve448PrivateKeyToDer_oneasymkey_version(void); #define TEST_CURVE448_DECLS \ TEST_DECL_GROUP("curve448", test_wc_curve448_make_key), \ TEST_DECL_GROUP("curve448", test_wc_curve448_shared_secret_ex), \ + TEST_DECL_GROUP("curve448", test_wc_curve448_shared_secret_zero_check), \ TEST_DECL_GROUP("curve448", test_wc_curve448_export_public_ex), \ TEST_DECL_GROUP("curve448", test_wc_curve448_export_private_raw_ex), \ TEST_DECL_GROUP("curve448", test_wc_curve448_export_key_raw), \ diff --git a/wolfcrypt/src/curve25519.c b/wolfcrypt/src/curve25519.c index a12ad9ee99..9ec92db147 100644 --- a/wolfcrypt/src/curve25519.c +++ b/wolfcrypt/src/curve25519.c @@ -637,7 +637,7 @@ static int wc_curve25519_shared_secret_nb(curve25519_key* privKey, } break; case 2: - #ifdef WOLFSSL_ECDHX_SHARED_NOT_ZERO + #ifndef WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK { int i; byte t = 0; @@ -649,13 +649,13 @@ static int wc_curve25519_shared_secret_nb(curve25519_key* privKey, ret = ECC_OUT_OF_RANGE_E; } else - #endif /* WOLFSSL_ECDHX_SHARED_NOT_ZERO */ + #endif /* !WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK */ { curve25519_copy_point(out, privKey->nb_ctx->o.point, endian); *outlen = CURVE25519_KEYSIZE; ret = 0; } - #ifdef WOLFSSL_ECDHX_SHARED_NOT_ZERO + #ifndef WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK } #endif break; @@ -761,7 +761,7 @@ int wc_curve25519_shared_secret_ex(curve25519_key* private_key, #endif } #endif /* FREESCALE_LTC_ECC */ -#ifdef WOLFSSL_ECDHX_SHARED_NOT_ZERO +#ifndef WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK if (ret == 0) { int i; byte t = 0; @@ -772,7 +772,7 @@ int wc_curve25519_shared_secret_ex(curve25519_key* private_key, ret = ECC_OUT_OF_RANGE_E; } } -#endif /* WOLFSSL_ECDHX_SHARED_NOT_ZERO */ +#endif /* !WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK */ if (ret == 0) { curve25519_copy_point(out, o.point, endian); *outlen = CURVE25519_KEYSIZE; diff --git a/wolfcrypt/src/curve448.c b/wolfcrypt/src/curve448.c index 8f1ded41dc..63c0f68089 100644 --- a/wolfcrypt/src/curve448.c +++ b/wolfcrypt/src/curve448.c @@ -33,7 +33,8 @@ * (when HAVE_CURVE448 is enabled) * HAVE_CURVE448_KEY_EXPORT: Enable Curve448 key export default: on * HAVE_CURVE448_KEY_IMPORT: Enable Curve448 key import default: on - * WOLFSSL_ECDHX_SHARED_NOT_ZERO: Check ECDH shared secret != 0 default: off + * WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK: Skip ECDH shared secret != 0 check + * default: off */ #include @@ -176,7 +177,7 @@ int wc_curve448_shared_secret_ex(curve448_key* private_key, if (ret == 0) { ret = curve448(o, private_key->k, public_key->p); } -#ifdef WOLFSSL_ECDHX_SHARED_NOT_ZERO +#ifndef WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK if (ret == 0) { byte t = 0; for (i = 0; i < CURVE448_PUB_KEY_SIZE; i++) { From dc6381a695b6ba9406f2847af7f78448aecae6d6 Mon Sep 17 00:00:00 2001 From: Kareem Date: Wed, 13 May 2026 17:11:23 -0700 Subject: [PATCH 3/5] Code review feedback, add test. --- doc/dox_comments/header_files/ssl.h | 3 ++ src/tls13.c | 14 ++++++--- tests/api/test_tls13.c | 48 +++++++++++++++++++++++++++++ tests/api/test_tls13.h | 2 ++ 4 files changed, 63 insertions(+), 4 deletions(-) diff --git a/doc/dox_comments/header_files/ssl.h b/doc/dox_comments/header_files/ssl.h index ac01a6e100..1a56a4ae7c 100644 --- a/doc/dox_comments/header_files/ssl.h +++ b/doc/dox_comments/header_files/ssl.h @@ -14232,10 +14232,13 @@ int wolfSSL_CTX_allow_post_handshake_auth(WOLFSSL_CTX* ctx); This is useful when connecting to a web server that has some pages that require client authentication and others that don't. + This function must be called before wolfSSL_connect() on the WOLFSSL object. + \param [in,out] ssl a pointer to a WOLFSSL structure, created using wolfSSL_new(). \return BAD_FUNC_ARG if ssl is NULL or not using TLS v1.3. \return SIDE_ERROR if called with a server. + \return BAD_STATE_E if called after the handshake has started. \return 0 if successful. _Example_ diff --git a/src/tls13.c b/src/tls13.c index 41d80e94bb..d447171f62 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -13166,9 +13166,9 @@ static int SanityCheckTls13MsgReceived(WOLFSSL* ssl, byte type) /* RFC 8446 4.6.2: A client that receives a post-handshake * CertificateRequest message without having sent the * "post_handshake_auth" extension MUST send an - * "unexpected_message" fatal alert. The caller of - * SanityCheckTls13MsgReceived() converts OUT_OF_ORDER_E into an - * unexpected_message alert. */ + * "unexpected_message" fatal alert. wolfSSL_allow_post_handshake_auth() + * must be called before wolfSSL_connect() so postHandshakeAuth + * reflects whether the extension was offered. */ if (ssl->options.serverState >= SERVER_FINISHED_COMPLETE && ssl->options.clientState == CLIENT_FINISHED_COMPLETE && !ssl->options.postHandshakeAuth) { @@ -14874,7 +14874,11 @@ int wolfSSL_CTX_allow_post_handshake_auth(WOLFSSL_CTX* ctx) * * ssl The SSL/TLS object. * returns BAD_FUNC_ARG when ssl is NULL, or not using TLS v1.3, - * SIDE_ERROR when not a client and 0 on success. + * SIDE_ERROR when not a client, BAD_STATE_E when called after the handshake + * has started, and 0 on success. + * + * Must be called before wolfSSL_connect() so the post_handshake_auth + * extension can be included in the ClientHello. */ int wolfSSL_allow_post_handshake_auth(WOLFSSL* ssl) { @@ -14882,6 +14886,8 @@ int wolfSSL_allow_post_handshake_auth(WOLFSSL* ssl) return BAD_FUNC_ARG; if (ssl->options.side == WOLFSSL_SERVER_END) return SIDE_ERROR; + if (ssl->options.handShakeState != NULL_STATE) + return BAD_STATE_E; ssl->options.postHandshakeAuth = 1; diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index de924e3052..a23b009660 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -5596,6 +5596,54 @@ int test_tls13_post_handshake_auth_no_ext(void) return EXPECT_RESULT(); } +/* wolfSSL_allow_post_handshake_auth() must be called before the handshake + * starts. A late call must fail with BAD_STATE_E and must not enable + * post-handshake authentication for the connection. */ +int test_tls13_post_handshake_auth_late_allow(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_POST_HANDSHAKE_AUTH) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + 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; + char readBuf[8]; + + 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); + ExpectIntEQ(wolfSSL_no_ticket_TLSv13(ssl_s), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* Too late: handshake is complete. */ + ExpectIntEQ(wolfSSL_allow_post_handshake_auth(ssl_c), + WC_NO_ERR_TRACE(BAD_STATE_E)); + + if (ssl_s != NULL) + ssl_s->options.postHandshakeAuth = 1; + ExpectIntEQ(wolfSSL_request_certificate(ssl_s), WOLFSSL_SUCCESS); + + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, (int)sizeof(readBuf)), + WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WC_NO_ERR_TRACE(OUT_OF_ORDER_E)); + ExpectIntEQ(wolfSSL_get_alert_history(ssl_c, &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(); +} + /* Test that a TLS 1.3-capable client rejects downgrade sentinels in a * downgraded ServerHello random for both TLS 1.2 and TLS 1.1-or-lower. */ int test_tls13_downgrade_sentinel(void) diff --git a/tests/api/test_tls13.h b/tests/api/test_tls13.h index 01b20029f9..9c86f98da9 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -59,6 +59,7 @@ int test_tls13_peerauth_failsafe(void); int test_tls13_hrr_bad_cookie(void); int test_tls13_zero_inner_content_type(void); int test_tls13_post_handshake_auth_no_ext(void); +int test_tls13_post_handshake_auth_late_allow(void); int test_tls13_downgrade_sentinel(void); int test_tls13_serverhello_bad_cipher_suites(void); int test_tls13_cert_with_extern_psk_apis(void); @@ -111,6 +112,7 @@ int test_tls13_cipher_fuzz_aes128_ccm_8_sha256(void); TEST_DECL_GROUP("tls13", test_tls13_hrr_bad_cookie), \ TEST_DECL_GROUP("tls13", test_tls13_zero_inner_content_type), \ TEST_DECL_GROUP("tls13", test_tls13_post_handshake_auth_no_ext), \ + TEST_DECL_GROUP("tls13", test_tls13_post_handshake_auth_late_allow), \ TEST_DECL_GROUP("tls13", test_tls13_downgrade_sentinel), \ TEST_DECL_GROUP("tls13", test_tls13_serverhello_bad_cipher_suites), \ TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_apis), \ From 775af7d967ef75c04603c63b999d806f5d0ecfc7 Mon Sep 17 00:00:00 2001 From: Kareem Date: Wed, 13 May 2026 17:34:29 -0700 Subject: [PATCH 4/5] Fix unit test failures with --enable-all. --- tests/api/test_tls13.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index a23b009660..a83125fe0e 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -5575,7 +5575,11 @@ int test_tls13_post_handshake_auth_no_ext(void) * one anyway by toggling the flag directly. */ if (ssl_s != NULL) ssl_s->options.postHandshakeAuth = 1; + /* OPENSSL_COMPATIBLE_DEFAULTS may leave groupMessages set on ssl_s; that + * suppresses SendBuffered() for the post-handshake CertificateRequest. */ + ExpectIntEQ(wolfSSL_clear_group_messages(ssl_s), WOLFSSL_SUCCESS); ExpectIntEQ(wolfSSL_request_certificate(ssl_s), WOLFSSL_SUCCESS); + ExpectIntGT(test_ctx.c_len, 0); /* The client must reject the unsolicited CertificateRequest. */ ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, (int)sizeof(readBuf)), @@ -5626,7 +5630,11 @@ int test_tls13_post_handshake_auth_late_allow(void) if (ssl_s != NULL) ssl_s->options.postHandshakeAuth = 1; + /* OPENSSL_COMPATIBLE_DEFAULTS may leave groupMessages set on ssl_s; that + * suppresses SendBuffered() for the post-handshake CertificateRequest. */ + ExpectIntEQ(wolfSSL_clear_group_messages(ssl_s), WOLFSSL_SUCCESS); ExpectIntEQ(wolfSSL_request_certificate(ssl_s), WOLFSSL_SUCCESS); + ExpectIntGT(test_ctx.c_len, 0); ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, (int)sizeof(readBuf)), WOLFSSL_FATAL_ERROR); From bd95858a95a055a1556267712c24dacfdc5fc266 Mon Sep 17 00:00:00 2001 From: Kareem Date: Fri, 1 May 2026 16:50:01 -0700 Subject: [PATCH 5/5] Avoid overwriting shared secret in non-blocking mode --- wolfcrypt/src/curve25519.c | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/wolfcrypt/src/curve25519.c b/wolfcrypt/src/curve25519.c index 9ec92db147..d239633808 100644 --- a/wolfcrypt/src/curve25519.c +++ b/wolfcrypt/src/curve25519.c @@ -625,12 +625,17 @@ static int wc_curve25519_shared_secret_nb(curve25519_key* privKey, switch (privKey->nb_ctx->ssState) { case 0: - XMEMSET(&privKey->nb_ctx->o, 0, sizeof(privKey->nb_ctx->o)); privKey->nb_ctx->ssState = 1; break; case 1: - ret = curve25519_nb(privKey->nb_ctx->o.point, privKey->k, - pubKey->p.point, privKey->nb_ctx); + /* Write the result directly into the caller's 'out' buffer. + * curve25519_nb() zeroes the non-blocking context on completion, + * so any output buffer that lives inside nb_ctx (e.g. + * nb_ctx->o.point) would be clobbered to zero before we could + * read it. The output is little-endian; case 2 handles the + * optional byte-reversal for EC25519_BIG_ENDIAN. */ + ret = curve25519_nb(out, privKey->k, pubKey->p.point, + privKey->nb_ctx); if (ret == 0) { ret = FP_WOULDBLOCK; privKey->nb_ctx->ssState = 2; @@ -643,21 +648,27 @@ static int wc_curve25519_shared_secret_nb(curve25519_key* privKey, byte t = 0; for (i = 0; i < CURVE25519_KEYSIZE; i++) { - t |= privKey->nb_ctx->o.point[i]; + t |= out[i]; } if (t == 0) { + ForceZero(out, CURVE25519_KEYSIZE); ret = ECC_OUT_OF_RANGE_E; + break; } - else + } #endif /* !WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK */ - { - curve25519_copy_point(out, privKey->nb_ctx->o.point, endian); - *outlen = CURVE25519_KEYSIZE; - ret = 0; + if (endian == EC25519_BIG_ENDIAN) { + /* Reverse the little-endian result in place. */ + int i; + byte tmp; + for (i = 0; i < CURVE25519_KEYSIZE / 2; i++) { + tmp = out[i]; + out[i] = out[CURVE25519_KEYSIZE - 1 - i]; + out[CURVE25519_KEYSIZE - 1 - i] = tmp; } - #ifndef WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK } - #endif + *outlen = CURVE25519_KEYSIZE; + ret = 0; break; }