diff --git a/communication/src/main/java/okhttp3/internal/platform/PatchPlatform.java b/communication/src/main/java/okhttp3/internal/platform/PatchPlatform.java new file mode 100644 index 00000000000..dc9f9c2d4b6 --- /dev/null +++ b/communication/src/main/java/okhttp3/internal/platform/PatchPlatform.java @@ -0,0 +1,332 @@ +/* + * 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. + */ +package okhttp3.internal.platform; + +import de.thetaphi.forbiddenapis.SuppressForbidden; +import java.io.IOException; +import java.lang.reflect.Field; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.Nullable; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.X509TrustManager; +import okhttp3.OkHttpClient; +import okhttp3.Protocol; +import okhttp3.internal.tls.BasicCertificateChainCleaner; +import okhttp3.internal.tls.BasicTrustRootIndex; +import okhttp3.internal.tls.CertificateChainCleaner; +import okhttp3.internal.tls.TrustRootIndex; +import okio.Buffer; + +/** + * Replacement Platform class that avoids eager call to Security.getProviders() + * ---------------------------------------------------------------------------- + * + *

Access to platform-specific features. + * + *

Server name indication (SNI)

+ * + *

Supported on Android 2.3+. + * + *

Supported on OpenJDK 7+ + * + *

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 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 + * manager that was used to create an {@link SSLSocketFactory}. + * + *

Android Cleartext Permit Detection

+ * + *

Supported on Android 6.0+ via {@code NetworkSecurityPolicy}. + */ +public class PatchPlatform { + private static final Platform PLATFORM = findPlatform(); + public static final int INFO = 4; + public static final int WARN = 5; + private static final Logger logger = Logger.getLogger(OkHttpClient.class.getName()); + + public static Platform get() { + return PLATFORM; + } + + /** Prefix used on custom headers. */ + public String getPrefix() { + return "OkHttp"; + } + + @SuppressForbidden // allow this use of Class.forName() + protected @Nullable X509TrustManager trustManager(SSLSocketFactory sslSocketFactory) { + // Attempt to get the trust manager from an OpenJDK socket factory. We attempt this on all + // platforms in order to support Robolectric, which mixes classes from both Android and the + // Oracle JDK. Note that we don't support HTTP/2 or other nice features on Robolectric. + try { + Class sslContextClass = Class.forName("sun.security.ssl.SSLContextImpl"); + Object context = readFieldOrNull(sslSocketFactory, sslContextClass, "context"); + if (context == null) return null; + return readFieldOrNull(context, X509TrustManager.class, "trustManager"); + } catch (ClassNotFoundException e) { + return null; + } + } + + /** + * 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, @Nullable String hostname, List protocols) + throws IOException {} + + /** + * 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 @Nullable String getSelectedProtocol(SSLSocket socket) { + return null; + } + + public void connectSocket(Socket socket, InetSocketAddress address, int connectTimeout) + throws IOException { + socket.connect(address, connectTimeout); + } + + public void log(int level, String message, @Nullable Throwable t) { + Level logLevel = level == WARN ? Level.WARNING : Level.INFO; + logger.log(logLevel, message, t); + } + + public boolean isCleartextTrafficPermitted(String hostname) { + return true; + } + + /** + * Returns an object that holds a stack trace created at the moment this method is executed. This + * should be used specifically for {@link java.io.Closeable} objects and in conjunction with + * {@link #logCloseableLeak(String, Object)}. + */ + public Object getStackTraceForCloseable(String closer) { + if (logger.isLoggable(Level.FINE)) { + return new Throwable(closer); // These are expensive to allocate. + } + return null; + } + + public void logCloseableLeak(String message, Object stackTrace) { + if (stackTrace == null) { + message += + " To see where this was allocated, set the OkHttpClient logger level to FINE: " + + "Logger.getLogger(OkHttpClient.class.getName()).setLevel(Level.FINE);"; + } + log(WARN, message, (Throwable) stackTrace); + } + + 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; + } + + public CertificateChainCleaner buildCertificateChainCleaner(X509TrustManager trustManager) { + return new BasicCertificateChainCleaner(buildTrustRootIndex(trustManager)); + } + + public CertificateChainCleaner buildCertificateChainCleaner(SSLSocketFactory sslSocketFactory) { + X509TrustManager trustManager = trustManager(sslSocketFactory); + + if (trustManager == null) { + throw new IllegalStateException( + "Unable to extract the trust manager on " + + Platform.get() + + ", sslSocketFactory is " + + sslSocketFactory.getClass()); + } + + return buildCertificateChainCleaner(trustManager); + } + + /* Avoid eager call to Security.getProviders() + ---------------------------------------------- + | public static boolean isConscryptPreferred() { + | // mainly to allow tests to run cleanly + | if ("conscrypt".equals(System.getProperty("okhttp.platform"))) { + | return true; + | } + | + | // check if Provider manually installed + | String preferredProvider = Security.getProviders()[0].getName(); + | return "Conscrypt".equals(preferredProvider); + | } + ---------------------------------------------- */ + + /** Attempt to match the host runtime to a capable Platform implementation. */ + private static Platform findPlatform() { + if (isAndroid()) { + return findAndroidPlatform(); + } else { + return findJvmPlatform(); + } + } + + public static boolean isAndroid() { + // This explicit check avoids activating in Android Studio with Android specific classes + // available when running plugins inside the IDE. + return "Dalvik".equals(System.getProperty("java.vm.name")); + } + + private static Platform findJvmPlatform() { + /* Avoid eager call to Security.getProviders() + ---------------------------------------------- + | if (isConscryptPreferred()) { + | Platform conscrypt = ConscryptPlatform.buildIfSupported(); + | + | if (conscrypt != null) { + | return conscrypt; + | } + | } + ---------------------------------------------- */ + + Platform jdk9 = Jdk9Platform.buildIfSupported(); + + if (jdk9 != null) { + return jdk9; + } + + Platform jdkWithJettyBoot = JdkWithJettyBootPlatform.buildIfSupported(); + + if (jdkWithJettyBoot != null) { + return jdkWithJettyBoot; + } + + // Probably an Oracle JDK like OpenJDK. + return new Platform(); + } + + private static Platform findAndroidPlatform() { + Platform android10 = Android10Platform.buildIfSupported(); + + if (android10 != null) { + return android10; + } + + Platform android = AndroidPlatform.buildIfSupported(); + + if (android == null) { + throw new NullPointerException("No platform found on Android"); + } + + return android; + } + + /** + * Returns the concatenation of 8-bit, length prefixed protocol names. + * http://tools.ietf.org/html/draft-agl-tls-nextprotoneg-04#page-4 + */ + 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(); + } + + static @Nullable T readFieldOrNull(Object instance, Class fieldType, String fieldName) { + for (Class c = instance.getClass(); c != Object.class; c = c.getSuperclass()) { + try { + Field field = c.getDeclaredField(fieldName); + field.setAccessible(true); + Object value = field.get(instance); + if (value == null || !fieldType.isInstance(value)) return null; + return fieldType.cast(value); + } catch (NoSuchFieldException ignored) { + } catch (IllegalAccessException e) { + throw new AssertionError(); + } + } + + // Didn't find the field we wanted. As a last gasp attempt, try to find the value on a delegate. + if (!fieldName.equals("delegate")) { + Object delegate = readFieldOrNull(instance, Object.class, "delegate"); + if (delegate != null) return readFieldOrNull(delegate, fieldType, fieldName); + } + + return null; + } + + public SSLContext getSSLContext() { + String jvmVersion = System.getProperty("java.specification.version"); + if ("1.7".equals(jvmVersion)) { + try { + // JDK 1.7 (public version) only support > TLSv1 with named protocols + return SSLContext.getInstance("TLSv1.2"); + } catch (NoSuchAlgorithmException e) { + // fallback to TLS + } + } + + try { + return SSLContext.getInstance("TLS"); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("No TLS provider", e); + } + } + + public TrustRootIndex buildTrustRootIndex(X509TrustManager trustManager) { + return new BasicTrustRootIndex(trustManager.getAcceptedIssuers()); + } + + public void configureSslSocketFactory(SSLSocketFactory socketFactory) {} + + @Override + public String toString() { + return getClass().getSimpleName(); + } +} diff --git a/dd-java-agent/build.gradle b/dd-java-agent/build.gradle index 9885181de25..c583bb6f588 100644 --- a/dd-java-agent/build.gradle +++ b/dd-java-agent/build.gradle @@ -102,6 +102,9 @@ def generalShadowJarConfig(ShadowJar shadowJarTask) { // patch JFFI loading mechanism to maintain isolation exclude '**/com/kenai/jffi/Init.class' relocate('com.kenai.jffi.Init', 'com.kenai.jffi.PatchInit') + // patch OkHttp to avoid premain provider lookup + exclude '**/okhttp3/internal/platform/Platform.class' + relocate('okhttp3.internal.platform.Platform', 'datadog.okhttp3.internal.platform.PatchPlatform') // use dd-instrument-java's embedded copy of asm relocate('org.objectweb.asm', 'datadog.instrument.asm')