Some applications might want or need to negotiate a shared application level value before a TLS handshake has completed. For example, HTTP/2 uses the Application Layer Protocol Negotiation mechanism to help establish which HTTP version ("h2", "spdy/3", "http/1.1") can or will be used on a particular TCP or UDP port. ALPN (RFC 7301) does this without adding network round-trips between the client and the server. In the case of HTTP/2 the protocol must be established before the connection is negotiated, as client and server need to know what version of HTTP to use before they start communicating. Without ALPN it would not be possible to have application protocols HTTP/1 and HTTP/2 on the same port.
The client uses the ALPN extension at the beginning of the TLS handshake to send a list of supported application protocols to
the server as part of the
ClientHello
. The server reads the list of supported application protocols in the
ClientHello
, and determines which of the supported protocols it prefers. It then sends a
ServerHello
message back to the client with the negotiation result. The message may contain either the name of the
protocol that has been chosen or that no protocol has been chosen.
The application protocol negotiation can thus be accomplished within the TLS handshake, without adding network round-trips, and allows the server to associate a different certificate with each application protocol, if desired.
Unlike many other TLS extensions, this extension does not establish properties of the session, only of the connection.
That's why you'll find the negotiated values in the
SSLSocket
/
SSLEngine
, not the
SSLSession
. When session resumption or session tickets are used (see TLS Session Resumption without Server-Side State), the previously
negotiated values are irrelevant, and only the values in the new handshake messages are considered.
Set the Application Layer Protocol Negotiation (ALPN) values supported by the client. During the handshake with the server, the server will read the client's list of application protocols and will determine which is most suitable.
For the client, use the
SSLParameters.setApplicationProtocols(String[])
method, followed by the
setSSLParameters
method of either
SSLSocket
or
SSLEngine
to set up the application protocols to send to the server.
For example, here are the steps to set ALPN values of
"three"
and
"two"
, on the client.
To run the code the property
javax.net.ssl.trustStore
must be set to a valid root certificate. (This can be done on the command line).
import java.io.*; import java.util.*; import javax.net.ssl.*; public class SSLClient { public static void main(String[] args) throws Exception { // Code for creating a client side SSLSocket SSLSocketFactory sslsf = (SSLSocketFactory) SSLSocketFactory.getDefault(); SSLSocket sslSocket = (SSLSocket) sslsf.createSocket("localhost", 9999); // Get an SSLParameters object from the SSLSocket SSLParameters sslp = sslSocket.getSSLParameters(); // Populate SSLParameters with the ALPN values // On the client side the order doesn't matter as // when connecting to a JDK server, the server's list takes priority String[] clientAPs = {"three", "two"}; sslp.setApplicationProtocols(clientAPs); // Populate the SSLSocket object with the SSLParameters object // containing the ALPN values sslSocket.setSSLParameters(sslp); sslSocket.startHandshake(); // After the handshake, get the application protocol that has been negotiated String ap = sslSocket.getApplicationProtocol(); System.out.println("Application Protocol client side: \"" + ap + "\""); // Do simple write/read InputStream sslIS = sslSocket.getInputStream(); OutputStream sslOS = sslSocket.getOutputStream(); sslOS.write(280); sslOS.flush(); sslIS.read(); sslSocket.close(); } }
When this code is run and sends a
ClientHello
to a Java server that has set the ALPN values
one
,
two
, and
three
, the output will be:
Application Protocol client side: two
It is also possible to check the results of the negotiation during handshaking. See Determining Negotiated ALPN Value during Handshaking.
Use the default ALPN mechanism to determine a suitable application protocol by setting ALPN values on the server.
To use the default mechanism for ALPN on the server, populate anSSLParameters
object with the ALPN values you wish to set, and then use this
SSLParameters
object to populate either the
SSLSocket
object or the
SSLEngine
object with these parameters as you have done when you set up ALPN on the client (see the section
Setting up ALPN on the Client). The first value of the ALPN values set on the server that matches any of the ALPN values
contained in the
ClientHello
will be chosen and returned to the client as part of the
ServerHello
.
Here is the code for a Java server that uses the default approach for protocol negotiation. To run the code the property
javax.net.ssl.keyStore
must be set to a valid keystore. (This can be done on the command line, see
Creating
a Keystore to Use with JSSE).
import java.util.*; import javax.net.ssl.*; public class SSLServer { public static void main(String[] args) throws Exception { // Code for creating a server side SSLSocket SSLServerSocketFactory sslssf = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault(); SSLServerSocket sslServerSocket = (SSLServerSocket) sslssf.createServerSocket(9999); SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept(); // Get an SSLParameters object from the SSLSocket SSLParameters sslp = sslSocket.getSSLParameters(); // Populate SSLParameters with the ALPN values // As this is server side, put them in order of preference String[] serverAPs ={ "one", "two", "three" }; sslp.setApplicationProtocols(serverAPs); // If necessary at any time, get the ALPN values set on the // SSLParameters object with: // String serverAPs = sslp.setApplicationProtocols(); // Populate the SSLSocket object with the ALPN values sslSocket.setSSLParameters(sslp); sslSocket.startHandshake(); // After the handshake, get the application protocol that // has been negotiated String ap = sslSocket.getApplicationProtocol(); System.out.println("Application Protocol server side: \"" + ap + "\""); // Continue with the work of the server InputStream sslIS = sslSocket.getInputStream(); OutputStream sslOS = sslSocket.getOutputStream(); sslIS.read(); sslOS.write(85); sslOS.flush(); sslSocket.close(); } }When this code is run and a Java client sends a
ClientHello
with ALPN values
three
and
two
, the output is:
Application Protocol server side: two
It is also possible to check the results of the negotiation during handshaking. See Determining Negotiated ALPN Value during Handshaking.
Use the custom ALPN mechanism to determine a suitable application protocol by setting up a callback method.
If you do not want to use the server's default negotiation protocol, you can use the
setHandshakeApplicationProtocolSelector
method of
SSLEngine
or
SSLSocket
to register a
BiFunction
(lambda) callback that can examine the handshake state so far, and then make your selection based on the
client's list of application protocols and any other relevant information. For example, you may consider using the cipher suite
suggested, or the Server Name Indication (SNI) or any other data you can obtain in making the choice. If custom negotiation is
used, the values set by the
setApplicationProtocols
method (default negotiation) will be ignored.
Here is the code for a Java server that uses the custom mechanism for protocol negotiation. To run the code the property
javax.net.ssl.keyStore
must be set to a valid certificate. (This can be done on the command line, see
Creating
a Keystore to Use with JSSE).
import java.util.*; import javax.net.ssl.*; public class SSLServer { public static void main(String[] args) throws Exception { // Code for creating a server side SSLSocket SSLServerSocketFactory sslssf = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault(); SSLServerSocket sslServerSocket = (SSLServerSocket) sslssf.createServerSocket(9999); SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept(); // Code to set up a callback function // Pass in the current SSLSocket to be inspected and client AP values sslSocket.setHandshakeApplicationProtocolSelector( (serverSocket, clientProtocols) -> { SSLSession handshakeSession = serverSocket.getHandshakeSession(); // callback function called with current SSLSocket and client AP values // plus any other useful information to help determine appropriate // application protocol. Here the protocol and ciphersuite are also // passed to the callback function. return chooseApplicationProtocol( serverSocket, clientProtocols, handshakeSession.getProtocol(), handshakeSession.getCipherSuite()); }); sslSocket.startHandshake(); // After the handshake, get the application protocol that has been // returned from the callback method. String ap = sslSocket.getApplicationProtocol(); System.out.println("Application Protocol server side: \"" + ap + "\""); // Continue with the work of the server InputStream sslIS = sslSocket.getInputStream(); OutputStream sslOS = sslSocket.getOutputStream(); sslIS.read(); sslOS.write(85); sslOS.flush(); sslSocket.close(); } // The callback method. Note how the parameters match the call within // the setHandshakeApplicationProtocolSelector method above. public static String chooseApplicationProtocol(SSLSocket serverSocket, List<String> clientProtocols, String protocol, String cipherSuite ) { // For example, check the cipher suite and return an application protocol // value based on that. if (cipherSuite.equals("<--a_particular_ciphersuite-->")) { return "three"; } else { return ""; } } }
If the cipher suite matches the one you specify in the condition statement when this code is run , then the value
three
will be returned. Otherwise an empty string will be returned.
Note that the
BiFunction
object's return value is a
String
, which will be the application protocol name, or null to indicate that none of the advertised names are
acceptable. If the return value is an empty String
, then application protocol indications will not be used. 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, the server
code will send a "no_application_protocol" alert and terminate the connection.)
After handshaking completes on both client and server, you can check the result of the negotiation by calling the
getApplicationProtocol
method on either the
SSLSocket
object or the
SSLEngine
object.
To determine the ALPN value that has been negotiated during the handshaking, create a custom
KeyManager
or
TrustManager
class, and include in this custom class a call to the
getHandshakeApplicationProtocol
method.
There are some use cases where the selected ALPN and SNI values will affect the choices made by a
KeyManager
or
TrustManager
. For example, an application might want to select different certificate/private key sets depending on
the attributes of the server and the chosen ALPN/SNI/ciphersuite values.
The sample code given illustrates how to call the
getHandshakeApplicationProtocol
method from within a custom
X509ExtendedKeyManager
that you create and register as the
KeyManager
object.
This example shows the entire code for a custom
KeyManager
that extends
X509ExtendedKeyManager
. Most methods simply return the value returned from the
KeyManager
class that is being wrapped by this
MyX509ExtendedKeyManager
class. However the
chooseServerAlias
method calls the
getHandshakeApplicationProtocol
on the
SSLSocket
object and therefore can determine the current negotiated ALPN value.
import java.net.Socket; import java.security.*; import javax.net.ssl.*; public class MyX509ExtendedKeyManager extends X509ExtendedKeyManager { // X509ExtendedKeyManager is an abstract class so your new class // needs to implement all the abstract methods in this class. // The easiest way to do this is to wrap an existing KeyManager // and call its methods for each of the methods you need to implement. X509ExtendedKeyManager akm; public MyX509ExtendedKeyManager(X509ExtendedKeyManager akm) { this.akm = akm; } @Override public String[] getClientAliases(String keyType, Principal[] issuers) { return akm.getClientAliases(keyType, issuers); } @Override public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) { return akm.chooseClientAlias(keyType, issuers, socket); } @Override public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) { // This method has access to a Socket, so it is possible to call the // getHandshakeApplicationProtocol method here. Note the cast from // a Socket to an SSLSocket String ap = ((SSLSocket) socket).getHandshakeApplicationProtocol(); System.out.println("In chooseServerAlias, ap is: " + ap); return akm.chooseServerAlias(keyType, issuers, socket); } @Override public String[] getServerAliases(String keyType, Principal[] issuers) { return akm.getServerAliases(keyType, issuers); } @Override public X509Certificate[] getCertificateChain(String alias) { return akm.getCertificateChain(alias); } @Override public PrivateKey getPrivateKey(String alias) { return akm.getPrivateKey(alias); } }When this code is registered as the
KeyManager
for a Java server and a Java client sends a
ClientHello
with ALPN values, the output will be:
In chooseServerAlias, ap is: <negotiated value>
This example shows a simple Java server that uses the default ALPN negotiation strategy and the custom
KeyManager
,
MyX509ExtendedKeyManager
, shown in the prior code sample.
import java.io.*; import java.util.*; import javax.net.ssl.*; import java.security.KeyStore; public class SSLServerHandshake { public static void main(String[] args) throws Exception { SSLContext ctx = SSLContext.getInstance("TLS"); // You need to explicitly create a create a custom KeyManager // Keystores KeyStore keyKS = KeyStore.getInstance("PKCS12"); keyKS.load(new FileInputStream("serverCert.p12"), "password".toCharArray()); // Generate KeyManager KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX"); kmf.init(keyKS, "password".toCharArray()); KeyManager[] kms = kmf.getKeyManagers(); // Code to substitute MyX509ExtendedKeyManager if (!(kms[0] instanceof X509ExtendedKeyManager)) { throw new Exception("kms[0] not X509ExtendedKeyManager"); } // Create a new KeyManager array and set the first index // of the array to an instance of MyX509ExtendedKeyManager. // Notice how creating this object is done by passing in the // existing default X509ExtendedKeyManager kms = new KeyManager[] { new MyX509ExtendedKeyManager((X509ExtendedKeyManager) kms[0])}; // Initialize SSLContext using the new KeyManager ctx.init(kms, null, null); // Instead of using SSLServerSocketFactory.getDefault(), // get a SSLServerSocketFactory based on the SSLContext SSLServerSocketFactory sslssf = ctx.getServerSocketFactory(); SSLServerSocket sslServerSocket = (SSLServerSocket) sslssf.createServerSocket(9999); SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept(); SSLParameters sslp = sslSocket.getSSLParameters(); String[] serverAPs ={"one","two","three"}; sslp.setApplicationProtocols(serverAPs); sslSocket.setSSLParameters(sslp); sslSocket.startHandshake(); String ap = sslSocket.getApplicationProtocol(); System.out.println("Application Protocol server side: \"" + ap + "\""); InputStream sslIS = sslSocket.getInputStream(); OutputStream sslOS = sslSocket.getOutputStream(); sslIS.read(); sslOS.write(85); sslOS.flush(); sslSocket.close(); sslServerSocket.close(); } }
With the custom
X509ExtendedKeyManager
in place, when
chooseServerAlias
is called during handshaking the
KeyManager
has the opportunity to examine the negotiated application protocol value. In the case of the example
shown, this value is output to the console.
ClientHello
with ALPN values
three
and
two
, the output will be:
Application Protocol server side: two
ALPN transports data with byte arrays, which means that it expects text to be encoded with single byte character encodings such as US-ASCII. Java ALPN APIs use the String
class for text, but prior to Java SE 16/11.0.2/8u301, the SunJSSE provider converts String
instances to byte arrays with UTF-8. However, UTF-8 is a variable-width character encoding. It encodes characters above U+007F with more than one byte, which may not be expected by an ALPN peer.
In Java SE 16/11.0.2/8u301 and later, the SunJSSE provider encodes and decodes String characters as 8-bit ISO_8859_1/LATIN-1 characters.
ALPN values are now represented using the network byte representation expected by the peer, which should require no modification for standard 7-bit ASCII-based String
instances.
The methods in the javax.net.ssl.SSLSocket
and javax.net.ssl.SSLEngine
return ApplicationProtocol
String
values in the network byte representation sent by the peer.
However, if you have Unicode data with characters that are above U+007F, then your application must correctly encode or decode them to byte arrays before sending or receiving them instead of relying on the SunJSSE provider to automatically encode or decode Unicode characters. Alternatively, you can set the security property jdk.tls.alpnCharset
to UTF-8
to revert to the previous behavior.
To compare ALPN values with their expected values, you can convert them to byte arrays and then compare them.
The expected ALPN values in the following example are the string http/1.1
and the UTF-8 encoded string (in hexadecimal) 0xABCD0xABCE0xABCF
(which are the Meetei Mayek letters "HUK UN I"). The example converts the ALPN value to a byte array with ISO-8859-1, converts http/1.1
to a byte array with UTF-8, and manually specifies the byte array representation of 0xABCD0xABCE0xABCF
.
// Get the ALPN value negotiated by the TLS handshake currently // in progress String networkString = sslEngine.getHandshakeApplicationProtocol(); // Encode the ALPN value into a byte array with the ISO-8859-1 // character encoding byte[] bytes = networkString.getBytes(StandardCharsets.ISO_8859_1); String HTTP1_1 = "http/1.1"; // Encode the String "http/1.1" into a byte array with the // UTF-8 character set byte[] HTTP1_1_BYTES = HTTP1_1.getBytes(StandardCharsets.UTF_8); // Create a byte array representing the Unicode characters 0xABCD, // 0xABCE, and 0xABCF, which are the Meetei Mayek letters "HUK UN I" byte[] HUK_UN_I_BYTES = new byte[] { (byte) 0xab, (byte) 0xcd, (byte) 0xab, (byte) 0xce, (byte) 0xab, (byte) 0xcf}; // Test whether the APLN value is equal to "http/1.1" or // 0xABCD0xABCE0xABCF if ((Arrays.compare(bytes, HTTP1_1_BYTES) == 0 ) || Arrays.compare(bytes, HUK_UN_I_BYTES) == 0) { // ... }
Alternatively, you can compare ALPN values with the method String.equals()
if you know that the ALPN value was encoded from a String
using a certain character set, for example UTF-8
. You must decode the ALPN value to a Unicode String
before comparing it.
String unicodeString = new String(bytes, StandardCharsets.UTF_8); if (unicodeString.equals(HTTP1_1) || unicodeString.equals("\uabcd\uabce\uabcf")) { // ... }
For the method javax.net.ssl.SSLParameters.setApplicationProtocols(String[] protocols)
, you must convert its String arguments to the network byte representation expected by the peer. For example, if the peer expects ALPN values in UTF-8, you must convert it to a byte array with UTF-8 and then store it as a byte-oriented String:
// Convert Meetei Mayek letters "HUK UN I" (in hexadecimal, 0xABCD0xABCE0xABCF) // to a byte array with UTF-8 byte[] bytes = "\uabcd\uabce\uabcf".getBytes(StandardCharsets.UTF_8); // Create a byte-oriented String with ISO-8859-1 String HUK_UN_I = new String(bytes, StandardCharsets.ISO_8859_1); // GREASE value {0x8A, 0x8A} String rfc7301Grease8A = "\u008A\u008A"; SSLParameters p = sslSocket.getSSLParameters(); p.setApplicationProtocols(new String[] {"h2", "http/1.1", rfc7301Grease8A, HUK_UN_I}); sslSocket.setSSLParameters(p);
At the beginning of the TLS handshake, the client sends a list of ALPN values to the server, and the server selects which values it can use and ignores those that it doesn't recognize. However, a flawed TLS implementation might instead reject unrecognized ALPN values, which may prevent the handshake from proceeding, but developers or administrators may not notice this flaw because it will still enable clients and servers whose ALPN values it recognizes to connect.
Consequently, the TLS specification has introduced Generate Random Extensions And Sustain Extensibility (GREASE) values: a reserved set of TLS protocol values that a TLS implementation may randomly advertise to ensure that peers correctly handle unrecognized values.
In the previous example, one of the values passed to the method setApplicationProtocols
, rfc7301Grease8A
, is a GREASE value. The peer should ignore it instead of reject it.
These classes and methods are used when working with Application Layer Protocol Negotiation (ALPN).
SSLEngine
and
SSLSocket
contain the same ALPN related methods and they have the same functionality.
Class | Method | Purpose |
---|---|---|
SSLParameters
|
public String[] getApplicationProtocols();
|
Client-side and server-side: use the method to return a
String array containing each protocol set. |
SSLParameters
|
public void setApplicationProtocols([] protocols);
|
Client-side: use the method to set the protocols that can be chosen by the server. Server-side: use the method to set the protocols that the server can use. The String array should contain the protocols in order of preference. |
SSLEngine
SSLSocket
|
public String getApplicationProtocol();
|
Client-side and server-side: use the method
after TLS protocol negotiation has completed to return a
String containing the protocol that has been chosen for the connection. |
SSLEngine
SSLSocket
|
public String getHandshakeApplicationProtocol();
|
Client-side and server-side: use the method
during handshaking to return a
String containing the protocol that has been chosen for the connection. If this method is called before or
after handshaking, it will return null. See
Determining Negotiated ALPN Value during Handshaking for instructions on how to call this method. |
SSLEngine
SSLSocket
|
public void setHandshakeApplicationProtocolSelector(BiFunction,String> selector)
|
Server-side: use the method to register a callback function. The application protocol value can then be set in the callback based on any information available, for example the protocol or cipher suite. See Setting up Custom ALPN on the Server for instructions on how to use this method. |