Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions configs/sni.yaml.default
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@
# The location of the certificate file is relative to proxy.config.ssl.server.cert.path directory.
# client_key - sets the file containing the client private key that corresponds to the certificate for the outbound connection.
# client_sni_policy - policy of SNI on outbound connection.
# ssl_ticket_enabled - enables or disables session tickets for matched inbound TLS connections; parameters = 1 or 0.
# This overrides proxy.config.ssl.server.session_ticket.enable.
# ssl_ticket_number - sets the number of TLSv1.3 session tickets issued for matched inbound TLS connections;
# parameters = INTEGER. This overrides proxy.config.ssl.server.session_ticket.number.
# BoringSSL does not support setting the ticket number on a per-SNI basis,
# so this configuration is ignored when ATS is linked against BoringSSL.
# http2 - adds or removes HTTP/2 (H2) from the protocol list advertised by ATS; parameter required = None, parameters = on or off
# tunnel_route - sets the e2e tunnel route
# forward_route - destination as an FQDN and port, separated by a colon :.
Expand Down
5 changes: 5 additions & 0 deletions doc/admin-guide/files/records.yaml.en.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4017,6 +4017,11 @@ SSL Termination
In those scenarios, increasing the number of tickets could be potentially beneficial for clients performing
multiple requests over concurrent TLS connections as per RFC 8446 clients SHOULDN'T reuse TLS Tickets.

This setting is applied at the SSL context level. BoringSSL does not support setting the
ticket number on a per-SNI basis, so the :file:`sni.yaml` :code:`ssl_ticket_number`
configuration does not apply when ATS is linked against BoringSSL and this context-level
value remains in effect.

For more information see https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_num_tickets.html

.. ts:cv:: CONFIG proxy.config.ssl.hsts_max_age INT -1
Expand Down
15 changes: 15 additions & 0 deletions doc/admin-guide/files/sni.yaml.en.rst
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,21 @@ server_group_list Inbound Specifies an override to the
`OpenSSL SSL_CTX_set_groups_list <https://docs.openssl.org/3.5/man3/SSL_CTX_set1_curves/>`_
documentation.

ssl_ticket_enabled Inbound Specifies an override to the global
:ts:cv:`proxy.config.ssl.server.session_ticket.enable`
:file:`records.yaml` configuration. Set this to :code:`1` to enable
session tickets or :code:`0` to disable them for matching inbound TLS
connections.

ssl_ticket_number Inbound Specifies an override to the global
:ts:cv:`proxy.config.ssl.server.session_ticket.number`
:file:`records.yaml` configuration. This controls how many TLSv1.3
session tickets are issued for matching inbound TLS connections.
BoringSSL does not support setting the ticket number on a
per-SNI basis, so this configuration does not apply when ATS is
linked against BoringSSL. The configured ticket count from the
selected SSL context remains in effect.

host_sni_policy Inbound One of the values :code:`DISABLED`, :code:`PERMISSIVE`, or :code:`ENFORCED`.

If not specified, the value of :ts:cv:`proxy.config.http.host_sni_policy` is used.
Expand Down
2 changes: 2 additions & 0 deletions include/iocore/net/TLSSNISupport.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ class TLSSNISupport
std::optional<int32_t> http2_max_priority_frames_per_minute;
std::optional<int32_t> http2_max_rst_stream_frames_per_minute;
std::optional<int32_t> http2_max_continuation_frames_per_minute;
std::optional<int32_t> ssl_ticket_enabled;
std::optional<int32_t> ssl_ticket_number;
std::optional<std::string_view> outbound_sni_policy;
} hints_from_sni;

Expand Down
4 changes: 4 additions & 0 deletions include/iocore/net/YamlSNIConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ TSDECL(client_sni_policy);
TSDECL(server_cipher_suite);
TSDECL(server_TLSv1_3_cipher_suites);
TSDECL(server_groups_list);
TSDECL(ssl_ticket_enabled);
TSDECL(ssl_ticket_number);
TSDECL(ip_allow);
TSDECL(valid_tls_versions_in);
TSDECL(valid_tls_version_min_in);
Expand Down Expand Up @@ -107,6 +109,8 @@ struct YamlSNIConfig {
std::string server_cipher_suite;
std::string server_TLSv1_3_cipher_suites;
std::string server_groups_list;
std::optional<int> ssl_ticket_enabled;
std::optional<int> ssl_ticket_number;
std::string ip_allow;
bool protocol_unset = true;
unsigned long protocol_mask;
Expand Down
34 changes: 34 additions & 0 deletions src/iocore/net/SNIActionPerformer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,40 @@ ServerMaxEarlyData::SNIAction([[maybe_unused]] SSL &ssl, const Context & /* ctx
return SSL_TLSEXT_ERR_OK;
}

int
ServerSessionTicketEnabled::SNIAction(SSL &ssl, const Context & /* ctx ATS_UNUSED */) const
{
#if TS_HAS_TLS_SESSION_TICKET
if (auto snis = TLSSNISupport::getInstance(&ssl)) {
const char *servername = snis->get_sni_server_name();
Dbg(dbg_ctl_ssl_sni, "Setting session ticket support from sni.yaml to %d for fqdn [%s]", session_ticket_enabled, servername);
snis->hints_from_sni.ssl_ticket_enabled = session_ticket_enabled;
}

// Apply the ticket enable/disable flag immediately so the current handshake
// sees the per-SNI override before TLS session ticket processing kicks in.
if (session_ticket_enabled != 0) {
SSL_clear_options(&ssl, SSL_OP_NO_TICKET);
} else {
SSL_set_options(&ssl, SSL_OP_NO_TICKET);
}
#endif
return SSL_TLSEXT_ERR_OK;
}

int
ServerSessionTicketNumber::SNIAction(SSL &ssl, const Context & /* ctx ATS_UNUSED */) const
{
#if TS_HAS_TLS_SESSION_TICKET
if (auto snis = TLSSNISupport::getInstance(&ssl)) {
const char *servername = snis->get_sni_server_name();
Dbg(dbg_ctl_ssl_sni, "Setting session ticket count from sni.yaml to %d for fqdn [%s]", session_ticket_number, servername);
snis->hints_from_sni.ssl_ticket_number = session_ticket_number;
}
#endif
return SSL_TLSEXT_ERR_OK;
}

int
ServerCipherSuite::SNIAction(SSL &ssl, const Context & /* ctx ATS_UNUSED */) const
{
Expand Down
30 changes: 30 additions & 0 deletions src/iocore/net/SNIActionPerformer.h
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,36 @@ class ServerMaxEarlyData : public ActionItem
#endif
};

/**
Override session ticket support by ssl_ticket_enabled in sni.yaml
*/
class ServerSessionTicketEnabled : public ActionItem
{
public:
ServerSessionTicketEnabled(int value) : session_ticket_enabled(value) {}
~ServerSessionTicketEnabled() override {}

int SNIAction(SSL &ssl, const Context &ctx) const override;

private:
int session_ticket_enabled = 0;
};

/**
Override the number of issued TLSv1.3 session tickets by ssl_ticket_number in sni.yaml
*/
class ServerSessionTicketNumber : public ActionItem
{
public:
ServerSessionTicketNumber(int value) : session_ticket_number(value) {}
~ServerSessionTicketNumber() override {}

int SNIAction(SSL &ssl, const Context &ctx) const override;

private:
int session_ticket_number = 0;
};

/**
Override proxy.config.ssl.server.cipher_suite by server_cipher_suite in sni.yaml
*/
Expand Down
106 changes: 90 additions & 16 deletions src/iocore/net/SSLUtils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ static DbgCtl dbg_ctl_ssl_session_cache{"ssl.session_cache"};
static DbgCtl dbg_ctl_ssl_error{"ssl.error"};
static DbgCtl dbg_ctl_ssl_verify{"ssl_verify"};

#if TS_HAS_TLS_SESSION_TICKET
static bool ssl_context_enable_ticket_callback(SSL_CTX *ctx);
static bool ssl_apply_sni_session_ticket_properties(SSL *ssl);
static bool ssl_set_session_ticket_number(SSL *ssl, size_t num_tickets);
#endif

/* Using pthread thread ID and mutex functions directly, instead of
* ATS this_ethread / ProxyMutex, so that other linked libraries
* may use pthreads and openssl without confusing us here. (TS-2271).
Expand Down Expand Up @@ -400,13 +406,9 @@ ssl_cert_callback(SSL *ssl, [[maybe_unused]] void *arg)
setClientCertCACerts(ssl, sslnetvc->get_ca_cert_file(), sslnetvc->get_ca_cert_dir());
}

// Reset the ticket callback if needed
SSL_CTX *ctx = SSL_get_SSL_CTX(ssl);
#ifdef HAVE_SSL_CTX_SET_TLSEXT_TICKET_KEY_EVP_CB
SSL_CTX_set_tlsext_ticket_key_evp_cb(ctx, ssl_callback_session_ticket);
#else
SSL_CTX_set_tlsext_ticket_key_cb(ctx, ssl_callback_session_ticket);
#endif
if (!ssl_apply_sni_session_ticket_properties(ssl)) {
retval = 0;
}
}
#endif

Expand Down Expand Up @@ -586,6 +588,77 @@ ssl_context_enable_dhe(const char *dhparams_file, SSL_CTX *ctx)
return ctx;
}

#if TS_HAS_TLS_SESSION_TICKET
static bool
ssl_context_enable_ticket_callback(SSL_CTX *ctx)
{
#ifdef HAVE_SSL_CTX_SET_TLSEXT_TICKET_KEY_EVP_CB
if (SSL_CTX_set_tlsext_ticket_key_evp_cb(ctx, ssl_callback_session_ticket) == 0) {
#else
if (SSL_CTX_set_tlsext_ticket_key_cb(ctx, ssl_callback_session_ticket) == 0) {
#endif
Error("failed to set session ticket callback");
return false;
}
return true;
}

static bool
ssl_set_session_ticket_number(SSL *ssl, size_t num_tickets)
{
#if defined(OPENSSL_IS_BORINGSSL)
// BoringSSL only exposes SSL_CTX_set_num_tickets(), so the per-connection
// sni.yaml override is not available here.
(void)ssl;
(void)num_tickets;
return true;
#else
return SSL_set_num_tickets(ssl, num_tickets) == 1;
#endif
}

static bool
ssl_apply_sni_session_ticket_properties(SSL *ssl)
{
auto snis = TLSSNISupport::getInstance(ssl);
if (snis == nullptr) {
return true;
}

auto const &hints = snis->hints_from_sni;
if (!hints.ssl_ticket_enabled.has_value() && !hints.ssl_ticket_number.has_value()) {
return true;
}

std::optional<size_t> num_tickets;

if (hints.ssl_ticket_enabled.has_value()) {
if (hints.ssl_ticket_enabled.value() != 0) {
SSL_clear_options(ssl, SSL_OP_NO_TICKET);
Dbg(dbg_ctl_ssl_load, "Enabled session tickets due to sni.yaml override");
} else {
SSL_set_options(ssl, SSL_OP_NO_TICKET);
num_tickets = 0;
Dbg(dbg_ctl_ssl_load, "Disabled session tickets due to sni.yaml override");
}
}

if ((!hints.ssl_ticket_enabled.has_value() || hints.ssl_ticket_enabled.value() != 0) && hints.ssl_ticket_number.has_value()) {
num_tickets = hints.ssl_ticket_number.value() > 0 ? static_cast<size_t>(hints.ssl_ticket_number.value()) : 0;
}

if (num_tickets.has_value()) {
if (!ssl_set_session_ticket_number(ssl, num_tickets.value())) {
Error("failed to set session ticket number from sni.yaml");
return false;
}
Dbg(dbg_ctl_ssl_load, "Set session ticket number from sni.yaml to %zu", num_tickets.value());
}

return true;
}
#endif

static ssl_ticket_key_block *
ssl_context_enable_tickets(SSL_CTX *ctx, const char *ticket_key_path)
{
Expand All @@ -599,15 +672,10 @@ ssl_context_enable_tickets(SSL_CTX *ctx, const char *ticket_key_path)
Metrics::Counter::increment(ssl_rsb.total_ticket_keys_renewed);
}

// Setting the callback can only fail if OpenSSL does not recognize the
// SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB constant. we set the callback first
// so that we don't leave a ticket_key pointer attached if it fails.
#ifdef HAVE_SSL_CTX_SET_TLSEXT_TICKET_KEY_EVP_CB
if (SSL_CTX_set_tlsext_ticket_key_evp_cb(ctx, ssl_callback_session_ticket) == 0) {
#else
if (SSL_CTX_set_tlsext_ticket_key_cb(ctx, ssl_callback_session_ticket) == 0) {
#endif
Error("failed to set session ticket callback");
// Setting the callback can only fail if OpenSSL does not recognize the
// SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB constant. we set the callback first
// so that we don't leave a ticket_key pointer attached if it fails.
if (!ssl_context_enable_ticket_callback(ctx)) {
ticket_block_free(keyblock);
return nullptr;
}
Expand Down Expand Up @@ -1274,6 +1342,12 @@ SSLMultiCertConfigLoader::init_server_ssl_ctx(CertLoadData const &data, const SS
}
}

#if TS_HAS_TLS_SESSION_TICKET
if (!ssl_context_enable_ticket_callback(ctx)) {
goto fail;
}
#endif

if (!this->_setup_client_cert_verification(ctx)) {
goto fail;
}
Expand Down
1 change: 1 addition & 0 deletions src/iocore/net/TLSSNISupport.cc
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ TLSSNISupport::set_sni_server_name(SSL *ssl, char const *name)
void
TLSSNISupport::_clear()
{
hints_from_sni = {};
_sni_server_name.reset();
}

Expand Down
20 changes: 20 additions & 0 deletions src/iocore/net/YamlSNIConfig.cc
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,18 @@ YamlSNIConfig::Item::populate_sni_actions(action_vector_t &actions)
if (!server_groups_list.empty()) {
actions.push_back(std::make_unique<ServerGroupsList>(server_groups_list));
}
if (ssl_ticket_enabled.has_value()) {
actions.push_back(std::make_unique<ServerSessionTicketEnabled>(ssl_ticket_enabled.value()));
}
if (ssl_ticket_number.has_value()) {
#if defined(OPENSSL_IS_BORINGSSL)
const char *servername = fqdn.empty() ? "*" : fqdn.c_str();
Warning(
"sni.yaml: BoringSSL does not support setting the session ticket number, so ssl_ticket_number does not apply for fqdn '%s'",
servername);
#endif
actions.push_back(std::make_unique<ServerSessionTicketNumber>(ssl_ticket_number.value()));
}
if (http2_buffer_water_mark.has_value()) {
actions.push_back(std::make_unique<HTTP2BufferWaterMark>(http2_buffer_water_mark.value()));
}
Expand Down Expand Up @@ -230,6 +242,8 @@ std::set<std::string> valid_sni_config_keys = {TS_fqdn,
TS_server_TLSv1_3_cipher_suites,
#endif
TS_server_groups_list,
TS_ssl_ticket_enabled,
TS_ssl_ticket_number,
TS_http2,
TS_http2_buffer_water_mark,
TS_http2_initial_window_size_in,
Expand Down Expand Up @@ -465,6 +479,12 @@ template <> struct convert<YamlSNIConfig::Item> {
if (node[TS_server_groups_list]) {
item.server_groups_list = node[TS_server_groups_list].as<std::string>();
}
if (node[TS_ssl_ticket_enabled]) {
item.ssl_ticket_enabled = node[TS_ssl_ticket_enabled].as<int>();
}
if (node[TS_ssl_ticket_number]) {
item.ssl_ticket_number = node[TS_ssl_ticket_number].as<int>();
}
if (node[TS_ip_allow]) {
item.ip_allow = node[TS_ip_allow].as<std::string>();
}
Expand Down
5 changes: 5 additions & 0 deletions src/iocore/net/unit_tests/sni_conf_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,8 @@ sni:

# test glob in the middle, this will be an exact match
- fqdn: "cat.*.com"

# test session ticket overrides
- fqdn: tickets.com
ssl_ticket_enabled: 1
ssl_ticket_number: 3
7 changes: 7 additions & 0 deletions src/iocore/net/unit_tests/test_SSLSNIConfig.cc
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,13 @@ TEST_CASE("Test SSLSNIConfig")
REQUIRE(actions.first->size() == 3);
}

SECTION("The config matches an SNI for tickets.com")
{
auto const &actions{params.get("tickets.com", 443)};
REQUIRE(actions.first);
REQUIRE(actions.first->size() == 4); ///< ticket enabled + ticket number + early data + fqdn
}

SECTION("Matching order")
{
auto const &actions{params.get("foo.bar.com", 443)};
Expand Down
11 changes: 10 additions & 1 deletion src/iocore/net/unit_tests/test_YamlSNIConfig.cc
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ TEST_CASE("YamlSNIConfig sets port ranges appropriately")
FAIL(errorstream.str());
}
REQUIRE(zret.is_ok());
REQUIRE(conf.items.size() == 10);
REQUIRE(conf.items.size() == 11);

SECTION("If no ports were specified, port range should contain all ports.")
{
Expand Down Expand Up @@ -102,6 +102,15 @@ TEST_CASE("YamlSNIConfig sets port ranges appropriately")
{
CHECK(conf.items[2].inbound_port_ranges.size() == 1);
}

SECTION("Session ticket overrides are parsed.")
{
auto const &item{conf.items[10]};
REQUIRE(item.ssl_ticket_enabled.has_value());
CHECK(item.ssl_ticket_enabled.value() == 1);
REQUIRE(item.ssl_ticket_number.has_value());
CHECK(item.ssl_ticket_number.value() == 3);
}
}

TEST_CASE("YamlConfig handles bad ports appropriately.")
Expand Down
Loading