From 27b2f773be4db49762d78635c2c322a95bd141ce Mon Sep 17 00:00:00 2001 From: Adam Tkac Date: Thu, 18 Nov 2010 13:33:57 +0000 Subject: [PATCH] [Development] client: Add dialog window to accept/save invalid X509 certificates. (Guillaume Destuynder) git-svn-id: svn://svn.code.sf.net/p/tigervnc/code/trunk@4198 3789f03b-4d11-0410-bbf8-ca57d06f2519 --- common/rfb/CSecurityTLS.cxx | 126 +++++++++++++++++++++++++++++++--- common/rfb/CSecurityTLS.h | 4 ++ common/rfb/SecurityClient.cxx | 12 ++++ common/rfb/SecurityClient.h | 2 + common/rfb/UserMsgBox.h | 1 + unix/vncviewer/CConn.cxx | 15 +++- unix/vncviewer/CConn.h | 6 +- unix/vncviewer/vncviewer.cxx | 3 + 8 files changed, 158 insertions(+), 11 deletions(-) diff --git a/common/rfb/CSecurityTLS.cxx b/common/rfb/CSecurityTLS.cxx index 651f8521..12273a60 100644 --- a/common/rfb/CSecurityTLS.cxx +++ b/common/rfb/CSecurityTLS.cxx @@ -2,6 +2,7 @@ * Copyright (C) 2004 Red Hat Inc. * Copyright (C) 2005 Martin Koegler * Copyright (C) 2010 TigerVNC Team + * Copyright (C) 2010 m-privacy GmbH * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -27,13 +28,18 @@ #error "This header should not be compiled without HAVE_GNUTLS defined" #endif +#include +#include + #include #include #include #include #include +#include #include #include +#include #include @@ -76,6 +82,26 @@ CSecurityTLS::CSecurityTLS(bool _anon) : session(0), anon_cred(0), crlfile = x509crl.getData(); } +void CSecurityTLS::setDefaults() +{ + char* homeDir = NULL; + + if (gethomedir(&homeDir) == -1) { + vlog.error("Could not obtain home directory path"); + return; + } + + CharArray caDefault(strlen(homeDir)+17); + sprintf(caDefault.buf, "%s/.vnc/x509_certs", homeDir); + delete [] homeDir; + + /* XXX Do we need access() check here? */ + if (!access(caDefault.buf, R_OK)) + x509ca.setDefaultStr(strdup(caDefault.buf)); + else + vlog.error("Failed to open ~/.vnc/x509_certs"); +} + void CSecurityTLS::shutdown() { if (session) @@ -206,6 +232,7 @@ void CSecurityTLS::checkSession() const gnutls_datum *cert_list; unsigned int cert_list_size = 0; unsigned int i; + gnutls_datum_t info; if (anon) return; @@ -226,25 +253,108 @@ void CSecurityTLS::checkSession() throw AuthFailureException("certificate verification failed"); } - if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) - throw AuthFailureException("certificate issuer unknown"); + if (status & GNUTLS_CERT_REVOKED) { + throw AuthFailureException("certificate has been revoked"); + } - if (status & GNUTLS_CERT_INVALID) - throw AuthFailureException("certificate not trusted"); + if (status & GNUTLS_CERT_NOT_ACTIVATED) { + throw AuthFailureException("certificate has not been activated"); + } for (i = 0; i < cert_list_size; i++) { gnutls_x509_crt crt; gnutls_x509_crt_init(&crt); if (gnutls_x509_crt_import(crt, &cert_list[i],GNUTLS_X509_FMT_DER) < 0) - throw AuthFailureException("Decoding of certificate failed"); + throw AuthFailureException("decoding of certificate failed"); + + if (gnutls_x509_crt_print(crt, GNUTLS_CRT_PRINT_ONELINE, &info)) { + gnutls_free(info.data); + throw AuthFailureException("Could not find certificate to display"); + } if (gnutls_x509_crt_check_hostname(crt, client->getServerName()) == 0) { -#if 0 - throw AuthFailureException("Hostname mismatch"); /* Non-fatal for now... */ -#endif + char buf[255]; + sprintf(buf, "Hostname (%s) does not match any certificate, do you want to continue?", client->getServerName()); + vlog.debug("hostname mismatch"); + if(!msg->showMsgBox(UserMsgBox::M_YESNO, "hostname mismatch", buf)) + throw AuthFailureException("hostname mismatch"); } + + if (status & GNUTLS_CERT_EXPIRED) { + vlog.debug("certificate has expired"); + if (!msg->showMsgBox(UserMsgBox::M_YESNO, "certficate has expired", "The certificate of the server has expired, do you want to continue?")) + throw AuthFailureException("certificate has expired"); + } + + if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) { + size_t out_size; + char *homeDir = NULL; + char *out_buf = NULL; + char *certinfo = NULL; + int len = 0; + + vlog.debug("certificate issuer unknown"); + + len = snprintf(NULL, 0, "This certificate has been signed by an unknown authority:\n\n%s\n\nDo you want to save it and continue?\n ", info.data); + if (len < 0) + AuthFailureException("certificate decoding error"); + + vlog.debug("%s", info.data); + + certinfo = new char[len]; + if (certinfo == NULL) + throw AuthFailureException("Out of memory"); + + snprintf(certinfo, len, "This certificate has been signed by an unknown authority:\n\n%s\n\nDo you want to save it and continue? ", info.data); + + for (int i = 0; i < len - 1; i++) + if (certinfo[i] == ',' && certinfo[i + 1] == ' ') + certinfo[i] = '\n'; + + if (!msg->showMsgBox(UserMsgBox::M_YESNO, "certificate issuer unknown", + certinfo)) { + delete [] certinfo; + throw AuthFailureException("certificate issuer unknown"); + } + delete [] certinfo; + + if (gnutls_x509_crt_export(crt, GNUTLS_X509_FMT_PEM, NULL, &out_size) + == GNUTLS_E_SHORT_MEMORY_BUFFER) + AuthFailureException("Out of memory"); + + // Save cert + out_buf = new char[out_size]; + if (out_buf == NULL) + AuthFailureException("Out of memory"); + + if (gnutls_x509_crt_export(crt, GNUTLS_X509_FMT_PEM, out_buf, &out_size) + < 0) + AuthFailureException("certificate issuer unknown, and certificate " + "export failed"); + + if (gethomedir(&homeDir) == -1) + vlog.error("Could not obtain home directory path"); + else { + FILE *f; + CharArray caSave(strlen(homeDir)+17); + sprintf(caSave.buf, "%s/.vnc/x509_certs", homeDir); + delete [] homeDir; + f = fopen(caSave.buf, "a+"); + if (!f) + msg->showMsgBox(UserMsgBox::M_OK, "certificate save failed", + "Could not save the certificate"); + else { + fprintf(f, "%s\n", out_buf); + fclose(f); + } + } + delete [] out_buf; + } else if (status & GNUTLS_CERT_INVALID) + throw AuthFailureException("certificate not trusted"); + gnutls_x509_crt_deinit(crt); + gnutls_free(info.data); } } diff --git a/common/rfb/CSecurityTLS.h b/common/rfb/CSecurityTLS.h index 1ff7ac0d..bd6297d7 100644 --- a/common/rfb/CSecurityTLS.h +++ b/common/rfb/CSecurityTLS.h @@ -33,11 +33,13 @@ #include #include #include +#include #include #include #include namespace rfb { + class UserMsgBox; class CSecurityTLS : public CSecurity { public: CSecurityTLS(bool _anon); @@ -46,9 +48,11 @@ namespace rfb { virtual int getType() const { return anon ? secTypeTLSNone : secTypeX509None; } virtual const char* description() const { return anon ? "TLS Encryption without VncAuth" : "X509 Encryption without VncAuth"; } + static void setDefaults(); static StringParameter x509ca; static StringParameter x509crl; + static UserMsgBox *msg; protected: void shutdown(); diff --git a/common/rfb/SecurityClient.cxx b/common/rfb/SecurityClient.cxx index e5e428c6..0b69298e 100644 --- a/common/rfb/SecurityClient.cxx +++ b/common/rfb/SecurityClient.cxx @@ -37,6 +37,9 @@ using namespace rdr; using namespace rfb; UserPasswdGetter *CSecurity::upg = NULL; +#ifdef HAVE_GNUTLS +UserMsgBox *CSecurityTLS::msg = NULL; +#endif StringParameter SecurityClient::secTypes ("SecurityTypes", @@ -51,6 +54,9 @@ ConfViewer); CSecurity* SecurityClient::GetCSecurity(U32 secType) { assert (CSecurity::upg != NULL); /* (upg == NULL) means bug in the viewer */ +#ifdef HAVE_GNUTLS + assert (CSecurityTLS::msg != NULL); +#endif if (!IsSupported(secType)) goto bail; @@ -86,3 +92,9 @@ bail: throw Exception("Security type not supported"); } +void SecurityClient::setDefaults() +{ +#ifdef HAVE_GNUTLS + CSecurityTLS::setDefaults(); +#endif +} diff --git a/common/rfb/SecurityClient.h b/common/rfb/SecurityClient.h index c30fdfcd..b8ad8315 100644 --- a/common/rfb/SecurityClient.h +++ b/common/rfb/SecurityClient.h @@ -35,6 +35,8 @@ namespace rfb { /* Create client side CSecurity class instance */ CSecurity* GetCSecurity(rdr::U32 secType); + static void setDefaults(void); + static StringParameter secTypes; }; diff --git a/common/rfb/UserMsgBox.h b/common/rfb/UserMsgBox.h index 8b29587f..392950d1 100644 --- a/common/rfb/UserMsgBox.h +++ b/common/rfb/UserMsgBox.h @@ -33,6 +33,7 @@ namespace rfb { M_DEFBUTTON1 = 0, M_DEFBUTTON2 = 0x100 }; + /* TODO Implement as function with variable arguments */ virtual bool showMsgBox(int flags,const char* title, const char* text)=0; }; } diff --git a/unix/vncviewer/CConn.cxx b/unix/vncviewer/CConn.cxx index 1a75a11d..8a647870 100644 --- a/unix/vncviewer/CConn.cxx +++ b/unix/vncviewer/CConn.cxx @@ -75,7 +75,10 @@ CConn::CConn(Display* dpy_, int argc_, char** argv_, network::Socket* sock_, menuKeysym = XStringToKeysym(menuKeyStr.buf); setShared(shared); - CSecurity::upg = this; /* Security instance is created in CConnection costructor. */ + CSecurity::upg = this; /* Security instance is created in CConnection constructor. */ +#ifdef HAVE_GNUTLS + CSecurityTLS::msg = this; +#endif CharArray encStr(preferredEncoding.getData()); int encNum = encodingNum(encStr.buf); @@ -125,6 +128,15 @@ CConn::~CConn() { delete sock; } +bool CConn::showMsgBox(int flags, const char* title, const char* text) +{ + CharArray titleText(strlen(title) + 12); + sprintf(titleText.buf, "VNC Viewer: %s", title); + + TXMsgBox msgBox(dpy,text,flags,titleText.buf); + return msgBox.show(); +} + // deleteWindow() is called when the user closes the desktop or menu windows. void CConn::deleteWindow(TXWindow* w) { @@ -232,7 +244,6 @@ void CConn::getUserPasswd(char** user, char** password) *password = strDup(dlg.passwdEntry.getText()); } - // CConnection callback methods // serverInit() is called when the serverInit message has been received. At diff --git a/unix/vncviewer/CConn.h b/unix/vncviewer/CConn.h index ac889637..929e5b55 100644 --- a/unix/vncviewer/CConn.h +++ b/unix/vncviewer/CConn.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -44,7 +45,7 @@ class CConn : public rfb::CConnection, public rfb::UserPasswdGetter, public TXDeleteWindowCallback, public rdr::FdInStreamBlockCallback, public TXMenuCallback , public OptionsDialogCallback, - public TXEventHandler + public TXEventHandler, public rfb::UserMsgBox { public: @@ -61,6 +62,9 @@ public: // UserPasswdGetter methods virtual void getUserPasswd(char** user, char** password); + // UserMsgBox methods + virtual bool showMsgBox(int flags, const char* title, const char* text); + // TXMenuCallback methods void menuSelect(long id, TXMenu* m); diff --git a/unix/vncviewer/vncviewer.cxx b/unix/vncviewer/vncviewer.cxx index 9edb80d3..f3a5e01a 100644 --- a/unix/vncviewer/vncviewer.cxx +++ b/unix/vncviewer/vncviewer.cxx @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include "TXWindow.h" @@ -278,6 +279,8 @@ int main(int argc, char** argv) "Copyright (C) 2004-2009 Peter Astrand for Cendio AB\n" "See http://www.tigervnc.org for information on TigerVNC."); + rfb::SecurityClient::setDefaults(); + // Write about text to console, still using normal locale codeset snprintf(aboutText, sizeof(aboutText), gettext(englishAbout), PACKAGE_VERSION, buildtime); -- 2.39.5