From a84000d1d2806c296bfc0fa4b505b5d5ef750715 Mon Sep 17 00:00:00 2001 From: Simon Ruderich Date: Mon, 29 Jul 2013 13:08:47 +0200 Subject: [PATCH] Add basic digest authentication (-a option). --- src/connection.c | 32 ++++++++++++++ src/tlsproxy.c | 71 ++++++++++++++++++++++++++++++- src/tlsproxy.h | 9 ++++ tests/Makefile.am | 4 +- tests/client.c | 8 +++- tests/common.sh | 10 +++++ tests/tests-authentication.sh | 80 +++++++++++++++++++++++++++++++++++ 7 files changed, 208 insertions(+), 6 deletions(-) create mode 100755 tests/tests-authentication.sh diff --git a/src/connection.c b/src/connection.c index 08a7280..ed1bc63 100644 --- a/src/connection.c +++ b/src/connection.c @@ -62,6 +62,7 @@ static int initialize_tls_session_server(int peer_socket, static int fdopen_read_write(int socket, FILE **read_fd, FILE **write_fd); static int read_http_request(FILE *client_fd, char *request, size_t length); static void send_bad_request(FILE *client_fd); +static void send_authentication_required(FILE *client_fd); static void send_forwarding_failure(FILE *client_fd); static void tls_send_invalid_cert_message(gnutls_session_t session); @@ -131,6 +132,10 @@ void handle_connection(int client_socket) { LOG(WARNING, "read_http_request(): client EOF"); send_bad_request(client_fd_write); goto out; + } else if (result == -3) { + LOG(DEBUG, "read_http_request(): proxy authentication failed"); + send_authentication_required(client_fd_write); + goto out; } if (parse_request(buffer, host, port, &version_minor) != 0) { @@ -521,6 +526,7 @@ static int fdopen_read_write(int socket, FILE **read_fd, FILE **write_fd) { */ static int read_http_request(FILE *client_fd, char *request, size_t length) { char buffer[MAX_REQUEST_LINE]; + int found_proxy_authorization; if (fgets(request, (int)length, client_fd) == NULL) { if (ferror(client_fd)) { @@ -531,7 +537,22 @@ static int read_http_request(FILE *client_fd, char *request, size_t length) { return -2; } + found_proxy_authorization = 0; while (fgets(buffer, sizeof(buffer), client_fd) != NULL) { + const char *authentication = "Proxy-Authorization: Basic "; + + if (http_digest_authorization != NULL + && !strncmp(buffer, authentication, strlen(authentication))) { + found_proxy_authorization = 1; + + /* Check if the passphrase matches. */ + strtok(buffer, "\r\n"); + if (strcmp(buffer + strlen(authentication), + http_digest_authorization)) { + return -3; + } + } + /* End of header. */ if (!strcmp(buffer, "\n") || !strcmp(buffer, "\r\n")) { break; @@ -542,6 +563,10 @@ static int read_http_request(FILE *client_fd, char *request, size_t length) { return -1; } + if (http_digest_authorization != NULL && !found_proxy_authorization) { + return -3; + } + return 0; } @@ -551,6 +576,13 @@ static void send_bad_request(FILE *client_fd) { fprintf(client_fd, HTTP_RESPONSE_FORMAT, error, "", error, error, msg); fflush(client_fd); } +static void send_authentication_required(FILE *client_fd) { + const char error[] = "407 Proxy Authentication Required"; + const char auth[] = "Proxy-Authenticate: Basic realm=\"tlsproxy\"\r\n"; + const char msg[] = "TODO"; + fprintf(client_fd, HTTP_RESPONSE_FORMAT, error, auth, error, error, msg); + fflush(client_fd); +} static void send_forwarding_failure(FILE *client_fd) { const char error[] = "503 Forwarding failure"; const char msg[] = "Failed to connect to server, check logs."; diff --git a/src/tlsproxy.c b/src/tlsproxy.c index ba8386b..be1789a 100644 --- a/src/tlsproxy.c +++ b/src/tlsproxy.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -72,6 +73,7 @@ static void sigint_handler(int signal); static void parse_arguments(int argc, char **argv); static void print_usage(const char *argv); +static char *slurp_file(const char *path); static void initialize_gnutls(void); static void deinitialize_gnutls(void); @@ -93,6 +95,10 @@ int main(int argc, char **argv) { struct sigaction action; + /* Required in a few places. */ + ct_assert(sizeof(size_t) >= sizeof(int)); + ct_assert(sizeof(size_t) >= sizeof(ssize_t)); + parse_arguments(argc, argv); port = atoi(argv[argc - 1]); @@ -256,8 +262,26 @@ static void parse_arguments(int argc, char **argv) { #endif global_passthrough_unknown = 0; - while ((option = getopt(argc, argv, "d:p:t:uh?")) != -1) { + while ((option = getopt(argc, argv, "a:d:p:t:uh?")) != -1) { switch (option) { + case 'a': { + http_digest_authorization = slurp_file(optarg); + if (http_digest_authorization == NULL) { + fprintf(stderr, "failed to open authorization file '%s': ", + optarg); + perror(""); + exit(EXIT_FAILURE); + } else if (strlen(http_digest_authorization) == 0) { + fprintf(stderr, "empty authorization file '%s'\n", + optarg); + exit(EXIT_FAILURE); + } + + /* Just in case the file has a trailing newline. */ + strtok(http_digest_authorization, "\r\n"); + + break; + } case 'd': { global_log_level = atoi(optarg); if (global_log_level < 0) { @@ -326,9 +350,10 @@ static void parse_arguments(int argc, char **argv) { static void print_usage(const char *argv) { fprintf(stderr, "tlsproxy %s, a certificate checking TLS proxy\n", VERSION); - fprintf(stderr, "Usage: %s [-d level] [-p host:port] [-t count] [-u] port\n", + fprintf(stderr, "Usage: %s [-a file] [-d level] [-p host:port] [-t count] [-u] port\n", argv); fprintf(stderr, "\n"); + fprintf(stderr, "-a digest authentication file [default: none]\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"); @@ -401,3 +426,45 @@ static void *worker_thread(void *unused) { return NULL; } + +static char *slurp_file(const char *path) { + struct stat stat; + size_t size_read; + char *content = NULL; + + FILE *file = fopen(path, "r"); + if (file == NULL) { + return NULL; + } + + ct_assert(sizeof(stat.st_size) <= sizeof(size_t)); + + if (fstat(fileno(file), &stat) != 0) { + goto out; + } + if (stat.st_size < 0) { /* just in case ... */ + abort(); + } else if ((size_t)stat.st_size >= SIZE_MAX - 1) { + errno = 0; + goto out; + } + + content = malloc((size_t)stat.st_size + 1); + if (content == NULL) { + goto out; + } + + errno = 0; + size_read = fread(content, 1, (size_t)stat.st_size, file); + if (size_read != (size_t)stat.st_size) { + free(content); + content = NULL; + goto out; + } + content[size_read] = '\0'; + +out: + fclose(file); + + return content; +} diff --git a/src/tlsproxy.h b/src/tlsproxy.h index 15b1902..1c6e551 100644 --- a/src/tlsproxy.h +++ b/src/tlsproxy.h @@ -53,6 +53,9 @@ char *global_proxy_host; char *global_proxy_port; +/* Passphrase for authentication of this proxy. Used with the -a option. */ +char *http_digest_authorization; + /* Log level, command line option. */ int global_log_level; @@ -64,4 +67,10 @@ int global_passthrough_unknown; gnutls_priority_t global_tls_priority_cache; gnutls_dh_params_t global_tls_dh_params; +/* Very simple compile time asserts. No good error message though. */ +#define ct_assert(x) { \ + int unused[(x) ? 1 : -1]; \ + (void)unused; \ +} + #endif diff --git a/tests/Makefile.am b/tests/Makefile.am index 7d52627..27ecaff 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,5 +1,5 @@ -TESTS = tests-normal.sh tests-passthrough.sh -dist_check_SCRIPTS = tests-normal.sh tests-passthrough.sh common.sh +TESTS = tests-normal.sh tests-passthrough.sh tests-authentication.sh +dist_check_SCRIPTS = $(TESTS) common.sh check_PROGRAMS = client client_SOURCES = client.c diff --git a/tests/client.c b/tests/client.c index c11684d..866afd1 100644 --- a/tests/client.c +++ b/tests/client.c @@ -53,9 +53,10 @@ int main (int argc, char *argv[]) { const gnutls_datum_t *cert_list; unsigned int cert_list_size; - if (argc != 5) { + if (argc != 5 && argc != 6) { fprintf(stderr, - "Usage: %s \n", + "Usage: %s " + "[]\n", argv[0]); return EXIT_FAILURE; } @@ -82,6 +83,9 @@ int main (int argc, char *argv[]) { /* Talk to tlsproxy. */ fprintf(fd, "CONNECT %s:%s HTTP/1.0\r\n", argv[2], argv[3]); + if (argc == 6) { + fprintf(fd, "Proxy-Authorization: Basic %s\r\n", argv[5]); + } fprintf(fd, "\r\n"); fflush(fd); if (read_http_request(fd, buffer, sizeof(buffer)) == -1) { diff --git a/tests/common.sh b/tests/common.sh index ff45745..c41c32d 100644 --- a/tests/common.sh +++ b/tests/common.sh @@ -92,6 +92,16 @@ test_proxy_failure() { grep 'response: HTTP/1.0 503 Forwarding failure' tmp >/dev/null \ || abort 'test_proxy_failure 2' } +test_proxy_authentication_failure() { + grep 'proxy failure' tmp >/dev/null \ + || abort 'test_proxy_authentication_failure' + grep 'response: HTTP/1.0 407 Proxy Authentication Required' tmp >/dev/null \ + || abort 'test_proxy_authentication_failure 2' +} +test_authentication_missing() { + grep 'response: HTTP/1.0 407 Proxy Authentication Required' tmp >/dev/null \ + || abort 'test_authentication_missing' +} test_proxy_successful() { grep 'response: HTTP/1.0 200 Connection established' tmp >/dev/null \ || abort 'test_proxy_successful' diff --git a/tests/tests-authentication.sh b/tests/tests-authentication.sh new file mode 100755 index 0000000..1ad5bc7 --- /dev/null +++ b/tests/tests-authentication.sh @@ -0,0 +1,80 @@ +#!/bin/sh + +# Test authentication. + +# Copyright (C) 2013 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 . + + +test "x$srcdir" = x && srcdir=. +. "$srcdir/common.sh" + + +# Create necessary files. +cleanup +tlsproxy_setup + +echo 'dXNlcm5hbWU6cGFzc3dvcmQ=' > digest-authentication +tlsproxy -a digest-authentication 4711 +server --x509certfile "$srcdir/server.pem" \ + --x509keyfile "$srcdir/server-key.pem" +wait_for_ports 4711 4712 +rm -f digest-authentication + +echo missing authentication 1 +client localhost 4712 invalid \ + && abort 'client localhost 4712 invalid' +test_proxy_authentication_failure + +# Create the proxy certificate. +tlsproxy_add localhost server.pem + +echo missing authentication 2 +client localhost 4712 localhost \ + && abort 'client localhost 4712 localhost' +test_proxy_authentication_failure + +echo invalid authentication 1 +client localhost 4712 localhost 'username:password' \ + && abort 'client localhost 4712 localhost username:password' +test_proxy_authentication_failure + +echo invalid authentication 2 +client localhost 4712 localhost 'dXNlcm5hbWU6cGFzc3dvcmQ' \ + && abort 'client localhost 4712 localhost dXNlcm5hbWU6cGFzc3dvcmQ' +test_proxy_authentication_failure + +echo invalid authentication 3 +client localhost 4712 localhost 'dXNlcm5hbWU6cGFzc3dvcmQ=X' \ + && abort 'client localhost 4712 localhost dXNlcm5hbWU6cGFzc3dvcmQ=X' +test_proxy_authentication_failure + +echo valid authentication +client localhost 4712 localhost 'dXNlcm5hbWU6cGFzc3dvcmQ=' \ + || abort 'client localhost 4712 localhost dXNlcm5hbWU6cGFzc3dvcmQ=' +test_proxy_successful + +echo valid authentication with invalid certificate +tlsproxy_add localhost server-bad.pem +client localhost 4712 localhost 'dXNlcm5hbWU6cGFzc3dvcmQ=' \ + && abort 'client localhost 4712 invalid dXNlcm5hbWU6cGFzc3dvcmQ=' +test_proxy_successful +test_invalid_certificate + + +# stop_servers in trap-handler +cleanup + +exit 0 -- 2.45.2