From 494f4e02354a0e070412dd7198016f987a904664 Mon Sep 17 00:00:00 2001 From: Yosuke Shimizu Date: Thu, 5 Nov 2026 14:18:28 +0900 Subject: [PATCH] tls13: report WANT_WRITE from a blocked post-handshake auth response - DoTls13HandShakeMsgType() returns WANT_WRITE from the client's post-handshake auth wolfSSL_connect_TLSv13() call instead of POST_HAND_AUTH_ERROR for TLS only. - DoProcessReplyEx() finishes the CertificateRequest record for a WANT_WRITE as well as a WC_PENDING_E from that call - Add test_tls13_pha_want_write. --- src/internal.c | 23 +++++------ src/tls13.c | 5 ++- tests/api/test_tls13.c | 88 ++++++++++++++++++++++++++++++++++++++++++ tests/api/test_tls13.h | 2 + 4 files changed, 103 insertions(+), 15 deletions(-) diff --git a/src/internal.c b/src/internal.c index 81f89bd4c90..42edfc2a1a3 100644 --- a/src/internal.c +++ b/src/internal.c @@ -27997,20 +27997,15 @@ static int DoProcessReplyEx(WOLFSSL* ssl, int allowSocketErr) } } #endif - #if defined(WOLFSSL_ASYNC_CRYPT) && defined(WOLFSSL_POST_HANDSHAKE_AUTH) - /* Post-handshake auth resumes through - * wolfSSL_negotiate() instead of reprocessing this - * record, so it leaves processReply at doProcessInit - * (an ordinary pending message leaves it at - * runProcessingOneMessage). Finish the record here or - * the trailing MAC is read as the next record header - * and fails with VERSION_ERROR. Mirrors the end of - * record block below: resume inside the record when - * content is left, else skip the padding. Skipped for - * a key-schedule pend (kdfMsgStep != 0): the block - * above already finished the record, and running this - * one too would skip the padding twice. */ - if (ret == WC_NO_ERR_TRACE(WC_PENDING_E) && + #ifdef WOLFSSL_POST_HANDSHAKE_AUTH + /* An unfinished post-handshake auth response resumes + * through wolfSSL_negotiate(), so finish this record + * now. A key-schedule pend already finished it. */ + if ((ret == WC_NO_ERR_TRACE(WANT_WRITE) + #ifdef WOLFSSL_ASYNC_CRYPT + || ret == WC_NO_ERR_TRACE(WC_PENDING_E) + #endif + ) && ssl->kdfMsgStep == TLS13_MSG_KDF_NONE && ssl->options.processReply == doProcessInit) { if ((ssl->buffers.inputBuffer.idx - diff --git a/src/tls13.c b/src/tls13.c index 1a4e34e73c6..fab4e80e6b6 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -16534,7 +16534,10 @@ int DoTls13HandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx, if (wolfSSL_connect_TLSv13(ssl) != WOLFSSL_SUCCESS) { ret = ssl->error; - if (ret != WC_NO_ERR_TRACE(WC_PENDING_E)) + /* DTLS does not yet resume a blocked response */ + if (ret != WC_NO_ERR_TRACE(WC_PENDING_E) && + (ret != WC_NO_ERR_TRACE(WANT_WRITE) || + ssl->options.dtls)) ret = POST_HAND_AUTH_ERROR; } } diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index 4101dfa9b3d..03c9d1e3bdd 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -12366,6 +12366,94 @@ int test_tls13_post_handshake_auth_late_allow(void) return EXPECT_RESULT(); } +/* A client that reads a post-handshake CertificateRequest while its socket + * takes no writes reports WANT_WRITE, then finishes the response from the + * next wolfSSL_read() or wolfSSL_write(). */ +int test_tls13_pha_want_write(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) && \ + !defined(NO_RSA) && !defined(NO_CERTS) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + char msg[] = "after post-handshake auth"; + char readBuf[64]; + int retryByWrite; + + for (retryByWrite = 0; retryByWrite <= 1 && !EXPECT_FAIL(); + retryByWrite++) { + 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); + 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_allow_post_handshake_auth(ssl_c), 0); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, cliCertFile, + NULL), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_no_ticket_TLSv13(ssl_s), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_PEER | + WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + ExpectIntEQ(wolfSSL_request_certificate(ssl_s), WOLFSSL_SUCCESS); + + test_memio_simulate_want_write(&test_ctx, 1, 1); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, (int)sizeof(readBuf)), + WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_WRITE); + ExpectIntEQ(test_ctx.s_len, 0); + test_memio_simulate_want_write(&test_ctx, 1, 0); + + if (retryByWrite) { + ExpectIntEQ(wolfSSL_write(ssl_c, msg, (int)sizeof(msg)), + (int)sizeof(msg)); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, (int)sizeof(readBuf)), + (int)sizeof(msg)); + } + else { + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, (int)sizeof(readBuf)), + WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + ExpectIntGT(test_ctx.s_len, 0); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, (int)sizeof(readBuf)), + WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_s, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + } + ExpectIntEQ(ssl_s->options.havePeerCert, 1); + ExpectIntEQ(ssl_s->options.havePeerVerify, 1); + ExpectIntEQ(ssl_s->msgsReceived.got_finished, 1); + + /* The client's read side is intact after the response. */ + ExpectIntEQ(wolfSSL_write(ssl_s, msg, (int)sizeof(msg)), + (int)sizeof(msg)); + XMEMSET(readBuf, 0, sizeof(readBuf)); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, (int)sizeof(readBuf)), + (int)sizeof(msg)); + ExpectBufEQ(readBuf, msg, sizeof(msg)); + + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_CTX_free(ctx_c); + ctx_c = NULL; + wolfSSL_free(ssl_s); + ssl_s = NULL; + wolfSSL_CTX_free(ctx_s); + ctx_s = NULL; + } +#endif + return EXPECT_RESULT(); +} + /* A post-handshake CertificateRequest must reach the wire from * wolfSSL_request_certificate() even when the server groups messages. */ int test_tls13_pha_group_messages(void) diff --git a/tests/api/test_tls13.h b/tests/api/test_tls13.h index 3b1059dcb25..777e32e1bc2 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -112,6 +112,7 @@ int test_tls13_server_cookie_parse(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_pha_want_write(void); int test_tls13_pha_group_messages(void); int test_tls13_downgrade_sentinel(void); int test_tls13_downgrade_sentinel_resumption(void); @@ -300,6 +301,7 @@ int test_tls13_AEAD_limit_KeyUpdate_want_write(void); 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_pha_want_write), \ TEST_DECL_GROUP("tls13", test_tls13_pha_group_messages), \ TEST_DECL_GROUP("tls13", test_tls13_downgrade_sentinel), \ TEST_DECL_GROUP("tls13", test_tls13_downgrade_sentinel_resumption), \