Skip to content

Commit dd5c360

Browse files
committed
Support TLS hostname verification with HostnameVerifier
This is a solution for Java 6. For Java 7 and more, setting the server validation algorithm on the SSLParameters is more appropriate. This commit tries to make the activation of hostname verification as simpler as possible for the developer. The client detects it's running on Java 6 and sets up the hostname verifier from the Commons HttpClient project (it's still possible to provide a customer HostnameVerifier). The client uses the SSLParameters solution if possible. References #394
1 parent fc4bce1 commit dd5c360

13 files changed

+570
-88
lines changed

pom.xml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,13 @@
5858
<metrics.version>3.2.6</metrics.version>
5959
<micrometer.version>1.0.2</micrometer.version>
6060
<jackson.version>2.9.6</jackson.version>
61+
<httpclient.version>4.5.6</httpclient.version>
6162
<logback.version>1.2.3</logback.version>
6263
<commons-cli.version>1.1</commons-cli.version>
6364
<junit.version>4.12</junit.version>
6465
<awaitility.version>3.1.0</awaitility.version>
6566
<mockito.version>2.16.0</mockito.version>
67+
<bouncycastle.version>1.60</bouncycastle.version>
6668

6769
<maven.javadoc.plugin.version>3.0.0</maven.javadoc.plugin.version>
6870
<maven.release.plugin.version>2.5.3</maven.release.plugin.version>
@@ -647,6 +649,12 @@
647649
<version>${jackson.version}</version>
648650
<optional>true</optional>
649651
</dependency>
652+
<dependency>
653+
<groupId>org.apache.httpcomponents</groupId>
654+
<artifactId>httpclient</artifactId>
655+
<version>${httpclient.version}</version>
656+
<optional>true</optional>
657+
</dependency>
650658
<dependency>
651659
<groupId>commons-cli</groupId>
652660
<artifactId>commons-cli</artifactId>
@@ -683,6 +691,12 @@
683691
<version>1.3</version>
684692
<scope>test</scope>
685693
</dependency>
694+
<dependency>
695+
<groupId>org.bouncycastle</groupId>
696+
<artifactId>bcpkix-jdk15on</artifactId>
697+
<version>${bouncycastle.version}</version>
698+
<scope>test</scope>
699+
</dependency>
686700
</dependencies>
687701

688702
<build>
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright (c) 2018 Pivotal Software, Inc. All rights reserved.
2+
//
3+
// This software, the RabbitMQ Java client library, is triple-licensed under the
4+
// Mozilla Public License 1.1 ("MPL"), the GNU General Public License version 2
5+
// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see
6+
// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL,
7+
// please see LICENSE-APACHE2.
8+
//
9+
// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,
10+
// either express or implied. See the LICENSE file for specific language governing
11+
// rights and limitations of this software.
12+
//
13+
// If you have any questions regarding licensing, please contact us at
14+
// info@rabbitmq.com.
15+
16+
package com.rabbitmq.client;
17+
18+
import javax.net.ssl.SSLSession;
19+
import java.net.Socket;
20+
21+
/**
22+
* The context of the freshly open TCP connection.
23+
*
24+
* @see ConnectionPostProcessor
25+
* @since 4.8.0
26+
*/
27+
public class ConnectionContext {
28+
29+
private final Socket socket;
30+
private final Address address;
31+
private final boolean ssl;
32+
private final SSLSession sslSession;
33+
34+
public ConnectionContext(Socket socket, Address address, boolean ssl, SSLSession sslSession) {
35+
this.socket = socket;
36+
this.address = address;
37+
this.ssl = ssl;
38+
this.sslSession = sslSession;
39+
}
40+
41+
/**
42+
* The network socket. Can be an {@link javax.net.ssl.SSLSocket}.
43+
*
44+
* @return
45+
*/
46+
public Socket getSocket() {
47+
return socket;
48+
}
49+
50+
/**
51+
* The address (hostname and port) used for connecting.
52+
*
53+
* @return
54+
*/
55+
public Address getAddress() {
56+
return address;
57+
}
58+
59+
/**
60+
* Whether this is a SSL/TLS connection.
61+
* @return
62+
*/
63+
public boolean isSsl() {
64+
return ssl;
65+
}
66+
67+
/**
68+
* The {@link SSLSession} for a TLS connection, <code>null</code> otherwise.
69+
* @return
70+
*/
71+
public SSLSession getSslSession() {
72+
return sslSession;
73+
}
74+
}

src/main/java/com/rabbitmq/client/ConnectionFactory.java

Lines changed: 86 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,10 @@
2929
import com.rabbitmq.client.impl.recovery.AutorecoveringConnection;
3030
import com.rabbitmq.client.impl.recovery.RetryHandler;
3131
import com.rabbitmq.client.impl.recovery.TopologyRecoveryFilter;
32+
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
3233

3334
import javax.net.SocketFactory;
35+
import javax.net.ssl.HostnameVerifier;
3436
import javax.net.ssl.SSLContext;
3537
import javax.net.ssl.SSLSocketFactory;
3638
import javax.net.ssl.TrustManager;
@@ -188,6 +190,19 @@ public class ConnectionFactory implements Cloneable {
188190
*/
189191
private RetryHandler topologyRecoveryRetryHandler;
190192

193+
/**
194+
* Hook to post-process the freshly open TCP connection.
195+
*
196+
* @since 4.8.0
197+
*/
198+
private ConnectionPostProcessor connectionPostProcessor = new ConnectionPostProcessor() {
199+
200+
@Override
201+
public void postProcess(ConnectionContext context) {
202+
203+
}
204+
};
205+
191206
/** @return the default host to use for connections */
192207
public String getHost() {
193208
return host;
@@ -679,12 +694,19 @@ public void useSslProtocol(String protocol)
679694
* Pass in the TLS protocol version to use, e.g. "TLSv1.2" or "TLSv1.1", and
680695
* a desired {@link TrustManager}.
681696
*
697+
* Note <strong>you must explicitly enable hostname verification</strong> with the
698+
* {@link ConnectionFactory#enableHostnameVerification()} or the
699+
* {@link ConnectionFactory#enableHostnameVerification(HostnameVerifier)}
700+
* method.
701+
*
682702
*
683703
* The produced {@link SSLContext} instance will be shared with all
684704
* the connections created by this connection factory.
685705
* @param protocol the TLS protocol to use.
686706
* @param trustManager the {@link TrustManager} implementation to use.
687707
* @see #useSslProtocol(SSLContext)
708+
* @see #enableHostnameVerification()
709+
* @see #enableHostnameVerification(HostnameVerifier)
688710
*/
689711
public void useSslProtocol(String protocol, TrustManager trustManager)
690712
throws NoSuchAlgorithmException, KeyManagementException
@@ -699,9 +721,16 @@ public void useSslProtocol(String protocol, TrustManager trustManager)
699721
* for setting up the context with a {@link TrustManager} with suitable security guarantees,
700722
* e.g. peer verification.
701723
*
724+
* Note <strong>you must explicitly enable hostname verification</strong> with the
725+
* {@link ConnectionFactory#enableHostnameVerification()} or the
726+
* {@link ConnectionFactory#enableHostnameVerification(HostnameVerifier)}
727+
* method.
728+
*
702729
* The {@link SSLContext} instance will be shared with all
703730
* the connections created by this connection factory.
704731
* @param context An initialized SSLContext
732+
* @see #enableHostnameVerification()
733+
* @see #enableHostnameVerification(HostnameVerifier)
705734
*/
706735
public void useSslProtocol(SSLContext context) {
707736
setSocketFactory(context.getSocketFactory());
@@ -716,6 +745,15 @@ public void useSslProtocol(SSLContext context) {
716745
* <p>
717746
* This can be called typically after setting the {@link SSLContext}
718747
* with one of the <code>useSslProtocol</code> methods.
748+
* <p>
749+
* If <strong>using Java 7 or more</strong>, the hostname verification will be
750+
* performed by Java, as part of the TLS handshake.
751+
* <p>
752+
* If <strong>using Java 6</strong>, the hostname verification will be handled after
753+
* the TLS handshake, using the {@link HostnameVerifier} from the
754+
* Commons HttpClient project. This requires to add Commons HttpClient
755+
* and its dependencies to the classpath. To use a custom {@link HostnameVerifier},
756+
* use {@link ConnectionFactory#enableHostnameVerification(HostnameVerifier)}.
719757
*
720758
* @see NioParams#enableHostnameVerification()
721759
* @see NioParams#setSslEngineConfigurator(SslEngineConfigurator)
@@ -725,10 +763,43 @@ public void useSslProtocol(SSLContext context) {
725763
* @see ConnectionFactory#useSslProtocol(SSLContext)
726764
* @see ConnectionFactory#useSslProtocol()
727765
* @see ConnectionFactory#useSslProtocol(String, TrustManager)
766+
* @see ConnectionFactory#enableHostnameVerification(HostnameVerifier)
767+
* @since 4.8.0
728768
*/
729769
public void enableHostnameVerification() {
730-
enableHostnameVerificationForNio();
731-
enableHostnameVerificationForBlockingIo();
770+
if (isJava6()) {
771+
enableHostnameVerification(new DefaultHostnameVerifier());
772+
} else {
773+
enableHostnameVerificationForNio();
774+
enableHostnameVerificationForBlockingIo();
775+
}
776+
}
777+
778+
/**
779+
* Enable TLS hostname verification performed by the passed-in {@link HostnameVerifier}.
780+
* <p>
781+
* Using an {@link HostnameVerifier} is relevant only for Java 6, for Java 7 or more,
782+
* calling ConnectionFactory{@link #enableHostnameVerification()} is enough.
783+
*
784+
* @param hostnameVerifier
785+
* @since 4.8.0
786+
* @see ConnectionFactory#enableHostnameVerification()
787+
*/
788+
public void enableHostnameVerification(HostnameVerifier hostnameVerifier) {
789+
if (this.connectionPostProcessor == null) {
790+
this.connectionPostProcessor = ConnectionPostProcessors.builder()
791+
.enableHostnameVerification(hostnameVerifier)
792+
.build();
793+
} else {
794+
this.connectionPostProcessor = ConnectionPostProcessors.builder()
795+
.add(this.connectionPostProcessor)
796+
.enableHostnameVerification(hostnameVerifier)
797+
.build();
798+
}
799+
}
800+
801+
protected boolean isJava6() {
802+
return System.getProperty("java.specification.version").startsWith("1.6");
732803
}
733804

734805
protected void enableHostnameVerificationForNio() {
@@ -835,11 +906,11 @@ protected synchronized FrameHandlerFactory createFrameHandlerFactory() throws IO
835906
if(this.nioParams.getNioExecutor() == null && this.nioParams.getThreadFactory() == null) {
836907
this.nioParams.setThreadFactory(getThreadFactory());
837908
}
838-
this.frameHandlerFactory = new SocketChannelFrameHandlerFactory(connectionTimeout, nioParams, isSSL(), sslContext);
909+
this.frameHandlerFactory = new SocketChannelFrameHandlerFactory(connectionTimeout, nioParams, isSSL(), sslContext, connectionPostProcessor);
839910
}
840911
return this.frameHandlerFactory;
841912
} else {
842-
return new SocketFrameHandlerFactory(connectionTimeout, factory, socketConf, isSSL(), this.shutdownExecutor);
913+
return new SocketFrameHandlerFactory(connectionTimeout, factory, socketConf, isSSL(), this.shutdownExecutor, connectionPostProcessor);
843914
}
844915

845916
}
@@ -1483,4 +1554,15 @@ public void setTopologyRecoveryFilter(TopologyRecoveryFilter topologyRecoveryFil
14831554
public void setTopologyRecoveryRetryHandler(RetryHandler topologyRecoveryRetryHandler) {
14841555
this.topologyRecoveryRetryHandler = topologyRecoveryRetryHandler;
14851556
}
1557+
1558+
/**
1559+
* Hook to post-process the freshly open TCP connection.
1560+
*
1561+
* @param connectionPostProcessor
1562+
* @see #enableHostnameVerification()
1563+
* @see #enableHostnameVerification(HostnameVerifier)
1564+
*/
1565+
public void setConnectionPostProcessor(ConnectionPostProcessor connectionPostProcessor) {
1566+
this.connectionPostProcessor = connectionPostProcessor;
1567+
}
14861568
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright (c) 2018 Pivotal Software, Inc. All rights reserved.
2+
//
3+
// This software, the RabbitMQ Java client library, is triple-licensed under the
4+
// Mozilla Public License 1.1 ("MPL"), the GNU General Public License version 2
5+
// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see
6+
// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL,
7+
// please see LICENSE-APACHE2.
8+
//
9+
// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,
10+
// either express or implied. See the LICENSE file for specific language governing
11+
// rights and limitations of this software.
12+
//
13+
// If you have any questions regarding licensing, please contact us at
14+
// info@rabbitmq.com.
15+
16+
package com.rabbitmq.client;
17+
18+
import java.io.IOException;
19+
20+
/**
21+
* Hook to add processing on the open TCP connection.
22+
* <p>
23+
* Used e.g. to add hostname verification on TLS connection.
24+
*
25+
* @since 4.8.0
26+
*/
27+
public interface ConnectionPostProcessor {
28+
29+
/**
30+
* Post-process the open TCP connection.
31+
*
32+
* @param context some TCP connection context (e.g. socket)
33+
* @throws IOException
34+
*/
35+
void postProcess(ConnectionContext context) throws IOException;
36+
}

0 commit comments

Comments
 (0)