|
18 | 18 |
|
19 | 19 | import static com.google.cloud.bigquery.storage.v1.stub.BigQueryReadStubSettings.defaultGrpcTransportProviderBuilder; |
20 | 20 |
|
| 21 | +import com.google.api.client.googleapis.GoogleUtils; |
21 | 22 | import com.google.api.client.http.HttpTransport; |
22 | 23 | import com.google.api.client.http.apache.v5.Apache5HttpTransport; |
| 24 | +import com.google.api.client.http.javanet.NetHttpTransport; |
23 | 25 | import com.google.api.gax.rpc.TransportChannelProvider; |
24 | 26 | import com.google.auth.http.HttpTransportFactory; |
25 | 27 | import com.google.cloud.bigquery.exception.BigQueryJdbcRuntimeException; |
|
35 | 37 | import java.net.SocketAddress; |
36 | 38 | import java.security.GeneralSecurityException; |
37 | 39 | import java.security.KeyStore; |
| 40 | +import java.security.cert.CertificateException; |
| 41 | +import java.security.cert.X509Certificate; |
38 | 42 | import java.util.HashMap; |
39 | 43 | import java.util.Map; |
40 | 44 | import java.util.regex.Pattern; |
41 | 45 | import javax.net.ssl.SSLContext; |
| 46 | +import javax.net.ssl.TrustManager; |
42 | 47 | import javax.net.ssl.TrustManagerFactory; |
| 48 | +import javax.net.ssl.X509TrustManager; |
43 | 49 | import org.apache.hc.client5.http.auth.AuthScope; |
44 | 50 | import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; |
45 | 51 | import org.apache.hc.client5.http.impl.DefaultAuthenticationStrategy; |
@@ -136,17 +142,27 @@ static HttpTransportOptions getHttpTransportOptions( |
136 | 142 | boolean hasProxyOrSsl = |
137 | 143 | proxyProperties.containsKey(BigQueryJdbcUrlUtility.PROXY_HOST_PROPERTY_NAME) |
138 | 144 | || sslTrustStorePath != null; |
139 | | - boolean hasTimeoutConfig = connectTimeout != null || readTimeout != null; |
140 | | - |
141 | | - if (!hasProxyOrSsl && !hasTimeoutConfig) { |
142 | | - return null; |
143 | | - } |
144 | 145 |
|
145 | 146 | HttpTransportOptions.Builder httpTransportOptionsBuilder = HttpTransportOptions.newBuilder(); |
146 | 147 | if (hasProxyOrSsl) { |
147 | 148 | httpTransportOptionsBuilder.setHttpTransportFactory( |
148 | 149 | getHttpTransportFactory( |
149 | 150 | 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 | + }); |
150 | 166 | } |
151 | 167 |
|
152 | 168 | if (connectTimeout != null) { |
@@ -196,15 +212,19 @@ private static HttpTransportFactory getHttpTransportFactory( |
196 | 212 | SSLContext sslContext = SSLContext.getInstance("TLS"); |
197 | 213 | sslContext.init(null, trustManagerFactory.getTrustManagers(), null); |
198 | 214 |
|
199 | | - SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext); |
200 | | - httpClientBuilder.setConnectionManager( |
201 | | - PoolingHttpClientConnectionManagerBuilder.create() |
202 | | - .setSSLSocketFactory(sslSocketFactory) |
203 | | - .build()); |
| 215 | + setSslSocketFactory(httpClientBuilder, sslContext); |
204 | 216 | } catch (IOException | GeneralSecurityException e) { |
205 | 217 | throw new BigQueryJdbcRuntimeException( |
206 | 218 | "Failed to configure SSL TrustStore for HTTP transport", e); |
207 | 219 | } |
| 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 | + } |
208 | 228 | } |
209 | 229 | addAuthToProxyIfPresent(proxyProperties, httpClientBuilder, callerClassName); |
210 | 230 |
|
@@ -318,4 +338,91 @@ private static HttpConnectProxiedSocketAddress getHttpConnectProxiedSocketAddres |
318 | 338 | } |
319 | 339 | return builder.build(); |
320 | 340 | } |
| 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 | + } |
321 | 428 | } |
0 commit comments