TLS 1.3: fix for post-handshake authentication

Only exempt the missing-certificate check during the initial handshake; once a
post-handshake CertificateRequest is outstanding the server again requires the
client certificate (and its CertificateVerify). Adds a post-handshake auth
test.
This commit is contained in:
Tobias Frauenschläger
2026-06-16 15:59:54 +02:00
parent c929798460
commit f23544f094
3 changed files with 87 additions and 2 deletions
+17 -2
View File
@@ -11835,7 +11835,10 @@ int DoTls13Finished(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
#endif
if (
#ifdef WOLFSSL_POST_HANDSHAKE_AUTH
!ssl->options.verifyPostHandshake &&
/* Exempt only the initial handshake; a pending post-handshake
* CertificateRequest (certReqCtx != NULL) still requires a peer
* certificate and a valid CertificateVerify. */
(!ssl->options.verifyPostHandshake || ssl->certReqCtx != NULL) &&
#endif
(!ssl->options.havePeerCert || !ssl->options.havePeerVerify)) {
ret = NO_PEER_CERT; /* NO_PEER_VERIFY */
@@ -13507,7 +13510,19 @@ static int SanityCheckTls13MsgReceived(WOLFSSL* ssl, byte type)
*/
if (ssl->options.verifyPeer &&
#ifdef WOLFSSL_POST_HANDSHAKE_AUTH
!ssl->options.verifyPostHandshake &&
/* The post-handshake-auth exemption is only valid during
* the initial handshake. On the server, once a
* post-handshake CertificateRequest is outstanding
* (certReqCtx != NULL), a Certificate is required again.
* Scoped to the server: certReqCtx means something
* different on the client (a received request) and the
* client does not process an inbound Finished in that
* state. Whether an empty Certificate is then accepted
* follows the verify mode (FAIL_IF_NO_PEER_CERT), exactly
* as for first-handshake client authentication. */
(!ssl->options.verifyPostHandshake ||
(ssl->options.side == WOLFSSL_SERVER_END &&
ssl->certReqCtx != NULL)) &&
#endif
!ssl->msgsReceived.got_certificate) {
WOLFSSL_MSG("Finished received out of order - "
+68
View File
@@ -2820,6 +2820,74 @@ int test_tls13_rpk_handshake_no_negotiation(void)
return EXPECT_RESULT();
}
/* Post-handshake authentication (PHA) positive guard. A server configured with
* WOLFSSL_VERIFY_POST_HANDSHAKE must complete the initial handshake WITHOUT a
* client certificate (verification is deferred) and then be able to request one
* post-handshake. This guards the property the auth-bypass fix must preserve:
* the missing-certificate exemption stays in force while no post-handshake
* request is outstanding (certReqCtx == NULL). If the fix wrongly treated the
* initial handshake as a pending PHA request, the handshake below would fail.
*
* Scope: this is a positive guard only.
* - The bypass itself is NOT reproduced here: the
* SanityCheckTls13MsgReceived / DoTls13Finished branches the fix re-enables
* only fire when a client sends a Finished with NO Certificate message at all
* (got_certificate == 0). A conformant wolfSSL client always sends at least
* an empty Certificate, so reproducing the bypass needs a non-conformant
* client; the report used a state-machine model. The negative direction
* rests on code review.
* - Driving the full post-handshake certificate exchange to completion is
* intentionally not asserted: whether the peer chain ends up populated
* depends on build options unrelated to this fix (it differs between
* --enable-all and the user_settings_all header build), so it is not a
* portable assertion. */
int test_tls13_pha(void)
{
EXPECT_DECLS;
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TLS13) && \
defined(WOLFSSL_POST_HANDSHAKE_AUTH) && defined(SESSION_CERTS) && \
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \
!defined(NO_RSA) && !defined(NO_CERTS)
WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL;
WOLFSSL *ssl_c = NULL, *ssl_s = NULL;
struct test_memio_ctx test_ctx;
WOLFSSL_X509_CHAIN* chain = NULL;
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: trust the (self-signed) client certificate and defer
* verification to post-handshake. */
ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, cliCertFile, NULL),
WOLFSSL_SUCCESS);
wolfSSL_set_verify(ssl_s,
WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_POST_HANDSHAKE, NULL);
/* Client: load a cert/key and advertise post-handshake auth. */
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);
/* Initial handshake must complete even though no client cert was sent -
* the verifyPostHandshake exemption (certReqCtx == NULL) is intact. */
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
ExpectNotNull(chain = wolfSSL_get_peer_chain(ssl_s));
ExpectIntEQ(wolfSSL_get_chain_count(chain), 0);
/* And the server can issue a post-handshake certificate request. */
ExpectIntEQ(wolfSSL_request_certificate(ssl_s), WOLFSSL_SUCCESS);
wolfSSL_free(ssl_c);
wolfSSL_free(ssl_s);
wolfSSL_CTX_free(ctx_c);
wolfSSL_CTX_free(ctx_s);
#endif
return EXPECT_RESULT();
}
#if defined(HAVE_IO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TLS13) && \
defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE) && \
+2
View File
@@ -29,6 +29,7 @@ int test_tls13_cipher_suites(void);
int test_tls13_bad_psk_binder(void);
int test_tls13_rpk_handshake(void);
int test_tls13_rpk_handshake_no_negotiation(void);
int test_tls13_pha(void);
int test_tls13_pq_groups(void);
int test_tls13_multi_pqc_key_share(void);
int test_tls13_early_data(void);
@@ -90,6 +91,7 @@ int test_tls13_AEAD_limit_KU_aes128_ccm_8_sha256(void);
TEST_DECL_GROUP("tls13", test_tls13_bad_psk_binder), \
TEST_DECL_GROUP("tls13", test_tls13_rpk_handshake), \
TEST_DECL_GROUP("tls13", test_tls13_rpk_handshake_no_negotiation), \
TEST_DECL_GROUP("tls13", test_tls13_pha), \
TEST_DECL_GROUP("tls13", test_tls13_pq_groups), \
TEST_DECL_GROUP("tls13", test_tls13_multi_pqc_key_share), \
TEST_DECL_GROUP("tls13", test_tls13_early_data), \