Skip the CN next to a DNS SAN on Mbed TLS and wolfSSL

Hostname verification on these backends fell back to the subject CN
even when the certificate had a DNS SAN, unlike X509_check_host and
RFC 6125 6.4.4. It does so only without one now. Mbed TLS also reads
the CN by its OID instead of searching the formatted DN for "CN=", and
no longer stops at an empty SAN.
This commit is contained in:
yhirose committed 2026-10-16 21:38:40 -04:00
1 parent bf08ec763c
commit 9724ea6b2e
2 files changed
+25 -37

No files matched your search

+21 -37
View File
@@ -21463,8 +21463,9 @@ inline bool verify_hostname(cert_t cert, const char *hostname) {
// Check Subject Alternative Names (SAN). Mbed TLS keeps the GeneralName type // Check Subject Alternative Names (SAN). Mbed TLS keeps the GeneralName type
// in buf.tag and the raw value in buf.p / buf.len. // in buf.tag and the raw value in buf.p / buf.len.
const mbedtls_x509_sequence *san = &mcert->subject_alt_names; auto has_dns_san = false;
while (san != nullptr && san->buf.p != nullptr && san->buf.len > 0) { for (auto san = &mcert->subject_alt_names; san; san = san->next) {
if (!san->buf.p || san->buf.len == 0) { continue; }
const unsigned char *p = san->buf.p; const unsigned char *p = san->buf.p;
size_t len = san->buf.len; size_t len = san->buf.len;
auto san_type = san->buf.tag & MBEDTLS_ASN1_TAG_VALUE_MASK; auto san_type = san->buf.tag & MBEDTLS_ASN1_TAG_VALUE_MASK;
@@ -21477,31 +21478,21 @@ inline bool verify_hostname(cert_t cert, const char *hostname) {
return true; return true;
} }
} else if (san_type == MBEDTLS_X509_SAN_DNS_NAME) { } else if (san_type == MBEDTLS_X509_SAN_DNS_NAME) {
has_dns_san = true;
std::string san_name(reinterpret_cast<const char *>(p), len); std::string san_name(reinterpret_cast<const char *>(p), len);
if (detail::match_hostname(san_name, host_str)) { return true; } if (detail::match_hostname(san_name, host_str)) { return true; }
} }
san = san->next;
} }
// Fallback: Check Common Name (CN) in subject. Skipped for IP-literal hosts: // Fallback: the Common Names (CN) in the subject, as X509_check_host does it:
// an IP identity is only valid via an iPAddress SAN, never the CN (RFC 9110; // never for an IP host, which only an iPAddress SAN names, and never next to
// the OpenSSL backend's X509_check_ip behaves the same way). // a DNS SAN (RFC 6125 6.4.4).
if (!is_ip) { if (is_ip || has_dns_san) { return false; }
char cn[256]; for (auto name = &mcert->subject; name; name = name->next) {
int ret = mbedtls_x509_dn_gets(cn, sizeof(cn), &mcert->subject); if (MBEDTLS_OID_CMP(MBEDTLS_OID_AT_CN, &name->oid) == 0) {
if (ret > 0) { std::string cn(reinterpret_cast<const char *>(name->val.p),
std::string cn_str(cn); name->val.len);
if (detail::match_hostname(cn, host_str)) { return true; }
// Look for "CN=" in the DN string
size_t cn_pos = cn_str.find("CN=");
if (cn_pos != std::string::npos) {
size_t start = cn_pos + 3;
size_t end = cn_str.find(',', start);
std::string cn_value =
cn_str.substr(start, end == std::string::npos ? end : end - start);
if (detail::match_hostname(cn_value, host_str)) { return true; }
}
} }
} }
@@ -22626,6 +22617,7 @@ inline bool verify_hostname(cert_t cert, const char *hostname) {
auto is_ip = ip_len > 0; auto is_ip = ip_len > 0;
// Check Subject Alternative Names // Check Subject Alternative Names
auto has_dns_san = false;
auto *san_names = static_cast<WOLF_STACK_OF(WOLFSSL_GENERAL_NAME) *>( auto *san_names = static_cast<WOLF_STACK_OF(WOLFSSL_GENERAL_NAME) *>(
wolfSSL_X509_get_ext_d2i(x509, NID_subject_alt_name, nullptr, nullptr)); wolfSSL_X509_get_ext_d2i(x509, NID_subject_alt_name, nullptr, nullptr));
@@ -22638,6 +22630,7 @@ inline bool verify_hostname(cert_t cert, const char *hostname) {
if (!is_ip && names->type == WOLFSSL_GEN_DNS) { if (!is_ip && names->type == WOLFSSL_GEN_DNS) {
// DNS name // DNS name
has_dns_san = true;
unsigned char *dns_name = nullptr; unsigned char *dns_name = nullptr;
int dns_len = wolfSSL_ASN1_STRING_to_UTF8(&dns_name, names->d.dNSName); int dns_len = wolfSSL_ASN1_STRING_to_UTF8(&dns_name, names->d.dNSName);
if (dns_name && dns_len > 0) { if (dns_name && dns_len > 0) {
@@ -22664,21 +22657,12 @@ inline bool verify_hostname(cert_t cert, const char *hostname) {
wolfSSL_sk_free(san_names); wolfSSL_sk_free(san_names);
} }
// Fallback: Check Common Name (CN) in subject. Skipped for IP-literal hosts: // Fallback: the Common Name (CN) in the subject, as X509_check_host does it:
// an IP identity is only valid via an iPAddress SAN, never the CN (RFC 9110; // never for an IP host, which only an iPAddress SAN names, and never next to
// the OpenSSL backend's X509_check_ip behaves the same way). // a DNS SAN (RFC 6125 6.4.4).
auto subject = is_ip ? nullptr : wolfSSL_X509_get_subject_name(x509); if (is_ip || has_dns_san) { return false; }
if (subject) { auto cn = get_cert_subject_cn(cert);
char cn[256] = {}; return !cn.empty() && detail::match_hostname(cn, host_str);
int cn_len = wolfSSL_X509_NAME_get_text_by_NID(subject, NID_commonName, cn,
sizeof(cn));
if (cn_len > 0) {
std::string cn_str(cn, static_cast<size_t>(cn_len));
if (detail::match_hostname(cn_str, host_str)) { return true; }
}
}
return false;
} }
inline uint64_t hostname_mismatch_code() { inline uint64_t hostname_mismatch_code() {
+4
View File
@@ -16196,6 +16196,7 @@ TEST(SSLClientServerTest, TlsVerifyHostnameSanType) {
bool ip_san_matched = false; bool ip_san_matched = false;
bool ip_matched_via_dns_san = true; bool ip_matched_via_dns_san = true;
bool dns_matched_via_ip_san = true; bool dns_matched_via_ip_san = true;
bool cn_matched_next_to_dns_san = true;
SSLClient cli(HOST, port); SSLClient cli(HOST, port);
cli.enable_server_certificate_verification(true); cli.enable_server_certificate_verification(true);
@@ -16211,6 +16212,7 @@ TEST(SSLClientServerTest, TlsVerifyHostnameSanType) {
ip_matched_via_dns_san = ctx.check_hostname("97.46.122.122"); ip_matched_via_dns_san = ctx.check_hostname("97.46.122.122");
dns_matched_via_ip_san = ctx.check_hostname("b.zz"); dns_matched_via_ip_san = ctx.check_hostname("b.zz");
cn_matched_next_to_dns_san = ctx.check_hostname("san-types");
return true; // Accept for the purpose of this test return true; // Accept for the purpose of this test
}); });
@@ -16226,6 +16228,8 @@ TEST(SSLClientServerTest, TlsVerifyHostnameSanType) {
<< "An IP host must not be authenticated via a dNSName SAN"; << "An IP host must not be authenticated via a dNSName SAN";
EXPECT_FALSE(dns_matched_via_ip_san) EXPECT_FALSE(dns_matched_via_ip_san)
<< "A DNS host must not be authenticated via an iPAddress SAN"; << "A DNS host must not be authenticated via an iPAddress SAN";
EXPECT_FALSE(cn_matched_next_to_dns_san)
<< "The CN must not be consulted next to a dNSName SAN (RFC 6125)";
} }
// RFC 6125 6.4.3: only the leftmost label of a dNSName may be a wildcard, so // RFC 6125 6.4.3: only the leftmost label of a dNSName may be a wildcard, so