Merge pull request #6167 from yuhaoth/pr/finalize-tls13-session-tickets

diff --git a/include/mbedtls/mbedtls_config.h b/include/mbedtls/mbedtls_config.h
index 990dc58..687c5ef 100644
--- a/include/mbedtls/mbedtls_config.h
+++ b/include/mbedtls/mbedtls_config.h
@@ -1550,6 +1550,26 @@
 //#define MBEDTLS_SSL_TLS1_3_COMPATIBILITY_MODE
 
 /**
+ * \def MBEDTLS_SSL_TLS1_3_TICKET_AGE_TOLERANCE
+ *
+ * Maximum time difference in milliseconds tolerated between the age of a
+ * ticket from the server and client point of view.
+ * From the client point of view, the age of a ticket is the time difference
+ * between the time when the client proposes to the server to use the ticket
+ * (time of writing of the Pre-Shared Key Extension including the ticket) and
+ * the time the client received the ticket from the server.
+ * From the server point of view, the age of a ticket is the time difference
+ * between the time when the server receives a proposition from the client
+ * to use the ticket and the time when the ticket was created by the server.
+ * The server age is expected to be always greater than the client one and
+ * MBEDTLS_SSL_TLS1_3_TICKET_AGE_TOLERANCE defines the
+ * maximum difference tolerated for the server to accept the ticket.
+ * This is not used in TLS 1.2.
+ *
+ */
+#define MBEDTLS_SSL_TLS1_3_TICKET_AGE_TOLERANCE 6000
+
+/**
  * \def MBEDTLS_SSL_TLS1_3_TICKET_NONCE_LENGTH
  *
  * Size in bytes of a ticket nonce. This is not used in TLS 1.2.
diff --git a/library/ssl_misc.h b/library/ssl_misc.h
index a59e672..0b3ba90 100644
--- a/library/ssl_misc.h
+++ b/library/ssl_misc.h
@@ -2457,16 +2457,6 @@
 #endif
 
 #if defined(MBEDTLS_KEY_EXCHANGE_SOME_PSK_ENABLED)
-/* Check if we have any PSK to offer, returns 0 if PSK is available.
- * Assign the psk and ticket if pointers are present.
- */
-MBEDTLS_CHECK_RETURN_CRITICAL
-int mbedtls_ssl_get_psk_to_offer(
-        const mbedtls_ssl_context *ssl,
-        int *psk_type,
-        const unsigned char **psk, size_t *psk_len,
-        const unsigned char **psk_identity, size_t *psk_identity_len );
-
 /**
  * \brief Given an SSL context and its associated configuration, write the TLS
  *        1.3 specific Pre-Shared key extension.
diff --git a/library/ssl_tls13_client.c b/library/ssl_tls13_client.c
index 505f8dd..33e8cc6 100644
--- a/library/ssl_tls13_client.c
+++ b/library/ssl_tls13_client.c
@@ -664,10 +664,59 @@
     ssl->handshake->extensions_present |= MBEDTLS_SSL_EXT_PSK_KEY_EXCHANGE_MODES;
     return ( 0 );
 }
-#endif /* MBEDTLS_KEY_EXCHANGE_SOME_PSK_ENABLED */
+
+/* Check if we have any PSK to offer, returns 0 if a PSK is available. */
+MBEDTLS_CHECK_RETURN_CRITICAL
+static int ssl_tls13_get_psk_to_offer(
+        const mbedtls_ssl_context *ssl,
+        int *psk_type,
+        const unsigned char **psk, size_t *psk_len,
+        const unsigned char **psk_identity, size_t *psk_identity_len )
+{
+    *psk = NULL;
+    *psk_len = 0;
+    *psk_identity = NULL;
+    *psk_identity_len = 0;
+    *psk_type = MBEDTLS_SSL_TLS1_3_PSK_EXTERNAL;
+
+#if defined(MBEDTLS_SSL_SESSION_TICKETS)
+    /* Check if a ticket has been configured. */
+    if( ssl->session_negotiate != NULL &&
+        ssl->session_negotiate->ticket != NULL )
+    {
+#if defined(MBEDTLS_HAVE_TIME)
+        mbedtls_time_t now = mbedtls_time( NULL );
+        if( ssl->session_negotiate->ticket_received <= now &&
+            (uint64_t)( now - ssl->session_negotiate->ticket_received )
+                    <= ssl->session_negotiate->ticket_lifetime )
+        {
+            *psk_type = MBEDTLS_SSL_TLS1_3_PSK_RESUMPTION;
+            *psk = ssl->session_negotiate->resumption_key;
+            *psk_len = ssl->session_negotiate->resumption_key_len;
+            *psk_identity = ssl->session_negotiate->ticket;
+            *psk_identity_len = ssl->session_negotiate->ticket_len;
+            return( 0 );
+        }
+#endif /* MBEDTLS_HAVE_TIME */
+        MBEDTLS_SSL_DEBUG_MSG( 3, ( "ticket expired" ) );
+    }
+#endif
+
+    /* Check if an external PSK has been configured. */
+    if( ssl->conf->psk != NULL )
+    {
+        *psk = ssl->conf->psk;
+        *psk_len = ssl->conf->psk_len;
+        *psk_identity = ssl->conf->psk_identity;
+        *psk_identity_len = ssl->conf->psk_identity_len;
+        return( 0 );
+    }
+
+    return( MBEDTLS_ERR_ERROR_GENERIC_ERROR );
+}
 
 /*
- * mbedtls_ssl_tls13_write_pre_shared_key_ext() structure:
+ * mbedtls_ssl_tls13_write_identities_of_pre_shared_key_ext() structure:
  *
  * struct {
  *   opaque identity<1..2^16-1>;
@@ -689,9 +738,6 @@
  * } PreSharedKeyExtension;
  *
  */
-
-#if defined(MBEDTLS_KEY_EXCHANGE_SOME_PSK_ENABLED)
-
 int mbedtls_ssl_tls13_write_identities_of_pre_shared_key_ext(
     mbedtls_ssl_context *ssl,
     unsigned char *buf, unsigned char *end,
@@ -725,9 +771,8 @@
      *   configured, offer that.
      * - Otherwise, skip the PSK extension.
      */
-
-    if( mbedtls_ssl_get_psk_to_offer( ssl, &psk_type, &psk, &psk_len,
-                                      &psk_identity, &psk_identity_len ) != 0 )
+    if( ssl_tls13_get_psk_to_offer( ssl, &psk_type, &psk, &psk_len,
+                                    &psk_identity, &psk_identity_len ) != 0 )
     {
         MBEDTLS_SSL_DEBUG_MSG( 3, ( "skip pre_shared_key extensions" ) );
         return( 0 );
@@ -757,6 +802,26 @@
             break;
         }
     }
+    else
+#if defined(MBEDTLS_SSL_SESSION_TICKETS)
+    if( psk_type == MBEDTLS_SSL_TLS1_3_PSK_RESUMPTION )
+    {
+#if defined(MBEDTLS_HAVE_TIME)
+        mbedtls_time_t now = mbedtls_time( NULL );
+
+        obfuscated_ticket_age =
+            ( (uint32_t)( now - ssl->session_negotiate->ticket_received ) * 1000 )
+              + ssl->session_negotiate->ticket_age_add;
+#endif
+    }
+    else
+#endif /* MBEDTLS_SSL_SESSION_TICKETS */
+    {
+        MBEDTLS_SSL_DEBUG_MSG( 1, ( "write_identities_of_pre_shared_key_ext: "
+                                    "should never happen" ) );
+        return( MBEDTLS_ERR_SSL_INTERNAL_ERROR );
+    }
+
 
     ciphersuite_info = mbedtls_ssl_ciphersuite_from_id(
             ssl->session_negotiate->ciphersuite );
@@ -831,8 +896,8 @@
     unsigned char transcript[MBEDTLS_MD_MAX_SIZE];
     size_t transcript_len;
 
-    if( mbedtls_ssl_get_psk_to_offer( ssl, &psk_type, &psk, &psk_len,
-                                      &psk_identity, &psk_identity_len ) != 0 )
+    if( ssl_tls13_get_psk_to_offer( ssl, &psk_type, &psk, &psk_len,
+                                    &psk_identity, &psk_identity_len ) != 0 )
     {
         return( MBEDTLS_ERR_SSL_INTERNAL_ERROR );
     }
@@ -1266,15 +1331,15 @@
     size_t psk_len;
     const unsigned char *psk_identity;
     size_t psk_identity_len;
-
+    int psk_type;
 
     /* Check which PSK we've offered.
      *
      * NOTE: Ultimately, we want to offer multiple PSKs, and in this
      *       case, we need to iterate over them here.
      */
-    if( mbedtls_ssl_get_psk_to_offer( ssl, NULL, &psk, &psk_len,
-                                      &psk_identity, &psk_identity_len ) != 0 )
+    if( ssl_tls13_get_psk_to_offer( ssl, &psk_type, &psk, &psk_len,
+                                    &psk_identity, &psk_identity_len ) != 0 )
     {
         /* If we haven't offered a PSK, the server must not send
          * a PSK identity extension. */
@@ -1622,16 +1687,19 @@
         /* Only the pre_shared_key extension was received */
         case MBEDTLS_SSL_EXT_PRE_SHARED_KEY:
             handshake->key_exchange_mode = MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK;
+            MBEDTLS_SSL_DEBUG_MSG( 2, ( "key exchange mode: psk" ) );
             break;
 
         /* Only the key_share extension was received */
         case MBEDTLS_SSL_EXT_KEY_SHARE:
             handshake->key_exchange_mode = MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL;
+            MBEDTLS_SSL_DEBUG_MSG( 2, ( "key exchange mode: ephemeral" ) );
             break;
 
         /* Both the pre_shared_key and key_share extensions were received */
         case ( MBEDTLS_SSL_EXT_PRE_SHARED_KEY | MBEDTLS_SSL_EXT_KEY_SHARE ):
             handshake->key_exchange_mode = MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_EPHEMERAL;
+            MBEDTLS_SSL_DEBUG_MSG( 2, ( "key exchange mode: psk_ephemeral" ) );
             break;
 
         /* Neither pre_shared_key nor key_share extension was received */
@@ -1819,7 +1887,12 @@
          */
         switch( extension_type )
         {
+            case MBEDTLS_TLS_EXT_SERVERNAME:
+                MBEDTLS_SSL_DEBUG_MSG( 3, ( "found server_name extension" ) );
 
+                /* The server_name extension should be an empty extension */
+
+                break;
             case MBEDTLS_TLS_EXT_SUPPORTED_GROUPS:
                 MBEDTLS_SSL_DEBUG_MSG( 3, ( "found extensions supported groups" ) );
                 break;
@@ -2237,11 +2310,11 @@
     if( ret != 0 )
         return( ret );
 
-    ret = mbedtls_ssl_tls13_generate_resumption_master_secret( ssl );
+    ret = mbedtls_ssl_tls13_compute_resumption_master_secret( ssl );
     if( ret != 0 )
     {
         MBEDTLS_SSL_DEBUG_RET( 1,
-                "mbedtls_ssl_tls13_generate_resumption_master_secret ", ret );
+                "mbedtls_ssl_tls13_compute_resumption_master_secret ", ret );
         return ( ret );
     }
 
@@ -2405,6 +2478,9 @@
         return( ret );
     }
 
+    /* session has been updated, allow export */
+    session->exported = 0;
+
     return( 0 );
 }
 
diff --git a/library/ssl_tls13_generic.c b/library/ssl_tls13_generic.c
index 6f60fab..f74cb40 100644
--- a/library/ssl_tls13_generic.c
+++ b/library/ssl_tls13_generic.c
@@ -1504,41 +1504,4 @@
 }
 #endif /* MBEDTLS_ECDH_C */
 
-#if defined(MBEDTLS_KEY_EXCHANGE_SOME_PSK_ENABLED)
-/* Check if we have any PSK to offer, returns 0 if PSK is available.
- * Assign the psk and ticket if pointers are present.
- */
-int mbedtls_ssl_get_psk_to_offer(
-        const mbedtls_ssl_context *ssl,
-        int *psk_type,
-        const unsigned char **psk, size_t *psk_len,
-        const unsigned char **psk_identity, size_t *psk_identity_len )
-{
-    int ptrs_present = 0;
-
-    if( psk_type != NULL && psk != NULL && psk_len != NULL &&
-        psk_identity != NULL && psk_identity_len != NULL )
-    {
-        ptrs_present = 1;
-    }
-
-    /* Check if an external PSK has been configured. */
-    if( ssl->conf->psk != NULL )
-    {
-        if( ptrs_present )
-        {
-            *psk_type = MBEDTLS_SSL_TLS1_3_PSK_EXTERNAL;
-            *psk = ssl->conf->psk;
-            *psk_len = ssl->conf->psk_len;
-            *psk_identity = ssl->conf->psk_identity;
-            *psk_identity_len = ssl->conf->psk_identity_len;
-        }
-
-        return( 0 );
-    }
-
-    return( 1 );
-}
-#endif /* MBEDTLS_KEY_EXCHANGE_SOME_PSK_ENABLED */
-
 #endif /* MBEDTLS_SSL_TLS_C && MBEDTLS_SSL_PROTO_TLS1_3 */
diff --git a/library/ssl_tls13_keys.c b/library/ssl_tls13_keys.c
index 32a4f2a..48de3d0 100644
--- a/library/ssl_tls13_keys.c
+++ b/library/ssl_tls13_keys.c
@@ -1504,12 +1504,43 @@
     return( ret );
 }
 
-int mbedtls_ssl_tls13_generate_resumption_master_secret(
-    mbedtls_ssl_context *ssl )
+int mbedtls_ssl_tls13_compute_resumption_master_secret( mbedtls_ssl_context *ssl )
 {
+    int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
+    mbedtls_md_type_t md_type;
+    mbedtls_ssl_handshake_params *handshake = ssl->handshake;
+    unsigned char transcript[MBEDTLS_TLS1_3_MD_MAX_SIZE];
+    size_t transcript_len;
+
+    MBEDTLS_SSL_DEBUG_MSG( 2,
+        ( "=> mbedtls_ssl_tls13_compute_resumption_master_secret" ) );
+
+    md_type = handshake->ciphersuite_info->mac;
+
+    ret = mbedtls_ssl_get_handshake_transcript( ssl, md_type,
+                                                transcript, sizeof( transcript ),
+                                                &transcript_len );
+    if( ret != 0 )
+        return( ret );
+
+    ret = mbedtls_ssl_tls13_derive_resumption_master_secret(
+                              mbedtls_psa_translate_md( md_type ),
+                              handshake->tls13_master_secrets.app,
+                              transcript, transcript_len,
+                              &ssl->session_negotiate->app_secrets );
+    if( ret != 0 )
+        return( ret );
+
     /* Erase master secrets */
-    mbedtls_platform_zeroize( &ssl->handshake->tls13_master_secrets,
-                              sizeof( ssl->handshake->tls13_master_secrets ) );
+    mbedtls_platform_zeroize( &handshake->tls13_master_secrets,
+                              sizeof( handshake->tls13_master_secrets ) );
+
+    MBEDTLS_SSL_DEBUG_BUF( 4, "Resumption master secret",
+             ssl->session_negotiate->app_secrets.resumption_master_secret,
+             PSA_HASH_LENGTH( mbedtls_psa_translate_md( md_type ) ) ) ;
+
+    MBEDTLS_SSL_DEBUG_MSG( 2,
+        ( "<= mbedtls_ssl_tls13_compute_resumption_master_secret" ) );
     return( 0 );
 }
 
diff --git a/library/ssl_tls13_keys.h b/library/ssl_tls13_keys.h
index d82bf7a..f3bdf37 100644
--- a/library/ssl_tls13_keys.h
+++ b/library/ssl_tls13_keys.h
@@ -636,8 +636,7 @@
  * \returns    A negative error code on failure.
  */
 MBEDTLS_CHECK_RETURN_CRITICAL
-int mbedtls_ssl_tls13_generate_resumption_master_secret(
-    mbedtls_ssl_context *ssl );
+int mbedtls_ssl_tls13_compute_resumption_master_secret( mbedtls_ssl_context *ssl );
 
 /**
  * \brief Calculate the verify_data value for the client or server TLS 1.3
diff --git a/library/ssl_tls13_server.c b/library/ssl_tls13_server.c
index a10e59b..e185dc1 100644
--- a/library/ssl_tls13_server.c
+++ b/library/ssl_tls13_server.c
@@ -121,14 +121,170 @@
 
 #define SSL_TLS1_3_OFFERED_PSK_NOT_MATCH   1
 #define SSL_TLS1_3_OFFERED_PSK_MATCH       0
+
+#if defined(MBEDTLS_SSL_SESSION_TICKETS)
+
+MBEDTLS_CHECK_RETURN_CRITICAL
+static int ssl_tls13_offered_psks_check_identity_match_ticket(
+                                            mbedtls_ssl_context *ssl,
+                                            const unsigned char *identity,
+                                            size_t identity_len,
+                                            uint32_t obfuscated_ticket_age,
+                                            mbedtls_ssl_session *session )
+{
+    int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
+    unsigned char *ticket_buffer;
+#if defined(MBEDTLS_HAVE_TIME)
+    mbedtls_time_t now;
+    uint64_t age_in_s;
+    int64_t age_diff_in_ms;
+#endif
+
+    ((void) obfuscated_ticket_age);
+
+    MBEDTLS_SSL_DEBUG_MSG( 2, ( "=> check_identity_match_ticket" ) );
+
+    /* Ticket parser is not configured, Skip */
+    if( ssl->conf->f_ticket_parse == NULL || identity_len == 0 )
+        return( 0 );
+
+    /* We create a copy of the encrypted ticket since the ticket parsing
+     * function is allowed to use its input buffer as an output buffer
+     * (in-place decryption). We do, however, need the original buffer for
+     * computing the PSK binder value.
+     */
+    ticket_buffer = mbedtls_calloc( 1, identity_len );
+    if( ticket_buffer == NULL )
+    {
+        MBEDTLS_SSL_DEBUG_MSG( 1, ( "buffer too small" ) );
+        return ( MBEDTLS_ERR_SSL_ALLOC_FAILED );
+    }
+    memcpy( ticket_buffer, identity, identity_len );
+
+    if( ( ret = ssl->conf->f_ticket_parse( ssl->conf->p_ticket,
+                                           session,
+                                           ticket_buffer, identity_len ) ) != 0 )
+    {
+        if( ret == MBEDTLS_ERR_SSL_INVALID_MAC )
+            MBEDTLS_SSL_DEBUG_MSG( 3, ( "ticket is not authentic" ) );
+        else if( ret == MBEDTLS_ERR_SSL_SESSION_TICKET_EXPIRED )
+            MBEDTLS_SSL_DEBUG_MSG( 3, ( "ticket is expired" ) );
+        else
+            MBEDTLS_SSL_DEBUG_RET( 1, "ticket_parse", ret );
+    }
+
+    /* We delete the temporary buffer */
+    mbedtls_free( ticket_buffer );
+
+    if( ret != 0 )
+        goto exit;
+
+    ret = MBEDTLS_ERR_SSL_SESSION_TICKET_EXPIRED;
+#if defined(MBEDTLS_HAVE_TIME)
+    now = mbedtls_time( NULL );
+
+    if( now < session->start )
+    {
+        MBEDTLS_SSL_DEBUG_MSG(
+            3, ( "Ticket expired: now=%" MBEDTLS_PRINTF_LONGLONG
+                    ", start=%" MBEDTLS_PRINTF_LONGLONG,
+                    (long long)now, (long long)session->start ) );
+        goto exit;
+    }
+
+    age_in_s = (uint64_t)( now - session->start );
+
+    /* RFC 8446 section 4.6.1
+     *
+     * Servers MUST NOT use any value greater than 604800 seconds (7 days).
+     *
+     * RFC 8446 section 4.2.11.1
+     *
+     * Clients MUST NOT attempt to use tickets which have ages greater than
+     * the "ticket_lifetime" value which was provided with the ticket.
+     *
+     * For time being, the age MUST be less than 604800 seconds (7 days).
+     */
+    if( age_in_s > 604800 )
+    {
+        MBEDTLS_SSL_DEBUG_MSG(
+            3, ( "Ticket expired: Ticket age exceed limitation ticket_age=%lu",
+                 (long unsigned int)age_in_s ) );
+        goto exit;
+    }
+
+    /* RFC 8446 section 4.2.10
+     *
+     * For PSKs provisioned via NewSessionTicket, a server MUST validate that
+     * the ticket age for the selected PSK identity (computed by subtracting
+     * ticket_age_add from PskIdentity.obfuscated_ticket_age modulo 2^32) is
+     * within a small tolerance of the time since the ticket was issued.
+     *
+     * NOTE: When `now == session->start`, `age_diff_in_ms` may be negative
+     *       as the age units are different on the server (s) and in the
+     *       client (ms) side. Add a -1000 ms tolerance window to take this
+     *       into account.
+     */
+    age_diff_in_ms = age_in_s * 1000;
+    age_diff_in_ms -= ( obfuscated_ticket_age - session->ticket_age_add );
+    if( age_diff_in_ms <= -1000 ||
+        age_diff_in_ms > MBEDTLS_SSL_TLS1_3_TICKET_AGE_TOLERANCE )
+    {
+        MBEDTLS_SSL_DEBUG_MSG(
+            3, ( "Ticket expired: Ticket age outside tolerance window "
+                     "( diff=%d )", (int)age_diff_in_ms ) );
+        goto exit;
+    }
+
+    ret = 0;
+
+#endif /* MBEDTLS_HAVE_TIME */
+
+exit:
+    if( ret != 0 )
+        mbedtls_ssl_session_free( session );
+
+    MBEDTLS_SSL_DEBUG_MSG( 2, ( "<= check_identity_match_ticket" ) );
+    return( ret );
+}
+#endif /* MBEDTLS_SSL_SESSION_TICKETS */
+
 MBEDTLS_CHECK_RETURN_CRITICAL
 static int ssl_tls13_offered_psks_check_identity_match(
                mbedtls_ssl_context *ssl,
                const unsigned char *identity,
                size_t identity_len,
-               int *psk_type )
+               uint32_t obfuscated_ticket_age,
+               int *psk_type,
+               mbedtls_ssl_session *session )
 {
+    ((void) session);
+    ((void) obfuscated_ticket_age);
     *psk_type = MBEDTLS_SSL_TLS1_3_PSK_EXTERNAL;
+
+    MBEDTLS_SSL_DEBUG_BUF( 4, "identity", identity, identity_len );
+    ssl->handshake->resume = 0;
+
+#if defined(MBEDTLS_SSL_SESSION_TICKETS)
+    if( ssl_tls13_offered_psks_check_identity_match_ticket(
+            ssl, identity, identity_len, obfuscated_ticket_age,
+            session ) == SSL_TLS1_3_OFFERED_PSK_MATCH )
+    {
+        ssl->handshake->resume = 1;
+        *psk_type = MBEDTLS_SSL_TLS1_3_PSK_RESUMPTION;
+        mbedtls_ssl_set_hs_psk( ssl,
+                                session->resumption_key,
+                                session->resumption_key_len );
+
+        MBEDTLS_SSL_DEBUG_BUF( 4, "Ticket-resumed PSK:",
+                               session->resumption_key,
+                               session->resumption_key_len );
+        MBEDTLS_SSL_DEBUG_MSG( 4, ( "ticket: obfuscated_ticket_age: %u",
+                                    (unsigned)obfuscated_ticket_age ) );
+        return( SSL_TLS1_3_OFFERED_PSK_MATCH );
+    }
+#endif /* MBEDTLS_SSL_SESSION_TICKETS */
+
     /* Check identity with external configured function */
     if( ssl->conf->f_psk != NULL )
     {
@@ -256,6 +412,7 @@
     return( MBEDTLS_ERR_SSL_HANDSHAKE_FAILURE );
 }
 
+#if defined(MBEDTLS_SSL_SESSION_TICKETS)
 MBEDTLS_CHECK_RETURN_CRITICAL
 static int ssl_tls13_select_ciphersuite_for_resumption(
                mbedtls_ssl_context *ssl,
@@ -265,15 +422,46 @@
                uint16_t *selected_ciphersuite,
                const mbedtls_ssl_ciphersuite_t **selected_ciphersuite_info )
 {
-    ((void) ssl);
-    ((void) session);
-    ((void) cipher_suites);
-    ((void) cipher_suites_end);
+
     *selected_ciphersuite = 0;
     *selected_ciphersuite_info = NULL;
-    return( MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE );
+    for( const unsigned char *p = cipher_suites; p < cipher_suites_end; p += 2 )
+    {
+        uint16_t cipher_suite = MBEDTLS_GET_UINT16_BE( p, 0 );
+        const mbedtls_ssl_ciphersuite_t *ciphersuite_info;
+
+        if( cipher_suite != session->ciphersuite )
+            continue;
+
+        ciphersuite_info = ssl_tls13_validate_peer_ciphersuite( ssl,
+                                                                cipher_suite );
+        if( ciphersuite_info == NULL )
+            continue;
+
+        *selected_ciphersuite = cipher_suite;
+        *selected_ciphersuite_info = ciphersuite_info;
+
+        return( 0 );
+    }
+
+    return( MBEDTLS_ERR_SSL_HANDSHAKE_FAILURE );
 }
 
+MBEDTLS_CHECK_RETURN_CRITICAL
+static int ssl_tls13_session_copy_ticket( mbedtls_ssl_session *dst,
+                                          const mbedtls_ssl_session *src )
+{
+    dst->ticket_age_add = src->ticket_age_add;
+    dst->ticket_flags = src->ticket_flags;
+    dst->resumption_key_len = src->resumption_key_len;
+    if( src->resumption_key_len == 0 )
+        return( MBEDTLS_ERR_SSL_INTERNAL_ERROR );
+    memcpy( dst->resumption_key, src->resumption_key, src->resumption_key_len );
+
+    return( 0 );
+}
+#endif /* MBEDTLS_SSL_SESSION_TICKETS */
+
 /* Parser for pre_shared_key extension in client hello
  *    struct {
  *        opaque identity<1..2^16-1>;
@@ -343,17 +531,23 @@
     {
         const unsigned char *identity;
         size_t identity_len;
+        uint32_t obfuscated_ticket_age;
         const unsigned char *binder;
         size_t binder_len;
         int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
         int psk_type;
         uint16_t cipher_suite;
         const mbedtls_ssl_ciphersuite_t *ciphersuite_info;
+#if defined(MBEDTLS_SSL_SESSION_TICKETS)
+        mbedtls_ssl_session session;
+        mbedtls_ssl_session_init( &session );
+#endif
 
         MBEDTLS_SSL_CHK_BUF_READ_PTR( p_identity_len, identities_end, 2 + 1 + 4 );
         identity_len = MBEDTLS_GET_UINT16_BE( p_identity_len, 0 );
         identity = p_identity_len + 2;
         MBEDTLS_SSL_CHK_BUF_READ_PTR( identity, identities_end, identity_len + 4 );
+        obfuscated_ticket_age = MBEDTLS_GET_UINT32_BE( identity , identity_len );
         p_identity_len += identity_len + 6;
 
         MBEDTLS_SSL_CHK_BUF_READ_PTR( p_binder_len, binders_end, 1 + 32 );
@@ -367,7 +561,8 @@
             continue;
 
         ret = ssl_tls13_offered_psks_check_identity_match(
-                                    ssl, identity, identity_len, &psk_type );
+                  ssl, identity, identity_len, obfuscated_ticket_age,
+                  &psk_type, &session );
         if( ret != SSL_TLS1_3_OFFERED_PSK_MATCH )
             continue;
 
@@ -380,9 +575,15 @@
                             &cipher_suite, &ciphersuite_info );
                 break;
             case MBEDTLS_SSL_TLS1_3_PSK_RESUMPTION:
+#if defined(MBEDTLS_SSL_SESSION_TICKETS)
                 ret = ssl_tls13_select_ciphersuite_for_resumption(
-                            ssl, ciphersuites, ciphersuites_end, NULL,
+                            ssl, ciphersuites, ciphersuites_end, &session,
                             &cipher_suite, &ciphersuite_info );
+                if( ret != 0 )
+                    mbedtls_ssl_session_free( &session );
+#else
+                ret = MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE;
+#endif
                 break;
             default:
                 return( MBEDTLS_ERR_SSL_INTERNAL_ERROR );
@@ -406,6 +607,9 @@
             /* For security reasons, the handshake should be aborted when we
              * fail to validate a binder value. See RFC 8446 section 4.2.11.2
              * and appendix E.6. */
+#if defined(MBEDTLS_SSL_SESSION_TICKETS)
+            mbedtls_ssl_session_free( &session );
+#endif
             MBEDTLS_SSL_DEBUG_MSG( 3, ( "Invalid binder." ) );
             MBEDTLS_SSL_DEBUG_RET( 1,
                 "ssl_tls13_offered_psks_check_binder_match" , ret );
@@ -418,11 +622,20 @@
         matched_identity = identity_id;
 
         /* Update handshake parameters */
-        ssl->session_negotiate->ciphersuite = cipher_suite;
         ssl->handshake->ciphersuite_info = ciphersuite_info;
+        ssl->session_negotiate->ciphersuite = cipher_suite;
         MBEDTLS_SSL_DEBUG_MSG( 2, ( "overwrite ciphersuite: %04x - %s",
                                     cipher_suite, ciphersuite_info->name ) );
-
+#if defined(MBEDTLS_SSL_SESSION_TICKETS)
+        if( psk_type == MBEDTLS_SSL_TLS1_3_PSK_RESUMPTION )
+        {
+            ret = ssl_tls13_session_copy_ticket( ssl->session_negotiate,
+                                                 &session );
+            mbedtls_ssl_session_free( &session );
+            if( ret != 0 )
+                return( ret );
+        }
+#endif /* MBEDTLS_SSL_SESSION_TICKETS */
     }
 
     if( p_identity_len != identities_end || p_binder_len != binders_end )
@@ -2366,11 +2579,11 @@
     if( ret != 0 )
         return( ret );
 
-    ret = mbedtls_ssl_tls13_generate_resumption_master_secret( ssl );
+    ret = mbedtls_ssl_tls13_compute_resumption_master_secret( ssl );
     if( ret != 0 )
     {
         MBEDTLS_SSL_DEBUG_RET( 1,
-            "mbedtls_ssl_tls13_generate_resumption_master_secret ", ret );
+            "mbedtls_ssl_tls13_compute_resumption_master_secret", ret );
     }
 
     mbedtls_ssl_handshake_set_state( ssl, MBEDTLS_SSL_HANDSHAKE_WRAPUP );
diff --git a/programs/ssl/ssl_client2.c b/programs/ssl/ssl_client2.c
index 8915011..050d518 100644
--- a/programs/ssl/ssl_client2.c
+++ b/programs/ssl/ssl_client2.c
@@ -657,6 +657,58 @@
 }
 #endif /* MBEDTLS_SSL_DTLS_CONNECTION_ID */
 
+static int ssl_save_session_serialize( mbedtls_ssl_context *ssl,
+                                       unsigned char **session_data,
+                                       size_t *session_data_len )
+{
+    int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
+    mbedtls_ssl_session exported_session;
+
+    /* free any previously saved data */
+    if( *session_data != NULL )
+    {
+        mbedtls_platform_zeroize( *session_data, *session_data_len );
+        mbedtls_free( *session_data );
+        *session_data = NULL;
+        *session_data_len = 0;
+    }
+
+    mbedtls_ssl_session_init( &exported_session );
+    ret = mbedtls_ssl_get_session( ssl, &exported_session );
+    if( ret != 0 )
+    {
+        mbedtls_printf(
+            "failed\n  ! mbedtls_ssl_get_session() returned -%#02x\n",
+            (unsigned) -ret );
+        goto exit;
+    }
+
+    /* get size of the buffer needed */
+    mbedtls_ssl_session_save( &exported_session, NULL, 0, session_data_len );
+    *session_data = mbedtls_calloc( 1, *session_data_len );
+    if( *session_data == NULL )
+    {
+        mbedtls_printf( " failed\n  ! alloc %u bytes for session data\n",
+                        (unsigned) *session_data_len );
+        ret = MBEDTLS_ERR_SSL_ALLOC_FAILED;
+        goto exit;
+    }
+
+    /* actually save session data */
+    if( ( ret = mbedtls_ssl_session_save( &exported_session,
+                                          *session_data, *session_data_len,
+                                          session_data_len ) ) != 0 )
+    {
+        mbedtls_printf( " failed\n  ! mbedtls_ssl_session_saved returned -0x%04x\n\n",
+                        (unsigned int) -ret );
+        goto exit;
+    }
+
+exit:
+    mbedtls_ssl_session_free( &exported_session );
+    return( ret );
+}
+
 int main( int argc, char *argv[] )
 {
     int ret = 0, len, tail_len, i, written, frags, retry_left;
@@ -2360,57 +2412,21 @@
         }
     }
 #endif /* MBEDTLS_SSL_DTLS_SRTP */
-    if( opt.reconnect != 0 )
+    if( opt.reconnect != 0 && ssl.tls_version != MBEDTLS_SSL_VERSION_TLS1_3 )
     {
         mbedtls_printf("  . Saving session for reuse..." );
         fflush( stdout );
 
         if( opt.reco_mode == 1 )
         {
-            mbedtls_ssl_session exported_session;
-
-            /* free any previously saved data */
-            if( session_data != NULL )
+            if( ( ret = ssl_save_session_serialize( &ssl,
+                            &session_data, &session_data_len ) ) != 0 )
             {
-                mbedtls_platform_zeroize( session_data, session_data_len );
-                mbedtls_free( session_data );
-                session_data = NULL;
-            }
-
-            mbedtls_ssl_session_init( &exported_session );
-            ret = mbedtls_ssl_get_session( &ssl, &exported_session );
-            if( ret != 0 )
-            {
-                mbedtls_printf(
-                    "failed\n  ! mbedtls_ssl_get_session() returned -%#02x\n",
-                    (unsigned) -ret );
-                goto exit;
-            }
-
-            /* get size of the buffer needed */
-            mbedtls_ssl_session_save( &exported_session, NULL, 0, &session_data_len );
-            session_data = mbedtls_calloc( 1, session_data_len );
-            if( session_data == NULL )
-            {
-                mbedtls_printf( " failed\n  ! alloc %u bytes for session data\n",
-                                (unsigned) session_data_len );
-                mbedtls_ssl_session_free( &exported_session );
-                ret = MBEDTLS_ERR_SSL_ALLOC_FAILED;
-                goto exit;
-            }
-
-            /* actually save session data */
-            if( ( ret = mbedtls_ssl_session_save( &exported_session,
-                                                  session_data, session_data_len,
-                                                  &session_data_len ) ) != 0 )
-            {
-                mbedtls_printf( " failed\n  ! mbedtls_ssl_session_saved returned -0x%04x\n\n",
+                mbedtls_printf( " failed\n  ! ssl_save_session_serialize returned -0x%04x\n\n",
                                 (unsigned int) -ret );
-                mbedtls_ssl_session_free( &exported_session );
                 goto exit;
             }
 
-            mbedtls_ssl_session_free( &exported_session );
         }
         else
         {
@@ -2700,6 +2716,40 @@
                         /* We were waiting for application data but got
                          * a NewSessionTicket instead. */
                         mbedtls_printf( " got new session ticket.\n" );
+                        if( opt.reconnect != 0 )
+                        {
+                            mbedtls_printf("  . Saving session for reuse..." );
+                            fflush( stdout );
+
+                            if( opt.reco_mode == 1 )
+                            {
+                                if( ( ret = ssl_save_session_serialize( &ssl,
+                                                &session_data, &session_data_len ) ) != 0 )
+                                {
+                                    mbedtls_printf( " failed\n  ! ssl_save_session_serialize returned -0x%04x\n\n",
+                                                    (unsigned int) -ret );
+                                    goto exit;
+                                }
+                            }
+                            else
+                            {
+                                if( ( ret = mbedtls_ssl_get_session( &ssl, &saved_session ) ) != 0 )
+                                {
+                                    mbedtls_printf( " failed\n  ! mbedtls_ssl_get_session returned -0x%x\n\n",
+                                                    (unsigned int) -ret );
+                                    goto exit;
+                                }
+                            }
+
+                            mbedtls_printf( " ok\n" );
+
+                            if( opt.reco_mode == 1 )
+                            {
+                                mbedtls_printf( "    [ Saved %u bytes of session data]\n",
+                                                (unsigned) session_data_len );
+                            }
+                        }
+
                         continue;
 #endif /* MBEDTLS_SSL_SESSION_TICKETS */
 
diff --git a/tests/ssl-opt.sh b/tests/ssl-opt.sh
index 9e14af1..84bcd3c 100755
--- a/tests/ssl-opt.sh
+++ b/tests/ssl-opt.sh
@@ -12096,7 +12096,7 @@
 run_test    "TLS 1.3, default suite, PSK" \
             "$P_SRV nbio=2 debug_level=5 force_version=tls13 psk=010203 psk_identity=0a0b0c tls13_kex_modes=psk" \
             "$P_CLI nbio=2 debug_level=5 force_version=tls13 psk=010203 psk_identity=0a0b0c tls13_kex_modes=psk" \
-            1 \
+            0 \
             -c "=> write client hello" \
             -c "client hello, adding pre_shared_key extension, omitting PSK binder list" \
             -c "client hello, adding psk_key_exchange_modes extension" \
@@ -12111,7 +12111,7 @@
 run_test    "TLS 1.3, default suite, PSK - openssl" \
             "$O_NEXT_SRV -msg -debug -tls1_3 -psk_identity 0a0b0c -psk 010203 -allow_no_dhe_kex -nocert" \
             "$P_CLI debug_level=4 psk=010203 psk_identity=0a0b0c tls13_kex_modes=psk" \
-            1 \
+            0 \
             -c "=> write client hello" \
             -c "client hello, adding pre_shared_key extension, omitting PSK binder list" \
             -c "client hello, adding psk_key_exchange_modes extension" \
@@ -12756,13 +12756,13 @@
 requires_config_enabled MBEDTLS_SSL_CLI_C
 requires_config_enabled MBEDTLS_SSL_TLS1_3_COMPATIBILITY_MODE
 run_test    "TLS 1.3: NewSessionTicket: Basic check, m->O" \
-            "$O_NEXT_SRV -msg -tls1_3 -no_resume_ephemeral -no_cache " \
-            "$P_CLI debug_level=4 reco_mode=1 reconnect=1" \
+            "$O_NEXT_SRV -msg -tls1_3 -no_resume_ephemeral -no_cache --num_tickets 4" \
+            "$P_CLI debug_level=1 reco_mode=1 reconnect=1" \
             0 \
             -c "Protocol is TLSv1.3" \
-            -c "MBEDTLS_SSL_NEW_SESSION_TICKET" \
             -c "got new session ticket." \
             -c "Saving session for reuse... ok" \
+            -c "Reconnecting with saved session" \
             -c "HTTP/1.0 200 ok"
 
 requires_gnutls_tls1_3
@@ -12771,27 +12771,15 @@
 requires_config_enabled MBEDTLS_DEBUG_C
 requires_config_enabled MBEDTLS_SSL_CLI_C
 run_test    "TLS 1.3: NewSessionTicket: Basic check, m->G" \
-            "$G_NEXT_SRV --priority=NORMAL:-VERS-ALL:+VERS-TLS1.3:+CIPHER-ALL:+PSK --disable-client-cert" \
-            "$P_CLI debug_level=4 reco_mode=1 reconnect=1" \
+            "$G_NEXT_SRV -d 10 --priority=NORMAL:-VERS-ALL:+VERS-TLS1.3:+CIPHER-ALL:+PSK --disable-client-cert" \
+            "$P_CLI debug_level=1 reco_mode=1 reconnect=1" \
             0 \
             -c "Protocol is TLSv1.3" \
-            -c "MBEDTLS_SSL_NEW_SESSION_TICKET" \
             -c "got new session ticket." \
             -c "Saving session for reuse... ok" \
-            -c "HTTP/1.0 200 OK"
-
-requires_openssl_tls1_3
-requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_3
-requires_config_enabled MBEDTLS_SSL_SESSION_TICKETS
-requires_config_enabled MBEDTLS_SSL_SRV_C
-requires_config_enabled MBEDTLS_DEBUG_C
-run_test    "TLS 1.3: NewSessionTicket: Basic check, O->m" \
-            "$P_SRV debug_level=4 crt_file=data_files/server5.crt key_file=data_files/server5.key force_version=tls13 tickets=1" \
-            "$O_NEXT_CLI -msg -debug -tls1_3 -no_middlebox" \
-            0 \
-            -s "=> write NewSessionTicket msg" \
-            -s "server state: MBEDTLS_SSL_NEW_SESSION_TICKET" \
-            -s "server state: MBEDTLS_SSL_NEW_SESSION_TICKET_FLUSH"
+            -c "Reconnecting with saved session" \
+            -c "HTTP/1.0 200 OK" \
+            -s "This is a resumed session"
 
 requires_gnutls_tls1_3
 requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_3
@@ -12800,12 +12788,16 @@
 requires_config_enabled MBEDTLS_DEBUG_C
 run_test    "TLS 1.3: NewSessionTicket: Basic check, G->m" \
             "$P_SRV debug_level=4 crt_file=data_files/server5.crt key_file=data_files/server5.key force_version=tls13 tickets=1" \
-            "$G_NEXT_CLI localhost -d 4 --priority=NORMAL:-VERS-ALL:+VERS-TLS1.3:%DISABLE_TLS13_COMPAT_MODE -V" \
+            "$G_NEXT_CLI localhost -d 4 --priority=NORMAL:-VERS-ALL:+VERS-TLS1.3:%DISABLE_TLS13_COMPAT_MODE -V -r" \
             0 \
+            -c "Connecting again- trying to resume previous session" \
+            -c "NEW SESSION TICKET (4) was received" \
             -s "=> write NewSessionTicket msg" \
             -s "server state: MBEDTLS_SSL_NEW_SESSION_TICKET" \
             -s "server state: MBEDTLS_SSL_NEW_SESSION_TICKET_FLUSH" \
-            -c "NEW SESSION TICKET (4) was received"
+            -s "key exchange mode: ephemeral" \
+            -s "key exchange mode: psk_ephemeral" \
+            -s "found pre_shared_key extension"
 
 requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_3
 requires_config_enabled MBEDTLS_SSL_SESSION_TICKETS
@@ -12817,13 +12809,17 @@
             "$P_CLI debug_level=4 reco_mode=1 reconnect=1" \
             0 \
             -c "Protocol is TLSv1.3" \
-            -c "MBEDTLS_SSL_NEW_SESSION_TICKET" \
             -c "got new session ticket." \
             -c "Saving session for reuse... ok" \
+            -c "Reconnecting with saved session" \
             -c "HTTP/1.0 200 OK"    \
             -s "=> write NewSessionTicket msg" \
             -s "server state: MBEDTLS_SSL_NEW_SESSION_TICKET" \
-            -s "server state: MBEDTLS_SSL_NEW_SESSION_TICKET_FLUSH"
+            -s "server state: MBEDTLS_SSL_NEW_SESSION_TICKET_FLUSH" \
+            -s "key exchange mode: ephemeral" \
+            -s "key exchange mode: psk_ephemeral" \
+            -s "found pre_shared_key extension"
+
 
 requires_openssl_tls1_3
 requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2