Enable the ability to set the number of TLSv1.3 session tickets sent

We send a session ticket automatically in TLSv1.3 at the end of the
handshake. This commit provides the ability to set how many tickets should
be sent. By default this is one.

Fixes #4978

Reviewed-by: Viktor Dukhovni <viktor@openssl.org>
Reviewed-by: Rich Salz <rsalz@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/5227)
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 1f4f261..db0a2d5 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -2095,6 +2095,11 @@
 void *SSL_get_record_padding_callback_arg(SSL *ssl);
 int SSL_set_block_padding(SSL *ssl, size_t block_size);
 
+int SSL_set_num_tickets(SSL *s, size_t num_tickets);
+size_t SSL_get_num_tickets(SSL *s);
+int SSL_CTX_set_num_tickets(SSL_CTX *ctx, size_t num_tickets);
+size_t SSL_CTX_get_num_tickets(SSL_CTX *ctx);
+
 # if OPENSSL_API_COMPAT < 0x10100000L
 #  define SSL_cache_hit(s) SSL_session_reused(s)
 # endif
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c
index 41574c4..2c29d7f 100644
--- a/ssl/ssl_lib.c
+++ b/ssl/ssl_lib.c
@@ -699,6 +699,7 @@
     s->mode = ctx->mode;
     s->max_cert_list = ctx->max_cert_list;
     s->max_early_data = ctx->max_early_data;
+    s->num_tickets = ctx->num_tickets;
 
     /* Shallow copy of the ciphersuites stack */
     s->tls13_ciphersuites = sk_SSL_CIPHER_dup(ctx->tls13_ciphersuites);
@@ -3033,6 +3034,9 @@
      */
     ret->max_early_data = 0;
 
+    /* By default we send one session ticket automatically in TLSv1.3 */
+    ret->num_tickets = 1;
+
     ssl_ctx_system_config(ret);
 
     return ret;
@@ -4314,6 +4318,30 @@
     return 1;
 }
 
+int SSL_set_num_tickets(SSL *s, size_t num_tickets)
+{
+    s->num_tickets = num_tickets;
+
+    return 1;
+}
+
+size_t SSL_get_num_tickets(SSL *s)
+{
+    return s->num_tickets;
+}
+
+int SSL_CTX_set_num_tickets(SSL_CTX *ctx, size_t num_tickets)
+{
+    ctx->num_tickets = num_tickets;
+
+    return 1;
+}
+
+size_t SSL_CTX_get_num_tickets(SSL_CTX *ctx)
+{
+    return ctx->num_tickets;
+}
+
 /*
  * Allocates new EVP_MD_CTX and sets pointer to it into given pointer
  * variable, freeing EVP_MD_CTX previously stored in that variable, if any.
diff --git a/ssl/ssl_locl.h b/ssl/ssl_locl.h
index e02f5a1..4aec810 100644
--- a/ssl/ssl_locl.h
+++ b/ssl/ssl_locl.h
@@ -1049,6 +1049,9 @@
     SSL_CTX_generate_session_ticket_fn generate_ticket_cb;
     SSL_CTX_decrypt_session_ticket_fn decrypt_ticket_cb;
     void *ticket_cb_data;
+
+    /* The number of TLS1.3 tickets to automatically send */
+    size_t num_tickets;
 };
 
 struct ssl_st {
@@ -1418,6 +1421,12 @@
     size_t block_padding;
 
     CRYPTO_RWLOCK *lock;
+    RAND_DRBG *drbg;
+
+    /* The number of TLS1.3 tickets to automatically send */
+    size_t num_tickets;
+    /* The number of TLS1.3 tickets actually sent so far */
+    size_t sent_tickets;
 };
 
 /*
diff --git a/ssl/statem/statem_srvr.c b/ssl/statem/statem_srvr.c
index 22786be..dfeba17 100644
--- a/ssl/statem/statem_srvr.c
+++ b/ssl/statem/statem_srvr.c
@@ -480,13 +480,9 @@
     case TLS_ST_SR_FINISHED:
         /*
          * Technically we have finished the handshake at this point, but we're
-         * going to remain "in_init" for now and write out the session ticket
+         * going to remain "in_init" for now and write out any session tickets
          * immediately.
-         * TODO(TLS1.3): Perhaps we need to be able to control this behaviour
-         * and give the application the opportunity to delay sending the
-         * session ticket?
          */
-        st->hand_state = TLS_ST_SW_SESSION_TICKET;
         if (s->post_handshake_auth == SSL_PHA_REQUESTED) {
             s->post_handshake_auth = SSL_PHA_EXT_RECEIVED;
         } else if (!s->ext.ticket_expected) {
@@ -495,7 +491,12 @@
              * handshake at this point.
              */
             st->hand_state = TLS_ST_OK;
+            return WRITE_TRAN_CONTINUE;
         }
+        if (s->num_tickets > s->sent_tickets)
+            st->hand_state = TLS_ST_SW_SESSION_TICKET;
+        else
+            st->hand_state = TLS_ST_OK;
         return WRITE_TRAN_CONTINUE;
 
     case TLS_ST_SR_KEY_UPDATE:
@@ -507,7 +508,14 @@
 
     case TLS_ST_SW_KEY_UPDATE:
     case TLS_ST_SW_SESSION_TICKET:
-        st->hand_state = TLS_ST_OK;
+        /* In a resumption we only ever send a maximum of one new ticket.
+         * Following an initial handshake we send the number of tickets we have
+         * been configured for.
+         */
+        if (s->hit || s->num_tickets <= s->sent_tickets) {
+            /* We've written enough tickets out. */
+            st->hand_state = TLS_ST_OK;
+        }
         return WRITE_TRAN_CONTINUE;
     }
 }
@@ -3743,21 +3751,41 @@
     } age_add_u;
 
     if (SSL_IS_TLS13(s)) {
-        if (s->post_handshake_auth != SSL_PHA_EXT_RECEIVED) {
-            void (*cb) (const SSL *ssl, int type, int val) = NULL;
+        void (*cb) (const SSL *ssl, int type, int val) = NULL;
 
+        if (s->info_callback != NULL)
+            cb = s->info_callback;
+        else if (s->ctx->info_callback != NULL)
+            cb = s->ctx->info_callback;
+
+
+        if (cb != NULL) {
             /*
-             * This is the first session ticket we've sent. In the state
-             * machine we "cheated" and tacked this onto the end of the first
-             * handshake. From an info callback perspective this should appear
-             * like the start of a new handshake.
+             * We don't start and stop the handshake in between each ticket when
+             * sending more than one - but it should appear that way to the info
+             * callback.
              */
-            if (s->info_callback != NULL)
-                cb = s->info_callback;
-            else if (s->ctx->info_callback != NULL)
-                cb = s->ctx->info_callback;
-            if (cb != NULL)
-                cb(s, SSL_CB_HANDSHAKE_START, 1);
+            if (s->sent_tickets != 0) {
+                ossl_statem_set_in_init(s, 0);
+                cb(s, SSL_CB_HANDSHAKE_DONE, 1);
+                ossl_statem_set_in_init(s, 1);
+            }
+            cb(s, SSL_CB_HANDSHAKE_START, 1);
+        }
+        /*
+         * If we already sent one NewSessionTicket then we need to take a copy
+         * of it and create a new session from it.
+         */
+        if (s->sent_tickets != 0) {
+            SSL_SESSION *new_sess = ssl_session_dup(s->session, 0);
+
+            if (new_sess == NULL) {
+                /* SSLfatal already called */
+                goto err;
+            }
+
+            SSL_SESSION_free(s->session);
+            s->session = new_sess;
         }
 
         if (!ssl_generate_session_id(s, s->session)) {
@@ -3968,6 +3996,7 @@
             /* SSLfatal() already called */
             goto err;
         }
+        s->sent_tickets++;
     }
     EVP_CIPHER_CTX_free(ctx);
     HMAC_CTX_free(hctx);
diff --git a/util/libssl.num b/util/libssl.num
index 344d684..3495903 100644
--- a/util/libssl.num
+++ b/util/libssl.num
@@ -486,3 +486,7 @@
 SSL_CTX_set_stateless_cookie_verify_cb  487	1_1_1	EXIST::FUNCTION:
 SSL_CTX_set_ciphersuites                488	1_1_1	EXIST::FUNCTION:
 SSL_set_ciphersuites                    489	1_1_1	EXIST::FUNCTION:
+SSL_set_num_tickets                     490	1_1_1	EXIST::FUNCTION:
+SSL_CTX_get_num_tickets                 491	1_1_1	EXIST::FUNCTION:
+SSL_get_num_tickets                     492	1_1_1	EXIST::FUNCTION:
+SSL_CTX_set_num_tickets                 493	1_1_1	EXIST::FUNCTION: