From f64ec1deefb67d471a642004e102cd1c501a1db3 Mon Sep 17 00:00:00 2001
From: Kevin McCarthy <kevin@8t8.us>
Date: Sat, 6 Jun 2020 20:03:56 -0700
Subject: [PATCH] Fix GnuTLS interactive prompt short-circuiting.

tls_verify_peers() doesn't verify expiration dates.  So aborting early
because of a 0 certstat and the leaf passing tls_check_preauth() does
not mean subsequent intermediate certs are okay: they could beexpired.

In the saved-cert preauth loop, instead of just noting the
tls_check_preauth() rc for the leaf, note the highest cert that passes
preauth.

Then, in the interactive loop (which goes in the opposite order, from
CA to leaf) check that value instead.  Since we are trusting certs one
by one, anything that passed in the previous loop will certainly pass
the preauth check at the beginning of tls_check_one_certificate().
---
 mutt_ssl_gnutls.c | 17 ++++++-----------
 1 file changed, 6 insertions(+), 11 deletions(-)

diff --git a/mutt_ssl_gnutls.c b/mutt_ssl_gnutls.c
index 6f98f50..09d628a 100644
--- a/mutt_ssl_gnutls.c
+++ b/mutt_ssl_gnutls.c
@@ -1148,7 +1148,7 @@ static int tls_check_certificate (CONNECTION* conn)
   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;
 
   /* tls_verify_peers() calls gnutls_certificate_verify_peers2(),
@@ -1176,13 +1176,8 @@ static int tls_check_certificate (CONNECTION* conn)
     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)
     {
@@ -1214,10 +1209,10 @@ static int tls_check_certificate (CONNECTION* conn)
       if (tls_verify_peers (state, &certstat) != 0)
 	return 0;
 
-      /* If the cert chain now verifies, and the peer's cert was otherwise
-       * valid (rcpeer==0), we are done.
+      /* 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;
     }
   }
-- 
2.27.0

