mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-29 12:50:51 +00:00
Match certificate names as X509_check_host does
The Mbed TLS and wolfSSL backends matched *.com, a bare *, vic*.test and names with empty labels, which OpenSSL rejects. Require two labels after a wildcard, none in an IDNA label and no empty label, and ignore a trailing dot on the host, so every backend accepts the same names.
This commit is contained in:
1 parent
07c3e8c9e0
commit
314e5577b8
2 files changed
+59
-16
No files matched your search
@@ -10902,31 +10902,51 @@ inline std::pair<std::string, std::string> make_digest_authentication_header(
|
|||||||
return std::make_pair(key, field);
|
return std::make_pair(key, field);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Follows X509_check_host, which the OpenSSL backend uses, so that every
|
||||||
|
// backend accepts the same names.
|
||||||
inline bool match_hostname(const std::string &pattern,
|
inline bool match_hostname(const std::string &pattern,
|
||||||
const std::string &hostname) {
|
const std::string &hostname) {
|
||||||
|
// A trailing dot on the host is ignored
|
||||||
|
auto host = hostname;
|
||||||
|
if (host.size() > 1 && host.back() == '.') { host.pop_back(); }
|
||||||
|
|
||||||
// Exact match (case-insensitive)
|
// Exact match (case-insensitive)
|
||||||
if (detail::case_ignore::equal(hostname, pattern)) { return true; }
|
if (detail::case_ignore::equal(host, pattern)) { return true; }
|
||||||
|
|
||||||
// Split both pattern and hostname into components by '.'
|
// Split both pattern and hostname into components by '.', keeping any empty
|
||||||
std::vector<std::string> pattern_components;
|
// one, which no name may have
|
||||||
if (!pattern.empty()) {
|
auto labels = [](const std::string &s) {
|
||||||
split(pattern.data(), pattern.data() + pattern.size(), '.',
|
std::vector<std::string> v;
|
||||||
[&](const char *b, const char *e) {
|
size_t b = 0;
|
||||||
pattern_components.emplace_back(b, e);
|
while (true) {
|
||||||
});
|
auto e = s.find('.', b);
|
||||||
}
|
v.push_back(s.substr(b, e - b));
|
||||||
|
if (e == std::string::npos) { return v; }
|
||||||
std::vector<std::string> host_components;
|
b = e + 1;
|
||||||
if (!hostname.empty()) {
|
}
|
||||||
split(hostname.data(), hostname.data() + hostname.size(), '.',
|
};
|
||||||
[&](const char *b, const char *e) {
|
auto pattern_components = labels(pattern);
|
||||||
host_components.emplace_back(b, e);
|
auto host_components = labels(host);
|
||||||
});
|
auto has_empty_label = [](const std::vector<std::string> &v) {
|
||||||
|
return std::any_of(v.begin(), v.end(),
|
||||||
|
[](const std::string &l) { return l.empty(); });
|
||||||
|
};
|
||||||
|
if (has_empty_label(pattern_components) || has_empty_label(host_components)) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Component count must match
|
// Component count must match
|
||||||
if (host_components.size() != pattern_components.size()) { return false; }
|
if (host_components.size() != pattern_components.size()) { return false; }
|
||||||
|
|
||||||
|
// A wildcard needs two labels after it, so that "*.com" matches nothing,
|
||||||
|
// and an IDNA label may not have one
|
||||||
|
const auto &leftmost = pattern_components.front();
|
||||||
|
if (leftmost.find('*') != std::string::npos &&
|
||||||
|
(pattern_components.size() < 3 ||
|
||||||
|
case_ignore::equal(leftmost.substr(0, 4), "xn--"))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Compare each component with wildcard support
|
// Compare each component with wildcard support
|
||||||
// Supports: "*" (full wildcard), "prefix*" (partial wildcard)
|
// Supports: "*" (full wildcard), "prefix*" (partial wildcard)
|
||||||
// https://bugs.launchpad.net/ubuntu/+source/firefox-3.0/+bug/376484
|
// https://bugs.launchpad.net/ubuntu/+source/firefox-3.0/+bug/376484
|
||||||
|
|||||||
@@ -562,6 +562,9 @@ find_content_type(const std::string &path,
|
|||||||
const std::string &default_content_type);
|
const std::string &default_content_type);
|
||||||
bool can_compress_content_type(const std::string &content_type);
|
bool can_compress_content_type(const std::string &content_type);
|
||||||
bool is_prohibited_header_name(const std::string &name);
|
bool is_prohibited_header_name(const std::string &name);
|
||||||
|
#ifdef CPPHTTPLIB_SSL_ENABLED
|
||||||
|
bool match_hostname(const std::string &pattern, const std::string &hostname);
|
||||||
|
#endif
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
} // namespace httplib
|
} // namespace httplib
|
||||||
|
|
||||||
@@ -645,6 +648,26 @@ TEST(FileExtensionTest, TakesTheAlphanumericSuffix) {
|
|||||||
EXPECT_EQ(long_ext, detail::file_extension("a." + long_ext));
|
EXPECT_EQ(long_ext, detail::file_extension("a." + long_ext));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef CPPHTTPLIB_SSL_ENABLED
|
||||||
|
TEST(MatchHostnameTest, FollowsX509CheckHost) {
|
||||||
|
EXPECT_TRUE(detail::match_hostname("*.example.com", "www.example.com"));
|
||||||
|
EXPECT_TRUE(detail::match_hostname("www*.example.com", "www1.example.com"));
|
||||||
|
EXPECT_TRUE(detail::match_hostname("www.example.com", "WWW.example.com."));
|
||||||
|
|
||||||
|
// A wildcard needs two labels after it, in the leftmost label only
|
||||||
|
EXPECT_FALSE(detail::match_hostname("*.com", "example.com"));
|
||||||
|
EXPECT_FALSE(detail::match_hostname("*", "localhost"));
|
||||||
|
EXPECT_FALSE(detail::match_hostname("vic*.test", "victim.test"));
|
||||||
|
EXPECT_FALSE(detail::match_hostname("www.*.com", "www.a.com"));
|
||||||
|
EXPECT_FALSE(
|
||||||
|
detail::match_hostname("xn--*.example.com", "xn--a.example.com"));
|
||||||
|
|
||||||
|
// No name may have an empty label
|
||||||
|
EXPECT_FALSE(detail::match_hostname("*.example.com", "a..example.com"));
|
||||||
|
EXPECT_FALSE(detail::match_hostname("a..example.com", "a.example.com"));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
TEST(CaseIgnoreHashTest, IgnoresCaseAndTakesLongKeys) {
|
TEST(CaseIgnoreHashTest, IgnoresCaseAndTakesLongKeys) {
|
||||||
detail::case_ignore::hash hash;
|
detail::case_ignore::hash hash;
|
||||||
EXPECT_EQ(hash("Content-Type"), hash("content-TYPE"));
|
EXPECT_EQ(hash("Content-Type"), hash("content-TYPE"));
|
||||||
|
|||||||
Reference in new issue
Block a user