diff --git a/doc/html/man5/3proxy.cfg.5.html b/doc/html/man5/3proxy.cfg.5.html index 59c4109..c6955f6 100644 --- a/doc/html/man5/3proxy.cfg.5.html +++ b/doc/html/man5/3proxy.cfg.5.html @@ -1380,6 +1380,16 @@ ssl_client_mode mode - when to establish TLS connection: 0 - on connect (default), 1 - after authentication, 2 - before data, 3 - only for secure parent types (ending with ’s’)
+ssl_ktls
- use kernel TLS (Linux, FreeBSD) for the +connections of the services which follow, where the TLS +library, the kernel and the negotiated cipher allow it: the +kernel then encrypts and decrypts the data; ssl_noktls +(default) keeps it in 3proxy. Needs OpenSSL 3.0 or later built +with kernel TLS (enable-ktls) and, on Linux, the tls kernel +module; receiving with TLS 1.3 needs a later OpenSSL than 3.0. +Whether the kernel took sending and receiving is logged on the +first TLS connection of a service. Not supported with wolfSSL. +
ssl_certcache
/path/to/cache/ - location for the generated MITM certificates cache

diff --git a/man/3proxy.cfg.5 b/man/3proxy.cfg.5 index ba97060..b0bddbd 100644 --- a/man/3proxy.cfg.5 +++ b/man/3proxy.cfg.5 @@ -1588,6 +1588,15 @@ SSL/TLS can be used for: .BR ssl_client_mode \fImode\fR - when to establish TLS connection: 0 - on connect (default), 1 - after authentication, 2 - before data, 3 - only for secure parent types (ending with 's') .br +.BR ssl_ktls +- use kernel TLS (Linux, FreeBSD) for the connections of the services which +follow, where the TLS library, the kernel and the negotiated cipher allow it: +the kernel then encrypts and decrypts the data; \fBssl_noktls\fR (default) +keeps it in 3proxy. Needs OpenSSL 3.0 or later built with kernel TLS +(enable-ktls) and, on Linux, the tls kernel module; receiving with TLS 1.3 +needs a later OpenSSL than 3.0. Whether the kernel took sending and receiving is +logged on the first TLS connection of a service. Not supported with wolfSSL. +.br .BR ssl_certcache \fI/path/to/cache/\fR - location for the generated MITM certificates cache diff --git a/src/conf.c b/src/conf.c index 6176ab5..e8565a2 100644 --- a/src/conf.c +++ b/src/conf.c @@ -2046,6 +2046,8 @@ int h_no_client_verify(int argc, unsigned char **argv); int h_server_verify(int argc, unsigned char **argv); int h_no_server_verify(int argc, unsigned char **argv); int h_client_mode(int argc, unsigned char **argv); +int h_ktls(int argc, unsigned char **argv); +int h_noktls(int argc, unsigned char **argv); #endif #ifdef WITH_HTTPSRV int h_http_content_type(int argc, unsigned char **argv); @@ -2183,6 +2185,8 @@ struct commands commandhandlers[]={ {NULL, "ssl_client_sni", h_client_sni, 1, 2}, {NULL, "ssl_client_alpn", h_client_alpn, 1, 0}, {NULL, "ssl_client_mode", h_client_mode, 1, 2}, + {NULL, "ssl_ktls", h_ktls, 1, 1}, + {NULL, "ssl_noktls", h_noktls, 1, 1}, {NULL, "ssl_certcache", h_certcache, 2, 2}, #endif #ifdef WITH_TRANSPARENT diff --git a/src/ssl.c b/src/ssl.c index 1ed12b4..f7547e3 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -69,6 +69,7 @@ static char * client_cipher_list = NULL; static char * server_cipher_list = NULL; static char * client_sni = NULL; static int client_mode = 0; +static int ktls = 0; struct SSLsock { @@ -393,6 +394,32 @@ SSL_CONN ssl_handshake_to_client(SOCKET s, SSL_CONFIG *config, X509 *server_cert #define PCONF (((struct SSLstate *)param->sostate)->config) +/* After a handshake: read ahead, unless the kernel took receiving (kernel TLS + reads a record at a time), and say once for the service whether it took + either direction, which depends on the library, the kernel and the cipher. */ +static void ssl_after_handshake(struct clientparam* param, SSL_CONN conn){ + SSL *ssl = ((ssl_conn *)conn)->ssl; + int ksend = 0, krecv = 0; + + SSL_set_mode(ssl, SSL_MODE_ENABLE_PARTIAL_WRITE|SSL_MODE_AUTO_RETRY); +#if defined(SSL_OP_ENABLE_KTLS) && defined(BIO_get_ktls_send) && defined(BIO_get_ktls_recv) + if(PCONF->ktls){ + ksend = BIO_get_ktls_send(SSL_get_wbio(ssl)); + krecv = BIO_get_ktls_recv(SSL_get_rbio(ssl)); + if(!PCONF->ktls_reported){ + char buf[96]; + + PCONF->ktls_reported = 1; + sprintf(buf, "kernel TLS: sending %s, receiving %s", + ksend? "in the kernel" : "in 3proxy", krecv? "in the kernel" : "in 3proxy"); + param->srv->logfunc(param, (unsigned char *)buf); + } + } +#endif + if(!krecv) SSL_set_read_ahead(ssl, SSL_READAHEAD); + (void)ksend; +} + SSL_CONN dosrvcon(struct clientparam* param, SSL_CERT* cert){ SSL_CONN ServerConn; char *errSSL=NULL; @@ -408,8 +435,7 @@ SSL_CONN dosrvcon(struct clientparam* param, SSL_CERT* cert){ return NULL; } - SSL_set_mode((SSL *)((ssl_conn *)ServerConn)->ssl, SSL_MODE_ENABLE_PARTIAL_WRITE|SSL_MODE_AUTO_RETRY); - SSL_set_read_ahead((SSL *)((ssl_conn *)ServerConn)->ssl, SSL_READAHEAD); + ssl_after_handshake(param, ServerConn); return ServerConn; } @@ -441,8 +467,7 @@ int domitm(struct clientparam* param){ return 3; } - SSL_set_mode((SSL *)((ssl_conn *)ClientConn)->ssl, SSL_MODE_ENABLE_PARTIAL_WRITE|SSL_MODE_AUTO_RETRY); - SSL_set_read_ahead((SSL *)((ssl_conn *)ClientConn)->ssl, SSL_READAHEAD); + ssl_after_handshake(param, ClientConn); addSSL(param->clisock, ClientConn, param->remsock, ServerConn, param); @@ -549,6 +574,9 @@ SSL_CTX * ssl_cli_ctx(SSL_CONFIG *config, X509 *server_cert, EVP_PKEY *server_ke } } SSL_CTX_set_session_id_context(ctx, (const unsigned char *)"3proxy", 6); +#ifdef SSL_OP_ENABLE_KTLS + if(config->ktls) SSL_CTX_set_options(ctx, SSL_OP_ENABLE_KTLS); +#endif if(config->server_min_proto_version)SSL_CTX_set_min_proto_version(ctx, config->server_min_proto_version); if(config->server_max_proto_version)SSL_CTX_set_max_proto_version(ctx, config->server_max_proto_version); if(config->server_cipher_list)SSL_CTX_set_cipher_list(ctx, config->server_cipher_list); @@ -589,6 +617,7 @@ static void* ssl_filter_open(void * idata, struct srvparam * srv){ sc->server_min_proto_version = server_min_proto_version; sc->server_max_proto_version = server_max_proto_version; sc->client_mode = client_mode; + sc->ktls = ktls; sc->timeout = srv->timeouts[CONNECT_TO]; sc->client_verify = client_verify; sc->server_verify = server_verify; @@ -707,6 +736,9 @@ static void* ssl_filter_open(void * idata, struct srvparam * srv){ fprintf(stderr, "failed to set client context\n"); sc->mitm = sc->cli = 0; } +#ifdef SSL_OP_ENABLE_KTLS + if(sc->srv_ctx && sc->ktls) SSL_CTX_set_options(sc->srv_ctx, SSL_OP_ENABLE_KTLS); +#endif if(sc->client_cert){ SSL_CTX_use_certificate(sc->srv_ctx, (X509 *) sc->client_cert); ssl_ctx_use_pkey(sc->srv_ctx, sc->client_key); @@ -774,8 +806,7 @@ static FILTER_ACTION ssl_filter_client(void *fo, struct clientparam * param, voi if(err)param->srv->logfunc(param, (unsigned char *)err); return REJECT; } - SSL_set_mode((SSL *)((ssl_conn *)ClientConn)->ssl, SSL_MODE_ENABLE_PARTIAL_WRITE|SSL_MODE_AUTO_RETRY); - SSL_set_read_ahead((SSL *)((ssl_conn *)ClientConn)->ssl, SSL_READAHEAD); + ssl_after_handshake(param, ClientConn); addSSL(param->clisock, ClientConn, INVALID_SOCKET, NULL, param); } return CONTINUE; @@ -1184,6 +1215,20 @@ int h_no_server_verify(int argc, unsigned char **argv){ return 0; } +int h_ktls(int argc, unsigned char **argv){ +#ifdef SSL_OP_ENABLE_KTLS + ktls = 1; +#else + fprintf(stderr, "ssl_ktls: kernel TLS is not supported by this TLS library, ignored\n"); +#endif + return 0; +} + +int h_noktls(int argc, unsigned char **argv){ + ktls = 0; + return 0; +} + static struct symbol ssl_symbols[] = { {NULL, "ssl_parent", (void *)&ssl_parent}, }; @@ -1214,6 +1259,7 @@ void ssl_install(void){ client_verify = 0; server_verify = 0; client_mode = 0; + ktls = 0; free(client_ciphersuites); client_ciphersuites = NULL; free(server_ciphersuites); diff --git a/src/ssl.h b/src/ssl.h index 59fe580..4df3679 100644 --- a/src/ssl.h +++ b/src/ssl.h @@ -52,6 +52,8 @@ struct ssl_config { int client_verify; int server_verify; int client_mode; + int ktls; /* ssl_ktls: kernel TLS asked for (SSL_OP_ENABLE_KTLS) */ + int ktls_reported; /* whether the kernel took it has been logged */ int timeout; /* seconds a handshake step may wait: the service's CONNECT_TO */ }; diff --git a/tests/cases/ktls.py b/tests/cases/ktls.py new file mode 100644 index 0000000..4555bce --- /dev/null +++ b/tests/cases/ktls.py @@ -0,0 +1,76 @@ +"""Kernel TLS (ssl_ktls): a TLS chain works the same whether or not the +kernel takes the encryption, and the proxy says which it did. + +Whether it does depends on the TLS library (OpenSSL built with enable-ktls), +the kernel (the tls module on Linux) and the cipher, so the case checks the +data and the report, not that the kernel took it. +""" + + +def run(t): + certs = t.certs() + if not certs: + t.skip("kernel TLS (openssl is not available to generate certificates)") + return + if not certs.verified: + t.fail("the generated certificate chain verifies", "OK", + certs.verify_output or "openssl verify failed") + return + + origin = t.free_port() + parent = t.free_port() + client = t.free_port() + + server = t.start("ktls", f""" + log + auth iponly + allow * + http echo * /echo** + http data * /data + httpsrv -p{origin} + + flush + ssl_ktls + ssl_server_cert {certs.server} + ssl_server_key {certs.server_key} + ssl_serv + auth iponly + allow * + proxy -p{parent} + + flush + ssl_noserv + auth iponly + allow * + parent 1000 connects 127.0.0.1 {parent} + ssl_client_mode 3 + ssl_client_ca_file {certs.ca} + ssl_client_verify + ssl_cli + proxy -p{client} + """, ports=[origin, parent, client]) + + out = server.output() + if "Unknown command" in out: + t.skip("kernel TLS (this build has no SSL support)") + return + + through = f"127.0.0.1:{client}" + r = t.http(f"http://127.0.0.1:{origin}/echo", proxy=through) + t.eq(200, r.status, "a request through the TLS chain with ssl_ktls arrives") + t.contains(r, "path=/echo", "the origin sees it") + + # many records, both ways through both TLS legs + for size in (100000, 3000000): + t.eq(size, t.http(f"http://127.0.0.1:{origin}/data?size={size}", + proxy=through).length, + f"a {size} byte body survives the kernel TLS chain") + t.eq(200000, t.http(f"http://127.0.0.1:{origin}/data?size=200000&chunked=1", + proxy=through).length, + "a chunked body survives the kernel TLS chain") + + if "not supported by this TLS library" in server.output(): + t.skip("the kernel TLS report (this TLS library has no kernel TLS)") + return + log = t.wait_output(server, "kernel TLS:") + t.contains(log, "kernel TLS: sending", "the proxy says whether the kernel took TLS")