From: Joe Orton
Date: Fri, 12 Sep 2025 08:05:11 +0000 (+0000)
Subject: mod_ssl: Add support for Encrypted Client Hello (ECH) based off
X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=0c9cd095ce9081fd225f0da7787419e80de7c701;p=thirdparty%2Fapache%2Fhttpd.git
mod_ssl: Add support for Encrypted Client Hello (ECH) based off
proposed OpenSSL 4.0 API. Notes from PR #551:
This build only supports ECH "shared-mode" where mod_ssl does the ECH
decryption and also hosts both the ECH `public-name` and `backend` web
sites.
## Build
> [!NOTE]
> ECH is not yet a part of an OpenSSL release, our current goal is that ECH be
> part of an OpenSSL 4.0 release in spring 2026.
There is client and server ECH code in the OpenSSL ECH feature branch at
[https://github.com/openssl/openssl/tree/feature/ech](https://github.com/openssl/openssl/tree/feature/ech).
At present, ECH-enabling apache2 therefore requires building from source, using
the OpenSSL ECH feature branch.
## Code changes
- All code changes are within `modules/ssl` and are protected via `#ifdef
HAVE_OPENSSL_ECH`. That's defined in `ssl_private.h` if the included
`ssl.h` defines `SSL_OP_ECH_GREASE`.
- There're a bunch of changes to add the new `SSLECHKeyDir` directive that
are mosly obvious.
- We load the keys from `SSLECHKeyDir` using the `load_echkeys()` function in
`ssl_engine_init.c`. That also ECH-enables the `SSL_CTX` when keys are
loaded, which triggers ECH decryption as needed.
> [!NOTE]
> `load_echkeys()` will include the public component all loaded keys in the ECH
> `retry-configs` in the fallback scenario. If desired, we could add a naming
> convention or additional configuration setting to distinguish which to
> include in `retry-configs` or not. For now, we assume that'd better be done
> in a subsequent PR, if experience shows the feature is really useful/needed.
> (We can envisage some odd deployments where that might be the case, but not
> clear those'd really happen - it'd seem to need loads of key pairs or else
> some that are never published in the DNS that we don't want to expose to
> random clients - neither seems compelling.)
- We add a callback to `SSL_CTX_ech_set_callback` also in `ssl_engine_init.c`.
- We add calls to set the `SSL_ECH_STATUS` etc. variables to the environment
(for PHP etc) in `ssl_engine_kernel.c` and also do the logging of ECH outcomes
(to the error log).
Submitted by: sftcd , rpluem
Github: closes #551
git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1928357 13f79535-47bb-0310-9956-ffa450edef68
---
diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml
index 449532d5df..dd89c3aebf 100644
--- a/.github/workflows/linux.yml
+++ b/.github/workflows/linux.yml
@@ -316,6 +316,18 @@ jobs:
APU_CONFIG="--without-crypto"
pkgs: subversion
# -------------------------------------------------------------------------
+ - name: OpenSSL ECH branch
+ config: --enable-mods-shared=most --enable-maintainer-mode --disable-md --disable-http2 --disable-ldap --disable-crypto
+ notest-cflags: -Werror -O2
+ env: |
+ TEST_OPENSSL3=ech
+ TEST_OPENSSL3_BRANCH=feature/ech
+ OPENSSL_CONFIG=no-engine
+ APR_VERSION=1.7.6
+ APU_VERSION=1.6.3
+ APU_CONFIG="--without-crypto"
+ pkgs: subversion
+ # -------------------------------------------------------------------------
runs-on: ${{ matrix.os == '' && 'ubuntu-latest' || matrix.os }}
timeout-minutes: 30
env:
diff --git a/changes-entries/ech.txt b/changes-entries/ech.txt
new file mode 100644
index 0000000000..3d3ce480a5
--- /dev/null
+++ b/changes-entries/ech.txt
@@ -0,0 +1,2 @@
+ *) mod_ssl: Add support for Encrypted Client Hello (ECH)
+ Github #551. [Stephen Farrell ]
diff --git a/docs/log-message-tags/next-number b/docs/log-message-tags/next-number
index 9a5b59edd0..772a41124d 100644
--- a/docs/log-message-tags/next-number
+++ b/docs/log-message-tags/next-number
@@ -1 +1 @@
-10519
+10542
diff --git a/docs/manual/mod/mod_ssl.xml b/docs/manual/mod/mod_ssl.xml
index fe92af5bb7..854a84dc73 100644
--- a/docs/manual/mod/mod_ssl.xml
+++ b/docs/manual/mod/mod_ssl.xml
@@ -118,6 +118,9 @@ compatibility variables.
SSL_CLIENTHELLO_SIG_ALGOS | string | Value of Signature Algorithms extension (13) from ClientHello as four hex encoded characters per item |
SSL_CLIENTHELLO_ALPN | string | Value of ALPN extension (16) from ClientHello as hex encoded string including leading string lengths |
SSL_CLIENTHELLO_VERSIONS | string | Value of Supported Versions extension (43) from ClientHello as four hex encoded characters per item |
+SSL_ECH_STATUS | string | success means that others also mean what they say |
+SSL_ECH_INNER_SNI | string | SNI value that was encrypted in ECH (or `NONE`) |
+SSL_ECH_OUTER_SNI | string | SNI value that was seen in plaintext SNI (or `NONE`) |
x509 specifies a component of an X.509 DN; one of
@@ -3016,4 +3019,123 @@ httpd -t -D DUMP_SSL_POLICIES
+
+SSLECHKeyDir
+Load the set of Encrypted Client Hello (ECH) PEM files in the named directory
+SSLECHKeyDir dirname
+server config
+Available in Apache HTTP Server 2.5.1 and later
+
+
+
+
+ECH is specified in
+ draft-ietf-tls-esni
+httpd supports ECH "shared-mode" where the httpd instance does the
+ECH decryption and also hosts both the ECH `public-name` and `backend` web
+sites.
+
+
+
+The SSLECHKeyDir
directive
+names the directory where ECH PEM files (named *.ech
) are stored.
+Once an ECH PEM file is successfully loaded, httpd will perform ECH decryption
+and, if that succeeds, will process the relevant TLS session using the
+SNI from the inner ClientHello.
+
+
+Example ECH Config
+
+...
+SSLEngine On
+SSLProtocol TLSv1.3
+SSLECHKeyDir /etc/apache2/echkeydir
+...
+# virtual hosts
+<VirtualHost *:443>
+ SSLEngine On
+ SSLProtocol TLSv1.3
+ ServerName example.com
+ DocumentRoot "/var/www/dir-example.com"
+</VirtualHost>
+<VirtualHost *:443>
+ SSLEngine On
+ SSLProtocol TLSv1.3
+ ServerName foo.example.com
+ DocumentRoot "/var/www/dir-foo.example.com"
+</VirtualHost>
+...
+
+
+
+ECH Key Generation and Publication
+
+In the above, we describe a configuration that uses example.com
as the
+ECH public-name
and where foo.example.com
is a web-site for which we want
+ECH to be used, with both hosted on the same httpd instance.
+
+
+Using ECH requries that httpd load an ECH key pair with a private value for ECH
+decryption. Browsers will require that the public component of that key pair be
+published in the DNS. With OpenSSL we generate and store that key pair in an ECH PEM
+formatted file as shown below.
+
+
+To generate ECH PEM files, use the ECH-enabled openssl command line
+to generate an ECH key pair and store the result in an ECH PEM file.
+You must also supply the public-name
required by the ECH protocol.
+
+
+Key generation operations should be carried out under whatever local account is
+used for httpd configuration.
+
+Example: ECH Key Generation
+
+~# OSSL=/home/user/code/openssl/apps/openssl
+~# mkdir -p /etc/apache2/echkeydir
+~# chmod 700 /etc/apache2/echkeydir
+~# cd /etc/apache2/echkeydir
+~# $OSSL ech -public-name example.com -o example.com.pem.ech
+~# cat example.com.pem.ech
+-----BEGIN PRIVATE KEY-----
+MC4CAQAwBQYDK2VuBCIEIJi22Im2rJ/lJqzNFZdGfsVfmknXAc8xz3fYPhD0Na5I
+-----END PRIVATE KEY-----
+-----BEGIN ECHCONFIG-----
+AD7+DQA6QwAgACA8mxkEsSTp2xXC/RUFCC6CZMMgdM4x1iTWKu3EONjbMAAEAAEA
+AQALZXhhbXBsZS5vcmcAAA==
+-----END ECHCONFIG-----
+
+
+
+The ECHConfig value then needs to be published in an HTTPS resource record in
+the DNS, so as to be accessible as shown below:
+
+Accessing an ECH config from DNS
+
+$ dig +short HTTPS foo.example.com
+1 . ech=AD7+DQA6QwAgACA8mxkEsSTp2xXC/RUFCC6CZMMgdM4x1iTWKu3EONjbMAAEAAEAAQALZXhhbXBsZS5vcmcAAA==
+
+
+
+Various other fields may be included in an HTTPS resource record. For many
+httpd deployments, existing methods for publishing DNS records may be used to
+achieve the above. In some cases, one might use
+
+A well-known URI for publishing service parameters
+designed to assist web servers in handling e.g. frequent ECH key rotation.
+
+
+
+Reloading ECH Keys
+
+
+Giving httpd a command line argument of -k graceful
causes a graceful reload
+of the configuration, without dropping existing connections.
+
+
+
+
+
+
+
diff --git a/modules/ssl/mod_ssl.c b/modules/ssl/mod_ssl.c
index e48efb1a47..7ccc06006c 100644
--- a/modules/ssl/mod_ssl.c
+++ b/modules/ssl/mod_ssl.c
@@ -121,6 +121,11 @@ static const command_rec ssl_config_cmds[] = {
SSL_CMD_SRV(SessionTicketKeyFile, TAKE1,
"TLS session ticket encryption/decryption key file (RFC 5077) "
"('/path/to/file' - file with 48 bytes of random data)")
+#endif
+#ifdef HAVE_OPENSSL_ECH
+ SSL_CMD_SRV(ECHKeyDir, TAKE1,
+ "TLS ECH Key Directory"
+ "('/path/to/dir' - directory with ECH key pairs)")
#endif
SSL_CMD_ALL(CACertificatePath, TAKE1,
"SSL CA Certificate path "
diff --git a/modules/ssl/ssl_engine_config.c b/modules/ssl/ssl_engine_config.c
index 50c61bca32..e9c65e9995 100644
--- a/modules/ssl/ssl_engine_config.c
+++ b/modules/ssl/ssl_engine_config.c
@@ -222,6 +222,9 @@ static SSLSrvConfigRec *ssl_config_server_new(apr_pool_t *p)
#endif
sc->clienthello_vars = UNSET;
sc->session_tickets = UNSET;
+#ifdef HAVE_OPENSSL_ECH
+ sc->echkeydir = NULL;
+#endif
modssl_ctx_init_server(sc, p);
@@ -356,6 +359,9 @@ void *ssl_config_server_merge(apr_pool_t *p, void *basev, void *addv)
cfgMergeBool(compression);
#endif
cfgMergeBool(session_tickets);
+#ifdef HAVE_OPENSSL_ECH
+ cfgMergeString(echkeydir);
+#endif
modssl_ctx_cfg_merge_server(p, base->server, add->server, mrg->server);
@@ -840,6 +846,25 @@ const char *ssl_cmd_SSLEngine(cmd_parms *cmd, void *dcfg, const char *arg)
return "Argument must be On or Off";
}
+#ifdef HAVE_OPENSSL_ECH
+const char *ssl_cmd_SSLECHKeyDir(cmd_parms *cmd, void *dcfg, const char *arg)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+
+ sc->echkeydir=arg;
+
+#if !defined(SSL_HAVE_PROTOCOL_TLSV1_3)
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10533)
+ "ECHKeyDir configured but TLSv1.3 not supported - exiting.");
+ return "ECHKeyDir configured but TLSv1.3 not supported";
+#endif
+ ap_log_error(APLOG_MARK, APLOG_TRACE4, 0, cmd->server,
+ "%s: ECHKeyDir set to %s",
+ cmd->cmd->name, sc->echkeydir);
+ return NULL;
+}
+#endif
+
const char *ssl_cmd_SSLFIPS(cmd_parms *cmd, void *dcfg, int flag)
{
#ifdef HAVE_FIPS
@@ -2654,6 +2679,9 @@ static void ssl_srv_dump(SSLSrvConfigRec *sc, apr_pool_t *p,
DMP_LONG( "SSLSessionCacheTimeout", sc->session_cache_timeout);
DMP_ON_OFF("SSLStrictSNIVHostCheck", sc->strict_sni_vhost_check);
DMP_ON_OFF("SSLSessionTickets", sc->session_tickets);
+#ifdef HAVE_OPENSSL_ECH
+ DMP_STRING("SSLECHKeyDir", sc->echkeydir);
+#endif
}
static void ssl_policy_dump(SSLSrvConfigRec *policy, apr_pool_t *p,
diff --git a/modules/ssl/ssl_engine_init.c b/modules/ssl/ssl_engine_init.c
index bdbe594215..c47684a3da 100644
--- a/modules/ssl/ssl_engine_init.c
+++ b/modules/ssl/ssl_engine_init.c
@@ -189,6 +189,114 @@ static void ssl_add_version_components(apr_pool_t *ptemp, apr_pool_t *pconf,
modver, AP_SERVER_BASEVERSION, incver);
}
+#ifdef HAVE_OPENSSL_ECH
+/*
+ * Load any ECH PEM files we find in the ECHKeyDir directory
+ * Those are files matching "*.ech"
+ * The caller checks that echdir is non-NULL.
+ */
+static int load_echkeys(SSL_CTX *ctx, const char *echdir, server_rec *s,
+ apr_pool_t *ptemp)
+{
+ size_t elen = 0;
+ int keystried = 0, keysworked = 0, keysloaded=0;
+ OSSL_ECHSTORE *es = NULL;
+ apr_dir_t *dir = NULL;
+ apr_finfo_t direntry;
+ apr_int32_t finfo_flags = APR_FINFO_TYPE|APR_FINFO_NAME;
+ apr_status_t dorv;
+
+ elen = strlen(echdir);
+ if ((elen + 7) >= PATH_MAX) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10521)
+ "load_echkeys: directory name too long: %s - exiting", echdir);
+ return -1;
+ }
+ dorv = apr_dir_open(&dir, echdir, ptemp);
+ if (dorv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10522)
+ "load_echkeys: can't open directory %s - exiting (error %d)",
+ echdir, dorv);
+ return -1;
+ }
+ es = OSSL_ECHSTORE_new(NULL, NULL);
+ if (es == NULL) {
+ apr_dir_close(dir);
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10523)
+ "load_echkeys: can't alloc store");
+ return -1;
+ }
+
+ while ((apr_dir_read(&direntry, finfo_flags, dir)) == APR_SUCCESS) {
+ const char *fname;
+ size_t pnlen = 0;
+ apr_finfo_t theinfo;
+
+ if (direntry.filetype == APR_DIR) {
+ continue; /* don't try to load directories */
+ }
+ fname = apr_pstrcat(ptemp, echdir, "/", direntry.name, NULL);
+ if (!fname) {
+ continue;
+ }
+ pnlen = strlen(fname);
+ if (pnlen < 5 || pnlen > (PATH_MAX-1)) {
+ continue;
+ }
+ if (!(fname[pnlen - 4] == '.'
+ && fname[pnlen - 3] == 'e'
+ && fname[pnlen - 2] == 'c'
+ && fname[pnlen - 1] == 'h')) {
+ continue;
+ }
+ if (apr_stat(&theinfo, fname, APR_FINFO_MIN, ptemp) == APR_SUCCESS) {
+ BIO *in = BIO_new_file(fname, "r");
+ const int is_retry_config = OSSL_ECH_FOR_RETRY;
+
+ keystried++;
+ if (in && OSSL_ECHSTORE_read_pem(es, in, is_retry_config) == 1) {
+ ap_log_error(APLOG_MARK, APLOG_TRACE4, 0, s,
+ "load_echkeys: worked for %s",fname);
+ keysworked++;
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(10525)
+ "load_echkeys: failed for %s (could be non-fatal)",
+ fname);
+ }
+ BIO_free_all(in);
+ }
+
+ }
+ apr_dir_close(dir);
+
+ if (!OSSL_ECHSTORE_num_keys(es, &keysloaded)) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10526)
+ "OSSL_ECHSTORE_num_keys failed - exiting");
+ OSSL_ECHSTORE_free(es);
+ return -1;
+ }
+ if (SSL_CTX_set1_echstore(ctx, es) != 1) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10527)
+ "load_echkeys: SSL_CTX_set1_echstore failed");
+ OSSL_ECHSTORE_free(es);
+ return -1;
+ }
+ OSSL_ECHSTORE_free(es);
+ if (keysworked == 0) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10528)
+ "load_echkeys: didn't load new keys (%d tried/failed) "
+ "but we have already some (%d) - continuing",
+ keystried, keysloaded);
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(10529)
+ "ECH: %d keys loaded", keysloaded);
+ }
+ return 0;
+}
+#endif
+
/* _________________________________________________________________
**
** Let other answer special connection attempts.
@@ -545,6 +653,9 @@ static apr_status_t ssl_init_ctx_tls_extensions(server_rec *s,
modssl_ctx_t *mctx)
{
apr_status_t rv;
+#ifdef HAVE_OPENSSL_ECH
+ SSLSrvConfigRec *sc = mySrvConfig(s);
+#endif
/*
* Configure TLS extensions support
@@ -577,6 +688,13 @@ static apr_status_t ssl_init_ctx_tls_extensions(server_rec *s,
SSL_CTX_set_client_hello_cb(mctx->ssl_ctx, ssl_callback_ClientHello, NULL);
#endif
+#ifdef HAVE_OPENSSL_ECH
+ if (sc != NULL && sc->echkeydir != NULL) {
+ /* callback logs ECH outcome */
+ SSL_CTX_ech_set_callback(mctx->ssl_ctx, ssl_callback_ECH);
+ }
+#endif
+
#ifdef HAVE_OCSP_STAPLING
/*
* OCSP Stapling support, status_request extension
@@ -903,7 +1021,30 @@ static apr_status_t ssl_init_ctx_protocol(server_rec *s,
SSL_CTX_set_options(ctx, SSL_OP_IGNORE_UNEXPECTED_EOF);
}
#endif
-
+
+#ifdef HAVE_OPENSSL_ECH
+ /* ECH only really makes sense for TLSv1.3 */
+ prot = SSL_CTX_get_max_proto_version(ctx);
+ if (sc->echkeydir) {
+ if (prot == TLS1_3_VERSION) {
+ /* try load the keys */
+ if (load_echkeys(ctx, sc->echkeydir, s, ptemp) != 0) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10531)
+ "ECHKeyDir failed to load keys - exiting.");
+ SSL_CTX_free(ctx);
+ mctx->ssl_ctx = NULL;
+ return ssl_die(s);
+ }
+ } else {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10532)
+ "ECHKeyDir configured but TLSv1.3 turned off - exiting.");
+ SSL_CTX_free(ctx);
+ mctx->ssl_ctx = NULL;
+ return ssl_die(s);
+ }
+ }
+#endif
+
return APR_SUCCESS;
}
diff --git a/modules/ssl/ssl_engine_kernel.c b/modules/ssl/ssl_engine_kernel.c
index 45d986364f..aa9c76ec04 100644
--- a/modules/ssl/ssl_engine_kernel.c
+++ b/modules/ssl/ssl_engine_kernel.c
@@ -33,6 +33,10 @@
#include "util_md5.h"
#include "scoreboard.h"
+#ifdef HAVE_OPENSSL_ECH
+#include
+#endif
+
static void ssl_configure_env(request_rec *r, SSLConnRec *sslconn);
#ifdef HAVE_TLSEXT
static int ssl_find_vhost(void *servername, conn_rec *c, server_rec *s);
@@ -1516,6 +1520,11 @@ int ssl_hook_Fixup(request_rec *r)
apr_table_set(env, "SSL_TLS_SNI", servername);
}
#endif
+#ifdef HAVE_OPENSSL_ECH
+ extract_to_env(r, env, "SSL_ECH_INNER_SNI");
+ extract_to_env(r, env, "SSL_ECH_OUTER_SNI");
+ extract_to_env(r, env, "SSL_ECH_STATUS");
+#endif
/* standard SSL environment variables */
if (dc->nOptions & SSL_OPT_STDENVVARS) {
@@ -2377,6 +2386,46 @@ static apr_status_t init_vhost(conn_rec *c, SSL *ssl, const char *servername)
return APR_NOTFOUND;
}
+#ifdef HAVE_OPENSSL_ECH
+unsigned int ssl_callback_ECH(SSL *ssl, const char *str)
+{
+ char *inner_sni = NULL, *outer_sni = NULL;
+ int echrv;
+ conn_rec *c = NULL;
+ const char *ech_servername;
+
+ c = (conn_rec *)SSL_get_app_data(ssl);
+ ech_servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
+ if (ech_servername == NULL) {
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+ echrv = SSL_ech_get1_status(ssl, &inner_sni, &outer_sni);
+ switch (echrv) {
+ case SSL_ECH_STATUS_NOT_TRIED:
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, "ECH not attempted");
+ break;
+ case SSL_ECH_STATUS_FAILED:
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, "ECH tried but failed");
+ break;
+ case SSL_ECH_STATUS_BAD_NAME:
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, "ECH worked but bad name");
+ break;
+ case SSL_ECH_STATUS_SUCCESS:
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
+ "ECH success outer_sni: %s inner_sni: %s",
+ (outer_sni ? outer_sni : "NONE"),
+ (inner_sni ? inner_sni : "NONE"));
+ break;
+ default:
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
+ "Error getting ECH status");
+ }
+ OPENSSL_free(inner_sni);
+ OPENSSL_free(outer_sni);
+ return 1;
+}
+#endif
+
/*
* This callback function is executed when OpenSSL encounters an extended
* client hello with a server name indication extension ("SNI", cf. RFC 6066).
diff --git a/modules/ssl/ssl_engine_vars.c b/modules/ssl/ssl_engine_vars.c
index 45a5a5bab6..34989f1d24 100644
--- a/modules/ssl/ssl_engine_vars.c
+++ b/modules/ssl/ssl_engine_vars.c
@@ -430,6 +430,50 @@ const char *ssl_var_lookup(apr_pool_t *p, server_rec *s,
return result ? result : "";
}
+#ifdef HAVE_OPENSSL_ECH
+/* Extract ECH status variable from SSL object 'ssl' */
+static const char *ssl_var_lookup_ech_status(apr_pool_t *p, const char *var,
+ SSL *ssl)
+{
+ char *inner_sni = NULL, *outer_sni = NULL;
+ int echrv;
+ const char *result = NULL;
+
+ if (ssl == NULL)
+ return result;
+ echrv = SSL_ech_get1_status(ssl, &inner_sni, &outer_sni);
+ if (strcEQ(var, "STATUS")) {
+ switch (echrv) {
+ case SSL_ECH_STATUS_NOT_TRIED:
+ result = "not attempted";
+ break;
+ case SSL_ECH_STATUS_FAILED:
+ result = "tried but failed";
+ break;
+ case SSL_ECH_STATUS_BAD_NAME:
+ result = "ECH worked but bad name";
+ break;
+ case SSL_ECH_STATUS_SUCCESS:
+ result = "success";
+ break;
+ default:
+ result = "error getting ECH status";
+ }
+ }
+ else if (echrv == SSL_ECH_STATUS_SUCCESS) {
+ if (strcEQ(var, "INNER_SNI")) {
+ result = apr_pstrdup(p, inner_sni);
+ }
+ if (strcEQ(var, "OUTER_SNI")) {
+ result = apr_pstrdup(p, outer_sni);
+ }
+ }
+ OPENSSL_free(inner_sni);
+ OPENSSL_free(outer_sni);
+ return result;
+}
+#endif
+
static const char *ssl_var_lookup_ssl(apr_pool_t *p, const SSLConnRec *sslconn,
request_rec *r, const char *var)
{
@@ -544,6 +588,11 @@ static const char *ssl_var_lookup_ssl(apr_pool_t *p, const SSLConnRec *sslconn,
}
}
#endif
+#ifdef HAVE_OPENSSL_ECH
+ else if (ssl != NULL && strcEQn(var, "ECH_", 4)) {
+ result = ssl_var_lookup_ech_status(p, var+4, ssl);
+ }
+#endif
return result;
}
diff --git a/modules/ssl/ssl_private.h b/modules/ssl/ssl_private.h
index 071bd69b4c..3f64841737 100644
--- a/modules/ssl/ssl_private.h
+++ b/modules/ssl/ssl_private.h
@@ -127,6 +127,16 @@
#define MODSSL_HAVE_OPENSSL_STORE 0
#endif
+/*
+ * Check if we have an ECH-enabled OpenSSL. If we do then this symbol will
+ * be defined in ssl.h and we compile in ECH code.
+ */
+#if defined(SSL_OP_ECH_GREASE)
+#define HAVE_OPENSSL_ECH
+#else
+#undef HAVE_OPENSSL_ECH
+#endif
+
#if (OPENSSL_VERSION_NUMBER < 0x0090801f)
#error mod_ssl requires OpenSSL 0.9.8a or later
#endif
@@ -870,6 +880,9 @@ struct SSLSrvConfigRec {
#endif
BOOL session_tickets;
BOOL clienthello_vars;
+#ifdef HAVE_OPENSSL_ECH
+ const char *echkeydir;
+#endif
};
/**
@@ -917,6 +930,9 @@ const char *ssl_cmd_SSLPassPhraseDialog(cmd_parms *, void *, const char *);
const char *ssl_cmd_SSLCryptoDevice(cmd_parms *, void *, const char *);
const char *ssl_cmd_SSLRandomSeed(cmd_parms *, void *, const char *, const char *, const char *);
const char *ssl_cmd_SSLEngine(cmd_parms *, void *, const char *);
+#ifdef HAVE_OPENSSL_ECH
+const char *ssl_cmd_SSLECHKeyDir(cmd_parms *cmd, void *dcfg, const char *arg);
+#endif
const char *ssl_cmd_SSLCipherSuite(cmd_parms *, void *, const char *, const char *);
const char *ssl_cmd_SSLCertificateFile(cmd_parms *, void *, const char *);
const char *ssl_cmd_SSLCertificateKeyFile(cmd_parms *, void *, const char *);
@@ -1031,6 +1047,9 @@ int ssl_callback_ServerNameIndication(SSL *, int *, modssl_ctx_t *);
#endif
#if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER)
int ssl_callback_ClientHello(SSL *, int *, void *);
+#ifdef HAVE_OPENSSL_ECH
+unsigned int ssl_callback_ECH(SSL *, const char *);
+#endif
#endif
#ifdef HAVE_TLS_SESSION_TICKETS
int ssl_callback_SessionTicket(SSL *ssl,
diff --git a/test/travis_before_linux.sh b/test/travis_before_linux.sh
index 9b6267722d..b163e759f0 100755
--- a/test/travis_before_linux.sh
+++ b/test/travis_before_linux.sh
@@ -172,8 +172,12 @@ if test -v TEST_OPENSSL3; then
mkdir -p build/openssl
pushd build/openssl
- curl -L "https://github.com/openssl/openssl/releases/download/openssl-${TEST_OPENSSL3}/openssl-${TEST_OPENSSL3}.tar.gz" |
- tar -xzf -
+ if test -v TEST_OPENSSL3_BRANCH; then
+ git clone -b $TEST_OPENSSL3_BRANCH -q https://github.com/openssl/openssl openssl-${TEST_OPENSSL3}
+ else
+ curl -L "https://github.com/openssl/openssl/releases/download/openssl-${TEST_OPENSSL3}/openssl-${TEST_OPENSSL3}.tar.gz" |
+ tar -xzf -
+ fi
cd openssl-${TEST_OPENSSL3}
# Build with RPATH so ./bin/openssl doesn't require $LD_LIBRARY_PATH
./Configure --prefix=$HOME/root/openssl3 \