Thursday, April 1, 2010

Ussing SSL in NMS.ActiveMQ

With the recent addition of SSL support into NMS.ActiveMQ and NMS.Stomp I thought it'd be a good idea to write an article covering how to use the new functionality and explain some of the things to watch out for.  At the time of this writing the SSL support is only in the trunk code, but will make its way into the NMS 1.3.0 release some time soon, until the release you can go to the NMS Site to get the latest code and find build instructions.  

Once you have your NMS and NMS.ActiveMQ builds in hand you need to do a few more things before you can connect to a broker via SSL.

Broker SSL Configuration


The default broker configuration doesn't enable SSL so the first thing you need to do is add configuration of the SSL Transport to your Broker's configuration file, there's a pretty good how-to on the ActiveMQ website showing how to do that, so I won't reproduce it all here.

Of SSL Certificates and Trust

Now that you've configured your broker to support SSL connections its time to setup the client machine so that your newly built NMS and NMS.ActiveMQ assemblies can actually connect.  When the NMS client tries to connect over SSL its going to want to validate that it can trust the server on the other end of the connection, this implies that the Certificate the broker sends the client when it connects is one that we trust.  The trust relationship between the .NET client and the Broker comes from the broker sending a certificate that is either signed by a trusted Certificate Authtority or by virtue of us having added the Certificate itself to our list of trusted Certificates.

To add the Broker's Certificate to the list of trusted Certificates that .NET clients are aware of can be a painful and mysterious process.  I'm working from a Linux box running Mono so I can tell you how to I did it, how its done on a Windows machine might be a bit different.  

By following the instructions on the ActiveMQ sites "How do I use SSL" article you should have exported your Broker's SSL Certificate to a file named broker_cert (I named mine broker.cer instead).  You can use the 'certmgr.exe' command to add that certificate to you list of Trusted Certificates like so:
certmgr -add -c Trust broker.cer
This add the Certificate to a list of trusted Certificates that should allow your NMS client to connect without encountering validation errors.  I'll give you a hint on another way to make the connection to the Broker without adding the Certificate to you Trust store later on.

Connecting over SSL with NMS.ActiveMQ

Like any other connection using NMS the way we specify what server to connect to and how we want to do so is done via the connection URI.  This means if you already have a working NMS client you can easily start using the SSL functionality simply by referencing the new Assemblies and updating you connection URI.  So what does the new URI look like?  Pretty much the same as the old one except where you would have put tcp you now put ssl.  Sounds easy, lets take a look at a URI that I use to connect to my Broker:
ssl://localhost:61617
That's it, same old URI, new protocol. If you added the Broker's certificate to the Trust store then your client should connect and run just like it always has.

Something Went Wrong!

Well you knew it was to good to be true, it couldn't be that easy.  You tried running your client and got a bunch of exceptions when it tried to connect, what to do.

A couple things can go wrong here, lets take a look:
  • The Broker Certificate wasn't in the Trust store.
  • The host name in the URI you used to connect doesn't match the common name of the Certificate your broker is using.
Earlier I showed you how to add the Broker's certificate to the Trust store, but maybe you didn't read that part, or maybe you couldn't figure out how to accomplish that for your particular version of Windows, well there's a workaround.  Since I didn't want to always have to put the dummy self signed Certificates that I test with in the Trust store I added an URI option in NMS.ActiveMQ to let me bypass this, its called 'transport.acceptInvalidBrokerCert' and your URI would look as follows if you decided to use it:
ssl://localhost:61617?transport.acceptInvalidBrokerCert=true
This options pretty much forces the NMS client to accept any old Certificate that your Broker sends, so best to only use this in test environments.

Now onto the other common problem, your Broker's Certificate could have something in its Common Name field that's not the same as the host name you put in your URI.  This could happen for a number of reasons especially when you are using self signed Certificates, fortunately we gave you a way around this one as well, if you need to override the name we use to look up the Server's Certificate from the host name use the 'transport.serverName' option:
ssl://localhost:61617?transport.serverName=\"My Test Cert\"
This will cause the NMS code to use "My Test Cert" as the name of lookup when authenticating the Server connection.  Normally the name in the Certificate would match the URL of the server but when testing you might just be using IP addresses so this is a convenient workaround.

Conclusion

That's about all there is to it, pretty easy right?  There's more to it if you want to do two way Client / Server authentication but I think I will save that for another posting.  Hopefully you found this somewhat helpful.  If you have questions or comments please let me know, I will update the posting if something is unclear or incorrect.


30 comments:

Max said...

Thanks for this good article !

I am trying to connect using https transportConnector in ActiveMq server, and cannot succeed in doing it because it appears that the https connector is speaking only plain text, and not ssl, even if my sslContext is well configured (i can use correctly the ssl:// tranportConnector as well).


Any idea on how to make https transportConnector speak SSL ?

Thanks alot if you have any clue

max

www.maxizone.fr

Tim said...

I'm confused, are you asking if you can use NMS to communicate over https or is this a broker configuration question?

Max said...

Sorry for being unclear, and thanks for you quick answer

my question is about ActiveMQ (i don't know what NMS.ActiveMQ is) and i am trying to enable https connection in activemq.xml, adding a transportConnector corresponding to https.

The problem is that this connector doesn't speak SSL, even if my SSL connector is correctly configured and work well (it works with an SSL client).

thanks:

Frank said...

I've recently brought some actually very reasonably priced SSL Certificates from SSL247.com. But I might also need to set up another certificate for another experimental domain. How might this differ with the process if I tried a self-signed certificate? From what I gathered of what you said it doesn't!

Tim said...

It shouldn't differ much at all so long as you create valid self signed certificates. I use a free tool called XCA to create my own Root CA and then create signed server and client certificates from there signed by my Root CA. If you place the Root CA in your trusted Certificates store then it should just work.

Ameya said...

Can you help me with CMS and SSL.

I am struggling with it from last week. Here are the things I have:

1) OpenSSL Libraries for windows.
2) ActiveMQ CPP is build as per (http://activemq.apache.org/cms/how-to-enable-ssl-support-on-windows.html)

About certificates and CA's
3) CA certificate created by XCA tool.
4) Signed public key certificate for Java app. (This public certificate is signed by (3). I generated truststore and keystore for Java app from this public certificate using OpenSSL)

Here's my problem:

When CMS client does a connection->start(), on Java app side I get unknown_ca .

It seems to do all TLSv1 handshakes and at the very end it comes up with SSL exception.

Any help on this..

Ameya

Tim said...

You need to tell the CMS SSLTransport where the broker's certificate is that it should trust, doing something like:

decaf::lang::System::setProperty( "decaf.net.ssl.trustStore", "/certificate.pem" );

See this FAQ entry:
http://activemq.apache.org/cms/how-do-i-use-the-ssl-transport.html

Ameya said...

Hi Tim,

Thanks for your quick response. I am already setting "trustStore" property brfore creating a CMSConnectionFactory. In order to veirfy the certificate I am using SSL_CTX_load_verify_locations() function provided by OpenSSL. I believe you are using same function under the hood.
First question, what should be it set to? As per your reply it seems I should have brokers public certificate, right? Or should it be CA certificate which signed broker's certificate? I tried both but both do not seem to work for me.

Case 1) When provided Broker's certificate
*** ServerHello, TLSv1
.
. (many things in between)
.
*** ServerHelloDone
ActiveMQ Task, handling exception: javax.net.ssl.SSLHandshakeException: Received fatal alert: unknown_ca


Case 2) When provided CA's certificate (personally, seems to be correct way)
*** ServerHello, TLSv1 ...
*** ServerHelloDone ...
*** ClientKeyExchange, DH ...
*** Finished ...
ActiveMQ Task, WRITE: TLSv1 Handshake, length = 40
%% Cached server session: [Session-104, SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA]
ActiveMQ Task, WRITE: TLSv1 Application Data, length = 248
ActiveMQ Transport: ssl:///145.1.233.104:1881, READ: TLSv1 Application Data, length = 248
BrokerService, WRITE: TLSv1 Application Data, length = 136
TcpSocketClose: java.util.concurrent.ThreadPoolExecutor$Worker@10e98d8, called close()
TcpSocketClose: java.util.concurrent.ThreadPoolExecutor$Worker@10e98d8, called closeInternal(true)
TcpSocketClose: java.util.concurrent.ThreadPoolExecutor$Worker@10e98d8, SEND TLSv1 ALERT: warning, description = close_notify
TcpSocketClose: java.util.concurrent.ThreadPoolExecutor$Worker@10e98d8, WRITE: TLSv1 Alert, length = 24
ActiveMQ Transport: ssl:///145.1.233.104:1879, handling exception: java.net.SocketException: socket closed
%% Invalidated: [Session-103, SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA]
ActiveMQ Transport: ssl:///145.1.233.104:1879, SEND TLSv1 ALERT: fatal, description = unexpected_message
ActiveMQ Transport: ssl:///145.1.233.104:1879, WRITE: TLSv1 Alert, length = 24
ActiveMQ Transport: ssl:///145.1.233.104:1879, Exception sending alert: java.net.SocketException: Socket closed
ActiveMQ Transport: ssl:///145.1.233.104:1879, called closeSocket()


In both the above cases it throws an exception on connection->start() with no relevant information.

I think I am missing a link either on Java side or on C side.

I am specifying my configuration details, in case you need it
1. OS - Windows XP 32 bit
2. ActiveMQ 5.3.1
3. ActiveMQ-CPP 3.2.3
4. OpenSSL 1.0.0
5. JDK 1.6.0_17

Thanks, if you can identify anything from the above.

Tim said...

You can use either the broker's own certificate, or the CA that signed the brokers certificate.

The question would be what is the URI you are using to connect with via CMS, and does the hostname you are passing match the name field in the broker's certificate.

For example the default broker cert that ships with activemq has localhost as the name in the certificate so you would get an error if you tried to connect using the ip address 127.0.0.1 instead of specifying localhost.

If you are still stuck you can post over in the ActiveMQ mailing list as well, I don't always get to these blog post comments in a timely manner :)

Ameya said...

Hi Tim,

Finally, the problem is SOLVED. I played around with

SSLContext context = SSLContext.getInstance("SSLv3");

protocols type. Initially, I was having error with "SSL", then I changed it to "TLS" and CMS side gave the error that you described.

I changed my protocol to SSLv3(because on CMS side OpenSSL have error containing this protocol name) and created a new self-signed certificate such that host name and broker certificate name are same.

Thanks :) for your insight on this.

Tim said...

Great, glad its working for you.

BTW - You shouldn't need to change the SSL protocol, TLS should be the preferred and default choice on both sides.

chasingNirvana said...

I am able to work with SSL partially. Here's what is working:

Broker running on machine A. CPP clients running on A and B. Everything is fine. CPP Clients are connecting using CA certificate.

1) I would like to use public keys instead of CA certificate. Why can not a self-signed CA certificate be used to sign a public-key of a broker, and ship out that signed public key?

2) I want vice a versa configuration up and running that is:
Machine B to run Broker and CPP client on A and B. I am trying to start brokers on A and B both in failover configuration.

So ideally I want a Root CA certificate which can contain both URI. Any tip how you do so? I am using XCA tool. I tried adding DNS, URI in subject alternative name, which crashed my CPP(strange). I also tried adding multiple common names but no help.

PS:- I am in process of registering myself in mailing list, as soon as it is approved I would post this topic there. but in the meantime if you can share some thoughts onto this.

THANKS in advance.

chasingNirvana said...

I think I found solution to the above problem. I need someone from ActiveMQ-CPP peopler to confirm it and update their code as well.

In "void OpenSSLSocket::verifyServerCert( const std::string& serverName )" method I made following changes:

method->i2v( method, method->d2i( NULL, &data, extension->value->length ), NULL );

to

method->i2v( method, X509_get_ext_d2i(cert, OBJ_obj2nid( X509_EXTENSION_get_object( extension ) ), NULL, NULL) , NULL );


Reference:
http://markmail.org/message/sixjgp4rpwm2wg7c#query:+page:1+mid:57sgja3zrc6xhqgz+state:results

Karel Gardas said...

Hello,
thanks for excellent article. However, I do have some issues getting SSL working with NMS. It looks like I'm not able to use ssl://hostname:61617 as NMS complains about unavailable connection factory implementation. I'm using 1.5.1 ActiveMQ NMS agains 5.5.0 ActiveMQ. Do you know where is the issue with this? Thanks! Karel

Tim said...

Either you don't have both the NMS and NMS.ActiveMQ dlls in the applications path, or you are using this on .NET compact framework. Those are my two guesses anyway, would need more info to make any other guesses.

Karel Gardas said...

Hello,
thanks for excellent article. The problem I see here is that while using ssl://hostname:61617 scheme, NMS complains about unavailable connection factory implementation for connection URI: ssl://:61617.
That's while using NMS 1.5.0 and ActiveMQ NMS 1.5.1 in VS 2010 C# Express.
Do you know how to make this working? Thanks! Karel

Karel Gardas said...

Hello Tim,
sorry for double post! Anyway, my testing C# application is using .NET Framework 4. It's a console application (I've needed to retarget from .NET Framework 4 Client Profile to full .NET Framework 4) and I added both dlls from net-4.0/debug directories so I see them in solution explorer in reference node.
Perhaps the problem might be with my code? I think the most problematic (from which exception is thrown) is this:
IConnectionFactory factory = new NMSConnectionFactory("ssl://127.0.0.1:61617", "C# test client");
--- OK! And this is the issue. When I changed NMSConnnectionFactory to Apache.NMS.ActiveMQ.ConnectionFactory -- it starts working sudenly. :-)
Thanks a lot for your help with this!
Karel

chalicham said...

Can someone please send me the sample to connect to ActiveMQ vis SSL.

I am trying to setup transport.ClientCertFilename in URI but some reason its not working.

Thanks
Suresh

varguc said...

Hello Tim,
thank you for this article that helped me a lot in connecting to a broker listening over an SSL transport connection. Now I have another problem. We have to connect from our NMS client to a broker which needs client authentication. I have'nt found any information about this situation. In the Conclusion of your article you promised a new article about two way Client / Server authentication. Can you help me?
varguc

Tim said...

In general the source code is your best source of truth on the functionality. If you look at the source for the SSL transport you will find three properties,

private string clientCertSubject;
private string clientCertFilename;
private string clientCertPassword;

You need to set the values for these on the Uri to tell the client where its Certificate is, and you need to include that certificate or its root in the trust store for the Broker.

chalicham said...

Tim,

Thanks for your repaly. After adding the right property's to URI i am able to connect server using SSSl connectionactory.CreateConnection().

However i am getting following error when i strat connection (connection.start())
java.lang.SecurityException : Unable to authenticate transport without SSL certificate.

In trace log finally i see following :
System.Net.Sockets Verbose: 0 : [3272] Exiting Socket#59191269::Receive() -> 121#121
System.Net.Sockets Verbose: 0 : [3272] Socket#59191269::Receive()
System.Net.Sockets Verbose: 0 : [5620] Socket#59191269::Shutdown(Both#2)
System.Net.Sockets Verbose: 0 : [5620] Exiting Socket#59191269::Shutdown()
System.Net.Sockets Verbose: 0 : [5620] Socket#59191269::Close()

Appriciate if you could help here.

Tim said...

.NET SSL is a real PITA. Best thing to do is to enable tracing using the NmsTracer Trace setter to log the data from the client and see what's going on. Also running in the debugger and stepping through the SSL Transport code might show you where its going wrong. There's no magic here, just brute force really to get the crappy .NET SSL stream to read in the correct certificates.

chalicham said...

Tim,

Thanks for your repaly. What exactly is the "clientCertFilename" property?
I have tried with certificate name but i am getting error "file name speified could not be found".

My case , client provided client.ks and client.ts, then i converted them to .cer and storeing in trusted root.

What is the "clientCertFilename" name i should specify here?

Thanks
Suresh

Mark said...

I've been trying to get the SSL Client authentication to work with my ActiveMQ. The subject of my test client cert is "CN=client".
If I pass in that value in the URI like follows, the parser throws an error because of the "=" in the parameter:

string sSslUrl = "ssl://localhost:61617";
sSslUrl += "&transport.clientCertSubject=CN=client";

I then tried encoding the "=" like follows:

string sSslUrl = "ssl://localhost:61617";
sSslUrl += "&transport.clientCertSubject= " + HttpUtility.UrlEncode ("CN=client");

This made the parser happy because the "=" is changed to "%3d". However, the encoded string
"CN%3dclient" is passed into the "SslTransport::SelectLocalCertificate" method.
Since the encoded equals is never decoded (i.e. "%3d" was never changed back to "="), the match can't be made.
I experimented with a work around by modifying the ClientCertSubject property to decode the value as follows:

public string ClientCertSubject
{
get { return this.clientCertSubject; }
set
{
/*MW, 2/22/12, work around for equal sign in URL parameter
this.clientCertSubject = value;
*/
//MW, 2/22/12, work around for equal sign in URL parameter
this.clientCertSubject = HttpUtility.UrlDecode (value).Trim();
}
}

This made all the code happy and client authentication succeeded.

Is there a way to pass in the certificate subject name in the Url so the equals ("=") is properly passed to the SelectLocalCertificate call so this change isn't needed?

Thanks,
Mark

Tim said...

Don't think there is any way to fix it without the code change, which as far as I know is already done in trunk.

Mark said...

Thanks Tim!

I'll keep on the look out for the next stable release of the API and use my "hack" in the meantime.

Mark

Mattias Zetterberg said...

I know that it is an old thread but are there still somebody here that can help me?

I am trying to connect to a HornetQ server with Stomp in .NET but are having problems with the certification validation. I think it is the hostname verification that is the problem and I have read the part about using the serverName option but cannot get it to work. If someone could provide a small sample code where you use the NMSConnectionFactory I would be very greatful.

IConnectionFactory factory = new NMSConnectionFactory(stomp:ssl://jms.dev.local:61613?transport.serverName=\"test\"");
IConnection connection = factory.CreateConnection();

Error! Could not connect to broker URL: ssl://jms.dev.local:61613/?transport.servername="test".

//Mattias

Mattias Zetterberg said...

I know that it is an old thread but are there still somebody here that can help me?

I am trying to connect to a HornetQ server with Stomp in .NET but are having problems with the certification validation. I think it is the hostname verification that is the problem and I have read the part about using the serverName option but cannot get it to work. If someone could provide a small sample code where you use the NMSConnectionFactory I would be very greatful.

IConnectionFactory factory = new NMSConnectionFactory(stomp:ssl://jms.dev.local:61613?transport.serverName=\"test\"");
IConnection connection = factory.CreateConnection();

Error! Could not connect to broker URL: ssl://jms.dev.local:61613/?transport.servername="test". Why are there a slash after the port?

//Mattias

lacike said...

Mattias, have you enabled the ssl protocol in activemq.xml?

Haris M said...

more thank this i believe we have to add following line in
activemq.xml as well







but when Add above line i got following issue when I start up ActiveMQ

org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException: Line 166 in XML document from class path resource [activemq.xml] is invalid; nested exception is org.xml.sax.SAXParseException; lineNumber: 166; columnNumber: 14; cvc-complex-type.2.4.a: Invalid content was found starting with element 'sslContext'. One of '{"http://www.springframework.org/schema/beans":import, "http://www.springframework.org/schema/beans":alias, "http://www.springframework.org/schema/beans":bean, WC[##other:"http://www.springframework.org/schema/beans"], "http://www.springframework.org/schema/beans":beans}' is expected.
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:396)

any idea?