diff --git a/jdk/src/share/classes/javax/net/ssl/ExtendedSSLSession.java b/jdk/src/share/classes/javax/net/ssl/ExtendedSSLSession.java index 8afd963458d..70f98ce221d 100644 --- a/jdk/src/share/classes/javax/net/ssl/ExtendedSSLSession.java +++ b/jdk/src/share/classes/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/jdk/src/share/classes/javax/net/ssl/HandshakeCompletedEvent.java b/jdk/src/share/classes/javax/net/ssl/HandshakeCompletedEvent.java index b9fe920f265..39e802c4d86 100644 --- a/jdk/src/share/classes/javax/net/ssl/HandshakeCompletedEvent.java +++ b/jdk/src/share/classes/javax/net/ssl/HandshakeCompletedEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2003, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 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 @@ -186,8 +186,7 @@ public class HandshakeCompletedEvent extends EventObject // if the provider does not support it, fallback to peer certs. // return the X500Principal of the end-entity cert. Certificate[] certs = getPeerCertificates(); - principal = (X500Principal) - ((X509Certificate)certs[0]).getSubjectX500Principal(); + principal = ((X509Certificate)certs[0]).getSubjectX500Principal(); } return principal; } @@ -216,7 +215,7 @@ public class HandshakeCompletedEvent extends EventObject // return the X500Principal of the end-entity cert. Certificate[] certs = getLocalCertificates(); if (certs != null) { - principal = (X500Principal) + principal = ((X509Certificate)certs[0]).getSubjectX500Principal(); } } diff --git a/jdk/src/share/classes/javax/net/ssl/SNIHostName.java b/jdk/src/share/classes/javax/net/ssl/SNIHostName.java new file mode 100644 index 00000000000..6f2092c662f --- /dev/null +++ b/jdk/src/share/classes/javax/net/ssl/SNIHostName.java @@ -0,0 +1,394 @@ +/* + * 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.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: + *

+ * @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(String)} instead. + *

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

+ * + *

+ * 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 + * @throws NullPointerException if {@code regex} is + * {@code null} + * @throws 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/jdk/src/share/classes/javax/net/ssl/SNIMatcher.java b/jdk/src/share/classes/javax/net/ssl/SNIMatcher.java new file mode 100644 index 00000000000..5025b99ed79 --- /dev/null +++ b/jdk/src/share/classes/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/jdk/src/share/classes/javax/net/ssl/SNIServerName.java b/jdk/src/share/classes/javax/net/ssl/SNIServerName.java new file mode 100644 index 00000000000..afc2286d761 --- /dev/null +++ b/jdk/src/share/classes/javax/net/ssl/SNIServerName.java @@ -0,0 +1,213 @@ +/* + * 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/jdk/src/share/classes/javax/net/ssl/SSLEngine.java b/jdk/src/share/classes/javax/net/ssl/SSLEngine.java index 411626cd7a1..433361c6a6b 100644 --- a/jdk/src/share/classes/javax/net/ssl/SSLEngine.java +++ b/jdk/src/share/classes/javax/net/ssl/SSLEngine.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 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 @@ -1214,15 +1214,19 @@ public abstract class SSLEngine { * *

This means: *

* * @param params the parameters diff --git a/jdk/src/share/classes/javax/net/ssl/SSLParameters.java b/jdk/src/share/classes/javax/net/ssl/SSLParameters.java index 0cb5b7d424c..18a1f97040f 100644 --- a/jdk/src/share/classes/javax/net/ssl/SSLParameters.java +++ b/jdk/src/share/classes/javax/net/ssl/SSLParameters.java @@ -26,13 +26,23 @@ package javax.net.ssl; import java.security.AlgorithmConstraints; +import java.util.Map; +import java.util.List; +import java.util.HashSet; +import java.util.HashMap; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.regex.Pattern; /** * 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. *

* SSLParameters can be created via the constructors in this class. * Objects can also be obtained using the getSSLParameters() @@ -47,7 +57,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 +73,15 @@ public class SSLParameters { private boolean needClientAuth; private String identificationAlgorithm; private AlgorithmConstraints algorithmConstraints; + private Map sniNames = null; + private Map sniMatchers = null; /** * 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, * wantClientAuth and needClientAuth are set to false. */ public SSLParameters() { @@ -254,4 +268,173 @@ 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 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 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 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 Collection getSNIMatchers() { + if (sniMatchers != null) { + if (!sniMatchers.isEmpty()) { + return Collections.unmodifiableList( + new ArrayList<>(sniMatchers.values())); + } else { + return Collections.emptyList(); + } + } + + return null; + } } + diff --git a/jdk/src/share/classes/javax/net/ssl/SSLServerSocket.java b/jdk/src/share/classes/javax/net/ssl/SSLServerSocket.java index d5bbaaf5da5..48dc7facd44 100644 --- a/jdk/src/share/classes/javax/net/ssl/SSLServerSocket.java +++ b/jdk/src/share/classes/javax/net/ssl/SSLServerSocket.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 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 @@ -484,15 +484,19 @@ public abstract class SSLServerSocket extends ServerSocket { * *

This means: *

* * @param params the parameters diff --git a/jdk/src/share/classes/javax/net/ssl/SSLSocket.java b/jdk/src/share/classes/javax/net/ssl/SSLSocket.java index b61f94a5a75..ab090130f2d 100644 --- a/jdk/src/share/classes/javax/net/ssl/SSLSocket.java +++ b/jdk/src/share/classes/javax/net/ssl/SSLSocket.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 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 @@ -626,15 +626,19 @@ public abstract class SSLSocket extends Socket * *

This means: *

* * @param params the parameters diff --git a/jdk/src/share/classes/javax/net/ssl/SSLSocketFactory.java b/jdk/src/share/classes/javax/net/ssl/SSLSocketFactory.java index c3d33c34f70..73f48d197a4 100644 --- a/jdk/src/share/classes/javax/net/ssl/SSLSocketFactory.java +++ b/jdk/src/share/classes/javax/net/ssl/SSLSocketFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 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 @@ -29,6 +29,7 @@ package javax.net.ssl; import java.net.*; import javax.net.SocketFactory; import java.io.IOException; +import java.io.InputStream; import java.security.*; import java.util.Locale; @@ -180,8 +181,55 @@ public abstract class SSLSocketFactory extends SocketFactory * @throws NullPointerException if the parameter s is null */ public abstract Socket createSocket(Socket s, String host, - int port, boolean autoClose) - throws IOException; + int port, boolean autoClose) throws IOException; + + /** + * Creates a server mode {@link Socket} layered over an + * existing connected socket, and is able to read data which has + * already been consumed/removed from the {@link Socket}'s + * underlying {@link InputStream}. + *

+ * This method can be used by a server application that needs to + * observe the inbound data but still create valid SSL/TLS + * connections: for example, inspection of Server Name Indication + * (SNI) extensions (See section 3 of TLS Extensions + * (RFC6066)). Data that has been already removed from the + * underlying {@link InputStream} should be loaded into the + * {@code consumed} stream before this method is called, perhaps + * using a {@link ByteArrayInputStream}. When this {@link Socket} + * begins handshaking, it will read all of the data in + * {@code consumed} until it reaches {@code EOF}, then all further + * data is read from the underlying {@link InputStream} as + * usual. + *

+ * The returned socket is configured using the socket options + * established for this factory, and is set to use server mode when + * handshaking (see {@link SSLSocket#setUseClientMode(boolean)}). + * + * @param s + * the existing socket + * @param consumed + * the consumed inbound network data that has already been + * removed from the existing {@link Socket} + * {@link InputStream}. This parameter may be + * {@code null} if no data has been removed. + * @param autoClose close the underlying socket when this socket is closed. + * + * @return the {@link Socket} compliant with the socket options + * established for this factory + * + * @throws IOException if an I/O error occurs when creating the socket + * @throws UnsupportedOperationException if the underlying provider + * does not implement the operation + * @throws NullPointerException if {@code s} is {@code null} + * + * @since 1.8 + */ + public Socket createSocket(Socket s, InputStream consumed, + boolean autoClose) throws IOException { + throw new UnsupportedOperationException(); + } } diff --git a/jdk/src/share/classes/javax/net/ssl/StandardConstants.java b/jdk/src/share/classes/javax/net/ssl/StandardConstants.java new file mode 100644 index 00000000000..8e1df977b97 --- /dev/null +++ b/jdk/src/share/classes/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/jdk/src/share/classes/sun/security/ssl/BaseSSLSocketImpl.java b/jdk/src/share/classes/sun/security/ssl/BaseSSLSocketImpl.java index ef7503b9322..1542585ebe8 100644 --- a/jdk/src/share/classes/sun/security/ssl/BaseSSLSocketImpl.java +++ b/jdk/src/share/classes/sun/security/ssl/BaseSSLSocketImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2008, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 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 @@ -23,7 +23,6 @@ * questions. */ - package sun.security.ssl; import java.io.*; @@ -55,16 +54,25 @@ abstract class BaseSSLSocketImpl extends SSLSocket { * recurse infinitely ... e.g. close() calling itself, or doing * I/O in terms of our own streams. */ - final Socket self; + final private Socket self; + final private InputStream consumedInput; BaseSSLSocketImpl() { super(); this.self = this; + this.consumedInput = null; } BaseSSLSocketImpl(Socket socket) { super(); this.self = socket; + this.consumedInput = null; + } + + BaseSSLSocketImpl(Socket socket, InputStream consumed) { + super(); + this.self = socket; + this.consumedInput = consumed; } // @@ -541,4 +549,57 @@ abstract class BaseSSLSocketImpl extends SSLSocket { } } + @Override + public String toString() { + if (self == this) { + return super.toString(); + } + + return self.toString(); + } + + @Override + public InputStream getInputStream() throws IOException { + if (self == this) { + return super.getInputStream(); + } + + if (consumedInput != null) { + return new SequenceInputStream(consumedInput, + self.getInputStream()); + } + + return self.getInputStream(); + } + + @Override + public OutputStream getOutputStream() throws IOException { + if (self == this) { + return super.getOutputStream(); + } + + return self.getOutputStream(); + } + + @Override + public synchronized void close() throws IOException { + if (self == this) { + super.close(); + } else { + self.close(); + } + } + + @Override + public synchronized void setSoTimeout(int timeout) throws SocketException { + if (self == this) { + super.setSoTimeout(timeout); + } else { + self.setSoTimeout(timeout); + } + } + + boolean isLayered() { + return (self != this); + } } diff --git a/jdk/src/share/classes/sun/security/ssl/ClientHandshaker.java b/jdk/src/share/classes/sun/security/ssl/ClientHandshaker.java index 9ebeb75c5aa..d18af55b2b0 100644 --- a/jdk/src/share/classes/sun/security/ssl/ClientHandshaker.java +++ b/jdk/src/share/classes/sun/security/ssl/ClientHandshaker.java @@ -48,8 +48,6 @@ import sun.security.ssl.HandshakeMessage.*; import sun.security.ssl.CipherSuite.*; import static sun.security.ssl.CipherSuite.KeyExchange.*; -import sun.net.util.IPAddressUtil; - /** * ClientHandshaker does the protocol handshaking from the point * of view of a client. It is driven asychronously by handshake messages @@ -92,6 +90,9 @@ final class ClientHandshaker extends Handshaker { private final static boolean enableSNIExtension = Debug.getBooleanProperty("jsse.enableSNIExtension", true); + private List requestedServerNames = + Collections.emptyList(); + /* * Constructors */ @@ -579,6 +580,7 @@ final class ClientHandshaker extends Handshaker { session = new SSLSessionImpl(protocolVersion, cipherSuite, getLocalSupportedSignAlgs(), mesg.sessionId, getHostSE(), getPortSE()); + session.setRequestedServerNames(requestedServerNames); setHandshakeSessionSE(session); if (debug != null && Debug.isOn("handshake")) { System.out.println("** " + cipherSuite); @@ -1246,17 +1248,14 @@ final class ClientHandshaker extends Handshaker { // add server_name extension if (enableSNIExtension) { - // We cannot use the hostname resolved from name services. For - // virtual hosting, multiple hostnames may be bound to the same IP - // address, so the hostname resolved from name services is not - // reliable. - String hostname = getRawHostnameSE(); + if (session != null) { + requestedServerNames = session.getRequestedServerNames(); + } else { + requestedServerNames = serverNames; + } - // we only allow FQDN - if (hostname != null && hostname.indexOf('.') > 0 && - !IPAddressUtil.isIPv4LiteralAddress(hostname) && - !IPAddressUtil.isIPv6LiteralAddress(hostname)) { - clientHelloMessage.addServerNameIndicationExtension(hostname); + if (!requestedServerNames.isEmpty()) { + clientHelloMessage.addSNIExtension(requestedServerNames); } } diff --git a/jdk/src/share/classes/sun/security/ssl/HandshakeInStream.java b/jdk/src/share/classes/sun/security/ssl/HandshakeInStream.java index f6ca477efe2..294c1fcee3e 100644 --- a/jdk/src/share/classes/sun/security/ssl/HandshakeInStream.java +++ b/jdk/src/share/classes/sun/security/ssl/HandshakeInStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2009, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 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 @@ -121,7 +121,8 @@ public class HandshakeInStream extends InputStream { r.mark(readlimit); } - public void reset() { + @Override + public void reset() throws IOException { r.reset(); } diff --git a/jdk/src/share/classes/sun/security/ssl/HandshakeMessage.java b/jdk/src/share/classes/sun/security/ssl/HandshakeMessage.java index c4cca9edba0..746773590d3 100644 --- a/jdk/src/share/classes/sun/security/ssl/HandshakeMessage.java +++ b/jdk/src/share/classes/sun/security/ssl/HandshakeMessage.java @@ -256,13 +256,9 @@ static final class ClientHello extends HandshakeMessage { } // add server_name extension - void addServerNameIndicationExtension(String hostname) { - // We would have checked that the hostname ia a FQDN. - ArrayList hostnames = new ArrayList<>(1); - hostnames.add(hostname); - + void addSNIExtension(List serverNames) { try { - extensions.add(new ServerNameExtension(hostnames)); + extensions.add(new ServerNameExtension(serverNames)); } catch (IOException ioe) { // ignore the exception and return } diff --git a/jdk/src/share/classes/sun/security/ssl/Handshaker.java b/jdk/src/share/classes/sun/security/ssl/Handshaker.java index 4fb7fec7d61..cd5ebe4fa05 100644 --- a/jdk/src/share/classes/sun/security/ssl/Handshaker.java +++ b/jdk/src/share/classes/sun/security/ssl/Handshaker.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 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 @@ -112,6 +112,12 @@ abstract class Handshaker { */ private CipherSuiteList activeCipherSuites; + // The server name indication and matchers + List serverNames = + Collections.emptyList(); + Collection sniMatchers = + Collections.emptyList(); + private boolean isClient; private boolean needCertVerify; @@ -287,14 +293,7 @@ abstract class Handshaker { } } - String getRawHostnameSE() { - if (conn != null) { - return conn.getRawHostname(); - } else { - return engine.getPeerHost(); - } - } - + // ONLY used by ClientHandshaker to setup the peer host in SSLSession. String getHostSE() { if (conn != null) { return conn.getHost(); @@ -303,6 +302,7 @@ abstract class Handshaker { } } + // ONLY used by ServerHandshaker to setup the peer host in SSLSession. String getHostAddressSE() { if (conn != null) { return conn.getInetAddress().getHostAddress(); @@ -435,6 +435,22 @@ abstract class Handshaker { this.identificationProtocol = protocol; } + /** + * Sets the server name indication of the handshake. + */ + void setSNIServerNames(List serverNames) { + // The serverNames parameter is unmodifiable. + this.serverNames = serverNames; + } + + /** + * Sets the server name matchers of the handshaking. + */ + void setSNIMatchers(Collection sniMatchers) { + // The sniMatchers parameter is unmodifiable. + this.sniMatchers = sniMatchers; + } + /** * Prior to handshaking, activate the handshake and initialize the version, * input stream and output stream. diff --git a/jdk/src/share/classes/sun/security/ssl/HelloExtensions.java b/jdk/src/share/classes/sun/security/ssl/HelloExtensions.java index 9db69274692..ea769fd30dd 100644 --- a/jdk/src/share/classes/sun/security/ssl/HelloExtensions.java +++ b/jdk/src/share/classes/sun/security/ssl/HelloExtensions.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2006, 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 @@ -28,11 +28,10 @@ package sun.security.ssl; import java.io.IOException; import java.io.PrintStream; import java.util.*; - +import javax.net.ssl.*; +import java.nio.charset.StandardCharsets; import java.security.spec.ECParameterSpec; -import javax.net.ssl.SSLProtocolException; - /** * This file contains all the classes relevant to TLS Extensions for the * ClientHello and ServerHello messages. The extension mechanism and @@ -274,11 +273,11 @@ final class UnknownExtension extends HelloExtension { } /* - * [RFC4366] To facilitate secure connections to servers that host multiple - * 'virtual' servers at a single underlying network address, clients MAY - * include an extension of type "server_name" in the (extended) client hello. - * The "extension_data" field of this extension SHALL contain "ServerNameList" - * where: + * [RFC 4366/6066] To facilitate secure connections to servers that host + * multiple 'virtual' servers at a single underlying network address, clients + * MAY include an extension of type "server_name" in the (extended) client + * hello. The "extension_data" field of this extension SHALL contain + * "ServerNameList" where: * * struct { * NameType name_type; @@ -299,44 +298,47 @@ final class UnknownExtension extends HelloExtension { */ final class ServerNameExtension extends HelloExtension { - final static int NAME_HOST_NAME = 0; - - private List names; + // For backward compatibility, all future data structures associated with + // new NameTypes MUST begin with a 16-bit length field. + final static int NAME_HEADER_LENGTH = 3; // NameType: 1 byte + // Name length: 2 bytes + private Map sniMap; private int listLength; // ServerNameList length - ServerNameExtension(List hostnames) throws IOException { + // constructor for ServerHello + ServerNameExtension() throws IOException { super(ExtensionType.EXT_SERVER_NAME); listLength = 0; - names = new ArrayList(hostnames.size()); - for (String hostname : hostnames) { - if (hostname != null && hostname.length() != 0) { - // we only support DNS hostname now. - ServerName serverName = - new ServerName(NAME_HOST_NAME, hostname); - names.add(serverName); - listLength += serverName.length; + sniMap = Collections.emptyMap(); + } + + // constructor for ClientHello + ServerNameExtension(List serverNames) + throws IOException { + super(ExtensionType.EXT_SERVER_NAME); + + listLength = 0; + sniMap = new LinkedHashMap<>(); + for (SNIServerName serverName : serverNames) { + // check for duplicated server name type + if (sniMap.put(serverName.getType(), serverName) != null) { + // unlikely to happen, but in case ... + throw new RuntimeException( + "Duplicated server name of type " + serverName.getType()); } + + listLength += serverName.getEncoded().length + NAME_HEADER_LENGTH; } - // As we only support DNS hostname now, the hostname list must - // not contain more than one hostname - if (names.size() > 1) { - throw new SSLProtocolException( - "The ServerNameList MUST NOT contain more than " + - "one name of the same name_type"); - } - - // We only need to add "server_name" extension in ClientHello unless - // we support SNI in server side in the future. It is possible that - // the SNI is empty in ServerHello. As we don't support SNI in - // ServerHello now, we will throw exception for empty list for now. + // This constructor is used for ClientHello only. Empty list is + // not allowed in client mode. if (listLength == 0) { - throw new SSLProtocolException( - "The ServerNameList cannot be empty"); + throw new RuntimeException("The ServerNameList cannot be empty"); } } + // constructor for ServerHello for parsing SNI extension ServerNameExtension(HandshakeInStream s, int len) throws IOException { super(ExtensionType.EXT_SERVER_NAME); @@ -350,17 +352,54 @@ final class ServerNameExtension extends HelloExtension { } remains -= 2; - names = new ArrayList(); + sniMap = new LinkedHashMap<>(); while (remains > 0) { - ServerName name = new ServerName(s); - names.add(name); - remains -= name.length; + int code = s.getInt8(); // NameType - // we may need to check the duplicated ServerName type + // HostName (length read in getBytes16); + byte[] encoded = s.getBytes16(); + SNIServerName serverName; + switch (code) { + case StandardConstants.SNI_HOST_NAME: + if (encoded.length == 0) { + throw new SSLProtocolException( + "Empty HostName in server name indication"); + } + try { + serverName = new SNIHostName(encoded); + } catch (IllegalArgumentException iae) { + SSLProtocolException spe = new SSLProtocolException( + "Illegal server name, type=host_name(" + + code + "), name=" + + (new String(encoded, StandardCharsets.UTF_8)) + + ", value=" + Debug.toString(encoded)); + spe.initCause(iae); + throw spe; + } + break; + default: + try { + serverName = new UnknownServerName(code, encoded); + } catch (IllegalArgumentException iae) { + SSLProtocolException spe = new SSLProtocolException( + "Illegal server name, type=(" + code + + "), value=" + Debug.toString(encoded)); + spe.initCause(iae); + throw spe; + } + } + // check for duplicated server name type + if (sniMap.put(serverName.getType(), serverName) != null) { + throw new SSLProtocolException( + "Duplicated server name of type " + + serverName.getType()); + } + + remains -= encoded.length + NAME_HEADER_LENGTH; } } else if (len == 0) { // "server_name" extension in ServerHello listLength = 0; - names = Collections.emptyList(); + sniMap = Collections.emptyMap(); } if (remains != 0) { @@ -368,39 +407,72 @@ final class ServerNameExtension extends HelloExtension { } } - static class ServerName { - final int length; - final int type; - final byte[] data; - final String hostname; - - ServerName(int type, String hostname) throws IOException { - this.type = type; // NameType - this.hostname = hostname; - this.data = hostname.getBytes("UTF8"); // HostName - this.length = data.length + 3; // NameType: 1 byte - // HostName length: 2 bytes + List getServerNames() { + if (sniMap != null && !sniMap.isEmpty()) { + return Collections.unmodifiableList( + new ArrayList<>(sniMap.values())); } - ServerName(HandshakeInStream s) throws IOException { - type = s.getInt8(); // NameType - data = s.getBytes16(); // HostName (length read in getBytes16) - length = data.length + 3; // NameType: 1 byte - // HostName length: 2 bytes - if (type == NAME_HOST_NAME) { - hostname = new String(data, "UTF8"); - } else { - hostname = null; + return Collections.emptyList(); + } + + /* + * Is the extension recognized by the corresponding matcher? + * + * This method is used to check whether the server name indication can + * be recognized by the server name matchers. + * + * Per RFC 6066, if the server understood the ClientHello extension but + * does not recognize the server name, the server SHOULD take one of two + * actions: either abort the handshake by sending a fatal-level + * unrecognized_name(112) alert or continue the handshake. + * + * If there is an instance of SNIMatcher defined for a particular name + * type, it must be used to perform match operations on the server name. + */ + boolean isMatched(Collection matchers) { + if (sniMap != null && !sniMap.isEmpty()) { + for (SNIMatcher matcher : matchers) { + SNIServerName sniName = sniMap.get(matcher.getType()); + if (sniName != null && (!matcher.matches(sniName))) { + return false; + } } } - public String toString() { - if (type == NAME_HOST_NAME) { - return "host_name: " + hostname; - } else { - return "unknown-" + type + ": " + Debug.toString(data); + return true; + } + + /* + * Is the extension is identical to a server name list? + * + * This method is used to check the server name indication during session + * resumption. + * + * Per RFC 6066, when the server is deciding whether or not to accept a + * request to resume a session, the contents of a server_name extension + * MAY be used in the lookup of the session in the session cache. The + * client SHOULD include the same server_name extension in the session + * resumption request as it did in the full handshake that established + * the session. A server that implements this extension MUST NOT accept + * the request to resume the session if the server_name extension contains + * a different name. Instead, it proceeds with a full handshake to + * establish a new session. When resuming a session, the server MUST NOT + * include a server_name extension in the server hello. + */ + boolean isIdentical(List other) { + if (other.size() == sniMap.size()) { + for(SNIServerName sniInOther : other) { + SNIServerName sniName = sniMap.get(sniInOther.getType()); + if (sniName == null || !sniInOther.equals(sniName)) { + return false; + } } + + return true; } + + return false; } int length() { @@ -409,25 +481,34 @@ final class ServerNameExtension extends HelloExtension { void send(HandshakeOutStream s) throws IOException { s.putInt16(type.id); - s.putInt16(listLength + 2); - if (listLength != 0) { - s.putInt16(listLength); + if (listLength == 0) { + s.putInt16(listLength); // in ServerHello, empty extension_data + } else { + s.putInt16(listLength + 2); // length of extension_data + s.putInt16(listLength); // length of ServerNameList - for (ServerName name : names) { - s.putInt8(name.type); // NameType - s.putBytes16(name.data); // HostName + for (SNIServerName sniName : sniMap.values()) { + s.putInt8(sniName.getType()); // server name type + s.putBytes16(sniName.getEncoded()); // server name value } } } public String toString() { StringBuffer buffer = new StringBuffer(); - for (ServerName name : names) { - buffer.append("[" + name + "]"); + for (SNIServerName sniName : sniMap.values()) { + buffer.append("[" + sniName + "]"); } return "Extension " + type + ", server_name: " + buffer; } + + private static class UnknownServerName extends SNIServerName { + UnknownServerName(int code, byte[] encoded) { + super(code, encoded); + } + } + } final class SupportedEllipticCurvesExtension extends HelloExtension { diff --git a/jdk/src/share/classes/sun/security/ssl/ProtocolList.java b/jdk/src/share/classes/sun/security/ssl/ProtocolList.java index a57a9bdfdff..2a4bbee8005 100644 --- a/jdk/src/share/classes/sun/security/ssl/ProtocolList.java +++ b/jdk/src/share/classes/sun/security/ssl/ProtocolList.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 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 @@ -79,7 +79,7 @@ final class ProtocolList { throw new IllegalArgumentException("Protocols may not be null"); } - ArrayList versions = new ArrayList<>(3); + ArrayList versions = new ArrayList<>(names.length); for (int i = 0; i < names.length; i++ ) { ProtocolVersion version = ProtocolVersion.valueOf(names[i]); if (versions.contains(version) == false) { diff --git a/jdk/src/share/classes/sun/security/ssl/SSLEngineImpl.java b/jdk/src/share/classes/sun/security/ssl/SSLEngineImpl.java index a396bf7c13f..267bd55722b 100644 --- a/jdk/src/share/classes/sun/security/ssl/SSLEngineImpl.java +++ b/jdk/src/share/classes/sun/security/ssl/SSLEngineImpl.java @@ -27,6 +27,7 @@ package sun.security.ssl; import java.io.*; import java.nio.*; +import java.util.*; import java.security.*; import javax.crypto.BadPaddingException; @@ -34,7 +35,6 @@ import javax.crypto.BadPaddingException; import javax.net.ssl.*; import javax.net.ssl.SSLEngineResult.*; - /** * Implementation of an non-blocking SSLEngine. * @@ -253,6 +253,12 @@ final public class SSLEngineImpl extends SSLEngine { // The cryptographic algorithm constraints private AlgorithmConstraints algorithmConstraints = null; + // The server name indication and matchers + List serverNames = + Collections.emptyList(); + Collection sniMatchers = + Collections.emptyList(); + // Have we been told whether we're client or server? private boolean serverModeSet = false; private boolean roleIsServer; @@ -361,6 +367,10 @@ final public class SSLEngineImpl extends SSLEngine { roleIsServer = true; connectionState = cs_START; + // default server name indication + serverNames = + Utilities.addToSNIServerNameList(serverNames, getPeerHost()); + /* * default read and write side cipher and MAC support * @@ -459,11 +469,13 @@ final public class SSLEngineImpl extends SSLEngine { enabledProtocols, doClientAuth, protocolVersion, connectionState == cs_HANDSHAKE, secureRenegotiation, clientVerifyData, serverVerifyData); + handshaker.setSNIMatchers(sniMatchers); } else { handshaker = new ClientHandshaker(this, sslContext, enabledProtocols, protocolVersion, connectionState == cs_HANDSHAKE, secureRenegotiation, clientVerifyData, serverVerifyData); + handshaker.setSNIServerNames(serverNames); } handshaker.setEnabledCipherSuites(enabledCipherSuites); handshaker.setEnableSessionCreation(enableSessionCreation); @@ -1100,7 +1112,7 @@ final public class SSLEngineImpl extends SSLEngine { // TLS requires that unrecognized records be ignored. // if (debug != null && Debug.isOn("ssl")) { - System.out.println(threadName() + + System.out.println(Thread.currentThread().getName() + ", Received record type: " + inputRecord.contentType()); } @@ -1384,7 +1396,7 @@ final public class SSLEngineImpl extends SSLEngine { * for handshaking and bad_record_mac for other records. */ if (debug != null && Debug.isOn("ssl")) { - System.out.println(threadName() + + System.out.println(Thread.currentThread().getName() + ", sequence number extremely close to overflow " + "(2^64-1 packets). Closing connection."); } @@ -1402,7 +1414,8 @@ final public class SSLEngineImpl extends SSLEngine { */ if ((type != Record.ct_handshake) && mac.seqNumIsHuge()) { if (debug != null && Debug.isOn("ssl")) { - System.out.println(threadName() + ", request renegotiation " + + System.out.println(Thread.currentThread().getName() + + ", request renegotiation " + "to avoid sequence number overflow"); } @@ -1420,7 +1433,8 @@ final public class SSLEngineImpl extends SSLEngine { private void closeOutboundInternal() { if ((debug != null) && Debug.isOn("ssl")) { - System.out.println(threadName() + ", closeOutboundInternal()"); + System.out.println(Thread.currentThread().getName() + + ", closeOutboundInternal()"); } /* @@ -1467,7 +1481,8 @@ final public class SSLEngineImpl extends SSLEngine { * Dump out a close_notify to the remote side */ if ((debug != null) && Debug.isOn("ssl")) { - System.out.println(threadName() + ", called closeOutbound()"); + System.out.println(Thread.currentThread().getName() + + ", called closeOutbound()"); } closeOutboundInternal(); @@ -1487,7 +1502,8 @@ final public class SSLEngineImpl extends SSLEngine { private void closeInboundInternal() { if ((debug != null) && Debug.isOn("ssl")) { - System.out.println(threadName() + ", closeInboundInternal()"); + System.out.println(Thread.currentThread().getName() + + ", closeInboundInternal()"); } /* @@ -1519,7 +1535,8 @@ final public class SSLEngineImpl extends SSLEngine { * someday in the future. */ if ((debug != null) && Debug.isOn("ssl")) { - System.out.println(threadName() + ", called closeInbound()"); + System.out.println(Thread.currentThread().getName() + + ", called closeInbound()"); } /* @@ -1642,7 +1659,7 @@ final public class SSLEngineImpl extends SSLEngine { */ if (closeReason != null) { if ((debug != null) && Debug.isOn("ssl")) { - System.out.println(threadName() + + System.out.println(Thread.currentThread().getName() + ", fatal: engine already closed. Rethrowing " + cause.toString()); } @@ -1656,7 +1673,7 @@ final public class SSLEngineImpl extends SSLEngine { } if ((debug != null) && Debug.isOn("ssl")) { - System.out.println(threadName() + System.out.println(Thread.currentThread().getName() + ", fatal error: " + description + ": " + diagnostic + "\n" + cause.toString()); } @@ -1723,7 +1740,7 @@ final public class SSLEngineImpl extends SSLEngine { if (debug != null && (Debug.isOn("record") || Debug.isOn("handshake"))) { synchronized (System.out) { - System.out.print(threadName()); + System.out.print(Thread.currentThread().getName()); System.out.print(", RECV " + protocolVersion + " ALERT: "); if (level == Alerts.alert_fatal) { System.out.print("fatal, "); @@ -1790,7 +1807,7 @@ final public class SSLEngineImpl extends SSLEngine { boolean useDebug = debug != null && Debug.isOn("ssl"); if (useDebug) { synchronized (System.out) { - System.out.print(threadName()); + System.out.print(Thread.currentThread().getName()); System.out.print(", SEND " + protocolVersion + " ALERT: "); if (level == Alerts.alert_fatal) { System.out.print("fatal, "); @@ -1810,7 +1827,7 @@ final public class SSLEngineImpl extends SSLEngine { writeRecord(r); } catch (IOException e) { if (useDebug) { - System.out.println(threadName() + + System.out.println(Thread.currentThread().getName() + ", Exception sending alert: " + e); } } @@ -1948,7 +1965,7 @@ final public class SSLEngineImpl extends SSLEngine { default: if (debug != null && Debug.isOn("ssl")) { - System.out.println(threadName() + + System.out.println(Thread.currentThread().getName() + ", setUseClientMode() invoked in state = " + connectionState); } @@ -2050,6 +2067,8 @@ final public class SSLEngineImpl extends SSLEngine { // the super implementation does not handle the following parameters params.setEndpointIdentificationAlgorithm(identificationProtocol); params.setAlgorithmConstraints(algorithmConstraints); + params.setSNIMatchers(sniMatchers); + params.setServerNames(serverNames); return params; } @@ -2063,19 +2082,28 @@ final public class SSLEngineImpl extends SSLEngine { // the super implementation does not handle the following parameters identificationProtocol = params.getEndpointIdentificationAlgorithm(); algorithmConstraints = params.getAlgorithmConstraints(); + + List sniNames = params.getServerNames(); + if (sniNames != null) { + serverNames = sniNames; + } + + Collection matchers = params.getSNIMatchers(); + if (matchers != null) { + sniMatchers = matchers; + } + if ((handshaker != null) && !handshaker.started()) { handshaker.setIdentificationProtocol(identificationProtocol); handshaker.setAlgorithmConstraints(algorithmConstraints); + if (roleIsServer) { + handshaker.setSNIMatchers(sniMatchers); + } else { + handshaker.setSNIServerNames(serverNames); + } } } - /** - * Return the name of the current thread. Utility method. - */ - private static String threadName() { - return Thread.currentThread().getName(); - } - /** * Returns a printable representation of this end of the connection. */ diff --git a/jdk/src/share/classes/sun/security/ssl/SSLServerSocketImpl.java b/jdk/src/share/classes/sun/security/ssl/SSLServerSocketImpl.java index c2098c1db19..0dcf2cd3ce4 100644 --- a/jdk/src/share/classes/sun/security/ssl/SSLServerSocketImpl.java +++ b/jdk/src/share/classes/sun/security/ssl/SSLServerSocketImpl.java @@ -39,6 +39,7 @@ import javax.net.ServerSocketFactory; import javax.net.ssl.SSLException; import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLParameters; +import javax.net.ssl.SNIMatcher; /** @@ -92,6 +93,10 @@ class SSLServerSocketImpl extends SSLServerSocket // The cryptographic algorithm constraints private AlgorithmConstraints algorithmConstraints = null; + // The server name indication + Collection sniMatchers = + Collections.emptyList(); + /** * Create an SSL server socket on a port, using a non-default * authentication context and a specified connection backlog. @@ -289,6 +294,7 @@ class SSLServerSocketImpl extends SSLServerSocket // the super implementation does not handle the following parameters params.setEndpointIdentificationAlgorithm(identificationProtocol); params.setAlgorithmConstraints(algorithmConstraints); + params.setSNIMatchers(sniMatchers); return params; } @@ -302,6 +308,10 @@ class SSLServerSocketImpl extends SSLServerSocket // the super implementation does not handle the following parameters identificationProtocol = params.getEndpointIdentificationAlgorithm(); algorithmConstraints = params.getAlgorithmConstraints(); + Collection matchers = params.getSNIMatchers(); + if (matchers != null) { + sniMatchers = params.getSNIMatchers(); + } } /** @@ -312,7 +322,8 @@ class SSLServerSocketImpl extends SSLServerSocket public Socket accept() throws IOException { SSLSocketImpl s = new SSLSocketImpl(sslContext, useServerMode, enabledCipherSuites, doClientAuth, enableSessionCreation, - enabledProtocols, identificationProtocol, algorithmConstraints); + enabledProtocols, identificationProtocol, algorithmConstraints, + sniMatchers); implAccept(s); s.doneConnect(); diff --git a/jdk/src/share/classes/sun/security/ssl/SSLSessionImpl.java b/jdk/src/share/classes/sun/security/ssl/SSLSessionImpl.java index 110fc9ccaee..1c4575f8b0d 100644 --- a/jdk/src/share/classes/sun/security/ssl/SSLSessionImpl.java +++ b/jdk/src/share/classes/sun/security/ssl/SSLSessionImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 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 @@ -33,6 +33,9 @@ import java.util.Hashtable; import java.util.Vector; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.ArrayList; import java.security.Principal; import java.security.PrivateKey; @@ -51,6 +54,7 @@ import javax.net.ssl.SSLSession; import javax.net.ssl.SSLPermission; import javax.net.ssl.SSLException; import javax.net.ssl.ExtendedSSLSession; +import javax.net.ssl.SNIServerName; import javax.security.auth.x500.X500Principal; @@ -111,6 +115,8 @@ final class SSLSessionImpl extends ExtendedSSLSession { private PrivateKey localPrivateKey; private String[] localSupportedSignAlgs; private String[] peerSupportedSignAlgs; + private List requestedServerNames; + // Principals for non-certificate based cipher suites private Principal peerPrincipal; @@ -212,6 +218,10 @@ final class SSLSessionImpl extends ExtendedSSLSession { SignatureAndHashAlgorithm.getAlgorithmNames(algorithms); } + void setRequestedServerNames(List requestedServerNames) { + this.requestedServerNames = new ArrayList<>(requestedServerNames); + } + /** * Set the peer principal. */ @@ -748,6 +758,7 @@ final class SSLSessionImpl extends ExtendedSSLSession { * Gets an array of supported signature algorithms that the local side is * willing to verify. */ + @Override public String[] getLocalSupportedSignatureAlgorithms() { if (localSupportedSignAlgs != null) { return localSupportedSignAlgs.clone(); @@ -760,6 +771,7 @@ final class SSLSessionImpl extends ExtendedSSLSession { * Gets an array of supported signature algorithms that the peer is * able to verify. */ + @Override public String[] getPeerSupportedSignatureAlgorithms() { if (peerSupportedSignAlgs != null) { return peerSupportedSignAlgs.clone(); @@ -768,6 +780,20 @@ final class SSLSessionImpl extends ExtendedSSLSession { return new String[0]; } + /** + * Obtains a List containing all {@link SNIServerName}s + * of the requested Server Name Indication (SNI) extension. + */ + @Override + public List getRequestedServerNames() { + if (requestedServerNames != null && !requestedServerNames.isEmpty()) { + return Collections.unmodifiableList( + requestedServerNames); + } + + return Collections.emptyList(); + } + /** Returns a string representation of this SSL session */ public String toString() { return "[Session-" + sessionCount diff --git a/jdk/src/share/classes/sun/security/ssl/SSLSocketFactoryImpl.java b/jdk/src/share/classes/sun/security/ssl/SSLSocketFactoryImpl.java index 135b46205e8..dc61b7b15ea 100644 --- a/jdk/src/share/classes/sun/security/ssl/SSLSocketFactoryImpl.java +++ b/jdk/src/share/classes/sun/security/ssl/SSLSocketFactoryImpl.java @@ -109,6 +109,16 @@ final public class SSLSocketFactoryImpl extends SSLSocketFactory { return new SSLSocketImpl(context, s, host, port, autoClose); } + @Override + public Socket createSocket(Socket s, InputStream consumed, + boolean autoClose) throws IOException { + if (s == null) { + throw new NullPointerException( + "the existing socket cannot be null"); + } + + return new SSLSocketImpl(context, s, consumed, autoClose); + } /** * Constructs an SSL connection to a server at a specified address diff --git a/jdk/src/share/classes/sun/security/ssl/SSLSocketImpl.java b/jdk/src/share/classes/sun/security/ssl/SSLSocketImpl.java index 49ab309ce0c..2e27d627b7d 100644 --- a/jdk/src/share/classes/sun/security/ssl/SSLSocketImpl.java +++ b/jdk/src/share/classes/sun/security/ssl/SSLSocketImpl.java @@ -36,9 +36,9 @@ import java.security.AlgorithmConstraints; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; +import java.nio.charset.StandardCharsets; import javax.crypto.BadPaddingException; - import javax.net.ssl.*; /** @@ -198,14 +198,6 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { private boolean autoClose = true; private AccessControlContext acc; - /* - * We cannot use the hostname resolved from name services. For - * virtual hosting, multiple hostnames may be bound to the same IP - * address, so the hostname resolved from name services is not - * reliable. - */ - private String rawHostname; - // The cipher suites enabled for use on this connection. private CipherSuiteList enabledCipherSuites; @@ -215,6 +207,12 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { // The cryptographic algorithm constraints private AlgorithmConstraints algorithmConstraints = null; + // The server name indication and matchers + List serverNames = + Collections.emptyList(); + Collection sniMatchers = + Collections.emptyList(); + /* * READ ME * READ ME * READ ME * READ ME * READ ME * READ ME * * IMPORTANT STUFF TO UNDERSTANDING THE SYNCHRONIZATION ISSUES. @@ -397,7 +395,8 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { throws IOException, UnknownHostException { super(); this.host = host; - this.rawHostname = host; + this.serverNames = + Utilities.addToSNIServerNameList(this.serverNames, this.host); init(context, false); SocketAddress socketAddress = host != null ? new InetSocketAddress(host, port) : @@ -440,7 +439,8 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { throws IOException, UnknownHostException { super(); this.host = host; - this.rawHostname = host; + this.serverNames = + Utilities.addToSNIServerNameList(this.serverNames, this.host); init(context, false); bind(new InetSocketAddress(localAddr, localPort)); SocketAddress socketAddress = @@ -482,13 +482,15 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { CipherSuiteList suites, byte clientAuth, boolean sessionCreation, ProtocolList protocols, String identificationProtocol, - AlgorithmConstraints algorithmConstraints) throws IOException { + AlgorithmConstraints algorithmConstraints, + Collection sniMatchers) throws IOException { super(); doClientAuth = clientAuth; enableSessionCreation = sessionCreation; this.identificationProtocol = identificationProtocol; this.algorithmConstraints = algorithmConstraints; + this.sniMatchers = sniMatchers; init(context, serverMode); /* @@ -535,12 +537,35 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { throw new SocketException("Underlying socket is not connected"); } this.host = host; - this.rawHostname = host; + this.serverNames = + Utilities.addToSNIServerNameList(this.serverNames, this.host); init(context, false); this.autoClose = autoClose; doneConnect(); } + /** + * Creates a server mode {@link Socket} layered over an + * existing connected socket, and is able to read data which has + * already been consumed/removed from the {@link Socket}'s + * underlying {@link InputStream}. + */ + SSLSocketImpl(SSLContextImpl context, Socket sock, + InputStream consumed, boolean autoClose) throws IOException { + super(sock, consumed); + // We always layer over a connected socket + if (!sock.isConnected()) { + throw new SocketException("Underlying socket is not connected"); + } + + // In server mode, it is not necessary to set host and serverNames. + // Otherwise, would require a reverse DNS lookup to get the hostname. + + init(context, true); + this.autoClose = autoClose; + doneConnect(); + } + /** * Initializes the client socket. */ @@ -604,7 +629,7 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { public void connect(SocketAddress endpoint, int timeout) throws IOException { - if (self != this) { + if (isLayered()) { throw new SocketException("Already connected"); } @@ -628,13 +653,8 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { * java.net actually connects using the socket "self", else * we get some pretty bizarre failure modes. */ - if (self == this) { - sockInput = super.getInputStream(); - sockOutput = super.getOutputStream(); - } else { - sockInput = self.getInputStream(); - sockOutput = self.getOutputStream(); - } + sockInput = super.getInputStream(); + sockOutput = super.getOutputStream(); /* * Move to handshaking state, with pending session initialized @@ -761,13 +781,14 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { // For layered, non-autoclose sockets, we are not // able to bring them into a usable state, so we // treat it as fatal error. - if (self != this && !autoClose) { + if (isLayered() && !autoClose) { // Note that the alert description is // specified as -1, so no message will be send // to peer anymore. fatal((byte)(-1), ssle); } else if ((debug != null) && Debug.isOn("ssl")) { - System.out.println(threadName() + + System.out.println( + Thread.currentThread().getName() + ", received Exception: " + ssle); } @@ -935,7 +956,7 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { boolean handshaking = (getConnectionState() <= cs_HANDSHAKE); boolean rethrow = requireCloseNotify || handshaking; if ((debug != null) && Debug.isOn("ssl")) { - System.out.println(threadName() + + System.out.println(Thread.currentThread().getName() + ", received EOFException: " + (rethrow ? "error" : "ignored")); } @@ -1119,7 +1140,7 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { // TLS requires that unrecognized records be ignored. // if (debug != null && Debug.isOn("ssl")) { - System.out.println(threadName() + + System.out.println(Thread.currentThread().getName() + ", Received record type: " + r.contentType()); } @@ -1183,7 +1204,7 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { * for handshaking and bad_record_mac for other records. */ if (debug != null && Debug.isOn("ssl")) { - System.out.println(threadName() + + System.out.println(Thread.currentThread().getName() + ", sequence number extremely close to overflow " + "(2^64-1 packets). Closing connection."); @@ -1200,7 +1221,8 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { */ if ((type != Record.ct_handshake) && mac.seqNumIsHuge()) { if (debug != null && Debug.isOn("ssl")) { - System.out.println(threadName() + ", request renegotiation " + + System.out.println(Thread.currentThread().getName() + + ", request renegotiation " + "to avoid sequence number overflow"); } @@ -1278,11 +1300,13 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { enabledProtocols, doClientAuth, protocolVersion, connectionState == cs_HANDSHAKE, secureRenegotiation, clientVerifyData, serverVerifyData); + handshaker.setSNIMatchers(sniMatchers); } else { handshaker = new ClientHandshaker(this, sslContext, enabledProtocols, protocolVersion, connectionState == cs_HANDSHAKE, secureRenegotiation, clientVerifyData, serverVerifyData); + handshaker.setSNIServerNames(serverNames); } handshaker.setEnabledCipherSuites(enabledCipherSuites); handshaker.setEnableSessionCreation(enableSessionCreation); @@ -1509,24 +1533,20 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { protected void closeSocket() throws IOException { if ((debug != null) && Debug.isOn("ssl")) { - System.out.println(threadName() + ", called closeSocket()"); - } - if (self == this) { - super.close(); - } else { - self.close(); + System.out.println(Thread.currentThread().getName() + + ", called closeSocket()"); } + + super.close(); } private void closeSocket(boolean selfInitiated) throws IOException { if ((debug != null) && Debug.isOn("ssl")) { - System.out.println(threadName() + + System.out.println(Thread.currentThread().getName() + ", called closeSocket(" + selfInitiated + ")"); } - if (self == this) { + if (!isLayered() || autoClose) { super.close(); - } else if (autoClose) { - self.close(); } else if (selfInitiated) { // layered && non-autoclose // read close_notify alert to clear input stream @@ -1549,7 +1569,8 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { */ public void close() throws IOException { if ((debug != null) && Debug.isOn("ssl")) { - System.out.println(threadName() + ", called close()"); + System.out.println(Thread.currentThread().getName() + + ", called close()"); } closeInternal(true); // caller is initiating close setConnectionState(cs_APP_CLOSED); @@ -1567,8 +1588,8 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { */ private void closeInternal(boolean selfInitiated) throws IOException { if ((debug != null) && Debug.isOn("ssl")) { - System.out.println(threadName() + ", called closeInternal(" - + selfInitiated + ")"); + System.out.println(Thread.currentThread().getName() + + ", called closeInternal(" + selfInitiated + ")"); } int state = getConnectionState(); @@ -1630,7 +1651,7 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { // closing since it is already in progress. if (state == cs_SENT_CLOSE) { if (debug != null && Debug.isOn("ssl")) { - System.out.println(threadName() + + System.out.println(Thread.currentThread().getName() + ", close invoked again; state = " + getConnectionState()); } @@ -1653,7 +1674,7 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { } } if ((debug != null) && Debug.isOn("ssl")) { - System.out.println(threadName() + + System.out.println(Thread.currentThread().getName() + ", after primary close; state = " + getConnectionState()); } @@ -1701,7 +1722,7 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { */ void waitForClose(boolean rethrow) throws IOException { if (debug != null && Debug.isOn("ssl")) { - System.out.println(threadName() + + System.out.println(Thread.currentThread().getName() + ", waiting for close_notify or alert: state " + getConnectionState()); } @@ -1726,7 +1747,7 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { inrec = null; } catch (IOException e) { if (debug != null && Debug.isOn("ssl")) { - System.out.println(threadName() + + System.out.println(Thread.currentThread().getName() + ", Exception while waiting for close " +e); } if (rethrow) { @@ -1788,8 +1809,8 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { synchronized private void handleException(Exception e, boolean resumable) throws IOException { if ((debug != null) && Debug.isOn("ssl")) { - System.out.println(threadName() - + ", handling exception: " + e.toString()); + System.out.println(Thread.currentThread().getName() + + ", handling exception: " + e.toString()); } // don't close the Socket in case of timeouts or interrupts if @@ -1935,7 +1956,7 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { if (debug != null && (Debug.isOn("record") || Debug.isOn("handshake"))) { synchronized (System.out) { - System.out.print(threadName()); + System.out.print(Thread.currentThread().getName()); System.out.print(", RECV " + protocolVersion + " ALERT: "); if (level == Alerts.alert_fatal) { System.out.print("fatal, "); @@ -2001,7 +2022,7 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { boolean useDebug = debug != null && Debug.isOn("ssl"); if (useDebug) { synchronized (System.out) { - System.out.print(threadName()); + System.out.print(Thread.currentThread().getName()); System.out.print(", SEND " + protocolVersion + " ALERT: "); if (level == Alerts.alert_fatal) { System.out.print("fatal, "); @@ -2021,7 +2042,7 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { writeRecord(r); } catch (IOException e) { if (useDebug) { - System.out.println(threadName() + + System.out.println(Thread.currentThread().getName() + ", Exception sending alert: " + e); } } @@ -2118,14 +2139,15 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { return host; } - synchronized String getRawHostname() { - return rawHostname; - } - // ONLY used by HttpsClient to setup the URI specified hostname + // + // Please NOTE that this method MUST be called before calling to + // SSLSocket.setSSLParameters(). Otherwise, the {@code host} parameter + // may override SNIHostName in the customized server name indication. synchronized public void setHost(String host) { this.host = host; - this.rawHostname = host; + this.serverNames = + Utilities.addToSNIServerNameList(this.serverNames, this.host); } /** @@ -2186,7 +2208,7 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { } catch (IOException e) { // handshake failed. log and return a nullSession if (debug != null && Debug.isOn("handshake")) { - System.out.println(threadName() + + System.out.println(Thread.currentThread().getName() + ", IOException in getSession(): " + e); } } @@ -2328,7 +2350,7 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { default: if (debug != null && Debug.isOn("ssl")) { - System.out.println(threadName() + + System.out.println(Thread.currentThread().getName() + ", setUseClientMode() invoked in state = " + connectionState); } @@ -2422,14 +2444,11 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { */ public void setSoTimeout(int timeout) throws SocketException { if ((debug != null) && Debug.isOn("ssl")) { - System.out.println(threadName() + + System.out.println(Thread.currentThread().getName() + ", setSoTimeout(" + timeout + ") called"); } - if (self == this) { - super.setSoTimeout(timeout); - } else { - self.setSoTimeout(timeout); - } + + super.setSoTimeout(timeout); } /** @@ -2474,6 +2493,8 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { // the super implementation does not handle the following parameters params.setEndpointIdentificationAlgorithm(identificationProtocol); params.setAlgorithmConstraints(algorithmConstraints); + params.setSNIMatchers(sniMatchers); + params.setServerNames(serverNames); return params; } @@ -2487,9 +2508,25 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { // the super implementation does not handle the following parameters identificationProtocol = params.getEndpointIdentificationAlgorithm(); algorithmConstraints = params.getAlgorithmConstraints(); + + List sniNames = params.getServerNames(); + if (sniNames != null) { + serverNames = sniNames; + } + + Collection matchers = params.getSNIMatchers(); + if (matchers != null) { + sniMatchers = matchers; + } + if ((handshaker != null) && !handshaker.started()) { handshaker.setIdentificationProtocol(identificationProtocol); handshaker.setAlgorithmConstraints(algorithmConstraints); + if (roleIsServer) { + handshaker.setSNIMatchers(sniMatchers); + } else { + handshaker.setSNIServerNames(serverNames); + } } } @@ -2530,13 +2567,6 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { } } - /** - * Return the name of the current thread. Utility method. - */ - private static String threadName() { - return Thread.currentThread().getName(); - } - /** * Returns a printable representation of this end of the connection. */ @@ -2548,11 +2578,7 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { retval.append(sess.getCipherSuite()); retval.append(": "); - if (self == this) { - retval.append(super.toString()); - } else { - retval.append(self.toString()); - } + retval.append(super.toString()); retval.append("]"); return retval.toString(); diff --git a/jdk/src/share/classes/sun/security/ssl/ServerHandshaker.java b/jdk/src/share/classes/sun/security/ssl/ServerHandshaker.java index 8b10f8a9499..475746e9fb7 100644 --- a/jdk/src/share/classes/sun/security/ssl/ServerHandshaker.java +++ b/jdk/src/share/classes/sun/security/ssl/ServerHandshaker.java @@ -276,6 +276,18 @@ final class ServerHandshaker extends Handshaker { mesg.print(System.out); } + // check the server name indication if required + ServerNameExtension clientHelloSNIExt = (ServerNameExtension) + mesg.extensions.get(ExtensionType.EXT_SERVER_NAME); + if (!sniMatchers.isEmpty()) { + // we do not reject client without SNI extension + if (clientHelloSNIExt != null && + !clientHelloSNIExt.isMatched(sniMatchers)) { + fatalSE(Alerts.alert_unrecognized_name, + "Unrecognized server name indication"); + } + } + // Does the message include security renegotiation indication? boolean renegotiationIndicated = false; @@ -474,6 +486,26 @@ final class ServerHandshaker extends Handshaker { } } + // cannot resume session with different server name indication + if (resumingSession) { + List oldServerNames = + previous.getRequestedServerNames(); + if (clientHelloSNIExt != null) { + if (!clientHelloSNIExt.isIdentical(oldServerNames)) { + resumingSession = false; + } + } else if (!oldServerNames.isEmpty()) { + resumingSession = false; + } + + if (!resumingSession && + debug != null && Debug.isOn("handshake")) { + System.out.println( + "The requested server name indication " + + "is not identical to the previous one"); + } + } + if (resumingSession && (doClientAuth == SSLEngineImpl.clauth_required)) { try { @@ -613,6 +645,14 @@ final class ServerHandshaker extends Handshaker { // algorithms in chooseCipherSuite() } + // set the server name indication in the session + List clientHelloSNI = + Collections.emptyList(); + if (clientHelloSNIExt != null) { + clientHelloSNI = clientHelloSNIExt.getServerNames(); + } + session.setRequestedServerNames(clientHelloSNI); + // set the handshake session setHandshakeSessionSE(session); @@ -654,6 +694,15 @@ final class ServerHandshaker extends Handshaker { m1.extensions.add(serverHelloRI); } + if (!sniMatchers.isEmpty() && clientHelloSNIExt != null) { + // When resuming a session, the server MUST NOT include a + // server_name extension in the server hello. + if (!resumingSession) { + ServerNameExtension serverHelloSNI = new ServerNameExtension(); + m1.extensions.add(serverHelloSNI); + } + } + if (debug != null && Debug.isOn("handshake")) { m1.print(System.out); System.out.println("Cipher suite: " + session.getSuite()); diff --git a/jdk/src/share/classes/sun/security/ssl/SunJSSE.java b/jdk/src/share/classes/sun/security/ssl/SunJSSE.java index 16cb35cd42e..6946063077d 100644 --- a/jdk/src/share/classes/sun/security/ssl/SunJSSE.java +++ b/jdk/src/share/classes/sun/security/ssl/SunJSSE.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 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 @@ -103,7 +103,7 @@ public abstract class SunJSSE extends java.security.Provider { // standard constructor protected SunJSSE() { - super("SunJSSE", 1.7d, info); + super("SunJSSE", 1.8d, info); subclassCheck(); if (Boolean.TRUE.equals(fips)) { throw new ProviderException diff --git a/jdk/src/share/classes/sun/security/ssl/Utilities.java b/jdk/src/share/classes/sun/security/ssl/Utilities.java new file mode 100644 index 00000000000..aefb02c9a19 --- /dev/null +++ b/jdk/src/share/classes/sun/security/ssl/Utilities.java @@ -0,0 +1,115 @@ +/* + * 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 sun.security.ssl; + +import javax.net.ssl.*; +import java.util.*; +import sun.net.util.IPAddressUtil; + +/** + * A utility class to share the static methods. + */ +final class Utilities { + /** + * Puts {@code hostname} into the {@code serverNames} list. + *

+ * If the {@code serverNames} does not look like a legal FQDN, it will + * not be put into the returned list. + *

+ * Note that the returned list does not allow duplicated name type. + * + * @return a list of {@link SNIServerName} + */ + static List addToSNIServerNameList( + List serverNames, String hostname) { + + SNIHostName sniHostName = rawToSNIHostName(hostname); + if (sniHostName == null) { + return serverNames; + } + + int size = serverNames.size(); + List sniList = (size != 0) ? + new ArrayList(serverNames) : + new ArrayList(1); + + boolean reset = false; + for (int i = 0; i < size; i++) { + SNIServerName serverName = sniList.get(i); + if (serverName.getType() == StandardConstants.SNI_HOST_NAME) { + sniList.set(i, sniHostName); + if (Debug.isOn("ssl")) { + System.out.println(Thread.currentThread().getName() + + ", the previous server name in SNI (" + serverName + + ") was replaced with (" + sniHostName + ")"); + } + reset = true; + break; + } + } + + if (!reset) { + sniList.add(sniHostName); + } + + return Collections.unmodifiableList(sniList); + } + + /** + * Converts string hostname to {@code SNIHostName}. + *

+ * Note that to check whether a hostname is a valid domain name, we cannot + * use the hostname resolved from name services. For virtual hosting, + * multiple hostnames may be bound to the same IP address, so the hostname + * resolved from name services is not always reliable. + * + * @param hostname + * the raw hostname + * @return an instance of {@link SNIHostName}, or null if the hostname does + * not look like a FQDN + */ + private static SNIHostName rawToSNIHostName(String hostname) { + SNIHostName sniHostName = null; + if (hostname != null && hostname.indexOf('.') > 0 && + !hostname.endsWith(".") && + !IPAddressUtil.isIPv4LiteralAddress(hostname) && + !IPAddressUtil.isIPv6LiteralAddress(hostname)) { + + try { + sniHostName = new SNIHostName(hostname); + } catch (IllegalArgumentException iae) { + // don't bother to handle illegal host_name + if (Debug.isOn("ssl")) { + System.out.println(Thread.currentThread().getName() + + ", \"" + hostname + "\" " + + "is not a legal HostName for server name indication"); + } + } + } + + return sniHostName; + } +} diff --git a/jdk/src/share/classes/sun/security/ssl/X509KeyManagerImpl.java b/jdk/src/share/classes/sun/security/ssl/X509KeyManagerImpl.java index 45e6c997586..4b8a101f529 100644 --- a/jdk/src/share/classes/sun/security/ssl/X509KeyManagerImpl.java +++ b/jdk/src/share/classes/sun/security/ssl/X509KeyManagerImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 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 @@ -128,13 +128,35 @@ final class X509KeyManagerImpl extends X509ExtendedKeyManager public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) { return chooseAlias(getKeyTypes(keyType), issuers, CheckType.SERVER, - getAlgorithmConstraints(socket)); + getAlgorithmConstraints(socket), + X509TrustManagerImpl.getRequestedServerNames(socket), + "HTTPS"); // The SNI HostName is a fully qualified domain name. + // The certificate selection scheme for SNI HostName + // is similar to HTTPS endpoint identification scheme + // implemented in this provider. + // + // Using HTTPS endpoint identification scheme to guide + // the selection of an appropriate authentication + // certificate according to requested SNI extension. + // + // It is not a really HTTPS endpoint identification. } public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) { return chooseAlias(getKeyTypes(keyType), issuers, CheckType.SERVER, - getAlgorithmConstraints(engine)); + getAlgorithmConstraints(engine), + X509TrustManagerImpl.getRequestedServerNames(engine), + "HTTPS"); // The SNI HostName is a fully qualified domain name. + // The certificate selection scheme for SNI HostName + // is similar to HTTPS endpoint identification scheme + // implemented in this provider. + // + // Using HTTPS endpoint identification scheme to guide + // the selection of an appropriate authentication + // certificate according to requested SNI extension. + // + // It is not a really HTTPS endpoint identification. } public String[] getClientAliases(String keyType, Principal[] issuers) { @@ -321,8 +343,8 @@ final class X509KeyManagerImpl extends X509ExtendedKeyManager * The algorithm we use is: * . scan through all the aliases in all builders in order * . as soon as we find a perfect match, return - * (i.e. a match with a cert that has appropriate key usage - * and is not expired). + * (i.e. a match with a cert that has appropriate key usage, + * qualified endpoint identity, and is not expired). * . if we do not find a perfect match, keep looping and remember * the imperfect matches * . at the end, sort the imperfect matches. we prefer expired certs @@ -331,6 +353,15 @@ final class X509KeyManagerImpl extends X509ExtendedKeyManager */ private String chooseAlias(List keyTypeList, Principal[] issuers, CheckType checkType, AlgorithmConstraints constraints) { + + return chooseAlias(keyTypeList, issuers, + checkType, constraints, null, null); + } + + private String chooseAlias(List keyTypeList, Principal[] issuers, + CheckType checkType, AlgorithmConstraints constraints, + List requestedServerNames, String idAlgorithm) { + if (keyTypeList == null || keyTypeList.isEmpty()) { return null; } @@ -340,7 +371,8 @@ final class X509KeyManagerImpl extends X509ExtendedKeyManager for (int i = 0, n = builders.size(); i < n; i++) { try { List results = getAliases(i, keyTypeList, - issuerSet, false, checkType, constraints); + issuerSet, false, checkType, constraints, + requestedServerNames, idAlgorithm); if (results != null) { // the results will either be a single perfect match // or 1 or more imperfect matches @@ -394,7 +426,8 @@ final class X509KeyManagerImpl extends X509ExtendedKeyManager for (int i = 0, n = builders.size(); i < n; i++) { try { List results = getAliases(i, keyTypeList, - issuerSet, true, checkType, constraints); + issuerSet, true, checkType, constraints, + null, null); if (results != null) { if (allResults == null) { allResults = new ArrayList(); @@ -504,7 +537,9 @@ final class X509KeyManagerImpl extends X509ExtendedKeyManager // first check extensions, if they match, check expiration // note: we may want to move this code into the sun.security.validator // package - CheckResult check(X509Certificate cert, Date date) { + CheckResult check(X509Certificate cert, Date date, + List serverNames, String idAlgorithm) { + if (this == NONE) { return CheckResult.OK; } @@ -553,11 +588,11 @@ final class X509KeyManagerImpl extends X509ExtendedKeyManager return CheckResult.EXTENSION_MISMATCH; } // For servers, also require key agreement. - // This is not totally accurate as the keyAgreement bit - // is only necessary for static ECDH key exchange and - // not ephemeral ECDH. We leave it in for now until - // there are signs that this check causes problems - // for real world EC certificates. + // This is not totally accurate as the keyAgreement + // bit is only necessary for static ECDH key + // exchange and not ephemeral ECDH. We leave it in + // for now until there are signs that this check + // causes problems for real world EC certificates. if ((this == SERVER) && (getBit(ku, 4) == false)) { return CheckResult.EXTENSION_MISMATCH; } @@ -571,10 +606,50 @@ final class X509KeyManagerImpl extends X509ExtendedKeyManager try { cert.checkValidity(date); - return CheckResult.OK; } catch (CertificateException e) { return CheckResult.EXPIRED; } + + if (serverNames != null && !serverNames.isEmpty()) { + for (SNIServerName serverName : serverNames) { + if (serverName.getType() == + StandardConstants.SNI_HOST_NAME) { + if (!(serverName instanceof SNIHostName)) { + try { + serverName = + new SNIHostName(serverName.getEncoded()); + } catch (IllegalArgumentException iae) { + // unlikely to happen, just in case ... + if (useDebug) { + debug.println( + "Illegal server name: " + serverName); + } + + return CheckResult.INSENSITIVE; + } + } + String hostname = + ((SNIHostName)serverName).getAsciiName(); + + try { + X509TrustManagerImpl.checkIdentity(hostname, + cert, idAlgorithm); + } catch (CertificateException e) { + if (useDebug) { + debug.println( + "Certificate identity does not match " + + "Server Name Inidication (SNI): " + + hostname); + } + return CheckResult.INSENSITIVE; + } + + break; + } + } + } + + return CheckResult.OK; } } @@ -583,6 +658,7 @@ final class X509KeyManagerImpl extends X509ExtendedKeyManager // for sorting, i.e. OK is best, followed by EXPIRED and EXTENSION_MISMATCH private static enum CheckResult { OK, // ok or not checked + INSENSITIVE, // server name indication insensitive EXPIRED, // extensions valid but cert expired EXTENSION_MISMATCH, // extensions invalid (expiration not checked) } @@ -616,7 +692,10 @@ final class X509KeyManagerImpl extends X509ExtendedKeyManager private List getAliases(int builderIndex, List keyTypes, Set issuerSet, boolean findAll, CheckType checkType, - AlgorithmConstraints constraints) throws Exception { + AlgorithmConstraints constraints, + List requestedServerNames, + String idAlgorithm) throws Exception { + Builder builder = builders.get(builderIndex); KeyStore ks = builder.getKeyStore(); List results = null; @@ -699,7 +778,8 @@ final class X509KeyManagerImpl extends X509ExtendedKeyManager date = new Date(); } CheckResult checkResult = - checkType.check((X509Certificate)chain[0], date); + checkType.check((X509Certificate)chain[0], date, + requestedServerNames, idAlgorithm); EntryStatus status = new EntryStatus(builderIndex, keyIndex, alias, chain, checkResult); diff --git a/jdk/src/share/classes/sun/security/ssl/X509TrustManagerImpl.java b/jdk/src/share/classes/sun/security/ssl/X509TrustManagerImpl.java index d38e727010c..13bbb18b8de 100644 --- a/jdk/src/share/classes/sun/security/ssl/X509TrustManagerImpl.java +++ b/jdk/src/share/classes/sun/security/ssl/X509TrustManagerImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 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 @@ -28,15 +28,14 @@ package sun.security.ssl; import java.net.Socket; import javax.net.ssl.SSLSession; +import java.nio.charset.StandardCharsets; import java.util.*; import java.security.*; import java.security.cert.*; - import javax.net.ssl.*; import sun.security.validator.*; - import sun.security.util.HostnameChecker; /** @@ -199,8 +198,8 @@ final class X509TrustManagerImpl extends X509ExtendedTrustManager String identityAlg = sslSocket.getSSLParameters(). getEndpointIdentificationAlgorithm(); if (identityAlg != null && identityAlg.length() != 0) { - String hostname = session.getPeerHost(); - checkIdentity(hostname, chain[0], identityAlg); + checkIdentity(session, chain[0], identityAlg, isClient, + getRequestedServerNames(socket)); } // create the algorithm constraints @@ -251,8 +250,8 @@ final class X509TrustManagerImpl extends X509ExtendedTrustManager String identityAlg = engine.getSSLParameters(). getEndpointIdentificationAlgorithm(); if (identityAlg != null && identityAlg.length() != 0) { - String hostname = session.getPeerHost(); - checkIdentity(hostname, chain[0], identityAlg); + checkIdentity(session, chain[0], identityAlg, isClient, + getRequestedServerNames(engine)); } // create the algorithm constraints @@ -329,6 +328,117 @@ final class X509TrustManagerImpl extends X509ExtendedTrustManager } } + // Get string representation of HostName from a list of server names. + // + // We are only accepting host_name name type in the list. + private static String getHostNameInSNI(List sniNames) { + + SNIHostName hostname = null; + for (SNIServerName sniName : sniNames) { + if (sniName.getType() != StandardConstants.SNI_HOST_NAME) { + continue; + } + + if (sniName instanceof SNIHostName) { + hostname = (SNIHostName)sniName; + } else { + try { + hostname = new SNIHostName(sniName.getEncoded()); + } catch (IllegalArgumentException iae) { + // unlikely to happen, just in case ... + if ((debug != null) && Debug.isOn("trustmanager")) { + byte[] encoded = hostname.getEncoded(); + System.out.println("Illegal server name: " + sniName); + } + } + } + + // no more than server name of the same name type + break; + } + + if (hostname != null) { + return hostname.getAsciiName(); + } + + return null; + } + + // Also used by X509KeyManagerImpl + static List getRequestedServerNames(Socket socket) { + if (socket != null && socket.isConnected() && + socket instanceof SSLSocket) { + + SSLSocket sslSocket = (SSLSocket)socket; + SSLSession session = sslSocket.getHandshakeSession(); + + if (session != null && (session instanceof ExtendedSSLSession)) { + ExtendedSSLSession extSession = (ExtendedSSLSession)session; + return extSession.getRequestedServerNames(); + } + } + + return Collections.emptyList(); + } + + // Also used by X509KeyManagerImpl + static List getRequestedServerNames(SSLEngine engine) { + if (engine != null) { + SSLSession session = engine.getHandshakeSession(); + + if (session != null && (session instanceof ExtendedSSLSession)) { + ExtendedSSLSession extSession = (ExtendedSSLSession)session; + return extSession.getRequestedServerNames(); + } + } + + return Collections.emptyList(); + } + + /* + * Per RFC 6066, if an application negotiates a server name using an + * application protocol and then upgrades to TLS, and if a server_name + * extension is sent, then the extension SHOULD contain the same name + * that was negotiated in the application protocol. If the server_name + * is established in the TLS session handshake, the client SHOULD NOT + * attempt to request a different server name at the application layer. + * + * According to the above spec, we only need to check either the identity + * in server_name extension or the peer host of the connection. Peer host + * is not always a reliable fully qualified domain name. The HostName in + * server_name extension is more reliable than peer host. So we prefer + * the identity checking aginst the server_name extension if present, and + * may failove to peer host checking. + */ + private static void checkIdentity(SSLSession session, + X509Certificate cert, + String algorithm, + boolean isClient, + List sniNames) throws CertificateException { + + boolean identifiable = false; + String peerHost = session.getPeerHost(); + if (isClient) { + String hostname = getHostNameInSNI(sniNames); + if (hostname != null) { + try { + checkIdentity(hostname, cert, algorithm); + identifiable = true; + } catch (CertificateException ce) { + if (hostname.equalsIgnoreCase(peerHost)) { + throw ce; + } + + // otherwisw, failover to check peer host + } + } + } + + if (!identifiable) { + checkIdentity(peerHost, cert, algorithm); + } + } + /* * Identify the peer by its certificate and hostname. * diff --git a/jdk/test/sun/security/ssl/javax/net/ssl/NewAPIs/SSLEngine/LargePacket.java b/jdk/test/sun/security/ssl/javax/net/ssl/NewAPIs/SSLEngine/LargePacket.java index 498df71463a..c95ebfdf7dc 100644 --- a/jdk/test/sun/security/ssl/javax/net/ssl/NewAPIs/SSLEngine/LargePacket.java +++ b/jdk/test/sun/security/ssl/javax/net/ssl/NewAPIs/SSLEngine/LargePacket.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2006, 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 @@ -21,13 +21,18 @@ * questions. */ +// +// SunJSSE does not support dynamic system properties, no way to re-use +// system properties in samevm/agentvm mode. +// + /* * @test * * @bug 6388456 * @summary Need adjustable TLS max record size for interoperability * with non-compliant - * @run main/othervm -Djsse.enableCBCProtection=false LargePacket + * @run main/othervm LargePacket * * @author Xuelei Fan */ @@ -83,12 +88,12 @@ public class LargePacket extends SSLEngineService { SocketChannel sc = ssc.accept(); // Complete connection. - while (!sc.finishConnect() ) { + while (!sc.finishConnect()) { // waiting for the connection completed. } // handshaking - handshaking(ssle, sc); + handshaking(ssle, sc, null); // receive application data receive(ssle, sc); @@ -131,7 +136,7 @@ public class LargePacket extends SSLEngineService { } // handshaking - handshaking(ssle, sc); + handshaking(ssle, sc, null); // send out application data deliver(ssle, sc); @@ -169,6 +174,8 @@ public class LargePacket extends SSLEngineService { * Fork off the other side, then do your work. */ LargePacket() throws Exception { + super("../../../../../etc"); + if (separateServerThread) { startServer(true); startClient(false); diff --git a/jdk/test/sun/security/ssl/javax/net/ssl/NewAPIs/SSLEngine/SSLEngineService.java b/jdk/test/sun/security/ssl/javax/net/ssl/NewAPIs/SSLEngine/SSLEngineService.java index b00780e6361..bc4c6f7db12 100644 --- a/jdk/test/sun/security/ssl/javax/net/ssl/NewAPIs/SSLEngine/SSLEngineService.java +++ b/jdk/test/sun/security/ssl/javax/net/ssl/NewAPIs/SSLEngine/SSLEngineService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2006, 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 @@ -22,8 +22,6 @@ */ /* - * - * * @bug 6388456 * @summary Need adjustable TLS max record size for interoperability * with non-compliant stacks @@ -42,17 +40,31 @@ import java.nio.channels.*; public class SSLEngineService { - private static String pathToStores = "../../../../../etc"; private static String keyStoreFile = "keystore"; private static String trustStoreFile = "truststore"; private static char[] passphrase = "passphrase".toCharArray(); - private static String keyFilename = + private String pathToStores; + private String keyFilename; + private String trustFilename; + + protected SSLEngineService() { + init("../../../../../etc"); + } + + protected SSLEngineService(String pathToStores) { + init(pathToStores); + } + + private void init(String pathToStores) { + this.pathToStores = pathToStores; + this.keyFilename = System.getProperty("test.src", "./") + "/" + pathToStores + "/" + keyStoreFile; - private static String trustFilename = + this.trustFilename = System.getProperty("test.src", "./") + "/" + pathToStores + "/" + trustStoreFile; + } // deliver local application data. protected static void deliver(SSLEngine ssle, SocketChannel sc) @@ -143,9 +155,12 @@ public class SSLEngineService { ByteBuffer peerNetData = ByteBuffer.allocate(netBufferMax/2); int received = -1; + boolean needToReadMore = true; while (received != 0) { - if (ssle.isInboundDone() || sc.read(peerNetData) < 0) { - break; + if (needToReadMore) { + if (ssle.isInboundDone() || sc.read(peerNetData) < 0) { + break; + } } peerNetData.flip(); @@ -186,6 +201,8 @@ public class SSLEngineService { " bytes large packet "); } + needToReadMore = (peerNetData.position() > 0) ? false : true; + break; case BUFFER_OVERFLOW : @@ -206,6 +223,8 @@ public class SSLEngineService { " bytes for BUFFER_UNDERFLOW"); peerNetData = enlargeBuffer(peerNetData, size); } + + needToReadMore = true; break; default : // CLOSED : @@ -215,8 +234,8 @@ public class SSLEngineService { } } - protected static void handshaking(SSLEngine ssle, SocketChannel sc) - throws Exception { + protected static void handshaking(SSLEngine ssle, SocketChannel sc, + ByteBuffer additional) throws Exception { int appBufferMax = ssle.getSession().getApplicationBufferSize(); int netBufferMax = ssle.getSession().getPacketBufferSize(); @@ -232,15 +251,39 @@ public class SSLEngineService { SSLEngineResult.HandshakeStatus hs = ssle.getHandshakeStatus(); // start handshaking from unwrap + byte[] buffer = new byte[0xFF]; + boolean underflow = false; do { switch (hs) { case NEED_UNWRAP : if (peerNetData.position() == 0) { + if (additional != null && additional.hasRemaining()) { + do { + int len = Math.min(buffer.length, + peerNetData.remaining()); + len = Math.min(len, additional.remaining()); + if (len != 0) { + additional.get(buffer, 0, len); + peerNetData.put(buffer, 0, len); + } + } while (peerNetData.remaining() > 0 && + additional.hasRemaining()); + } else { + if (sc.read(peerNetData) < 0) { + ssle.closeInbound(); + return; + } + } + } + + if (underflow) { if (sc.read(peerNetData) < 0) { ssle.closeInbound(); return; } + + underflow = false; } peerNetData.flip(); @@ -259,6 +302,8 @@ public class SSLEngineService { size + " bytes for BUFFER_UNDERFLOW"); peerNetData = enlargeBuffer(peerNetData, size); } + + underflow = true; break; case BUFFER_OVERFLOW : // maybe need to enlarge the peer application data buffer. @@ -339,7 +384,7 @@ public class SSLEngineService { /* * Create an initialized SSLContext to use for this test. */ - protected static SSLEngine createSSLEngine(boolean mode) throws Exception { + protected SSLEngine createSSLEngine(boolean mode) throws Exception { SSLEngine ssle; diff --git a/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLEngineExplorer.java b/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLEngineExplorer.java new file mode 100644 index 00000000000..2cd46354010 --- /dev/null +++ b/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLEngineExplorer.java @@ -0,0 +1,335 @@ +/* + * 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. + * + * 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. + */ + +// +// SunJSSE does not support dynamic system properties, no way to re-use +// system properties in samevm/agentvm mode. +// + +/* + * @test + * @bug 7068321 + * @summary Support TLS Server Name Indication (SNI) Extension in JSSE Server + * @library ../NewAPIs/SSLEngine ../../../../templates + * @build SSLEngineService SSLCapabilities SSLExplorer + * @run main/othervm SSLEngineExplorer SSLv2Hello,SSLv3 + * @run main/othervm SSLEngineExplorer SSLv3 + * @run main/othervm SSLEngineExplorer TLSv1 + * @run main/othervm SSLEngineExplorer TLSv1.1 + * @run main/othervm SSLEngineExplorer TLSv1.2 + */ + +import javax.net.ssl.*; +import java.nio.*; +import java.net.*; +import java.util.*; +import java.nio.channels.*; + +public class SSLEngineExplorer extends SSLEngineService { + + /* + * ============================================================= + * Set the various variables needed for the tests, then + * specify what tests to run on each side. + */ + + /* + * Should we run the client or server in a separate thread? + * Both sides can throw exceptions, but do you have a preference + * as to which side should be the main thread. + */ + static boolean separateServerThread = true; + + // Is the server ready to serve? + volatile static boolean serverReady = false; + + /* + * Turn on SSL debugging? + */ + static boolean debug = false; + + /* + * Define the server side of the test. + * + * If the server prematurely exits, serverReady will be set to true + * to avoid infinite hangs. + */ + void doServerSide() throws Exception { + + // create SSLEngine. + SSLEngine ssle = createSSLEngine(false); + + // Create a server socket channel. + InetSocketAddress isa = + new InetSocketAddress(InetAddress.getLocalHost(), serverPort); + ServerSocketChannel ssc = ServerSocketChannel.open(); + ssc.socket().bind(isa); + serverPort = ssc.socket().getLocalPort(); + + // Signal Client, we're ready for his connect. + serverReady = true; + + // Accept a socket channel. + SocketChannel sc = ssc.accept(); + sc.configureBlocking(false); + + // Complete connection. + while (!sc.finishConnect()) { + Thread.sleep(50); + // waiting for the connection completed. + } + + ByteBuffer buffer = ByteBuffer.allocate(0xFF); + int position = 0; + SSLCapabilities capabilities = null; + + // Read the header of TLS record + buffer.limit(SSLExplorer.RECORD_HEADER_SIZE); + while (position < SSLExplorer.RECORD_HEADER_SIZE) { + int n = sc.read(buffer); + if (n < 0) { + throw new Exception("unexpected end of stream!"); + } + position += n; + } + buffer.flip(); + + int recordLength = SSLExplorer.getRequiredSize(buffer); + if (buffer.capacity() < recordLength) { + ByteBuffer oldBuffer = buffer; + buffer = ByteBuffer.allocate(recordLength); + buffer.put(oldBuffer); + } + + buffer.position(SSLExplorer.RECORD_HEADER_SIZE); + buffer.limit(buffer.capacity()); + while (position < recordLength) { + int n = sc.read(buffer); + if (n < 0) { + throw new Exception("unexpected end of stream!"); + } + position += n; + } + buffer.flip(); + + capabilities = SSLExplorer.explore(buffer); + if (capabilities != null) { + System.out.println("Record version: " + + capabilities.getRecordVersion()); + System.out.println("Hello version: " + + capabilities.getHelloVersion()); + } + + // handshaking + handshaking(ssle, sc, buffer); + + // receive application data + receive(ssle, sc); + + // send out application data + deliver(ssle, sc); + + ExtendedSSLSession session = (ExtendedSSLSession)ssle.getSession(); + checkCapabilities(capabilities, session); + + // close the socket channel. + sc.close(); + ssc.close(); + } + + /* + * Define the client side of the test. + * + * If the server prematurely exits, serverReady will be set to true + * to avoid infinite hangs. + */ + void doClientSide() throws Exception { + // create SSLEngine. + SSLEngine ssle = createSSLEngine(true); + + /* + * Wait for server to get started. + */ + while (!serverReady) { + Thread.sleep(50); + } + + // Create a non-blocking socket channel. + SocketChannel sc = SocketChannel.open(); + sc.configureBlocking(false); + InetSocketAddress isa = + new InetSocketAddress(InetAddress.getLocalHost(), serverPort); + sc.connect(isa); + + // Complete connection. + while (!sc.finishConnect() ) { + Thread.sleep(50); + // waiting for the connection completed. + } + + // enable the specified TLS protocol + ssle.setEnabledProtocols(supportedProtocols); + + // handshaking + handshaking(ssle, sc, null); + + // send out application data + deliver(ssle, sc); + + // receive application data + receive(ssle, sc); + + // close the socket channel. + sc.close(); + } + + void checkCapabilities(SSLCapabilities capabilities, + ExtendedSSLSession session) throws Exception { + + List sessionSNI = session.getRequestedServerNames(); + if (!sessionSNI.equals(capabilities.getServerNames())) { + throw new Exception( + "server name indication does not match capabilities"); + } + } + + private static String[] supportedProtocols; // supported protocols + + private static void parseArguments(String[] args) { + supportedProtocols = args[0].split(","); + } + + + /* + * ============================================================= + * The remainder is just support stuff + */ + volatile Exception serverException = null; + volatile Exception clientException = null; + + // use any free port by default + volatile int serverPort = 0; + + public static void main(String args[]) throws Exception { + if (debug) + System.setProperty("javax.net.debug", "all"); + + /* + * Get the customized arguments. + */ + parseArguments(args); + + new SSLEngineExplorer(); + } + + Thread clientThread = null; + Thread serverThread = null; + + /* + * Primary constructor, used to drive remainder of the test. + * + * Fork off the other side, then do your work. + */ + SSLEngineExplorer() throws Exception { + super("../../../../etc"); + + if (separateServerThread) { + startServer(true); + startClient(false); + } else { + startClient(true); + startServer(false); + } + + /* + * Wait for other side to close down. + */ + if (separateServerThread) { + serverThread.join(); + } else { + clientThread.join(); + } + + /* + * When we get here, the test is pretty much over. + * + * If the main thread excepted, that propagates back + * immediately. If the other thread threw an exception, we + * should report back. + */ + if (serverException != null) { + System.out.print("Server Exception:"); + throw serverException; + } + if (clientException != null) { + System.out.print("Client Exception:"); + throw clientException; + } + } + + void startServer(boolean newThread) throws Exception { + if (newThread) { + serverThread = new Thread() { + public void run() { + try { + doServerSide(); + } catch (Exception e) { + /* + * Our server thread just died. + * + * Release the client, if not active already... + */ + System.err.println("Server died..."); + System.err.println(e); + serverReady = true; + serverException = e; + } + } + }; + serverThread.start(); + } else { + doServerSide(); + } + } + + void startClient(boolean newThread) throws Exception { + if (newThread) { + clientThread = new Thread() { + public void run() { + try { + doClientSide(); + } catch (Exception e) { + /* + * Our client thread just died. + */ + System.err.println("Client died..."); + clientException = e; + } + } + }; + clientThread.start(); + } else { + doClientSide(); + } + } +} diff --git a/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLEngineExplorerMatchedSNI.java b/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLEngineExplorerMatchedSNI.java new file mode 100644 index 00000000000..c201bee86ad --- /dev/null +++ b/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLEngineExplorerMatchedSNI.java @@ -0,0 +1,393 @@ +/* + * 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. + * + * 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. + */ + +// +// SunJSSE does not support dynamic system properties, no way to re-use +// system properties in samevm/agentvm mode. +// + +/* + * @test + * @bug 7068321 + * @summary Support TLS Server Name Indication (SNI) Extension in JSSE Server + * @library ../NewAPIs/SSLEngine ../../../../templates + * @build SSLEngineService SSLCapabilities SSLExplorer + * @run main/othervm SSLEngineExplorerMatchedSNI www.example.com + * www\.example\.com + * @run main/othervm SSLEngineExplorerMatchedSNI www.example.com + * www\.example\.(com|org) + * @run main/othervm SSLEngineExplorerMatchedSNI example.com + * (.*\.)*example\.(com|org) + * @run main/othervm SSLEngineExplorerMatchedSNI www.example.com + * (.*\.)*example\.(com|org) + * @run main/othervm SSLEngineExplorerMatchedSNI www.us.example.com + * (.*\.)*example\.(com|org) + */ + +import javax.net.ssl.*; +import java.nio.*; +import java.net.*; +import java.util.*; +import java.nio.channels.*; + +public class SSLEngineExplorerMatchedSNI extends SSLEngineService { + + /* + * ============================================================= + * Set the various variables needed for the tests, then + * specify what tests to run on each side. + */ + + /* + * Should we run the client or server in a separate thread? + * Both sides can throw exceptions, but do you have a preference + * as to which side should be the main thread. + */ + static boolean separateServerThread = false; + + // Is the server ready to serve? + volatile static boolean serverReady = false; + + /* + * Turn on SSL debugging? + */ + static boolean debug = false; + + /* + * Define the server side of the test. + * + * If the server prematurely exits, serverReady will be set to true + * to avoid infinite hangs. + */ + void doServerSide() throws Exception { + + // create SSLEngine. + SSLEngine ssle = createSSLEngine(false); + + // Create a server socket channel. + InetSocketAddress isa = + new InetSocketAddress(InetAddress.getLocalHost(), serverPort); + ServerSocketChannel ssc = ServerSocketChannel.open(); + ssc.socket().bind(isa); + serverPort = ssc.socket().getLocalPort(); + + // Signal Client, we're ready for his connect. + serverReady = true; + + // Accept a socket channel. + SocketChannel sc = ssc.accept(); + + // Complete connection. + while (!sc.finishConnect()) { + Thread.sleep(50); + // waiting for the connection completed. + } + + ByteBuffer buffer = ByteBuffer.allocate(0xFF); + int position = 0; + SSLCapabilities capabilities = null; + + // Read the header of TLS record + buffer.limit(SSLExplorer.RECORD_HEADER_SIZE); + while (position < SSLExplorer.RECORD_HEADER_SIZE) { + int n = sc.read(buffer); + if (n < 0) { + throw new Exception("unexpected end of stream!"); + } + position += n; + } + buffer.flip(); + + int recordLength = SSLExplorer.getRequiredSize(buffer); + if (buffer.capacity() < recordLength) { + ByteBuffer oldBuffer = buffer; + buffer = ByteBuffer.allocate(recordLength); + buffer.put(oldBuffer); + } + + buffer.position(SSLExplorer.RECORD_HEADER_SIZE); + buffer.limit(buffer.capacity()); + while (position < recordLength) { + int n = sc.read(buffer); + if (n < 0) { + throw new Exception("unexpected end of stream!"); + } + position += n; + } + buffer.flip(); + + capabilities = SSLExplorer.explore(buffer); + if (capabilities != null) { + System.out.println("Record version: " + + capabilities.getRecordVersion()); + System.out.println("Hello version: " + + capabilities.getHelloVersion()); + } + + // enable server name indication checking + SNIMatcher matcher = SNIHostName.createSNIMatcher( + serverAcceptableHostname); + Collection matchers = new ArrayList<>(1); + matchers.add(matcher); + SSLParameters params = ssle.getSSLParameters(); + params.setSNIMatchers(matchers); + ssle.setSSLParameters(params); + + // handshaking + handshaking(ssle, sc, buffer); + + // receive application data + receive(ssle, sc); + + // send out application data + deliver(ssle, sc); + + // check server name indication + ExtendedSSLSession session = (ExtendedSSLSession)ssle.getSession(); + checkCapabilities(capabilities, session); + + // close the socket channel. + sc.close(); + ssc.close(); + } + + /* + * Define the client side of the test. + * + * If the server prematurely exits, serverReady will be set to true + * to avoid infinite hangs. + */ + void doClientSide() throws Exception { + // create SSLEngine. + SSLEngine ssle = createSSLEngine(true); + + /* + * Wait for server to get started. + */ + while (!serverReady) { + Thread.sleep(50); + } + + // Create a non-blocking socket channel. + SocketChannel sc = SocketChannel.open(); + sc.configureBlocking(false); + InetSocketAddress isa = + new InetSocketAddress(InetAddress.getLocalHost(), serverPort); + sc.connect(isa); + + // Complete connection. + while (!sc.finishConnect() ) { + Thread.sleep(50); + // waiting for the connection completed. + } + + SNIHostName serverName = new SNIHostName(clientRequestedHostname); + List serverNames = new ArrayList<>(1); + serverNames.add(serverName); + SSLParameters params = ssle.getSSLParameters(); + params.setServerNames(serverNames); + ssle.setSSLParameters(params); + + // handshaking + handshaking(ssle, sc, null); + + // send out application data + deliver(ssle, sc); + + // receive application data + receive(ssle, sc); + + // check server name indication + ExtendedSSLSession session = (ExtendedSSLSession)ssle.getSession(); + checkSNIInSession(session); + + // close the socket channel. + sc.close(); + } + + void checkCapabilities(SSLCapabilities capabilities, + ExtendedSSLSession session) throws Exception { + List sessionSNI = session.getRequestedServerNames(); + if (!sessionSNI.equals(capabilities.getServerNames())) { + for (SNIServerName sni : sessionSNI) { + System.out.println("SNI in session is " + sni); + } + + List capaSNI = capabilities.getServerNames(); + for (SNIServerName sni : capaSNI) { + System.out.println("SNI in session is " + sni); + } + + throw new Exception( + "server name indication does not match capabilities"); + } + + checkSNIInSession(session); + } + + void checkSNIInSession(ExtendedSSLSession session) throws Exception { + List sessionSNI = session.getRequestedServerNames(); + if (sessionSNI.isEmpty()) { + throw new Exception( + "unexpected empty request server name indication"); + } + + if (sessionSNI.size() != 1) { + throw new Exception( + "unexpected request server name indication"); + } + + SNIServerName serverName = sessionSNI.get(0); + if (!(serverName instanceof SNIHostName)) { + throw new Exception( + "unexpected instance of request server name indication"); + } + + String hostname = ((SNIHostName)serverName).getAsciiName(); + if (!clientRequestedHostname.equalsIgnoreCase(hostname)) { + throw new Exception( + "unexpected request server name indication value"); + } + } + + private static String clientRequestedHostname; + private static String serverAcceptableHostname; + + private static void parseArguments(String[] args) { + clientRequestedHostname = args[0]; + serverAcceptableHostname = args[1]; + } + + /* + * ============================================================= + * The remainder is just support stuff + */ + volatile Exception serverException = null; + volatile Exception clientException = null; + + // use any free port by default + volatile int serverPort = 0; + + public static void main(String args[]) throws Exception { + if (debug) + System.setProperty("javax.net.debug", "all"); + + /* + * Get the customized arguments. + */ + parseArguments(args); + + new SSLEngineExplorerMatchedSNI(); + } + + Thread clientThread = null; + Thread serverThread = null; + + /* + * Primary constructor, used to drive remainder of the test. + * + * Fork off the other side, then do your work. + */ + SSLEngineExplorerMatchedSNI() throws Exception { + super("../../../../etc"); + + if (separateServerThread) { + startServer(true); + startClient(false); + } else { + startClient(true); + startServer(false); + } + + /* + * Wait for other side to close down. + */ + if (separateServerThread) { + serverThread.join(); + } else { + clientThread.join(); + } + + /* + * When we get here, the test is pretty much over. + * + * If the main thread excepted, that propagates back + * immediately. If the other thread threw an exception, we + * should report back. + */ + if (serverException != null) { + System.out.print("Server Exception:"); + throw serverException; + } + if (clientException != null) { + System.out.print("Client Exception:"); + throw clientException; + } + } + + void startServer(boolean newThread) throws Exception { + if (newThread) { + serverThread = new Thread() { + public void run() { + try { + doServerSide(); + } catch (Exception e) { + /* + * Our server thread just died. + * + * Release the client, if not active already... + */ + System.err.println("Server died..."); + System.err.println(e); + serverReady = true; + serverException = e; + } + } + }; + serverThread.start(); + } else { + doServerSide(); + } + } + + void startClient(boolean newThread) throws Exception { + if (newThread) { + clientThread = new Thread() { + public void run() { + try { + doClientSide(); + } catch (Exception e) { + /* + * Our client thread just died. + */ + System.err.println("Client died..."); + clientException = e; + } + } + }; + clientThread.start(); + } else { + doClientSide(); + } + } +} diff --git a/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLEngineExplorerUnmatchedSNI.java b/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLEngineExplorerUnmatchedSNI.java new file mode 100644 index 00000000000..2a9647be457 --- /dev/null +++ b/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLEngineExplorerUnmatchedSNI.java @@ -0,0 +1,406 @@ +/* + * 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. + * + * 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. + */ + +// +// SunJSSE does not support dynamic system properties, no way to re-use +// system properties in samevm/agentvm mode. +// + +/* + * @test + * @bug 7068321 + * @summary Support TLS Server Name Indication (SNI) Extension in JSSE Server + * @library ../NewAPIs/SSLEngine ../../../../templates + * @build SSLEngineService SSLCapabilities SSLExplorer + * @run main/othervm SSLEngineExplorerUnmatchedSNI www.example.com + * www\.example\.org + */ + +import javax.net.ssl.*; +import java.io.*; +import java.nio.*; +import java.net.*; +import java.util.*; +import java.nio.channels.*; + +public class SSLEngineExplorerUnmatchedSNI extends SSLEngineService { + + /* + * ============================================================= + * Set the various variables needed for the tests, then + * specify what tests to run on each side. + */ + + /* + * Should we run the client or server in a separate thread? + * Both sides can throw exceptions, but do you have a preference + * as to which side should be the main thread. + */ + static boolean separateServerThread = false; + + // Is the server ready to serve? + volatile static boolean serverReady = false; + + /* + * Turn on SSL debugging? + */ + static boolean debug = false; + + /* + * Define the server side of the test. + * + * If the server prematurely exits, serverReady will be set to true + * to avoid infinite hangs. + */ + void doServerSide() throws Exception { + + // create SSLEngine. + SSLEngine ssle = createSSLEngine(false); + + // Create a server socket channel. + InetSocketAddress isa = + new InetSocketAddress(InetAddress.getLocalHost(), serverPort); + ServerSocketChannel ssc = ServerSocketChannel.open(); + ssc.socket().bind(isa); + serverPort = ssc.socket().getLocalPort(); + + // Signal Client, we're ready for his connect. + serverReady = true; + + // Accept a socket channel. + SocketChannel sc = ssc.accept(); + + // Complete connection. + while (!sc.finishConnect()) { + Thread.sleep(50); + // waiting for the connection completed. + } + + ByteBuffer buffer = ByteBuffer.allocate(0xFF); + int position = 0; + SSLCapabilities capabilities = null; + + // Read the header of TLS record + buffer.limit(SSLExplorer.RECORD_HEADER_SIZE); + while (position < SSLExplorer.RECORD_HEADER_SIZE) { + int n = sc.read(buffer); + if (n < 0) { + throw new Exception("unexpected end of stream!"); + } + position += n; + } + buffer.flip(); + + int recordLength = SSLExplorer.getRequiredSize(buffer); + if (buffer.capacity() < recordLength) { + ByteBuffer oldBuffer = buffer; + buffer = ByteBuffer.allocate(recordLength); + buffer.put(oldBuffer); + } + + buffer.position(SSLExplorer.RECORD_HEADER_SIZE); + buffer.limit(buffer.capacity()); + while (position < recordLength) { + int n = sc.read(buffer); + if (n < 0) { + throw new Exception("unexpected end of stream!"); + } + position += n; + } + buffer.flip(); + + capabilities = SSLExplorer.explore(buffer); + if (capabilities != null) { + System.out.println("Record version: " + + capabilities.getRecordVersion()); + System.out.println("Hello version: " + + capabilities.getHelloVersion()); + } + + // enable server name indication checking + SNIMatcher matcher = SNIHostName.createSNIMatcher( + serverAcceptableHostname); + Collection matchers = new ArrayList<>(1); + matchers.add(matcher); + SSLParameters params = ssle.getSSLParameters(); + params.setSNIMatchers(matchers); + ssle.setSSLParameters(params); + + try { + // handshaking + handshaking(ssle, sc, buffer); + + // receive application data + receive(ssle, sc); + + // send out application data + deliver(ssle, sc); + + // check server name indication + ExtendedSSLSession session = (ExtendedSSLSession)ssle.getSession(); + checkCapabilities(capabilities, session); + + throw new Exception( + "Mismatched server name indication was accepted"); + } catch (SSLHandshakeException sslhe) { + // the expected unrecognized server name indication exception + } catch (IOException ioe) { + // the peer may have closed the socket because of the unmatched + // server name indication. + } finally { + // close the socket channel. + sc.close(); + ssc.close(); + } + } + + /* + * Define the client side of the test. + * + * If the server prematurely exits, serverReady will be set to true + * to avoid infinite hangs. + */ + void doClientSide() throws Exception { + // create SSLEngine. + SSLEngine ssle = createSSLEngine(true); + + /* + * Wait for server to get started. + */ + while (!serverReady) { + Thread.sleep(50); + } + + // Create a non-blocking socket channel. + SocketChannel sc = SocketChannel.open(); + sc.configureBlocking(false); + InetSocketAddress isa = + new InetSocketAddress(InetAddress.getLocalHost(), serverPort); + sc.connect(isa); + + // Complete connection. + while (!sc.finishConnect() ) { + Thread.sleep(50); + // waiting for the connection completed. + } + + SNIHostName serverName = new SNIHostName(clientRequestedHostname); + List serverNames = new ArrayList<>(1); + serverNames.add(serverName); + SSLParameters params = ssle.getSSLParameters(); + params.setServerNames(serverNames); + ssle.setSSLParameters(params); + + try { + // handshaking + handshaking(ssle, sc, null); + + // send out application data + deliver(ssle, sc); + + // receive application data + receive(ssle, sc); + + // check server name indication + ExtendedSSLSession session = (ExtendedSSLSession)ssle.getSession(); + checkSNIInSession(session); + + throw new Exception( + "Mismatched server name indication was accepted"); + } catch (SSLHandshakeException sslhe) { + // the expected unrecognized server name indication exception + } catch (IOException ioe) { + // the peer may have closed the socket because of the unmatched + // server name indication. + } finally { + // close the socket channel. + sc.close(); + } + } + + void checkCapabilities(SSLCapabilities capabilities, + ExtendedSSLSession session) throws Exception { + List sessionSNI = session.getRequestedServerNames(); + if (!sessionSNI.equals(capabilities.getServerNames())) { + for (SNIServerName sni : sessionSNI) { + System.out.println("SNI in session is " + sni); + } + + List capaSNI = capabilities.getServerNames(); + for (SNIServerName sni : capaSNI) { + System.out.println("SNI in session is " + sni); + } + + throw new Exception( + "server name indication does not match capabilities"); + } + + checkSNIInSession(session); + } + + void checkSNIInSession(ExtendedSSLSession session) throws Exception { + List sessionSNI = session.getRequestedServerNames(); + if (sessionSNI.isEmpty()) { + throw new Exception( + "unexpected empty request server name indication"); + } + + if (sessionSNI.size() != 1) { + throw new Exception( + "unexpected request server name indication"); + } + + SNIServerName serverName = sessionSNI.get(0); + if (!(serverName instanceof SNIHostName)) { + throw new Exception( + "unexpected instance of request server name indication"); + } + + String hostname = ((SNIHostName)serverName).getAsciiName(); + if (!clientRequestedHostname.equalsIgnoreCase(hostname)) { + throw new Exception( + "unexpected request server name indication value"); + } + } + + private static String clientRequestedHostname; + private static String serverAcceptableHostname; + + private static void parseArguments(String[] args) { + clientRequestedHostname = args[0]; + serverAcceptableHostname = args[1]; + } + + /* + * ============================================================= + * The remainder is just support stuff + */ + volatile Exception serverException = null; + volatile Exception clientException = null; + + // use any free port by default + volatile int serverPort = 0; + + public static void main(String args[]) throws Exception { + if (debug) + System.setProperty("javax.net.debug", "all"); + + /* + * Get the customized arguments. + */ + parseArguments(args); + + new SSLEngineExplorerUnmatchedSNI(); + } + + Thread clientThread = null; + Thread serverThread = null; + + /* + * Primary constructor, used to drive remainder of the test. + * + * Fork off the other side, then do your work. + */ + SSLEngineExplorerUnmatchedSNI() throws Exception { + super("../../../../etc"); + + if (separateServerThread) { + startServer(true); + startClient(false); + } else { + startClient(true); + startServer(false); + } + + /* + * Wait for other side to close down. + */ + if (separateServerThread) { + serverThread.join(); + } else { + clientThread.join(); + } + + /* + * When we get here, the test is pretty much over. + * + * If the main thread excepted, that propagates back + * immediately. If the other thread threw an exception, we + * should report back. + */ + if (serverException != null) { + System.out.print("Server Exception:"); + throw serverException; + } + if (clientException != null) { + System.out.print("Client Exception:"); + throw clientException; + } + } + + void startServer(boolean newThread) throws Exception { + if (newThread) { + serverThread = new Thread() { + public void run() { + try { + doServerSide(); + } catch (Exception e) { + /* + * Our server thread just died. + * + * Release the client, if not active already... + */ + System.err.println("Server died..."); + System.err.println(e); + serverReady = true; + serverException = e; + } + } + }; + serverThread.start(); + } else { + doServerSide(); + } + } + + void startClient(boolean newThread) throws Exception { + if (newThread) { + clientThread = new Thread() { + public void run() { + try { + doClientSide(); + } catch (Exception e) { + /* + * Our client thread just died. + */ + System.err.println("Client died..."); + clientException = e; + } + } + }; + clientThread.start(); + } else { + doClientSide(); + } + } +} diff --git a/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLEngineExplorerWithCli.java b/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLEngineExplorerWithCli.java new file mode 100644 index 00000000000..bfa23cb38de --- /dev/null +++ b/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLEngineExplorerWithCli.java @@ -0,0 +1,365 @@ +/* + * 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. + * + * 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. + */ + +// +// SunJSSE does not support dynamic system properties, no way to re-use +// system properties in samevm/agentvm mode. +// + +/* + * @test + * @bug 7068321 + * @summary Support TLS Server Name Indication (SNI) Extension in JSSE Server + * @library ../NewAPIs/SSLEngine ../../../../templates + * @build SSLEngineService SSLCapabilities SSLExplorer + * @run main/othervm SSLEngineExplorerWithCli + */ + +import javax.net.ssl.*; +import java.nio.*; +import java.net.*; +import java.util.*; +import java.nio.channels.*; + +public class SSLEngineExplorerWithCli extends SSLEngineService { + + /* + * ============================================================= + * Set the various variables needed for the tests, then + * specify what tests to run on each side. + */ + + /* + * Should we run the client or server in a separate thread? + * Both sides can throw exceptions, but do you have a preference + * as to which side should be the main thread. + */ + static boolean separateServerThread = true; + + // Is the server ready to serve? + volatile static boolean serverReady = false; + + /* + * Turn on SSL debugging? + */ + static boolean debug = false; + + /* + * Define the server side of the test. + * + * If the server prematurely exits, serverReady will be set to true + * to avoid infinite hangs. + */ + void doServerSide() throws Exception { + + // create SSLEngine. + SSLEngine ssle = createSSLEngine(false); + + // Create a server socket channel. + InetSocketAddress isa = + new InetSocketAddress(InetAddress.getLocalHost(), serverPort); + ServerSocketChannel ssc = ServerSocketChannel.open(); + ssc.socket().bind(isa); + serverPort = ssc.socket().getLocalPort(); + + // Signal Client, we're ready for his connect. + serverReady = true; + + // Accept a socket channel. + SocketChannel sc = ssc.accept(); + + // Complete connection. + while (!sc.finishConnect()) { + Thread.sleep(50); + // waiting for the connection completed. + } + + ByteBuffer buffer = ByteBuffer.allocate(0xFF); + int position = 0; + SSLCapabilities capabilities = null; + + // Read the header of TLS record + buffer.limit(SSLExplorer.RECORD_HEADER_SIZE); + while (position < SSLExplorer.RECORD_HEADER_SIZE) { + int n = sc.read(buffer); + if (n < 0) { + throw new Exception("unexpected end of stream!"); + } + position += n; + } + buffer.flip(); + + int recordLength = SSLExplorer.getRequiredSize(buffer); + if (buffer.capacity() < recordLength) { + ByteBuffer oldBuffer = buffer; + buffer = ByteBuffer.allocate(recordLength); + buffer.put(oldBuffer); + } + + buffer.position(SSLExplorer.RECORD_HEADER_SIZE); + buffer.limit(buffer.capacity()); + while (position < recordLength) { + int n = sc.read(buffer); + if (n < 0) { + throw new Exception("unexpected end of stream!"); + } + position += n; + } + buffer.flip(); + + capabilities = SSLExplorer.explore(buffer); + if (capabilities != null) { + System.out.println("Record version: " + + capabilities.getRecordVersion()); + System.out.println("Hello version: " + + capabilities.getHelloVersion()); + } + + // handshaking + handshaking(ssle, sc, buffer); + + // receive application data + receive(ssle, sc); + + // send out application data + deliver(ssle, sc); + + ExtendedSSLSession session = (ExtendedSSLSession)ssle.getSession(); + checkCapabilities(capabilities, session); + + // close the socket channel. + sc.close(); + ssc.close(); + } + + /* + * Define the client side of the test. + * + * If the server prematurely exits, serverReady will be set to true + * to avoid infinite hangs. + */ + void doClientSide() throws Exception { + // create SSLEngine. + SSLEngine ssle = createSSLEngine(true); + + /* + * Wait for server to get started. + */ + while (!serverReady) { + Thread.sleep(50); + } + + // Create a non-blocking socket channel. + SocketChannel sc = SocketChannel.open(); + sc.configureBlocking(false); + InetSocketAddress isa = + new InetSocketAddress(InetAddress.getLocalHost(), serverPort); + sc.connect(isa); + + // Complete connection. + while (!sc.finishConnect() ) { + Thread.sleep(50); + // waiting for the connection completed. + } + + SNIHostName serverName = new SNIHostName(clientRequestedHostname); + List serverNames = new ArrayList<>(1); + serverNames.add(serverName); + SSLParameters params = ssle.getSSLParameters(); + params.setServerNames(serverNames); + ssle.setSSLParameters(params); + + // handshaking + handshaking(ssle, sc, null); + + // send out application data + deliver(ssle, sc); + + // receive application data + receive(ssle, sc); + + // check server name indication + ExtendedSSLSession session = (ExtendedSSLSession)ssle.getSession(); + checkSNIInSession(session); + + // close the socket channel. + sc.close(); + } + + private static String clientRequestedHostname = "www.example.com"; + private static String serverAcceptableHostname = + "www\\.example\\.(com|org)"; + + void checkCapabilities(SSLCapabilities capabilities, + ExtendedSSLSession session) throws Exception { + List sessionSNI = session.getRequestedServerNames(); + if (!sessionSNI.equals(capabilities.getServerNames())) { + for (SNIServerName sni : sessionSNI) { + System.out.println("SNI in session is " + sni); + } + + List capaSNI = capabilities.getServerNames(); + for (SNIServerName sni : capaSNI) { + System.out.println("SNI in session is " + sni); + } + + throw new Exception( + "server name indication does not match capabilities"); + } + + checkSNIInSession(session); + } + + void checkSNIInSession(ExtendedSSLSession session) throws Exception { + List sessionSNI = session.getRequestedServerNames(); + if (sessionSNI.isEmpty()) { + throw new Exception( + "unexpected empty request server name indication"); + } + + if (sessionSNI.size() != 1) { + throw new Exception( + "unexpected request server name indication"); + } + + SNIServerName serverName = sessionSNI.get(0); + if (!(serverName instanceof SNIHostName)) { + throw new Exception( + "unexpected instance of request server name indication"); + } + + String hostname = ((SNIHostName)serverName).getAsciiName(); + if (!clientRequestedHostname.equalsIgnoreCase(hostname)) { + throw new Exception( + "unexpected request server name indication value"); + } + } + + /* + * ============================================================= + * The remainder is just support stuff + */ + volatile Exception serverException = null; + volatile Exception clientException = null; + + // use any free port by default + volatile int serverPort = 0; + + public static void main(String args[]) throws Exception { + if (debug) + System.setProperty("javax.net.debug", "all"); + + new SSLEngineExplorerWithCli(); + } + + Thread clientThread = null; + Thread serverThread = null; + + /* + * Primary constructor, used to drive remainder of the test. + * + * Fork off the other side, then do your work. + */ + SSLEngineExplorerWithCli() throws Exception { + super("../../../../etc"); + + if (separateServerThread) { + startServer(true); + startClient(false); + } else { + startClient(true); + startServer(false); + } + + /* + * Wait for other side to close down. + */ + if (separateServerThread) { + serverThread.join(); + } else { + clientThread.join(); + } + + /* + * When we get here, the test is pretty much over. + * + * If the main thread excepted, that propagates back + * immediately. If the other thread threw an exception, we + * should report back. + */ + if (serverException != null) { + System.out.print("Server Exception:"); + throw serverException; + } + if (clientException != null) { + System.out.print("Client Exception:"); + throw clientException; + } + } + + void startServer(boolean newThread) throws Exception { + if (newThread) { + serverThread = new Thread() { + public void run() { + try { + doServerSide(); + } catch (Exception e) { + /* + * Our server thread just died. + * + * Release the client, if not active already... + */ + System.err.println("Server died..."); + System.err.println(e); + serverReady = true; + serverException = e; + } + } + }; + serverThread.start(); + } else { + doServerSide(); + } + } + + void startClient(boolean newThread) throws Exception { + if (newThread) { + clientThread = new Thread() { + public void run() { + try { + doClientSide(); + } catch (Exception e) { + /* + * Our client thread just died. + */ + System.err.println("Client died..."); + clientException = e; + } + } + }; + clientThread.start(); + } else { + doClientSide(); + } + } +} diff --git a/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLEngineExplorerWithSrv.java b/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLEngineExplorerWithSrv.java new file mode 100644 index 00000000000..e8fd2707f25 --- /dev/null +++ b/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLEngineExplorerWithSrv.java @@ -0,0 +1,351 @@ +/* + * 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. + * + * 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. + */ + +// +// SunJSSE does not support dynamic system properties, no way to re-use +// system properties in samevm/agentvm mode. +// + +/* + * @test + * @bug 7068321 + * @summary Support TLS Server Name Indication (SNI) Extension in JSSE Server + * @library ../NewAPIs/SSLEngine ../../../../templates + * @build SSLEngineService SSLCapabilities SSLExplorer + * @run main/othervm SSLEngineExplorerWithSrv + */ + +import javax.net.ssl.*; +import java.nio.*; +import java.net.*; +import java.util.*; +import java.nio.channels.*; + +public class SSLEngineExplorerWithSrv extends SSLEngineService { + + /* + * ============================================================= + * Set the various variables needed for the tests, then + * specify what tests to run on each side. + */ + + /* + * Should we run the client or server in a separate thread? + * Both sides can throw exceptions, but do you have a preference + * as to which side should be the main thread. + */ + static boolean separateServerThread = true; + + // Is the server ready to serve? + volatile static boolean serverReady = false; + + /* + * Turn on SSL debugging? + */ + static boolean debug = false; + + /* + * Define the server side of the test. + * + * If the server prematurely exits, serverReady will be set to true + * to avoid infinite hangs. + */ + void doServerSide() throws Exception { + + // create SSLEngine. + SSLEngine ssle = createSSLEngine(false); + + // Create a server socket channel. + InetSocketAddress isa = + new InetSocketAddress(InetAddress.getLocalHost(), serverPort); + ServerSocketChannel ssc = ServerSocketChannel.open(); + ssc.socket().bind(isa); + serverPort = ssc.socket().getLocalPort(); + + // Signal Client, we're ready for his connect. + serverReady = true; + + // Accept a socket channel. + SocketChannel sc = ssc.accept(); + + // Complete connection. + while (!sc.finishConnect()) { + Thread.sleep(50); + // waiting for the connection completed. + } + + ByteBuffer buffer = ByteBuffer.allocate(0xFF); + int position = 0; + SSLCapabilities capabilities = null; + + // Read the header of TLS record + buffer.limit(SSLExplorer.RECORD_HEADER_SIZE); + while (position < SSLExplorer.RECORD_HEADER_SIZE) { + int n = sc.read(buffer); + if (n < 0) { + throw new Exception("unexpected end of stream!"); + } + position += n; + } + buffer.flip(); + + int recordLength = SSLExplorer.getRequiredSize(buffer); + if (buffer.capacity() < recordLength) { + ByteBuffer oldBuffer = buffer; + buffer = ByteBuffer.allocate(recordLength); + buffer.put(oldBuffer); + } + + buffer.position(SSLExplorer.RECORD_HEADER_SIZE); + buffer.limit(buffer.capacity()); + while (position < recordLength) { + int n = sc.read(buffer); + if (n < 0) { + throw new Exception("unexpected end of stream!"); + } + position += n; + } + buffer.flip(); + + capabilities = SSLExplorer.explore(buffer); + if (capabilities != null) { + System.out.println("Record version: " + + capabilities.getRecordVersion()); + System.out.println("Hello version: " + + capabilities.getHelloVersion()); + } + + // enable server name indication checking + SNIMatcher matcher = SNIHostName.createSNIMatcher( + serverAcceptableHostname); + Collection matchers = new ArrayList<>(1); + matchers.add(matcher); + SSLParameters params = ssle.getSSLParameters(); + params.setSNIMatchers(matchers); + ssle.setSSLParameters(params); + + // handshaking + handshaking(ssle, sc, buffer); + + // receive application data + receive(ssle, sc); + + // send out application data + deliver(ssle, sc); + + // check server name indication + ExtendedSSLSession session = (ExtendedSSLSession)ssle.getSession(); + checkCapabilities(capabilities, session); + + // close the socket channel. + sc.close(); + ssc.close(); + } + + /* + * Define the client side of the test. + * + * If the server prematurely exits, serverReady will be set to true + * to avoid infinite hangs. + */ + void doClientSide() throws Exception { + // create SSLEngine. + SSLEngine ssle = createSSLEngine(true); + + /* + * Wait for server to get started. + */ + while (!serverReady) { + Thread.sleep(50); + } + + // Create a non-blocking socket channel. + SocketChannel sc = SocketChannel.open(); + sc.configureBlocking(false); + InetSocketAddress isa = + new InetSocketAddress(InetAddress.getLocalHost(), serverPort); + sc.connect(isa); + + // Complete connection. + while (!sc.finishConnect() ) { + Thread.sleep(50); + // waiting for the connection completed. + } + + // handshaking + handshaking(ssle, sc, null); + + // send out application data + deliver(ssle, sc); + + // receive application data + receive(ssle, sc); + + // check server name indication + ExtendedSSLSession session = (ExtendedSSLSession)ssle.getSession(); + checkSNIInSession(session); + + // close the socket channel. + sc.close(); + } + + private static String clientRequestedHostname = "www.example.com"; + private static String serverAcceptableHostname = + "www\\.example\\.(com|org)"; + + void checkCapabilities(SSLCapabilities capabilities, + ExtendedSSLSession session) throws Exception { + List sessionSNI = session.getRequestedServerNames(); + if (!sessionSNI.equals(capabilities.getServerNames())) { + for (SNIServerName sni : sessionSNI) { + System.out.println("SNI in session is " + sni); + } + + List capaSNI = capabilities.getServerNames(); + for (SNIServerName sni : capaSNI) { + System.out.println("SNI in session is " + sni); + } + + throw new Exception( + "server name indication does not match capabilities"); + } + + checkSNIInSession(session); + } + + void checkSNIInSession(ExtendedSSLSession session) throws Exception { + List sessionSNI = session.getRequestedServerNames(); + if (!sessionSNI.isEmpty()) { + throw new Exception( + "should be empty request server name indication"); + } + } + + /* + * ============================================================= + * The remainder is just support stuff + */ + volatile Exception serverException = null; + volatile Exception clientException = null; + + // use any free port by default + volatile int serverPort = 0; + + public static void main(String args[]) throws Exception { + if (debug) + System.setProperty("javax.net.debug", "all"); + + new SSLEngineExplorerWithSrv(); + } + + Thread clientThread = null; + Thread serverThread = null; + + /* + * Primary constructor, used to drive remainder of the test. + * + * Fork off the other side, then do your work. + */ + SSLEngineExplorerWithSrv() throws Exception { + super("../../../../etc"); + + if (separateServerThread) { + startServer(true); + startClient(false); + } else { + startClient(true); + startServer(false); + } + + /* + * Wait for other side to close down. + */ + if (separateServerThread) { + serverThread.join(); + } else { + clientThread.join(); + } + + /* + * When we get here, the test is pretty much over. + * + * If the main thread excepted, that propagates back + * immediately. If the other thread threw an exception, we + * should report back. + */ + if (serverException != null) { + System.out.print("Server Exception:"); + throw serverException; + } + if (clientException != null) { + System.out.print("Client Exception:"); + throw clientException; + } + } + + void startServer(boolean newThread) throws Exception { + if (newThread) { + serverThread = new Thread() { + public void run() { + try { + doServerSide(); + } catch (Exception e) { + /* + * Our server thread just died. + * + * Release the client, if not active already... + */ + System.err.println("Server died..."); + System.err.println(e); + serverReady = true; + serverException = e; + } + } + }; + serverThread.start(); + } else { + doServerSide(); + } + } + + void startClient(boolean newThread) throws Exception { + if (newThread) { + clientThread = new Thread() { + public void run() { + try { + doClientSide(); + } catch (Exception e) { + /* + * Our client thread just died. + */ + System.err.println("Client died..."); + clientException = e; + } + } + }; + clientThread.start(); + } else { + doClientSide(); + } + } +} diff --git a/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLSocketConsistentSNI.java b/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLSocketConsistentSNI.java new file mode 100644 index 00000000000..d9ff691e06c --- /dev/null +++ b/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLSocketConsistentSNI.java @@ -0,0 +1,357 @@ +/* + * 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. + * + * 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. + */ + +// +// SunJSSE does not support dynamic system properties, no way to re-use +// system properties in samevm/agentvm mode. +// + +/** + * @test + * @bug 7068321 + * @summary Support TLS Server Name Indication (SNI) Extension in JSSE Server + * @run main/othervm SSLSocketConsistentSNI + */ + +import java.io.*; +import java.nio.*; +import java.nio.channels.*; +import java.util.*; +import java.net.*; +import javax.net.ssl.*; + +public class SSLSocketConsistentSNI { + + /* + * ============================================================= + * Set the various variables needed for the tests, then + * specify what tests to run on each side. + */ + + /* + * Should we run the client or server in a separate thread? + * Both sides can throw exceptions, but do you have a preference + * as to which side should be the main thread. + */ + static boolean separateServerThread = true; + + /* + * Where do we find the keystores? + */ + static String pathToStores = "../../../../etc"; + static String keyStoreFile = "keystore"; + static String trustStoreFile = "truststore"; + static String passwd = "passphrase"; + + /* + * Is the server ready to serve? + */ + volatile static boolean serverReady = false; + + /* + * Turn on SSL debugging? + */ + static boolean debug = false; + + /* + * If the client or server is doing some kind of object creation + * that the other side depends on, and that thread prematurely + * exits, you may experience a hang. The test harness will + * terminate all hung threads after its timeout has expired, + * currently 3 minutes by default, but you might try to be + * smart about it.... + */ + + /* + * Define the server side of the test. + * + * If the server prematurely exits, serverReady will be set to true + * to avoid infinite hangs. + */ + void doServerSide() throws Exception { + SSLServerSocketFactory sslssf = + (SSLServerSocketFactory) SSLServerSocketFactory.getDefault(); + SSLServerSocket sslServerSocket = + (SSLServerSocket) sslssf.createServerSocket(serverPort); + + SNIMatcher matcher = SNIHostName.createSNIMatcher( + serverAcceptableHostname); + Collection matchers = new ArrayList<>(1); + matchers.add(matcher); + SSLParameters params = sslServerSocket.getSSLParameters(); + params.setSNIMatchers(matchers); + sslServerSocket.setSSLParameters(params); + + serverPort = sslServerSocket.getLocalPort(); + + /* + * Signal Client, we're ready for his connect. + */ + serverReady = true; + + SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept(); + try { + InputStream sslIS = sslSocket.getInputStream(); + OutputStream sslOS = sslSocket.getOutputStream(); + + sslIS.read(); + sslOS.write(85); + sslOS.flush(); + + ExtendedSSLSession session = + (ExtendedSSLSession)sslSocket.getSession(); + checkSNIInSession(session); + } finally { + sslSocket.close(); + sslServerSocket.close(); + } + } + + + /* + * Define the client side of the test. + * + * If the server prematurely exits, serverReady will be set to true + * to avoid infinite hangs. + */ + void doClientSide() throws Exception { + + /* + * Wait for server to get started. + */ + while (!serverReady) { + Thread.sleep(50); + } + + SSLSocketFactory sslsf = + (SSLSocketFactory) SSLSocketFactory.getDefault(); + SSLSocket sslSocket = (SSLSocket) + sslsf.createSocket("localhost", serverPort); + + SNIHostName serverName = new SNIHostName(clientRequestedHostname); + List serverNames = new ArrayList<>(1); + serverNames.add(serverName); + SSLParameters params = sslSocket.getSSLParameters(); + params.setServerNames(serverNames); + sslSocket.setSSLParameters(params); + + try { + InputStream sslIS = sslSocket.getInputStream(); + OutputStream sslOS = sslSocket.getOutputStream(); + + sslOS.write(280); + sslOS.flush(); + sslIS.read(); + + ExtendedSSLSession session = + (ExtendedSSLSession)sslSocket.getSession(); + checkSNIInSession(session); + } finally { + sslSocket.close(); + } + } + + private static String clientRequestedHostname = "www.example.com"; + private static String serverAcceptableHostname = "www\\.example\\.com"; + + void checkSNIInSession(ExtendedSSLSession session) throws Exception { + List sessionSNI = session.getRequestedServerNames(); + if (sessionSNI.isEmpty()) { + throw new Exception( + "unexpected empty request server name indication"); + } + + if (sessionSNI.size() != 1) { + throw new Exception( + "unexpected request server name indication"); + } + + SNIServerName serverName = sessionSNI.get(0); + if (!(serverName instanceof SNIHostName)) { + throw new Exception( + "unexpected instance of request server name indication"); + } + + String hostname = ((SNIHostName)serverName).getAsciiName(); + if (!clientRequestedHostname.equalsIgnoreCase(hostname)) { + throw new Exception( + "unexpected request server name indication value"); + } + } + + /* + * ============================================================= + * The remainder is just support stuff + */ + + // use any free port by default + volatile int serverPort = 0; + + volatile Exception serverException = null; + volatile Exception clientException = null; + + public static void main(String[] args) throws Exception { + String keyFilename = + System.getProperty("test.src", ".") + "/" + pathToStores + + "/" + keyStoreFile; + String trustFilename = + System.getProperty("test.src", ".") + "/" + pathToStores + + "/" + trustStoreFile; + + System.setProperty("javax.net.ssl.keyStore", keyFilename); + System.setProperty("javax.net.ssl.keyStorePassword", passwd); + System.setProperty("javax.net.ssl.trustStore", trustFilename); + System.setProperty("javax.net.ssl.trustStorePassword", passwd); + + if (debug) + System.setProperty("javax.net.debug", "all"); + + /* + * Start the tests. + */ + new SSLSocketConsistentSNI(); + } + + Thread clientThread = null; + Thread serverThread = null; + + /* + * Primary constructor, used to drive remainder of the test. + * + * Fork off the other side, then do your work. + */ + SSLSocketConsistentSNI() throws Exception { + try { + if (separateServerThread) { + startServer(true); + startClient(false); + } else { + startClient(true); + startServer(false); + } + } catch (Exception e) { + // swallow for now. Show later + } + + /* + * Wait for other side to close down. + */ + if (separateServerThread) { + serverThread.join(); + } else { + clientThread.join(); + } + + /* + * When we get here, the test is pretty much over. + * Which side threw the error? + */ + Exception local; + Exception remote; + String whichRemote; + + if (separateServerThread) { + remote = serverException; + local = clientException; + whichRemote = "server"; + } else { + remote = clientException; + local = serverException; + whichRemote = "client"; + } + + /* + * If both failed, return the curthread's exception, but also + * print the remote side Exception + */ + if ((local != null) && (remote != null)) { + System.out.println(whichRemote + " also threw:"); + remote.printStackTrace(); + System.out.println(); + throw local; + } + + if (remote != null) { + throw remote; + } + + if (local != null) { + throw local; + } + } + + void startServer(boolean newThread) throws Exception { + if (newThread) { + serverThread = new Thread() { + public void run() { + try { + doServerSide(); + } catch (Exception e) { + /* + * Our server thread just died. + * + * Release the client, if not active already... + */ + System.err.println("Server died..."); + serverReady = true; + serverException = e; + } + } + }; + serverThread.start(); + } else { + try { + doServerSide(); + } catch (Exception e) { + serverException = e; + } finally { + serverReady = true; + } + } + } + + void startClient(boolean newThread) throws Exception { + if (newThread) { + clientThread = new Thread() { + public void run() { + try { + doClientSide(); + } catch (Exception e) { + /* + * Our client thread just died. + */ + System.err.println("Client died..."); + clientException = e; + } + } + }; + clientThread.start(); + } else { + try { + doClientSide(); + } catch (Exception e) { + clientException = e; + } + } + } +} diff --git a/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLSocketExplorer.java b/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLSocketExplorer.java new file mode 100644 index 00000000000..27e2b6abce7 --- /dev/null +++ b/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLSocketExplorer.java @@ -0,0 +1,375 @@ +/* + * 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. + * + * 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. + */ + +// +// SunJSSE does not support dynamic system properties, no way to re-use +// system properties in samevm/agentvm mode. +// + +/** + * @test + * @bug 7068321 + * @summary Support TLS Server Name Indication (SNI) Extension in JSSE Server + * @library ../../../../templates + * @build SSLCapabilities SSLExplorer + * @run main/othervm SSLSocketExplorer SSLv2Hello,SSLv3 + * @run main/othervm SSLSocketExplorer SSLv3 + * @run main/othervm SSLSocketExplorer TLSv1 + * @run main/othervm SSLSocketExplorer TLSv1.1 + * @run main/othervm SSLSocketExplorer TLSv1.2 + */ + +import java.io.*; +import java.nio.*; +import java.nio.channels.*; +import java.util.*; +import java.net.*; +import javax.net.ssl.*; + +public class SSLSocketExplorer { + + /* + * ============================================================= + * Set the various variables needed for the tests, then + * specify what tests to run on each side. + */ + + /* + * Should we run the client or server in a separate thread? + * Both sides can throw exceptions, but do you have a preference + * as to which side should be the main thread. + */ + static boolean separateServerThread = true; + + /* + * Where do we find the keystores? + */ + static String pathToStores = "../../../../etc"; + static String keyStoreFile = "keystore"; + static String trustStoreFile = "truststore"; + static String passwd = "passphrase"; + + /* + * Is the server ready to serve? + */ + volatile static boolean serverReady = false; + + /* + * Turn on SSL debugging? + */ + static boolean debug = false; + + /* + * If the client or server is doing some kind of object creation + * that the other side depends on, and that thread prematurely + * exits, you may experience a hang. The test harness will + * terminate all hung threads after its timeout has expired, + * currently 3 minutes by default, but you might try to be + * smart about it.... + */ + + /* + * Define the server side of the test. + * + * If the server prematurely exits, serverReady will be set to true + * to avoid infinite hangs. + */ + void doServerSide() throws Exception { + + ServerSocket serverSocket = new ServerSocket(serverPort); + + // Signal Client, we're ready for his connect. + serverPort = serverSocket.getLocalPort(); + serverReady = true; + + Socket socket = serverSocket.accept(); + InputStream ins = socket.getInputStream(); + + byte[] buffer = new byte[0xFF]; + int position = 0; + SSLCapabilities capabilities = null; + + // Read the header of TLS record + while (position < SSLExplorer.RECORD_HEADER_SIZE) { + int count = SSLExplorer.RECORD_HEADER_SIZE - position; + int n = ins.read(buffer, position, count); + if (n < 0) { + throw new Exception("unexpected end of stream!"); + } + position += n; + } + + int recordLength = SSLExplorer.getRequiredSize(buffer, 0, position); + if (buffer.length < recordLength) { + buffer = Arrays.copyOf(buffer, recordLength); + } + + while (position < recordLength) { + int count = recordLength - position; + int n = ins.read(buffer, position, count); + if (n < 0) { + throw new Exception("unexpected end of stream!"); + } + position += n; + } + + capabilities = SSLExplorer.explore(buffer, 0, recordLength);; + if (capabilities != null) { + System.out.println("Record version: " + + capabilities.getRecordVersion()); + System.out.println("Hello version: " + + capabilities.getHelloVersion()); + } + + SSLSocketFactory sslsf = + (SSLSocketFactory) SSLSocketFactory.getDefault(); + ByteArrayInputStream bais = + new ByteArrayInputStream(buffer, 0, position); + SSLSocket sslSocket = (SSLSocket)sslsf.createSocket(socket, bais, true); + + InputStream sslIS = sslSocket.getInputStream(); + OutputStream sslOS = sslSocket.getOutputStream(); + + sslIS.read(); + sslOS.write(85); + sslOS.flush(); + + ExtendedSSLSession session = (ExtendedSSLSession)sslSocket.getSession(); + checkCapabilities(capabilities, session); + + sslSocket.close(); + serverSocket.close(); + } + + + /* + * Define the client side of the test. + * + * If the server prematurely exits, serverReady will be set to true + * to avoid infinite hangs. + */ + void doClientSide() throws Exception { + + /* + * Wait for server to get started. + */ + while (!serverReady) { + Thread.sleep(50); + } + + SSLSocketFactory sslsf = + (SSLSocketFactory) SSLSocketFactory.getDefault(); + SSLSocket sslSocket = (SSLSocket) + sslsf.createSocket("localhost", serverPort); + + // enable the specified TLS protocol + sslSocket.setEnabledProtocols(supportedProtocols); + + InputStream sslIS = sslSocket.getInputStream(); + OutputStream sslOS = sslSocket.getOutputStream(); + + sslOS.write(280); + sslOS.flush(); + sslIS.read(); + + sslSocket.close(); + } + + void checkCapabilities(SSLCapabilities capabilities, + ExtendedSSLSession session) throws Exception { + + List sessionSNI = session.getRequestedServerNames(); + if (!sessionSNI.equals(capabilities.getServerNames())) { + throw new Exception( + "server name indication does not match capabilities"); + } + } + + private static String[] supportedProtocols; // supported protocols + + private static void parseArguments(String[] args) { + supportedProtocols = args[0].split(","); + } + + + /* + * ============================================================= + * The remainder is just support stuff + */ + + // use any free port by default + volatile int serverPort = 0; + + volatile Exception serverException = null; + volatile Exception clientException = null; + + public static void main(String[] args) throws Exception { + String keyFilename = + System.getProperty("test.src", ".") + "/" + pathToStores + + "/" + keyStoreFile; + String trustFilename = + System.getProperty("test.src", ".") + "/" + pathToStores + + "/" + trustStoreFile; + + System.setProperty("javax.net.ssl.keyStore", keyFilename); + System.setProperty("javax.net.ssl.keyStorePassword", passwd); + System.setProperty("javax.net.ssl.trustStore", trustFilename); + System.setProperty("javax.net.ssl.trustStorePassword", passwd); + + if (debug) + System.setProperty("javax.net.debug", "all"); + + /* + * Get the customized arguments. + */ + parseArguments(args); + + /* + * Start the tests. + */ + new SSLSocketExplorer(); + } + + Thread clientThread = null; + Thread serverThread = null; + + /* + * Primary constructor, used to drive remainder of the test. + * + * Fork off the other side, then do your work. + */ + SSLSocketExplorer() throws Exception { + try { + if (separateServerThread) { + startServer(true); + startClient(false); + } else { + startClient(true); + startServer(false); + } + } catch (Exception e) { + // swallow for now. Show later + } + + /* + * Wait for other side to close down. + */ + if (separateServerThread) { + serverThread.join(); + } else { + clientThread.join(); + } + + /* + * When we get here, the test is pretty much over. + * Which side threw the error? + */ + Exception local; + Exception remote; + String whichRemote; + + if (separateServerThread) { + remote = serverException; + local = clientException; + whichRemote = "server"; + } else { + remote = clientException; + local = serverException; + whichRemote = "client"; + } + + /* + * If both failed, return the curthread's exception, but also + * print the remote side Exception + */ + if ((local != null) && (remote != null)) { + System.out.println(whichRemote + " also threw:"); + remote.printStackTrace(); + System.out.println(); + throw local; + } + + if (remote != null) { + throw remote; + } + + if (local != null) { + throw local; + } + } + + void startServer(boolean newThread) throws Exception { + if (newThread) { + serverThread = new Thread() { + public void run() { + try { + doServerSide(); + } catch (Exception e) { + /* + * Our server thread just died. + * + * Release the client, if not active already... + */ + System.err.println("Server died..."); + serverReady = true; + serverException = e; + } + } + }; + serverThread.start(); + } else { + try { + doServerSide(); + } catch (Exception e) { + serverException = e; + } finally { + serverReady = true; + } + } + } + + void startClient(boolean newThread) throws Exception { + if (newThread) { + clientThread = new Thread() { + public void run() { + try { + doClientSide(); + } catch (Exception e) { + /* + * Our client thread just died. + */ + System.err.println("Client died..."); + clientException = e; + } + } + }; + clientThread.start(); + } else { + try { + doClientSide(); + } catch (Exception e) { + clientException = e; + } + } + } +} diff --git a/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLSocketExplorerFailure.java b/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLSocketExplorerFailure.java new file mode 100644 index 00000000000..44a37150ef8 --- /dev/null +++ b/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLSocketExplorerFailure.java @@ -0,0 +1,383 @@ +/* + * 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. + * + * 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. + */ + +// +// SunJSSE does not support dynamic system properties, no way to re-use +// system properties in samevm/agentvm mode. +// + +/** + * @test + * @bug 7068321 + * @summary Support TLS Server Name Indication (SNI) Extension in JSSE Server + * @library ../../../../templates + * @build SSLCapabilities SSLExplorer + * @run main/othervm SSLSocketExplorerFailure SSLv2Hello,SSLv3 + * @run main/othervm SSLSocketExplorerFailure SSLv3 + * @run main/othervm SSLSocketExplorerFailure TLSv1 + * @run main/othervm SSLSocketExplorerFailure TLSv1.1 + * @run main/othervm SSLSocketExplorerFailure TLSv1.2 + */ + +import java.io.*; +import java.nio.*; +import java.nio.channels.*; +import java.util.*; +import java.net.*; +import javax.net.ssl.*; + +public class SSLSocketExplorerFailure { + + /* + * ============================================================= + * Set the various variables needed for the tests, then + * specify what tests to run on each side. + */ + + /* + * Should we run the client or server in a separate thread? + * Both sides can throw exceptions, but do you have a preference + * as to which side should be the main thread. + */ + static boolean separateServerThread = true; + + /* + * Where do we find the keystores? + */ + static String pathToStores = "../../../../etc"; + static String keyStoreFile = "keystore"; + static String trustStoreFile = "truststore"; + static String passwd = "passphrase"; + + /* + * Is the server ready to serve? + */ + volatile static boolean serverReady = false; + + /* + * Turn on SSL debugging? + */ + static boolean debug = false; + + /* + * If the client or server is doing some kind of object creation + * that the other side depends on, and that thread prematurely + * exits, you may experience a hang. The test harness will + * terminate all hung threads after its timeout has expired, + * currently 3 minutes by default, but you might try to be + * smart about it.... + */ + + /* + * Define the server side of the test. + * + * If the server prematurely exits, serverReady will be set to true + * to avoid infinite hangs. + */ + void doServerSide() throws Exception { + + ServerSocket serverSocket = new ServerSocket(serverPort); + + // Signal Client, we're ready for his connect. + serverPort = serverSocket.getLocalPort(); + serverReady = true; + + Socket socket = serverSocket.accept(); + InputStream ins = socket.getInputStream(); + + byte[] buffer = new byte[0xFF]; + int position = 0; + SSLCapabilities capabilities = null; + boolean failed = false; + try { + // Read the header of TLS record + while (position < SSLExplorer.RECORD_HEADER_SIZE) { + int count = SSLExplorer.RECORD_HEADER_SIZE - position; + int n = ins.read(buffer, position, count); + if (n < 0) { + throw new Exception("unexpected end of stream!"); + } + position += n; + } + + int recordLength = SSLExplorer.getRequiredSize(buffer, 0, position); + if (buffer.length < recordLength) { + buffer = Arrays.copyOf(buffer, recordLength); + } + + while (position < recordLength) { + int count = recordLength - position; + int n = ins.read(buffer, position, count); + if (n < 0) { + throw new Exception("unexpected end of stream!"); + } + position += n; + } + + capabilities = SSLExplorer.explore(buffer, 0, recordLength);; + if (capabilities != null) { + System.out.println("Record version: " + + capabilities.getRecordVersion()); + System.out.println("Hello version: " + + capabilities.getHelloVersion()); + } + + // want an I/O exception + throw new IOException("We just want a I/O exception"); + } catch (Exception e) { + failed = true; + } + + // off course, the above explore failed. Faile to failure handler + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, null, null); + SSLSocketFactory sslsf = context.getSocketFactory(); + ByteArrayInputStream bais = + new ByteArrayInputStream(buffer, 0, position); + SSLSocket sslSocket = (SSLSocket)sslsf.createSocket(socket, bais, true); + + try { + InputStream sslIS = sslSocket.getInputStream(); + OutputStream sslOS = sslSocket.getOutputStream(); + + sslIS.read(); + if (!failed) { + sslOS.write(85); + sslOS.flush(); + } else { + sslSocket.close(); + } + } catch (Exception e) { + System.out.println("server exception " + e); + } finally { + sslSocket.close(); + serverSocket.close(); + } + } + + + /* + * Define the client side of the test. + * + * If the server prematurely exits, serverReady will be set to true + * to avoid infinite hangs. + */ + void doClientSide() throws Exception { + + /* + * Wait for server to get started. + */ + while (!serverReady) { + Thread.sleep(50); + } + + SSLSocketFactory sslsf = + (SSLSocketFactory) SSLSocketFactory.getDefault(); + SSLSocket sslSocket = (SSLSocket) + sslsf.createSocket("localhost", serverPort); + + // enable the specified TLS protocol + sslSocket.setEnabledProtocols(supportedProtocols); + + try { + InputStream sslIS = sslSocket.getInputStream(); + OutputStream sslOS = sslSocket.getOutputStream(); + + sslOS.write(280); + sslOS.flush(); + sslIS.read(); + } catch (Exception e) { + System.out.println("client exception " + e); + } finally { + sslSocket.close(); + } + } + + private static String[] supportedProtocols; // supported protocols + + private static void parseArguments(String[] args) { + supportedProtocols = args[0].split(","); + } + + + /* + * ============================================================= + * The remainder is just support stuff + */ + + // use any free port by default + volatile int serverPort = 0; + + volatile Exception serverException = null; + volatile Exception clientException = null; + + public static void main(String[] args) throws Exception { + String keyFilename = + System.getProperty("test.src", ".") + "/" + pathToStores + + "/" + keyStoreFile; + String trustFilename = + System.getProperty("test.src", ".") + "/" + pathToStores + + "/" + trustStoreFile; + + System.setProperty("javax.net.ssl.keyStore", keyFilename); + System.setProperty("javax.net.ssl.keyStorePassword", passwd); + System.setProperty("javax.net.ssl.trustStore", trustFilename); + System.setProperty("javax.net.ssl.trustStorePassword", passwd); + + if (debug) + System.setProperty("javax.net.debug", "all"); + + /* + * Get the customized arguments. + */ + parseArguments(args); + + /* + * Start the tests. + */ + new SSLSocketExplorerFailure(); + } + + Thread clientThread = null; + Thread serverThread = null; + + /* + * Primary constructor, used to drive remainder of the test. + * + * Fork off the other side, then do your work. + */ + SSLSocketExplorerFailure() throws Exception { + try { + if (separateServerThread) { + startServer(true); + startClient(false); + } else { + startClient(true); + startServer(false); + } + } catch (Exception e) { + // swallow for now. Show later + } + + /* + * Wait for other side to close down. + */ + if (separateServerThread) { + serverThread.join(); + } else { + clientThread.join(); + } + + /* + * When we get here, the test is pretty much over. + * Which side threw the error? + */ + Exception local; + Exception remote; + String whichRemote; + + if (separateServerThread) { + remote = serverException; + local = clientException; + whichRemote = "server"; + } else { + remote = clientException; + local = serverException; + whichRemote = "client"; + } + + /* + * If both failed, return the curthread's exception, but also + * print the remote side Exception + */ + if ((local != null) && (remote != null)) { + System.out.println(whichRemote + " also threw:"); + remote.printStackTrace(); + System.out.println(); + throw local; + } + + if (remote != null) { + throw remote; + } + + if (local != null) { + throw local; + } + } + + void startServer(boolean newThread) throws Exception { + if (newThread) { + serverThread = new Thread() { + public void run() { + try { + doServerSide(); + } catch (Exception e) { + /* + * Our server thread just died. + * + * Release the client, if not active already... + */ + System.err.println("Server died..."); + serverReady = true; + serverException = e; + } + } + }; + serverThread.start(); + } else { + try { + doServerSide(); + } catch (Exception e) { + serverException = e; + } finally { + serverReady = true; + } + } + } + + void startClient(boolean newThread) throws Exception { + if (newThread) { + clientThread = new Thread() { + public void run() { + try { + doClientSide(); + } catch (Exception e) { + /* + * Our client thread just died. + */ + System.err.println("Client died..."); + clientException = e; + } + } + }; + clientThread.start(); + } else { + try { + doClientSide(); + } catch (Exception e) { + clientException = e; + } + } + } +} diff --git a/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLSocketExplorerMatchedSNI.java b/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLSocketExplorerMatchedSNI.java new file mode 100644 index 00000000000..c9ae26c42a6 --- /dev/null +++ b/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLSocketExplorerMatchedSNI.java @@ -0,0 +1,435 @@ +/* + * 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. + * + * 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. + */ + +// +// SunJSSE does not support dynamic system properties, no way to re-use +// system properties in samevm/agentvm mode. +// + +/** + * @test + * @bug 7068321 + * @summary Support TLS Server Name Indication (SNI) Extension in JSSE Server + * @library ../../../../templates + * @build SSLCapabilities SSLExplorer + * @run main/othervm SSLSocketExplorerMatchedSNI www.example.com + * www\.example\.com + * @run main/othervm SSLSocketExplorerMatchedSNI www.example.com + * www\.example\.(com|org) + * @run main/othervm SSLSocketExplorerMatchedSNI example.com + * (.*\.)*example\.(com|org) + * @run main/othervm SSLSocketExplorerMatchedSNI www.example.com + * (.*\.)*example\.(com|org) + * @run main/othervm SSLSocketExplorerMatchedSNI www.us.example.com + * (.*\.)*example\.(com|org) + */ + +import java.io.*; +import java.nio.*; +import java.nio.channels.*; +import java.util.*; +import java.net.*; +import javax.net.ssl.*; + +public class SSLSocketExplorerMatchedSNI { + + /* + * ============================================================= + * Set the various variables needed for the tests, then + * specify what tests to run on each side. + */ + + /* + * Should we run the client or server in a separate thread? + * Both sides can throw exceptions, but do you have a preference + * as to which side should be the main thread. + */ + static boolean separateServerThread = false; + + /* + * Where do we find the keystores? + */ + static String pathToStores = "../../../../etc"; + static String keyStoreFile = "keystore"; + static String trustStoreFile = "truststore"; + static String passwd = "passphrase"; + + /* + * Is the server ready to serve? + */ + volatile static boolean serverReady = false; + + /* + * Turn on SSL debugging? + */ + static boolean debug = false; + + /* + * If the client or server is doing some kind of object creation + * that the other side depends on, and that thread prematurely + * exits, you may experience a hang. The test harness will + * terminate all hung threads after its timeout has expired, + * currently 3 minutes by default, but you might try to be + * smart about it.... + */ + + /* + * Define the server side of the test. + * + * If the server prematurely exits, serverReady will be set to true + * to avoid infinite hangs. + */ + void doServerSide() throws Exception { + + ServerSocket serverSocket = new ServerSocket(serverPort); + + // Signal Client, we're ready for his connect. + serverPort = serverSocket.getLocalPort(); + serverReady = true; + + Socket socket = serverSocket.accept(); + InputStream ins = socket.getInputStream(); + + byte[] buffer = new byte[0xFF]; + int position = 0; + SSLCapabilities capabilities = null; + + // Read the header of TLS record + while (position < SSLExplorer.RECORD_HEADER_SIZE) { + int count = SSLExplorer.RECORD_HEADER_SIZE - position; + int n = ins.read(buffer, position, count); + if (n < 0) { + throw new Exception("unexpected end of stream!"); + } + position += n; + } + + int recordLength = SSLExplorer.getRequiredSize(buffer, 0, position); + if (buffer.length < recordLength) { + buffer = Arrays.copyOf(buffer, recordLength); + } + + while (position < recordLength) { + int count = recordLength - position; + int n = ins.read(buffer, position, count); + if (n < 0) { + throw new Exception("unexpected end of stream!"); + } + position += n; + } + + capabilities = SSLExplorer.explore(buffer, 0, recordLength);; + if (capabilities != null) { + System.out.println("Record version: " + + capabilities.getRecordVersion()); + System.out.println("Hello version: " + + capabilities.getHelloVersion()); + } + + SSLSocketFactory sslsf = + (SSLSocketFactory) SSLSocketFactory.getDefault(); + ByteArrayInputStream bais = + new ByteArrayInputStream(buffer, 0, position); + SSLSocket sslSocket = (SSLSocket)sslsf.createSocket(socket, bais, true); + + SNIMatcher matcher = SNIHostName.createSNIMatcher( + serverAcceptableHostname); + Collection matchers = new ArrayList<>(1); + matchers.add(matcher); + SSLParameters params = sslSocket.getSSLParameters(); + params.setSNIMatchers(matchers); + sslSocket.setSSLParameters(params); + + InputStream sslIS = sslSocket.getInputStream(); + OutputStream sslOS = sslSocket.getOutputStream(); + + sslIS.read(); + sslOS.write(85); + sslOS.flush(); + + ExtendedSSLSession session = (ExtendedSSLSession)sslSocket.getSession(); + checkCapabilities(capabilities, session); + + sslSocket.close(); + serverSocket.close(); + } + + + /* + * Define the client side of the test. + * + * If the server prematurely exits, serverReady will be set to true + * to avoid infinite hangs. + */ + void doClientSide() throws Exception { + + /* + * Wait for server to get started. + */ + while (!serverReady) { + Thread.sleep(50); + } + + SSLSocketFactory sslsf = + (SSLSocketFactory) SSLSocketFactory.getDefault(); + SSLSocket sslSocket = (SSLSocket) + sslsf.createSocket("localhost", serverPort); + + SNIHostName serverName = new SNIHostName(clientRequestedHostname); + List serverNames = new ArrayList<>(1); + serverNames.add(serverName); + SSLParameters params = sslSocket.getSSLParameters(); + params.setServerNames(serverNames); + sslSocket.setSSLParameters(params); + + InputStream sslIS = sslSocket.getInputStream(); + OutputStream sslOS = sslSocket.getOutputStream(); + + sslOS.write(280); + sslOS.flush(); + sslIS.read(); + + ExtendedSSLSession session = (ExtendedSSLSession)sslSocket.getSession(); + checkSNIInSession(session); + + sslSocket.close(); + } + + + void checkCapabilities(SSLCapabilities capabilities, + ExtendedSSLSession session) throws Exception { + + List sessionSNI = session.getRequestedServerNames(); + if (!sessionSNI.equals(capabilities.getServerNames())) { + for (SNIServerName sni : sessionSNI) { + System.out.println("SNI in session is " + sni); + } + + List capaSNI = capabilities.getServerNames(); + for (SNIServerName sni : capaSNI) { + System.out.println("SNI in session is " + sni); + } + + throw new Exception( + "server name indication does not match capabilities"); + } + + checkSNIInSession(session); + } + + void checkSNIInSession(ExtendedSSLSession session) throws Exception { + List sessionSNI = session.getRequestedServerNames(); + if (sessionSNI.isEmpty()) { + throw new Exception( + "unexpected empty request server name indication"); + } + + if (sessionSNI.size() != 1) { + throw new Exception( + "unexpected request server name indication"); + } + + SNIServerName serverName = sessionSNI.get(0); + if (!(serverName instanceof SNIHostName)) { + throw new Exception( + "unexpected instance of request server name indication"); + } + + String hostname = ((SNIHostName)serverName).getAsciiName(); + if (!clientRequestedHostname.equalsIgnoreCase(hostname)) { + throw new Exception( + "unexpected request server name indication value"); + } + } + + private static String clientRequestedHostname; + private static String serverAcceptableHostname; + + private static void parseArguments(String[] args) { + clientRequestedHostname = args[0]; + serverAcceptableHostname = args[1]; + } + + + /* + * ============================================================= + * The remainder is just support stuff + */ + + // use any free port by default + volatile int serverPort = 0; + + volatile Exception serverException = null; + volatile Exception clientException = null; + + + public static void main(String[] args) throws Exception { + String keyFilename = + System.getProperty("test.src", ".") + "/" + pathToStores + + "/" + keyStoreFile; + String trustFilename = + System.getProperty("test.src", ".") + "/" + pathToStores + + "/" + trustStoreFile; + + System.setProperty("javax.net.ssl.keyStore", keyFilename); + System.setProperty("javax.net.ssl.keyStorePassword", passwd); + System.setProperty("javax.net.ssl.trustStore", trustFilename); + System.setProperty("javax.net.ssl.trustStorePassword", passwd); + + if (debug) + System.setProperty("javax.net.debug", "all"); + + /* + * Get the customized arguments. + */ + parseArguments(args); + + /* + * Start the tests. + */ + new SSLSocketExplorerMatchedSNI(); + } + + Thread clientThread = null; + Thread serverThread = null; + + /* + * Primary constructor, used to drive remainder of the test. + * + * Fork off the other side, then do your work. + */ + SSLSocketExplorerMatchedSNI() throws Exception { + try { + if (separateServerThread) { + startServer(true); + startClient(false); + } else { + startClient(true); + startServer(false); + } + } catch (Exception e) { + // swallow for now. Show later + } + + /* + * Wait for other side to close down. + */ + if (separateServerThread) { + serverThread.join(); + } else { + clientThread.join(); + } + + /* + * When we get here, the test is pretty much over. + * Which side threw the error? + */ + Exception local; + Exception remote; + String whichRemote; + + if (separateServerThread) { + remote = serverException; + local = clientException; + whichRemote = "server"; + } else { + remote = clientException; + local = serverException; + whichRemote = "client"; + } + + /* + * If both failed, return the curthread's exception, but also + * print the remote side Exception + */ + if ((local != null) && (remote != null)) { + System.out.println(whichRemote + " also threw:"); + remote.printStackTrace(); + System.out.println(); + throw local; + } + + if (remote != null) { + throw remote; + } + + if (local != null) { + throw local; + } + } + + void startServer(boolean newThread) throws Exception { + if (newThread) { + serverThread = new Thread() { + public void run() { + try { + doServerSide(); + } catch (Exception e) { + /* + * Our server thread just died. + * + * Release the client, if not active already... + */ + System.err.println("Server died..."); + serverReady = true; + serverException = e; + } + } + }; + serverThread.start(); + } else { + try { + doServerSide(); + } catch (Exception e) { + serverException = e; + } finally { + serverReady = true; + } + } + } + + void startClient(boolean newThread) throws Exception { + if (newThread) { + clientThread = new Thread() { + public void run() { + try { + doClientSide(); + } catch (Exception e) { + /* + * Our client thread just died. + */ + System.err.println("Client died..."); + clientException = e; + } + } + }; + clientThread.start(); + } else { + try { + doClientSide(); + } catch (Exception e) { + clientException = e; + } + } + } +} diff --git a/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLSocketExplorerUnmatchedSNI.java b/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLSocketExplorerUnmatchedSNI.java new file mode 100644 index 00000000000..eab48002d6f --- /dev/null +++ b/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLSocketExplorerUnmatchedSNI.java @@ -0,0 +1,392 @@ +/* + * 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. + * + * 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. + */ + +// +// SunJSSE does not support dynamic system properties, no way to re-use +// system properties in samevm/agentvm mode. +// + +/** + * @test + * @bug 7068321 + * @summary Support TLS Server Name Indication (SNI) Extension in JSSE Server + * @library ../../../../templates + * @build SSLCapabilities SSLExplorer + * @run main/othervm SSLSocketExplorerUnmatchedSNI www.example.com + * www\.example\.org + */ + +import java.io.*; +import java.nio.*; +import java.nio.channels.*; +import java.util.*; +import java.net.*; +import javax.net.ssl.*; + +public class SSLSocketExplorerUnmatchedSNI { + + /* + * ============================================================= + * Set the various variables needed for the tests, then + * specify what tests to run on each side. + */ + + /* + * Should we run the client or server in a separate thread? + * Both sides can throw exceptions, but do you have a preference + * as to which side should be the main thread. + */ + static boolean separateServerThread = false; + + /* + * Where do we find the keystores? + */ + static String pathToStores = "../../../../etc"; + static String keyStoreFile = "keystore"; + static String trustStoreFile = "truststore"; + static String passwd = "passphrase"; + + /* + * Is the server ready to serve? + */ + volatile static boolean serverReady = false; + + /* + * Turn on SSL debugging? + */ + static boolean debug = false; + + /* + * If the client or server is doing some kind of object creation + * that the other side depends on, and that thread prematurely + * exits, you may experience a hang. The test harness will + * terminate all hung threads after its timeout has expired, + * currently 3 minutes by default, but you might try to be + * smart about it.... + */ + + /* + * Define the server side of the test. + * + * If the server prematurely exits, serverReady will be set to true + * to avoid infinite hangs. + */ + void doServerSide() throws Exception { + + ServerSocket serverSocket = new ServerSocket(serverPort); + + // Signal Client, we're ready for his connect. + serverPort = serverSocket.getLocalPort(); + serverReady = true; + + Socket socket = serverSocket.accept(); + InputStream ins = socket.getInputStream(); + + byte[] buffer = new byte[0xFF]; + int position = 0; + SSLCapabilities capabilities = null; + + // Read the header of TLS record + while (position < SSLExplorer.RECORD_HEADER_SIZE) { + int count = SSLExplorer.RECORD_HEADER_SIZE - position; + int n = ins.read(buffer, position, count); + if (n < 0) { + throw new Exception("unexpected end of stream!"); + } + position += n; + } + + int recordLength = SSLExplorer.getRequiredSize(buffer, 0, position); + if (buffer.length < recordLength) { + buffer = Arrays.copyOf(buffer, recordLength); + } + + while (position < recordLength) { + int count = recordLength - position; + int n = ins.read(buffer, position, count); + if (n < 0) { + throw new Exception("unexpected end of stream!"); + } + position += n; + } + + capabilities = SSLExplorer.explore(buffer, 0, recordLength);; + if (capabilities != null) { + System.out.println("Record version: " + + capabilities.getRecordVersion()); + System.out.println("Hello version: " + + capabilities.getHelloVersion()); + } + + SSLSocketFactory sslsf = + (SSLSocketFactory) SSLSocketFactory.getDefault(); + ByteArrayInputStream bais = + new ByteArrayInputStream(buffer, 0, position); + SSLSocket sslSocket = (SSLSocket)sslsf.createSocket(socket, bais, true); + + SNIMatcher matcher = SNIHostName.createSNIMatcher( + serverAcceptableHostname); + Collection matchers = new ArrayList<>(1); + matchers.add(matcher); + SSLParameters params = sslSocket.getSSLParameters(); + params.setSNIMatchers(matchers); + sslSocket.setSSLParameters(params); + + InputStream sslIS = sslSocket.getInputStream(); + OutputStream sslOS = sslSocket.getOutputStream(); + + try { + sslIS.read(); + sslOS.write(85); + sslOS.flush(); + + throw new Exception( + "Mismatched server name indication was accepted"); + } catch (SSLHandshakeException sslhe) { + // the expected unrecognized server name indication exception + } catch (IOException ioe) { + // the peer may have closed the socket because of the unmatched + // server name indication. + } finally { + sslSocket.close(); + serverSocket.close(); + } + } + + + /* + * Define the client side of the test. + * + * If the server prematurely exits, serverReady will be set to true + * to avoid infinite hangs. + */ + void doClientSide() throws Exception { + + /* + * Wait for server to get started. + */ + while (!serverReady) { + Thread.sleep(50); + } + + SSLSocketFactory sslsf = + (SSLSocketFactory) SSLSocketFactory.getDefault(); + SSLSocket sslSocket = (SSLSocket) + sslsf.createSocket("localhost", serverPort); + + SNIHostName serverName = new SNIHostName(clientRequestedHostname); + List serverNames = new ArrayList<>(1); + serverNames.add(serverName); + SSLParameters params = sslSocket.getSSLParameters(); + params.setServerNames(serverNames); + sslSocket.setSSLParameters(params); + + InputStream sslIS = sslSocket.getInputStream(); + OutputStream sslOS = sslSocket.getOutputStream(); + + try { + sslOS.write(280); + sslOS.flush(); + sslIS.read(); + + throw new Exception( + "Mismatched server name indication was accepted"); + } catch (SSLHandshakeException sslhe) { + // the expected unrecognized server name indication exception + } catch (IOException ioe) { + // the peer may have closed the socket because of the unmatched + // server name indication. + } finally { + sslSocket.close(); + } + } + + private static String clientRequestedHostname; + private static String serverAcceptableHostname; + + private static void parseArguments(String[] args) { + clientRequestedHostname = args[0]; + serverAcceptableHostname = args[1]; + } + + /* + * ============================================================= + * The remainder is just support stuff + */ + + // use any free port by default + volatile int serverPort = 0; + + volatile Exception serverException = null; + volatile Exception clientException = null; + + public static void main(String[] args) throws Exception { + String keyFilename = + System.getProperty("test.src", ".") + "/" + pathToStores + + "/" + keyStoreFile; + String trustFilename = + System.getProperty("test.src", ".") + "/" + pathToStores + + "/" + trustStoreFile; + + System.setProperty("javax.net.ssl.keyStore", keyFilename); + System.setProperty("javax.net.ssl.keyStorePassword", passwd); + System.setProperty("javax.net.ssl.trustStore", trustFilename); + System.setProperty("javax.net.ssl.trustStorePassword", passwd); + + if (debug) + System.setProperty("javax.net.debug", "all"); + + /* + * Get the customized arguments. + */ + parseArguments(args); + + /* + * Start the tests. + */ + new SSLSocketExplorerUnmatchedSNI(); + } + + Thread clientThread = null; + Thread serverThread = null; + + /* + * Primary constructor, used to drive remainder of the test. + * + * Fork off the other side, then do your work. + */ + SSLSocketExplorerUnmatchedSNI() throws Exception { + try { + if (separateServerThread) { + startServer(true); + startClient(false); + } else { + startClient(true); + startServer(false); + } + } catch (Exception e) { + // swallow for now. Show later + } + + /* + * Wait for other side to close down. + */ + if (separateServerThread) { + serverThread.join(); + } else { + clientThread.join(); + } + + /* + * When we get here, the test is pretty much over. + * Which side threw the error? + */ + Exception local; + Exception remote; + String whichRemote; + + if (separateServerThread) { + remote = serverException; + local = clientException; + whichRemote = "server"; + } else { + remote = clientException; + local = serverException; + whichRemote = "client"; + } + + /* + * If both failed, return the curthread's exception, but also + * print the remote side Exception + */ + if ((local != null) && (remote != null)) { + System.out.println(whichRemote + " also threw:"); + remote.printStackTrace(); + System.out.println(); + throw local; + } + + if (remote != null) { + throw remote; + } + + if (local != null) { + throw local; + } + } + + void startServer(boolean newThread) throws Exception { + if (newThread) { + serverThread = new Thread() { + public void run() { + try { + doServerSide(); + } catch (Exception e) { + /* + * Our server thread just died. + * + * Release the client, if not active already... + */ + System.err.println("Server died..."); + serverReady = true; + serverException = e; + } + } + }; + serverThread.start(); + } else { + try { + doServerSide(); + } catch (Exception e) { + serverException = e; + } finally { + serverReady = true; + } + } + } + + void startClient(boolean newThread) throws Exception { + if (newThread) { + clientThread = new Thread() { + public void run() { + try { + doClientSide(); + } catch (Exception e) { + /* + * Our client thread just died. + */ + System.err.println("Client died..."); + clientException = e; + } + } + }; + clientThread.start(); + } else { + try { + doClientSide(); + } catch (Exception e) { + clientException = e; + } + } + } +} diff --git a/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLSocketExplorerWithCliSNI.java b/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLSocketExplorerWithCliSNI.java new file mode 100644 index 00000000000..6933c9a9894 --- /dev/null +++ b/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLSocketExplorerWithCliSNI.java @@ -0,0 +1,407 @@ +/* + * 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. + * + * 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. + */ + +// +// SunJSSE does not support dynamic system properties, no way to re-use +// system properties in samevm/agentvm mode. +// + +/** + * @test + * @bug 7068321 + * @summary Support TLS Server Name Indication (SNI) Extension in JSSE Server + * @library ../../../../templates + * @build SSLCapabilities SSLExplorer + * @run main/othervm SSLSocketExplorerWithCliSNI + */ + +import java.io.*; +import java.nio.*; +import java.nio.channels.*; +import java.util.*; +import java.net.*; +import javax.net.ssl.*; + +public class SSLSocketExplorerWithCliSNI { + + /* + * ============================================================= + * Set the various variables needed for the tests, then + * specify what tests to run on each side. + */ + + /* + * Should we run the client or server in a separate thread? + * Both sides can throw exceptions, but do you have a preference + * as to which side should be the main thread. + */ + static boolean separateServerThread = true; + + /* + * Where do we find the keystores? + */ + static String pathToStores = "../../../../etc"; + static String keyStoreFile = "keystore"; + static String trustStoreFile = "truststore"; + static String passwd = "passphrase"; + + /* + * Is the server ready to serve? + */ + volatile static boolean serverReady = false; + + /* + * Turn on SSL debugging? + */ + static boolean debug = false; + + /* + * If the client or server is doing some kind of object creation + * that the other side depends on, and that thread prematurely + * exits, you may experience a hang. The test harness will + * terminate all hung threads after its timeout has expired, + * currently 3 minutes by default, but you might try to be + * smart about it.... + */ + + /* + * Define the server side of the test. + * + * If the server prematurely exits, serverReady will be set to true + * to avoid infinite hangs. + */ + void doServerSide() throws Exception { + + ServerSocket serverSocket = new ServerSocket(serverPort); + + // Signal Client, we're ready for his connect. + serverPort = serverSocket.getLocalPort(); + serverReady = true; + + Socket socket = serverSocket.accept(); + InputStream ins = socket.getInputStream(); + + byte[] buffer = new byte[0xFF]; + int position = 0; + SSLCapabilities capabilities = null; + + // Read the header of TLS record + while (position < SSLExplorer.RECORD_HEADER_SIZE) { + int count = SSLExplorer.RECORD_HEADER_SIZE - position; + int n = ins.read(buffer, position, count); + if (n < 0) { + throw new Exception("unexpected end of stream!"); + } + position += n; + } + + int recordLength = SSLExplorer.getRequiredSize(buffer, 0, position); + if (buffer.length < recordLength) { + buffer = Arrays.copyOf(buffer, recordLength); + } + + while (position < recordLength) { + int count = recordLength - position; + int n = ins.read(buffer, position, count); + if (n < 0) { + throw new Exception("unexpected end of stream!"); + } + position += n; + } + + capabilities = SSLExplorer.explore(buffer, 0, recordLength);; + if (capabilities != null) { + System.out.println("Record version: " + + capabilities.getRecordVersion()); + System.out.println("Hello version: " + + capabilities.getHelloVersion()); + } + + SSLSocketFactory sslsf = + (SSLSocketFactory) SSLSocketFactory.getDefault(); + ByteArrayInputStream bais = + new ByteArrayInputStream(buffer, 0, position); + SSLSocket sslSocket = (SSLSocket)sslsf.createSocket(socket, bais, true); + + InputStream sslIS = sslSocket.getInputStream(); + OutputStream sslOS = sslSocket.getOutputStream(); + + sslIS.read(); + sslOS.write(85); + sslOS.flush(); + + ExtendedSSLSession session = (ExtendedSSLSession)sslSocket.getSession(); + checkCapabilities(capabilities, session); + + sslSocket.close(); + serverSocket.close(); + } + + + /* + * Define the client side of the test. + * + * If the server prematurely exits, serverReady will be set to true + * to avoid infinite hangs. + */ + void doClientSide() throws Exception { + + /* + * Wait for server to get started. + */ + while (!serverReady) { + Thread.sleep(50); + } + + SSLSocketFactory sslsf = + (SSLSocketFactory) SSLSocketFactory.getDefault(); + SSLSocket sslSocket = (SSLSocket) + sslsf.createSocket("localhost", serverPort); + + SNIHostName serverName = new SNIHostName(clientRequestedHostname); + List serverNames = new ArrayList<>(1); + serverNames.add(serverName); + SSLParameters params = sslSocket.getSSLParameters(); + params.setServerNames(serverNames); + sslSocket.setSSLParameters(params); + + InputStream sslIS = sslSocket.getInputStream(); + OutputStream sslOS = sslSocket.getOutputStream(); + + sslOS.write(280); + sslOS.flush(); + sslIS.read(); + + ExtendedSSLSession session = (ExtendedSSLSession)sslSocket.getSession(); + checkSNIInSession(session); + + sslSocket.close(); + } + + private static String clientRequestedHostname = "www.example.com"; + private static String serverAcceptableHostname = + "www\\.example\\.(com|org)"; + + void checkCapabilities(SSLCapabilities capabilities, + ExtendedSSLSession session) throws Exception { + + List sessionSNI = session.getRequestedServerNames(); + if (!sessionSNI.equals(capabilities.getServerNames())) { + for (SNIServerName sni : sessionSNI) { + System.out.println("SNI in session is " + sni); + } + + List capaSNI = capabilities.getServerNames(); + for (SNIServerName sni : capaSNI) { + System.out.println("SNI in session is " + sni); + } + + throw new Exception( + "server name indication does not match capabilities"); + } + + checkSNIInSession(session); + } + + void checkSNIInSession(ExtendedSSLSession session) throws Exception { + List sessionSNI = session.getRequestedServerNames(); + if (sessionSNI.isEmpty()) { + throw new Exception( + "unexpected empty request server name indication"); + } + + if (sessionSNI.size() != 1) { + throw new Exception( + "unexpected request server name indication"); + } + + SNIServerName serverName = sessionSNI.get(0); + if (!(serverName instanceof SNIHostName)) { + throw new Exception( + "unexpected instance of request server name indication"); + } + + String hostname = ((SNIHostName)serverName).getAsciiName(); + if (!clientRequestedHostname.equalsIgnoreCase(hostname)) { + throw new Exception( + "unexpected request server name indication value"); + } + } + + + /* + * ============================================================= + * The remainder is just support stuff + */ + + // use any free port by default + volatile int serverPort = 0; + + volatile Exception serverException = null; + volatile Exception clientException = null; + + public static void main(String[] args) throws Exception { + String keyFilename = + System.getProperty("test.src", ".") + "/" + pathToStores + + "/" + keyStoreFile; + String trustFilename = + System.getProperty("test.src", ".") + "/" + pathToStores + + "/" + trustStoreFile; + + System.setProperty("javax.net.ssl.keyStore", keyFilename); + System.setProperty("javax.net.ssl.keyStorePassword", passwd); + System.setProperty("javax.net.ssl.trustStore", trustFilename); + System.setProperty("javax.net.ssl.trustStorePassword", passwd); + + if (debug) + System.setProperty("javax.net.debug", "all"); + + /* + * Start the tests. + */ + new SSLSocketExplorerWithCliSNI(); + } + + Thread clientThread = null; + Thread serverThread = null; + + /* + * Primary constructor, used to drive remainder of the test. + * + * Fork off the other side, then do your work. + */ + SSLSocketExplorerWithCliSNI() throws Exception { + try { + if (separateServerThread) { + startServer(true); + startClient(false); + } else { + startClient(true); + startServer(false); + } + } catch (Exception e) { + // swallow for now. Show later + } + + /* + * Wait for other side to close down. + */ + if (separateServerThread) { + serverThread.join(); + } else { + clientThread.join(); + } + + /* + * When we get here, the test is pretty much over. + * Which side threw the error? + */ + Exception local; + Exception remote; + String whichRemote; + + if (separateServerThread) { + remote = serverException; + local = clientException; + whichRemote = "server"; + } else { + remote = clientException; + local = serverException; + whichRemote = "client"; + } + + /* + * If both failed, return the curthread's exception, but also + * print the remote side Exception + */ + if ((local != null) && (remote != null)) { + System.out.println(whichRemote + " also threw:"); + remote.printStackTrace(); + System.out.println(); + throw local; + } + + if (remote != null) { + throw remote; + } + + if (local != null) { + throw local; + } + } + + void startServer(boolean newThread) throws Exception { + if (newThread) { + serverThread = new Thread() { + public void run() { + try { + doServerSide(); + } catch (Exception e) { + /* + * Our server thread just died. + * + * Release the client, if not active already... + */ + System.err.println("Server died..."); + serverReady = true; + serverException = e; + } + } + }; + serverThread.start(); + } else { + try { + doServerSide(); + } catch (Exception e) { + serverException = e; + } finally { + serverReady = true; + } + } + } + + void startClient(boolean newThread) throws Exception { + if (newThread) { + clientThread = new Thread() { + public void run() { + try { + doClientSide(); + } catch (Exception e) { + /* + * Our client thread just died. + */ + System.err.println("Client died..."); + clientException = e; + } + } + }; + clientThread.start(); + } else { + try { + doClientSide(); + } catch (Exception e) { + clientException = e; + } + } + } +} diff --git a/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLSocketExplorerWithSrvSNI.java b/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLSocketExplorerWithSrvSNI.java new file mode 100644 index 00000000000..64b1ae9a63b --- /dev/null +++ b/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLSocketExplorerWithSrvSNI.java @@ -0,0 +1,390 @@ +/* + * 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. + * + * 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. + */ + +// +// SunJSSE does not support dynamic system properties, no way to re-use +// system properties in samevm/agentvm mode. +// + +/** + * @test + * @bug 7068321 + * @summary Support TLS Server Name Indication (SNI) Extension in JSSE Server + * @library ../../../../templates + * @build SSLCapabilities SSLExplorer + * @run main/othervm SSLSocketExplorerWithSrvSNI + */ + +import java.io.*; +import java.nio.*; +import java.nio.channels.*; +import java.util.*; +import java.net.*; +import javax.net.ssl.*; + +public class SSLSocketExplorerWithSrvSNI { + + /* + * ============================================================= + * Set the various variables needed for the tests, then + * specify what tests to run on each side. + */ + + /* + * Should we run the client or server in a separate thread? + * Both sides can throw exceptions, but do you have a preference + * as to which side should be the main thread. + */ + static boolean separateServerThread = true; + + /* + * Where do we find the keystores? + */ + static String pathToStores = "../../../../etc"; + static String keyStoreFile = "keystore"; + static String trustStoreFile = "truststore"; + static String passwd = "passphrase"; + + /* + * Is the server ready to serve? + */ + volatile static boolean serverReady = false; + + /* + * Turn on SSL debugging? + */ + static boolean debug = false; + + /* + * If the client or server is doing some kind of object creation + * that the other side depends on, and that thread prematurely + * exits, you may experience a hang. The test harness will + * terminate all hung threads after its timeout has expired, + * currently 3 minutes by default, but you might try to be + * smart about it.... + */ + + /* + * Define the server side of the test. + * + * If the server prematurely exits, serverReady will be set to true + * to avoid infinite hangs. + */ + void doServerSide() throws Exception { + + ServerSocket serverSocket = new ServerSocket(serverPort); + + // Signal Client, we're ready for his connect. + serverPort = serverSocket.getLocalPort(); + serverReady = true; + + Socket socket = serverSocket.accept(); + InputStream ins = socket.getInputStream(); + + byte[] buffer = new byte[0xFF]; + int position = 0; + SSLCapabilities capabilities = null; + + // Read the header of TLS record + while (position < SSLExplorer.RECORD_HEADER_SIZE) { + int count = SSLExplorer.RECORD_HEADER_SIZE - position; + int n = ins.read(buffer, position, count); + if (n < 0) { + throw new Exception("unexpected end of stream!"); + } + position += n; + } + + int recordLength = SSLExplorer.getRequiredSize(buffer, 0, position); + if (buffer.length < recordLength) { + buffer = Arrays.copyOf(buffer, recordLength); + } + + while (position < recordLength) { + int count = recordLength - position; + int n = ins.read(buffer, position, count); + if (n < 0) { + throw new Exception("unexpected end of stream!"); + } + position += n; + } + + capabilities = SSLExplorer.explore(buffer, 0, recordLength);; + if (capabilities != null) { + System.out.println("Record version: " + + capabilities.getRecordVersion()); + System.out.println("Hello version: " + + capabilities.getHelloVersion()); + } + + SSLSocketFactory sslsf = + (SSLSocketFactory) SSLSocketFactory.getDefault(); + ByteArrayInputStream bais = + new ByteArrayInputStream(buffer, 0, position); + SSLSocket sslSocket = (SSLSocket)sslsf.createSocket(socket, bais, true); + + SNIMatcher matcher = SNIHostName.createSNIMatcher( + serverAcceptableHostname); + Collection matchers = new ArrayList<>(1); + matchers.add(matcher); + SSLParameters params = sslSocket.getSSLParameters(); + params.setSNIMatchers(matchers); + sslSocket.setSSLParameters(params); + + InputStream sslIS = sslSocket.getInputStream(); + OutputStream sslOS = sslSocket.getOutputStream(); + + sslIS.read(); + sslOS.write(85); + sslOS.flush(); + + ExtendedSSLSession session = (ExtendedSSLSession)sslSocket.getSession(); + checkCapabilities(capabilities, session); + + sslSocket.close(); + serverSocket.close(); + } + + + /* + * Define the client side of the test. + * + * If the server prematurely exits, serverReady will be set to true + * to avoid infinite hangs. + */ + void doClientSide() throws Exception { + + /* + * Wait for server to get started. + */ + while (!serverReady) { + Thread.sleep(50); + } + + SSLSocketFactory sslsf = + (SSLSocketFactory) SSLSocketFactory.getDefault(); + SSLSocket sslSocket = (SSLSocket) + sslsf.createSocket("localhost", serverPort); + + InputStream sslIS = sslSocket.getInputStream(); + OutputStream sslOS = sslSocket.getOutputStream(); + + sslOS.write(280); + sslOS.flush(); + sslIS.read(); + + ExtendedSSLSession session = (ExtendedSSLSession)sslSocket.getSession(); + checkSNIInSession(session); + + sslSocket.close(); + } + + private static String clientRequestedHostname = "www.example.com"; + private static String serverAcceptableHostname = + "www\\.example\\.(com|org)"; + + void checkCapabilities(SSLCapabilities capabilities, + ExtendedSSLSession session) throws Exception { + + List sessionSNI = session.getRequestedServerNames(); + if (!sessionSNI.equals(capabilities.getServerNames())) { + for (SNIServerName sni : sessionSNI) { + System.out.println("SNI in session is " + sni); + } + + List capaSNI = capabilities.getServerNames(); + for (SNIServerName sni : capaSNI) { + System.out.println("SNI in session is " + sni); + } + + throw new Exception( + "server name indication does not match capabilities"); + } + + checkSNIInSession(session); + } + + void checkSNIInSession(ExtendedSSLSession session) throws Exception { + List sessionSNI = session.getRequestedServerNames(); + if (!sessionSNI.isEmpty()) { + throw new Exception( + "should be empty request server name indication"); + } + } + + /* + * ============================================================= + * The remainder is just support stuff + */ + + // use any free port by default + volatile int serverPort = 0; + + volatile Exception serverException = null; + volatile Exception clientException = null; + + public static void main(String[] args) throws Exception { + String keyFilename = + System.getProperty("test.src", ".") + "/" + pathToStores + + "/" + keyStoreFile; + String trustFilename = + System.getProperty("test.src", ".") + "/" + pathToStores + + "/" + trustStoreFile; + + System.setProperty("javax.net.ssl.keyStore", keyFilename); + System.setProperty("javax.net.ssl.keyStorePassword", passwd); + System.setProperty("javax.net.ssl.trustStore", trustFilename); + System.setProperty("javax.net.ssl.trustStorePassword", passwd); + + if (debug) + System.setProperty("javax.net.debug", "all"); + + /* + * Start the tests. + */ + new SSLSocketExplorerWithSrvSNI(); + } + + Thread clientThread = null; + Thread serverThread = null; + + /* + * Primary constructor, used to drive remainder of the test. + * + * Fork off the other side, then do your work. + */ + SSLSocketExplorerWithSrvSNI() throws Exception { + try { + if (separateServerThread) { + startServer(true); + startClient(false); + } else { + startClient(true); + startServer(false); + } + } catch (Exception e) { + // swallow for now. Show later + } + + /* + * Wait for other side to close down. + */ + if (separateServerThread) { + serverThread.join(); + } else { + clientThread.join(); + } + + /* + * When we get here, the test is pretty much over. + * Which side threw the error? + */ + Exception local; + Exception remote; + String whichRemote; + + if (separateServerThread) { + remote = serverException; + local = clientException; + whichRemote = "server"; + } else { + remote = clientException; + local = serverException; + whichRemote = "client"; + } + + /* + * If both failed, return the curthread's exception, but also + * print the remote side Exception + */ + if ((local != null) && (remote != null)) { + System.out.println(whichRemote + " also threw:"); + remote.printStackTrace(); + System.out.println(); + throw local; + } + + if (remote != null) { + throw remote; + } + + if (local != null) { + throw local; + } + } + + void startServer(boolean newThread) throws Exception { + if (newThread) { + serverThread = new Thread() { + public void run() { + try { + doServerSide(); + } catch (Exception e) { + /* + * Our server thread just died. + * + * Release the client, if not active already... + */ + System.err.println("Server died..."); + serverReady = true; + serverException = e; + } + } + }; + serverThread.start(); + } else { + try { + doServerSide(); + } catch (Exception e) { + serverException = e; + } finally { + serverReady = true; + } + } + } + + void startClient(boolean newThread) throws Exception { + if (newThread) { + clientThread = new Thread() { + public void run() { + try { + doClientSide(); + } catch (Exception e) { + /* + * Our client thread just died. + */ + System.err.println("Client died..."); + clientException = e; + } + } + }; + clientThread.start(); + } else { + try { + doClientSide(); + } catch (Exception e) { + clientException = e; + } + } + } +} diff --git a/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLSocketInconsistentSNI.java b/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLSocketInconsistentSNI.java new file mode 100644 index 00000000000..6ba1dfd9c21 --- /dev/null +++ b/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLSocketInconsistentSNI.java @@ -0,0 +1,340 @@ +/* + * 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. + * + * 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. + */ + +// +// SunJSSE does not support dynamic system properties, no way to re-use +// system properties in samevm/agentvm mode. +// + +/** + * @test + * @bug 7068321 + * @summary Support TLS Server Name Indication (SNI) Extension in JSSE Server + * @run main/othervm SSLSocketInconsistentSNI + */ + +import java.io.*; +import java.nio.*; +import java.nio.channels.*; +import java.util.*; +import java.net.*; +import javax.net.ssl.*; + +public class SSLSocketInconsistentSNI { + + /* + * ============================================================= + * Set the various variables needed for the tests, then + * specify what tests to run on each side. + */ + + /* + * Should we run the client or server in a separate thread? + * Both sides can throw exceptions, but do you have a preference + * as to which side should be the main thread. + */ + static boolean separateServerThread = true; + + /* + * Where do we find the keystores? + */ + static String pathToStores = "../../../../etc"; + static String keyStoreFile = "keystore"; + static String trustStoreFile = "truststore"; + static String passwd = "passphrase"; + + /* + * Is the server ready to serve? + */ + volatile static boolean serverReady = false; + + /* + * Turn on SSL debugging? + */ + static boolean debug = false; + + /* + * If the client or server is doing some kind of object creation + * that the other side depends on, and that thread prematurely + * exits, you may experience a hang. The test harness will + * terminate all hung threads after its timeout has expired, + * currently 3 minutes by default, but you might try to be + * smart about it.... + */ + + /* + * Define the server side of the test. + * + * If the server prematurely exits, serverReady will be set to true + * to avoid infinite hangs. + */ + void doServerSide() throws Exception { + SSLServerSocketFactory sslssf = + (SSLServerSocketFactory) SSLServerSocketFactory.getDefault(); + SSLServerSocket sslServerSocket = + (SSLServerSocket) sslssf.createServerSocket(serverPort); + + SNIMatcher matcher = SNIHostName.createSNIMatcher( + serverAcceptableHostname); + Collection matchers = new ArrayList<>(1); + matchers.add(matcher); + SSLParameters params = sslServerSocket.getSSLParameters(); + params.setSNIMatchers(matchers); + sslServerSocket.setSSLParameters(params); + + serverPort = sslServerSocket.getLocalPort(); + + /* + * Signal Client, we're ready for his connect. + */ + serverReady = true; + + SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept(); + try { + InputStream sslIS = sslSocket.getInputStream(); + OutputStream sslOS = sslSocket.getOutputStream(); + + sslIS.read(); + sslOS.write(85); + sslOS.flush(); + + throw new Exception( + "Mismatched server name indication was accepted"); + } catch (SSLHandshakeException sslhe) { + // the expected unrecognized server name indication exception + } catch (IOException ioe) { + // the peer may have closed the socket because of the unmatched + // server name indication. + } finally { + sslSocket.close(); + sslServerSocket.close(); + } + } + + + /* + * Define the client side of the test. + * + * If the server prematurely exits, serverReady will be set to true + * to avoid infinite hangs. + */ + void doClientSide() throws Exception { + + /* + * Wait for server to get started. + */ + while (!serverReady) { + Thread.sleep(50); + } + + SSLSocketFactory sslsf = + (SSLSocketFactory) SSLSocketFactory.getDefault(); + SSLSocket sslSocket = (SSLSocket) + sslsf.createSocket("localhost", serverPort); + + SNIHostName serverName = new SNIHostName(clientRequestedHostname); + List serverNames = new ArrayList<>(1); + serverNames.add(serverName); + SSLParameters params = sslSocket.getSSLParameters(); + params.setServerNames(serverNames); + sslSocket.setSSLParameters(params); + + try { + InputStream sslIS = sslSocket.getInputStream(); + OutputStream sslOS = sslSocket.getOutputStream(); + + sslOS.write(280); + sslOS.flush(); + sslIS.read(); + + throw new Exception( + "Mismatched server name indication was accepted"); + } catch (SSLHandshakeException sslhe) { + // the expected unrecognized server name indication exception + } catch (IOException ioe) { + // the peer may have closed the socket because of the unmatched + // server name indication. + } finally { + sslSocket.close(); + } + } + + private static String clientRequestedHostname = "www.example.com"; + private static String serverAcceptableHostname = "www\\.example\\.org"; + + /* + * ============================================================= + * The remainder is just support stuff + */ + + // use any free port by default + volatile int serverPort = 0; + + volatile Exception serverException = null; + volatile Exception clientException = null; + + public static void main(String[] args) throws Exception { + String keyFilename = + System.getProperty("test.src", ".") + "/" + pathToStores + + "/" + keyStoreFile; + String trustFilename = + System.getProperty("test.src", ".") + "/" + pathToStores + + "/" + trustStoreFile; + + System.setProperty("javax.net.ssl.keyStore", keyFilename); + System.setProperty("javax.net.ssl.keyStorePassword", passwd); + System.setProperty("javax.net.ssl.trustStore", trustFilename); + System.setProperty("javax.net.ssl.trustStorePassword", passwd); + + if (debug) + System.setProperty("javax.net.debug", "all"); + + /* + * Start the tests. + */ + new SSLSocketInconsistentSNI(); + } + + Thread clientThread = null; + Thread serverThread = null; + + /* + * Primary constructor, used to drive remainder of the test. + * + * Fork off the other side, then do your work. + */ + SSLSocketInconsistentSNI() throws Exception { + try { + if (separateServerThread) { + startServer(true); + startClient(false); + } else { + startClient(true); + startServer(false); + } + } catch (Exception e) { + // swallow for now. Show later + } + + /* + * Wait for other side to close down. + */ + if (separateServerThread) { + serverThread.join(); + } else { + clientThread.join(); + } + + /* + * When we get here, the test is pretty much over. + * Which side threw the error? + */ + Exception local; + Exception remote; + String whichRemote; + + if (separateServerThread) { + remote = serverException; + local = clientException; + whichRemote = "server"; + } else { + remote = clientException; + local = serverException; + whichRemote = "client"; + } + + /* + * If both failed, return the curthread's exception, but also + * print the remote side Exception + */ + if ((local != null) && (remote != null)) { + System.out.println(whichRemote + " also threw:"); + remote.printStackTrace(); + System.out.println(); + throw local; + } + + if (remote != null) { + throw remote; + } + + if (local != null) { + throw local; + } + } + + void startServer(boolean newThread) throws Exception { + if (newThread) { + serverThread = new Thread() { + public void run() { + try { + doServerSide(); + } catch (Exception e) { + /* + * Our server thread just died. + * + * Release the client, if not active already... + */ + System.err.println("Server died..."); + serverReady = true; + serverException = e; + } + } + }; + serverThread.start(); + } else { + try { + doServerSide(); + } catch (Exception e) { + serverException = e; + } finally { + serverReady = true; + } + } + } + + void startClient(boolean newThread) throws Exception { + if (newThread) { + clientThread = new Thread() { + public void run() { + try { + doClientSide(); + } catch (Exception e) { + /* + * Our client thread just died. + */ + System.err.println("Client died..."); + clientException = e; + } + } + }; + clientThread.start(); + } else { + try { + doClientSide(); + } catch (Exception e) { + clientException = e; + } + } + } +} diff --git a/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLSocketSNISensitive.java b/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLSocketSNISensitive.java new file mode 100644 index 00000000000..4c50c305704 --- /dev/null +++ b/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/SSLSocketSNISensitive.java @@ -0,0 +1,564 @@ +/* + * 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. + */ + +// +// SunJSSE does not support dynamic system properties, no way to re-use +// system properties in samevm/agentvm mode. +// + +/* + * @test + * @bug 7068321 + * @summary Support TLS Server Name Indication (SNI) Extension in JSSE Server + * @run main/othervm SSLSocketSNISensitive PKIX www.example.com + * @run main/othervm SSLSocketSNISensitive SunX509 www.example.com + * @run main/othervm SSLSocketSNISensitive PKIX www.example.net + * @run main/othervm SSLSocketSNISensitive SunX509 www.example.net + * @run main/othervm SSLSocketSNISensitive PKIX www.invalid.com + * @run main/othervm SSLSocketSNISensitive SunX509 www.invalid.com + */ + +import java.net.*; +import java.util.*; +import java.io.*; +import javax.net.ssl.*; +import java.security.KeyStore; +import java.security.KeyFactory; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.security.cert.CertificateFactory; +import java.security.spec.*; +import java.security.interfaces.*; +import sun.misc.BASE64Decoder; + + +public class SSLSocketSNISensitive { + + /* + * ============================================================= + * Set the various variables needed for the tests, then + * specify what tests to run on each side. + */ + + /* + * Should we run the client or server in a separate thread? + * Both sides can throw exceptions, but do you have a preference + * as to which side should be the main thread. + */ + static boolean separateServerThread = false; + + /* + * Where do we find the keystores? + */ + // Certificates and key used in the test. + static String trustedCertStr = + "-----BEGIN CERTIFICATE-----\n" + + "MIICkjCCAfugAwIBAgIBADANBgkqhkiG9w0BAQQFADA7MQswCQYDVQQGEwJVUzEN\n" + + "MAsGA1UEChMESmF2YTEdMBsGA1UECxMUU3VuSlNTRSBUZXN0IFNlcml2Y2UwHhcN\n" + + "MTIwNDE3MTIwNjA3WhcNMzMwMzI4MTIwNjA3WjA7MQswCQYDVQQGEwJVUzENMAsG\n" + + "A1UEChMESmF2YTEdMBsGA1UECxMUU3VuSlNTRSBUZXN0IFNlcml2Y2UwgZ8wDQYJ\n" + + "KoZIhvcNAQEBBQADgY0AMIGJAoGBANY+7Enp+1S566kLcKk+qe4Ki6BxaHGZ+v7r\n" + + "vLksx9IQZCbAEf4YLbrZhKzKD3SPIJXyxPFwknAknIh3Knk8mViOZks7T8L3GnJr\n" + + "TBaVvDyTzDJum/QYiahfO2qpfN/Oya2UILmqsBAeLyWpzbQsAyWBXfoUtkOUgnzK\n" + + "fk6QAKYrAgMBAAGjgaUwgaIwHQYDVR0OBBYEFEtmQi7jT1ijXOafPsfkrLwSVu9e\n" + + "MGMGA1UdIwRcMFqAFEtmQi7jT1ijXOafPsfkrLwSVu9eoT+kPTA7MQswCQYDVQQG\n" + + "EwJVUzENMAsGA1UEChMESmF2YTEdMBsGA1UECxMUU3VuSlNTRSBUZXN0IFNlcml2\n" + + "Y2WCAQAwDwYDVR0TAQH/BAUwAwEB/zALBgNVHQ8EBAMCAQYwDQYJKoZIhvcNAQEE\n" + + "BQADgYEAkKWxMc4+ODk5WwLXXweB8/IKfVfrizNn0KLEgsZ6xNXFIXDpiPGAFcgl\n" + + "MzFO424JgyvUulsUc/X16Cnuwwntkk6KUG7vEV7h4o9sAV7Cax3gfQE/EZFb4ybn\n" + + "aBm1UsujMKd/ovqbbbxJbmOWzCeo0QfIGleDEyh3NBBZ0i11Kiw=\n" + + "-----END CERTIFICATE-----"; + + // web server certificate, www.example.com + static String targetCertStr_A = + "-----BEGIN CERTIFICATE-----\n" + + "MIICVTCCAb6gAwIBAgIBAjANBgkqhkiG9w0BAQQFADA7MQswCQYDVQQGEwJVUzEN\n" + + "MAsGA1UEChMESmF2YTEdMBsGA1UECxMUU3VuSlNTRSBUZXN0IFNlcml2Y2UwHhcN\n" + + "MTIwNDE3MTIwNjA4WhcNMzIwMTAzMTIwNjA4WjBVMQswCQYDVQQGEwJVUzENMAsG\n" + + "A1UEChMESmF2YTEdMBsGA1UECxMUU3VuSlNTRSBUZXN0IFNlcml2Y2UxGDAWBgNV\n" + + "BAMTD3d3dy5leGFtcGxlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA\n" + + "4zFp3PZNzsd3ZwG6FNNWO9eSN+UBymlf8oCwpKJM2tIinmMWvWIXnlx/2UXIfSAq\n" + + "QEG3aXkAFyEiGGpQlBbqcfrESsHsiz2pnnm5dG2v/eS0Bwz1jmcuNmwnh3UQw2Vl\n" + + "+BLk8ukdrLjiCT8jARiHExYf1Xg+wUqQ9y8NV26hdaUCAwEAAaNPME0wCwYDVR0P\n" + + "BAQDAgPoMB0GA1UdDgQWBBQwtx+gqzn2w4y82brXlp7tqBYEZDAfBgNVHSMEGDAW\n" + + "gBRLZkIu409Yo1zmnz7H5Ky8ElbvXjANBgkqhkiG9w0BAQQFAAOBgQAJWo8B6Ud+\n" + + "/OU+UcZLihlfMX02OSlK2ZB7mfqpj2G3JT9yb0A+VbY3uuajmaYYIIxl3kXGz/n8\n" + + "M2Q/Ux/MDxG+IFKHC26Kuj4dAQgzjq2pILVPTE2QnaQTNCsgVZtTaC47SG9FRSoC\n" + + "qvnIvn/oTpKSqus76I1cR4joDtiV2OEuVw==\n" + + "-----END CERTIFICATE-----"; + + // Private key in the format of PKCS#8 + static String targetPrivateKey_A = + "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAOMxadz2Tc7Hd2cB\n" + + "uhTTVjvXkjflAcppX/KAsKSiTNrSIp5jFr1iF55cf9lFyH0gKkBBt2l5ABchIhhq\n" + + "UJQW6nH6xErB7Is9qZ55uXRtr/3ktAcM9Y5nLjZsJ4d1EMNlZfgS5PLpHay44gk/\n" + + "IwEYhxMWH9V4PsFKkPcvDVduoXWlAgMBAAECgYAqX2nuIyXp3fvgA0twXOYlbRRB\n" + + "Rn3qAXM6qFPJsNeCrFR2k+aG1cev6nKR1FkLNTeMGnWZv06MAcr5IML8i7WXyG4C\n" + + "LY/C0gedn94FDKFlln+bTENwQTGjn4lKysDA+IuNpasTeMCajbic+dPByhIdTOjZ\n" + + "iMCyxbLfpk40zQopVQJBAPyfGmkeHB3GjdbdgujWCGKb2UxBa4O8dy3O4l2yizTn\n" + + "uUqMGcwGY4ciNSVvZQ7jKo4vDmkSuYib4/woPChaNfMCQQDmO0BQuSWYGNtSwV35\n" + + "lafZfX1dNCLKm1iNA6A12evXgvQiE9WT4mqionig0VZW16HtiY4/BkHOcos/K9Um\n" + + "ARQHAkA8mkaRtSF1my5nv1gqVz5Hua+VdZQ/VDUbDiiL5cszc+ulkJqXsWirAG/T\n" + + "fTe3LJQG7A7+8fkEZrF4yoY0AAA1AkEAotokezULj5N9iAL5SzL9wIzQYV4ggfny\n" + + "YATBjXXxKccakwQ+ndWZIiMUeoS4ssLialhTgucVI0fIkU2a/r/ifwJAc6e+5Pvh\n" + + "MghQj/U788Od/v6rgqz/NGsduZ7uilCMcWiwA73OR2MHMH/OIuoofuEPrfuV9isV\n" + + "xVXhgpKfP/pdOA=="; + + // web server certificate, www.example.net + static String targetCertStr_B = + "-----BEGIN CERTIFICATE-----\n" + + "MIICVTCCAb6gAwIBAgIBBDANBgkqhkiG9w0BAQQFADA7MQswCQYDVQQGEwJVUzEN\n" + + "MAsGA1UEChMESmF2YTEdMBsGA1UECxMUU3VuSlNTRSBUZXN0IFNlcml2Y2UwHhcN\n" + + "MTIwNDE3MTIwNjA5WhcNMzIwMTAzMTIwNjA5WjBVMQswCQYDVQQGEwJVUzENMAsG\n" + + "A1UEChMESmF2YTEdMBsGA1UECxMUU3VuSlNTRSBUZXN0IFNlcml2Y2UxGDAWBgNV\n" + + "BAMTD3d3dy5leGFtcGxlLm5ldDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA\n" + + "2VlzF1fvWYczDChrUeJiLJ1M/dIShCaOTfYGiXfQGEZCAWTacUclwr+rVMnZ75/c\n" + + "wwg5pNdXRijxMil8DBTS1gFcIFQhosLHvzIAe6ULlg/xB+/L6KBz+NTWfo/2KF6t\n" + + "xatmcToNrCcwi7eUOfbzQje65Tizs56jJYem2m7Rk0ECAwEAAaNPME0wCwYDVR0P\n" + + "BAQDAgPoMB0GA1UdDgQWBBQT/FR0cAWcZQ7h0X79KGki34OSQjAfBgNVHSMEGDAW\n" + + "gBRLZkIu409Yo1zmnz7H5Ky8ElbvXjANBgkqhkiG9w0BAQQFAAOBgQB67cPIT6fz\n" + + "6Ws8fBpYgW2ad4ci66i1WduBD9CpGFE+jRK2feRj6hvYBXocKj0AMWUFIEB2E3hA\n" + + "oIjxcf1GxIpHVl9DjlhxqXbA0Ktl7/NGNRlDSLTizOTl3FB1mMTlOGvXDVmpcFhl\n" + + "HuoP1hYvhTsBwPx5igGNchuPtDIUzL2mXw==\n" + + "-----END CERTIFICATE-----"; + + static String targetPrivateKey_B = + "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBANlZcxdX71mHMwwo\n" + + "a1HiYiydTP3SEoQmjk32Bol30BhGQgFk2nFHJcK/q1TJ2e+f3MMIOaTXV0Yo8TIp\n" + + "fAwU0tYBXCBUIaLCx78yAHulC5YP8Qfvy+igc/jU1n6P9ihercWrZnE6DawnMIu3\n" + + "lDn280I3uuU4s7OeoyWHptpu0ZNBAgMBAAECgYEAl19H26sfhD+32rDPxZCgBShs\n" + + "dZ33zVe45i0Bcn4iTLWpxKTDyf7eGps4rO2DvfKdYqt40ggzvSZIjUH9JcDe8GmG\n" + + "d3m0ILB7pg4jsFlpyeHpTO8grPLxA1G9s3o0DoFpz/rooqgFfe/DrRDmRoOSkgfV\n" + + "/gseIbgJHRO/Ctyvdh0CQQD6uFd0HxhH1jl/JzvPzIH4LSnPcdEh9zsMEb6uzh75\n" + + "9qL+IHD5N2I/pYZTKqDFIwhJf701+LKag55AX/zrDt7rAkEA3e00AbnwanDMa6Wj\n" + + "+gFekUQveSVra38LiihzCkyVvQpFjbiF1rUhSNQ0dpU5/hmrYF0C6H9VXAesfkUY\n" + + "WhpDgwJAYjgZOop77piDycZK7isFt32p5XSHIzFBVocVFlH1XKM8UyXOXDNQL/Le\n" + + "XnJSrSf+NRzvuNcG0PVC56Ey6brXpQJAY4M4vcltt5zq3R5CQBmbGRJ1IyKXX3Vx\n" + + "bDslEqoyvri7ZYgnY5aG3UxiVgYmIf3KrgQnCLAIS6MZQumiuMxsFwJAK5pEG063\n" + + "9ngUof4fDMvZphqZjZR1zMKz/V/9ge0DWBINaqFgsgebNu+MyImsC8C6WKjGmV/2\n" + + "f1MY0D7sC2vU/Q=="; + + // web server certificate, www.invalid.com + static String targetCertStr_C = + "-----BEGIN CERTIFICATE-----\n" + + "MIICVTCCAb6gAwIBAgIBAzANBgkqhkiG9w0BAQQFADA7MQswCQYDVQQGEwJVUzEN\n" + + "MAsGA1UEChMESmF2YTEdMBsGA1UECxMUU3VuSlNTRSBUZXN0IFNlcml2Y2UwHhcN\n" + + "MTIwNDE3MTIwNjA5WhcNMzIwMTAzMTIwNjA5WjBVMQswCQYDVQQGEwJVUzENMAsG\n" + + "A1UEChMESmF2YTEdMBsGA1UECxMUU3VuSlNTRSBUZXN0IFNlcml2Y2UxGDAWBgNV\n" + + "BAMTD3d3dy5pbnZhbGlkLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA\n" + + "q6MyQwzCr2nJ41l0frmHL0qULSyW51MhevBC+1W28i0LE/efrmpwV3LdnlQEGFak\n" + + "DLDwtnff3iru8dSMcA7KdWVkivsE7ZTP+qFDaWBAy7XXiSsv6yZ2Nh4jJb0YcD28\n" + + "45zk2nAl5Az1/PuoTi1vpQxzFZKuBm1HGgz3MEZvBvMCAwEAAaNPME0wCwYDVR0P\n" + + "BAQDAgPoMB0GA1UdDgQWBBRRMifrND015Nm8N6gV5X7cg1YjjjAfBgNVHSMEGDAW\n" + + "gBRLZkIu409Yo1zmnz7H5Ky8ElbvXjANBgkqhkiG9w0BAQQFAAOBgQBjkUO6Ri/B\n" + + "uDC2gDMIyL5+NTe/1dPPQYM4HhCNa/KQYvU5lzCKO9Vpa+i+nyrUNNXUu8Tkyq4Y\n" + + "A+aGSm6+FT/i9rFwkYUdorBtD3KfQiwTIWrVERXBkWI5iZNaVZhx0TFy4vUpf65d\n" + + "QtwkbHpC66fdKc2EdLXkuY9KkmtZZJJ7YA==\n" + + "-----END CERTIFICATE-----"; + + static String targetPrivateKey_C = + "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAKujMkMMwq9pyeNZ\n" + + "dH65hy9KlC0sludTIXrwQvtVtvItCxP3n65qcFdy3Z5UBBhWpAyw8LZ3394q7vHU\n" + + "jHAOynVlZIr7BO2Uz/qhQ2lgQMu114krL+smdjYeIyW9GHA9vOOc5NpwJeQM9fz7\n" + + "qE4tb6UMcxWSrgZtRxoM9zBGbwbzAgMBAAECgYASJDK40Y12Wvki1Z6xkkyOnBRj\n" + + "XfYpRykfxGtgA2RN3qLwHlk7Zzaul46DIKA6LlYynTUkJDF+Ww1cdDnP0lBlwcmM\n" + + "iD0ck3zYyYBLhQHuVbkK3SYE+ANRhM0icvvqANP2at/U4awQcPNEae/KCiecLNu3\n" + + "CJGqyhPDdrEAqPuJGQJBAN46pQC6l3yrcSYE2s53jSmsm2HVVOFlFXjU6k/RMTxG\n" + + "FfDJtGUAOQ37rPQ06ugr/gjLAmmPp+FXozaBdA32D80CQQDFuGRgv3WYqbglIcRL\n" + + "JRs6xlj9w1F97s/aiUenuwhIPNiUoRbV7mnNuZ/sGF0svOVE7SazRjuFX6UqL9Y9\n" + + "HzG/AkEA170pCI8cl4w8eUNHRB9trGKEKjMXhwVCFh7lJf2ZBcGodSzr8w2HVhrZ\n" + + "Ke7hiemDYffrbJ1oxmv05+o+x3r0lQJBAL6adVm2+FyFMFnLZXmzeb59O4jWY5bt\n" + + "Qz6/HG6bpO5OidMuP99YCHMkQQDOs/PO3Y5GuAoW6IY4n/Y9S2B80+0CQBl1/H9/\n" + + "0n/vrb6vW6Azds49tuS82RFAnOhtwTyBEajs08WF8rZQ3WD2RHJnH0+jjfL0anIp\n" + + "dQBSeNN7s7b6rRk="; + + // This is a certificate for client + static String targetCertStr_D= + "-----BEGIN CERTIFICATE-----\n" + + "MIICVDCCAb2gAwIBAgIBBTANBgkqhkiG9w0BAQQFADA7MQswCQYDVQQGEwJVUzEN\n" + + "MAsGA1UEChMESmF2YTEdMBsGA1UECxMUU3VuSlNTRSBUZXN0IFNlcml2Y2UwHhcN\n" + + "MTIwNDE3MTIwNjEwWhcNMzIwMTAzMTIwNjEwWjBUMQswCQYDVQQGEwJVUzENMAsG\n" + + "A1UEChMESmF2YTEdMBsGA1UECxMUU3VuSlNTRSBUZXN0IFNlcml2Y2UxFzAVBgNV\n" + + "BAMTDkludGVyT3AgVGVzdGVyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDo\n" + + "Q/KoAIAC2ljFfW2KwjnxTzi4NQJeUuk2seqKpsAY8x4O5dvixzUl6142zmljapqi\n" + + "bJloQVpfB+CEc5/l4h5gzGRVzkuqP1oPzDrpZ5GsvmvuHenV/TzCIgX1cLETzQVt\n" + + "6Rk06okoBPnw3hDJEJiEc1Rv7HCE8p/p+SaiHrskwwIDAQABo08wTTALBgNVHQ8E\n" + + "BAMCA+gwHQYDVR0OBBYEFPr91O33RIGfFSqza2AwQIgE4QswMB8GA1UdIwQYMBaA\n" + + "FEtmQi7jT1ijXOafPsfkrLwSVu9eMA0GCSqGSIb3DQEBBAUAA4GBANIDFYgAhoj3\n" + + "B8u1YpqeoEp2Lt9TwrYBshaIrbmBPCwCGio0JIsoov3n8BCSg5F+8MnOtPl+TjeO\n" + + "0Ug+7guPdCk/wg8YNxLHgSsQlpcNJDjWiErqmUPVrg5BPPQb65qMund6KTmMN0y6\n" + + "4EbSmxRpZO/N0/5oK4umTk0EeXKNekBj\n" + + "-----END CERTIFICATE-----"; + + static String targetPrivateKey_D = + "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAOhD8qgAgALaWMV9\n" + + "bYrCOfFPOLg1Al5S6Tax6oqmwBjzHg7l2+LHNSXrXjbOaWNqmqJsmWhBWl8H4IRz\n" + + "n+XiHmDMZFXOS6o/Wg/MOulnkay+a+4d6dX9PMIiBfVwsRPNBW3pGTTqiSgE+fDe\n" + + "EMkQmIRzVG/scITyn+n5JqIeuyTDAgMBAAECgYBw37yIKp4LRONJLnhSq6sO+0n8\n" + + "Mz6waiiN/Q6XTQwj09pysQAYCGlqwSRrDAqpVsBJWO+Ae+oYLrLMi4hUZnwN75v3\n" + + "pe1nXlrD11RmPLXwBxqFxNSvAs2FgLHZEtwHI7Bn8KybT/8bGkQ8csLceInYtMDD\n" + + "MuTyy2KRk/pj60zIKQJBAPgebQiAH6viFQ88AwHaNvQhlUfwmSC1i6f8LVoeqaHC\n" + + "lnP0LJBwlyDeeEInhHrCR2ibnCB6I/Pig+49XQgabK8CQQDvpJwuGEbsOO+3rkJJ\n" + + "OpOw4toG0QJZdRnT6l8I6BlboQRZSfFh+lGGahvFXkxc4KdUpJ7QPtXU7HHk6Huk\n" + + "8RYtAkA9CW8VGj+wTuuTVdX/jKjcIa7RhbSFwWNbrcOSWdys+Gt+luCnn6rt4QyA\n" + + "aaxDbquWZkFgE+voQR7nap0KM0XtAkAznd0WAJymHM1lXt9gLoHJQ9N6TGKZKiPa\n" + + "BU1a+cMcfV4WbVrUo7oTnZ9Fr73681iXXq3mZOJh7lvJ1llreZIxAkBEnbiTgEf4\n" + + "tvku68jHcRbRPmdS7CBSWNEBaHLOm4pUSTcxVTKKMHw7vmM5/UYUxJ8QNKCYxn6O\n" + + "+vtiBwBawwzN"; + + static String[] serverCerts = {targetCertStr_A, + targetCertStr_B, targetCertStr_C}; + static String[] serverKeys = {targetPrivateKey_A, + targetPrivateKey_B, targetPrivateKey_C}; + static String[] clientCerts = {targetCertStr_D}; + static String[] clientKeys = {targetPrivateKey_D}; + + static char passphrase[] = "passphrase".toCharArray(); + + /* + * Is the server ready to serve? + */ + volatile static boolean serverReady = false; + + /* + * Turn on SSL debugging? + */ + static boolean debug = false; + + /* + * Define the server side of the test. + * + * If the server prematurely exits, serverReady will be set to true + * to avoid infinite hangs. + */ + void doServerSide() throws Exception { + SSLContext context = generateSSLContext(false); + SSLServerSocketFactory sslssf = context.getServerSocketFactory(); + SSLServerSocket sslServerSocket = + (SSLServerSocket)sslssf.createServerSocket(serverPort); + serverPort = sslServerSocket.getLocalPort(); + + /* + * Signal Client, we're ready for his connect. + */ + serverReady = true; + + SSLSocket sslSocket = (SSLSocket)sslServerSocket.accept(); + try { + InputStream sslIS = sslSocket.getInputStream(); + OutputStream sslOS = sslSocket.getOutputStream(); + + sslIS.read(); + sslOS.write('A'); + sslOS.flush(); + + SSLSession session = sslSocket.getSession(); + checkCertificate(session.getLocalCertificates(), + clientRequestedHostname); + } finally { + sslSocket.close(); + sslServerSocket.close(); + } + } + + /* + * Define the client side of the test. + * + * If the server prematurely exits, serverReady will be set to true + * to avoid infinite hangs. + */ + void doClientSide() throws Exception { + + /* + * Wait for server to get started. + */ + while (!serverReady) { + Thread.sleep(50); + } + + SSLContext context = generateSSLContext(true); + SSLSocketFactory sslsf = context.getSocketFactory(); + + SSLSocket sslSocket = + (SSLSocket)sslsf.createSocket("localhost", serverPort); + + SNIHostName serverName = new SNIHostName(clientRequestedHostname); + List serverNames = new ArrayList<>(1); + serverNames.add(serverName); + SSLParameters params = sslSocket.getSSLParameters(); + params.setServerNames(serverNames); + sslSocket.setSSLParameters(params); + + try { + InputStream sslIS = sslSocket.getInputStream(); + OutputStream sslOS = sslSocket.getOutputStream(); + + sslOS.write('B'); + sslOS.flush(); + sslIS.read(); + + SSLSession session = sslSocket.getSession(); + checkCertificate(session.getPeerCertificates(), + clientRequestedHostname); + } finally { + sslSocket.close(); + } + } + + private static void checkCertificate(Certificate[] certs, + String hostname) throws Exception { + if (certs != null && certs.length != 0) { + X509Certificate x509Cert = (X509Certificate)certs[0]; + + String subject = x509Cert.getSubjectX500Principal().getName(); + + if (!subject.contains(hostname)) { + throw new Exception( + "Not the expected certificate: " + subject); + } + } + } + + /* + * ============================================================= + * The remainder is just support stuff + */ + private static String tmAlgorithm; // trust manager + private static String clientRequestedHostname; // server name indication + + private static void parseArguments(String[] args) { + tmAlgorithm = args[0]; + clientRequestedHostname = args[1]; + } + + private static SSLContext generateSSLContext(boolean isClient) + throws Exception { + + // generate certificate from cert string + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + + // create a key store + KeyStore ks = KeyStore.getInstance("JKS"); + ks.load(null, null); + + // import the trused cert + ByteArrayInputStream is = + new ByteArrayInputStream(trustedCertStr.getBytes()); + Certificate trusedCert = cf.generateCertificate(is); + is.close(); + + ks.setCertificateEntry("RSA Export Signer", trusedCert); + + String[] certStrs = null; + String[] keyStrs = null; + if (isClient) { + certStrs = clientCerts; + keyStrs = clientKeys; + } else { + certStrs = serverCerts; + keyStrs = serverKeys; + } + + for (int i = 0; i < certStrs.length; i++) { + // generate the private key. + String keySpecStr = keyStrs[i]; + PKCS8EncodedKeySpec priKeySpec = new PKCS8EncodedKeySpec( + new BASE64Decoder().decodeBuffer(keySpecStr)); + KeyFactory kf = KeyFactory.getInstance("RSA"); + RSAPrivateKey priKey = + (RSAPrivateKey)kf.generatePrivate(priKeySpec); + + // generate certificate chain + String keyCertStr = certStrs[i]; + is = new ByteArrayInputStream(keyCertStr.getBytes()); + Certificate keyCert = cf.generateCertificate(is); + is.close(); + + Certificate[] chain = new Certificate[2]; + chain[0] = keyCert; + chain[1] = trusedCert; + + // import the key entry. + ks.setKeyEntry("key-entry-" + i, priKey, passphrase, chain); + } + + // create SSL context + TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmAlgorithm); + tmf.init(ks); + + SSLContext ctx = SSLContext.getInstance("TLS"); + KeyManagerFactory kmf = KeyManagerFactory.getInstance("NewSunX509"); + kmf.init(ks, passphrase); + + ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + ks = null; + + return ctx; + } + + // use any free port by default + volatile int serverPort = 0; + + volatile Exception serverException = null; + volatile Exception clientException = null; + + public static void main(String[] args) throws Exception { + if (debug) + System.setProperty("javax.net.debug", "all"); + + /* + * Get the customized arguments. + */ + parseArguments(args); + + /* + * Start the tests. + */ + new SSLSocketSNISensitive(); + } + + Thread clientThread = null; + Thread serverThread = null; + + /* + * Primary constructor, used to drive remainder of the test. + * + * Fork off the other side, then do your work. + */ + SSLSocketSNISensitive() throws Exception { + try { + if (separateServerThread) { + startServer(true); + startClient(false); + } else { + startClient(true); + startServer(false); + } + } catch (Exception e) { + // swallow for now. Show later + } + + /* + * Wait for other side to close down. + */ + if (separateServerThread) { + serverThread.join(); + } else { + clientThread.join(); + } + + /* + * When we get here, the test is pretty much over. + * Which side threw the error? + */ + Exception local; + Exception remote; + String whichRemote; + + if (separateServerThread) { + remote = serverException; + local = clientException; + whichRemote = "server"; + } else { + remote = clientException; + local = serverException; + whichRemote = "client"; + } + + /* + * If both failed, return the curthread's exception, but also + * print the remote side Exception + */ + if ((local != null) && (remote != null)) { + System.out.println(whichRemote + " also threw:"); + remote.printStackTrace(); + System.out.println(); + throw local; + } + + if (remote != null) { + throw remote; + } + + if (local != null) { + throw local; + } + } + + void startServer(boolean newThread) throws Exception { + if (newThread) { + serverThread = new Thread() { + public void run() { + try { + doServerSide(); + } catch (Exception e) { + /* + * Our server thread just died. + * + * Release the client, if not active already... + */ + System.err.println("Server died..."); + serverReady = true; + serverException = e; + } + } + }; + serverThread.start(); + } else { + try { + doServerSide(); + } catch (Exception e) { + serverException = e; + } finally { + serverReady = true; + } + } + } + + void startClient(boolean newThread) throws Exception { + if (newThread) { + clientThread = new Thread() { + public void run() { + try { + doClientSide(); + } catch (Exception e) { + /* + * Our client thread just died. + */ + System.err.println("Client died..."); + clientException = e; + } + } + }; + clientThread.start(); + } else { + try { + doClientSide(); + } catch (Exception e) { + clientException = e; + } + } + } +} diff --git a/jdk/test/sun/security/ssl/templates/SSLCapabilities.java b/jdk/test/sun/security/ssl/templates/SSLCapabilities.java new file mode 100644 index 00000000000..22ea3600054 --- /dev/null +++ b/jdk/test/sun/security/ssl/templates/SSLCapabilities.java @@ -0,0 +1,70 @@ +/* + * 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. + */ + +import java.util.List; +import javax.net.ssl.SNIServerName; + +/** + * Encapsulates the security capabilities of an SSL/TLS connection. + *

+ * The security capabilities are the list of ciphersuites to be accepted in + * an SSL/TLS handshake, the record version, the hello version, and server + * name indication, etc., of an SSL/TLS connection. + *

+ * SSLCapabilities can be retrieved by exploring the network + * data of an SSL/TLS connection via {@link SSLExplorer#explore(ByteBuffer)} + * or {@link SSLExplorer#explore(byte[], int, int)}. + * + * @see SSLExplorer + */ +public abstract class SSLCapabilities { + + /** + * Returns the record version of an SSL/TLS connection + * + * @return a non-null record version + */ + public abstract String getRecordVersion(); + + /** + * Returns the hello version of an SSL/TLS connection + * + * @return a non-null hello version + */ + public abstract String getHelloVersion(); + + /** + * Returns a List containing all {@link SNIServerName}s + * of the server name indication. + * + * @return a non-null immutable list of {@link SNIServerName}s + * of the server name indication parameter, may be empty + * if no server name indication. + * + * @see SNIServerName + */ + public abstract List getServerNames(); +} + diff --git a/jdk/test/sun/security/ssl/templates/SSLExplorer.java b/jdk/test/sun/security/ssl/templates/SSLExplorer.java new file mode 100644 index 00000000000..9b912c850dd --- /dev/null +++ b/jdk/test/sun/security/ssl/templates/SSLExplorer.java @@ -0,0 +1,615 @@ +/* + * 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. + */ + +import java.nio.ByteBuffer; +import java.nio.BufferUnderflowException; +import java.io.IOException; +import javax.net.ssl.*; +import java.util.*; + +import sun.misc.HexDumpEncoder; + +/** + * Instances of this class acts as an explorer of the network data of an + * SSL/TLS connection. + */ +public final class SSLExplorer { + + // Private constructor prevents construction outside this class. + private SSLExplorer() { + } + + /** + * The header size of TLS/SSL records. + *

+ * The value of this constant is {@value}. + */ + public final static int RECORD_HEADER_SIZE = 0x05; + + /** + * Returns the required number of bytes in the {@code source} + * {@link ByteBuffer} necessary to explore SSL/TLS connection. + *

+ * This method tries to parse as few bytes as possible from + * {@code source} byte buffer to get the length of an + * SSL/TLS record. + *

+ * This method accesses the {@code source} parameter in read-only + * mode, and does not update the buffer's properties such as capacity, + * limit, position, and mark values. + * + * @param source + * a {@link ByteBuffer} containing + * inbound or outbound network data for an SSL/TLS connection. + * @throws BufferUnderflowException if less than {@code RECORD_HEADER_SIZE} + * bytes remaining in {@code source} + * @return the required size in byte to explore an SSL/TLS connection + */ + public final static int getRequiredSize(ByteBuffer source) { + + ByteBuffer input = source.duplicate(); + + // Do we have a complete header? + if (input.remaining() < RECORD_HEADER_SIZE) { + throw new BufferUnderflowException(); + } + + // Is it a handshake message? + byte firstByte = input.get(); + byte secondByte = input.get(); + byte thirdByte = input.get(); + if ((firstByte & 0x80) != 0 && thirdByte == 0x01) { + // looks like a V2ClientHello + // return (((firstByte & 0x7F) << 8) | (secondByte & 0xFF)) + 2; + return RECORD_HEADER_SIZE; // Only need the header fields + } else { + return (((input.get() & 0xFF) << 8) | (input.get() & 0xFF)) + 5; + } + } + + /** + * Returns the required number of bytes in the {@code source} byte array + * necessary to explore SSL/TLS connection. + *

+ * This method tries to parse as few bytes as possible from + * {@code source} byte array to get the length of an + * SSL/TLS record. + * + * @param source + * a byte array containing inbound or outbound network data for + * an SSL/TLS connection. + * @param offset + * the start offset in array {@code source} at which the + * network data is read from. + * @param length + * the maximum number of bytes to read. + * + * @throws BufferUnderflowException if less than {@code RECORD_HEADER_SIZE} + * bytes remaining in {@code source} + * @return the required size in byte to explore an SSL/TLS connection + */ + public final static int getRequiredSize(byte[] source, + int offset, int length) throws IOException { + + ByteBuffer byteBuffer = + ByteBuffer.wrap(source, offset, length).asReadOnlyBuffer(); + return getRequiredSize(byteBuffer); + } + + /** + * Launch and explore the security capabilities from byte buffer. + *

+ * This method tries to parse as few records as possible from + * {@code source} byte buffer to get the {@link SSLCapabilities} + * of an SSL/TLS connection. + *

+ * Please NOTE that this method must be called before any handshaking + * occurs. The behavior of this method is not defined in this release + * if the handshake has begun, or has completed. + *

+ * This method accesses the {@code source} parameter in read-only + * mode, and does not update the buffer's properties such as capacity, + * limit, position, and mark values. + * + * @param source + * a {@link ByteBuffer} containing + * inbound or outbound network data for an SSL/TLS connection. + * + * @throws IOException on network data error + * @throws BufferUnderflowException if not enough source bytes available + * to make a complete exploration. + * + * @return the explored {@link SSLCapabilities} of the SSL/TLS + * connection + */ + public final static SSLCapabilities explore(ByteBuffer source) + throws IOException { + + ByteBuffer input = source.duplicate(); + + // Do we have a complete header? + if (input.remaining() < RECORD_HEADER_SIZE) { + throw new BufferUnderflowException(); + } + + // Is it a handshake message? + byte firstByte = input.get(); + byte secondByte = input.get(); + byte thirdByte = input.get(); + if ((firstByte & 0x80) != 0 && thirdByte == 0x01) { + // looks like a V2ClientHello + return exploreV2HelloRecord(input, + firstByte, secondByte, thirdByte); + } else if (firstByte == 22) { // 22: handshake record + return exploreTLSRecord(input, + firstByte, secondByte, thirdByte); + } else { + throw new SSLException("Not handshake record"); + } + } + + /** + * Launch and explore the security capabilities from byte array. + *

+ * Please NOTE that this method must be called before any handshaking + * occurs. The behavior of this method is not defined in this release + * if the handshake has begun, or has completed. Once handshake has + * begun, or has completed, the security capabilities can not and + * should not be launched with this method. + * + * @param source + * a byte array containing inbound or outbound network data for + * an SSL/TLS connection. + * @param offset + * the start offset in array {@code source} at which the + * network data is read from. + * @param length + * the maximum number of bytes to read. + * + * @throws IOException on network data error + * @throws BufferUnderflowException if not enough source bytes available + * to make a complete exploration. + * @return the explored {@link SSLCapabilities} of the SSL/TLS + * connection + * + * @see #explore(ByteBuffer) + */ + public final static SSLCapabilities explore(byte[] source, + int offset, int length) throws IOException { + ByteBuffer byteBuffer = + ByteBuffer.wrap(source, offset, length).asReadOnlyBuffer(); + return explore(byteBuffer); + } + + /* + * uint8 V2CipherSpec[3]; + * struct { + * uint16 msg_length; // The highest bit MUST be 1; + * // the remaining bits contain the length + * // of the following data in bytes. + * uint8 msg_type; // MUST be 1 + * Version version; + * uint16 cipher_spec_length; // It cannot be zero and MUST be a + * // multiple of the V2CipherSpec length. + * uint16 session_id_length; // This field MUST be empty. + * uint16 challenge_length; // SHOULD use a 32-byte challenge + * V2CipherSpec cipher_specs[V2ClientHello.cipher_spec_length]; + * opaque session_id[V2ClientHello.session_id_length]; + * opaque challenge[V2ClientHello.challenge_length; + * } V2ClientHello; + */ + private static SSLCapabilities exploreV2HelloRecord( + ByteBuffer input, byte firstByte, byte secondByte, + byte thirdByte) throws IOException { + + // We only need the header. We have already had enough source bytes. + // int recordLength = (firstByte & 0x7F) << 8) | (secondByte & 0xFF); + try { + // Is it a V2ClientHello? + if (thirdByte != 0x01) { + throw new SSLException( + "Unsupported or Unrecognized SSL record"); + } + + // What's the hello version? + byte helloVersionMajor = input.get(); + byte helloVersionMinor = input.get(); + + // 0x00: major version of SSLv20 + // 0x02: minor version of SSLv20 + // + // SNIServerName is an extension, SSLv20 doesn't support extension. + return new SSLCapabilitiesImpl((byte)0x00, (byte)0x02, + helloVersionMajor, helloVersionMinor, + Collections.emptyList()); + } catch (BufferUnderflowException bufe) { + throw new SSLProtocolException( + "Invalid handshake record"); + } + } + + /* + * struct { + * uint8 major; + * uint8 minor; + * } ProtocolVersion; + * + * enum { + * change_cipher_spec(20), alert(21), handshake(22), + * application_data(23), (255) + * } ContentType; + * + * struct { + * ContentType type; + * ProtocolVersion version; + * uint16 length; + * opaque fragment[TLSPlaintext.length]; + * } TLSPlaintext; + */ + private static SSLCapabilities exploreTLSRecord( + ByteBuffer input, byte firstByte, byte secondByte, + byte thirdByte) throws IOException { + + // Is it a handshake message? + if (firstByte != 22) { // 22: handshake record + throw new SSLException("Not handshake record"); + } + + // We need the record version to construct SSLCapabilities. + byte recordMajorVersion = secondByte; + byte recordMinorVersion = thirdByte; + + // Is there enough data for a full record? + int recordLength = getInt16(input); + if (recordLength > input.remaining()) { + throw new BufferUnderflowException(); + } + + // We have already had enough source bytes. + try { + return exploreHandshake(input, + recordMajorVersion, recordMinorVersion, recordLength); + } catch (BufferUnderflowException bufe) { + throw new SSLProtocolException( + "Invalid handshake record"); + } + } + + /* + * enum { + * hello_request(0), client_hello(1), server_hello(2), + * certificate(11), server_key_exchange (12), + * certificate_request(13), server_hello_done(14), + * certificate_verify(15), client_key_exchange(16), + * finished(20) + * (255) + * } HandshakeType; + * + * struct { + * HandshakeType msg_type; + * uint24 length; + * select (HandshakeType) { + * case hello_request: HelloRequest; + * case client_hello: ClientHello; + * case server_hello: ServerHello; + * case certificate: Certificate; + * case server_key_exchange: ServerKeyExchange; + * case certificate_request: CertificateRequest; + * case server_hello_done: ServerHelloDone; + * case certificate_verify: CertificateVerify; + * case client_key_exchange: ClientKeyExchange; + * case finished: Finished; + * } body; + * } Handshake; + */ + private static SSLCapabilities exploreHandshake( + ByteBuffer input, byte recordMajorVersion, + byte recordMinorVersion, int recordLength) throws IOException { + + // What is the handshake type? + byte handshakeType = input.get(); + if (handshakeType != 0x01) { // 0x01: client_hello message + throw new IllegalStateException("Not initial handshaking"); + } + + // What is the handshake body length? + int handshakeLength = getInt24(input); + + // Theoretically, a single handshake message might span multiple + // records, but in practice this does not occur. + if (handshakeLength > (recordLength - 4)) { // 4: handshake header size + throw new SSLException("Handshake message spans multiple records"); + } + + input = input.duplicate(); + input.limit(handshakeLength + input.position()); + return exploreClientHello(input, + recordMajorVersion, recordMinorVersion); + } + + /* + * struct { + * uint32 gmt_unix_time; + * opaque random_bytes[28]; + * } Random; + * + * opaque SessionID<0..32>; + * + * uint8 CipherSuite[2]; + * + * enum { null(0), (255) } CompressionMethod; + * + * struct { + * ProtocolVersion client_version; + * Random random; + * SessionID session_id; + * CipherSuite cipher_suites<2..2^16-2>; + * CompressionMethod compression_methods<1..2^8-1>; + * select (extensions_present) { + * case false: + * struct {}; + * case true: + * Extension extensions<0..2^16-1>; + * }; + * } ClientHello; + */ + private static SSLCapabilities exploreClientHello( + ByteBuffer input, + byte recordMajorVersion, + byte recordMinorVersion) throws IOException { + + List snList = Collections.emptyList(); + + // client version + byte helloMajorVersion = input.get(); + byte helloMinorVersion = input.get(); + + // ignore random + int position = input.position(); + input.position(position + 32); // 32: the length of Random + + // ignore session id + ignoreByteVector8(input); + + // ignore cipher_suites + ignoreByteVector16(input); + + // ignore compression methods + ignoreByteVector8(input); + + if (input.remaining() > 0) { + snList = exploreExtensions(input); + } + + return new SSLCapabilitiesImpl( + recordMajorVersion, recordMinorVersion, + helloMajorVersion, helloMinorVersion, snList); + } + + /* + * struct { + * ExtensionType extension_type; + * opaque extension_data<0..2^16-1>; + * } Extension; + * + * enum { + * server_name(0), max_fragment_length(1), + * client_certificate_url(2), trusted_ca_keys(3), + * truncated_hmac(4), status_request(5), (65535) + * } ExtensionType; + */ + private static List exploreExtensions(ByteBuffer input) + throws IOException { + + int length = getInt16(input); // length of extensions + while (length > 0) { + int extType = getInt16(input); // extenson type + int extLen = getInt16(input); // length of extension data + + if (extType == 0x00) { // 0x00: type of server name indication + return exploreSNIExt(input, extLen); + } else { // ignore other extensions + ignoreByteVector(input, extLen); + } + + length -= extLen + 4; + } + + return Collections.emptyList(); + } + + /* + * struct { + * NameType name_type; + * select (name_type) { + * case host_name: HostName; + * } name; + * } ServerName; + * + * enum { + * host_name(0), (255) + * } NameType; + * + * opaque HostName<1..2^16-1>; + * + * struct { + * ServerName server_name_list<1..2^16-1> + * } ServerNameList; + */ + private static List exploreSNIExt(ByteBuffer input, + int extLen) throws IOException { + + Map sniMap = new LinkedHashMap<>(); + + int remains = extLen; + if (extLen >= 2) { // "server_name" extension in ClientHello + int listLen = getInt16(input); // length of server_name_list + if (listLen == 0 || listLen + 2 != extLen) { + throw new SSLProtocolException( + "Invalid server name indication extension"); + } + + remains -= 2; // 0x02: the length field of server_name_list + while (remains > 0) { + int code = getInt8(input); // name_type + int snLen = getInt16(input); // length field of server name + if (snLen > remains) { + throw new SSLProtocolException( + "Not enough data to fill declared vector size"); + } + byte[] encoded = new byte[snLen]; + input.get(encoded); + + SNIServerName serverName; + switch (code) { + case StandardConstants.SNI_HOST_NAME: + if (encoded.length == 0) { + throw new SSLProtocolException( + "Empty HostName in server name indication"); + } + serverName = new SNIHostName(encoded); + break; + default: + serverName = new UnknownServerName(code, encoded); + } + // check for duplicated server name type + if (sniMap.put(serverName.getType(), serverName) != null) { + throw new SSLProtocolException( + "Duplicated server name of type " + + serverName.getType()); + } + + remains -= encoded.length + 3; // NameType: 1 byte + // HostName length: 2 bytes + } + } else if (extLen == 0) { // "server_name" extension in ServerHello + throw new SSLProtocolException( + "Not server name indication extension in client"); + } + + if (remains != 0) { + throw new SSLProtocolException( + "Invalid server name indication extension"); + } + + return Collections.unmodifiableList( + new ArrayList<>(sniMap.values())); + } + + private static int getInt8(ByteBuffer input) { + return input.get(); + } + + private static int getInt16(ByteBuffer input) { + return ((input.get() & 0xFF) << 8) | (input.get() & 0xFF); + } + + private static int getInt24(ByteBuffer input) { + return ((input.get() & 0xFF) << 16) | ((input.get() & 0xFF) << 8) | + (input.get() & 0xFF); + } + + private static void ignoreByteVector8(ByteBuffer input) { + ignoreByteVector(input, getInt8(input)); + } + + private static void ignoreByteVector16(ByteBuffer input) { + ignoreByteVector(input, getInt16(input)); + } + + private static void ignoreByteVector24(ByteBuffer input) { + ignoreByteVector(input, getInt24(input)); + } + + private static void ignoreByteVector(ByteBuffer input, int length) { + if (length != 0) { + int position = input.position(); + input.position(position + length); + } + } + + private static class UnknownServerName extends SNIServerName { + UnknownServerName(int code, byte[] encoded) { + super(code, encoded); + } + } + + private static final class SSLCapabilitiesImpl extends SSLCapabilities { + private final static Map versionMap = new HashMap<>(5); + + private final String recordVersion; + private final String helloVersion; + List sniNames; + + static { + versionMap.put(0x0002, "SSLv2Hello"); + versionMap.put(0x0300, "SSLv3"); + versionMap.put(0x0301, "TLSv1"); + versionMap.put(0x0302, "TLSv1.1"); + versionMap.put(0x0303, "TLSv1.2"); + } + + SSLCapabilitiesImpl(byte recordMajorVersion, byte recordMinorVersion, + byte helloMajorVersion, byte helloMinorVersion, + List sniNames) { + + int version = (recordMajorVersion << 8) | recordMinorVersion; + this.recordVersion = versionMap.get(version) != null ? + versionMap.get(version) : + unknownVersion(recordMajorVersion, recordMinorVersion); + + version = (helloMajorVersion << 8) | helloMinorVersion; + this.helloVersion = versionMap.get(version) != null ? + versionMap.get(version) : + unknownVersion(helloMajorVersion, helloMinorVersion); + + this.sniNames = sniNames; + } + + @Override + public String getRecordVersion() { + return recordVersion; + } + + @Override + public String getHelloVersion() { + return helloVersion; + } + + @Override + public List getServerNames() { + if (!sniNames.isEmpty()) { + return Collections.unmodifiableList(sniNames); + } + + return sniNames; + } + + private static String unknownVersion(byte major, byte minor) { + return "Unknown-" + ((int)major) + "." + ((int)minor); + } + } +} +