]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: ssl/ech: config and load keys
authorsftcd <stephen.farrell@cs.tcd.ie>
Fri, 26 Sep 2025 21:10:36 +0000 (22:10 +0100)
committerWilliam Lallemand <wlallemand@haproxy.com>
Thu, 30 Oct 2025 09:37:12 +0000 (10:37 +0100)
This patch introduces the USE_ECH option in the Makefile to enable
support for Encrypted Client Hello (ECH) with OpenSSL.

A new function, load_echkeys, is added to load ECH keys from a specified
directory. The SSL context initialization process in ssl_sock.c is
updated to load these keys if configured.

A new configuration directive, `ech`, is introduced to allow users to
specify the ECH key  directory in the listener configuration.

Makefile
include/haproxy/ech.h [new file with mode: 0644]
include/haproxy/listener-t.h
src/ech.c [new file with mode: 0644]
src/ssl_sock.c

index d26e579486c8f2483f30582e33652ad97bca14b4..fffc4f4f35d82c054056ff2bcb2634b1c1fb323f 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -35,6 +35,7 @@
 #   USE_OPENSSL             : enable use of OpenSSL. Recommended, but see below.
 #   USE_OPENSSL_AWSLC       : enable use of AWS-LC
 #   USE_OPENSSL_WOLFSSL     : enable use of wolfSSL with the OpenSSL API
+#   USE_ECH                 : enable use of ECH with the OpenSSL API
 #   USE_QUIC                : enable use of QUIC with the quictls API (quictls, libressl, boringssl)
 #   USE_QUIC_OPENSSL_COMPAT : enable use of QUIC with the standard openssl API (limited features)
 #   USE_ENGINE              : enable use of OpenSSL Engine.
@@ -341,6 +342,7 @@ use_opts = USE_EPOLL USE_KQUEUE USE_NETFILTER USE_POLL                        \
            USE_TPROXY USE_LINUX_TPROXY USE_LINUX_CAP                          \
            USE_LINUX_SPLICE USE_LIBCRYPT USE_CRYPT_H USE_ENGINE               \
            USE_GETADDRINFO USE_OPENSSL USE_OPENSSL_WOLFSSL USE_OPENSSL_AWSLC  \
+              USE_ECH                                                            \
            USE_SSL USE_LUA USE_ACCEPT4 USE_CLOSEFROM USE_ZLIB USE_SLZ         \
            USE_CPU_AFFINITY USE_TFO USE_NS USE_DL USE_RT USE_LIBATOMIC        \
            USE_MATH USE_DEVICEATLAS USE_51DEGREES                             \
@@ -1000,7 +1002,7 @@ OBJS += src/mux_h2.o src/mux_h1.o src/mux_fcgi.o src/log.o                \
         src/ebsttree.o src/freq_ctr.o src/systemd.o src/init.o         \
         src/http_acl.o src/dict.o src/dgram.o src/pipe.o               \
         src/hpack-huff.o src/hpack-enc.o src/ebtree.o src/hash.o       \
-        src/httpclient_cli.o src/version.o src/ncbmbuf.o
+        src/httpclient_cli.o src/version.o src/ncbmbuf.o src/ech.o
 
 ifneq ($(TRACE),)
   OBJS += src/calltrace.o
diff --git a/include/haproxy/ech.h b/include/haproxy/ech.h
new file mode 100644 (file)
index 0000000..dac74cf
--- /dev/null
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#ifndef _HAPROXY_ECH_H
+# define _HAPROXY_ECH_H
+#ifdef USE_ECH
+
+#include <openssl/ech.h>
+
+int load_echkeys(SSL_CTX *ctx, char *dirname, int *loaded);
+
+# endif /* USE_ECH */
+#endif /* _HAPROXY_ECH_H */
index 0fbed6c7f40119d12fc15d62855d590d31d9ee58..ce189736d8fdc2e23155f81aca89720b3cfb6779 100644 (file)
@@ -152,6 +152,9 @@ struct ssl_bind_conf {
        char *client_sigalgs;      /* Client Signature algorithms */
        struct tls_version_filter ssl_methods_cfg; /* original ssl methods found in configuration */
        struct tls_version_filter ssl_methods; /* actual ssl methods used at runtime */
+#ifdef USE_ECH
+       char *ech_filedir;         /* ECH config, file/directory name */
+#endif
 #endif
 };
 
diff --git a/src/ech.c b/src/ech.c
new file mode 100644 (file)
index 0000000..69b522c
--- /dev/null
+++ b/src/ech.c
@@ -0,0 +1,100 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifdef USE_ECH
+
+
+#include <dirent.h>
+#include <sys/stat.h>
+
+#include <haproxy/applet.h>
+#include <haproxy/cli.h>
+#include <haproxy/ech.h>
+#include <haproxy/fd.h>
+#include <haproxy/global.h>
+#include <haproxy/listener.h>
+#include <haproxy/log.h>
+#include <haproxy/obj_type.h>
+#include <haproxy/openssl-compat.h>
+#include <haproxy/proxy.h>
+#include <haproxy/ssl_sock-t.h>
+
+
+/*
+ * load any key files called <name>.ech we find in the named
+ * directory
+ */
+int load_echkeys(SSL_CTX *ctx, char *dirname, int *loaded)
+{
+       struct dirent **de_list = NULL;
+       struct stat thestat;
+       int rv = 0, i, nrv, somekeyworked = 0;
+       char *den = NULL, *last4 = NULL, privname[PATH_MAX];
+       size_t elen = 0, nlen = 0;
+       OSSL_ECHSTORE * const es = OSSL_ECHSTORE_new(NULL, NULL);
+
+       if (es == NULL)
+               goto end;
+       nrv = scandir(dirname, &de_list, 0, alphasort);
+       if (nrv < 0)
+               goto end;
+       for (i = 0; i != nrv; i++) {
+               struct dirent *de = de_list[i];
+
+               den = de->d_name;
+               nlen = strlen(den);
+               if (nlen > 4) {
+                       last4 = den + nlen - 4;
+                       if (strncmp(last4, ".ech", 4))
+                               goto ignore_entry;
+                       if ((elen + 1 + nlen + 1) >= PATH_MAX)
+                               goto ignore_entry;
+                       snprintf(privname, PATH_MAX,"%s/%s", dirname, den);
+                       if (stat(privname, &thestat) == 0) {
+                               BIO *in = BIO_new_file(privname, "r");
+                               const int is_retry_config = OSSL_ECH_FOR_RETRY;
+
+                               if (in != NULL && 1 == OSSL_ECHSTORE_read_pem(es, in, is_retry_config))
+                                       somekeyworked = 1;
+                               BIO_free_all(in);
+                       }
+               }
+ignore_entry:
+               free(de);
+       }
+
+       if (somekeyworked == 0)
+               goto end;
+       if (OSSL_ECHSTORE_num_keys(es, loaded) != 1)
+               goto end;
+       if (1 != SSL_CTX_set1_echstore(ctx, es))
+               goto end;
+       rv = 1;
+end:
+       free(de_list);
+       OSSL_ECHSTORE_free(es);
+       return rv;
+}
+
+
+static int bind_parse_ech(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err)
+{
+       if (!experimental_directives_allowed) {
+               memprintf(err, "'%s' directive is experimental, must be allowed via a global 'expose-experimental-directives'",
+                              args[0]);
+               return -1;
+       }
+       mark_tainted(TAINTED_CONFIG_EXP_KW_DECLARED);
+
+       free(conf->ssl_conf.ech_filedir);
+       conf->ssl_conf.ech_filedir = strdup(args[cur_arg+1]);
+       return 0;
+}
+
+
+static struct bind_kw_list bind_kws = { "SSL", { }, {
+       { "ech",    bind_parse_ech,     1 }, /* set ECH PEM file */
+       { 0, NULL, 0 },
+}};
+
+
+INITCALL1(STG_REGISTER, bind_register_keywords, &bind_kws);
+#endif
index 14b817980f592f42386d26d54e3923b768fe1783..0cdd6c77e06f9dad148ec9e13fd30e7cbd230250 100644 (file)
@@ -90,7 +90,9 @@
 #include <haproxy/ssl_ocsp.h>
 #include <haproxy/trace.h>
 #include <haproxy/ssl_trace.h>
-
+#ifdef USE_ECH
+#include <haproxy/ech.h>
+#endif
 
 /* ***** READ THIS before adding code here! *****
  *
@@ -3954,6 +3956,19 @@ ssl_sock_initial_ctx(struct bind_conf *bind_conf)
        if (global_ssl.security_level > -1)
                SSL_CTX_set_security_level(ctx, global_ssl.security_level);
 
+#ifdef USE_ECH
+       if (bind_conf->ssl_conf.ech_filedir) {
+               int loaded = 0;
+
+        if (load_echkeys(ctx, bind_conf->ssl_conf.ech_filedir, &loaded) != 1) {
+                   cfgerr += 1;
+                   ha_alert("Proxy '%s': failed to load ECH key s from %s for '%s' at [%s:%d].\n",
+                           bind_conf->frontend->id, bind_conf->ssl_conf.ech_filedir,
+                bind_conf->arg, bind_conf->file, bind_conf->line);
+        }
+    }
+#endif
+
        if (conf_ssl_methods->flags && (conf_ssl_methods->min || conf_ssl_methods->max))
                ha_warning("Proxy '%s': no-sslv3/no-tlsv1x are ignored for bind '%s' at [%s:%d]. "
                           "Use only 'ssl-min-ver' and 'ssl-max-ver' to fix.\n",
@@ -5320,9 +5335,15 @@ int ssl_sock_prepare_bind_conf(struct bind_conf *bind_conf)
                                   px->id, bind_conf->arg, bind_conf->file, bind_conf->line);
                }
                else {
+#ifdef USE_ECH
+            if (!bind_conf->ssl_conf.ech_filedir) {
+#endif
                        ha_alert("Proxy '%s': no SSL certificate specified for bind '%s' at [%s:%d] (use 'crt').\n",
                                 px->id, bind_conf->arg, bind_conf->file, bind_conf->line);
                        return -1;
+#ifdef USE_ECH
+        }
+#endif
                }
        }