Skip to content

Commit 8d75849

Browse files
authored
Add sni.yaml session ticket overrides (#13006)
Add ssl_ticket_enabled and ssl_ticket_number as sni.yaml overrides, apply them during SNI handling so they affect TLS 1.2 resumption and TLS 1.3 ticket issuance, and add unit and AuTest coverage plus docs and sample config updates. Fixes #12953
1 parent 2021eda commit 8d75849

14 files changed

Lines changed: 489 additions & 16 deletions

File tree

configs/sni.yaml.default

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@
2828
# The location of the certificate file is relative to proxy.config.ssl.server.cert.path directory.
2929
# client_key - sets the file containing the client private key that corresponds to the certificate for the outbound connection.
3030
# client_sni_policy - policy of SNI on outbound connection.
31+
# ssl_ticket_enabled - enables or disables session tickets for matched inbound TLS connections; parameters = 1 or 0.
32+
# This overrides proxy.config.ssl.server.session_ticket.enable.
33+
# ssl_ticket_number - sets the number of TLSv1.3 session tickets issued for matched inbound TLS connections;
34+
# parameters = INTEGER. This overrides proxy.config.ssl.server.session_ticket.number.
35+
# BoringSSL does not support setting the ticket number on a per-SNI basis,
36+
# so this configuration is ignored when ATS is linked against BoringSSL.
3137
# http2 - adds or removes HTTP/2 (H2) from the protocol list advertised by ATS; parameter required = None, parameters = on or off
3238
# tunnel_route - sets the e2e tunnel route
3339
# forward_route - destination as an FQDN and port, separated by a colon :.

doc/admin-guide/files/records.yaml.en.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4190,6 +4190,11 @@ SSL Termination
41904190
Increasing the number of tickets could be potentially beneficial for clients performing
41914191
multiple requests over concurrent TLS connections as per RFC 8446 clients SHOULDN'T reuse TLS Tickets.
41924192

4193+
This setting is applied at the SSL context level. BoringSSL does not support setting the
4194+
ticket number on a per-SNI basis, so the :file:`sni.yaml` :code:`ssl_ticket_number`
4195+
configuration does not apply when ATS is linked against BoringSSL and this context-level
4196+
value remains in effect.
4197+
41934198
For more information see https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_num_tickets.html
41944199

41954200
.. ts:cv:: CONFIG proxy.config.ssl.hsts_max_age INT -1

doc/admin-guide/files/sni.yaml.en.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,21 @@ server_groups_list Inbound Specifies an override to the
172172
`OpenSSL SSL_CTX_set_groups_list <https://docs.openssl.org/3.5/man3/SSL_CTX_set1_curves/>`_
173173
documentation.
174174

175+
ssl_ticket_enabled Inbound Specifies an override to the global
176+
:ts:cv:`proxy.config.ssl.server.session_ticket.enable`
177+
:file:`records.yaml` configuration. Set this to :code:`1` to enable
178+
session tickets or :code:`0` to disable them for matching inbound TLS
179+
connections.
180+
181+
ssl_ticket_number Inbound Specifies an override to the global
182+
:ts:cv:`proxy.config.ssl.server.session_ticket.number`
183+
:file:`records.yaml` configuration. This controls how many TLSv1.3
184+
session tickets are issued for matching inbound TLS connections.
185+
BoringSSL does not support setting the ticket number on a
186+
per-SNI basis, so this configuration does not apply when ATS is
187+
linked against BoringSSL. The configured ticket count from the
188+
selected SSL context remains in effect.
189+
175190
host_sni_policy Inbound One of the values :code:`DISABLED`, :code:`PERMISSIVE`, or :code:`ENFORCED`.
176191

177192
If not specified, the value of :ts:cv:`proxy.config.http.host_sni_policy` is used.

include/iocore/net/TLSSNISupport.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ class TLSSNISupport
140140
std::optional<int32_t> http2_max_priority_frames_per_minute;
141141
std::optional<int32_t> http2_max_rst_stream_frames_per_minute;
142142
std::optional<int32_t> http2_max_continuation_frames_per_minute;
143+
std::optional<int32_t> ssl_ticket_enabled;
144+
std::optional<int32_t> ssl_ticket_number;
143145
std::optional<std::string_view> outbound_sni_policy;
144146
} hints_from_sni;
145147

include/iocore/net/YamlSNIConfig.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ TSDECL(client_sni_policy);
5959
TSDECL(server_cipher_suite);
6060
TSDECL(server_TLSv1_3_cipher_suites);
6161
TSDECL(server_groups_list);
62+
TSDECL(ssl_ticket_enabled);
63+
TSDECL(ssl_ticket_number);
6264
TSDECL(ip_allow);
6365
TSDECL(valid_tls_versions_in);
6466
TSDECL(valid_tls_version_min_in);
@@ -107,6 +109,8 @@ struct YamlSNIConfig {
107109
std::string server_cipher_suite;
108110
std::string server_TLSv1_3_cipher_suites;
109111
std::string server_groups_list;
112+
std::optional<int> ssl_ticket_enabled;
113+
std::optional<int> ssl_ticket_number;
110114
std::string ip_allow;
111115
bool protocol_unset = true;
112116
unsigned long protocol_mask;

src/iocore/net/SNIActionPerformer.cc

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,40 @@ ServerMaxEarlyData::SNIAction([[maybe_unused]] SSL &ssl, const Context & /* ctx
475475
return SSL_TLSEXT_ERR_OK;
476476
}
477477

478+
int
479+
ServerSessionTicketEnabled::SNIAction(SSL &ssl, const Context & /* ctx ATS_UNUSED */) const
480+
{
481+
#if TS_HAS_TLS_SESSION_TICKET
482+
if (auto snis = TLSSNISupport::getInstance(&ssl)) {
483+
const char *servername = snis->get_sni_server_name();
484+
Dbg(dbg_ctl_ssl_sni, "Setting session ticket support from sni.yaml to %d for fqdn [%s]", session_ticket_enabled, servername);
485+
snis->hints_from_sni.ssl_ticket_enabled = session_ticket_enabled;
486+
}
487+
488+
// Apply the ticket enable/disable flag immediately so the current handshake
489+
// sees the per-SNI override before TLS session ticket processing kicks in.
490+
if (session_ticket_enabled != 0) {
491+
SSL_clear_options(&ssl, SSL_OP_NO_TICKET);
492+
} else {
493+
SSL_set_options(&ssl, SSL_OP_NO_TICKET);
494+
}
495+
#endif
496+
return SSL_TLSEXT_ERR_OK;
497+
}
498+
499+
int
500+
ServerSessionTicketNumber::SNIAction(SSL &ssl, const Context & /* ctx ATS_UNUSED */) const
501+
{
502+
#if TS_HAS_TLS_SESSION_TICKET
503+
if (auto snis = TLSSNISupport::getInstance(&ssl)) {
504+
const char *servername = snis->get_sni_server_name();
505+
Dbg(dbg_ctl_ssl_sni, "Setting session ticket count from sni.yaml to %d for fqdn [%s]", session_ticket_number, servername);
506+
snis->hints_from_sni.ssl_ticket_number = session_ticket_number;
507+
}
508+
#endif
509+
return SSL_TLSEXT_ERR_OK;
510+
}
511+
478512
int
479513
ServerCipherSuite::SNIAction(SSL &ssl, const Context & /* ctx ATS_UNUSED */) const
480514
{

src/iocore/net/SNIActionPerformer.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,36 @@ class ServerMaxEarlyData : public ActionItem
313313
#endif
314314
};
315315

316+
/**
317+
Override session ticket support by ssl_ticket_enabled in sni.yaml
318+
*/
319+
class ServerSessionTicketEnabled : public ActionItem
320+
{
321+
public:
322+
ServerSessionTicketEnabled(int value) : session_ticket_enabled(value) {}
323+
~ServerSessionTicketEnabled() override {}
324+
325+
int SNIAction(SSL &ssl, const Context &ctx) const override;
326+
327+
private:
328+
int session_ticket_enabled = 0;
329+
};
330+
331+
/**
332+
Override the number of issued TLSv1.3 session tickets by ssl_ticket_number in sni.yaml
333+
*/
334+
class ServerSessionTicketNumber : public ActionItem
335+
{
336+
public:
337+
ServerSessionTicketNumber(int value) : session_ticket_number(value) {}
338+
~ServerSessionTicketNumber() override {}
339+
340+
int SNIAction(SSL &ssl, const Context &ctx) const override;
341+
342+
private:
343+
int session_ticket_number = 0;
344+
};
345+
316346
/**
317347
Override proxy.config.ssl.server.cipher_suite by server_cipher_suite in sni.yaml
318348
*/

src/iocore/net/SSLUtils.cc

Lines changed: 86 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,12 @@ static DbgCtl dbg_ctl_ssl_session_cache{"ssl.session_cache"};
9797
static DbgCtl dbg_ctl_ssl_error{"ssl.error"};
9898
static DbgCtl dbg_ctl_ssl_verify{"ssl_verify"};
9999

100+
#if TS_HAS_TLS_SESSION_TICKET
101+
static bool ssl_context_enable_ticket_callback(SSL_CTX *ctx);
102+
static bool ssl_apply_sni_session_ticket_properties(SSL *ssl);
103+
static bool ssl_set_session_ticket_number(SSL *ssl, size_t num_tickets);
104+
#endif
105+
100106
/* Using pthread thread ID and mutex functions directly, instead of
101107
* ATS this_ethread / ProxyMutex, so that other linked libraries
102108
* may use pthreads and openssl without confusing us here. (TS-2271).
@@ -304,15 +310,8 @@ ssl_cert_callback(SSL *ssl, [[maybe_unused]] void *arg)
304310
setClientCertCACerts(ssl, sslnetvc->get_ca_cert_file(), sslnetvc->get_ca_cert_dir());
305311
}
306312

307-
// Reset the ticket callback if needed
308-
SSL_CTX *ctx = SSL_get_SSL_CTX(ssl);
309-
shared_SSLMultiCertConfigParams sslMultiCertSettings = std::make_shared<SSLMultiCertConfigParams>();
310-
if (sslMultiCertSettings->session_ticket_enabled != 0) {
311-
#ifdef HAVE_SSL_CTX_SET_TLSEXT_TICKET_KEY_EVP_CB
312-
SSL_CTX_set_tlsext_ticket_key_evp_cb(ctx, ssl_callback_session_ticket);
313-
#else
314-
SSL_CTX_set_tlsext_ticket_key_cb(ctx, ssl_callback_session_ticket);
315-
#endif
313+
if (!ssl_apply_sni_session_ticket_properties(ssl)) {
314+
retval = 0;
316315
}
317316
}
318317
#endif
@@ -493,6 +492,77 @@ ssl_context_enable_dhe(const char *dhparams_file, SSL_CTX *ctx)
493492
return ctx;
494493
}
495494

495+
#if TS_HAS_TLS_SESSION_TICKET
496+
static bool
497+
ssl_context_enable_ticket_callback(SSL_CTX *ctx)
498+
{
499+
#ifdef HAVE_SSL_CTX_SET_TLSEXT_TICKET_KEY_EVP_CB
500+
if (SSL_CTX_set_tlsext_ticket_key_evp_cb(ctx, ssl_callback_session_ticket) == 0) {
501+
#else
502+
if (SSL_CTX_set_tlsext_ticket_key_cb(ctx, ssl_callback_session_ticket) == 0) {
503+
#endif
504+
Error("failed to set session ticket callback");
505+
return false;
506+
}
507+
return true;
508+
}
509+
510+
static bool
511+
ssl_set_session_ticket_number(SSL *ssl, size_t num_tickets)
512+
{
513+
#if defined(OPENSSL_IS_BORINGSSL)
514+
// BoringSSL only exposes SSL_CTX_set_num_tickets(), so the per-connection
515+
// sni.yaml override is not available here.
516+
(void)ssl;
517+
(void)num_tickets;
518+
return true;
519+
#else
520+
return SSL_set_num_tickets(ssl, num_tickets) == 1;
521+
#endif
522+
}
523+
524+
static bool
525+
ssl_apply_sni_session_ticket_properties(SSL *ssl)
526+
{
527+
auto snis = TLSSNISupport::getInstance(ssl);
528+
if (snis == nullptr) {
529+
return true;
530+
}
531+
532+
auto const &hints = snis->hints_from_sni;
533+
if (!hints.ssl_ticket_enabled.has_value() && !hints.ssl_ticket_number.has_value()) {
534+
return true;
535+
}
536+
537+
std::optional<size_t> num_tickets;
538+
539+
if (hints.ssl_ticket_enabled.has_value()) {
540+
if (hints.ssl_ticket_enabled.value() != 0) {
541+
SSL_clear_options(ssl, SSL_OP_NO_TICKET);
542+
Dbg(dbg_ctl_ssl_load, "Enabled session tickets due to sni.yaml override");
543+
} else {
544+
SSL_set_options(ssl, SSL_OP_NO_TICKET);
545+
num_tickets = 0;
546+
Dbg(dbg_ctl_ssl_load, "Disabled session tickets due to sni.yaml override");
547+
}
548+
}
549+
550+
if ((!hints.ssl_ticket_enabled.has_value() || hints.ssl_ticket_enabled.value() != 0) && hints.ssl_ticket_number.has_value()) {
551+
num_tickets = hints.ssl_ticket_number.value() > 0 ? static_cast<size_t>(hints.ssl_ticket_number.value()) : 0;
552+
}
553+
554+
if (num_tickets.has_value()) {
555+
if (!ssl_set_session_ticket_number(ssl, num_tickets.value())) {
556+
Error("failed to set session ticket number from sni.yaml");
557+
return false;
558+
}
559+
Dbg(dbg_ctl_ssl_load, "Set session ticket number from sni.yaml to %zu", num_tickets.value());
560+
}
561+
562+
return true;
563+
}
564+
#endif
565+
496566
static ssl_ticket_key_block *
497567
ssl_context_enable_tickets(SSL_CTX *ctx, const char *ticket_key_path)
498568
{
@@ -509,12 +579,7 @@ ssl_context_enable_tickets(SSL_CTX *ctx, const char *ticket_key_path)
509579
// Setting the callback can only fail if OpenSSL does not recognize the
510580
// SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB constant. we set the callback first
511581
// so that we don't leave a ticket_key pointer attached if it fails.
512-
#ifdef HAVE_SSL_CTX_SET_TLSEXT_TICKET_KEY_EVP_CB
513-
if (SSL_CTX_set_tlsext_ticket_key_evp_cb(ctx, ssl_callback_session_ticket) == 0) {
514-
#else
515-
if (SSL_CTX_set_tlsext_ticket_key_cb(ctx, ssl_callback_session_ticket) == 0) {
516-
#endif
517-
Error("failed to set session ticket callback");
582+
if (!ssl_context_enable_ticket_callback(ctx)) {
518583
ticket_block_free(keyblock);
519584
return nullptr;
520585
}
@@ -1179,6 +1244,12 @@ SSLMultiCertConfigLoader::init_server_ssl_ctx(CertLoadData const &data, const SS
11791244
}
11801245
}
11811246

1247+
#if TS_HAS_TLS_SESSION_TICKET
1248+
if (!ssl_context_enable_ticket_callback(ctx)) {
1249+
goto fail;
1250+
}
1251+
#endif
1252+
11821253
if (!this->_setup_client_cert_verification(ctx)) {
11831254
goto fail;
11841255
}

src/iocore/net/TLSSNISupport.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ TLSSNISupport::set_sni_server_name(SSL *ssl, char const *name)
165165
void
166166
TLSSNISupport::_clear()
167167
{
168+
hints_from_sni = {};
168169
_sni_server_name.reset();
169170
}
170171

src/iocore/net/YamlSNIConfig.cc

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,18 @@ YamlSNIConfig::Item::populate_sni_actions(action_vector_t &actions)
164164
if (!server_groups_list.empty()) {
165165
actions.push_back(std::make_unique<ServerGroupsList>(server_groups_list));
166166
}
167+
if (ssl_ticket_enabled.has_value()) {
168+
actions.push_back(std::make_unique<ServerSessionTicketEnabled>(ssl_ticket_enabled.value()));
169+
}
170+
if (ssl_ticket_number.has_value()) {
171+
#if defined(OPENSSL_IS_BORINGSSL)
172+
const char *servername = fqdn.empty() ? "*" : fqdn.c_str();
173+
Warning(
174+
"sni.yaml: BoringSSL does not support setting the session ticket number, so ssl_ticket_number does not apply for fqdn '%s'",
175+
servername);
176+
#endif
177+
actions.push_back(std::make_unique<ServerSessionTicketNumber>(ssl_ticket_number.value()));
178+
}
167179
if (http2_buffer_water_mark.has_value()) {
168180
actions.push_back(std::make_unique<HTTP2BufferWaterMark>(http2_buffer_water_mark.value()));
169181
}
@@ -230,6 +242,8 @@ std::set<std::string> valid_sni_config_keys = {TS_fqdn,
230242
TS_server_TLSv1_3_cipher_suites,
231243
#endif
232244
TS_server_groups_list,
245+
TS_ssl_ticket_enabled,
246+
TS_ssl_ticket_number,
233247
TS_http2,
234248
TS_http2_buffer_water_mark,
235249
TS_http2_initial_window_size_in,
@@ -465,6 +479,12 @@ template <> struct convert<YamlSNIConfig::Item> {
465479
if (node[TS_server_groups_list]) {
466480
item.server_groups_list = node[TS_server_groups_list].as<std::string>();
467481
}
482+
if (node[TS_ssl_ticket_enabled]) {
483+
item.ssl_ticket_enabled = node[TS_ssl_ticket_enabled].as<int>();
484+
}
485+
if (node[TS_ssl_ticket_number]) {
486+
item.ssl_ticket_number = node[TS_ssl_ticket_number].as<int>();
487+
}
468488
if (node[TS_ip_allow]) {
469489
item.ip_allow = node[TS_ip_allow].as<std::string>();
470490
}

0 commit comments

Comments
 (0)