0

I am having issue connecting through SSL to my mosquitto broker. I have configured the broker correctly as it connects fine to my embedded device using lwIP mqtt client service. Sadly, i cannot use the same code on my android device.

I started down the rabbit hole...

Investigating potential clients for the android app; Paho client seemed the reasonable application software as it is part of the eclipse suite; as is the mosquitto broker. I spent many hours on the paho application, but i hit a wall that couldnt be overcome; the repo is just not up to date with the new androidX platform and the client would disconnect itself, i would get a proper certification and connection; the client would connect to the broker (YES!) but then Android would disconnect it and barf out the following error;

com.example.pahoclient: Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent.

i investigated this; turns out this is not easily solved as the source code for the library is not easily modified. (At least not by someone with as little java experience as myself.) I tried to work through many "solutions" before giving up.

I then decided to try the HiveMQ client for android. While this seems a lot more current and active, I am unable to get the SSL cert to be received by my broker. its the same cert file but uses slightly modified functions. I will outline both codes here, starting with the successful client + broker connection.

my mosquitto broker is set up the following way;

listener 8883
#listener 1883
cafile certs/ca.crt
certfile certs/server.crt
keyfile certs/server.key
protocol mqtt
tls_version tlsv1.2


require_certificate false
allow_anonymous false
password_file certs/password
max_keepalive 5000

I load only the CA cert on the client. Hardcoded as follows;

package com.example.pahoclient

//import info.mqtt.android.service.MqttAndroidClient;
import android.app.Notification
import android.content.Context
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import org.eclipse.paho.android.service.MqttAndroidClient
import org.eclipse.paho.android.service.MqttService
import org.eclipse.paho.client.mqttv3.*
import java.security.KeyStore
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.TrustManagerFactory


class MQTTClient(context: Context,
                 serverURI: String = "ssl://example.com:8883",
                 clientID: String = "nonNull") {
    companion object {
        private const val TAG = "MQTTClient"
        private val certString: String = "-----BEGIN CERTIFICATE-----\n" +
                ...
                "rtVtZNE+luuMaDyGQYkNt3d1S3TWFVgd\n" +
                "-----END CERTIFICATE-----\n"

        private fun createSSLSocketFactory(): SSLSocketFactory? {
            val cf = CertificateFactory.getInstance("X.509")
            val cert = cf.generateCertificate(certString.byteInputStream()) as X509Certificate

            val ks = KeyStore.getInstance(KeyStore.getDefaultType())
            ks.load(null, null)
            ks.setCertificateEntry("ca", cert)

            val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
            tmf.init(ks)

            val sslContext = SSLContext.getInstance("TLS")
            sslContext.init(null, tmf.trustManagers, null)
            return sslContext.socketFactory
        }
    }

    private var mqttClient = MqttAndroidClient(context, serverURI, clientID)

    fun connect(username:   String               = "...",
                password:   String               = "...",
                cbConnect:  IMqttActionListener  = defaultCbConnect,
                cbClient:   MqttCallback         = defaultCbClient

    ) {

        mqttClient.setCallback(cbClient)

        val options = MqttConnectOptions()
        options.userName = username
        options.password = password.toCharArray()
        options.isCleanSession = false
        options.socketFactory = createSSLSocketFactory()
        options.keepAliveInterval = 20


        try {
            mqttClient.connect(options, null, cbConnect)
        } catch (e: MqttException) {
            e.printStackTrace()
        }
    }

no client cert is used. This is sufficient for my embedded device+lwIP; however, I would get the following output from the mosquitto broker when trying with the android device;

1676858808: mosquitto version 2.0.15 running
1676858841: New connection from 207.216.33.43:54808 on port 8883.
<Client disconnected>...

This was accompanied by the FLAG_MUTABLE... error in my android console..

com.example.pahoclient: Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent.

This tells me the cert is correctly parsed and forwarded , however when i try to use a similiar setup with the HiveMQ client, the cert does not get through and i get the following output in the console;

1676863126: New connection from 207.216.33.43:54922 on port 8883.
1676863126: OpenSSL Error[0]: error:14094416:SSL routines:ssl3_read_bytes:sslv3 alert certificate unknown
1676863126: Client <unknown> disconnected: Protocol error.

Here is the HiveMQ code;


package com.example.pahoclient


import android.R.attr.password
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.hivemq.client.mqtt.MqttClient
import com.hivemq.client.mqtt.mqtt3.Mqtt3AsyncClient
import java.security.KeyStore
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import javax.net.ssl.KeyManagerFactory
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.TrustManagerFactory


private val certString: String = "-----BEGIN CERTIFICATE-----\n"+
...
"-----END CERTIFICATE-----"

class MainActivity : AppCompatActivity() {

    private var client: Mqtt3AsyncClient? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

/**Call the socket factory**/

        //CA stuff
        val cf = CertificateFactory.getInstance("X.509")
        val cert = cf.generateCertificate(certString.byteInputStream()) as X509Certificate

        val ks = KeyStore.getInstance(KeyStore.getDefaultType())
        ks.load(null, null)
        ks.setCertificateEntry("ca", cert)

        val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
        tmf.init(ks)

        client = MqttClient.builder()
            .useMqttVersion3()
            .identifier("my-mqtt-client-id")
            .serverHost("******.com")
            .serverPort(8883)
            .sslConfig()
                .keyManagerFactory(null)
                .trustManagerFactory(tmf)
                 .applySslConfig()
            .buildAsync()

        client?.connectWith()
            ?.simpleAuth()
            ?.username("******")
            ?.password("*****".toByteArray())
            ?.applySimpleAuth()
            ?.send()
            ?.whenComplete { connAck, throwable ->
                if (throwable != null) {
                    // handle failure
                } else {
                    // setup subscribes or start publishing
                }
            }
    }

}

Why would my cert fail with one implementation and not the other? Is a client cert necessary with HiveMQ?

Both paho and hiveMQ use SSlSocketFactory, but HiveMQ has a requirement for KeyManagerFactory, which i have set to null.

Do I need to supply a client cert as well? I do have them created, but am unsure how i would go about implementing this.

Any suggestions?

I tried 2 different client, HiveMQ and Paho. I tried to connect HiveMQ with CA cert loaded as well as a client cert loaded.

1 Answers1

0

@terminalObserver, How to connect to a HiveMQ broker depends on its configuration. If you are using a HiveMQ Cloud broker (one you can register for free at http://cloud.hivemq.com), then to connect you need to add the CAfile to your client's Truststore (to establish an SSL connection) and supply the MQTT credentials username and password (to authenticate). If you are hosting a HiveMQ broker on-premise, then the way you connect really depends on the broker's configuration. I encourage you to ask your question in HiveMQ Community Forum and supply your broker configuration details, such as /opt/hivemq/conf/config.xml file.

Best regards, Dasha from HiveMQ Team

guinpin
  • 1
  • 4