Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

CSecurityTLS.cxx 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  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/SSecurityVeNCrypt.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. StringParameter CSecurityTLS::X509CA("X509CA", "X509 CA certificate", "", ConfViewer);
  57. StringParameter CSecurityTLS::X509CRL("X509CRL", "X509 CRL file", "", ConfViewer);
  58. static LogWriter vlog("TLS");
  59. void CSecurityTLS::initGlobal()
  60. {
  61. static bool globalInitDone = false;
  62. if (!globalInitDone) {
  63. gnutls_global_init();
  64. globalInitDone = true;
  65. }
  66. }
  67. CSecurityTLS::CSecurityTLS(bool _anon) : session(0), anon_cred(0),
  68. anon(_anon), fis(0), fos(0)
  69. {
  70. cafile = X509CA.getData();
  71. crlfile = X509CRL.getData();
  72. }
  73. void CSecurityTLS::setDefaults()
  74. {
  75. char* homeDir = NULL;
  76. if (getvnchomedir(&homeDir) == -1) {
  77. vlog.error("Could not obtain VNC home directory path");
  78. return;
  79. }
  80. int len = strlen(homeDir) + 1;
  81. CharArray caDefault(len + 11);
  82. CharArray crlDefault(len + 12);
  83. sprintf(caDefault.buf, "%sx509_ca.pem", homeDir);
  84. sprintf(crlDefault.buf, "%s509_crl.pem", homeDir);
  85. delete [] homeDir;
  86. if (!fileexists(caDefault.buf))
  87. X509CA.setDefaultStr(strdup(caDefault.buf));
  88. if (!fileexists(crlDefault.buf))
  89. X509CRL.setDefaultStr(strdup(crlDefault.buf));
  90. }
  91. void CSecurityTLS::shutdown(bool needbye)
  92. {
  93. if (session && needbye)
  94. if (gnutls_bye(session, GNUTLS_SHUT_RDWR) != GNUTLS_E_SUCCESS)
  95. vlog.error("gnutls_bye failed");
  96. if (anon_cred) {
  97. gnutls_anon_free_client_credentials(anon_cred);
  98. anon_cred = 0;
  99. }
  100. if (cert_cred) {
  101. gnutls_certificate_free_credentials(cert_cred);
  102. cert_cred = 0;
  103. }
  104. if (session) {
  105. gnutls_deinit(session);
  106. session = 0;
  107. gnutls_global_deinit();
  108. }
  109. }
  110. CSecurityTLS::~CSecurityTLS()
  111. {
  112. shutdown(true);
  113. if (fis)
  114. delete fis;
  115. if (fos)
  116. delete fos;
  117. delete[] cafile;
  118. delete[] crlfile;
  119. }
  120. bool CSecurityTLS::processMsg(CConnection* cc)
  121. {
  122. rdr::InStream* is = cc->getInStream();
  123. rdr::OutStream* os = cc->getOutStream();
  124. client = cc;
  125. initGlobal();
  126. if (!session) {
  127. if (!is->checkNoWait(1))
  128. return false;
  129. if (is->readU8() == 0) {
  130. rdr::U32 result = is->readU32();
  131. CharArray reason;
  132. if (result == secResultFailed || result == secResultTooMany)
  133. reason.buf = is->readString();
  134. else
  135. reason.buf = strDup("Authentication failure (protocol error)");
  136. throw AuthFailureException(reason.buf);
  137. }
  138. if (gnutls_init(&session, GNUTLS_CLIENT) != GNUTLS_E_SUCCESS)
  139. throw AuthFailureException("gnutls_init failed");
  140. if (gnutls_set_default_priority(session) != GNUTLS_E_SUCCESS)
  141. throw AuthFailureException("gnutls_set_default_priority failed");
  142. setParam();
  143. }
  144. rdr::TLSInStream *tlsis = new rdr::TLSInStream(is, session);
  145. rdr::TLSOutStream *tlsos = new rdr::TLSOutStream(os, session);
  146. int err;
  147. err = gnutls_handshake(session);
  148. if (err != GNUTLS_E_SUCCESS) {
  149. delete tlsis;
  150. delete tlsos;
  151. if (!gnutls_error_is_fatal(err))
  152. return false;
  153. vlog.error("TLS Handshake failed: %s\n", gnutls_strerror (err));
  154. shutdown(false);
  155. throw AuthFailureException("TLS Handshake failed");
  156. }
  157. checkSession();
  158. cc->setStreams(fis = tlsis, fos = tlsos);
  159. return true;
  160. }
  161. void CSecurityTLS::setParam()
  162. {
  163. static const char kx_anon_priority[] = "NORMAL:+ANON-ECDH:+ANON-DH";
  164. static const char kx_priority[] = "NORMAL";
  165. int ret;
  166. const char *err;
  167. if (anon) {
  168. ret = gnutls_priority_set_direct(session, kx_anon_priority, &err);
  169. if (ret != GNUTLS_E_SUCCESS) {
  170. if (ret == GNUTLS_E_INVALID_REQUEST)
  171. vlog.error("GnuTLS priority syntax error at: %s", err);
  172. throw AuthFailureException("gnutls_set_priority_direct failed");
  173. }
  174. if (gnutls_anon_allocate_client_credentials(&anon_cred) != GNUTLS_E_SUCCESS)
  175. throw AuthFailureException("gnutls_anon_allocate_client_credentials failed");
  176. if (gnutls_credentials_set(session, GNUTLS_CRD_ANON, anon_cred) != GNUTLS_E_SUCCESS)
  177. throw AuthFailureException("gnutls_credentials_set failed");
  178. vlog.debug("Anonymous session has been set");
  179. } else {
  180. ret = gnutls_priority_set_direct(session, kx_priority, &err);
  181. 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. }
  186. if (gnutls_certificate_allocate_credentials(&cert_cred) != GNUTLS_E_SUCCESS)
  187. throw AuthFailureException("gnutls_certificate_allocate_credentials failed");
  188. if (*cafile && gnutls_certificate_set_x509_trust_file(cert_cred,cafile,GNUTLS_X509_FMT_PEM) < 0)
  189. throw AuthFailureException("load of CA cert failed");
  190. /* Load previously saved certs */
  191. char *homeDir = NULL;
  192. int err;
  193. if (getvnchomedir(&homeDir) == -1)
  194. vlog.error("Could not obtain VNC home directory path");
  195. else {
  196. CharArray caSave(strlen(homeDir) + 19 + 1);
  197. sprintf(caSave.buf, "%sx509_savedcerts.pem", homeDir);
  198. delete [] homeDir;
  199. err = gnutls_certificate_set_x509_trust_file(cert_cred, caSave.buf,
  200. GNUTLS_X509_FMT_PEM);
  201. if (err < 0)
  202. vlog.debug("Failed to load saved server certificates from %s", caSave.buf);
  203. }
  204. if (*crlfile && gnutls_certificate_set_x509_crl_file(cert_cred,crlfile,GNUTLS_X509_FMT_PEM) < 0)
  205. throw AuthFailureException("load of CRL failed");
  206. if (gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, cert_cred) != GNUTLS_E_SUCCESS)
  207. throw AuthFailureException("gnutls_credentials_set failed");
  208. vlog.debug("X509 session has been set");
  209. }
  210. }
  211. void CSecurityTLS::checkSession()
  212. {
  213. const unsigned allowed_errors = GNUTLS_CERT_INVALID |
  214. GNUTLS_CERT_SIGNER_NOT_FOUND |
  215. GNUTLS_CERT_SIGNER_NOT_CA;
  216. unsigned int status;
  217. const gnutls_datum_t *cert_list;
  218. unsigned int cert_list_size = 0;
  219. int err;
  220. gnutls_datum_t info;
  221. if (anon)
  222. return;
  223. if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509)
  224. throw AuthFailureException("unsupported certificate type");
  225. err = gnutls_certificate_verify_peers2(session, &status);
  226. if (err != 0) {
  227. vlog.error("server certificate verification failed: %s", gnutls_strerror(err));
  228. throw AuthFailureException("server certificate verification failed");
  229. }
  230. if (status & GNUTLS_CERT_REVOKED)
  231. throw AuthFailureException("server certificate has been revoked");
  232. #ifndef WITHOUT_X509_TIMES
  233. if (status & GNUTLS_CERT_NOT_ACTIVATED)
  234. throw AuthFailureException("server certificate has not been activated");
  235. if (status & GNUTLS_CERT_EXPIRED) {
  236. vlog.debug("server certificate has expired");
  237. if (!msg->showMsgBox(UserMsgBox::M_YESNO, "certificate has expired",
  238. "The certificate of the server has expired, "
  239. "do you want to continue?"))
  240. throw AuthFailureException("server certificate has expired");
  241. }
  242. #endif
  243. /* Process other errors later */
  244. cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
  245. if (!cert_list_size)
  246. throw AuthFailureException("empty certificate chain");
  247. /* Process only server's certificate, not issuer's certificate */
  248. gnutls_x509_crt_t crt;
  249. gnutls_x509_crt_init(&crt);
  250. if (gnutls_x509_crt_import(crt, &cert_list[0], GNUTLS_X509_FMT_DER) < 0)
  251. throw AuthFailureException("decoding of certificate failed");
  252. if (gnutls_x509_crt_check_hostname(crt, client->getServerName()) == 0) {
  253. char buf[255];
  254. vlog.debug("hostname mismatch");
  255. snprintf(buf, sizeof(buf), "Hostname (%s) does not match any certificate, "
  256. "do you want to continue?", client->getServerName());
  257. buf[sizeof(buf) - 1] = '\0';
  258. if (!msg->showMsgBox(UserMsgBox::M_YESNO, "hostname mismatch", buf))
  259. throw AuthFailureException("hostname mismatch");
  260. }
  261. if (status == 0) {
  262. /* Everything is fine (hostname + verification) */
  263. gnutls_x509_crt_deinit(crt);
  264. return;
  265. }
  266. if (status & GNUTLS_CERT_INVALID)
  267. vlog.debug("server certificate invalid");
  268. if (status & GNUTLS_CERT_SIGNER_NOT_FOUND)
  269. vlog.debug("server cert signer not found");
  270. if (status & GNUTLS_CERT_SIGNER_NOT_CA)
  271. vlog.debug("server cert signer not CA");
  272. if ((status & (~allowed_errors)) != 0) {
  273. /* No other errors are allowed */
  274. vlog.debug("GNUTLS status of certificate verification: %u", status);
  275. throw AuthFailureException("Invalid status of server certificate verification");
  276. }
  277. vlog.debug("Saved server certificates don't match");
  278. if (gnutls_x509_crt_print(crt, GNUTLS_CRT_PRINT_ONELINE, &info)) {
  279. /*
  280. * GNUTLS doesn't correctly export gnutls_free symbol which is
  281. * a function pointer. Linking with Visual Studio 2008 Express will
  282. * fail when you call gnutls_free().
  283. */
  284. #if WIN32
  285. free(info.data);
  286. #else
  287. gnutls_free(info.data);
  288. #endif
  289. throw AuthFailureException("Could not find certificate to display");
  290. }
  291. size_t out_size = 0;
  292. char *out_buf = NULL;
  293. char *certinfo = NULL;
  294. int len = 0;
  295. vlog.debug("certificate issuer unknown");
  296. len = snprintf(NULL, 0, "This certificate has been signed by an unknown "
  297. "authority:\n\n%s\n\nDo you want to save it and "
  298. "continue?\n ", info.data);
  299. if (len < 0)
  300. AuthFailureException("certificate decoding error");
  301. vlog.debug("%s", info.data);
  302. certinfo = new char[len];
  303. if (certinfo == NULL)
  304. throw AuthFailureException("Out of memory");
  305. snprintf(certinfo, len, "This certificate has been signed by an unknown "
  306. "authority:\n\n%s\n\nDo you want to save it and "
  307. "continue? ", info.data);
  308. for (int i = 0; i < len - 1; i++)
  309. if (certinfo[i] == ',' && certinfo[i + 1] == ' ')
  310. certinfo[i] = '\n';
  311. if (!msg->showMsgBox(UserMsgBox::M_YESNO, "certificate issuer unknown",
  312. certinfo)) {
  313. delete [] certinfo;
  314. throw AuthFailureException("certificate issuer unknown");
  315. }
  316. delete [] certinfo;
  317. if (gnutls_x509_crt_export(crt, GNUTLS_X509_FMT_PEM, NULL, &out_size)
  318. == GNUTLS_E_SHORT_MEMORY_BUFFER)
  319. AuthFailureException("Out of memory");
  320. // Save cert
  321. out_buf = new char[out_size];
  322. if (out_buf == NULL)
  323. AuthFailureException("Out of memory");
  324. if (gnutls_x509_crt_export(crt, GNUTLS_X509_FMT_PEM, out_buf, &out_size) < 0)
  325. AuthFailureException("certificate issuer unknown, and certificate "
  326. "export failed");
  327. char *homeDir = NULL;
  328. if (getvnchomedir(&homeDir) == -1)
  329. vlog.error("Could not obtain VNC home directory path");
  330. else {
  331. FILE *f;
  332. CharArray caSave(strlen(homeDir) + 1 + 19);
  333. sprintf(caSave.buf, "%sx509_savedcerts.pem", homeDir);
  334. delete [] homeDir;
  335. f = fopen(caSave.buf, "a+");
  336. if (!f)
  337. msg->showMsgBox(UserMsgBox::M_OK, "certificate save failed",
  338. "Could not save the certificate");
  339. else {
  340. fprintf(f, "%s\n", out_buf);
  341. fclose(f);
  342. }
  343. }
  344. delete [] out_buf;
  345. gnutls_x509_crt_deinit(crt);
  346. /*
  347. * GNUTLS doesn't correctly export gnutls_free symbol which is
  348. * a function pointer. Linking with Visual Studio 2008 Express will
  349. * fail when you call gnutls_free().
  350. */
  351. #if WIN32
  352. free(info.data);
  353. #else
  354. gnutls_free(info.data);
  355. #endif
  356. }