diff --git a/doc/configuration.txt b/doc/configuration.txt index a9fac77c0..a0310de93 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -6958,6 +6958,13 @@ no-tlsv12 cannot be enabled using any configuration option. See also "force-tls*", and "force-sslv3". +npn + This enables the NPN TLS extension and advertises the specified protocol list + 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). + ssl This setting is only available when support for OpenSSL was built in. It enables SSL deciphering on connections instanciated from this listener. A @@ -8344,6 +8351,9 @@ ssl_npn 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 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. ssl_sni 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 6a0e0602e..918ba0ac7 100644 --- a/include/types/listener.h +++ b/include/types/listener.h @@ -122,6 +122,8 @@ struct bind_conf { int ssl_options; /* ssl options */ int verify; /* verify method (set of SSL_VERIFY_* flags) */ SSL_CTX *default_ctx; /* SSL context of first/default certificate */ + char *npn_str; /* NPN protocol string */ + int npn_len; /* NPN protocol string length */ 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 */ #endif diff --git a/src/ssl_sock.c b/src/ssl_sock.c index 6121b1284..53f6d83ce 100644 --- a/src/ssl_sock.c +++ b/src/ssl_sock.c @@ -141,6 +141,21 @@ int ssl_sock_verifycbk(int ok, X509_STORE_CTX *x_store) return 0; } +#ifdef OPENSSL_NPN_NEGOTIATED +/* This callback is used so that the server advertises the list of + * negociable protocols for NPN. + */ +static int ssl_sock_advertise_npn_protos(SSL *s, const unsigned char **data, + unsigned int *len, void *arg) +{ + struct bind_conf *conf = arg; + + *data = (const unsigned char *)conf->npn_str; + *len = conf->npn_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 @@ -548,6 +563,11 @@ int ssl_sock_prepare_ctx(struct bind_conf *bind_conf, SSL_CTX *ctx, struct proxy } SSL_CTX_set_info_callback(ctx, ssl_sock_infocbk); +#ifdef OPENSSL_NPN_NEGOTIATED + if (bind_conf->npn_str) + SSL_CTX_set_next_protos_advertised_cb(ctx, ssl_sock_advertise_npn_protos, bind_conf); +#endif + #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME SSL_CTX_set_tlsext_servername_callback(ctx, ssl_sock_switchctx_cbk); SSL_CTX_set_tlsext_servername_arg(ctx, bind_conf); @@ -1118,11 +1138,11 @@ smp_fetch_has_sni(struct proxy *px, struct session *l4, void *l7, unsigned int o #endif } +#ifdef OPENSSL_NPN_NEGOTIATED static int smp_fetch_ssl_npn(struct proxy *px, struct session *l4, void *l7, unsigned int opt, const struct arg *args, struct sample *smp) { -#ifdef OPENSSL_NPN_NEGOTIATED smp->flags = 0; smp->type = SMP_T_CSTR; @@ -1137,10 +1157,8 @@ smp_fetch_ssl_npn(struct proxy *px, struct session *l4, void *l7, unsigned int o return 0; return 1; -#else - return 0; -#endif } +#endif static int smp_fetch_ssl_sni(struct proxy *px, struct session *l4, void *l7, unsigned int opt, @@ -1466,6 +1484,54 @@ static int bind_parse_no_tlsv12(char **args, int cur_arg, struct proxy *px, stru return 0; } +/* parse the "npn" bind keyword */ +static int bind_parse_npn(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ +#ifdef OPENSSL_NPN_NEGOTIATED + char *p1, *p2; + + if (!*args[cur_arg + 1]) { + memprintf(err, "'%s' : missing the comma-delimited NPN protocol suite", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + free(conf->npn_str); + + /* the NPN string is built as a suite of ( )* */ + conf->npn_len = strlen(args[cur_arg + 1]) + 1; + conf->npn_str = calloc(1, conf->npn_len); + memcpy(conf->npn_str + 1, args[cur_arg + 1], conf->npn_len); + + /* replace commas with the name length */ + p1 = conf->npn_str; + p2 = p1 + 1; + while (1) { + p2 = memchr(p1 + 1, ',', conf->npn_str + conf->npn_len - (p1 + 1)); + if (!p2) + p2 = p1 + 1 + strlen(p1 + 1); + + if (p2 - (p1 + 1) > 255) { + *p2 = '\0'; + memprintf(err, "'%s' : NPN 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 NPN 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) { @@ -1743,6 +1809,7 @@ static struct bind_kw_list bind_kws = { "SSL", { }, { { "no-tls-tickets", bind_parse_no_tls_tickets, 0 }, /* disable session resumption tickets */ { "ssl", bind_parse_ssl, 0 }, /* enable SSL processing */ { "verify", bind_parse_verify, 1 }, /* set SSL verify method */ + { "npn", bind_parse_npn, 1 }, /* set NPN supported protocols */ { NULL, NULL, 0 }, }};