changeset 16291:d4fe8dcfd6e7

8170282: Enable ALPN parameters to be supplied during the TLS handshake Reviewed-by: wetmore, xuelei
author vinnie
date Fri, 16 Dec 2016 14:32:51 +0000
parents ddcc67ffde8c
children adc00ab4ac58
files src/java.base/share/classes/javax/net/ssl/SSLEngine.java src/java.base/share/classes/javax/net/ssl/SSLSocket.java src/java.base/share/classes/sun/security/ssl/Handshaker.java src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java src/java.base/share/classes/sun/security/ssl/ServerHandshaker.java test/javax/net/ssl/ALPN/MyX509ExtendedKeyManager.java test/javax/net/ssl/ALPN/SSLEngineAlpnTest.java test/javax/net/ssl/ALPN/SSLServerSocketAlpnTest.java test/javax/net/ssl/ALPN/SSLSocketAlpnTest.java
diffstat 10 files changed, 580 insertions(+), 82 deletions(-) [+]
line wrap: on
line diff
--- a/src/java.base/share/classes/javax/net/ssl/SSLEngine.java	Fri Dec 16 19:50:35 2016 +0800
+++ b/src/java.base/share/classes/javax/net/ssl/SSLEngine.java	Fri Dec 16 14:32:51 2016 +0000
@@ -27,6 +27,8 @@
 
 import java.nio.ByteBuffer;
 import java.nio.ReadOnlyBufferException;
+import java.util.List;
+import java.util.function.BiFunction;
 
 
 /**
@@ -1332,4 +1334,89 @@
     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:
+     * <blockquote>
+     * <dl>
+     * <dt> {@code SSLEngine}
+     * <dd> The function's first argument allows the current {@code SSLEngine}
+     *      to be inspected, including the handshake session and configuration
+     *      settings.
+     * <dt> {@code List<String>}
+     * <dd> The function's second argument lists the application protocol names
+     *      advertised by the TLS peer.
+     * <dt> {@code String}
+     * <dd> 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.)
+     * </dl>
+     * </blockquote>
+     *
+     * For example, the following call registers a callback function that
+     * examines the TLS handshake parameters and selects an application protocol
+     * name:
+     * <pre>{@code
+     *     serverEngine.setHandshakeApplicationProtocolSelector(
+     *         (serverEngine, clientProtocols) -> {
+     *             SSLSession session = serverEngine.getHandshakeSession();
+     *             return chooseApplicationProtocol(
+     *                 serverEngine,
+     *                 clientProtocols,
+     *                 session.getProtocol(),
+     *                 session.getCipherSuite());
+     *         });
+     * }</pre>
+     *
+     * @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<SSLEngine, List<String>, 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<SSLEngine, List<String>, String>
+            getHandshakeApplicationProtocolSelector() {
+        throw new UnsupportedOperationException();
+    }
 }
--- a/src/java.base/share/classes/javax/net/ssl/SSLSocket.java	Fri Dec 16 19:50:35 2016 +0800
+++ b/src/java.base/share/classes/javax/net/ssl/SSLSocket.java	Fri Dec 16 14:32:51 2016 +0000
@@ -28,6 +28,8 @@
 
 import java.io.IOException;
 import java.net.*;
+import java.util.List;
+import java.util.function.BiFunction;
 
 /**
  * This class extends <code>Socket</code>s and provides secure
@@ -742,4 +744,89 @@
     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:
+     * <blockquote>
+     * <dl>
+     * <dt> {@code SSLSocket}
+     * <dd> The function's first argument allows the current {@code SSLSocket}
+     *      to be inspected, including the handshake session and configuration
+     *      settings.
+     * <dt> {@code List<String>}
+     * <dd> The function's second argument lists the application protocol names
+     *      advertised by the TLS peer.
+     * <dt> {@code String}
+     * <dd> 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.)
+     * </dl>
+     * </blockquote>
+     *
+     * For example, the following call registers a callback function that
+     * examines the TLS handshake parameters and selects an application protocol
+     * name:
+     * <pre>{@code
+     *     serverSocket.setHandshakeApplicationProtocolSelector(
+     *         (serverSocket, clientProtocols) -> {
+     *             SSLSession session = serverSocket.getHandshakeSession();
+     *             return chooseApplicationProtocol(
+     *                 serverSocket,
+     *                 clientProtocols,
+     *                 session.getProtocol(),
+     *                 session.getCipherSuite());
+     *         });
+     * }</pre>
+     *
+     * @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<SSLSocket, List<String>, 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<SSLSocket, List<String>, String>
+            getHandshakeApplicationProtocolSelector() {
+        throw new UnsupportedOperationException();
+    }
 }
--- a/src/java.base/share/classes/sun/security/ssl/Handshaker.java	Fri Dec 16 19:50:35 2016 +0800
+++ b/src/java.base/share/classes/sun/security/ssl/Handshaker.java	Fri Dec 16 14:32:51 2016 +0000
@@ -36,6 +36,7 @@
 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 @@
     // Negotiated ALPN value
     String applicationProtocol = null;
 
+    // Application protocol callback function (for SSLEngine)
+    BiFunction<SSLEngine,List<String>,String>
+        appProtocolSelectorSSLEngine = null;
+
+    // Application protocol callback function (for SSLSocket)
+    BiFunction<SSLSocket,List<String>,String>
+        appProtocolSelectorSSLSocket = null;
+
     // The maximum expected network packet size for SSL/TLS/DTLS records.
     int                         maximumPacketSize = 0;
 
@@ -501,6 +510,22 @@
     }
 
     /**
+     * Sets the Application Protocol selector function for SSLEngine.
+     */
+    void setApplicationProtocolSelectorSSLEngine(
+        BiFunction<SSLEngine,List<String>,String> selector) {
+        this.appProtocolSelectorSSLEngine = selector;
+    }
+
+    /**
+     * Sets the Application Protocol selector function for SSLSocket.
+     */
+    void setApplicationProtocolSelectorSSLSocket(
+        BiFunction<SSLSocket,List<String>,String> selector) {
+        this.appProtocolSelectorSSLSocket = selector;
+    }
+
+    /**
      * Sets the cipher suites preference.
      */
     void setUseCipherSuitesOrder(boolean on) {
--- a/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java	Fri Dec 16 19:50:35 2016 +0800
+++ b/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java	Fri Dec 16 14:32:51 2016 +0000
@@ -27,8 +27,9 @@
 
 import java.io.*;
 import java.nio.*;
+import java.security.*;
 import java.util.*;
-import java.security.*;
+import java.util.function.BiFunction;
 
 import javax.crypto.BadPaddingException;
 
@@ -206,6 +207,10 @@
     // 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<SSLEngine, List<String>, String> applicationProtocolSelector;
+
     // Have we been told whether we're client or server?
     private boolean                     serverModeSet = false;
     private boolean                     roleIsServer;
@@ -442,6 +447,8 @@
         handshaker.setEnabledCipherSuites(enabledCipherSuites);
         handshaker.setEnableSessionCreation(enableSessionCreation);
         handshaker.setApplicationProtocols(applicationProtocols);
+        handshaker.setApplicationProtocolSelectorSSLEngine(
+            applicationProtocolSelector);
 
         outputRecord.initHandshaker();
     }
@@ -2264,6 +2271,21 @@
         return null;
     }
 
+    @Override
+    public synchronized void setHandshakeApplicationProtocolSelector(
+        BiFunction<SSLEngine, List<String>, String> selector) {
+        applicationProtocolSelector = selector;
+        if ((handshaker != null) && !handshaker.activated()) {
+            handshaker.setApplicationProtocolSelectorSSLEngine(selector);
+        }
+    }
+
+    @Override
+    public synchronized BiFunction<SSLEngine, List<String>, String>
+        getHandshakeApplicationProtocolSelector() {
+        return this.applicationProtocolSelector;
+    }
+
     /**
      * Returns a printable representation of this end of the connection.
      */
--- a/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java	Fri Dec 16 19:50:35 2016 +0800
+++ b/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java	Fri Dec 16 14:32:51 2016 +0000
@@ -37,6 +37,7 @@
 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 @@
     // 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<SSLSocket, List<String>, 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 @@
         handshaker.setEnabledCipherSuites(enabledCipherSuites);
         handshaker.setEnableSessionCreation(enableSessionCreation);
         handshaker.setApplicationProtocols(applicationProtocols);
+        handshaker.setApplicationProtocolSelectorSSLSocket(
+            applicationProtocolSelector);
     }
 
     /**
@@ -2658,6 +2665,21 @@
         return null;
     }
 
+    @Override
+    public synchronized void setHandshakeApplicationProtocolSelector(
+        BiFunction<SSLSocket, List<String>, String> selector) {
+        applicationProtocolSelector = selector;
+        if ((handshaker != null) && !handshaker.activated()) {
+            handshaker.setApplicationProtocolSelectorSSLSocket(selector);
+        }
+    }
+
+    @Override
+    public synchronized BiFunction<SSLSocket, List<String>, String>
+        getHandshakeApplicationProtocolSelector() {
+        return this.applicationProtocolSelector;
+    }
+
     //
     // We allocate a separate thread to deliver handshake completion
     // events.  This ensures that the notifications don't block the
--- a/src/java.base/share/classes/sun/security/ssl/ServerHandshaker.java	Fri Dec 16 19:50:35 2016 +0800
+++ b/src/java.base/share/classes/sun/security/ssl/ServerHandshaker.java	Fri Dec 16 14:32:51 2016 +0000
@@ -34,6 +34,7 @@
 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 @@
         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<String> 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<String> 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;
+
+            } else {
+                applicationProtocol = "";
             }
-
-            if (negotiatedValue == null) {
-                fatalSE(Alerts.alert_no_application_protocol,
-                    new SSLHandshakeException(
-                        "No matching ALPN values"));
-            }
-            applicationProtocol = negotiatedValue;
-
-        } else {
-            applicationProtocol = "";
-        }
+        }  // Otherwise, applicationProtocol will be set by the callback.
 
         session = null; // forget about the current session
         //
@@ -892,8 +901,36 @@
         }
 
         // Prepare the ALPN response
-        if (applicationProtocol != null && !applicationProtocol.isEmpty()) {
-            m1.extensions.add(new ALPNExtension(applicationProtocol));
+        if (clientHelloALPN != null) {
+            List<String> 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")) {
--- a/test/javax/net/ssl/ALPN/MyX509ExtendedKeyManager.java	Fri Dec 16 19:50:35 2016 +0800
+++ b/test/javax/net/ssl/ALPN/MyX509ExtendedKeyManager.java	Fri Dec 16 14:32:51 2016 +0000
@@ -34,15 +34,17 @@
     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 @@
 
     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");
         }
--- a/test/javax/net/ssl/ALPN/SSLEngineAlpnTest.java	Fri Dec 16 19:50:35 2016 +0800
+++ b/test/javax/net/ssl/ALPN/SSLEngineAlpnTest.java	Fri Dec 16 14:32:51 2016 +0000
@@ -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 <server-APs> <callback-AP> <client-APs> <result>
+ *
+ * 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 java.io.*;
 import java.security.*;
 import java.nio.*;
+import java.util.Arrays;
 
 public class SSLEngineAlpnTest {
 
@@ -117,6 +148,9 @@
      */
     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 @@
         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 @@
         }
 
         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 @@
      * 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 @@
      * 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 @@
      * 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 @@
          */
         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) ||
--- a/test/javax/net/ssl/ALPN/SSLServerSocketAlpnTest.java	Fri Dec 16 19:50:35 2016 +0800
+++ b/test/javax/net/ssl/ALPN/SSLServerSocketAlpnTest.java	Fri Dec 16 14:32:51 2016 +0000
@@ -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
+ *             <server-APs> <callback-AP> <client-APs> <result>
+ *
+ * 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 @@
     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 @@
     static boolean debug = false;
 
     static String[] serverAPs;
+    static String callbackAP;
     static String[] clientAPs;
     static String expectedAP;
 
@@ -129,7 +172,9 @@
         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 @@
                     + "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 @@
         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 @@
         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 @@
         }
 
         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 @@
      * 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 {
--- a/test/javax/net/ssl/ALPN/SSLSocketAlpnTest.java	Fri Dec 16 19:50:35 2016 +0800
+++ b/test/javax/net/ssl/ALPN/SSLSocketAlpnTest.java	Fri Dec 16 14:32:51 2016 +0000
@@ -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 <server-APs> <callback-AP> <client-APs> <result>
+ *
+ * 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 @@
     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 @@
     static boolean debug = false;
 
     static String[] serverAPs;
+    static String callbackAP;
     static String[] clientAPs;
     static String expectedAP;
 
@@ -136,7 +178,9 @@
         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 @@
                     + "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 @@
         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 @@
         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 @@
         }
 
         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 @@
      * 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 {