diff --git a/doc/configuration.txt b/doc/configuration.txt index bd8cafa23..7ab0f3c59 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 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..1c923c578 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 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 939a7a137..e632ce5fe 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 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..579a25b3b 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); @@ -4844,6 +4892,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)) { @@ -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 */