diff --git a/.wolfssl_known_macro_extras b/.wolfssl_known_macro_extras index 3be2bfaa0a..3b9b9b3a99 100644 --- a/.wolfssl_known_macro_extras +++ b/.wolfssl_known_macro_extras @@ -1096,6 +1096,7 @@ WOLFSSL_TLS13_DRAFT WOLFSSL_TLS13_IGNORE_AEAD_LIMITS WOLFSSL_TLS13_IGNORE_PT_ALERT_ON_ENC WOLFSSL_TLS13_SHA512 +WOLFSSL_TLS13_TICKET_CHECK_PSK_MODES WOLFSSL_TLS13_TICKET_BEFORE_FINISHED WOLFSSL_TLSX_PQC_MLKEM_STORE_PRIV_KEY WOLFSSL_TRACK_MEMORY_FULL diff --git a/src/ssl.c b/src/ssl.c index 2e3b49452e..822d1e59b9 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -5719,6 +5719,13 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out, #ifdef HAVE_SESSION_TICKET #ifdef WOLFSSL_TLS13 ssl->options.ticketsSent = 0; + #if !defined(NO_WOLFSSL_SERVER) && \ + defined(WOLFSSL_TLS13_TICKET_CHECK_PSK_MODES) + /* Recorded from the ClientHello, so it must not carry into the next + * connection on a reused object. */ + ssl->options.pskKeModes = 0; + ssl->options.pskKeModesRecvd = 0; + #endif #endif ssl->options.rejectTicket = 0; #endif diff --git a/src/ssl_sess.c b/src/ssl_sess.c index 862a7afbd9..c70feefd08 100644 --- a/src/ssl_sess.c +++ b/src/ssl_sess.c @@ -1052,8 +1052,11 @@ WOLFSSL_SESSION* wolfSSL_GetSessionClient(WOLFSSL* ssl, const byte* id, int len) #else current = &sessRow->Sessions[clSess[idx].serverIdx]; #endif - if (current && XMEMCMP(current->serverID, id, - (unsigned long)len) == 0) { + /* Require the same length as well as the same bytes. Comparing only + * the requested length lets a short ID alias the prefix of a longer + * cached one, mixing sessions the application meant to keep apart. */ + if (current && current->idLen == (word16)len && + XMEMCMP(current->serverID, id, (unsigned long)len) == 0) { WOLFSSL_MSG("Found a serverid match for client"); if (LowResTimer() < (current->bornOn + current->timeout)) { WOLFSSL_MSG("Session valid"); diff --git a/src/tls.c b/src/tls.c index 895bcb3b99..bf313c5c6e 100644 --- a/src/tls.c +++ b/src/tls.c @@ -12911,8 +12911,17 @@ static int TLSX_PskKeModes_Parse(WOLFSSL* ssl, const byte* input, word16 length, byte modes; ret = TLSX_PskKeyModes_Parse_Modes(input, length, msgType, &modes); - if (ret == 0) + if (ret == 0) { +#if defined(HAVE_SESSION_TICKET) && !defined(NO_WOLFSSL_SERVER) && \ + defined(WOLFSSL_TLS13_TICKET_CHECK_PSK_MODES) + /* Keep the advertised modes for the NewSessionTicket decision. The + * extension object is dropped with the rest of the handshake state + * once the handshake is done. */ + ssl->options.pskKeModes = modes; + ssl->options.pskKeModesRecvd = 1; +#endif ret = TLSX_PskKeyModes_Use(ssl, modes); + } if (ret != 0) { WOLFSSL_ERROR_VERBOSE(ret); diff --git a/src/tls13.c b/src/tls13.c index 115ec8391b..0cadbc91c8 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -71,6 +71,12 @@ * WOLFSSL_TICKET_HAVE_ID: Session tickets include ID default: off * Forced on when WOLFSSL_EARLY_DATA is set. * WOLFSSL_TICKET_NONCE_MALLOC: Dynamically allocate ticket nonce default: off + * WOLFSSL_TLS13_TICKET_CHECK_PSK_MODES: Withhold NewSessionTicket default: off + * when the ClientHello advertised no usable + * psk_key_exchange_modes, as RFC 9846 Sections + * 4.3.9 and 4.7.1 require. Off by default: a peer + * that omits the extension but expects a ticket + * stops getting one. * * TLS 1.3 Key Exchange: * HAVE_KEYING_MATERIAL: Export keying material (RFC 8446 7.5) default: off @@ -13666,12 +13672,12 @@ static int DoTls13NewSessionTicket(WOLFSSL* ssl, const byte* input, *inOutIdx += EXTS_SZ; if ((*inOutIdx - begin) + length != size) return BUFFER_ERROR; - #ifdef WOLFSSL_EARLY_DATA - ret = TLSX_Parse(ssl, (byte *)input + (*inOutIdx), length, session_ticket, - NULL); + /* RFC 9846 Section 4.7.1: the extensions are Section 4.3 Extension TLVs. + * Malformed framing is a syntax error even when no extension in the list + * is one we act on. */ + ret = TLSX_Parse(ssl, input + *inOutIdx, length, session_ticket, NULL); if (ret != 0) return ret; - #endif *inOutIdx += length; SetupSession(ssl); @@ -13811,6 +13817,48 @@ static int ExpectedResumptionSecret(WOLFSSL* ssl) } #endif +/* Check the client advertised a PSK key exchange mode a resumption ticket can + * be used with. + * + * RFC 9846 Section 4.3.9: psk_key_exchange_modes restricts both the PSKs + * offered in the ClientHello and those the server might supply through + * NewSessionTicket, and servers should not send tickets that are incompatible + * with the advertised modes. RFC 9846 Section 4.7.1 makes sending a ticket + * conditional on the client's hello carrying a suitable extension. + * + * ssl The SSL/TLS object. + * returns 0 when a ticket may be sent, MISSING_HANDSHAKE_DATA when the + * extension was not received and PSK_KEY_ERROR when none of the + * advertised modes is usable. + */ +static int CheckTls13TicketPskModes(WOLFSSL* ssl) +{ +#ifdef WOLFSSL_TLS13_TICKET_CHECK_PSK_MODES + if (!ssl->options.pskKeModesRecvd) { + WOLFSSL_MSG("No psk_key_exchange_modes in ClientHello"); + return MISSING_HANDSHAKE_DATA; + } + + if ((ssl->options.pskKeModes & (1 << PSK_KE)) != 0 + #ifdef HAVE_SUPPORTED_CURVES + && !ssl->options.onlyPskDheKe + #endif + ) { + return 0; + } + if ((ssl->options.pskKeModes & (1 << PSK_DHE_KE)) != 0 && + !ssl->options.noPskDheKe) { + return 0; + } + + WOLFSSL_MSG("No usable psk_key_exchange_modes advertised by client"); + return PSK_KEY_ERROR; +#else + (void)ssl; + return 0; +#endif +} + /* Send New Session Ticket handshake message. * Message contains the information required to perform resumption. * @@ -13835,6 +13883,12 @@ static int SendTls13NewSessionTicket(WOLFSSL* ssl) return 0; } + if (CheckTls13TicketPskModes(ssl) != 0) { + WOLFSSL_MSG("Client advertised no usable PSK key exchange mode; " + "skipping ticket"); + return 0; + } + #ifdef WOLFSSL_DTLS13 if (ssl->options.dtls) idx = Dtls13GetRlHeaderLength(ssl, 1) + DTLS_HANDSHAKE_HEADER_SZ; @@ -17045,17 +17099,29 @@ int wolfSSL_accept_TLSv13(WOLFSSL* ssl) * returns BAD_FUNC_ARG when ssl is NULL, or not using TLS v1.3, * SIDE_ERROR when not a server, * NOT_READY_ERROR when handshake not complete, + * MISSING_HANDSHAKE_DATA when the ClientHello had no + * psk_key_exchange_modes extension and + * WOLFSSL_TLS13_TICKET_CHECK_PSK_MODES is defined, + * PSK_KEY_ERROR when no advertised PSK key exchange mode is usable and + * WOLFSSL_TLS13_TICKET_CHECK_PSK_MODES is defined, * WOLFSSL_FATAL_ERROR when creating or sending message fails, and * WOLFSSL_SUCCESS on success. */ int wolfSSL_send_SessionTicket(WOLFSSL* ssl) { + int ret; + if (ssl == NULL || !IsAtLeastTLSv1_3(ssl->version)) return BAD_FUNC_ARG; if (ssl->options.side == WOLFSSL_CLIENT_END) return SIDE_ERROR; if (ssl->options.handShakeState != HANDSHAKE_DONE) return NOT_READY_ERROR; + ret = CheckTls13TicketPskModes(ssl); + if (ret != 0) { + WOLFSSL_ERROR_VERBOSE(ret); + return ret; + } if ((ssl->error = SendTls13NewSessionTicket(ssl)) != 0) { WOLFSSL_ERROR(ssl->error); diff --git a/tests/api/test_session.c b/tests/api/test_session.c index 3c08f32bef..5ad90c8eb8 100644 --- a/tests/api/test_session.c +++ b/tests/api/test_session.c @@ -1739,3 +1739,82 @@ int test_wolfSSL_GetSessionAtIndex(void) #endif /* SESSION_INDEX && HAVE_SESSION_TICKET && !NO_SESSION_CACHE && * !NO_WOLFSSL_CLIENT && !NO_TLS */ + +/* RFC 9846 Appendix C.4: client applications should not offer tickets across + * connections meant to be uncorrelated. wolfSSL_SetServerID() is how an + * application keeps such connections apart, so a shorter ID must not match a + * cached entry that merely starts with the same bytes. */ +int test_wolfSSL_client_cache_id_prefix(void) +{ + EXPECT_DECLS; +#if !defined(NO_SESSION_CACHE) && !defined(NO_CLIENT_CACHE) && \ + !defined(NO_TLS) && !defined(WOLFSSL_NO_TLS12) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + WOLFSSL* ssl = NULL; + struct test_memio_ctx test_ctx; + static const byte prefix[] = { 'w', 'o', 'l', 'f', 'S', 'S', 'L', ':' }; + byte id[sizeof(prefix) + 4]; + byte sessId[ID_LEN]; + word32 i; + /* The cache row is picked from a hash of the ID, so the prefix and any + * one long ID rarely share a row. Every long ID here starts with the + * prefix, and there are enough of them to cover all rows, so the prefix + * lookup lands on a row holding one of them. */ + const word32 fill = 4096; + + /* TLS 1.2 so the client has a complete session to cache as soon as the + * handshake is done, with or without session tickets. */ + 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(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(ssl_c->session->isSetup, 1); + + XMEMCPY(id, prefix, sizeof(prefix)); + XMEMSET(sessId, 0, sizeof(sessId)); + for (i = 0; i < fill && EXPECT_SUCCESS(); i++) { + ClientSession* entry = NULL; + + c32toa(i, id + sizeof(prefix)); + c32toa(i, sessId); + ExpectIntEQ(wolfSSL_SetServerID(ssl_c, id, (int)sizeof(id), 1), + WOLFSSL_SUCCESS); + if (EXPECT_SUCCESS()) { + XMEMCPY(ssl_c->session->sessionID, sessId, ID_LEN); + XMEMCPY(ssl_c->session->altSessionID, sessId, ID_LEN); + ssl_c->session->sessionIDSz = ID_LEN; + } + ExpectIntEQ(AddSessionToCache(ctx_c, ssl_c->session, sessId, ID_LEN, + NULL, WOLFSSL_CLIENT_END, 0, &entry), 0); + ExpectNotNull(entry); + } + + /* The prefix is a distinct partition key: no cached session for it. */ + ExpectNotNull(ssl = wolfSSL_new(ctx_c)); + ExpectIntEQ(ssl->session->isSetup, 0); + ExpectIntEQ(wolfSSL_SetServerID(ssl, prefix, (int)sizeof(prefix), 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(ssl->session->isSetup, 0); + ExpectIntEQ(ssl->session->idLen, (int)sizeof(prefix)); + wolfSSL_free(ssl); + ssl = NULL; + + /* Control: the ID it was cached under still finds it. */ + ExpectNotNull(ssl = wolfSSL_new(ctx_c)); + ExpectIntEQ(wolfSSL_SetServerID(ssl, id, (int)sizeof(id), 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(ssl->session->isSetup, 1); + wolfSSL_free(ssl); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_session.h b/tests/api/test_session.h index be3deb5f04..8781fd8ac4 100644 --- a/tests/api/test_session.h +++ b/tests/api/test_session.h @@ -37,6 +37,7 @@ int test_wolfSSL_CTX_sess_set_remove_cb(void); int test_wolfSSL_ticket_keys(void); int test_wolfSSL_SESSION_get_ex_new_index(void); int test_wolfSSL_GetSessionAtIndex(void); +int test_wolfSSL_client_cache_id_prefix(void); #define TEST_SESSION_DECLS \ TEST_DECL_GROUP("session", test_wolfSSL_CTX_add_session), \ @@ -51,6 +52,7 @@ int test_wolfSSL_GetSessionAtIndex(void); TEST_DECL_GROUP("session", test_wolfSSL_CTX_sess_set_remove_cb), \ TEST_DECL_GROUP("session", test_wolfSSL_ticket_keys), \ TEST_DECL_GROUP("session", test_wolfSSL_SESSION_get_ex_new_index), \ - TEST_DECL_GROUP("session", test_wolfSSL_GetSessionAtIndex) + TEST_DECL_GROUP("session", test_wolfSSL_GetSessionAtIndex), \ + TEST_DECL_GROUP("session", test_wolfSSL_client_cache_id_prefix) #endif /* WOLFCRYPT_TEST_SESSION_H */ diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index 2fe353732d..2308a24444 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -9282,3 +9282,315 @@ int test_tls13_pha_status_request(void) #endif return EXPECT_RESULT(); } + +#if defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + defined(WOLFSSL_TLS13_TICKET_CHECK_PSK_MODES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) +/* Drive a TLS 1.3 handshake up to, but not including, the server's final + * wolfSSL_accept() - the call that runs the NewSessionTicket loop. */ +static int test_tls13_handshake_to_ticket(WOLFSSL* ssl_c, WOLFSSL* ssl_s) +{ + EXPECT_DECLS; + + /* ClientHello. */ + ExpectIntEQ(wolfSSL_connect(ssl_c), WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + /* Server flight. */ + ExpectIntEQ(wolfSSL_accept(ssl_s), WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)); + ExpectIntEQ(wolfSSL_get_error(ssl_s, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + /* Client Finished. */ + ExpectIntEQ(wolfSSL_connect(ssl_c), WOLFSSL_SUCCESS); + + return EXPECT_RESULT(); +} +#endif + +/* RFC 9846 Section 4.3.9 and Section 4.7.1: a NewSessionTicket creates a + * resumption PSK, so the server may only send one when the ClientHello + * advertised a psk_key_exchange_modes mode it can be used with. */ +int test_tls13_ticket_psk_modes(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + defined(WOLFSSL_TLS13_TICKET_CHECK_PSK_MODES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + + /* The wolfSSL client always advertises psk_key_exchange_modes, so drive + * the no-extension case by clearing the recorded flag after the + * handshake: a ClientHello without the extension gets no ticket. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(test_tls13_handshake_to_ticket(ssl_c, ssl_s), TEST_SUCCESS); + ExpectIntEQ(ssl_s->options.pskKeModesRecvd, 1); + if (EXPECT_SUCCESS()) { + ssl_s->options.pskKeModesRecvd = 0; + } + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(wolfSSL_accept(ssl_s), WOLFSSL_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + + 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; + + /* Control: the wolfSSL client advertises the modes, so a ticket is sent. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(test_tls13_handshake_to_ticket(ssl_c, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(wolfSSL_accept(ssl_s), WOLFSSL_SUCCESS); + ExpectIntGT(test_ctx.c_len, 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* wolfSSL_send_SessionTicket() applies the same RFC 9846 precondition as the + * automatic ticket path, and reports why it will not send. */ +int test_tls13_send_session_ticket_psk_modes(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + defined(WOLFSSL_TLS13_TICKET_CHECK_PSK_MODES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + ExpectIntEQ(wolfSSL_send_SessionTicket(ssl_s), WOLFSSL_SUCCESS); + if (EXPECT_SUCCESS()) { + /* No psk_key_exchange_modes extension in the ClientHello. */ + ssl_s->options.pskKeModesRecvd = 0; + } + ExpectIntEQ(wolfSSL_send_SessionTicket(ssl_s), + WC_NO_ERR_TRACE(MISSING_HANDSHAKE_DATA)); + if (EXPECT_SUCCESS()) { + /* Extension present but carrying only unrecognized modes. */ + ssl_s->options.pskKeModesRecvd = 1; + ssl_s->options.pskKeModes = 0; + } + ExpectIntEQ(wolfSSL_send_SessionTicket(ssl_s), + WC_NO_ERR_TRACE(PSK_KEY_ERROR)); + if (EXPECT_SUCCESS()) { + /* psk_dhe_ke only, but the server refuses (EC)DHE with PSK. */ + ssl_s->options.pskKeModes = 1 << PSK_DHE_KE; + ssl_s->options.noPskDheKe = 1; + } + ExpectIntEQ(wolfSSL_send_SessionTicket(ssl_s), + WC_NO_ERR_TRACE(PSK_KEY_ERROR)); + if (EXPECT_SUCCESS()) { + ssl_s->options.noPskDheKe = 0; + } + ExpectIntEQ(wolfSSL_send_SessionTicket(ssl_s), WOLFSSL_SUCCESS); +#ifdef HAVE_SUPPORTED_CURVES + if (EXPECT_SUCCESS()) { + /* psk_ke only, but the server requires (EC)DHE with PSK. */ + ssl_s->options.pskKeModes = 1 << PSK_KE; + ssl_s->options.onlyPskDheKe = 1; + } + ExpectIntEQ(wolfSSL_send_SessionTicket(ssl_s), + WC_NO_ERR_TRACE(PSK_KEY_ERROR)); + if (EXPECT_SUCCESS()) { + ssl_s->options.onlyPskDheKe = 0; + } + ExpectIntEQ(wolfSSL_send_SessionTicket(ssl_s), WOLFSSL_SUCCESS); +#endif + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +#if defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) +/* Build a NewSessionTicket handshake message carrying the given extensions + * block verbatim. Returns the message length, including the handshake + * header, or -1 when it does not fit. */ +static int test_tls13_make_nst(byte* out, int outSz, const byte* exts, + int extsSz) +{ + static const byte body[] = { + 0x00, 0x00, 0x0e, 0x10, /* ticket_lifetime: 3600 */ + 0x01, 0x02, 0x03, 0x04, /* ticket_age_add */ + 0x01, 0x00, /* ticket_nonce<1> */ + 0x00, 0x04, 0xde, 0xad, 0xbe, 0xef /* ticket<4> */ + }; + int len = (int)sizeof(body) + OPAQUE16_LEN + extsSz; + + if (outSz < HANDSHAKE_HEADER_SZ + len) + return -1; + + out[0] = session_ticket; + out[1] = (byte)(len >> 16); + out[2] = (byte)(len >> 8); + out[3] = (byte)len; + XMEMCPY(out + HANDSHAKE_HEADER_SZ, body, sizeof(body)); + c16toa((word16)extsSz, out + HANDSHAKE_HEADER_SZ + sizeof(body)); + if (extsSz > 0) { + XMEMCPY(out + HANDSHAKE_HEADER_SZ + sizeof(body) + OPAQUE16_LEN, exts, + (size_t)extsSz); + } + + return HANDSHAKE_HEADER_SZ + len; +} + +/* Encrypt a post-handshake message with the server's keys and hand it to the + * client. Returns 0 on success. */ +static int test_tls13_send_post_hs(struct test_memio_ctx* test_ctx, + WOLFSSL* ssl_s, const byte* msg, int msgSz) +{ + EXPECT_DECLS; + byte rec[256]; + int recSz; + + recSz = BuildTls13Message(ssl_s, rec, (int)sizeof(rec), msg, msgSz, + handshake, 0, 0, 0); + ExpectIntGT(recSz, 0); + ExpectIntLE(recSz, (int)sizeof(rec)); + ExpectIntEQ(test_memio_inject_message(test_ctx, 1, (const char*)rec, recSz), + 0); + + return EXPECT_RESULT(); +} +#endif + +/* RFC 9846 Section 4.7.1 defines NewSessionTicket.extensions as a list of + * Section 4.3 Extension TLVs, and Section 6 requires a decode_error alert for + * a message that cannot be parsed. The framing has to be checked whether or + * not early data - the only extension wolfSSL acts on there - is compiled in. + */ +int test_tls13_new_session_ticket_ext_framing(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + /* Well formed extension of an unknown type - must be ignored. */ + static const byte extOk[] = { 0x12, 0x34, 0x00, 0x02, 0xaa, 0xbb }; + /* Two unknown types, the second with empty extension_data. */ + static const byte extTwo[] = { 0x12, 0x34, 0x00, 0x02, 0xaa, 0xbb, + 0x56, 0x78, 0x00, 0x00 }; + /* Vector too short to hold an Extension header. */ + static const byte extShort[] = { 0x00, 0x2a, 0x00 }; + /* extension_data length runs past the end of the vector. */ + static const byte extTrunc[] = { 0x12, 0x34, 0x00, 0x04, 0xaa, 0xbb }; + static const char appData[] = "still talking"; + struct { + const byte* exts; + int extsSz; + int expectErr; + } cases[] = { + { NULL, 0, 0 }, + { extOk, (int)sizeof(extOk), 0 }, + { extTwo, (int)sizeof(extTwo), 0 }, + { extShort, (int)sizeof(extShort), BUFFER_ERROR }, + { extTrunc, (int)sizeof(extTrunc), BUFFER_ERROR }, + }; + size_t i; + char buf[64]; + + for (i = 0; i < XELEM_CNT(cases) && EXPECT_SUCCESS(); i++) { + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + WOLFSSL_ALERT_HISTORY h; + byte msg[64]; + int msgSz; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* Consume the server's own NewSessionTicket. */ + ExpectIntEQ(wolfSSL_read(ssl_c, buf, sizeof(buf)), + WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + + msgSz = -1; + if (EXPECT_SUCCESS()) { + msgSz = test_tls13_make_nst(msg, (int)sizeof(msg), cases[i].exts, + cases[i].extsSz); + } + ExpectIntGT(msgSz, 0); + ExpectIntEQ(test_tls13_send_post_hs(&test_ctx, ssl_s, msg, msgSz), + TEST_SUCCESS); + + ExpectIntEQ(wolfSSL_read(ssl_c, buf, sizeof(buf)), + WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)); + if (cases[i].expectErr == 0) { + /* Accepted: no application data follows the ticket, and the + * client raised no alert. */ + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_get_alert_history(ssl_c, &h), WOLFSSL_SUCCESS); + ExpectIntEQ(h.last_tx.code, -1); + ExpectIntEQ(h.last_tx.level, -1); + /* The connection carries on: data still flows both ways. */ + ExpectIntEQ(wolfSSL_write(ssl_s, appData, (int)sizeof(appData) - 1), + (int)sizeof(appData) - 1); + ExpectIntEQ(wolfSSL_read(ssl_c, buf, sizeof(buf)), + (int)sizeof(appData) - 1); + ExpectIntEQ(XMEMCMP(buf, appData, sizeof(appData) - 1), 0); + ExpectIntEQ(wolfSSL_write(ssl_c, appData, (int)sizeof(appData) - 1), + (int)sizeof(appData) - 1); + ExpectIntEQ(wolfSSL_read(ssl_s, buf, sizeof(buf)), + (int)sizeof(appData) - 1); + ExpectIntEQ(XMEMCMP(buf, appData, sizeof(appData) - 1), 0); + } + else { + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + cases[i].expectErr); + ExpectIntEQ(wolfSSL_get_alert_history(ssl_c, &h), WOLFSSL_SUCCESS); + ExpectIntEQ(h.last_tx.code, decode_error); + ExpectIntEQ(h.last_tx.level, alert_fatal); + } + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + } +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_tls13.h b/tests/api/test_tls13.h index 707f3b8771..feeefcfc6a 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -109,6 +109,9 @@ int test_tls13_AEAD_limit_KU_aes128_ccm_8_sha256(void); int test_tls13_KeyUpdate_sender_limit(void); int test_tls13_pqc_hybrid_async_server(void); int test_tls13_pha_status_request(void); +int test_tls13_ticket_psk_modes(void); +int test_tls13_send_session_ticket_psk_modes(void); +int test_tls13_new_session_ticket_ext_framing(void); #define TEST_TLS13_DECLS \ TEST_DECL_GROUP("tls13", test_tls13_apis), \ @@ -195,6 +198,9 @@ int test_tls13_pha_status_request(void); TEST_DECL_GROUP("tls13", test_tls13_AEAD_limit_KU_aes128_ccm_8_sha256), \ TEST_DECL_GROUP("tls13", test_tls13_KeyUpdate_sender_limit), \ TEST_DECL_GROUP("tls13", test_tls13_pqc_hybrid_async_server), \ - TEST_DECL_GROUP("tls13", test_tls13_pha_status_request) + TEST_DECL_GROUP("tls13", test_tls13_pha_status_request), \ + TEST_DECL_GROUP("tls13", test_tls13_ticket_psk_modes), \ + TEST_DECL_GROUP("tls13", test_tls13_send_session_ticket_psk_modes), \ + TEST_DECL_GROUP("tls13", test_tls13_new_session_ticket_ext_framing) #endif /* WOLFCRYPT_TEST_TLS13_H */ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 222ac75c3f..da3b740230 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -5386,6 +5386,10 @@ struct Options { #if defined(HAVE_SESSION_TICKET) && defined(WOLFSSL_TLS13) unsigned int maxTicketTls13; /* maximum number of tickets to send */ unsigned int ticketsSent; /* keep track of the total sent */ +#if !defined(NO_WOLFSSL_SERVER) && \ + defined(WOLFSSL_TLS13_TICKET_CHECK_PSK_MODES) + byte pskKeModes; /* modes client advertised in CH */ +#endif #endif /* on/off or small bit flags, optimize layout */ @@ -5465,6 +5469,10 @@ struct Options { word16 noTicketTls12:1; /* TLS 1.2 server won't send ticket */ #ifdef WOLFSSL_TLS13 word16 noTicketTls13:1; /* Server won't create new Ticket */ +#if !defined(NO_WOLFSSL_SERVER) && \ + defined(WOLFSSL_TLS13_TICKET_CHECK_PSK_MODES) + word16 pskKeModesRecvd:1; /* CH had psk_key_exchange_modes */ +#endif #endif #endif #ifdef WOLFSSL_DTLS