blob: 651f8521cb1884bf27566cf78fe91f0f2ccf68d4 [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
5 *
6 * This 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 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This software 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 software; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
19 * USA.
20 */
21
22#ifdef HAVE_CONFIG_H
23#include <config.h>
24#endif
25
Adam Tkac43958232010-07-21 09:06:59 +000026#ifndef HAVE_GNUTLS
27#error "This header should not be compiled without HAVE_GNUTLS defined"
28#endif
Adam Tkacb10489b2010-04-23 14:16:04 +000029
Adam Tkac3c5be392010-07-21 09:27:34 +000030#include <rfb/CSecurityTLS.h>
Adam Tkac0e61c342010-07-21 09:23:25 +000031#include <rfb/SSecurityVeNCrypt.h>
Adam Tkacb10489b2010-04-23 14:16:04 +000032#include <rfb/CConnection.h>
33#include <rfb/LogWriter.h>
34#include <rfb/Exception.h>
35#include <rdr/TLSInStream.h>
36#include <rdr/TLSOutStream.h>
37
Adam Tkac0e61c342010-07-21 09:23:25 +000038#include <gnutls/x509.h>
39
Adam Tkacb10489b2010-04-23 14:16:04 +000040#define TLS_DEBUG
41
42using namespace rfb;
43
Adam Tkac3c5be392010-07-21 09:27:34 +000044StringParameter CSecurityTLS::x509ca("x509ca", "X509 CA certificate", "", ConfViewer);
45StringParameter CSecurityTLS::x509crl("x509crl", "X509 CRL file", "", ConfViewer);
Adam Tkac0e61c342010-07-21 09:23:25 +000046
Adam Tkacb10489b2010-04-23 14:16:04 +000047static LogWriter vlog("TLS");
48
49#ifdef TLS_DEBUG
50static void debug_log(int level, const char* str)
51{
52 vlog.debug(str);
53}
54#endif
55
Adam Tkac3c5be392010-07-21 09:27:34 +000056void CSecurityTLS::initGlobal()
Adam Tkacb10489b2010-04-23 14:16:04 +000057{
58 static bool globalInitDone = false;
59
60 if (!globalInitDone) {
61 gnutls_global_init();
62
63#ifdef TLS_DEBUG
64 gnutls_global_set_log_level(10);
65 gnutls_global_set_log_function(debug_log);
66#endif
67
68 globalInitDone = true;
69 }
70}
71
Adam Tkac3c5be392010-07-21 09:27:34 +000072CSecurityTLS::CSecurityTLS(bool _anon) : session(0), anon_cred(0),
Adam Tkac0e61c342010-07-21 09:23:25 +000073 anon(_anon), fis(0), fos(0)
Adam Tkacb10489b2010-04-23 14:16:04 +000074{
Adam Tkac0e61c342010-07-21 09:23:25 +000075 cafile = x509ca.getData();
76 crlfile = x509crl.getData();
Adam Tkacb10489b2010-04-23 14:16:04 +000077}
78
Adam Tkac3c5be392010-07-21 09:27:34 +000079void CSecurityTLS::shutdown()
Adam Tkacb10489b2010-04-23 14:16:04 +000080{
Adam Tkac0e61c342010-07-21 09:23:25 +000081 if (session)
Adam Tkac6948ead2010-08-11 15:58:59 +000082 if (gnutls_bye(session, GNUTLS_SHUT_RDWR) != GNUTLS_E_SUCCESS)
83 throw Exception("gnutls_bye failed");
Adam Tkac0e61c342010-07-21 09:23:25 +000084
85 if (anon_cred) {
86 gnutls_anon_free_client_credentials(anon_cred);
87 anon_cred = 0;
88 }
89
90 if (cert_cred) {
91 gnutls_certificate_free_credentials(cert_cred);
92 cert_cred = 0;
93 }
94
95 if (session) {
96 gnutls_deinit(session);
97 session = 0;
98
99 gnutls_global_deinit();
100 }
Adam Tkacb10489b2010-04-23 14:16:04 +0000101}
102
103
Adam Tkac3c5be392010-07-21 09:27:34 +0000104CSecurityTLS::~CSecurityTLS()
Adam Tkacb10489b2010-04-23 14:16:04 +0000105{
Adam Tkac0e61c342010-07-21 09:23:25 +0000106 shutdown();
107
Adam Tkacb10489b2010-04-23 14:16:04 +0000108 if (fis)
109 delete fis;
110 if (fos)
111 delete fos;
Adam Tkac0e61c342010-07-21 09:23:25 +0000112
113 delete[] cafile;
114 delete[] crlfile;
Adam Tkacb10489b2010-04-23 14:16:04 +0000115}
116
Adam Tkac3c5be392010-07-21 09:27:34 +0000117bool CSecurityTLS::processMsg(CConnection* cc)
Adam Tkacb10489b2010-04-23 14:16:04 +0000118{
119 rdr::InStream* is = cc->getInStream();
120 rdr::OutStream* os = cc->getOutStream();
121 client = cc;
122
123 initGlobal();
124
125 if (!session) {
126 if (!is->checkNoWait(1))
127 return false;
128
129 if (is->readU8() == 0)
130 return true;
131
Adam Tkac6948ead2010-08-11 15:58:59 +0000132 if (gnutls_init(&session, GNUTLS_CLIENT) != GNUTLS_E_SUCCESS)
133 throw AuthFailureException("gnutls_init failed");
134
135 if (gnutls_set_default_priority(session) != GNUTLS_E_SUCCESS)
136 throw AuthFailureException("gnutls_set_default_priority failed");
Adam Tkacb10489b2010-04-23 14:16:04 +0000137
Adam Tkac0e61c342010-07-21 09:23:25 +0000138 setParam();
Adam Tkacb10489b2010-04-23 14:16:04 +0000139
140 gnutls_transport_set_pull_function(session, rdr::gnutls_InStream_pull);
141 gnutls_transport_set_push_function(session, rdr::gnutls_OutStream_push);
142 gnutls_transport_set_ptr2(session,
143 (gnutls_transport_ptr) is,
144 (gnutls_transport_ptr) os);
145 }
146
147 int err;
148 err = gnutls_handshake(session);
149 if (err != GNUTLS_E_SUCCESS && !gnutls_error_is_fatal(err))
150 return false;
151
152 if (err != GNUTLS_E_SUCCESS) {
153 vlog.error("TLS Handshake failed: %s\n", gnutls_strerror (err));
Adam Tkac0e61c342010-07-21 09:23:25 +0000154 shutdown();
Adam Tkacb10489b2010-04-23 14:16:04 +0000155 throw AuthFailureException("TLS Handshake failed");
156 }
Adam Tkac0e61c342010-07-21 09:23:25 +0000157
158 checkSession();
Adam Tkacb10489b2010-04-23 14:16:04 +0000159
160 cc->setStreams(fis = new rdr::TLSInStream(is, session),
161 fos = new rdr::TLSOutStream(os, session));
162
163 return true;
164}
165
Adam Tkac3c5be392010-07-21 09:27:34 +0000166void CSecurityTLS::setParam()
Adam Tkac0e61c342010-07-21 09:23:25 +0000167{
168 static const int kx_anon_priority[] = { GNUTLS_KX_ANON_DH, 0 };
169 static const int kx_priority[] = { GNUTLS_KX_DHE_DSS, GNUTLS_KX_RSA,
170 GNUTLS_KX_DHE_RSA, GNUTLS_KX_SRP, 0 };
171
172 if (anon) {
Adam Tkac6948ead2010-08-11 15:58:59 +0000173 if (gnutls_kx_set_priority(session, kx_anon_priority) != GNUTLS_E_SUCCESS)
174 throw AuthFailureException("gnutls_kx_set_priority failed");
175
176 if (gnutls_anon_allocate_client_credentials(&anon_cred) != GNUTLS_E_SUCCESS)
177 throw AuthFailureException("gnutls_anon_allocate_client_credentials failed");
178
179 if (gnutls_credentials_set(session, GNUTLS_CRD_ANON, anon_cred) != GNUTLS_E_SUCCESS)
180 throw AuthFailureException("gnutls_credentials_set failed");
Adam Tkac0e61c342010-07-21 09:23:25 +0000181
182 vlog.debug("Anonymous session has been set");
183 } else {
Adam Tkac6948ead2010-08-11 15:58:59 +0000184 if (gnutls_kx_set_priority(session, kx_priority) != GNUTLS_E_SUCCESS)
185 throw AuthFailureException("gnutls_kx_set_priority failed");
186
187 if (gnutls_certificate_allocate_credentials(&cert_cred) != GNUTLS_E_SUCCESS)
188 throw AuthFailureException("gnutls_certificate_allocate_credentials failed");
Adam Tkac0e61c342010-07-21 09:23:25 +0000189
190 if (*cafile && gnutls_certificate_set_x509_trust_file(cert_cred,cafile,GNUTLS_X509_FMT_PEM) < 0)
191 throw AuthFailureException("load of CA cert failed");
192
193 if (*crlfile && gnutls_certificate_set_x509_crl_file(cert_cred,crlfile,GNUTLS_X509_FMT_PEM) < 0)
194 throw AuthFailureException("load of CRL failed");
195
Adam Tkac6948ead2010-08-11 15:58:59 +0000196 if (gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, cert_cred) != GNUTLS_E_SUCCESS)
197 throw AuthFailureException("gnutls_credentials_set failed");
Adam Tkac0e61c342010-07-21 09:23:25 +0000198
199 vlog.debug("X509 session has been set");
200 }
201}
202
Adam Tkac3c5be392010-07-21 09:27:34 +0000203void CSecurityTLS::checkSession()
Adam Tkac0e61c342010-07-21 09:23:25 +0000204{
205 int status;
206 const gnutls_datum *cert_list;
207 unsigned int cert_list_size = 0;
208 unsigned int i;
209
210 if (anon)
211 return;
212
213 if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509)
214 throw AuthFailureException("unsupported certificate type");
215
216 cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
217 if (!cert_list_size)
218 throw AuthFailureException("unsupported certificate type");
219
220 status = gnutls_certificate_verify_peers(session);
221 if (status == GNUTLS_E_NO_CERTIFICATE_FOUND)
222 throw AuthFailureException("no certificate sent");
223
224 if (status < 0) {
225 vlog.error("X509 verify failed: %s\n", gnutls_strerror (status));
226 throw AuthFailureException("certificate verification failed");
227 }
228
229 if (status & GNUTLS_CERT_SIGNER_NOT_FOUND)
230 throw AuthFailureException("certificate issuer unknown");
231
232 if (status & GNUTLS_CERT_INVALID)
233 throw AuthFailureException("certificate not trusted");
234
235 for (i = 0; i < cert_list_size; i++) {
236 gnutls_x509_crt crt;
237 gnutls_x509_crt_init(&crt);
238
239 if (gnutls_x509_crt_import(crt, &cert_list[i],GNUTLS_X509_FMT_DER) < 0)
240 throw AuthFailureException("Decoding of certificate failed");
241
242 if (gnutls_x509_crt_check_hostname(crt, client->getServerName()) == 0) {
243#if 0
244 throw AuthFailureException("Hostname mismatch"); /* Non-fatal for now... */
245#endif
246 }
247 gnutls_x509_crt_deinit(crt);
248 }
249}
250