]> ruderich.org/simon Gitweb - tlsproxy/tlsproxy.git/blob - src/verify.c
4cd9d503d791ae3c427c7ffe3e845037897747cd
[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",
125                 path);
126             fclose(file);
127             return -1;
128         }
129
130         strcat(stored_cert, buffer);
131     }
132     if (ferror(file)) {
133         fclose(file);
134         LOG(LOG_WARNING,
135             "verify_tls_connection(): failed to read from '%s': %s",
136             path, strerror(errno));
137         return -1;
138     }
139     fclose(file);
140
141     /* Check if the server certificate matches our stored certificate. */
142     if (0 != strcmp(stored_cert, server_cert)) {
143         LOG(LOG_ERROR,
144             "verify_tls_connection(): server certificate changed!",
145             path, strerror(errno));
146         return -2;
147     }
148
149     return 0;
150 }
151
152 int server_certificate_path(FILE **file, const char *hostname, 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 }