[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
diff --git a/common/rfb/CSecurityTLS.cxx b/common/rfb/CSecurityTLS.cxx
index c22fa2d..7d58469 100644
--- a/common/rfb/CSecurityTLS.cxx
+++ b/common/rfb/CSecurityTLS.cxx
@@ -65,9 +65,11 @@
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 @@
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::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 @@
if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509)
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");
+ }
+
+ if (status & GNUTLS_CERT_REVOKED)
+ throw AuthFailureException("server certificate has been revoked");
+
+ if (status & GNUTLS_CERT_NOT_ACTIVATED)
+ throw AuthFailureException("server certificate has not been activated");
+
+ 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 */
+
cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
if (!cert_list_size)
- throw AuthFailureException("unsupported certificate type");
+ throw AuthFailureException("empty certificate chain");
- status = gnutls_certificate_verify_peers(session);
- if (status == GNUTLS_E_NO_CERTIFICATE_FOUND)
- throw AuthFailureException("no certificate sent");
+ /* Process only server's certificate, not issuer's certificate */
+ gnutls_x509_crt crt;
+ gnutls_x509_crt_init(&crt);
- if (status < 0) {
- vlog.error("X509 verify failed: %s\n", gnutls_strerror (status));
- throw AuthFailureException("certificate verification failed");
+ 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");
}
- if (status & GNUTLS_CERT_REVOKED) {
- throw AuthFailureException("certificate has been revoked");
- }
-
- 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");
-
- #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 & 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");
-
+ 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");
+ }
+
+ vlog.debug("Saved server certificates don't match");
+
+ #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 @@
#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
}