From bd0a13fa2174a951612e7ae428ef3739aee8fa42 Mon Sep 17 00:00:00 2001 From: Vinnie Ryan Date: Fri, 16 Dec 2016 14:32:51 +0000 Subject: [PATCH] 8170282: Enable ALPN parameters to be supplied during the TLS handshake Reviewed-by: wetmore, xuelei --- .../classes/javax/net/ssl/SSLEngine.java | 87 ++++++++++++++ .../classes/javax/net/ssl/SSLSocket.java | 87 ++++++++++++++ .../classes/sun/security/ssl/Handshaker.java | 25 ++++ .../sun/security/ssl/SSLEngineImpl.java | 24 +++- .../sun/security/ssl/SSLSocketImpl.java | 22 ++++ .../sun/security/ssl/ServerHandshaker.java | 81 +++++++++---- .../ssl/ALPN/MyX509ExtendedKeyManager.java | 10 +- .../javax/net/ssl/ALPN/SSLEngineAlpnTest.java | 112 ++++++++++++++---- .../net/ssl/ALPN/SSLServerSocketAlpnTest.java | 107 ++++++++++++++--- .../javax/net/ssl/ALPN/SSLSocketAlpnTest.java | 105 +++++++++++++--- 10 files changed, 579 insertions(+), 81 deletions(-) diff --git a/jdk/src/java.base/share/classes/javax/net/ssl/SSLEngine.java b/jdk/src/java.base/share/classes/javax/net/ssl/SSLEngine.java index 300265cbcd4..2c724f73a67 100644 --- a/jdk/src/java.base/share/classes/javax/net/ssl/SSLEngine.java +++ b/jdk/src/java.base/share/classes/javax/net/ssl/SSLEngine.java @@ -27,6 +27,8 @@ package javax.net.ssl; import java.nio.ByteBuffer; import java.nio.ReadOnlyBufferException; +import java.util.List; +import java.util.function.BiFunction; /** @@ -1332,4 +1334,89 @@ public abstract class SSLEngine { public String getHandshakeApplicationProtocol() { throw new UnsupportedOperationException(); } + + /** + * Registers a callback function that selects an application protocol + * value for a SSL/TLS/DTLS handshake. + * The function overrides any values set using + * {@link SSLParameters#setApplicationProtocols + * SSLParameters.setApplicationProtocols} and it supports the following + * type parameters: + *
+ *
+ *
{@code SSLEngine} + *
The function's first argument allows the current {@code SSLEngine} + * to be inspected, including the handshake session and configuration + * settings. + *
{@code List} + *
The function's second argument lists the application protocol names + * advertised by the TLS peer. + *
{@code String} + *
The function's result is an application protocol name, or null to + * indicate that none of the advertised names are acceptable. + * If the return value is null (no value chosen) or is a value that + * was not advertised by the peer, the underlying protocol will + * determine what action to take. (For example, ALPN will send a + * "no_application_protocol" alert and terminate the connection.) + *
+ *
+ * + * For example, the following call registers a callback function that + * examines the TLS handshake parameters and selects an application protocol + * name: + *
{@code
+     *     serverEngine.setHandshakeApplicationProtocolSelector(
+     *         (serverEngine, clientProtocols) -> {
+     *             SSLSession session = serverEngine.getHandshakeSession();
+     *             return chooseApplicationProtocol(
+     *                 serverEngine,
+     *                 clientProtocols,
+     *                 session.getProtocol(),
+     *                 session.getCipherSuite());
+     *         });
+     * }
+ * + * @apiNote + * This method should be called by TLS server applications before the TLS + * handshake begins. Also, this {@code SSLEngine} should be configured with + * parameters that are compatible with the application protocol selected by + * the callback function. For example, enabling a poor choice of cipher + * suites could result in no suitable application protocol. + * See {@link SSLParameters}. + * + * @implSpec + * The implementation in this class throws + * {@code UnsupportedOperationException} and performs no other action. + * + * @param selector the callback function, or null to disable the callback + * functionality. + * @throws UnsupportedOperationException if the underlying provider + * does not implement the operation. + * @since 9 + */ + public void setHandshakeApplicationProtocolSelector( + BiFunction, String> selector) { + throw new UnsupportedOperationException(); + } + + /** + * Retrieves the callback function that selects an application protocol + * value during a SSL/TLS/DTLS handshake. + * See {@link #setHandshakeApplicationProtocolSelector + * setHandshakeApplicationProtocolSelector} + * for the function's type parameters. + * + * @implSpec + * The implementation in this class throws + * {@code UnsupportedOperationException} and performs no other action. + * + * @return the callback function, or null if none has been set. + * @throws UnsupportedOperationException if the underlying provider + * does not implement the operation. + * @since 9 + */ + public BiFunction, String> + getHandshakeApplicationProtocolSelector() { + throw new UnsupportedOperationException(); + } } diff --git a/jdk/src/java.base/share/classes/javax/net/ssl/SSLSocket.java b/jdk/src/java.base/share/classes/javax/net/ssl/SSLSocket.java index daaefe08ec7..ebbc9d9eb8d 100644 --- a/jdk/src/java.base/share/classes/javax/net/ssl/SSLSocket.java +++ b/jdk/src/java.base/share/classes/javax/net/ssl/SSLSocket.java @@ -28,6 +28,8 @@ package javax.net.ssl; import java.io.IOException; import java.net.*; +import java.util.List; +import java.util.function.BiFunction; /** * This class extends Sockets and provides secure @@ -742,4 +744,89 @@ public abstract class SSLSocket extends Socket public String getHandshakeApplicationProtocol() { throw new UnsupportedOperationException(); } + + + /** + * Registers a callback function that selects an application protocol + * value for a SSL/TLS/DTLS handshake. + * The function overrides any values set using + * {@link SSLParameters#setApplicationProtocols + * SSLParameters.setApplicationProtocols} and it supports the following + * type parameters: + *
+ *
+ *
{@code SSLSocket} + *
The function's first argument allows the current {@code SSLSocket} + * to be inspected, including the handshake session and configuration + * settings. + *
{@code List} + *
The function's second argument lists the application protocol names + * advertised by the TLS peer. + *
{@code String} + *
The function's result is an application protocol name, or null to + * indicate that none of the advertised names are acceptable. + * If the return value is null (no value chosen) or is a value that + * was not advertised by the peer, the underlying protocol will + * determine what action to take. (For example, ALPN will send a + * "no_application_protocol" alert and terminate the connection.) + *
+ *
+ * + * For example, the following call registers a callback function that + * examines the TLS handshake parameters and selects an application protocol + * name: + *
{@code
+     *     serverSocket.setHandshakeApplicationProtocolSelector(
+     *         (serverSocket, clientProtocols) -> {
+     *             SSLSession session = serverSocket.getHandshakeSession();
+     *             return chooseApplicationProtocol(
+     *                 serverSocket,
+     *                 clientProtocols,
+     *                 session.getProtocol(),
+     *                 session.getCipherSuite());
+     *         });
+     * }
+ * + * @apiNote + * This method should be called by TLS server applications before the TLS + * handshake begins. Also, this {@code SSLSocket} should be configured with + * parameters that are compatible with the application protocol selected by + * the callback function. For example, enabling a poor choice of cipher + * suites could result in no suitable application protocol. + * See {@link SSLParameters}. + * + * @implSpec + * The implementation in this class throws + * {@code UnsupportedOperationException} and performs no other action. + * + * @param selector the callback function, or null to de-register. + * @throws UnsupportedOperationException if the underlying provider + * does not implement the operation. + * @since 9 + */ + public void setHandshakeApplicationProtocolSelector( + BiFunction, String> selector) { + throw new UnsupportedOperationException(); + } + + /** + * Retrieves the callback function that selects an application protocol + * value during a SSL/TLS/DTLS handshake. + * See {@link #setHandshakeApplicationProtocolSelector + * setHandshakeApplicationProtocolSelector} + * for the function's type parameters. + * + * @implSpec + * The implementation in this class throws + * {@code UnsupportedOperationException} and performs no other action. + * + * @return the callback function, or null if none has been set. + * @throws UnsupportedOperationException if the underlying provider + * does not implement the operation. + * @since 9 + */ + public BiFunction, String> + getHandshakeApplicationProtocolSelector() { + throw new UnsupportedOperationException(); + } } diff --git a/jdk/src/java.base/share/classes/sun/security/ssl/Handshaker.java b/jdk/src/java.base/share/classes/sun/security/ssl/Handshaker.java index 2e309dc8bb5..f830bc70ce1 100644 --- a/jdk/src/java.base/share/classes/sun/security/ssl/Handshaker.java +++ b/jdk/src/java.base/share/classes/sun/security/ssl/Handshaker.java @@ -36,6 +36,7 @@ import java.security.AlgorithmConstraints; import java.security.AccessControlContext; import java.security.PrivilegedExceptionAction; import java.security.PrivilegedActionException; +import java.util.function.BiFunction; import javax.crypto.*; import javax.crypto.spec.*; @@ -122,6 +123,14 @@ abstract class Handshaker { // Negotiated ALPN value String applicationProtocol = null; + // Application protocol callback function (for SSLEngine) + BiFunction,String> + appProtocolSelectorSSLEngine = null; + + // Application protocol callback function (for SSLSocket) + BiFunction,String> + appProtocolSelectorSSLSocket = null; + // The maximum expected network packet size for SSL/TLS/DTLS records. int maximumPacketSize = 0; @@ -500,6 +509,22 @@ abstract class Handshaker { return applicationProtocol; } + /** + * Sets the Application Protocol selector function for SSLEngine. + */ + void setApplicationProtocolSelectorSSLEngine( + BiFunction,String> selector) { + this.appProtocolSelectorSSLEngine = selector; + } + + /** + * Sets the Application Protocol selector function for SSLSocket. + */ + void setApplicationProtocolSelectorSSLSocket( + BiFunction,String> selector) { + this.appProtocolSelectorSSLSocket = selector; + } + /** * Sets the cipher suites preference. */ diff --git a/jdk/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java b/jdk/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java index 6aaaf893d80..f9941165771 100644 --- a/jdk/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java +++ b/jdk/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java @@ -27,8 +27,9 @@ package sun.security.ssl; import java.io.*; import java.nio.*; -import java.util.*; import java.security.*; +import java.util.*; +import java.util.function.BiFunction; import javax.crypto.BadPaddingException; @@ -206,6 +207,10 @@ public final class SSLEngineImpl extends SSLEngine { // The value under negotiation will be obtained from handshaker. String applicationProtocol = null; + // Callback function that selects the application protocol value during + // the SSL/TLS handshake. + BiFunction, String> applicationProtocolSelector; + // Have we been told whether we're client or server? private boolean serverModeSet = false; private boolean roleIsServer; @@ -442,6 +447,8 @@ public final class SSLEngineImpl extends SSLEngine { handshaker.setEnabledCipherSuites(enabledCipherSuites); handshaker.setEnableSessionCreation(enableSessionCreation); handshaker.setApplicationProtocols(applicationProtocols); + handshaker.setApplicationProtocolSelectorSSLEngine( + applicationProtocolSelector); outputRecord.initHandshaker(); } @@ -2264,6 +2271,21 @@ public final class SSLEngineImpl extends SSLEngine { return null; } + @Override + public synchronized void setHandshakeApplicationProtocolSelector( + BiFunction, String> selector) { + applicationProtocolSelector = selector; + if ((handshaker != null) && !handshaker.activated()) { + handshaker.setApplicationProtocolSelectorSSLEngine(selector); + } + } + + @Override + public synchronized BiFunction, String> + getHandshakeApplicationProtocolSelector() { + return this.applicationProtocolSelector; + } + /** * Returns a printable representation of this end of the connection. */ diff --git a/jdk/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java b/jdk/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java index 880c98a80a4..2e20d538d6e 100644 --- a/jdk/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java +++ b/jdk/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java @@ -37,6 +37,7 @@ import java.security.AlgorithmConstraints; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; +import java.util.function.BiFunction; import javax.crypto.BadPaddingException; import javax.net.ssl.*; @@ -223,6 +224,10 @@ public final class SSLSocketImpl extends BaseSSLSocketImpl { // The value under negotiation will be obtained from handshaker. String applicationProtocol = null; + // Callback function that selects the application protocol value during + // the SSL/TLS handshake. + BiFunction, String> applicationProtocolSelector; + /* * READ ME * READ ME * READ ME * READ ME * READ ME * READ ME * * IMPORTANT STUFF TO UNDERSTANDING THE SYNCHRONIZATION ISSUES. @@ -1370,6 +1375,8 @@ public final class SSLSocketImpl extends BaseSSLSocketImpl { handshaker.setEnabledCipherSuites(enabledCipherSuites); handshaker.setEnableSessionCreation(enableSessionCreation); handshaker.setApplicationProtocols(applicationProtocols); + handshaker.setApplicationProtocolSelectorSSLSocket( + applicationProtocolSelector); } /** @@ -2658,6 +2665,21 @@ public final class SSLSocketImpl extends BaseSSLSocketImpl { return null; } + @Override + public synchronized void setHandshakeApplicationProtocolSelector( + BiFunction, String> selector) { + applicationProtocolSelector = selector; + if ((handshaker != null) && !handshaker.activated()) { + handshaker.setApplicationProtocolSelectorSSLSocket(selector); + } + } + + @Override + public synchronized BiFunction, String> + getHandshakeApplicationProtocolSelector() { + return this.applicationProtocolSelector; + } + // // We allocate a separate thread to deliver handshake completion // events. This ensures that the notifications don't block the diff --git a/jdk/src/java.base/share/classes/sun/security/ssl/ServerHandshaker.java b/jdk/src/java.base/share/classes/sun/security/ssl/ServerHandshaker.java index 17777825f3f..4ad18b163e5 100644 --- a/jdk/src/java.base/share/classes/sun/security/ssl/ServerHandshaker.java +++ b/jdk/src/java.base/share/classes/sun/security/ssl/ServerHandshaker.java @@ -34,6 +34,7 @@ import java.security.cert.*; import java.security.interfaces.*; import java.security.spec.ECParameterSpec; import java.math.BigInteger; +import java.util.function.BiFunction; import javax.crypto.SecretKey; import javax.net.ssl.*; @@ -532,31 +533,39 @@ final class ServerHandshaker extends Handshaker { ALPNExtension clientHelloALPN = (ALPNExtension) mesg.extensions.get(ExtensionType.EXT_ALPN); - if ((clientHelloALPN != null) && (localApl.length > 0)) { + // Use the application protocol callback when provided. + // Otherwise use the local list of application protocols. + boolean hasAPCallback = + ((engine != null && appProtocolSelectorSSLEngine != null) || + (conn != null && appProtocolSelectorSSLSocket != null)); - // Intersect the requested and the locally supported, - // and save for later. - String negotiatedValue = null; - List protocols = clientHelloALPN.getPeerAPs(); + if (!hasAPCallback) { + if ((clientHelloALPN != null) && (localApl.length > 0)) { - // Use server preference order - for (String ap : localApl) { - if (protocols.contains(ap)) { - negotiatedValue = ap; - break; + // Intersect the requested and the locally supported, + // and save for later. + String negotiatedValue = null; + List protocols = clientHelloALPN.getPeerAPs(); + + // Use server preference order + for (String ap : localApl) { + if (protocols.contains(ap)) { + negotiatedValue = ap; + break; + } } - } - if (negotiatedValue == null) { - fatalSE(Alerts.alert_no_application_protocol, - new SSLHandshakeException( - "No matching ALPN values")); - } - applicationProtocol = negotiatedValue; + if (negotiatedValue == null) { + fatalSE(Alerts.alert_no_application_protocol, + new SSLHandshakeException( + "No matching ALPN values")); + } + applicationProtocol = negotiatedValue; - } else { - applicationProtocol = ""; - } + } else { + applicationProtocol = ""; + } + } // Otherwise, applicationProtocol will be set by the callback. session = null; // forget about the current session // @@ -892,8 +901,36 @@ final class ServerHandshaker extends Handshaker { } // Prepare the ALPN response - if (applicationProtocol != null && !applicationProtocol.isEmpty()) { - m1.extensions.add(new ALPNExtension(applicationProtocol)); + if (clientHelloALPN != null) { + List peerAPs = clientHelloALPN.getPeerAPs(); + + // check for a callback function + if (hasAPCallback) { + if (conn != null) { + applicationProtocol = + appProtocolSelectorSSLSocket.apply(conn, peerAPs); + } else { + applicationProtocol = + appProtocolSelectorSSLEngine.apply(engine, peerAPs); + } + } + + // check for no-match and that the selected name was also proposed + // by the TLS peer + if (applicationProtocol == null || + (!applicationProtocol.isEmpty() && + !peerAPs.contains(applicationProtocol))) { + + fatalSE(Alerts.alert_no_application_protocol, + new SSLHandshakeException( + "No matching ALPN values")); + + } else if (!applicationProtocol.isEmpty()) { + m1.extensions.add(new ALPNExtension(applicationProtocol)); + } + } else { + // Nothing was negotiated, returned at end of the handshake + applicationProtocol = ""; } if (debug != null && Debug.isOn("handshake")) { diff --git a/jdk/test/javax/net/ssl/ALPN/MyX509ExtendedKeyManager.java b/jdk/test/javax/net/ssl/ALPN/MyX509ExtendedKeyManager.java index d457766d5d4..ecd7dcfc4eb 100644 --- a/jdk/test/javax/net/ssl/ALPN/MyX509ExtendedKeyManager.java +++ b/jdk/test/javax/net/ssl/ALPN/MyX509ExtendedKeyManager.java @@ -34,15 +34,17 @@ public class MyX509ExtendedKeyManager extends X509ExtendedKeyManager { static final String ERROR = "ERROR"; X509ExtendedKeyManager akm; String expectedAP; + boolean doCheck = true; MyX509ExtendedKeyManager(X509ExtendedKeyManager akm) { this.akm = akm; } public MyX509ExtendedKeyManager( - X509ExtendedKeyManager akm, String expectedAP) { + X509ExtendedKeyManager akm, String expectedAP, boolean doCheck) { this.akm = akm; this.expectedAP = expectedAP; + this.doCheck = doCheck; } @@ -104,6 +106,12 @@ public class MyX509ExtendedKeyManager extends X509ExtendedKeyManager { private void checkALPN(String ap) { + if (!doCheck) { + System.out.println("Skipping KeyManager checks " + + "because a callback has been registered"); + return; + } + if (ERROR.equals(expectedAP)) { throw new RuntimeException("Should not reach here"); } diff --git a/jdk/test/javax/net/ssl/ALPN/SSLEngineAlpnTest.java b/jdk/test/javax/net/ssl/ALPN/SSLEngineAlpnTest.java index de4ea6cc0b6..b17da745841 100644 --- a/jdk/test/javax/net/ssl/ALPN/SSLEngineAlpnTest.java +++ b/jdk/test/javax/net/ssl/ALPN/SSLEngineAlpnTest.java @@ -26,23 +26,53 @@ /* * @test - * @bug 8051498 8145849 + * @bug 8051498 8145849 8170282 * @summary JEP 244: TLS Application-Layer Protocol Negotiation Extension * @compile MyX509ExtendedKeyManager.java - * @run main/othervm SSLEngineAlpnTest h2 h2 h2 - * @run main/othervm SSLEngineAlpnTest h2 h2,http/1.1 h2 - * @run main/othervm SSLEngineAlpnTest h2,http/1.1 h2,http/1.1 h2 - * @run main/othervm SSLEngineAlpnTest http/1.1,h2 h2,http/1.1 http/1.1 - * @run main/othervm SSLEngineAlpnTest h4,h3,h2 h1,h2 h2 - * @run main/othervm SSLEngineAlpnTest EMPTY h2,http/1.1 NONE - * @run main/othervm SSLEngineAlpnTest h2 EMPTY NONE - * @run main/othervm SSLEngineAlpnTest H2 h2 ERROR - * @run main/othervm SSLEngineAlpnTest h2 http/1.1 ERROR + * + * @run main/othervm SSLEngineAlpnTest h2 UNUSED h2 h2 + * @run main/othervm SSLEngineAlpnTest h2 UNUSED h2,http/1.1 h2 + * @run main/othervm SSLEngineAlpnTest h2,http/1.1 UNUSED h2,http/1.1 h2 + * @run main/othervm SSLEngineAlpnTest http/1.1,h2 UNUSED h2,http/1.1 http/1.1 + * @run main/othervm SSLEngineAlpnTest h4,h3,h2 UNUSED h1,h2 h2 + * @run main/othervm SSLEngineAlpnTest EMPTY UNUSED h2,http/1.1 NONE + * @run main/othervm SSLEngineAlpnTest h2 UNUSED EMPTY NONE + * @run main/othervm SSLEngineAlpnTest H2 UNUSED h2 ERROR + * @run main/othervm SSLEngineAlpnTest h2 UNUSED http/1.1 ERROR + * + * @run main/othervm SSLEngineAlpnTest UNUSED h2 h2 h2 + * @run main/othervm SSLEngineAlpnTest UNUSED h2 h2,http/1.1 h2 + * @run main/othervm SSLEngineAlpnTest UNUSED h2 http/1.1,h2 h2 + * @run main/othervm SSLEngineAlpnTest UNUSED http/1.1 h2,http/1.1 http/1.1 + * @run main/othervm SSLEngineAlpnTest UNUSED EMPTY h2,http/1.1 NONE + * @run main/othervm SSLEngineAlpnTest UNUSED h2 EMPTY NONE + * @run main/othervm SSLEngineAlpnTest UNUSED H2 h2 ERROR + * @run main/othervm SSLEngineAlpnTest UNUSED h2 http/1.1 ERROR + * + * @run main/othervm SSLEngineAlpnTest h2 h2 h2 h2 + * @run main/othervm SSLEngineAlpnTest H2 h2 h2,http/1.1 h2 + * @run main/othervm SSLEngineAlpnTest h2,http/1.1 http/1.1 h2,http/1.1 http/1.1 + * @run main/othervm SSLEngineAlpnTest http/1.1,h2 h2 h2,http/1.1 h2 + * @run main/othervm SSLEngineAlpnTest EMPTY h2 h2 h2 + * @run main/othervm SSLEngineAlpnTest h2,http/1.1 EMPTY http/1.1 NONE + * @run main/othervm SSLEngineAlpnTest h2,http/1.1 h2 EMPTY NONE + * @run main/othervm SSLEngineAlpnTest UNUSED UNUSED http/1.1,h2 NONE + * @run main/othervm SSLEngineAlpnTest h2 h2 http/1.1 ERROR + * @run main/othervm SSLEngineAlpnTest h2,http/1.1 H2 http/1.1 ERROR */ /** * A simple SSLEngine-based client/server that demonstrates the proposed API * changes for JEP 244 in support of the TLS ALPN extension (RFC 7301). * + * Usage: + * java SSLEngineAlpnTest + * + * where: + * EMPTY indicates that ALPN is disabled + * UNUSED indicates that no ALPN values are supplied (server-side only) + * ERROR indicates that an exception is expected + * NONE indicates that no ALPN is expected + * * This example is based on our standard SSLEngineTemplate. * * The immediate consumer of ALPN will be HTTP/2 (RFC 7540), aka H2. The H2 IETF @@ -98,6 +128,7 @@ import javax.net.ssl.SSLEngineResult.*; import java.io.*; import java.security.*; import java.nio.*; +import java.util.Arrays; public class SSLEngineAlpnTest { @@ -117,6 +148,9 @@ public class SSLEngineAlpnTest { */ private static final boolean debug = false; + private static boolean hasServerAPs; // whether server APs are present + private static boolean hasCallback; // whether a callback is present + private final SSLContext sslc; private SSLEngine clientEngine; // client Engine @@ -157,17 +191,21 @@ public class SSLEngineAlpnTest { if (debug) { System.setProperty("javax.net.debug", "all"); } + System.out.println("Test args: " + Arrays.toString(args)); // Validate parameters - if (args.length != 3) { + if (args.length != 4) { throw new Exception("Invalid number of test parameters"); } - SSLEngineAlpnTest test = new SSLEngineAlpnTest(args[2]); + hasServerAPs = !args[0].equals("UNUSED"); // are server APs being used? + hasCallback = !args[1].equals("UNUSED"); // is callback being used? + + SSLEngineAlpnTest test = new SSLEngineAlpnTest(args[3]); try { - test.runTest(convert(args[0]), convert(args[1]), args[2]); + test.runTest(convert(args[0]), args[1], convert(args[2]), args[3]); } catch (SSLHandshakeException she) { - if (args[2].equals("ERROR")) { + if (args[3].equals("ERROR")) { System.out.println("Caught the expected exception: " + she); } else { throw she; @@ -199,7 +237,8 @@ public class SSLEngineAlpnTest { } kms = new KeyManager[] { new MyX509ExtendedKeyManager( - (X509ExtendedKeyManager) kms[0], expectedAP) }; + (X509ExtendedKeyManager) kms[0], expectedAP, + !hasCallback && hasServerAPs) }; TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); tmf.init(ts); @@ -215,12 +254,15 @@ public class SSLEngineAlpnTest { * Convert a comma-separated list into an array of strings. */ private static String[] convert(String list) { - String[] strings = null; + if (list.equals("UNUSED")) { + return null; + } if (list.equals("EMPTY")) { return new String[0]; } + String[] strings; if (list.indexOf(',') > 0) { strings = list.split(","); } else { @@ -247,12 +289,12 @@ public class SSLEngineAlpnTest { * One could easily separate these phases into separate * sections of code. */ - private void runTest(String[] serverAPs, String[] clientAPs, - String expectedAP) throws Exception { + private void runTest(String[] serverAPs, String callbackAP, + String[] clientAPs, String expectedAP) throws Exception { boolean dataDone = false; - createSSLEngines(serverAPs, clientAPs); + createSSLEngines(serverAPs, callbackAP, clientAPs); createBuffers(); SSLEngineResult clientResult; // results from client's last operation @@ -364,8 +406,8 @@ public class SSLEngineAlpnTest { * Using the SSLContext created during object creation, * create/configure the SSLEngines we'll use for this test. */ - private void createSSLEngines(String[] serverAPs, String[] clientAPs) - throws Exception { + private void createSSLEngines(String[] serverAPs, String callbackAP, + String[] clientAPs) throws Exception { /* * Configure the serverEngine to act as a server in the SSL/TLS * handshake. Also, require SSL client authentication. @@ -385,18 +427,42 @@ public class SSLEngineAlpnTest { */ String[] suites = sslp.getCipherSuites(); sslp.setCipherSuites(suites); - sslp.setApplicationProtocols(serverAPs); + if (serverAPs != null) { + sslp.setApplicationProtocols(serverAPs); + } sslp.setUseCipherSuitesOrder(true); // Set server side order serverEngine.setSSLParameters(sslp); + // check that no callback has been registered + if (serverEngine.getHandshakeApplicationProtocolSelector() != null) { + throw new Exception("getHandshakeApplicationProtocolSelector() " + + "should return null"); + } + + if (hasCallback) { + serverEngine.setHandshakeApplicationProtocolSelector( + (sslEngine, clientProtocols) -> { + return callbackAP.equals("EMPTY") ? "" : callbackAP; + }); + + // check that the callback can be retrieved + if (serverEngine.getHandshakeApplicationProtocolSelector() + == null) { + throw new Exception("getHandshakeApplicationProtocolSelector()" + + " should return non-null"); + } + } + /* * Similar to above, but using client mode instead. */ clientEngine = sslc.createSSLEngine("client", 80); clientEngine.setUseClientMode(true); sslp = clientEngine.getSSLParameters(); - sslp.setApplicationProtocols(clientAPs); + if (clientAPs != null) { + sslp.setApplicationProtocols(clientAPs); + } clientEngine.setSSLParameters(sslp); if ((clientEngine.getHandshakeApplicationProtocol() != null) || diff --git a/jdk/test/javax/net/ssl/ALPN/SSLServerSocketAlpnTest.java b/jdk/test/javax/net/ssl/ALPN/SSLServerSocketAlpnTest.java index a310cbb0336..a9373ed09f9 100644 --- a/jdk/test/javax/net/ssl/ALPN/SSLServerSocketAlpnTest.java +++ b/jdk/test/javax/net/ssl/ALPN/SSLServerSocketAlpnTest.java @@ -26,22 +26,61 @@ /* * @test - * @bug 8051498 8145849 8158978 + * @bug 8051498 8145849 8158978 8170282 * @summary JEP 244: TLS Application-Layer Protocol Negotiation Extension * @compile MyX509ExtendedKeyManager.java - * @run main/othervm SSLServerSocketAlpnTest h2 h2 h2 - * @run main/othervm SSLServerSocketAlpnTest h2 h2,http/1.1 h2 - * @run main/othervm SSLServerSocketAlpnTest h2,http/1.1 h2,http/1.1 h2 - * @run main/othervm SSLServerSocketAlpnTest http/1.1,h2 h2,http/1.1 http/1.1 - * @run main/othervm SSLServerSocketAlpnTest h4,h3,h2 h1,h2 h2 - * @run main/othervm SSLServerSocketAlpnTest EMPTY h2,http/1.1 NONE - * @run main/othervm SSLServerSocketAlpnTest h2 EMPTY NONE - * @run main/othervm SSLServerSocketAlpnTest H2 h2 ERROR - * @run main/othervm SSLServerSocketAlpnTest h2 http/1.1 ERROR + * + * @run main/othervm SSLServerSocketAlpnTest h2 UNUSED h2 h2 + * @run main/othervm SSLServerSocketAlpnTest h2 UNUSED h2,http/1.1 h2 + * @run main/othervm SSLServerSocketAlpnTest h2,http/1.1 UNUSED h2,http/1.1 h2 + * @run main/othervm SSLServerSocketAlpnTest http/1.1,h2 UNUSED h2,http/1.1 http/1.1 + * @run main/othervm SSLServerSocketAlpnTest h4,h3,h2 UNUSED h1,h2 h2 + * @run main/othervm SSLServerSocketAlpnTest EMPTY UNUSED h2,http/1.1 NONE + * @run main/othervm SSLServerSocketAlpnTest h2 UNUSED EMPTY NONE + * @run main/othervm SSLServerSocketAlpnTest H2 UNUSED h2 ERROR + * @run main/othervm SSLServerSocketAlpnTest h2 UNUSED http/1.1 ERROR + * + * @run main/othervm SSLServerSocketAlpnTest UNUSED h2 h2 h2 + * @run main/othervm SSLServerSocketAlpnTest UNUSED h2 h2,http/1.1 h2 + * @run main/othervm SSLServerSocketAlpnTest UNUSED h2 http/1.1,h2 h2 + * @run main/othervm SSLServerSocketAlpnTest UNUSED http/1.1 h2,http/1.1 http/1.1 + * @run main/othervm SSLServerSocketAlpnTest UNUSED EMPTY h2,http/1.1 NONE + * @run main/othervm SSLServerSocketAlpnTest UNUSED h2 EMPTY NONE + * @run main/othervm SSLServerSocketAlpnTest UNUSED H2 h2 ERROR + * @run main/othervm SSLServerSocketAlpnTest UNUSED h2 http/1.1 ERROR + * + * @run main/othervm SSLServerSocketAlpnTest h2 h2 h2 h2 + * @run main/othervm SSLServerSocketAlpnTest H2 h2 h2,http/1.1 h2 + * @run main/othervm SSLServerSocketAlpnTest h2,http/1.1 http/1.1 h2,http/1.1 http/1.1 + * @run main/othervm SSLServerSocketAlpnTest http/1.1,h2 h2 h2,http/1.1 h2 + * @run main/othervm SSLServerSocketAlpnTest EMPTY h2 h2 h2 + * @run main/othervm SSLServerSocketAlpnTest h2,http/1.1 EMPTY http/1.1 NONE + * @run main/othervm SSLServerSocketAlpnTest h2,http/1.1 h2 EMPTY NONE + * @run main/othervm SSLServerSocketAlpnTest UNUSED UNUSED http/1.1,h2 NONE + * @run main/othervm SSLServerSocketAlpnTest h2 h2 http/1.1 ERROR + * @run main/othervm SSLServerSocketAlpnTest h2,http/1.1 H2 http/1.1 ERROR + * * @author Brad Wetmore */ +/** + * A simple SSLSocket-based client/server that demonstrates the proposed API + * changes for JEP 244 in support of the TLS ALPN extension (RFC 7301). + * + * Usage: + * java SSLServerSocketAlpnTest + * + * + * where: + * EMPTY indicates that ALPN is disabled + * UNUSED indicates that no ALPN values are supplied (server-side only) + * ERROR indicates that an exception is expected + * NONE indicates that no ALPN is expected + * + * This example is based on our standard SSLSocketTemplate. + */ import java.io.*; import java.security.KeyStore; +import java.util.Arrays; import javax.net.ssl.*; @@ -73,6 +112,9 @@ public class SSLServerSocketAlpnTest { static String trustFilename = System.getProperty("test.src", ".") + "/" + pathToStores + "/" + trustStoreFile; + private static boolean hasServerAPs; // whether server APs are present + private static boolean hasCallback; // whether a callback is present + /* * SSLContext */ @@ -89,6 +131,7 @@ public class SSLServerSocketAlpnTest { static boolean debug = false; static String[] serverAPs; + static String callbackAP; static String[] clientAPs; static String expectedAP; @@ -129,7 +172,9 @@ public class SSLServerSocketAlpnTest { sslp.setUseCipherSuitesOrder(true); // Set server side order // Set the ALPN selection. - sslp.setApplicationProtocols(serverAPs); + if (serverAPs != null) { + sslp.setApplicationProtocols(serverAPs); + } sslServerSocket.setSSLParameters(sslp); serverPort = sslServerSocket.getLocalPort(); @@ -146,6 +191,25 @@ public class SSLServerSocketAlpnTest { + "return null before the handshake starts"); } + // check that no callback has been registered + if (sslSocket.getHandshakeApplicationProtocolSelector() != null) { + throw new Exception("getHandshakeApplicationProtocolSelector() " + + "should return null"); + } + + if (hasCallback) { + sslSocket.setHandshakeApplicationProtocolSelector( + (serverSocket, clientProtocols) -> { + return callbackAP.equals("EMPTY") ? "" : callbackAP; + }); + + // check that the callback can be retrieved + if (sslSocket.getHandshakeApplicationProtocolSelector() == null) { + throw new Exception("getHandshakeApplicationProtocolSelector()" + + " should return non-null"); + } + } + sslSocket.startHandshake(); if (sslSocket.getHandshakeApplicationProtocol() != null) { @@ -276,14 +340,19 @@ public class SSLServerSocketAlpnTest { if (debug) { System.setProperty("javax.net.debug", "all"); } + System.out.println("Test args: " + Arrays.toString(args)); // Validate parameters - if (args.length != 3) { + if (args.length != 4) { throw new Exception("Invalid number of test parameters"); } serverAPs = convert(args[0]); - clientAPs = convert(args[1]); - expectedAP = args[2]; + callbackAP = args[1]; + clientAPs = convert(args[2]); + expectedAP = args[3]; + + hasServerAPs = !args[0].equals("UNUSED"); // are server APs being used? + hasCallback = !callbackAP.equals("UNUSED"); // is callback being used? /* * Start the tests. @@ -291,7 +360,7 @@ public class SSLServerSocketAlpnTest { try { new SSLServerSocketAlpnTest(); } catch (SSLHandshakeException she) { - if (args[2].equals("ERROR")) { + if (args[3].equals("ERROR")) { System.out.println("Caught the expected exception: " + she); } else { throw she; @@ -322,7 +391,8 @@ public class SSLServerSocketAlpnTest { } kms = new KeyManager[] { new MyX509ExtendedKeyManager( - (X509ExtendedKeyManager) kms[0], expectedAP) }; + (X509ExtendedKeyManager) kms[0], expectedAP, + !hasCallback && hasServerAPs) }; TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); tmf.init(trustKS); @@ -338,12 +408,15 @@ public class SSLServerSocketAlpnTest { * Convert a comma-separated list into an array of strings. */ private static String[] convert(String list) { - String[] strings; + if (list.equals("UNUSED")) { + return null; + } if (list.equals("EMPTY")) { return new String[0]; } + String[] strings; if (list.indexOf(',') > 0) { strings = list.split(","); } else { diff --git a/jdk/test/javax/net/ssl/ALPN/SSLSocketAlpnTest.java b/jdk/test/javax/net/ssl/ALPN/SSLSocketAlpnTest.java index fd52f251f9d..ef72474f417 100644 --- a/jdk/test/javax/net/ssl/ALPN/SSLSocketAlpnTest.java +++ b/jdk/test/javax/net/ssl/ALPN/SSLSocketAlpnTest.java @@ -26,22 +26,60 @@ /* * @test - * @bug 8051498 8145849 + * @bug 8051498 8145849 8170282 * @summary JEP 244: TLS Application-Layer Protocol Negotiation Extension * @compile MyX509ExtendedKeyManager.java - * @run main/othervm SSLSocketAlpnTest h2 h2 h2 - * @run main/othervm SSLSocketAlpnTest h2 h2,http/1.1 h2 - * @run main/othervm SSLSocketAlpnTest h2,http/1.1 h2,http/1.1 h2 - * @run main/othervm SSLSocketAlpnTest http/1.1,h2 h2,http/1.1 http/1.1 - * @run main/othervm SSLSocketAlpnTest h4,h3,h2 h1,h2 h2 - * @run main/othervm SSLSocketAlpnTest EMPTY h2,http/1.1 NONE - * @run main/othervm SSLSocketAlpnTest h2 EMPTY NONE - * @run main/othervm SSLSocketAlpnTest H2 h2 ERROR - * @run main/othervm SSLSocketAlpnTest h2 http/1.1 ERROR + * + * @run main/othervm SSLSocketAlpnTest h2 UNUSED h2 h2 + * @run main/othervm SSLSocketAlpnTest h2 UNUSED h2,http/1.1 h2 + * @run main/othervm SSLSocketAlpnTest h2,http/1.1 UNUSED h2,http/1.1 h2 + * @run main/othervm SSLSocketAlpnTest http/1.1,h2 UNUSED h2,http/1.1 http/1.1 + * @run main/othervm SSLSocketAlpnTest h4,h3,h2 UNUSED h1,h2 h2 + * @run main/othervm SSLSocketAlpnTest EMPTY UNUSED h2,http/1.1 NONE + * @run main/othervm SSLSocketAlpnTest h2 UNUSED EMPTY NONE + * @run main/othervm SSLSocketAlpnTest H2 UNUSED h2 ERROR + * @run main/othervm SSLSocketAlpnTest h2 UNUSED http/1.1 ERROR + * + * @run main/othervm SSLSocketAlpnTest UNUSED h2 h2 h2 + * @run main/othervm SSLSocketAlpnTest UNUSED h2 h2,http/1.1 h2 + * @run main/othervm SSLSocketAlpnTest UNUSED h2 http/1.1,h2 h2 + * @run main/othervm SSLSocketAlpnTest UNUSED http/1.1 h2,http/1.1 http/1.1 + * @run main/othervm SSLSocketAlpnTest UNUSED EMPTY h2,http/1.1 NONE + * @run main/othervm SSLSocketAlpnTest UNUSED h2 EMPTY NONE + * @run main/othervm SSLSocketAlpnTest UNUSED H2 h2 ERROR + * @run main/othervm SSLSocketAlpnTest UNUSED h2 http/1.1 ERROR + * + * @run main/othervm SSLSocketAlpnTest h2 h2 h2 h2 + * @run main/othervm SSLSocketAlpnTest H2 h2 h2,http/1.1 h2 + * @run main/othervm SSLSocketAlpnTest h2,http/1.1 http/1.1 h2,http/1.1 http/1.1 + * @run main/othervm SSLSocketAlpnTest http/1.1,h2 h2 h2,http/1.1 h2 + * @run main/othervm SSLSocketAlpnTest EMPTY h2 h2 h2 + * @run main/othervm SSLSocketAlpnTest h2,http/1.1 EMPTY http/1.1 NONE + * @run main/othervm SSLSocketAlpnTest h2,http/1.1 h2 EMPTY NONE + * @run main/othervm SSLSocketAlpnTest UNUSED UNUSED http/1.1,h2 NONE + * @run main/othervm SSLSocketAlpnTest h2 h2 http/1.1 ERROR + * @run main/othervm SSLSocketAlpnTest h2,http/1.1 H2 http/1.1 ERROR + * * @author Brad Wetmore */ +/** + * A simple SSLSocket-based client/server that demonstrates the proposed API + * changes for JEP 244 in support of the TLS ALPN extension (RFC 7301). + * + * Usage: + * java SSLSocketAlpnTest + * + * where: + * EMPTY indicates that ALPN is disabled + * UNUSED indicates that no ALPN values are supplied (server-side only) + * ERROR indicates that an exception is expected + * NONE indicates that no ALPN is expected + * + * This example is based on our standard SSLSocketTemplate. + */ import java.io.*; import java.security.KeyStore; +import java.util.Arrays; import javax.net.ssl.*; @@ -73,6 +111,9 @@ public class SSLSocketAlpnTest { static String trustFilename = System.getProperty("test.src", ".") + "/" + pathToStores + "/" + trustStoreFile; + private static boolean hasServerAPs; // whether server APs are present + private static boolean hasCallback; // whether a callback is present + /* * SSLContext */ @@ -89,6 +130,7 @@ public class SSLSocketAlpnTest { static boolean debug = false; static String[] serverAPs; + static String callbackAP; static String[] clientAPs; static String expectedAP; @@ -136,7 +178,9 @@ public class SSLSocketAlpnTest { sslp.setUseCipherSuitesOrder(true); // Set server side order // Set the ALPN selection. - sslp.setApplicationProtocols(serverAPs); + if (serverAPs != null) { + sslp.setApplicationProtocols(serverAPs); + } sslSocket.setSSLParameters(sslp); if (sslSocket.getHandshakeApplicationProtocol() != null) { @@ -144,6 +188,24 @@ public class SSLSocketAlpnTest { + "return null before the handshake starts"); } + // check that no callback has been registered + if (sslSocket.getHandshakeApplicationProtocolSelector() != null) { + throw new Exception("getHandshakeApplicationProtocolSelector() " + + "should return null"); + } + + if (hasCallback) { + sslSocket.setHandshakeApplicationProtocolSelector( + (serverSocket, clientProtocols) -> { + return callbackAP.equals("EMPTY") ? "" : callbackAP; + }); + + // check that the callback can be retrieved + if (sslSocket.getHandshakeApplicationProtocolSelector() == null) { + throw new Exception("getHandshakeApplicationProtocolSelector()" + " should return non-null"); + } + } + sslSocket.startHandshake(); if (sslSocket.getHandshakeApplicationProtocol() != null) { @@ -274,14 +336,19 @@ public class SSLSocketAlpnTest { if (debug) { System.setProperty("javax.net.debug", "all"); } + System.out.println("Test args: " + Arrays.toString(args)); // Validate parameters - if (args.length != 3) { + if (args.length != 4) { throw new Exception("Invalid number of test parameters"); } serverAPs = convert(args[0]); - clientAPs = convert(args[1]); - expectedAP = args[2]; + callbackAP = args[1]; + clientAPs = convert(args[2]); + expectedAP = args[3]; + + hasServerAPs = !args[0].equals("UNUSED"); // are server APs being used? + hasCallback = !callbackAP.equals("UNUSED"); // is callback being used? /* * Start the tests. @@ -289,7 +356,7 @@ public class SSLSocketAlpnTest { try { new SSLSocketAlpnTest(); } catch (SSLHandshakeException she) { - if (args[2].equals("ERROR")) { + if (args[3].equals("ERROR")) { System.out.println("Caught the expected exception: " + she); } else { throw she; @@ -320,7 +387,8 @@ public class SSLSocketAlpnTest { } kms = new KeyManager[] { new MyX509ExtendedKeyManager( - (X509ExtendedKeyManager) kms[0], expectedAP) }; + (X509ExtendedKeyManager) kms[0], expectedAP, + !hasCallback && hasServerAPs) }; TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); tmf.init(trustKS); @@ -336,12 +404,15 @@ public class SSLSocketAlpnTest { * Convert a comma-separated list into an array of strings. */ private static String[] convert(String list) { - String[] strings; + if (list.equals("UNUSED")) { + return null; + } if (list.equals("EMPTY")) { return new String[0]; } + String[] strings; if (list.indexOf(',') > 0) { strings = list.split(","); } else {