--- a/jdk/src/java.base/share/classes/sun/security/util/HostnameChecker.java Thu Feb 23 12:08:44 2017 +0000
+++ b/jdk/src/java.base/share/classes/sun/security/util/HostnameChecker.java Mon Mar 20 07:38:52 2017 -0400
@@ -28,18 +28,16 @@
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
-import java.util.*;
-
import java.security.Principal;
import java.security.cert.*;
-
+import java.util.*;
import javax.security.auth.x500.X500Principal;
+import sun.net.util.IPAddressUtil;
import sun.security.ssl.ClientKeyExchangeService;
+import sun.security.ssl.Debug;
import sun.security.x509.X500Name;
-import sun.net.util.IPAddressUtil;
-
/**
* Class to check hostnames against the names specified in a certificate as
* required for TLS and LDAP.
@@ -61,6 +59,8 @@
private static final int ALTNAME_DNS = 2;
private static final int ALTNAME_IP = 7;
+ private static final Debug debug = Debug.getInstance("ssl");
+
// the algorithm to follow to perform the check. Currently unused.
private final byte checkType;
@@ -84,18 +84,27 @@
/**
* Perform the check.
*
- * @exception CertificateException if the name does not match any of
- * the names specified in the certificate
+ * @param expectedName the expected host name or ip address
+ * @param cert the certificate to check against
+ * @param chainsToPublicCA true if the certificate chains to a public
+ * root CA (as pre-installed in the cacerts file)
+ * @throws CertificateException if the name does not match any of
+ * the names specified in the certificate
*/
- public void match(String expectedName, X509Certificate cert)
- throws CertificateException {
+ public void match(String expectedName, X509Certificate cert,
+ boolean chainsToPublicCA) throws CertificateException {
if (isIpAddress(expectedName)) {
matchIP(expectedName, cert);
} else {
- matchDNS(expectedName, cert);
+ matchDNS(expectedName, cert, chainsToPublicCA);
}
}
+ public void match(String expectedName, X509Certificate cert)
+ throws CertificateException {
+ match(expectedName, cert, false);
+ }
+
/**
* Perform the check for Kerberos.
*/
@@ -185,20 +194,21 @@
* Certification Authorities are encouraged to use the dNSName instead.
*
* Matching is performed using the matching rules specified by
- * [RFC2459]. If more than one identity of a given type is present in
+ * [RFC5280]. If more than one identity of a given type is present in
* the certificate (e.g., more than one dNSName name, a match in any one
* of the set is considered acceptable.)
*/
- private void matchDNS(String expectedName, X509Certificate cert)
+ private void matchDNS(String expectedName, X509Certificate cert,
+ boolean chainsToPublicCA)
throws CertificateException {
Collection<List<?>> subjAltNames = cert.getSubjectAlternativeNames();
if (subjAltNames != null) {
boolean foundDNS = false;
- for ( List<?> next : subjAltNames) {
+ for (List<?> next : subjAltNames) {
if (((Integer)next.get(0)).intValue() == ALTNAME_DNS) {
foundDNS = true;
String dnsName = (String)next.get(1);
- if (isMatched(expectedName, dnsName)) {
+ if (isMatched(expectedName, dnsName, chainsToPublicCA)) {
return;
}
}
@@ -215,7 +225,8 @@
(X500Name.commonName_oid);
if (derValue != null) {
try {
- if (isMatched(expectedName, derValue.getAsString())) {
+ if (isMatched(expectedName, derValue.getAsString(),
+ chainsToPublicCA)) {
return;
}
} catch (IOException e) {
@@ -261,7 +272,11 @@
* The <code>template</code> parameter
* may contain the wildcard character *
*/
- private boolean isMatched(String name, String template) {
+ private boolean isMatched(String name, String template,
+ boolean chainsToPublicCA) {
+ if (hasIllegalWildcard(name, template, chainsToPublicCA)) {
+ return false;
+ }
if (checkType == TYPE_TLS) {
return matchAllWildcards(name, template);
} else if (checkType == TYPE_LDAP) {
@@ -271,6 +286,61 @@
}
}
+ /**
+ * Returns true if the template contains an illegal wildcard character.
+ */
+ private static boolean hasIllegalWildcard(String domain, String template,
+ boolean chainsToPublicCA) {
+ // not ok if it is a single wildcard character or "*."
+ if (template.equals("*") || template.equals("*.")) {
+ if (debug != null) {
+ debug.println("Certificate domain name has illegal single " +
+ "wildcard character: " + template);
+ }
+ return true;
+ }
+
+ int lastWildcardIndex = template.lastIndexOf("*");
+
+ // ok if it has no wildcard character
+ if (lastWildcardIndex == -1) {
+ return false;
+ }
+
+ String afterWildcard = template.substring(lastWildcardIndex);
+ int firstDotIndex = afterWildcard.indexOf(".");
+
+ // not ok if there is no dot after wildcard (ex: "*com")
+ if (firstDotIndex == -1) {
+ if (debug != null) {
+ debug.println("Certificate domain name has illegal wildcard, " +
+ "no dot after wildcard character: " + template);
+ }
+ return true;
+ }
+
+ // If the wildcarded domain is a top-level domain under which names
+ // can be registered, then a wildcard is not allowed.
+
+ if (!chainsToPublicCA) {
+ return false; // skip check for non-public certificates
+ }
+ Optional<RegisteredDomain> rd = RegisteredDomain.from(domain)
+ .filter(d -> d.type() == RegisteredDomain.Type.ICANN);
+
+ if (rd.isPresent()) {
+ String wDomain = afterWildcard.substring(firstDotIndex + 1);
+ if (rd.get().publicSuffix().equalsIgnoreCase(wDomain)) {
+ if (debug != null) {
+ debug.println("Certificate domain name has illegal " +
+ "wildcard for public suffix: " + template);
+ }
+ return true;
+ }
+ }
+
+ return false;
+ }
/**
* Returns true if name matches against template.<p>
@@ -317,9 +387,9 @@
name = name.toLowerCase(Locale.ENGLISH);
template = template.toLowerCase(Locale.ENGLISH);
- // Retreive leftmost component
- int templateIdx = template.indexOf('.');
- int nameIdx = name.indexOf('.');
+ // Retrieve leftmost component
+ int templateIdx = template.indexOf(".");
+ int nameIdx = name.indexOf(".");
if (templateIdx == -1)
templateIdx = template.length();
@@ -344,7 +414,7 @@
*/
private static boolean matchWildCards(String name, String template) {
- int wildcardIdx = template.indexOf('*');
+ int wildcardIdx = template.indexOf("*");
if (wildcardIdx == -1)
return name.equals(template);
@@ -367,7 +437,7 @@
// update the match scope
name = name.substring(beforeStartIdx + beforeWildcard.length());
- wildcardIdx = afterWildcard.indexOf('*');
+ wildcardIdx = afterWildcard.indexOf("*");
}
return name.endsWith(afterWildcard);
}