You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

CSecurityTLS.cxx 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. /*
  2. * Copyright (C) 2004 Red Hat Inc.
  3. * Copyright (C) 2005 Martin Koegler
  4. * Copyright (C) 2010 TigerVNC Team
  5. * Copyright (C) 2010 m-privacy GmbH
  6. *
  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. #ifdef HAVE_CONFIG_H
  23. #include <config.h>
  24. #endif
  25. #ifndef HAVE_GNUTLS
  26. #error "This header should not be compiled without HAVE_GNUTLS defined"
  27. #endif
  28. #include <stdlib.h>
  29. #ifndef WIN32
  30. #include <unistd.h>
  31. #endif
  32. #include <rfb/CSecurityTLS.h>
  33. #include <rfb/CConnection.h>
  34. #include <rfb/LogWriter.h>
  35. #include <rfb/Exception.h>
  36. #include <rfb/UserMsgBox.h>
  37. #include <rdr/TLSInStream.h>
  38. #include <rdr/TLSOutStream.h>
  39. #include <os/os.h>
  40. #include <gnutls/x509.h>
  41. /*
  42. * GNUTLS 2.6.5 and older didn't have some variables defined so don't use them.
  43. * GNUTLS 1.X.X defined LIBGNUTLS_VERSION_NUMBER so treat it as "old" gnutls as
  44. * well
  45. */
  46. #if (defined(GNUTLS_VERSION_NUMBER) && GNUTLS_VERSION_NUMBER < 0x020606) || \
  47. defined(LIBGNUTLS_VERSION_NUMBER)
  48. #define WITHOUT_X509_TIMES
  49. #endif
  50. /* Ancient GNUTLS... */
  51. #if !defined(GNUTLS_VERSION_NUMBER) && !defined(LIBGNUTLS_VERSION_NUMBER)
  52. #define WITHOUT_X509_TIMES
  53. #endif
  54. using namespace rfb;
  55. StringParameter CSecurityTLS::X509CA("X509CA", "X509 CA certificate", "", ConfViewer);
  56. StringParameter CSecurityTLS::X509CRL("X509CRL", "X509 CRL file", "", ConfViewer);
  57. static LogWriter vlog("TLS");
  58. CSecurityTLS::CSecurityTLS(CConnection* cc, bool _anon)
  59. : CSecurity(cc), session(NULL), anon_cred(NULL), cert_cred(NULL),
  60. anon(_anon), tlsis(NULL), tlsos(NULL), rawis(NULL), rawos(NULL)
  61. {
  62. cafile = X509CA.getData();
  63. crlfile = X509CRL.getData();
  64. if (gnutls_global_init() != GNUTLS_E_SUCCESS)
  65. throw AuthFailureException("gnutls_global_init failed");
  66. }
  67. void CSecurityTLS::setDefaults()
  68. {
  69. char* homeDir = NULL;
  70. if (getvnchomedir(&homeDir) == -1) {
  71. vlog.error("Could not obtain VNC home directory path");
  72. return;
  73. }
  74. int len = strlen(homeDir) + 1;
  75. CharArray caDefault(len + 11);
  76. CharArray crlDefault(len + 12);
  77. sprintf(caDefault.buf, "%sx509_ca.pem", homeDir);
  78. sprintf(crlDefault.buf, "%s509_crl.pem", homeDir);
  79. delete [] homeDir;
  80. if (!fileexists(caDefault.buf))
  81. X509CA.setDefaultStr(caDefault.buf);
  82. if (!fileexists(crlDefault.buf))
  83. X509CRL.setDefaultStr(crlDefault.buf);
  84. }
  85. void CSecurityTLS::shutdown(bool needbye)
  86. {
  87. if (session && needbye)
  88. if (gnutls_bye(session, GNUTLS_SHUT_RDWR) != GNUTLS_E_SUCCESS)
  89. vlog.error("gnutls_bye failed");
  90. if (anon_cred) {
  91. gnutls_anon_free_client_credentials(anon_cred);
  92. anon_cred = 0;
  93. }
  94. if (cert_cred) {
  95. gnutls_certificate_free_credentials(cert_cred);
  96. cert_cred = 0;
  97. }
  98. if (rawis && rawos) {
  99. cc->setStreams(rawis, rawos);
  100. rawis = NULL;
  101. rawos = NULL;
  102. }
  103. if (tlsis) {
  104. delete tlsis;
  105. tlsis = NULL;
  106. }
  107. if (tlsos) {
  108. delete tlsos;
  109. tlsos = NULL;
  110. }
  111. if (session) {
  112. gnutls_deinit(session);
  113. session = 0;
  114. }
  115. }
  116. CSecurityTLS::~CSecurityTLS()
  117. {
  118. shutdown(true);
  119. delete[] cafile;
  120. delete[] crlfile;
  121. gnutls_global_deinit();
  122. }
  123. bool CSecurityTLS::processMsg()
  124. {
  125. rdr::InStream* is = cc->getInStream();
  126. rdr::OutStream* os = cc->getOutStream();
  127. client = cc;
  128. if (!session) {
  129. if (!is->hasData(1))
  130. return false;
  131. if (is->readU8() == 0)
  132. throw AuthFailureException("Server failed to initialize TLS session");
  133. if (gnutls_init(&session, GNUTLS_CLIENT) != GNUTLS_E_SUCCESS)
  134. throw AuthFailureException("gnutls_init failed");
  135. if (gnutls_set_default_priority(session) != GNUTLS_E_SUCCESS)
  136. throw AuthFailureException("gnutls_set_default_priority failed");
  137. setParam();
  138. // Create these early as they set up the push/pull functions
  139. // for GnuTLS
  140. tlsis = new rdr::TLSInStream(is, session);
  141. tlsos = new rdr::TLSOutStream(os, session);
  142. rawis = is;
  143. rawos = os;
  144. }
  145. int err;
  146. err = gnutls_handshake(session);
  147. if (err != GNUTLS_E_SUCCESS) {
  148. if (!gnutls_error_is_fatal(err)) {
  149. vlog.debug("Deferring completion of TLS handshake: %s", gnutls_strerror(err));
  150. return false;
  151. }
  152. vlog.error("TLS Handshake failed: %s\n", gnutls_strerror (err));
  153. shutdown(false);
  154. throw AuthFailureException("TLS Handshake failed");
  155. }
  156. vlog.debug("TLS handshake completed with %s",
  157. gnutls_session_get_desc(session));
  158. checkSession();
  159. cc->setStreams(tlsis, tlsos);
  160. return true;
  161. }
  162. void CSecurityTLS::setParam()
  163. {
  164. static const char kx_anon_priority[] = ":+ANON-ECDH:+ANON-DH";
  165. int ret;
  166. // Custom priority string specified?
  167. if (strcmp(Security::GnuTLSPriority, "") != 0) {
  168. char *prio;
  169. const char *err;
  170. prio = (char*)malloc(strlen(Security::GnuTLSPriority) +
  171. strlen(kx_anon_priority) + 1);
  172. if (prio == NULL)
  173. throw AuthFailureException("Not enough memory for GnuTLS priority string");
  174. strcpy(prio, Security::GnuTLSPriority);
  175. if (anon)
  176. strcat(prio, kx_anon_priority);
  177. ret = gnutls_priority_set_direct(session, prio, &err);
  178. free(prio);
  179. if (ret != GNUTLS_E_SUCCESS) {
  180. if (ret == GNUTLS_E_INVALID_REQUEST)
  181. vlog.error("GnuTLS priority syntax error at: %s", err);
  182. throw AuthFailureException("gnutls_set_priority_direct failed");
  183. }
  184. } else if (anon) {
  185. const char *err;
  186. #if GNUTLS_VERSION_NUMBER >= 0x030603
  187. ret = gnutls_set_default_priority_append(session, kx_anon_priority, &err, 0);
  188. if (ret != GNUTLS_E_SUCCESS) {
  189. if (ret == GNUTLS_E_INVALID_REQUEST)
  190. vlog.error("GnuTLS priority syntax error at: %s", err);
  191. throw AuthFailureException("gnutls_set_default_priority_append failed");
  192. }
  193. #else
  194. // We don't know what the system default priority is, so we guess
  195. // it's what upstream GnuTLS has
  196. static const char gnutls_default_priority[] = "NORMAL";
  197. char *prio;
  198. prio = (char*)malloc(strlen(gnutls_default_priority) +
  199. strlen(kx_anon_priority) + 1);
  200. if (prio == NULL)
  201. throw AuthFailureException("Not enough memory for GnuTLS priority string");
  202. strcpy(prio, gnutls_default_priority);
  203. strcat(prio, kx_anon_priority);
  204. ret = gnutls_priority_set_direct(session, prio, &err);
  205. free(prio);
  206. if (ret != GNUTLS_E_SUCCESS) {
  207. if (ret == GNUTLS_E_INVALID_REQUEST)
  208. vlog.error("GnuTLS priority syntax error at: %s", err);
  209. throw AuthFailureException("gnutls_set_priority_direct failed");
  210. }
  211. #endif
  212. }
  213. if (anon) {
  214. if (gnutls_anon_allocate_client_credentials(&anon_cred) != GNUTLS_E_SUCCESS)
  215. throw AuthFailureException("gnutls_anon_allocate_client_credentials failed");
  216. if (gnutls_credentials_set(session, GNUTLS_CRD_ANON, anon_cred) != GNUTLS_E_SUCCESS)
  217. throw AuthFailureException("gnutls_credentials_set failed");
  218. vlog.debug("Anonymous session has been set");
  219. } else {
  220. if (gnutls_certificate_allocate_credentials(&cert_cred) != GNUTLS_E_SUCCESS)
  221. throw AuthFailureException("gnutls_certificate_allocate_credentials failed");
  222. if (gnutls_certificate_set_x509_system_trust(cert_cred) != GNUTLS_E_SUCCESS)
  223. vlog.error("Could not load system certificate trust store");
  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. if (*crlfile && gnutls_certificate_set_x509_crl_file(cert_cred,crlfile,GNUTLS_X509_FMT_PEM) < 0)
  227. throw AuthFailureException("load of CRL failed");
  228. if (gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, cert_cred) != GNUTLS_E_SUCCESS)
  229. throw AuthFailureException("gnutls_credentials_set failed");
  230. if (gnutls_server_name_set(session, GNUTLS_NAME_DNS,
  231. client->getServerName(),
  232. strlen(client->getServerName())) != GNUTLS_E_SUCCESS)
  233. vlog.error("Failed to configure the server name for TLS handshake");
  234. vlog.debug("X509 session has been set");
  235. }
  236. }
  237. void CSecurityTLS::checkSession()
  238. {
  239. const unsigned allowed_errors = GNUTLS_CERT_INVALID |
  240. GNUTLS_CERT_SIGNER_NOT_FOUND |
  241. GNUTLS_CERT_SIGNER_NOT_CA;
  242. unsigned int status;
  243. const gnutls_datum_t *cert_list;
  244. unsigned int cert_list_size = 0;
  245. int err;
  246. char *homeDir;
  247. gnutls_datum_t info;
  248. size_t len;
  249. if (anon)
  250. return;
  251. if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509)
  252. throw AuthFailureException("unsupported certificate type");
  253. err = gnutls_certificate_verify_peers2(session, &status);
  254. if (err != 0) {
  255. vlog.error("server certificate verification failed: %s", gnutls_strerror(err));
  256. throw AuthFailureException("server certificate verification failed");
  257. }
  258. if (status & GNUTLS_CERT_REVOKED)
  259. throw AuthFailureException("server certificate has been revoked");
  260. #ifndef WITHOUT_X509_TIMES
  261. if (status & GNUTLS_CERT_NOT_ACTIVATED)
  262. throw AuthFailureException("server certificate has not been activated");
  263. if (status & GNUTLS_CERT_EXPIRED) {
  264. vlog.debug("server certificate has expired");
  265. if (!msg->showMsgBox(UserMsgBox::M_YESNO, "certificate has expired",
  266. "The certificate of the server has expired, "
  267. "do you want to continue?"))
  268. throw AuthFailureException("server certificate has expired");
  269. }
  270. #endif
  271. /* Process other errors later */
  272. cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
  273. if (!cert_list_size)
  274. throw AuthFailureException("empty certificate chain");
  275. /* Process only server's certificate, not issuer's certificate */
  276. gnutls_x509_crt_t crt;
  277. gnutls_x509_crt_init(&crt);
  278. if (gnutls_x509_crt_import(crt, &cert_list[0], GNUTLS_X509_FMT_DER) < 0)
  279. throw AuthFailureException("decoding of certificate failed");
  280. if (gnutls_x509_crt_check_hostname(crt, client->getServerName()) == 0) {
  281. CharArray text;
  282. vlog.debug("hostname mismatch");
  283. text.format("Hostname (%s) does not match the server certificate, "
  284. "do you want to continue?", client->getServerName());
  285. if (!msg->showMsgBox(UserMsgBox::M_YESNO,
  286. "Certificate hostname mismatch", text.buf))
  287. throw AuthFailureException("Certificate hostname mismatch");
  288. }
  289. if (status == 0) {
  290. /* Everything is fine (hostname + verification) */
  291. gnutls_x509_crt_deinit(crt);
  292. return;
  293. }
  294. if (status & GNUTLS_CERT_INVALID)
  295. vlog.debug("server certificate invalid");
  296. if (status & GNUTLS_CERT_SIGNER_NOT_FOUND)
  297. vlog.debug("server cert signer not found");
  298. if (status & GNUTLS_CERT_SIGNER_NOT_CA)
  299. vlog.debug("server cert signer not CA");
  300. if (status & GNUTLS_CERT_INSECURE_ALGORITHM)
  301. throw AuthFailureException("The server certificate uses an insecure algorithm");
  302. if ((status & (~allowed_errors)) != 0) {
  303. /* No other errors are allowed */
  304. vlog.debug("GNUTLS status of certificate verification: %u", status);
  305. throw AuthFailureException("Invalid status of server certificate verification");
  306. }
  307. /* Certificate is fine, except we don't know the issuer, so TOFU time */
  308. homeDir = NULL;
  309. if (getvnchomedir(&homeDir) == -1) {
  310. throw AuthFailureException("Could not obtain VNC home directory "
  311. "path for known hosts storage");
  312. }
  313. CharArray dbPath(strlen(homeDir) + 16 + 1);
  314. sprintf(dbPath.buf, "%sx509_known_hosts", homeDir);
  315. delete [] homeDir;
  316. err = gnutls_verify_stored_pubkey(dbPath.buf, NULL,
  317. client->getServerName(), NULL,
  318. GNUTLS_CRT_X509, &cert_list[0], 0);
  319. /* Previously known? */
  320. if (err == GNUTLS_E_SUCCESS) {
  321. vlog.debug("Server certificate found in known hosts file");
  322. gnutls_x509_crt_deinit(crt);
  323. return;
  324. }
  325. if ((err != GNUTLS_E_NO_CERTIFICATE_FOUND) &&
  326. (err != GNUTLS_E_CERTIFICATE_KEY_MISMATCH)) {
  327. throw AuthFailureException("Could not load known hosts database");
  328. }
  329. if (gnutls_x509_crt_print(crt, GNUTLS_CRT_PRINT_ONELINE, &info))
  330. throw AuthFailureException("Could not find certificate to display");
  331. len = strlen((char*)info.data);
  332. for (size_t i = 0; i < len - 1; i++) {
  333. if (info.data[i] == ',' && info.data[i + 1] == ' ')
  334. info.data[i] = '\n';
  335. }
  336. /* New host */
  337. if (err == GNUTLS_E_NO_CERTIFICATE_FOUND) {
  338. CharArray text;
  339. vlog.debug("Server host not previously known");
  340. vlog.debug("%s", info.data);
  341. text.format("This certificate has been signed by an unknown "
  342. "authority:\n\n%s\n\nSomeone could be trying to "
  343. "impersonate the site and you should not "
  344. "continue.\n\nDo you want to make an exception "
  345. "for this server?", info.data);
  346. if (!msg->showMsgBox(UserMsgBox::M_YESNO,
  347. "Unknown certificate issuer",
  348. text.buf))
  349. throw AuthFailureException("Unknown certificate issuer");
  350. } else if (err == GNUTLS_E_CERTIFICATE_KEY_MISMATCH) {
  351. CharArray text;
  352. vlog.debug("Server host key mismatch");
  353. vlog.debug("%s", info.data);
  354. text.format("This host is previously known with a different "
  355. "certificate, and the new certificate has been "
  356. "signed by an unknown authority:\n\n%s\n\nSomeone "
  357. "could be trying to impersonate the site and you "
  358. "should not continue.\n\nDo you want to make an "
  359. "exception for this server?", info.data);
  360. if (!msg->showMsgBox(UserMsgBox::M_YESNO,
  361. "Unexpected server certificate",
  362. text.buf))
  363. throw AuthFailureException("Unexpected server certificate");
  364. }
  365. if (gnutls_store_pubkey(dbPath.buf, NULL, client->getServerName(),
  366. NULL, GNUTLS_CRT_X509, &cert_list[0], 0, 0))
  367. vlog.error("Failed to store server certificate to known hosts database");
  368. gnutls_x509_crt_deinit(crt);
  369. /*
  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
  377. gnutls_free(info.data);
  378. #endif
  379. }