static int ssl_check_certificate (CONNECTION *conn, sslsockdata *data) { int i, preauthrc, chain_len; STACK_OF(X509) *chain; X509 *cert; 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); /* 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. */ for (i = chain_len-1; i >= 0; i--) { cert = sk_X509_value (chain, i); /* 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)) { ssl_cache_trusted_cert (cert); if (ssl_check_preauth (data->cert, conn->account.host)) return 1; } } return 0; }
/** * ssl_verify_callback - Certificate verification callback * @param preverify_ok If true, don't question the user if they skipped verification * @param ctx X509 store context * @retval true Certificate is valid * @retval false Error, or Certificate is invalid * * Called for each certificate in the chain sent by the peer, starting from the * root; returning true means that the given certificate is trusted, returning * false immediately aborts the SSL connection */ static int ssl_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) { char buf[256]; const char *host = NULL; size_t len; int pos; X509 *cert = NULL; SSL *ssl = NULL; bool skip_mode; ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); if (!ssl) { mutt_debug(LL_DEBUG1, "failed to retrieve SSL structure from X509_STORE_CTX\n"); return false; } host = SSL_get_ex_data(ssl, HostExDataIndex); if (!host) { mutt_debug(LL_DEBUG1, "failed to retrieve hostname from SSL structure\n"); return false; } /* 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)); cert = X509_STORE_CTX_get_current_cert(ctx); pos = X509_STORE_CTX_get_error_depth(ctx); len = sk_X509_num(X509_STORE_CTX_get0_chain(ctx)); mutt_debug(LL_DEBUG1, "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); #ifdef HAVE_SSL_PARTIAL_CHAIN /* Sometimes, when a certificate is (s)kipped, OpenSSL will pass it * a second time with preverify_ok = 1. Don't show it or the user * will think their "s" key is broken. */ if (C_SslVerifyPartialChains) { static int last_pos = 0; static X509 *last_cert = NULL; if (skip_mode && preverify_ok && (pos == last_pos) && last_cert) { unsigned char last_cert_md[EVP_MAX_MD_SIZE]; unsigned int last_cert_mdlen; if (X509_digest(last_cert, EVP_sha256(), last_cert_md, &last_cert_mdlen) && compare_certificates(cert, last_cert, last_cert_md, last_cert_mdlen)) { mutt_debug(LL_DEBUG2, "ignoring duplicate skipped certificate.\n"); return true; } } last_pos = pos; if (last_cert) X509_free(last_cert); last_cert = X509_dup(cert); } #endif /* check session cache first */ if (check_certificate_cache(cert)) { mutt_debug(LL_DEBUG2, "using cached certificate\n"); SSL_set_ex_data(ssl, SkipModeExDataIndex, NULL); return true; } /* check hostname only for the leaf certificate */ buf[0] = '\0'; if ((pos == 0) && (C_SslVerifyHost != MUTT_NO)) { if (check_host(cert, host, buf, sizeof(buf)) == 0) { mutt_error(_("Certificate host check failed: %s"), buf); /* we disallow (a)ccept always in the prompt, because it will have no effect * for hostname mismatches. */ return interactive_check_cert(cert, pos, len, ssl, false); } mutt_debug(LL_DEBUG2, "hostname check passed\n"); } if (!preverify_ok || skip_mode) { /* automatic check from user's database */ if (C_CertificateFile && check_certificate_by_digest(cert)) { mutt_debug(LL_DEBUG2, "digest check passed\n"); SSL_set_ex_data(ssl, SkipModeExDataIndex, NULL); return true; } /* log verification error */ int err = X509_STORE_CTX_get_error(ctx); snprintf(buf, sizeof(buf), "%s (%d)", X509_verify_cert_error_string(err), err); mutt_debug(LL_DEBUG2, "X509_verify_cert: %s\n", buf); /* prompt user */ return interactive_check_cert(cert, pos, len, ssl, true); } return true; }