]> ruderich.org/simon Gitweb - tlsproxy/tlsproxy.git/blob - src/verify.c
Verify the server certificate against a stored copy.
[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 it. */
110     gnutls_x509_crt_deinit(cert);
111
112     /* Hostname too long. */
113     if (sizeof(path) - strlen(STORED_SERVER_CERT_FORMAT) <= strlen(hostname)) {
114         LOG(LOG_WARNING,
115             "verify_tls_connection(): hostname too long: '%s'",
116             hostname);
117         return -1;
118     }
119     /* Try to prevent path traversals in hostnames. */
120     if (NULL != strstr(hostname, "..")) {
121         LOG(LOG_WARNING,
122             "verify_tls_connection(): possible path traversal: '%s'",
123             hostname);
124         return -1;
125     }
126     snprintf(path, sizeof(path), STORED_SERVER_CERT_FORMAT, hostname);
127
128     /* Open the stored certificate file. */
129     file = fopen(path, "rb");
130     if (NULL == file) {
131         LOG(LOG_WARNING,
132             "verify_tls_connection(): failed to open '%s': %s",
133             path, strerror(errno));
134         return -1;
135     }
136
137     size = 1; /* '\0' */
138     stored_cert[0] = '\0'; /* for strcat() */
139
140     while (NULL != fgets(buffer, sizeof(buffer), file)) {
141         size += strlen(buffer);
142         /* Make sure the buffer is big enough. */
143         if (sizeof(stored_cert) <= size) {
144             LOG(LOG_WARNING, "verify_tls_connection(): '%s' too big",
145                 path);
146             fclose(file);
147             return -1;
148         }
149
150         strcat(stored_cert, buffer);
151     }
152     if (ferror(file)) {
153         fclose(file);
154         LOG(LOG_WARNING,
155             "verify_tls_connection(): failed to read from '%s': %s",
156             path, strerror(errno));
157         return -1;
158     }
159     fclose(file);
160
161     /* Check if the server certificate matches our stored certificate. */
162     if (0 != strcmp(stored_cert, server_cert)) {
163         LOG(LOG_ERROR,
164             "verify_tls_connection(): server certificate changed!",
165             path, strerror(errno));
166         return -2;
167     }
168
169     return 0;
170 }