blob: 427d2005c55d576a31cccd8e7b9a3c602ab4fc77 [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 Tkac179d2b12011-01-19 14:15:14 +000045#include <os/print.h>
Adam Tkacb10489b2010-04-23 14:16:04 +000046
Adam Tkac0e61c342010-07-21 09:23:25 +000047#include <gnutls/x509.h>
48
Adam Tkacb10489b2010-04-23 14:16:04 +000049#define TLS_DEBUG
50
51using namespace rfb;
52
Adam Tkac3c5be392010-07-21 09:27:34 +000053StringParameter CSecurityTLS::x509ca("x509ca", "X509 CA certificate", "", ConfViewer);
54StringParameter CSecurityTLS::x509crl("x509crl", "X509 CRL file", "", ConfViewer);
Adam Tkac0e61c342010-07-21 09:23:25 +000055
Adam Tkacb10489b2010-04-23 14:16:04 +000056static LogWriter vlog("TLS");
57
58#ifdef TLS_DEBUG
59static void debug_log(int level, const char* str)
60{
61 vlog.debug(str);
62}
63#endif
64
Adam Tkac3c5be392010-07-21 09:27:34 +000065void CSecurityTLS::initGlobal()
Adam Tkacb10489b2010-04-23 14:16:04 +000066{
67 static bool globalInitDone = false;
68
69 if (!globalInitDone) {
70 gnutls_global_init();
71
72#ifdef TLS_DEBUG
73 gnutls_global_set_log_level(10);
74 gnutls_global_set_log_function(debug_log);
75#endif
76
77 globalInitDone = true;
78 }
79}
80
Adam Tkac3c5be392010-07-21 09:27:34 +000081CSecurityTLS::CSecurityTLS(bool _anon) : session(0), anon_cred(0),
Adam Tkac0e61c342010-07-21 09:23:25 +000082 anon(_anon), fis(0), fos(0)
Adam Tkacb10489b2010-04-23 14:16:04 +000083{
Adam Tkac0e61c342010-07-21 09:23:25 +000084 cafile = x509ca.getData();
85 crlfile = x509crl.getData();
Adam Tkacb10489b2010-04-23 14:16:04 +000086}
87
Adam Tkac27b2f772010-11-18 13:33:57 +000088void CSecurityTLS::setDefaults()
89{
90 char* homeDir = NULL;
91
Adam Tkacaf081722011-02-07 10:45:15 +000092 if (getvnchomedir(&homeDir) == -1) {
93 vlog.error("Could not obtain VNC home directory path");
Adam Tkac27b2f772010-11-18 13:33:57 +000094 return;
95 }
96
Adam Tkacaf081722011-02-07 10:45:15 +000097 int len = strlen(homeDir) + 1;
98 CharArray caDefault(len + 7);
99 CharArray crlDefault(len + 8);
100 sprintf(caDefault.buf, "%sx509_ca", homeDir);
101 sprintf(crlDefault.buf, "%s509_crl", homeDir);
Adam Tkac27b2f772010-11-18 13:33:57 +0000102 delete [] homeDir;
103
Adam Tkacc4674db2011-01-19 14:11:16 +0000104 x509ca.setDefaultStr(strdup(caDefault.buf));
Adam Tkacaf081722011-02-07 10:45:15 +0000105 x509crl.setDefaultStr(strdup(crlDefault.buf));
Adam Tkac27b2f772010-11-18 13:33:57 +0000106}
107
Adam Tkac3c5be392010-07-21 09:27:34 +0000108void CSecurityTLS::shutdown()
Adam Tkacb10489b2010-04-23 14:16:04 +0000109{
Adam Tkac0e61c342010-07-21 09:23:25 +0000110 if (session)
Adam Tkac6948ead2010-08-11 15:58:59 +0000111 if (gnutls_bye(session, GNUTLS_SHUT_RDWR) != GNUTLS_E_SUCCESS)
112 throw Exception("gnutls_bye failed");
Adam Tkac0e61c342010-07-21 09:23:25 +0000113
114 if (anon_cred) {
115 gnutls_anon_free_client_credentials(anon_cred);
116 anon_cred = 0;
117 }
118
119 if (cert_cred) {
120 gnutls_certificate_free_credentials(cert_cred);
121 cert_cred = 0;
122 }
123
124 if (session) {
125 gnutls_deinit(session);
126 session = 0;
127
128 gnutls_global_deinit();
129 }
Adam Tkacb10489b2010-04-23 14:16:04 +0000130}
131
132
Adam Tkac3c5be392010-07-21 09:27:34 +0000133CSecurityTLS::~CSecurityTLS()
Adam Tkacb10489b2010-04-23 14:16:04 +0000134{
Adam Tkac0e61c342010-07-21 09:23:25 +0000135 shutdown();
136
Adam Tkacb10489b2010-04-23 14:16:04 +0000137 if (fis)
138 delete fis;
139 if (fos)
140 delete fos;
Adam Tkac0e61c342010-07-21 09:23:25 +0000141
142 delete[] cafile;
143 delete[] crlfile;
Adam Tkacb10489b2010-04-23 14:16:04 +0000144}
145
Adam Tkac3c5be392010-07-21 09:27:34 +0000146bool CSecurityTLS::processMsg(CConnection* cc)
Adam Tkacb10489b2010-04-23 14:16:04 +0000147{
148 rdr::InStream* is = cc->getInStream();
149 rdr::OutStream* os = cc->getOutStream();
150 client = cc;
151
152 initGlobal();
153
154 if (!session) {
155 if (!is->checkNoWait(1))
156 return false;
157
158 if (is->readU8() == 0)
159 return true;
160
Adam Tkac6948ead2010-08-11 15:58:59 +0000161 if (gnutls_init(&session, GNUTLS_CLIENT) != GNUTLS_E_SUCCESS)
162 throw AuthFailureException("gnutls_init failed");
163
164 if (gnutls_set_default_priority(session) != GNUTLS_E_SUCCESS)
165 throw AuthFailureException("gnutls_set_default_priority failed");
Adam Tkacb10489b2010-04-23 14:16:04 +0000166
Adam Tkac0e61c342010-07-21 09:23:25 +0000167 setParam();
Adam Tkacb10489b2010-04-23 14:16:04 +0000168
169 gnutls_transport_set_pull_function(session, rdr::gnutls_InStream_pull);
170 gnutls_transport_set_push_function(session, rdr::gnutls_OutStream_push);
171 gnutls_transport_set_ptr2(session,
172 (gnutls_transport_ptr) is,
173 (gnutls_transport_ptr) os);
174 }
175
176 int err;
177 err = gnutls_handshake(session);
178 if (err != GNUTLS_E_SUCCESS && !gnutls_error_is_fatal(err))
179 return false;
180
181 if (err != GNUTLS_E_SUCCESS) {
182 vlog.error("TLS Handshake failed: %s\n", gnutls_strerror (err));
Adam Tkac0e61c342010-07-21 09:23:25 +0000183 shutdown();
Adam Tkacb10489b2010-04-23 14:16:04 +0000184 throw AuthFailureException("TLS Handshake failed");
185 }
Adam Tkac0e61c342010-07-21 09:23:25 +0000186
187 checkSession();
Adam Tkacb10489b2010-04-23 14:16:04 +0000188
189 cc->setStreams(fis = new rdr::TLSInStream(is, session),
190 fos = new rdr::TLSOutStream(os, session));
191
192 return true;
193}
194
Adam Tkac3c5be392010-07-21 09:27:34 +0000195void CSecurityTLS::setParam()
Adam Tkac0e61c342010-07-21 09:23:25 +0000196{
197 static const int kx_anon_priority[] = { GNUTLS_KX_ANON_DH, 0 };
198 static const int kx_priority[] = { GNUTLS_KX_DHE_DSS, GNUTLS_KX_RSA,
199 GNUTLS_KX_DHE_RSA, GNUTLS_KX_SRP, 0 };
200
201 if (anon) {
Adam Tkac6948ead2010-08-11 15:58:59 +0000202 if (gnutls_kx_set_priority(session, kx_anon_priority) != GNUTLS_E_SUCCESS)
203 throw AuthFailureException("gnutls_kx_set_priority failed");
204
205 if (gnutls_anon_allocate_client_credentials(&anon_cred) != GNUTLS_E_SUCCESS)
206 throw AuthFailureException("gnutls_anon_allocate_client_credentials failed");
207
208 if (gnutls_credentials_set(session, GNUTLS_CRD_ANON, anon_cred) != GNUTLS_E_SUCCESS)
209 throw AuthFailureException("gnutls_credentials_set failed");
Adam Tkac0e61c342010-07-21 09:23:25 +0000210
211 vlog.debug("Anonymous session has been set");
212 } else {
Adam Tkac6948ead2010-08-11 15:58:59 +0000213 if (gnutls_kx_set_priority(session, kx_priority) != GNUTLS_E_SUCCESS)
214 throw AuthFailureException("gnutls_kx_set_priority failed");
215
216 if (gnutls_certificate_allocate_credentials(&cert_cred) != GNUTLS_E_SUCCESS)
217 throw AuthFailureException("gnutls_certificate_allocate_credentials failed");
Adam Tkac0e61c342010-07-21 09:23:25 +0000218
219 if (*cafile && gnutls_certificate_set_x509_trust_file(cert_cred,cafile,GNUTLS_X509_FMT_PEM) < 0)
220 throw AuthFailureException("load of CA cert failed");
221
222 if (*crlfile && gnutls_certificate_set_x509_crl_file(cert_cred,crlfile,GNUTLS_X509_FMT_PEM) < 0)
223 throw AuthFailureException("load of CRL failed");
224
Adam Tkac6948ead2010-08-11 15:58:59 +0000225 if (gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, cert_cred) != GNUTLS_E_SUCCESS)
226 throw AuthFailureException("gnutls_credentials_set failed");
Adam Tkac0e61c342010-07-21 09:23:25 +0000227
228 vlog.debug("X509 session has been set");
229 }
230}
231
Adam Tkac3c5be392010-07-21 09:27:34 +0000232void CSecurityTLS::checkSession()
Adam Tkac0e61c342010-07-21 09:23:25 +0000233{
234 int status;
235 const gnutls_datum *cert_list;
236 unsigned int cert_list_size = 0;
237 unsigned int i;
Adam Tkac27b2f772010-11-18 13:33:57 +0000238 gnutls_datum_t info;
Adam Tkac0e61c342010-07-21 09:23:25 +0000239
240 if (anon)
241 return;
242
243 if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509)
244 throw AuthFailureException("unsupported certificate type");
245
246 cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
247 if (!cert_list_size)
248 throw AuthFailureException("unsupported certificate type");
249
250 status = gnutls_certificate_verify_peers(session);
251 if (status == GNUTLS_E_NO_CERTIFICATE_FOUND)
252 throw AuthFailureException("no certificate sent");
253
254 if (status < 0) {
255 vlog.error("X509 verify failed: %s\n", gnutls_strerror (status));
256 throw AuthFailureException("certificate verification failed");
257 }
258
Adam Tkac27b2f772010-11-18 13:33:57 +0000259 if (status & GNUTLS_CERT_REVOKED) {
260 throw AuthFailureException("certificate has been revoked");
261 }
Adam Tkac0e61c342010-07-21 09:23:25 +0000262
Adam Tkac27b2f772010-11-18 13:33:57 +0000263 if (status & GNUTLS_CERT_NOT_ACTIVATED) {
264 throw AuthFailureException("certificate has not been activated");
265 }
Adam Tkac0e61c342010-07-21 09:23:25 +0000266
267 for (i = 0; i < cert_list_size; i++) {
268 gnutls_x509_crt crt;
269 gnutls_x509_crt_init(&crt);
270
271 if (gnutls_x509_crt_import(crt, &cert_list[i],GNUTLS_X509_FMT_DER) < 0)
Adam Tkac27b2f772010-11-18 13:33:57 +0000272 throw AuthFailureException("decoding of certificate failed");
273
274 if (gnutls_x509_crt_print(crt, GNUTLS_CRT_PRINT_ONELINE, &info)) {
Adam Tkac5d4c6ac2011-01-19 14:06:48 +0000275 /*
276 * GNUTLS doesn't correctly export gnutls_free symbol which is
277 * a function pointer. Linking with Visual Studio 2008 Express will
278 * fail when you call gnutls_free().
279 */
280#if WIN32
281 free(info.data);
282#else
Adam Tkac27b2f772010-11-18 13:33:57 +0000283 gnutls_free(info.data);
Adam Tkac5d4c6ac2011-01-19 14:06:48 +0000284#endif
Adam Tkac27b2f772010-11-18 13:33:57 +0000285 throw AuthFailureException("Could not find certificate to display");
286 }
Adam Tkac0e61c342010-07-21 09:23:25 +0000287
288 if (gnutls_x509_crt_check_hostname(crt, client->getServerName()) == 0) {
Adam Tkac27b2f772010-11-18 13:33:57 +0000289 char buf[255];
290 sprintf(buf, "Hostname (%s) does not match any certificate, do you want to continue?", client->getServerName());
291 vlog.debug("hostname mismatch");
292 if(!msg->showMsgBox(UserMsgBox::M_YESNO, "hostname mismatch", buf))
293 throw AuthFailureException("hostname mismatch");
Adam Tkac0e61c342010-07-21 09:23:25 +0000294 }
Adam Tkac27b2f772010-11-18 13:33:57 +0000295
296 if (status & GNUTLS_CERT_EXPIRED) {
297 vlog.debug("certificate has expired");
298 if (!msg->showMsgBox(UserMsgBox::M_YESNO, "certficate has expired", "The certificate of the server has expired, do you want to continue?"))
299 throw AuthFailureException("certificate has expired");
300 }
301
302 if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) {
303 size_t out_size;
304 char *homeDir = NULL;
305 char *out_buf = NULL;
306 char *certinfo = NULL;
307 int len = 0;
308
309 vlog.debug("certificate issuer unknown");
310
311 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);
312 if (len < 0)
313 AuthFailureException("certificate decoding error");
314
315 vlog.debug("%s", info.data);
316
317 certinfo = new char[len];
318 if (certinfo == NULL)
319 throw AuthFailureException("Out of memory");
320
321 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);
322
323 for (int i = 0; i < len - 1; i++)
324 if (certinfo[i] == ',' && certinfo[i + 1] == ' ')
325 certinfo[i] = '\n';
326
327 if (!msg->showMsgBox(UserMsgBox::M_YESNO, "certificate issuer unknown",
328 certinfo)) {
329 delete [] certinfo;
330 throw AuthFailureException("certificate issuer unknown");
331 }
332 delete [] certinfo;
333
334 if (gnutls_x509_crt_export(crt, GNUTLS_X509_FMT_PEM, NULL, &out_size)
335 == GNUTLS_E_SHORT_MEMORY_BUFFER)
336 AuthFailureException("Out of memory");
337
338 // Save cert
339 out_buf = new char[out_size];
340 if (out_buf == NULL)
341 AuthFailureException("Out of memory");
342
343 if (gnutls_x509_crt_export(crt, GNUTLS_X509_FMT_PEM, out_buf, &out_size)
344 < 0)
345 AuthFailureException("certificate issuer unknown, and certificate "
346 "export failed");
Adam Tkacaf081722011-02-07 10:45:15 +0000347
348 if (getvnchomedir(&homeDir) == -1)
349 vlog.error("Could not obtain VNC home directory path");
Adam Tkac27b2f772010-11-18 13:33:57 +0000350 else {
351 FILE *f;
Adam Tkacaf081722011-02-07 10:45:15 +0000352 CharArray caSave(strlen(homeDir) + 11);
353 sprintf(caSave.buf, "%sx509_certs", homeDir);
Adam Tkac27b2f772010-11-18 13:33:57 +0000354 delete [] homeDir;
355 f = fopen(caSave.buf, "a+");
356 if (!f)
357 msg->showMsgBox(UserMsgBox::M_OK, "certificate save failed",
358 "Could not save the certificate");
359 else {
360 fprintf(f, "%s\n", out_buf);
361 fclose(f);
362 }
363 }
364 delete [] out_buf;
365 } else if (status & GNUTLS_CERT_INVALID)
366 throw AuthFailureException("certificate not trusted");
367
Adam Tkac0e61c342010-07-21 09:23:25 +0000368 gnutls_x509_crt_deinit(crt);
Adam Tkac5d4c6ac2011-01-19 14:06:48 +0000369 /*
370 * GNUTLS doesn't correctly export gnutls_free symbol which is
371 * a function pointer. Linking with Visual Studio 2008 Express will
372 * fail when you call gnutls_free().
373 */
374#if WIN32
375 free(info.data);
376#else
Adam Tkac27b2f772010-11-18 13:33:57 +0000377 gnutls_free(info.data);
Adam Tkac5d4c6ac2011-01-19 14:06:48 +0000378#endif
Adam Tkac0e61c342010-07-21 09:23:25 +0000379 }
380}
381