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);
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) {
*/
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)) {
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;
return -1;
}
+ if (http_digest_authorization != NULL && !found_proxy_authorization) {
+ return -3;
+ }
+
return 0;
}
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.";
#include <pthread.h>
#include <signal.h>
#include <sys/socket.h>
+#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
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);
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]);
#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) {
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");
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;
+}
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;
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
-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
const gnutls_datum_t *cert_list;
unsigned int cert_list_size;
- if (argc != 5) {
+ if (argc != 5 && argc != 6) {
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;
}
/* 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) {
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'
--- /dev/null
+#!/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