diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 45f33f3b60228..33c3170f2cc6f 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -667,6 +667,17 @@ ${project.version} + + io.quarkus + quarkus-tls-registry + ${project.version} + + + io.quarkus + quarkus-tls-registry-deployment + ${project.version} + + diff --git a/extensions/pom.xml b/extensions/pom.xml index cbd5443006e62..c718dc0574018 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -14,8 +14,9 @@ Quarkus - Extensions - Parent pom pom - virtual-threads + tls-registry + arc scheduler diff --git a/extensions/tls-registry/deployment/pom.xml b/extensions/tls-registry/deployment/pom.xml new file mode 100644 index 0000000000000..ec95c9987b432 --- /dev/null +++ b/extensions/tls-registry/deployment/pom.xml @@ -0,0 +1,89 @@ + + + + quarkus-tls-registry-parent + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + quarkus-tls-registry-deployment + Quarkus - TLS Registry - Deployment + + + + + io.quarkus + quarkus-vertx-deployment + + + io.quarkus + quarkus-arc-deployment + + + io.quarkus + quarkus-credentials-deployment + + + io.quarkus + quarkus-tls-registry + + + + + io.quarkus + quarkus-junit5-internal + test + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + org.awaitility + awaitility + test + + + org.assertj + assertj-core + test + + + io.vertx + vertx-web-client + test + + + + me.escoffier.certs + certificate-generator-junit5 + test + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/tls-registry/deployment/src/main/java/io/quarkus/tls/CertificatesProcessor.java b/extensions/tls-registry/deployment/src/main/java/io/quarkus/tls/CertificatesProcessor.java new file mode 100644 index 0000000000000..2708c1bfa544b --- /dev/null +++ b/extensions/tls-registry/deployment/src/main/java/io/quarkus/tls/CertificatesProcessor.java @@ -0,0 +1,39 @@ +package io.quarkus.tls; + +import java.util.List; + +import jakarta.enterprise.context.ApplicationScoped; + +import io.quarkus.arc.deployment.SyntheticBeanBuildItem; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.tls.runtime.CertificateRecorder; +import io.quarkus.tls.runtime.config.TlsConfig; +import io.quarkus.vertx.deployment.VertxBuildItem; + +public class CertificatesProcessor { + + @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) + public void initializeCertificate( + TlsConfig config, VertxBuildItem vertx, CertificateRecorder recorder, + BuildProducer syntheticBeans, + List otherCertificates) { + recorder.validateCertificates(config, vertx.getVertx()); + for (TlsCertificateBuildItem certificate : otherCertificates) { + recorder.register(certificate.name, certificate.supplier); + } + + SyntheticBeanBuildItem.ExtendedBeanConfigurator configurator = SyntheticBeanBuildItem + .configure(Registry.class) + .supplier(recorder.getSupplier()) + .scope(ApplicationScoped.class) + .unremovable() + .setRuntimeInit(); + + syntheticBeans.produce(configurator.done()); + } + +} diff --git a/extensions/tls-registry/deployment/src/main/java/io/quarkus/tls/TlsCertificateBuildItem.java b/extensions/tls-registry/deployment/src/main/java/io/quarkus/tls/TlsCertificateBuildItem.java new file mode 100644 index 0000000000000..ac7a9e6c2b1bd --- /dev/null +++ b/extensions/tls-registry/deployment/src/main/java/io/quarkus/tls/TlsCertificateBuildItem.java @@ -0,0 +1,28 @@ +package io.quarkus.tls; + +import java.util.function.Supplier; + +import io.quarkus.builder.item.MultiBuildItem; + +/** + * A build item to register a TLS certificate. + */ +public final class TlsCertificateBuildItem extends MultiBuildItem { + + public final String name; + + public final Supplier supplier; + + /** + * Create an instance of {@link TlsCertificateBuildItem} to register a TLS certificate. + * The certificate will be registered just after the regular TLS certificate configuration is registered. + * + * @param name the name of the certificate, cannot be {@code null}, cannot be {@code } + * @param supplier the supplier providing the TLS configuration, must not return {@code null} + */ + public TlsCertificateBuildItem(String name, Supplier supplier) { + this.name = name; + this.supplier = supplier; + } + +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/BuildTimeRegistrationTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/BuildTimeRegistrationTest.java new file mode 100644 index 0000000000000..3c32e3a192423 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/BuildTimeRegistrationTest.java @@ -0,0 +1,95 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStore; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.builder.BuildChainBuilder; +import io.quarkus.builder.BuildContext; +import io.quarkus.builder.BuildStep; +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-registration", password = "password", formats = Format.PKCS12) +}) +public class BuildTimeRegistrationTest { + + private static final String configuration = """ + # no configuration by default + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")) + .addBuildChainCustomizer(buildCustomizer());; + + @Inject + Registry registry; + + @Test + void testBuildTimeRegistration() { + TlsConfiguration conf = registry.get("named").orElseThrow(); + assertThat(conf.getKeyStore()).isNotNull(); + assertThat(conf.getTrustStore()).isNotNull(); + } + + static Consumer buildCustomizer() { + return new Consumer() { + @Override + public void accept(BuildChainBuilder builder) { + builder.addBuildStep(new BuildStep() { + @Override + public void execute(BuildContext context) { + TlsCertificateBuildItem item = new TlsCertificateBuildItem("named", new MyCertificateSupplier()); + context.produce(item); + } + }) + .produces(TlsCertificateBuildItem.class) + .build(); + } + }; + } + + public static class MyCertificateSupplier implements Supplier { + + @Override + public TlsConfiguration get() { + try { + KeyStore ks = KeyStore.getInstance("PKCS12"); + ks.load(getClass().getResourceAsStream("target/certs/test-registration-keystore.p12"), + "password".toCharArray()); + KeyStore ts = KeyStore.getInstance("PKCS12"); + ts.load(getClass().getResourceAsStream("target/certs/test-registration-truststore.p12"), + "password".toCharArray()); + return new BaseTlsConfiguration() { + @Override + public KeyStore getKeyStore() { + return ks; + } + + @Override + public KeyStore getTrustStore() { + return ts; + } + }; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/CertificateRevocationListTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/CertificateRevocationListTest.java new file mode 100644 index 0000000000000..f16e6f1f19925 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/CertificateRevocationListTest.java @@ -0,0 +1,38 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class CertificateRevocationListTest { + + private static final String configuration = """ + quarkus.tls.certificate-revocation-list=src/test/resources/revocations/revoked-cert.der + quarkus.tls.foo.certificate-revocation-list=src/test/resources/revocations/revoked-cert.der + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + TlsConfiguration foo = certificates.get("foo").orElseThrow(); + assertThat(def.getSSLOptions().getCrlValues()).hasSize(1); + assertThat(foo.getSSLOptions().getCrlValues()).hasSize(1); + } + +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultJKSKeyStoreAndTrustStoreTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultJKSKeyStoreAndTrustStoreTest.java new file mode 100644 index 0000000000000..8296e60b1f2e8 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultJKSKeyStoreAndTrustStoreTest.java @@ -0,0 +1,67 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class DefaultJKSKeyStoreAndTrustStoreTest { + + private static final String configuration = """ + quarkus.tls.key-store.jks.path=target/certs/test-formats-keystore.jks + quarkus.tls.key-store.jks.password=password + quarkus.tls.trust-store.jks.path=target/certs/test-formats-truststore.jks + quarkus.tls.trust-store.jks.password=password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + + TlsConfiguration def = certificates.getDefault().orElseThrow(); + + assertThat(def.getKeyStoreOptions()).isNotNull(); + assertThat(def.getKeyStore()).isNotNull(); + + assertThat(def.getTrustStore()).isNotNull(); + assertThat(def.getTrustStoreOptions()).isNotNull(); + + X509Certificate certificate = (X509Certificate) def.getKeyStore().getCertificate("test-formats"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("localhost"); + }); + + certificate = (X509Certificate) def.getTrustStore().getCertificate("test-formats"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("localhost"); + }); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultJKSKeyStoreTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultJKSKeyStoreTest.java new file mode 100644 index 0000000000000..bb1c7c0bce1cc --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultJKSKeyStoreTest.java @@ -0,0 +1,54 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class DefaultJKSKeyStoreTest { + + private static final String configuration = """ + quarkus.tls.key-store.jks.path=target/certs/test-formats-keystore.jks + quarkus.tls.key-store.jks.password=password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + + assertThat(def.getKeyStoreOptions()).isNotNull(); + assertThat(def.getKeyStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) def.getKeyStore().getCertificate("test-formats"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("localhost"); + }); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultJKSKeyStoreWithAliasTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultJKSKeyStoreWithAliasTest.java new file mode 100644 index 0000000000000..8b07ef603126f --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultJKSKeyStoreWithAliasTest.java @@ -0,0 +1,59 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-alias-jks", password = "password", formats = { Format.JKS }, aliases = { + @me.escoffier.certs.junit5.Alias(name = "alias1", password = "alias-password", subjectAlternativeNames = "dns:acme.org"), + @me.escoffier.certs.junit5.Alias(name = "alias2", password = "alias-password-2") + }) +}) +public class DefaultJKSKeyStoreWithAliasTest { + + private static final String configuration = """ + quarkus.tls.key-store.jks.path=target/certs/test-alias-jks-keystore.jks + quarkus.tls.key-store.jks.password=password + quarkus.tls.key-store.jks.alias=alias1 + quarkus.tls.key-store.jks.alias-password=alias-password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + + assertThat(def.getKeyStoreOptions()).isNotNull(); + assertThat(def.getKeyStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) def.getKeyStore().getCertificate("alias1"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("dns:acme.org"); + }); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultJKSTrustStoreTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultJKSTrustStoreTest.java new file mode 100644 index 0000000000000..3e437ff9f4fac --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultJKSTrustStoreTest.java @@ -0,0 +1,54 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class DefaultJKSTrustStoreTest { + + private static final String configuration = """ + quarkus.tls.trust-store.jks.path=target/certs/test-formats-truststore.jks + quarkus.tls.trust-store.jks.password=password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + + assertThat(def.getTrustStoreOptions()).isNotNull(); + assertThat(def.getTrustStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) def.getTrustStore().getCertificate("test-formats"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("localhost"); + }); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultJKSTrustStoreWithAliasTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultJKSTrustStoreWithAliasTest.java new file mode 100644 index 0000000000000..4b862e4c975e7 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultJKSTrustStoreWithAliasTest.java @@ -0,0 +1,58 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-alias-jks", password = "password", formats = { Format.JKS }, aliases = { + @me.escoffier.certs.junit5.Alias(name = "alias1", password = "alias-password", subjectAlternativeNames = "dns:acme.org"), + @me.escoffier.certs.junit5.Alias(name = "alias2", password = "alias-password-2") + }) +}) +public class DefaultJKSTrustStoreWithAliasTest { + + private static final String configuration = """ + quarkus.tls.trust-store.jks.path=target/certs/test-alias-jks-truststore.jks + quarkus.tls.trust-store.jks.password=password + quarkus.tls.trust-store.jks.alias=alias1 + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + + assertThat(def.getTrustStore()).isNotNull(); + assertThat(def.getTrustStoreOptions()).isNotNull(); + + X509Certificate certificate = (X509Certificate) def.getTrustStore().getCertificate("alias1"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("dns:acme.org"); + }); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultJKSTrustStoreWithCredentialsProviderTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultJKSTrustStoreWithCredentialsProviderTest.java new file mode 100644 index 0000000000000..91d3fdbe1b836 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultJKSTrustStoreWithCredentialsProviderTest.java @@ -0,0 +1,70 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.credentials.CredentialsProvider; +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-credentials-provider", password = "secret123!", formats = { Format.JKS, Format.PKCS12 }) +}) +public class DefaultJKSTrustStoreWithCredentialsProviderTest { + + private static final String configuration = """ + quarkus.tls.trust-store.jks.path=target/certs/test-credentials-provider-truststore.jks + quarkus.tls.trust-store.credentials-provider.name=tls + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClass(MyCredentialProvider.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + + assertThat(def.getTrustStoreOptions()).isNotNull(); + assertThat(def.getTrustStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) def.getTrustStore().getCertificate("test-credentials-provider"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("localhost"); + }); + } + + @ApplicationScoped + public static class MyCredentialProvider implements CredentialsProvider { + + private final Map> credentials = Map.of("tls", + Map.of(CredentialsProvider.PASSWORD_PROPERTY_NAME, "secret123!")); + + @Override + public Map getCredentials(String credentialsProviderName) { + return credentials.get(credentialsProviderName); + } + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultJKSTrustStoreWithCredentialsProviderWithAliasTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultJKSTrustStoreWithCredentialsProviderWithAliasTest.java new file mode 100644 index 0000000000000..16dff1fafe9ba --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultJKSTrustStoreWithCredentialsProviderWithAliasTest.java @@ -0,0 +1,73 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.credentials.CredentialsProvider; +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Alias; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-credentials-provider-alias", password = "secret123!", formats = { Format.JKS, + Format.PKCS12 }, aliases = @Alias(name = "my-alias", password = "alias-secret123!", subjectAlternativeNames = "dns:acme.org")) +}) +public class DefaultJKSTrustStoreWithCredentialsProviderWithAliasTest { + + private static final String configuration = """ + quarkus.tls.trust-store.jks.path=target/certs/test-credentials-provider-alias-truststore.jks + quarkus.tls.trust-store.jks.alias=my-alias + quarkus.tls.trust-store.credentials-provider.name=tls + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClass(MyCredentialProvider.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + + assertThat(def.getTrustStoreOptions()).isNotNull(); + assertThat(def.getTrustStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) def.getTrustStore().getCertificate("my-alias"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("dns:acme.org"); + }); + } + + @ApplicationScoped + public static class MyCredentialProvider implements CredentialsProvider { + + private final Map> credentials = Map.of("tls", + Map.of(CredentialsProvider.PASSWORD_PROPERTY_NAME, "secret123!")); + + @Override + public Map getCredentials(String credentialsProviderName) { + return credentials.get(credentialsProviderName); + } + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultJKSTrustStoreWithCredentialsProviderWithCustomKeyTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultJKSTrustStoreWithCredentialsProviderWithCustomKeyTest.java new file mode 100644 index 0000000000000..37939e4d08362 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultJKSTrustStoreWithCredentialsProviderWithCustomKeyTest.java @@ -0,0 +1,70 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.credentials.CredentialsProvider; +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-credentials-provider", password = "secret123!", formats = { Format.JKS, Format.PKCS12 }) +}) +public class DefaultJKSTrustStoreWithCredentialsProviderWithCustomKeyTest { + + private static final String configuration = """ + quarkus.tls.trust-store.jks.path=target/certs/test-credentials-provider-truststore.jks + quarkus.tls.trust-store.credentials-provider.name=tls + quarkus.tls.trust-store.credentials-provider.password-key=secret + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClass(MyCredentialProvider.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + + assertThat(def.getTrustStoreOptions()).isNotNull(); + assertThat(def.getTrustStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) def.getTrustStore().getCertificate("test-credentials-provider"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("localhost"); + }); + } + + @ApplicationScoped + public static class MyCredentialProvider implements CredentialsProvider { + + private final Map> credentials = Map.of("tls", Map.of("secret", "secret123!")); + + @Override + public Map getCredentials(String credentialsProviderName) { + return credentials.get(credentialsProviderName); + } + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultP12KeyStoreTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultP12KeyStoreTest.java new file mode 100644 index 0000000000000..efae5cff4fff3 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultP12KeyStoreTest.java @@ -0,0 +1,54 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class DefaultP12KeyStoreTest { + + private static final String configuration = """ + quarkus.tls.key-store.p12.path=target/certs/test-formats-keystore.p12 + quarkus.tls.key-store.p12.password=password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + + assertThat(def.getKeyStoreOptions()).isNotNull(); + assertThat(def.getKeyStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) def.getKeyStore().getCertificate("test-formats"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("localhost"); + }); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultP12KeyStoreWithAliasTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultP12KeyStoreWithAliasTest.java new file mode 100644 index 0000000000000..045df544b7974 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultP12KeyStoreWithAliasTest.java @@ -0,0 +1,58 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-alias-p12", password = "password", formats = { Format.PKCS12 }, aliases = { + @me.escoffier.certs.junit5.Alias(name = "alias1", password = "alias-password", subjectAlternativeNames = "dns:acme.org"), + @me.escoffier.certs.junit5.Alias(name = "alias2", password = "alias-password-2", subjectAlternativeNames = "dns:example.com") }) +}) +public class DefaultP12KeyStoreWithAliasTest { + + private static final String configuration = """ + quarkus.tls.key-store.p12.path=target/certs/test-alias-p12-keystore.p12 + quarkus.tls.key-store.p12.password=password + quarkus.tls.key-store.p12.alias=alias1 + quarkus.tls.key-store.p12.alias-password=alias-password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + + assertThat(def.getKeyStoreOptions()).isNotNull(); + assertThat(def.getKeyStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) def.getKeyStore().getCertificate("alias1"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("dns:acme.org"); + }); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultP12TrustStoreTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultP12TrustStoreTest.java new file mode 100644 index 0000000000000..403f60d8a02bc --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultP12TrustStoreTest.java @@ -0,0 +1,55 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class DefaultP12TrustStoreTest { + + private static final String configuration = """ + quarkus.tls.trust-store.p12.path=target/certs/test-formats-truststore.p12 + quarkus.tls.trust-store.p12.password=password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + + assertThat(def.getTrustStoreOptions()).isNotNull(); + assertThat(def.getTrustStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) def.getTrustStore() + .getCertificate("test-formats"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("localhost"); + }); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultP12TrustStoreWithAliasTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultP12TrustStoreWithAliasTest.java new file mode 100644 index 0000000000000..4719884b7b367 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultP12TrustStoreWithAliasTest.java @@ -0,0 +1,57 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-alias-p12", password = "password", formats = { Format.PKCS12 }, aliases = { + @me.escoffier.certs.junit5.Alias(name = "alias1", password = "alias-password", subjectAlternativeNames = "dns:acme.org"), + @me.escoffier.certs.junit5.Alias(name = "alias2", password = "alias-password-2", subjectAlternativeNames = "dns:example.com") }) +}) +public class DefaultP12TrustStoreWithAliasTest { + + private static final String configuration = """ + quarkus.tls.trust-store.p12.path=target/certs/test-alias-p12-truststore.p12 + quarkus.tls.trust-store.p12.password=password + quarkus.tls.trust-store.p12.alias=alias1 + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + + assertThat(def.getTrustStoreOptions()).isNotNull(); + assertThat(def.getTrustStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) def.getTrustStore().getCertificate("alias1"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("dns:acme.org"); + }); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultP12TrustStoreWithCredentialsProviderTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultP12TrustStoreWithCredentialsProviderTest.java new file mode 100644 index 0000000000000..dc212bd8a8b5b --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultP12TrustStoreWithCredentialsProviderTest.java @@ -0,0 +1,70 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.credentials.CredentialsProvider; +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-credentials-provider", password = "secret123!", formats = { Format.JKS, Format.PKCS12 }) +}) +public class DefaultP12TrustStoreWithCredentialsProviderTest { + + private static final String configuration = """ + quarkus.tls.trust-store.p12.path=target/certs/test-credentials-provider-truststore.p12 + quarkus.tls.trust-store.credentials-provider.name=tls + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClass(MyCredentialProvider.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + + assertThat(def.getTrustStoreOptions()).isNotNull(); + assertThat(def.getTrustStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) def.getTrustStore().getCertificate("test-credentials-provider"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("localhost"); + }); + } + + @ApplicationScoped + public static class MyCredentialProvider implements CredentialsProvider { + + private final Map> credentials = Map.of("tls", + Map.of(CredentialsProvider.PASSWORD_PROPERTY_NAME, "secret123!")); + + @Override + public Map getCredentials(String credentialsProviderName) { + return credentials.get(credentialsProviderName); + } + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultP12TrustStoreWithCredentialsProviderWithAliasTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultP12TrustStoreWithCredentialsProviderWithAliasTest.java new file mode 100644 index 0000000000000..2904d2c6aa51b --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultP12TrustStoreWithCredentialsProviderWithAliasTest.java @@ -0,0 +1,73 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.credentials.CredentialsProvider; +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Alias; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-credentials-provider-alias", password = "secret123!", formats = { Format.JKS, + Format.PKCS12 }, aliases = @Alias(name = "my-alias", password = "alias-secret123!", subjectAlternativeNames = "dns:acme.org")) +}) +public class DefaultP12TrustStoreWithCredentialsProviderWithAliasTest { + + private static final String configuration = """ + quarkus.tls.trust-store.p12.path=target/certs/test-credentials-provider-alias-truststore.p12 + quarkus.tls.trust-store.p12.alias=my-alias + quarkus.tls.trust-store.credentials-provider.name=tls + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClass(MyCredentialProvider.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + + assertThat(def.getTrustStoreOptions()).isNotNull(); + assertThat(def.getTrustStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) def.getTrustStore().getCertificate("my-alias"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("dns:acme.org"); + }); + } + + @ApplicationScoped + public static class MyCredentialProvider implements CredentialsProvider { + + private final Map> credentials = Map.of("tls", + Map.of(CredentialsProvider.PASSWORD_PROPERTY_NAME, "secret123!")); + + @Override + public Map getCredentials(String credentialsProviderName) { + return credentials.get(credentialsProviderName); + } + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultPemKeyStoreTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultPemKeyStoreTest.java new file mode 100644 index 0000000000000..93d87d861006a --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultPemKeyStoreTest.java @@ -0,0 +1,56 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class DefaultPemKeyStoreTest { + + private static final String configuration = """ + quarkus.tls.key-store.pem[0].cert=target/certs/test-formats.crt + quarkus.tls.key-store.pem[0].key=target/certs/test-formats.key + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + + assertThat(def.getKeyStoreOptions()).isNotNull(); + assertThat(def.getKeyStore()).isNotNull(); + + // dummy-entry-x is the alias of the certificate in the keystore generated by Vert.x. + + X509Certificate certificate = (X509Certificate) def.getKeyStore().getCertificate("dummy-entry-0"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("localhost"); + }); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultPemKeyStoreWithMultipleAliasesTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultPemKeyStoreWithMultipleAliasesTest.java new file mode 100644 index 0000000000000..b098bd6734005 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultPemKeyStoreWithMultipleAliasesTest.java @@ -0,0 +1,64 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-alias-pem", formats = { Format.PEM }, aliases = { + @me.escoffier.certs.junit5.Alias(name = "alias1", subjectAlternativeNames = "dns:acme.org"), + @me.escoffier.certs.junit5.Alias(name = "alias2") + }) +}) +public class DefaultPemKeyStoreWithMultipleAliasesTest { + + private static final String configuration = """ + quarkus.tls.key-store.pem[0].key=target/certs/test-alias-pem.key + quarkus.tls.key-store.pem[0].cert=target/certs/test-alias-pem.crt + quarkus.tls.key-store.pem[1].key=target/certs/alias1.key + quarkus.tls.key-store.pem[1].cert=target/certs/alias1.crt + quarkus.tls.key-store.pem[2].key=target/certs/alias2.key + quarkus.tls.key-store.pem[2].cert=target/certs/alias2.crt + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + + assertThat(def.getKeyStoreOptions()).isNotNull(); + assertThat(def.getKeyStore()).isNotNull(); + + List list = new ArrayList<>(); + Iterator iterator = def.getKeyStore().aliases().asIterator(); + while (iterator.hasNext()) { + list.add((X509Certificate) def.getKeyStore().getCertificate(iterator.next())); + } + assertThat(list).hasSize(3); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultPemTrustStoreTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultPemTrustStoreTest.java new file mode 100644 index 0000000000000..ded17b3adb866 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultPemTrustStoreTest.java @@ -0,0 +1,53 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class DefaultPemTrustStoreTest { + + private static final String configuration = """ + quarkus.tls.trust-store.pem.certs=target/certs/test-formats-ca.crt + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + + assertThat(def.getTrustStoreOptions()).isNotNull(); + assertThat(def.getTrustStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) def.getTrustStore().getCertificate("cert-0"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("localhost"); + }); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultPemTrustStoreWithMultipleAliasesTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultPemTrustStoreWithMultipleAliasesTest.java new file mode 100644 index 0000000000000..648e84b38016e --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultPemTrustStoreWithMultipleAliasesTest.java @@ -0,0 +1,59 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-alias-pem", formats = { Format.PEM }, aliases = { + @me.escoffier.certs.junit5.Alias(name = "alias1", subjectAlternativeNames = "dns:acme.org"), + @me.escoffier.certs.junit5.Alias(name = "alias2") + }) +}) +public class DefaultPemTrustStoreWithMultipleAliasesTest { + + private static final String configuration = """ + quarkus.tls.trust-store.pem.certs=target/certs/test-alias-pem-ca.crt,target/certs/alias1-ca.crt,target/certs/alias2-ca.crt + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + + assertThat(def.getTrustStoreOptions()).isNotNull(); + assertThat(def.getTrustStore()).isNotNull(); + + List list = new ArrayList<>(); + Iterator iterator = def.getTrustStore().aliases().asIterator(); + while (iterator.hasNext()) { + list.add((X509Certificate) def.getTrustStore().getCertificate(iterator.next())); + } + assertThat(list).hasSize(3); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultSSLOptionsTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultSSLOptionsTest.java new file mode 100644 index 0000000000000..cc302dcd63d92 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/DefaultSSLOptionsTest.java @@ -0,0 +1,67 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.concurrent.TimeUnit; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; + +@me.escoffier.certs.junit5.Certificates(baseDir = "target/certs", certificates = { + @me.escoffier.certs.junit5.Certificate(name = "test-ssl-options", password = "password", formats = { Format.PKCS12 }) +} + +) +public class DefaultSSLOptionsTest { + + private static final String configuration = """ + quarkus.tls.alpn=true + + quarkus.tls.cipher-suites=TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + quarkus.tls.protocols=TLSv1.3 + quarkus.tls.handshake-timeout=20s + quarkus.tls.session-timeout=20s + + + quarkus.tls.key-store.jks.path=target/certs/test-ssl-options-keystore.p12 + quarkus.tls.key-store.jks.password=password + + quarkus.tls.trust-store.jks.path=target/certs/test-ssl-options-truststore.p12 + quarkus.tls.trust-store.jks.password=password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + + assertThat(def.isTrustAll()).isFalse(); + assertThat(def.getHostnameVerificationAlgorithm()).isEmpty(); + + assertThat(def.getSSLOptions().getKeyCertOptions()).isNotNull(); + assertThat(def.getSSLOptions().getTrustOptions()).isNotNull(); + + assertThat(def.getSSLOptions().isUseAlpn()).isTrue(); + assertThat(def.getSSLOptions().getEnabledCipherSuites()).contains("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"); + + assertThat(def.getSSLOptions().getEnabledSecureTransportProtocols()).containsExactly("TLSv1.3"); + assertThat(def.getSSLOptions().getSslHandshakeTimeoutUnit()).isEqualTo(TimeUnit.SECONDS); + assertThat(def.getSSLOptions().getSslHandshakeTimeout()).isEqualTo(20); + } + +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSKeyStoreCredentialsProviderTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSKeyStoreCredentialsProviderTest.java new file mode 100644 index 0000000000000..69338366dc7a1 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSKeyStoreCredentialsProviderTest.java @@ -0,0 +1,70 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.credentials.CredentialsProvider; +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-credentials-provider", password = "secret123!", formats = { Format.JKS, Format.PKCS12 }) +}) +public class JKSKeyStoreCredentialsProviderTest { + + private static final String configuration = """ + quarkus.tls.key-store.jks.path=target/certs/test-credentials-provider-keystore.jks + quarkus.tls.key-store.credentials-provider.name=tls + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClass(MyCredentialProvider.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + + assertThat(def.getKeyStoreOptions()).isNotNull(); + assertThat(def.getKeyStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) def.getKeyStore().getCertificate("test-credentials-provider"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("localhost"); + }); + } + + @ApplicationScoped + public static class MyCredentialProvider implements CredentialsProvider { + + private final Map> credentials = Map.of("tls", + Map.of(CredentialsProvider.PASSWORD_PROPERTY_NAME, "secret123!")); + + @Override + public Map getCredentials(String credentialsProviderName) { + return credentials.get(credentialsProviderName); + } + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSKeyStoreCredentialsProviderWithCustomKeysTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSKeyStoreCredentialsProviderWithCustomKeysTest.java new file mode 100644 index 0000000000000..b79d02689b150 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSKeyStoreCredentialsProviderWithCustomKeysTest.java @@ -0,0 +1,71 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.credentials.CredentialsProvider; +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-credentials-provider", password = "secret123!", formats = { Format.JKS, Format.PKCS12 }) +}) +public class JKSKeyStoreCredentialsProviderWithCustomKeysTest { + + private static final String configuration = """ + quarkus.tls.key-store.jks.path=target/certs/test-credentials-provider-keystore.jks + quarkus.tls.key-store.credentials-provider.name=tls + quarkus.tls.key-store.credentials-provider.password-key=pwd + quarkus.tls.key-store.credentials-provider.alias-password-key=ak + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClass(MyCredentialProvider.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + + assertThat(def.getKeyStoreOptions()).isNotNull(); + assertThat(def.getKeyStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) def.getKeyStore().getCertificate("test-credentials-provider"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("localhost"); + }); + } + + @ApplicationScoped + public static class MyCredentialProvider implements CredentialsProvider { + + private final Map> credentials = Map.of("tls", Map.of("pwd", "secret123!")); + + @Override + public Map getCredentials(String credentialsProviderName) { + return credentials.get(credentialsProviderName); + } + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSKeyStoreFromClassPathTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSKeyStoreFromClassPathTest.java new file mode 100644 index 0000000000000..441219660a562 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSKeyStoreFromClassPathTest.java @@ -0,0 +1,56 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class JKSKeyStoreFromClassPathTest { + + private static final String configuration = """ + quarkus.tls.key-store.jks.path=/certs/keystore.jks + quarkus.tls.key-store.jks.password=password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addAsResource(new File("target/certs/test-formats-keystore.jks"), "/certs/keystore.jks") + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + + assertThat(def.getKeyStoreOptions()).isNotNull(); + assertThat(def.getKeyStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) def.getKeyStore().getCertificate("test-formats"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("localhost"); + }); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSKeyStoreWithAliasCredentialsProviderTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSKeyStoreWithAliasCredentialsProviderTest.java new file mode 100644 index 0000000000000..84eef787f53cf --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSKeyStoreWithAliasCredentialsProviderTest.java @@ -0,0 +1,74 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.credentials.CredentialsProvider; +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Alias; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-credentials-provider-alias", password = "secret123!", formats = { Format.JKS, + Format.PKCS12 }, aliases = @Alias(name = "my-alias", password = "alias-secret123!", subjectAlternativeNames = "dns:acme.org")) +}) +public class JKSKeyStoreWithAliasCredentialsProviderTest { + + private static final String configuration = """ + quarkus.tls.key-store.jks.path=target/certs/test-credentials-provider-alias-keystore.jks + quarkus.tls.key-store.jks.alias=my-alias + quarkus.tls.key-store.credentials-provider.name=tls + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClass(MyCredentialProvider.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + + assertThat(def.getKeyStoreOptions()).isNotNull(); + assertThat(def.getKeyStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) def.getKeyStore().getCertificate("my-alias"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("dns:acme.org"); + }); + } + + @ApplicationScoped + public static class MyCredentialProvider implements CredentialsProvider { + + private final Map> credentials = Map.of("tls", + Map.of(CredentialsProvider.PASSWORD_PROPERTY_NAME, "secret123!", + "alias-password", "alias-secret123!")); + + @Override + public Map getCredentials(String credentialsProviderName) { + return credentials.get(credentialsProviderName); + } + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSKeyStoreWithMissingAliasPasswordTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSKeyStoreWithMissingAliasPasswordTest.java new file mode 100644 index 0000000000000..9129fde0bbb14 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSKeyStoreWithMissingAliasPasswordTest.java @@ -0,0 +1,47 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-alias-jks", password = "password", formats = { Format.JKS }, aliases = { + @me.escoffier.certs.junit5.Alias(name = "alias1", password = "alias-password", subjectAlternativeNames = "dns:acme.org"), + @me.escoffier.certs.junit5.Alias(name = "alias2", password = "alias-password-2") + }) +}) +public class JKSKeyStoreWithMissingAliasPasswordTest { + + private static final String configuration = """ + quarkus.tls.key-store.jks.path=target/certs/test-alias-jks-keystore.jks + quarkus.tls.key-store.jks.password=password + quarkus.tls.key-store.jks.alias=missing + quarkus.tls.key-store.jks.alias-password=alias-password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")) + .assertException(t -> { + assertThat(t).hasMessageContaining("", "password"); + }); + + @Test + void test() throws KeyStoreException, CertificateParsingException { + fail("This test should not be called"); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSKeyStoreWithOverriddenCredentialsProviderTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSKeyStoreWithOverriddenCredentialsProviderTest.java new file mode 100644 index 0000000000000..e9f97eeec02a1 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSKeyStoreWithOverriddenCredentialsProviderTest.java @@ -0,0 +1,66 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.Collections; +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.credentials.CredentialsProvider; +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-credentials-provider", password = "secret123!", formats = { Format.JKS, Format.PKCS12 }) +}) +public class JKSKeyStoreWithOverriddenCredentialsProviderTest { + + private static final String configuration = """ + quarkus.tls.key-store.jks.path=target/certs/test-credentials-provider-keystore.jks + quarkus.tls.key-store.jks.password=secret123! + quarkus.tls.key-store.credentials-provider.name=tls + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClass(MyCredentialProvider.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + + X509Certificate certificate = (X509Certificate) def.getKeyStore().getCertificate("test-credentials-provider"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("localhost"); + }); + } + + @ApplicationScoped + public static class MyCredentialProvider implements CredentialsProvider { + + @Override + public Map getCredentials(String credentialsProviderName) { + return Collections.emptyMap(); // Called for the alias-password + } + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSKeyStoreWithSniTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSKeyStoreWithSniTest.java new file mode 100644 index 0000000000000..23d907879a46e --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSKeyStoreWithSniTest.java @@ -0,0 +1,51 @@ +package io.quarkus.tls; + +import static me.escoffier.certs.Format.JKS; +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.junit5.Alias; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @me.escoffier.certs.junit5.Certificate(name = "test-sni-jks", password = "sni", formats = { JKS }, aliases = { + @Alias(name = "sni-1", password = "sni", cn = "acme.org"), + @Alias(name = "sni-2", password = "sni", cn = "example.com"), + }) +}) +public class JKSKeyStoreWithSniTest { + + private static final String configuration = """ + quarkus.tls.key-store.jks.path=target/certs/test-sni-jks-keystore.jks + quarkus.tls.key-store.jks.password=sni + quarkus.tls.key-store.jks.alias-password=sni + quarkus.tls.key-store.sni=true + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry registry; + + @Test + void test() throws KeyStoreException { + TlsConfiguration tlsConfiguration = registry.getDefault().orElseThrow(); + assertThat(tlsConfiguration.usesSni()).isTrue(); + + assertThat(tlsConfiguration.getKeyStore().size()).isEqualTo(3); + } + +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSKeyStoreWithWrongAliasPasswordTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSKeyStoreWithWrongAliasPasswordTest.java new file mode 100644 index 0000000000000..c1fa0faac4626 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSKeyStoreWithWrongAliasPasswordTest.java @@ -0,0 +1,45 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-alias-jks", password = "password", formats = { Format.JKS }, aliases = { + @me.escoffier.certs.junit5.Alias(name = "alias1", password = "alias-password", subjectAlternativeNames = "dns:acme.org"), + @me.escoffier.certs.junit5.Alias(name = "alias2", password = "alias-password-2") + }) +}) +public class JKSKeyStoreWithWrongAliasPasswordTest { + + private static final String configuration = """ + quarkus.tls.key-store.jks.path=target/certs/test-alias-jks-keystore.jks + quarkus.tls.key-store.jks.password=password + quarkus.tls.key-store.jks.alias=alias1 + quarkus.tls.key-store.jks.alias-password=wrong-password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")) + .assertException(t -> assertThat(t).hasMessageContaining("", "password")); + + @Test + void test() throws KeyStoreException, CertificateParsingException { + fail("This test should not be called"); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSKeyStoreWithWrongPasswordTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSKeyStoreWithWrongPasswordTest.java new file mode 100644 index 0000000000000..81906566e5a6f --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSKeyStoreWithWrongPasswordTest.java @@ -0,0 +1,40 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class JKSKeyStoreWithWrongPasswordTest { + + private static final String configuration = """ + quarkus.tls.key-store.jks.path=target/certs/test-formats-keystore.jks + quarkus.tls.key-store.jks.password=not-the-right-password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")) + .assertException(t -> assertThat(t.getMessage()).contains("default", "JKS", "password")); + + @Test + void test() throws KeyStoreException, CertificateParsingException { + fail("Should not be called as the extension should fail before."); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSKeyStoreWithoutPasswordTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSKeyStoreWithoutPasswordTest.java new file mode 100644 index 0000000000000..60031ed20a01f --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSKeyStoreWithoutPasswordTest.java @@ -0,0 +1,59 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.credentials.CredentialsProvider; +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-credentials-provider", password = "secret123!", formats = { Format.JKS, Format.PKCS12 }) +}) +public class JKSKeyStoreWithoutPasswordTest { + + private static final String configuration = """ + quarkus.tls.key-store.jks.path=target/certs/test-credentials-provider-keystore.jks + quarkus.tls.key-store.credentials-provider.name=tls + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClass(MyCredentialProvider.class) + .add(new StringAsset(configuration), "application.properties")) + .assertException(t -> { + assertThat(t).hasStackTraceContaining( + "the key store password is not set and cannot be retrieved from the credential provider"); + }); + + @Test + void test() throws KeyStoreException, CertificateParsingException { + fail("Should not be called"); + } + + @ApplicationScoped + public static class MyCredentialProvider implements CredentialsProvider { + + private final Map> credentials = Map.of("tls", Map.of()); + + @Override + public Map getCredentials(String credentialsProviderName) { + return credentials.get(credentialsProviderName); + } + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSTrustStoreFromClassPathTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSTrustStoreFromClassPathTest.java new file mode 100644 index 0000000000000..d7ba47c3292f5 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSTrustStoreFromClassPathTest.java @@ -0,0 +1,57 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class JKSTrustStoreFromClassPathTest { + + private static final String configuration = """ + quarkus.tls.trust-store.jks.path=/certs/truststore.jks + quarkus.tls.trust-store.jks.password=password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addAsResource(new File("target/certs/test-formats-truststore.jks"), "/certs/truststore.jks") + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + + assertThat(def.getTrustStoreOptions()).isNotNull(); + assertThat(def.getTrustStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) def.getTrustStore() + .getCertificate("test-formats"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("localhost"); + }); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSTrustStoreWithOverriddenCredentialsProviderTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSTrustStoreWithOverriddenCredentialsProviderTest.java new file mode 100644 index 0000000000000..a22291face278 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSTrustStoreWithOverriddenCredentialsProviderTest.java @@ -0,0 +1,68 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.credentials.CredentialsProvider; +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-credentials-provider", password = "secret123!", formats = { Format.JKS, Format.PKCS12 }) +}) +public class JKSTrustStoreWithOverriddenCredentialsProviderTest { + + private static final String configuration = """ + quarkus.tls.trust-store.jks.path=target/certs/test-credentials-provider-truststore.jks + quarkus.tls.trust-store.jks.password=secret123! + quarkus.tls.trust-store.credentials-provider.name=tls + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClass(MyCredentialProvider.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + + assertThat(def.getTrustStoreOptions()).isNotNull(); + assertThat(def.getTrustStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) def.getTrustStore().getCertificate("test-credentials-provider"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("localhost"); + }); + } + + @ApplicationScoped + public static class MyCredentialProvider implements CredentialsProvider { + + @Override + public Map getCredentials(String credentialsProviderName) { + throw new RuntimeException("Should not be called"); + } + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSTrustStoreWithWrongPasswordTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSTrustStoreWithWrongPasswordTest.java new file mode 100644 index 0000000000000..ad67132e0ba0c --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSTrustStoreWithWrongPasswordTest.java @@ -0,0 +1,40 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class JKSTrustStoreWithWrongPasswordTest { + + private static final String configuration = """ + quarkus.tls.trust-store.jks.path=target/certs/test-formats-truststore.jks + quarkus.tls.trust-store.jks.password=not-the-right-password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")) + .assertException(t -> assertThat(t.getMessage()).contains("default", "JKS", "password", "trust")); + + @Test + void test() throws KeyStoreException, CertificateParsingException { + fail("Should not be called as the extension should fail before."); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSTrustStoreWithoutPasswordTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSTrustStoreWithoutPasswordTest.java new file mode 100644 index 0000000000000..4ec81c20c884a --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/JKSTrustStoreWithoutPasswordTest.java @@ -0,0 +1,59 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.credentials.CredentialsProvider; +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-credentials-provider", password = "secret123!", formats = { Format.JKS, Format.PKCS12 }) +}) +public class JKSTrustStoreWithoutPasswordTest { + + private static final String configuration = """ + quarkus.tls.trust-store.jks.path=target/certs/test-credentials-provider-truststore.jks + quarkus.tls.trust-store.credentials-provider.name=tls + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClass(MyCredentialProvider.class) + .add(new StringAsset(configuration), "application.properties")) + .assertException(t -> { + assertThat(t).hasStackTraceContaining( + "the trust store password is not set and cannot be retrieved from the credential provider"); + }); + + @Test + void test() throws KeyStoreException, CertificateParsingException { + fail("Should not be called"); + } + + @ApplicationScoped + public static class MyCredentialProvider implements CredentialsProvider { + + private final Map> credentials = Map.of("tls", Map.of()); + + @Override + public Map getCredentials(String credentialsProviderName) { + return credentials.get(credentialsProviderName); + } + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/KeyStoreWithMissingKeyCredentialsProviderTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/KeyStoreWithMissingKeyCredentialsProviderTest.java new file mode 100644 index 0000000000000..42b2e802b6afb --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/KeyStoreWithMissingKeyCredentialsProviderTest.java @@ -0,0 +1,56 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import java.util.Collections; +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.credentials.CredentialsProvider; +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-credentials-provider", password = "secret123!", formats = { Format.JKS, Format.PKCS12 }) +}) +public class KeyStoreWithMissingKeyCredentialsProviderTest { + + private static final String configuration = """ + quarkus.tls.foo.key-store.p12.path=target/certs/test-credentials-provider-truststore.p12 + quarkus.tls.foo.key-store.credentials-provider.name=tls + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")) + .assertException(t -> { + assertThat(t) + .hasMessageContaining("Invalid", "trust store", "foo") + .hasStackTraceContaining("the key store password"); + }); + + @Test + void test() { + fail("This test should not be called"); + } + + @ApplicationScoped + public static class MyCredentialProvider implements CredentialsProvider { + + @Override + public Map getCredentials(String credentialsProviderName) { + return Collections.emptyMap(); + } + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/KeyStoreWithSelectedCredentialsProviderTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/KeyStoreWithSelectedCredentialsProviderTest.java new file mode 100644 index 0000000000000..b1d89c5d13d6e --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/KeyStoreWithSelectedCredentialsProviderTest.java @@ -0,0 +1,85 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.inject.Named; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.credentials.CredentialsProvider; +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-credentials-provider", password = "secret123!", formats = { Format.JKS, Format.PKCS12 }) +}) +public class KeyStoreWithSelectedCredentialsProviderTest { + + private static final String configuration = """ + quarkus.tls.foo.key-store.p12.path=target/certs/test-credentials-provider-keystore.p12 + quarkus.tls.foo.key-store.credentials-provider.name=tls + quarkus.tls.foo.key-store.credentials-provider.bean-name=my-provider + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClass(MyCredentialProvider.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.get("foo").orElseThrow(); + + assertThat(def.getKeyStoreOptions()).isNotNull(); + assertThat(def.getKeyStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) def.getKeyStore().getCertificate("test-credentials-provider"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("localhost"); + }); + } + + @ApplicationScoped + @Named("my-provider") + public static class MyCredentialProvider implements CredentialsProvider { + + private final Map> credentials = Map.of("tls", + Map.of(CredentialsProvider.PASSWORD_PROPERTY_NAME, "secret123!")); + + @Override + public Map getCredentials(String credentialsProviderName) { + return credentials.get(credentialsProviderName); + } + } + + @ApplicationScoped + public static class MyOtherCredentialProvider implements CredentialsProvider { + + private final Map> credentials = Map.of("tls", + Map.of(CredentialsProvider.PASSWORD_PROPERTY_NAME, "wrong")); + + @Override + public Map getCredentials(String credentialsProviderName) { + return credentials.get(credentialsProviderName); + } + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/KeyStoreWithSniAndAliasSetTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/KeyStoreWithSniAndAliasSetTest.java new file mode 100644 index 0000000000000..f203ccca8afbc --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/KeyStoreWithSniAndAliasSetTest.java @@ -0,0 +1,46 @@ +package io.quarkus.tls; + +import static me.escoffier.certs.Format.PKCS12; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import java.security.KeyStoreException; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.junit5.Alias; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @me.escoffier.certs.junit5.Certificate(name = "test-sni-p12", password = "sni", formats = { PKCS12 }, aliases = { + @Alias(name = "sni-1", password = "sni", cn = "acme.org"), + @Alias(name = "sni-2", password = "sni", cn = "example.com"), + }) +}) +public class KeyStoreWithSniAndAliasSetTest { + + private static final String configuration = """ + quarkus.tls.key-store.p12.path=target/certs/test-sni-p12-keystore.p12 + quarkus.tls.key-store.p12.password=sni + quarkus.tls.key-store.p12.alias-password=sni + quarkus.tls.key-store.p12.alias=sni-1 + quarkus.tls.key-store.sni=true + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")) + .assertException(t -> assertThat(t).hasMessageContaining("alias", "sni")); + + @Test + void test() throws KeyStoreException { + fail("Should not be called as the deployment should fail due to the alias in the configuration."); + } + +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/KeyStoreWithSniAndSingleAliasSetTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/KeyStoreWithSniAndSingleAliasSetTest.java new file mode 100644 index 0000000000000..cd1bd0b7a1b9a --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/KeyStoreWithSniAndSingleAliasSetTest.java @@ -0,0 +1,40 @@ +package io.quarkus.tls; + +import static me.escoffier.certs.Format.PKCS12; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import java.security.KeyStoreException; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @me.escoffier.certs.junit5.Certificate(name = "test-sni-single", password = "sni", formats = { PKCS12 }) +}) +public class KeyStoreWithSniAndSingleAliasSetTest { + + private static final String configuration = """ + quarkus.tls.key-store.p12.path=target/certs/test-sni-single-keystore.p12 + quarkus.tls.key-store.p12.password=sni + quarkus.tls.key-store.sni=true + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")) + .assertException(t -> assertThat(t).hasMessageContaining("alias", "sni")); + + @Test + void test() throws KeyStoreException { + fail("Should not be called as the deployment should fail."); + } + +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/MissingJKSKeyStoreFromClassPathTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/MissingJKSKeyStoreFromClassPathTest.java new file mode 100644 index 0000000000000..64a58f630f83f --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/MissingJKSKeyStoreFromClassPathTest.java @@ -0,0 +1,42 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class MissingJKSKeyStoreFromClassPathTest { + + private static final String configuration = """ + quarkus.tls.key-store.jks.path=/certs/missing.jks + quarkus.tls.key-store.jks.password=password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")) + .assertException(t -> { + assertThat(t.getMessage()).contains("default", "JKS", "file", "/certs/missing.jks"); + }); + + @Test + void test() throws KeyStoreException, CertificateParsingException { + fail("Should not be called as the extension should fail before."); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/MissingJKSKeyStoreFromFileSystemTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/MissingJKSKeyStoreFromFileSystemTest.java new file mode 100644 index 0000000000000..8107cc815cb00 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/MissingJKSKeyStoreFromFileSystemTest.java @@ -0,0 +1,42 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class MissingJKSKeyStoreFromFileSystemTest { + + private static final String configuration = """ + quarkus.tls.key-store.jks.path=target/certs/missing.jks + quarkus.tls.key-store.jks.password=password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")) + .assertException(t -> { + assertThat(t.getMessage()).contains("default", "JKS", "file", "missing.jks"); + }); + + @Test + void test() throws KeyStoreException, CertificateParsingException { + fail("Should not be called as the extension should fail before."); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/MissingJKSTrustStoreFromClassPathTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/MissingJKSTrustStoreFromClassPathTest.java new file mode 100644 index 0000000000000..499277db13211 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/MissingJKSTrustStoreFromClassPathTest.java @@ -0,0 +1,42 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class MissingJKSTrustStoreFromClassPathTest { + + private static final String configuration = """ + quarkus.tls.trust-store.jks.path=/certs/missing.jks + quarkus.tls.trust-store.jks.password=password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")) + .assertException(t -> { + assertThat(t.getMessage()).contains("default", "JKS", "file", "/certs/missing.jks", "trust"); + }); + + @Test + void test() throws KeyStoreException, CertificateParsingException { + fail("Should not be called as the extension should fail before."); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/MissingP12KeyStoreFromClassPathTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/MissingP12KeyStoreFromClassPathTest.java new file mode 100644 index 0000000000000..f72418c38ebd8 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/MissingP12KeyStoreFromClassPathTest.java @@ -0,0 +1,42 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class MissingP12KeyStoreFromClassPathTest { + + private static final String configuration = """ + quarkus.tls.key-store.p12.path=/certs/missing.p12 + quarkus.tls.key-store.p12.password=password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")) + .assertException(t -> { + assertThat(t.getMessage()).contains("default", "P12", "file", "/certs/missing.p12"); + }); + + @Test + void test() throws KeyStoreException, CertificateParsingException { + fail("Should not be called as the extension should fail before."); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/MissingP12KeyStoreFromFileSystemTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/MissingP12KeyStoreFromFileSystemTest.java new file mode 100644 index 0000000000000..ed6cd4f231f3c --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/MissingP12KeyStoreFromFileSystemTest.java @@ -0,0 +1,42 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class MissingP12KeyStoreFromFileSystemTest { + + private static final String configuration = """ + quarkus.tls.key-store.p12.path=target/certs/missing.p12 + quarkus.tls.key-store.p12.password=password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")) + .assertException(t -> { + assertThat(t.getMessage()).contains("default", "P12", "file", "/certs/missing.p12"); + }); + + @Test + void test() throws KeyStoreException, CertificateParsingException { + fail("Should not be called as the extension should fail before."); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/MissingP12TrustStoreFromClassPathTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/MissingP12TrustStoreFromClassPathTest.java new file mode 100644 index 0000000000000..42b646ad15130 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/MissingP12TrustStoreFromClassPathTest.java @@ -0,0 +1,42 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class MissingP12TrustStoreFromClassPathTest { + + private static final String configuration = """ + quarkus.tls.trust-store.p12.path=/certs/missing.p12 + quarkus.tls.trust-store.p12.password=password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")) + .assertException(t -> { + assertThat(t.getMessage()).contains("default", "P12", "file", "/certs/missing.p12", "trust"); + }); + + @Test + void test() throws KeyStoreException, CertificateParsingException { + fail("Should not be called as the extension should fail before."); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/MissingP12TrustStoreFromFileSystemTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/MissingP12TrustStoreFromFileSystemTest.java new file mode 100644 index 0000000000000..8022feb4694d5 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/MissingP12TrustStoreFromFileSystemTest.java @@ -0,0 +1,42 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class MissingP12TrustStoreFromFileSystemTest { + + private static final String configuration = """ + quarkus.tls.trust-store.p12.path=target/certs/missing.p12 + quarkus.tls.trust-store.p12.password=password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")) + .assertException(t -> { + assertThat(t.getMessage()).contains("default", "P12", "file", "/certs/missing.p12", "trust"); + }); + + @Test + void test() throws KeyStoreException, CertificateParsingException { + fail("Should not be called as the extension should fail before."); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/MissingPemCertTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/MissingPemCertTest.java new file mode 100644 index 0000000000000..cf35c7c682e10 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/MissingPemCertTest.java @@ -0,0 +1,40 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class MissingPemCertTest { + + private static final String configuration = """ + quarkus.tls.key-store.pem[0].cert=target/certs/missing.crt + quarkus.tls.key-store.pem[0].key=target/certs/test-formats.key + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")) + .assertException(t -> assertThat(t.getMessage()).contains("default", "file", "cannot read")); + + @Test + void test() throws KeyStoreException, CertificateParsingException { + fail("Should not be called as the extension should fail before."); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/MissingPemKeyTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/MissingPemKeyTest.java new file mode 100644 index 0000000000000..dfcd4469ad23d --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/MissingPemKeyTest.java @@ -0,0 +1,40 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class MissingPemKeyTest { + + private static final String configuration = """ + quarkus.tls.key-store.pem[0].cert=target/certs/test-formats.crt + quarkus.tls.key-store.pem[0].key=target/certs/missing.key + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")) + .assertException(t -> assertThat(t.getMessage()).contains("default", "file", "cannot read")); + + @Test + void test() throws KeyStoreException, CertificateParsingException { + fail("Should not be called as the extension should fail before."); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/MissingTrustStorePemCertsTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/MissingTrustStorePemCertsTest.java new file mode 100644 index 0000000000000..f5f04f822024a --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/MissingTrustStorePemCertsTest.java @@ -0,0 +1,39 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class MissingTrustStorePemCertsTest { + + private static final String configuration = """ + quarkus.tls.trust-store.pem.certs=target/certs/missing.crt + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")) + .assertException(t -> assertThat(t.getMessage()).contains("default", "file", "cannot read")); + + @Test + void test() throws KeyStoreException, CertificateParsingException { + fail("Should not be called as the extension should fail before."); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedJKSKeyStoreCredentialsProviderTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedJKSKeyStoreCredentialsProviderTest.java new file mode 100644 index 0000000000000..692acd6050957 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedJKSKeyStoreCredentialsProviderTest.java @@ -0,0 +1,70 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.credentials.CredentialsProvider; +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-credentials-provider", password = "secret123!", formats = { Format.JKS, Format.PKCS12 }) +}) +public class NamedJKSKeyStoreCredentialsProviderTest { + + private static final String configuration = """ + quarkus.tls.foo.key-store.jks.path=target/certs/test-credentials-provider-keystore.jks + quarkus.tls.foo.key-store.credentials-provider.name=tls + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClass(MyCredentialProvider.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.get("foo").orElseThrow(); + + assertThat(def.getKeyStoreOptions()).isNotNull(); + assertThat(def.getKeyStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) def.getKeyStore().getCertificate("test-credentials-provider"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("localhost"); + }); + } + + @ApplicationScoped + public static class MyCredentialProvider implements CredentialsProvider { + + private final Map> credentials = Map.of("tls", + Map.of(CredentialsProvider.PASSWORD_PROPERTY_NAME, "secret123!")); + + @Override + public Map getCredentials(String credentialsProviderName) { + return credentials.get(credentialsProviderName); + } + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedJKSKeyStoreTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedJKSKeyStoreTest.java new file mode 100644 index 0000000000000..ed8c1acec6baf --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedJKSKeyStoreTest.java @@ -0,0 +1,58 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class NamedJKSKeyStoreTest { + + private static final String configuration = """ + quarkus.tls.http.key-store.jks.path=target/certs/test-formats-keystore.jks + quarkus.tls.http.key-store.jks.password=password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + TlsConfiguration named = certificates.get("http").orElseThrow(); + + assertThat(def.getKeyStoreOptions()).isNull(); + assertThat(def.getKeyStore()).isNull(); + + assertThat(named.getKeyStoreOptions()).isNotNull(); + assertThat(named.getKeyStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) named.getKeyStore().getCertificate("test-formats"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("localhost"); + }); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedJKSKeyStoreWithAliasCredentialsProviderTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedJKSKeyStoreWithAliasCredentialsProviderTest.java new file mode 100644 index 0000000000000..601641fd8d508 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedJKSKeyStoreWithAliasCredentialsProviderTest.java @@ -0,0 +1,74 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.credentials.CredentialsProvider; +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Alias; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-credentials-provider-alias", password = "secret123!", formats = { Format.JKS, + Format.PKCS12 }, aliases = @Alias(name = "my-alias", password = "alias-secret123!", subjectAlternativeNames = "dns:acme.org")) +}) +public class NamedJKSKeyStoreWithAliasCredentialsProviderTest { + + private static final String configuration = """ + quarkus.tls.foo.key-store.jks.path=target/certs/test-credentials-provider-alias-keystore.jks + quarkus.tls.foo.key-store.jks.alias=my-alias + quarkus.tls.foo.key-store.credentials-provider.name=tls + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClass(MyCredentialProvider.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.get("foo").orElseThrow(); + + assertThat(def.getKeyStoreOptions()).isNotNull(); + assertThat(def.getKeyStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) def.getKeyStore().getCertificate("my-alias"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("dns:acme.org"); + }); + } + + @ApplicationScoped + public static class MyCredentialProvider implements CredentialsProvider { + + private final Map> credentials = Map.of("tls", + Map.of(CredentialsProvider.PASSWORD_PROPERTY_NAME, "secret123!", + "alias-password", "alias-secret123!")); + + @Override + public Map getCredentials(String credentialsProviderName) { + return credentials.get(credentialsProviderName); + } + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedJKSTrustStoreTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedJKSTrustStoreTest.java new file mode 100644 index 0000000000000..94b83073324a0 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedJKSTrustStoreTest.java @@ -0,0 +1,58 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class NamedJKSTrustStoreTest { + + private static final String configuration = """ + quarkus.tls.http.trust-store.jks.path=target/certs/test-formats-truststore.jks + quarkus.tls.http.trust-store.jks.password=password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + TlsConfiguration named = certificates.get("http").orElseThrow(); + + assertThat(def.getTrustStoreOptions()).isNull(); + assertThat(def.getTrustStore()).isNull(); + + assertThat(named.getTrustStoreOptions()).isNotNull(); + assertThat(named.getTrustStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) named.getTrustStore().getCertificate("test-formats"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("localhost"); + }); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedJKSTrustStoreWithCredentialsProviderTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedJKSTrustStoreWithCredentialsProviderTest.java new file mode 100644 index 0000000000000..ee5073548e6c8 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedJKSTrustStoreWithCredentialsProviderTest.java @@ -0,0 +1,70 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.credentials.CredentialsProvider; +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-credentials-provider", password = "secret123!", formats = { Format.JKS, Format.PKCS12 }) +}) +public class NamedJKSTrustStoreWithCredentialsProviderTest { + + private static final String configuration = """ + quarkus.tls.foo.trust-store.jks.path=target/certs/test-credentials-provider-truststore.jks + quarkus.tls.foo.trust-store.credentials-provider.name=tls + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClass(MyCredentialProvider.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.get("foo").orElseThrow(); + + assertThat(def.getTrustStoreOptions()).isNotNull(); + assertThat(def.getTrustStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) def.getTrustStore().getCertificate("test-credentials-provider"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("localhost"); + }); + } + + @ApplicationScoped + public static class MyCredentialProvider implements CredentialsProvider { + + private final Map> credentials = Map.of("tls", + Map.of(CredentialsProvider.PASSWORD_PROPERTY_NAME, "secret123!")); + + @Override + public Map getCredentials(String credentialsProviderName) { + return credentials.get(credentialsProviderName); + } + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedJKSTrustStoreWithCredentialsProviderWithAliasTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedJKSTrustStoreWithCredentialsProviderWithAliasTest.java new file mode 100644 index 0000000000000..77bc04bf8245d --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedJKSTrustStoreWithCredentialsProviderWithAliasTest.java @@ -0,0 +1,73 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.credentials.CredentialsProvider; +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Alias; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-credentials-provider-alias", password = "secret123!", formats = { Format.JKS, + Format.PKCS12 }, aliases = @Alias(name = "my-alias", password = "alias-secret123!", subjectAlternativeNames = "dns:acme.org")) +}) +public class NamedJKSTrustStoreWithCredentialsProviderWithAliasTest { + + private static final String configuration = """ + quarkus.tls.foo.trust-store.jks.path=target/certs/test-credentials-provider-alias-truststore.jks + quarkus.tls.foo.trust-store.jks.alias=my-alias + quarkus.tls.foo.trust-store.credentials-provider.name=tls + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClass(MyCredentialProvider.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.get("foo").orElseThrow(); + + assertThat(def.getTrustStoreOptions()).isNotNull(); + assertThat(def.getTrustStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) def.getTrustStore().getCertificate("my-alias"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("dns:acme.org"); + }); + } + + @ApplicationScoped + public static class MyCredentialProvider implements CredentialsProvider { + + private final Map> credentials = Map.of("tls", + Map.of(CredentialsProvider.PASSWORD_PROPERTY_NAME, "secret123!")); + + @Override + public Map getCredentials(String credentialsProviderName) { + return credentials.get(credentialsProviderName); + } + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedP12KeyStoreCredentialsProviderTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedP12KeyStoreCredentialsProviderTest.java new file mode 100644 index 0000000000000..76e56e41d5f0b --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedP12KeyStoreCredentialsProviderTest.java @@ -0,0 +1,70 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.credentials.CredentialsProvider; +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-credentials-provider", password = "secret123!", formats = { Format.JKS, Format.PKCS12 }) +}) +public class NamedP12KeyStoreCredentialsProviderTest { + + private static final String configuration = """ + quarkus.tls.foo.key-store.p12.path=target/certs/test-credentials-provider-keystore.p12 + quarkus.tls.foo.key-store.credentials-provider.name=tls + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClass(MyCredentialProvider.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.get("foo").orElseThrow(); + + assertThat(def.getKeyStoreOptions()).isNotNull(); + assertThat(def.getKeyStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) def.getKeyStore().getCertificate("test-credentials-provider"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("localhost"); + }); + } + + @ApplicationScoped + public static class MyCredentialProvider implements CredentialsProvider { + + private final Map> credentials = Map.of("tls", + Map.of(CredentialsProvider.PASSWORD_PROPERTY_NAME, "secret123!")); + + @Override + public Map getCredentials(String credentialsProviderName) { + return credentials.get(credentialsProviderName); + } + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedP12KeyStoreTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedP12KeyStoreTest.java new file mode 100644 index 0000000000000..1e6c4a58c938b --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedP12KeyStoreTest.java @@ -0,0 +1,58 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class NamedP12KeyStoreTest { + + private static final String configuration = """ + quarkus.tls.http.key-store.p12.path=target/certs/test-formats-keystore.p12 + quarkus.tls.http.key-store.p12.password=password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + TlsConfiguration named = certificates.get("http").orElseThrow(); + + assertThat(def.getKeyStoreOptions()).isNull(); + assertThat(def.getKeyStore()).isNull(); + + assertThat(named.getKeyStoreOptions()).isNotNull(); + assertThat(named.getKeyStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) named.getKeyStore().getCertificate("test-formats"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("localhost"); + }); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedP12KeyStoreWithAliasCredentialsProviderTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedP12KeyStoreWithAliasCredentialsProviderTest.java new file mode 100644 index 0000000000000..9c0f2b3a63acc --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedP12KeyStoreWithAliasCredentialsProviderTest.java @@ -0,0 +1,74 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.credentials.CredentialsProvider; +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Alias; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-credentials-provider-alias", password = "secret123!", formats = { Format.JKS, + Format.PKCS12 }, aliases = @Alias(name = "my-alias", password = "alias-secret123!", subjectAlternativeNames = "dns:acme.org")) +}) +public class NamedP12KeyStoreWithAliasCredentialsProviderTest { + + private static final String configuration = """ + quarkus.tls.foo.key-store.p12.path=target/certs/test-credentials-provider-alias-keystore.p12 + quarkus.tls.foo.key-store.p12.alias=my-alias + quarkus.tls.foo.key-store.credentials-provider.name=tls + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClass(MyCredentialProvider.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.get("foo").orElseThrow(); + + assertThat(def.getKeyStoreOptions()).isNotNull(); + assertThat(def.getKeyStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) def.getKeyStore().getCertificate("my-alias"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("dns:acme.org"); + }); + } + + @ApplicationScoped + public static class MyCredentialProvider implements CredentialsProvider { + + private final Map> credentials = Map.of("tls", + Map.of(CredentialsProvider.PASSWORD_PROPERTY_NAME, "secret123!", + "alias-password", "alias-secret123!")); + + @Override + public Map getCredentials(String credentialsProviderName) { + return credentials.get(credentialsProviderName); + } + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedP12TrustStoreTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedP12TrustStoreTest.java new file mode 100644 index 0000000000000..1c910c1b97a0d --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedP12TrustStoreTest.java @@ -0,0 +1,58 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class NamedP12TrustStoreTest { + + private static final String configuration = """ + quarkus.tls.http.trust-store.p12.path=target/certs/test-formats-truststore.p12 + quarkus.tls.http.trust-store.p12.password=password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + TlsConfiguration named = certificates.get("http").orElseThrow(); + + assertThat(def.getTrustStoreOptions()).isNull(); + assertThat(def.getTrustStore()).isNull(); + + assertThat(named.getTrustStoreOptions()).isNotNull(); + assertThat(named.getTrustStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) named.getTrustStore().getCertificate("test-formats"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("localhost"); + }); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedP12TrustStoreWithCredentialsProviderTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedP12TrustStoreWithCredentialsProviderTest.java new file mode 100644 index 0000000000000..b64f12a4af95d --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedP12TrustStoreWithCredentialsProviderTest.java @@ -0,0 +1,70 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.credentials.CredentialsProvider; +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-credentials-provider", password = "secret123!", formats = { Format.JKS, Format.PKCS12 }) +}) +public class NamedP12TrustStoreWithCredentialsProviderTest { + + private static final String configuration = """ + quarkus.tls.foo.trust-store.p12.path=target/certs/test-credentials-provider-truststore.p12 + quarkus.tls.foo.trust-store.credentials-provider.name=tls + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClass(MyCredentialProvider.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.get("foo").orElseThrow(); + + assertThat(def.getTrustStoreOptions()).isNotNull(); + assertThat(def.getTrustStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) def.getTrustStore().getCertificate("test-credentials-provider"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("localhost"); + }); + } + + @ApplicationScoped + public static class MyCredentialProvider implements CredentialsProvider { + + private final Map> credentials = Map.of("tls", + Map.of(CredentialsProvider.PASSWORD_PROPERTY_NAME, "secret123!")); + + @Override + public Map getCredentials(String credentialsProviderName) { + return credentials.get(credentialsProviderName); + } + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedP12TrustStoreWithCredentialsProviderWithAliasTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedP12TrustStoreWithCredentialsProviderWithAliasTest.java new file mode 100644 index 0000000000000..4a7eb34a61f24 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedP12TrustStoreWithCredentialsProviderWithAliasTest.java @@ -0,0 +1,73 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.credentials.CredentialsProvider; +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Alias; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-credentials-provider-alias", password = "secret123!", formats = { Format.JKS, + Format.PKCS12 }, aliases = @Alias(name = "my-alias", password = "alias-secret123!", subjectAlternativeNames = "dns:acme.org")) +}) +public class NamedP12TrustStoreWithCredentialsProviderWithAliasTest { + + private static final String configuration = """ + quarkus.tls.foo.trust-store.p12.path=target/certs/test-credentials-provider-alias-truststore.p12 + quarkus.tls.foo.trust-store.p12.alias=my-alias + quarkus.tls.foo.trust-store.credentials-provider.name=tls + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClass(MyCredentialProvider.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.get("foo").orElseThrow(); + + assertThat(def.getTrustStoreOptions()).isNotNull(); + assertThat(def.getTrustStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) def.getTrustStore().getCertificate("my-alias"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("dns:acme.org"); + }); + } + + @ApplicationScoped + public static class MyCredentialProvider implements CredentialsProvider { + + private final Map> credentials = Map.of("tls", + Map.of(CredentialsProvider.PASSWORD_PROPERTY_NAME, "secret123!")); + + @Override + public Map getCredentials(String credentialsProviderName) { + return credentials.get(credentialsProviderName); + } + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedPemKeyStoreTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedPemKeyStoreTest.java new file mode 100644 index 0000000000000..f241065adb63c --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedPemKeyStoreTest.java @@ -0,0 +1,58 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class NamedPemKeyStoreTest { + + private static final String configuration = """ + quarkus.tls.http.key-store.pem[0].cert=target/certs/test-formats.crt + quarkus.tls.http.key-store.pem[0].key=target/certs/test-formats.key + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + TlsConfiguration named = certificates.get("http").orElseThrow(); + + assertThat(def.getKeyStoreOptions()).isNull(); + assertThat(def.getKeyStore()).isNull(); + + assertThat(named.getKeyStoreOptions()).isNotNull(); + assertThat(named.getKeyStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) named.getKeyStore().getCertificate("dummy-entry-0"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("localhost"); + }); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedPemTrustStoreTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedPemTrustStoreTest.java new file mode 100644 index 0000000000000..617bd2b575928 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedPemTrustStoreTest.java @@ -0,0 +1,57 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class NamedPemTrustStoreTest { + + private static final String configuration = """ + quarkus.tls.http.trust-store.pem.certs=target/certs/test-formats.crt + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + TlsConfiguration named = certificates.get("http").orElseThrow(); + + assertThat(def.getTrustStoreOptions()).isNull(); + assertThat(def.getTrustStore()).isNull(); + + assertThat(named.getTrustStoreOptions()).isNotNull(); + assertThat(named.getTrustStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) named.getTrustStore().getCertificate("cert-0"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("localhost"); + }); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedSSLOptionsTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedSSLOptionsTest.java new file mode 100644 index 0000000000000..ab5bfc91c8bc5 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/NamedSSLOptionsTest.java @@ -0,0 +1,70 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.concurrent.TimeUnit; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.vertx.core.net.SSLOptions; +import me.escoffier.certs.Format; + +@me.escoffier.certs.junit5.Certificates(baseDir = "target/certs", certificates = { + @me.escoffier.certs.junit5.Certificate(name = "test-ssl-options", password = "password", formats = { Format.PKCS12 }) +} + +) +public class NamedSSLOptionsTest { + + private static final String configuration = """ + quarkus.tls.foo.alpn=true + + quarkus.tls.foo.cipher-suites=TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + quarkus.tls.foo.protocols=TLSv1.3 + quarkus.tls.foo.handshake-timeout=20s + quarkus.tls.foo.session-timeout=20s + + + quarkus.tls.foo.key-store.jks.path=target/certs/test-ssl-options-keystore.p12 + quarkus.tls.foo.key-store.jks.password=password + + quarkus.tls.foo.trust-store.jks.path=target/certs/test-ssl-options-truststore.p12 + quarkus.tls.foo.trust-store.jks.password=password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() { + TlsConfiguration named = certificates.get("foo").orElseThrow(); + + assertThat(named.isTrustAll()).isFalse(); + assertThat(named.getHostnameVerificationAlgorithm()).isEmpty(); + + SSLOptions options = named.getSSLOptions(); + + assertThat(options.getKeyCertOptions()).isNotNull(); + assertThat(options.getTrustOptions()).isNotNull(); + + assertThat(options.isUseAlpn()).isTrue(); + assertThat(options.getEnabledCipherSuites()).contains("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"); + + assertThat(options.getEnabledSecureTransportProtocols()).containsExactly("TLSv1.3"); + assertThat(options.getSslHandshakeTimeoutUnit()).isEqualTo(TimeUnit.SECONDS); + assertThat(options.getSslHandshakeTimeout()).isEqualTo(20); + } + +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12KeyStoreCredentialsProviderTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12KeyStoreCredentialsProviderTest.java new file mode 100644 index 0000000000000..5dc5f207f4215 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12KeyStoreCredentialsProviderTest.java @@ -0,0 +1,70 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.credentials.CredentialsProvider; +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-credentials-provider", password = "secret123!", formats = { Format.JKS, Format.PKCS12 }) +}) +public class P12KeyStoreCredentialsProviderTest { + + private static final String configuration = """ + quarkus.tls.key-store.p12.path=target/certs/test-credentials-provider-keystore.p12 + quarkus.tls.key-store.credentials-provider.name=tls + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClass(MyCredentialProvider.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + + assertThat(def.getKeyStoreOptions()).isNotNull(); + assertThat(def.getKeyStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) def.getKeyStore().getCertificate("test-credentials-provider"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("localhost"); + }); + } + + @ApplicationScoped + public static class MyCredentialProvider implements CredentialsProvider { + + private final Map> credentials = Map.of("tls", + Map.of(CredentialsProvider.PASSWORD_PROPERTY_NAME, "secret123!")); + + @Override + public Map getCredentials(String credentialsProviderName) { + return credentials.get(credentialsProviderName); + } + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12KeyStoreFromClassPathTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12KeyStoreFromClassPathTest.java new file mode 100644 index 0000000000000..c9493311e32ca --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12KeyStoreFromClassPathTest.java @@ -0,0 +1,56 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class P12KeyStoreFromClassPathTest { + + private static final String configuration = """ + quarkus.tls.key-store.p12.path=/certs/keystore.p12 + quarkus.tls.key-store.p12.password=password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addAsResource(new File("target/certs/test-formats-keystore.p12"), "/certs/keystore.p12") + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + + assertThat(def.getKeyStoreOptions()).isNotNull(); + assertThat(def.getKeyStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) def.getKeyStore().getCertificate("test-formats"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("localhost"); + }); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12KeyStoreWithAliasCredentialsProviderTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12KeyStoreWithAliasCredentialsProviderTest.java new file mode 100644 index 0000000000000..1f8e0e45085bd --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12KeyStoreWithAliasCredentialsProviderTest.java @@ -0,0 +1,74 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.credentials.CredentialsProvider; +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Alias; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-credentials-provider-alias", password = "secret123!", formats = { Format.JKS, + Format.PKCS12 }, aliases = @Alias(name = "my-alias", password = "alias-secret123!", subjectAlternativeNames = "dns:acme.org")) +}) +public class P12KeyStoreWithAliasCredentialsProviderTest { + + private static final String configuration = """ + quarkus.tls.key-store.p12.path=target/certs/test-credentials-provider-alias-keystore.p12 + quarkus.tls.key-store.p12.alias=my-alias + quarkus.tls.key-store.credentials-provider.name=tls + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClass(MyCredentialProvider.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + + assertThat(def.getKeyStoreOptions()).isNotNull(); + assertThat(def.getKeyStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) def.getKeyStore().getCertificate("my-alias"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("dns:acme.org"); + }); + } + + @ApplicationScoped + public static class MyCredentialProvider implements CredentialsProvider { + + private final Map> credentials = Map.of("tls", + Map.of(CredentialsProvider.PASSWORD_PROPERTY_NAME, "secret123!", + "alias-password", "alias-secret123!")); + + @Override + public Map getCredentials(String credentialsProviderName) { + return credentials.get(credentialsProviderName); + } + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12KeyStoreWithMissingAliasPasswordTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12KeyStoreWithMissingAliasPasswordTest.java new file mode 100644 index 0000000000000..d21e9a74c9a81 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12KeyStoreWithMissingAliasPasswordTest.java @@ -0,0 +1,46 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-alias-p12", password = "password", formats = { Format.PKCS12 }, aliases = { + @me.escoffier.certs.junit5.Alias(name = "alias1", password = "alias-password", subjectAlternativeNames = "dns:acme.org"), + @me.escoffier.certs.junit5.Alias(name = "alias2", password = "alias-password-2", subjectAlternativeNames = "dns:example.com") }) +}) +public class P12KeyStoreWithMissingAliasPasswordTest { + + private static final String configuration = """ + quarkus.tls.key-store.p12.path=target/certs/test-alias-p12-keystore.p12 + quarkus.tls.key-store.p12.password=password + quarkus.tls.key-store.p12.alias=missing + quarkus.tls.key-store.p12.alias-password=alias-password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")) + .assertException(t -> { + assertThat(t).hasMessageContaining("", "password"); + }); + + @Test + void test() throws KeyStoreException, CertificateParsingException { + fail("This test should not be called"); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12KeyStoreWithOverriddenCredentialsProviderTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12KeyStoreWithOverriddenCredentialsProviderTest.java new file mode 100644 index 0000000000000..757e2c9375970 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12KeyStoreWithOverriddenCredentialsProviderTest.java @@ -0,0 +1,66 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.Collections; +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.credentials.CredentialsProvider; +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-credentials-provider", password = "secret123!", formats = { Format.JKS, Format.PKCS12 }) +}) +public class P12KeyStoreWithOverriddenCredentialsProviderTest { + + private static final String configuration = """ + quarkus.tls.key-store.p12.path=target/certs/test-credentials-provider-keystore.p12 + quarkus.tls.key-store.p12.password=secret123! + quarkus.tls.key-store.credentials-provider.name=tls + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClass(MyCredentialProvider.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + + X509Certificate certificate = (X509Certificate) def.getKeyStore().getCertificate("test-credentials-provider"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("localhost"); + }); + } + + @ApplicationScoped + public static class MyCredentialProvider implements CredentialsProvider { + + @Override + public Map getCredentials(String credentialsProviderName) { + return Collections.emptyMap(); // Called for the alias-password + } + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12KeyStoreWithSniTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12KeyStoreWithSniTest.java new file mode 100644 index 0000000000000..1ec8105e466a2 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12KeyStoreWithSniTest.java @@ -0,0 +1,51 @@ +package io.quarkus.tls; + +import static me.escoffier.certs.Format.PKCS12; +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.junit5.Alias; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @me.escoffier.certs.junit5.Certificate(name = "test-sni-p12", password = "sni", formats = { PKCS12 }, aliases = { + @Alias(name = "sni-1", password = "sni", cn = "acme.org"), + @Alias(name = "sni-2", password = "sni", cn = "example.com"), + }) +}) +public class P12KeyStoreWithSniTest { + + private static final String configuration = """ + quarkus.tls.key-store.p12.path=target/certs/test-sni-p12-keystore.p12 + quarkus.tls.key-store.p12.password=sni + quarkus.tls.key-store.p12.alias-password=sni + quarkus.tls.key-store.sni=true + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry registry; + + @Test + void test() throws KeyStoreException { + TlsConfiguration tlsConfiguration = registry.getDefault().orElseThrow(); + assertThat(tlsConfiguration.usesSni()).isTrue(); + + assertThat(tlsConfiguration.getKeyStore().size()).isEqualTo(3); + } + +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12KeyStoreWithWrongAliasPasswordTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12KeyStoreWithWrongAliasPasswordTest.java new file mode 100644 index 0000000000000..c5e5a3bac363f --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12KeyStoreWithWrongAliasPasswordTest.java @@ -0,0 +1,46 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-alias-p12", password = "password", formats = { Format.PKCS12 }, aliases = { + @me.escoffier.certs.junit5.Alias(name = "alias1", password = "alias-password", subjectAlternativeNames = "dns:acme.org"), + @me.escoffier.certs.junit5.Alias(name = "alias2", password = "alias-password-2", subjectAlternativeNames = "dns:example.com") }) +}) +public class P12KeyStoreWithWrongAliasPasswordTest { + + private static final String configuration = """ + quarkus.tls.key-store.p12.path=target/certs/test-alias-p12-keystore.p12 + quarkus.tls.key-store.p12.password=password + quarkus.tls.key-store.p12.alias=alias1 + quarkus.tls.key-store.p12.alias-password=wrong-password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")) + .assertException(t -> { + assertThat(t).hasMessageContaining("", "password"); + }); + + @Test + void test() throws KeyStoreException, CertificateParsingException { + fail("This test should not be called"); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12KeyStoreWithWrongPasswordTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12KeyStoreWithWrongPasswordTest.java new file mode 100644 index 0000000000000..0ba5959389c8d --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12KeyStoreWithWrongPasswordTest.java @@ -0,0 +1,40 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class P12KeyStoreWithWrongPasswordTest { + + private static final String configuration = """ + quarkus.tls.key-store.p12.path=target/certs/test-formats-keystore.p12 + quarkus.tls.key-store.p12.password=not-the-right-password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")) + .assertException(t -> assertThat(t.getMessage()).contains("default", "P12", "password")); + + @Test + void test() throws KeyStoreException, CertificateParsingException { + fail("Should not be called as the extension should fail before."); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12KeyStoreWithoutPasswordTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12KeyStoreWithoutPasswordTest.java new file mode 100644 index 0000000000000..3ae4dbaf7389c --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12KeyStoreWithoutPasswordTest.java @@ -0,0 +1,59 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.credentials.CredentialsProvider; +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-credentials-provider", password = "secret123!", formats = { Format.JKS, Format.PKCS12 }) +}) +public class P12KeyStoreWithoutPasswordTest { + + private static final String configuration = """ + quarkus.tls.key-store.p12.path=target/certs/test-credentials-provider-keystore.p12 + quarkus.tls.key-store.credentials-provider.name=tls + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClass(MyCredentialProvider.class) + .add(new StringAsset(configuration), "application.properties")) + .assertException(t -> { + assertThat(t).hasStackTraceContaining( + "the key store password is not set and cannot be retrieved from the credential provider"); + }); + + @Test + void test() throws KeyStoreException, CertificateParsingException { + fail("Should not be called"); + } + + @ApplicationScoped + public static class MyCredentialProvider implements CredentialsProvider { + + private final Map> credentials = Map.of("tls", Map.of()); + + @Override + public Map getCredentials(String credentialsProviderName) { + return credentials.get(credentialsProviderName); + } + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12TrustStoreFromClassPathTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12TrustStoreFromClassPathTest.java new file mode 100644 index 0000000000000..56e5ac5b581e1 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12TrustStoreFromClassPathTest.java @@ -0,0 +1,57 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class P12TrustStoreFromClassPathTest { + + private static final String configuration = """ + quarkus.tls.trust-store.p12.path=/certs/truststore.p12 + quarkus.tls.trust-store.p12.password=password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addAsResource(new File("target/certs/test-formats-truststore.p12"), "/certs/truststore.p12") + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + + assertThat(def.getTrustStoreOptions()).isNotNull(); + assertThat(def.getTrustStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) def.getTrustStore() + .getCertificate("test-formats"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("localhost"); + }); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12TrustStoreWithOverriddenCredentialsProviderTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12TrustStoreWithOverriddenCredentialsProviderTest.java new file mode 100644 index 0000000000000..d2f7b73d64b21 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12TrustStoreWithOverriddenCredentialsProviderTest.java @@ -0,0 +1,68 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.credentials.CredentialsProvider; +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-credentials-provider", password = "secret123!", formats = { Format.JKS, Format.PKCS12 }) +}) +public class P12TrustStoreWithOverriddenCredentialsProviderTest { + + private static final String configuration = """ + quarkus.tls.trust-store.p12.path=target/certs/test-credentials-provider-truststore.p12 + quarkus.tls.trust-store.p12.password=secret123! + quarkus.tls.trust-store.credentials-provider.name=tls + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClass(MyCredentialProvider.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + + assertThat(def.getTrustStoreOptions()).isNotNull(); + assertThat(def.getTrustStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) def.getTrustStore().getCertificate("test-credentials-provider"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("localhost"); + }); + } + + @ApplicationScoped + public static class MyCredentialProvider implements CredentialsProvider { + + @Override + public Map getCredentials(String credentialsProviderName) { + throw new RuntimeException("Should not be called"); + } + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12TrustStoreWithWrongPasswordTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12TrustStoreWithWrongPasswordTest.java new file mode 100644 index 0000000000000..2aa9dac9fd506 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12TrustStoreWithWrongPasswordTest.java @@ -0,0 +1,40 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class P12TrustStoreWithWrongPasswordTest { + + private static final String configuration = """ + quarkus.tls.trust-store.p12.path=target/certs/test-formats-truststore.p12 + quarkus.tls.trust-store.p12.password=not-the-right-password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")) + .assertException(t -> assertThat(t.getMessage()).contains("default", "P12", "password", "trust")); + + @Test + void test() throws KeyStoreException, CertificateParsingException { + fail("Should not be called as the extension should fail before."); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12TrustStoreWithoutPasswordTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12TrustStoreWithoutPasswordTest.java new file mode 100644 index 0000000000000..e27ad4d653ead --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/P12TrustStoreWithoutPasswordTest.java @@ -0,0 +1,59 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.credentials.CredentialsProvider; +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-credentials-provider", password = "secret123!", formats = { Format.JKS, Format.PKCS12 }) +}) +public class P12TrustStoreWithoutPasswordTest { + + private static final String configuration = """ + quarkus.tls.trust-store.p12.path=target/certs/test-credentials-provider-truststore.p12 + quarkus.tls.trust-store.credentials-provider.name=tls + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClass(MyCredentialProvider.class) + .add(new StringAsset(configuration), "application.properties")) + .assertException(t -> { + assertThat(t).hasStackTraceContaining( + "the trust store password is not set and cannot be retrieved from the credential provider"); + }); + + @Test + void test() throws KeyStoreException, CertificateParsingException { + fail("Should not be called"); + } + + @ApplicationScoped + public static class MyCredentialProvider implements CredentialsProvider { + + private final Map> credentials = Map.of("tls", Map.of()); + + @Override + public Map getCredentials(String credentialsProviderName) { + return credentials.get(credentialsProviderName); + } + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/PemKeyStoreFromClassPathTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/PemKeyStoreFromClassPathTest.java new file mode 100644 index 0000000000000..d546fde103536 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/PemKeyStoreFromClassPathTest.java @@ -0,0 +1,58 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.security.cert.X509Certificate; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class PemKeyStoreFromClassPathTest { + + private static final String configuration = """ + quarkus.tls.key-store.pem[0].cert=/certs/cert.pem + quarkus.tls.key-store.pem[0].key=certs/key.pem + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addAsResource(new File("target/certs/test-formats.crt"), "/certs/cert.pem") + .addAsResource(new File("target/certs/test-formats.key"), "/certs/key.pem") + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws Exception { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + + assertThat(def.getKeyStoreOptions()).isNotNull(); + assertThat(def.getKeyStore()).isNotNull(); + + // dummy-entry-x is the alias of the certificate in the keystore generated by Vert.x. + X509Certificate certificate = (X509Certificate) def.getKeyStore().getCertificate("dummy-entry-0"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("localhost"); + }); + + assertThat(def.createSSLContext()).isNotNull(); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/PemKeyStoreWithSniTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/PemKeyStoreWithSniTest.java new file mode 100644 index 0000000000000..bd5dd557d425c --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/PemKeyStoreWithSniTest.java @@ -0,0 +1,49 @@ +package io.quarkus.tls; + +import static me.escoffier.certs.Format.PEM; +import static org.assertj.core.api.Assertions.assertThat; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.junit5.Alias; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @me.escoffier.certs.junit5.Certificate(name = "test-sni-pem", formats = { PEM }, aliases = { + @Alias(name = "sni-1", cn = "acme.org"), + @Alias(name = "sni-2", cn = "example.com"), + }) +}) +public class PemKeyStoreWithSniTest { + + private static final String configuration = """ + quarkus.tls.key-store.pem[0].cert=target/certs/sni-1.crt + quarkus.tls.key-store.pem[0].key=target/certs/sni-1.key + quarkus.tls.key-store.pem[1].cert=target/certs/sni-2.crt + quarkus.tls.key-store.pem[1].key=target/certs/sni-2.key + quarkus.tls.key-store.sni=true + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry registry; + + @Test + void test() throws Exception { + TlsConfiguration tlsConfiguration = registry.getDefault().orElseThrow(); + assertThat(tlsConfiguration.usesSni()).isTrue(); + assertThat(tlsConfiguration.getKeyStore().size()).isEqualTo(2); + } + +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/PemTrustStoreFromClassPathTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/PemTrustStoreFromClassPathTest.java new file mode 100644 index 0000000000000..249184a809aed --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/PemTrustStoreFromClassPathTest.java @@ -0,0 +1,55 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class PemTrustStoreFromClassPathTest { + + private static final String configuration = """ + quarkus.tls.trust-store.pem.certs=/certs/ca.pem + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addAsResource(new File("target/certs/test-formats-ca.crt"), "/certs/ca.pem") + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + + assertThat(def.getTrustStoreOptions()).isNotNull(); + assertThat(def.getTrustStore()).isNotNull(); + + X509Certificate certificate = (X509Certificate) def.getTrustStore().getCertificate("cert-0"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("localhost"); + }); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/PemWithoutKeyTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/PemWithoutKeyTest.java new file mode 100644 index 0000000000000..0bca7a8380c07 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/PemWithoutKeyTest.java @@ -0,0 +1,39 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class PemWithoutKeyTest { + + private static final String configuration = """ + quarkus.tls.key-store.pem[0].cert=target/certs/test-formats.crt + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")) + .assertException(t -> assertThat(t.getCause().getMessage()).contains("quarkus.tls.key-store.pem[0].key")); + + @Test + void test() throws KeyStoreException, CertificateParsingException { + fail("Should not be called as the extension should fail before."); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/ReloadKeyStoreTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/ReloadKeyStoreTest.java new file mode 100644 index 0000000000000..e247ed4b0328a --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/ReloadKeyStoreTest.java @@ -0,0 +1,85 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.UUID; + +import jakarta.inject.Inject; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-reload-A", password = "password", formats = Format.PKCS12, subjectAlternativeNames = "dns:localhost"), + @Certificate(name = "test-reload-B", password = "password", formats = Format.PKCS12, subjectAlternativeNames = "dns:acme.org") +}) +public class ReloadKeyStoreTest { + + private static final String configuration = """ + # No config - overridden in the test + """; + + public static final File temp = new File("target/test-certificates-" + UUID.randomUUID()); + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")) + .overrideRuntimeConfigKey("quarkus.tls.key-store.p12.path", temp.getAbsolutePath() + "/tls.p12") + .overrideRuntimeConfigKey("quarkus.tls.key-store.p12.password", "password") + .overrideRuntimeConfigKey("loc", temp.getAbsolutePath()) + .setBeforeAllCustomizer(() -> { + try { + // Prepare a random directory to store the certificates. + temp.mkdirs(); + Files.copy(new File("target/certs/test-reload-A-keystore.p12").toPath(), + new File(temp, "/tls.p12").toPath()); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + + @Inject + Registry registry; + + @ConfigProperty(name = "loc") + File certs; + + @Test + void testReloading() throws CertificateParsingException, KeyStoreException, IOException { + TlsConfiguration def = registry.getDefault().orElseThrow(); + X509Certificate certificate = (X509Certificate) def.getKeyStore().getCertificate("test-reload-A"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("dns:localhost"); + }); + + Files.copy(new File("target/certs/test-reload-B-keystore.p12").toPath(), + new File(certs, "/tls.p12").toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING); + + assertThat(def.reload()).isTrue(); + certificate = (X509Certificate) def.getKeyStore().getCertificate("test-reload-B"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("dns:acme.org"); + }); + } + +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/ReloadTrustStoreTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/ReloadTrustStoreTest.java new file mode 100644 index 0000000000000..db12a54bbc309 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/ReloadTrustStoreTest.java @@ -0,0 +1,85 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.UUID; + +import jakarta.inject.Inject; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-reload-A", password = "password", formats = Format.PKCS12, subjectAlternativeNames = "dns:localhost"), + @Certificate(name = "test-reload-B", password = "password", formats = Format.PKCS12, subjectAlternativeNames = "dns:acme.org") +}) +public class ReloadTrustStoreTest { + + private static final String configuration = """ + # No config - overridden in the test + """; + + public static final File temp = new File("target/test-certificates-" + UUID.randomUUID()); + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")) + .overrideRuntimeConfigKey("quarkus.tls.trust-store.p12.path", temp.getAbsolutePath() + "/tls.p12") + .overrideRuntimeConfigKey("quarkus.tls.trust-store.p12.password", "password") + .overrideRuntimeConfigKey("loc", temp.getAbsolutePath()) + .setBeforeAllCustomizer(() -> { + try { + // Prepare a random directory to store the certificates. + temp.mkdirs(); + Files.copy(new File("target/certs/test-reload-A-truststore.p12").toPath(), + new File(temp, "/tls.p12").toPath()); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + + @Inject + Registry registry; + + @ConfigProperty(name = "loc") + File certs; + + @Test + void testReloading() throws CertificateParsingException, KeyStoreException, IOException { + TlsConfiguration def = registry.getDefault().orElseThrow(); + X509Certificate certificate = (X509Certificate) def.getTrustStore().getCertificate("test-reload-A"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("dns:localhost"); + }); + + Files.copy(new File("target/certs/test-reload-B-truststore.p12").toPath(), + new File(certs, "/tls.p12").toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING); + + assertThat(def.reload()).isTrue(); + certificate = (X509Certificate) def.getTrustStore().getCertificate("test-reload-B"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("dns:acme.org"); + }); + } + +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/ReloadWithoutConfigTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/ReloadWithoutConfigTest.java new file mode 100644 index 0000000000000..05570f48f30f8 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/ReloadWithoutConfigTest.java @@ -0,0 +1,40 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.util.UUID; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class ReloadWithoutConfigTest { + + private static final String configuration = """ + # No config - overridden in the test + """; + + public static final File temp = new File("target/test-certificates-" + UUID.randomUUID()); + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry registry; + + @Test + void testReloading() { + TlsConfiguration def = registry.getDefault().orElseThrow(); + assertThat(def.reload()).isFalse(); + } + +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/RuntimeRegistrationTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/RuntimeRegistrationTest.java new file mode 100644 index 0000000000000..4bc7c54d0bcaf --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/RuntimeRegistrationTest.java @@ -0,0 +1,90 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.io.IOException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-registration", password = "password", formats = Format.PKCS12) +}) +public class RuntimeRegistrationTest { + + private static final String configuration = """ + # no configuration by default + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry registry; + + @Test + void testRegistration() throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException { + assertThat(registry.getDefault()).isNotEmpty(); + assertThat(registry.getDefault().orElseThrow().isTrustAll()).isFalse(); + assertThat(registry.get("named")).isEmpty(); + + KeyStore ks = KeyStore.getInstance("PKCS12"); + ks.load(getClass().getResourceAsStream("target/certs/test-registration-keystore.p12"), "password".toCharArray()); + KeyStore ts = KeyStore.getInstance("PKCS12"); + ts.load(getClass().getResourceAsStream("target/certs/test-registration-truststore.p12"), "password".toCharArray()); + + registry.register("named", new BaseTlsConfiguration() { + @Override + public KeyStore getKeyStore() { + return ks; + } + + @Override + public KeyStore getTrustStore() { + return ts; + } + }); + + TlsConfiguration conf = registry.get("named").orElseThrow(); + assertThat(conf.getKeyStore()).isSameAs(ks); + assertThat(conf.getTrustStore()).isSameAs(ts); + } + + @Test + void cannotRegisterWithNull() { + assertThatThrownBy(() -> registry.register(null, null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("The name of the TLS configuration to register cannot be null"); + } + + @Test + void cannotRegisterWithDefault() { + assertThatThrownBy(() -> registry.register("", null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("The name of the TLS configuration to register cannot be "); + } + + @Test + void cannotRegisterNull() { + assertThatThrownBy(() -> registry.register("foo", null)) + .isInstanceOf(IllegalArgumentException.class); + } + +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/TooManyKeyStoreConfiguredJKSAndP12Test.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/TooManyKeyStoreConfiguredJKSAndP12Test.java new file mode 100644 index 0000000000000..1398b582fa82d --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/TooManyKeyStoreConfiguredJKSAndP12Test.java @@ -0,0 +1,41 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class TooManyKeyStoreConfiguredJKSAndP12Test { + + private static final String configuration = """ + quarkus.tls.key-store.jks.path=target/certs/test-formats-keystore.jks + quarkus.tls.key-store.jks.password=password + quarkus.tls.key-store.p12.path=target/certs/test-formats-keystore.p12 + quarkus.tls.key-store.p12.password=password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")) + .assertException(t -> { + assertThat(t).hasMessageContaining("PKCS12", "JKS"); + }); + + @Test + void shouldNotBeCalled() { + fail("This test should not be called"); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/TooManyKeyStoreConfiguredPemAndJKSTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/TooManyKeyStoreConfiguredPemAndJKSTest.java new file mode 100644 index 0000000000000..14c3edb63da65 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/TooManyKeyStoreConfiguredPemAndJKSTest.java @@ -0,0 +1,41 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class TooManyKeyStoreConfiguredPemAndJKSTest { + + private static final String configuration = """ + quarkus.tls.key-store.pem[0].cert=target/certs/test-formats.crt + quarkus.tls.key-store.pem[0].key=target/certs/test-formats.key + quarkus.tls.key-store.jks.path=target/certs/test-formats-keystore.jks + quarkus.tls.key-store.jks.password=password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")) + .assertException(t -> { + assertThat(t).hasMessageContaining("PEM", "JKS"); + }); + + @Test + void shouldNotBeCalled() { + fail("This test should not be called"); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/TooManyKeyStoreConfiguredPemAndP12Test.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/TooManyKeyStoreConfiguredPemAndP12Test.java new file mode 100644 index 0000000000000..2f56c716f12e3 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/TooManyKeyStoreConfiguredPemAndP12Test.java @@ -0,0 +1,41 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class TooManyKeyStoreConfiguredPemAndP12Test { + + private static final String configuration = """ + quarkus.tls.key-store.pem[0].cert=target/certs/test-formats.crt + quarkus.tls.key-store.pem[0].key=target/certs/test-formats.key + quarkus.tls.key-store.p12.path=target/certs/test-formats-keystore.p12 + quarkus.tls.key-store.p12.password=password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")) + .assertException(t -> { + assertThat(t).hasMessageContaining("PEM", "PKCS12"); + }); + + @Test + void shouldNotBeCalled() { + fail("This test should not be called"); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/TooManyTrustStoreConfiguredJKSAndP12Test.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/TooManyTrustStoreConfiguredJKSAndP12Test.java new file mode 100644 index 0000000000000..8c67987910c9b --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/TooManyTrustStoreConfiguredJKSAndP12Test.java @@ -0,0 +1,41 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class TooManyTrustStoreConfiguredJKSAndP12Test { + + private static final String configuration = """ + quarkus.tls.trust-store.jks.path=target/certs/test-formats-truststore.jks + quarkus.tls.trust-store.jks.password=password + quarkus.tls.trust-store.p12.path=target/certs/test-formats-truststore.p12 + quarkus.tls.trust-store.p12.password=password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")) + .assertException(t -> { + assertThat(t).hasMessageContaining("PKCS12", "JKS", "trust"); + }); + + @Test + void shouldNotBeCalled() { + fail("This test should not be called"); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/TooManyTrustStoreConfiguredPemAndJKSTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/TooManyTrustStoreConfiguredPemAndJKSTest.java new file mode 100644 index 0000000000000..a85db2cd51722 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/TooManyTrustStoreConfiguredPemAndJKSTest.java @@ -0,0 +1,40 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class TooManyTrustStoreConfiguredPemAndJKSTest { + + private static final String configuration = """ + quarkus.tls.trust-store.pem.certs=target/certs/test-formats.crt + quarkus.tls.trust-store.jks.path=target/certs/test-formats-keystore.jks + quarkus.tls.trust-store.jks.password=password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")) + .assertException(t -> { + assertThat(t).hasMessageContaining("PEM", "JKS", "trust"); + }); + + @Test + void shouldNotBeCalled() { + fail("This test should not be called"); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/TooManyTrustStoreConfiguredPemAndP12Test.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/TooManyTrustStoreConfiguredPemAndP12Test.java new file mode 100644 index 0000000000000..20f2432b37f2c --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/TooManyTrustStoreConfiguredPemAndP12Test.java @@ -0,0 +1,40 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class TooManyTrustStoreConfiguredPemAndP12Test { + + private static final String configuration = """ + quarkus.tls.trust-store.pem.certs=target/certs/test-formats-ca.crt + quarkus.tls.trust-store.p12.path=target/certs/test-formats-truststore.p12 + quarkus.tls.trust-store.p12.password=password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")) + .assertException(t -> { + assertThat(t).hasMessageContaining("PEM", "PKCS12", "trust"); + }); + + @Test + void shouldNotBeCalled() { + fail("This test should not be called"); + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/TrustAllAndHostnameVerifierAlgorithmTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/TrustAllAndHostnameVerifierAlgorithmTest.java new file mode 100644 index 0000000000000..b7d373cf9217e --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/TrustAllAndHostnameVerifierAlgorithmTest.java @@ -0,0 +1,55 @@ +package io.quarkus.tls; + +import jakarta.inject.Inject; + +import org.assertj.core.api.Assertions; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.quarkus.tls.runtime.keystores.TrustAllOptions; + +public class TrustAllAndHostnameVerifierAlgorithmTest { + + private static final String configuration = """ + quarkus.tls.trust-all=false + + quarkus.tls.open.trust-all=true + quarkus.tls.open.hostname-verification-algorithm=NONE + + quarkus.tls.closed.trust-all=false + quarkus.tls.closed.hostname-verification-algorithm=HTTPS + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() { + TlsConfiguration def = certificates.getDefault().orElseThrow(); + TlsConfiguration open = certificates.get("open").orElseThrow(); + TlsConfiguration closed = certificates.get("closed").orElseThrow(); + + Assertions.assertThat(def.isTrustAll()).isFalse(); + Assertions.assertThat(def.getHostnameVerificationAlgorithm()).isEmpty(); + + Assertions.assertThat(open.isTrustAll()).isTrue(); + Assertions.assertThat(open.getTrustStore()).isNull(); + Assertions.assertThat(open.getTrustStoreOptions()).isEqualTo(TrustAllOptions.INSTANCE); + Assertions.assertThat(open.getHostnameVerificationAlgorithm()).hasValue("NONE"); + + Assertions.assertThat(closed.isTrustAll()).isFalse(); + Assertions.assertThat(closed.getTrustStore()).isNull(); + Assertions.assertThat(closed.getTrustStoreOptions()).isNull(); + Assertions.assertThat(closed.getHostnameVerificationAlgorithm()).hasValue("HTTPS"); + } + +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/TrustAllWithTrustStoreTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/TrustAllWithTrustStoreTest.java new file mode 100644 index 0000000000000..416e344b09a1e --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/TrustAllWithTrustStoreTest.java @@ -0,0 +1,41 @@ +package io.quarkus.tls; + +import static org.junit.jupiter.api.Assertions.fail; + +import org.assertj.core.api.Assertions; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; + +/** + * Verify that is trust all is set, trust store is not set. + */ +@me.escoffier.certs.junit5.Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-formats", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }) +}) +public class TrustAllWithTrustStoreTest { + + private static final String configuration = """ + quarkus.tls.http.trust-all=true + quarkus.tls.http.trust-store.jks.path=target/certs/test-formats-truststore.jks + quarkus.tls.http.trust-store.jks.password=password + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")) + .assertException(t -> Assertions.assertThat(t).hasMessageContaining("trust-all", "trust-store")); + + @Test + void test() { + fail("This test should not be called"); + } + +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/TrustStoreWithMissingCredentialsProviderTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/TrustStoreWithMissingCredentialsProviderTest.java new file mode 100644 index 0000000000000..5b6dfe60293b7 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/TrustStoreWithMissingCredentialsProviderTest.java @@ -0,0 +1,42 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-credentials-provider", password = "secret123!", formats = { Format.JKS, Format.PKCS12 }) +}) +public class TrustStoreWithMissingCredentialsProviderTest { + + private static final String configuration = """ + quarkus.tls.foo.trust-store.p12.path=target/certs/test-credentials-provider-truststore.p12 + quarkus.tls.foo.trust-store.credentials-provider.name=tls + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")) + .assertException(t -> { + assertThat(t) + .hasMessageContaining("Invalid", "trust store", "foo") + .hasStackTraceContaining("Unable to find the default credentials provider"); + }); + + @Test + void test() { + fail("This test should not be called"); + } + +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/TrustStoreWithMissingKeyCredentialsProviderTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/TrustStoreWithMissingKeyCredentialsProviderTest.java new file mode 100644 index 0000000000000..b4320e4732a1f --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/TrustStoreWithMissingKeyCredentialsProviderTest.java @@ -0,0 +1,56 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import java.util.Collections; +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.credentials.CredentialsProvider; +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-credentials-provider", password = "secret123!", formats = { Format.JKS, Format.PKCS12 }) +}) +public class TrustStoreWithMissingKeyCredentialsProviderTest { + + private static final String configuration = """ + quarkus.tls.foo.trust-store.p12.path=target/certs/test-credentials-provider-truststore.p12 + quarkus.tls.foo.trust-store.credentials-provider.name=tls + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .add(new StringAsset(configuration), "application.properties")) + .assertException(t -> { + assertThat(t) + .hasMessageContaining("Invalid", "trust store", "foo") + .hasStackTraceContaining("the trust store password"); + }); + + @Test + void test() { + fail("This test should not be called"); + } + + @ApplicationScoped + public static class MyCredentialProvider implements CredentialsProvider { + + @Override + public Map getCredentials(String credentialsProviderName) { + return Collections.emptyMap(); + } + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/TrustStoreWithMissingSelectedCredentialsProviderTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/TrustStoreWithMissingSelectedCredentialsProviderTest.java new file mode 100644 index 0000000000000..8974a5a0b6e1c --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/TrustStoreWithMissingSelectedCredentialsProviderTest.java @@ -0,0 +1,74 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Named; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.credentials.CredentialsProvider; +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-credentials-provider", password = "secret123!", formats = { Format.JKS, Format.PKCS12 }) +}) +public class TrustStoreWithMissingSelectedCredentialsProviderTest { + + private static final String configuration = """ + quarkus.tls.foo.trust-store.p12.path=target/certs/test-credentials-provider-truststore.p12 + quarkus.tls.foo.trust-store.credentials-provider.name=tls + quarkus.tls.foo.trust-store.credentials-provider.bean-name=missing + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClass(MyCredentialProvider.class) + .add(new StringAsset(configuration), "application.properties")) + .assertException(t -> { + assertThat(t) + .hasMessageContaining("Invalid", "trust store", "foo") + .hasStackTraceContaining("Unable to find the credentials provider named 'missing'"); + }); + + @Test + void test() { + fail("This test should not be called"); + } + + @ApplicationScoped + @Named("my-provider") + public static class MyCredentialProvider implements CredentialsProvider { + + private final Map> credentials = Map.of("tls", + Map.of(CredentialsProvider.PASSWORD_PROPERTY_NAME, "secret123!")); + + @Override + public Map getCredentials(String credentialsProviderName) { + return credentials.get(credentialsProviderName); + } + } + + @ApplicationScoped + public static class MyOtherCredentialProvider implements CredentialsProvider { + + private final Map> credentials = Map.of("tls", + Map.of(CredentialsProvider.PASSWORD_PROPERTY_NAME, "wrong")); + + @Override + public Map getCredentials(String credentialsProviderName) { + return credentials.get(credentialsProviderName); + } + } +} diff --git a/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/TrustStoreWithSelectedCredentialsProviderTest.java b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/TrustStoreWithSelectedCredentialsProviderTest.java new file mode 100644 index 0000000000000..37ef60da42fe2 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/java/io/quarkus/tls/TrustStoreWithSelectedCredentialsProviderTest.java @@ -0,0 +1,82 @@ +package io.quarkus.tls; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.KeyStoreException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.inject.Named; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.credentials.CredentialsProvider; +import io.quarkus.test.QuarkusUnitTest; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "test-credentials-provider", password = "secret123!", formats = { Format.JKS, Format.PKCS12 }) +}) +public class TrustStoreWithSelectedCredentialsProviderTest { + + private static final String configuration = """ + quarkus.tls.foo.trust-store.p12.path=target/certs/test-credentials-provider-truststore.p12 + quarkus.tls.foo.trust-store.credentials-provider.name=tls + quarkus.tls.foo.trust-store.credentials-provider.bean-name=my-provider + """; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClass(MyCredentialProvider.class) + .add(new StringAsset(configuration), "application.properties")); + + @Inject + Registry certificates; + + @Test + void test() throws KeyStoreException, CertificateParsingException { + TlsConfiguration def = certificates.get("foo").orElseThrow(); + + X509Certificate certificate = (X509Certificate) def.getTrustStore().getCertificate("test-credentials-provider"); + assertThat(certificate).isNotNull(); + assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> { + assertThat(l.get(0)).isEqualTo(2); + assertThat(l.get(1)).isEqualTo("localhost"); + }); + } + + @ApplicationScoped + @Named("my-provider") + public static class MyCredentialProvider implements CredentialsProvider { + + private final Map> credentials = Map.of("tls", + Map.of(CredentialsProvider.PASSWORD_PROPERTY_NAME, "secret123!")); + + @Override + public Map getCredentials(String credentialsProviderName) { + return credentials.get(credentialsProviderName); + } + } + + @ApplicationScoped + public static class MyOtherCredentialProvider implements CredentialsProvider { + + private final Map> credentials = Map.of("tls", + Map.of(CredentialsProvider.PASSWORD_PROPERTY_NAME, "wrong")); + + @Override + public Map getCredentials(String credentialsProviderName) { + return credentials.get(credentialsProviderName); + } + } +} diff --git a/extensions/tls-registry/deployment/src/test/resources/revocations/revoked-cert.der b/extensions/tls-registry/deployment/src/test/resources/revocations/revoked-cert.der new file mode 100644 index 0000000000000..31bbb84038058 --- /dev/null +++ b/extensions/tls-registry/deployment/src/test/resources/revocations/revoked-cert.der @@ -0,0 +1 @@ +3082017A30820126A003020102020900A1F43A2D12D3080B300906052B0E03021A0500301C311A3018060355040A1311446576696365205465737420434131193017060355040B13107777772E6465766963652E636F6D313430320603550403131B44657669636520546573742043657274696669636174696F6E204C697374302E06092A864886F70D01090116107365727665722E6465766963652E636F6D301E170D3231303330393037303633325A170D3331303330363037303633325A303C311A3018060355040A1311446576696365205465737420434131193017060355040B13107777772E6465766963652E636F6D313430320603550403131B44657669636520546573742043657274696669636174696F6E204C69737430820122300D06092A864886F70D01010105000382010F003082010A0282010100A38BB56A81D1E2D8A14A85A5B835C4B1B4C96A4FBB78C7D9044B7DB4D514EAF13C725AB4C3D0F3257912C73F3A0B44DC0363CE23D37A00BDC722C18BDAF5E0F656CF76953F5A82F7B4E72D36B5A94BFAAB990757C1AEF27E024D2FA6586D59E5B838C36C4F8D19A2078F99AD0FDBE9C7A50313C27DCC2A2AC6A19B3721D6C181C1A508E39A07292F0A0461B3147D4B7921F148FB3C12FE71A9638F9F31F57031D2F60C1D5D6924AD93A09AC751EF0F7A4F7FAA5A3A91D2C10A0EC44A4060267F4F0AA8D5FEAB1BC5596B130EAC15E78DE15835A3EC680ECBCDCA5E9ECA5128109E06A16FADFD2FAE3E7A589BF51B530E4FE4ADDF69E2609DB02163CFA9F0F7CE5C7CABFCAFFC27C7E93F29C1F60DF65AB3B70C62B69D46FD0C2F1FC9079E4412D0212EC4D45FD9F4055EFA30203010001A38201E0308201DC301F0603551D23041830168014B7B9A578DAD862E409C11E7D7E8F78DE04AB0C301D0603551D0E04160414B7B9A578DAD862E409C11E7D7E8F78DE04AB0C300F0603551D130101FF040530030101FF301D0603551D11041630148210656E6372797074696E672E6578616D706C652E636F6D301F0603551D20041830168014B7B9A578DAD862E409C11E7D7E8F78DE04AB0C300D06092A864886F70D0101050500038201010024FBDA9A2E4A4C6E1A68B38B1EF3FC66942D9F51898DE03DF5F7A5A0AD43957A04CB94B01576E3B0B8A463DC2508A9B77473FA72700EF007018B1E2E138F5404B32B0C4B39AFA7F89D40B05E0E255D1FC6D1950F0DE1C063FBE0F24586CBA25B50015F8D1E3D1A4C0B7E2B3F5316B571FA3D5F31A823F049D1C8E4826ED6BB2E77CEB4AB5DFE575C022869ADE3CB2ED0E7C800E64D03BC2C8D928DF23AEB2D3704760BCF67B41A3EF706DAD66E69B13150B92E0E7D9E9B0CC24B3EBB63E0033C64A9886A49 \ No newline at end of file diff --git a/extensions/tls-registry/pom.xml b/extensions/tls-registry/pom.xml new file mode 100644 index 0000000000000..0b2e443bc9f6e --- /dev/null +++ b/extensions/tls-registry/pom.xml @@ -0,0 +1,22 @@ + + + + + quarkus-extensions-parent + io.quarkus + 999-SNAPSHOT + ../pom.xml + + 4.0.0 + + quarkus-tls-registry-parent + Quarkus - TLS Registry + pom + + deployment + runtime + + + diff --git a/extensions/tls-registry/runtime/pom.xml b/extensions/tls-registry/runtime/pom.xml new file mode 100644 index 0000000000000..772156e706b06 --- /dev/null +++ b/extensions/tls-registry/runtime/pom.xml @@ -0,0 +1,53 @@ + + + + quarkus-tls-registry-parent + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + quarkus-tls-registry + Quarkus - TLS Registry - Runtime + An internal TLS certificate registry. + + + io.quarkus + quarkus-vertx + + + + io.quarkus + quarkus-arc + + + + io.quarkus + quarkus-credentials + + + + + + + io.quarkus + quarkus-extension-maven-plugin + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + + diff --git a/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/BaseTlsConfiguration.java b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/BaseTlsConfiguration.java new file mode 100644 index 0000000000000..dcd410e768549 --- /dev/null +++ b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/BaseTlsConfiguration.java @@ -0,0 +1,102 @@ +package io.quarkus.tls; + +import java.security.KeyStore; +import java.util.Optional; + +import javax.net.ssl.SSLContext; + +import io.vertx.core.net.KeyCertOptions; +import io.vertx.core.net.SSLOptions; +import io.vertx.core.net.TrustOptions; + +/** + * A base implementation of the transport layer security configuration interface. + */ +public abstract class BaseTlsConfiguration implements TlsConfiguration { + + /** + * Returns the key store. + * + * @return the key store if configured. + */ + public KeyStore getKeyStore() { + return null; + } + + /** + * Returns the key store options. + * + * @return the key store options if configured. + */ + public KeyCertOptions getKeyStoreOptions() { + return null; + } + + /** + * Returns the trust store. + * + * @return the trust store if configured. + */ + public KeyStore getTrustStore() { + return null; + } + + /** + * Returns the trust store options. + * + * @return the trust store options if configured. + */ + public TrustOptions getTrustStoreOptions() { + return null; + } + + /** + * Returns the (Vert.x) SSL options. + * + * @return the {@link SSLOptions}, {@code null} if not configured. + */ + public SSLOptions getSSLOptions() { + return null; + } + + /** + * Creates and returns the SSL Context. + * + * @return the {@link SSLContext}, {@code null} if not configured. + */ + public SSLContext createSSLContext() throws Exception { + return null; + } + + /** + * Returns the hostname verification algorithm for this configuration. + * {@code "NONE"} means no hostname verification. + * + * @return the hostname verification algorithm. + */ + public Optional getHostnameVerificationAlgorithm() { + return Optional.empty(); + } + + /** + * Returns whether the key store is configured to use SNI. + * When SNI is used, the client indicate the server name during the TLS handshake, allowing the server to select the + * right certificate. + * + * @return {@code true} if the key store is configured to use SNI, {@code false} otherwise. + */ + public boolean usesSni() { + return false; + } + + /** + * Reloads the configuration. + * It usually means reloading the key store and trust store, especially when they are files. + * + * @return {@code true} if the configuration has been reloaded, {@code false} otherwise. + */ + public boolean reload() { + return false; + } + +} diff --git a/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/Registry.java b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/Registry.java new file mode 100644 index 0000000000000..102d4cc4e0660 --- /dev/null +++ b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/Registry.java @@ -0,0 +1,33 @@ +package io.quarkus.tls; + +import java.util.Optional; + +public interface Registry { + + /** + * Returns the named transport layer security configuration. + * + * @param name the name + * @return the configuration, empty if not configured. + */ + Optional get(String name); + + /** + * Returns the default transport layer security configuration. + * + * @return the configuration, empty if not configured. + */ + Optional getDefault(); + + /** + * Registers a TLS configuration into the registry. + * Note that only subsequents calls to {@link #get(String)} will return the configuration. + *

+ * The passed configuration is not validated, so it's up to the caller to ensure the configuration is correct. + * + * @param name the name of the configuration, cannot be {@code null}, cannot be {@code }. + * @param configuration the configuration cannot be {@code null}. + */ + void register(String name, TlsConfiguration configuration); + +} diff --git a/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/TlsConfiguration.java b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/TlsConfiguration.java new file mode 100644 index 0000000000000..01daca0c6fb46 --- /dev/null +++ b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/TlsConfiguration.java @@ -0,0 +1,95 @@ +package io.quarkus.tls; + +import java.security.KeyStore; +import java.util.Optional; + +import javax.net.ssl.SSLContext; + +import io.quarkus.tls.runtime.keystores.TrustAllOptions; +import io.vertx.core.net.KeyCertOptions; +import io.vertx.core.net.SSLOptions; +import io.vertx.core.net.TrustOptions; + +/** + * The transport layer security configuration. + */ +public interface TlsConfiguration { + + /** + * Returns the key store. + * + * @return the key store if configured. + */ + KeyStore getKeyStore(); + + /** + * Returns the key store options. + * + * @return the key store options if configured. + */ + KeyCertOptions getKeyStoreOptions(); + + /** + * Returns the trust store. + * + * @return the trust store if configured. + */ + KeyStore getTrustStore(); + + /** + * Returns the trust store options. + * + * @return the trust store options if configured. + */ + TrustOptions getTrustStoreOptions(); + + /** + * Returns the (Vert.x) SSL options. + * + * @return the {@link SSLOptions}, {@code null} if not configured. + */ + SSLOptions getSSLOptions(); + + /** + * Creates and returns the SSL Context. + * + * @return the {@link SSLContext}, {@code null} if not configured. + * @throws Exception if the SSL Context cannot be created. + */ + SSLContext createSSLContext() throws Exception; + + /** + * Returns whether the trust store is configured to trust all certificates. + * + * @return {@code true} if the trust store is configured to trust all certificates, {@code false} otherwise. + */ + default boolean isTrustAll() { + return getTrustStoreOptions() == TrustAllOptions.INSTANCE; + } + + /** + * Returns the hostname verification algorithm for this configuration. + * {@code "NONE"} means no hostname verification. + * + * @return the hostname verification algorithm. + */ + Optional getHostnameVerificationAlgorithm(); + + /** + * Returns whether the key store is configured to use SNI. + * When SNI is used, the client indicate the server name during the TLS handshake, allowing the server to select the + * right certificate. + * + * @return {@code true} if the key store is configured to use SNI, {@code false} otherwise. + */ + boolean usesSni(); + + /** + * Reloads the configuration. + * It usually means reloading the key store and trust store, especially when they are files. + * + * @return {@code true} if the configuration has been reloaded, {@code false} otherwise. + */ + boolean reload(); + +} diff --git a/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/CertificateRecorder.java b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/CertificateRecorder.java new file mode 100644 index 0000000000000..58782029cc6b5 --- /dev/null +++ b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/CertificateRecorder.java @@ -0,0 +1,147 @@ +package io.quarkus.tls.runtime; + +import java.security.KeyStoreException; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; + +import jakarta.inject.Singleton; + +import io.quarkus.runtime.RuntimeValue; +import io.quarkus.runtime.annotations.Recorder; +import io.quarkus.tls.Registry; +import io.quarkus.tls.TlsConfiguration; +import io.quarkus.tls.runtime.config.KeyStoreConfig; +import io.quarkus.tls.runtime.config.TlsBucketConfig; +import io.quarkus.tls.runtime.config.TlsConfig; +import io.quarkus.tls.runtime.config.TrustStoreConfig; +import io.quarkus.tls.runtime.keystores.JKSKeyStores; +import io.quarkus.tls.runtime.keystores.P12KeyStores; +import io.quarkus.tls.runtime.keystores.PemKeyStores; +import io.quarkus.tls.runtime.keystores.TrustAllOptions; +import io.vertx.core.Vertx; + +@Recorder +@Singleton +public class CertificateRecorder implements Registry { + + private final Map certificates = new ConcurrentHashMap<>(); + + /** + * Validate the certificate configuration. + *

+ * Verify that each certificate file exists and that the key store and trust store are correctly configured. + * When aliases are set, aliases are validated. + * + * @param config the configuration + * @param vertx the Vert.x instance + */ + public void validateCertificates(TlsConfig config, RuntimeValue vertx) { + // Verify the default config + if (config.defaultCertificateConfig().isPresent()) { + verifyCertificateConfig(config.defaultCertificateConfig().get(), vertx.getValue(), TlsConfig.DEFAULT_NAME); + } + + // Verify the named config + for (String name : config.namedCertificateConfig().keySet()) { + verifyCertificateConfig(config.namedCertificateConfig().get(name), vertx.getValue(), name); + } + } + + public void verifyCertificateConfig(TlsBucketConfig config, Vertx vertx, String name) { + // Verify the key store + KeyStoreAndKeyCertOptions ks = null; + boolean sni; + if (config.keyStore().isPresent()) { + KeyStoreConfig keyStoreConfig = config.keyStore().get(); + ks = verifyKeyStore(keyStoreConfig, vertx, name); + sni = keyStoreConfig.sni(); + if (sni) { + try { + if (Collections.list(ks.keyStore.aliases()).size() <= 1) { + throw new IllegalStateException( + "The SNI option cannot be used when the keystore contains only one alias or the `alias` property has been set"); + } + } catch (KeyStoreException e) { + // Should not happen + throw new RuntimeException(e); + } + } + } + + // Verify the trust store + TrustStoreAndTrustOptions ts = null; + if (config.trustStore().isPresent()) { + ts = verifyTrustStore(config.trustStore().get(), vertx, name); + } + + if (config.trustAll() && ts != null) { + throw new IllegalStateException("The trust-all option cannot be used when a trust-store is configured"); + } else if (config.trustAll()) { + ts = new TrustStoreAndTrustOptions(null, TrustAllOptions.INSTANCE); + } + + certificates.put(name, new VertxCertificateHolder(vertx, name, config, ks, ts)); + } + + public static KeyStoreAndKeyCertOptions verifyKeyStore(KeyStoreConfig config, Vertx vertx, String name) { + config.validate(name); + + if (config.pem().isPresent()) { + return PemKeyStores.verifyPEMKeyStore(config, vertx, name); + } else if (config.p12().isPresent()) { + return P12KeyStores.verifyP12KeyStore(config, vertx, name); + } else if (config.jks().isPresent()) { + return JKSKeyStores.verifyJKSKeyStore(config, vertx, name); + } + return null; + } + + public static TrustStoreAndTrustOptions verifyTrustStore(TrustStoreConfig config, Vertx vertx, String name) { + config.validate(name); + + if (config.pem().isPresent()) { + return PemKeyStores.verifyPEMTrustStoreStore(config, vertx, name); + } else if (config.p12().isPresent()) { + return P12KeyStores.verifyP12TrustStoreStore(config, vertx, name); + } else if (config.jks().isPresent()) { + return JKSKeyStores.verifyJKSTrustStoreStore(config, vertx, name); + } + + return null; + } + + @Override + public Optional get(String name) { + return Optional.ofNullable(certificates.get(name)); + } + + @Override + public Optional getDefault() { + return get(TlsConfig.DEFAULT_NAME); + } + + @Override + public void register(String name, TlsConfiguration configuration) { + if (name == null) { + throw new IllegalArgumentException("The name of the TLS configuration to register cannot be null"); + } + if (name.equals(TlsConfig.DEFAULT_NAME)) { + throw new IllegalArgumentException("The name of the TLS configuration to register cannot be "); + } + if (configuration == null) { + throw new IllegalArgumentException("The TLS configuration to register cannot be null"); + } + certificates.put(name, configuration); + } + + public Supplier getSupplier() { + return () -> this; + } + + public void register(String name, Supplier supplier) { + register(name, supplier.get()); + } +} diff --git a/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/KeyStoreAndKeyCertOptions.java b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/KeyStoreAndKeyCertOptions.java new file mode 100644 index 0000000000000..2095b84502f2d --- /dev/null +++ b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/KeyStoreAndKeyCertOptions.java @@ -0,0 +1,18 @@ +package io.quarkus.tls.runtime; + +import java.security.KeyStore; + +import io.vertx.core.net.KeyCertOptions; + +/** + * A structure storing a key store and its associated Vert.x options. + */ +public final class KeyStoreAndKeyCertOptions { + public final KeyStore keyStore; + public final KeyCertOptions options; + + public KeyStoreAndKeyCertOptions(KeyStore keyStore, KeyCertOptions options) { + this.keyStore = keyStore; + this.options = options; + } +} diff --git a/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/TrustStoreAndTrustOptions.java b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/TrustStoreAndTrustOptions.java new file mode 100644 index 0000000000000..447e0076ba534 --- /dev/null +++ b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/TrustStoreAndTrustOptions.java @@ -0,0 +1,18 @@ +package io.quarkus.tls.runtime; + +import java.security.KeyStore; + +import io.vertx.core.net.TrustOptions; + +/** + * A structure storing a trust store and its associated Vert.x options. + */ +public final class TrustStoreAndTrustOptions { + public final KeyStore trustStore; + public final TrustOptions options; + + public TrustStoreAndTrustOptions(KeyStore keyStore, TrustOptions options) { + this.trustStore = keyStore; + this.options = options; + } +} diff --git a/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/VertxCertificateHolder.java b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/VertxCertificateHolder.java new file mode 100644 index 0000000000000..7c61a8c3ee28a --- /dev/null +++ b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/VertxCertificateHolder.java @@ -0,0 +1,194 @@ +package io.quarkus.tls.runtime; + +import java.nio.file.Path; +import java.security.KeyStore; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +import javax.net.ssl.*; + +import io.quarkus.tls.TlsConfiguration; +import io.quarkus.tls.runtime.config.KeyStoreConfig; +import io.quarkus.tls.runtime.config.TlsBucketConfig; +import io.quarkus.tls.runtime.config.TlsConfigUtils; +import io.vertx.core.Vertx; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.net.KeyCertOptions; +import io.vertx.core.net.SSLOptions; +import io.vertx.core.net.TrustOptions; + +public class VertxCertificateHolder implements TlsConfiguration { + + private final TlsBucketConfig config; + private final List crls; + private TrustOptions trustOptions; + private KeyStore trustStore; + private KeyCertOptions keyStoreOptions; + private KeyStore keyStore; + + private final Vertx vertx; + private final String name; + + VertxCertificateHolder(Vertx vertx, String name, TlsBucketConfig config, KeyStoreAndKeyCertOptions ks, + TrustStoreAndTrustOptions ts) { + this.config = config; + this.vertx = vertx; + this.name = name; + if (ks != null) { + keyStoreOptions = ks.options; + keyStore = ks.keyStore; + } else { + keyStoreOptions = null; + keyStore = null; + } + if (ts != null) { + trustOptions = ts.options; + trustStore = ts.trustStore; + } else { + trustOptions = null; + trustStore = null; + } + + crls = new ArrayList<>(); + if (config().certificateRevocationList().isPresent()) { + for (Path path : config().certificateRevocationList().get()) { + byte[] bytes = TlsConfigUtils.read(path); + crls.add(Buffer.buffer(bytes)); + } + } + } + + @Override + public synchronized KeyCertOptions getKeyStoreOptions() { + return keyStoreOptions; + } + + @Override + public synchronized KeyStore getKeyStore() { + return keyStore; + } + + @Override + public synchronized TrustOptions getTrustStoreOptions() { + return trustOptions; + } + + @Override + public synchronized KeyStore getTrustStore() { + return trustStore; + } + + @Override + public synchronized SSLContext createSSLContext() throws Exception { + KeyManagerFactory keyManagerFactory; + KeyManager[] keyManagers = null; + if (keyStoreOptions != null) { + keyManagerFactory = keyStoreOptions.getKeyManagerFactory(vertx); + keyManagers = keyManagerFactory.getKeyManagers(); + } + TrustManagerFactory trustManagerFactory; + TrustManager[] trustManagers = null; + if (trustOptions != null) { + trustManagerFactory = trustOptions.getTrustManagerFactory(vertx); + trustManagers = trustManagerFactory.getTrustManagers(); + } + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(keyManagers, trustManagers, new SecureRandom()); + return sslContext; + } + + @Override + public synchronized SSLOptions getSSLOptions() { + SSLOptions options = new SSLOptions(); + options.setKeyCertOptions(getKeyStoreOptions()); + options.setTrustOptions(getTrustStoreOptions()); + options.setUseAlpn(config().alpn()); + options.setSslHandshakeTimeoutUnit(TimeUnit.SECONDS); + options.setSslHandshakeTimeout(config().handshakeTimeout().toSeconds()); + options.setEnabledSecureTransportProtocols(config().protocols()); + + for (Buffer buffer : crls) { + options.addCrlValue(buffer); + } + + for (String cipher : config().cipherSuites().orElse(Collections.emptyList())) { + options.addEnabledCipherSuite(cipher); + } + + return options; + } + + @Override + public boolean isTrustAll() { + return config().trustAll(); + } + + @Override + public Optional getHostnameVerificationAlgorithm() { + return config.hostnameVerificationAlgorithm(); + } + + @Override + public boolean usesSni() { + return config.keyStore().map(KeyStoreConfig::sni).orElse(false); + } + + @Override + public boolean reload() { + if (keyStore == null && trustStore == null) { + return false; + } + + KeyStoreAndKeyCertOptions keyStoreUpdateResult = null; + TrustStoreAndTrustOptions trustStoreUpdateResult = null; + // Reload keystore + if (keyStore != null) { + try { + keyStoreUpdateResult = CertificateRecorder.verifyKeyStore(config.keyStore().orElseThrow(), vertx, name); + } catch (Exception e) { + return false; + } + } + + // Reload truststore + if (trustStore != null) { + try { + trustStoreUpdateResult = CertificateRecorder.verifyTrustStore(config.trustStore().orElseThrow(), vertx, name); + } catch (Exception e) { + return false; + } + } + + if (keyStoreUpdateResult == null && trustStoreUpdateResult == null) { + return false; + } + + // Also reload the revoked certificates + List newCRLs = new ArrayList<>(); + if (config().certificateRevocationList().isPresent()) { + for (Path path : config().certificateRevocationList().get()) { + byte[] bytes = TlsConfigUtils.read(path); + newCRLs.add(Buffer.buffer(bytes)); + } + } + + synchronized (this) { + keyStoreOptions = keyStoreUpdateResult != null ? keyStoreUpdateResult.options : null; + keyStore = keyStoreUpdateResult != null ? keyStoreUpdateResult.keyStore : null; + trustOptions = trustStoreUpdateResult != null ? trustStoreUpdateResult.options : null; + trustStore = trustStoreUpdateResult != null ? trustStoreUpdateResult.trustStore : null; + crls.clear(); + crls.addAll(newCRLs); + } + return true; + } + + public TlsBucketConfig config() { + return config; + } + +} diff --git a/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/JKSKeyStoreConfig.java b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/JKSKeyStoreConfig.java new file mode 100644 index 0000000000000..03f1c82fdbaeb --- /dev/null +++ b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/JKSKeyStoreConfig.java @@ -0,0 +1,38 @@ +package io.quarkus.tls.runtime.config; + +import java.nio.file.Path; +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigGroup; + +@ConfigGroup +public interface JKSKeyStoreConfig { + + /** + * Path to the keystore file (JKS format). + */ + Path path(); + + /** + * Password of the key store. + * When not set, the password must be retrieved from the credential provider. + */ + Optional password(); + + /** + * Alias of the private key and certificate in the key store. + */ + Optional alias(); + + /** + * Password of the alias in the key store. + * When not set, the password may be retrieved from the credential provider. + */ + Optional aliasPassword(); + + /** + * Provider of the key store. + */ + Optional provider(); + +} diff --git a/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/JKSTrustStoreConfig.java b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/JKSTrustStoreConfig.java new file mode 100644 index 0000000000000..8e3ecf6e11b99 --- /dev/null +++ b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/JKSTrustStoreConfig.java @@ -0,0 +1,32 @@ +package io.quarkus.tls.runtime.config; + +import java.nio.file.Path; +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigGroup; + +@ConfigGroup +public interface JKSTrustStoreConfig { + + /** + * Path to the trust store file (JKS format). + */ + Path path(); + + /** + * Password of the trust store. + * If not set, the password must be retrieved from the credential provider. + */ + Optional password(); + + /** + * Alias of the key in the trust store. + */ + Optional alias(); + + /** + * Provider of the trust store. + */ + Optional provider(); + +} diff --git a/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/KeyStoreConfig.java b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/KeyStoreConfig.java new file mode 100644 index 0000000000000..10973c4585f2f --- /dev/null +++ b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/KeyStoreConfig.java @@ -0,0 +1,64 @@ +package io.quarkus.tls.runtime.config; + +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigGroup; +import io.smallrye.config.WithDefault; + +@ConfigGroup +public interface KeyStoreConfig { + + /** + * Configures the PEM key/certificate pair. + */ + Optional pem(); + + /** + * Configure the PKCS12 key store. + */ + Optional p12(); + + /** + * Configure the JKS key store. + */ + Optional jks(); + + /** + * Enables Server Name Indication (SNI). + *

+ * Server Name Indication (SNI) is a TLS extension that allows a client to specify the hostname it is attempting to + * connect to during the TLS handshake. This enables a server to present different SSL certificates for multiple + * domains on a single IP address, facilitating secure communication for virtual hosting scenarios. + *

+ * With this setting enabled, the client indicate the server name during the TLS handshake, allowing the server to + * select the right certificate. + *

+ * When configuring the keystore with PEM files, multiple CRT/Key must be given. + * When configuring the keystore with a JKS or a P12 file, it selects one alias based on the SNI hostname. + * In this case, all the keystore password and alias password must be the same (configured with the {@code password} + * and {@code alias-password} properties. Do not set the {@code alias} property. + */ + @WithDefault("false") + boolean sni(); + + /** + * The credential provider configuration for the keys store. + * A credential provider offers a way to retrieve the key store password and alias password. + * Note that the credential provider is only used if the password / alias password are not set in the configuration. + */ + KeyStoreCredentialProviderConfig credentialsProvider(); + + default void validate(String name) { + if (pem().isPresent() && (p12().isPresent() || jks().isPresent())) { + throw new IllegalStateException( + "Invalid keystore '" + name + + "' - The keystore cannot be configured with PEM and PKCS12 or JKS at the same time"); + } + + if (p12().isPresent() && jks().isPresent()) { + throw new IllegalStateException( + "Invalid keystore '" + name + "' - The keystore cannot be configured with PKCS12 and JKS at the same time"); + } + } + +} diff --git a/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/KeyStoreCredentialProviderConfig.java b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/KeyStoreCredentialProviderConfig.java new file mode 100644 index 0000000000000..cfd0803892566 --- /dev/null +++ b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/KeyStoreCredentialProviderConfig.java @@ -0,0 +1,49 @@ +package io.quarkus.tls.runtime.config; + +import java.util.Optional; + +import io.quarkus.credentials.CredentialsProvider; +import io.quarkus.runtime.annotations.ConfigGroup; +import io.smallrye.config.WithDefault; + +@ConfigGroup +public interface KeyStoreCredentialProviderConfig { + + /** + * The name of the "credential" bucket (map key -> passwords) to retrieve from the + * {@link io.quarkus.credentials.CredentialsProvider}. If not set, the credential provider will not be used. + *

+ * A credential provider offers a way to retrieve the key store password as well as alias password. + * Note that the credential provider is only used if the passwords are not set in the configuration. + */ + Optional name(); + + /** + * The name of the bean providing the credential provider. + *

+ * The name is used to select the credential provider to use. + * The credential provider must be exposed as a CDI bean and with the {@code @Named} annotation set to the + * configured name to be selected. + *

+ * If not set, the default credential provider is used. + */ + Optional beanName(); + + /** + * The key used to retrieve the key store password. + *

+ * If the selected credential provider does not support the key, the password is not retrieved. + * Otherwise, the retrieved value is used to open the key store. + */ + @WithDefault(CredentialsProvider.PASSWORD_PROPERTY_NAME) + String passwordKey(); + + /** + * The key used to retrieve the key store alias password. + *

+ * If the selected credential provider does not contain the key, the alias password is not retrieved. + * Otherwise, the retrieved value is used to access the alias {@code private key} from the key store. + */ + @WithDefault("alias-password") + String aliasPasswordKey(); +} diff --git a/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/P12KeyStoreConfig.java b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/P12KeyStoreConfig.java new file mode 100644 index 0000000000000..ebce07dd3fb5e --- /dev/null +++ b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/P12KeyStoreConfig.java @@ -0,0 +1,37 @@ +package io.quarkus.tls.runtime.config; + +import java.nio.file.Path; +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigGroup; + +@ConfigGroup +public interface P12KeyStoreConfig { + + /** + * Path to the key store file (P12 / PFX format). + */ + Path path(); + + /** + * Password of the key store. + * When not set, the password must be retrieved from the credential provider. + */ + Optional password(); + + /** + * Alias of the private key and certificate in the key store. + */ + Optional alias(); + + /** + * Password of the alias in the key store. + * If not set, the password will be retrieved from the credential provider. + */ + Optional aliasPassword(); + + /** + * Provider of the key store. + */ + Optional provider(); +} diff --git a/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/P12TrustStoreConfig.java b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/P12TrustStoreConfig.java new file mode 100644 index 0000000000000..7f780524ffb40 --- /dev/null +++ b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/P12TrustStoreConfig.java @@ -0,0 +1,31 @@ +package io.quarkus.tls.runtime.config; + +import java.nio.file.Path; +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigGroup; + +@ConfigGroup +public interface P12TrustStoreConfig { + + /** + * Path to the trust store file (P12 / PFX format). + */ + Path path(); + + /** + * Password of the trust store. + * If not set, the password must be retrieved from the credential provider. + */ + Optional password(); + + /** + * Alias of the trust store. + */ + Optional alias(); + + /** + * Provider of the trust store. + */ + Optional provider(); +} diff --git a/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/PemCertsConfig.java b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/PemCertsConfig.java new file mode 100644 index 0000000000000..d4fc4f0b8ee79 --- /dev/null +++ b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/PemCertsConfig.java @@ -0,0 +1,46 @@ +package io.quarkus.tls.runtime.config; + +import static io.quarkus.tls.runtime.config.TlsConfigUtils.read; + +import java.nio.file.Path; +import java.util.List; +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigGroup; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.net.PemTrustOptions; + +@ConfigGroup +public interface PemCertsConfig { + + /** + * List of the trusted cert paths (Pem format). + */ + Optional> certs(); + + default PemTrustOptions toOptions() { + PemTrustOptions options = new PemTrustOptions(); + + if (certs().isEmpty()) { + throw new IllegalArgumentException("You must specify the key files and certificate files"); + } + for (Path path : certs().get()) { + options.addCertValue(Buffer.buffer(read(path))); + } + return options; + } + + interface KeyCertConfig { + + /** + * The path to the key file (in PEM format). + */ + Path key(); + + /** + * The path to the certificate file (in PEM format). + */ + Path cert(); + } + +} diff --git a/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/PemKeyCertConfig.java b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/PemKeyCertConfig.java new file mode 100644 index 0000000000000..c1c6c52a045a5 --- /dev/null +++ b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/PemKeyCertConfig.java @@ -0,0 +1,49 @@ +package io.quarkus.tls.runtime.config; + +import static io.quarkus.tls.runtime.config.TlsConfigUtils.read; + +import java.nio.file.Path; +import java.util.List; +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigGroup; +import io.smallrye.config.WithParentName; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.net.PemKeyCertOptions; + +@ConfigGroup +public interface PemKeyCertConfig { + + /** + * List of the PEM key/cert files (Pem format). + */ + @WithParentName + Optional> keyCerts(); + + default PemKeyCertOptions toOptions() { + PemKeyCertOptions options = new PemKeyCertOptions(); + + if (keyCerts().isEmpty()) { + throw new IllegalArgumentException("You must specify the key files and certificate files"); + } + for (KeyCertConfig config : keyCerts().get()) { + options.addCertValue(Buffer.buffer(read(config.cert()))); + options.addKeyValue(Buffer.buffer(read(config.key()))); + } + return options; + } + + interface KeyCertConfig { + + /** + * The path to the key file (in PEM format). + */ + Path key(); + + /** + * The path to the certificate file (in PEM format). + */ + Path cert(); + } + +} diff --git a/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/TlsBucketConfig.java b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/TlsBucketConfig.java new file mode 100644 index 0000000000000..4e1cfc9745d25 --- /dev/null +++ b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/TlsBucketConfig.java @@ -0,0 +1,109 @@ +package io.quarkus.tls.runtime.config; + +import java.nio.file.Path; +import java.time.Duration; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import io.quarkus.runtime.annotations.ConfigGroup; +import io.smallrye.config.WithDefault; + +@ConfigGroup +public interface TlsBucketConfig { + + /** + * The key store configuration. + * Key stores are used to store private keys and their associated X.509 certificate chains. + * For example, for {@code HTTPS}, it stores the server's private key and the server's certificate. + * The certificate is used to prove the server's identity to the client. + */ + Optional keyStore(); + + /** + * The trust store configuration. + * Trust stores are used to store certificates from trusted entities. + * For example, for {@code HTTPS}, it stores the certificate authorities that are trusted by the server. + * The server uses the trust store to verify the client's certificate when mTLS (client authentication) is enabled. + */ + Optional trustStore(); + + /** + * Sets the ordered list of enabled cipher suites. + * s * If none is given, a reasonable default is selected from the built-in ciphers. + *

+ * When suites are set, it takes precedence over the default suite defined by the {@code SSLEngineOptions} in use. + */ + Optional> cipherSuites(); + + /** + * Sets the ordered list of enabled TLS protocols. + *

+ * If not set, it defaults to {@code "TLSv1.3, TLSv1.2"}. + * The following list of protocols are supported: {@code TLSv1, TLSv1.1, TLSv1.2, TLSv1.3}. + * To only enable {@code TLSv1.3}, set the value to {@code to "TLSv1.3"}. + *

+ * Note that setting an empty list, and enabling TLS is invalid. + * You must at least have one protocol. + *

+ * Also, setting this replaces the default list of protocols. + */ + @WithDefault("TLSv1.3,TLSv1.2") + Set protocols(); + + /** + * The timeout for the TLS handshake phase. + *

+ * If not set, it defaults to 10 seconds. + */ + @WithDefault("10S") + Duration handshakeTimeout(); + + /** + * Enables the Application-Layer Protocol Negotiation (ALPN). + *

+ * Application-Layer Protocol Negotiation is a TLS extension that allows the client and server during the TLS + * handshake to negotiate which protocol they will use for communication. ALPN enables more efficient communication + * by allowing the client to indicate its preferred application protocol to the server before the TLS connection is + * established. This helps in scenarios such as HTTP/2 where multiple protocols may be available, allowing for + * faster protocol selection. + */ + @WithDefault("false") + boolean alpn(); + + /** + * Sets the list of revoked certificates (paths to files). + *

+ * A Certificate Revocation List (CRL) is a list of digital certificates that have been revoked by the issuing + * Certificate Authority (CA) before their scheduled expiration date. When a certificate is compromised, no + * longer needed, or deemed invalid for any reason, the CA adds it to the CRL to inform relying parties not to + * trust the certificate anymore. + *

+ * Two formats are allowed: DER and PKCS#7 (also known as P7B). + * When using the DER format, you must pass DER-encoded CRLs. + * When using the PKCS#7 format, you must pass PKCS#7 {@code SignedData} object, with the only significant field + * being {@code crls}. + */ + Optional> certificateRevocationList(); + + /** + * If set to {@code true}, the server trusts all certificates. + *

+ * This is useful for testing, but should not be used in production. + */ + @WithDefault("false") + boolean trustAll(); + + /** + * The hostname verification algorithm to use in case the server's identity should be checked. + * Should be {@code HTTPS} (default), {@code LDAPS} or an {@code NONE}. + *

+ * If set to {@code NONE}, it does not verify the hostname. + *

+ * If not set, the configured extension decides the default algorithm to use. + * For example, for HTTP, it will be "HTTPS". For TCP, it can depend on the protocol. + * Nevertheless, it is recommended to set it to "HTTPS" or "LDAPS". + */ + Optional hostnameVerificationAlgorithm(); + +} diff --git a/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/TlsConfig.java b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/TlsConfig.java new file mode 100644 index 0000000000000..ab0a77134bcb5 --- /dev/null +++ b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/TlsConfig.java @@ -0,0 +1,30 @@ +package io.quarkus.tls.runtime.config; + +import java.util.Map; +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigDocMapKey; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; +import io.smallrye.config.ConfigMapping; +import io.smallrye.config.WithParentName; + +@ConfigMapping(prefix = "quarkus.tls") +@ConfigRoot(phase = ConfigPhase.RUN_TIME) +public interface TlsConfig { + + String DEFAULT_NAME = ""; + + /** + * The default TLS bucket configuration + */ + @WithParentName + Optional defaultCertificateConfig(); + + /** + * Configures additional (named) TLS bucket configurations. + */ + @WithParentName + @ConfigDocMapKey("tls-bucket-name") + Map namedCertificateConfig(); +} diff --git a/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/TlsConfigUtils.java b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/TlsConfigUtils.java new file mode 100644 index 0000000000000..05140f08e5ec1 --- /dev/null +++ b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/TlsConfigUtils.java @@ -0,0 +1,45 @@ +package io.quarkus.tls.runtime.config; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import io.quarkus.runtime.util.ClassPathUtils; + +public class TlsConfigUtils { + + private TlsConfigUtils() { + // Avoid direct instantiation + } + + /** + * Read the content of the path. + *

+ * The file is read from the classpath if it exists, otherwise it is read from the file system. + * + * @param path the path, must not be {@code null} + * @return the content of the file + */ + public static byte[] read(Path path) { + byte[] data; + try { + final InputStream resource = Thread.currentThread().getContextClassLoader() + .getResourceAsStream(ClassPathUtils.toResourceName(path)); + if (resource != null) { + try (InputStream is = resource) { + data = is.readAllBytes(); + } + } else { + try (InputStream is = Files.newInputStream(path)) { + data = is.readAllBytes(); + } + } + } catch (IOException e) { + throw new UncheckedIOException("Unable to read file " + path, e); + } + return data; + } + +} diff --git a/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/TrustStoreConfig.java b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/TrustStoreConfig.java new file mode 100644 index 0000000000000..7d7d75df0ffc0 --- /dev/null +++ b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/TrustStoreConfig.java @@ -0,0 +1,46 @@ +package io.quarkus.tls.runtime.config; + +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigGroup; + +@ConfigGroup +public interface TrustStoreConfig { + + /** + * Configures the list of trusted certificates. + */ + Optional pem(); + + /** + * Configure the PKCS12 trust store. + */ + Optional p12(); + + /** + * Configure the JKS trust store. + */ + Optional jks(); + + /** + * The credential provider configuration for the trust store. + * A credential provider offers a way to retrieve the trust store password. + * Note that the credential provider is only used if the password is not set in the configuration. + */ + TrustStoreCredentialProviderConfig credentialsProvider(); + + default void validate(String name) { + if (pem().isPresent() && (p12().isPresent() || jks().isPresent())) { + throw new IllegalStateException( + "Invalid truststore '" + name + + "' - The truststore cannot be configured with PEM and PKCS12 or JKS at the same time"); + } + + if (p12().isPresent() && jks().isPresent()) { + throw new IllegalStateException( + "Invalid truststore '" + name + + "' - The truststore cannot be configured with PKCS12 and JKS at the same time"); + } + } + +} diff --git a/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/TrustStoreCredentialProviderConfig.java b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/TrustStoreCredentialProviderConfig.java new file mode 100644 index 0000000000000..f1a9e3c93ea22 --- /dev/null +++ b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/config/TrustStoreCredentialProviderConfig.java @@ -0,0 +1,41 @@ +package io.quarkus.tls.runtime.config; + +import java.util.Optional; + +import io.quarkus.credentials.CredentialsProvider; +import io.quarkus.runtime.annotations.ConfigGroup; +import io.smallrye.config.WithDefault; + +@ConfigGroup +public interface TrustStoreCredentialProviderConfig { + + /** + * The name of the "credential" bucket (map key -> passwords) to retrieve from the + * {@link io.quarkus.credentials.CredentialsProvider}. If not set, the credential provider will not be used. + *

+ * A credential provider offers a way to retrieve the key store password as well as alias password. + * Note that the credential provider is only used if the passwords are not set in the configuration. + */ + Optional name(); + + /** + * The name of the bean providing the credential provider. + *

+ * The name is used to select the credential provider to use. + * The credential provider must be exposed as a CDI bean and with the {@code @Named} annotation set to the + * configured name to be selected. + *

+ * If not set, the default credential provider is used. + */ + Optional beanName(); + + /** + * The key used to retrieve the trust store password. + *

+ * If the selected credential provider does not contain the configured key, the password is not retrieved. + * Otherwise, the retrieved value is used to open the trust store. + */ + @WithDefault(CredentialsProvider.PASSWORD_PROPERTY_NAME) + String passwordKey(); + +} diff --git a/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/keystores/CredentialProviders.java b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/keystores/CredentialProviders.java new file mode 100644 index 0000000000000..60bdeab4b0620 --- /dev/null +++ b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/keystores/CredentialProviders.java @@ -0,0 +1,75 @@ +package io.quarkus.tls.runtime.keystores; + +import java.util.Map; +import java.util.Optional; + +import jakarta.enterprise.inject.literal.NamedLiteral; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.InstanceHandle; +import io.quarkus.credentials.CredentialsProvider; +import io.quarkus.tls.runtime.config.KeyStoreCredentialProviderConfig; +import io.quarkus.tls.runtime.config.TrustStoreCredentialProviderConfig; + +public class CredentialProviders { + + public static Optional getKeyStorePassword(Optional maybePasswordFromConfig, + KeyStoreCredentialProviderConfig config) { + if (maybePasswordFromConfig.isPresent()) { + return maybePasswordFromConfig; + } + if (config.name().isPresent()) { + CredentialsProvider provider = lookup(config.beanName().orElse(null)); + Map credentials = provider.getCredentials(config.name().get()); + return Optional.ofNullable(credentials.get(config.passwordKey())); + } + return Optional.empty(); + } + + public static Optional getAliasPassword(Optional maybePasswordFromConfig, + KeyStoreCredentialProviderConfig config) { + if (maybePasswordFromConfig.isPresent()) { + return maybePasswordFromConfig; + } + if (config.name().isPresent()) { + CredentialsProvider provider = lookup(config.beanName().orElse(null)); + Map credentials = provider.getCredentials(config.name().get()); + return Optional.ofNullable(credentials.get(config.aliasPasswordKey())); + } + return Optional.empty(); + } + + public static Optional getTrustStorePassword(Optional maybePasswordFromConfig, + TrustStoreCredentialProviderConfig config) { + if (maybePasswordFromConfig.isPresent()) { + return maybePasswordFromConfig; + } + if (config.name().isPresent()) { + CredentialsProvider provider = lookup(config.beanName().orElse(null)); + Map credentials = provider.getCredentials(config.name().get()); + return Optional.ofNullable(credentials.get(config.passwordKey())); + } + return Optional.empty(); + } + + static CredentialsProvider lookup(String name) { + ArcContainer container = Arc.container(); + InstanceHandle instance; + if (name == null) { + instance = container.instance(CredentialsProvider.class); + } else { + instance = container.instance(CredentialsProvider.class, NamedLiteral.of(name)); + } + + if (!instance.isAvailable()) { + if (name == null) { + throw new RuntimeException("Unable to find the default credentials provider"); + } else { + throw new RuntimeException("Unable to find the credentials provider named '" + name + "'"); + } + } + + return instance.get(); + } +} diff --git a/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/keystores/JKSKeyStores.java b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/keystores/JKSKeyStores.java new file mode 100644 index 0000000000000..7b95486e9780f --- /dev/null +++ b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/keystores/JKSKeyStores.java @@ -0,0 +1,149 @@ +package io.quarkus.tls.runtime.keystores; + +import static io.quarkus.tls.runtime.config.TlsConfigUtils.read; + +import java.io.UncheckedIOException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; + +import io.quarkus.tls.runtime.KeyStoreAndKeyCertOptions; +import io.quarkus.tls.runtime.TrustStoreAndTrustOptions; +import io.quarkus.tls.runtime.config.*; +import io.vertx.core.Vertx; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.net.JksOptions; + +/** + * A utility class to validate JKS key store and trust store configurations. + */ +public class JKSKeyStores { + + private JKSKeyStores() { + // Avoid direct instantiation + } + + public static KeyStoreAndKeyCertOptions verifyJKSKeyStore(KeyStoreConfig config, Vertx vertx, String name) { + var jksKeyStoreConfig = config.jks().orElseThrow(); + JksOptions options = toOptions(jksKeyStoreConfig, config.credentialsProvider(), name); + KeyStore ks = loadKeyStore(vertx, name, options, "key"); + verifyKeyStoreAlias(options, name, ks); + return new KeyStoreAndKeyCertOptions(ks, options); + } + + public static TrustStoreAndTrustOptions verifyJKSTrustStoreStore(TrustStoreConfig config, Vertx vertx, + String name) { + JKSTrustStoreConfig jksConfig = config.jks().orElseThrow(); + JksOptions options = toOptions(jksConfig, config.credentialsProvider(), name); + KeyStore ks = loadKeyStore(vertx, name, options, "trust"); + verifyTrustStoreAlias(options, name, ks); + return new TrustStoreAndTrustOptions(ks, options); + } + + private static JksOptions toOptions(JKSKeyStoreConfig config, + KeyStoreCredentialProviderConfig keyStoreCredentialProviderConfig, String name) { + JksOptions options = new JksOptions(); + try { + options.setValue(Buffer.buffer(read(config.path()))); + String p = CredentialProviders.getKeyStorePassword(config.password(), keyStoreCredentialProviderConfig) + .orElse(null); + if (p == null) { + throw new IllegalArgumentException("Invalid JKS key store configuration for certificate '" + name + + "' - the key store password is not set and cannot be retrieved from the credential provider."); + } + options.setPassword(p); + config.alias().ifPresent(options::setAlias); + String ap = CredentialProviders.getAliasPassword(config.aliasPassword(), keyStoreCredentialProviderConfig) + .orElse(null); + options.setAliasPassword(ap); + return options; + } catch (UncheckedIOException e) { + throw new IllegalStateException("Invalid JKS key store configuration for certificate '" + name + + "' - cannot read the key store file '" + config.path() + "'", e); + } catch (Exception e) { + throw new IllegalStateException("Invalid JKS key store configuration for certificate '" + name + "'", e); + } + } + + private static JksOptions toOptions(JKSTrustStoreConfig config, + TrustStoreCredentialProviderConfig trustStoreCredentialProviderConfig, String name) { + JksOptions options = new JksOptions(); + try { + options.setValue(Buffer.buffer(read(config.path()))); + String password = CredentialProviders.getTrustStorePassword(config.password(), trustStoreCredentialProviderConfig) + .orElse(null); + if (password == null) { + throw new IllegalStateException("Invalid JKS trust store configuration for certificate '" + name + + "' - the trust store password is not set and cannot be retrieved from the credential provider."); + } + options.setPassword(password); + config.alias().ifPresent(options::setAlias); + } catch (UncheckedIOException e) { + throw new IllegalStateException("Invalid JKS trust store configuration for certificate '" + name + + "' - cannot read the trust store file '" + config.path() + "'", e); + } catch (Exception e) { + throw new IllegalStateException("Invalid JKS trust store configuration for certificate '" + name + "'", e); + } + return options; + } + + private static void verifyKeyStoreAlias(JksOptions options, String name, KeyStore ks) { + String alias = options.getAlias(); + // Credential provider already called. + String aliasPassword = options.getAliasPassword(); + if (alias != null) { + try { + if (ks.getCertificate(alias) == null) { + throw new IllegalStateException( + "Alias '" + alias + "' not found in JKS key store (certificate not found)'" + name + "'"); + } + } catch (KeyStoreException e) { + throw new IllegalStateException("Unable to verify alias '" + alias + "' in JKS key store '" + name + "'", e); + } + + char[] ap = null; + if (aliasPassword != null) { + ap = aliasPassword.toCharArray(); + } + + try { + if (ks.getKey(alias, ap) == null) { + throw new IllegalStateException( + "Alias '" + alias + "' not found in JKS key store (private key not found)'" + name + "'"); + } + if (ks.getCertificate(alias) == null) { + throw new IllegalStateException( + "Alias '" + alias + "' not found in JKS key store (certificate not found)'" + name + "'"); + } + } catch (KeyStoreException | NoSuchAlgorithmException e) { + throw new IllegalStateException("Unable to verify alias '" + alias + "' in JKS key store '" + name + "'", e); + } catch (UnrecoverableKeyException e) { + throw new IllegalArgumentException( + "Unable to recover the key for alias '" + alias + "' in JKS key store '" + name + "'", e); + } + } + } + + private static void verifyTrustStoreAlias(JksOptions options, String name, KeyStore ks) { + String alias = options.getAlias(); + if (alias != null) { + try { + if (ks.getCertificate(alias) == null) { + throw new IllegalStateException( + "Alias '" + alias + "' not found in JKS trust store (certificate not found)'" + name + "'"); + } + } catch (KeyStoreException e) { + throw new IllegalStateException("Unable to verify alias '" + alias + "' in JKS trust store '" + name + "'", e); + } + } + } + + private static KeyStore loadKeyStore(Vertx vertx, String name, JksOptions options, String type) { + try { + return options.loadKeyStore(vertx); + } catch (Exception e) { + throw new IllegalStateException("Unable to load JKS " + type + " store '" + name + "', verify the password.", e); + } + } +} diff --git a/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/keystores/P12KeyStores.java b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/keystores/P12KeyStores.java new file mode 100644 index 0000000000000..0a55fedbc582d --- /dev/null +++ b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/keystores/P12KeyStores.java @@ -0,0 +1,148 @@ +package io.quarkus.tls.runtime.keystores; + +import static io.quarkus.tls.runtime.config.TlsConfigUtils.read; + +import java.io.UncheckedIOException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.util.Optional; + +import io.quarkus.tls.runtime.KeyStoreAndKeyCertOptions; +import io.quarkus.tls.runtime.TrustStoreAndTrustOptions; +import io.quarkus.tls.runtime.config.*; +import io.vertx.core.Vertx; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.net.PfxOptions; + +/** + * A utility class to validate P12 key store and trust store configurations. + */ +public class P12KeyStores { + + private P12KeyStores() { + // Avoid direct instantiation + } + + public static KeyStoreAndKeyCertOptions verifyP12KeyStore(KeyStoreConfig ksc, Vertx vertx, String name) { + P12KeyStoreConfig config = ksc.p12().orElseThrow(); + PfxOptions options = toOptions(config, ksc.credentialsProvider(), name); + KeyStore ks = loadKeyStore(vertx, name, options, "key"); + verifyKeyStoreAlias(options, name, ks); + return new KeyStoreAndKeyCertOptions(ks, options); + } + + public static TrustStoreAndTrustOptions verifyP12TrustStoreStore(TrustStoreConfig config, Vertx vertx, String name) { + P12TrustStoreConfig p12Config = config.p12().orElseThrow(); + PfxOptions options = toOptions(p12Config, config.credentialsProvider(), name); + KeyStore ks = loadKeyStore(vertx, name, options, "trust"); + verifyTrustStoreAlias(p12Config.alias(), name, ks); + return new TrustStoreAndTrustOptions(ks, options); + } + + private static PfxOptions toOptions(P12KeyStoreConfig config, KeyStoreCredentialProviderConfig pc, String name) { + PfxOptions options = new PfxOptions(); + try { + options.setValue(Buffer.buffer(read(config.path()))); + String password = CredentialProviders.getKeyStorePassword(config.password(), pc) + .orElse(null); + if (password == null) { + throw new IllegalStateException("Invalid P12 key store configuration for certificate '" + name + + "' - the key store password is not set and cannot be retrieved from the credential provider."); + } + options.setPassword(password); + config.alias().ifPresent(options::setAlias); + String ap = CredentialProviders.getAliasPassword(config.aliasPassword(), pc).orElse(null); + options.setAliasPassword(ap); + } catch (UncheckedIOException e) { + throw new IllegalStateException("Invalid P12 key store configuration for certificate '" + name + + "' - cannot read the key store file '" + config.path() + "'", e); + } catch (Exception e) { + throw new IllegalStateException("Invalid P12 key store configuration for certificate '" + name + "'", e); + } + return options; + } + + private static PfxOptions toOptions(P12TrustStoreConfig config, TrustStoreCredentialProviderConfig cp, String name) { + PfxOptions options = new PfxOptions(); + try { + options.setValue(Buffer.buffer(read(config.path()))); + String password = CredentialProviders.getTrustStorePassword(config.password(), cp) + .orElse(null); + if (password == null) { + throw new IllegalStateException("Invalid P12 trust store configuration for certificate '" + name + + "' - the trust store password is not set and cannot be retrieved from the credential provider."); + } + options.setPassword(password); + config.alias().ifPresent(options::setAlias); + } catch (UncheckedIOException e) { + throw new IllegalStateException("Invalid P12 trust store configuration for certificate '" + name + + "' - cannot read the trust store file '" + config.path() + "'", e); + } catch (Exception e) { + throw new IllegalStateException("Invalid P12 trust store configuration for certificate '" + name + "'", e); + } + return options; + } + + private static void verifyKeyStoreAlias(PfxOptions options, String name, + KeyStore ks) { + String alias = options.getAlias(); + String aliasPassword = options.getAliasPassword(); + if (alias != null) { + try { + if (ks.getCertificate(alias) == null) { + throw new IllegalStateException( + "Alias '" + alias + "' not found in P12 key store (certificate not found)'" + name + "'"); + } + } catch (KeyStoreException e) { + throw new IllegalStateException("Unable to verify alias '" + alias + "' in P12 key store '" + name + "'", e); + } + + char[] pwd = null; + if (aliasPassword != null) { + pwd = aliasPassword.toCharArray(); + } + + try { + if (ks.getKey(alias, pwd) == null) { + throw new IllegalStateException( + "Alias '" + alias + "' not found in P12 key store (private key not found)'" + name + "'"); + } + if (ks.getCertificate(alias) == null) { + throw new IllegalStateException( + "Alias '" + alias + "' not found in P12 key store (certificate not found)'" + name + "'"); + } + } catch (KeyStoreException | NoSuchAlgorithmException e) { + throw new IllegalStateException("Unable to verify alias '" + alias + "' in P12 key store '" + name + "'", e); + } catch (UnrecoverableKeyException e) { + throw new IllegalArgumentException( + "Unable to recover the key for alias '" + alias + "' in P12 key store '" + name + "'", e); + } + } + } + + private static void verifyTrustStoreAlias(Optional maybeAlias, String name, KeyStore ks) { + if (maybeAlias.isPresent()) { + String alias = maybeAlias.get(); + try { + if (ks.getCertificate(alias) == null) { + throw new IllegalStateException( + "Alias '" + alias + "' not found in P12 trust store (certificate not found)'" + name + "'"); + } + } catch (KeyStoreException e) { + throw new IllegalStateException("Unable to verify alias '" + alias + "' in P12 trust store '" + name + "'", e); + } + } + } + + private static KeyStore loadKeyStore(Vertx vertx, String name, PfxOptions options, String type) { + KeyStore ks; + try { + ks = options.loadKeyStore(vertx); + } catch (Exception e) { + throw new IllegalStateException("Unable to load P12 " + type + " store '" + name + "', verify the password.", e); + } + return ks; + } +} diff --git a/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/keystores/PemKeyStores.java b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/keystores/PemKeyStores.java new file mode 100644 index 0000000000000..69041ca8c7251 --- /dev/null +++ b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/keystores/PemKeyStores.java @@ -0,0 +1,55 @@ +package io.quarkus.tls.runtime.keystores; + +import java.io.UncheckedIOException; +import java.security.KeyStore; + +import io.quarkus.tls.runtime.KeyStoreAndKeyCertOptions; +import io.quarkus.tls.runtime.TrustStoreAndTrustOptions; +import io.quarkus.tls.runtime.config.KeyStoreConfig; +import io.quarkus.tls.runtime.config.PemKeyCertConfig; +import io.quarkus.tls.runtime.config.TrustStoreConfig; +import io.vertx.core.Vertx; +import io.vertx.core.net.PemKeyCertOptions; + +/** + * A utility class to validate PEM key store and trust store configurations. + */ +public class PemKeyStores { + + private PemKeyStores() { + // Avoid direct instantiation + } + + public static KeyStoreAndKeyCertOptions verifyPEMKeyStore(KeyStoreConfig ksc, Vertx vertx, String name) { + PemKeyCertConfig config = ksc.pem().orElseThrow(); + if (config.keyCerts().isEmpty() || config.keyCerts().get().isEmpty()) { + throw new IllegalStateException("No key/certificate pair configured for certificate '" + name + "'"); + } + try { + PemKeyCertOptions options = config.toOptions(); + return new KeyStoreAndKeyCertOptions(options.loadKeyStore(vertx), options); + } catch (UncheckedIOException e) { + throw new IllegalStateException("Invalid key/certificate pair configuration for certificate '" + name + + "' - cannot read the key/certificate files", e); + } catch (Exception e) { + throw new IllegalStateException("Invalid key/certificate pair configuration for certificate '" + name + "'", e); + } + } + + public static TrustStoreAndTrustOptions verifyPEMTrustStoreStore(TrustStoreConfig tsc, Vertx vertx, String name) { + var config = tsc.pem().orElseThrow(); + if (config.certs().isEmpty() || config.certs().get().isEmpty()) { + throw new IllegalStateException("No PEM certificates configured for the trust store of '" + name + "'"); + } + try { + var options = config.toOptions(); + KeyStore keyStore = options.loadKeyStore(vertx); + return new TrustStoreAndTrustOptions(keyStore, options); + } catch (UncheckedIOException e) { + throw new IllegalStateException("Invalid PEM trusted certificates configuration for certificate '" + name + + "' - cannot read the PEM certificate files", e); + } catch (Exception e) { + throw new IllegalStateException("Invalid PEM trusted certificates configuration for certificate '" + name + "'", e); + } + } +} diff --git a/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/keystores/TrustAllOptions.java b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/keystores/TrustAllOptions.java new file mode 100644 index 0000000000000..ad99870aeafc1 --- /dev/null +++ b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/runtime/keystores/TrustAllOptions.java @@ -0,0 +1,69 @@ +package io.quarkus.tls.runtime.keystores; + +import java.security.KeyStore; +import java.security.Provider; +import java.security.cert.X509Certificate; +import java.util.function.Function; + +import javax.net.ssl.*; + +import io.vertx.core.Vertx; +import io.vertx.core.net.TrustOptions; + +public class TrustAllOptions implements TrustOptions { + + public static TrustAllOptions INSTANCE = new TrustAllOptions(); + + private static final TrustManager TRUST_ALL_MANAGER = new X509TrustManager() { + @Override + public void checkClientTrusted(X509Certificate[] x509Certificates, String s) { + } + + @Override + public void checkServerTrusted(X509Certificate[] x509Certificates, String s) { + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + }; + + private static final Provider PROVIDER = new Provider("", "0.0", "") { + + }; + + private TrustAllOptions() { + // Avoid direct instantiation. + } + + @Override + public TrustOptions copy() { + return this; + } + + @Override + public TrustManagerFactory getTrustManagerFactory(Vertx vertx) { + return new TrustManagerFactory(new TrustManagerFactorySpi() { + @Override + protected void engineInit(KeyStore keyStore) { + } + + @Override + protected void engineInit(ManagerFactoryParameters managerFactoryParameters) { + } + + @Override + protected TrustManager[] engineGetTrustManagers() { + return new TrustManager[] { TRUST_ALL_MANAGER }; + } + }, PROVIDER, "") { + + }; + } + + @Override + public Function trustManagerMapper(Vertx vertx) { + return name -> new TrustManager[] { TRUST_ALL_MANAGER }; + } +} diff --git a/extensions/tls-registry/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/tls-registry/runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 0000000000000..81db75cb09125 --- /dev/null +++ b/extensions/tls-registry/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,10 @@ +--- +artifact: ${project.groupId}:${project.artifactId}:${project.version} +name: "TLS certificate registry" +metadata: + categories: + - "web" + status: "stable" + unlisted: true + config: + - "quarkus.tls." diff --git a/versions.txt b/versions.txt new file mode 100644 index 0000000000000..d9679e30c87b7 --- /dev/null +++ b/versions.txt @@ -0,0 +1,67 @@ +3.0.0.Alpha4 +3.0.0.Alpha5 +3.0.0.Alpha6 +3.0.0.Beta1 +3.0.0.CR1 +3.0.0.CR2 +3.0.0.Final +3.0.1.Final +3.0.2.Final +3.0.3.Final +3.0.4.Final +3.1.0.CR1 +3.1.0.Final +3.1.1.Final +3.1.2.Final +3.1.3.Final +3.2.0.CR1 +3.2.0.Final +3.2.1.Final +3.2.10.Final +3.2.11.Final +3.2.2.Final +3.2.3.Final +3.2.4.Final +3.2.5.Final +3.2.6.Final +3.2.7.Final +3.2.8.Final +3.2.9.Final +3.3.0 +3.3.0.CR1 +3.3.1 +3.3.2 +3.3.3 +3.4.0 +3.4.0.CR1 +3.4.1 +3.4.2 +3.4.3 +3.5.0 +3.5.0.CR1 +3.5.1 +3.5.2 +3.5.3 +3.6.0 +3.6.0.CR1 +3.6.1 +3.6.2 +3.6.3 +3.6.4 +3.6.5 +3.6.6 +3.6.7 +3.6.8 +3.6.9 +3.7.0 +3.7.0.CR1 +3.7.1 +3.7.2 +3.7.3 +3.7.4 +3.8.0 +3.8.0.CR1 +3.8.1 +3.8.2 +3.9.0.CR1 +3.9.0.CR2