Messaging that just works
As of version 1.7.0, RabbitMQ has inbuilt support for SSL.
SSL/TLS support in the RabbitMQ server requires
the new_ssl Erlang/OTP
application. For everything to work correctly, ensure the
following minimum configuration:
OpenSSL is a large and complex topic. For a thorough understanding of OpenSSL and how to get the most out of it, we would recommend the use of other resources, for example Network Security with OpenSSL.
OpenSSL can be used simply to establish an encrypted communication channel, but can additionally exchange signed certificates between the end points of the channel, and those certificates can optionally be verified. The verification of a certificate requires establishing a chain of trust from a known, trusted root certificate, and the certificate presented. The root certificate is a self-signed certificate, made available by a Certificate Authority. These exist as commercial companies, and will, for a fee, sign SSL Certificates that you have generated.
For the purposes of this guide, we will start by creating our own Certificate Authority. Once we have done this, we will generate signed certificates for the server and clients, in a number of formats. These will we then use with the Java, .Net and Erlang AMQP clients. Note that Mono has more stringent requirements on OpenSSL certificates (and a few bugs too), so we will be specifying slightly more stringent key usage constraints than is normally necessary.
# mkdir testca # cd testca # mkdir certs private # chmod 700 private # echo 01 > serial # touch index.txt
Now place the following in openssl.cnf within the testca
directory we've just created:
[ ca ] default_ca = testca [ testca ] dir = . certificate = $dir/cacert.pem database = $dir/index.txt new_certs_dir = $dir/certs private_key = $dir/private/cakey.pem serial = $dir/serial default_crl_days = 7 default_days = 365 default_md = sha1 policy = testca_policy x509_extensions = certificate_extensions [ testca_policy ] commonName = supplied stateOrProvinceName = optional countryName = optional emailAddress = optional organizationName = optional organizationalUnitName = optional [ certificate_extensions ] basicConstraints = CA:false [ req ] default_bits = 2048 default_keyfile = ./private/cakey.pem default_md = sha1 prompt = yes distinguished_name = root_ca_distinguished_name x509_extensions = root_ca_extensions [ root_ca_distinguished_name ] commonName = hostname [ root_ca_extensions ] basicConstraints = CA:true keyUsage = keyCertSign, cRLSign [ client_ca_extensions ] basicConstraints = CA:false keyUsage = digitalSignature extendedKeyUsage = 1.3.6.1.5.5.7.3.2 [ server_ca_extensions ] basicConstraints = CA:false keyUsage = keyEncipherment extendedKeyUsage = 1.3.6.1.5.5.7.3.1
Now we can generate the key and certificates that our test
Certificate Authority will use. Still within the testca
directory:
# openssl req -x509 -config openssl.cnf -newkey rsa:2048 -days 365 \
-out cacert.pem -outform PEM -subj /CN=MyTestCA/ -nodes
# openssl x509 -in cacert.pem -out cacert.cer -outform DER
This is all that is needed to generate our test Certificate
Authority. The root certificate is in testca/cacert.pem
and is also in testca/cacert.cer. These two files contain the
same information, but in different formats. Whilst the vast majority
of the world is perfectly happy to use the PEM format, Microsoft
and Mono like to be different, and so use the DER format.
Having set up our Certificate Authority, we now need to generate keys and certificates for the clients and the server. The Erlang client and the RabbitMQ broker are both able to use PEM files directly. They will both be informed of three files: the root certificate, which is implicitly trusted, the private key, which is used to prove ownership of the public certificate being presented, and the public certificate itself, which identifies the peer.
For convenience, we provide to the Java and .Net clients, a PKCS #12 store, which contains both the client's certificate and key. The PKCS store is usually password protected itself, and so that password must also be provided.
The process for creating server and client certificates is very similar. The only difference is the keyUsage field that is added when signing the certificate. First the server:
# cd ..
# ls
testca
# mkdir server
# cd server
# openssl req -newkey rsa:2048 -keyout key.pem -keyform PEM -out \
req.pem -subj /CN=$(hostname)/O=server/ -outform PEM -nodes
# cd ../testca
# openssl ca -config openssl.cnf -in ../server/req.pem -out \
../server/cert.pem -notext -batch -extensions server_ca_extensions
# cd ../server
# openssl pkcs12 -export -out keycert.p12 -in cert.pem -inkey key.pem -passout pass:MySecretPassword
And now the client:
# cd ..
# ls
server testca
# mkdir client
# cd client
# openssl req -newkey rsa:2048 -keyout key.pem -keyform PEM -out \
req.pem -subj /CN=$(hostname)/O=client/ -outform PEM -nodes
# cd ../testca
# openssl ca -config openssl.cnf -in ../client/req.pem -out \
../client/cert.pem -notext -batch -extensions client_ca_extensions
# cd ../client
# openssl pkcs12 -export -out keycert.p12 -in cert.pem -inkey key.pem -passout pass:MySecretPassword
To enable the SSL/TLS support in RabbitMQ, we need to provide to RabbitMQ the location of the root certificate, the server's certificate file, and the server's key. We also need to tell it to listen on a socket that is going to be used for SSL connections, and we need to tell it whether it should ask for clients to present certificates, and if the client does present a certificate, whether we should accept the certificate if we can't establish a chain of trust to it. These settings are all controlled by two arguments to RabbitMQ:
-rabbit ssl_listeners
This is a tuple-list of {IpAddress, Port} pairs which tells
RabbitMQ to listen on these addresses and ports for SSL
connections. Setting the IpAddress to 0.0.0.0 tells
RabbitMQ to listen on all available interfaces.
-rabbit ssl_options
This is a tuple list of new_ssl options. The complete list
of ssl_options is available via the new_ssl man page:
i.e. erl -man new_ssl, but the most important are cacertfile, certfile and keyfile.
The simplest way to set these options, is to edit
the rabbitmq.config file (in the
appropriate location for your platform, as discussed in
the installation
guide). An example of the config file is below, which
will start one ssl_listener on port 5671 on all interfaces
on this hostname:
[
{rabbit, [
{ssl_listeners, [{"0.0.0.0",5671}]},
{ssl_options, [{cacertfile,"/path/to/testca/cacert.pem"},
{certfile,"/path/to/server/cert.pem"},
{keyfile,"/path/to/server/key.pem"},
{verify,verify_peer},
{fail_if_no_peer_cert,false}]}
]}
].
When a web browser connects to an HTTPS web server, the server presents its public certificate, the web browser attempts to establish a chain of trust between the root certificates the browser is aware of and the server's certificate, and all being well, an encrypted communication channel is established. Although not used normally by web browsers and web servers, SSL allows the server to ask the client to present a certificate. In this way the server can verify that the client is who they say they are.
This policy of whether or not the server asks for a
certificate from the client, and whether or not they demand
that they are able to trust the certificate, is what
the verify
and fail_if_no_peer_cert arguments
control. Through
the {fail_if_no_peer_cert,false}
option, we state that we're prepared to accept clients which
don't have a certificate to send us, but through
the {verify,verify_peer} option, we
state that if the client does send us a certificate, we must
be able to establish a chain of trust to it. Note that these
values can change across versions of ssl shipped with
Erlang/OTP, so check your man page erl -man
new_ssl to ensure you have the proper values. In
particular, note that Erlang R12B5 has a single
configuration value verify_code which takes the
value 0, 1 or 2. There is
no fail_if_no_peer_cert option in Erlang
R12B5. The options changed with the release of R13
to verify and fail_if_no_peer_cert
as documented here.
On starting the server you should see the following in the start up log:
+---+ +---+ | | | | | | | | | | | | | +---+ +-------+ | | | RabbitMQ +---+ | | | | | | v%%VSN%% +---+ | | | +-------------------+ AMQP 8-0 Copyright (C) 2007-2009 LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. Licensed under the MPL. See http://www.rabbitmq.com/ node : rabbit@koba log : /tmp/rabbit.log sasl log : /tmp/rabbit-sasl.log database dir: /tmp/rabbitmq-rabbit-mnesia starting database ...done starting core processes ...done starting recovery ...done starting persister ...done starting guid generator ...done starting builtin applications ...done starting TCP listeners ...done starting SSL listeners ...done broker running
Note the line that says "Starting SSL listeners". You should then see
the following in the rabbit.log:
=INFO REPORT==== 14-May-2009::11:41:15 === started TCP Listener on 0.0.0.0:5672 =INFO REPORT==== 14-May-2009::11:41:16 === started SSL Listener on 0.0.0.0:5671
Also, take note of the last line, which shows that RabbitMQ server is up and running and listening for ssl connections.
Currently, we're telling RabbitMQ to look at the testca/cacert.pem file. This contains just the public certificate of
our test Certificate Authority. We may have certificates being
presented by clients which have been signed by several different
Certificate Authorities, and we wish RabbitMQ to trust all of
them. Therefore, we can simply append these certificates to one
another and provide the path to this new file as the cacerts
argument to RabbitMQ:
# cat testca/cacert.pem >> all_cacerts.pem # cat otherca/cacert.pem >> all_cacerts.pem
and so forth.
new_sslIn Erlang before release R13B02,
the new_ssl application has a bug in
which, if it asks the peer for a certificate, and the peer
totally fails to respond to this request rather than
rejecting the connection,
the new_ssl application permits the
connection to be established. Note that this requires the
client to break the SSL specifications itself: the
specifications require that if the client has no
certificate to send and a certificate has been requested
by the server, the client must send back a null
certificate. If this happens, then
the new_ssl application behaves
correctly, and acts on the presence of the null
certificate.
If the client fails to send any certificate
at all, the new_ssl application
silently ignores this failure and allows the connection to
be established (i.e. it doesn't enforce that if it demands
a certificate, a certificate really is sent back by the
client).
In Erlang release R13B02, the above behaviour has been
fixed: if the client is asked to present a certificate,
then it must do so. However, other bugs have been
introduced: in particular,
the fail_if_no_peer_cert is not honoured any
more, meaning that if you set {verify,
verify_peer}, then clients must present a
certificate. I.e. it is not possible to now permit the
case where either a client presents no certificate, or it
presents a certificate for which we can verify a chain of
trust.
See also the bugs in Mono.
When setting up an SSL connection there are two important stages in the protocol.
The first stage is when the peers optionally exchange certificates. Having exchanged certificates, the peers can optionally attempt to establish a chain of trust between their root certificates, and the certificates presented. This acts to verify that the peer is who it claims to be (provided the private key hasn't been stolen!).
The second stage is where the peers negotiate a symmetric encryption key that will be used for the rest of the communication. If certificates were exchanged, the public/private keys will be used in the key negotiation.
Thus you can create an encrypted SSL connection without having to verify certificates. The Java client supports both modes of operation.
There are three components to be aware of in the Java security framework: Key Manager, Trust Manager and Key Store.
A Key Manager is used by a peer to manage its certificates. This means that in a session set-up, the Key Manager will control which certificates to send to the remote peer.
A Trust Manager is used by a peer to manage remote certificates. This means that in a session set-up, the Trust Manager will control which certificates are trusted from a remote peer.
A Key Store is a Java encapsulation of certificates. Java needs all certificates to either be converted into a Java specific binary format or to be in the PKCS#12 format. These formats are managed using the Key Store class. For the server certificate, we'll use the Java binary format, but for client key/certificate pair, we'll use the PKCS#12 format.
Our first example will show a simple client, connecting to a RabbitMQ server over SSL without validating the server certificate, and without presenting any client certificate.
import java.io.*;
import java.security.*;
import com.rabbitmq.client.*;
public class Example1
{
public static void main(String[] args) throws Exception
{
ConnectionFactory factory = new ConnectionFactory();
factory.useSslProtocol(); // Tells the library to setup the default Key and Trust managers for you
// which do not do any form of remote server trust verification
Connection conn = factory.newConnection("localhost", 5671);
Channel channel = conn.createChannel();
//non-durable, exclusive, auto-delete queue
channel.queueDeclare("rabbitmq-java-test", false, false, true, true, null);
channel.basicPublish("", "rabbitmq-java-test", null, "Hello, World".getBytes());
GetResponse chResponse = channel.basicGet("rabbitmq-java-test", false);
if(chResponse == null) {
System.out.println("No message retrieved");
} else {
byte[] body = chResponse.getBody();
System.out.println("Recieved: " + new String(body));
}
channel.close();
conn.close();
}
}
This simple example is an echo test. It creates a channel rabbitmq-java-test and publishes to the default direct exchange, then
reads back what has been published and echoes it out. Note that we use
an exclusive, non-durable, auto-delete queue so we don't have to worry
about manually cleaning up after ourselves.
First, we set-up our Key Store. We'll assume that we have the certificate for the server we want to connect to, so we now need to add it to our Key Store which we will use for the Trust Manager.
# keytool -import -alias server1 -file /path/to/server/cert.pem -keystore /path/to/rabbitstore
The above command will import cert.pem into the rabbitstore and will internally refer to it as server1. The
alias argument is used when you have many certificates or keys, as
they must all have internally distinct names.
Be sure to answer yes to the question about trusting this
certificate, and to pick a passphrase. For this example I set my
passphrase to rabbitstore.
We then use our client certificate and key in a PKCS#12 file as already shown above.
Our next example will be a modification of the previous one, to now use our Key Store with our Key Manager and Trust Manager
import java.io.*;
import java.security.*;
import javax.net.ssl.*;
import com.rabbitmq.client.*;
public class Example2
{
public static void main(String[] args) throws Exception
{
char[] keyPassphrase = "MySecretPassword".toCharArray();
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(new FileInputStream("/path/to/client/keycert.p12"), keyPassphrase);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, passphrase);
char[] trustPassphrase = "rabbitstore".toCharArray();
KeyStore tks = KeyStore.getInstance("JKS");
tks.load(new FileInputStream("/path/to/trustStore"), trustPassphrase);
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(tks);
SSLContext c = SSLContext.getInstance("SSLv3");
c.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
ConnectionFactory factory = new ConnectionFactory();
factory.useSslProtocol(c);
Connection conn = factory.newConnection("localhost", 5671);
Channel channel = conn.createChannel();
channel.queueDeclare("rabbitmq-java-test", false, false, true, true, null);
channel.basicPublish("", "rabbitmq-java-test", null, "Hello, World".getBytes());
GetResponse chResponse = channel.basicGet("rabbitmq-java-test", false);
if(chResponse == null) {
System.out.println("No message retrieved");
} else {
byte[] body = chResponse.getBody();
System.out.println("Recieved: " + new String(body));
}
channel.close();
conn.close();
}
}
To ensure that the above code works in the other case, try setting up your RabbitMQ server with a certificate that has not been imported into the key store and watch the verification exceptions decorate your screen.
For a server certificate to be understood on the .Net platform, they
can be in a number of formats including DER and PKCS #12, but
of course, not PEM. For the DER format, .Net expects them to
be stored in files with .cer extension. In the steps above, when
creating the test Certificate Authority, we converted the
certificate from PEM to DER format using:
# openssl x509 -in /path/to/testca/cacert.pem -out /path/to/testca/cacert.cer -outform DER
PEM is a base64 encoding of this DER format, enclosed within delimiters. This encoding is usually done to make it easier to transfer the data over 7-bit limited protocols, such as email (SMTP).
As mentioned above, Mono is rather more stringent than is normal about enforcing that certificates are only used for the purposes indicated by the certificate itself.
SSL certificate and keys can be used for a variety of purposes, for example, email signing, code signing, traffic encryption, etc. (For our purposes, we're interested in TCP Traffic encryption). RFC 5280 specifies a number of different purposes, and allows a certificate to be signed for a specific set of purposes.
SSL v3 certificates can contain a number of different extensions. The extension that deals with how a certificate can be used is call the Key Usage Extension. The various usages permitted are not, in general, well supported, or even well defined, and their usage is subject to wide interpretation. Some of the key usages have been deprecated, and mainly, they're just totally ignored.
There is a further extension, which also specifies usages, but chooses to do so using O.I.Ns, such as "1.3.6.1.5.5.7.3.1". Clearly English is lacking something that apparently random numbers add. This is the Extended Key Usage extension - a sequence of object identifiers that further defines which uses of the certificate are permissible.
Mono, however, seems to think that these extensions are both important, and need to be observed. Mono chooses to invalidate the certificate if the certificate omits a Key Usage Extension. By default, OpenSSL omits the Key Usage Extension for self-signed certificates because it is expected that if no Key Usage Extension is found, the certificate is valid to be used for any purpose.
This is the reason why in the sample openssl.cnf file listed
above, the extensions specified for root_ca_extensions, client_ca_extensions and server_ca_extensions all have keyUsage specified, and the lacodeer two also have extendedKeyUsage defined. Thus the certificates generated above are
valid for use by Mono; keyEncipherment specifies that the
certificate can be used by an SSL Server, and digitalSignature
specifies that the certificate can be used by an SSL client. The
values in the extendedKeyUsage fields precodey much just say the
same thing.
You can use this small tool to
check that the certificate presented by the RabbitMQ server is
acceptable to Mono (note, you'll need to convert the server/cert.pem to server/cert.cer using the OpenSSL command
above):
# mono certcheck.exe /path/to/server/cert.cer Checking if certificate is SSLv3... Ok Checking for KeyUsageExtension (2.5.29.15)... Ok Checking if KeyEncipherment flag is set... Ok This certificate CAN be used by Mono for Server validation
On the .NET platform, by default the hostname of the server
you're connecting to needs to match the CN (Common Name) field on
the server's certificate, otherwise the certificate will be
rejected. This is why the commands at the start of this guide specify
...-subj /CN=$(hostname)/... which dynamically looks up your
hostname. If you're generating certificates on one machine, and using
them on the other then be sure to swap out the $(hostname)
section, and replace it with the correct hostname for your server.
To suppress the match check, an application can set
the System.Net.Security.SslPolicyErrors.RemoteCertificateNameMismatch
flag in SslOptions.AcceptablePolicyErrors.
On the .NET platform, remote certificates are managed by putting them into any of a number of Stores. All management of these stores is done with the 'certmgr' tool which is available on both Microsoft's .Net implementation and on Mono.
NB: On some flavours of Windows there are two versions of the command - one that ships with the operating system and provides a graphical interface only, and one that ships with the .NET Framework Tools and provides both a graphical and command line interface. Either will do the job, but the examples below are based on the latter.
For our case, because we're supplying the client certificate/key pair in a separate PKCS #12 file, all we need to do is to import the certificate of the root Certificate Authority into the Root (Windows) / Trust (Mono) store. All certificates signed by any certificate in that store are automatically trusted.
In contrast to the Java client, which is happy to use an
SSL connection without verifying the server's
certificate, the .NET client by default requires this
verification to succeed. To suppress verification, an
application can set
the System.Net.Security.SslPolicyErrors.RemoteCertificateNotAvailable
and System.Net.Security.SslPolicyErrors.RemoteCertificateChainErrors
flags
in SslOptions.AcceptablePolicyErrors.
certmgr allows us to Add, Delete, List and perform other
actions on a specified Store. These stores can be per-user stores, or
machine wide. Only admin users can have write access to the machine
wide stores.
To add a certificate to the users Root (Windows) / Trust (Mono) store we run:
(Windows) # certmgr -add -all \path\to\cacert.cer -s Root (Mono) # certmgr -add -c Trust /path/to/cert.cer
To add a certificate to the machine wide store instead we run
(Windows) # certmgr -add -all \path\to\cacert.cer -s -r localMachine Root (Mono) # certmgr -add -c -m Trust /path/to/cert.cer
After adding to a store, we can view the contents of that store with the -list switch:
(Windows) # certmgr -all -s Root (Mono) # certmgr -list -c Trust Mono Certificate Manager - version 2.4.0.0 Manage X.509 certificates and CRL from stores. Copyright 2002, 2003 Motus Technologies. Copyright 2004-2008 Novell. BSD licensed. Self-signed X.509 v3 Certificate Serial Number: AC3F2B74ECDD9EEA00 Issuer Name: CN=MyTestCA Subject Name: CN=MyTestCA Valid From: 25/08/2009 14:03:01 Valid Until: 24/09/2009 14:03:01 Unique Hash: 1F04D1D2C20B97BDD5DB70B9EB2013550697A05E
As we can see, there is one Self-signed X.509 v3 Certificate in the trust store. The Unique Hash uniquely identifies this certificate in this store. To delete this certificate, use the unique hash:
(Windows) # certmgr -del -c -sha1 1F04D1D2C20B97BDD5DB70B9EB2013550697A05E -s Root (Mono) # certmgr -del -c Trust 1F04D1D2C20B97BDD5DB70B9EB2013550697A05E Mono Certificate Manager - version 2.4.0.0 Manage X.509 certificates and CRL from stores. Copyright 2002, 2003 Motus Technologies. Copyright 2004-2008 Novell. BSD licensed. Certificate removed from store.
With these simple steps we can go ahead and add/delete/list our root certificates to the client side store.
To create an SSL connection to RabbitMQ, we need to set some new fields in the ConnectionFactory's Parameters field. To make things easier, there is a new Field Parameters.Ssl that acts like a namespace for all the other fields that we need to set. The fields are:
Ssl.CertPath:
This is the path to the client's certificate in
PKCS#12 format if your server expects client side verification. This
is optional.Ssl.CertPassphrase:
If you are using a client certificate in PKCS#12
format then it'll probably have a password, which you specify in
this field.Ssl.Enabled: This is a boolean field that turns SSL support on or
off. It is off by default.Ssl.ServerName:
Remember that .Net expects this to match the CN on
the certificate that the server sends over.// Other initialization here ConnectionFactory cf = new ConnectionFactory(); cf.Parameters.Ssl.ServerName = System.Net.Dns.GetHostName(); cf.Parameters.Ssl.CertPath = "/path/to/client/keycert.p12"; cf.Parameters.Ssl.CertPassphrase = "MySecretPassword"; cf.Parameters.Ssl.Enabled = true; IProtocol proto = Protocols.DefaultProtocol; IConnection conn = cf.CreateConnection(proto, "localhost", 5671); IModel ch = conn.CreateModel(); // Continue with channel commands here
When the server asks the client to send a certificate,
if the client has no certificates to send, it should
(according to various specifications) send back
a null certificate. Mono fails to do this. Instead,
it blindly carries on, acting as if the server hadn't
asked for a certificate at all. Coupled with
the bug
in new_ssl, mentioned above,
this means that with RabbitMQ set
to {fail_if_no_peer_cert,true} on
Erlang before R13B02, if the .Net client is running under
Mono, and does not have a certificate to present, the
connection will successfully be established.
Enabling SSL in the RabbitMQ Erlang client is rather
straight-forward. In the #amqp_params record, we just need to
supply values in the ssl_options field. These you will recognise
from the options we specified to RabbitMQ.
The three important options which must be supplied are:
cacertfile option specifies the certificates of the root
Certificate Authorities that we wish to implicitly trust.certfile is the client's own certificate in PEM formatkeyfile is the client's private key file in PEM format.As with RabbitMQ itself,
the verify
and fail_if_no_peer_cert options are
used in Erlang R13 and onwards to specify what action to
take if the server doesn't present a certificate or if
we're unable to establish a chain of trust to the server's
certificate. In R12B5 and prior,
the verify_code option serves the same
purpose. Check erl -man new_ssl to make sure
you have the right options for your version of Erlang.
Params = #amqp_params
{port = 5671,
ssl_options = [{cacertfile, "/path/to/testca/cacert.pem"},
{certfile, "/path/to/client/cert.pem"},
{keyfile, "/path/to/client/key.pem"},
{verify, verify_peer}, {fail_if_no_peer_cert, true}]},
Conn = amqp_connection:start_network(Params).
You can now go ahead and use Conn as a normal connection.