[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
 }