How to validate server SSL/TLS certificate against Certificate Revocation List in RabbitMQ .NET Client

I had a need to validate the server certificate that was used by RabbitMQ to ensure it wasn’t revoked. This adds an additional layer of security when communicating with the server and helps ensure you don’t have a man in the middle attack with an unsavory ill-gotten certificate.

Is it really needed, well, that is up to your business needs. I added the ability to do the check in the library itself, but that won’t come out until the next release.

In the mean time, if you need this functionality now, or don’t want the new client, then you simply need to override the server certificate validation callback and implement the following code.

Callback code for validating full certificate chain including CRL

// The following method is invoked by the RemoteCertificateValidationDelegate.
      public static bool ValidateServerCertificate(
            object sender,
            X509Certificate certificate,
            X509Chain chain,
            SslPolicyErrors sslPolicyErrors)
      {
 
          Console.WriteLine("Certificate error: {0}", sslPolicyErrors);
 
          //first gate, if any errors bail immediately. no sense in double check.
          if (sslPolicyErrors != SslPolicyErrors.None)
              return false;
 
          //revalidate for CRL.
 
          var cert = new X509Certificate2(certificate);
          chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
          chain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot;
          var result = chain.Build(cert);
                 
          if(!result)
              Console.WriteLine("Certificate was revoked.");
          return result;
      }

If you want a full example, here is a class I wrote that demos it.

using RabbitMQ.Client;
using RabbitMQ.Util;
using System;
using System.Diagnostics;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text;
 
namespace RabbitSSLValidation
{
    public  class RabbitTest:IRabbitTest
    {
        public SslPolicyErrors ErrorsAllowed { getset; }
        public SslPolicyErrors ErrorsFound { getprotected set; } = SslPolicyErrors.None;
 
        public RabbitTest(SslPolicyErrors errorsAllowed)
        {
            ErrorsAllowed = errorsAllowed;
        }
        const string HelloWorldQueue = "HELLO";
 
        public  bool ConnectToServer(string host,string sslHost,string username,string password, string certificate,string certificatePassword)
        {
            var rtn = false;
            var factory = new ConnectionFactory() {
                HostName = host,
                UserName = username,
                Port = 5671,
                Password = password,
                Ssl =new SslOption()
                {
                    Enabled=true,
                    AcceptablePolicyErrors = ErrorsAllowed,
                    ServerName = sslHost,
                    CertificateValidationCallback = ValidateServerCertificate,
                    CertPath = certificate,
                    CertPassphrase = certificatePassword
                }
            };
            using (var connection = factory.CreateConnection())
            using (var channel = connection.CreateModel())
            {
                channel.QueueDeclare(HelloWorldQueue, falsefalsefalsenull);
                channel.BasicPublish("", HelloWorldQueue, nullEncoding.UTF8.GetBytes("Hello, World"));
                BasicGetResult result = channel.BasicGet(HelloWorldQueue, true);
                if (result != null)
                {
                    rtn = true;
                    DebugUtil.DumpProperties(result, Console.Out, 0);
                }
                channel.QueueDelete(HelloWorldQueue);
            }
 
            return rtn;
        }
 
 
        private  bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain,
            SslPolicyErrors sslPolicyErrors)
        {
            ErrorsFound = sslPolicyErrors;
            
            if ((sslPolicyErrors & ~ErrorsAllowed) != SslPolicyErrors.None)
            {
                Debug.WriteLine("Certificate error: {0}", sslPolicyErrors);
                return false;
            }
 
            //check crl ourselves, which actually rechecks the entire thing again in full.
            chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
            return chain.Build(new X509Certificate2(certificate));
         }
 
    }
 
   
}

Happy coding!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.