Skip to content

Commit b5dc350

Browse files
committed
DNS host / identity normalization should be performed only once per public API call
1 parent 2f57d44 commit b5dc350

File tree

4 files changed

+133
-119
lines changed

4 files changed

+133
-119
lines changed

httpclient5/src/main/java/org/apache/hc/client5/http/psl/PublicSuffixMatcher.java

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,17 @@ public String getDomainRoot(final String domain, final DomainType expectedType)
138138
if (domain.startsWith(".")) {
139139
return null;
140140
}
141-
final DomainRootInfo match = resolveDomainRoot(domain, expectedType);
142-
return match != null ? match.root : null;
141+
String normalized = DnsUtils.normalize(domain);
142+
final boolean punyCoded = normalized.contains("xn-");
143+
if (punyCoded) {
144+
normalized = IDN.toUnicode(normalized);
145+
}
146+
final DomainRootInfo match = resolveDomainRoot(normalized, expectedType);
147+
String domainRoot = match != null ? match.root : null;
148+
if (domainRoot != null && punyCoded) {
149+
domainRoot = IDN.toASCII(domainRoot);
150+
}
151+
return domainRoot;
143152
}
144153

145154
static final class DomainRootInfo {
@@ -156,11 +165,11 @@ static final class DomainRootInfo {
156165
}
157166

158167
DomainRootInfo resolveDomainRoot(final String domain, final DomainType expectedType) {
159-
String segment = DnsUtils.normalize(domain);
168+
String segment = domain;
160169
String result = null;
161170
while (segment != null) {
162171
// An exception rule takes priority over any other matching rule.
163-
final String key = IDN.toUnicode(segment);
172+
final String key = segment;
164173
final DomainType exceptionRule = findEntry(exceptions, key);
165174
if (match(exceptionRule, expectedType)) {
166175
return new DomainRootInfo(segment, key, exceptionRule);
@@ -174,7 +183,7 @@ DomainRootInfo resolveDomainRoot(final String domain, final DomainType expectedT
174183
final String nextSegment = nextdot != -1 ? segment.substring(nextdot + 1) : null;
175184

176185
// look for wildcard entries
177-
final String wildcardKey = (nextSegment == null) ? "*" : "*." + IDN.toUnicode(nextSegment);
186+
final String wildcardKey = (nextSegment == null) ? "*" : "*." + nextSegment;
178187
final DomainType wildcardDomainRule = findEntry(rules, wildcardKey);
179188
if (match(wildcardDomainRule, expectedType)) {
180189
return new DomainRootInfo(result, wildcardKey, wildcardDomainRule);
@@ -239,19 +248,11 @@ public boolean verify(final String domain) {
239248
if (domain == null) {
240249
return false;
241250
}
242-
return verifyStrict(domain.startsWith(".") ? domain.substring(1) : domain);
251+
return verifyInternal(domain.startsWith(".") ? domain.substring(1) : domain);
243252
}
244253

245-
/**
246-
* Verifies if the given domain does not represent a public domain root and is
247-
* allowed to set cookies, have an identity represented by a certificate, etc.
248-
*/
249254
@Internal
250-
public boolean verifyStrict(final String domain) {
251-
Args.notNull(domain, "Domain");
252-
if (domain.startsWith(".")) {
253-
return false;
254-
}
255+
public boolean verifyInternal(final String domain) {
255256
final DomainRootInfo domainRootInfo = resolveDomainRoot(domain, null);
256257
if (domainRootInfo == null) {
257258
return false;

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

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

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

30-
import java.net.IDN;
3130
import java.net.InetAddress;
3231
import java.net.UnknownHostException;
3332
import java.security.cert.Certificate;
@@ -159,11 +158,11 @@ static void matchIPv6Address(final String host, final List<SubjectName> subjectA
159158

160159
static void matchDNSName(final String host, final List<SubjectName> subjectAlts,
161160
final PublicSuffixMatcher publicSuffixMatcher) throws SSLPeerUnverifiedException {
162-
final String normalizedHost = DnsUtils.normalize(host);
161+
final String normalizedHost = DnsUtils.normalizeUnicode(host);
163162
for (final SubjectName subjectAlt : subjectAlts) {
164163
if (subjectAlt.getType() == SubjectName.DNS) {
165-
final String normalizedSubjectAlt = DnsUtils.normalize(subjectAlt.getValue());
166-
if (matchIdentityStrict(normalizedHost, normalizedSubjectAlt, publicSuffixMatcher)) {
164+
final String normalizedSubjectAlt = DnsUtils.normalizeUnicode(subjectAlt.getValue());
165+
if (matchIdentity(normalizedHost, normalizedSubjectAlt, publicSuffixMatcher, true)) {
167166
return;
168167
}
169168
}
@@ -180,9 +179,9 @@ static void matchCN(final String host, final X509Certificate cert,
180179
throw new SSLPeerUnverifiedException("Certificate subject for <" + host + "> doesn't contain " +
181180
"a common name and does not have alternative names");
182181
}
183-
final String normalizedHost = DnsUtils.normalize(host);
184-
final String normalizedCn = DnsUtils.normalize(cn);
185-
if (!matchIdentityStrict(normalizedHost, normalizedCn, publicSuffixMatcher)) {
182+
final String normalizedHost = DnsUtils.normalizeUnicode(host);
183+
final String normalizedCn = DnsUtils.normalizeUnicode(cn);
184+
if (!matchIdentity(normalizedHost, normalizedCn, publicSuffixMatcher, true)) {
186185
throw new SSLPeerUnverifiedException("Certificate for <" + host + "> doesn't match " +
187186
"common name of the certificate subject: " + cn);
188187
}
@@ -224,21 +223,11 @@ static boolean matchDomainRoot(final String host, final String domainRoot) {
224223
return false;
225224
}
226225

227-
private static boolean matchIdentity(final String host, final String identity,
226+
static boolean matchIdentity(final String host, final String identity,
228227
final PublicSuffixMatcher publicSuffixMatcher,
229228
final boolean strict) {
230-
231-
final String normalizedIdentity;
232-
try {
233-
// Convert only the identity to its Unicode form
234-
normalizedIdentity = IDN.toUnicode(identity);
235-
} catch (final IllegalArgumentException e) {
236-
return false;
237-
}
238-
239-
// Public suffix check on the Unicode identity
240229
if (publicSuffixMatcher != null && host.contains(".")) {
241-
if (!publicSuffixMatcher.verifyStrict(normalizedIdentity)) {
230+
if (!publicSuffixMatcher.verifyInternal(identity)) {
242231
if (LOG.isDebugEnabled()) {
243232
LOG.debug("Public Suffix List verification failed for identity '{}'", identity);
244233
}
@@ -251,10 +240,10 @@ private static boolean matchIdentity(final String host, final String identity,
251240
// character * which is considered to match any single domain name
252241
// component or component fragment..."
253242
// Based on this statement presuming only singular wildcard is legal
254-
final int asteriskIdx = normalizedIdentity.indexOf('*');
243+
final int asteriskIdx = identity.indexOf('*');
255244
if (asteriskIdx != -1) {
256-
final String prefix = normalizedIdentity.substring(0, asteriskIdx);
257-
final String suffix = normalizedIdentity.substring(asteriskIdx + 1);
245+
final String prefix = identity.substring(0, asteriskIdx);
246+
final String suffix = identity.substring(asteriskIdx + 1);
258247

259248
if (!prefix.isEmpty() && !host.startsWith(prefix)) {
260249
return false;
@@ -274,25 +263,7 @@ private static boolean matchIdentity(final String host, final String identity,
274263
}
275264

276265
// Direct Unicode comparison
277-
return host.equalsIgnoreCase(normalizedIdentity);
278-
}
279-
280-
static boolean matchIdentity(final String host, final String identity,
281-
final PublicSuffixMatcher publicSuffixMatcher) {
282-
return matchIdentity(host, identity, publicSuffixMatcher, false);
283-
}
284-
285-
static boolean matchIdentity(final String host, final String identity) {
286-
return matchIdentity(host, identity, null, false);
287-
}
288-
289-
static boolean matchIdentityStrict(final String host, final String identity,
290-
final PublicSuffixMatcher publicSuffixMatcher) {
291-
return matchIdentity(host, identity, publicSuffixMatcher, true);
292-
}
293-
294-
static boolean matchIdentityStrict(final String host, final String identity) {
295-
return matchIdentity(host, identity, null, true);
266+
return host.equalsIgnoreCase(identity);
296267
}
297268

298269
static String extractCN(final String subjectPrincipal) throws SSLException {

httpclient5/src/main/java/org/apache/hc/client5/http/utils/DnsUtils.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@
2727

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

30+
import java.net.IDN;
31+
32+
import org.apache.hc.core5.annotation.Internal;
33+
3034
/**
3135
* A collection of utilities relating to Domain Name System.
3236
*
@@ -72,4 +76,21 @@ public static String normalize(final String s) {
7276
return s;
7377
}
7478

79+
/**
80+
* Decodes to Unicode and normalizes the given DNS name.
81+
*/
82+
@Internal
83+
public static String normalizeUnicode(final String s) {
84+
if (s == null) {
85+
return null;
86+
}
87+
String decoded;
88+
try {
89+
decoded = IDN.toUnicode(s);
90+
} catch (final IllegalArgumentException ignore) {
91+
decoded = s;
92+
}
93+
return normalize(decoded);
94+
}
95+
7596
}

0 commit comments

Comments
 (0)