Hi, You'll find attached updated patches, rebased on the latest master, and on top of Emmanuel's latest patches (also attached for reference). This version allows to enable 0RTT per SNI. It unfortunately still can't send early data to servers, this may or may not happen later.
Regards, Olivier
>From 25d10a4b30d946de138ccdd3b2595fa84a9da675 Mon Sep 17 00:00:00 2001 From: Emmanuel Hocdet <m...@gandi.net> Date: Wed, 16 Aug 2017 11:28:44 +0200 Subject: [PATCH 1/6] MEDIUM: ssl: convert CBS (BoringSSL api) usage to neutral code switchctx early callback is only supported for BoringSSL. To prepare the support of openssl 1.1.1 early callback, convert CBS api to neutral code to work with any ssl libs. --- src/ssl_sock.c | 109 ++++++++++++++++++++++++++++++--------------------------- 1 file changed, 58 insertions(+), 51 deletions(-) diff --git a/src/ssl_sock.c b/src/ssl_sock.c index 3d9723949..25b846b25 100644 --- a/src/ssl_sock.c +++ b/src/ssl_sock.c @@ -1965,50 +1965,57 @@ static int ssl_sock_switchctx_err_cbk(SSL *ssl, int *al, void *priv) static int ssl_sock_switchctx_cbk(const struct ssl_early_callback_ctx *ctx) { + SSL *ssl = ctx->ssl; struct connection *conn; struct bind_conf *s; const uint8_t *extension_data; size_t extension_len; - CBS extension, cipher_suites, server_name_list, host_name, sig_algs; - const SSL_CIPHER *cipher; - uint16_t cipher_suite; - uint8_t name_type, hash, sign; int has_rsa = 0, has_ecdsa = 0, has_ecdsa_sig = 0; char *wildp = NULL; const uint8_t *servername; + size_t servername_len; struct ebmb_node *node, *n, *node_ecdsa = NULL, *node_rsa = NULL, *node_anonymous = NULL; int i; - conn = SSL_get_app_data(ctx->ssl); + conn = SSL_get_app_data(ssl); s = objt_listener(conn->target)->bind_conf; if (SSL_early_callback_ctx_extension_get(ctx, TLSEXT_TYPE_server_name, &extension_data, &extension_len)) { - CBS_init(&extension, extension_data, extension_len); - - if (!CBS_get_u16_length_prefixed(&extension, &server_name_list) - || !CBS_get_u8(&server_name_list, &name_type) - /* Although the server_name extension was intended to be extensible to - * new name types and multiple names, OpenSSL 1.0.x had a bug which meant - * different name types will cause an error. Further, RFC 4366 originally - * defined syntax inextensibly. RFC 6066 corrected this mistake, but - * adding new name types is no longer feasible. - * - * Act as if the extensibility does not exist to simplify parsing. */ - || !CBS_get_u16_length_prefixed(&server_name_list, &host_name) - || CBS_len(&server_name_list) != 0 - || CBS_len(&extension) != 0 - || name_type != TLSEXT_NAMETYPE_host_name - || CBS_len(&host_name) == 0 - || CBS_len(&host_name) > TLSEXT_MAXLEN_host_name - || CBS_contains_zero_byte(&host_name)) { + /* + * The server_name extension was given too much extensibility when it + * was written, so parsing the normal case is a bit complex. + */ + size_t len; + if (extension_len <= 2) goto abort; - } + /* Extract the length of the supplied list of names. */ + len = (*extension_data++) << 8; + len |= *extension_data++; + if (len + 2 != extension_len) + goto abort; + /* + * The list in practice only has a single element, so we only consider + * the first one. + */ + if (len == 0 || *extension_data++ != TLSEXT_NAMETYPE_host_name) + goto abort; + extension_len = len - 1; + /* Now we can finally pull out the byte array with the actual hostname. */ + if (extension_len <= 2) + goto abort; + len = (*extension_data++) << 8; + len |= *extension_data++; + if (len == 0 || len + 2 > extension_len || len > TLSEXT_MAXLEN_host_name + || memchr(extension_data, 0, len) != NULL) + goto abort; + servername = extension_data; + servername_len = len; } else { /* without SNI extension, is the default_ctx (need SSL_TLSEXT_ERR_NOACK) */ if (!s->strict_sni) { - ssl_sock_switchctx_set(ctx->ssl, s->default_ctx); + ssl_sock_switchctx_set(ssl, s->default_ctx); return 1; } goto abort; @@ -2016,21 +2023,19 @@ static int ssl_sock_switchctx_cbk(const struct ssl_early_callback_ctx *ctx) /* extract/check clientHello informations */ if (SSL_early_callback_ctx_extension_get(ctx, TLSEXT_TYPE_signature_algorithms, &extension_data, &extension_len)) { - CBS_init(&extension, extension_data, extension_len); - - if (!CBS_get_u16_length_prefixed(&extension, &sig_algs) - || CBS_len(&sig_algs) == 0 - || CBS_len(&extension) != 0) { + uint8_t sign; + size_t len; + if (extension_len < 2) goto abort; - } - if (CBS_len(&sig_algs) % 2 != 0) { + len = (*extension_data++) << 8; + len |= *extension_data++; + if (len + 2 != extension_len) goto abort; - } - while (CBS_len(&sig_algs) != 0) { - if (!CBS_get_u8(&sig_algs, &hash) - || !CBS_get_u8(&sig_algs, &sign)) { - goto abort; - } + if (len % 2 != 0) + goto abort; + for (; len > 0; len -= 2) { + extension_data++; /* hash */ + sign = *extension_data++; switch (sign) { case TLSEXT_signature_rsa: has_rsa = 1; @@ -2049,12 +2054,15 @@ static int ssl_sock_switchctx_cbk(const struct ssl_early_callback_ctx *ctx) has_rsa = 1; } if (has_ecdsa_sig) { /* in very rare case: has ecdsa sign but not a ECDSA cipher */ - CBS_init(&cipher_suites, ctx->cipher_suites, ctx->cipher_suites_len); - - while (CBS_len(&cipher_suites) != 0) { - if (!CBS_get_u16(&cipher_suites, &cipher_suite)) { - goto abort; - } + const SSL_CIPHER *cipher; + size_t len; + const uint8_t *cipher_suites; + len = ctx->cipher_suites_len; + cipher_suites = ctx->cipher_suites; + if (len % 2 != 0) + goto abort; + for (; len != 0; len -= 2, cipher_suites += 2) { + uint16_t cipher_suite = (cipher_suites[0] << 8) | cipher_suites[1]; cipher = SSL_get_cipher_by_value(cipher_suite); if (cipher && SSL_CIPHER_get_auth_nid(cipher) == NID_auth_ecdsa) { has_ecdsa = 1; @@ -2063,8 +2071,7 @@ static int ssl_sock_switchctx_cbk(const struct ssl_early_callback_ctx *ctx) } } - servername = CBS_data(&host_name); - for (i = 0; i < trash.size && i < CBS_len(&host_name); i++) { + for (i = 0; i < trash.size && i < servername_len; i++) { trash.str[i] = tolower(servername[i]); if (!wildp && (trash.str[i] == '.')) wildp = &trash.str[i]; @@ -2132,20 +2139,20 @@ static int ssl_sock_switchctx_cbk(const struct ssl_early_callback_ctx *ctx) if (node) { /* switch ctx */ struct ssl_bind_conf *conf = container_of(node, struct sni_ctx, name)->conf; - ssl_sock_switchctx_set(ctx->ssl, container_of(node, struct sni_ctx, name)->ctx); - methodVersions[conf->ssl_methods.min].ssl_set_version(ctx->ssl, SET_MIN); - methodVersions[conf->ssl_methods.max].ssl_set_version(ctx->ssl, SET_MAX); + 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 (!s->strict_sni) { /* no certificate match, is the default_ctx */ - ssl_sock_switchctx_set(ctx->ssl, s->default_ctx); + ssl_sock_switchctx_set(ssl, s->default_ctx); return 1; } abort: /* abort handshake (was SSL_TLSEXT_ERR_ALERT_FATAL) */ conn->err_code = CO_ER_SSL_HANDSHAKE; - return -1; + return ssl_select_cert_error; } #else /* OPENSSL_IS_BORINGSSL */ -- 2.13.5
>From 3019e1900645ebef278e84a858bfad40a2e3f867 Mon Sep 17 00:00:00 2001 From: Emmanuel Hocdet <m...@gandi.net> Date: Wed, 16 Aug 2017 11:33:17 +0200 Subject: [PATCH 2/6] MINOR: ssl: support Openssl 1.1.1 early callback for switchctx Use Openssl-1.1.1 SSL_CTX_set_client_hello_cb to mimic BoringSSL early callback. Native multi certificate and SSL/TLS method per certificate is now supported by Openssl >= 1.1.1. --- doc/configuration.txt | 13 +++++++------ src/ssl_sock.c | 35 ++++++++++++++++++++++++++++++++--- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index bba695afd..66b15b30f 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -10399,8 +10399,9 @@ crt <cert> to use both RSA and ECDSA cipher suites. Users connecting with an SNI of "rsa.example.com" will only be able to use RSA cipher suites, and users connecting with "ecdsa.example.com" will only be able to use ECDSA cipher - suites. With BoringSSL multi-cert is natively supported, no need to bundle - certificates. ECDSA certificate will be preferred if client support it. + suites. With BoringSSL and Openssl >= 1.1.1 multi-cert is natively supported, + no need to bundle certificates. ECDSA certificate will be preferred if client + support it. If a directory name is given as the <cert> argument, haproxy will automatically search and load bundled files in that directory. @@ -10424,7 +10425,7 @@ crt-list <file> sslbindconf support "npn", "alpn", "verify", "ca-file", "no-ca-names", crl-file", "ecdhe", "curves", "ciphers" configuration. With BoringSSL - "ssl-min-ver" and "ssl-max-ver" are also supported. + and Openssl >= 1.1.1 "ssl-min-ver" and "ssl-max-ver" are also supported. It override the configuration set in bind line for the certificate. Wildcards are supported in the SNI filter. Negative filter are also supported, @@ -10438,9 +10439,9 @@ crt-list <file> Multi-cert bundling (see "crt") is supported with crt-list, as long as only the base name is given in the crt-list. SNI filter will do the same work on - all bundled certificates. With BoringSSL multi-cert is natively supported, - avoid multi-cert bundling. RSA and ECDSA certificates can be declared in a - row, and set different ssl and filter parameter. + all bundled certificates. With BoringSSL and Openssl >= 1.1.1 multi-cert is + natively supported, avoid multi-cert bundling. RSA and ECDSA certificates can + be declared in a row, and set different ssl and filter parameter. crt-list file example: cert1.pem diff --git a/src/ssl_sock.c b/src/ssl_sock.c index 25b846b25..e1cfc6697 100644 --- a/src/ssl_sock.c +++ b/src/ssl_sock.c @@ -1828,7 +1828,7 @@ ssl_sock_generate_certificate(const char *servername, struct bind_conf *bind_con #ifndef SSL_OP_NO_TLSv1_2 /* needs OpenSSL >= 1.0.1 */ #define SSL_OP_NO_TLSv1_2 0 #endif -#ifndef SSL_OP_NO_TLSv1_3 /* dev */ +#ifndef SSL_OP_NO_TLSv1_3 /* needs OpenSSL >= 1.1.1 */ #define SSL_OP_NO_TLSv1_3 0 #endif #ifndef SSL_OP_SINGLE_DH_USE /* needs OpenSSL >= 0.9.6 */ @@ -1951,7 +1951,7 @@ static void ssl_sock_switchctx_set(SSL *ssl, SSL_CTX *ctx) SSL_set_SSL_CTX(ssl, ctx); } -#ifdef OPENSSL_IS_BORINGSSL +#if (OPENSSL_VERSION_NUMBER >= 0x10101000L) || defined(OPENSSL_IS_BORINGSSL) static int ssl_sock_switchctx_err_cbk(SSL *ssl, int *al, void *priv) { @@ -1963,9 +1963,14 @@ static int ssl_sock_switchctx_err_cbk(SSL *ssl, int *al, void *priv) return SSL_TLSEXT_ERR_OK; } +#ifdef OPENSSL_IS_BORINGSSL static int ssl_sock_switchctx_cbk(const struct ssl_early_callback_ctx *ctx) { SSL *ssl = ctx->ssl; +#else +static int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *arg) +{ +#endif struct connection *conn; struct bind_conf *s; const uint8_t *extension_data; @@ -1981,8 +1986,12 @@ static int ssl_sock_switchctx_cbk(const struct ssl_early_callback_ctx *ctx) conn = SSL_get_app_data(ssl); s = objt_listener(conn->target)->bind_conf; +#ifdef OPENSSL_IS_BORINGSSL if (SSL_early_callback_ctx_extension_get(ctx, TLSEXT_TYPE_server_name, &extension_data, &extension_len)) { +#else + if (SSL_client_hello_get0_ext(ssl, TLSEXT_TYPE_server_name, &extension_data, &extension_len)) { +#endif /* * The server_name extension was given too much extensibility when it * was written, so parsing the normal case is a bit complex. @@ -2022,7 +2031,11 @@ static int ssl_sock_switchctx_cbk(const struct ssl_early_callback_ctx *ctx) } /* extract/check clientHello informations */ +#ifdef OPENSSL_IS_BORINGSSL if (SSL_early_callback_ctx_extension_get(ctx, TLSEXT_TYPE_signature_algorithms, &extension_data, &extension_len)) { +#else + if (SSL_client_hello_get0_ext(ssl, TLSEXT_TYPE_signature_algorithms, &extension_data, &extension_len)) { +#endif uint8_t sign; size_t len; if (extension_len < 2) @@ -2057,13 +2070,21 @@ static int ssl_sock_switchctx_cbk(const struct ssl_early_callback_ctx *ctx) const SSL_CIPHER *cipher; size_t len; const uint8_t *cipher_suites; +#ifdef OPENSSL_IS_BORINGSSL len = ctx->cipher_suites_len; cipher_suites = ctx->cipher_suites; +#else + len = SSL_client_hello_get0_ciphers(ssl, &cipher_suites); +#endif if (len % 2 != 0) goto abort; for (; len != 0; len -= 2, cipher_suites += 2) { +#ifdef OPENSSL_IS_BORINGSSL uint16_t cipher_suite = (cipher_suites[0] << 8) | cipher_suites[1]; cipher = SSL_get_cipher_by_value(cipher_suite); +#else + cipher = SSL_CIPHER_find(ssl, cipher_suites); +#endif if (cipher && SSL_CIPHER_get_auth_nid(cipher) == NID_auth_ecdsa) { has_ecdsa = 1; break; @@ -2152,7 +2173,12 @@ static int ssl_sock_switchctx_cbk(const struct ssl_early_callback_ctx *ctx) abort: /* abort handshake (was SSL_TLSEXT_ERR_ALERT_FATAL) */ conn->err_code = CO_ER_SSL_HANDSHAKE; +#ifdef OPENSSL_IS_BORINGSSL return ssl_select_cert_error; +#else + *al = SSL_AD_UNRECOGNIZED_NAME; + return 0; +#endif } #else /* OPENSSL_IS_BORINGSSL */ @@ -3647,6 +3673,9 @@ ssl_sock_initial_ctx(struct bind_conf *bind_conf) #ifdef OPENSSL_IS_BORINGSSL SSL_CTX_set_select_certificate_cb(ctx, ssl_sock_switchctx_cbk); SSL_CTX_set_tlsext_servername_callback(ctx, ssl_sock_switchctx_err_cbk); +#elif (OPENSSL_VERSION_NUMBER >= 0x10101000L) + SSL_CTX_set_client_hello_cb(ctx, ssl_sock_switchctx_cbk, NULL); + SSL_CTX_set_tlsext_servername_callback(ctx, ssl_sock_switchctx_err_cbk); #else SSL_CTX_set_tlsext_servername_callback(ctx, ssl_sock_switchctx_cbk); SSL_CTX_set_tlsext_servername_arg(ctx, bind_conf); @@ -6773,7 +6802,7 @@ static int parse_tls_method_minmax(char **args, int cur_arg, struct tls_version_ static int ssl_bind_parse_tls_method_minmax(char **args, int cur_arg, struct proxy *px, struct ssl_bind_conf *conf, char **err) { -#if !defined(OPENSSL_IS_BORINGSSL) +#if (OPENSSL_VERSION_NUMBER < 0x10101000L) || !defined(OPENSSL_IS_BORINGSSL) Warning("crt-list: ssl-min-ver and ssl-max-ver are not supported with this Openssl version (skipped).\n"); #endif return parse_tls_method_minmax(args, cur_arg, &conf->ssl_methods, err); -- 2.13.5
>From 6abc2b0efa191436250128e91be461a58cdc5a0c Mon Sep 17 00:00:00 2001 From: Emmanuel Hocdet <m...@gandi.net> Date: Mon, 14 Aug 2017 11:01:25 +0200 Subject: [PATCH 3/6] MINOR: ssl: generated certificate is missing in switchctx early callback Openssl 1.1.1 support switchctx early callback and generated certificate. Generated certificate calls must be available in switchctx early callback. --- src/ssl_sock.c | 70 ++++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 43 insertions(+), 27 deletions(-) diff --git a/src/ssl_sock.c b/src/ssl_sock.c index e1cfc6697..fceccc9ba 100644 --- a/src/ssl_sock.c +++ b/src/ssl_sock.c @@ -1771,7 +1771,7 @@ ssl_sock_generated_cert_key(const void *data, size_t len) /* Generate a cert and immediately assign it to the SSL session so that the cert's * refcount is maintained regardless of the cert's presence in the LRU cache. */ -static SSL_CTX * +static int ssl_sock_generate_certificate(const char *servername, struct bind_conf *bind_conf, SSL *ssl) { X509 *cacert = bind_conf->ca_sign_cert; @@ -1789,14 +1789,35 @@ ssl_sock_generate_certificate(const char *servername, struct bind_conf *bind_con lru64_commit(lru, ssl_ctx, cacert, 0, (void (*)(void *))SSL_CTX_free); } SSL_set_SSL_CTX(ssl, ssl_ctx); + return 1; } else { ssl_ctx = ssl_sock_do_create_cert(servername, bind_conf, ssl); SSL_set_SSL_CTX(ssl, ssl_ctx); /* No LRU cache, this CTX will be released as soon as the session dies */ SSL_CTX_free(ssl_ctx); + return 1; } - return ssl_ctx; + return 0; +} +static int +ssl_sock_generate_certificate_from_conn(struct bind_conf *bind_conf, SSL *ssl) +{ + unsigned int key; + SSL_CTX *ssl_ctx = NULL; + struct connection *conn = SSL_get_app_data(ssl); + + conn_get_to_addr(conn); + if (conn->flags & CO_FL_ADDR_TO_SET) { + key = ssl_sock_generated_cert_key(&conn->addr.to, get_addr_len(&conn->addr.to)); + ssl_ctx = ssl_sock_get_generated_cert(key, bind_conf); + if (ssl_ctx) { + /* switch ctx */ + SSL_set_SSL_CTX(ssl, ssl_ctx); + return 1; + } + } + return 0; } #endif /* !defined SSL_NO_GENERATE_CERTIFICATES */ @@ -1955,12 +1976,12 @@ static void ssl_sock_switchctx_set(SSL *ssl, SSL_CTX *ctx) static int ssl_sock_switchctx_err_cbk(SSL *ssl, int *al, void *priv) { + struct bind_conf *s = priv; (void)al; /* shut gcc stupid warning */ - (void)priv; - if (!SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name)) - return SSL_TLSEXT_ERR_NOACK; - return SSL_TLSEXT_ERR_OK; + if (SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name) || s->generate_certs) + return SSL_TLSEXT_ERR_OK; + return SSL_TLSEXT_ERR_NOACK; } #ifdef OPENSSL_IS_BORINGSSL @@ -2022,6 +2043,11 @@ static int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *arg) servername = extension_data; servername_len = len; } else { +#if (!defined SSL_NO_GENERATE_CERTIFICATES) + if (s->generate_certs && ssl_sock_generate_certificate_from_conn(s, ssl)) { + return 1; + } +#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); @@ -2165,6 +2191,12 @@ static int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *arg) methodVersions[conf->ssl_methods.max].ssl_set_version(ssl, SET_MAX); return 1; } +#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; + } +#endif if (!s->strict_sni) { /* no certificate match, is the default_ctx */ ssl_sock_switchctx_set(ssl, s->default_ctx); @@ -2199,22 +2231,8 @@ static int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *priv) servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); if (!servername) { #if (!defined SSL_NO_GENERATE_CERTIFICATES) - if (s->generate_certs) { - struct connection *conn = SSL_get_app_data(ssl); - unsigned int key; - SSL_CTX *ctx; - - conn_get_to_addr(conn); - if (conn->flags & CO_FL_ADDR_TO_SET) { - key = ssl_sock_generated_cert_key(&conn->addr.to, get_addr_len(&conn->addr.to)); - ctx = ssl_sock_get_generated_cert(key, s); - if (ctx) { - /* switch ctx */ - SSL_set_SSL_CTX(ssl, ctx); - return SSL_TLSEXT_ERR_OK; - } - } - } + if (s->generate_certs && ssl_sock_generate_certificate_from_conn(s, ssl)) + return SSL_TLSEXT_ERR_OK; #endif if (s->strict_sni) return SSL_TLSEXT_ERR_ALERT_FATAL; @@ -2247,10 +2265,8 @@ static int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *priv) } if (!node || container_of(node, struct sni_ctx, name)->neg) { #if (!defined SSL_NO_GENERATE_CERTIFICATES) - SSL_CTX *ctx; - if (s->generate_certs && - (ctx = ssl_sock_generate_certificate(servername, s, ssl))) { - /* switch ctx */ + if (s->generate_certs && ssl_sock_generate_certificate(servername, s, ssl)) { + /* switch ctx done in ssl_sock_generate_certificate */ return SSL_TLSEXT_ERR_OK; } #endif @@ -3678,8 +3694,8 @@ ssl_sock_initial_ctx(struct bind_conf *bind_conf) SSL_CTX_set_tlsext_servername_callback(ctx, ssl_sock_switchctx_err_cbk); #else SSL_CTX_set_tlsext_servername_callback(ctx, ssl_sock_switchctx_cbk); - SSL_CTX_set_tlsext_servername_arg(ctx, bind_conf); #endif + SSL_CTX_set_tlsext_servername_arg(ctx, bind_conf); #endif return cfgerr; } -- 2.13.5
>From 7f8d3065d86a5177c3f664d4a2ed34e62ff60bb3 Mon Sep 17 00:00:00 2001 From: Olivier Houchard <ohouch...@haproxy.com> Date: Fri, 22 Sep 2017 18:26:28 +0200 Subject: [PATCH 4/6] 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. 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. --- doc/configuration.txt | 4 + include/proto/connection.h | 1 + include/types/connection.h | 6 +- include/types/listener.h | 2 + src/proto_http.c | 16 ++++ src/proto_tcp.c | 4 +- src/session.c | 2 +- src/ssl_sock.c | 186 +++++++++++++++++++++++++++++++++++++++------ 8 files changed, 193 insertions(+), 28 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index 66b15b30f..bc5164006 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 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..661e12b91 100644 --- a/include/types/connection.h +++ b/include/types/connection.h @@ -95,11 +95,12 @@ 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 handhsake yet */ + CO_FL_EARLY_DATA = 0x00008000, /* At least some of the data are early data */ /* unused : 0x00010000 */ /* unused : 0x00020000 */ + /* flags used to remember what shutdown have been performed/reported */ CO_FL_SOCK_RD_SH = 0x00040000, /* SOCK layer was notified about shutr/read0 */ CO_FL_SOCK_WR_SH = 0x00080000, /* SOCK layer asked for shutw */ @@ -299,6 +300,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 066204166..799b7bb27 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -3288,6 +3288,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 */ @@ -3334,6 +3335,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..9cb1b7bac 100644 --- a/src/ssl_sock.c +++ b/src/ssl_sock.c @@ -1314,7 +1314,8 @@ 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; } @@ -2003,10 +2004,13 @@ static int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *arg) size_t servername_len; struct ebmb_node *node, *n, *node_ecdsa = NULL, *node_rsa = NULL, *node_anonymous = NULL; int i; + int allow_early = 0; 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 +2049,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 +2193,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_BORINGSS + 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; @@ -2273,6 +2287,7 @@ static int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *priv) if (s->strict_sni) return SSL_TLSEXT_ERR_ALERT_FATAL; ssl_sock_switchctx_set(ssl, s->default_ctx); + return SSL_TLSEXT_ERR_OK; } @@ -3912,7 +3927,17 @@ int ssl_sock_prepare_ctx(struct bind_conf *bind_conf, struct ssl_bind_conf *ssl_ int i; EC_KEY *ecdh; 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 : +#if (OPENSSL_VERSION_NUMBER >= 0x10101000L) + NULL); + + if (ecdhe == NULL) { + SSL_CTX_set_dh_auto(ctx, 1); + goto no_ecdhe; + } +#else + ECDHE_DEFAULT_CURVE); +#endif i = OBJ_sn2nid(ecdhe); if (!i || ((ecdh = EC_KEY_new_by_curve_name(i)) == NULL)) { @@ -3927,6 +3952,9 @@ int ssl_sock_prepare_ctx(struct bind_conf *bind_conf, struct ssl_bind_conf *ssl_ } #endif +#if (OPENSSL_VERSION_NUMBER >= 0x10101000L) +no_ecdhe: +#endif return cfgerr; } @@ -4627,6 +4655,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 +4685,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 +4804,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 +4896,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 +4971,19 @@ 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 +4991,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 +5003,43 @@ 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 +5050,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 +5078,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 +5132,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 +5151,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 +6962,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 +7514,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 +8181,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 +8197,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 */ -- 2.13.5
>From ffdab259448982edd71a28e5cdc6df3294e2c345 Mon Sep 17 00:00:00 2001 From: Olivier Houchard <ohouch...@haproxy.com> Date: Mon, 2 Oct 2017 11:51:03 +0200 Subject: [PATCH 5/6] MINOR: ssl/proto_http: Add keywords to take care of early data. Add a new sample fetch, "ssl_fc_has_early", a boolean that will be true if early data were sent, and a new action, "wait-for-handshake", if used, the request won't be forwarded until the SSL handshake is done. --- doc/configuration.txt | 9 ++++++++ src/ssl_sock.c | 58 ++++++++++++++++++++++++++++++++++++++++++++------ src/stream_interface.c | 11 ++++++++++ 3 files changed, 71 insertions(+), 7 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index bc5164006..329aff778 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -4163,6 +4163,10 @@ http-request { allow | auth [realm <realm>] | redirect <rule> | pass the first router, though it's still delivered to local networks. Do not use it unless you fully understand how it works. + - "wait-for-handshake" : this will delay the processing of the request + until the SSL handshake happened. This is mostly useful to delay + processing early data until we're sure they are valid. + There is no limit to the number of http-request statements per instance. It is important to know that http-request rules are processed very early in @@ -14252,6 +14256,11 @@ ssl_fc_has_crt : boolean from the cache or the ticket. So prefer "ssl_c_used" if you want to check if current SSL session uses a client certificate. +ssl_fc_has_early : boolean + Returns true if early data were sent, and the handshake didn't happen yet. As + it has security implications, it is useful to be able to refuse those, or + wait until the handshake happened. + ssl_fc_has_sni : boolean This checks for the presence of a Server Name Indication TLS extension (SNI) in an incoming connection was made over an SSL/TLS transport layer. Returns diff --git a/src/ssl_sock.c b/src/ssl_sock.c index 9cb1b7bac..35c85820d 100644 --- a/src/ssl_sock.c +++ b/src/ssl_sock.c @@ -95,6 +95,7 @@ #include <proto/openssl-compat.h> #include <proto/pattern.h> #include <proto/proto_tcp.h> +#include <proto/proto_http.h> #include <proto/server.h> #include <proto/stream_interface.h> #include <proto/log.h> @@ -4896,13 +4897,6 @@ 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)) { @@ -5653,6 +5647,23 @@ static int ssl_sock_get_alpn(const struct connection *conn, const char **str, in /***** Below are some sample fetching functions for ACL/patterns *****/ +static int +smp_fetch_ssl_fc_has_early(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct connection *conn; + + conn = objt_conn(smp->sess->origin); + if (!conn || conn->xprt != &ssl_sock) + return 0; + + smp->flags = 0; + smp->data.type = SMP_T_BOOL; + smp->data.u.sint = (conn->flags & CO_FL_EARLY_DATA) ? 1 : 0; + + return 1; + +} + /* boolean, returns true if client cert was present */ static int smp_fetch_ssl_fc_has_crt(const struct arg *args, struct sample *smp, const char *kw, void *private) @@ -8144,6 +8155,8 @@ static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, { { "ssl_fc_alg_keysize", smp_fetch_ssl_fc_alg_keysize, 0, NULL, SMP_T_SINT, SMP_USE_L5CLI }, { "ssl_fc_cipher", smp_fetch_ssl_fc_cipher, 0, NULL, SMP_T_STR, SMP_USE_L5CLI }, { "ssl_fc_has_crt", smp_fetch_ssl_fc_has_crt, 0, NULL, SMP_T_BOOL, SMP_USE_L5CLI }, + { "ssl_fc_has_early", smp_fetch_ssl_fc_has_early, 0, + NULL, SMP_T_BOOL, SMP_USE_L5CLI }, { "ssl_fc_has_sni", smp_fetch_ssl_fc_has_sni, 0, NULL, SMP_T_BOOL, SMP_USE_L5CLI }, { "ssl_fc_is_resumed", smp_fetch_ssl_fc_is_resumed, 0, NULL, SMP_T_BOOL, SMP_USE_L5CLI }, #ifdef OPENSSL_NPN_NEGOTIATED @@ -8322,6 +8335,35 @@ static struct xprt_ops ssl_sock = { .name = "SSL", }; +enum act_return ssl_action_wait_for_hs(struct act_rule *rule, struct proxy *px, + struct session *sess, struct stream *s, int flags) +{ + struct connection *conn; + + conn = objt_conn(sess->origin); + + if (conn) { + if (conn->flags & (CO_FL_EARLY_SSL_HS | CO_FL_SSL_WAIT_HS)) + { + s->req.flags |= CF_READ_NULL; + return ACT_RET_YIELD; + } + } + return (ACT_RET_CONT); +} + +static enum act_parse_ret ssl_parse_wait_for_hs(const char **args, int *orig_arg, struct proxy *px, struct act_rule *rule, char **err) +{ + rule->action_ptr = ssl_action_wait_for_hs; + + return ACT_RET_PRS_OK; +} + +static struct action_kw_list http_req_actions = {ILH, { + { "wait-for-handshake", ssl_parse_wait_for_hs }, + { /* END */ } +}}; + #if (OPENSSL_VERSION_NUMBER >= 0x1000200fL && !defined OPENSSL_NO_TLSEXT && !defined OPENSSL_IS_BORINGSSL && !defined LIBRESSL_VERSION_NUMBER) static void ssl_sock_sctl_free_func(void *parent, void *ptr, CRYPTO_EX_DATA *ad, int idx, long argl, void *argp) @@ -8424,6 +8466,8 @@ static void __ssl_sock_init(void) #endif /* Load SSL string for the verbose & debug mode. */ ERR_load_SSL_strings(); + + http_req_keywords_register(&http_req_actions); } #ifndef OPENSSL_NO_ENGINE diff --git a/src/stream_interface.c b/src/stream_interface.c index 53b201c08..93c3e24b1 100644 --- a/src/stream_interface.c +++ b/src/stream_interface.c @@ -572,6 +572,17 @@ static int si_conn_wake_cb(struct connection *conn) if (conn->flags & CO_FL_ERROR) si->flags |= SI_FL_ERR; + /* If we had early data, and the handshake ended, then + * we can remove the flag, and attempt to wake the task up, + * in the event there's an analyser waiting for the end of + * the handshake. + */ + if ((conn->flags & (CO_FL_EARLY_DATA | CO_FL_EARLY_SSL_HS)) == + CO_FL_EARLY_DATA) { + conn->flags &= ~CO_FL_EARLY_DATA; + task_wakeup(si_task(si), TASK_WOKEN_MSG); + } + if ((si->state < SI_ST_EST) && (conn->flags & (CO_FL_CONNECTED | CO_FL_HANDSHAKE)) == CO_FL_CONNECTED) { si->exp = TICK_ETERNITY; -- 2.13.5
>From 2544b6a4e4c3b74eae668464189414c6260c49ef Mon Sep 17 00:00:00 2001 From: Olivier Houchard <ohouch...@haproxy.com> Date: Mon, 2 Oct 2017 16:12:07 +0200 Subject: [PATCH 6/6] MINOR: http: Mark the 425 code as "Too Early". --- include/types/proto_http.h | 1 + src/proto_http.c | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/include/types/proto_http.h b/include/types/proto_http.h index 027bfce42..cf0fdb698 100644 --- a/include/types/proto_http.h +++ b/include/types/proto_http.h @@ -196,6 +196,7 @@ enum { HTTP_ERR_403, HTTP_ERR_405, HTTP_ERR_408, + HTTP_ERR_425, HTTP_ERR_429, HTTP_ERR_500, HTTP_ERR_502, diff --git a/src/proto_http.c b/src/proto_http.c index 799b7bb27..d8bf2a90a 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -140,6 +140,7 @@ const int http_err_codes[HTTP_ERR_SIZE] = { [HTTP_ERR_403] = 403, [HTTP_ERR_405] = 405, [HTTP_ERR_408] = 408, + [HTTP_ERR_425] = 425, [HTTP_ERR_429] = 429, [HTTP_ERR_500] = 500, [HTTP_ERR_502] = 502, @@ -188,6 +189,14 @@ static const char *http_err_msgs[HTTP_ERR_SIZE] = { "\r\n" "<html><body><h1>408 Request Time-out</h1>\nYour browser didn't send a complete request in time.\n</body></html>\n", + [HTTP_ERR_425] = + "HTTP/1.0 425 Too Early\r\n" + "Cache-Control: no-cache\r\n" + "Connection: close\r\n" + "Content-Type: text/html\r\n" + "\r\n" + "<html><body><h1>425 Too Early</h1>\nYour browser sent early data></html>\n", + [HTTP_ERR_429] = "HTTP/1.0 429 Too Many Requests\r\n" "Cache-Control: no-cache\r\n" @@ -332,7 +341,7 @@ const char *get_reason(unsigned int status) case 422: return "Unprocessable entity"; case 423: return "Locked"; case 424: return "Method failure"; - case 425: return "Unordered Collection"; + case 425: return "Too Early"; case 426: return "Upgrade Required"; case 428: return "Precondition Required"; case 429: return "Too Many Requests"; @@ -378,6 +387,7 @@ static const int http_get_status_idx(unsigned int status) case 403: return HTTP_ERR_403; case 405: return HTTP_ERR_405; case 408: return HTTP_ERR_408; + case 425: return HTTP_ERR_425; case 429: return HTTP_ERR_429; case 500: return HTTP_ERR_500; case 502: return HTTP_ERR_502; -- 2.13.5