|
12 | 12 | import com.clickhouse.client.api.ServerException; |
13 | 13 | import com.clickhouse.client.api.enums.ProxyType; |
14 | 14 | import com.clickhouse.client.api.enums.SSLMode; |
15 | | -import com.clickhouse.client.config.ClickHouseSslMode; |
16 | 15 | import com.clickhouse.client.api.http.ClickHouseHttpProto; |
17 | 16 | import com.clickhouse.client.api.transport.Endpoint; |
18 | | -import com.clickhouse.client.config.ClickHouseDefaultSslContextProvider; |
19 | 17 | import com.clickhouse.data.ClickHouseFormat; |
20 | 18 | import net.jpountz.lz4.LZ4Factory; |
21 | 19 | import org.apache.commons.compress.compressors.CompressorStreamFactory; |
|
69 | 67 | import javax.net.ssl.HostnameVerifier; |
70 | 68 | import javax.net.ssl.SNIHostName; |
71 | 69 | import javax.net.ssl.SSLContext; |
72 | | -import javax.net.ssl.SSLException; |
73 | 70 | import javax.net.ssl.SSLParameters; |
74 | 71 | import javax.net.ssl.SSLSocket; |
75 | 72 | import java.io.IOException; |
|
87 | 84 | import java.net.URLEncoder; |
88 | 85 | import java.net.UnknownHostException; |
89 | 86 | import java.nio.charset.StandardCharsets; |
90 | | -import java.security.NoSuchAlgorithmException; |
91 | 87 | import java.util.Arrays; |
92 | 88 | import java.util.Base64; |
93 | 89 | import java.util.Collection; |
@@ -133,7 +129,7 @@ public class HttpAPIClientHelper { |
133 | 129 |
|
134 | 130 | LZ4Factory lz4Factory; |
135 | 131 |
|
136 | | - private final ClickHouseDefaultSslContextProvider sslContextProvider = new ClickHouseDefaultSslContextProvider(); |
| 132 | + private final SslContextProvider sslContextProvider = new SslContextProvider(); |
137 | 133 |
|
138 | 134 | public HttpAPIClientHelper(Map<String, Object> configuration, Object metricsRegistry, boolean initSslContext, LZ4Factory lz4Factory) { |
139 | 135 | this.metricsRegistry = metricsRegistry; |
@@ -161,56 +157,46 @@ public HttpAPIClientHelper(Map<String, Object> configuration, Object metricsRegi |
161 | 157 | * @return SSLContext |
162 | 158 | */ |
163 | 159 | 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 | | - } |
170 | 160 | final SSLMode sslMode = ClientConfigProperties.SSL_MODE.getOrDefault(configuration); |
171 | 161 | final String trustStorePath = (String) configuration.get(ClientConfigProperties.SSL_TRUST_STORE.getKey()); |
172 | 162 | final String caCertificate = (String) configuration.get(ClientConfigProperties.CA_CERTIFICATE.getKey()); |
173 | 163 | final String sslCertificate = (String) configuration.get(ClientConfigProperties.SSL_CERTIFICATE.getKey()); |
174 | 164 | final String sslKey = (String) configuration.get(ClientConfigProperties.SSL_KEY.getKey()); |
175 | 165 |
|
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); |
181 | 172 | } |
182 | 173 |
|
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"); |
191 | 181 | } |
| 182 | + builder.trustAllCertificates(); |
192 | 183 | } 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. |
193 | 186 | 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."); |
211 | 189 | } |
| 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); |
212 | 196 | } |
213 | | - return sslContext; |
| 197 | + // else VERIFY_CA / STRICT with no trust material: the JVM default trust store is used. |
| 198 | + |
| 199 | + return builder.build(); |
214 | 200 | } |
215 | 201 |
|
216 | 202 | private static final long CONNECTION_INACTIVITY_CHECK = 5000L; |
@@ -299,7 +285,7 @@ public CloseableHttpClient createHttpClient(boolean initSslContext, Map<String, |
299 | 285 | SSLMode sslMode = ClientConfigProperties.SSL_MODE.getOrDefault(configuration); |
300 | 286 | // Trust and VerifyCa skip hostname verification. The same applies when a custom SNI is |
301 | 287 | // 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; |
303 | 289 | if (socketSNI != null && !socketSNI.trim().isEmpty() || trustAllHostnames) { |
304 | 290 | sslConnectionSocketFactory = new CustomSSLConnectionFactory(socketSNI, sslContext, (hostname, session) -> true); |
305 | 291 | } else { |
|
0 commit comments