#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>
/* 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;
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*);
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))
+ {
+ mutt_debug (2, "ssl_set_verify_partial: SSL_CTX_set1_param() failed.");
+ rv = -1;
+ }
+ X509_VERIFY_PARAM_free(param);
+ }
+ else
+ {
+ mutt_debug (2, "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)
}
}
+ 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)))
{
mutt_debug (1, "mutt_ssl_starttls: Error allocating SSL\n");
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);
return -1;
}
+ if ((SkipModeExDataIndex = SSL_get_ex_new_index (0, "skip", NULL, NULL, NULL)) == -1)
+ {
+ mutt_debug (1, "failed to get index for application specific data\n");
+ return -1;
+ }
+
+ if (! SSL_set_ex_data (ssldata->ssl, SkipModeExDataIndex, NULL))
+ {
+ mutt_debug (1, "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 ();
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 ())))
{
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));
- mutt_debug (1, "ssl_verify_callback: checking cert chain entry %s (preverify: %d)\n",
- X509_NAME_oneline (X509_get_subject_name (cert),
- buf, sizeof (buf)), preverify_ok);
+ mutt_debug (1, "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))
{
mutt_debug (2, "ssl_verify_callback: using cached certificate\n");
+ SSL_set_ex_data (ssl, SkipModeExDataIndex, NULL);
return 1;
}
{
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);
}
mutt_debug (2, "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))
{
mutt_debug (2, "ssl_verify_callback: digest check passed\n");
+ SSL_set_ex_data (ssl, SkipModeExDataIndex, NULL);
return 1;
}
}
#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 */
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 = safe_calloc (1, menu->max * sizeof (char *));
_("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';
done = 1;
break;
case OP_MAX + 3: /* accept always */
+ if (!allow_always)
+ break;
done = 0;
if ((fp = fopen (SslCertFile, "a")))
{
/* 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);