8038893: Recertify certificate matching
Reviewed-by: xuelei, jdn, erikj, asmotrak
--- 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));
+ }
+}