@{argLine}
diff --git a/src/main/java/com/databricks/jdbc/api/impl/volume/DBFSVolumeClient.java b/src/main/java/com/databricks/jdbc/api/impl/volume/DBFSVolumeClient.java
index 11786760d6..34be2f2c7d 100644
--- a/src/main/java/com/databricks/jdbc/api/impl/volume/DBFSVolumeClient.java
+++ b/src/main/java/com/databricks/jdbc/api/impl/volume/DBFSVolumeClient.java
@@ -16,6 +16,7 @@
import com.databricks.jdbc.dbclient.IDatabricksHttpClient;
import com.databricks.jdbc.dbclient.impl.common.ClientConfigurator;
import com.databricks.jdbc.dbclient.impl.http.DatabricksHttpClientFactory;
+import com.databricks.jdbc.exception.DatabricksHttpException;
import com.databricks.jdbc.exception.DatabricksSQLException;
import com.databricks.jdbc.exception.DatabricksVolumeOperationException;
import com.databricks.jdbc.log.JdbcLogger;
@@ -58,7 +59,8 @@ public DBFSVolumeClient(WorkspaceClient workspaceClient) {
this.allowedVolumeIngestionPaths = "";
}
- public DBFSVolumeClient(IDatabricksConnectionContext connectionContext) {
+ public DBFSVolumeClient(IDatabricksConnectionContext connectionContext)
+ throws DatabricksHttpException {
this.connectionContext = connectionContext;
this.workspaceClient = getWorkspaceClientFromConnectionContext(connectionContext);
this.apiClient = workspaceClient.apiClient();
@@ -392,7 +394,7 @@ public boolean deleteObject(String catalog, String schema, String volume, String
}
WorkspaceClient getWorkspaceClientFromConnectionContext(
- IDatabricksConnectionContext connectionContext) {
+ IDatabricksConnectionContext connectionContext) throws DatabricksHttpException {
ClientConfigurator clientConfigurator = new ClientConfigurator(connectionContext);
DatabricksThreadContextHolder.setDatabricksConfig(clientConfigurator.getDatabricksConfig());
return clientConfigurator.getWorkspaceClient();
diff --git a/src/main/java/com/databricks/jdbc/api/impl/volume/DatabricksVolumeClientFactory.java b/src/main/java/com/databricks/jdbc/api/impl/volume/DatabricksVolumeClientFactory.java
index 487951fa25..ce1f24536a 100644
--- a/src/main/java/com/databricks/jdbc/api/impl/volume/DatabricksVolumeClientFactory.java
+++ b/src/main/java/com/databricks/jdbc/api/impl/volume/DatabricksVolumeClientFactory.java
@@ -3,6 +3,7 @@
import com.databricks.jdbc.api.IDatabricksVolumeClient;
import com.databricks.jdbc.api.internal.IDatabricksConnectionContext;
import com.databricks.jdbc.common.util.DatabricksThreadContextHolder;
+import com.databricks.jdbc.exception.DatabricksHttpException;
import com.databricks.jdbc.log.JdbcLogger;
import com.databricks.jdbc.log.JdbcLoggerFactory;
import java.sql.Connection;
@@ -33,7 +34,7 @@ public static IDatabricksVolumeClient getVolumeClient(Connection con) {
* @return an instance of {@link IDatabricksVolumeClient}
*/
public static IDatabricksVolumeClient getVolumeClient(
- IDatabricksConnectionContext connectionContext) {
+ IDatabricksConnectionContext connectionContext) throws DatabricksHttpException {
LOGGER.debug(
String.format(
"Entering public static IDatabricksVolumeClient getVolumeClient with IDatabricksConnectionContext connectionContext = {%s}",
diff --git a/src/main/java/com/databricks/jdbc/common/util/SocketFactoryUtil.java b/src/main/java/com/databricks/jdbc/common/util/SocketFactoryUtil.java
index 77e9dfd30a..d7112fae70 100644
--- a/src/main/java/com/databricks/jdbc/common/util/SocketFactoryUtil.java
+++ b/src/main/java/com/databricks/jdbc/common/util/SocketFactoryUtil.java
@@ -5,7 +5,6 @@
import com.databricks.sdk.core.DatabricksException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
-import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
@@ -13,67 +12,71 @@
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
+import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
public class SocketFactoryUtil {
-
private static final JdbcLogger LOGGER = JdbcLoggerFactory.getLogger(SocketFactoryUtil.class);
/**
- * NOTE: Only for testing purposes and should never be used in production.
- *
- * Builds a registry of connection socket factories that trusts all SSL certificates.
+ * Builds a registry of connection socket factories that trusts all SSL certificates. This should
+ * only be used in testing environments or when explicitly configured to allow self-signed
+ * certificates.
*
* @return A registry of connection socket factories.
*/
public static Registry getTrustAllSocketFactoryRegistry() {
LOGGER.warn(
- "This driver is configured to trust all SSL certificates. This is insecure and should be never used in production.");
- LOGGER.debug("Entering the getTrustAllSocketFactoryRegistry method");
-
+ "This driver is configured to trust all SSL certificates. This is insecure and should never be used in production.");
try {
// Create a TrustManager that trusts all certificates
- TrustManager[] trustAllCerts =
- new TrustManager[] {
- new X509TrustManager() {
- @Override
- public X509Certificate[] getAcceptedIssuers() {
- return null; // Accept all issuers
- }
-
- @Override
- public void checkClientTrusted(X509Certificate[] certs, String authType) {
- // No-op: Trust all client certificates
- }
-
- @Override
- public void checkServerTrusted(X509Certificate[] certs, String authType) {
- // No-op: Trust all server certificates
- }
- }
- };
+ TrustManager[] trustAllCerts = getTrustManagerThatTrustsAllCertificates();
// Initialize the SSLContext with trust-all settings
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, new SecureRandom());
- // Disable hostname verification
- HostnameVerifier allHostsValid = (hostname, session) -> true;
-
- // Configure SSLConnectionSocketFactory with the trust-all SSLContext
+ // Use the NoopHostnameVerifier to disable hostname verification
SSLConnectionSocketFactory sslSocketFactory =
- new SSLConnectionSocketFactory(sslContext, allHostsValid);
+ new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
// Build and return the registry
return RegistryBuilder.create()
.register("https", sslSocketFactory)
.register("http", new PlainConnectionSocketFactory())
.build();
-
} catch (Exception e) {
String errorMessage = "Error while setting up trust-all SSL context.";
LOGGER.error(errorMessage, e);
throw new DatabricksException(errorMessage, e);
}
}
+
+ /**
+ * Creates a TrustManager array that accepts all certificates without validation. This should only
+ * be used in testing environments or when explicitly configured to allow self-signed
+ * certificates.
+ *
+ * @return An array containing a single TrustManager that trusts all certificates.
+ */
+ public static TrustManager[] getTrustManagerThatTrustsAllCertificates() {
+ return new TrustManager[] {
+ new X509TrustManager() {
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return new X509Certificate[0]; // Empty array instead of null for better compatibility
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] certs, String authType) {
+ // No-op: Trust all client certificates
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] certs, String authType) {
+ // No-op: Trust all server certificates
+ }
+ }
+ };
+ }
}
diff --git a/src/main/java/com/databricks/jdbc/dbclient/impl/common/ClientConfigurator.java b/src/main/java/com/databricks/jdbc/dbclient/impl/common/ClientConfigurator.java
index 10671926f8..d114973396 100644
--- a/src/main/java/com/databricks/jdbc/dbclient/impl/common/ClientConfigurator.java
+++ b/src/main/java/com/databricks/jdbc/dbclient/impl/common/ClientConfigurator.java
@@ -8,6 +8,7 @@
import com.databricks.jdbc.common.AuthMech;
import com.databricks.jdbc.common.DatabricksJdbcConstants;
import com.databricks.jdbc.common.util.DriverUtil;
+import com.databricks.jdbc.exception.DatabricksHttpException;
import com.databricks.jdbc.exception.DatabricksParsingException;
import com.databricks.jdbc.log.JdbcLogger;
import com.databricks.jdbc.log.JdbcLoggerFactory;
@@ -41,7 +42,8 @@ public class ClientConfigurator {
private final IDatabricksConnectionContext connectionContext;
private DatabricksConfig databricksConfig;
- public ClientConfigurator(IDatabricksConnectionContext connectionContext) {
+ public ClientConfigurator(IDatabricksConnectionContext connectionContext)
+ throws DatabricksHttpException {
this.connectionContext = connectionContext;
this.databricksConfig = new DatabricksConfig();
CommonsHttpClient.Builder httpClientBuilder = new CommonsHttpClient.Builder();
@@ -106,7 +108,8 @@ private static String createUniqueIdentifier(String host, String clientId, List<
*
* @param httpClientBuilder The builder to which the SSL configuration should be added.
*/
- void setupConnectionManager(CommonsHttpClient.Builder httpClientBuilder) {
+ void setupConnectionManager(CommonsHttpClient.Builder httpClientBuilder)
+ throws DatabricksHttpException {
PoolingHttpClientConnectionManager connManager =
ConfiguratorUtils.getBaseConnectionManager(connectionContext);
// Default value is 100 which is consistent with the value in the SDK
diff --git a/src/main/java/com/databricks/jdbc/dbclient/impl/common/ConfiguratorUtils.java b/src/main/java/com/databricks/jdbc/dbclient/impl/common/ConfiguratorUtils.java
index 3fcfbb23f5..a510b17e76 100644
--- a/src/main/java/com/databricks/jdbc/dbclient/impl/common/ConfiguratorUtils.java
+++ b/src/main/java/com/databricks/jdbc/dbclient/impl/common/ConfiguratorUtils.java
@@ -5,15 +5,17 @@
import com.databricks.jdbc.api.internal.IDatabricksConnectionContext;
import com.databricks.jdbc.common.DatabricksJdbcConstants;
import com.databricks.jdbc.common.util.SocketFactoryUtil;
+import com.databricks.jdbc.exception.DatabricksHttpException;
import com.databricks.jdbc.log.JdbcLogger;
import com.databricks.jdbc.log.JdbcLoggerFactory;
-import com.databricks.sdk.core.DatabricksException;
+import com.databricks.jdbc.model.telemetry.enums.DatabricksDriverErrorCode;
+import java.io.File;
import java.io.FileInputStream;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.KeyStore;
-import java.security.NoSuchAlgorithmException;
+import java.io.IOException;
+import java.security.*;
import java.security.cert.*;
import java.util.Arrays;
+import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import javax.net.ssl.*;
@@ -28,156 +30,331 @@
public class ConfiguratorUtils {
private static final JdbcLogger LOGGER = JdbcLoggerFactory.getLogger(ConfiguratorUtils.class);
- /**
- * @return Environment is either test or prod
- */
+ private static final String JAVA_TRUST_STORE_PATH_PROPERTY = "javax.net.ssl.trustStore";
+ private static final String JAVA_TRUST_STORE_PASSWORD_PROPERTY =
+ "javax.net.ssl.trustStorePassword";
+ private static final String JAVA_TRUST_STORE_TYPE_PROPERTY = "javax.net.ssl.trustStoreType";
+
private static boolean isJDBCTestEnv() {
return Boolean.parseBoolean(System.getenv(IS_JDBC_TEST_ENV));
}
/**
- * @param connectionContext The connection context to use to get the truststore and properties.
- * @return The connection manager based on the truststore and properties set in the connection
+ * Creates and configures the connection manager based on the connection context.
+ *
+ * @param connectionContext The connection context to use for configuration.
+ * @return A configured PoolingHttpClientConnectionManager.
+ * @throws DatabricksHttpException If there is an error during configuration.
*/
public static PoolingHttpClientConnectionManager getBaseConnectionManager(
- IDatabricksConnectionContext connectionContext) {
-
+ IDatabricksConnectionContext connectionContext) throws DatabricksHttpException {
+ // For test environments, use a trust-all socket factory
if (isJDBCTestEnv()) {
+ LOGGER.info("Using trust-all socket factory for JDBC test environment");
return new PoolingHttpClientConnectionManager(
SocketFactoryUtil.getTrustAllSocketFactoryRegistry());
}
- if (connectionContext.getSSLTrustStore() == null
- && connectionContext.checkCertificateRevocation()
- && !connectionContext.acceptUndeterminedCertificateRevocation()) {
- return new PoolingHttpClientConnectionManager();
- }
-
+ // For standard SSL configuration, create a custom socket factory registry
Registry socketFactoryRegistry =
- getConnectionSocketFactoryRegistry(connectionContext);
+ createConnectionSocketFactoryRegistry(connectionContext);
return new PoolingHttpClientConnectionManager(socketFactoryRegistry);
}
/**
- * This function returns the registry of connection socket factories based on the truststore and
- * properties set in the connection context.
+ * Creates a registry of connection socket factories based on the connection context.
+ *
+ * @param connectionContext The connection context to use for configuration.
+ * @return A configured Registry of ConnectionSocketFactory.
+ * @throws DatabricksHttpException If there is an error during configuration.
+ */
+ public static Registry createConnectionSocketFactoryRegistry(
+ IDatabricksConnectionContext connectionContext) throws DatabricksHttpException {
+
+ return createRegistryWithSystemOrDefaultTrustStore(connectionContext);
+ }
+
+ /**
+ * Creates a socket factory registry using either the system property trust store or JDK default.
*
- * @param connectionContext The connection context to use to get the truststore, certificate
- * revocation settings.
- * @return The registry of connection socket factories.
+ * @param connectionContext The connection context for configuration.
+ * @return A registry of connection socket factories.
+ * @throws DatabricksHttpException If there is an error during setup.
*/
- public static Registry getConnectionSocketFactoryRegistry(
- IDatabricksConnectionContext connectionContext) {
- // if truststore is not provided, null will use default truststore
- KeyStore trustStore = loadTruststoreOrNull(connectionContext);
- Set trustAnchors = getTrustAnchorsFromTrustStore(trustStore);
- // Build custom TrustManager based on above SSL trust store and certificate revocation settings
- // from context
+ private static Registry createRegistryWithSystemOrDefaultTrustStore(
+ IDatabricksConnectionContext connectionContext) throws DatabricksHttpException {
+
+ String sysTrustStore = null;
+ if (connectionContext.useSystemTrustStore()) {
+ // When useSystemTrustStore=true, check for javax.net.ssl.trustStore system property
+ sysTrustStore = System.getProperty(JAVA_TRUST_STORE_PATH_PROPERTY);
+ }
+
+ // If system property is set and useSystemTrustStore=true, use that trust store
+ if (sysTrustStore != null && !sysTrustStore.isEmpty()) {
+ return createRegistryWithSystemPropertyTrustStore(connectionContext, sysTrustStore);
+ }
+ // No system property set or useSystemTrustStore=false, use JDK's default trust store (cacerts)
+ else {
+ return createRegistryWithJdkDefaultTrustStore(connectionContext);
+ }
+ }
+
+ /**
+ * Creates a socket factory registry using the trust store specified by system property.
+ *
+ * @param connectionContext The connection context for configuration.
+ * @param sysTrustStore The path to the system property trust store.
+ * @return A registry of connection socket factories.
+ * @throws DatabricksHttpException If there is an error during setup.
+ */
+ private static Registry createRegistryWithSystemPropertyTrustStore(
+ IDatabricksConnectionContext connectionContext, String sysTrustStore)
+ throws DatabricksHttpException {
+
try {
- TrustManagerFactory customTrustManagerFactory =
- TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
- // Custom trust store and certificate revocation parameters are provided
- CertPathTrustManagerParameters trustManagerParameters =
- buildTrustManagerParameters(
+ LOGGER.info(
+ "Using system property javax.net.ssl.trustStore: "
+ + sysTrustStore
+ + " (This overrides the JDK's default cacerts store)");
+
+ // Load the system property trust store
+ File trustStoreFile = new File(sysTrustStore);
+ if (!trustStoreFile.exists()) {
+ String errorMessage = "System property trust store file does not exist: " + sysTrustStore;
+ handleError(errorMessage, new IOException(errorMessage));
+ }
+
+ // Load the system property trust store
+ KeyStore trustStore =
+ KeyStore.getInstance(System.getProperty(JAVA_TRUST_STORE_TYPE_PROPERTY, "JKS"));
+ char[] password = null;
+ String passwordProp = System.getProperty(JAVA_TRUST_STORE_PASSWORD_PROPERTY);
+ if (passwordProp != null) {
+ password = passwordProp.toCharArray();
+ }
+
+ try (FileInputStream fis = new FileInputStream(sysTrustStore)) {
+ trustStore.load(fis, password);
+ }
+
+ // Get trust anchors and create trust managers
+ Set trustAnchors = getTrustAnchorsFromTrustStore(trustStore);
+ return createRegistryFromTrustAnchors(
+ trustAnchors, connectionContext, "system property trust store: " + sysTrustStore);
+ } catch (DatabricksHttpException
+ | KeyStoreException
+ | NoSuchAlgorithmException
+ | CertificateException
+ | IOException e) {
+ handleError("Error while setting up system property trust store: " + sysTrustStore, e);
+ }
+ return null;
+ }
+
+ /**
+ * Creates a socket factory registry using the JDK's default trust store (cacerts).
+ *
+ * @param connectionContext The connection context for configuration.
+ * @return A registry of connection socket factories.
+ * @throws DatabricksHttpException If there is an error during setup.
+ */
+ private static Registry createRegistryWithJdkDefaultTrustStore(
+ IDatabricksConnectionContext connectionContext) throws DatabricksHttpException {
+
+ try {
+ if (connectionContext.useSystemTrustStore()) {
+ LOGGER.info(
+ "No system property trust store found, using JDK default trust store (cacerts)");
+ } else {
+ LOGGER.info(
+ "UseSystemTrustStore=false, using JDK default trust store (cacerts) and ignoring system properties");
+ }
+
+ Set systemTrustAnchors = getTrustAnchorsFromTrustStore(null);
+ return createRegistryFromTrustAnchors(
+ systemTrustAnchors, connectionContext, "JDK default trust store (cacerts)");
+ } catch (DatabricksHttpException e) {
+ handleError("Error while setting up JDK default trust store", e);
+ }
+ return null;
+ }
+
+ private static Registry createRegistryFromTrustAnchors(
+ Set trustAnchors,
+ IDatabricksConnectionContext connectionContext,
+ String sourceDescription)
+ throws DatabricksHttpException {
+ if (trustAnchors == null || trustAnchors.isEmpty()) {
+ throw new DatabricksHttpException(
+ sourceDescription + " contains no trust anchors",
+ DatabricksDriverErrorCode.SSL_HANDSHAKE_ERROR);
+ }
+
+ try {
+ TrustManager[] trustManagers =
+ createTrustManagers(
trustAnchors,
connectionContext.checkCertificateRevocation(),
connectionContext.acceptUndeterminedCertificateRevocation());
- customTrustManagerFactory.init(trustManagerParameters);
- SSLContext sslContext = SSLContext.getInstance(DatabricksJdbcConstants.TLS);
- sslContext.init(null, customTrustManagerFactory.getTrustManagers(), null);
- SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext);
- return RegistryBuilder.create()
- .register(DatabricksJdbcConstants.HTTPS, sslSocketFactory)
- .register(DatabricksJdbcConstants.HTTP, new PlainConnectionSocketFactory())
- .build();
+
+ return createSocketFactoryRegistry(trustManagers);
} catch (Exception e) {
- String errorMessage = "Error while building trust manager parameters";
- LOGGER.error(e, errorMessage);
- throw new DatabricksException(errorMessage, e);
+ handleError("Error setting up trust managers for " + sourceDescription, e);
}
+ return null;
+ }
+
+ /**
+ * Creates a socket factory registry with the provided trust managers.
+ *
+ * @param trustManagers The trust managers to use.
+ * @return A registry of connection socket factories.
+ * @throws NoSuchAlgorithmException If there is an error during SSL context creation.
+ * @throws KeyManagementException If there is an error during SSL context creation.
+ */
+ private static Registry createSocketFactoryRegistry(
+ TrustManager[] trustManagers) throws NoSuchAlgorithmException, KeyManagementException {
+
+ SSLContext sslContext = SSLContext.getInstance(DatabricksJdbcConstants.TLS);
+ sslContext.init(null, trustManagers, null);
+ SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext);
+
+ return RegistryBuilder.create()
+ .register(DatabricksJdbcConstants.HTTPS, sslSocketFactory)
+ .register(DatabricksJdbcConstants.HTTP, new PlainConnectionSocketFactory())
+ .build();
+ }
+
+ /**
+ * Creates trust managers based on the provided trust anchors and settings.
+ *
+ * @param trustAnchors The trust anchors to use.
+ * @param checkCertificateRevocation Whether to check certificate revocation.
+ * @param acceptUndeterminedCertificateRevocation Whether to accept undetermined revocation
+ * status.
+ * @return An array of trust managers.
+ * @throws NoSuchAlgorithmException If there is an error during trust manager creation.
+ * @throws InvalidAlgorithmParameterException If there is an error during trust manager creation.
+ */
+ private static TrustManager[] createTrustManagers(
+ Set trustAnchors,
+ boolean checkCertificateRevocation,
+ boolean acceptUndeterminedCertificateRevocation)
+ throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, DatabricksHttpException {
+
+ // Always use the custom trust manager with trust anchors
+ CertPathTrustManagerParameters trustManagerParams =
+ buildTrustManagerParameters(
+ trustAnchors, checkCertificateRevocation, acceptUndeterminedCertificateRevocation);
+
+ TrustManagerFactory customTmf =
+ TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ customTmf.init(trustManagerParams);
+
+ LOGGER.info("Certificate revocation check: " + checkCertificateRevocation);
+ return customTmf.getTrustManagers();
}
/**
- * @param connectionContext The connection context to use to get the truststore.
- * @return The truststore loaded from the connection context or null if the truststore is not set.
+ * Finds the X509TrustManager in an array of TrustManager objects.
+ *
+ * @param trustManagers Array of TrustManager objects to search.
+ * @return The X509TrustManager if found, null otherwise.
*/
- public static KeyStore loadTruststoreOrNull(IDatabricksConnectionContext connectionContext) {
- if (connectionContext.getSSLTrustStore() == null) {
+ private static X509TrustManager findX509TrustManager(TrustManager[] trustManagers) {
+ if (trustManagers == null) {
return null;
}
- // Flow to provide custom SSL truststore
- try {
- try (FileInputStream trustStoreStream =
- new FileInputStream(connectionContext.getSSLTrustStore())) {
- char[] password = null;
- if (connectionContext.getSSLTrustStorePassword() != null) {
- password = connectionContext.getSSLTrustStorePassword().toCharArray();
- }
- KeyStore trustStore = KeyStore.getInstance(connectionContext.getSSLTrustStoreType());
- trustStore.load(trustStoreStream, password);
- return trustStore;
+
+ for (TrustManager tm : trustManagers) {
+ if (tm instanceof X509TrustManager) {
+ return (X509TrustManager) tm;
}
- } catch (Exception e) {
- String errorMessage = "Error while loading truststore";
- LOGGER.error(e, errorMessage);
- throw new DatabricksException(errorMessage, e);
}
+
+ return null;
}
/**
- * @param trustAnchors The trust anchors to use in the trust manager.
- * @param checkCertificateRevocation Whether to check certificate revocation.
- * @param acceptUndeterminedCertificateRevocation Whether to accept undetermined certificate
- * @return The trust manager parameters based on the input parameters.
+ * Extracts trust anchors from a KeyStore.
+ *
+ * @param trustStore The KeyStore from which to extract trust anchors.
+ * @return A Set of TrustAnchor objects extracted from the KeyStore.
+ * @throws DatabricksHttpException If there is an error during extraction.
*/
+ public static Set getTrustAnchorsFromTrustStore(KeyStore trustStore)
+ throws DatabricksHttpException {
+ try {
+ TrustManagerFactory trustManagerFactory =
+ TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ trustManagerFactory.init(trustStore);
+
+ // Get the trust managers
+ TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
+ X509TrustManager x509TrustManager = findX509TrustManager(trustManagers);
+
+ if (x509TrustManager == null || x509TrustManager.getAcceptedIssuers().length == 0) {
+ // No trust anchors found
+ return Collections.emptySet();
+ }
+
+ return Arrays.stream(x509TrustManager.getAcceptedIssuers())
+ .map(cert -> new TrustAnchor(cert, null))
+ .collect(Collectors.toSet());
+ } catch (KeyStoreException | NoSuchAlgorithmException e) {
+ handleError("Error while getting trust anchors from trust store: " + e.getMessage(), e);
+ }
+ return Collections.emptySet();
+ }
+
public static CertPathTrustManagerParameters buildTrustManagerParameters(
Set trustAnchors,
boolean checkCertificateRevocation,
- boolean acceptUndeterminedCertificateRevocation) {
+ boolean acceptUndeterminedCertificateRevocation)
+ throws DatabricksHttpException {
try {
PKIXBuilderParameters pkixBuilderParameters =
new PKIXBuilderParameters(trustAnchors, new X509CertSelector());
pkixBuilderParameters.setRevocationEnabled(checkCertificateRevocation);
- CertPathValidator certPathValidator =
- CertPathValidator.getInstance(DatabricksJdbcConstants.PKIX);
- PKIXRevocationChecker revocationChecker =
- (PKIXRevocationChecker) certPathValidator.getRevocationChecker();
- if (acceptUndeterminedCertificateRevocation) {
- revocationChecker.setOptions(
- Set.of(
- PKIXRevocationChecker.Option.SOFT_FAIL,
- PKIXRevocationChecker.Option.NO_FALLBACK,
- PKIXRevocationChecker.Option.PREFER_CRLS));
- }
+
if (checkCertificateRevocation) {
+ CertPathValidator certPathValidator =
+ CertPathValidator.getInstance(DatabricksJdbcConstants.PKIX);
+ PKIXRevocationChecker revocationChecker =
+ (PKIXRevocationChecker) certPathValidator.getRevocationChecker();
+
+ if (acceptUndeterminedCertificateRevocation) {
+ revocationChecker.setOptions(
+ Set.of(
+ PKIXRevocationChecker.Option.SOFT_FAIL,
+ PKIXRevocationChecker.Option.NO_FALLBACK,
+ PKIXRevocationChecker.Option.PREFER_CRLS));
+ }
+ LOGGER.info(
+ "Certificate revocation enabled. Undetermined revocation accepted: "
+ + acceptUndeterminedCertificateRevocation);
+
pkixBuilderParameters.addCertPathChecker(revocationChecker);
}
+
return new CertPathTrustManagerParameters(pkixBuilderParameters);
} catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
- String errorMessage = "Error while building trust manager parameters";
- LOGGER.error(e, errorMessage);
- throw new DatabricksException(errorMessage, e);
+ handleError("Error while building trust manager parameters: " + e.getMessage(), e);
}
+ return null;
}
/**
- * @param trustStore The trust store from which to get the trust anchors.
- * @return The set of trust anchors from the trust store.
+ * Centralized error handling method for logging and throwing exceptions.
+ *
+ * @param errorMessage The error message to log.
+ * @param e The exception to log and throw.
+ * @throws DatabricksHttpException The wrapped exception.
*/
- public static Set getTrustAnchorsFromTrustStore(KeyStore trustStore) {
- try {
- TrustManagerFactory trustManagerFactory =
- TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
- trustManagerFactory.init(trustStore);
- X509TrustManager trustManager = (X509TrustManager) trustManagerFactory.getTrustManagers()[0];
- X509Certificate[] certs = trustManager.getAcceptedIssuers();
- return Arrays.stream(certs)
- .map(cert -> new TrustAnchor(cert, null))
- .collect(Collectors.toSet());
- } catch (Exception e) {
- String errorMessage = "Error while getting trust anchors from trust store";
- LOGGER.error(e, errorMessage);
- throw new DatabricksException(errorMessage, e);
- }
+ private static void handleError(String errorMessage, Exception e) throws DatabricksHttpException {
+ LOGGER.error(errorMessage, e);
+ throw new DatabricksHttpException(
+ errorMessage, e, DatabricksDriverErrorCode.SSL_HANDSHAKE_ERROR);
}
}
diff --git a/src/main/java/com/databricks/jdbc/dbclient/impl/http/DatabricksHttpClient.java b/src/main/java/com/databricks/jdbc/dbclient/impl/http/DatabricksHttpClient.java
index 6f913c1fc9..6c33ea9541 100644
--- a/src/main/java/com/databricks/jdbc/dbclient/impl/http/DatabricksHttpClient.java
+++ b/src/main/java/com/databricks/jdbc/dbclient/impl/http/DatabricksHttpClient.java
@@ -125,11 +125,19 @@ public void close() throws IOException {
private PoolingHttpClientConnectionManager initializeConnectionManager(
IDatabricksConnectionContext connectionContext) {
- PoolingHttpClientConnectionManager connectionManager =
- ConfiguratorUtils.getBaseConnectionManager(connectionContext);
- connectionManager.setMaxTotal(DEFAULT_MAX_HTTP_CONNECTIONS);
- connectionManager.setDefaultMaxPerRoute(DEFAULT_MAX_HTTP_CONNECTIONS_PER_ROUTE);
- return connectionManager;
+ try {
+ PoolingHttpClientConnectionManager connectionManager =
+ ConfiguratorUtils.getBaseConnectionManager(connectionContext);
+ connectionManager.setMaxTotal(DEFAULT_MAX_HTTP_CONNECTIONS);
+ connectionManager.setDefaultMaxPerRoute(DEFAULT_MAX_HTTP_CONNECTIONS_PER_ROUTE);
+ return connectionManager;
+ } catch (DatabricksHttpException e) {
+ LOGGER.error("Failed to initialize HTTP connection manager", e);
+ // Currently only SSL Handshake failure causes this exception.
+ throw new DatabricksDriverException(
+ "Failed to initialize HTTP connection manager",
+ DatabricksDriverErrorCode.SSL_HANDSHAKE_ERROR);
+ }
}
private RequestConfig makeRequestConfig(int timeoutSeconds) {
diff --git a/src/main/java/com/databricks/jdbc/dbclient/impl/sqlexec/DatabricksSdkClient.java b/src/main/java/com/databricks/jdbc/dbclient/impl/sqlexec/DatabricksSdkClient.java
index 6a39539904..6af87973ee 100644
--- a/src/main/java/com/databricks/jdbc/dbclient/impl/sqlexec/DatabricksSdkClient.java
+++ b/src/main/java/com/databricks/jdbc/dbclient/impl/sqlexec/DatabricksSdkClient.java
@@ -58,7 +58,7 @@ public class DatabricksSdkClient implements IDatabricksClient {
private volatile ApiClient apiClient;
public DatabricksSdkClient(IDatabricksConnectionContext connectionContext)
- throws DatabricksParsingException {
+ throws DatabricksParsingException, DatabricksHttpException {
this.connectionContext = connectionContext;
this.clientConfigurator = new ClientConfigurator(connectionContext);
this.workspaceClient = clientConfigurator.getWorkspaceClient();
@@ -70,7 +70,7 @@ public DatabricksSdkClient(
IDatabricksConnectionContext connectionContext,
StatementExecutionService statementExecutionService,
ApiClient apiClient)
- throws DatabricksParsingException {
+ throws DatabricksParsingException, DatabricksHttpException {
this.connectionContext = connectionContext;
this.clientConfigurator = new ClientConfigurator(connectionContext);
this.workspaceClient =
diff --git a/src/main/java/com/databricks/jdbc/dbclient/impl/thrift/DatabricksThriftAccessor.java b/src/main/java/com/databricks/jdbc/dbclient/impl/thrift/DatabricksThriftAccessor.java
index 402292bfd3..7bd6af894b 100644
--- a/src/main/java/com/databricks/jdbc/dbclient/impl/thrift/DatabricksThriftAccessor.java
+++ b/src/main/java/com/databricks/jdbc/dbclient/impl/thrift/DatabricksThriftAccessor.java
@@ -57,7 +57,7 @@ final class DatabricksThriftAccessor {
private TProtocolVersion serverProtocolVersion = JDBC_THRIFT_VERSION;
DatabricksThriftAccessor(IDatabricksConnectionContext connectionContext)
- throws DatabricksParsingException {
+ throws DatabricksParsingException, DatabricksHttpException {
this.enableDirectResults = connectionContext.getDirectResultMode();
this.databricksConfig = new ClientConfigurator(connectionContext).getDatabricksConfig();
String endPointUrl = connectionContext.getEndpointURL();
diff --git a/src/main/java/com/databricks/jdbc/dbclient/impl/thrift/DatabricksThriftServiceClient.java b/src/main/java/com/databricks/jdbc/dbclient/impl/thrift/DatabricksThriftServiceClient.java
index a3e20b633e..a74294488a 100644
--- a/src/main/java/com/databricks/jdbc/dbclient/impl/thrift/DatabricksThriftServiceClient.java
+++ b/src/main/java/com/databricks/jdbc/dbclient/impl/thrift/DatabricksThriftServiceClient.java
@@ -21,6 +21,7 @@
import com.databricks.jdbc.dbclient.IDatabricksMetadataClient;
import com.databricks.jdbc.dbclient.impl.common.MetadataResultSetBuilder;
import com.databricks.jdbc.dbclient.impl.common.StatementId;
+import com.databricks.jdbc.exception.DatabricksHttpException;
import com.databricks.jdbc.exception.DatabricksParsingException;
import com.databricks.jdbc.exception.DatabricksSQLException;
import com.databricks.jdbc.log.JdbcLogger;
@@ -45,7 +46,7 @@ public class DatabricksThriftServiceClient implements IDatabricksClient, IDatabr
private TProtocolVersion serverProtocolVersion = JDBC_THRIFT_VERSION;
public DatabricksThriftServiceClient(IDatabricksConnectionContext connectionContext)
- throws DatabricksParsingException {
+ throws DatabricksParsingException, DatabricksHttpException {
this.connectionContext = connectionContext;
this.thriftAccessor = new DatabricksThriftAccessor(connectionContext);
}
diff --git a/src/test/java/com/databricks/client/jdbc/SSLTest.java b/src/test/java/com/databricks/client/jdbc/SSLTest.java
new file mode 100644
index 0000000000..b55fd86036
--- /dev/null
+++ b/src/test/java/com/databricks/client/jdbc/SSLTest.java
@@ -0,0 +1,229 @@
+package com.databricks.client.jdbc;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.Statement;
+import java.util.logging.Logger;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+public class SSLTest {
+
+ private static final Logger LOGGER = Logger.getLogger(SSLTest.class.getName());
+ private static String patToken;
+ private static String host;
+ private static String httpPath;
+ private static String httpProxyUrl;
+ private static String httpsProxyUrl;
+ private static String trustStorePath;
+ private static String trustStorePassword;
+
+ @BeforeAll
+ public static void setupEnv() {
+ patToken = System.getenv("DATABRICKS_TOKEN");
+ host = System.getenv("DATABRICKS_HOST");
+ httpPath = System.getenv("DATABRICKS_HTTP_PATH");
+ httpProxyUrl = System.getenv("HTTP_PROXY_URL");
+ httpsProxyUrl = System.getenv("HTTPS_PROXY_URL");
+ trustStorePath = System.getenv("TRUSTSTORE_PATH");
+ trustStorePassword = System.getenv("TRUSTSTORE_PASSWORD");
+ }
+
+ private String buildJdbcUrl(
+ boolean useThriftClient,
+ boolean useProxy,
+ boolean useHttpsProxy,
+ boolean allowSelfSignedCerts,
+ boolean useSystemTrustStore,
+ boolean useCustomTrustStore) {
+
+ String defaultProxyHost = "localhost";
+ String defaultProxyPort = "3128";
+ if (httpProxyUrl != null && httpProxyUrl.startsWith("http")) {
+ String trimmed = httpProxyUrl.replace("http://", "").replace("https://", "");
+ String[] parts = trimmed.split(":");
+ if (parts.length > 1) {
+ defaultProxyHost = parts[0];
+ defaultProxyPort = parts[1];
+ }
+ }
+
+ String defaultHttpsProxyHost = "localhost";
+ String defaultHttpsProxyPort = "3129";
+ if (httpsProxyUrl != null && httpsProxyUrl.startsWith("http")) {
+ String trimmed = httpsProxyUrl.replace("http://", "").replace("https://", "");
+ String[] parts = trimmed.split(":");
+ if (parts.length > 1) {
+ defaultHttpsProxyHost = parts[0];
+ defaultHttpsProxyPort = parts[1];
+ }
+ }
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("jdbc:databricks://")
+ .append(host)
+ .append("/default")
+ .append(";httpPath=")
+ .append(httpPath)
+ .append(";AuthMech=3")
+ .append(";usethriftclient=")
+ .append(useThriftClient ? "true" : "false")
+ .append(";");
+
+ if (useProxy) {
+ sb.append("useproxy=1;")
+ .append("ProxyHost=")
+ .append(defaultProxyHost)
+ .append(";")
+ .append("ProxyPort=")
+ .append(defaultProxyPort)
+ .append(";");
+ } else {
+ sb.append("useproxy=0;");
+ }
+
+ if (useHttpsProxy) {
+ sb.append("ProxyHost=")
+ .append(defaultHttpsProxyHost)
+ .append(";")
+ .append("ProxyPort=")
+ .append(defaultHttpsProxyPort)
+ .append(";");
+ }
+
+ sb.append("AllowSelfSignedCerts=")
+ .append(allowSelfSignedCerts ? "1" : "0")
+ .append(";")
+ .append("UseSystemTrustStore=")
+ .append(useSystemTrustStore ? "1" : "0")
+ .append(";");
+
+ if (useCustomTrustStore && trustStorePath != null && !trustStorePath.isEmpty()) {
+ sb.append("SSLTrustStore=").append(trustStorePath).append(";");
+
+ if (trustStorePassword != null && !trustStorePassword.isEmpty()) {
+ sb.append("SSLTrustStorePwd=").append(trustStorePassword).append(";");
+ // Add trust store type when we know it
+ sb.append("SSLTrustStoreType=").append("JKS").append(";");
+ }
+ }
+
+ sb.append("ssl=1;");
+ return sb.toString();
+ }
+
+ private void verifyConnect(String jdbcUrl) throws Exception {
+ LOGGER.info("Attempting to connect with URL: " + jdbcUrl);
+
+ try (Connection conn = DriverManager.getConnection(jdbcUrl, "token", patToken)) {
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery("SELECT 1");
+ assertTrue(rs.next(), "Should get at least one row");
+ assertEquals(1, rs.getInt(1), "Value should be 1");
+ LOGGER.info("Success!");
+ }
+ }
+
+ @Test
+ public void testDirectConnectionDefaultSSL() {
+ LOGGER.info("Scenario: Direct connection with default SSL settings");
+ for (boolean thrift : new boolean[] {true, false}) {
+ String url = buildJdbcUrl(thrift, false, false, false, false, false);
+ try {
+ verifyConnect(url);
+ } catch (Exception e) {
+ fail("Direct connection test failed (thrift=" + thrift + "): " + e.getMessage());
+ }
+ }
+ }
+
+ @Test
+ public void testHttpProxyDefaultSSL() {
+ LOGGER.info("Scenario: HTTP Proxy with default SSL settings");
+ for (boolean thrift : new boolean[] {true, false}) {
+ String url = buildJdbcUrl(thrift, true, false, false, false, false);
+ try {
+ verifyConnect(url);
+ } catch (Exception e) {
+ fail("HTTP proxy test failed (thrift=" + thrift + "): " + e.getMessage());
+ }
+ }
+ }
+
+ @Test
+ public void testWithSystemTrustStore() {
+ LOGGER.info("Scenario: Testing with UseSystemTrustStore=1");
+ for (boolean thrift : new boolean[] {true, false}) {
+ String url = buildJdbcUrl(thrift, true, false, false, true, false);
+ try {
+ verifyConnect(url);
+ } catch (Exception e) {
+ fail("UseSystemTrustStore=1 test failed (thrift=" + thrift + "): " + e.getMessage());
+ }
+ }
+ }
+
+ @Test
+ public void testDirectConnectionSystemTrustStoreFallback() {
+ LOGGER.info(
+ "Scenario: UseSystemTrustStore=1 with no system property -> fallback to cacerts (direct)");
+
+ // ensure the property is *unset* for this test run
+ String savedProp = System.getProperty("javax.net.ssl.trustStore");
+ try {
+ System.clearProperty("javax.net.ssl.trustStore");
+
+ for (boolean thrift : new boolean[] {true, false}) {
+ String url = buildJdbcUrl(thrift, false, false, false, true, false);
+ try {
+ verifyConnect(url);
+ } catch (Exception e) {
+ fail(
+ "Fallback‑to‑cacerts direct connect failed (thrift="
+ + thrift
+ + "): "
+ + e.getMessage());
+ }
+ }
+ } finally {
+ // restore original system state
+ if (savedProp != null) {
+ System.setProperty("javax.net.ssl.trustStore", savedProp);
+ }
+ }
+ }
+
+ @Test
+ public void testIgnoreSystemPropertyWhenUseSystemTrustStoreDisabled() {
+ LOGGER.info(
+ "Scenario: bogus javax.net.ssl.trustStore present but UseSystemTrustStore=0 (driver must ignore)");
+
+ String savedProp = System.getProperty("javax.net.ssl.trustStore");
+ try {
+ System.setProperty("javax.net.ssl.trustStore", "/path/that/does/not/exist.jks");
+
+ for (boolean thrift : new boolean[] {true, false}) {
+ String url = buildJdbcUrl(thrift, false, false, false, false, false);
+ try {
+ verifyConnect(url);
+ } catch (Exception e) {
+ fail(
+ "Driver failed to ignore bogus system trust store (thrift="
+ + thrift
+ + "): "
+ + e.getMessage());
+ }
+ }
+ } finally {
+ // restore original value
+ if (savedProp != null) {
+ System.setProperty("javax.net.ssl.trustStore", savedProp);
+ } else {
+ System.clearProperty("javax.net.ssl.trustStore");
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/databricks/jdbc/dbclient/impl/common/ClientConfiguratorTest.java b/src/test/java/com/databricks/jdbc/dbclient/impl/common/ClientConfiguratorTest.java
index 2be91691b2..f37cf4202b 100644
--- a/src/test/java/com/databricks/jdbc/dbclient/impl/common/ClientConfiguratorTest.java
+++ b/src/test/java/com/databricks/jdbc/dbclient/impl/common/ClientConfiguratorTest.java
@@ -12,6 +12,7 @@
import com.databricks.jdbc.common.AuthFlow;
import com.databricks.jdbc.common.AuthMech;
import com.databricks.jdbc.common.DatabricksJdbcConstants;
+import com.databricks.jdbc.exception.DatabricksHttpException;
import com.databricks.jdbc.exception.DatabricksParsingException;
import com.databricks.jdbc.exception.DatabricksSQLException;
import com.databricks.sdk.WorkspaceClient;
@@ -38,7 +39,8 @@ public class ClientConfiguratorTest {
private ClientConfigurator configurator;
@Test
- void getWorkspaceClient_PAT_AuthenticatesWithAccessToken() throws DatabricksParsingException {
+ void getWorkspaceClient_PAT_AuthenticatesWithAccessToken()
+ throws DatabricksParsingException, DatabricksHttpException {
when(mockContext.getAuthMech()).thenReturn(AuthMech.PAT);
when(mockContext.getHostUrl()).thenReturn("https://pat.databricks.com");
when(mockContext.getToken()).thenReturn("pat-token");
@@ -56,7 +58,7 @@ void getWorkspaceClient_PAT_AuthenticatesWithAccessToken() throws DatabricksPars
@Test
void getWorkspaceClient_OAuthWithTokenPassthrough_AuthenticatesCorrectly()
- throws DatabricksParsingException {
+ throws DatabricksParsingException, DatabricksHttpException {
when(mockContext.getAuthMech()).thenReturn(AuthMech.OAUTH);
when(mockContext.getAuthFlow()).thenReturn(AuthFlow.TOKEN_PASSTHROUGH);
when(mockContext.getHostUrl()).thenReturn("https://oauth-token.databricks.com");
@@ -75,7 +77,7 @@ void getWorkspaceClient_OAuthWithTokenPassthrough_AuthenticatesCorrectly()
@Test
void getWorkspaceClient_OAuthWithClientCredentials_AuthenticatesCorrectly()
- throws DatabricksParsingException {
+ throws DatabricksParsingException, DatabricksHttpException {
when(mockContext.getAuthMech()).thenReturn(AuthMech.OAUTH);
when(mockContext.getAuthFlow()).thenReturn(AuthFlow.CLIENT_CREDENTIALS);
when(mockContext.getHostForOAuth()).thenReturn("https://oauth-client.databricks.com");
@@ -96,7 +98,7 @@ void getWorkspaceClient_OAuthWithClientCredentials_AuthenticatesCorrectly()
@Test
void getWorkspaceClient_OAuthWithClientCredentials_AuthenticatesCorrectlyGCP()
- throws DatabricksParsingException {
+ throws DatabricksParsingException, DatabricksHttpException {
when(mockContext.getAuthMech()).thenReturn(AuthMech.OAUTH);
when(mockContext.getAuthFlow()).thenReturn(AuthFlow.CLIENT_CREDENTIALS);
when(mockContext.getHostForOAuth()).thenReturn("https://oauth-client.databricks.com");
@@ -116,7 +118,7 @@ void getWorkspaceClient_OAuthWithClientCredentials_AuthenticatesCorrectlyGCP()
@Test
void getWorkspaceClient_OAuthWithClientCredentials_AuthenticatesCorrectlyWithJWT()
- throws DatabricksParsingException {
+ throws DatabricksParsingException, DatabricksHttpException {
when(mockContext.getConnectionUuid()).thenReturn("connection-uuid");
when(mockContext.getAuthMech()).thenReturn(AuthMech.OAUTH);
when(mockContext.getAuthFlow()).thenReturn(AuthFlow.CLIENT_CREDENTIALS);
@@ -163,7 +165,7 @@ void testM2MWithJWT() throws DatabricksSQLException {
@Test
void getWorkspaceClient_OAuthWithBrowserBasedAuthentication_AuthenticatesCorrectly()
- throws DatabricksParsingException {
+ throws DatabricksParsingException, DatabricksHttpException {
when(mockContext.getAuthMech()).thenReturn(AuthMech.OAUTH);
when(mockContext.getAuthFlow()).thenReturn(AuthFlow.BROWSER_BASED_AUTHENTICATION);
when(mockContext.getHostForOAuth()).thenReturn("https://oauth-browser.databricks.com");
@@ -188,7 +190,7 @@ void getWorkspaceClient_OAuthWithBrowserBasedAuthentication_AuthenticatesCorrect
@Test
void
getWorkspaceClient_OAuthWithBrowserBasedAuthentication_WithDiscoveryURL_AuthenticatesCorrectly()
- throws DatabricksParsingException, IOException {
+ throws DatabricksParsingException, IOException, DatabricksHttpException {
when(mockContext.getAuthMech()).thenReturn(AuthMech.OAUTH);
when(mockContext.getAuthFlow()).thenReturn(AuthFlow.BROWSER_BASED_AUTHENTICATION);
when(mockContext.getHostForOAuth()).thenReturn("https://oauth-browser.databricks.com");
@@ -214,7 +216,7 @@ void getWorkspaceClient_OAuthWithBrowserBasedAuthentication_AuthenticatesCorrect
}
@Test
- void testNonOauth() {
+ void testNonOauth() throws DatabricksHttpException {
when(mockContext.getAuthMech()).thenReturn(AuthMech.OTHER);
when(mockContext.getHttpConnectionPoolSize()).thenReturn(100);
configurator = new ClientConfigurator(mockContext);
@@ -244,7 +246,7 @@ void testNonProxyHostsFormatConversion() {
}
@Test
- void testSetupProxyConfig() {
+ void testSetupProxyConfig() throws DatabricksHttpException {
when(mockContext.getAuthMech()).thenReturn(AuthMech.PAT);
when(mockContext.getUseProxy()).thenReturn(true);
when(mockContext.getProxyHost()).thenReturn("proxy.host.com");
@@ -274,7 +276,8 @@ void testSetupProxyConfig() {
}
@Test
- void setupM2MConfig_WithAzureTenantId_ConfiguresCorrectly() throws DatabricksParsingException {
+ void setupM2MConfig_WithAzureTenantId_ConfiguresCorrectly()
+ throws DatabricksParsingException, DatabricksHttpException {
when(mockContext.getAuthMech()).thenReturn(AuthMech.OAUTH);
when(mockContext.getAuthFlow()).thenReturn(AuthFlow.CLIENT_CREDENTIALS);
when(mockContext.getHostForOAuth()).thenReturn("https://azure-oauth.databricks.com");
@@ -437,7 +440,8 @@ void getWorkspaceClient_OAuthWithBrowserBasedAuthentication_SetsCustomRedirectUr
}
@Test
- void testSetupU2MConfig_WithTokenCache() throws DatabricksParsingException {
+ void testSetupU2MConfig_WithTokenCache()
+ throws DatabricksParsingException, DatabricksHttpException {
when(mockContext.getAuthMech()).thenReturn(AuthMech.OAUTH);
when(mockContext.getAuthFlow()).thenReturn(AuthFlow.BROWSER_BASED_AUTHENTICATION);
when(mockContext.getHostForOAuth()).thenReturn("https://oauth-browser.databricks.com");
@@ -480,7 +484,8 @@ void testSetupU2MConfig_WithTokenCacheNoPassphrase() throws DatabricksParsingExc
}
@Test
- void testSetupU2MConfig_WithoutTokenCache() throws DatabricksParsingException {
+ void testSetupU2MConfig_WithoutTokenCache()
+ throws DatabricksParsingException, DatabricksHttpException {
when(mockContext.getAuthMech()).thenReturn(AuthMech.OAUTH);
when(mockContext.getAuthFlow()).thenReturn(AuthFlow.BROWSER_BASED_AUTHENTICATION);
when(mockContext.getHostForOAuth()).thenReturn("https://oauth-browser.databricks.com");
diff --git a/src/test/java/com/databricks/jdbc/dbclient/impl/common/ConfiguratorUtilsTest.java b/src/test/java/com/databricks/jdbc/dbclient/impl/common/ConfiguratorUtilsTest.java
index 1887bba744..d578dce994 100644
--- a/src/test/java/com/databricks/jdbc/dbclient/impl/common/ConfiguratorUtilsTest.java
+++ b/src/test/java/com/databricks/jdbc/dbclient/impl/common/ConfiguratorUtilsTest.java
@@ -5,9 +5,9 @@
import com.databricks.jdbc.api.internal.IDatabricksConnectionContext;
import com.databricks.jdbc.common.DatabricksJdbcConstants;
+import com.databricks.jdbc.exception.DatabricksHttpException;
import com.databricks.jdbc.log.JdbcLogger;
import com.databricks.jdbc.log.JdbcLoggerFactory;
-import com.databricks.sdk.core.DatabricksException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
@@ -17,8 +17,12 @@
import java.security.cert.CertificateException;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
+import java.util.Collections;
import java.util.Date;
import java.util.Set;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
import org.apache.http.config.Registry;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
@@ -60,24 +64,20 @@ static void setup() throws Exception {
private static void createEmptyTrustStore()
throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException {
- String password = TRUST_STORE_PASSWORD;
// Create an empty JKS keystore
KeyStore keyStore = KeyStore.getInstance(TRUST_STORE_TYPE);
- keyStore.load(null, password.toCharArray());
+ keyStore.load(null, TRUST_STORE_PASSWORD.toCharArray());
// Save the empty keystore to a file
try (FileOutputStream fos = new FileOutputStream(EMPTY_TRUST_STORE_PATH)) {
- keyStore.store(fos, password.toCharArray());
+ keyStore.store(fos, TRUST_STORE_PASSWORD.toCharArray());
}
}
private static void createDummyTrustStore() throws Exception {
- String trustStorePassword = TRUST_STORE_PASSWORD; // Password for the trust store
- String alias = "dummy-cert"; // Alias for the dummy certificate
-
// Create an empty JKS keystore
KeyStore keyStore = KeyStore.getInstance(TRUST_STORE_TYPE);
- keyStore.load(null, trustStorePassword.toCharArray());
+ keyStore.load(null, TRUST_STORE_PASSWORD.toCharArray());
// Generate a key pair (public and private keys)
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
@@ -86,13 +86,9 @@ private static void createDummyTrustStore() throws Exception {
// Create a self-signed certificate
X509Certificate certificate = generateBarebonesCertificate(keyPair);
-
- // Add the certificate to the keystore
- keyStore.setCertificateEntry(alias, certificate);
-
- // Save the keystore to a file
+ keyStore.setCertificateEntry("dummy-cert", certificate);
try (FileOutputStream fos = new FileOutputStream(DUMMY_TRUST_STORE_PATH)) {
- keyStore.store(fos, trustStorePassword.toCharArray());
+ keyStore.store(fos, TRUST_STORE_PASSWORD.toCharArray());
}
}
@@ -134,55 +130,22 @@ static void cleanup() {
}
@Test
- void testGetConnectionSocketFactoryRegistry() {
- when(mockContext.getSSLTrustStorePassword()).thenReturn(TRUST_STORE_PASSWORD);
- when(mockContext.getSSLTrustStoreType()).thenReturn(TRUST_STORE_TYPE);
- when(mockContext.getSSLTrustStore()).thenReturn(EMPTY_TRUST_STORE_PATH);
- assertThrows(
- DatabricksException.class,
- () -> ConfiguratorUtils.getConnectionSocketFactoryRegistry(mockContext),
- "the trustAnchors parameter must be non-empty");
-
- when(mockContext.getSSLTrustStore()).thenReturn(DUMMY_TRUST_STORE_PATH);
- Registry registry =
- ConfiguratorUtils.getConnectionSocketFactoryRegistry(mockContext);
- assertInstanceOf(
- SSLConnectionSocketFactory.class, registry.lookup(DatabricksJdbcConstants.HTTPS));
- assertInstanceOf(
- PlainConnectionSocketFactory.class, registry.lookup(DatabricksJdbcConstants.HTTP));
- }
-
- @Test
- void testGetTrustAnchorsFromTrustStore() {
- when(mockContext.getSSLTrustStorePassword()).thenReturn(TRUST_STORE_PASSWORD);
- when(mockContext.getSSLTrustStoreType()).thenReturn(TRUST_STORE_TYPE);
- when(mockContext.getSSLTrustStore()).thenReturn(DUMMY_TRUST_STORE_PATH);
- KeyStore trustStore = ConfiguratorUtils.loadTruststoreOrNull(mockContext);
- Set trustAnchors = ConfiguratorUtils.getTrustAnchorsFromTrustStore(trustStore);
- assertTrue(
- trustAnchors.stream()
- .anyMatch(ta -> ta.getTrustedCert().getIssuerDN().toString().contains(CERTIFICATE_CN)));
- }
-
- @Test
- void testGetBaseConnectionManager_NoSSLTrustStoreAndRevocationCheckEnabled() {
- // Define behavior for mock context to meet conditions for not calling
- // getConnectionSocketFactoryRegistry
- when(mockContext.getSSLTrustStore()).thenReturn(null);
+ void testGetBaseConnectionManager_NoSSLTrustStoreAndRevocationCheckEnabled()
+ throws DatabricksHttpException {
+ // Define behavior for mock context
when(mockContext.checkCertificateRevocation()).thenReturn(true);
when(mockContext.acceptUndeterminedCertificateRevocation()).thenReturn(false);
+ when(mockContext.useSystemTrustStore()).thenReturn(false);
+
+ try (MockedStatic configuratorUtils =
+ mockStatic(ConfiguratorUtils.class, withSettings().defaultAnswer(CALLS_REAL_METHODS))) {
- try (MockedStatic configuratorUtils = mockStatic(ConfiguratorUtils.class)) {
- configuratorUtils
- .when(() -> ConfiguratorUtils.getBaseConnectionManager(mockContext))
- .thenCallRealMethod();
// Call getBaseConnectionManager with the mock context
PoolingHttpClientConnectionManager connManager =
ConfiguratorUtils.getBaseConnectionManager(mockContext);
- // Assert that getConnectionSocketFactoryRegistry was NOT called
configuratorUtils.verify(
- () -> ConfiguratorUtils.getConnectionSocketFactoryRegistry(mockContext), never());
+ () -> ConfiguratorUtils.createConnectionSocketFactoryRegistry(any()), times(1));
// Ensure the returned connection manager is not null
assertNotNull(connManager);
@@ -190,16 +153,13 @@ void testGetBaseConnectionManager_NoSSLTrustStoreAndRevocationCheckEnabled() {
}
@Test
- void testGetBaseConnectionManager_WithSSLTrustStore() {
- // Define behavior for mock context where SSLTrustStore is set
- when(mockContext.getSSLTrustStore()).thenReturn(DUMMY_TRUST_STORE_PATH);
-
+ void testGetBaseConnectionManager_WithSSLTrustStore() throws DatabricksHttpException {
try (MockedStatic configuratorUtils = mockStatic(ConfiguratorUtils.class)) {
configuratorUtils
.when(() -> ConfiguratorUtils.getBaseConnectionManager(mockContext))
.thenCallRealMethod();
configuratorUtils
- .when(() -> ConfiguratorUtils.getConnectionSocketFactoryRegistry(mockContext))
+ .when(() -> ConfiguratorUtils.createConnectionSocketFactoryRegistry(mockContext))
.thenReturn(mock(Registry.class));
// Call getBaseConnectionManager with the mock context
PoolingHttpClientConnectionManager connManager =
@@ -207,10 +167,203 @@ void testGetBaseConnectionManager_WithSSLTrustStore() {
// Assert that getConnectionSocketFactoryRegistry was called
configuratorUtils.verify(
- () -> ConfiguratorUtils.getConnectionSocketFactoryRegistry(mockContext), times(1));
+ () -> ConfiguratorUtils.createConnectionSocketFactoryRegistry(mockContext), times(1));
// Ensure the returned connection manager is not null
assertNotNull(connManager);
}
}
+
+ @Test
+ void testUseSystemTrustStoreFalse_NoCustomTrustStore() throws DatabricksHttpException {
+ // Scenario: useSystemTrustStore=false and no custom trust store provided
+ // Should use JDK default trust store and ignore system property
+
+ when(mockContext.useSystemTrustStore()).thenReturn(false);
+ when(mockContext.checkCertificateRevocation()).thenReturn(false);
+
+ Registry registry =
+ ConfiguratorUtils.createConnectionSocketFactoryRegistry(mockContext);
+ assertNotNull(registry);
+ assertInstanceOf(
+ SSLConnectionSocketFactory.class, registry.lookup(DatabricksJdbcConstants.HTTPS));
+ }
+
+ @Test
+ void testCustomTrustStore_WithRevocationChecking() throws DatabricksHttpException {
+ // Scenario: Custom trust store with certificate revocation checking
+
+ when(mockContext.checkCertificateRevocation()).thenReturn(true);
+ when(mockContext.acceptUndeterminedCertificateRevocation()).thenReturn(true);
+
+ Registry registry =
+ ConfiguratorUtils.createConnectionSocketFactoryRegistry(mockContext);
+
+ assertNotNull(registry);
+ assertInstanceOf(
+ SSLConnectionSocketFactory.class, registry.lookup(DatabricksJdbcConstants.HTTPS));
+ }
+
+ @Test
+ void testCreateRegistryWithSystemPropertyTrustStore() throws DatabricksHttpException {
+ // Save original system properties to restore later
+ String originalTrustStore = System.getProperty("javax.net.ssl.trustStore");
+ String originalPassword = System.getProperty("javax.net.ssl.trustStorePassword");
+ String originalType = System.getProperty("javax.net.ssl.trustStoreType");
+
+ try {
+ // Set system properties to use the dummy trust store
+ System.setProperty("javax.net.ssl.trustStore", DUMMY_TRUST_STORE_PATH);
+ System.setProperty("javax.net.ssl.trustStorePassword", TRUST_STORE_PASSWORD);
+ System.setProperty("javax.net.ssl.trustStoreType", TRUST_STORE_TYPE);
+ when(mockContext.useSystemTrustStore()).thenReturn(true);
+ when(mockContext.checkCertificateRevocation()).thenReturn(false);
+
+ Registry registry =
+ ConfiguratorUtils.createConnectionSocketFactoryRegistry(mockContext);
+
+ assertNotNull(registry);
+ assertInstanceOf(
+ SSLConnectionSocketFactory.class, registry.lookup(DatabricksJdbcConstants.HTTPS));
+ } finally {
+ // Restore original system properties
+ if (originalTrustStore != null) {
+ System.setProperty("javax.net.ssl.trustStore", originalTrustStore);
+ } else {
+ System.clearProperty("javax.net.ssl.trustStore");
+ }
+
+ if (originalPassword != null) {
+ System.setProperty("javax.net.ssl.trustStorePassword", originalPassword);
+ } else {
+ System.clearProperty("javax.net.ssl.trustStorePassword");
+ }
+
+ if (originalType != null) {
+ System.setProperty("javax.net.ssl.trustStoreType", originalType);
+ } else {
+ System.clearProperty("javax.net.ssl.trustStoreType");
+ }
+ }
+ }
+
+ @Test
+ void testCreateRegistryWithSystemPropertyTrustStore_WithRevocationChecking()
+ throws DatabricksHttpException {
+ // Save original system properties to restore later
+ String originalTrustStore = System.getProperty("javax.net.ssl.trustStore");
+ String originalPassword = System.getProperty("javax.net.ssl.trustStorePassword");
+ String originalType = System.getProperty("javax.net.ssl.trustStoreType");
+
+ try {
+ // Set system properties to use the dummy trust store
+ System.setProperty("javax.net.ssl.trustStore", DUMMY_TRUST_STORE_PATH);
+ System.setProperty("javax.net.ssl.trustStorePassword", TRUST_STORE_PASSWORD);
+ System.setProperty("javax.net.ssl.trustStoreType", TRUST_STORE_TYPE);
+
+ when(mockContext.useSystemTrustStore()).thenReturn(true);
+ when(mockContext.checkCertificateRevocation()).thenReturn(true);
+ when(mockContext.acceptUndeterminedCertificateRevocation()).thenReturn(true);
+
+ Registry registry =
+ ConfiguratorUtils.createConnectionSocketFactoryRegistry(mockContext);
+
+ assertNotNull(registry);
+ assertInstanceOf(
+ SSLConnectionSocketFactory.class, registry.lookup(DatabricksJdbcConstants.HTTPS));
+ } finally {
+ // Restore original system properties
+ if (originalTrustStore != null) {
+ System.setProperty("javax.net.ssl.trustStore", originalTrustStore);
+ } else {
+ System.clearProperty("javax.net.ssl.trustStore");
+ }
+
+ if (originalPassword != null) {
+ System.setProperty("javax.net.ssl.trustStorePassword", originalPassword);
+ } else {
+ System.clearProperty("javax.net.ssl.trustStorePassword");
+ }
+
+ if (originalType != null) {
+ System.setProperty("javax.net.ssl.trustStoreType", originalType);
+ } else {
+ System.clearProperty("javax.net.ssl.trustStoreType");
+ }
+ }
+ }
+
+ @Test
+ void testCreateTrustManagers_WithAndWithoutRevocationChecking() throws Exception {
+ // Load a real trust store to test with
+ when(mockContext.checkCertificateRevocation()).thenReturn(true);
+ when(mockContext.acceptUndeterminedCertificateRevocation()).thenReturn(false);
+ Registry revocationCheckingRegistry =
+ ConfiguratorUtils.createConnectionSocketFactoryRegistry(mockContext);
+ assertNotNull(revocationCheckingRegistry);
+
+ // Test with revocation checking disabled
+ when(mockContext.checkCertificateRevocation()).thenReturn(false);
+ Registry noRevocationCheckingRegistry =
+ ConfiguratorUtils.createConnectionSocketFactoryRegistry(mockContext);
+ assertNotNull(noRevocationCheckingRegistry);
+ }
+
+ @Test
+ void testFindX509TrustManager() throws Exception {
+ TrustManagerFactory tmf =
+ TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ tmf.init((KeyStore) null);
+ TrustManager[] trustManagers = tmf.getTrustManagers();
+
+ // Verify we have at least one trust manager
+ assertNotNull(trustManagers);
+ assertTrue(trustManagers.length > 0);
+
+ // Verify at least one is an X509TrustManager
+ boolean foundX509TrustManager = false;
+ for (TrustManager tm : trustManagers) {
+ if (tm instanceof X509TrustManager) {
+ foundX509TrustManager = true;
+ break;
+ }
+ }
+ assertTrue(foundX509TrustManager, "Should find at least one X509TrustManager");
+ }
+
+ @Test
+ void testEmptyTrustAnchorsException() {
+ // Test the behavior when trust anchors are empty
+ Set emptyTrustAnchors = Collections.emptySet();
+
+ DatabricksHttpException exception =
+ assertThrows(
+ DatabricksHttpException.class,
+ () -> ConfiguratorUtils.buildTrustManagerParameters(emptyTrustAnchors, true, false));
+
+ assertTrue(
+ exception.getMessage().contains("parameter must be non-empty"),
+ "Exception should mention empty parameter");
+ }
+
+ @Test
+ void testCreateSocketFactoryRegistry() throws Exception {
+ // Test using a real trust manager
+ TrustManagerFactory tmf =
+ TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ tmf.init((KeyStore) null);
+
+ // Create a registry with the system default trust managers
+ when(mockContext.checkCertificateRevocation()).thenReturn(false);
+ when(mockContext.useSystemTrustStore()).thenReturn(false);
+
+ Registry registry =
+ ConfiguratorUtils.createConnectionSocketFactoryRegistry(mockContext);
+
+ assertNotNull(registry);
+ assertInstanceOf(
+ SSLConnectionSocketFactory.class, registry.lookup(DatabricksJdbcConstants.HTTPS));
+ assertInstanceOf(
+ PlainConnectionSocketFactory.class, registry.lookup(DatabricksJdbcConstants.HTTP));
+ }
}