From d67f86dc2f379d95f409b4ef464590da485cbf26 Mon Sep 17 00:00:00 2001 From: JuanC Galvis <8420868+juancgalvis@users.noreply.github.com> Date: Fri, 27 Dec 2024 15:57:37 -0500 Subject: [PATCH] feat(tls): ssl default options (#128) * feat(tls): use default sslcontext when ssl is enabled, and allow ssl mapping from properties in rabbitmq --- .../async/rabbit/RabbitMQSetupUtils.java | 131 +++++++++++++++++- 1 file changed, 126 insertions(+), 5 deletions(-) diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/RabbitMQSetupUtils.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/RabbitMQSetupUtils.java index a88dde9..c97c51f 100644 --- a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/RabbitMQSetupUtils.java +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/RabbitMQSetupUtils.java @@ -16,6 +16,7 @@ import org.reactivecommons.async.rabbit.config.ConnectionFactoryProvider; import org.reactivecommons.async.rabbit.config.RabbitProperties; import org.reactivecommons.async.rabbit.config.props.AsyncProps; +import org.reactivecommons.async.rabbit.config.spring.RabbitPropertiesBase; import org.springframework.boot.context.properties.PropertyMapper; import reactor.core.publisher.Mono; import reactor.rabbitmq.ChannelPool; @@ -29,7 +30,24 @@ import reactor.rabbitmq.Utils; import reactor.util.retry.Retry; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; import java.time.Duration; +import java.util.Arrays; import java.util.logging.Level; @Log @@ -37,6 +55,25 @@ public class RabbitMQSetupUtils { private static final String LISTENER_TYPE = "listener"; private static final String SENDER_TYPE = "sender"; + private static final String DEFAULT_PROTOCOL; + public static final int START_INTERVAL = 300; + public static final int MAX_BACKOFF_INTERVAL = 3000; + + static { + String protocol = "TLSv1.1"; + try { + String[] protocols = SSLContext.getDefault().getSupportedSSLParameters().getProtocols(); + for (String prot : protocols) { + if ("TLSv1.2".equals(prot)) { + protocol = "TLSv1.2"; + break; + } + } + } catch (NoSuchAlgorithmException e) { + // nothing + } + DEFAULT_PROTOCOL = protocol; + } @SneakyThrows public static ConnectionFactoryProvider connectionFactoryProvider(RabbitProperties properties) { @@ -48,9 +85,7 @@ public static ConnectionFactoryProvider connectionFactoryProvider(RabbitProperti map.from(properties::determinePassword).whenNonNull().to(factory::setPassword); map.from(properties::determineVirtualHost).whenNonNull().to(factory::setVirtualHost); factory.useNio(); - if (properties.getSsl() != null && properties.getSsl().isEnabled()) { - factory.useSslProtocol(); - } + setUpSSL(factory, properties); return () -> factory; } @@ -118,9 +153,95 @@ private static Mono createConnectionMono(ConnectionFactory factory, log.log(Level.SEVERE, "Error creating connection to RabbitMQ Broker in host" + factory.getHost() + ". Starting retry process...", err) ) - .retryWhen(Retry.backoff(Long.MAX_VALUE, Duration.ofMillis(300)) - .maxBackoff(Duration.ofMillis(3000))) + .retryWhen(Retry.backoff(Long.MAX_VALUE, Duration.ofMillis(START_INTERVAL)) + .maxBackoff(Duration.ofMillis(MAX_BACKOFF_INTERVAL))) .cache(); } + // SSL based on RabbitConnectionFactoryBean + // https://github.com/spring-projects/spring-amqp/blob/main/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/RabbitConnectionFactoryBean.java + + private static void setUpSSL(ConnectionFactory factory, RabbitProperties properties) + throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException, + CertificateException, IOException { + var ssl = properties.getSsl(); + if (ssl != null && ssl.isEnabled()) { + var keyManagers = configureKeyManagers(ssl); + var trustManagers = configureTrustManagers(ssl); + var secureRandom = SecureRandom.getInstanceStrong(); + + if (log.isLoggable(Level.FINE)) { + log.fine("Initializing SSLContext with KM: " + Arrays.toString(keyManagers) + + ", TM: " + Arrays.toString(trustManagers) + ", random: " + secureRandom); + } + var context = createSSLContext(ssl); + context.init(keyManagers, trustManagers, secureRandom); + factory.useSslProtocol(context); + + logDetails(trustManagers); + + if (ssl.getVerifyHostname()) { + factory.enableHostnameVerification(); + } + } + } + + private static KeyManager[] configureKeyManagers(RabbitPropertiesBase.Ssl ssl) throws KeyStoreException, + IOException, + NoSuchAlgorithmException, + CertificateException, UnrecoverableKeyException { + KeyManager[] keyManagers = null; + if (ssl.getKeyStore() != null) { + var ks = KeyStore.getInstance(ssl.getKeyStoreType()); + char[] keyPassphrase = null; + if (ssl.getKeyStorePassword() != null) { + keyPassphrase = ssl.getKeyStorePassword().toCharArray(); + } + try (var inputStream = new FileInputStream(ssl.getKeyStore())) { + ks.load(inputStream, keyPassphrase); + } + var kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(ks, keyPassphrase); + keyManagers = kmf.getKeyManagers(); + } + return keyManagers; + } + + private static TrustManager[] configureTrustManagers(RabbitPropertiesBase.Ssl ssl) + throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { + KeyStore tks = null; + if (ssl.getTrustStore() != null) { + tks = KeyStore.getInstance(ssl.getTrustStoreType()); + char[] trustPassphrase = null; + if (ssl.getTrustStorePassword() != null) { + trustPassphrase = ssl.getTrustStorePassword().toCharArray(); + } + try (InputStream inputStream = new FileInputStream(ssl.getTrustStore())) { + tks.load(inputStream, trustPassphrase); + } + } + + var tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(tks); + return tmf.getTrustManagers(); + } + + private static SSLContext createSSLContext(RabbitPropertiesBase.Ssl ssl) throws NoSuchAlgorithmException { + return SSLContext.getInstance(ssl.getAlgorithm() != null ? ssl.getAlgorithm() : DEFAULT_PROTOCOL); + } + + private static void logDetails(TrustManager[] managers) { + var found = false; + for (var trustManager : managers) { + if (trustManager instanceof X509TrustManager) { + found = true; + var x509TrustManager = (X509TrustManager) trustManager; + log.info("Loaded " + x509TrustManager.getAcceptedIssuers().length + " accepted issuers for rabbitmq"); + } + } + if (!found) { + log.warning("No X509TrustManager found in the truststore."); + } + } + }