mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-09-21 22:01:31 +02:00
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:
parent
a2dbeb22fc
commit
4afdd13842
@ -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
|
||||||
|
172
src/payload.c
172
src/payload.c
@ -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 },
|
||||||
|
Loading…
x
Reference in New Issue
Block a user