Skip to content

Commit 3fa07f1

Browse files
committed
HTTPCLIENT-2353: Fix IDN hostname mismatch by normalizing identity with IDN.toUnicode before comparison so that Unicode and punycode forms match correctly. (#607)
(cherry picked from commit 9e3559e)
1 parent f17a948 commit 3fa07f1

File tree

2 files changed

+98
-6
lines changed

2 files changed

+98
-6
lines changed

httpclient5/src/main/java/org/apache/hc/client5/http/ssl/DefaultHostnameVerifier.java

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
package org.apache.hc.client5.http.ssl;
2929

30+
import java.net.IDN;
3031
import java.net.InetAddress;
3132
import java.net.UnknownHostException;
3233
import java.security.cert.Certificate;
@@ -228,8 +229,18 @@ private static boolean matchIdentity(final String host, final String identity,
228229
final PublicSuffixMatcher publicSuffixMatcher,
229230
final DomainType domainType,
230231
final boolean strict) {
232+
233+
final String normalizedIdentity;
234+
try {
235+
// Convert only the identity to its Unicode form
236+
normalizedIdentity = IDN.toUnicode(identity);
237+
} catch (final IllegalArgumentException e) {
238+
return false;
239+
}
240+
241+
// Public suffix check on the Unicode identity
231242
if (publicSuffixMatcher != null && host.contains(".")) {
232-
if (publicSuffixMatcher.getDomainRoot(identity, domainType) == null) {
243+
if (publicSuffixMatcher.getDomainRoot(normalizedIdentity, domainType) == null) {
233244
return false;
234245
}
235246
}
@@ -239,10 +250,11 @@ private static boolean matchIdentity(final String host, final String identity,
239250
// character * which is considered to match any single domain name
240251
// component or component fragment..."
241252
// Based on this statement presuming only singular wildcard is legal
242-
final int asteriskIdx = identity.indexOf('*');
253+
final int asteriskIdx = normalizedIdentity.indexOf('*');
243254
if (asteriskIdx != -1) {
244-
final String prefix = identity.substring(0, asteriskIdx);
245-
final String suffix = identity.substring(asteriskIdx + 1);
255+
final String prefix = normalizedIdentity.substring(0, asteriskIdx);
256+
final String suffix = normalizedIdentity.substring(asteriskIdx + 1);
257+
246258
if (!prefix.isEmpty() && !host.startsWith(prefix)) {
247259
return false;
248260
}
@@ -252,12 +264,16 @@ private static boolean matchIdentity(final String host, final String identity,
252264
// Additional sanity checks on content selected by wildcard can be done here
253265
if (strict) {
254266
final String remainder = host.substring(
255-
prefix.length(), host.length() - suffix.length());
267+
prefix.length(),
268+
host.length() - suffix.length()
269+
);
256270
return !remainder.contains(".");
257271
}
258272
return true;
259273
}
260-
return host.equalsIgnoreCase(identity);
274+
275+
// Direct Unicode comparison
276+
return host.equalsIgnoreCase(normalizedIdentity);
261277
}
262278

263279
static boolean matchIdentity(final String host, final String identity,

httpclient5/src/test/java/org/apache/hc/client5/http/ssl/TestDefaultHostnameVerifier.java

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,4 +471,80 @@ void testMatchDNSName() throws Exception {
471471
publicSuffixMatcher));
472472
}
473473

474+
@Test
475+
void testMatchIdentity() {
476+
// Test 1: IDN matching punycode
477+
final String unicodeHost1 = "поиск-слов.рф";
478+
final String punycodeHost1 = "xn----dtbqigoecuc.xn--p1ai";
479+
480+
// These should now match, thanks to IDN.toASCII():
481+
Assertions.assertTrue(
482+
DefaultHostnameVerifier.matchIdentity(unicodeHost1, punycodeHost1),
483+
"Expected the Unicode host and its punycode to match"
484+
);
485+
486+
// ‘example.com’ vs. an unrelated punycode domain should fail:
487+
Assertions.assertFalse(
488+
DefaultHostnameVerifier.matchIdentity("example.com", punycodeHost1),
489+
"Expected mismatch between example.com and xn----dtbqigoecuc.xn--p1ai"
490+
);
491+
492+
// Test 2: Unicode host and Unicode identity
493+
final String unicodeHost2 = "пример.рф";
494+
final String unicodeIdentity2 = "пример.рф";
495+
Assertions.assertTrue(
496+
DefaultHostnameVerifier.matchIdentity(unicodeHost2, unicodeIdentity2),
497+
"Expected Unicode host and Unicode identity to match"
498+
);
499+
500+
// Test 3: Punycode host and Unicode identity
501+
final String unicodeHost3 = "пример.рф";
502+
final String punycodeIdentity3 = "xn--e1afmkfd.xn--p1ai";
503+
Assertions.assertTrue(
504+
DefaultHostnameVerifier.matchIdentity(unicodeHost3, punycodeIdentity3),
505+
"Expected Unicode host and punycode identity to match"
506+
);
507+
508+
// Test 4: Wildcard matching in the left-most label
509+
final String unicodeHost4 = "sub.пример.рф";
510+
final String unicodeIdentity4 = "*.пример.рф";
511+
Assertions.assertTrue(
512+
DefaultHostnameVerifier.matchIdentity(unicodeHost4, unicodeIdentity4),
513+
"Expected wildcard to match subdomain"
514+
);
515+
516+
// Test 5: Invalid host
517+
final String invalidHost = "invalid_host";
518+
final String unicodeIdentity5 = "пример.рф";
519+
Assertions.assertFalse(
520+
DefaultHostnameVerifier.matchIdentity(invalidHost, unicodeIdentity5),
521+
"Expected invalid host to not match"
522+
);
523+
524+
// Test 6: Invalid identity
525+
final String unicodeHost4b = "пример.рф";
526+
final String invalidIdentity = "xn--invalid-punycode";
527+
Assertions.assertFalse(
528+
DefaultHostnameVerifier.matchIdentity(unicodeHost4b, invalidIdentity),
529+
"Expected invalid identity to not match"
530+
);
531+
532+
// Test 7: Mixed case comparison
533+
final String unicodeHost5 = "ПрИмеР.рф";
534+
final String unicodeIdentity6 = "пример.рф";
535+
Assertions.assertTrue(
536+
DefaultHostnameVerifier.matchIdentity(unicodeHost5, unicodeIdentity6),
537+
"Expected case-insensitive Unicode comparison to match"
538+
);
539+
540+
541+
// Test 8: Wildcard in the middle label (per RFC 2818, should match)
542+
final String unicodeHost6 = "sub.пример.рф";
543+
final String unicodeIdentity8 = "sub.*.рф";
544+
Assertions.assertTrue(
545+
DefaultHostnameVerifier.matchIdentity(unicodeHost6, unicodeIdentity8),
546+
"Expected wildcard in the middle label to match"
547+
);
548+
}
549+
474550
}

0 commit comments

Comments
 (0)