From e76d259818faf3b1282a3dab1cab306cbcb3d038 Mon Sep 17 00:00:00 2001 From: NguyenHoangSon96 Date: Tue, 29 Sep 2026 00:07:47 +0700 Subject: [PATCH 1/2] feat: support tls --- CHANGELOG.md | 4 + .../com/influxdb/internal/RestClientTest.java | 38 +-- .../java/com/influxdb/utils/TlsUtils.java | 250 ++++++++++++++++++ .../client/InfluxDBClientOptions.java | 186 +++++++++++++ .../client/InfluxDBClientOptionsTest.java | 83 +++++- .../influxdb/client/InfluxDBClientTest.java | 156 +++++++++++ .../java/com/influxdb/client/tls/client.crt | 20 ++ .../java/com/influxdb/client/tls/client.key | 28 ++ .../java/com/influxdb/client/tls/client.p12 | Bin 0 -> 2682 bytes .../com/influxdb/client/tls/client_pkcs8.key | 30 +++ .../client/tls/generate-self-signed-cert.sh | 52 ++++ .../java/com/influxdb/client/tls/influxdb.crt | 21 ++ .../java/com/influxdb/client/tls/influxdb.key | 28 ++ .../java/com/influxdb/client/tls/influxdb.p12 | Bin 0 -> 2714 bytes .../com/influxdb/client/tls/other-server.crt | 21 ++ .../com/influxdb/client/tls/other-server.key | 28 ++ .../com/influxdb/client/tls/other-server.p12 | Bin 0 -> 2714 bytes pom.xml | 2 +- 18 files changed, 926 insertions(+), 21 deletions(-) create mode 100644 client-utils/src/main/java/com/influxdb/utils/TlsUtils.java create mode 100644 client/src/test/java/com/influxdb/client/tls/client.crt create mode 100644 client/src/test/java/com/influxdb/client/tls/client.key create mode 100644 client/src/test/java/com/influxdb/client/tls/client.p12 create mode 100644 client/src/test/java/com/influxdb/client/tls/client_pkcs8.key create mode 100644 client/src/test/java/com/influxdb/client/tls/generate-self-signed-cert.sh create mode 100644 client/src/test/java/com/influxdb/client/tls/influxdb.crt create mode 100644 client/src/test/java/com/influxdb/client/tls/influxdb.key create mode 100644 client/src/test/java/com/influxdb/client/tls/influxdb.p12 create mode 100644 client/src/test/java/com/influxdb/client/tls/other-server.crt create mode 100644 client/src/test/java/com/influxdb/client/tls/other-server.key create mode 100644 client/src/test/java/com/influxdb/client/tls/other-server.p12 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..b37cec83530 --- /dev/null +++ b/client-utils/src/main/java/com/influxdb/utils/TlsUtils.java @@ -0,0 +1,250 @@ +/* + * 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 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 tlsCases() { + return List.of( + new TlsTest(false, false), + new TlsTest(false, true), + new TlsTest(true, false), + new TlsTest(true, true) + ); + } + + @Test + public void testMutualTlsSuccess() throws Exception { + for (TlsTest tlsCase : tlsCases()) { + boolean isMutualTls = tlsCase.isMutualTls; + boolean isP12 = tlsCase.isP12; + + MockWebServer mockServer = getMutualTlsMockServer(isMutualTls, isP12); + try { + InfluxDBClientOptions.Builder options = InfluxDBClientOptions.builder() + .url(mockServer.url("/").url().toString()) + .authenticateToken("my-token".toCharArray()) + .trustFilePath(isP12 ? influxdbP12 : influxdbCertPath, defaultPassword); + + if (isMutualTls) { + if (isP12) { + options.certificateP12FilePath(clientP12, defaultPassword); + } else { + options.certificateFilePath(clientCertPath, clientKeyPath); + } + } + + try (InfluxDBClient client = InfluxDBClientFactory.create(options.build())) { + client.version(); + } + } finally { + mockServer.shutdown(); + } + } + } + + @Test + public void testMutualTlsFailClientUntrustedServer() throws Exception { + // Client trusts other cert, but server uses influxdb cert -> handshake should fail + for (boolean isP12 : List.of(false, true)) { + MockWebServer mockServer = getMutualTlsMockServer(false, isP12); + InfluxDBClientOptions.Builder options = InfluxDBClientOptions.builder() + .url(mockServer.url("/").url().toString()) + .authenticateToken("my-token".toCharArray()); + if (isP12) { + options.trustFilePath(otherP12, defaultPassword); + } else { + options.trustFilePath(otherCertPath, defaultPassword); + } + + try (InfluxDBClient client = InfluxDBClientFactory.create(options.build())) { + Assertions.assertThatThrownBy(client::version) + .isInstanceOf(com.influxdb.exceptions.InfluxException.class); + } finally { + mockServer.shutdown(); + } + } + } + + @Test + public void testMutualTlsFailServerUntrustedClient() throws Exception { + // Server requires client auth and trusts client cert, but client provides wrong (other) cert -> handshake should fail + for (boolean isP12 : List.of(false, true)) { + MockWebServer mockServer = getMutualTlsMockServer(true, isP12); + InfluxDBClientOptions.Builder options = InfluxDBClientOptions.builder() + .url(mockServer.url("/").url().toString()) + .authenticateToken("my-token".toCharArray()); + + if (isP12) { + options.certificateP12FilePath(otherP12, defaultPassword) + .trustFilePath(influxdbP12, defaultPassword); + } else { + options.certificateFilePath(otherCertPath, otherKeyCertPath) + .trustFilePath(influxdbCertPath, defaultPassword); + } + + try (InfluxDBClient client = InfluxDBClientFactory.create(options.build())) { + Assertions.assertThatThrownBy(client::version) + .isInstanceOf(com.influxdb.exceptions.InfluxException.class); + } finally { + mockServer.shutdown(); + } + } + } + + @Test + public void testMutualTlsSuccessDifferentFileTypes() throws Exception { + MockWebServer mockServer = getMutualTlsMockServer(true, true); + InfluxDBClientOptions options = InfluxDBClientOptions.builder() + .url(mockServer.url("/").url().toString()) + .certificateFilePath(clientCertPath, clientKeyPath) + .trustFilePath(influxdbP12, defaultPassword) + .authenticateToken("my-token".toCharArray()) + .build(); + + try (InfluxDBClient client = InfluxDBClientFactory.create(options)) { + client.version(); + } finally { + mockServer.shutdown(); + } + } + @Test public void connectionStringPrecision() { InfluxDBClientOptions options = InfluxDBClientOptions.builder() @@ -547,4 +676,31 @@ private void queryAndTest(final String expected) throws InterruptedException { Assertions.assertThat(request.getRequestUrl()).isNotNull(); Assertions.assertThat(request.getRequestUrl().toString()).isEqualTo(expected + "?org=my-org"); } + + private MockWebServer getMutualTlsMockServer(final boolean isMutualTls, final boolean isP12) throws Exception { + KeyManagerFactory kmf; + TrustManagerFactory tmf; + + + if (isP12) { + kmf = TlsUtils.createKmfP12(influxdbP12, defaultPassword); + tmf = isMutualTls ? TlsUtils.createTmfP12(clientP12, defaultPassword) : null; + } else { + kmf = TlsUtils.createKmf(influxdbCertPath, influxdbKeyPath); + tmf = isMutualTls ? TlsUtils.createTmf(clientCertPath, defaultPassword) : null; + } + + SSLContext sslContext = TlsUtils.buildSslContext(kmf, tmf); + + var mockServer = new MockWebServer(); + mockServer.useHttps(sslContext.getSocketFactory(), false); + + if (isMutualTls) { + mockServer.requireClientAuth(); + } + mockServer.start(); + mockServer.enqueue(createResponse("ok")); + + return mockServer; + } } \ No newline at end of file diff --git a/client/src/test/java/com/influxdb/client/tls/client.crt b/client/src/test/java/com/influxdb/client/tls/client.crt new file mode 100644 index 00000000000..b9c82617ca0 --- /dev/null +++ b/client/src/test/java/com/influxdb/client/tls/client.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDUzCCAjugAwIBAgIUOAIDGap+seWtPsjBPcEsPOAuPs8wDQYJKoZIhvcNAQEL +BQAwOTEUMBIGA1UEAwwLdGVzdC1jbGllbnQxFDASBgNVBAoMC0RldmVsb3BtZW50 +MQswCQYDVQQGEwJVUzAeFw0yNjA5MjkxMDI5NDNaFw0zNjA5MjYxMDI5NDNaMDkx +FDASBgNVBAMMC3Rlc3QtY2xpZW50MRQwEgYDVQQKDAtEZXZlbG9wbWVudDELMAkG +A1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+27lZ3TDT +EK8rvgmFA4VM+632NaDzzRPs+hNO62vLbjgirimRY1NImnR/Nd5Bjxz04nwKvnyg +gegOxdxeGY+1U+ErMexP7LZ5rpJyI6XF9AjLPN1G96seYq5/B5QghR7Lm4O6Veag +P3EJJ8m1Ul4PGjvPD+fPMXV0PEo38kSS0//DCFVy5vkvhoIba44HDSdaw0Pm3Fuc +5AncV73IX9H3qdvWdyXwQH+0OLqcB09If0hifHEnChuD1HAJYMRIk0ylGs7W3ftS +vN1emJj3SJLAKKwhxN5SuHGJBL5st1ZT6/cM/ksUlUWPxMhlGoFBfqte6Xb+D2/z +7XvAn2X60TcPAgMBAAGjUzBRMB0GA1UdDgQWBBTcwxlCIzoI5kmr53H14yhkmWqA +wjAfBgNVHSMEGDAWgBTcwxlCIzoI5kmr53H14yhkmWqAwjAPBgNVHRMBAf8EBTAD +AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBN7VCH9faju4KlGdaVT5OhNqvCpA/mWoUX +clwbNXrZuVd5VoUCn/r2TjTWH3S4E1e0Rq2pgYFfZfl65tmcZAgL5v6H2c9aTdrP +VeD7FZE3+oU7Pv170u0u8+KmVFBradR651cmosXfs7mVqvVAUejXjrECn5JbO74s +ghWu+mZm5EiVCiImkKx9LpEOs6t+LZWYJmS8ZyyvKEck9m8nYn5NxiR1V9Lh6eR7 +FrDG8mpUK9ZMedmVcO0XKldSgyTOiCjX9cw+MvnZTkGcbGosGddBTV6s+JHxNwL4 +6Ex29IEPb/+Nx7pD19iJcjxInLRpKmjKndsYsf1U/kxA2W0Xbp5E +-----END CERTIFICATE----- diff --git a/client/src/test/java/com/influxdb/client/tls/client.key b/client/src/test/java/com/influxdb/client/tls/client.key new file mode 100644 index 00000000000..091953b0684 --- /dev/null +++ b/client/src/test/java/com/influxdb/client/tls/client.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC+27lZ3TDTEK8r +vgmFA4VM+632NaDzzRPs+hNO62vLbjgirimRY1NImnR/Nd5Bjxz04nwKvnyggegO +xdxeGY+1U+ErMexP7LZ5rpJyI6XF9AjLPN1G96seYq5/B5QghR7Lm4O6VeagP3EJ +J8m1Ul4PGjvPD+fPMXV0PEo38kSS0//DCFVy5vkvhoIba44HDSdaw0Pm3Fuc5Anc +V73IX9H3qdvWdyXwQH+0OLqcB09If0hifHEnChuD1HAJYMRIk0ylGs7W3ftSvN1e +mJj3SJLAKKwhxN5SuHGJBL5st1ZT6/cM/ksUlUWPxMhlGoFBfqte6Xb+D2/z7XvA +n2X60TcPAgMBAAECggEAU7LfL4ohgcZE072Exjjbif2rdhhhq9lJPjkTI555iN6e +rpLLNu+kk+fsY+c3P2/oBnqWZE3SML+XiXb646d5Ds+opP1BQXGxOl606Wo5pjyV +aK+Z6KfveqVTGfE0ZCiM70SVea93Mtpvk1DIFAT5q8zY4r76bTIm7KEN+Uf9C5g6 +THosL87Oo6nNVzYKSu5bwIyzbgUbbchX14DS7SAdRN0jUo0v8YOtYLNPiwOHoxuW +aTSeP6BKgw8YOiEsl0VXMI6DESvt/SP2zLx5N4B89sxJpzn4ZRrO0r07tY56QZex +n6gmmuxD440kreKLzE2erU+DYWpWU8O3R77nZFGysQKBgQD9zdsKqqe4XJcSYhHY +X7e7mXTM3Wm1I2HKPfaZ1V6PHiX5PGlK5dqGZMhT6quwZpTqV1Zx2Vhthj6YXAwY +Ka2bEnZQhw4umZOKzmiMSkFxDZQ/wfTzBOuGIdSsFk2387U1Q7hCwxZ2U5C+x+4S ++gfeISKeojpCxjk/qbIhdppK3wKBgQDAgnOGT1AWpfDNvGSxNq+vjuevFqlXbYjz +PE3jUF3IN/7iYJ2I5C43FoGl37BCxV2ZzxGIfvEu1fuI0+ZzmZdhjz4cpWkrIGf/ +y36UwLcxhfX+hmScqAQi8IrK+EGir7P4I8zlPH9gdhZGt7zP15EkCNo1OOoJAnMp +gBkYQQ/J0QKBgGd8Yk3NG6n+htLGDgStsprZjVhjB1EGZj8yWLSM3Yt5fX5MdZpG +cn3N5ijhTLQWf621gtfVCUtPK4KwNXY8uD68nmVjTBMQJ6q3UsWxGVHheYstqJTW +cro0XST1yyawRji7rgv6w9PnauN/XcF7FW4rEJSiDSNg88LTjIA8fdj9AoGAFjBz +JG4L6zBhWzV13b9R7MhqCBJynnMvr+mpiKQAy005AdrlDZxPf2YGt5na9TdOnKXz +fWo0XpOnlJPoIjb37fTW0fJ29tObaS6JfpfgBcNNIXNYMX5kS6qyNMb8ucXK1rU5 +rsqUXAgAdVJEXyiXwDGNBUl0IGm54HS1b8hAC6ECgYEAz1+mbr7Q37mjE+Z4+7rZ +zYe7plbcWn0wwPuhHC+18plqmQlsY6mbCsNXAwMyeW9/yDIsIfx7Xr9y/dTF3pCA +KfRhXvhS5hbDJatqkcSkGePbtOZuSPE8sMcFEQUAOgWF9aEyQ8VstfYWcXvOn8J6 +p/NjOzj9eM5NX2tFsIvHlAQ= +-----END PRIVATE KEY----- diff --git a/client/src/test/java/com/influxdb/client/tls/client.p12 b/client/src/test/java/com/influxdb/client/tls/client.p12 new file mode 100644 index 0000000000000000000000000000000000000000..6322a62b798d5a4969a9b5ea904cc67cd4fe3483 GIT binary patch literal 2682 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 QKt|iAvP`A1H-8iPe|YWFQUCw| literal 0 HcmV?d00001 diff --git a/client/src/test/java/com/influxdb/client/tls/client_pkcs8.key b/client/src/test/java/com/influxdb/client/tls/client_pkcs8.key new file mode 100644 index 00000000000..23768a6d6d0 --- /dev/null +++ b/client/src/test/java/com/influxdb/client/tls/client_pkcs8.key @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFNjBgBgkqhkiG9w0BBQ0wUzAyBgkqhkiG9w0BBQwwJQQQOtcvRcyoide95s6N +BTv97QIDD0JAMAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBDYyE/OTmY47dVw +slVc8u9bBIIE0IJu+R+i5nWle3CzxKgm9o3p69pS6vWJ+QsbZHbSf9zYP7rqQ6R5 +cese63LVgxy+3FmszDspZmo/LgOHeCJ75KQre6WkfxG6fGeZ0HHyxU1mZ7GS0M51 +Vk4/hDtmzroBFTX4E2s2ozfbR6urg5KuVf4WQ9PdiCt2OcJQkCADI4nrHgD5NfBG +8FrXpxK7c/XyrMC/v3yOPGib0OjNriHgdzXDaA7M5Jdf4RsQeaTb82xF12pucPO7 +sxdbk0gpL7aQvYHO79phWE0dbqffo1yB1LZkyVTieJzusxhFs7szy9TUGpxjUiXc +25DUCdcsRUi7UMRzmmnDZaJHSTgGxYI4ytIW0Av/yyZmo1oP/N/RF+fNZC3yTYlO +gvmQsQl/gHLM8BJNlts52w/O3YCuYA53WEMd88OUY6TYSACOzKZOOOfXUMCsDtwV +hnIy3A/tDrnQsWfJWY3Yn1AHPEhgIfqEGeyU45Ur+xrJaBc4LnoZ7ndtvxAGTjpk +LG/9L5nNh+5qNFt3r6T8ReCWYTD6yozFIAxeXWeiLDagD6b2cBL6BVyZQy/RzFC3 +QVaaMtc+nL7KpVWnrNtn9uLxZwfXFxmDHCss/hVLtwFp1tK/oZV+BJ2AAnkRUiFE +6CwjMqTHwMmERN5kWY5hmEG6YBGjnA32Qk4J/ewTF05/TLUX2KhVQ/UihEgeuCao +v8na8eZVYcbZdnIkgNxWRpmJJuAY5jSXFZV4H1hty66pLPd5XbtdhNW+DefzmuBC +tFpsCBqxYM4TcHUBvnOmdf8wv89GfqpwOJBrFRs6+KBrhzxSXlPKqfErL3Pdh5cx +HAtx4RwdwR17ThdEmpgp1b4NRRawozN2PgT80XQ1VNtXqdL5aVRie3/aVYbPOhOf +pTbxKdchYvLoMdn+QZnHmo2pOxCmcgVslENQbrxGqwAibG2Hjl08fklRPvAykNvc +3XFqvibBeisTW0Zm/fjPz9MIOZLkuRL+nwU9PKYhQobheKPU72kOmlERuiEhD4sj +NYU83RU0GQq1aVdvCp8/Xn6hMttomDcC3CwqEJuzxUP6y3QLGw7t4xW8gg5+zVFV +ZL/fa3n07iF1v203jjswxhaOqACPXX1eHsH0ahTj/Zoto0fjGVXsGsxuEU3929b5 +cCE2LN++gaEdnQCQFKysh+0ne5EKaLOH4MXMiOs3OK1MovibFJuV2pxGrH5jW0cJ +y9kIHygXk83oAtig9//ErsHhshm0VZbX6CPT4Xlvu1RA6RAoRSZl2UszmExUkJK7 +Zhj7FWfsDXh4UhFyRcCO8WKYgNQYqJvBLTjSiMHxHAUdjbKXBUFN59kAbs45WI+f +Y/TA74KqFmirXoWnFg41CrIJaiIAPL2PeuJdyBoyLaC08hose6ih+U2X7fkXHpys +6ZmO1svTX/T8Tif/gaweYmXQAyrx2ZydG5njKs0r4Cox1udqH8MdZw0pvorc/lTg +BnNENR9t4nLRG2c9lVyn1Lgn58oG04nkCBSJtQy+mn78T9MnjhcRkLWaS3piL9xE +4EOmSG9pDJIZkTN5P6e9DoFA0hUuFLJwApo6hzWkVI4iJQY7X4/JeXR4AQmzr7xv +xF+APefWoS8avFkgKXpl/+ZRSHHg9nkVdJeQdYbEcvZbGBVr/byvPSaf +-----END ENCRYPTED PRIVATE KEY----- diff --git a/client/src/test/java/com/influxdb/client/tls/generate-self-signed-cert.sh b/client/src/test/java/com/influxdb/client/tls/generate-self-signed-cert.sh new file mode 100644 index 00000000000..4ccdde58fec --- /dev/null +++ b/client/src/test/java/com/influxdb/client/tls/generate-self-signed-cert.sh @@ -0,0 +1,52 @@ +#!/bin/bash +# +# 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. +# + +set -e + +PKCS12_PASSWORD="changeit" +INFLUXDB_SERVER_NAME="influxdb" +OTHER_SERVER_NAME="other-server" + +# Gen Server .crt and .key with SAN (Subject Alternative Names) then convert to .p12 file +for SERVER_NAME in "$INFLUXDB_SERVER_NAME" "$OTHER_SERVER_NAME"; do + echo "### Generate server key and certificate for $SERVER_NAME..." + openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 | openssl pkcs8 -topk8 -nocrypt -out "${SERVER_NAME}.key" + + openssl req -new -x509 -key "${SERVER_NAME}.key" -out "${SERVER_NAME}.crt" -days 3650 \ + -subj "/CN=localhost/O=Development/C=US" \ + -addext "subjectAltName = DNS:localhost,IP:127.0.0.1" + + openssl pkcs12 -export -in "${SERVER_NAME}.crt" -inkey "${SERVER_NAME}.key" \ + -out "${SERVER_NAME}.p12" -name "myalias" -password "pass:$PKCS12_PASSWORD" +done + +# Gen Client .crt and .key then convert to .p12 file +echo "### Generate client key..." +openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 | openssl pkcs8 -topk8 -nocrypt -out client.key +openssl req -new -x509 -key client.key -out client.crt -days 3650 \ + -subj "/CN=test-client/O=Development/C=US" +openssl pkcs12 -export -in client.crt -inkey client.key \ + -out client.p12 -name "myalias" -password "pass:$PKCS12_PASSWORD" + +#Encrypt pkcs8 for client.key for testing +openssl pkcs8 -topk8 -v2 aes-256-cbc -iter 1000000 -in client.key -out client_pkcs8.key \ No newline at end of file diff --git a/client/src/test/java/com/influxdb/client/tls/influxdb.crt b/client/src/test/java/com/influxdb/client/tls/influxdb.crt new file mode 100644 index 00000000000..0c35189c4e1 --- /dev/null +++ b/client/src/test/java/com/influxdb/client/tls/influxdb.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDazCCAlOgAwIBAgIUWWaZWiL+PUXwO22PUF5pduaF3tUwDQYJKoZIhvcNAQEL +BQAwNzESMBAGA1UEAwwJbG9jYWxob3N0MRQwEgYDVQQKDAtEZXZlbG9wbWVudDEL +MAkGA1UEBhMCVVMwHhcNMjYwOTI5MTAyOTQzWhcNMzYwOTI2MTAyOTQzWjA3MRIw +EAYDVQQDDAlsb2NhbGhvc3QxFDASBgNVBAoMC0RldmVsb3BtZW50MQswCQYDVQQG +EwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALtYoIuLWvp3BrGU +4lsZO7bMTPVcdq9hFQE8ivmonzDYMiyc9oNdYF3gNh58gV2GNhGZvZqEos1S8EgL +2jVmegfRxBUW5bD7FjjcBwhpanHvxldkwfQJXSvS4dadTtqBlo+d+yY2WgWshnnT +aoMk3djiJxxHVHn7yvHDgC+tTxI4MBKC/5NRGCCbtJNaRIL0OE04DUzcGm5pVQN4 +AsEEXfqFOvz3QNtJbbWArlFuYkWwthWaRKTQmlxWmRpqH8RqDtcXQYgTtsJyFhH0 +CrF1lQ5KrncGW/W4GTM5ZBbCGG1x9l8t3GsEaJ2JeqKBGraWWAmmARG3yPyIQxHh +NixqrI8CAwEAAaNvMG0wHQYDVR0OBBYEFL5EDMd8Fe5/94JMeonCVd1Ha/g4MB8G +A1UdIwQYMBaAFL5EDMd8Fe5/94JMeonCVd1Ha/g4MA8GA1UdEwEB/wQFMAMBAf8w +GgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQBx +oycxsXHIfzkuwt6aiemFialtrm+GqJLv+1lnTSlItnQGBm1vz4Tv9FdUdMD5YoyP +BTZduAk/aXMFWEQ8TsLlzdQjn5rIyv04rjApCPSFMz7XD4KPXzaqnqUpJkQyJ4/m +cGhu73GLAtgLamDITdaZL5k1I4vMRr0dOgSeLvl/vTC01LgzJPeDfAcFOhnTNOBW +5spL+9hmFRy1cQ3p8+/QurevmI8QBKo6/iUI9LESYBmTS6iXlJFAcerKE7RJWxJk +aDI2t58IbLd+5U8SAcHwhvP3iH4DLBVmIagaH2iAjRffSu/EJQJZ1iW7OkrHVN5p +v0BExOtzcZn2jpyfSiJE +-----END CERTIFICATE----- diff --git a/client/src/test/java/com/influxdb/client/tls/influxdb.key b/client/src/test/java/com/influxdb/client/tls/influxdb.key new file mode 100644 index 00000000000..b62fcc2231d --- /dev/null +++ b/client/src/test/java/com/influxdb/client/tls/influxdb.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC7WKCLi1r6dwax +lOJbGTu2zEz1XHavYRUBPIr5qJ8w2DIsnPaDXWBd4DYefIFdhjYRmb2ahKLNUvBI +C9o1ZnoH0cQVFuWw+xY43AcIaWpx78ZXZMH0CV0r0uHWnU7agZaPnfsmNloFrIZ5 +02qDJN3Y4iccR1R5+8rxw4AvrU8SODASgv+TURggm7STWkSC9DhNOA1M3BpuaVUD +eALBBF36hTr890DbSW21gK5RbmJFsLYVmkSk0JpcVpkaah/Eag7XF0GIE7bCchYR +9AqxdZUOSq53Blv1uBkzOWQWwhhtcfZfLdxrBGidiXqigRq2llgJpgERt8j8iEMR +4TYsaqyPAgMBAAECggEAUqRCUbwb2l+V+tDn1vK3FihiDFwI2M1U9DIuRfodmvDL +lUV6MrGGmHKWvPUEJerVwAbXDinTHQqohsd/rs2xJ/De88ShduQTxeo9SfciZvcz +wYpnSky1JmdEiyTiS14dueWhC5avOFCGE4hwtEAXgpwkT4Ohnpu1RteMHLvxJOIW +DsYSwE7qEN734TARVnPeg3XioKKurgRGrup2D49eIhqTgji5MOJXPP89KXzzxda+ +tw60TIkxd3zwokxln5wsXLBrXeIfE7n/nB7Ii4TXAmFYGM+tBPvdsvwe1MgOhwQc +qksJnanGD6sZdM4gXyP1Jq0gSJ53iypKEBkAIoVKUQKBgQDc0AmHPKfVZ05+Ljkk +/Rx6to2YS8iQ9aDtbxVYI6l0Yo/4r+6lRiaLf/KL5qptFFE+51+hKB6mXH7PliIz +DeqZ6Fil/UGCX+ENBEtcXqWaYG7rJWAcxnJS+kM3U5O69wuLTp0JobjAB4rim8Md +hZ8Ir/jfTx9Hh5Rc9jxaDleVEQKBgQDZM1b6kH9hiHXYzGP9Gjbs9yl+RoMEWpvr +K8J8DcFMm8XVd7WU/AZYupnF8cRHWDdXkba4IGEodwfUgQI4sxBYnEKW+GVNsK6k +7SOZS0lYHPCL3t/dY/9fqaQ3jIg4gArLy6lUvURGlfCgyPVivrjQ6y/v5zANcGKJ +1Ce86VennwKBgQCOY6TRTV7Y8T4fhgUZghKcWx3hqHTUbWBx26EC7jQ2tdwrPKAC +ecMfT8mDR6J8po5hpuf2zx08oAayLBkvnPi8eKS5nR9iSBuivjMuhGX7r9W20qow +xBMyyOkRQ1bPSMFr+qyvalbtla+Mw27FmcXc30T4E1iTUq1saOiwFfGKUQKBgQCc +WWCbLbkENH7geQDhCSXQnnq383oMgE0MlIuDUUN8KgXXDD0h6emHpYtb+hUYZmAw +ISwujuBhiI2SYut+dSenIZStqFEEA9Mz1aBykMdTLkHZ7a2oaHtEGZGkivzj7wfW +e9yAq12ikfyNQRui4TeY7DOttfXZNrU4Eggo7K+0cQKBgQC6AtruugX5PD4IQhFt +ejaIbKoZb6no9q/1gLpLQqAP3ayEibahFkK1OdN4d0ns1sT08jcHFhy3S8RfsTtS +t5TKIOooVTJk2CqQpKj/AJgBYshV71btAJrPuUc//41Uj4LhefdNALPs+0F8zEQm +jLdNMflXyDlEHW/t8rgy++p/Pw== +-----END PRIVATE KEY----- diff --git a/client/src/test/java/com/influxdb/client/tls/influxdb.p12 b/client/src/test/java/com/influxdb/client/tls/influxdb.p12 new file mode 100644 index 0000000000000000000000000000000000000000..6aad3565968b76b82c632dc33ba67ad7d028458d GIT binary patch literal 2714 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 ${project.organization.name} From 2e5c9c0b1b7949b5890fc4240cca8f858bad4ffb Mon Sep 17 00:00:00 2001 From: NguyenHoangSon96 Date: Wed, 30 Sep 2026 14:06:43 +0700 Subject: [PATCH 2/2] feat: support tls --- client-utils/src/main/java/com/influxdb/utils/TlsUtils.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client-utils/src/main/java/com/influxdb/utils/TlsUtils.java b/client-utils/src/main/java/com/influxdb/utils/TlsUtils.java index b37cec83530..8b574d8cd18 100644 --- a/client-utils/src/main/java/com/influxdb/utils/TlsUtils.java +++ b/client-utils/src/main/java/com/influxdb/utils/TlsUtils.java @@ -77,7 +77,8 @@ public static SSLContext buildSslContext(@Nullable final KeyManagerFactory kmf, * * @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}. + * @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