Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

## Unreleased

### SDK

#### Exporters

* OTLP: Fix OkHttp HTTP sender dropping client certificates when client TLS is configured without custom trusted certificates.

## Version 1.63.0 (2026-06-05)

### API
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,18 @@ public X509TrustManager getTrustManager() {
return trustManager;
}

@Nullable
public X509TrustManager getEffectiveTrustManager() {
if (trustManager != null) {
return trustManager;
}
try {
return TlsUtil.defaultTrustManager();
} catch (SSLException e) {
throw new IllegalArgumentException(e);
}
}

/** Get the {@link SSLContext}. */
@Nullable
public SSLContext getSslContext() {
Expand All @@ -122,7 +134,7 @@ public SSLContext getSslContext() {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(
keyManager == null ? null : new KeyManager[] {keyManager},
trustManager == null ? null : new TrustManager[] {trustManager},
new TrustManager[] {getEffectiveTrustManager()},
null);
return sslContext;
} catch (NoSuchAlgorithmException | KeyManagementException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,27 @@ public static X509KeyManager keyManager(byte[] privateKeyPem, byte[] certificate
}
}

/** Returns the platform default {@link X509TrustManager}. */
public static X509TrustManager defaultTrustManager() throws SSLException {
try {
TrustManagerFactory tmf =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());

// Initialize with the platform default trust store.
tmf.init((KeyStore) null);

for (TrustManager trustManager : tmf.getTrustManagers()) {
if (trustManager instanceof X509TrustManager) {
return (X509TrustManager) trustManager;
}
}

throw new SSLException("No X509TrustManager found");
} catch (KeyStoreException | NoSuchAlgorithmException e) {
throw new SSLException("Could not build default TrustManager.", e);
}
}

// Visible for testing
static PrivateKey generatePrivateKey(PKCS8EncodedKeySpec keySpec, List<KeyFactory> keyFactories)
throws SSLException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,22 @@ void setSslContext_AlreadyExists_Throws() throws Exception {
.isInstanceOf(IllegalStateException.class)
.hasMessageContaining("sslContext or trustManager has been previously configured");
}

@Test
void getEffectiveTrustManager_returnsConfiguredTrustManager()
throws CertificateEncodingException {
helper.setTrustManagerFromCerts(serverTls.certificate().getEncoded());

assertThat(helper.getEffectiveTrustManager()).isSameAs(helper.getTrustManager());
}

@Test
void getEffectiveTrustManager_returnsDefaultTrustManager() {
assertThat(helper.getEffectiveTrustManager()).isNotNull();
}

@Test
void getSslContext_usesDefaultTrustManagerWhenUnset() {
assertThat(helper.getSslContext()).isNotNull();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ void generatePrivateKey_Invalid() {
.hasMessage("Unable to generate key from supported algorithms: [EC]");
}

@Test
void defaultTrustManager() {
assertThatCode(TlsUtil::defaultTrustManager).doesNotThrowAnyException();
}

/**
* Append <a href="https://datatracker.ietf.org/doc/html/rfc7468#section-5.2">explanatory text</a>
* prefix and verify {@link TlsUtil#keyManager(byte[], byte[])} succeeds.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import io.opentelemetry.api.impl.InstrumentationUtil;
import io.opentelemetry.exporter.internal.RetryUtil;
import io.opentelemetry.exporter.internal.TlsUtil;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.common.export.Compressor;
import io.opentelemetry.sdk.common.export.HttpResponse;
Expand All @@ -28,6 +29,7 @@
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.X509TrustManager;
import okhttp3.Call;
import okhttp3.Callback;
Expand Down Expand Up @@ -108,8 +110,17 @@ public OkHttpHttpSender(
boolean isPlainHttp = endpoint.getScheme().equals("http");
if (isPlainHttp) {
builder.connectionSpecs(Collections.singletonList(ConnectionSpec.CLEARTEXT));
} else if (sslContext != null && trustManager != null) {
builder.sslSocketFactory(sslContext.getSocketFactory(), trustManager);
} else if (sslContext != null) {
X509TrustManager effectiveTrustManager = trustManager;

if (effectiveTrustManager == null) {
try {
effectiveTrustManager = TlsUtil.defaultTrustManager();
} catch (SSLException e) {
throw new IllegalStateException("Unable to initialize default trust manager", e);
}
}
builder.sslSocketFactory(sslContext.getSocketFactory(), effectiveTrustManager);
}

this.client = builder.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import javax.net.ssl.SSLContext;
import org.junit.jupiter.api.Test;

class OkHttpHttpSenderTest {
Expand Down Expand Up @@ -60,4 +61,27 @@ public int getContentLength() {
return 0;
}
}

@Test
void constructor_usesDefaultTrustManagerWhenTrustManagerIsNull() throws Exception {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, null, null);

OkHttpHttpSender sender =
new OkHttpHttpSender(
URI.create("https://localhost"),
"text/plain",
null,
Duration.ofSeconds(10),
Duration.ofSeconds(10),
Collections::emptyMap,
null,
null,
sslContext,
null,
null,
Long.MAX_VALUE);

assertThat(sender).isNotNull();
}
}
Loading