All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.grpc.okhttp.internal.Platform Maven / Gradle / Ivy

There is a newer version: 1.66.0
Show newest version
/*
 * Copyright (C) 2012 Square, Inc.
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * Forked from OkHttp 2.5.0
 */

package io.grpc.okhttp.internal;

import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
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.
 *
 * 

Server name indication (SNI)

* * Supported on Android 2.3+. * *

Session Tickets

* * Supported on Android 2.3+. * *

Android Traffic Stats (Socket Tagging)

* * Supported on Android 4.0+. * *

ALPN (Application Layer Protocol Negotiation)

* * Supported on Android 5.0+. The APIs were present in Android 4.4, but that implementation was * unstable. * *

Supported on OpenJDK 9+. * *

Supported on OpenJDK 7 and 8 (via the JettyALPN-boot library). */ public class Platform { public static final Logger logger = Logger.getLogger(Platform.class.getName()); public enum TlsExtensionType { ALPN_AND_NPN, NPN, NONE, } /** * List of recognized security providers. The first recognized security provider according to the * preference order returned by {@link Security#getProviders} will be selected. */ private static final String[] ANDROID_SECURITY_PROVIDERS = new String[] { // See https://developer.android.com/training/articles/security-gms-provider.html "com.google.android.gms.org.conscrypt.OpenSSLProvider", "org.conscrypt.OpenSSLProvider", "com.android.org.conscrypt.OpenSSLProvider", "org.apache.harmony.xnet.provider.jsse.OpenSSLProvider", "com.google.android.libraries.stitch.sslguard.SslGuardProvider" }; private static final Platform PLATFORM = findPlatform(); public static Platform get() { return PLATFORM; } private final Provider sslProvider; public Platform(Provider sslProvider) { this.sslProvider = sslProvider; } /** Prefix used on custom headers. */ public String getPrefix() { return "OkHttp"; } public void logW(String warning) { System.out.println(warning); } public void tagSocket(Socket socket) throws SocketException { } public void untagSocket(Socket socket) throws SocketException { } public Provider getProvider() { return sslProvider; } /** Returns the TLS extension type available (ALPN and NPN, NPN, or None). */ public TlsExtensionType getTlsExtensionType() { return TlsExtensionType.NONE; } /** * Configure TLS extensions on {@code sslSocket} for {@code route}. * * @param hostname non-null for client-side handshakes; null for * server-side handshakes. */ public void configureTlsExtensions(SSLSocket sslSocket, String hostname, List protocols) { } /** * Called after the TLS handshake to release resources allocated by {@link * #configureTlsExtensions}. */ public void afterHandshake(SSLSocket sslSocket) { } /** Returns the negotiated protocol, or null if no protocol was negotiated. */ public String getSelectedProtocol(SSLSocket socket) { return null; } public void connectSocket(Socket socket, InetSocketAddress address, int connectTimeout) throws IOException { socket.connect(address, connectTimeout); } /** Attempt to match the host runtime to a capable Platform implementation. */ private static Platform findPlatform() { Provider androidOrAppEngineProvider = getAndroidSecurityProvider(); if (androidOrAppEngineProvider != null) { // Attempt to find Android 2.3+ APIs. OptionalMethod setUseSessionTickets = new OptionalMethod<>(null, "setUseSessionTickets", boolean.class); OptionalMethod setHostname = new OptionalMethod<>(null, "setHostname", String.class); Method trafficStatsTagSocket = null; Method trafficStatsUntagSocket = null; OptionalMethod getAlpnSelectedProtocol = new OptionalMethod<>(byte[].class, "getAlpnSelectedProtocol"); OptionalMethod setAlpnProtocols = new OptionalMethod<>(null, "setAlpnProtocols", byte[].class); // Attempt to find Android 4.0+ APIs. try { Class trafficStats = Class.forName("android.net.TrafficStats"); trafficStatsTagSocket = trafficStats.getMethod("tagSocket", Socket.class); trafficStatsUntagSocket = trafficStats.getMethod("untagSocket", Socket.class); } catch (ClassNotFoundException ignored) { // On older Android } catch (NoSuchMethodException ignored) { // On older Android } TlsExtensionType tlsExtensionType; if (androidOrAppEngineProvider.getName().equals("GmsCore_OpenSSL") || androidOrAppEngineProvider.getName().equals("Conscrypt") || androidOrAppEngineProvider.getName().equals("Ssl_Guard")) { tlsExtensionType = TlsExtensionType.ALPN_AND_NPN; } else if (isAtLeastAndroid5()) { tlsExtensionType = TlsExtensionType.ALPN_AND_NPN; } else if (isAtLeastAndroid41()) { tlsExtensionType = TlsExtensionType.NPN; } else { tlsExtensionType = TlsExtensionType.NONE; } return new Android( setUseSessionTickets, setHostname, trafficStatsTagSocket, trafficStatsUntagSocket, getAlpnSelectedProtocol, setAlpnProtocols, androidOrAppEngineProvider, tlsExtensionType); } Provider sslProvider; try { sslProvider = SSLContext.getDefault().getProvider(); } catch (NoSuchAlgorithmException nsae) { 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 = SSLEngine.class.getMethod("getApplicationProtocol"); getEngineApplicationProtocol.invoke(engine); Method setApplicationProtocols = SSLParameters.class.getMethod("setApplicationProtocols", String[].class); Method getApplicationProtocol = SSLSocket.class.getMethod("getApplicationProtocol"); return new JdkAlpnPlatform(sslProvider, setApplicationProtocols, getApplicationProtocol); } catch (NoSuchAlgorithmException ignored) { // On older Java } catch (KeyManagementException ignored) { // On older Java } catch (IllegalAccessException ignored) { // On older Java } catch (NoSuchMethodException ignored) { // On older Java } catch (InvocationTargetException ignored) { // On older Java } // Find Jetty's ALPN extension for OpenJDK. try { String negoClassName = "org.eclipse.jetty.alpn.ALPN"; Class negoClass = Class.forName(negoClassName); Class providerClass = Class.forName(negoClassName + "$Provider"); Class clientProviderClass = Class.forName(negoClassName + "$ClientProvider"); Class serverProviderClass = Class.forName(negoClassName + "$ServerProvider"); Method putMethod = negoClass.getMethod("put", SSLSocket.class, providerClass); Method getMethod = negoClass.getMethod("get", SSLSocket.class); Method removeMethod = negoClass.getMethod("remove", SSLSocket.class); return new JdkWithJettyBootPlatform( putMethod, getMethod, removeMethod, clientProviderClass, serverProviderClass, sslProvider); } catch (ClassNotFoundException ignored) { // No Jetty ALPN } catch (NoSuchMethodException ignored) { // Weird Jetty ALPN } // TODO(ericgribkoff) Return null here return new Platform(sslProvider); } private static boolean isAtLeastAndroid5() { try { Platform.class .getClassLoader() .loadClass("android.net.Network"); // Arbitrary class added in Android 5.0. return true; } catch (ClassNotFoundException e) { logger.log(Level.FINE, "Can't find class", e); } return false; } private static boolean isAtLeastAndroid41() { try { Platform.class .getClassLoader() .loadClass("android.app.ActivityOptions"); // Arbitrary class added in Android 4.1. return true; } catch (ClassNotFoundException e) { logger.log(Level.FINE, "Can't find class", e); } return false; } /** * Select the first recognized security provider according to the preference order returned by * {@link Security#getProviders}. If a recognized provider is not found then warn but continue. */ private static Provider getAndroidSecurityProvider() { Provider[] providers = Security.getProviders(); for (Provider availableProvider : providers) { for (String providerClassName : ANDROID_SECURITY_PROVIDERS) { if (providerClassName.equals(availableProvider.getClass().getName())) { logger.log(Level.FINE, "Found registered provider {0}", providerClassName); return availableProvider; } } } logger.log(Level.WARNING, "Unable to find Conscrypt"); return null; } /** Android 2.3 or better, or AppEngine with Conscrypt. */ private static class Android extends Platform { private final OptionalMethod setUseSessionTickets; private final OptionalMethod setHostname; // Non-null on Android 4.0+. private final Method trafficStatsTagSocket; private final Method trafficStatsUntagSocket; // Non-null on Android 5.0+. private final OptionalMethod getAlpnSelectedProtocol; private final OptionalMethod setAlpnProtocols; private final TlsExtensionType tlsExtensionType; public Android( OptionalMethod setUseSessionTickets, OptionalMethod setHostname, Method trafficStatsTagSocket, Method trafficStatsUntagSocket, OptionalMethod getAlpnSelectedProtocol, OptionalMethod setAlpnProtocols, Provider provider, TlsExtensionType tlsExtensionType) { super(provider); this.setUseSessionTickets = setUseSessionTickets; this.setHostname = setHostname; this.trafficStatsTagSocket = trafficStatsTagSocket; this.trafficStatsUntagSocket = trafficStatsUntagSocket; this.getAlpnSelectedProtocol = getAlpnSelectedProtocol; this.setAlpnProtocols = setAlpnProtocols; this.tlsExtensionType = tlsExtensionType; } @Override public TlsExtensionType getTlsExtensionType() { return tlsExtensionType; } @Override public void connectSocket(Socket socket, InetSocketAddress address, int connectTimeout) throws IOException { try { socket.connect(address, connectTimeout); } catch (SecurityException se) { // Before android 4.3, socket.connect could throw a SecurityException // if opening a socket resulted in an EACCES error. IOException ioException = new IOException("Exception in connect"); ioException.initCause(se); throw ioException; } } @Override public void configureTlsExtensions( SSLSocket sslSocket, String hostname, List protocols) { // Enable SNI and session tickets. if (hostname != null) { setUseSessionTickets.invokeOptionalWithoutCheckedException(sslSocket, true); setHostname.invokeOptionalWithoutCheckedException(sslSocket, hostname); } // Enable ALPN. if (setAlpnProtocols.isSupported(sslSocket)) { Object[] parameters = { concatLengthPrefixed(protocols) }; setAlpnProtocols.invokeWithoutCheckedException(sslSocket, parameters); } } @Override public String getSelectedProtocol(SSLSocket socket) { if (!getAlpnSelectedProtocol.isSupported(socket)) return null; byte[] alpnResult = (byte[]) getAlpnSelectedProtocol.invokeWithoutCheckedException(socket); return alpnResult != null ? new String(alpnResult, Util.UTF_8) : null; } @Override public void tagSocket(Socket socket) throws SocketException { if (trafficStatsTagSocket == null) return; try { trafficStatsTagSocket.invoke(null, socket); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e.getCause()); } } @Override public void untagSocket(Socket socket) throws SocketException { if (trafficStatsUntagSocket == null) return; try { trafficStatsUntagSocket.invoke(null, socket); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e.getCause()); } } } /** 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 protocols) { SSLParameters parameters = sslSocket.getSSLParameters(); List names = new ArrayList<>(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) { throw new RuntimeException(e); } } } /** * OpenJDK 7+ with {@code org.mortbay.jetty.alpn/alpn-boot} in the boot class path. */ private static class JdkWithJettyBootPlatform extends Platform { private final Method putMethod; private final Method getMethod; private final Method removeMethod; private final Class clientProviderClass; private final Class serverProviderClass; public JdkWithJettyBootPlatform(Method putMethod, Method getMethod, Method removeMethod, Class clientProviderClass, Class serverProviderClass, Provider provider) { super(provider); this.putMethod = putMethod; this.getMethod = getMethod; this.removeMethod = removeMethod; this.clientProviderClass = clientProviderClass; this.serverProviderClass = serverProviderClass; } @Override public TlsExtensionType getTlsExtensionType() { return TlsExtensionType.ALPN_AND_NPN; } @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()); } try { Object provider = Proxy.newProxyInstance(Platform.class.getClassLoader(), new Class[] { clientProviderClass, serverProviderClass }, new JettyNegoProvider(names)); putMethod.invoke(null, sslSocket, provider); } catch (InvocationTargetException e) { throw new AssertionError(e); } catch (IllegalAccessException e) { throw new AssertionError(e); } } @Override public void afterHandshake(SSLSocket sslSocket) { try { removeMethod.invoke(null, sslSocket); } catch (IllegalAccessException ignored) { throw new AssertionError(); } catch (InvocationTargetException ex) { // This would be very surprising and there's not much to do about it logger.log(Level.FINE, "Failed to remove SSLSocket from Jetty ALPN", ex); } } @Override public String getSelectedProtocol(SSLSocket socket) { try { JettyNegoProvider provider = (JettyNegoProvider) Proxy.getInvocationHandler(getMethod.invoke(null, socket)); if (!provider.unsupported && provider.selected == null) { logger.log(Level.INFO, "ALPN callback dropped: SPDY and HTTP/2 are disabled. " + "Is alpn-boot on the boot class path?"); return null; } return provider.unsupported ? null : provider.selected; } catch (InvocationTargetException e) { throw new AssertionError(); } catch (IllegalAccessException e) { throw new AssertionError(); } } } /** * Handle the methods of ALPN's ClientProvider and ServerProvider * without a compile-time dependency on those interfaces. */ private static class JettyNegoProvider implements InvocationHandler { /** This peer's supported protocols. */ private final List protocols; /** Set when remote peer notifies ALPN is unsupported. */ private boolean unsupported; /** The protocol the server selected. */ private String selected; public JettyNegoProvider(List protocols) { this.protocols = protocols; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); Class returnType = method.getReturnType(); if (args == null) { args = Util.EMPTY_STRING_ARRAY; } if (methodName.equals("supports") && boolean.class == returnType) { return true; // ALPN is supported. } else if (methodName.equals("unsupported") && void.class == returnType) { this.unsupported = true; // Peer doesn't support ALPN. return null; } else if (methodName.equals("protocols") && args.length == 0) { return protocols; // Client advertises these protocols. } else if ((methodName.equals("selectProtocol") || methodName.equals("select")) && String.class == returnType && args.length == 1 && args[0] instanceof List) { @SuppressWarnings("unchecked") List peerProtocols = (List) args[0]; // Pick the first known protocol the peer advertises. for (int i = 0, size = peerProtocols.size(); i < size; i++) { if (protocols.contains(peerProtocols.get(i))) { return selected = peerProtocols.get(i); } } return selected = protocols.get(0); // On no intersection, try peer's first protocol. } else if ((methodName.equals("protocolSelected") || methodName.equals("selected")) && args.length == 1) { this.selected = (String) args[0]; // Server selected this protocol. return null; } else { return method.invoke(this, args); } } } /** * Returns the concatenation of 8-bit, length prefixed protocol names. * http://tools.ietf.org/html/draft-agl-tls-nextprotoneg-04#page-4 */ public static byte[] concatLengthPrefixed(List protocols) { Buffer result = new Buffer(); 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. result.writeByte(protocol.toString().length()); result.writeUtf8(protocol.toString()); } return result.readByteArray(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy