Establishing a SSL connection to RabbitMQ using the .NET client

First, I'm making the assumption that you've read, re-read, and followed the SSL tutorial on rabbitmq's website.  If you haven't done everything that it's instructed you to do (including adding your certificates to the Windows Certificate Store using certmgr), none of this code is going to work for you.  If you have, this code should "Just Work™".

Here's the complete code file that works for me.  You will (obviously) need to change the names of the servers, and the thumbprint of your certificate.

Note that this code only uses your client and server's certificates to establish a secure connection.  You are still logging in as guest.  I will show you how to use your client certificate to authenticate yourself below.
using System;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;

using RabbitMQ.Client;
using RabbitMQ.Util;


namespace RabbitSslTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Program p = new Program();
            p.Start();

            Console.WriteLine();
            Console.WriteLine("Press <ENTER> to exit.");
            Console.ReadLine();
        }


        public void Start()
        {
            try
            {
                // we use the rabbit connection factory, just like normal
                ConnectionFactory cf = new ConnectionFactory();

                // set the hostname and the port
                cf.HostName = "rabbitmq-server";
                cf.Port = AmqpTcpEndpoint.DefaultAmqpSslPort;

                // I've imported my certificate into my certificate store
                // (the Personal/Certificates folder in the certmgr mmc snap-in)
                // Let's open that store right now.
                X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
                store.Open(OpenFlags.ReadOnly);

                // and find my certificate by its thumbprint.
                X509Certificate cert = store.Certificates
                    .Find(
                        X509FindType.FindByThumbprint,
                        "6d 36 c0 f9 c2 f8 72 05 d1 01 6c 54 e1 91 bb 2d 66 7b ac 94",
                        true
                    )
                    .OfType<X509Certificate>()
                    .First();

                // now, let's set the connection factory's ssl-specific settings
                // NOTE: it's absolutely required that what you set as Ssl.ServerName be
                //       what's on your rabbitmq server's certificate (its CN - common name)
                cf.Ssl.Certs = new X509CertificateCollection(new X509Certificate[] { cert });
                cf.Ssl.ServerName = "rabbitmq-server";
                cf.Ssl.Enabled = true;

                // we're ready to create an SSL connection to the rabbitmq server
                using (IConnection conn = cf.CreateConnection())
                {
                    using (IModel channel = conn.CreateModel())
                    {
                        channel.QueueDeclare("rabbitmq-dotnet-test", false, false, false, null);
                       
                        // create some basic properties for the message we're going to send
                        IBasicProperties props = channel.CreateBasicProperties();
                        props.ContentEncoding = "utf-8";
                        props.MessageId = Guid.NewGuid().ToString();

                        // publish a hello world message
                        channel.BasicPublish("", "rabbitmq-dotnet-test", props, Encoding.UTF8.GetBytes("Hello, World"));

                        // and go get it
                        BasicGetResult result = channel.BasicGet("rabbitmq-dotnet-test", true);

                        if (null == result)
                        {
                            Console.WriteLine("No message received.");
                        }
                        else
                        {
                            Console.WriteLine("-----==<([ Received ])>==-----");
                            DebugUtil.DumpProperties(result, Console.Out, 0);
                        }

                        channel.QueueDelete("rabbitmq-dotnet-test");
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }
    }
}
 And the output:

Using your certificate to authenticate
If you want to authenticate yourself with your certificate as well, you need to change the code and your rabbitmq server's configuration.

First, update your rabbitmq server's configuration.  If you don't know where to look for that, read this page.  Here's what mine looks like:
  [
  {rabbit, [
     {auth_mechanisms,['EXTERNAL']},
     {ssl_listeners, [5671]},
     {ssl_options, [{cacertfile,"C:/Path/To/Your/cacert.pem"},
                    {certfile,"C:/Path/To/Your/cert.pem"},
                    {keyfile,"C:/Path/To/Your/key.pem"},
                    {verify,verify_peer},
                    {fail_if_no_peer_cert,true}]}
   ]}
].
The thing to note there is the addition of {auth_mechanisms,['EXTERNAL']}.  Don't forget to stop your service, re-install it, and start it after making these changes.
rabbitmq-service.bat stop
rabbitmq-service.bat install
rabbitmq-service.bat start
Next, let's update our client code just a tad by setting the connection factory's auth mechanisms.

// we use the rabbit connection factory, just like normal ConnectionFactory cf = new ConnectionFactory();

// set the hostname and the port
cf.HostName = "rabbitmq-server";
cf.Port = AmqpTcpEndpoint.DefaultAmqpSslPort;
cf.AuthMechanisms = new AuthMechanismFactory[] { new ExternalMechanismFactory() };

// I've imported my certificate into my certificate store
// (the Personal/Certificates folder in the certmgr mmc snap-in)
// Let's open that store right now.
X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
The other change I made was to insert a Console.ReadLine() just before all of my using () statements cause everything to close down.  By doing this, I can view the RabbitMQ management web app to ensure that I'm authenticating using my certificate.


Comments

  1. Great article John. The only other thing I had to do was execute rabbitmq-plugins enable rabbitmq_auth_mechanism_ssl and restart the server. Otherwise, client's would fail to authenticate because rabbitmq wasn't providing the ssl option as an authentication mechanism.

    ReplyDelete
  2. Hi John. I am trying to following your example to use SSL for authentication but running into "endpoint not reachable error". Reading between lines, in your example, you locate your certificate by thumbprint - but is it the client side certificate in client/cert.pem in the tutorial and you convert it to DER format? Please clarify what exactly the certificate you find from your code. Thanks a lot. Alex

    ReplyDelete
  3. We use a Microsoft PKI, so our certs are already in DER. We import our certs into our Windows Certificate Store, using certmgr.msc. This is like Mac's Keychain app, but for Windows. Each account (including the computer's account has its own keystore).

    The code you're looking at actually opens your Windows Certificate Store (the one for your account), and searches through it (by thumbprint) for the correct certificate to use.

    So if you're using openSSL, you'll want to build a PKCS12 file (instructions here), which is both your cert and your key and then import that into your Windows Certificate Store using the certmgr.msc GUI, which looks like this. Then, use your certificate's thumbprint in the code I show above and everything should work just fine!

    ReplyDelete
  4. Thanks John. We are on Windows too. However, in the configuration file for the Rabbit server, it requires
    {ssl_options, [{cacertfile,"C:/Path/To/Your/cacert.pem"},
    {certfile,"C:/Path/To/Your/cert.pem"},
    {keyfile,"C:/Path/To/Your/key.pem"},
    ...
    How do you generate these files from Windows PKI for the server? Yes, I have the client certificate in .PFX format (I suppose that's the what thumbprint identifies) from Personal store. I can create from mmc/snap-in for a localhost, say, but how do I generate the separate certificate, private key and the CA files? Thanks a lot for your help, John.

    ReplyDelete
  5. We use openssl to generate a private key, and then to generate a signing request based on the private key. We then upload the signing request to the certificate web site. For example. if your issuing CA is issue01.example.com, go to http://issue01.example.com/certsrv/ and select "request a certificate".

    Then select "advanced certificate request". There, you'll be able to paste the certificate signing request you created with openssl, and you may also select the certificate template to use. If you haven't defined your own templates, then "Web Server" will do nicely for your RabbitMQ server. You can leave "Additional Attributes" empty.

    If you (the person who logged into the app) have the appropriate privileges, you'll be able to download the issued certificate, the certificate chain, and the CA chain. You'll download this in DER format, which is a .cer or .crt file.

    You can then use openssl to convert the certificate from DER format to PEM format. You can also convert the certificate authority certificates from DER to PEM and then concatenate them into a single pem file. Just make sure that the "root" cert is the last in your concatenated file.

    By now, you have everything you need: the private key file (which is the first thing you generated), the CAcerts.pem file, and the PEM certificate for your RabbitMQ server.

    Hope this was helpful!

    ReplyDelete
  6. Thanks, John. After much tweaking and format conversions, we managed to run the first part of your program with 'guest' and PLAIN authentication. We followed your instructions to use SASL EXTERNAL and we got the error message in the server log: {EXTERNAL login refused: user 'CN=myPC' - invalid credentials" Giving that our certificates can pass the verification chain, what else can be invalid for SASL EXTERNAL? We appreciate your help and insights!!
    'connection.start_ok'}}

    ReplyDelete
  7. Did you create a user called "CN=myPC" with no password in the rabbit management console? Don't forget to set permissions for the user after you create it.

    ReplyDelete
    Replies
    1. Hi John, I still create the user "CN=userName" without password and granted all permission and administrator tag, but I still encountered the same problem, {EXTERNAL login refused: user 'CN=userName' - invalid credentials". What did I missed or other checking I need to make. Thanks

      Delete
    2. with the external mechanism, your credentials are your private key. It's stored along with your certificate in certmgr.msc. If your credentials are bad, then you need to ensure that your cert has a private key associated with it.

      Look at this image here: https://www.simple-talk.com/wp-content/uploads/imported/833-Digita1.jpg

      Do you see how that cert has a little key icon in the top left? That means there's a private key associated with it. Yours also needs to have that icon or it won't work.

      If it does have the icon, then you need to make sure that you're really getting the cert from the store before you try to pass it to RabbitMQ for authentication.

      Delete
    3. ya ya everything with the cert is good, its working for me now the problem was I configured the SSL to use cert's CN instead of its DN as the username; {ssl_cert_login_from, common_name} inside the SSL Option. But when I put this config under the root rabbit config, then it works well. Am sure this is the right way configuring the ssl.

      I really appreciate your help and quick response.

      Delete
    4. This comment has been removed by the author.

      Delete
    5. Just one more advice I need from you; This is the scenario;
      1. I have a central server to update some data for each of 5 branch shops.
      2. I have 5 branch shops and need to update back to the central server for the sales and other transactions they made and need to exchange some data between them also.
      I need to connect the server with these 5 branches and each branches with the other and to the central server using rabbitmq federation link using ssl with the certificates and the ca self signed for test case. What kind of approaches do u recommend me to setup the ca and certs? Do i need CA and Certs for the server and separate CA and Certs for each shop? or what is the better approaches do you recommend from this perspective?

      Thanks

      Delete
    6. I'm afraid that your situation will have too many variables for me to give you 'good' advice.

      My first piece of advice is to not have a federation of rabbitMQ clusters just because it's simpler to use the centralized one (or put one in the cloud) for everything. But see? Maybe you have good reasons for your federation. Let's say that you do...

      If you have properly protected the servers that are running rabbitMQ (everything behind a well-configured firewall) and you are using certs for authentication of all rabbitMQ clients... then you can be less concerned about man-in-the-middle attacks and just focus on protecting message content that might make it into malign hands. Good crypto is all you need to achieve that, so you can just create your own very minimal CA and create certs for everyone to use. I wouldn't create a CA for each branch! As long as each branch can get to your centralized CA to verify the cert chain... you should be good to go.

      See how many assumptions I've made already? My advice is to look at the fundamentals of information security... do you need non-repudiation? how will you achieve it? Do you need confidentiality? How will you achieve it? Do you need message integrity? How w.... you get the point.

      Sorry for a lack-luster response.

      Delete
    7. Thanks sir for guiding me through the dimensions I need to revise my security concerns from which I have to sort out these issues I raised for you earlier. Now I decided to get deep into Network Security and SSL..

      Thanks again.

      Delete
    8. This comment has been removed by the author.

      Delete
  8. That's it, John. I really appreciate your help and pointing out the potholes! Now the program runs as you stated.

    ReplyDelete
  9. I'm glad that I was able to help you. Thanks for visiting my blog!

    ReplyDelete
  10. I don't want to use certificate authentication please let me know the secure way for authentication.

    ReplyDelete
  11. There's a difference between using certificates for authentication and just using them for a secure connection.

    What you want to do is use certificates to just protect the data going between your clients and your server. It's really quite easy to setup, even in Windows (the certs in this post were created in Windows). However, if you really don't want to use any certs at all, then you should have a look at the list of supported authentication mechanisms and choose the one that will work for you: https://www.rabbitmq.com/authentication.html

    ReplyDelete
  12. HI John,
    System Enviourmenyt
    Windows 7

    I create certificates using MakeCert command. following this article
    http://www.jayway.com/2014/09/03/creating-self-signed-certificates-with-makecert-exe-for-development


    I create 9 file 3 for each client,server and CA root with the extension of these files are .pvk, .cer and .pfx.

    Now my question is that how i will set the because i am not havive these extension files and also let me know where is rabbitMQ.config file.

    D:/RabbitMQ/certs/MyCA.pem
    D:/RabbitMQ/certs/MyRabbitServer.pem
    D:/RabbitMQ/certs/MyRabbitServer.key


    %% -*- mode: erlang -*-
    [
    {rabbit, [
    {ssl_listeners, [5671]},
    {ssl_options, [{cacertfile,"D:/RabbitMQ/certs/MyCA.pem"},
    {certfile,"D:/RabbitMQ/certs/MyRabbitServer.pem"},
    {keyfile,"D:/RabbitMQ/certs/MyRabbitServer.key"},
    {verify,verify_none},
    {fail_if_no_peer_cert,false}]}
    ]}
    ].

    Please let me know about the solution.

    ReplyDelete
    Replies
    1. Your CER is equivalent to a PEM, but in a different format. You can use the openssl command-line to convert your CER into a PEM. This website will show you the exact command(s) to use: https://www.sslshopper.com/article-most-common-openssl-commands.html

      Delete

Post a Comment

Popular posts from this blog

Join CentOS to a Windows Domain

Configure a FIPS 140-2 Compliant Java Provider on RedHat/CentOS/Fedora