Repository: kroxylicious/kroxylicious (java)

Issue:
Allow JdkTls class to be shared between KMSs (or Filters)

**Is your feature request related to a problem? Please describe.**

The role of `JdkTls` class is to provide a customised `javax.net.ssl.SSLContext` which has been tuned to the user's use-case.  This might be the application of keystores or truststore, or restrictions on TLS cipher suites or protocols.  The resulting `SSLContent` is used to initialise the HTTPClient used to connect with 3rd party systems.

The class was written for the Vault KMS implementation.  The AWS KMS implementation has copied it.

It'd be nice to move this class to a shared location.  

It is likely some Filter implementation and KMS implementations might need this function.

**Describe the solution you'd like**

A standard way to customized SSLContent objects.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.


Pull request:
Fix #1218 - introduce a shared module for TLS support classes used by KMSs
### Type of change

_Select the type of your PR_

- Refactoring

### Description

Lift and shift the `JdkTls` classes copied pasted by both the AWS and HashiCorp KMS implementation to a new common module so they can be shared.

Fortanix DSM KMS needs the same functionality which has resurfaced the issue now.

No user facing change.

### Additional Context

_Why are you making this pull request?_

### Checklist

_Please go through this checklist and make sure all applicable tasks have been done_

- [ ] Write tests
- [ ] Update documentation
- [ ] Make sure all unit/integration tests pass
- [ ] Make sure all Sonarcloud warnings are addressed or are justifiably ignored.
- [ ] If applicable to the change, [trigger the system test suite](../blob/main/DEV_GUIDE.md#jenkins-pipeline-for-system-tests).  Make sure tests pass.
- [ ] If applicable to the change, [trigger the performance test suite](../blob/main/PERFORMANCE.md#jenkins-pipeline-for-performance). Ensure that any degradations to performance numbers are understood and justified.
- [ ] Ensure the PR references relevant issue(s) so they are closed on merging.
- [ ] For user facing changes, update CHANGELOG.md (remember to include changes affecting the API of the test artefacts too).

> **_NOTE:_**  You must be a member of `@kroxylicious/developers` to trigger the system test and performance test suites.  If you are not part of this group, comment on the PR requesting a trigger, tagging `@kroxylicious/developers`.


Patch:
diff --git a/kroxylicious-bom/pom.xml b/kroxylicious-bom/pom.xml
index 3dead69e..9f269481 100644
--- a/kroxylicious-bom/pom.xml
+++ b/kroxylicious-bom/pom.xml
@@ -123,6 +123,12 @@
                 <version>${project.version}</version>
             </dependency>
 
+            <dependency>
+                <groupId>io.kroxylicious</groupId>
+                <artifactId>kroxylicious-kms-tls-support</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+
             <dependency>
                 <groupId>io.kroxylicious</groupId>
                 <artifactId>kroxylicious-kms-provider-kroxylicious-inmemory</artifactId>
diff --git a/kroxylicious-kms-provider-aws-kms/pom.xml b/kroxylicious-kms-provider-aws-kms/pom.xml
index 01a385bc..62235ee1 100644
--- a/kroxylicious-kms-provider-aws-kms/pom.xml
+++ b/kroxylicious-kms-provider-aws-kms/pom.xml
@@ -36,6 +36,10 @@
             <groupId>io.kroxylicious</groupId>
             <artifactId>kroxylicious-api</artifactId>
         </dependency>
+        <dependency>
+            <groupId>io.kroxylicious</groupId>
+            <artifactId>kroxylicious-kms-tls-support</artifactId>
+        </dependency>
 
         <!-- third party dependencies - build and compile -->
         <dependency>
diff --git a/kroxylicious-kms-provider-aws-kms/src/main/java/io/kroxylicious/kms/provider/aws/kms/config/Config.java b/kroxylicious-kms-provider-aws-kms/src/main/java/io/kroxylicious/kms/provider/aws/kms/config/Config.java
index bcaec568..cb1bdd7e 100644
--- a/kroxylicious-kms-provider-aws-kms/src/main/java/io/kroxylicious/kms/provider/aws/kms/config/Config.java
+++ b/kroxylicious-kms-provider-aws-kms/src/main/java/io/kroxylicious/kms/provider/aws/kms/config/Config.java
@@ -16,6 +16,8 @@ import com.fasterxml.jackson.annotation.JsonProperty;
 
 import io.kroxylicious.proxy.config.secret.PasswordProvider;
 import io.kroxylicious.proxy.config.tls.Tls;
+import io.kroxylicious.proxy.tls.JdkTls;
+import io.kroxylicious.proxy.tls.SslConfigurationException;
 
 /**
  * Configuration for the AWS KMS service.
diff --git a/kroxylicious-kms-provider-hashicorp-vault/pom.xml b/kroxylicious-kms-provider-hashicorp-vault/pom.xml
index f8461513..a3aaae51 100644
--- a/kroxylicious-kms-provider-hashicorp-vault/pom.xml
+++ b/kroxylicious-kms-provider-hashicorp-vault/pom.xml
@@ -54,10 +54,6 @@
             <groupId>org.apache.kafka</groupId>
             <artifactId>kafka-clients</artifactId>
         </dependency>
-        <dependency>
-            <groupId>org.slf4j</groupId>
-            <artifactId>slf4j-api</artifactId>
-        </dependency>
         <dependency>
             <groupId>com.github.spotbugs</groupId>
             <artifactId>spotbugs-annotations</artifactId>
@@ -90,19 +86,13 @@
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>org.bouncycastle</groupId>
-            <artifactId>bcpkix-jdk18on</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.bouncycastle</groupId>
-            <artifactId>bcprov-jdk18on</artifactId>
+            <groupId>io.kroxylicious</groupId>
+            <artifactId>kroxylicious-kms-test-support</artifactId>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>io.kroxylicious</groupId>
-            <artifactId>kroxylicious-kms-test-support</artifactId>
-            <scope>test</scope>
+            <artifactId>kroxylicious-kms-tls-support</artifactId>
         </dependency>
     </dependencies>
 
diff --git a/kroxylicious-kms-provider-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/config/Config.java b/kroxylicious-kms-provider-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/config/Config.java
index fa9ae241..d5772e8e 100644
--- a/kroxylicious-kms-provider-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/config/Config.java
+++ b/kroxylicious-kms-provider-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/config/Config.java
@@ -16,6 +16,8 @@ import com.fasterxml.jackson.annotation.JsonProperty;
 
 import io.kroxylicious.proxy.config.secret.PasswordProvider;
 import io.kroxylicious.proxy.config.tls.Tls;
+import io.kroxylicious.proxy.tls.JdkTls;
+import io.kroxylicious.proxy.tls.SslConfigurationException;
 
 import edu.umd.cs.findbugs.annotations.NonNull;
 
diff --git a/kroxylicious-kms-provider-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/config/JdkTls.java b/kroxylicious-kms-provider-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/config/JdkTls.java
deleted file mode 100644
index 45949d6d..00000000
--- a/kroxylicious-kms-provider-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/config/JdkTls.java
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Copyright Kroxylicious Authors.
- *
- * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
- */
-
-package io.kroxylicious.kms.provider.hashicorp.vault.config;
-
-import java.io.FileInputStream;
-import java.security.KeyStore;
-import java.security.SecureRandom;
-import java.security.cert.X509Certificate;
-import java.util.Optional;
-
-import javax.net.ssl.KeyManager;
-import javax.net.ssl.KeyManagerFactory;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.TrustManagerFactory;
-import javax.net.ssl.X509TrustManager;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import io.kroxylicious.proxy.config.secret.PasswordProvider;
-import io.kroxylicious.proxy.config.tls.InsecureTls;
-import io.kroxylicious.proxy.config.tls.KeyPair;
-import io.kroxylicious.proxy.config.tls.KeyProvider;
-import io.kroxylicious.proxy.config.tls.KeyProviderVisitor;
-import io.kroxylicious.proxy.config.tls.PlatformTrustProvider;
-import io.kroxylicious.proxy.config.tls.Tls;
-import io.kroxylicious.proxy.config.tls.TrustProvider;
-import io.kroxylicious.proxy.config.tls.TrustProviderVisitor;
-import io.kroxylicious.proxy.config.tls.TrustStore;
-import io.kroxylicious.proxy.tag.VisibleForTesting;
-
-import edu.umd.cs.findbugs.annotations.NonNull;
-import edu.umd.cs.findbugs.annotations.Nullable;
-
-/**
- * Encapsulates parameters for an TLS connection with Vault.
- *
- * @param tls tls configuration
- *
- * TODO ability to restrict by TLS protocol and cipher suite (<a href="https://github.com/kroxylicious/kroxylicious/issues/1006">#1006</a>)
- */
-public record JdkTls(Tls tls) {
-
-    private static final Logger logger = LoggerFactory.getLogger(JdkTls.class);
-
-    public JdkTls {
-        if (tls != null && tls.key() != null) {
-            logger.warn("TLS key material is currently not supported by the vault client");
-        }
-    }
-
-    public static final X509TrustManager INSECURE_TRUST_MANAGER = new X509TrustManager() {
-
-        // suppressing sonar security warning as we are intentionally throwing security out the window
-        @SuppressWarnings("java:S4830")
-        @Override
-        public void checkClientTrusted(X509Certificate[] chain, String authType) {
-            // do nothing, the api is to throw if not trusted
-        }
-
-        // suppressing sonar security warning as we are intentionally throwing security out the window
-        @SuppressWarnings("java:S4830")
-        @Override
-        public void checkServerTrusted(X509Certificate[] chain, String authType) {
-            // do nothing, the api is to throw if not trusted
-        }
-
-        @Override
-        public X509Certificate[] getAcceptedIssuers() {
-            return new X509Certificate[0];
-        }
-    };
-    public static final TrustManager[] INSECURE_TRUST_MANAGERS = { INSECURE_TRUST_MANAGER };
-
-    public SSLContext sslContext() {
-        try {
-            if (tls != null) {
-                TrustManager[] trustManagers = null;
-                KeyManager[] keyManagers = null;
-                if (tls.trust() != null) {
-                    trustManagers = getTrustManagers(tls.trust());
-                }
-                if (tls.key() != null) {
-                    keyManagers = getKeyManagers(tls.key());
-                }
-                return getSslContext(trustManagers, keyManagers);
-            }
-            else {
-                return SSLContext.getDefault();
-            }
-        }
-        catch (Exception e) {
-            throw new SslConfigurationException(e);
-        }
-    }
-
-    @VisibleForTesting
-    static KeyManager[] getKeyManagers(KeyProvider key) {
-        return key.accept(new KeyProviderVisitor<>() {
-            @Override
-            public KeyManager[] visit(KeyPair keyPair) {
-                throw new SslConfigurationException("KeyPair is not supported by vault KMS yet");
-            }
-
-            @Override
-            public KeyManager[] visit(io.kroxylicious.proxy.config.tls.KeyStore keyStore) {
-                try {
-                    if (keyStore.isPemType()) {
-                        throw new SslConfigurationException("PEM is not supported by vault KMS yet");
-                    }
-                    KeyStore store = KeyStore.getInstance(keyStore.getType());
-                    char[] storePassword = passwordOrNull(keyStore.storePasswordProvider());
-                    try (FileInputStream fileInputStream = new FileInputStream(keyStore.storeFile())) {
-                        store.load(fileInputStream, storePassword);
-                    }
-                    KeyManagerFactory instance = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
-                    char[] keyPassword = passwordOrNull(keyStore.keyPasswordProvider());
-                    keyPassword = keyPassword == null ? storePassword : keyPassword;
-                    instance.init(store, keyPassword);
-                    return instance.getKeyManagers();
-                }
-                catch (Exception e) {
-                    throw new SslConfigurationException(e);
-                }
-            }
-
-            @Nullable
-            private static char[] passwordOrNull(PasswordProvider value) {
-                return Optional.ofNullable(value).map(PasswordProvider::getProvidedPassword).map(String::toCharArray).orElse(null);
-            }
-        });
-    }
-
-    @NonNull
-    private static SSLContext getSslContext(TrustManager[] trustManagers, KeyManager[] keyManagers) {
-        try {
-            if (trustManagers == null && keyManagers == null) {
-                return SSLContext.getDefault();
-            }
-            SSLContext context = SSLContext.getInstance("TLS");
-            context.init(keyManagers, trustManagers, new SecureRandom());
-            return context;
-        }
-        catch (Exception e) {
-            throw new SslConfigurationException(e);
-        }
-    }
-
-    @VisibleForTesting
-    static TrustManager[] getTrustManagers(TrustProvider trust) {
-
-        return trust.accept(new TrustProviderVisitor<>() {
-            @Override
-            public TrustManager[] visit(TrustStore trustStore) {
-                if (trustStore.isPemType()) {
-                    throw new SslConfigurationException("PEM trust not supported by vault yet");
-                }
-                try {
-                    KeyStore instance = KeyStore.getInstance(trustStore.getType());
-                    char[] charArray = trustStore.storePasswordProvider() != null ? trustStore.storePasswordProvider().getProvidedPassword().toCharArray() : null;
-                    instance.load(new FileInputStream(trustStore.storeFile()), charArray);
-                    TrustManagerFactory managerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
-                    managerFactory.init(instance);
-                    return managerFactory.getTrustManagers();
-                }
-                catch (Exception e) {
-                    throw new SslConfigurationException(e);
-                }
-            }
-
-            @Override
-            public TrustManager[] visit(InsecureTls insecureTls) {
-                if (insecureTls.insecure()) {
-                    return INSECURE_TRUST_MANAGERS;
-                }
-                else {
-                    return getDefaultTrustManagers();
-                }
-            }
-
-            @Override
-            public TrustManager[] visit(PlatformTrustProvider platformTrustProviderTls) {
-                return getDefaultTrustManagers();
-            }
-
-            private static TrustManager[] getDefaultTrustManagers() {
-                try {
-                    TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
-                    factory.init((KeyStore) null);
-                    return factory.getTrustManagers();
-                }
-                catch (Exception e) {
-                    throw new SslConfigurationException(e);
-                }
-            }
-        });
-    }
-}
diff --git a/kroxylicious-kms-provider-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/config/SslConfigurationException.java b/kroxylicious-kms-provider-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/config/SslConfigurationException.java
deleted file mode 100644
index 1fbed505..00000000
--- a/kroxylicious-kms-provider-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/config/SslConfigurationException.java
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * Copyright Kroxylicious Authors.
- *
- * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
- */
-
-package io.kroxylicious.kms.provider.hashicorp.vault.config;
-
-public class SslConfigurationException extends RuntimeException {
-    public SslConfigurationException(Exception cause) {
-        super(cause);
-    }
-
-    public SslConfigurationException(String message) {
-        super(message);
-    }
-}
diff --git a/kroxylicious-kms-tls-support/pom.xml b/kroxylicious-kms-tls-support/pom.xml
new file mode 100644
index 00000000..a5902012
--- /dev/null
+++ b/kroxylicious-kms-tls-support/pom.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright Kroxylicious Authors.
+
+    Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>io.kroxylicious</groupId>
+        <artifactId>kroxylicious-parent</artifactId>
+        <version>0.10.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>kroxylicious-kms-tls-support</artifactId>
+    <description>Optional TLS support classes for KMS implementations that need to make HTTPs connections to services.</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>io.kroxylicious</groupId>
+            <artifactId>kroxylicious-api</artifactId>
+        </dependency>
+
+        <!-- third party dependencies - build and compile -->
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.spotbugs</groupId>
+            <artifactId>spotbugs-annotations</artifactId>
+        </dependency>
+
+        <!-- third party dependencies - test -->
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-api</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.kroxylicious</groupId>
+            <artifactId>kroxylicious-kms-test-support</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
\ No newline at end of file
diff --git a/kroxylicious-kms-provider-aws-kms/src/main/java/io/kroxylicious/kms/provider/aws/kms/config/JdkTls.java b/kroxylicious-kms-tls-support/src/main/java/io/kroxylicious/proxy/tls/JdkTls.java
similarity index 97%
rename from kroxylicious-kms-provider-aws-kms/src/main/java/io/kroxylicious/kms/provider/aws/kms/config/JdkTls.java
rename to kroxylicious-kms-tls-support/src/main/java/io/kroxylicious/proxy/tls/JdkTls.java
index e37045d3..2d2dbc9a 100644
--- a/kroxylicious-kms-provider-aws-kms/src/main/java/io/kroxylicious/kms/provider/aws/kms/config/JdkTls.java
+++ b/kroxylicious-kms-tls-support/src/main/java/io/kroxylicious/proxy/tls/JdkTls.java
@@ -4,7 +4,7 @@
  * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
  */
 
-package io.kroxylicious.kms.provider.aws.kms.config;
+package io.kroxylicious.proxy.tls;
 
 import java.io.FileInputStream;
 import java.security.KeyStore;
@@ -50,7 +50,7 @@ public record JdkTls(Tls tls) {
 
     public JdkTls {
         if (tls != null && tls.key() != null) {
-            logger.warn("TLS key material is currently not supported by the AWS client");
+            logger.warn("TLS key material is currently not supported by this client");
         }
     }
 
@@ -111,7 +111,7 @@ public record JdkTls(Tls tls) {
             public KeyManager[] visit(io.kroxylicious.proxy.config.tls.KeyStore keyStore) {
                 try {
                     if (keyStore.isPemType()) {
-                        throw new SslConfigurationException("PEM is not supported by vault KMS yet");
+                        throw new SslConfigurationException("PEM is not supported by this client");
                     }
                     KeyStore store = KeyStore.getInstance(keyStore.getType());
                     char[] storePassword = passwordOrNull(keyStore.storePasswordProvider());
@@ -185,7 +185,7 @@ public record JdkTls(Tls tls) {
 
             @Override
             public TrustManager[] visit(PlatformTrustProvider platformTrustProviderTls) {
-                return new TrustManager[0];
+                return getDefaultTrustManagers();
             }
 
             private static TrustManager[] getDefaultTrustManagers() {
diff --git a/kroxylicious-kms-provider-aws-kms/src/main/java/io/kroxylicious/kms/provider/aws/kms/config/SslConfigurationException.java b/kroxylicious-kms-tls-support/src/main/java/io/kroxylicious/proxy/tls/SslConfigurationException.java
similarity index 88%
rename from kroxylicious-kms-provider-aws-kms/src/main/java/io/kroxylicious/kms/provider/aws/kms/config/SslConfigurationException.java
rename to kroxylicious-kms-tls-support/src/main/java/io/kroxylicious/proxy/tls/SslConfigurationException.java
index 2c5157cf..c0da89ea 100644
--- a/kroxylicious-kms-provider-aws-kms/src/main/java/io/kroxylicious/kms/provider/aws/kms/config/SslConfigurationException.java
+++ b/kroxylicious-kms-tls-support/src/main/java/io/kroxylicious/proxy/tls/SslConfigurationException.java
@@ -4,7 +4,7 @@
  * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
  */
 
-package io.kroxylicious.kms.provider.aws.kms.config;
+package io.kroxylicious.proxy.tls;
 
 public class SslConfigurationException extends RuntimeException {
     public SslConfigurationException(Exception cause) {
diff --git a/pom.xml b/pom.xml
index d1b4dde2..6283c683 100644
--- a/pom.xml
+++ b/pom.xml
@@ -445,6 +445,7 @@
         <module>kroxylicious-kafka-message-tools</module>
         <module>kroxylicious-kms</module>
         <module>kroxylicious-kms-test-support</module>
+        <module>kroxylicious-kms-tls-support</module>
         <module>kroxylicious-kms-provider-aws-kms</module>
         <module>kroxylicious-kms-provider-aws-kms-test-support</module>
         <module>kroxylicious-kms-provider-kroxylicious-inmemory</module>


Test patch:
diff --git a/kroxylicious-kms-provider-hashicorp-vault/src/test/java/io/kroxylicious/kms/provider/hashicorp/vault/TestVault.java b/kroxylicious-kms-provider-hashicorp-vault/src/test/java/io/kroxylicious/kms/provider/hashicorp/vault/TestVault.java
index 3cc666d8..c9b2f14f 100644
--- a/kroxylicious-kms-provider-hashicorp-vault/src/test/java/io/kroxylicious/kms/provider/hashicorp/vault/TestVault.java
+++ b/kroxylicious-kms-provider-hashicorp-vault/src/test/java/io/kroxylicious/kms/provider/hashicorp/vault/TestVault.java
@@ -20,6 +20,8 @@ import org.testcontainers.vault.VaultContainer;
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
 
+import io.kroxylicious.proxy.tls.CertificateGenerator;
+
 import static org.assertj.core.api.Assertions.assertThat;
 
 public class TestVault implements Closeable {
@@ -34,6 +36,7 @@ public class TestVault implements Closeable {
     private static final int TLS_PORT = 8202;
 
     private TestVault(CertificateGenerator.Keys serverKeys, CertificateGenerator.Keys clientKeys) {
+
         if (clientKeys != null && serverKeys == null) {
             throw new IllegalArgumentException("server TLS key/cert must be supplied if we want to configure client TLS auth");
         }
diff --git a/kroxylicious-kms-provider-hashicorp-vault/src/test/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultKmsTlsIT.java b/kroxylicious-kms-provider-hashicorp-vault/src/test/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultKmsTlsIT.java
index 283e913a..e49bd337 100644
--- a/kroxylicious-kms-provider-hashicorp-vault/src/test/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultKmsTlsIT.java
+++ b/kroxylicious-kms-provider-hashicorp-vault/src/test/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultKmsTlsIT.java
@@ -28,6 +28,7 @@ import io.kroxylicious.proxy.config.tls.InsecureTls;
 import io.kroxylicious.proxy.config.tls.KeyStore;
 import io.kroxylicious.proxy.config.tls.Tls;
 import io.kroxylicious.proxy.config.tls.TrustStore;
+import io.kroxylicious.proxy.tls.CertificateGenerator;
 
 import edu.umd.cs.findbugs.annotations.NonNull;
 
diff --git a/kroxylicious-kms-test-support/pom.xml b/kroxylicious-kms-test-support/pom.xml
index 7a0994a3..f8bc90b5 100644
--- a/kroxylicious-kms-test-support/pom.xml
+++ b/kroxylicious-kms-test-support/pom.xml
@@ -35,6 +35,16 @@
             <artifactId>junit-jupiter-api</artifactId>
             <scope>compile</scope>
         </dependency>
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcpkix-jdk18on</artifactId>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-jdk18on</artifactId>
+            <scope>compile</scope>
+        </dependency>
         <dependency>
             <groupId>com.github.spotbugs</groupId>
             <artifactId>spotbugs-annotations</artifactId>
diff --git a/kroxylicious-kms-provider-hashicorp-vault/src/test/java/io/kroxylicious/kms/provider/hashicorp/vault/CertificateGenerator.java b/kroxylicious-kms-test-support/src/main/java/io/kroxylicious/proxy/tls/CertificateGenerator.java
similarity index 93%
rename from kroxylicious-kms-provider-hashicorp-vault/src/test/java/io/kroxylicious/kms/provider/hashicorp/vault/CertificateGenerator.java
rename to kroxylicious-kms-test-support/src/main/java/io/kroxylicious/proxy/tls/CertificateGenerator.java
index cc229d01..6e913452 100644
--- a/kroxylicious-kms-provider-hashicorp-vault/src/test/java/io/kroxylicious/kms/provider/hashicorp/vault/CertificateGenerator.java
+++ b/kroxylicious-kms-test-support/src/main/java/io/kroxylicious/proxy/tls/CertificateGenerator.java
@@ -4,7 +4,7 @@
  * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
  */
 
-package io.kroxylicious.kms.provider.hashicorp.vault;
+package io.kroxylicious.proxy.tls;
 
 import java.io.File;
 import java.io.FileOutputStream;
@@ -135,11 +135,11 @@ public class CertificateGenerator {
     private static Path buildTrustStore(X509Certificate cert, String password, String suffix, String type) {
         try {
             File certFile = createTempFile("trust", suffix);
-            java.security.KeyStore pkcs12Store = java.security.KeyStore.getInstance(type);
-            pkcs12Store.load(null, null);
-            pkcs12Store.setCertificateEntry(ALIAS, cert);
+            java.security.KeyStore store = java.security.KeyStore.getInstance(type);
+            store.load(null, null);
+            store.setCertificateEntry(ALIAS, cert);
             char[] pass = password != null ? password.toCharArray() : null;
-            pkcs12Store.store(new FileOutputStream(certFile), pass);
+            store.store(new FileOutputStream(certFile), pass);
             return certFile.toPath();
         }
         catch (Exception e) {
@@ -184,16 +184,16 @@ public class CertificateGenerator {
     public static KeyStore createJksKeystore(KeyPair privateKeyPem, X509Certificate x509Certificate, String storePassword, String keyPassword) {
         try {
             File tempFile = createTempFile("keystore", "jks");
-            java.security.KeyStore store = java.security.KeyStore.getInstance("JKS");
+            java.security.KeyStore store = java.security.KeyStore.getInstance(JKS);
             store.load(null);
-            store.setKeyEntry("alias", privateKeyPem.getPrivate(), keyPassword.toCharArray(), new Certificate[]{ x509Certificate });
+            store.setKeyEntry(ALIAS, privateKeyPem.getPrivate(), keyPassword.toCharArray(), new Certificate[]{ x509Certificate });
             try (FileOutputStream stream = new FileOutputStream(tempFile)) {
                 store.store(stream, storePassword.toCharArray());
             }
             Path path = tempFile.toPath();
             Path storePasswordFile = writeToTempFile(storePassword);
             Path keyPasswordFile = writeToTempFile(keyPassword);
-            return new KeyStore(path, "JKS", storePassword, storePasswordFile, keyPassword, keyPasswordFile);
+            return new KeyStore(path, JKS, storePassword, storePasswordFile, keyPassword, keyPasswordFile);
         }
         catch (Exception e) {
             throw new RuntimeException(e);
diff --git a/kroxylicious-kms-test-support/src/test/java/io/kroxylicious/proxy/tls/CertificateGeneratorTest.java b/kroxylicious-kms-test-support/src/test/java/io/kroxylicious/proxy/tls/CertificateGeneratorTest.java
new file mode 100644
index 00000000..584cfafc
--- /dev/null
+++ b/kroxylicious-kms-test-support/src/test/java/io/kroxylicious/proxy/tls/CertificateGeneratorTest.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright Kroxylicious Authors.
+ *
+ * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+package io.kroxylicious.proxy.tls;
+
+import java.io.FileReader;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.Key;
+import java.security.KeyPair;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.RSAPrivateKey;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.openssl.PEMException;
+import org.bouncycastle.openssl.PEMKeyPair;
+import org.bouncycastle.openssl.PEMParser;
+import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
+import org.junit.jupiter.api.Test;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+class CertificateGeneratorTest {
+
+    @Test
+    void rsaKeyPair() {
+        KeyPair keyPair = CertificateGenerator.generateRsaKeyPair();
+        assertRsaKeyPairGenerated(keyPair);
+    }
+
+    @Test
+    void writeRsaPrivateKeyPem() throws IOException {
+        KeyPair keyPair = CertificateGenerator.generateRsaKeyPair();
+        Path path = CertificateGenerator.writeRsaPrivateKeyPem(keyPair);
+        assertPemAtPathContainsPrivateKey(path, keyPair);
+    }
+
+    @Test
+    void generateSelfSignedX509Certificate() {
+        KeyPair keyPair = CertificateGenerator.generateRsaKeyPair();
+        X509Certificate x509Certificate = CertificateGenerator.generateSelfSignedX509Certificate(keyPair);
+        assertSelfSignedCertGeneratedForKeyPair(x509Certificate, keyPair);
+    }
+
+    @Test
+    void generateCertPem() throws IOException {
+        KeyPair keyPair = CertificateGenerator.generateRsaKeyPair();
+        X509Certificate x509Certificate = CertificateGenerator.generateSelfSignedX509Certificate(keyPair);
+        Path path = CertificateGenerator.generateCertPem(x509Certificate);
+        Object pair = new PEMParser(new FileReader(path.toFile())).readObject();
+        assertThat(pair).isInstanceOfSatisfying(X509CertificateHolder.class, x509CertificateHolder -> {
+            X509Certificate certificate = convertToJcaX509Cert(x509CertificateHolder);
+            assertThat(certificate).isEqualTo(x509Certificate);
+        });
+    }
+
+    @Test
+    void createJksKeystore() throws Exception {
+        KeyPair keyPair = CertificateGenerator.generateRsaKeyPair();
+        X509Certificate x509Certificate = CertificateGenerator.generateSelfSignedX509Certificate(keyPair);
+        CertificateGenerator.KeyStore jksKeystore = CertificateGenerator.createJksKeystore(keyPair, x509Certificate, "storePazz", "keyPazz");
+        assertKeyStoreContains(jksKeystore, keyPair, x509Certificate, "keyPazz", "storePazz");
+    }
+
+    @Test
+    void generate() throws Exception {
+        CertificateGenerator.Keys keys = CertificateGenerator.generate();
+        assertRsaKeyPairGenerated(keys.serverKey());
+        assertPemAtPathContainsPrivateKey(keys.privateKeyPem(), keys.serverKey());
+
+        assertThat(keys.selfSignedCertificatePem()).isNotNull();
+        Object cert = new PEMParser(new FileReader(keys.selfSignedCertificatePem().toFile())).readObject();
+        assertThat(cert).isInstanceOf(X509CertificateHolder.class);
+        X509Certificate certificate = convertToJcaX509Cert((X509CertificateHolder) cert);
+        assertSelfSignedCertGeneratedForKeyPair(certificate, keys.serverKey());
+
+        assertKeyStoreContains(keys.jksServerKeystore(), keys.serverKey(), certificate, "keypass", "changeit");
+
+        assertTrustStore(keys.jksClientTruststore(), certificate, "changeit", "JKS");
+        assertTrustStore(keys.pkcs12ClientTruststore(), certificate, "changeit", "PKCS12");
+        assertTrustStore(keys.pkcs12NoPasswordClientTruststore(), certificate, null, "PKCS12");
+    }
+
+    private void assertTrustStore(CertificateGenerator.TrustStore trustStore, X509Certificate certificate, String password, String type) throws Exception {
+        assertThat(trustStore).isNotNull();
+        assertThat(trustStore.password()).isEqualTo(password);
+        if (password != null) {
+            assertThat(trustStore.passwordFile()).isNotNull();
+            assertThat(Files.readString(trustStore.passwordFile())).isEqualTo(password);
+        }
+        else {
+            assertThat(trustStore.passwordFile()).isNull();
+        }
+        KeyStore keyStore = loadKeyStore(trustStore.path(), password, type);
+        Certificate trustedCert = keyStore.getCertificate(CertificateGenerator.ALIAS);
+        assertThat(trustedCert).isNotNull().isEqualTo(certificate);
+    }
+
+    private static void assertRsaKeyPairGenerated(KeyPair keyPair) {
+        assertThat(keyPair).isNotNull();
+        PublicKey publicKey = keyPair.getPublic();
+        assertThat(publicKey.getAlgorithm()).isEqualTo("RSA");
+        assertThat(publicKey.getFormat()).isEqualTo("X.509");
+        PrivateKey privateKey = keyPair.getPrivate();
+        assertThat(privateKey.getAlgorithm()).isEqualTo("RSA");
+        assertThat(privateKey.getFormat()).isEqualTo("PKCS#8");
+        assertThat(privateKey).isInstanceOfSatisfying(RSAPrivateKey.class, rsaPrivateKey -> {
+            assertThat(rsaPrivateKey.getModulus().bitLength()).isEqualTo(1024);
+        });
+    }
+
+    private static void assertPemAtPathContainsPrivateKey(Path path, KeyPair keyPair) throws IOException {
+        assertThat(path).isNotNull();
+        Object pair = new PEMParser(new FileReader(path.toFile())).readObject();
+        assertThat(pair).isInstanceOfSatisfying(PEMKeyPair.class, pemKeyPair -> {
+            KeyPair jcaKeyPair = convertToJcaKeyPair(pemKeyPair);
+            assertThat(jcaKeyPair.getPublic()).isEqualTo(keyPair.getPublic());
+            assertThat(jcaKeyPair.getPrivate()).isEqualTo(keyPair.getPrivate());
+        });
+    }
+
+    private static KeyPair convertToJcaKeyPair(PEMKeyPair pemKeyPair) {
+        try {
+            return new JcaPEMKeyConverter().getKeyPair(pemKeyPair);
+        }
+        catch (PEMException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static X509Certificate convertToJcaX509Cert(X509CertificateHolder x509CertificateHolder) {
+        try {
+            return new JcaX509CertificateConverter().getCertificate(x509CertificateHolder);
+        }
+        catch (CertificateException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static void assertSelfSignedCertGeneratedForKeyPair(X509Certificate x509Certificate, KeyPair keyPair) {
+        assertThat(x509Certificate).isNotNull();
+        assertThat(x509Certificate.getSigAlgName()).isEqualTo("SHA256withRSA");
+        assertThat(x509Certificate.getSubjectX500Principal().getName()).isEqualTo("CN=localhost");
+        assertThat(x509Certificate.getIssuerX500Principal().getName()).isEqualTo("CN=localhost");
+        assertThat(x509Certificate.getSerialNumber()).isEqualTo(BigInteger.valueOf(1));
+        assertThat(x509Certificate.getNotBefore().toInstant()).isBefore(Instant.now());
+        Instant approxAfter = Instant.now().plus(9999, ChronoUnit.DAYS).minus(5, ChronoUnit.SECONDS);
+        assertThat(x509Certificate.getNotAfter().toInstant()).isAfter(approxAfter);
+        assertThat(x509Certificate.getPublicKey().getEncoded()).isEqualTo(keyPair.getPublic().getEncoded());
+
+        // Verifies that this certificate was signed using the private key that corresponds to the specified public key.
+        assertThatCode(() -> x509Certificate.verify(keyPair.getPublic())).doesNotThrowAnyException();
+    }
+
+    private static void assertKeyStoreContains(CertificateGenerator.KeyStore jksKeystore, KeyPair keyPair, X509Certificate x509Certificate, String keyPassword,
+                                               String storePassword)
+            throws Exception {
+        assertThat(jksKeystore).isNotNull();
+        assertThat(jksKeystore.keyPassword()).isNotNull().isEqualTo(keyPassword);
+        assertThat(jksKeystore.keyPasswordFile()).isNotNull();
+        assertThat(Files.readString(jksKeystore.keyPasswordFile())).isEqualTo(keyPassword);
+        assertThat(jksKeystore.storePassword()).isNotNull().isEqualTo(storePassword);
+        assertThat(jksKeystore.storePasswordFile()).isNotNull();
+        assertThat(Files.readString(jksKeystore.storePasswordFile())).isEqualTo(storePassword);
+        assertThat(jksKeystore.path()).isNotNull();
+        assertThat(jksKeystore.type()).isEqualTo("JKS");
+        KeyStore store = loadKeyStore(jksKeystore.path(), jksKeystore.storePassword(), "JKS");
+        Key key = store.getKey(CertificateGenerator.ALIAS, jksKeystore.keyPassword().toCharArray());
+        assertThat(key).isEqualTo(keyPair.getPrivate());
+        Certificate certificate = store.getCertificate(CertificateGenerator.ALIAS);
+        assertThat(certificate).isEqualTo(x509Certificate);
+    }
+
+    private static @NonNull KeyStore loadKeyStore(Path path, String password, String type) {
+        try {
+            KeyStore store = KeyStore.getInstance(type);
+            store.load(Files.newInputStream(path), password == null ? null : password.toCharArray());
+            return store;
+        }
+        catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+}
diff --git a/kroxylicious-kms-provider-hashicorp-vault/src/test/java/io/kroxylicious/kms/provider/hashicorp/vault/config/JdkTlsTest.java b/kroxylicious-kms-tls-support/src/test/java/io/kroxylicious/proxy/tls/JdkTlsTest.java
similarity index 94%
rename from kroxylicious-kms-provider-hashicorp-vault/src/test/java/io/kroxylicious/kms/provider/hashicorp/vault/config/JdkTlsTest.java
rename to kroxylicious-kms-tls-support/src/test/java/io/kroxylicious/proxy/tls/JdkTlsTest.java
index 2f08a319..8279a501 100644
--- a/kroxylicious-kms-provider-hashicorp-vault/src/test/java/io/kroxylicious/kms/provider/hashicorp/vault/config/JdkTlsTest.java
+++ b/kroxylicious-kms-tls-support/src/test/java/io/kroxylicious/proxy/tls/JdkTlsTest.java
@@ -4,7 +4,7 @@
  * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
  */
 
-package io.kroxylicious.kms.provider.hashicorp.vault.config;
+package io.kroxylicious.proxy.tls;
 
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -20,7 +20,6 @@ import javax.net.ssl.X509TrustManager;
 
 import org.junit.jupiter.api.Test;
 
-import io.kroxylicious.kms.provider.hashicorp.vault.CertificateGenerator;
 import io.kroxylicious.proxy.config.secret.InlinePassword;
 import io.kroxylicious.proxy.config.tls.InsecureTls;
 import io.kroxylicious.proxy.config.tls.KeyPair;
@@ -28,9 +27,9 @@ import io.kroxylicious.proxy.config.tls.KeyStore;
 import io.kroxylicious.proxy.config.tls.Tls;
 import io.kroxylicious.proxy.config.tls.TrustStore;
 
-import static io.kroxylicious.kms.provider.hashicorp.vault.CertificateGenerator.createJksKeystore;
-import static io.kroxylicious.kms.provider.hashicorp.vault.CertificateGenerator.generateRsaKeyPair;
-import static io.kroxylicious.kms.provider.hashicorp.vault.CertificateGenerator.generateSelfSignedX509Certificate;
+import static io.kroxylicious.proxy.tls.CertificateGenerator.createJksKeystore;
+import static io.kroxylicious.proxy.tls.CertificateGenerator.generateRsaKeyPair;
+import static io.kroxylicious.proxy.tls.CertificateGenerator.generateSelfSignedX509Certificate;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatCode;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -164,7 +163,7 @@ class JdkTlsTest {
         KeyStore store = new KeyStore("/tmp/pem", null, null, "PEM");
         assertThatThrownBy(() -> {
             JdkTls.getKeyManagers(store);
-        }).isInstanceOf(SslConfigurationException.class).hasMessageContaining("PEM is not supported by vault KMS yet");
+        }).isInstanceOf(SslConfigurationException.class).hasMessageContaining("PEM is not supported by this client");
     }
 
     @Test