diff options
Diffstat (limited to 'modules/tls')
-rw-r--r-- | modules/tls/Makefile.in | 20 | ||||
-rw-r--r-- | modules/tls/config2.m4 | 173 | ||||
-rw-r--r-- | modules/tls/mod_tls.c | 288 | ||||
-rw-r--r-- | modules/tls/mod_tls.h | 19 | ||||
-rw-r--r-- | modules/tls/tls_cache.c | 310 | ||||
-rw-r--r-- | modules/tls/tls_cache.h | 63 | ||||
-rw-r--r-- | modules/tls/tls_cert.c | 564 | ||||
-rw-r--r-- | modules/tls/tls_cert.h | 211 | ||||
-rw-r--r-- | modules/tls/tls_conf.c | 780 | ||||
-rw-r--r-- | modules/tls/tls_conf.h | 185 | ||||
-rw-r--r-- | modules/tls/tls_core.c | 1433 | ||||
-rw-r--r-- | modules/tls/tls_core.h | 184 | ||||
-rw-r--r-- | modules/tls/tls_filter.c | 1017 | ||||
-rw-r--r-- | modules/tls/tls_filter.h | 90 | ||||
-rw-r--r-- | modules/tls/tls_ocsp.c | 120 | ||||
-rw-r--r-- | modules/tls/tls_ocsp.h | 47 | ||||
-rw-r--r-- | modules/tls/tls_proto.c | 603 | ||||
-rw-r--r-- | modules/tls/tls_proto.h | 124 | ||||
-rw-r--r-- | modules/tls/tls_util.c | 367 | ||||
-rw-r--r-- | modules/tls/tls_util.h | 157 | ||||
-rw-r--r-- | modules/tls/tls_var.c | 397 | ||||
-rw-r--r-- | modules/tls/tls_var.h | 39 | ||||
-rw-r--r-- | modules/tls/tls_version.h | 39 |
23 files changed, 7230 insertions, 0 deletions
diff --git a/modules/tls/Makefile.in b/modules/tls/Makefile.in new file mode 100644 index 0000000..4395bc3 --- /dev/null +++ b/modules/tls/Makefile.in @@ -0,0 +1,20 @@ +# 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. + +# +# standard stuff +# + +include $(top_srcdir)/build/special.mk diff --git a/modules/tls/config2.m4 b/modules/tls/config2.m4 new file mode 100644 index 0000000..8a32490 --- /dev/null +++ b/modules/tls/config2.m4 @@ -0,0 +1,173 @@ +dnl Licensed to the Apache Software Foundation (ASF) under one or more +dnl contributor license agreements. See the NOTICE file distributed with +dnl this work for additional information regarding copyright ownership. +dnl The ASF licenses this file to You under the Apache License, Version 2.0 +dnl (the "License"); you may not use this file except in compliance with +dnl the License. You may obtain a copy of the License at +dnl +dnl http://www.apache.org/licenses/LICENSE-2.0 +dnl +dnl Unless required by applicable law or agreed to in writing, software +dnl distributed under the License is distributed on an "AS IS" BASIS, +dnl WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +dnl See the License for the specific language governing permissions and +dnl limitations under the License. + +dnl # start of module specific part +APACHE_MODPATH_INIT(tls) + +dnl # list of module object files +tls_objs="dnl +mod_tls.lo dnl +tls_cache.lo dnl +tls_cert.lo dnl +tls_conf.lo dnl +tls_core.lo dnl +tls_filter.lo dnl +tls_ocsp.lo dnl +tls_proto.lo dnl +tls_util.lo dnl +tls_var.lo dnl +" + +dnl +dnl APACHE_CHECK_TLS +dnl +dnl Configure for rustls, giving preference to +dnl "--with-rustls=<path>" if it was specified. +dnl +AC_DEFUN([APACHE_CHECK_RUSTLS],[ + AC_CACHE_CHECK([for rustls], [ac_cv_rustls], [ + dnl initialise the variables we use + ac_cv_rustls=no + ap_rustls_found="" + ap_rustls_base="" + ap_rustls_libs="" + + dnl Determine the rustls base directory, if any + AC_MSG_CHECKING([for user-provided rustls base directory]) + AC_ARG_WITH(rustls, APACHE_HELP_STRING(--with-rustls=PATH, rustls installation directory), [ + dnl If --with-rustls specifies a directory, we use that directory + if test "x$withval" != "xyes" -a "x$withval" != "x"; then + dnl This ensures $withval is actually a directory and that it is absolute + ap_rustls_base="`cd $withval ; pwd`" + fi + ]) + if test "x$ap_rustls_base" = "x"; then + AC_MSG_RESULT(none) + else + AC_MSG_RESULT($ap_rustls_base) + fi + + dnl Run header and version checks + saved_CPPFLAGS="$CPPFLAGS" + saved_LIBS="$LIBS" + saved_LDFLAGS="$LDFLAGS" + + dnl Before doing anything else, load in pkg-config variables + if test -n "$PKGCONFIG"; then + saved_PKG_CONFIG_PATH="$PKG_CONFIG_PATH" + AC_MSG_CHECKING([for pkg-config along $PKG_CONFIG_PATH]) + if test "x$ap_rustls_base" != "x" ; then + if test -f "${ap_rustls_base}/lib/pkgconfig/librustls.pc"; then + dnl Ensure that the given path is used by pkg-config too, otherwise + dnl the system librustls.pc might be picked up instead. + PKG_CONFIG_PATH="${ap_rustls_base}/lib/pkgconfig${PKG_CONFIG_PATH+:}${PKG_CONFIG_PATH}" + export PKG_CONFIG_PATH + elif test -f "${ap_rustls_base}/lib64/pkgconfig/librustls.pc"; then + dnl Ensure that the given path is used by pkg-config too, otherwise + dnl the system librustls.pc might be picked up instead. + PKG_CONFIG_PATH="${ap_rustls_base}/lib64/pkgconfig${PKG_CONFIG_PATH+:}${PKG_CONFIG_PATH}" + export PKG_CONFIG_PATH + fi + fi + ap_rustls_libs="`$PKGCONFIG $PKGCONFIG_LIBOPTS --libs-only-l --silence-errors librustls`" + if test $? -eq 0; then + ap_rustls_found="yes" + pkglookup="`$PKGCONFIG --cflags-only-I librustls`" + APR_ADDTO(CPPFLAGS, [$pkglookup]) + APR_ADDTO(MOD_CFLAGS, [$pkglookup]) + pkglookup="`$PKGCONFIG $PKGCONFIG_LIBOPTS --libs-only-L librustls`" + APR_ADDTO(LDFLAGS, [$pkglookup]) + APR_ADDTO(MOD_LDFLAGS, [$pkglookup]) + pkglookup="`$PKGCONFIG $PKGCONFIG_LIBOPTS --libs-only-other librustls`" + APR_ADDTO(LDFLAGS, [$pkglookup]) + APR_ADDTO(MOD_LDFLAGS, [$pkglookup]) + fi + PKG_CONFIG_PATH="$saved_PKG_CONFIG_PATH" + fi + + dnl fall back to the user-supplied directory if not found via pkg-config + if test "x$ap_rustls_base" != "x" -a "x$ap_rustls_found" = "x"; then + APR_ADDTO(CPPFLAGS, [-I$ap_rustls_base/include]) + APR_ADDTO(MOD_CFLAGS, [-I$ap_rustls_base/include]) + APR_ADDTO(LDFLAGS, [-L$ap_rustls_base/lib]) + APR_ADDTO(MOD_LDFLAGS, [-L$ap_rustls_base/lib]) + if test "x$ap_platform_runtime_link_flag" != "x"; then + APR_ADDTO(LDFLAGS, [$ap_platform_runtime_link_flag$ap_rustls_base/lib]) + APR_ADDTO(MOD_LDFLAGS, [$ap_platform_runtime_link_flag$ap_rustls_base/lib]) + fi + fi + + AC_MSG_CHECKING([for rustls version >= 0.9.2]) + AC_TRY_COMPILE([#include <rustls.h>],[ +rustls_version(); +rustls_acceptor_new(); +], + [AC_MSG_RESULT(OK) + ac_cv_rustls=yes], + [AC_MSG_RESULT(FAILED)]) + + dnl restore + CPPFLAGS="$saved_CPPFLAGS" + LIBS="$saved_LIBS" + LDFLAGS="$saved_LDFLAGS" + ]) + if test "x$ac_cv_rustls" = "xyes"; then + AC_DEFINE(HAVE_RUSTLS, 1, [Define if rustls is available]) + fi +]) + + +dnl # hook module into the Autoconf mechanism (--enable-http2) +APACHE_MODULE(tls, [TLS protocol handling using rustls. Implemented by mod_tls. +This module requires a librustls installation. +See --with-rustls on how to manage non-standard locations. This module +is usually linked shared and requires loading. ], $tls_objs, , most, [ + APACHE_CHECK_RUSTLS + if test "$ac_cv_rustls" = "yes" ; then + if test "x$enable_tls" = "xshared"; then + case `uname` in + "Darwin") + MOD_TLS_LINK_LIBS="-lrustls -framework Security -framework Foundation" + ;; + *) + MOD_TLS_LINK_LIBS="-lrustls" + ;; + esac + + # Some rustls versions need an extra -lm when linked + # See https://github.com/rustls/rustls-ffi/issues/133 + rustls_version=`rustc --version` + case "$rustls_version" in + *1.55*) need_lm="yes" ;; + *1.56*) need_lm="yes" ;; + *1.57*) need_lm="yes" ;; + esac + if test "$need_lm" = "yes" ; then + MOD_TLS_LINK_LIBS="$MOD_TLS_LINK_LIBS -lm" + fi + + # The only symbol which needs to be exported is the module + # structure, so ask libtool to hide everything else: + APR_ADDTO(MOD_TLS_LDADD, [$MOD_TLS_LINK_LIBS -export-symbols-regex tls_module]) + fi + else + enable_tls=no + fi +]) + + +dnl # end of module specific part +APACHE_MODPATH_FINISH + diff --git a/modules/tls/mod_tls.c b/modules/tls/mod_tls.c new file mode 100644 index 0000000..9d79521 --- /dev/null +++ b/modules/tls/mod_tls.c @@ -0,0 +1,288 @@ +/* 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 <assert.h> +#include <apr_optional.h> +#include <apr_strings.h> + +#include <mpm_common.h> +#include <httpd.h> +#include <http_core.h> +#include <http_connection.h> +#include <http_log.h> +#include <http_protocol.h> +#include <http_ssl.h> +#include <http_request.h> +#include <ap_socache.h> + +#include <rustls.h> + +#include "mod_tls.h" +#include "tls_conf.h" +#include "tls_core.h" +#include "tls_cache.h" +#include "tls_proto.h" +#include "tls_filter.h" +#include "tls_var.h" +#include "tls_version.h" + +#include "mod_proxy.h" + +static void tls_hooks(apr_pool_t *pool); + +AP_DECLARE_MODULE(tls) = { + STANDARD20_MODULE_STUFF, + tls_conf_create_dir, /* create per dir config */ + tls_conf_merge_dir, /* merge per dir config */ + tls_conf_create_svr, /* create per server config */ + tls_conf_merge_svr, /* merge per server config (inheritance) */ + tls_conf_cmds, /* command handlers */ + tls_hooks, +#if defined(AP_MODULE_FLAG_NONE) + AP_MODULE_FLAG_ALWAYS_MERGE +#endif +}; + +static const char* crustls_version(apr_pool_t *p) +{ + struct rustls_str rversion; + + rversion = rustls_version(); + return apr_pstrndup(p, rversion.data, rversion.len); +} + +static int tls_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp) +{ + tls_proto_pre_config(pconf, ptemp); + tls_cache_pre_config(pconf, plog, ptemp); + return OK; +} + +static apr_status_t tls_post_config(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + const char *tls_init_key = "mod_tls_init_counter"; + tls_conf_server_t *sc; + void *data = NULL; + + (void)plog; + sc = tls_conf_server_get(s); + assert(sc); + assert(sc->global); + sc->global->module_version = "mod_tls/" MOD_TLS_VERSION; + sc->global->crustls_version = crustls_version(p); + + apr_pool_userdata_get(&data, tls_init_key, s->process->pool); + if (data == NULL) { + /* At the first start, httpd makes a config check dry run + * to see if the config is ok in principle. + */ + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, "post config dry run"); + apr_pool_userdata_set((const void *)1, tls_init_key, + apr_pool_cleanup_null, s->process->pool); + } + else { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(10365) + "%s (%s), initializing...", + sc->global->module_version, + sc->global->crustls_version); + } + + return tls_core_init(p, ptemp, s); +} + +static apr_status_t tls_post_proxy_config( + apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) +{ + tls_conf_server_t *sc = tls_conf_server_get(s); + (void)plog; + sc->global->mod_proxy_post_config_done = 1; + return tls_core_init(p, ptemp, s); +} + +#if AP_MODULE_MAGIC_AT_LEAST(20120211, 109) +static int tls_ssl_outgoing(conn_rec *c, ap_conf_vector_t *dir_conf, int enable_ssl) +{ + /* we are not handling proxy connections - for now */ + tls_core_conn_bind(c, dir_conf); + if (enable_ssl && tls_core_setup_outgoing(c) == OK) { + ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, c->base_server, + "accepted ssl_bind_outgoing(enable=%d) for %s", + enable_ssl, c->base_server->server_hostname); + return OK; + } + tls_core_conn_disable(c); + ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, c->base_server, + "declined ssl_bind_outgoing(enable=%d) for %s", + enable_ssl, c->base_server->server_hostname); + return DECLINED; +} + +#else /* #if AP_MODULE_MAGIC_AT_LEAST(20120211, 109) */ + +APR_DECLARE_OPTIONAL_FN(int, ssl_proxy_enable, (conn_rec *)); +APR_DECLARE_OPTIONAL_FN(int, ssl_engine_disable, (conn_rec *)); +APR_DECLARE_OPTIONAL_FN(int, ssl_engine_set, (conn_rec *, + ap_conf_vector_t *, + int proxy, int enable)); +static APR_OPTIONAL_FN_TYPE(ssl_engine_set) *module_ssl_engine_set; + +static int ssl_engine_set( + conn_rec *c, ap_conf_vector_t *dir_conf, int proxy, int enable) +{ + ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, c->base_server, + "ssl_engine_set(proxy=%d, enable=%d) for %s", + proxy, enable, c->base_server->server_hostname); + tls_core_conn_bind(c, dir_conf); + if (enable && tls_core_setup_outgoing(c) == OK) { + if (module_ssl_engine_set) { + module_ssl_engine_set(c, dir_conf, proxy, 0); + } + return 1; + } + if (proxy || !enable) { + /* we are not handling proxy connections - for now */ + tls_core_conn_disable(c); + } + if (module_ssl_engine_set) { + return module_ssl_engine_set(c, dir_conf, proxy, enable); + } + return 0; +} + +static int ssl_proxy_enable(conn_rec *c) +{ + return ssl_engine_set(c, NULL, 1, 1); +} + +static int ssl_engine_disable(conn_rec *c) +{ + return ssl_engine_set(c, NULL, 0, 0); +} + +static apr_status_t tls_post_config_proxy_ssl( + apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) +{ + if (1) { + const char *tls_init_key = "mod_tls_proxy_ssl_counter"; + void *data = NULL; + APR_OPTIONAL_FN_TYPE(ssl_engine_set) *fn_ssl_engine_set; + + (void)p; + (void)plog; + (void)ptemp; + apr_pool_userdata_get(&data, tls_init_key, s->process->pool); + if (data == NULL) { + /* At the first start, httpd makes a config check dry run + * to see if the config is ok in principle. + */ + apr_pool_userdata_set((const void *)1, tls_init_key, + apr_pool_cleanup_null, s->process->pool); + return APR_SUCCESS; + } + + /* mod_ssl (if so loaded, has registered its optional functions. + * When mod_proxy runs in post-config, it looks up those functions and uses + * them to manipulate SSL status for backend connections. + * We provide our own implementations to avoid becoming active on such + * connections for now. + * */ + fn_ssl_engine_set = APR_RETRIEVE_OPTIONAL_FN(ssl_engine_set); + module_ssl_engine_set = (fn_ssl_engine_set + && fn_ssl_engine_set != ssl_engine_set)? fn_ssl_engine_set : NULL; + APR_REGISTER_OPTIONAL_FN(ssl_engine_set); + APR_REGISTER_OPTIONAL_FN(ssl_proxy_enable); + APR_REGISTER_OPTIONAL_FN(ssl_engine_disable); + } + return APR_SUCCESS; +} +#endif /* #if AP_MODULE_MAGIC_AT_LEAST(20120211, 109) */ + +static void tls_init_child(apr_pool_t *p, server_rec *s) +{ + tls_cache_init_child(p, s); +} + +static int hook_pre_connection(conn_rec *c, void *csd) +{ + (void)csd; /* mpm specific socket data, not used */ + + /* are we on a primary connection? */ + if (c->master) return DECLINED; + + /* Decide connection TLS stats and install our + * input/output filters for handling TLS/application data + * if enabled. + */ + return tls_filter_pre_conn_init(c); +} + +static int hook_connection(conn_rec* c) +{ + tls_filter_conn_init(c); + /* we do *not* take over. we are not processing requests. */ + return DECLINED; +} + +static const char *tls_hook_http_scheme(const request_rec *r) +{ + return (tls_conn_check_ssl(r->connection) == OK)? "https" : NULL; +} + +static apr_port_t tls_hook_default_port(const request_rec *r) +{ + return (tls_conn_check_ssl(r->connection) == OK) ? 443 : 0; +} + +static const char* const mod_http2[] = { "mod_http2.c", NULL}; + +static void tls_hooks(apr_pool_t *pool) +{ + /* If our request check denies further processing, certain things + * need to be in place for the response to be correctly generated. */ + static const char *dep_req_check[] = { "mod_setenvif.c", NULL }; + static const char *dep_proxy[] = { "mod_proxy.c", NULL }; + + ap_log_perror(APLOG_MARK, APLOG_TRACE1, 0, pool, "installing hooks"); + tls_filter_register(pool); + + ap_hook_pre_config(tls_pre_config, NULL,NULL, APR_HOOK_MIDDLE); + /* run post-config hooks one before, one after mod_proxy, as the + * mod_proxy's own one calls us in its "section_post_config" hook. */ + ap_hook_post_config(tls_post_config, NULL, dep_proxy, APR_HOOK_MIDDLE); + APR_OPTIONAL_HOOK(proxy, section_post_config, + tls_proxy_section_post_config, NULL, NULL, + APR_HOOK_MIDDLE); + ap_hook_post_config(tls_post_proxy_config, dep_proxy, NULL, APR_HOOK_MIDDLE); + ap_hook_child_init(tls_init_child, NULL,NULL, APR_HOOK_MIDDLE); + /* connection things */ + ap_hook_pre_connection(hook_pre_connection, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_process_connection(hook_connection, NULL, mod_http2, APR_HOOK_MIDDLE); + /* request things */ + ap_hook_default_port(tls_hook_default_port, NULL,NULL, APR_HOOK_MIDDLE); + ap_hook_http_scheme(tls_hook_http_scheme, NULL,NULL, APR_HOOK_MIDDLE); + ap_hook_post_read_request(tls_core_request_check, dep_req_check, NULL, APR_HOOK_MIDDLE); + ap_hook_fixups(tls_var_request_fixup, NULL,NULL, APR_HOOK_MIDDLE); + + ap_hook_ssl_conn_is_ssl(tls_conn_check_ssl, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_ssl_var_lookup(tls_var_lookup, NULL, NULL, APR_HOOK_MIDDLE); + +#if AP_MODULE_MAGIC_AT_LEAST(20120211, 109) + ap_hook_ssl_bind_outgoing(tls_ssl_outgoing, NULL, NULL, APR_HOOK_MIDDLE); +#else + ap_hook_post_config(tls_post_config_proxy_ssl, NULL, dep_proxy, APR_HOOK_MIDDLE); +#endif + +} diff --git a/modules/tls/mod_tls.h b/modules/tls/mod_tls.h new file mode 100644 index 0000000..db7dc41 --- /dev/null +++ b/modules/tls/mod_tls.h @@ -0,0 +1,19 @@ +/* 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. + */ +#ifndef mod_tls_h +#define mod_tls_h + +#endif /* mod_tls_h */
\ No newline at end of file diff --git a/modules/tls/tls_cache.c b/modules/tls/tls_cache.c new file mode 100644 index 0000000..de4be18 --- /dev/null +++ b/modules/tls/tls_cache.c @@ -0,0 +1,310 @@ +/* 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 <assert.h> +#include <apr_lib.h> +#include <apr_strings.h> +#include <apr_hash.h> + +#include <httpd.h> +#include <http_connection.h> +#include <http_log.h> +#include <ap_socache.h> +#include <util_mutex.h> + +#include <rustls.h> + +#include "tls_conf.h" +#include "tls_core.h" +#include "tls_cache.h" + +extern module AP_MODULE_DECLARE_DATA tls_module; +APLOG_USE_MODULE(tls); + +#define TLS_CACHE_DEF_PROVIDER "shmcb" +#define TLS_CACHE_DEF_DIR "tls" +#define TLS_CACHE_DEF_FILE "session_cache" +#define TLS_CACHE_DEF_SIZE 512000 + +static const char *cache_provider_unknown(const char *name, apr_pool_t *p) +{ + apr_array_header_t *known; + const char *known_names; + + known = ap_list_provider_names(p, AP_SOCACHE_PROVIDER_GROUP, + AP_SOCACHE_PROVIDER_VERSION); + known_names = apr_array_pstrcat(p, known, ','); + return apr_psprintf(p, "cache type '%s' not supported " + "(known names: %s). Maybe you need to load the " + "appropriate socache module (mod_socache_%s?).", + name, known_names, name); +} + +void tls_cache_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp) +{ + (void)plog; + (void)ptemp; + /* we make this visible, in case someone wants to configure it. + * this does not mean that we will really use it, which is determined + * by configuration and cache provider capabilities. */ + ap_mutex_register(pconf, TLS_SESSION_CACHE_MUTEX_TYPE, NULL, APR_LOCK_DEFAULT, 0); +} + +static const char *cache_init(tls_conf_global_t *gconf, apr_pool_t *p, apr_pool_t *ptemp) +{ + const char *err = NULL; + const char *name, *args = NULL; + apr_status_t rv; + + if (gconf->session_cache) { + goto cleanup; + } + else if (!apr_strnatcasecmp("none", gconf->session_cache_spec)) { + gconf->session_cache_provider = NULL; + gconf->session_cache = NULL; + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, gconf->ap_server, APLOGNO(10346) + "session cache explicitly disabled"); + goto cleanup; + } + else if (!apr_strnatcasecmp("default", gconf->session_cache_spec)) { + const char *path = TLS_CACHE_DEF_DIR; + +#if AP_MODULE_MAGIC_AT_LEAST(20180906, 2) + path = ap_state_dir_relative(p, path); +#endif + gconf->session_cache_spec = apr_psprintf(p, "%s:%s/%s(%ld)", + TLS_CACHE_DEF_PROVIDER, path, TLS_CACHE_DEF_FILE, (long)TLS_CACHE_DEF_SIZE); + gconf->session_cache_spec = "shmcb:mod_tls-sesss(64000)"; + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, gconf->ap_server, APLOGNO(10347) + "Using session cache: %s", gconf->session_cache_spec); + name = gconf->session_cache_spec; + args = ap_strchr((char*)name, ':'); + if (args) { + name = apr_pstrmemdup(p, name, (apr_size_t)(args - name)); + ++args; + } + gconf->session_cache_provider = ap_lookup_provider(AP_SOCACHE_PROVIDER_GROUP, + name, AP_SOCACHE_PROVIDER_VERSION); + if (!gconf->session_cache_provider) { + err = cache_provider_unknown(name, p); + goto cleanup; + } + err = gconf->session_cache_provider->create(&gconf->session_cache, args, ptemp, p); + if (err != NULL) goto cleanup; + + if (gconf->session_cache_provider->flags & AP_SOCACHE_FLAG_NOTMPSAFE + && !gconf->session_cache_mutex) { + /* we need a global lock to access the cache */ + rv = ap_global_mutex_create(&gconf->session_cache_mutex, NULL, + TLS_SESSION_CACHE_MUTEX_TYPE, NULL, gconf->ap_server, p, 0); + if (APR_SUCCESS != rv) { + err = apr_psprintf(p, "error setting up global %s mutex: %d", + TLS_SESSION_CACHE_MUTEX_TYPE, rv); + gconf->session_cache_mutex = NULL; + goto cleanup; + } + } + +cleanup: + if (NULL != err) { + gconf->session_cache_provider = NULL; + gconf->session_cache = NULL; + } + return err; +} + +const char *tls_cache_set_specification( + const char *spec, tls_conf_global_t *gconf, apr_pool_t *p, apr_pool_t *ptemp) +{ + gconf->session_cache_spec = spec; + return cache_init(gconf, p, ptemp); +} + +apr_status_t tls_cache_post_config(apr_pool_t *p, apr_pool_t *ptemp, server_rec *s) +{ + tls_conf_server_t *sc = tls_conf_server_get(s); + const char *err; + apr_status_t rv = APR_SUCCESS; + + err = cache_init(sc->global, p, ptemp); + if (err) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(10348) + "session cache [%s] could not be initialized, will continue " + "without session one. Since this will impact performance, " + "consider making use of the 'TLSSessionCache' directive. The " + "error was: %s", sc->global->session_cache_spec, err); + } + + if (sc->global->session_cache) { + struct ap_socache_hints hints; + + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, "provider init session cache [%s]", + sc->global->session_cache_spec); + memset(&hints, 0, sizeof(hints)); + hints.avg_obj_size = 100; + hints.avg_id_len = 33; + hints.expiry_interval = 30; + + rv = sc->global->session_cache_provider->init( + sc->global->session_cache, "mod_tls-sess", &hints, s, p); + if (APR_SUCCESS != rv) { + ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10349) + "error initializing session cache."); + } + } + return rv; +} + +void tls_cache_init_child(apr_pool_t *p, server_rec *s) +{ + tls_conf_server_t *sc = tls_conf_server_get(s); + const char *lockfile; + apr_status_t rv; + + if (sc->global->session_cache_mutex) { + lockfile = apr_global_mutex_lockfile(sc->global->session_cache_mutex); + rv = apr_global_mutex_child_init(&sc->global->session_cache_mutex, lockfile, p); + if (APR_SUCCESS != rv) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10350) + "Cannot reinit %s mutex (file `%s`)", + TLS_SESSION_CACHE_MUTEX_TYPE, lockfile? lockfile : "-"); + } + } +} + +void tls_cache_free(server_rec *s) +{ + tls_conf_server_t *sc = tls_conf_server_get(s); + if (sc->global->session_cache_provider) { + sc->global->session_cache_provider->destroy(sc->global->session_cache, s); + } +} + +static void tls_cache_lock(tls_conf_global_t *gconf) +{ + if (gconf->session_cache_mutex) { + apr_status_t rv = apr_global_mutex_lock(gconf->session_cache_mutex); + if (APR_SUCCESS != rv) { + ap_log_error(APLOG_MARK, APLOG_WARNING, rv, gconf->ap_server, APLOGNO(10351) + "Failed to acquire TLS session cache lock"); + } + } +} + +static void tls_cache_unlock(tls_conf_global_t *gconf) +{ + if (gconf->session_cache_mutex) { + apr_status_t rv = apr_global_mutex_unlock(gconf->session_cache_mutex); + if (APR_SUCCESS != rv) { + ap_log_error(APLOG_MARK, APLOG_WARNING, rv, gconf->ap_server, APLOGNO(10352) + "Failed to release TLS session cache lock"); + } + } +} + +static rustls_result tls_cache_get( + void *userdata, + const rustls_slice_bytes *key, + int remove_after, + unsigned char *buf, + size_t count, + size_t *out_n) +{ + conn_rec *c = userdata; + tls_conf_conn_t *cc = tls_conf_conn_get(c); + tls_conf_server_t *sc = tls_conf_server_get(cc->server); + apr_status_t rv = APR_ENOENT; + unsigned int vlen, klen; + const unsigned char *kdata; + + if (!sc->global->session_cache) goto not_found; + tls_cache_lock(sc->global); + + kdata = key->data; + klen = (unsigned int)key->len; + vlen = (unsigned int)count; + rv = sc->global->session_cache_provider->retrieve( + sc->global->session_cache, cc->server, kdata, klen, buf, &vlen, c->pool); + + if (APLOGctrace4(c)) { + apr_ssize_t n = klen; + ap_log_cerror(APLOG_MARK, APLOG_TRACE4, rv, c, "retrieve key %d[%8x], found %d val", + klen, apr_hashfunc_default((const char*)kdata, &n), vlen); + } + if (remove_after || (APR_SUCCESS != rv && !APR_STATUS_IS_NOTFOUND(rv))) { + sc->global->session_cache_provider->remove( + sc->global->session_cache, cc->server, key->data, klen, c->pool); + } + + tls_cache_unlock(sc->global); + if (APR_SUCCESS != rv) goto not_found; + cc->session_id_cache_hit = 1; + *out_n = count; + return RUSTLS_RESULT_OK; + +not_found: + *out_n = 0; + return RUSTLS_RESULT_NOT_FOUND; +} + +static rustls_result tls_cache_put( + void *userdata, + const rustls_slice_bytes *key, + const rustls_slice_bytes *val) +{ + conn_rec *c = userdata; + tls_conf_conn_t *cc = tls_conf_conn_get(c); + tls_conf_server_t *sc = tls_conf_server_get(cc->server); + apr_status_t rv = APR_ENOENT; + apr_time_t expires_at; + unsigned int klen, vlen; + const unsigned char *kdata; + + if (!sc->global->session_cache) goto not_stored; + tls_cache_lock(sc->global); + + expires_at = apr_time_now() + apr_time_from_sec(300); + kdata = key->data; + klen = (unsigned int)key->len; + vlen = (unsigned int)val->len; + rv = sc->global->session_cache_provider->store(sc->global->session_cache, cc->server, + kdata, klen, expires_at, + (unsigned char*)val->data, vlen, c->pool); + if (APLOGctrace4(c)) { + ap_log_cerror(APLOG_MARK, APLOG_TRACE4, rv, c, + "stored %d key bytes, with %d val bytes", klen, vlen); + } + tls_cache_unlock(sc->global); + if (APR_SUCCESS != rv) goto not_stored; + return RUSTLS_RESULT_OK; + +not_stored: + return RUSTLS_RESULT_NOT_FOUND; +} + +apr_status_t tls_cache_init_server( + rustls_server_config_builder *builder, server_rec *s) +{ + tls_conf_server_t *sc = tls_conf_server_get(s); + + if (sc && sc->global->session_cache) { + ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s, "adding session persistence to rustls"); + rustls_server_config_builder_set_persistence( + builder, tls_cache_get, tls_cache_put); + } + return APR_SUCCESS; +} diff --git a/modules/tls/tls_cache.h b/modules/tls/tls_cache.h new file mode 100644 index 0000000..64ca077 --- /dev/null +++ b/modules/tls/tls_cache.h @@ -0,0 +1,63 @@ +/* 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. + */ +#ifndef tls_cache_h +#define tls_cache_h + +/* name of the global session cache mutex, should we need it */ +#define TLS_SESSION_CACHE_MUTEX_TYPE "tls-session-cache" + + +/** + * Set the specification of the session cache to use. The syntax is + * "default|none|<provider_name>(:<arguments>)?" + * + * @param spec the cache specification + * @param gconf the modules global configuration + * @param p pool for permanent allocations + * @param ptemp pool for temporary allocations + * @return NULL on success or an error message + */ +const char *tls_cache_set_specification( + const char *spec, tls_conf_global_t *gconf, apr_pool_t *p, apr_pool_t *ptemp); + +/** + * Setup before configuration runs, announces our potential global mutex. + */ +void tls_cache_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp); + +/** + * Verify the cache settings at the end of the configuration and + * create the default session cache, if not already done. + */ +apr_status_t tls_cache_post_config(apr_pool_t *p, apr_pool_t *ptemp, server_rec *s); + +/** + * Started a new child, make sure that global mutex we might use is set up. + */ +void tls_cache_init_child(apr_pool_t *p, server_rec *s); + +/** + * Free all cache related resources. + */ +void tls_cache_free(server_rec *s); + +/** + * Initialize the session store for the server's config builder. + */ +apr_status_t tls_cache_init_server( + rustls_server_config_builder *builder, server_rec *s); + +#endif /* tls_cache_h */
\ No newline at end of file diff --git a/modules/tls/tls_cert.c b/modules/tls/tls_cert.c new file mode 100644 index 0000000..624535a --- /dev/null +++ b/modules/tls/tls_cert.c @@ -0,0 +1,564 @@ +/* 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 <assert.h> +#include <apr_lib.h> +#include <apr_encode.h> +#include <apr_strings.h> + +#include <httpd.h> +#include <http_connection.h> +#include <http_core.h> +#include <http_log.h> + +#include <rustls.h> + +#include "tls_cert.h" +#include "tls_util.h" + +extern module AP_MODULE_DECLARE_DATA tls_module; +APLOG_USE_MODULE(tls); + + +apr_status_t tls_cert_load_pem( + apr_pool_t *p, const tls_cert_spec_t *cert, tls_cert_pem_t **ppem) +{ + apr_status_t rv; + const char *fpath; + tls_cert_pem_t *cpem; + + ap_assert(cert->cert_file); + cpem = apr_pcalloc(p, sizeof(*cpem)); + fpath = ap_server_root_relative(p, cert->cert_file); + if (NULL == fpath) { + rv = APR_ENOENT; goto cleanup; + } + rv = tls_util_file_load(p, fpath, 0, 100*1024, &cpem->cert_pem); + if (APR_SUCCESS != rv) goto cleanup; + + if (cert->pkey_file) { + fpath = ap_server_root_relative(p, cert->pkey_file); + if (NULL == fpath) { + rv = APR_ENOENT; goto cleanup; + } + rv = tls_util_file_load(p, fpath, 0, 100*1024, &cpem->pkey_pem); + if (APR_SUCCESS != rv) goto cleanup; + } + else { + cpem->pkey_pem = cpem->cert_pem; + } +cleanup: + *ppem = (APR_SUCCESS == rv)? cpem : NULL; + return rv; +} + +#define PEM_IN_CHUNK 48 /* PEM demands at most 64 chars per line */ + +static apr_status_t tls_der_to_pem( + const char **ppem, apr_pool_t *p, + const unsigned char *der_data, apr_size_t der_len, + const char *header, const char *footer) +{ + apr_status_t rv = APR_SUCCESS; + char *pem = NULL, *s; + apr_size_t b64_len, n, hd_len, ft_len; + apr_ssize_t in_len, i; + + if (der_len > INT_MAX) { + rv = APR_ENOMEM; + goto cleanup; + } + in_len = (apr_ssize_t)der_len; + rv = apr_encode_base64(NULL, (const char*)der_data, in_len, APR_ENCODE_NONE, &b64_len); + if (APR_SUCCESS != rv) goto cleanup; + if (b64_len > INT_MAX) { + rv = APR_ENOMEM; + goto cleanup; + } + hd_len = header? strlen(header) : 0; + ft_len = footer? strlen(footer) : 0; + s = pem = apr_pcalloc(p, + + b64_len + (der_len/PEM_IN_CHUNK) + 1 /* \n per chunk */ + + hd_len +1 + ft_len + 1 /* adding \n */ + + 1); /* NUL-terminated */ + if (header) { + strcpy(s, header); + s += hd_len; + *s++ = '\n'; + } + for (i = 0; in_len > 0; i += PEM_IN_CHUNK, in_len -= PEM_IN_CHUNK) { + rv = apr_encode_base64(s, + (const char*)der_data + i, in_len > PEM_IN_CHUNK? PEM_IN_CHUNK : in_len, + APR_ENCODE_NONE, &n); + s += n; + *s++ = '\n'; + } + if (footer) { + strcpy(s, footer); + s += ft_len; + *s++ = '\n'; + } +cleanup: + *ppem = (APR_SUCCESS == rv)? pem : NULL; + return rv; +} + +#define PEM_CERT_HD "-----BEGIN CERTIFICATE-----" +#define PEM_CERT_FT "-----END CERTIFICATE-----" + +apr_status_t tls_cert_to_pem(const char **ppem, apr_pool_t *p, const rustls_certificate *cert) +{ + const unsigned char* der_data; + size_t der_len; + rustls_result rr = RUSTLS_RESULT_OK; + apr_status_t rv = APR_SUCCESS; + const char *pem = NULL; + + rr = rustls_certificate_get_der(cert, &der_data, &der_len); + if (RUSTLS_RESULT_OK != rr) goto cleanup; + rv = tls_der_to_pem(&pem, p, der_data, der_len, PEM_CERT_HD, PEM_CERT_FT); +cleanup: + if (RUSTLS_RESULT_OK != rr) { + rv = tls_util_rustls_error(p, rr, NULL); + } + *ppem = (APR_SUCCESS == rv)? pem : NULL; + return rv; +} + +static void nullify_key_pem(tls_cert_pem_t *pems) +{ + if (pems->pkey_pem.len) { + memset((void*)pems->pkey_pem.data, 0, pems->pkey_pem.len); + } +} + +static apr_status_t make_certified_key( + apr_pool_t *p, const char *name, + const tls_data_t *cert_pem, const tls_data_t *pkey_pem, + const rustls_certified_key **pckey) +{ + const rustls_certified_key *ckey = NULL; + rustls_result rr = RUSTLS_RESULT_OK; + apr_status_t rv = APR_SUCCESS; + + rr = rustls_certified_key_build( + cert_pem->data, cert_pem->len, + pkey_pem->data, pkey_pem->len, + &ckey); + + if (RUSTLS_RESULT_OK != rr) { + const char *err_descr; + rv = tls_util_rustls_error(p, rr, &err_descr); + ap_log_perror(APLOG_MARK, APLOG_ERR, rv, p, APLOGNO(10363) + "Failed to load certified key %s: [%d] %s", + name, (int)rr, err_descr); + } + if (APR_SUCCESS == rv) { + *pckey = ckey; + } + else if (ckey) { + rustls_certified_key_free(ckey); + } + return rv; +} + +apr_status_t tls_cert_load_cert_key( + apr_pool_t *p, const tls_cert_spec_t *spec, + const char **pcert_pem, const rustls_certified_key **pckey) +{ + apr_status_t rv = APR_SUCCESS; + + if (spec->cert_file) { + tls_cert_pem_t *pems; + + rv = tls_cert_load_pem(p, spec, &pems); + if (APR_SUCCESS != rv) goto cleanup; + if (pcert_pem) *pcert_pem = tls_data_to_str(p, &pems->cert_pem); + rv = make_certified_key(p, spec->cert_file, &pems->cert_pem, &pems->pkey_pem, pckey); + /* dont want them hanging around in memory unnecessarily. */ + nullify_key_pem(pems); + } + else if (spec->cert_pem) { + tls_data_t pkey_pem, pem; + pem = tls_data_from_str(spec->cert_pem); + if (spec->pkey_pem) { + pkey_pem = tls_data_from_str(spec->pkey_pem); + } + else { + pkey_pem = pem; + } + if (pcert_pem) *pcert_pem = spec->cert_pem; + rv = make_certified_key(p, "memory", &pem, &pkey_pem, pckey); + /* pems provided from outside are responsibility of the caller */ + } + else { + rv = APR_ENOENT; goto cleanup; + } +cleanup: + return rv; +} + +typedef struct { + const char *id; + const char *cert_pem; + server_rec *server; + const rustls_certified_key *certified_key; +} tls_cert_reg_entry_t; + +static int reg_entry_cleanup(void *ctx, const void *key, apr_ssize_t klen, const void *val) +{ + tls_cert_reg_entry_t *entry = (tls_cert_reg_entry_t*)val; + (void)ctx; (void)key; (void)klen; + if (entry->certified_key) { + rustls_certified_key_free(entry->certified_key); + entry->certified_key = NULL; + } + return 1; +} + +static apr_status_t reg_cleanup(void *data) +{ + tls_cert_reg_t *reg = data; + if (reg->id2entry) { + apr_hash_do(reg_entry_cleanup, reg, reg->id2entry); + apr_hash_clear(reg->id2entry); + if (reg->key2entry) apr_hash_clear(reg->key2entry); + } + return APR_SUCCESS; +} + +tls_cert_reg_t *tls_cert_reg_make(apr_pool_t *p) +{ + tls_cert_reg_t *reg; + + reg = apr_pcalloc(p, sizeof(*reg)); + reg->pool = p; + reg->id2entry = apr_hash_make(p); + reg->key2entry = apr_hash_make(p); + apr_pool_cleanup_register(p, reg, reg_cleanup, apr_pool_cleanup_null); + return reg; +} + +apr_size_t tls_cert_reg_count(tls_cert_reg_t *reg) +{ + return apr_hash_count(reg->id2entry); +} + +static const char *cert_spec_to_id(const tls_cert_spec_t *spec) +{ + if (spec->cert_file) return spec->cert_file; + if (spec->cert_pem) return spec->cert_pem; + return NULL; +} + +apr_status_t tls_cert_reg_get_certified_key( + tls_cert_reg_t *reg, server_rec *s, const tls_cert_spec_t *spec, + const rustls_certified_key **pckey) +{ + apr_status_t rv = APR_SUCCESS; + const char *id; + tls_cert_reg_entry_t *entry; + + id = cert_spec_to_id(spec); + assert(id); + entry = apr_hash_get(reg->id2entry, id, APR_HASH_KEY_STRING); + if (!entry) { + const rustls_certified_key *certified_key; + const char *cert_pem; + rv = tls_cert_load_cert_key(reg->pool, spec, &cert_pem, &certified_key); + if (APR_SUCCESS != rv) goto cleanup; + entry = apr_pcalloc(reg->pool, sizeof(*entry)); + entry->id = apr_pstrdup(reg->pool, id); + entry->cert_pem = cert_pem; + entry->server = s; + entry->certified_key = certified_key; + apr_hash_set(reg->id2entry, entry->id, APR_HASH_KEY_STRING, entry); + /* associates the pointer value */ + apr_hash_set(reg->key2entry, &entry->certified_key, sizeof(entry->certified_key), entry); + } + +cleanup: + if (APR_SUCCESS == rv) { + *pckey = entry->certified_key; + } + else { + *pckey = NULL; + } + return rv; +} + +typedef struct { + void *userdata; + tls_cert_reg_visitor *visitor; +} reg_visit_ctx_t; + +static int reg_visit(void *vctx, const void *key, apr_ssize_t klen, const void *val) +{ + reg_visit_ctx_t *ctx = vctx; + tls_cert_reg_entry_t *entry = (tls_cert_reg_entry_t*)val; + + (void)key; (void)klen; + return ctx->visitor(ctx->userdata, entry->server, entry->id, entry->cert_pem, entry->certified_key); +} + +void tls_cert_reg_do( + tls_cert_reg_visitor *visitor, void *userdata, tls_cert_reg_t *reg) +{ + reg_visit_ctx_t ctx; + ctx.visitor = visitor; + ctx.userdata = userdata; + apr_hash_do(reg_visit, &ctx, reg->id2entry); +} + +const char *tls_cert_reg_get_id(tls_cert_reg_t *reg, const rustls_certified_key *certified_key) +{ + tls_cert_reg_entry_t *entry; + + entry = apr_hash_get(reg->key2entry, &certified_key, sizeof(certified_key)); + return entry? entry->id : NULL; +} + +apr_status_t tls_cert_load_root_store( + apr_pool_t *p, const char *store_file, rustls_root_cert_store **pstore) +{ + const char *fpath; + tls_data_t pem; + rustls_root_cert_store *store = NULL; + rustls_result rr = RUSTLS_RESULT_OK; + apr_pool_t *ptemp = NULL; + apr_status_t rv; + + ap_assert(store_file); + + rv = apr_pool_create(&ptemp, p); + if (APR_SUCCESS != rv) goto cleanup; + apr_pool_tag(ptemp, "tls_load_root_cert_store"); + fpath = ap_server_root_relative(ptemp, store_file); + if (NULL == fpath) { + rv = APR_ENOENT; goto cleanup; + } + /* we use this for client auth CAs. 1MB seems large enough. */ + rv = tls_util_file_load(ptemp, fpath, 0, 1024*1024, &pem); + if (APR_SUCCESS != rv) goto cleanup; + + store = rustls_root_cert_store_new(); + rr = rustls_root_cert_store_add_pem(store, pem.data, pem.len, 1); + if (RUSTLS_RESULT_OK != rr) goto cleanup; + +cleanup: + if (RUSTLS_RESULT_OK != rr) { + const char *err_descr; + rv = tls_util_rustls_error(p, rr, &err_descr); + ap_log_perror(APLOG_MARK, APLOG_ERR, rv, p, APLOGNO(10364) + "Failed to load root store %s: [%d] %s", + store_file, (int)rr, err_descr); + } + if (APR_SUCCESS == rv) { + *pstore = store; + } + else { + *pstore = NULL; + if (store) rustls_root_cert_store_free(store); + } + if (ptemp) apr_pool_destroy(ptemp); + return rv; +} + +typedef struct { + const char *id; + rustls_root_cert_store *store; +} tls_cert_root_stores_entry_t; + +static int stores_entry_cleanup(void *ctx, const void *key, apr_ssize_t klen, const void *val) +{ + tls_cert_root_stores_entry_t *entry = (tls_cert_root_stores_entry_t*)val; + (void)ctx; (void)key; (void)klen; + if (entry->store) { + rustls_root_cert_store_free(entry->store); + entry->store = NULL; + } + return 1; +} + +static apr_status_t stores_cleanup(void *data) +{ + tls_cert_root_stores_t *stores = data; + tls_cert_root_stores_clear(stores); + return APR_SUCCESS; +} + +tls_cert_root_stores_t *tls_cert_root_stores_make(apr_pool_t *p) +{ + tls_cert_root_stores_t *stores; + + stores = apr_pcalloc(p, sizeof(*stores)); + stores->pool = p; + stores->file2store = apr_hash_make(p); + apr_pool_cleanup_register(p, stores, stores_cleanup, apr_pool_cleanup_null); + return stores; +} + +void tls_cert_root_stores_clear(tls_cert_root_stores_t *stores) +{ + if (stores->file2store) { + apr_hash_do(stores_entry_cleanup, stores, stores->file2store); + apr_hash_clear(stores->file2store); + } +} + +apr_status_t tls_cert_root_stores_get( + tls_cert_root_stores_t *stores, + const char *store_file, + rustls_root_cert_store **pstore) +{ + apr_status_t rv = APR_SUCCESS; + tls_cert_root_stores_entry_t *entry; + + entry = apr_hash_get(stores->file2store, store_file, APR_HASH_KEY_STRING); + if (!entry) { + rustls_root_cert_store *store; + rv = tls_cert_load_root_store(stores->pool, store_file, &store); + if (APR_SUCCESS != rv) goto cleanup; + entry = apr_pcalloc(stores->pool, sizeof(*entry)); + entry->id = apr_pstrdup(stores->pool, store_file); + entry->store = store; + apr_hash_set(stores->file2store, entry->id, APR_HASH_KEY_STRING, entry); + } + +cleanup: + if (APR_SUCCESS == rv) { + *pstore = entry->store; + } + else { + *pstore = NULL; + } + return rv; +} + +typedef struct { + const char *id; + const rustls_client_cert_verifier *client_verifier; + const rustls_client_cert_verifier_optional *client_verifier_opt; +} tls_cert_verifiers_entry_t; + +static int verifiers_entry_cleanup(void *ctx, const void *key, apr_ssize_t klen, const void *val) +{ + tls_cert_verifiers_entry_t *entry = (tls_cert_verifiers_entry_t*)val; + (void)ctx; (void)key; (void)klen; + if (entry->client_verifier) { + rustls_client_cert_verifier_free(entry->client_verifier); + entry->client_verifier = NULL; + } + if (entry->client_verifier_opt) { + rustls_client_cert_verifier_optional_free(entry->client_verifier_opt); + entry->client_verifier_opt = NULL; + } + return 1; +} + +static apr_status_t verifiers_cleanup(void *data) +{ + tls_cert_verifiers_t *verifiers = data; + tls_cert_verifiers_clear(verifiers); + return APR_SUCCESS; +} + +tls_cert_verifiers_t *tls_cert_verifiers_make( + apr_pool_t *p, tls_cert_root_stores_t *stores) +{ + tls_cert_verifiers_t *verifiers; + + verifiers = apr_pcalloc(p, sizeof(*verifiers)); + verifiers->pool = p; + verifiers->stores = stores; + verifiers->file2verifier = apr_hash_make(p); + apr_pool_cleanup_register(p, verifiers, verifiers_cleanup, apr_pool_cleanup_null); + return verifiers; +} + +void tls_cert_verifiers_clear(tls_cert_verifiers_t *verifiers) +{ + if (verifiers->file2verifier) { + apr_hash_do(verifiers_entry_cleanup, verifiers, verifiers->file2verifier); + apr_hash_clear(verifiers->file2verifier); + } +} + +static tls_cert_verifiers_entry_t * verifiers_get_or_make_entry( + tls_cert_verifiers_t *verifiers, + const char *store_file) +{ + tls_cert_verifiers_entry_t *entry; + + entry = apr_hash_get(verifiers->file2verifier, store_file, APR_HASH_KEY_STRING); + if (!entry) { + entry = apr_pcalloc(verifiers->pool, sizeof(*entry)); + entry->id = apr_pstrdup(verifiers->pool, store_file); + apr_hash_set(verifiers->file2verifier, entry->id, APR_HASH_KEY_STRING, entry); + } + return entry; +} + +apr_status_t tls_cert_client_verifiers_get( + tls_cert_verifiers_t *verifiers, + const char *store_file, + const rustls_client_cert_verifier **pverifier) +{ + apr_status_t rv = APR_SUCCESS; + tls_cert_verifiers_entry_t *entry; + + entry = verifiers_get_or_make_entry(verifiers, store_file); + if (!entry->client_verifier) { + rustls_root_cert_store *store; + rv = tls_cert_root_stores_get(verifiers->stores, store_file, &store); + if (APR_SUCCESS != rv) goto cleanup; + entry->client_verifier = rustls_client_cert_verifier_new(store); + } + +cleanup: + if (APR_SUCCESS == rv) { + *pverifier = entry->client_verifier; + } + else { + *pverifier = NULL; + } + return rv; +} + +apr_status_t tls_cert_client_verifiers_get_optional( + tls_cert_verifiers_t *verifiers, + const char *store_file, + const rustls_client_cert_verifier_optional **pverifier) +{ + apr_status_t rv = APR_SUCCESS; + tls_cert_verifiers_entry_t *entry; + + entry = verifiers_get_or_make_entry(verifiers, store_file); + if (!entry->client_verifier_opt) { + rustls_root_cert_store *store; + rv = tls_cert_root_stores_get(verifiers->stores, store_file, &store); + if (APR_SUCCESS != rv) goto cleanup; + entry->client_verifier_opt = rustls_client_cert_verifier_optional_new(store); + } + +cleanup: + if (APR_SUCCESS == rv) { + *pverifier = entry->client_verifier_opt; + } + else { + *pverifier = NULL; + } + return rv; +} diff --git a/modules/tls/tls_cert.h b/modules/tls/tls_cert.h new file mode 100644 index 0000000..6ab3f48 --- /dev/null +++ b/modules/tls/tls_cert.h @@ -0,0 +1,211 @@ +/* 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. + */ +#ifndef tls_cert_h +#define tls_cert_h + +#include "tls_util.h" + +/** + * The PEM data of a certificate and its key. + */ +typedef struct { + tls_data_t cert_pem; + tls_data_t pkey_pem; +} tls_cert_pem_t; + +/** + * Specify a certificate via files or PEM data. + */ +typedef struct { + const char *cert_file; /* file path, relative to ap_root */ + const char *pkey_file; /* file path, relative to ap_root */ + const char *cert_pem; /* NUL-terminated PEM string */ + const char *pkey_pem; /* NUL-terminated PEM string */ +} tls_cert_spec_t; + +/** + * Load the PEM data for a certificate file and key file as given in `cert`. + */ +apr_status_t tls_cert_load_pem( + apr_pool_t *p, const tls_cert_spec_t *cert, tls_cert_pem_t **ppem); + +apr_status_t tls_cert_to_pem(const char **ppem, apr_pool_t *p, const rustls_certificate *cert); + +/** + * Load a rustls certified key from a certificate specification. + * The returned `rustls_certified_key` is owned by the caller. + * @param p the memory pool to use + * @param spec the specification for the certificate (file or PEM data) + * @param cert_pem return the PEM data used for loading the certificates, optional + * @param pckey the loaded certified key on return + */ +apr_status_t tls_cert_load_cert_key( + apr_pool_t *p, const tls_cert_spec_t *spec, + const char **pcert_pem, const rustls_certified_key **pckey); + +/** + * A registry of rustls_certified_key* by identifier. + */ +typedef struct tls_cert_reg_t tls_cert_reg_t; +struct tls_cert_reg_t{ + apr_pool_t *pool; + apr_hash_t *id2entry; + apr_hash_t *key2entry; +}; + +/** + * Create a new registry with lifetime based on the memory pool. + * The registry will take care of its memory and allocated keys when + * the pool is destroyed. + */ +tls_cert_reg_t *tls_cert_reg_make(apr_pool_t *p); + +/** + * Return the number of certified keys in the registry. + */ +apr_size_t tls_cert_reg_count(tls_cert_reg_t *reg); + +/** + * Get a the `rustls_certified_key` identified by `spec` from the registry. + * This will load the key the first time it is requested. + * The returned `rustls_certified_key` is owned by the registry. + * @param reg the certified key registry + * @param s the server_rec this is loaded into, useful for error logging + * @param spec the specification of the certified key + * @param pckey the certified key instance on return + */ +apr_status_t tls_cert_reg_get_certified_key( + tls_cert_reg_t *reg, server_rec *s, const tls_cert_spec_t *spec, const rustls_certified_key **pckey); + +/** + * Visit all certified keys in the registry. + * The callback may return 0 to abort the iteration. + * @param userdata supplied by the visit invocation + * @param s the server_rec the certified was load into first + * @param id internal identifier of the certified key + * @param cert_pem the PEM data of the certificate and its chain + * @param certified_key the key instance itself + */ +typedef int tls_cert_reg_visitor( + void *userdata, server_rec *s, + const char *id, const char *cert_pem, const rustls_certified_key *certified_key); + +/** + * Visit all certified_key entries in the registry. + * @param visitor callback invoked on each entry until it returns 0. + * @param userdata passed to callback + * @param reg the registry to iterate over + */ +void tls_cert_reg_do( + tls_cert_reg_visitor *visitor, void *userdata, tls_cert_reg_t *reg); + +/** + * Get the identity assigned to a loaded, certified key. Returns NULL, if the + * key is not part of the registry. The returned bytes are owned by the registry + * entry. + * @param reg the registry to look in. + * @param certified_key the key to get the identifier for + */ +const char *tls_cert_reg_get_id(tls_cert_reg_t *reg, const rustls_certified_key *certified_key); + +/** + * Load all root certificates from a PEM file into a rustls_root_cert_store. + * @param p the memory pool to use + * @param store_file the (server relative) path of the PEM file + * @param pstore the loaded root store on success + */ +apr_status_t tls_cert_load_root_store( + apr_pool_t *p, const char *store_file, rustls_root_cert_store **pstore); + +typedef struct tls_cert_root_stores_t tls_cert_root_stores_t; +struct tls_cert_root_stores_t { + apr_pool_t *pool; + apr_hash_t *file2store; +}; + +/** + * Create a new root stores registry with lifetime based on the memory pool. + * The registry will take care of its memory and allocated stores when + * the pool is destroyed. + */ +tls_cert_root_stores_t *tls_cert_root_stores_make(apr_pool_t *p); + +/** + * Clear the root stores registry, freeing all stores. + */ +void tls_cert_root_stores_clear(tls_cert_root_stores_t *stores); + +/** + * Load all root certificates from a PEM file into a rustls_root_cert_store. + * @param p the memory pool to use + * @param store_file the (server relative) path of the PEM file + * @param pstore the loaded root store on success + */ +apr_status_t tls_cert_root_stores_get( + tls_cert_root_stores_t *stores, + const char *store_file, + rustls_root_cert_store **pstore); + +typedef struct tls_cert_verifiers_t tls_cert_verifiers_t; +struct tls_cert_verifiers_t { + apr_pool_t *pool; + tls_cert_root_stores_t *stores; + apr_hash_t *file2verifier; +}; + +/** + * Create a new registry for certificate verifiers with lifetime based on the memory pool. + * The registry will take care of its memory and allocated verifiers when + * the pool is destroyed. + * @param p the memory pool to use + * @param stores the store registry for lookups + */ +tls_cert_verifiers_t *tls_cert_verifiers_make( + apr_pool_t *p, tls_cert_root_stores_t *stores); + +/** + * Clear the verifiers registry, freeing all verifiers. + */ +void tls_cert_verifiers_clear( + tls_cert_verifiers_t *verifiers); + +/** + * Get the mandatory client certificate verifier for the + * root certificate store in `store_file`. Will create + * the verifier if not already known. + * @param verifiers the registry of certificate verifiers + * @param store_file the (server relative) path of the PEM file with certificates + * @param pverifiers the verifier on success + */ +apr_status_t tls_cert_client_verifiers_get( + tls_cert_verifiers_t *verifiers, + const char *store_file, + const rustls_client_cert_verifier **pverifier); + +/** + * Get the optional client certificate verifier for the + * root certificate store in `store_file`. Will create + * the verifier if not already known. + * @param verifiers the registry of certificate verifiers + * @param store_file the (server relative) path of the PEM file with certificates + * @param pverifiers the verifier on success + */ +apr_status_t tls_cert_client_verifiers_get_optional( + tls_cert_verifiers_t *verifiers, + const char *store_file, + const rustls_client_cert_verifier_optional **pverifier); + +#endif /* tls_cert_h */
\ No newline at end of file diff --git a/modules/tls/tls_conf.c b/modules/tls/tls_conf.c new file mode 100644 index 0000000..a9f27de --- /dev/null +++ b/modules/tls/tls_conf.c @@ -0,0 +1,780 @@ +/* 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 <assert.h> +#include <apr_lib.h> +#include <apr_strings.h> +#include <apr_version.h> + +#include <httpd.h> +#include <http_core.h> +#include <http_config.h> +#include <http_log.h> +#include <http_main.h> +#include <ap_socache.h> + +#include <rustls.h> + +#include "tls_cert.h" +#include "tls_proto.h" +#include "tls_conf.h" +#include "tls_util.h" +#include "tls_var.h" +#include "tls_cache.h" + + +extern module AP_MODULE_DECLARE_DATA tls_module; +APLOG_USE_MODULE(tls); + +static tls_conf_global_t *conf_global_get_or_make(apr_pool_t *pool, server_rec *s) +{ + tls_conf_global_t *gconf; + + /* we create this only once for apache's one ap_server_conf. + * If this gets called for another server, we should already have + * done it for ap_server_conf. */ + if (ap_server_conf && s != ap_server_conf) { + tls_conf_server_t *sconf = tls_conf_server_get(ap_server_conf); + ap_assert(sconf); + ap_assert(sconf->global); + return sconf->global; + } + + gconf = apr_pcalloc(pool, sizeof(*gconf)); + gconf->ap_server = ap_server_conf; + gconf->status = TLS_CONF_ST_INIT; + gconf->proto = tls_proto_init(pool, s); + gconf->proxy_configs = apr_array_make(pool, 10, sizeof(tls_conf_proxy_t*)); + + gconf->var_lookups = apr_hash_make(pool); + tls_var_init_lookup_hash(pool, gconf->var_lookups); + gconf->session_cache_spec = "default"; + + return gconf; +} + +tls_conf_server_t *tls_conf_server_get(server_rec *s) +{ + tls_conf_server_t *sc = ap_get_module_config(s->module_config, &tls_module); + ap_assert(sc); + return sc; +} + + +#define CONF_S_NAME(s) (s && s->server_hostname? s->server_hostname : "default") + +void *tls_conf_create_svr(apr_pool_t *pool, server_rec *s) +{ + tls_conf_server_t *conf; + + conf = apr_pcalloc(pool, sizeof(*conf)); + conf->global = conf_global_get_or_make(pool, s); + conf->server = s; + + conf->enabled = TLS_FLAG_UNSET; + conf->cert_specs = apr_array_make(pool, 3, sizeof(tls_cert_spec_t*)); + conf->honor_client_order = TLS_FLAG_UNSET; + conf->strict_sni = TLS_FLAG_UNSET; + conf->tls_protocol_min = TLS_FLAG_UNSET; + conf->tls_pref_ciphers = apr_array_make(pool, 3, sizeof(apr_uint16_t));; + conf->tls_supp_ciphers = apr_array_make(pool, 3, sizeof(apr_uint16_t));; + return conf; +} + +#define MERGE_INT(base, add, field) \ + (add->field == TLS_FLAG_UNSET)? base->field : add->field; + +void *tls_conf_merge_svr(apr_pool_t *pool, void *basev, void *addv) +{ + tls_conf_server_t *base = basev; + tls_conf_server_t *add = addv; + tls_conf_server_t *nconf; + + nconf = apr_pcalloc(pool, sizeof(*nconf)); + nconf->server = add->server; + nconf->global = add->global? add->global : base->global; + + nconf->enabled = MERGE_INT(base, add, enabled); + nconf->cert_specs = apr_array_append(pool, base->cert_specs, add->cert_specs); + nconf->tls_protocol_min = MERGE_INT(base, add, tls_protocol_min); + nconf->tls_pref_ciphers = add->tls_pref_ciphers->nelts? + add->tls_pref_ciphers : base->tls_pref_ciphers; + nconf->tls_supp_ciphers = add->tls_supp_ciphers->nelts? + add->tls_supp_ciphers : base->tls_supp_ciphers; + nconf->honor_client_order = MERGE_INT(base, add, honor_client_order); + nconf->client_ca = add->client_ca? add->client_ca : base->client_ca; + nconf->client_auth = (add->client_auth != TLS_CLIENT_AUTH_UNSET)? + add->client_auth : base->client_auth; + nconf->var_user_name = add->var_user_name? add->var_user_name : base->var_user_name; + return nconf; +} + +tls_conf_dir_t *tls_conf_dir_get(request_rec *r) +{ + tls_conf_dir_t *dc = ap_get_module_config(r->per_dir_config, &tls_module); + ap_assert(dc); + return dc; +} + +tls_conf_dir_t *tls_conf_dir_server_get(server_rec *s) +{ + tls_conf_dir_t *dc = ap_get_module_config(s->lookup_defaults, &tls_module); + ap_assert(dc); + return dc; +} + +void *tls_conf_create_dir(apr_pool_t *pool, char *dir) +{ + tls_conf_dir_t *conf; + + (void)dir; + conf = apr_pcalloc(pool, sizeof(*conf)); + conf->std_env_vars = TLS_FLAG_UNSET; + conf->proxy_enabled = TLS_FLAG_UNSET; + conf->proxy_protocol_min = TLS_FLAG_UNSET; + conf->proxy_pref_ciphers = apr_array_make(pool, 3, sizeof(apr_uint16_t));; + conf->proxy_supp_ciphers = apr_array_make(pool, 3, sizeof(apr_uint16_t));; + conf->proxy_machine_cert_specs = apr_array_make(pool, 3, sizeof(tls_cert_spec_t*)); + return conf; +} + + +static int same_proxy_settings(tls_conf_dir_t *a, tls_conf_dir_t *b) +{ + return a->proxy_ca == b->proxy_ca; +} + +static void dir_assign_merge( + tls_conf_dir_t *dest, apr_pool_t *pool, tls_conf_dir_t *base, tls_conf_dir_t *add) +{ + tls_conf_dir_t local; + + memset(&local, 0, sizeof(local)); + local.std_env_vars = MERGE_INT(base, add, std_env_vars); + local.export_cert_vars = MERGE_INT(base, add, export_cert_vars); + local.proxy_enabled = MERGE_INT(base, add, proxy_enabled); + local.proxy_ca = add->proxy_ca? add->proxy_ca : base->proxy_ca; + local.proxy_protocol_min = MERGE_INT(base, add, proxy_protocol_min); + local.proxy_pref_ciphers = add->proxy_pref_ciphers->nelts? + add->proxy_pref_ciphers : base->proxy_pref_ciphers; + local.proxy_supp_ciphers = add->proxy_supp_ciphers->nelts? + add->proxy_supp_ciphers : base->proxy_supp_ciphers; + local.proxy_machine_cert_specs = apr_array_append(pool, + base->proxy_machine_cert_specs, add->proxy_machine_cert_specs); + if (local.proxy_enabled == TLS_FLAG_TRUE) { + if (add->proxy_config) { + local.proxy_config = same_proxy_settings(&local, add)? add->proxy_config : NULL; + } + else if (base->proxy_config) { + local.proxy_config = same_proxy_settings(&local, base)? add->proxy_config : NULL; + } + } + memcpy(dest, &local, sizeof(*dest)); +} + +void *tls_conf_merge_dir(apr_pool_t *pool, void *basev, void *addv) +{ + tls_conf_dir_t *base = basev; + tls_conf_dir_t *add = addv; + tls_conf_dir_t *nconf = apr_pcalloc(pool, sizeof(*nconf)); + dir_assign_merge(nconf, pool, base, add); + return nconf; +} + +static void tls_conf_dir_set_options_defaults(apr_pool_t *pool, tls_conf_dir_t *dc) +{ + (void)pool; + dc->std_env_vars = TLS_FLAG_FALSE; + dc->export_cert_vars = TLS_FLAG_FALSE; +} + +apr_status_t tls_conf_server_apply_defaults(tls_conf_server_t *sc, apr_pool_t *p) +{ + (void)p; + if (sc->enabled == TLS_FLAG_UNSET) sc->enabled = TLS_FLAG_FALSE; + if (sc->tls_protocol_min == TLS_FLAG_UNSET) sc->tls_protocol_min = 0; + if (sc->honor_client_order == TLS_FLAG_UNSET) sc->honor_client_order = TLS_FLAG_TRUE; + if (sc->strict_sni == TLS_FLAG_UNSET) sc->strict_sni = TLS_FLAG_TRUE; + if (sc->client_auth == TLS_CLIENT_AUTH_UNSET) sc->client_auth = TLS_CLIENT_AUTH_NONE; + return APR_SUCCESS; +} + +apr_status_t tls_conf_dir_apply_defaults(tls_conf_dir_t *dc, apr_pool_t *p) +{ + (void)p; + if (dc->std_env_vars == TLS_FLAG_UNSET) dc->std_env_vars = TLS_FLAG_FALSE; + if (dc->export_cert_vars == TLS_FLAG_UNSET) dc->export_cert_vars = TLS_FLAG_FALSE; + if (dc->proxy_enabled == TLS_FLAG_UNSET) dc->proxy_enabled = TLS_FLAG_FALSE; + return APR_SUCCESS; +} + +tls_conf_proxy_t *tls_conf_proxy_make( + apr_pool_t *p, tls_conf_dir_t *dc, tls_conf_global_t *gc, server_rec *s) +{ + tls_conf_proxy_t *pc = apr_pcalloc(p, sizeof(*pc)); + pc->defined_in = s; + pc->global = gc; + pc->proxy_ca = dc->proxy_ca; + pc->proxy_protocol_min = dc->proxy_protocol_min; + pc->proxy_pref_ciphers = dc->proxy_pref_ciphers; + pc->proxy_supp_ciphers = dc->proxy_supp_ciphers; + pc->machine_cert_specs = dc->proxy_machine_cert_specs; + pc->machine_certified_keys = apr_array_make(p, 3, sizeof(const rustls_certified_key*)); + return pc; +} + +int tls_proxy_section_post_config( + apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s, + ap_conf_vector_t *section_config) +{ + tls_conf_dir_t *proxy_dc, *server_dc; + tls_conf_server_t *sc; + + /* mod_proxy collects the <Proxy>...</Proxy> sections per server (base server or virtualhost) + * and in its post_config hook, calls our function registered at its hook for each with + * s - the server they were define in + * section_config - the set of dir_configs for a <Proxy> section + * + * If none of _our_ config directives had been used, here or in the server, we get a NULL. + * Which means we have to do nothing. Otherwise, we add to `proxy_dc` the + * settings from `server_dc` - since this is not automagically done by apache. + * + * `proxy_dc` is then complete and tells us if we handle outgoing connections + * here and with what parameter settings. + */ + (void)ptemp; (void)plog; + ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s, + "%s: tls_proxy_section_post_config called", s->server_hostname); + proxy_dc = ap_get_module_config(section_config, &tls_module); + if (!proxy_dc) goto cleanup; + server_dc = ap_get_module_config(s->lookup_defaults, &tls_module); + ap_assert(server_dc); + dir_assign_merge(proxy_dc, p, server_dc, proxy_dc); + tls_conf_dir_apply_defaults(proxy_dc, p); + if (proxy_dc->proxy_enabled && !proxy_dc->proxy_config) { + /* remember `proxy_dc` for subsequent configuration of outoing TLS setups */ + sc = tls_conf_server_get(s); + proxy_dc->proxy_config = tls_conf_proxy_make(p, proxy_dc, sc->global, s); + ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s, + "%s: adding proxy_conf to globals in proxy_post_config_section", + s->server_hostname); + APR_ARRAY_PUSH(sc->global->proxy_configs, tls_conf_proxy_t*) = proxy_dc->proxy_config; + } +cleanup: + return OK; +} + +static const char *cmd_check_file(cmd_parms *cmd, const char *fpath) +{ + char *real_path; + + /* just a dump of the configuration, dont resolve/check */ + if (ap_state_query(AP_SQ_RUN_MODE) == AP_SQ_RM_CONFIG_DUMP) { + return NULL; + } + real_path = ap_server_root_relative(cmd->pool, fpath); + if (!real_path) { + return apr_pstrcat(cmd->pool, cmd->cmd->name, + ": Invalid file path ", fpath, NULL); + } + if (!tls_util_is_file(cmd->pool, real_path)) { + return apr_pstrcat(cmd->pool, cmd->cmd->name, + ": file '", real_path, + "' does not exist or is empty", NULL); + } + return NULL; +} + +static const char *tls_conf_add_engine(cmd_parms *cmd, void *dc, const char*v) +{ + tls_conf_server_t *sc = tls_conf_server_get(cmd->server); + tls_conf_global_t *gc = sc->global; + const char *err = NULL; + char *host, *scope_id; + apr_port_t port; + apr_sockaddr_t *sa; + server_addr_rec *sar; + apr_status_t rv; + + (void)dc; + /* Example of use: + * TLSEngine 443 + * TLSEngine hostname:443 + * TLSEngine 91.0.0.1:443 + * TLSEngine [::0]:443 + */ + rv = apr_parse_addr_port(&host, &scope_id, &port, v, cmd->pool); + if (APR_SUCCESS != rv) { + err = apr_pstrcat(cmd->pool, cmd->cmd->name, + ": invalid address/port in '", v, "'", NULL); + goto cleanup; + } + + /* translate host/port to a sockaddr that we can match with incoming connections */ + rv = apr_sockaddr_info_get(&sa, host, APR_UNSPEC, port, 0, cmd->pool); + if (APR_SUCCESS != rv) { + err = apr_pstrcat(cmd->pool, cmd->cmd->name, + ": unable to get sockaddr for '", host, "'", NULL); + goto cleanup; + } + + if (scope_id) { +#if APR_VERSION_AT_LEAST(1,7,0) + rv = apr_sockaddr_zone_set(sa, scope_id); + if (APR_SUCCESS != rv) { + err = apr_pstrcat(cmd->pool, cmd->cmd->name, + ": error setting ipv6 scope id: '", scope_id, "'", NULL); + goto cleanup; + } +#else + err = apr_pstrcat(cmd->pool, cmd->cmd->name, + ": IPv6 scopes not supported by your APR: '", scope_id, "'", NULL); + goto cleanup; +#endif + } + + sar = apr_pcalloc(cmd->pool, sizeof(*sar)); + sar->host_addr = sa; + sar->virthost = host; + sar->host_port = port; + + sar->next = gc->tls_addresses; + gc->tls_addresses = sar; +cleanup: + return err; +} + +static int flag_value( + const char *arg) +{ + if (!strcasecmp(arg, "On")) { + return TLS_FLAG_TRUE; + } + else if (!strcasecmp(arg, "Off")) { + return TLS_FLAG_FALSE; + } + return TLS_FLAG_UNSET; +} + +static const char *flag_err( + cmd_parms *cmd, const char *v) +{ + return apr_pstrcat(cmd->pool, cmd->cmd->name, + ": value must be 'On' or 'Off': '", v, "'", NULL); +} + +static const char *tls_conf_add_certificate( + cmd_parms *cmd, void *dc, const char *cert_file, const char *pkey_file) +{ + tls_conf_server_t *sc = tls_conf_server_get(cmd->server); + const char *err = NULL, *fpath; + tls_cert_spec_t *cert; + + (void)dc; + if (NULL != (err = cmd_check_file(cmd, cert_file))) goto cleanup; + /* key file may be NULL, in which case cert_file must contain the key PEM */ + if (pkey_file && NULL != (err = cmd_check_file(cmd, pkey_file))) goto cleanup; + + cert = apr_pcalloc(cmd->pool, sizeof(*cert)); + fpath = ap_server_root_relative(cmd->pool, cert_file); + if (!tls_util_is_file(cmd->pool, fpath)) { + return apr_pstrcat(cmd->pool, cmd->cmd->name, + ": unable to find certificate file: '", fpath, "'", NULL); + } + cert->cert_file = cert_file; + if (pkey_file) { + fpath = ap_server_root_relative(cmd->pool, pkey_file); + if (!tls_util_is_file(cmd->pool, fpath)) { + return apr_pstrcat(cmd->pool, cmd->cmd->name, + ": unable to find certificate key file: '", fpath, "'", NULL); + } + } + cert->pkey_file = pkey_file; + *(const tls_cert_spec_t **)apr_array_push(sc->cert_specs) = cert; + +cleanup: + return err; +} + +static const char *parse_ciphers( + cmd_parms *cmd, + tls_conf_global_t *gc, + const char *nop_name, + int argc, char *const argv[], + apr_array_header_t *ciphers) +{ + apr_array_clear(ciphers); + if (argc > 1 || apr_strnatcasecmp(nop_name, argv[0])) { + apr_uint16_t cipher; + int i; + + for (i = 0; i < argc; ++i) { + char *name, *last = NULL; + const char *value = argv[i]; + + name = apr_strtok(apr_pstrdup(cmd->pool, value), ":", &last); + while (name) { + if (tls_proto_get_cipher_by_name(gc->proto, name, &cipher) != APR_SUCCESS) { + return apr_pstrcat(cmd->pool, cmd->cmd->name, + ": cipher not recognized '", name, "'", NULL); + } + APR_ARRAY_PUSH(ciphers, apr_uint16_t) = cipher; + name = apr_strtok(NULL, ":", &last); + } + } + } + return NULL; +} + +static const char *tls_conf_set_preferred_ciphers( + cmd_parms *cmd, void *dc, int argc, char *const argv[]) +{ + tls_conf_server_t *sc = tls_conf_server_get(cmd->server); + const char *err = NULL; + + (void)dc; + if (!argc) { + err = "specify the TLS ciphers to prefer or 'default' for the rustls default ordering."; + goto cleanup; + } + err = parse_ciphers(cmd, sc->global, "default", argc, argv, sc->tls_pref_ciphers); +cleanup: + return err; +} + +static const char *tls_conf_set_suppressed_ciphers( + cmd_parms *cmd, void *dc, int argc, char *const argv[]) +{ + tls_conf_server_t *sc = tls_conf_server_get(cmd->server); + const char *err = NULL; + + (void)dc; + if (!argc) { + err = "specify the TLS ciphers to never use or 'none'."; + goto cleanup; + } + err = parse_ciphers(cmd, sc->global, "none", argc, argv, sc->tls_supp_ciphers); +cleanup: + return err; +} + +static const char *tls_conf_set_honor_client_order( + cmd_parms *cmd, void *dc, const char *v) +{ + tls_conf_server_t *sc = tls_conf_server_get(cmd->server); + int flag = flag_value(v); + + (void)dc; + if (TLS_FLAG_UNSET == flag) return flag_err(cmd, v); + sc->honor_client_order = flag; + return NULL; +} + +static const char *tls_conf_set_strict_sni( + cmd_parms *cmd, void *dc, const char *v) +{ + tls_conf_server_t *sc = tls_conf_server_get(cmd->server); + int flag = flag_value(v); + + (void)dc; + if (TLS_FLAG_UNSET == flag) return flag_err(cmd, v); + sc->strict_sni = flag; + return NULL; +} + +static const char *get_min_protocol( + cmd_parms *cmd, const char *v, int *pmin) +{ + tls_conf_server_t *sc = tls_conf_server_get(cmd->server); + const char *err = NULL; + + if (!apr_strnatcasecmp("default", v)) { + *pmin = 0; + } + else if (*v && v[strlen(v)-1] == '+') { + char *name = apr_pstrdup(cmd->pool, v); + name[strlen(name)-1] = '\0'; + *pmin = tls_proto_get_version_by_name(sc->global->proto, name); + if (!*pmin) { + err = apr_pstrcat(cmd->pool, cmd->cmd->name, + ": unrecognized protocol version specifier (try TLSv1.2+ or TLSv1.3+): '", v, "'", NULL); + goto cleanup; + } + } + else { + err = apr_pstrcat(cmd->pool, cmd->cmd->name, + ": value must be 'default', 'TLSv1.2+' or 'TLSv1.3+': '", v, "'", NULL); + goto cleanup; + } +cleanup: + return err; +} + +static const char *tls_conf_set_protocol( + cmd_parms *cmd, void *dc, const char *v) +{ + tls_conf_server_t *sc = tls_conf_server_get(cmd->server); + (void)dc; + return get_min_protocol(cmd, v, &sc->tls_protocol_min); +} + +static const char *tls_conf_set_options( + cmd_parms *cmd, void *dcv, int argc, char *const argv[]) +{ + tls_conf_dir_t *dc = dcv; + const char *err = NULL, *option; + int i, val; + + /* Are we only having deltas (+/-) or do we reset the options? */ + for (i = 0; i < argc; ++i) { + if (argv[i][0] != '+' && argv[i][0] != '-') { + tls_conf_dir_set_options_defaults(cmd->pool, dc); + break; + } + } + + for (i = 0; i < argc; ++i) { + option = argv[i]; + if (!apr_strnatcasecmp("Defaults", option)) { + dc->std_env_vars = TLS_FLAG_FALSE; + dc->export_cert_vars = TLS_FLAG_FALSE; + } + else { + val = TLS_FLAG_TRUE; + if (*option == '+' || *option == '-') { + val = (*option == '+')? TLS_FLAG_TRUE : TLS_FLAG_FALSE; + ++option; + } + + if (!apr_strnatcasecmp("StdEnvVars", option)) { + dc->std_env_vars = val; + } + else if (!apr_strnatcasecmp("ExportCertData", option)) { + dc->export_cert_vars = val; + } + else { + err = apr_pstrcat(cmd->pool, cmd->cmd->name, + ": unknown option '", option, "'", NULL); + goto cleanup; + } + } + } +cleanup: + return err; +} + +static const char *tls_conf_set_session_cache( + cmd_parms *cmd, void *dc, const char *value) +{ + tls_conf_server_t *sc = tls_conf_server_get(cmd->server); + const char *err = NULL; + + (void)dc; + if ((err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) goto cleanup; + + err = tls_cache_set_specification(value, sc->global, cmd->pool, cmd->temp_pool); +cleanup: + return err; +} + +static const char *tls_conf_set_proxy_engine(cmd_parms *cmd, void *dir_conf, int flag) +{ + tls_conf_dir_t *dc = dir_conf; + (void)cmd; + dc->proxy_enabled = flag ? TLS_FLAG_TRUE : TLS_FLAG_FALSE; + return NULL; +} + +static const char *tls_conf_set_proxy_ca( + cmd_parms *cmd, void *dir_conf, const char *proxy_ca) +{ + tls_conf_dir_t *dc = dir_conf; + const char *err = NULL; + + if (strcasecmp(proxy_ca, "default") && NULL != (err = cmd_check_file(cmd, proxy_ca))) goto cleanup; + dc->proxy_ca = proxy_ca; +cleanup: + return err; +} + +static const char *tls_conf_set_proxy_protocol( + cmd_parms *cmd, void *dir_conf, const char *v) +{ + tls_conf_dir_t *dc = dir_conf; + return get_min_protocol(cmd, v, &dc->proxy_protocol_min); +} + +static const char *tls_conf_set_proxy_preferred_ciphers( + cmd_parms *cmd, void *dir_conf, int argc, char *const argv[]) +{ + tls_conf_server_t *sc = tls_conf_server_get(cmd->server); + tls_conf_dir_t *dc = dir_conf; + const char *err = NULL; + + if (!argc) { + err = "specify the proxy TLS ciphers to prefer or 'default' for the rustls default ordering."; + goto cleanup; + } + err = parse_ciphers(cmd, sc->global, "default", argc, argv, dc->proxy_pref_ciphers); +cleanup: + return err; +} + +static const char *tls_conf_set_proxy_suppressed_ciphers( + cmd_parms *cmd, void *dir_conf, int argc, char *const argv[]) +{ + tls_conf_server_t *sc = tls_conf_server_get(cmd->server); + tls_conf_dir_t *dc = dir_conf; + const char *err = NULL; + + if (!argc) { + err = "specify the proxy TLS ciphers to never use or 'none'."; + goto cleanup; + } + err = parse_ciphers(cmd, sc->global, "none", argc, argv, dc->proxy_supp_ciphers); +cleanup: + return err; +} + +#if TLS_CLIENT_CERTS + +static const char *tls_conf_set_client_ca( + cmd_parms *cmd, void *dc, const char *client_ca) +{ + tls_conf_server_t *sc = tls_conf_server_get(cmd->server); + const char *err; + + (void)dc; + if (NULL != (err = cmd_check_file(cmd, client_ca))) goto cleanup; + sc->client_ca = client_ca; +cleanup: + return err; +} + +static const char *tls_conf_set_client_auth( + cmd_parms *cmd, void *dc, const char *mode) +{ + tls_conf_server_t *sc = tls_conf_server_get(cmd->server); + const char *err = NULL; + (void)dc; + if (!strcasecmp(mode, "required")) { + sc->client_auth = TLS_CLIENT_AUTH_REQUIRED; + } + else if (!strcasecmp(mode, "optional")) { + sc->client_auth = TLS_CLIENT_AUTH_OPTIONAL; + } + else if (!strcasecmp(mode, "none")) { + sc->client_auth = TLS_CLIENT_AUTH_NONE; + } + else { + err = apr_pstrcat(cmd->pool, cmd->cmd->name, + ": unknown value: '", mode, "', use required/optional/none.", NULL); + } + return err; +} + +static const char *tls_conf_set_user_name( + cmd_parms *cmd, void *dc, const char *var_user_name) +{ + tls_conf_server_t *sc = tls_conf_server_get(cmd->server); + (void)dc; + sc->var_user_name = var_user_name; + return NULL; +} + +#endif /* if TLS_CLIENT_CERTS */ + +#if TLS_MACHINE_CERTS + +static const char *tls_conf_add_proxy_machine_certificate( + cmd_parms *cmd, void *dir_conf, const char *cert_file, const char *pkey_file) +{ + tls_conf_dir_t *dc = dir_conf; + const char *err = NULL, *fpath; + tls_cert_spec_t *cert; + + (void)dc; + if (NULL != (err = cmd_check_file(cmd, cert_file))) goto cleanup; + /* key file may be NULL, in which case cert_file must contain the key PEM */ + if (pkey_file && NULL != (err = cmd_check_file(cmd, pkey_file))) goto cleanup; + + cert = apr_pcalloc(cmd->pool, sizeof(*cert)); + fpath = ap_server_root_relative(cmd->pool, cert_file); + if (!tls_util_is_file(cmd->pool, fpath)) { + return apr_pstrcat(cmd->pool, cmd->cmd->name, + ": unable to find certificate file: '", fpath, "'", NULL); + } + cert->cert_file = cert_file; + if (pkey_file) { + fpath = ap_server_root_relative(cmd->pool, pkey_file); + if (!tls_util_is_file(cmd->pool, fpath)) { + return apr_pstrcat(cmd->pool, cmd->cmd->name, + ": unable to find certificate key file: '", fpath, "'", NULL); + } + } + cert->pkey_file = pkey_file; + *(const tls_cert_spec_t **)apr_array_push(dc->proxy_machine_cert_specs) = cert; + +cleanup: + return err; +} + +#endif /* if TLS_MACHINE_CERTS */ + +const command_rec tls_conf_cmds[] = { + AP_INIT_TAKE12("TLSCertificate", tls_conf_add_certificate, NULL, RSRC_CONF, + "Add a certificate to the server by specifying a file containing the " + "certificate PEM, followed by its chain PEMs. The PEM of the key must " + "either also be there or can be given as a separate file."), + AP_INIT_TAKE_ARGV("TLSCiphersPrefer", tls_conf_set_preferred_ciphers, NULL, RSRC_CONF, + "Set the TLS ciphers to prefer when negotiating with a client."), + AP_INIT_TAKE_ARGV("TLSCiphersSuppress", tls_conf_set_suppressed_ciphers, NULL, RSRC_CONF, + "Set the TLS ciphers to never use when negotiating with a client."), + AP_INIT_TAKE1("TLSHonorClientOrder", tls_conf_set_honor_client_order, NULL, RSRC_CONF, + "Set 'on' to have the server honor client preferences in cipher suites, default off."), + AP_INIT_TAKE1("TLSEngine", tls_conf_add_engine, NULL, RSRC_CONF, + "Specify an address+port where the module shall handle incoming TLS connections."), + AP_INIT_TAKE_ARGV("TLSOptions", tls_conf_set_options, NULL, OR_OPTIONS, + "En-/disables optional features in the module."), + AP_INIT_TAKE1("TLSProtocol", tls_conf_set_protocol, NULL, RSRC_CONF, + "Set the minimum TLS protocol version to use."), + AP_INIT_TAKE1("TLSStrictSNI", tls_conf_set_strict_sni, NULL, RSRC_CONF, + "Set strictness of client server name (SNI) check against hosts, default on."), + AP_INIT_TAKE1("TLSSessionCache", tls_conf_set_session_cache, NULL, RSRC_CONF, + "Set which cache to use for TLS sessions."), + AP_INIT_FLAG("TLSProxyEngine", tls_conf_set_proxy_engine, NULL, RSRC_CONF|PROXY_CONF, + "Enable TLS encryption of outgoing connections in this location/server."), + AP_INIT_TAKE1("TLSProxyCA", tls_conf_set_proxy_ca, NULL, RSRC_CONF|PROXY_CONF, + "Set the trust anchors for certificates from proxied backend servers from a PEM file."), + AP_INIT_TAKE1("TLSProxyProtocol", tls_conf_set_proxy_protocol, NULL, RSRC_CONF|PROXY_CONF, + "Set the minimum TLS protocol version to use for proxy connections."), + AP_INIT_TAKE_ARGV("TLSProxyCiphersPrefer", tls_conf_set_proxy_preferred_ciphers, NULL, RSRC_CONF|PROXY_CONF, + "Set the TLS ciphers to prefer when negotiating a proxy connection."), + AP_INIT_TAKE_ARGV("TLSProxyCiphersSuppress", tls_conf_set_proxy_suppressed_ciphers, NULL, RSRC_CONF|PROXY_CONF, + "Set the TLS ciphers to never use when negotiating a proxy connection."), +#if TLS_CLIENT_CERTS + AP_INIT_TAKE1("TLSClientCA", tls_conf_set_client_ca, NULL, RSRC_CONF, + "Set the trust anchors for client certificates from a PEM file."), + AP_INIT_TAKE1("TLSClientCertificate", tls_conf_set_client_auth, NULL, RSRC_CONF, + "If TLS client authentication is 'required', 'optional' or 'none'."), + AP_INIT_TAKE1("TLSUserName", tls_conf_set_user_name, NULL, RSRC_CONF, + "Set the SSL variable to be used as user name."), +#endif /* if TLS_CLIENT_CERTS */ +#if TLS_MACHINE_CERTS + AP_INIT_TAKE12("TLSProxyMachineCertificate", tls_conf_add_proxy_machine_certificate, NULL, RSRC_CONF|PROXY_CONF, + "Add a certificate to be used as client certificate on a proxy connection. "), +#endif /* if TLS_MACHINE_CERTS */ + AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL) +}; diff --git a/modules/tls/tls_conf.h b/modules/tls/tls_conf.h new file mode 100644 index 0000000..e924412 --- /dev/null +++ b/modules/tls/tls_conf.h @@ -0,0 +1,185 @@ +/* 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. + */ +#ifndef tls_conf_h +#define tls_conf_h + +/* Configuration flags */ +#define TLS_FLAG_UNSET (-1) +#define TLS_FLAG_FALSE (0) +#define TLS_FLAG_TRUE (1) + +struct tls_proto_conf_t; +struct tls_cert_reg_t; +struct tls_cert_root_stores_t; +struct tls_cert_verifiers_t; +struct ap_socache_instance_t; +struct ap_socache_provider_t; +struct apr_global_mutex_t; + + +/* disabled, since rustls support is lacking + * - x.509 retrieval of certificate fields and extensions + * - certificate revocation lists (CRL) + * - x.509 access to issuer of trust chain in x.509 CA store: + * server CA has ca1, ca2, ca3 + * client present certA + * rustls verifies that it is signed by *one of* ca* certs + * OCSP check needs (certA, issuing cert) for query + */ +#define TLS_CLIENT_CERTS 0 + +/* support for this exists as PR <https://github.com/rustls/rustls-ffi/pull/128> + */ +#define TLS_MACHINE_CERTS 1 + + +typedef enum { + TLS_CLIENT_AUTH_UNSET, + TLS_CLIENT_AUTH_NONE, + TLS_CLIENT_AUTH_REQUIRED, + TLS_CLIENT_AUTH_OPTIONAL, +} tls_client_auth_t; + +typedef enum { + TLS_CONF_ST_INIT, + TLS_CONF_ST_INCOMING_DONE, + TLS_CONF_ST_OUTGOING_DONE, + TLS_CONF_ST_DONE, +} tls_conf_status_t; + +/* The global module configuration, created after post-config + * and then readonly. + */ +typedef struct { + server_rec *ap_server; /* the global server we initialized on */ + const char *module_version; + const char *crustls_version; + + tls_conf_status_t status; + int mod_proxy_post_config_done; /* if mod_proxy did its post-config things */ + + server_addr_rec *tls_addresses; /* the addresses/ports our engine is enabled on */ + apr_array_header_t *proxy_configs; /* tls_conf_proxy_t* collected from everywhere */ + + struct tls_proto_conf_t *proto; /* TLS protocol/rustls specific globals */ + apr_hash_t *var_lookups; /* variable lookup functions by var name */ + struct tls_cert_reg_t *cert_reg; /* all certified keys loaded */ + struct tls_cert_root_stores_t *stores; /* loaded certificate stores */ + struct tls_cert_verifiers_t *verifiers; /* registry of certificate verifiers */ + + const char *session_cache_spec; /* how the session cache was specified */ + const struct ap_socache_provider_t *session_cache_provider; /* provider used for session cache */ + struct ap_socache_instance_t *session_cache; /* session cache instance */ + struct apr_global_mutex_t *session_cache_mutex; /* global mutex for access to session cache */ + + const rustls_server_config *rustls_hello_config; /* used for initial client hello parsing */ +} tls_conf_global_t; + +/* The module configuration for a server (vhost). + * Populated during config parsing, merged and completed + * in the post config phase. Readonly after that. + */ +typedef struct { + server_rec *server; /* server this config belongs to */ + tls_conf_global_t *global; /* global module config, singleton */ + + int enabled; /* TLS_FLAG_TRUE if mod_tls is active on this server */ + apr_array_header_t *cert_specs; /* array of (tls_cert_spec_t*) of configured certificates */ + int tls_protocol_min; /* the minimum TLS protocol version to use */ + apr_array_header_t *tls_pref_ciphers; /* List of apr_uint16_t cipher ids to prefer */ + apr_array_header_t *tls_supp_ciphers; /* List of apr_uint16_t cipher ids to suppress */ + const apr_array_header_t *ciphersuites; /* Computed post-config, ordered list of rustls cipher suites */ + int honor_client_order; /* honor client cipher ordering */ + int strict_sni; + + const char *client_ca; /* PEM file with trust anchors for client certs */ + tls_client_auth_t client_auth; /* how client authentication with certificates is used */ + const char *var_user_name; /* which SSL variable to use as user name */ + + apr_array_header_t *certified_keys; /* rustls_certified_key list configured */ + int base_server; /* != 0 iff this is the base server */ + int service_unavailable; /* TLS not trustworthy configured, return 503s */ +} tls_conf_server_t; + +typedef struct { + server_rec *defined_in; /* the server/host defining this dir_conf */ + tls_conf_global_t *global; /* global module config, singleton */ + const char *proxy_ca; /* PEM file with trust anchors for proxied remote server certs */ + int proxy_protocol_min; /* the minimum TLS protocol version to use for proxy connections */ + apr_array_header_t *proxy_pref_ciphers; /* List of apr_uint16_t cipher ids to prefer */ + apr_array_header_t *proxy_supp_ciphers; /* List of apr_uint16_t cipher ids to suppress */ + apr_array_header_t *machine_cert_specs; /* configured machine certificates specs */ + apr_array_header_t *machine_certified_keys; /* rustls_certified_key list */ + const rustls_client_config *rustls_config; +} tls_conf_proxy_t; + +typedef struct { + int std_env_vars; + int export_cert_vars; + int proxy_enabled; /* TLS_FLAG_TRUE if mod_tls is active on outgoing connections */ + const char *proxy_ca; /* PEM file with trust anchors for proxied remote server certs */ + int proxy_protocol_min; /* the minimum TLS protocol version to use for proxy connections */ + apr_array_header_t *proxy_pref_ciphers; /* List of apr_uint16_t cipher ids to prefer */ + apr_array_header_t *proxy_supp_ciphers; /* List of apr_uint16_t cipher ids to suppress */ + apr_array_header_t *proxy_machine_cert_specs; /* configured machine certificates specs */ + + tls_conf_proxy_t *proxy_config; +} tls_conf_dir_t; + +/* our static registry of configuration directives. */ +extern const command_rec tls_conf_cmds[]; + +/* create the modules configuration for a server_rec. */ +void *tls_conf_create_svr(apr_pool_t *pool, server_rec *s); + +/* merge (inherit) server configurations for the module. + * Settings in 'add' overwrite the ones in 'base' and unspecified + * settings shine through. */ +void *tls_conf_merge_svr(apr_pool_t *pool, void *basev, void *addv); + +/* create the modules configuration for a directory. */ +void *tls_conf_create_dir(apr_pool_t *pool, char *dir); + +/* merge (inherit) directory configurations for the module. + * Settings in 'add' overwrite the ones in 'base' and unspecified + * settings shine through. */ +void *tls_conf_merge_dir(apr_pool_t *pool, void *basev, void *addv); + + +/* Get the server specific module configuration. */ +tls_conf_server_t *tls_conf_server_get(server_rec *s); + +/* Get the directory specific module configuration for the request. */ +tls_conf_dir_t *tls_conf_dir_get(request_rec *r); + +/* Get the directory specific module configuration for the server. */ +tls_conf_dir_t *tls_conf_dir_server_get(server_rec *s); + +/* If any configuration values are unset, supply the global server defaults. */ +apr_status_t tls_conf_server_apply_defaults(tls_conf_server_t *sc, apr_pool_t *p); + +/* If any configuration values are unset, supply the global dir defaults. */ +apr_status_t tls_conf_dir_apply_defaults(tls_conf_dir_t *dc, apr_pool_t *p); + +/* create a new proxy configuration from directory config in server */ +tls_conf_proxy_t *tls_conf_proxy_make( + apr_pool_t *p, tls_conf_dir_t *dc, tls_conf_global_t *gc, server_rec *s); + +int tls_proxy_section_post_config( + apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s, + ap_conf_vector_t *section_config); + +#endif /* tls_conf_h */ diff --git a/modules/tls/tls_core.c b/modules/tls/tls_core.c new file mode 100644 index 0000000..2547939 --- /dev/null +++ b/modules/tls/tls_core.c @@ -0,0 +1,1433 @@ +/* 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 <assert.h> +#include <apr_lib.h> +#include <apr_strings.h> +#include <apr_network_io.h> + +#include <httpd.h> +#include <http_core.h> +#include <http_log.h> +#include <http_protocol.h> +#include <http_ssl.h> +#include <http_vhost.h> +#include <http_main.h> +#include <ap_socache.h> + +#include <rustls.h> + +#include "tls_proto.h" +#include "tls_cert.h" +#include "tls_conf.h" +#include "tls_core.h" +#include "tls_ocsp.h" +#include "tls_util.h" +#include "tls_cache.h" +#include "tls_var.h" + + +extern module AP_MODULE_DECLARE_DATA tls_module; +APLOG_USE_MODULE(tls); + +tls_conf_conn_t *tls_conf_conn_get(conn_rec *c) +{ + return ap_get_module_config(c->conn_config, &tls_module); +} + +void tls_conf_conn_set(conn_rec *c, tls_conf_conn_t *cc) +{ + ap_set_module_config(c->conn_config, &tls_module, cc); +} + +int tls_conn_check_ssl(conn_rec *c) +{ + tls_conf_conn_t *cc = tls_conf_conn_get(c->master? c->master : c); + if (TLS_CONN_ST_IS_ENABLED(cc)) { + return OK; + } + return DECLINED; +} + +static int we_listen_on(tls_conf_global_t *gc, server_rec *s, tls_conf_server_t *sc) +{ + server_addr_rec *sa, *la; + + if (gc->tls_addresses && sc->base_server) { + /* The base server listens to every port and may be selected via SNI */ + return 1; + } + for (la = gc->tls_addresses; la; la = la->next) { + for (sa = s->addrs; sa; sa = sa->next) { + if (la->host_port == sa->host_port + && la->host_addr->ipaddr_len == sa->host_addr->ipaddr_len + && !memcmp(la->host_addr->ipaddr_ptr, + la->host_addr->ipaddr_ptr, (size_t)la->host_addr->ipaddr_len)) { + /* exact match */ + return 1; + } + } + } + return 0; +} + +static apr_status_t tls_core_free(void *data) +{ + server_rec *base_server = (server_rec *)data; + tls_conf_server_t *sc = tls_conf_server_get(base_server); + + if (sc && sc->global && sc->global->rustls_hello_config) { + rustls_server_config_free(sc->global->rustls_hello_config); + sc->global->rustls_hello_config = NULL; + } + tls_cache_free(base_server); + return APR_SUCCESS; +} + +static apr_status_t load_certified_keys( + apr_array_header_t *keys, server_rec *s, + apr_array_header_t *cert_specs, + tls_cert_reg_t *cert_reg) +{ + apr_status_t rv = APR_SUCCESS; + const rustls_certified_key *ckey; + tls_cert_spec_t *spec; + int i; + + if (cert_specs && cert_specs->nelts > 0) { + for (i = 0; i < cert_specs->nelts; ++i) { + spec = APR_ARRAY_IDX(cert_specs, i, tls_cert_spec_t*); + rv = tls_cert_reg_get_certified_key(cert_reg, s, spec, &ckey); + if (APR_SUCCESS != rv) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10318) + "Failed to load certificate %d[cert=%s(%d), key=%s(%d)] for %s", + i, spec->cert_file, (int)(spec->cert_pem? strlen(spec->cert_pem) : 0), + spec->pkey_file, (int)(spec->pkey_pem? strlen(spec->pkey_pem) : 0), + s->server_hostname); + goto cleanup; + } + assert(ckey); + APR_ARRAY_PUSH(keys, const rustls_certified_key*) = ckey; + } + } +cleanup: + return rv; +} + +static apr_status_t use_local_key( + conn_rec *c, const char *cert_pem, const char *pkey_pem) +{ + tls_conf_conn_t *cc = tls_conf_conn_get(c); + const rustls_certified_key *ckey = NULL; + tls_cert_spec_t spec; + apr_status_t rv = APR_SUCCESS; + + memset(&spec, 0, sizeof(spec)); + spec.cert_pem = cert_pem; + spec.pkey_pem = pkey_pem; + rv = tls_cert_load_cert_key(c->pool, &spec, NULL, &ckey); + if (APR_SUCCESS != rv) goto cleanup; + + cc->local_keys = apr_array_make(c->pool, 2, sizeof(const rustls_certified_key*)); + APR_ARRAY_PUSH(cc->local_keys, const rustls_certified_key*) = ckey; + ckey = NULL; + +cleanup: + if (ckey != NULL) rustls_certified_key_free(ckey); + return rv; +} + +static void add_file_specs( + apr_array_header_t *certificates, + apr_pool_t *p, + apr_array_header_t *cert_files, + apr_array_header_t *key_files) +{ + tls_cert_spec_t *spec; + int i; + + for (i = 0; i < cert_files->nelts; ++i) { + spec = apr_pcalloc(p, sizeof(*spec)); + spec->cert_file = APR_ARRAY_IDX(cert_files, i, const char*); + spec->pkey_file = (i < key_files->nelts)? APR_ARRAY_IDX(key_files, i, const char*) : NULL; + *(const tls_cert_spec_t**)apr_array_push(certificates) = spec; + } +} + +static apr_status_t calc_ciphers( + apr_pool_t *pool, + server_rec *s, + tls_conf_global_t *gc, + const char *proxy, + apr_array_header_t *pref_ciphers, + apr_array_header_t *supp_ciphers, + const apr_array_header_t **pciphers) +{ + apr_array_header_t *ordered_ciphers; + const apr_array_header_t *ciphers; + apr_array_header_t *unsupported = NULL; + rustls_result rr = RUSTLS_RESULT_OK; + apr_status_t rv = APR_SUCCESS; + apr_uint16_t id; + int i; + + + /* remove all suppressed ciphers from the ones supported by rustls */ + ciphers = tls_util_array_uint16_remove(pool, gc->proto->supported_cipher_ids, supp_ciphers); + ordered_ciphers = NULL; + /* if preferred ciphers are actually still present in allowed_ciphers, put + * them into `ciphers` in this order */ + for (i = 0; i < pref_ciphers->nelts; ++i) { + id = APR_ARRAY_IDX(pref_ciphers, i, apr_uint16_t); + ap_log_error(APLOG_MARK, APLOG_TRACE4, rv, s, + "checking preferred cipher %s: %d", + s->server_hostname, id); + if (tls_util_array_uint16_contains(ciphers, id)) { + ap_log_error(APLOG_MARK, APLOG_TRACE4, rv, s, + "checking preferred cipher %s: %d is known", + s->server_hostname, id); + if (ordered_ciphers == NULL) { + ordered_ciphers = apr_array_make(pool, ciphers->nelts, sizeof(apr_uint16_t)); + } + APR_ARRAY_PUSH(ordered_ciphers, apr_uint16_t) = id; + } + else if (!tls_proto_is_cipher_supported(gc->proto, id)) { + ap_log_error(APLOG_MARK, APLOG_TRACE4, rv, s, + "checking preferred cipher %s: %d is unsupported", + s->server_hostname, id); + if (!unsupported) unsupported = apr_array_make(pool, 5, sizeof(apr_uint16_t)); + APR_ARRAY_PUSH(unsupported, apr_uint16_t) = id; + } + } + /* if we found ciphers with preference among allowed_ciphers, + * append all other allowed ciphers. */ + if (ordered_ciphers) { + for (i = 0; i < ciphers->nelts; ++i) { + id = APR_ARRAY_IDX(ciphers, i, apr_uint16_t); + if (!tls_util_array_uint16_contains(ordered_ciphers, id)) { + APR_ARRAY_PUSH(ordered_ciphers, apr_uint16_t) = id; + } + } + ciphers = ordered_ciphers; + } + + if (ciphers == gc->proto->supported_cipher_ids) { + ciphers = NULL; + } + + if (unsupported && unsupported->nelts) { + ap_log_error(APLOG_MARK, APLOG_WARNING, rv, s, APLOGNO(10319) + "Server '%s' has TLS%sCiphersPrefer configured that are not " + "supported by rustls. These will not have an effect: %s", + s->server_hostname, proxy, + tls_proto_get_cipher_names(gc->proto, unsupported, pool)); + } + + if (RUSTLS_RESULT_OK != rr) { + const char *err_descr; + rv = tls_util_rustls_error(pool, rr, &err_descr); + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10320) + "Failed to configure ciphers %s: [%d] %s", + s->server_hostname, (int)rr, err_descr); + } + *pciphers = (APR_SUCCESS == rv)? ciphers : NULL; + return rv; +} + +static apr_status_t get_server_ciphersuites( + const apr_array_header_t **pciphersuites, + apr_pool_t *pool, tls_conf_server_t *sc) +{ + const apr_array_header_t *ciphers, *suites = NULL; + apr_status_t rv = APR_SUCCESS; + + rv = calc_ciphers(pool, sc->server, sc->global, + "", sc->tls_pref_ciphers, sc->tls_supp_ciphers, + &ciphers); + if (APR_SUCCESS != rv) goto cleanup; + + if (ciphers) { + suites = tls_proto_get_rustls_suites( + sc->global->proto, ciphers, pool); + if (APLOGtrace2(sc->server)) { + tls_proto_conf_t *conf = sc->global->proto; + ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, sc->server, + "tls ciphers configured[%s]: %s", + sc->server->server_hostname, + tls_proto_get_cipher_names(conf, ciphers, pool)); + } + } + +cleanup: + *pciphersuites = (APR_SUCCESS == rv)? suites : NULL; + return rv; +} + +static apr_array_header_t *complete_cert_specs( + apr_pool_t *p, tls_conf_server_t *sc) +{ + apr_array_header_t *cert_adds, *key_adds, *specs; + + /* Take the configured certificate specifications and ask + * around for other modules to add specifications to this server. + * This is the way mod_md provides certificates. + * + * If the server then still has no cert specifications, ask + * around for `fallback` certificates which are commonly self-signed, + * temporary ones which let the server startup in order to + * obtain the `real` certificates from sources like ACME. + * Servers will fallbacks will answer all requests with 503. + */ + specs = apr_array_copy(p, sc->cert_specs); + cert_adds = apr_array_make(p, 2, sizeof(const char*)); + key_adds = apr_array_make(p, 2, sizeof(const char*)); + + ap_ssl_add_cert_files(sc->server, p, cert_adds, key_adds); + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, sc->server, + "init server: complete_cert_specs added %d certs", cert_adds->nelts); + add_file_specs(specs, p, cert_adds, key_adds); + + if (apr_is_empty_array(specs)) { + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, sc->server, + "init server: no certs configured, looking for fallback"); + ap_ssl_add_fallback_cert_files(sc->server, p, cert_adds, key_adds); + if (cert_adds->nelts > 0) { + add_file_specs(specs, p, cert_adds, key_adds); + sc->service_unavailable = 1; + ap_log_error(APLOG_MARK, APLOG_INFO, 0, sc->server, APLOGNO(10321) + "Init: %s will respond with '503 Service Unavailable' for now. There " + "are no SSL certificates configured and no other module contributed any.", + sc->server->server_hostname); + } + else if (!sc->base_server) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, sc->server, APLOGNO(10322) + "Init: %s has no certificates configured. Use 'TLSCertificate' to " + "configure a certificate and key file.", + sc->server->server_hostname); + } + } + return specs; +} + +static const rustls_certified_key *select_certified_key( + void* userdata, const rustls_client_hello *hello) +{ + conn_rec *c = userdata; + tls_conf_conn_t *cc; + tls_conf_server_t *sc; + apr_array_header_t *keys; + const rustls_certified_key *clone; + rustls_result rr = RUSTLS_RESULT_OK; + apr_status_t rv; + + ap_assert(c); + cc = tls_conf_conn_get(c); + ap_assert(cc); + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, "client hello select certified key"); + if (!cc || !cc->server) goto cleanup; + sc = tls_conf_server_get(cc->server); + if (!sc) goto cleanup; + + cc->key = NULL; + cc->key_cloned = 0; + if (cc->local_keys && cc->local_keys->nelts > 0) { + keys = cc->local_keys; + } + else { + keys = sc->certified_keys; + } + if (!keys || keys->nelts <= 0) goto cleanup; + + rr = rustls_client_hello_select_certified_key(hello, + (const rustls_certified_key**)keys->elts, (size_t)keys->nelts, &cc->key); + if (RUSTLS_RESULT_OK != rr) goto cleanup; + + if (APR_SUCCESS == tls_ocsp_update_key(c, cc->key, &clone)) { + /* got OCSP response data for it, meaning the key was cloned and we need to remember */ + cc->key_cloned = 1; + cc->key = clone; + } + if (APLOGctrace2(c)) { + const char *key_id = tls_cert_reg_get_id(sc->global->cert_reg, cc->key); + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, APLOGNO(10323) + "client hello selected key: %s", key_id? key_id : "unknown"); + } + return cc->key; + +cleanup: + if (RUSTLS_RESULT_OK != rr) { + const char *err_descr; + rv = tls_util_rustls_error(c->pool, rr, &err_descr); + ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c, APLOGNO(10324) + "Failed to select certified key: [%d] %s", (int)rr, err_descr); + } + return NULL; +} + +static apr_status_t server_conf_setup( + apr_pool_t *p, apr_pool_t *ptemp, tls_conf_server_t *sc, tls_conf_global_t *gc) +{ + apr_array_header_t *cert_specs; + apr_status_t rv = APR_SUCCESS; + + /* TODO: most code has been stripped here with the changes in rustls-ffi v0.8.0 + * and this means that any errors are only happening at connection setup, which + * is too late. + */ + (void)p; + ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, sc->server, + "init server: %s", sc->server->server_hostname); + + if (sc->client_auth != TLS_CLIENT_AUTH_NONE && !sc->client_ca) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, sc->server, APLOGNO(10325) + "TLSClientAuthentication is enabled for %s, but no client CA file is set. " + "Use 'TLSClientCA <file>' to specify the trust anchors.", + sc->server->server_hostname); + rv = APR_EINVAL; goto cleanup; + } + + cert_specs = complete_cert_specs(ptemp, sc); + sc->certified_keys = apr_array_make(p, 3, sizeof(rustls_certified_key *)); + rv = load_certified_keys(sc->certified_keys, sc->server, cert_specs, gc->cert_reg); + if (APR_SUCCESS != rv) goto cleanup; + + rv = get_server_ciphersuites(&sc->ciphersuites, p, sc); + if (APR_SUCCESS != rv) goto cleanup; + + ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, sc->server, + "init server: %s with %d certificates loaded", + sc->server->server_hostname, sc->certified_keys->nelts); +cleanup: + return rv; +} + +static apr_status_t get_proxy_ciphers(const apr_array_header_t **pciphersuites, + apr_pool_t *pool, tls_conf_proxy_t *pc) +{ + const apr_array_header_t *ciphers, *suites = NULL; + apr_status_t rv = APR_SUCCESS; + + rv = calc_ciphers(pool, pc->defined_in, pc->global, + "", pc->proxy_pref_ciphers, pc->proxy_supp_ciphers, &ciphers); + if (APR_SUCCESS != rv) goto cleanup; + + if (ciphers) { + suites = tls_proto_get_rustls_suites(pc->global->proto, ciphers, pool); + /* this changed the default rustls ciphers, configure it. */ + if (APLOGtrace2(pc->defined_in)) { + tls_proto_conf_t *conf = pc->global->proto; + ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, pc->defined_in, + "tls proxy ciphers configured[%s]: %s", + pc->defined_in->server_hostname, + tls_proto_get_cipher_names(conf, ciphers, pool)); + } + } + +cleanup: + *pciphersuites = (APR_SUCCESS == rv)? suites : NULL; + return rv; +} + +static apr_status_t proxy_conf_setup( + apr_pool_t *p, apr_pool_t *ptemp, tls_conf_proxy_t *pc, tls_conf_global_t *gc) +{ + apr_status_t rv = APR_SUCCESS; + + (void)p; (void)ptemp; + ap_assert(pc->defined_in); + pc->global = gc; + + if (pc->proxy_ca && strcasecmp(pc->proxy_ca, "default")) { + ap_log_error(APLOG_MARK, APLOG_TRACE2, rv, pc->defined_in, + "proxy: will use roots in %s from %s", + pc->defined_in->server_hostname, pc->proxy_ca); + } + else { + ap_log_error(APLOG_MARK, APLOG_WARNING, rv, pc->defined_in, + "proxy: there is no TLSProxyCA configured in %s which means " + "the certificates of remote servers contacted from here will not be trusted.", + pc->defined_in->server_hostname); + } + + if (pc->proxy_protocol_min > 0) { + apr_array_header_t *tls_versions; + + ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, pc->defined_in, + "init server: set proxy protocol min version %04x", pc->proxy_protocol_min); + tls_versions = tls_proto_create_versions_plus( + gc->proto, (apr_uint16_t)pc->proxy_protocol_min, ptemp); + if (tls_versions->nelts > 0) { + if (pc->proxy_protocol_min != APR_ARRAY_IDX(tls_versions, 0, apr_uint16_t)) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, pc->defined_in, APLOGNO(10326) + "Init: the minimum proxy protocol version configured for %s (%04x) " + "is not supported and version %04x was selected instead.", + pc->defined_in->server_hostname, pc->proxy_protocol_min, + APR_ARRAY_IDX(tls_versions, 0, apr_uint16_t)); + } + } + else { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, pc->defined_in, APLOGNO(10327) + "Unable to configure the proxy protocol version for %s: " + "neither the configured minimum version (%04x), nor any higher one is " + "available.", pc->defined_in->server_hostname, pc->proxy_protocol_min); + rv = APR_ENOTIMPL; goto cleanup; + } + } + +#if TLS_MACHINE_CERTS + rv = load_certified_keys(pc->machine_certified_keys, pc->defined_in, + pc->machine_cert_specs, gc->cert_reg); + if (APR_SUCCESS != rv) goto cleanup; +#endif + +cleanup: + return rv; +} + +static const rustls_certified_key *extract_client_hello_values( + void* userdata, const rustls_client_hello *hello) +{ + conn_rec *c = userdata; + tls_conf_conn_t *cc = tls_conf_conn_get(c); + size_t i, len; + unsigned short n; + + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, "extract client hello values"); + if (!cc) goto cleanup; + cc->client_hello_seen = 1; + if (hello->server_name.len > 0) { + cc->sni_hostname = apr_pstrndup(c->pool, hello->server_name.data, hello->server_name.len); + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "sni detected: %s", cc->sni_hostname); + } + else { + cc->sni_hostname = NULL; + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "no sni from client"); + } + if (APLOGctrace4(c) && hello->signature_schemes.len > 0) { + for (i = 0; i < hello->signature_schemes.len; ++i) { + n = hello->signature_schemes.data[i]; + ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, c, + "client supports signature scheme: %x", (int)n); + } + } + if ((len = rustls_slice_slice_bytes_len(hello->alpn)) > 0) { + apr_array_header_t *alpn = apr_array_make(c->pool, 5, sizeof(const char*)); + const char *protocol; + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "ALPN: client proposes %d protocols", (int)len); + for (i = 0; i < len; ++i) { + rustls_slice_bytes rs = rustls_slice_slice_bytes_get(hello->alpn, i); + protocol = apr_pstrndup(c->pool, (const char*)rs.data, rs.len); + APR_ARRAY_PUSH(alpn, const char*) = protocol; + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, + "ALPN: client proposes %d: `%s`", (int)i, protocol); + } + cc->alpn = alpn; + } + else { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, "ALPN: no alpn proposed by client"); + } +cleanup: + return NULL; +} + +static apr_status_t setup_hello_config(apr_pool_t *p, server_rec *base_server, tls_conf_global_t *gc) +{ + rustls_server_config_builder *builder; + rustls_result rr = RUSTLS_RESULT_OK; + apr_status_t rv = APR_SUCCESS; + + builder = rustls_server_config_builder_new(); + if (!builder) { + rr = RUSTLS_RESULT_PANIC; goto cleanup; + } + rustls_server_config_builder_set_hello_callback(builder, extract_client_hello_values); + gc->rustls_hello_config = rustls_server_config_builder_build(builder); + if (!gc->rustls_hello_config) { + rr = RUSTLS_RESULT_PANIC; goto cleanup; + } + +cleanup: + if (RUSTLS_RESULT_OK != rr) { + const char *err_descr = NULL; + rv = tls_util_rustls_error(p, rr, &err_descr); + ap_log_error(APLOG_MARK, APLOG_ERR, rv, base_server, APLOGNO(10328) + "Failed to init generic hello config: [%d] %s", (int)rr, err_descr); + goto cleanup; + } + return rv; +} + +static apr_status_t init_incoming(apr_pool_t *p, apr_pool_t *ptemp, server_rec *base_server) +{ + tls_conf_server_t *sc = tls_conf_server_get(base_server); + tls_conf_global_t *gc = sc->global; + server_rec *s; + apr_status_t rv = APR_ENOMEM; + + ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, base_server, "tls_core_init incoming"); + apr_pool_cleanup_register(p, base_server, tls_core_free, + apr_pool_cleanup_null); + + rv = tls_proto_post_config(p, ptemp, base_server); + if (APR_SUCCESS != rv) goto cleanup; + + for (s = base_server; s; s = s->next) { + sc = tls_conf_server_get(s); + assert(sc); + ap_assert(sc->global == gc); + + /* If 'TLSEngine' has been configured, use those addresses to + * decide if we are enabled on this server. */ + sc->base_server = (s == base_server); + sc->enabled = we_listen_on(gc, s, sc)? TLS_FLAG_TRUE : TLS_FLAG_FALSE; + } + + rv = tls_cache_post_config(p, ptemp, base_server); + if (APR_SUCCESS != rv) goto cleanup; + + rv = setup_hello_config(p, base_server, gc); + if (APR_SUCCESS != rv) goto cleanup; + + /* Setup server configs and collect all certificates we use. */ + gc->cert_reg = tls_cert_reg_make(p); + gc->stores = tls_cert_root_stores_make(p); + gc->verifiers = tls_cert_verifiers_make(p, gc->stores); + for (s = base_server; s; s = s->next) { + sc = tls_conf_server_get(s); + rv = tls_conf_server_apply_defaults(sc, p); + if (APR_SUCCESS != rv) goto cleanup; + if (sc->enabled != TLS_FLAG_TRUE) continue; + rv = server_conf_setup(p, ptemp, sc, gc); + if (APR_SUCCESS != rv) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, "server setup failed: %s", + s->server_hostname); + goto cleanup; + } + } + +cleanup: + if (APR_SUCCESS != rv) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, base_server, "error during post_config"); + } + return rv; +} + +static apr_status_t init_outgoing(apr_pool_t *p, apr_pool_t *ptemp, server_rec *base_server) +{ + tls_conf_server_t *sc = tls_conf_server_get(base_server); + tls_conf_global_t *gc = sc->global; + tls_conf_dir_t *dc; + tls_conf_proxy_t *pc; + server_rec *s; + apr_status_t rv = APR_SUCCESS; + int i; + + (void)p; (void)ptemp; + (void)gc; + ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, base_server, "tls_core_init outgoing"); + ap_assert(gc->mod_proxy_post_config_done); + /* Collect all proxy'ing default server dir configs. + * All <Proxy> section dir_configs should already be there - if there were any. */ + for (s = base_server; s; s = s->next) { + dc = tls_conf_dir_server_get(s); + rv = tls_conf_dir_apply_defaults(dc, p); + if (APR_SUCCESS != rv) goto cleanup; + if (dc->proxy_enabled != TLS_FLAG_TRUE) continue; + dc->proxy_config = tls_conf_proxy_make(p, dc, gc, s); + ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s, "%s: adding proxy_conf to globals", + s->server_hostname); + APR_ARRAY_PUSH(gc->proxy_configs, tls_conf_proxy_t*) = dc->proxy_config; + } + /* Now gc->proxy_configs contains all configurations we need to possibly + * act on for outgoing connections. */ + for (i = 0; i < gc->proxy_configs->nelts; ++i) { + pc = APR_ARRAY_IDX(gc->proxy_configs, i, tls_conf_proxy_t*); + rv = proxy_conf_setup(p, ptemp, pc, gc); + if (APR_SUCCESS != rv) goto cleanup; + } + +cleanup: + return rv; +} + +apr_status_t tls_core_init(apr_pool_t *p, apr_pool_t *ptemp, server_rec *base_server) +{ + tls_conf_server_t *sc = tls_conf_server_get(base_server); + tls_conf_global_t *gc = sc->global; + apr_status_t rv = APR_SUCCESS; + + ap_assert(gc); + if (TLS_CONF_ST_INIT == gc->status) { + rv = init_incoming(p, ptemp, base_server); + if (APR_SUCCESS != rv) goto cleanup; + gc->status = TLS_CONF_ST_INCOMING_DONE; + } + if (TLS_CONF_ST_INCOMING_DONE == gc->status) { + if (!gc->mod_proxy_post_config_done) goto cleanup; + + rv = init_outgoing(p, ptemp, base_server); + if (APR_SUCCESS != rv) goto cleanup; + gc->status = TLS_CONF_ST_OUTGOING_DONE; + } + if (TLS_CONF_ST_OUTGOING_DONE == gc->status) { + /* register all loaded certificates for OCSP stapling */ + rv = tls_ocsp_prime_certs(gc, p, base_server); + if (APR_SUCCESS != rv) goto cleanup; + + if (gc->verifiers) tls_cert_verifiers_clear(gc->verifiers); + if (gc->stores) tls_cert_root_stores_clear(gc->stores); + gc->status = TLS_CONF_ST_DONE; + } +cleanup: + return rv; +} + +static apr_status_t tls_core_conn_free(void *data) +{ + tls_conf_conn_t *cc = data; + + /* free all rustls things we are owning. */ + if (cc->rustls_connection) { + rustls_connection_free(cc->rustls_connection); + cc->rustls_connection = NULL; + } + if (cc->rustls_server_config) { + rustls_server_config_free(cc->rustls_server_config); + cc->rustls_server_config = NULL; + } + if (cc->rustls_client_config) { + rustls_client_config_free(cc->rustls_client_config); + cc->rustls_client_config = NULL; + } + if (cc->key_cloned && cc->key) { + rustls_certified_key_free(cc->key); + cc->key = NULL; + } + if (cc->local_keys) { + const rustls_certified_key *key; + int i; + + for (i = 0; i < cc->local_keys->nelts; ++i) { + key = APR_ARRAY_IDX(cc->local_keys, i, const rustls_certified_key*); + rustls_certified_key_free(key); + } + apr_array_clear(cc->local_keys); + } + return APR_SUCCESS; +} + +static tls_conf_conn_t *cc_get_or_make(conn_rec *c) +{ + tls_conf_conn_t *cc = tls_conf_conn_get(c); + if (!cc) { + cc = apr_pcalloc(c->pool, sizeof(*cc)); + cc->server = c->base_server; + cc->state = TLS_CONN_ST_INIT; + tls_conf_conn_set(c, cc); + apr_pool_cleanup_register(c->pool, cc, tls_core_conn_free, + apr_pool_cleanup_null); + } + return cc; +} + +void tls_core_conn_disable(conn_rec *c) +{ + tls_conf_conn_t *cc; + cc = cc_get_or_make(c); + if (cc->state == TLS_CONN_ST_INIT) { + cc->state = TLS_CONN_ST_DISABLED; + } +} + +void tls_core_conn_bind(conn_rec *c, ap_conf_vector_t *dir_conf) +{ + tls_conf_conn_t *cc = cc_get_or_make(c); + cc->dc = dir_conf? ap_get_module_config(dir_conf, &tls_module) : NULL; +} + + +static apr_status_t init_outgoing_connection(conn_rec *c) +{ + tls_conf_conn_t *cc = tls_conf_conn_get(c); + tls_conf_proxy_t *pc; + const apr_array_header_t *ciphersuites = NULL; + apr_array_header_t *tls_versions = NULL; + rustls_client_config_builder *builder = NULL; + rustls_root_cert_store *ca_store = NULL; + const char *hostname = NULL, *alpn_note = NULL; + rustls_result rr = RUSTLS_RESULT_OK; + apr_status_t rv = APR_SUCCESS; + + ap_assert(cc->outgoing); + ap_assert(cc->dc); + pc = cc->dc->proxy_config; + ap_assert(pc); + + hostname = apr_table_get(c->notes, "proxy-request-hostname"); + alpn_note = apr_table_get(c->notes, "proxy-request-alpn-protos"); + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, c->base_server, + "setup_outgoing: to %s [ALPN: %s] from configuration in %s" + " using CA %s", hostname, alpn_note, pc->defined_in->server_hostname, pc->proxy_ca); + + rv = get_proxy_ciphers(&ciphersuites, c->pool, pc); + if (APR_SUCCESS != rv) goto cleanup; + + if (pc->proxy_protocol_min > 0) { + tls_versions = tls_proto_create_versions_plus( + pc->global->proto, (apr_uint16_t)pc->proxy_protocol_min, c->pool); + } + + if (ciphersuites && ciphersuites->nelts > 0 + && tls_versions && tls_versions->nelts >= 0) { + rr = rustls_client_config_builder_new_custom( + (const struct rustls_supported_ciphersuite *const *)ciphersuites->elts, + (size_t)ciphersuites->nelts, + (const uint16_t *)tls_versions->elts, (size_t)tls_versions->nelts, + &builder); + if (RUSTLS_RESULT_OK != rr) goto cleanup; + } + else { + builder = rustls_client_config_builder_new(); + if (NULL == builder) { + rv = APR_ENOMEM; + goto cleanup; + } + } + + if (pc->proxy_ca && strcasecmp(pc->proxy_ca, "default")) { + rv = tls_cert_root_stores_get(pc->global->stores, pc->proxy_ca, &ca_store); + if (APR_SUCCESS != rv) goto cleanup; + rustls_client_config_builder_use_roots(builder, ca_store); + } + +#if TLS_MACHINE_CERTS + if (pc->machine_certified_keys->nelts > 0) { + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, c->base_server, + "setup_outgoing: adding %d client certificate", (int)pc->machine_certified_keys->nelts); + rr = rustls_client_config_builder_set_certified_key( + builder, (const rustls_certified_key**)pc->machine_certified_keys->elts, + (size_t)pc->machine_certified_keys->nelts); + if (RUSTLS_RESULT_OK != rr) goto cleanup; + } +#endif + + if (hostname) { + rustls_client_config_builder_set_enable_sni(builder, true); + } + else { + hostname = "unknown.proxy.local"; + rustls_client_config_builder_set_enable_sni(builder, false); + } + + if (alpn_note) { + apr_array_header_t *alpn_proposed = NULL; + char *p, *last; + apr_size_t len; + + alpn_proposed = apr_array_make(c->pool, 3, sizeof(const char*)); + p = apr_pstrdup(c->pool, alpn_note); + while ((p = apr_strtok(p, ", ", &last))) { + len = (apr_size_t)(last - p - (*last? 1 : 0)); + if (len > 255) { + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(10329) + "ALPN proxy protocol identifier too long: %s", p); + rv = APR_EGENERAL; + goto cleanup; + } + APR_ARRAY_PUSH(alpn_proposed, const char*) = apr_pstrndup(c->pool, p, len); + p = NULL; + } + if (alpn_proposed->nelts > 0) { + apr_array_header_t *rustls_protocols; + const char* proto; + rustls_slice_bytes bytes; + int i; + + rustls_protocols = apr_array_make(c->pool, alpn_proposed->nelts, sizeof(rustls_slice_bytes)); + for (i = 0; i < alpn_proposed->nelts; ++i) { + proto = APR_ARRAY_IDX(alpn_proposed, i, const char*); + bytes.data = (const unsigned char*)proto; + bytes.len = strlen(proto); + APR_ARRAY_PUSH(rustls_protocols, rustls_slice_bytes) = bytes; + } + + rr = rustls_client_config_builder_set_alpn_protocols(builder, + (rustls_slice_bytes*)rustls_protocols->elts, (size_t)rustls_protocols->nelts); + if (RUSTLS_RESULT_OK != rr) goto cleanup; + + ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, c->base_server, + "setup_outgoing: to %s, added %d ALPN protocols from %s", + hostname, rustls_protocols->nelts, alpn_note); + } + } + + cc->rustls_client_config = rustls_client_config_builder_build(builder); + builder = NULL; + + rr = rustls_client_connection_new(cc->rustls_client_config, hostname, &cc->rustls_connection); + if (RUSTLS_RESULT_OK != rr) goto cleanup; + rustls_connection_set_userdata(cc->rustls_connection, c); + +cleanup: + if (builder != NULL) rustls_client_config_builder_free(builder); + if (RUSTLS_RESULT_OK != rr) { + const char *err_descr = NULL; + rv = tls_util_rustls_error(c->pool, rr, &err_descr); + ap_log_error(APLOG_MARK, APLOG_ERR, rv, cc->server, APLOGNO(10330) + "Failed to init pre_session for outgoing %s to %s: [%d] %s", + cc->server->server_hostname, hostname, (int)rr, err_descr); + c->aborted = 1; + cc->state = TLS_CONN_ST_DISABLED; + goto cleanup; + } + return rv; +} + +int tls_core_pre_conn_init(conn_rec *c) +{ + tls_conf_server_t *sc = tls_conf_server_get(c->base_server); + tls_conf_conn_t *cc; + + cc = cc_get_or_make(c); + if (cc->state == TLS_CONN_ST_INIT) { + /* Need to decide if we TLS this connection or not */ + int enabled = +#if AP_MODULE_MAGIC_AT_LEAST(20120211, 109) + !c->outgoing && +#endif + sc->enabled == TLS_FLAG_TRUE; + cc->state = enabled? TLS_CONN_ST_CLIENT_HELLO : TLS_CONN_ST_DISABLED; + cc->client_auth = sc->client_auth; + ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, c->base_server, + "tls_core_conn_init: %s for tls: %s", + enabled? "enabled" : "disabled", c->base_server->server_hostname); + } + else if (cc->state == TLS_CONN_ST_DISABLED) { + ap_log_error(APLOG_MARK, APLOG_TRACE4, 0, c->base_server, + "tls_core_conn_init, not our connection: %s", + c->base_server->server_hostname); + goto cleanup; + } + +cleanup: + return TLS_CONN_ST_IS_ENABLED(cc)? OK : DECLINED; +} + +apr_status_t tls_core_conn_init(conn_rec *c) +{ + tls_conf_server_t *sc = tls_conf_server_get(c->base_server); + tls_conf_conn_t *cc; + rustls_result rr = RUSTLS_RESULT_OK; + apr_status_t rv = APR_SUCCESS; + + cc = tls_conf_conn_get(c); + if (cc && TLS_CONN_ST_IS_ENABLED(cc) && !cc->rustls_connection) { + if (cc->outgoing) { + rv = init_outgoing_connection(c); + if (APR_SUCCESS != rv) goto cleanup; + } + else { + /* Use a generic rustls_connection with its defaults, which we feed + * the first TLS bytes from the client. Its Hello message will trigger + * our callback where we can inspect the (possibly) supplied SNI and + * select another server. + */ + rr = rustls_server_connection_new(sc->global->rustls_hello_config, &cc->rustls_connection); + if (RUSTLS_RESULT_OK != rr) goto cleanup; + /* we might refuse requests on this connection, e.g. ACME challenge */ + cc->service_unavailable = sc->service_unavailable; + } + rustls_connection_set_userdata(cc->rustls_connection, c); + } + +cleanup: + if (RUSTLS_RESULT_OK != rr) { + const char *err_descr = NULL; + rv = tls_util_rustls_error(c->pool, rr, &err_descr); + ap_log_error(APLOG_MARK, APLOG_ERR, rv, sc->server, APLOGNO(10331) + "Failed to init TLS connection for server %s: [%d] %s", + sc->server->server_hostname, (int)rr, err_descr); + c->aborted = 1; + cc->state = TLS_CONN_ST_DISABLED; + goto cleanup; + } + return rv; +} + +static int find_vhost(void *sni_hostname, conn_rec *c, server_rec *s) +{ + if (tls_util_name_matches_server(sni_hostname, s)) { + tls_conf_conn_t *cc = tls_conf_conn_get(c); + cc->server = s; + return 1; + } + return 0; +} + +static apr_status_t select_application_protocol( + conn_rec *c, server_rec *s, rustls_server_config_builder *builder) +{ + tls_conf_conn_t *cc = tls_conf_conn_get(c); + const char *proposed; + rustls_result rr = RUSTLS_RESULT_OK; + apr_status_t rv = APR_SUCCESS; + + /* The server always has a protocol it uses, normally "http/1.1". + * if the client, via ALPN, proposes protocols, they are in + * order of preference. + * We propose those to modules registered in the server and + * get the protocol back that someone is willing to run on this + * connection. + * If this is different from what the connection already does, + * we tell the server (and all protocol modules) to switch. + * If successful, we announce that protocol back to the client as + * our only ALPN protocol and then do the 'real' handshake. + */ + cc->application_protocol = ap_get_protocol(c); + if (cc->alpn && cc->alpn->nelts > 0) { + rustls_slice_bytes rsb; + + proposed = ap_select_protocol(c, NULL, s, cc->alpn); + if (!proposed) { + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, c, + "ALPN: no protocol selected in server"); + goto cleanup; + } + + if (strcmp(proposed, cc->application_protocol)) { + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, c, + "ALPN: switching protocol from `%s` to `%s`", cc->application_protocol, proposed); + rv = ap_switch_protocol(c, NULL, cc->server, proposed); + if (APR_SUCCESS != rv) goto cleanup; + } + + rsb.data = (const unsigned char*)proposed; + rsb.len = strlen(proposed); + rr = rustls_server_config_builder_set_alpn_protocols(builder, &rsb, 1); + if (RUSTLS_RESULT_OK != rr) goto cleanup; + + cc->application_protocol = proposed; + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, c, + "ALPN: using connection protocol `%s`", cc->application_protocol); + + /* protocol was switched, this could be a challenge protocol + * such as "acme-tls/1". Give handlers the opportunity to + * override the certificate for this connection. */ + if (strcmp("h2", proposed) && strcmp("http/1.1", proposed)) { + const char *cert_pem = NULL, *key_pem = NULL; + if (ap_ssl_answer_challenge(c, cc->sni_hostname, &cert_pem, &key_pem)) { + /* With ACME we can have challenge connections to a unknown domains + * that need to be answered with a special certificate and will + * otherwise not answer any requests. See RFC 8555 */ + rv = use_local_key(c, cert_pem, key_pem); + if (APR_SUCCESS != rv) goto cleanup; + + cc->service_unavailable = 1; + cc->client_auth = TLS_CLIENT_AUTH_NONE; + } + } + } + +cleanup: + if (rr != RUSTLS_RESULT_OK) { + const char *err_descr = NULL; + rv = tls_util_rustls_error(c->pool, rr, &err_descr); + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10332) + "Failed to init session for server %s: [%d] %s", + s->server_hostname, (int)rr, err_descr); + c->aborted = 1; + goto cleanup; + } + return rv; +} + +static apr_status_t build_server_connection(rustls_connection **pconnection, + const rustls_server_config **pconfig, + conn_rec *c) +{ + tls_conf_conn_t *cc = tls_conf_conn_get(c); + tls_conf_server_t *sc; + const apr_array_header_t *tls_versions = NULL; + rustls_server_config_builder *builder = NULL; + const rustls_server_config *config = NULL; + rustls_connection *rconnection = NULL; + rustls_result rr = RUSTLS_RESULT_OK; + apr_status_t rv = APR_SUCCESS; + + sc = tls_conf_server_get(cc->server); + + if (sc->tls_protocol_min > 0) { + ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, sc->server, + "init server: set protocol min version %04x", sc->tls_protocol_min); + tls_versions = tls_proto_create_versions_plus( + sc->global->proto, (apr_uint16_t)sc->tls_protocol_min, c->pool); + if (tls_versions->nelts > 0) { + if (sc->tls_protocol_min != APR_ARRAY_IDX(tls_versions, 0, apr_uint16_t)) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, sc->server, APLOGNO(10333) + "Init: the minimum protocol version configured for %s (%04x) " + "is not supported and version %04x was selected instead.", + sc->server->server_hostname, sc->tls_protocol_min, + APR_ARRAY_IDX(tls_versions, 0, apr_uint16_t)); + } + } + else { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, sc->server, APLOGNO(10334) + "Unable to configure the protocol version for %s: " + "neither the configured minimum version (%04x), nor any higher one is " + "available.", sc->server->server_hostname, sc->tls_protocol_min); + rv = APR_ENOTIMPL; goto cleanup; + } + } + else if (sc->ciphersuites && sc->ciphersuites->nelts > 0) { + /* FIXME: rustls-ffi current has not way to make a builder with ALL_PROTOCOL_VERSIONS */ + tls_versions = tls_proto_create_versions_plus(sc->global->proto, 0, c->pool); + } + + if (sc->ciphersuites && sc->ciphersuites->nelts > 0 + && tls_versions && tls_versions->nelts >= 0) { + rr = rustls_server_config_builder_new_custom( + (const struct rustls_supported_ciphersuite *const *)sc->ciphersuites->elts, + (size_t)sc->ciphersuites->nelts, + (const uint16_t *)tls_versions->elts, (size_t)tls_versions->nelts, + &builder); + if (RUSTLS_RESULT_OK != rr) goto cleanup; + } + else { + builder = rustls_server_config_builder_new(); + if (NULL == builder) { + rv = APR_ENOMEM; + goto cleanup; + } + } + + /* decide on the application protocol, this may change other + * settings like client_auth. */ + rv = select_application_protocol(c, cc->server, builder); + + if (cc->client_auth != TLS_CLIENT_AUTH_NONE) { + ap_assert(sc->client_ca); /* checked in server_setup */ + if (cc->client_auth == TLS_CLIENT_AUTH_REQUIRED) { + const rustls_client_cert_verifier *verifier; + rv = tls_cert_client_verifiers_get(sc->global->verifiers, sc->client_ca, &verifier); + if (APR_SUCCESS != rv) goto cleanup; + rustls_server_config_builder_set_client_verifier(builder, verifier); + } + else { + const rustls_client_cert_verifier_optional *verifier; + rv = tls_cert_client_verifiers_get_optional(sc->global->verifiers, sc->client_ca, &verifier); + if (APR_SUCCESS != rv) goto cleanup; + rustls_server_config_builder_set_client_verifier_optional(builder, verifier); + } + } + + rustls_server_config_builder_set_hello_callback(builder, select_certified_key); + + rr = rustls_server_config_builder_set_ignore_client_order( + builder, sc->honor_client_order == TLS_FLAG_FALSE); + if (RUSTLS_RESULT_OK != rr) goto cleanup; + + rv = tls_cache_init_server(builder, sc->server); + if (APR_SUCCESS != rv) goto cleanup; + + config = rustls_server_config_builder_build(builder); + builder = NULL; + if (!config) { + rv = APR_ENOMEM; goto cleanup; + } + + rr = rustls_server_connection_new(config, &rconnection); + if (RUSTLS_RESULT_OK != rr) goto cleanup; + rustls_connection_set_userdata(rconnection, c); + +cleanup: + if (rr != RUSTLS_RESULT_OK) { + const char *err_descr = NULL; + rv = tls_util_rustls_error(c->pool, rr, &err_descr); + ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, sc->server, APLOGNO(10335) + "Failed to init session for server %s: [%d] %s", + sc->server->server_hostname, (int)rr, err_descr); + } + if (APR_SUCCESS == rv) { + *pconfig = config; + *pconnection = rconnection; + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, sc->server, + "tls_core_conn_server_init done: %s", + sc->server->server_hostname); + } + else { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, sc->server, APLOGNO(10336) + "Failed to init session for server %s", + sc->server->server_hostname); + c->aborted = 1; + if (config) rustls_server_config_free(config); + if (builder) rustls_server_config_builder_free(builder); + } + return rv; +} + +apr_status_t tls_core_conn_seen_client_hello(conn_rec *c) +{ + tls_conf_conn_t *cc = tls_conf_conn_get(c); + tls_conf_server_t *sc; + apr_status_t rv = APR_SUCCESS; + int sni_match = 0; + + /* The initial rustls generic session has been fed the client hello and + * we have extracted SNI and ALPN values (so present). + * Time to select the actual server_rec and application protocol that + * will be used on this connection. */ + ap_assert(cc); + sc = tls_conf_server_get(cc->server); + if (!cc->client_hello_seen) goto cleanup; + + if (cc->sni_hostname) { + if (ap_vhost_iterate_given_conn(c, find_vhost, (void*)cc->sni_hostname)) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c, APLOGNO(10337) + "vhost_init: virtual host found for SNI '%s'", cc->sni_hostname); + sni_match = 1; + } + else if (tls_util_name_matches_server(cc->sni_hostname, ap_server_conf)) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c, APLOGNO(10338) + "vhost_init: virtual host NOT found, but base server[%s] matches SNI '%s'", + ap_server_conf->server_hostname, cc->sni_hostname); + cc->server = ap_server_conf; + sni_match = 1; + } + else if (sc->strict_sni == TLS_FLAG_FALSE) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c, APLOGNO(10339) + "vhost_init: no virtual host found, relaxed SNI checking enabled, SNI '%s'", + cc->sni_hostname); + } + else { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c, APLOGNO(10340) + "vhost_init: no virtual host, nor base server[%s] matches SNI '%s'", + c->base_server->server_hostname, cc->sni_hostname); + cc->server = sc->global->ap_server; + rv = APR_NOTFOUND; goto cleanup; + } + } + else { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(10341) + "vhost_init: no SNI hostname provided by client"); + } + + /* reinit, we might have a new server selected */ + sc = tls_conf_server_get(cc->server); + /* on relaxed SNI matches, we do not enforce the 503 of fallback + * certificates. */ + if (!cc->service_unavailable) { + cc->service_unavailable = sni_match? sc->service_unavailable : 0; + } + + /* if found or not, cc->server will be the server we use now to do + * the real handshake and, if successful, the traffic after that. + * Free the current session and create the real one for the + * selected server. */ + if (cc->rustls_server_config) { + rustls_server_config_free(cc->rustls_server_config); + cc->rustls_server_config = NULL; + } + rustls_connection_free(cc->rustls_connection); + cc->rustls_connection = NULL; + + rv = build_server_connection(&cc->rustls_connection, &cc->rustls_server_config, c); + +cleanup: + return rv; +} + +apr_status_t tls_core_conn_post_handshake(conn_rec *c) +{ + tls_conf_conn_t *cc = tls_conf_conn_get(c); + tls_conf_server_t *sc = tls_conf_server_get(cc->server); + const rustls_supported_ciphersuite *rsuite; + const rustls_certificate *cert; + apr_status_t rv = APR_SUCCESS; + + if (rustls_connection_is_handshaking(cc->rustls_connection)) { + rv = APR_EGENERAL; + ap_log_error(APLOG_MARK, APLOG_ERR, rv, cc->server, APLOGNO(10342) + "post handshake, but rustls claims to still be handshaking: %s", + cc->server->server_hostname); + goto cleanup; + } + + cc->tls_protocol_id = rustls_connection_get_protocol_version(cc->rustls_connection); + cc->tls_protocol_name = tls_proto_get_version_name(sc->global->proto, + cc->tls_protocol_id, c->pool); + rsuite = rustls_connection_get_negotiated_ciphersuite(cc->rustls_connection); + if (!rsuite) { + rv = APR_EGENERAL; + ap_log_error(APLOG_MARK, APLOG_ERR, rv, cc->server, APLOGNO(10343) + "post handshake, but rustls does not report negotiated cipher suite: %s", + cc->server->server_hostname); + goto cleanup; + } + cc->tls_cipher_id = rustls_supported_ciphersuite_get_suite(rsuite); + cc->tls_cipher_name = tls_proto_get_cipher_name(sc->global->proto, + cc->tls_cipher_id, c->pool); + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "post_handshake %s: %s [%s]", + cc->server->server_hostname, cc->tls_protocol_name, cc->tls_cipher_name); + + cert = rustls_connection_get_peer_certificate(cc->rustls_connection, 0); + if (cert) { + size_t i = 0; + + cc->peer_certs = apr_array_make(c->pool, 5, sizeof(const rustls_certificate*)); + while (cert) { + APR_ARRAY_PUSH(cc->peer_certs, const rustls_certificate*) = cert; + cert = rustls_connection_get_peer_certificate(cc->rustls_connection, ++i); + } + } + if (!cc->peer_certs && sc->client_auth == TLS_CLIENT_AUTH_REQUIRED) { + ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(10344) + "A client certificate is required, but no acceptable certificate was presented."); + rv = APR_ECONNABORTED; + } + + rv = tls_var_handshake_done(c); +cleanup: + return rv; +} + +/** + * Return != 0, if a connection also serve requests for server <other>. + */ +static int tls_conn_compatible_for(tls_conf_conn_t *cc, server_rec *other) +{ + tls_conf_server_t *oc, *sc; + const rustls_certified_key *sk, *ok; + int i; + + /* - differences in certificates are the responsibility of the client. + * if it thinks the SNI server works for r->server, we are fine with that. + * - if there are differences in requirements to client certificates, we + * need to deny the request. + */ + if (!cc->server || !other) return 0; + if (cc->server == other) return 1; + oc = tls_conf_server_get(other); + if (!oc) return 0; + sc = tls_conf_server_get(cc->server); + if (!sc) return 0; + + /* same certified keys used? */ + if (sc->certified_keys->nelts != oc->certified_keys->nelts) return 0; + for (i = 0; i < sc->certified_keys->nelts; ++i) { + sk = APR_ARRAY_IDX(sc->certified_keys, i, const rustls_certified_key*); + ok = APR_ARRAY_IDX(oc->certified_keys, i, const rustls_certified_key*); + if (sk != ok) return 0; + } + + /* If the connection TLS version is below other other min one, no */ + if (oc->tls_protocol_min > 0 && cc->tls_protocol_id < oc->tls_protocol_min) return 0; + /* If the connection TLS cipher is listed as suppressed by other, no */ + if (oc->tls_supp_ciphers && tls_util_array_uint16_contains( + oc->tls_supp_ciphers, cc->tls_cipher_id)) return 0; + return 1; +} + +int tls_core_request_check(request_rec *r) +{ + conn_rec *c = r->connection; + tls_conf_conn_t *cc = tls_conf_conn_get(c->master? c->master : c); + int rv = DECLINED; /* do not object to the request */ + + /* If we are not enabled on this connection, leave. We are not renegotiating. + * Otherwise: + * - service is unavailable when we have only a fallback certificate or + * when a challenge protocol is active (ACME tls-alpn-01 for example). + * - with vhosts configured and no SNI from the client, deny access. + * - are servers compatible for connection sharing? + */ + if (!TLS_CONN_ST_IS_ENABLED(cc)) goto cleanup; + + ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, + "tls_core_request_check[%s, %d]: %s", r->hostname, + cc? cc->service_unavailable : 2, r->the_request); + if (cc->service_unavailable) { + rv = HTTP_SERVICE_UNAVAILABLE; goto cleanup; + } + if (!cc->sni_hostname && r->connection->vhost_lookup_data) { + rv = HTTP_FORBIDDEN; goto cleanup; + } + if (!tls_conn_compatible_for(cc, r->server)) { + rv = HTTP_MISDIRECTED_REQUEST; + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10345) + "Connection host %s, selected via SNI, and request host %s" + " have incompatible TLS configurations.", + cc->server->server_hostname, r->hostname); + goto cleanup; + } +cleanup: + return rv; +} + +apr_status_t tls_core_error(conn_rec *c, rustls_result rr, const char **perrstr) +{ + tls_conf_conn_t *cc = tls_conf_conn_get(c); + apr_status_t rv; + + rv = tls_util_rustls_error(c->pool, rr, perrstr); + if (cc) { + cc->last_error = rr; + cc->last_error_descr = *perrstr; + } + return rv; +} + +int tls_core_setup_outgoing(conn_rec *c) +{ + tls_conf_conn_t *cc; + int rv = DECLINED; + + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, + "tls_core_setup_outgoing called"); +#if AP_MODULE_MAGIC_AT_LEAST(20120211, 109) + if (!c->outgoing) goto cleanup; +#endif + cc = cc_get_or_make(c); + if (cc->state == TLS_CONN_ST_DISABLED) { + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, + "tls_core_setup_outgoing: already disabled"); + goto cleanup; + } + if (TLS_CONN_ST_IS_ENABLED(cc)) { + /* we already handle it, allow repeated calls */ + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, + "tls_core_setup_outgoing: already enabled"); + rv = OK; goto cleanup; + } + cc->outgoing = 1; + if (!cc->dc) { + /* In case there is not dir_conf bound for this connection, we fallback + * to the defaults in the base server (we have no virtual host config to use) */ + cc->dc = ap_get_module_config(c->base_server->lookup_defaults, &tls_module); + } + if (cc->dc->proxy_enabled != TLS_FLAG_TRUE) { + cc->state = TLS_CONN_ST_DISABLED; + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, + "tls_core_setup_outgoing: TLSProxyEngine not configured"); + goto cleanup; + } + /* we handle this connection */ + cc->state = TLS_CONN_ST_CLIENT_HELLO; + rv = OK; + +cleanup: + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, + "tls_core_setup_outgoing returns %s", rv == OK? "OK" : "DECLINED"); + return rv; +} diff --git a/modules/tls/tls_core.h b/modules/tls/tls_core.h new file mode 100644 index 0000000..6ee1713 --- /dev/null +++ b/modules/tls/tls_core.h @@ -0,0 +1,184 @@ +/* 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. + */ +#ifndef tls_core_h +#define tls_core_h + +/* The module's state handling of a connection in normal chronological order, + */ +typedef enum { + TLS_CONN_ST_INIT, /* being initialized */ + TLS_CONN_ST_DISABLED, /* TLS is disabled here */ + TLS_CONN_ST_CLIENT_HELLO, /* TLS is enabled, prep handshake */ + TLS_CONN_ST_HANDSHAKE, /* TLS is enabled, handshake ongonig */ + TLS_CONN_ST_TRAFFIC, /* TLS is enabled, handshake done */ + TLS_CONN_ST_NOTIFIED, /* TLS is enabled, notification to end sent */ + TLS_CONN_ST_DONE, /* TLS is enabled, TLS has shut down */ +} tls_conn_state_t; + +#define TLS_CONN_ST_IS_ENABLED(cc) (cc && cc->state >= TLS_CONN_ST_CLIENT_HELLO) + +struct tls_filter_ctx_t; + +/* The modules configuration for a connection. Created at connection + * start and mutable during the lifetime of the connection. + * (A conn_rec is only ever processed by one thread at a time.) + */ +typedef struct { + server_rec *server; /* the server_rec selected for this connection, + * initially c->base_server, to be negotiated via SNI. */ + tls_conf_dir_t *dc; /* directory config applying here */ + tls_conn_state_t state; + int outgoing; /* != 0 iff outgoing connection (redundant once c->outgoing is everywhere) */ + int service_unavailable; /* we 503 all requests on this connection */ + tls_client_auth_t client_auth; /* how client authentication with certificates is used */ + int client_hello_seen; /* the client hello has been inspected */ + + rustls_connection *rustls_connection; /* the session used on this connection or NULL */ + const rustls_server_config *rustls_server_config; /* the config made for this connection (incoming) or NULL */ + const rustls_client_config *rustls_client_config; /* the config made for this connection (outgoing) or NULL */ + struct tls_filter_ctx_t *filter_ctx; /* the context used by this connection's tls filters */ + + apr_array_header_t *local_keys; /* rustls_certified_key* array of connection specific keys */ + const rustls_certified_key *key; /* the key selected for the session */ + int key_cloned; /* != 0 iff the key is a unique clone, to be freed */ + apr_array_header_t *peer_certs; /* handshaked peer ceritificates or NULL */ + const char *sni_hostname; /* the SNI value from the client hello, or NULL */ + const apr_array_header_t *alpn; /* the protocols proposed via ALPN by the client */ + const char *application_protocol; /* the ALPN selected protocol or NULL */ + + int session_id_cache_hit; /* if a submitted session id was found in our cache */ + + apr_uint16_t tls_protocol_id; /* the TLS version negotiated */ + const char *tls_protocol_name; /* the name of the TLS version negotiated */ + apr_uint16_t tls_cipher_id; /* the TLS cipher suite negotiated */ + const char *tls_cipher_name; /* the name of TLS cipher suite negotiated */ + + const char *user_name; /* != NULL if we derived a TLSUserName from the client_cert */ + apr_table_t *subprocess_env; /* common TLS variables for this connection */ + + rustls_result last_error; + const char *last_error_descr; + +} tls_conf_conn_t; + +/* Get the connection specific module configuration. */ +tls_conf_conn_t *tls_conf_conn_get(conn_rec *c); + +/* Set the module configuration for a connection. */ +void tls_conf_conn_set(conn_rec *c, tls_conf_conn_t *cc); + +/* Return OK iff this connection is a TSL connection (or a secondary on a TLS connection). */ +int tls_conn_check_ssl(conn_rec *c); + +/** + * Initialize the module's global and server specific settings. This runs + * in Apache's "post-config" phase, meaning the configuration has been read + * and checked for syntactic and other easily verifiable errors and now + * it is time to load everything in and make it ready for traffic. + * <p> a memory pool staying with us the whole time until the server stops/reloads. + * <ptemp> a temporary pool as a scratch buffer that will be destroyed shortly after. + * <base_server> the server for the global configuration which links -> next to + * all contained virtual hosts configured. + */ +apr_status_t tls_core_init(apr_pool_t *p, apr_pool_t *ptemp, server_rec *base_server); + +/** + * Initialize the module's outgoing connection settings. This runs + * in Apache's "post-config" phase after mod_proxy. + */ +apr_status_t tls_core_init_outgoing(apr_pool_t *p, apr_pool_t *ptemp, server_rec *base_server); + +/** + * Supply a directory configuration for the connection to work with. This + * maybe NULL. This can be called several times during the lifetime of a + * connection and must not change the current TLS state. + * @param c the connection + * @param dir_conf optional directory configuration that applies + */ +void tls_core_conn_bind(conn_rec *c, ap_conf_vector_t *dir_conf); + +/** + * Disable TLS on a new connection. Will do nothing on already initialized + * connections. + * @param c a new connection + */ +void tls_core_conn_disable(conn_rec *c); + +/** + * Initialize the tls_conf_connt_t for the connection + * and decide if TLS is enabled or not. + * @return OK if enabled, DECLINED otherwise + */ +int tls_core_pre_conn_init(conn_rec *c); + +/** + * Initialize the module for a TLS enabled connection. + * @param c a new connection + */ +apr_status_t tls_core_conn_init(conn_rec *c); + +/** + * Called when the ClientHello has been received and values from it + * have been extracted into the `tls_conf_conn_t` of the connection. + * + * Decides: + * - which `server_rec` this connection is for (SNI) + * - which application protocol to use (ALPN) + * This may be unsuccessful for several reasons. The SNI + * from the client may not be known or the selected server + * has not certificates available. etc. + * On success, a proper `rustls_connection` will have been + * created and set in the `tls_conf_conn_t` of the connection. + */ +apr_status_t tls_core_conn_seen_client_hello(conn_rec *c); + +/** + * The TLS handshake for the connection has been successfully performed. + * This means that TLS related properties, such as TLS version and cipher, + * are known and the props in `tls_conf_conn_t` of the connection + * can be set. + */ +apr_status_t tls_core_conn_post_handshake(conn_rec *c); + +/** + * After a request has been read, but before processing is started, we + * check if everything looks good to us: + * - was an SNI hostname provided by the client when we have vhosts to choose from? + * if not, we deny it. + * - if the SNI hostname and request host are not the same, are they - from TLS + * point of view - 'compatible' enough? For example, if one server requires + * client certificates and the other not (or with different settings), such + * a request will also be denied. + * returns DECLINED if everything is ok, otherwise an HTTP response code to + * generate an error page for. + */ +int tls_core_request_check(request_rec *r); + +/** + * A Rustls error happened while processing the connection. Look up an + * error description, determine the apr_status_t to use for it and remember + * this as the last error at tls_conf_conn_t. + */ +apr_status_t tls_core_error(conn_rec *c, rustls_result rr, const char **perrstr); + +/** + * Determine if we handle the TLS for an outgoing connection or not. + * @param c the connection + * @return OK if we handle the TLS, DECLINED otherwise. + */ +int tls_core_setup_outgoing(conn_rec *c); + +#endif /* tls_core_h */ diff --git a/modules/tls/tls_filter.c b/modules/tls/tls_filter.c new file mode 100644 index 0000000..0ee6be6 --- /dev/null +++ b/modules/tls/tls_filter.c @@ -0,0 +1,1017 @@ +/* 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 <assert.h> +#include <apr_lib.h> +#include <apr_strings.h> + +#include <httpd.h> +#include <http_connection.h> +#include <http_core.h> +#include <http_request.h> +#include <http_log.h> +#include <ap_socache.h> + +#include <rustls.h> + +#include "tls_proto.h" +#include "tls_conf.h" +#include "tls_core.h" +#include "tls_filter.h" +#include "tls_util.h" + + +extern module AP_MODULE_DECLARE_DATA tls_module; +APLOG_USE_MODULE(tls); + + +static rustls_io_result tls_read_callback( + void *userdata, unsigned char *buf, size_t n, size_t *out_n) +{ + tls_data_t *d = userdata; + size_t len = d->len > n? n : d->len; + memcpy(buf, d->data, len); + *out_n = len; + return 0; +} + +/** + * Provide TLS encrypted data to the rustls server_session in <fctx->cc->rustls_connection>. + * + * If <fctx->fin_tls_bb> holds data, take it from there. Otherwise perform a + * read via the network filters below us into that brigade. + * + * <fctx->fin_block> determines if we do a blocking read inititally or not. + * If the first read did to not produce enough data, any secondary read is done + * non-blocking. + * + * Had any data been added to <fctx->cc->rustls_connection>, call its "processing" + * function to handle the added data before leaving. + */ +static apr_status_t read_tls_to_rustls( + tls_filter_ctx_t *fctx, apr_size_t len, apr_read_type_e block, int errors_expected) +{ + tls_data_t d; + apr_size_t rlen; + apr_off_t passed = 0; + rustls_result rr = RUSTLS_RESULT_OK; + int os_err; + apr_status_t rv = APR_SUCCESS; + + if (APR_BRIGADE_EMPTY(fctx->fin_tls_bb)) { + ap_log_error(APLOG_MARK, APLOG_TRACE2, rv, fctx->cc->server, + "read_tls_to_rustls, get data from network, block=%d", block); + rv = ap_get_brigade(fctx->fin_ctx->next, fctx->fin_tls_bb, + AP_MODE_READBYTES, block, (apr_off_t)len); + if (APR_SUCCESS != rv) { + goto cleanup; + } + } + + while (!APR_BRIGADE_EMPTY(fctx->fin_tls_bb) && passed < (apr_off_t)len) { + apr_bucket *b = APR_BRIGADE_FIRST(fctx->fin_tls_bb); + + if (APR_BUCKET_IS_EOS(b)) { + ap_log_error(APLOG_MARK, APLOG_TRACE2, rv, fctx->cc->server, + "read_tls_to_rustls, EOS"); + if (fctx->fin_tls_buffer_bb) { + apr_brigade_cleanup(fctx->fin_tls_buffer_bb); + } + rv = APR_EOF; goto cleanup; + } + + rv = apr_bucket_read(b, (const char**)&d.data, &d.len, block); + if (APR_STATUS_IS_EOF(rv)) { + apr_bucket_delete(b); + continue; + } + else if (APR_SUCCESS != rv) { + goto cleanup; + } + + if (d.len > 0) { + /* got something, do not block on getting more */ + block = APR_NONBLOCK_READ; + + os_err = rustls_connection_read_tls(fctx->cc->rustls_connection, + tls_read_callback, &d, &rlen); + if (os_err) { + rv = APR_FROM_OS_ERROR(os_err); + goto cleanup; + } + + if (fctx->fin_tls_buffer_bb) { + /* we buffer for later replay on the 'real' rustls_connection */ + apr_brigade_write(fctx->fin_tls_buffer_bb, NULL, NULL, (const char*)d.data, rlen); + } + if (rlen >= d.len) { + apr_bucket_delete(b); + } + else { + b->start += (apr_off_t)rlen; + b->length -= rlen; + } + fctx->fin_bytes_in_rustls += (apr_off_t)d.len; + passed += (apr_off_t)rlen; + } + else if (d.len == 0) { + apr_bucket_delete(b); + } + } + + if (passed > 0) { + rr = rustls_connection_process_new_packets(fctx->cc->rustls_connection); + if (rr != RUSTLS_RESULT_OK) goto cleanup; + } + +cleanup: + if (rr != RUSTLS_RESULT_OK) { + rv = APR_ECONNRESET; + if (!errors_expected) { + const char *err_descr = ""; + rv = tls_core_error(fctx->c, rr, &err_descr); + ap_log_cerror(APLOG_MARK, APLOG_WARNING, rv, fctx->c, APLOGNO(10353) + "processing TLS data: [%d] %s", (int)rr, err_descr); + } + } + else if (APR_STATUS_IS_EOF(rv) && passed > 0) { + /* encountering EOF while actually having read sth is a success. */ + rv = APR_SUCCESS; + } + else if (APR_SUCCESS == rv && passed == 0 && fctx->fin_block == APR_NONBLOCK_READ) { + rv = APR_EAGAIN; + } + else { + ap_log_error(APLOG_MARK, APLOG_TRACE2, rv, fctx->cc->server, + "read_tls_to_rustls, passed %ld bytes to rustls", (long)passed); + } + return rv; +} + +static apr_status_t fout_pass_tls_to_net(tls_filter_ctx_t *fctx) +{ + apr_status_t rv = APR_SUCCESS; + + if (!APR_BRIGADE_EMPTY(fctx->fout_tls_bb)) { + rv = ap_pass_brigade(fctx->fout_ctx->next, fctx->fout_tls_bb); + if (APR_SUCCESS == rv && fctx->c->aborted) { + rv = APR_ECONNRESET; + } + fctx->fout_bytes_in_tls_bb = 0; + apr_brigade_cleanup(fctx->fout_tls_bb); + } + return rv; +} + +static apr_status_t fout_pass_all_to_net( + tls_filter_ctx_t *fctx, int flush); + +static apr_status_t filter_abort( + tls_filter_ctx_t *fctx) +{ + apr_status_t rv; + + if (fctx->cc->state != TLS_CONN_ST_DONE) { + if (fctx->cc->state > TLS_CONN_ST_CLIENT_HELLO) { + rustls_connection_send_close_notify(fctx->cc->rustls_connection); + rv = fout_pass_all_to_net(fctx, 1); + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, fctx->c, "filter_abort, flushed output"); + } + fctx->c->aborted = 1; + fctx->cc->state = TLS_CONN_ST_DONE; + } + return APR_ECONNABORTED; +} + +static apr_status_t filter_recv_client_hello(tls_filter_ctx_t *fctx) +{ + apr_status_t rv = APR_SUCCESS; + + ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, fctx->cc->server, + "tls_filter, server=%s, recv client hello", fctx->cc->server->server_hostname); + /* only for incoming connections */ + ap_assert(!fctx->cc->outgoing); + + if (rustls_connection_is_handshaking(fctx->cc->rustls_connection)) { + apr_bucket_brigade *bb_tmp; + + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, fctx->c, "filter_recv_client_hello: start"); + fctx->fin_tls_buffer_bb = apr_brigade_create(fctx->c->pool, fctx->c->bucket_alloc); + do { + if (rustls_connection_wants_read(fctx->cc->rustls_connection)) { + rv = read_tls_to_rustls(fctx, fctx->fin_max_in_rustls, APR_BLOCK_READ, 1); + if (APR_SUCCESS != rv) { + if (fctx->cc->client_hello_seen) { + rv = APR_EAGAIN; /* we got what we needed */ + break; + } + /* Something went wrong before we saw the client hello. + * This is a real error on which we should not continue. */ + goto cleanup; + } + } + /* Notice: we never write here to the client. We just want to inspect + * the client hello. */ + } while (!fctx->cc->client_hello_seen); + + /* We have seen the client hello and selected the server (vhost) to use + * on this connection. Set up the 'real' rustls_connection based on the + * servers 'real' rustls_config. */ + rv = tls_core_conn_seen_client_hello(fctx->c); + if (APR_SUCCESS != rv) goto cleanup; + + bb_tmp = fctx->fin_tls_bb; /* data we have yet to feed to rustls */ + fctx->fin_tls_bb = fctx->fin_tls_buffer_bb; /* data we already fed to the pre_session */ + fctx->fin_tls_buffer_bb = NULL; + APR_BRIGADE_CONCAT(fctx->fin_tls_bb, bb_tmp); /* all tls data from the client so far, reloaded */ + apr_brigade_destroy(bb_tmp); + rv = APR_SUCCESS; + } + +cleanup: + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, fctx->c, "filter_recv_client_hello: done"); + return rv; +} + +static apr_status_t filter_send_client_hello(tls_filter_ctx_t *fctx) +{ + apr_status_t rv = APR_SUCCESS; + + ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, fctx->cc->server, + "tls_filter, server=%s, send client hello", fctx->cc->server->server_hostname); + /* Only for outgoing connections */ + ap_assert(fctx->cc->outgoing); + if (rustls_connection_is_handshaking(fctx->cc->rustls_connection)) { + while (rustls_connection_wants_write(fctx->cc->rustls_connection)) { + /* write flushed, so it really gets out */ + rv = fout_pass_all_to_net(fctx, 1); + if (APR_SUCCESS != rv) goto cleanup; + } + } + +cleanup: + return rv; +} + +/** + * While <fctx->cc->rustls_connection> indicates that a handshake is ongoing, + * write TLS data from and read network TLS data to the server session. + * + * @return APR_SUCCESS when the handshake is completed + */ +static apr_status_t filter_do_handshake( + tls_filter_ctx_t *fctx) +{ + apr_status_t rv = APR_SUCCESS; + + ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, fctx->cc->server, + "tls_filter, server=%s, do handshake", fctx->cc->server->server_hostname); + if (rustls_connection_is_handshaking(fctx->cc->rustls_connection)) { + do { + if (rustls_connection_wants_write(fctx->cc->rustls_connection)) { + rv = fout_pass_all_to_net(fctx, 1); + if (APR_SUCCESS != rv) goto cleanup; + } + else if (rustls_connection_wants_read(fctx->cc->rustls_connection)) { + rv = read_tls_to_rustls(fctx, fctx->fin_max_in_rustls, APR_BLOCK_READ, 0); + if (APR_SUCCESS != rv) goto cleanup; + } + } + while (rustls_connection_is_handshaking(fctx->cc->rustls_connection)); + + /* rustls reports the TLS handshake to be done, when it *internally* has + * processed everything into its buffers. Not when the buffers have been + * send to the other side. */ + if (rustls_connection_wants_write(fctx->cc->rustls_connection)) { + rv = fout_pass_all_to_net(fctx, 1); + if (APR_SUCCESS != rv) goto cleanup; + } + } +cleanup: + ap_log_error(APLOG_MARK, APLOG_TRACE2, rv, fctx->cc->server, + "tls_filter, server=%s, handshake done", fctx->cc->server->server_hostname); + if (APR_SUCCESS != rv) { + if (fctx->cc->last_error_descr) { + ap_log_cerror(APLOG_MARK, APLOG_INFO, APR_ECONNABORTED, fctx->c, APLOGNO(10354) + "handshake failed: %s", fctx->cc->last_error_descr); + } + } + return rv; +} + +static apr_status_t progress_tls_atleast_to(tls_filter_ctx_t *fctx, tls_conn_state_t state) +{ + apr_status_t rv = APR_SUCCESS; + + /* handle termination immediately */ + if (state == TLS_CONN_ST_DONE) { + rv = APR_ECONNABORTED; + goto cleanup; + } + + if (state > TLS_CONN_ST_CLIENT_HELLO + && TLS_CONN_ST_CLIENT_HELLO == fctx->cc->state) { + rv = tls_core_conn_init(fctx->c); + if (APR_SUCCESS != rv) goto cleanup; + + if (fctx->cc->outgoing) { + rv = filter_send_client_hello(fctx); + } + else { + rv = filter_recv_client_hello(fctx); + } + if (APR_SUCCESS != rv) goto cleanup; + fctx->cc->state = TLS_CONN_ST_HANDSHAKE; + } + + if (state > TLS_CONN_ST_HANDSHAKE + && TLS_CONN_ST_HANDSHAKE== fctx->cc->state) { + rv = filter_do_handshake(fctx); + if (APR_SUCCESS != rv) goto cleanup; + rv = tls_core_conn_post_handshake(fctx->c); + if (APR_SUCCESS != rv) goto cleanup; + fctx->cc->state = TLS_CONN_ST_TRAFFIC; + } + + if (state < fctx->cc->state) { + rv = APR_ECONNABORTED; + } + +cleanup: + if (APR_SUCCESS != rv) { + filter_abort(fctx); /* does change the state itself */ + } + return rv; +} + +/** + * The connection filter converting TLS encrypted network data into plain, unencrpyted + * traffic data to be processed by filters above it in the filter chain. + * + * Unfortunately, Apache's filter infrastructure places a heavy implementation + * complexity on its input filters for the various use cases its HTTP/1.x parser + * (mainly) finds convenient: + * + * <bb> the bucket brigade to place the data into. + * <mode> one of + * - AP_MODE_READBYTES: just add up to <readbytes> data into <bb> + * - AP_MODE_GETLINE: make a best effort to get data up to and including a CRLF. + * it can be less, but not more t than that. + * - AP_MODE_EATCRLF: never used, we puke on it. + * - AP_MODE_SPECULATIVE: read data without consuming it. + * - AP_MODE_EXHAUSTIVE: never used, we puke on it. + * - AP_MODE_INIT: called once on a connection. needs to pass down the filter + * chain, giving every filter the change to "INIT". + * <block> do blocking or non-blocking reads + * <readbytes> max amount of data to add to <bb>, seems to be 0 for GETLINE + */ +static apr_status_t filter_conn_input( + ap_filter_t *f, apr_bucket_brigade *bb, ap_input_mode_t mode, + apr_read_type_e block, apr_off_t readbytes) +{ + tls_filter_ctx_t *fctx = f->ctx; + apr_status_t rv = APR_SUCCESS; + apr_off_t passed = 0, nlen; + rustls_result rr = RUSTLS_RESULT_OK; + apr_size_t in_buf_len; + char *in_buf = NULL; + + fctx->fin_block = block; + if (f->c->aborted) { + rv = filter_abort(fctx); goto cleanup; + } + + ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, fctx->cc->server, + "tls_filter_conn_input, server=%s, mode=%d, block=%d, readbytes=%ld", + fctx->cc->server->server_hostname, mode, block, (long)readbytes); + + rv = progress_tls_atleast_to(fctx, TLS_CONN_ST_TRAFFIC); + if (APR_SUCCESS != rv) goto cleanup; /* this also leaves on APR_EAGAIN */ + + if (!fctx->cc->rustls_connection) { + return ap_get_brigade(f->next, bb, mode, block, readbytes); + } + +#if AP_MODULE_MAGIC_AT_LEAST(20200420, 1) + ap_filter_reinstate_brigade(f, fctx->fin_plain_bb, NULL); +#endif + + if (AP_MODE_INIT == mode) { + /* INIT is used to trigger the handshake, it does not return any traffic data. */ + goto cleanup; + } + + /* If we have nothing buffered, try getting more input. + * a) ask rustls_connection for decrypted data, if it has any. + * Note that only full records can be decrypted. We might have + * written TLS data to the session, but that does not mean it + * can give unencryted data out again. + * b) read TLS bytes from the network and feed them to the rustls session. + * c) go back to a) if b) added data. + */ + while (APR_BRIGADE_EMPTY(fctx->fin_plain_bb)) { + apr_size_t rlen = 0; + apr_bucket *b; + + if (fctx->fin_bytes_in_rustls > 0) { + in_buf_len = APR_BUCKET_BUFF_SIZE; + in_buf = ap_calloc(in_buf_len, sizeof(char)); + rr = rustls_connection_read(fctx->cc->rustls_connection, + (unsigned char*)in_buf, in_buf_len, &rlen); + if (rr == RUSTLS_RESULT_PLAINTEXT_EMPTY) { + rr = RUSTLS_RESULT_OK; + rlen = 0; + } + if (rr != RUSTLS_RESULT_OK) goto cleanup; + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, fctx->c, + "tls_filter_conn_input: got %ld plain bytes from rustls", (long)rlen); + if (rlen > 0) { + b = apr_bucket_heap_create(in_buf, rlen, free, fctx->c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(fctx->fin_plain_bb, b); + } + else { + free(in_buf); + } + in_buf = NULL; + } + if (rlen == 0) { + /* that did not produce anything either. try getting more + * TLS data from the network into the rustls session. */ + fctx->fin_bytes_in_rustls = 0; + rv = read_tls_to_rustls(fctx, fctx->fin_max_in_rustls, block, 0); + if (APR_SUCCESS != rv) goto cleanup; /* this also leave on APR_EAGAIN */ + } + } + + if (AP_MODE_GETLINE == mode) { + if (readbytes <= 0) readbytes = HUGE_STRING_LEN; + rv = tls_util_brigade_split_line(bb, fctx->fin_plain_bb, block, readbytes, &nlen); + if (APR_SUCCESS != rv) goto cleanup; + passed += nlen; + } + else if (AP_MODE_READBYTES == mode) { + ap_assert(readbytes > 0); + rv = tls_util_brigade_transfer(bb, fctx->fin_plain_bb, readbytes, &nlen); + if (APR_SUCCESS != rv) goto cleanup; + passed += nlen; + } + else if (AP_MODE_SPECULATIVE == mode) { + ap_assert(readbytes > 0); + rv = tls_util_brigade_copy(bb, fctx->fin_plain_bb, readbytes, &nlen); + if (APR_SUCCESS != rv) goto cleanup; + passed += nlen; + } + else if (AP_MODE_EXHAUSTIVE == mode) { + /* return all we have */ + APR_BRIGADE_CONCAT(bb, fctx->fin_plain_bb); + } + else { + /* We do support any other mode */ + rv = APR_ENOTIMPL; goto cleanup; + } + + fout_pass_all_to_net(fctx, 0); + +cleanup: + if (NULL != in_buf) free(in_buf); + + if (APLOGctrace3(fctx->c)) { + tls_util_bb_log(fctx->c, APLOG_TRACE3, "tls_input, fctx->fin_plain_bb", fctx->fin_plain_bb); + tls_util_bb_log(fctx->c, APLOG_TRACE3, "tls_input, bb", bb); + } + if (rr != RUSTLS_RESULT_OK) { + const char *err_descr = ""; + + rv = tls_core_error(fctx->c, rr, &err_descr); + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, fctx->c, APLOGNO(10355) + "tls_filter_conn_input: [%d] %s", (int)rr, err_descr); + } + else if (APR_STATUS_IS_EAGAIN(rv)) { + ap_log_cerror(APLOG_MARK, APLOG_TRACE4, rv, fctx->c, + "tls_filter_conn_input: no data available"); + } + else if (APR_SUCCESS != rv) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, fctx->c, APLOGNO(10356) + "tls_filter_conn_input"); + } + else { + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, fctx->c, + "tls_filter_conn_input: passed %ld bytes", (long)passed); + } + +#if AP_MODULE_MAGIC_AT_LEAST(20200420, 1) + if (APR_SUCCESS == rv || APR_STATUS_IS_EAGAIN(rv)) { + ap_filter_setaside_brigade(f, fctx->fin_plain_bb); + } +#endif + return rv; +} + +static rustls_io_result tls_write_callback( + void *userdata, const unsigned char *buf, size_t n, size_t *out_n) +{ + tls_filter_ctx_t *fctx = userdata; + apr_status_t rv; + + if ((apr_off_t)n + fctx->fout_bytes_in_tls_bb >= (apr_off_t)fctx->fout_auto_flush_size) { + apr_bucket *b = apr_bucket_transient_create((const char*)buf, n, fctx->fout_tls_bb->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(fctx->fout_tls_bb, b); + fctx->fout_bytes_in_tls_bb += (apr_off_t)n; + rv = fout_pass_tls_to_net(fctx); + *out_n = n; + } + else { + rv = apr_brigade_write(fctx->fout_tls_bb, NULL, NULL, (const char*)buf, n); + if (APR_SUCCESS != rv) goto cleanup; + fctx->fout_bytes_in_tls_bb += (apr_off_t)n; + *out_n = n; + } +cleanup: + ap_log_error(APLOG_MARK, APLOG_TRACE5, rv, fctx->cc->server, + "tls_write_callback: %ld bytes", (long)n); + return APR_TO_OS_ERROR(rv); +} + +static rustls_io_result tls_write_vectored_callback( + void *userdata, const rustls_iovec *riov, size_t count, size_t *out_n) +{ + tls_filter_ctx_t *fctx = userdata; + const struct iovec *iov = (const struct iovec*)riov; + apr_status_t rv; + size_t i, n = 0; + apr_bucket *b; + + for (i = 0; i < count; ++i, ++iov) { + b = apr_bucket_transient_create((const char*)iov->iov_base, iov->iov_len, fctx->fout_tls_bb->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(fctx->fout_tls_bb, b); + n += iov->iov_len; + } + fctx->fout_bytes_in_tls_bb += (apr_off_t)n; + rv = fout_pass_tls_to_net(fctx); + *out_n = n; + ap_log_error(APLOG_MARK, APLOG_TRACE5, rv, fctx->cc->server, + "tls_write_vectored_callback: %ld bytes in %d slices", (long)n, (int)count); + return APR_TO_OS_ERROR(rv); +} + +#define TLS_WRITE_VECTORED 1 +/** + * Read TLS encrypted data from <fctx->cc->rustls_connection> and pass it down + * Apache's filter chain to the network. + * + * For now, we always FLUSH the data, since that is what we need during handshakes. + */ +static apr_status_t fout_pass_rustls_to_tls(tls_filter_ctx_t *fctx) +{ + apr_status_t rv = APR_SUCCESS; + + if (rustls_connection_wants_write(fctx->cc->rustls_connection)) { + size_t dlen; + int os_err; + + if (TLS_WRITE_VECTORED) { + do { + os_err = rustls_connection_write_tls_vectored( + fctx->cc->rustls_connection, tls_write_vectored_callback, fctx, &dlen); + if (os_err) { + rv = APR_FROM_OS_ERROR(os_err); + goto cleanup; + } + } + while (rustls_connection_wants_write(fctx->cc->rustls_connection)); + } + else { + do { + os_err = rustls_connection_write_tls( + fctx->cc->rustls_connection, tls_write_callback, fctx, &dlen); + if (os_err) { + rv = APR_FROM_OS_ERROR(os_err); + goto cleanup; + } + } + while (rustls_connection_wants_write(fctx->cc->rustls_connection)); + ap_log_cerror(APLOG_MARK, APLOG_TRACE3, rv, fctx->c, + "fout_pass_rustls_to_tls, %ld bytes ready for network", (long)fctx->fout_bytes_in_tls_bb); + fctx->fout_bytes_in_rustls = 0; + } + } +cleanup: + return rv; +} + +static apr_status_t fout_pass_buf_to_rustls( + tls_filter_ctx_t *fctx, const char *buf, apr_size_t len) +{ + apr_status_t rv = APR_SUCCESS; + rustls_result rr = RUSTLS_RESULT_OK; + apr_size_t written; + + while (len) { + /* check if we will exceed the limit of data in rustls. + * rustls does not guarantuee that it will accept all data, so we + * iterate and flush when needed. */ + if (fctx->fout_bytes_in_rustls + (apr_off_t)len > (apr_off_t)fctx->fout_max_in_rustls) { + rv = fout_pass_rustls_to_tls(fctx); + if (APR_SUCCESS != rv) goto cleanup; + } + + rr = rustls_connection_write(fctx->cc->rustls_connection, + (const unsigned char*)buf, len, &written); + if (rr != RUSTLS_RESULT_OK) goto cleanup; + ap_assert(written <= len); + fctx->fout_bytes_in_rustls += (apr_off_t)written; + buf += written; + len -= written; + if (written == 0) { + rv = APR_EAGAIN; + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, fctx->c, APLOGNO(10357) + "fout_pass_buf_to_rustls: not read by rustls at all"); + goto cleanup; + } + } +cleanup: + if (rr != RUSTLS_RESULT_OK) { + const char *err_descr = ""; + rv = tls_core_error(fctx->c, rr, &err_descr); + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, fctx->c, APLOGNO(10358) + "fout_pass_buf_to_tls to rustls: [%d] %s", (int)rr, err_descr); + } + return rv; +} + +static apr_status_t fout_pass_all_to_tls(tls_filter_ctx_t *fctx) +{ + apr_status_t rv = APR_SUCCESS; + + if (fctx->fout_buf_plain_len) { + rv = fout_pass_buf_to_rustls(fctx, fctx->fout_buf_plain, fctx->fout_buf_plain_len); + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, fctx->c, + "fout_pass_all_to_tls: %ld plain bytes written to rustls", + (long)fctx->fout_buf_plain_len); + if (APR_SUCCESS != rv) goto cleanup; + fctx->fout_buf_plain_len = 0; + } + + rv = fout_pass_rustls_to_tls(fctx); +cleanup: + return rv; +} + +static apr_status_t fout_pass_all_to_net(tls_filter_ctx_t *fctx, int flush) +{ + apr_status_t rv; + + rv = fout_pass_all_to_tls(fctx); + if (APR_SUCCESS != rv) goto cleanup; + if (flush) { + apr_bucket *b = apr_bucket_flush_create(fctx->fout_tls_bb->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(fctx->fout_tls_bb, b); + } + rv = fout_pass_tls_to_net(fctx); +cleanup: + return rv; +} + +static apr_status_t fout_add_bucket_to_plain(tls_filter_ctx_t *fctx, apr_bucket *b) +{ + const char *data; + apr_size_t dlen, buf_remain; + apr_status_t rv = APR_SUCCESS; + + ap_assert((apr_size_t)-1 != b->length); + if (b->length == 0) { + apr_bucket_delete(b); + goto cleanup; + } + + buf_remain = fctx->fout_buf_plain_size - fctx->fout_buf_plain_len; + if (buf_remain == 0) { + rv = fout_pass_all_to_tls(fctx); + if (APR_SUCCESS != rv) goto cleanup; + buf_remain = fctx->fout_buf_plain_size - fctx->fout_buf_plain_len; + ap_assert(buf_remain > 0); + } + if (b->length > buf_remain) { + apr_bucket_split(b, buf_remain); + } + rv = apr_bucket_read(b, &data, &dlen, APR_BLOCK_READ); + if (APR_SUCCESS != rv) goto cleanup; + /*if (dlen > TLS_PREF_PLAIN_CHUNK_SIZE)*/ + ap_assert(dlen <= buf_remain); + memcpy(fctx->fout_buf_plain + fctx->fout_buf_plain_len, data, dlen); + fctx->fout_buf_plain_len += dlen; + apr_bucket_delete(b); +cleanup: + return rv; +} + +static apr_status_t fout_add_bucket_to_tls(tls_filter_ctx_t *fctx, apr_bucket *b) +{ + apr_status_t rv; + + rv = fout_pass_all_to_tls(fctx); + if (APR_SUCCESS != rv) goto cleanup; + APR_BUCKET_REMOVE(b); + APR_BRIGADE_INSERT_TAIL(fctx->fout_tls_bb, b); + if (AP_BUCKET_IS_EOC(b)) { + rustls_connection_send_close_notify(fctx->cc->rustls_connection); + fctx->cc->state = TLS_CONN_ST_NOTIFIED; + rv = fout_pass_rustls_to_tls(fctx); + if (APR_SUCCESS != rv) goto cleanup; + } +cleanup: + return rv; +} + +static apr_status_t fout_append_plain(tls_filter_ctx_t *fctx, apr_bucket *b) +{ + const char *data; + apr_size_t dlen, buf_remain; + rustls_result rr = RUSTLS_RESULT_OK; + apr_status_t rv = APR_SUCCESS; + const char *lbuf = NULL; + int flush = 0; + + if (b) { + /* if our plain buffer is full, now is a good time to flush it. */ + buf_remain = fctx->fout_buf_plain_size - fctx->fout_buf_plain_len; + if (buf_remain == 0) { + rv = fout_pass_all_to_tls(fctx); + if (APR_SUCCESS != rv) goto cleanup; + buf_remain = fctx->fout_buf_plain_size - fctx->fout_buf_plain_len; + ap_assert(buf_remain > 0); + } + + /* Resolve any indeterminate bucket to a "real" one by reading it. */ + if ((apr_size_t)-1 == b->length) { + rv = apr_bucket_read(b, &data, &dlen, APR_BLOCK_READ); + if (APR_STATUS_IS_EOF(rv)) { + apr_bucket_delete(b); + goto maybe_flush; + } + else if (APR_SUCCESS != rv) goto cleanup; + } + /* Now `b` is the bucket that we need to append and consume */ + if (APR_BUCKET_IS_METADATA(b)) { + /* outgoing buckets: + * [PLAINDATA META PLAINDATA META META] + * need to become: + * [TLSDATA META TLSDATA META META] + * because we need to send the meta buckets down the + * network filters. */ + rv = fout_add_bucket_to_tls(fctx, b); + flush = 1; + } + else if (b->length == 0) { + apr_bucket_delete(b); + } + else if (b->length < 1024 || fctx->fout_buf_plain_len > 0) { + /* we want to buffer small chunks to create larger TLS records and + * not leak security relevant information. So, we buffer small + * chunks and add (parts of) later, larger chunks if the plain + * buffer contains data. */ + rv = fout_add_bucket_to_plain(fctx, b); + if (APR_SUCCESS != rv) goto cleanup; + } + else { + /* we have a large chunk and our plain buffer is empty, write it + * directly into rustls. */ +#define TLS_FILE_CHUNK_SIZE 4 * TLS_PREF_PLAIN_CHUNK_SIZE + if (b->length > TLS_FILE_CHUNK_SIZE) { + apr_bucket_split(b, TLS_FILE_CHUNK_SIZE); + } + + if (APR_BUCKET_IS_FILE(b) + && (lbuf = malloc(b->length))) { + /* A file bucket is a most wonderous thing. Since the dawn of time, + * it has been subject to many optimizations for efficient handling + * of large data in the server: + * - unless one reads from it, it will just consist of a file handle + * and the offset+length information. + * - a apr_bucket_read() will transform itself to a bucket holding + * some 8000 bytes of data (APR_BUCKET_BUFF_SIZE), plus a following + * bucket that continues to hold the file handle and updated offsets/length + * information. + * Using standard bucket brigade handling, one would send 8000 bytes + * chunks to the network and that is fine for many occasions. + * - to have improved performance, the http: network handler takes + * the file handle directly and uses sendfile() when the OS supports it. + * - But there is not sendfile() for TLS (netflix did some experiments). + * So. + * rustls will try to collect max length traffic data into ont TLS + * message, but it can only work with what we gave it. If we give it buffers + * that fit what it wants to assemble already, its work is much easier. + * + * We can read file buckets in large chunks than APR_BUCKET_BUFF_SIZE, + * with a bit of knowledge about how they work. + */ + apr_bucket_file *f = (apr_bucket_file *)b->data; + apr_file_t *fd = f->fd; + apr_off_t offset = b->start; + + dlen = b->length; + rv = apr_file_seek(fd, APR_SET, &offset); + if (APR_SUCCESS != rv) goto cleanup; + rv = apr_file_read(fd, (void*)lbuf, &dlen); + if (APR_SUCCESS != rv && !APR_STATUS_IS_EOF(rv)) goto cleanup; + rv = fout_pass_buf_to_rustls(fctx, lbuf, dlen); + if (APR_SUCCESS != rv) goto cleanup; + apr_bucket_delete(b); + } + else { + rv = apr_bucket_read(b, &data, &dlen, APR_BLOCK_READ); + if (APR_SUCCESS != rv) goto cleanup; + rv = fout_pass_buf_to_rustls(fctx, data, dlen); + if (APR_SUCCESS != rv) goto cleanup; + apr_bucket_delete(b); + } + } + } + +maybe_flush: + if (flush) { + rv = fout_pass_all_to_net(fctx, 1); + if (APR_SUCCESS != rv) goto cleanup; + } + +cleanup: + if (lbuf) free((void*)lbuf); + if (rr != RUSTLS_RESULT_OK) { + const char *err_descr = ""; + rv = tls_core_error(fctx->c, rr, &err_descr); + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, fctx->c, APLOGNO(10359) + "write_bucket_to_rustls: [%d] %s", (int)rr, err_descr); + } + return rv; +} + +/** + * The connection filter converting plain, unencrypted traffic data into TLS + * encrypted bytes and send the down the Apache filter chain out to the network. + * + * <bb> the data to send, including "meta data" such as FLUSH indicators + * to force filters to write any data set aside (an apache term for + * 'buffering'). + * The buckets in <bb> need to be completely consumed, e.g. <bb> will be + * empty on a successful return. but unless FLUSHed, filters may hold + * buckets back internally, for various reasons. However they always + * need to be processed in the order they arrive. + */ +static apr_status_t filter_conn_output( + ap_filter_t *f, apr_bucket_brigade *bb) +{ + tls_filter_ctx_t *fctx = f->ctx; + apr_status_t rv = APR_SUCCESS; + rustls_result rr = RUSTLS_RESULT_OK; + + if (f->c->aborted) { + ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, fctx->c, + "tls_filter_conn_output: aborted conn"); + apr_brigade_cleanup(bb); + rv = APR_ECONNABORTED; goto cleanup; + } + + rv = progress_tls_atleast_to(fctx, TLS_CONN_ST_TRAFFIC); + if (APR_SUCCESS != rv) goto cleanup; /* this also leaves on APR_EAGAIN */ + + if (fctx->cc->state == TLS_CONN_ST_DONE) { + /* have done everything, just pass through */ + ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, fctx->c, + "tls_filter_conn_output: tls session is already done"); + rv = ap_pass_brigade(f->next, bb); + goto cleanup; + } + + ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, fctx->cc->server, + "tls_filter_conn_output, server=%s", fctx->cc->server->server_hostname); + if (APLOGctrace5(fctx->c)) { + tls_util_bb_log(fctx->c, APLOG_TRACE5, "filter_conn_output", bb); + } + + while (!APR_BRIGADE_EMPTY(bb)) { + rv = fout_append_plain(fctx, APR_BRIGADE_FIRST(bb)); + if (APR_SUCCESS != rv) goto cleanup; + } + + if (APLOGctrace5(fctx->c)) { + tls_util_bb_log(fctx->c, APLOG_TRACE5, "filter_conn_output, processed plain", bb); + tls_util_bb_log(fctx->c, APLOG_TRACE5, "filter_conn_output, tls", fctx->fout_tls_bb); + } + +cleanup: + if (rr != RUSTLS_RESULT_OK) { + const char *err_descr = ""; + rv = tls_core_error(fctx->c, rr, &err_descr); + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, fctx->c, APLOGNO(10360) + "tls_filter_conn_output: [%d] %s", (int)rr, err_descr); + } + else { + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, fctx->c, + "tls_filter_conn_output: done"); + } + return rv; +} + +int tls_filter_pre_conn_init(conn_rec *c) +{ + tls_conf_conn_t *cc; + tls_filter_ctx_t *fctx; + + if (OK != tls_core_pre_conn_init(c)) { + return DECLINED; + } + + ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, c->base_server, + "tls_filter_pre_conn_init on %s", c->base_server->server_hostname); + + cc = tls_conf_conn_get(c); + ap_assert(cc); + + fctx = apr_pcalloc(c->pool, sizeof(*fctx)); + fctx->c = c; + fctx->cc = cc; + cc->filter_ctx = fctx; + + /* a bit tricky: registering out filters returns the ap_filter_t* + * that it created for it. The ->next field points always + * to the filter "below" our filter. That will be other registered + * filters and last, but not least, the network filter on the socket. + * + * Therefore, wenn we need to read/write TLS data during handshake, we can + * pass the data to/call on ->next- Since ->next can change during the setup of + * a connections (other modules register also sth.), we keep the ap_filter_t* + * returned here, since httpd core will update the ->next whenever someone + * adds a filter or removes one. This can potentially happen all the time. + */ + fctx->fin_ctx = ap_add_input_filter(TLS_FILTER_RAW, fctx, NULL, c); + fctx->fin_tls_bb = apr_brigade_create(c->pool, c->bucket_alloc); + fctx->fin_tls_buffer_bb = NULL; + fctx->fin_plain_bb = apr_brigade_create(c->pool, c->bucket_alloc); + fctx->fout_ctx = ap_add_output_filter(TLS_FILTER_RAW, fctx, NULL, c); + fctx->fout_tls_bb = apr_brigade_create(c->pool, c->bucket_alloc); + fctx->fout_buf_plain_size = APR_BUCKET_BUFF_SIZE; + fctx->fout_buf_plain = apr_pcalloc(c->pool, fctx->fout_buf_plain_size); + fctx->fout_buf_plain_len = 0; + + /* Let the filters have 2 max-length TLS Messages in the rustls buffers. + * The effects we would like to achieve here are: + * 1. pass data out, so that every bucket becomes its own TLS message. + * This hides, if possible, the length of response parts. + * If we give rustls enough plain data, it will use the max TLS message + * size and things are more hidden. But we can only write what the application + * or protocol gives us. + * 2. max length records result in less overhead for all layers involved. + * 3. a TLS message from the client can only be decrypted when it has + * completely arrived. If we provide rustls with enough data (if the + * network has it for us), it should always be able to decrypt at least + * one TLS message and we have plain bytes to forward to the protocol + * handler. + */ + fctx->fin_max_in_rustls = 4 * TLS_REC_MAX_SIZE; + fctx->fout_max_in_rustls = 4 * TLS_PREF_PLAIN_CHUNK_SIZE; + fctx->fout_auto_flush_size = 2 * TLS_REC_MAX_SIZE; + + return OK; +} + +void tls_filter_conn_init(conn_rec *c) +{ + tls_conf_conn_t *cc = tls_conf_conn_get(c); + + if (cc && cc->filter_ctx && !cc->outgoing) { + /* We are one in a row of hooks that - possibly - want to process this + * connection, the (HTTP) protocol handlers among them. + * + * For incoming connections, we need to select the protocol to use NOW, + * so that the later protocol handlers do the right thing. + * Send an INIT down the input filter chain to trigger the TLS handshake, + * which will select a protocol via ALPN. */ + apr_bucket_brigade* temp; + + ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, c->base_server, + "tls_filter_conn_init on %s, triggering handshake", c->base_server->server_hostname); + temp = apr_brigade_create(c->pool, c->bucket_alloc); + ap_get_brigade(c->input_filters, temp, AP_MODE_INIT, APR_BLOCK_READ, 0); + apr_brigade_destroy(temp); + } +} + +void tls_filter_register( + apr_pool_t *pool) +{ + (void)pool; + ap_register_input_filter(TLS_FILTER_RAW, filter_conn_input, NULL, AP_FTYPE_CONNECTION + 5); + ap_register_output_filter(TLS_FILTER_RAW, filter_conn_output, NULL, AP_FTYPE_CONNECTION + 5); +} diff --git a/modules/tls/tls_filter.h b/modules/tls/tls_filter.h new file mode 100644 index 0000000..4f3d38b --- /dev/null +++ b/modules/tls/tls_filter.h @@ -0,0 +1,90 @@ +/* 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. + */ +#ifndef tls_filter_h +#define tls_filter_h + +#define TLS_FILTER_RAW "TLS raw" + +typedef struct tls_filter_ctx_t tls_filter_ctx_t; + +struct tls_filter_ctx_t { + conn_rec *c; /* connection this context is for */ + tls_conf_conn_t *cc; /* tls module configuration of connection */ + + ap_filter_t *fin_ctx; /* Apache's entry into the input filter chain */ + apr_bucket_brigade *fin_tls_bb; /* TLS encrypted, incoming network data */ + apr_bucket_brigade *fin_tls_buffer_bb; /* TLS encrypted, incoming network data buffering */ + apr_bucket_brigade *fin_plain_bb; /* decrypted, incoming traffic data */ + apr_off_t fin_bytes_in_rustls; /* # of input TLS bytes in rustls_connection */ + apr_read_type_e fin_block; /* Do we block on input reads or not? */ + + ap_filter_t *fout_ctx; /* Apache's entry into the output filter chain */ + char *fout_buf_plain; /* a buffer to collect plain bytes for output */ + apr_size_t fout_buf_plain_len; /* the amount of bytes in the buffer */ + apr_size_t fout_buf_plain_size; /* the total size of the buffer */ + apr_bucket_brigade *fout_tls_bb; /* TLS encrypted, outgoing network data */ + apr_off_t fout_bytes_in_rustls; /* # of output plain bytes in rustls_connection */ + apr_off_t fout_bytes_in_tls_bb; /* # of output tls bytes in our brigade */ + + apr_size_t fin_max_in_rustls; /* how much tls we like to read into rustls */ + apr_size_t fout_max_in_rustls; /* how much plain bytes we like in rustls */ + apr_size_t fout_max_bucket_size; /* how large bucket chunks we handle before splitting */ + apr_size_t fout_auto_flush_size; /* on much outoing TLS data we flush to network */ +}; + +/** + * Register the in-/output filters for converting TLS to application data and vice versa. + */ +void tls_filter_register(apr_pool_t *pool); + +/** + * Initialize the pre_connection state. Install all filters. + * + * @return OK if TLS on connection is enabled, DECLINED otherwise + */ +int tls_filter_pre_conn_init(conn_rec *c); + +/** + * Initialize the connection for use, perform the TLS handshake. + * + * Any failure will lead to the connection becoming aborted. + */ +void tls_filter_conn_init(conn_rec *c); + +/* + * <https://tools.ietf.org/html/rfc8449> says: + * "For large data transfers, small record sizes can materially affect performance." + * and + * "For TLS 1.2 and earlier, that limit is 2^14 octets. TLS 1.3 uses a limit of + * 2^14+1 octets." + * Maybe future TLS versions will raise that value, but for now these limits stand. + * Given the choice, we would like rustls to provide traffic data in those chunks. + */ +#define TLS_PREF_PLAIN_CHUNK_SIZE (16384) + +/* + * When retrieving TLS chunks for rustls, or providing it a buffer + * to pass out TLS chunks (which are then bucketed and written to the + * network filters), we ideally would do that in multiples of TLS + * messages sizes. + * That would be TLS_PREF_WRITE_SIZE + TLS Message Overhead, such as + * MAC and padding. But these vary with protocol and ciphers chosen, so + * we define something which should be "large enough", but not overly so. + */ +#define TLS_REC_EXTRA (1024) +#define TLS_REC_MAX_SIZE (TLS_PREF_PLAIN_CHUNK_SIZE + TLS_REC_EXTRA) + +#endif /* tls_filter_h */
\ No newline at end of file diff --git a/modules/tls/tls_ocsp.c b/modules/tls/tls_ocsp.c new file mode 100644 index 0000000..37e95b1 --- /dev/null +++ b/modules/tls/tls_ocsp.c @@ -0,0 +1,120 @@ +/* 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 <assert.h> +#include <apr_lib.h> +#include <apr_strings.h> + +#include <httpd.h> +#include <http_connection.h> +#include <http_core.h> +#include <http_log.h> +#include <http_ssl.h> + +#include <rustls.h> + +#include "tls_cert.h" +#include "tls_conf.h" +#include "tls_core.h" +#include "tls_proto.h" +#include "tls_ocsp.h" + +extern module AP_MODULE_DECLARE_DATA tls_module; +APLOG_USE_MODULE(tls); + + +static int prime_cert( + void *userdata, server_rec *s, const char *cert_id, const char *cert_pem, + const rustls_certified_key *certified_key) +{ + apr_pool_t *p = userdata; + apr_status_t rv; + + (void)certified_key; + rv = ap_ssl_ocsp_prime(s, p, cert_id, strlen(cert_id), cert_pem); + ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, s, "ocsp prime of cert [%s] from %s", + cert_id, s->server_hostname); + return 1; +} + +apr_status_t tls_ocsp_prime_certs(tls_conf_global_t *gc, apr_pool_t *p, server_rec *s) +{ + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, "ocsp priming of %d certs", + (int)tls_cert_reg_count(gc->cert_reg)); + tls_cert_reg_do(prime_cert, p, gc->cert_reg); + return APR_SUCCESS; +} + +typedef struct { + conn_rec *c; + const rustls_certified_key *key_in; + const rustls_certified_key *key_out; +} ocsp_copy_ctx_t; + +static void ocsp_clone_key(const unsigned char *der, apr_size_t der_len, void *userdata) +{ + ocsp_copy_ctx_t *ctx = userdata; + rustls_slice_bytes rslice; + rustls_result rr; + + rslice.data = der; + rslice.len = der_len; + + rr = rustls_certified_key_clone_with_ocsp(ctx->key_in, der_len? &rslice : NULL, &ctx->key_out); + if (RUSTLS_RESULT_OK != rr) { + const char *err_descr = NULL; + apr_status_t rv = tls_util_rustls_error(ctx->c->pool, rr, &err_descr); + ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, ctx->c, APLOGNO(10362) + "Failed add OCSP data to certificate: [%d] %s", (int)rr, err_descr); + } + else { + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, ctx->c, + "provided %ld bytes of ocsp response DER data to key.", (long)der_len); + } +} + +apr_status_t tls_ocsp_update_key( + conn_rec *c, const rustls_certified_key *certified_key, + const rustls_certified_key **pkey_out) +{ + tls_conf_conn_t *cc = tls_conf_conn_get(c); + tls_conf_server_t *sc; + const char *key_id; + apr_status_t rv = APR_SUCCESS; + ocsp_copy_ctx_t ctx; + + assert(cc); + assert(cc->server); + sc = tls_conf_server_get(cc->server); + key_id = tls_cert_reg_get_id(sc->global->cert_reg, certified_key); + if (!key_id) { + rv = APR_ENOENT; + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c, "certified key not registered"); + goto cleanup; + } + + ctx.c = c; + ctx.key_in = certified_key; + ctx.key_out = NULL; + rv = ap_ssl_ocsp_get_resp(cc->server, c, key_id, strlen(key_id), ocsp_clone_key, &ctx); + if (APR_SUCCESS != rv) { + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c, + "ocsp response not available for cert %s", key_id); + } + +cleanup: + *pkey_out = (APR_SUCCESS == rv)? ctx.key_out : NULL; + return rv; +} diff --git a/modules/tls/tls_ocsp.h b/modules/tls/tls_ocsp.h new file mode 100644 index 0000000..60770a9 --- /dev/null +++ b/modules/tls/tls_ocsp.h @@ -0,0 +1,47 @@ +/* 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. + */ +#ifndef tls_ocsp_h +#define tls_ocsp_h + +/** + * Prime the collected certified keys for OCSP response provisioning (aka. Stapling). + * + * To be called in the post-config phase of the server before connections are handled. + * @param gc the global module configuration with the certified_key registry + * @param p the pool to use for allocations + * @param s the base server record + */ +apr_status_t tls_ocsp_prime_certs(tls_conf_global_t *gc, apr_pool_t *p, server_rec *s); + +/** + * Provide the OCSP response data for the certified_key into the offered buffer, + * so available. + * If not data is available `out_n` is set to 0. Same, if the offered buffer + * is not large enough to hold the complete response. + * If OCSP response DER data is copied, the number of copied bytes is given in `out_n`. + * + * Note that only keys that have been primed initially will have OCSP data available. + * @param c the current connection + * @param certified_key the key to get the OCSP response data for + * @param buf a buffer which can hold up to `buf_len` bytes + * @param buf_len the length of `buf` + * @param out_n the number of OCSP response DER bytes copied or 0. + */ +apr_status_t tls_ocsp_update_key( + conn_rec *c, const rustls_certified_key *certified_key, + const rustls_certified_key **key_out); + +#endif /* tls_ocsp_h */ diff --git a/modules/tls/tls_proto.c b/modules/tls/tls_proto.c new file mode 100644 index 0000000..95a903b --- /dev/null +++ b/modules/tls/tls_proto.c @@ -0,0 +1,603 @@ +/* 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 <assert.h> +#include <apr_lib.h> +#include <apr_strings.h> + +#include <httpd.h> +#include <http_connection.h> +#include <http_core.h> +#include <http_log.h> + +#include <rustls.h> + +#include "tls_proto.h" +#include "tls_conf.h" +#include "tls_util.h" + +extern module AP_MODULE_DECLARE_DATA tls_module; +APLOG_USE_MODULE(tls); + + + +/** + * Known cipher as registered in + * <https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-4> + */ +static tls_cipher_t KNOWN_CIPHERS[] = { + { 0x0000, "TLS_NULL_WITH_NULL_NULL", NULL }, + { 0x0001, "TLS_RSA_WITH_NULL_MD5", NULL }, + { 0x0002, "TLS_RSA_WITH_NULL_SHA", NULL }, + { 0x0003, "TLS_RSA_EXPORT_WITH_RC4_40_MD5", NULL }, + { 0x0004, "TLS_RSA_WITH_RC4_128_MD5", NULL }, + { 0x0005, "TLS_RSA_WITH_RC4_128_SHA", NULL }, + { 0x0006, "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", NULL }, + { 0x0007, "TLS_RSA_WITH_IDEA_CBC_SHA", NULL }, + { 0x0008, "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", NULL }, + { 0x0009, "TLS_RSA_WITH_DES_CBC_SHA", NULL }, + { 0x000a, "TLS_RSA_WITH_3DES_EDE_CBC_SHA", NULL }, + { 0x000b, "TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA", NULL }, + { 0x000c, "TLS_DH_DSS_WITH_DES_CBC_SHA", NULL }, + { 0x000d, "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", NULL }, + { 0x000e, "TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA", NULL }, + { 0x000f, "TLS_DH_RSA_WITH_DES_CBC_SHA", NULL }, + { 0x0010, "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", NULL }, + { 0x0011, "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", NULL }, + { 0x0012, "TLS_DHE_DSS_WITH_DES_CBC_SHA", NULL }, + { 0x0013, "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", NULL }, + { 0x0014, "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", NULL }, + { 0x0015, "TLS_DHE_RSA_WITH_DES_CBC_SHA", NULL }, + { 0x0016, "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", NULL }, + { 0x0017, "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", NULL }, + { 0x0018, "TLS_DH_anon_WITH_RC4_128_MD5", NULL }, + { 0x0019, "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", NULL }, + { 0x001a, "TLS_DH_anon_WITH_DES_CBC_SHA", NULL }, + { 0x001b, "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", NULL }, + { 0x001c, "SSL_FORTEZZA_KEA_WITH_NULL_SHA", NULL }, + { 0x001d, "SSL_FORTEZZA_KEA_WITH_FORTEZZA_CBC_SHA", NULL }, + { 0x001e, "TLS_KRB5_WITH_DES_CBC_SHA_or_SSL_FORTEZZA_KEA_WITH_RC4_128_SHA", NULL }, + { 0x001f, "TLS_KRB5_WITH_3DES_EDE_CBC_SHA", NULL }, + { 0x0020, "TLS_KRB5_WITH_RC4_128_SHA", NULL }, + { 0x0021, "TLS_KRB5_WITH_IDEA_CBC_SHA", NULL }, + { 0x0022, "TLS_KRB5_WITH_DES_CBC_MD5", NULL }, + { 0x0023, "TLS_KRB5_WITH_3DES_EDE_CBC_MD5", NULL }, + { 0x0024, "TLS_KRB5_WITH_RC4_128_MD5", NULL }, + { 0x0025, "TLS_KRB5_WITH_IDEA_CBC_MD5", NULL }, + { 0x0026, "TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA", NULL }, + { 0x0027, "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA", NULL }, + { 0x0028, "TLS_KRB5_EXPORT_WITH_RC4_40_SHA", NULL }, + { 0x0029, "TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5", NULL }, + { 0x002a, "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5", NULL }, + { 0x002b, "TLS_KRB5_EXPORT_WITH_RC4_40_MD5", NULL }, + { 0x002c, "TLS_PSK_WITH_NULL_SHA", NULL }, + { 0x002d, "TLS_DHE_PSK_WITH_NULL_SHA", NULL }, + { 0x002e, "TLS_RSA_PSK_WITH_NULL_SHA", NULL }, + { 0x002f, "TLS_RSA_WITH_AES_128_CBC_SHA", NULL }, + { 0x0030, "TLS_DH_DSS_WITH_AES_128_CBC_SHA", NULL }, + { 0x0031, "TLS_DH_RSA_WITH_AES_128_CBC_SHA", NULL }, + { 0x0032, "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", NULL }, + { 0x0033, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", NULL }, + { 0x0034, "TLS_DH_anon_WITH_AES_128_CBC_SHA", NULL }, + { 0x0035, "TLS_RSA_WITH_AES_256_CBC_SHA", NULL }, + { 0x0036, "TLS_DH_DSS_WITH_AES_256_CBC_SHA", NULL }, + { 0x0037, "TLS_DH_RSA_WITH_AES_256_CBC_SHA", NULL }, + { 0x0038, "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", NULL }, + { 0x0039, "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", NULL }, + { 0x003a, "TLS_DH_anon_WITH_AES_256_CBC_SHA", NULL }, + { 0x003b, "TLS_RSA_WITH_NULL_SHA256", "NULL-SHA256" }, + { 0x003c, "TLS_RSA_WITH_AES_128_CBC_SHA256", "AES128-SHA256" }, + { 0x003d, "TLS_RSA_WITH_AES_256_CBC_SHA256", "AES256-SHA256" }, + { 0x003e, "TLS_DH_DSS_WITH_AES_128_CBC_SHA256", "DH-DSS-AES128-SHA256" }, + { 0x003f, "TLS_DH_RSA_WITH_AES_128_CBC_SHA256", "DH-RSA-AES128-SHA256" }, + { 0x0040, "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", "DHE-DSS-AES128-SHA256" }, + { 0x0041, "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", NULL }, + { 0x0042, "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", NULL }, + { 0x0043, "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", NULL }, + { 0x0044, "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", NULL }, + { 0x0045, "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", NULL }, + { 0x0046, "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", NULL }, + { 0x0047, "TLS_ECDH_ECDSA_WITH_NULL_SHA_draft", NULL }, + { 0x0048, "TLS_ECDH_ECDSA_WITH_RC4_128_SHA_draft", NULL }, + { 0x0049, "TLS_ECDH_ECDSA_WITH_DES_CBC_SHA_draft", NULL }, + { 0x004a, "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA_draft", NULL }, + { 0x004b, "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA_draft", NULL }, + { 0x004c, "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA_draft", NULL }, + { 0x004d, "TLS_ECDH_ECNRA_WITH_DES_CBC_SHA_draft", NULL }, + { 0x004e, "TLS_ECDH_ECNRA_WITH_3DES_EDE_CBC_SHA_draft", NULL }, + { 0x004f, "TLS_ECMQV_ECDSA_NULL_SHA_draft", NULL }, + { 0x0050, "TLS_ECMQV_ECDSA_WITH_RC4_128_SHA_draft", NULL }, + { 0x0051, "TLS_ECMQV_ECDSA_WITH_DES_CBC_SHA_draft", NULL }, + { 0x0052, "TLS_ECMQV_ECDSA_WITH_3DES_EDE_CBC_SHA_draft", NULL }, + { 0x0053, "TLS_ECMQV_ECNRA_NULL_SHA_draft", NULL }, + { 0x0054, "TLS_ECMQV_ECNRA_WITH_RC4_128_SHA_draft", NULL }, + { 0x0055, "TLS_ECMQV_ECNRA_WITH_DES_CBC_SHA_draft", NULL }, + { 0x0056, "TLS_ECMQV_ECNRA_WITH_3DES_EDE_CBC_SHA_draft", NULL }, + { 0x0057, "TLS_ECDH_anon_NULL_WITH_SHA_draft", NULL }, + { 0x0058, "TLS_ECDH_anon_WITH_RC4_128_SHA_draft", NULL }, + { 0x0059, "TLS_ECDH_anon_WITH_DES_CBC_SHA_draft", NULL }, + { 0x005a, "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA_draft", NULL }, + { 0x005b, "TLS_ECDH_anon_EXPORT_WITH_DES40_CBC_SHA_draft", NULL }, + { 0x005c, "TLS_ECDH_anon_EXPORT_WITH_RC4_40_SHA_draft", NULL }, + { 0x0060, "TLS_RSA_EXPORT1024_WITH_RC4_56_MD5", NULL }, + { 0x0061, "TLS_RSA_EXPORT1024_WITH_RC2_CBC_56_MD5", NULL }, + { 0x0062, "TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA", NULL }, + { 0x0063, "TLS_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA", NULL }, + { 0x0064, "TLS_RSA_EXPORT1024_WITH_RC4_56_SHA", NULL }, + { 0x0065, "TLS_DHE_DSS_EXPORT1024_WITH_RC4_56_SHA", NULL }, + { 0x0066, "TLS_DHE_DSS_WITH_RC4_128_SHA", NULL }, + { 0x0067, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", "DHE-RSA-AES128-SHA256" }, + { 0x0068, "TLS_DH_DSS_WITH_AES_256_CBC_SHA256", "DH-DSS-AES256-SHA256" }, + { 0x0069, "TLS_DH_RSA_WITH_AES_256_CBC_SHA256", "DH-RSA-AES256-SHA256" }, + { 0x006a, "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", "DHE-DSS-AES256-SHA256" }, + { 0x006b, "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", "DHE-RSA-AES256-SHA256" }, + { 0x006c, "TLS_DH_anon_WITH_AES_128_CBC_SHA256", "ADH-AES128-SHA256" }, + { 0x006d, "TLS_DH_anon_WITH_AES_256_CBC_SHA256", "ADH-AES256-SHA256" }, + { 0x0072, "TLS_DHE_DSS_WITH_3DES_EDE_CBC_RMD", NULL }, + { 0x0073, "TLS_DHE_DSS_WITH_AES_128_CBC_RMD", NULL }, + { 0x0074, "TLS_DHE_DSS_WITH_AES_256_CBC_RMD", NULL }, + { 0x0077, "TLS_DHE_RSA_WITH_3DES_EDE_CBC_RMD", NULL }, + { 0x0078, "TLS_DHE_RSA_WITH_AES_128_CBC_RMD", NULL }, + { 0x0079, "TLS_DHE_RSA_WITH_AES_256_CBC_RMD", NULL }, + { 0x007c, "TLS_RSA_WITH_3DES_EDE_CBC_RMD", NULL }, + { 0x007d, "TLS_RSA_WITH_AES_128_CBC_RMD", NULL }, + { 0x007e, "TLS_RSA_WITH_AES_256_CBC_RMD", NULL }, + { 0x0080, "TLS_GOSTR341094_WITH_28147_CNT_IMIT", NULL }, + { 0x0081, "TLS_GOSTR341001_WITH_28147_CNT_IMIT", NULL }, + { 0x0082, "TLS_GOSTR341094_WITH_NULL_GOSTR3411", NULL }, + { 0x0083, "TLS_GOSTR341001_WITH_NULL_GOSTR3411", NULL }, + { 0x0084, "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", NULL }, + { 0x0085, "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", NULL }, + { 0x0086, "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", NULL }, + { 0x0087, "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", NULL }, + { 0x0088, "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", NULL }, + { 0x0089, "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", NULL }, + { 0x008a, "TLS_PSK_WITH_RC4_128_SHA", "PSK-RC4-SHA" }, + { 0x008b, "TLS_PSK_WITH_3DES_EDE_CBC_SHA", "PSK-3DES-EDE-CBC-SHA" }, + { 0x008c, "TLS_PSK_WITH_AES_128_CBC_SHA", NULL }, + { 0x008d, "TLS_PSK_WITH_AES_256_CBC_SHA", NULL }, + { 0x008e, "TLS_DHE_PSK_WITH_RC4_128_SHA", NULL }, + { 0x008f, "TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA", NULL }, + { 0x0090, "TLS_DHE_PSK_WITH_AES_128_CBC_SHA", NULL }, + { 0x0091, "TLS_DHE_PSK_WITH_AES_256_CBC_SHA", NULL }, + { 0x0092, "TLS_RSA_PSK_WITH_RC4_128_SHA", NULL }, + { 0x0093, "TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA", NULL }, + { 0x0094, "TLS_RSA_PSK_WITH_AES_128_CBC_SHA", NULL }, + { 0x0095, "TLS_RSA_PSK_WITH_AES_256_CBC_SHA", NULL }, + { 0x0096, "TLS_RSA_WITH_SEED_CBC_SHA", NULL }, + { 0x0097, "TLS_DH_DSS_WITH_SEED_CBC_SHA", NULL }, + { 0x0098, "TLS_DH_RSA_WITH_SEED_CBC_SHA", NULL }, + { 0x0099, "TLS_DHE_DSS_WITH_SEED_CBC_SHA", NULL }, + { 0x009a, "TLS_DHE_RSA_WITH_SEED_CBC_SHA", NULL }, + { 0x009b, "TLS_DH_anon_WITH_SEED_CBC_SHA", NULL }, + { 0x009c, "TLS_RSA_WITH_AES_128_GCM_SHA256", "AES128-GCM-SHA256" }, + { 0x009d, "TLS_RSA_WITH_AES_256_GCM_SHA384", "AES256-GCM-SHA384" }, + { 0x009e, "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", "DHE-RSA-AES128-GCM-SHA256" }, + { 0x009f, "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", "DHE-RSA-AES256-GCM-SHA384" }, + { 0x00a0, "TLS_DH_RSA_WITH_AES_128_GCM_SHA256", "DH-RSA-AES128-GCM-SHA256" }, + { 0x00a1, "TLS_DH_RSA_WITH_AES_256_GCM_SHA384", "DH-RSA-AES256-GCM-SHA384" }, + { 0x00a2, "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", "DHE-DSS-AES128-GCM-SHA256" }, + { 0x00a3, "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", "DHE-DSS-AES256-GCM-SHA384" }, + { 0x00a4, "TLS_DH_DSS_WITH_AES_128_GCM_SHA256", "DH-DSS-AES128-GCM-SHA256" }, + { 0x00a5, "TLS_DH_DSS_WITH_AES_256_GCM_SHA384", "DH-DSS-AES256-GCM-SHA384" }, + { 0x00a6, "TLS_DH_anon_WITH_AES_128_GCM_SHA256", "ADH-AES128-GCM-SHA256" }, + { 0x00a7, "TLS_DH_anon_WITH_AES_256_GCM_SHA384", "ADH-AES256-GCM-SHA384" }, + { 0x00a8, "TLS_PSK_WITH_AES_128_GCM_SHA256", NULL }, + { 0x00a9, "TLS_PSK_WITH_AES_256_GCM_SHA384", NULL }, + { 0x00aa, "TLS_DHE_PSK_WITH_AES_128_GCM_SHA256", NULL }, + { 0x00ab, "TLS_DHE_PSK_WITH_AES_256_GCM_SHA384", NULL }, + { 0x00ac, "TLS_RSA_PSK_WITH_AES_128_GCM_SHA256", NULL }, + { 0x00ad, "TLS_RSA_PSK_WITH_AES_256_GCM_SHA384", NULL }, + { 0x00ae, "TLS_PSK_WITH_AES_128_CBC_SHA256", "PSK-AES128-CBC-SHA" }, + { 0x00af, "TLS_PSK_WITH_AES_256_CBC_SHA384", "PSK-AES256-CBC-SHA" }, + { 0x00b0, "TLS_PSK_WITH_NULL_SHA256", NULL }, + { 0x00b1, "TLS_PSK_WITH_NULL_SHA384", NULL }, + { 0x00b2, "TLS_DHE_PSK_WITH_AES_128_CBC_SHA256", NULL }, + { 0x00b3, "TLS_DHE_PSK_WITH_AES_256_CBC_SHA384", NULL }, + { 0x00b4, "TLS_DHE_PSK_WITH_NULL_SHA256", NULL }, + { 0x00b5, "TLS_DHE_PSK_WITH_NULL_SHA384", NULL }, + { 0x00b6, "TLS_RSA_PSK_WITH_AES_128_CBC_SHA256", NULL }, + { 0x00b7, "TLS_RSA_PSK_WITH_AES_256_CBC_SHA384", NULL }, + { 0x00b8, "TLS_RSA_PSK_WITH_NULL_SHA256", NULL }, + { 0x00b9, "TLS_RSA_PSK_WITH_NULL_SHA384", NULL }, + { 0x00ba, "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256", NULL }, + { 0x00bb, "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256", NULL }, + { 0x00bc, "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256", NULL }, + { 0x00bd, "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256", NULL }, + { 0x00be, "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", NULL }, + { 0x00bf, "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256", NULL }, + { 0x00c0, "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256", NULL }, + { 0x00c1, "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256", NULL }, + { 0x00c2, "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256", NULL }, + { 0x00c3, "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256", NULL }, + { 0x00c4, "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256", NULL }, + { 0x00c5, "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256", NULL }, + { 0x00ff, "TLS_EMPTY_RENEGOTIATION_INFO_SCSV", NULL }, + { 0x1301, "TLS_AES_128_GCM_SHA256", "TLS13_AES_128_GCM_SHA256" }, + { 0x1302, "TLS_AES_256_GCM_SHA384", "TLS13_AES_256_GCM_SHA384" }, + { 0x1303, "TLS_CHACHA20_POLY1305_SHA256", "TLS13_CHACHA20_POLY1305_SHA256" }, + { 0x1304, "TLS_AES_128_CCM_SHA256", "TLS13_AES_128_CCM_SHA256" }, + { 0x1305, "TLS_AES_128_CCM_8_SHA256", "TLS13_AES_128_CCM_8_SHA256" }, + { 0xc001, "TLS_ECDH_ECDSA_WITH_NULL_SHA", NULL }, + { 0xc002, "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", NULL }, + { 0xc003, "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", NULL }, + { 0xc004, "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", NULL }, + { 0xc005, "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", NULL }, + { 0xc006, "TLS_ECDHE_ECDSA_WITH_NULL_SHA", NULL }, + { 0xc007, "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", NULL }, + { 0xc008, "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", NULL }, + { 0xc009, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", NULL }, + { 0xc00a, "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", NULL }, + { 0xc00b, "TLS_ECDH_RSA_WITH_NULL_SHA", NULL }, + { 0xc00c, "TLS_ECDH_RSA_WITH_RC4_128_SHA", NULL }, + { 0xc00d, "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", NULL }, + { 0xc00e, "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", NULL }, + { 0xc00f, "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", NULL }, + { 0xc010, "TLS_ECDHE_RSA_WITH_NULL_SHA", NULL }, + { 0xc011, "TLS_ECDHE_RSA_WITH_RC4_128_SHA", NULL }, + { 0xc012, "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", NULL }, + { 0xc013, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", NULL }, + { 0xc014, "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", NULL }, + { 0xc015, "TLS_ECDH_anon_WITH_NULL_SHA", NULL }, + { 0xc016, "TLS_ECDH_anon_WITH_RC4_128_SHA", NULL }, + { 0xc017, "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", NULL }, + { 0xc018, "TLS_ECDH_anon_WITH_AES_128_CBC_SHA", NULL }, + { 0xc019, "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", NULL }, + { 0xc01a, "TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA", NULL }, + { 0xc01b, "TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA", NULL }, + { 0xc01c, "TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA", NULL }, + { 0xc01d, "TLS_SRP_SHA_WITH_AES_128_CBC_SHA", NULL }, + { 0xc01e, "TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA", NULL }, + { 0xc01f, "TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA", NULL }, + { 0xc020, "TLS_SRP_SHA_WITH_AES_256_CBC_SHA", NULL }, + { 0xc021, "TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA", NULL }, + { 0xc022, "TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA", NULL }, + { 0xc023, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", "ECDHE-ECDSA-AES128-SHA256" }, + { 0xc024, "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", "ECDHE-ECDSA-AES256-SHA384" }, + { 0xc025, "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256", "ECDH-ECDSA-AES128-SHA256" }, + { 0xc026, "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384", "ECDH-ECDSA-AES256-SHA384" }, + { 0xc027, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "ECDHE-RSA-AES128-SHA256" }, + { 0xc028, "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "ECDHE-RSA-AES256-SHA384" }, + { 0xc029, "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256", "ECDH-RSA-AES128-SHA256" }, + { 0xc02a, "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384", "ECDH-RSA-AES256-SHA384" }, + { 0xc02b, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "ECDHE-ECDSA-AES128-GCM-SHA256" }, + { 0xc02c, "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "ECDHE-ECDSA-AES256-GCM-SHA384" }, + { 0xc02d, "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256", "ECDH-ECDSA-AES128-GCM-SHA256" }, + { 0xc02e, "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384", "ECDH-ECDSA-AES256-GCM-SHA384" }, + { 0xc02f, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "ECDHE-RSA-AES128-GCM-SHA256" }, + { 0xc030, "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "ECDHE-RSA-AES256-GCM-SHA384" }, + { 0xc031, "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256", "ECDH-RSA-AES128-GCM-SHA256" }, + { 0xc032, "TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384", "ECDH-RSA-AES256-GCM-SHA384" }, + { 0xc033, "TLS_ECDHE_PSK_WITH_RC4_128_SHA", NULL }, + { 0xc034, "TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA", NULL }, + { 0xc035, "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA", NULL }, + { 0xc036, "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA", NULL }, + { 0xc037, "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256", NULL }, + { 0xc038, "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384", NULL }, + { 0xc039, "TLS_ECDHE_PSK_WITH_NULL_SHA", NULL }, + { 0xc03a, "TLS_ECDHE_PSK_WITH_NULL_SHA256", NULL }, + { 0xc03b, "TLS_ECDHE_PSK_WITH_NULL_SHA384", NULL }, + { 0xc03c, "TLS_RSA_WITH_ARIA_128_CBC_SHA256", NULL }, + { 0xc03d, "TLS_RSA_WITH_ARIA_256_CBC_SHA384", NULL }, + { 0xc03e, "TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256", NULL }, + { 0xc03f, "TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384", NULL }, + { 0xc040, "TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256", NULL }, + { 0xc041, "TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384", NULL }, + { 0xc042, "TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256", NULL }, + { 0xc043, "TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384", NULL }, + { 0xc044, "TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256", NULL }, + { 0xc045, "TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384", NULL }, + { 0xc046, "TLS_DH_anon_WITH_ARIA_128_CBC_SHA256", NULL }, + { 0xc047, "TLS_DH_anon_WITH_ARIA_256_CBC_SHA384", NULL }, + { 0xc048, "TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256", NULL }, + { 0xc049, "TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384", NULL }, + { 0xc04a, "TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256", NULL }, + { 0xc04b, "TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384", NULL }, + { 0xc04c, "TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256", NULL }, + { 0xc04d, "TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384", NULL }, + { 0xc04e, "TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256", NULL }, + { 0xc04f, "TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384", NULL }, + { 0xc050, "TLS_RSA_WITH_ARIA_128_GCM_SHA256", NULL }, + { 0xc051, "TLS_RSA_WITH_ARIA_256_GCM_SHA384", NULL }, + { 0xc052, "TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256", NULL }, + { 0xc053, "TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384", NULL }, + { 0xc054, "TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256", NULL }, + { 0xc055, "TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384", NULL }, + { 0xc056, "TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256", NULL }, + { 0xc057, "TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384", NULL }, + { 0xc058, "TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256", NULL }, + { 0xc059, "TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384", NULL }, + { 0xc05a, "TLS_DH_anon_WITH_ARIA_128_GCM_SHA256", NULL }, + { 0xc05b, "TLS_DH_anon_WITH_ARIA_256_GCM_SHA384", NULL }, + { 0xc05c, "TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256", NULL }, + { 0xc05d, "TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384", NULL }, + { 0xc05e, "TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256", NULL }, + { 0xc05f, "TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384", NULL }, + { 0xc060, "TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256", NULL }, + { 0xc061, "TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384", NULL }, + { 0xc062, "TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256", NULL }, + { 0xc063, "TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384", NULL }, + { 0xc064, "TLS_PSK_WITH_ARIA_128_CBC_SHA256", NULL }, + { 0xc065, "TLS_PSK_WITH_ARIA_256_CBC_SHA384", NULL }, + { 0xc066, "TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256", NULL }, + { 0xc067, "TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384", NULL }, + { 0xc068, "TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256", NULL }, + { 0xc069, "TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384", NULL }, + { 0xc06a, "TLS_PSK_WITH_ARIA_128_GCM_SHA256", NULL }, + { 0xc06b, "TLS_PSK_WITH_ARIA_256_GCM_SHA384", NULL }, + { 0xc06c, "TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256", NULL }, + { 0xc06d, "TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384", NULL }, + { 0xc06e, "TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256", NULL }, + { 0xc06f, "TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384", NULL }, + { 0xc070, "TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256", NULL }, + { 0xc071, "TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384", NULL }, + { 0xc072, "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", NULL }, + { 0xc073, "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", NULL }, + { 0xc074, "TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", NULL }, + { 0xc075, "TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", NULL }, + { 0xc076, "TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", NULL }, + { 0xc077, "TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384", NULL }, + { 0xc078, "TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256", NULL }, + { 0xc079, "TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384", NULL }, + { 0xc07a, "TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256", NULL }, + { 0xc07b, "TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384", NULL }, + { 0xc07c, "TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256", NULL }, + { 0xc07d, "TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384", NULL }, + { 0xc07e, "TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256", NULL }, + { 0xc07f, "TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384", NULL }, + { 0xc080, "TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256", NULL }, + { 0xc081, "TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384", NULL }, + { 0xc082, "TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256", NULL }, + { 0xc083, "TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384", NULL }, + { 0xc084, "TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256", NULL }, + { 0xc085, "TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384", NULL }, + { 0xc086, "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256", NULL }, + { 0xc087, "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384", NULL }, + { 0xc088, "TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256", NULL }, + { 0xc089, "TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384", NULL }, + { 0xc08a, "TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256", NULL }, + { 0xc08b, "TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384", NULL }, + { 0xc08c, "TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256", NULL }, + { 0xc08d, "TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384", NULL }, + { 0xc08e, "TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256", NULL }, + { 0xc08f, "TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384", NULL }, + { 0xc090, "TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256", NULL }, + { 0xc091, "TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384", NULL }, + { 0xc092, "TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256", NULL }, + { 0xc093, "TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384", NULL }, + { 0xc094, "TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256", NULL }, + { 0xc095, "TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384", NULL }, + { 0xc096, "TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256", NULL }, + { 0xc097, "TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384", NULL }, + { 0xc098, "TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256", NULL }, + { 0xc099, "TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384", NULL }, + { 0xc09a, "TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256", NULL }, + { 0xc09b, "TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384", NULL }, + { 0xc09c, "TLS_RSA_WITH_AES_128_CCM", NULL }, + { 0xc09d, "TLS_RSA_WITH_AES_256_CCM", NULL }, + { 0xc09e, "TLS_DHE_RSA_WITH_AES_128_CCM", NULL }, + { 0xc09f, "TLS_DHE_RSA_WITH_AES_256_CCM", NULL }, + { 0xc0a0, "TLS_RSA_WITH_AES_128_CCM_8", NULL }, + { 0xc0a1, "TLS_RSA_WITH_AES_256_CCM_8", NULL }, + { 0xc0a2, "TLS_DHE_RSA_WITH_AES_128_CCM_8", NULL }, + { 0xc0a3, "TLS_DHE_RSA_WITH_AES_256_CCM_8", NULL }, + { 0xc0a4, "TLS_PSK_WITH_AES_128_CCM", NULL }, + { 0xc0a5, "TLS_PSK_WITH_AES_256_CCM", NULL }, + { 0xc0a6, "TLS_DHE_PSK_WITH_AES_128_CCM", NULL }, + { 0xc0a7, "TLS_DHE_PSK_WITH_AES_256_CCM", NULL }, + { 0xc0a8, "TLS_PSK_WITH_AES_128_CCM_8", NULL }, + { 0xc0a9, "TLS_PSK_WITH_AES_256_CCM_8", NULL }, + { 0xc0aa, "TLS_PSK_DHE_WITH_AES_128_CCM_8", NULL }, + { 0xc0ab, "TLS_PSK_DHE_WITH_AES_256_CCM_8", NULL }, + { 0xcca8, "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", "ECDHE-RSA-CHACHA20-POLY1305" }, + { 0xcca9, "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", "ECDHE-ECDSA-CHACHA20-POLY1305" }, + { 0xccaa, "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256", "DHE-RSA-CHACHA20-POLY1305" }, + { 0xccab, "TLS_PSK_WITH_CHACHA20_POLY1305_SHA256", "PSK-CHACHA20-POLY1305" }, + { 0xccac, "TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256", "ECDHE-PSK-CHACHA20-POLY1305" }, + { 0xccad, "TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256", "DHE-PSK-CHACHA20-POLY1305" }, + { 0xccae, "TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256", "RSA-PSK-CHACHA20-POLY1305" }, + { 0xfefe, "SSL_RSA_FIPS_WITH_DES_CBC_SHA", NULL }, + { 0xfeff, "SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA", NULL }, +}; + +typedef struct { + apr_uint16_t id; + const rustls_supported_ciphersuite *rustls_suite; +} rustls_cipher_t; + +tls_proto_conf_t *tls_proto_init(apr_pool_t *pool, server_rec *s) +{ + tls_proto_conf_t *conf; + tls_cipher_t *cipher; + const rustls_supported_ciphersuite *rustls_suite; + rustls_cipher_t *rcipher; + apr_uint16_t id; + apr_size_t i; + + (void)s; + conf = apr_pcalloc(pool, sizeof(*conf)); + + conf->supported_versions = apr_array_make(pool, 3, sizeof(apr_uint16_t)); + /* Until we can look that up at crustls, we assume what we currently know */ + APR_ARRAY_PUSH(conf->supported_versions, apr_uint16_t) = TLS_VERSION_1_2; + APR_ARRAY_PUSH(conf->supported_versions, apr_uint16_t) = TLS_VERSION_1_3; + + conf->known_ciphers_by_name = apr_hash_make(pool); + conf->known_ciphers_by_id = apr_hash_make(pool); + for (i = 0; i < TLS_DIM(KNOWN_CIPHERS); ++i) { + cipher = &KNOWN_CIPHERS[i]; + apr_hash_set(conf->known_ciphers_by_id, &cipher->id, sizeof(apr_uint16_t), cipher); + apr_hash_set(conf->known_ciphers_by_name, cipher->name, APR_HASH_KEY_STRING, cipher); + if (cipher->alias) { + apr_hash_set(conf->known_ciphers_by_name, cipher->alias, APR_HASH_KEY_STRING, cipher); + } + } + + conf->supported_cipher_ids = apr_array_make(pool, 10, sizeof(apr_uint16_t)); + conf->rustls_ciphers_by_id = apr_hash_make(pool); + i = 0; + while ((rustls_suite = rustls_all_ciphersuites_get_entry(i++))) { + id = rustls_supported_ciphersuite_get_suite(rustls_suite); + rcipher = apr_pcalloc(pool, sizeof(*rcipher)); + rcipher->id = id; + rcipher->rustls_suite = rustls_suite; + APR_ARRAY_PUSH(conf->supported_cipher_ids, apr_uint16_t) = id; + apr_hash_set(conf->rustls_ciphers_by_id, &rcipher->id, sizeof(apr_uint16_t), rcipher); + + } + + return conf; +} + +const char *tls_proto_get_cipher_names( + tls_proto_conf_t *conf, const apr_array_header_t *ciphers, apr_pool_t *pool) +{ + apr_array_header_t *names; + int n; + + names = apr_array_make(pool, ciphers->nelts, sizeof(const char*)); + for (n = 0; n < ciphers->nelts; ++n) { + apr_uint16_t id = APR_ARRAY_IDX(ciphers, n, apr_uint16_t); + APR_ARRAY_PUSH(names, const char *) = tls_proto_get_cipher_name(conf, id, pool); + } + return apr_array_pstrcat(pool, names, ':'); +} + +apr_status_t tls_proto_pre_config(apr_pool_t *pool, apr_pool_t *ptemp) +{ + (void)pool; + (void)ptemp; + return APR_SUCCESS; +} + +apr_status_t tls_proto_post_config(apr_pool_t *pool, apr_pool_t *ptemp, server_rec *s) +{ + tls_conf_server_t *sc = tls_conf_server_get(s); + tls_proto_conf_t *conf = sc->global->proto; + + (void)pool; + if (APLOGdebug(s)) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10314) + "tls ciphers supported: %s", + tls_proto_get_cipher_names(conf, conf->supported_cipher_ids, ptemp)); + } + return APR_SUCCESS; +} + +static apr_status_t get_uint16_from(const char *name, const char *prefix, apr_uint16_t *pint) +{ + apr_size_t plen = strlen(prefix); + if (strlen(name) == plen+4 && !strncmp(name, prefix, plen)) { + /* may be a hex notation cipher id */ + char *end = NULL; + apr_int64_t code = apr_strtoi64(name + plen, &end, 16); + if ((!end || !*end) && code && code <= APR_UINT16_MAX) { + *pint = (apr_uint16_t)code; + return APR_SUCCESS; + } + } + return APR_ENOENT; +} + +apr_uint16_t tls_proto_get_version_by_name(tls_proto_conf_t *conf, const char *name) +{ + apr_uint16_t version; + (void)conf; + if (!apr_strnatcasecmp(name, "TLSv1.2")) { + return TLS_VERSION_1_2; + } + else if (!apr_strnatcasecmp(name, "TLSv1.3")) { + return TLS_VERSION_1_3; + } + if (APR_SUCCESS == get_uint16_from(name, "TLSv0x", &version)) { + return version; + } + return 0; +} + +const char *tls_proto_get_version_name( + tls_proto_conf_t *conf, apr_uint16_t id, apr_pool_t *pool) +{ + (void)conf; + switch (id) { + case TLS_VERSION_1_2: + return "TLSv1.2"; + case TLS_VERSION_1_3: + return "TLSv1.3"; + default: + return apr_psprintf(pool, "TLSv0x%04x", id); + } +} + +apr_array_header_t *tls_proto_create_versions_plus( + tls_proto_conf_t *conf, apr_uint16_t min_version, apr_pool_t *pool) +{ + apr_array_header_t *versions = apr_array_make(pool, 3, sizeof(apr_uint16_t)); + apr_uint16_t version; + int i; + + for (i = 0; i < conf->supported_versions->nelts; ++i) { + version = APR_ARRAY_IDX(conf->supported_versions, i, apr_uint16_t); + if (version >= min_version) { + APR_ARRAY_PUSH(versions, apr_uint16_t) = version; + } + } + return versions; +} + +int tls_proto_is_cipher_supported(tls_proto_conf_t *conf, apr_uint16_t cipher) +{ + return tls_util_array_uint16_contains(conf->supported_cipher_ids, cipher); +} + +apr_status_t tls_proto_get_cipher_by_name( + tls_proto_conf_t *conf, const char *name, apr_uint16_t *pcipher) +{ + tls_cipher_t *cipher = apr_hash_get(conf->known_ciphers_by_name, name, APR_HASH_KEY_STRING); + if (cipher) { + *pcipher = cipher->id; + return APR_SUCCESS; + } + return get_uint16_from(name, "TLS_CIPHER_0x", pcipher); +} + +const char *tls_proto_get_cipher_name( + tls_proto_conf_t *conf, apr_uint16_t id, apr_pool_t *pool) +{ + tls_cipher_t *cipher = apr_hash_get(conf->known_ciphers_by_id, &id, sizeof(apr_uint16_t)); + if (cipher) { + return cipher->name; + } + return apr_psprintf(pool, "TLS_CIPHER_0x%04x", id); +} + +apr_array_header_t *tls_proto_get_rustls_suites( + tls_proto_conf_t *conf, const apr_array_header_t *ids, apr_pool_t *pool) +{ + apr_array_header_t *suites; + rustls_cipher_t *rcipher; + apr_uint16_t id; + int i; + + suites = apr_array_make(pool, ids->nelts, sizeof(const rustls_supported_ciphersuite*)); + for (i = 0; i < ids->nelts; ++i) { + id = APR_ARRAY_IDX(ids, i, apr_uint16_t); + rcipher = apr_hash_get(conf->rustls_ciphers_by_id, &id, sizeof(apr_uint16_t)); + if (rcipher) { + APR_ARRAY_PUSH(suites, const rustls_supported_ciphersuite *) = rcipher->rustls_suite; + } + } + return suites; +} diff --git a/modules/tls/tls_proto.h b/modules/tls/tls_proto.h new file mode 100644 index 0000000..a3fe881 --- /dev/null +++ b/modules/tls/tls_proto.h @@ -0,0 +1,124 @@ +/* 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. + */ +#ifndef tls_proto_h +#define tls_proto_h + +#include "tls_util.h" + + +#define TLS_VERSION_1_2 0x0303 +#define TLS_VERSION_1_3 0x0304 + +/** + * Specification of a TLS cipher by name, possible alias and its 16 bit value + * as assigned by IANA. + */ +typedef struct { + apr_uint16_t id; /* IANA 16-bit assigned value as used on the wire */ + const char *name; /* IANA given name of the cipher */ + const char *alias; /* Optional, commonly known alternate name */ +} tls_cipher_t; + +/** + * TLS protocol related definitions constructed + * by querying crustls lib. + */ +typedef struct tls_proto_conf_t tls_proto_conf_t; +struct tls_proto_conf_t { + apr_array_header_t *supported_versions; /* supported protocol versions (apr_uint16_t) */ + apr_hash_t *known_ciphers_by_name; /* hash by name of known tls_cipher_t* */ + apr_hash_t *known_ciphers_by_id; /* hash by id of known tls_cipher_t* */ + apr_hash_t *rustls_ciphers_by_id; /* hash by id of rustls rustls_supported_ciphersuite* */ + apr_array_header_t *supported_cipher_ids; /* cipher ids (apr_uint16_t) supported by rustls */ + const rustls_root_cert_store *native_roots; +}; + +/** + * Create and populate the protocol configuration. + */ +tls_proto_conf_t *tls_proto_init(apr_pool_t *p, server_rec *s); + +/** + * Called during pre-config phase to start initialization + * of the tls protocol configuration. + */ +apr_status_t tls_proto_pre_config(apr_pool_t *pool, apr_pool_t *ptemp); + +/** + * Called during post-config phase to conclude the initialization + * of the tls protocol configuration. + */ +apr_status_t tls_proto_post_config(apr_pool_t *p, apr_pool_t *ptemp, server_rec *s); + +/** + * Get the TLS protocol identifier (as used on the wire) for the TLS + * protocol of the given name. Returns 0 if protocol is unknown. + */ +apr_uint16_t tls_proto_get_version_by_name(tls_proto_conf_t *conf, const char *name); + +/** + * Get the name of the protocol version identified by its identifier. This + * will return the name from the protocol configuration or, if unknown, create + * the string `TLSv0x%04x` from the 16bit identifier. + */ +const char *tls_proto_get_version_name( + tls_proto_conf_t *conf, apr_uint16_t id, apr_pool_t *pool); + +/** + * Create an array of the given TLS protocol version identifier `min_version` + * and all supported new ones. The array carries apr_uint16_t values. + */ +apr_array_header_t *tls_proto_create_versions_plus( + tls_proto_conf_t *conf, apr_uint16_t min_version, apr_pool_t *pool); + +/** + * Get a TLS cipher spec by name/alias. + */ +apr_status_t tls_proto_get_cipher_by_name( + tls_proto_conf_t *conf, const char *name, apr_uint16_t *pcipher); + +/** + * Return != 0 iff the cipher is supported by the rustls library. + */ +int tls_proto_is_cipher_supported(tls_proto_conf_t *conf, apr_uint16_t cipher); + +/** + * Get the name of a TLS cipher for the IANA assigned 16bit value. This will + * return the name in the protocol configuration, if the cipher is known, and + * create the string `TLS_CIPHER_0x%04x` for the 16bit cipher value. + */ +const char *tls_proto_get_cipher_name( + tls_proto_conf_t *conf, apr_uint16_t cipher, apr_pool_t *pool); + +/** + * Get the concatenated names with ':' as separator of all TLS cipher identifiers + * as given in `ciphers`. + * @param conf the TLS protocol configuration + * @param ciphers the 16bit values of the TLS ciphers + * @param pool to use for allocation the string. + */ +const char *tls_proto_get_cipher_names( + tls_proto_conf_t *conf, const apr_array_header_t *ciphers, apr_pool_t *pool); + +/** + * Convert an array of TLS cipher 16bit identifiers into the `rustls_supported_ciphersuite` + * instances that can be passed to crustls in session configurations. + * Any cipher identifier not supported by rustls we be silently omitted. + */ +apr_array_header_t *tls_proto_get_rustls_suites( + tls_proto_conf_t *conf, const apr_array_header_t *ids, apr_pool_t *pool); + +#endif /* tls_proto_h */ diff --git a/modules/tls/tls_util.c b/modules/tls/tls_util.c new file mode 100644 index 0000000..9eac212 --- /dev/null +++ b/modules/tls/tls_util.c @@ -0,0 +1,367 @@ +/* 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 <assert.h> +#include <apr_lib.h> +#include <apr_file_info.h> +#include <apr_strings.h> + +#include <httpd.h> +#include <http_core.h> +#include <http_log.h> + +#include <rustls.h> + +#include "tls_proto.h" +#include "tls_util.h" + + +extern module AP_MODULE_DECLARE_DATA tls_module; +APLOG_USE_MODULE(tls); + + +tls_data_t tls_data_from_str(const char *s) +{ + tls_data_t d; + d.data = (const unsigned char*)s; + d.len = s? strlen(s) : 0; + return d; +} + +tls_data_t tls_data_assign_copy(apr_pool_t *p, const tls_data_t *d) +{ + tls_data_t copy; + copy.data = apr_pmemdup(p, d->data, d->len); + copy.len = d->len; + return copy; +} + +tls_data_t *tls_data_copy(apr_pool_t *p, const tls_data_t *d) +{ + tls_data_t *copy; + copy = apr_pcalloc(p, sizeof(*copy)); + *copy = tls_data_assign_copy(p, d); + return copy; +} + +const char *tls_data_to_str(apr_pool_t *p, const tls_data_t *d) +{ + char *s = apr_pcalloc(p, d->len+1); + memcpy(s, d->data, d->len); + return s; +} + +apr_status_t tls_util_rustls_error( + apr_pool_t *p, rustls_result rr, const char **perr_descr) +{ + if (perr_descr) { + char buffer[HUGE_STRING_LEN]; + apr_size_t len = 0; + + rustls_error(rr, buffer, sizeof(buffer), &len); + *perr_descr = apr_pstrndup(p, buffer, len); + } + return APR_EGENERAL; +} + +int tls_util_is_file( + apr_pool_t *p, const char *fpath) +{ + apr_finfo_t finfo; + + return (fpath != NULL + && apr_stat(&finfo, fpath, APR_FINFO_TYPE|APR_FINFO_SIZE, p) == 0 + && finfo.filetype == APR_REG); +} + +apr_status_t tls_util_file_load( + apr_pool_t *p, const char *fpath, apr_size_t min_len, apr_size_t max_len, tls_data_t *data) +{ + apr_finfo_t finfo; + apr_status_t rv; + apr_file_t *f = NULL; + unsigned char *buffer; + apr_size_t len; + const char *err = NULL; + tls_data_t *d; + + rv = apr_stat(&finfo, fpath, APR_FINFO_TYPE|APR_FINFO_SIZE, p); + if (APR_SUCCESS != rv) { + err = "cannot stat"; goto cleanup; + } + if (finfo.filetype != APR_REG) { + err = "not a plain file"; + rv = APR_EINVAL; goto cleanup; + } + if (finfo.size > LONG_MAX) { + err = "file is too large"; + rv = APR_EINVAL; goto cleanup; + } + len = (apr_size_t)finfo.size; + if (len < min_len || len > max_len) { + err = "file size not in allowed range"; + rv = APR_EINVAL; goto cleanup; + } + d = apr_pcalloc(p, sizeof(*d)); + buffer = apr_pcalloc(p, len+1); /* keep it NUL terminated in any case */ + rv = apr_file_open(&f, fpath, APR_FOPEN_READ, 0, p); + if (APR_SUCCESS != rv) { + err = "error opening"; goto cleanup; + } + rv = apr_file_read(f, buffer, &len); + if (APR_SUCCESS != rv) { + err = "error reading"; goto cleanup; + } +cleanup: + if (f) apr_file_close(f); + if (APR_SUCCESS == rv) { + data->data = buffer; + data->len = len; + } + else { + memset(data, 0, sizeof(*data)); + ap_log_perror(APLOG_MARK, APLOG_ERR, rv, p, APLOGNO(10361) + "Failed to load file %s: %s", fpath, err? err: "-"); + } + return rv; +} + +int tls_util_array_uint16_contains(const apr_array_header_t* a, apr_uint16_t n) +{ + int i; + for (i = 0; i < a->nelts; ++i) { + if (APR_ARRAY_IDX(a, i, apr_uint16_t) == n) return 1; + } + return 0; +} + +const apr_array_header_t *tls_util_array_uint16_remove( + apr_pool_t *pool, const apr_array_header_t* from, const apr_array_header_t* others) +{ + apr_array_header_t *na = NULL; + apr_uint16_t id; + int i, j; + + for (i = 0; i < from->nelts; ++i) { + id = APR_ARRAY_IDX(from, i, apr_uint16_t); + if (tls_util_array_uint16_contains(others, id)) { + if (na == NULL) { + /* first removal, make a new result array, copy elements before */ + na = apr_array_make(pool, from->nelts, sizeof(apr_uint16_t)); + for (j = 0; j < i; ++j) { + APR_ARRAY_PUSH(na, apr_uint16_t) = APR_ARRAY_IDX(from, j, apr_uint16_t); + } + } + } + else if (na) { + APR_ARRAY_PUSH(na, apr_uint16_t) = id; + } + } + return na? na : from; +} + +apr_status_t tls_util_brigade_transfer( + apr_bucket_brigade *dest, apr_bucket_brigade *src, apr_off_t length, + apr_off_t *pnout) +{ + apr_bucket *b; + apr_off_t remain = length; + apr_status_t rv = APR_SUCCESS; + const char *ign; + apr_size_t ilen; + + *pnout = 0; + while (!APR_BRIGADE_EMPTY(src)) { + b = APR_BRIGADE_FIRST(src); + + if (APR_BUCKET_IS_METADATA(b)) { + APR_BUCKET_REMOVE(b); + APR_BRIGADE_INSERT_TAIL(dest, b); + } + else { + if (remain == (apr_off_t)b->length) { + /* fall through */ + } + else if (remain <= 0) { + goto cleanup; + } + else { + if (b->length == ((apr_size_t)-1)) { + rv= apr_bucket_read(b, &ign, &ilen, APR_BLOCK_READ); + if (APR_SUCCESS != rv) goto cleanup; + } + if (remain < (apr_off_t)b->length) { + apr_bucket_split(b, (apr_size_t)remain); + } + } + APR_BUCKET_REMOVE(b); + APR_BRIGADE_INSERT_TAIL(dest, b); + remain -= (apr_off_t)b->length; + *pnout += (apr_off_t)b->length; + } + } +cleanup: + return rv; +} + +apr_status_t tls_util_brigade_copy( + apr_bucket_brigade *dest, apr_bucket_brigade *src, apr_off_t length, + apr_off_t *pnout) +{ + apr_bucket *b, *next; + apr_off_t remain = length; + apr_status_t rv = APR_SUCCESS; + const char *ign; + apr_size_t ilen; + + *pnout = 0; + for (b = APR_BRIGADE_FIRST(src); + b != APR_BRIGADE_SENTINEL(src); + b = next) { + next = APR_BUCKET_NEXT(b); + + if (APR_BUCKET_IS_METADATA(b)) { + /* fall through */ + } + else { + if (remain == (apr_off_t)b->length) { + /* fall through */ + } + else if (remain <= 0) { + goto cleanup; + } + else { + if (b->length == ((apr_size_t)-1)) { + rv = apr_bucket_read(b, &ign, &ilen, APR_BLOCK_READ); + if (APR_SUCCESS != rv) goto cleanup; + } + if (remain < (apr_off_t)b->length) { + apr_bucket_split(b, (apr_size_t)remain); + } + } + } + rv = apr_bucket_copy(b, &b); + if (APR_SUCCESS != rv) goto cleanup; + APR_BRIGADE_INSERT_TAIL(dest, b); + remain -= (apr_off_t)b->length; + *pnout += (apr_off_t)b->length; + } +cleanup: + return rv; +} + +apr_status_t tls_util_brigade_split_line( + apr_bucket_brigade *dest, apr_bucket_brigade *src, + apr_read_type_e block, apr_off_t length, + apr_off_t *pnout) +{ + apr_off_t nstart, nend; + apr_status_t rv; + + apr_brigade_length(dest, 0, &nstart); + rv = apr_brigade_split_line(dest, src, block, length); + if (APR_SUCCESS != rv) goto cleanup; + apr_brigade_length(dest, 0, &nend); + /* apr_brigade_split_line() has the nasty habit of leaving a 0-length bucket + * at the start of the brigade when it transferred the whole content. Get rid of it. + */ + if (!APR_BRIGADE_EMPTY(src)) { + apr_bucket *b = APR_BRIGADE_FIRST(src); + if (!APR_BUCKET_IS_METADATA(b) && 0 == b->length) { + APR_BUCKET_REMOVE(b); + apr_bucket_delete(b); + } + } +cleanup: + *pnout = (APR_SUCCESS == rv)? (nend - nstart) : 0; + return rv; +} + +int tls_util_name_matches_server(const char *name, server_rec *s) +{ + apr_array_header_t *names; + char **alias; + int i; + + if (!s || !s->server_hostname) return 0; + if (!strcasecmp(name, s->server_hostname)) return 1; + /* first the fast equality match, then the pattern wild_name matches */ + names = s->names; + if (!names) return 0; + alias = (char **)names->elts; + for (i = 0; i < names->nelts; ++i) { + if (alias[i] && !strcasecmp(name, alias[i])) return 1; + } + names = s->wild_names; + if (!names) return 0; + alias = (char **)names->elts; + for (i = 0; i < names->nelts; ++i) { + if (alias[i] && !ap_strcasecmp_match(name, alias[i])) return 1; + } + return 0; +} + +apr_size_t tls_util_bucket_print(char *buffer, apr_size_t bmax, + apr_bucket *b, const char *sep) +{ + apr_size_t off = 0; + if (sep && *sep) { + off += (size_t)apr_snprintf(buffer+off, bmax-off, "%s", sep); + } + + if (bmax <= off) { + return off; + } + else if (APR_BUCKET_IS_METADATA(b)) { + off += (size_t)apr_snprintf(buffer+off, bmax-off, "%s", b->type->name); + } + else if (bmax > off) { + off += (size_t)apr_snprintf(buffer+off, bmax-off, "%s[%ld]", + b->type->name, (long)(b->length == ((apr_size_t)-1)? + -1 : (int)b->length)); + } + return off; +} + +apr_size_t tls_util_bb_print(char *buffer, apr_size_t bmax, + const char *tag, const char *sep, + apr_bucket_brigade *bb) +{ + apr_size_t off = 0; + const char *sp = ""; + apr_bucket *b; + + if (bmax > 1) { + if (bb) { + memset(buffer, 0, bmax--); + off += (size_t)apr_snprintf(buffer+off, bmax-off, "%s(", tag); + for (b = APR_BRIGADE_FIRST(bb); + (bmax > off) && (b != APR_BRIGADE_SENTINEL(bb)); + b = APR_BUCKET_NEXT(b)) { + + off += tls_util_bucket_print(buffer+off, bmax-off, b, sp); + sp = " "; + } + if (bmax > off) { + off += (size_t)apr_snprintf(buffer+off, bmax-off, ")%s", sep); + } + } + else { + off += (size_t)apr_snprintf(buffer+off, bmax-off, "%s(null)%s", tag, sep); + } + } + return off; +} + diff --git a/modules/tls/tls_util.h b/modules/tls/tls_util.h new file mode 100644 index 0000000..18ae4df --- /dev/null +++ b/modules/tls/tls_util.h @@ -0,0 +1,157 @@ +/* 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. + */ +#ifndef tls_util_h +#define tls_util_h + +#define TLS_DIM(a) (sizeof(a)/sizeof(a[0])) + + +/** + * Simple struct to hold a range of bytes and its length together. + */ +typedef struct tls_data_t tls_data_t; +struct tls_data_t { + const unsigned char* data; + apr_size_t len; +}; + +/** + * Return a tls_data_t for a string. + */ +tls_data_t tls_data_from_str(const char *s); + +/** + * Create a copy of a tls_data_t using the given pool. + */ +tls_data_t *tls_data_copy(apr_pool_t *p, const tls_data_t *d); + +/** + * Return a copy of a tls_data_t bytes allocated from pool. + */ +tls_data_t tls_data_assign_copy(apr_pool_t *p, const tls_data_t *d); + +/** + * Convert the data bytes in `d` into a NUL-terminated string. + * There is no check if the data bytes already contain NUL. + */ +const char *tls_data_to_str(apr_pool_t *p, const tls_data_t *d); + +/** + * Return != 0 if fpath is a 'real' file. + */ +int tls_util_is_file(apr_pool_t *p, const char *fpath); + +/** + * Inspect a 'rustls_result', retrieve the error description for it and + * return the apr_status_t to use as our error status. + */ +apr_status_t tls_util_rustls_error(apr_pool_t *p, rustls_result rr, const char **perr_descr); + +/** + * Load up to `max_len` bytes into a buffer allocated from the pool. + * @return ARP_SUCCESS on successful load. + * APR_EINVAL when the file was not a regular file or is too large. + */ +apr_status_t tls_util_file_load( + apr_pool_t *p, const char *fpath, size_t min_len, size_t max_len, tls_data_t *data); + +/** + * Return != 0 iff the array of apr_uint16_t contains value n. + */ +int tls_util_array_uint16_contains(const apr_array_header_t* a, apr_uint16_t n); + +/** + * Remove all apr_uint16_t in `others` from array `from`. + * Returns the new array or, if no overlap was found, the `from` array unchanged. + */ +const apr_array_header_t *tls_util_array_uint16_remove( + apr_pool_t *pool, const apr_array_header_t* from, const apr_array_header_t* others); + +/** + * Transfer up to <length> bytes from <src> to <dest>, including all + * encountered meta data buckets. The transferred buckets/data are + * removed from <src>. + * Return the actual byte count transferred in <pnout>. + */ +apr_status_t tls_util_brigade_transfer( + apr_bucket_brigade *dest, apr_bucket_brigade *src, apr_off_t length, + apr_off_t *pnout); + +/** + * Copy up to <length> bytes from <src> to <dest>, including all + * encountered meta data buckets. <src> remains semantically unchaanged, + * meaning there might have been buckets split or changed while reading + * their content. + * Return the actual byte count copied in <pnout>. + */ +apr_status_t tls_util_brigade_copy( + apr_bucket_brigade *dest, apr_bucket_brigade *src, apr_off_t length, + apr_off_t *pnout); + +/** + * Get a line of max `length` bytes from `src` into `dest`. + * Return the number of bytes transferred in `pnout`. + */ +apr_status_t tls_util_brigade_split_line( + apr_bucket_brigade *dest, apr_bucket_brigade *src, + apr_read_type_e block, apr_off_t length, + apr_off_t *pnout); + +/** + * Return != 0 iff the given <name> matches the configured 'ServerName' + * or one of the 'ServerAlias' name of <s>, including wildcard patterns + * as understood by ap_strcasecmp_match(). + */ +int tls_util_name_matches_server(const char *name, server_rec *s); + + +/** + * Print a bucket's meta data (type and length) to the buffer. + * @return number of characters printed + */ +apr_size_t tls_util_bucket_print(char *buffer, apr_size_t bmax, + apr_bucket *b, const char *sep); + +/** + * Prints the brigade bucket types and lengths into the given buffer + * up to bmax. + * @return number of characters printed + */ +apr_size_t tls_util_bb_print(char *buffer, apr_size_t bmax, + const char *tag, const char *sep, + apr_bucket_brigade *bb); +/** + * Logs the bucket brigade (which bucket types with what length) + * to the log at the given level. + * @param c the connection to log for + * @param sid the stream identifier this brigade belongs to + * @param level the log level (as in APLOG_*) + * @param tag a short message text about the context + * @param bb the brigade to log + */ +#define tls_util_bb_log(c, level, tag, bb) \ +do { \ + char buffer[4 * 1024]; \ + const char *line = "(null)"; \ + apr_size_t len, bmax = sizeof(buffer)/sizeof(buffer[0]); \ + len = tls_util_bb_print(buffer, bmax, (tag), "", (bb)); \ + ap_log_cerror(APLOG_MARK, level, 0, (c), "bb_dump(%ld): %s", \ + ((c)->master? (c)->master->id : (c)->id), (len? buffer : line)); \ +} while(0) + + + +#endif /* tls_util_h */ diff --git a/modules/tls/tls_var.c b/modules/tls/tls_var.c new file mode 100644 index 0000000..fa4ae2a --- /dev/null +++ b/modules/tls/tls_var.c @@ -0,0 +1,397 @@ +/* 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 <assert.h> +#include <apr_lib.h> +#include <apr_strings.h> + +#include <httpd.h> +#include <http_connection.h> +#include <http_core.h> +#include <http_main.h> +#include <http_log.h> +#include <ap_socache.h> + +#include <rustls.h> + +#include "tls_conf.h" +#include "tls_core.h" +#include "tls_cert.h" +#include "tls_util.h" +#include "tls_var.h" +#include "tls_version.h" + + +extern module AP_MODULE_DECLARE_DATA tls_module; +APLOG_USE_MODULE(tls); + +typedef struct { + apr_pool_t *p; + server_rec *s; + conn_rec *c; + request_rec *r; + tls_conf_conn_t *cc; + const char *name; + const char *arg_s; + int arg_i; +} tls_var_lookup_ctx_t; + +typedef const char *var_lookup(const tls_var_lookup_ctx_t *ctx); + +static const char *var_get_ssl_protocol(const tls_var_lookup_ctx_t *ctx) +{ + return ctx->cc->tls_protocol_name; +} + +static const char *var_get_ssl_cipher(const tls_var_lookup_ctx_t *ctx) +{ + return ctx->cc->tls_cipher_name; +} + +static const char *var_get_sni_hostname(const tls_var_lookup_ctx_t *ctx) +{ + return ctx->cc->sni_hostname; +} + +static const char *var_get_version_interface(const tls_var_lookup_ctx_t *ctx) +{ + tls_conf_server_t *sc = tls_conf_server_get(ctx->s); + return sc->global->module_version; +} + +static const char *var_get_version_library(const tls_var_lookup_ctx_t *ctx) +{ + tls_conf_server_t *sc = tls_conf_server_get(ctx->s); + return sc->global->crustls_version; +} + +static const char *var_get_false(const tls_var_lookup_ctx_t *ctx) +{ + (void)ctx; + return "false"; +} + +static const char *var_get_null(const tls_var_lookup_ctx_t *ctx) +{ + (void)ctx; + return "NULL"; +} + +static const char *var_get_client_s_dn_cn(const tls_var_lookup_ctx_t *ctx) +{ + /* There is no support in the crustls/rustls/webpki APIs to + * parse X.509 certificates and extract information about + * subject, issuer, etc. */ + if (!ctx->cc->peer_certs || !ctx->cc->peer_certs->nelts) return NULL; + return "Not Implemented"; +} + +static const char *var_get_client_verify(const tls_var_lookup_ctx_t *ctx) +{ + return ctx->cc->peer_certs? "SUCCESS" : "NONE"; +} + +static const char *var_get_session_resumed(const tls_var_lookup_ctx_t *ctx) +{ + return ctx->cc->session_id_cache_hit? "Resumed" : "Initial"; +} + +static const char *var_get_client_cert(const tls_var_lookup_ctx_t *ctx) +{ + const rustls_certificate *cert; + const char *pem; + apr_status_t rv; + int cert_idx = 0; + + if (ctx->arg_s) { + if (strcmp(ctx->arg_s, "chain")) return NULL; + /* ctx->arg_i'th chain cert, which is in out list as */ + cert_idx = ctx->arg_i + 1; + } + if (!ctx->cc->peer_certs || cert_idx >= ctx->cc->peer_certs->nelts) return NULL; + cert = APR_ARRAY_IDX(ctx->cc->peer_certs, cert_idx, const rustls_certificate*); + if (APR_SUCCESS != (rv = tls_cert_to_pem(&pem, ctx->p, cert))) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, ctx->s, APLOGNO(10315) + "Failed to create client certificate PEM"); + return NULL; + } + return pem; +} + +static const char *var_get_server_cert(const tls_var_lookup_ctx_t *ctx) +{ + const rustls_certificate *cert; + const char *pem; + apr_status_t rv; + + if (!ctx->cc->key) return NULL; + cert = rustls_certified_key_get_certificate(ctx->cc->key, 0); + if (!cert) return NULL; + if (APR_SUCCESS != (rv = tls_cert_to_pem(&pem, ctx->p, cert))) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, ctx->s, APLOGNO(10316) + "Failed to create server certificate PEM"); + return NULL; + } + return pem; +} + +typedef struct { + const char *name; + var_lookup* fn; + const char *arg_s; + int arg_i; +} var_def_t; + +static const var_def_t VAR_DEFS[] = { + { "SSL_PROTOCOL", var_get_ssl_protocol, NULL, 0 }, + { "SSL_CIPHER", var_get_ssl_cipher, NULL, 0 }, + { "SSL_TLS_SNI", var_get_sni_hostname, NULL, 0 }, + { "SSL_CLIENT_S_DN_CN", var_get_client_s_dn_cn, NULL, 0 }, + { "SSL_VERSION_INTERFACE", var_get_version_interface, NULL, 0 }, + { "SSL_VERSION_LIBRARY", var_get_version_library, NULL, 0 }, + { "SSL_SECURE_RENEG", var_get_false, NULL, 0 }, + { "SSL_COMPRESS_METHOD", var_get_null, NULL, 0 }, + { "SSL_CIPHER_EXPORT", var_get_false, NULL, 0 }, + { "SSL_CLIENT_VERIFY", var_get_client_verify, NULL, 0 }, + { "SSL_SESSION_RESUMED", var_get_session_resumed, NULL, 0 }, + { "SSL_CLIENT_CERT", var_get_client_cert, NULL, 0 }, + { "SSL_CLIENT_CHAIN_0", var_get_client_cert, "chain", 0 }, + { "SSL_CLIENT_CHAIN_1", var_get_client_cert, "chain", 1 }, + { "SSL_CLIENT_CHAIN_2", var_get_client_cert, "chain", 2 }, + { "SSL_CLIENT_CHAIN_3", var_get_client_cert, "chain", 3 }, + { "SSL_CLIENT_CHAIN_4", var_get_client_cert, "chain", 4 }, + { "SSL_CLIENT_CHAIN_5", var_get_client_cert, "chain", 5 }, + { "SSL_CLIENT_CHAIN_6", var_get_client_cert, "chain", 6 }, + { "SSL_CLIENT_CHAIN_7", var_get_client_cert, "chain", 7 }, + { "SSL_CLIENT_CHAIN_8", var_get_client_cert, "chain", 8 }, + { "SSL_CLIENT_CHAIN_9", var_get_client_cert, "chain", 9 }, + { "SSL_SERVER_CERT", var_get_server_cert, NULL, 0 }, +}; + +static const char *const TlsAlwaysVars[] = { + "SSL_TLS_SNI", + "SSL_PROTOCOL", + "SSL_CIPHER", + "SSL_CLIENT_S_DN_CN", +}; + +/* what mod_ssl defines, plus server cert and client cert DN and SAN entries */ +static const char *const StdEnvVars[] = { + "SSL_VERSION_INTERFACE", /* implemented: module version string */ + "SSL_VERSION_LIBRARY", /* implemented: crustls/rustls version string */ + "SSL_SECURE_RENEG", /* implemented: always "false" */ + "SSL_COMPRESS_METHOD", /* implemented: always "NULL" */ + "SSL_CIPHER_EXPORT", /* implemented: always "false" */ + "SSL_CIPHER_USEKEYSIZE", + "SSL_CIPHER_ALGKEYSIZE", + "SSL_CLIENT_VERIFY", /* implemented: always "SUCCESS" or "NONE" */ + "SSL_CLIENT_M_VERSION", + "SSL_CLIENT_M_SERIAL", + "SSL_CLIENT_V_START", + "SSL_CLIENT_V_END", + "SSL_CLIENT_V_REMAIN", + "SSL_CLIENT_S_DN", + "SSL_CLIENT_I_DN", + "SSL_CLIENT_A_KEY", + "SSL_CLIENT_A_SIG", + "SSL_CLIENT_CERT_RFC4523_CEA", + "SSL_SERVER_M_VERSION", + "SSL_SERVER_M_SERIAL", + "SSL_SERVER_V_START", + "SSL_SERVER_V_END", + "SSL_SERVER_S_DN", + "SSL_SERVER_I_DN", + "SSL_SERVER_A_KEY", + "SSL_SERVER_A_SIG", + "SSL_SESSION_ID", /* not implemented: highly sensitive data we do not expose */ + "SSL_SESSION_RESUMED", /* implemented: if our cache was hit successfully */ +}; + +/* Cert related variables, export when TLSOption ExportCertData is set */ +static const char *const ExportCertVars[] = { + "SSL_CLIENT_CERT", /* implemented: */ + "SSL_CLIENT_CHAIN_0", /* implemented: */ + "SSL_CLIENT_CHAIN_1", /* implemented: */ + "SSL_CLIENT_CHAIN_2", /* implemented: */ + "SSL_CLIENT_CHAIN_3", /* implemented: */ + "SSL_CLIENT_CHAIN_4", /* implemented: */ + "SSL_CLIENT_CHAIN_5", /* implemented: */ + "SSL_CLIENT_CHAIN_6", /* implemented: */ + "SSL_CLIENT_CHAIN_7", /* implemented: */ + "SSL_CLIENT_CHAIN_8", /* implemented: */ + "SSL_CLIENT_CHAIN_9", /* implemented: */ + "SSL_SERVER_CERT", /* implemented: */ +}; + +void tls_var_init_lookup_hash(apr_pool_t *pool, apr_hash_t *map) +{ + const var_def_t *def; + apr_size_t i; + + (void)pool; + for (i = 0; i < TLS_DIM(VAR_DEFS); ++i) { + def = &VAR_DEFS[i]; + apr_hash_set(map, def->name, APR_HASH_KEY_STRING, def); + } +} + +static const char *invoke(var_def_t* def, tls_var_lookup_ctx_t *ctx) +{ + if (TLS_CONN_ST_IS_ENABLED(ctx->cc)) { + const char *val = ctx->cc->subprocess_env? + apr_table_get(ctx->cc->subprocess_env, def->name) : NULL; + if (val && *val) return val; + ctx->arg_s = def->arg_s; + ctx->arg_i = def->arg_i; + return def->fn(ctx); + } + return NULL; +} + +static void set_var( + tls_var_lookup_ctx_t *ctx, apr_hash_t *lookups, apr_table_t *table) +{ + var_def_t* def = apr_hash_get(lookups, ctx->name, APR_HASH_KEY_STRING); + if (def) { + const char *val = invoke(def, ctx); + if (val && *val) { + apr_table_setn(table, ctx->name, val); + } + } +} + +const char *tls_var_lookup( + apr_pool_t *p, server_rec *s, conn_rec *c, request_rec *r, const char *name) +{ + const char *val = NULL; + tls_conf_server_t *sc; + var_def_t* def; + + ap_assert(p); + ap_assert(name); + s = s? s : (r? r->server : (c? c->base_server : NULL)); + c = c? c : (r? r->connection : NULL); + + sc = tls_conf_server_get(s? s : ap_server_conf); + def = apr_hash_get(sc->global->var_lookups, name, APR_HASH_KEY_STRING); + if (def) { + tls_var_lookup_ctx_t ctx; + ctx.p = p; + ctx.s = s; + ctx.c = c; + ctx.r = r; + ctx.cc = c? tls_conf_conn_get(c->master? c->master : c) : NULL; + ctx.cc = c? tls_conf_conn_get(c->master? c->master : c) : NULL; + ctx.name = name; + val = invoke(def, &ctx); + ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c, "tls lookup of var '%s' -> '%s'", name, val); + } + return val; +} + +static void add_vars(apr_table_t *env, conn_rec *c, server_rec *s, request_rec *r) +{ + tls_conf_server_t *sc; + tls_conf_dir_t *dc, *sdc; + tls_var_lookup_ctx_t ctx; + apr_size_t i; + int overlap; + + sc = tls_conf_server_get(s); + dc = r? tls_conf_dir_get(r) : tls_conf_dir_server_get(s); + sdc = r? tls_conf_dir_server_get(s): dc; + ctx.p = r? r->pool : c->pool; + ctx.s = s; + ctx.c = c; + ctx.r = r; + ctx.cc = tls_conf_conn_get(c->master? c->master : c); + /* Can we re-use the precomputed connection values? */ + overlap = (r && ctx.cc->subprocess_env && r->server == ctx.cc->server); + if (overlap) { + apr_table_overlap(env, ctx.cc->subprocess_env, APR_OVERLAP_TABLES_SET); + } + else { + apr_table_setn(env, "HTTPS", "on"); + for (i = 0; i < TLS_DIM(TlsAlwaysVars); ++i) { + ctx.name = TlsAlwaysVars[i]; + set_var(&ctx, sc->global->var_lookups, env); + } + } + if (dc->std_env_vars == TLS_FLAG_TRUE) { + for (i = 0; i < TLS_DIM(StdEnvVars); ++i) { + ctx.name = StdEnvVars[i]; + set_var(&ctx, sc->global->var_lookups, env); + } + } + else if (overlap && sdc->std_env_vars == TLS_FLAG_TRUE) { + /* Remove variables added on connection init that are disabled here */ + for (i = 0; i < TLS_DIM(StdEnvVars); ++i) { + apr_table_unset(env, StdEnvVars[i]); + } + } + if (dc->export_cert_vars == TLS_FLAG_TRUE) { + for (i = 0; i < TLS_DIM(ExportCertVars); ++i) { + ctx.name = ExportCertVars[i]; + set_var(&ctx, sc->global->var_lookups, env); + } + } + else if (overlap && sdc->std_env_vars == TLS_FLAG_TRUE) { + /* Remove variables added on connection init that are disabled here */ + for (i = 0; i < TLS_DIM(ExportCertVars); ++i) { + apr_table_unset(env, ExportCertVars[i]); + } + } + } + +apr_status_t tls_var_handshake_done(conn_rec *c) +{ + tls_conf_conn_t *cc; + tls_conf_server_t *sc; + apr_status_t rv = APR_SUCCESS; + + cc = tls_conf_conn_get(c); + if (!TLS_CONN_ST_IS_ENABLED(cc)) goto cleanup; + + sc = tls_conf_server_get(cc->server); + if (cc->peer_certs && sc->var_user_name) { + cc->user_name = tls_var_lookup(c->pool, cc->server, c, NULL, sc->var_user_name); + if (!cc->user_name) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cc->server, APLOGNO(10317) + "Failed to set r->user to '%s'", sc->var_user_name); + } + } + cc->subprocess_env = apr_table_make(c->pool, 5); + add_vars(cc->subprocess_env, c, cc->server, NULL); + +cleanup: + return rv; +} + +int tls_var_request_fixup(request_rec *r) +{ + conn_rec *c = r->connection; + tls_conf_conn_t *cc; + + cc = tls_conf_conn_get(c->master? c->master : c); + if (!TLS_CONN_ST_IS_ENABLED(cc)) goto cleanup; + if (cc->user_name) { + /* why is r->user a char* and not const? */ + r->user = apr_pstrdup(r->pool, cc->user_name); + } + add_vars(r->subprocess_env, c, r->server, r); + +cleanup: + return DECLINED; +} diff --git a/modules/tls/tls_var.h b/modules/tls/tls_var.h new file mode 100644 index 0000000..2e8c0bb --- /dev/null +++ b/modules/tls/tls_var.h @@ -0,0 +1,39 @@ +/* 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. + */ +#ifndef tls_var_h +#define tls_var_h + +void tls_var_init_lookup_hash(apr_pool_t *pool, apr_hash_t *map); + +/** + * Callback for installation in Apache's 'ssl_var_lookup' hook to provide + * SSL related variable lookups to other modules. + */ +const char *tls_var_lookup( + apr_pool_t *p, server_rec *s, conn_rec *c, request_rec *r, const char *name); + +/** + * A connection has been handshaked. Prepare commond TLS variables on this connection. + */ +apr_status_t tls_var_handshake_done(conn_rec *c); + +/** + * A request is ready for processing, add TLS variables r->subprocess_env if applicable. + * This is a hook function returning OK/DECLINED. + */ +int tls_var_request_fixup(request_rec *r); + +#endif /* tls_var_h */
\ No newline at end of file diff --git a/modules/tls/tls_version.h b/modules/tls/tls_version.h new file mode 100644 index 0000000..811d6f1 --- /dev/null +++ b/modules/tls/tls_version.h @@ -0,0 +1,39 @@ +/* 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. + */ +#ifndef mod_tls_version_h +#define mod_tls_version_h + +#undef PACKAGE_VERSION +#undef PACKAGE_TARNAME +#undef PACKAGE_STRING +#undef PACKAGE_NAME +#undef PACKAGE_BUGREPORT + +/** + * @macro + * Version number of the md module as c string + */ +#define MOD_TLS_VERSION "0.8.3" + +/** + * @macro + * Numerical representation of the version number of the md module + * release. This is a 24 bit number with 8 bits for major number, 8 bits + * for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203. + */ +#define MOD_TLS_VERSION_NUM 0x000802 + +#endif /* mod_md_md_version_h */ |