SSL Support

RabbitMQ has inbuilt support for SSL.

The recommended Erlang distribution is R14B (SSL 4.0.1) or later. This should be considered the minimum configuration for Java and Erlang clients due to an incorrect RC4 implementation in earlier versions of Erlang.

The Erlang crypto application must be installed and working. This may be an issue for Windows users and those who compile Erlang from source.

For Windows users: Versions of Erlang as recent as R13B03 do not include the OpenSSL library needed by the crypto application. This post explains how to install it. It is necessary to install the 32-bit OpenSSL libraries, even on 64-bit versions of Windows.

For Windows XP users running RabbitMQ as a service: It is not possible to use SSL with RabbitMQ running as a service on Windows XP in combination with OpenSSL versions 0.9.8 or later. The bug has been confirmed on Windows XP SP3 with OpenSSL v0.9.8r and v1.0.0d. Suggested workarounds if you want to run RabbitMQ as a service is to upgrade to Windows 7 or downgrade to an earlier version of OpenSSL (v0.9.7e is known to work).

For those compiling Erlang from source: Ensure configure finds OpenSSL and builds the crypto application.

Keys, Certificates and CA Certificates

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 genrsa -out key.pem 2048
# openssl req -new -key key.pem -out req.pem -outform PEM \
    -subj /CN=$(hostname)/O=server/ -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 genrsa -out key.pem 2048
# openssl req -new -key key.pem -out req.pem -outform PEM \
    -subj /CN=$(hostname)/O=client/ -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

Enabling SSL Support in RabbitMQ

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 list of ports to listen on for SSL connections. To listen on a single network interface, add something like {"127.0.0.1", 5671} to the list.

  • -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 configuration file. 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, [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}]}
   ]}
].

Note to Windows users: Backslashes ("\") in the configuration file are interpreted as escape sequences - so for example to specify the path c:\cacert.pem for the CA certificate you would need to enter {cacertfile, "c:\\cacert.pem"} or {cacertfile, "c:/cacert.pem"}.

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.

Note that if {verify, verify_none} is used, no certificate exchange takes place from the client to the server and rabbitmqctl list_connections will output empty strings for the peer certificate info items.

After starting the broker, you should then see the following in the rabbit.log:

=INFO REPORT==== 9-Aug-2010::15:10:55 ===
started TCP Listener on 0.0.0.0:5672

=INFO REPORT==== 9-Aug-2010::15:10:55 ===
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.

Levels of Trust

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.

Connecting without validating certificates

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.setHost("localhost");
        factory.setPort(5671);

        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();
        Channel channel = conn.createChannel();

        //non-durable, exclusive, auto-delete queue
        channel.queueDeclare("rabbitmq-java-test", 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.

Presenting and validating certificates

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.setHost("localhost");
        factory.setPort(5671);
        factory.useSslProtocol(c);

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

        channel.queueDeclare("rabbitmq-java-test", 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.

Configuring the .Net client

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).

RFC 5280, Certificate Key Usage and Mono

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 latter 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 that you'll need to convert the server/cert.pem to server/cert.cer using an appropriate OpenSSL command:

# openssl x509 -in /path/to/server/cert.pem -out /path/to/server/cert.cer -outform DER
# mono certcheck.exe /path/to/server/cert.
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

Certificate Management with Certmgr

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/cacert.cer

To add a certificate to the machine certificate store instead we run

(Windows) > certmgr -add -all \path\to\cacert.cer -s -r localMachine Root
(Mono)    $ certmgr -add -c -m Trust /path/to/cacert.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.

Example

This is the same example as in the Java section. It creates a channel, rabbitmq-dotnet-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

using System;
using System.IO;
using System.Text;

using RabbitMQ.Client;
using RabbitMQ.Util;

namespace RabbitMQ.Client.Examples {
  public class TestSSL {
    public static int Main(string[] args) {
      ConnectionFactory cf = new ConnectionFactory();
      
      cf.Ssl.ServerName = System.Net.Dns.GetHostName();
      cf.Ssl.CertPath = "/path/to/client/keycert.p12";
      cf.Ssl.CertPassphrase = "MySecretPassword";
      cf.Ssl.Enabled = true;
      
      using (IConnection conn = cf.CreateConnection()) {
        using (IModel ch = conn.CreateModel()) {
          ch.QueueDeclare("rabbitmq-dotnet-test", false, false, false, null);
          ch.BasicPublish("", "rabbitmq-dotnet-test", null,
                          Encoding.UTF8.GetBytes("Hello, World"));
          BasicGetResult result = ch.BasicGet("rabbitmq-dotnet-test", true);
          if (result == null) {
            Console.WriteLine("No message received.");
          } else {
            Console.WriteLine("Received:");
            DebugUtil.DumpProperties(result, Console.Out, 0);
          }
          ch.QueueDelete("rabbitmq-dotnet-test");
        }
      }
      return 0;
    }
  }
}

Note that, on Windows XP, running the example may fail with

  CryptographicException: Key not valid for use in specified state.
In this case, you may be successful in loading the certificate from the certificate store and setting ConnectionFactory's Ssl.Certs parameter directly.

Configuring the Erlang client

Enabling SSL in the RabbitMQ Erlang client is rather straight-forward. In the #amqp_params_network record, we just need to supply values in the ssl_options field. These, you will recognise from the options we specified to RabbitMQ.