From 2cef018734eb562ad598498a17d88eb4fe5e2a31 Mon Sep 17 00:00:00 2001 From: Sean Mullan Date: Mon, 20 Mar 2017 07:38:52 -0400 Subject: [PATCH] 8038893: Recertify certificate matching Reviewed-by: xuelei, jdn, erikj, asmotrak --- jdk/make/CompileTools.gmk | 12 +- jdk/make/Tools.gmk | 5 +- .../classes/java/net/SocketPermission.java | 15 +- .../classes/sun/net/RegisteredDomain.java | 36 ++++- .../security/ssl/X509TrustManagerImpl.java | 135 +++++++++--------- .../sun/security/util/HostnameChecker.java | 114 ++++++++++++--- .../sun/security/util/RegisteredDomain.java | 93 ++++++++++++ 7 files changed, 312 insertions(+), 98 deletions(-) create mode 100644 jdk/src/java.base/share/classes/sun/security/util/RegisteredDomain.java diff --git a/jdk/make/CompileTools.gmk b/jdk/make/CompileTools.gmk index 9d76d660e2d..7670d694323 100644 --- a/jdk/make/CompileTools.gmk +++ b/jdk/make/CompileTools.gmk @@ -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 MakeBase.gmk 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)) diff --git a/jdk/make/Tools.gmk b/jdk/make/Tools.gmk index 92af1ee102f..153da84124d 100644 --- a/jdk/make/Tools.gmk +++ b/jdk/make/Tools.gmk @@ -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 @@ _TOOLS_GMK := 1 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 diff --git a/jdk/src/java.base/share/classes/java/net/SocketPermission.java b/jdk/src/java.base/share/classes/java/net/SocketPermission.java index b1a94154af0..a7eec857cbc 100644 --- a/jdk/src/java.base/share/classes/java/net/SocketPermission.java +++ b/jdk/src/java.base/share/classes/java/net/SocketPermission.java @@ -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.StringJoiner; 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 @@ public final class SocketPermission extends Permission 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 diff --git a/jdk/src/java.base/share/classes/sun/net/RegisteredDomain.java b/jdk/src/java.base/share/classes/sun/net/RegisteredDomain.java index 7e75b321eb6..3a506da9831 100644 --- a/jdk/src/java.base/share/classes/sun/net/RegisteredDomain.java +++ b/jdk/src/java.base/share/classes/sun/net/RegisteredDomain.java @@ -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.Map; 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 @@ static { top3Map.put("tr", new HashSet(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 diff --git a/jdk/src/java.base/share/classes/sun/security/ssl/X509TrustManagerImpl.java b/jdk/src/java.base/share/classes/sun/security/ssl/X509TrustManagerImpl.java index a8e7328b753..42c84872bad 100644 --- a/jdk/src/java.base/share/classes/sun/security/ssl/X509TrustManagerImpl.java +++ b/jdk/src/java.base/share/classes/sun/security/ssl/X509TrustManagerImpl.java @@ -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 java.security.cert.*; import javax.net.ssl.*; import sun.security.validator.*; +import sun.security.util.AnchorCertificates; import sun.security.util.HostnameChecker; /** @@ -186,13 +187,11 @@ final class X509TrustManagerImpl extends X509ExtendedTrustManager 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 responseList = Collections.emptyList(); + X509Certificate[] trustedChain = null; if ((socket != null) && socket.isConnected() && (socket instanceof SSLSocket)) { @@ -202,48 +201,46 @@ final class X509TrustManagerImpl extends X509ExtendedTrustManager 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 responseList = Collections.emptyList(); + if (!isClient && isExtSession) { responseList = ((ExtendedSSLSession)session).getStatusResponses(); } - } + trustedChain = validate(v, chain, responseList, + constraints, isClient ? null : authType); - X509Certificate[] trustedChain = null; - if (isClient) { - trustedChain = validate(v, chain, Collections.emptyList(), - constraints, null); + // check if EE certificate chains to a public root CA (as + // pre-installed in cacerts) + boolean chainsToPublicCA = + AnchorCertificates.contains(trustedChain[trustedChain.length-1]); + + // 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, responseList, constraints, - authType); + trustedChain = validate(v, chain, Collections.emptyList(), + null, isClient ? null : authType); } if (debug != null && Debug.isOn("trustmanager")) { System.out.println("Found trusted certificate:"); @@ -255,56 +252,53 @@ final class X509TrustManagerImpl extends X509ExtendedTrustManager SSLEngine engine, boolean isClient) throws CertificateException { Validator v = checkTrustedInit(chain, authType, isClient); - AlgorithmConstraints constraints = null; - List 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 responseList = Collections.emptyList(); + if (!isClient && isExtSession) { responseList = ((ExtendedSSLSession)session).getStatusResponses(); } - } + trustedChain = validate(v, chain, responseList, + constraints, isClient ? null : authType); - X509Certificate[] trustedChain = null; - if (isClient) { - trustedChain = validate(v, chain, Collections.emptyList(), - constraints, null); + // check if EE certificate chains to a public root CA (as + // pre-installed in cacerts) + boolean chainsToPublicCA = + AnchorCertificates.contains(trustedChain[trustedChain.length-1]); + + // 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, responseList, constraints, - authType); + trustedChain = validate(v, chain, Collections.emptyList(), + null, isClient ? null : authType); } if (debug != null && Debug.isOn("trustmanager")) { System.out.println("Found trusted certificate:"); @@ -437,7 +431,8 @@ final class X509TrustManagerImpl extends X509ExtendedTrustManager X509Certificate cert, String algorithm, boolean isClient, - List sniNames) throws CertificateException { + List sniNames, + boolean chainsToPublicCA) throws CertificateException { boolean identifiable = false; String peerHost = session.getPeerHost(); @@ -445,7 +440,7 @@ final class X509TrustManagerImpl extends X509ExtendedTrustManager 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 @@ final class X509TrustManagerImpl extends X509ExtendedTrustManager } if (!identifiable) { - checkIdentity(peerHost, cert, algorithm); + checkIdentity(peerHost, cert, algorithm, chainsToPublicCA); } } @@ -469,6 +464,12 @@ final class X509TrustManagerImpl extends X509ExtendedTrustManager */ 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 @@ final class X509TrustManagerImpl extends X509ExtendedTrustManager 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); diff --git a/jdk/src/java.base/share/classes/sun/security/util/HostnameChecker.java b/jdk/src/java.base/share/classes/sun/security/util/HostnameChecker.java index afa0fe0bbcf..145dff297f3 100644 --- a/jdk/src/java.base/share/classes/sun/security/util/HostnameChecker.java +++ b/jdk/src/java.base/share/classes/sun/security/util/HostnameChecker.java @@ -28,17 +28,15 @@ package sun.security.util; 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.security.ssl.ClientKeyExchangeService; -import sun.security.x509.X500Name; - import sun.net.util.IPAddressUtil; +import sun.security.ssl.ClientKeyExchangeService; +import sun.security.ssl.Debug; +import sun.security.x509.X500Name; /** * Class to check hostnames against the names specified in a certificate as @@ -61,6 +59,8 @@ public class HostnameChecker { 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 @@ public class HostnameChecker { /** * 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 @@ public class HostnameChecker { * 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> 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 @@ public class HostnameChecker { (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 @@ public class HostnameChecker { * The template 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 @@ public class HostnameChecker { } } + /** + * 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 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.

@@ -317,9 +387,9 @@ public class HostnameChecker { 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 @@ public class HostnameChecker { */ 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 @@ public class HostnameChecker { // update the match scope name = name.substring(beforeStartIdx + beforeWildcard.length()); - wildcardIdx = afterWildcard.indexOf('*'); + wildcardIdx = afterWildcard.indexOf("*"); } return name.endsWith(afterWildcard); } diff --git a/jdk/src/java.base/share/classes/sun/security/util/RegisteredDomain.java b/jdk/src/java.base/share/classes/sun/security/util/RegisteredDomain.java new file mode 100644 index 00000000000..ec601d51db4 --- /dev/null +++ b/jdk/src/java.base/share/classes/sun/security/util/RegisteredDomain.java @@ -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. + *

+ * 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} 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}; the {@code Optional} is + * empty if the domain is unknown or not registerable + * @throws NullPointerException if domain is null + */ + public static Optional from(String domain) { + if (domain == null) { + throw new NullPointerException(); + } + return Optional.ofNullable(sun.net.RegisteredDomain.registeredDomain(domain)); + } +}