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.
This commit is contained in:
Alex Zorin 2018-12-30 13:56:28 +11:00 committed by Willy Tarreau
parent a2dbeb22fc
commit 4afdd13842
2 changed files with 188 additions and 0 deletions

View File

@ -15465,6 +15465,22 @@ rdp_cookie_cnt([name]) : integer (deprecated)
ACL derivatives : ACL derivatives :
req_rdp_cookie_cnt([<name>]) : integer match req_rdp_cookie_cnt([<name>]) : 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 req.ssl_ec_ext : boolean
Returns a boolean identifying if client sent the Supported Elliptic Curves Returns a boolean identifying if client sent the Supported Elliptic Curves
Extension as defined in RFC4492, section 5.1. within the SSL ClientHello Extension as defined in RFC4492, section 5.1. within the SSL ClientHello

View File

@ -659,6 +659,177 @@ smp_fetch_ssl_hello_sni(const struct arg *args, struct sample *smp, const char *
return 0; 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 <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;
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 <cname>:<clen>, or any cookie if /* Fetch the request RDP cookie identified in <cname>:<clen>, or any cookie if
* <clen> is empty (cname is then ignored). It returns the data into sample <smp> * <clen> is empty (cname is then ignored). It returns the data into sample <smp>
* of type SMP_T_CSTR. Note: this decoder only works with non-wrapping data. * 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_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_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_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 }, { "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.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 }, { "res.payload", smp_fetch_payload, ARG2(2,SINT,SINT), NULL, SMP_T_BIN, SMP_USE_L6RES },