+
+/* sanity-checking wrapper for gnutls_certificate_verify_peers */
+static gnutls_certificate_status tls_verify_peers (gnutls_session tlsstate)
+{
+ gnutls_certificate_status certstat;
+
+ certstat = gnutls_certificate_verify_peers (tlsstate);
+ if (!certstat)
+ return certstat;
+
+ if (certstat == GNUTLS_E_NO_CERTIFICATE_FOUND)
+ {
+ mutt_error (_("Unable to get certificate from peer"));
+ mutt_sleep (2);
+ return 0;
+ }
+ if (certstat < 0)
+ {
+ mutt_error (_("Certificate verification error (%s)"),
+ gnutls_strerror (certstat));
+ mutt_sleep (2);
+ return 0;
+ }
+
+ /* 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 certstat;
+}
+
+static int tls_check_certificate (CONNECTION* conn)
+{
+ tlssockdata *data = conn->sockdata;
+ gnutls_session state = data->state;
+ const gnutls_datum *cert_list;
+ unsigned int cert_list_size = 0;
+ gnutls_certificate_status certstat;
+ int certerr, i, preauthrc, savedcert, rc = 0;
+
+ if (gnutls_auth_get_type (state) != GNUTLS_CRD_CERTIFICATE)
+ {
+ mutt_error (_("Unable to get certificate from peer"));
+ mutt_sleep (2);
+ return 0;
+ }
+
+ certstat = tls_verify_peers (state);
+
+ cert_list = gnutls_certificate_get_peers (state, &cert_list_size);
+ if (!cert_list)
+ {
+ mutt_error (_("Unable to get certificate from peer"));
+ mutt_sleep (2);
+ return 0;
+ }
+
+ /* tls_verify_peers doesn't check hostname or expiration, so walk
+ * from most specific to least checking these. If we see a saved certificate,
+ * its status short-circuits the remaining checks. */
+ preauthrc = 0;
+ for (i = 0; i < cert_list_size; i++) {
+ rc = tls_check_preauth(&cert_list[i], certstat, conn->account.host, i,
+ &certerr, &savedcert);
+ preauthrc += rc;
+
+ if (savedcert)
+ {
+ if (!preauthrc)
+ return 1;
+ else
+ break;
+ }
+ }
+
+ /* then check interactively, starting from chain root */
+ for (i = cert_list_size - 1; i >= 0; i--)
+ {
+ rc = tls_check_one_certificate (&cert_list[i], certstat, conn->account.host,
+ i, cert_list_size);
+
+ /* 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 (!certstat)
+ return 1;
+ }
+ }
+
+ return rc;
+}