Skip to content

Commit f8566d3

Browse files
Merge pull request #585 from rabbitmq/rabbitmq-java-client-394-tls-hostname-verification-after-tls-guide-revamp
Document hostname verification for Java client
2 parents d0195b1 + a207ab6 commit f8566d3

File tree

2 files changed

+112
-23
lines changed

2 files changed

+112
-23
lines changed

site/api-guide.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1698,7 +1698,7 @@ factory.useSslProtocol();
16981698
To learn more about TLS support in RabbitMQ, see
16991699
the <a href="ssl.html">TLS guide</a>. If you only want to configure
17001700
the Java client (especially the peer verification and trust manager parts),
1701-
read <a href="ssl.html#trust-levels">the appropriate section</a> of the TLS guide.
1701+
read <a href="ssl.html#java-client">the appropriate section</a> of the TLS guide.
17021702
</p>
17031703
</doc:section>
17041704
</body>

site/ssl.xml

+111-22
Original file line numberDiff line numberDiff line change
@@ -479,11 +479,29 @@ ssl_options.password = t0p$3kRe7
479479
the chain of certificates presented by the peer
480480
and if a trusted certificate is found, considers the peer trusted.
481481
If no trusted and otherwise valid certificate is found, peer verification fails and client connection is closed
482-
with an error ("alert" in OpenSSL parlance).
482+
with an error ("alert" in OpenSSL parlance) that says "Unknown CA" or similar. The alert
483+
will be logged by the server with a message similar to this:
484+
485+
<pre class="sourcecode ini">
486+
2018-09-10 18:10:46.502 [info] &lt;0.902.0&gt; TLS server generated SERVER ALERT: Fatal - Unknown CA
487+
</pre>
483488
</p>
484489
<p>
485490
Certificate validity is also checked at every step. Certificates that are expired
486-
and aren't yet valid will be rejected and skipped.
491+
or aren't yet valid will be rejected. The TLS alert in that case will look something
492+
like this:
493+
494+
<pre class="sourcecode ini">
495+
2018-09-10 18:11:05.168 [info] &lt;0.923.0&gt; TLS server generated SERVER ALERT: Fatal - Certificate Expired
496+
</pre>
497+
498+
</p>
499+
500+
<p>
501+
The examples above demonstrate TLS alert messages logged by RabbitMQ running on Erlang/OTP 21.
502+
Clients that perform peer verification will also raise alerts but may use different
503+
error messages. <a href="https://tools.ietf.org/html/rfc8446#section-6.2">RFC 8446 section 6.2</a>
504+
provides an overview of various alerts and what they mean.
487505
</p>
488506
</doc:subsection>
489507

@@ -722,14 +740,11 @@ ssl_options.fail_if_no_peer_cert = false
722740
import java.io.*;
723741
import java.security.*;
724742

725-
726743
import com.rabbitmq.client.*;
727744

728-
public class Example1
729-
{
730-
public static void main(String[] args) throws Exception
731-
{
745+
public class Example1 {
732746

747+
public static void main(String[] args) throws Exception {
733748
ConnectionFactory factory = new ConnectionFactory();
734749
factory.setHost(&quot;localhost&quot;);
735750
factory.setPort(5671);
@@ -745,16 +760,14 @@ public class Example1
745760
channel.queueDeclare(&quot;rabbitmq-java-test&quot;, false, true, true, null);
746761
channel.basicPublish(&quot;&quot;, &quot;rabbitmq-java-test&quot;, null, &quot;Hello, World&quot;.getBytes());
747762

748-
749763
GetResponse chResponse = channel.basicGet(&quot;rabbitmq-java-test&quot;, false);
750-
if(chResponse == null) {
764+
if (chResponse == null) {
751765
System.out.println(&quot;No message retrieved&quot;);
752766
} else {
753767
byte[] body = chResponse.getBody();
754-
System.out.println(&quot;Recieved: &quot; + new String(body));
768+
System.out.println(&quot;Received: &quot; + new String(body));
755769
}
756770

757-
758771
channel.close();
759772
conn.close();
760773
}
@@ -792,7 +805,7 @@ keytool -import -alias server1 -file /path/to/server/certificate.pem -keystore /
792805
<code>keytool</code> will confirm that the certificate is trusted and ask for a password.
793806
</p>
794807
<p>
795-
We then use our client certificate and key in a <code>PKCS#12</code> file as
808+
The client certificate and key in a <code>PKCS#12</code> file are then used as
796809
already shown above.
797810
</p>
798811
<p>
@@ -806,12 +819,9 @@ import javax.net.ssl.*;
806819

807820
import com.rabbitmq.client.*;
808821

822+
public class Example2 {
809823

810-
public class Example2
811-
{
812-
public static void main(String[] args) throws Exception
813-
{
814-
824+
public static void main(String[] args) throws Exception {
815825
char[] keyPassphrase = &quot;MySecretPassword&quot;.toCharArray();
816826
KeyStore ks = KeyStore.getInstance(&quot;PKCS12&quot;);
817827
ks.load(new FileInputStream(&quot;/path/to/client_key.p12&quot;), keyPassphrase);
@@ -826,30 +836,29 @@ public class Example2
826836
TrustManagerFactory tmf = TrustManagerFactory.getInstance(&quot;SunX509&quot;);
827837
tmf.init(tks);
828838

829-
SSLContext c = SSLContext.getInstance(&quot;TLSv1.1&quot;);
839+
SSLContext c = SSLContext.getInstance(&quot;TLSv1.2&quot;);
830840
c.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
831841

832842
ConnectionFactory factory = new ConnectionFactory();
833843
factory.setHost(&quot;localhost&quot;);
834844
factory.setPort(5671);
835845
factory.useSslProtocol(c);
846+
factory.enableHostnameVerification();
836847

837848
Connection conn = factory.newConnection();
838849
Channel channel = conn.createChannel();
839850

840851
channel.queueDeclare(&quot;rabbitmq-java-test&quot;, false, true, true, null);
841852
channel.basicpublish(&quot;&quot;, &quot;rabbitmq-java-test&quot;, null, &quot;Hello, World&quot;.getBytes());
842853

843-
844854
GetResponse chResponse = channel.basicGet(&quot;rabbitmq-java-test&quot;, false);
845-
if(chResponse == null) {
855+
if (chResponse == null) {
846856
System.out.println(&quot;No message retrieved&quot;);
847857
} else {
848858
byte[] body = chResponse.getBody();
849-
System.out.println(&quot;Recieved: &quot; + new String(body));
859+
System.out.println(&quot;Received: &quot; + new String(body));
850860
}
851861

852-
853862
channel.close();
854863
conn.close();
855864
}
@@ -861,6 +870,86 @@ public class Example2
861870
a RabbitMQ node with a certificate that has not been imported
862871
into the key store and watch the connection fail.
863872
</p>
873+
874+
<h4><a class="anchor" href="#java-client-hostname-verification">Server Hostname Verification</a></h4>
875+
<p>
876+
Hostname verification must be enabled separately using the
877+
<code>ConnectionFactory#enableHostnameVerification()</code> method. This is done in the example
878+
above, for instance:
879+
880+
<pre class="sourcecode java">
881+
import java.io.*;
882+
import java.security.*;
883+
import javax.net.ssl.*;
884+
885+
import com.rabbitmq.client.*;
886+
887+
public class Example2 {
888+
889+
public static void main(String[] args) throws Exception {
890+
char[] keyPassphrase = &quot;MySecretPassword&quot;.toCharArray();
891+
KeyStore ks = KeyStore.getInstance(&quot;PKCS12&quot;);
892+
ks.load(new FileInputStream(&quot;/path/to/client_key.p12&quot;), keyPassphrase);
893+
894+
KeyManagerFactory kmf = KeyManagerFactory.getInstance(&quot;SunX509&quot;);
895+
kmf.init(ks, passphrase);
896+
897+
char[] trustPassphrase = &quot;rabbitstore&quot;.toCharArray();
898+
KeyStore tks = KeyStore.getInstance(&quot;JKS&quot;);
899+
tks.load(new FileInputStream(&quot;/path/to/trustStore&quot;), trustPassphrase);
900+
901+
TrustManagerFactory tmf = TrustManagerFactory.getInstance(&quot;SunX509&quot;);
902+
tmf.init(tks);
903+
904+
SSLContext c = SSLContext.getInstance(&quot;TLSv1.2&quot;);
905+
c.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
906+
907+
ConnectionFactory factory = new ConnectionFactory();
908+
factory.setHost(&quot;localhost&quot;);
909+
factory.setPort(5671);
910+
factory.useSslProtocol(c);
911+
factory.enableHostnameVerification();
912+
913+
// this connection will both perform peer verification
914+
// and server hostname verification
915+
Connection conn = factory.newConnection();
916+
917+
// snip ...
918+
}
919+
}</pre>
920+
921+
This will verify
922+
that the server certificate has been issued for the hostname the
923+
client is connecting to. Unlike certificate chain verification, this feature
924+
is client-specific (not usually performed by the server).
925+
</p>
926+
927+
<p>
928+
With JDK 6, it is necessary to add a dependency on
929+
<a href="https://hc.apache.org/">Apache Commons HttpClient</a> for hostname verification to work, e.g. with Maven:
930+
931+
<pre class="sourcecode xml">
932+
&lt;!-- Maven dependency to add for hostname verification on JDK 6 --&gt;
933+
&lt;dependency&gt;
934+
&lt;groupId&gt;org.apache.httpcomponents&lt;/groupId&gt;
935+
&lt;artifactId&gt;httpclient&lt;/artifactId&gt;
936+
&lt;version&gt;4.5.6&lt;/version&gt;
937+
&lt;/dependency&gt;
938+
</pre>
939+
940+
With Gradle:
941+
942+
<pre class="sourcecode groovy">
943+
// Gradle dependency to add for hostname verification on JDK 6
944+
compile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.6'
945+
</pre>
946+
</p>
947+
<p>
948+
Alternatively with JDK 6
949+
<code>ConnectionFactory#enableHostnameVerification(HostnameVerifier)</code>
950+
can be provided a <code>HostnameVerifier</code> instance of choice.
951+
</p>
952+
864953
</doc:subsection>
865954

866955
<doc:subsection name="tls-versions-java-client">

0 commit comments

Comments
 (0)