From e9b5e1609f4f5db5175e67bf72871da5533883e7 Mon Sep 17 00:00:00 2001 From: Kaveh Shamsi Date: Fri, 22 Mar 2024 15:51:17 +0100 Subject: [PATCH] Supports mTLS for config-server clients (#109) * Supports mTLS for config-server clients * Common KeyStore codecleanup and refactoring Signed-off-by: kvmw --------- Signed-off-by: kvmw --- README.md | 21 ++- ...nfigServerBindingsPropertiesProcessor.java | 38 ++++- .../EurekaBindingsPropertiesProcessor.java | 50 +----- .../bindings/boot/pem/PemSslStoreHelper.java | 64 ++++++-- ...ServerBindingsPropertiesProcessorTest.java | 146 +++++++++++++++++- .../boot/pem/PemSslStoreHelperTests.java | 28 ++-- 6 files changed, 272 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index 4f1485e..14ac847 100644 --- a/README.md +++ b/README.md @@ -273,12 +273,21 @@ Disable Property: `org.springframework.cloud.bindings.boot.hana.enable` Type: `config` Disable Property: `org.springframework.cloud.bindings.boot.config.enable` -| Property | Value | -| -------------------------------------------------- | -------------------- | -| `spring.cloud.config.uri` | `{uri}` | -| `spring.cloud.config.client.oauth2.clientId` | `{client-id}` | -| `spring.cloud.config.client.oauth2.clientSecret` | `{client-secret}` | -| `spring.cloud.config.client.oauth2.accessTokenUri` | `{access-token-uri}` | +| Property | Value | +|------------------------------------------------------|--------------------------------------------------------| +| `spring.cloud.config.uri` | `{uri}` | +| `spring.cloud.config.client.oauth2.clientId` | `{client-id}` | +| `spring.cloud.config.client.oauth2.clientSecret` | `{client-secret}` | +| `spring.cloud.config.client.oauth2.accessTokenUri` | `{access-token-uri}` | +| `spring.cloud.config.tls.enabled` | `true` when `{tls.crt}` and `{tls.key}` are set | +| `spring.cloud.config.tls.key-store` | derived from `{tls.crt}` and `{tls.key}` | +| `spring.cloud.config.tls.key-store-type` | `"PKCS12"` when `{tls.crt}` and `{tls.key}` are set | +| `spring.cloud.config.tls.key-store-password` | random string when `{tls.crt}` and `{tls.key}` are set | +| `spring.cloud.config.tls.key-alias` | `"config"` when `{tls.crt}` and `{tls.key}` are set | +| `spring.cloud.config.tls.key-password` | `""` when `{tls.crt}` and `{tls.key}` are set | +| `spring.cloud.config.tls.trust-store` | derived from `{ca.crt}` when it is set | +| `spring.cloud.config.tls.trust-store-type` | `"PKCS12"` when `{ca.crt}` is set | +| `spring.cloud.config.tls.trust-store-password` | random string when `{ca.crt}` is set | ## SCS Eureka diff --git a/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/ConfigServerBindingsPropertiesProcessor.java b/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/ConfigServerBindingsPropertiesProcessor.java index 2374aab..b9b5079 100644 --- a/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/ConfigServerBindingsPropertiesProcessor.java +++ b/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/ConfigServerBindingsPropertiesProcessor.java @@ -18,10 +18,14 @@ package org.springframework.cloud.bindings.boot; import org.springframework.cloud.bindings.Binding; import org.springframework.cloud.bindings.Bindings; +import org.springframework.cloud.bindings.boot.pem.PemSslStoreHelper; import org.springframework.core.env.Environment; +import org.springframework.util.StringUtils; +import java.nio.file.Path; import java.util.Map; + import static org.springframework.cloud.bindings.boot.Guards.isTypeEnabled; /** @@ -40,12 +44,42 @@ final class ConfigServerBindingsPropertiesProcessor implements BindingsPropertie } bindings.filterBindings(TYPE).forEach(binding -> { - MapMapper map = new MapMapper(binding.getSecret(), properties); + Map secret = binding.getSecret(); + MapMapper map = new MapMapper(secret, properties); map.from("uri").to("spring.cloud.config.uri"); map.from("client-id").to("spring.cloud.config.client.oauth2.clientId"); map.from("client-secret").to("spring.cloud.config.client.oauth2.clientSecret"); map.from("access-token-uri").to("spring.cloud.config.client.oauth2.accessTokenUri"); - }); + // When tls.crt and tls.key are set, enable mTLS for config client. + String clientKey = secret.get("tls.key"); + String clientCert = secret.get("tls.crt"); + if (StringUtils.hasText(clientCert) != StringUtils.hasText(clientKey)) { + throw new IllegalArgumentException("binding secret error: tls.key and tls.crt must both be set if either is set"); + } + + if (clientKey != null && !clientKey.isEmpty()) { + String generatedPassword = PemSslStoreHelper.generatePassword(); + + // Create a keystore + Path keyFilePath = PemSslStoreHelper.createKeyStoreFile("config-keystore", generatedPassword, clientCert, clientKey, "config"); + + properties.put("spring.cloud.config.tls.enabled", true); + properties.put("spring.cloud.config.tls.key-alias", "config"); + properties.put("spring.cloud.config.tls.key-store", "file:" + keyFilePath); + properties.put("spring.cloud.config.tls.key-store-type", PemSslStoreHelper.PKCS12_STORY_TYPE); + properties.put("spring.cloud.config.tls.key-store-password", generatedPassword); + properties.put("spring.cloud.config.tls.key-password", ""); + + String caCert = secret.get("ca.crt"); + if (caCert != null && !caCert.isEmpty()) { + // Create a truststore from the CA cert + Path trustFilePath = PemSslStoreHelper.createKeyStoreFile("config-truststore", generatedPassword, caCert, null, "ca"); + properties.put("spring.cloud.config.tls.trust-store", "file:" + trustFilePath); + properties.put("spring.cloud.config.tls.trust-store-type", PemSslStoreHelper.PKCS12_STORY_TYPE); + properties.put("spring.cloud.config.tls.trust-store-password", generatedPassword); + } + } + }); } } diff --git a/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/EurekaBindingsPropertiesProcessor.java b/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/EurekaBindingsPropertiesProcessor.java index 5c45e88..92e15ab 100644 --- a/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/EurekaBindingsPropertiesProcessor.java +++ b/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/EurekaBindingsPropertiesProcessor.java @@ -22,12 +22,8 @@ import org.springframework.core.env.Environment; import org.springframework.cloud.bindings.boot.pem.PemSslStoreHelper; import org.springframework.util.StringUtils; -import java.io.*; -import java.nio.file.Paths; -import java.security.*; -import java.security.cert.CertificateException; +import java.nio.file.Path; import java.util.Map; -import java.util.Random; import static org.springframework.cloud.bindings.boot.Guards.isTypeEnabled; @@ -66,19 +62,14 @@ final class EurekaBindingsPropertiesProcessor implements BindingsPropertiesProce properties.put("eureka.instance.preferIpAddress", true); } - Random random = new Random(); - String generatedPassword = random.ints(97 /* letter a */, 122 /* letter z */ + 1) - .limit(10) - .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) - .toString(); + String generatedPassword = PemSslStoreHelper.generatePassword(); // Create a trust store from the CA cert - String trustFilePath = Paths.get(System.getProperty("java.io.tmpdir"), "client-truststore.p12").toString(); - KeyStore trustStore = PemSslStoreHelper.createKeyStore("trust", "PKCS12", caCert, null, "rootca"); - createStoreFile("truststore", generatedPassword, trustFilePath, trustStore); + Path trustFilePath = PemSslStoreHelper.createKeyStoreFile("eureka-truststore", generatedPassword, caCert, null, "rootca"); + properties.put("eureka.client.tls.enabled", true); properties.put("eureka.client.tls.trust-store", "file:"+trustFilePath); - properties.put("eureka.client.tls.trust-store-type", "PKCS12"); + properties.put("eureka.client.tls.trust-store-type", PemSslStoreHelper.PKCS12_STORY_TYPE); properties.put("eureka.client.tls.trust-store-password", generatedPassword); // When tls.crt and tls.key are set, enable mTLS for Eureka @@ -90,41 +81,14 @@ final class EurekaBindingsPropertiesProcessor implements BindingsPropertiesProce if (clientKey != null && !clientKey.isEmpty()) { // Create a keystore - String keyFilePath = Paths.get(System.getProperty("java.io.tmpdir"), "client-keystore.p12").toString(); - KeyStore keyStore = PemSslStoreHelper.createKeyStore("key", "PKCS12", clientCert, clientKey, "eureka"); - createStoreFile("keystore", generatedPassword, keyFilePath, keyStore); + Path keyFilePath = PemSslStoreHelper.createKeyStoreFile("eureka-keystore", generatedPassword, clientCert, clientKey, "eureka"); properties.put("eureka.client.tls.key-alias", "eureka"); properties.put("eureka.client.tls.key-store", "file:" + keyFilePath); - properties.put("eureka.client.tls.key-store-type", "PKCS12"); + properties.put("eureka.client.tls.key-store-type", PemSslStoreHelper.PKCS12_STORY_TYPE); properties.put("eureka.client.tls.key-store-password", generatedPassword); properties.put("eureka.client.tls.key-password", ""); } } }); } - - private static void createStoreFile(String storeType, String generatedPassword, String filePath, KeyStore ks) { - try { - FileOutputStream fos = new FileOutputStream(filePath); - try { - ks.store(fos, generatedPassword.toCharArray()); - } catch (KeyStoreException e) { - throw new IllegalStateException("Unable to write " + storeType, e); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("Cryptographic algorithm not available", e); - } catch (CertificateException e) { - throw new IllegalStateException("Unable to process certificate", e); - } catch (IOException e) { - throw new IllegalStateException("Unable to create " + storeType, e); - } finally { - try { - fos.close(); - } catch (IOException e) { - throw new IllegalStateException("Unable to close " + storeType + " output file", e); - } - } - } catch (FileNotFoundException e) { - throw new IllegalStateException("Unable to open " + storeType + " output file", e); - } - } } diff --git a/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/pem/PemSslStoreHelper.java b/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/pem/PemSslStoreHelper.java index 2062325..e2a434c 100644 --- a/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/pem/PemSslStoreHelper.java +++ b/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/pem/PemSslStoreHelper.java @@ -16,34 +16,76 @@ package org.springframework.cloud.bindings.boot.pem; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.security.KeyStore; import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; +import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import java.util.Random; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; /** * helper for creating stores from PEM-encoded certificates and private keys. */ public class PemSslStoreHelper { + public static final String PKCS12_STORY_TYPE = "PKCS12"; private static final String DEFAULT_KEY_ALIAS = "ssl"; /** - * Utility method to create a KeyStore - * @param name the name of the keystore - * @param storeType the type of the keystore (JKS, PKCS12, etc.) - * @param certificate a certificate as string that will be added to the keystore - * @param privateKey the keystore private key as string + * Utility method to create a KeyStore and save it in the tmp directory with give name. + * @param name the store file name + * @param password the store password + * @param certificate the certificate to add to the store + * @param privateKey the private key to add to the store * @param keyAlias the alias - * @return the keystore + * @return the path which store file is saved */ - public static KeyStore createKeyStore(String name, String storeType, String certificate, String privateKey, String keyAlias) { + public static Path createKeyStoreFile(String name, String password, String certificate, String privateKey, String keyAlias) { + KeyStore store = createKeyStore(certificate, privateKey, keyAlias); + + Path path; + try { + path = Files.createTempFile(Paths.get(System.getProperty("java.io.tmpdir")), name, ".p12"); + } catch (IOException e) { + throw new IllegalStateException("Unable to create " + name, e); + } + + try (FileOutputStream fos = new FileOutputStream(path.toString())) { + store.store(fos, password.toCharArray()); + } catch (KeyStoreException e) { + throw new IllegalStateException("Unable to write " + name, e); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("Cryptographic algorithm not available", e); + } catch (CertificateException e) { + throw new IllegalStateException("Unable to process certificate", e); + } catch (IOException e) { + throw new IllegalStateException("Unable to create " + name, e); + } + return path; + } + + /** + * Generates a password to use for KeyStore and/or TrustStore + * @return the password + */ + public static String generatePassword() { + return new Random().ints(97 /* letter a */, 122 /* letter z */ + 1) + .limit(10) + .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) + .toString(); + } + + private static KeyStore createKeyStore(String certificate, String privateKey, String keyAlias) { try { Assert.notNull(certificate, "CertificateContent must not be null"); - String type = StringUtils.hasText(storeType) ? storeType : KeyStore.getDefaultType(); - KeyStore store = KeyStore.getInstance(type); + KeyStore store = KeyStore.getInstance(PKCS12_STORY_TYPE); store.load(null); String certificateContent = PemContent.load(certificate); String privateKeyContent = PemContent.load(privateKey); @@ -53,7 +95,7 @@ public class PemSslStoreHelper { return store; } catch (Exception ex) { - throw new IllegalStateException(String.format("Unable to create %s store: %s", name, ex.getMessage()), ex); + throw new IllegalStateException(String.format("Unable to create key/trust store: %s", ex.getMessage()), ex); } } diff --git a/spring-cloud-bindings/src/test/java/org/springframework/cloud/bindings/boot/ConfigServerBindingsPropertiesProcessorTest.java b/spring-cloud-bindings/src/test/java/org/springframework/cloud/bindings/boot/ConfigServerBindingsPropertiesProcessorTest.java index 96b6c81..ee9b12d 100644 --- a/spring-cloud-bindings/src/test/java/org/springframework/cloud/bindings/boot/ConfigServerBindingsPropertiesProcessorTest.java +++ b/spring-cloud-bindings/src/test/java/org/springframework/cloud/bindings/boot/ConfigServerBindingsPropertiesProcessorTest.java @@ -16,23 +16,28 @@ package org.springframework.cloud.bindings.boot; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.cloud.bindings.Binding; import org.springframework.cloud.bindings.Bindings; import org.springframework.cloud.bindings.FluentMap; +import org.springframework.core.io.ClassPathResource; import org.springframework.mock.env.MockEnvironment; +import java.io.File; import java.nio.file.Paths; import java.util.HashMap; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.springframework.cloud.bindings.boot.ConfigServerBindingsPropertiesProcessor.TYPE; @DisplayName("Config Server BindingsPropertiesProcessor") final class ConfigServerBindingsPropertiesProcessorTest { - private final Bindings bindings = new Bindings( + private Bindings bindings = new Bindings( new Binding("test-name", Paths.get("test-path"), new FluentMap() .withEntry(Binding.TYPE, TYPE) @@ -47,9 +52,20 @@ final class ConfigServerBindingsPropertiesProcessorTest { private final HashMap properties = new HashMap<>(); + private String cert; + private String key; + + @BeforeEach + void fetchCerts() { + assertDoesNotThrow(() -> { + this.cert = TestHelper.resourceAsString(new ClassPathResource("pem/test-cert.pem")); + this.key = TestHelper.resourceAsString(new ClassPathResource("pem/test-key.pem")); + }); + } + @Test @DisplayName("contributes properties") - void test() { + void whenEnabled() { new ConfigServerBindingsPropertiesProcessor().process(environment, bindings, properties); assertThat(properties) .containsEntry("spring.cloud.config.uri", "test-uri") @@ -60,7 +76,7 @@ final class ConfigServerBindingsPropertiesProcessorTest { @Test @DisplayName("can be disabled") - void disabled() { + void whenDisabled() { environment.setProperty("org.springframework.cloud.bindings.boot.config.enable", "false"); new ConfigServerBindingsPropertiesProcessor().process(environment, bindings, properties); @@ -68,4 +84,128 @@ final class ConfigServerBindingsPropertiesProcessorTest { assertThat(properties).isEmpty(); } + @Test + @DisplayName("contributes tls key-store properties when set") + void whenKeystoreValuesSet() { + bindings = new Bindings( + new Binding("test-name", Paths.get("test-path"), + new FluentMap() + .withEntry(Binding.TYPE, ConfigServerBindingsPropertiesProcessor.TYPE) + .withEntry("tls.key", key) + .withEntry("tls.crt", cert) + ) + ); + + new ConfigServerBindingsPropertiesProcessor().process(environment, bindings, properties); + + assertThat(properties) + .containsEntry("spring.cloud.config.tls.enabled", true) + .containsEntry("spring.cloud.config.tls.key-store-type", "PKCS12") + .containsEntry("spring.cloud.config.tls.key-alias", "config") + .containsKey("spring.cloud.config.tls.key-store") + .containsKey("spring.cloud.config.tls.key-store-password") + .containsKey("spring.cloud.config.tls.key-password") + .doesNotContainKey("spring.cloud.config.tls.trust-store") + .doesNotContainKey("spring.cloud.config.tls.trust-store-type") + .doesNotContainKey("spring.cloud.config.tls.trust-store-password"); + + String path = properties.get("spring.cloud.config.tls.key-store").toString().substring(5); + File f = new File(path); + assertThat(f.exists()).isTrue(); + assertThat(f.isFile()).isTrue(); + } + + @Test + @DisplayName("contributes tls trust-store properties when set") + void whenTruststoreValuesSet() { + bindings = new Bindings( + new Binding("test-name", Paths.get("test-path"), + new FluentMap() + .withEntry(Binding.TYPE, ConfigServerBindingsPropertiesProcessor.TYPE) + .withEntry("tls.key", key) + .withEntry("tls.crt", cert) + .withEntry("ca.crt", cert) + ) + ); + + new ConfigServerBindingsPropertiesProcessor().process(environment, bindings, properties); + + assertThat(properties) + .containsEntry("spring.cloud.config.tls.enabled", true) + .containsEntry("spring.cloud.config.tls.trust-store-type", "PKCS12") + .containsKey("spring.cloud.config.tls.trust-store") + .containsKey("spring.cloud.config.tls.trust-store-password"); + + String path = properties.get("spring.cloud.config.tls.trust-store").toString().substring(5); + File f = new File(path); + assertThat(f.exists()).isTrue(); + assertThat(f.isFile()).isTrue(); + } + + @Test + @DisplayName("throws when bad tls key-store values are set") + void whenKeystoreValueIsNotValid() { + bindings = new Bindings( + new Binding("test-name", Paths.get("test-path"), + new FluentMap() + .withEntry(Binding.TYPE, ConfigServerBindingsPropertiesProcessor.TYPE) + .withEntry("tls.key", key) + .withEntry("tls.crt", "this isn't a valid certificate") + ) + ); + + assertThrows(IllegalStateException.class, () -> { + new ConfigServerBindingsPropertiesProcessor().process(environment, bindings, properties); + }); + } + + @Test + @DisplayName("throws when bad tls trust-store values are set") + void whenTruststoreValueIsNotValid() { + bindings = new Bindings( + new Binding("test-name", Paths.get("test-path"), + new FluentMap() + .withEntry(Binding.TYPE, ConfigServerBindingsPropertiesProcessor.TYPE) + .withEntry("tls.key", key) + .withEntry("tls.crt", cert) + .withEntry("ca.crt", "this isn't a valid certificate") + ) + ); + + assertThrows(IllegalStateException.class, () -> { + new ConfigServerBindingsPropertiesProcessor().process(environment, bindings, properties); + }); + } + + @Test + @DisplayName("throws when tls.crt is set but tls.key isn't") + void whenCertificateSetWithoutPrivateKey() { + bindings = new Bindings( + new Binding("test-name", Paths.get("test-path"), + new FluentMap() + .withEntry(Binding.TYPE, ConfigServerBindingsPropertiesProcessor.TYPE) + .withEntry("tls.crt", cert) + ) + ); + + assertThrows(IllegalArgumentException.class, () -> { + new ConfigServerBindingsPropertiesProcessor().process(environment, bindings, properties); + }); + } + + @Test + @DisplayName("throws when tls.key is set but tls.crt isn't") + void whenPrivateKeySetWithoutCertificate() { + bindings = new Bindings( + new Binding("test-name", Paths.get("test-path"), + new FluentMap() + .withEntry(Binding.TYPE, ConfigServerBindingsPropertiesProcessor.TYPE) + .withEntry("tls.key", key) + ) + ); + + assertThrows(IllegalArgumentException.class, () -> { + new ConfigServerBindingsPropertiesProcessor().process(environment, bindings, properties); + }); + } } diff --git a/spring-cloud-bindings/src/test/java/org/springframework/cloud/bindings/boot/pem/PemSslStoreHelperTests.java b/spring-cloud-bindings/src/test/java/org/springframework/cloud/bindings/boot/pem/PemSslStoreHelperTests.java index a95ac64..8995280 100644 --- a/spring-cloud-bindings/src/test/java/org/springframework/cloud/bindings/boot/pem/PemSslStoreHelperTests.java +++ b/spring-cloud-bindings/src/test/java/org/springframework/cloud/bindings/boot/pem/PemSslStoreHelperTests.java @@ -16,10 +16,8 @@ package org.springframework.cloud.bindings.boot.pem; +import java.nio.file.Path; import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableKeyException; import org.junit.jupiter.api.Test; @@ -33,15 +31,16 @@ import static org.junit.jupiter.api.Assertions.assertThrows; */ class PemSslStoreHelperTests { @Test - void whenNullValues() { + void createKeyStoreFileWhenNullValues() { assertThrows(java.lang.IllegalStateException.class, () -> { - PemSslStoreHelper.createKeyStore("key", "PKCS12", null, null, "some-alias"); + PemSslStoreHelper.createKeyStoreFile("key", "secret",null, null, "some-alias"); }); } @Test - void whenHasKeyStoreDetailsCertAndKey() { - KeyStore keyStore = PemSslStoreHelper.createKeyStore("key", "PKCS12", "classpath:pem/test-cert.pem", "classpath:pem/test-key.pem", "some-alias"); + void createKeyStoreFileWhenHasKeyStoreDetailsCertAndKey() throws Exception { + Path path = PemSslStoreHelper.createKeyStoreFile("key", "secret", "classpath:pem/test-cert.pem", "classpath:pem/test-key.pem", "some-alias"); + KeyStore keyStore = KeyStore.getInstance(path.toFile(), "secret".toCharArray()); assertDoesNotThrow(() -> { assertThat(keyStore).isNotNull(); assertThat(keyStore.getType()).isEqualTo("PKCS12"); @@ -52,9 +51,10 @@ class PemSslStoreHelperTests { } @Test - void whenHasTrustStoreDetailsWithoutKey() throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException { - KeyStore keyStore = PemSslStoreHelper.createKeyStore("trust", "PKCS12", "classpath:pem/test-cert.pem", null, null); - assertDoesNotThrow(() -> { + void createKeyStoreFileWhenHasTrustStoreDetailsWithoutKey() throws Exception { + Path path = PemSslStoreHelper.createKeyStoreFile("trust", "secret", "classpath:pem/test-cert.pem", null, null); + KeyStore keyStore = KeyStore.getInstance(path.toFile(), "secret".toCharArray()); + assertDoesNotThrow(() -> { assertThat(keyStore).isNotNull(); assertThat(keyStore.getType()).isEqualTo("PKCS12"); assertThat(keyStore.containsAlias("ssl-0")).isTrue(); @@ -62,4 +62,12 @@ class PemSslStoreHelperTests { assertThat(keyStore.getKey("ssl-0", new char[]{})).isNull(); }); } + + @Test + void generatePassword() { + String s = PemSslStoreHelper.generatePassword(); + assertThat(s).isNotNull(); + assertThat(s.length()).isEqualTo(10); + System.out.println(s); + } }