Skip to content

Commit 6722f91

Browse files
committed
feat(bigquery-jdbc): respect JVM trustStore properties and default to JVM cacerts with google.p12 fallback
1 parent 9aab7eb commit 6722f91

2 files changed

Lines changed: 123 additions & 14 deletions

File tree

java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcProxyUtility.java

Lines changed: 117 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@
1818

1919
import static com.google.cloud.bigquery.storage.v1.stub.BigQueryReadStubSettings.defaultGrpcTransportProviderBuilder;
2020

21+
import com.google.api.client.googleapis.GoogleUtils;
2122
import com.google.api.client.http.HttpTransport;
2223
import com.google.api.client.http.apache.v5.Apache5HttpTransport;
24+
import com.google.api.client.http.javanet.NetHttpTransport;
2325
import com.google.api.gax.rpc.TransportChannelProvider;
2426
import com.google.auth.http.HttpTransportFactory;
2527
import com.google.cloud.bigquery.exception.BigQueryJdbcRuntimeException;
@@ -35,11 +37,15 @@
3537
import java.net.SocketAddress;
3638
import java.security.GeneralSecurityException;
3739
import java.security.KeyStore;
40+
import java.security.cert.CertificateException;
41+
import java.security.cert.X509Certificate;
3842
import java.util.HashMap;
3943
import java.util.Map;
4044
import java.util.regex.Pattern;
4145
import javax.net.ssl.SSLContext;
46+
import javax.net.ssl.TrustManager;
4247
import javax.net.ssl.TrustManagerFactory;
48+
import javax.net.ssl.X509TrustManager;
4349
import org.apache.hc.client5.http.auth.AuthScope;
4450
import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
4551
import org.apache.hc.client5.http.impl.DefaultAuthenticationStrategy;
@@ -136,17 +142,27 @@ static HttpTransportOptions getHttpTransportOptions(
136142
boolean hasProxyOrSsl =
137143
proxyProperties.containsKey(BigQueryJdbcUrlUtility.PROXY_HOST_PROPERTY_NAME)
138144
|| sslTrustStorePath != null;
139-
boolean hasTimeoutConfig = connectTimeout != null || readTimeout != null;
140-
141-
if (!hasProxyOrSsl && !hasTimeoutConfig) {
142-
return null;
143-
}
144145

145146
HttpTransportOptions.Builder httpTransportOptionsBuilder = HttpTransportOptions.newBuilder();
146147
if (hasProxyOrSsl) {
147148
httpTransportOptionsBuilder.setHttpTransportFactory(
148149
getHttpTransportFactory(
149150
proxyProperties, sslTrustStorePath, sslTrustStorePassword, callerClassName));
151+
} else {
152+
// Default to NetHttpTransport configured with a MergedTrustManager that trusts
153+
// both the JVM's default trust store and Google's bundled certificate store.
154+
httpTransportOptionsBuilder.setHttpTransportFactory(
155+
() -> {
156+
try {
157+
SSLContext sslContext = createMergedSslContext();
158+
return new NetHttpTransport.Builder()
159+
.setSslSocketFactory(sslContext.getSocketFactory())
160+
.build();
161+
} catch (GeneralSecurityException | IOException e) {
162+
throw new BigQueryJdbcRuntimeException(
163+
"Failed to configure SSL for HTTP transport", e);
164+
}
165+
});
150166
}
151167

152168
if (connectTimeout != null) {
@@ -196,15 +212,19 @@ private static HttpTransportFactory getHttpTransportFactory(
196212
SSLContext sslContext = SSLContext.getInstance("TLS");
197213
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
198214

199-
SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext);
200-
httpClientBuilder.setConnectionManager(
201-
PoolingHttpClientConnectionManagerBuilder.create()
202-
.setSSLSocketFactory(sslSocketFactory)
203-
.build());
215+
setSslSocketFactory(httpClientBuilder, sslContext);
204216
} catch (IOException | GeneralSecurityException e) {
205217
throw new BigQueryJdbcRuntimeException(
206218
"Failed to configure SSL TrustStore for HTTP transport", e);
207219
}
220+
} else {
221+
// Default to MergedTrustManager when no custom SSLTrustStore is specified, ensuring standard
222+
// JVM properties (like javax.net.ssl.trustStore) and google.p12 fallback are respected.
223+
try {
224+
setSslSocketFactory(httpClientBuilder, createMergedSslContext());
225+
} catch (IOException | GeneralSecurityException e) {
226+
throw new BigQueryJdbcRuntimeException("Failed to configure SSL for HTTP transport", e);
227+
}
208228
}
209229
addAuthToProxyIfPresent(proxyProperties, httpClientBuilder, callerClassName);
210230

@@ -318,4 +338,91 @@ private static HttpConnectProxiedSocketAddress getHttpConnectProxiedSocketAddres
318338
}
319339
return builder.build();
320340
}
341+
342+
private static SSLContext createMergedSslContext() throws GeneralSecurityException, IOException {
343+
SSLContext sslContext = SSLContext.getInstance("TLS");
344+
sslContext.init(null, new TrustManager[] {createMergedTrustManager()}, null);
345+
return sslContext;
346+
}
347+
348+
private static void setSslSocketFactory(
349+
HttpClientBuilder httpClientBuilder, SSLContext sslContext) {
350+
SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext);
351+
httpClientBuilder.setConnectionManager(
352+
PoolingHttpClientConnectionManagerBuilder.create()
353+
.setSSLSocketFactory(sslSocketFactory)
354+
.build());
355+
}
356+
357+
private static X509TrustManager createMergedTrustManager()
358+
throws GeneralSecurityException, IOException {
359+
// 1. Get default JVM TrustManager
360+
TrustManagerFactory defaultTmf =
361+
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
362+
defaultTmf.init((KeyStore) null);
363+
X509TrustManager defaultTm = findX509TrustManager(defaultTmf);
364+
365+
// 2. Get Google TrustManager
366+
KeyStore googleKeystore = GoogleUtils.getCertificateTrustStore();
367+
TrustManagerFactory googleTmf =
368+
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
369+
googleTmf.init(googleKeystore);
370+
X509TrustManager googleTm = findX509TrustManager(googleTmf);
371+
372+
if (defaultTm == null || googleTm == null) {
373+
throw new IllegalStateException("Could not find X509TrustManager");
374+
}
375+
376+
return new MergedTrustManager(defaultTm, googleTm);
377+
}
378+
379+
private static X509TrustManager findX509TrustManager(TrustManagerFactory tmf) {
380+
for (TrustManager tm : tmf.getTrustManagers()) {
381+
if (tm instanceof X509TrustManager) {
382+
return (X509TrustManager) tm;
383+
}
384+
}
385+
return null;
386+
}
387+
388+
private static class MergedTrustManager implements X509TrustManager {
389+
private final X509TrustManager defaultTm;
390+
private final X509TrustManager googleTm;
391+
392+
public MergedTrustManager(X509TrustManager defaultTm, X509TrustManager googleTm) {
393+
this.defaultTm = defaultTm;
394+
this.googleTm = googleTm;
395+
}
396+
397+
@Override
398+
public X509Certificate[] getAcceptedIssuers() {
399+
X509Certificate[] defaultIssuers = defaultTm.getAcceptedIssuers();
400+
X509Certificate[] googleIssuers = googleTm.getAcceptedIssuers();
401+
X509Certificate[] result = new X509Certificate[defaultIssuers.length + googleIssuers.length];
402+
System.arraycopy(defaultIssuers, 0, result, 0, defaultIssuers.length);
403+
System.arraycopy(googleIssuers, 0, result, defaultIssuers.length, googleIssuers.length);
404+
return result;
405+
}
406+
407+
@Override
408+
public void checkClientTrusted(X509Certificate[] chain, String authType)
409+
throws CertificateException {
410+
try {
411+
defaultTm.checkClientTrusted(chain, authType);
412+
} catch (CertificateException e) {
413+
googleTm.checkClientTrusted(chain, authType);
414+
}
415+
}
416+
417+
@Override
418+
public void checkServerTrusted(X509Certificate[] chain, String authType)
419+
throws CertificateException {
420+
try {
421+
defaultTm.checkServerTrusted(chain, authType);
422+
} catch (CertificateException e) {
423+
// Fall back to Google's trusted certs
424+
googleTm.checkServerTrusted(chain, authType);
425+
}
426+
}
427+
}
321428
}

java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcProxyUtilityTest.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ public void testGetHttpTransportOptionsWithNonAuthenticatedProxy() {
161161
}
162162

163163
@Test
164-
public void testGetHttpTransportOptionsWithNoProxySettingsReturnsNull() {
164+
public void testGetHttpTransportOptionsWithNoProxySettingsReturnsDefaultOptions() {
165165
String connection_uri =
166166
"jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;"
167167
+ "ProjectId=TestProject"
@@ -172,7 +172,8 @@ public void testGetHttpTransportOptionsWithNoProxySettingsReturnsNull() {
172172
HttpTransportOptions result =
173173
BigQueryJdbcProxyUtility.getHttpTransportOptions(
174174
proxyProperties, null, null, null, null, "TestClass");
175-
assertNull(result);
175+
assertNotNull(result);
176+
assertNotNull(result.getHttpTransportFactory());
176177
}
177178

178179
private String getTestResourcePath(String resourceName) throws URISyntaxException {
@@ -299,11 +300,12 @@ public void testGetTransportChannelProvider_noProxyNoSsl_returnsNull() {
299300
}
300301

301302
@Test
302-
public void testGetHttpTransportOptions_noProxyNoSsl_returnsNull() {
303+
public void testGetHttpTransportOptions_noProxyNoSsl_returnsDefaultOptions() {
303304
HttpTransportOptions options =
304305
BigQueryJdbcProxyUtility.getHttpTransportOptions(
305306
Collections.<String, String>emptyMap(), null, null, null, null, "TestClass");
306-
assertNull(options);
307+
assertNotNull(options);
308+
assertNotNull(options.getHttpTransportFactory());
307309
}
308310

309311
@Test

0 commit comments

Comments
 (0)