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