]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: ssl: add support for the "alpn" bind keyword
authorWilly Tarreau <w@1wt.eu>
Tue, 2 Apr 2013 00:30:41 +0000 (02:30 +0200)
committerWilly Tarreau <w@1wt.eu>
Wed, 3 Apr 2013 00:13:02 +0000 (02:13 +0200)
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
include/types/listener.h
src/ssl_sock.c

index 0b14d0ef0a6bcf534698f592ff229296b2d074a3..fd3273beabcd7bb333ae32ff5ba098ee0a9f2350 100644 (file)
@@ -7141,6 +7141,14 @@ accept-proxy
   X-Forwarded-For mechanism which is not always reliable and not even always
   usable.
 
+alpn <protocols>
+  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 <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 <protocols>
   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 <integer>
   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 <string>
+  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 <string>
   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 <string>
   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 <string>
   Returns true when the incoming connection was made over an SSL/TLS transport
index 6e931255721c565629f8b7e97964b8b72cd89900..64d3cb94778e50776f45af1cab2e909918f35c8c 100644 (file)
@@ -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 */
index 0f6526d7894785f76028fdb1485f5a755ceead2c..917ca158a1f3512fb261e9592705ad3a819e9882 100644 (file)
@@ -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 <ssl> 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 (<len> <name>)* */
+       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 */