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.