TS-3006: Add SSL extensions and examples.
Project: http://git-wip-us.apache.org/repos/asf/trafficserver/repo Commit: http://git-wip-us.apache.org/repos/asf/trafficserver/commit/044da699 Tree: http://git-wip-us.apache.org/repos/asf/trafficserver/tree/044da699 Diff: http://git-wip-us.apache.org/repos/asf/trafficserver/diff/044da699 Branch: refs/heads/master Commit: 044da6999442449434b282d8b537d8858505bbfc Parents: 2f6d6e0 Author: Susan Hinrichs <[email protected]> Authored: Mon Sep 22 14:12:15 2014 -0500 Committer: Alan M. Carroll <[email protected]> Committed: Mon Sep 22 14:12:15 2014 -0500 ---------------------------------------------------------------------- CHANGES | 2 + build/plugins.mk | 3 +- configure.ac | 1 + example/Makefile.am | 6 + example/ssl-preaccept/ats-util.h | 40 ++ example/ssl-preaccept/ssl-preaccept.cc | 197 +++++++ example/ssl-preaccept/ssl_preaccept.config | 7 + example/ssl-sni-whitelist/ssl-sni-whitelist.cc | 141 +++++ .../ssl-sni-whitelist/ssl_sni_whitelist.config | 3 + example/ssl-sni/ssl-sni.cc | 162 ++++++ example/ssl-sni/ssl_sni.config | 7 + iocore/net/Makefile.am | 4 +- iocore/net/OCSPStapling.cc | 31 +- iocore/net/P_SSLCertLookup.h | 53 +- iocore/net/P_SSLNetVConnection.h | 56 ++ iocore/net/SSLCertLookup.cc | 171 +++--- iocore/net/SSLNetVConnection.cc | 508 +++++++++++++++-- iocore/net/SSLUtils.cc | 110 +++- iocore/net/UnixNetVConnection.cc | 6 +- lib/records/I_RecHttp.h | 9 +- lib/ts/Vec.h | 7 + lib/ts/apidefs.h.in | 20 + lib/ts/ink_sock.cc | 10 + lib/ts/ink_sock.h | 1 + plugins/experimental/Makefile.am | 1 + .../experimental/ssl_cert_loader/Makefile.am | 26 + plugins/experimental/ssl_cert_loader/ats-util.h | 40 ++ .../experimental/ssl_cert_loader/domain-tree.cc | 168 ++++++ .../experimental/ssl_cert_loader/domain-tree.h | 66 +++ .../ssl_cert_loader/ssl-cert-loader.cc | 541 +++++++++++++++++++ .../ssl_cert_loader/ssl_cert_loader.cfg | 135 +++++ .../experimental/ssl_cert_loader/ssl_start.cfg | 55 ++ proxy/InkAPI.cc | 129 ++++- proxy/InkAPIInternal.h | 12 + proxy/api/ts/ts.h | 15 + proxy/config/ssl_multicert.config.default | 14 +- proxy/http/HttpDebugNames.cc | 4 + proxy/http/HttpSM.cc | 2 +- proxy/http/HttpSessionAccept.cc | 6 +- proxy/http/HttpSessionAccept.h | 1 + proxy/http/HttpTransact.cc | 8 +- proxy/http/HttpTunnel.cc | 3 +- 42 files changed, 2620 insertions(+), 161 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/CHANGES ---------------------------------------------------------------------- diff --git a/CHANGES b/CHANGES index d79dadb..446f140 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,7 @@ -*- coding: utf-8 -*- Changes with Apache Traffic Server 5.2.0 + *) [TS-3006] Add SSL extensions and examples. + Author: Susan Hinrichs <[email protected]> *) [TS-3054] Forward partial chunked data to client to be more transparent. Author: Susan Hinrichs <[email protected]> http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/build/plugins.mk ---------------------------------------------------------------------- diff --git a/build/plugins.mk b/build/plugins.mk index 560d8b9..b92eb1f 100644 --- a/build/plugins.mk +++ b/build/plugins.mk @@ -26,7 +26,8 @@ TS_PLUGIN_CPPFLAGS = \ -I$(top_builddir)/proxy/api \ -I$(top_srcdir)/proxy/api \ -I$(top_builddir)/lib/ts \ - -I$(top_srcdir)/lib/ts + -I$(top_srcdir)/lib/ts \ + -I$(top_srcdir)/lib # Provide a default AM_CPPFLAGS. Automake handles this correctly, but libtool # throws an error if we try to do the same with AM_LDFLAGS. Hence, we provide http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/configure.ac ---------------------------------------------------------------------- diff --git a/configure.ac b/configure.ac index 0785790..c3c7e0e 100644 --- a/configure.ac +++ b/configure.ac @@ -1942,6 +1942,7 @@ AS_IF([test "x$enable_experimental_plugins" = xyes], [ plugins/experimental/remap_stats/Makefile plugins/experimental/s3_auth/Makefile plugins/experimental/sslheaders/Makefile + plugins/experimental/ssl_cert_loader/Makefile plugins/experimental/stale_while_revalidate/Makefile plugins/experimental/ts_lua/Makefile plugins/experimental/url_sig/Makefile http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/example/Makefile.am ---------------------------------------------------------------------- diff --git a/example/Makefile.am b/example/Makefile.am index c57fbea..47c1d90 100644 --- a/example/Makefile.am +++ b/example/Makefile.am @@ -40,6 +40,9 @@ plugins = \ response-header-1.la \ secure-link.la \ server-transform.la \ + ssl-preaccept.la \ + ssl-sni.la \ + ssl-sni-whitelist.la \ thread-1.la \ version.la @@ -72,6 +75,9 @@ thread_1_la_SOURCES = thread-1/thread-1.c psi_la_SOURCES = thread-pool/psi.c thread-pool/thread.c version_la_SOURCES = version/version.c secure_link_la_SOURCES = secure-link/secure-link.c +ssl_preaccept_la_SOURCES = ssl-preaccept/ssl-preaccept.cc +ssl_sni_la_SOURCES = ssl-sni/ssl-sni.cc +ssl_sni_whitelist_la_SOURCES = ssl-sni-whitelist/ssl-sni-whitelist.cc # The following examples do not build: # http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/example/ssl-preaccept/ats-util.h ---------------------------------------------------------------------- diff --git a/example/ssl-preaccept/ats-util.h b/example/ssl-preaccept/ats-util.h new file mode 100644 index 0000000..1164a20 --- /dev/null +++ b/example/ssl-preaccept/ats-util.h @@ -0,0 +1,40 @@ +# if !defined(_ats_util_h) +# define _ats_util_h + +# if defined(__cplusplus) +/** Set data to zero. + + Calls @c memset on @a t with a value of zero and a length of @c + sizeof(t). This can be used on ordinary and array variables. While + this can be used on variables of intrinsic type it's inefficient. + + @note Because this uses templates it cannot be used on unnamed or + locally scoped structures / classes. This is an inherent + limitation of templates. + + Examples: + @code + foo bar; // value. + ink_zero(bar); // zero bar. + + foo *bar; // pointer. + ink_zero(bar); // WRONG - makes the pointer @a bar zero. + ink_zero(*bar); // zero what bar points at. + + foo bar[ZOMG]; // Array of structs. + ink_zero(bar); // Zero all structs in array. + + foo *bar[ZOMG]; // array of pointers. + ink_zero(bar); // zero all pointers in the array. + @endcode + + */ +template < typename T > inline void +ink_zero( + T& t ///< Object to zero. + ) { + memset(&t, 0, sizeof(t)); +} +# endif /* __cplusplus */ + +# endif // ats-util.h http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/example/ssl-preaccept/ssl-preaccept.cc ---------------------------------------------------------------------- diff --git a/example/ssl-preaccept/ssl-preaccept.cc b/example/ssl-preaccept/ssl-preaccept.cc new file mode 100644 index 0000000..9a9a7be --- /dev/null +++ b/example/ssl-preaccept/ssl-preaccept.cc @@ -0,0 +1,197 @@ +/** @file + SSL Preaccept test plugin + Implements blind tunneling based on the client IP address + The client ip addresses are specified in the plugin's + config file as an array of IP addresses or IP address ranges under the + key "client-blind-tunnel" +*/ + +# include <stdio.h> +# include <memory.h> +# include <inttypes.h> +# include <ts/ts.h> +# include <tsconfig/TsValue.h> +# include <alloca.h> +# include <ts/ink_inet.h> + +using ts::config::Configuration; +using ts::config::Value; + +# define PN "ssl-preaccept" +# define PCP "[" PN " Plugin] " + +namespace { + +std::string ConfigPath; +typedef std::pair<IpAddr, IpAddr> IpRange; +typedef std::deque<IpRange> IpRangeQueue; +IpRangeQueue ClientBlindTunnelIp; + +Configuration Config; // global configuration + +void +Parse_Addr_String(ts::ConstBuffer const &text, IpRange &range) { + IpAddr newAddr; + std::string textstr(text._ptr, text._size); + // Is there a hyphen? + size_t hyphen_pos = textstr.find("-"); + if (hyphen_pos != std::string::npos) { + std::string addr1 = textstr.substr(0, hyphen_pos); + std::string addr2 = textstr.substr(hyphen_pos+1); + range.first.load(ts::ConstBuffer(addr1.c_str(), addr1.length())); + range.second.load(ts::ConstBuffer(addr2.c_str(), addr2.length())); + } + else { // Assume it is a single address + newAddr.load(text); + range.first = newAddr; + range.second = newAddr; + } +} + +/// Get a string value from a config node. +void Load_Config_Value(Value const& parent, char const* name, IpRangeQueue &addrs) { + Value v = parent[name]; + std::string zret; + IpRange ipRange; + if (v.isLiteral()) { + Parse_Addr_String(v.getText(), ipRange); + addrs.push_back(ipRange); + } else if (v.isContainer()) { + size_t i; + for (i = 0; i < v.childCount(); i++) { + std::string val_str(v[i].getText()._ptr, v[i].getText()._size); + Parse_Addr_String(v[i].getText(), ipRange); + addrs.push_back(ipRange); + } + } +} + + +int +Load_Config_File() { + ts::Rv<Configuration> cv = Configuration::loadFromPath(ConfigPath.c_str()); + if (!cv.isOK()) { + TSError(PCP "Failed to parse %s as TSConfig format", ConfigPath.c_str()); + return -1; + } + Config = cv; + return 1; +} + +int +Load_Configuration(int argc, const char *argv[]) { +ts::ConstBuffer text; + std::string s; // temp holder. + TSMgmtString config_path = NULL; + + // get the path to the config file if one was specified + static char const * const CONFIG_ARG = "--config="; + int arg_idx; + for (arg_idx = 0; arg_idx < argc; arg_idx++) { + if (0 == memcmp(argv[arg_idx], CONFIG_ARG, strlen(CONFIG_ARG))) { + config_path = TSstrdup(argv[arg_idx] + strlen(CONFIG_ARG)); + TSDebug(PN, "Found config path %s", config_path); + } + } + if (NULL == config_path) { + static char const * const DEFAULT_CONFIG_PATH = "ssl_preaccept.config"; + config_path = TSstrdup(DEFAULT_CONFIG_PATH); + TSDebug(PN, "No config path set in arguments, using default: %s", DEFAULT_CONFIG_PATH); + } + + // translate relative paths to absolute + if (config_path[0] != '/') { + ConfigPath = std::string(TSConfigDirGet()) + '/' + std::string(config_path); + } else { + ConfigPath = config_path; + } + + // free up the path + TSfree(config_path); + + int ret = Load_Config_File(); + if (ret != 0) { + TSError(PCP "Failed to load the config file, check debug output for errata"); + } + + // Still need to use the file + Value root = Config.getRoot(); + Load_Config_Value(root, "client-blind-tunnel", ClientBlindTunnelIp); + + return 0; +} + +int +CB_Pre_Accept(TSCont, TSEvent event, void *edata) { + TSVConn ssl_vc = reinterpret_cast<TSVConn>(edata); + IpAddr ip(TSNetVConnLocalAddrGet(ssl_vc)); + char buff[INET6_ADDRSTRLEN]; + IpAddr ip_client(TSNetVConnRemoteAddrGet(ssl_vc)); + char buff2[INET6_ADDRSTRLEN]; + + TSDebug("skh", "Pre accept callback %p - event is %s, target address %s, client address %s" + , ssl_vc + , event == TS_EVENT_VCONN_PRE_ACCEPT ? "good" : "bad" + , ip.toString(buff, sizeof(buff)) + , ip_client.toString(buff2, sizeof(buff2)) + ); + + // Not the worlds most efficient address comparison. For short lists + // shouldn't be too bad. If the client IP is in any of the ranges, + // flip the tunnel to be blind tunneled instead of decrypted and proxied + bool proxy_tunnel = true; + IpRangeQueue::iterator iter; + for (iter = ClientBlindTunnelIp.begin(); iter != ClientBlindTunnelIp.end() && proxy_tunnel; iter++) { + if (ip_client >= iter->first && ip_client <= iter->second) { + proxy_tunnel = false; + } + } + if (!proxy_tunnel) { + TSDebug("skh", "Blind tunnel"); + // Push everything to blind tunnel + TSVConnTunnel(ssl_vc); + } + else { + TSDebug("skh", "Proxy tunnel"); + } + + // All done, reactivate things + TSVConnReenable(ssl_vc); + return TS_SUCCESS; +} + +} // Anon namespace + +// Called by ATS as our initialization point +void +TSPluginInit(int argc, const char *argv[]) { + bool success = false; + TSPluginRegistrationInfo info; + TSCont cb_pa = 0; // pre-accept callback continuation + + info.plugin_name = const_cast<char*>("SSL Preaccept test"); + info.vendor_name = const_cast<char*>("Network Geographics"); + info.support_email = const_cast<char*>("[email protected]"); + + if (TS_SUCCESS != TSPluginRegister(TS_SDK_VERSION_2_0, &info)) { + TSError(PCP "registration failed."); + } else if (TSTrafficServerVersionGetMajor() < 2) { + TSError(PCP "requires Traffic Server 2.0 or later."); + } else if (0 > Load_Configuration(argc, argv)) { + TSError(PCP "Failed to load config file."); + } else if (0 == (cb_pa = TSContCreate(&CB_Pre_Accept, TSMutexCreate()))) { + TSError(PCP "Failed to pre-accept callback."); + } else { + TSHttpHookAdd(TS_VCONN_PRE_ACCEPT_HOOK, cb_pa); + success = true; + } + + if (!success) { + if (cb_pa) TSContDestroy(cb_pa); + TSError(PCP "not initialized"); + } + TSDebug(PN, "Plugin %s", success ? "online" : "offline"); + + return; +} + http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/example/ssl-preaccept/ssl_preaccept.config ---------------------------------------------------------------------- diff --git a/example/ssl-preaccept/ssl_preaccept.config b/example/ssl-preaccept/ssl_preaccept.config new file mode 100644 index 0000000..2ec52ec --- /dev/null +++ b/example/ssl-preaccept/ssl_preaccept.config @@ -0,0 +1,7 @@ + +// SSL traffic initiating from these addresses should +// be placed into a blind tunnel. ATS should not inspect +// the tunnel +client-blind-tunnel = "192.168.56.145" + +//client-blind-tunnel = "192.168.56.0/24" http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/example/ssl-sni-whitelist/ssl-sni-whitelist.cc ---------------------------------------------------------------------- diff --git a/example/ssl-sni-whitelist/ssl-sni-whitelist.cc b/example/ssl-sni-whitelist/ssl-sni-whitelist.cc new file mode 100644 index 0000000..4cd1aaa --- /dev/null +++ b/example/ssl-sni-whitelist/ssl-sni-whitelist.cc @@ -0,0 +1,141 @@ +/** @file + SSL SNI white list plugin + If the server name and IP address are not in the ssl_multicert.config + go head and blind tunnel it. +*/ + +# include <stdio.h> +# include <memory.h> +# include <inttypes.h> +# include <ts/ts.h> +# include <tsconfig/TsValue.h> +# include <alloca.h> +# include <openssl/ssl.h> + +using ts::config::Configuration; +using ts::config::Value; + +# define PN "ssl-sni-whitelist" +# define PCP "[" PN " Plugin] " + +namespace { + +std::string ConfigPath; + +Configuration Config; // global configuration + +int +Load_Config_File() { + ts::Rv<Configuration> cv = Configuration::loadFromPath(ConfigPath.c_str()); + if (!cv.isOK()) { + TSError(PCP "Failed to parse %s as TSConfig format", ConfigPath.c_str()); + return -1; + } + Config = cv; + return 1; +} + +int +Load_Configuration(int argc, const char *argv[]) { +ts::ConstBuffer text; + std::string s; // temp holder. + TSMgmtString config_path = NULL; + + // get the path to the config file if one was specified + static char const * const CONFIG_ARG = "--config="; + int arg_idx; + for (arg_idx = 0; arg_idx < argc; arg_idx++) { + if (0 == memcmp(argv[arg_idx], CONFIG_ARG, strlen(CONFIG_ARG))) { + config_path = TSstrdup(argv[arg_idx] + strlen(CONFIG_ARG)); + TSDebug(PN, "Found config path %s", config_path); + } + } + if (NULL == config_path) { + static char const * const DEFAULT_CONFIG_PATH = "ssl_sni_whitelist.config"; + config_path = TSstrdup(DEFAULT_CONFIG_PATH); + TSDebug(PN, "No config path set in arguments, using default: %s", DEFAULT_CONFIG_PATH); + } + + // translate relative paths to absolute + if (config_path[0] != '/') { + ConfigPath = std::string(TSConfigDirGet()) + '/' + std::string(config_path); + } else { + ConfigPath = config_path; + } + + // free up the path + TSfree(config_path); + + int ret = Load_Config_File(); + if (ret != 0) { + TSError(PCP "Failed to load the config file, check debug output for errata"); + } + + return 0; +} + +int +CB_servername_whitelist(TSCont contp, TSEvent event, void *edata) { + TSVConn ssl_vc = reinterpret_cast<TSVConn>(edata); + TSSslConnection sslobj = TSVConnSSLConnectionGet(ssl_vc); + SSL *ssl = reinterpret_cast<SSL *>(sslobj); + const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + + bool do_blind_tunnel = true; + if (servername != NULL) { + TSSslContext ctxobj = TSSslContextFindByName(servername); + if (ctxobj != NULL) { + do_blind_tunnel = false; + } + else { + // Look up by destination address + ctxobj = TSSslContextFindByAddr(TSNetVConnRemoteAddrGet(ssl_vc)); + if (ctxobj != NULL) { + do_blind_tunnel = false; + } + } + } + if (do_blind_tunnel) { + TSDebug("skh", "SNI callback: do blind tunnel for %s", servername); + TSVConnTunnel(ssl_vc); + return TS_SUCCESS; // Don't re-enable so we interrupt processing + } + TSVConnReenable(ssl_vc); + return TS_SUCCESS; +} + +} // Anon namespace + +// Called by ATS as our initialization point +void +TSPluginInit(int argc, const char *argv[]) { + bool success = false; + TSPluginRegistrationInfo info; + TSCont cb_sni = 0; // sni callback continuation + + info.plugin_name = const_cast<char*>("SSL SNI whitelist"); + info.vendor_name = const_cast<char*>("Network Geographics"); + info.support_email = const_cast<char*>("[email protected]"); + + if (TS_SUCCESS != TSPluginRegister(TS_SDK_VERSION_2_0, &info)) { + TSError(PCP "registration failed."); + } else if (TSTrafficServerVersionGetMajor() < 2) { + TSError(PCP "requires Traffic Server 2.0 or later."); + } else if (0 > Load_Configuration(argc, argv)) { + TSError(PCP "Failed to load config file."); + } else if (0 == (cb_sni = TSContCreate(&CB_servername_whitelist, TSMutexCreate()))) { + TSError(PCP "Failed to create SNI callback."); + } else { + TSHttpHookAdd(TS_SSL_SNI_HOOK, cb_sni); + success = true; + } + + if (!success) { + if (cb_sni) TSContDestroy(cb_sni); + TSError(PCP "not initialized"); + } + TSDebug(PN, "Plugin %s", success ? "online" : "offline"); + + return; +} + http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/example/ssl-sni-whitelist/ssl_sni_whitelist.config ---------------------------------------------------------------------- diff --git a/example/ssl-sni-whitelist/ssl_sni_whitelist.config b/example/ssl-sni-whitelist/ssl_sni_whitelist.config new file mode 100644 index 0000000..706e6a8 --- /dev/null +++ b/example/ssl-sni-whitelist/ssl_sni_whitelist.config @@ -0,0 +1,3 @@ +// +// Place holder + http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/example/ssl-sni/ssl-sni.cc ---------------------------------------------------------------------- diff --git a/example/ssl-sni/ssl-sni.cc b/example/ssl-sni/ssl-sni.cc new file mode 100644 index 0000000..d1aa856 --- /dev/null +++ b/example/ssl-sni/ssl-sni.cc @@ -0,0 +1,162 @@ +/** @file + SSL Preaccept test plugin + Implements blind tunneling based on the client IP address + The client ip addresses are specified in the plugin's + config file as an array of IP addresses or IP address ranges under the + key "client-blind-tunnel" +*/ + +# include <stdio.h> +# include <memory.h> +# include <inttypes.h> +# include <ts/ts.h> +# include <tsconfig/TsValue.h> +# include <alloca.h> +# include <openssl/ssl.h> + +using ts::config::Configuration; +using ts::config::Value; + +# define PN "ssl-sni-test" +# define PCP "[" PN " Plugin] " + +namespace { + +std::string ConfigPath; + +Configuration Config; // global configuration + +int +Load_Config_File() { + ts::Rv<Configuration> cv = Configuration::loadFromPath(ConfigPath.c_str()); + if (!cv.isOK()) { + TSError(PCP "Failed to parse %s as TSConfig format", ConfigPath.c_str()); + return -1; + } + Config = cv; + return 1; +} + +int +Load_Configuration(int argc, const char *argv[]) { +ts::ConstBuffer text; + std::string s; // temp holder. + TSMgmtString config_path = NULL; + + // get the path to the config file if one was specified + static char const * const CONFIG_ARG = "--config="; + int arg_idx; + for (arg_idx = 0; arg_idx < argc; arg_idx++) { + if (0 == memcmp(argv[arg_idx], CONFIG_ARG, strlen(CONFIG_ARG))) { + config_path = TSstrdup(argv[arg_idx] + strlen(CONFIG_ARG)); + TSDebug(PN, "Found config path %s", config_path); + } + } + if (NULL == config_path) { + static char const * const DEFAULT_CONFIG_PATH = "ssl_sni.config"; + config_path = TSstrdup(DEFAULT_CONFIG_PATH); + TSDebug(PN, "No config path set in arguments, using default: %s", DEFAULT_CONFIG_PATH); + } + + // translate relative paths to absolute + if (config_path[0] != '/') { + ConfigPath = std::string(TSConfigDirGet()) + '/' + std::string(config_path); + } else { + ConfigPath = config_path; + } + + // free up the path + TSfree(config_path); + + int ret = Load_Config_File(); + if (ret != 0) { + TSError(PCP "Failed to load the config file, check debug output for errata"); + } + + return 0; +} + +/** + Somewhat nonscensically exercise some scenarios of proxying + and blind tunneling from the SNI callback plugin + + Case 1: If the servername ends in facebook.com, blind tunnel + Case 2: If the servername is www.yahoo.com and there is a context + entry for "safelyfiled.com", use the "safelyfiled.com" context for + this connection. + */ +int +CB_servername(TSCont /* contp */, TSEvent /* event */, void *edata) { + TSVConn ssl_vc = reinterpret_cast<TSVConn>(edata); + TSSslConnection sslobj = TSVConnSSLConnectionGet(ssl_vc); + SSL *ssl = reinterpret_cast<SSL *>(sslobj); + const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + if (servername != NULL) { + int servername_len = strlen(servername); + int facebook_name_len = strlen("facebook.com"); + if (servername_len >= facebook_name_len) { + const char *server_ptr = servername + (servername_len - facebook_name_len); + if (strcmp(server_ptr, "facebook.com") == 0) { + TSDebug("skh", "Blind tunnel from SNI callback"); + TSVConnTunnel(ssl_vc); + // Don't reenable to ensure that we break out of the + // SSL handshake processing + return TS_SUCCESS; // Don't re-enable so we interrupt processing + } + } + // If the name is yahoo, look for a context for safelyfiled and use that here + if (strcmp("www.yahoo.com", servername) == 0) { + TSDebug("skh", "SNI name is yahoo ssl obj is %p", sslobj); + if (sslobj) { + TSSslContext ctxobj = TSSslContextFindByName("safelyfiled.com"); + if (ctxobj != NULL) { + TSDebug("skh", "Found cert for safelyfiled"); + SSL_CTX *ctx = reinterpret_cast<SSL_CTX *>(ctxobj); + SSL_set_SSL_CTX(ssl, ctx); + TSDebug("skh", "SNI plugin cb: replace SSL CTX"); + } + } + } + } + + + // All done, reactivate things + TSVConnReenable(ssl_vc); + return TS_SUCCESS; +} + +} // Anon namespace + +// Called by ATS as our initialization point +void +TSPluginInit(int argc, const char *argv[]) { + bool success = false; + TSPluginRegistrationInfo info; + TSCont cb_sni = 0; // sni callback continuation + + info.plugin_name = const_cast<char*>("SSL SNI callback test"); + info.vendor_name = const_cast<char*>("Network Geographics"); + info.support_email = const_cast<char*>("[email protected]"); + + if (TS_SUCCESS != TSPluginRegister(TS_SDK_VERSION_2_0, &info)) { + TSError(PCP "registration failed."); + } else if (TSTrafficServerVersionGetMajor() < 2) { + TSError(PCP "requires Traffic Server 2.0 or later."); + } else if (0 > Load_Configuration(argc, argv)) { + TSError(PCP "Failed to load config file."); + } else if (0 == (cb_sni = TSContCreate(&CB_servername, TSMutexCreate()))) { + TSError(PCP "Failed to create SNI callback."); + } else { + TSHttpHookAdd(TS_SSL_SNI_HOOK, cb_sni); + success = true; + } + + if (!success) { + if (cb_sni) TSContDestroy(cb_sni); + TSError(PCP "not initialized"); + } + TSDebug(PN, "Plugin %s", success ? "online" : "offline"); + + return; +} + http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/example/ssl-sni/ssl_sni.config ---------------------------------------------------------------------- diff --git a/example/ssl-sni/ssl_sni.config b/example/ssl-sni/ssl_sni.config new file mode 100644 index 0000000..2ec52ec --- /dev/null +++ b/example/ssl-sni/ssl_sni.config @@ -0,0 +1,7 @@ + +// SSL traffic initiating from these addresses should +// be placed into a blind tunnel. ATS should not inspect +// the tunnel +client-blind-tunnel = "192.168.56.145" + +//client-blind-tunnel = "192.168.56.0/24" http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/iocore/net/Makefile.am ---------------------------------------------------------------------- diff --git a/iocore/net/Makefile.am b/iocore/net/Makefile.am index 907be2e..0120528 100644 --- a/iocore/net/Makefile.am +++ b/iocore/net/Makefile.am @@ -26,7 +26,9 @@ AM_CPPFLAGS = \ -I$(top_srcdir)/proxy/hdrs \ -I$(top_srcdir)/proxy/shared \ -I$(top_srcdir)/mgmt \ - -I$(top_srcdir)/mgmt/utils + -I$(top_srcdir)/mgmt/utils \ + -I$(top_srcdir)/proxy/api/ts \ + -I$(top_srcdir)/proxy/http TESTS = $(check_PROGRAMS) http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/iocore/net/OCSPStapling.cc ---------------------------------------------------------------------- diff --git a/iocore/net/OCSPStapling.cc b/iocore/net/OCSPStapling.cc index 136c623..51212a7 100644 --- a/iocore/net/OCSPStapling.cc +++ b/iocore/net/OCSPStapling.cc @@ -363,20 +363,23 @@ ocsp_update() const unsigned ctxCount = certLookup->count(); for (unsigned i = 0; i < ctxCount; i++) { - ctx = certLookup->get(i); - cinf = stapling_get_cert_info(ctx); - if (cinf) { - ink_mutex_acquire(&cinf->stapling_mutex); - current_time = time(NULL); - if (cinf->resp_derlen == 0 || cinf->is_expire || cinf->expire_time < current_time) { - ink_mutex_release(&cinf->stapling_mutex); - if (stapling_refresh_response(cinf, &resp)) { - Note("Success to refresh OCSP response for 1 certificate."); - } else { - Note("Fail to refresh OCSP response for 1 certificate."); - } - } else { - ink_mutex_release(&cinf->stapling_mutex); + SSLCertContext *cc = certLookup->get(i); + if (cc && cc->ctx) { + ctx = cc->ctx; + cinf = stapling_get_cert_info(ctx); + if (cinf) { + ink_mutex_acquire(&cinf->stapling_mutex); + current_time = time(NULL); + if (cinf->resp_derlen == 0 || cinf->is_expire || cinf->expire_time < current_time) { + ink_mutex_release(&cinf->stapling_mutex); + if (stapling_refresh_response(cinf, &resp)) { + Note("Success to refresh OCSP response for 1 certificate."); + } else { + Note("Fail to refresh OCSP response for 1 certificate."); + } + } else { + ink_mutex_release(&cinf->stapling_mutex); + } } } } http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/iocore/net/P_SSLCertLookup.h ---------------------------------------------------------------------- diff --git a/iocore/net/P_SSLCertLookup.h b/iocore/net/P_SSLCertLookup.h index f2328ef..05d908c 100644 --- a/iocore/net/P_SSLCertLookup.h +++ b/iocore/net/P_SSLCertLookup.h @@ -30,21 +30,64 @@ struct SSLConfigParams; struct SSLContextStorage; +/** A certificate context. + + This holds data about a certificate and how it is used by the SSL logic. Current this is mainly + the openSSL certificate and an optional action, which in turn is limited to just tunneling. + + Instances are passed around and returned when matching connections to certificates. + + Instances of this class are stored on a list and then referenced via index in that list so that + there is exactly one place we can find all the @c SSL_CTX instances exactly once. + +*/ +struct SSLCertContext +{ + /** Special things to do instead of use a context. + In general an option will be associated with a @c NULL context because + the context is not used. + */ + enum Option { + OPT_NONE, ///< Nothing special. Implies valid context. + OPT_TUNNEL ///< Just tunnel, don't terminate. + }; + + SSLCertContext() : ctx(0), opt(OPT_NONE) {} + explicit SSLCertContext(SSL_CTX* c) : ctx(c), opt(OPT_NONE) {} + SSLCertContext(SSL_CTX* c, Option o) : ctx(c), opt(o) {} + + SSL_CTX* ctx; ///< openSSL context. + Option opt; ///< Special handling option. +}; + struct SSLCertLookup : public ConfigInfo { SSLContextStorage * ssl_storage; SSL_CTX * ssl_default; - bool insert(SSL_CTX * ctx, const char * name); - bool insert(SSL_CTX * ctx, const IpEndpoint& address); - SSL_CTX * findInfoInHash(const char * address) const; - SSL_CTX * findInfoInHash(const IpEndpoint& address) const; + int insert(const char *name, SSLCertContext const &cc); + int insert(const IpEndpoint& address, SSLCertContext const &cc); + + /** Find certificate context by IP address. + The IP addresses are taken from the socket @a s. + Exact matches have priority, then wildcards. The destination address is preferred to the source address. + @return @c A pointer to the matched context, @c NULL if no match is found. + */ + SSLCertContext* find(const IpEndpoint& address) const; + + /** Find certificate context by name (FQDN). + Exact matches have priority, then wildcards. Only destination based matches are checked. + @return @c A pointer to the matched context, @c NULL if no match is found. + */ + SSLCertContext* find(char const* name) const; + + // Return the last-resort default TLS context if there is no name or address match. SSL_CTX * defaultContext() const { return ssl_default; } unsigned count() const; - SSL_CTX * get(unsigned i) const; + SSLCertContext * get(unsigned i) const; SSLCertLookup(); virtual ~SSLCertLookup(); http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/iocore/net/P_SSLNetVConnection.h ---------------------------------------------------------------------- diff --git a/iocore/net/P_SSLNetVConnection.h b/iocore/net/P_SSLNetVConnection.h index c464e60..863b5da 100644 --- a/iocore/net/P_SSLNetVConnection.h +++ b/iocore/net/P_SSLNetVConnection.h @@ -36,6 +36,7 @@ #include "P_EventSystem.h" #include "P_UnixNetVConnection.h" #include "P_UnixNet.h" +#include "apidefs.h" #include <openssl/ssl.h> #include <openssl/err.h> @@ -52,6 +53,7 @@ #endif class SSLNextProtocolSet; +struct SSLCertLookup; ////////////////////////////////////////////////////////////////// // @@ -62,6 +64,7 @@ class SSLNextProtocolSet; ////////////////////////////////////////////////////////////////// class SSLNetVConnection:public UnixNetVConnection { + typedef UnixNetVConnection super; ///< Parent type. public: virtual int sslStartHandShake(int event, int &err); virtual void free(EThread * t); @@ -120,6 +123,36 @@ public: sslClientRenegotiationAbort = state; }; + /// Reenable the VC after a pre-accept or SNI hook is called. + virtual void reenable(NetHandler* nh); + /// Set the SSL context. + /// @note This must be called after the SSL endpoint has been created. + virtual bool sslContextSet(void* ctx); + + /// Set by asynchronous hooks to request a specific operation. + TSSslVConnOp hookOpRequested; + + // Store the servername returned by SNI + char sniServername[TS_MAX_HOST_NAME_LEN]; + + int64_t read_raw_data(); + void initialize_handshake_buffers() { + this->handShakeBuffer = new_MIOBuffer(); + this->handShakeReader = this->handShakeBuffer->alloc_reader(); + this->handShakeHolder = this->handShakeReader->clone(); + } + void free_handshake_buffers() { + + this->handShakeReader->dealloc(); + this->handShakeHolder->dealloc(); + free_MIOBuffer(this->handShakeBuffer); + this->handShakeReader = NULL; + this->handShakeHolder = NULL; + this->handShakeBuffer = NULL; + } + // Returns true if all the hooks reenabled + bool callHooks(TSHttpHookID eventId); + private: SSLNetVConnection(const SSLNetVConnection &); SSLNetVConnection & operator =(const SSLNetVConnection &); @@ -127,6 +160,29 @@ private: bool sslHandShakeComplete; bool sslClientConnection; bool sslClientRenegotiationAbort; + MIOBuffer *handShakeBuffer; + IOBufferReader *handShakeHolder; + IOBufferReader *handShakeReader; + + /// The current hook. + /// @note For @C SSL_HOOKS_INVOKE, this is the hook to invoke. + class APIHook* curHook; + + enum { + SSL_HOOKS_INIT, ///< Initial state, no hooks called yet. + SSL_HOOKS_INVOKE, ///< Waiting to invoke hook. + SSL_HOOKS_ACTIVE, ///< Hook invoked, waiting for it to complete. + SSL_HOOKS_CONTINUE, ///< All hooks have been called and completed + SSL_HOOKS_DONE ///< All hooks have been called and completed + } sslPreAcceptHookState; + + enum { + SNI_HOOKS_INIT, + SNI_HOOKS_ACTIVE, + SNI_HOOKS_DONE, + SNI_HOOKS_CONTINUE + } sslSNIHookState; + const SSLNextProtocolSet * npnSet; Continuation * npnEndpoint; }; http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/iocore/net/SSLCertLookup.cc ---------------------------------------------------------------------- diff --git a/iocore/net/SSLCertLookup.cc b/iocore/net/SSLCertLookup.cc index 253f9a2..19c22ca 100644 --- a/iocore/net/SSLCertLookup.cc +++ b/iocore/net/SSLCertLookup.cc @@ -70,28 +70,51 @@ private: struct SSLContextStorage { +public: SSLContextStorage(); ~SSLContextStorage(); - bool insert(SSL_CTX * ctx, const char * name); - SSL_CTX * lookup(const char * name) const; - unsigned count() const { return this->references.count(); } - SSL_CTX * get(unsigned i) const { return this->references[i]; } + /// Add a cert context to storage + /// @return The @a host_store index or -1 on error. + int insert(const char * name, SSLCertContext const& cc); + /// Add a cert context to storage. + /// @a idx must be a value returned by a previous call to insert. + /// This creates an alias, a different @a name referring to the same + /// cert context. + /// @return @a idx + int insert(const char * name, int idx); + SSLCertContext* lookup(const char * name) const; + unsigned count() const { return this->ctx_store.length(); } + SSLCertContext* get(unsigned i) const { return &this->ctx_store[i]; } private: - struct SSLEntry + /** A struct that can stored a @c Trie. + It contains the index of the real certificate and the + linkage required by @c Trie. + */ + struct ContextRef { - explicit SSLEntry(SSL_CTX * c) : ctx(c) {} - - void Print() const { Debug("ssl", "SSLEntry=%p SSL_CTX=%p", this, ctx); } - - SSL_CTX * ctx; - LINK(SSLEntry, link); + ContextRef(): idx(-1) {} + explicit ContextRef(int n) : idx(n) {} + void Print() const { Debug("ssl", "Item=%p SSL_CTX=#%d", this, idx); } + int idx; ///< Index in the context store. + LINK(ContextRef, link); ///< Require by @c Trie }; - Trie<SSLEntry> wildcards; + /// Items tored by wildcard name + Trie<ContextRef> wildcards; + /// Contexts store by IP address or FQDN InkHashTable * hostnames; - Vec<SSL_CTX *> references; + /// List for cleanup. + /// Exactly one pointer to each SSL context is stored here. + Vec<SSLCertContext> ctx_store; + + /// Add a context to the clean up list. + /// @return The index of the added context. + int store(SSLCertContext const& cc); + /// Remove last added context + void unstore(); + }; SSLCertLookup::SSLCertLookup() @@ -104,21 +127,21 @@ SSLCertLookup::~SSLCertLookup() delete this->ssl_storage; } -SSL_CTX * -SSLCertLookup::findInfoInHash(const char * address) const +SSLCertContext * +SSLCertLookup::find(const char * address) const { return this->ssl_storage->lookup(address); } -SSL_CTX * -SSLCertLookup::findInfoInHash(const IpEndpoint& address) const +SSLCertContext * +SSLCertLookup::find(const IpEndpoint& address) const { - SSL_CTX * ctx; + SSLCertContext * cc; SSLAddressLookupKey key(address); // First try the full address. - if ((ctx = this->ssl_storage->lookup(key.get()))) { - return ctx; + if ((cc = this->ssl_storage->lookup(key.get()))) { + return cc; } // If that failed, try the address without the port. @@ -130,17 +153,17 @@ SSLCertLookup::findInfoInHash(const IpEndpoint& address) const return NULL; } -bool -SSLCertLookup::insert(SSL_CTX * ctx, const char * name) +int +SSLCertLookup::insert(const char *name, SSLCertContext const &cc) { - return this->ssl_storage->insert(ctx, name); + return this->ssl_storage->insert(name, cc); } -bool -SSLCertLookup::insert(SSL_CTX * ctx, const IpEndpoint& address) +int +SSLCertLookup::insert(const IpEndpoint& address, SSLCertContext const &cc) { SSLAddressLookupKey key(address); - return this->ssl_storage->insert(ctx, key.get()); + return this->ssl_storage->insert(key.get(), cc); } unsigned @@ -149,7 +172,7 @@ SSLCertLookup::count() const return ssl_storage->count(); } -SSL_CTX * +SSLCertContext * SSLCertLookup::get(unsigned i) const { return ssl_storage->get(i); @@ -212,15 +235,38 @@ SSLContextStorage::SSLContextStorage() SSLContextStorage::~SSLContextStorage() { - for (unsigned i = 0; i < this->references.count(); ++i) { - SSLReleaseContext(this->references[i]); + for (unsigned i = 0; i < this->ctx_store.length(); ++i) { + SSLReleaseContext(this->ctx_store[i].ctx); } ink_hash_table_destroy(this->hostnames); } -bool -SSLContextStorage::insert(SSL_CTX * ctx, const char * name) +int +SSLContextStorage::store(SSLCertContext const& cc) +{ + int idx = this->ctx_store.length(); + this->ctx_store.add(cc); + return idx; +} + +void +SSLContextStorage::unstore() +{ + this->ctx_store.drop(); +} + +int +SSLContextStorage::insert(const char* name, SSLCertContext const& cc) +{ + int idx = this->store(cc); + idx = this->insert(name, idx); + if (idx < 0) this->unstore(); + return idx; +} + +int +SSLContextStorage::insert(const char* name, int idx) { ats_wildcard_matcher wildcard; bool inserted = false; @@ -230,70 +276,59 @@ SSLContextStorage::insert(SSL_CTX * ctx, const char * name) // so that we can do a longest match lookup. char namebuf[TS_MAX_HOST_NAME_LEN + 1]; char * reversed; - ats_scoped_obj<SSLEntry> entry; + ats_scoped_obj<ContextRef> ref; reversed = reverse_dns_name(name + 1, namebuf); if (!reversed) { Error("wildcard name '%s' is too long", name); - return false; + return -1; } - entry = new SSLEntry(ctx); - inserted = this->wildcards.Insert(reversed, entry, 0 /* rank */, -1 /* keylen */); + ref = new ContextRef(idx); + inserted = this->wildcards.Insert(reversed, ref, 0 /* rank */, -1 /* keylen */); if (!inserted) { - SSLEntry * found; + ContextRef * found; // We fail to insert, so the longest wildcard match search should return the full match value. found = this->wildcards.Search(reversed); - if (found != NULL && found->ctx != ctx) { - Warning("previously indexed wildcard certificate for '%s' as '%s', cannot index it with SSL_CTX %p now", - name, reversed, ctx); + if (found != NULL && found->idx != idx) { + Warning("previously indexed wildcard certificate for '%s' as '%s', cannot index it with SSL_CTX #%d now", + name, reversed, idx); } - - goto done; + idx = -1; } - Debug("ssl", "indexed wildcard certificate for '%s' as '%s' with SSL_CTX %p", name, reversed, ctx); - entry.release(); + Debug("ssl", "%s wildcard certificate for '%s' as '%s' with SSL_CTX %p [%d]", + idx >= 0 ? "index" : "failed to index", name, reversed, this->ctx_store[(*ref).idx].ctx, (*ref).idx); + ref.release(); } else { InkHashTableValue value; - if (ink_hash_table_lookup(this->hostnames, name, &value) && (void *)ctx != value) { - Warning("previously indexed '%s' with SSL_CTX %p, cannot index it with SSL_CTX %p now", name, value, ctx); - goto done; + if (ink_hash_table_lookup(this->hostnames, name, &value) && (void *)idx != value) { + Warning("previously indexed '%s' with SSL_CTX %p, cannot index it with SSL_CTX #%d now", name, value, idx); + } else { + inserted = true; + ink_hash_table_insert(this->hostnames, name, reinterpret_cast<void*>(static_cast<intptr_t>(idx))); + Debug("ssl", "indexed '%s' with SSL_CTX %p [%d]", + name, this->ctx_store[idx].ctx, idx); } - - inserted = true; - ink_hash_table_insert(this->hostnames, name, (void *)ctx); - Debug("ssl", "indexed '%s' with SSL_CTX %p", name, ctx); } - -done: - // Keep a unique reference to the SSL_CTX, so that we can free it later. Since we index by name, multiple - // certificates can be indexed for the same name. If this happens, we will overwrite the previous pointer - // and leak a context. So if we insert a certificate, keep an ownership reference to it. - if (inserted) { - if (this->references.in(ctx) == NULL) { - this->references.push_back(ctx); - } - } - - return inserted; + return idx; } -SSL_CTX * +SSLCertContext * SSLContextStorage::lookup(const char * name) const { InkHashTableValue value; if (ink_hash_table_lookup(const_cast<InkHashTable *>(this->hostnames), name, &value)) { - return (SSL_CTX *)value; + return &(this->ctx_store[reinterpret_cast<intptr_t>(value)]); } if (!this->wildcards.Empty()) { char namebuf[TS_MAX_HOST_NAME_LEN + 1]; char * reversed; - SSLEntry * entry; + ContextRef * ref; reversed = reverse_dns_name(name, namebuf); if (!reversed) { @@ -302,9 +337,9 @@ SSLContextStorage::lookup(const char * name) const } Debug("ssl", "attempting wildcard match for %s", reversed); - entry = this->wildcards.Search(reversed); - if (entry) { - return entry->ctx; + ref = this->wildcards.Search(reversed); + if (ref) { + return &(this->ctx_store[ref->idx]); } } http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/iocore/net/SSLNetVConnection.cc ---------------------------------------------------------------------- diff --git a/iocore/net/SSLNetVConnection.cc b/iocore/net/SSLNetVConnection.cc index 7978013..b4269e7 100644 --- a/iocore/net/SSLNetVConnection.cc +++ b/iocore/net/SSLNetVConnection.cc @@ -21,9 +21,11 @@ limitations under the License. */ #include "ink_config.h" +#include "records/I_RecHttp.h" #include "P_Net.h" #include "P_SSLNextProtocolSet.h" #include "P_SSLUtils.h" +#include "InkAPIInternal.h" // Added to include the ssl_hook definitions #define SSL_READ_ERROR_NONE 0 #define SSL_READ_ERROR 1 @@ -36,9 +38,87 @@ #define SSL_HANDSHAKE_WANT_ACCEPT 8 #define SSL_HANDSHAKE_WANT_CONNECT 9 #define SSL_WRITE_WOULD_BLOCK 10 +#define SSL_WAIT_FOR_HOOK 11 + +#ifndef UIO_MAXIOV +#define NET_MAX_IOV 16 // UIO_MAXIOV shall be at least 16 1003.1g (5.4.1.1) +#else +#define NET_MAX_IOV UIO_MAXIOV +#endif ClassAllocator<SSLNetVConnection> sslNetVCAllocator("sslNetVCAllocator"); +namespace { + /// Callback to get two locks. + /// The lock for this continuation, and for the target continuation. + class ContWrapper : public Continuation + { + public: + /** Constructor. + This takes the secondary @a mutex and the @a target continuation + to invoke, along with the arguments for that invocation. + */ + ContWrapper( + ProxyMutex* mutex ///< Mutex for this continuation (primary lock). + , Continuation* target ///< "Real" continuation we want to call. + , int eventId = EVENT_IMMEDIATE ///< Event ID for invocation of @a target. + , void* edata = 0 ///< Data for invocation of @a target. + ) + : Continuation(mutex) + , _target(target) + , _eventId(eventId) + , _edata(edata) + { + SET_HANDLER(&ContWrapper::event_handler); + } + + /// Required event handler method. + int event_handler(int, void*) + { + EThread* eth = this_ethread(); + + MUTEX_TRY_LOCK(lock, _target->mutex, eth); + if (lock) { // got the target lock, we can proceed. + _target->handleEvent(_eventId, _edata); + delete this; + } else { // can't get both locks, try again. + eventProcessor.schedule_imm(this, ET_NET); + } + return 0; + } + + /** Convenience static method. + + This lets a client make one call and not have to (accurately) + copy the invocation logic embedded here. We duplicate it near + by textually so it is easier to keep in sync. + + This takes the same arguments as the constructor but, if the + lock can be obtained immediately, does not construct an + instance but simply calls the @a target. + */ + static void wrap( + ProxyMutex* mutex ///< Mutex for this continuation (primary lock). + , Continuation* target ///< "Real" continuation we want to call. + , int eventId = EVENT_IMMEDIATE ///< Event ID for invocation of @a target. + , void* edata = 0 ///< Data for invocation of @a target. + ) { + EThread* eth = this_ethread(); + MUTEX_TRY_LOCK(lock, target->mutex, eth); + if (lock) { + target->handleEvent(eventId, edata); + } else { + eventProcessor.schedule_imm(new ContWrapper(mutex, target, eventId, edata), ET_NET); + } + } + + private: + Continuation* _target; ///< Continuation to invoke. + int _eventId; ///< with this event + void* _edata; ///< and this data + }; +} + // // Private // @@ -49,7 +129,19 @@ make_ssl_connection(SSL_CTX * ctx, SSLNetVConnection * netvc) SSL * ssl; if (likely(ssl = SSL_new(ctx))) { - SSL_set_fd(ssl, netvc->get_socket()); + netvc->ssl = ssl; + + // Only set up the bio stuff for the server side + if (netvc->getSSLClientConnection()) { + SSL_set_fd(ssl, netvc->get_socket()); + } else { + netvc->initialize_handshake_buffers(); + BIO *rbio = BIO_new(BIO_s_mem()); + BIO *wbio = BIO_new_fd(netvc->get_socket(), BIO_NOCLOSE); + BIO_set_mem_eof_return(wbio, -1); + SSL_set_bio(ssl, rbio, wbio); + } + SSL_set_app_data(ssl, netvc); } @@ -208,6 +300,99 @@ ssl_read_from_net(SSLNetVConnection * sslvc, EThread * lthread, int64_t &ret) } +/** + * Read from socket directly for handshake data. Store the data in + * a MIOBuffer. Place the data in the read BIO so the openssl library + * has access to it. + * If for some ready we much abort out of the handshake, we can replay + * the stored data (e.g. back out to blind tunneling) + */ +int64_t +SSLNetVConnection::read_raw_data() +{ + int64_t r = 0; + int64_t toread = INT_MAX; + + // read data + int64_t rattempted = 0, total_read = 0; + int niov = 0; + IOVec tiovec[NET_MAX_IOV]; + if (toread) { + IOBufferBlock *b = this->handShakeBuffer->first_write_block(); + do { + niov = 0; + rattempted = 0; + while (b && niov < NET_MAX_IOV) { + int64_t a = b->write_avail(); + if (a > 0) { + tiovec[niov].iov_base = b->_end; + int64_t togo = toread - total_read - rattempted; + if (a > togo) + a = togo; + tiovec[niov].iov_len = a; + rattempted += a; + niov++; + if (a >= togo) + break; + } + b = b->next; + } + + if (niov == 1) { + r = socketManager.read(this->con.fd, tiovec[0].iov_base, tiovec[0].iov_len); + } else { + r = socketManager.readv(this->con.fd, &tiovec[0], niov); + } + NET_DEBUG_COUNT_DYN_STAT(net_calls_to_read_stat, 1); + total_read += rattempted; + } while (rattempted && r == rattempted && total_read < toread); + + // if we have already moved some bytes successfully, summarize in r + if (total_read != rattempted) { + if (r <= 0) + r = total_read - rattempted; + else + r = total_read - rattempted + r; + } + // check for errors + if (r <= 0) { + + if (r == -EAGAIN || r == -ENOTCONN) { + NET_DEBUG_COUNT_DYN_STAT(net_calls_to_read_nodata_stat, 1); + return r; + } + + if (!r || r == -ECONNRESET) { + return r; + } + return r; + } + NET_SUM_DYN_STAT(net_read_bytes_stat, r); + + this->handShakeBuffer->fill(r); + + } else + r = 0; + + char *start = this->handShakeReader->start(); + char *end = this->handShakeReader->end(); + + // Sets up the buffer as a read only bio target + // Must be reset on each read + BIO *rbio = BIO_new_mem_buf(start, end - start); + BIO_set_mem_eof_return(rbio, -1); + // Assigning directly into the SSL structure + // is dirty, but there is no openssl function that only + // assigns the read bio. Originally I was getting and + // resetting the same write bio, but that caused the + // inserted buffer bios to be freed and then reinserted. + //BIO *wbio = SSL_get_wbio(this->ssl); + //SSL_set_bio(this->ssl, rbio, wbio); + this->ssl->rbio = rbio; + + return r; +} + //changed by YTS Team, yamsat void @@ -220,6 +405,11 @@ SSLNetVConnection::net_read_io(NetHandler *nh, EThread *lthread) MIOBufferAccessor &buf = s->vio.buffer; int64_t ntodo = s->vio.ntodo(); + if (HttpProxyPort::TRANSPORT_BLIND_TUNNEL == this->attributes) { + this->super::net_read_io(nh, lthread); + return; + } + if (sslClientRenegotiationAbort == true) { this->read.triggered = 0; readSignalError(nh, (int)r); @@ -246,37 +436,82 @@ SSLNetVConnection::net_read_io(NetHandler *nh, EThread *lthread) // vc is an SSLNetVConnection. if (!getSSLHandShakeComplete()) { int err; + int data_to_read = 0; + char *data_ptr = NULL; - if (getSSLClientConnection()) { - ret = sslStartHandShake(SSL_EVENT_CLIENT, err); - } else { - ret = sslStartHandShake(SSL_EVENT_SERVER, err); - } + // Not done handshaking, go into the SSL handshake logic again + if (!getSSLHandShakeComplete()) { - if (ret == EVENT_ERROR) { - this->read.triggered = 0; - readSignalError(nh, err); - } else if (ret == SSL_HANDSHAKE_WANT_READ || ret == SSL_HANDSHAKE_WANT_ACCEPT) { - read.triggered = 0; - nh->read_ready_list.remove(this); - readReschedule(nh); - } else if (ret == SSL_HANDSHAKE_WANT_CONNECT || ret == SSL_HANDSHAKE_WANT_WRITE) { - write.triggered = 0; - nh->write_ready_list.remove(this); - writeReschedule(nh); - } else if (ret == EVENT_DONE) { - // If this was driven by a zero length read, signal complete when - // the handshake is complete. Otherwise set up for continuing read - // operations. - if (ntodo <= 0) { - readSignalDone(VC_EVENT_READ_COMPLETE, nh); + if (getSSLClientConnection()) { + ret = sslStartHandShake(SSL_EVENT_CLIENT, err); } else { - read.triggered = 1; - if (read.enabled) - nh->read_ready_list.in_or_enqueue(this); + ret = sslStartHandShake(SSL_EVENT_SERVER, err); } - } else - readReschedule(nh); + // If we have flipped to blind tunnel, don't read ahead + if (this->handShakeReader) { + if (this->attributes != HttpProxyPort::TRANSPORT_BLIND_TUNNEL) { + // Check and consume data that has been read + int data_still_to_read = BIO_get_mem_data(SSL_get_rbio(this->ssl), &data_ptr); + data_to_read = this->handShakeReader->read_avail(); + this->handShakeReader->consume(data_to_read - data_still_to_read); + } + else { // Now in blind tunnel. Set things up to read what is in the buffer + this->readSignalDone(VC_EVENT_READ_COMPLETE, nh); + + // If the handshake isn't set yet, this means the tunnel + // decision was make in the SNI callback. We must move + // the client hello message back into the standard read.vio + // so it will get forwarded onto the origin server + if (!this->sslHandShakeComplete) { + // Kick things to get the http forwarding buffers set up + this->sslHandShakeComplete = 1; + // Copy over all data already read in during the SSL_accept + // (the client hello message) + NetState *s = &this->read; + MIOBufferAccessor &buf = s->vio.buffer; + int64_t r = buf.writer()->write(this->handShakeHolder); + s->vio.nbytes += r; + s->vio.ndone += r; + + // Clean up the handshake buffers + this->free_handshake_buffers(); + + // Kick things again, so the data that was copied into the + // vio.read buffer gets processed + this->readSignalDone(VC_EVENT_READ_COMPLETE, nh); + } + return; + } + } + + if (ret == EVENT_ERROR) { + this->read.triggered = 0; + readSignalError(nh, err); + } else if (ret == SSL_HANDSHAKE_WANT_READ || ret == SSL_HANDSHAKE_WANT_ACCEPT) { + read.triggered = 0; + nh->read_ready_list.remove(this); + readReschedule(nh); + } else if (ret == SSL_HANDSHAKE_WANT_CONNECT || ret == SSL_HANDSHAKE_WANT_WRITE) { + write.triggered = 0; + nh->write_ready_list.remove(this); + writeReschedule(nh); + } else if (ret == EVENT_DONE) { + // If this was driven by a zero length read, signal complete when + // the handshake is complete. Otherwise set up for continuing read + // operations. + if (ntodo <= 0) { + readSignalDone(VC_EVENT_READ_COMPLETE, nh); + } else { + read.triggered = 1; + if (read.enabled) + nh->read_ready_list.in_or_enqueue(this); + } + } else if (ret == SSL_WAIT_FOR_HOOK) { + // avoid readReschedule - done when the plugin calls us back to reenable + } else { + readReschedule(nh); + } + } return; } @@ -286,7 +521,43 @@ SSLNetVConnection::net_read_io(NetHandler *nh, EThread *lthread) return; } - // not sure if this do-while loop is really needed here, please replace this comment if you know + // At this point we are at the post-handshake SSL processing + // If the read BIO is not already a socket, consider changing it + if (this->handShakeReader) { + if (this->handShakeReader->read_avail() <= 0) { + // Switch the read bio over to a socket bio + SSL_set_rfd(this->ssl, this->get_socket()); + this->free_handshake_buffers(); + } + else { // There is still data in the buffer to drain + char *data_ptr = NULL; + int data_still_to_read = BIO_get_mem_data(SSL_get_rbio(this->ssl), &data_ptr); + if (data_still_to_read > 0) { + // Still data remaining in the current BIO block + } + else { + // reset the block + char *start = this->handShakeReader->start(); + char *end = this->handShakeReader->end(); + // Sets up the buffer as a read only bio target + // Must be reset on each read + BIO *rbio = BIO_new_mem_buf(start, end - start); + BIO_set_mem_eof_return(rbio, -1); + // So assigning directly into the SSL structure + // is dirty, but there is no openssl function that only + // assigns the read bio. Originally I was getting and + // resetting the same write bio, but that caused the + // inserted buffer bios to be freed and then reinserted. + this->ssl->rbio = rbio; + //BIO *wbio = SSL_get_wbio(this->ssl); + //SSL_set_bio(this->ssl, rbio, wbio); + } + } + } + // Otherwise, we already replaced the buffer bio with a socket bio + + // not sure if this do-while loop is really needed here, please replace + // this comment if you know do { ret = ssl_read_from_net(this, lthread, r); if (ret == SSL_READ_READY || ret == SSL_READ_ERROR_NONE) { @@ -370,6 +641,10 @@ SSLNetVConnection::load_buffer_and_write(int64_t towrite, int64_t &wattempted, i int64_t offset = buf.reader()->start_offset; IOBufferBlock *b = buf.reader()->block; + if (HttpProxyPort::TRANSPORT_BLIND_TUNNEL == this->attributes) { + return this->super::load_buffer_and_write(towrite, wattempted, total_wrote, buf, needs); + } + do { // check if we have done this block l = b->read_avail(); @@ -476,14 +751,21 @@ SSLNetVConnection::load_buffer_and_write(int64_t towrite, int64_t &wattempted, i } SSLNetVConnection::SSLNetVConnection(): + ssl(NULL), + sslHandshakeBeginTime(0), + hookOpRequested(TS_SSL_HOOK_OP_DEFAULT), sslHandShakeComplete(false), sslClientConnection(false), sslClientRenegotiationAbort(false), + handShakeBuffer(NULL), + handShakeHolder(NULL), + handShakeReader(NULL), + sslPreAcceptHookState(SSL_HOOKS_INIT), + sslSNIHookState(SNI_HOOKS_INIT), npnSet(NULL), npnEndpoint(NULL) { - ssl = NULL; - sslHandshakeBeginTime = 0; + sniServername[0] = '\0'; } void @@ -511,6 +793,12 @@ SSLNetVConnection::free(EThread * t) { sslHandShakeComplete = false; sslClientConnection = false; sslClientRenegotiationAbort = false; + if (SSL_HOOKS_ACTIVE == sslPreAcceptHookState) { + Error("SSLNetVconnection freed with outstanding hook"); + } + sslPreAcceptHookState = SSL_HOOKS_INIT; + curHook = 0; + hookOpRequested = TS_SSL_HOOK_OP_DEFAULT; npnSet = NULL; npnEndpoint= NULL; @@ -529,6 +817,33 @@ SSLNetVConnection::sslStartHandShake(int event, int &err) case SSL_EVENT_SERVER: if (this->ssl == NULL) { SSLCertificateConfig::scoped_config lookup; + IpEndpoint ip; + int namelen = sizeof(ip); + safe_getsockname(this->get_socket(), &ip.sa, &namelen); + SSLCertContext *cc = lookup->find(ip); + if (is_debug_tag_set("ssl")) { + IpEndpoint src, dst; + ip_port_text_buffer ipb1, ipb2; + int ip_len; + + safe_getsockname(this->get_socket(), &dst.sa, &(ip_len = sizeof ip)); + safe_getpeername(this->get_socket(), &src.sa, &(ip_len = sizeof ip)); + ats_ip_nptop(&dst, ipb1, sizeof(ipb1)); + ats_ip_nptop(&src, ipb2, sizeof(ipb2)); + Debug("ssl", "IP context is %p for [%s] -> [%s], default context %p", cc, ipb2, ipb1, lookup->defaultContext()); + } + + // Escape if this is marked to be a tunnel. + // No data has been read at this point, so we can go + // directly into blind tunnel mode + if (cc && SSLCertContext::OPT_TUNNEL == cc->opt && this->is_transparent) { + this->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL; + sslHandShakeComplete = 1; + SSL_free(this->ssl); + this->ssl = NULL; + return EVENT_DONE; + } + // Attach the default SSL_CTX to this SSL session. The default context is never going to be able // to negotiate a SSL session, but it's enough to trampoline us into the SNI callback where we @@ -565,7 +880,66 @@ SSLNetVConnection::sslStartHandShake(int event, int &err) int SSLNetVConnection::sslServerHandShakeEvent(int &err) { - int ret = SSL_accept(ssl); + int ret; + + if (SSL_HOOKS_DONE != sslPreAcceptHookState) { + // Get the first hook if we haven't started invoking yet. + if (SSL_HOOKS_INIT == sslPreAcceptHookState) { + curHook = ssl_hooks->get(TS_VCONN_PRE_ACCEPT_INTERNAL_HOOK); + sslPreAcceptHookState = SSL_HOOKS_INVOKE; + } else if (SSL_HOOKS_INVOKE == sslPreAcceptHookState) { + // if the state is anything else, we haven't finished + // the previous hook yet. + curHook = curHook->next(); + } + if (SSL_HOOKS_INVOKE == sslPreAcceptHookState) { + if (0 == curHook) { // no hooks left, we're done + sslPreAcceptHookState = SSL_HOOKS_DONE; + } else { + sslPreAcceptHookState = SSL_HOOKS_ACTIVE; + ContWrapper::wrap(mutex, curHook->m_cont, TS_EVENT_VCONN_PRE_ACCEPT, this); + return SSL_WAIT_FOR_HOOK; + } + } else { // waiting for hook to complete + /* A note on waiting for the hook. I believe that because this logic + cannot proceed as long as a hook is outstanding, the underlying VC + can't go stale. If that can happen for some reason, we'll need to be + more clever and provide some sort of cancel mechanism. I have a trap + in SSLNetVConnection::free to check for this. + */ + return SSL_WAIT_FOR_HOOK; + } + } + + // If a blind tunnel was requested in the pre-accept calls, convert. + // Again no data has been exchanged, so we can go directly + // without data replay. + // Note we can't arrive here if a hook is active. + if (TS_SSL_HOOK_OP_TUNNEL == hookOpRequested) { + this->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL; + SSL_free(this->ssl); + this->ssl = NULL; + sslHandShakeComplete = 1; + return EVENT_DONE; + } else if (TS_SSL_HOOK_OP_TERMINATE == hookOpRequested) { + sslHandShakeComplete = 1; + return EVENT_DONE; + } + + // All the pre-accept hooks have completed, proceed with the actual accept. + + char *data_ptr = NULL; + int data_to_read = BIO_get_mem_data(SSL_get_rbio(this->ssl), &data_ptr); + if (data_to_read <= 0) { // If there is not already data in the buffer + // Read from socket to fill in the BIO buffer with the + // raw handshake data before calling the ssl accept calls. + int64_t data_read; + if ((data_read = this->read_raw_data()) > 0) { + data_to_read = BIO_get_mem_data(SSL_get_rbio(this->ssl), &data_ptr); + } + } + + ret = SSL_accept(ssl); int ssl_error = SSL_get_error(ssl, ret); if (ssl_error != SSL_ERROR_NONE) { @@ -644,6 +1018,23 @@ SSLNetVConnection::sslServerHandShakeEvent(int &err) case SSL_ERROR_WANT_READ: return SSL_HANDSHAKE_WANT_READ; +// This value is only defined in openssl has been patched to +// enable the sni callback to break out of the SSL_accept processing +#ifdef SSL_ERROR_WANT_SNI_RESOLVE + case SSL_ERROR_WANT_SNI_RESOLVE: + if (this->attributes == HttpProxyPort::TRANSPORT_BLIND_TUNNEL || + TS_SSL_HOOK_OP_TUNNEL == hookOpRequested) { + this->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL; + sslHandShakeComplete = 0; + return EVENT_CONT; + } + else { + // Stopping for some other reason, perhaps loading certificate + //;return EVENT_ERROR; + return EVENT_CONT; + } +#endif + case SSL_ERROR_WANT_ACCEPT: case SSL_ERROR_WANT_X509_LOOKUP: return EVENT_CONT; @@ -673,7 +1064,7 @@ SSLNetVConnection::sslClientHandShakeEvent(int &err) } } #endif - + ret = SSL_connect(ssl); switch (SSL_get_error(ssl, ret)) { case SSL_ERROR_NONE: @@ -794,3 +1185,54 @@ SSLNetVConnection::select_next_protocol(SSL * ssl, const unsigned char ** out, u *outlen = 0; return SSL_TLSEXT_ERR_NOACK; } + +void +SSLNetVConnection::reenable(NetHandler* nh) { + if (this->sslPreAcceptHookState != SSL_HOOKS_DONE) { + this->sslPreAcceptHookState = SSL_HOOKS_INVOKE; + this->readReschedule(nh); + } else { + // Reenabling from the SNI callback + this->sslSNIHookState = SNI_HOOKS_CONTINUE; + } +} + + +bool +SSLNetVConnection::sslContextSet(void* ctx) { + bool zret = true; + if (ssl) + SSL_set_SSL_CTX(ssl, static_cast<SSL_CTX*>(ctx)); + else + zret = false; + return zret; +} + +bool +SSLNetVConnection::callHooks(TSHttpHookID eventId) +{ + // Only dealing with the SNI hook so far + ink_assert(eventId == TS_SSL_SNI_HOOK); + + APIHook *hook = ssl_hooks->get(TS_SSL_SNI_INTERNAL_HOOK); + bool reenabled = true; + while (hook && reenabled) { + // Must reset to a completed state for each invocation + this->sslSNIHookState = SNI_HOOKS_DONE; + + // Invoke the hook + hook->invoke(TS_SSL_SNI_HOOK, this); + + // If it did not re-enable, return the code to + // stop the accept processing + if (this->sslSNIHookState == SNI_HOOKS_DONE) { + reenabled = false; + } + + // Otherwise, look for the next plugin code + hook = hook->next(); + } + return reenabled; +} + + http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/iocore/net/SSLUtils.cc ---------------------------------------------------------------------- diff --git a/iocore/net/SSLUtils.cc b/iocore/net/SSLUtils.cc index 9ebdf2e..8a144cd 100644 --- a/iocore/net/SSLUtils.cc +++ b/iocore/net/SSLUtils.cc @@ -20,6 +20,7 @@ */ #include "ink_config.h" +#include "records/I_RecHttp.h" #include "libts.h" #include "I_Layout.h" #include "P_Net.h" @@ -57,6 +58,8 @@ #define SSL_CERT_TAG "ssl_cert_name" #define SSL_PRIVATE_KEY_TAG "ssl_key_name" #define SSL_CA_TAG "ssl_ca_name" +#define SSL_ACTION_TAG "action" +#define SSL_ACTION_TUNNEL_TAG "tunnel" #define SSL_SESSION_TICKET_ENABLED "ssl_ticket_enabled" #define SSL_SESSION_TICKET_KEY_FILE_TAG "ticket_key_name" #define SSL_KEY_DIALOG "ssl_key_dialog" @@ -84,7 +87,7 @@ typedef SSL_METHOD * ink_ssl_method_t; // gather user provided settings from ssl_multicert.config in to a single struct struct ssl_user_config { - ssl_user_config () : session_ticket_enabled(1) { + ssl_user_config () : session_ticket_enabled(1), opt(SSLCertContext::OPT_NONE) { } int session_ticket_enabled; // ssl_ticket_enabled - session ticket enabled @@ -95,6 +98,7 @@ struct ssl_user_config ats_scoped_str key; // ssl_key_name - Private key ats_scoped_str ticket_key_filename; // ticket_key_name - session key file. [key_name (16Byte) + HMAC_secret (16Byte) + AES_key (16Byte)] ats_scoped_str dialog; // ssl_key_dialog - Private key dialog + SSLCertContext::Option opt; }; // Check if the ticket_key callback #define is available, and if so, enable session tickets. @@ -173,27 +177,51 @@ SSL_CTX_add_extra_chain_cert_file(SSL_CTX * ctx, const char * chainfile) #if TS_USE_TLS_SNI static int -ssl_servername_callback(SSL * ssl, int * ad, void * arg) +ssl_servername_callback(SSL * ssl, int * ad, void * /*arg*/) { SSL_CTX * ctx = NULL; - SSLCertLookup * lookup = (SSLCertLookup *) arg; + SSLCertContext * cc = NULL; + // Fetching the lookup via the callback arg allows for race + // condition with reconfigure + //SSLCertLookup * lookup = (SSLCertLookup *) arg; + SSLCertLookup *lookup = SSLCertificateConfig::acquire(); const char * servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); SSLNetVConnection * netvc = (SSLNetVConnection *)SSL_get_app_data(ssl); + bool found = true; + bool reenabled; + int retval = SSL_TLSEXT_ERR_OK; - Debug("ssl", "ssl_servername_callback ssl=%p ad=%d lookup=%p server=%s handshake_complete=%d", ssl, *ad, lookup, servername, + Debug("ssl", "ssl_servername_callback ssl=%p ad=%d server=%s handshake_complete=%d", ssl, *ad, servername, netvc->getSSLHandShakeComplete()); + + if (servername != NULL) { + strncpy(netvc->sniServername, servername, TS_MAX_HOST_NAME_LEN); + } // catch the client renegotiation early on if (SSLConfigParams::ssl_allow_client_renegotiation == false && netvc->getSSLHandShakeComplete()) { Debug("ssl", "ssl_servername_callback trying to renegotiate from the client"); - return SSL_TLSEXT_ERR_ALERT_FATAL; + retval = SSL_TLSEXT_ERR_ALERT_FATAL; + goto done; } // 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 = lookup->findInfoInHash((char *)servername); + cc = lookup->find((char *)servername); + if (cc && cc->ctx) ctx = cc->ctx; + if (cc && SSLCertContext::OPT_TUNNEL == cc->opt && netvc->get_is_transparent()) { +#ifdef SSL_TLSEXT_ERR_READ_AGAIN + netvc->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL; + netvc->setSSLHandShakeComplete(true); + retval = SSL_TLSEXT_ERR_READ_AGAIN; +#else + Error("Must have openssl patch to support OPT_TUNNEL from SNI callback"); + retval = SSL_TLSEXT_ERR_ALERT_FATAL; +#endif + goto done; + } } // If there's no match on the server name, try to match on the peer address. @@ -202,36 +230,60 @@ ssl_servername_callback(SSL * ssl, int * ad, void * arg) int namelen = sizeof(ip); safe_getsockname(netvc->get_socket(), &ip.sa, &namelen); - ctx = lookup->findInfoInHash(ip); + cc = lookup->find(ip); + if (cc && cc->ctx) ctx = cc->ctx; } if (ctx != NULL) { SSL_set_SSL_CTX(ssl, ctx); } + else { + found = false; + } ctx = SSL_get_SSL_CTX(ssl); - Debug("ssl", "ssl_servername_callback found SSL context %p for requested name '%s'", ctx, servername); + Debug("ssl", "ssl_servername_callback %s SSL context %p for requested name '%s'", found ? "found" : "using", ctx, servername); if (ctx == NULL) { - return SSL_TLSEXT_ERR_NOACK; + retval = SSL_TLSEXT_ERR_NOACK; + goto done; + } + + // Call the plugin SNI code + reenabled = netvc->callHooks(TS_SSL_SNI_HOOK); + // If it did not re-enable, return the code to + // stop the accept processing + if (!reenabled){ +#ifdef SSL_TLSEXT_ERR_READ_AGAIN + retval = SSL_TLSEXT_ERR_READ_AGAIN; +#else + Error("Must have openssl patch to support OPT_TUNNEL from SNI callback"); + retval = SSL_TLSEXT_ERR_ALERT_FATAL; +#endif + goto done; } +done: + SSLCertificateConfig::release(lookup); // We need to return one of the SSL_TLSEXT_ERR constants. If we return an // error, we can fill in *ad with an alert code to propgate to the // client, see SSL_AD_*. - return SSL_TLSEXT_ERR_OK; + return retval; } #endif /* TS_USE_TLS_SNI */ static SSL_CTX * -ssl_context_enable_sni(SSL_CTX * ctx, SSLCertLookup * lookup) +ssl_context_enable_sni(SSL_CTX * ctx, SSLCertLookup * /*lookup*/) { #if TS_USE_TLS_SNI if (ctx) { Debug("ssl", "setting SNI callbacks with for ctx %p", ctx); SSL_CTX_set_tlsext_servername_callback(ctx, ssl_servername_callback); - SSL_CTX_set_tlsext_servername_arg(ctx, lookup); + // Possible race conditions against reconfigure here + // Better to use the SSLCertificate.acquire function to access the + // lookup data structure safely + //SSL_CTX_set_tlsext_servername_arg(ctx, lookup); } #else (void)lookup; @@ -487,12 +539,13 @@ SSLRecRawStatSyncCount(const char *name, RecDataT data_type, RecData *data, RecR if (certLookup) { const unsigned ctxCount = certLookup->count(); for (size_t i = 0; i < ctxCount; i++) { - SSL_CTX * ctx = certLookup->get(i); - - sessions += SSL_CTX_sess_accept_good(ctx); - hits += SSL_CTX_sess_hits(ctx); - misses += SSL_CTX_sess_misses(ctx); - timeouts += SSL_CTX_sess_timeouts(ctx); + SSLCertContext *cc = certLookup->get(i); + if (cc && cc->ctx) { + sessions += SSL_CTX_sess_accept_good(cc->ctx); + hits += SSL_CTX_sess_hits(cc->ctx); + misses += SSL_CTX_sess_misses(cc->ctx); + timeouts += SSL_CTX_sess_timeouts(cc->ctx); + } } } @@ -1224,7 +1277,7 @@ asn1_strdup(ASN1_STRING * s) // table aliases for subject CN and subjectAltNames DNS without wildcard, // insert trie aliases for those with wildcard. static void -ssl_index_certificate(SSLCertLookup * lookup, SSL_CTX * ctx, const char * certfile) +ssl_index_certificate(SSLCertLookup * lookup, SSLCertContext const& cc, const char * certfile) { X509_NAME * subject = NULL; X509 * cert; @@ -1250,7 +1303,7 @@ ssl_index_certificate(SSLCertLookup * lookup, SSL_CTX * ctx, const char * certfi ats_scoped_str name(asn1_strdup(cn)); Debug("ssl", "mapping '%s' to certificate %s", (const char *) name, certfile); - lookup->insert(ctx, name); + lookup->insert(name, cc); } } @@ -1266,7 +1319,7 @@ ssl_index_certificate(SSLCertLookup * lookup, SSL_CTX * ctx, const char * certfi if (name->type == GEN_DNS) { ats_scoped_str dns(asn1_strdup(name->d.dNSName)); Debug("ssl", "mapping '%s' to certificate %s", (const char *) dns, certfile); - lookup->insert(ctx, dns); + lookup->insert(dns, cc); } } @@ -1339,13 +1392,13 @@ ssl_store_ssl_context( if (sslMultCertSettings.addr) { if (strcmp(sslMultCertSettings.addr, "*") == 0) { lookup->ssl_default = ctx; - lookup->insert(ctx, sslMultCertSettings.addr); + lookup->insert(sslMultCertSettings.addr, SSLCertContext(ctx, sslMultCertSettings.opt)); } else { IpEndpoint ep; if (ats_ip_pton(sslMultCertSettings.addr, &ep) == 0) { Debug("ssl", "mapping '%s' to certificate %s", (const char *)sslMultCertSettings.addr, (const char *)certpath); - lookup->insert(ctx, ep); + lookup->insert(ep, SSLCertContext(ctx, sslMultCertSettings.opt)); } else { Error("'%s' is not a valid IPv4 or IPv6 address", (const char *)sslMultCertSettings.addr); } @@ -1386,7 +1439,7 @@ ssl_store_ssl_context( // this code is updated to reconfigure the SSL certificates, it will need some sort of // refcounting or alternate way of avoiding double frees. Debug("ssl", "importing SNI names from %s", (const char *)certpath); - ssl_index_certificate(lookup, ctx, certpath); + ssl_index_certificate(lookup, SSLCertContext(ctx, sslMultCertSettings.opt), certpath); if (SSLConfigParams::init_ssl_ctx_cb) { SSLConfigParams::init_ssl_ctx_cb(ctx, true); @@ -1439,6 +1492,15 @@ ssl_extract_certificate( if (strcasecmp(label, SSL_KEY_DIALOG) == 0) { sslMultCertSettings.dialog = ats_strdup(value); } + if (strcasecmp(label, SSL_ACTION_TAG) == 0) { + if (strcasecmp(SSL_ACTION_TUNNEL_TAG, value) == 0) { + sslMultCertSettings.opt = SSLCertContext::OPT_TUNNEL; + } + else { + Error("Unrecognized action for " SSL_ACTION_TAG); + return false; + } + } } if (!sslMultCertSettings.cert) { Error("missing %s tag", SSL_CERT_TAG); http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/iocore/net/UnixNetVConnection.cc ---------------------------------------------------------------------- diff --git a/iocore/net/UnixNetVConnection.cc b/iocore/net/UnixNetVConnection.cc index ec8605c..35db956 100644 --- a/iocore/net/UnixNetVConnection.cc +++ b/iocore/net/UnixNetVConnection.cc @@ -384,7 +384,9 @@ write_to_net_io(NetHandler *nh, UnixNetVConnection *vc, EThread *thread) nh->read_ready_list.remove(vc); vc->write.triggered = 0; nh->write_ready_list.remove(vc); - if (!(ret == SSL_HANDSHAKE_WANT_READ || ret == SSL_HANDSHAKE_WANT_ACCEPT)) + if (ret == SSL_HANDSHAKE_WANT_READ || ret == SSL_HANDSHAKE_WANT_ACCEPT) + read_reschedule(nh, vc); + else write_reschedule(nh, vc); } else if (ret == EVENT_DONE) { vc->write.triggered = 1; @@ -497,7 +499,6 @@ write_to_net_io(NetHandler *nh, UnixNetVConnection *vc, EThread *thread) } else if (signalled && (wbe_event != vc->write_buffer_empty_event)) { // @a signalled means we won't send an event, and the event values differing means we // had a write buffer trap and cleared it, so we need to send it now. - Debug("amc", "empty write buffer trap [%d]", wbe_event); if (write_signal_and_update(wbe_event, vc) != EVENT_CONT) return; } else if (!signalled) { @@ -1187,6 +1188,7 @@ UnixNetVConnection::free(EThread *t) action_.mutex.clear(); got_remote_addr = 0; got_local_addr = 0; + attributes = 0; read.vio.mutex.clear(); write.vio.mutex.clear(); flags = 0; http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/lib/records/I_RecHttp.h ---------------------------------------------------------------------- diff --git a/lib/records/I_RecHttp.h b/lib/records/I_RecHttp.h index c121dfe..32a9f2d 100644 --- a/lib/records/I_RecHttp.h +++ b/lib/records/I_RecHttp.h @@ -181,11 +181,12 @@ public: /// Type of transport on the connection. enum TransportType { - TRANSPORT_DEFAULT = 0, ///< Default (normal HTTP). - TRANSPORT_COMPRESSED, ///< Compressed HTTP. + TRANSPORT_NONE = 0, ///< Unspecified / uninitialized + TRANSPORT_DEFAULT, ///< Default (normal HTTP). + TRANSPORT_COMPRESSED, ///< Compressed HTTP. TRANSPORT_BLIND_TUNNEL, ///< Blind tunnel (no processing). - TRANSPORT_SSL, ///< SSL connection. - TRANSPORT_PLUGIN /// < Protocol plugin connection + TRANSPORT_SSL, ///< SSL connection. + TRANSPORT_PLUGIN /// < Protocol plugin connection }; int m_fd; ///< Pre-opened file descriptor if present. http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/lib/ts/Vec.h ---------------------------------------------------------------------- diff --git a/lib/ts/Vec.h b/lib/ts/Vec.h index 589da0e..9dcf933 100644 --- a/lib/ts/Vec.h +++ b/lib/ts/Vec.h @@ -61,6 +61,7 @@ class Vec { void push_back(C a) { add(a); } // std::vector name bool add_exclusive(C a); C& add(); + void drop(); C pop(); void reset(); void clear(); @@ -211,6 +212,12 @@ Vec<C,A,S>::add() { return *ret; } +template <class C, class A, int S> inline void +Vec<C,A,S>::drop() { + if (n && 0 == --n) + clear(); +} + template <class C, class A, int S> inline C Vec<C,A,S>::pop() { if (!n) http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/lib/ts/apidefs.h.in ---------------------------------------------------------------------- diff --git a/lib/ts/apidefs.h.in b/lib/ts/apidefs.h.in index 96896f6..2b62427 100644 --- a/lib/ts/apidefs.h.in +++ b/lib/ts/apidefs.h.in @@ -255,6 +255,10 @@ extern "C" The two transform hooks can ONLY be added as transaction hooks. + TS_VCONN_PRE_ACCEPT_HOOK - Called before the SSL hand + shake starts. No handshake data has been read or sent (from the + proxy) at this point + TS_HTTP_LAST_HOOK _must_ be the last element. Only right place to insert a new element is just before TS_HTTP_LAST_HOOK. @@ -278,6 +282,12 @@ extern "C" TS_HTTP_PRE_REMAP_HOOK, TS_HTTP_POST_REMAP_HOOK, TS_HTTP_RESPONSE_CLIENT_HOOK, + // Putting the SSL hooks in the same enum space + // So both sets of hooks can be set by the same Hook function + TS_SSL_FIRST_HOOK, + TS_VCONN_PRE_ACCEPT_HOOK = TS_SSL_FIRST_HOOK, + TS_SSL_SNI_HOOK, + TS_SSL_LAST_HOOK = TS_SSL_SNI_HOOK, TS_HTTP_LAST_HOOK } TSHttpHookID; #define TS_HTTP_READ_REQUEST_PRE_REMAP_HOOK TS_HTTP_PRE_REMAP_HOOK /* backwards compat */ @@ -437,6 +447,7 @@ extern "C" TS_EVENT_LIFECYCLE_CACHE_READY = 60020, TS_EVENT_LIFECYCLE_SERVER_SSL_CTX_INITIALIZED = 60021, TS_EVENT_LIFECYCLE_CLIENT_SSL_CTX_INITIALIZED = 60022, + TS_EVENT_VCONN_PRE_ACCEPT = 60023, TS_EVENT_MGMT_UPDATE = 60100, /* EVENTS 60200 - 60202 for internal use */ @@ -781,6 +792,13 @@ extern "C" TS_MILESTONE_LAST_ENTRY } TSMilestonesType; + /// Operation requested by asynchronous hooks during SSL processing + typedef enum { + TS_SSL_HOOK_OP_DEFAULT, ///< Null / initialization value. Do normal processing. + TS_SSL_HOOK_OP_TUNNEL, ///< Switch to blind tunnel + TS_SSL_HOOK_OP_TERMINATE, ///< Termination connection / transaction. + TS_SSL_HOOK_OP_LAST = TS_SSL_HOOK_OP_TERMINATE ///< End marker value. + } TSSslVConnOp; /* These typedefs are used with the corresponding TSMgmt*Get functions for storing the values retrieved by those functions. For example, @@ -796,6 +814,7 @@ extern "C" typedef struct tsapi_mbuffer* TSMBuffer; typedef struct tsapi_httpssn* TSHttpSsn; typedef struct tsapi_httptxn* TSHttpTxn; + typedef struct tsapi_ssl_obj* TSSslConnection; typedef struct tsapi_httpaltinfo* TSHttpAltInfo; typedef struct tsapi_mimeparser* TSMimeParser; typedef struct tsapi_httpparser* TSHttpParser; @@ -810,6 +829,7 @@ extern "C" typedef struct tsapi_config* TSConfig; typedef struct tsapi_cont* TSCont; typedef struct tsapi_cont* TSVConn; /* a VConn is really a specialized TSCont */ + typedef struct tsapi_ssl_context* TSSslContext; typedef struct tsapi_action* TSAction; typedef struct tsapi_iobuffer* TSIOBuffer; typedef struct tsapi_iobufferdata* TSIOBufferData; http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/lib/ts/ink_sock.cc ---------------------------------------------------------------------- diff --git a/lib/ts/ink_sock.cc b/lib/ts/ink_sock.cc index 7429e53..055cd38 100644 --- a/lib/ts/ink_sock.cc +++ b/lib/ts/ink_sock.cc @@ -192,6 +192,16 @@ safe_getsockname(int s, struct sockaddr *name, int *namelen) return r; } +int +safe_getpeername(int s, struct sockaddr *name, int *namelen) +{ + int r; + do { + r = getpeername(s, name, (socklen_t*)namelen); + } while (r < 0 && (errno == EAGAIN || errno == EINTR)); + return r; +} + char fd_read_char(int fd) { http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/lib/ts/ink_sock.h ---------------------------------------------------------------------- diff --git a/lib/ts/ink_sock.h b/lib/ts/ink_sock.h index 39488d9..0f6660d 100644 --- a/lib/ts/ink_sock.h +++ b/lib/ts/ink_sock.h @@ -40,6 +40,7 @@ int safe_getsockopt(int s, int level, int optname, char *optval, int *optlevel); int safe_bind(int s, struct sockaddr const* name, int namelen); int safe_listen(int s, int backlog); int safe_getsockname(int s, struct sockaddr *name, int *namelen); +int safe_getpeername(int s, struct sockaddr *name, int *namelen); int safe_fcntl(int fd, int cmd, int arg); int safe_ioctl(int fd, int request, char *arg); http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/plugins/experimental/Makefile.am ---------------------------------------------------------------------- diff --git a/plugins/experimental/Makefile.am b/plugins/experimental/Makefile.am index 7e5ab51..7bb1452 100644 --- a/plugins/experimental/Makefile.am +++ b/plugins/experimental/Makefile.am @@ -32,6 +32,7 @@ SUBDIRS = \ regex_revalidate \ remap_stats \ s3_auth \ + ssl_cert_loader \ sslheaders \ stale_while_revalidate \ url_sig \ http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/plugins/experimental/ssl_cert_loader/Makefile.am ---------------------------------------------------------------------- diff --git a/plugins/experimental/ssl_cert_loader/Makefile.am b/plugins/experimental/ssl_cert_loader/Makefile.am new file mode 100644 index 0000000..def7a7f --- /dev/null +++ b/plugins/experimental/ssl_cert_loader/Makefile.am @@ -0,0 +1,26 @@ +# 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 $(top_srcdir)/build/plugins.mk + +AM_CPPFLAGS += -I$(top_srcdir)/lib -I$(top_srcdir)/lib/ts + +pkglib_LTLIBRARIES = ssl_cert_loader.la + +ssl_cert_loader_la_SOURCES = ssl-cert-loader.cc ip.cc IpMap.cc domain-tree.cc +ssl_cert_loader_la_LDFLAGS = $(TS_PLUGIN_LDFLAGS) -L$(top_srcdir)/lib/tsconfig/.libs -ltsconfig + + http://git-wip-us.apache.org/repos/asf/trafficserver/blob/044da699/plugins/experimental/ssl_cert_loader/ats-util.h ---------------------------------------------------------------------- diff --git a/plugins/experimental/ssl_cert_loader/ats-util.h b/plugins/experimental/ssl_cert_loader/ats-util.h new file mode 100644 index 0000000..1164a20 --- /dev/null +++ b/plugins/experimental/ssl_cert_loader/ats-util.h @@ -0,0 +1,40 @@ +# if !defined(_ats_util_h) +# define _ats_util_h + +# if defined(__cplusplus) +/** Set data to zero. + + Calls @c memset on @a t with a value of zero and a length of @c + sizeof(t). This can be used on ordinary and array variables. While + this can be used on variables of intrinsic type it's inefficient. + + @note Because this uses templates it cannot be used on unnamed or + locally scoped structures / classes. This is an inherent + limitation of templates. + + Examples: + @code + foo bar; // value. + ink_zero(bar); // zero bar. + + foo *bar; // pointer. + ink_zero(bar); // WRONG - makes the pointer @a bar zero. + ink_zero(*bar); // zero what bar points at. + + foo bar[ZOMG]; // Array of structs. + ink_zero(bar); // Zero all structs in array. + + foo *bar[ZOMG]; // array of pointers. + ink_zero(bar); // zero all pointers in the array. + @endcode + + */ +template < typename T > inline void +ink_zero( + T& t ///< Object to zero. + ) { + memset(&t, 0, sizeof(t)); +} +# endif /* __cplusplus */ + +# endif // ats-util.h
