我在通过 SSL 连接到我的 mosquitto 代理时遇到问题。我已经正确配置了代理,因为它使用 lwIP mqtt 客户端服务可以很好地连接到我的嵌入式设备。可悲的是,我不能在我的安卓设备上使用相同的代码。
我开始钻进兔子洞……
调查安卓应用的潜在客户; Paho 客户端似乎是合理的应用程序软件,因为它是 eclipse 套件的一部分;蚊子经纪人也是如此。我在 paho 申请上花了很多时间,但我遇到了无法克服的障碍;回购协议只是没有与新的 androidX 平台保持同步,客户端会自行断开连接,我会获得适当的认证和连接;客户端将连接到代理(是!)但随后 Android 将断开连接并吐出以下错误;
com.example.pahoclient: Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent.
我调查过这个;事实证明这并不容易解决,因为库的源代码不容易修改。 (至少不是像我这样缺乏 Java 经验的人。)在放弃之前,我尝试了很多“解决方案”。
然后我决定尝试 android 的 HiveMQ 客户端。虽然这看起来更加最新和活跃,但我无法让我的经纪人收到 SSL 证书。它是相同的证书文件,但使用的功能略有修改。我将在这里概述这两个代码,从成功的客户端 + 代理连接开始。
我的mosquitto broker设置如下;
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
我只在客户端加载 CA 证书。硬编码如下;
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()
}
}
没有使用客户端证书。 这对我的嵌入式设备+lwIP 来说已经足够了;但是,在尝试使用 android 设备时,我会从 mosquitto 代理获得以下输出;
1676858808: mosquitto version 2.0.15 running
1676858841: New connection from 207.216.33.43:54808 on port 8883.
<Client disconnected>...
这伴随着 FLAG_MUTABLE... 我的 android 控制台中的错误..
com.example.pahoclient: Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent.
这告诉我证书已正确解析和转发,但是当我尝试对 HiveMQ 客户端使用类似的设置时,证书无法通过,我在控制台中得到以下输出;
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.
这里是HiveMQ代码;
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
}
}
}
}
为什么我的证书会因一种实施而不是另一种实施而失败? HiveMQ 是否需要客户端证书?
paho 和 hiveMQ 都使用 SSlSocketFactory,但 HiveMQ 对 KeyManagerFactory 有要求,我已将其设置为 null。
我还需要提供客户证书吗?我确实创建了它们,但不确定我将如何实施它。
有什么建议吗?
我尝试了 2 个不同的客户端,HiveMQ 和 Paho。 我尝试将 HiveMQ 连接到已加载的 CA 证书以及已加载的客户端证书。
@terminalObserver, 如何连接到 HiveMQ 代理取决于其配置。如果您使用的是 HiveMQ 云代理(您可以在 http://cloud.hivemq.com 免费注册),那么要连接,您需要将 CAfile 添加到客户端的信任库(以建立 SSL 连接)和提供 MQTT 凭据用户名和密码(以进行身份验证)。 如果您在本地托管 HiveMQ 代理,那么您连接的方式实际上取决于代理的配置。 我鼓励您在 HiveMQ 社区论坛 中提问,并提供您的代理配置详细信息,例如
/opt/hivemq/conf/config.xml
文件。
致以诚挚的问候, 来自 HiveMQ 团队的 Dasha