From 5360ce9b1a5a10fd972cd3d6e4f05059a394fd53 Mon Sep 17 00:00:00 2001 From: Brendan Cully Date: Sun, 11 Jan 2009 21:47:22 -0800 Subject: [PATCH] GNUTLS: check all available certs noninteractively before presenting any menus --- ChangeLog | 8 +++ mutt_ssl_gnutls.c | 164 +++++++++++++++++++++++++++++----------------- 2 files changed, 111 insertions(+), 61 deletions(-) diff --git a/ChangeLog b/ChangeLog index d4bd9548..9f9e369c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +2009-01-11 16:04 -0800 Brendan Cully (1097a060842a) + + * UPDATING, init.h, mutt.h, mutt_ssl.c, mutt_ssl_gnutls.c: Add + $ssl_verify_dates option to relax certificate date validation + + * UPDATING, init.h, mutt.h, mutt_ssl.c, mutt_ssl_gnutls.c: Add + $ssl_verify_host to allow skipping host name validation + 2009-01-10 22:09 -0800 Brendan Cully (db3a61fcde35) * imap/util.c: Assume INBOX for ""/NULL in imap_mxcmp diff --git a/mutt_ssl_gnutls.c b/mutt_ssl_gnutls.c index 1f9b070e..958d0544 100644 --- a/mutt_ssl_gnutls.c +++ b/mutt_ssl_gnutls.c @@ -33,6 +33,15 @@ #include "mutt_ssl.h" #include "mutt_regex.h" +/* certificate error bitmap values */ +#define CERTERR_VALID 0 +#define CERTERR_EXPIRED 1 +#define CERTERR_NOTYETVALID 2 +#define CERTERR_REVOKED 4 +#define CERTERR_NOTTRUSTED 8 +#define CERTERR_HOSTNAME 16 +#define CERTERR_SIGNERNOTCA 32 + typedef struct _tlssockdata { gnutls_session state; @@ -532,43 +541,21 @@ static int tls_check_stored_hostname (const gnutls_datum *cert, return 0; } -static int tls_check_one_certificate (const gnutls_datum_t *certdata, - gnutls_certificate_status certstat, - const char* hostname, int idx, int len) +static int tls_check_preauth (const gnutls_datum_t *certdata, + gnutls_certificate_status certstat, + const char *hostname, int checkhost, int* certerr) { gnutls_x509_crt cert; - int certerr_hostname = 0; - int certerr_expired = 0; - int certerr_notyetvalid = 0; - int certerr_nottrusted = 0; - int certerr_revoked = 0; - int certerr_signernotca = 0; - char buf[SHORT_STRING]; - char fpbuf[SHORT_STRING]; - size_t buflen; - char dn_common_name[SHORT_STRING]; - char dn_email[SHORT_STRING]; - char dn_organization[SHORT_STRING]; - char dn_organizational_unit[SHORT_STRING]; - char dn_locality[SHORT_STRING]; - char dn_province[SHORT_STRING]; - char dn_country[SHORT_STRING]; - time_t t; - char datestr[30]; - MUTTMENU *menu; - char helpstr[LONG_STRING]; - char title[STRING]; - FILE *fp; - gnutls_datum pemdata; - int i, row, done, ret; + + *certerr = CERTERR_VALID; if (gnutls_x509_crt_init (&cert) < 0) { mutt_error (_("Error initialising gnutls certificate data")); mutt_sleep (2); - return 0; + return -1; } - + if (gnutls_x509_crt_import (cert, certdata, GNUTLS_X509_FMT_DER) < 0) { mutt_error (_("Error processing certificate data")); @@ -580,19 +567,16 @@ static int tls_check_one_certificate (const gnutls_datum_t *certdata, if (option (OPTSSLVERIFYDATES) != M_NO) { if (gnutls_x509_crt_get_expiration_time (cert) < time(NULL)) - certerr_expired = 1; + *certerr |= CERTERR_EXPIRED; if (gnutls_x509_crt_get_activation_time (cert) > time(NULL)) - certerr_notyetvalid = 1; + *certerr |= CERTERR_NOTYETVALID; } - if (!idx) - { - if (!gnutls_x509_crt_check_hostname (cert, hostname) && - !tls_check_stored_hostname (certdata, hostname) && - option (OPTSSLVERIFYHOST) != M_NO) - certerr_hostname = 1; - } - + if (checkhost && option (OPTSSLVERIFYHOST) != M_NO + && !gnutls_x509_crt_check_hostname (cert, hostname) + && !tls_check_stored_hostname (certdata, hostname)) + *certerr |= CERTERR_HOSTNAME; + /* see whether certificate is in our cache (certificates file) */ if (tls_compare_certificates (certdata)) { @@ -602,14 +586,14 @@ static int tls_check_one_certificate (const gnutls_datum_t *certdata, certificate is in our trusted cache */ certstat ^= GNUTLS_CERT_INVALID; } - + if (certstat & GNUTLS_CERT_SIGNER_NOT_FOUND) { /* doesn't matter that we haven't found the signer, since certificate is in our trusted cache */ certstat ^= GNUTLS_CERT_SIGNER_NOT_FOUND; } - + if (certstat & GNUTLS_CERT_SIGNER_NOT_CA) { /* Hmm. Not really sure how to handle this, but let's say @@ -622,42 +606,89 @@ static int tls_check_one_certificate (const gnutls_datum_t *certdata, if (certstat & GNUTLS_CERT_REVOKED) { - certerr_revoked = 1; + *certerr |= CERTERR_REVOKED; certstat ^= GNUTLS_CERT_REVOKED; } - + if (certstat & GNUTLS_CERT_INVALID) { - certerr_nottrusted = 1; + *certerr |= CERTERR_NOTTRUSTED; certstat ^= GNUTLS_CERT_INVALID; } - + if (certstat & GNUTLS_CERT_SIGNER_NOT_FOUND) { /* NB: already cleared if cert in cache */ - certerr_nottrusted = 1; + *certerr |= CERTERR_NOTTRUSTED; certstat ^= GNUTLS_CERT_SIGNER_NOT_FOUND; } - + if (certstat & GNUTLS_CERT_SIGNER_NOT_CA) { /* NB: already cleared if cert in cache */ - certerr_signernotca = 1; + *certerr |= CERTERR_SIGNERNOTCA; certstat ^= GNUTLS_CERT_SIGNER_NOT_CA; } /* OK if signed by (or is) a trusted certificate */ - /* we've been zeroing the interesting bits in certstat - + /* we've been zeroing the interesting bits in certstat - don't return OK if there are any unhandled bits we don't understand */ - if (!(certerr_expired || certerr_notyetvalid || - certerr_hostname || certerr_nottrusted) && certstat == 0) + if (!(*certerr & (CERTERR_EXPIRED | CERTERR_NOTYETVALID + | CERTERR_HOSTNAME | CERTERR_NOTTRUSTED)) + && certstat == 0) { gnutls_x509_crt_deinit (cert); - return 1; + return 0; } + return -1; +} + +static int tls_check_one_certificate (const gnutls_datum_t *certdata, + gnutls_certificate_status certstat, + const char* hostname, int idx, int len) +{ + int certerr; + gnutls_x509_crt cert; + char buf[SHORT_STRING]; + char fpbuf[SHORT_STRING]; + size_t buflen; + char dn_common_name[SHORT_STRING]; + char dn_email[SHORT_STRING]; + char dn_organization[SHORT_STRING]; + char dn_organizational_unit[SHORT_STRING]; + char dn_locality[SHORT_STRING]; + char dn_province[SHORT_STRING]; + char dn_country[SHORT_STRING]; + time_t t; + char datestr[30]; + MUTTMENU *menu; + char helpstr[LONG_STRING]; + char title[STRING]; + FILE *fp; + gnutls_datum pemdata; + int i, row, done, ret; + + if (!tls_check_preauth (certdata, certstat, hostname, !idx, &certerr)) + return 1; + /* interactive check from user */ + if (gnutls_x509_crt_init (&cert) < 0) + { + mutt_error (_("Error initialising gnutls certificate data")); + mutt_sleep (2); + return 0; + } + + if (gnutls_x509_crt_import (cert, certdata, GNUTLS_X509_FMT_DER) < 0) + { + mutt_error (_("Error processing certificate data")); + mutt_sleep (2); + gnutls_x509_crt_deinit (cert); + return -1; + } + menu = mutt_new_menu (-1); menu->max = 25; menu->dialog = (char **) safe_calloc (1, menu->max * sizeof (char *)); @@ -760,27 +791,27 @@ static int tls_check_one_certificate (const gnutls_datum_t *certdata, tls_fingerprint (GNUTLS_DIG_MD5, fpbuf, sizeof (fpbuf), certdata); snprintf (menu->dialog[row++], SHORT_STRING, _("MD5 Fingerprint: %s"), fpbuf); - if (certerr_notyetvalid) + if (certerr & CERTERR_NOTYETVALID) { row++; strfcpy (menu->dialog[row], _("WARNING: Server certificate is not yet valid"), SHORT_STRING); } - if (certerr_expired) + if (certerr & CERTERR_EXPIRED) { row++; strfcpy (menu->dialog[row], _("WARNING: Server certificate has expired"), SHORT_STRING); } - if (certerr_revoked) + if (certerr & CERTERR_REVOKED) { row++; strfcpy (menu->dialog[row], _("WARNING: Server certificate has been revoked"), SHORT_STRING); } - if (certerr_hostname) + if (certerr & CERTERR_HOSTNAME) { row++; strfcpy (menu->dialog[row], _("WARNING: Server hostname does not match certificate"), SHORT_STRING); } - if (certerr_signernotca) + if (certerr & CERTERR_SIGNERNOTCA) { row++; strfcpy (menu->dialog[row], _("WARNING: Signer of server certificate is not a CA"), SHORT_STRING); @@ -792,7 +823,8 @@ static int tls_check_one_certificate (const gnutls_datum_t *certdata, menu->title = title; /* certificates with bad dates, or that are revoked, must be accepted manually each and every time */ - if (SslCertFile && !certerr_expired && !certerr_notyetvalid && !certerr_revoked) + if (SslCertFile && !(certerr & (CERTERR_EXPIRED | CERTERR_NOTYETVALID + | CERTERR_REVOKED))) { menu->prompt = _("(r)eject, accept (o)nce, (a)ccept always"); menu->keys = _("roa"); @@ -826,12 +858,12 @@ static int tls_check_one_certificate (const gnutls_datum_t *certdata, if ((fp = fopen (SslCertFile, "a"))) { /* save hostname if necessary */ - if (certerr_hostname) + if (certerr & CERTERR_HOSTNAME) { fprintf(fp, "#H %s %s\n", hostname, fpbuf); done = 1; } - if (certerr_nottrusted) + if (certerr & CERTERR_NOTTRUSTED) { done = 0; ret = gnutls_pem_base64_encode_alloc ("CERTIFICATE", certdata, @@ -877,7 +909,7 @@ static int tls_check_certificate (CONNECTION* conn) const gnutls_datum *cert_list; unsigned int cert_list_size = 0; gnutls_certificate_status certstat; - int i, rc; + int certerr, i, rc; if (gnutls_auth_get_type (state) != GNUTLS_CRD_CERTIFICATE) { @@ -918,6 +950,16 @@ static int tls_check_certificate (CONNECTION* conn) return 0; } + /* first check chain noninteractively */ + for (i = 0; i < cert_list_size; i++) + { + rc = tls_check_preauth(&cert_list[i], certstat, conn->account.host, !i, + &certerr); + if (!rc) + return 1; + } + + /* 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, -- 2.40.0