From 78926cec0519cc738831816e5e38a0db741ed08b Mon Sep 17 00:00:00 2001 From: Kareem Date: Tue, 29 Sep 2026 16:50:13 -0700 Subject: [PATCH 01/11] Clear session ticket resumption state in wolfSSL_clear. Reset useTicket and createTicket, the server's alternate session id, and the per-connection alert count, empty record count, fragment offset and message building state, so a reused object starts the next handshake clean. DoClientHello resets the ticket flags itself, except during secure renegotiation, where the hello inherits the connection's. Thanks to ByteRay Ltd for the report. --- src/internal.c | 9 +++ src/ssl.c | 16 ++++- tests/api/test_tls.c | 154 +++++++++++++++++++++++++++++++++++++++++++ tests/api/test_tls.h | 4 ++ 4 files changed, 182 insertions(+), 1 deletion(-) diff --git a/src/internal.c b/src/internal.c index 5dcaf668e65..84e0825cf04 100644 --- a/src/internal.c +++ b/src/internal.c @@ -42991,6 +42991,15 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) /* Reset to sane value for SCR */ ssl->options.resuming = 0; ssl->arrays->sessionIDSz = 0; +#ifdef HAVE_SESSION_TICKET + /* Only this ClientHello may set them, in TLSX_SessionTicket_Parse(), + * which ignores the extension during SCR - so an SCR hello inherits + * the connection's. */ + if (!IsSCR(ssl)) { + ssl->options.useTicket = 0; + ssl->options.createTicket = 0; + } +#endif /* protocol version, random and session id length check */ if (OPAQUE16_LEN + RAN_LEN + OPAQUE8_LEN > helloSz) diff --git a/src/ssl.c b/src/ssl.c index d4dfe1afee7..6c5e8e4eadc 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -5811,6 +5811,9 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out, ssl->options.connReset = 0; ssl->options.sentNotify = 0; ssl->options.closeNotify = 0; + /* Per-connection budgets, not cumulative across a reused object. */ + ssl->options.alertCount = 0; + ssl->options.emptyRecordCount = 0; ssl->options.sendVerify = 0; ssl->options.serverState = NULL_STATE; ssl->options.clientState = NULL_STATE; @@ -5834,8 +5837,9 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out, DYNAMIC_TYPE_TMP_BUFFER); ssl->buffers.certVerifyMsg.buffer = NULL; ssl->buffers.certVerifyMsg.length = 0; - ssl->fragOffset = 0; #endif + ssl->fragOffset = 0; + ssl->options.buildingMsg = 0; ssl->options.processReply = 0; /* doProcessInit */ ssl->options.havePeerVerify = 0; ssl->options.havePeerCert = 0; @@ -5955,7 +5959,17 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out, #endif #endif ssl->options.rejectTicket = 0; + ssl->options.useTicket = 0; + ssl->options.createTicket = 0; #endif + /* A server keeps its session across the reset, but the next client + * has not asked for it. A client is the side that resumes what it + * held, so it keeps both. */ + if ((ssl->options.side == WOLFSSL_SERVER_END) && + (ssl->session != NULL)) { + ssl->session->haveAltSessionID = 0; + ForceZero(ssl->session->altSessionID, ID_LEN); + } #ifdef WOLFSSL_EARLY_DATA ssl->earlyData = no_early_data; ssl->earlyDataSz = 0; diff --git a/tests/api/test_tls.c b/tests/api/test_tls.c index 24792031363..64838085ba4 100644 --- a/tests/api/test_tls.c +++ b/tests/api/test_tls.c @@ -3417,6 +3417,160 @@ int test_tls12_resume_ticket_wrong_suite(void) return EXPECT_RESULT(); } +/* Catches per-connection Options state that wolfSSL_clear() forgets, rather + * than one field at a time. Every Options is born inside + * XMEMSET(ssl, 0, sizeof(WOLFSSL)), so padding and bitfield slack are zero in + * both objects and only written fields can differ. */ +int test_tls12_clear_resets_options(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(WOLFSSL_NO_TLS12) && defined(HAVE_SESSION_TICKET) && \ + !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL, *fresh = NULL; + struct test_memio_ctx test_ctx; + char msg[] = "test"; + char reply[sizeof(msg)]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + /* Data both ways and a bidirectional close, so record-layer and shutdown + * state is covered too, not just the handshake. */ + ExpectIntEQ(wolfSSL_write(ssl_c, msg, (int)sizeof(msg)), (int)sizeof(msg)); + ExpectIntEQ(wolfSSL_read(ssl_s, reply, (int)sizeof(reply)), + (int)sizeof(msg)); + ExpectIntEQ(wolfSSL_write(ssl_s, msg, (int)sizeof(msg)), (int)sizeof(msg)); + ExpectIntEQ(wolfSSL_read(ssl_c, reply, (int)sizeof(reply)), + (int)sizeof(msg)); + ExpectIntEQ(wolfSSL_shutdown(ssl_c), WOLFSSL_SHUTDOWN_NOT_DONE); + ExpectIntEQ(wolfSSL_read(ssl_s, reply, (int)sizeof(reply)), 0); + ExpectIntEQ(wolfSSL_shutdown(ssl_s), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_shutdown(ssl_c), WOLFSSL_SUCCESS); + /* Same CTX, never handshaked, so its Options is what a reset should + * produce. Created after the handshake so the CTX is fully configured. */ + ExpectNotNull(fresh = wolfSSL_new(ctx_s)); + ExpectIntEQ(wolfSSL_clear(ssl_s), WOLFSSL_SUCCESS); + + if ((ssl_s != NULL) && (fresh != NULL)) { + /* Negotiated results, kept on purpose for the accessors. */ + ssl_s->options.cipherSuite0 = fresh->options.cipherSuite0; + ssl_s->options.cipherSuite = fresh->options.cipherSuite; + ssl_s->options.hashAlgo = fresh->options.hashAlgo; + ssl_s->options.sigAlgo = fresh->options.sigAlgo; + ssl_s->options.haveDH = fresh->options.haveDH; + /* wolfSSL_get_shutdown() reports a completed bidirectional shutdown + * from this after a clear, by documented intent. */ + ssl_s->options.shutdownDone = fresh->options.shutdownDone; + /* Reset only under WOLFSSL_ASYNC_CRYPT. */ + ssl_s->options.asyncState = fresh->options.asyncState; + /* Same condition the fields are declared under. */ +#if !defined(NO_DH) && !defined(WOLFSSL_OLD_PRIME_CHECK) && \ + !defined(HAVE_FIPS) && !defined(HAVE_SELFTEST) + ssl_s->options.dhDoKeyTest = fresh->options.dhDoKeyTest; + ssl_s->options.dhKeyTested = fresh->options.dhKeyTested; +#endif + /* // NOLINTNEXTLINE(bugprone-suspicious-memory-comparison) */ + ExpectIntEQ(XMEMCMP(&ssl_s->options, &fresh->options, sizeof(Options)), + 0); + if (EXPECT_FAIL()) { + /* Offsets rather than names: Options is mostly bitfields. */ + const byte* a = (const byte*)&ssl_s->options; + const byte* b = (const byte*)&fresh->options; + word32 k; + for (k = 0; k < (word32)sizeof(Options); k++) { + if (a[k] != b[k]) { + fprintf(stderr, "Options differ at byte %u:" + " reused=0x%02x fresh=0x%02x\n", k, a[k], b[k]); + } + } + } + } + + wolfSSL_free(fresh); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* useTicket is per-handshake: an object reused with wolfSSL_clear() must not + * carry it into the next ClientHello, or the retained session is resumed for + * a client that never held it. The id offered below is dropped from the cache, + * so only stale state could resume it. */ +int test_tls12_reuse_clears_use_ticket(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(WOLFSSL_NO_TLS12) && defined(HAVE_SESSION_TICKET) && \ + !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && !defined(NO_SESSION_CACHE) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL, *ctx_s2 = NULL; + WOLFSSL *ssl_c = NULL, *ssl_c2 = NULL, *ssl_c3 = NULL; + WOLFSSL *ssl_s = NULL, *ssl_s2 = NULL; + WOLFSSL_SESSION* sess = NULL; + struct test_memio_ctx test_ctx; + int useTicketAfterClear = -1; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->options.useTicket, 1); + + /* From a server issuing no tickets, so the next ClientHello carries a + * session id only. Not modified in place: it may be cache-backed. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s2, &ssl_c2, &ssl_s2, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c2, ssl_s2, 10, NULL), 0); + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c2)); + /* Premise, not the fix: a ticket would resume legitimately. */ + ExpectIntEQ(sess->ticketLen, 0); + /* The two ends share one cache in-process. */ + ExpectIntEQ(wolfSSL_SSL_CTX_remove_session(ctx_s2, sess), 1); + + ExpectIntEQ(wolfSSL_clear(ssl_s), WOLFSSL_SUCCESS); + /* Assert last, or a failure here skips the behavioural half. */ + if (ssl_s != NULL) + useTicketAfterClear = ssl_s->options.useTicket; + + /* Point the reused server at the new transport. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c3, NULL, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + if (ssl_s != NULL) { + wolfSSL_SetIOWriteCtx(ssl_s, &test_ctx); + wolfSSL_SetIOReadCtx(ssl_s, &test_ctx); + } + ExpectIntEQ(wolfSSL_set_session(ssl_c3, sess), WOLFSSL_SUCCESS); + /* May fail; what matters is it did not hand over the retained session. */ + if ((ssl_c3 != NULL) && (ssl_s != NULL)) + test_memio_do_handshake(ssl_c3, ssl_s, 10, NULL); + ExpectIntEQ(wolfSSL_session_reused(ssl_s), 0); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->options.resuming, 0); + ExpectIntEQ(useTicketAfterClear, 0); + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_c2); + wolfSSL_free(ssl_c3); + wolfSSL_free(ssl_s); + wolfSSL_free(ssl_s2); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + wolfSSL_CTX_free(ctx_s2); +#endif + return EXPECT_RESULT(); +} + /* A ticket the server can't honor must fall back to a full handshake (RFC 5077 * 3.4), even under a different suite than the cached ticket session - the * F-5811 suite check must not abort it. The second handshake uses a fresh diff --git a/tests/api/test_tls.h b/tests/api/test_tls.h index 4824c5ecc88..db032ab46ba 100644 --- a/tests/api/test_tls.h +++ b/tests/api/test_tls.h @@ -61,6 +61,8 @@ int test_tls_version_mask_alert_record(void); int test_tls_version_error_alert_mapping(void); int test_tls12_etm_failed_resumption(void); int test_tls12_resume_ticket_wrong_suite(void); +int test_tls12_clear_resets_options(void); +int test_tls12_reuse_clears_use_ticket(void); int test_tls12_resume_ticket_decline_fallback(void); int test_tls12_ticket_dropped_on_bad_finished(void); int test_tls12_ticket_cached_after_finished(void); @@ -142,6 +144,8 @@ int test_tls_param_flags_crl_check(void); TEST_DECL_GROUP("tls", test_tls_version_error_alert_mapping), \ TEST_DECL_GROUP("tls", test_tls12_etm_failed_resumption), \ TEST_DECL_GROUP("tls", test_tls12_resume_ticket_wrong_suite), \ + TEST_DECL_GROUP("tls", test_tls12_clear_resets_options), \ + TEST_DECL_GROUP("tls", test_tls12_reuse_clears_use_ticket), \ TEST_DECL_GROUP("tls", test_tls12_resume_ticket_decline_fallback), \ TEST_DECL_GROUP("tls", test_tls12_ticket_dropped_on_bad_finished), \ TEST_DECL_GROUP("tls", test_tls12_ticket_cached_after_finished), \ From 7460ecc18b3fe0c7f0e24c78385883bbed6681cb Mon Sep 17 00:00:00 2001 From: Kareem Date: Tue, 29 Sep 2026 17:10:08 -0700 Subject: [PATCH 02/11] Ensure that a resumed session properly handles client auth. Record in the session and in the ticket whether the client presented a certificate and proved possession of its key, and do a full handshake instead of resuming (session id, ticket, session secret callback and TLS 1.3 PSK) when the policy requires a client certificate the session never had. Every site deciding that policy uses ClientAuthRequired(), which covers mutual auth without verifyPeer and the PSK exception of WOLFSSL_VERIFY_FAIL_EXCEPT_PSK. The TLS 1.3 post-handshake exemption stays with the callers that defer. wolfSSL_mutual_auth() sets the verify mode so the certificate is asked for, and FAIL_IF_NO_PEER_CERT and FAIL_EXCEPT_PSK imply VERIFY_PEER. The i2d/d2i session encoding carries the new flag and d2i requires the full length. Thanks to ByteRay Ltd for the report. --- src/internal.c | 90 +++-- src/ssl.c | 1 + src/ssl_api_cert.c | 19 + src/ssl_api_hs.c | 4 +- src/ssl_sess.c | 22 +- src/tls13.c | 23 +- tests/api.c | 78 +++- tests/api/test_ssl_cert.c | 20 +- tests/api/test_tls.c | 752 +++++++++++++++++++++++++++++++++++++- tests/api/test_tls.h | 25 ++ wolfssl/internal.h | 28 +- 11 files changed, 1012 insertions(+), 50 deletions(-) diff --git a/src/internal.c b/src/internal.c index 84e0825cf04..8cd9ebc288a 100644 --- a/src/internal.c +++ b/src/internal.c @@ -19116,10 +19116,10 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, #endif /* HAVE_RPK && WOLFSSL_TLS13 */ if (args->count == 0) { - /* Empty certificate message. */ - if ((ssl->options.side == WOLFSSL_SERVER_END) && - (ssl->options.mutualAuth || (ssl->options.failNoCert && - IsAtLeastTLSv1_2(ssl)))) { + /* Empty certificate message. Only mutualAuth fails here + * before TLS 1.2; the later check catches the rest. */ + if (ClientAuthRequired(ssl, SSL_USING_PSK(ssl)) && + (ssl->options.mutualAuth || IsAtLeastTLSv1_2(ssl))) { WOLFSSL_MSG("No peer cert from Client"); ret = NO_PEER_CERT; WOLFSSL_ERROR_VERBOSE(ret); @@ -42658,7 +42658,15 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) if (!session) { WOLFSSL_MSG("Session lookup for resume failed"); ssl->options.resuming = 0; - } else { + } + else if (ClientAuthRequired(ssl, CipherRequires( + session->cipherSuite0, session->cipherSuite, + REQUIRES_PSK)) && !session->peerAuthOk) { + WOLFSSL_MSG("Session lacks client auth, do full handshake"); + ssl->options.resuming = 0; + ssl->options.peerAuthGood = 0; + } + else { if (MatchSuite(ssl, &clSuites) < 0) { WOLFSSL_MSG("Unsupported cipher suite, OldClientHello"); return UNSUPPORTED_SUITE; @@ -42694,6 +42702,22 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) #endif /* OLD_HELLO_ALLOWED */ +#ifndef NO_CERTS +/* Does a full handshake on this connection require a client certificate? + * usingPsk is whether the suite in question authenticates with a PSK, which + * only the caller knows. verifyPostHandshake is deliberately absent: it defers + * the demand, not the policy, so the callers that care apply it. */ +int ClientAuthRequired(const WOLFSSL* ssl, int usingPsk) +{ + if (ssl->options.side != WOLFSSL_SERVER_END) + return 0; + if (ssl->options.verifyNone) + return 0; + return ssl->options.mutualAuth || ssl->options.failNoCert || + (ssl->options.failNoCertxPSK && !usingPsk); +} +#endif /* !NO_CERTS */ + #ifndef WOLFSSL_NO_TLS12 /** @@ -42742,6 +42766,15 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) #endif ) { int secretSz = SECRET_LEN; + /* The secret comes from the callback, and nothing records an auth + * outcome for it. */ + if (ClientAuthRequired(ssl, ssl->options.usingPSK_cipher)) { + WOLFSSL_MSG("Session secret callback cannot assert client auth," + " do full handshake"); + ssl->options.resuming = 0; + ssl->options.peerAuthGood = 0; + return ret; + } WOLFSSL_MSG("Calling session secret callback"); ret = wc_RNG_GenerateBlock(ssl->rng, ssl->arrays->serverRandom, RAN_LEN); @@ -42789,6 +42822,14 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) ssl->options.resuming = 0; return ret; } + if (ClientAuthRequired(ssl, CipherRequires(session->cipherSuite0, + session->cipherSuite, REQUIRES_PSK)) && + !session->peerAuthOk) { + WOLFSSL_MSG("Session lacks client auth, do full handshake"); + ssl->options.resuming = 0; + ssl->options.peerAuthGood = 0; + return ret; + } #if defined(HAVE_SESSION_TICKET) && \ (defined(HAVE_SNI) || defined(HAVE_ALPN)) /* Do not resume session if sniHash/alpnHash do not match. */ @@ -44486,6 +44527,15 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) it->suite[0] = ssl->options.cipherSuite0; it->suite[1] = ssl->options.cipherSuite; + /* Both arms below only add to this, and an async re-entry may not + * have re-zeroed the buffer. A resumed connection sends no + * Certificate, so it carries what it resumed. */ + it->flags = 0; + if (ssl->options.resuming ? ssl->session->peerAuthOk : + (ssl->options.havePeerCert && ssl->options.havePeerVerify)) { + it->flags |= WOLFSSL_TICKET_FLAG_PEER_AUTH; + } + #ifdef WOLFSSL_EARLY_DATA c32toa(ssl->options.maxEarlyDataSz, it->maxEarlyDataSz); #endif @@ -44500,7 +44550,8 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) #ifndef NO_ASN_TIME c32toa(LowResTimer(), it->timestamp); #endif - it->haveEMS = (byte) ssl->options.haveEMS; + if (ssl->options.haveEMS) + it->flags |= WOLFSSL_TICKET_FLAG_EMS; } else { #ifdef WOLFSSL_TLS13 @@ -45059,6 +45110,9 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) #ifdef HAVE_ALPN XMEMCPY(ssl->session->alpnHash, it->alpnHash, TICKET_BINDING_HASH_SZ); #endif + /* After the DupSession above, which would otherwise overwrite it. */ + ssl->session->peerAuthOk = + (it->flags & WOLFSSL_TICKET_FLAG_PEER_AUTH) ? 1 : 0; if (!IsAtLeastTLSv1_3(ssl->version)) { if (ssl->arrays == NULL) @@ -45066,7 +45120,8 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) XMEMCPY(ssl->arrays->masterSecret, it->msecret, SECRET_LEN); /* Copy the haveExtendedMasterSecret property from the ticket to * the saved session, so the property may be checked later. */ - ssl->session->haveEMS = it->haveEMS; + ssl->session->haveEMS = + (it->flags & WOLFSSL_TICKET_FLAG_EMS) ? 1 : 0; ato32((const byte*)&it->timestamp, &ssl->session->bornOn); #ifndef NO_RESUME_SUITE_CHECK ssl->session->cipherSuite0 = it->suite[0]; @@ -45147,7 +45202,8 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) c32toa((word32)(milliBornOn >> 32), it->timestamp); c32toa((word32)milliBornOn , it->timestamp + OPAQUE32_LEN); #endif - it->haveEMS = (byte)sess->haveEMS; + it->flags = (byte)((sess->haveEMS ? WOLFSSL_TICKET_FLAG_EMS : 0) | + (sess->peerAuthOk ? WOLFSSL_TICKET_FLAG_PEER_AUTH : 0)); c32toa(sess->ticketAdd, it->ageAdd); c16toa(sess->namedGroup, it->namedGroup); if (sess->ticketNonce.len <= MAX_TICKET_NONCE_STATIC_SZ) { @@ -46661,20 +46717,10 @@ static int DefTicketEncCb(WOLFSSL* ssl, byte key_name[WOLFSSL_TICKET_NAME_SZ], } #if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CLIENT_AUTH) - if (ssl->options.verifyPeer && - (ssl->options.mutualAuth || ssl->options.failNoCert)) { - if (!ssl->options.havePeerCert) { - WOLFSSL_MSG("client didn't present peer cert"); - ERROR_OUT(NO_PEER_CERT, exit_dcke); - } - } - - if (ssl->options.verifyPeer && ssl->options.failNoCertxPSK) { - if (!ssl->options.havePeerCert && - !ssl->options.usingPSK_cipher) { - WOLFSSL_MSG("client didn't present peer cert"); - ERROR_OUT(NO_PEER_CERT, exit_dcke); - } + if (ClientAuthRequired(ssl, SSL_USING_PSK(ssl)) && + !ssl->options.havePeerCert) { + WOLFSSL_MSG("client didn't present peer cert"); + ERROR_OUT(NO_PEER_CERT, exit_dcke); } #endif /* !NO_CERTS && !WOLFSSL_NO_CLIENT_AUTH */ diff --git a/src/ssl.c b/src/ssl.c index 6c5e8e4eadc..1b214ce77d9 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -5969,6 +5969,7 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out, (ssl->session != NULL)) { ssl->session->haveAltSessionID = 0; ForceZero(ssl->session->altSessionID, ID_LEN); + ssl->session->peerAuthOk = 0; } #ifdef WOLFSSL_EARLY_DATA ssl->earlyData = no_early_data; diff --git a/src/ssl_api_cert.c b/src/ssl_api_cert.c index 62e8f653d56..a941c1cae02 100644 --- a/src/ssl_api_cert.c +++ b/src/ssl_api_cert.c @@ -47,6 +47,13 @@ int wolfSSL_CTX_mutual_auth(WOLFSSL_CTX* ctx, int req) return SIDE_ERROR; ctx->mutualAuth = (byte)req; + /* Required means ask for one and fail without it: sites reading only + * failNoCert would otherwise treat it as optional. */ + if (req) { + ctx->verifyNone = 0; + ctx->verifyPeer = 1; + ctx->failNoCert = 1; + } return 0; } @@ -69,6 +76,13 @@ int wolfSSL_mutual_auth(WOLFSSL* ssl, int req) return SIDE_ERROR; ssl->options.mutualAuth = (word16)req; + /* Required means ask for one and fail without it: sites reading only + * failNoCert would otherwise treat it as optional. */ + if (req) { + ssl->options.verifyNone = 0; + ssl->options.verifyPeer = 1; + ssl->options.failNoCert = 1; + } return 0; } @@ -587,6 +601,11 @@ static SetVerifyOptions ModeToVerifyOptions(int mode) (mode & WOLFSSL_VERIFY_FAIL_EXCEPT_PSK) != 0; opts.failNoCert = (mode & WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT) != 0; + /* Both only fail a certificate that was asked for, so they imply + * asking. */ + if (opts.failNoCert || opts.failNoCertxPSK) { + opts.verifyPeer = 1; + } #if defined(WOLFSSL_TLS13) && defined(WOLFSSL_POST_HANDSHAKE_AUTH) opts.verifyPostHandshake = (mode & WOLFSSL_VERIFY_POST_HANDSHAKE) != 0; diff --git a/src/ssl_api_hs.c b/src/ssl_api_hs.c index 5feaff1d091..521bc38b8a5 100644 --- a/src/ssl_api_hs.c +++ b/src/ssl_api_hs.c @@ -1102,11 +1102,11 @@ int wolfSSL_accept(WOLFSSL* ssl) case ACCEPT_SECOND_REPLY_DONE : #ifndef NO_CERTS /* SERVER: When not resuming and verifying peer but no certificate - * received and not failing when not received then peer auth good. + * received and none was required then peer auth good. */ if ((!ssl->options.resuming) && (ssl->options.verifyPeer) && (!ssl->options.havePeerCert) && - (!ssl->options.failNoCert)) { + (!ClientAuthRequired(ssl, ssl->options.usingPSK_cipher))) { ssl->options.peerAuthGood = 1; } #endif /* !NO_CERTS */ diff --git a/src/ssl_sess.c b/src/ssl_sess.c index ba42dea2168..bbd77b9afa0 100644 --- a/src/ssl_sess.c +++ b/src/ssl_sess.c @@ -2953,6 +2953,8 @@ int wolfSSL_i2d_SSL_SESSION(WOLFSSL_SESSION* sess, unsigned char** p) #endif #endif /* !NO_WOLFSSL_SERVER && !NO_TLS */ #endif + /* peerAuthOk, last so an older reader stops before it. */ + size += OPAQUE8_LEN; if (p != NULL) { unsigned char *data; @@ -3048,6 +3050,7 @@ int wolfSSL_i2d_SSL_SESSION(WOLFSSL_SESSION* sess, unsigned char** p) #endif #endif /* !NO_WOLFSSL_SERVER && !NO_TLS */ #endif + data[idx++] = sess->peerAuthOk; } #endif @@ -3068,6 +3071,11 @@ int wolfSSL_i2d_SSL_SESSION(WOLFSSL_SESSION* sess, unsigned char** p) * enabled with the wolfSSL library exporting a session then it is * expected to be turned on with the wolfSSL library importing the * session. + * + * i is how many bytes are readable at *p, not necessarily the size of the + * session: trailing bytes are ignored, and *p is advanced to the end of the + * session decoded, so sessions stored back to back decode with repeated + * calls. Passing in too few bytes will cause an error. */ WOLFSSL_SESSION* wolfSSL_d2i_SSL_SESSION(WOLFSSL_SESSION** sess, const unsigned char** p, long i) @@ -3354,7 +3362,11 @@ WOLFSSL_SESSION* wolfSSL_d2i_SSL_SESSION(WOLFSSL_SESSION** sess, #endif #endif /* !NO_WOLFSSL_SERVER && !NO_TLS */ #endif - (void)idx; + if (i - idx < OPAQUE8_LEN) { + ret = BUFFER_ERROR; + goto end; + } + s->peerAuthOk = (data[idx++] != 0); if (sess != NULL) { wolfSSL_FreeSession(NULL, *sess); @@ -3928,6 +3940,14 @@ void SetupSession(WOLFSSL* ssl) session->haveEMS = 1; else session->haveEMS = ssl->options.haveEMS; + /* Server only: on a client these flags describe the server certificate, + * a different statement that nothing reads. A resumed connection sends no + * Certificate, so it keeps what it inherited. */ + if (!ssl->options.resuming && + (ssl->options.side == WOLFSSL_SERVER_END)) { + session->peerAuthOk = (byte)(ssl->options.havePeerCert && + ssl->options.havePeerVerify); + } #ifdef WOLFSSL_SESSION_ID_CTX /* If using compatibility layer then check for and copy over session context * id. */ diff --git a/src/tls13.c b/src/tls13.c index 437218fefa0..edb36ecd12e 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -7098,6 +7098,16 @@ static int DoPreSharedKeys(WOLFSSL* ssl, const byte* input, word32 inputSz, #endif if (ret == 0) DoClientTicketFinalize(ssl, current->it, current->sess); + /* This is the PSK path, so failNoCertxPSK excuses it. */ + if (ret == 0 && ClientAuthRequired(ssl, 1) && + #ifdef WOLFSSL_POST_HANDSHAKE_AUTH + /* Deferred, so this handshake asks for nothing. */ + !ssl->options.verifyPostHandshake && + #endif + !ssl->session->peerAuthOk) { + WOLFSSL_MSG("Ticket session lacks client auth, skipping PSK"); + ret = WOLFSSL_FATAL_ERROR; + } if (current->sess_free_cb != NULL) { current->sess_free_cb(ssl, current->sess, ¤t->sess_free_cb_ctx); @@ -13708,11 +13718,11 @@ int DoTls13Finished(WOLFSSL* ssl, const byte* input, word32* inOutIdx, WOLFSSL_START(WC_FUNC_FINISHED_DO); WOLFSSL_ENTER("DoTls13Finished"); -#if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CLIENT_AUTH) +#if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CLIENT_AUTH) && \ + !defined(NO_WOLFSSL_SERVER) /* verify the client sent certificate if required */ - if (ssl->options.side == WOLFSSL_SERVER_END && - (!ssl->options.resuming || TLS13_AFTER_HANDSHAKE(ssl)) && - (ssl->options.mutualAuth || ssl->options.failNoCert)) { + if ((!ssl->options.resuming || TLS13_AFTER_HANDSHAKE(ssl)) && + ClientAuthRequired(ssl, SSL_USING_PSK(ssl))) { #ifdef OPENSSL_COMPATIBLE_DEFAULTS if (ssl->options.isPSK && !TLS13_AFTER_HANDSHAKE(ssl)) { WOLFSSL_MSG("TLS v1.3 client used PSK but cert required. Allowing " @@ -18378,13 +18388,14 @@ int wolfSSL_accept_TLSv13(WOLFSSL* ssl) case TLS13_ACCEPT_FINISHED_DONE : /* SERVER: When not resuming and verifying peer but no certificate - * received and not failing when not received then peer auth good. + * received and none was required then peer auth good. */ if (!ssl->options.resuming && ssl->options.verifyPeer && #ifdef WOLFSSL_POST_HANDSHAKE_AUTH !ssl->options.verifyPostHandshake && #endif - !ssl->options.havePeerCert && !ssl->options.failNoCert) { + !ssl->options.havePeerCert && + !ClientAuthRequired(ssl, SSL_USING_PSK(ssl))) { ssl->options.peerAuthGood = 1; } /* SERVER: check peer authentication. */ diff --git a/tests/api.c b/tests/api.c index 182c5ab7996..0750b4eced9 100644 --- a/tests/api.c +++ b/tests/api.c @@ -23089,6 +23089,76 @@ static int test_wolfSSL_sigalg_info(void) return EXPECT_RESULT(); } +/* peerAuthOk is the last field, so a blob written before it existed is simply + * short and an older reader ignores the trailing byte. */ +static int test_wolfSSL_i2d_SSL_SESSION_peer_auth(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && defined(HAVE_EXT_CACHE) && \ + !defined(NO_SESSION_CACHE) + WOLFSSL_SESSION* sess = NULL; + WOLFSSL_SESSION* restored = NULL; + unsigned char* der = NULL; + const unsigned char* ptr = NULL; + unsigned char* pp = NULL; + int sz = 0; + + ExpectNotNull(sess = wolfSSL_SESSION_new()); + if (sess != NULL) { + sess->peerAuthOk = 1; + sess->isSetup = 1; + } + ExpectIntGT((sz = wolfSSL_i2d_SSL_SESSION(sess, NULL)), 0); + /* Two sessions back to back, then slack, to exercise the length contract. */ + ExpectNotNull(der = (unsigned char*)XMALLOC((size_t)(2 * sz) + 8, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + if (der != NULL) { + XMEMSET(der, 0xAA, (size_t)(2 * sz) + 8); + pp = der; + ExpectIntGT(wolfSSL_i2d_SSL_SESSION(sess, &pp), 0); + /* i2d does not advance *p, so place the second one by hand. */ + pp = der + sz; + ExpectIntGT(wolfSSL_i2d_SSL_SESSION(sess, &pp), 0); + } + + /* Round trip keeps it, and consumes exactly the session. */ + ptr = der; + ExpectNotNull(restored = wolfSSL_d2i_SSL_SESSION(NULL, &ptr, (long)sz)); + if (restored != NULL) + ExpectIntEQ(restored->peerAuthOk, 1); + ExpectPtrEq(ptr, der + sz); + wolfSSL_SESSION_free(restored); + restored = NULL; + + /* Too few bytes is an error, not a partial import. */ + ptr = der; + ExpectNull(wolfSSL_d2i_SSL_SESSION(NULL, &ptr, (long)sz - 1)); + + /* More bytes than the session is allowed: the extra is ignored, the field + * survives and *p stops at the end of the session. */ + ptr = der; + ExpectNotNull(restored = wolfSSL_d2i_SSL_SESSION(NULL, &ptr, (long)sz + 8)); + if (restored != NULL) + ExpectIntEQ(restored->peerAuthOk, 1); + ExpectPtrEq(ptr, der + sz); + wolfSSL_SESSION_free(restored); + restored = NULL; + + /* So the second of two stored back to back decodes from where the first + * one left off. */ + ExpectNotNull(restored = wolfSSL_d2i_SSL_SESSION(NULL, &ptr, + (long)(der + (2 * sz) - ptr))); + if (restored != NULL) + ExpectIntEQ(restored->peerAuthOk, 1); + ExpectPtrEq(ptr, der + (2 * sz)); + wolfSSL_SESSION_free(restored); + + XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); + wolfSSL_SESSION_free(sess); +#endif + return EXPECT_RESULT(); +} + static int test_wolfSSL_d2i_SSL_SESSION_bounds_check(void) { EXPECT_DECLS; @@ -24886,11 +24956,14 @@ static int test_wolfSSL_verify_mode(void) wolfSSL_set_verify(ssl, SSL_VERIFY_NONE, 0); ExpectIntEQ(SSL_get_verify_mode(ssl), SSL_VERIFY_NONE); + /* Either fail-on-no-cert flag implies SSL_VERIFY_PEER. */ wolfSSL_set_verify(ssl, SSL_VERIFY_FAIL_IF_NO_PEER_CERT, 0); - ExpectIntEQ(SSL_get_verify_mode(ssl), SSL_VERIFY_FAIL_IF_NO_PEER_CERT); + ExpectIntEQ(SSL_get_verify_mode(ssl), + SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT); wolfSSL_set_verify(ssl, SSL_VERIFY_FAIL_EXCEPT_PSK, 0); - ExpectIntEQ(SSL_get_verify_mode(ssl), SSL_VERIFY_FAIL_EXCEPT_PSK); + ExpectIntEQ(SSL_get_verify_mode(ssl), + SSL_VERIFY_PEER | SSL_VERIFY_FAIL_EXCEPT_PSK); #if defined(WOLFSSL_TLS13) && defined(WOLFSSL_POST_HANDSHAKE_AUTH) wolfSSL_set_verify(ssl, SSL_VERIFY_POST_HANDSHAKE, 0); @@ -46170,6 +46243,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_wolfSSL_ciphersuite_auth), TEST_DECL(test_wolfSSL_sigalg_info), /* Can't memory test as tcp_connect aborts. */ + TEST_DECL(test_wolfSSL_i2d_SSL_SESSION_peer_auth), TEST_DECL(test_wolfSSL_d2i_SSL_SESSION_bounds_check), TEST_DECL(test_wolfSSL_sk_GENERAL_NAME), TEST_DECL(test_wolfSSL_sk_GENERAL_NAME_new_null), diff --git a/tests/api/test_ssl_cert.c b/tests/api/test_ssl_cert.c index 68dcedca73a..5f003929448 100644 --- a/tests/api/test_ssl_cert.c +++ b/tests/api/test_ssl_cert.c @@ -1773,10 +1773,12 @@ static const VerifyModeCase verifyModeCases[] = { WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT | WOLFSSL_VERIFY_FAIL_EXCEPT_PSK, 0, 1, 1, 1 }, - /* FAIL_IF_NO_PEER_CERT on its own is recorded even though a server that - * never requests a certificate cannot act on it. */ + /* Either fail-on-no-cert flag on its own turns verifyPeer on: failing + * without a certificate only means anything when one was asked for. */ { "FAIL_IF_NO_PEER_CERT", WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, - 0, 0, 1, 0 }, + 0, 1, 1, 0 }, + { "FAIL_EXCEPT_PSK", WOLFSSL_VERIFY_FAIL_EXCEPT_PSK, + 0, 1, 0, 1 }, /* VERIFY_DEFAULT clears every option instead of setting one. That is * why it does not round-trip through get_verify_mode(). */ { "DEFAULT", WOLFSSL_VERIFY_DEFAULT, 0, 0, 0, 0 }, @@ -2337,9 +2339,8 @@ int test_wolfSSL_verify_none_server_no_request(void) * without WOLFSSL_VERIFY_PEER still refuses a client that has no * certificate. * - * The CertificateRequest is sent only for WOLFSSL_VERIFY_PEER, so nothing - * asks the client for a certificate and nothing notices it is missing until - * DoTls13Finished checks at the end of the handshake. + * The flag implies WOLFSSL_VERIFY_PEER, so the CertificateRequest is sent and + * the client answers with an empty Certificate, which is then refused. * * @return TEST_SUCCESS on success. */ @@ -2357,15 +2358,16 @@ int test_wolfSSL_verify_tls13_failnocert_only(void) wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); - ExpectIntEQ(ssl_s->options.verifyPeer, 0); + ExpectIntEQ(ssl_s->options.verifyPeer, 1); ExpectIntEQ(ssl_s->options.failNoCert, 1); 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(NO_PEER_CERT)); - /* The client was never asked, so it never sent one. */ - ExpectIntEQ(ssl_s->msgsReceived.got_certificate, 0); + /* The client was asked, and answered with an empty Certificate. */ + ExpectIntEQ(ssl_s->msgsReceived.got_certificate, 1); + ExpectIntEQ(ssl_s->options.havePeerCert, 0); wolfSSL_free(ssl_c); wolfSSL_free(ssl_s); diff --git a/tests/api/test_tls.c b/tests/api/test_tls.c index 64838085ba4..0ad9f42f447 100644 --- a/tests/api/test_tls.c +++ b/tests/api/test_tls.c @@ -3499,30 +3499,765 @@ int test_tls12_clear_resets_options(void) return EXPECT_RESULT(); } -/* useTicket is per-handshake: an object reused with wolfSSL_clear() must not - * carry it into the next ClientHello, or the retained session is resumed for - * a client that never held it. The id offered below is dropped from the cache, - * so only stale state could resume it. */ +/* mutual_auth is an alternative to failNoCert in the full handshake, so + * resumption has to agree. */ +int test_tls12_resume_ticket_mutual_auth(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(WOLFSSL_NO_TLS12) && defined(HAVE_SESSION_TICKET) && \ + !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && !defined(NO_CERTS) && \ + !defined(WOLFSSL_NO_CLIENT_AUTH) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL, *ssl_c2 = NULL, *ssl_s2 = NULL; + WOLFSSL_SESSION* sess = 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_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c), WOLFSSL_SUCCESS); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_NONE, NULL); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c2, &ssl_s2, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c2), WOLFSSL_SUCCESS); + wolfSSL_set_verify(ssl_s2, WOLFSSL_VERIFY_PEER, NULL); + ExpectIntEQ(wolfSSL_mutual_auth(ssl_s2, 1), 0); + ExpectIntEQ(wolfSSL_set_session(ssl_c2, sess), WOLFSSL_SUCCESS); + test_memio_do_handshake(ssl_c2, ssl_s2, 20, NULL); + ExpectIntEQ(wolfSSL_session_reused(ssl_s2), 0); + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_free(ssl_c2); + wolfSSL_free(ssl_s2); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* A post-handshake-auth server records no outcome, so it must still resume. */ +int test_tls13_resume_psk_post_handshake_auth(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TLS13) && \ + defined(WOLFSSL_POST_HANDSHAKE_AUTH) && defined(HAVE_SESSION_TICKET) && \ + !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && !defined(NO_SESSION_CACHE) && \ + !defined(NO_CERTS) && !defined(NO_RSA) && \ + !defined(WOLFSSL_NO_CLIENT_AUTH) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL_SESSION* sess = NULL; + struct test_memio_ctx test_ctx; + byte readBuf[16]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + 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_load_verify_locations(ctx_s, cliCertFile, NULL), + WOLFSSL_SUCCESS); + 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_s, WOLFSSL_VERIFY_PEER | + WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT | WOLFSSL_VERIFY_POST_HANDSHAKE, + NULL); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + 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)); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->session->peerAuthOk, 0); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_PEER | + WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT | WOLFSSL_VERIFY_POST_HANDSHAKE, + NULL); + ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_session_reused(ssl_s), 1); + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* TLS 1.3: a PSK must not stand in for the required client certificate. */ +int test_tls13_resume_psk_client_auth(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TLS13) && \ + defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + !defined(NO_SESSION_CACHE) && !defined(NO_CERTS) && \ + !defined(WOLFSSL_NO_CLIENT_AUTH) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL_SESSION* sess = NULL; + struct test_memio_ctx test_ctx; + 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_s, WOLFSSL_VERIFY_NONE, NULL); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + /* Drives the NewSessionTicket onto the client session. */ + 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)); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->session->peerAuthOk, 0); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + wolfSSL_set_verify(ssl_s, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS); + /* The skipped PSK becomes a full handshake. */ + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + ExpectIntEQ(wolfSSL_session_reused(ssl_s), 0); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->error, WC_NO_ERR_TRACE(NO_PEER_CERT)); + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* A session that did authenticate the client must still resume. */ +int test_tls13_resume_psk_client_auth_ok(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TLS13) && \ + defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + !defined(NO_SESSION_CACHE) && !defined(NO_CERTS) && !defined(NO_RSA) && \ + !defined(WOLFSSL_NO_CLIENT_AUTH) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL_SESSION* sess = NULL; + struct test_memio_ctx test_ctx; + byte readBuf[16]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + /* CTXs first: credentials must precede the objects inheriting them. */ + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + 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_load_verify_locations(ctx_s, cliCertFile, NULL), + WOLFSSL_SUCCESS); + 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_s, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + 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)); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->session->peerAuthOk, 1); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + wolfSSL_set_verify(ssl_s, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_session_reused(ssl_s), 1); + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* The outcome has to survive an i2d/d2i external cache. No internal cache and + * no ticket, so only the deserialized session can carry it. */ +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(WOLFSSL_NO_TLS12) && defined(OPENSSL_EXTRA) && \ + defined(HAVE_EXT_CACHE) && !defined(NO_SESSION_CACHE) && \ + !defined(NO_CERTS) && !defined(NO_RSA) && \ + !defined(WOLFSSL_NO_CLIENT_AUTH) +static byte test_extcache_der[2048]; +static int test_extcache_derSz = 0; +static int test_extcache_gets = 0; + +static int test_extcache_new_cb(WOLFSSL* ssl, WOLFSSL_SESSION* sess) +{ + unsigned char* p = test_extcache_der; + int sz; + + (void)ssl; + sz = wolfSSL_i2d_SSL_SESSION(sess, NULL); + if ((sz > 0) && (sz <= (int)sizeof(test_extcache_der))) { + sz = wolfSSL_i2d_SSL_SESSION(sess, &p); + if (sz > 0) + test_extcache_derSz = sz; + } + return 0; +} + +static WOLFSSL_SESSION* test_extcache_get_cb(WOLFSSL* ssl, + const unsigned char* id, int len, int* ref) +{ + const unsigned char* p = test_extcache_der; + + (void)ssl; + (void)id; + (void)len; + test_extcache_gets++; + /* A fresh object each time, so wolfSSL owns it. */ + *ref = 0; + if (test_extcache_derSz <= 0) + return NULL; + return wolfSSL_d2i_SSL_SESSION(NULL, &p, (long)test_extcache_derSz); +} +#endif + +int test_tls12_ext_cache_client_auth_resume(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(WOLFSSL_NO_TLS12) && defined(OPENSSL_EXTRA) && \ + defined(HAVE_EXT_CACHE) && !defined(NO_SESSION_CACHE) && \ + !defined(NO_CERTS) && !defined(NO_RSA) && \ + !defined(WOLFSSL_NO_CLIENT_AUTH) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL *ssl_c2 = NULL, *ssl_s2 = NULL; + WOLFSSL_SESSION* sess = NULL; + struct test_memio_ctx test_ctx; + + test_extcache_derSz = 0; + test_extcache_gets = 0; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + /* CTXs first: credentials must precede the objects inheriting them. */ + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + 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_load_verify_locations(ctx_s, cliCertFile, NULL), + WOLFSSL_SUCCESS); + /* Only the external cache answers. */ + if (ctx_s != NULL) { + wolfSSL_CTX_set_session_cache_mode(ctx_s, + WOLFSSL_SESS_CACHE_SERVER | WOLFSSL_SESS_CACHE_NO_INTERNAL); + wolfSSL_CTX_sess_set_new_cb(ctx_s, test_extcache_new_cb); + wolfSSL_CTX_sess_set_get_cb(ctx_s, test_extcache_get_cb); + } + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + wolfSSL_set_verify(ssl_s, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->session->peerAuthOk, 1); + ExpectIntGT(test_extcache_derSz, 0); + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); + + /* The server can only get it back through d2i. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c2, &ssl_s2, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + wolfSSL_set_verify(ssl_s2, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + ExpectIntEQ(wolfSSL_set_session(ssl_c2, sess), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c2, ssl_s2, 10, NULL), 0); + ExpectIntGT(test_extcache_gets, 0); + ExpectIntEQ(wolfSSL_session_reused(ssl_s2), 1); + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_free(ssl_c2); + wolfSSL_free(ssl_s2); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* A ticket minted under VERIFY_NONE, replayed on the same CTX against a + * connection requiring a client certificate, must be declined. */ +int test_tls12_resume_ticket_client_auth(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(WOLFSSL_NO_TLS12) && defined(HAVE_SESSION_TICKET) && \ + !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && !defined(NO_CERTS) && \ + !defined(WOLFSSL_NO_CLIENT_AUTH) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL *ssl_c2 = NULL, *ssl_s2 = NULL; + WOLFSSL_SESSION* sess = 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_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c), WOLFSSL_SUCCESS); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_NONE, NULL); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); + ExpectIntGT(sess->ticketLen, 0); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->session->peerAuthOk, 0); + + /* Same CTX, so the ticket decrypts. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c2, &ssl_s2, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c2), WOLFSSL_SUCCESS); + wolfSSL_set_verify(ssl_s2, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + ExpectIntEQ(wolfSSL_set_session(ssl_c2, sess), WOLFSSL_SUCCESS); + + /* The decline becomes a full handshake. */ + ExpectIntNE(test_memio_do_handshake(ssl_c2, ssl_s2, 20, NULL), 0); + ExpectIntEQ(wolfSSL_session_reused(ssl_s2), 0); + if (ssl_s2 != NULL) { + ExpectIntEQ(ssl_s2->options.resuming, 0); + /* Rejected for the certificate, not accepted off the ticket. */ + ExpectIntEQ(ssl_s2->error, WC_NO_ERR_TRACE(NO_PEER_CERT)); + } + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_free(ssl_c2); + wolfSSL_free(ssl_s2); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* Which server policies require a client certificate. The client holds none, + * so only the server's policy ends the handshake. WOLFSSL_VERIFY_NONE waives + * client auth as it does in OpenSSL, and wolfSSL_mutual_auth() turns + * verifyPeer on so the certificate it requires is actually asked for. Every + * site deciding this goes through ClientAuthRequired(), so they cannot drift + * apart; see test_wolfSSL_verify_tls13_failnocert_only for the TLS 1.3 half. */ +int test_tls12_client_auth_flags_enforced(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(WOLFSSL_NO_TLS12) && !defined(NO_CERTS) && \ + !defined(WOLFSSL_NO_CLIENT_AUTH) && !defined(NO_RSA) + int i; + + for (i = 0; i < 6; i++) { + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int wantCert = (i < 4); + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), + 0); + switch (i) { + case 0: + ExpectIntEQ(wolfSSL_mutual_auth(ssl_s, 1), 0); + /* The API states the whole requirement in the verify mode: + * ask for the certificate, and fail without it. */ + if (ssl_s != NULL) { + ExpectIntEQ(ssl_s->options.verifyPeer, 1); + ExpectIntEQ(ssl_s->options.verifyNone, 0); + ExpectIntEQ(ssl_s->options.failNoCert, 1); + } +#if defined(OPENSSL_ALL) || defined(OPENSSL_EXTRA) || defined(HAVE_STUNNEL) || \ + defined(WOLFSSL_NGINX) || defined(WOLFSSL_HAPROXY) || defined(HAVE_LIGHTY) + ExpectIntEQ(wolfSSL_get_verify_mode(ssl_s), + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT); +#endif + break; + case 1: + wolfSSL_set_verify(ssl_s, + WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + break; + case 2: + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_PEER | + WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + break; + case 3: + /* Not a PSK suite, so the PSK exemption does not apply. */ + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_PEER | + WOLFSSL_VERIFY_FAIL_EXCEPT_PSK, NULL); + break; + case 4: + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_NONE, NULL); + break; + default: + /* An explicit WOLFSSL_VERIFY_NONE afterwards waives it. */ + ExpectIntEQ(wolfSSL_mutual_auth(ssl_s, 1), 0); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_NONE, NULL); + break; + } + + if (wantCert) { + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->error, WC_NO_ERR_TRACE(NO_PEER_CERT)); + } + else { + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->error, 0); + } + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->options.havePeerCert, 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + } +#endif + return EXPECT_RESULT(); +} + +/* failNoCertxPSK requires a client certificate on a non-PSK suite, so a ticket + * minted without client auth must not resume into it either. The suite judged + * is the resumed session's, which is why the gate is given it explicitly. */ +int test_tls12_resume_ticket_fail_except_psk(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(WOLFSSL_NO_TLS12) && defined(HAVE_SESSION_TICKET) && \ + !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && !defined(NO_CERTS) && \ + !defined(WOLFSSL_NO_CLIENT_AUTH) && !defined(NO_RSA) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL *ssl_c2 = NULL, *ssl_s2 = NULL; + WOLFSSL_SESSION* sess = 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_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c), WOLFSSL_SUCCESS); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_NONE, NULL); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); + ExpectIntGT(sess->ticketLen, 0); + + /* Same CTX, so the ticket decrypts. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c2, &ssl_s2, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c2), WOLFSSL_SUCCESS); + wolfSSL_set_verify(ssl_s2, WOLFSSL_VERIFY_PEER | + WOLFSSL_VERIFY_FAIL_EXCEPT_PSK, NULL); + ExpectIntEQ(wolfSSL_set_session(ssl_c2, sess), WOLFSSL_SUCCESS); + /* Premise: the exempting flag is the only one set. */ + if (ssl_s2 != NULL) { + ExpectIntEQ(ssl_s2->options.failNoCertxPSK, 1); + ExpectIntEQ(ssl_s2->options.failNoCert, 0); + ExpectIntEQ(ssl_s2->options.mutualAuth, 0); + } + + ExpectIntNE(test_memio_do_handshake(ssl_c2, ssl_s2, 20, NULL), 0); + ExpectIntEQ(wolfSSL_session_reused(ssl_s2), 0); + if (ssl_s2 != NULL) { + ExpectIntEQ(ssl_s2->options.resuming, 0); + ExpectIntEQ(ssl_s2->options.peerAuthGood, 0); + } + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_free(ssl_c2); + wolfSSL_free(ssl_s2); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* An empty client Certificate is rejected at the Certificate message from + * TLS 1.2 on, but before that the rejection is left to the later + * DoClientKeyExchange check, so the server reads one more message first. Both + * end in NO_PEER_CERT, so only the message count shows which fired. */ +int test_tls1x_empty_cert_reject_point(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(WOLFSSL_NO_TLS12) && !defined(NO_OLD_TLS) && \ + !defined(NO_CERTS) && !defined(WOLFSSL_NO_CLIENT_AUTH) && \ + !defined(NO_RSA) && !defined(NO_SHA) + int i; + + for (i = 0; i < 2; i++) { + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *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, + i ? wolfTLSv1_2_client_method + : wolfTLSv1_1_client_method, + i ? wolfTLSv1_2_server_method + : wolfTLSv1_1_server_method), 0); + /* verifyPeer so the CertificateRequest is sent; the client holds no + * certificate, so it answers with an empty one. */ + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_PEER | + WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + if (ssl_s != NULL) { + ExpectIntEQ(ssl_s->error, WC_NO_ERR_TRACE(NO_PEER_CERT)); + ExpectIntEQ(ssl_s->msgsReceived.got_certificate, 1); + /* TLS 1.2 stops at the Certificate; TLS 1.1 reads on. */ + ExpectIntEQ(ssl_s->msgsReceived.got_client_key_exchange, !i); + } + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + } +#endif + return EXPECT_RESULT(); +} + +/* verifyPostHandshake defers when the certificate is demanded, so the + * enclosing handshake sends no CertificateRequest and completes without one. + * It must not also excuse an empty Certificate that arrives anyway: nothing + * rejects an unsolicited one before ProcessPeerCerts, so its empty-cert rule + * has to stay independent of the deferral. */ +int test_tls13_pha_unsolicited_blank_cert(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TLS13) && \ + defined(WOLFSSL_POST_HANDSHAKE_AUTH) && !defined(NO_CERTS) && \ + !defined(WOLFSSL_NO_CLIENT_AUTH) && !defined(NO_RSA) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *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_s, WOLFSSL_VERIFY_PEER | + WOLFSSL_VERIFY_POST_HANDSHAKE | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, + NULL); + /* No conforming client sends this, so ask for it directly. */ + if (ssl_c != NULL) + ssl_c->options.sendVerify = SEND_BLANK_CERT; + + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + if (ssl_s != NULL) { + /* It reached the rule rather than being refused earlier. */ + ExpectIntEQ(ssl_s->msgsReceived.got_certificate, 1); + ExpectIntEQ(ssl_s->error, WC_NO_ERR_TRACE(NO_PEER_CERT)); + ExpectIntEQ(ssl_s->options.havePeerCert, 0); + } + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* verifyPostHandshake defers the request only in TLS 1.3. A TLS 1.2 full + * handshake still demands the certificate, so resumption must not read the + * flag as a waiver. */ +int test_tls12_resume_ticket_post_handshake_auth(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(WOLFSSL_NO_TLS12) && defined(HAVE_SESSION_TICKET) && \ + !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && !defined(NO_CERTS) && \ + !defined(WOLFSSL_NO_CLIENT_AUTH) && defined(WOLFSSL_TLS13) && \ + defined(WOLFSSL_POST_HANDSHAKE_AUTH) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL *ssl_c2 = NULL, *ssl_s2 = NULL; + WOLFSSL_SESSION* sess = 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_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c), WOLFSSL_SUCCESS); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_NONE, NULL); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); + ExpectIntGT(sess->ticketLen, 0); + + /* Same CTX, so the ticket decrypts. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c2, &ssl_s2, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c2), WOLFSSL_SUCCESS); + wolfSSL_set_verify(ssl_s2, WOLFSSL_VERIFY_PEER | + WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT | WOLFSSL_VERIFY_POST_HANDSHAKE, + NULL); + ExpectIntEQ(wolfSSL_set_session(ssl_c2, sess), WOLFSSL_SUCCESS); + /* Premise: the flag really is set on a TLS 1.2 connection. */ + if (ssl_s2 != NULL) + ExpectIntEQ(ssl_s2->options.verifyPostHandshake, 1); + + /* The decline becomes a full handshake. */ + ExpectIntNE(test_memio_do_handshake(ssl_c2, ssl_s2, 20, NULL), 0); + ExpectIntEQ(wolfSSL_session_reused(ssl_s2), 0); + if (ssl_s2 != NULL) { + ExpectIntEQ(ssl_s2->options.resuming, 0); + ExpectIntEQ(ssl_s2->error, WC_NO_ERR_TRACE(NO_PEER_CERT)); + } + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_free(ssl_c2); + wolfSSL_free(ssl_s2); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* A client that does present its certificate must still abbreviate. */ +int test_tls12_resume_ticket_client_auth_ok(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(WOLFSSL_NO_TLS12) && defined(HAVE_SESSION_TICKET) && \ + !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && !defined(NO_CERTS) && \ + !defined(NO_RSA) && \ + !defined(WOLFSSL_NO_CLIENT_AUTH) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL *ssl_c2 = NULL, *ssl_s2 = NULL; + WOLFSSL_SESSION* sess = NULL; + struct test_memio_ctx test_ctx; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + /* CTXs first: credentials must precede the objects inheriting them. */ + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + 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_load_verify_locations(ctx_s, cliCertFile, NULL), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c), WOLFSSL_SUCCESS); + wolfSSL_set_verify(ssl_s, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->session->peerAuthOk, 1); + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c2, &ssl_s2, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c2), WOLFSSL_SUCCESS); + wolfSSL_set_verify(ssl_s2, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + ExpectIntEQ(wolfSSL_set_session(ssl_c2, sess), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c2, ssl_s2, 10, NULL), 0); + ExpectIntEQ(wolfSSL_session_reused(ssl_s2), 1); + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_free(ssl_c2); + wolfSSL_free(ssl_s2); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* useTicket and peerAuthOk are per-handshake: an object reused with + * wolfSSL_clear() must not carry either into the next ClientHello, or the + * retained session is resumed for a client that never held it. The id offered + * below is dropped from the cache, so only stale state could resume it. */ int test_tls12_reuse_clears_use_ticket(void) { EXPECT_DECLS; #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ !defined(WOLFSSL_NO_TLS12) && defined(HAVE_SESSION_TICKET) && \ - !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && !defined(NO_SESSION_CACHE) + !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && !defined(NO_SESSION_CACHE) && \ + !defined(NO_CERTS) && !defined(NO_RSA) WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL, *ctx_s2 = NULL; WOLFSSL *ssl_c = NULL, *ssl_c2 = NULL, *ssl_c3 = NULL; WOLFSSL *ssl_s = NULL, *ssl_s2 = NULL; WOLFSSL_SESSION* sess = NULL; struct test_memio_ctx test_ctx; int useTicketAfterClear = -1; + int peerAuthAfterClear = -1; XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + /* CTXs first: credentials must precede the objects inheriting them. */ + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + 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_load_verify_locations(ctx_s, cliCertFile, NULL), + WOLFSSL_SUCCESS); ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c), WOLFSSL_SUCCESS); + wolfSSL_set_verify(ssl_s, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); - if (ssl_s != NULL) + if (ssl_s != NULL) { ExpectIntEQ(ssl_s->options.useTicket, 1); +#ifndef WOLFSSL_NO_CLIENT_AUTH + ExpectIntEQ(ssl_s->session->peerAuthOk, 1); +#endif + } + /* A different statement on the client, and unread there. */ + if (ssl_c != NULL) + ExpectIntEQ(ssl_c->session->peerAuthOk, 0); /* From a server issuing no tickets, so the next ClientHello carries a * session id only. Not modified in place: it may be cache-backed. */ @@ -3538,8 +4273,10 @@ int test_tls12_reuse_clears_use_ticket(void) ExpectIntEQ(wolfSSL_clear(ssl_s), WOLFSSL_SUCCESS); /* Assert last, or a failure here skips the behavioural half. */ - if (ssl_s != NULL) + if (ssl_s != NULL) { useTicketAfterClear = ssl_s->options.useTicket; + peerAuthAfterClear = ssl_s->session->peerAuthOk; + } /* Point the reused server at the new transport. */ XMEMSET(&test_ctx, 0, sizeof(test_ctx)); @@ -3557,6 +4294,7 @@ int test_tls12_reuse_clears_use_ticket(void) if (ssl_s != NULL) ExpectIntEQ(ssl_s->options.resuming, 0); ExpectIntEQ(useTicketAfterClear, 0); + ExpectIntEQ(peerAuthAfterClear, 0); wolfSSL_SESSION_free(sess); wolfSSL_free(ssl_c); diff --git a/tests/api/test_tls.h b/tests/api/test_tls.h index db032ab46ba..abf80315639 100644 --- a/tests/api/test_tls.h +++ b/tests/api/test_tls.h @@ -62,6 +62,18 @@ int test_tls_version_error_alert_mapping(void); int test_tls12_etm_failed_resumption(void); int test_tls12_resume_ticket_wrong_suite(void); int test_tls12_clear_resets_options(void); +int test_tls12_resume_ticket_mutual_auth(void); +int test_tls13_resume_psk_post_handshake_auth(void); +int test_tls13_resume_psk_client_auth(void); +int test_tls13_resume_psk_client_auth_ok(void); +int test_tls12_ext_cache_client_auth_resume(void); +int test_tls12_resume_ticket_client_auth(void); +int test_tls12_resume_ticket_post_handshake_auth(void); +int test_tls12_client_auth_flags_enforced(void); +int test_tls12_resume_ticket_fail_except_psk(void); +int test_tls1x_empty_cert_reject_point(void); +int test_tls13_pha_unsolicited_blank_cert(void); +int test_tls12_resume_ticket_client_auth_ok(void); int test_tls12_reuse_clears_use_ticket(void); int test_tls12_resume_ticket_decline_fallback(void); int test_tls12_ticket_dropped_on_bad_finished(void); @@ -145,6 +157,19 @@ int test_tls_param_flags_crl_check(void); TEST_DECL_GROUP("tls", test_tls12_etm_failed_resumption), \ TEST_DECL_GROUP("tls", test_tls12_resume_ticket_wrong_suite), \ TEST_DECL_GROUP("tls", test_tls12_clear_resets_options), \ + TEST_DECL_GROUP("tls", test_tls12_resume_ticket_mutual_auth), \ + TEST_DECL_GROUP("tls", test_tls13_resume_psk_post_handshake_auth), \ + TEST_DECL_GROUP("tls", test_tls13_resume_psk_client_auth), \ + TEST_DECL_GROUP("tls", test_tls13_resume_psk_client_auth_ok), \ + TEST_DECL_GROUP("tls", test_tls12_ext_cache_client_auth_resume), \ + TEST_DECL_GROUP("tls", test_tls12_resume_ticket_client_auth), \ + TEST_DECL_GROUP("tls", \ + test_tls12_resume_ticket_post_handshake_auth), \ + TEST_DECL_GROUP("tls", test_tls12_client_auth_flags_enforced), \ + TEST_DECL_GROUP("tls", test_tls12_resume_ticket_fail_except_psk), \ + TEST_DECL_GROUP("tls", test_tls1x_empty_cert_reject_point), \ + TEST_DECL_GROUP("tls", test_tls13_pha_unsolicited_blank_cert), \ + TEST_DECL_GROUP("tls", test_tls12_resume_ticket_client_auth_ok), \ TEST_DECL_GROUP("tls", test_tls12_reuse_clears_use_ticket), \ TEST_DECL_GROUP("tls", test_tls12_resume_ticket_decline_fallback), \ TEST_DECL_GROUP("tls", test_tls12_ticket_dropped_on_bad_finished), \ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 991a818a541..7e8b3d67286 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -2418,6 +2418,21 @@ WOLFSSL_LOCAL int CookiePolicySet(WOLFSSL* ssl, const byte* hrrSecret, WOLFSSL_LOCAL int CookiePolicyEnable(WOLFSSL* ssl); WOLFSSL_LOCAL int CheckCookieState(WOLFSSL* ssl); #endif +/* Is this connection authenticating with a PSK? TLS 1.3 uses the extension, + * earlier versions a PSK cipher suite. */ +#if defined(WOLFSSL_TLS13) && (defined(HAVE_SESSION_TICKET) || \ + !defined(NO_PSK)) + #define SSL_USING_PSK(ssl) \ + ((ssl)->options.usingPSK_cipher || (ssl)->options.pskNegotiated) +#else + #define SSL_USING_PSK(ssl) ((ssl)->options.usingPSK_cipher) +#endif +#if !defined(NO_CERTS) && !defined(NO_WOLFSSL_SERVER) && !defined(NO_TLS) +WOLFSSL_LOCAL int ClientAuthRequired(const WOLFSSL* ssl, int usingPsk); +#else +/* Only a TLS server asks for a certificate, so only it can require one. */ +#define ClientAuthRequired(ssl, usingPsk) 0 +#endif WOLFSSL_LOCAL int DoClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, word32 helloSz); #ifdef WOLFSSL_TLS13 @@ -4008,12 +4023,20 @@ WOLFSSL_LOCAL int SetupClientSecureRenegotiation(WOLFSSL* ssl); /* Our ticket format. All members need to be a byte or array of byte to * avoid alignment issues */ +/* Bits of InternalTicket.flags. Bit 0 is where this byte has always held + * haveEMS, so sizeof(InternalTicket) and every offset are unchanged and an + * older ticket parses with the newer bits clear. An older peer handed a ticket + * carrying bit 1 copies the whole byte into haveEMS and fails the handshake + * cleanly, so a shared TLS 1.2 ticket key needs matching versions. */ +#define WOLFSSL_TICKET_FLAG_EMS 0x01 +#define WOLFSSL_TICKET_FLAG_PEER_AUTH 0x02 + typedef struct InternalTicket { ProtocolVersion pv; /* version when ticket created */ byte suite[SUITE_LEN]; /* cipher suite when created */ byte msecret[SECRET_LEN]; /* master secret */ byte timestamp[TIMESTAMP_LEN]; /* born on */ - byte haveEMS; /* have extended master secret */ + byte flags; /* WOLFSSL_TICKET_FLAG_* */ #ifdef WOLFSSL_TLS13 byte ageAdd[AGEADD_LEN]; /* Obfuscation of age */ byte namedGroup[NAMEDGROUP_LEN]; /* Named group used */ @@ -5417,6 +5440,9 @@ struct WOLFSSL_SESSION { * WOLFSSL_MFL_2_8 (6) */ #endif /* HAVE_MAX_FRAGMENT */ byte isSetup:1; + /* Client presented a certificate and proved key possession. Server + * sessions only; shares isSetup's byte so the layout is unchanged. */ + byte peerAuthOk:1; }; #if defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) && \ From 0ad347ec2a941a0682ed8405630676edc09e423a Mon Sep 17 00:00:00 2001 From: Kareem Date: Thu, 15 Oct 2026 16:31:13 -0700 Subject: [PATCH 03/11] Refactor peerAuthOk and haveEMS into bitfields. Fix peerAuthOK logic around resumption and PHA. Fix PSK determination logic. Ensure PSK/anon suite options are fully reset between connections and in wolfSSL_clear. Don't set failNoCert in _mutual_auth functions as it is redundant and wouldn't get reset on clear. Add tests. --- src/internal.c | 30 ++-- src/keys.c | 10 +- src/ssl.c | 24 ++- src/ssl_api_cert.c | 8 +- src/ssl_sess.c | 16 +- tests/api/test_ssl_cert.c | 11 ++ tests/api/test_tls.c | 305 ++++++++++++++++++++++++++++++++++++-- tests/api/test_tls.h | 4 + wolfssl/internal.h | 16 +- 9 files changed, 350 insertions(+), 74 deletions(-) diff --git a/src/internal.c b/src/internal.c index 8cd9ebc288a..5a47a191cac 100644 --- a/src/internal.c +++ b/src/internal.c @@ -42767,8 +42767,9 @@ int ClientAuthRequired(const WOLFSSL* ssl, int usingPsk) ) { int secretSz = SECRET_LEN; /* The secret comes from the callback, and nothing records an auth - * outcome for it. */ - if (ClientAuthRequired(ssl, ssl->options.usingPSK_cipher)) { + * outcome for it. The suite is not chosen yet, so no PSK exception + * applies. */ + if (ClientAuthRequired(ssl, 0)) { WOLFSSL_MSG("Session secret callback cannot assert client auth," " do full handshake"); ssl->options.resuming = 0; @@ -44527,14 +44528,10 @@ int ClientAuthRequired(const WOLFSSL* ssl, int usingPsk) it->suite[0] = ssl->options.cipherSuite0; it->suite[1] = ssl->options.cipherSuite; - /* Both arms below only add to this, and an async re-entry may not - * have re-zeroed the buffer. A resumed connection sends no - * Certificate, so it carries what it resumed. */ - it->flags = 0; - if (ssl->options.resuming ? ssl->session->peerAuthOk : - (ssl->options.havePeerCert && ssl->options.havePeerVerify)) { - it->flags |= WOLFSSL_TICKET_FLAG_PEER_AUTH; - } + /* Post-handshake auth can add a certificate to a resumed connection, + * so fresh proof wins over what was resumed. */ + it->peerAuthOk = (byte)(SSL_PEER_CERT_VERIFIED(ssl) || + (ssl->options.resuming && ssl->session->peerAuthOk)); #ifdef WOLFSSL_EARLY_DATA c32toa(ssl->options.maxEarlyDataSz, it->maxEarlyDataSz); @@ -44550,8 +44547,7 @@ int ClientAuthRequired(const WOLFSSL* ssl, int usingPsk) #ifndef NO_ASN_TIME c32toa(LowResTimer(), it->timestamp); #endif - if (ssl->options.haveEMS) - it->flags |= WOLFSSL_TICKET_FLAG_EMS; + it->haveEMS = (byte) ssl->options.haveEMS; } else { #ifdef WOLFSSL_TLS13 @@ -45111,8 +45107,7 @@ int ClientAuthRequired(const WOLFSSL* ssl, int usingPsk) XMEMCPY(ssl->session->alpnHash, it->alpnHash, TICKET_BINDING_HASH_SZ); #endif /* After the DupSession above, which would otherwise overwrite it. */ - ssl->session->peerAuthOk = - (it->flags & WOLFSSL_TICKET_FLAG_PEER_AUTH) ? 1 : 0; + ssl->session->peerAuthOk = it->peerAuthOk; if (!IsAtLeastTLSv1_3(ssl->version)) { if (ssl->arrays == NULL) @@ -45120,8 +45115,7 @@ int ClientAuthRequired(const WOLFSSL* ssl, int usingPsk) XMEMCPY(ssl->arrays->masterSecret, it->msecret, SECRET_LEN); /* Copy the haveExtendedMasterSecret property from the ticket to * the saved session, so the property may be checked later. */ - ssl->session->haveEMS = - (it->flags & WOLFSSL_TICKET_FLAG_EMS) ? 1 : 0; + ssl->session->haveEMS = it->haveEMS; ato32((const byte*)&it->timestamp, &ssl->session->bornOn); #ifndef NO_RESUME_SUITE_CHECK ssl->session->cipherSuite0 = it->suite[0]; @@ -45202,8 +45196,8 @@ int ClientAuthRequired(const WOLFSSL* ssl, int usingPsk) c32toa((word32)(milliBornOn >> 32), it->timestamp); c32toa((word32)milliBornOn , it->timestamp + OPAQUE32_LEN); #endif - it->flags = (byte)((sess->haveEMS ? WOLFSSL_TICKET_FLAG_EMS : 0) | - (sess->peerAuthOk ? WOLFSSL_TICKET_FLAG_PEER_AUTH : 0)); + it->haveEMS = (byte)sess->haveEMS; + it->peerAuthOk = sess->peerAuthOk; c32toa(sess->ticketAdd, it->ageAdd); c16toa(sess->namedGroup, it->namedGroup); if (sess->ticketNonce.len <= MAX_TICKET_NONCE_STATIC_SZ) { diff --git a/src/keys.c b/src/keys.c index d1ab48e4f63..96f6920eafa 100644 --- a/src/keys.c +++ b/src/keys.c @@ -46,9 +46,13 @@ int SetCipherSpecs(WOLFSSL* ssl) { - int ret = GetCipherSpec(ssl->options.side, ssl->options.cipherSuite0, - ssl->options.cipherSuite, &ssl->specs, - &ssl->options); + int ret; + + /* GetCipherSpec only sets these, for the suite being set. */ + ssl->options.usingPSK_cipher = 0; + ssl->options.usingAnon_cipher = 0; + ret = GetCipherSpec(ssl->options.side, ssl->options.cipherSuite0, + ssl->options.cipherSuite, &ssl->specs, &ssl->options); if (ret == 0) { #ifdef WOLFSSL_TLS13 /* The client only checks that the ServerHello suite was offered, and diff --git a/src/ssl.c b/src/ssl.c index 1b214ce77d9..ce82642a158 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -5793,8 +5793,10 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out, return WOLFSSL_FAILURE; } - if (!ssl->options.handShakeDone) { - /* Only reset the session if we didn't complete a handshake */ + /* A client keeps the session it may resume. A server's is per + * connection, and the application may still hold it. */ + if (!ssl->options.handShakeDone || + (ssl->options.side == WOLFSSL_SERVER_END)) { wolfSSL_FreeSession(ssl->ctx, ssl->session); ssl->session = wolfSSL_NewSession(ssl->heap); if (ssl->session == NULL) { @@ -5844,6 +5846,11 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out, ssl->options.havePeerVerify = 0; ssl->options.havePeerCert = 0; ssl->options.peerAuthGood = 0; + ssl->options.usingPSK_cipher = 0; + ssl->options.usingAnon_cipher = 0; +#if defined(WOLFSSL_TLS13) && (defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)) + ssl->options.pskNegotiated = 0; +#endif ssl->options.tls1_3 = 0; ssl->options.haveSessionId = 0; ssl->options.tls = 0; @@ -5943,10 +5950,6 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out, #ifdef HAVE_SESSION_TICKET #ifdef WOLFSSL_TLS13 ssl->options.ticketsSent = 0; - #ifndef NO_WOLFSSL_SERVER - if (ssl->options.side == WOLFSSL_SERVER_END) - ssl->session->ticketNonce.len = 0; - #endif #if !defined(NO_WOLFSSL_SERVER) && \ defined(WOLFSSL_TLS13_TICKET_CHECK_PSK_MODES) /* Recorded from the ClientHello, so it must not carry into the next @@ -5962,15 +5965,6 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out, ssl->options.useTicket = 0; ssl->options.createTicket = 0; #endif - /* A server keeps its session across the reset, but the next client - * has not asked for it. A client is the side that resumes what it - * held, so it keeps both. */ - if ((ssl->options.side == WOLFSSL_SERVER_END) && - (ssl->session != NULL)) { - ssl->session->haveAltSessionID = 0; - ForceZero(ssl->session->altSessionID, ID_LEN); - ssl->session->peerAuthOk = 0; - } #ifdef WOLFSSL_EARLY_DATA ssl->earlyData = no_early_data; ssl->earlyDataSz = 0; diff --git a/src/ssl_api_cert.c b/src/ssl_api_cert.c index a941c1cae02..beb194edd6b 100644 --- a/src/ssl_api_cert.c +++ b/src/ssl_api_cert.c @@ -47,12 +47,10 @@ int wolfSSL_CTX_mutual_auth(WOLFSSL_CTX* ctx, int req) return SIDE_ERROR; ctx->mutualAuth = (byte)req; - /* Required means ask for one and fail without it: sites reading only - * failNoCert would otherwise treat it as optional. */ + /* A certificate has to be asked for to be required. */ if (req) { ctx->verifyNone = 0; ctx->verifyPeer = 1; - ctx->failNoCert = 1; } return 0; @@ -76,12 +74,10 @@ int wolfSSL_mutual_auth(WOLFSSL* ssl, int req) return SIDE_ERROR; ssl->options.mutualAuth = (word16)req; - /* Required means ask for one and fail without it: sites reading only - * failNoCert would otherwise treat it as optional. */ + /* A certificate has to be asked for to be required. */ if (req) { ssl->options.verifyNone = 0; ssl->options.verifyPeer = 1; - ssl->options.failNoCert = 1; } return 0; diff --git a/src/ssl_sess.c b/src/ssl_sess.c index bbd77b9afa0..2d7f7a5021a 100644 --- a/src/ssl_sess.c +++ b/src/ssl_sess.c @@ -2953,7 +2953,7 @@ int wolfSSL_i2d_SSL_SESSION(WOLFSSL_SESSION* sess, unsigned char** p) #endif #endif /* !NO_WOLFSSL_SERVER && !NO_TLS */ #endif - /* peerAuthOk, last so an older reader stops before it. */ + /* peerAuthOk */ size += OPAQUE8_LEN; if (p != NULL) { @@ -3941,12 +3941,14 @@ void SetupSession(WOLFSSL* ssl) else session->haveEMS = ssl->options.haveEMS; /* Server only: on a client these flags describe the server certificate, - * a different statement that nothing reads. A resumed connection sends no - * Certificate, so it keeps what it inherited. */ - if (!ssl->options.resuming && - (ssl->options.side == WOLFSSL_SERVER_END)) { - session->peerAuthOk = (byte)(ssl->options.havePeerCert && - ssl->options.havePeerVerify); + * a different statement that nothing reads. A full handshake records its + * own outcome; a resumed one keeps what it inherited unless post-handshake + * auth added a certificate. */ + if (ssl->options.side == WOLFSSL_SERVER_END) { + if (!ssl->options.resuming) + session->peerAuthOk = 0; + if (SSL_PEER_CERT_VERIFIED(ssl)) + session->peerAuthOk = 1; } #ifdef WOLFSSL_SESSION_ID_CTX /* If using compatibility layer then check for and copy over session context diff --git a/tests/api/test_ssl_cert.c b/tests/api/test_ssl_cert.c index 5f003929448..51d37f9001c 100644 --- a/tests/api/test_ssl_cert.c +++ b/tests/api/test_ssl_cert.c @@ -1030,9 +1030,20 @@ int test_wolfSSL_mutual_auth(void) ExpectIntEQ(wolfSSL_CTX_mutual_auth(ctx, 1), 0); ExpectIntEQ(wolfSSL_mutual_auth(ssl, 1), 0); + /* Requiring the certificate asks for it and sets nothing else. */ + ExpectIntEQ(ctx->mutualAuth, 1); + ExpectIntEQ(ctx->verifyPeer, 1); + ExpectIntEQ(ctx->verifyNone, 0); + ExpectIntEQ(ctx->failNoCert, 0); + ExpectIntEQ(ssl->options.mutualAuth, 1); + ExpectIntEQ(ssl->options.verifyPeer, 1); + ExpectIntEQ(ssl->options.verifyNone, 0); + ExpectIntEQ(ssl->options.failNoCert, 0); /* The setting can be turned back off. */ ExpectIntEQ(wolfSSL_CTX_mutual_auth(ctx, 0), 0); ExpectIntEQ(wolfSSL_mutual_auth(ssl, 0), 0); + ExpectIntEQ(ctx->mutualAuth, 0); + ExpectIntEQ(ssl->options.mutualAuth, 0); wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); diff --git a/tests/api/test_tls.c b/tests/api/test_tls.c index 0ad9f42f447..09e25b1d7ae 100644 --- a/tests/api/test_tls.c +++ b/tests/api/test_tls.c @@ -3417,16 +3417,41 @@ int test_tls12_resume_ticket_wrong_suite(void) return EXPECT_RESULT(); } -/* Catches per-connection Options state that wolfSSL_clear() forgets, rather - * than one field at a time. Every Options is born inside - * XMEMSET(ssl, 0, sizeof(WOLFSSL)), so padding and bitfield slack are zero in - * both objects and only written fields can differ. */ -int test_tls12_clear_resets_options(void) +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_PSK) +static const byte test_clear_psk[] = { 0x5a, 0x6b, 0x7c, 0x8d }; + +static unsigned int test_clear_psk_client_cb(WOLFSSL* ssl, const char* hint, + char* identity, unsigned int id_max_len, unsigned char* key, + unsigned int key_max_len) { - EXPECT_DECLS; + (void)ssl; + (void)hint; + if ((id_max_len == 0) || (key_max_len < sizeof(test_clear_psk))) + return 0; + XSTRNCPY(identity, "clear_client", id_max_len); + XMEMCPY(key, test_clear_psk, sizeof(test_clear_psk)); + return (unsigned int)sizeof(test_clear_psk); +} + +static unsigned int test_clear_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_clear_psk))) + return 0; + if (XSTRCMP(id, "clear_client") != 0) + return 0; + XMEMCPY(key, test_clear_psk, sizeof(test_clear_psk)); + return (unsigned int)sizeof(test_clear_psk); +} +#endif + #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ !defined(WOLFSSL_NO_TLS12) && defined(HAVE_SESSION_TICKET) && \ !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) +static int test_tls12_clear_resets_options_ex(int psk) +{ + EXPECT_DECLS; WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; WOLFSSL *ssl_c = NULL, *ssl_s = NULL, *fresh = NULL; struct test_memio_ctx test_ctx; @@ -3434,6 +3459,19 @@ int test_tls12_clear_resets_options(void) char reply[sizeof(msg)]; XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); +#ifndef NO_PSK + if (psk) { + /* On the CTXs, so the fresh object below is configured alike. */ + wolfSSL_CTX_set_psk_client_callback(ctx_c, test_clear_psk_client_cb); + wolfSSL_CTX_set_psk_server_callback(ctx_s, test_clear_psk_server_cb); + ExpectIntEQ(wolfSSL_CTX_set_cipher_list(ctx_c, + "DHE-PSK-AES128-GCM-SHA256"), WOLFSSL_SUCCESS); + } +#else + (void)psk; +#endif ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c), WOLFSSL_SUCCESS); @@ -3462,6 +3500,9 @@ int test_tls12_clear_resets_options(void) ssl_s->options.hashAlgo = fresh->options.hashAlgo; ssl_s->options.sigAlgo = fresh->options.sigAlgo; ssl_s->options.haveDH = fresh->options.haveDH; +#ifndef NO_DH + ssl_s->options.dhKeySz = fresh->options.dhKeySz; +#endif /* wolfSSL_get_shutdown() reports a completed bidirectional shutdown * from this after a clear, by documented intent. */ ssl_s->options.shutdownDone = fresh->options.shutdownDone; @@ -3495,6 +3536,105 @@ int test_tls12_clear_resets_options(void) wolfSSL_free(ssl_s); wolfSSL_CTX_free(ctx_c); wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +/* Catches per-connection Options state that wolfSSL_clear() forgets, rather + * than one field at a time. Every Options is born inside + * XMEMSET(ssl, 0, sizeof(WOLFSSL)), so padding and bitfield slack are zero in + * both objects and only written fields can differ. */ +int test_tls12_clear_resets_options(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(WOLFSSL_NO_TLS12) && defined(HAVE_SESSION_TICKET) && \ + !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) + ExpectIntEQ(test_tls12_clear_resets_options_ex(0), TEST_SUCCESS); +#ifndef NO_PSK + /* A PSK suite leaves different state behind. */ + ExpectIntEQ(test_tls12_clear_resets_options_ex(1), TEST_SUCCESS); +#endif +#endif + return EXPECT_RESULT(); +} + +/* usingPSK_cipher and pskNegotiated describe the suite in use, so a reused + * object must not carry the previous connection's into a handshake whose + * policy excuses only PSK suites from presenting a certificate. */ +int test_tls_clear_resets_psk_flags(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_PSK) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + +#ifndef WOLFSSL_NO_TLS12 + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + wolfSSL_set_psk_client_callback(ssl_c, test_clear_psk_client_cb); + wolfSSL_set_psk_server_callback(ssl_s, test_clear_psk_server_cb); + ExpectIntEQ(wolfSSL_set_cipher_list(ssl_c, "DHE-PSK-AES128-GCM-SHA256"), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->options.usingPSK_cipher, 1); + ExpectIntEQ(wolfSSL_clear(ssl_s), WOLFSSL_SUCCESS); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->options.usingPSK_cipher, 0); + +#ifndef WOLFSSL_NO_CLIENT_AUTH + /* A client with neither a certificate nor a PSK, on the reused server + * under a policy that excuses only PSK suites. */ + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_CTX_free(ctx_c); + ctx_c = NULL; + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, NULL, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + if (ssl_s != NULL) { + wolfSSL_SetIOWriteCtx(ssl_s, &test_ctx); + wolfSSL_SetIOReadCtx(ssl_s, &test_ctx); + } + wolfSSL_set_verify(ssl_s, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_EXCEPT_PSK, NULL); + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->error, WC_NO_ERR_TRACE(NO_PEER_CERT)); +#endif + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(ssl_s); + ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); + ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); + ctx_s = NULL; +#endif /* !WOLFSSL_NO_TLS12 */ + +#ifdef WOLFSSL_TLS13 + 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_clear_psk_client_cb); + wolfSSL_set_psk_server_callback(ssl_s, test_clear_psk_server_cb); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->options.pskNegotiated, 1); + ExpectIntEQ(wolfSSL_clear(ssl_s), WOLFSSL_SUCCESS); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->options.pskNegotiated, 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif #endif return EXPECT_RESULT(); } @@ -3542,7 +3682,9 @@ int test_tls12_resume_ticket_mutual_auth(void) return EXPECT_RESULT(); } -/* A post-handshake-auth server records no outcome, so it must still resume. */ +/* A post-handshake-auth server records no outcome, so it must still resume. + * Authenticating afterwards is an outcome, and a session written then + * records it even though the connection was resumed. */ int test_tls13_resume_psk_post_handshake_auth(void) { EXPECT_DECLS; @@ -3555,7 +3697,8 @@ int test_tls13_resume_psk_post_handshake_auth(void) WOLFSSL *ssl_c = NULL, *ssl_s = NULL; WOLFSSL_SESSION* sess = NULL; struct test_memio_ctx test_ctx; - byte readBuf[16]; + char msg[] = "hello wolfssl!"; + char readBuf[sizeof(msg)]; XMEMSET(&test_ctx, 0, sizeof(test_ctx)); ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, @@ -3566,6 +3709,7 @@ int test_tls13_resume_psk_post_handshake_auth(void) WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, cliCertFile, NULL), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_allow_post_handshake_auth(ctx_c), 0); 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_s, WOLFSSL_VERIFY_PEER | @@ -3590,6 +3734,28 @@ int test_tls13_resume_psk_post_handshake_auth(void) ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS); ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); ExpectIntEQ(wolfSSL_session_reused(ssl_s), 1); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + + /* The server's write carries the CertificateRequest, the client's reply + * the certificate. */ + 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, readBuf, sizeof(readBuf) - 1), + (int)sizeof(msg) - 1); + ExpectIntEQ(wolfSSL_write(ssl_c, msg, (int)sizeof(msg) - 1), + (int)sizeof(msg) - 1); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf) - 1), + (int)sizeof(msg) - 1); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->options.havePeerVerify, 1); + /* A session written now records the authentication. */ + ExpectIntEQ(wolfSSL_send_SessionTicket(ssl_s), WOLFSSL_SUCCESS); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->session->peerAuthOk, 1); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); wolfSSL_SESSION_free(sess); wolfSSL_free(ssl_c); @@ -3884,30 +4050,40 @@ int test_tls12_client_auth_flags_enforced(void) !defined(WOLFSSL_NO_CLIENT_AUTH) && !defined(NO_RSA) int i; - for (i = 0; i < 6; i++) { + for (i = 0; i < 10; i++) { WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; WOLFSSL *ssl_c = NULL, *ssl_s = NULL; struct test_memio_ctx test_ctx; - int wantCert = (i < 4); + int wantCert = (i < 4) || (i == 7) || (i == 8); XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), + 0); + if (i >= 8) { + /* Inherited by the objects created below. */ + ExpectIntEQ(wolfSSL_CTX_mutual_auth(ctx_s, 1), 0); + if (i == 9) + ExpectIntEQ(wolfSSL_CTX_mutual_auth(ctx_s, 0), 0); + } ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); switch (i) { case 0: ExpectIntEQ(wolfSSL_mutual_auth(ssl_s, 1), 0); - /* The API states the whole requirement in the verify mode: - * ask for the certificate, and fail without it. */ + /* Asks for the certificate; the requirement itself stays + * in mutualAuth, so nothing else is disturbed. */ if (ssl_s != NULL) { ExpectIntEQ(ssl_s->options.verifyPeer, 1); ExpectIntEQ(ssl_s->options.verifyNone, 0); - ExpectIntEQ(ssl_s->options.failNoCert, 1); + ExpectIntEQ(ssl_s->options.failNoCert, 0); + ExpectIntEQ(ssl_s->options.mutualAuth, 1); } #if defined(OPENSSL_ALL) || defined(OPENSSL_EXTRA) || defined(HAVE_STUNNEL) || \ defined(WOLFSSL_NGINX) || defined(WOLFSSL_HAPROXY) || defined(HAVE_LIGHTY) ExpectIntEQ(wolfSSL_get_verify_mode(ssl_s), - WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT); + WOLFSSL_VERIFY_PEER); #endif break; case 1: @@ -3926,11 +4102,26 @@ int test_tls12_client_auth_flags_enforced(void) case 4: wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_NONE, NULL); break; - default: + case 5: /* An explicit WOLFSSL_VERIFY_NONE afterwards waives it. */ ExpectIntEQ(wolfSSL_mutual_auth(ssl_s, 1), 0); wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_NONE, NULL); break; + case 6: + /* Turned off again, nothing of it remains. */ + ExpectIntEQ(wolfSSL_mutual_auth(ssl_s, 1), 0); + ExpectIntEQ(wolfSSL_mutual_auth(ssl_s, 0), 0); + break; + case 7: + /* Turning it off leaves a requirement set separately. */ + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_PEER | + WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + ExpectIntEQ(wolfSSL_mutual_auth(ssl_s, 1), 0); + ExpectIntEQ(wolfSSL_mutual_auth(ssl_s, 0), 0); + break; + default: + /* Configured on the CTX above. */ + break; } if (wantCert) { @@ -4100,6 +4291,79 @@ int test_tls13_pha_unsolicited_blank_cert(void) return EXPECT_RESULT(); } +/* A certificate the server did not ask for is parsed but not verified, so it + * is not client auth and must not be recorded as such for resumption. */ +int test_tls13_unsolicited_cert_not_recorded(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TLS13) && \ + defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + !defined(NO_SESSION_CACHE) && !defined(NO_CERTS) && \ + !defined(WOLFSSL_NO_CLIENT_AUTH) && !defined(NO_RSA) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL_SESSION* sess = NULL; + struct test_memio_ctx test_ctx; + byte readBuf[16]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + 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_load_verify_locations(ctx_s, cliCertFile, NULL), + WOLFSSL_SUCCESS); + 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_s, WOLFSSL_VERIFY_NONE, NULL); + /* No conforming client sends these unasked, so ask for them directly. + * The hash is normally taken from the CertificateRequest. */ + if (ssl_c != NULL) { + ssl_c->options.sendVerify = SEND_CERT; + ssl_c->options.hashAlgo = sha256_mac; + } + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + if (ssl_s != NULL) { + /* Premise: the certificate and its proof were accepted. */ + ExpectIntEQ(ssl_s->options.havePeerCert, 1); + ExpectIntEQ(ssl_s->options.havePeerVerify, 1); + ExpectIntEQ(ssl_s->session->peerAuthOk, 0); + } + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); + + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(ssl_s); + ssl_s = NULL; + + /* Requiring a certificate now means a full handshake. */ + 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_s, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_session_reused(ssl_s), 0); + if (ssl_s != NULL) { + ExpectIntEQ(ssl_s->options.havePeerCert, 1); + /* Asked for and verified this time, so it counts. */ + ExpectIntEQ(ssl_s->session->peerAuthOk, 1); + } + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + /* verifyPostHandshake defers the request only in TLS 1.3. A TLS 1.2 full * handshake still demands the certificate, so resumption must not read the * flag as a waiver. */ @@ -4228,7 +4492,7 @@ int test_tls12_reuse_clears_use_ticket(void) WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL, *ctx_s2 = NULL; WOLFSSL *ssl_c = NULL, *ssl_c2 = NULL, *ssl_c3 = NULL; WOLFSSL *ssl_s = NULL, *ssl_s2 = NULL; - WOLFSSL_SESSION* sess = NULL; + WOLFSSL_SESSION *sess = NULL, *sessS = NULL; struct test_memio_ctx test_ctx; int useTicketAfterClear = -1; int peerAuthAfterClear = -1; @@ -4271,7 +4535,15 @@ int test_tls12_reuse_clears_use_ticket(void) /* The two ends share one cache in-process. */ ExpectIntEQ(wolfSSL_SSL_CTX_remove_session(ctx_s2, sess), 1); + ExpectNotNull(sessS = wolfSSL_get1_session(ssl_s)); ExpectIntEQ(wolfSSL_clear(ssl_s), WOLFSSL_SUCCESS); + /* The object starts over with a session of its own; what the + * application holds is left alone. */ + if (ssl_s != NULL) + ExpectTrue(ssl_s->session != sessS); +#ifndef WOLFSSL_NO_CLIENT_AUTH + ExpectIntEQ(sessS->peerAuthOk, 1); +#endif /* Assert last, or a failure here skips the behavioural half. */ if (ssl_s != NULL) { useTicketAfterClear = ssl_s->options.useTicket; @@ -4297,6 +4569,7 @@ int test_tls12_reuse_clears_use_ticket(void) ExpectIntEQ(peerAuthAfterClear, 0); wolfSSL_SESSION_free(sess); + wolfSSL_SESSION_free(sessS); wolfSSL_free(ssl_c); wolfSSL_free(ssl_c2); wolfSSL_free(ssl_c3); diff --git a/tests/api/test_tls.h b/tests/api/test_tls.h index abf80315639..c42b70f04aa 100644 --- a/tests/api/test_tls.h +++ b/tests/api/test_tls.h @@ -62,6 +62,7 @@ int test_tls_version_error_alert_mapping(void); int test_tls12_etm_failed_resumption(void); int test_tls12_resume_ticket_wrong_suite(void); int test_tls12_clear_resets_options(void); +int test_tls_clear_resets_psk_flags(void); int test_tls12_resume_ticket_mutual_auth(void); int test_tls13_resume_psk_post_handshake_auth(void); int test_tls13_resume_psk_client_auth(void); @@ -73,6 +74,7 @@ int test_tls12_client_auth_flags_enforced(void); int test_tls12_resume_ticket_fail_except_psk(void); int test_tls1x_empty_cert_reject_point(void); int test_tls13_pha_unsolicited_blank_cert(void); +int test_tls13_unsolicited_cert_not_recorded(void); int test_tls12_resume_ticket_client_auth_ok(void); int test_tls12_reuse_clears_use_ticket(void); int test_tls12_resume_ticket_decline_fallback(void); @@ -157,6 +159,7 @@ int test_tls_param_flags_crl_check(void); TEST_DECL_GROUP("tls", test_tls12_etm_failed_resumption), \ TEST_DECL_GROUP("tls", test_tls12_resume_ticket_wrong_suite), \ TEST_DECL_GROUP("tls", test_tls12_clear_resets_options), \ + TEST_DECL_GROUP("tls", test_tls_clear_resets_psk_flags), \ TEST_DECL_GROUP("tls", test_tls12_resume_ticket_mutual_auth), \ TEST_DECL_GROUP("tls", test_tls13_resume_psk_post_handshake_auth), \ TEST_DECL_GROUP("tls", test_tls13_resume_psk_client_auth), \ @@ -169,6 +172,7 @@ int test_tls_param_flags_crl_check(void); TEST_DECL_GROUP("tls", test_tls12_resume_ticket_fail_except_psk), \ TEST_DECL_GROUP("tls", test_tls1x_empty_cert_reject_point), \ TEST_DECL_GROUP("tls", test_tls13_pha_unsolicited_blank_cert), \ + TEST_DECL_GROUP("tls", test_tls13_unsolicited_cert_not_recorded), \ TEST_DECL_GROUP("tls", test_tls12_resume_ticket_client_auth_ok), \ TEST_DECL_GROUP("tls", test_tls12_reuse_clears_use_ticket), \ TEST_DECL_GROUP("tls", test_tls12_resume_ticket_decline_fallback), \ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 7e8b3d67286..2e3e5b0ec44 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -2427,6 +2427,11 @@ WOLFSSL_LOCAL int CheckCookieState(WOLFSSL* ssl); #else #define SSL_USING_PSK(ssl) ((ssl)->options.usingPSK_cipher) #endif +/* A verified client certificate with proof of possession. Nothing is verified + * under verifyNone. */ +#define SSL_PEER_CERT_VERIFIED(ssl) \ + ((ssl)->options.havePeerCert && (ssl)->options.havePeerVerify && \ + !(ssl)->options.verifyNone) #if !defined(NO_CERTS) && !defined(NO_WOLFSSL_SERVER) && !defined(NO_TLS) WOLFSSL_LOCAL int ClientAuthRequired(const WOLFSSL* ssl, int usingPsk); #else @@ -4023,20 +4028,13 @@ WOLFSSL_LOCAL int SetupClientSecureRenegotiation(WOLFSSL* ssl); /* Our ticket format. All members need to be a byte or array of byte to * avoid alignment issues */ -/* Bits of InternalTicket.flags. Bit 0 is where this byte has always held - * haveEMS, so sizeof(InternalTicket) and every offset are unchanged and an - * older ticket parses with the newer bits clear. An older peer handed a ticket - * carrying bit 1 copies the whole byte into haveEMS and fails the handshake - * cleanly, so a shared TLS 1.2 ticket key needs matching versions. */ -#define WOLFSSL_TICKET_FLAG_EMS 0x01 -#define WOLFSSL_TICKET_FLAG_PEER_AUTH 0x02 - typedef struct InternalTicket { ProtocolVersion pv; /* version when ticket created */ byte suite[SUITE_LEN]; /* cipher suite when created */ byte msecret[SECRET_LEN]; /* master secret */ byte timestamp[TIMESTAMP_LEN]; /* born on */ - byte flags; /* WOLFSSL_TICKET_FLAG_* */ + byte haveEMS:1; /* have extended master secret */ + byte peerAuthOk:1; /* client cert was verified */ #ifdef WOLFSSL_TLS13 byte ageAdd[AGEADD_LEN]; /* Obfuscation of age */ byte namedGroup[NAMEDGROUP_LEN]; /* Named group used */ From e1349ad81af56d628cf98f1f86a41a75e5f0e36f Mon Sep 17 00:00:00 2001 From: Kareem Date: Thu, 15 Oct 2026 18:44:02 -0700 Subject: [PATCH 04/11] Fix FIPS test failures --- tests/api/test_tls.c | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/tests/api/test_tls.c b/tests/api/test_tls.c index 09e25b1d7ae..4daf899fb10 100644 --- a/tests/api/test_tls.c +++ b/tests/api/test_tls.c @@ -3417,7 +3417,9 @@ int test_tls12_resume_ticket_wrong_suite(void) return EXPECT_RESULT(); } -#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_PSK) +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_PSK) && \ + (defined(BUILD_TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256) || \ + defined(WOLFSSL_TLS13)) static const byte test_clear_psk[] = { 0x5a, 0x6b, 0x7c, 0x8d }; static unsigned int test_clear_psk_client_cb(WOLFSSL* ssl, const char* hint, @@ -3461,19 +3463,24 @@ static int test_tls12_clear_resets_options_ex(int psk) XMEMSET(&test_ctx, 0, sizeof(test_ctx)); ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); -#ifndef NO_PSK +#if !defined(NO_PSK) && defined(BUILD_TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256) if (psk) { /* On the CTXs, so the fresh object below is configured alike. */ wolfSSL_CTX_set_psk_client_callback(ctx_c, test_clear_psk_client_cb); wolfSSL_CTX_set_psk_server_callback(ctx_s, test_clear_psk_server_cb); ExpectIntEQ(wolfSSL_CTX_set_cipher_list(ctx_c, - "DHE-PSK-AES128-GCM-SHA256"), WOLFSSL_SUCCESS); + "ECDHE-PSK-AES128-GCM-SHA256"), WOLFSSL_SUCCESS); } #else (void)psk; #endif ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); +#if !defined(NO_PSK) && defined(BUILD_TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256) + /* The CTX suite list predates the callback; this rebuilds the object's. */ + if (psk) + wolfSSL_set_psk_server_callback(ssl_s, test_clear_psk_server_cb); +#endif ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c), WOLFSSL_SUCCESS); ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); /* Data both ways and a bidirectional close, so record-layer and shutdown @@ -3500,9 +3507,6 @@ static int test_tls12_clear_resets_options_ex(int psk) ssl_s->options.hashAlgo = fresh->options.hashAlgo; ssl_s->options.sigAlgo = fresh->options.sigAlgo; ssl_s->options.haveDH = fresh->options.haveDH; -#ifndef NO_DH - ssl_s->options.dhKeySz = fresh->options.dhKeySz; -#endif /* wolfSSL_get_shutdown() reports a completed bidirectional shutdown * from this after a clear, by documented intent. */ ssl_s->options.shutdownDone = fresh->options.shutdownDone; @@ -3551,7 +3555,7 @@ int test_tls12_clear_resets_options(void) !defined(WOLFSSL_NO_TLS12) && defined(HAVE_SESSION_TICKET) && \ !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) ExpectIntEQ(test_tls12_clear_resets_options_ex(0), TEST_SUCCESS); -#ifndef NO_PSK +#if !defined(NO_PSK) && defined(BUILD_TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256) /* A PSK suite leaves different state behind. */ ExpectIntEQ(test_tls12_clear_resets_options_ex(1), TEST_SUCCESS); #endif @@ -3565,18 +3569,21 @@ int test_tls12_clear_resets_options(void) int test_tls_clear_resets_psk_flags(void) { EXPECT_DECLS; -#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_PSK) +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_PSK) && \ + (defined(BUILD_TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256) || \ + defined(WOLFSSL_TLS13)) WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; WOLFSSL *ssl_c = NULL, *ssl_s = NULL; struct test_memio_ctx test_ctx; -#ifndef WOLFSSL_NO_TLS12 +#if !defined(WOLFSSL_NO_TLS12) && \ + defined(BUILD_TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256) XMEMSET(&test_ctx, 0, sizeof(test_ctx)); ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); wolfSSL_set_psk_client_callback(ssl_c, test_clear_psk_client_cb); wolfSSL_set_psk_server_callback(ssl_s, test_clear_psk_server_cb); - ExpectIntEQ(wolfSSL_set_cipher_list(ssl_c, "DHE-PSK-AES128-GCM-SHA256"), + ExpectIntEQ(wolfSSL_set_cipher_list(ssl_c, "ECDHE-PSK-AES128-GCM-SHA256"), WOLFSSL_SUCCESS); ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); if (ssl_s != NULL) @@ -3613,7 +3620,7 @@ int test_tls_clear_resets_psk_flags(void) ctx_c = NULL; wolfSSL_CTX_free(ctx_s); ctx_s = NULL; -#endif /* !WOLFSSL_NO_TLS12 */ +#endif /* !WOLFSSL_NO_TLS12 && BUILD_TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256 */ #ifdef WOLFSSL_TLS13 XMEMSET(&test_ctx, 0, sizeof(test_ctx)); From 6c872e0a23d89798a86e159220a4b4cf42dbf353 Mon Sep 17 00:00:00 2001 From: Kareem Date: Tue, 20 Oct 2026 14:59:38 -0700 Subject: [PATCH 05/11] Only run wolfSSL_FreeSession in wolfSSL_clear if the session has at least one reference, otherwise clear the session. Use isPsk to determine PSK status rather than verifyPeer. Add tests. --- src/ssl.c | 17 ++++++- src/tls13.c | 28 ++++++----- tests/api/test_tls.c | 115 +++++++++++++++++++++++++++++++++++++++++++ tests/api/test_tls.h | 4 ++ 4 files changed, 149 insertions(+), 15 deletions(-) diff --git a/src/ssl.c b/src/ssl.c index ce82642a158..7e5d7001abb 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -5794,15 +5794,26 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out, } /* A client keeps the session it may resume. A server's is per - * connection, and the application may still hold it. */ + * connection: take a new one when the application holds a reference, + * else reset ours, as wolfSSL_get_session() hands out a pointer + * without taking one. */ if (!ssl->options.handShakeDone || - (ssl->options.side == WOLFSSL_SERVER_END)) { + ((ssl->options.side == WOLFSSL_SERVER_END) && + (wolfSSL_RefCur(ssl->session->ref) > 1))) { wolfSSL_FreeSession(ssl->ctx, ssl->session); ssl->session = wolfSSL_NewSession(ssl->heap); if (ssl->session == NULL) { return WOLFSSL_FAILURE; } } + else if (ssl->options.side == WOLFSSL_SERVER_END) { + ssl->session->haveAltSessionID = 0; + ForceZero(ssl->session->altSessionID, ID_LEN); + ssl->session->peerAuthOk = 0; + #if defined(HAVE_SESSION_TICKET) && defined(WOLFSSL_TLS13) + ssl->session->ticketNonce.len = 0; + #endif + } /* reset error */ ssl->error = 0; @@ -5848,6 +5859,8 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out, ssl->options.peerAuthGood = 0; ssl->options.usingPSK_cipher = 0; ssl->options.usingAnon_cipher = 0; + /* Left set, the next client here is never asked for a certificate. */ + ssl->options.isPSK = 0; #if defined(WOLFSSL_TLS13) && (defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)) ssl->options.pskNegotiated = 0; #endif diff --git a/src/tls13.c b/src/tls13.c index edb36ecd12e..d5bcc35f19c 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -6904,11 +6904,6 @@ static int FindPsk(WOLFSSL* ssl, PreSharedKey* psk, const byte* suite, int* err) psk->resumption = 0; /* Default to ciphersuite if cb doesn't specify. */ ssl->options.resuming = 0; - /* Don't send certificate request when using PSK. */ -#ifdef WOLFSSL_CERT_WITH_EXTERN_PSK - if (!ssl->options.certWithExternPsk) -#endif - ssl->options.verifyPeer = 0; /* obfuscated_ticket_age is not checked: RFC 8446 Section 4.2.11 * requires servers to ignore it for an external identity. */ @@ -6922,13 +6917,17 @@ static int FindPsk(WOLFSSL* ssl, PreSharedKey* psk, const byte* suite, int* err) } if (ret == 0) { /* PSK negotiation has succeeded */ - ssl->options.isPSK = 1; #ifdef WOLFSSL_CERT_WITH_EXTERN_PSK + /* RFC 9973 pairs the PSK with a certificate, so the PSK is not + * the client's authentication. Read before the extension is + * resolved for the connection. */ if (!ssl->options.certWithExternPsk) #endif { - /* SERVER: using PSK for peer authentication. */ + /* SERVER: using PSK for peer authentication, so no + * certificate is requested below. */ ssl->options.peerAuthGood = 1; + ssl->options.isPSK = 1; } } } @@ -7096,18 +7095,20 @@ static int DoPreSharedKeys(WOLFSSL* ssl, const byte* input, word32 inputSz, #endif } #endif - if (ret == 0) - DoClientTicketFinalize(ssl, current->it, current->sess); - /* This is the PSK path, so failNoCertxPSK excuses it. */ + /* This is the PSK path, so failNoCertxPSK excuses it. Ahead of + * DoClientTicketFinalize(), like the checks above, so a ticket + * this connection will not use leaves nothing on the session. */ if (ret == 0 && ClientAuthRequired(ssl, 1) && #ifdef WOLFSSL_POST_HANDSHAKE_AUTH /* Deferred, so this handshake asks for nothing. */ !ssl->options.verifyPostHandshake && #endif - !ssl->session->peerAuthOk) { + !current->it->peerAuthOk) { WOLFSSL_MSG("Ticket session lacks client auth, skipping PSK"); ret = WOLFSSL_FATAL_ERROR; } + if (ret == 0) + DoClientTicketFinalize(ssl, current->it, current->sess); if (current->sess_free_cb != NULL) { current->sess_free_cb(ssl, current->sess, ¤t->sess_free_cb_ctx); @@ -18270,7 +18271,7 @@ int wolfSSL_accept_TLSv13(WOLFSSL* ssl) case TLS13_SERVER_EXTENSIONS_SENT : #ifndef NO_CERTS if (!ssl->options.resuming) { - if (ssl->options.verifyPeer + if (ssl->options.verifyPeer && !ssl->options.isPSK #ifdef WOLFSSL_POST_HANDSHAKE_AUTH && !ssl->options.verifyPostHandshake #endif @@ -18339,7 +18340,8 @@ int wolfSSL_accept_TLSv13(WOLFSSL* ssl) #endif #ifdef HAVE_SESSION_TICKET #ifdef WOLFSSL_TLS13_TICKET_BEFORE_FINISHED - if (!ssl->options.verifyPeer && !ssl->options.noTicketTls13 && + if ((!ssl->options.verifyPeer || ssl->options.isPSK) && + !ssl->options.noTicketTls13 && ssl->ctx->ticketEncCb != NULL && ssl->options.maxTicketTls13 > 0) { if ((ssl->error = SendTls13NewSessionTicket(ssl)) != 0) { diff --git a/tests/api/test_tls.c b/tests/api/test_tls.c index 4daf899fb10..2a4f612786b 100644 --- a/tests/api/test_tls.c +++ b/tests/api/test_tls.c @@ -3646,6 +3646,121 @@ int test_tls_clear_resets_psk_flags(void) return EXPECT_RESULT(); } +/* wolfSSL_get_session() hands the application a pointer without taking a + * reference, so a server reusing the object must not free the session out + * from under it. */ +int test_tls12_clear_keeps_borrowed_session(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(WOLFSSL_NO_TLS12) && !defined(NO_CERTS) && !defined(NO_RSA) && \ + !defined(WOLFSSL_NO_CLIENT_AUTH) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL_SESSION* borrowed = NULL; + struct test_memio_ctx test_ctx; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + 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_load_verify_locations(ctx_s, cliCertFile, NULL), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + wolfSSL_set_verify(ssl_s, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectNotNull(borrowed = wolfSSL_get_session(ssl_s)); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->session->peerAuthOk, 1); + + ExpectIntEQ(wolfSSL_clear(ssl_s), WOLFSSL_SUCCESS); + /* Still the application's to read, and holding nothing of the connection + * that ended. */ + if (ssl_s != NULL) + ExpectPtrEq(ssl_s->session, borrowed); + ExpectIntEQ(borrowed->peerAuthOk, 0); + ExpectIntEQ(borrowed->haveAltSessionID, 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* An external PSK means this client is not asked for a certificate. Left to + * stand, the next client on a reused object is not asked either, and then + * fails the check that one was presented. */ +int test_tls13_psk_clear_restores_verify_peer(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TLS13) && \ + !defined(NO_PSK) && !defined(NO_CERTS) && !defined(NO_RSA) && \ + !defined(WOLFSSL_NO_CLIENT_AUTH) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *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, NULL, NULL, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, cliCertFile, NULL), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + /* Only a PSK suite excuses the certificate. */ + wolfSSL_set_verify(ssl_s, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_EXCEPT_PSK, NULL); + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); + wolfSSL_set_psk_client_callback(ssl_c, test_clear_psk_client_cb); + wolfSSL_set_psk_server_callback(ssl_s, test_clear_psk_server_cb); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + if (ssl_s != NULL) { + ExpectIntEQ(ssl_s->options.isPSK, 1); + /* The configured mode outlives the connection. */ + ExpectIntEQ(ssl_s->options.verifyPeer, 1); + } + + ExpectIntEQ(wolfSSL_clear(ssl_s), WOLFSSL_SUCCESS); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->options.isPSK, 0); + + /* A second client, with a certificate and no PSK, on the reused object. */ + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_CTX_free(ctx_c); + ctx_c = NULL; + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + 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(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, NULL, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + if (ssl_s != NULL) { + wolfSSL_SetIOWriteCtx(ssl_s, &test_ctx); + wolfSSL_SetIOReadCtx(ssl_s, &test_ctx); + } + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->options.havePeerCert, 1); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + /* mutual_auth is an alternative to failNoCert in the full handshake, so * resumption has to agree. */ int test_tls12_resume_ticket_mutual_auth(void) diff --git a/tests/api/test_tls.h b/tests/api/test_tls.h index c42b70f04aa..148da5329a3 100644 --- a/tests/api/test_tls.h +++ b/tests/api/test_tls.h @@ -63,6 +63,8 @@ int test_tls12_etm_failed_resumption(void); int test_tls12_resume_ticket_wrong_suite(void); int test_tls12_clear_resets_options(void); int test_tls_clear_resets_psk_flags(void); +int test_tls12_clear_keeps_borrowed_session(void); +int test_tls13_psk_clear_restores_verify_peer(void); int test_tls12_resume_ticket_mutual_auth(void); int test_tls13_resume_psk_post_handshake_auth(void); int test_tls13_resume_psk_client_auth(void); @@ -160,6 +162,8 @@ int test_tls_param_flags_crl_check(void); TEST_DECL_GROUP("tls", test_tls12_resume_ticket_wrong_suite), \ TEST_DECL_GROUP("tls", test_tls12_clear_resets_options), \ TEST_DECL_GROUP("tls", test_tls_clear_resets_psk_flags), \ + TEST_DECL_GROUP("tls", test_tls12_clear_keeps_borrowed_session), \ + TEST_DECL_GROUP("tls", test_tls13_psk_clear_restores_verify_peer), \ TEST_DECL_GROUP("tls", test_tls12_resume_ticket_mutual_auth), \ TEST_DECL_GROUP("tls", test_tls13_resume_psk_post_handshake_auth), \ TEST_DECL_GROUP("tls", test_tls13_resume_psk_client_auth), \ From cb7b287962d2a4d09a97235da4174c3fa658307a Mon Sep 17 00:00:00 2001 From: Kareem Date: Tue, 20 Oct 2026 16:48:36 -0700 Subject: [PATCH 06/11] Fix failing test. --- tests/api/test_tls.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/api/test_tls.c b/tests/api/test_tls.c index 2a4f612786b..137e3e1f6b7 100644 --- a/tests/api/test_tls.c +++ b/tests/api/test_tls.c @@ -3654,7 +3654,13 @@ int test_tls12_clear_keeps_borrowed_session(void) EXPECT_DECLS; #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ !defined(WOLFSSL_NO_TLS12) && !defined(NO_CERTS) && !defined(NO_RSA) && \ - !defined(WOLFSSL_NO_CLIENT_AUTH) + !defined(WOLFSSL_NO_CLIENT_AUTH) && !defined(NO_SESSION_CACHE) && \ + (!defined(WOLFSSL_BLIND_PRIVATE_KEY) || defined(OPENSSL_EXTRA) || \ + defined(WOLFSSL_WPAS_SMALL)) + /* Needs wolfSSL_get_session(), and a server whose key outlives the + * handshake: FreeHandshakeResources() unloads a key the object owns, + * which with blinding is its own copy, and wolfSSL_clear() then has + * none to re-arm the suites with. */ WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; WOLFSSL *ssl_c = NULL, *ssl_s = NULL; WOLFSSL_SESSION* borrowed = NULL; From 8b5bf5ab5256f5726309a12a3141f3edeed1ba90 Mon Sep 17 00:00:00 2001 From: Kareem Date: Wed, 21 Oct 2026 10:54:50 -0700 Subject: [PATCH 07/11] Fix failing test in --enable-coding=no builds. --- tests/api/test_tls.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/api/test_tls.c b/tests/api/test_tls.c index 137e3e1f6b7..b1448f5b3d2 100644 --- a/tests/api/test_tls.c +++ b/tests/api/test_tls.c @@ -3655,12 +3655,13 @@ int test_tls12_clear_keeps_borrowed_session(void) #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ !defined(WOLFSSL_NO_TLS12) && !defined(NO_CERTS) && !defined(NO_RSA) && \ !defined(WOLFSSL_NO_CLIENT_AUTH) && !defined(NO_SESSION_CACHE) && \ + defined(WOLFSSL_PEM_TO_DER) && \ (!defined(WOLFSSL_BLIND_PRIVATE_KEY) || defined(OPENSSL_EXTRA) || \ defined(WOLFSSL_WPAS_SMALL)) - /* Needs wolfSSL_get_session(), and a server whose key outlives the - * handshake: FreeHandshakeResources() unloads a key the object owns, - * which with blinding is its own copy, and wolfSSL_clear() then has - * none to re-arm the suites with. */ + /* Loads PEM credentials, and needs wolfSSL_get_session() and a server + * whose key outlives the handshake: FreeHandshakeResources() unloads a + * key the object owns, which with blinding is its own copy, and + * wolfSSL_clear() then has none to re-arm the suites with. */ WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; WOLFSSL *ssl_c = NULL, *ssl_s = NULL; WOLFSSL_SESSION* borrowed = NULL; From 4036c8d01983561fde9b1d500385e54997ae78cc Mon Sep 17 00:00:00 2001 From: Kareem Date: Thu, 22 Oct 2026 12:51:44 -0700 Subject: [PATCH 08/11] Disable PSK parameter for ClientAuthRequired in DoPreSharedKeys. ClientAuthRequired should only check if the ticket was authenticated for this case. --- src/tls13.c | 7 ++- tests/api/test_tls.c | 122 +++++++++++++++++++++++++++++++++++++++++++ tests/api/test_tls.h | 5 ++ 3 files changed, 130 insertions(+), 4 deletions(-) diff --git a/src/tls13.c b/src/tls13.c index d5bcc35f19c..f39a52a3f96 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -7095,10 +7095,9 @@ static int DoPreSharedKeys(WOLFSSL* ssl, const byte* input, word32 inputSz, #endif } #endif - /* This is the PSK path, so failNoCertxPSK excuses it. Ahead of - * DoClientTicketFinalize(), like the checks above, so a ticket - * this connection will not use leaves nothing on the session. */ - if (ret == 0 && ClientAuthRequired(ssl, 1) && + /* A ticket is not the PSK failNoCertxPSK excuses. Before + * Finalize, so a declined ticket leaves nothing on the session. */ + if (ret == 0 && ClientAuthRequired(ssl, 0) && #ifdef WOLFSSL_POST_HANDSHAKE_AUTH /* Deferred, so this handshake asks for nothing. */ !ssl->options.verifyPostHandshake && diff --git a/tests/api/test_tls.c b/tests/api/test_tls.c index b1448f5b3d2..7819fc813fa 100644 --- a/tests/api/test_tls.c +++ b/tests/api/test_tls.c @@ -3945,6 +3945,128 @@ int test_tls13_resume_psk_client_auth(void) return EXPECT_RESULT(); } +/* failNoCertxPSK excuses a PSK, not a ticket. A ticket minted by a + * certificate session that never authenticated its client must not resume + * under it, matching TLS 1.2 and the full handshake the same client gets. */ +int test_tls13_resume_ticket_fail_except_psk(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TLS13) && \ + defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + !defined(NO_SESSION_CACHE) && !defined(NO_CERTS) && \ + !defined(WOLFSSL_NO_CLIENT_AUTH) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL_SESSION* sess = NULL; + struct test_memio_ctx test_ctx; + 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_s, WOLFSSL_VERIFY_NONE, NULL); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + /* Drives the NewSessionTicket onto the client session. */ + 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)); + /* Nothing authenticated the client. */ + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->session->peerAuthOk, 0); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + wolfSSL_set_verify(ssl_s, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_EXCEPT_PSK, NULL); + ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS); + /* Premise: the exempting flag is the only one set. */ + if (ssl_s != NULL) { + ExpectIntEQ(ssl_s->options.failNoCertxPSK, 1); + ExpectIntEQ(ssl_s->options.failNoCert, 0); + ExpectIntEQ(ssl_s->options.mutualAuth, 0); + } + /* The skipped PSK becomes a full handshake, which the certificate-less + * client cannot satisfy either. */ + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + ExpectIntEQ(wolfSSL_session_reused(ssl_s), 0); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->error, WC_NO_ERR_TRACE(NO_PEER_CERT)); + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* The other half: a ticket is not itself the PSK that failNoCertxPSK + * excuses, so one minted by an external-PSK session is declined too. The + * client offers that PSK alongside the ticket, so the connection still + * completes - as a fresh PSK handshake rather than a resumption. */ +int test_tls13_resume_psk_ticket_fail_except_psk(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TLS13) && \ + defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + !defined(NO_SESSION_CACHE) && !defined(NO_PSK) && !defined(NO_CERTS) && \ + !defined(WOLFSSL_NO_CLIENT_AUTH) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL_SESSION* sess = NULL; + struct test_memio_ctx test_ctx; + 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); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_NONE, NULL); + wolfSSL_set_psk_client_callback(ssl_c, test_clear_psk_client_cb); + wolfSSL_set_psk_server_callback(ssl_s, test_clear_psk_server_cb); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + 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)); + /* The PSK authenticated the client, so no certificate was recorded. */ + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->session->peerAuthOk, 0); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); + wolfSSL_set_verify(ssl_s, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_EXCEPT_PSK, NULL); + wolfSSL_set_psk_client_callback(ssl_c, test_clear_psk_client_cb); + wolfSSL_set_psk_server_callback(ssl_s, test_clear_psk_server_cb); + ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + ExpectIntEQ(wolfSSL_session_reused(ssl_s), 0); + /* The external PSK identity, offered alongside the ticket, authenticated + * the client in the ticket's place. */ + if (ssl_s != NULL) { + ExpectIntEQ(ssl_s->options.isPSK, 1); + ExpectIntEQ(ssl_s->options.peerAuthGood, 1); + } + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + /* A session that did authenticate the client must still resume. */ int test_tls13_resume_psk_client_auth_ok(void) { diff --git a/tests/api/test_tls.h b/tests/api/test_tls.h index 148da5329a3..8d3363d3733 100644 --- a/tests/api/test_tls.h +++ b/tests/api/test_tls.h @@ -68,6 +68,8 @@ int test_tls13_psk_clear_restores_verify_peer(void); int test_tls12_resume_ticket_mutual_auth(void); int test_tls13_resume_psk_post_handshake_auth(void); int test_tls13_resume_psk_client_auth(void); +int test_tls13_resume_ticket_fail_except_psk(void); +int test_tls13_resume_psk_ticket_fail_except_psk(void); int test_tls13_resume_psk_client_auth_ok(void); int test_tls12_ext_cache_client_auth_resume(void); int test_tls12_resume_ticket_client_auth(void); @@ -167,6 +169,9 @@ int test_tls_param_flags_crl_check(void); TEST_DECL_GROUP("tls", test_tls12_resume_ticket_mutual_auth), \ TEST_DECL_GROUP("tls", test_tls13_resume_psk_post_handshake_auth), \ TEST_DECL_GROUP("tls", test_tls13_resume_psk_client_auth), \ + TEST_DECL_GROUP("tls", test_tls13_resume_ticket_fail_except_psk), \ + TEST_DECL_GROUP("tls", \ + test_tls13_resume_psk_ticket_fail_except_psk), \ TEST_DECL_GROUP("tls", test_tls13_resume_psk_client_auth_ok), \ TEST_DECL_GROUP("tls", test_tls12_ext_cache_client_auth_resume), \ TEST_DECL_GROUP("tls", test_tls12_resume_ticket_client_auth), \ From bcf0db71526778a26cf73b8d82fec8811fcd4233 Mon Sep 17 00:00:00 2001 From: Kareem Date: Fri, 23 Oct 2026 16:26:31 -0700 Subject: [PATCH 09/11] Combine isPsk and usingPSK_cipher into just usingPSK_cipher. Add isPsk to the session for TLS 1.3, as it has no other way of knowing if a session ticket is PSK. Refactor to always build ClientAuthRequired function. Search for CERT_WITH_EXTERN_PSK extension from the peer rather than relying on certWithExternPsk option. Add tests. --- src/internal.c | 49 +++++----- src/ssl.c | 3 +- src/ssl_sess.c | 16 +++- src/tls13.c | 23 +++-- tests/api.c | 20 +++- tests/api/test_tls.c | 208 ++++++++++++++++++++++++++++++++++++----- tests/api/test_tls.h | 9 +- tests/api/test_tls13.c | 77 ++++++++++++--- tests/api/test_tls13.h | 2 + wolfssl/internal.h | 29 ++---- 10 files changed, 335 insertions(+), 101 deletions(-) diff --git a/src/internal.c b/src/internal.c index 5a47a191cac..65f5f522d4a 100644 --- a/src/internal.c +++ b/src/internal.c @@ -19118,7 +19118,7 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, if (args->count == 0) { /* Empty certificate message. Only mutualAuth fails here * before TLS 1.2; the later check catches the rest. */ - if (ClientAuthRequired(ssl, SSL_USING_PSK(ssl)) && + if (ClientAuthRequired(ssl, ssl->options.usingPSK_cipher) && (ssl->options.mutualAuth || IsAtLeastTLSv1_2(ssl))) { WOLFSSL_MSG("No peer cert from Client"); ret = NO_PEER_CERT; @@ -40363,6 +40363,26 @@ const byte* MaskCurve25519PeerKey(const byte* pub, word32 pubSz, return MATCH_SUITE_ERROR; } +/* Does a full handshake on this connection require a client certificate? + * usingPsk is whether the suite in question authenticates with a PSK, which + * only the caller knows. verifyPostHandshake is deliberately absent: it defers + * the demand, not the policy, so the callers that care apply it. */ +int ClientAuthRequired(const WOLFSSL* ssl, int usingPsk) +{ +#if !defined(NO_CERTS) && !defined(NO_WOLFSSL_SERVER) && !defined(NO_TLS) + if (ssl->options.side != WOLFSSL_SERVER_END) + return 0; + if (ssl->options.verifyNone) + return 0; + return ssl->options.mutualAuth || ssl->options.failNoCert || + (ssl->options.failNoCertxPSK && !usingPsk); +#else + /* Only a TLS server asks for a certificate, so only it can require one. */ + (void)ssl; + (void)usingPsk; + return 0; +#endif +} #if !defined(NO_WOLFSSL_SERVER) && !defined(NO_TLS) @@ -42702,22 +42722,6 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) #endif /* OLD_HELLO_ALLOWED */ -#ifndef NO_CERTS -/* Does a full handshake on this connection require a client certificate? - * usingPsk is whether the suite in question authenticates with a PSK, which - * only the caller knows. verifyPostHandshake is deliberately absent: it defers - * the demand, not the policy, so the callers that care apply it. */ -int ClientAuthRequired(const WOLFSSL* ssl, int usingPsk) -{ - if (ssl->options.side != WOLFSSL_SERVER_END) - return 0; - if (ssl->options.verifyNone) - return 0; - return ssl->options.mutualAuth || ssl->options.failNoCert || - (ssl->options.failNoCertxPSK && !usingPsk); -} -#endif /* !NO_CERTS */ - #ifndef WOLFSSL_NO_TLS12 /** @@ -44528,10 +44532,9 @@ int ClientAuthRequired(const WOLFSSL* ssl, int usingPsk) it->suite[0] = ssl->options.cipherSuite0; it->suite[1] = ssl->options.cipherSuite; - /* Post-handshake auth can add a certificate to a resumed connection, - * so fresh proof wins over what was resumed. */ - it->peerAuthOk = (byte)(SSL_PEER_CERT_VERIFIED(ssl) || - (ssl->options.resuming && ssl->session->peerAuthOk)); + it->peerAuthOk = (byte)SSL_PEER_CERT_VERIFIED(ssl); + it->isPSK = (byte)(ssl->options.usingPSK_cipher || + (ssl->options.resuming && ssl->session->isPSK)); #ifdef WOLFSSL_EARLY_DATA c32toa(ssl->options.maxEarlyDataSz, it->maxEarlyDataSz); @@ -45108,6 +45111,7 @@ int ClientAuthRequired(const WOLFSSL* ssl, int usingPsk) #endif /* After the DupSession above, which would otherwise overwrite it. */ ssl->session->peerAuthOk = it->peerAuthOk; + ssl->session->isPSK = it->isPSK; if (!IsAtLeastTLSv1_3(ssl->version)) { if (ssl->arrays == NULL) @@ -45198,6 +45202,7 @@ int ClientAuthRequired(const WOLFSSL* ssl, int usingPsk) #endif it->haveEMS = (byte)sess->haveEMS; it->peerAuthOk = sess->peerAuthOk; + it->isPSK = sess->isPSK; c32toa(sess->ticketAdd, it->ageAdd); c16toa(sess->namedGroup, it->namedGroup); if (sess->ticketNonce.len <= MAX_TICKET_NONCE_STATIC_SZ) { @@ -46711,7 +46716,7 @@ static int DefTicketEncCb(WOLFSSL* ssl, byte key_name[WOLFSSL_TICKET_NAME_SZ], } #if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CLIENT_AUTH) - if (ClientAuthRequired(ssl, SSL_USING_PSK(ssl)) && + if (ClientAuthRequired(ssl, ssl->options.usingPSK_cipher) && !ssl->options.havePeerCert) { WOLFSSL_MSG("client didn't present peer cert"); ERROR_OUT(NO_PEER_CERT, exit_dcke); diff --git a/src/ssl.c b/src/ssl.c index 7e5d7001abb..9f4c4e998c6 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -5810,6 +5810,7 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out, ssl->session->haveAltSessionID = 0; ForceZero(ssl->session->altSessionID, ID_LEN); ssl->session->peerAuthOk = 0; + ssl->session->isPSK = 0; #if defined(HAVE_SESSION_TICKET) && defined(WOLFSSL_TLS13) ssl->session->ticketNonce.len = 0; #endif @@ -5859,8 +5860,6 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out, ssl->options.peerAuthGood = 0; ssl->options.usingPSK_cipher = 0; ssl->options.usingAnon_cipher = 0; - /* Left set, the next client here is never asked for a certificate. */ - ssl->options.isPSK = 0; #if defined(WOLFSSL_TLS13) && (defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)) ssl->options.pskNegotiated = 0; #endif diff --git a/src/ssl_sess.c b/src/ssl_sess.c index 2d7f7a5021a..23a0865947c 100644 --- a/src/ssl_sess.c +++ b/src/ssl_sess.c @@ -2953,7 +2953,7 @@ int wolfSSL_i2d_SSL_SESSION(WOLFSSL_SESSION* sess, unsigned char** p) #endif #endif /* !NO_WOLFSSL_SERVER && !NO_TLS */ #endif - /* peerAuthOk */ + /* peerAuthOk and isPSK */ size += OPAQUE8_LEN; if (p != NULL) { @@ -3050,7 +3050,7 @@ int wolfSSL_i2d_SSL_SESSION(WOLFSSL_SESSION* sess, unsigned char** p) #endif #endif /* !NO_WOLFSSL_SERVER && !NO_TLS */ #endif - data[idx++] = sess->peerAuthOk; + data[idx++] = (byte)(sess->peerAuthOk | (sess->isPSK << 1)); } #endif @@ -3366,7 +3366,9 @@ WOLFSSL_SESSION* wolfSSL_d2i_SSL_SESSION(WOLFSSL_SESSION** sess, ret = BUFFER_ERROR; goto end; } - s->peerAuthOk = (data[idx++] != 0); + s->peerAuthOk = (data[idx] & 0x01) != 0; + s->isPSK = (data[idx] & 0x02) != 0; + idx++; if (sess != NULL) { wolfSSL_FreeSession(NULL, *sess); @@ -3945,11 +3947,17 @@ void SetupSession(WOLFSSL* ssl) * own outcome; a resumed one keeps what it inherited unless post-handshake * auth added a certificate. */ if (ssl->options.side == WOLFSSL_SERVER_END) { - if (!ssl->options.resuming) + if (!ssl->options.resuming) { session->peerAuthOk = 0; + session->isPSK = ssl->options.usingPSK_cipher; + } if (SSL_PEER_CERT_VERIFIED(ssl)) session->peerAuthOk = 1; } + else { + session->peerAuthOk = 0; + session->isPSK = 0; + } #ifdef WOLFSSL_SESSION_ID_CTX /* If using compatibility layer then check for and copy over session context * id. */ diff --git a/src/tls13.c b/src/tls13.c index f39a52a3f96..3138967e9fd 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -6918,16 +6918,15 @@ static int FindPsk(WOLFSSL* ssl, PreSharedKey* psk, const byte* suite, int* err) if (ret == 0) { /* PSK negotiation has succeeded */ #ifdef WOLFSSL_CERT_WITH_EXTERN_PSK - /* RFC 9973 pairs the PSK with a certificate, so the PSK is not - * the client's authentication. Read before the extension is - * resolved for the connection. */ - if (!ssl->options.certWithExternPsk) + /* RFC 9973 applies only if the client sent the extension; the + * server opting in is not enough. */ + if (TLSX_Find(ssl->extensions, TLSX_CERT_WITH_EXTERN_PSK) == NULL) #endif { /* SERVER: using PSK for peer authentication, so no * certificate is requested below. */ ssl->options.peerAuthGood = 1; - ssl->options.isPSK = 1; + ssl->options.usingPSK_cipher = 1; } } } @@ -7095,9 +7094,9 @@ static int DoPreSharedKeys(WOLFSSL* ssl, const byte* input, word32 inputSz, #endif } #endif - /* A ticket is not the PSK failNoCertxPSK excuses. Before + /* failNoCertxPSK excuses only a ticket from a PSK session. Before * Finalize, so a declined ticket leaves nothing on the session. */ - if (ret == 0 && ClientAuthRequired(ssl, 0) && + if (ret == 0 && ClientAuthRequired(ssl, current->it->isPSK) && #ifdef WOLFSSL_POST_HANDSHAKE_AUTH /* Deferred, so this handshake asks for nothing. */ !ssl->options.verifyPostHandshake && @@ -13722,9 +13721,9 @@ int DoTls13Finished(WOLFSSL* ssl, const byte* input, word32* inOutIdx, !defined(NO_WOLFSSL_SERVER) /* verify the client sent certificate if required */ if ((!ssl->options.resuming || TLS13_AFTER_HANDSHAKE(ssl)) && - ClientAuthRequired(ssl, SSL_USING_PSK(ssl))) { + ClientAuthRequired(ssl, ssl->options.usingPSK_cipher)) { #ifdef OPENSSL_COMPATIBLE_DEFAULTS - if (ssl->options.isPSK && !TLS13_AFTER_HANDSHAKE(ssl)) { + if (ssl->options.usingPSK_cipher && !TLS13_AFTER_HANDSHAKE(ssl)) { WOLFSSL_MSG("TLS v1.3 client used PSK but cert required. Allowing " "for OpenSSL compatibility"); } @@ -18270,7 +18269,7 @@ int wolfSSL_accept_TLSv13(WOLFSSL* ssl) case TLS13_SERVER_EXTENSIONS_SENT : #ifndef NO_CERTS if (!ssl->options.resuming) { - if (ssl->options.verifyPeer && !ssl->options.isPSK + if (ssl->options.verifyPeer && !ssl->options.usingPSK_cipher #ifdef WOLFSSL_POST_HANDSHAKE_AUTH && !ssl->options.verifyPostHandshake #endif @@ -18339,7 +18338,7 @@ int wolfSSL_accept_TLSv13(WOLFSSL* ssl) #endif #ifdef HAVE_SESSION_TICKET #ifdef WOLFSSL_TLS13_TICKET_BEFORE_FINISHED - if ((!ssl->options.verifyPeer || ssl->options.isPSK) && + if ((!ssl->options.verifyPeer || ssl->options.usingPSK_cipher) && !ssl->options.noTicketTls13 && ssl->ctx->ticketEncCb != NULL && ssl->options.maxTicketTls13 > 0) { @@ -18396,7 +18395,7 @@ int wolfSSL_accept_TLSv13(WOLFSSL* ssl) !ssl->options.verifyPostHandshake && #endif !ssl->options.havePeerCert && - !ClientAuthRequired(ssl, SSL_USING_PSK(ssl))) { + !ClientAuthRequired(ssl, ssl->options.usingPSK_cipher)) { ssl->options.peerAuthGood = 1; } /* SERVER: check peer authentication. */ diff --git a/tests/api.c b/tests/api.c index 0750b4eced9..26ead94cf5c 100644 --- a/tests/api.c +++ b/tests/api.c @@ -23124,8 +23124,10 @@ static int test_wolfSSL_i2d_SSL_SESSION_peer_auth(void) /* Round trip keeps it, and consumes exactly the session. */ ptr = der; ExpectNotNull(restored = wolfSSL_d2i_SSL_SESSION(NULL, &ptr, (long)sz)); - if (restored != NULL) + if (restored != NULL) { ExpectIntEQ(restored->peerAuthOk, 1); + ExpectIntEQ(restored->isPSK, 0); + } ExpectPtrEq(ptr, der + sz); wolfSSL_SESSION_free(restored); restored = NULL; @@ -23152,6 +23154,22 @@ static int test_wolfSSL_i2d_SSL_SESSION_peer_auth(void) ExpectIntEQ(restored->peerAuthOk, 1); ExpectPtrEq(ptr, der + (2 * sz)); wolfSSL_SESSION_free(restored); + restored = NULL; + + /* isPSK shares the byte, and each flag decodes on its own. */ + if (sess != NULL) { + sess->peerAuthOk = 0; + sess->isPSK = 1; + } + pp = der; + ExpectIntEQ(wolfSSL_i2d_SSL_SESSION(sess, &pp), sz); + ptr = der; + ExpectNotNull(restored = wolfSSL_d2i_SSL_SESSION(NULL, &ptr, (long)sz)); + if (restored != NULL) { + ExpectIntEQ(restored->peerAuthOk, 0); + ExpectIntEQ(restored->isPSK, 1); + } + wolfSSL_SESSION_free(restored); XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); wolfSSL_SESSION_free(sess); diff --git a/tests/api/test_tls.c b/tests/api/test_tls.c index 7819fc813fa..4a49f71aa3f 100644 --- a/tests/api/test_tls.c +++ b/tests/api/test_tls.c @@ -3729,14 +3729,14 @@ int test_tls13_psk_clear_restores_verify_peer(void) wolfSSL_set_psk_server_callback(ssl_s, test_clear_psk_server_cb); ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); if (ssl_s != NULL) { - ExpectIntEQ(ssl_s->options.isPSK, 1); + ExpectIntEQ(ssl_s->options.usingPSK_cipher, 1); /* The configured mode outlives the connection. */ ExpectIntEQ(ssl_s->options.verifyPeer, 1); } ExpectIntEQ(wolfSSL_clear(ssl_s), WOLFSSL_SUCCESS); if (ssl_s != NULL) - ExpectIntEQ(ssl_s->options.isPSK, 0); + ExpectIntEQ(ssl_s->options.usingPSK_cipher, 0); /* A second client, with a certificate and no PSK, on the reused object. */ wolfSSL_free(ssl_c); @@ -4005,57 +4005,131 @@ int test_tls13_resume_ticket_fail_except_psk(void) return EXPECT_RESULT(); } -/* The other half: a ticket is not itself the PSK that failNoCertxPSK - * excuses, so one minted by an external-PSK session is declined too. The - * client offers that PSK alongside the ticket, so the connection still - * completes - as a fresh PSK handshake rather than a resumption. */ -int test_tls13_resume_psk_ticket_fail_except_psk(void) +/* With post-handshake auth the ticket's missing client auth is deferred, not + * excused: the later empty Certificate must still fail failNoCertxPSK. */ +int test_tls13_resume_ticket_fail_except_psk_post_handshake(void) { EXPECT_DECLS; #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TLS13) && \ - defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ - !defined(NO_SESSION_CACHE) && !defined(NO_PSK) && !defined(NO_CERTS) && \ - !defined(WOLFSSL_NO_CLIENT_AUTH) + defined(WOLFSSL_POST_HANDSHAKE_AUTH) && defined(HAVE_SESSION_TICKET) && \ + !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && !defined(NO_SESSION_CACHE) && \ + !defined(NO_CERTS) && !defined(WOLFSSL_NO_CLIENT_AUTH) WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; WOLFSSL *ssl_c = NULL, *ssl_s = NULL; WOLFSSL_SESSION* sess = NULL; struct test_memio_ctx test_ctx; - byte readBuf[16]; + char msg[] = "hello wolfssl!"; + char readBuf[sizeof(msg)]; XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_allow_post_handshake_auth(ctx_c), 0); 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_clear_psk_client_cb); - wolfSSL_set_psk_server_callback(ssl_s, test_clear_psk_server_cb); - ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); 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)); - /* The PSK authenticated the client, so no certificate was recorded. */ if (ssl_s != NULL) ExpectIntEQ(ssl_s->session->peerAuthOk, 0); wolfSSL_free(ssl_c); ssl_c = NULL; wolfSSL_free(ssl_s); ssl_s = NULL; + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_PEER | + WOLFSSL_VERIFY_FAIL_EXCEPT_PSK | WOLFSSL_VERIFY_POST_HANDSHAKE, NULL); + ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_session_reused(ssl_s), 1); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + + /* The certificate-less client answers with an empty Certificate. */ + 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, readBuf, sizeof(readBuf) - 1), + (int)sizeof(msg) - 1); + ExpectIntEQ(wolfSSL_write(ssl_c, msg, (int)sizeof(msg) - 1), + (int)sizeof(msg) - 1); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf) - 1), + WOLFSSL_FATAL_ERROR); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->error, WC_NO_ERR_TRACE(NO_PEER_CERT)); + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* The other half: a ticket from an external-PSK session carries the PSK, so + * failNoCertxPSK still excuses it, and so does the ticket that resumption + * issues in turn. */ +int test_tls13_resume_psk_ticket_fail_except_psk_ok(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TLS13) && \ + defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + !defined(NO_SESSION_CACHE) && !defined(NO_PSK) && !defined(NO_CERTS) && \ + !defined(WOLFSSL_NO_CLIENT_AUTH) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL_SESSION *sess = NULL, *sess2 = NULL; + struct test_memio_ctx test_ctx; + byte readBuf[16]; + int 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, wolfTLSv1_3_server_method), 0); wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); - wolfSSL_set_verify(ssl_s, - WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_EXCEPT_PSK, NULL); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_NONE, NULL); wolfSSL_set_psk_client_callback(ssl_c, test_clear_psk_client_cb); wolfSSL_set_psk_server_callback(ssl_s, test_clear_psk_server_cb); - ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS); ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); - ExpectIntEQ(wolfSSL_session_reused(ssl_s), 0); - /* The external PSK identity, offered alongside the ticket, authenticated - * the client in the ticket's place. */ + 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)); + /* The PSK authenticated the client, so no certificate was recorded. */ if (ssl_s != NULL) { - ExpectIntEQ(ssl_s->options.isPSK, 1); - ExpectIntEQ(ssl_s->options.peerAuthGood, 1); + ExpectIntEQ(ssl_s->session->isPSK, 1); + ExpectIntEQ(ssl_s->session->peerAuthOk, 0); + } + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + + /* The first resumption, then one from the ticket it issued. */ + for (i = 0; i < 2; 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, + wolfTLSv1_3_server_method), 0); + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); + wolfSSL_set_verify(ssl_s, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_EXCEPT_PSK, NULL); + wolfSSL_set_psk_client_callback(ssl_c, test_clear_psk_client_cb); + wolfSSL_set_psk_server_callback(ssl_s, test_clear_psk_server_cb); + ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + ExpectIntEQ(wolfSSL_session_reused(ssl_s), 1); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectNotNull(sess2 = wolfSSL_get1_session(ssl_c)); + wolfSSL_SESSION_free(sess); + sess = sess2; + sess2 = NULL; + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; } wolfSSL_SESSION_free(sess); @@ -5514,6 +5588,92 @@ int test_tls12_session_id_resumption_sni_mismatch(void) return EXPECT_RESULT(); } +/* A TLS 1.2 ticket that decrypts is copied to the session before it can be + * declined, and the server writes its next ticket before SetupSession() + * resets that. The new ticket must record only what the full handshake + * proved, or it carries the declined ticket's client auth. */ +int test_tls12_declined_ticket_auth_not_reminted(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(WOLFSSL_NO_TLS12) && defined(HAVE_SNI) && \ + defined(HAVE_SESSION_TICKET) && !defined(NO_SESSION_CACHE) && \ + !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && !defined(NO_CERTS) && \ + !defined(NO_RSA) && !defined(WOLFSSL_NO_CLIENT_AUTH) && \ + defined(WOLFSSL_PEM_TO_DER) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL_SESSION *sess = NULL, *sess2 = NULL; + struct test_memio_ctx test_ctx; + const char* sniA = "public.example"; + const char* sniB = "admin.example"; + + /* 1: the client authenticates. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, cliCertFile, NULL), + WOLFSSL_SUCCESS); + wolfSSL_CTX_set_servername_callback(ctx_s, accept_any_sni_cb); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_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_UseSessionTicket(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSNI(ssl_c, WOLFSSL_SNI_HOST_NAME, + sniA, (word16)XSTRLEN(sniA)), WOLFSSL_SUCCESS); + wolfSSL_set_verify(ssl_s, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + + /* 2: under another SNI the ticket decrypts, is declined, and the full + * handshake that follows, on a lax server, authenticates nothing. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSNI(ssl_c, WOLFSSL_SNI_HOST_NAME, + sniB, (word16)XSTRLEN(sniB)), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_NONE, NULL); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_session_reused(ssl_s), 0); + ExpectNotNull(sess2 = wolfSSL_get1_session(ssl_c)); + if (sess2 != NULL) + ExpectIntGT(sess2->ticketLen, 0); + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + + /* 3: that ticket must not satisfy a server requiring a certificate. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSNI(ssl_c, WOLFSSL_SNI_HOST_NAME, + sniB, (word16)XSTRLEN(sniB)), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_session(ssl_c, sess2), WOLFSSL_SUCCESS); + wolfSSL_set_verify(ssl_s, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + ExpectIntEQ(wolfSSL_session_reused(ssl_s), 0); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->error, WC_NO_ERR_TRACE(NO_PEER_CERT)); + + wolfSSL_SESSION_free(sess); + wolfSSL_SESSION_free(sess2); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + /* TLS 1.3 PSK resumption must fall back to a full handshake if the SNI in * the resumed ClientHello does not match the SNI bound to the original * session (RFC 6066 Section 3 / RFC 8446 Section 4.6.1). */ diff --git a/tests/api/test_tls.h b/tests/api/test_tls.h index 8d3363d3733..8c44fcad812 100644 --- a/tests/api/test_tls.h +++ b/tests/api/test_tls.h @@ -69,7 +69,8 @@ int test_tls12_resume_ticket_mutual_auth(void); int test_tls13_resume_psk_post_handshake_auth(void); int test_tls13_resume_psk_client_auth(void); int test_tls13_resume_ticket_fail_except_psk(void); -int test_tls13_resume_psk_ticket_fail_except_psk(void); +int test_tls13_resume_ticket_fail_except_psk_post_handshake(void); +int test_tls13_resume_psk_ticket_fail_except_psk_ok(void); int test_tls13_resume_psk_client_auth_ok(void); int test_tls12_ext_cache_client_auth_resume(void); int test_tls12_resume_ticket_client_auth(void); @@ -90,6 +91,7 @@ int test_tls12_server_hello_tls13_suite(void); int test_tls12_client_hello_no_tls13_suites(void); int test_tls_set_session_min_downgrade(void); int test_tls12_session_id_resumption_sni_mismatch(void); +int test_tls12_declined_ticket_auth_not_reminted(void); int test_tls13_session_resumption_sni_mismatch(void); int test_tls13_resumption_with_alpn(void); int test_tls12_session_id_resumption_alpn_mismatch(void); @@ -171,7 +173,9 @@ int test_tls_param_flags_crl_check(void); TEST_DECL_GROUP("tls", test_tls13_resume_psk_client_auth), \ TEST_DECL_GROUP("tls", test_tls13_resume_ticket_fail_except_psk), \ TEST_DECL_GROUP("tls", \ - test_tls13_resume_psk_ticket_fail_except_psk), \ + test_tls13_resume_ticket_fail_except_psk_post_handshake), \ + TEST_DECL_GROUP("tls", \ + test_tls13_resume_psk_ticket_fail_except_psk_ok), \ TEST_DECL_GROUP("tls", test_tls13_resume_psk_client_auth_ok), \ TEST_DECL_GROUP("tls", test_tls12_ext_cache_client_auth_resume), \ TEST_DECL_GROUP("tls", test_tls12_resume_ticket_client_auth), \ @@ -193,6 +197,7 @@ int test_tls_param_flags_crl_check(void); TEST_DECL_GROUP("tls", test_tls12_client_hello_no_tls13_suites), \ TEST_DECL_GROUP("tls", test_tls_set_session_min_downgrade), \ TEST_DECL_GROUP("tls", test_tls12_session_id_resumption_sni_mismatch), \ + TEST_DECL_GROUP("tls", test_tls12_declined_ticket_auth_not_reminted), \ TEST_DECL_GROUP("tls", test_tls13_session_resumption_sni_mismatch), \ TEST_DECL_GROUP("tls", test_tls13_resumption_with_alpn), \ TEST_DECL_GROUP("tls", test_tls12_session_id_resumption_alpn_mismatch),\ diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index 18730cb07f5..cb18b253316 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -3069,7 +3069,7 @@ static int test_tls13_psk_age_alert(const char* identity, int* alertOut, * and not to a fixture that never negotiated a PSK at all. */ ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); if (ssl_s != NULL) { - ExpectIntEQ(ssl_s->options.isPSK, 1); + ExpectIntEQ(ssl_s->options.usingPSK_cipher, 1); } ExpectIntEQ(test_tls13_psk_age_matched, 1); @@ -3095,12 +3095,13 @@ static int test_tls13_psk_age_alert(const char* identity, int* alertOut, ExpectIntEQ(wolfSSL_get_error(ssl_s, WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), WC_NO_ERR_TRACE(BAD_BINDER)); - /* isPSK is set only after FindPskSuite() reported a match and the early - * secret was derived, so it proves the known run really took the matched - * path. The callback flag alone would not: the callback runs before the - * ciphersuite match that decides whether the PSK is used at all. */ + /* usingPSK_cipher is set only after FindPskSuite() reported a match and + * the early secret was derived, so it proves the known run really took + * the matched path. The callback flag alone would not: the callback runs + * before the ciphersuite match that decides whether the PSK is used at + * all. */ if (ssl_s != NULL) { - ExpectIntEQ(ssl_s->options.isPSK, expectMatch); + ExpectIntEQ(ssl_s->options.usingPSK_cipher, expectMatch); } ExpectIntEQ(test_tls13_psk_age_matched, expectMatch); @@ -4851,19 +4852,31 @@ static unsigned int test_tls13_pha_psk_server_cb(WOLFSSL* ssl, const char* id, } #endif +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TLS13) && \ + defined(WOLFSSL_CERT_WITH_EXTERN_PSK) && !defined(NO_PSK) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_RSA) && !defined(NO_CERTS) && defined(WOLFSSL_CERT_SETUP_CB) +static int test_tls13_psk_forget_auth_cb(WOLFSSL* ssl, void* arg) +{ + (void)arg; + ssl->options.usingPSK_cipher = 0; + return 1; +} +#endif + /* Bounds the other side of the RFC 8446 4.3.2 relaxation: a CertificateRequest * during the main handshake under a PSK must still be rejected. The client is * untouched here - it has not sent its Finished, so handShakeDone is 0 on its - * own. A wolfSSL server normally clears verifyPeer once a PSK is chosen and so - * never sends one; cert_with_extern_psk on the server alone suppresses that - * clearing, which makes it emit the request the client must refuse. */ + * own. A wolfSSL server never sends one once a PSK authenticates the client, + * so the cert setup callback, which runs after the PSK is chosen, makes it + * forget that and emit the request the client must refuse. */ int test_tls13_psk_cert_request_in_handshake(void) { EXPECT_DECLS; #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TLS13) && \ defined(WOLFSSL_CERT_WITH_EXTERN_PSK) && !defined(NO_PSK) && \ !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ - !defined(NO_RSA) && !defined(NO_CERTS) + !defined(NO_RSA) && !defined(NO_CERTS) && defined(WOLFSSL_CERT_SETUP_CB) WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; WOLFSSL *ssl_c = NULL, *ssl_s = NULL; struct test_memio_ctx test_ctx; @@ -4873,9 +4886,7 @@ int test_tls13_psk_cert_request_in_handshake(void) wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); wolfSSL_set_psk_client_callback(ssl_c, test_tls13_pha_psk_client_cb); wolfSSL_set_psk_server_callback(ssl_s, test_tls13_pha_psk_server_cb); - /* Server side only: the client neither offers the extension nor takes the - * matching exemption, so it must reject on message order alone. */ - ExpectIntEQ(wolfSSL_set_cert_with_extern_psk(ssl_s, 1), WOLFSSL_SUCCESS); + wolfSSL_CTX_set_cert_cb(ctx_s, test_tls13_psk_forget_auth_cb, NULL); wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_PEER, NULL); ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); @@ -4892,6 +4903,44 @@ int test_tls13_psk_cert_request_in_handshake(void) return EXPECT_RESULT(); } +/* Opting a server into cert_with_extern_psk does not make RFC 9973 apply: a + * client that did not send the extension is authenticated by its PSK, so no + * CertificateRequest is sent. */ +int test_tls13_cert_with_extern_psk_not_offered(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TLS13) && \ + defined(WOLFSSL_CERT_WITH_EXTERN_PSK) && !defined(NO_PSK) && \ + !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; + + 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_psk_client_callback(ssl_c, test_tls13_pha_psk_client_cb); + wolfSSL_set_psk_server_callback(ssl_s, test_tls13_pha_psk_server_cb); + ExpectIntEQ(wolfSSL_set_cert_with_extern_psk(ssl_s, 1), WOLFSSL_SUCCESS); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_PEER, NULL); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + if (ssl_s != NULL) { + ExpectIntEQ(ssl_s->options.usingPSK_cipher, 1); + ExpectIntEQ(ssl_s->options.peerAuthGood, 1); + ExpectIntEQ(ssl_s->options.havePeerCert, 0); + } + ExpectIntEQ(ssl_c->options.certWithExternPsk, 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + /* A client with no certificate answers the post-handshake request with an empty * Certificate; a server requiring one must reject it. Pins behaviour on the * resumed path that nothing else covers - the rejection is in ProcessPeerCerts() @@ -16570,7 +16619,7 @@ int test_tls13_psk_key_zeroized(void) wolfSSL_KeepArrays(ssl_s); ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); - ExpectIntEQ(ssl_s->options.isPSK, 1); + ExpectIntEQ(ssl_s->options.usingPSK_cipher, 1); ExpectIntEQ(XMEMCMP(ssl_c->arrays->psk_key, zeros, MAX_PSK_KEY_LEN), 0); ExpectIntEQ(XMEMCMP(ssl_s->arrays->psk_key, zeros, MAX_PSK_KEY_LEN), 0); diff --git a/tests/api/test_tls13.h b/tests/api/test_tls13.h index 123831145b1..50013e8a336 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -39,6 +39,7 @@ int test_tls13_pha_after_early_data(void); int test_tls13_pha_resumption_bare_finished(void); int test_tls13_pha_resumption_blank_cert(void); int test_tls13_psk_cert_request_in_handshake(void); +int test_tls13_cert_with_extern_psk_not_offered(void); int test_tls13_pha_resumption_second_round(void); int test_tls13_rpk_untrusted(void); int test_tls13_rpk_trust(void); @@ -218,6 +219,7 @@ int test_tls13_cks_hrr_reparse(void); TEST_DECL_GROUP("tls13", test_tls13_pha_resumption_bare_finished), \ TEST_DECL_GROUP("tls13", test_tls13_pha_resumption_blank_cert), \ TEST_DECL_GROUP("tls13", test_tls13_psk_cert_request_in_handshake), \ + TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_not_offered), \ TEST_DECL_GROUP("tls13", test_tls13_pha_resumption_second_round), \ TEST_DECL_GROUP("tls13", test_tls13_rpk_untrusted), \ TEST_DECL_GROUP("tls13", test_tls13_rpk_trust), \ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 2e3e5b0ec44..0bd74cc3708 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -2418,26 +2418,13 @@ WOLFSSL_LOCAL int CookiePolicySet(WOLFSSL* ssl, const byte* hrrSecret, WOLFSSL_LOCAL int CookiePolicyEnable(WOLFSSL* ssl); WOLFSSL_LOCAL int CheckCookieState(WOLFSSL* ssl); #endif -/* Is this connection authenticating with a PSK? TLS 1.3 uses the extension, - * earlier versions a PSK cipher suite. */ -#if defined(WOLFSSL_TLS13) && (defined(HAVE_SESSION_TICKET) || \ - !defined(NO_PSK)) - #define SSL_USING_PSK(ssl) \ - ((ssl)->options.usingPSK_cipher || (ssl)->options.pskNegotiated) -#else - #define SSL_USING_PSK(ssl) ((ssl)->options.usingPSK_cipher) -#endif -/* A verified client certificate with proof of possession. Nothing is verified - * under verifyNone. */ +/* A verified client certificate with proof of possession, on this connection + * or the one it resumed. Nothing is verified under verifyNone. */ #define SSL_PEER_CERT_VERIFIED(ssl) \ - ((ssl)->options.havePeerCert && (ssl)->options.havePeerVerify && \ - !(ssl)->options.verifyNone) -#if !defined(NO_CERTS) && !defined(NO_WOLFSSL_SERVER) && !defined(NO_TLS) + (((ssl)->options.havePeerCert && (ssl)->options.havePeerVerify && \ + !(ssl)->options.verifyNone) || \ + ((ssl)->options.resuming && (ssl)->session->peerAuthOk)) WOLFSSL_LOCAL int ClientAuthRequired(const WOLFSSL* ssl, int usingPsk); -#else -/* Only a TLS server asks for a certificate, so only it can require one. */ -#define ClientAuthRequired(ssl, usingPsk) 0 -#endif WOLFSSL_LOCAL int DoClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, word32 helloSz); #ifdef WOLFSSL_TLS13 @@ -4035,6 +4022,7 @@ typedef struct InternalTicket { byte timestamp[TIMESTAMP_LEN]; /* born on */ byte haveEMS:1; /* have extended master secret */ byte peerAuthOk:1; /* client cert was verified */ + byte isPSK:1; /* a PSK authenticated the client */ #ifdef WOLFSSL_TLS13 byte ageAdd[AGEADD_LEN]; /* Obfuscation of age */ byte namedGroup[NAMEDGROUP_LEN]; /* Named group used */ @@ -5439,8 +5427,10 @@ struct WOLFSSL_SESSION { #endif /* HAVE_MAX_FRAGMENT */ byte isSetup:1; /* Client presented a certificate and proved key possession. Server - * sessions only; shares isSetup's byte so the layout is unchanged. */ + * sessions only. */ byte peerAuthOk:1; + /* A PSK authenticated the client. Server sessions only. */ + byte isPSK:1; }; #if defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) && \ @@ -5820,7 +5810,6 @@ struct Options { #ifdef HAVE_SECURE_RENEGOTIATION word16 resumed:1; /* resuming may be reset on SCR */ #endif - word16 isPSK:1; word16 haveSessionId:1; /* server may not send */ word16 tls:1; /* using TLS ? */ word16 tls1_1:1; /* using TLSv1.1+ ? */ From ec59b5906758ff0d0bb383a8351513df512527b1 Mon Sep 17 00:00:00 2001 From: Kareem Date: Mon, 26 Oct 2026 10:49:49 -0700 Subject: [PATCH 10/11] Fix MSVC warning --- src/ssl_sess.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ssl_sess.c b/src/ssl_sess.c index 23a0865947c..b90131d9f00 100644 --- a/src/ssl_sess.c +++ b/src/ssl_sess.c @@ -3949,7 +3949,7 @@ void SetupSession(WOLFSSL* ssl) if (ssl->options.side == WOLFSSL_SERVER_END) { if (!ssl->options.resuming) { session->peerAuthOk = 0; - session->isPSK = ssl->options.usingPSK_cipher; + session->isPSK = (byte)ssl->options.usingPSK_cipher; } if (SSL_PEER_CERT_VERIFIED(ssl)) session->peerAuthOk = 1; From 69b23051328997c6da5aa1e771fa65fcb107a657 Mon Sep 17 00:00:00 2001 From: Kareem Date: Wed, 28 Oct 2026 13:56:32 -0700 Subject: [PATCH 11/11] Properly set PSK status for TLS 1.3 resumption. Gate WOLFSSL_SESSION members on !NO_WOLFSSL_SERVER. --- src/internal.c | 3 +- src/ssl.c | 2 ++ src/ssl_sess.c | 8 +++++ src/tls13.c | 2 ++ tests/api.c | 2 +- tests/api/test_tls.c | 75 ++++++++++++++++++++++++++++++++++++++++++++ tests/api/test_tls.h | 3 ++ wolfssl/internal.h | 2 ++ 8 files changed, 94 insertions(+), 3 deletions(-) diff --git a/src/internal.c b/src/internal.c index 65f5f522d4a..240cbe4761e 100644 --- a/src/internal.c +++ b/src/internal.c @@ -44533,8 +44533,7 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) it->suite[1] = ssl->options.cipherSuite; it->peerAuthOk = (byte)SSL_PEER_CERT_VERIFIED(ssl); - it->isPSK = (byte)(ssl->options.usingPSK_cipher || - (ssl->options.resuming && ssl->session->isPSK)); + it->isPSK = (byte)ssl->options.usingPSK_cipher; #ifdef WOLFSSL_EARLY_DATA c32toa(ssl->options.maxEarlyDataSz, it->maxEarlyDataSz); diff --git a/src/ssl.c b/src/ssl.c index 9f4c4e998c6..7d0cb3cc46d 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -5809,8 +5809,10 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out, else if (ssl->options.side == WOLFSSL_SERVER_END) { ssl->session->haveAltSessionID = 0; ForceZero(ssl->session->altSessionID, ID_LEN); +#ifndef NO_WOLFSSL_SERVER ssl->session->peerAuthOk = 0; ssl->session->isPSK = 0; +#endif #if defined(HAVE_SESSION_TICKET) && defined(WOLFSSL_TLS13) ssl->session->ticketNonce.len = 0; #endif diff --git a/src/ssl_sess.c b/src/ssl_sess.c index b90131d9f00..341ccd77afc 100644 --- a/src/ssl_sess.c +++ b/src/ssl_sess.c @@ -2953,8 +2953,10 @@ int wolfSSL_i2d_SSL_SESSION(WOLFSSL_SESSION* sess, unsigned char** p) #endif #endif /* !NO_WOLFSSL_SERVER && !NO_TLS */ #endif +#ifndef NO_WOLFSSL_SERVER /* peerAuthOk and isPSK */ size += OPAQUE8_LEN; +#endif if (p != NULL) { unsigned char *data; @@ -3050,7 +3052,9 @@ int wolfSSL_i2d_SSL_SESSION(WOLFSSL_SESSION* sess, unsigned char** p) #endif #endif /* !NO_WOLFSSL_SERVER && !NO_TLS */ #endif +#ifndef NO_WOLFSSL_SERVER data[idx++] = (byte)(sess->peerAuthOk | (sess->isPSK << 1)); +#endif } #endif @@ -3362,6 +3366,7 @@ WOLFSSL_SESSION* wolfSSL_d2i_SSL_SESSION(WOLFSSL_SESSION** sess, #endif #endif /* !NO_WOLFSSL_SERVER && !NO_TLS */ #endif +#ifndef NO_WOLFSSL_SERVER if (i - idx < OPAQUE8_LEN) { ret = BUFFER_ERROR; goto end; @@ -3369,6 +3374,7 @@ WOLFSSL_SESSION* wolfSSL_d2i_SSL_SESSION(WOLFSSL_SESSION** sess, s->peerAuthOk = (data[idx] & 0x01) != 0; s->isPSK = (data[idx] & 0x02) != 0; idx++; +#endif if (sess != NULL) { wolfSSL_FreeSession(NULL, *sess); @@ -3946,6 +3952,7 @@ void SetupSession(WOLFSSL* ssl) * a different statement that nothing reads. A full handshake records its * own outcome; a resumed one keeps what it inherited unless post-handshake * auth added a certificate. */ +#ifndef NO_WOLFSSL_SERVER if (ssl->options.side == WOLFSSL_SERVER_END) { if (!ssl->options.resuming) { session->peerAuthOk = 0; @@ -3958,6 +3965,7 @@ void SetupSession(WOLFSSL* ssl) session->peerAuthOk = 0; session->isPSK = 0; } +#endif #ifdef WOLFSSL_SESSION_ID_CTX /* If using compatibility layer then check for and copy over session context * id. */ diff --git a/src/tls13.c b/src/tls13.c index 3138967e9fd..7cf731a3d2f 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -7166,6 +7166,8 @@ static int DoPreSharedKeys(WOLFSSL* ssl, const byte* input, word32 inputSz, ret = SetCipherSpecs(ssl); if (ret != 0) goto cleanup; + /* TLS 1.3 suites do not record it, so restore it from the ticket. */ + ssl->options.usingPSK_cipher = ssl->session->isPSK; /* Resumption PSK is resumption master secret. */ ssl->arrays->psk_keySz = ssl->specs.hash_size; diff --git a/tests/api.c b/tests/api.c index 26ead94cf5c..f9ad8cff0de 100644 --- a/tests/api.c +++ b/tests/api.c @@ -23095,7 +23095,7 @@ static int test_wolfSSL_i2d_SSL_SESSION_peer_auth(void) { EXPECT_DECLS; #if defined(OPENSSL_EXTRA) && defined(HAVE_EXT_CACHE) && \ - !defined(NO_SESSION_CACHE) + !defined(NO_SESSION_CACHE) && !defined(NO_WOLFSSL_SERVER) WOLFSSL_SESSION* sess = NULL; WOLFSSL_SESSION* restored = NULL; unsigned char* der = NULL; diff --git a/tests/api/test_tls.c b/tests/api/test_tls.c index 4a49f71aa3f..6ead5e411cf 100644 --- a/tests/api/test_tls.c +++ b/tests/api/test_tls.c @@ -4071,6 +4071,81 @@ int test_tls13_resume_ticket_fail_except_psk_post_handshake(void) return EXPECT_RESULT(); } +/* A ticket from an external-PSK connection keeps its exemption through + * post-handshake auth. */ +int test_tls13_resume_psk_ticket_fail_except_psk_post_handshake(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TLS13) && \ + defined(WOLFSSL_POST_HANDSHAKE_AUTH) && defined(HAVE_SESSION_TICKET) && \ + !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && !defined(NO_SESSION_CACHE) && \ + !defined(NO_PSK) && !defined(NO_CERTS) && !defined(WOLFSSL_NO_CLIENT_AUTH) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL_SESSION* sess = NULL; + struct test_memio_ctx test_ctx; + char msg[] = "hello wolfssl!"; + char readBuf[sizeof(msg)]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_allow_post_handshake_auth(ctx_c), 0); + 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_clear_psk_client_cb); + wolfSSL_set_psk_server_callback(ssl_s, test_clear_psk_server_cb); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + 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; + + /* Only the ticket is offered this time. */ + 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_PEER | + WOLFSSL_VERIFY_FAIL_EXCEPT_PSK | WOLFSSL_VERIFY_POST_HANDSHAKE, NULL); + ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + ExpectIntEQ(wolfSSL_session_reused(ssl_s), 1); + if (ssl_s != NULL) { + ExpectIntEQ(ssl_s->options.usingPSK_cipher, 1); + ExpectIntEQ(ssl_s->session->isPSK, 1); + } + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + + /* The certificate-less client answers with an empty Certificate. */ + 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, readBuf, sizeof(readBuf) - 1), + (int)sizeof(msg) - 1); + ExpectIntEQ(wolfSSL_write(ssl_c, msg, (int)sizeof(msg) - 1), + (int)sizeof(msg) - 1); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf) - 1), + (int)sizeof(msg) - 1); + if (ssl_s != NULL) { + ExpectIntEQ(ssl_s->error, 0); + ExpectIntEQ(ssl_s->options.havePeerCert, 0); + } + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + /* The other half: a ticket from an external-PSK session carries the PSK, so * failNoCertxPSK still excuses it, and so does the ticket that resumption * issues in turn. */ diff --git a/tests/api/test_tls.h b/tests/api/test_tls.h index 8c44fcad812..a63a16bdc77 100644 --- a/tests/api/test_tls.h +++ b/tests/api/test_tls.h @@ -70,6 +70,7 @@ int test_tls13_resume_psk_post_handshake_auth(void); int test_tls13_resume_psk_client_auth(void); int test_tls13_resume_ticket_fail_except_psk(void); int test_tls13_resume_ticket_fail_except_psk_post_handshake(void); +int test_tls13_resume_psk_ticket_fail_except_psk_post_handshake(void); int test_tls13_resume_psk_ticket_fail_except_psk_ok(void); int test_tls13_resume_psk_client_auth_ok(void); int test_tls12_ext_cache_client_auth_resume(void); @@ -174,6 +175,8 @@ int test_tls_param_flags_crl_check(void); TEST_DECL_GROUP("tls", test_tls13_resume_ticket_fail_except_psk), \ TEST_DECL_GROUP("tls", \ test_tls13_resume_ticket_fail_except_psk_post_handshake), \ + TEST_DECL_GROUP("tls", \ + test_tls13_resume_psk_ticket_fail_except_psk_post_handshake), \ TEST_DECL_GROUP("tls", \ test_tls13_resume_psk_ticket_fail_except_psk_ok), \ TEST_DECL_GROUP("tls", test_tls13_resume_psk_client_auth_ok), \ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 0bd74cc3708..6a2cd709e14 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -5426,11 +5426,13 @@ struct WOLFSSL_SESSION { * WOLFSSL_MFL_2_8 (6) */ #endif /* HAVE_MAX_FRAGMENT */ byte isSetup:1; +#ifndef NO_WOLFSSL_SERVER /* Client presented a certificate and proved key possession. Server * sessions only. */ byte peerAuthOk:1; /* A PSK authenticated the client. Server sessions only. */ byte isPSK:1; +#endif }; #if defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) && \