From 4afdd138424ab3f9fc4697ab50df0b05e18c34e6 Mon Sep 17 00:00:00 2001 From: Alex Zorin Date: Sun, 30 Dec 2018 13:56:28 +1100 Subject: [PATCH] MINOR: payload: add sample fetch for TLS ALPN Application-Layer Protocol Negotiation (ALPN, RFC7301) is a TLS extension which allows a client to present a preference for which protocols it wishes to connect to, when a single port supports multiple multiple application protocols. It allows a transparent proxy to take a decision based on the beginning of an SSL/TLS stream without deciphering it. The new fetch "req.ssl_alpn" extracts the ALPN protocol names that may be present in the ClientHello message. --- doc/configuration.txt | 16 ++++ src/payload.c | 172 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 188 insertions(+) diff --git a/doc/configuration.txt b/doc/configuration.txt index 6ca63d64ad..dc1f22243c 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -15465,6 +15465,22 @@ rdp_cookie_cnt([name]) : integer (deprecated) ACL derivatives : req_rdp_cookie_cnt([]) : integer match +req.ssl_alpn : string + Returns a string containing the values of the Application-Layer Protocol + Negotiation (ALPN) TLS extension (RFC7301), sent by the client within the SSL + ClientHello message. Note that this only applies to raw contents found in the + request buffer and not to the contents deciphered via an SSL data layer, so + this will not work with "bind" lines having the "ssl" option. This is useful + in ACL to make a routing decision based upon the ALPN preferences of a TLS + client, like in the example below. + + 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_backend bk_acme if { req_ssl.alpn acme-tls/1 } + default_backend bk_default + req.ssl_ec_ext : boolean Returns a boolean identifying if client sent the Supported Elliptic Curves Extension as defined in RFC4492, section 5.1. within the SSL ClientHello diff --git a/src/payload.c b/src/payload.c index 7ef6d97e22..a16f8c601b 100644 --- a/src/payload.c +++ b/src/payload.c @@ -659,6 +659,177 @@ smp_fetch_ssl_hello_sni(const struct arg *args, struct sample *smp, const char * 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] + */ +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; + + 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 */ + 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; + + ext_type = (data[0] << 8) + data[1]; + ext_len = (data[2] << 8) + data[3]; + + if (ext_len > hs_len - 4) /* Extension too long */ + goto not_ssl_hello; + + if (ext_type == 16) { /* ALPN */ + if (ext_len < 3) /* one list length [uint16] + at least one name length [uint8] */ + goto not_ssl_hello; + + /* 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]; + + 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.data = name_len; + smp->flags = SMP_F_VOLATILE | SMP_F_CONST; + + /* May have more protocol names remaining */ + if (name_len + name_offset - 3 < ext_len) { + smp->ctx.i = name_offset + name_len + 1; + smp->flags |= SMP_F_NOT_LAST; + } + + return 1; + } + + hs_len -= 4 + ext_len; + data += 4 + ext_len; + } + /* alpn not found */ + goto not_ssl_hello; + + too_short: + smp->flags = SMP_F_MAY_CHANGE; + + not_ssl_hello: + + return 0; +} + /* Fetch the request RDP cookie identified in :, or any cookie if * is empty (cname is then ignored). It returns the data into sample * of type SMP_T_CSTR. Note: this decoder only works with non-wrapping data. @@ -1150,6 +1321,7 @@ 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_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.5