Skip to content

Commit

Permalink
Test OkHttp patch
Browse files Browse the repository at this point in the history
  • Loading branch information
mcculls committed Jan 25, 2025
1 parent 1b56d50 commit fb48dfb
Show file tree
Hide file tree
Showing 5 changed files with 339 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@
import java.net.URISyntaxException;
import java.net.URL;
import java.security.CodeSource;
import java.security.Security;
import java.util.EnumSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
Expand Down Expand Up @@ -565,7 +564,6 @@ private void resumeRemoteComponents() {
// remote components were paused for custom log-manager/jmx-builder
// add small delay before resuming remote I/O to help stabilization
Thread.sleep(okHttpDelayMillis);
Security.getProviders();
scoClass.getMethod("resume").invoke(sco);
} catch (InterruptedException ignore) {
} catch (Throwable e) {
Expand Down
5 changes: 5 additions & 0 deletions dd-java-agent/agent-okhttp/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
apply from: "$rootDir/gradle/java.gradle"

dependencies {
api libs.okhttp
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,329 @@
/*
* ******** PATCHED TO REMOVE REFERENCE TO Security.getProviders() ********
*
* 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 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;

/**
* Access to platform-specific features.
*
* <h3>Server name indication (SNI)</h3>
*
* <p>Supported on Android 2.3+.
*
* <p>Supported on OpenJDK 7+
*
* <h3>Session Tickets</h3>
*
* <p>Supported on Android 2.3+.
*
* <h3>Android Traffic Stats (Socket Tagging)</h3>
*
* <p>Supported on Android 4.0+.
*
* <h3>ALPN (Application Layer Protocol Negotiation)</h3>
*
* <p>Supported on Android 5.0+. The APIs were present in Android 4.4, but that implementation was
* unstable.
*
* <p>Supported on OpenJDK 7 and 8 (via the JettyALPN-boot library).
*
* <p>Supported on OpenJDK 9 via SSLParameters and SSLSocket features.
*
* <h3>Trust Manager Extraction</h3>
*
* <p>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}.
*
* <h3>Android Cleartext Permit Detection</h3>
*
* <p>Supported on Android 6.0+ via {@code NetworkSecurityPolicy}.
*/
public class Platform {
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";
}

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<Protocol> 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<String> alpnProtocolNames(List<Protocol> protocols) {
List<String> 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);
}

public static boolean isConscryptPreferred() {
return false;

// We don't embed conscrypt so we can skip this check in the agent
// which lets us avoid an eager lookup of Security.getProviders()
/* ---------------------------------------------------------------
// 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() {
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<Protocol> 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> T readFieldOrNull(Object instance, Class<T> 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();
}
}
4 changes: 4 additions & 0 deletions dd-java-agent/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,10 @@ dependencies {
shadowInclude project(path: ':dd-java-agent:agent-otel:otel-bootstrap', configuration: 'shadow')

// Includes for the shared internal shadow jar
// patch OkHttp to avoid eager call to Security.getProviders()
sharedShadowInclude project(':dd-java-agent:agent-okhttp'), {
transitive = false
}
sharedShadowInclude deps.shared
// force a controlled version of ASM that is used by Debugger while pulled transitively by jnr
sharedShadowInclude libs.bundles.asm
Expand Down
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ include ':dd-java-agent:agent-tooling'
include ':dd-java-agent:agent-jmxfetch'
include ':dd-java-agent:agent-logging'
include ':dd-java-agent:agent-logs-intake'
include ':dd-java-agent:agent-okhttp'
include ':dd-java-agent:load-generator'

// profiling
Expand Down

0 comments on commit fb48dfb

Please sign in to comment.