Commit f4fbf0bb authored by Matthias Andree's avatar Matthias Andree

Add $ssl_verify_partial_chains option for OpenSSL. (closes #3916)

The reworked OpenSSL certificate validation took away a "feature" of
the previous implementation: the ability to reject a node in the chain
and yet continue to the next node.

If this new option is set to 'yes', enables OpenSSL's
X509_V_FLAG_PARTIAL_CHAIN flag to reinstate the functionality and permit
to use a non-root certificate as the trust anchor.

This option is only available if OpenSSL offers the
X509_V_FLAG_PARTIAL_CHAIN macro, which should be the case as of 1.0.2b
or later.

Code written by Kevin McCarthy and Matthias Andree.
parent 5f112a84
......@@ -19,6 +19,9 @@
# ifndef USE_SSL_OPENSSL
# define USE_SSL_OPENSSL
# endif
# ifndef X509_V_FLAG_PARTIAL_CHAIN
# define X509_V_FLAG_PARTIAL_CHAIN
# endif
# ifndef USE_SSL_GNUTLS
# define USE_SSL_GNUTLS
# endif
......
......@@ -79,6 +79,10 @@ struct option_t
#define UL (unsigned long)
#ifdef USE_SSL_OPENSSL
/* need to check X509_V_FLAG_PARTIAL_CHAIN later */
# include <openssl/x509_vfy.h>
#endif
#endif /* _MAKEDOC */
#ifndef ISPELL
......@@ -3377,6 +3381,24 @@ struct option_t MuttVars[] = {
** URL. You should only unset this for particular known hosts, using
** the \fC$<account-hook>\fP function.
*/
# ifdef USE_SSL_OPENSSL
# ifdef X509_V_FLAG_PARTIAL_CHAIN
{ "ssl_verify_partial_chains", DT_BOOL, R_NONE, OPTSSLVERIFYPARTIAL, 0 },
/*
** .pp
** This option should not be changed from the default unless you understand
** what you are doing.
** .pp
** Setting this variable to \fIyes\fP will permit verifying partial
** certification chains, i. e. a certificate chain where not the root,
** but an intermediate certificate CA, or the host certificate, are
** marked trusted (in $$certificate_file), without marking the root
** signing CA as trusted.
** .pp
** (OpenSSL 1.0.2b and newer only).
*/
# endif /* defined X509_V_FLAG_PARTIAL_CHAIN */
# endif /* defined USE_SSL_OPENSSL */
{ "ssl_ciphers", DT_STR, R_NONE, UL &SslCiphers, UL 0 },
/*
** .pp
......
......@@ -396,6 +396,9 @@ enum
OPTSSLFORCETLS,
OPTSSLVERIFYDATES,
OPTSSLVERIFYHOST,
# ifdef USE_SSL_OPENSSL
OPTSSLVERIFYPARTIAL,
# endif /* USE_SSL_OPENSSL */
#endif /* defined(USE_SSL) */
OPTIMPLICITAUTOVIEW,
OPTINCLUDEONLYFIRST,
......
......@@ -23,6 +23,7 @@
#include <openssl/ssl.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/x509_vfy.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <openssl/evp.h>
......@@ -57,6 +58,12 @@ static int entropy_byte_count = 0;
/* index for storing hostname as application specific data in SSL structure */
static int HostExDataIndex = -1;
/* Index for storing the "skip mode" state in SSL structure. When the
* user skips a certificate in the chain, the stored value will be
* non-null. */
static int SkipModeExDataIndex = -1;
/* keep a handle on accepted certificates in case we want to
* open up another connection to the same server in this session */
static STACK_OF(X509) *SslSessionCerts = NULL;
......@@ -82,7 +89,7 @@ static void ssl_err (sslsockdata *data, int err);
static void ssl_dprint_err_stack (void);
static int ssl_cache_trusted_cert (X509 *cert);
static int ssl_verify_callback (int preverify_ok, X509_STORE_CTX *ctx);
static int interactive_check_cert (X509 *cert, int idx, int len);
static int interactive_check_cert (X509 *cert, int idx, int len, SSL *ssl);
static void ssl_get_client_cert(sslsockdata *ssldata, CONNECTION *conn);
static int ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata);
static int ssl_negotiate (CONNECTION *conn, sslsockdata*);
......@@ -136,6 +143,35 @@ static int ssl_load_certificates (SSL_CTX *ctx)
return rv;
}
static int ssl_set_verify_partial (SSL_CTX *ctx)
{
int rv = 0;
#ifdef X509_V_FLAG_PARTIAL_CHAIN
X509_VERIFY_PARAM *param;
if (option (OPTSSLVERIFYPARTIAL))
{
param = X509_VERIFY_PARAM_new();
if (param)
{
X509_VERIFY_PARAM_set_flags(param, X509_V_FLAG_PARTIAL_CHAIN);
if (0 == SSL_CTX_set1_param(ctx, param))
{
dprint (2, (debugfile, "ssl_set_verify_partial: SSL_CTX_set1_param() failed."));
rv = -1;
}
X509_VERIFY_PARAM_free(param);
}
else
{
dprint (2, (debugfile, "ssl_set_verify_partial: X509_VERIFY_PARAM_new() failed."));
rv = -1;
}
}
#endif
return rv;
}
/* mutt_ssl_starttls: Negotiate TLS over an already opened connection.
* TODO: Merge this code better with ssl_socket_open. */
int mutt_ssl_starttls (CONNECTION* conn)
......@@ -206,6 +242,12 @@ int mutt_ssl_starttls (CONNECTION* conn)
}
}
if (ssl_set_verify_partial (ssldata->ctx))
{
mutt_error (_("Warning: error enabling ssl_verify_partial_chains"));
mutt_sleep (2);
}
if (! (ssldata->ssl = SSL_new (ssldata->ctx)))
{
dprint (1, (debugfile, "mutt_ssl_starttls: Error allocating SSL\n"));
......@@ -453,6 +495,12 @@ static int ssl_socket_open (CONNECTION * conn)
SSL_CTX_set_cipher_list (data->ctx, SslCiphers);
}
if (ssl_set_verify_partial (data->ctx))
{
mutt_error (_("Warning: error enabling ssl_verify_partial_chains"));
mutt_sleep (2);
}
data->ssl = SSL_new (data->ctx);
SSL_set_fd (data->ssl, conn->fd);
......@@ -489,6 +537,18 @@ static int ssl_negotiate (CONNECTION *conn, sslsockdata* ssldata)
return -1;
}
if ((SkipModeExDataIndex = SSL_get_ex_new_index (0, "skip", NULL, NULL, NULL)) == -1)
{
dprint (1, (debugfile, "failed to get index for application specific data\n"));
return -1;
}
if (! SSL_set_ex_data (ssldata->ssl, SkipModeExDataIndex, NULL))
{
dprint (1, (debugfile, "failed to save skip mode in SSL structure\n"));
return -1;
}
SSL_set_verify (ssldata->ssl, SSL_VERIFY_PEER, ssl_verify_callback);
SSL_set_mode (ssldata->ssl, SSL_MODE_AUTO_RETRY);
ERR_clear_error ();
......@@ -947,6 +1007,7 @@ static int ssl_verify_callback (int preverify_ok, X509_STORE_CTX *ctx)
int len, pos;
X509 *cert;
SSL *ssl;
int skip_mode;
if (! (ssl = X509_STORE_CTX_get_ex_data (ctx, SSL_get_ex_data_X509_STORE_CTX_idx ())))
{
......@@ -959,18 +1020,28 @@ static int ssl_verify_callback (int preverify_ok, X509_STORE_CTX *ctx)
return 0;
}
/* This is true when a previous entry in the certificate chain did
* not verify and the user manually chose to skip it via the
* $ssl_verify_partial_chains option.
* In this case, all following certificates need to be treated as non-verified
* until one is actually verified.
*/
skip_mode = (SSL_get_ex_data (ssl, SkipModeExDataIndex) != NULL);
cert = X509_STORE_CTX_get_current_cert (ctx);
pos = X509_STORE_CTX_get_error_depth (ctx);
len = sk_X509_num (X509_STORE_CTX_get_chain (ctx));
dprint (1, (debugfile, "ssl_verify_callback: checking cert chain entry %s (preverify: %d)\n",
X509_NAME_oneline (X509_get_subject_name (cert),
buf, sizeof (buf)), preverify_ok));
dprint (1, (debugfile,
"ssl_verify_callback: checking cert chain entry %s (preverify: %d skipmode: %d)\n",
X509_NAME_oneline (X509_get_subject_name (cert), buf, sizeof (buf)),
preverify_ok, skip_mode));
/* check session cache first */
if (check_certificate_cache (cert))
{
dprint (2, (debugfile, "ssl_verify_callback: using cached certificate\n"));
SSL_set_ex_data (ssl, SkipModeExDataIndex, NULL);
return 1;
}
......@@ -982,17 +1053,18 @@ static int ssl_verify_callback (int preverify_ok, X509_STORE_CTX *ctx)
{
mutt_error (_("Certificate host check failed: %s"), buf);
mutt_sleep (2);
return interactive_check_cert (cert, pos, len);
return interactive_check_cert (cert, pos, len, ssl);
}
dprint (2, (debugfile, "ssl_verify_callback: hostname check passed\n"));
}
if (!preverify_ok)
if (!preverify_ok || skip_mode)
{
/* automatic check from user's database */
if (SslCertFile && check_certificate_by_digest (cert))
{
dprint (2, (debugfile, "ssl_verify_callback: digest check passed\n"));
SSL_set_ex_data (ssl, SkipModeExDataIndex, NULL);
return 1;
}
......@@ -1006,14 +1078,14 @@ static int ssl_verify_callback (int preverify_ok, X509_STORE_CTX *ctx)
}
#endif
/* prompt user */
return interactive_check_cert (cert, pos, len);
/* prompt user */
return interactive_check_cert (cert, pos, len, ssl);
}
return 1;
}
static int interactive_check_cert (X509 *cert, int idx, int len)
static int interactive_check_cert (X509 *cert, int idx, int len, SSL *ssl)
{
static const int part[] =
{ NID_commonName, /* CN */
......@@ -1032,6 +1104,7 @@ static int interactive_check_cert (X509 *cert, int idx, int len)
int done, row, i;
unsigned u;
FILE *fp;
int allow_always = 0, allow_skip = 0;
menu->max = mutt_array_size (part) * 2 + 10;
menu->dialog = (char **) safe_calloc (1, menu->max * sizeof (char *));
......@@ -1073,18 +1146,38 @@ 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;
/* The leaf/host certificate can't be skipped. */
#ifdef X509_V_FLAG_PARTIAL_CHAIN
if ((idx != 0) &&
(option (OPTSSLVERIFYPARTIAL)))
allow_skip = 1;
#endif
/* L10N:
* These four letters correspond to the choices in the next four strings:
* (r)eject, accept (o)nce, (a)ccept always, (s)kip.
* These prompts are the interactive certificate confirmation prompts for
* an OpenSSL connection.
*/
menu->keys = _("roas");
if (SslCertFile
&& (option (OPTSSLVERIFYDATES) == MUTT_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");
allow_always = 1;
if (allow_skip)
menu->prompt = _("(r)eject, accept (o)nce, (a)ccept always, (s)kip");
else
menu->prompt = _("(r)eject, accept (o)nce, (a)ccept always");
}
else
{
menu->prompt = _("(r)eject, accept (o)nce");
menu->keys = _("ro");
if (allow_skip)
menu->prompt = _("(r)eject, accept (o)nce, (s)kip");
else
menu->prompt = _("(r)eject, accept (o)nce");
}
helpstr[0] = '\0';
......@@ -1106,6 +1199,8 @@ static int interactive_check_cert (X509 *cert, int idx, int len)
done = 1;
break;
case OP_MAX + 3: /* accept always */
if (!allow_always)
break;
done = 0;
if ((fp = fopen (SslCertFile, "a")))
{
......@@ -1126,8 +1221,15 @@ static int interactive_check_cert (X509 *cert, int idx, int len)
/* fall through */
case OP_MAX + 2: /* accept once */
done = 2;
SSL_set_ex_data (ssl, SkipModeExDataIndex, NULL);
ssl_cache_trusted_cert (cert);
break;
case OP_MAX + 4: /* skip */
if (!allow_skip)
break;
done = 2;
SSL_set_ex_data (ssl, SkipModeExDataIndex, &SkipModeExDataIndex);
break;
}
}
unset_option(OPTIGNOREMACROEVENTS);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment