Skip to content

Commit f16cf1f

Browse files
Merge pull request #399 from rabbitmq/rabbitmq-java-client-394-tls-hostname-verification-4.x.x
Backport hostname verification to 4.x.x
2 parents b2f5cf5 + dd5c360 commit f16cf1f

26 files changed

+1207
-38
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: 135 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515

1616
package com.rabbitmq.client;
1717

18-
import static java.util.concurrent.TimeUnit.*;
19-
2018
import com.rabbitmq.client.impl.AMQConnection;
2119
import com.rabbitmq.client.impl.ConnectionParams;
2220
import com.rabbitmq.client.impl.CredentialsProvider;
@@ -31,7 +29,13 @@
3129
import com.rabbitmq.client.impl.recovery.AutorecoveringConnection;
3230
import com.rabbitmq.client.impl.recovery.RetryHandler;
3331
import com.rabbitmq.client.impl.recovery.TopologyRecoveryFilter;
32+
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
3433

34+
import javax.net.SocketFactory;
35+
import javax.net.ssl.HostnameVerifier;
36+
import javax.net.ssl.SSLContext;
37+
import javax.net.ssl.SSLSocketFactory;
38+
import javax.net.ssl.TrustManager;
3539
import java.io.IOException;
3640
import java.net.URI;
3741
import java.net.URISyntaxException;
@@ -49,10 +53,8 @@
4953
import java.util.concurrent.ScheduledExecutorService;
5054
import java.util.concurrent.ThreadFactory;
5155
import java.util.concurrent.TimeoutException;
52-
import javax.net.SocketFactory;
53-
import javax.net.ssl.SSLContext;
54-
import javax.net.ssl.SSLSocketFactory;
55-
import javax.net.ssl.TrustManager;
56+
57+
import static java.util.concurrent.TimeUnit.MINUTES;
5658

5759
/**
5860
* Convenience "factory" class to facilitate opening a {@link Connection} to an AMQP broker.
@@ -126,7 +128,7 @@ public class ConnectionFactory implements Cloneable {
126128
// connections uses, see rabbitmq/rabbitmq-java-client#86
127129
private ExecutorService shutdownExecutor;
128130
private ScheduledExecutorService heartbeatExecutor;
129-
private SocketConfigurator socketConf = new DefaultSocketConfigurator();
131+
private SocketConfigurator socketConf = SocketConfigurators.defaultConfigurator();
130132
private ExceptionHandler exceptionHandler = new DefaultExceptionHandler();
131133
private CredentialsProvider credentialsProvider = new DefaultCredentialsProvider(DEFAULT_USER, DEFAULT_PASS);
132134

@@ -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,15 +721,108 @@ 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());
708737
this.sslContext = context;
709738
}
710739

740+
/**
741+
* Enable server hostname verification for TLS connections.
742+
* <p>
743+
* This enables hostname verification regardless of the IO mode
744+
* used (blocking or non-blocking IO).
745+
* <p>
746+
* This can be called typically after setting the {@link SSLContext}
747+
* 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)}.
757+
*
758+
* @see NioParams#enableHostnameVerification()
759+
* @see NioParams#setSslEngineConfigurator(SslEngineConfigurator)
760+
* @see SslEngineConfigurators#ENABLE_HOSTNAME_VERIFICATION
761+
* @see SocketConfigurators#ENABLE_HOSTNAME_VERIFICATION
762+
* @see ConnectionFactory#useSslProtocol(String)
763+
* @see ConnectionFactory#useSslProtocol(SSLContext)
764+
* @see ConnectionFactory#useSslProtocol()
765+
* @see ConnectionFactory#useSslProtocol(String, TrustManager)
766+
* @see ConnectionFactory#enableHostnameVerification(HostnameVerifier)
767+
* @since 4.8.0
768+
*/
769+
public void enableHostnameVerification() {
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");
803+
}
804+
805+
protected void enableHostnameVerificationForNio() {
806+
if (this.nioParams == null) {
807+
this.nioParams = new NioParams();
808+
}
809+
this.nioParams = this.nioParams.enableHostnameVerification();
810+
}
811+
812+
protected void enableHostnameVerificationForBlockingIo() {
813+
if (this.socketConf == null) {
814+
this.socketConf = SocketConfigurators.builder()
815+
.defaultConfigurator()
816+
.enableHostnameVerification()
817+
.build();
818+
} else {
819+
this.socketConf = SocketConfigurators.builder()
820+
.add(this.socketConf)
821+
.enableHostnameVerification()
822+
.build();
823+
}
824+
}
825+
711826
public static String computeDefaultTlsProcotol(String[] supportedProtocols) {
712827
if(supportedProtocols != null) {
713828
for (String supportedProtocol : supportedProtocols) {
@@ -791,11 +906,11 @@ protected synchronized FrameHandlerFactory createFrameHandlerFactory() throws IO
791906
if(this.nioParams.getNioExecutor() == null && this.nioParams.getThreadFactory() == null) {
792907
this.nioParams.setThreadFactory(getThreadFactory());
793908
}
794-
this.frameHandlerFactory = new SocketChannelFrameHandlerFactory(connectionTimeout, nioParams, isSSL(), sslContext);
909+
this.frameHandlerFactory = new SocketChannelFrameHandlerFactory(connectionTimeout, nioParams, isSSL(), sslContext, connectionPostProcessor);
795910
}
796911
return this.frameHandlerFactory;
797912
} else {
798-
return new SocketFrameHandlerFactory(connectionTimeout, factory, socketConf, isSSL(), this.shutdownExecutor);
913+
return new SocketFrameHandlerFactory(connectionTimeout, factory, socketConf, isSSL(), this.shutdownExecutor, connectionPostProcessor);
799914
}
800915

801916
}
@@ -1439,4 +1554,15 @@ public void setTopologyRecoveryFilter(TopologyRecoveryFilter topologyRecoveryFil
14391554
public void setTopologyRecoveryRetryHandler(RetryHandler topologyRecoveryRetryHandler) {
14401555
this.topologyRecoveryRetryHandler = topologyRecoveryRetryHandler;
14411556
}
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+
}
14421568
}
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)