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,
|