Merge pull request #11178 from julek-wolfssl/fix/rfc9846-session-ticket-compliance

Harden TLS 1.3 NewSessionTicket handling per RFC 9846
pull/11340/merge
JacobBarthelmeh 2026-09-14 16:03:40 -06:00 committed by GitHub
commit 5d17d603e2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 871 additions and 39 deletions

View File

@ -1148,6 +1148,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

View File

@ -816,16 +816,27 @@ static int SendStatelessReplyDtls13(const WOLFSSL* ssl, WolfSSL_CH* ch)
if (ret != 0)
goto dtls13_cleanup;
if ((modes & (1 << PSK_DHE_KE)) &&
!ssl->options.noPskDheKe) {
!ssl->options.noPskDheKePolicy) {
if (!haveKS)
ERROR_OUT(PSK_KEY_ERROR, dtls13_cleanup);
doKE = 1;
usePSK = 1;
}
else if ((modes & (1 << PSK_KE)) == 0 ||
ssl->options.onlyPskDheKe) {
else if ((modes & (1 << PSK_KE)) != 0 &&
!ssl->options.onlyPskDheKe) {
usePSK = 1;
}
else if (!haveKS || !haveSA || !haveSG) {
/* No usable mode and nothing to fall back to. */
ERROR_OUT(PSK_KEY_ERROR, dtls13_cleanup);
}
usePSK = 1;
else {
/* RFC 9846 Section 4.3.11: ignore the PSK and do a full
* handshake. Mirrors PskModesUsable() so this stateless reply
* and the stateful ClientHello agree on the cipher suite. */
WOLFSSL_MSG("psk_key_exchange_modes offer no usable mode, "
"ignoring PSK");
}
}
}
#endif
@ -857,6 +868,14 @@ static int SendStatelessReplyDtls13(const WOLFSSL* ssl, WolfSSL_CH* ch)
else
#endif /* defined(HAVE_SESSION_TICKET) || !defined(NO_PSK) */
{
#if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)
/* Not using a PSK, so a key share is needed. DoTls13ClientHello() does
* the same for the stateful pass. Without it a no-(EC)DHE-with-PSK
* server omits key_share from the HelloRetryRequest while the cookie
* still records the group, and the two transcripts diverge. The
* configured policy is kept in noPskDheKePolicy. */
((WOLFSSL*)ssl)->options.noPskDheKe = 0;
#endif
/* https://datatracker.ietf.org/doc/html/rfc8446#section-9.2 */
if (!haveKS || !haveSA || !haveSG) {
WOLFSSL_MSG("Client didn't send KeyShare or SigAlgs or "

View File

@ -8476,6 +8476,7 @@ static void InitSSL_Tls13Options(WOLFSSL* ssl, WOLFSSL_CTX* ctx)
#endif
#if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)
ssl->options.noPskDheKe = ctx->noPskDheKe;
ssl->options.noPskDheKePolicy = ctx->noPskDheKe;
#ifdef HAVE_SUPPORTED_CURVES
ssl->options.onlyPskDheKe = ctx->onlyPskDheKe;
#endif /* HAVE_SUPPORTED_CURVES */

View File

@ -5712,6 +5712,7 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out,
#ifdef WOLFSSL_TLS13
#if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)
ssl->options.noPskDheKe = ssl->ctx->noPskDheKe;
ssl->options.noPskDheKePolicy = ssl->ctx->noPskDheKe;
#ifdef HAVE_SUPPORTED_CURVES
ssl->options.onlyPskDheKe = ssl->ctx->onlyPskDheKe;
#endif
@ -5745,6 +5746,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

View File

@ -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");

View File

@ -13053,8 +13053,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);
@ -19410,9 +19419,12 @@ WOLFSSL_TEST_VIS int TLSX_Parse(WOLFSSL* ssl, const byte* input, word16 length,
#ifdef HAVE_EXTENDED_MASTER
if (IsAtLeastTLSv1_3(ssl->version) &&
(msgType == hello_retry_request || msgType == hello_verify_request)) {
(msgType == hello_retry_request || msgType == hello_verify_request ||
msgType == session_ticket)) {
/* Don't change EMS status until server_hello received.
* Second ClientHello must have same extensions.
* NewSessionTicket is post-handshake and never carries the extension,
* so its absence there says nothing about what was negotiated.
*/
}
else if (!isRequest && ssl->options.haveEMS && !pendingEMS)

View File

@ -79,6 +79,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
@ -7040,6 +7046,63 @@ cleanup:
return ret;
}
/* Check whether a PSK may be used given the key exchange modes the client
* advertised and the modes this server is configured to allow.
*
* RFC 9846 Section 4.3.9 forbids selecting a mode the client did not list.
* Section 4.3.11 says a server that finds no acceptable PSK should perform a
* non-PSK handshake instead of aborting. Deciding before a PSK is selected
* leaves nothing to unwind: no ticket is decrypted, no binder is verified, no
* secret is derived and no early data is accepted.
*
* The configured policy is read, not ssl->options.noPskDheKe, which also
* carries negotiated state and is cleared by every certificate handshake.
*
* ssl SSL/TLS object.
* clSuites Client's cipher suite list.
* returns 1 when PSK selection may proceed and 0 when the PSK must be ignored.
*/
static int PskModesUsable(const WOLFSSL* ssl, const Suites* clSuites)
{
#ifdef HAVE_SUPPORTED_CURVES
TLSX* ext;
word32 modes;
/* Offering pre_shared_key without psk_key_exchange_modes is a MUST-level
* abort (Section 4.3.9). Leave it to CheckPreSharedKeys. */
ext = TLSX_Find(ssl->extensions, TLSX_PSK_KEY_EXCHANGE_MODES);
if (ext == NULL)
return 1;
modes = ext->val;
#ifdef WOLFSSL_CERT_WITH_EXTERN_PSK
/* RFC 9973 requires psk_dhe_ke and overrides the no-(EC)DHE policy, so a
* mismatch must abort rather than fall back. */
if (TLSX_Find(ssl->extensions, TLSX_CERT_WITH_EXTERN_PSK) != NULL)
return 1;
#endif
/* Only decline when a certificate handshake can actually run instead.
* These are the same two things DoTls13ClientHello() requires of a
* ClientHello that negotiates no PSK. */
if (TLSX_Find(ssl->extensions, TLSX_KEY_SHARE) == NULL)
return 1;
if (clSuites == NULL || clSuites->hashSigAlgoSz == 0)
return 1;
if ((modes & (1 << PSK_DHE_KE)) != 0 && !ssl->options.noPskDheKePolicy)
return 1;
if (ssl->options.onlyPskDheKe)
return 0;
return (modes & (1 << PSK_KE)) != 0;
#else
/* Without (EC)DHE there is no certificate handshake to fall back to. */
(void)ssl;
(void)clSuites;
return 1;
#endif
}
/* Handle any Pre-Shared Key (PSK) extension.
* Must do this in ClientHello as it requires a hash of the truncated message.
* Don't know size of binders until Pre-Shared Key extension has been parsed.
@ -7057,6 +7120,7 @@ static int CheckPreSharedKeys(WOLFSSL* ssl, const byte* input, word32 helloSz,
TLSX* ext;
word16 bindersLen;
int first = 0;
int usePsk;
#ifndef WOLFSSL_PSK_ONE_ID
int i;
const Suites* suites;
@ -7109,6 +7173,12 @@ static int CheckPreSharedKeys(WOLFSSL* ssl, const byte* input, word32 helloSz,
/* Refine list for PSK processing. */
sslRefineSuites(ssl, clSuites);
usePsk = PskModesUsable(ssl, clSuites);
if (!usePsk) {
WOLFSSL_MSG("No usable psk_key_exchange_modes, ignoring PSK");
}
#ifndef WOLFSSL_PSK_ONE_ID
if (usingPSK == NULL)
return BAD_FUNC_ARG;
@ -7117,7 +7187,7 @@ static int CheckPreSharedKeys(WOLFSSL* ssl, const byte* input, word32 helloSz,
suites = WOLFSSL_SUITES(ssl);
/* Server list has only common suites from refining in server or client
* order. */
for (i = 0; !(*usingPSK) && i < suites->suiteSz; i += 2) {
for (i = 0; usePsk && !(*usingPSK) && i < suites->suiteSz; i += 2) {
ret = DoPreSharedKeys(ssl, input, helloSz - bindersLen,
suites->suites + i, usingPSK, &first);
if (ret != 0) {
@ -7135,11 +7205,13 @@ static int CheckPreSharedKeys(WOLFSSL* ssl, const byte* input, word32 helloSz,
CleanupClientTickets((PreSharedKey*)ext->data);
#endif
#else
ret = DoPreSharedKeys(ssl, input, helloSz - bindersLen, suite, usingPSK,
&first);
if (ret != 0) {
WOLFSSL_MSG_EX("DoPreSharedKeys: %d", ret);
return ret;
if (usePsk) {
ret = DoPreSharedKeys(ssl, input, helloSz - bindersLen, suite, usingPSK,
&first);
if (ret != 0) {
WOLFSSL_MSG_EX("DoPreSharedKeys: %d", ret);
return ret;
}
}
#endif
@ -7306,8 +7378,8 @@ static int CheckPreSharedKeys(WOLFSSL* ssl, const byte* input, word32 helloSz,
#ifdef HAVE_SUPPORTED_CURVES
ext = TLSX_Find(ssl->extensions, TLSX_KEY_SHARE);
/* Use (EC)DHE for forward-security if possible. */
if (((modes & (1 << PSK_DHE_KE)) != 0 && !ssl->options.noPskDheKe &&
ext != NULL)
if (((modes & (1 << PSK_DHE_KE)) != 0 &&
!ssl->options.noPskDheKePolicy && ext != NULL)
#ifdef WOLFSSL_CERT_WITH_EXTERN_PSK
|| usingCertWithExternPsk
#endif
@ -7327,7 +7399,11 @@ static int CheckPreSharedKeys(WOLFSSL* ssl, const byte* input, word32 helloSz,
(ssl->options.failNoPSK && !ssl->options.resuming)) {
/* A mandatory external PSK (failNoPSK) must be combined with
* (EC)DHE for forward secrecy, so reject a pure psk_ke
* negotiation. Session-ticket resumption is exempt. */
* negotiation. Session-ticket resumption is exempt.
* onlyPskDheKe only reaches here when PskModesUsable() could not
* decline, i.e. there is no certificate handshake to fall back
* to. */
WOLFSSL_ERROR_VERBOSE(PSK_KEY_ERROR);
return PSK_KEY_ERROR;
}
else
@ -14472,12 +14548,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);
@ -14623,6 +14699,50 @@ restore:
}
#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;
}
/* The configured policy, not noPskDheKe - a certificate handshake clears
* that one before the ticket is sent, which would make this always true. */
if ((ssl->options.pskKeModes & (1 << PSK_DHE_KE)) != 0 &&
!ssl->options.noPskDheKePolicy) {
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.
*
@ -14647,6 +14767,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;
@ -17087,6 +17213,7 @@ int wolfSSL_no_dhe_psk(WOLFSSL* ssl)
#if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)
ssl->options.noPskDheKe = 1;
ssl->options.noPskDheKePolicy = 1;
#endif
return 0;
@ -18137,17 +18264,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);

View File

@ -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();
}

View File

@ -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 */

View File

@ -2099,7 +2099,8 @@ int test_tls13_fail_if_no_psk_resumption_exempt_from_dhe(void)
EXPECT_DECLS;
#if defined(WOLFSSL_TLS13) && !defined(NO_PSK) && defined(HAVE_SESSION_TICKET) && \
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
defined(HAVE_SUPPORTED_CURVES) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \
defined(HAVE_SUPPORTED_CURVES) && \
!defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \
!defined(NO_CERTS) && !defined(NO_FILESYSTEM) && \
(defined(HAVE_ECC) || !defined(NO_RSA))
@ -10619,11 +10620,13 @@ int test_tls13_clear_preserves_psk_dhe(void)
ExpectIntEQ(wolfSSL_CTX_no_dhe_psk(ctx), 0);
ExpectNotNull(ssl = wolfSSL_new(ctx));
ExpectIntEQ(ssl->options.noPskDheKe, 1);
ExpectIntEQ(ssl->options.noPskDheKePolicy, 1);
/* SSL reuse must preserve the CTX-level noPskDheKe; resetting to 0
* would silently re-enable psk_dhe_ke for the next handshake. */
ExpectIntEQ(wolfSSL_clear(ssl), WOLFSSL_SUCCESS);
ExpectIntEQ(ssl->options.noPskDheKe, 1);
ExpectIntEQ(ssl->options.noPskDheKePolicy, 1);
wolfSSL_free(ssl);
wolfSSL_CTX_free(ctx);
@ -12662,3 +12665,531 @@ int test_tls13_cryptocb_async(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. Set
* through the public API - the handshake clears noPskDheKe, so the
* decision has to read the configured policy. */
ssl_s->options.pskKeModes = 1 << PSK_DHE_KE;
}
ExpectIntEQ(wolfSSL_no_dhe_psk(ssl_s), 0);
ExpectIntEQ(wolfSSL_send_SessionTicket(ssl_s),
WC_NO_ERR_TRACE(PSK_KEY_ERROR));
if (EXPECT_SUCCESS()) {
ssl_s->options.noPskDheKePolicy = 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;
}
ExpectIntEQ(wolfSSL_only_dhe_psk(ssl_s), 0);
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();
}
/* Parsing a NewSessionTicket must not disturb the extended_master_secret
* state. TLSX_Parse() clears haveEMS for any non-request message that does
* not carry the extension, and wolfSSL_clear() never puts it back, so a
* cleared flag would leave a reused object negotiating TLS 1.2 without EMS. */
int test_tls13_new_session_ticket_keeps_ems(void)
{
EXPECT_DECLS;
#if defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) && \
defined(HAVE_EXTENDED_MASTER) && \
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
!defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \
!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;
byte msg[64];
char buf[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);
if (EXPECT_SUCCESS() && ssl_c != NULL)
ssl_c->options.haveEMS = 1;
/* A ticket with an empty extensions vector. */
msgSz = -1;
if (EXPECT_SUCCESS())
msgSz = test_tls13_make_nst(msg, (int)sizeof(msg), NULL, 0);
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));
ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR),
WOLFSSL_ERROR_WANT_READ);
if (EXPECT_SUCCESS() && ssl_c != NULL)
ExpectIntEQ(ssl_c->options.haveEMS, 1);
wolfSSL_free(ssl_c);
wolfSSL_free(ssl_s);
wolfSSL_CTX_free(ctx_c);
wolfSSL_CTX_free(ctx_s);
#endif
return EXPECT_RESULT();
}
/* RFC 9846 Section 4.3.11: when the modes the client advertised leave no PSK
* the server may use, the PSK is ignored and a certificate handshake runs.
* Aborting instead would kill a connection that can still be completed. */
int test_tls13_psk_mode_mismatch_falls_back(void)
{
EXPECT_DECLS;
#if defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) && \
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
defined(HAVE_SUPPORTED_CURVES) && \
!defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \
!defined(NO_CERTS) && !defined(NO_FILESYSTEM)
int round;
for (round = 0; round < 2 && EXPECT_SUCCESS(); round++) {
WOLFSSL_CTX* ctx_c = NULL;
WOLFSSL_CTX* ctx_s = NULL;
WOLFSSL* ssl_c = NULL;
WOLFSSL* ssl_s = NULL;
WOLFSSL_SESSION* sess = NULL;
struct test_memio_ctx test_ctx;
WOLFSSL_ALERT_HISTORY h;
byte readBuf[16];
char buf[32];
static const char appData[] = "still talking";
/* Full handshake with no mode policy, to obtain a 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);
wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL);
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
/* Drain the NewSessionTicket so the session carries a ticket. */
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;
/* Resume into a mode mismatch. */
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);
if (round == 0) {
/* Client offers psk_ke only, server requires psk_dhe_ke. */
ExpectIntEQ(wolfSSL_no_dhe_psk(ssl_c), 0);
ExpectIntEQ(wolfSSL_only_dhe_psk(ssl_s), 0);
}
else {
/* Client offers psk_dhe_ke only, server refuses (EC)DHE. */
ExpectIntEQ(wolfSSL_only_dhe_psk(ssl_c), 0);
ExpectIntEQ(wolfSSL_no_dhe_psk(ssl_s), 0);
}
ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS);
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0);
/* The PSK was declined, not used, and no alert was raised. */
ExpectIntEQ(wolfSSL_session_reused(ssl_c), 0);
ExpectIntEQ(wolfSSL_session_reused(ssl_s), 0);
ExpectIntEQ(ssl_s->options.resuming, 0);
ExpectIntEQ(wolfSSL_get_alert_history(ssl_s, &h), WOLFSSL_SUCCESS);
ExpectIntEQ(h.last_tx.code, -1);
ExpectIntEQ(h.last_tx.level, -1);
if (round == 1) {
/* The certificate handshake clears noPskDheKe, so the configured
* policy has to be kept separately. */
ExpectIntEQ(ssl_s->options.noPskDheKePolicy, 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);
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 ticket decision must use the configured (EC)DHE policy. A certificate
* handshake clears ssl->options.noPskDheKe before the ticket is sent, so
* reading that field would make the psk_dhe_ke case always look compatible. */
int test_tls13_ticket_psk_modes_uses_policy(void)
{
EXPECT_DECLS;
#if defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) && \
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
defined(HAVE_SUPPORTED_CURVES) && \
!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;
/* Client advertises psk_dhe_ke only, server is configured to refuse
* (EC)DHE with PSK, so no ticket it could issue is usable. */
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0);
ExpectIntEQ(wolfSSL_only_dhe_psk(ssl_c), 0);
ExpectIntEQ(wolfSSL_no_dhe_psk(ssl_s), 0);
ExpectIntEQ(test_tls13_handshake_to_ticket(ssl_c, ssl_s), TEST_SUCCESS);
/* The handshake used a certificate, which clears the negotiated flag. */
ExpectIntEQ(ssl_s->options.noPskDheKe, 0);
ExpectIntEQ(ssl_s->options.noPskDheKePolicy, 1);
ExpectIntEQ(test_ctx.c_len, 0);
ExpectIntEQ(wolfSSL_accept(ssl_s), WOLFSSL_SUCCESS);
ExpectIntEQ(test_ctx.c_len, 0);
ExpectIntEQ(wolfSSL_send_SessionTicket(ssl_s),
WC_NO_ERR_TRACE(PSK_KEY_ERROR));
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: same client, server with no policy, 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(wolfSSL_only_dhe_psk(ssl_c), 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();
}

View File

@ -146,6 +146,12 @@ int test_tls13_pha_status_request(void);
int test_tls13_x25519_keyshare_masks_reserved_bit(void);
int test_tls13_is_init_finished_want_write(void);
int test_tls13_cryptocb_async(void);
int test_tls13_ticket_psk_modes(void);
int test_tls13_psk_mode_mismatch_falls_back(void);
int test_tls13_ticket_psk_modes_uses_policy(void);
int test_tls13_send_session_ticket_psk_modes(void);
int test_tls13_new_session_ticket_ext_framing(void);
int test_tls13_new_session_ticket_keeps_ems(void);
#define TEST_TLS13_DECLS \
TEST_DECL_GROUP("tls13", test_tls13_apis), \
@ -270,6 +276,12 @@ int test_tls13_cryptocb_async(void);
TEST_DECL_GROUP("tls13", test_tls13_pha_status_request), \
TEST_DECL_GROUP("tls13", test_tls13_x25519_keyshare_masks_reserved_bit), \
TEST_DECL_GROUP("tls13", test_tls13_is_init_finished_want_write), \
TEST_DECL_GROUP("tls13", test_tls13_cryptocb_async)
TEST_DECL_GROUP("tls13", test_tls13_cryptocb_async), \
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), \
TEST_DECL_GROUP("tls13", test_tls13_new_session_ticket_keeps_ems), \
TEST_DECL_GROUP("tls13", test_tls13_psk_mode_mismatch_falls_back), \
TEST_DECL_GROUP("tls13", test_tls13_ticket_psk_modes_uses_policy)
#endif /* WOLFCRYPT_TEST_TLS13_H */

View File

@ -292,15 +292,13 @@ int test_tls13_feat_psk_ke_no_dhe(void)
return EXPECT_RESULT();
}
/* The rejecting partner of the vector above, for the
*
* else if (onlyPskDheKe || (failNoPSK && !resumption))
*
* arms of CheckPreSharedKeys() and SetupPskKey(): a server that insists on
* forward secrecy (wolfSSL_only_dhe_psk) facing a client that offers
* psk_ke only. onlyPskDheKe had never been set on a live handshake -- the
* group only exercised it through the argument-validation API test. */
int test_tls13_feat_psk_only_dhe_rejects_psk_ke(void)
/* The declining partner of the vector above, for the onlyPskDheKe arm of
* PskModesUsable(): a server that insists on forward secrecy
* (wolfSSL_only_dhe_psk) facing a client that offers psk_ke only. No mode is
* usable, so the PSK is ignored and a certificate handshake runs instead.
* onlyPskDheKe had never been set on a live handshake -- the group only
* exercised it through the argument-validation API test. */
int test_tls13_feat_psk_only_dhe_ignores_psk_ke(void)
{
EXPECT_DECLS;
#if !defined(NO_PSK) && defined(HAVE_SUPPORTED_CURVES)
@ -322,9 +320,11 @@ int test_tls13_feat_psk_only_dhe_rejects_psk_ke(void)
ExpectIntEQ(wolfSSL_no_dhe_psk(ssl_c), 0);
ExpectIntEQ(wolfSSL_only_dhe_psk(ssl_s), 0);
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(PSK_KEY_ERROR));
/* RFC 9846 Section 4.3.11: no advertised mode is usable, so the server
* ignores the PSK and runs a certificate handshake instead of aborting. */
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
ExpectIntEQ(ssl_c->options.pskNegotiated, 0);
ExpectIntEQ(ssl_s->options.pskNegotiated, 0);
wolfSSL_free(ssl_c);
wolfSSL_CTX_free(ctx_c);
@ -1183,7 +1183,7 @@ int test_tls13_feat_psk_ke_no_dhe(void)
{
return TEST_SKIPPED;
}
int test_tls13_feat_psk_only_dhe_rejects_psk_ke(void)
int test_tls13_feat_psk_only_dhe_ignores_psk_ke(void)
{
return TEST_SKIPPED;
}

View File

@ -27,7 +27,7 @@
int test_tls13_feat_optional_client_cert(void);
int test_tls13_feat_post_handshake_unexpected_msg(void);
int test_tls13_feat_psk_ke_no_dhe(void);
int test_tls13_feat_psk_only_dhe_rejects_psk_ke(void);
int test_tls13_feat_psk_only_dhe_ignores_psk_ke(void);
int test_tls13_feat_no_ticket_enc_cb(void);
int test_tls13_feat_psk_ke_empty_key_share(void);
int test_tls13_feat_optional_psk_falls_back_to_cert(void);
@ -48,7 +48,7 @@ int test_tls13_feat_ech_psk_disabled_client(void);
TEST_DECL_GROUP("tls13", test_tls13_feat_optional_client_cert), \
TEST_DECL_GROUP("tls13", test_tls13_feat_post_handshake_unexpected_msg), \
TEST_DECL_GROUP("tls13", test_tls13_feat_psk_ke_no_dhe), \
TEST_DECL_GROUP("tls13", test_tls13_feat_psk_only_dhe_rejects_psk_ke), \
TEST_DECL_GROUP("tls13", test_tls13_feat_psk_only_dhe_ignores_psk_ke), \
TEST_DECL_GROUP("tls13", test_tls13_feat_no_ticket_enc_cb), \
TEST_DECL_GROUP("tls13", test_tls13_feat_psk_ke_empty_key_share), \
TEST_DECL_GROUP("tls13", test_tls13_feat_optional_psk_falls_back_to_cert), \

View File

@ -290,3 +290,15 @@
-u
-v 4
-l TLS13-SHA384-SHA384
# server DTLSv1.3 no (EC)DHE with PSK, must still key share in the HRR
-u
-v 4
-l TLS13-AES128-GCM-SHA256
-K
# client DTLSv1.3 HelloRetryRequest against a no-(EC)DHE-with-PSK server
-u
-v 4
-l TLS13-AES128-GCM-SHA256
-J

View File

@ -5457,6 +5457,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 */
@ -5509,6 +5513,11 @@ struct Options {
word16 usingAnon_cipher:1; /* are we using an anon cipher */
#if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)
word16 noPskDheKe:1; /* Don't use (EC)DHE with PSK */
/* noPskDheKe doubles as negotiated state - it is set when psk_ke is chosen
* and cleared on every certificate handshake. Decisions that must follow
* what the application configured use this copy, which is only written by
* the configuration APIs. */
word16 noPskDheKePolicy:1; /* Configured no (EC)DHE with PSK */
#ifdef HAVE_SUPPORTED_CURVES
word16 onlyPskDheKe:1; /* Only use (EC)DHE with PSK */
#endif
@ -5539,6 +5548,10 @@ struct Options {
#ifdef WOLFSSL_EARLY_DATA
word16 ticketPredatesCtx:1; /* PSK ticket minted before ctx */
#endif
#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