blob: 72b83db6d4ed8aceb346eb62740fe7e38fadd74e [file] [log] [blame]
Adam Tkacdfe19cf2010-04-23 14:14:11 +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 Tkacdf799702010-04-28 15:45:53 +000026#ifndef HAVE_GNUTLS
27#error "This source should not be compiled without HAVE_GNUTLS defined"
28#endif
Adam Tkacdfe19cf2010-04-23 14:14:11 +000029
Pierre Ossman27eb55e2015-01-29 13:31:06 +010030#include <stdlib.h>
31
Adam Tkac21b61a52010-07-21 09:19:00 +000032#include <rfb/SSecurityTLS.h>
Adam Tkacdfe19cf2010-04-23 14:14:11 +000033#include <rfb/SConnection.h>
34#include <rfb/LogWriter.h>
35#include <rfb/Exception.h>
36#include <rdr/TLSInStream.h>
37#include <rdr/TLSOutStream.h>
Brian P. Hinz4b9b8972017-11-15 05:08:16 -050038#include <gnutls/x509.h>
Adam Tkacdfe19cf2010-04-23 14:14:11 +000039
Adam Tkacf39671d2010-07-21 09:10:54 +000040#define DH_BITS 1024 /* XXX This should be configurable! */
Adam Tkacdfe19cf2010-04-23 14:14:11 +000041
42using namespace rfb;
43
Adam Tkac21b61a52010-07-21 09:19:00 +000044StringParameter SSecurityTLS::X509_CertFile
Pierre Ossman3d2a84b2014-09-17 16:45:35 +020045("X509Cert", "Path to the X509 certificate in PEM format", "", ConfServer);
Adam Tkacf39671d2010-07-21 09:10:54 +000046
Adam Tkac21b61a52010-07-21 09:19:00 +000047StringParameter SSecurityTLS::X509_KeyFile
Pierre Ossman3d2a84b2014-09-17 16:45:35 +020048("X509Key", "Path to the key of the X509 certificate in PEM format", "", ConfServer);
Adam Tkacf39671d2010-07-21 09:10:54 +000049
Adam Tkacdfe19cf2010-04-23 14:14:11 +000050static LogWriter vlog("TLS");
Adam Tkacdfe19cf2010-04-23 14:14:11 +000051
Pierre Ossmanad2b3c42018-09-21 15:31:11 +020052SSecurityTLS::SSecurityTLS(SConnection* sc, bool _anon)
53 : SSecurity(sc), session(NULL), dh_params(NULL), anon_cred(NULL),
54 cert_cred(NULL), anon(_anon), fis(NULL), fos(NULL)
Adam Tkacdfe19cf2010-04-23 14:14:11 +000055{
Adam Tkacf39671d2010-07-21 09:10:54 +000056 certfile = X509_CertFile.getData();
57 keyfile = X509_KeyFile.getData();
Pierre Ossman8aa4bc52016-08-23 17:02:58 +020058
59 if (gnutls_global_init() != GNUTLS_E_SUCCESS)
60 throw AuthFailureException("gnutls_global_init failed");
Adam Tkacdfe19cf2010-04-23 14:14:11 +000061}
62
Adam Tkac21b61a52010-07-21 09:19:00 +000063void SSecurityTLS::shutdown()
Adam Tkacdfe19cf2010-04-23 14:14:11 +000064{
Adam Tkacf39671d2010-07-21 09:10:54 +000065 if (session) {
66 if (gnutls_bye(session, GNUTLS_SHUT_RDWR) != GNUTLS_E_SUCCESS) {
67 /* FIXME: Treat as non-fatal error */
68 vlog.error("TLS session wasn't terminated gracefully");
69 }
70 }
71
72 if (dh_params) {
73 gnutls_dh_params_deinit(dh_params);
74 dh_params = 0;
75 }
76
77 if (anon_cred) {
78 gnutls_anon_free_server_credentials(anon_cred);
79 anon_cred = 0;
80 }
81
82 if (cert_cred) {
83 gnutls_certificate_free_credentials(cert_cred);
84 cert_cred = 0;
85 }
86
87 if (session) {
88 gnutls_deinit(session);
89 session = 0;
Adam Tkacf39671d2010-07-21 09:10:54 +000090 }
Adam Tkacdfe19cf2010-04-23 14:14:11 +000091}
92
93
Adam Tkac21b61a52010-07-21 09:19:00 +000094SSecurityTLS::~SSecurityTLS()
Adam Tkacdfe19cf2010-04-23 14:14:11 +000095{
Adam Tkacf39671d2010-07-21 09:10:54 +000096 shutdown();
97
98 if (fis)
Adam Tkacdfe19cf2010-04-23 14:14:11 +000099 delete fis;
Adam Tkacf39671d2010-07-21 09:10:54 +0000100 if (fos)
Adam Tkacdfe19cf2010-04-23 14:14:11 +0000101 delete fos;
Adam Tkacf39671d2010-07-21 09:10:54 +0000102
103 delete[] keyfile;
104 delete[] certfile;
Pierre Ossman8aa4bc52016-08-23 17:02:58 +0200105
106 gnutls_global_deinit();
Adam Tkacdfe19cf2010-04-23 14:14:11 +0000107}
108
Pierre Ossmanad2b3c42018-09-21 15:31:11 +0200109bool SSecurityTLS::processMsg()
Adam Tkacdfe19cf2010-04-23 14:14:11 +0000110{
111 rdr::InStream* is = sc->getInStream();
112 rdr::OutStream* os = sc->getOutStream();
113
114 vlog.debug("Process security message (session %p)", session);
115
116 if (!session) {
Adam Tkacdfe19cf2010-04-23 14:14:11 +0000117 if (gnutls_init(&session, GNUTLS_SERVER) != GNUTLS_E_SUCCESS)
118 throw AuthFailureException("gnutls_init failed");
119
120 if (gnutls_set_default_priority(session) != GNUTLS_E_SUCCESS)
121 throw AuthFailureException("gnutls_set_default_priority failed");
122
123 try {
124 setParams(session);
125 }
126 catch(...) {
127 os->writeU8(0);
128 throw;
129 }
130
Adam Tkacdfe19cf2010-04-23 14:14:11 +0000131 os->writeU8(1);
132 os->flush();
133 }
134
Pierre Ossmanfe48cd42012-07-03 14:43:38 +0000135 rdr::TLSInStream *tlsis = new rdr::TLSInStream(is, session);
136 rdr::TLSOutStream *tlsos = new rdr::TLSOutStream(os, session);
137
Adam Tkacdfe19cf2010-04-23 14:14:11 +0000138 int err;
Pierre Ossmanfe48cd42012-07-03 14:43:38 +0000139 err = gnutls_handshake(session);
140 if (err != GNUTLS_E_SUCCESS) {
141 delete tlsis;
142 delete tlsos;
143
Adam Tkacdfe19cf2010-04-23 14:14:11 +0000144 if (!gnutls_error_is_fatal(err)) {
145 vlog.debug("Deferring completion of TLS handshake: %s", gnutls_strerror(err));
146 return false;
147 }
148 vlog.error("TLS Handshake failed: %s", gnutls_strerror (err));
Adam Tkacf39671d2010-07-21 09:10:54 +0000149 shutdown();
Adam Tkacdfe19cf2010-04-23 14:14:11 +0000150 throw AuthFailureException("TLS Handshake failed");
151 }
152
153 vlog.debug("Handshake completed");
154
Pierre Ossmanfe48cd42012-07-03 14:43:38 +0000155 sc->setStreams(fis = tlsis, fos = tlsos);
Adam Tkacdfe19cf2010-04-23 14:14:11 +0000156
157 return true;
158}
159
Pierre Ossman88c24ed2015-01-29 13:12:22 +0100160void SSecurityTLS::setParams(gnutls_session_t session)
Adam Tkacf39671d2010-07-21 09:10:54 +0000161{
Pierre Ossman27eb55e2015-01-29 13:31:06 +0100162 static const char kx_anon_priority[] = ":+ANON-ECDH:+ANON-DH";
Adam Tkacf39671d2010-07-21 09:10:54 +0000163
Pierre Ossman88c24ed2015-01-29 13:12:22 +0100164 int ret;
Pierre Ossman27eb55e2015-01-29 13:31:06 +0100165 char *prio;
Pierre Ossman88c24ed2015-01-29 13:12:22 +0100166 const char *err;
167
Pierre Ossman27eb55e2015-01-29 13:31:06 +0100168 prio = (char*)malloc(strlen(Security::GnuTLSPriority) +
169 strlen(kx_anon_priority) + 1);
170 if (prio == NULL)
171 throw AuthFailureException("Not enough memory for GnuTLS priority string");
172
173 strcpy(prio, Security::GnuTLSPriority);
174 if (anon)
175 strcat(prio, kx_anon_priority);
176
177 ret = gnutls_priority_set_direct(session, prio, &err);
178
179 free(prio);
180
Pierre Ossman88c24ed2015-01-29 13:12:22 +0100181 if (ret != GNUTLS_E_SUCCESS) {
182 if (ret == GNUTLS_E_INVALID_REQUEST)
183 vlog.error("GnuTLS priority syntax error at: %s", err);
184 throw AuthFailureException("gnutls_set_priority_direct failed");
185 }
Adam Tkacf39671d2010-07-21 09:10:54 +0000186
187 if (gnutls_dh_params_init(&dh_params) != GNUTLS_E_SUCCESS)
188 throw AuthFailureException("gnutls_dh_params_init failed");
189
190 if (gnutls_dh_params_generate2(dh_params, DH_BITS) != GNUTLS_E_SUCCESS)
191 throw AuthFailureException("gnutls_dh_params_generate2 failed");
192
193 if (anon) {
194 if (gnutls_anon_allocate_server_credentials(&anon_cred) != GNUTLS_E_SUCCESS)
195 throw AuthFailureException("gnutls_anon_allocate_server_credentials failed");
196
197 gnutls_anon_set_server_dh_params(anon_cred, dh_params);
198
199 if (gnutls_credentials_set(session, GNUTLS_CRD_ANON, anon_cred)
200 != GNUTLS_E_SUCCESS)
201 throw AuthFailureException("gnutls_credentials_set failed");
202
203 vlog.debug("Anonymous session has been set");
204
205 } else {
206 if (gnutls_certificate_allocate_credentials(&cert_cred) != GNUTLS_E_SUCCESS)
207 throw AuthFailureException("gnutls_certificate_allocate_credentials failed");
208
209 gnutls_certificate_set_dh_params(cert_cred, dh_params);
210
Brian P. Hinzcab73382017-11-14 20:57:07 -0500211 switch (gnutls_certificate_set_x509_key_file(cert_cred, certfile, keyfile, GNUTLS_X509_FMT_PEM)) {
212 case GNUTLS_E_SUCCESS:
213 break;
214 case GNUTLS_E_CERTIFICATE_KEY_MISMATCH:
215 throw AuthFailureException("Private key does not match certificate");
216 case GNUTLS_E_UNSUPPORTED_CERTIFICATE_TYPE:
217 throw AuthFailureException("Unsupported certificate type");
218 default:
219 throw AuthFailureException("Error loading X509 certificate or key");
220 }
Adam Tkacf39671d2010-07-21 09:10:54 +0000221
222 if (gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, cert_cred)
223 != GNUTLS_E_SUCCESS)
224 throw AuthFailureException("gnutls_credentials_set failed");
225
226 vlog.debug("X509 session has been set");
227
228 }
229
230}