From e32573a56b10b4ba0a2c0151647fccbb40481dde Mon Sep 17 00:00:00 2001 From: Adam Tkac Date: Wed, 9 Feb 2011 14:13:41 +0000 Subject: [PATCH] [Bugfix] client: improve server certificate verification code. git-svn-id: svn://svn.code.sf.net/p/tigervnc/code/trunk@4276 3789f03b-4d11-0410-bbf8-ca57d06f2519 --- common/rfb/CSecurityTLS.cxx | 281 +++++++++++++++++++++--------------- 1 file changed, 162 insertions(+), 119 deletions(-) diff --git a/common/rfb/CSecurityTLS.cxx b/common/rfb/CSecurityTLS.cxx index c22fa2d9..7d584690 100644 --- a/common/rfb/CSecurityTLS.cxx +++ b/common/rfb/CSecurityTLS.cxx @@ -65,9 +65,11 @@ StringParameter CSecurityTLS::x509crl("x509crl", "X509 CRL file", "", ConfViewer static LogWriter vlog("TLS"); #ifdef TLS_DEBUG +static LogWriter vlog_raw("Raw TLS"); + static void debug_log(int level, const char* str) { - vlog.debug(str); + vlog_raw.debug(str); } #endif @@ -230,6 +232,22 @@ void CSecurityTLS::setParam() if (*cafile && gnutls_certificate_set_x509_trust_file(cert_cred,cafile,GNUTLS_X509_FMT_PEM) < 0) throw AuthFailureException("load of CA cert failed"); + /* Load previously saved certs */ + char *homeDir = NULL; + int err; + if (getvnchomedir(&homeDir) == -1) + vlog.error("Could not obtain VNC home directory path"); + else { + CharArray caSave(strlen(homeDir) + 19 + 1); + sprintf(caSave.buf, "%sx509_savedcerts.pem", homeDir); + delete [] homeDir; + + err = gnutls_certificate_set_x509_trust_file(cert_cred, caSave.buf, + GNUTLS_X509_FMT_PEM); + if (err < 0) + vlog.debug("Failed to load saved server certificates from %s", caSave.buf); + } + if (*crlfile && gnutls_certificate_set_x509_crl_file(cert_cred,crlfile,GNUTLS_X509_FMT_PEM) < 0) throw AuthFailureException("load of CRL failed"); @@ -242,10 +260,13 @@ void CSecurityTLS::setParam() void CSecurityTLS::checkSession() { - int status; + const unsigned allowed_errors = GNUTLS_CERT_INVALID | + GNUTLS_CERT_SIGNER_NOT_FOUND | + GNUTLS_CERT_SIGNER_NOT_CA; + unsigned int status; const gnutls_datum *cert_list; unsigned int cert_list_size = 0; - unsigned int i; + int err; gnutls_datum info; if (anon) @@ -254,131 +275,71 @@ void CSecurityTLS::checkSession() if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509) throw AuthFailureException("unsupported certificate type"); - cert_list = gnutls_certificate_get_peers(session, &cert_list_size); - if (!cert_list_size) - throw AuthFailureException("unsupported certificate type"); + err = gnutls_certificate_verify_peers2(session, &status); + if (err != 0) { + vlog.error("server certificate verification failed: %s", gnutls_strerror(err)); + throw AuthFailureException("server certificate verification failed"); + } - status = gnutls_certificate_verify_peers(session); - if (status == GNUTLS_E_NO_CERTIFICATE_FOUND) - throw AuthFailureException("no certificate sent"); + if (status & GNUTLS_CERT_REVOKED) + throw AuthFailureException("server certificate has been revoked"); - if (status < 0) { - vlog.error("X509 verify failed: %s\n", gnutls_strerror (status)); - throw AuthFailureException("certificate verification failed"); - } + if (status & GNUTLS_CERT_NOT_ACTIVATED) + throw AuthFailureException("server certificate has not been activated"); - if (status & GNUTLS_CERT_REVOKED) { - throw AuthFailureException("certificate has been revoked"); + if (status & GNUTLS_CERT_EXPIRED) { + vlog.debug("server certificate has expired"); + if (!msg->showMsgBox(UserMsgBox::M_YESNO, "certificate has expired", + "The certificate of the server has expired, " + "do you want to continue?")) + throw AuthFailureException("server certificate has expired"); } + /* Process other errors later */ - if (status & GNUTLS_CERT_NOT_ACTIVATED) { - throw AuthFailureException("certificate has not been activated"); + cert_list = gnutls_certificate_get_peers(session, &cert_list_size); + if (!cert_list_size) + throw AuthFailureException("empty certificate chain"); + + /* Process only server's certificate, not issuer's certificate */ + gnutls_x509_crt crt; + gnutls_x509_crt_init(&crt); + + if (gnutls_x509_crt_import(crt, &cert_list[0], GNUTLS_X509_FMT_DER) < 0) + throw AuthFailureException("decoding of certificate failed"); + + if (gnutls_x509_crt_check_hostname(crt, client->getServerName()) == 0) { + char buf[255]; + vlog.debug("hostname mismatch"); + snprintf(buf, sizeof(buf), "Hostname (%s) does not match any certificate, " + "do you want to continue?", client->getServerName()); + buf[sizeof(buf) - 1] = '\0'; + if (!msg->showMsgBox(UserMsgBox::M_YESNO, "hostname mismatch", buf)) + throw AuthFailureException("hostname mismatch"); } - 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"); - - #if defined(GNUTLS_VERSION_NUMBER) && (GNUTLS_VERSION_NUMBER >= 0x010706) - if (gnutls_x509_crt_print(crt, GNUTLS_CRT_PRINT_ONELINE, &info)) { - /* - * GNUTLS doesn't correctly export gnutls_free symbol which is - * a function pointer. Linking with Visual Studio 2008 Express will - * fail when you call gnutls_free(). - */ -#if WIN32 - free(info.data); -#else - gnutls_free(info.data); -#endif - throw AuthFailureException("Could not find certificate to display"); - } - #endif - - if (gnutls_x509_crt_check_hostname(crt, client->getServerName()) == 0) { - 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 == 0) { + /* Everything is fine (hostname + verification) */ + gnutls_x509_crt_deinit(crt); + return; + } + + if (status & GNUTLS_CERT_INVALID) + vlog.debug("server certificate invalid"); + if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) + vlog.debug("server cert signer not found"); + if (status & GNUTLS_CERT_SIGNER_NOT_CA) + vlog.debug("server cert signer not CA"); + + if ((status & (~allowed_errors)) != 0) { + /* No other errors are allowed */ + vlog.debug("GNUTLS status of certificate verification: %u", status); + throw AuthFailureException("Invalid status of server certificate verification"); + } - 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 (getvnchomedir(&homeDir) == -1) - vlog.error("Could not obtain VNC home directory path"); - else { - FILE *f; - CharArray caSave(strlen(homeDir) + 1 + 19); - sprintf(caSave.buf, "%sx509_savedcerts.pem", 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"); + vlog.debug("Saved server certificates don't match"); - gnutls_x509_crt_deinit(crt); + #if defined(GNUTLS_VERSION_NUMBER) && (GNUTLS_VERSION_NUMBER >= 0x010706) + if (gnutls_x509_crt_print(crt, GNUTLS_CRT_PRINT_ONELINE, &info)) { /* * GNUTLS doesn't correctly export gnutls_free symbol which is * a function pointer. Linking with Visual Studio 2008 Express will @@ -389,6 +350,88 @@ void CSecurityTLS::checkSession() #else gnutls_free(info.data); #endif + throw AuthFailureException("Could not find certificate to display"); + } + #endif + + size_t out_size; + 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"); + + char *homeDir = NULL; + if (getvnchomedir(&homeDir) == -1) + vlog.error("Could not obtain VNC home directory path"); + else { + FILE *f; + CharArray caSave(strlen(homeDir) + 1 + 19); + sprintf(caSave.buf, "%sx509_savedcerts.pem", 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; + + gnutls_x509_crt_deinit(crt); + /* + * GNUTLS doesn't correctly export gnutls_free symbol which is + * a function pointer. Linking with Visual Studio 2008 Express will + * fail when you call gnutls_free(). + */ +#if WIN32 + free(info.data); +#else + gnutls_free(info.data); +#endif } -- 2.39.5