/* * 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); /* Open stored server certificate file. */ if (0 != server_certificate_path(&file, hostname, path, sizeof(path))) { 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; } int server_certificate_path(FILE **file, const char *hostname, char *path, size_t size) { /* Hostname too long. */ if (size - strlen(STORED_SERVER_CERT_FORMAT) <= strlen(hostname)) { LOG(LOG_WARNING, "server_certificate_path(): hostname too long: '%s'", hostname); return -1; } /* Try to prevent path traversals in hostnames. */ if (NULL != strstr(hostname, "..")) { LOG(LOG_WARNING, "server_certificate_path(): possible path traversal: '%s'", hostname); return -1; } snprintf(path, size, STORED_SERVER_CERT_FORMAT, hostname); /* Open the stored certificate file. */ *file = fopen(path, "rb"); if (NULL == *file) { LOG(global_passthrough_unknown ? LOG_DEBUG : LOG_WARNING, "server_certificate_path(): failed to open '%s': %s", path, strerror(errno)); /* Couldn't open the file, special case. */ return -2; } return 0; }