Hi,

The attached patches makes TLS 1.3 session resumption work (it is a bit
different than the previous version, as the session is created after the
handshake), and enable sending early data to the server, as long as the
client used early data (we can't afford to send early data without knowing
if the client can handle a 425 too early, as that's the only thing we can
return if the server rejected the early data, as we no longer have them to
resend them).
Any feedback is welcome, as it's been mostly tested with only OpenSSL 1.1.1.

Regards,

Olivier
>From 7db328b4e5028a80c9817049108f5625513a87e8 Mon Sep 17 00:00:00 2001
From: Olivier Houchard <cog...@ci0.org>
Date: Thu, 2 Nov 2017 19:04:38 +0100
Subject: [PATCH 1/4] BUG/MINOR; ssl: Don't assume we have a ssl_bind_conf
 because a SNI is matched.

We only have a ssl_bind_conf if crt-list is used, however we can still
match a certificate SNI, so don't assume we have a ssl_bind_conf.
---
 src/ssl_sock.c | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index b1d39dbbd..66930d220 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -2267,11 +2267,13 @@ static int ssl_sock_switchctx_cbk(SSL *ssl, int *al, 
void *arg)
                /* switch ctx */
                struct ssl_bind_conf *conf = container_of(node, struct sni_ctx, 
name)->conf;
                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);
-               if (conf->early_data)
-                       allow_early = 1;
-               goto allow_early;
+                       if (conf) {
+                               
methodVersions[conf->ssl_methods.min].ssl_set_version(ssl, SET_MIN);
+                               
methodVersions[conf->ssl_methods.max].ssl_set_version(ssl, SET_MAX);
+                               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)) {
-- 
2.13.0

>From c2728dd7ee527c1eb99588b17eb4f917d622fef1 Mon Sep 17 00:00:00 2001
From: Olivier Houchard <cog...@ci0.org>
Date: Fri, 3 Nov 2017 13:43:35 +0100
Subject: [PATCH 2/4] MINOR: ssl: Handle session resumption with TLS 1.3

With TLS 1.3, session aren't established until after the main handshake
has completed. So we can't just rely on calling SSL_get1_session(). Instead,
we now register a callback for the "new session" event. This should work for
previous versions of TLS as well.
---
 src/ssl_sock.c | 30 ++++++++++++++++++++----------
 1 file changed, 20 insertions(+), 10 deletions(-)

diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index 66930d220..1dd3a4488 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -3852,6 +3852,23 @@ static int sh_ssl_sess_store(unsigned char *s_id, 
unsigned char *data, int data_
        return 1;
 }
 
+/* SSL callback used when a new session is created while connecting to a 
server */
+static int ssl_sess_new_srv_cb(SSL *ssl, SSL_SESSION *sess)
+{
+       struct connection *conn = SSL_get_app_data(ssl);
+
+       /* check if session was reused, if not store current session on server 
for reuse */
+       if (objt_server(conn->target)->ssl_ctx.reused_sess[tid]) {
+               
SSL_SESSION_free(objt_server(conn->target)->ssl_ctx.reused_sess[tid]);
+               objt_server(conn->target)->ssl_ctx.reused_sess[tid] = NULL;
+       }
+
+       if (!(objt_server(conn->target)->ssl_ctx.options & SRV_SSL_O_NO_REUSE))
+               objt_server(conn->target)->ssl_ctx.reused_sess[tid] = 
SSL_get1_session(conn->xprt_ctx);
+
+       return 1;
+}
+
 /* SSL callback used on new session creation */
 int sh_ssl_sess_new_cb(SSL *ssl, SSL_SESSION *sess)
 {
@@ -4580,7 +4597,9 @@ int ssl_sock_prepare_srv_ctx(struct server *srv)
 #endif
        }
 
-       SSL_CTX_set_session_cache_mode(srv->ssl_ctx.ctx, SSL_SESS_CACHE_OFF);
+       SSL_CTX_set_session_cache_mode(srv->ssl_ctx.ctx, SSL_SESS_CACHE_CLIENT |
+           SSL_SESS_CACHE_NO_INTERNAL_STORE);
+       SSL_CTX_sess_set_new_cb(srv->ssl_ctx.ctx, ssl_sess_new_srv_cb);
        if (srv->ssl_ctx.ciphers &&
                !SSL_CTX_set_cipher_list(srv->ssl_ctx.ctx, 
srv->ssl_ctx.ciphers)) {
                Alert("Proxy '%s', server '%s' [%s:%d] : unable to set SSL 
cipher list to '%s'.\n",
@@ -5208,15 +5227,6 @@ reneg_ok:
                        update_freq_ctr(&global.ssl_be_keys_per_sec, 1);
                        if (global.ssl_be_keys_per_sec.curr_ctr > 
global.ssl_be_keys_max)
                                global.ssl_be_keys_max = 
global.ssl_be_keys_per_sec.curr_ctr;
-
-                       /* check if session was reused, if not store current 
session on server for reuse */
-                       if 
(objt_server(conn->target)->ssl_ctx.reused_sess[tid]) {
-                               
SSL_SESSION_free(objt_server(conn->target)->ssl_ctx.reused_sess[tid]);
-                               
objt_server(conn->target)->ssl_ctx.reused_sess[tid] = NULL;
-                       }
-
-                       if (!(objt_server(conn->target)->ssl_ctx.options & 
SRV_SSL_O_NO_REUSE))
-                               
objt_server(conn->target)->ssl_ctx.reused_sess[tid] = 
SSL_get1_session(conn->xprt_ctx);
                }
                else {
                        update_freq_ctr(&global.ssl_fe_keys_per_sec, 1);
-- 
2.13.0

>From 9e794fa0508f52258baa0503d775db0ad45df782 Mon Sep 17 00:00:00 2001
From: Olivier Houchard <cog...@ci0.org>
Date: Fri, 3 Nov 2017 13:50:53 +0100
Subject: [PATCH 3/4] MINOR: ssl: Spell 0x10101000L correctly.

---
 src/ssl_sock.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index 1dd3a4488..1eccd5fe6 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -4980,7 +4980,7 @@ 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
+#if OPENSSL_VERSION_NUMBER >= 0x10101000L
                conn->flags |= CO_FL_EARLY_SSL_HS;
 #endif
 
-- 
2.13.0

>From 87368cd3bddf030a3eae3fb8e06b0ccc3b994771 Mon Sep 17 00:00:00 2001
From: Olivier Houchard <cog...@ci0.org>
Date: Fri, 3 Nov 2017 16:27:47 +0100
Subject: [PATCH 4/4] MINOR: ssl: Handle sending early data to server.

This adds a new keyword on the "server" line, "allow-0rtt", if set, we'll try
to send early data to the server, as long as the client sent early data, as
in case the server rejects the early data, we no longer have them, and can't
resend them, so the only option we have is to send back a 425, and we need
to be sure the client knows how to interpret it correctly.
---
 include/types/connection.h |  1 +
 include/types/server.h     |  1 +
 src/backend.c              | 14 ++++++++++--
 src/proto_http.c           | 11 +++++++++
 src/ssl_sock.c             | 56 +++++++++++++++++++++++++++++++++++++++++++---
 5 files changed, 78 insertions(+), 5 deletions(-)

diff --git a/include/types/connection.h b/include/types/connection.h
index beb0b71b8..eb674556c 100644
--- a/include/types/connection.h
+++ b/include/types/connection.h
@@ -226,6 +226,7 @@ enum {
        CO_ER_SSL_HANDSHAKE_HB, /* SSL error during handshake with heartbeat 
present */
        CO_ER_SSL_KILLED_HB,    /* Stopped a TLSv1 heartbeat attack 
(CVE-2014-0160) */
        CO_ER_SSL_NO_TARGET,    /* unknown target (not client nor server) */
+       CO_ER_SSL_EARLY_FAILED, /* Server refused early data */
 };
 
 /* source address settings for outgoing connections */
diff --git a/include/types/server.h b/include/types/server.h
index 4a31934d5..76225f7d3 100644
--- a/include/types/server.h
+++ b/include/types/server.h
@@ -167,6 +167,7 @@ enum srv_initaddr {
 #define SRV_SSL_O_NONE         0x0000
 #define SRV_SSL_O_NO_TLS_TICKETS 0x0100 /* disable session resumption tickets 
*/
 #define SRV_SSL_O_NO_REUSE     0x200  /* disable session reuse */
+#define SRV_SSL_O_EARLY_DATA   0x400  /* Allow using early data */
 #endif
 
 struct pid_list {
diff --git a/src/backend.c b/src/backend.c
index 9dbbd9198..ca064b695 100644
--- a/src/backend.c
+++ b/src/backend.c
@@ -1038,7 +1038,7 @@ static void assign_tproxy_address(struct stream *s)
  */
 int connect_server(struct stream *s)
 {
-       struct connection *cli_conn;
+       struct connection *cli_conn = NULL;
        struct connection *srv_conn;
        struct conn_stream *srv_cs;
        struct conn_stream *old_cs;
@@ -1180,10 +1180,11 @@ int connect_server(struct stream *s)
 
                /* process the case where the server requires the PROXY 
protocol to be sent */
                srv_conn->send_proxy_ofs = 0;
+               cli_conn = objt_conn(strm_orig(s));
+
                if (srv && srv->pp_opts) {
                        srv_conn->flags |= CO_FL_PRIVATE;
                        srv_conn->send_proxy_ofs = 1; /* must compute size */
-                       cli_conn = objt_conn(strm_orig(s));
                        if (cli_conn)
                                conn_get_to_addr(cli_conn);
                }
@@ -1208,6 +1209,15 @@ int connect_server(struct stream *s)
 
        err = si_connect(&s->si[1]);
 
+       if (!reuse && cli_conn && srv &&
+           (srv->ssl_ctx.options & SRV_SSL_O_EARLY_DATA) &&
+                   (cli_conn->flags & CO_FL_EARLY_DATA) &&
+                   !channel_is_empty(si_oc(&s->si[1])) &&
+                   srv_conn->flags & CO_FL_SSL_WAIT_HS) {
+               srv_conn->flags &= ~(CO_FL_SSL_WAIT_HS | CO_FL_WAIT_L6_CONN);
+               srv_conn->flags |= CO_FL_EARLY_SSL_HS;
+       }
+
        if (err != SF_ERR_NONE)
                return err;
 
diff --git a/src/proto_http.c b/src/proto_http.c
index 8d813d32a..ae6890eca 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -5147,6 +5147,17 @@ int http_wait_for_response(struct stream *s, struct 
channel *rep, int an_bit)
                        channel_auto_close(rep);
                        rep->analysers &= AN_RES_FLT_END;
                        txn->status = 502;
+
+                       /* Check to see if the server refused the early data.
+                        * If so, just send a 425
+                        */
+                       if (objt_cs(s->si[1].end)) {
+                               struct connection *conn = 
objt_cs(s->si[1].end)->conn;
+
+                               if (conn->err_code == CO_ER_SSL_EARLY_FAILED)
+                                       txn->status = 425;
+                       }
+
                        s->si[1].flags |= SI_FL_NOLINGER;
                        channel_truncate(rep);
                        http_reply_and_close(s, txn->status, 
http_error_message(s));
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index 1eccd5fe6..9dff17e0e 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -5210,6 +5210,22 @@ check_error:
                        goto out_error;
                }
        }
+#if (OPENSSL_VERSION_NUMBER >= 0x10101000L)
+       else {
+               /*
+                * If the server refused the early data, we have to send a
+                * 425 to the client, as we no longer have the data to sent
+                * them again.
+                */
+               if ((conn->flags & CO_FL_EARLY_DATA) && 
(objt_server(conn->target))) {
+                       if (SSL_get_early_data_status(conn->xprt_ctx) == 
SSL_EARLY_DATA_REJECTED) {
+                               conn->err_code = CO_ER_SSL_EARLY_FAILED;
+                               goto out_error;
+                       }
+               }
+       }
+#endif
+
 
 reneg_ok:
 
@@ -5328,7 +5344,8 @@ static int ssl_sock_to_buf(struct connection *conn, 
struct buffer *buf, int coun
 
                        ret = SSL_read_early_data(conn->xprt_ctx,
                            bi_end(buf), try, &read_length);
-                       if (read_length > 0)
+                       if (ret == SSL_READ_EARLY_DATA_SUCCESS &&
+                           read_length > 0)
                                conn->flags |= CO_FL_EARLY_DATA;
                        if (ret == SSL_READ_EARLY_DATA_SUCCESS ||
                            ret == SSL_READ_EARLY_DATA_FINISH) {
@@ -5465,16 +5482,34 @@ static int ssl_sock_from_buf(struct connection *conn, 
struct buffer *buf, int fl
                        if (conn->tmp_early_data == -1)
                                conn->tmp_early_data = 0;
 
-                       max_early = SSL_get_max_early_data(conn->xprt_ctx);
+                       if (objt_listener(conn->target))
+                               max_early = 
SSL_get_max_early_data(conn->xprt_ctx);
+                       else {
+                               if (SSL_get0_session(conn->xprt_ctx))
+                                       max_early = 
SSL_SESSION_get_max_early_data(SSL_get0_session(conn->xprt_ctx));
+                               else
+                                       max_early = 0;
+                       }
+
                        if (try + conn->tmp_early_data > max_early) {
                                try -= (try + conn->tmp_early_data) - max_early;
-                               if (try <= 0)
+                               if (try <= 0) {
+                                       if (objt_server(conn->target)) {
+                                               conn->flags &= 
~CO_FL_EARLY_SSL_HS;
+                                               conn->flags |= 
CO_FL_SSL_WAIT_HS | CO_FL_WAIT_L6_CONN;
+                                       }
                                        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;
+                               if (objt_server(conn->target)) {
+                                       conn->flags &= ~CO_FL_EARLY_SSL_HS;
+                                       conn->flags |= CO_FL_SSL_WAIT_HS | 
CO_FL_WAIT_L6_CONN | CO_FL_EARLY_DATA;
+                               }
+
                        }
 
                } else
@@ -5600,6 +5635,13 @@ static void ssl_sock_close(struct connection *conn) {
  */
 static void ssl_sock_shutw(struct connection *conn, int clean)
 {
+       /* If we're done with the connection before we did the handshake
+        * force the handshake anyway, so that the session is in a consistent
+        * state
+        */
+       if (conn->flags & CO_FL_EARLY_SSL_HS)
+               SSL_do_handshake(conn->xprt_ctx);
+
        if (conn->flags & CO_FL_HANDSHAKE)
                return;
        if (!clean)
@@ -7705,6 +7747,13 @@ static int srv_parse_no_ssl(char **args, int *cur_arg, 
struct proxy *px, struct
        return 0;
 }
 
+/* parse the "allow-0rtt" server keyword */
+static int srv_parse_allow_0rtt(char **args, int *cur_arg, struct proxy *px, 
struct server *newsrv, char **err)
+{
+       newsrv->ssl_ctx.options |= SRV_SSL_O_EARLY_DATA;
+       return 0;
+}
+
 /* parse the "no-ssl-reuse" server keyword */
 static int srv_parse_no_ssl_reuse(char **args, int *cur_arg, struct proxy *px, 
struct server *newsrv, char **err)
 {
@@ -8558,6 +8607,7 @@ static struct bind_kw_list bind_kws = { "SSL", { }, {
  * not enabled.
  */
 static struct srv_kw_list srv_kws = { "SSL", { }, {
+       { "allow-0rtt",              srv_parse_allow_0rtt,         0, 1 }, /* 
Allow using early data on this server */
        { "ca-file",                 srv_parse_ca_file,            1, 1 }, /* 
set CAfile to process verify server cert */
        { "check-sni",               srv_parse_check_sni,          1, 1 }, /* 
set SNI */
        { "check-ssl",               srv_parse_check_ssl,          0, 1 }, /* 
enable SSL for health checks */
-- 
2.13.0

Reply via email to