You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by ic...@apache.org on 2021/11/30 16:29:21 UTC

svn commit: r1895432 [3/5] - in /httpd/httpd/trunk: changes-entries/ modules/tls/

Added: httpd/httpd/trunk/modules/tls/tls_core.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/tls/tls_core.c?rev=1895432&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/tls/tls_core.c (added)
+++ httpd/httpd/trunk/modules/tls/tls_core.c Tue Nov 30 16:29:20 2021
@@ -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->sni_name.len > 0) {
+        cc->sni_hostname = apr_pstrndup(c->pool, hello->sni_name.data, hello->sni_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 configration 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 extraced 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;
+}

Added: httpd/httpd/trunk/modules/tls/tls_core.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/tls/tls_core.h?rev=1895432&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/tls/tls_core.h (added)
+++ httpd/httpd/trunk/modules/tls/tls_core.h Tue Nov 30 16:29:20 2021
@@ -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);
+
+/**
+ * Initialiaze 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 */
\ No newline at end of file