Merge pull request #6180 from yuhaoth/pr/add-tls13-multiple-session-tickets

TLS 1.3: NewSessionTicket: Add support for sending multiple tickets per session.
diff --git a/include/mbedtls/mbedtls_config.h b/include/mbedtls/mbedtls_config.h
index 8359a9f..9e8ae37 100644
--- a/include/mbedtls/mbedtls_config.h
+++ b/include/mbedtls/mbedtls_config.h
@@ -1580,6 +1580,16 @@
 #define MBEDTLS_SSL_TLS1_3_TICKET_NONCE_LENGTH 32
 
 /**
+ * \def MBEDTLS_SSL_TLS1_3_DEFAULT_NEW_SESSION_TICKETS
+ *
+ * Default number of NewSessionTicket messages to be sent by a TLS 1.3 server
+ * after handshake completion. This is not used in TLS 1.2 and relevant only if
+ * the MBEDTLS_SSL_SESSION_TICKETS option is enabled.
+ *
+ */
+#define MBEDTLS_SSL_TLS1_3_DEFAULT_NEW_SESSION_TICKETS 1
+
+/**
  * \def MBEDTLS_SSL_PROTO_DTLS
  *
  * Enable support for DTLS (all available versions).
diff --git a/include/mbedtls/ssl.h b/include/mbedtls/ssl.h
index b40b4f4..eda6bc2 100644
--- a/include/mbedtls/ssl.h
+++ b/include/mbedtls/ssl.h
@@ -1323,9 +1323,17 @@
 #if defined(MBEDTLS_SSL_RENEGOTIATION)
     uint8_t MBEDTLS_PRIVATE(disable_renegotiation); /*!< disable renegotiation?     */
 #endif
-#if defined(MBEDTLS_SSL_SESSION_TICKETS)
-    uint8_t MBEDTLS_PRIVATE(session_tickets);   /*!< use session tickets?           */
+#if defined(MBEDTLS_SSL_SESSION_TICKETS) && \
+    defined(MBEDTLS_SSL_CLI_C)
+    uint8_t MBEDTLS_PRIVATE(session_tickets);   /*!< use session tickets? */
 #endif
+
+#if defined(MBEDTLS_SSL_SESSION_TICKETS) && \
+    defined(MBEDTLS_SSL_SRV_C) && \
+    defined(MBEDTLS_SSL_PROTO_TLS1_3)
+    uint16_t MBEDTLS_PRIVATE(new_session_tickets_count);   /*!< number of NewSessionTicket */
+#endif
+
 #if defined(MBEDTLS_SSL_SRV_C)
     uint8_t MBEDTLS_PRIVATE(cert_req_ca_list);  /*!< enable sending CA list in
                                                      Certificate Request messages? */
@@ -4103,7 +4111,8 @@
 void mbedtls_ssl_conf_preference_order( mbedtls_ssl_config *conf, int order );
 #endif /* MBEDTLS_SSL_SRV_C */
 
-#if defined(MBEDTLS_SSL_SESSION_TICKETS) && defined(MBEDTLS_SSL_CLI_C)
+#if defined(MBEDTLS_SSL_SESSION_TICKETS) && \
+    defined(MBEDTLS_SSL_CLI_C)
 /**
  * \brief          Enable / Disable session tickets (client only).
  *                 (Default: MBEDTLS_SSL_SESSION_TICKETS_ENABLED.)
@@ -4115,7 +4124,34 @@
  *                                         MBEDTLS_SSL_SESSION_TICKETS_DISABLED)
  */
 void mbedtls_ssl_conf_session_tickets( mbedtls_ssl_config *conf, int use_tickets );
-#endif /* MBEDTLS_SSL_SESSION_TICKETS && MBEDTLS_SSL_CLI_C */
+#endif /* MBEDTLS_SSL_SESSION_TICKETS &&
+          MBEDTLS_SSL_CLI_C */
+
+#if defined(MBEDTLS_SSL_SESSION_TICKETS) && \
+    defined(MBEDTLS_SSL_SRV_C) && \
+    defined(MBEDTLS_SSL_PROTO_TLS1_3)
+/**
+ * \brief   Number of NewSessionTicket messages for the server to send
+ *          after handshake completion.
+ *
+ * \note    The default value is
+ *          \c MBEDTLS_SSL_TLS1_3_DEFAULT_NEW_SESSION_TICKETS.
+ *
+ * \note    In case of a session resumption, this setting only partially apply.
+ *          At most one ticket is sent in that case to just renew the pool of
+ *          tickets of the client. The rationale is to avoid the number of
+ *          tickets on the server to become rapidly out of control when the
+ *          server has the same configuration for all its connection instances.
+ *
+ * \param conf    SSL configuration
+ * \param num_tickets    Number of NewSessionTicket.
+ *
+ */
+void mbedtls_ssl_conf_new_session_tickets( mbedtls_ssl_config *conf,
+                                           uint16_t num_tickets );
+#endif /* MBEDTLS_SSL_SESSION_TICKETS &&
+          MBEDTLS_SSL_SRV_C &&
+          MBEDTLS_SSL_PROTO_TLS1_3*/
 
 #if defined(MBEDTLS_SSL_RENEGOTIATION)
 /**
diff --git a/library/ssl_misc.h b/library/ssl_misc.h
index 2e35e6c..afacb76 100644
--- a/library/ssl_misc.h
+++ b/library/ssl_misc.h
@@ -624,6 +624,9 @@
 #if defined(MBEDTLS_KEY_EXCHANGE_SOME_PSK_ENABLED)
     uint8_t tls13_kex_modes; /*!< Key exchange modes supported by the client */
 #endif
+#if defined(MBEDTLS_SSL_SESSION_TICKETS)
+    uint16_t new_session_tickets_count;         /*!< number of session tickets */
+#endif
 #endif /* MBEDTLS_SSL_SRV_C */
 
 #endif /* MBEDTLS_SSL_PROTO_TLS1_3 */
diff --git a/library/ssl_tls.c b/library/ssl_tls.c
index 12e1c1b..f0615ea 100644
--- a/library/ssl_tls.c
+++ b/library/ssl_tls.c
@@ -763,6 +763,13 @@
     mbedtls_ssl_transform_init( ssl->transform_negotiate );
     ssl_handshake_params_init( ssl->handshake );
 
+#if defined(MBEDTLS_SSL_PROTO_TLS1_3) && \
+    defined(MBEDTLS_SSL_SRV_C) && \
+    defined(MBEDTLS_SSL_SESSION_TICKETS)
+    ssl->handshake->new_session_tickets_count =
+        ssl->conf->new_session_tickets_count ;
+#endif
+
 #if defined(MBEDTLS_SSL_PROTO_DTLS)
     if( ssl->conf->transport == MBEDTLS_SSL_TRANSPORT_DATAGRAM )
     {
@@ -2611,6 +2618,15 @@
 #endif
 
 #if defined(MBEDTLS_SSL_SRV_C)
+
+#if defined(MBEDTLS_SSL_PROTO_TLS1_3) && defined(MBEDTLS_SSL_SESSION_TICKETS)
+void mbedtls_ssl_conf_new_session_tickets( mbedtls_ssl_config *conf,
+                                           uint16_t num_tickets )
+{
+    conf->new_session_tickets_count = num_tickets;
+}
+#endif
+
 void mbedtls_ssl_conf_session_tickets_cb( mbedtls_ssl_config *conf,
         mbedtls_ssl_ticket_write_t *f_ticket_write,
         mbedtls_ssl_ticket_parse_t *f_ticket_parse,
@@ -4644,6 +4660,10 @@
 #endif
 
 #if defined(MBEDTLS_SSL_PROTO_TLS1_3)
+#if defined(MBEDTLS_SSL_SRV_C) && defined(MBEDTLS_SSL_SESSION_TICKETS)
+    mbedtls_ssl_conf_new_session_tickets(
+        conf, MBEDTLS_SSL_TLS1_3_DEFAULT_NEW_SESSION_TICKETS );
+#endif
     /*
      * Allow all TLS 1.3 key exchange modes by default.
      */
diff --git a/library/ssl_tls13_server.c b/library/ssl_tls13_server.c
index 94a07c8..bf281bf 100644
--- a/library/ssl_tls13_server.c
+++ b/library/ssl_tls13_server.c
@@ -2619,7 +2619,21 @@
     /* Check whether the use of session tickets is enabled */
     if( ssl->conf->f_ticket_write == NULL )
     {
-        MBEDTLS_SSL_DEBUG_MSG( 2, ( "new session ticket is not enabled" ) );
+        MBEDTLS_SSL_DEBUG_MSG( 2, ( "NewSessionTicket: disabled,"
+                                        " callback is not set" ) );
+        return( SSL_NEW_SESSION_TICKET_SKIP );
+    }
+    if( ssl->conf->new_session_tickets_count == 0 )
+    {
+        MBEDTLS_SSL_DEBUG_MSG( 2, ( "NewSessionTicket: disabled,"
+                                        " configured count is zero" ) );
+        return( SSL_NEW_SESSION_TICKET_SKIP );
+    }
+
+    if( ssl->handshake->new_session_tickets_count == 0 )
+    {
+        MBEDTLS_SSL_DEBUG_MSG( 2, ( "NewSessionTicket: all tickets have "
+                                        "been sent." ) );
         return( SSL_NEW_SESSION_TICKET_SKIP );
     }
 
@@ -2852,6 +2866,15 @@
         MBEDTLS_SSL_PROC_CHK( mbedtls_ssl_finish_handshake_msg(
                                   ssl, buf_len, msg_len ) );
 
+        /* Limit session tickets count to one when resumption connection.
+         *
+         * See document of mbedtls_ssl_conf_new_session_tickets.
+         */
+        if( ssl->handshake->resume == 1 )
+            ssl->handshake->new_session_tickets_count = 0;
+        else
+            ssl->handshake->new_session_tickets_count--;
+
         mbedtls_ssl_handshake_set_state( ssl,
                                          MBEDTLS_SSL_NEW_SESSION_TICKET_FLUSH );
     }
@@ -3002,7 +3025,11 @@
              * as part of ssl_prepare_handshake_step.
              */
             ret = 0;
-            mbedtls_ssl_handshake_set_state( ssl, MBEDTLS_SSL_HANDSHAKE_OVER );
+
+            if( ssl->handshake->new_session_tickets_count == 0 )
+                mbedtls_ssl_handshake_set_state( ssl, MBEDTLS_SSL_HANDSHAKE_OVER );
+            else
+                mbedtls_ssl_handshake_set_state( ssl, MBEDTLS_SSL_NEW_SESSION_TICKET );
             break;
 
 #endif /* MBEDTLS_SSL_SESSION_TICKETS */
diff --git a/programs/ssl/ssl_client2.c b/programs/ssl/ssl_client2.c
index 6beaa12..64baa19 100644
--- a/programs/ssl/ssl_client2.c
+++ b/programs/ssl/ssl_client2.c
@@ -1139,7 +1139,7 @@
         else if( strcmp( p, "tickets" ) == 0 )
         {
             opt.tickets = atoi( q );
-            if( opt.tickets < 0 || opt.tickets > 2 )
+            if( opt.tickets < 0 )
                 goto usage;
         }
         else if( strcmp( p, "alpn" ) == 0 )
@@ -2668,6 +2668,9 @@
      */
     if( opt.transport == MBEDTLS_SSL_TRANSPORT_STREAM )
     {
+#if defined(MBEDTLS_SSL_PROTO_TLS1_3) && defined(MBEDTLS_SSL_SESSION_TICKETS)
+        int ticket_id = 0;
+#endif
         do
         {
             len = sizeof( buf ) - 1;
@@ -2715,7 +2718,8 @@
                     case MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET:
                         /* We were waiting for application data but got
                          * a NewSessionTicket instead. */
-                        mbedtls_printf( " got new session ticket.\n" );
+                        mbedtls_printf( " got new session ticket ( %d ).\n",
+                                        ticket_id++ );
                         if( opt.reconnect != 0 )
                         {
                             mbedtls_printf("  . Saving session for reuse..." );
@@ -2749,7 +2753,6 @@
                                                 (unsigned) session_data_len );
                             }
                         }
-
                         continue;
 #endif /* MBEDTLS_SSL_SESSION_TICKETS */
 
diff --git a/programs/ssl/ssl_server2.c b/programs/ssl/ssl_server2.c
index 3113d1b..a129de6 100644
--- a/programs/ssl/ssl_server2.c
+++ b/programs/ssl/ssl_server2.c
@@ -1997,7 +1997,7 @@
         else if( strcmp( p, "tickets" ) == 0 )
         {
             opt.tickets = atoi( q );
-            if( opt.tickets < 0 || opt.tickets > 1 )
+            if( opt.tickets < 0 )
                 goto usage;
         }
         else if( strcmp( p, "ticket_rotate" ) == 0 )
@@ -2915,7 +2915,7 @@
 #endif
 
 #if defined(MBEDTLS_SSL_SESSION_TICKETS)
-    if( opt.tickets == MBEDTLS_SSL_SESSION_TICKETS_ENABLED )
+    if( opt.tickets != MBEDTLS_SSL_SESSION_TICKETS_DISABLED )
     {
         if( ( ret = mbedtls_ssl_ticket_setup( &ticket_ctx,
                         rng_get, &rng,
@@ -2930,7 +2930,9 @@
                 mbedtls_ssl_ticket_write,
                 mbedtls_ssl_ticket_parse,
                 &ticket_ctx );
-
+#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
+        mbedtls_ssl_conf_new_session_tickets( &conf, opt.tickets );
+#endif
         /* exercise manual ticket rotation (not required for typical use)
          * (used for external synchronization of session ticket encryption keys)
          */
diff --git a/tests/ssl-opt.sh b/tests/ssl-opt.sh
index 84bcd3c..f51d945 100755
--- a/tests/ssl-opt.sh
+++ b/tests/ssl-opt.sh
@@ -12781,14 +12781,32 @@
             -c "HTTP/1.0 200 OK" \
             -s "This is a resumed session"
 
+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
+requires_config_enabled MBEDTLS_SSL_TLS1_3_COMPATIBILITY_MODE
+# https://github.com/openssl/openssl/issues/10714
+# Until now, OpenSSL client does not support reconnect.
+skip_next_test
+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=4" \
+            "$O_NEXT_CLI -msg -debug -tls1_3 -reconnect" \
+            0 \
+            -s "=> write NewSessionTicket msg" \
+            -s "server state: MBEDTLS_SSL_NEW_SESSION_TICKET" \
+            -s "server state: MBEDTLS_SSL_NEW_SESSION_TICKET_FLUSH"
+
 requires_gnutls_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
+requires_config_enabled MBEDTLS_SSL_TLS1_3_COMPATIBILITY_MODE
 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 -r" \
+            "$P_SRV debug_level=4 crt_file=data_files/server5.crt key_file=data_files/server5.key force_version=tls13 tickets=4" \
+            "$G_NEXT_CLI localhost -d 4 --priority=NORMAL:-VERS-ALL:+VERS-TLS1.3 -V -r" \
             0 \
             -c "Connecting again- trying to resume previous session" \
             -c "NEW SESSION TICKET (4) was received" \
@@ -12805,11 +12823,11 @@
 requires_config_enabled MBEDTLS_SSL_CLI_C
 requires_config_enabled MBEDTLS_DEBUG_C
 run_test    "TLS 1.3: NewSessionTicket: Basic check, m->m" \
-            "$P_SRV debug_level=4 crt_file=data_files/server5.crt key_file=data_files/server5.key force_version=tls13 tickets=1" \
+            "$P_SRV debug_level=4 crt_file=data_files/server5.crt key_file=data_files/server5.key force_version=tls13 tickets=4" \
             "$P_CLI debug_level=4 reco_mode=1 reconnect=1" \
             0 \
             -c "Protocol is TLSv1.3" \
-            -c "got new session ticket." \
+            -c "got new session ticket ( 3 )" \
             -c "Saving session for reuse... ok" \
             -c "Reconnecting with saved session" \
             -c "HTTP/1.0 200 OK"    \