Hi Willy, Thank you for the reply.
On Tue, Aug 24, 2021 at 8:12 PM Willy Tarreau <[email protected]> wrote: > Hi Marcin, > > I'm finally back to your patch set! Overall that looks fine, but I have > some comments, mostly cosmetic. > > > From b3a254b41411f22307a622250a6e95ac39fefee8 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 > (...) > > > diff --git a/include/haproxy/ssl_sock-t.h b/include/haproxy/ssl_sock-t.h > > index 5acedcfc5..4b993894a 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; > > No need to mention "int" after "short" or "long", that's implicit. I > guess you did it because of the "unsigned long long int" above but that > is not needed. Since 2.4 we have shorter type names like "ushort", "uint", > "uchar" etc that we're progressively trying to standardize the new code > to, but they haven't slipped into older code yet. This could make sense > here since the whole structure is being replaced. But that's not an > obligation :-) > Converted all to new types, so we are future proof :-) > > diff --git a/src/ssl_sock.c b/src/ssl_sock.c > > index ba61243ac..2965a1446 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; > > Same here for the type names. > Also converted. > > @@ -1747,10 +1763,106 @@ 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; > > Given the number of fields to preset to zero, you should instead replace > the previous pool_alloc() call with a pool_zalloc(), it would be more > future-proof and safer against the risk of accidentally missing one > such field when more are added. > Understood. replaced pool_alloc() with pool_zalloc() are removed initialization of individual fields as they are not needed anymore. > > + /* 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 */ > > Could you put a link here to the registry where you found this definition ? > I guess it comes from an RFC or anything, but it does help when some > extensions are needed or when dealing with interoperability issues, to > figure if any other values need to be considered. > Added links to the list of known TLS extensions and to individual RFCs covering used extensions. > > + if (ec_start) { > > + rec_len = MIN(global_ssl.capture_cipherlist - offset, > ec_len); > > I'm not fond of using MIN() here as you're dealing with two different > types (one signed int due to the subtract, and an unsigned short). It > actually does the right thing but it's fragile. What about this: > > rec_len = ec_len; > if (offset + rec_len > global_ssl.capture_cipherlist) > rec_len = global_ssl.capture_cipherlist - offset; > Converted both cases. > > +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. > > I find that placing a lone boolean "1" here in the argument will complicate > future extensions. I'm not necessarily fond of placing bare optional words > either, but it's the fact that we're forcing a field for just one option, > with a value that's not obvious especially if we add others in the future, > when it could look like: > > ssl_fc_cipherlist_bin(0,,,1,2) > > Maybe a tiny variant which will leave the code as-is could be to just > rename the argument to "filter_option" and mention in the doc that > "filter_option" designates some optional filtering to perform on the > input before processing it, and that for now only two values are > permitted: > - 0 : take the full list, including GREASE if any, this is the default > - 1 : take the full list, excluding GREASE if any > > This way, depending on evolutions we may decide to either add new discrete > values or make some of them combinations. However it will be important that > the config parser rejects unsupported values. This should be the same for > all of those which support this exclude_grease argument. > Makes sense as it will make things potentially more extensible in the future. Replaced exclude_grease with filter_option > > + 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)] > > I thought we finally had an fmt-based "set-var" action, but looking > through the doc, it seems that I was wrong. I've probably got too much > used to rewriting it in todo lists :-( It would have made this slightly > cleaner by not relying on a header in my opinion. But that's not a big > deal. > Indeed, having ability to set variable from fmt would be very desirable and made it much cleaner. Personally I run into this limitation many times. The good side of this limitation is that you start become creative :-) > > + acl is_malware req.fhdr(x-ssl-ja3),digest(md5),hex,\ > > + map(/path/to/file/with/malware-ja3.lst) -m found > > Actually you're exactly reimplementing an ACL from a map :-) This > is equivalent to: > > acl is_malware req.fhdr(x-ssl-ja3),digest(md5),hex -f > /path/to/file/with/malware-ja3.lst > > Be careful, there are a few more occurrences in your other examples. > Updated all occurences. > > +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. > > OK > > > + If the function > > + is not enabled, this sample-fetch returns the hash like > > + "ssl_fc_cipherlist_xxh". > > Hmm let's avoid this. Better simply fail to parse it or kindly mention > "ssl_fc_cipherlist_str() requires OpenSSL >= 1.0.2" and be done with it. > Otherwise it will result in users mistakenly using it for their hash, and > getting breakage when upgrading. It looks convenient at first but it is > not because we must tell users how not to shoot themselves in the foot. > I don't know what was the reasoning to fallback to xxh64 hash when OpenSSL call to convert ciphers to strings is not available. Presumably we want to have something if certain OpenSSL functionality is not available. > > 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. > > Just curious, why the full one requires OpenSSL >= 1.0.2 and not this one ? > Maybe it's just the conversion to strings that only exists since 1.0.2 ? > I would say so - xxh64 takes binary input and makes hash out of it. Conversion to string requires specific OpenSSL call: https://www.openssl.org/docs/man1.0.2/man3/SSL_CIPHER_get_name.html > > 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); > > I'm fine with having such a function in ssl_sample.c or something like > this, but it should not be in tools with this name for the reason that > "GREASE" is a generic term that is here applied to TLS, and what it > does precisely is to deal with this. Maybe it's even just one aspect > of greasing TLS fields, I don't know. Thus generally speaking please > make sure it's as precise as possible (e.g. if RFC8701 mentions other > parts supporting GREASE outside of the fields supported here), and > move it in the SSL parts. > Moved to ssl_utils.c and renamed to exclude_tls_grease. > > diff --git a/src/ssl_sample.c b/src/ssl_sample.c > > index a21dcc16f..1eecf61bf 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) { > > You're adding arguments to this sample fetch, please add a comment above > the function to enumerate them. As a general rule, whenever a sample fetch > function or a converter makes use of arguments, please always enumerate > them in comments before the function and indicate their various validity > domains. When troubleshooting it's a real pain to have no idea what the > function expects. And please also recall it in front of the parsing > function (for the same reasons, especially as it helps seeing that an > argument is not initialized or any such issue). > Added arguments in comments and their types or values expected to all converters / fetchers added or modified. > > + Example: > > + bin(01020304050607),be2dec(:,2) # 258:772:1286:7 > > + bin(01020304050607),be2dec(-,2,1) # 258-772-1286 > > + bin(01020304050607),be2dec(,2,1) # 2587721286 > > + > > This looks pretty versatile :-) I'm figuring that we could even > use it to turn plain binary into IPv4 addresses: > > bin(7f000001),be2dec(.,1) # 127.0.0.1 > Or IPv6 :-)) BTW: Added your example as well.. > > + while (ptr < last && trash->data <= max_size) { > > + start = trash->data; > > + if (ptr) { > > + /* Separator */ > > I guess you meant that you want to add a separator, but my interpretation > here was that you found one (while the code disagreed). Please simply > update the comment accordingly. > Updated. > > + 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) > > That code above is compact and it's not immediately obvious there's a loop > embedded inside. Please try to leave some blank lines around logical > "blocks" > (with "block" being something everyone sees a different definition for). I > would personally skip one line before and after the loop to isolate it a > little bit but it's a matter of taste. > Added blank lines around while loop. > Exact same comments as for be2dec. > Done for both be2dec and be2hex. > Thus overall it looks pretty good to me. I'm really sorry to have > missed your first resend a month ago, I'll be more careful this time. > Do not hesitate to Cc me with the next series! > No worries. Hopefully soon this will get merged. Attaching latest patches with all modification included. Regards, Marcin Deranek
From e9a0001779497a1504942f866a831153351956e7 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 | 60 +++++++++++++++++++++++++++++++++ src/sample.c | 61 ++++++++++++++++++++++++++++++++++ 3 files changed, 135 insertions(+) create mode 100644 reg-tests/converter/be2hex.vtc diff --git a/doc/configuration.txt b/doc/configuration.txt index cbc967a7c..9c1e2b229 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -16105,6 +16105,20 @@ be2dec(<separator>,<chunk_size>,[<truncate>]) bin(01020304050607),be2dec(,2,1) # 2587721286 bin(7f000001),be2dec(.,1) # 127.0.0.1 +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..86e504292 --- /dev/null +++ b/reg-tests/converter/be2hex.vtc @@ -0,0 +1,60 @@ +varnishtest "be2hex converter Test" + +feature cmd "$HAPROXY_PROGRAM -cc 'version_atleast(2.5-dev0)'" +feature ignore_unknown_macro + +server s1 { + rxreq + txresp +} -repeat 3 -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:" + rxresp + expect resp.status == 200 + expect resp.http.be2hex == "" + expect resp.http.be2hex-1 == "" + expect resp.http.be2hex-2 == "" + expect resp.http.be2hex-3 == "" + 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 b354fc7bb..6f0ddee20 100644 --- a/src/sample.c +++ b/src/sample.c @@ -2123,6 +2123,66 @@ 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; +} + +/* Converts big-endian binary input sample to a hex string containing two hex + * digits per input byte. <separator> is put every <chunk_size> binary input + * bytes if specified. Optional <truncate> flag indicates if input is truncated + * at <chunk_size> boundaries. + * Arguments: separator (string), chunk_size (integer), truncate (0,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) { + /* Add 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; + + /* Add 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(); @@ -4318,6 +4378,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.33.0
From 7b506a792b87246ffe0f2dd8a25cccb48c2b8054 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 | 123 ++++++++++++++++++++++++---- include/haproxy/ssl_utils.h | 1 + src/ssl_sample.c | 154 ++++++++++++++++++++++++++++++++++-- src/ssl_utils.c | 19 +++++ 4 files changed, 276 insertions(+), 21 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index 9a8d97d0e..ba6727a53 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -18875,27 +18875,103 @@ 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([<filter_option>]) : binary + Returns the binary form of the client hello cipher list. The maximum + returned value length is limited by the shared capture buffer size + controlled by "tune.ssl.capture-cipherlist-size" setting. Setting + <filter_option> allows to filter returned data. Accepted values: + 0 : return the full list of ciphers (default) + 1 : exclude GREASE (RFC8701) values from the output -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 \ + -f /path/to/file/with/malware-ja3.lst + http-request set-header X-Malware True if is_malware + http-request set-header X-Malware False if !is_malware + +ssl_fc_cipherlist_hex([<filter_option>]) : 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 + hexadecimal. The maximum returned value length is limited by the shared + capture buffer size controlled by "tune.ssl.capture-cipherlist-size" + setting. Setting <filter_option> allows to filter returned data. Accepted + values: + 0 : return the full list of ciphers (default) + 1 : exclude GREASE (RFC8701) values from the output + +ssl_fc_cipherlist_str([<filter_option>]) : 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". + returned value length is limited by the shared capture buffer size + controlled by "tune.ssl.capture-cipherlist-size" setting. Setting + <filter_option> allows to filter returned data. Accepted values: + 0 : return the full list of ciphers (default) + 1 : exclude GREASE (RFC8701) values from the output + 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 \ + -f /path/to/file/with/malware-ja3.lst + http-request set-header X-Malware True if is_malware + http-request set-header X-Malware False if !is_malware + +ssl_fc_eclist_bin([<filter_option>]) : binary + Returns the binary form of the client hello supported elliptic curves. The + maximum returned value length is limited by the shared capture buffer size + controlled by "tune.ssl.capture-cipherlist-size" setting. Setting + <filter_option> allows to filter returned data. Accepted values: + 0 : return the full list of supported elliptic curves (default) + 1 : exclude GREASE (RFC8701) values from the output + + 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 \ + -f /path/to/file/with/malware-ja3.lst + http-request set-header X-Malware True if is_malware + http-request set-header X-Malware False if !is_malware + +ssl_fc_extlist_bin([<filter_option>]) : binary + Returns the binary form of the client hello extension list. The maximum + returned value length is limited by the shared capture buffer size + controlled by "tune.ssl.capture-cipherlist-size" setting. Setting + <filter_option> allows to filter returned data. Accepted values: + 0 : return the full list of extensions (default) + 1 : exclude GREASE (RFC8701) values from the output + + 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 \ + -f /path/to/file/with/malware-ja3.lst + 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 @@ -19005,6 +19081,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 \ + -f /path/to/file/with/malware-ja3.lst + 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/ssl_utils.h b/include/haproxy/ssl_utils.h index 9851e8a36..e14aaf1c6 100644 --- a/include/haproxy/ssl_utils.h +++ b/include/haproxy/ssl_utils.h @@ -40,6 +40,7 @@ int ssl_sock_get_dn_formatted(X509_NAME *a, const struct buffer *format, struct int ssl_sock_get_dn_oneline(X509_NAME *a, struct buffer *out); X509* ssl_sock_get_peer_certificate(SSL *ssl); unsigned int openssl_version_parser(const char *version); +void exclude_tls_grease(char *input, int len, struct buffer *output); #endif /* _HAPROXY_SSL_UTILS_H */ #endif /* USE_OPENSSL */ diff --git a/src/ssl_sample.c b/src/ssl_sample.c index 9f041ad8e..aa9a547e4 100644 --- a/src/ssl_sample.c +++ b/src/ssl_sample.c @@ -1127,9 +1127,13 @@ smp_fetch_ssl_fc_sni(const struct arg *args, struct sample *smp, const char *kw, } #endif +/* binary, returns tls client hello cipher list. + * Arguments: filter_option (0,1) + */ 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,13 +1147,26 @@ 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_tls_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; } +/* binary, returns tls client hello cipher list as hexadecimal string. + * Arguments: filter_option (0,1) + */ static int smp_fetch_ssl_fc_cl_hex(const struct arg *args, struct sample *smp, const char *kw, void *private) { @@ -1166,6 +1183,7 @@ smp_fetch_ssl_fc_cl_hex(const struct arg *args, struct sample *smp, const char * return 1; } +/* integer, returns xxh64 hash of tls client hello cipher list. */ static int smp_fetch_ssl_fc_cl_xxh64(const struct arg *args, struct sample *smp, const char *kw, void *private) { @@ -1213,6 +1231,28 @@ smp_fetch_ssl_fc_hsk_err(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_hsk_err_str(const struct arg *args, struct sample *smp, const char *kw, void *private) { @@ -1243,6 +1283,104 @@ smp_fetch_ssl_fc_hsk_err_str(const struct arg *args, struct sample *smp, const c return 1; } +/* binary, returns tls client hello extensions list. + * Arguments: filter_option (0,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_tls_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; +} + +/* binary, returns tls client hello supported elliptic curves. + * Arguments: filter_option (0,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_tls_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; +} + +/* binary, returns tls client hello supported elliptic curve point formats */ +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) @@ -1597,12 +1735,16 @@ 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_hsk_err", smp_fetch_ssl_fc_hsk_err, 0, NULL, SMP_T_SINT, SMP_USE_L5CLI }, { "ssl_fc_hsk_err_str", smp_fetch_ssl_fc_hsk_err_str, 0, NULL, SMP_T_STR, 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/ssl_utils.c b/src/ssl_utils.c index 578212182..35c06f73d 100644 --- a/src/ssl_utils.c +++ b/src/ssl_utils.c @@ -397,3 +397,22 @@ unsigned int openssl_version_parser(const char *version) return 0; } + +/* Exclude GREASE (RFC8701) values from input buffer */ +void exclude_tls_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]; +} -- 2.33.0
From ae476fcd3894c579346c66eaaecd29a66ef95e4e 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 | 13 +++++++ reg-tests/converter/be2dec.vtc | 56 +++++++++++++++++++++++++++++ src/sample.c | 65 ++++++++++++++++++++++++++++++++++ 3 files changed, 134 insertions(+) create mode 100644 reg-tests/converter/be2dec.vtc diff --git a/doc/configuration.txt b/doc/configuration.txt index ba6727a53..cbc967a7c 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -16092,6 +16092,19 @@ 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 big-endian 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 + bin(7f000001),be2dec(.,1) # 127.0.0.1 + 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..bdb903523 --- /dev/null +++ b/reg-tests/converter/be2dec.vtc @@ -0,0 +1,56 @@ +varnishtest "be2dec converter Test" + +feature cmd "$HAPROXY_PROGRAM -cc 'version_atleast(2.5-dev0)'" +feature ignore_unknown_macro + +server s1 { + rxreq + txresp +} -repeat 3 -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:" + rxresp + expect resp.status == 200 + expect resp.http.be2dec-1 == "" + expect resp.http.be2dec-2 == "" + expect resp.http.be2dec-3 == "" + 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 c71256363..b354fc7bb 100644 --- a/src/sample.c +++ b/src/sample.c @@ -2059,6 +2059,70 @@ 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; +} + +/* Converts big-endian binary input sample to a string containing an unsigned + * integer number per <chunk_size> input bytes separated with <separator>. + * Optional <truncate> flag indicates if input is truncated at <chunk_size> + * boundaries. + * Arguments: separator (string), chunk_size (integer), truncate (0,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) { + /* Add 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; + + /* Add 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(); @@ -4253,6 +4317,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.33.0
From ebc63cb1f03291e55b95c35bcd1a5f9961b9e2f6 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 | 14 +++- src/ssl_sample.c | 2 +- src/ssl_sock.c | 137 ++++++++++++++++++++++++++++++++--- 4 files changed, 142 insertions(+), 18 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index 9b794de12..9a8d97d0e 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -2806,9 +2806,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 5acedcfc5..321d7b7ed 100644 --- a/include/haproxy/ssl_sock-t.h +++ b/include/haproxy/ssl_sock-t.h @@ -201,9 +201,17 @@ 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]; + ullong xxh64; + ushort protocol_version; + ushort ciphersuite_len; + ushort extensions_len; + ushort ec_len; + uint ciphersuite_offset; + uint extensions_offset; + uint ec_offset; + uint ec_formats_offset; + uchar ec_formats_len; + char data[VAR_ARRAY]; }; #ifdef HAVE_SSL_KEYLOG diff --git a/src/ssl_sample.c b/src/ssl_sample.c index f4e0c93a8..9f041ad8e 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 83003d9d0..b8914a78d 100644 --- a/src/ssl_sock.c +++ b/src/ssl_sock.c @@ -1682,9 +1682,18 @@ static void ssl_sock_parse_clienthello(struct connection *conn, int write_p, int SSL *ssl) { struct ssl_capture *capture; - unsigned char *msg; - unsigned char *end; - size_t rec_len; + uchar *msg; + uchar *end; + uchar *extensions_end; + uchar *ec_start = NULL; + uchar *ec_formats_start = NULL; + uchar *list_end; + ushort protocol_version; + ushort extension_id; + ushort ec_len = 0; + uchar 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 @@ -1746,11 +1755,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; @@ -1772,17 +1788,116 @@ static void ssl_sock_parse_clienthello(struct connection *conn, int write_p, int if (msg + rec_len > end || msg + rec_len < msg) return; - capture = pool_alloc(pool_head_ssl_capture); + capture = pool_zalloc(pool_head_ssl_capture); if (!capture) return; /* Compute the xxh64 of the ciphersuite. */ 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; + /* 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; + /* TLS Extensions + * https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml */ + if (extension_id == 0x000a) { + /* Elliptic Curves: + * https://www.rfc-editor.org/rfc/rfc8422.html + * https://www.rfc-editor.org/rfc/rfc7919.html */ + 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: + * https://www.rfc-editor.org/rfc/rfc8422.html */ + 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; + } + + if (ec_start) { + rec_len = ec_len; + if (offset + rec_len > global_ssl.capture_cipherlist) + rec_len = global_ssl.capture_cipherlist - offset; + memcpy(capture->data + offset, ec_start, rec_len); + capture->ec_offset = offset; + capture->ec_len = rec_len; + offset += rec_len; + } + if (ec_formats_start) { + rec_len = ec_formats_len; + if (offset + rec_len > global_ssl.capture_cipherlist) + rec_len = global_ssl.capture_cipherlist - offset; + 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.33.0
From 72a026117d289a88d20905e68fa3dadc317bb62c 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 | 29 +++++++++++++------------ 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_errors.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 | 16 +++++++------- 22 files changed, 55 insertions(+), 48 deletions(-) diff --git a/.github/h2spec.config b/.github/h2spec.config index a7b34ce80..745a637e1 100644 --- a/.github/h2spec.config +++ b/.github/h2spec.config @@ -1,7 +1,7 @@ global log stdout local0 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 9c1e2b229..bac18ab35 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -1110,7 +1110,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 @@ -2805,7 +2806,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, @@ -18905,7 +18907,7 @@ ssl_fc_cipher : string ssl_fc_cipherlist_bin([<filter_option>]) : binary Returns the binary form of the client hello cipher list. The maximum returned value length is limited by the shared capture buffer size - controlled by "tune.ssl.capture-cipherlist-size" setting. Setting + controlled by "tune.ssl.capture-buffer-size" setting. Setting <filter_option> allows to filter returned data. Accepted values: 0 : return the full list of ciphers (default) 1 : exclude GREASE (RFC8701) values from the output @@ -18924,16 +18926,15 @@ ssl_fc_cipherlist_bin([<filter_option>]) : binary ssl_fc_cipherlist_hex([<filter_option>]) : string Returns the binary form of the client hello cipher list encoded as hexadecimal. The maximum returned value length is limited by the shared - capture buffer size controlled by "tune.ssl.capture-cipherlist-size" - setting. Setting <filter_option> allows to filter returned data. Accepted - values: + capture buffer size controlled by "tune.ssl.capture-buffer-size" setting. + Setting <filter_option> allows to filter returned data. Accepted values: 0 : return the full list of ciphers (default) 1 : exclude GREASE (RFC8701) values from the output ssl_fc_cipherlist_str([<filter_option>]) : string Returns the decoded text form of the client hello cipher list. The maximum returned value length is limited by the shared capture buffer size - controlled by "tune.ssl.capture-cipherlist-size" setting. Setting + controlled by "tune.ssl.capture-buffer-size" setting. Setting <filter_option> allows to filter returned data. Accepted values: 0 : return the full list of ciphers (default) 1 : exclude GREASE (RFC8701) values from the output @@ -18943,13 +18944,13 @@ ssl_fc_cipherlist_str([<filter_option>]) : string 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 - take into account all the data of the cipher list. + "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],\ @@ -18965,7 +18966,7 @@ ssl_fc_ecformats_bin : binary ssl_fc_eclist_bin([<filter_option>]) : binary Returns the binary form of the client hello supported elliptic curves. The maximum returned value length is limited by the shared capture buffer size - controlled by "tune.ssl.capture-cipherlist-size" setting. Setting + controlled by "tune.ssl.capture-buffer-size" setting. Setting <filter_option> allows to filter returned data. Accepted values: 0 : return the full list of supported elliptic curves (default) 1 : exclude GREASE (RFC8701) values from the output @@ -18984,7 +18985,7 @@ ssl_fc_eclist_bin([<filter_option>]) : binary ssl_fc_extlist_bin([<filter_option>]) : binary Returns the binary form of the client hello extension list. The maximum returned value length is limited by the shared capture buffer size - controlled by "tune.ssl.capture-cipherlist-size" setting. Setting + controlled by "tune.ssl.capture-buffer-size" setting. Setting <filter_option> allows to filter returned data. Accepted values: 0 : return the full list of extensions (default) 1 : exclude GREASE (RFC8701) values from the output @@ -19111,8 +19112,8 @@ 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 - than 0. + return only if the value "tune.ssl.capture-buffer-size" is set greater than + 0. Example: http-request set-header X-SSL-JA3 %[ssl_fc_protocol_hello_id],\ diff --git a/include/haproxy/ssl_sock-t.h b/include/haproxy/ssl_sock-t.h index 321d7b7ed..f3ed90982 100644 --- a/include/haproxy/ssl_sock-t.h +++ b/include/haproxy/ssl_sock-t.h @@ -276,7 +276,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_errors.vtc b/reg-tests/ssl/ssl_errors.vtc index 0d652d4a0..d8fea4321 100644 --- a/reg-tests/ssl/ssl_errors.vtc +++ b/reg-tests/ssl/ssl_errors.vtc @@ -106,7 +106,7 @@ syslog Slg_logconnerror -level info { 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_frontend_samples.vtc b/reg-tests/ssl/ssl_frontend_samples.vtc index bca085674..92eec6ac1 100644 --- a/reg-tests/ssl/ssl_frontend_samples.vtc +++ b/reg-tests/ssl/ssl_frontend_samples.vtc @@ -12,7 +12,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 b8914a78d..ae30c2cb4 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 @@ -556,7 +556,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; } @@ -1795,7 +1795,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; @@ -1827,7 +1827,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; @@ -1880,8 +1880,8 @@ static void ssl_sock_parse_clienthello(struct connection *conn, int write_p, int if (ec_start) { rec_len = ec_len; - if (offset + rec_len > global_ssl.capture_cipherlist) - rec_len = global_ssl.capture_cipherlist - offset; + if (offset + rec_len > global_ssl.capture_buffer_size) + rec_len = global_ssl.capture_buffer_size - offset; memcpy(capture->data + offset, ec_start, rec_len); capture->ec_offset = offset; capture->ec_len = rec_len; @@ -1889,8 +1889,8 @@ static void ssl_sock_parse_clienthello(struct connection *conn, int write_p, int } if (ec_formats_start) { rec_len = ec_formats_len; - if (offset + rec_len > global_ssl.capture_cipherlist) - rec_len = global_ssl.capture_cipherlist - offset; + if (offset + rec_len > global_ssl.capture_buffer_size) + rec_len = global_ssl.capture_buffer_size - offset; memcpy(capture->data + offset, ec_formats_start, rec_len); capture->ec_formats_offset = offset; capture->ec_formats_len = rec_len; -- 2.33.0

