Hi, > I take it that means theres no internal debug logging for the tls errors that > we can just expose via logfile?
Proof of concept patches are attached with build instructions. You may wish to edit the haproxy-2.2.3/rules/debian folder to increase the -j setting to your current number of cores. The "disambiguate-ssl-handshake-errors-1.patch" only adds additional error messages for the initial ClientHello processing - realistically, it's only useful to see if there is no SNI being sent (bots, healthchecks are the usual offenders). The "disambiguate-ssl-handshake-errors-2.patch" implements everything the first patch implements, adds a trash chunk for logging additional error data to the conn structure, and reuses the SSL error logging logic from ssl_sock_dump_errors Practically, this means that memory usage is higher - if I recall correctly (and it's way to late/early at this point) it's a 16KB overhead per connection (echo "show pools" | socat stdio /var/run/haproxy.sock will have a more detailed breakdown). Watching the output of show pools is recommended - while I haven't noticed a memory leak, keeping an eye on the trash pool is a good idea. The fcgi protocol is also affected by the addition of the extra_err_data. I have to do a smoke test if "proto fcgi" behaves as expected, or if there's a potential segfault. The patch works, but it requires more extensive testing. Sharing it as-is since I might not be able to pursue this further in a significant way for some time. The mapping between the error messages and the potential causes can be a bit obscure, but it's still useful. E.g. an invalid SNI when using strict-sni maps to: tls_post_process_client_hello: no shared cipher If there's a cipher mismatch, this also maps to the above error message. A protocol version mismatch (e.g. trying TLS1 when only TLS1.2 is supported) results in: tls_early_post_process_client_hello: unsupported protocol. The list of error codes is available upstream at https://github.com/openssl/openssl/blob/OpenSSL_1_1_1-stable/crypto/err/openssl.txt#L2774 . Regarding the packet capture question - exporting libpcap data via SPOE might be possible. It's an ongoing topic. Regards, Bruno
diff -ur a/include/haproxy/connection.h b/include/haproxy/connection.h
--- a/include/haproxy/connection.h 2020-09-10 05:49:02.705917730 +0200
+++ b/include/haproxy/connection.h 2020-09-10 05:12:30.287797636 +0200
@@ -382,8 +382,10 @@
struct connection *conn;
conn = pool_alloc(pool_head_connection);
+ conn->extra_err_data = alloc_trash_chunk();
if (likely(conn != NULL))
- conn_init(conn);
+ if(likely(conn->extra_err_data != NULL))
+ conn_init(conn);
return conn;
}
@@ -458,7 +460,7 @@
sess->idle_conns--;
session_unown_conn(sess, conn);
}
-
+ free_trash_chunk(conn->extra_err_data);
sockaddr_free(&conn->src);
sockaddr_free(&conn->dst);
@@ -697,8 +699,12 @@
case CO_ER_SSL_CRT_FAIL: return "SSL client certificate not trusted";
case CO_ER_SSL_MISMATCH: return "Server presented an SSL certificate different from the configured one";
case CO_ER_SSL_MISMATCH_SNI: return "Server presented an SSL certificate different from the expected one";
- case CO_ER_SSL_HANDSHAKE: return "SSL handshake failure";
+ case CO_ER_SSL_HANDSHAKE: return "SSL handshake failure";
case CO_ER_SSL_HANDSHAKE_HB: return "SSL handshake failure after heartbeat";
+ case CO_ER_SSL_HSHK_CL_SNI_GBRSH: return "SSL handshake failure: ClientHello server name (SNI) null, too long, or invalid";
+ case CO_ER_SSL_HSHK_CL_S_SNI: return "SSL handshake failure: ClientHello server name (SNI) missing. SNI is required when strict-sni is used";
+ case CO_ER_SSL_HSHK_CL_CIPHERS: return "SSL handshake failure: ClientHello ciphers are invalid";
+ case CO_ER_SSL_HSHK_CL_SIGALGS: return "SSL handshake failure: ClientHello signature algorithms are invalid";
case CO_ER_SSL_KILLED_HB: return "Stopped a TLSv1 heartbeat attack (CVE-2014-0160)";
case CO_ER_SSL_NO_TARGET: return "Attempt to use SSL on an unknown target (internal error)";
diff -ur a/include/haproxy/connection-t.h b/include/haproxy/connection-t.h
--- a/include/haproxy/connection-t.h 2020-09-10 05:49:02.705917730 +0200
+++ b/include/haproxy/connection-t.h 2020-09-10 05:13:00.007799264 +0200
@@ -233,6 +233,10 @@
CO_ER_SSL_MISMATCH_SNI, /* Server presented an SSL certificate different from the expected one */
CO_ER_SSL_HANDSHAKE, /* SSL error during handshake */
CO_ER_SSL_HANDSHAKE_HB, /* SSL error during handshake with heartbeat present */
+ CO_ER_SSL_HSHK_CL_SNI_GBRSH, /* SSL handshake failure: ClientHello server name (SNI) null, too long, or invalid */
+ CO_ER_SSL_HSHK_CL_S_SNI, /* SSL handshake failure: ClientHello server name (SNI) missing - SNI is required if strict-sni is enabled */
+ CO_ER_SSL_HSHK_CL_CIPHERS, /* SSL handshake failure: ClientHello ciphers are invalid */
+ CO_ER_SSL_HSHK_CL_SIGALGS, /* SSL handshake failure: ClientHello signature algorithms are invalid */
CO_ER_SSL_KILLED_HB, /* Stopped a TLSv1 heartbeat attack (CVE-2014-0160) */
CO_ER_SSL_NO_TARGET, /* unknown target (not client nor server) */
CO_ER_SSL_EARLY_FAILED, /* Server refused early data */
@@ -467,6 +471,7 @@
/* first cache line */
enum obj_type obj_type; /* differentiates connection from applet context */
unsigned char err_code; /* CO_ER_* */
+ struct buffer *extra_err_data; /* For storing (usually SSL-related) extra data */
signed short send_proxy_ofs; /* <0 = offset to (re)send from the end, >0 = send all (reused for SOCKS4) */
unsigned int flags; /* CO_FL_* */
const struct protocol *ctrl; /* operations at the socket layer */
diff -ur a/src/mux_fcgi.c b/src/mux_fcgi.c
--- a/src/mux_fcgi.c 2020-09-10 05:49:02.713917730 +0200
+++ b/src/mux_fcgi.c 2020-09-10 03:56:42.229548508 +0200
@@ -783,6 +783,7 @@
task_destroy(t);
if (fconn->wait_event.tasklet)
tasklet_free(fconn->wait_event.tasklet);
+ free_trash_chunk(fconn->conn->extra_err_data);
pool_free(pool_head_fcgi_conn, fconn);
fail_conn:
conn->ctx = conn_ctx; // restore saved ctx
@@ -853,6 +854,7 @@
conn->xprt->unsubscribe(conn, conn->xprt_ctx, fconn->wait_event.events,
&fconn->wait_event);
+ free_trash_chunk(fconn->conn->extra_err_data);
pool_free(pool_head_fcgi_conn, fconn);
}
diff -ur a/src/session.c b/src/session.c
--- a/src/session.c 2020-09-10 05:49:02.713917730 +0200
+++ b/src/session.c 2020-09-10 05:44:14.149901924 +0200
@@ -381,12 +381,13 @@
session_prepare_log_prefix(sess);
err_msg = conn_err_code_str(conn);
+
if (err_msg)
- send_log(sess->fe, level, "%s: %s\n", trash.area,
- err_msg);
+ send_log(sess->fe, level, "%s: %s %s\n", trash.area,
+ err_msg, conn->extra_err_data->area);
else
- send_log(sess->fe, level, "%s: unknown connection error (code=%d flags=%08x)\n",
- trash.area, conn->err_code, conn->flags);
+ send_log(sess->fe, level, "%s: unknown connection error (code=%d flags=%08x) %s\n",
+ trash.area, conn->err_code, conn->flags, conn->extra_err_data->area);
}
/* kill the connection now */
diff -ur a/src/ssl_sock.c b/src/ssl_sock.c
--- a/src/ssl_sock.c 2020-09-10 05:49:02.711917730 +0200
+++ b/src/ssl_sock.c 2020-09-10 05:34:41.710870567 +0200
@@ -2240,28 +2240,38 @@
* was written, so parsing the normal case is a bit complex.
*/
size_t len;
- if (extension_len <= 2)
+ if (extension_len <= 2) {
+ conn->err_code = CO_ER_SSL_HSHK_CL_SNI_GBRSH;
goto abort;
+ }
/* Extract the length of the supplied list of names. */
len = (*extension_data++) << 8;
len |= *extension_data++;
- if (len + 2 != extension_len)
+ if (len + 2 != extension_len) {
+ conn->err_code = CO_ER_SSL_HSHK_CL_SNI_GBRSH;
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)
+ if (len == 0 || *extension_data++ != TLSEXT_NAMETYPE_host_name) {
+ conn->err_code = CO_ER_SSL_HSHK_CL_SNI_GBRSH;
goto abort;
+ }
extension_len = len - 1;
/* Now we can finally pull out the byte array with the actual hostname. */
- if (extension_len <= 2)
+ if (extension_len <= 2) {
+ conn->err_code = CO_ER_SSL_HSHK_CL_SNI_GBRSH;
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)
+ || memchr(extension_data, 0, len) != NULL) {
+ conn->err_code = CO_ER_SSL_HSHK_CL_SNI_GBRSH;
goto abort;
+ }
servername = extension_data;
servername_len = len;
} else {
@@ -2277,6 +2287,7 @@
HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock);
goto allow_early;
}
+ conn->err_code = CO_ER_SSL_HSHK_CL_S_SNI;
goto abort;
}
@@ -2288,14 +2299,20 @@
#endif
uint8_t sign;
size_t len;
- if (extension_len < 2)
+ if (extension_len < 2) {
+ conn->err_code = CO_ER_SSL_HSHK_CL_SIGALGS;
goto abort;
+ }
len = (*extension_data++) << 8;
len |= *extension_data++;
- if (len + 2 != extension_len)
+ if (len + 2 != extension_len) {
+ conn->err_code = CO_ER_SSL_HSHK_CL_SIGALGS;
goto abort;
- if (len % 2 != 0)
+ }
+ if (len % 2 != 0) {
+ conn->err_code = CO_ER_SSL_HSHK_CL_SIGALGS;
goto abort;
+ }
for (; len > 0; len -= 2) {
extension_data++; /* hash */
sign = *extension_data++;
@@ -2327,8 +2344,10 @@
#else
len = SSL_client_hello_get0_ciphers(ssl, &cipher_suites);
#endif
- if (len % 2 != 0)
+ if (len % 2 != 0) {
+ conn->err_code = CO_ER_SSL_HSHK_CL_CIPHERS;
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];
@@ -2446,7 +2465,8 @@
return 1;
abort:
/* abort handshake (was SSL_TLSEXT_ERR_ALERT_FATAL) */
- conn->err_code = CO_ER_SSL_HANDSHAKE;
+ if (!conn->err_code)
+ conn->err_code = CO_ER_SSL_HANDSHAKE;
#ifdef OPENSSL_IS_BORINGSSL
return ssl_select_cert_error;
#else
@@ -5554,6 +5574,10 @@
out_error:
/* Clear openssl global errors stack */
+ ret = ERR_get_error();
+ chunk_printf(conn->extra_err_data, "fd[%04x] OpenSSL error[0x%lx] %s: %s",
+ (unsigned short)conn->handle.fd, ret,
+ ERR_func_error_string(ret), ERR_reason_error_string(ret));
ssl_sock_dump_errors(conn);
ERR_clear_error();
diff -ur a/include/haproxy/connection-t.h b/include/haproxy/connection-t.h
--- a/include/haproxy/connection-t.h 2020-07-31 09:54:32.000000000 +0000
+++ b/include/haproxy/connection-t.h 2020-09-02 04:01:41.287339829 +0000
@@ -233,6 +233,10 @@
CO_ER_SSL_MISMATCH_SNI, /* Server presented an SSL certificate different from the expected one */
CO_ER_SSL_HANDSHAKE, /* SSL error during handshake */
CO_ER_SSL_HANDSHAKE_HB, /* SSL error during handshake with heartbeat present */
+ CO_ER_SSL_HSHK_CL_SNI_GBRSH, /* SSL handshake failure: ClientHello server name (SNI) null, too long, or invalid */
+ CO_ER_SSL_HSHK_CL_S_SNI, /* SSL handshake failure: ClientHello server name (SNI) invalid in strict-sni context */
+ CO_ER_SSL_HSHK_CL_CIPHERS, /* SSL handshake failure: ClientHello ciphers are invalid */
+ CO_ER_SSL_HSHK_CL_SIGALGS, /* SSL handshake failure: ClientHello signature algorithms are invalid */
CO_ER_SSL_KILLED_HB, /* Stopped a TLSv1 heartbeat attack (CVE-2014-0160) */
CO_ER_SSL_NO_TARGET, /* unknown target (not client nor server) */
CO_ER_SSL_EARLY_FAILED, /* Server refused early data */
diff -ur a/include/haproxy/connection.h b/include/haproxy/connection.h
--- a/include/haproxy/connection.h 2020-07-31 09:54:32.000000000 +0000
+++ b/include/haproxy/connection.h 2020-09-02 04:01:41.295339815 +0000
@@ -697,8 +697,12 @@
case CO_ER_SSL_CRT_FAIL: return "SSL client certificate not trusted";
case CO_ER_SSL_MISMATCH: return "Server presented an SSL certificate different from the configured one";
case CO_ER_SSL_MISMATCH_SNI: return "Server presented an SSL certificate different from the expected one";
- case CO_ER_SSL_HANDSHAKE: return "SSL handshake failure";
+ case CO_ER_SSL_HANDSHAKE: return "SSL handshake failure";
case CO_ER_SSL_HANDSHAKE_HB: return "SSL handshake failure after heartbeat";
+ case CO_ER_SSL_HSHK_CL_SNI_GBRSH: return "SSL handshake failure: ClientHello server name (SNI) null, too long, or invalid";
+ case CO_ER_SSL_HSHK_CL_S_SNI: return "SSL handshake failure: ClientHello server name (SNI) invalid in strict-sni context";
+ case CO_ER_SSL_HSHK_CL_CIPHERS: return "SSL handshake failure: ClientHello ciphers are invalid";
+ case CO_ER_SSL_HSHK_CL_SIGALGS: return "SSL handshake failure: ClientHello signature algorithms are invalid";
case CO_ER_SSL_KILLED_HB: return "Stopped a TLSv1 heartbeat attack (CVE-2014-0160)";
case CO_ER_SSL_NO_TARGET: return "Attempt to use SSL on an unknown target (internal error)";
diff -ur a/src/ssl_sock.c b/src/ssl_sock.c
--- a/src/ssl_sock.c 2020-07-31 09:54:32.000000000 +0000
+++ b/src/ssl_sock.c 2020-09-02 04:01:41.315339779 +0000
@@ -2207,28 +2207,38 @@
* was written, so parsing the normal case is a bit complex.
*/
size_t len;
- if (extension_len <= 2)
+ if (extension_len <= 2) {
+ conn->err_code = CO_ER_SSL_HSHK_CL_SNI_GBRSH;
goto abort;
+ }
/* Extract the length of the supplied list of names. */
len = (*extension_data++) << 8;
len |= *extension_data++;
- if (len + 2 != extension_len)
+ if (len + 2 != extension_len) {
+ conn->err_code = CO_ER_SSL_HSHK_CL_SNI_GBRSH;
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)
+ if (len == 0 || *extension_data++ != TLSEXT_NAMETYPE_host_name) {
+ conn->err_code = CO_ER_SSL_HSHK_CL_SNI_GBRSH;
goto abort;
+ }
extension_len = len - 1;
/* Now we can finally pull out the byte array with the actual hostname. */
- if (extension_len <= 2)
+ if (extension_len <= 2) {
+ conn->err_code = CO_ER_SSL_HSHK_CL_SNI_GBRSH;
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)
+ || memchr(extension_data, 0, len) != NULL) {
+ conn->err_code = CO_ER_SSL_HSHK_CL_SNI_GBRSH;
goto abort;
+ }
servername = extension_data;
servername_len = len;
} else {
@@ -2244,6 +2254,7 @@
HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock);
goto allow_early;
}
+ conn->err_code = CO_ER_SSL_HSHK_CL_S_SNI;
goto abort;
}
@@ -2255,14 +2266,20 @@
#endif
uint8_t sign;
size_t len;
- if (extension_len < 2)
+ if (extension_len < 2) {
+ conn->err_code = CO_ER_SSL_HSHK_CL_SIGALGS;
goto abort;
+ }
len = (*extension_data++) << 8;
len |= *extension_data++;
- if (len + 2 != extension_len)
+ if (len + 2 != extension_len) {
+ conn->err_code = CO_ER_SSL_HSHK_CL_SIGALGS;
goto abort;
- if (len % 2 != 0)
+ }
+ if (len % 2 != 0) {
+ conn->err_code = CO_ER_SSL_HSHK_CL_SIGALGS;
goto abort;
+ }
for (; len > 0; len -= 2) {
extension_data++; /* hash */
sign = *extension_data++;
@@ -2294,8 +2311,10 @@
#else
len = SSL_client_hello_get0_ciphers(ssl, &cipher_suites);
#endif
- if (len % 2 != 0)
+ if (len % 2 != 0) {
+ conn->err_code = CO_ER_SSL_HSHK_CL_CIPHERS;
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];
@@ -2391,7 +2410,8 @@
return 1;
abort:
/* abort handshake (was SSL_TLSEXT_ERR_ALERT_FATAL) */
- conn->err_code = CO_ER_SSL_HANDSHAKE;
+ if (!conn->err_code)
+ conn->err_code = CO_ER_SSL_HANDSHAKE;
#ifdef OPENSSL_IS_BORINGSSL
return ssl_select_cert_error;
#else
apt-debuild
Description: Binary data

