]> ruderich.org/simon Gitweb - tlsproxy/tlsproxy.git/commitdiff
Add basic digest authentication (-a option).
authorSimon Ruderich <simon@ruderich.org>
Mon, 29 Jul 2013 11:08:47 +0000 (13:08 +0200)
committerSimon Ruderich <simon@ruderich.org>
Mon, 29 Jul 2013 11:08:47 +0000 (13:08 +0200)
src/connection.c
src/tlsproxy.c
src/tlsproxy.h
tests/Makefile.am
tests/client.c
tests/common.sh
tests/tests-authentication.sh [new file with mode: 0755]

index 08a72809a2f30c308f6e9e3fc4b7acd3575e48ad..ed1bc63c6e030c5349f9d2aa46a179c8d60bf2f9 100644 (file)
@@ -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 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);
 
 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;
         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) {
     }
 
     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];
  */
 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)) {
 
     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;
     }
 
         return -2;
     }
 
+    found_proxy_authorization = 0;
     while (fgets(buffer, sizeof(buffer), client_fd) != NULL) {
     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;
         /* 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;
     }
 
         return -1;
     }
 
+    if (http_digest_authorization != NULL && !found_proxy_authorization) {
+        return -3;
+    }
+
     return 0;
 }
 
     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);
 }
     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.";
 static void send_forwarding_failure(FILE *client_fd) {
     const char error[] = "503 Forwarding failure";
     const char msg[]   = "Failed to connect to server, check logs.";
index ba8386b2125c9ddf56efae46848eb772ff484701..be1789a111a8b6356b9200021e211efe443ed7d5 100644 (file)
@@ -28,6 +28,7 @@
 #include <pthread.h>
 #include <signal.h>
 #include <sys/socket.h>
 #include <pthread.h>
 #include <signal.h>
 #include <sys/socket.h>
+#include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
 
 #include <sys/types.h>
 #include <unistd.h>
 
@@ -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 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);
 
 static void initialize_gnutls(void);
 static void deinitialize_gnutls(void);
@@ -93,6 +95,10 @@ int main(int argc, char **argv) {
 
     struct sigaction action;
 
 
     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]);
     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;
 
 #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) {
         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) {
             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);
 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");
                     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");
     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;
 }
 
     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;
+}
index 15b1902d7d35a39f500e4ce4d0fe28b7e31b3169..1c6e551466346808b0f9b48d547c11ae9dd6f432 100644 (file)
@@ -53,6 +53,9 @@
 char *global_proxy_host;
 char *global_proxy_port;
 
 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;
 
 /* 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;
 
 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
 #endif
index 7d526270c987236f5aa3c1a787abb4c6823b4ae0..27ecaff8de411a827b7d1e7a195839746ba3ef9a 100644 (file)
@@ -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
 
 check_PROGRAMS = client
 client_SOURCES = client.c
 
index c11684d2e19bd57d7a60f422601b8aefe7386a8e..866afd13dd2a5f43e623019db6c0b5a613890b88 100644 (file)
@@ -53,9 +53,10 @@ int main (int argc, char *argv[]) {
     const gnutls_datum_t *cert_list;
     unsigned int cert_list_size;
 
     const gnutls_datum_t *cert_list;
     unsigned int cert_list_size;
 
-    if (argc != 5) {
+    if (argc != 5 && argc != 6) {
         fprintf(stderr,
         fprintf(stderr,
-                "Usage: %s <ca-file> <hostname> <port> <hostname-verify>\n",
+                "Usage: %s <ca-file> <hostname> <port> <hostname-verify> "
+                          "[<digest-authentication>]\n",
                 argv[0]);
         return EXIT_FAILURE;
     }
                 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]);
 
     /* 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) {
     fprintf(fd, "\r\n");
     fflush(fd);
     if (read_http_request(fd, buffer, sizeof(buffer)) == -1) {
index ff457457c3dfee76eadead61a7a7d3b536e34a92..c41c32dd0f9b1d2901c1a522d618fb7d7ee9ce30 100644 (file)
@@ -92,6 +92,16 @@ test_proxy_failure() {
     grep 'response: HTTP/1.0 503 Forwarding failure' tmp >/dev/null \
         || abort 'test_proxy_failure 2'
 }
     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'
 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 (executable)
index 0000000..1ad5bc7
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+
+
+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