From 14106ea40a55acbba0d14a6f66350221ade044ab Mon Sep 17 00:00:00 2001 From: Simon Ruderich Date: Tue, 8 Mar 2011 03:47:29 +0100 Subject: [PATCH] Intercept TLS connections between client and server. Talk to the client as TLS server and to the server as TLS client and transferring data transparently between them. _No_ fingerprint validation of the server certificate is done yet! --- configure.ac | 3 +- src/connection.c | 337 ++++++++++++++++++++++++++++++++++++++++++++++- src/tlsproxy.c | 57 ++++++++ src/tlsproxy.h | 17 ++- 4 files changed, 410 insertions(+), 4 deletions(-) diff --git a/configure.ac b/configure.ac index 82957a7..9ca7e39 100644 --- a/configure.ac +++ b/configure.ac @@ -9,7 +9,8 @@ AM_INIT_AUTOMAKE([foreign -Wall -Werror]) AC_PROG_CC if test "x$GCC" = xyes; then - CFLAGS="-std=c89 -pedantic -Wall -Wextra -Werror -D_XOPEN_SOURCE=500 $CFLAGS" + CFLAGS="-std=c89 -pedantic -Wall -Wextra -Werror $CFLAGS" + CFLAGS="-D_XOPEN_SOURCE=500 -Wno-error=int-to-pointer-cast $CFLAGS" fi AC_CHECK_LIB([pthread], [pthread_create], diff --git a/src/connection.c b/src/connection.c index 37f2550..8e023fe 100644 --- a/src/connection.c +++ b/src/connection.c @@ -39,6 +39,10 @@ * should be a good limit to make processing simpler. */ #define MAX_REQUEST_LINE 4096 +/* Paths to necessary TLS files: the CA and the server key. */ +#define PROXY_CA_FILE "proxy-ca.pem" +#define PROXY_KEY_FILE "proxy-key.pem" + /* Helper macro for LOG/LOG_PERROR. Print file/line number if compiled with * debug output. */ #ifdef DEBUG @@ -56,12 +60,26 @@ log_message(level, "%s: %s", message, strerror(errno)) +static int initialize_tls_session_client(int peer_socket, + const char *hostname, + gnutls_session_t *session, + gnutls_certificate_credentials_t *x509_cred); +static int initialize_tls_session_server(int peer_socket, + gnutls_session_t *session, + gnutls_certificate_credentials_t *x509_cred); + static int read_http_request(FILE *client_fd, char *request, size_t length); static void send_bad_request(FILE *client_fd); static void send_forwarding_failure(FILE *client_fd); +#if 0 static void transfer_data(int client, int server); static int read_from_write_to(int from, int to); +#endif +static void transfer_data_tls(int client, int server, + gnutls_session_t client_session, + gnutls_session_t server_session); +static int read_from_write_to_tls(gnutls_session_t from, gnutls_session_t to); static int connect_to_host(const char *hostname, const char *port); @@ -82,11 +100,26 @@ void handle_connection(int client_socket) { int version_minor; int result; + /* client_x509_cred is used when talking to the client (acting as a TSL + * server), server_x509_cred is used when talking to the server (acting as + * a TSL client). */ + gnutls_certificate_credentials_t client_x509_cred, server_x509_cred; + + gnutls_session_t client_session, server_session; + /* initialize_tls_session_*() called? - used for goto out */ + int client_session_init, server_session_init; + /* gnutls_handshake() called? - used for goto out */ + int client_session_started, server_session_started; + LOG(LOG_DEBUG, "new connection"); server_socket = -1; client_fd = NULL; server_fd = NULL; + client_session_init = 0; + server_session_init = 0; + client_session_started = 0; + server_session_started = 0; client_fd = fdopen(client_socket, "a+"); if (NULL == client_fd) { @@ -171,10 +204,83 @@ void handle_connection(int client_socket) { fprintf(client_fd, "\r\n"); fflush(client_fd); - /* And transfer all data between client and server transparently. */ - transfer_data(client_socket, server_socket); + /* Initialize TLS server credentials to talk to the client. */ + result = initialize_tls_session_client(client_socket, host, + &client_session, + &client_x509_cred); + if (0 != result) { + LOG(LOG_WARNING, "initialize_tls_session_client() failed"); + send_forwarding_failure(client_fd); + goto out; + } + client_session_init = 1; + + LOG(LOG_DEBUG, "starting client TLS handshake"); + + /* Try to establish TLS handshake between client and us. */ + result = gnutls_handshake(client_session); + if (GNUTLS_E_SUCCESS != result) { + LOG(LOG_WARNING, "client TLS handshake failed: %s", + gnutls_strerror(result)); + send_forwarding_failure(client_fd); + goto out; + } + client_session_started = 1; + + LOG(LOG_DEBUG, "client TLS handshake finished"); + + result = initialize_tls_session_server(server_socket, &server_session, + &server_x509_cred); + /* Initialize TLS client credentials to talk to the server. */ + if (0 != result) { + LOG(LOG_WARNING, "initialize_tls_session_server() failed"); + send_forwarding_failure(client_fd); + goto out; + } + server_session_init = 1; + + LOG(LOG_DEBUG, "starting server TLS handshake"); + + /* Try to establish TLS handshake between us and server. */ + result = gnutls_handshake(server_session); + if (GNUTLS_E_SUCCESS != result) { + LOG(LOG_WARNING, "server TLS handshake failed: %s", + gnutls_strerror(result)); + send_forwarding_failure(client_fd); + goto out; + } + server_session_started = 1; + + LOG(LOG_DEBUG, "server TLS handshake finished, transferring data"); + + /* FIXME: verify server's fingerprint */ + + /* Proxy data between client and server until one suite is done (EOF or + * error). */ + transfer_data_tls(client_socket, server_socket, + client_session, server_session); + + LOG(LOG_DEBUG, "finished transferring data"); out: + /* Close TLS sessions if necessary. */ + if (0 != server_session_started) { + gnutls_bye(server_session, GNUTLS_SHUT_WR); + } + if (0 != client_session_started) { + gnutls_bye(client_session, GNUTLS_SHUT_WR); + } + if (0 != server_session_init) { + gnutls_deinit(server_session); + gnutls_certificate_free_credentials(server_x509_cred); + } + if (0 != client_session_init) { + gnutls_deinit(client_session); + gnutls_certificate_free_cas(client_x509_cred); + gnutls_certificate_free_keys(client_x509_cred); + gnutls_certificate_free_credentials(client_x509_cred); + } + /* Close connection to server/proxy. */ if (NULL != server_fd) { fclose(server_fd); @@ -193,6 +299,138 @@ out: LOG(LOG_DEBUG, "connection finished"); } + +static int initialize_tls_session_client(int peer_socket, + const char *hostname, + gnutls_session_t *session, + gnutls_certificate_credentials_t *x509_cred) { + int result; + char path[1024]; + /* The server certificate for the given hostname is stored in + * "./certificate-hostname-proxy.pem". */ +#define PATH_FORMAT "./certificate-%s-proxy.pem" + + /* Hostname too long. */ + if (sizeof(path) - strlen(PATH_FORMAT) <= strlen(hostname)) { + LOG(LOG_WARNING, + "initialize_tls_session_client(): hostname too long: '%s'", + hostname); + return -1; + } + /* Try to prevent path traversals in hostnames. */ + if (NULL != strstr(hostname, "..")) { + LOG(LOG_WARNING, + "initialize_tls_session_client(): possible path traversal: '%s'", + hostname); + return -1; + } + snprintf(path, sizeof(path), PATH_FORMAT, hostname); +#undef PATH_FORMAT + + result = gnutls_certificate_allocate_credentials(x509_cred); + if (GNUTLS_E_SUCCESS != result) { + LOG(LOG_ERROR, + "initialize_tls_session_client(): \ +gnutls_certificate_allocate_credentials(): %s", + gnutls_strerror(result)); + return -1; + } + + /* Load proxy CA file, this CA "list" is send to the client. */ + result = gnutls_certificate_set_x509_trust_file(*x509_cred, + PROXY_CA_FILE, + GNUTLS_X509_FMT_PEM); + if (0 >= result) { + LOG(LOG_ERROR, + "initialize_tls_session_client(): can't read CA file: '%s'", + PROXY_CA_FILE); + return -1; + } + /* And certificate for this website and proxy's private key. */ + result = gnutls_certificate_set_x509_key_file(*x509_cred, + path, PROXY_KEY_FILE, + GNUTLS_X509_FMT_PEM); + if (GNUTLS_E_SUCCESS != result) { + LOG(LOG_ERROR, + "initialize_tls_session_client(): \ +can't read server certificate ('%s') or key file ('%s'): %s", + path, PROXY_KEY_FILE, gnutls_strerror(result)); + /* Could be a missing certificate. */ + return -2; + } + + gnutls_certificate_set_dh_params(*x509_cred, tls_dh_params); + + result = gnutls_init(session, GNUTLS_SERVER); + if (GNUTLS_E_SUCCESS != result) { + LOG(LOG_ERROR, + "initialize_tls_session_client(): gnutls_init(): %s", + gnutls_strerror(result)); + return -1; + } + result = gnutls_priority_set(*session, tls_priority_cache); + if (GNUTLS_E_SUCCESS != result) { + LOG(LOG_ERROR, + "initialize_tls_session_client(): gnutls_priority_set(): %s", + gnutls_strerror(result)); + return -1; + } + result = gnutls_credentials_set(*session, + GNUTLS_CRD_CERTIFICATE, *x509_cred); + if (GNUTLS_E_SUCCESS != result) { + LOG(LOG_ERROR, + "initialize_tls_session_client(): gnutls_credentials_set(): %s", + gnutls_strerror(result)); + return -1; + } + + gnutls_transport_set_ptr(*session, (gnutls_transport_ptr_t)peer_socket); + + return 0; +} +static int initialize_tls_session_server(int peer_socket, + gnutls_session_t *session, + gnutls_certificate_credentials_t *x509_cred) { + int result; + + result = gnutls_certificate_allocate_credentials(x509_cred); + if (GNUTLS_E_SUCCESS != result) { + LOG(LOG_ERROR, + "initialize_tls_session_server(): \ +gnutls_certificate_allocate_credentials(): %s", + gnutls_strerror(result)); + return -1; + } + + result = gnutls_init(session, GNUTLS_CLIENT); + if (GNUTLS_E_SUCCESS != result) { + LOG(LOG_ERROR, + "initialize_tls_session_server(): gnutls_init(): %s", + gnutls_strerror(result)); + return -1; + } + gnutls_priority_set(*session, tls_priority_cache); + if (GNUTLS_E_SUCCESS != result) { + LOG(LOG_ERROR, + "initialize_tls_session_server(): gnutls_priority_set(): %s", + gnutls_strerror(result)); + return -1; + } + result = gnutls_credentials_set(*session, + GNUTLS_CRD_CERTIFICATE, *x509_cred); + if (GNUTLS_E_SUCCESS != result) { + LOG(LOG_ERROR, + "initialize_tls_session_server(): gnutls_credentials_set(): %s", + gnutls_strerror(result)); + return -1; + } + + gnutls_transport_set_ptr(*session, (gnutls_transport_ptr_t)peer_socket); + + return 0; +} + + /* Read HTTP request line and headers (ignored). * * On success 0 is returned, -1 on client error, -2 on unexpected EOF. @@ -233,6 +471,7 @@ static void send_forwarding_failure(FILE *client_fd) { } +#if 0 /* Transfer data between client and server sockets until one closes the * connection. */ static void transfer_data(int client, int server) { @@ -307,6 +546,100 @@ static int read_from_write_to(int from, int to) { return 0; } +#endif + +/* Transfer data between client and server TLS connection until one closes the + * connection. */ +static void transfer_data_tls(int client, int server, + gnutls_session_t client_session, + gnutls_session_t server_session) { + struct pollfd fds[2]; + fds[0].fd = client; + fds[0].events = POLLIN | POLLPRI | POLLHUP | POLLERR; + fds[0].revents = 0; + fds[1].fd = server; + fds[1].events = POLLIN | POLLPRI | POLLHUP | POLLERR; + fds[1].revents = 0; + + for (;;) { + int result = poll(fds, 2, -1 /* no timeout */); + if (result < 0) { + LOG_PERROR(LOG_ERROR, "transfer_data(): poll()"); + return; + } + + /* Data available from client. */ + if (fds[0].revents & POLLIN || fds[0].revents & POLLPRI) { + if (0 != read_from_write_to_tls(client_session, server_session)) { + /* EOF (or other error) */ + break; + } + } + /* Data available from server. */ + if (fds[1].revents & POLLIN || fds[1].revents & POLLPRI) { + if (0 != read_from_write_to_tls(server_session, client_session)) { + /* EOF (or other error) */ + break; + } + } + + /* Client closed connection. */ + if (fds[0].revents & POLLERR || fds[0].revents & POLLHUP) { + break; + } + /* Server closed connection. */ + if (fds[1].revents & POLLERR || fds[1].revents & POLLHUP) { + break; + } + } +} + +/* Read available data from session from and write to session to. */ +static int read_from_write_to_tls(gnutls_session_t from, + gnutls_session_t to) { + size_t size; + ssize_t size_read; + ssize_t size_written; + char buffer[16384]; + + /* Get maximum possible buffer size. */ + size = gnutls_record_get_max_size(from); + LOG(LOG_DEBUG, "read_from_write_to_tls(): suggested buffer size: %ld", + (long int)size); + if (size > gnutls_record_get_max_size(to)) { + size = gnutls_record_get_max_size(to); + } + if (size > sizeof(buffer)) { + size = sizeof(buffer); + } + LOG(LOG_DEBUG, "read_from_write_to_tls(): used buffer size: %ld", + (long int)size); + + size_read = gnutls_record_recv(from, buffer, size); + if (0 > size_read) { + LOG(LOG_WARNING, "read_from_write_to_tls(): gnutls_record_recv(): %s", + gnutls_strerror((int)size_read)); + return -1; + } + /* EOF */ + if (0 == size_read) { + return -1; + } + + size_written = gnutls_record_send(to, buffer, (size_t)size_read); + if (0 > size_written) { + LOG(LOG_WARNING, "read_from_write_to_tls(): gnutls_record_send(): %s", + gnutls_strerror((int)size_written)); + return -1; + } + if (size_read != size_written) { + LOG(LOG_ERROR, "read_from_write_to_tls(): only written %ld of %ld bytes!", + (long int)size_written, (long int)size_read); + return -1; + } + + return 0; +} static int connect_to_host(const char *hostname, const char *port) { diff --git a/src/tlsproxy.c b/src/tlsproxy.c index 2a35eae..0e09568 100644 --- a/src/tlsproxy.c +++ b/src/tlsproxy.c @@ -35,15 +35,25 @@ /* pthread_*() */ #include +/* For GnuTLS. */ +#include + +GCRY_THREAD_OPTION_PTHREAD_IMPL; + + /* Size of ringbuffer. */ #define RINGBUFFER_SIZE 10 +/* Bit size of Diffie-Hellman key exchange parameters. */ +#define DH_SIZE 1024 + /* Server should shut down. Set by SIGINT handler. */ static volatile int done; /* Number of threads. */ static size_t thread_count; + /* Synchronized ring buffer storing accept()ed client sockets. */ static int ringbuffer[RINGBUFFER_SIZE]; static int ringbuffer_read; @@ -58,6 +68,9 @@ static void sigint_handler(int signal); static void parse_arguments(int argc, char **argv); static void print_usage(const char *argv); +static void initialize_gnutls(void); +static void deinitialize_gnutls(void); + static void worker_thread(void); @@ -103,6 +116,8 @@ int main(int argc, char **argv) { return EXIT_FAILURE; } + initialize_gnutls(); + /* Spawn worker threads to handle requests. */ threads = (pthread_t *)malloc(thread_count * sizeof(pthread_t)); if (NULL == threads) { @@ -202,6 +217,8 @@ int main(int argc, char **argv) { free(threads); + deinitialize_gnutls(); + free(global_proxy_host); free(global_proxy_port); @@ -298,6 +315,46 @@ static void print_usage(const char *argv) { fprintf(stderr, "-t number of threads [default: 10]\n"); } +static void initialize_gnutls(void) { + int result; + gcry_error_t error = 0; + + /* Thread safe setup. Must be called before gnutls_global_init(). */ + error = gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread); + if (error) { + fprintf(stderr, "gcry_control(): %s/%s\n", gcry_strsource(error), + gcry_strerror(error)); + exit(EXIT_FAILURE); + } + /* Prevent usage of blocking /dev/random. */ + error = gcry_control(GCRYCTL_ENABLE_QUICK_RANDOM, 0); + if (error) { + fprintf(stderr, "gcry_control(): %s/%s\n", gcry_strsource(error), + gcry_strerror(error)); + exit(EXIT_FAILURE); + } + + /* Initialize GnuTLS. */ + result = gnutls_global_init(); + GNUTLS_ERROR_EXIT(result, "gnutls_global_init()"); + + /* Setup GnuTLS cipher suites. */ + result = gnutls_priority_init(&tls_priority_cache, "NORMAL", NULL); + GNUTLS_ERROR_EXIT(result, "gnutls_priority_init()"); + + /* Generate Diffie-Hellman parameters. */ + result = gnutls_dh_params_init(&tls_dh_params); + GNUTLS_ERROR_EXIT(result, "gnutls_dh_params_init()"); + result = gnutls_dh_params_generate2(tls_dh_params, DH_SIZE); + GNUTLS_ERROR_EXIT(result, "gnutls_dh_params_generate2()"); +} +static void deinitialize_gnutls(void) { + gnutls_dh_params_deinit(tls_dh_params); + gnutls_priority_deinit(tls_priority_cache); + + gnutls_global_deinit(); +} + static void worker_thread(void) { int client_socket; diff --git a/src/tlsproxy.h b/src/tlsproxy.h index 8b6f4fc..b2d690e 100644 --- a/src/tlsproxy.h +++ b/src/tlsproxy.h @@ -1,5 +1,5 @@ /* - * Global variables. + * Global variables/defines. * * Copyright (C) 2011 Simon Ruderich * @@ -26,6 +26,9 @@ #include #include +/* GnuTLS */ +#include + /* Log level constants. */ #define LOG_ERROR 0 @@ -33,6 +36,14 @@ #define LOG_DEBUG 2 +/* Macros for shorter error handling. */ +#define GNUTLS_ERROR_EXIT(error, message) \ + if (GNUTLS_E_SUCCESS != error) { \ + fprintf(stderr, "%s: %s\n", message, gnutls_strerror(error)); \ + exit(EXIT_FAILURE); \ + } + + /* Proxy hostname and port if specified on the command line. */ char *global_proxy_host; char *global_proxy_port; @@ -40,4 +51,8 @@ char *global_proxy_port; /* Log level, command line option. */ int global_log_level; +/* "Global" GnuTLS data used by all threads, read only. */ +gnutls_priority_t tls_priority_cache; +gnutls_dh_params_t tls_dh_params; + #endif -- 2.43.2