Add new sample fetches to get the ciphers, supported groups, key shares and signature algorithms that the client supports during a TLS handshake as part of the contents of a TLS ClientHello. Currently we can get the following contents of the ClientHello message: SNI(req_ssl_sni) and TLS protocol version(req_ssl_ver). The following new sample fetches will be added to get the following contents of the ClientHello message exchanged during the TLS handshake (supported by the client): - req.ssl_cipherlist: Returns the binary form of the list of symmetric cipher options - req.ssl_sigalgs: Returns the binary form of the list of signature algorithms - req.ssl_keyshare_groups: Return the binary format of the list of key share group - req.ssl_supported_groups: Returns the binary form of the list of supported groups used in key exchange
This added functionality would allow routing with fine granularity pending the capabilities the client indicates in the ClientHello. For example, this would allow the ability to enable TLS passthrough or TLS termination based on the supported groups detected in the ClientHello message. Another usage is to take client key shares into consideration when deciding which of the client supported groups should be used for groups considered to have 'equal security level' as well as enabling fine grain selection of certificate types(beyond the RSA vs ECC distinction). All of that is relevant in the context of rapidly upcoming PQC operation modes. Fixes: #2532 --- doc/configuration.txt | 66 ++ reg-tests/checks/tcp-check-client-hello.vtc | 82 +++ src/payload.c | 697 ++++++++++---------- 3 files changed, 479 insertions(+), 366 deletions(-) create mode 100644 reg-tests/checks/tcp-check-client-hello.vtc diff --git a/doc/configuration.txt b/doc/configuration.txt index da9d8b540..ab39da283 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -25057,6 +25057,10 @@ req_ssl_sni string req.ssl_st_ext integer req.ssl_ver integer req_ssl_ver integer +req.ssl_cipherlist binary +req.ssl_sigalgs binary +req.ssl_keyshare_groups binary +req.ssl_supported_groups binary res.len integer res.payload(<offset>,<length>) binary res.payload_lv(<offset1>,<length>[,<offset2>]) binary @@ -25261,6 +25265,68 @@ req_ssl_sni : string (deprecated) use_backend bk_allow if { req.ssl_sni -f allowed_sites } default_backend bk_sorry_page +req.ssl_cipherlist binary + Returns the binary form of the list of symmetric cipher options supported by + the client as reported in the contents of a TLS ClientHello. Note that this + only applies to raw contents found in the request buffer and not to contents + deciphered via an SSL data layer, so this will not work with "bind" lines + having the "ssl" option. Refer to "ssl_fc_cipherlist_bin" which is the SSL + bind equivalent that can be used when the "ssl" option is specified. + + Examples : + # Wait for a client hello for at most 5 seconds + tcp-request inspect-delay 5s + tcp-request content accept if { req.ssl_hello_type 1 } + use-server fe3 if { req.ssl_cipherlist,be2hex(:,2),lower -m sub 1302:009f } + server fe3 ${htst_fe3_addr}:${htst_fe3_port} + +req.ssl_sigalgs binary + Returns the binary form of the list of signature algorithms supported by the + client as reported in the TLS ClientHello. This is available as a client hello + extension. Note that this only applies to raw contents found in the request + buffer and not to contents deciphered via an SSL data layer, so this will not + work with "bind" lines having the "ssl" option. Refer to "ssl_fc_sigalgs_bin" + which is the SSL bind equivalent that can be used when the "ssl" option is + specified. + + Examples : + # Wait for a client hello for at most 5 seconds + tcp-request inspect-delay 5s + tcp-request content accept if { req.ssl_hello_type 1 } + use-server fe4 if { req.ssl_sigalgs,be2hex(:,2),lower -m sub 0403:0805 } + server fe4 ${htst_fe4_addr}:${htst_fe4_port} + +req.ssl_keyshare_groups binary + Return the binary format of the list of cryptographic parameters for key exchange + supported by the client as reported in the TLS ClientHello. In TLS v1.3, keyshare + is part of the ClientHello message and is the final client hello extension. Note + that this only applies to raw contents found in the request buffer and not to + contents deciphered via an SSL data layer, so this will not work with "bind" + lines having the "ssl" option. + + Examples : + # Wait for a client hello for at most 5 seconds + tcp-request inspect-delay 5s + tcp-request content accept if { req.ssl_hello_type 1 } + use-server fe3 if { req.ssl_keyshare_groups,be2hex(:,2),lower -m sub 001d } + server fe3 ${htst_fe3_addr}:${htst_fe3_port} + +req.ssl_supported_groups binary + Returns the binary form of the list of supported groups supported by the client + as reported in the TLS ClientHello and used for key exchange which can include + both elliptic curve and non-EC key exchange. Note that this only applies to raw + contents found in the request buffer and not to contents deciphered via an SSL + data layer, so this will not work with "bind" lines having the "ssl" option. + Refer to "ssl_fc_eclist_bin" hich is the SSL bind equivalent that can be used + when the "ssl" option is specified. + + Examples : + # Wait for a client hello for at most 5 seconds + tcp-request inspect-delay 5s + tcp-request content accept if { req.ssl_hello_type 1 } + use-server fe3 if { req.ssl_supported_groups, be2hex(:,2),lower -m sub 0017 } + server fe3 ${htst_fe3_addr}:${htst_fe3_port} + req.ssl_st_ext : integer Returns 0 if the client didn't send a SessionTicket TLS Extension (RFC5077) Returns 1 if the client sent SessionTicket TLS Extension diff --git a/reg-tests/checks/tcp-check-client-hello.vtc b/reg-tests/checks/tcp-check-client-hello.vtc new file mode 100644 index 000000000..137361c65 --- /dev/null +++ b/reg-tests/checks/tcp-check-client-hello.vtc @@ -0,0 +1,82 @@ +#REQUIRE_OPTION=OPENSSL +#REGTEST_TYPE=slow +#EXCLUDE_TARGETS=osx,generic + +varnishtest "Health checks: test enhanced observability of TLS ClientHello" +feature cmd "$HAPROXY_PROGRAM -cc 'feature(OPENSSL) && !ssllib_name_startswith(wolfSSL) && !ssllib_name_startswith(LibreSSL) && openssl_version_atleast(1.1.1)'" +feature ignore_unknown_macro + +syslog S_ok -level notice { + recv + expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Health check for server be[0-9]+/srv succeeded, reason: Layer6 check passed.+check duration: [[:digit:]]+ms, status: 1/1 UP." + recv + expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Health check for server be[0-9]+/srv succeeded, reason: Layer6 check passed.+check duration: [[:digit:]]+ms, status: 1/1 UP." + recv + expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Health check for server be[0-9]+/srv succeeded, reason: Layer6 check passed.+check duration: [[:digit:]]+ms, status: 1/1 UP." +} -start + +haproxy htst -conf { + global + ssl-default-bind-options ssl-min-ver TLSv1.2 ssl-max-ver TLSv1.3 + + defaults + log global + option tcplog + timeout client "${HAPROXY_TEST_TIMEOUT-5s}" + timeout server "${HAPROXY_TEST_TIMEOUT-5s}" + timeout connect "${HAPROXY_TEST_TIMEOUT-5s}" + + listen li1 + mode tcp + bind "fd@${li1}" + tcp-request inspect-delay 100ms + + acl check_sig_algs req.ssl_sigalgs,be2hex(:,2),lower -m found + acl check_key_shares req.ssl_keyshare_groups,be2hex(:,2),lower -m found + tcp-request content accept if check_sig_algs + tcp-request content accept if check_key_shares + + # Refer to https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.8 && https://tls13.xargs.org/#client-hello/annotated to get the binary values + use-server fe3 if { req.ssl_cipherlist,be2hex(:,2),lower -m sub 1302:1303:1301:009f } || { req.ssl_supported_groups, be2hex(:,2),lower -m sub 001d } + server fe3 ${htst_fe3_addr}:${htst_fe3_port} + + use-server fe1 if { req.ssl_supported_groups, be2hex(:,2),lower -m sub 0017 } + server fe1 ${htst_fe1_addr}:${htst_fe1_port} + + frontend fe1 + bind "fd@${fe1}" ssl crt ${testdir}/common.pem curves P-256:P-384 + + frontend fe3 + bind "fd@${fe3}" ssl crt ${testdir}/common.pem +} -start + +haproxy h1 -conf { + defaults + mode tcp + timeout client "${HAPROXY_TEST_TIMEOUT-5s}" + timeout server "${HAPROXY_TEST_TIMEOUT-5s}" + timeout connect "${HAPROXY_TEST_TIMEOUT-5s}" + + backend be1 + mode tcp + log ${S_ok_addr}:${S_ok_port} daemon + option log-health-checks + option tcp-check + server srv ${htst_li1_addr}:${htst_li1_port} check inter 1s rise 1 fall 1 check-ssl verify none curves X25519 + + backend be2 + mode tcp + log ${S_ok_addr}:${S_ok_port} daemon + option log-health-checks + option tcp-check + server srv ${htst_li1_addr}:${htst_li1_port} check inter 1s rise 1 fall 1 check-ssl verify none curves P-256:P-384 + + backend be3 + mode tcp + log ${S_ok_addr}:${S_ok_port} daemon + option log-health-checks + option tcp-check + server srv ${htst_li1_addr}:${htst_li1_port} check inter 1s rise 1 fall 1 check-ssl verify none ciphers DHE-RSA-AES256-GCM-SHA384 +} -start + +syslog S_ok -wait diff --git a/src/payload.c b/src/payload.c index 6a536d719..bb1970c8b 100644 --- a/src/payload.c +++ b/src/payload.c @@ -31,6 +31,19 @@ /* All supported sample fetch functions must be declared here */ /************************************************************************/ +int NOT_SSL_HELLO = 1; +int TOO_SHORT = 2; + +/** + * struct to hold variables used during generic client hello parsing + */ +struct clnt_hello_proc { + int hs_len; + int ext_len; + int status; + unsigned char *data; +}; + /* wait for more data as long as possible, then return TRUE. This should be * used with content inspection. */ @@ -75,18 +88,44 @@ smp_fetch_len(const struct arg *args, struct sample *smp, const char *kw, void * return 1; } -/* Returns 0 if the client didn't send a SessionTicket Extension - * Returns 1 if the client sent SessionTicket Extension - * Returns 2 if the client also sent non-zero length SessionTicket - * Returns SMP_T_SINT data type +/* Extract information presented in a TLS client hello handshake message. + * The format of the message is the following (cf RFC5246 + RFC6066) : + * TLS frame : + * - uint8 type = 0x16 (Handshake) + * - uint16 version >= 0x0301 (TLSv1) + * - uint16 length (frame length) + * - TLS handshake : + * - uint8 msg_type = 0x01 (ClientHello) + * - uint24 length (handshake message length) + * - ClientHello : + * - uint16 client_version >= 0x0301 (TLSv1) + * - uint8 Random[32] (4 first ones are timestamp) + * - SessionID : + * - uint8 session_id_len (0..32) (SessionID len in bytes) + * - uint8 session_id[session_id_len] + * - CipherSuite : + * - uint16 cipher_len >= 2 (Cipher length in bytes) + * - uint16 ciphers[cipher_len/2] + * - CompressionMethod : + * - uint8 compression_len >= 1 (# of supported methods) + * - uint8 compression_methods[compression_len] + * - optional client_extension_len (in bytes) + * - optional sequence of ClientHelloExtensions (as many bytes as above): + * - uint16 extension_type = 0 for server_name + * - uint16 extension_len + * - opaque extension_data[extension_len] + * - uint16 server_name_list_len (# of bytes here) + * - opaque server_names[server_name_list_len bytes] + * - uint8 name_type = 0 for host_name + * - uint16 name_len + * - opaque hostname[name_len bytes] */ -static int -smp_fetch_req_ssl_st_ext(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - int hs_len, ext_len, bleft; +struct clnt_hello_proc smp_client_hello_parse( struct sample *smp, int processCompressionMethods ) { + int hs_len, ext_len, bleft, status=0; struct channel *chn; + struct clnt_hello_proc cp; unsigned char *data; - + if (!smp->strm) goto not_ssl_hello; @@ -94,8 +133,7 @@ smp_fetch_req_ssl_st_ext(const struct arg *args, struct sample *smp, const char if (IS_HTX_STRM(smp->strm)) goto not_ssl_hello; - chn = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_RES) ? &smp->strm->res : &smp->strm->req; - + chn = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_RES) ? &smp->strm->res : &smp->strm->req; bleft = ci_data(chn); data = (unsigned char *)ci_head(chn); @@ -103,7 +141,7 @@ smp_fetch_req_ssl_st_ext(const struct arg *args, struct sample *smp, const char /* Check for SSL/TLS Handshake */ if (!bleft) goto too_short; - if (*data != 0x16) + if(*data != 0x16) goto not_ssl_hello; /* Check for SSLv3 or later (SSL version >= 3.0) in the record layer*/ @@ -114,10 +152,11 @@ smp_fetch_req_ssl_st_ext(const struct arg *args, struct sample *smp, const char if (bleft < 5) goto too_short; + hs_len = (data[3] << 8) + data[4]; if (hs_len < 1 + 3 + 2 + 32 + 1 + 2 + 2 + 1 + 1 + 2 + 2) goto not_ssl_hello; /* too short to have an extension */ - + data += 5; /* enter TLS handshake */ bleft -= 5; @@ -130,6 +169,7 @@ smp_fetch_req_ssl_st_ext(const struct arg *args, struct sample *smp, const char /* Check the Hello's length */ if (bleft < 4) goto too_short; + hs_len = (data[1] << 16) + (data[2] << 8) + data[3]; if (hs_len < 2 + 32 + 1 + 2 + 2 + 1 + 1 + 2 + 2) goto not_ssl_hello; /* too short to have an extension */ @@ -156,32 +196,64 @@ smp_fetch_req_ssl_st_ext(const struct arg *args, struct sample *smp, const char ext_len > hs_len) goto not_ssl_hello; - /* Jump to the compression methods */ - hs_len -= 2 + ext_len; - data += 2 + ext_len; + /* Jump to the compression methods. For fetching cipher list this processing is not required. */ + if (processCompressionMethods == 1) { + hs_len -= 2 + ext_len; + data += 2 + ext_len; - if (hs_len < 2 || /* minimum one compression method */ + if (hs_len < 2 || /* minimum one compression method */ data[0] < 1 || data[0] > hs_len) /* minimum 1 bytes for a method */ - goto not_ssl_hello; + goto not_ssl_hello; - /* Jump to the extensions */ - hs_len -= 1 + data[0]; - data += 1 + data[0]; + /* Jump to the extensions */ + hs_len -= 1 + data[0]; + data += 1 + data[0]; - if (hs_len < 2 || /* minimum one extension list length */ + if (hs_len < 2 || /* minimum one extension list length */ (ext_len = (data[0] << 8) + data[1]) > hs_len - 2) /* list too long */ - goto not_ssl_hello; + goto not_ssl_hello; - hs_len = ext_len; /* limit ourselves to the extension length */ - data += 2; + hs_len = ext_len; /* limit ourselves to the extension length */ + data += 2; + } + cp.hs_len = hs_len; + cp.ext_len = ext_len; + cp.data = data; + cp.status = status; + return cp; + + not_ssl_hello: + cp.status = NOT_SSL_HELLO; + + too_short: + cp.status = TOO_SHORT; + + return cp; +} - while (hs_len >= 4) { +/* Returns 0 if the client didn't send a SessionTicket Extension + * Returns 1 if the client sent SessionTicket Extension + * Returns 2 if the client also sent non-zero length SessionTicket + * Returns SMP_T_SINT data type + */ +static int +smp_fetch_req_ssl_st_ext(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct clnt_hello_proc cp; + + cp = smp_client_hello_parse(smp, 1); + if (cp.status == NOT_SSL_HELLO) + goto not_ssl_hello; + else if (cp.status == TOO_SHORT) + goto too_short; + + while (cp.hs_len >= 4) { int ext_type, ext_len; - ext_type = (data[0] << 8) + data[1]; - ext_len = (data[2] << 8) + data[3]; + ext_type = (cp.data[0] << 8) + cp.data[1]; + ext_len = (cp.data[2] << 8) + cp.data[3]; - if (ext_len > hs_len - 4) /* Extension too long */ + if (ext_len > cp.hs_len - 4) /* Extension too long */ goto not_ssl_hello; /* SesstionTicket extension */ @@ -197,8 +269,8 @@ smp_fetch_req_ssl_st_ext(const struct arg *args, struct sample *smp, const char return 1; } - hs_len -= 4 + ext_len; - data += 4 + ext_len; + cp.hs_len -= 4 + ext_len; + cp.data += 4 + ext_len; } /* SessionTicket Extension not found */ smp->data.type = SMP_T_SINT; @@ -219,103 +291,21 @@ smp_fetch_req_ssl_st_ext(const struct arg *args, struct sample *smp, const char static int smp_fetch_req_ssl_ec_ext(const struct arg *args, struct sample *smp, const char *kw, void *private) { - int hs_len, ext_len, bleft; - struct channel *chn; - unsigned char *data; + struct clnt_hello_proc cp; - if (!smp->strm) - goto not_ssl_hello; - - /* meaningless for HTX buffers */ - if (IS_HTX_STRM(smp->strm)) - goto not_ssl_hello; - - chn = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_RES) ? &smp->strm->res : &smp->strm->req; - bleft = ci_data(chn); - data = (unsigned char *)ci_head(chn); - - /* Check for SSL/TLS Handshake */ - if (!bleft) - goto too_short; - if (*data != 0x16) - goto not_ssl_hello; - - /* Check for SSLv3 or later (SSL version >= 3.0) in the record layer*/ - if (bleft < 3) + cp = smp_client_hello_parse(smp, 1); + if (cp.status == NOT_SSL_HELLO) + goto not_ssl_hello; + else if (cp.status == TOO_SHORT) goto too_short; - if (data[1] < 0x03) - goto not_ssl_hello; - if (bleft < 5) - goto too_short; - hs_len = (data[3] << 8) + data[4]; - if (hs_len < 1 + 3 + 2 + 32 + 1 + 2 + 2 + 1 + 1 + 2 + 2) - goto not_ssl_hello; /* too short to have an extension */ - - data += 5; /* enter TLS handshake */ - bleft -= 5; - - /* Check for a complete client hello starting at <data> */ - if (bleft < 1) - goto too_short; - if (data[0] != 0x01) /* msg_type = Client Hello */ - goto not_ssl_hello; - - /* Check the Hello's length */ - if (bleft < 4) - goto too_short; - hs_len = (data[1] << 16) + (data[2] << 8) + data[3]; - if (hs_len < 2 + 32 + 1 + 2 + 2 + 1 + 1 + 2 + 2) - goto not_ssl_hello; /* too short to have an extension */ - - /* We want the full handshake here */ - if (bleft < hs_len) - goto too_short; - - data += 4; - /* Start of the ClientHello message */ - if (data[0] < 0x03 || data[1] < 0x01) /* TLSv1 minimum */ - goto not_ssl_hello; - - ext_len = data[34]; /* session_id_len */ - if (ext_len > 32 || ext_len > (hs_len - 35)) /* check for correct session_id len */ - goto not_ssl_hello; - - /* Jump to cipher suite */ - hs_len -= 35 + ext_len; - data += 35 + ext_len; - - if (hs_len < 4 || /* minimum one cipher */ - (ext_len = (data[0] << 8) + data[1]) < 2 || /* minimum 2 bytes for a cipher */ - ext_len > hs_len) - goto not_ssl_hello; - - /* Jump to the compression methods */ - hs_len -= 2 + ext_len; - data += 2 + ext_len; - - if (hs_len < 2 || /* minimum one compression method */ - data[0] < 1 || data[0] > hs_len) /* minimum 1 bytes for a method */ - goto not_ssl_hello; - - /* Jump to the extensions */ - hs_len -= 1 + data[0]; - data += 1 + data[0]; - - if (hs_len < 2 || /* minimum one extension list length */ - (ext_len = (data[0] << 8) + data[1]) > hs_len - 2) /* list too long */ - goto not_ssl_hello; - - hs_len = ext_len; /* limit ourselves to the extension length */ - data += 2; - - while (hs_len >= 4) { + while (cp.hs_len >= 4) { int ext_type, ext_len; - ext_type = (data[0] << 8) + data[1]; - ext_len = (data[2] << 8) + data[3]; + ext_type = (cp.data[0] << 8) + cp.data[1]; + ext_len = (cp.data[2] << 8) + cp.data[3]; - if (ext_len > hs_len - 4) /* Extension too long */ + if (ext_len > cp.hs_len - 4) /* Extension too long */ goto not_ssl_hello; /* Elliptic curves extension */ @@ -326,8 +316,8 @@ smp_fetch_req_ssl_ec_ext(const struct arg *args, struct sample *smp, const char return 1; } - hs_len -= 4 + ext_len; - data += 4 + ext_len; + cp.hs_len -= 4 + ext_len; + cp.data += 4 + ext_len; } /* server name not found */ goto not_ssl_hello; @@ -336,9 +326,9 @@ smp_fetch_req_ssl_ec_ext(const struct arg *args, struct sample *smp, const char smp->flags = SMP_F_MAY_CHANGE; not_ssl_hello: - return 0; } + /* returns the type of SSL hello message (mainly used to detect an SSL hello) */ static int smp_fetch_ssl_hello_type(const struct arg *args, struct sample *smp, const char *kw, void *private) @@ -522,163 +512,246 @@ smp_fetch_req_ssl_ver(const struct arg *args, struct sample *smp, const char *kw return 0; } -/* Try to extract the Server Name Indication that may be presented in a TLS - * client hello handshake message. The format of the message is the following - * (cf RFC5246 + RFC6066) : - * TLS frame : - * - uint8 type = 0x16 (Handshake) - * - uint16 version >= 0x0301 (TLSv1) - * - uint16 length (frame length) - * - TLS handshake : - * - uint8 msg_type = 0x01 (ClientHello) - * - uint24 length (handshake message length) - * - ClientHello : - * - uint16 client_version >= 0x0301 (TLSv1) - * - uint8 Random[32] (4 first ones are timestamp) - * - SessionID : - * - uint8 session_id_len (0..32) (SessionID len in bytes) - * - uint8 session_id[session_id_len] - * - CipherSuite : - * - uint16 cipher_len >= 2 (Cipher length in bytes) - * - uint16 ciphers[cipher_len/2] - * - CompressionMethod : - * - uint8 compression_len >= 1 (# of supported methods) - * - uint8 compression_methods[compression_len] - * - optional client_extension_len (in bytes) - * - optional sequence of ClientHelloExtensions (as many bytes as above): - * - uint16 extension_type = 0 for server_name - * - uint16 extension_len - * - opaque extension_data[extension_len] - * - uint16 server_name_list_len (# of bytes here) - * - opaque server_names[server_name_list_len bytes] - * - uint8 name_type = 0 for host_name - * - uint16 name_len - * - opaque hostname[name_len bytes] +/* + * Extract the ciphers that may be presented in a TLS client hello handshake message. */ static int -smp_fetch_ssl_hello_sni(const struct arg *args, struct sample *smp, const char *kw, void *private) +smp_fetch_ssl_cipherlist(const struct arg *args, struct sample *smp, const char *kw, void *private) { - int hs_len, ext_len, bleft; - struct channel *chn; - unsigned char *data; + struct clnt_hello_proc cp; - if (!smp->strm) - goto not_ssl_hello; + cp = smp_client_hello_parse(smp, 0); + if (cp.status == NOT_SSL_HELLO) + goto not_ssl_hello; + else if (cp.status == TOO_SHORT) + goto too_short; - /* meaningless for HTX buffers */ - if (IS_HTX_STRM(smp->strm)) - goto not_ssl_hello; + smp->data.type = SMP_T_BIN; + smp->data.u.str.area = (char *)cp.data + 2; + smp->data.u.str.data = cp.ext_len; + smp->flags = SMP_F_VOLATILE | SMP_F_CONST; - chn = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_RES) ? &smp->strm->res : &smp->strm->req; - bleft = ci_data(chn); - data = (unsigned char *)ci_head(chn); + return 1; - /* Check for SSL/TLS Handshake */ - if (!bleft) - goto too_short; - if (*data != 0x16) - goto not_ssl_hello; + too_short: + smp->flags = SMP_F_MAY_CHANGE; - /* Check for SSLv3 or later (SSL version >= 3.0) in the record layer*/ - if (bleft < 3) - goto too_short; - if (data[1] < 0x03) - goto not_ssl_hello; + not_ssl_hello: + return 0; +} - if (bleft < 5) +/* + * Extract the supported group that may be presented in a TLS client hello handshake + */ +static int +smp_fetch_ssl_supported_groups(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct clnt_hello_proc cp; + cp = smp_client_hello_parse(smp, 1); + if (cp.status == NOT_SSL_HELLO) + goto not_ssl_hello; + else if (cp.status == TOO_SHORT) goto too_short; - hs_len = (data[3] << 8) + data[4]; - if (hs_len < 1 + 3 + 2 + 32 + 1 + 2 + 2 + 1 + 1 + 2 + 2) - goto not_ssl_hello; /* too short to have an extension */ - data += 5; /* enter TLS handshake */ - bleft -= 5; + while (cp.hs_len >= 4) { + int ext_type, ext_len, grp_len; - /* Check for a complete client hello starting at <data> */ - if (bleft < 1) - goto too_short; - if (data[0] != 0x01) /* msg_type = Client Hello */ - goto not_ssl_hello; + ext_type = (cp.data[0] << 8) + cp.data[1]; /* Extension type */ + ext_len = (cp.data[2] << 8) + cp.data[3]; /* Extension length */ - /* Check the Hello's length */ - if (bleft < 4) - goto too_short; - hs_len = (data[1] << 16) + (data[2] << 8) + data[3]; - if (hs_len < 2 + 32 + 1 + 2 + 2 + 1 + 1 + 2 + 2) - goto not_ssl_hello; /* too short to have an extension */ + if (ext_len > cp.hs_len - 4) /* Extension too long */ + goto not_ssl_hello; - /* We want the full handshake here */ - if (bleft < hs_len) + if (ext_type == 10) { /* Supported groups extension type ID is 10dec */ + if (ext_len < 2) /* need at least one entry of 2 bytes in the list length */ + goto not_ssl_hello; + + grp_len = (cp.data[4] << 8) + cp.data[5]; /* Supported group list length */ + if (grp_len < 2 || grp_len > cp.hs_len - 6) + goto not_ssl_hello; /* at least 2 bytes per supported group */ + + smp->data.type = SMP_T_BIN; + smp->data.u.str.area = (char *)cp.data + 6; + smp->data.u.str.data = grp_len; + smp->flags = SMP_F_VOL_SESS | SMP_F_CONST; + + return 1; + } + cp.hs_len -= 4 + ext_len; + cp.data += 4 + ext_len; + } + /* supported groups not found */ + goto not_ssl_hello; + + too_short: + smp->flags = SMP_F_MAY_CHANGE; + + not_ssl_hello: + return 0; +} + +/* + * Extract the signature algorithms that may be presented in a TLS client hello + * handshake message. + */ +static int +smp_fetch_ssl_sigalgs(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct clnt_hello_proc cp; + cp = smp_client_hello_parse(smp, 1); + if (cp.status == NOT_SSL_HELLO) + goto not_ssl_hello; + else if (cp.status == TOO_SHORT) goto too_short; - data += 4; - /* Start of the ClientHello message */ - if (data[0] < 0x03 || data[1] < 0x01) /* TLSv1 minimum */ - goto not_ssl_hello; + while (cp.hs_len >= 4) { + int ext_type, ext_len, sigalg_len; - ext_len = data[34]; /* session_id_len */ - if (ext_len > 32 || ext_len > (hs_len - 35)) /* check for correct session_id len */ - goto not_ssl_hello; + ext_type = (cp.data[0] << 8) + cp.data[1]; /* Extension type */ + ext_len = (cp.data[2] << 8) + cp.data[3]; /* Extension length */ - /* Jump to cipher suite */ - hs_len -= 35 + ext_len; - data += 35 + ext_len; + if (ext_len > cp.hs_len - 4) /* Extension too long */ + goto not_ssl_hello; - if (hs_len < 4 || /* minimum one cipher */ - (ext_len = (data[0] << 8) + data[1]) < 2 || /* minimum 2 bytes for a cipher */ - ext_len > hs_len) - goto not_ssl_hello; + if (ext_type == 13) { /* Sigalgs extension type ID is 13dec */ + if (ext_len < 2) /* need at least one entry of 2 bytes in the list length */ + goto not_ssl_hello; - /* Jump to the compression methods */ - hs_len -= 2 + ext_len; - data += 2 + ext_len; + sigalg_len = (cp.data[4] << 8) + cp.data[5]; /* Sigalgs list length */ + if (sigalg_len < 2 || sigalg_len > cp.hs_len - 6) + goto not_ssl_hello; /* at least 2 bytes per sigalg */ - if (hs_len < 2 || /* minimum one compression method */ - data[0] < 1 || data[0] > hs_len) /* minimum 1 bytes for a method */ - goto not_ssl_hello; + smp->data.type = SMP_T_BIN; + smp->data.u.str.area = (char *)cp.data + 6; + smp->data.u.str.data = sigalg_len; + smp->flags = SMP_F_VOLATILE | SMP_F_CONST; - /* Jump to the extensions */ - hs_len -= 1 + data[0]; - data += 1 + data[0]; + return 1; - if (hs_len < 2 || /* minimum one extension list length */ - (ext_len = (data[0] << 8) + data[1]) > hs_len - 2) /* list too long */ - goto not_ssl_hello; + } + cp.hs_len -= 4 + ext_len; + cp.data += 4 + ext_len; + } + /* sigalgs not found */ + goto not_ssl_hello; - hs_len = ext_len; /* limit ourselves to the extension length */ - data += 2; + too_short: + smp->flags = SMP_F_MAY_CHANGE; - while (hs_len >= 4) { - int ext_type, name_type, srv_len, name_len; + not_ssl_hello: + return 0; +} - ext_type = (data[0] << 8) + data[1]; - ext_len = (data[2] << 8) + data[3]; +/* + * Extract the key shares that may be presented in a TLS client hello handshake message. + */ +static int +smp_fetch_ssl_keyshare_groups(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + int readPosition, numberOfKeyshares; + struct buffer *smp_trash = NULL; + unsigned char *dataPointer; + struct clnt_hello_proc cp; + + cp = smp_client_hello_parse(smp, 1); + if (cp.status == NOT_SSL_HELLO) + goto not_ssl_hello; + else if (cp.status == TOO_SHORT) + goto too_short; - if (ext_len > hs_len - 4) /* Extension too long */ + while (cp.hs_len >= 4) { + int ext_type, ext_len, keyshare_len; + + ext_type = (cp.data[0] << 8) + cp.data[1]; /* Extension type */ + ext_len = (cp.data[2] << 8) + cp.data[3]; /* Extension length */ + + if (ext_len > cp.hs_len - 4) /* Extension too long */ + goto not_ssl_hello; + + if (ext_type == 51) { /* Keyshare extension type ID is 51dec */ + if (ext_len < 2) /* need at least one entry of 2 bytes in the list length */ + goto not_ssl_hello; + + keyshare_len = (cp.data[4] << 8) + cp.data[5]; /* Client keyshare length */ + if (keyshare_len < 2 || keyshare_len > cp.hs_len - 6) + goto not_ssl_hello; /* at least 2 bytes per keyshare */ + + dataPointer = cp.data + 6; /* start of keyshare entries */ + readPosition = 0; + numberOfKeyshares = 0; + smp_trash = get_trash_chunk(); + while (readPosition < keyshare_len) { + /* Get the binary value of the keyshare group and move the offset to the end of the related keyshare */ + memmove(b_orig(smp_trash) + (2*numberOfKeyshares), &dataPointer[readPosition], 2); + numberOfKeyshares++; + readPosition += ((int)dataPointer[readPosition+2] << 8) + (int)dataPointer[readPosition+3] + 4; + } + smp->data.type = SMP_T_BIN; + /*smp->data.u.str.area = (char *)&smp->data.u.str.bin;*/ + smp->data.u.str.area = smp_trash->area; + smp->data.u.str.data = 2*numberOfKeyshares; + smp->flags = SMP_F_VOLATILE | SMP_F_CONST; + + return 1; + } + cp.hs_len -= 4 + ext_len; + cp.data += 4 + ext_len; + } + /* keyshare groups not found */ + goto not_ssl_hello; + + too_short: + smp->flags = SMP_F_MAY_CHANGE; + not_ssl_hello: + return 0; +} + +/* + * Try to extract the Server Name Indication that may be presented in a TLS + * client hello handshake message. + */ +static int +smp_fetch_ssl_hello_sni(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct clnt_hello_proc cp; + + cp = smp_client_hello_parse(smp, 1); + if (cp.status == NOT_SSL_HELLO) + goto not_ssl_hello; + else if (cp.status == TOO_SHORT) + goto too_short; + + while (cp.hs_len >= 4) { + int ext_len, ext_type, name_type, srv_len, name_len; + + ext_type = (cp.data[0] << 8) + cp.data[1]; + ext_len = (cp.data[2] << 8) + cp.data[3]; + + if (ext_len > cp.hs_len - 4) /* Extension too long */ goto not_ssl_hello; if (ext_type == 0) { /* Server name */ if (ext_len < 2) /* need one list length */ goto not_ssl_hello; - srv_len = (data[4] << 8) + data[5]; - if (srv_len < 4 || srv_len > hs_len - 6) + srv_len = (cp.data[4] << 8) + cp.data[5]; + if (srv_len < 4 || srv_len > cp.hs_len - 6) goto not_ssl_hello; /* at least 4 bytes per server name */ - name_type = data[6]; - name_len = (data[7] << 8) + data[8]; + name_type = cp.data[6]; + name_len = (cp.data[7] << 8) + cp.data[8]; if (name_type == 0) { /* hostname */ smp->data.type = SMP_T_STR; - smp->data.u.str.area = (char *)data + 9; + smp->data.u.str.area = (char *)cp.data + 9; smp->data.u.str.data = name_len; smp->flags = SMP_F_VOLATILE | SMP_F_CONST; return 1; } } - hs_len -= 4 + ext_len; - data += 4 + ext_len; + cp.hs_len -= 4 + ext_len; + cp.data += 4 + ext_len; } /* server name not found */ goto not_ssl_hello; @@ -687,144 +760,32 @@ smp_fetch_ssl_hello_sni(const struct arg *args, struct sample *smp, const char * smp->flags = SMP_F_MAY_CHANGE; not_ssl_hello: - return 0; } /* Try to extract the Application-Layer Protocol Negotiation (ALPN) protocol * names that may be presented in a TLS client hello handshake message. As the * message presents a list of protocol names in descending order of preference, - * it may return iteratively. The format of the message is the following - * (cf RFC5246 + RFC7301) : - * TLS frame : - * - uint8 type = 0x16 (Handshake) - * - uint16 version >= 0x0301 (TLSv1) - * - uint16 length (frame length) - * - TLS handshake : - * - uint8 msg_type = 0x01 (ClientHello) - * - uint24 length (handshake message length) - * - ClientHello : - * - uint16 client_version >= 0x0301 (TLSv1) - * - uint8 Random[32] (4 first ones are timestamp) - * - SessionID : - * - uint8 session_id_len (0..32) (SessionID len in bytes) - * - uint8 session_id[session_id_len] - * - CipherSuite : - * - uint16 cipher_len >= 2 (Cipher length in bytes) - * - uint16 ciphers[cipher_len/2] - * - CompressionMethod : - * - uint8 compression_len >= 1 (# of supported methods) - * - uint8 compression_methods[compression_len] - * - optional client_extension_len (in bytes) - * - optional sequence of ClientHelloExtensions (as many bytes as above): - * - uint16 extension_type = 16 for application_layer_protocol_negotiation - * - uint16 extension_len - * - opaque extension_data[extension_len] - * - uint16 protocol_names_len (# of bytes here) - * - opaque protocol_names[protocol_names_len bytes] - * - uint8 name_len - * - opaque protocol_name[name_len bytes] + * it may return iteratively. */ static int smp_fetch_ssl_hello_alpn(const struct arg *args, struct sample *smp, const char *kw, void *private) { - int hs_len, ext_len, bleft; - struct channel *chn; - unsigned char *data; - - if (!smp->strm) - goto not_ssl_hello; - - /* meaningless for HTX buffers */ - if (IS_HTX_STRM(smp->strm)) - goto not_ssl_hello; + struct clnt_hello_proc cp; - chn = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_RES) ? &smp->strm->res : &smp->strm->req; - bleft = ci_data(chn); - data = (unsigned char *)ci_head(chn); - - /* Check for SSL/TLS Handshake */ - if (!bleft) - goto too_short; - if (*data != 0x16) - goto not_ssl_hello; - - /* Check for SSLv3 or later (SSL version >= 3.0) in the record layer*/ - if (bleft < 3) + cp = smp_client_hello_parse(smp, 1); + if (cp.status == NOT_SSL_HELLO) + goto not_ssl_hello; + else if (cp.status == TOO_SHORT) goto too_short; - if (data[1] < 0x03) - goto not_ssl_hello; - - if (bleft < 5) - goto too_short; - hs_len = (data[3] << 8) + data[4]; - if (hs_len < 1 + 3 + 2 + 32 + 1 + 2 + 2 + 1 + 1 + 2 + 2) - goto not_ssl_hello; /* too short to have an extension */ - - data += 5; /* enter TLS handshake */ - bleft -= 5; - - /* Check for a complete client hello starting at <data> */ - if (bleft < 1) - goto too_short; - if (data[0] != 0x01) /* msg_type = Client Hello */ - goto not_ssl_hello; - - /* Check the Hello's length */ - if (bleft < 4) - goto too_short; - hs_len = (data[1] << 16) + (data[2] << 8) + data[3]; - if (hs_len < 2 + 32 + 1 + 2 + 2 + 1 + 1 + 2 + 2) - goto not_ssl_hello; /* too short to have an extension */ - - /* We want the full handshake here */ - if (bleft < hs_len) - goto too_short; - - data += 4; - /* Start of the ClientHello message */ - if (data[0] < 0x03 || data[1] < 0x01) /* TLSv1 minimum */ - goto not_ssl_hello; - - ext_len = data[34]; /* session_id_len */ - if (ext_len > 32 || ext_len > (hs_len - 35)) /* check for correct session_id len */ - goto not_ssl_hello; - - /* Jump to cipher suite */ - hs_len -= 35 + ext_len; - data += 35 + ext_len; - - if (hs_len < 4 || /* minimum one cipher */ - (ext_len = (data[0] << 8) + data[1]) < 2 || /* minimum 2 bytes for a cipher */ - ext_len > hs_len) - goto not_ssl_hello; - - /* Jump to the compression methods */ - hs_len -= 2 + ext_len; - data += 2 + ext_len; - - if (hs_len < 2 || /* minimum one compression method */ - data[0] < 1 || data[0] > hs_len) /* minimum 1 bytes for a method */ - goto not_ssl_hello; - - /* Jump to the extensions */ - hs_len -= 1 + data[0]; - data += 1 + data[0]; - - if (hs_len < 2 || /* minimum one extension list length */ - (ext_len = (data[0] << 8) + data[1]) > hs_len - 2) /* list too long */ - goto not_ssl_hello; - - hs_len = ext_len; /* limit ourselves to the extension length */ - data += 2; - while (hs_len >= 4) { - int ext_type, name_len, name_offset; + while (cp.hs_len >= 4) { + int ext_len, ext_type, name_len, name_offset; - ext_type = (data[0] << 8) + data[1]; - ext_len = (data[2] << 8) + data[3]; + ext_type = (cp.data[0] << 8) + cp.data[1]; + ext_len = (cp.data[2] << 8) + cp.data[3]; - if (ext_len > hs_len - 4) /* Extension too long */ + if (ext_len > cp.hs_len - 4) /* Extension too long */ goto not_ssl_hello; if (ext_type == 16) { /* ALPN */ @@ -833,13 +794,13 @@ smp_fetch_ssl_hello_alpn(const struct arg *args, struct sample *smp, const char /* Name cursor in ctx, must begin after protocol_names_len */ name_offset = smp->ctx.i < 6 ? 6 : smp->ctx.i; - name_len = data[name_offset]; + name_len = cp.data[name_offset]; if (name_len + name_offset - 3 > ext_len) goto not_ssl_hello; smp->data.type = SMP_T_STR; - smp->data.u.str.area = (char *)data + name_offset + 1; /* +1 to skip name_len */ + smp->data.u.str.area = (char *)cp.data + name_offset + 1; /* +1 to skip name_len */ smp->data.u.str.data = name_len; smp->flags = SMP_F_VOLATILE | SMP_F_CONST; @@ -852,8 +813,8 @@ smp_fetch_ssl_hello_alpn(const struct arg *args, struct sample *smp, const char return 1; } - hs_len -= 4 + ext_len; - data += 4 + ext_len; + cp.hs_len -= 4 + ext_len; + cp.data += 4 + ext_len; } /* alpn not found */ goto not_ssl_hello; @@ -1412,7 +1373,11 @@ static struct sample_fetch_kw_list smp_kws = {ILH, { { "req.ssl_st_ext", smp_fetch_req_ssl_st_ext, 0, NULL, SMP_T_SINT, SMP_USE_L6REQ }, { "req.ssl_hello_type", smp_fetch_ssl_hello_type, 0, NULL, SMP_T_SINT, SMP_USE_L6REQ }, { "req.ssl_sni", smp_fetch_ssl_hello_sni, 0, NULL, SMP_T_STR, SMP_USE_L6REQ }, - { "req.ssl_alpn", smp_fetch_ssl_hello_alpn, 0, NULL, SMP_T_STR, SMP_USE_L6REQ }, + { "req.ssl_cipherlist", smp_fetch_ssl_cipherlist, 0, NULL, SMP_T_BIN, SMP_USE_L6REQ|SMP_USE_L4CLI|SMP_USE_L5CLI|SMP_USE_FTEND }, + { "req.ssl_supported_groups", smp_fetch_ssl_supported_groups, 0, NULL, SMP_T_BIN, SMP_USE_L6REQ|SMP_USE_L4CLI|SMP_USE_L5CLI|SMP_USE_FTEND }, + { "req.ssl_sigalgs", smp_fetch_ssl_sigalgs, 0, NULL, SMP_T_BIN, SMP_USE_L6REQ|SMP_USE_L4CLI|SMP_USE_L5CLI|SMP_USE_FTEND }, + { "req.ssl_keyshare_groups", smp_fetch_ssl_keyshare_groups, 0, NULL, SMP_T_BIN, SMP_USE_L6REQ|SMP_USE_L4CLI|SMP_USE_L5CLI|SMP_USE_FTEND }, + { "req.ssl_alpn", smp_fetch_ssl_hello_alpn, 0, NULL, SMP_T_STR, SMP_USE_L6REQ }, { "req.ssl_ver", smp_fetch_req_ssl_ver, 0, NULL, SMP_T_SINT, SMP_USE_L6REQ }, { "res.len", smp_fetch_len, 0, NULL, SMP_T_SINT, SMP_USE_L6RES }, { "res.payload", smp_fetch_payload, ARG2(2,SINT,SINT), NULL, SMP_T_BIN, SMP_USE_L6RES }, -- 2.39.3 (Apple Git-145)