Skip to content

Instantly share code, notes, and snippets.

@mkwm
Last active February 10, 2018 17:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mkwm/13dd32fe2b5ec21182f8a06a304228df to your computer and use it in GitHub Desktop.
Save mkwm/13dd32fe2b5ec21182f8a06a304228df to your computer and use it in GitHub Desktop.
From c2aae74f010f97a3415542fe649198a5d3be1ea8 Mon Sep 17 00:00:00 2001
From: Olivier Houchard <ohouchard@haproxy.com>
Date: Fri, 22 Sep 2017 18:26:28 +0200
Subject: [PATCH 1/1] MEDIUM: 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.
For this, we add a new connection flag, CO_FL_EARLY_SSL_HS, which is not
part of the CO_FL_HANDSHAKE set, allowing to proceed with a session even
before an SSL handshake is completed.
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, as
specified in this draft from the HTTP working group :
https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-replay
---
doc/configuration.txt | 4 +
include/proto/connection.h | 1 +
include/types/connection.h | 5 +-
include/types/listener.h | 2 +
src/proto_http.c | 16 ++++
src/proto_tcp.c | 4 +-
src/session.c | 2 +-
src/ssl_sock.c | 181 ++++++++++++++++++++++++++++++++++++++------
8 files changed, 187 insertions(+), 28 deletions(-)
diff --git a/doc/configuration.txt b/doc/configuration.txt
index bd8cafa..7ab0f3c 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 0044d81..7060046 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 c1560cb..1c923c5 100644
--- a/include/types/connection.h
+++ b/include/types/connection.h
@@ -95,8 +95,8 @@ 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 handshake yet */
+ CO_FL_EARLY_DATA = 0x00008000, /* At least some of the data are early data */
/* unused : 0x00010000 */
/* unused : 0x00020000 */
@@ -299,6 +299,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 3d9ad7f..19d1dbe 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 939a7a1..e632ce5 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -3341,6 +3341,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 */
@@ -3387,6 +3388,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 e4e6483..b43fdef 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 bc0b6d6..ecfa2f1 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 fceccc9..579a25b 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -1314,7 +1314,7 @@ 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;
}
@@ -2002,11 +2002,14 @@ static int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *arg)
const uint8_t *servername;
size_t servername_len;
struct ebmb_node *node, *n, *node_ecdsa = NULL, *node_rsa = NULL, *node_anonymous = NULL;
+ int allow_early = 0;
int i;
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 +2048,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 +2192,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_BORINGSSL
+ 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;
@@ -3911,8 +3924,20 @@ int ssl_sock_prepare_ctx(struct bind_conf *bind_conf, struct ssl_bind_conf *ssl_
if (!conf_curves) {
int i;
EC_KEY *ecdh;
+#if (OPENSSL_VERSION_NUMBER >= 0x10101000L)
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 :
+ NULL);
+
+ if (ecdhe == NULL) {
+ SSL_CTX_set_dh_auto(ctx, 1);
+ return cfgerr;
+ }
+#else
+ const char *ecdhe = (ssl_conf && ssl_conf->ecdhe) ? ssl_conf->ecdhe :
+ (bind_conf->ssl_conf.ecdhe ? bind_conf->ssl_conf.ecdhe :
+ ECDHE_DEFAULT_CURVE);
+#endif
i = OBJ_sn2nid(ecdhe);
if (!i || ((ecdh = EC_KEY_new_by_curve_name(i)) == NULL)) {
@@ -4627,6 +4652,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 +4682,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 +4801,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 +4893,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 +4968,18 @@ 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 +4987,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 +4999,42 @@ 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 +5045,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 +5073,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 +5127,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 +5146,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 +6957,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 +7509,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 +8176,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 +8192,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 */
--
1.7.10.4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment