diff --git a/CHANGELOG.md b/CHANGELOG.md index ef68d17e6..8adf43a2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ [Release Migration Guide](docs/releases/0_11_0.md) +### New Features + +- **[client-v2, jdbc-v2]** Added TLS cipher suite selection. `Client.Builder.setSSLCipherSuites(String...)` (client-v2) + and the comma-separated `ssl_cipher_suites` connection property (client-v2 and jdbc-v2) restrict the cipher suites + enabled on secure connections; when unset, the JVM defaults are used. Cipher-suite selection is independent of the + trust configuration and `ssl_mode`. (https://github.com/ClickHouse/clickhouse-java/issues/2882) + ### Bug Fixes - **[client-v2]** Fixed container query parameters being sent unquoted, so `Client.query(sql, params, settings)` binding diff --git a/client-v2/src/main/java/com/clickhouse/client/api/Client.java b/client-v2/src/main/java/com/clickhouse/client/api/Client.java index 3bf94c529..b416e2782 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/Client.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/Client.java @@ -59,6 +59,7 @@ import java.time.ZoneId; import java.time.temporal.ChronoUnit; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -785,6 +786,21 @@ public Builder setSSLMode(SSLMode sslMode) { return this; } + /** + * Restricts the TLS cipher suites the client may negotiate on secure connections. When set, only + * the listed cipher suites are enabled on the SSL socket (subject to what the JVM and the server + * support); when not set, the JVM defaults are used. Suite names use the standard JSSE names, for + * example {@code TLS_AES_256_GCM_SHA384}. + * + * @param cipherSuites cipher suite names to enable + * @return same instance of the builder + */ + public Builder setSSLCipherSuites(String... cipherSuites) { + this.configuration.put(ClientConfigProperties.SSL_CIPHER_SUITES.getKey(), + ClientConfigProperties.commaSeparated(Arrays.asList(cipherSuites))); + return this; + } + /** * Configure client to use server timezone for date/datetime columns. Default is true. * If this options is selected then server timezone should be set as well. diff --git a/client-v2/src/main/java/com/clickhouse/client/api/ClientConfigProperties.java b/client-v2/src/main/java/com/clickhouse/client/api/ClientConfigProperties.java index 9a38232ba..cd20dc3a9 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/ClientConfigProperties.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/ClientConfigProperties.java @@ -118,6 +118,13 @@ public enum ClientConfigProperties { SSL_MODE("ssl_mode", SSLMode.class, SSLMode.STRICT.name()), + /** + * Comma-separated list of TLS cipher suites the client is allowed to negotiate on secure connections. + * When set, only these cipher suites are enabled on the SSL socket (subject to what the JVM and server + * support); when unset, the JVM defaults are used. + */ + SSL_CIPHER_SUITES("ssl_cipher_suites", List.class), + RETRY_ON_FAILURE("retry", Integer.class, "3"), INPUT_OUTPUT_FORMAT("format", ClickHouseFormat.class), diff --git a/client-v2/src/main/java/com/clickhouse/client/api/internal/HttpAPIClientHelper.java b/client-v2/src/main/java/com/clickhouse/client/api/internal/HttpAPIClientHelper.java index ab4b0153c..0302dc190 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/internal/HttpAPIClientHelper.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/internal/HttpAPIClientHelper.java @@ -288,8 +288,21 @@ public CloseableHttpClient createHttpClient(boolean initSslContext, Map true); + boolean hasSNI = socketSNI != null && !socketSNI.trim().isEmpty(); + List cipherSuites = ClientConfigProperties.SSL_CIPHER_SUITES.getOrDefault(configuration); + String[] enabledCipherSuites = cipherSuites == null || cipherSuites.isEmpty() + ? null : cipherSuites.toArray(new String[0]); + if (hasSNI || trustAllHostnames || enabledCipherSuites != null) { + // Skip hostname verification only for trust-all modes or when a custom SNI is used (the + // connection hostname would not match the certificate); otherwise a null verifier makes the + // factory fall back to the JDK/HttpClient default verifier, keeping STRICT verification. + // java:S5527 - the permissive verifier is applied only for SSLMode.TRUST/VERIFY_CA (where the + // user has explicitly opted out of hostname verification) or a custom SNI; STRICT keeps the + // default verifying behaviour, so secure-by-default hostname verification is preserved. + @SuppressWarnings("java:S5527") + HostnameVerifier hostnameVerifier = trustAllHostnames || hasSNI ? (hostname, session) -> true : null; + sslConnectionSocketFactory = new CustomSSLConnectionFactory(socketSNI, sslContext, hostnameVerifier, + enabledCipherSuites); } else { sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext); } @@ -1065,8 +1078,17 @@ public static class CustomSSLConnectionFactory extends SSLConnectionSocketFactor private final SNIHostName defaultSNI; + // Retained for backward compatibility; delegates with no cipher-suite restriction (JVM defaults). public CustomSSLConnectionFactory(String defaultSNI, SSLContext sslContext, HostnameVerifier hostnameVerifier) { - super(sslContext, hostnameVerifier); + this(defaultSNI, sslContext, hostnameVerifier, null); + } + + public CustomSSLConnectionFactory(String defaultSNI, SSLContext sslContext, HostnameVerifier hostnameVerifier, + String[] supportedCipherSuites) { + // supportedProtocols is left as null (JDK defaults are used); supportedCipherSuites, when + // provided, restricts the cipher suites the base factory enables on each socket. A null + // hostnameVerifier makes the base factory fall back to its default verifier. + super(sslContext, null, supportedCipherSuites, hostnameVerifier); this.defaultSNI = defaultSNI == null || defaultSNI.trim().isEmpty() ? null : new SNIHostName(defaultSNI); } diff --git a/client-v2/src/test/java/com/clickhouse/client/api/ClientBuilderTest.java b/client-v2/src/test/java/com/clickhouse/client/api/ClientBuilderTest.java index baccdc767..7ae0bad37 100644 --- a/client-v2/src/test/java/com/clickhouse/client/api/ClientBuilderTest.java +++ b/client-v2/src/test/java/com/clickhouse/client/api/ClientBuilderTest.java @@ -6,7 +6,9 @@ import org.testng.annotations.Test; import java.lang.reflect.Field; +import java.util.Arrays; import java.util.List; +import java.util.Map; public class ClientBuilderTest { @@ -86,6 +88,57 @@ public void testSslModeInvalidValueRejected() { .build()); } + @Test + public void testSetSSLCipherSuitesStoredInConfiguration() throws Exception { + try (Client client = new Client.Builder() + .addEndpoint("https://localhost:8443") + .setUsername("default") + .setPassword("") + .setSSLCipherSuites("TLS_AES_256_GCM_SHA384", "TLS_AES_128_GCM_SHA256") + .build()) { + Assert.assertEquals(extractConfiguration(client).get(ClientConfigProperties.SSL_CIPHER_SUITES.getKey()), + Arrays.asList("TLS_AES_256_GCM_SHA384", "TLS_AES_128_GCM_SHA256"), + "Cipher suites set via the builder should be stored as a parsed list"); + } + } + + @Test + public void testSSLCipherSuitesViaSetOptionParsedAsList() throws Exception { + // The comma-separated string form is the path used by URL/JDBC properties. + try (Client client = new Client.Builder() + .addEndpoint("https://localhost:8443") + .setUsername("default") + .setPassword("") + .setOption(ClientConfigProperties.SSL_CIPHER_SUITES.getKey(), + "TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256") + .build()) { + Assert.assertEquals(extractConfiguration(client).get(ClientConfigProperties.SSL_CIPHER_SUITES.getKey()), + Arrays.asList("TLS_AES_256_GCM_SHA384", "TLS_AES_128_GCM_SHA256"), + "Comma-separated cipher suites should be parsed into a list"); + } + } + + @Test + public void testClientBuildsWithCipherSuitesOverHttps() { + // Exercises the HTTPS connection-socket-factory path with cipher suites configured (STRICT mode, + // no SNI): the client must build without error. + try (Client client = new Client.Builder() + .addEndpoint("https://localhost:8443") + .setUsername("default") + .setPassword("") + .setSSLCipherSuites("TLS_AES_256_GCM_SHA384") + .build()) { + Assert.assertNotNull(client); + } + } + + @SuppressWarnings("unchecked") + private static Map extractConfiguration(Client client) throws Exception { + Field configField = Client.class.getDeclaredField("configuration"); + configField.setAccessible(true); + return (Map) configField.get(client); + } + private static String extractFirstEndpointUri(Client client) throws Exception { Field endpointsField = Client.class.getDeclaredField("endpoints"); endpointsField.setAccessible(true); diff --git a/client-v2/src/test/java/com/clickhouse/client/api/internal/HttpAPIClientHelperTest.java b/client-v2/src/test/java/com/clickhouse/client/api/internal/HttpAPIClientHelperTest.java index f03e84af6..869f371c3 100644 --- a/client-v2/src/test/java/com/clickhouse/client/api/internal/HttpAPIClientHelperTest.java +++ b/client-v2/src/test/java/com/clickhouse/client/api/internal/HttpAPIClientHelperTest.java @@ -1,5 +1,93 @@ -package com.clickhouse.client.api.internal; - -public class HttpAPIClientHelperTest { - -} \ No newline at end of file +package com.clickhouse.client.api.internal; + +import com.clickhouse.client.api.internal.HttpAPIClientHelper.CustomSSLConnectionFactory; +import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; +import org.mockito.ArgumentCaptor; +import org.testng.annotations.Test; + +import javax.net.ssl.SNIHostName; +import javax.net.ssl.SNIServerName; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSocket; +import java.lang.reflect.Field; +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNull; + +public class HttpAPIClientHelperTest { + + /** + * The configured cipher suites must be forwarded to the base {@link SSLConnectionSocketFactory}, which is + * what enables them on each secure connection. This is the core of the cipher-suite feature. + */ + @Test + public void testCipherSuiteConstructorForwardsSuitesToBaseFactory() throws Exception { + String[] suites = {"TLS_AES_256_GCM_SHA384", "TLS_AES_128_GCM_SHA256"}; + CustomSSLConnectionFactory factory = new CustomSSLConnectionFactory( + null, SSLContext.getDefault(), (hostname, session) -> true, suites); + + assertEquals(baseSupportedCipherSuites(factory), suites, + "configured cipher suites must reach the base socket factory that enables them per connection"); + } + + /** + * The three-argument constructor is retained for backward compatibility and must delegate with no cipher + * restriction, so callers that do not configure cipher suites keep the JVM defaults. + */ + @Test + public void testLegacyConstructorAppliesNoCipherRestriction() throws Exception { + CustomSSLConnectionFactory factory = new CustomSSLConnectionFactory( + "legacy.example.com", SSLContext.getDefault(), (hostname, session) -> true); + + assertNull(baseSupportedCipherSuites(factory), + "the legacy constructor must not restrict cipher suites"); + } + + /** + * A configured SNI host is applied to every prepared socket via the standard SSL parameters. + */ + @Test + public void testConfiguredSniAppliedToPreparedSocket() throws Exception { + CustomSSLConnectionFactory factory = new CustomSSLConnectionFactory( + "sni.example.com", SSLContext.getDefault(), (hostname, session) -> true, null); + + SSLSocket socket = mock(SSLSocket.class); + when(socket.getSSLParameters()).thenReturn(new SSLParameters()); + + factory.prepareSocket(socket, null); + + ArgumentCaptor params = ArgumentCaptor.forClass(SSLParameters.class); + verify(socket).setSSLParameters(params.capture()); + List serverNames = params.getValue().getServerNames(); + assertEquals(serverNames.size(), 1, "the configured SNI host must be applied to the socket"); + assertEquals(((SNIHostName) serverNames.get(0)).getAsciiName(), "sni.example.com"); + } + + /** + * A blank SNI is treated as unset: the socket's SSL parameters must be left untouched so the defaults + * (and any cipher suites the base factory already applied) are preserved. + */ + @Test + public void testBlankSniLeavesSocketParametersUntouched() throws Exception { + CustomSSLConnectionFactory factory = new CustomSSLConnectionFactory( + " ", SSLContext.getDefault(), (hostname, session) -> true, null); + + SSLSocket socket = mock(SSLSocket.class); + factory.prepareSocket(socket, null); + + verify(socket, never()).setSSLParameters(any()); + } + + private static String[] baseSupportedCipherSuites(CustomSSLConnectionFactory factory) throws Exception { + Field field = SSLConnectionSocketFactory.class.getDeclaredField("supportedCipherSuites"); + field.setAccessible(true); + return (String[]) field.get(factory); + } +} diff --git a/docs/features.md b/docs/features.md index 11a0e4ac4..5969cafcb 100644 --- a/docs/features.md +++ b/docs/features.md @@ -7,6 +7,7 @@ This document lists stable, user-visible behavior in `client-v2` and `jdbc-v2` t - HTTP and HTTPS connectivity: Connects to ClickHouse over HTTP(S), supports endpoint paths, and exposes a basic `ping` health check. - TLS configuration: Supports trust stores, client certificates/keys, SSL certificate authentication, and SNI for HTTPS connections. Trust material (root CA and client certificate/key) can be supplied either as a file path or directly as PEM content. - SSL verification modes: `Client.Builder.setSSLMode(SSLMode)` (or the `ssl_mode` property) controls how strictly the server identity is verified on secure connections: `DISABLED` (SSL not used; plain protocols only), `TRUST` (accept any server certificate and skip hostname verification; a configured trust store or CA certificate is ignored with a warning, while a client certificate/key is still applied for mTLS if configured), `VERIFY_CA` (validate the certificate chain but skip hostname verification), and `STRICT` (full chain and hostname verification, default). +- TLS cipher suite selection: `Client.Builder.setSSLCipherSuites(String...)` (or the comma-separated `ssl_cipher_suites` property) restricts the cipher suites enabled on secure connections. When set, only the listed suites are enabled on the SSL socket (subject to JVM and server support); when unset, the JVM defaults apply. Cipher-suite selection is independent of the trust configuration and `ssl_mode`. - Authentication modes: Supports username/password credentials, ClickHouse auth headers, bearer tokens, and optional HTTP Basic authentication. - Runtime credential updates: Existing `Client` instances can update username/password or bearer-token credentials for subsequent requests without rebuilding the client. - Proxy support: Can send requests through configured HTTP proxies, including proxy credentials. @@ -53,7 +54,7 @@ Compatibility-sensitive traits: - JDBC driver registration: Registers through the standard JDBC service mechanism and is available through `DriverManager`. - JDBC URL parsing: Accepts `jdbc:clickhouse:` and `jdbc:ch:` URLs with host, port, optional HTTP path, optional database, and query parameters. -- SSL URL support: Supports HTTPS connections through URL and property configuration, including default protocol and port handling. The `ssl_mode` property selects the verification strictness (`disabled`, `trust`, `verify_ca`, `strict`); values are case-insensitive and the traditional JDBC value `none` is accepted as an alias for `trust`. Root CA and client certificate/key may be supplied as a file path or as inline PEM content. +- SSL URL support: Supports HTTPS connections through URL and property configuration, including default protocol and port handling. The `ssl_mode` property selects the verification strictness (`disabled`, `trust`, `verify_ca`, `strict`); values are case-insensitive and the traditional JDBC value `none` is accepted as an alias for `trust`. Root CA and client certificate/key may be supplied as a file path or as inline PEM content. The `ssl_cipher_suites` property (a comma-separated list) restricts the negotiated TLS cipher suites and is forwarded to the underlying `client-v2` transport. - Driver and client properties: Separates JDBC-specific properties from passthrough client options used by the underlying `client-v2` transport. - DataSource support: Provides a JDBC `DataSource` implementation backed by the same driver configuration model. - Connection lifecycle: Supports connection close, validity checks, ping-based health checks, and network timeout management. diff --git a/examples/client-v2/src/main/java/com/clickhouse/examples/client_v2/SSLExamples.java b/examples/client-v2/src/main/java/com/clickhouse/examples/client_v2/SSLExamples.java index 520b82a03..d037e61c0 100644 --- a/examples/client-v2/src/main/java/com/clickhouse/examples/client_v2/SSLExamples.java +++ b/examples/client-v2/src/main/java/com/clickhouse/examples/client_v2/SSLExamples.java @@ -26,6 +26,9 @@ *
  • Connecting to a server with a self-signed certificate without any trust material - * {@link SSLMode#TRUST} accepts any server certificate and skips hostname verification. * Use it only for testing or in fully trusted environments.
  • + *
  • Restricting the negotiated TLS cipher suites with + * {@link Client.Builder#setSSLCipherSuites(String...)} - useful to enforce a stronger or + * compliance-mandated set of cipher suites instead of the JVM defaults.
  • * * *

    More SSL examples (mTLS, trust stores, SNI) will be added to this class later.

    @@ -70,6 +73,7 @@ public static void main(String[] args) { if (rootCert != null) { connectWithCustomRootCertificate(endpoint, database, user, password, rootCert); connectWithRootCertificateAsString(endpoint, database, user, password, rootCert); + connectWithCipherSuites(endpoint, database, user, password, rootCert); } else { log.info("chRootCert is not set - skipping the custom CA certificate examples. " + "Pass the path to the CA certificate (PEM) that signed the server certificate to run them."); @@ -88,6 +92,8 @@ public static void main(String[] args) { SecureServerSupport.USER, SecureServerSupport.PASSWORD, server.getCaCertPath()); connectWithRootCertificateAsString(server.getEndpoint(), database, SecureServerSupport.USER, SecureServerSupport.PASSWORD, server.getCaCertPath()); + connectWithCipherSuites(server.getEndpoint(), database, + SecureServerSupport.USER, SecureServerSupport.PASSWORD, server.getCaCertPath()); } catch (Exception e) { log.error("Failed to run the SSL example against a local Docker server", e); } @@ -192,6 +198,38 @@ static void connectWithRootCertificateAsString(String endpoint, String database, } } + /** + * Connects while restricting the TLS cipher suites the client is allowed to negotiate, using + * {@link Client.Builder#setSSLCipherSuites(String...)}. Only the listed suites are enabled on the + * socket (subject to what the JVM and the server support); this is useful to enforce a stronger or + * compliance-mandated set of cipher suites rather than relying on the JVM defaults. + * + *

    The CA certificate is still used to verify the server, and hostname verification stays enabled - + * cipher-suite selection is independent of the trust configuration and the SSL mode. The suites below + * cover TLS 1.3 and TLS 1.2; keep at least one suite the server actually supports, or the handshake + * fails.

    + */ + static void connectWithCipherSuites(String endpoint, String database, String user, String password, + String rootCert) { + log.info("Connecting to {} with a restricted set of TLS cipher suites", endpoint); + try (Client client = new Client.Builder() + .addEndpoint(endpoint) + .setUsername(user) + .setPassword(password) + .setDefaultDatabase(database) + .setRootCertificate(rootCert) + // Restrict negotiation to these cipher suites (TLS 1.3 and TLS 1.2). + .setSSLCipherSuites("TLS_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384") + .build()) { + + List rows = client.queryAll("SELECT currentUser() AS user, version() AS version"); + log.info("Connected securely (restricted cipher suites) as '{}' to ClickHouse {}", + rows.get(0).getString("user"), rows.get(0).getString("version")); + } catch (Exception e) { + log.error("Secure connection with restricted cipher suites failed", e); + } + } + private static String trimToNull(String value) { if (value == null) { return null; diff --git a/examples/jdbc/src/main/java/com/clickhouse/examples/jdbc/SSLExamples.java b/examples/jdbc/src/main/java/com/clickhouse/examples/jdbc/SSLExamples.java index f6a1cef1f..54c1758bb 100644 --- a/examples/jdbc/src/main/java/com/clickhouse/examples/jdbc/SSLExamples.java +++ b/examples/jdbc/src/main/java/com/clickhouse/examples/jdbc/SSLExamples.java @@ -31,6 +31,9 @@ * the {@code ssl_mode=trust} connection property accepts any server certificate and skips * hostname verification ({@code ssl_mode=none} is accepted as an alias). Use it only for * testing or in fully trusted environments. + *
  • Restricting the negotiated TLS cipher suites with the {@code ssl_cipher_suites} connection + * property (a comma-separated list) - useful to enforce a stronger or compliance-mandated set of + * cipher suites instead of the JVM defaults.
  • * * *

    More SSL examples (mTLS, trust stores, SNI) will be added to this class later.

    @@ -72,6 +75,7 @@ public static void main(String[] args) { if (rootCert != null) { connectWithCustomRootCertificate(url, user, password, rootCert); connectWithRootCertificateAsString(url, user, password, rootCert); + connectWithCipherSuites(url, user, password, rootCert); } else { log.info("chRootCert is not set - skipping the custom CA certificate examples. " + "Pass the path to the CA certificate (PEM) that signed the server certificate to run them."); @@ -93,6 +97,8 @@ public static void main(String[] args) { SecureServerSupport.USER, SecureServerSupport.PASSWORD, server.getCaCertPath()); connectWithRootCertificateAsString(server.getJdbcUrl(), SecureServerSupport.USER, SecureServerSupport.PASSWORD, server.getCaCertPath()); + connectWithCipherSuites(server.getJdbcUrl(), + SecureServerSupport.USER, SecureServerSupport.PASSWORD, server.getCaCertPath()); } catch (Exception e) { log.error("Failed to run the SSL example against a local Docker server", e); Runtime.getRuntime().exit(-1); @@ -196,6 +202,39 @@ static void connectWithRootCertificateAsString(String url, String user, String p } } + /** + * Connects while restricting the TLS cipher suites the driver is allowed to negotiate, using the + * {@code ssl_cipher_suites} connection property (a comma-separated list). Only the listed suites are + * enabled on the socket (subject to what the JVM and the server support); this is useful to enforce a + * stronger or compliance-mandated set of cipher suites rather than relying on the JVM defaults. + * + *

    The CA certificate is still used to verify the server and hostname verification stays enabled - + * cipher-suite selection is independent of the trust configuration and {@code ssl_mode}. Keep at least + * one suite the server actually supports, or the handshake fails.

    + */ + static void connectWithCipherSuites(String url, String user, String password, String rootCert) + throws SQLException { + log.info("Connecting to {} with a restricted set of TLS cipher suites", url); + + Properties properties = new Properties(); + properties.setProperty(ClientConfigProperties.USER.getKey(), user); // user + properties.setProperty(ClientConfigProperties.PASSWORD.getKey(), password); // password + properties.setProperty("ssl", "true"); // enable TLS even if the URL has no https scheme + properties.setProperty(ClientConfigProperties.CA_CERTIFICATE.getKey(), rootCert); // sslrootcert + // Restrict negotiation to these cipher suites (TLS 1.3 and TLS 1.2), comma-separated. + properties.setProperty(ClientConfigProperties.SSL_CIPHER_SUITES.getKey(), + "TLS_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"); // ssl_cipher_suites + + try (Connection connection = DriverManager.getConnection(url, properties); + Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT currentUser() AS user, version() AS version")) { + if (rs.next()) { + log.info("Connected securely (restricted cipher suites) as '{}' to ClickHouse {}", + rs.getString("user"), rs.getString("version")); + } + } + } + private static String trimToNull(String value) { if (value == null) { return null; diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/internal/JdbcConfigurationTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/internal/JdbcConfigurationTest.java index e328bc398..a53e711f7 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/internal/JdbcConfigurationTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/internal/JdbcConfigurationTest.java @@ -221,6 +221,24 @@ public void testSSLModeInvalidValue() { () -> new JdbcConfiguration("jdbc:clickhouse://localhost:8123/?ssl_mode=insecure", new Properties())); } + @Test + public void testSSLCipherSuitesForwardedToClientProperties() throws Exception { + String cipherSuites = "TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256"; + + // passed via Properties + Properties properties = new Properties(); + properties.setProperty(ClientConfigProperties.SSL_CIPHER_SUITES.getKey(), cipherSuites); + JdbcConfiguration configuration = new JdbcConfiguration("jdbc:clickhouse://localhost:8123/", properties); + assertEquals(configuration.getClientProperties().get(ClientConfigProperties.SSL_CIPHER_SUITES.getKey()), + cipherSuites); + + // passed as a URL parameter + configuration = new JdbcConfiguration( + "jdbc:clickhouse://localhost:8123/?ssl_cipher_suites=" + cipherSuites, new Properties()); + assertEquals(configuration.getClientProperties().get(ClientConfigProperties.SSL_CIPHER_SUITES.getKey()), + cipherSuites); + } + @DataProvider(name = "typeMappingsPropertyKey") public Object[][] typeMappingsPropertyKey() { return new Object[][] {