diff --git a/iocore/net/Makefile.am b/iocore/net/Makefile.am index f6f34dccf85..f28614b8813 100644 --- a/iocore/net/Makefile.am +++ b/iocore/net/Makefile.am @@ -196,6 +196,7 @@ libinknet_a_SOURCES = \ TLSSessionResumptionSupport.cc \ TLSSNISupport.cc \ TLSTunnelSupport.cc \ + TLSCertSwitchSupport.cc \ UDPIOEvent.cc \ UnixConnection.cc \ UnixNet.cc \ diff --git a/iocore/net/P_SSLNetVConnection.h b/iocore/net/P_SSLNetVConnection.h index f14ffffba14..d119b79f6c8 100644 --- a/iocore/net/P_SSLNetVConnection.h +++ b/iocore/net/P_SSLNetVConnection.h @@ -52,6 +52,7 @@ #include "TLSEarlyDataSupport.h" #include "TLSTunnelSupport.h" #include "TLSBasicSupport.h" +#include "TLSCertSwitchSupport.h" #include "P_SSLUtils.h" #include "P_SSLConfig.h" @@ -103,6 +104,7 @@ class SSLNetVConnection : public UnixNetVConnection, public TLSSNISupport, public TLSEarlyDataSupport, public TLSTunnelSupport, + public TLSCertSwitchSupport, public TLSBasicSupport { typedef UnixNetVConnection super; ///< Parent type. @@ -406,6 +408,10 @@ class SSLNetVConnection : public UnixNetVConnection, void _fire_ssl_servername_event() override; + bool _isTryingRenegotiation() const override; + shared_SSL_CTX _lookupContextByName(const std::string &servername, SSLCertContextType ctxType) override; + shared_SSL_CTX _lookupContextByIP() override; + private: std::string_view map_tls_protocol_to_tag(const char *proto_string) const; bool update_rbio(bool move_to_socket); diff --git a/iocore/net/SSLNetVConnection.cc b/iocore/net/SSLNetVConnection.cc index c3a5771f6da..3e43303d6b1 100644 --- a/iocore/net/SSLNetVConnection.cc +++ b/iocore/net/SSLNetVConnection.cc @@ -227,6 +227,7 @@ SSLNetVConnection::_bindSSLObject() TLSSNISupport::bind(this->ssl, this); TLSEarlyDataSupport::bind(this->ssl, this); TLSTunnelSupport::bind(this->ssl, this); + TLSCertSwitchSupport::bind(this->ssl, this); } void @@ -239,6 +240,7 @@ SSLNetVConnection::_unbindSSLObject() TLSSNISupport::unbind(this->ssl); TLSEarlyDataSupport::unbind(this->ssl); TLSTunnelSupport::unbind(this->ssl); + TLSCertSwitchSupport::unbind(this->ssl); } static void @@ -976,6 +978,7 @@ SSLNetVConnection::clear() TLSSessionResumptionSupport::clear(); TLSSNISupport::_clear(); TLSTunnelSupport::_clear(); + TLSCertSwitchSupport::_clear(); sslHandshakeStatus = SSL_HANDSHAKE_ONGOING; sslLastWriteTime = 0; @@ -1975,6 +1978,77 @@ SSLNetVConnection::_fire_ssl_servername_event() this->callHooks(TS_EVENT_SSL_SERVERNAME); } +bool +SSLNetVConnection::_isTryingRenegotiation() const +{ + if (SSLConfigParams::ssl_allow_client_renegotiation == false && this->getSSLHandShakeComplete()) { + return true; + } else { + return false; + } +} + +shared_SSL_CTX +SSLNetVConnection::_lookupContextByName(const std::string &servername, SSLCertContextType ctxType) +{ + shared_SSL_CTX ctx = nullptr; + SSLCertificateConfig::scoped_config lookup; + SSLCertContext *cc = lookup->find(servername, ctxType); + + if (cc) { + ctx = cc->getCtx(); + } + + if (cc && ctx && SSLCertContextOption::OPT_TUNNEL == cc->opt && this->get_is_transparent()) { + this->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL; + this->setSSLHandShakeComplete(SSL_HANDSHAKE_DONE); + return nullptr; + } else { + return ctx; + } +} + +shared_SSL_CTX +SSLNetVConnection::_lookupContextByIP() +{ + shared_SSL_CTX ctx = nullptr; + SSLCertificateConfig::scoped_config lookup; + IpEndpoint ip; + int namelen = sizeof(ip); + + // Return null if this vc is already configured as a tunnel + if (this->attributes == HttpProxyPort::TRANSPORT_BLIND_TUNNEL) { + return nullptr; + } + + SSLCertContext *cc = nullptr; + if (this->get_is_proxy_protocol() && this->get_proxy_protocol_version() != ProxyProtocolVersion::UNDEFINED) { + ip.sa = *(this->get_proxy_protocol_dst_addr()); + ip_port_text_buffer ipb1; + ats_ip_nptop(&ip, ipb1, sizeof(ipb1)); + cc = lookup->find(ip); + if (is_debug_tag_set("proxyprotocol")) { + IpEndpoint src; + ip_port_text_buffer ipb2; + int ip_len = sizeof(src); + + if (0 != safe_getpeername(this->get_socket(), &src.sa, &ip_len)) { + Debug("proxyprotocol", "Failed to get src ip, errno = [%d]", errno); + return nullptr; + } + ats_ip_nptop(&src, ipb2, sizeof(ipb2)); + Debug("proxyprotocol", "IP context is %p for [%s] -> [%s], default context %p", cc, ipb2, ipb1, lookup->defaultContext()); + } + } else if (0 == safe_getsockname(this->get_socket(), &ip.sa, &namelen)) { + cc = lookup->find(ip); + } + if (cc) { + ctx = cc->getCtx(); + } + + return ctx; +} + void SSLNetVConnection::set_ca_cert_file(std::string_view file, std::string_view dir) { diff --git a/iocore/net/SSLUtils.cc b/iocore/net/SSLUtils.cc index f05ca5eb993..beb49611264 100644 --- a/iocore/net/SSLUtils.cc +++ b/iocore/net/SSLUtils.cc @@ -261,118 +261,6 @@ ssl_rm_cached_session(SSL_CTX *ctx, SSL_SESSION *sess) session_cache->removeSession(sid); } -static int -set_context_cert(SSL *ssl, void *arg) -{ - shared_SSL_CTX ctx = nullptr; - SSL_CTX *verify_ctx = nullptr; - SSLCertContext *cc = nullptr; - SSLCertificateConfig::scoped_config lookup; - - const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); - SSLNetVConnection *netvc = SSLNetVCAccess(ssl); - bool found = true; - int retval = 1; - SSLCertContextType ctxType = SSLCertContextType::GENERIC; - - if (!netvc || netvc->ssl != ssl) { - Debug("ssl.error", "set_context_cert call back on stale netvc"); - retval = 0; // Error - goto done; - } - - Debug("ssl_load", "set_context_cert ssl=%p server=%s handshake_complete=%d", ssl, servername, netvc->getSSLHandShakeComplete()); - - // catch the client renegotiation early on - if (SSLConfigParams::ssl_allow_client_renegotiation == false && netvc->getSSLHandShakeComplete()) { - Debug("ssl_load", "set_context_cert trying to renegotiate from the client"); - retval = 0; // Error - goto done; - } - -#ifdef OPENSSL_IS_BORINGSSL - if (arg != nullptr) { - const SSL_CLIENT_HELLO *client_hello = (const SSL_CLIENT_HELLO *)arg; - const bool client_ecdsa_capable = BoringSSLUtils::isClientEcdsaCapable(client_hello); - ctxType = client_ecdsa_capable ? SSLCertContextType::EC : SSLCertContextType::RSA; - } -#endif - - // The incoming SSL_CTX is either the one mapped from the inbound IP address or the default one. If we - // don't find a name-based match at this point, we *do not* want to mess with the context because we've - // already made a best effort to find the best match. - if (likely(servername)) { - cc = lookup->find(servername, ctxType); - if (cc) { - ctx = cc->getCtx(); - } - if (cc && ctx && SSLCertContextOption::OPT_TUNNEL == cc->opt && netvc->get_is_transparent()) { - netvc->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL; - netvc->setSSLHandShakeComplete(SSL_HANDSHAKE_DONE); - retval = -1; - goto done; - } - } - - // If there's no match on the server name, try to match on the peer address. - if (ctx == nullptr) { - IpEndpoint ip; - int namelen = sizeof(ip); - - if (netvc->get_is_proxy_protocol() && netvc->get_proxy_protocol_version() != ProxyProtocolVersion::UNDEFINED) { - ip.sa = *(netvc->get_proxy_protocol_dst_addr()); - ip_port_text_buffer ipb1; - ats_ip_nptop(&ip, ipb1, sizeof(ipb1)); - cc = lookup->find(ip); - if (is_debug_tag_set("proxyprotocol")) { - IpEndpoint src; - ip_port_text_buffer ipb2; - int ip_len = sizeof(src); - - if (0 != safe_getpeername(netvc->get_socket(), &src.sa, &ip_len)) { - Debug("proxyprotocol", "Failed to get src ip, errno = [%d]", errno); - return EVENT_ERROR; - } - ats_ip_nptop(&src, ipb2, sizeof(ipb2)); - Debug("proxyprotocol", "IP context is %p for [%s] -> [%s], default context %p", cc, ipb2, ipb1, lookup->defaultContext()); - } - } else if (0 == safe_getsockname(netvc->get_socket(), &ip.sa, &namelen)) { - cc = lookup->find(ip); - } - if (cc) { - ctx = cc->getCtx(); - } - } - - if (ctx != nullptr) { - SSL_set_SSL_CTX(ssl, ctx.get()); -#if TS_HAS_TLS_SESSION_TICKET - // Reset the ticket callback if needed -#ifdef HAVE_SSL_CTX_SET_TLSEXT_TICKET_KEY_EVP_CB - SSL_CTX_set_tlsext_ticket_key_evp_cb(ctx.get(), ssl_callback_session_ticket); -#else - SSL_CTX_set_tlsext_ticket_key_cb(ctx.get(), ssl_callback_session_ticket); -#endif -#endif - // After replacing the SSL_CTX, make sure the overridden ca_cert_file is still set - setClientCertCACerts(ssl, netvc->get_ca_cert_file(), netvc->get_ca_cert_dir()); - } else { - found = false; - } - - verify_ctx = SSL_get_SSL_CTX(ssl); - // set_context_cert found SSL context for ... - Debug("ssl_load", "ssl_cert_callback %s SSL context %p for requested name '%s'", found ? "found" : "using", verify_ctx, - servername); - - if (verify_ctx == nullptr) { - retval = 0; - goto done; - } -done: - return retval; -} - // Callback function for verifying client certificate static int ssl_verify_client_callback(int preverify_ok, X509_STORE_CTX *ctx) @@ -418,17 +306,19 @@ ssl_client_hello_callback(SSL *s, int *al, void *arg) return SSL_CLIENT_HELLO_ERROR; } - 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; - } - - bool reenabled = netvc->callHooks(TS_EVENT_SSL_CLIENT_HELLO); + SSLNetVConnection *netvc = dynamic_cast(snis); + if (netvc) { + if (netvc->ssl != s) { + Debug("ssl.error", "ssl_client_hello_callback call back on stale netvc"); + return SSL_CLIENT_HELLO_ERROR; + } - if (!reenabled) { - return SSL_CLIENT_HELLO_RETRY; + bool reenabled = netvc->callHooks(TS_EVENT_SSL_CLIENT_HELLO); + if (!reenabled) { + return SSL_CLIENT_HELLO_RETRY; + } } + return SSL_CLIENT_HELLO_SUCCESS; } #elif defined(OPENSSL_IS_BORINGSSL) @@ -452,17 +342,19 @@ ssl_client_hello_callback(const SSL_CLIENT_HELLO *client_hello) return ssl_select_cert_error; } - SSLNetVConnection *netvc = SSLNetVCAccess(s); - if (!netvc || netvc->ssl != s) { - Debug("ssl.error", "ssl_client_hello_callback call back on stale netvc"); - return ssl_select_cert_error; - } - - bool reenabled = netvc->callHooks(TS_EVENT_SSL_CLIENT_HELLO); + SSLNetVConnection *netvc = dynamic_cast(snis); + if (netvc) { + if (netvc->ssl != s) { + Debug("ssl.error", "ssl_client_hello_callback call back on stale netvc"); + return ssl_select_cert_error; + } - if (!reenabled) { - return ssl_select_cert_retry; + bool reenabled = netvc->callHooks(TS_EVENT_SSL_CLIENT_HELLO); + if (!reenabled) { + return ssl_select_cert_retry; + } } + return ssl_select_cert_success; } #endif @@ -474,16 +366,13 @@ ssl_client_hello_callback(const SSL_CLIENT_HELLO *client_hello) static int ssl_cert_callback(SSL *ssl, void *arg) { - SSLNetVConnection *netvc = SSLNetVCAccess(ssl); + TLSCertSwitchSupport *tcss = TLSCertSwitchSupport::getInstance(ssl); + SSLNetVConnection *sslnetvc = dynamic_cast(tcss); bool reenabled; int retval = 1; - if (!netvc || netvc->ssl != ssl) { - Debug("ssl.error", "ssl_cert_callback call back on stale netvc"); - return 0; - } - // If we are in tunnel mode, don't select a cert. Pause! + NetVConnection *netvc = reinterpret_cast(sslnetvc); if (HttpProxyPort::TRANSPORT_BLIND_TUNNEL == netvc->attributes) { #ifdef OPENSSL_IS_BORINGSSL return -2; // Retry @@ -492,22 +381,56 @@ ssl_cert_callback(SSL *ssl, void *arg) #endif } - // Do the common certificate lookup only once. If we pause - // and restart processing, do not execute the common logic again - if (!netvc->calledHooks(TS_EVENT_SSL_CERT)) { - retval = set_context_cert(ssl, arg); - if (retval != 1) { - return retval; + SSLCertContextType ctxType = SSLCertContextType::GENERIC; +#ifdef OPENSSL_IS_BORINGSSL + if (arg != nullptr) { + const SSL_CLIENT_HELLO *client_hello = (const SSL_CLIENT_HELLO *)arg; + const bool client_ecdsa_capable = BoringSSLUtils::isClientEcdsaCapable(client_hello); + ctxType = client_ecdsa_capable ? SSLCertContextType::EC : SSLCertContextType::RSA; + } +#endif + + if (sslnetvc) { + // Do the common certificate lookup only once. If we pause + // and restart processing, do not execute the common logic again + if (!sslnetvc->calledHooks(TS_EVENT_SSL_CERT)) { + retval = sslnetvc->selectCertificate(ssl, ctxType); + if (retval != 1) { + return retval; + } + } + + // Call the plugin cert code + reenabled = sslnetvc->callHooks(TS_EVENT_SSL_CERT); + // If it did not re-enable, return the code to + // stop the accept processing + if (!reenabled) { + retval = -1; // Pause + } + } else { + if (tcss->selectCertificate(ssl, ctxType) == 1) { + retval = 1; + } else { + retval = 0; } } - // Call the plugin cert code - reenabled = netvc->callHooks(TS_EVENT_SSL_CERT); - // If it did not re-enable, return the code to - // stop the accept processing - if (!reenabled) { - retval = -1; // Pause +#if TS_HAS_TLS_SESSION_TICKET + if (retval == 1) { + // After replacing the SSL_CTX, make sure the overridden ca_cert_file is still set + if (sslnetvc) { + 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 } +#endif // Return 1 for success, 0 for error, or -1 to pause return retval; @@ -1020,6 +943,7 @@ SSLInitializeLibrary() TLSSNISupport::initialize(); TLSEarlyDataSupport::initialize(); TLSTunnelSupport::initialize(); + TLSCertSwitchSupport::initialize(); open_ssl_initialized = true; } diff --git a/iocore/net/TLSCertSwitchSupport.cc b/iocore/net/TLSCertSwitchSupport.cc new file mode 100644 index 00000000000..4ee68605d75 --- /dev/null +++ b/iocore/net/TLSCertSwitchSupport.cc @@ -0,0 +1,103 @@ +/** @file + + @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 "TLSCertSwitchSupport.h" +#include "P_SSLCertLookup.h" + +int TLSCertSwitchSupport::_ex_data_index = -1; + +void +TLSCertSwitchSupport::initialize() +{ + ink_assert(_ex_data_index == -1); + if (_ex_data_index == -1) { + _ex_data_index = SSL_get_ex_new_index(0, (void *)"TLSEarlyDataSupport index", nullptr, nullptr, nullptr); + } +} + +TLSCertSwitchSupport * +TLSCertSwitchSupport::getInstance(SSL *ssl) +{ + return static_cast(SSL_get_ex_data(ssl, _ex_data_index)); +} + +void +TLSCertSwitchSupport::bind(SSL *ssl, TLSCertSwitchSupport *tcss) +{ + SSL_set_ex_data(ssl, _ex_data_index, tcss); +} + +void +TLSCertSwitchSupport::unbind(SSL *ssl) +{ + SSL_set_ex_data(ssl, _ex_data_index, nullptr); +} + +void +TLSCertSwitchSupport::_clear() +{ +} + +int +TLSCertSwitchSupport::selectCertificate(SSL *ssl, SSLCertContextType ctxType) +{ + shared_SSL_CTX ctx = nullptr; + + const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + bool found = true; + + Debug("ssl", "set_context_cert ssl=%p server=%s", ssl, servername); + + // catch the client renegotiation early on + if (this->_isTryingRenegotiation()) { + Debug("ssl_load", "set_context_cert trying to renegotiate from the client"); + return 0; + } + + // The incoming SSL_CTX is either the one mapped from the inbound IP address or the default one. If we + // don't find a name-based match at this point, we *do not* want to mess with the context because we've + // already made a best effort to find the best match. + if (likely(servername)) { + ctx = this->_lookupContextByName(servername, ctxType); + } + + // If there's no match on the server name, try to match on the peer address. + if (ctx == nullptr) { + ctx = this->_lookupContextByIP(); + } + + if (ctx != nullptr) { + SSL_set_SSL_CTX(ssl, ctx.get()); + } else { + found = false; + } + + SSL_CTX *verify_ctx = SSL_get_SSL_CTX(ssl); + // set_context_cert found SSL context for ... + Debug("ssl_load", "ssl_cert_callback %s SSL context %p for requested name '%s'", found ? "found" : "using", verify_ctx, + servername); + + if (verify_ctx == nullptr) { + return 0; + } + + return 1; +} diff --git a/iocore/net/TLSCertSwitchSupport.h b/iocore/net/TLSCertSwitchSupport.h new file mode 100644 index 00000000000..451933f5f37 --- /dev/null +++ b/iocore/net/TLSCertSwitchSupport.h @@ -0,0 +1,51 @@ +/** @file + + TLSCertSwitchSupport implements common methods and members to + support switching certificate + + @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 +#include "P_SSLCertLookup.h" + +class TLSCertSwitchSupport +{ +public: + virtual ~TLSCertSwitchSupport() = default; + + static void initialize(); + static TLSCertSwitchSupport *getInstance(SSL *ssl); + static void bind(SSL *ssl, TLSCertSwitchSupport *tcss); + static void unbind(SSL *ssl); + + int selectCertificate(SSL *ssl, SSLCertContextType ctxType); + +protected: + void _clear(); + + virtual bool _isTryingRenegotiation() const = 0; + virtual shared_SSL_CTX _lookupContextByName(const std::string &servername, SSLCertContextType ctxType) = 0; + virtual shared_SSL_CTX _lookupContextByIP() = 0; + +private: + static int _ex_data_index; +};