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/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/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 1dc587d3ec5..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) @@ -5051,9 +5211,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 +5218,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 +5228,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 +5241,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 +5255,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(); } 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), \