]> ruderich.org/simon Gitweb - tlsproxy/tlsproxy.git/commitdiff
Verify the server certificate against a stored copy.
authorSimon Ruderich <simon@ruderich.org>
Thu, 10 Mar 2011 23:26:08 +0000 (00:26 +0100)
committerSimon Ruderich <simon@ruderich.org>
Thu, 10 Mar 2011 23:26:08 +0000 (00:26 +0100)
src/Makefile.am
src/connection.c
src/log.h
src/tlsproxy.c
src/tlsproxy.h
src/verify.c [new file with mode: 0644]
src/verify.h [new file with mode: 0644]

index 837f651baa5bf7a31b1459df4b10d780fc782419..ba2cf19bb8c48a166228dae83b5c8b8d2e3b897f 100644 (file)
@@ -3,4 +3,5 @@ tlsproxy_SOURCES = \
        tlsproxy.c tlsproxy.h \
        sem.h sem.c \
        connection.h connection.c \
-       log.h log.c
+       log.h log.c \
+       verify.h verify.c
index 520ce40e031a0052581a7c2dc27e93c649fbf74f..1421afa0c384073e51776b5bda129f031d1fcf29 100644 (file)
@@ -19,6 +19,7 @@
 
 #include "tlsproxy.h"
 #include "connection.h"
+#include "verify.h"
 
 /* close() */
 #include <unistd.h>
@@ -47,6 +48,7 @@ static int initialize_tls_session_server(int peer_socket,
 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);
+static void tls_send_server_error(gnutls_session_t session);
 
 #if 0
 static void transfer_data(int client, int server);
@@ -227,7 +229,12 @@ void handle_connection(int client_socket) {
 
     LOG(LOG_DEBUG, "server TLS handshake finished, transferring data");
 
-    /* FIXME: verify server's fingerprint */
+    /* Make sure the server certificate is valid and known. */
+    if (0 != verify_tls_connection(server_session, host)) {
+        LOG(LOG_ERROR, "server certificate validation failed!");
+        tls_send_server_error(client_session);
+        goto out;
+    }
 
     /* Proxy data between client and server until one suite is done (EOF or
      * error). */
@@ -440,6 +447,10 @@ static void send_forwarding_failure(FILE *client_fd) {
     fprintf(client_fd, "HTTP/1.0 503 Forwarding failure\r\n");
     fprintf(client_fd, "\r\n");
 }
+static void tls_send_server_error(gnutls_session_t session) {
+    gnutls_record_send(session, "HTTP/1.0 500 Internal Server Error\r\n", 36);
+    gnutls_record_send(session, "\r\n", 2);
+}
 
 
 #if 0
index e129e9f29fc8a424e946870bcc00967c839371d8..041f45318076b1816a04e2f2352980eb6868956d 100644 (file)
--- a/src/log.h
+++ b/src/log.h
@@ -28,7 +28,7 @@
 /* Helper macro for LOG/LOG_PERROR. Print file/line number if compiled with
  * debug output. */
 #ifdef DEBUG
-#define LOG_PRINT_LOCATION fprintf(stdout, "%s:%-3d ", __FILE__, __LINE__);
+#define LOG_PRINT_LOCATION fprintf(stdout, "%-12s:%-3d ", __FILE__, __LINE__);
 #else
 #define LOG_PRINT_LOCATION
 #endif
index 85ca776af98516a91f3d98e789ba2fc266e190bf..17c49e971b6a27d1e7c096c2a85977256265d1de 100644 (file)
@@ -1,5 +1,7 @@
 /*
- * tlsproxy is a transparent TLS proxy for HTTPS connections.
+ * tlsproxy is a TLS proxy for HTTPS which intercepts the connections and
+ * ensures the server certificate doesn't change. Normally this isn't detected
+ * if a trusted CA for the new server certificate is installed.
  *
  * Copyright (C) 2011  Simon Ruderich
  *
index 9c908084945857c49275b4fa90ae0135d87e8530..c151344db84753e3b4a02119cfc2e003465973ac 100644 (file)
  * "./certificate-hostname-proxy.pem" - we use this for the connection to the
  * client. */
 #define PROXY_SERVER_CERT_FORMAT "./certificate-%s-proxy.pem"
+/* The remote server certificate for the given hostname is stored in
+ * "./certificate-hostname-proxy.pem" - we make sure the server sends this
+ * certificate. */
+#define STORED_SERVER_CERT_FORMAT "./certificate-%s-server.pem"
 
 
 /* Proxy hostname and port if specified on the command line. */
diff --git a/src/verify.c b/src/verify.c
new file mode 100644 (file)
index 0000000..0052ca6
--- /dev/null
@@ -0,0 +1,170 @@
+/*
+ * Verify established TLS connections.
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "tlsproxy.h"
+#include "verify.h"
+
+/* errno */
+#include <errno.h>
+/* gnutls_x509_*() */
+#include <gnutls/x509.h>
+
+
+int verify_tls_connection(gnutls_session_t session, const char *hostname) {
+    int result;
+    char path[1024];
+
+    size_t size;
+    unsigned int status;
+    gnutls_x509_crt_t cert;
+    const gnutls_datum_t *cert_list;
+    unsigned int cert_list_size;
+    FILE *file;
+    char buffer[66]; /* one line in a PEM file is 64 bytes + '\n' + '\0' */
+    char server_cert[8192];
+    char stored_cert[8192];
+
+    result = gnutls_certificate_verify_peers2(session, &status);
+    /* Verification failed (!= invalid certificate but worse), no need for any
+     * more checks. */
+    if (0 > result) {
+        LOG(LOG_WARNING,
+            "verify_tls_connection(): gnutls_certificate_verify_peers2() failed: %s",
+            gnutls_strerror(result));
+        return -1;
+    }
+    /* Definitely an invalid certificate, abort. */
+    if (status & GNUTLS_CERT_EXPIRED
+            || status & GNUTLS_CERT_REVOKED
+            || status & GNUTLS_CERT_NOT_ACTIVATED
+            || status & GNUTLS_CERT_INSECURE_ALGORITHM) {
+        LOG(LOG_WARNING,
+            "verify_tls_connection(): invalid server certificate");
+        return -1;
+    }
+
+    /* We only handle X509 certificates for now. Let validation fail to
+     * prevent an attacker from changing the certificate type to prevent
+     * detection. */
+    if (GNUTLS_CRT_X509 != gnutls_certificate_type_get(session)) {
+        LOG(LOG_WARNING,
+            "verify_tls_connection(): no X509 server certificate");
+        return -1;
+    }
+
+    /* Get server certificate. */
+
+    if (0 > (result = gnutls_x509_crt_init(&cert))) {
+        LOG(LOG_WARNING,
+            "verify_tls_connection(): gnutls_x509_crt_init() failed: %s",
+            gnutls_strerror(result));
+        return -1;
+    }
+
+    cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
+    if (NULL == cert_list) {
+        LOG(LOG_WARNING,
+            "verify_tls_connection(): gnutls_certificate_get_peers() failed");
+        gnutls_x509_crt_deinit(cert);
+        return -1;
+    }
+
+    if (0 > (result = gnutls_x509_crt_import(cert, &cert_list[0],
+                                             GNUTLS_X509_FMT_DER))) {
+        LOG(LOG_WARNING,
+            "verify_tls_connection(): gnutls_x509_crt_import() failed: %s",
+            gnutls_strerror(result));
+        gnutls_x509_crt_deinit(cert);
+        return -1;
+    }
+
+    /* Export the certificate as PEM to compare it with the known certificate
+     * stored on disk. */
+    size = sizeof(server_cert);
+    result = gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_PEM,
+                                    server_cert, &size);
+    if (GNUTLS_E_SUCCESS != result) {
+        LOG(LOG_WARNING,
+            "verify_tls_connection(): gnutls_x509_crt_export() failed: %s",
+            gnutls_strerror(result));
+        gnutls_x509_crt_deinit(cert);
+        return -1;
+    }
+    /* 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));
+        return -1;
+    }
+
+    size = 1; /* '\0' */
+    stored_cert[0] = '\0'; /* for strcat() */
+
+    while (NULL != fgets(buffer, sizeof(buffer), file)) {
+        size += strlen(buffer);
+        /* Make sure the buffer is big enough. */
+        if (sizeof(stored_cert) <= size) {
+            LOG(LOG_WARNING, "verify_tls_connection(): '%s' too big",
+                path);
+            fclose(file);
+            return -1;
+        }
+
+        strcat(stored_cert, buffer);
+    }
+    if (ferror(file)) {
+        fclose(file);
+        LOG(LOG_WARNING,
+            "verify_tls_connection(): failed to read from '%s': %s",
+            path, strerror(errno));
+        return -1;
+    }
+    fclose(file);
+
+    /* Check if the server certificate matches our stored certificate. */
+    if (0 != strcmp(stored_cert, server_cert)) {
+        LOG(LOG_ERROR,
+            "verify_tls_connection(): server certificate changed!",
+            path, strerror(errno));
+        return -2;
+    }
+
+    return 0;
+}
diff --git a/src/verify.h b/src/verify.h
new file mode 100644 (file)
index 0000000..8b78e77
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * Verify established TLS connections.
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef VERIFY_H
+#define VERIFY_H
+
+int verify_tls_connection(gnutls_session_t session, const char *hostname);
+
+#endif