blob: 884086860de7ff945af9736069334105832a739d [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
92 if (gethomedir(&homeDir) == -1) {
93 vlog.error("Could not obtain home directory path");
94 return;
95 }
96
97 CharArray caDefault(strlen(homeDir)+17);
98 sprintf(caDefault.buf, "%s/.vnc/x509_certs", homeDir);
99 delete [] homeDir;
100
Adam Tkacc4674db2011-01-19 14:11:16 +0000101#ifndef WIN32
Adam Tkac27b2f772010-11-18 13:33:57 +0000102 /* XXX Do we need access() check here? */
103 if (!access(caDefault.buf, R_OK))
104 x509ca.setDefaultStr(strdup(caDefault.buf));
105 else
106 vlog.error("Failed to open ~/.vnc/x509_certs");
Adam Tkacc4674db2011-01-19 14:11:16 +0000107#else
108 /* Windows doesn't have access() function. */
109 x509ca.setDefaultStr(strdup(caDefault.buf));
110#endif
Adam Tkac27b2f772010-11-18 13:33:57 +0000111}
112
Adam Tkac3c5be392010-07-21 09:27:34 +0000113void CSecurityTLS::shutdown()
Adam Tkacb10489b2010-04-23 14:16:04 +0000114{
Adam Tkac0e61c342010-07-21 09:23:25 +0000115 if (session)
Adam Tkac6948ead2010-08-11 15:58:59 +0000116 if (gnutls_bye(session, GNUTLS_SHUT_RDWR) != GNUTLS_E_SUCCESS)
117 throw Exception("gnutls_bye failed");
Adam Tkac0e61c342010-07-21 09:23:25 +0000118
119 if (anon_cred) {
120 gnutls_anon_free_client_credentials(anon_cred);
121 anon_cred = 0;
122 }
123
124 if (cert_cred) {
125 gnutls_certificate_free_credentials(cert_cred);
126 cert_cred = 0;
127 }
128
129 if (session) {
130 gnutls_deinit(session);
131 session = 0;
132
133 gnutls_global_deinit();
134 }
Adam Tkacb10489b2010-04-23 14:16:04 +0000135}
136
137
Adam Tkac3c5be392010-07-21 09:27:34 +0000138CSecurityTLS::~CSecurityTLS()
Adam Tkacb10489b2010-04-23 14:16:04 +0000139{
Adam Tkac0e61c342010-07-21 09:23:25 +0000140 shutdown();
141
Adam Tkacb10489b2010-04-23 14:16:04 +0000142 if (fis)
143 delete fis;
144 if (fos)
145 delete fos;
Adam Tkac0e61c342010-07-21 09:23:25 +0000146
147 delete[] cafile;
148 delete[] crlfile;
Adam Tkacb10489b2010-04-23 14:16:04 +0000149}
150
Adam Tkac3c5be392010-07-21 09:27:34 +0000151bool CSecurityTLS::processMsg(CConnection* cc)
Adam Tkacb10489b2010-04-23 14:16:04 +0000152{
153 rdr::InStream* is = cc->getInStream();
154 rdr::OutStream* os = cc->getOutStream();
155 client = cc;
156
157 initGlobal();
158
159 if (!session) {
160 if (!is->checkNoWait(1))
161 return false;
162
163 if (is->readU8() == 0)
164 return true;
165
Adam Tkac6948ead2010-08-11 15:58:59 +0000166 if (gnutls_init(&session, GNUTLS_CLIENT) != GNUTLS_E_SUCCESS)
167 throw AuthFailureException("gnutls_init failed");
168
169 if (gnutls_set_default_priority(session) != GNUTLS_E_SUCCESS)
170 throw AuthFailureException("gnutls_set_default_priority failed");
Adam Tkacb10489b2010-04-23 14:16:04 +0000171
Adam Tkac0e61c342010-07-21 09:23:25 +0000172 setParam();
Adam Tkacb10489b2010-04-23 14:16:04 +0000173
174 gnutls_transport_set_pull_function(session, rdr::gnutls_InStream_pull);
175 gnutls_transport_set_push_function(session, rdr::gnutls_OutStream_push);
176 gnutls_transport_set_ptr2(session,
177 (gnutls_transport_ptr) is,
178 (gnutls_transport_ptr) os);
179 }
180
181 int err;
182 err = gnutls_handshake(session);
183 if (err != GNUTLS_E_SUCCESS && !gnutls_error_is_fatal(err))
184 return false;
185
186 if (err != GNUTLS_E_SUCCESS) {
187 vlog.error("TLS Handshake failed: %s\n", gnutls_strerror (err));
Adam Tkac0e61c342010-07-21 09:23:25 +0000188 shutdown();
Adam Tkacb10489b2010-04-23 14:16:04 +0000189 throw AuthFailureException("TLS Handshake failed");
190 }
Adam Tkac0e61c342010-07-21 09:23:25 +0000191
192 checkSession();
Adam Tkacb10489b2010-04-23 14:16:04 +0000193
194 cc->setStreams(fis = new rdr::TLSInStream(is, session),
195 fos = new rdr::TLSOutStream(os, session));
196
197 return true;
198}
199
Adam Tkac3c5be392010-07-21 09:27:34 +0000200void CSecurityTLS::setParam()
Adam Tkac0e61c342010-07-21 09:23:25 +0000201{
202 static const int kx_anon_priority[] = { GNUTLS_KX_ANON_DH, 0 };
203 static const int kx_priority[] = { GNUTLS_KX_DHE_DSS, GNUTLS_KX_RSA,
204 GNUTLS_KX_DHE_RSA, GNUTLS_KX_SRP, 0 };
205
206 if (anon) {
Adam Tkac6948ead2010-08-11 15:58:59 +0000207 if (gnutls_kx_set_priority(session, kx_anon_priority) != GNUTLS_E_SUCCESS)
208 throw AuthFailureException("gnutls_kx_set_priority failed");
209
210 if (gnutls_anon_allocate_client_credentials(&anon_cred) != GNUTLS_E_SUCCESS)
211 throw AuthFailureException("gnutls_anon_allocate_client_credentials failed");
212
213 if (gnutls_credentials_set(session, GNUTLS_CRD_ANON, anon_cred) != GNUTLS_E_SUCCESS)
214 throw AuthFailureException("gnutls_credentials_set failed");
Adam Tkac0e61c342010-07-21 09:23:25 +0000215
216 vlog.debug("Anonymous session has been set");
217 } else {
Adam Tkac6948ead2010-08-11 15:58:59 +0000218 if (gnutls_kx_set_priority(session, kx_priority) != GNUTLS_E_SUCCESS)
219 throw AuthFailureException("gnutls_kx_set_priority failed");
220
221 if (gnutls_certificate_allocate_credentials(&cert_cred) != GNUTLS_E_SUCCESS)
222 throw AuthFailureException("gnutls_certificate_allocate_credentials failed");
Adam Tkac0e61c342010-07-21 09:23:25 +0000223
224 if (*cafile && gnutls_certificate_set_x509_trust_file(cert_cred,cafile,GNUTLS_X509_FMT_PEM) < 0)
225 throw AuthFailureException("load of CA cert failed");
226
227 if (*crlfile && gnutls_certificate_set_x509_crl_file(cert_cred,crlfile,GNUTLS_X509_FMT_PEM) < 0)
228 throw AuthFailureException("load of CRL failed");
229
Adam Tkac6948ead2010-08-11 15:58:59 +0000230 if (gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, cert_cred) != GNUTLS_E_SUCCESS)
231 throw AuthFailureException("gnutls_credentials_set failed");
Adam Tkac0e61c342010-07-21 09:23:25 +0000232
233 vlog.debug("X509 session has been set");
234 }
235}
236
Adam Tkac3c5be392010-07-21 09:27:34 +0000237void CSecurityTLS::checkSession()
Adam Tkac0e61c342010-07-21 09:23:25 +0000238{
239 int status;
240 const gnutls_datum *cert_list;
241 unsigned int cert_list_size = 0;
242 unsigned int i;
Adam Tkac27b2f772010-11-18 13:33:57 +0000243 gnutls_datum_t info;
Adam Tkac0e61c342010-07-21 09:23:25 +0000244
245 if (anon)
246 return;
247
248 if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509)
249 throw AuthFailureException("unsupported certificate type");
250
251 cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
252 if (!cert_list_size)
253 throw AuthFailureException("unsupported certificate type");
254
255 status = gnutls_certificate_verify_peers(session);
256 if (status == GNUTLS_E_NO_CERTIFICATE_FOUND)
257 throw AuthFailureException("no certificate sent");
258
259 if (status < 0) {
260 vlog.error("X509 verify failed: %s\n", gnutls_strerror (status));
261 throw AuthFailureException("certificate verification failed");
262 }
263
Adam Tkac27b2f772010-11-18 13:33:57 +0000264 if (status & GNUTLS_CERT_REVOKED) {
265 throw AuthFailureException("certificate has been revoked");
266 }
Adam Tkac0e61c342010-07-21 09:23:25 +0000267
Adam Tkac27b2f772010-11-18 13:33:57 +0000268 if (status & GNUTLS_CERT_NOT_ACTIVATED) {
269 throw AuthFailureException("certificate has not been activated");
270 }
Adam Tkac0e61c342010-07-21 09:23:25 +0000271
272 for (i = 0; i < cert_list_size; i++) {
273 gnutls_x509_crt crt;
274 gnutls_x509_crt_init(&crt);
275
276 if (gnutls_x509_crt_import(crt, &cert_list[i],GNUTLS_X509_FMT_DER) < 0)
Adam Tkac27b2f772010-11-18 13:33:57 +0000277 throw AuthFailureException("decoding of certificate failed");
278
279 if (gnutls_x509_crt_print(crt, GNUTLS_CRT_PRINT_ONELINE, &info)) {
Adam Tkac5d4c6ac2011-01-19 14:06:48 +0000280 /*
281 * GNUTLS doesn't correctly export gnutls_free symbol which is
282 * a function pointer. Linking with Visual Studio 2008 Express will
283 * fail when you call gnutls_free().
284 */
285#if WIN32
286 free(info.data);
287#else
Adam Tkac27b2f772010-11-18 13:33:57 +0000288 gnutls_free(info.data);
Adam Tkac5d4c6ac2011-01-19 14:06:48 +0000289#endif
Adam Tkac27b2f772010-11-18 13:33:57 +0000290 throw AuthFailureException("Could not find certificate to display");
291 }
Adam Tkac0e61c342010-07-21 09:23:25 +0000292
293 if (gnutls_x509_crt_check_hostname(crt, client->getServerName()) == 0) {
Adam Tkac27b2f772010-11-18 13:33:57 +0000294 char buf[255];
295 sprintf(buf, "Hostname (%s) does not match any certificate, do you want to continue?", client->getServerName());
296 vlog.debug("hostname mismatch");
297 if(!msg->showMsgBox(UserMsgBox::M_YESNO, "hostname mismatch", buf))
298 throw AuthFailureException("hostname mismatch");
Adam Tkac0e61c342010-07-21 09:23:25 +0000299 }
Adam Tkac27b2f772010-11-18 13:33:57 +0000300
301 if (status & GNUTLS_CERT_EXPIRED) {
302 vlog.debug("certificate has expired");
303 if (!msg->showMsgBox(UserMsgBox::M_YESNO, "certficate has expired", "The certificate of the server has expired, do you want to continue?"))
304 throw AuthFailureException("certificate has expired");
305 }
306
307 if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) {
308 size_t out_size;
309 char *homeDir = NULL;
310 char *out_buf = NULL;
311 char *certinfo = NULL;
312 int len = 0;
313
314 vlog.debug("certificate issuer unknown");
315
316 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);
317 if (len < 0)
318 AuthFailureException("certificate decoding error");
319
320 vlog.debug("%s", info.data);
321
322 certinfo = new char[len];
323 if (certinfo == NULL)
324 throw AuthFailureException("Out of memory");
325
326 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);
327
328 for (int i = 0; i < len - 1; i++)
329 if (certinfo[i] == ',' && certinfo[i + 1] == ' ')
330 certinfo[i] = '\n';
331
332 if (!msg->showMsgBox(UserMsgBox::M_YESNO, "certificate issuer unknown",
333 certinfo)) {
334 delete [] certinfo;
335 throw AuthFailureException("certificate issuer unknown");
336 }
337 delete [] certinfo;
338
339 if (gnutls_x509_crt_export(crt, GNUTLS_X509_FMT_PEM, NULL, &out_size)
340 == GNUTLS_E_SHORT_MEMORY_BUFFER)
341 AuthFailureException("Out of memory");
342
343 // Save cert
344 out_buf = new char[out_size];
345 if (out_buf == NULL)
346 AuthFailureException("Out of memory");
347
348 if (gnutls_x509_crt_export(crt, GNUTLS_X509_FMT_PEM, out_buf, &out_size)
349 < 0)
350 AuthFailureException("certificate issuer unknown, and certificate "
351 "export failed");
352
353 if (gethomedir(&homeDir) == -1)
354 vlog.error("Could not obtain home directory path");
355 else {
356 FILE *f;
357 CharArray caSave(strlen(homeDir)+17);
358 sprintf(caSave.buf, "%s/.vnc/x509_certs", homeDir);
359 delete [] homeDir;
360 f = fopen(caSave.buf, "a+");
361 if (!f)
362 msg->showMsgBox(UserMsgBox::M_OK, "certificate save failed",
363 "Could not save the certificate");
364 else {
365 fprintf(f, "%s\n", out_buf);
366 fclose(f);
367 }
368 }
369 delete [] out_buf;
370 } else if (status & GNUTLS_CERT_INVALID)
371 throw AuthFailureException("certificate not trusted");
372
Adam Tkac0e61c342010-07-21 09:23:25 +0000373 gnutls_x509_crt_deinit(crt);
Adam Tkac5d4c6ac2011-01-19 14:06:48 +0000374 /*
375 * GNUTLS doesn't correctly export gnutls_free symbol which is
376 * a function pointer. Linking with Visual Studio 2008 Express will
377 * fail when you call gnutls_free().
378 */
379#if WIN32
380 free(info.data);
381#else
Adam Tkac27b2f772010-11-18 13:33:57 +0000382 gnutls_free(info.data);
Adam Tkac5d4c6ac2011-01-19 14:06:48 +0000383#endif
Adam Tkac0e61c342010-07-21 09:23:25 +0000384 }
385}
386