]> ruderich.org/simon Gitweb - tlsproxy/tlsproxy.git/blob - src/verify.c
NEWS: Update.
[tlsproxy/tlsproxy.git] / src / verify.c
1 /*
2  * Verify established TLS connections.
3  *
4  * Copyright (C) 2011-2013  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 #include <assert.h>
24 #include <errno.h>
25
26 #include <gnutls/x509.h>
27
28
29 /* Compatibility for older GnuTLS versions. Define the constants in a way
30  * which doesn't affect the status check below. */
31 #ifndef GNUTLS_CERT_SIGNER_NOT_CA
32 # define GNUTLS_CERT_SIGNER_NOT_CA 0
33 #endif
34 #ifndef GNUTLS_CERT_SIGNATURE_FAILURE
35 # define GNUTLS_CERT_SIGNATURE_FAILURE 0
36 #endif
37 #ifndef GNUTLS_CERT_REVOCATION_DATA_SUPERSEDED
38 # define GNUTLS_CERT_REVOCATION_DATA_SUPERSEDED 0
39 #endif
40 #ifndef GNUTLS_CERT_UNEXPECTED_OWNER
41 # define GNUTLS_CERT_UNEXPECTED_OWNER 0
42 #endif
43 #ifndef GNUTLS_CERT_REVOCATION_DATA_ISSUED_IN_FUTURE
44 # define GNUTLS_CERT_REVOCATION_DATA_ISSUED_IN_FUTURE 0
45 #endif
46 #ifndef GNUTLS_CERT_SIGNER_CONSTRAINTS_FAILURE
47 # define GNUTLS_CERT_SIGNER_CONSTRAINTS_FAILURE 0
48 #endif
49 #ifndef GNUTLS_CERT_MISMATCH
50 # define GNUTLS_CERT_MISMATCH 0
51 #endif
52
53
54 static int get_certificate_path(const char *format,
55         const char *hostname, char *buffer, size_t size);
56
57
58 int verify_tls_connection(gnutls_session_t session, const char *hostname) {
59     int result;
60     char path[TLSPROXY_MAX_PATH_LENGTH];
61
62     size_t size;
63     unsigned int status;
64     gnutls_x509_crt_t cert;
65     const gnutls_datum_t *cert_list;
66     unsigned int cert_list_size;
67     FILE *file;
68     char buffer[66]; /* one line in a PEM file is 64 bytes + '\n' + '\0' */
69     char server_cert[16384];
70     char stored_cert[16384];
71
72     result = gnutls_certificate_verify_peers2(session, &status);
73     /* Verification failed (!= invalid certificate but worse), no need for any
74      * more checks. */
75     if (result < 0) {
76         LOG(WARNING,
77             "verify_tls_connection(): "
78             "gnutls_certificate_verify_peers2() failed: %s",
79             gnutls_strerror(result));
80         return -1;
81     }
82     /* Definitely an invalid certificate, abort. We don't perform any CA
83      * verification so don't check for GNUTLS_CERT_INVALID. */
84     if (status & GNUTLS_CERT_REVOKED
85             || status & GNUTLS_CERT_SIGNER_NOT_CA
86             || status & GNUTLS_CERT_INSECURE_ALGORITHM
87             || status & GNUTLS_CERT_NOT_ACTIVATED
88             || status & GNUTLS_CERT_EXPIRED
89             || status & GNUTLS_CERT_SIGNATURE_FAILURE
90             || status & GNUTLS_CERT_REVOCATION_DATA_SUPERSEDED
91             || status & GNUTLS_CERT_UNEXPECTED_OWNER
92             || status & GNUTLS_CERT_REVOCATION_DATA_ISSUED_IN_FUTURE
93             || status & GNUTLS_CERT_SIGNER_CONSTRAINTS_FAILURE
94             || status & GNUTLS_CERT_MISMATCH) {
95         LOG(WARNING, "verify_tls_connection(): invalid server certificate");
96         return -1;
97     }
98
99     /* We only handle X509 certificates for now. Let validation fail to
100      * prevent an attacker from changing the certificate type to prevent
101      * detection. */
102     if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509) {
103         LOG(WARNING, "verify_tls_connection(): no X509 server certificate");
104         return -1;
105     }
106
107     /* Get server certificate. */
108
109     result = gnutls_x509_crt_init(&cert);
110     if (result < 0) {
111         LOG(WARNING,
112             "verify_tls_connection(): gnutls_x509_crt_init() failed: %s",
113             gnutls_strerror(result));
114         return -1;
115     }
116
117     cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
118     if (cert_list == NULL) {
119         LOG(WARNING,
120             "verify_tls_connection(): gnutls_certificate_get_peers() failed");
121         gnutls_x509_crt_deinit(cert);
122         return -1;
123     }
124
125     result = gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER);
126     if (result < 0) {
127         LOG(WARNING,
128             "verify_tls_connection(): gnutls_x509_crt_import() failed: %s",
129             gnutls_strerror(result));
130         gnutls_x509_crt_deinit(cert);
131         return -1;
132     }
133
134     /* Export the certificate as PEM to compare it with the known certificate
135      * stored on disk. */
136     size = sizeof(server_cert);
137     result = gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_PEM,
138                                     server_cert, &size);
139     if (result != GNUTLS_E_SUCCESS) {
140         LOG(WARNING,
141             "verify_tls_connection(): gnutls_x509_crt_export() failed: %s",
142             gnutls_strerror(result));
143         gnutls_x509_crt_deinit(cert);
144         return -1;
145     }
146     /* We got the certificate as PEM, free the crt struct. */
147     gnutls_x509_crt_deinit(cert);
148
149     /* Open stored server certificate file. */
150     if (server_certificate_file(&file, hostname, path, sizeof(path)) != 0) {
151         LOG(DEBUG1, "server certificate:\n%s", server_cert);
152         return -1;
153     }
154
155     size = 1; /* '\0' */
156     stored_cert[0] = '\0'; /* for strcat() */
157
158     while (fgets(buffer, sizeof(buffer), file) != NULL) {
159         size += strlen(buffer);
160         /* Make sure the buffer is big enough. */
161         if (size >= sizeof(stored_cert)) {
162             LOG(WARNING, "verify_tls_connection(): '%s' too big", path);
163             fclose(file);
164
165             LOG(DEBUG1, "server certificate:\n%s", server_cert);
166             return -1;
167         }
168
169         strcat(stored_cert, buffer);
170     }
171     if (ferror(file)) {
172         LOG(WARNING, "verify_tls_connection(): failed to read from '%s': %s",
173                      path, strerror(errno));
174         fclose(file);
175
176         LOG(DEBUG1, "server certificate:\n%s", server_cert);
177         return -1;
178     }
179     fclose(file);
180
181     /* Check if the server certificate matches our stored certificate. */
182     if (strcmp(stored_cert, server_cert)) {
183         LOG(ERROR, "verify_tls_connection(): server certificate changed!",
184                    path, strerror(errno));
185
186         LOG(WARNING, "server certificate:\n%s", server_cert);
187         return -2;
188     }
189
190     /* Check that the proxy certificate file for this domain exists and is
191      * readable. This ensures we send an "invalid" certificate if the proxy
192      * certificate doesn't exist.
193      *
194      * If the file gets removed or becomes unreadable after the check we won't
195      * be able to establish a connection to the real server so this
196      * race-condition has no security issues and is only a convenience for the
197      * user. */
198     if (proxy_certificate_path(hostname, path, sizeof(path)) != 0) {
199         return -1;
200     }
201     file = fopen(path, "r");
202     if (file == NULL) {
203         LOG(WARNING,
204             "verify_tls_connection(): proxy certificate doesn't exist: '%s'",
205             path);
206         return -1;
207     }
208     fclose(file);
209
210     return 0;
211 }
212
213
214 static int get_certificate_path(const char *format,
215         const char *hostname, char *buffer, size_t size) {
216     int result;
217
218     /* Hostname too long. */
219     assert(size > strlen(format));
220     if (size - strlen(format) <= strlen(hostname)) {
221         LOG(WARNING, "get_certificate_path(): hostname too long: '%s'",
222                      hostname);
223         return -1;
224     }
225     /* Try to prevent path traversals in hostnames. */
226     if (strstr(hostname, "..") != NULL) {
227         LOG(WARNING, "get_certificate_path(): possible path traversal: '%s'",
228                      hostname);
229         return -1;
230     }
231     /* Safe as format is no user input. */
232     result = snprintf(buffer, size, format, hostname);
233     if (result < 0) {
234         LOG_PERROR(ERROR, "get_certificate_path(): snprintf failed");
235         return -1;
236     } else if ((size_t)result >= size) {
237         LOG(ERROR, "get_certificate_path(): snprintf buffer too short");
238         return -1;
239     }
240
241     return 0;
242 }
243
244 int proxy_certificate_path(const char *hostname, char *path, size_t size) {
245     return get_certificate_path(PROXY_SERVER_CERT_FILE_FORMAT,
246                                 hostname, path, size);
247 }
248
249 int server_certificate_file(FILE **file, const char *hostname,
250                             char *path, size_t size) {
251     if (get_certificate_path(STORED_SERVER_CERT_FILE_FORMAT,
252                              hostname, path, size) != 0) {
253         LOG_PERROR(ERROR, "server_certificate_file(): failed to get path");
254         return -1;
255     }
256
257     /* Open the stored certificate file. */
258     *file = fopen(path, "rb");
259     if (*file == NULL) {
260         if (global_passthrough_unknown) {
261             LOG(DEBUG1, "server_certificate_file(): failed to open '%s': %s",
262                         path, strerror(errno));
263         } else {
264             LOG(WARNING, "server_certificate_file(): failed to open '%s': %s",
265                          path, strerror(errno));
266         }
267         /* Couldn't open the file, special case. */
268         return -2;
269     }
270
271     return 0;
272 }