From: sftcd Date: Fri, 26 Sep 2025 21:17:13 +0000 (+0100) Subject: MINOR: ssl/ech: add logging and sample fetches for ECH status and outer SNI X-Git-Tag: v3.3-dev11~10 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=23f5cbb41101f81699b5c73befc93570129fdae9;p=thirdparty%2Fhaproxy.git MINOR: ssl/ech: add logging and sample fetches for ECH status and outer SNI This patch adds functions to expose Encrypted Client Hello (ECH) status and outer SNI information for logging and sample fetching. Two new helper functions are introduced in ech.c: - conn_get_ech_status() places the ECH processing status string into a buffer. - conn_get_ech_outer_sni() retrieves the outer SNI value if ECH succeeded. Two new sample fetch keywords are added: - "ssl_fc_ech_status" returns the ECH status string. - "ssl_fc_ech_outer_sni" returns the outer SNI value seen during ECH. These allow ECH information to be used in HAProxy logs, ACLs, and captures. --- diff --git a/include/haproxy/ech.h b/include/haproxy/ech.h index dac74cfba..0772db9b5 100644 --- a/include/haproxy/ech.h +++ b/include/haproxy/ech.h @@ -6,6 +6,8 @@ #include int load_echkeys(SSL_CTX *ctx, char *dirname, int *loaded); +int conn_get_ech_status(struct connection *conn, struct buffer *buf); +int conn_get_ech_outer_sni(struct connection *conn, struct buffer *buf); # endif /* USE_ECH */ #endif /* _HAPROXY_ECH_H */ diff --git a/src/ech.c b/src/ech.c index 69b522c3a..786a4e96d 100644 --- a/src/ech.c +++ b/src/ech.c @@ -74,6 +74,58 @@ end: return rv; } +/* + * Place an ECH status string into a trash buffer + * ECH status string examples: + * SSL_ECH_STATUS_GREASE + * SSL_ECH_STATUS_NOT_TRIED + * SSL_ECH_STATUS_SUCCESS + * The status values are those defined in + * as the define'd returns from `SSL_ech_get1_status()` + */ +int conn_get_ech_status(struct connection *conn, struct buffer *buf) +{ + struct ssl_sock_ctx *ctx = conn_get_ssl_sock_ctx(conn); + char *sni_ech = NULL; + char *sni_clr = NULL; + const char *lstr = NULL; + + if (!ctx) + return 0; +#define s(x) #x + switch (SSL_ech_get1_status(ctx->ssl, &sni_ech, &sni_clr)) { + case SSL_ECH_STATUS_SUCCESS: lstr = s(SSL_ECH_STATUS_SUCCESS); break; + case SSL_ECH_STATUS_NOT_TRIED: lstr = s(SSL_ECH_STATUS_NOT_TRIED); break; + case SSL_ECH_STATUS_FAILED: lstr = s(SSL_ECH_STATUS_FAILED); break; + case SSL_ECH_STATUS_BAD_NAME: lstr = s(SSL_ECH_STATUS_BAD_NAME); break; + case SSL_ECH_STATUS_BAD_CALL: lstr = s(SSL_ECH_STATUS_BAD_CALL); break; + case SSL_ECH_STATUS_GREASE: lstr = s(SSL_ECH_STATUS_GREASE); break; + case SSL_ECH_STATUS_BACKEND: lstr = s(SSL_ECH_STATUS_BACKEND); break; + default: lstr = ""; break; + } +#undef s + chunk_printf(buf, "%s", lstr); + OPENSSL_free(sni_ech); + OPENSSL_free(sni_clr); + return 1; +} + +/* If ECH succeeded, return the outer SNI value seen */ +int conn_get_ech_outer_sni(struct connection *conn, struct buffer *buf) +{ + struct ssl_sock_ctx *ctx = conn_get_ssl_sock_ctx(conn); + char *sni_ech = NULL; + char *sni_clr = NULL; + + if (!ctx) + return 0; + if (SSL_ech_get1_status(ctx->ssl, &sni_ech, &sni_clr) + == SSL_ECH_STATUS_SUCCESS && sni_clr != NULL) + chunk_printf(buf, "%s", sni_clr); + OPENSSL_free(sni_ech); + OPENSSL_free(sni_clr); + return 1; +} static int bind_parse_ech(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) { @@ -97,4 +149,5 @@ static struct bind_kw_list bind_kws = { "SSL", { }, { INITCALL1(STG_REGISTER, bind_register_keywords, &bind_kws); + #endif diff --git a/src/ssl_sample.c b/src/ssl_sample.c index 4746b8c0c..4cf1c6d1d 100644 --- a/src/ssl_sample.c +++ b/src/ssl_sample.c @@ -33,6 +33,9 @@ #include #include #include +#ifdef USE_ECH +#include +#endif /***** Below are some sample fetching functions for ACL/patterns *****/ @@ -1880,6 +1883,48 @@ smp_fetch_ssl_fc_sni(const struct arg *args, struct sample *smp, const char *kw, #endif } +#ifdef USE_ECH +static int +smp_fetch_ssl_fc_ech_status(const struct arg *args, struct sample *smp, + const char *kw, void *private) +{ + struct buffer *smp_trash; + struct connection *conn; + + smp->flags = SMP_F_VOL_SESS | SMP_F_CONST; + smp->data.type = SMP_T_STR; + conn = objt_conn(smp->sess->origin); + if (!conn) + return 0; + smp_trash = get_trash_chunk(); + if (conn_get_ech_status(conn, smp_trash) == 1) { + smp->data.u.str.area = smp_trash->area; + smp->data.u.str.data = smp_trash->data; + } + return 1; +} + +static int +smp_fetch_ssl_fc_ech_outer_sni(const struct arg *args, struct sample *smp, + const char *kw, void *private) +{ + struct buffer *smp_trash; + struct connection *conn; + + smp->flags = SMP_F_VOL_SESS | SMP_F_CONST; + smp->data.type = SMP_T_STR; + conn = objt_conn(smp->sess->origin); + if (!conn) + return 0; + smp_trash = get_trash_chunk(); + if (conn_get_ech_outer_sni(conn, smp_trash) == 1) { + smp->data.u.str.area = smp_trash->area; + smp->data.u.str.data = smp_trash->data; + } + return 1; +} +#endif + /* binary, returns tls client hello cipher list. * Arguments: filter_option (0,1) */ @@ -2572,6 +2617,10 @@ static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, { #endif { "ssl_fc_sni", smp_fetch_ssl_fc_sni, 0, NULL, SMP_T_STR, SMP_USE_L5CLI }, +#ifdef USE_ECH + { "ssl_fc_ech_status", smp_fetch_ssl_fc_ech_status, 0, NULL, SMP_T_STR, SMP_USE_L5CLI }, + { "ssl_fc_ech_outer_sni", smp_fetch_ssl_fc_ech_outer_sni, 0, NULL, SMP_T_STR, SMP_USE_L5CLI }, +#endif { "ssl_fc_cipherlist_bin", smp_fetch_ssl_fc_cl_bin, ARG1(0,SINT), NULL, SMP_T_STR, SMP_USE_L5CLI }, { "ssl_fc_cipherlist_hex", smp_fetch_ssl_fc_cl_hex, ARG1(0,SINT), NULL, SMP_T_BIN, SMP_USE_L5CLI }, { "ssl_fc_cipherlist_str", smp_fetch_ssl_fc_cl_str, ARG1(0,SINT), NULL, SMP_T_STR, SMP_USE_L5CLI },