]> ruderich.org/simon Gitweb - tlsproxy/tlsproxy.git/commitdiff
Intercept TLS connections between client and server.
authorSimon Ruderich <simon@ruderich.org>
Tue, 8 Mar 2011 02:47:29 +0000 (03:47 +0100)
committerSimon Ruderich <simon@ruderich.org>
Tue, 8 Mar 2011 02:47:29 +0000 (03:47 +0100)
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
src/connection.c
src/tlsproxy.c
src/tlsproxy.h

index 82957a7776f5d6d2688581dd3d348a42a9612216..9ca7e3950256102807c43a98b206cadc17c0872a 100644 (file)
@@ -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],
index 37f255013201a7bcc120da1453d71b6a5e3bb866..8e023fe551718c75359a5d9f70d48fa82c00e782 100644 (file)
  * 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
     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) {
index 2a35eaefc6b45e3d4e6b35a62c32d5a8407a8aac..0e095689374b7cae7e4ce51546491d1e9ade25d4 100644 (file)
 /* pthread_*() */
 #include <pthread.h>
 
+/* For GnuTLS. */
+#include <gcrypt.h>
+
+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;
 
index 8b6f4fc5d5e71ac4c1d0967dd8a08bdace086c4f..b2d690e92c5a8206bdb9e82fd666833584802761 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Global variables.
+ * Global variables/defines.
  *
  * Copyright (C) 2011  Simon Ruderich
  *
@@ -26,6 +26,9 @@
 #include <stdio.h>
 #include <string.h>
 
+/* GnuTLS */
+#include <gnutls/gnutls.h>
+
 
 /* Log level constants. */
 #define LOG_ERROR   0
 #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