]> ruderich.org/simon Gitweb - tlsproxy/tlsproxy.git/blob - src/verify.c
Rename *_SERVER_CERT_FORMAT to *_SERVER_CERT_FILE_FORMAT.
[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 exists and is readable for this
191      * domain. This ensures we send an "invalid" certificate even if the proxy
192      * certificate doesn't exist. */
193     if (proxy_certificate_path(hostname, path, sizeof(path)) != 0) {
194         return -1;
195     }
196     file = fopen(path, "r");
197     if (file == NULL) {
198         LOG(WARNING,
199             "verify_tls_connection(): proxy certificate doesn't exist: '%s'",
200             path);
201         return -1;
202     }
203     fclose(file);
204
205     return 0;
206 }
207
208
209 static int get_certificate_path(const char *format,
210         const char *hostname, char *buffer, size_t size) {
211     int result;
212
213     /* Hostname too long. */
214     assert(size > strlen(format));
215     if (size - strlen(format) <= strlen(hostname)) {
216         LOG(WARNING, "get_certificate_path(): hostname too long: '%s'",
217                      hostname);
218         return -1;
219     }
220     /* Try to prevent path traversals in hostnames. */
221     if (strstr(hostname, "..") != NULL) {
222         LOG(WARNING, "get_certificate_path(): possible path traversal: '%s'",
223                      hostname);
224         return -1;
225     }
226     /* Safe as format is no user input. */
227     result = snprintf(buffer, size, format, hostname);
228     if (result < 0) {
229         LOG_PERROR(ERROR, "get_certificate_path(): snprintf failed");
230         return -1;
231     } else if ((size_t)result >= size) {
232         LOG(ERROR, "get_certificate_path(): snprintf buffer too short");
233         return -1;
234     }
235
236     return 0;
237 }
238
239 int proxy_certificate_path(const char *hostname, char *path, size_t size) {
240     return get_certificate_path(PROXY_SERVER_CERT_FILE_FORMAT,
241                                 hostname, path, size);
242 }
243
244 int server_certificate_file(FILE **file, const char *hostname,
245                             char *path, size_t size) {
246     if (get_certificate_path(STORED_SERVER_CERT_FILE_FORMAT,
247                              hostname, path, size) != 0) {
248         LOG_PERROR(ERROR, "server_certificate_file(): failed to get path");
249         return -1;
250     }
251
252     /* Open the stored certificate file. */
253     *file = fopen(path, "rb");
254     if (*file == NULL) {
255         if (global_passthrough_unknown) {
256             LOG(DEBUG1, "server_certificate_file(): failed to open '%s': %s",
257                         path, strerror(errno));
258         } else {
259             LOG(WARNING, "server_certificate_file(): failed to open '%s': %s",
260                          path, strerror(errno));
261         }
262         /* Couldn't open the file, special case. */
263         return -2;
264     }
265
266     return 0;
267 }