static int check_certificate_by_digest (X509 *peercert) { unsigned char peermd[EVP_MAX_MD_SIZE]; unsigned int peermdlen; X509 *cert = NULL; int pass = 0; FILE *fp; /* expiration check */ if (option (OPTSSLVERIFYDATES) != M_NO) { 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) return 0; if (!X509_digest (peercert, EVP_sha1(), peermd, &peermdlen)) { 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); safe_fclose (&fp); return pass; }
static int check_certificate_cache (X509 *peercert) { unsigned char peermd[EVP_MAX_MD_SIZE]; unsigned int peermdlen; X509 *cert; int i; if (!X509_digest (peercert, EVP_sha1(), peermd, &peermdlen) || !SslSessionCerts) { return 0; } for (i = sk_X509_num (SslSessionCerts); i-- > 0;) { cert = sk_X509_value (SslSessionCerts, i); if (!compare_certificates (cert, peercert, peermd, peermdlen)) { return 1; } } return 0; }
/** * check_certificate_cache - Is the X509 Certificate in the cache? * @param peercert Certificate * @retval true Certificate is in the cache */ static bool check_certificate_cache(X509 *peercert) { unsigned char peermd[EVP_MAX_MD_SIZE]; unsigned int peermdlen; X509 *cert = NULL; if (!X509_digest(peercert, EVP_sha256(), peermd, &peermdlen) || !SslSessionCerts) { return false; } for (int i = sk_X509_num(SslSessionCerts); i-- > 0;) { cert = sk_X509_value(SslSessionCerts, i); if (compare_certificates(cert, peercert, peermd, peermdlen)) { return true; } } return false; }
/** * check_certificate_file - Read and check a certificate file * @param peercert Certificate * @retval true Certificate is valid * @retval false Error, or certificate is invalid */ static bool check_certificate_file(X509 *peercert) { unsigned char peermd[EVP_MAX_MD_SIZE]; unsigned int peermdlen; X509 *cert = NULL; int pass = false; FILE *fp = NULL; if (!C_CertificateFile) return false; fp = fopen(C_CertificateFile, "rt"); if (!fp) return false; if (!X509_digest(peercert, EVP_sha256(), peermd, &peermdlen)) { mutt_file_fclose(&fp); return false; } while (PEM_read_X509(fp, &cert, NULL, NULL)) { if (compare_certificates(cert, peercert, peermd, peermdlen) && check_certificate_expiration(cert, true)) { pass = true; break; } } /* PEM_read_X509 sets an error on eof */ if (!pass) ERR_clear_error(); X509_free(cert); mutt_file_fclose(&fp); return pass; }
/** * 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; }