blob: 446674f481fd07ba04d44368f38b95da056553c2 [file] [log] [blame]
Adam Tkacb10489b2010-04-23 14:16:04 +00001/*
2 * Copyright (C) 2004 Red Hat Inc.
3 * Copyright (C) 2005 Martin Koegler
4 * Copyright (C) 2010 TigerVNC Team
Adam Tkac27b2f772010-11-18 13:33:57 +00005 * Copyright (C) 2010 m-privacy GmbH
Adam Tkacb10489b2010-04-23 14:16:04 +00006 *
7 * This is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this software; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
20 * USA.
21 */
22
23#ifdef HAVE_CONFIG_H
24#include <config.h>
25#endif
26
Adam Tkac43958232010-07-21 09:06:59 +000027#ifndef HAVE_GNUTLS
28#error "This header should not be compiled without HAVE_GNUTLS defined"
29#endif
Adam Tkacb10489b2010-04-23 14:16:04 +000030
Adam Tkac27b2f772010-11-18 13:33:57 +000031#include <stdlib.h>
Adam Tkacc4674db2011-01-19 14:11:16 +000032#ifndef WIN32
Adam Tkac27b2f772010-11-18 13:33:57 +000033#include <unistd.h>
Adam Tkacc4674db2011-01-19 14:11:16 +000034#endif
Adam Tkac27b2f772010-11-18 13:33:57 +000035
Adam Tkac3c5be392010-07-21 09:27:34 +000036#include <rfb/CSecurityTLS.h>
Adam Tkac0e61c342010-07-21 09:23:25 +000037#include <rfb/SSecurityVeNCrypt.h>
Adam Tkacb10489b2010-04-23 14:16:04 +000038#include <rfb/CConnection.h>
39#include <rfb/LogWriter.h>
40#include <rfb/Exception.h>
Adam Tkac27b2f772010-11-18 13:33:57 +000041#include <rfb/UserMsgBox.h>
Adam Tkacb10489b2010-04-23 14:16:04 +000042#include <rdr/TLSInStream.h>
43#include <rdr/TLSOutStream.h>
Adam Tkac27b2f772010-11-18 13:33:57 +000044#include <os/os.h>
Adam Tkacb10489b2010-04-23 14:16:04 +000045
Adam Tkac0e61c342010-07-21 09:23:25 +000046#include <gnutls/x509.h>
47
Adam Tkacb10489b2010-04-23 14:16:04 +000048#define TLS_DEBUG
49
50using namespace rfb;
51
Adam Tkac3c5be392010-07-21 09:27:34 +000052StringParameter CSecurityTLS::x509ca("x509ca", "X509 CA certificate", "", ConfViewer);
53StringParameter CSecurityTLS::x509crl("x509crl", "X509 CRL file", "", ConfViewer);
Adam Tkac0e61c342010-07-21 09:23:25 +000054
Adam Tkacb10489b2010-04-23 14:16:04 +000055static LogWriter vlog("TLS");
56
57#ifdef TLS_DEBUG
58static void debug_log(int level, const char* str)
59{
60 vlog.debug(str);
61}
62#endif
63
Adam Tkac3c5be392010-07-21 09:27:34 +000064void CSecurityTLS::initGlobal()
Adam Tkacb10489b2010-04-23 14:16:04 +000065{
66 static bool globalInitDone = false;
67
68 if (!globalInitDone) {
69 gnutls_global_init();
70
71#ifdef TLS_DEBUG
72 gnutls_global_set_log_level(10);
73 gnutls_global_set_log_function(debug_log);
74#endif
75
76 globalInitDone = true;
77 }
78}
79
Adam Tkac3c5be392010-07-21 09:27:34 +000080CSecurityTLS::CSecurityTLS(bool _anon) : session(0), anon_cred(0),
Adam Tkac0e61c342010-07-21 09:23:25 +000081 anon(_anon), fis(0), fos(0)
Adam Tkacb10489b2010-04-23 14:16:04 +000082{
Adam Tkac0e61c342010-07-21 09:23:25 +000083 cafile = x509ca.getData();
84 crlfile = x509crl.getData();
Adam Tkacb10489b2010-04-23 14:16:04 +000085}
86
Adam Tkac27b2f772010-11-18 13:33:57 +000087void CSecurityTLS::setDefaults()
88{
89 char* homeDir = NULL;
90
91 if (gethomedir(&homeDir) == -1) {
92 vlog.error("Could not obtain home directory path");
93 return;
94 }
95
96 CharArray caDefault(strlen(homeDir)+17);
97 sprintf(caDefault.buf, "%s/.vnc/x509_certs", homeDir);
98 delete [] homeDir;
99
Adam Tkacc4674db2011-01-19 14:11:16 +0000100#ifndef WIN32
Adam Tkac27b2f772010-11-18 13:33:57 +0000101 /* XXX Do we need access() check here? */
102 if (!access(caDefault.buf, R_OK))
103 x509ca.setDefaultStr(strdup(caDefault.buf));
104 else
105 vlog.error("Failed to open ~/.vnc/x509_certs");
Adam Tkacc4674db2011-01-19 14:11:16 +0000106#else
107 /* Windows doesn't have access() function. */
108 x509ca.setDefaultStr(strdup(caDefault.buf));
109#endif
Adam Tkac27b2f772010-11-18 13:33:57 +0000110}
111
Adam Tkac3c5be392010-07-21 09:27:34 +0000112void CSecurityTLS::shutdown()
Adam Tkacb10489b2010-04-23 14:16:04 +0000113{
Adam Tkac0e61c342010-07-21 09:23:25 +0000114 if (session)
Adam Tkac6948ead2010-08-11 15:58:59 +0000115 if (gnutls_bye(session, GNUTLS_SHUT_RDWR) != GNUTLS_E_SUCCESS)
116 throw Exception("gnutls_bye failed");
Adam Tkac0e61c342010-07-21 09:23:25 +0000117
118 if (anon_cred) {
119 gnutls_anon_free_client_credentials(anon_cred);
120 anon_cred = 0;
121 }
122
123 if (cert_cred) {
124 gnutls_certificate_free_credentials(cert_cred);
125 cert_cred = 0;
126 }
127
128 if (session) {
129 gnutls_deinit(session);
130 session = 0;
131
132 gnutls_global_deinit();
133 }
Adam Tkacb10489b2010-04-23 14:16:04 +0000134}
135
136
Adam Tkac3c5be392010-07-21 09:27:34 +0000137CSecurityTLS::~CSecurityTLS()
Adam Tkacb10489b2010-04-23 14:16:04 +0000138{
Adam Tkac0e61c342010-07-21 09:23:25 +0000139 shutdown();
140
Adam Tkacb10489b2010-04-23 14:16:04 +0000141 if (fis)
142 delete fis;
143 if (fos)
144 delete fos;
Adam Tkac0e61c342010-07-21 09:23:25 +0000145
146 delete[] cafile;
147 delete[] crlfile;
Adam Tkacb10489b2010-04-23 14:16:04 +0000148}
149
Adam Tkac3c5be392010-07-21 09:27:34 +0000150bool CSecurityTLS::processMsg(CConnection* cc)
Adam Tkacb10489b2010-04-23 14:16:04 +0000151{
152 rdr::InStream* is = cc->getInStream();
153 rdr::OutStream* os = cc->getOutStream();
154 client = cc;
155
156 initGlobal();
157
158 if (!session) {
159 if (!is->checkNoWait(1))
160 return false;
161
162 if (is->readU8() == 0)
163 return true;
164
Adam Tkac6948ead2010-08-11 15:58:59 +0000165 if (gnutls_init(&session, GNUTLS_CLIENT) != GNUTLS_E_SUCCESS)
166 throw AuthFailureException("gnutls_init failed");
167
168 if (gnutls_set_default_priority(session) != GNUTLS_E_SUCCESS)
169 throw AuthFailureException("gnutls_set_default_priority failed");
Adam Tkacb10489b2010-04-23 14:16:04 +0000170
Adam Tkac0e61c342010-07-21 09:23:25 +0000171 setParam();
Adam Tkacb10489b2010-04-23 14:16:04 +0000172
173 gnutls_transport_set_pull_function(session, rdr::gnutls_InStream_pull);
174 gnutls_transport_set_push_function(session, rdr::gnutls_OutStream_push);
175 gnutls_transport_set_ptr2(session,
176 (gnutls_transport_ptr) is,
177 (gnutls_transport_ptr) os);
178 }
179
180 int err;
181 err = gnutls_handshake(session);
182 if (err != GNUTLS_E_SUCCESS && !gnutls_error_is_fatal(err))
183 return false;
184
185 if (err != GNUTLS_E_SUCCESS) {
186 vlog.error("TLS Handshake failed: %s\n", gnutls_strerror (err));
Adam Tkac0e61c342010-07-21 09:23:25 +0000187 shutdown();
Adam Tkacb10489b2010-04-23 14:16:04 +0000188 throw AuthFailureException("TLS Handshake failed");
189 }
Adam Tkac0e61c342010-07-21 09:23:25 +0000190
191 checkSession();
Adam Tkacb10489b2010-04-23 14:16:04 +0000192
193 cc->setStreams(fis = new rdr::TLSInStream(is, session),
194 fos = new rdr::TLSOutStream(os, session));
195
196 return true;
197}
198
Adam Tkac3c5be392010-07-21 09:27:34 +0000199void CSecurityTLS::setParam()
Adam Tkac0e61c342010-07-21 09:23:25 +0000200{
201 static const int kx_anon_priority[] = { GNUTLS_KX_ANON_DH, 0 };
202 static const int kx_priority[] = { GNUTLS_KX_DHE_DSS, GNUTLS_KX_RSA,
203 GNUTLS_KX_DHE_RSA, GNUTLS_KX_SRP, 0 };
204
205 if (anon) {
Adam Tkac6948ead2010-08-11 15:58:59 +0000206 if (gnutls_kx_set_priority(session, kx_anon_priority) != GNUTLS_E_SUCCESS)
207 throw AuthFailureException("gnutls_kx_set_priority failed");
208
209 if (gnutls_anon_allocate_client_credentials(&anon_cred) != GNUTLS_E_SUCCESS)
210 throw AuthFailureException("gnutls_anon_allocate_client_credentials failed");
211
212 if (gnutls_credentials_set(session, GNUTLS_CRD_ANON, anon_cred) != GNUTLS_E_SUCCESS)
213 throw AuthFailureException("gnutls_credentials_set failed");
Adam Tkac0e61c342010-07-21 09:23:25 +0000214
215 vlog.debug("Anonymous session has been set");
216 } else {
Adam Tkac6948ead2010-08-11 15:58:59 +0000217 if (gnutls_kx_set_priority(session, kx_priority) != GNUTLS_E_SUCCESS)
218 throw AuthFailureException("gnutls_kx_set_priority failed");
219
220 if (gnutls_certificate_allocate_credentials(&cert_cred) != GNUTLS_E_SUCCESS)
221 throw AuthFailureException("gnutls_certificate_allocate_credentials failed");
Adam Tkac0e61c342010-07-21 09:23:25 +0000222
223 if (*cafile && gnutls_certificate_set_x509_trust_file(cert_cred,cafile,GNUTLS_X509_FMT_PEM) < 0)
224 throw AuthFailureException("load of CA cert failed");
225
226 if (*crlfile && gnutls_certificate_set_x509_crl_file(cert_cred,crlfile,GNUTLS_X509_FMT_PEM) < 0)
227 throw AuthFailureException("load of CRL failed");
228
Adam Tkac6948ead2010-08-11 15:58:59 +0000229 if (gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, cert_cred) != GNUTLS_E_SUCCESS)
230 throw AuthFailureException("gnutls_credentials_set failed");
Adam Tkac0e61c342010-07-21 09:23:25 +0000231
232 vlog.debug("X509 session has been set");
233 }
234}
235
Adam Tkac3c5be392010-07-21 09:27:34 +0000236void CSecurityTLS::checkSession()
Adam Tkac0e61c342010-07-21 09:23:25 +0000237{
238 int status;
239 const gnutls_datum *cert_list;
240 unsigned int cert_list_size = 0;
241 unsigned int i;
Adam Tkac27b2f772010-11-18 13:33:57 +0000242 gnutls_datum_t info;
Adam Tkac0e61c342010-07-21 09:23:25 +0000243
244 if (anon)
245 return;
246
247 if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509)
248 throw AuthFailureException("unsupported certificate type");
249
250 cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
251 if (!cert_list_size)
252 throw AuthFailureException("unsupported certificate type");
253
254 status = gnutls_certificate_verify_peers(session);
255 if (status == GNUTLS_E_NO_CERTIFICATE_FOUND)
256 throw AuthFailureException("no certificate sent");
257
258 if (status < 0) {
259 vlog.error("X509 verify failed: %s\n", gnutls_strerror (status));
260 throw AuthFailureException("certificate verification failed");
261 }
262
Adam Tkac27b2f772010-11-18 13:33:57 +0000263 if (status & GNUTLS_CERT_REVOKED) {
264 throw AuthFailureException("certificate has been revoked");
265 }
Adam Tkac0e61c342010-07-21 09:23:25 +0000266
Adam Tkac27b2f772010-11-18 13:33:57 +0000267 if (status & GNUTLS_CERT_NOT_ACTIVATED) {
268 throw AuthFailureException("certificate has not been activated");
269 }
Adam Tkac0e61c342010-07-21 09:23:25 +0000270
271 for (i = 0; i < cert_list_size; i++) {
272 gnutls_x509_crt crt;
273 gnutls_x509_crt_init(&crt);
274
275 if (gnutls_x509_crt_import(crt, &cert_list[i],GNUTLS_X509_FMT_DER) < 0)
Adam Tkac27b2f772010-11-18 13:33:57 +0000276 throw AuthFailureException("decoding of certificate failed");
277
278 if (gnutls_x509_crt_print(crt, GNUTLS_CRT_PRINT_ONELINE, &info)) {
Adam Tkac5d4c6ac2011-01-19 14:06:48 +0000279 /*
280 * GNUTLS doesn't correctly export gnutls_free symbol which is
281 * a function pointer. Linking with Visual Studio 2008 Express will
282 * fail when you call gnutls_free().
283 */
284#if WIN32
285 free(info.data);
286#else
Adam Tkac27b2f772010-11-18 13:33:57 +0000287 gnutls_free(info.data);
Adam Tkac5d4c6ac2011-01-19 14:06:48 +0000288#endif
Adam Tkac27b2f772010-11-18 13:33:57 +0000289 throw AuthFailureException("Could not find certificate to display");
290 }
Adam Tkac0e61c342010-07-21 09:23:25 +0000291
292 if (gnutls_x509_crt_check_hostname(crt, client->getServerName()) == 0) {
Adam Tkac27b2f772010-11-18 13:33:57 +0000293 char buf[255];
294 sprintf(buf, "Hostname (%s) does not match any certificate, do you want to continue?", client->getServerName());
295 vlog.debug("hostname mismatch");
296 if(!msg->showMsgBox(UserMsgBox::M_YESNO, "hostname mismatch", buf))
297 throw AuthFailureException("hostname mismatch");
Adam Tkac0e61c342010-07-21 09:23:25 +0000298 }
Adam Tkac27b2f772010-11-18 13:33:57 +0000299
300 if (status & GNUTLS_CERT_EXPIRED) {
301 vlog.debug("certificate has expired");
302 if (!msg->showMsgBox(UserMsgBox::M_YESNO, "certficate has expired", "The certificate of the server has expired, do you want to continue?"))
303 throw AuthFailureException("certificate has expired");
304 }
305
306 if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) {
307 size_t out_size;
308 char *homeDir = NULL;
309 char *out_buf = NULL;
310 char *certinfo = NULL;
311 int len = 0;
312
313 vlog.debug("certificate issuer unknown");
314
315 len = snprintf(NULL, 0, "This certificate has been signed by an unknown authority:\n\n%s\n\nDo you want to save it and continue?\n ", info.data);
316 if (len < 0)
317 AuthFailureException("certificate decoding error");
318
319 vlog.debug("%s", info.data);
320
321 certinfo = new char[len];
322 if (certinfo == NULL)
323 throw AuthFailureException("Out of memory");
324
325 snprintf(certinfo, len, "This certificate has been signed by an unknown authority:\n\n%s\n\nDo you want to save it and continue? ", info.data);
326
327 for (int i = 0; i < len - 1; i++)
328 if (certinfo[i] == ',' && certinfo[i + 1] == ' ')
329 certinfo[i] = '\n';
330
331 if (!msg->showMsgBox(UserMsgBox::M_YESNO, "certificate issuer unknown",
332 certinfo)) {
333 delete [] certinfo;
334 throw AuthFailureException("certificate issuer unknown");
335 }
336 delete [] certinfo;
337
338 if (gnutls_x509_crt_export(crt, GNUTLS_X509_FMT_PEM, NULL, &out_size)
339 == GNUTLS_E_SHORT_MEMORY_BUFFER)
340 AuthFailureException("Out of memory");
341
342 // Save cert
343 out_buf = new char[out_size];
344 if (out_buf == NULL)
345 AuthFailureException("Out of memory");
346
347 if (gnutls_x509_crt_export(crt, GNUTLS_X509_FMT_PEM, out_buf, &out_size)
348 < 0)
349 AuthFailureException("certificate issuer unknown, and certificate "
350 "export failed");
351
352 if (gethomedir(&homeDir) == -1)
353 vlog.error("Could not obtain home directory path");
354 else {
355 FILE *f;
356 CharArray caSave(strlen(homeDir)+17);
357 sprintf(caSave.buf, "%s/.vnc/x509_certs", homeDir);
358 delete [] homeDir;
359 f = fopen(caSave.buf, "a+");
360 if (!f)
361 msg->showMsgBox(UserMsgBox::M_OK, "certificate save failed",
362 "Could not save the certificate");
363 else {
364 fprintf(f, "%s\n", out_buf);
365 fclose(f);
366 }
367 }
368 delete [] out_buf;
369 } else if (status & GNUTLS_CERT_INVALID)
370 throw AuthFailureException("certificate not trusted");
371
Adam Tkac0e61c342010-07-21 09:23:25 +0000372 gnutls_x509_crt_deinit(crt);
Adam Tkac5d4c6ac2011-01-19 14:06:48 +0000373 /*
374 * GNUTLS doesn't correctly export gnutls_free symbol which is
375 * a function pointer. Linking with Visual Studio 2008 Express will
376 * fail when you call gnutls_free().
377 */
378#if WIN32
379 free(info.data);
380#else
Adam Tkac27b2f772010-11-18 13:33:57 +0000381 gnutls_free(info.data);
Adam Tkac5d4c6ac2011-01-19 14:06:48 +0000382#endif
Adam Tkac0e61c342010-07-21 09:23:25 +0000383 }
384}
385