AMQP-830 Enable Hostname Verification by default

JIRA: https://jira.spring.io/browse/AMQP-830
This commit is contained in:
Gary Russell
2018-08-28 13:05:55 -04:00
committed by Artem Bilan
parent 159d2b4315
commit aff4d0aefc
2 changed files with 113 additions and 14 deletions

View File

@@ -16,11 +16,11 @@
package org.springframework.amqp.rabbit.connection;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
@@ -28,8 +28,10 @@ import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicReference;
import javax.net.SocketFactory;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
@@ -42,6 +44,10 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.MethodCallback;
import org.springframework.util.ReflectionUtils.MethodFilter;
import org.springframework.util.StringUtils;
import com.rabbitmq.client.ConnectionFactory;
@@ -75,6 +81,37 @@ import com.rabbitmq.client.impl.nio.NioParams;
*/
public class RabbitConnectionFactoryBean extends AbstractFactoryBean<ConnectionFactory> {
public static final Method enableHostnameVerificationNoArgMethod;
public static final Method enableHostnameVerificationOneArgMethod;
static {
final AtomicReference<Method> method1 = new AtomicReference<Method>();
final AtomicReference<Method> method2 = new AtomicReference<Method>();
ReflectionUtils.doWithMethods(ConnectionFactory.class, new MethodCallback() {
@Override
public void doWith(Method m) throws IllegalArgumentException, IllegalAccessException {
if (m.getParameterTypes().length == 0) {
method1.set(m);
}
else if (m.getParameterTypes().length == 1 && m.getParameterTypes()[0].equals(HostnameVerifier.class)) {
method2.set(m);
}
}
}, new MethodFilter() {
@Override
public boolean matches(Method m) {
return m.getName().equals("enableHostnameVerification");
}
});
enableHostnameVerificationNoArgMethod = method1.get();
enableHostnameVerificationOneArgMethod = method2.get();
}
private final Log logger = LogFactory.getLog(getClass());
private static final String KEY_STORE = "keyStore";
@@ -103,30 +140,34 @@ public class RabbitConnectionFactoryBean extends AbstractFactoryBean<ConnectionF
private Resource sslPropertiesLocation;
private volatile String keyStore;
private String keyStore;
private volatile String trustStore;
private String trustStore;
private volatile Resource keyStoreResource;
private Resource keyStoreResource;
private volatile Resource trustStoreResource;
private Resource trustStoreResource;
private volatile String keyStorePassphrase;
private String keyStorePassphrase;
private volatile String trustStorePassphrase;
private String trustStorePassphrase;
private volatile String keyStoreType;
private String keyStoreType;
private volatile String trustStoreType;
private String trustStoreType;
private volatile String sslAlgorithm = TLS_V1_1;
private String sslAlgorithm = TLS_V1_1;
private volatile boolean sslAlgorithmSet;
private boolean sslAlgorithmSet;
private volatile SecureRandom secureRandom;
private SecureRandom secureRandom;
private boolean skipServerCertificateValidation = true;
private boolean enableHostnameVerification;
private HostnameVerifier hostnameVerifier;
public RabbitConnectionFactoryBean() {
this.connectionFactory.setAutomaticRecoveryEnabled(false);
}
@@ -592,6 +633,46 @@ public class RabbitConnectionFactoryBean extends AbstractFactoryBean<ConnectionF
this.connectionFactory.setTopologyRecoveryEnabled(topologyRecoveryEnabled);
}
/**
* Enable server hostname verification for TLS connections.
* <p>
* This enables hostname verification regardless of the IO mode used (blocking or
* non-blocking IO).
* <p>
* If <strong>using Java 7 or more</strong>, the hostname verification will be
* performed by Java, as part of the TLS handshake.
* <p>
* If <strong>using Java 6</strong>, the hostname verification will be handled after
* the TLS handshake, using the {@link HostnameVerifier} from the Commons HttpClient
* project. This requires to add Commons HttpClient and its dependencies to the
* classpath. To use a custom {@link HostnameVerifier}, use
* {@link #setHostnameVerifier(HostnameVerifier)}. Requires
* amqp-client 4.8.0 or later.
* @param enable true to enable.
* @since 1.7.10
* @see #setHostnameVerifier(HostnameVerifier)
*/
public void setEnableHostnameVerification(boolean enable) {
Assert.notNull(enableHostnameVerificationNoArgMethod,
"Host name verification requires amqp-client 4.8.0 or later");
this.enableHostnameVerification = enable;
}
/**
* Set a custom {@link HostnameVerifier} for use with
* {@link #setEnableHostnameVerification(boolean)}.
* @param hostnameVerifier the verifier.
* @see #setEnableHostnameVerification(boolean)
* @deprecated only used with Java 6
*/
@Deprecated
public void setHostnameVerifier(HostnameVerifier hostnameVerifier) {
Assert.notNull(enableHostnameVerificationOneArgMethod,
"Host name verification requires amqp-client 4.8.0 or later, "
+ "when using 5.8.0 or later, a custom verifier is not required");
this.hostnameVerifier = hostnameVerifier;
}
@Override
public Class<?> getObjectType() {
return ConnectionFactory.class;
@@ -674,6 +755,7 @@ public class RabbitConnectionFactoryBean extends AbstractFactoryBean<ConnectionF
SSLContext context = createSSLContext();
context.init(keyManagers, trustManagers, this.secureRandom);
this.connectionFactory.useSslProtocol(context);
checkHostVerification();
}
}
@@ -689,14 +771,25 @@ public class RabbitConnectionFactoryBean extends AbstractFactoryBean<ConnectionF
}
private void useDefaultTrustStoreMechanism()
throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException {
private void useDefaultTrustStoreMechanism() throws Exception {
SSLContext sslContext = SSLContext.getInstance(this.sslAlgorithm);
TrustManagerFactory trustManagerFactory =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
this.connectionFactory.useSslProtocol(sslContext);
checkHostVerification();
}
private void checkHostVerification() throws Exception {
if (this.enableHostnameVerification) {
if (this.hostnameVerifier == null) {
enableHostnameVerificationNoArgMethod.invoke(this.connectionFactory);
}
else {
enableHostnameVerificationOneArgMethod.invoke(this.connectionFactory, this.hostnameVerifier);
}
}
}
}

View File

@@ -62,6 +62,12 @@ See <<connections>> for more information.
Message requeue on transaction rollback can now be configured to be consistent, regardless of whether or not a transaction manager is configured.
See <<transaction-rollback>> for more information.
===== Connection Factory Bean Changes
The `RabbitConnectionFactoryBean` now provides an `enabaleHostnameVerification` property; set it to `true` to enable host name verification.
Also `setHostnameVerifier` is provided for when using Java 6; see the connection factory javadocs for more information.
Host name verification requires overriding the `amqp-client` to 4.8.0 or later.
==== Earlier Releases
See <<previous-whats-new>> for changes in previous versions.