Skip to content

Commit

Permalink
HTTPCLIENT-2353: Fix IDN hostname mismatch by normalizing identity wi…
Browse files Browse the repository at this point in the history
…th IDN.toUnicode before comparison so that Unicode and punycode forms match correctly.
  • Loading branch information
arturobernalg committed Jan 5, 2025
1 parent 4b2a365 commit accc382
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -228,36 +229,44 @@ private static boolean matchIdentity(final String host, final String identity,
final PublicSuffixMatcher publicSuffixMatcher,
final DomainType domainType,
final boolean strict) {

final String unicodeIdentity;
try {
unicodeIdentity = 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(unicodeIdentity, domainType) == null) {
return false;
}
}

// 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 = identity.indexOf('*');
// Handle wildcard in the Unicode identity
final int asteriskIdx = unicodeIdentity.indexOf('*');
if (asteriskIdx != -1) {
final String prefix = identity.substring(0, asteriskIdx);
final String suffix = identity.substring(asteriskIdx + 1);
final String prefix = unicodeIdentity.substring(0, asteriskIdx);
final String suffix = unicodeIdentity.substring(asteriskIdx + 1);

if (!prefix.isEmpty() && !host.startsWith(prefix)) {
return false;
}
if (!suffix.isEmpty() && !host.endsWith(suffix)) {
return false;
}
// Additional sanity checks on content selected by wildcard can be done here

// Additional sanity checks on the wildcard portion
if (strict) {
final String remainder = host.substring(
prefix.length(), host.length() - suffix.length());
final String remainder = host.substring(prefix.length(), host.length() - suffix.length());
return !remainder.contains(".");
}
return true;
}
return host.equalsIgnoreCase(identity);

// Direct Unicode comparison
return host.equalsIgnoreCase(unicodeIdentity);
}

static boolean matchIdentity(final String host, final String identity,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -472,4 +472,22 @@ void testMatchDNSName() throws Exception {
publicSuffixMatcher));
}

@Test
void testMatchIdentityWithIDN() {
final String unicodeHost = "поиск-слов.рф";
final String punycodeHost = "xn----dtbqigoecuc.xn--p1ai";

// These should now match, thanks to IDN.toASCII():
Assertions.assertTrue(
DefaultHostnameVerifier.matchIdentity(unicodeHost, punycodeHost),
"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),
"Expected mismatch between example.com and xn----dtbqigoecuc.xn--p1ai"
);
}

}

0 comments on commit accc382

Please sign in to comment.