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 | 61 ++ include/haproxy/buf-t.h | 2 + reg-tests/checks/tcp-check-client-hello.vtc | 84 +++ src/payload.c | 629 +++++++++++++++++++- 4 files changed, 775 insertions(+), 1 deletion(-) create mode 100644 reg-tests/checks/tcp-check-client-hello.vtc diff --git a/doc/configuration.txt b/doc/configuration.txt index 76b622bce..6d5ea4a7d 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -25030,6 +25030,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 @@ -25234,6 +25238,63 @@ 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. + + 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_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. + + 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. + + 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/include/haproxy/buf-t.h b/include/haproxy/buf-t.h index 5c59b0aaf..59472da84 100644 --- a/include/haproxy/buf-t.h +++ b/include/haproxy/buf-t.h @@ -31,11 +31,13 @@ #include <haproxy/api-t.h> /* Structure defining a buffer's head */ +#define buffer_bin_size 16 /* Space to hold up to 8 code points, e.g for key share groups */ struct buffer { size_t size; /* buffer size in bytes */ char *area; /* points to <size> bytes */ size_t data; /* amount of data after head including wrapping */ size_t head; /* start offset of remaining data relative to area */ + uint8_t bin[buffer_bin_size]; /* space to hold binary data, used to collect non-consecutive data */ }; /* A buffer may be in 3 different states : 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..56c94b543 --- /dev/null +++ b/reg-tests/checks/tcp-check-client-hello.vtc @@ -0,0 +1,84 @@ +#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(OpenSSL) && 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 + tune.ssl.default-dh-param 2048 + log 127.0.0.1:514 local2 + 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..9762deab7 100644 --- a/src/payload.c +++ b/src/payload.c @@ -522,6 +522,629 @@ smp_fetch_req_ssl_ver(const struct arg *args, struct sample *smp, const char *kw return 0; } +/* Extract the ciphers that may be presented in a TLS client hello handshake message. + * 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_ssl_cipherlist(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + int hs_len, ext_len, bleft; + struct channel *chn; + unsigned char *data; + + if (!smp->strm) + goto not_ssl_hello; + + /* meaningless for HTX buffers */ + if (IS_HTX_STRM(smp->strm)) + goto not_ssl_hello; + + chn = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_RES) ? &smp->strm->res : &smp->strm->req; + bleft = ci_data(chn); + data = (unsigned char *)ci_head(chn); + + /* Check for SSL/TLS Handshake */ + if (!bleft) + goto too_short; + if (*data != 0x16) + goto not_ssl_hello; + + /* Check for SSLv3 or later (SSL version >= 3.0) in the record layer*/ + if (bleft < 3) + goto too_short; + if (data[1] < 0x03) + goto not_ssl_hello; + + if (bleft < 5) + goto too_short; + hs_len = (data[3] << 8) + data[4]; + if (hs_len < 1 + 3 + 2 + 32 + 1 + 2 + 2 + 1 + 1 + 2 + 2) + goto not_ssl_hello; /* too short to have an extension */ + + data += 5; /* enter TLS handshake */ + bleft -= 5; + + /* Check for a complete client hello starting at <data> */ + if (bleft < 1) + goto too_short; + if (data[0] != 0x01) /* msg_type = Client Hello */ + goto not_ssl_hello; + + /* Check the Hello's length */ + if (bleft < 4) + goto too_short; + hs_len = (data[1] << 16) + (data[2] << 8) + data[3]; + if (hs_len < 2 + 32 + 1 + 2 + 2 + 1 + 1 + 2 + 2) + goto not_ssl_hello; /* too short to have an extension */ + + /* We want the full handshake here */ + if (bleft < hs_len) + goto too_short; + + data += 4; + /* Start of the ClientHello message */ + if (data[0] < 0x03 || data[1] < 0x01) /* TLSv1 minimum */ + goto not_ssl_hello; + + ext_len = data[34]; /* session_id_len */ + if (ext_len > 32 || ext_len > (hs_len - 35)) /* check for correct session_id len */ + goto not_ssl_hello; + + /* Jump to cipher suite */ + hs_len -= 35 + ext_len; + data += 35 + ext_len; + + if (hs_len < 4 || /* minimum one cipher */ + (ext_len = (data[0] << 8) + data[1]) < 2 || /* minimum 2 bytes for a cipher */ + ext_len > hs_len) + goto not_ssl_hello; + + smp->data.type = SMP_T_BIN; + smp->data.u.str.area = (char *)data + 2; + smp->data.u.str.data = ext_len; + smp->flags = SMP_F_VOLATILE | SMP_F_CONST; + + return 1; + + + too_short: + smp->flags = SMP_F_MAY_CHANGE; + + not_ssl_hello: + + return 0; +} + +/* Extract the supported group that may be presented in a TLS client hello handshake + * message. 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_ssl_supported_groups(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + int hs_len, ext_len, bleft; + struct channel *chn; + unsigned char *data; + + if (!smp->strm) + goto not_ssl_hello; + + /* meaningless for HTX buffers */ + if (IS_HTX_STRM(smp->strm)) + goto not_ssl_hello; + + chn = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_RES) ? &smp->strm->res : &smp->strm->req; + bleft = ci_data(chn); + data = (unsigned char *)ci_head(chn); + + /* Check for SSL/TLS Handshake */ + if (!bleft) + goto too_short; + if (*data != 0x16) + goto not_ssl_hello; + + /* Check for SSLv3 or later (SSL version >= 3.0) in the record layer*/ + if (bleft < 3) + goto too_short; + if (data[1] < 0x03) + goto not_ssl_hello; + + if (bleft < 5) + goto too_short; + hs_len = (data[3] << 8) + data[4]; + if (hs_len < 1 + 3 + 2 + 32 + 1 + 2 + 2 + 1 + 1 + 2 + 2) + goto not_ssl_hello; /* too short to have an extension */ + + data += 5; /* enter TLS handshake */ + bleft -= 5; + /* Check for a complete client hello starting at <data> */ + if (bleft < 1) + goto too_short; + if (data[0] != 0x01) /* msg_type = Client Hello */ + goto not_ssl_hello; + + /* Check the Hello's length */ + if (bleft < 4) + goto too_short; + hs_len = (data[1] << 16) + (data[2] << 8) + data[3]; + if (hs_len < 2 + 32 + 1 + 2 + 2 + 1 + 1 + 2 + 2) + goto not_ssl_hello; /* too short to have an extension */ + + /* We want the full handshake here */ + if (bleft < hs_len) + goto too_short; + + data += 4; + /* Start of the ClientHello message */ + if (data[0] < 0x03 || data[1] < 0x01) /* TLSv1 minimum */ + goto not_ssl_hello; + + ext_len = data[34]; /* session_id_len */ + if (ext_len > 32 || ext_len > (hs_len - 35)) /* check for correct session_id len */ + goto not_ssl_hello; + + /* Jump to cipher suite */ + hs_len -= 35 + ext_len; + data += 35 + ext_len; + + if (hs_len < 4 || /* minimum one cipher */ + (ext_len = (data[0] << 8) + data[1]) < 2 || /* minimum 2 bytes for a cipher */ + ext_len > hs_len) + goto not_ssl_hello; + + /* Jump to the compression methods */ + hs_len -= 2 + ext_len; + data += 2 + ext_len; + + if (hs_len < 2 || /* minimum one compression method */ + data[0] < 1 || data[0] > hs_len) /* minimum 1 bytes for a method */ + goto not_ssl_hello; + + /* Jump to the extensions */ + hs_len -= 1 + data[0]; + data += 1 + data[0]; + + if (hs_len < 2 || /* minimum one extension list length */ + (ext_len = (data[0] << 8) + data[1]) > hs_len - 2) /* list too long */ + goto not_ssl_hello; + + hs_len = ext_len; /* limit ourselves to the extension length */ + data += 2; /* Now 'data' points to the first content byte of an extension */ + + while (hs_len >= 4) { + + int ext_type, grp_len; + + ext_type = (data[0] << 8) + data[1]; /* Extension type */ + ext_len = (data[2] << 8) + data[3]; /* Extension length */ + + if (ext_len > hs_len - 4) /* Extension too long */ + goto not_ssl_hello; + + if (ext_type == 10) { /* Supported groups extension type ID is 10dec */ + if (ext_len < 2) /* need at least one entry of 2 bytes in the list length */ + goto not_ssl_hello; + + grp_len = (data[4] << 8) + data[5]; /* Supported group list length */ + if (grp_len < 2 || grp_len > hs_len - 6) + goto not_ssl_hello; /* at least 2 bytes per supported group */ + + smp->data.type = SMP_T_BIN; + smp->data.u.str.area = (char *)data + 6; + smp->data.u.str.data = grp_len; + smp->flags = SMP_F_VOL_SESS | SMP_F_CONST; + + return 1; + + } + hs_len -= 4 + ext_len; + data += 4 + ext_len; + } + /* supported groups not found */ + goto not_ssl_hello; + + too_short: + smp->flags = SMP_F_MAY_CHANGE; + + not_ssl_hello: + + return 0; +} + +/* Extract the signature algorithms that may be presented in a TLS client hello + * handshake message. 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_ssl_sigalgs(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + int hs_len, ext_len, bleft; + struct channel *chn; + unsigned char *data; + + if (!smp->strm) + goto not_ssl_hello; + + /* meaningless for HTX buffers */ + if (IS_HTX_STRM(smp->strm)) + goto not_ssl_hello; + + chn = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_RES) ? &smp->strm->res : &smp->strm->req; + bleft = ci_data(chn); + data = (unsigned char *)ci_head(chn); + + /* Check for SSL/TLS Handshake */ + if (!bleft) + goto too_short; + if (*data != 0x16) + goto not_ssl_hello; + + /* Check for SSLv3 or later (SSL version >= 3.0) in the record layer*/ + if (bleft < 3) + goto too_short; + if (data[1] < 0x03) + goto not_ssl_hello; + + if (bleft < 5) + goto too_short; + hs_len = (data[3] << 8) + data[4]; + if (hs_len < 1 + 3 + 2 + 32 + 1 + 2 + 2 + 1 + 1 + 2 + 2) + goto not_ssl_hello; /* too short to have an extension */ + + data += 5; /* enter TLS handshake */ + bleft -= 5; + /* Check for a complete client hello starting at <data> */ + if (bleft < 1) + goto too_short; + if (data[0] != 0x01) /* msg_type = Client Hello */ + goto not_ssl_hello; + + /* Check the Hello's length */ + if (bleft < 4) + goto too_short; + hs_len = (data[1] << 16) + (data[2] << 8) + data[3]; + if (hs_len < 2 + 32 + 1 + 2 + 2 + 1 + 1 + 2 + 2) + goto not_ssl_hello; /* too short to have an extension */ + + /* We want the full handshake here */ + if (bleft < hs_len) + goto too_short; + + data += 4; + /* Start of the ClientHello message */ + if (data[0] < 0x03 || data[1] < 0x01) /* TLSv1 minimum */ + goto not_ssl_hello; + + ext_len = data[34]; /* session_id_len */ + if (ext_len > 32 || ext_len > (hs_len - 35)) /* check for correct session_id len */ + goto not_ssl_hello; + + /* Jump to cipher suite */ + hs_len -= 35 + ext_len; + data += 35 + ext_len; + + if (hs_len < 4 || /* minimum one cipher */ + (ext_len = (data[0] << 8) + data[1]) < 2 || /* minimum 2 bytes for a cipher */ + ext_len > hs_len) + goto not_ssl_hello; + + /* Jump to the compression methods */ + hs_len -= 2 + ext_len; + data += 2 + ext_len; + + if (hs_len < 2 || /* minimum one compression method */ + data[0] < 1 || data[0] > hs_len) /* minimum 1 bytes for a method */ + goto not_ssl_hello; + + /* Jump to the extensions */ + hs_len -= 1 + data[0]; + data += 1 + data[0]; + + if (hs_len < 2 || /* minimum one extension list length */ + (ext_len = (data[0] << 8) + data[1]) > hs_len - 2) /* list too long */ + goto not_ssl_hello; + + hs_len = ext_len; /* limit ourselves to the extension length */ + data += 2; /* Now 'data' points to the first content byte of an extension */ + + while (hs_len >= 4) { + + int ext_type, sigalg_len; + + ext_type = (data[0] << 8) + data[1]; /* Extension type */ + ext_len = (data[2] << 8) + data[3]; /* Extension length */ + + if (ext_len > hs_len - 4) /* Extension too long */ + goto not_ssl_hello; + + if (ext_type == 13) { /* Sigalgs extension type ID is 13dec */ + if (ext_len < 2) /* need at least one entry of 2 bytes in the list length */ + goto not_ssl_hello; + + sigalg_len = (data[4] << 8) + data[5]; /* Sigalgs list length */ + if (sigalg_len < 2 || sigalg_len > hs_len - 6) + goto not_ssl_hello; /* at least 2 bytes per sigalg */ + + smp->data.type = SMP_T_BIN; + smp->data.u.str.area = (char *)data + 6; + smp->data.u.str.data = sigalg_len; + smp->flags = SMP_F_VOLATILE | SMP_F_CONST; + + return 1; + + } + hs_len -= 4 + ext_len; + data += 4 + ext_len; + } + /* sigalgs not found */ + goto not_ssl_hello; + + too_short: + smp->flags = SMP_F_MAY_CHANGE; + + not_ssl_hello: + + return 0; +} + +/* Extract the key shares that may be presented in a TLS client hello handshake message. + * 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_ssl_keyshare_groups(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + int hs_len, ext_len, bleft, readPosition, numberOfKeyshares; + struct channel *chn; + unsigned char *data; + unsigned char *dataPointer; + + if (!smp->strm) + goto not_ssl_hello; + + /* meaningless for HTX buffers */ + if (IS_HTX_STRM(smp->strm)) + goto not_ssl_hello; + + chn = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_RES) ? &smp->strm->res : &smp->strm->req; + bleft = ci_data(chn); + data = (unsigned char *)ci_head(chn); + + /* Check for SSL/TLS Handshake */ + if (!bleft) + goto too_short; + if (*data != 0x16) + goto not_ssl_hello; + + /* Check for SSLv3 or later (SSL version >= 3.0) in the record layer*/ + if (bleft < 3) + goto too_short; + if (data[1] < 0x03) + goto not_ssl_hello; + + if (bleft < 5) + goto too_short; + hs_len = (data[3] << 8) + data[4]; + if (hs_len < 1 + 3 + 2 + 32 + 1 + 2 + 2 + 1 + 1 + 2 + 2) + goto not_ssl_hello; /* too short to have an extension */ + + data += 5; /* enter TLS handshake */ + bleft -= 5; + + /* Check for a complete client hello starting at <data> */ + if (bleft < 1) + goto too_short; + if (data[0] != 0x01) /* msg_type = Client Hello */ + goto not_ssl_hello; + + /* Check the Hello's length */ + if (bleft < 4) + goto too_short; + hs_len = (data[1] << 16) + (data[2] << 8) + data[3]; + if (hs_len < 2 + 32 + 1 + 2 + 2 + 1 + 1 + 2 + 2) + goto not_ssl_hello; /* too short to have an extension */ + + /* We want the full handshake here */ + if (bleft < hs_len) + goto too_short; + + data += 4; + /* Start of the ClientHello message */ + if (data[0] < 0x03 || data[1] < 0x01) /* TLSv1 minimum */ + goto not_ssl_hello; + + ext_len = data[34]; /* session_id_len */ + if (ext_len > 32 || ext_len > (hs_len - 35)) /* check for correct session_id len */ + goto not_ssl_hello; + + /* Jump to cipher suite */ + hs_len -= 35 + ext_len; + data += 35 + ext_len; + + if (hs_len < 4 || /* minimum one cipher */ + (ext_len = (data[0] << 8) + data[1]) < 2 || /* minimum 2 bytes for a cipher */ + ext_len > hs_len) + goto not_ssl_hello; + + /* Jump to the compression methods */ + hs_len -= 2 + ext_len; + data += 2 + ext_len; + + if (hs_len < 2 || /* minimum one compression method */ + data[0] < 1 || data[0] > hs_len) /* minimum 1 bytes for a method */ + goto not_ssl_hello; + + /* Jump to the extensions */ + hs_len -= 1 + data[0]; + data += 1 + data[0]; + + if (hs_len < 2 || /* minimum one extension list length */ + (ext_len = (data[0] << 8) + data[1]) > hs_len - 2) /* list too long */ + goto not_ssl_hello; + + hs_len = ext_len; /* limit ourselves to the extension length */ + data += 2; /* Now 'data' points to the first content byte of an extension */ + + while (hs_len >= 4) { + + int ext_type, keyshare_len; + + ext_type = (data[0] << 8) + data[1]; /* Extension type */ + ext_len = (data[2] << 8) + data[3]; /* Extension length */ + + if (ext_len > hs_len - 4) /* Extension too long */ + goto not_ssl_hello; + + if (ext_type == 51) { /* Keyshare extension type ID is 51dec */ + if (ext_len < 2) /* need at least one entry of 2 bytes in the list length */ + goto not_ssl_hello; + + keyshare_len = (data[4] << 8) + data[5]; /* Client keyshare length */ + if (keyshare_len < 2 || keyshare_len > hs_len - 6) + goto not_ssl_hello; /* at least 2 bytes per keyshare */ + dataPointer = data + 6; /* start of keyshare entries */ + readPosition = 0; + numberOfKeyshares = 0; + while (readPosition < keyshare_len) { + /* Get the binary value of the keyshare group and move the offset to the end of the related keyshare */ + memmove(&smp->data.u.str.bin[2*numberOfKeyshares], &dataPointer[readPosition], 2); + numberOfKeyshares++; + if (2*numberOfKeyshares > buffer_bin_size) + goto not_ssl_hello; /* not enough space to store all keyshare groups --> something is wrong */ + 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.data = 2*numberOfKeyshares; + smp->flags = SMP_F_VOLATILE | SMP_F_CONST; + + return 1; + } + hs_len -= 4 + ext_len; + data += 4 + ext_len; + } + /* keyshare groups not found */ + goto not_ssl_hello; + + too_short: + smp->flags = SMP_F_MAY_CHANGE; + not_ssl_hello: + return 0; +} + /* Try to extract the Server Name Indication that may be presented in a TLS * client hello handshake message. The format of the message is the following * (cf RFC5246 + RFC6066) : @@ -1412,7 +2035,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)