-
-
Save sharonbn/4104301 to your computer and use it in GitHub Desktop.
SSL/TLS connection from Eclipse Paho Java client to mosquitto MQTT broker | |
By Sharon Ben Asher | |
AVG Mobilation | |
Sharon.Ben-Asher@AVG.com | |
Mosquitto is an Open Source MQTT v3.1 Broker written in C (http://mosquitto.org) | |
Eclipse Paho project has a Java MQTT client (http://eclipse.org/paho/) | |
The code snippet below demonstrates how to establish a secured connection from a Paho client to a mosquitto broker. | |
The connection includes server and client authentication through openssl (PEM formatted) certificates. | |
1) Follow the instructions on the mosquitto site to produce all the necessary certificates | |
http://mosquitto.org/man/mosquitto-tls-7.html | |
2) Configure the broker to expect SSL connections. | |
example configuration: | |
listener 1883 | |
cafile /home/ubuntu/etc/ca.crt | |
certfile /home/ubuntu/etc/server.crt | |
keyfile /home/ubuntu/etc/server.key | |
require_certificate true | |
use_identity_as_username true | |
3) On the client side, Paho has several options for specifying properties for the creation of SSL sockets | |
(Properties, JVM arguments, etc). However, none of them will work with mosquitto (historically, Paho worked with IBM brokers). | |
Fortunately, it also accepts a custom made instance of javax.net.ssl.SSLSocketFactory through the method MqttConnectOptions.setSocketFactory() and this works. | |
example code using Paho API to establish connection: | |
String serverUrl = "ssl://myMosquittoServer.com:1883"; | |
MqttClient client = new MqttClient(serverUrl, "consumerId" , null); | |
client.setCallback(new MyCallback()); | |
MqttConnectOptions options = new MqttConnectOptions(); | |
options.setConnectionTimeout(60); | |
options.setKeepAliveInterval(60); | |
options.setSocketFactory(SslUtil.getSocketFactory("caFilePath", "clientCrtFilePath", "clientKeyFilePath", "password")); | |
client.connect(options); | |
client.subscribe("topic", 0); | |
The interesting bit is, of course, SslUtil.getSocketFactory() method. The code is attached seperately. | |
Since Java cannot read PEM formatted certificates, the method is using bouncy castle (http://www.bouncycastle.org/) to load the necessary files: | |
ca.crt is used to authenticate the server and is used to init an instance of javax.net.ssl.TrustManagerFactory. | |
client.crt/.key are sent to mosquitto for client authentication, and therefore are used to init an instance of javax.net.ssl.KeyManagerFactory. | |
The method expects all files as String full paths. | |
The method is using Files.readAllBytes() which is available in JDK 7. | |
basically, you need to load the file into byte array and pass that array to the constructor of ByteArrayInputStream as is demonstrated in the code. |
import java.io.*; | |
import java.nio.file.*; | |
import java.security.*; | |
import java.security.cert.*; | |
import javax.net.ssl.*; | |
import org.bouncycastle.jce.provider.*; | |
import org.bouncycastle.openssl.*; | |
public class SslUtil | |
{ | |
static SSLSocketFactory getSocketFactory (final String caCrtFile, final String crtFile, final String keyFile, | |
final String password) throws Exception | |
{ | |
Security.addProvider(new BouncyCastleProvider()); | |
// load CA certificate | |
PEMReader reader = new PEMReader(new InputStreamReader(new ByteArrayInputStream(Files.readAllBytes(Paths.get(caCrtFile))))); | |
X509Certificate caCert = (X509Certificate)reader.readObject(); | |
reader.close(); | |
// load client certificate | |
reader = new PEMReader(new InputStreamReader(new ByteArrayInputStream(Files.readAllBytes(Paths.get(crtFile))))); | |
X509Certificate cert = (X509Certificate)reader.readObject(); | |
reader.close(); | |
// load client private key | |
reader = new PEMReader( | |
new InputStreamReader(new ByteArrayInputStream(Files.readAllBytes(Paths.get(keyFile)))), | |
new PasswordFinder() { | |
@Override | |
public char[] getPassword() { | |
return password.toCharArray(); | |
} | |
} | |
); | |
KeyPair key = (KeyPair)reader.readObject(); | |
reader.close(); | |
// CA certificate is used to authenticate server | |
KeyStore caKs = KeyStore.getInstance(KeyStore.getDefaultType()); | |
caKs.load(null, null); | |
caKs.setCertificateEntry("ca-certificate", caCert); | |
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); | |
tmf.init(caKs); | |
// client key and certificates are sent to server so it can authenticate us | |
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); | |
ks.load(null, null); | |
ks.setCertificateEntry("certificate", cert); | |
ks.setKeyEntry("private-key", key.getPrivate(), password.toCharArray(), new java.security.cert.Certificate[]{cert}); | |
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); | |
kmf.init(ks, password.toCharArray()); | |
// finally, create SSL socket factory | |
SSLContext context = SSLContext.getInstance("TLSv1"); | |
context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); | |
return context.getSocketFactory(); | |
} | |
} |
In our scenario, we were connecting mqtt on the localhost
. So certificates were generated using OpenSSL. The above code I posted should work with the CA certificate.
I haven't encountered the --insecure
option as this was a bit of a slapdash proof of concept. If I am reading your query correctly I would assume to achieve what you want in your scenario change the CA cert file location below to the absolute path of the CA file:
static {
Instance = SslUtil.getSocketFactory(
"N:\\work\\acme\\mqtt-ssl\\messaging\\mqtt\\certs\\m2mqtt_ca.crt");
}
I also wrote a powershell script to generate the certificates using OpenSSL. I think this will do the trick to work with the java code above:
# Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
Set-Location (Split-Path $MyInvocation.MyCommand.Path -Parent)
$env:Path = $env:Path + ";" + $env:JAVA_HOME + "\bin"
Clear-Host
#
# create ca-cert
#
openssl req -new -x509 -days 3650 -key localhost_ca.key -out localhost_ca.crt -passin pass:password -subj "/C=GB/ST=Timbuktu/L=Timbuktu/O=acme/CN=localhost-CA"
#
# create server-key
#
openssl genrsa -out localhost_srv.key 2048
#
# create server-csr
#
openssl req -new -out localhost_srv.csr -key localhost_srv.key -passin pass:password -subj "/C=GB/ST=Timbuktu/L=Timbuktu/O=acme/CN=localhost"
#
# create server-crt
# verify and sign: server-csr = ca-crt + ca-key
#
openssl x509 -req -in localhost_srv.csr -CA localhost_ca.crt -CAkey localhost_ca.key -CAcreateserial -out localhost_srv.crt -passin pass:password -days 3650
#
# convert ca-cert to pfx/p12
#
openssl pkcs12 -export -in localhost_ca.crt -inkey localhost_ca.key -out localhost_ca.p12 -passin pass:password -passout pass:password
# verify
write-host "Verifying..."
openssl verify -CAfile localhost_ca.crt localhost_srv.crt
So in the above the CN
would be the name of your server that mosquito runs on.
@andez2000 thank you very much! the connection to the mosquitto broker is successful now 😃
@jkomericki - cool :) good work.
Hi @andez2000
I'm trying to use your code to connect to RabbitMQ using TLS v1.2, but I'm receiving this error: "javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure Reason code: 0". Have you ever encountered this error?
Thank you
In case you are getting Caused by: java.security.cert.CertificateException: No name matching XYZ found error, below will be very helpful to ignore server hostname verification. This will be helpful in testing localhost and certificates which you are sharing across servers.
Also it works with latest BouncyCastle version 1.68.
This will also support for client and server certificate verification as well only server certificate for clients who doesn't have client certs but only have rootCA using which server certs were signed.
https://gist.github.com/saumilsdk/1e17e30e33d0a18f44ce4e2b5841b281
Hi, did you manage to work through this, i am also stuck with this same problem, @andez2000 code doesnt work for me either