Package: release.debian.org Severity: normal Tags: buster User: release.debian....@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.f...@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 <oss...@cendio.se> and Brian P. Hinz <bph...@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