diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index f315cd6dc2510..3b120e5ccfcd2 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -22,6 +22,7 @@ Bug Fixes * data plane: fixing error handling where writing to a socket failed while under the stack of processing. This should genreally affect HTTP/3. This behavioral change can be reverted by setting ``envoy.reloadable_features.allow_upstream_inline_write`` to false. * eds: fix the eds cluster update by allowing update on the locality of the cluster endpoints. This behavioral change can be temporarily reverted by setting runtime guard ``envoy.reloadable_features.support_locality_update_on_eds_cluster_endpoints`` to false. +* tls: fix a bug while matching a certificate SAN with an exact value in ``match_typed_subject_alt_names`` of a listener where wildcard ``*`` character is not the only character of the dns label. Example, ``baz*.example.net`` and ``*baz.example.net`` and ``b*z.example.net`` will match ``baz1.example.net`` and ``foobaz.example.net`` and ``buzz.example.net``, respectively. * xray: fix the AWS X-Ray tracer extension to not sample the trace if ``sampled=`` keyword is not present in the header ``x-amzn-trace-id``. Removed Config or Runtime diff --git a/source/extensions/transport_sockets/tls/utility.cc b/source/extensions/transport_sockets/tls/utility.cc index e2b220c9468ed..3c89b60139306 100644 --- a/source/extensions/transport_sockets/tls/utility.cc +++ b/source/extensions/transport_sockets/tls/utility.cc @@ -71,21 +71,46 @@ Envoy::Ssl::CertificateDetailsPtr Utility::certificateDetails(X509* cert, const return certificate_details; } +bool Utility::labelWildcardMatch(absl::string_view dns_label, absl::string_view pattern) { + constexpr char glob = '*'; + // Check the special case of a single * pattern, as it's common. + if (pattern.size() == 1 && pattern[0] == glob) { + return true; + } + // Only valid if wildcard character appear once. + if (std::count(pattern.begin(), pattern.end(), glob) == 1) { + std::vector split_pattern = absl::StrSplit(pattern, glob); + return (pattern.size() <= dns_label.size() + 1) && + absl::StartsWith(dns_label, split_pattern[0]) && + absl::EndsWith(dns_label, split_pattern[1]); + } + return false; +} + bool Utility::dnsNameMatch(absl::string_view dns_name, absl::string_view pattern) { + // A-label ACE prefix https://www.rfc-editor.org/rfc/rfc5890#section-2.3.2.5. + constexpr absl::string_view ACE_prefix = "xn--"; const std::string lower_case_dns_name = absl::AsciiStrToLower(dns_name); const std::string lower_case_pattern = absl::AsciiStrToLower(pattern); if (lower_case_dns_name == lower_case_pattern) { return true; } - size_t pattern_len = lower_case_pattern.length(); - if (pattern_len > 1 && lower_case_pattern[0] == '*' && lower_case_pattern[1] == '.') { - if (lower_case_dns_name.length() > pattern_len - 1) { - const size_t off = lower_case_dns_name.length() - pattern_len + 1; - return lower_case_dns_name.substr(0, off).find('.') == std::string::npos && - lower_case_dns_name.substr(off, pattern_len - 1) == - lower_case_pattern.substr(1, pattern_len - 1); - } + std::vector split_pattern = + absl::StrSplit(lower_case_pattern, absl::MaxSplits('.', 1)); + std::vector split_dns_name = + absl::StrSplit(lower_case_dns_name, absl::MaxSplits('.', 1)); + + // dns name and pattern should contain more than 1 label to match. + if (split_pattern.size() < 2 || split_dns_name.size() < 2) { + return false; + } + // Only the left-most label in the pattern contains wildcard '*' and is not an A-label. + if ((split_pattern[0].find('*') != absl::string_view::npos) && + (split_pattern[1].find('*') == absl::string_view::npos) && + (!absl::StartsWith(split_pattern[0], ACE_prefix))) { + return (split_dns_name[1] == split_pattern[1]) && + labelWildcardMatch(split_dns_name[0], split_pattern[0]); } return false; diff --git a/source/extensions/transport_sockets/tls/utility.h b/source/extensions/transport_sockets/tls/utility.h index e38589e30d887..c1fe2a1d212f4 100644 --- a/source/extensions/transport_sockets/tls/utility.h +++ b/source/extensions/transport_sockets/tls/utility.h @@ -21,13 +21,25 @@ Envoy::Ssl::CertificateDetailsPtr certificateDetails(X509* cert, const std::stri TimeSource& time_source); /** - * Determines whether the given name matches 'pattern' which may optionally begin with a wildcard. + * Determines whether the given name matches 'pattern' which may optionally begin with a wildcard + * or contain a wildcard inside the pattern's first label. + * See: https://www.rfc-editor.org/rfc/rfc6125#section-6.4.3. * @param dns_name the DNS name to match - * @param pattern the pattern to match against (*.example.com) + * @param pattern the pattern to match against (*.example.com) or (test*.example.com) * @return true if the san matches pattern */ bool dnsNameMatch(absl::string_view dns_name, absl::string_view pattern); +/** + * Determines whether the given DNS label matches 'pattern' which may contain a wildcard. e.g., + * patterns "baz*" and "*baz" and "b*z" would match DNS labels "baz1" and "foobaz" and "buzz", + * respectively. + * @param dns_label the DNS name label to match in lower case + * @param pattern the pattern to match against in lower case + * @return true if the dns_label matches pattern + */ +bool labelWildcardMatch(absl::string_view dns_label, absl::string_view pattern); + /** * Retrieves the serial number of a certificate. * @param cert the certificate diff --git a/test/extensions/transport_sockets/tls/utility_test.cc b/test/extensions/transport_sockets/tls/utility_test.cc index 22aef56c88ecf..b01d4a6e1d0fd 100644 --- a/test/extensions/transport_sockets/tls/utility_test.cc +++ b/test/extensions/transport_sockets/tls/utility_test.cc @@ -29,12 +29,30 @@ TEST(UtilityTest, TestDnsNameMatching) { EXPECT_TRUE(Utility::dnsNameMatch("lyft.com", "lyft.com")); EXPECT_TRUE(Utility::dnsNameMatch("a.lyft.com", "*.lyft.com")); EXPECT_TRUE(Utility::dnsNameMatch("a.LYFT.com", "*.lyft.COM")); + EXPECT_TRUE(Utility::dnsNameMatch("lyft.com", "*yft.com")); + EXPECT_TRUE(Utility::dnsNameMatch("LYFT.com", "*yft.com")); + EXPECT_TRUE(Utility::dnsNameMatch("lyft.com", "*lyft.com")); + EXPECT_TRUE(Utility::dnsNameMatch("lyft.com", "lyf*.com")); + EXPECT_TRUE(Utility::dnsNameMatch("lyft.com", "lyft*.com")); + EXPECT_TRUE(Utility::dnsNameMatch("lyft.com", "l*ft.com")); + EXPECT_TRUE(Utility::dnsNameMatch("t.lyft.com", "t*.lyft.com")); + EXPECT_TRUE(Utility::dnsNameMatch("test.lyft.com", "t*.lyft.com")); + EXPECT_TRUE(Utility::dnsNameMatch("l-lots-of-stuff-ft.com", "l*ft.com")); + EXPECT_FALSE(Utility::dnsNameMatch("t.lyft.com", "t*t.lyft.com")); + EXPECT_FALSE(Utility::dnsNameMatch("lyft.com", "l*ft.co")); + EXPECT_FALSE(Utility::dnsNameMatch("lyft.com", "ly?t.com")); + EXPECT_FALSE(Utility::dnsNameMatch("lyft.com", "lf*t.com")); + EXPECT_FALSE(Utility::dnsNameMatch(".lyft.com", "*lyft.com")); + EXPECT_FALSE(Utility::dnsNameMatch("lyft.com", "**lyft.com")); + EXPECT_FALSE(Utility::dnsNameMatch("lyft.com", "lyft**.com")); + EXPECT_FALSE(Utility::dnsNameMatch("lyft.com", "ly**ft.com")); + EXPECT_FALSE(Utility::dnsNameMatch("lyft.com", "lyft.c*m")); + EXPECT_FALSE(Utility::dnsNameMatch("lyft.com", "*yft.c*m")); + EXPECT_FALSE(Utility::dnsNameMatch("test.lyft.com.extra", "*.lyft.com")); EXPECT_FALSE(Utility::dnsNameMatch("a.b.lyft.com", "*.lyft.com")); EXPECT_FALSE(Utility::dnsNameMatch("foo.test.com", "*.lyft.com")); EXPECT_FALSE(Utility::dnsNameMatch("lyft.com", "*.lyft.com")); EXPECT_FALSE(Utility::dnsNameMatch("alyft.com", "*.lyft.com")); - EXPECT_FALSE(Utility::dnsNameMatch("alyft.com", "*lyft.com")); - EXPECT_FALSE(Utility::dnsNameMatch("lyft.com", "*lyft.com")); EXPECT_FALSE(Utility::dnsNameMatch("", "*lyft.com")); EXPECT_FALSE(Utility::dnsNameMatch("lyft.com", "")); }