/* based on verify_extract_name from tls_client.c in postfix */ static gboolean irssi_ssl_verify_hostname(X509 *cert, const char *hostname) { int gen_index, gen_count; gboolean matched = FALSE, has_dns_name = FALSE; const char *cert_dns_name; char *cert_subject_cn; const GENERAL_NAME *gn; STACK_OF(GENERAL_NAME) * gens; GString *alt_names; alt_names = g_string_new(""); /* Verify the dNSName(s) in the peer certificate against the hostname. */ gens = X509_get_ext_d2i(cert, NID_subject_alt_name, 0, 0); if (gens) { gen_count = sk_GENERAL_NAME_num(gens); for (gen_index = 0; gen_index < gen_count && !matched; ++gen_index) { gn = sk_GENERAL_NAME_value(gens, gen_index); if (gn->type != GEN_DNS) continue; /* Even if we have an invalid DNS name, we still ultimately ignore the CommonName, because subjectAltName:DNS is present (though malformed). */ has_dns_name = TRUE; cert_dns_name = tls_dns_name(gn); if (cert_dns_name && *cert_dns_name) { g_string_append_printf(alt_names, " '%s'", cert_dns_name); matched = match_hostname(cert_dns_name, hostname); } } /* Free stack *and* member GENERAL_NAME objects */ sk_GENERAL_NAME_pop_free(gens, GENERAL_NAME_free); } if (has_dns_name) { if (! matched) { /* The CommonName in the issuer DN is obsolete when SubjectAltName is available. */ g_warning("None of the Subject Alt Names%s in the certificate match hostname '%s'", alt_names->str, hostname); } g_string_free(alt_names, TRUE); return matched; } else { /* No subjectAltNames, look at CommonName */ g_string_free(alt_names, TRUE); cert_subject_cn = tls_text_name(X509_get_subject_name(cert), NID_commonName); if (cert_subject_cn && *cert_subject_cn) { matched = match_hostname(cert_subject_cn, hostname); if (! matched) { g_warning("SSL certificate common name '%s' doesn't match host name '%s'", cert_subject_cn, hostname); } } else { g_warning("No subjectAltNames and no valid common name in certificate"); } free(cert_subject_cn); } return matched; }
static void verify_extract_name(TLS_SESS_STATE *TLScontext, X509 *peercert, const TLS_CLIENT_START_PROPS *props) { int i; int r; int matched = 0; int dnsname_match; int verify_peername = 0; int log_certmatch; int verbose; const char *dnsname; const GENERAL_NAME *gn; general_name_stack_t *gens; /* * On exit both peer_CN and issuer_CN should be set. */ TLScontext->issuer_CN = tls_issuer_CN(peercert, TLScontext); /* * Is the certificate trust chain valid and trusted? */ if (SSL_get_verify_result(TLScontext->con) == X509_V_OK) TLScontext->peer_status |= TLS_CERT_FLAG_TRUSTED; /* * With fingerprint or dane we may already be done. Otherwise, verify the * peername if using traditional PKI or DANE with trust-anchors. */ if (!TLS_CERT_IS_MATCHED(TLScontext) && TLS_CERT_IS_TRUSTED(TLScontext) && TLS_MUST_TRUST(props->tls_level)) verify_peername = 1; /* Force cert processing so we can log the data? */ log_certmatch = TLScontext->log_mask & TLS_LOG_CERTMATCH; /* Log cert details when processing? */ verbose = log_certmatch || (TLScontext->log_mask & TLS_LOG_VERBOSE); if (verify_peername || log_certmatch) { /* * Verify the dNSName(s) in the peer certificate against the nexthop * and hostname. * * If DNS names are present, we use the first matching (or else simply * the first) DNS name as the subject CN. The CommonName in the * issuer DN is obsolete when SubjectAltName is available. This * yields much less surprising logs, because we log the name we * verified or a name we checked and failed to match. * * XXX: The nexthop and host name may both be the same network address * rather than a DNS name. In this case we really should be looking * for GEN_IPADD entries, not GEN_DNS entries. * * XXX: In ideal world the caller who used the address to build the * connection would tell us that the nexthop is the connection * address, but if that is not practical, we can parse the nexthop * again here. */ gens = X509_get_ext_d2i(peercert, NID_subject_alt_name, 0, 0); if (gens) { r = sk_GENERAL_NAME_num(gens); for (i = 0; i < r; ++i) { gn = sk_GENERAL_NAME_value(gens, i); if (gn->type != GEN_DNS) continue; /* * Even if we have an invalid DNS name, we still ultimately * ignore the CommonName, because subjectAltName:DNS is * present (though malformed). Replace any previous peer_CN * if empty or we get a match. * * We always set at least an empty peer_CN if the ALTNAME cert * flag is set. If not, we set peer_CN from the cert * CommonName below, so peer_CN is always non-null on return. */ TLScontext->peer_status |= TLS_CERT_FLAG_ALTNAME; dnsname = tls_dns_name(gn, TLScontext); if (dnsname && *dnsname) { if ((dnsname_match = match_servername(dnsname, props)) != 0) matched++; /* Keep the first matched name. */ if (TLScontext->peer_CN && ((dnsname_match && matched == 1) || *TLScontext->peer_CN == 0)) { myfree(TLScontext->peer_CN); TLScontext->peer_CN = 0; } if (verbose) msg_info("%s: %ssubjectAltName: %s", props->namaddr, dnsname_match ? "Matched " : "", dnsname); } if (TLScontext->peer_CN == 0) TLScontext->peer_CN = mystrdup(dnsname ? dnsname : ""); if (matched && !log_certmatch) break; } if (verify_peername && matched) TLScontext->peer_status |= TLS_CERT_FLAG_MATCHED; /* * (Sam Rushing, Ironport) Free stack *and* member GENERAL_NAME * objects */ sk_GENERAL_NAME_pop_free(gens, GENERAL_NAME_free); } /* * No subjectAltNames, peer_CN is taken from CommonName. */ if (TLScontext->peer_CN == 0) { TLScontext->peer_CN = tls_peer_CN(peercert, TLScontext); if (*TLScontext->peer_CN) matched = match_servername(TLScontext->peer_CN, props); if (verify_peername && matched) TLScontext->peer_status |= TLS_CERT_FLAG_MATCHED; if (verbose) msg_info("%s %sCommonName %s", props->namaddr, matched ? "Matched " : "", TLScontext->peer_CN); } else if (verbose) { char *tmpcn = tls_peer_CN(peercert, TLScontext); /* * Though the CommonName was superceded by a subjectAltName, log * it when certificate match debugging was requested. */ msg_info("%s CommonName %s", TLScontext->namaddr, tmpcn); myfree(tmpcn); } } else TLScontext->peer_CN = tls_peer_CN(peercert, TLScontext); /* * Give them a clue. Problems with trust chain verification are logged * when the session is first negotiated, before the session is stored * into the cache. We don't want mystery failures, so log the fact the * real problem is to be found in the past. */ if (!TLS_CERT_IS_TRUSTED(TLScontext) && (TLScontext->log_mask & TLS_LOG_UNTRUSTED)) { if (TLScontext->session_reused == 0) tls_log_verify_error(TLScontext); else msg_info("%s: re-using session with untrusted certificate, " "look for details earlier in the log", props->namaddr); } }
static void verify_extract_name(TLS_SESS_STATE *TLScontext, X509 *peercert, const TLS_CLIENT_START_PROPS *props) { int i; int r; int matched = 0; const char *dnsname; const GENERAL_NAME *gn; STACK_OF(GENERAL_NAME) * gens; /* * On exit both peer_CN and issuer_CN should be set. */ TLScontext->issuer_CN = tls_issuer_CN(peercert, TLScontext); /* * Is the certificate trust chain valid and trusted? */ if (SSL_get_verify_result(TLScontext->con) == X509_V_OK) TLScontext->peer_status |= TLS_CERT_FLAG_TRUSTED; if (TLS_CERT_IS_TRUSTED(TLScontext) && props->tls_level >= TLS_LEV_VERIFY) { /* * Verify the dNSName(s) in the peer certificate against the nexthop * and hostname. * * If DNS names are present, we use the first matching (or else simply * the first) DNS name as the subject CN. The CommonName in the * issuer DN is obsolete when SubjectAltName is available. This * yields much less surprising logs, because we log the name we * verified or a name we checked and failed to match. * * XXX: The nexthop and host name may both be the same network address * rather than a DNS name. In this case we really should be looking * for GEN_IPADD entries, not GEN_DNS entries. * * XXX: In ideal world the caller who used the address to build the * connection would tell us that the nexthop is the connection * address, but if that is not practical, we can parse the nexthop * again here. */ gens = X509_get_ext_d2i(peercert, NID_subject_alt_name, 0, 0); if (gens) { r = sk_GENERAL_NAME_num(gens); for (i = 0; i < r && !matched; ++i) { gn = sk_GENERAL_NAME_value(gens, i); if (gn->type != GEN_DNS) continue; /* * Even if we have an invalid DNS name, we still ultimately * ignore the CommonName, because subjectAltName:DNS is * present (though malformed). Replace any previous peer_CN * if empty or we get a match. * * We always set at least an empty peer_CN if the ALTNAME cert * flag is set. If not, we set peer_CN from the cert * CommonName below, so peer_CN is always non-null on return. */ TLScontext->peer_status |= TLS_CERT_FLAG_ALTNAME; dnsname = tls_dns_name(gn, TLScontext); if (dnsname && *dnsname) { matched = match_hostname(dnsname, props); if (TLScontext->peer_CN && (matched || *TLScontext->peer_CN == 0)) { acl_myfree(TLScontext->peer_CN); TLScontext->peer_CN = 0; } } if (TLScontext->peer_CN == 0) TLScontext->peer_CN = acl_mystrdup(dnsname ? dnsname : ""); } /* * (Sam Rushing, Ironport) Free stack *and* member GENERAL_NAME * objects */ sk_GENERAL_NAME_pop_free(gens, GENERAL_NAME_free); } /* * No subjectAltNames, peer_CN is taken from CommonName. */ if (TLScontext->peer_CN == 0) { TLScontext->peer_CN = tls_peer_CN(peercert, TLScontext); if (*TLScontext->peer_CN) matched = match_hostname(TLScontext->peer_CN, props); } if (matched) TLScontext->peer_status |= TLS_CERT_FLAG_MATCHED; /* * - Matched: Trusted and peername matches - Trusted: Signed by * trusted CA(s), but peername not matched - Untrusted: Can't verify * the trust chain, reason already logged. */ if (TLScontext->log_level >= 2) acl_msg_info("%s: %s subject_CN=%s, issuer_CN=%s", props->namaddr, TLS_CERT_IS_MATCHED(TLScontext) ? "Matched" : TLS_CERT_IS_TRUSTED(TLScontext) ? "Trusted" : "Untrusted", TLScontext->peer_CN, TLScontext->issuer_CN); } else TLScontext->peer_CN = tls_peer_CN(peercert, TLScontext); /* * Give them a clue. Problems with trust chain verification were logged * when the session was first negotiated, before the session was stored * into the cache. We don't want mystery failures, so log the fact the * real problem is to be found in the past. */ if (TLScontext->session_reused && !TLS_CERT_IS_TRUSTED(TLScontext) && TLScontext->log_level >= 1) acl_msg_info("%s: re-using session with untrusted certificate, " "look for details earlier in the log", props->namaddr); }