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
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 @@
   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 @@
 	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 @@
 	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 @@
 	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 @@
 #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 @@
 #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 @@
 	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 @@
 		}
 	}
 
+	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 @@
 		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 @@
 	 *           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 @@
 
 	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 @@
 	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 @@
 	} 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 @@
 		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 @@
 	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 @@
 
 		/* 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 @@
 	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 @@
 		/* 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 @@
 	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 @@
 		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 @@
 	 * 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 @@
 		}
 		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 @@
 			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 @@
 				/* 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 @@
 	 * 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 @@
 			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 @@
 	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 @@
 	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 @@
  * 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 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 */