Skip to content

Instantly share code, notes, and snippets.

@sharonbn
Last active August 21, 2022 05:48
Show Gist options
  • Save sharonbn/4104301 to your computer and use it in GitHub Desktop.
Save sharonbn/4104301 to your computer and use it in GitHub Desktop.
SSL/TLS connection from Eclipse Paho Java client to mosquitto MQTT broker
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();
}
}
@tusharbapte
Copy link

exception throwing after line :KeyPair key = (KeyPair)reader.readObject();

org.bouncycastle.openssl.PEMException: problem parsing ENCRYPTED PRIVATE KEY: javax.crypto.BadPaddingException: pad block corrupted
at org.bouncycastle.openssl.PEMReader$EncryptedPrivateKeyParser.parseObject(Unknown Source)
at org.bouncycastle.openssl.PEMReader.readObject(Unknown Source)
at org.eclipse.paho.sample.mqttv3app.SslUtil.getSocketFactory(SslUtil.java:38)
at org.eclipse.paho.sample.mqttv3app.Sample.(Sample.java:194)
at org.eclipse.paho.sample.mqttv3app.Sample.main(Sample.java:137)
Caused by: javax.crypto.BadPaddingException: pad block corrupted

@sharonbn
Copy link
Author

sharonbn commented Jan 7, 2013

from this post
http://stackoverflow.com/questions/14054593/aes-decryption-javax-crypto-badpaddingexception-pad-block-corrupted-in-android
it seems like this is either an encoding problem or perhaps corrupted certificate file? if the client is in Windows it might be new-line character problem

@tusharbapte
Copy link

Hi sharonbn,

I have added above class file into my package but i am unable to use this class from my Sample.java(paho) file.

conOpt.setSocketFactory(SslUtil.getSocketFactory(CA_CERT_PATH,CLIENT_CERT_PATH,CLIENT_KEY_PATH, PASSWORD));

I am getting following error:
src/org/eclipse/paho/sample/mqttv3app/Sample.java:195: error: cannot find symbol
conOpt.setSocketFactory(SslUtil.getSocketFactory(CA_CERT_PATH,CLIENT_CERT_PATH, CLIENT_KEY_PATH, PASSWORD));
^
symbol: variable SslUtil
location: class Sample
1 error

@tusharbapte
Copy link

Hi sharonbn,

Can you provide steps to compile java paho client after adding SslUtil.java , i am using ant tool to compile paho client as given in link:
http://mobilave.info/blog/2012/Quick_start_guide_for_the_Paho_MQTT_Java_Client.html.

Thanks,
Tushar

@tusharbapte
Copy link

Hi sharonbn,

i am getting following error when compiling Sample.java

javac -cp /home/tushar/new_jar_paho/bcpkix-jdk15on-148.jar:/home/tushar/new_jar_paho/bcprov-jdk15on-148.jar:/home/tushar/new_jar_paho/nio_framework-1.1beta_all.jar:/tmp/Mqttv3ClientOut/ship/org.eclipse.paho.client.mqttv3.jar src/org/eclipse/paho/sample/mqttv3app/Sample.java

Note: src/org/eclipse/paho/sample/mqttv3app/Sample.java uses or overrides a deprecated API.

Note: Recompile with -Xlint:deprecation for details.

Thanks,
Tushar

@ziakhancluuz
Copy link

Hi,

I am trying to use paho library to connect to an ActiveMQ mqtt broker, I have followed the steps on the ActiveMQ site to generate some self signed certifcates, keystore files and some trust store files. I am not quite sure how all of them translate to the input for this class. as i don't have a caCertifcate and private key file. can I generate them using java "keytool" Any help would be really appreciated as I am really new to this and don't quite understand what i need to provide. All I am trying use the SSL for is the encryption and i don't really need to client authentication and I can blindly trust the server.

Thanks in advance,

Z

@anemethy
Copy link

anemethy commented Apr 2, 2015

Is there an update on this ??? The code is 2 years old and all of the bouncy castle libraries have changed. Please advise. Regards

@rohanag12
Copy link

I've written similar code using a newer version of the BouncyCastle library. Those looking for an update to this, check out https://gist.github.com/rohanag12/07ab7eb22556244e9698

@fgarsombke
Copy link

Thank you! Exactly what I was looking for :) Much Karma going your way.

@sadaf13
Copy link

sadaf13 commented Feb 3, 2016

Hey!! i tried the above solution but facing the following exception.

Exception in thread "main" MqttException (0) - javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
at org.eclipse.paho.client.mqttv3.internal.ExceptionHelper.createMqttException(ExceptionHelper.java:38)
at org.eclipse.paho.client.mqttv3.internal.ClientComms$ConnectBG.run(ClientComms.java:604)
at java.lang.Thread.run(Thread.java:745)
Caused by: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
at sun.security.ssl.Alerts.getSSLException(Alerts.java:154)
at sun.security.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.java:1979)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1086)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1332)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1359)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1343)
at org.eclipse.paho.client.mqttv3.internal.SSLNetworkModule.start(SSLNetworkModule.java:89)
at org.eclipse.paho.client.mqttv3.internal.ClientComms$ConnectBG.run(ClientComms.java:590)
... 1 more

@disharaval
Copy link

hey,I am facing following exception:
The specified SocketFactory type does not match the broker URI (32105)
at org.eclipse.paho.client.mqttv3.internal.ExceptionHelper.createMqttException(ExceptionHelper.java:31)
at org.eclipse.paho.client.mqttv3.MqttAsyncClient.createNetworkModule(MqttAsyncClient.java:368)
at org.eclipse.paho.client.mqttv3.MqttAsyncClient.createNetworkModules(MqttAsyncClient.java:331)
at org.eclipse.paho.client.mqttv3.MqttAsyncClient.connect(MqttAsyncClient.java:488)
at org.eclipse.paho.client.mqttv3.MqttClient.connect(MqttClient.java:238)
at com.ei.paho.client.subscriber.SslSubscriber.start(SslSubscriber.java:77)
at com.ei.paho.client.subscriber.SslSubscriber.main(SslSubscriber.java:39)

@rohitsureka
Copy link

@sadaf13 Were you able to get past that issue?
I'm getting the exact same exception. If yes, can you suggest me what you had to do to get around it?

@vishalkothari
Copy link

@dishraval I am getting exact same exception trace. Did you find any solution for the issue The specified SocketFactory type does not match the broker URI (32105)?

@wendyhi
Copy link

wendyhi commented Apr 27, 2016

In my case, changing the url of the mqtt server from tcp://1.2.3.4:8883 to ssl://1.2.3.4:8883 resolve the issue about
The specified SocketFactory type does not match the broker URI (32105)

@javaproject
Copy link

Hello, I am trying to connect to AWS iot using Paho Mqtt Java Client. I have to specify the client_id, but also configure the connection tls_set meaning that I will specify root certificate (.crt), certificate file (.pem.crt) and the private key (.pem.crt) . I have already implemented this on Python, but unfortunately I need it in Java. Is there anyway you could help me ? The URI that I have is an endpoint from Amazon like : .iot.us-west-2.amazonaws.com

How can I specify this in my code ??! Any kind of help would be appreciated. Thank you !!

@gokulrama
Copy link

gokulrama commented Dec 20, 2017

hello,I'm trying to connect to cloud using Paho Mqtt client.I wanted to attach .crt/.bks file to my android project,i used SSL utility class from (http://rijware.com/accessing-a-secure-mqtt-broker-with-android/) but getting MQTT fatal exception.I have pinned the certificate file in res/raw folder .I also want to know if there are options to skip keystore password.i used both below mentioned methods still getting MQTT fatal exception after 10 seconds of operation

method 1:
public static SSLSocketFactory getSocketFactory(int certificateId, String certificatePassword) {
SSLSocketFactory result = mSocketFactoryMap.get(certificateId); // check to see if already created
if ((null == result) && (null != mContext)) { // not cached so need to load server certificate
try {
KeyStore keystoreTrust = KeyStore.getInstance("BKS"); // Bouncy Castle
keystoreTrust.load(mContext.getResources().openRawResource(certificateId),certificatePassword.toCharArray());
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keystoreTrust);
SSLContext sslContext = SSLContext.getInstance("TLS");//tls
sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
result = sslContext.getSocketFactory();
mSocketFactoryMap.put(certificateId, result); // cache for reuse
} catch (Exception ex) {
// log exception
}
}
return result;
}
}

method 2:

public static SSLSocketFactory getSocketFactory (final String caCrtFile) 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();
// CA certificate is used to authenticate server
KeyStore caKs = KeyStore.getInstance(KeyStore.getDefaultType());
caKs.load(null, null);
caKs.setCertificateEntry("mosqca", caCert);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(caKs);

   finally, create SSL socket factory
   SSLContext context = SSLContext.getInstance("TLSv1");
   context.init(null, tmf.getTrustManagers(), null);
   return context.getSocketFactory();
}

@ketankr9
Copy link

ketankr9 commented Jun 7, 2018

  • I only want to connect using caFile file and not the client certificate and client key, please explain how to achieve this .
  • Does it also authenticates the domain name of the server? How do I activate the --insecure option, where the client does not authenticates the authenticity of the domain name because my broker is on local network and its ip is variable and i use self-signed certificate.

@btf1998
Copy link

btf1998 commented Sep 9, 2019

Hello, I am trying to connect to AWS iot using Paho Mqtt Java Client. I have to specify the client_id, but also configure the connection tls_set meaning that I will specify root certificate (.crt), certificate file (.pem.crt) and the private key (.pem.crt) . I have already implemented this on Python, but unfortunately I need it in Java. Is there anyway you could help me ? The URI that I have is an endpoint from Amazon like : .iot.us-west-2.amazonaws.com

How can I specify this in my code ??! Any kind of help would be appreciated. Thank you !!

Hey its a few years down the road, so I doubt this is still relevant to you, but I was just in the same boat you were in. Just use "ssl://xxx.iot.us-east-2.amazonaws.com:8883"

@andez2000
Copy link

andez2000 commented Nov 26, 2020

I'll leave this here as it works for me with Bouncy Castle and Paho Message Client:

Maven packages:

  • org.bouncycastle:bcpkix-jdk15on:1.67
  • org.bouncycastle:bcprov-jdk15on:1.52
  • org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5

Code

package acme.messaging;

import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMParser;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.Security;

public class SslUtil {

    public static final SSLSocketFactory Instance;

    static {
        Instance = SslUtil.getSocketFactory(
                "N:\\work\\acme\\mqtt-ssl\\messaging\\mqtt\\certs\\m2mqtt_ca.crt");
    }

    private static X509CertificateHolder loadCACert(String caCrtFile) throws IOException {
        PEMParser reader =
                new PEMParser(
                        new InputStreamReader(new ByteArrayInputStream(
                                Files.readAllBytes(Paths.get(caCrtFile)))));
        X509CertificateHolder caCert = (X509CertificateHolder) reader.readObject();
        reader.close();

        return caCert;
    }

    static SSLSocketFactory getSocketFactory(
            final String caCrtFile) {

        try {
            Security.addProvider(new BouncyCastleProvider());

            JcaX509CertificateConverter jcaX509CertificateConverter = new JcaX509CertificateConverter();

            X509CertificateHolder caCertificateHolder = loadCACert(caCrtFile);

            // CA certificate is used to authenticate server
            KeyStore caKs = KeyStore.getInstance(KeyStore.getDefaultType());
            caKs.load(null, null);
            caKs.setCertificateEntry("ca-certificate", jcaX509CertificateConverter.getCertificate(caCertificateHolder));
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            tmf.init(caKs);

            // finally, create SSL socket factory
            SSLContext context = SSLContext.getInstance("TLSv1.2");

            context.init(null, tmf.getTrustManagers(), null);

            return context.getSocketFactory();
        } catch (Exception ex) {
            return null;
        }
    }
}

Then connection wise, it is important to use the SSL in the uri:

String uri = "ssl://localhost:1884";
String clientId = "1002";

final MqttMessage mqttMessage = new MqttMessage();
String messageText = "Some data";
mqttMessage.setPayload(messageText.getBytes());

try (MqttClient client = new MqttClient(uri, clientId)) {
    if (!client.isConnected()) {
        final MqttConnectOptions options = new MqttConnectOptions();
        options.setUserName("acme-user");
        options.setPassword("acme-user".toCharArray());

        options.setSocketFactory(SslUtil.Instance);

        client.connect(options);
    }

    mqttMessage.setRetained(true);

    client.publish("test/writeme", mqttMessage);
}

@jkomericki
Copy link

  • I only want to connect using caFile file and not the client certificate and client key, please explain how to achieve this .
  • Does it also authenticates the domain name of the server? How do I activate the --insecure option, where the client does not authenticates the authenticity of the domain name because my broker is on local network and its ip is variable and i use self-signed certificate.

Hi, did you manage to work through this, i am also stuck with this same problem, @andez2000 code doesnt work for me either

@andez2000
Copy link

andez2000 commented Jan 10, 2021

@jkomericki

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.

@jkomericki
Copy link

jkomericki commented Jan 10, 2021

@andez2000 thank you very much! the connection to the mosquitto broker is successful now 😃

@andez2000
Copy link

@jkomericki - cool :) good work.

@shebaloo
Copy link

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

@saumilsdk
Copy link

saumilsdk commented Mar 9, 2021

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment