HTTPCLIENT-2353: Fix IDN hostname mismatch by normalizing identity with IDN.toUnicode before comparison so that Unicode and punycode forms match correctly. (#607)
This commit is contained in:
parent
55cdd9e94f
commit
9e3559ef93
|
@ -27,6 +27,7 @@
|
|||
|
||||
package org.apache.hc.client5.http.ssl;
|
||||
|
||||
import java.net.IDN;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.cert.Certificate;
|
||||
|
@ -228,8 +229,18 @@ public final class DefaultHostnameVerifier implements HttpClientHostnameVerifier
|
|||
final PublicSuffixMatcher publicSuffixMatcher,
|
||||
final DomainType domainType,
|
||||
final boolean strict) {
|
||||
|
||||
final String normalizedIdentity;
|
||||
try {
|
||||
// Convert only the identity to its Unicode form
|
||||
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(identity, domainType) == null) {
|
||||
if (publicSuffixMatcher.getDomainRoot(normalizedIdentity, domainType) == null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -239,10 +250,11 @@ public final class DefaultHostnameVerifier implements HttpClientHostnameVerifier
|
|||
// 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 = identity.indexOf('*');
|
||||
final int asteriskIdx = normalizedIdentity.indexOf('*');
|
||||
if (asteriskIdx != -1) {
|
||||
final String prefix = identity.substring(0, asteriskIdx);
|
||||
final String suffix = identity.substring(asteriskIdx + 1);
|
||||
final String prefix = normalizedIdentity.substring(0, asteriskIdx);
|
||||
final String suffix = normalizedIdentity.substring(asteriskIdx + 1);
|
||||
|
||||
if (!prefix.isEmpty() && !host.startsWith(prefix)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -252,12 +264,16 @@ public final class DefaultHostnameVerifier implements HttpClientHostnameVerifier
|
|||
// 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());
|
||||
prefix.length(),
|
||||
host.length() - suffix.length()
|
||||
);
|
||||
return !remainder.contains(".");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return host.equalsIgnoreCase(identity);
|
||||
|
||||
// Direct Unicode comparison
|
||||
return host.equalsIgnoreCase(normalizedIdentity);
|
||||
}
|
||||
|
||||
static boolean matchIdentity(final String host, final String identity,
|
||||
|
|
|
@ -472,4 +472,80 @@ class TestDefaultHostnameVerifier {
|
|||
publicSuffixMatcher));
|
||||
}
|
||||
|
||||
@Test
|
||||
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(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", 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 unicodeHost3 = "пример.рф";
|
||||
final String punycodeIdentity3 = "xn--e1afmkfd.xn--p1ai";
|
||||
Assertions.assertTrue(
|
||||
DefaultHostnameVerifier.matchIdentity(unicodeHost3, punycodeIdentity3),
|
||||
"Expected Unicode host and punycode identity to match"
|
||||
);
|
||||
|
||||
// Test 4: 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 5: Invalid host
|
||||
final String invalidHost = "invalid_host";
|
||||
final String unicodeIdentity5 = "пример.рф";
|
||||
Assertions.assertFalse(
|
||||
DefaultHostnameVerifier.matchIdentity(invalidHost, unicodeIdentity5),
|
||||
"Expected invalid host to not match"
|
||||
);
|
||||
|
||||
// Test 6: Invalid identity
|
||||
final String unicodeHost4b = "пример.рф";
|
||||
final String invalidIdentity = "xn--invalid-punycode";
|
||||
Assertions.assertFalse(
|
||||
DefaultHostnameVerifier.matchIdentity(unicodeHost4b, invalidIdentity),
|
||||
"Expected invalid identity to not match"
|
||||
);
|
||||
|
||||
// Test 7: Mixed case comparison
|
||||
final String unicodeHost5 = "ПрИмеР.рф";
|
||||
final String unicodeIdentity6 = "пример.рф";
|
||||
Assertions.assertTrue(
|
||||
DefaultHostnameVerifier.matchIdentity(unicodeHost5, unicodeIdentity6),
|
||||
"Expected case-insensitive Unicode comparison to match"
|
||||
);
|
||||
|
||||
|
||||
// Test 8: 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"
|
||||
);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue