From 1fbf58d428baf5ae7f762f25e4508b32d2401e6c Mon Sep 17 00:00:00 2001 From: Rocco Rutte Date: Thu, 21 Aug 2008 07:33:52 +0200 Subject: [PATCH] Port certificate host checking from msmtp to mutt. It supports IDN, wildcards and extracting the hostname from subject alternative field as well as common name which should be the same gnutls supports. Closes #3087. --- ChangeLog | 9 ++++ mutt_ssl.c | 136 +++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 136 insertions(+), 9 deletions(-) diff --git a/ChangeLog b/ChangeLog index 9ff80753e..4670d63d9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2008-08-19 13:17 -0700 Brendan Cully (573d1aab3c89) + + * init.c: Silence an incorrect uninitialized variable warning. + +2008-08-19 13:14 -0700 Brendan Cully (6b4f25cd9dac) + + * ChangeLog, init.h: Better documentation for how quote_regexp + determines quote level. Closes #1463. + 2008-08-19 09:39 +0200 Rocco Rutte (3e850c6e43fd) * handler.c, mutt.h: Make text/enriched handler multibyte aware. diff --git a/mutt_ssl.c b/mutt_ssl.c index 0b3ec10de..5aeb815c4 100644 --- a/mutt_ssl.c +++ b/mutt_ssl.c @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -34,6 +35,7 @@ #include "mutt_menu.h" #include "mutt_curses.h" #include "mutt_ssl.h" +#include "mutt_idna.h" #if OPENSSL_VERSION_NUMBER >= 0x00904000L #define READ_X509_KEY(fp, key) PEM_read_X509(fp, key, NULL, NULL) @@ -588,20 +590,131 @@ static int check_certificate_by_digest (X509 *peercert) return pass; } -static int check_certificate_hostname(X509 *peercert, const char *host) +/* port to mutt from msmtp's tls.c */ +static int hostname_match (const char *hostname, const char *certname) { - char cert_CN[STRING]; + const char *cmp1, *cmp2; - if (!host || !*host) + if (strncmp(certname, "*.", 2) == 0) + { + cmp1 = certname + 2; + cmp2 = strchr(hostname, '.'); + if (!cmp2) + { + return 0; + } + else + { + cmp2++; + } + } + else + { + cmp1 = certname; + cmp2 = hostname; + } + + if (*cmp1 == '\0' || *cmp2 == '\0') + { return 0; + } + + if (strcasecmp(cmp1, cmp2) != 0) + { + return 0; + } + + return 1; +} + +/* port to mutt from msmtp's tls.c */ +static int check_host (X509 *x509cert, const char *hostname, char *err, size_t errlen) +{ + int i, rc = 0; + /* hostname in ASCII format: */ + char *hostname_ascii = NULL; + /* needed to get the common name: */ + X509_NAME *x509_subject; + char *buf = NULL; + int bufsize; + /* needed to get the DNS subjectAltNames: */ + STACK *subj_alt_names; + int subj_alt_names_count; + GENERAL_NAME *subj_alt_name; + /* did we find a name matching hostname? */ + int match_found; + + /* Check if 'hostname' matches the one of the subjectAltName extensions of + * type DNS or the Common Name (CN). */ + +#ifdef HAVE_LIBIDN + if (idna_to_ascii_lz(hostname, &hostname_ascii, 0) != IDNA_SUCCESS) + { + hostname_ascii = safe_strdup(hostname); + } +#else + hostname_ascii = safe_strdup(hostname); +#endif - X509_NAME_get_text_by_NID (X509_get_subject_name (peercert), - NID_commonName, cert_CN, sizeof (cert_CN)); + /* Try the DNS subjectAltNames. */ + match_found = 0; + if ((subj_alt_names = X509_get_ext_d2i(x509cert, NID_subject_alt_name, + NULL, NULL))) + { + subj_alt_names_count = sk_GENERAL_NAME_num(subj_alt_names); + for (i = 0; i < subj_alt_names_count; i++) + { + subj_alt_name = sk_GENERAL_NAME_value(subj_alt_names, i); + if (subj_alt_name->type == GEN_DNS) + { + if ((match_found = hostname_match(hostname_ascii, + (char *)(subj_alt_name->d.ia5->data)))) + { + break; + } + } + } + } - dprint (2, (debugfile, "check_certificate_hostname: cert=[%s] host=[%s]\n", - cert_CN, host)); + if (!match_found) + { + /* Try the common name */ + if (!(x509_subject = X509_get_subject_name(x509cert))) + { + if (err && errlen) + strfcpy (err, _("cannot get certificate subject"), errlen); + goto out; + } - return strcmp (cert_CN, host) == 0; + bufsize = X509_NAME_get_text_by_NID(x509_subject, NID_commonName, + NULL, 0); + bufsize++; + buf = safe_malloc((size_t)bufsize); + if (X509_NAME_get_text_by_NID(x509_subject, NID_commonName, + buf, bufsize) == -1) + { + if (err && errlen) + strfcpy (err, _("cannot get certificate common name"), errlen); + goto out; + } + match_found = hostname_match(hostname_ascii, buf); + } + + if (!match_found) + { + if (err && errlen) + snprintf (err, errlen, _("certificate owner does not match hostname %s"), + hostname); + goto out; + } + + rc = 1; + +out: + FREE(&buf); + FREE(&hostname_ascii); + + return rc; } static int ssl_check_certificate (CONNECTION *conn, sslsockdata * data) @@ -622,12 +735,17 @@ static int ssl_check_certificate (CONNECTION *conn, sslsockdata * data) return 1; } - if (check_certificate_hostname (data->cert, conn->account.host)) + buf[0] = 0; + if (check_host (data->cert, conn->account.host, buf, sizeof (buf))) { dprint (1, (debugfile, "ssl_check_certificate: hostname check passed\n")); } else + { + mutt_error (_("Certificate host check failed: %s"), buf); + mutt_sleep (2); certerr_hostname = 1; + } if (!certerr_hostname && check_certificate_by_signer (data->cert)) { -- 2.50.0