From ec8013a7d66496551a85e9c7f829386ae053b9fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Sat, 7 Nov 2026 09:11:01 +0100 Subject: [PATCH 1/2] Ignore the ServerHello session ID after offering a TLS 1.2 ticket A TLS 1.2 client sends its ticket with an empty session ID and learns from the server's next handshake message whether the ticket was accepted, as RFC 5077 Section 3.4 allows. When a Certificate or ServerKeyExchange follows, the client falls back to a full handshake, but only if the ServerHello carried no session ID: a session ID was taken as a promise to resume, and the handshake failed with OUT_OF_ORDER_E. A server that declines the ticket may still send a session ID, to offer stateful resumption (RFC 5077 Section 3.4), and a client that sent an empty session ID ignores it. Any server that declined or ignored the ticket and sent a session ID made the client fail. That includes a server without ticket support, and a wolfSSL server with its session cache on, for example after a restart with a new ticket key. A server that declines with an empty session ID, as OpenSSL does, was not affected. Keep the fatal error for a session ID the client sent and the server echoed, and ignore the ServerHello session ID when the client offered a ticket. After the fallback the client also kept the declined ticket next to the new session. Its next connection offered that ticket, so a server that could still decrypt it resumed with the ticket's master secret while the client used the new one, and the handshake failed. The fallback now drops the ticket, as a failed Finished check already does, and the client resumes by the new session ID instead. The fallback also drops the session ID the client made up for the ticket. With that ID left in place, SetupSession() stored the new session under it, and the next connection offered it in place of the server's session ID. test_tls12_resume_ticket_decline_fallback turned the server's session cache off to avoid the first failure. It now keeps the cache on, checks that the declining server sent a session ID, and resumes a third connection by that session ID. It fails without this change. A second run gets its first ticket with an empty session ID, so the client makes up an ID, and frees every other reference to the session before the fallback, so the made-up ID survives to SetupSession(). It fails without the new reset. --- src/internal.c | 30 ++++++++++++++++++++--- tests/api/test_tls.c | 58 +++++++++++++++++++++++++++++++++++++------- 2 files changed, 75 insertions(+), 13 deletions(-) diff --git a/src/internal.c b/src/internal.c index ea0c39e8482..46ccd5e6470 100644 --- a/src/internal.c +++ b/src/internal.c @@ -22543,9 +22543,8 @@ int DoHandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx, * "certificate" or "server_key_exchange" are received then we * are doing a full handshake */ - /* If the server included a session id then we - * treat this as a fatal error, since the server said it was - * doing resumption, but did not. */ + /* A server that echoed the client's session id said it was + * resuming, so a full handshake after it is a fatal error. */ /* https://www.rfc-editor.org/rfc/rfc5077.html#section-3.4 * Alternatively, the client MAY include an empty Session ID @@ -22555,7 +22554,12 @@ int DoHandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx, * messages. */ #ifndef WOLFSSL_WPAS - if (ssl->arrays->sessionIDSz != 0) { + if (ssl->arrays->sessionIDSz != 0 + #ifdef HAVE_SESSION_TICKET + /* A ticket goes out with an empty session ID */ + && ssl->session->ticketLen == 0 + #endif + ) { /* Fatal error. Only try to send an alert. RFC 5246 does not * allow for reverting back to a full handshake after the * server has indicated the intention to do a resumption. */ @@ -22571,6 +22575,24 @@ int DoHandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx, ssl->options.resuming = 0; /* No longer resuming, reset peer authentication state. */ ssl->options.peerAuthGood = 0; +#if defined(HAVE_SESSION_TICKET) && !defined(NO_WOLFSSL_CLIENT) + /* The declined ticket does not belong to the new session */ + if (ssl->session->ticketLen > 0) { + if (!HaveUniqueSessionObj(ssl)) + return MEMORY_ERROR; + ForceZero(ssl->session->ticket, ssl->session->ticketLen); + if (ssl->session->ticketLenAlloc > 0) { + XFREE(ssl->session->ticket, ssl->session->heap, + DYNAMIC_TYPE_SESSION_TICK); + ssl->session->ticket = ssl->session->staticTicket; + ssl->session->ticketLenAlloc = 0; + } + ssl->session->ticketLen = 0; + /* The ID made up for the ticket goes with it */ + ForceZero(ssl->session->altSessionID, ID_LEN); + ssl->session->haveAltSessionID = 0; + } +#endif } } } diff --git a/tests/api/test_tls.c b/tests/api/test_tls.c index 1dc587d3ec5..252a9c1eeb7 100644 --- a/tests/api/test_tls.c +++ b/tests/api/test_tls.c @@ -5051,9 +5051,6 @@ int test_tls12_reuse_clears_use_ticket(void) * F-5811 suite check must not abort it. The second handshake uses a fresh * server CTX (new ticket key -> decline) offering only suite B while the client * offers B and the session's suite A. */ -int test_tls12_resume_ticket_decline_fallback(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) && \ @@ -5061,6 +5058,9 @@ int test_tls12_resume_ticket_decline_fallback(void) !defined(NO_AES) && defined(HAVE_AESGCM) && !defined(NO_SHA256) && \ defined(BUILD_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) && \ defined(BUILD_TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) +static int test_tls12_resume_ticket_decline_fallback_ex(int soleRef) +{ + EXPECT_DECLS; const char* suiteA = "ECDHE-RSA-AES128-GCM-SHA256"; const char* suiteB = "ECDHE-RSA-AES256-GCM-SHA384"; const char* suiteBA = @@ -5068,9 +5068,11 @@ int test_tls12_resume_ticket_decline_fallback(void) WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL, *ctx_s2 = NULL; WOLFSSL *ssl_c = NULL, *ssl_s = NULL; WOLFSSL *ssl_c2 = NULL, *ssl_s2 = NULL; - WOLFSSL_SESSION *sess = NULL; + WOLFSSL *ssl_c3 = NULL, *ssl_s3 = NULL; + WOLFSSL_SESSION *sess = NULL, *sess2 = NULL; struct test_memio_ctx test_ctx; struct test_memio_ctx test_ctx2; + struct test_memio_ctx test_ctx3; /* First handshake: establish a ticket-based TLS 1.2 session on suite A. */ XMEMSET(&test_ctx, 0, sizeof(test_ctx)); @@ -5079,6 +5081,9 @@ int test_tls12_resume_ticket_decline_fallback(void) ExpectIntEQ(wolfSSL_set_cipher_list(ssl_c, suiteA), WOLFSSL_SUCCESS); ExpectIntEQ(wolfSSL_set_cipher_list(ssl_s, suiteA), WOLFSSL_SUCCESS); ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c), WOLFSSL_SUCCESS); + /* A ticket with an empty session ID gives the client a made-up ID */ + if (soleRef && ssl_s != NULL) + ssl_s->options.sessionCacheOff = 1; ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); ExpectIntGT(sess->ticketLen, 0); @@ -5090,26 +5095,61 @@ int test_tls12_resume_ticket_decline_fallback(void) wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); ExpectIntEQ(wolfSSL_set_cipher_list(ssl_c2, suiteBA), WOLFSSL_SUCCESS); ExpectIntEQ(wolfSSL_set_cipher_list(ssl_s2, suiteB), WOLFSSL_SUCCESS); - /* Session cache off so the declining server emits an empty session ID and - * the client takes the graceful full-handshake fallback (set on the SSL as - * the flag is copied from the CTX at wolfSSL_new() time). */ - if (ssl_s2 != NULL) - ssl_s2->options.sessionCacheOff = 1; ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c2), WOLFSSL_SUCCESS); ExpectIntEQ(wolfSSL_set_session(ssl_c2, sess), WOLFSSL_SUCCESS); + if (soleRef) { + /* The fallback then changes the only copy of the session */ + wolfSSL_SESSION_free(sess); + sess = NULL; + wolfSSL_free(ssl_c); + ssl_c = NULL; + } /* Fallback must succeed (no MATCH_SUITE_ERROR), not resume, and use B. */ ExpectIntEQ(test_memio_do_handshake(ssl_c2, ssl_s2, 10, NULL), 0); ExpectIntEQ(wolfSSL_session_reused(ssl_c2), 0); ExpectStrEQ(wolfSSL_get_cipher_name(ssl_c2), suiteB); + /* The declining server offered a session ID for stateful resumption */ + ExpectIntEQ(wolfSSL_get_session(ssl_s2) != NULL ? + wolfSSL_get_session(ssl_s2)->sessionIDSz : 0, ID_LEN); + + /* The client dropped the declined ticket and resumes by session ID. */ + ExpectNotNull(sess2 = wolfSSL_get1_session(ssl_c2)); + ExpectIntEQ(sess2 != NULL ? (int)sess2->ticketLen : -1, 0); + XMEMSET(&test_ctx3, 0, sizeof(test_ctx3)); + ExpectIntEQ(test_memio_setup(&test_ctx3, &ctx_c, &ctx_s2, &ssl_c3, &ssl_s3, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_set_cipher_list(ssl_s3, suiteB), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_session(ssl_c3, sess2), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c3, ssl_s3, 10, NULL), 0); + ExpectIntEQ(wolfSSL_session_reused(ssl_c3), 1); wolfSSL_SESSION_free(sess); + wolfSSL_SESSION_free(sess2); wolfSSL_free(ssl_c); wolfSSL_free(ssl_s); wolfSSL_free(ssl_c2); wolfSSL_free(ssl_s2); + wolfSSL_free(ssl_c3); + wolfSSL_free(ssl_s3); wolfSSL_CTX_free(ctx_c); wolfSSL_CTX_free(ctx_s); wolfSSL_CTX_free(ctx_s2); + return EXPECT_RESULT(); +} +#endif + +int test_tls12_resume_ticket_decline_fallback(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(NO_RESUME_SUITE_CHECK) && !defined(NO_RSA) && defined(HAVE_ECC) && \ + !defined(NO_AES) && defined(HAVE_AESGCM) && !defined(NO_SHA256) && \ + defined(BUILD_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) && \ + defined(BUILD_TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) + ExpectIntEQ(test_tls12_resume_ticket_decline_fallback_ex(0), TEST_SUCCESS); + ExpectIntEQ(test_tls12_resume_ticket_decline_fallback_ex(1), TEST_SUCCESS); #endif return EXPECT_RESULT(); } From 7ea4866951b70e28b0e7fedb25aea4371725e3c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Sat, 7 Nov 2026 09:11:02 +0100 Subject: [PATCH 2/2] Do not resume by session ID when given a TLS 1.2 ticket RFC 5077 Section 3.4 says a server that gets a ticket must not use the ClientHello session ID for stateful resumption. A wolfSSL server that did not use the ticket kept the resumption attempt the session ID had started, and looked the ID up in its session cache. That happened when the ticket failed to decrypt, when TLS 1.2 tickets were turned off with wolfSSL_NoTicketTLSv12(), and when no ticket callback was set. A client that sends a session ID next to its ticket, as OpenSSL does, could then be resumed from the cache. End the resumption attempt whenever a TLS 1.2 ClientHello carries a ticket; an accepted ticket starts it again. With WOLFSSL_DTLS_NO_HVR_ON_RESUME the stateless ClientHello check made the same choice. A ticket that failed to decrypt fell back to the session ID, so the server skipped the cookie exchange and then ran a full handshake. It also resumed on a ticket that decrypted while wolfSSL_NoTicketTLSv12() was set, which the server then ignores. The check now looks at the session ID only when the ClientHello has no ticket, and treats a TLS 1.2 ticket as declined under wolfSSL_NoTicketTLSv12(). An empty ticket counts as a ticket when the server would answer it with a new one, because the server then does not resume by the session ID either. test_tls12_ticket_declined_no_id_resumption puts a session ID the server has cached into a ClientHello carrying a ticket the server cannot use, for each of the three cases. It checks that the server does not resume. The wolfSSL_NoTicketTLSv12() case uses a ticket the server can decrypt. With WOLFSSL_DTLS_NO_HVR_ON_RESUME, the only build where the stateless check can resume, DTLS 1.2 runs of the three cases and of a cached session ID with an empty ticket also check that the server asks for a cookie. Each case fails without this change. --- src/dtls.c | 21 ++++-- src/tls.c | 15 ++-- tests/api/test_tls.c | 160 +++++++++++++++++++++++++++++++++++++++++++ tests/api/test_tls.h | 2 + 4 files changed, 187 insertions(+), 11 deletions(-) diff --git a/src/dtls.c b/src/dtls.c index e4361001ed1..debb1cbdc01 100644 --- a/src/dtls.c +++ b/src/dtls.c @@ -426,7 +426,7 @@ static int FindExtByType(WolfSSL_ConstVector* ret, word16 extType, #if defined(WOLFSSL_DTLS_NO_HVR_ON_RESUME) #ifdef HAVE_SESSION_TICKET static int TlsTicketIsValid(const WOLFSSL* ssl, WolfSSL_ConstVector exts, - int* resume) + int* resume, int* haveTicket) { WolfSSL_ConstVector tlsxSessionTicket; byte tempTicket[SESSION_TICKET_LEN]; @@ -440,9 +440,16 @@ static int TlsTicketIsValid(const WOLFSSL* ssl, WolfSSL_ConstVector exts, &tlsxFound); if (ret != 0) return ret; - if (tlsxSessionTicket.size == 0) + if (tlsxSessionTicket.size == 0) { + /* The server answers a blank ticket with a new one, not resumption */ + if (tlsxFound && ssl->ctx->ticketEncCb != NULL && + !ssl->options.noTicketTls12) + *haveTicket = TRUE; return 0; - if (tlsxSessionTicket.size > SESSION_TICKET_LEN) + } + *haveTicket = TRUE; + if (ssl->options.noTicketTls12 || + tlsxSessionTicket.size > SESSION_TICKET_LEN) return 0; XMEMCPY(tempTicket, tlsxSessionTicket.elements, tlsxSessionTicket.size); ret = DoDecryptTicket(ssl, tempTicket, (word32)tlsxSessionTicket.size, &it); @@ -522,12 +529,14 @@ static int TlsResumptionIsValid(const WOLFSSL* ssl, WolfSSL_CH* ch, int* resume) { int ret; - #ifdef HAVE_SESSION_TICKET - ret = TlsTicketIsValid(ssl, ch->extension, resume); + int haveTicket = FALSE; + + ret = TlsTicketIsValid(ssl, ch->extension, resume, &haveTicket); if (ret != 0) return ret; - if (*resume) + /* No resumption by the session ID that came with a ticket */ + if (*resume || haveTicket) return 0; #endif /* HAVE_SESSION_TICKET */ ret = TlsSessionIdIsValid(ssl, ch->sessionId, resume); diff --git a/src/tls.c b/src/tls.c index 69a41fc7f74..1148a74cd69 100644 --- a/src/tls.c +++ b/src/tls.c @@ -7093,11 +7093,6 @@ static int TLSX_SessionTicket_Parse(WOLFSSL* ssl, const byte* input, #ifndef NO_WOLFSSL_SERVER else { /* server side */ - if (ssl->ctx->ticketEncCb == NULL) { - WOLFSSL_MSG("Client sent session ticket, server has no callback"); - return 0; - } - #ifdef HAVE_SECURE_RENEGOTIATION if (IsSCR(ssl)) { WOLFSSL_MSG("Client sent session ticket during SCR. Ignoring."); @@ -7105,6 +7100,16 @@ static int TLSX_SessionTicket_Parse(WOLFSSL* ssl, const byte* input, } #endif + /* No stateful resumption with the session ID that came with a ticket + * (RFC 5077 Section 3.4) */ + if (length > 0 && !IsAtLeastTLSv1_3(ssl->version)) + ssl->options.resuming = 0; + + if (ssl->ctx->ticketEncCb == NULL) { + WOLFSSL_MSG("Client sent session ticket, server has no callback"); + return 0; + } + if (length > SESSION_TICKET_LEN) { ret = BAD_TICKET_MSG_SZ; WOLFSSL_ERROR_VERBOSE(ret); diff --git a/tests/api/test_tls.c b/tests/api/test_tls.c index 252a9c1eeb7..a78346fed3e 100644 --- a/tests/api/test_tls.c +++ b/tests/api/test_tls.c @@ -3832,6 +3832,166 @@ int test_tls13_psk_clear_restores_verify_peer(void) return EXPECT_RESULT(); } +#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) +/* Puts id into the empty session ID of a captured ClientHello */ +static void test_hello_add_session_id(byte* hello, int* helloSz, + const byte* id, int dtls) +{ + int recHdr = dtls ? DTLS_RECORD_HEADER_SZ : RECORD_HEADER_SZ; + int sidOff = recHdr + (dtls ? DTLS_HANDSHAKE_HEADER_SZ : + HANDSHAKE_HEADER_SZ) + VERSION_SZ + RAN_LEN; + word16 recSz = 0; + word32 hsSz = 0; + + XMEMMOVE(hello + sidOff + 1 + ID_LEN, hello + sidOff + 1, + (size_t)(*helloSz - sidOff - 1)); + hello[sidOff] = ID_LEN; + XMEMCPY(hello + sidOff + 1, id, ID_LEN); + *helloSz += ID_LEN; + ato16(hello + recHdr - OPAQUE16_LEN, &recSz); + c16toa((word16)(recSz + ID_LEN), hello + recHdr - OPAQUE16_LEN); + ato24(hello + recHdr + 1, &hsSz); + c32to24(hsSz + ID_LEN, hello + recHdr + 1); + if (dtls) { + /* fragment length */ + ato24(hello + recHdr + 9, &hsSz); + c32to24(hsSz + ID_LEN, hello + recHdr + 9); + } +} + +static int test_ticket_declined_no_id_resumption_ex(int dtls, + int noTicketTls12, + int noEncCb, + int blankTicket) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL, *ssl_c2 = NULL, *ssl_s2 = NULL; + WOLFSSL *ssl_c3 = NULL, *ssl_s3 = NULL; + WOLFSSL_SESSION* sess = NULL; + struct test_memio_ctx test_ctx; + method_provider method_c = wolfTLSv1_2_client_method; + method_provider method_s = wolfTLSv1_2_server_method; + int recHdr = dtls ? DTLS_RECORD_HEADER_SZ : RECORD_HEADER_SZ; + int sidOff = recHdr + (dtls ? DTLS_HANDSHAKE_HEADER_SZ : + HANDSHAKE_HEADER_SZ) + VERSION_SZ + RAN_LEN; + byte ticket[SESSION_TICKET_LEN]; + word32 ticketSz = 64; + byte hello[2048]; + int helloSz = (int)sizeof(hello) - ID_LEN; + +#ifdef WOLFSSL_DTLS + if (dtls) { + method_c = wolfDTLSv1_2_client_method; + method_s = wolfDTLSv1_2_server_method; + } +#endif + XMEMSET(ticket, 0x5a, sizeof(ticket)); + + /* No ticket, so the server keeps the session in its cache. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + method_c, method_s), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); + ExpectIntEQ(sess != NULL ? sess->sessionIDSz : 0, ID_LEN); + + /* A ticket the server can decrypt, so only noTicketTls12 declines it */ + if (noTicketTls12) { + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c3, + &ssl_s3, method_c, method_s), 0); + ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c3), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c3, ssl_s3, 10, NULL), 0); + ticketSz = (word32)sizeof(ticket); + ExpectIntEQ(wolfSSL_get_SessionTicket(ssl_c3, ticket, &ticketSz), + WOLFSSL_SUCCESS); + ExpectIntGT(ticketSz, 0); + } + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c2, &ssl_s2, + method_c, method_s), 0); + if (noTicketTls12) + ExpectIntEQ(wolfSSL_NoTicketTLSv12(ssl_s2), WOLFSSL_SUCCESS); + if (noEncCb) + ExpectIntEQ(wolfSSL_CTX_set_TicketEncCb(ctx_s, NULL), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_session(ssl_c2, sess), WOLFSSL_SUCCESS); + if (blankTicket) { + ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c2), WOLFSSL_SUCCESS); + } + else { + ExpectIntEQ(wolfSSL_set_SessionTicket(ssl_c2, ticket, ticketSz), + WOLFSSL_SUCCESS); + } + ExpectIntEQ(wolfSSL_connect(ssl_c2), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_c2, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + + ExpectIntEQ(test_memio_copy_message(&test_ctx, 0, (char*)hello, &helloSz, + 0), 0); + ExpectIntGT(helloSz, sidOff); + ExpectIntEQ(helloSz > sidOff ? hello[sidOff] : -1, + blankTicket ? ID_LEN : 0); + if (EXPECT_SUCCESS() && sess != NULL && !blankTicket) + test_hello_add_session_id(hello, &helloSz, sess->sessionID, dtls); + test_memio_clear_buffer(&test_ctx, 0); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (char*)hello, + helloSz), 0); + ExpectIntEQ(wolfSSL_accept(ssl_s2), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_s2, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_session_reused(ssl_s2), 0); + /* The DTLS server still asks for a cookie */ + if (dtls) { + ExpectIntGT(test_ctx.c_len, recHdr); + ExpectIntEQ(test_ctx.c_buff[recHdr], hello_verify_request); + } + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_free(ssl_c2); + wolfSSL_free(ssl_s2); + wolfSSL_free(ssl_c3); + wolfSSL_free(ssl_s3); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +/* A server given a ticket does not resume by the session ID next to it (RFC + * 5077 Section 3.4). A wolfSSL client sends none, so the test adds one. */ +int test_tls12_ticket_declined_no_id_resumption(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) + ExpectIntEQ(test_ticket_declined_no_id_resumption_ex(0, 0, 0, 0), + TEST_SUCCESS); + ExpectIntEQ(test_ticket_declined_no_id_resumption_ex(0, 1, 0, 0), + TEST_SUCCESS); + ExpectIntEQ(test_ticket_declined_no_id_resumption_ex(0, 0, 1, 0), + TEST_SUCCESS); +#if defined(WOLFSSL_DTLS) && defined(WOLFSSL_DTLS_NO_HVR_ON_RESUME) + /* Only the stateless check can resume before the cookie exchange */ + ExpectIntEQ(test_ticket_declined_no_id_resumption_ex(1, 0, 0, 0), + TEST_SUCCESS); + ExpectIntEQ(test_ticket_declined_no_id_resumption_ex(1, 1, 0, 0), + TEST_SUCCESS); + ExpectIntEQ(test_ticket_declined_no_id_resumption_ex(1, 0, 1, 0), + TEST_SUCCESS); + ExpectIntEQ(test_ticket_declined_no_id_resumption_ex(1, 0, 0, 1), + TEST_SUCCESS); +#endif +#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 0c5a30b1ef4..5d1bf66b44f 100644 --- a/tests/api/test_tls.h +++ b/tests/api/test_tls.h @@ -66,6 +66,7 @@ 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_ticket_declined_no_id_resumption(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); @@ -178,6 +179,7 @@ int test_tls_hmac_inner_cid_decision_coverage(void); 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_ticket_declined_no_id_resumption), \ 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), \