8038893: Recertify certificate matching
authormullan
Mon, 20 Mar 2017 07:38:52 -0400
changeset 44760 61b03b960583
parent 44759 b4a251d223e2
child 44761 36aae904cea9
8038893: Recertify certificate matching Reviewed-by: xuelei, jdn, erikj, asmotrak
jdk/make/CompileTools.gmk
jdk/make/Tools.gmk
jdk/src/java.base/share/classes/java/net/SocketPermission.java
jdk/src/java.base/share/classes/sun/net/RegisteredDomain.java
jdk/src/java.base/share/classes/sun/security/ssl/X509TrustManagerImpl.java
jdk/src/java.base/share/classes/sun/security/util/HostnameChecker.java
jdk/src/java.base/share/classes/sun/security/util/RegisteredDomain.java
--- a/jdk/make/CompileTools.gmk	Thu Feb 23 12:08:44 2017 +0000
+++ b/jdk/make/CompileTools.gmk	Mon Mar 20 07:38:52 2017 -0400
@@ -1,5 +1,5 @@
 #
-# Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved.
 # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 #
 # This code is free software; you can redistribute it and/or modify it
@@ -30,11 +30,19 @@
 include JavaCompilation.gmk
 include SetupJavaCompilers.gmk
 
+$(eval $(call IncludeCustomExtension, jdk, CompileTools.gmk))
+
 ################################################################################
 
+# Use += to be able to add to this from a custom extension
+BUILD_TOOLS_SRC_DIRS += \
+    $(JDK_TOPDIR)/make/src/classes \
+    $(BUILDTOOLS_OUTPUTDIR)/interim_cldrconverter_classes \
+    #
+
 $(eval $(call SetupJavaCompilation,BUILD_TOOLS_JDK, \
     SETUP := GENERATE_OLDBYTECODE, \
-    SRC := $(JDK_TOPDIR)/make/src/classes $(BUILDTOOLS_OUTPUTDIR)/interim_cldrconverter_classes, \
+    SRC := $(BUILD_TOOLS_SRC_DIRS), \
     EXCLUDES := build/tools/deps \
                 build/tools/jigsaw, \
     BIN := $(BUILDTOOLS_OUTPUTDIR)/jdk_tools_classes))
--- a/jdk/make/Tools.gmk	Thu Feb 23 12:08:44 2017 +0000
+++ b/jdk/make/Tools.gmk	Mon Mar 20 07:38:52 2017 -0400
@@ -1,5 +1,5 @@
 #
-# Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved.
 # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 #
 # This code is free software; you can redistribute it and/or modify it
@@ -28,6 +28,9 @@
 
 include JavaCompilation.gmk
 
+# Hook to include the corresponding custom file, if present.
+$(eval $(call IncludeCustomExtension, jdk, Tools.gmk))
+
 ################################################################################
 # To avoid reevaluating the compilation setup for the tools each time this file
 # is included, the actual compilation is handled by CompileTools.gmk. The
--- a/jdk/src/java.base/share/classes/java/net/SocketPermission.java	Thu Feb 23 12:08:44 2017 +0000
+++ b/jdk/src/java.base/share/classes/java/net/SocketPermission.java	Mon Mar 20 07:38:52 2017 -0400
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -44,8 +44,8 @@
 import java.util.StringTokenizer;
 import java.util.concurrent.ConcurrentSkipListMap;
 import sun.net.util.IPAddressUtil;
-import sun.net.RegisteredDomain;
 import sun.net.PortConfig;
+import sun.security.util.RegisteredDomain;
 import sun.security.util.SecurityConstants;
 import sun.security.util.Debug;
 
@@ -678,13 +678,18 @@
         String a = cname.toLowerCase();
         String b = hname.toLowerCase();
         if (a.startsWith(b)  &&
-            ((a.length() == b.length()) || (a.charAt(b.length()) == '.')))
+            ((a.length() == b.length()) || (a.charAt(b.length()) == '.'))) {
             return true;
+        }
         if (cdomain == null) {
-            cdomain = RegisteredDomain.getRegisteredDomain(a);
+            cdomain = RegisteredDomain.from(a)
+                                      .map(RegisteredDomain::name)
+                                      .orElse(a);
         }
         if (hdomain == null) {
-            hdomain = RegisteredDomain.getRegisteredDomain(b);
+            hdomain = RegisteredDomain.from(b)
+                                      .map(RegisteredDomain::name)
+                                      .orElse(b);
         }
 
         return cdomain.length() != 0 && hdomain.length() != 0
--- a/jdk/src/java.base/share/classes/sun/net/RegisteredDomain.java	Thu Feb 23 12:08:44 2017 +0000
+++ b/jdk/src/java.base/share/classes/sun/net/RegisteredDomain.java	Mon Mar 20 07:38:52 2017 -0400
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -32,6 +32,10 @@
 import java.util.Set;
 
 /*
+ * WARNING: This class may contain out-of-date information. It should be
+ * updated or replaced with an appropriate implementation. See
+ * sun.security.util.RegisteredDomain for more information.
+ *
  * The naming tables listed below were gathered from publicly available data such as
  * the subdomain registration websites listed for each top-level domain by the Internet
  * Assigned Numbers Authority and the website of the Internet Corporation for Assigned Names
@@ -696,6 +700,36 @@
     top3Map.put("tr", new HashSet<String>(Arrays.asList("gov.nc.tr")));
 }
 
+    /**
+     * Returns a {@code sun.security.util.RegisteredDomain} representing the
+     * registered part of the specified domain.
+     *
+     * @param domain the domain name
+     * @return a {@code sun.security.util.RegisteredDomain} or null
+     *    if the domain is unknown or not registerable
+     * @throws NullPointerException if domain is null
+     */
+    public static sun.security.util.RegisteredDomain registeredDomain(String domain) {
+        String name = getRegisteredDomain(domain);
+        if (name.equals(domain)) {
+            return null;
+        }
+        return new sun.security.util.RegisteredDomain() {
+            private String rname = name;
+            @Override
+            public String name() {
+                return rname;
+            }
+            @Override
+            public sun.security.util.RegisteredDomain.Type type() {
+                return sun.security.util.RegisteredDomain.Type.ICANN;
+            }
+            @Override
+            public String publicSuffix() {
+                return rname.substring(rname.indexOf(".") + 1);
+            }
+        };
+    }
 
     /*
      * Return the registered part of a qualified domain
--- a/jdk/src/java.base/share/classes/sun/security/ssl/X509TrustManagerImpl.java	Thu Feb 23 12:08:44 2017 +0000
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/X509TrustManagerImpl.java	Mon Mar 20 07:38:52 2017 -0400
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -35,6 +35,7 @@
 import javax.net.ssl.*;
 
 import sun.security.validator.*;
+import sun.security.util.AnchorCertificates;
 import sun.security.util.HostnameChecker;
 
 /**
@@ -186,13 +187,11 @@
         return v;
     }
 
-
     private void checkTrusted(X509Certificate[] chain, String authType,
                 Socket socket, boolean isClient) throws CertificateException {
         Validator v = checkTrustedInit(chain, authType, isClient);
 
-        AlgorithmConstraints constraints = null;
-        List<byte[]> responseList = Collections.emptyList();
+        X509Certificate[] trustedChain = null;
         if ((socket != null) && socket.isConnected() &&
                                         (socket instanceof SSLSocket)) {
 
@@ -202,48 +201,46 @@
                 throw new CertificateException("No handshake session");
             }
 
-            // check endpoint identity
-            String identityAlg = sslSocket.getSSLParameters().
-                    getEndpointIdentificationAlgorithm();
-            if (identityAlg != null && identityAlg.length() != 0) {
-                checkIdentity(session, chain[0], identityAlg, isClient,
-                        getRequestedServerNames(socket));
-            }
-
             // create the algorithm constraints
             ProtocolVersion protocolVersion =
                 ProtocolVersion.valueOf(session.getProtocol());
-            if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
-                if (session instanceof ExtendedSSLSession) {
-                    ExtendedSSLSession extSession =
-                                    (ExtendedSSLSession)session;
-                    String[] localSupportedSignAlgs =
-                            extSession.getLocalSupportedSignatureAlgorithms();
+            boolean isExtSession = (session instanceof ExtendedSSLSession);
+            AlgorithmConstraints constraints = null;
+            if (protocolVersion.v >= ProtocolVersion.TLS12.v && isExtSession) {
+                ExtendedSSLSession extSession = (ExtendedSSLSession)session;
+                String[] localSupportedSignAlgs =
+                        extSession.getLocalSupportedSignatureAlgorithms();
 
-                    constraints = new SSLAlgorithmConstraints(
-                                    sslSocket, localSupportedSignAlgs, false);
-                } else {
-                    constraints =
-                            new SSLAlgorithmConstraints(sslSocket, false);
-                }
+                constraints = new SSLAlgorithmConstraints(
+                                sslSocket, localSupportedSignAlgs, false);
             } else {
                 constraints = new SSLAlgorithmConstraints(sslSocket, false);
             }
 
             // Grab any stapled OCSP responses for use in validation
-            if (session instanceof ExtendedSSLSession) {
+            List<byte[]> responseList = Collections.emptyList();
+            if (!isClient && isExtSession) {
                 responseList =
                         ((ExtendedSSLSession)session).getStatusResponses();
             }
-        }
+            trustedChain = validate(v, chain, responseList,
+                    constraints, isClient ? null : authType);
+
+            // check if EE certificate chains to a public root CA (as
+            // pre-installed in cacerts)
+            boolean chainsToPublicCA =
+                AnchorCertificates.contains(trustedChain[trustedChain.length-1]);
 
-        X509Certificate[] trustedChain = null;
-        if (isClient) {
+            // check endpoint identity
+            String identityAlg = sslSocket.getSSLParameters().
+                    getEndpointIdentificationAlgorithm();
+            if (identityAlg != null && identityAlg.length() != 0) {
+                checkIdentity(session, trustedChain[0], identityAlg, isClient,
+                        getRequestedServerNames(socket), chainsToPublicCA);
+            }
+        } else {
             trustedChain = validate(v, chain, Collections.emptyList(),
-                    constraints, null);
-        } else {
-            trustedChain = validate(v, chain, responseList, constraints,
-                    authType);
+                    null, isClient ? null : authType);
         }
         if (debug != null && Debug.isOn("trustmanager")) {
             System.out.println("Found trusted certificate:");
@@ -255,56 +252,53 @@
             SSLEngine engine, boolean isClient) throws CertificateException {
         Validator v = checkTrustedInit(chain, authType, isClient);
 
-        AlgorithmConstraints constraints = null;
-        List<byte[]> responseList = Collections.emptyList();
+        X509Certificate[] trustedChain = null;
         if (engine != null) {
             SSLSession session = engine.getHandshakeSession();
             if (session == null) {
                 throw new CertificateException("No handshake session");
             }
 
-            // check endpoint identity
-            String identityAlg = engine.getSSLParameters().
-                    getEndpointIdentificationAlgorithm();
-            if (identityAlg != null && identityAlg.length() != 0) {
-                checkIdentity(session, chain[0], identityAlg, isClient,
-                        getRequestedServerNames(engine));
-            }
-
             // create the algorithm constraints
             ProtocolVersion protocolVersion =
                 ProtocolVersion.valueOf(session.getProtocol());
-            if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
-                if (session instanceof ExtendedSSLSession) {
-                    ExtendedSSLSession extSession =
-                                    (ExtendedSSLSession)session;
-                    String[] localSupportedSignAlgs =
-                            extSession.getLocalSupportedSignatureAlgorithms();
+            boolean isExtSession = (session instanceof ExtendedSSLSession);
+            AlgorithmConstraints constraints = null;
+            if (protocolVersion.v >= ProtocolVersion.TLS12.v && isExtSession) {
+                ExtendedSSLSession extSession = (ExtendedSSLSession)session;
+                String[] localSupportedSignAlgs =
+                        extSession.getLocalSupportedSignatureAlgorithms();
 
-                    constraints = new SSLAlgorithmConstraints(
-                                    engine, localSupportedSignAlgs, false);
-                } else {
-                    constraints =
-                            new SSLAlgorithmConstraints(engine, false);
-                }
+                constraints = new SSLAlgorithmConstraints(
+                                engine, localSupportedSignAlgs, false);
             } else {
                 constraints = new SSLAlgorithmConstraints(engine, false);
             }
 
             // Grab any stapled OCSP responses for use in validation
-            if (session instanceof ExtendedSSLSession) {
+            List<byte[]> responseList = Collections.emptyList();
+            if (!isClient && isExtSession) {
                 responseList =
                         ((ExtendedSSLSession)session).getStatusResponses();
             }
-        }
+            trustedChain = validate(v, chain, responseList,
+                    constraints, isClient ? null : authType);
+
+            // check if EE certificate chains to a public root CA (as
+            // pre-installed in cacerts)
+            boolean chainsToPublicCA =
+                AnchorCertificates.contains(trustedChain[trustedChain.length-1]);
 
-        X509Certificate[] trustedChain = null;
-        if (isClient) {
+            // check endpoint identity
+            String identityAlg = engine.getSSLParameters().
+                    getEndpointIdentificationAlgorithm();
+            if (identityAlg != null && identityAlg.length() != 0) {
+                checkIdentity(session, trustedChain[0], identityAlg, isClient,
+                        getRequestedServerNames(engine), chainsToPublicCA);
+            }
+        } else {
             trustedChain = validate(v, chain, Collections.emptyList(),
-                    constraints, null);
-        } else {
-            trustedChain = validate(v, chain, responseList, constraints,
-                    authType);
+                    null, isClient ? null : authType);
         }
         if (debug != null && Debug.isOn("trustmanager")) {
             System.out.println("Found trusted certificate:");
@@ -437,7 +431,8 @@
             X509Certificate cert,
             String algorithm,
             boolean isClient,
-            List<SNIServerName> sniNames) throws CertificateException {
+            List<SNIServerName> sniNames,
+            boolean chainsToPublicCA) throws CertificateException {
 
         boolean identifiable = false;
         String peerHost = session.getPeerHost();
@@ -445,7 +440,7 @@
             String hostname = getHostNameInSNI(sniNames);
             if (hostname != null) {
                 try {
-                    checkIdentity(hostname, cert, algorithm);
+                    checkIdentity(hostname, cert, algorithm, chainsToPublicCA);
                     identifiable = true;
                 } catch (CertificateException ce) {
                     if (hostname.equalsIgnoreCase(peerHost)) {
@@ -458,7 +453,7 @@
         }
 
         if (!identifiable) {
-            checkIdentity(peerHost, cert, algorithm);
+            checkIdentity(peerHost, cert, algorithm, chainsToPublicCA);
         }
     }
 
@@ -469,6 +464,12 @@
      */
     static void checkIdentity(String hostname, X509Certificate cert,
             String algorithm) throws CertificateException {
+        checkIdentity(hostname, cert, algorithm, false);
+    }
+
+    private static void checkIdentity(String hostname, X509Certificate cert,
+            String algorithm, boolean chainsToPublicCA)
+            throws CertificateException {
         if (algorithm != null && algorithm.length() != 0) {
             // if IPv6 strip off the "[]"
             if ((hostname != null) && hostname.startsWith("[") &&
@@ -478,11 +479,11 @@
 
             if (algorithm.equalsIgnoreCase("HTTPS")) {
                 HostnameChecker.getInstance(HostnameChecker.TYPE_TLS).match(
-                        hostname, cert);
+                        hostname, cert, chainsToPublicCA);
             } else if (algorithm.equalsIgnoreCase("LDAP") ||
                     algorithm.equalsIgnoreCase("LDAPS")) {
                 HostnameChecker.getInstance(HostnameChecker.TYPE_LDAP).match(
-                        hostname, cert);
+                        hostname, cert, chainsToPublicCA);
             } else {
                 throw new CertificateException(
                         "Unknown identification algorithm: " + algorithm);
--- 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);
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/sun/security/util/RegisteredDomain.java	Mon Mar 20 07:38:52 2017 -0400
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.security.util;
+
+import java.util.Optional;
+
+/**
+ * A domain that is registered under a "public suffix". The public suffix is
+ * a top-level domain under which names can be registered. For example,
+ * "com" and "co.uk" are public suffixes, and "example.com" and "example.co.uk"
+ * are registered domains.
+ * <p>
+ * The primary purpose of this class is to determine if domains are safe to
+ * use in various use-cases.
+ */
+public interface RegisteredDomain {
+
+    public enum Type {
+        /**
+         * An ICANN registered domain.
+         */
+        ICANN,
+        /**
+         * A private registered domain.
+         */
+        PRIVATE
+    }
+
+    /**
+     * Returns the name of the registered domain.
+     *
+     * @return the name of the registered domain
+     */
+    String name();
+
+    /**
+     * Returns the type of the registered domain.
+     *
+     * @return the type of the registered domain
+     */
+    Type type();
+
+    /**
+     * Returns the public suffix of the registered domain.
+     *
+     * @return the public suffix of the registered domain
+     */
+    String publicSuffix();
+
+    /**
+     * Returns an {@code Optional<RegisteredDomain>} representing the
+     * registered part of the specified domain.
+     *
+     * {@implNote}
+     * The default implementation is based on the legacy
+     * {@code sun.net.RegisteredDomain} class which is no longer maintained.
+     * It should be updated or replaced with an appropriate implementation.
+     *
+     * @param domain the domain name
+     * @return an {@code Optional<RegisteredDomain>}; the {@code Optional} is
+     *    empty if the domain is unknown or not registerable
+     * @throws NullPointerException if domain is null
+     */
+    public static Optional<RegisteredDomain> from(String domain) {
+        if (domain == null) {
+            throw new NullPointerException();
+        }
+        return Optional.ofNullable(sun.net.RegisteredDomain.registeredDomain(domain));
+    }
+}