]> ruderich.org/simon Gitweb - tlsproxy/tlsproxy.git/blob - src/verify.c
verify.c: Perform additional checks on server certificate.
[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. */
83     if (status & GNUTLS_CERT_REVOKED
84             || status & GNUTLS_CERT_SIGNER_NOT_CA
85             || status & GNUTLS_CERT_INSECURE_ALGORITHM
86             || status & GNUTLS_CERT_NOT_ACTIVATED
87             || status & GNUTLS_CERT_EXPIRED
88             || status & GNUTLS_CERT_SIGNATURE_FAILURE
89             || status & GNUTLS_CERT_REVOCATION_DATA_SUPERSEDED
90             || status & GNUTLS_CERT_UNEXPECTED_OWNER
91             || status & GNUTLS_CERT_REVOCATION_DATA_ISSUED_IN_FUTURE
92             || status & GNUTLS_CERT_SIGNER_CONSTRAINTS_FAILURE
93             || status & GNUTLS_CERT_MISMATCH) {
94         LOG(WARNING, "verify_tls_connection(): invalid server certificate");
95         return -1;
96     }
97
98     /* We only handle X509 certificates for now. Let validation fail to
99      * prevent an attacker from changing the certificate type to prevent
100      * detection. */
101     if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509) {
102         LOG(WARNING, "verify_tls_connection(): no X509 server certificate");
103         return -1;
104     }
105
106     /* Get server certificate. */
107
108     result = gnutls_x509_crt_init(&cert);
109     if (result < 0) {
110         LOG(WARNING,
111             "verify_tls_connection(): gnutls_x509_crt_init() failed: %s",
112             gnutls_strerror(result));
113         return -1;
114     }
115
116     cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
117     if (cert_list == NULL) {
118         LOG(WARNING,
119             "verify_tls_connection(): gnutls_certificate_get_peers() failed");
120         gnutls_x509_crt_deinit(cert);
121         return -1;
122     }
123
124     result = gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER);
125     if (result < 0) {
126         LOG(WARNING,
127             "verify_tls_connection(): gnutls_x509_crt_import() failed: %s",
128             gnutls_strerror(result));
129         gnutls_x509_crt_deinit(cert);
130         return -1;
131     }
132
133     /* Export the certificate as PEM to compare it with the known certificate
134      * stored on disk. */
135     size = sizeof(server_cert);
136     result = gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_PEM,
137                                     server_cert, &size);
138     if (result != GNUTLS_E_SUCCESS) {
139         LOG(WARNING,
140             "verify_tls_connection(): gnutls_x509_crt_export() failed: %s",
141             gnutls_strerror(result));
142         gnutls_x509_crt_deinit(cert);
143         return -1;
144     }
145     /* We got the certificate as PEM, free the crt struct. */
146     gnutls_x509_crt_deinit(cert);
147
148     /* Open stored server certificate file. */
149     if (server_certificate_file(&file, hostname, path, sizeof(path)) != 0) {
150         LOG(DEBUG1, "server certificate:\n%s", server_cert);
151         return -1;
152     }
153
154     size = 1; /* '\0' */
155     stored_cert[0] = '\0'; /* for strcat() */
156
157     while (fgets(buffer, sizeof(buffer), file) != NULL) {
158         size += strlen(buffer);
159         /* Make sure the buffer is big enough. */
160         if (size >= sizeof(stored_cert)) {
161             LOG(WARNING, "verify_tls_connection(): '%s' too big", path);
162             fclose(file);
163
164             LOG(DEBUG1, "server certificate:\n%s", server_cert);
165             return -1;
166         }
167
168         strcat(stored_cert, buffer);
169     }
170     if (ferror(file)) {
171         LOG(WARNING, "verify_tls_connection(): failed to read from '%s': %s",
172                      path, strerror(errno));
173         fclose(file);
174
175         LOG(DEBUG1, "server certificate:\n%s", server_cert);
176         return -1;
177     }
178     fclose(file);
179
180     /* Check if the server certificate matches our stored certificate. */
181     if (strcmp(stored_cert, server_cert)) {
182         LOG(ERROR, "verify_tls_connection(): server certificate changed!",
183                    path, strerror(errno));
184
185         LOG(WARNING, "server certificate:\n%s", server_cert);
186         return -2;
187     }
188
189     /* Check that the proxy certificate file exists and is readable for this
190      * domain. This ensures we send an "invalid" certificate even if the proxy
191      * certificate doesn't exist. */
192     if (proxy_certificate_path(hostname, path, sizeof(path)) != 0) {
193         return -1;
194     }
195     file = fopen(path, "r");
196     if (file == NULL) {
197         LOG(WARNING,
198             "verify_tls_connection(): proxy certificate doesn't exist: '%s'",
199             path);
200         return -1;
201     }
202     fclose(file);
203
204     return 0;
205 }
206
207
208 static int get_certificate_path(const char *format,
209         const char *hostname, char *buffer, size_t size) {
210     int result;
211
212     /* Hostname too long. */
213     assert(size > strlen(format));
214     if (size - strlen(format) <= strlen(hostname)) {
215         LOG(WARNING, "get_certificate_path(): hostname too long: '%s'",
216                      hostname);
217         return -1;
218     }
219     /* Try to prevent path traversals in hostnames. */
220     if (strstr(hostname, "..") != NULL) {
221         LOG(WARNING, "get_certificate_path(): possible path traversal: '%s'",
222                      hostname);
223         return -1;
224     }
225     /* Safe as format is no user input. */
226     result = snprintf(buffer, size, format, hostname);
227     if (result < 0) {
228         LOG_PERROR(ERROR, "get_certificate_path(): snprintf failed");
229         return -1;
230     } else if ((size_t)result >= size) {
231         LOG(ERROR, "get_certificate_path(): snprintf buffer too short");
232         return -1;
233     }
234
235     return 0;
236 }
237
238 int proxy_certificate_path(const char *hostname, char *path, size_t size) {
239     return get_certificate_path(PROXY_SERVER_CERT_FORMAT,
240                                 hostname, path, size);
241 }
242
243 int server_certificate_file(FILE **file, const char *hostname,
244                             char *path, size_t size) {
245     if (get_certificate_path(STORED_SERVER_CERT_FORMAT,
246                              hostname, path, size) != 0) {
247         LOG_PERROR(ERROR, "server_certificate_file(): failed to get path");
248         return -1;
249     }
250
251     /* Open the stored certificate file. */
252     *file = fopen(path, "rb");
253     if (*file == NULL) {
254         if (global_passthrough_unknown) {
255             LOG(DEBUG1, "server_certificate_file(): failed to open '%s': %s",
256                         path, strerror(errno));
257         } else {
258             LOG(WARNING, "server_certificate_file(): failed to open '%s': %s",
259                          path, strerror(errno));
260         }
261         /* Couldn't open the file, special case. */
262         return -2;
263     }
264
265     return 0;
266 }