From 80e82203daef6daf7a16219876fc404e12f82c8a Mon Sep 17 00:00:00 2001 From: Simon Ruderich Date: Tue, 15 Mar 2011 21:52:43 +0100 Subject: [PATCH] Add -u option to pass through unknown hostnames. Also add tests. If -d 2 is used then use SO_REUSEADDR, necessary for the test suite as we have to restart the proxy and otherwise we have to wait for the timeout. --- src/connection.c | 39 +++++++++++-- src/tlsproxy.c | 16 ++++-- src/tlsproxy.h | 4 ++ src/verify.c | 54 ++++++++++-------- src/verify.h | 1 + tests/Makefile.am | 2 +- tests/tests-passthrough.sh | 113 +++++++++++++++++++++++++++++++++++++ tests/tests.sh | 5 ++ 8 files changed, 200 insertions(+), 34 deletions(-) create mode 100755 tests/tests-passthrough.sh diff --git a/src/connection.c b/src/connection.c index 0f491e2..513be7e 100644 --- a/src/connection.c +++ b/src/connection.c @@ -50,10 +50,8 @@ static void send_bad_request(FILE *client_fd); static void send_forwarding_failure(FILE *client_fd); static void tls_send_invalid_cert_message(gnutls_session_t session); -#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); @@ -179,6 +177,35 @@ void handle_connection(int client_socket) { LOG(LOG_DEBUG, "connection to server established"); + /* If the -u option is used and we don't know this hostname's server + * certificate then just pass through the connection and let the client + * verify the server certificate. */ + if (global_passthrough_unknown) { + char path[1024]; + FILE *file = NULL; + + if (-2 == server_certificate_path(&file, host, path, sizeof(path))) { + /* We've established a connection, tell the client. */ + fprintf(client_fd, "HTTP/1.0 200 Connection established\r\n"); + fprintf(client_fd, "\r\n"); + fflush(client_fd); + + LOG(LOG_DEBUG, "transferring data"); + + /* Proxy data between client and server until one suite is done + * (EOF or error). */ + transfer_data(client_socket, server_socket); + + LOG(LOG_DEBUG, "finished transferring data"); + + goto out; + } + /* server_certificate_path() may open the file, close it. */ + if (NULL != file) { + fclose(file); + } + } + result = initialize_tls_session_server(server_socket, &server_session, &server_x509_cred); /* Initialize TLS client credentials to talk to the server. */ @@ -253,14 +280,14 @@ void handle_connection(int client_socket) { goto out; } - LOG(LOG_DEBUG, "transferring data"); + LOG(LOG_DEBUG, "transferring TLS data"); /* 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"); + LOG(LOG_DEBUG, "finished transferring TLS data"); out: /* Close TLS sessions if necessary. Use GNUTLS_SHUT_RDWR so the data is @@ -504,7 +531,6 @@ static void tls_send_invalid_cert_message(gnutls_session_t session) { } -#if 0 /* Transfer data between client and server sockets until one closes the * connection. */ static void transfer_data(int client, int server) { @@ -556,6 +582,8 @@ static int read_from_write_to(int from, int to) { ssize_t size_written; char buffer[4096]; + LOG(LOG_DEBUG, "read_from_write_to(): %d -> %d", from, to); + size_read = read(from, buffer, sizeof(buffer)); if (0 > size_read) { LOG_PERROR(LOG_WARNING, "read_from_write_to(): read()"); @@ -579,7 +607,6 @@ 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. */ diff --git a/src/tlsproxy.c b/src/tlsproxy.c index 7a27f55..25f3704 100644 --- a/src/tlsproxy.c +++ b/src/tlsproxy.c @@ -168,14 +168,12 @@ int main(int argc, char **argv) { return EXIT_FAILURE; } -#ifdef DEBUG /* Fast rebinding for debug mode, could cause invalid packets. */ - { + if (LOG_DEBUG <= global_log_level) { int socket_option = 1; setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &socket_option, sizeof(socket_option)); } -#endif /* Bind to the listen socket. */ memset(&server_in, 0, sizeof(server_in)); @@ -272,8 +270,9 @@ static void parse_arguments(int argc, char **argv) { #else global_log_level = LOG_WARNING; #endif + global_passthrough_unknown = 0; - while (-1 != (option = getopt(argc, argv, "d:p:t:h?"))) { + while (-1 != (option = getopt(argc, argv, "d:p:t:uh?"))) { switch (option) { case 'd': { if (0 > atoi(optarg)) { @@ -324,6 +323,10 @@ static void parse_arguments(int argc, char **argv) { thread_count = (size_t)atoi(optarg); break; } + case 'u': { + global_passthrough_unknown = 1; + break; + } case 'h': default: /* '?' */ print_usage(argv[0]); @@ -338,12 +341,15 @@ static void parse_arguments(int argc, char **argv) { } } static void print_usage(const char *argv) { - fprintf(stderr, "Usage: %s [-d level] [-p host:port] [-t count] port\n", + fprintf(stderr, "Usage: %s [-d level] [-p host:port] [-t count] [-u] port\n", argv); fprintf(stderr, "\n"); fprintf(stderr, "-d debug level: 0=errors only, 2=debug [default: 1]\n"); fprintf(stderr, "-p proxy hostname and port\n"); fprintf(stderr, "-t number of threads [default: 10]\n"); + fprintf(stderr, "-u passthrough connection if no certificate is stored \ +[default: error]\n"); + fprintf(stderr, " WARNING: might be a security problem!\n"); } static void initialize_gnutls(void) { diff --git a/src/tlsproxy.h b/src/tlsproxy.h index bcb9c2d..ae6a818 100644 --- a/src/tlsproxy.h +++ b/src/tlsproxy.h @@ -55,6 +55,10 @@ char *global_proxy_port; /* Log level, command line option. */ int global_log_level; +/* Passthrough connections if no certificate is stored for this hostname? + * Specified on the command line. */ +int global_passthrough_unknown; + /* "Global" GnuTLS data used by all threads, read only. */ gnutls_priority_t tls_priority_cache; gnutls_dh_params_t tls_dh_params; diff --git a/src/verify.c b/src/verify.c index 0052ca6..6558d42 100644 --- a/src/verify.c +++ b/src/verify.c @@ -109,28 +109,8 @@ int verify_tls_connection(gnutls_session_t session, const char *hostname) { /* We got the certificate as PEM, free it. */ gnutls_x509_crt_deinit(cert); - /* Hostname too long. */ - if (sizeof(path) - strlen(STORED_SERVER_CERT_FORMAT) <= strlen(hostname)) { - LOG(LOG_WARNING, - "verify_tls_connection(): hostname too long: '%s'", - hostname); - return -1; - } - /* Try to prevent path traversals in hostnames. */ - if (NULL != strstr(hostname, "..")) { - LOG(LOG_WARNING, - "verify_tls_connection(): possible path traversal: '%s'", - hostname); - return -1; - } - snprintf(path, sizeof(path), STORED_SERVER_CERT_FORMAT, hostname); - - /* Open the stored certificate file. */ - file = fopen(path, "rb"); - if (NULL == file) { - LOG(LOG_WARNING, - "verify_tls_connection(): failed to open '%s': %s", - path, strerror(errno)); + /* Open stored server certificate file. */ + if (0 != server_certificate_path(&file, hostname, path, sizeof(path))) { return -1; } @@ -168,3 +148,33 @@ int verify_tls_connection(gnutls_session_t session, const char *hostname) { return 0; } + +int server_certificate_path(FILE **file, const char *hostname, char *path, size_t size) { + /* Hostname too long. */ + if (size - strlen(STORED_SERVER_CERT_FORMAT) <= strlen(hostname)) { + LOG(LOG_WARNING, + "server_certificate_path(): hostname too long: '%s'", + hostname); + return -1; + } + /* Try to prevent path traversals in hostnames. */ + if (NULL != strstr(hostname, "..")) { + LOG(LOG_WARNING, + "server_certificate_path(): possible path traversal: '%s'", + hostname); + return -1; + } + snprintf(path, size, STORED_SERVER_CERT_FORMAT, hostname); + + /* Open the stored certificate file. */ + *file = fopen(path, "rb"); + if (NULL == *file) { + LOG(global_passthrough_unknown ? LOG_DEBUG : LOG_WARNING, + "server_certificate_path(): failed to open '%s': %s", + path, strerror(errno)); + /* Couldn't open the file, special case. */ + return -2; + } + + return 0; +} diff --git a/src/verify.h b/src/verify.h index 8b78e77..e39cc59 100644 --- a/src/verify.h +++ b/src/verify.h @@ -20,6 +20,7 @@ #ifndef VERIFY_H #define VERIFY_H +int server_certificate_path(FILE **file, const char *hostname, char *path, size_t size); int verify_tls_connection(gnutls_session_t session, const char *hostname); #endif diff --git a/tests/Makefile.am b/tests/Makefile.am index f41c6b0..d9055ae 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,5 +1,5 @@ TESTS = tests.sh -dist_check_SCRIPTS = tests.sh tests-normal.sh common.sh +dist_check_SCRIPTS = tests.sh tests-normal.sh tests-passthrough.sh common.sh check_PROGRAMS = client client_SOURCES = client.c diff --git a/tests/tests-passthrough.sh b/tests/tests-passthrough.sh new file mode 100755 index 0000000..eb40a02 --- /dev/null +++ b/tests/tests-passthrough.sh @@ -0,0 +1,113 @@ +#!/bin/sh + +# tlsproxy tests for the -u option. +# +# Copyright (C) 2011 Simon Ruderich +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +# Handle empty $srcdir. +[ "x$srcdir" = x ] && srcdir=. + +. $srcdir/common.sh + + +# Create necessary files. +cleanup +$srcdir/../src/tlsproxy-setup >/dev/null 2>/dev/null + +# Normal tests. +../src/tlsproxy -d2 -u 4711 >/dev/null & +server --x509certfile $srcdir/server.pem \ + --x509keyfile $srcdir/server-key.pem +sleep 1 + + +echo invalid hostname +client unknown-host 80 - && abort +test_proxy_failure +test_no_invalid_certificate + +echo missing proxy and server certificate +client localhost 4712 'test server' || abort +test_proxy_successful +test_invalid_certificate + +# Create the proxy certificate. +$srcdir/../src/tlsproxy-add localhost $srcdir/server.pem \ + >/dev/null 2>/dev/null +rm -f certificate-localhost-server.pem + +echo missing server certificate +client localhost 4712 'test server' || abort +test_proxy_successful +test_invalid_certificate + +# Create the proxy and server certificate. +$srcdir/../src/tlsproxy-add localhost $srcdir/server.pem \ + >/dev/null 2>/dev/null + +echo normal connection +client localhost 4712 localhost || abort +test_proxy_successful +test_no_invalid_certificate + + +# Stop server and try a "MITM" with a bad certificate. +echo +pkill -n gnutls-serv +server --x509certfile $srcdir/server-bad.pem \ + --x509keyfile $srcdir/server-key.pem +sleep 1 +rm -f certificate-localhost-proxy.pem certificate-localhost-server.pem + + +echo mitm invalid hostname +client unknown-host 80 - && abort +test_proxy_failure +test_no_invalid_certificate + +echo mitm missing proxy and server certificate +client localhost 4712 'test server bad' || abort +test_proxy_successful +test_invalid_certificate + +# Create the proxy certificate. +$srcdir/../src/tlsproxy-add localhost $srcdir/server.pem \ + >/dev/null 2>/dev/null +rm -f certificate-localhost-server.pem + +echo mitm missing server certificate +client localhost 4712 'test server bad' || abort +test_proxy_successful +test_invalid_certificate + +# Create the proxy and server certificate. +$srcdir/../src/tlsproxy-add localhost $srcdir/server.pem \ + >/dev/null 2>/dev/null + +echo mitm normal connection +client localhost 4712 invalid || abort +test_proxy_successful +test_invalid_certificate + + +pkill -n gnutls-serv +pkill -n tlsproxy + +cleanup +rm -f tmp + +exit 0 diff --git a/tests/tests.sh b/tests/tests.sh index 0dc7912..a7b940a 100755 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -23,3 +23,8 @@ echo "RUNNING NORMAL TESTS" $srcdir/tests-normal.sh + +echo + +echo "RUNNING PASSTHROUGH (-u) TESTS" +$srcdir/tests-passthrough.sh -- 2.45.2