Hi,

Thank you for all comments I have received regarding JA3 Fingerprinting
patches. Here is the new set of patches which incorporated all your
suggestions.
Willy: I lowered memory requirements for ssl_capture (now 40 extra bytes),
but I did not go with the lowest as you suggested (unsigned char for
length/unsigned short for offset). Potentially this would work just fine,
yet specification allows to exceed that (
https://mta.openssl.org/pipermail/openssl-dev/2015-September/002860.html)
and personally I'm more in favour of sticking to standards as things could
bite us in the future. Dropping precalculated xxh64 hash would allow us to
go as low as 28 bytes if we care a lot about memory.

Regards,

Marcin Deranek
From 14a84a136e7e52957ae44fecaec432bdb9e3f4c9 Mon Sep 17 00:00:00 2001
From: Marcin Deranek <[email protected]>
Date: Tue, 13 Jul 2021 14:05:24 +0200
Subject: [PATCH 3/5] MINOR: sample: Add be2dec converter

Add be2dec converter which allows to build JA3 compatible TLS
fingerprints by converting big-endian binary data into string
separated unsigned integers eg.

http-request set-header X-SSL-JA3 %[ssl_fc_protocol_hello_id],\
    %[ssl_fc_cipherlist_bin(1),be2dec(-,2)],\
    %[ssl_fc_extlist_bin(1),be2dec(-,2)],\
    %[ssl_fc_eclist_bin(1),be2dec(-,2)],\
    %[ssl_fc_ecformats_bin,be2dec(-,1)]
---
 doc/configuration.txt          | 12 +++++++
 reg-tests/converter/be2dec.vtc | 50 +++++++++++++++++++++++++++++
 src/sample.c                   | 57 ++++++++++++++++++++++++++++++++++
 3 files changed, 119 insertions(+)
 create mode 100644 reg-tests/converter/be2dec.vtc

diff --git a/doc/configuration.txt b/doc/configuration.txt
index ecbbcdd04..d39e90752 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -16064,6 +16064,18 @@ base64
   an SSL ID can be copied in a header). For base64url("URL and Filename
   Safe Alphabet" (RFC 4648)) variant see "ub64enc".
 
+be2dec(<separator>,<chunk_size>,[<truncate>])
+  Converts a binary input sample to a string containing an unsigned integer
+  number per <chunk_size> input bytes. <separator> is put every <chunk_size>
+  binary input bytes if specified. <truncate> flag indicates whatever binary
+  input is truncated at <chunk_size> boundaries. <chunk_size> maximum value is
+  limited by the size of long long int (8 bytes).
+
+  Example:
+      bin(01020304050607),be2dec(:,2)   # 258:772:1286:7
+      bin(01020304050607),be2dec(-,2,1) # 258-772-1286
+      bin(01020304050607),be2dec(,2,1)  # 2587721286
+
 bool
   Returns a boolean TRUE if the input value of type signed integer is
   non-null, otherwise returns FALSE. Used in conjunction with and(), it can be
diff --git a/reg-tests/converter/be2dec.vtc b/reg-tests/converter/be2dec.vtc
new file mode 100644
index 000000000..d329e01b3
--- /dev/null
+++ b/reg-tests/converter/be2dec.vtc
@@ -0,0 +1,50 @@
+varnishtest "be2dec converter Test"
+
+#REQUIRE_VERSION=2.5
+
+feature ignore_unknown_macro
+
+server s1 {
+	rxreq
+	txresp
+} -repeat 2 -start
+
+haproxy h1 -conf {
+    defaults
+	mode http
+	timeout connect 1s
+	timeout client  1s
+	timeout server  1s
+
+    frontend fe
+	bind "fd@${fe}"
+
+	#### requests
+	http-request  set-var(txn.input) req.hdr(input)
+
+	http-response set-header be2dec-1   "%[var(txn.input),be2dec(:,1)]"
+	http-response set-header be2dec-2   "%[var(txn.input),be2dec(-,3)]"
+	http-response set-header be2dec-3   "%[var(txn.input),be2dec(::,3,1)]"
+
+	default_backend be
+
+    backend be
+	server s1 ${s1_addr}:${s1_port}
+} -start
+
+client c1 -connect ${h1_fe_sock} {
+	txreq -url "/" \
+	  -hdr "input: 0123456789"
+	rxresp
+	expect resp.status == 200
+	expect resp.http.be2dec-1 == "48:49:50:51:52:53:54:55:56:57"
+	expect resp.http.be2dec-2 == "3158322-3355701-3553080-57"
+	expect resp.http.be2dec-3 == "3158322::3355701::3553080"
+	txreq -url "/" \
+	  -hdr "input: abcdefghijklmnopqrstuvwxyz"
+	rxresp
+	expect resp.status == 200
+	expect resp.http.be2dec-1 == "97:98:99:100:101:102:103:104:105:106:107:108:109:110:111:112:113:114:115:116:117:118:119:120:121:122"
+	expect resp.http.be2dec-2 == "6382179-6579558-6776937-6974316-7171695-7369074-7566453-7763832-31098"
+	expect resp.http.be2dec-3 == "6382179::6579558::6776937::6974316::7171695::7369074::7566453::7763832"
+} -run
diff --git a/src/sample.c b/src/sample.c
index d02034cf0..5b7ad8b34 100644
--- a/src/sample.c
+++ b/src/sample.c
@@ -2057,6 +2057,62 @@ static int sample_conv_crypto_hmac(const struct arg *args, struct sample *smp, v
 
 #endif /* USE_OPENSSL */
 
+static int sample_conv_be2dec_check(struct arg *args, struct sample_conv *conv,
+                                    const char *file, int line, char **err)
+{
+	if (args[1].data.sint <= 0 || args[1].data.sint > sizeof(unsigned long long)) {
+		memprintf(err, "chunk_size out of [1..%ld] range (%lld)", sizeof(unsigned long long), args[1].data.sint);
+		return 0;
+	}
+
+	if (args[2].data.sint != 0 && args[2].data.sint != 1) {
+		memprintf(err, "Unsupported truncate value (%lld)", args[2].data.sint);
+		return 0;
+	}
+
+	return 1;
+}
+
+static int sample_conv_be2dec(const struct arg *args, struct sample *smp, void *private)
+{
+	struct buffer *trash = get_trash_chunk();
+	const int last = args[2].data.sint ? smp->data.u.str.data - args[1].data.sint + 1 : smp->data.u.str.data;
+	int max_size = trash->size - 2;
+	int i;
+	int start;
+	int ptr = 0;
+	unsigned long long number;
+	char *pos;
+
+	trash->data = 0;
+
+	while (ptr < last && trash->data <= max_size) {
+		start = trash->data;
+		if (ptr) {
+			/* Separator */
+			memcpy(trash->area + trash->data, args[0].data.str.area, args[0].data.str.data);
+			trash->data += args[0].data.str.data;
+		}
+		else
+			max_size -= args[0].data.str.data;
+		/* Integer */
+		for (number = 0, i = 0; i < args[1].data.sint && ptr < smp->data.u.str.data; i++)
+			number = (number << 8) + (unsigned char)smp->data.u.str.area[ptr++];
+		pos = ulltoa(number, trash->area + trash->data, trash->size - trash->data);
+		if (pos)
+			trash->data = pos - trash->area;
+		else {
+			trash->data = start;
+			break;
+		}
+	}
+
+	smp->data.u.str = *trash;
+	smp->data.type = SMP_T_STR;
+	smp->flags &= ~SMP_F_CONST;
+	return 1;
+}
+
 static int sample_conv_bin2hex(const struct arg *arg_p, struct sample *smp, void *private)
 {
 	struct buffer *trash = get_trash_chunk();
@@ -4251,6 +4307,7 @@ static struct sample_conv_kw_list sample_conv_kws = {ILH, {
 	{ "upper",   sample_conv_str2upper,    0,                     NULL,                     SMP_T_STR,  SMP_T_STR  },
 	{ "lower",   sample_conv_str2lower,    0,                     NULL,                     SMP_T_STR,  SMP_T_STR  },
 	{ "length",  sample_conv_length,       0,                     NULL,                     SMP_T_STR,  SMP_T_SINT },
+	{ "be2dec",  sample_conv_be2dec,       ARG3(1,STR,SINT,SINT), sample_conv_be2dec_check, SMP_T_BIN,  SMP_T_STR  },
 	{ "hex",     sample_conv_bin2hex,      0,                     NULL,                     SMP_T_BIN,  SMP_T_STR  },
 	{ "hex2i",   sample_conv_hex2int,      0,                     NULL,                     SMP_T_STR,  SMP_T_SINT },
 	{ "ipmask",  sample_conv_ipmask,       ARG2(1,MSK4,MSK6),     NULL,                     SMP_T_ADDR, SMP_T_IPV4 },
-- 
2.32.0

From 77cb1818ff1decac28ba02b3ee68a6ded9cd6565 Mon Sep 17 00:00:00 2001
From: Marcin Deranek <[email protected]>
Date: Tue, 13 Jul 2021 15:14:21 +0200
Subject: [PATCH 2/5] MINOR: sample: Expose SSL captures using new fetchers

To be able to provide JA3 compatible TLS Fingerprints we need to expose
all Client Hello captured data using fetchers. Patch provides new
and modifies existing fetchers to add ability to filter out GREASE values:
- ssl_fc_cipherlist_*
- ssl_fc_ecformats_bin
- ssl_fc_eclist_bin
- ssl_fc_extlist_bin
- ssl_fc_protocol_hello_id
---
 doc/configuration.txt   | 115 ++++++++++++++++++++++++++++-----
 include/haproxy/tools.h |   2 +
 src/ssl_sample.c        | 140 ++++++++++++++++++++++++++++++++++++++--
 src/tools.c             |  19 ++++++
 4 files changed, 254 insertions(+), 22 deletions(-)

diff --git a/doc/configuration.txt b/doc/configuration.txt
index c3d640ab8..ecbbcdd04 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -18786,27 +18786,93 @@ ssl_fc_cipher : string
   Returns the name of the used cipher when the incoming connection was made
   over an SSL/TLS transport layer.
 
-ssl_fc_cipherlist_bin : binary
-  Returns the binary form of the client hello cipher list. The maximum returned
-  value length is according with the value of
-  "tune.ssl.capture-cipherlist-size".
+ssl_fc_cipherlist_bin([<exclude_grease>]) : binary
+  Returns the binary form of the client hello cipher list. Setting
+  <exlude_grease> to 1 will exclude GREASE (RFC8701) values from the output.
+  The maximum returned value length is limited by the shared capture buffer
+  size controlled by "tune.ssl.capture-cipherlist-size" setting.
 
-ssl_fc_cipherlist_hex : string
+  Example:
+      http-request set-header X-SSL-JA3 %[ssl_fc_protocol_hello_id],\
+          %[ssl_fc_cipherlist_bin(1),be2dec(-,2)],\
+          %[ssl_fc_extlist_bin(1),be2dec(-,2)],\
+          %[ssl_fc_eclist_bin(1),be2dec(-,2)],\
+          %[ssl_fc_ecformats_bin,be2dec(-,1)]
+      acl is_malware req.fhdr(x-ssl-ja3),digest(md5),hex,\
+          map(/path/to/file/with/malware-ja3.lst) -m found
+      http-request set-header X-Malware True if is_malware
+      http-request set-header X-Malware False if !is_malware
+
+ssl_fc_cipherlist_hex([<exclude_grease>]) : string
   Returns the binary form of the client hello cipher list encoded as
-  hexadecimal. The maximum returned value length is according with the value of
-  "tune.ssl.capture-cipherlist-size".
-
-ssl_fc_cipherlist_str : string
-  Returns the decoded text form of the client hello cipher list. The maximum
-  number of ciphers returned is according with the value of
-  "tune.ssl.capture-cipherlist-size". Note that this sample-fetch is only
-  available with OpenSSL >= 1.0.2. If the function is not enabled, this
-  sample-fetch returns the hash like "ssl_fc_cipherlist_xxh".
+  hexadecimal. Setting <exlude_grease> to 1 will exclude GREASE (RFC8701)
+  values from the output. The maximum returned value length is limited by the
+  shared capture buffer size controlled by "tune.ssl.capture-cipherlist-size"
+  setting.
+
+ssl_fc_cipherlist_str([<exclude_grease>]) : string
+  Returns the decoded text form of the client hello cipher list. Setting
+  <exlude_grease> to 1 will exclude GREASE (RFC8701) values from the output.
+  The maximum returned value length is limited by the shared capture buffer
+  size controlled by "tune.ssl.capture-cipherlist-size" setting. Note that
+  this sample-fetch is only available with OpenSSL >= 1.0.2. If the function
+  is not enabled, this sample-fetch returns the hash like
+  "ssl_fc_cipherlist_xxh".
 
 ssl_fc_cipherlist_xxh : integer
-  Returns a xxh64 of the cipher list. This hash can be return only is the value
+  Returns a xxh64 of the cipher list. This hash can return only if the value
   "tune.ssl.capture-cipherlist-size" is set greater than 0, however the hash
-  take in account all the data of the cipher list.
+  take into account all the data of the cipher list.
+
+ssl_fc_ecformats_bin : binary
+  Return the binary form of the client hello supported elliptic curve point
+  formats. The maximum returned value length is limited by the shared capture
+  buffer size controlled by "tune.ssl.capture-cipherlist-size" setting.
+
+  Example:
+      http-request set-header X-SSL-JA3 %[ssl_fc_protocol_hello_id],\
+          %[ssl_fc_cipherlist_bin(1),be2dec(-,2)],\
+          %[ssl_fc_extlist_bin(1),be2dec(-,2)],\
+          %[ssl_fc_eclist_bin(1),be2dec(-,2)],\
+          %[ssl_fc_ecformats_bin,be2dec(-,1)]
+      acl is_malware req.fhdr(x-ssl-ja3),digest(md5),hex,\
+          map(/path/to/file/with/malware-ja3.lst) -m found
+      http-request set-header X-Malware True if is_malware
+      http-request set-header X-Malware False if !is_malware
+
+ssl_fc_eclist_bin([<exclude_grease>]) : binary
+  Returns the binary form of the client hello supported elliptic curves.
+  Setting <exlude_grease> to 1 will exclude GREASE (RFC8701) values from the
+  output. The maximum returned value length is limited by the shared capture
+  buffer size controlled by "tune.ssl.capture-cipherlist-size" setting.
+
+  Example:
+      http-request set-header X-SSL-JA3 %[ssl_fc_protocol_hello_id],\
+          %[ssl_fc_cipherlist_bin(1),be2dec(-,2)],\
+          %[ssl_fc_extlist_bin(1),be2dec(-,2)],\
+          %[ssl_fc_eclist_bin(1),be2dec(-,2)],\
+          %[ssl_fc_ecformats_bin,be2dec(-,1)]
+      acl is_malware req.fhdr(x-ssl-ja3),digest(md5),hex,\
+          map(/path/to/file/with/malware-ja3.lst) -m found
+      http-request set-header X-Malware True if is_malware
+      http-request set-header X-Malware False if !is_malware
+
+ssl_fc_extlist_bin([<exclude_grease>]) : binary
+  Returns the binary form of the client hello extension list. Setting
+  <exlude_grease> to 1 will exclude GREASE (RFC8701) values from the output.
+  The maximum returned value length is limited by the shared capture buffer
+  size controlled by "tune.ssl.capture-cipherlist-size" setting.
+
+  Example:
+      http-request set-header X-SSL-JA3 %[ssl_fc_protocol_hello_id],\
+          %[ssl_fc_cipherlist_bin(1),be2dec(-,2)],\
+          %[ssl_fc_extlist_bin(1),be2dec(-,2)],\
+          %[ssl_fc_eclist_bin(1),be2dec(-,2)],\
+          %[ssl_fc_ecformats_bin,be2dec(-,1)]
+      acl is_malware req.fhdr(x-ssl-ja3),digest(md5),hex,\
+          map(/path/to/file/with/malware-ja3.lst) -m found
+      http-request set-header X-Malware True if is_malware
+      http-request set-header X-Malware False if !is_malware
 
 ssl_fc_client_random : binary
   Returns the client random of the front connection when the incoming connection
@@ -18897,6 +18963,23 @@ ssl_fc_protocol : string
   Returns the name of the used protocol when the incoming connection was made
   over an SSL/TLS transport layer.
 
+ssl_fc_protocol_hello_id : integer
+  The version of the TLS protocol by which the client wishes to communicate
+  during the session as indicated in client hello message. This value can
+  return only if the value "tune.ssl.capture-cipherlist-size" is set greater
+  than 0.
+
+  Example:
+      http-request set-header X-SSL-JA3 %[ssl_fc_protocol_hello_id],\
+          %[ssl_fc_cipherlist_bin(1),be2dec(-,2)],\
+          %[ssl_fc_extlist_bin(1),be2dec(-,2)],\
+          %[ssl_fc_eclist_bin(1),be2dec(-,2)],\
+          %[ssl_fc_ecformats_bin,be2dec(-,1)]
+      acl is_malware req.fhdr(x-ssl-ja3),digest(md5),hex,\
+          map(/path/to/file/with/malware-ja3.lst) -m found
+      http-request set-header X-Malware True if is_malware
+      http-request set-header X-Malware False if !is_malware
+
 ssl_fc_unique_id : binary
   When the incoming connection was made over an SSL/TLS transport layer,
   returns the TLS unique ID as defined in RFC5929 section 3. The unique id
diff --git a/include/haproxy/tools.h b/include/haproxy/tools.h
index e376a716e..3ccbfd356 100644
--- a/include/haproxy/tools.h
+++ b/include/haproxy/tools.h
@@ -973,6 +973,8 @@ static inline unsigned long long rdtsc()
 struct list;
 int list_append_word(struct list *li, const char *str, char **err);
 
+void exclude_grease(char *input, int len, struct buffer *output);
+
 int dump_text(struct buffer *out, const char *buf, int bsize);
 int dump_binary(struct buffer *out, const char *buf, int bsize);
 int dump_text_line(struct buffer *out, const char *buf, int bsize, int len,
diff --git a/src/ssl_sample.c b/src/ssl_sample.c
index 3093a36e4..68311d598 100644
--- a/src/ssl_sample.c
+++ b/src/ssl_sample.c
@@ -1130,6 +1130,7 @@ smp_fetch_ssl_fc_sni(const struct arg *args, struct sample *smp, const char *kw,
 static int
 smp_fetch_ssl_fc_cl_bin(const struct arg *args, struct sample *smp, const char *kw, void *private)
 {
+	struct buffer *smp_trash;
 	struct connection *conn;
 	struct ssl_capture *capture;
 	SSL *ssl;
@@ -1143,10 +1144,20 @@ smp_fetch_ssl_fc_cl_bin(const struct arg *args, struct sample *smp, const char *
 	if (!capture)
 		return 0;
 
-	smp->flags = SMP_F_VOL_TEST | SMP_F_CONST;
+	if (args[0].data.sint) {
+		smp_trash = get_trash_chunk();
+		exclude_grease(capture->data + capture->ciphersuite_offset, capture->ciphersuite_len, smp_trash);
+		smp->data.u.str.area = smp_trash->area;
+		smp->data.u.str.data = smp_trash->data;
+		smp->flags = SMP_F_VOL_SESS;
+	}
+	else {
+		smp->data.u.str.area = capture->data + capture->ciphersuite_offset;
+		smp->data.u.str.data = capture->ciphersuite_len;
+		smp->flags = SMP_F_VOL_TEST | SMP_F_CONST;
+	}
+
 	smp->data.type = SMP_T_BIN;
-	smp->data.u.str.area = capture->data + capture->ciphersuite_offset;
-	smp->data.u.str.data = capture->ciphersuite_len;
 	return 1;
 }
 
@@ -1188,6 +1199,119 @@ smp_fetch_ssl_fc_cl_xxh64(const struct arg *args, struct sample *smp, const char
 	return 1;
 }
 
+static int
+smp_fetch_ssl_fc_protocol_hello_id(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct connection *conn;
+	struct ssl_capture *capture;
+	SSL *ssl;
+
+	conn = objt_conn(smp->sess->origin);
+	ssl = ssl_sock_get_ssl_object(conn);
+	if (!ssl)
+		return 0;
+
+	capture = SSL_get_ex_data(ssl, ssl_capture_ptr_index);
+	if (!capture)
+		return 0;
+
+	smp->flags = SMP_F_VOL_SESS;
+	smp->data.type = SMP_T_SINT;
+	smp->data.u.sint = capture->protocol_version;
+	return 1;
+}
+
+static int
+smp_fetch_ssl_fc_ext_bin(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct buffer *smp_trash;
+	struct connection *conn;
+	struct ssl_capture *capture;
+	SSL *ssl;
+
+	conn = objt_conn(smp->sess->origin);
+	ssl = ssl_sock_get_ssl_object(conn);
+	if (!ssl)
+		return 0;
+
+	capture = SSL_get_ex_data(ssl, ssl_capture_ptr_index);
+	if (!capture)
+		return 0;
+
+	if (args[0].data.sint) {
+		smp_trash = get_trash_chunk();
+		exclude_grease(capture->data + capture->extensions_offset, capture->extensions_len, smp_trash);
+		smp->data.u.str.area = smp_trash->area;
+		smp->data.u.str.data = smp_trash->data;
+		smp->flags = SMP_F_VOL_SESS;
+	}
+	else {
+		smp->data.u.str.area = capture->data + capture->extensions_offset;
+		smp->data.u.str.data = capture->extensions_len;
+		smp->flags = SMP_F_VOL_TEST | SMP_F_CONST;
+	}
+
+	smp->data.type = SMP_T_BIN;
+	return 1;
+}
+
+static int
+smp_fetch_ssl_fc_ecl_bin(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct buffer *smp_trash;
+	struct connection *conn;
+	struct ssl_capture *capture;
+	SSL *ssl;
+
+	conn = objt_conn(smp->sess->origin);
+	ssl = ssl_sock_get_ssl_object(conn);
+	if (!ssl)
+		return 0;
+
+	capture = SSL_get_ex_data(ssl, ssl_capture_ptr_index);
+	if (!capture)
+		return 0;
+
+	if (args[0].data.sint) {
+		smp_trash = get_trash_chunk();
+		exclude_grease(capture->data + capture->ec_offset, capture->ec_len, smp_trash);
+		smp->data.u.str.area = smp_trash->area;
+		smp->data.u.str.data = smp_trash->data;
+		smp->flags = SMP_F_VOL_SESS;
+	}
+	else {
+		smp->data.u.str.area = capture->data + capture->ec_offset;
+		smp->data.u.str.data = capture->ec_len;
+		smp->flags = SMP_F_VOL_TEST | SMP_F_CONST;
+	}
+
+	smp->data.type = SMP_T_BIN;
+	return 1;
+}
+
+static int
+smp_fetch_ssl_fc_ecf_bin(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct connection *conn;
+	struct ssl_capture *capture;
+	SSL *ssl;
+
+	conn = objt_conn(smp->sess->origin);
+	ssl = ssl_sock_get_ssl_object(conn);
+	if (!ssl)
+		return 0;
+
+	capture = SSL_get_ex_data(ssl, ssl_capture_ptr_index);
+	if (!capture)
+		return 0;
+
+	smp->flags = SMP_F_VOL_TEST | SMP_F_CONST;
+	smp->data.type = SMP_T_BIN;
+	smp->data.u.str.area = capture->data + capture->ec_formats_offset;
+	smp->data.u.str.data = capture->ec_formats_len;
+	return 1;
+}
+
 /* Dump the SSL keylog, it only works with "tune.ssl.keylog 1" */
 #ifdef HAVE_SSL_KEYLOG
 static int smp_fetch_ssl_x_keylog(const struct arg *args, struct sample *smp, const char *kw, void *private)
@@ -1533,10 +1657,14 @@ static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, {
 #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
 	{ "ssl_fc_sni",             smp_fetch_ssl_fc_sni,         0,                   NULL,    SMP_T_STR,  SMP_USE_L5CLI },
 #endif
-	{ "ssl_fc_cipherlist_bin",  smp_fetch_ssl_fc_cl_bin,      0,                   NULL,    SMP_T_STR,  SMP_USE_L5CLI },
-	{ "ssl_fc_cipherlist_hex",  smp_fetch_ssl_fc_cl_hex,      0,                   NULL,    SMP_T_BIN,  SMP_USE_L5CLI },
-	{ "ssl_fc_cipherlist_str",  smp_fetch_ssl_fc_cl_str,      0,                   NULL,    SMP_T_STR,  SMP_USE_L5CLI },
+	{ "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 },
 	{ "ssl_fc_cipherlist_xxh",  smp_fetch_ssl_fc_cl_xxh64,    0,                   NULL,    SMP_T_SINT, SMP_USE_L5CLI },
+	{ "ssl_fc_protocol_hello_id",smp_fetch_ssl_fc_protocol_hello_id,0,             NULL,    SMP_T_SINT, SMP_USE_L5CLI },
+	{ "ssl_fc_extlist_bin",     smp_fetch_ssl_fc_ext_bin,     ARG1(0,SINT),        NULL,    SMP_T_STR,  SMP_USE_L5CLI },
+	{ "ssl_fc_eclist_bin",      smp_fetch_ssl_fc_ecl_bin,     ARG1(0,SINT),        NULL,    SMP_T_STR,  SMP_USE_L5CLI },
+	{ "ssl_fc_ecformats_bin",   smp_fetch_ssl_fc_ecf_bin,     0,                   NULL,    SMP_T_STR,  SMP_USE_L5CLI },
 
 /* SSL server certificate fetches */
 	{ "ssl_s_der",              smp_fetch_ssl_x_der,          0,                   NULL,    SMP_T_BIN,  SMP_USE_L5CLI },
diff --git a/src/tools.c b/src/tools.c
index bd6bf4edc..5e47691ef 100644
--- a/src/tools.c
+++ b/src/tools.c
@@ -4502,6 +4502,25 @@ int may_access(const void *ptr)
 	return 1;
 }
 
+/* Exclude GREASE (RFC8701) values from input buffer */
+void exclude_grease(char *input, int len, struct buffer *output)
+{
+	int ptr = 0;
+
+	while (ptr < len - 1) {
+		if (input[ptr] != input[ptr+1] || (input[ptr] & 0x0f) != 0x0a) {
+			if (output->data <= output->size - 2) {
+				memcpy(output->area + output->data, input + ptr, 2);
+				output->data += 2;
+			} else
+				break;
+		}
+		ptr += 2;
+	}
+	if (output->size - output->data > 0 && len - ptr > 0)
+		output->area[output->data++] = input[ptr];
+}
+
 /* print a string of text buffer to <out>. The format is :
  * Non-printable chars \t, \n, \r and \e are * encoded in C format.
  * Other non-printable chars are encoded "\xHH". Space, '\', and '=' are also escaped.
-- 
2.32.0

From 88d0feb385005559d7c922194455cc24e093ec5c Mon Sep 17 00:00:00 2001
From: Marcin Deranek <[email protected]>
Date: Tue, 13 Jul 2021 14:08:56 +0200
Subject: [PATCH 4/5] MINOR: sample: Add be2hex converter

Add be2hex converter to convert big-endian binary data into hex string
with optional string separators.
---
 doc/configuration.txt          | 14 +++++++++
 reg-tests/converter/be2hex.vtc | 53 +++++++++++++++++++++++++++++++++
 src/sample.c                   | 54 ++++++++++++++++++++++++++++++++++
 3 files changed, 121 insertions(+)
 create mode 100644 reg-tests/converter/be2hex.vtc

diff --git a/doc/configuration.txt b/doc/configuration.txt
index d39e90752..d2958fc3a 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -16076,6 +16076,20 @@ be2dec(<separator>,<chunk_size>,[<truncate>])
       bin(01020304050607),be2dec(-,2,1) # 258-772-1286
       bin(01020304050607),be2dec(,2,1)  # 2587721286
 
+be2hex([<separator>],[<chunk_size>],[<truncate>])
+  Converts big-endian binary input sample to a hex string containing two hex
+  digits per input byte. It is used to log or transfer hex dumps of some
+  binary input data in a way that can be reliably transferred (e.g. an SSL ID
+  can be copied in a header). <separator> is put every <chunk_size> binary
+  input bytes if specified. <truncate> flag indicates whatever binary input is
+  truncated at <chunk_size> boundaries.
+
+  Example:
+      bin(01020304050607),be2hex         # 01020304050607
+      bin(01020304050607),be2hex(:,2)    # 0102:0304:0506:07
+      bin(01020304050607),be2hex(--,2,1) # 0102--0304--0506
+      bin(0102030405060708),be2hex(,3,1) # 010203040506
+
 bool
   Returns a boolean TRUE if the input value of type signed integer is
   non-null, otherwise returns FALSE. Used in conjunction with and(), it can be
diff --git a/reg-tests/converter/be2hex.vtc b/reg-tests/converter/be2hex.vtc
new file mode 100644
index 000000000..b696a43de
--- /dev/null
+++ b/reg-tests/converter/be2hex.vtc
@@ -0,0 +1,53 @@
+varnishtest "be2hex converter Test"
+
+#REQUIRE_VERSION=2.5
+
+feature ignore_unknown_macro
+
+server s1 {
+	rxreq
+	txresp
+} -repeat 2 -start
+
+haproxy h1 -conf {
+    defaults
+	mode http
+	timeout connect 1s
+	timeout client  1s
+	timeout server  1s
+
+    frontend fe
+	bind "fd@${fe}"
+
+	#### requests
+	http-request  set-var(txn.input) req.hdr(input)
+
+	http-response set-header be2hex     "%[var(txn.input),be2hex,lower]"
+	http-response set-header be2hex-1   "%[var(txn.input),be2hex(:,1),lower]"
+	http-response set-header be2hex-2   "%[var(txn.input),be2hex(--,3),lower]"
+	http-response set-header be2hex-3   "%[var(txn.input),be2hex(.,3,1),lower]"
+
+	default_backend be
+
+    backend be
+	server s1 ${s1_addr}:${s1_port}
+} -start
+
+client c1 -connect ${h1_fe_sock} {
+	txreq -url "/" \
+	  -hdr "input: 0123456789"
+	rxresp
+	expect resp.status == 200
+	expect resp.http.be2hex   == "30313233343536373839"
+	expect resp.http.be2hex-1 == "30:31:32:33:34:35:36:37:38:39"
+	expect resp.http.be2hex-2 == "303132--333435--363738--39"
+	expect resp.http.be2hex-3 == "303132.333435.363738"
+	txreq -url "/" \
+	  -hdr "input: abcdefghijklmnopqrstuvwxyz"
+	rxresp
+	expect resp.status == 200
+	expect resp.http.be2hex   == "6162636465666768696a6b6c6d6e6f707172737475767778797a"
+	expect resp.http.be2hex-1 == "61:62:63:64:65:66:67:68:69:6a:6b:6c:6d:6e:6f:70:71:72:73:74:75:76:77:78:79:7a"
+	expect resp.http.be2hex-2 == "616263--646566--676869--6a6b6c--6d6e6f--707172--737475--767778--797a"
+	expect resp.http.be2hex-3 == "616263.646566.676869.6a6b6c.6d6e6f.707172.737475.767778"
+} -run
diff --git a/src/sample.c b/src/sample.c
index 5b7ad8b34..00f296b6c 100644
--- a/src/sample.c
+++ b/src/sample.c
@@ -2113,6 +2113,59 @@ static int sample_conv_be2dec(const struct arg *args, struct sample *smp, void *
 	return 1;
 }
 
+static int sample_conv_be2hex_check(struct arg *args, struct sample_conv *conv,
+                                    const char *file, int line, char **err)
+{
+	if (args[1].data.sint <= 0 && (args[0].data.str.data > 0 || args[2].data.sint != 0)) {
+		memprintf(err, "chunk_size needs to be positive (%lld)", args[1].data.sint);
+		return 0;
+	}
+
+	if (args[2].data.sint != 0 && args[2].data.sint != 1) {
+		memprintf(err, "Unsupported truncate value (%lld)", args[2].data.sint);
+		return 0;
+	}
+
+	return 1;
+}
+
+static int sample_conv_be2hex(const struct arg *args, struct sample *smp, void *private)
+{
+	struct buffer *trash = get_trash_chunk();
+	int chunk_size = args[1].data.sint;
+	const int last = args[2].data.sint ? smp->data.u.str.data - chunk_size + 1 : smp->data.u.str.data;
+	int i;
+	int max_size;
+	int ptr = 0;
+	unsigned char c;
+
+	trash->data = 0;
+	if (args[0].data.str.data == 0 && args[2].data.sint == 0)
+		chunk_size = smp->data.u.str.data;
+	max_size = trash->size - 2 * chunk_size;
+
+	while (ptr < last && trash->data <= max_size) {
+		if (ptr) {
+			/* Separator */
+			memcpy(trash->area + trash->data, args[0].data.str.area, args[0].data.str.data);
+			trash->data += args[0].data.str.data;
+		}
+		else
+			max_size -= args[0].data.str.data;
+		/* Hex */
+		for (i = 0; i < chunk_size && ptr < smp->data.u.str.data; i++) {
+			c = smp->data.u.str.area[ptr++];
+			trash->area[trash->data++] = hextab[(c >> 4) & 0xF];
+			trash->area[trash->data++] = hextab[c & 0xF];
+		}
+	}
+
+	smp->data.u.str = *trash;
+	smp->data.type = SMP_T_STR;
+	smp->flags &= ~SMP_F_CONST;
+	return 1;
+}
+
 static int sample_conv_bin2hex(const struct arg *arg_p, struct sample *smp, void *private)
 {
 	struct buffer *trash = get_trash_chunk();
@@ -4308,6 +4361,7 @@ static struct sample_conv_kw_list sample_conv_kws = {ILH, {
 	{ "lower",   sample_conv_str2lower,    0,                     NULL,                     SMP_T_STR,  SMP_T_STR  },
 	{ "length",  sample_conv_length,       0,                     NULL,                     SMP_T_STR,  SMP_T_SINT },
 	{ "be2dec",  sample_conv_be2dec,       ARG3(1,STR,SINT,SINT), sample_conv_be2dec_check, SMP_T_BIN,  SMP_T_STR  },
+	{ "be2hex",  sample_conv_be2hex,       ARG3(1,STR,SINT,SINT), sample_conv_be2hex_check, SMP_T_BIN,  SMP_T_STR  },
 	{ "hex",     sample_conv_bin2hex,      0,                     NULL,                     SMP_T_BIN,  SMP_T_STR  },
 	{ "hex2i",   sample_conv_hex2int,      0,                     NULL,                     SMP_T_STR,  SMP_T_SINT },
 	{ "ipmask",  sample_conv_ipmask,       ARG2(1,MSK4,MSK6),     NULL,                     SMP_T_ADDR, SMP_T_IPV4 },
-- 
2.32.0

From e2edf7b89270d79887753b2410936c59bb786dc6 Mon Sep 17 00:00:00 2001
From: Marcin Deranek <[email protected]>
Date: Mon, 12 Jul 2021 14:16:55 +0200
Subject: [PATCH 1/5] MEDIUM: ssl: Capture more info from Client Hello

When we set tune.ssl.capture-cipherlist-size to a non-zero value
we are able to capture cipherlist supported by the client. To be able to
provide JA3 compatible TLS fingerprinting we need to capture more
information from Client Hello message:
- SSL Version
- SSL Extensions
- Elliptic Curves
- Elliptic Curve Point Formats
This patch allows HAProxy to capture such information and store it for
later use.
---
 doc/configuration.txt        |   7 +-
 include/haproxy/ssl_sock-t.h |  12 +++-
 src/ssl_sample.c             |   2 +-
 src/ssl_sock.c               | 129 ++++++++++++++++++++++++++++++++---
 4 files changed, 136 insertions(+), 14 deletions(-)

diff --git a/doc/configuration.txt b/doc/configuration.txt
index b128718cd..c3d640ab8 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -2783,9 +2783,10 @@ tune.ssl.ssl-ctx-cache-size <number>
   1000 entries.
 
 tune.ssl.capture-cipherlist-size <number>
-  Sets the maximum size of the buffer used for capturing client-hello cipher
-  list. If the value is 0 (default value) the capture is disabled, otherwise
-  a buffer is allocated for each SSL/TLS connection.
+  Sets the maximum size of the buffer used for capturing client hello cipher
+  list, extensions list, elliptic curves list and elliptic curve point
+  formats. If the value is 0 (default value) the capture is disabled,
+  otherwise a buffer is allocated for each SSL/TLS connection.
 
 tune.vars.global-max-size <size>
 tune.vars.proc-max-size <size>
diff --git a/include/haproxy/ssl_sock-t.h b/include/haproxy/ssl_sock-t.h
index 98390113f..e9fa3877b 100644
--- a/include/haproxy/ssl_sock-t.h
+++ b/include/haproxy/ssl_sock-t.h
@@ -202,8 +202,16 @@ struct ssl_sock_msg_callback {
 /* This memory pool is used for capturing clienthello parameters. */
 struct ssl_capture {
 	unsigned long long int xxh64;
-	unsigned char ciphersuite_len;
-	char ciphersuite[VAR_ARRAY];
+	unsigned short int protocol_version;
+	unsigned short int ciphersuite_len;
+	unsigned short int extensions_len;
+	unsigned short int ec_len;
+	unsigned int ciphersuite_offset;
+	unsigned int extensions_offset;
+	unsigned int ec_offset;
+	unsigned int ec_formats_offset;
+	unsigned char ec_formats_len;
+	char data[VAR_ARRAY];
 };
 
 #ifdef HAVE_SSL_KEYLOG
diff --git a/src/ssl_sample.c b/src/ssl_sample.c
index bfa61bdcd..3093a36e4 100644
--- a/src/ssl_sample.c
+++ b/src/ssl_sample.c
@@ -1145,7 +1145,7 @@ smp_fetch_ssl_fc_cl_bin(const struct arg *args, struct sample *smp, const char *
 
 	smp->flags = SMP_F_VOL_TEST | SMP_F_CONST;
 	smp->data.type = SMP_T_BIN;
-	smp->data.u.str.area = capture->ciphersuite;
+	smp->data.u.str.area = capture->data + capture->ciphersuite_offset;
 	smp->data.u.str.data = capture->ciphersuite_len;
 	return 1;
 }
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index e27611572..6fa67a81a 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -1652,7 +1652,16 @@ static void ssl_sock_parse_clienthello(struct connection *conn, int write_p, int
 	struct ssl_capture *capture;
 	unsigned char *msg;
 	unsigned char *end;
-	size_t rec_len;
+	unsigned char *extensions_end;
+	unsigned char *ec_start = NULL;
+	unsigned char *ec_formats_start = NULL;
+	unsigned char *list_end;
+	unsigned short int protocol_version;
+	unsigned short int extension_id;
+	unsigned short int ec_len = 0;
+	unsigned char ec_formats_len = 0;
+	int offset = 0;
+	int rec_len;
 
 	/* This function is called for "from client" and "to server"
 	 * connections. The combination of write_p == 0 and content_type == 22
@@ -1714,11 +1723,18 @@ static void ssl_sock_parse_clienthello(struct connection *conn, int write_p, int
 	if (end < msg)
 		return;
 
-	/* Expect 2 bytes for protocol version (1 byte for major and 1 byte
-	 * for minor, the random, composed by 4 bytes for the unix time and
-	 * 28 bytes for unix payload. So we jump 1 + 1 + 4 + 28.
+	/* Expect 2 bytes for protocol version
+	 * (1 byte for major and 1 byte for minor)
 	 */
-	msg += 1 + 1 + 4 + 28;
+	if (msg + 2 > end)
+		return;
+	protocol_version = (msg[0] << 8) + msg[1];
+	msg += 2;
+
+	/* Expect the random, composed by 4 bytes for the unix time and
+	 * 28 bytes for unix payload. So we jump 4 + 28.
+	 */
+	msg += 4 + 28;
 	if (msg > end)
 		return;
 
@@ -1747,10 +1763,107 @@ static void ssl_sock_parse_clienthello(struct connection *conn, int write_p, int
 	capture->xxh64 = XXH64(msg, rec_len, 0);
 
 	/* Capture the ciphersuite. */
-	capture->ciphersuite_len = (global_ssl.capture_cipherlist < rec_len) ?
-		global_ssl.capture_cipherlist : rec_len;
-	memcpy(capture->ciphersuite, msg, capture->ciphersuite_len);
+	capture->ciphersuite_len = MIN(global_ssl.capture_cipherlist, rec_len);
+	capture->ciphersuite_offset = 0;
+	memcpy(capture->data, msg, capture->ciphersuite_len);
+	msg += rec_len;
+	offset += capture->ciphersuite_len;
+
+	/* Initialize other data */
+	capture->protocol_version = protocol_version;
+	capture->extensions_len = 0;
+	capture->extensions_offset = 0;
+	capture->ec_len = 0;
+	capture->ec_offset = 0;
+	capture->ec_formats_len = 0;
+	capture->ec_formats_offset = 0;
+
+	/* Next, compression methods:
+	 * if present, we have to jump by length + 1 for the size information
+	 * if not present, we have to jump by 1 only
+	 */
+	if (msg[0] > 0)
+		msg += msg[0];
+	msg += 1;
+	if (msg > end)
+		goto store_capture;
 
+	/* We reached extensions */
+	if (msg + 2 > end)
+		goto store_capture;
+	rec_len = (msg[0] << 8) + msg[1];
+	msg += 2;
+	if (msg + rec_len > end || msg + rec_len < msg)
+		goto store_capture;
+	extensions_end = msg + rec_len;
+	capture->extensions_offset = offset;
+
+	/* Parse each extension */
+	while (msg + 4 < extensions_end) {
+		/* Add 2 bytes of extension_id */
+		if (global_ssl.capture_cipherlist >= offset + 2) {
+			capture->data[offset++] = msg[0];
+			capture->data[offset++] = msg[1];
+			capture->extensions_len += 2;
+		}
+		else
+			break;
+		extension_id = (msg[0] << 8) + msg[1];
+		/* Length of the extension */
+		rec_len = (msg[2] << 8) + msg[3];
+
+		/* Expect 2 bytes extension id + 2 bytes extension size */
+		msg += 2 + 2;
+		if (msg + rec_len > extensions_end || msg + rec_len < msg)
+			goto store_capture;
+		if (extension_id == 0x000a) {
+			/* Elliptic Curves */
+			list_end = msg + rec_len;
+			if (msg + 2 > list_end)
+				goto store_capture;
+			rec_len = (msg[0] << 8) + msg[1];
+			msg += 2;
+
+			if (msg + rec_len > list_end || msg + rec_len < msg)
+				goto store_capture;
+			/* Store location/size of the list */
+			ec_start = msg;
+			ec_len = rec_len;
+		}
+		else if (extension_id == 0x000b) {
+			/* Elliptic Curves Point Formats */
+			list_end = msg + rec_len;
+			if (msg + 1 > list_end)
+				goto store_capture;
+			rec_len = msg[0];
+			msg += 1;
+
+			if (msg + rec_len > list_end || msg + rec_len < msg)
+				goto store_capture;
+			/* Store location/size of the list */
+			ec_formats_start = msg;
+			ec_formats_len = rec_len;
+		}
+		msg += rec_len;
+	}
+
+	rec_len = MIN(global_ssl.capture_cipherlist - offset, ec_len);
+	if (ec_start) {
+		memcpy(capture->data + offset, ec_start, rec_len);
+		capture->ec_offset = offset;
+		capture->ec_len = rec_len;
+		offset += rec_len;
+	}
+
+	rec_len = MIN(global_ssl.capture_cipherlist - offset, ec_formats_len);
+	if (ec_formats_start) {
+		memcpy(capture->data + offset, ec_formats_start, rec_len);
+		capture->ec_formats_offset = offset;
+		capture->ec_formats_len = rec_len;
+		offset += rec_len;
+	}
+
+ store_capture:
 	SSL_set_ex_data(ssl, ssl_capture_ptr_index, capture);
 }
 
-- 
2.32.0

From 2ae787760200c0346cdfb55ba94aa282c421438a Mon Sep 17 00:00:00 2001
From: Marcin Deranek <[email protected]>
Date: Tue, 13 Jul 2021 19:04:24 +0200
Subject: [PATCH 5/5] MINOR: config: Deprecate tune.ssl.capture-cipherlist-size

Deprecate tune.ssl.capture-cipherlist-size in favor of
tune.ssl.capture-buffer-size which better describes the purpose of the
setting.
---
 .github/h2spec.config                   |  2 +-
 doc/configuration.txt                   | 22 ++++++++++++----------
 include/haproxy/ssl_sock-t.h            |  2 +-
 reg-tests/ssl/add_ssl_crt-list.vtc      |  2 +-
 reg-tests/ssl/del_ssl_crt-list.vtc      |  2 +-
 reg-tests/ssl/new_del_ssl_cafile.vtc    |  2 +-
 reg-tests/ssl/new_del_ssl_crlfile.vtc   |  2 +-
 reg-tests/ssl/set_ssl_cafile.vtc        |  2 +-
 reg-tests/ssl/set_ssl_cert.vtc          |  2 +-
 reg-tests/ssl/set_ssl_cert_bundle.vtc   |  2 +-
 reg-tests/ssl/set_ssl_cert_noext.vtc    |  2 +-
 reg-tests/ssl/set_ssl_crlfile.vtc       |  2 +-
 reg-tests/ssl/set_ssl_server_cert.vtc   |  2 +-
 reg-tests/ssl/show_ssl_ocspresponse.vtc |  2 +-
 reg-tests/ssl/ssl_client_samples.vtc    |  2 +-
 reg-tests/ssl/ssl_default_server.vtc    |  2 +-
 reg-tests/ssl/ssl_frontend_samples.vtc  |  2 +-
 reg-tests/ssl/ssl_server_samples.vtc    |  2 +-
 reg-tests/ssl/wrong_ctx_storage.vtc     |  2 +-
 src/cfgparse-ssl.c                      | 20 +++++++++++++-------
 src/ssl_sock.c                          | 12 ++++++------
 21 files changed, 49 insertions(+), 41 deletions(-)

diff --git a/.github/h2spec.config b/.github/h2spec.config
index f15045221..c38e7f7d6 100644
--- a/.github/h2spec.config
+++ b/.github/h2spec.config
@@ -2,7 +2,7 @@ global
     log stdout local0
     nbthread 4
     tune.ssl.default-dh-param 2048
-    tune.ssl.capture-cipherlist-size 1
+    tune.ssl.capture-buffer-size 1
 
 defaults
     mode http
diff --git a/doc/configuration.txt b/doc/configuration.txt
index d2958fc3a..269c412ba 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -1091,7 +1091,8 @@ The following keywords are supported in the "global" section :
    - tune.ssl.maxrecord
    - tune.ssl.default-dh-param
    - tune.ssl.ssl-ctx-cache-size
-   - tune.ssl.capture-cipherlist-size
+   - tune.ssl.capture-buffer-size
+   - tune.ssl.capture-cipherlist-size (deprecated)
    - tune.vars.global-max-size
    - tune.vars.proc-max-size
    - tune.vars.reqres-max-size
@@ -2782,7 +2783,8 @@ tune.ssl.ssl-ctx-cache-size <number>
   dynamically is expensive, they are cached. The default cache size is set to
   1000 entries.
 
-tune.ssl.capture-cipherlist-size <number>
+tune.ssl.capture-buffer-size <number>
+tune.ssl.capture-cipherlist-size <number> (deprecated)
   Sets the maximum size of the buffer used for capturing client hello cipher
   list, extensions list, elliptic curves list and elliptic curve point
   formats. If the value is 0 (default value) the capture is disabled,
@@ -18816,7 +18818,7 @@ ssl_fc_cipherlist_bin([<exclude_grease>]) : binary
   Returns the binary form of the client hello cipher list. Setting
   <exlude_grease> to 1 will exclude GREASE (RFC8701) values from the output.
   The maximum returned value length is limited by the shared capture buffer
-  size controlled by "tune.ssl.capture-cipherlist-size" setting.
+  size controlled by "tune.ssl.capture-buffer-size" setting.
 
   Example:
       http-request set-header X-SSL-JA3 %[ssl_fc_protocol_hello_id],\
@@ -18833,27 +18835,27 @@ ssl_fc_cipherlist_hex([<exclude_grease>]) : string
   Returns the binary form of the client hello cipher list encoded as
   hexadecimal. Setting <exlude_grease> to 1 will exclude GREASE (RFC8701)
   values from the output. The maximum returned value length is limited by the
-  shared capture buffer size controlled by "tune.ssl.capture-cipherlist-size"
+  shared capture buffer size controlled by "tune.ssl.capture-buffer-size"
   setting.
 
 ssl_fc_cipherlist_str([<exclude_grease>]) : string
   Returns the decoded text form of the client hello cipher list. Setting
   <exlude_grease> to 1 will exclude GREASE (RFC8701) values from the output.
   The maximum returned value length is limited by the shared capture buffer
-  size controlled by "tune.ssl.capture-cipherlist-size" setting. Note that
+  size controlled by "tune.ssl.capture-buffer-size" setting. Note that
   this sample-fetch is only available with OpenSSL >= 1.0.2. If the function
   is not enabled, this sample-fetch returns the hash like
   "ssl_fc_cipherlist_xxh".
 
 ssl_fc_cipherlist_xxh : integer
   Returns a xxh64 of the cipher list. This hash can return only if the value
-  "tune.ssl.capture-cipherlist-size" is set greater than 0, however the hash
+  "tune.ssl.capture-buffer-size" is set greater than 0, however the hash
   take into account all the data of the cipher list.
 
 ssl_fc_ecformats_bin : binary
   Return the binary form of the client hello supported elliptic curve point
   formats. The maximum returned value length is limited by the shared capture
-  buffer size controlled by "tune.ssl.capture-cipherlist-size" setting.
+  buffer size controlled by "tune.ssl.capture-buffer-size" setting.
 
   Example:
       http-request set-header X-SSL-JA3 %[ssl_fc_protocol_hello_id],\
@@ -18870,7 +18872,7 @@ ssl_fc_eclist_bin([<exclude_grease>]) : binary
   Returns the binary form of the client hello supported elliptic curves.
   Setting <exlude_grease> to 1 will exclude GREASE (RFC8701) values from the
   output. The maximum returned value length is limited by the shared capture
-  buffer size controlled by "tune.ssl.capture-cipherlist-size" setting.
+  buffer size controlled by "tune.ssl.capture-buffer-size" setting.
 
   Example:
       http-request set-header X-SSL-JA3 %[ssl_fc_protocol_hello_id],\
@@ -18887,7 +18889,7 @@ ssl_fc_extlist_bin([<exclude_grease>]) : binary
   Returns the binary form of the client hello extension list. Setting
   <exlude_grease> to 1 will exclude GREASE (RFC8701) values from the output.
   The maximum returned value length is limited by the shared capture buffer
-  size controlled by "tune.ssl.capture-cipherlist-size" setting.
+  size controlled by "tune.ssl.capture-buffer-size" setting.
 
   Example:
       http-request set-header X-SSL-JA3 %[ssl_fc_protocol_hello_id],\
@@ -18992,7 +18994,7 @@ ssl_fc_protocol : string
 ssl_fc_protocol_hello_id : integer
   The version of the TLS protocol by which the client wishes to communicate
   during the session as indicated in client hello message. This value can
-  return only if the value "tune.ssl.capture-cipherlist-size" is set greater
+  return only if the value "tune.ssl.capture-buffer-size" is set greater
   than 0.
 
   Example:
diff --git a/include/haproxy/ssl_sock-t.h b/include/haproxy/ssl_sock-t.h
index e9fa3877b..bda9a55b7 100644
--- a/include/haproxy/ssl_sock-t.h
+++ b/include/haproxy/ssl_sock-t.h
@@ -275,7 +275,7 @@ struct global_ssl {
 	unsigned int max_record; /* SSL max record size */
 	unsigned int default_dh_param; /* SSL maximum DH parameter size */
 	int ctx_cache; /* max number of entries in the ssl_ctx cache. */
-	int capture_cipherlist; /* Size of the cipherlist buffer. */
+	int capture_buffer_size; /* Size of the capture buffer. */
 	int keylog; /* activate keylog  */
 	int extra_files; /* which files not defined in the configuration file are we looking for */
 	int extra_files_noext; /* whether we remove the extension when looking up a extra file */
diff --git a/reg-tests/ssl/add_ssl_crt-list.vtc b/reg-tests/ssl/add_ssl_crt-list.vtc
index 7aae2338a..d3f8d7599 100644
--- a/reg-tests/ssl/add_ssl_crt-list.vtc
+++ b/reg-tests/ssl/add_ssl_crt-list.vtc
@@ -24,7 +24,7 @@ server s1 -repeat 2 {
 haproxy h1 -conf {
     global
         tune.ssl.default-dh-param 2048
-        tune.ssl.capture-cipherlist-size 1
+        tune.ssl.capture-buffer-size 1
         crt-base ${testdir}
         stats socket "${tmpdir}/h1/stats" level admin
 
diff --git a/reg-tests/ssl/del_ssl_crt-list.vtc b/reg-tests/ssl/del_ssl_crt-list.vtc
index 4bf89f9cc..4815e2de7 100644
--- a/reg-tests/ssl/del_ssl_crt-list.vtc
+++ b/reg-tests/ssl/del_ssl_crt-list.vtc
@@ -22,7 +22,7 @@ server s1 -repeat 2 {
 haproxy h1 -conf {
     global
         tune.ssl.default-dh-param 2048
-        tune.ssl.capture-cipherlist-size 1
+        tune.ssl.capture-buffer-size 1
         crt-base ${testdir}
         stats socket "${tmpdir}/h1/stats" level admin
 
diff --git a/reg-tests/ssl/new_del_ssl_cafile.vtc b/reg-tests/ssl/new_del_ssl_cafile.vtc
index 1b5bef1a4..b6cbc2064 100644
--- a/reg-tests/ssl/new_del_ssl_cafile.vtc
+++ b/reg-tests/ssl/new_del_ssl_cafile.vtc
@@ -22,7 +22,7 @@ server s1 -repeat 2 {
 haproxy h1 -conf {
     global
         tune.ssl.default-dh-param 2048
-        tune.ssl.capture-cipherlist-size 1
+        tune.ssl.capture-buffer-size 1
         stats socket "${tmpdir}/h1/stats" level admin
         crt-base ${testdir}
 
diff --git a/reg-tests/ssl/new_del_ssl_crlfile.vtc b/reg-tests/ssl/new_del_ssl_crlfile.vtc
index 54bbdc239..7330163c0 100644
--- a/reg-tests/ssl/new_del_ssl_crlfile.vtc
+++ b/reg-tests/ssl/new_del_ssl_crlfile.vtc
@@ -22,7 +22,7 @@ server s1 -repeat 3 {
 haproxy h1 -conf {
     global
         tune.ssl.default-dh-param 2048
-        tune.ssl.capture-cipherlist-size 1
+        tune.ssl.capture-buffer-size 1
         stats socket "${tmpdir}/h1/stats" level admin
         crt-base ${testdir}
 
diff --git a/reg-tests/ssl/set_ssl_cafile.vtc b/reg-tests/ssl/set_ssl_cafile.vtc
index 72ce3e6dc..0b5c3bac6 100644
--- a/reg-tests/ssl/set_ssl_cafile.vtc
+++ b/reg-tests/ssl/set_ssl_cafile.vtc
@@ -28,7 +28,7 @@ server s1 -repeat 4 {
 haproxy h1 -conf {
     global
         tune.ssl.default-dh-param 2048
-        tune.ssl.capture-cipherlist-size 1
+        tune.ssl.capture-buffer-size 1
         stats socket "${tmpdir}/h1/stats" level admin
 
     defaults
diff --git a/reg-tests/ssl/set_ssl_cert.vtc b/reg-tests/ssl/set_ssl_cert.vtc
index 85684bc3e..6938b20d9 100644
--- a/reg-tests/ssl/set_ssl_cert.vtc
+++ b/reg-tests/ssl/set_ssl_cert.vtc
@@ -33,7 +33,7 @@ server s1 -repeat 9 {
 haproxy h1 -conf {
     global
         tune.ssl.default-dh-param 2048
-        tune.ssl.capture-cipherlist-size 1
+        tune.ssl.capture-buffer-size 1
         stats socket "${tmpdir}/h1/stats" level admin
         crt-base ${testdir}
 
diff --git a/reg-tests/ssl/set_ssl_cert_bundle.vtc b/reg-tests/ssl/set_ssl_cert_bundle.vtc
index 218f7bfb4..11abdafdd 100644
--- a/reg-tests/ssl/set_ssl_cert_bundle.vtc
+++ b/reg-tests/ssl/set_ssl_cert_bundle.vtc
@@ -28,7 +28,7 @@ server s1 -repeat 9 {
 haproxy h1 -conf {
     global
         tune.ssl.default-dh-param 2048
-        tune.ssl.capture-cipherlist-size 1
+        tune.ssl.capture-buffer-size 1
         stats socket "${tmpdir}/h1/stats" level admin
         crt-base ${testdir}
 
diff --git a/reg-tests/ssl/set_ssl_cert_noext.vtc b/reg-tests/ssl/set_ssl_cert_noext.vtc
index b7bafa8a3..0947fe012 100644
--- a/reg-tests/ssl/set_ssl_cert_noext.vtc
+++ b/reg-tests/ssl/set_ssl_cert_noext.vtc
@@ -25,7 +25,7 @@ server s1 -repeat 3 {
 haproxy h1 -conf {
     global
         tune.ssl.default-dh-param 2048
-        tune.ssl.capture-cipherlist-size 1
+        tune.ssl.capture-buffer-size 1
         ssl-load-extra-del-ext
         stats socket "${tmpdir}/h1/stats" level admin
 
diff --git a/reg-tests/ssl/set_ssl_crlfile.vtc b/reg-tests/ssl/set_ssl_crlfile.vtc
index f6d97ce6b..ce83ff771 100644
--- a/reg-tests/ssl/set_ssl_crlfile.vtc
+++ b/reg-tests/ssl/set_ssl_crlfile.vtc
@@ -31,7 +31,7 @@ server s1 -repeat 4 {
 haproxy h1 -conf {
     global
         tune.ssl.default-dh-param 2048
-        tune.ssl.capture-cipherlist-size 1
+        tune.ssl.capture-buffer-size 1
         stats socket "${tmpdir}/h1/stats" level admin
 
     defaults
diff --git a/reg-tests/ssl/set_ssl_server_cert.vtc b/reg-tests/ssl/set_ssl_server_cert.vtc
index 3fccaa65c..880e7b0c9 100644
--- a/reg-tests/ssl/set_ssl_server_cert.vtc
+++ b/reg-tests/ssl/set_ssl_server_cert.vtc
@@ -17,7 +17,7 @@ server s1 -repeat 4 {
 haproxy h1 -conf {
     global
         tune.ssl.default-dh-param 2048
-        tune.ssl.capture-cipherlist-size 1
+        tune.ssl.capture-buffer-size 1
         stats socket "${tmpdir}/h1/stats" level admin
         nbthread 1
 
diff --git a/reg-tests/ssl/show_ssl_ocspresponse.vtc b/reg-tests/ssl/show_ssl_ocspresponse.vtc
index 387f36a2f..d2a934576 100644
--- a/reg-tests/ssl/show_ssl_ocspresponse.vtc
+++ b/reg-tests/ssl/show_ssl_ocspresponse.vtc
@@ -27,7 +27,7 @@ feature ignore_unknown_macro
 haproxy h1 -conf {
     global
         tune.ssl.default-dh-param 2048
-        tune.ssl.capture-cipherlist-size 1
+        tune.ssl.capture-buffer-size 1
         stats socket "${tmpdir}/h1/stats" level admin
 
     defaults
diff --git a/reg-tests/ssl/ssl_client_samples.vtc b/reg-tests/ssl/ssl_client_samples.vtc
index 83662be6d..2b6fd5b0b 100644
--- a/reg-tests/ssl/ssl_client_samples.vtc
+++ b/reg-tests/ssl/ssl_client_samples.vtc
@@ -13,7 +13,7 @@ server s1 -repeat 3 {
 haproxy h1 -conf {
     global
         tune.ssl.default-dh-param 2048
-        tune.ssl.capture-cipherlist-size 1
+        tune.ssl.capture-buffer-size 1
         crt-base ${testdir}
 
     defaults
diff --git a/reg-tests/ssl/ssl_default_server.vtc b/reg-tests/ssl/ssl_default_server.vtc
index 607225d51..32179b1f6 100644
--- a/reg-tests/ssl/ssl_default_server.vtc
+++ b/reg-tests/ssl/ssl_default_server.vtc
@@ -23,7 +23,7 @@ server s1 -repeat 7 {
 haproxy h1 -conf {
     global
         tune.ssl.default-dh-param 2048
-        tune.ssl.capture-cipherlist-size 1
+        tune.ssl.capture-buffer-size 1
         stats socket "${tmpdir}/h1/stats" level admin
         crt-base ${testdir}
         ca-base ${testdir}
diff --git a/reg-tests/ssl/ssl_frontend_samples.vtc b/reg-tests/ssl/ssl_frontend_samples.vtc
index 86851cf75..b62b4753f 100644
--- a/reg-tests/ssl/ssl_frontend_samples.vtc
+++ b/reg-tests/ssl/ssl_frontend_samples.vtc
@@ -13,7 +13,7 @@ server s1 -repeat 3 {
 haproxy h1 -conf {
     global
         tune.ssl.default-dh-param 2048
-        tune.ssl.capture-cipherlist-size 1
+        tune.ssl.capture-buffer-size 1
         crt-base ${testdir}
 
     defaults
diff --git a/reg-tests/ssl/ssl_server_samples.vtc b/reg-tests/ssl/ssl_server_samples.vtc
index 136ccafa4..17b1bc438 100644
--- a/reg-tests/ssl/ssl_server_samples.vtc
+++ b/reg-tests/ssl/ssl_server_samples.vtc
@@ -13,7 +13,7 @@ server s1 -repeat 3 {
 haproxy h1 -conf {
     global
         tune.ssl.default-dh-param 2048
-        tune.ssl.capture-cipherlist-size 1
+        tune.ssl.capture-buffer-size 1
         crt-base ${testdir}
         stats socket "${tmpdir}/h1/stats" level admin
 
diff --git a/reg-tests/ssl/wrong_ctx_storage.vtc b/reg-tests/ssl/wrong_ctx_storage.vtc
index 7dc7528a5..c6cb19ad5 100644
--- a/reg-tests/ssl/wrong_ctx_storage.vtc
+++ b/reg-tests/ssl/wrong_ctx_storage.vtc
@@ -25,7 +25,7 @@ feature ignore_unknown_macro
 haproxy h1 -conf {
   global
     tune.ssl.default-dh-param 2048
-    tune.ssl.capture-cipherlist-size 1
+    tune.ssl.capture-buffer-size 1
 
   listen frt
     mode http
diff --git a/src/cfgparse-ssl.c b/src/cfgparse-ssl.c
index 6df5911eb..0ca8cab31 100644
--- a/src/cfgparse-ssl.c
+++ b/src/cfgparse-ssl.c
@@ -272,8 +272,13 @@ static int ssl_parse_global_int(char **args, int section_type, struct proxy *cur
 		target = &global_ssl.ctx_cache;
 	else if (strcmp(args[0], "maxsslconn") == 0)
 		target = &global.maxsslconn;
-	else if (strcmp(args[0], "tune.ssl.capture-cipherlist-size") == 0)
-		target = &global_ssl.capture_cipherlist;
+	else if (strcmp(args[0], "tune.ssl.capture-buffer-size") == 0)
+		target = &global_ssl.capture_buffer_size;
+	else if (strcmp(args[0], "tune.ssl.capture-cipherlist-size") == 0) {
+		target = &global_ssl.capture_buffer_size;
+		ha_warning("parsing [%s:%d]: '%s' is deprecated and will be removed in version 2.7. Please use 'tune.ssl.capture-buffer-size' instead.\n",
+		           file, line, args[0]);
+	}
 	else {
 		memprintf(err, "'%s' keyword not unhandled (please report this bug).", args[0]);
 		return -1;
@@ -295,9 +300,9 @@ static int ssl_parse_global_int(char **args, int section_type, struct proxy *cur
 	return 0;
 }
 
-static int ssl_parse_global_capture_cipherlist(char **args, int section_type, struct proxy *curpx,
-                                               const struct proxy *defpx, const char *file, int line,
-                                               char **err)
+static int ssl_parse_global_capture_buffer(char **args, int section_type, struct proxy *curpx,
+                                           const struct proxy *defpx, const char *file, int line,
+                                           char **err)
 {
 	int ret;
 
@@ -310,7 +315,7 @@ static int ssl_parse_global_capture_cipherlist(char **args, int section_type, st
 		return -1;
 	}
 
-	pool_head_ssl_capture = create_pool("ssl-capture", sizeof(struct ssl_capture) + global_ssl.capture_cipherlist, MEM_F_SHARED);
+	pool_head_ssl_capture = create_pool("ssl-capture", sizeof(struct ssl_capture) + global_ssl.capture_buffer_size, MEM_F_SHARED);
 	if (!pool_head_ssl_capture) {
 		memprintf(err, "Out of memory error.");
 		return -1;
@@ -1946,7 +1951,8 @@ static struct cfg_kw_list cfg_kws = {ILH, {
 	{ CFG_GLOBAL, "tune.ssl.lifetime", ssl_parse_global_lifetime },
 	{ CFG_GLOBAL, "tune.ssl.maxrecord", ssl_parse_global_int },
 	{ CFG_GLOBAL, "tune.ssl.ssl-ctx-cache-size", ssl_parse_global_int },
-	{ CFG_GLOBAL, "tune.ssl.capture-cipherlist-size", ssl_parse_global_capture_cipherlist },
+	{ CFG_GLOBAL, "tune.ssl.capture-cipherlist-size", ssl_parse_global_capture_buffer },
+	{ CFG_GLOBAL, "tune.ssl.capture-buffer-size", ssl_parse_global_capture_buffer },
 	{ CFG_GLOBAL, "tune.ssl.keylog", ssl_parse_global_keylog },
 	{ CFG_GLOBAL, "ssl-default-bind-ciphers", ssl_parse_global_ciphers },
 	{ CFG_GLOBAL, "ssl-default-server-ciphers", ssl_parse_global_ciphers },
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index 6fa67a81a..d4b7c2c79 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -124,7 +124,7 @@ struct global_ssl global_ssl = {
 #endif
 	.default_dh_param = SSL_DEFAULT_DH_PARAM,
 	.ctx_cache = DEFAULT_SSL_CTX_CACHE,
-	.capture_cipherlist = 0,
+	.capture_buffer_size = 0,
 	.extra_files = SSL_GF_ALL,
 	.extra_files_noext = 0,
 #ifdef HAVE_SSL_KEYLOG
@@ -554,7 +554,7 @@ static int ssl_sock_register_msg_callbacks(void)
 	if (!ssl_sock_register_msg_callback(ssl_sock_parse_heartbeat))
 		return ERR_ABORT;
 #endif
-	if (global_ssl.capture_cipherlist > 0) {
+	if (global_ssl.capture_buffer_size > 0) {
 		if (!ssl_sock_register_msg_callback(ssl_sock_parse_clienthello))
 			return ERR_ABORT;
 	}
@@ -1763,7 +1763,7 @@ static void ssl_sock_parse_clienthello(struct connection *conn, int write_p, int
 	capture->xxh64 = XXH64(msg, rec_len, 0);
 
 	/* Capture the ciphersuite. */
-	capture->ciphersuite_len = MIN(global_ssl.capture_cipherlist, rec_len);
+	capture->ciphersuite_len = MIN(global_ssl.capture_buffer_size, rec_len);
 	capture->ciphersuite_offset = 0;
 	memcpy(capture->data, msg, capture->ciphersuite_len);
 	msg += rec_len;
@@ -1801,7 +1801,7 @@ static void ssl_sock_parse_clienthello(struct connection *conn, int write_p, int
 	/* Parse each extension */
 	while (msg + 4 < extensions_end) {
 		/* Add 2 bytes of extension_id */
-		if (global_ssl.capture_cipherlist >= offset + 2) {
+		if (global_ssl.capture_buffer_size >= offset + 2) {
 			capture->data[offset++] = msg[0];
 			capture->data[offset++] = msg[1];
 			capture->extensions_len += 2;
@@ -1847,7 +1847,7 @@ static void ssl_sock_parse_clienthello(struct connection *conn, int write_p, int
 		msg += rec_len;
 	}
 
-	rec_len = MIN(global_ssl.capture_cipherlist - offset, ec_len);
+	rec_len = MIN(global_ssl.capture_buffer_size - offset, ec_len);
 	if (ec_start) {
 		memcpy(capture->data + offset, ec_start, rec_len);
 		capture->ec_offset = offset;
@@ -1855,7 +1855,7 @@ static void ssl_sock_parse_clienthello(struct connection *conn, int write_p, int
 		offset += rec_len;
 	}
 
-	rec_len = MIN(global_ssl.capture_cipherlist - offset, ec_formats_len);
+	rec_len = MIN(global_ssl.capture_buffer_size - offset, ec_formats_len);
 	if (ec_formats_start) {
 		memcpy(capture->data + offset, ec_formats_start, rec_len);
 		capture->ec_formats_offset = offset;
-- 
2.32.0

Reply via email to