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 bfce2cf80..057317939 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 abb09d03e..72d9b8aee 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 */