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 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  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. * Copyright (C) 2012-2021 Pierre Ossman for Cendio AB
  7. *
  8. * This is free software; you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation; either version 2 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * This software is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this software; if not, write to the Free Software
  20. * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
  21. * USA.
  22. */
  23. #ifdef HAVE_CONFIG_H
  24. #include <config.h>
  25. #endif
  26. #ifndef HAVE_GNUTLS
  27. #error "This header should not be compiled without HAVE_GNUTLS defined"
  28. #endif
  29. #include <stdlib.h>
  30. #ifndef WIN32
  31. #include <unistd.h>
  32. #endif
  33. #include <rfb/CSecurityTLS.h>
  34. #include <rfb/CConnection.h>
  35. #include <rfb/LogWriter.h>
  36. #include <rfb/Exception.h>
  37. #include <rfb/UserMsgBox.h>
  38. #include <rdr/TLSInStream.h>
  39. #include <rdr/TLSOutStream.h>
  40. #include <os/os.h>
  41. #include <gnutls/x509.h>
  42. /*
  43. * GNUTLS 2.6.5 and older didn't have some variables defined so don't use them.
  44. * GNUTLS 1.X.X defined LIBGNUTLS_VERSION_NUMBER so treat it as "old" gnutls as
  45. * well
  46. */
  47. #if (defined(GNUTLS_VERSION_NUMBER) && GNUTLS_VERSION_NUMBER < 0x020606) || \
  48. defined(LIBGNUTLS_VERSION_NUMBER)
  49. #define WITHOUT_X509_TIMES
  50. #endif
  51. /* Ancient GNUTLS... */
  52. #if !defined(GNUTLS_VERSION_NUMBER) && !defined(LIBGNUTLS_VERSION_NUMBER)
  53. #define WITHOUT_X509_TIMES
  54. #endif
  55. using namespace rfb;
  56. static const char* homedirfn(const char* fn);
  57. StringParameter CSecurityTLS::X509CA("X509CA", "X509 CA certificate",
  58. homedirfn("x509_ca.pem"),
  59. ConfViewer);
  60. StringParameter CSecurityTLS::X509CRL("X509CRL", "X509 CRL file",
  61. homedirfn("x509_crl.pem"),
  62. ConfViewer);
  63. static LogWriter vlog("TLS");
  64. static const char* homedirfn(const char* fn)
  65. {
  66. static char full_path[PATH_MAX];
  67. const char* homedir;
  68. homedir = os::getvnchomedir();
  69. if (homedir == NULL)
  70. return "";
  71. snprintf(full_path, sizeof(full_path), "%s/%s", homedir, fn);
  72. return full_path;
  73. }
  74. CSecurityTLS::CSecurityTLS(CConnection* cc, bool _anon)
  75. : CSecurity(cc), session(NULL), anon_cred(NULL), cert_cred(NULL),
  76. anon(_anon), tlsis(NULL), tlsos(NULL), rawis(NULL), rawos(NULL)
  77. {
  78. if (gnutls_global_init() != GNUTLS_E_SUCCESS)
  79. throw AuthFailureException("gnutls_global_init failed");
  80. }
  81. void CSecurityTLS::shutdown()
  82. {
  83. if (session) {
  84. int ret;
  85. // FIXME: We can't currently wait for the response, so we only send
  86. // our close and hope for the best
  87. ret = gnutls_bye(session, GNUTLS_SHUT_WR);
  88. if ((ret != GNUTLS_E_SUCCESS) && (ret != GNUTLS_E_INVALID_SESSION))
  89. vlog.error("TLS shutdown failed: %s", gnutls_strerror(ret));
  90. }
  91. if (anon_cred) {
  92. gnutls_anon_free_client_credentials(anon_cred);
  93. anon_cred = 0;
  94. }
  95. if (cert_cred) {
  96. gnutls_certificate_free_credentials(cert_cred);
  97. cert_cred = 0;
  98. }
  99. if (rawis && rawos) {
  100. cc->setStreams(rawis, rawos);
  101. rawis = NULL;
  102. rawos = NULL;
  103. }
  104. if (tlsis) {
  105. delete tlsis;
  106. tlsis = NULL;
  107. }
  108. if (tlsos) {
  109. delete tlsos;
  110. tlsos = NULL;
  111. }
  112. if (session) {
  113. gnutls_deinit(session);
  114. session = 0;
  115. }
  116. }
  117. CSecurityTLS::~CSecurityTLS()
  118. {
  119. shutdown();
  120. gnutls_global_deinit();
  121. }
  122. bool CSecurityTLS::processMsg()
  123. {
  124. rdr::InStream* is = cc->getInStream();
  125. rdr::OutStream* os = cc->getOutStream();
  126. client = cc;
  127. if (!session) {
  128. if (!is->hasData(1))
  129. return false;
  130. if (is->readU8() == 0)
  131. throw AuthFailureException("Server failed to initialize TLS session");
  132. if (gnutls_init(&session, GNUTLS_CLIENT) != GNUTLS_E_SUCCESS)
  133. throw AuthFailureException("gnutls_init failed");
  134. if (gnutls_set_default_priority(session) != GNUTLS_E_SUCCESS)
  135. throw AuthFailureException("gnutls_set_default_priority failed");
  136. setParam();
  137. // Create these early as they set up the push/pull functions
  138. // for GnuTLS
  139. tlsis = new rdr::TLSInStream(is, session);
  140. tlsos = new rdr::TLSOutStream(os, session);
  141. rawis = is;
  142. rawos = os;
  143. }
  144. int err;
  145. err = gnutls_handshake(session);
  146. if (err != GNUTLS_E_SUCCESS) {
  147. if (!gnutls_error_is_fatal(err)) {
  148. vlog.debug("Deferring completion of TLS handshake: %s", gnutls_strerror(err));
  149. return false;
  150. }
  151. vlog.error("TLS Handshake failed: %s\n", gnutls_strerror (err));
  152. shutdown();
  153. throw AuthFailureException("TLS Handshake failed");
  154. }
  155. vlog.debug("TLS handshake completed with %s",
  156. gnutls_session_get_desc(session));
  157. checkSession();
  158. cc->setStreams(tlsis, tlsos);
  159. return true;
  160. }
  161. void CSecurityTLS::setParam()
  162. {
  163. static const char kx_anon_priority[] = ":+ANON-ECDH:+ANON-DH";
  164. int ret;
  165. // Custom priority string specified?
  166. if (strcmp(Security::GnuTLSPriority, "") != 0) {
  167. char *prio;
  168. const char *err;
  169. prio = (char*)malloc(strlen(Security::GnuTLSPriority) +
  170. strlen(kx_anon_priority) + 1);
  171. if (prio == NULL)
  172. throw AuthFailureException("Not enough memory for GnuTLS priority string");
  173. strcpy(prio, Security::GnuTLSPriority);
  174. if (anon)
  175. strcat(prio, kx_anon_priority);
  176. ret = gnutls_priority_set_direct(session, prio, &err);
  177. free(prio);
  178. if (ret != GNUTLS_E_SUCCESS) {
  179. if (ret == GNUTLS_E_INVALID_REQUEST)
  180. vlog.error("GnuTLS priority syntax error at: %s", err);
  181. throw AuthFailureException("gnutls_set_priority_direct failed");
  182. }
  183. } else if (anon) {
  184. const char *err;
  185. #if GNUTLS_VERSION_NUMBER >= 0x030603
  186. // gnutls_set_default_priority_appends() expects a normal priority string that
  187. // doesn't start with ":".
  188. ret = gnutls_set_default_priority_append(session, kx_anon_priority + 1, &err, 0);
  189. if (ret != GNUTLS_E_SUCCESS) {
  190. if (ret == GNUTLS_E_INVALID_REQUEST)
  191. vlog.error("GnuTLS priority syntax error at: %s", err);
  192. throw AuthFailureException("gnutls_set_default_priority_append failed");
  193. }
  194. #else
  195. // We don't know what the system default priority is, so we guess
  196. // it's what upstream GnuTLS has
  197. static const char gnutls_default_priority[] = "NORMAL";
  198. char *prio;
  199. prio = (char*)malloc(strlen(gnutls_default_priority) +
  200. strlen(kx_anon_priority) + 1);
  201. if (prio == NULL)
  202. throw AuthFailureException("Not enough memory for GnuTLS priority string");
  203. strcpy(prio, gnutls_default_priority);
  204. strcat(prio, kx_anon_priority);
  205. ret = gnutls_priority_set_direct(session, prio, &err);
  206. free(prio);
  207. if (ret != GNUTLS_E_SUCCESS) {
  208. if (ret == GNUTLS_E_INVALID_REQUEST)
  209. vlog.error("GnuTLS priority syntax error at: %s", err);
  210. throw AuthFailureException("gnutls_set_priority_direct failed");
  211. }
  212. #endif
  213. }
  214. if (anon) {
  215. if (gnutls_anon_allocate_client_credentials(&anon_cred) != GNUTLS_E_SUCCESS)
  216. throw AuthFailureException("gnutls_anon_allocate_client_credentials failed");
  217. if (gnutls_credentials_set(session, GNUTLS_CRD_ANON, anon_cred) != GNUTLS_E_SUCCESS)
  218. throw AuthFailureException("gnutls_credentials_set failed");
  219. vlog.debug("Anonymous session has been set");
  220. } else {
  221. if (gnutls_certificate_allocate_credentials(&cert_cred) != GNUTLS_E_SUCCESS)
  222. throw AuthFailureException("gnutls_certificate_allocate_credentials failed");
  223. if (gnutls_certificate_set_x509_system_trust(cert_cred) < 1)
  224. vlog.error("Could not load system certificate trust store");
  225. if (gnutls_certificate_set_x509_trust_file(cert_cred, X509CA, GNUTLS_X509_FMT_PEM) < 0)
  226. vlog.error("Could not load user specified certificate authority");
  227. if (gnutls_certificate_set_x509_crl_file(cert_cred, X509CRL, GNUTLS_X509_FMT_PEM) < 0)
  228. vlog.error("Could not load user specified certificate revocation list");
  229. if (gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, cert_cred) != GNUTLS_E_SUCCESS)
  230. throw AuthFailureException("gnutls_credentials_set failed");
  231. if (gnutls_server_name_set(session, GNUTLS_NAME_DNS,
  232. client->getServerName(),
  233. strlen(client->getServerName())) != GNUTLS_E_SUCCESS)
  234. vlog.error("Failed to configure the server name for TLS handshake");
  235. vlog.debug("X509 session has been set");
  236. }
  237. }
  238. void CSecurityTLS::checkSession()
  239. {
  240. const unsigned allowed_errors = GNUTLS_CERT_INVALID |
  241. GNUTLS_CERT_SIGNER_NOT_FOUND |
  242. GNUTLS_CERT_SIGNER_NOT_CA |
  243. GNUTLS_CERT_EXPIRED;
  244. unsigned int status;
  245. const gnutls_datum_t *cert_list;
  246. unsigned int cert_list_size = 0;
  247. int err;
  248. const char *homeDir;
  249. gnutls_datum_t info;
  250. size_t len;
  251. if (anon)
  252. return;
  253. if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509)
  254. throw AuthFailureException("unsupported certificate type");
  255. err = gnutls_certificate_verify_peers2(session, &status);
  256. if (err != 0) {
  257. vlog.error("server certificate verification failed: %s", gnutls_strerror(err));
  258. throw AuthFailureException("server certificate verification failed");
  259. }
  260. if (status & GNUTLS_CERT_REVOKED)
  261. throw AuthFailureException("server certificate has been revoked");
  262. #ifndef WITHOUT_X509_TIMES
  263. if (status & GNUTLS_CERT_NOT_ACTIVATED)
  264. throw AuthFailureException("server certificate has not been activated");
  265. if (status & GNUTLS_CERT_EXPIRED) {
  266. vlog.debug("server certificate has expired");
  267. if (!msg->showMsgBox(UserMsgBox::M_YESNO, "certificate has expired",
  268. "The certificate of the server has expired, "
  269. "do you want to continue?"))
  270. throw AuthFailureException("server certificate has expired");
  271. }
  272. #endif
  273. /* Process other errors later */
  274. cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
  275. if (!cert_list_size)
  276. throw AuthFailureException("empty certificate chain");
  277. /* Process only server's certificate, not issuer's certificate */
  278. gnutls_x509_crt_t crt;
  279. gnutls_x509_crt_init(&crt);
  280. if (gnutls_x509_crt_import(crt, &cert_list[0], GNUTLS_X509_FMT_DER) < 0)
  281. throw AuthFailureException("decoding of certificate failed");
  282. if (gnutls_x509_crt_check_hostname(crt, client->getServerName()) == 0) {
  283. std::string text;
  284. vlog.debug("hostname mismatch");
  285. text = strFormat("Hostname (%s) does not match the server "
  286. "certificate, do you want to continue?",
  287. client->getServerName());
  288. if (!msg->showMsgBox(UserMsgBox::M_YESNO,
  289. "Certificate hostname mismatch",
  290. text.c_str()))
  291. throw AuthFailureException("Certificate hostname mismatch");
  292. }
  293. if (status == 0) {
  294. /* Everything is fine (hostname + verification) */
  295. gnutls_x509_crt_deinit(crt);
  296. return;
  297. }
  298. if (status & GNUTLS_CERT_INVALID)
  299. vlog.debug("server certificate invalid");
  300. if (status & GNUTLS_CERT_SIGNER_NOT_FOUND)
  301. vlog.debug("server cert signer not found");
  302. if (status & GNUTLS_CERT_SIGNER_NOT_CA)
  303. vlog.debug("server cert signer not CA");
  304. if (status & GNUTLS_CERT_EXPIRED)
  305. vlog.debug("server certificate has expired");
  306. if (status & GNUTLS_CERT_INSECURE_ALGORITHM)
  307. throw AuthFailureException("The server certificate uses an insecure algorithm");
  308. if ((status & (~allowed_errors)) != 0) {
  309. /* No other errors are allowed */
  310. vlog.debug("GNUTLS status of certificate verification: 0x%x", status);
  311. throw AuthFailureException("Invalid status of server certificate verification");
  312. }
  313. /* Certificate is fine, except we don't know the issuer, so TOFU time */
  314. homeDir = os::getvnchomedir();
  315. if (homeDir == NULL) {
  316. throw AuthFailureException("Could not obtain VNC home directory "
  317. "path for known hosts storage");
  318. }
  319. std::string dbPath;
  320. dbPath = (std::string)homeDir + "/x509_known_hosts";
  321. err = gnutls_verify_stored_pubkey(dbPath.c_str(), NULL,
  322. client->getServerName(), NULL,
  323. GNUTLS_CRT_X509, &cert_list[0], 0);
  324. /* Previously known? */
  325. if (err == GNUTLS_E_SUCCESS) {
  326. vlog.debug("Server certificate found in known hosts file");
  327. gnutls_x509_crt_deinit(crt);
  328. return;
  329. }
  330. if ((err != GNUTLS_E_NO_CERTIFICATE_FOUND) &&
  331. (err != GNUTLS_E_CERTIFICATE_KEY_MISMATCH)) {
  332. throw AuthFailureException("Could not load known hosts database");
  333. }
  334. if (gnutls_x509_crt_print(crt, GNUTLS_CRT_PRINT_ONELINE, &info))
  335. throw AuthFailureException("Could not find certificate to display");
  336. len = strlen((char*)info.data);
  337. for (size_t i = 0; i < len - 1; i++) {
  338. if (info.data[i] == ',' && info.data[i + 1] == ' ')
  339. info.data[i] = '\n';
  340. }
  341. /* New host */
  342. if (err == GNUTLS_E_NO_CERTIFICATE_FOUND) {
  343. std::string text;
  344. vlog.debug("Server host not previously known");
  345. vlog.debug("%s", info.data);
  346. if (status & (GNUTLS_CERT_SIGNER_NOT_FOUND |
  347. GNUTLS_CERT_SIGNER_NOT_CA)) {
  348. text = strFormat("This certificate has been signed by an "
  349. "unknown authority:\n"
  350. "\n"
  351. "%s\n"
  352. "\n"
  353. "Someone could be trying to impersonate the "
  354. "site and you should not continue.\n"
  355. "\n"
  356. "Do you want to make an exception for this "
  357. "server?", info.data);
  358. if (!msg->showMsgBox(UserMsgBox::M_YESNO,
  359. "Unknown certificate issuer",
  360. text.c_str()))
  361. throw AuthFailureException("Unknown certificate issuer");
  362. }
  363. if (status & GNUTLS_CERT_EXPIRED) {
  364. text = strFormat("This certificate has expired:\n"
  365. "\n"
  366. "%s\n"
  367. "\n"
  368. "Someone could be trying to impersonate the "
  369. "site and you should not continue.\n"
  370. "\n"
  371. "Do you want to make an exception for this "
  372. "server?", info.data);
  373. if (!msg->showMsgBox(UserMsgBox::M_YESNO,
  374. "Expired certificate",
  375. text.c_str()))
  376. throw AuthFailureException("Expired certificate");
  377. }
  378. } else if (err == GNUTLS_E_CERTIFICATE_KEY_MISMATCH) {
  379. std::string text;
  380. vlog.debug("Server host key mismatch");
  381. vlog.debug("%s", info.data);
  382. if (status & (GNUTLS_CERT_SIGNER_NOT_FOUND |
  383. GNUTLS_CERT_SIGNER_NOT_CA)) {
  384. text = strFormat("This host is previously known with a "
  385. "different certificate, and the new "
  386. "certificate has been signed by an "
  387. "unknown authority:\n"
  388. "\n"
  389. "%s\n"
  390. "\n"
  391. "Someone could be trying to impersonate the "
  392. "site and you should not continue.\n"
  393. "\n"
  394. "Do you want to make an exception for this "
  395. "server?", info.data);
  396. if (!msg->showMsgBox(UserMsgBox::M_YESNO,
  397. "Unexpected server certificate",
  398. text.c_str()))
  399. throw AuthFailureException("Unexpected server certificate");
  400. }
  401. if (status & GNUTLS_CERT_EXPIRED) {
  402. text = strFormat("This host is previously known with a "
  403. "different certificate, and the new "
  404. "certificate has expired:\n"
  405. "\n"
  406. "%s\n"
  407. "\n"
  408. "Someone could be trying to impersonate the "
  409. "site and you should not continue.\n"
  410. "\n"
  411. "Do you want to make an exception for this "
  412. "server?", info.data);
  413. if (!msg->showMsgBox(UserMsgBox::M_YESNO,
  414. "Unexpected server certificate",
  415. text.c_str()))
  416. throw AuthFailureException("Unexpected server certificate");
  417. }
  418. }
  419. if (gnutls_store_pubkey(dbPath.c_str(), NULL, client->getServerName(),
  420. NULL, GNUTLS_CRT_X509, &cert_list[0], 0, 0))
  421. vlog.error("Failed to store server certificate to known hosts database");
  422. gnutls_x509_crt_deinit(crt);
  423. /*
  424. * GNUTLS doesn't correctly export gnutls_free symbol which is
  425. * a function pointer. Linking with Visual Studio 2008 Express will
  426. * fail when you call gnutls_free().
  427. */
  428. #if WIN32
  429. free(info.data);
  430. #else
  431. gnutls_free(info.data);
  432. #endif
  433. }