diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/DefaultHostnameVerifier.java b/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/DefaultHostnameVerifier.java index 5b022c45b..b6eb74eba 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/DefaultHostnameVerifier.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/DefaultHostnameVerifier.java @@ -230,43 +230,51 @@ private static boolean matchIdentity(final String host, final String identity, final DomainType domainType, final boolean strict) { - final String unicodeIdentity; + final String normalizedHost; + final String normalizedIdentity; try { - unicodeIdentity = IDN.toUnicode(identity); + normalizedHost = IDN.toUnicode(host); + normalizedIdentity = IDN.toUnicode(identity); } catch (final IllegalArgumentException e) { return false; } // Public suffix check on the Unicode identity - if (publicSuffixMatcher != null && host.contains(".")) { - if (publicSuffixMatcher.getDomainRoot(unicodeIdentity, domainType) == null) { + if (publicSuffixMatcher != null && normalizedHost.contains(".")) { + if (publicSuffixMatcher.getDomainRoot(normalizedIdentity, domainType) == null) { return false; } } - // Handle wildcard in the Unicode identity - final int asteriskIdx = unicodeIdentity.indexOf('*'); + // RFC 2818, 3.1. Server Identity + // "...Names may contain the wildcard + // character * which is considered to match any single domain name + // component or component fragment..." + // Based on this statement presuming only singular wildcard is legal + final int asteriskIdx = normalizedIdentity.indexOf('*'); if (asteriskIdx != -1) { - final String prefix = unicodeIdentity.substring(0, asteriskIdx); - final String suffix = unicodeIdentity.substring(asteriskIdx + 1); + final String prefix = normalizedIdentity.substring(0, asteriskIdx); + final String suffix = normalizedIdentity.substring(asteriskIdx + 1); - if (!prefix.isEmpty() && !host.startsWith(prefix)) { + if (!prefix.isEmpty() && !normalizedHost.startsWith(prefix)) { return false; } - if (!suffix.isEmpty() && !host.endsWith(suffix)) { + if (!suffix.isEmpty() && !normalizedHost.endsWith(suffix)) { return false; } - - // Additional sanity checks on the wildcard portion + // Additional sanity checks on content selected by wildcard can be done here if (strict) { - final String remainder = host.substring(prefix.length(), host.length() - suffix.length()); + final String remainder = normalizedHost.substring( + prefix.length(), + normalizedHost.length() - suffix.length() + ); return !remainder.contains("."); } return true; } // Direct Unicode comparison - return host.equalsIgnoreCase(unicodeIdentity); + return normalizedHost.equalsIgnoreCase(normalizedIdentity); } static boolean matchIdentity(final String host, final String identity, diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/ssl/TestDefaultHostnameVerifier.java b/httpclient5/src/test/java/org/apache/hc/client5/http/ssl/TestDefaultHostnameVerifier.java index 6bee8d2c5..f806f0f71 100644 --- a/httpclient5/src/test/java/org/apache/hc/client5/http/ssl/TestDefaultHostnameVerifier.java +++ b/httpclient5/src/test/java/org/apache/hc/client5/http/ssl/TestDefaultHostnameVerifier.java @@ -473,21 +473,94 @@ void testMatchDNSName() throws Exception { } @Test - void testMatchIdentityWithIDN() { - final String unicodeHost = "поиск-слов.рф"; - final String punycodeHost = "xn----dtbqigoecuc.xn--p1ai"; + void testMatchIdentity() { + // Test 1: IDN matching punycode + final String unicodeHost1 = "поиск-слов.рф"; + final String punycodeHost1 = "xn----dtbqigoecuc.xn--p1ai"; // These should now match, thanks to IDN.toASCII(): Assertions.assertTrue( - DefaultHostnameVerifier.matchIdentity(unicodeHost, punycodeHost), + DefaultHostnameVerifier.matchIdentity(unicodeHost1, punycodeHost1), "Expected the Unicode host and its punycode to match" ); // ‘example.com’ vs. an unrelated punycode domain should fail: Assertions.assertFalse( - DefaultHostnameVerifier.matchIdentity("example.com", punycodeHost), + DefaultHostnameVerifier.matchIdentity("example.com", punycodeHost1), "Expected mismatch between example.com and xn----dtbqigoecuc.xn--p1ai" ); + + // Test 2: Unicode host and Unicode identity + final String unicodeHost2 = "пример.рф"; + final String unicodeIdentity2 = "пример.рф"; + Assertions.assertTrue( + DefaultHostnameVerifier.matchIdentity(unicodeHost2, unicodeIdentity2), + "Expected Unicode host and Unicode identity to match" + ); + + // Test 3: Punycode host and Unicode identity + final String punycodeHost2 = "xn--e1afmkfd.xn--p1ai"; + final String unicodeIdentity3 = "пример.рф"; + Assertions.assertTrue( + DefaultHostnameVerifier.matchIdentity(punycodeHost2, unicodeIdentity3), + "Expected punycode host and Unicode identity to match" + ); + + // Test 4: Unicode host and Punycode identity + final String unicodeHost3 = "пример.рф"; + final String punycodeIdentity3 = "xn--e1afmkfd.xn--p1ai"; + Assertions.assertTrue( + DefaultHostnameVerifier.matchIdentity(unicodeHost3, punycodeIdentity3), + "Expected Unicode host and punycode identity to match" + ); + + // Test 5: Wildcard matching in the left-most label + final String unicodeHost4 = "sub.пример.рф"; + final String unicodeIdentity4 = "*.пример.рф"; + Assertions.assertTrue( + DefaultHostnameVerifier.matchIdentity(unicodeHost4, unicodeIdentity4), + "Expected wildcard to match subdomain" + ); + + // Test 6: Invalid host + final String invalidHost = "invalid_host"; + final String unicodeIdentity5 = "пример.рф"; + Assertions.assertFalse( + DefaultHostnameVerifier.matchIdentity(invalidHost, unicodeIdentity5), + "Expected invalid host to not match" + ); + + // Test 7: Invalid identity + final String unicodeHost4b = "пример.рф"; + final String invalidIdentity = "xn--invalid-punycode"; + Assertions.assertFalse( + DefaultHostnameVerifier.matchIdentity(unicodeHost4b, invalidIdentity), + "Expected invalid identity to not match" + ); + + // Test 8: Mixed case comparison + final String unicodeHost5 = "ПрИмеР.рф"; + final String unicodeIdentity6 = "пример.рф"; + Assertions.assertTrue( + DefaultHostnameVerifier.matchIdentity(unicodeHost5, unicodeIdentity6), + "Expected case-insensitive Unicode comparison to match" + ); + + // Test 9: Wildcard with punycode host + final String punycodeHost3 = "xn--a-7h.xn--e1afmkfd.xn--p1ai"; + final String unicodeIdentity7 = "*.пример.рф"; + Assertions.assertTrue( + DefaultHostnameVerifier.matchIdentity(punycodeHost3, unicodeIdentity7), + "Expected wildcard to match punycode-encoded host" + ); + + // Test 10: Wildcard in the middle label (per RFC 2818, should match) + final String unicodeHost6 = "sub.пример.рф"; + final String unicodeIdentity8 = "sub.*.рф"; + Assertions.assertTrue( + DefaultHostnameVerifier.matchIdentity(unicodeHost6, unicodeIdentity8), + "Expected wildcard in the middle label to match" + ); } } \ No newline at end of file