-
Notifications
You must be signed in to change notification settings - Fork 3.9k
okhttp: support JDK9 ALPN #4136
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,34 +29,46 @@ | |
import java.net.InetSocketAddress; | ||
import java.net.Socket; | ||
import java.net.SocketException; | ||
import java.security.AccessController; | ||
import java.security.KeyManagementException; | ||
import java.security.NoSuchAlgorithmException; | ||
import java.security.PrivilegedActionException; | ||
import java.security.PrivilegedExceptionAction; | ||
import java.security.Provider; | ||
import java.security.Security; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.logging.Level; | ||
import java.util.logging.Logger; | ||
import javax.net.ssl.SSLContext; | ||
import javax.net.ssl.SSLEngine; | ||
import javax.net.ssl.SSLParameters; | ||
import javax.net.ssl.SSLSocket; | ||
import okio.Buffer; | ||
|
||
/** | ||
* Access to platform-specific features. | ||
* | ||
* <h3>Server name indication (SNI)</h3> | ||
* | ||
* Supported on Android 2.3+. | ||
* | ||
* <h3>Session Tickets</h3> | ||
* | ||
* Supported on Android 2.3+. | ||
* | ||
* <h3>Android Traffic Stats (Socket Tagging)</h3> | ||
* | ||
* Supported on Android 4.0+. | ||
* | ||
* <h3>ALPN (Application Layer Protocol Negotiation)</h3> | ||
* | ||
* Supported on Android 5.0+. The APIs were present in Android 4.4, but that implementation was | ||
* unstable. | ||
* | ||
* Supported on OpenJDK 7 and 8 (via the JettyALPN-boot library). | ||
* <p>Supported on OpenJDK 9+. | ||
* | ||
* <p>Supported on OpenJDK 7 and 8 (via the JettyALPN-boot library). | ||
*/ | ||
public class Platform { | ||
public static final Logger logger = Logger.getLogger(Platform.class.getName()); | ||
|
@@ -199,6 +211,47 @@ private static Platform findPlatform() { | |
throw new RuntimeException(nsae); | ||
} | ||
|
||
// Find JDK9+ ALPN support | ||
try { | ||
// getApplicationProtocol() may throw UnsupportedOperationException, so first construct a | ||
// dummy SSLEngine and verify the method does not throw. | ||
SSLContext context = SSLContext.getInstance("TLS", sslProvider); | ||
context.init(null, null, null); | ||
SSLEngine engine = context.createSSLEngine(); | ||
Method getEngineApplicationProtocol = | ||
AccessController.doPrivileged( | ||
new PrivilegedExceptionAction<Method>() { | ||
@Override | ||
public Method run() throws Exception { | ||
return SSLEngine.class.getMethod("getApplicationProtocol"); | ||
} | ||
}); | ||
getEngineApplicationProtocol.invoke(engine); | ||
|
||
Method setApplicationProtocols = | ||
AccessController.doPrivileged( | ||
new PrivilegedExceptionAction<Method>() { | ||
@Override | ||
public Method run() throws Exception { | ||
return SSLParameters.class.getMethod("setApplicationProtocols", String[].class); | ||
} | ||
}); | ||
Method getApplicationProtocol = | ||
AccessController.doPrivileged( | ||
new PrivilegedExceptionAction<Method>() { | ||
@Override | ||
public Method run() throws Exception { | ||
return SSLSocket.class.getMethod("getApplicationProtocol"); | ||
} | ||
}); | ||
return new JdkAlpnPlatform(sslProvider, setApplicationProtocols, getApplicationProtocol); | ||
} catch (NoSuchAlgorithmException ignored) { | ||
} catch (KeyManagementException ignored) { | ||
} catch (PrivilegedActionException ignored) { | ||
} catch (IllegalAccessException ignored) { | ||
} catch (InvocationTargetException ignored) { | ||
} | ||
|
||
// Find Jetty's ALPN extension for OpenJDK. | ||
try { | ||
String negoClassName = "org.eclipse.jetty.alpn.ALPN"; | ||
|
@@ -375,6 +428,56 @@ public TlsExtensionType getTlsExtensionType() { | |
} | ||
} | ||
|
||
/** OpenJDK 9+. */ | ||
private static class JdkAlpnPlatform extends Platform { | ||
private final Method setApplicationProtocols; | ||
private final Method getApplicationProtocol; | ||
|
||
private JdkAlpnPlatform( | ||
Provider provider, Method setApplicationProtocols, Method getApplicationProtocol) { | ||
super(provider); | ||
this.setApplicationProtocols = setApplicationProtocols; | ||
this.getApplicationProtocol = getApplicationProtocol; | ||
} | ||
|
||
@Override | ||
public TlsExtensionType getTlsExtensionType() { | ||
return TlsExtensionType.ALPN_AND_NPN; | ||
} | ||
|
||
@Override | ||
public void configureTlsExtensions( | ||
SSLSocket sslSocket, String hostname, List<Protocol> protocols) { | ||
SSLParameters parameters = sslSocket.getSSLParameters(); | ||
List<String> names = new ArrayList<String>(protocols.size()); | ||
for (Protocol protocol : protocols) { | ||
if (protocol == Protocol.HTTP_1_0) continue; // No HTTP/1.0 for ALPN. | ||
names.add(protocol.toString()); | ||
} | ||
try { | ||
setApplicationProtocols.invoke( | ||
parameters, new Object[] {names.toArray(new String[names.size()])}); | ||
} catch (IllegalAccessException e) { | ||
throw new RuntimeException(e); | ||
} catch (InvocationTargetException e) { | ||
throw new RuntimeException(e); | ||
} | ||
sslSocket.setSSLParameters(parameters); | ||
} | ||
|
||
/** Returns the negotiated protocol, or null if no protocol was negotiated. */ | ||
@Override | ||
public String getSelectedProtocol(SSLSocket socket) { | ||
try { | ||
return (String) getApplicationProtocol.invoke(socket); | ||
} catch (IllegalAccessException e) { | ||
throw new RuntimeException(e); | ||
} catch (InvocationTargetException e) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is known that getApplicationProtocol can throw For one, we can do (Actually, if we verify the provider supports this method ahead-of-time, then I'm fine with this current code.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now verify at startup that an SSLEngine created using the provider does not throw when |
||
throw new RuntimeException(e); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* OpenJDK 7+ with {@code org.mortbay.jetty.alpn/alpn-boot} in the boot class path. | ||
*/ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should verify that
sslProvider
supports ALPN by callinggetApplicationProtocol()
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. AFAICT this requires instantiating an SSLEngine to invoke
getApplicationProtocol()
, which is the approach now taken.