From ab861d38569bb6c98844b95c52f626d7e5de054e Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Tue, 2 Apr 2013 02:30:41 +0200 Subject: [PATCH] MINOR: ssl: add support for the "alpn" bind keyword The ALPN extension is meant to replace the now deprecated NPN extension. This patch implements support for it. It requires a version of openssl with support for this extension. Patches are available here right now : http://html5labs.interopbridges.com/media/167447/alpn_patches.zip --- doc/configuration.txt | 24 +++++++++- include/types/listener.h | 2 + src/ssl_sock.c | 96 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 2 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index 0b14d0ef0..fd3273bea 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -7141,6 +7141,14 @@ accept-proxy X-Forwarded-For mechanism which is not always reliable and not even always usable. +alpn + This enables the TLS ALPN extension and advertises the specified protocol + list as supported on top of ALPN. The protocol list consists in a comma- + delimited list of protocol names, for instance: "http/1.1,http/1.0" (without + quotes). This requires that the SSL library is build with support for TLS + extensions enabled (check with haproxy -vv). The ALPN extension replaces the + initial NPN extension. + backlog Sets the socket's backlog to this value. If unspecified, the frontend's backlog is used instead, which generally defaults to the maxconn value. @@ -7384,7 +7392,8 @@ npn as supported on top of NPN. The protocol list consists in a comma-delimited list of protocol names, for instance: "http/1.1,http/1.0" (without quotes). This requires that the SSL library is build with support for TLS extensions - enabled (check with haproxy -vv). + enabled (check with haproxy -vv). Note that the NPN extension has been + replaced with the ALPN extension (see the "alpn" keyword). ssl This setting is only available when support for OpenSSL was built in. It @@ -9016,6 +9025,16 @@ ssl_fc_alg_keysize Returns true when the incoming connection was made over an SSL/TLS transport layer and the symmetric cipher key size supported in bits matches the value. +ssl_fc_alpn + Returns true when the incoming connection was made over an SSL/TLS transport + layer which deciphered it and found a Next Protocol Negociation TLS extension + sent by the client, matching the specified string. This requires that the SSL + library is build with support for TLS extensions enabled (check haproxy -vv). + Note that the TLS ALPN extension is not advertised unless the "alpn" keyword + on the "bind" line specifies a protocol list. Also, nothing forces the client + to pick a protocol from this list, any other one may be requested. The TLS + ALPN extension is meant to replace the TLS NPN extension. + ssl_fc_cipher returns true when the incoming connection was made over an ssl/tls transport layer and the name of the used cipher matches the string. @@ -9042,7 +9061,8 @@ ssl_fc_npn library is build with support for TLS extensions enabled (check haproxy -vv). Note that the TLS NPN extension is not advertised unless the "npn" keyword on the "bind" line specifies a protocol list. Also, nothing forces the client to - pick a protocol from this list, any other one may be requested. + pick a protocol from this list, any other one may be requested. Please note + that the TLS NPN extension was replaced with ALPN. ssl_fc_protocol Returns true when the incoming connection was made over an SSL/TLS transport diff --git a/include/types/listener.h b/include/types/listener.h index 6e9312557..64d3cb947 100644 --- a/include/types/listener.h +++ b/include/types/listener.h @@ -127,6 +127,8 @@ struct bind_conf { SSL_CTX *default_ctx; /* SSL context of first/default certificate */ char *npn_str; /* NPN protocol string */ int npn_len; /* NPN protocol string length */ + char *alpn_str; /* ALPN protocol string */ + int alpn_len; /* ALPN protocol string length */ int strict_sni; /* refuse negotiation if sni doesn't match a certificate */ struct eb_root sni_ctx; /* sni_ctx tree of all known certs full-names sorted by name */ struct eb_root sni_w_ctx; /* sni_ctx tree of all known certs wildcards sorted by name */ diff --git a/src/ssl_sock.c b/src/ssl_sock.c index 0f6526d78..917ca158a 100644 --- a/src/ssl_sock.c +++ b/src/ssl_sock.c @@ -165,6 +165,21 @@ static int ssl_sock_advertise_npn_protos(SSL *s, const unsigned char **data, } #endif +#ifdef OPENSSL_ALPN_NEGOTIATED +/* This callback is used so that the server advertises the list of + * negociable protocols for ALPN. + */ +static int ssl_sock_advertise_alpn_protos(SSL *s, const unsigned char **data, + unsigned int *len, void *arg) +{ + struct bind_conf *conf = arg; + + *data = (const unsigned char *)conf->alpn_str; + *len = conf->alpn_len; + return SSL_TLSEXT_ERR_OK; +} +#endif + #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME /* Sets the SSL ctx of to match the advertised server name. Returns a * warning when no match is found, which implies the default (first) cert @@ -692,6 +707,10 @@ int ssl_sock_prepare_ctx(struct bind_conf *bind_conf, SSL_CTX *ctx, struct proxy if (bind_conf->npn_str) SSL_CTX_set_next_protos_advertised_cb(ctx, ssl_sock_advertise_npn_protos, bind_conf); #endif +#ifdef OPENSSL_ALPN_NEGOTIATED + if (bind_conf->alpn_str) + SSL_CTX_set_alpn_advertised_cb(ctx, ssl_sock_advertise_alpn_protos, bind_conf); +#endif #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME SSL_CTX_set_tlsext_servername_callback(ctx, ssl_sock_switchctx_cbk); @@ -2253,6 +2272,28 @@ smp_fetch_ssl_fc_npn(struct proxy *px, struct session *l4, void *l7, unsigned in } #endif +#ifdef OPENSSL_ALPN_NEGOTIATED +static int +smp_fetch_ssl_fc_alpn(struct proxy *px, struct session *l4, void *l7, unsigned int opt, + const struct arg *args, struct sample *smp) +{ + smp->flags = 0; + smp->type = SMP_T_CSTR; + + if (!l4 || !l4->si[0].conn->xprt_ctx || l4->si[0].conn->xprt != &ssl_sock) + return 0; + + smp->data.str.str = NULL; + SSL_get0_alpn_negotiated(l4->si[0].conn->xprt_ctx, + (const unsigned char **)&smp->data.str.str, (unsigned *)&smp->data.str.len); + + if (!smp->data.str.str) + return 0; + + return 1; +} +#endif + static int smp_fetch_ssl_fc_protocol(struct proxy *px, struct session *l4, void *l7, unsigned int opt, const struct arg *args, struct sample *smp) @@ -2687,6 +2728,54 @@ static int bind_parse_npn(char **args, int cur_arg, struct proxy *px, struct bin #endif } +/* parse the "alpn" bind keyword */ +static int bind_parse_alpn(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ +#ifdef OPENSSL_ALPN_NEGOTIATED + char *p1, *p2; + + if (!*args[cur_arg + 1]) { + memprintf(err, "'%s' : missing the comma-delimited ALPN protocol suite", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + free(conf->alpn_str); + + /* the ALPN string is built as a suite of ( )* */ + conf->alpn_len = strlen(args[cur_arg + 1]) + 1; + conf->alpn_str = calloc(1, conf->alpn_len); + memcpy(conf->alpn_str + 1, args[cur_arg + 1], conf->alpn_len); + + /* replace commas with the name length */ + p1 = conf->alpn_str; + p2 = p1 + 1; + while (1) { + p2 = memchr(p1 + 1, ',', conf->alpn_str + conf->alpn_len - (p1 + 1)); + if (!p2) + p2 = p1 + 1 + strlen(p1 + 1); + + if (p2 - (p1 + 1) > 255) { + *p2 = '\0'; + memprintf(err, "'%s' : ALPN protocol name too long : '%s'", args[cur_arg], p1 + 1); + return ERR_ALERT | ERR_FATAL; + } + + *p1 = p2 - (p1 + 1); + p1 = p2; + + if (!*p2) + break; + + *(p2++) = '\0'; + } + return 0; +#else + if (err) + memprintf(err, "'%s' : library does not support TLS ALPN extension", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; +#endif +} + /* parse the "ssl" bind keyword */ static int bind_parse_ssl(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) { @@ -2955,6 +3044,9 @@ static struct sample_fetch_kw_list sample_fetch_keywords = {{ },{ { "ssl_fc_has_sni", smp_fetch_ssl_fc_has_sni, 0, NULL, SMP_T_BOOL, SMP_USE_L5CLI }, #ifdef OPENSSL_NPN_NEGOTIATED { "ssl_fc_npn", smp_fetch_ssl_fc_npn, 0, NULL, SMP_T_CSTR, SMP_USE_L5CLI }, +#endif +#ifdef OPENSSL_ALPN_NEGOTIATED + { "ssl_fc_alpn", smp_fetch_ssl_fc_alpn, 0, NULL, SMP_T_CSTR, SMP_USE_L5CLI }, #endif { "ssl_fc_protocol", smp_fetch_ssl_fc_protocol, 0, NULL, SMP_T_CSTR, SMP_USE_L5CLI }, { "ssl_fc_use_keysize", smp_fetch_ssl_fc_use_keysize, 0, NULL, SMP_T_UINT, SMP_USE_L5CLI }, @@ -2995,6 +3087,9 @@ static struct acl_kw_list acl_kws = {{ },{ { "ssl_fc_has_sni", NULL, acl_parse_nothing, acl_match_nothing }, #ifdef OPENSSL_NPN_NEGOTIATED { "ssl_fc_npn", NULL, acl_parse_str, acl_match_str }, +#endif +#ifdef OPENSSL_ALPN_NEGOTIATED + { "ssl_fc_alpn", NULL, acl_parse_str, acl_match_str }, #endif { "ssl_fc_protocol", NULL, acl_parse_str, acl_match_str }, { "ssl_fc_use_keysize", NULL, acl_parse_int, acl_match_int }, @@ -3012,6 +3107,7 @@ static struct acl_kw_list acl_kws = {{ },{ * not enabled. */ static struct bind_kw_list bind_kws = { "SSL", { }, { + { "alpn", bind_parse_alpn, 1 }, /* set ALPN supported protocols */ { "ca-file", bind_parse_ca_file, 1 }, /* set CAfile to process verify on client cert */ { "ca-ignore-err", bind_parse_ignore_err, 1 }, /* set error IDs to ignore on verify depth > 0 */ { "ciphers", bind_parse_ciphers, 1 }, /* set SSL cipher suite */