X-Git-Url: https://git.llucax.com/software/mutt-debian.git/blobdiff_plain/19304f7c526fbe36ba0db2fb80bcaf3bd974d81d..9ae284163f491c64de122fcd555019040e0d4da7:/mutt_ssl.c diff --git a/mutt_ssl.c b/mutt_ssl.c index 7de574e..616a068 100644 --- a/mutt_ssl.c +++ b/mutt_ssl.c @@ -1,16 +1,16 @@ /* * Copyright (C) 1999-2001 Tommi Komulainen - * + * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. @@ -64,11 +64,12 @@ static int entropy_byte_count = 0; * open up another connection to the same server in this session */ static STACK_OF(X509) *SslSessionCerts = NULL; -typedef struct _sslsockdata +typedef struct { SSL_CTX *ctx; SSL *ssl; X509 *cert; + unsigned char isopen; } sslsockdata; @@ -80,6 +81,7 @@ static int ssl_socket_write (CONNECTION* conn, const char* buf, size_t len); static int ssl_socket_open (CONNECTION * conn); static int ssl_socket_close (CONNECTION * conn); static int tls_close (CONNECTION* conn); +static void ssl_err (sslsockdata *data, int err); static int ssl_cache_trusted_cert (X509 *cert); static int ssl_check_certificate (CONNECTION *conn, sslsockdata * data); static int interactive_check_cert (X509 *cert, int idx, int len); @@ -122,6 +124,8 @@ int mutt_ssl_starttls (CONNECTION* conn) if (ssl_negotiate (conn, ssldata)) goto bail_ssl; + ssldata->isopen = 1; + /* hmm. watch out if we're starting TLS over any method other than raw. */ conn->sockdata = ssldata; conn->conn_read = ssl_socket_read; @@ -143,7 +147,7 @@ int mutt_ssl_starttls (CONNECTION* conn) return -1; } -/* +/* * OpenSSL library needs to be fed with sufficient entropy. On systems * with /dev/urandom, this is done transparently by the library itself, * on other systems we need to fill the entropy pool ourselves. @@ -166,7 +170,7 @@ static int ssl_init (void) /* load entropy from files */ add_entropy (SslEntropyFile); add_entropy (RAND_file_name (path, sizeof (path))); - + /* load entropy from egd sockets */ #ifdef HAVE_RAND_EGD add_entropy (getenv ("EGDSOCKET")); @@ -206,9 +210,9 @@ static int add_entropy (const char *file) mutt_message (_("Filling entropy pool: %s...\n"), file); - + /* check that the file permissions are secure */ - if (st.st_uid != getuid () || + if (st.st_uid != getuid () || ((st.st_mode & (S_IWGRP | S_IRGRP)) != 0) || ((st.st_mode & (S_IWOTH | S_IROTH)) != 0)) { @@ -257,13 +261,28 @@ int mutt_ssl_socket_setup (CONNECTION * conn) static int ssl_socket_read (CONNECTION* conn, char* buf, size_t len) { sslsockdata *data = conn->sockdata; - return SSL_read (data->ssl, buf, len); + int rc; + + rc = SSL_read (data->ssl, buf, len); + if (rc <= 0) + { + data->isopen = 0; + ssl_err (data, rc); + } + + return rc; } static int ssl_socket_write (CONNECTION* conn, const char* buf, size_t len) { sslsockdata *data = conn->sockdata; - return SSL_write (data->ssl, buf, len); + int rc; + + rc = SSL_write (data->ssl, buf, len); + if (rc <= 0) + ssl_err (data, rc); + + return rc; } static int ssl_socket_open (CONNECTION * conn) @@ -280,15 +299,15 @@ static int ssl_socket_open (CONNECTION * conn) data->ctx = SSL_CTX_new (SSLv23_client_method ()); /* disable SSL protocols as needed */ - if (!option(OPTTLSV1)) + if (!option(OPTTLSV1)) { SSL_CTX_set_options(data->ctx, SSL_OP_NO_TLSv1); } - if (!option(OPTSSLV2)) + if (!option(OPTSSLV2)) { SSL_CTX_set_options(data->ctx, SSL_OP_NO_SSLv2); } - if (!option(OPTSSLV3)) + if (!option(OPTSSLV3)) { SSL_CTX_set_options(data->ctx, SSL_OP_NO_SSLv3); } @@ -303,7 +322,9 @@ static int ssl_socket_open (CONNECTION * conn) mutt_socket_close (conn); return -1; } - + + data->isopen = 1; + conn->ssf = SSL_CIPHER_get_bits (SSL_get_current_cipher (data->ssl), &maxbits); @@ -336,7 +357,7 @@ static int ssl_negotiate (CONNECTION *conn, sslsockdata* ssldata) default: errmsg = _("unknown error"); } - + mutt_error (_("SSL failed: %s"), errmsg); mutt_sleep (1); @@ -354,7 +375,7 @@ static int ssl_negotiate (CONNECTION *conn, sslsockdata* ssldata) if (!ssl_check_certificate (conn, ssldata)) return -1; - mutt_message (_("SSL connection using %s (%s)"), + mutt_message (_("SSL connection using %s (%s)"), SSL_get_cipher_version (ssldata->ssl), SSL_get_cipher_name (ssldata->ssl)); mutt_sleep (0); @@ -366,7 +387,8 @@ static int ssl_socket_close (CONNECTION * conn) sslsockdata *data = conn->sockdata; if (data) { - SSL_shutdown (data->ssl); + if (data->isopen) + SSL_shutdown (data->ssl); /* hold onto this for the life of mutt, in case we want to reconnect. * The purist in me wants a mutt_exit hook. */ @@ -393,6 +415,63 @@ static int tls_close (CONNECTION* conn) return rc; } +static void ssl_err (sslsockdata *data, int err) +{ + const char* errmsg; + unsigned long sslerr; + + switch (SSL_get_error (data->ssl, err)) + { + case SSL_ERROR_NONE: + return; + case SSL_ERROR_ZERO_RETURN: + errmsg = "SSL connection closed"; + data->isopen = 0; + break; + case SSL_ERROR_WANT_READ: + errmsg = "retry read"; + break; + case SSL_ERROR_WANT_WRITE: + errmsg = "retry write"; + break; + case SSL_ERROR_WANT_CONNECT: + errmsg = "retry connect"; + break; + case SSL_ERROR_WANT_ACCEPT: + errmsg = "retry accept"; + break; + case SSL_ERROR_WANT_X509_LOOKUP: + errmsg = "retry x509 lookup"; + break; + case SSL_ERROR_SYSCALL: + errmsg = "I/O error"; + data->isopen = 0; + break; + case SSL_ERROR_SSL: + sslerr = ERR_get_error (); + switch (sslerr) + { + case 0: + switch (err) + { + case 0: + errmsg = "EOF"; + break; + default: + errmsg = strerror(errno); + } + break; + default: + errmsg = ERR_error_string (sslerr, NULL); + } + break; + default: + errmsg = "unknown error"; + } + + dprint (1, (debugfile, "SSL error: %s\n", errmsg)); +} + static char *x509_get_part (char *line, const char *ndx) { static char ret[SHORT_STRING]; @@ -442,7 +521,7 @@ static char *asn1time_to_string (ASN1_UTCTIME *tm) BIO *bio; strfcpy (buf, _("[invalid date]"), sizeof (buf)); - + bio = BIO_new (BIO_s_mem()); if (bio) { @@ -474,7 +553,7 @@ static int check_certificate_by_signer (X509 *peercert) if (X509_STORE_load_locations (ctx, SslCertFile, NULL)) pass++; else - dprint (2, (debugfile, "X509_STORE_load_locations_failed\n")); + dprint (2, (debugfile, "X509_STORE_load_locations failed\n")); for (i = 0; i < sk_X509_num (SslSessionCerts); i++) pass += (X509_STORE_add_cert (ctx, sk_X509_value (SslSessionCerts, i)) != 0); @@ -496,7 +575,7 @@ static int check_certificate_by_signer (X509 *peercert) int err; err = X509_STORE_CTX_get_error (&xsc); - snprintf (buf, sizeof (buf), "%s (%d)", + snprintf (buf, sizeof (buf), "%s (%d)", X509_verify_cert_error_string(err), err); dprint (2, (debugfile, "X509_verify_cert: %s\n", buf)); dprint (2, (debugfile, " [%s]\n", peercert->name)); @@ -513,17 +592,17 @@ static int compare_certificates (X509 *cert, X509 *peercert, { unsigned char md[EVP_MAX_MD_SIZE]; unsigned int mdlen; - + /* Avoid CPU-intensive digest calculation if the certificates are * not even remotely equal. */ if (X509_subject_name_cmp (cert, peercert) != 0 || X509_issuer_name_cmp (cert, peercert) != 0) return -1; - + if (!X509_digest (cert, EVP_sha1(), md, &mdlen) || peermdlen != mdlen) return -1; - + if (memcmp(peermd, md, mdlen) != 0) return -1; @@ -542,7 +621,7 @@ static int check_certificate_cache (X509 *peercert) { return 0; } - + for (i = sk_X509_num (SslSessionCerts); i-- > 0;) { cert = sk_X509_value (SslSessionCerts, i); @@ -551,7 +630,7 @@ static int check_certificate_cache (X509 *peercert) return 1; } } - + return 0; } @@ -564,19 +643,22 @@ static int check_certificate_by_digest (X509 *peercert) FILE *fp; /* expiration check */ - if (X509_cmp_current_time (X509_get_notBefore (peercert)) >= 0) - { - dprint (2, (debugfile, "Server certificate is not yet valid\n")); - mutt_error (_("Server certificate is not yet valid")); - mutt_sleep (2); - return 0; - } - if (X509_cmp_current_time (X509_get_notAfter (peercert)) <= 0) + if (option (OPTSSLVERIFYDATES) != M_NO) { - dprint (2, (debugfile, "Server certificate has expired")); - mutt_error (_("Server certificate has expired")); - mutt_sleep (2); - return 0; + if (X509_cmp_current_time (X509_get_notBefore (peercert)) >= 0) + { + dprint (2, (debugfile, "Server certificate is not yet valid\n")); + mutt_error (_("Server certificate is not yet valid")); + mutt_sleep (2); + return 0; + } + if (X509_cmp_current_time (X509_get_notAfter (peercert)) <= 0) + { + dprint (2, (debugfile, "Server certificate has expired")); + mutt_error (_("Server certificate has expired")); + mutt_sleep (2); + return 0; + } } if ((fp = fopen (SslCertFile, "rt")) == NULL) @@ -584,19 +666,19 @@ static int check_certificate_by_digest (X509 *peercert) if (!X509_digest (peercert, EVP_sha1(), peermd, &peermdlen)) { - fclose (fp); + safe_fclose (&fp); return 0; } while ((cert = READ_X509_KEY (fp, &cert)) != NULL) { pass = compare_certificates (cert, peercert, peermd, peermdlen) ? 0 : 1; - + if (pass) break; } X509_free (cert); - fclose (fp); + safe_fclose (&fp); return pass; } @@ -649,7 +731,7 @@ static int check_host (X509 *x509cert, const char *hostname, char *err, size_t e char *buf = NULL; int bufsize; /* needed to get the DNS subjectAltNames: */ - STACK *subj_alt_names; + STACK_OF(GENERAL_NAME) *subj_alt_names; int subj_alt_names_count; GENERAL_NAME *subj_alt_name; /* did we find a name matching hostname? */ @@ -678,7 +760,9 @@ static int check_host (X509 *x509cert, const char *hostname, char *err, size_t e 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, + if (subj_alt_name->d.ia5->length >= 0 && + mutt_strlen((char *)subj_alt_name->d.ia5->data) == (size_t)subj_alt_name->d.ia5->length && + (match_found = hostname_match(hostname_ascii, (char *)(subj_alt_name->d.ia5->data)))) { break; @@ -697,9 +781,16 @@ static int check_host (X509 *x509cert, const char *hostname, char *err, size_t e goto out; } + /* first get the space requirements */ bufsize = X509_NAME_get_text_by_NID(x509_subject, NID_commonName, NULL, 0); - bufsize++; + if (bufsize == -1) + { + if (err && errlen) + strfcpy (err, _("cannot get certificate common name"), errlen); + goto out; + } + bufsize++; /* space for the terminal nul char */ buf = safe_malloc((size_t)bufsize); if (X509_NAME_get_text_by_NID(x509_subject, NID_commonName, buf, bufsize) == -1) @@ -708,7 +799,12 @@ static int check_host (X509 *x509cert, const char *hostname, char *err, size_t e strfcpy (err, _("cannot get certificate common name"), errlen); goto out; } - match_found = hostname_match(hostname_ascii, buf); + /* cast is safe since bufsize is incremented above, so bufsize-1 is always + * zero or greater. + */ + if (mutt_strlen(buf) == (size_t)bufsize - 1) { + match_found = hostname_match(hostname_ascii, buf); + } } if (!match_found) @@ -732,12 +828,14 @@ static int ssl_cache_trusted_cert (X509 *c) { dprint (1, (debugfile, "trusted: %s\n", c->name)); if (!SslSessionCerts) - SslSessionCerts = sk_new_null(); + SslSessionCerts = sk_X509_new_null(); return (sk_X509_push (SslSessionCerts, X509_dup(c))); } -/* check whether cert is preauthorized */ -static int ssl_check_preauth (X509 *cert, CONNECTION *conn) +/* check whether cert is preauthorized. If host is not null, verify that + * it matches the certificate. + * Return > 0: authorized, < 0: problems, 0: unknown validity */ +static int ssl_check_preauth (X509 *cert, const char* host) { char buf[SHORT_STRING]; @@ -749,13 +847,16 @@ static int ssl_check_preauth (X509 *cert, CONNECTION *conn) } buf[0] = 0; - if (!check_host (cert, conn->account.host, buf, sizeof (buf))) + if (host && option (OPTSSLVERIFYHOST) != M_NO) { - mutt_error (_("Certificate host check failed: %s"), buf); - mutt_sleep (2); - return -1; + if (!check_host (cert, host, buf, sizeof (buf))) + { + mutt_error (_("Certificate host check failed: %s"), buf); + mutt_sleep (2); + return -1; + } + dprint (2, (debugfile, "ssl_check_preauth: hostname check passed\n")); } - dprint (2, (debugfile, "ssl_check_preauth: hostname check passed\n")); if (check_certificate_by_signer (cert)) { @@ -779,44 +880,30 @@ static int ssl_check_certificate (CONNECTION *conn, sslsockdata *data) STACK_OF(X509) *chain; X509 *cert; - if ((preauthrc = ssl_check_preauth (data->cert, conn)) > 0) + if ((preauthrc = ssl_check_preauth (data->cert, conn->account.host)) > 0) return preauthrc; chain = SSL_get_peer_cert_chain (data->ssl); chain_len = sk_X509_num (chain); - if (!chain || (chain_len < 1)) + /* negative preauthrc means the certificate won't be accepted without + * manual override. */ + if (preauthrc < 0 || !chain || (chain_len <= 1)) return interactive_check_cert (data->cert, 0, 0); - /* check the chain from root to peer */ + /* check the chain from root to peer. */ for (i = chain_len-1; i >= 0; i--) { cert = sk_X509_value (chain, i); - if (check_certificate_cache (cert)) - dprint (2, (debugfile, "ssl chain: already cached: %s\n", cert->name)); - else if (i /* 0 is the peer */ || !preauthrc) + + /* if the certificate validates or is manually accepted, then add it to + * the trusted set and recheck the peer certificate */ + if (ssl_check_preauth (cert, NULL) + || interactive_check_cert (cert, i, chain_len)) { - if (check_certificate_by_signer (cert)) - { - dprint (2, (debugfile, "ssl chain: checked by signer: %s\n", cert->name)); - ssl_cache_trusted_cert (cert); - return 1; - } - else if (SslCertFile && check_certificate_by_digest (cert)) - { - dprint (2, (debugfile, "ssl chain: trusted with file: %s\n", cert->name)); - ssl_cache_trusted_cert (cert); + ssl_cache_trusted_cert (cert); + if (ssl_check_preauth (data->cert, conn->account.host)) return 1; - } - else /* allow users to shoot their foot */ - { - dprint (2, (debugfile, "ssl chain: check failed: %s\n", cert->name)); - if (interactive_check_cert (cert, i, chain_len)) - return 1; - } } - else /* highly suspicious because (i==0 && preauthrc < 0) */ - if (interactive_check_cert (cert, i, chain_len)) - return 1; } return 0; @@ -847,7 +934,7 @@ static int interactive_check_cert (X509 *cert, int idx, int len) name = X509_NAME_oneline (X509_get_subject_name (cert), buf, sizeof (buf)); dprint (2, (debugfile, "oneline: %s\n", name)); - + for (i = 0; i < 5; i++) { c = x509_get_part (name, part[i]); @@ -867,9 +954,9 @@ static int interactive_check_cert (X509 *cert, int idx, int len) row++; snprintf (menu->dialog[row++], SHORT_STRING, _("This certificate is valid")); - snprintf (menu->dialog[row++], SHORT_STRING, _(" from %s"), + snprintf (menu->dialog[row++], SHORT_STRING, _(" from %s"), asn1time_to_string (X509_get_notBefore (cert))); - snprintf (menu->dialog[row++], SHORT_STRING, _(" to %s"), + snprintf (menu->dialog[row++], SHORT_STRING, _(" to %s"), asn1time_to_string (X509_get_notAfter (cert))); row++; @@ -881,8 +968,10 @@ static int interactive_check_cert (X509 *cert, int idx, int len) _("SSL Certificate check (certificate %d of %d in chain)"), len - idx, len); menu->title = title; - if (SslCertFile && X509_cmp_current_time (X509_get_notAfter (cert)) >= 0 - && X509_cmp_current_time (X509_get_notBefore (cert)) < 0) + if (SslCertFile + && (option (OPTSSLVERIFYDATES) == M_NO + || (X509_cmp_current_time (X509_get_notAfter (cert)) >= 0 + && X509_cmp_current_time (X509_get_notBefore (cert)) < 0))) { menu->prompt = _("(r)eject, accept (o)nce, (a)ccept always"); menu->keys = _("roa"); @@ -892,7 +981,7 @@ static int interactive_check_cert (X509 *cert, int idx, int len) menu->prompt = _("(r)eject, accept (o)nce"); menu->keys = _("ro"); } - + helpstr[0] = '\0'; mutt_make_help (buf, sizeof (buf), _("Exit "), MENU_GENERIC, OP_EXIT); safe_strcat (helpstr, sizeof (helpstr), buf); @@ -917,7 +1006,7 @@ static int interactive_check_cert (X509 *cert, int idx, int len) { if (PEM_write_X509 (fp, cert)) done = 1; - fclose (fp); + safe_fclose (&fp); } if (!done) { @@ -951,7 +1040,7 @@ static void ssl_get_client_cert(sslsockdata *ssldata, CONNECTION *conn) SSL_CTX_set_default_passwd_cb(ssldata->ctx, ssl_passwd_cb); SSL_CTX_use_certificate_file(ssldata->ctx, SslClientCert, SSL_FILETYPE_PEM); SSL_CTX_use_PrivateKey_file(ssldata->ctx, SslClientCert, SSL_FILETYPE_PEM); - + /* if we are using a client cert, SASL may expect an external auth name */ mutt_account_getuser (&conn->account); } @@ -966,7 +1055,7 @@ static int ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata) dprint (2, (debugfile, "ssl_passwd_cb: getting password for %s@%s:%u\n", account->user, account->host, account->port)); - + if (mutt_account_getpass (account)) return 0;