diff --git a/CHANGELOG.md b/CHANGELOG.md index 186639517f1..72f2952807f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## 8.1.0 [unreleased] +### Features + +- [#948](https://github.com/influxdata/influxdb-client-java/pull/948): Support TLS and mTLS configurations. + ### Dependencies Update dependencies: diff --git a/client-core/src/test/java/com/influxdb/internal/RestClientTest.java b/client-core/src/test/java/com/influxdb/internal/RestClientTest.java index 0da95f5be9d..579d43cc6cd 100644 --- a/client-core/src/test/java/com/influxdb/internal/RestClientTest.java +++ b/client-core/src/test/java/com/influxdb/internal/RestClientTest.java @@ -26,6 +26,25 @@ import java.util.concurrent.CountDownLatch; import javax.annotation.Nonnull; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Protocol; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.ResponseBody; +import okhttp3.logging.HttpLoggingInterceptor; +import okhttp3.mockwebserver.MockResponse; +import okio.Buffer; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import retrofit2.Call; +import retrofit2.Response; +import retrofit2.Retrofit; +import retrofit2.http.GET; +import retrofit2.http.Headers; +import retrofit2.http.Path; + import com.influxdb.LogLevel; import com.influxdb.exceptions.BadGatewayException; import com.influxdb.exceptions.BadRequestException; @@ -46,25 +65,6 @@ import com.influxdb.exceptions.UnprocessableEntityException; import com.influxdb.test.AbstractMockServerTest; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Protocol; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.ResponseBody; -import okhttp3.logging.HttpLoggingInterceptor; -import okhttp3.mockwebserver.MockResponse; -import okio.Buffer; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import retrofit2.Call; -import retrofit2.Response; -import retrofit2.Retrofit; -import retrofit2.http.GET; -import retrofit2.http.Headers; -import retrofit2.http.Path; - /** * @author Jakub Bednar (bednar@github) (04/10/2018 07:57) */ diff --git a/client-utils/src/main/java/com/influxdb/utils/TlsUtils.java b/client-utils/src/main/java/com/influxdb/utils/TlsUtils.java new file mode 100644 index 00000000000..8b574d8cd18 --- /dev/null +++ b/client-utils/src/main/java/com/influxdb/utils/TlsUtils.java @@ -0,0 +1,251 @@ +/* + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.influxdb.utils; + +import java.io.FileInputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Base64; +import java.util.Locale; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + +public final class TlsUtils { + private static final String TLS = "TLS"; + private static final char[] DEFAULT_PASSWORD_CHAR_ARRAY = "".toCharArray(); + private static final String X509 = "X.509"; + private static final String PKCS12 = "PKCS12"; + + private TlsUtils() { + } + + /** + * Builds an {@link SSLContext} using the provided {@link KeyManagerFactory} and/or + * {@link TrustManagerFactory}. If both factories are null, this method returns null. + * + * @param kmf the {@link KeyManagerFactory} to use for key management, or null if no key management is required. + * @param tmf the {@link TrustManagerFactory} to use for trust management, or null if no trust management is + * required. + * @return an initialized {@link SSLContext}, or null if both input parameters are null. + * @throws Exception if an error occurs during the SSLContext initialization. + */ + @Nullable + public static SSLContext buildSslContext(@Nullable final KeyManagerFactory kmf, + @Nullable final TrustManagerFactory tmf) throws Exception { + if (kmf == null && tmf == null) { + return null; + } + + SSLContext sslContext = SSLContext.getInstance(TLS); + sslContext.init(kmf != null ? kmf.getKeyManagers() : null, tmf != null ? tmf.getTrustManagers() : null, null); + return sslContext; + } + + /** + * Retrieves an instance of {@link X509TrustManager} from the provided {@link TrustManagerFactory}. + * If the input TrustManagerFactory is null, a default TrustManagerFactory is created and initialized. + * + * @param tmf the {@link TrustManagerFactory} to retrieve the {@link X509TrustManager} from, + * or null to use a default {@link TrustManagerFactory}. + * @return an instance of {@link X509TrustManager} initialized from the given or + * default {@link TrustManagerFactory}. + * @throws Exception if an error occurs during the initialization or retrieval of the {@link X509TrustManager}. + */ + @Nonnull + public static X509TrustManager getX509TrustManager(@Nullable final TrustManagerFactory tmf) throws Exception { + TrustManagerFactory factory = tmf; + if (factory == null) { + factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + factory.init((KeyStore) null); + } + return (X509TrustManager) factory.getTrustManagers()[0]; + } + + /** + * Loads a private key from a file specified by the provided path. The key must be in PKCS#8 format and unencrypted. + * Encrypted private keys are not supported. + * + * @param path the file system path to the private key file; must not be null. + * @return the {@link PrivateKey} object loaded from the provided file path. + * @throws IllegalArgumentException if the private key is encrypted or in an unsupported format. + * @throws Exception if an error occurs during file reading, Base64 decoding, or key generation. + */ + public static PrivateKey loadPrivateKey(@Nonnull final String path) throws Exception { + String keyPem = Files.readString(Paths.get(path)); + if (keyPem.contains("-----BEGIN ENCRYPTED PRIVATE KEY-----")) { + throw new IllegalArgumentException("Encrypted PKCS#8 private keys are not supported. Use an unencrypted " + + "PKCS#8 key or a PKCS#12 file."); + } + + String privateKeyPEM = keyPem + .replace("-----BEGIN PRIVATE KEY-----", "") + .replace("-----END PRIVATE KEY-----", "") + .replaceAll("\\s+", ""); + + byte[] encoded = Base64.getDecoder().decode(privateKeyPEM); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded); + GeneralSecurityException lastException = null; + for (String algorithm : new String[]{"RSA", "EC", "DSA"}) { + try { + return KeyFactory.getInstance(algorithm).generatePrivate(keySpec); + } catch (GeneralSecurityException e) { + lastException = e; + } + } + + throw new GeneralSecurityException("Unsupported private key algorithm", lastException); + } + + /** + * Creates and initializes a {@link KeyManagerFactory} using a PKCS#12 keystore file + * located at the specified path. This method loads the keystore using the provided + * password (or a default password if none is provided) and initializes a + * {@link KeyManagerFactory} with it. + * + * @param path the file path to the PKCS#12 keystore; must not be null. + * @param password the password for the keystore, or null/empty if the default password + * should be used. + * @return a {@link KeyManagerFactory} instance initialized with the provided keystore. + * @throws Exception if an error occurs while reading the file, loading the keystore, + * or initializing the {@link KeyManagerFactory}. + */ + public static KeyManagerFactory createKmfP12(@Nonnull final String path, + @Nullable final char[] password) throws Exception { + char[] pass = password != null && password.length > 0 ? password : DEFAULT_PASSWORD_CHAR_ARRAY; + KeyStore keyStore = KeyStore.getInstance(PKCS12); + try (FileInputStream fis = new FileInputStream(path)) { + keyStore.load(fis, pass); + } + + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(keyStore, pass); + return kmf; + } + + /** + * Creates and initializes a {@link KeyManagerFactory} using the specified certificate and private key files. + * The method loads the certificate chain from the provided `certPath` and the private key from the provided + * `keyPath`. + * These are stored in a {@link KeyStore}, which is then used to initialize the {@link KeyManagerFactory}. + * + * @param certPath the file path to the certificate chain in X.509 format; must not be null. + * @param keyPath the file path to the private key in PKCS#8 format; must not be null. + * @return a {@link KeyManagerFactory} instance initialized with the provided certificate and private key. + * @throws Exception if an error occurs while reading the files, loading the credentials, or initializing + * the {@link KeyManagerFactory}. + */ + public static KeyManagerFactory createKmf(@Nonnull final String certPath, + @Nonnull final String keyPath) throws Exception { + java.util.Collection extends Certificate> certificateChain; + try (FileInputStream fis = new FileInputStream(certPath)) { + certificateChain = CertificateFactory.getInstance(X509).generateCertificates(fis); + } + + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(null, null); + keyStore.setKeyEntry("alias", + TlsUtils.loadPrivateKey(keyPath), + null, + certificateChain.toArray(new Certificate[0])); + + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(keyStore, null); + + return kmf; + } + + /** + * Creates and initializes a TrustManagerFactory from a PKCS#12 keystore located at the specified path. + * This method loads the keystore using the provided password and initializes a TrustManagerFactory with it. + * + * @param path the file path to the PKCS#12 keystore; must not be null. + * @param password the password for the keystore, or null/empty if the default password should be used. + * @return a TrustManagerFactory instance initialized with the provided keystore. + * @throws Exception if an error occurs while reading the file, loading the keystore, + * or initializing the TrustManagerFactory. + */ + public static TrustManagerFactory createTmfP12(@Nonnull final String path, + @Nullable final char[] password) throws Exception { + char[] pass = password != null && password.length > 0 ? password : DEFAULT_PASSWORD_CHAR_ARRAY; + + KeyStore trustStore = KeyStore.getInstance("PKCS12"); + try (FileInputStream fis = new FileInputStream(path)) { + trustStore.load(fis, pass); + } + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(trustStore); + + return tmf; + } + + /** + * Creates and initializes a TrustManagerFactory from the provided certificate file. + * The file format is determined based on its extension. Supported formats include: + * - .p12 or .pfx: Loaded as a PKCS#12 keystore. + * - .crt, .cert, or .pem: Loaded as individual X.509 certificates. + * + * @param path the file path to the certificate or keystore; must not be null. + * @param password the password for the keystore, or null if not required. + * @return a TrustManagerFactory instance initialized with the provided certificates or keystore. + * @throws Exception if an error occurs while reading the file, processing the certificates, + * or initializing the TrustManagerFactory. + * @throws IllegalArgumentException if the provided file format is unsupported. + */ + public static TrustManagerFactory createTmf(@Nonnull final String path, + @Nullable final char[] password) throws Exception { + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + + String extension = path.toLowerCase(Locale.ROOT); + + if (extension.endsWith(".p12") || extension.endsWith(".pfx")) { + char[] pass = password != null && password.length > 0 ? password : DEFAULT_PASSWORD_CHAR_ARRAY; + tmf = createTmfP12(path, pass); + } else if (extension.endsWith(".crt") || extension.endsWith(".cert") || extension.endsWith(".pem")) { + KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); + trustStore.load(null, null); + + CertificateFactory certFactory = CertificateFactory.getInstance(X509); + try (FileInputStream fis = new FileInputStream(path)) { + int alias = 0; + for (Certificate certificate : certFactory.generateCertificates(fis)) { + trustStore.setCertificateEntry("alias-" + alias++, certificate); + } + } + tmf.init(trustStore); + } else { + throw new IllegalArgumentException("Unsupported certificate format"); + } + + return tmf; + } +} diff --git a/client/src/main/java/com/influxdb/client/InfluxDBClientOptions.java b/client/src/main/java/com/influxdb/client/InfluxDBClientOptions.java index 6ae4e0746a2..6c758a8e802 100644 --- a/client/src/main/java/com/influxdb/client/InfluxDBClientOptions.java +++ b/client/src/main/java/com/influxdb/client/InfluxDBClientOptions.java @@ -32,6 +32,10 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.concurrent.NotThreadSafe; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; import com.influxdb.LogLevel; import com.influxdb.client.domain.WriteConsistency; @@ -40,6 +44,7 @@ import com.influxdb.client.write.WriteParameters; import com.influxdb.exceptions.InfluxException; import com.influxdb.utils.Arguments; +import com.influxdb.utils.TlsUtils; import okhttp3.HttpUrl; import okhttp3.OkHttpClient; @@ -65,6 +70,15 @@ public final class InfluxDBClientOptions { private final String username; private final char[] password; + private final String certificatePath; + private final String certificateKeyPath; + private final String certificateP12FilePath; + private final char[] keyPassword; + + private final String trustFilePath; + private final char[] trustFilePassword; + + private final String org; private final String bucket; private final WritePrecision precision; @@ -89,6 +103,13 @@ private InfluxDBClientOptions(@Nonnull final InfluxDBClientOptions.Builder build this.precision = builder.precision != null ? builder.precision : WriteParameters.DEFAULT_WRITE_PRECISION; this.consistency = builder.consistency; this.pointSettings = builder.pointSettings; + + this.certificatePath = builder.certificatePath; + this.certificateKeyPath = builder.certificateKeyPath; + this.certificateP12FilePath = builder.certificateP12FilePath; + this.keyPassword = builder.keyPassword; + this.trustFilePath = builder.trustFilePath; + this.trustFilePassword = builder.trustFilePassword; } /** @@ -238,6 +259,66 @@ public PointSettings getPointSettings() { return pointSettings; } + /** + * Retrieves the file path of the certificate used for secure communication. + * + * @return the file path of the certificate, or null if no certificate path is specified + */ + @Nullable + public String getCertificatePath() { + return certificatePath; + } + + /** + * Retrieves the file path of the certificate key used for secure communication. + * + * @return the file path of the certificate key, or null if no certificate key path is specified + */ + @Nullable + public String getCertificateKeyPath() { + return certificateKeyPath; + } + + /** + * Retrieves the file path of the PKCS#12 (P12) certificate used for secure communication. + * + * @return the file path of the PKCS#12 certificate, or null if no certificate path is specified + */ + @Nullable + public String getCertificateP12FilePath() { + return certificateP12FilePath; + } + + /** + * Retrieves the password for the key used in secure communication. + * + * @return the key password as a character array, or null if no key password is specified + */ + @Nullable + public char[] getKeyPassword() { + return keyPassword; + } + + /** + * Retrieves the file path of the trust store used for secure communication. + * + * @return the file path of the trust store, or null if no trust store path is specified + */ + @Nullable + public String getTrustFilePath() { + return trustFilePath; + } + + /** + * Retrieves the password for the trust store used in secure communication. + * + * @return the trust store password as a character array, or null if no trust store password is specified + */ + @Nullable + public char[] getTrustFilePassword() { + return trustFilePassword; + } + /** * Creates a builder instance. * @@ -264,6 +345,14 @@ public static class Builder { private String username; private char[] password; + private String certificatePath; + private String certificateKeyPath; + private String certificateP12FilePath; + private char[] keyPassword; + + private String trustFilePath; + private char[] trustFilePassword; + private String org; private String bucket; private WritePrecision precision; @@ -448,6 +537,54 @@ public InfluxDBClientOptions.Builder consistency(@Nullable final WriteConsistenc return this; } + /** + * Sets the file paths for the certificate and its corresponding private key for secure connections. + * + * @param certificatePath the file path to the certificate file in PEM format, must not be null. + * @param certificateKeyPath the file path to the certificate's private key in PEM format, must not be null. + * @return the updated {@link InfluxDBClientOptions.Builder} instance. + */ + @Nonnull + public InfluxDBClientOptions.Builder certificateFilePath(@Nonnull final String certificatePath, + @Nonnull final String certificateKeyPath) { + this.certificatePath = certificatePath; + this.certificateKeyPath = certificateKeyPath; + + return this; + } + + /** + * Sets the file path to the certificate in P12 format and the optional password for the certificate key. + * + * @param p12FilePath the file path to the P12 certificate. Must not be null. + * @param password the optional password for the certificate key. Can be null if no password is required. + * @return the Builder instance for method chaining. + */ + @Nonnull + public InfluxDBClientOptions.Builder certificateP12FilePath(@Nonnull final String p12FilePath, + @Nullable final char[] password) { + this.certificateP12FilePath = p12FilePath; + this.keyPassword = password; + + return this; + } + + /** + * Sets the file path to the trusted certificate for SSL/TLS communication. + * + * @param trustFilePath the file path to the trusted certificate; must not be null. + * @param password the password for the trusted certificate file; can be null if not required. + * @return the updated {@link InfluxDBClientOptions.Builder} instance. + */ + @Nonnull + public InfluxDBClientOptions.Builder trustFilePath(@Nonnull final String trustFilePath, + @Nullable final char[] password) { + this.trustFilePath = trustFilePath; + this.trustFilePassword = password; + + return this; + } + /** * Add default tag that will be use for writes by Point and POJO. *
@@ -584,6 +721,11 @@ public InfluxDBClientOptions build() {
.protocols(Collections.singletonList(Protocol.HTTP_1_1));
}
+ HttpUrl parsedUrl = HttpUrl.parse(url);
+ if (parsedUrl != null && parsedUrl.isHttps()) {
+ configureTls(okHttpClient);
+ }
+
if (logLevel == null) {
logLevel = LogLevel.NONE;
}
@@ -591,6 +733,50 @@ public InfluxDBClientOptions build() {
return new InfluxDBClientOptions(this);
}
+ /**
+ * Configures TLS for an OkHttpClient by setting up SSL context and trust managers based on
+ * provided certificate paths or trust file paths.
+ *
+ * @param okHttpClient an OkHttpClient.Builder instance on which TLS configuration will be applied.
+ * This is required to establish secure connections with the server.
+ * @throws IllegalArgumentException if both {@code certificatePath} and {@code certificateP12FilePath}
+ * are set, as only one can be specified at a time.
+ * @throws InfluxException if there is an error during the TLS configuration process, such as issues
+ * with loading the certificate, trust file, or setting up the SSL context.
+ */
+ private void configureTls(@Nonnull final OkHttpClient.Builder okHttpClient) {
+
+ if (certificatePath != null && certificateP12FilePath != null) {
+ throw new IllegalArgumentException("Cannot set both p12FilePath and certificatePath");
+ }
+
+ if (certificatePath == null && certificateP12FilePath == null && trustFilePath == null) {
+ return;
+ }
+
+ try {
+ TrustManagerFactory tmf = null;
+ if (trustFilePath != null) {
+ tmf = TlsUtils.createTmf(trustFilePath, trustFilePassword);
+ }
+
+ KeyManagerFactory kmf = null;
+ if (certificatePath != null) {
+ kmf = TlsUtils.createKmf(certificatePath, certificateKeyPath);
+ } else if (certificateP12FilePath != null) {
+ kmf = TlsUtils.createKmfP12(certificateP12FilePath, keyPassword);
+ }
+
+ SSLContext sslContext = TlsUtils.buildSslContext(kmf, tmf);
+ if (sslContext != null) {
+ X509TrustManager trustManager = TlsUtils.getX509TrustManager(tmf);
+ okHttpClient.sslSocketFactory(sslContext.getSocketFactory(), trustManager);
+ }
+ } catch (Exception e) {
+ throw new InfluxException(e);
+ }
+ }
+
@Nonnull
private InfluxDBClientOptions.Builder configure(@Nonnull final String url,
@Nullable final String org,
diff --git a/client/src/test/java/com/influxdb/client/InfluxDBClientOptionsTest.java b/client/src/test/java/com/influxdb/client/InfluxDBClientOptionsTest.java
index c95ae134bd6..6a6cf6295ab 100644
--- a/client/src/test/java/com/influxdb/client/InfluxDBClientOptionsTest.java
+++ b/client/src/test/java/com/influxdb/client/InfluxDBClientOptionsTest.java
@@ -27,8 +27,9 @@
import java.util.Map;
import com.influxdb.client.domain.WritePrecision;
-
import com.influxdb.exceptions.InfluxException;
+import com.influxdb.utils.TlsUtils;
+
import okhttp3.OkHttpClient;
import okhttp3.Protocol;
import org.assertj.core.api.Assertions;
@@ -153,6 +154,35 @@ public void customClientTypeFromConnectionString() {
Assertions.assertThat(options.getClientType()).isEqualTo("url-service");
}
+ @Test
+ public void tlsFilesConfig() {
+ String tlsDir = "src/test/java/com/influxdb/client/tls/";
+ String influxdbCertPath = tlsDir + "influxdb.crt";
+ String clientCertPath = tlsDir + "client.crt";
+ String clientKeyPath = tlsDir + "client.key";
+ String clientP12 = tlsDir + "client.p12";
+
+ InfluxDBClientOptions options = InfluxDBClientOptions.builder()
+ .url("http://localhost:8086")
+ .trustFilePath(influxdbCertPath, null)
+ .certificateFilePath(clientCertPath, clientKeyPath)
+ .build();
+
+ Assertions.assertThat(options.getTrustFilePath()).isEqualTo(influxdbCertPath);
+ Assertions.assertThat(options.getCertificatePath()).isEqualTo(clientCertPath);
+ Assertions.assertThat(options.getCertificateKeyPath()).isEqualTo(clientKeyPath);
+
+ // For .p12 files
+ var password = "changeit".toCharArray();
+ InfluxDBClientOptions options1 = InfluxDBClientOptions.builder()
+ .url("http://localhost:8086")
+ .certificateP12FilePath(clientP12, password)
+ .build();
+
+ Assertions.assertThat(options1.getCertificateP12FilePath()).isEqualTo(clientP12);
+ Assertions.assertThat(options1.getKeyPassword()).isEqualTo(password);
+ }
+
@Test
public void customClientTypeFromProperties() {
InfluxDBClientOptions options = InfluxDBClientOptions.builder().loadProperties().build();
@@ -223,7 +253,58 @@ public void ipv6Invalid(){
.build();}).isInstanceOf(InfluxException.class)
.hasMessage(String.format("Unable to parse connection string http://%s:9999/api/v2/query?orgID=my-org", ipv6));
}
+ }
+ @Test
+ void tlsBothCertificateAndP12Configured() {
+ Assertions.assertThatThrownBy(() -> InfluxDBClientOptions.builder()
+ .url("https://localhost:9999")
+ .certificateFilePath("cert.pem", "key.pem")
+ .certificateP12FilePath("client.p12", null)
+ .build())
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Cannot set both p12FilePath and certificatePath");
}
+ @Test
+ void tlsOnlyClientCertificatesConfigured() {
+ String clientCertPath = "src/test/java/com/influxdb/client/tls/client.crt";
+ String clientKeyPath = "src/test/java/com/influxdb/client/tls/client.key";
+
+ InfluxDBClientOptions options = InfluxDBClientOptions.builder()
+ .url("https://localhost:9999")
+ .certificateFilePath(clientCertPath, clientKeyPath)
+ .build();
+
+ Assertions.assertThat(options.getOkHttpClient()).isNotNull();
+ }
+
+ @Test
+ void tlsOnlyClientP12Configured() {
+ String clientP12 = "src/test/java/com/influxdb/client/tls/client.p12";
+
+ InfluxDBClientOptions options = InfluxDBClientOptions.builder()
+ .url("https://localhost:9999")
+ .certificateP12FilePath(clientP12, "changeit".toCharArray())
+ .build();
+
+ Assertions.assertThat(options.getOkHttpClient()).isNotNull();
+ }
+
+ @Test
+ void tlsInvalidCertificatePath() {
+ Assertions.assertThatThrownBy(() -> InfluxDBClientOptions.builder()
+ .url("https://localhost:9999")
+ .certificateFilePath("non_existing_file.pem", "non_existing_file.key")
+ .build())
+ .isInstanceOf(InfluxException.class);
+ }
+
+ @Test
+ void encryptedPemKeyIsRejected() {
+ Assertions.assertThatThrownBy(() ->
+ TlsUtils.loadPrivateKey("src/test/java/com/influxdb/client/tls/client_pkcs8.key"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("Encrypted PKCS#8 private keys are not supported");
+ }
}
\ No newline at end of file
diff --git a/client/src/test/java/com/influxdb/client/InfluxDBClientTest.java b/client/src/test/java/com/influxdb/client/InfluxDBClientTest.java
index d706e4d9e3b..8e2d3fc75fd 100644
--- a/client/src/test/java/com/influxdb/client/InfluxDBClientTest.java
+++ b/client/src/test/java/com/influxdb/client/InfluxDBClientTest.java
@@ -30,6 +30,10 @@
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import javax.annotation.Nonnull;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
import okhttp3.HttpUrl;
import okhttp3.Interceptor;
@@ -53,6 +57,7 @@
import com.influxdb.client.domain.WritePrecision;
import com.influxdb.client.internal.AbstractInfluxDBClientTest;
import com.influxdb.client.service.InfluxQLQueryService;
+import com.influxdb.utils.TlsUtils;
/**
* @author Jakub Bednar (bednar@github) (05/09/2018 14:00)
@@ -421,6 +426,130 @@ public Response intercept(@Nonnull final Chain chain) throws IOException {
proxy.shutdown();
}
+ String tlsDir = "src/test/java/com/influxdb/client/tls/";
+
+ String influxdbCertPath = tlsDir + "influxdb.crt";
+ String influxdbKeyPath = tlsDir + "influxdb.key";
+ String influxdbP12 = tlsDir + "influxdb.p12";
+
+ String otherCertPath = tlsDir + "other-server.crt";
+ String otherKeyCertPath = tlsDir + "other-server.key";
+ String otherP12 = tlsDir + "other-server.p12";
+
+ String clientCertPath = tlsDir + "client.crt";
+ String clientKeyPath = tlsDir + "client.key";
+ String clientP12 = tlsDir + "client.p12";
+
+ char[] defaultPassword = "changeit".toCharArray();
+
+ record TlsTest(boolean isMutualTls, boolean isP12) {
+ }
+
+ private static List