]> ruderich.org/simon Gitweb - tlsproxy/tlsproxy.git/blob - src/verify.c
14c7127b7c39cd01ad0b9aa0082cb1396b35f6ca
[tlsproxy/tlsproxy.git] / src / verify.c
1 /*
2  * Verify established TLS connections.
3  *
4  * Copyright (C) 2011  Simon Ruderich
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include "tlsproxy.h"
21 #include "verify.h"
22
23 /* errno */
24 #include <errno.h>
25 /* gnutls_x509_*() */
26 #include <gnutls/x509.h>
27
28
29 int verify_tls_connection(gnutls_session_t session, const char *hostname) {
30     int result;
31     char path[1024];
32
33     size_t size;
34     unsigned int status;
35     gnutls_x509_crt_t cert;
36     const gnutls_datum_t *cert_list;
37     unsigned int cert_list_size;
38     FILE *file;
39     char buffer[66]; /* one line in a PEM file is 64 bytes + '\n' + '\0' */
40     char server_cert[8192];
41     char stored_cert[8192];
42
43     result = gnutls_certificate_verify_peers2(session, &status);
44     /* Verification failed (!= invalid certificate but worse), no need for any
45      * more checks. */
46     if (0 > result) {
47         LOG(LOG_WARNING,
48             "verify_tls_connection(): gnutls_certificate_verify_peers2() failed: %s",
49             gnutls_strerror(result));
50         return -1;
51     }
52     /* Definitely an invalid certificate, abort. */
53     if (status & GNUTLS_CERT_EXPIRED
54             || status & GNUTLS_CERT_REVOKED
55             || status & GNUTLS_CERT_NOT_ACTIVATED
56             || status & GNUTLS_CERT_INSECURE_ALGORITHM) {
57         LOG(LOG_WARNING,
58             "verify_tls_connection(): invalid server certificate");
59         return -1;
60     }
61
62     /* We only handle X509 certificates for now. Let validation fail to
63      * prevent an attacker from changing the certificate type to prevent
64      * detection. */
65     if (GNUTLS_CRT_X509 != gnutls_certificate_type_get(session)) {
66         LOG(LOG_WARNING,
67             "verify_tls_connection(): no X509 server certificate");
68         return -1;
69     }
70
71     /* Get server certificate. */
72
73     if (0 > (result = gnutls_x509_crt_init(&cert))) {
74         LOG(LOG_WARNING,
75             "verify_tls_connection(): gnutls_x509_crt_init() failed: %s",
76             gnutls_strerror(result));
77         return -1;
78     }
79
80     cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
81     if (NULL == cert_list) {
82         LOG(LOG_WARNING,
83             "verify_tls_connection(): gnutls_certificate_get_peers() failed");
84         gnutls_x509_crt_deinit(cert);
85         return -1;
86     }
87
88     if (0 > (result = gnutls_x509_crt_import(cert, &cert_list[0],
89                                              GNUTLS_X509_FMT_DER))) {
90         LOG(LOG_WARNING,
91             "verify_tls_connection(): gnutls_x509_crt_import() failed: %s",
92             gnutls_strerror(result));
93         gnutls_x509_crt_deinit(cert);
94         return -1;
95     }
96
97     /* Export the certificate as PEM to compare it with the known certificate
98      * stored on disk. */
99     size = sizeof(server_cert);
100     result = gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_PEM,
101                                     server_cert, &size);
102     if (GNUTLS_E_SUCCESS != result) {
103         LOG(LOG_WARNING,
104             "verify_tls_connection(): gnutls_x509_crt_export() failed: %s",
105             gnutls_strerror(result));
106         gnutls_x509_crt_deinit(cert);
107         return -1;
108     }
109     /* We got the certificate as PEM, free the crt struct. */
110     gnutls_x509_crt_deinit(cert);
111
112     /* Open stored server certificate file. */
113     if (0 != server_certificate_path(&file, hostname, path, sizeof(path))) {
114         return -1;
115     }
116
117     size = 1; /* '\0' */
118     stored_cert[0] = '\0'; /* for strcat() */
119
120     while (NULL != fgets(buffer, sizeof(buffer), file)) {
121         size += strlen(buffer);
122         /* Make sure the buffer is big enough. */
123         if (sizeof(stored_cert) <= size) {
124             LOG(LOG_WARNING, "verify_tls_connection(): '%s' too big", path);
125             fclose(file);
126             return -1;
127         }
128
129         strcat(stored_cert, buffer);
130     }
131     if (ferror(file)) {
132         fclose(file);
133         LOG(LOG_WARNING,
134             "verify_tls_connection(): failed to read from '%s': %s",
135             path, strerror(errno));
136         return -1;
137     }
138     fclose(file);
139
140     /* Check if the server certificate matches our stored certificate. */
141     if (0 != strcmp(stored_cert, server_cert)) {
142         LOG(LOG_ERROR,
143             "verify_tls_connection(): server certificate changed!",
144             path, strerror(errno));
145         return -2;
146     }
147
148     return 0;
149 }
150
151 int server_certificate_path(FILE **file, const char *hostname,
152                             char *path, size_t size) {
153     /* Hostname too long. */
154     if (size - strlen(STORED_SERVER_CERT_FORMAT) <= strlen(hostname)) {
155         LOG(LOG_WARNING,
156             "server_certificate_path(): hostname too long: '%s'",
157             hostname);
158         return -1;
159     }
160     /* Try to prevent path traversals in hostnames. */
161     if (NULL != strstr(hostname, "..")) {
162         LOG(LOG_WARNING,
163             "server_certificate_path(): possible path traversal: '%s'",
164             hostname);
165         return -1;
166     }
167     snprintf(path, size, STORED_SERVER_CERT_FORMAT, hostname);
168
169     /* Open the stored certificate file. */
170     *file = fopen(path, "rb");
171     if (NULL == *file) {
172         LOG(global_passthrough_unknown ? LOG_DEBUG : LOG_WARNING,
173             "server_certificate_path(): failed to open '%s': %s",
174             path, strerror(errno));
175         /* Couldn't open the file, special case. */
176         return -2;
177     }
178
179     return 0;
180 }