Skip to content

Commit 7da0f1f

Browse files
committed
Merge branch 'ssl-truststore-handling-2' into ssl-tests-and-ci
2 parents b28d0fb + 2042083 commit 7da0f1f

5 files changed

Lines changed: 88 additions & 141 deletions

File tree

.github/workflows/sslTesting.yml

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,22 @@
1+
# ===================================================================
2+
# GitHub Action: SSL Certificate Validation Test with Squid Proxy
3+
#
4+
# Purpose:
5+
# This workflow simulates real-world SSL trust chain configurations
6+
# to validate JDBC driver support for:
7+
# - Custom trust stores
8+
# - System trust stores
9+
# - Self-signed certificate handling
10+
# - Revocation and fallback behavior
11+
#
12+
# How:
13+
# - Generates a Root CA, Intermediate CA, and signs a server cert (mirroring real world use-cases)
14+
# - Starts a Squid HTTPS proxy using the signed cert
15+
# - Creates a Java truststore with the correct anchors
16+
# - Optionally installs the Root CA into system trust store
17+
# - Runs targeted JDBC integration tests using SSLTest.java
18+
# ===================================================================1
19+
120
name: SSL Certificate Validation Test with Squid Proxy
221

322
on:
@@ -145,7 +164,7 @@ jobs:
145164
- name: Wait for Squid to be Ready
146165
run: |
147166
for i in {1..5}; do
148-
if curl -v -x http://localhost:3128 http://example.com -m 10 -o /dev/null; then
167+
if curl -v -x http://localhost:3128 http://databricks.com -m 10 -o /dev/null; then
149168
echo "HTTP proxy on 3128 is working!"
150169
break
151170
fi

src/main/java/com/databricks/jdbc/dbclient/impl/common/ConfiguratorUtils.java

Lines changed: 46 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@
3030
public class ConfiguratorUtils {
3131
private static final JdbcLogger LOGGER = JdbcLoggerFactory.getLogger(ConfiguratorUtils.class);
3232

33+
private static final String JAVA_TRUST_STORE_PATH_PROPERTY = "javax.net.ssl.trustStore";
34+
private static final String JAVA_TRUST_STORE_PASSWORD_PROPERTY =
35+
"javax.net.ssl.trustStorePassword";
36+
private static final String JAVA_TRUST_STORE_TYPE_PROPERTY = "javax.net.ssl.trustStoreType";
37+
3338
private static boolean isJDBCTestEnv() {
3439
return Boolean.parseBoolean(System.getenv(IS_JDBC_TEST_ENV));
3540
}
@@ -143,7 +148,7 @@ private static Registry<ConnectionSocketFactory> createRegistryWithSystemOrDefau
143148
String sysTrustStore = null;
144149
if (connectionContext.useSystemTrustStore()) {
145150
// When useSystemTrustStore=true, check for javax.net.ssl.trustStore system property
146-
sysTrustStore = System.getProperty("javax.net.ssl.trustStore");
151+
sysTrustStore = System.getProperty(JAVA_TRUST_STORE_PATH_PROPERTY);
147152
}
148153

149154
// If system property is set and useSystemTrustStore=true, use that trust store
@@ -183,9 +188,9 @@ private static Registry<ConnectionSocketFactory> createRegistryWithSystemPropert
183188

184189
// Load the system property trust store
185190
KeyStore trustStore =
186-
KeyStore.getInstance(System.getProperty("javax.net.ssl.trustStoreType", "JKS"));
191+
KeyStore.getInstance(System.getProperty(JAVA_TRUST_STORE_TYPE_PROPERTY, "JKS"));
187192
char[] password = null;
188-
String passwordProp = System.getProperty("javax.net.ssl.trustStorePassword");
193+
String passwordProp = System.getProperty(JAVA_TRUST_STORE_PASSWORD_PROPERTY);
189194
if (passwordProp != null) {
190195
password = passwordProp.toCharArray();
191196
}
@@ -196,28 +201,16 @@ private static Registry<ConnectionSocketFactory> createRegistryWithSystemPropert
196201

197202
// Get trust anchors and create trust managers
198203
Set<TrustAnchor> trustAnchors = getTrustAnchorsFromTrustStore(trustStore);
199-
if (trustAnchors.isEmpty()) {
200-
String errorMessage = "System property trust store contains no trust anchors.";
201-
handleError(errorMessage, new KeyStoreException(errorMessage));
202-
}
203-
204-
TrustManager[] trustManagers =
205-
createTrustManagers(
206-
trustAnchors,
207-
connectionContext.checkCertificateRevocation(),
208-
connectionContext.acceptUndeterminedCertificateRevocation());
209-
210-
return createSocketFactoryRegistry(trustManagers);
204+
return createRegistryFromTrustAnchors(
205+
trustAnchors, connectionContext, "system property trust store: " + sysTrustStore);
211206
} catch (DatabricksHttpException
212207
| KeyStoreException
213208
| NoSuchAlgorithmException
214209
| CertificateException
215-
| IOException
216-
| InvalidAlgorithmParameterException
217-
| KeyManagementException e) {
210+
| IOException e) {
218211
handleError("Error while setting up system property trust store: " + sysTrustStore, e);
219212
}
220-
return null; // This will never be reached, but is required for method signature.
213+
return null;
221214
}
222215

223216
/**
@@ -239,46 +232,38 @@ private static Registry<ConnectionSocketFactory> createRegistryWithJdkDefaultTru
239232
"UseSystemTrustStore=false, using JDK default trust store (cacerts) and ignoring system properties");
240233
}
241234

242-
// Initialize with default trust managers from JDK's cacerts
243-
TrustManagerFactory tmf =
244-
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
245-
tmf.init((KeyStore) null); // null uses the JDK's default trust store (cacerts)
246-
247-
// Extract trust anchors from default trust store
248-
X509TrustManager x509TrustManager = findX509TrustManager(tmf.getTrustManagers());
249-
if (x509TrustManager == null) {
250-
throw new DatabricksHttpException(
251-
"No X509TrustManager found in JDK default trust store",
252-
DatabricksDriverErrorCode.SSL_HANDSHAKE_ERROR);
253-
}
254-
255-
Set<TrustAnchor> systemTrustAnchors =
256-
Arrays.stream(x509TrustManager.getAcceptedIssuers())
257-
.map(cert -> new TrustAnchor(cert, null))
258-
.collect(Collectors.toSet());
235+
Set<TrustAnchor> systemTrustAnchors = getTrustAnchorsFromTrustStore(null);
236+
return createRegistryFromTrustAnchors(
237+
systemTrustAnchors, connectionContext, "JDK default trust store (cacerts)");
238+
} catch (DatabricksHttpException e) {
239+
handleError("Error while setting up JDK default trust store", e);
240+
}
241+
return null;
242+
}
259243

260-
if (systemTrustAnchors.isEmpty()) {
261-
throw new DatabricksHttpException(
262-
"JDK default trust store contains no trust anchors",
263-
DatabricksDriverErrorCode.SSL_HANDSHAKE_ERROR);
264-
}
244+
private static Registry<ConnectionSocketFactory> createRegistryFromTrustAnchors(
245+
Set<TrustAnchor> trustAnchors,
246+
IDatabricksConnectionContext connectionContext,
247+
String sourceDescription)
248+
throws DatabricksHttpException {
249+
if (trustAnchors == null || trustAnchors.isEmpty()) {
250+
throw new DatabricksHttpException(
251+
sourceDescription + " contains no trust anchors",
252+
DatabricksDriverErrorCode.SSL_HANDSHAKE_ERROR);
253+
}
265254

266-
// Always use the same trust manager creation mechanism with revocation settings
255+
try {
267256
TrustManager[] trustManagers =
268257
createTrustManagers(
269-
systemTrustAnchors,
258+
trustAnchors,
270259
connectionContext.checkCertificateRevocation(),
271260
connectionContext.acceptUndeterminedCertificateRevocation());
272261

273262
return createSocketFactoryRegistry(trustManagers);
274-
} catch (DatabricksHttpException
275-
| KeyStoreException
276-
| NoSuchAlgorithmException
277-
| InvalidAlgorithmParameterException
278-
| KeyManagementException e) {
279-
handleError("Error while setting up JDK default trust store: ", e);
263+
} catch (Exception e) {
264+
handleError("Error setting up trust managers for " + sourceDescription, e);
280265
}
281-
return null; // This will never be reached, but is required for method signature.
266+
return null;
282267
}
283268

284269
/**
@@ -328,12 +313,7 @@ private static TrustManager[] createTrustManagers(
328313
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
329314
customTmf.init(trustManagerParams);
330315

331-
if (checkCertificateRevocation) {
332-
LOGGER.info("Certificate revocation checking enabled");
333-
} else {
334-
LOGGER.info("Certificate revocation checking disabled");
335-
}
336-
316+
LOGGER.info("Certificate revocation check: " + checkCertificateRevocation);
337317
return customTmf.getTrustManagers();
338318
}
339319

@@ -418,62 +398,28 @@ public static KeyStore loadTruststoreOrNull(IDatabricksConnectionContext connect
418398
public static Set<TrustAnchor> getTrustAnchorsFromTrustStore(KeyStore trustStore)
419399
throws DatabricksHttpException {
420400
try {
421-
if (trustStore == null) {
422-
return Collections.emptySet();
423-
}
424-
425-
// Create a trust manager factory
426401
TrustManagerFactory trustManagerFactory =
427402
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
428403
trustManagerFactory.init(trustStore);
429404

430405
// Get the trust managers
431406
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
432-
if (trustManagers == null || trustManagers.length == 0) {
433-
LOGGER.warn("No trust managers found in the trust store");
434-
return Collections.emptySet();
435-
}
436-
437-
// Find the X509TrustManager
438407
X509TrustManager x509TrustManager = findX509TrustManager(trustManagers);
439-
if (x509TrustManager == null) {
440-
LOGGER.warn("No X509TrustManager found in the trust store");
441-
return Collections.emptySet();
442-
}
443408

444-
// Get the accepted issuers (trust anchors)
445-
X509Certificate[] acceptedIssuers = x509TrustManager.getAcceptedIssuers();
446-
if (acceptedIssuers == null || acceptedIssuers.length == 0) {
447-
LOGGER.warn("No accepted issuers found in the X509TrustManager");
409+
if (x509TrustManager == null || x509TrustManager.getAcceptedIssuers().length == 0) {
410+
// No trust anchors found
448411
return Collections.emptySet();
449412
}
450413

451-
// Convert certificates to trust anchors
452-
Set<TrustAnchor> trustAnchors =
453-
Arrays.stream(acceptedIssuers)
454-
.map(cert -> new TrustAnchor(cert, null))
455-
.collect(Collectors.toSet());
456-
457-
LOGGER.info("Found " + trustAnchors.size() + " trust anchors in the trust store");
458-
return trustAnchors;
414+
return Arrays.stream(x509TrustManager.getAcceptedIssuers())
415+
.map(cert -> new TrustAnchor(cert, null))
416+
.collect(Collectors.toSet());
459417
} catch (KeyStoreException | NoSuchAlgorithmException e) {
460-
String errorMessage = "Error while getting trust anchors from trust store: " + e.getMessage();
461-
handleError(errorMessage, e);
418+
handleError("Error while getting trust anchors from trust store: " + e.getMessage(), e);
462419
}
463-
return Collections.emptySet(); // Return empty set if error occurs
420+
return Collections.emptySet();
464421
}
465422

466-
/**
467-
* Builds trust manager parameters for certificate path validation including certificate
468-
* revocation checking.
469-
*
470-
* @param trustAnchors The trust anchors to use in the trust manager.
471-
* @param checkCertificateRevocation Whether to check certificate revocation.
472-
* @param acceptUndeterminedCertificateRevocation Whether to accept undetermined certificate
473-
* revocation status.
474-
* @return The trust manager parameters based on the input parameters.
475-
* @throws DatabricksHttpException If there is an error during configuration.
476-
*/
477423
public static CertPathTrustManagerParameters buildTrustManagerParameters(
478424
Set<TrustAnchor> trustAnchors,
479425
boolean checkCertificateRevocation,
@@ -507,16 +453,10 @@ public static CertPathTrustManagerParameters buildTrustManagerParameters(
507453
}
508454

509455
return new CertPathTrustManagerParameters(pkixBuilderParameters);
510-
} catch (NoSuchAlgorithmException e) {
511-
String errorMessage =
512-
"No such algorithm error while building trust manager parameters: " + e.getMessage();
513-
handleError(errorMessage, e);
514-
} catch (InvalidAlgorithmParameterException e) {
515-
String errorMessage =
516-
"Invalid parameter error while building trust manager parameters: " + e.getMessage();
517-
handleError(errorMessage, e);
456+
} catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
457+
handleError("Error while building trust manager parameters: " + e.getMessage(), e);
518458
}
519-
return null; // Return null in case of error
459+
return null;
520460
}
521461

522462
/**

src/main/java/com/databricks/jdbc/dbclient/impl/http/DatabricksHttpClient.java

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,7 @@ public class DatabricksHttpClient implements IDatabricksHttpClient, Closeable {
5050
private IdleConnectionEvictor idleConnectionEvictor;
5151
private CloseableHttpAsyncClient asyncClient;
5252

53-
DatabricksHttpClient(IDatabricksConnectionContext connectionContext, HttpClientType type)
54-
throws DatabricksHttpException {
53+
DatabricksHttpClient(IDatabricksConnectionContext connectionContext, HttpClientType type) {
5554
connectionManager = initializeConnectionManager(connectionContext);
5655
httpClient = makeClosableHttpClient(connectionContext, type);
5756
idleConnectionEvictor =
@@ -125,12 +124,17 @@ public void close() throws IOException {
125124
}
126125

127126
private PoolingHttpClientConnectionManager initializeConnectionManager(
128-
IDatabricksConnectionContext connectionContext) throws DatabricksHttpException {
129-
PoolingHttpClientConnectionManager connectionManager =
130-
ConfiguratorUtils.getBaseConnectionManager(connectionContext);
131-
connectionManager.setMaxTotal(DEFAULT_MAX_HTTP_CONNECTIONS);
132-
connectionManager.setDefaultMaxPerRoute(DEFAULT_MAX_HTTP_CONNECTIONS_PER_ROUTE);
133-
return connectionManager;
127+
IDatabricksConnectionContext connectionContext) {
128+
try {
129+
PoolingHttpClientConnectionManager connectionManager =
130+
ConfiguratorUtils.getBaseConnectionManager(connectionContext);
131+
connectionManager.setMaxTotal(DEFAULT_MAX_HTTP_CONNECTIONS);
132+
connectionManager.setDefaultMaxPerRoute(DEFAULT_MAX_HTTP_CONNECTIONS_PER_ROUTE);
133+
return connectionManager;
134+
} catch (DatabricksHttpException e) {
135+
LOGGER.error("Failed to initialize HTTP connection manager", e);
136+
throw new RuntimeException("Failed to initialize HTTP connection manager", e);
137+
}
134138
}
135139

136140
private RequestConfig makeRequestConfig(int timeoutSeconds) {

src/main/java/com/databricks/jdbc/dbclient/impl/http/DatabricksHttpClientFactory.java

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import com.databricks.jdbc.api.internal.IDatabricksConnectionContext;
66
import com.databricks.jdbc.common.HttpClientType;
77
import com.databricks.jdbc.dbclient.IDatabricksHttpClient;
8-
import com.databricks.jdbc.exception.DatabricksHttpException;
98
import com.databricks.jdbc.log.JdbcLogger;
109
import com.databricks.jdbc.log.JdbcLoggerFactory;
1110
import java.io.IOException;
@@ -34,13 +33,7 @@ public IDatabricksHttpClient getClient(
3433
IDatabricksConnectionContext context, HttpClientType type) {
3534
return instances.computeIfAbsent(
3635
getClientKey(context.getConnectionUuid(), type),
37-
k -> {
38-
try {
39-
return new DatabricksHttpClient(context, type);
40-
} catch (DatabricksHttpException e) {
41-
throw new RuntimeException(e);
42-
}
43-
});
36+
k -> new DatabricksHttpClient(context, type));
4437
}
4538

4639
public void removeClient(IDatabricksConnectionContext context) {

0 commit comments

Comments
 (0)