< prev index next >

src/java.base/share/classes/sun/security/util/HostnameChecker.java

Print this page




  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.security.util;
  27 
  28 import java.io.IOException;
  29 import java.net.InetAddress;
  30 import java.net.UnknownHostException;
  31 import java.security.Principal;
  32 import java.security.cert.*;
  33 import java.util.*;
  34 import javax.security.auth.x500.X500Principal;
  35 import javax.net.ssl.SNIHostName;
  36 
  37 import sun.net.util.IPAddressUtil;
  38 import sun.security.ssl.ClientKeyExchangeService;
  39 import sun.security.ssl.Debug;
  40 import sun.security.x509.X500Name;

  41 
  42 /**
  43  * Class to check hostnames against the names specified in a certificate as
  44  * required for TLS and LDAP.
  45  *
  46  */
  47 public class HostnameChecker {
  48 
  49     // Constant for a HostnameChecker for TLS
  50     public static final byte TYPE_TLS = 1;
  51     private static final HostnameChecker INSTANCE_TLS =
  52                                         new HostnameChecker(TYPE_TLS);
  53 
  54     // Constant for a HostnameChecker for LDAP
  55     public static final byte TYPE_LDAP = 2;
  56     private static final HostnameChecker INSTANCE_LDAP =
  57                                         new HostnameChecker(TYPE_LDAP);
  58 
  59     // constants for subject alt names of type DNS and IP
  60     private static final int ALTNAME_DNS = 2;
  61     private static final int ALTNAME_IP  = 7;
  62 
  63     private static final Debug debug = Debug.getInstance("ssl");
  64 
  65     // the algorithm to follow to perform the check. Currently unused.
  66     private final byte checkType;
  67 
  68     private HostnameChecker(byte checkType) {
  69         this.checkType = checkType;
  70     }
  71 
  72     /**
  73      * Get a HostnameChecker instance. checkType should be one of the
  74      * TYPE_* constants defined in this class.
  75      */
  76     public static HostnameChecker getInstance(byte checkType) {
  77         if (checkType == TYPE_TLS) {
  78             return INSTANCE_TLS;
  79         } else if (checkType == TYPE_LDAP) {
  80             return INSTANCE_LDAP;
  81         }
  82         throw new IllegalArgumentException("Unknown check type: " + checkType);
  83     }
  84 


 101         }
 102     }
 103 
 104     public void match(String expectedName, X509Certificate cert)
 105             throws CertificateException {
 106         match(expectedName, cert, false);
 107     }
 108 
 109     /**
 110      * Perform the check for Kerberos.
 111      */
 112     public static boolean match(String expectedName, Principal principal) {
 113         String hostName = getServerName(principal);
 114         return (expectedName.equalsIgnoreCase(hostName));
 115     }
 116 
 117     /**
 118      * Return the Server name from Kerberos principal.
 119      */
 120     public static String getServerName(Principal principal) {

 121         ClientKeyExchangeService p =
 122                 ClientKeyExchangeService.find("KRB5");
 123         if (p == null) {
 124             throw new AssertionError("Kerberos should have been available");
 125         }
 126         return p.getServiceHostName(principal);


 127     }
 128 
 129     /**
 130      * Test whether the given hostname looks like a literal IPv4 or IPv6
 131      * address. The hostname does not need to be a fully qualified name.
 132      *
 133      * This is not a strict check that performs full input validation.
 134      * That means if the method returns true, name need not be a correct
 135      * IP address, rather that it does not represent a valid DNS hostname.
 136      * Likewise for IP addresses when it returns false.
 137      */
 138     private static boolean isIpAddress(String name) {
 139         if (IPAddressUtil.isIPv4LiteralAddress(name) ||
 140             IPAddressUtil.isIPv6LiteralAddress(name)) {
 141             return true;
 142         } else {
 143             return false;
 144         }
 145     }
 146 


 299             // It would be nice to add debug log if not matching.
 300             return false;
 301         }
 302 
 303         if (checkType == TYPE_TLS) {
 304             return matchAllWildcards(name, template);
 305         } else if (checkType == TYPE_LDAP) {
 306             return matchLeftmostWildcard(name, template);
 307         } else {
 308             return false;
 309         }
 310     }
 311 
 312     /**
 313      * Returns true if the template contains an illegal wildcard character.
 314      */
 315     private static boolean hasIllegalWildcard(String domain, String template,
 316                                               boolean chainsToPublicCA) {
 317         // not ok if it is a single wildcard character or "*."
 318         if (template.equals("*") || template.equals("*.")) {
 319             if (debug != null) {
 320                 debug.println("Certificate domain name has illegal single " +

 321                               "wildcard character: " + template);
 322             }
 323             return true;
 324         }
 325 
 326         int lastWildcardIndex = template.lastIndexOf("*");
 327 
 328         // ok if it has no wildcard character
 329         if (lastWildcardIndex == -1) {
 330             return false;
 331         }
 332 
 333         String afterWildcard = template.substring(lastWildcardIndex);
 334         int firstDotIndex = afterWildcard.indexOf(".");
 335 
 336         // not ok if there is no dot after wildcard (ex: "*com")
 337         if (firstDotIndex == -1) {
 338             if (debug != null) {
 339                 debug.println("Certificate domain name has illegal wildcard, " +

 340                               "no dot after wildcard character: " + template);
 341             }
 342             return true;
 343         }
 344 
 345         // If the wildcarded domain is a top-level domain under which names
 346         // can be registered, then a wildcard is not allowed.
 347 
 348         if (!chainsToPublicCA) {
 349             return false; // skip check for non-public certificates
 350         }
 351         Optional<RegisteredDomain> rd = RegisteredDomain.from(domain)
 352                 .filter(d -> d.type() == RegisteredDomain.Type.ICANN);
 353 
 354         if (rd.isPresent()) {
 355             String wDomain = afterWildcard.substring(firstDotIndex + 1);
 356             if (rd.get().publicSuffix().equalsIgnoreCase(wDomain)) {
 357                 if (debug != null) {
 358                     debug.println("Certificate domain name has illegal " +

 359                                   "wildcard for public suffix: " + template);
 360                 }
 361                 return true;
 362             }
 363         }
 364 
 365         return false;
 366     }
 367 
 368     /**
 369      * Returns true if name matches against template.<p>
 370      *
 371      * According to RFC 2818, section 3.1 -
 372      * Names may contain the wildcard character * which is
 373      * considered to match any single domain name component
 374      * or component fragment.
 375      * E.g., *.a.com matches foo.a.com but not
 376      * bar.foo.a.com. f*.com matches foo.com but not bar.com.
 377      */
 378     private static boolean matchAllWildcards(String name,




  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.security.util;
  27 
  28 import java.io.IOException;
  29 import java.net.InetAddress;
  30 import java.net.UnknownHostException;
  31 import java.security.Principal;
  32 import java.security.cert.*;
  33 import java.util.*;
  34 import javax.security.auth.x500.X500Principal;
  35 import javax.net.ssl.SNIHostName;
  36 
  37 import sun.net.util.IPAddressUtil;


  38 import sun.security.x509.X500Name;
  39 import sun.security.ssl.SSLLogger;
  40 
  41 /**
  42  * Class to check hostnames against the names specified in a certificate as
  43  * required for TLS and LDAP.
  44  *
  45  */
  46 public class HostnameChecker {
  47 
  48     // Constant for a HostnameChecker for TLS
  49     public static final byte TYPE_TLS = 1;
  50     private static final HostnameChecker INSTANCE_TLS =
  51                                         new HostnameChecker(TYPE_TLS);
  52 
  53     // Constant for a HostnameChecker for LDAP
  54     public static final byte TYPE_LDAP = 2;
  55     private static final HostnameChecker INSTANCE_LDAP =
  56                                         new HostnameChecker(TYPE_LDAP);
  57 
  58     // constants for subject alt names of type DNS and IP
  59     private static final int ALTNAME_DNS = 2;
  60     private static final int ALTNAME_IP  = 7;
  61 


  62     // the algorithm to follow to perform the check. Currently unused.
  63     private final byte checkType;
  64 
  65     private HostnameChecker(byte checkType) {
  66         this.checkType = checkType;
  67     }
  68 
  69     /**
  70      * Get a HostnameChecker instance. checkType should be one of the
  71      * TYPE_* constants defined in this class.
  72      */
  73     public static HostnameChecker getInstance(byte checkType) {
  74         if (checkType == TYPE_TLS) {
  75             return INSTANCE_TLS;
  76         } else if (checkType == TYPE_LDAP) {
  77             return INSTANCE_LDAP;
  78         }
  79         throw new IllegalArgumentException("Unknown check type: " + checkType);
  80     }
  81 


  98         }
  99     }
 100 
 101     public void match(String expectedName, X509Certificate cert)
 102             throws CertificateException {
 103         match(expectedName, cert, false);
 104     }
 105 
 106     /**
 107      * Perform the check for Kerberos.
 108      */
 109     public static boolean match(String expectedName, Principal principal) {
 110         String hostName = getServerName(principal);
 111         return (expectedName.equalsIgnoreCase(hostName));
 112     }
 113 
 114     /**
 115      * Return the Server name from Kerberos principal.
 116      */
 117     public static String getServerName(Principal principal) {
 118 /*
 119         ClientKeyExchangeService p =
 120                 ClientKeyExchangeService.find("KRB5");
 121         if (p == null) {
 122             throw new AssertionError("Kerberos should have been available");
 123         }
 124         return p.getServiceHostName(principal);
 125 */
 126         return null;
 127     }
 128 
 129     /**
 130      * Test whether the given hostname looks like a literal IPv4 or IPv6
 131      * address. The hostname does not need to be a fully qualified name.
 132      *
 133      * This is not a strict check that performs full input validation.
 134      * That means if the method returns true, name need not be a correct
 135      * IP address, rather that it does not represent a valid DNS hostname.
 136      * Likewise for IP addresses when it returns false.
 137      */
 138     private static boolean isIpAddress(String name) {
 139         if (IPAddressUtil.isIPv4LiteralAddress(name) ||
 140             IPAddressUtil.isIPv6LiteralAddress(name)) {
 141             return true;
 142         } else {
 143             return false;
 144         }
 145     }
 146 


 299             // It would be nice to add debug log if not matching.
 300             return false;
 301         }
 302 
 303         if (checkType == TYPE_TLS) {
 304             return matchAllWildcards(name, template);
 305         } else if (checkType == TYPE_LDAP) {
 306             return matchLeftmostWildcard(name, template);
 307         } else {
 308             return false;
 309         }
 310     }
 311 
 312     /**
 313      * Returns true if the template contains an illegal wildcard character.
 314      */
 315     private static boolean hasIllegalWildcard(String domain, String template,
 316                                               boolean chainsToPublicCA) {
 317         // not ok if it is a single wildcard character or "*."
 318         if (template.equals("*") || template.equals("*.")) {
 319             if (SSLLogger.isOn) {
 320                 SSLLogger.fine(
 321                     "Certificate domain name has illegal single " +
 322                       "wildcard character: " + template);
 323             }
 324             return true;
 325         }
 326 
 327         int lastWildcardIndex = template.lastIndexOf("*");
 328 
 329         // ok if it has no wildcard character
 330         if (lastWildcardIndex == -1) {
 331             return false;
 332         }
 333 
 334         String afterWildcard = template.substring(lastWildcardIndex);
 335         int firstDotIndex = afterWildcard.indexOf(".");
 336 
 337         // not ok if there is no dot after wildcard (ex: "*com")
 338         if (firstDotIndex == -1) {
 339             if (SSLLogger.isOn) {
 340                 SSLLogger.fine(
 341                     "Certificate domain name has illegal wildcard, " +
 342                     "no dot after wildcard character: " + template);
 343             }
 344             return true;
 345         }
 346 
 347         // If the wildcarded domain is a top-level domain under which names
 348         // can be registered, then a wildcard is not allowed.
 349 
 350         if (!chainsToPublicCA) {
 351             return false; // skip check for non-public certificates
 352         }
 353         Optional<RegisteredDomain> rd = RegisteredDomain.from(domain)
 354                 .filter(d -> d.type() == RegisteredDomain.Type.ICANN);
 355 
 356         if (rd.isPresent()) {
 357             String wDomain = afterWildcard.substring(firstDotIndex + 1);
 358             if (rd.get().publicSuffix().equalsIgnoreCase(wDomain)) {
 359                 if (SSLLogger.isOn) {
 360                     SSLLogger.fine(
 361                         "Certificate domain name has illegal " +
 362                         "wildcard for public suffix: " + template);
 363                 }
 364                 return true;
 365             }
 366         }
 367 
 368         return false;
 369     }
 370 
 371     /**
 372      * Returns true if name matches against template.<p>
 373      *
 374      * According to RFC 2818, section 3.1 -
 375      * Names may contain the wildcard character * which is
 376      * considered to match any single domain name component
 377      * or component fragment.
 378      * E.g., *.a.com matches foo.a.com but not
 379      * bar.foo.a.com. f*.com matches foo.com but not bar.com.
 380      */
 381     private static boolean matchAllWildcards(String name,


< prev index next >