Implement SSL_CTX_set_num_tickets.

CPython and wpa_supplicant are using this nowadays. To avoid needing to
tweak the ticket nonce derivation, I've just internally capped the
number of tickets at 16, which should be plenty.

Change-Id: Ie84c15b81a2abe8ec729992e515e0bd4cc351037
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/52465
Reviewed-by: Adam Langley <agl@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index a3b530e..f0ca7f7 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -2281,6 +2281,13 @@
 OPENSSL_EXPORT SSL_SESSION *SSL_process_tls13_new_session_ticket(
     SSL *ssl, const uint8_t *buf, size_t buf_len);
 
+// SSL_CTX_set_num_tickets configures |ctx| to send |num_tickets| immediately
+// after a successful TLS 1.3 handshake as a server. It returns one. Large
+// values of |num_tickets| will be capped within the library.
+//
+// By default, BoringSSL sends two tickets.
+OPENSSL_EXPORT int SSL_CTX_set_num_tickets(SSL_CTX *ctx, size_t num_tickets);
+
 
 // Elliptic curve Diffie-Hellman.
 //
diff --git a/ssl/internal.h b/ssl/internal.h
index 110b221..fbf9745 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -2056,6 +2056,11 @@
   uint8_t grease_seed[ssl_grease_last_index + 1] = {0};
 };
 
+// kMaxTickets is the maximum number of tickets to send immediately after the
+// handshake. We use a one-byte ticket nonce, and there is no point in sending
+// so many tickets.
+constexpr size_t kMaxTickets = 16;
+
 UniquePtr<SSL_HANDSHAKE> ssl_handshake_new(SSL *ssl);
 
 // ssl_check_message_type checks if |msg| has type |type|. If so it returns
@@ -3416,6 +3421,11 @@
   // and is further constrainted by |SSL_OP_NO_*|.
   uint16_t conf_min_version = 0;
 
+  // num_tickets is the number of tickets to send immediately after the TLS 1.3
+  // handshake. TLS 1.3 recommends single-use tickets so, by default, issue two
+  /// in case the client makes several connections before getting a renewal.
+  uint8_t num_tickets = 2;
+
   // quic_method is the method table corresponding to the QUIC hooks.
   const SSL_QUIC_METHOD *quic_method = nullptr;
 
diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc
index e2e2436..5e96bef 100644
--- a/ssl/ssl_lib.cc
+++ b/ssl/ssl_lib.cc
@@ -140,6 +140,8 @@
 
 #include <openssl/ssl.h>
 
+#include <algorithm>
+
 #include <assert.h>
 #include <stdlib.h>
 #include <string.h>
@@ -3025,6 +3027,13 @@
   return session.release();
 }
 
+int SSL_CTX_set_num_tickets(SSL_CTX *ctx, size_t num_tickets) {
+  num_tickets = std::min(num_tickets, kMaxTickets);
+  static_assert(kMaxTickets <= 0xff, "Too many tickets.");
+  ctx->num_tickets = static_cast<uint8_t>(num_tickets);
+  return 1;
+}
+
 int SSL_set_tlsext_status_type(SSL *ssl, int type) {
   if (!ssl->config) {
     return 0;
diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc
index 567d206..e2db5a4 100644
--- a/ssl/ssl_test.cc
+++ b/ssl/ssl_test.cc
@@ -8113,5 +8113,51 @@
   }
 }
 
+TEST(SSLTest, NumTickets) {
+  bssl::UniquePtr<SSL_CTX> server_ctx(SSL_CTX_new(TLS_method()));
+  ASSERT_TRUE(server_ctx);
+  bssl::UniquePtr<SSL_CTX> client_ctx(SSL_CTX_new(TLS_method()));
+  ASSERT_TRUE(client_ctx);
+  bssl::UniquePtr<X509> cert = GetTestCertificate();
+  ASSERT_TRUE(cert);
+  bssl::UniquePtr<EVP_PKEY> key = GetTestKey();
+  ASSERT_TRUE(key);
+  ASSERT_TRUE(SSL_CTX_use_certificate(server_ctx.get(), cert.get()));
+  ASSERT_TRUE(SSL_CTX_use_PrivateKey(server_ctx.get(), key.get()));
+  SSL_CTX_set_session_cache_mode(server_ctx.get(), SSL_SESS_CACHE_BOTH);
+
+  SSL_CTX_set_session_cache_mode(client_ctx.get(), SSL_SESS_CACHE_BOTH);
+  static size_t ticket_count;
+  SSL_CTX_sess_set_new_cb(client_ctx.get(), [](SSL *, SSL_SESSION *) -> int {
+    ticket_count++;
+    return 0;
+  });
+
+  auto count_tickets = [&]() -> size_t {
+    ticket_count = 0;
+    bssl::UniquePtr<SSL> client, server;
+    if (!ConnectClientAndServer(&client, &server, client_ctx.get(),
+                                server_ctx.get()) ||
+        !FlushNewSessionTickets(client.get(), server.get())) {
+      ADD_FAILURE() << "Could not run handshake";
+      return 0;
+    }
+    return ticket_count;
+  };
+
+  // By default, we should send two tickets.
+  EXPECT_EQ(count_tickets(), 2u);
+
+  for (size_t num_tickets : {0, 1, 2, 3, 4, 5}) {
+    SCOPED_TRACE(num_tickets);
+    ASSERT_TRUE(SSL_CTX_set_num_tickets(server_ctx.get(), num_tickets));
+    EXPECT_EQ(count_tickets(), num_tickets);
+  }
+
+  // Configuring too many tickets causes us to stop at some point.
+  ASSERT_TRUE(SSL_CTX_set_num_tickets(server_ctx.get(), 100000));
+  EXPECT_EQ(count_tickets(), 16u);
+}
+
 }  // namespace
 BSSL_NAMESPACE_END
diff --git a/ssl/tls13_server.cc b/ssl/tls13_server.cc
index dbf239d..9a65e0f 100644
--- a/ssl/tls13_server.cc
+++ b/ssl/tls13_server.cc
@@ -131,15 +131,12 @@
     return true;
   }
 
-  // TLS 1.3 recommends single-use tickets, so issue multiple tickets in case
-  // the client makes several connections before getting a renewal.
-  static const int kNumTickets = 2;
-
   // Rebase the session timestamp so that it is measured from ticket
   // issuance.
   ssl_session_rebase_time(ssl, hs->new_session.get());
 
-  for (int i = 0; i < kNumTickets; i++) {
+  assert(ssl->session_ctx->num_tickets <= kMaxTickets);
+  for (size_t i = 0; i < ssl->session_ctx->num_tickets; i++) {
     UniquePtr<SSL_SESSION> session(
         SSL_SESSION_dup(hs->new_session.get(), SSL_SESSION_INCLUDE_NONAUTH));
     if (!session) {
@@ -160,7 +157,8 @@
           ssl->quic_method != nullptr ? 0xffffffff : kMaxEarlyDataAccepted;
     }
 
-    static_assert(kNumTickets < 256, "Too many tickets");
+    static_assert(kMaxTickets < 256, "Too many tickets");
+    assert(i < 256);
     uint8_t nonce[] = {static_cast<uint8_t>(i)};
 
     ScopedCBB cbb;