You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by zw...@apache.org on 2021/02/03 23:45:59 UTC
[trafficserver] 04/20: Generalize SNI support (#6870)
This is an automated email from the ASF dual-hosted git repository.
zwoop pushed a commit to branch 9.1.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
commit 3ba9fbdb97db59b9266048743c8673baeaf2a718
Author: Masakazu Kitajo <ma...@apache.org>
AuthorDate: Fri Jan 29 09:08:19 2021 +0900
Generalize SNI support (#6870)
* Introduce SNISupport to generalize callback function
This makes callback functions for SNI usable on QUIC connections.
* Move code for SNI actions from the callback functions to SNIActionPerformers
---
iocore/net/Makefile.am | 1 +
iocore/net/P_SNIActionPerformer.h | 31 ++++----
iocore/net/P_SSLNetVConnection.h | 17 +++--
iocore/net/SSLNetVConnection.cc | 13 ++--
iocore/net/SSLUtils.cc | 121 +++++++++----------------------
iocore/net/TLSSNISupport.cc | 145 ++++++++++++++++++++++++++++++++++++++
iocore/net/TLSSNISupport.h | 61 ++++++++++++++++
7 files changed, 273 insertions(+), 116 deletions(-)
diff --git a/iocore/net/Makefile.am b/iocore/net/Makefile.am
index 0209c3a..34d9fff 100644
--- a/iocore/net/Makefile.am
+++ b/iocore/net/Makefile.am
@@ -185,6 +185,7 @@ libinknet_a_SOURCES = \
SSLUtils.cc \
OCSPStapling.cc \
TLSSessionResumptionSupport.cc \
+ TLSSNISupport.cc \
UDPIOEvent.cc \
UnixConnection.cc \
UnixNet.cc \
diff --git a/iocore/net/P_SNIActionPerformer.h b/iocore/net/P_SNIActionPerformer.h
index cd0cd6e..b255969 100644
--- a/iocore/net/P_SNIActionPerformer.h
+++ b/iocore/net/P_SNIActionPerformer.h
@@ -49,7 +49,7 @@ public:
std::optional<std::vector<std::string>> _fqdn_wildcard_captured_groups;
};
- virtual int SNIAction(Continuation *cont, const Context &ctx) const = 0;
+ virtual int SNIAction(TLSSNISupport *snis, const Context &ctx) const = 0;
/**
This method tests whether this action would have been triggered by a
@@ -72,9 +72,9 @@ public:
~ControlH2() override {}
int
- SNIAction(Continuation *cont, const Context &ctx) const override
+ SNIAction(TLSSNISupport *snis, const Context &ctx) const override
{
- auto ssl_vc = dynamic_cast<SSLNetVConnection *>(cont);
+ auto ssl_vc = dynamic_cast<SSLNetVConnection *>(snis);
if (ssl_vc) {
if (!enable_h2) {
ssl_vc->disableProtocol(TS_ALPN_PROTOCOL_INDEX_HTTP_2_0);
@@ -100,10 +100,10 @@ public:
~TunnelDestination() override {}
int
- SNIAction(Continuation *cont, const Context &ctx) const override
+ SNIAction(TLSSNISupport *snis, const Context &ctx) const override
{
// Set the netvc option?
- SSLNetVConnection *ssl_netvc = dynamic_cast<SSLNetVConnection *>(cont);
+ SSLNetVConnection *ssl_netvc = dynamic_cast<SSLNetVConnection *>(snis);
if (ssl_netvc) {
// If needed, we will try to amend the tunnel destination.
if (ctx._fqdn_wildcard_captured_groups && need_fix) {
@@ -113,6 +113,9 @@ public:
} else {
ssl_netvc->set_tunnel_destination(destination, tunnel_decrypt, tls_upstream);
}
+ if (ssl_netvc->has_tunnel_destination() && !ssl_netvc->decrypt_tunnel()) {
+ ssl_netvc->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL;
+ }
}
return SSL_TLSEXT_ERR_OK;
}
@@ -201,9 +204,9 @@ public:
VerifyClient(const char *param, std::string_view file, std::string_view dir) : VerifyClient(atoi(param), file, dir) {}
~VerifyClient() override;
int
- SNIAction(Continuation *cont, const Context &ctx) const override
+ SNIAction(TLSSNISupport *snis, const Context &ctx) const override
{
- auto ssl_vc = dynamic_cast<SSLNetVConnection *>(cont);
+ auto ssl_vc = dynamic_cast<SSLNetVConnection *>(snis);
Debug("ssl_sni", "action verify param %d", this->mode);
setClientCertLevel(ssl_vc->ssl, this->mode);
ssl_vc->set_ca_cert_file(ca_file, ca_dir);
@@ -232,7 +235,7 @@ public:
HostSniPolicy(uint8_t param) : policy(param) {}
~HostSniPolicy() override {}
int
- SNIAction(Continuation *cont, const Context &ctx) const override
+ SNIAction(TLSSNISupport *snis, const Context &ctx) const override
{
// On action this doesn't do anything
return SSL_TLSEXT_ERR_OK;
@@ -261,14 +264,14 @@ public:
TLSValidProtocols() : protocol_mask(max_mask) {}
TLSValidProtocols(unsigned long protocols) : unset(false), protocol_mask(protocols) {}
int
- SNIAction(Continuation *cont, const Context & /* ctx */) const override
+ SNIAction(TLSSNISupport *snis, const Context & /* ctx */) const override
{
if (!unset) {
- auto ssl_vc = dynamic_cast<SSLNetVConnection *>(cont);
+ auto ssl_vc = dynamic_cast<SSLNetVConnection *>(snis);
Debug("ssl_sni", "TLSValidProtocol param 0%x", static_cast<unsigned int>(this->protocol_mask));
- ssl_vc->protocol_mask_set = true;
- ssl_vc->protocol_mask = protocol_mask;
+ ssl_vc->set_valid_tls_protocols(protocol_mask, TLSValidProtocols::max_mask);
}
+
return SSL_TLSEXT_ERR_OK;
}
};
@@ -301,14 +304,14 @@ public:
} // end function SNI_IpAllow
int
- SNIAction(Continuation *cont, const Context &ctx) const override
+ SNIAction(TLSSNISupport *snis, const Context &ctx) const override
{
// i.e, ip filtering is not required
if (ip_map.count() == 0) {
return SSL_TLSEXT_ERR_OK;
}
- auto ssl_vc = dynamic_cast<SSLNetVConnection *>(cont);
+ auto ssl_vc = dynamic_cast<SSLNetVConnection *>(snis);
auto ip = ssl_vc->get_remote_endpoint();
// check the allowed ips
diff --git a/iocore/net/P_SSLNetVConnection.h b/iocore/net/P_SSLNetVConnection.h
index da47678..3a85970 100644
--- a/iocore/net/P_SSLNetVConnection.h
+++ b/iocore/net/P_SSLNetVConnection.h
@@ -48,6 +48,7 @@
#include "P_UnixNet.h"
#include "P_ALPNSupport.h"
#include "TLSSessionResumptionSupport.h"
+#include "TLSSNISupport.h"
#include "P_SSLUtils.h"
#include "P_SSLConfig.h"
@@ -93,7 +94,7 @@ enum SSLHandshakeStatus { SSL_HANDSHAKE_ONGOING, SSL_HANDSHAKE_DONE, SSL_HANDSHA
// A VConnection for a network socket.
//
//////////////////////////////////////////////////////////////////
-class SSLNetVConnection : public UnixNetVConnection, public ALPNSupport, public TLSSessionResumptionSupport
+class SSLNetVConnection : public UnixNetVConnection, public ALPNSupport, public TLSSessionResumptionSupport, public TLSSNISupport
{
typedef UnixNetVConnection super; ///< Parent type.
@@ -384,11 +385,9 @@ public:
const char *
get_server_name() const override
{
- return _serverName.get() ? _serverName.get() : "";
+ return _get_sni_server_name() ? _get_sni_server_name() : "";
}
- void set_server_name(std::string_view name);
-
bool
support_sni() const override
{
@@ -473,6 +472,13 @@ public:
return _ca_cert_dir.get();
}
+ void
+ set_valid_tls_protocols(unsigned long proto_mask, unsigned long max_mask)
+ {
+ SSL_set_options(this->ssl, proto_mask);
+ SSL_clear_options(this->ssl, max_mask & ~proto_mask);
+ }
+
protected:
const IpEndpoint &
_getLocalEndpoint() override
@@ -480,6 +486,8 @@ protected:
return local_addr;
}
+ void _fire_ssl_servername_event() override;
+
private:
std::string_view map_tls_protocol_to_tag(const char *proto_string) const;
bool update_rbio(bool move_to_socket);
@@ -526,7 +534,6 @@ private:
X509_STORE_CTX *verify_cert = nullptr;
// Null-terminated string, or nullptr if there is no SNI server name.
- std::unique_ptr<char[]> _serverName;
std::unique_ptr<char[]> _ca_cert_file;
std::unique_ptr<char[]> _ca_cert_dir;
diff --git a/iocore/net/SSLNetVConnection.cc b/iocore/net/SSLNetVConnection.cc
index 1f525df..f4eaaa8 100644
--- a/iocore/net/SSLNetVConnection.cc
+++ b/iocore/net/SSLNetVConnection.cc
@@ -215,6 +215,7 @@ SSLNetVConnection::_bindSSLObject()
{
SSLNetVCAttach(this->ssl, this);
TLSSessionResumptionSupport::bind(this->ssl, this);
+ TLSSNISupport::bind(this->ssl, this);
}
void
@@ -222,6 +223,7 @@ SSLNetVConnection::_unbindSSLObject()
{
SSLNetVCDetach(this->ssl);
TLSSessionResumptionSupport::unbind(this->ssl);
+ TLSSNISupport::unbind(this->ssl);
}
static void
@@ -928,7 +930,6 @@ SSLNetVConnection::do_io_close(int lerrno)
void
SSLNetVConnection::clear()
{
- _serverName.reset();
_ca_cert_file.reset();
_ca_cert_dir.reset();
@@ -938,6 +939,7 @@ SSLNetVConnection::clear()
}
ALPNSupport::clear();
TLSSessionResumptionSupport::clear();
+ TLSSNISupport::_clear();
sslHandshakeStatus = SSL_HANDSHAKE_ONGOING;
sslHandshakeBeginTime = 0;
@@ -1918,14 +1920,9 @@ SSLNetVConnection::protocol_contains(std::string_view prefix) const
}
void
-SSLNetVConnection::set_server_name(std::string_view name)
+SSLNetVConnection::_fire_ssl_servername_event()
{
- if (name.size()) {
- char *n = new char[name.size() + 1];
- std::memcpy(n, name.data(), name.size());
- n[name.size()] = '\0';
- _serverName.reset(n);
- }
+ this->callHooks(TS_EVENT_SSL_SERVERNAME);
}
void
diff --git a/iocore/net/SSLUtils.cc b/iocore/net/SSLUtils.cc
index 54a9d35..d34d6b8 100644
--- a/iocore/net/SSLUtils.cc
+++ b/iocore/net/SSLUtils.cc
@@ -347,76 +347,31 @@ ssl_verify_client_callback(int preverify_ok, X509_STORE_CTX *ctx)
return preverify_ok;
}
-static int
-PerformAction(Continuation *cont, const char *servername)
-{
- SNIConfig::scoped_config params;
- if (const auto &actions = params->get(servername); !actions.first) {
- Debug("ssl_sni", "%s not available in the map", servername);
- } else {
- for (auto &&item : *actions.first) {
- auto ret = item->SNIAction(cont, actions.second);
- if (ret != SSL_TLSEXT_ERR_OK) {
- return ret;
- }
- }
- }
- return SSL_TLSEXT_ERR_OK;
-}
-
#if TS_USE_HELLO_CB
// Pausable callback
static int
ssl_client_hello_callback(SSL *s, int *al, void *arg)
{
- SSLNetVConnection *netvc = SSLNetVCAccess(s);
- const char *servername = nullptr;
- const unsigned char *p;
- size_t remaining, len;
-
- if (!netvc || netvc->ssl != s) {
- Debug("ssl.error", "ssl_client_hello_callback call back on stale netvc");
+ TLSSNISupport *snis = TLSSNISupport::getInstance(s);
+ if (snis) {
+ snis->on_client_hello(s, al, arg);
+ int ret = snis->perform_sni_action();
+ if (ret != SSL_TLSEXT_ERR_OK) {
+ return SSL_CLIENT_HELLO_ERROR;
+ }
+ } else {
+ // This error suggests either of these:
+ // 1) Call back on unsupported netvc -- Don't register callback unnecessarily
+ // 2) Call back on stale netvc
+ Debug("ssl.error", "ssl_client_hello_callback was called unexpectedly");
return SSL_CLIENT_HELLO_ERROR;
}
- // Parse the server name if the get extension call succeeds and there are more than 2 bytes to parse
- if (SSL_client_hello_get0_ext(s, TLSEXT_TYPE_server_name, &p, &remaining) && remaining > 2) {
- // Parse to get to the name, originally from test/handshake_helper.c in openssl tree
- /* Extract the length of the supplied list of names. */
- len = *(p++) << 8;
- len += *(p++);
- if (len + 2 == remaining) {
- remaining = len;
- /*
- * The list in practice only has a single element, so we only consider
- * the first one.
- */
- if (*p++ == TLSEXT_NAMETYPE_host_name) {
- remaining--;
- /* Now we can finally pull out the byte array with the actual hostname. */
- if (remaining > 2) {
- len = *(p++) << 8;
- len += *(p++);
- if (len + 2 <= remaining) {
- servername = reinterpret_cast<const char *>(p);
- }
- }
- }
- }
- }
- if (servername) {
- netvc->set_server_name(std::string_view(servername, len));
- }
- int ret = PerformAction(netvc, netvc->get_server_name());
- if (ret != SSL_TLSEXT_ERR_OK) {
+ SSLNetVConnection *netvc = SSLNetVCAccess(s);
+ if (!netvc || netvc->ssl != s) {
+ Debug("ssl.error", "ssl_client_hello_callback call back on stale netvc");
return SSL_CLIENT_HELLO_ERROR;
}
- if (netvc->has_tunnel_destination() && !netvc->decrypt_tunnel()) {
- netvc->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL;
- }
- if (netvc->protocol_mask_set) {
- setTLSValidProtocols(s, netvc->protocol_mask, TLSValidProtocols::max_mask);
- }
bool reenabled = netvc->callHooks(TS_EVENT_SSL_CLIENT_HELLO);
@@ -473,32 +428,26 @@ ssl_cert_callback(SSL *ssl, void * /*arg*/)
* Cannot stop this callback. Always reeneabled
*/
static int
-ssl_servername_callback(SSL *ssl, int * /* ad */, void * /*arg*/)
+ssl_servername_callback(SSL *ssl, int *al, void *arg)
{
- SSLNetVConnection *netvc = SSLNetVCAccess(ssl);
-
- if (!netvc || netvc->ssl != ssl) {
- Debug("ssl.error", "ssl_servername_callback call back on stale netvc");
- return SSL_TLSEXT_ERR_ALERT_FATAL;
- }
-
- netvc->callHooks(TS_EVENT_SSL_SERVERNAME);
-
- const char *name = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
- if (name) {
- netvc->set_server_name(name);
- }
-
+ TLSSNISupport *snis = TLSSNISupport::getInstance(ssl);
+ if (snis) {
+ snis->on_servername(ssl, al, arg);
#if !TS_USE_HELLO_CB
- // Only call the SNI actions here if not already performed in the HELLO_CB
- int ret = PerformAction(netvc, netvc->get_server_name());
- if (ret != SSL_TLSEXT_ERR_OK) {
- return SSL_TLSEXT_ERR_ALERT_FATAL;
- }
+ // Only call the SNI actions here if not already performed in the HELLO_CB
+ int ret = snis->perform_sni_action();
+ if (ret != SSL_TLSEXT_ERR_OK) {
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+ }
#endif
- if (netvc->has_tunnel_destination() && !netvc->decrypt_tunnel()) {
- netvc->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL;
+ } else {
+ // This error suggests either of these:
+ // 1) Call back on unsupported netvc -- Don't register callback unnecessarily
+ // 2) Call back on stale netvc
+ Debug("ssl.error", "ssl_servername_callback was called unexpectedly");
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
}
+
return SSL_TLSEXT_ERR_OK;
}
@@ -912,6 +861,7 @@ SSLInitializeLibrary()
ssl_vc_index = SSL_get_ex_new_index(0, (void *)"NetVC index", nullptr, nullptr, nullptr);
TLSSessionResumptionSupport::initialize();
+ TLSSNISupport::initialize();
open_ssl_initialized = true;
}
@@ -1125,13 +1075,6 @@ SSLMultiCertConfigLoader::_set_handshake_callbacks(SSL_CTX *ctx)
}
void
-setTLSValidProtocols(SSL *ssl, unsigned long proto_mask, unsigned long max_mask)
-{
- SSL_set_options(ssl, proto_mask);
- SSL_clear_options(ssl, max_mask & ~proto_mask);
-}
-
-void
setClientCertLevel(SSL *ssl, uint8_t certLevel)
{
SSLConfig::scoped_config params;
diff --git a/iocore/net/TLSSNISupport.cc b/iocore/net/TLSSNISupport.cc
new file mode 100644
index 0000000..a7b883f
--- /dev/null
+++ b/iocore/net/TLSSNISupport.cc
@@ -0,0 +1,145 @@
+/** @file
+
+ SNISupport.cc provides implmentations for SNISupport methods
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+#include "TLSSNISupport.h"
+#include "tscore/ink_assert.h"
+#include "tscore/Diags.h"
+#include "P_SSLSNI.h"
+
+int TLSSNISupport::_ex_data_index = -1;
+
+void
+TLSSNISupport::initialize()
+{
+ ink_assert(_ex_data_index == -1);
+ if (_ex_data_index == -1) {
+ _ex_data_index = SSL_get_ex_new_index(0, (void *)"TLSSNISupport index", nullptr, nullptr, nullptr);
+ }
+}
+
+TLSSNISupport *
+TLSSNISupport::getInstance(SSL *ssl)
+{
+ return static_cast<TLSSNISupport *>(SSL_get_ex_data(ssl, _ex_data_index));
+}
+
+void
+TLSSNISupport::bind(SSL *ssl, TLSSNISupport *snis)
+{
+ SSL_set_ex_data(ssl, _ex_data_index, snis);
+}
+
+void
+TLSSNISupport::unbind(SSL *ssl)
+{
+ SSL_set_ex_data(ssl, _ex_data_index, nullptr);
+}
+
+int
+TLSSNISupport::perform_sni_action()
+{
+ const char *servername = this->_get_sni_server_name();
+ SNIConfig::scoped_config params;
+ if (const auto &actions = params->get(servername); !actions.first) {
+ Debug("ssl_sni", "%s not available in the map", servername);
+ } else {
+ for (auto &&item : *actions.first) {
+ auto ret = item->SNIAction(this, actions.second);
+ if (ret != SSL_TLSEXT_ERR_OK) {
+ return ret;
+ }
+ }
+ }
+ return SSL_TLSEXT_ERR_OK;
+}
+
+#if TS_USE_HELLO_CB
+void
+TLSSNISupport::on_client_hello(SSL *ssl, int *al, void *arg)
+{
+ const char *servername = nullptr;
+ const unsigned char *p;
+ size_t remaining, len;
+ // Parse the server name if the get extension call succeeds and there are more than 2 bytes to parse
+ if (SSL_client_hello_get0_ext(ssl, TLSEXT_TYPE_server_name, &p, &remaining) && remaining > 2) {
+ // Parse to get to the name, originally from test/handshake_helper.c in openssl tree
+ /* Extract the length of the supplied list of names. */
+ len = *(p++) << 8;
+ len += *(p++);
+ if (len + 2 == remaining) {
+ remaining = len;
+ /*
+ * The list in practice only has a single element, so we only consider
+ * the first one.
+ */
+ if (*p++ == TLSEXT_NAMETYPE_host_name) {
+ remaining--;
+ /* Now we can finally pull out the byte array with the actual hostname. */
+ if (remaining > 2) {
+ len = *(p++) << 8;
+ len += *(p++);
+ if (len + 2 <= remaining) {
+ servername = reinterpret_cast<const char *>(p);
+ }
+ }
+ }
+ }
+ }
+ if (servername) {
+ this->_set_sni_server_name(std::string_view(servername, len));
+ }
+}
+#endif
+
+void
+TLSSNISupport::on_servername(SSL *ssl, int *al, void *arg)
+{
+ this->_fire_ssl_servername_event();
+
+ const char *name = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
+ if (name) {
+ this->_set_sni_server_name(name);
+ }
+}
+
+void
+TLSSNISupport::_clear()
+{
+ _sni_server_name.reset();
+}
+
+const char *
+TLSSNISupport::_get_sni_server_name() const
+{
+ return _sni_server_name.get() ? _sni_server_name.get() : "";
+}
+
+void
+TLSSNISupport::_set_sni_server_name(std::string_view name)
+{
+ if (name.size()) {
+ char *n = new char[name.size() + 1];
+ std::memcpy(n, name.data(), name.size());
+ n[name.size()] = '\0';
+ _sni_server_name.reset(n);
+ }
+}
diff --git a/iocore/net/TLSSNISupport.h b/iocore/net/TLSSNISupport.h
new file mode 100644
index 0000000..8257202
--- /dev/null
+++ b/iocore/net/TLSSNISupport.h
@@ -0,0 +1,61 @@
+/** @file
+
+ TLSSNISupport implements common methods and members to
+ support protocols for Server Name Indication
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+#pragma once
+
+#include <string_view>
+#include <memory>
+#include <openssl/ssl.h>
+#include "tscore/ink_config.h"
+
+class TLSSNISupport
+{
+public:
+ virtual ~TLSSNISupport() = default;
+
+ static void initialize();
+ static TLSSNISupport *getInstance(SSL *ssl);
+ static void bind(SSL *ssl, TLSSNISupport *snis);
+ static void unbind(SSL *ssl);
+
+ int perform_sni_action();
+ // Callback functions for OpenSSL libraries
+#if TS_USE_HELLO_CB
+ void on_client_hello(SSL *ssl, int *al, void *arg);
+#endif
+ void on_servername(SSL *ssl, int *al, void *arg);
+
+protected:
+ virtual void _fire_ssl_servername_event() = 0;
+
+ void _clear();
+ const char *_get_sni_server_name() const;
+
+private:
+ static int _ex_data_index;
+
+ // Null-terminated string, or nullptr if there is no SNI server name.
+ std::unique_ptr<char[]> _sni_server_name;
+
+ void _set_sni_server_name(std::string_view name);
+};