aboutsummaryrefslogtreecommitdiff
path: root/ojluni/src/main/java/javax/net/ssl/SNIHostName.java
diff options
context:
space:
mode:
Diffstat (limited to 'ojluni/src/main/java/javax/net/ssl/SNIHostName.java')
-rw-r--r--ojluni/src/main/java/javax/net/ssl/SNIHostName.java396
1 files changed, 396 insertions, 0 deletions
diff --git a/ojluni/src/main/java/javax/net/ssl/SNIHostName.java b/ojluni/src/main/java/javax/net/ssl/SNIHostName.java
new file mode 100644
index 00000000000..d5e71414497
--- /dev/null
+++ b/ojluni/src/main/java/javax/net/ssl/SNIHostName.java
@@ -0,0 +1,396 @@
+/*
+ * Copyright (c) 2012, 2013, 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 javax.net.ssl;
+
+import java.net.IDN;
+import java.nio.ByteBuffer;
+import java.nio.charset.CodingErrorAction;
+import java.nio.charset.StandardCharsets;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CharacterCodingException;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.regex.Pattern;
+
+/**
+ * Instances of this class represent a server name of type
+ * {@link StandardConstants#SNI_HOST_NAME host_name} in a Server Name
+ * Indication (SNI) extension.
+ * <P>
+ * As described in section 3, "Server Name Indication", of
+ * <A HREF="http://www.ietf.org/rfc/rfc6066.txt">TLS Extensions (RFC 6066)</A>,
+ * "HostName" contains the fully qualified DNS hostname of the server, as
+ * understood by the client. The encoded server name value of a hostname is
+ * represented as a byte string using ASCII encoding without a trailing dot.
+ * This allows the support of Internationalized Domain Names (IDN) through
+ * the use of A-labels (the ASCII-Compatible Encoding (ACE) form of a valid
+ * string of Internationalized Domain Names for Applications (IDNA)) defined
+ * in <A HREF="http://www.ietf.org/rfc/rfc5890.txt">RFC 5890</A>.
+ * <P>
+ * Note that {@code SNIHostName} objects are immutable.
+ *
+ * @see SNIServerName
+ * @see StandardConstants#SNI_HOST_NAME
+ *
+ * @since 1.8
+ */
+public final class SNIHostName extends SNIServerName {
+
+ // the decoded string value of the server name
+ private final String hostname;
+
+ /**
+ * Creates an {@code SNIHostName} using the specified hostname.
+ * <P>
+ * Note that per <A HREF="http://www.ietf.org/rfc/rfc6066.txt">RFC 6066</A>,
+ * the encoded server name value of a hostname is
+ * {@link StandardCharsets#US_ASCII}-compliant. In this method,
+ * {@code hostname} can be a user-friendly Internationalized Domain Name
+ * (IDN). {@link IDN#toASCII(String, int)} is used to enforce the
+ * restrictions on ASCII characters in hostnames (see
+ * <A HREF="http://www.ietf.org/rfc/rfc3490.txt">RFC 3490</A>,
+ * <A HREF="http://www.ietf.org/rfc/rfc1122.txt">RFC 1122</A>,
+ * <A HREF="http://www.ietf.org/rfc/rfc1123.txt">RFC 1123</A>) and
+ * translate the {@code hostname} into ASCII Compatible Encoding (ACE), as:
+ * <pre>
+ * IDN.toASCII(hostname, IDN.USE_STD3_ASCII_RULES);
+ * </pre>
+ * <P>
+ * The {@code hostname} argument is illegal if it:
+ * <ul>
+ * <li> {@code hostname} is empty,</li>
+ * <li> {@code hostname} ends with a trailing dot,</li>
+ * <li> {@code hostname} is not a valid Internationalized
+ * Domain Name (IDN) compliant with the RFC 3490 specification.</li>
+ * </ul>
+ * @param hostname
+ * the hostname of this server name
+ *
+ * @throws NullPointerException if {@code hostname} is {@code null}
+ * @throws IllegalArgumentException if {@code hostname} is illegal
+ */
+ public SNIHostName(String hostname) {
+ // IllegalArgumentException will be thrown if {@code hostname} is
+ // not a valid IDN.
+ super(StandardConstants.SNI_HOST_NAME,
+ (hostname = IDN.toASCII(
+ Objects.requireNonNull(hostname,
+ "Server name value of host_name cannot be null"),
+ IDN.USE_STD3_ASCII_RULES))
+ .getBytes(StandardCharsets.US_ASCII));
+
+ this.hostname = hostname;
+
+ // check the validity of the string hostname
+ checkHostName();
+ }
+
+ /**
+ * Creates an {@code SNIHostName} using the specified encoded value.
+ * <P>
+ * This method is normally used to parse the encoded name value in a
+ * requested SNI extension.
+ * <P>
+ * Per <A HREF="http://www.ietf.org/rfc/rfc6066.txt">RFC 6066</A>,
+ * the encoded name value of a hostname is
+ * {@link StandardCharsets#US_ASCII}-compliant. However, in the previous
+ * version of the SNI extension (
+ * <A HREF="http://www.ietf.org/rfc/rfc4366.txt">RFC 4366</A>),
+ * the encoded hostname is represented as a byte string using UTF-8
+ * encoding. For the purpose of version tolerance, this method allows
+ * that the charset of {@code encoded} argument can be
+ * {@link StandardCharsets#UTF_8}, as well as
+ * {@link StandardCharsets#US_ASCII}. {@link IDN#toASCII(String)} is used
+ * to translate the {@code encoded} argument into ASCII Compatible
+ * Encoding (ACE) hostname.
+ * <P>
+ * It is strongly recommended that this constructor is only used to parse
+ * the encoded name value in a requested SNI extension. Otherwise, to
+ * comply with <A HREF="http://www.ietf.org/rfc/rfc6066.txt">RFC 6066</A>,
+ * please always use {@link StandardCharsets#US_ASCII}-compliant charset
+ * and enforce the restrictions on ASCII characters in hostnames (see
+ * <A HREF="http://www.ietf.org/rfc/rfc3490.txt">RFC 3490</A>,
+ * <A HREF="http://www.ietf.org/rfc/rfc1122.txt">RFC 1122</A>,
+ * <A HREF="http://www.ietf.org/rfc/rfc1123.txt">RFC 1123</A>)
+ * for {@code encoded} argument, or use
+ * {@link SNIHostName#SNIHostName(String)} instead.
+ * <P>
+ * The {@code encoded} argument is illegal if it:
+ * <ul>
+ * <li> {@code encoded} is empty,</li>
+ * <li> {@code encoded} ends with a trailing dot,</li>
+ * <li> {@code encoded} is not encoded in
+ * {@link StandardCharsets#US_ASCII} or
+ * {@link StandardCharsets#UTF_8}-compliant charset,</li>
+ * <li> {@code encoded} is not a valid Internationalized
+ * Domain Name (IDN) compliant with the RFC 3490 specification.</li>
+ * </ul>
+ *
+ * <P>
+ * Note that the {@code encoded} byte array is cloned
+ * to protect against subsequent modification.
+ *
+ * @param encoded
+ * the encoded hostname of this server name
+ *
+ * @throws NullPointerException if {@code encoded} is {@code null}
+ * @throws IllegalArgumentException if {@code encoded} is illegal
+ */
+ public SNIHostName(byte[] encoded) {
+ // NullPointerException will be thrown if {@code encoded} is null
+ super(StandardConstants.SNI_HOST_NAME, encoded);
+
+ // Compliance: RFC 4366 requires that the hostname is represented
+ // as a byte string using UTF_8 encoding [UTF8]
+ try {
+ // Please don't use {@link String} constructors because they
+ // do not report coding errors.
+ CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder()
+ .onMalformedInput(CodingErrorAction.REPORT)
+ .onUnmappableCharacter(CodingErrorAction.REPORT);
+
+ this.hostname = IDN.toASCII(
+ decoder.decode(ByteBuffer.wrap(encoded)).toString());
+ } catch (RuntimeException | CharacterCodingException e) {
+ throw new IllegalArgumentException(
+ "The encoded server name value is invalid", e);
+ }
+
+ // check the validity of the string hostname
+ checkHostName();
+ }
+
+ /**
+ * Returns the {@link StandardCharsets#US_ASCII}-compliant hostname of
+ * this {@code SNIHostName} object.
+ * <P>
+ * Note that, per
+ * <A HREF="http://www.ietf.org/rfc/rfc6066.txt">RFC 6066</A>, the
+ * returned hostname may be an internationalized domain name that
+ * contains A-labels. See
+ * <A HREF="http://www.ietf.org/rfc/rfc5890.txt">RFC 5890</A>
+ * for more information about the detailed A-label specification.
+ *
+ * @return the {@link StandardCharsets#US_ASCII}-compliant hostname
+ * of this {@code SNIHostName} object
+ */
+ public String getAsciiName() {
+ return hostname;
+ }
+
+ /**
+ * Compares this server name to the specified object.
+ * <P>
+ * Per <A HREF="http://www.ietf.org/rfc/rfc6066.txt">RFC 6066</A>, DNS
+ * hostnames are case-insensitive. Two server hostnames are equal if,
+ * and only if, they have the same name type, and the hostnames are
+ * equal in a case-independent comparison.
+ *
+ * @param other
+ * the other server name object to compare with.
+ * @return true if, and only if, the {@code other} is considered
+ * equal to this instance
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ if (other instanceof SNIHostName) {
+ return hostname.equalsIgnoreCase(((SNIHostName)other).hostname);
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns a hash code value for this {@code SNIHostName}.
+ * <P>
+ * The hash code value is generated using the case-insensitive hostname
+ * of this {@code SNIHostName}.
+ *
+ * @return a hash code value for this {@code SNIHostName}.
+ */
+ @Override
+ public int hashCode() {
+ int result = 17; // 17/31: prime number to decrease collisions
+ result = 31 * result + hostname.toUpperCase(Locale.ENGLISH).hashCode();
+
+ return result;
+ }
+
+ /**
+ * Returns a string representation of the object, including the DNS
+ * hostname in this {@code SNIHostName} object.
+ * <P>
+ * The exact details of the representation are unspecified and subject
+ * to change, but the following may be regarded as typical:
+ * <pre>
+ * "type=host_name (0), value={@literal <hostname>}"
+ * </pre>
+ * The "{@literal <hostname>}" is an ASCII representation of the hostname,
+ * which may contains A-labels. For example, a returned value of an pseudo
+ * hostname may look like:
+ * <pre>
+ * "type=host_name (0), value=www.example.com"
+ * </pre>
+ * or
+ * <pre>
+ * "type=host_name (0), value=xn--fsqu00a.xn--0zwm56d"
+ * </pre>
+ * <P>
+ * Please NOTE that the exact details of the representation are unspecified
+ * and subject to change.
+ *
+ * @return a string representation of the object.
+ */
+ @Override
+ public String toString() {
+ return "type=host_name (0), value=" + hostname;
+ }
+
+ /**
+ * Creates an {@link SNIMatcher} object for {@code SNIHostName}s.
+ * <P>
+ * This method can be used by a server to verify the acceptable
+ * {@code SNIHostName}s. For example,
+ * <pre>
+ * SNIMatcher matcher =
+ * SNIHostName.createSNIMatcher("www\\.example\\.com");
+ * </pre>
+ * will accept the hostname "www.example.com".
+ * <pre>
+ * SNIMatcher matcher =
+ * SNIHostName.createSNIMatcher("www\\.example\\.(com|org)");
+ * </pre>
+ * will accept hostnames "www.example.com" and "www.example.org".
+ *
+ * @param regex
+ * the <a href="{@docRoot}/java/util/regex/Pattern.html#sum">
+ * regular expression pattern</a>
+ * representing the hostname(s) to match
+ * @return a {@code SNIMatcher} object for {@code SNIHostName}s
+ * @throws NullPointerException if {@code regex} is
+ * {@code null}
+ * @throws java.util.regex.PatternSyntaxException if the regular expression's
+ * syntax is invalid
+ */
+ public static SNIMatcher createSNIMatcher(String regex) {
+ if (regex == null) {
+ throw new NullPointerException(
+ "The regular expression cannot be null");
+ }
+
+ return new SNIHostNameMatcher(regex);
+ }
+
+ // check the validity of the string hostname
+ private void checkHostName() {
+ if (hostname.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Server name value of host_name cannot be empty");
+ }
+
+ if (hostname.endsWith(".")) {
+ throw new IllegalArgumentException(
+ "Server name value of host_name cannot have the trailing dot");
+ }
+ }
+
+ private final static class SNIHostNameMatcher extends SNIMatcher {
+
+ // the compiled representation of a regular expression.
+ private final Pattern pattern;
+
+ /**
+ * Creates an SNIHostNameMatcher object.
+ *
+ * @param regex
+ * the <a href="{@docRoot}/java/util/regex/Pattern.html#sum">
+ * regular expression pattern</a>
+ * representing the hostname(s) to match
+ * @throws NullPointerException if {@code regex} is
+ * {@code null}
+ * @throws PatternSyntaxException if the regular expression's syntax
+ * is invalid
+ */
+ SNIHostNameMatcher(String regex) {
+ super(StandardConstants.SNI_HOST_NAME);
+ pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
+ }
+
+ /**
+ * Attempts to match the given {@link SNIServerName}.
+ *
+ * @param serverName
+ * the {@link SNIServerName} instance on which this matcher
+ * performs match operations
+ *
+ * @return {@code true} if, and only if, the matcher matches the
+ * given {@code serverName}
+ *
+ * @throws NullPointerException if {@code serverName} is {@code null}
+ * @throws IllegalArgumentException if {@code serverName} is
+ * not of {@code StandardConstants#SNI_HOST_NAME} type
+ *
+ * @see SNIServerName
+ */
+ @Override
+ public boolean matches(SNIServerName serverName) {
+ if (serverName == null) {
+ throw new NullPointerException(
+ "The SNIServerName argument cannot be null");
+ }
+
+ SNIHostName hostname;
+ if (!(serverName instanceof SNIHostName)) {
+ if (serverName.getType() != StandardConstants.SNI_HOST_NAME) {
+ throw new IllegalArgumentException(
+ "The server name type is not host_name");
+ }
+
+ try {
+ hostname = new SNIHostName(serverName.getEncoded());
+ } catch (NullPointerException | IllegalArgumentException e) {
+ return false;
+ }
+ } else {
+ hostname = (SNIHostName)serverName;
+ }
+
+ // Let's first try the ascii name matching
+ String asciiName = hostname.getAsciiName();
+ if (pattern.matcher(asciiName).matches()) {
+ return true;
+ }
+
+ // May be an internationalized domain name, check the Unicode
+ // representations.
+ return pattern.matcher(IDN.toUnicode(asciiName)).matches();
+ }
+ }
+}