Hi,

You'll find attached updated patches, rebased on the latest master, and on
top of Emmanuel's latest patches (also attached for reference).
This version allows to enable 0RTT per SNI.
It unfortunately still can't send early data to servers, this may or may
not happen later.

Regards,

Olivier
>From 25d10a4b30d946de138ccdd3b2595fa84a9da675 Mon Sep 17 00:00:00 2001
From: Emmanuel Hocdet <m...@gandi.net>
Date: Wed, 16 Aug 2017 11:28:44 +0200
Subject: [PATCH 1/6] MEDIUM: ssl: convert CBS (BoringSSL api) usage to neutral
 code

switchctx early callback is only supported for BoringSSL. To prepare
the support of openssl 1.1.1 early callback, convert CBS api to neutral
code to work with any ssl libs.
---
 src/ssl_sock.c | 109 ++++++++++++++++++++++++++++++---------------------------
 1 file changed, 58 insertions(+), 51 deletions(-)

diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index 3d9723949..25b846b25 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -1965,50 +1965,57 @@ static int ssl_sock_switchctx_err_cbk(SSL *ssl, int 
*al, void *priv)
 
 static int ssl_sock_switchctx_cbk(const struct ssl_early_callback_ctx *ctx)
 {
+       SSL *ssl = ctx->ssl;
        struct connection *conn;
        struct bind_conf *s;
        const uint8_t *extension_data;
        size_t extension_len;
-       CBS extension, cipher_suites, server_name_list, host_name, sig_algs;
-       const SSL_CIPHER *cipher;
-       uint16_t cipher_suite;
-       uint8_t name_type, hash, sign;
        int has_rsa = 0, has_ecdsa = 0, has_ecdsa_sig = 0;
 
        char *wildp = NULL;
        const uint8_t *servername;
+       size_t servername_len;
        struct ebmb_node *node, *n, *node_ecdsa = NULL, *node_rsa = NULL, 
*node_anonymous = NULL;
        int i;
 
-       conn = SSL_get_app_data(ctx->ssl);
+       conn = SSL_get_app_data(ssl);
        s = objt_listener(conn->target)->bind_conf;
 
        if (SSL_early_callback_ctx_extension_get(ctx, TLSEXT_TYPE_server_name,
                                                 &extension_data, 
&extension_len)) {
-               CBS_init(&extension, extension_data, extension_len);
-
-               if (!CBS_get_u16_length_prefixed(&extension, &server_name_list)
-                   || !CBS_get_u8(&server_name_list, &name_type)
-                   /* Although the server_name extension was intended to be 
extensible to
-                    * new name types and multiple names, OpenSSL 1.0.x had a 
bug which meant
-                    * different name types will cause an error. Further, RFC 
4366 originally
-                    * defined syntax inextensibly. RFC 6066 corrected this 
mistake, but
-                    * adding new name types is no longer feasible.
-                    *
-                    * Act as if the extensibility does not exist to simplify 
parsing. */
-                   || !CBS_get_u16_length_prefixed(&server_name_list, 
&host_name)
-                   || CBS_len(&server_name_list) != 0
-                   || CBS_len(&extension) != 0
-                   || name_type != TLSEXT_NAMETYPE_host_name
-                   || CBS_len(&host_name) == 0
-                   || CBS_len(&host_name) > TLSEXT_MAXLEN_host_name
-                   || CBS_contains_zero_byte(&host_name)) {
+               /*
+                * The server_name extension was given too much extensibility 
when it
+                * was written, so parsing the normal case is a bit complex.
+                */
+               size_t len;
+               if (extension_len <= 2)
                        goto abort;
-               }
+               /* Extract the length of the supplied list of names. */
+               len = (*extension_data++) << 8;
+               len |= *extension_data++;
+               if (len + 2 != extension_len)
+                       goto abort;
+               /*
+                * The list in practice only has a single element, so we only 
consider
+                * the first one.
+                */
+               if (len == 0 || *extension_data++ != TLSEXT_NAMETYPE_host_name)
+                       goto abort;
+               extension_len = len - 1;
+               /* Now we can finally pull out the byte array with the actual 
hostname. */
+               if (extension_len <= 2)
+                       goto abort;
+               len = (*extension_data++) << 8;
+               len |= *extension_data++;
+               if (len == 0 || len + 2 > extension_len || len > 
TLSEXT_MAXLEN_host_name
+                   || memchr(extension_data, 0, len) != NULL)
+                       goto abort;
+               servername = extension_data;
+               servername_len = len;
        } else {
                /* without SNI extension, is the default_ctx (need 
SSL_TLSEXT_ERR_NOACK) */
                if (!s->strict_sni) {
-                       ssl_sock_switchctx_set(ctx->ssl, s->default_ctx);
+                       ssl_sock_switchctx_set(ssl, s->default_ctx);
                        return 1;
                }
                goto abort;
@@ -2016,21 +2023,19 @@ static int ssl_sock_switchctx_cbk(const struct 
ssl_early_callback_ctx *ctx)
 
        /* extract/check clientHello informations */
        if (SSL_early_callback_ctx_extension_get(ctx, 
TLSEXT_TYPE_signature_algorithms, &extension_data, &extension_len)) {
-               CBS_init(&extension, extension_data, extension_len);
-
-               if (!CBS_get_u16_length_prefixed(&extension, &sig_algs)
-                   || CBS_len(&sig_algs) == 0
-                   || CBS_len(&extension) != 0) {
+               uint8_t sign;
+               size_t len;
+               if (extension_len < 2)
                        goto abort;
-               }
-               if (CBS_len(&sig_algs) % 2 != 0) {
+               len = (*extension_data++) << 8;
+               len |= *extension_data++;
+               if (len + 2 != extension_len)
                        goto abort;
-               }
-               while (CBS_len(&sig_algs) != 0) {
-                       if (!CBS_get_u8(&sig_algs, &hash)
-                           || !CBS_get_u8(&sig_algs, &sign)) {
-                               goto abort;
-                       }
+               if (len % 2 != 0)
+                       goto abort;
+               for (; len > 0; len -= 2) {
+                       extension_data++; /* hash */
+                       sign = *extension_data++;
                        switch (sign) {
                        case TLSEXT_signature_rsa:
                                has_rsa = 1;
@@ -2049,12 +2054,15 @@ static int ssl_sock_switchctx_cbk(const struct 
ssl_early_callback_ctx *ctx)
                has_rsa = 1;
        }
        if (has_ecdsa_sig) {  /* in very rare case: has ecdsa sign but not a 
ECDSA cipher */
-               CBS_init(&cipher_suites, ctx->cipher_suites, 
ctx->cipher_suites_len);
-
-               while (CBS_len(&cipher_suites) != 0) {
-                       if (!CBS_get_u16(&cipher_suites, &cipher_suite)) {
-                               goto abort;
-                       }
+               const SSL_CIPHER *cipher;
+               size_t len;
+               const uint8_t *cipher_suites;
+               len = ctx->cipher_suites_len;
+               cipher_suites = ctx->cipher_suites;
+               if (len % 2 != 0)
+                       goto abort;
+               for (; len != 0; len -= 2, cipher_suites += 2) {
+                       uint16_t cipher_suite = (cipher_suites[0] << 8) | 
cipher_suites[1];
                        cipher = SSL_get_cipher_by_value(cipher_suite);
                        if (cipher && SSL_CIPHER_get_auth_nid(cipher) == 
NID_auth_ecdsa) {
                                has_ecdsa = 1;
@@ -2063,8 +2071,7 @@ static int ssl_sock_switchctx_cbk(const struct 
ssl_early_callback_ctx *ctx)
                }
        }
 
-       servername = CBS_data(&host_name);
-       for (i = 0; i < trash.size && i < CBS_len(&host_name); i++) {
+       for (i = 0; i < trash.size && i < servername_len; i++) {
                trash.str[i] = tolower(servername[i]);
                if (!wildp && (trash.str[i] == '.'))
                        wildp = &trash.str[i];
@@ -2132,20 +2139,20 @@ static int ssl_sock_switchctx_cbk(const struct 
ssl_early_callback_ctx *ctx)
        if (node) {
                /* switch ctx */
                struct ssl_bind_conf *conf = container_of(node, struct sni_ctx, 
name)->conf;
-               ssl_sock_switchctx_set(ctx->ssl, container_of(node, struct 
sni_ctx, name)->ctx);
-               methodVersions[conf->ssl_methods.min].ssl_set_version(ctx->ssl, 
SET_MIN);
-               methodVersions[conf->ssl_methods.max].ssl_set_version(ctx->ssl, 
SET_MAX);
+               ssl_sock_switchctx_set(ssl, container_of(node, struct sni_ctx, 
name)->ctx);
+               methodVersions[conf->ssl_methods.min].ssl_set_version(ssl, 
SET_MIN);
+               methodVersions[conf->ssl_methods.max].ssl_set_version(ssl, 
SET_MAX);
                return 1;
        }
        if (!s->strict_sni) {
                /* no certificate match, is the default_ctx */
-               ssl_sock_switchctx_set(ctx->ssl, s->default_ctx);
+               ssl_sock_switchctx_set(ssl, s->default_ctx);
                return 1;
        }
  abort:
        /* abort handshake (was SSL_TLSEXT_ERR_ALERT_FATAL) */
        conn->err_code = CO_ER_SSL_HANDSHAKE;
-       return -1;
+       return ssl_select_cert_error;
 }
 
 #else /* OPENSSL_IS_BORINGSSL */
-- 
2.13.5

>From 3019e1900645ebef278e84a858bfad40a2e3f867 Mon Sep 17 00:00:00 2001
From: Emmanuel Hocdet <m...@gandi.net>
Date: Wed, 16 Aug 2017 11:33:17 +0200
Subject: [PATCH 2/6] MINOR: ssl: support Openssl 1.1.1 early callback for
 switchctx

Use Openssl-1.1.1 SSL_CTX_set_client_hello_cb to mimic BoringSSL early callback.
Native multi certificate and SSL/TLS method per certificate is now supported by
Openssl >= 1.1.1.
---
 doc/configuration.txt | 13 +++++++------
 src/ssl_sock.c        | 35 ++++++++++++++++++++++++++++++++---
 2 files changed, 39 insertions(+), 9 deletions(-)

diff --git a/doc/configuration.txt b/doc/configuration.txt
index bba695afd..66b15b30f 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -10399,8 +10399,9 @@ crt <cert>
   to use both RSA and ECDSA cipher suites. Users connecting with an SNI of
   "rsa.example.com" will only be able to use RSA cipher suites, and users
   connecting with "ecdsa.example.com" will only be able to use ECDSA cipher
-  suites. With BoringSSL multi-cert is natively supported, no need to bundle
-  certificates. ECDSA certificate will be preferred if client support it.
+  suites. With BoringSSL and Openssl >= 1.1.1 multi-cert is natively supported,
+  no need to bundle certificates. ECDSA certificate will be preferred if client
+  support it.
 
   If a directory name is given as the <cert> argument, haproxy will
   automatically search and load bundled files in that directory.
@@ -10424,7 +10425,7 @@ crt-list <file>
 
   sslbindconf support "npn", "alpn", "verify", "ca-file", "no-ca-names",
   crl-file", "ecdhe", "curves", "ciphers" configuration. With BoringSSL
-  "ssl-min-ver" and "ssl-max-ver" are also supported.
+  and Openssl >= 1.1.1 "ssl-min-ver" and "ssl-max-ver" are also supported.
   It override the configuration set in bind line for the certificate.
 
   Wildcards are supported in the SNI filter. Negative filter are also 
supported,
@@ -10438,9 +10439,9 @@ crt-list <file>
 
   Multi-cert bundling (see "crt") is supported with crt-list, as long as only
   the base name is given in the crt-list. SNI filter will do the same work on
-  all bundled certificates. With BoringSSL multi-cert is natively supported,
-  avoid multi-cert bundling. RSA and ECDSA certificates can be declared in a
-  row, and set different ssl and filter parameter.
+  all bundled certificates. With BoringSSL and Openssl >= 1.1.1 multi-cert is
+  natively supported, avoid multi-cert bundling. RSA and ECDSA certificates can
+  be declared in a row, and set different ssl and filter parameter.
 
   crt-list file example:
         cert1.pem
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index 25b846b25..e1cfc6697 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -1828,7 +1828,7 @@ ssl_sock_generate_certificate(const char *servername, 
struct bind_conf *bind_con
 #ifndef SSL_OP_NO_TLSv1_2                               /* needs OpenSSL >= 
1.0.1 */
 #define SSL_OP_NO_TLSv1_2 0
 #endif
-#ifndef SSL_OP_NO_TLSv1_3                               /* dev */
+#ifndef SSL_OP_NO_TLSv1_3                               /* needs OpenSSL >= 
1.1.1 */
 #define SSL_OP_NO_TLSv1_3 0
 #endif
 #ifndef SSL_OP_SINGLE_DH_USE                            /* needs OpenSSL >= 
0.9.6 */
@@ -1951,7 +1951,7 @@ static void ssl_sock_switchctx_set(SSL *ssl, SSL_CTX *ctx)
        SSL_set_SSL_CTX(ssl, ctx);
 }
 
-#ifdef OPENSSL_IS_BORINGSSL
+#if (OPENSSL_VERSION_NUMBER >= 0x10101000L) || defined(OPENSSL_IS_BORINGSSL)
 
 static int ssl_sock_switchctx_err_cbk(SSL *ssl, int *al, void *priv)
 {
@@ -1963,9 +1963,14 @@ static int ssl_sock_switchctx_err_cbk(SSL *ssl, int *al, 
void *priv)
        return SSL_TLSEXT_ERR_OK;
 }
 
+#ifdef OPENSSL_IS_BORINGSSL
 static int ssl_sock_switchctx_cbk(const struct ssl_early_callback_ctx *ctx)
 {
        SSL *ssl = ctx->ssl;
+#else
+static int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *arg)
+{
+#endif
        struct connection *conn;
        struct bind_conf *s;
        const uint8_t *extension_data;
@@ -1981,8 +1986,12 @@ static int ssl_sock_switchctx_cbk(const struct 
ssl_early_callback_ctx *ctx)
        conn = SSL_get_app_data(ssl);
        s = objt_listener(conn->target)->bind_conf;
 
+#ifdef OPENSSL_IS_BORINGSSL
        if (SSL_early_callback_ctx_extension_get(ctx, TLSEXT_TYPE_server_name,
                                                 &extension_data, 
&extension_len)) {
+#else
+       if (SSL_client_hello_get0_ext(ssl, TLSEXT_TYPE_server_name, 
&extension_data, &extension_len)) {
+#endif
                /*
                 * The server_name extension was given too much extensibility 
when it
                 * was written, so parsing the normal case is a bit complex.
@@ -2022,7 +2031,11 @@ static int ssl_sock_switchctx_cbk(const struct 
ssl_early_callback_ctx *ctx)
        }
 
        /* extract/check clientHello informations */
+#ifdef OPENSSL_IS_BORINGSSL
        if (SSL_early_callback_ctx_extension_get(ctx, 
TLSEXT_TYPE_signature_algorithms, &extension_data, &extension_len)) {
+#else
+       if (SSL_client_hello_get0_ext(ssl, TLSEXT_TYPE_signature_algorithms, 
&extension_data, &extension_len)) {
+#endif
                uint8_t sign;
                size_t len;
                if (extension_len < 2)
@@ -2057,13 +2070,21 @@ static int ssl_sock_switchctx_cbk(const struct 
ssl_early_callback_ctx *ctx)
                const SSL_CIPHER *cipher;
                size_t len;
                const uint8_t *cipher_suites;
+#ifdef OPENSSL_IS_BORINGSSL
                len = ctx->cipher_suites_len;
                cipher_suites = ctx->cipher_suites;
+#else
+               len = SSL_client_hello_get0_ciphers(ssl, &cipher_suites);
+#endif
                if (len % 2 != 0)
                        goto abort;
                for (; len != 0; len -= 2, cipher_suites += 2) {
+#ifdef OPENSSL_IS_BORINGSSL
                        uint16_t cipher_suite = (cipher_suites[0] << 8) | 
cipher_suites[1];
                        cipher = SSL_get_cipher_by_value(cipher_suite);
+#else
+                       cipher = SSL_CIPHER_find(ssl, cipher_suites);
+#endif
                        if (cipher && SSL_CIPHER_get_auth_nid(cipher) == 
NID_auth_ecdsa) {
                                has_ecdsa = 1;
                                break;
@@ -2152,7 +2173,12 @@ static int ssl_sock_switchctx_cbk(const struct 
ssl_early_callback_ctx *ctx)
  abort:
        /* abort handshake (was SSL_TLSEXT_ERR_ALERT_FATAL) */
        conn->err_code = CO_ER_SSL_HANDSHAKE;
+#ifdef OPENSSL_IS_BORINGSSL
        return ssl_select_cert_error;
+#else
+       *al = SSL_AD_UNRECOGNIZED_NAME;
+       return 0;
+#endif
 }
 
 #else /* OPENSSL_IS_BORINGSSL */
@@ -3647,6 +3673,9 @@ ssl_sock_initial_ctx(struct bind_conf *bind_conf)
 #ifdef OPENSSL_IS_BORINGSSL
        SSL_CTX_set_select_certificate_cb(ctx, ssl_sock_switchctx_cbk);
        SSL_CTX_set_tlsext_servername_callback(ctx, ssl_sock_switchctx_err_cbk);
+#elif (OPENSSL_VERSION_NUMBER >= 0x10101000L)
+       SSL_CTX_set_client_hello_cb(ctx, ssl_sock_switchctx_cbk, NULL);
+       SSL_CTX_set_tlsext_servername_callback(ctx, ssl_sock_switchctx_err_cbk);
 #else
        SSL_CTX_set_tlsext_servername_callback(ctx, ssl_sock_switchctx_cbk);
        SSL_CTX_set_tlsext_servername_arg(ctx, bind_conf);
@@ -6773,7 +6802,7 @@ static int parse_tls_method_minmax(char **args, int 
cur_arg, struct tls_version_
 
 static int ssl_bind_parse_tls_method_minmax(char **args, int cur_arg, struct 
proxy *px, struct ssl_bind_conf *conf, char **err)
 {
-#if !defined(OPENSSL_IS_BORINGSSL)
+#if (OPENSSL_VERSION_NUMBER < 0x10101000L) || !defined(OPENSSL_IS_BORINGSSL)
        Warning("crt-list: ssl-min-ver and ssl-max-ver are not supported with 
this Openssl version (skipped).\n");
 #endif
        return parse_tls_method_minmax(args, cur_arg, &conf->ssl_methods, err);
-- 
2.13.5

>From 6abc2b0efa191436250128e91be461a58cdc5a0c Mon Sep 17 00:00:00 2001
From: Emmanuel Hocdet <m...@gandi.net>
Date: Mon, 14 Aug 2017 11:01:25 +0200
Subject: [PATCH 3/6] MINOR: ssl: generated certificate is missing in switchctx
 early callback

Openssl 1.1.1 support switchctx early callback and generated certificate.
Generated certificate calls must be available in switchctx early callback.
---
 src/ssl_sock.c | 70 ++++++++++++++++++++++++++++++++++++----------------------
 1 file changed, 43 insertions(+), 27 deletions(-)

diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index e1cfc6697..fceccc9ba 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -1771,7 +1771,7 @@ ssl_sock_generated_cert_key(const void *data, size_t len)
 /* Generate a cert and immediately assign it to the SSL session so that the 
cert's
  * refcount is maintained regardless of the cert's presence in the LRU cache.
  */
-static SSL_CTX *
+static int
 ssl_sock_generate_certificate(const char *servername, struct bind_conf 
*bind_conf, SSL *ssl)
 {
        X509         *cacert  = bind_conf->ca_sign_cert;
@@ -1789,14 +1789,35 @@ ssl_sock_generate_certificate(const char *servername, 
struct bind_conf *bind_con
                        lru64_commit(lru, ssl_ctx, cacert, 0, (void (*)(void 
*))SSL_CTX_free);
                }
                SSL_set_SSL_CTX(ssl, ssl_ctx);
+               return 1;
        }
        else {
                ssl_ctx = ssl_sock_do_create_cert(servername, bind_conf, ssl);
                SSL_set_SSL_CTX(ssl, ssl_ctx);
                /* No LRU cache, this CTX will be released as soon as the 
session dies */
                SSL_CTX_free(ssl_ctx);
+               return 1;
        }
-       return ssl_ctx;
+       return 0;
+}
+static int
+ssl_sock_generate_certificate_from_conn(struct bind_conf *bind_conf, SSL *ssl)
+{
+       unsigned int key;
+       SSL_CTX     *ssl_ctx = NULL;
+       struct connection *conn = SSL_get_app_data(ssl);
+
+       conn_get_to_addr(conn);
+       if (conn->flags & CO_FL_ADDR_TO_SET) {
+               key = ssl_sock_generated_cert_key(&conn->addr.to, 
get_addr_len(&conn->addr.to));
+               ssl_ctx = ssl_sock_get_generated_cert(key, bind_conf);
+               if (ssl_ctx) {
+                       /* switch ctx */
+                       SSL_set_SSL_CTX(ssl, ssl_ctx);
+                       return 1;
+               }
+       }
+       return 0;
 }
 #endif /* !defined SSL_NO_GENERATE_CERTIFICATES */
 
@@ -1955,12 +1976,12 @@ static void ssl_sock_switchctx_set(SSL *ssl, SSL_CTX 
*ctx)
 
 static int ssl_sock_switchctx_err_cbk(SSL *ssl, int *al, void *priv)
 {
+       struct bind_conf *s = priv;
        (void)al; /* shut gcc stupid warning */
-       (void)priv;
 
-       if (!SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name))
-               return SSL_TLSEXT_ERR_NOACK;
-       return SSL_TLSEXT_ERR_OK;
+       if (SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name) || 
s->generate_certs)
+               return SSL_TLSEXT_ERR_OK;
+       return SSL_TLSEXT_ERR_NOACK;
 }
 
 #ifdef OPENSSL_IS_BORINGSSL
@@ -2022,6 +2043,11 @@ static int ssl_sock_switchctx_cbk(SSL *ssl, int *al, 
void *arg)
                servername = extension_data;
                servername_len = len;
        } else {
+#if (!defined SSL_NO_GENERATE_CERTIFICATES)
+               if (s->generate_certs && 
ssl_sock_generate_certificate_from_conn(s, ssl)) {
+                       return 1;
+               }
+#endif
                /* without SNI extension, is the default_ctx (need 
SSL_TLSEXT_ERR_NOACK) */
                if (!s->strict_sni) {
                        ssl_sock_switchctx_set(ssl, s->default_ctx);
@@ -2165,6 +2191,12 @@ static int ssl_sock_switchctx_cbk(SSL *ssl, int *al, 
void *arg)
                methodVersions[conf->ssl_methods.max].ssl_set_version(ssl, 
SET_MAX);
                return 1;
        }
+#if (!defined SSL_NO_GENERATE_CERTIFICATES)
+       if (s->generate_certs && ssl_sock_generate_certificate(trash.str, s, 
ssl)) {
+               /* switch ctx done in ssl_sock_generate_certificate */
+               return 1;
+       }
+#endif
        if (!s->strict_sni) {
                /* no certificate match, is the default_ctx */
                ssl_sock_switchctx_set(ssl, s->default_ctx);
@@ -2199,22 +2231,8 @@ static int ssl_sock_switchctx_cbk(SSL *ssl, int *al, 
void *priv)
        servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
        if (!servername) {
 #if (!defined SSL_NO_GENERATE_CERTIFICATES)
-               if (s->generate_certs) {
-                       struct connection *conn = SSL_get_app_data(ssl);
-                       unsigned int key;
-                       SSL_CTX *ctx;
-
-                       conn_get_to_addr(conn);
-                       if (conn->flags & CO_FL_ADDR_TO_SET) {
-                               key = 
ssl_sock_generated_cert_key(&conn->addr.to, get_addr_len(&conn->addr.to));
-                               ctx = ssl_sock_get_generated_cert(key, s);
-                               if (ctx) {
-                                       /* switch ctx */
-                                       SSL_set_SSL_CTX(ssl, ctx);
-                                       return SSL_TLSEXT_ERR_OK;
-                               }
-                       }
-               }
+               if (s->generate_certs && 
ssl_sock_generate_certificate_from_conn(s, ssl))
+                       return SSL_TLSEXT_ERR_OK;
 #endif
                if (s->strict_sni)
                        return SSL_TLSEXT_ERR_ALERT_FATAL;
@@ -2247,10 +2265,8 @@ static int ssl_sock_switchctx_cbk(SSL *ssl, int *al, 
void *priv)
        }
        if (!node || container_of(node, struct sni_ctx, name)->neg) {
 #if (!defined SSL_NO_GENERATE_CERTIFICATES)
-               SSL_CTX *ctx;
-               if (s->generate_certs &&
-                   (ctx = ssl_sock_generate_certificate(servername, s, ssl))) {
-                       /* switch ctx */
+               if (s->generate_certs && 
ssl_sock_generate_certificate(servername, s, ssl)) {
+                       /* switch ctx done in ssl_sock_generate_certificate */
                        return SSL_TLSEXT_ERR_OK;
                }
 #endif
@@ -3678,8 +3694,8 @@ ssl_sock_initial_ctx(struct bind_conf *bind_conf)
        SSL_CTX_set_tlsext_servername_callback(ctx, ssl_sock_switchctx_err_cbk);
 #else
        SSL_CTX_set_tlsext_servername_callback(ctx, ssl_sock_switchctx_cbk);
-       SSL_CTX_set_tlsext_servername_arg(ctx, bind_conf);
 #endif
+       SSL_CTX_set_tlsext_servername_arg(ctx, bind_conf);
 #endif
        return cfgerr;
 }
-- 
2.13.5

>From 7f8d3065d86a5177c3f664d4a2ed34e62ff60bb3 Mon Sep 17 00:00:00 2001
From: Olivier Houchard <ohouch...@haproxy.com>
Date: Fri, 22 Sep 2017 18:26:28 +0200
Subject: [PATCH 4/6] ssl: Handle early data with OpenSSL 1.1.1

When compiled with Openssl >= 1.1.1, before attempting to do the handshake,
try to read any early data. If any early data is present, then we'll create
the session, read the data, and handle the request before we're doing the
handshake.
As early data do have security implication, we let the origin server know
the request comes from early data by adding the "Early-Data" header.
---
 doc/configuration.txt      |   4 +
 include/proto/connection.h |   1 +
 include/types/connection.h |   6 +-
 include/types/listener.h   |   2 +
 src/proto_http.c           |  16 ++++
 src/proto_tcp.c            |   4 +-
 src/session.c              |   2 +-
 src/ssl_sock.c             | 186 +++++++++++++++++++++++++++++++++++++++------
 8 files changed, 193 insertions(+), 28 deletions(-)

diff --git a/doc/configuration.txt b/doc/configuration.txt
index 66b15b30f..bc5164006 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -10237,6 +10237,10 @@ accept-proxy
   usable. See also "tcp-request connection expect-proxy" for a finer-grained
   setting of which client is allowed to use the protocol.
 
+allow-0rtt
+  Allow receiving early data when using TLS 1.3. This is disabled by default,
+  due to security considerations.
+
 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-
diff --git a/include/proto/connection.h b/include/proto/connection.h
index 0044d8185..7060046b8 100644
--- a/include/proto/connection.h
+++ b/include/proto/connection.h
@@ -494,6 +494,7 @@ static inline void conn_init(struct connection *conn)
        conn->obj_type = OBJ_TYPE_CONN;
        conn->flags = CO_FL_NONE;
        conn->data = NULL;
+       conn->tmp_early_data = -1;
        conn->owner = NULL;
        conn->send_proxy_ofs = 0;
        conn->handle.fd = DEAD_FD_MAGIC;
diff --git a/include/types/connection.h b/include/types/connection.h
index c1560cb6d..661e12b91 100644
--- a/include/types/connection.h
+++ b/include/types/connection.h
@@ -95,11 +95,12 @@ enum {
        CO_FL_ADDR_FROM_SET = 0x00001000,  /* addr.from is set */
        CO_FL_ADDR_TO_SET   = 0x00002000,  /* addr.to is set */
 
-       /* unused : 0x00004000 */
-       /* unused : 0x00008000 */
+       CO_FL_EARLY_SSL_HS  = 0x00004000,  /* We have early data pending, don't 
start SSL handhsake yet */
+       CO_FL_EARLY_DATA    = 0x00008000,  /* At least some of the data are 
early data */
        /* unused : 0x00010000 */
        /* unused : 0x00020000 */
 
+
        /* flags used to remember what shutdown have been performed/reported */
        CO_FL_SOCK_RD_SH    = 0x00040000,  /* SOCK layer was notified about 
shutr/read0 */
        CO_FL_SOCK_WR_SH    = 0x00080000,  /* SOCK layer asked for shutw */
@@ -299,6 +300,7 @@ struct connection {
        const struct xprt_ops *xprt;  /* operations at the transport layer */
        const struct data_cb  *data;  /* data layer callbacks. Must be set 
before xprt->init() */
        void *xprt_ctx;               /* general purpose pointer, initialized 
to NULL */
+       int tmp_early_data;           /* 1st byte of early data, if any */
        void *owner;                  /* pointer to upper layer's entity (eg: 
session, stream interface) */
        int xprt_st;                  /* transport layer state, initialized to 
zero */
        union conn_handle handle;     /* connection handle at the socket layer 
*/
diff --git a/include/types/listener.h b/include/types/listener.h
index 3d9ad7f7b..19d1dbe3b 100644
--- a/include/types/listener.h
+++ b/include/types/listener.h
@@ -105,6 +105,7 @@ enum li_state {
 #define BC_SSL_O_NONE           0x0000
 #define BC_SSL_O_NO_TLS_TICKETS 0x0100 /* disable session resumption tickets */
 #define BC_SSL_O_PREF_CLIE_CIPH 0x0200  /* prefer client ciphers */
+#define BC_SSL_O_EARLY_DATA     0x0400  /* Accept early data */
 #endif
 
 /* ssl "bind" settings */
@@ -120,6 +121,7 @@ struct ssl_bind_conf {
 #endif
        int verify:3;              /* verify method (set of SSL_VERIFY_* flags) 
*/
        int no_ca_names:1;         /* do not send ca names to clients (ca_file 
related) */
+       int early_data:1;          /* early data allowed */
        char *ca_file;             /* CAfile to use on verify */
        char *crl_file;            /* CRLfile to use on verify */
        char *ciphers;             /* cipher suite to use if non-null */
diff --git a/src/proto_http.c b/src/proto_http.c
index 066204166..799b7bb27 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -3288,6 +3288,7 @@ int http_process_req_common(struct stream *s, struct 
channel *req, int an_bit, s
        struct cond_wordlist *wl;
        enum rule_result verdict;
        int deny_status = HTTP_ERR_403;
+       struct connection *conn = objt_conn(sess->origin);
 
        if (unlikely(msg->msg_state < HTTP_MSG_BODY)) {
                /* we need more data */
@@ -3334,6 +3335,21 @@ int http_process_req_common(struct stream *s, struct 
channel *req, int an_bit, s
                }
        }
 
+       if (conn && conn->flags & CO_FL_EARLY_DATA) {
+               struct hdr_ctx ctx;
+
+               ctx.idx = 0;
+               if (!http_find_header2("Early-Data", strlen("Early-Data"),
+                   s->req.buf->p, &txn->hdr_idx, &ctx)) {
+                       if (unlikely(http_header_add_tail2(&txn->req,
+                           &txn->hdr_idx, "Early-Data: 1",
+                           strlen("Early-Data: 1"))) < 0) {
+                               goto return_bad_req;
+                        }
+               }
+
+       }
+
        /* OK at this stage, we know that the request was accepted according to
         * the http-request rules, we can check for the stats. Note that the
         * URI is detected *before* the req* rules in order not to be affected
diff --git a/src/proto_tcp.c b/src/proto_tcp.c
index e4e6483db..b43fdef59 100644
--- a/src/proto_tcp.c
+++ b/src/proto_tcp.c
@@ -550,8 +550,10 @@ int tcp_connect_server(struct connection *conn, int data, 
int delack)
                return SF_ERR_RESOURCE;
        }
 
-       if (conn->flags & (CO_FL_HANDSHAKE | CO_FL_WAIT_L4_CONN)) {
+       if (conn->flags & (CO_FL_HANDSHAKE | CO_FL_WAIT_L4_CONN | 
CO_FL_EARLY_SSL_HS)) {
                conn_sock_want_send(conn);  /* for connect status, proxy 
protocol or SSL */
+               if (conn->flags & CO_FL_EARLY_SSL_HS)
+                       conn_xprt_want_send(conn);
        }
        else {
                /* If there's no more handshake, we need to notify the data
diff --git a/src/session.c b/src/session.c
index bc0b6d643..ecfa2f14d 100644
--- a/src/session.c
+++ b/src/session.c
@@ -240,7 +240,7 @@ int session_accept_fd(struct listener *l, int cfd, struct 
sockaddr_storage *addr
         *           v       |           |        |
         *          conn -- owner ---> task <-----+
         */
-       if (cli_conn->flags & CO_FL_HANDSHAKE) {
+       if (cli_conn->flags & (CO_FL_HANDSHAKE | CO_FL_EARLY_SSL_HS)) {
                if (unlikely((sess->task = task_new()) == NULL))
                        goto out_free_sess;
 
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index fceccc9ba..9cb1b7bac 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -1314,7 +1314,8 @@ void ssl_sock_infocbk(const SSL *ssl, int where, int ret)
 
        if (where & SSL_CB_HANDSHAKE_START) {
                /* Disable renegotiation (CVE-2009-3555) */
-               if (conn->flags & CO_FL_CONNECTED) {
+               if ((conn->flags & (CO_FL_CONNECTED | CO_FL_EARLY_SSL_HS)) ==
+                   CO_FL_CONNECTED) {
                        conn->flags |= CO_FL_ERROR;
                        conn->err_code = CO_ER_SSL_RENEG;
                }
@@ -2003,10 +2004,13 @@ static int ssl_sock_switchctx_cbk(SSL *ssl, int *al, 
void *arg)
        size_t servername_len;
        struct ebmb_node *node, *n, *node_ecdsa = NULL, *node_rsa = NULL, 
*node_anonymous = NULL;
        int i;
+       int allow_early = 0;
 
        conn = SSL_get_app_data(ssl);
        s = objt_listener(conn->target)->bind_conf;
 
+       if (s->ssl_options & BC_SSL_O_EARLY_DATA)
+               allow_early = 1;
 #ifdef OPENSSL_IS_BORINGSSL
        if (SSL_early_callback_ctx_extension_get(ctx, TLSEXT_TYPE_server_name,
                                                 &extension_data, 
&extension_len)) {
@@ -2045,13 +2049,13 @@ static int ssl_sock_switchctx_cbk(SSL *ssl, int *al, 
void *arg)
        } else {
 #if (!defined SSL_NO_GENERATE_CERTIFICATES)
                if (s->generate_certs && 
ssl_sock_generate_certificate_from_conn(s, ssl)) {
-                       return 1;
+                       goto allow_early;
                }
 #endif
                /* without SNI extension, is the default_ctx (need 
SSL_TLSEXT_ERR_NOACK) */
                if (!s->strict_sni) {
                        ssl_sock_switchctx_set(ssl, s->default_ctx);
-                       return 1;
+                       goto allow_early;
                }
                goto abort;
        }
@@ -2189,19 +2193,29 @@ static int ssl_sock_switchctx_cbk(SSL *ssl, int *al, 
void *arg)
                ssl_sock_switchctx_set(ssl, container_of(node, struct sni_ctx, 
name)->ctx);
                methodVersions[conf->ssl_methods.min].ssl_set_version(ssl, 
SET_MIN);
                methodVersions[conf->ssl_methods.max].ssl_set_version(ssl, 
SET_MAX);
-               return 1;
+               if (conf->early_data)
+                       allow_early = 1;
+               goto allow_early;
        }
 #if (!defined SSL_NO_GENERATE_CERTIFICATES)
        if (s->generate_certs && ssl_sock_generate_certificate(trash.str, s, 
ssl)) {
                /* switch ctx done in ssl_sock_generate_certificate */
-               return 1;
+               goto allow_early;
        }
 #endif
        if (!s->strict_sni) {
                /* no certificate match, is the default_ctx */
                ssl_sock_switchctx_set(ssl, s->default_ctx);
-               return 1;
        }
+allow_early:
+#ifdef OPENSSL_IS_BORINGSS
+       if (allow_early)
+               SSL_set_early_data_enabled(ssl, 1);
+#else
+       if (!allow_early)
+               SSL_set_max_early_data(ssl, 0);
+#endif
+       return 1;
  abort:
        /* abort handshake (was SSL_TLSEXT_ERR_ALERT_FATAL) */
        conn->err_code = CO_ER_SSL_HANDSHAKE;
@@ -2273,6 +2287,7 @@ static int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void 
*priv)
                if (s->strict_sni)
                        return SSL_TLSEXT_ERR_ALERT_FATAL;
                ssl_sock_switchctx_set(ssl, s->default_ctx);
+
                return SSL_TLSEXT_ERR_OK;
        }
 
@@ -3912,7 +3927,17 @@ int ssl_sock_prepare_ctx(struct bind_conf *bind_conf, 
struct ssl_bind_conf *ssl_
                int i;
                EC_KEY  *ecdh;
                const char *ecdhe = (ssl_conf && ssl_conf->ecdhe) ? 
ssl_conf->ecdhe :
-                       (bind_conf->ssl_conf.ecdhe ? bind_conf->ssl_conf.ecdhe 
: ECDHE_DEFAULT_CURVE);
+                       (bind_conf->ssl_conf.ecdhe ? bind_conf->ssl_conf.ecdhe :
+#if (OPENSSL_VERSION_NUMBER >= 0x10101000L)
+                        NULL);
+
+               if (ecdhe == NULL) {
+                       SSL_CTX_set_dh_auto(ctx, 1);
+                       goto no_ecdhe;
+               }
+#else
+                        ECDHE_DEFAULT_CURVE);
+#endif
 
                i = OBJ_sn2nid(ecdhe);
                if (!i || ((ecdh = EC_KEY_new_by_curve_name(i)) == NULL)) {
@@ -3927,6 +3952,9 @@ int ssl_sock_prepare_ctx(struct bind_conf *bind_conf, 
struct ssl_bind_conf *ssl_
        }
 #endif
 
+#if (OPENSSL_VERSION_NUMBER >= 0x10101000L)
+no_ecdhe:
+#endif
        return cfgerr;
 }
 
@@ -4627,6 +4655,9 @@ static int ssl_sock_init(struct connection *conn)
 
                /* leave init state and start handshake */
                conn->flags |= CO_FL_SSL_WAIT_HS | CO_FL_WAIT_L6_CONN;
+#if OPENSSL_VERSION_NUMBER >= 0x0101000L
+               conn->flags |= CO_FL_EARLY_SSL_HS;
+#endif
 
                sslconns++;
                totalsslconns++;
@@ -4654,6 +4685,26 @@ int ssl_sock_handshake(struct connection *conn, unsigned 
int flag)
        if (!conn->xprt_ctx)
                goto out_error;
 
+#if OPENSSL_VERSION_NUMBER >= 0x10101000L
+       /*
+        * Check if we have early data. If we do, we have to read them
+        * before SSL_do_handshake() is called, And there's no way to
+        * detect early data, except to try to read them
+        */
+       if (conn->flags & CO_FL_EARLY_SSL_HS) {
+               size_t read_data;
+
+               ret = SSL_read_early_data(conn->xprt_ctx, &conn->tmp_early_data,
+                   1, &read_data);
+               if (ret == SSL_READ_EARLY_DATA_ERROR)
+                       goto check_error;
+               if (ret == SSL_READ_EARLY_DATA_SUCCESS) {
+                       conn->flags &= ~(CO_FL_SSL_WAIT_HS | 
CO_FL_WAIT_L6_CONN);
+                       return 1;
+               } else
+                       conn->flags &= ~CO_FL_EARLY_SSL_HS;
+       }
+#endif
        /* If we use SSL_do_handshake to process a reneg initiated by
         * the remote peer, it sometimes returns SSL_ERROR_SSL.
         * Usually SSL_write and SSL_read are used and process implicitly
@@ -4753,8 +4804,8 @@ int ssl_sock_handshake(struct connection *conn, unsigned 
int flag)
                /* read some data: consider handshake completed */
                goto reneg_ok;
        }
-
        ret = SSL_do_handshake(conn->xprt_ctx);
+check_error:
        if (ret != 1) {
                /* handshake did not complete, let's find why */
                ret = SSL_get_error(conn->xprt_ctx, ret);
@@ -4845,6 +4896,13 @@ reneg_ok:
        if (global_ssl.async)
                SSL_clear_mode(conn->xprt_ctx, SSL_MODE_ASYNC);
 #endif
+#if OPENSSL_VERSION_NUMBER >= 0x10101000L
+       /* Once the handshake succeeded, we can consider the early data
+        * as valid.
+        */
+       if (conn->flags & CO_FL_EARLY_DATA)
+               conn->flags &= ~CO_FL_EARLY_DATA;
+#endif
        /* Handshake succeeded */
        if (!SSL_session_reused(conn->xprt_ctx)) {
                if (objt_server(conn->target)) {
@@ -4913,8 +4971,19 @@ static int ssl_sock_to_buf(struct connection *conn, 
struct buffer *buf, int coun
                return 0;
 
        /* let's realign the buffer to optimize I/O */
-       if (buffer_empty(buf))
+       if (buffer_empty(buf)) {
                buf->p = buf->data;
+#if (OPENSSL_VERSION_NUMBER >= 0x10101000L)
+               /*
+                * If we're done reading the early data, and we're using
+                * a new buffer, then we know for sure we're not tainted
+                * with early data anymore
+                */
+               if ((conn->flags & (CO_FL_EARLY_SSL_HS |CO_FL_EARLY_DATA)) ==
+                   CO_FL_EARLY_DATA)
+                       conn->flags &= ~CO_FL_EARLY_DATA;
+#endif
+       }
 
        /* read the largest possible block. For this, we perform only one call
         * to recv() unless the buffer wraps and we exactly fill the first hunk,
@@ -4922,6 +4991,8 @@ static int ssl_sock_to_buf(struct connection *conn, 
struct buffer *buf, int coun
         * EINTR too.
         */
        while (count > 0) {
+               int need_out = 0;
+
                /* first check if we have some room after p+i */
                try = buf->data + buf->size - (buf->p + buf->i);
                /* otherwise continue between data and p-o */
@@ -4932,7 +5003,43 @@ static int ssl_sock_to_buf(struct connection *conn, 
struct buffer *buf, int coun
                }
                if (try > count)
                        try = count;
+               if (((conn->flags & (CO_FL_EARLY_SSL_HS | CO_FL_EARLY_DATA)) ==
+                   CO_FL_EARLY_SSL_HS) && conn->tmp_early_data != -1) {
+                       *bi_end(buf) = conn->tmp_early_data;
+                       done++;
+                       try--;
+                       count--;
+                       buf->i++;
+                       conn->tmp_early_data = -1;
+                       continue;
+               }
 
+#if (OPENSSL_VERSION_NUMBER >= 0x10101000L)
+               if (conn->flags & CO_FL_EARLY_SSL_HS) {
+                       size_t read_length;
+
+                       ret = SSL_read_early_data(conn->xprt_ctx,
+                           bi_end(buf), try, &read_length);
+                       if (read_length > 0)
+                               conn->flags |= CO_FL_EARLY_DATA;
+                       if (ret == SSL_READ_EARLY_DATA_SUCCESS ||
+                           ret == SSL_READ_EARLY_DATA_FINISH) {
+                               if (ret == SSL_READ_EARLY_DATA_FINISH) {
+                                       /*
+                                        * We're done reading the early data,
+                                        * let's make the handshake
+                                        */
+                                       conn->flags &= ~CO_FL_EARLY_SSL_HS;
+                                       conn->flags |= CO_FL_SSL_WAIT_HS;
+                                       need_out = 1;
+                                       if (read_length == 0)
+                                               break;
+                               }
+                               ret = read_length;
+
+                       }
+               } else
+#endif
                ret = SSL_read(conn->xprt_ctx, bi_end(buf), try);
                if (conn->flags & CO_FL_ERROR) {
                        /* CO_FL_ERROR may be set by ssl_sock_infocbk */
@@ -4943,20 +5050,6 @@ static int ssl_sock_to_buf(struct connection *conn, 
struct buffer *buf, int coun
                        done += ret;
                        count -= ret;
                }
-               else if (ret == 0) {
-                       ret =  SSL_get_error(conn->xprt_ctx, ret);
-                       if (ret != SSL_ERROR_ZERO_RETURN) {
-                               /* error on protocol or underlying transport */
-                               if ((ret != SSL_ERROR_SYSCALL)
-                                    || (errno && (errno != EAGAIN)))
-                                       conn->flags |= CO_FL_ERROR;
-
-                               /* Clear openssl global errors stack */
-                               ssl_sock_dump_errors(conn);
-                               ERR_clear_error();
-                       }
-                       goto read0;
-               }
                else {
                        ret =  SSL_get_error(conn->xprt_ctx, ret);
                        if (ret == SSL_ERROR_WANT_WRITE) {
@@ -4985,10 +5078,13 @@ static int ssl_sock_to_buf(struct connection *conn, 
struct buffer *buf, int coun
                                /* we need to poll for retry a read later */
                                fd_cant_recv(conn->handle.fd);
                                break;
-                       }
+                       } else if (ret == SSL_ERROR_ZERO_RETURN)
+                               goto read0;
                        /* otherwise it's a real error */
                        goto out_error;
                }
+               if (need_out)
+                       break;
        }
  leave:
        conn_cond_update_sock_polling(conn);
@@ -5036,6 +5132,10 @@ static int ssl_sock_from_buf(struct connection *conn, 
struct buffer *buf, int fl
         * in which case we accept to do it once again.
         */
        while (buf->o) {
+#if (OPENSSL_VERSION_NUMBER >= 0x10101000L)
+               size_t written_data;
+#endif
+
                try = bo_contig_data(buf);
 
                if (!(flags & CO_SFL_STREAMER) &&
@@ -5051,6 +5151,27 @@ static int ssl_sock_from_buf(struct connection *conn, 
struct buffer *buf, int fl
                        conn->xprt_st |= SSL_SOCK_SEND_UNLIMITED;
                }
 
+#if (OPENSSL_VERSION_NUMBER >= 0x10101000L)
+               if (!SSL_is_init_finished(conn->xprt_ctx)) {
+                       unsigned int max_early;
+
+                       if (conn->tmp_early_data == -1)
+                               conn->tmp_early_data = 0;
+
+                       max_early = SSL_get_max_early_data(conn->xprt_ctx);
+                       if (try + conn->tmp_early_data > max_early) {
+                               try -= (try + conn->tmp_early_data) - max_early;
+                               if (try <= 0)
+                                       break;
+                       }
+                       ret = SSL_write_early_data(conn->xprt_ctx, bo_ptr(buf), 
try, &written_data);
+                       if (ret == 1) {
+                               ret = written_data;
+                               conn->tmp_early_data += ret;
+                       }
+
+               } else
+#endif
                ret = SSL_write(conn->xprt_ctx, bo_ptr(buf), try);
 
                if (conn->flags & CO_FL_ERROR) {
@@ -6841,6 +6962,19 @@ static int bind_parse_no_tls_tickets(char **args, int 
cur_arg, struct proxy *px,
        return 0;
 }
 
+/* parse the "allow-0rtt" bind keyword */
+static int ssl_bind_parse_allow_0rtt(char **args, int cur_arg, struct proxy 
*px, struct ssl_bind_conf *conf, char **err)
+{
+       conf->early_data = 1;
+       return 0;
+}
+
+static int bind_parse_allow_0rtt(char **args, int cur_arg, struct proxy *px, 
struct bind_conf *conf, char **err)
+{
+       conf->ssl_options |= BC_SSL_O_EARLY_DATA;
+       return 0;
+}
+
 /* parse the "npn" bind keyword */
 static int ssl_bind_parse_npn(char **args, int cur_arg, struct proxy *px, 
struct ssl_bind_conf *conf, char **err)
 {
@@ -7380,6 +7514,8 @@ static int ssl_parse_default_bind_options(char **args, 
int section_type, struct
        while (*(args[i])) {
                if (!strcmp(args[i], "no-tls-tickets"))
                        global_ssl.listen_default_ssloptions |= 
BC_SSL_O_NO_TLS_TICKETS;
+               else if (!strcmp(args[i], "allow-0rtt"))
+                       global_ssl.listen_default_ssloptions |= 
BC_SSL_O_EARLY_DATA;
                else if (!strcmp(args[i], "prefer-client-ciphers"))
                        global_ssl.listen_default_ssloptions |= 
BC_SSL_O_PREF_CLIE_CIPH;
                else if (!strcmp(args[i], "ssl-min-ver") || !strcmp(args[i], 
"ssl-max-ver")) {
@@ -8045,6 +8181,7 @@ static struct acl_kw_list acl_kws = {ILH, {
  * not enabled.
  */
 static struct ssl_bind_kw ssl_bind_kws[] = {
+       { "allow-0rtt",            ssl_bind_parse_allow_0rtt,       0 }, /* 
allow 0-RTT */
        { "alpn",                  ssl_bind_parse_alpn,             1 }, /* set 
ALPN supported protocols */
        { "ca-file",               ssl_bind_parse_ca_file,          1 }, /* set 
CAfile to process verify on client cert */
        { "ciphers",               ssl_bind_parse_ciphers,          1 }, /* set 
SSL cipher suite */
@@ -8060,6 +8197,7 @@ static struct ssl_bind_kw ssl_bind_kws[] = {
 };
 
 static struct bind_kw_list bind_kws = { "SSL", { }, {
+       { "allow-0rtt",            bind_parse_allow_0rtt,         0 }, /* Allow 
0RTT */
        { "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 */
-- 
2.13.5

>From ffdab259448982edd71a28e5cdc6df3294e2c345 Mon Sep 17 00:00:00 2001
From: Olivier Houchard <ohouch...@haproxy.com>
Date: Mon, 2 Oct 2017 11:51:03 +0200
Subject: [PATCH 5/6] MINOR: ssl/proto_http: Add keywords to take care of early
 data.

Add a new sample fetch, "ssl_fc_has_early", a boolean that will be true
if early data were sent, and a new action, "wait-for-handshake", if used,
the request won't be forwarded until the SSL handshake is done.
---
 doc/configuration.txt  |  9 ++++++++
 src/ssl_sock.c         | 58 ++++++++++++++++++++++++++++++++++++++++++++------
 src/stream_interface.c | 11 ++++++++++
 3 files changed, 71 insertions(+), 7 deletions(-)

diff --git a/doc/configuration.txt b/doc/configuration.txt
index bc5164006..329aff778 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -4163,6 +4163,10 @@ http-request { allow | auth [realm <realm>] | redirect 
<rule> |
       pass the first router, though it's still delivered to local networks. Do
       not use it unless you fully understand how it works.
 
+    - "wait-for-handshake" : this will delay the processing of the request
+      until the SSL handshake happened. This is mostly useful to delay
+      processing early data until we're sure they are valid.
+
   There is no limit to the number of http-request statements per instance.
 
   It is important to know that http-request rules are processed very early in
@@ -14252,6 +14256,11 @@ ssl_fc_has_crt : boolean
   from the cache or the ticket. So prefer "ssl_c_used" if you want to check if
   current SSL session uses a client certificate.
 
+ssl_fc_has_early : boolean
+  Returns true if early data were sent, and the handshake didn't happen yet. As
+  it has security implications, it is useful to be able to refuse those, or
+  wait until the handshake happened.
+
 ssl_fc_has_sni : boolean
   This checks for the presence of a Server Name Indication TLS extension (SNI)
   in an incoming connection was made over an SSL/TLS transport layer. Returns
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index 9cb1b7bac..35c85820d 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -95,6 +95,7 @@
 #include <proto/openssl-compat.h>
 #include <proto/pattern.h>
 #include <proto/proto_tcp.h>
+#include <proto/proto_http.h>
 #include <proto/server.h>
 #include <proto/stream_interface.h>
 #include <proto/log.h>
@@ -4896,13 +4897,6 @@ reneg_ok:
        if (global_ssl.async)
                SSL_clear_mode(conn->xprt_ctx, SSL_MODE_ASYNC);
 #endif
-#if OPENSSL_VERSION_NUMBER >= 0x10101000L
-       /* Once the handshake succeeded, we can consider the early data
-        * as valid.
-        */
-       if (conn->flags & CO_FL_EARLY_DATA)
-               conn->flags &= ~CO_FL_EARLY_DATA;
-#endif
        /* Handshake succeeded */
        if (!SSL_session_reused(conn->xprt_ctx)) {
                if (objt_server(conn->target)) {
@@ -5653,6 +5647,23 @@ static int ssl_sock_get_alpn(const struct connection 
*conn, const char **str, in
 
 /***** Below are some sample fetching functions for ACL/patterns *****/
 
+static int
+smp_fetch_ssl_fc_has_early(const struct arg *args, struct sample *smp, const 
char *kw, void *private)
+{
+       struct connection *conn;
+
+       conn = objt_conn(smp->sess->origin);
+       if (!conn || conn->xprt != &ssl_sock)
+               return 0;
+
+       smp->flags = 0;
+       smp->data.type = SMP_T_BOOL;
+       smp->data.u.sint = (conn->flags & CO_FL_EARLY_DATA) ? 1 : 0;
+
+       return 1;
+
+}
+
 /* boolean, returns true if client cert was present */
 static int
 smp_fetch_ssl_fc_has_crt(const struct arg *args, struct sample *smp, const 
char *kw, void *private)
@@ -8144,6 +8155,8 @@ static struct sample_fetch_kw_list sample_fetch_keywords 
= {ILH, {
        { "ssl_fc_alg_keysize",     smp_fetch_ssl_fc_alg_keysize, 0,            
       NULL,    SMP_T_SINT, SMP_USE_L5CLI },
        { "ssl_fc_cipher",          smp_fetch_ssl_fc_cipher,      0,            
       NULL,    SMP_T_STR,  SMP_USE_L5CLI },
        { "ssl_fc_has_crt",         smp_fetch_ssl_fc_has_crt,     0,            
       NULL,    SMP_T_BOOL, SMP_USE_L5CLI },
+       { "ssl_fc_has_early",       smp_fetch_ssl_fc_has_early,   0,
+       NULL,    SMP_T_BOOL, SMP_USE_L5CLI },
        { "ssl_fc_has_sni",         smp_fetch_ssl_fc_has_sni,     0,            
       NULL,    SMP_T_BOOL, SMP_USE_L5CLI },
        { "ssl_fc_is_resumed",      smp_fetch_ssl_fc_is_resumed,  0,            
       NULL,    SMP_T_BOOL, SMP_USE_L5CLI },
 #ifdef OPENSSL_NPN_NEGOTIATED
@@ -8322,6 +8335,35 @@ static struct xprt_ops ssl_sock = {
        .name     = "SSL",
 };
 
+enum act_return ssl_action_wait_for_hs(struct act_rule *rule, struct proxy *px,
+    struct session *sess, struct stream *s, int flags)
+{
+       struct connection *conn;
+
+       conn = objt_conn(sess->origin);
+
+       if (conn) {
+               if (conn->flags & (CO_FL_EARLY_SSL_HS | CO_FL_SSL_WAIT_HS))
+               {
+                       s->req.flags |= CF_READ_NULL;
+                       return ACT_RET_YIELD;
+               }
+       }
+       return (ACT_RET_CONT);
+}
+
+static enum act_parse_ret ssl_parse_wait_for_hs(const char **args, int 
*orig_arg, struct proxy *px, struct act_rule *rule, char **err)
+{
+       rule->action_ptr = ssl_action_wait_for_hs;
+
+       return ACT_RET_PRS_OK;
+}
+
+static struct action_kw_list http_req_actions = {ILH, {
+       { "wait-for-handshake", ssl_parse_wait_for_hs },
+       { /* END */ }
+}};
+
 #if (OPENSSL_VERSION_NUMBER >= 0x1000200fL && !defined OPENSSL_NO_TLSEXT && 
!defined OPENSSL_IS_BORINGSSL && !defined LIBRESSL_VERSION_NUMBER)
 
 static void ssl_sock_sctl_free_func(void *parent, void *ptr, CRYPTO_EX_DATA 
*ad, int idx, long argl, void *argp)
@@ -8424,6 +8466,8 @@ static void __ssl_sock_init(void)
 #endif
        /* Load SSL string for the verbose & debug mode. */
        ERR_load_SSL_strings();
+
+       http_req_keywords_register(&http_req_actions);
 }
 
 #ifndef OPENSSL_NO_ENGINE
diff --git a/src/stream_interface.c b/src/stream_interface.c
index 53b201c08..93c3e24b1 100644
--- a/src/stream_interface.c
+++ b/src/stream_interface.c
@@ -572,6 +572,17 @@ static int si_conn_wake_cb(struct connection *conn)
        if (conn->flags & CO_FL_ERROR)
                si->flags |= SI_FL_ERR;
 
+       /* If we had early data, and the handshake ended, then
+        * we can remove the flag, and attempt to wake the task up,
+        * in the event there's an analyser waiting for the end of
+        * the handshake.
+        */
+       if ((conn->flags & (CO_FL_EARLY_DATA | CO_FL_EARLY_SSL_HS)) ==
+           CO_FL_EARLY_DATA) {
+               conn->flags &= ~CO_FL_EARLY_DATA;
+               task_wakeup(si_task(si), TASK_WOKEN_MSG);
+       }
+
        if ((si->state < SI_ST_EST) &&
            (conn->flags & (CO_FL_CONNECTED | CO_FL_HANDSHAKE)) == 
CO_FL_CONNECTED) {
                si->exp = TICK_ETERNITY;
-- 
2.13.5

>From 2544b6a4e4c3b74eae668464189414c6260c49ef Mon Sep 17 00:00:00 2001
From: Olivier Houchard <ohouch...@haproxy.com>
Date: Mon, 2 Oct 2017 16:12:07 +0200
Subject: [PATCH 6/6] MINOR: http: Mark the 425 code as "Too Early".

---
 include/types/proto_http.h |  1 +
 src/proto_http.c           | 12 +++++++++++-
 2 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/include/types/proto_http.h b/include/types/proto_http.h
index 027bfce42..cf0fdb698 100644
--- a/include/types/proto_http.h
+++ b/include/types/proto_http.h
@@ -196,6 +196,7 @@ enum {
        HTTP_ERR_403,
        HTTP_ERR_405,
        HTTP_ERR_408,
+       HTTP_ERR_425,
        HTTP_ERR_429,
        HTTP_ERR_500,
        HTTP_ERR_502,
diff --git a/src/proto_http.c b/src/proto_http.c
index 799b7bb27..d8bf2a90a 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -140,6 +140,7 @@ const int http_err_codes[HTTP_ERR_SIZE] = {
        [HTTP_ERR_403] = 403,
        [HTTP_ERR_405] = 405,
        [HTTP_ERR_408] = 408,
+       [HTTP_ERR_425] = 425,
        [HTTP_ERR_429] = 429,
        [HTTP_ERR_500] = 500,
        [HTTP_ERR_502] = 502,
@@ -188,6 +189,14 @@ static const char *http_err_msgs[HTTP_ERR_SIZE] = {
        "\r\n"
        "<html><body><h1>408 Request Time-out</h1>\nYour browser didn't send a 
complete request in time.\n</body></html>\n",
 
+       [HTTP_ERR_425] =
+       "HTTP/1.0 425 Too Early\r\n"
+       "Cache-Control: no-cache\r\n"
+       "Connection: close\r\n"
+       "Content-Type: text/html\r\n"
+       "\r\n"
+       "<html><body><h1>425 Too Early</h1>\nYour browser sent early 
data></html>\n",
+
        [HTTP_ERR_429] =
        "HTTP/1.0 429 Too Many Requests\r\n"
        "Cache-Control: no-cache\r\n"
@@ -332,7 +341,7 @@ const char *get_reason(unsigned int status)
        case 422: return "Unprocessable entity";
        case 423: return "Locked";
        case 424: return "Method failure";
-       case 425: return "Unordered Collection";
+       case 425: return "Too Early";
        case 426: return "Upgrade Required";
        case 428: return "Precondition Required";
        case 429: return "Too Many Requests";
@@ -378,6 +387,7 @@ static const int http_get_status_idx(unsigned int status)
        case 403: return HTTP_ERR_403;
        case 405: return HTTP_ERR_405;
        case 408: return HTTP_ERR_408;
+       case 425: return HTTP_ERR_425;
        case 429: return HTTP_ERR_429;
        case 500: return HTTP_ERR_500;
        case 502: return HTTP_ERR_502;
-- 
2.13.5

Reply via email to