blob: d0b689a265feeba8242b6d30e48fa0c8324c4629 [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;
Adam Tkac437b0c22011-02-07 10:46:16 +000098 CharArray caDefault(len + 11);
99 CharArray crlDefault(len + 12);
100 sprintf(caDefault.buf, "%sx509_ca.pem", homeDir);
101 sprintf(crlDefault.buf, "%s509_crl.pem", homeDir);
Adam Tkac27b2f772010-11-18 13:33:57 +0000102 delete [] homeDir;
103
Adam Tkacf16a4212011-02-07 10:47:07 +0000104 if (!fileexists(caDefault.buf))
105 x509ca.setDefaultStr(strdup(caDefault.buf));
106 if (!fileexists(crlDefault.buf))
107 x509crl.setDefaultStr(strdup(crlDefault.buf));
Adam Tkac27b2f772010-11-18 13:33:57 +0000108}
109
Adam Tkac3c5be392010-07-21 09:27:34 +0000110void CSecurityTLS::shutdown()
Adam Tkacb10489b2010-04-23 14:16:04 +0000111{
Adam Tkac0e61c342010-07-21 09:23:25 +0000112 if (session)
Adam Tkac6948ead2010-08-11 15:58:59 +0000113 if (gnutls_bye(session, GNUTLS_SHUT_RDWR) != GNUTLS_E_SUCCESS)
114 throw Exception("gnutls_bye failed");
Adam Tkac0e61c342010-07-21 09:23:25 +0000115
116 if (anon_cred) {
117 gnutls_anon_free_client_credentials(anon_cred);
118 anon_cred = 0;
119 }
120
121 if (cert_cred) {
122 gnutls_certificate_free_credentials(cert_cred);
123 cert_cred = 0;
124 }
125
126 if (session) {
127 gnutls_deinit(session);
128 session = 0;
129
130 gnutls_global_deinit();
131 }
Adam Tkacb10489b2010-04-23 14:16:04 +0000132}
133
134
Adam Tkac3c5be392010-07-21 09:27:34 +0000135CSecurityTLS::~CSecurityTLS()
Adam Tkacb10489b2010-04-23 14:16:04 +0000136{
Adam Tkac0e61c342010-07-21 09:23:25 +0000137 shutdown();
138
Adam Tkacb10489b2010-04-23 14:16:04 +0000139 if (fis)
140 delete fis;
141 if (fos)
142 delete fos;
Adam Tkac0e61c342010-07-21 09:23:25 +0000143
144 delete[] cafile;
145 delete[] crlfile;
Adam Tkacb10489b2010-04-23 14:16:04 +0000146}
147
Adam Tkac3c5be392010-07-21 09:27:34 +0000148bool CSecurityTLS::processMsg(CConnection* cc)
Adam Tkacb10489b2010-04-23 14:16:04 +0000149{
150 rdr::InStream* is = cc->getInStream();
151 rdr::OutStream* os = cc->getOutStream();
152 client = cc;
153
154 initGlobal();
155
156 if (!session) {
157 if (!is->checkNoWait(1))
158 return false;
159
160 if (is->readU8() == 0)
161 return true;
162
Adam Tkac6948ead2010-08-11 15:58:59 +0000163 if (gnutls_init(&session, GNUTLS_CLIENT) != GNUTLS_E_SUCCESS)
164 throw AuthFailureException("gnutls_init failed");
165
166 if (gnutls_set_default_priority(session) != GNUTLS_E_SUCCESS)
167 throw AuthFailureException("gnutls_set_default_priority failed");
Adam Tkacb10489b2010-04-23 14:16:04 +0000168
Adam Tkac0e61c342010-07-21 09:23:25 +0000169 setParam();
Adam Tkacb10489b2010-04-23 14:16:04 +0000170
171 gnutls_transport_set_pull_function(session, rdr::gnutls_InStream_pull);
172 gnutls_transport_set_push_function(session, rdr::gnutls_OutStream_push);
173 gnutls_transport_set_ptr2(session,
174 (gnutls_transport_ptr) is,
175 (gnutls_transport_ptr) os);
176 }
177
178 int err;
179 err = gnutls_handshake(session);
180 if (err != GNUTLS_E_SUCCESS && !gnutls_error_is_fatal(err))
181 return false;
182
183 if (err != GNUTLS_E_SUCCESS) {
184 vlog.error("TLS Handshake failed: %s\n", gnutls_strerror (err));
Adam Tkac0e61c342010-07-21 09:23:25 +0000185 shutdown();
Adam Tkacb10489b2010-04-23 14:16:04 +0000186 throw AuthFailureException("TLS Handshake failed");
187 }
Adam Tkac0e61c342010-07-21 09:23:25 +0000188
189 checkSession();
Adam Tkacb10489b2010-04-23 14:16:04 +0000190
191 cc->setStreams(fis = new rdr::TLSInStream(is, session),
192 fos = new rdr::TLSOutStream(os, session));
193
194 return true;
195}
196
Adam Tkac3c5be392010-07-21 09:27:34 +0000197void CSecurityTLS::setParam()
Adam Tkac0e61c342010-07-21 09:23:25 +0000198{
199 static const int kx_anon_priority[] = { GNUTLS_KX_ANON_DH, 0 };
200 static const int kx_priority[] = { GNUTLS_KX_DHE_DSS, GNUTLS_KX_RSA,
201 GNUTLS_KX_DHE_RSA, GNUTLS_KX_SRP, 0 };
202
203 if (anon) {
Adam Tkac6948ead2010-08-11 15:58:59 +0000204 if (gnutls_kx_set_priority(session, kx_anon_priority) != GNUTLS_E_SUCCESS)
205 throw AuthFailureException("gnutls_kx_set_priority failed");
206
207 if (gnutls_anon_allocate_client_credentials(&anon_cred) != GNUTLS_E_SUCCESS)
208 throw AuthFailureException("gnutls_anon_allocate_client_credentials failed");
209
210 if (gnutls_credentials_set(session, GNUTLS_CRD_ANON, anon_cred) != GNUTLS_E_SUCCESS)
211 throw AuthFailureException("gnutls_credentials_set failed");
Adam Tkac0e61c342010-07-21 09:23:25 +0000212
213 vlog.debug("Anonymous session has been set");
214 } else {
Adam Tkac6948ead2010-08-11 15:58:59 +0000215 if (gnutls_kx_set_priority(session, kx_priority) != GNUTLS_E_SUCCESS)
216 throw AuthFailureException("gnutls_kx_set_priority failed");
217
218 if (gnutls_certificate_allocate_credentials(&cert_cred) != GNUTLS_E_SUCCESS)
219 throw AuthFailureException("gnutls_certificate_allocate_credentials failed");
Adam Tkac0e61c342010-07-21 09:23:25 +0000220
221 if (*cafile && gnutls_certificate_set_x509_trust_file(cert_cred,cafile,GNUTLS_X509_FMT_PEM) < 0)
222 throw AuthFailureException("load of CA cert failed");
223
224 if (*crlfile && gnutls_certificate_set_x509_crl_file(cert_cred,crlfile,GNUTLS_X509_FMT_PEM) < 0)
225 throw AuthFailureException("load of CRL failed");
226
Adam Tkac6948ead2010-08-11 15:58:59 +0000227 if (gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, cert_cred) != GNUTLS_E_SUCCESS)
228 throw AuthFailureException("gnutls_credentials_set failed");
Adam Tkac0e61c342010-07-21 09:23:25 +0000229
230 vlog.debug("X509 session has been set");
231 }
232}
233
Adam Tkac3c5be392010-07-21 09:27:34 +0000234void CSecurityTLS::checkSession()
Adam Tkac0e61c342010-07-21 09:23:25 +0000235{
236 int status;
237 const gnutls_datum *cert_list;
238 unsigned int cert_list_size = 0;
239 unsigned int i;
Adam Tkac27b2f772010-11-18 13:33:57 +0000240 gnutls_datum_t info;
Adam Tkac0e61c342010-07-21 09:23:25 +0000241
242 if (anon)
243 return;
244
245 if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509)
246 throw AuthFailureException("unsupported certificate type");
247
248 cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
249 if (!cert_list_size)
250 throw AuthFailureException("unsupported certificate type");
251
252 status = gnutls_certificate_verify_peers(session);
253 if (status == GNUTLS_E_NO_CERTIFICATE_FOUND)
254 throw AuthFailureException("no certificate sent");
255
256 if (status < 0) {
257 vlog.error("X509 verify failed: %s\n", gnutls_strerror (status));
258 throw AuthFailureException("certificate verification failed");
259 }
260
Adam Tkac27b2f772010-11-18 13:33:57 +0000261 if (status & GNUTLS_CERT_REVOKED) {
262 throw AuthFailureException("certificate has been revoked");
263 }
Adam Tkac0e61c342010-07-21 09:23:25 +0000264
Adam Tkac27b2f772010-11-18 13:33:57 +0000265 if (status & GNUTLS_CERT_NOT_ACTIVATED) {
266 throw AuthFailureException("certificate has not been activated");
267 }
Adam Tkac0e61c342010-07-21 09:23:25 +0000268
269 for (i = 0; i < cert_list_size; i++) {
270 gnutls_x509_crt crt;
271 gnutls_x509_crt_init(&crt);
272
273 if (gnutls_x509_crt_import(crt, &cert_list[i],GNUTLS_X509_FMT_DER) < 0)
Adam Tkac27b2f772010-11-18 13:33:57 +0000274 throw AuthFailureException("decoding of certificate failed");
275
276 if (gnutls_x509_crt_print(crt, GNUTLS_CRT_PRINT_ONELINE, &info)) {
Adam Tkac5d4c6ac2011-01-19 14:06:48 +0000277 /*
278 * GNUTLS doesn't correctly export gnutls_free symbol which is
279 * a function pointer. Linking with Visual Studio 2008 Express will
280 * fail when you call gnutls_free().
281 */
282#if WIN32
283 free(info.data);
284#else
Adam Tkac27b2f772010-11-18 13:33:57 +0000285 gnutls_free(info.data);
Adam Tkac5d4c6ac2011-01-19 14:06:48 +0000286#endif
Adam Tkac27b2f772010-11-18 13:33:57 +0000287 throw AuthFailureException("Could not find certificate to display");
288 }
Adam Tkac0e61c342010-07-21 09:23:25 +0000289
290 if (gnutls_x509_crt_check_hostname(crt, client->getServerName()) == 0) {
Adam Tkac27b2f772010-11-18 13:33:57 +0000291 char buf[255];
292 sprintf(buf, "Hostname (%s) does not match any certificate, do you want to continue?", client->getServerName());
293 vlog.debug("hostname mismatch");
294 if(!msg->showMsgBox(UserMsgBox::M_YESNO, "hostname mismatch", buf))
295 throw AuthFailureException("hostname mismatch");
Adam Tkac0e61c342010-07-21 09:23:25 +0000296 }
Adam Tkac27b2f772010-11-18 13:33:57 +0000297
298 if (status & GNUTLS_CERT_EXPIRED) {
299 vlog.debug("certificate has expired");
300 if (!msg->showMsgBox(UserMsgBox::M_YESNO, "certficate has expired", "The certificate of the server has expired, do you want to continue?"))
301 throw AuthFailureException("certificate has expired");
302 }
303
304 if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) {
305 size_t out_size;
306 char *homeDir = NULL;
307 char *out_buf = NULL;
308 char *certinfo = NULL;
309 int len = 0;
310
311 vlog.debug("certificate issuer unknown");
312
313 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);
314 if (len < 0)
315 AuthFailureException("certificate decoding error");
316
317 vlog.debug("%s", info.data);
318
319 certinfo = new char[len];
320 if (certinfo == NULL)
321 throw AuthFailureException("Out of memory");
322
323 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);
324
325 for (int i = 0; i < len - 1; i++)
326 if (certinfo[i] == ',' && certinfo[i + 1] == ' ')
327 certinfo[i] = '\n';
328
329 if (!msg->showMsgBox(UserMsgBox::M_YESNO, "certificate issuer unknown",
330 certinfo)) {
331 delete [] certinfo;
332 throw AuthFailureException("certificate issuer unknown");
333 }
334 delete [] certinfo;
335
336 if (gnutls_x509_crt_export(crt, GNUTLS_X509_FMT_PEM, NULL, &out_size)
337 == GNUTLS_E_SHORT_MEMORY_BUFFER)
338 AuthFailureException("Out of memory");
339
340 // Save cert
341 out_buf = new char[out_size];
342 if (out_buf == NULL)
343 AuthFailureException("Out of memory");
344
345 if (gnutls_x509_crt_export(crt, GNUTLS_X509_FMT_PEM, out_buf, &out_size)
346 < 0)
347 AuthFailureException("certificate issuer unknown, and certificate "
348 "export failed");
Adam Tkacaf081722011-02-07 10:45:15 +0000349
350 if (getvnchomedir(&homeDir) == -1)
351 vlog.error("Could not obtain VNC home directory path");
Adam Tkac27b2f772010-11-18 13:33:57 +0000352 else {
353 FILE *f;
Adam Tkac437b0c22011-02-07 10:46:16 +0000354 CharArray caSave(strlen(homeDir) + 1 + 19);
355 sprintf(caSave.buf, "%sx509_savedcerts.pem", homeDir);
Adam Tkac27b2f772010-11-18 13:33:57 +0000356 delete [] homeDir;
357 f = fopen(caSave.buf, "a+");
358 if (!f)
359 msg->showMsgBox(UserMsgBox::M_OK, "certificate save failed",
360 "Could not save the certificate");
361 else {
362 fprintf(f, "%s\n", out_buf);
363 fclose(f);
364 }
365 }
366 delete [] out_buf;
367 } else if (status & GNUTLS_CERT_INVALID)
368 throw AuthFailureException("certificate not trusted");
369
Adam Tkac0e61c342010-07-21 09:23:25 +0000370 gnutls_x509_crt_deinit(crt);
Adam Tkac5d4c6ac2011-01-19 14:06:48 +0000371 /*
372 * GNUTLS doesn't correctly export gnutls_free symbol which is
373 * a function pointer. Linking with Visual Studio 2008 Express will
374 * fail when you call gnutls_free().
375 */
376#if WIN32
377 free(info.data);
378#else
Adam Tkac27b2f772010-11-18 13:33:57 +0000379 gnutls_free(info.data);
Adam Tkac5d4c6ac2011-01-19 14:06:48 +0000380#endif
Adam Tkac0e61c342010-07-21 09:23:25 +0000381 }
382}
383