Path: blob/master/src/java.base/share/classes/javax/net/ssl/SNIHostName.java
41159 views
/*1* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package javax.net.ssl;2627import java.net.IDN;28import java.nio.ByteBuffer;29import java.nio.charset.CodingErrorAction;30import java.nio.charset.StandardCharsets;31import java.nio.charset.CharsetDecoder;32import java.nio.charset.CharacterCodingException;33import java.util.Locale;34import java.util.Objects;35import java.util.regex.Pattern;3637/**38* Instances of this class represent a server name of type39* {@link StandardConstants#SNI_HOST_NAME host_name} in a Server Name40* Indication (SNI) extension.41* <P>42* As described in section 3, "Server Name Indication", of43* <A HREF="http://www.ietf.org/rfc/rfc6066.txt">TLS Extensions (RFC 6066)</A>,44* "HostName" contains the fully qualified DNS hostname of the server, as45* understood by the client. The encoded server name value of a hostname is46* represented as a byte string using ASCII encoding without a trailing dot.47* This allows the support of Internationalized Domain Names (IDN) through48* the use of A-labels (the ASCII-Compatible Encoding (ACE) form of a valid49* string of Internationalized Domain Names for Applications (IDNA)) defined50* in <A HREF="http://www.ietf.org/rfc/rfc5890.txt">RFC 5890</A>.51* <P>52* Note that {@code SNIHostName} objects are immutable.53*54* @see SNIServerName55* @see StandardConstants#SNI_HOST_NAME56*57* @since 1.858*/59public final class SNIHostName extends SNIServerName {6061// the decoded string value of the server name62private final String hostname;6364/**65* Creates an {@code SNIHostName} using the specified hostname.66* <P>67* Note that per <A HREF="http://www.ietf.org/rfc/rfc6066.txt">RFC 6066</A>,68* the encoded server name value of a hostname is69* {@link StandardCharsets#US_ASCII}-compliant. In this method,70* {@code hostname} can be a user-friendly Internationalized Domain Name71* (IDN). {@link IDN#toASCII(String, int)} is used to enforce the72* restrictions on ASCII characters in hostnames (see73* <A HREF="http://www.ietf.org/rfc/rfc3490.txt">RFC 3490</A>,74* <A HREF="http://www.ietf.org/rfc/rfc1122.txt">RFC 1122</A>,75* <A HREF="http://www.ietf.org/rfc/rfc1123.txt">RFC 1123</A>) and76* translate the {@code hostname} into ASCII Compatible Encoding (ACE), as:77* <pre>78* IDN.toASCII(hostname, IDN.USE_STD3_ASCII_RULES);79* </pre>80* <P>81* The {@code hostname} argument is illegal if it:82* <ul>83* <li> {@code hostname} is empty,</li>84* <li> {@code hostname} ends with a trailing dot,</li>85* <li> {@code hostname} is not a valid Internationalized86* Domain Name (IDN) compliant with the RFC 3490 specification.</li>87* </ul>88* @param hostname89* the hostname of this server name90*91* @throws NullPointerException if {@code hostname} is {@code null}92* @throws IllegalArgumentException if {@code hostname} is illegal93*/94public SNIHostName(String hostname) {95// IllegalArgumentException will be thrown if {@code hostname} is96// not a valid IDN.97super(StandardConstants.SNI_HOST_NAME,98(hostname = IDN.toASCII(99Objects.requireNonNull(hostname,100"Server name value of host_name cannot be null"),101IDN.USE_STD3_ASCII_RULES))102.getBytes(StandardCharsets.US_ASCII));103104this.hostname = hostname;105106// check the validity of the string hostname107checkHostName();108}109110/**111* Creates an {@code SNIHostName} using the specified encoded value.112* <P>113* This method is normally used to parse the encoded name value in a114* requested SNI extension.115* <P>116* Per <A HREF="http://www.ietf.org/rfc/rfc6066.txt">RFC 6066</A>,117* the encoded name value of a hostname is118* {@link StandardCharsets#US_ASCII}-compliant. However, in the previous119* version of the SNI extension (120* <A HREF="http://www.ietf.org/rfc/rfc4366.txt">RFC 4366</A>),121* the encoded hostname is represented as a byte string using UTF-8122* encoding. For the purpose of version tolerance, this method allows123* that the charset of {@code encoded} argument can be124* {@link StandardCharsets#UTF_8}, as well as125* {@link StandardCharsets#US_ASCII}. {@link IDN#toASCII(String)} is used126* to translate the {@code encoded} argument into ASCII Compatible127* Encoding (ACE) hostname.128* <P>129* It is strongly recommended that this constructor is only used to parse130* the encoded name value in a requested SNI extension. Otherwise, to131* comply with <A HREF="http://www.ietf.org/rfc/rfc6066.txt">RFC 6066</A>,132* please always use {@link StandardCharsets#US_ASCII}-compliant charset133* and enforce the restrictions on ASCII characters in hostnames (see134* <A HREF="http://www.ietf.org/rfc/rfc3490.txt">RFC 3490</A>,135* <A HREF="http://www.ietf.org/rfc/rfc1122.txt">RFC 1122</A>,136* <A HREF="http://www.ietf.org/rfc/rfc1123.txt">RFC 1123</A>)137* for {@code encoded} argument, or use138* {@link SNIHostName#SNIHostName(String)} instead.139* <P>140* The {@code encoded} argument is illegal if it:141* <ul>142* <li> {@code encoded} is empty,</li>143* <li> {@code encoded} ends with a trailing dot,</li>144* <li> {@code encoded} is not encoded in145* {@link StandardCharsets#US_ASCII} or146* {@link StandardCharsets#UTF_8}-compliant charset,</li>147* <li> {@code encoded} is not a valid Internationalized148* Domain Name (IDN) compliant with the RFC 3490 specification.</li>149* </ul>150*151* <P>152* Note that the {@code encoded} byte array is cloned153* to protect against subsequent modification.154*155* @param encoded156* the encoded hostname of this server name157*158* @throws NullPointerException if {@code encoded} is {@code null}159* @throws IllegalArgumentException if {@code encoded} is illegal160*/161public SNIHostName(byte[] encoded) {162// NullPointerException will be thrown if {@code encoded} is null163super(StandardConstants.SNI_HOST_NAME, encoded);164165// Compliance: RFC 4366 requires that the hostname is represented166// as a byte string using UTF_8 encoding [UTF8]167try {168// Please don't use {@link String} constructors because they169// do not report coding errors.170CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder()171.onMalformedInput(CodingErrorAction.REPORT)172.onUnmappableCharacter(CodingErrorAction.REPORT);173174this.hostname = IDN.toASCII(175decoder.decode(ByteBuffer.wrap(encoded)).toString(),176IDN.USE_STD3_ASCII_RULES);177} catch (RuntimeException | CharacterCodingException e) {178throw new IllegalArgumentException(179"The encoded server name value is invalid", e);180}181182// check the validity of the string hostname183checkHostName();184}185186/**187* Returns the {@link StandardCharsets#US_ASCII}-compliant hostname of188* this {@code SNIHostName} object.189* <P>190* Note that, per191* <A HREF="http://www.ietf.org/rfc/rfc6066.txt">RFC 6066</A>, the192* returned hostname may be an internationalized domain name that193* contains A-labels. See194* <A HREF="http://www.ietf.org/rfc/rfc5890.txt">RFC 5890</A>195* for more information about the detailed A-label specification.196*197* @return the {@link StandardCharsets#US_ASCII}-compliant hostname198* of this {@code SNIHostName} object199*/200public String getAsciiName() {201return hostname;202}203204/**205* Compares this server name to the specified object.206* <P>207* Per <A HREF="http://www.ietf.org/rfc/rfc6066.txt">RFC 6066</A>, DNS208* hostnames are case-insensitive. Two server hostnames are equal if,209* and only if, they have the same name type, and the hostnames are210* equal in a case-independent comparison.211*212* @param other213* the other server name object to compare with.214* @return true if, and only if, the {@code other} is considered215* equal to this instance216*/217@Override218public boolean equals(Object other) {219if (this == other) {220return true;221}222223if (other instanceof SNIHostName) {224return hostname.equalsIgnoreCase(((SNIHostName)other).hostname);225}226227return false;228}229230/**231* Returns a hash code value for this {@code SNIHostName}.232* <P>233* The hash code value is generated using the case-insensitive hostname234* of this {@code SNIHostName}.235*236* @return a hash code value for this {@code SNIHostName}.237*/238@Override239public int hashCode() {240int result = 17; // 17/31: prime number to decrease collisions241result = 31 * result + hostname.toUpperCase(Locale.ENGLISH).hashCode();242243return result;244}245246/**247* Returns a string representation of the object, including the DNS248* hostname in this {@code SNIHostName} object.249* <P>250* The exact details of the representation are unspecified and subject251* to change, but the following may be regarded as typical:252* <pre>253* "type=host_name (0), value={@literal <hostname>}"254* </pre>255* The "{@literal <hostname>}" is an ASCII representation of the hostname,256* which may contains A-labels. For example, a returned value of an pseudo257* hostname may look like:258* <pre>259* "type=host_name (0), value=www.example.com"260* </pre>261* or262* <pre>263* "type=host_name (0), value=xn--fsqu00a.xn--0zwm56d"264* </pre>265* <P>266* Please NOTE that the exact details of the representation are unspecified267* and subject to change.268*269* @return a string representation of the object.270*/271@Override272public String toString() {273return "type=host_name (0), value=" + hostname;274}275276/**277* Creates an {@link SNIMatcher} object for {@code SNIHostName}s.278* <P>279* This method can be used by a server to verify the acceptable280* {@code SNIHostName}s. For example,281* <pre>282* SNIMatcher matcher =283* SNIHostName.createSNIMatcher("www\\.example\\.com");284* </pre>285* will accept the hostname "www.example.com".286* <pre>287* SNIMatcher matcher =288* SNIHostName.createSNIMatcher("www\\.example\\.(com|org)");289* </pre>290* will accept hostnames "www.example.com" and "www.example.org".291*292* @param regex293* the <a href="{@docRoot}/java.base/java/util/regex/Pattern.html#sum">294* regular expression pattern</a>295* representing the hostname(s) to match296* @return a {@code SNIMatcher} object for {@code SNIHostName}s297* @throws NullPointerException if {@code regex} is298* {@code null}299* @throws java.util.regex.PatternSyntaxException if the regular expression's300* syntax is invalid301*/302public static SNIMatcher createSNIMatcher(String regex) {303if (regex == null) {304throw new NullPointerException(305"The regular expression cannot be null");306}307308return new SNIHostNameMatcher(regex);309}310311// check the validity of the string hostname312private void checkHostName() {313if (hostname.isEmpty()) {314throw new IllegalArgumentException(315"Server name value of host_name cannot be empty");316}317318if (hostname.endsWith(".")) {319throw new IllegalArgumentException(320"Server name value of host_name cannot have the trailing dot");321}322}323324private static final class SNIHostNameMatcher extends SNIMatcher {325326// the compiled representation of a regular expression.327private final Pattern pattern;328329/**330* Creates an SNIHostNameMatcher object.331*332* @param regex333* the <a href="{@docRoot}/java.base/java/util/regex/Pattern.html#sum">334* regular expression pattern</a>335* representing the hostname(s) to match336* @throws NullPointerException if {@code regex} is337* {@code null}338* @throws PatternSyntaxException if the regular expression's syntax339* is invalid340*/341SNIHostNameMatcher(String regex) {342super(StandardConstants.SNI_HOST_NAME);343pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);344}345346/**347* Attempts to match the given {@link SNIServerName}.348*349* @param serverName350* the {@link SNIServerName} instance on which this matcher351* performs match operations352*353* @return {@code true} if, and only if, the matcher matches the354* given {@code serverName}355*356* @throws NullPointerException if {@code serverName} is {@code null}357* @throws IllegalArgumentException if {@code serverName} is358* not of {@code StandardConstants#SNI_HOST_NAME} type359*360* @see SNIServerName361*/362@Override363public boolean matches(SNIServerName serverName) {364if (serverName == null) {365throw new NullPointerException(366"The SNIServerName argument cannot be null");367}368369SNIHostName hostname;370if (!(serverName instanceof SNIHostName)) {371if (serverName.getType() != StandardConstants.SNI_HOST_NAME) {372throw new IllegalArgumentException(373"The server name type is not host_name");374}375376try {377hostname = new SNIHostName(serverName.getEncoded());378} catch (NullPointerException | IllegalArgumentException e) {379return false;380}381} else {382hostname = (SNIHostName)serverName;383}384385// Let's first try the ascii name matching386String asciiName = hostname.getAsciiName();387if (pattern.matcher(asciiName).matches()) {388return true;389}390391// May be an internationalized domain name, check the Unicode392// representations.393return pattern.matcher(IDN.toUnicode(asciiName)).matches();394}395}396}397398399