diff --git a/pom.xml b/pom.xml index e33b7389..baed0ff6 100644 --- a/pom.xml +++ b/pom.xml @@ -461,7 +461,7 @@ **/target/**, **/*.jar, **/.git/**, **/.*, **/*.png, **/*.iml, **/*.bolt, .idea/**, **/*nightly*/**, **/.m2/**, LICENSE, **/*.md, **/.github/**, license_header.txt, release.properties/, **/pom.xml.releaseBackup, **/pom.xml.tag, **/semantic.yml, - .circleci/config.yml, **/*.pem + .circleci/config.yml, **/*.pem, **/*.p12, **/*.crt, **/*.key diff --git a/src/main/java/com/influxdb/v3/client/config/ClientConfig.java b/src/main/java/com/influxdb/v3/client/config/ClientConfig.java index a87bfab8..33afa732 100644 --- a/src/main/java/com/influxdb/v3/client/config/ClientConfig.java +++ b/src/main/java/com/influxdb/v3/client/config/ClientConfig.java @@ -129,7 +129,15 @@ public final class ClientConfig { private final String proxyUrl; private final Authenticator authenticator; private final Map headers; + + private final String certificatePath; + private final String certificateKeyPath; + private final String certificateP12FilePath; + private final char[] keyPassword; + private final String sslRootsFilePath; + private final char[] sslRootFilePassword; + private final boolean disableGRPCCompression; private final List interceptors; @@ -337,6 +345,31 @@ public String sslRootsFilePath() { return sslRootsFilePath; } + @Nullable + public char[] getSslRootFilePassword() { + return sslRootFilePassword; + } + + @Nullable + public String getCertificatePath() { + return certificatePath; + } + + @Nullable + public String getCertificateKeyPath() { + return certificateKeyPath; + } + + @Nullable + public String getCertificateP12FilePath() { + return certificateP12FilePath; + } + + @Nullable + public char[] getKeyPassword() { + return keyPassword; + } + /** * Gets the (proxy) authenticator. * @@ -424,6 +457,11 @@ public boolean equals(final Object o) { && Objects.equals(authenticator, that.authenticator) && Objects.equals(headers, that.headers) && Objects.equals(sslRootsFilePath, that.sslRootsFilePath) + && Arrays.equals(sslRootFilePassword, that.sslRootFilePassword) + && Objects.equals(certificatePath, that.certificatePath) + && Objects.equals(certificateKeyPath, that.certificateKeyPath) + && Objects.equals(certificateP12FilePath, that.certificateP12FilePath) + && Arrays.equals(keyPassword, that.keyPassword) && disableGRPCCompression == that.disableGRPCCompression && Objects.equals(interceptors, that.interceptors); } @@ -434,7 +472,9 @@ public int hashCode() { database, writePrecision, gzipThreshold, writeNoSync, writeAcceptPartial, writeUseV2Api, timeout, writeTimeout, queryTimeout, allowHttpRedirects, disableServerCertificateValidation, proxy, proxyUrl, authenticator, headers, - defaultTags, sslRootsFilePath, disableGRPCCompression, interceptors); + defaultTags, sslRootsFilePath, Arrays.hashCode(sslRootFilePassword), certificatePath, + certificateKeyPath, certificateP12FilePath, Arrays.hashCode(keyPassword), + disableGRPCCompression, interceptors); } @Override @@ -459,6 +499,11 @@ public String toString() { .add("headers=" + headers) .add("defaultTags=" + defaultTags) .add("sslRootsFilePath=" + sslRootsFilePath) + .add("sslRootFilePassword=" + Arrays.toString(sslRootFilePassword)) + .add("certificatePath=" + certificatePath) + .add("certificateKeyPath=" + certificateKeyPath) + .add("certificateP12FilePath=" + certificateP12FilePath) + .add("keyPassword=" + Arrays.toString(keyPassword)) .add("disableGRPCCompression=" + disableGRPCCompression) .toString(); } @@ -490,7 +535,15 @@ public static final class Builder { private String proxyUrl; private Authenticator authenticator; private Map headers; + + private String certificatePath; + private String certificateKeyPath; + private String certificateP12FilePath; + private char[] keyPassword; + private String sslRootsFilePath; + private char[] sslRootFilePassword; + private boolean disableGRPCCompression; private List interceptors; @@ -805,6 +858,45 @@ public Builder sslRootsFilePath(@Nullable final String sslRootsFilePath) { return this; } + @Nonnull + public Builder sslRootFilePassword(@Nullable final char[] sslRootFilePassword) { + + this.sslRootFilePassword = sslRootFilePassword; + 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 ClientConfig.Builder} instance. + */ + @Nonnull + public Builder certificateFilePath(@Nonnull final String certificatePath, + @Nonnull final String certificateKeyPath) { + this.certificatePath = certificatePath; + this.certificateKeyPath = certificateKeyPath; + + return this; + } + + @Nonnull + public Builder certificateP12FilePath(@Nonnull final String certificateP12FilePath, + @Nullable final char[] password) { + this.certificateP12FilePath = certificateP12FilePath; + this.keyPassword = password; + + return this; + } + + @Nonnull + public Builder keyPassword(@Nullable final char[] keyPassword) { + + this.keyPassword = keyPassword; + return this; + } + /** * Sets whether to disable gRPC compression. Default is 'false'. * @@ -1023,6 +1115,11 @@ private ClientConfig(@Nonnull final Builder builder) { authenticator = builder.authenticator; headers = builder.headers; sslRootsFilePath = builder.sslRootsFilePath; + sslRootFilePassword = builder.sslRootFilePassword; + certificatePath = builder.certificatePath; + certificateKeyPath = builder.certificateKeyPath; + certificateP12FilePath = builder.certificateP12FilePath; + keyPassword = builder.keyPassword; disableGRPCCompression = builder.disableGRPCCompression; interceptors = builder.interceptors; } diff --git a/src/main/java/com/influxdb/v3/client/internal/FlightSqlClient.java b/src/main/java/com/influxdb/v3/client/internal/FlightSqlClient.java index 9880e7f2..8d8b0811 100644 --- a/src/main/java/com/influxdb/v3/client/internal/FlightSqlClient.java +++ b/src/main/java/com/influxdb/v3/client/internal/FlightSqlClient.java @@ -21,7 +21,6 @@ */ package com.influxdb.v3.client.internal; -import java.io.FileInputStream; import java.net.InetSocketAddress; import java.net.URI; import java.net.URISyntaxException; @@ -38,6 +37,8 @@ import java.util.stream.StreamSupport; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.TrustManagerFactory; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -190,9 +191,28 @@ SslContext createNettySslContext(@Nonnull final ClientConfig config) { sslContextBuilder = GrpcSslContexts.forClient(); if (config.getDisableServerCertificateValidation()) { sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE); - } else if (config.sslRootsFilePath() != null) { - try (FileInputStream fileInputStream = new FileInputStream(config.sslRootsFilePath())) { - sslContextBuilder.trustManager(fileInputStream); + } else { + TrustManagerFactory tmf = null; + KeyManagerFactory kmf = null; + if (config.sslRootsFilePath() != null) { + tmf = Utils.createTmf( + config.sslRootsFilePath(), + config.getSslRootFilePassword()); + } + + if (config.getCertificatePath() != null && config.getCertificateKeyPath() != null) { + kmf = Utils.createKmf(config.getCertificatePath(), config.getCertificateKeyPath()); + } + + if (config.getCertificateP12FilePath() != null) { + kmf = Utils.createKmfP12(config.getCertificateP12FilePath(), config.getKeyPassword()); + } + + if (tmf != null) { + sslContextBuilder.trustManager(tmf); + } + if (kmf != null) { + sslContextBuilder.keyManager(kmf); } } return sslContextBuilder.build(); diff --git a/src/main/java/com/influxdb/v3/client/internal/RestClient.java b/src/main/java/com/influxdb/v3/client/internal/RestClient.java index 72de031e..210c55a1 100644 --- a/src/main/java/com/influxdb/v3/client/internal/RestClient.java +++ b/src/main/java/com/influxdb/v3/client/internal/RestClient.java @@ -42,6 +42,7 @@ import java.util.stream.Stream; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; @@ -127,16 +128,32 @@ public void checkServerTrusted( if (baseUrl.startsWith("https")) { try { - SSLContext sslContext = SSLContext.getInstance("TLS"); if (config.getDisableServerCertificateValidation()) { + SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, TRUST_ALL_CERTS, new SecureRandom()); - } else if (config.sslRootsFilePath() != null) { - X509TrustManager x509TrustManager = getX509TrustManagerFromFile(config.sslRootsFilePath()); - sslContext.init(null, new X509TrustManager[]{x509TrustManager}, new SecureRandom()); + builder.sslContext(sslContext); } else { - sslContext.init(null, null, new SecureRandom()); + TrustManagerFactory tmf = null; + KeyManagerFactory kmf = null; + if (config.sslRootsFilePath() != null) { + tmf = Utils.createTmf( + config.sslRootsFilePath(), + config.getSslRootFilePassword()); + } + + if (config.getCertificatePath() != null && config.getCertificateKeyPath() != null) { + kmf = Utils.createKmf(config.getCertificatePath(), config.getCertificateKeyPath()); + } + + if (config.getCertificateP12FilePath() != null) { + kmf = Utils.createKmfP12(config.getCertificateP12FilePath(), config.getKeyPassword()); + } + + SSLContext sslContext = Utils.buildSslContext(kmf, tmf); + if (sslContext != null) { + builder.sslContext(sslContext); + } } - builder.sslContext(sslContext); } catch (Exception e) { throw new RuntimeException(e); } @@ -487,6 +504,7 @@ private String errNonEmptyField(@Nullable final JsonNode object, @Nonnull final return errNonEmptyText(object.get(fieldName)); } + //todo remove? private X509TrustManager getX509TrustManagerFromFile(@Nonnull final String filePath) { try { KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); diff --git a/src/main/java/com/influxdb/v3/client/internal/Utils.java b/src/main/java/com/influxdb/v3/client/internal/Utils.java index 70cc0eb1..c9d8f8f4 100644 --- a/src/main/java/com/influxdb/v3/client/internal/Utils.java +++ b/src/main/java/com/influxdb/v3/client/internal/Utils.java @@ -21,8 +21,33 @@ */ package com.influxdb.v3.client.internal; +import java.io.FileInputStream; +import java.io.IOException; +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 Utils { + 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 Utils() { } @@ -43,4 +68,210 @@ public static boolean isInteger(final String str) { return false; } } + + @Nullable + public static SSLContext buildSslContext(@Nullable final KeyManagerFactory kmf, + @Nullable final TrustManagerFactory tmf) throws GeneralSecurityException { + 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; + } + + + /** + * 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 IOException if an error occurs while reading the file. + * @throws GeneralSecurityException if an error occurs while loading the keystore + * or initializing the {@link KeyManagerFactory}. + */ + public static KeyManagerFactory createKmfP12(@Nonnull final String path, + @Nullable final char[] password) + throws IOException, GeneralSecurityException { + 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 IOException if an error occurs while reading the files. + * @throws GeneralSecurityException if an error occurs while loading the credentials or initializing + * the {@link KeyManagerFactory}. + */ + public static KeyManagerFactory createKmf(@Nonnull final String certPath, + @Nonnull final String keyPath) + throws IOException, GeneralSecurityException { + 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", + 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 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 IOException if an error occurs while reading the file. + * @throws GeneralSecurityException if an error occurs while 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 IOException, GeneralSecurityException { + 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; + } + + /** + * 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 IOException if an error occurs while reading the file. + * @throws GeneralSecurityException if an error occurs while loading the keystore + * or initializing the TrustManagerFactory. + */ + public static TrustManagerFactory createTmfP12(@Nonnull final String path, + @Nullable final char[] password) + throws IOException, GeneralSecurityException { + 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; + } + + /** + * 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 GeneralSecurityException if an error occurs during the initialization or retrieval + * of the {@link X509TrustManager}. + */ + @Nonnull + public static X509TrustManager getX509TrustManager(@Nullable final TrustManagerFactory tmf) + throws GeneralSecurityException { + 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 IOException if an error occurs during file reading. + * @throws GeneralSecurityException if an error occurs during Base64 decoding or key generation. + */ + public static PrivateKey loadPrivateKey(@Nonnull final String path) throws IOException, GeneralSecurityException { + 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."); + } + + if (!keyPem.contains("-----BEGIN PRIVATE KEY-----") || !keyPem.contains("-----END PRIVATE KEY-----")) { + throw new IllegalArgumentException("Invalid private key format. Private key must be in PKCS#8 format."); + } + + 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); + } } diff --git a/src/test/java/com/influxdb/v3/client/InfluxDBClientTest.java b/src/test/java/com/influxdb/v3/client/InfluxDBClientTest.java index de6f52a1..dda7f1aa 100644 --- a/src/test/java/com/influxdb/v3/client/InfluxDBClientTest.java +++ b/src/test/java/com/influxdb/v3/client/InfluxDBClientTest.java @@ -26,11 +26,22 @@ import java.util.List; import java.util.Map; import java.util.Properties; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; +import mockwebserver3.MockResponse; +import mockwebserver3.MockWebServer; +import okhttp3.Headers; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import com.influxdb.v3.client.config.ClientConfig; +import com.influxdb.v3.client.internal.Utils; + +import static org.assertj.core.api.AssertionsForClassTypes.catchThrowable; public class InfluxDBClientTest { @@ -144,10 +155,10 @@ void withDefaultTags() throws Exception { Map defaultTags = Map.of("unit", "U2", "model", "M1"); try (InfluxDBClient client = InfluxDBClient.getInstance( - "http://localhost:8086", - "MY-TOKEN".toCharArray(), - "MY-DATABASE", - defaultTags)) { + "http://localhost:8086", + "MY-TOKEN".toCharArray(), + "MY-DATABASE", + defaultTags)) { Assertions.assertThat(client).isNotNull(); } } @@ -166,4 +177,232 @@ public void unsupportedQueryParams() throws Exception { + "class com.influxdb.v3.client.internal.InfluxDBClientImpl"); } } + + String tlsDir = "src/test/java/com/influxdb/v3/client/testdata/certificates/"; + + 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( + @Nonnull String name, + @Nullable String clientCertFilePath, + @Nullable String clientKeyFilePath, + @Nonnull String sslRootFilePath, + boolean expectSuccess + ) { + } + + private List tlsCases() { + return List.of( + new TlsTest( + "Tls success with correct server certificate", + null, + null, + influxdbCertPath, + true + ), + new TlsTest( + "Tls success with correct .p12 server certificate", + null, + null, + influxdbP12, + true + ), + new TlsTest( + "Tls fail with incorrect server certificate", + null, + null, + otherCertPath, + false + ), + new TlsTest( + "mtls success with correct client certificate .key and .crt file", + clientCertPath, + clientKeyPath, + influxdbCertPath, + true + ), + new TlsTest( + "mtls success with correct client certificate .p12 file", + clientP12, + null, + influxdbCertPath, + true + ), + new TlsTest( + "mtls fail with incorrect client certificate .crt and .key file", + otherCertPath, + otherKeyCertPath, + influxdbCertPath, + false + ), + new TlsTest( + "mtls fail with incorrect client certificate .p12 file", + otherP12, + null, + influxdbCertPath, + false + ) + ); + } + + @Test + public void testWriteSecureSuccess() { + for (TlsTest tlsCase : tlsCases()) { + Throwable thrown = catchThrowable(() -> + testWriteSecureMockServer( + tlsCase.clientCertFilePath, + tlsCase.clientKeyFilePath, + defaultPassword, + tlsCase.sslRootFilePath, + defaultPassword + )); + if (!tlsCase.expectSuccess) { + Assertions.assertThat(thrown).isNotNull(); + Assertions.assertThat(thrown).isInstanceOf(InfluxDBApiException.class); + Assertions.assertThat(thrown).hasMessageContaining("ssl"); + } else { + Assertions.assertThat(thrown).isNull(); + } + } + } + +// @Test +// public void testMutualTlsSuccessQuery() throws Exception { +// for (TlsTest tlsCase : tlsCases()) { +// try (var flightServer = createSecureFlightServer(tlsCase.clientCertFilePath, tlsCase.clientKeyFilePath, tlsCase.sslRootFilePath)) { +// flightServer.start(); +// URI uri = flightServer.getLocation().getUri(); +// ClientConfig clientConfig = new ClientConfig.Builder() +// .host(String.format("https://%s:%d", uri.getHost(), uri.getPort())) +// .database("test") +// .sslRootsFilePath(influxdbCertPath) +// .build(); +// +// try (InfluxDBClient influxDBClient = InfluxDBClient.getInstance(clientConfig)) { +// Assertions.assertThatNoException().isThrownBy(() -> { +// Stream stream = influxDBClient.query("Select * from \"nothing\""); +// stream.forEach(objects -> { +// Assertions.assertThat(objects[0]).isEqualTo("Value"); +// }); +// stream.close(); +// }); +// } +// } +// +// } +// } + +// @Nonnull +// public FlightServer createSecureFlightServer(@Nullable final String serverCertFilePath, +// @Nullable final String serverKeyFilePath, +// @Nullable final String clientCertPath) +// throws Exception { +// URI uri = URI.create("https://127.0.0.1:" + TestUtils.findFreePort()); +// VectorSchemaRoot vectorSchemaRoot = TestUtils.generateVectorSchemaRootWithNull(); +// return TestUtils.secureFlightServer( +// uri, +// new RootAllocator(Long.MAX_VALUE), +// TestUtils.simpleProducer(vectorSchemaRoot), +// serverCertFilePath, +// serverKeyFilePath, +// clientCertPath +// ); +// } + + public ClientConfig.Builder getSecureClientConfigBuilder( + @Nonnull String host, + @Nonnull final String trustFilePath, + @Nullable final char[] trustFilePathPassword, + @Nullable final String clientCertFilePath, + @Nullable final String clientKeyFilePath, + @Nullable final char[] clientCertFilePassword + ) { + ClientConfig.Builder configBuilder = new ClientConfig.Builder() + .token("my-token".toCharArray()) + .database("mydb") + .sslRootsFilePath(trustFilePath); + + if (trustFilePathPassword != null) { + configBuilder.sslRootFilePassword(trustFilePathPassword); + } + + if (clientCertFilePath != null) { + if (clientCertFilePath.endsWith(".p12")) { + configBuilder.certificateP12FilePath(clientCertFilePath, clientCertFilePassword); + } else { + configBuilder.certificateFilePath(clientCertFilePath, clientKeyFilePath); + } + } + + configBuilder.host(host); + return configBuilder; + } + + public void testWriteSecureMockServer(@Nullable final String clientCertFilePath, + @Nullable final String clientKeyFilePath, + @Nullable final char[] clientCertFilePassword, + @Nonnull final String trustFilePath, + @Nullable final char[] trustFilePathPassword + ) + throws Exception { + MockWebServer mockServer = null; + InfluxDBClient client = null; + try { + mockServer = getWriteSecureMockServer(clientCertFilePath != null); + final ClientConfig clientConfig = getSecureClientConfigBuilder( + mockServer.url("/").url().toString(), + trustFilePath, + trustFilePathPassword, + clientCertFilePath, + clientKeyFilePath, + clientCertFilePassword + ).build(); + client = InfluxDBClient.getInstance(clientConfig); + client.writeRecord("mem,tag=one value=1.0"); + } catch (Exception e) { + throw e; + } finally { + if (client != null) { + client.close(); + } + + mockServer.close(); + } + } + + private MockWebServer getWriteSecureMockServer(final boolean isMutualTls) throws Exception { + KeyManagerFactory kmf = Utils.createKmfP12(influxdbP12, defaultPassword); + var mockServer = new MockWebServer(); + TrustManagerFactory tmf = null; + if (isMutualTls) { + tmf = Utils.createTmf(clientCertPath, defaultPassword); + mockServer.requireClientAuth(); + } + SSLContext sslContext = Utils.buildSslContext(kmf, tmf); + mockServer.useHttps(sslContext.getSocketFactory()); + mockServer.start(); + mockServer.enqueue(createResponse()); + + return mockServer; + } + + @Nonnull + protected MockResponse createResponse() { + return new MockResponse(200, + Headers.of("something", "something"), + "ok"); + } + } diff --git a/src/test/java/com/influxdb/v3/client/TestUtils.java b/src/test/java/com/influxdb/v3/client/TestUtils.java index 28103513..638d1e31 100644 --- a/src/test/java/com/influxdb/v3/client/TestUtils.java +++ b/src/test/java/com/influxdb/v3/client/TestUtils.java @@ -21,6 +21,7 @@ */ package com.influxdb.v3.client; +import java.io.File; import java.io.IOException; import java.net.ServerSocket; import java.net.URI; @@ -30,6 +31,7 @@ import java.util.List; import java.util.Map; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.apache.arrow.flight.FlightServer; import org.apache.arrow.flight.Location; @@ -51,13 +53,33 @@ private TestUtils() { throw new IllegalStateException("Utility class"); } + @Nonnull public static FlightServer simpleFlightServer(@Nonnull final URI uri, @Nonnull final BufferAllocator allocator, - @Nonnull final NoOpFlightProducer producer) throws Exception { + @Nonnull final NoOpFlightProducer producer) { Location location = Location.forGrpcInsecure(uri.getHost(), uri.getPort()); return FlightServer.builder(allocator, location, producer).build(); } + @Nonnull + public static FlightServer secureFlightServer(@Nonnull final URI uri, + @Nonnull final BufferAllocator allocator, + @Nonnull final NoOpFlightProducer producer, + @Nonnull final String serverCertPath, + @Nonnull final String serverKeyPath, + @Nullable final String clientCertPath) throws IOException { + Location location = Location.forGrpcTls(uri.getHost(), uri.getPort()); + + var serverBuilder = FlightServer.builder(allocator, location, producer) + .useTls(new File(serverCertPath), new File(serverKeyPath)); + + if (clientCertPath != null) { + serverBuilder.useMTlsClientVerification(new File(clientCertPath)); + } + + return serverBuilder.build(); + } + public static NoOpFlightProducer simpleProducer(@Nonnull final VectorSchemaRoot vectorSchemaRoot) { return new NoOpFlightProducer() { @Override diff --git a/src/test/java/com/influxdb/v3/client/internal/UtilsTest.java b/src/test/java/com/influxdb/v3/client/internal/UtilsTest.java index a4bd023c..038ca03b 100644 --- a/src/test/java/com/influxdb/v3/client/internal/UtilsTest.java +++ b/src/test/java/com/influxdb/v3/client/internal/UtilsTest.java @@ -21,14 +21,33 @@ */ package com.influxdb.v3.client.internal; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.util.Base64; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.NullAndEmptySource; import org.junit.jupiter.params.provider.ValueSource; class UtilsTest { + + private final Path tempDir; + + UtilsTest(@TempDir final Path tempDir) { + this.tempDir = tempDir; + } + @ParameterizedTest @ValueSource(strings = { "0", @@ -70,4 +89,91 @@ void isIntegerInvalid(final String value) { void testEmpty() { Assertions.assertThat(Utils.isInteger("")).isFalse(); } + + @Test + void loadPrivateKeyMissingBeginHeader() throws IOException { + Path keyFile = tempDir.resolve("missing_begin.key"); + Files.writeString(keyFile, + "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC3\n-----END PRIVATE KEY-----"); + + Assertions.assertThatThrownBy(() -> Utils.loadPrivateKey(keyFile.toString())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Invalid private key format. Private key must be in PKCS#8 format."); + } + + @Test + void loadPrivateKeyMissingEndHeader() throws IOException { + Path keyFile = tempDir.resolve("missing_end.key"); + Files.writeString(keyFile, + "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC3"); + + Assertions.assertThatThrownBy(() -> Utils.loadPrivateKey(keyFile.toString())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Invalid private key format. Private key must be in PKCS#8 format."); + } + + @Test + void loadPrivateKeyMissingBothHeaders() throws IOException { + Path keyFile = tempDir.resolve("missing_both.key"); + Files.writeString(keyFile, "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC3"); + + Assertions.assertThatThrownBy(() -> Utils.loadPrivateKey(keyFile.toString())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Invalid private key format. Private key must be in PKCS#8 format."); + } + + @Test + void loadPrivateKeyValid(final @TempDir Path tempDir) throws Exception { + KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); + keyPairGen.initialize(2048); + KeyPair keyPair = keyPairGen.generateKeyPair(); + String encodedKey = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded()); + String pem = "-----BEGIN PRIVATE KEY-----\n" + encodedKey + "\n-----END PRIVATE KEY-----\n"; + Path keyFile = tempDir.resolve("valid.key"); + Files.writeString(keyFile, pem); + + PrivateKey privateKey = Utils.loadPrivateKey(keyFile.toString()); + Assertions.assertThat(privateKey).isNotNull(); + } + + @Test + void loadPrivateKeyEmptyFile() throws IOException { + Path keyFile = tempDir.resolve("empty.key"); + Files.writeString(keyFile, ""); + + Assertions.assertThatThrownBy(() -> Utils.loadPrivateKey(keyFile.toString())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Invalid private key format. Private key must be in PKCS#8 format."); + } + + @Test + void loadPrivateKeyEncryptedPkcs8Rejected() throws IOException { + Path keyFile = tempDir.resolve("encrypted_pkcs8.key"); + Files.writeString(keyFile, + "-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIIE...\n-----END ENCRYPTED PRIVATE KEY-----"); + + Assertions.assertThatThrownBy(() -> Utils.loadPrivateKey(keyFile.toString())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Encrypted PKCS#8 private keys are not supported. " + + "Use an unencrypted PKCS#8 key or a PKCS#12 file."); + } + + @Test + void getX509TrustManagerDefault() throws Exception { + X509TrustManager trustManager = Utils.getX509TrustManager(null); + Assertions.assertThat(trustManager).isNotNull(); + } + + @Test + void getX509TrustManagerFromFactory() throws Exception { + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init((KeyStore) null); + X509TrustManager trustManager = Utils.getX509TrustManager(tmf); + Assertions.assertThat(trustManager).isNotNull(); + } + + @Test + void buildSslContextNull() throws Exception { + Assertions.assertThat(Utils.buildSslContext(null, null)).isNull(); + } } diff --git a/src/test/java/com/influxdb/v3/client/testdata/certificates/client.crt b/src/test/java/com/influxdb/v3/client/testdata/certificates/client.crt new file mode 100644 index 00000000..12a7c7a1 --- /dev/null +++ b/src/test/java/com/influxdb/v3/client/testdata/certificates/client.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDUzCCAjugAwIBAgIUc3JF73VbBZjwUWvj9IE/Za7RU2QwDQYJKoZIhvcNAQEL +BQAwOTEUMBIGA1UEAwwLdGVzdC1jbGllbnQxFDASBgNVBAoMC0RldmVsb3BtZW50 +MQswCQYDVQQGEwJVUzAeFw0yNjEwMjkxMDM3MDlaFw0zNjEwMjYxMDM3MDlaMDkx +FDASBgNVBAMMC3Rlc3QtY2xpZW50MRQwEgYDVQQKDAtEZXZlbG9wbWVudDELMAkG +A1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDNRUUv0653 +3LU9IwVIPc8ir8wDiwUAumLHRPplwon/3Z7i3vfDb/7r6EPR0Bdp+WtKBj7M0PY0 +X8/xWhD2eu/QX/W6Hzd9OKXP3RfM7vC0vNGdnqEjnvA/GEOB/vDoBCjsqm2H12FA +Ib+mwQ7mKjItVy6BkSNUIEJnYtKaGzXBF7mrJS5WIYxoN0ndHuyBfEmwwp0Aq8/p +Q4KNHu4dD0WOgBf8O14n1keVvHcLH5d2cCM5H+FeOtaJNaVb6qtKUstvYrl5/0I8 +14r6j0cQi9lIjR/U8XRA+KGBGtRuWHETcKpmdCQm0qOz1BrMKWzWqsYytedsASVT +NmPa6FnOiZSFAgMBAAGjUzBRMB0GA1UdDgQWBBSwxa3/EgP5Aq9QKjN///waCk2A +0jAfBgNVHSMEGDAWgBSwxa3/EgP5Aq9QKjN///waCk2A0jAPBgNVHRMBAf8EBTAD +AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAUU5iIfhi9B1Lpl5l8nSUy2RKwjBVVymfq +IYTqkPPFbhSOWAYEpdgi0J0NO/ieqU2/USK3gotEO/r1pmqekdekUyEglExON4j4 +RxpJdbz/l4MLXg2X48m2wDMFkaGwU8RuYUQkFRjht09Yz6lc2Z3CPm6R1xEt5/32 +yUfZZsJ/eMGq4bvLRFa9hfnZpGI8gRWC9bMoWNJCieejYEkDTUCxk5IGm2ZCthKD +IqyPj5uKaz3qzO5R9XdlHGWSmMVxQws+gc+5fTi+PoMgSSv/hnDghpj9YiSEA2Rq +YsMZ3CEhi4UES4wsPQFgmsXJidByQa4Rs3lU9djJ731Q9RhE7sCP +-----END CERTIFICATE----- diff --git a/src/test/java/com/influxdb/v3/client/testdata/certificates/client.key b/src/test/java/com/influxdb/v3/client/testdata/certificates/client.key new file mode 100644 index 00000000..64c74bbb --- /dev/null +++ b/src/test/java/com/influxdb/v3/client/testdata/certificates/client.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDNRUUv06533LU9 +IwVIPc8ir8wDiwUAumLHRPplwon/3Z7i3vfDb/7r6EPR0Bdp+WtKBj7M0PY0X8/x +WhD2eu/QX/W6Hzd9OKXP3RfM7vC0vNGdnqEjnvA/GEOB/vDoBCjsqm2H12FAIb+m +wQ7mKjItVy6BkSNUIEJnYtKaGzXBF7mrJS5WIYxoN0ndHuyBfEmwwp0Aq8/pQ4KN +Hu4dD0WOgBf8O14n1keVvHcLH5d2cCM5H+FeOtaJNaVb6qtKUstvYrl5/0I814r6 +j0cQi9lIjR/U8XRA+KGBGtRuWHETcKpmdCQm0qOz1BrMKWzWqsYytedsASVTNmPa +6FnOiZSFAgMBAAECggEAOziGZu930M3knfBKGFb4uQWl4snLVjPvxpTAdvZCsQZj +zglMyt43WiEm29PgxWq3iOI3YQjSVzbjO7rj1PCP2nqs3pcd/6sFr2wWb0HqQQKO +qR44WBoFLvbVlx4Rq6Zzlh/vPLcnMATskXP2U/INiuYxnSDd4+nwrnokz2AVSJEG +dGuVchOXSy3KfJ68ZU0wyCL/OvFzXg0bVO/o82YcXj2cU0VzdY8Rm/mlLVi8dDhw +UqudffLk7V6p3VXYtJjXcxTmVOrTjE2nXs2fBe806h+pN1VnrtiZsbkaNuhr/gKF +dPPkF+y/sfs6XZy9nOkLjdar4+uPrCn7LBX8SdvviwKBgQD57AZOfJK4hOq8l9Ax +vjZT+kd98h/q/DN95mlaRUBqtiKgudA9KpP0RaNSF4OZtFq6ShG2PHPkn0mIJ6yw +3oMFLvc/X/0NSx8ZURRVaHC07DCW79mM1sIsv75jzoYojCgho5X7A0C6V+d2l6+R +Fp2gi3jx7idT3MPOUIBeB55lHwKBgQDSQ0DHhhX2IDng5HubOMaJmfgzsuGDtHQv +i0gT15qmObxE7XXjxRU5BnB5AydrAMREOD6XNgAEHTq6hU0DbP0qUS1YS7JicvXD +MrYDSNMiOwhf8AuW56/n3xOVja0vqDmZugdM1q5DCRcTnGlL4hS8jAOB4C7P0kOq +NTuyecmN2wKBgBVufAv2+s3/9/E8Q4pAeYAyEjdmr/4Cqpe9ToODNMbx335Q8N6N +7CoiXqMHiS2BHdajy2kJPhb44eFfJZv1ON8U6/bvUE/3hS8ymL/KOQAK0Q/geZNu +RzB3MOh2KWMIwdhAovfni4FvUFYHI/MZMWM3EqQMfCrg35mtpZRqqtaZAoGAPmA5 +LXe3uCZjRTZC/Q9gxp1opl2gzSORfDFDlq9Vb46dLY2MdZ+X5izk3OF0pie6Ecrl +tpIMeyv4qjC1YhjJGe6vFwyNxOr5wYifumW2TOdaWquppIDjDFR/SNSY1hR/0f0A +AWmGlNhzghvamOpe4Hhg6fyJppln/WyBYzcB9hUCgYEAoA6h5VW7Y6qndjLixH/G +zGJkIDDRp97jEcIlZt2gpK6M4eHOaXE4LXJslUyKAXaREX+fCP8wSgEuLvEHYXHO +hO7TK3ysPI1UsLduPA1xM171MP25oZjs+cRPvoXzdz6k1m4sn9ftmV9XVSE7CNVV +GeKnEV8BAYwRAKeoavZM0bI= +-----END PRIVATE KEY----- diff --git a/src/test/java/com/influxdb/v3/client/testdata/certificates/client.p12 b/src/test/java/com/influxdb/v3/client/testdata/certificates/client.p12 new file mode 100644 index 00000000..83c7293b Binary files /dev/null and b/src/test/java/com/influxdb/v3/client/testdata/certificates/client.p12 differ diff --git a/src/test/java/com/influxdb/v3/client/testdata/certificates/client_pkcs8.key b/src/test/java/com/influxdb/v3/client/testdata/certificates/client_pkcs8.key new file mode 100644 index 00000000..48ae830a --- /dev/null +++ b/src/test/java/com/influxdb/v3/client/testdata/certificates/client_pkcs8.key @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFNjBgBgkqhkiG9w0BBQ0wUzAyBgkqhkiG9w0BBQwwJQQQ5aaRvLf8W1TGutZi +OMCyPgIDD0JAMAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBDRRiANcE00LJIu +GMt9sio/BIIE0G3qanDXKcg6OTWjAWDN1E4NFv+D/jl/UVhEJF+7Cc9ru9EfpLlw +wX24MZt1dDlahH6FHFgSrIdTjGtWQgNVFR3QvjcopbcCQiXzIZIVBEKKn7KISzxj +dIvGSvw8BPyKHscDXlSCbiIEfxzXNi9BtOQCeetFF+FhmbEPrfRLfo+c/R+BmPPD +h7an/zR1DT1Ri+dsQBIPvZOUGBoSabbZiCDdNxzoK8snElO9T+w8r1jZH07K/Udn +aKqu1rRSpEYrOrG4pZE/qdkcyNU5jEX42JRbfz1Sp+KhNs/rxQwYWXlh6roa84Es +e+aOwteUB+AR6W0zXq0qkemYbWtCAcVVW071R7YFN81Z+pyjubrO/i0Xd6uoIYOg +D1ZfYHFT/7Jh4Zti4rqIR+8L8JljPyU39fXsbmJ6Ge+UEX+ZGGOYI3/lM+DvCn5H +67l5b36Adoqz4hfA0OXf1hpyH9cVjutFDcV8k7PLHywgIiBdhtlKVSnf57DoSuGz +2ca9nx3jiEBk2gcFvg3SPeyxsP0tcJp755pGK3D/NGzuWOIRjmSSfqDgucmNbFwW +ybzMzpvkwxPfmrMa+Wawrr2/AqDEmTigpVUzBogfh0rNEe0erULrQnuHpj1TP4Mk +OOKg8Sj1lI6+Q+Ank2SklObm4tFkO7gnsDQpGF4bHunsH9skdGD2DmSMqvgZsgmp +wt0x+30FeO1b/WcaSTb2qFst/Yf3bbR282gftoqN88l1P2wY6FKRWPysppHwmZhg +1YrFUaO4KXYyNQC//Wv2B77DWDGKl0r+6cKht96KSPUnGMq2/hmt4wVwzQl/kYDR +qPtr482CHgIuJfgWwFpsyyxFfVRKgOkHbPoX5KgJ/uCyZt8sGZB/kw+M3wvDKbrm +hE4rWDHicr0iSm8wXRh3MrYqkSdFfxS3+2euo0Yl5Uj9eS/2XPp3lVaqBnIydDxy +xlG1NqBW/pK77JVyG190B/WfcXUvaewxK9/IQLC0Sh/jKm7hrLey2BS7RojmoP6p +zrAwOKVR/ipGMrluuojh6Oq1HYF+Kwc/7ULWY96sSPGKVF05kPVszvC2b9oiqbBs +kjO6lARtC4MXwJqbCScm+5miZLTVO5sp8pHZeVg+4N9RnYVCWRkPSRhN2kSzGVh7 +ISgwjpP+WN0RbNuU4bQFKGRP5i6mq0sPkL7Zg6KO4L3eSWYpaHicw5ZTV/WzDX5D +kFhxI5ZCnc/gffNLmJal1VL/sRsnmMATE4lQEEOjTW12x/Gifq1f7Fp39IEt2mTu ++D5jrkdlcSnAlqo9exPl0TVDfL4Z3ZGFlf/o7jZaGqIzKjBYGLsZdjgkRQ++mTik +27DZ5oYknFtzSlddb26Irg8DE3u0C/5k1PnLw25F+SAxMxcWIsaMiP35O09g+/3X +OVOmXmxwaV2dLdvQlSj2KNx7NSWkAzVCYSgeFlfxCgaQVrYh1dB1x2zYelKVp+ao +yr6VfRCiX+mVgcSFHPRUHxndwnco8OIWhS5LOb7tttI1LU62otliQ9x0JECqYGdI +WHXFC9zWrdDaKpBzwedYpDZM1CalXKL9ruVo2Gw6ni8pf5beCeFN+V6kDuFDqrAZ +6OjRoLqpkbbB+dJbPL8PfCh2f0XCzlYjemnzmPh22DA14t1IfnBRNDD9 +-----END ENCRYPTED PRIVATE KEY----- diff --git a/src/test/java/com/influxdb/v3/client/testdata/certificates/generate-self-signed-cert.sh b/src/test/java/com/influxdb/v3/client/testdata/certificates/generate-self-signed-cert.sh new file mode 100644 index 00000000..4ccdde58 --- /dev/null +++ b/src/test/java/com/influxdb/v3/client/testdata/certificates/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/src/test/java/com/influxdb/v3/client/testdata/certificates/influxdb.crt b/src/test/java/com/influxdb/v3/client/testdata/certificates/influxdb.crt new file mode 100644 index 00000000..fc6c1ffa --- /dev/null +++ b/src/test/java/com/influxdb/v3/client/testdata/certificates/influxdb.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDazCCAlOgAwIBAgIUCuI5OLr6MTBM04hUhk/cD7FaaFgwDQYJKoZIhvcNAQEL +BQAwNzESMBAGA1UEAwwJbG9jYWxob3N0MRQwEgYDVQQKDAtEZXZlbG9wbWVudDEL +MAkGA1UEBhMCVVMwHhcNMjYxMDI5MTAzNzA5WhcNMzYxMDI2MTAzNzA5WjA3MRIw +EAYDVQQDDAlsb2NhbGhvc3QxFDASBgNVBAoMC0RldmVsb3BtZW50MQswCQYDVQQG +EwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJKeu7sXw4Hmim6m +tUQCbHLbuovpxtrJPtJq7tvwR38zXeD1v1ht1pzknByT/GoUBAasUUdFuNnLdk8b +xNhcSxShSbrtvypu00Ar+vJWavXOfJ6Jbqd3N8CwhIxhivE3qy3vyHXRm/6CrBe/ +nq2H/6LN7tC04n83FM6guEFJPJ8RtJCMTV4rngsJWha5+8JJjEAOwjeKmq+kOD8v +g0B8FBUPgB7f5ewJIhny7X5/WxjJ10/mm5BTPWSKPz5BDE5U+9h4ajeAV02SJD2b +ZnxOkQlusn6l22hVGwgGXmfu15kw9CHEHdy+skYyrciCUXhHcbb3P/kNvi+6bdwa +g2jHcTUCAwEAAaNvMG0wHQYDVR0OBBYEFNhopvlaL0fs7z6yHuEBVn4reBGmMB8G +A1UdIwQYMBaAFNhopvlaL0fs7z6yHuEBVn4reBGmMA8GA1UdEwEB/wQFMAMBAf8w +GgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQAF ++y0SHe3W4hbhxnBvty33LlVrveFVHEAw4+BEi2r9PONNtxnz1dWExNhH5ZgGCIVz +sQQ8IDosbMdR1X8UwQpdlQtJyzq6olFao8gWGAngD+jaXEc8WbZUW5ilmUDrKf10 +lfiYdOU+Idsrrill0KCLgdijtCWOftCNV2SPPGn7ReGDUHu1I8MiTeX1GKkMd5b3 +kPhtJLjxgXUxVrXOML88qqaBh5G8sx1oJivkbsyPpBn6coxQ5tMB5LqSVfJOu9yc +Ajm39LcONlegWGd+qhvNJ50KjIeAtkM4wqls7ErKFSuUxeO5qNsCCLprfx2AISFK +OrUX7hMEWppILwdNh20u +-----END CERTIFICATE----- diff --git a/src/test/java/com/influxdb/v3/client/testdata/certificates/influxdb.key b/src/test/java/com/influxdb/v3/client/testdata/certificates/influxdb.key new file mode 100644 index 00000000..9587d7c9 --- /dev/null +++ b/src/test/java/com/influxdb/v3/client/testdata/certificates/influxdb.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCSnru7F8OB5opu +prVEAmxy27qL6cbayT7Sau7b8Ed/M13g9b9Ybdac5Jwck/xqFAQGrFFHRbjZy3ZP +G8TYXEsUoUm67b8qbtNAK/ryVmr1znyeiW6ndzfAsISMYYrxN6st78h10Zv+gqwX +v56th/+ize7QtOJ/NxTOoLhBSTyfEbSQjE1eK54LCVoWufvCSYxADsI3ipqvpDg/ +L4NAfBQVD4Ae3+XsCSIZ8u1+f1sYyddP5puQUz1kij8+QQxOVPvYeGo3gFdNkiQ9 +m2Z8TpEJbrJ+pdtoVRsIBl5n7teZMPQhxB3cvrJGMq3IglF4R3G29z/5Db4vum3c +GoNox3E1AgMBAAECggEAChhs47J8rzaRBJJs+7xQ/E2d2CFmJE1kCnf7b2Bpg9wl +FCbHoI6bjtz81MjpweIT7uOxiv9FfZWRVVwbnywnrUl3uqvBFGpiGFKO6l3B6p/n +m8DpwhUm3RkqX0IeH4LOQhD3FAm1PH6B2/bBNcCd/PdCANPbnpfL0nhlh3VD+83R +FI0xjAtQ1IQa6kfNhRQTfzYPhTzamor1W/Uq/vhvCJp5DF9z5bJfoDMsEnNSeKlI +xuc+1b+lyKja+Qivnfv0mRwUxEygNLAOmtyBjvsvzekSMVEQm6QGT+n1AldeGkmP +AlkSa7J4r7z8cJLCrX+d3Y+iJ/o/uwiA4vD0LHlzwQKBgQDKoscnT19EOQnpgFI8 +ARXmrNdmjgKc54nyfJ9xlViogcxfre0S93gwQYjG+wHwyD1Im/FrKiCWp0P52r38 +DDdl+WDYITlJ5QAg36LADlUeRJSElfPoftyidRdGJkISVvLfuWgUBAa2GDM3TucE +Himcdzm0UDi0qPb6KZJU8r1qYQKBgQC5O4E8Vvfe8ykyq/qqg/u0l7HbLWHDcHpE +db8+ZK6cOGDYZrVeZHQM2GXSzLUogdzHbHJaaLkwEYM4qKiN6I+jmMNx9J0VyXPY +B2vgPVA7I24vebbNvcerqfre8OWBSS7Unlz1lAXww8mAUIWWk9LtUkNkBsGYTZfq +TBzW3z1/VQKBgQC1exnQTBbvXAJTnTiJQxOhV/VGhTMz4W67vP9z06TmrfEjU/r6 +vKZo2ZitVQsE4eF5ESkjqEQtiTqpIE582VgYPUON7huH4c++oWv6hBuZkyRMppwf +AteJrEKe8g7ErBizZDMhkgWMupnMFB90oar01hbZNevDaXxgyjCjYg9goQKBgAbr +QhiO5xNc4d0Sr3pPE8fbIrmdKFm0idykyU/7UspjELsEmyG7fto7+xG0s8+fii+o +zVyzLLeu78feBO3IEhV6T1KiYHu/2qPnIxBtOeH7oNIFKd7lh9/RqZ1KQkhiA23N +vIsvzEkCBzTTzSmiu+5BdGL4HaMiwtntneYo0JNBAoGAes4n3XYApugRulCUtR3a +59aoNFSWEA5G2M6iOUOWLdYg4ARVIeIz1tSPkZnNBdANXkY7qXpTi+QKyblc4ac9 +6TWH7FFE+ipPZ8Xjm1txvKNdWnsXAdMMdacbtPYWm9b/93jixq1pOfHQFuPJpqNH +mZoE6SUmooHxrHu5yctgGVA= +-----END PRIVATE KEY----- diff --git a/src/test/java/com/influxdb/v3/client/testdata/certificates/influxdb.p12 b/src/test/java/com/influxdb/v3/client/testdata/certificates/influxdb.p12 new file mode 100644 index 00000000..47d8f665 Binary files /dev/null and b/src/test/java/com/influxdb/v3/client/testdata/certificates/influxdb.p12 differ diff --git a/src/test/java/com/influxdb/v3/client/testdata/certificates/other-server.crt b/src/test/java/com/influxdb/v3/client/testdata/certificates/other-server.crt new file mode 100644 index 00000000..d4b8eda3 --- /dev/null +++ b/src/test/java/com/influxdb/v3/client/testdata/certificates/other-server.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDazCCAlOgAwIBAgIUGU4zBhzMFJ1hIMTiqPg7hHnlTd0wDQYJKoZIhvcNAQEL +BQAwNzESMBAGA1UEAwwJbG9jYWxob3N0MRQwEgYDVQQKDAtEZXZlbG9wbWVudDEL +MAkGA1UEBhMCVVMwHhcNMjYxMDI5MTAzNzA5WhcNMzYxMDI2MTAzNzA5WjA3MRIw +EAYDVQQDDAlsb2NhbGhvc3QxFDASBgNVBAoMC0RldmVsb3BtZW50MQswCQYDVQQG +EwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALV80uIg8NVtlw8O +2CQjLS8qOOUBWqMF0AaXPyElrPbGjE6vKz8ScbTePGj5YIl+KMclAuQAGMBxrZ+W +Bv2xOKua927mlOUFaExTcTT3Ya0oGJaUzOPGiV/Zoiz3+veEb9m26H7ngvdDmHjH +3P3qpsWbqoo8JWF3FT4JZ9RendMNuPblnxpgGgirLPccj61aDO/2d/xX9PirlIfS +Hv3W8bJO3bvCVqO4QQ41z9DtR5NFwnSe1QqoMw9kCpBTqZUiTLUjzWpyE5HTmvpm +ImK8uBsw2TLO2PE+yRR4oxS4CTJ/e4vcM9JJInqX2xl0fgsFxIgf2WBku0sffvOm +s8zfBvcCAwEAAaNvMG0wHQYDVR0OBBYEFBkdD0m4zH0bYVFxW7arArtaYyboMB8G +A1UdIwQYMBaAFBkdD0m4zH0bYVFxW7arArtaYyboMA8GA1UdEwEB/wQFMAMBAf8w +GgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQA2 +HXDimpOM75yYQ40Nhw/WFGZPshckouAYzDgwgOp9arrGs8x1X2swiHLbO6QD0oXu +UJsI/iEccha101glK+2d16U8hX0jmj4oLEBRt5URy0rpTXtl5QGmkn8/tf5Mxb7N +w8DZeo1xacXRboTQgPSw9dKRy6Tw/HqJSY5cVfNU80gPPUoQoqgifJxqxYDumeMa +vtH8Z7yqk1AXl3WUlG8hKk5KvsRN1TzvMWjqDzWcSElGNAz9ctAHTYjGyFWFIcaS +Vueom0wSYkkogKDOqo+evx1DPAriksmzoJQ3doXIIR65HvyvpGI9+0YCNUd87PjD +Dn+I1GLBJE1JF5X67ymO +-----END CERTIFICATE----- diff --git a/src/test/java/com/influxdb/v3/client/testdata/certificates/other-server.key b/src/test/java/com/influxdb/v3/client/testdata/certificates/other-server.key new file mode 100644 index 00000000..42189430 --- /dev/null +++ b/src/test/java/com/influxdb/v3/client/testdata/certificates/other-server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC1fNLiIPDVbZcP +DtgkIy0vKjjlAVqjBdAGlz8hJaz2xoxOrys/EnG03jxo+WCJfijHJQLkABjAca2f +lgb9sTirmvdu5pTlBWhMU3E092GtKBiWlMzjxolf2aIs9/r3hG/Ztuh+54L3Q5h4 +x9z96qbFm6qKPCVhdxU+CWfUXp3TDbj25Z8aYBoIqyz3HI+tWgzv9nf8V/T4q5SH +0h791vGyTt27wlajuEEONc/Q7UeTRcJ0ntUKqDMPZAqQU6mVIky1I81qchOR05r6 +ZiJivLgbMNkyztjxPskUeKMUuAkyf3uL3DPSSSJ6l9sZdH4LBcSIH9lgZLtLH37z +prPM3wb3AgMBAAECggEAAYJXLeqIAeIRCwD6kCMqWHR9yxdOwhI2zXWgte1rPgQW +csuWCJNmQR95Ku0ZRnC/5B/Gw75lqX5ogG4wHFxo79yjmAYwXaQaK476NGGmGupo +Erx7BsAflup8vmG3x5FUU9eBKX5Seeh+WrfT0AxRo5Fbk8iglkf+liAtB6/1EDcs +16gHSVnog0EC9+UKJ3RZfrao29T3SRQ9OIQvmF7qSLd/q/iQinMHNKOV6lWz5Hpg +wzaR7YnFh8v2jkArb0UrqfQbbMRrwZ6StFRz76e4YdfIelzd4c48Y5lHypAZ9JBH +I9mmtNALq2xTyYGvF14L+gT+ED08og0zzm0Eb+YVpQKBgQDgMvUFrYTlq9yFAf2Z +jR755Z4fkGn1G8LRxBuuo41kC7ocILgB4RiGDJAF8/xLKMYGKO0EnqQynNmFF+oU +pRYbensQIcslX8NqIpo3hixX2UQyt5q7yDuJjdyo8KaYa0XOz+a5ZPTJzOsP/yzJ +6wm6RDbwiDO7cJzVcnHKM989LQKBgQDPOvEsl8dNz0JUir/UPmchMoPDf4G+k83U +DoKKldTfQ2CScOLpynO8WlNWP0SW3UYL4EcpK5DROIOPYC1JuiIVHGOqGTuAnUtO +T46JXkiMfon2ATAFi2ro2w3RmLIadDhLi2/+pfkY5oxEFF/xo3dgbxQ6lRhFqhPt +/0cD8XmTMwKBgHIffjn1qcB/SzEpYj3XZ/0stOC8UjNCfljoym6LMl3K2mGXBhNu +0Vzq6CUfnT1k8+gOzRec6VgaH19wXOdFYYt7PUlo6k+L/5z3IClDUy8kmWJ3s3wC +4MuUOs6EMUlE0bQEgB2/FhvjA4SRD+t4iOsX7h9iNXMkT7EXIHUdEnXBAoGAW96n +mLtY8hfWMiCo1nEryz3XyE24uUJRV5dvK/0suCXopENDdZ35HSF2HKBDyhYivZUa +NxhHRVmmiWN78FmCTpWdYYV6KZSSFA+VAzU3OIHIq7TvEyYxqTWjtK3bF3s6kdMp +PV1bRzy+JlhaCIgNnkTgVPBQKGI0H6iyVP8yhJ0CgYBHHGIdVxXHdlzpZbovZTh3 +wbsAWum6OiQZgAHlROE5EPQ9TN5Ee/+5MqGDhgOLuhQoD6PENwbExY9v/Pr13TbA +SHasvCnarv42WZGIDOKqtYWw4HRQ5MS4J01SSwVo59HGL8ZlmqxSHOm83KFknQ82 +L56ZvWE6jVRSXuRXrJHBVg== +-----END PRIVATE KEY----- diff --git a/src/test/java/com/influxdb/v3/client/testdata/certificates/other-server.p12 b/src/test/java/com/influxdb/v3/client/testdata/certificates/other-server.p12 new file mode 100644 index 00000000..4b4985a2 Binary files /dev/null and b/src/test/java/com/influxdb/v3/client/testdata/certificates/other-server.p12 differ