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/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;