From b96141a0e46ce6e769c70565bec7ec3fdd58d717 Mon Sep 17 00:00:00 2001 From: Kenny Root Date: Fri, 15 Jan 2016 14:01:49 -0800 Subject: Add SNI support to TLS sockets Pull in changes that relate to SNI on TLS sockets. Change-Id: I23b85308ac25fd00307d37cff1d93ee6c2bfba98 --- .../libcore/javax/net/ssl/SSLParametersTest.java | 91 +++++ .../java/libcore/javax/net/ssl/SSLSocketTest.java | 44 +++ .../java/javax/net/ssl/ExtendedSSLSession.java | 32 ++ .../src/main/java/javax/net/ssl/SNIHostName.java | 396 +++++++++++++++++++++ ojluni/src/main/java/javax/net/ssl/SNIMatcher.java | 105 ++++++ .../src/main/java/javax/net/ssl/SNIServerName.java | 212 +++++++++++ .../src/main/java/javax/net/ssl/SSLParameters.java | 191 +++++++++- .../main/java/javax/net/ssl/StandardConstants.java | 56 +++ openjdk_java_files.mk | 4 + 9 files changed, 1126 insertions(+), 5 deletions(-) create mode 100644 ojluni/src/main/java/javax/net/ssl/SNIHostName.java create mode 100644 ojluni/src/main/java/javax/net/ssl/SNIMatcher.java create mode 100644 ojluni/src/main/java/javax/net/ssl/SNIServerName.java create mode 100644 ojluni/src/main/java/javax/net/ssl/StandardConstants.java diff --git a/luni/src/test/java/libcore/javax/net/ssl/SSLParametersTest.java b/luni/src/test/java/libcore/javax/net/ssl/SSLParametersTest.java index 46d0b416200..03d697e5209 100644 --- a/luni/src/test/java/libcore/javax/net/ssl/SSLParametersTest.java +++ b/luni/src/test/java/libcore/javax/net/ssl/SSLParametersTest.java @@ -16,7 +16,13 @@ package libcore.javax.net.ssl; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import javax.net.ssl.SNIHostName; +import javax.net.ssl.SNIMatcher; +import javax.net.ssl.SNIServerName; import javax.net.ssl.SSLParameters; import junit.framework.TestCase; @@ -105,4 +111,89 @@ public class SSLParametersTest extends TestCase { assertTrue(p.getWantClientAuth()); assertFalse(p.getNeedClientAuth()); } + + public void test_SSLParameters_setServerNames_duplicatedNameThrows() throws Exception { + SSLParameters p = new SSLParameters(); + ArrayList dupeNames = new ArrayList(); + dupeNames.add((SNIServerName) new SNIHostName("www.example.com")); + dupeNames.add((SNIServerName) new SNIHostName("www.example.com")); + try { + p.setServerNames(dupeNames); + fail("Should throw IllegalArgumentException when names are duplicated"); + } catch (IllegalArgumentException expected) { + } + } + + public void test_SSLParameters_setServerNames_setNull_getNull() throws Exception { + SSLParameters p = new SSLParameters(); + p.setServerNames(Collections.singletonList( + (SNIServerName) new SNIHostName("www.example.com"))); + assertNotNull(p.getServerNames()); + p.setServerNames(null); + assertNull(p.getServerNames()); + } + + public void test_SSLParameters_setServerNames_setEmpty_getEmpty() throws Exception { + SSLParameters p = new SSLParameters(); + p.setServerNames(new ArrayList()); + Collection actual = p.getServerNames(); + assertNotNull(actual); + assertEquals(0, actual.size()); + } + + public void test_SSLParameters_getServerNames_unmodifiable() throws Exception { + SSLParameters p = new SSLParameters(); + p.setServerNames(Collections.singletonList( + (SNIServerName) new SNIHostName("www.example.com"))); + Collection actual = p.getServerNames(); + try { + actual.add((SNIServerName) new SNIHostName("www.foo.com")); + fail("Should not allow modifications to the list"); + } catch (UnsupportedOperationException expected) { + } + } + + public void test_SSLParameters_setSNIMatchers_duplicatedNameThrows() throws Exception { + SSLParameters p = new SSLParameters(); + ArrayList dupeMatchers = new ArrayList(); + dupeMatchers.add(SNIHostName.createSNIMatcher("www\\.example\\.com")); + dupeMatchers.add(SNIHostName.createSNIMatcher("www\\.example\\.com")); + try { + p.setSNIMatchers(dupeMatchers); + fail("Should throw IllegalArgumentException when matchers are duplicated"); + } catch (IllegalArgumentException expected) { + } + } + + public void test_SSLParameters_setSNIMatchers_setNull_getNull() throws Exception { + SSLParameters p = new SSLParameters(); + p.setSNIMatchers(Collections.singletonList( + SNIHostName.createSNIMatcher("www\\.example\\.com"))); + assertNotNull(p.getSNIMatchers()); + p.setSNIMatchers(null); + assertNull(p.getSNIMatchers()); + } + + public void test_SSLParameters_setSNIMatchers_setEmpty_getEmpty() throws Exception { + SSLParameters p = new SSLParameters(); + p.setSNIMatchers(Collections.singletonList( + SNIHostName.createSNIMatcher("www\\.example\\.com"))); + assertEquals(1, p.getSNIMatchers().size()); + p.setSNIMatchers(Collections.emptyList()); + Collection actual = p.getSNIMatchers(); + assertNotNull(actual); + assertEquals(0, actual.size()); + } + + public void test_SSLParameters_getSNIMatchers_unmodifiable() throws Exception { + SSLParameters p = new SSLParameters(); + p.setSNIMatchers(Collections.singletonList( + SNIHostName.createSNIMatcher("www\\.example\\.com"))); + Collection actual = p.getSNIMatchers(); + try { + actual.add(SNIHostName.createSNIMatcher("www\\.google\\.com")); + fail("Should not allow modification of list"); + } catch (UnsupportedOperationException expected) { + } + } } diff --git a/luni/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java b/luni/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java index e29e503d363..278d44e3787 100644 --- a/luni/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java +++ b/luni/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java @@ -39,6 +39,7 @@ import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; @@ -50,9 +51,13 @@ import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import javax.net.ServerSocketFactory; import javax.net.SocketFactory; +import javax.net.ssl.ExtendedSSLSession; import javax.net.ssl.HandshakeCompletedEvent; import javax.net.ssl.HandshakeCompletedListener; import javax.net.ssl.KeyManager; +import javax.net.ssl.SNIHostName; +import javax.net.ssl.SNIMatcher; +import javax.net.ssl.SNIServerName; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLException; import javax.net.ssl.SSLHandshakeException; @@ -1772,6 +1777,45 @@ public class SSLSocketTest extends TestCase { } } + public void test_SSLSocket_SNIHostName() throws Exception { + TestSSLContext c = TestSSLContext.create(); + + final SSLSocket client = (SSLSocket) c.clientContext.getSocketFactory().createSocket(); + SSLParameters clientParams = client.getSSLParameters(); + clientParams.setServerNames(Collections.singletonList( + (SNIServerName) new SNIHostName("www.example.com"))); + client.setSSLParameters(clientParams); + + SSLParameters serverParams = c.serverSocket.getSSLParameters(); + serverParams.setSNIMatchers(Collections.singletonList( + SNIHostName.createSNIMatcher("www\\.example\\.com"))); + c.serverSocket.setSSLParameters(serverParams); + + client.connect(new InetSocketAddress(c.host, c.port)); + final SSLSocket server = (SSLSocket) c.serverSocket.accept(); + + ExecutorService executor = Executors.newSingleThreadExecutor(); + Future future = executor.submit(new Callable() { + @Override public Void call() throws Exception { + client.startHandshake(); + return null; + } + }); + executor.shutdown(); + server.startHandshake(); + + SSLSession serverSession = server.getSession(); + assert(serverSession instanceof ExtendedSSLSession); + ExtendedSSLSession extendedServerSession = (ExtendedSSLSession) serverSession; + List requestedNames = extendedServerSession.getRequestedServerNames(); + assertNotNull(requestedNames); + assertEquals(1, requestedNames.size()); + SNIServerName serverName = requestedNames.get(0); + assertTrue(serverName instanceof SNIHostName); + SNIHostName serverHostName = (SNIHostName) serverName; + assertEquals("www.example.com", serverHostName.getAsciiName()); + } + public void test_SSLSocket_sendsTlsFallbackScsv_Fallback_Success() throws Exception { TestSSLContext context = TestSSLContext.create(); diff --git a/ojluni/src/main/java/javax/net/ssl/ExtendedSSLSession.java b/ojluni/src/main/java/javax/net/ssl/ExtendedSSLSession.java index 8afd963458d..70f98ce221d 100755 --- a/ojluni/src/main/java/javax/net/ssl/ExtendedSSLSession.java +++ b/ojluni/src/main/java/javax/net/ssl/ExtendedSSLSession.java @@ -25,6 +25,8 @@ package javax.net.ssl; +import java.util.List; + /** * Extends the SSLSession interface to support additional * session attributes. @@ -83,4 +85,34 @@ public abstract class ExtendedSSLSession implements SSLSession { * @see X509ExtendedKeyManager */ public abstract String[] getPeerSupportedSignatureAlgorithms(); + + /** + * Obtains a {@link List} containing all {@link SNIServerName}s + * of the requested Server Name Indication (SNI) extension. + *

+ * In server mode, unless the return {@link List} is empty, + * the server should use the requested server names to guide its + * selection of an appropriate authentication certificate, and/or + * other aspects of security policy. + *

+ * In client mode, unless the return {@link List} is empty, + * the client should use the requested server names to guide its + * endpoint identification of the peer's identity, and/or + * other aspects of security policy. + * + * @return a non-null immutable list of {@link SNIServerName}s of the + * requested server name indications. The returned list may be + * empty if no server name indications were requested. + * @throws UnsupportedOperationException if the underlying provider + * does not implement the operation + * + * @see SNIServerName + * @see X509ExtendedTrustManager + * @see X509ExtendedKeyManager + * + * @since 1.8 + */ + public List getRequestedServerNames() { + throw new UnsupportedOperationException(); + } } 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. + *

+ * As described in section 3, "Server Name Indication", of + * TLS Extensions (RFC 6066), + * "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 RFC 5890. + *

+ * 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. + *

+ * Note that per RFC 6066, + * 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 + * RFC 3490, + * RFC 1122, + * RFC 1123) and + * translate the {@code hostname} into ASCII Compatible Encoding (ACE), as: + *

+     *     IDN.toASCII(hostname, IDN.USE_STD3_ASCII_RULES);
+     * 
+ *

+ * The {@code hostname} argument is illegal if it: + *

    + *
  • {@code hostname} is empty,
  • + *
  • {@code hostname} ends with a trailing dot,
  • + *
  • {@code hostname} is not a valid Internationalized + * Domain Name (IDN) compliant with the RFC 3490 specification.
  • + *
+ * @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. + *

+ * This method is normally used to parse the encoded name value in a + * requested SNI extension. + *

+ * Per RFC 6066, + * the encoded name value of a hostname is + * {@link StandardCharsets#US_ASCII}-compliant. However, in the previous + * version of the SNI extension ( + * RFC 4366), + * 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. + *

+ * 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 RFC 6066, + * please always use {@link StandardCharsets#US_ASCII}-compliant charset + * and enforce the restrictions on ASCII characters in hostnames (see + * RFC 3490, + * RFC 1122, + * RFC 1123) + * for {@code encoded} argument, or use + * {@link SNIHostName#SNIHostName(String)} instead. + *

+ * The {@code encoded} argument is illegal if it: + *

    + *
  • {@code encoded} is empty,
  • + *
  • {@code encoded} ends with a trailing dot,
  • + *
  • {@code encoded} is not encoded in + * {@link StandardCharsets#US_ASCII} or + * {@link StandardCharsets#UTF_8}-compliant charset,
  • + *
  • {@code encoded} is not a valid Internationalized + * Domain Name (IDN) compliant with the RFC 3490 specification.
  • + *
+ * + *

+ * 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. + *

+ * Note that, per + * RFC 6066, the + * returned hostname may be an internationalized domain name that + * contains A-labels. See + * RFC 5890 + * 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. + *

+ * Per RFC 6066, 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}. + *

+ * 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. + *

+ * The exact details of the representation are unspecified and subject + * to change, but the following may be regarded as typical: + *

+     *     "type=host_name (0), value={@literal }"
+     * 
+ * The "{@literal }" is an ASCII representation of the hostname, + * which may contains A-labels. For example, a returned value of an pseudo + * hostname may look like: + *
+     *     "type=host_name (0), value=www.example.com"
+     * 
+ * or + *
+     *     "type=host_name (0), value=xn--fsqu00a.xn--0zwm56d"
+     * 
+ *

+ * 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. + *

+ * This method can be used by a server to verify the acceptable + * {@code SNIHostName}s. For example, + *

+     *     SNIMatcher matcher =
+     *         SNIHostName.createSNIMatcher("www\\.example\\.com");
+     * 
+ * will accept the hostname "www.example.com". + *
+     *     SNIMatcher matcher =
+     *         SNIHostName.createSNIMatcher("www\\.example\\.(com|org)");
+     * 
+ * will accept hostnames "www.example.com" and "www.example.org". + * + * @param regex + * the + * regular expression pattern + * 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 + * regular expression pattern + * 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(); + } + } +} diff --git a/ojluni/src/main/java/javax/net/ssl/SNIMatcher.java b/ojluni/src/main/java/javax/net/ssl/SNIMatcher.java new file mode 100644 index 00000000000..a6c9ab92ee2 --- /dev/null +++ b/ojluni/src/main/java/javax/net/ssl/SNIMatcher.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2012, 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; + +/** + * Instances of this class represent a matcher that performs match + * operations on an {@link SNIServerName} instance. + *

+ * Servers can use Server Name Indication (SNI) information to decide if + * specific {@link SSLSocket} or {@link SSLEngine} instances should accept + * a connection. For example, when multiple "virtual" or "name-based" + * servers are hosted on a single underlying network address, the server + * application can use SNI information to determine whether this server is + * the exact server that the client wants to access. Instances of this + * class can be used by a server to verify the acceptable server names of + * a particular type, such as host names. + *

+ * {@code SNIMatcher} objects are immutable. Subclasses should not provide + * methods that can change the state of an instance once it has been created. + * + * @see SNIServerName + * @see SNIHostName + * @see SSLParameters#getSNIMatchers() + * @see SSLParameters#setSNIMatchers(Collection) + * + * @since 1.8 + */ +public abstract class SNIMatcher { + + // the type of the server name that this matcher performs on + private final int type; + + /** + * Creates an {@code SNIMatcher} using the specified server name type. + * + * @param type + * the type of the server name that this matcher performs on + * + * @throws IllegalArgumentException if {@code type} is not in the range + * of 0 to 255, inclusive. + */ + protected SNIMatcher(int type) { + if (type < 0) { + throw new IllegalArgumentException( + "Server name type cannot be less than zero"); + } else if (type > 255) { + throw new IllegalArgumentException( + "Server name type cannot be greater than 255"); + } + + this.type = type; + } + + /** + * Returns the server name type of this {@code SNIMatcher} object. + * + * @return the server name type of this {@code SNIMatcher} object. + * + * @see SNIServerName + */ + public final int getType() { + return type; + } + + /** + * 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 the given server name type of this matcher + * + * @see SNIServerName + */ + public abstract boolean matches(SNIServerName serverName); +} diff --git a/ojluni/src/main/java/javax/net/ssl/SNIServerName.java b/ojluni/src/main/java/javax/net/ssl/SNIServerName.java new file mode 100644 index 00000000000..c5bb2536670 --- /dev/null +++ b/ojluni/src/main/java/javax/net/ssl/SNIServerName.java @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2012, 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.util.Arrays; + +/** + * Instances of this class represent a server name in a Server Name + * Indication (SNI) extension. + *

+ * The SNI extension is a feature that extends the SSL/TLS protocols to + * indicate what server name the client is attempting to connect to during + * handshaking. See section 3, "Server Name Indication", of TLS Extensions (RFC 6066). + *

+ * {@code SNIServerName} objects are immutable. Subclasses should not provide + * methods that can change the state of an instance once it has been created. + * + * @see SSLParameters#getServerNames() + * @see SSLParameters#setServerNames(List) + * + * @since 1.8 + */ +public abstract class SNIServerName { + + // the type of the server name + private final int type; + + // the encoded value of the server name + private final byte[] encoded; + + // the hex digitals + private static final char[] HEXES = "0123456789ABCDEF".toCharArray(); + + /** + * Creates an {@code SNIServerName} using the specified name type and + * encoded value. + *

+ * Note that the {@code encoded} byte array is cloned to protect against + * subsequent modification. + * + * @param type + * the type of the server name + * @param encoded + * the encoded value of the server name + * + * @throws IllegalArgumentException if {@code type} is not in the range + * of 0 to 255, inclusive. + * @throws NullPointerException if {@code encoded} is null + */ + protected SNIServerName(int type, byte[] encoded) { + if (type < 0) { + throw new IllegalArgumentException( + "Server name type cannot be less than zero"); + } else if (type > 255) { + throw new IllegalArgumentException( + "Server name type cannot be greater than 255"); + } + this.type = type; + + if (encoded == null) { + throw new NullPointerException( + "Server name encoded value cannot be null"); + } + this.encoded = encoded.clone(); + } + + + /** + * Returns the name type of this server name. + * + * @return the name type of this server name + */ + public final int getType() { + return type; + } + + /** + * Returns a copy of the encoded server name value of this server name. + * + * @return a copy of the encoded server name value of this server name + */ + public final byte[] getEncoded() { + return encoded.clone(); + } + + /** + * Indicates whether some other object is "equal to" this server name. + * + * @return true if, and only if, {@code other} is of the same class + * of this object, and has the same name type and + * encoded value as this server name. + */ + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (this.getClass() != other.getClass()) { + return false; + } + + SNIServerName that = (SNIServerName)other; + return (this.type == that.type) && + Arrays.equals(this.encoded, that.encoded); + } + + /** + * Returns a hash code value for this server name. + *

+ * The hash code value is generated using the name type and encoded + * value of this server name. + * + * @return a hash code value for this server name. + */ + @Override + public int hashCode() { + int result = 17; // 17/31: prime number to decrease collisions + result = 31 * result + type; + result = 31 * result + Arrays.hashCode(encoded); + + return result; + } + + /** + * Returns a string representation of this server name, including the server + * name type and the encoded server name value in this + * {@code SNIServerName} object. + *

+ * The exact details of the representation are unspecified and subject + * to change, but the following may be regarded as typical: + *

+     *     "type={@literal }, value={@literal }"
+     * 
+ *

+ * In this class, the format of "{@literal }" is + * "[LITERAL] (INTEGER)", where the optional "LITERAL" is the literal + * name, and INTEGER is the integer value of the name type. The format + * of "{@literal }" is "XX:...:XX", where "XX" is the + * hexadecimal digit representation of a byte value. For example, a + * returned value of an pseudo server name may look like: + *

+     *     "type=(31), value=77:77:77:2E:65:78:61:6D:70:6C:65:2E:63:6E"
+     * 
+ * or + *
+     *     "type=host_name (0), value=77:77:77:2E:65:78:61:6D:70:6C:65:2E:63:6E"
+     * 
+ * + *

+ * Please NOTE that the exact details of the representation are unspecified + * and subject to change, and subclasses may override the method with + * their own formats. + * + * @return a string representation of this server name + */ + @Override + public String toString() { + if (type == StandardConstants.SNI_HOST_NAME) { + return "type=host_name (0), value=" + toHexString(encoded); + } else { + return "type=(" + type + "), value=" + toHexString(encoded); + } + } + + // convert byte array to hex string + private static String toHexString(byte[] bytes) { + if (bytes.length == 0) { + return "(empty)"; + } + + StringBuilder sb = new StringBuilder(bytes.length * 3 - 1); + boolean isInitial = true; + for (byte b : bytes) { + if (isInitial) { + isInitial = false; + } else { + sb.append(':'); + } + + int k = b & 0xFF; + sb.append(HEXES[k >>> 4]); + sb.append(HEXES[k & 0xF]); + } + + return sb.toString(); + } +} diff --git a/ojluni/src/main/java/javax/net/ssl/SSLParameters.java b/ojluni/src/main/java/javax/net/ssl/SSLParameters.java index 0cb5b7d424c..90132ada341 100755 --- a/ojluni/src/main/java/javax/net/ssl/SSLParameters.java +++ b/ojluni/src/main/java/javax/net/ssl/SSLParameters.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 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 @@ -26,13 +26,21 @@ package javax.net.ssl; import java.security.AlgorithmConstraints; +import java.util.Map; +import java.util.List; +import java.util.HashMap; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; /** * Encapsulates parameters for an SSL/TLS connection. The parameters * are the list of ciphersuites to be accepted in an SSL/TLS handshake, * the list of protocols to be allowed, the endpoint identification - * algorithm during SSL/TLS handshaking, the algorithm constraints and - * whether SSL/TLS servers should request or require client authentication. + * algorithm during SSL/TLS handshaking, the Server Name Indication (SNI), + * the algorithm constraints and whether SSL/TLS servers should request + * or require client authentication, etc. *

* SSLParameters can be created via the constructors in this class. * Objects can also be obtained using the getSSLParameters() @@ -47,7 +55,7 @@ import java.security.AlgorithmConstraints; * SSLParameters can be applied to a connection via the methods * {@link SSLSocket#setSSLParameters SSLSocket.setSSLParameters()} and * {@link SSLServerSocket#setSSLParameters SSLServerSocket.setSSLParameters()} - * and {@link SSLEngine#setSSLParameters SSLEngine.getSSLParameters()}. + * and {@link SSLEngine#setSSLParameters SSLEngine.setSSLParameters()}. * * @see SSLSocket * @see SSLEngine @@ -63,11 +71,16 @@ public class SSLParameters { private boolean needClientAuth; private String identificationAlgorithm; private AlgorithmConstraints algorithmConstraints; + private Map sniNames = null; + private Map sniMatchers = null; + private boolean preferLocalCipherSuites; /** * Constructs SSLParameters. *

- * The cipherSuites and protocols values are set to null, + * The values of cipherSuites, protocols, cryptographic algorithm + * constraints, endpoint identification algorithm, server names and + * server name matchers are set to null, useCipherSuitesOrder, * wantClientAuth and needClientAuth are set to false. */ public SSLParameters() { @@ -254,4 +267,172 @@ public class SSLParameters { this.identificationAlgorithm = algorithm; } + /** + * Sets the desired {@link SNIServerName}s of the Server Name + * Indication (SNI) parameter. + *

+ * This method is only useful to {@link SSLSocket}s or {@link SSLEngine}s + * operating in client mode. + *

+ * Note that the {@code serverNames} list is cloned + * to protect against subsequent modification. + * + * @param serverNames + * the list of desired {@link SNIServerName}s (or null) + * + * @throws NullPointerException if the {@code serverNames} + * contains {@code null} element + * @throws IllegalArgumentException if the {@code serverNames} + * contains more than one name of the same name type + * + * @see SNIServerName + * @see #getServerNames() + * + * @since 1.8 + */ + public final void setServerNames(List serverNames) { + if (serverNames != null) { + if (!serverNames.isEmpty()) { + sniNames = new LinkedHashMap<>(serverNames.size()); + for (SNIServerName serverName : serverNames) { + if (sniNames.put(serverName.getType(), + serverName) != null) { + throw new IllegalArgumentException( + "Duplicated server name of type " + + serverName.getType()); + } + } + } else { + sniNames = Collections.emptyMap(); + } + } else { + sniNames = null; + } + } + + /** + * Returns a {@link List} containing all {@link SNIServerName}s of the + * Server Name Indication (SNI) parameter, or null if none has been set. + *

+ * This method is only useful to {@link SSLSocket}s or {@link SSLEngine}s + * operating in client mode. + *

+ * For SSL/TLS connections, the underlying SSL/TLS provider + * may specify a default value for a certain server name type. In + * client mode, it is recommended that, by default, providers should + * include the server name indication whenever the server can be located + * by a supported server name type. + *

+ * It is recommended that providers initialize default Server Name + * Indications when creating {@code SSLSocket}/{@code SSLEngine}s. + * In the following examples, the server name could be represented by an + * instance of {@link SNIHostName} which has been initialized with the + * hostname "www.example.com" and type + * {@link StandardConstants#SNI_HOST_NAME}. + * + *

+     *     Socket socket =
+     *         sslSocketFactory.createSocket("www.example.com", 443);
+     * 
+ * or + *
+     *     SSLEngine engine =
+     *         sslContext.createSSLEngine("www.example.com", 443);
+     * 
+ *

+ * + * @return null or an immutable list of non-null {@link SNIServerName}s + * + * @see List + * @see #setServerNames(List) + * + * @since 1.8 + */ + public final List getServerNames() { + if (sniNames != null) { + if (!sniNames.isEmpty()) { + return Collections.unmodifiableList( + new ArrayList<>(sniNames.values())); + } else { + return Collections.emptyList(); + } + } + + return null; + } + + /** + * Sets the {@link SNIMatcher}s of the Server Name Indication (SNI) + * parameter. + *

+ * This method is only useful to {@link SSLSocket}s or {@link SSLEngine}s + * operating in server mode. + *

+ * Note that the {@code matchers} collection is cloned to protect + * against subsequent modification. + * + * @param matchers + * the collection of {@link SNIMatcher}s (or null) + * + * @throws NullPointerException if the {@code matchers} + * contains {@code null} element + * @throws IllegalArgumentException if the {@code matchers} + * contains more than one name of the same name type + * + * @see Collection + * @see SNIMatcher + * @see #getSNIMatchers() + * + * @since 1.8 + */ + public final void setSNIMatchers(Collection matchers) { + if (matchers != null) { + if (!matchers.isEmpty()) { + sniMatchers = new HashMap<>(matchers.size()); + for (SNIMatcher matcher : matchers) { + if (sniMatchers.put(matcher.getType(), + matcher) != null) { + throw new IllegalArgumentException( + "Duplicated server name of type " + + matcher.getType()); + } + } + } else { + sniMatchers = Collections.emptyMap(); + } + } else { + sniMatchers = null; + } + } + + /** + * Returns a {@link Collection} containing all {@link SNIMatcher}s of the + * Server Name Indication (SNI) parameter, or null if none has been set. + *

+ * This method is only useful to {@link SSLSocket}s or {@link SSLEngine}s + * operating in server mode. + *

+ * For better interoperability, providers generally will not define + * default matchers so that by default servers will ignore the SNI + * extension and continue the handshake. + * + * @return null or an immutable collection of non-null {@link SNIMatcher}s + * + * @see SNIMatcher + * @see #setSNIMatchers(Collection) + * + * @since 1.8 + */ + public final Collection getSNIMatchers() { + if (sniMatchers != null) { + if (!sniMatchers.isEmpty()) { + return Collections.unmodifiableList( + new ArrayList<>(sniMatchers.values())); + } else { + return Collections.emptyList(); + } + } + + return null; + } } diff --git a/ojluni/src/main/java/javax/net/ssl/StandardConstants.java b/ojluni/src/main/java/javax/net/ssl/StandardConstants.java new file mode 100644 index 00000000000..8e1df977b97 --- /dev/null +++ b/ojluni/src/main/java/javax/net/ssl/StandardConstants.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2012, 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; + +/** + * Standard constants definitions + * + * @since 1.8 + */ +public final class StandardConstants { + + // Suppress default constructor for noninstantiability + private StandardConstants() { + throw new AssertionError( + "No javax.net.ssl.StandardConstants instances for you!"); + } + + /** + * The "host_name" type representing of a DNS hostname + * (see {@link SNIHostName}) in a Server Name Indication (SNI) extension. + *

+ * The SNI extension is a feature that extends the SSL/TLS protocols to + * indicate what server name the client is attempting to connect to during + * handshaking. See section 3, "Server Name Indication", of TLS Extensions (RFC 6066). + *

+ * The value of this constant is {@value}. + * + * @see SNIServerName + * @see SNIHostName + */ + public static final int SNI_HOST_NAME = 0x00; +} diff --git a/openjdk_java_files.mk b/openjdk_java_files.mk index d6cb106e92c..cfa9cf828c1 100644 --- a/openjdk_java_files.mk +++ b/openjdk_java_files.mk @@ -886,6 +886,9 @@ openjdk_javadoc_files := \ ojluni/src/main/java/javax/net/ssl/KeyManager.java \ ojluni/src/main/java/javax/net/ssl/KeyStoreBuilderParameters.java \ ojluni/src/main/java/javax/net/ssl/ManagerFactoryParameters.java \ + ojluni/src/main/java/javax/net/ssl/SNIHostName.java \ + ojluni/src/main/java/javax/net/ssl/SNIMatcher.java \ + ojluni/src/main/java/javax/net/ssl/SNIServerName.java \ ojluni/src/main/java/javax/net/ssl/SSLContext.java \ ojluni/src/main/java/javax/net/ssl/SSLContextSpi.java \ ojluni/src/main/java/javax/net/ssl/SSLEngine.java \ @@ -905,6 +908,7 @@ openjdk_javadoc_files := \ ojluni/src/main/java/javax/net/ssl/SSLSession.java \ ojluni/src/main/java/javax/net/ssl/SSLSocketFactory.java \ ojluni/src/main/java/javax/net/ssl/SSLSocket.java \ + ojluni/src/main/java/javax/net/ssl/StandardConstants.java \ ojluni/src/main/java/javax/net/ssl/TrustManagerFactory.java \ ojluni/src/main/java/javax/net/ssl/TrustManagerFactorySpi.java \ ojluni/src/main/java/javax/net/ssl/TrustManager.java \ -- cgit v1.2.3