diff --git a/okhttp/src/main/java/okhttp3/internal/Platform.java b/okhttp/src/main/java/okhttp3/internal/Platform.java index 2125c991d440..f0a97c23aa4d 100644 --- a/okhttp/src/main/java/okhttp3/internal/Platform.java +++ b/okhttp/src/main/java/okhttp3/internal/Platform.java @@ -28,6 +28,7 @@ import java.util.ArrayList; import java.util.List; import java.util.logging.Level; +import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.X509TrustManager; @@ -58,6 +59,8 @@ * * Supported on OpenJDK 7 and 8 (via the JettyALPN-boot library). * + * Supported on OpenJDK 9 via SSLParameters and SSLSocket features. + * *

Trust Manager Extraction

* *

Supported on Android 2.3+ and OpenJDK 7+. There are no public APIs to recover the trust @@ -123,6 +126,16 @@ public void log(String message) { System.out.println(message); } + public static List alpnProtocolNames(List protocols) { + List names = new ArrayList<>(protocols.size()); + for (int i = 0, size = protocols.size(); i < size; i++) { + Protocol protocol = protocols.get(i); + if (protocol == Protocol.HTTP_1_0) continue; // No HTTP/1.0 for ALPN. + names.add(protocol.toString()); + } + return names; + } + /** Attempt to match the host runtime to a capable Platform implementation. */ private static Platform findPlatform() { // Attempt to find Android 2.3+ APIs. @@ -157,6 +170,18 @@ private static Platform findPlatform() { // This isn't an Android runtime. } + + // Find JDK 9 new methods + try { + Method setProtocolMethod = + SSLParameters.class.getMethod("setApplicationProtocols", String[].class); + Method getProtocolMethod = SSLSocket.class.getMethod("getApplicationProtocol"); + + return new Jdk9Platform(setProtocolMethod, getProtocolMethod); + } catch (NoSuchMethodException ignored) { + // pre JDK 9 + } + // Find Jetty's ALPN extension for OpenJDK. try { String negoClassName = "org.eclipse.jetty.alpn.ALPN"; @@ -273,6 +298,54 @@ public Android(Class sslParametersClass, OptionalMethod setUseSession } } + /** + * OpenJDK 9+. + */ + private static final class Jdk9Platform extends Platform { + private final Method setProtocolMethod; + private final Method getProtocolMethod; + + public Jdk9Platform(Method setProtocolMethod, Method getProtocolMethod) { + this.setProtocolMethod = setProtocolMethod; + this.getProtocolMethod = getProtocolMethod; + } + + @Override + public void configureTlsExtensions(SSLSocket sslSocket, String hostname, + List protocols) { + try { + SSLParameters sslParameters = sslSocket.getSSLParameters(); + + List names = alpnProtocolNames(protocols); + + setProtocolMethod.invoke(sslParameters, + new Object[]{names.toArray(new String[names.size()])}); + + sslSocket.setSSLParameters(sslParameters); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new AssertionError(); + } + } + + @Override + public String getSelectedProtocol(SSLSocket socket) { + try { + String protocol = (String) getProtocolMethod.invoke(socket); + + // SSLSocket.getApplicationProtocol returns "" if application protocols values will not + // be used. Observed if you didn't specify SSLParameters.setApplicationProtocols + if (protocol == null || protocol.equals("")) { + return null; + } + + return protocol; + } catch (IllegalAccessException | InvocationTargetException e) { + throw new AssertionError(); + } + } + } + + /** * OpenJDK 7+ with {@code org.mortbay.jetty.alpn/alpn-boot} in the boot class path. */ @@ -294,12 +367,8 @@ public JdkWithJettyBootPlatform(Method putMethod, Method getMethod, Method remov @Override public void configureTlsExtensions( SSLSocket sslSocket, String hostname, List protocols) { - List names = new ArrayList<>(protocols.size()); - for (int i = 0, size = protocols.size(); i < size; i++) { - Protocol protocol = protocols.get(i); - if (protocol == Protocol.HTTP_1_0) continue; // No HTTP/1.0 for ALPN. - names.add(protocol.toString()); - } + List names = alpnProtocolNames(protocols); + try { Object provider = Proxy.newProxyInstance(Platform.class.getClassLoader(), new Class[] {clientProviderClass, serverProviderClass}, new JettyNegoProvider(names));