#include "tlsproxy.h"
#include "connection.h"
+#include "verify.h"
/* close() */
#include <unistd.h>
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);
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). */
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
--- /dev/null
+/*
+ * 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;
+}