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 <[email protected]> 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); +};
