MEDIUM: ssl: add support for the "npn" bind keyword

The ssl_npn match could not work by itself because clients do not use
the NPN extension unless the server advertises the protocols it supports.
Thanks to Simone Bordet for the explanations on how to get it right.
This commit is contained in:
Willy Tarreau 2012-10-18 18:57:14 +02:00
parent 338a4fc2a8
commit 6c9a3d5585
3 changed files with 83 additions and 4 deletions

View File

@ -6958,6 +6958,13 @@ no-tlsv12
cannot be enabled using any configuration option. See also "force-tls*",
and "force-sslv3".
npn <protocols>
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 <string>
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 <string>
Returns true when the incoming connection was made over an SSL/TLS transport

View File

@ -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

View File

@ -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 <ssl> 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 (<len> <name>)* */
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 },
}};