@@ -2878,13 +2878,6 @@ class SSLClient final : public ClientImpl {
28782878#endif
28792879
28802880 friend class ClientImpl;
2881-
2882- #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
2883- private:
2884- bool verify_host(X509 *server_cert) const;
2885- bool verify_host_with_subject_alt_name(X509 *server_cert) const;
2886- bool verify_host_with_common_name(X509 *server_cert) const;
2887- #endif
28882881};
28892882#endif // CPPHTTPLIB_SSL_ENABLED
28902883
@@ -16526,6 +16519,21 @@ inline bool parse_ipv4(const std::string &str, unsigned char *out) {
1652616519 return *p == '\0';
1652716520}
1652816521
16522+ // Parse an IP literal (IPv4 or IPv6) into raw network-order bytes.
16523+ // `out` must have room for at least 16 bytes. Returns the address length
16524+ // (4 for IPv4, 16 for IPv6) on success, or 0 if the string is not an IP
16525+ // literal. Used to match a host against iPAddress SANs the same way the
16526+ // OpenSSL backend does via X509_check_ip.
16527+ inline size_t parse_ip_address(const std::string &str, unsigned char *out) {
16528+ if (is_ipv4_address(str)) { return parse_ipv4(str, out) ? 4 : 0; }
16529+ struct in6_addr addr6 = {};
16530+ if (inet_pton(AF_INET6, str.c_str(), &addr6) == 1) {
16531+ memcpy(out, &addr6, 16);
16532+ return 16;
16533+ }
16534+ return 0;
16535+ }
16536+
1652916537#ifdef _WIN32
1653016538// Enumerate Windows system certificates and call callback with DER data
1653116539template <typename Callback>
@@ -17725,99 +17733,6 @@ inline std::string verify_error_string(long error_code) {
1772517733
1772617734} // namespace tls
1772717735
17728- inline bool SSLClient::verify_host(X509 *server_cert) const {
17729- /* Quote from RFC2818 section 3.1 "Server Identity"
17730-
17731- If a subjectAltName extension of type dNSName is present, that MUST
17732- be used as the identity. Otherwise, the (most specific) Common Name
17733- field in the Subject field of the certificate MUST be used. Although
17734- the use of the Common Name is existing practice, it is deprecated and
17735- Certification Authorities are encouraged to use the dNSName instead.
17736-
17737- Matching is performed using the matching rules specified by
17738- [RFC2459]. If more than one identity of a given type is present in
17739- the certificate (e.g., more than one dNSName name, a match in any one
17740- of the set is considered acceptable.) Names may contain the wildcard
17741- character * which is considered to match any single domain name
17742- component or component fragment. E.g., *.a.com matches foo.a.com but
17743- not bar.foo.a.com. f*.com matches foo.com but not bar.com.
17744-
17745- In some cases, the URI is specified as an IP address rather than a
17746- hostname. In this case, the iPAddress subjectAltName must be present
17747- in the certificate and must exactly match the IP in the URI.
17748-
17749- */
17750- return verify_host_with_subject_alt_name(server_cert) ||
17751- verify_host_with_common_name(server_cert);
17752- }
17753-
17754- inline bool
17755- SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const {
17756- auto ret = false;
17757-
17758- auto type = GEN_DNS;
17759-
17760- struct in6_addr addr6 = {};
17761- struct in_addr addr = {};
17762- size_t addr_len = 0;
17763-
17764- #ifndef __MINGW32__
17765- if (inet_pton(AF_INET6, host_.c_str(), &addr6)) {
17766- type = GEN_IPADD;
17767- addr_len = sizeof(struct in6_addr);
17768- } else if (inet_pton(AF_INET, host_.c_str(), &addr)) {
17769- type = GEN_IPADD;
17770- addr_len = sizeof(struct in_addr);
17771- }
17772- #endif
17773-
17774- auto alt_names = static_cast<const struct stack_st_GENERAL_NAME *>(
17775- X509_get_ext_d2i(server_cert, NID_subject_alt_name, nullptr, nullptr));
17776-
17777- if (alt_names) {
17778- auto dsn_matched = false;
17779- auto ip_matched = false;
17780-
17781- auto count = sk_GENERAL_NAME_num(alt_names);
17782-
17783- for (decltype(count) i = 0; i < count && !dsn_matched; i++) {
17784- auto val = sk_GENERAL_NAME_value(alt_names, i);
17785- if (!val || val->type != type) { continue; }
17786-
17787- auto name =
17788- reinterpret_cast<const char *>(ASN1_STRING_get0_data(val->d.ia5));
17789- if (name == nullptr) { continue; }
17790-
17791- auto name_len = static_cast<size_t>(ASN1_STRING_length(val->d.ia5));
17792-
17793- switch (type) {
17794- case GEN_DNS:
17795- dsn_matched =
17796- detail::match_hostname(std::string(name, name_len), host_);
17797- break;
17798-
17799- case GEN_IPADD:
17800- if (!memcmp(&addr6, name, addr_len) || !memcmp(&addr, name, addr_len)) {
17801- ip_matched = true;
17802- }
17803- break;
17804- }
17805- }
17806-
17807- if (dsn_matched || ip_matched) { ret = true; }
17808- }
17809-
17810- GENERAL_NAMES_free(const_cast<STACK_OF(GENERAL_NAME) *>(
17811- reinterpret_cast<const STACK_OF(GENERAL_NAME) *>(alt_names)));
17812- return ret;
17813- }
17814-
17815- inline bool SSLClient::verify_host_with_common_name(X509 *server_cert) const {
17816- auto cn = tls::get_cert_subject_cn(static_cast<tls::cert_t>(server_cert));
17817- if (cn.empty()) { return false; }
17818- return detail::match_hostname(cn, host_);
17819- }
17820-
1782117736#endif // CPPHTTPLIB_OPENSSL_SUPPORT
1782217737
1782317738/*
@@ -18620,10 +18535,10 @@ inline bool verify_hostname(cert_t cert, const char *hostname) {
1862018535 auto mcert = static_cast<const mbedtls_x509_crt *>(cert);
1862118536 std::string host_str(hostname);
1862218537
18623- // Check if hostname is an IP address
18624- bool is_ip = impl::is_ipv4_address(host_str) ;
18625- unsigned char ip_bytes[4] ;
18626- if ( is_ip) { impl::parse_ipv4(host_str, ip_bytes); }
18538+ // Check if hostname is an IP address (IPv4 or IPv6)
18539+ unsigned char ip_bytes[16] ;
18540+ auto ip_len = impl::parse_ip_address(host_str, ip_bytes) ;
18541+ auto is_ip = ip_len > 0;
1862718542
1862818543 // Check Subject Alternative Names (SAN)
1862918544 // In Mbed TLS 3.x, subject_alt_names contains raw values without ASN.1 tags
@@ -18635,9 +18550,9 @@ inline bool verify_hostname(cert_t cert, const char *hostname) {
1863518550 size_t len = san->buf.len;
1863618551
1863718552 if (is_ip) {
18638- // Check if this SAN is an IPv4 address (4 bytes)
18639- if (len == 4 && memcmp(p, ip_bytes, 4) == 0) { return true; }
18640- // Check if this SAN is an IPv6 address (16 bytes) - skip for now
18553+ // For an IP host, only a matching iPAddress SAN of the same family
18554+ // (4 bytes for IPv4, 16 bytes for IPv6) may authenticate it.
18555+ if (len == ip_len && memcmp(p, ip_bytes, ip_len) == 0) { return true; }
1864118556 } else {
1864218557 // Check if this SAN is a DNS name (printable ASCII string)
1864318558 bool is_dns = len > 0;
@@ -18652,21 +18567,25 @@ inline bool verify_hostname(cert_t cert, const char *hostname) {
1865218567 san = san->next;
1865318568 }
1865418569
18655- // Fallback: Check Common Name (CN) in subject
18656- char cn[256];
18657- int ret = mbedtls_x509_dn_gets(cn, sizeof(cn), &mcert->subject);
18658- if (ret > 0) {
18659- std::string cn_str(cn);
18660-
18661- // Look for "CN=" in the DN string
18662- size_t cn_pos = cn_str.find("CN=");
18663- if (cn_pos != std::string::npos) {
18664- size_t start = cn_pos + 3;
18665- size_t end = cn_str.find(',', start);
18666- std::string cn_value =
18667- cn_str.substr(start, end == std::string::npos ? end : end - start);
18668-
18669- if (detail::match_hostname(cn_value, host_str)) { return true; }
18570+ // Fallback: Check Common Name (CN) in subject. Skipped for IP-literal hosts:
18571+ // an IP identity is only valid via an iPAddress SAN, never the CN (RFC 9110;
18572+ // the OpenSSL backend's X509_check_ip behaves the same way).
18573+ if (!is_ip) {
18574+ char cn[256];
18575+ int ret = mbedtls_x509_dn_gets(cn, sizeof(cn), &mcert->subject);
18576+ if (ret > 0) {
18577+ std::string cn_str(cn);
18578+
18579+ // Look for "CN=" in the DN string
18580+ size_t cn_pos = cn_str.find("CN=");
18581+ if (cn_pos != std::string::npos) {
18582+ size_t start = cn_pos + 3;
18583+ size_t end = cn_str.find(',', start);
18584+ std::string cn_value =
18585+ cn_str.substr(start, end == std::string::npos ? end : end - start);
18586+
18587+ if (detail::match_hostname(cn_value, host_str)) { return true; }
18588+ }
1867018589 }
1867118590 }
1867218591
@@ -19772,10 +19691,10 @@ inline bool verify_hostname(cert_t cert, const char *hostname) {
1977219691 auto x509 = static_cast<WOLFSSL_X509 *>(cert);
1977319692 std::string host_str(hostname);
1977419693
19775- // Check if hostname is an IP address
19776- bool is_ip = impl::is_ipv4_address(host_str) ;
19777- unsigned char ip_bytes[4] ;
19778- if ( is_ip) { impl::parse_ipv4(host_str, ip_bytes); }
19694+ // Check if hostname is an IP address (IPv4 or IPv6)
19695+ unsigned char ip_bytes[16] ;
19696+ auto ip_len = impl::parse_ip_address(host_str, ip_bytes) ;
19697+ auto is_ip = ip_len > 0;
1977919698
1978019699 // Check Subject Alternative Names
1978119700 auto *san_names = static_cast<WOLF_STACK_OF(WOLFSSL_GENERAL_NAME) *>(
@@ -19802,10 +19721,12 @@ inline bool verify_hostname(cert_t cert, const char *hostname) {
1980219721 }
1980319722 }
1980419723 } else if (is_ip && names->type == WOLFSSL_GEN_IPADD) {
19805- // IP address
19724+ // IP address: only an iPAddress SAN of the same family (4 bytes for
19725+ // IPv4, 16 bytes for IPv6) may authenticate the host.
1980619726 unsigned char *ip_data = wolfSSL_ASN1_STRING_data(names->d.iPAddress);
19807- int ip_len = wolfSSL_ASN1_STRING_length(names->d.iPAddress);
19808- if (ip_data && ip_len == 4 && memcmp(ip_data, ip_bytes, 4) == 0) {
19727+ auto san_ip_len = wolfSSL_ASN1_STRING_length(names->d.iPAddress);
19728+ if (ip_data && san_ip_len == static_cast<int>(ip_len) &&
19729+ memcmp(ip_data, ip_bytes, ip_len) == 0) {
1980919730 wolfSSL_sk_free(san_names);
1981019731 return true;
1981119732 }
@@ -19814,8 +19735,10 @@ inline bool verify_hostname(cert_t cert, const char *hostname) {
1981419735 wolfSSL_sk_free(san_names);
1981519736 }
1981619737
19817- // Fallback: Check Common Name (CN) in subject
19818- WOLFSSL_X509_NAME *subject = wolfSSL_X509_get_subject_name(x509);
19738+ // Fallback: Check Common Name (CN) in subject. Skipped for IP-literal hosts:
19739+ // an IP identity is only valid via an iPAddress SAN, never the CN (RFC 9110;
19740+ // the OpenSSL backend's X509_check_ip behaves the same way).
19741+ auto subject = is_ip ? nullptr : wolfSSL_X509_get_subject_name(x509);
1981919742 if (subject) {
1982019743 char cn[256] = {};
1982119744 int cn_len = wolfSSL_X509_NAME_get_text_by_NID(subject, NID_commonName, cn,
0 commit comments