Skip to content

Commit 8d5d8d8

Browse files
committed
Add HostnameVerifier property (for Java 6)
References #55
1 parent 0b652ea commit 8d5d8d8

File tree

3 files changed

+116
-4
lines changed

3 files changed

+116
-4
lines changed

pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
<properties>
4444
<rabbitmq.version>4.8.1</rabbitmq.version>
4545
<slf4j-api.version>1.7.25</slf4j-api.version>
46+
<httpclient.version>4.5.6</httpclient.version>
4647
<junit.version>4.12</junit.version>
4748
<mockito-core.version>2.21.0</mockito-core.version>
4849
<awaitility.version>3.1.2</awaitility.version>
@@ -118,6 +119,12 @@
118119
<artifactId>slf4j-api</artifactId>
119120
<version>${slf4j-api.version}</version>
120121
</dependency>
122+
<dependency>
123+
<groupId>org.apache.httpcomponents</groupId>
124+
<artifactId>httpclient</artifactId>
125+
<version>${httpclient.version}</version>
126+
<optional>true</optional>
127+
</dependency>
121128

122129
<!-- test scope -->
123130
<dependency>

src/main/java/com/rabbitmq/jms/admin/RMQConnectionFactory.java

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import javax.naming.Reference;
2929
import javax.naming.Referenceable;
3030
import javax.naming.StringRefAddr;
31+
import javax.net.ssl.HostnameVerifier;
3132
import javax.net.ssl.SSLContext;
3233
import javax.net.ssl.SSLException;
3334
import java.io.IOException;
@@ -116,6 +117,13 @@ public class RMQConnectionFactory implements ConnectionFactory, Referenceable, S
116117
*/
117118
private boolean hostnameVerification = false;
118119

120+
/**
121+
* {@link HostnameVerifier} to use when TLS is on.
122+
*
123+
* @since 1.10.0
124+
*/
125+
private HostnameVerifier hostnameVerifier;
126+
119127

120128
/** The maximum number of messages to read on a queue browser, which must be non-negative;
121129
* 0 means unlimited and is the default; negative values are interpreted as 0. */
@@ -162,8 +170,8 @@ public Connection createConnection(String username, String password) throws JMSE
162170
com.rabbitmq.client.ConnectionFactory factory = new com.rabbitmq.client.ConnectionFactory();
163171
setRabbitUri(logger, this, factory, this.getUri());
164172
maybeEnableTLS(factory);
165-
factory.setMetricsCollector(this.metricsCollector);
166173
maybeEnableHostnameVerification(factory);
174+
factory.setMetricsCollector(this.metricsCollector);
167175
com.rabbitmq.client.Connection rabbitConnection = instantiateNodeConnection(factory);
168176

169177
RMQConnection conn = new RMQConnection(new ConnectionParams()
@@ -189,6 +197,7 @@ public Connection createConnection(String username, String password, List<Addres
189197
this.password = password;
190198
com.rabbitmq.client.ConnectionFactory cf = new com.rabbitmq.client.ConnectionFactory();
191199
maybeEnableTLS(cf);
200+
maybeEnableHostnameVerification(cf);
192201
cf.setMetricsCollector(this.metricsCollector);
193202
com.rabbitmq.client.Connection rabbitConnection = instantiateNodeConnection(cf, endpoints);
194203

@@ -350,9 +359,13 @@ private void maybeEnableTLS(com.rabbitmq.client.ConnectionFactory factory) {
350359
}
351360

352361
private void maybeEnableHostnameVerification(com.rabbitmq.client.ConnectionFactory factory) {
353-
if (hostnameVerification) {
362+
if (hostnameVerification || hostnameVerifier != null) {
354363
if (this.ssl) {
355-
factory.enableHostnameVerification();
364+
if (hostnameVerifier == null) {
365+
factory.enableHostnameVerification();
366+
} else {
367+
factory.enableHostnameVerification(this.hostnameVerifier);
368+
}
356369
} else {
357370
logger.warn("Hostname verification enabled, but not TLS, please enable TLS too.");
358371
}
@@ -764,14 +777,38 @@ public void setMetricsCollector(MetricsCollector metricsCollector) {
764777

765778
/**
766779
* Enable or disable hostname verification when TLS is used.
780+
* <p>
781+
* If using Java 7 and more, the hostname verification will be handled
782+
* by the JVM as part of the TLS handshake. If using Java 6,
783+
* the hostname verification is performed by the {@link HostnameVerifier}
784+
* from the Commons HttpClient project. This implies that Commons HttpClient
785+
* and its dependencies are added to the classpath. To use another {@link HostnameVerifier},
786+
* use {@link RMQConnectionFactory#setHostnameVerifier(HostnameVerifier)}.
787+
*
767788
*
768789
* @param hostnameVerification
769790
* @see com.rabbitmq.client.ConnectionFactory#enableHostnameVerification()
791+
* @see com.rabbitmq.client.ConnectionFactory#enableHostnameVerification(HostnameVerifier)
792+
* @see #setHostnameVerifier(HostnameVerifier)
770793
* @since 1.10.0
771794
*/
772795
public void setHostnameVerification(boolean hostnameVerification) {
773796
this.hostnameVerification = hostnameVerification;
774797
}
775798

799+
/**
800+
* Set the {@link HostnameVerifier} to use for the hostname verification.
801+
* <p>
802+
* Setting an {@link HostnameVerifier} is relevant for Java 6, as the JVM
803+
* can perform the hostname verification as of Java 7.
804+
*
805+
* @param hostnameVerifier
806+
* @see #setHostnameVerification(boolean)
807+
* @see com.rabbitmq.client.ConnectionFactory#enableHostnameVerification(HostnameVerifier)
808+
* @since 1.10.0
809+
*/
810+
public void setHostnameVerifier(HostnameVerifier hostnameVerifier) {
811+
this.hostnameVerifier = hostnameVerifier;
812+
}
776813
}
777814

src/test/java/com/rabbitmq/integration/tests/SSLHostnameVerificationIT.java

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,89 @@
22
package com.rabbitmq.integration.tests;
33

44
import com.rabbitmq.jms.admin.RMQConnectionFactory;
5+
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
56
import org.junit.Before;
67
import org.junit.BeforeClass;
78
import org.junit.Test;
9+
import org.junit.runner.RunWith;
10+
import org.junit.runners.Parameterized;
811

912
import javax.jms.Connection;
1013
import javax.jms.JMSException;
1114
import javax.jms.JMSSecurityException;
15+
import javax.net.ssl.HostnameVerifier;
1216
import javax.net.ssl.KeyManagerFactory;
1317
import javax.net.ssl.SSLContext;
18+
import javax.net.ssl.SSLSession;
1419
import javax.net.ssl.TrustManagerFactory;
1520
import java.io.FileInputStream;
1621
import java.security.KeyStore;
22+
import java.util.concurrent.atomic.AtomicInteger;
1723

24+
import static org.junit.Assert.assertEquals;
1825
import static org.junit.Assert.assertNotNull;
1926

2027
/**
2128
* Integration test for hostname verification with TLS.
2229
*/
30+
@RunWith(Parameterized.class)
2331
public class SSLHostnameVerificationIT {
2432

2533
static SSLContext sslContext;
34+
static AtomicInteger hostnameVerifierCalls;
35+
@Parameterized.Parameter(0)
36+
public ConnectionFactoryCustomizer customizer;
37+
@Parameterized.Parameter(1)
38+
public Runnable assertion;
2639
RMQConnectionFactory cf;
2740

41+
@Parameterized.Parameters
42+
public static Object[] data() {
43+
return new Object[] {
44+
new Object[] { enableHostnameVerification(), expectedCallsOnHostnameVerifierAssertion(0) },
45+
new Object[] { useCustomHostnameVerifier(), expectedCallsOnHostnameVerifierAssertion(1) }
46+
};
47+
}
48+
49+
private static Runnable expectedCallsOnHostnameVerifierAssertion(final int expectedCount) {
50+
return new Runnable() {
51+
52+
@Override
53+
public void run() {
54+
assertEquals(expectedCount, hostnameVerifierCalls.get());
55+
}
56+
};
57+
}
58+
59+
private static ConnectionFactoryCustomizer useCustomHostnameVerifier() {
60+
return new ConnectionFactoryCustomizer() {
61+
62+
@Override
63+
public void customize(RMQConnectionFactory connectionFactory) {
64+
final DefaultHostnameVerifier delegate = new DefaultHostnameVerifier();
65+
HostnameVerifier verifier = new HostnameVerifier() {
66+
67+
@Override
68+
public boolean verify(String hostname, SSLSession session) {
69+
hostnameVerifierCalls.incrementAndGet();
70+
return delegate.verify(hostname, session);
71+
}
72+
};
73+
connectionFactory.setHostnameVerifier(verifier);
74+
}
75+
};
76+
}
77+
78+
private static ConnectionFactoryCustomizer enableHostnameVerification() {
79+
return new ConnectionFactoryCustomizer() {
80+
81+
@Override
82+
public void customize(RMQConnectionFactory connectionFactory) {
83+
connectionFactory.setHostnameVerification(true);
84+
}
85+
};
86+
}
87+
2888
@BeforeClass
2989
public static void initCrypto() throws Exception {
3090
String keystorePath = System.getProperty("test-keystore.ca");
@@ -59,12 +119,13 @@ public static void initCrypto() throws Exception {
59119
public void init() {
60120
cf = new RMQConnectionFactory();
61121
cf.useSslProtocol(sslContext);
62-
cf.setHostnameVerification(true);
122+
hostnameVerifierCalls = new AtomicInteger(0);
63123
}
64124

65125
@Test
66126
public void hostnameVerificationEnabledShouldPassForLocalhost() throws JMSException {
67127
cf.setHost("localhost");
128+
customizer.customize(cf);
68129
Connection connection = null;
69130
try {
70131
connection = cf.createConnection();
@@ -73,11 +134,18 @@ public void hostnameVerificationEnabledShouldPassForLocalhost() throws JMSExcept
73134
connection.close();
74135
}
75136
}
137+
assertion.run();
76138
}
77139

78140
@Test(expected = JMSSecurityException.class)
79141
public void hostnameVerificationEnabledShouldFailForLoopbackInterface() throws JMSException {
80142
cf.setHost("127.0.0.1");
143+
customizer.customize(cf);
81144
cf.createConnection();
82145
}
146+
147+
interface ConnectionFactoryCustomizer {
148+
149+
void customize(RMQConnectionFactory connectionFactory);
150+
}
83151
}

0 commit comments

Comments
 (0)