Sophie

Sophie

distrib > Mageia > 7 > x86_64 > by-pkgid > 1edd577d2a2bef9c7165846cd9f59fde > files > 5

mutt-1.11.4-1.3.mga7.src.rpm

This patch fixes CVE-2020-14154 where mutt before 1.14.3 proceeds with a
connection even if, in response to a GnuTLS certificate prompt, the user rejects
an expired intermediate certificate.

The patch was applied by fetching the following patches from upstream:
https://github.com/muttmua/mutt/commit/bb0e6277a45a5d4c3a30d3b968eeb31d78124e95
https://github.com/muttmua/mutt/commit/5fccf603ebcf352ba783136d6b2d2600d811fb3b
https://github.com/muttmua/mutt/commit/f64ec1deefb67d471a642004e102cd1c501a1db3

The issue was initially raised (at the time there was no CVE) in
https://bugs.debian.org/961894.

--- a/mutt_ssl_gnutls.c
+++ b/mutt_ssl_gnutls.c
@@ -799,8 +799,8 @@
   return -1;
 }
 
-/*
- * Returns 0 on failure, nonzero on success.
+/* Returns 1 on success.
+ *         0 on failure.
  */
 static int tls_check_one_certificate (const gnutls_datum_t *certdata,
                                       gnutls_certificate_status_t certstat,
@@ -1083,44 +1083,57 @@
   mutt_menuDestroy (&menu);
   gnutls_x509_crt_deinit (cert);
 
-  return (done == 2);
+  return (done == 2) ? 1 : 0;
 }
 
-/* sanity-checking wrapper for gnutls_certificate_verify_peers */
-static gnutls_certificate_status_t tls_verify_peers (gnutls_session_t tlsstate)
+/* sanity-checking wrapper for gnutls_certificate_verify_peers.
+ *
+ * certstat is technically a bitwise-or of gnutls_certificate_status_t
+ * values.
+ *
+ * Returns:
+ *   - 0 if certstat was set. note: this does not mean success.
+ *   - nonzero on failure.
+ */
+static int tls_verify_peers (gnutls_session_t tlsstate,
+                             gnutls_certificate_status_t *certstat)
 {
   int verify_ret;
-  unsigned int status;
 
-  verify_ret = gnutls_certificate_verify_peers2 (tlsstate, &status);
+  /* gnutls_certificate_verify_peers2() chains to
+   * gnutls_x509_trust_list_verify_crt2().  That function's documentation says:
+   *
+   *   When a certificate chain of cert_list_size with more than one
+   *   certificates is provided, the verification status will apply to
+   *   the first certificate in the chain that failed
+   *   verification. The verification process starts from the end of
+   *   the chain (from CA to end certificate). The first certificate
+   *   in the chain must be the end-certificate while the rest of the
+   *   members may be sorted or not.
+   *
+   * This is why tls_check_certificate() loops from CA to host in that order,
+   * calling the menu, and recalling tls_verify_peers() for each approved
+   * cert in the chain.
+   */
+  verify_ret = gnutls_certificate_verify_peers2 (tlsstate, certstat);
+
+  /* certstat was set */
   if (!verify_ret)
-    return status;
+    return 0;
 
-  if (status == GNUTLS_E_NO_CERTIFICATE_FOUND)
-  {
+  if (verify_ret == GNUTLS_E_NO_CERTIFICATE_FOUND)
     mutt_error (_("Unable to get certificate from peer"));
-    mutt_sleep (2);
-    return 0;
-  }
-  if (verify_ret < 0)
-  {
+  else
     mutt_error (_("Certificate verification error (%s)"),
-                gnutls_strerror (status));
-    mutt_sleep (2);
-    return 0;
-  }
+                gnutls_strerror (verify_ret));
 
-  /* We only support X.509 certificates (not OpenPGP) at the moment */
-  if (gnutls_certificate_type_get (tlsstate) != GNUTLS_CRT_X509)
-  {
-    mutt_error (_("Certificate is not X.509"));
-    mutt_sleep (2);
-    return 0;
-  }
-
-  return status;
+  mutt_sleep (2);
+  return verify_ret;
 }
 
+/* Returns 1 on success.
+ *         0 on failure.
+ */
 static int tls_check_certificate (CONNECTION* conn)
 {
   tlssockdata *data = conn->sockdata;
@@ -1129,16 +1142,17 @@
   unsigned int cert_list_size = 0;
   gnutls_certificate_status_t certstat;
   int certerr, i, preauthrc, savedcert, rc = 0;
-  int rcpeer = -1; /* the result of tls_check_preauth() on the peer's EE cert */
+  int max_preauth_pass = -1;
+  int rcsettrust;
 
-  if (gnutls_auth_get_type (state) != GNUTLS_CRD_CERTIFICATE)
-  {
-    mutt_error (_("Unable to get certificate from peer"));
-    mutt_sleep (2);
+  /* tls_verify_peers() calls gnutls_certificate_verify_peers2(),
+   * which verifies the auth_type is GNUTLS_CRD_CERTIFICATE
+   * and that get_certificate_type() for the server is GNUTLS_CRT_X509.
+   * If it returns 0, certstat will be set with failure codes for the first
+   * cert in the chain (from CA to host) with an error.
+   */
+  if (tls_verify_peers (state, &certstat) != 0)
     return 0;
-  }
-
-  certstat = tls_verify_peers (state);
 
   cert_list = gnutls_certificate_get_peers (state, &cert_list_size);
   if (!cert_list)
@@ -1156,13 +1170,8 @@
     rc = tls_check_preauth(&cert_list[i], certstat, conn->account.host, i,
                            &certerr, &savedcert);
     preauthrc += rc;
-    if (i == 0)
-    {
-      /* This is the peer's end-entity X.509 certificate.  Stash the result
-       * to check later in this function.
-       */
-      rcpeer = rc;
-    }
+    if (!preauthrc)
+      max_preauth_pass = i;
 
     if (savedcert)
     {
@@ -1179,18 +1188,24 @@
     rc = tls_check_one_certificate (&cert_list[i], certstat, conn->account.host,
                                     i, cert_list_size);
 
+    /* Stop checking if the menu cert is aborted or rejected. */
+    if (!rc)
+      break;
+
     /* add signers to trust set, then reverify */
-    if (i && rc) {
-      rc = gnutls_certificate_set_x509_trust_mem (data->xcred, &cert_list[i],
-                                                  GNUTLS_X509_FMT_DER);
-      if (rc != 1)
-        dprint (1, (debugfile, "error trusting certificate %d: %d\n", i, rc));
-
-      certstat = tls_verify_peers (state);
-      /* If the cert chain now verifies, and the peer's cert was otherwise
-       * valid (rcpeer==0), we are done.
+    if (i) {
+      rcsettrust = gnutls_certificate_set_x509_trust_mem (data->xcred,
+                                                          &cert_list[i],
+                                                          GNUTLS_X509_FMT_DER);
+      if (rcsettrust != 1)
+        dprint (1, (debugfile, "error trusting certificate %d: %d\n", i, rcsettrust));
+
+      if (tls_verify_peers (state, &certstat) != 0)
+        return 0;
+      /* If the cert chain now verifies, and all lower certs already
+       * passed preauth, we are done.
        */
-      if (!certstat && !rcpeer)
+      if (!certstat && (max_preauth_pass >= i - 1))
         return 1;
     }
   }