Hello Mariam,

On Wed, Apr 16, 2025 at 08:36:06AM -0500, Mariam John wrote:
> Subject: [PATCH 0/3] Add 4 new sample fetches to get ciphers, supported 
> groups, key shares and sigalgs from ClientHello message
> Hello William,
> 
>   Thank you for your patience and valuable feedback and reviews. Appreciate 
> it. As you requested, I have
> broken the changes into 3 seperate patches to help with the review process. 
> The first patch contains the
> 4 new fetches + the doc changes as I had originally submitted (with all the 
> review comments addressed).
> The second patch contains just the new regression test added for the 4 new 
> fetches (with the review
> comments addressed).

There were still a lot of spaces and tabs issues that I fixed and I changed the 
subject to match our contributing guide
( https://github.com/haproxy/haproxy/blob/master/CONTRIBUTING#L632 ) in the 
first patch.

You should really look at the documentation of your editor to fix that, we are 
using tabs for indentation and spaces for
alignment. Also we have a documentation which explain it there: 
https://www.haproxy.org/coding-style.html

The reg-test had a small issue because we are more strict and now start with 
haproxy -dW (no warning), so I removed the
useless tcplog lines which was doing the warning.

I put the 2 patches in attachment, with the fixed alignment and indentation, I 
can merge them if you are fine with the
changes.

> The third patch has the most changes and contains the new helper method,
> `clnt_hello_proc smp_client_hello_parse` which does the initial processing of 
> the client hello message
> that was common to the following fetches:
> 
>  1. smp_fetch_req_ssl_st_ext
>  2. smp_fetch_req_ssl_ec_ext
>  3. smp_fetch_ssl_hello_sni
>  4. smp_fetch_ssl_hello_alpn
>  5. smp_fetch_ssl_supported_groups
>  6. smp_fetch_ssl_sigalgs
>  7. smp_fetch_ssl_keyshare_groups
>  8. smp_fetch_ssl_cipherlist
> 
> The first 7 fetches do some additional processing based on the TLS extensions 
> whereas the last one,
> `smp_fetch_ssl_cipherlist` does not. Instead it parses the CipherSuite field. 
> This distinction has been made
> in the new helped function by using the boolean parameter `parse_extensions`. 
> Fetches 1 to 7 will pass true
> and `smp_fetch_ssl_cipherlist` will pass `false`. You had mentioned to use a 
> union for this but I wasn't
> sure how to use it here.
> 
> Thank you once again for taking the time to review.

I'll make a review of the third patch separately.

Thanks!

-- 
William Lallemand
>From 26eecd56a0740b78e01bb317d2be283b91dc4cb5 Mon Sep 17 00:00:00 2001
From: Mariam John <john.mariam....@gmail.com>
Date: Wed, 16 Apr 2025 08:36:07 -0500
Subject: [PATCH 1/2] MINOR: sample: add 4 new sample fetches for clienthello
 parsing

This patch contains this 4 new fetches and doc changes for the new fetches:

- req.ssl_cipherlist
- req.ssl_sigalgs
- req.ssl_keyshare_groups
- req.ssl_supported_groups

Towards:#2532
---
 doc/configuration.txt |  66 ++++++
 src/payload.c         | 507 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 573 insertions(+)

diff --git a/doc/configuration.txt b/doc/configuration.txt
index 15f944687..ff79196f2 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -25651,6 +25651,10 @@ req_ssl_sni                                           string
 req.ssl_st_ext                                        integer
 req.ssl_ver                                           integer
 req_ssl_ver                                           integer
+req.ssl_cipherlist                                    binary
+req.ssl_sigalgs                                       binary
+req.ssl_keyshare_groups                               binary
+req.ssl_supported_groups                              binary
 res.len                                               integer
 res.payload(<offset>,<length>)                        binary
 res.payload_lv(<offset1>,<length>[,<offset2>])        binary
@@ -25855,6 +25859,68 @@ req_ssl_sni : string (deprecated)
      use_backend bk_allow if { req.ssl_sni -f allowed_sites }
      default_backend bk_sorry_page
 
+req.ssl_cipherlist binary
+  Returns the binary form of the list of symmetric cipher options supported by
+  the client as reported in the contents of a TLS ClientHello. Note that this
+  only applies to raw contents found in the request buffer and not to contents
+  deciphered via an SSL data layer, so this will not work with "bind" lines
+  having the "ssl" option. Refer to "ssl_fc_cipherlist_bin" which is the SSL
+  bind equivalent that can be used when the "ssl" option is specified.
+
+  Examples :
+    # Wait for a client hello for at most 5 seconds
+    tcp-request inspect-delay 5s
+    tcp-request content accept if { req.ssl_hello_type 1 }
+    use-server fe3 if { req.ssl_cipherlist,be2hex(:,2),lower -m sub 1302:009f }
+    server fe3  ${htst_fe3_addr}:${htst_fe3_port}
+
+req.ssl_sigalgs binary
+  Returns the binary form of the list of signature algorithms supported by the
+  client as reported in the TLS ClientHello. This is available as a client hello
+  extension. Note that this only applies to raw contents found in the request
+  buffer and not to contents deciphered via an SSL data layer, so this will not
+  work with "bind" lines having the "ssl" option. Refer to "ssl_fc_sigalgs_bin"
+  which is the SSL bind equivalent that can be used when the "ssl" option is
+  specified.
+
+  Examples :
+    # Wait for a client hello for at most 5 seconds
+    tcp-request inspect-delay 5s
+    tcp-request content accept if { req.ssl_hello_type 1 }
+    use-server fe4 if { req.ssl_sigalgs,be2hex(:,2),lower -m sub 0403:0805 }
+    server fe4  ${htst_fe4_addr}:${htst_fe4_port}
+
+req.ssl_keyshare_groups binary
+  Return the binary format of the list of cryptographic parameters for key exchange
+  supported by the client as reported in the TLS ClientHello. In TLS v1.3, keyshare
+  is part of the ClientHello message and is the final client hello extension. Note
+  that this only applies to raw contents found in the request buffer and not to
+  contents deciphered via an  SSL data layer, so this will not work with "bind"
+  lines having the "ssl" option.
+
+  Examples :
+    # Wait for a client hello for at most 5 seconds
+    tcp-request inspect-delay 5s
+    tcp-request content accept if { req.ssl_hello_type 1 }
+    use-server fe3 if { req.ssl_keyshare_groups,be2hex(:,2),lower -m sub 001d  }
+    server fe3  ${htst_fe3_addr}:${htst_fe3_port}
+
+req.ssl_supported_groups binary
+  Returns the binary form of the list of supported groups supported by the client
+  as reported in the TLS ClientHello and used for key exchange which can include
+  both elliptic curve and non-EC key exchange. Note that this only applies to raw
+  contents found in the request buffer and not to contents deciphered via an SSL
+  data layer, so this will not  work with "bind" lines having the "ssl" option.
+  Refer to "ssl_fc_eclist_bin" which is the SSL bind equivalent that can be used
+  when the "ssl" option is specified.
+
+  Examples :
+    # Wait for a client hello for at most 5 seconds
+    tcp-request inspect-delay 5s
+    tcp-request content accept if { req.ssl_hello_type 1 }
+    use-server fe3 if { req.ssl_supported_groups, be2hex(:,2),lower -m sub 0017 }
+    server fe3  ${htst_fe3_addr}:${htst_fe3_port}
+
 req.ssl_st_ext : integer
   Returns 0 if the client didn't send a SessionTicket TLS Extension (RFC5077)
   Returns 1 if the client sent SessionTicket TLS Extension
diff --git a/src/payload.c b/src/payload.c
index 6a536d719..04cc3d50e 100644
--- a/src/payload.c
+++ b/src/payload.c
@@ -522,6 +522,509 @@ smp_fetch_req_ssl_ver(const struct arg *args, struct sample *smp, const char *kw
 	return 0;
 }
 
+/*
+ * Extract the ciphers that may be presented in a TLS client hello handshake message.
+ */
+static int
+smp_fetch_ssl_cipherlist(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	int hs_len, ext_len, bleft;
+	struct channel *chn;
+	unsigned char *data;
+
+	if (!smp->strm)
+		goto not_ssl_hello;
+
+	/* meaningless for HTX buffers */
+	if (IS_HTX_STRM(smp->strm))
+		goto not_ssl_hello;
+
+	chn = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_RES) ? &smp->strm->res : &smp->strm->req;
+	bleft = ci_data(chn);
+	data = (unsigned char *)ci_head(chn);
+
+	/* Check for SSL/TLS Handshake */
+	if (!bleft)
+		goto too_short;
+	if (*data != 0x16)
+		goto not_ssl_hello;
+
+	/* Check for SSLv3 or later (SSL version >= 3.0) in the record layer*/
+	if (bleft < 3)
+		goto too_short;
+	if (data[1] < 0x03)
+		goto not_ssl_hello;
+
+	if (bleft < 5)
+		goto too_short;
+	hs_len = (data[3] << 8) + data[4];
+	if (hs_len < 1 + 3 + 2 + 32 + 1 + 2 + 2 + 1 + 1 + 2 + 2)
+		goto not_ssl_hello; /* too short to have an extension */
+
+	data += 5; /* enter TLS handshake */
+	bleft -= 5;
+
+	/* Check for a complete client hello starting at <data> */
+	if (bleft < 1)
+		goto too_short;
+	if (data[0] != 0x01) /* msg_type = Client Hello */
+		goto not_ssl_hello;
+
+	/* Check the Hello's length */
+	if (bleft < 4)
+		goto too_short;
+	hs_len = (data[1] << 16) + (data[2] << 8) + data[3];
+	if (hs_len < 2 + 32 + 1 + 2 + 2 + 1 + 1 + 2 + 2)
+		goto not_ssl_hello; /* too short to have an extension */
+
+	/* We want the full handshake here */
+	if (bleft < hs_len)
+		goto too_short;
+
+	data += 4;
+	/* Start of the ClientHello message */
+	if (data[0] < 0x03 || data[1] < 0x01) /* TLSv1 minimum */
+		goto not_ssl_hello;
+
+	ext_len = data[34]; /* session_id_len */
+	if (ext_len > 32 || ext_len > (hs_len - 35)) /* check for correct session_id len */
+		goto not_ssl_hello;
+
+	/* Jump to cipher suite */
+	hs_len -= 35 + ext_len;
+	data   += 35 + ext_len;
+
+	if (hs_len < 4 ||                               /* minimum one cipher */
+	    (ext_len = (data[0] << 8) + data[1]) < 2 ||  /* minimum 2 bytes for a cipher */
+	     ext_len > hs_len)
+		goto not_ssl_hello;
+
+	smp->data.type = SMP_T_BIN;
+	smp->data.u.str.area = (char *)data + 2;
+	smp->data.u.str.data = ext_len;
+	smp->flags = SMP_F_VOLATILE | SMP_F_CONST;
+
+	return 1;
+
+too_short:
+	smp->flags = SMP_F_MAY_CHANGE;
+
+not_ssl_hello:
+
+	return 0;
+}
+
+/* Extract the supported group that may be presented in a TLS client hello handshake
+ * message.
+ */
+static int
+smp_fetch_ssl_supported_groups(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	int hs_len, ext_len, bleft;
+	struct channel *chn;
+	unsigned char *data;
+
+	if (!smp->strm)
+		goto not_ssl_hello;
+
+	/* meaningless for HTX buffers */
+	if (IS_HTX_STRM(smp->strm))
+		goto not_ssl_hello;
+
+	chn = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_RES) ? &smp->strm->res : &smp->strm->req;
+	bleft = ci_data(chn);
+	data = (unsigned char *)ci_head(chn);
+
+	/* Check for SSL/TLS Handshake */
+	if (!bleft)
+		goto too_short;
+	if (*data != 0x16)
+		goto not_ssl_hello;
+
+	/* Check for SSLv3 or later (SSL version >= 3.0) in the record layer*/
+	if (bleft < 3)
+		goto too_short;
+	if (data[1] < 0x03)
+		goto not_ssl_hello;
+
+	if (bleft < 5)
+		goto too_short;
+	hs_len = (data[3] << 8) + data[4];
+	if (hs_len < 1 + 3 + 2 + 32 + 1 + 2 + 2 + 1 + 1 + 2 + 2)
+		goto not_ssl_hello; /* too short to have an extension */
+
+	data += 5; /* enter TLS handshake */
+	bleft -= 5;
+	/* Check for a complete client hello starting at <data> */
+	if (bleft < 1)
+		goto too_short;
+	if (data[0] != 0x01) /* msg_type = Client Hello */
+		goto not_ssl_hello;
+
+	/* Check the Hello's length */
+	if (bleft < 4)
+		goto too_short;
+	hs_len = (data[1] << 16) + (data[2] << 8) + data[3];
+	if (hs_len < 2 + 32 + 1 + 2 + 2 + 1 + 1 + 2 + 2)
+		goto not_ssl_hello; /* too short to have an extension */
+
+	/* We want the full handshake here */
+	if (bleft < hs_len)
+		goto too_short;
+
+	data += 4;
+	/* Start of the ClientHello message */
+	if (data[0] < 0x03 || data[1] < 0x01) /* TLSv1 minimum */
+		goto not_ssl_hello;
+
+	ext_len = data[34]; /* session_id_len */
+	if (ext_len > 32 || ext_len > (hs_len - 35)) /* check for correct session_id len */
+		goto not_ssl_hello;
+
+	/* Jump to cipher suite */
+	hs_len -= 35 + ext_len;
+	data   += 35 + ext_len;
+
+	if (hs_len < 4 ||                               /* minimum one cipher */
+	    (ext_len = (data[0] << 8) + data[1]) < 2 ||  /* minimum 2 bytes for a cipher */
+	     ext_len > hs_len)
+		goto not_ssl_hello;
+
+	/* Jump to the compression methods */
+	hs_len -= 2 + ext_len;
+	data   += 2 + ext_len;
+
+	if (hs_len < 2 ||                       /* minimum one compression method */
+	    data[0] < 1 || data[0] > hs_len)    /* minimum 1 bytes for a method */
+		goto not_ssl_hello;
+
+	/* Jump to the extensions */
+	hs_len -= 1 + data[0];
+	data   += 1 + data[0];
+
+	if (hs_len < 2 ||                       /* minimum one extension list length */
+	    (ext_len = (data[0] << 8) + data[1]) > hs_len - 2) /* list too long */
+		goto not_ssl_hello;
+
+	hs_len = ext_len; /* limit ourselves to the extension length */
+	data += 2; /* Now 'data' points to the first content byte of an extension */
+
+	while (hs_len >= 4) {
+		int ext_type, grp_len;
+
+		ext_type = (data[0] << 8) + data[1]; /* Extension type */
+		ext_len  = (data[2] << 8) + data[3]; /* Extension length */
+
+		if (ext_len > hs_len - 4) /* Extension too long */
+			goto not_ssl_hello;
+
+		if (ext_type == 10) { /* Supported groups extension type ID is 10dec */
+			if (ext_len < 2)  /* need at least one entry of 2 bytes in the list length */
+				goto not_ssl_hello;
+
+			grp_len = (data[4] << 8) + data[5]; /* Supported group list length */
+			if (grp_len < 2 || grp_len > hs_len - 6)
+				goto not_ssl_hello; /* at least 2 bytes per supported group */
+
+			smp->data.type = SMP_T_BIN;
+			smp->data.u.str.area = (char *)data + 6;
+			smp->data.u.str.data = grp_len;
+			smp->flags = SMP_F_VOL_SESS | SMP_F_CONST;
+
+			return 1;
+
+		}
+		hs_len -= 4 + ext_len;
+		data   += 4 + ext_len;
+	}
+	/* supported groups not found */
+	goto not_ssl_hello;
+
+too_short:
+	smp->flags = SMP_F_MAY_CHANGE;
+
+not_ssl_hello:
+
+	return 0;
+}
+
+/* Extract the signature algorithms that may be presented in a TLS client hello
+ * handshake message.
+ */
+static int
+smp_fetch_ssl_sigalgs(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	int hs_len, ext_len, bleft;
+	struct channel *chn;
+	unsigned char *data;
+
+	if (!smp->strm)
+		goto not_ssl_hello;
+
+	/* meaningless for HTX buffers */
+	if (IS_HTX_STRM(smp->strm))
+		goto not_ssl_hello;
+
+	chn = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_RES) ? &smp->strm->res : &smp->strm->req;
+	bleft = ci_data(chn);
+	data = (unsigned char *)ci_head(chn);
+
+	/* Check for SSL/TLS Handshake */
+	if (!bleft)
+		goto too_short;
+	if (*data != 0x16)
+		goto not_ssl_hello;
+
+	/* Check for SSLv3 or later (SSL version >= 3.0) in the record layer*/
+	if (bleft < 3)
+		goto too_short;
+	if (data[1] < 0x03)
+		goto not_ssl_hello;
+
+	if (bleft < 5)
+		goto too_short;
+	hs_len = (data[3] << 8) + data[4];
+	if (hs_len < 1 + 3 + 2 + 32 + 1 + 2 + 2 + 1 + 1 + 2 + 2)
+		goto not_ssl_hello; /* too short to have an extension */
+
+	data += 5; /* enter TLS handshake */
+	bleft -= 5;
+	/* Check for a complete client hello starting at <data> */
+	if (bleft < 1)
+		goto too_short;
+	if (data[0] != 0x01) /* msg_type = Client Hello */
+		goto not_ssl_hello;
+
+	/* Check the Hello's length */
+	if (bleft < 4)
+		goto too_short;
+	hs_len = (data[1] << 16) + (data[2] << 8) + data[3];
+	if (hs_len < 2 + 32 + 1 + 2 + 2 + 1 + 1 + 2 + 2)
+		goto not_ssl_hello; /* too short to have an extension */
+
+	/* We want the full handshake here */
+	if (bleft < hs_len)
+		goto too_short;
+
+	data += 4;
+	/* Start of the ClientHello message */
+	if (data[0] < 0x03 || data[1] < 0x01) /* TLSv1 minimum */
+		goto not_ssl_hello;
+
+	ext_len = data[34]; /* session_id_len */
+	if (ext_len > 32 || ext_len > (hs_len - 35)) /* check for correct session_id len */
+		goto not_ssl_hello;
+
+	/* Jump to cipher suite */
+	hs_len -= 35 + ext_len;
+	data   += 35 + ext_len;
+
+	if (hs_len < 4 ||                               /* minimum one cipher */
+			(ext_len = (data[0] << 8) + data[1]) < 2 || /* minimum 2 bytes for a cipher */
+			ext_len > hs_len)
+		goto not_ssl_hello;
+
+	/* Jump to the compression methods */
+	hs_len -= 2 + ext_len;
+	data   += 2 + ext_len;
+
+	if (hs_len < 2 ||                       /* minimum one compression method */
+	    data[0] < 1 || data[0] > hs_len)    /* minimum 1 bytes for a method */
+		goto not_ssl_hello;
+
+	/* Jump to the extensions */
+	hs_len -= 1 + data[0];
+	data   += 1 + data[0];
+
+	if (hs_len < 2 ||                       /* minimum one extension list length */
+	    (ext_len = (data[0] << 8) + data[1]) > hs_len - 2) /* list too long */
+		goto not_ssl_hello;
+
+	hs_len = ext_len; /* limit ourselves to the extension length */
+	data += 2; /* Now 'data' points to the first content byte of an extension */
+
+	while (hs_len >= 4) {
+		int ext_type, sigalg_len;
+
+		ext_type = (data[0] << 8) + data[1]; /* Extension type */
+		ext_len  = (data[2] << 8) + data[3]; /* Extension length */
+
+		if (ext_len > hs_len - 4) /* Extension too long */
+			goto not_ssl_hello;
+
+		if (ext_type == 13) { /* Sigalgs extension type ID is 13dec */
+			if (ext_len < 2) /* need at least one entry of 2 bytes in the list length */
+				goto not_ssl_hello;
+
+			sigalg_len = (data[4] << 8) + data[5]; /* Sigalgs list length */
+			if (sigalg_len < 2 || sigalg_len > hs_len - 6)
+				goto not_ssl_hello; /* at least 2 bytes per sigalg */
+
+			smp->data.type = SMP_T_BIN;
+			smp->data.u.str.area = (char *)data + 6;
+			smp->data.u.str.data = sigalg_len;
+			smp->flags = SMP_F_VOLATILE | SMP_F_CONST;
+
+			return 1;
+
+		}
+		hs_len -= 4 + ext_len;
+		data   += 4 + ext_len;
+	}
+	/* sigalgs not found */
+	goto not_ssl_hello;
+
+too_short:
+	smp->flags = SMP_F_MAY_CHANGE;
+
+not_ssl_hello:
+
+	return 0;
+}
+
+/*
+ * Extract the key shares that may be presented in a TLS client hello handshake message.
+*/
+static int
+smp_fetch_ssl_keyshare_groups(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	int hs_len, ext_len, bleft, readPosition, numberOfKeyshares;
+	struct channel *chn;
+	struct buffer *smp_trash = NULL;
+	unsigned char *data;
+	unsigned char *dataPointer;
+
+	if (!smp->strm)
+		goto not_ssl_hello;
+
+	/* meaningless for HTX buffers */
+	if (IS_HTX_STRM(smp->strm))
+		goto not_ssl_hello;
+
+	chn = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_RES) ? &smp->strm->res : &smp->strm->req;
+	bleft = ci_data(chn);
+	data = (unsigned char *)ci_head(chn);
+
+	/* Check for SSL/TLS Handshake */
+	if (!bleft)
+		goto too_short;
+	if (*data != 0x16)
+		goto not_ssl_hello;
+
+	/* Check for SSLv3 or later (SSL version >= 3.0) in the record layer*/
+	if (bleft < 3)
+		goto too_short;
+	if (data[1] < 0x03)
+		goto not_ssl_hello;
+
+	if (bleft < 5)
+		goto too_short;
+	hs_len = (data[3] << 8) + data[4];
+	if (hs_len < 1 + 3 + 2 + 32 + 1 + 2 + 2 + 1 + 1 + 2 + 2)
+		goto not_ssl_hello; /* too short to have an extension */
+
+	data += 5; /* enter TLS handshake */
+	bleft -= 5;
+
+	/* Check for a complete client hello starting at <data> */
+	if (bleft < 1)
+		goto too_short;
+	if (data[0] != 0x01) /* msg_type = Client Hello */
+		goto not_ssl_hello;
+
+	/* Check the Hello's length */
+	if (bleft < 4)
+		goto too_short;
+	hs_len = (data[1] << 16) + (data[2] << 8) + data[3];
+	if (hs_len < 2 + 32 + 1 + 2 + 2 + 1 + 1 + 2 + 2)
+		goto not_ssl_hello; /* too short to have an extension */
+
+	/* We want the full handshake here */
+	if (bleft < hs_len)
+		goto too_short;
+
+	data += 4;
+	/* Start of the ClientHello message */
+	if (data[0] < 0x03 || data[1] < 0x01) /* TLSv1 minimum */
+		goto not_ssl_hello;
+
+	ext_len = data[34]; /* session_id_len */
+	if (ext_len > 32 || ext_len > (hs_len - 35)) /* check for correct session_id len */
+		goto not_ssl_hello;
+
+	/* Jump to cipher suite */
+	hs_len -= 35 + ext_len;
+	data   += 35 + ext_len;
+
+	if (hs_len < 4 ||                               /* minimum one cipher */
+	    (ext_len = (data[0] << 8) + data[1]) < 2 || /* minimum 2 bytes for a cipher */
+	     ext_len > hs_len)
+		goto not_ssl_hello;
+
+	/* Jump to the compression methods */
+	hs_len -= 2 + ext_len;
+	data   += 2 + ext_len;
+
+	if (hs_len < 2 ||                       /* minimum one compression method */
+            data[0] < 1 || data[0] > hs_len)    /* minimum 1 bytes for a method */
+		goto not_ssl_hello;
+
+	/* Jump to the extensions */
+	hs_len -= 1 + data[0];
+	data   += 1 + data[0];
+
+	if (hs_len < 2 ||                       /* minimum one extension list length */
+	    (ext_len = (data[0] << 8) + data[1]) > hs_len - 2) /* list too long */
+		goto not_ssl_hello;
+
+	hs_len = ext_len; /* limit ourselves to the extension length */
+	data += 2; /* Now 'data' points to the first content byte of an extension */
+
+	while (hs_len >= 4) {
+		int ext_type, keyshare_len;
+
+		ext_type = (data[0] << 8) + data[1]; /* Extension type */
+		ext_len  = (data[2] << 8) + data[3]; /* Extension length */
+
+		if (ext_len > hs_len - 4) /* Extension too long */
+			goto not_ssl_hello;
+
+		if (ext_type == 51) { /* Keyshare extension type ID is 51dec */
+			if (ext_len < 2) /* need at least one entry of 2 bytes in the list length */
+				goto not_ssl_hello;
+
+			keyshare_len = (data[4] << 8) + data[5]; /* Client keyshare length */
+			if (keyshare_len < 2 || keyshare_len > hs_len - 6)
+				goto not_ssl_hello; /* at least 2 bytes per keyshare */
+			dataPointer = data + 6; /* start of keyshare entries */
+			readPosition = 0;
+			numberOfKeyshares = 0;
+			smp_trash = get_trash_chunk();
+			while (readPosition < keyshare_len) {
+				/* Get the binary value of the keyshare group and move the offset to the end of the related keyshare */
+				memmove(b_orig(smp_trash) + (2*numberOfKeyshares), &dataPointer[readPosition], 2);
+				numberOfKeyshares++;
+				readPosition += ((int)dataPointer[readPosition+2] << 8) + (int)dataPointer[readPosition+3] + 4;
+			}
+			smp->data.type = SMP_T_BIN;
+			smp->data.u.str.area = smp_trash->area;
+			smp->data.u.str.data = 2*numberOfKeyshares;
+			smp->flags = SMP_F_VOLATILE | SMP_F_CONST;
+
+			return 1;
+		}
+		hs_len -= 4 + ext_len;
+		data   += 4 + ext_len;
+	}
+	/* keyshare groups not found */
+	goto not_ssl_hello;
+
+too_short:
+	smp->flags = SMP_F_MAY_CHANGE;
+not_ssl_hello:
+	return 0;
+}
+
 /* Try to extract the Server Name Indication that may be presented in a TLS
  * client hello handshake message. The format of the message is the following
  * (cf RFC5246 + RFC6066) :
@@ -1412,6 +1915,10 @@ static struct sample_fetch_kw_list smp_kws = {ILH, {
 	{ "req.ssl_st_ext",      smp_fetch_req_ssl_st_ext, 0,                      NULL,           SMP_T_SINT, SMP_USE_L6REQ },
 	{ "req.ssl_hello_type",  smp_fetch_ssl_hello_type, 0,                      NULL,           SMP_T_SINT, SMP_USE_L6REQ },
 	{ "req.ssl_sni",         smp_fetch_ssl_hello_sni,  0,                      NULL,           SMP_T_STR,  SMP_USE_L6REQ },
+	{ "req.ssl_cipherlist",        smp_fetch_ssl_cipherlist,       0,          NULL,           SMP_T_BIN,  SMP_USE_L6REQ|SMP_USE_L4CLI|SMP_USE_L5CLI|SMP_USE_FTEND },
+	{ "req.ssl_supported_groups",  smp_fetch_ssl_supported_groups, 0,          NULL,           SMP_T_BIN,  SMP_USE_L6REQ|SMP_USE_L4CLI|SMP_USE_L5CLI|SMP_USE_FTEND },
+	{ "req.ssl_sigalgs",           smp_fetch_ssl_sigalgs,          0,          NULL,           SMP_T_BIN,  SMP_USE_L6REQ|SMP_USE_L4CLI|SMP_USE_L5CLI|SMP_USE_FTEND },
+	{ "req.ssl_keyshare_groups",   smp_fetch_ssl_keyshare_groups,  0,          NULL,           SMP_T_BIN,  SMP_USE_L6REQ|SMP_USE_L4CLI|SMP_USE_L5CLI|SMP_USE_FTEND },
 	{ "req.ssl_alpn",        smp_fetch_ssl_hello_alpn, 0,                      NULL,           SMP_T_STR,  SMP_USE_L6REQ },
 	{ "req.ssl_ver",         smp_fetch_req_ssl_ver,    0,                      NULL,           SMP_T_SINT, SMP_USE_L6REQ },
 	{ "res.len",             smp_fetch_len,            0,                      NULL,           SMP_T_SINT, SMP_USE_L6RES },
-- 
2.48.1

>From 05868535059b1b319a708b90ae89545985362581 Mon Sep 17 00:00:00 2001
From: Mariam John <john.mariam....@gmail.com>
Date: Wed, 16 Apr 2025 08:36:08 -0500
Subject: [PATCH 2/2] REGTEST: add new reg-test for the 4 new clienthello
 fetches

Add a reg-test which uses the 4 fetches:

- req.ssl_cipherlist
- req.ssl_sigalgs
- req.ssl_keyshare_groups
- req.ssl_supported_groups
---
 reg-tests/checks/tcp-check-client-hello.vtc | 79 +++++++++++++++++++++
 1 file changed, 79 insertions(+)
 create mode 100644 reg-tests/checks/tcp-check-client-hello.vtc

diff --git a/reg-tests/checks/tcp-check-client-hello.vtc b/reg-tests/checks/tcp-check-client-hello.vtc
new file mode 100644
index 000000000..435fec70f
--- /dev/null
+++ b/reg-tests/checks/tcp-check-client-hello.vtc
@@ -0,0 +1,79 @@
+#REGTEST_TYPE=devel
+#EXCLUDE_TARGETS=osx,generic
+
+varnishtest "Health checks: test enhanced observability of TLS ClientHello"
+feature cmd "$HAPROXY_PROGRAM -cc 'feature(OPENSSL) && !ssllib_name_startswith(wolfSSL) && !ssllib_name_startswith(LibreSSL) && openssl_version_atleast(1.1.1)'"
+feature ignore_unknown_macro
+
+syslog S_ok -level notice {
+    recv
+    expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Health check for server be[0-9]+/srv succeeded, reason: Layer6 check passed.+check duration: [[:digit:]]+ms, status: 1/1 UP."
+    recv
+    expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Health check for server be[0-9]+/srv succeeded, reason: Layer6 check passed.+check duration: [[:digit:]]+ms, status: 1/1 UP."
+    recv
+    expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Health check for server be[0-9]+/srv succeeded, reason: Layer6 check passed.+check duration: [[:digit:]]+ms, status: 1/1 UP."
+} -start
+
+haproxy htst -conf {
+    global
+        ssl-default-bind-options ssl-min-ver TLSv1.2 ssl-max-ver TLSv1.3
+
+    defaults
+        timeout  client "${HAPROXY_TEST_TIMEOUT-5s}"
+        timeout  server "${HAPROXY_TEST_TIMEOUT-5s}"
+        timeout  connect "${HAPROXY_TEST_TIMEOUT-5s}"
+
+    listen li1
+       mode tcp
+       bind "fd@${li1}"
+       tcp-request inspect-delay 100ms
+
+       acl check_sig_algs req.ssl_sigalgs,be2hex(:,2),lower -m found
+       acl check_key_shares req.ssl_keyshare_groups,be2hex(:,2),lower -m found
+       tcp-request content accept if check_sig_algs
+       tcp-request content accept if check_key_shares
+
+       # Refer to https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.8 && https://tls13.xargs.org/#client-hello/annotated to get the binary values
+       use-server fe3 if { req.ssl_cipherlist,be2hex(:,2),lower -m sub 1302:1303:1301:009f } || { req.ssl_supported_groups, be2hex(:,2),lower -m sub 001d }
+       server fe3  ${htst_fe3_addr}:${htst_fe3_port}
+
+       use-server fe1 if { req.ssl_supported_groups, be2hex(:,2),lower -m sub 0017 }
+       server fe1 ${htst_fe1_addr}:${htst_fe1_port}
+
+    frontend fe1
+        bind "fd@${fe1}" ssl crt ${testdir}/common.pem curves P-256:P-384
+
+    frontend fe3
+        bind "fd@${fe3}" ssl crt ${testdir}/common.pem
+} -start
+
+haproxy h1 -conf {
+    defaults
+        mode tcp
+        timeout  client "${HAPROXY_TEST_TIMEOUT-5s}"
+        timeout  server "${HAPROXY_TEST_TIMEOUT-5s}"
+        timeout connect "${HAPROXY_TEST_TIMEOUT-5s}"
+
+    backend be1
+        mode tcp
+        log ${S_ok_addr}:${S_ok_port} daemon
+        option log-health-checks
+        option tcp-check
+        server srv ${htst_li1_addr}:${htst_li1_port}  check inter 1s rise 1 fall 1 check-ssl verify none curves X25519
+
+    backend be2
+        mode tcp
+        log ${S_ok_addr}:${S_ok_port} daemon
+        option log-health-checks
+        option tcp-check
+        server srv ${htst_li1_addr}:${htst_li1_port}  check inter 1s rise 1 fall 1 check-ssl verify none curves P-256:P-384
+
+    backend be3
+        mode tcp
+        log ${S_ok_addr}:${S_ok_port} daemon
+        option log-health-checks
+        option tcp-check
+        server srv ${htst_li1_addr}:${htst_li1_port}  check inter 1s rise 1 fall 1 check-ssl verify none ciphers ECDHE-RSA-AES256-GCM-SHA384
+} -start
+
+syslog S_ok -wait
-- 
2.48.1

Reply via email to