]> ruderich.org/simon Gitweb - tlsproxy/tlsproxy.git/blob - src/verify.c
Check return value of snprintf().
[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         LOG(LOG_DEBUG, "server certificate:\n%s", server_cert);
115         return -1;
116     }
117
118     size = 1; /* '\0' */
119     stored_cert[0] = '\0'; /* for strcat() */
120
121     while (NULL != fgets(buffer, sizeof(buffer), file)) {
122         size += strlen(buffer);
123         /* Make sure the buffer is big enough. */
124         if (sizeof(stored_cert) <= size) {
125             LOG(LOG_WARNING, "verify_tls_connection(): '%s' too big", path);
126             fclose(file);
127
128             LOG(LOG_DEBUG, "server certificate:\n%s", server_cert);
129             return -1;
130         }
131
132         strcat(stored_cert, buffer);
133     }
134     if (ferror(file)) {
135         fclose(file);
136         LOG(LOG_WARNING,
137             "verify_tls_connection(): failed to read from '%s': %s",
138             path, strerror(errno));
139
140         LOG(LOG_DEBUG, "server certificate:\n%s", server_cert);
141         return -1;
142     }
143     fclose(file);
144
145     /* Check if the server certificate matches our stored certificate. */
146     if (0 != strcmp(stored_cert, server_cert)) {
147         LOG(LOG_ERROR,
148             "verify_tls_connection(): server certificate changed!",
149             path, strerror(errno));
150
151         LOG(LOG_WARNING, "server certificate:\n%s", server_cert);
152         return -2;
153     }
154
155     return 0;
156 }
157
158 int server_certificate_path(FILE **file, const char *hostname,
159                             char *path, size_t size) {
160     int result;
161
162     /* Hostname too long. */
163     if (size - strlen(STORED_SERVER_CERT_FORMAT) <= strlen(hostname)) {
164         LOG(LOG_WARNING,
165             "server_certificate_path(): hostname too long: '%s'",
166             hostname);
167         return -1;
168     }
169     /* Try to prevent path traversals in hostnames. */
170     if (NULL != strstr(hostname, "..")) {
171         LOG(LOG_WARNING,
172             "server_certificate_path(): possible path traversal: '%s'",
173             hostname);
174         return -1;
175     }
176     result = snprintf(path, size, STORED_SERVER_CERT_FORMAT, hostname);
177     if (result < 0) {
178         LOG_PERROR(LOG_ERROR,
179                    "server_certificate_path(): snprintf failed");
180         return -1;
181     } else if ((size_t)result >= size) {
182         LOG(LOG_ERROR,
183             "server_certificate_path(): snprintf buffer too short");
184         return -1;
185     }
186
187     /* Open the stored certificate file. */
188     *file = fopen(path, "rb");
189     if (NULL == *file) {
190         if (global_passthrough_unknown) {
191             LOG(LOG_DEBUG,
192                 "server_certificate_path(): failed to open '%s': %s",
193                 path, strerror(errno));
194         } else {
195             LOG(LOG_WARNING,
196                 "server_certificate_path(): failed to open '%s': %s",
197                 path, strerror(errno));
198         }
199         /* Couldn't open the file, special case. */
200         return -2;
201     }
202
203     return 0;
204 }