[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#971392: marked as done (buster-pu: package tigervnc/1.9.0+dfsg-3+deb10u3)



Your message dated Sat, 05 Dec 2020 11:02:00 +0000
with message-id <b70f86aac27195271a9b5212c7acc936da6ff100.camel@adam-barratt.org.uk>
and subject line Closing bugs for updates in 10.7 point release
has caused the Debian Bug report #971392,
regarding buster-pu: package tigervnc/1.9.0+dfsg-3+deb10u3
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact owner@bugs.debian.org
immediately.)


-- 
971392: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=971392
Debian Bug Tracking System
Contact owner@bugs.debian.org with problems
--- Begin Message ---
Package: release.debian.org
Severity: normal
Tags: buster
User: release.debian.org@packages.debian.org
Usertags: pu

Security fix for CVE-2020-26117 as detailed in bug #971272.

-- System Information:
Debian Release: 10.6
  APT prefers stable-updates
  APT policy: (500, 'stable-updates'), (500, 'stable')
Architecture: amd64 (x86_64)

Kernel: Linux 4.19.0-9-amd64 (SMP w/16 CPU cores)
Kernel taint flags: TAINT_WARN
Locale: LANG=de_DE.UTF-8, LC_CTYPE=de_DE.UTF-8 (charmap=UTF-8), LANGUAGE=de_DE.UTF-8 (charmap=UTF-8)
Shell: /bin/sh linked to /bin/dash
Init: systemd (via /run/systemd/system)
LSM: AppArmor: enabled
diff -Nru tigervnc-1.9.0+dfsg/debian/changelog tigervnc-1.9.0+dfsg/debian/changelog
--- tigervnc-1.9.0+dfsg/debian/changelog	2020-06-16 21:36:31.000000000 +0200
+++ tigervnc-1.9.0+dfsg/debian/changelog	2020-09-29 20:21:20.000000000 +0200
@@ -1,3 +1,13 @@
+tigervnc (1.9.0+dfsg-3+deb10u3) buster; urgency=high
+
+  [ Joachim Falk ]
+  * Properly store certificate exceptions in native and java VNC viewer. The
+    VNC viewers stored the certificate exceptions as authorities, meaning that
+    the owner of a certificate could impersonate any server after a client had
+    added an exception. This is issue CVE-2020-26117 (Closes: #971272).
+
+ -- Joachim Falk <joachim.falk@gmx.de>  Tue, 29 Sep 2020 20:21:20 +0200
+
 tigervnc (1.9.0+dfsg-3+deb10u2) buster; urgency=medium

   [ Joachim Falk ]
diff -Nru tigervnc-1.9.0+dfsg/debian/patches/CVE-2020-26117.patch tigervnc-1.9.0+dfsg/debian/patches/CVE-2020-26117.patch
--- tigervnc-1.9.0+dfsg/debian/patches/CVE-2020-26117.patch	1970-01-01 01:00:00.000000000 +0100
+++ tigervnc-1.9.0+dfsg/debian/patches/CVE-2020-26117.patch	2020-09-29 20:21:20.000000000 +0200
@@ -0,0 +1,460 @@
+Description: Properly store certificate exceptions in native and java VNC viewer.
+ They stored the certificates as authorities, meaning that the owner of a
+ certificate could impersonate any server after a client had added an exception.
+Author: Pierre Ossman <ossman@cendio.se> and Brian P. Hinz <bphinz@users.sf.net>
+Abstract:
+ Properly store certificate exceptions
+ .
+ . git commit b30f10c681ec87720cff85d490f67098568a9cba
+ .
+ The previous method stored the certificates as authorities, meaning that
+ the owner of that certificate could impersonate any server it wanted
+ after a client had added an exception.
+ .
+ Handle this more properly by only storing exceptions for specific
+ hostname/certificate combinations, the same way browsers or SSH does
+ things.
+ .
+ . git commit f029745f63ac7d22fb91639b2cb5b3ab56134d6e
+ .
+ Like the native viewer, the Java viewer didn't store certificate
+ exceptions properly. Whilst not as bad as the native viewer, it still
+ failed to check that a stored certificate wouldn't be maliciously used
+ for another server. In practice this can in most cases be used to
+ impersonate another server.
+ .
+ Handle this like the native viewer by storing exceptions for a specific
+ hostname/certificate combination.
+ .
+ This issue is CVE-2020-26117.
+
+Index: pkg-tigervnc/common/rfb/CSecurityTLS.cxx
+===================================================================
+--- pkg-tigervnc.orig/common/rfb/CSecurityTLS.cxx
++++ pkg-tigervnc/common/rfb/CSecurityTLS.cxx
+@@ -232,22 +232,6 @@ 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");
+
+@@ -272,7 +256,10 @@ void CSecurityTLS::checkSession()
+   const gnutls_datum_t *cert_list;
+   unsigned int cert_list_size = 0;
+   int err;
++
++  char *homeDir;
+   gnutls_datum_t info;
++  size_t len;
+
+   if (anon)
+     return;
+@@ -315,13 +302,13 @@ void CSecurityTLS::checkSession()
+     throw AuthFailureException("decoding of certificate failed");
+
+   if (gnutls_x509_crt_check_hostname(crt, client->getServerName()) == 0) {
+-    char buf[255];
++    CharArray text;
+     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");
++    text.format("Hostname (%s) does not match the server certificate, "
++                "do you want to continue?", client->getServerName());
++    if (!msg->showMsgBox(UserMsgBox::M_YESNO,
++                         "Certificate hostname mismatch", text.buf))
++      throw AuthFailureException("Certificate hostname mismatch");
+   }
+
+   if (status == 0) {
+@@ -348,87 +335,80 @@ void CSecurityTLS::checkSession()
+
+   vlog.debug("Saved server certificates don't match");
+
+-  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");
++  homeDir = NULL;
++  if (getvnchomedir(&homeDir) == -1) {
++    throw AuthFailureException("Could not obtain VNC home directory "
++                               "path for known hosts storage");
++  }
++
++  CharArray dbPath(strlen(homeDir) + 16 + 1);
++  sprintf(dbPath.buf, "%sx509_known_hosts", homeDir);
++  delete [] homeDir;
++
++  err = gnutls_verify_stored_pubkey(dbPath.buf, NULL,
++                                    client->getServerName(), NULL,
++                                    GNUTLS_CRT_X509, &cert_list[0], 0);
++
++  /* Previously known? */
++  if (err == GNUTLS_E_SUCCESS) {
++    vlog.debug("Server certificate found in known hosts file");
++    gnutls_x509_crt_deinit(crt);
++    return;
+   }
+
+-  size_t out_size = 0;
+-  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);
+-    }
++  if ((err != GNUTLS_E_NO_CERTIFICATE_FOUND) &&
++      (err != GNUTLS_E_CERTIFICATE_KEY_MISMATCH)) {
++    throw AuthFailureException("Could not load known hosts database");
+   }
+
+-  delete [] out_buf;
++  if (gnutls_x509_crt_print(crt, GNUTLS_CRT_PRINT_ONELINE, &info))
++    throw AuthFailureException("Could not find certificate to display");
++
++  len = strlen((char*)info.data);
++  for (size_t i = 0; i < len - 1; i++) {
++    if (info.data[i] == ',' && info.data[i + 1] == ' ')
++      info.data[i] = '\n';
++  }
++
++  /* New host */
++  if (err == GNUTLS_E_NO_CERTIFICATE_FOUND) {
++    CharArray text;
++
++    vlog.debug("Server host not previously known");
++    vlog.debug("%s", info.data);
++
++    text.format("This certificate has been signed by an unknown "
++                "authority:\n\n%s\n\nSomeone could be trying to "
++                "impersonate the site and you should not "
++                "continue.\n\nDo you want to make an exception "
++                "for this server?", info.data);
++
++    if (!msg->showMsgBox(UserMsgBox::M_YESNO,
++                         "Unknown certificate issuer",
++                         text.buf))
++      throw AuthFailureException("Unknown certificate issuer");
++  } else if (err == GNUTLS_E_CERTIFICATE_KEY_MISMATCH) {
++    CharArray text;
++
++    vlog.debug("Server host key mismatch");
++    vlog.debug("%s", info.data);
++
++    text.format("This host is previously known with a different "
++                "certificate, and the new certificate has been "
++                "signed by an unknown authority:\n\n%s\n\nSomeone "
++                "could be trying to impersonate the site and you "
++                "should not continue.\n\nDo you want to make an "
++                "exception for this server?", info.data);
++
++    if (!msg->showMsgBox(UserMsgBox::M_YESNO,
++                         "Unexpected server certificate",
++                         text.buf))
++      throw AuthFailureException("Unexpected server certificate");
++  }
++
++  if (gnutls_store_pubkey(dbPath.buf, NULL, client->getServerName(),
++                          NULL, GNUTLS_CRT_X509, &cert_list[0], 0, 0))
++    vlog.error("Failed to store server certificate to known hosts database");
+
+   gnutls_x509_crt_deinit(crt);
+   /*
+Index: pkg-tigervnc/java/com/tigervnc/rfb/CSecurityTLS.java
+===================================================================
+--- pkg-tigervnc.orig/java/com/tigervnc/rfb/CSecurityTLS.java
++++ pkg-tigervnc/java/com/tigervnc/rfb/CSecurityTLS.java
+@@ -107,12 +107,6 @@ public class CSecurityTLS extends CSecur
+       X509CRL.setDefaultStr(getDefaultCRL());
+   }
+
+-// FIXME:
+-// Need to shutdown the connection cleanly
+-
+-// FIXME?
+-// add a finalizer method that calls shutdown
+-
+   public boolean processMsg(CConnection cc) {
+     is = (FdInStream)cc.getInStream();
+     os = (FdOutStream)cc.getOutStream();
+@@ -257,8 +251,13 @@ public class CSecurityTLS extends CSecur
+     {
+       Collection<? extends Certificate> certs = null;
+       X509Certificate cert = chain[0];
++      String pk =
++        Base64.getEncoder().encodeToString(cert.getPublicKey().getEncoded());
+       try {
+         cert.checkValidity();
++        verifyHostname(cert);
++      } catch(CertificateParsingException e) {
++        throw new SystemException(e.getMessage());
+       } catch(CertificateNotYetValidException e) {
+         throw new AuthFailureException("server certificate has not been activated");
+       } catch(CertificateExpiredException e) {
+@@ -267,73 +266,111 @@ public class CSecurityTLS extends CSecur
+ 			      "do you want to continue?"))
+           throw new AuthFailureException("server certificate has expired");
+       }
+-      String thumbprint = getThumbprint(cert);
+       File vncDir = new File(FileUtils.getVncHomeDir());
+-      File certFile = new File(vncDir, "x509_savedcerts.pem");
+-      CertificateFactory cf = CertificateFactory.getInstance("X.509");
+-      if (vncDir.exists() && certFile.exists() && certFile.canRead()) {
+-        InputStream certStream = new MyFileInputStream(certFile);
+-        certs = cf.generateCertificates(certStream);
+-        for (Certificate c : certs)
+-          if (thumbprint.equals(getThumbprint((X509Certificate)c)))
+-            return;
+-      }
++      if (!vncDir.exists())
++        throw new AuthFailureException("Could not obtain VNC home directory "+
++                                       "path for known hosts storage");
++      File dbPath = new File(vncDir, "x509_known_hosts");
++      String info =
++        "  Subject: "+cert.getSubjectX500Principal().getName()+"\n"+
++        "  Issuer: "+cert.getIssuerX500Principal().getName()+"\n"+
++        "  Serial Number: "+cert.getSerialNumber()+"\n"+
++        "  Version: "+cert.getVersion()+"\n"+
++        "  Signature Algorithm: "+cert.getPublicKey().getAlgorithm()+"\n"+
++        "  Not Valid Before: "+cert.getNotBefore()+"\n"+
++        "  Not Valid After: "+cert.getNotAfter()+"\n"+
++        "  SHA-1 Fingerprint: "+getThumbprint(cert)+"\n";
+       try {
+-        verifyHostname(cert);
++        if (dbPath.exists()) {
++          FileReader db = new FileReader(dbPath);
++          BufferedReader dbBuf = new BufferedReader(db);
++          String line;
++          String server = client.getServerName().toLowerCase();
++          while ((line = dbBuf.readLine())!=null) {
++            String fields[] = line.split("\\|");
++            if (fields.length==6) {
++              if (server.equals(fields[2]) && pk.equals(fields[5])) {
++                vlog.debug("Server certificate found in known hosts file");
++                dbBuf.close();
++                return;
++              } else if (server.equals(fields[2]) && !pk.equals(fields[5]) ||
++                         !server.equals(fields[2]) && pk.equals(fields[5])) {
++                throw new CertStoreException();
++              }
++            }
++          }
++          dbBuf.close();
++        }
+         tm.checkServerTrusted(chain, authType);
++      } catch (IOException e) {
++        throw new AuthFailureException("Could not load known hosts database");
++      } catch (CertStoreException e) {
++        vlog.debug("Server host key mismatch");
++        vlog.debug(info);
++        String text =
++          "This host is previously known with a different "+
++          "certificate, and the new certificate has been "+
++          "signed by an unknown authority\n"+
++          "\n"+info+"\n"+
++          "Someone could be trying to impersonate the site and you should not continue.\n"+
++          "\n"+
++          "Do you want to make an exception for this server?";
++        if (!msg.showMsgBox(YES_NO_OPTION, "Unexpected certificate issuer", text))
++          throw new AuthFailureException("Unexpected certificate issuer");
++        store_pubkey(dbPath, client.getServerName().toLowerCase(), pk);
+       } catch (java.lang.Exception e) {
+         if (e.getCause() instanceof CertPathBuilderException) {
+-          String certinfo =
++          vlog.debug("Server host not previously known");
++          vlog.debug(info);
++          String text =
+             "This certificate has been signed by an unknown authority\n"+
++            "\n"+info+"\n"+
++            "Someone could be trying to impersonate the site and you should not continue.\n"+
+             "\n"+
+-            "  Subject: "+cert.getSubjectX500Principal().getName()+"\n"+
+-            "  Issuer: "+cert.getIssuerX500Principal().getName()+"\n"+
+-            "  Serial Number: "+cert.getSerialNumber()+"\n"+
+-            "  Version: "+cert.getVersion()+"\n"+
+-            "  Signature Algorithm: "+cert.getPublicKey().getAlgorithm()+"\n"+
+-            "  Not Valid Before: "+cert.getNotBefore()+"\n"+
+-            "  Not Valid After: "+cert.getNotAfter()+"\n"+
+-            "  SHA1 Fingerprint: "+getThumbprint(cert)+"\n"+
+-            "\n"+
+-            "Do you want to save it and continue?";
+-          if (!msg.showMsgBox(YES_NO_OPTION, "certificate issuer unknown",
+-                certinfo)) {
+-            throw new AuthFailureException("certificate issuer unknown");
+-          }
+-          if (certs == null || !certs.contains(cert)) {
+-            byte[] der = cert.getEncoded();
+-            String pem = DatatypeConverter.printBase64Binary(der);
+-            pem = pem.replaceAll("(.{64})", "$1\n");
+-            FileWriter fw = null;
+-            try {
+-              if (!vncDir.exists())
+-                vncDir.mkdir();
+-              if (!certFile.exists() && !certFile.createNewFile()) {
+-                vlog.error("Certificate save failed.");
+-              } else {
+-                fw = new FileWriter(certFile.getAbsolutePath(), true);
+-                fw.write("-----BEGIN CERTIFICATE-----\n");
+-                fw.write(pem+"\n");
+-                fw.write("-----END CERTIFICATE-----\n");
+-              }
+-            } catch (IOException ioe) {
+-              msg.showMsgBox(OK_OPTION, "certificate save failed",
+-                             "Could not save the certificate");
+-            } finally {
+-              try {
+-                if (fw != null)
+-                  fw.close();
+-              } catch(IOException ioe2) {
+-                throw new Exception(ioe2.getMessage());
+-              }
+-            }
+-          }
++            "Do you want to make an exception for this server?";
++          if (!msg.showMsgBox(YES_NO_OPTION, "Unknown certificate issuer", text))
++            throw new AuthFailureException("Unknown certificate issuer");
++          store_pubkey(dbPath, client.getServerName().toLowerCase(), pk);
+         } else {
+           throw new SystemException(e.getMessage());
+         }
+       }
+     }
+
++    private void store_pubkey(File dbPath, String serverName, String pk)
++    {
++      ArrayList<String> lines = new ArrayList<String>();
++      File vncDir = new File(FileUtils.getVncHomeDir());
++      try {
++        if (dbPath.exists()) {
++          FileReader db = new FileReader(dbPath);
++          BufferedReader dbBuf = new BufferedReader(db);
++          String line;
++          while ((line = dbBuf.readLine())!=null) {
++            String fields[] = line.split("\\|");
++            if (fields.length==6)
++              if (!serverName.equals(fields[2]) && !pk.equals(fields[5]))
++                lines.add(line);
++          }
++          dbBuf.close();
++        }
++      } catch (IOException e) {
++        throw new AuthFailureException("Could not load known hosts database");
++      }
++      try {
++        if (!dbPath.exists())
++          dbPath.createNewFile();
++        FileWriter fw = new FileWriter(dbPath.getAbsolutePath(), false);
++        Iterator i = lines.iterator();
++        while (i.hasNext())
++          fw.write((String)i.next()+"\n");
++        fw.write("|g0|"+serverName+"|*|0|"+pk+"\n");
++        fw.close();
++      } catch (IOException e) {
++        vlog.error("Failed to store server certificate to known hosts database");
++      }
++    }
++
+     public X509Certificate[] getAcceptedIssuers ()
+     {
+       return tm.getAcceptedIssuers();
+@@ -389,12 +426,13 @@ public class CSecurityTLS extends CSecur
+         }
+         Object[] answer = {"YES", "NO"};
+         int ret = JOptionPane.showOptionDialog(null,
+-          "Hostname verification failed. Do you want to continue?",
+-          "Hostname Verification Failure",
++          "Hostname ("+client.getServerName()+") does not match the"+
++          " server certificate, do you want to continue?",
++          "Certificate hostname mismatch",
+           JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE,
+           null, answer, answer[0]);
+         if (ret != JOptionPane.YES_OPTION)
+-          throw new WarningException("Hostname verification failed.");
++          throw new WarningException("Certificate hostname mismatch.");
+       } catch (CertificateParsingException e) {
+         throw new SystemException(e.getMessage());
+       } catch (InvalidNameException e) {
diff -Nru tigervnc-1.9.0+dfsg/debian/patches/series tigervnc-1.9.0+dfsg/debian/patches/series
--- tigervnc-1.9.0+dfsg/debian/patches/series	2020-06-16 21:34:14.000000000 +0200
+++ tigervnc-1.9.0+dfsg/debian/patches/series	2020-09-29 20:21:20.000000000 +0200
@@ -40,3 +40,4 @@
 CVE-2019-15693.patch
 CVE-2019-15694.patch
 CVE-2019-15695.patch
+CVE-2020-26117.patch

--- End Message ---
--- Begin Message ---
Package: release.debian.org
Version: 10.7

Hi,

Each of the updates referenced by these bugs was included in this
morning's buster 10.7 point release.

Regards,

Adam

--- End Message ---

Reply to: