Skip to content

Commit ea14e4f

Browse files
committed
Made building ssl context in builder way. Renamed SSLMode constants to match java naming convention. Misc fixes.
1 parent 86cd9ec commit ea14e4f

15 files changed

Lines changed: 512 additions & 140 deletions

File tree

clickhouse-client/src/main/java/com/clickhouse/client/config/ClickHouseDefaultSslContextProvider.java

Lines changed: 20 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -154,38 +154,14 @@ public SSLContext getJavaSslContext(ClickHouseConfig config) throws SSLException
154154
}
155155

156156
public SSLContext getSslContextFromCerts(String clientCert, String clientKey, String sslRootCert) throws SSLException {
157-
return getSslContextFromCerts(ClickHouseSslMode.STRICT, clientCert, clientKey, sslRootCert);
158-
}
159-
160-
/**
161-
* Creates an SSL context from certificates with an explicit SSL mode.
162-
* With {@link ClickHouseSslMode#NONE} the server certificate is not validated, while client
163-
* certificate and key are still used (if provided) so that mTLS keeps working.
164-
*
165-
* @param sslMode ssl mode
166-
* @param clientCert client certificate for mTLS, file path or PEM content; may be null
167-
* @param clientKey client private key for mTLS, file path or PEM content; may be null
168-
* @param sslRootCert CA certificate to validate the server certificate, file path or PEM content; may be null
169-
* @return SSL context
170-
* @throws SSLException when the context cannot be created
171-
*/
172-
public SSLContext getSslContextFromCerts(ClickHouseSslMode sslMode, String clientCert, String clientKey,
173-
String sslRootCert) throws SSLException {
174-
return getSslContextImpl(sslMode, clientCert, clientKey, sslRootCert, null, null, KeyStore.getDefaultType());
157+
return getSslContextImpl(ClickHouseSslMode.STRICT,
158+
clientCert, clientKey, sslRootCert, null, null, KeyStore.getDefaultType());
175159
}
176160

177161
public SSLContext getSslContextFromKeyStore(String truststorePath, String truststorePassword, String keyStoreType) throws SSLException {
178162
return getSslContextImpl(ClickHouseSslMode.STRICT, null, null, null, truststorePath, truststorePassword, keyStoreType);
179163
}
180164

181-
private KeyManager[] getKeyManagers(String clientCert, String clientKey)
182-
throws NoSuchAlgorithmException, InvalidKeySpecException, IOException, CertificateException,
183-
KeyStoreException, UnrecoverableKeyException {
184-
KeyManagerFactory factory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
185-
factory.init(getKeyStore(clientCert, clientKey), null);
186-
return factory.getKeyManagers();
187-
}
188-
189165
private SSLContext getSslContextImpl(ClickHouseSslMode sslMode, String clientCert, String clientKey, String sslRootCert, String truststorePath, String truststorePassword, String keyStoreType) throws SSLException {
190166
SSLContext ctx;
191167
try {
@@ -196,29 +172,34 @@ private SSLContext getSslContextImpl(ClickHouseSslMode sslMode, String clientCer
196172

197173
if (sslMode == ClickHouseSslMode.NONE) {
198174
tms = new TrustManager[]{new NonValidatingTrustManager()};
199-
// client certificate and key are independent from server verification - keep mTLS working
200-
kms = clientCert != null && !clientCert.isEmpty() ? getKeyManagers(clientCert, clientKey)
201-
: new KeyManager[0];
175+
kms = new KeyManager[0];
202176
sr = new SecureRandom();
203177
} else if (sslMode == ClickHouseSslMode.STRICT) {
204-
if (clientCert != null && !clientCert.isEmpty()) {
205-
kms = getKeyManagers(clientCert, clientKey);
206-
}
207-
208178
if (truststorePath != null && !truststorePath.isEmpty()) {
179+
209180
try (InputStream in = ClickHouseUtils.getFileInputStream(truststorePath)) {
210181
KeyStore myTrustStore = KeyStore.getInstance(keyStoreType);
211-
myTrustStore.load(in, truststorePassword == null ? null : truststorePassword.toCharArray());
182+
myTrustStore.load(in, truststorePassword.toCharArray());
212183
TrustManagerFactory factory = TrustManagerFactory
213184
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
214185
factory.init(myTrustStore);
215186
tms = factory.getTrustManagers();
187+
188+
}
189+
} else {
190+
if (clientCert != null && !clientCert.isEmpty()) {
191+
KeyManagerFactory factory = KeyManagerFactory
192+
.getInstance(KeyManagerFactory.getDefaultAlgorithm());
193+
factory.init(getKeyStore(clientCert, clientKey), null);
194+
kms = factory.getKeyManagers();
195+
}
196+
197+
if (sslRootCert != null && !sslRootCert.isEmpty()) {
198+
TrustManagerFactory factory = TrustManagerFactory
199+
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
200+
factory.init(getKeyStore(sslRootCert, null));
201+
tms = factory.getTrustManagers();
216202
}
217-
} else if (sslRootCert != null && !sslRootCert.isEmpty()) {
218-
TrustManagerFactory factory = TrustManagerFactory
219-
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
220-
factory.init(getKeyStore(sslRootCert, null));
221-
tms = factory.getTrustManagers();
222203
}
223204

224205
sr = new SecureRandom();

client-v2/src/main/java/com/clickhouse/client/api/Client.java

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -761,12 +761,13 @@ public Builder setClientKey(String path) {
761761
*
762762
* <p>Supported modes:</p>
763763
* <ul>
764-
* <li>{@link SSLMode#Disabled} - SSL is not used; only meaningful with plain protocols</li>
765-
* <li>{@link SSLMode#Trust} - encrypt, but accept any server certificate and skip
764+
* <li>{@link SSLMode#DISABLED} - SSL is not used; only meaningful with plain protocols</li>
765+
* <li>{@link SSLMode#TRUST} - encrypt, but accept any server certificate and skip
766+
* hostname verification; a configured trust store or CA certificate is ignored (a warning
767+
* is logged), while a client certificate/key is still applied for mTLS</li>
768+
* <li>{@link SSLMode#VERIFY_CA} - validate the server certificate chain, but skip
766769
* hostname verification</li>
767-
* <li>{@link SSLMode#VerifyCa} - validate the server certificate chain, but skip
768-
* hostname verification</li>
769-
* <li>{@link SSLMode#Strict} - full verification of the certificate chain and the
770+
* <li>{@link SSLMode#STRICT} - full verification of the certificate chain and the
770771
* hostname (default)</li>
771772
* </ul>
772773
*
@@ -1168,6 +1169,21 @@ public Client build() {
11681169
throw new ClientMisconfigurationException("Trust store and certificates cannot be used together");
11691170
}
11701171

1172+
// A trust store and a CA certificate are not rejected here: for VERIFY_CA/STRICT the trust
1173+
// store takes precedence and the CA certificate is ignored with a warning (see createSSLContext).
1174+
1175+
// SSLMode.DISABLED does not turn encryption off - the endpoint scheme decides that. So it
1176+
// contradicts a secure (https) endpoint and must be rejected here, before the client is created.
1177+
if (SSLMode.DISABLED.name().equals(configuration.get(ClientConfigProperties.SSL_MODE.getKey()))) {
1178+
for (Endpoint endpoint : this.endpoints) {
1179+
if ("https".equalsIgnoreCase(endpoint.getURI().getScheme())) {
1180+
throw new ClientMisconfigurationException("SSL mode '" + SSLMode.DISABLED
1181+
+ "' cannot be used with a secure (https) endpoint. Use '" + SSLMode.TRUST
1182+
+ "' to trust all certificates or use plain HTTP.");
1183+
}
1184+
}
1185+
}
1186+
11711187
// Check timezone settings
11721188
String useTimeZoneValue = this.configuration.get(ClientConfigProperties.USE_TIMEZONE.getKey());
11731189
String serverTimeZoneValue = this.configuration.get(ClientConfigProperties.SERVER_TIMEZONE.getKey());

client-v2/src/main/java/com/clickhouse/client/api/ClientConfigProperties.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ public enum ClientConfigProperties {
116116

117117
SSL_CERTIFICATE("sslcert", String.class),
118118

119-
SSL_MODE("ssl_mode", SSLMode.class, SSLMode.Strict.name()),
119+
SSL_MODE("ssl_mode", SSLMode.class, SSLMode.STRICT.name()),
120120

121121
RETRY_ON_FAILURE("retry", Integer.class, "3"),
122122

client-v2/src/main/java/com/clickhouse/client/api/enums/SSLMode.java

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,40 +9,43 @@
99
*
1010
* <p>Modes from the least to the most strict:</p>
1111
* <ul>
12-
* <li>{@link #Disabled} - SSL is not used. Plain protocols only.</li>
13-
* <li>{@link #Trust} - encryption is used, but the server certificate chain is not validated
14-
* and the hostname is not verified. Susceptible to MITM attacks - use only for testing or in
15-
* fully trusted environments.</li>
16-
* <li>{@link #VerifyCa} - the server certificate chain is validated against the trust material
12+
* <li>{@link #DISABLED} - SSL is not used. Plain protocols only.</li>
13+
* <li>{@link #TRUST} - the hostname is not verified and any server certificate is accepted, which
14+
* is susceptible to MITM attacks - use that only for testing or in fully trusted environments. A
15+
* configured trust store or CA certificate has no effect in this mode and is ignored (a warning is
16+
* logged); a configured client certificate/key is still applied for mTLS.</li>
17+
* <li>{@link #VERIFY_CA} - the server certificate chain is validated against the trust material
1718
* (default JVM trust store, configured trust store, or a CA certificate), but the hostname is
1819
* not checked against the certificate.</li>
19-
* <li>{@link #Strict} - full verification (default): certificate chain is validated and the
20+
* <li>{@link #STRICT} - full verification (default): certificate chain is validated and the
2021
* hostname must match the certificate.</li>
2122
* </ul>
2223
*/
2324
public enum SSLMode {
2425

2526
/**
26-
* SSL is not used. Connection is not encrypted.
27+
* SSL is not used. Connection is not encrypted. Doesn't work with HTTPS.
28+
* Reserved for TCP where protocol doesn't define encryption.
2729
*/
28-
Disabled,
30+
DISABLED,
2931

3032
/**
31-
* Encryption without verification: any server certificate is accepted and
32-
* the hostname is not verified.
33+
* The hostname is not verified and any server certificate is accepted. A configured trust store or
34+
* CA certificate has no effect in this mode and is ignored (a warning is logged). A configured
35+
* client certificate/key is still applied for mTLS.
3336
*/
34-
Trust,
37+
TRUST,
3538

3639
/**
3740
* Server certificate chain is validated, but the hostname is not verified.
3841
*/
39-
VerifyCa,
42+
VERIFY_CA,
4043

4144
/**
4245
* Full verification: certificate chain is validated and the hostname must match
43-
* the certificate. Default mode.
46+
* the certificate. Default mode for HTTPs.
4447
*/
45-
Strict;
48+
STRICT;
4649

4750
/**
4851
* Case-insensitive variant of {@link #valueOf(String)}.

client-v2/src/main/java/com/clickhouse/client/api/internal/HttpAPIClientHelper.java

Lines changed: 29 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,8 @@
1212
import com.clickhouse.client.api.ServerException;
1313
import com.clickhouse.client.api.enums.ProxyType;
1414
import com.clickhouse.client.api.enums.SSLMode;
15-
import com.clickhouse.client.config.ClickHouseSslMode;
1615
import com.clickhouse.client.api.http.ClickHouseHttpProto;
1716
import com.clickhouse.client.api.transport.Endpoint;
18-
import com.clickhouse.client.config.ClickHouseDefaultSslContextProvider;
1917
import com.clickhouse.data.ClickHouseFormat;
2018
import net.jpountz.lz4.LZ4Factory;
2119
import org.apache.commons.compress.compressors.CompressorStreamFactory;
@@ -69,7 +67,6 @@
6967
import javax.net.ssl.HostnameVerifier;
7068
import javax.net.ssl.SNIHostName;
7169
import javax.net.ssl.SSLContext;
72-
import javax.net.ssl.SSLException;
7370
import javax.net.ssl.SSLParameters;
7471
import javax.net.ssl.SSLSocket;
7572
import java.io.IOException;
@@ -87,7 +84,6 @@
8784
import java.net.URLEncoder;
8885
import java.net.UnknownHostException;
8986
import java.nio.charset.StandardCharsets;
90-
import java.security.NoSuchAlgorithmException;
9187
import java.util.Arrays;
9288
import java.util.Base64;
9389
import java.util.Collection;
@@ -133,7 +129,7 @@ public class HttpAPIClientHelper {
133129

134130
LZ4Factory lz4Factory;
135131

136-
private final ClickHouseDefaultSslContextProvider sslContextProvider = new ClickHouseDefaultSslContextProvider();
132+
private final SslContextProvider sslContextProvider = new SslContextProvider();
137133

138134
public HttpAPIClientHelper(Map<String, Object> configuration, Object metricsRegistry, boolean initSslContext, LZ4Factory lz4Factory) {
139135
this.metricsRegistry = metricsRegistry;
@@ -161,56 +157,46 @@ public HttpAPIClientHelper(Map<String, Object> configuration, Object metricsRegi
161157
* @return SSLContext
162158
*/
163159
public SSLContext createSSLContext(Map<String, Object> configuration) {
164-
SSLContext sslContext;
165-
try {
166-
sslContext = SSLContext.getDefault();
167-
} catch (NoSuchAlgorithmException e) {
168-
throw new ClientException("Failed to create default SSL context", e);
169-
}
170160
final SSLMode sslMode = ClientConfigProperties.SSL_MODE.getOrDefault(configuration);
171161
final String trustStorePath = (String) configuration.get(ClientConfigProperties.SSL_TRUST_STORE.getKey());
172162
final String caCertificate = (String) configuration.get(ClientConfigProperties.CA_CERTIFICATE.getKey());
173163
final String sslCertificate = (String) configuration.get(ClientConfigProperties.SSL_CERTIFICATE.getKey());
174164
final String sslKey = (String) configuration.get(ClientConfigProperties.SSL_KEY.getKey());
175165

176-
// This method is only reached when a secure (https) endpoint is configured, so SSLMode.Disabled
177-
// contradicts the endpoint scheme. The mode does not turn encryption off - the scheme decides it.
178-
if (sslMode == SSLMode.Disabled) {
179-
throw new ClientMisconfigurationException("SSL mode '" + SSLMode.Disabled
180-
+ "' cannot be used with a secure (https) endpoint. Use SSLMode.Trust to trust all certificates or use plain HTTP");
166+
SslContextProvider.Builder builder = sslContextProvider.builder();
167+
168+
// The client certificate/key (mTLS) are independent of how the server certificate is verified,
169+
// so they are applied whenever configured, regardless of the SSL mode.
170+
if (sslCertificate != null && !sslCertificate.isEmpty()) {
171+
builder.clientCertificate(sslCertificate, sslKey);
181172
}
182173

183-
if (sslMode == SSLMode.Trust) {
184-
// Server certificate is not validated. Trust material (trust store or CA certificate)
185-
// is not needed, but client certificate and key are still applied for mTLS.
186-
try {
187-
sslContext = sslContextProvider.getSslContextFromCerts(ClickHouseSslMode.NONE,
188-
sslCertificate, sslKey, null);
189-
} catch (SSLException e) {
190-
throw new ClientMisconfigurationException("Failed to create SSL context for the Trust SSL mode", e);
174+
if (sslMode == SSLMode.TRUST) {
175+
// TRUST accepts any server certificate and skips the hostname check (the latter is applied
176+
// where the connection socket factory is created). A configured trust store or CA
177+
// certificate has no effect in this mode and is ignored with a warning.
178+
if (trustStorePath != null || caCertificate != null) {
179+
LOG.warn("SSL mode '{}' trusts any server certificate; the configured {} is ignored.",
180+
SSLMode.TRUST, trustStorePath != null ? "trust store" : "CA certificate");
191181
}
182+
builder.trustAllCertificates();
192183
} else if (trustStorePath != null) {
184+
// VERIFY_CA / STRICT: validate against the trust store. A trust store and a CA certificate
185+
// cannot both take effect, so the CA certificate is ignored with a warning.
193186
if (caCertificate != null) {
194-
throw new ClientMisconfigurationException("CA certificate cannot be used together with a trust store."
195-
+ " The CA certificate should be imported into the trust store instead.");
196-
}
197-
try {
198-
sslContext = sslContextProvider.getSslContextFromKeyStore(
199-
trustStorePath,
200-
(String) configuration.get(ClientConfigProperties.SSL_KEY_STORE_PASSWORD.getKey()),
201-
(String) configuration.get(ClientConfigProperties.SSL_KEYSTORE_TYPE.getKey())
202-
);
203-
} catch (SSLException e) {
204-
throw new ClientMisconfigurationException("Failed to create SSL context from a keystore", e);
205-
}
206-
} else if (caCertificate != null || sslCertificate != null|| sslKey != null) {
207-
try {
208-
sslContext = sslContextProvider.getSslContextFromCerts(sslCertificate, sslKey, caCertificate);
209-
} catch (SSLException e) {
210-
throw new ClientMisconfigurationException("Failed to create SSL context from certificates", e);
187+
LOG.warn("Both a trust store and a CA certificate are configured; using the trust store and"
188+
+ " ignoring the CA certificate. Import the CA certificate into the trust store instead.");
211189
}
190+
builder.trustStore(trustStorePath,
191+
(String) configuration.get(ClientConfigProperties.SSL_KEY_STORE_PASSWORD.getKey()),
192+
(String) configuration.get(ClientConfigProperties.SSL_KEYSTORE_TYPE.getKey()));
193+
} else if (caCertificate != null) {
194+
// VERIFY_CA / STRICT: validate against the CA certificate.
195+
builder.rootCertificate(caCertificate);
212196
}
213-
return sslContext;
197+
// else VERIFY_CA / STRICT with no trust material: the JVM default trust store is used.
198+
199+
return builder.build();
214200
}
215201

216202
private static final long CONNECTION_INACTIVITY_CHECK = 5000L;
@@ -299,7 +285,7 @@ public CloseableHttpClient createHttpClient(boolean initSslContext, Map<String,
299285
SSLMode sslMode = ClientConfigProperties.SSL_MODE.getOrDefault(configuration);
300286
// Trust and VerifyCa skip hostname verification. The same applies when a custom SNI is
301287
// set because the connection hostname will not match the certificate.
302-
boolean trustAllHostnames = sslMode == SSLMode.Trust || sslMode == SSLMode.VerifyCa;
288+
boolean trustAllHostnames = sslMode == SSLMode.TRUST || sslMode == SSLMode.VERIFY_CA;
303289
if (socketSNI != null && !socketSNI.trim().isEmpty() || trustAllHostnames) {
304290
sslConnectionSocketFactory = new CustomSSLConnectionFactory(socketSNI, sslContext, (hostname, session) -> true);
305291
} else {

0 commit comments

Comments
 (0)