From: Simon Ruderich Date: Thu, 10 Mar 2011 23:26:08 +0000 (+0100) Subject: Verify the server certificate against a stored copy. X-Git-Tag: 0.1~16 X-Git-Url: https://ruderich.org/simon/gitweb/?p=tlsproxy%2Ftlsproxy.git;a=commitdiff_plain;h=946885b04de70f8481f58160de12f3ee3b0b380a Verify the server certificate against a stored copy. --- diff --git a/src/Makefile.am b/src/Makefile.am index 837f651..ba2cf19 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 diff --git a/src/connection.c b/src/connection.c index 520ce40..1421afa 100644 --- a/src/connection.c +++ b/src/connection.c @@ -19,6 +19,7 @@ #include "tlsproxy.h" #include "connection.h" +#include "verify.h" /* close() */ #include @@ -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 diff --git a/src/log.h b/src/log.h index e129e9f..041f453 100644 --- 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 diff --git a/src/tlsproxy.c b/src/tlsproxy.c index 85ca776..17c49e9 100644 --- a/src/tlsproxy.c +++ b/src/tlsproxy.c @@ -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 * diff --git a/src/tlsproxy.h b/src/tlsproxy.h index 9c90808..c151344 100644 --- a/src/tlsproxy.h +++ b/src/tlsproxy.h @@ -39,6 +39,10 @@ * "./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 index 0000000..0052ca6 --- /dev/null +++ b/src/verify.c @@ -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 . + */ + +#include "tlsproxy.h" +#include "verify.h" + +/* errno */ +#include +/* gnutls_x509_*() */ +#include + + +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 index 0000000..8b78e77 --- /dev/null +++ b/src/verify.h @@ -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 . + */ + +#ifndef VERIFY_H +#define VERIFY_H + +int verify_tls_connection(gnutls_session_t session, const char *hostname); + +#endif