我正在尝试在 Windows 11 上使用 GoLang Mochi MQTT 服务器 (https://github.com/mochi-mqtt/server) 和 Python Paho (https://pypi.org/project/paho-mqtt) /) 客户端在同一台机器上进行测试。我想使用的身份验证类型是 TLS (https://github.com/mochi-mqtt/server/blob/main/examples/tls/main.go)。
我使用 Python PKI 库在 Windows 11 计算机上设置本地 PKI 基础设施 (https://github.com/BrixIT/Python-PKI)。
我使用 PKI 库和 2 个客户端证书创建一个根 CA。
developer@docker-desktop:Python-PKI$ python3 pypki.py list
+---------------+----------------+---------+---------------------+----------+
| Common name | Organisation | State | Expiration date | Serial |
|---------------+----------------+---------+---------------------+----------|
| Dev Cert 1 | Me Co. | Valid | 2034-05-01 18:52:04 | 1000 |
| Dev Cert 2 | Me Co. | Valid | 2034-05-01 18:54:12 | 1001 |
+---------------+----------------+---------+---------------------+----------+
我将客户端证书复制到 Python Paho 测试客户端目录以及 GoLang MQTT 服务器目录。
我稍微修改了 TLS 的 Mochi MQTT 服务器示例,如下所示。
package main
import (
"crypto/tls"
"crypto/x509"
"github.com/mochi-mqtt/server/v2/packets"
"io/ioutil"
"log"
"os"
"os/signal"
"syscall"
mqtt "github.com/mochi-mqtt/server/v2"
"github.com/mochi-mqtt/server/v2/hooks/auth"
"github.com/mochi-mqtt/server/v2/listeners"
)
func main() {
sigs := make(chan os.Signal, 1)
done := make(chan bool, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigs
done <- true
}()
cer, loadCertErr := tls.LoadX509KeyPair("Dev Cert 2.cert.pem", "Dev Cert 2.key.pem")
if loadCertErr != nil {
log.Println(loadCertErr)
os.Exit(1)
}
// Optionally, if you want clients to authenticate only with certs issued by your CA,
// you might want to use something like this:
certPool := x509.NewCertPool()
caCertPem, certErr := ioutil.ReadFile("ca.cert.pem")
if certErr != nil {
log.Println("Error: Failed to subscribe to direct topic", "error", certErr)
os.Exit(1)
}
_ = certPool.AppendCertsFromPEM(caCertPem)
tlsConfig := &tls.Config{
ClientCAs: certPool,
ClientAuth: tls.RequireAndVerifyClientCert,
Certificates: []tls.Certificate{cer},
}
server := mqtt.New(&mqtt.Options{
InlineClient: true,
})
_ = server.AddHook(new(auth.AllowHook), nil)
log.Println("Listening on :1883")
tcp := listeners.NewTCP(listeners.Config{
ID: "t1",
Address: ":1883",
TLSConfig: tlsConfig,
})
err := server.AddListener(tcp)
if err != nil {
log.Fatal(err)
}
log.Println("Listening on :1882")
ws := listeners.NewWebsocket(listeners.Config{
ID: "ws1",
Address: ":1882",
TLSConfig: tlsConfig,
})
err = server.AddListener(ws)
if err != nil {
log.Fatal(err)
}
log.Println("Listening on :8080")
stats := listeners.NewHTTPStats(
listeners.Config{
ID: "stats",
Address: ":8080",
TLSConfig: tlsConfig,
}, server.Info,
)
err = server.AddListener(stats)
if err != nil {
log.Fatal(err)
}
log.Println("Running server")
go func() {
err := server.Serve()
if err != nil {
log.Fatal(err)
}
}()
// Inline subscribe
callbackFn := func(cl *mqtt.Client, sub packets.Subscription, pk packets.Packet) {
server.Log.Info("inline client received message from subscription", "client", cl.ID, "subscriptionId", sub.Identifier, "topic", pk.TopicName, "payload", string(pk.Payload))
}
subscribeErr := server.Subscribe("direct/#", 1, callbackFn)
if subscribeErr != nil {
server.Log.Error("Error: Failed to subscribe to direct topic", "error", subscribeErr)
os.Exit(1)
}
// Inline publish
publishErr := server.Publish("direct/publish", []byte("packet scheduled message"), false, 0)
if publishErr != nil {
server.Log.Error("Error: Failed to publish to direct topic", "error", publishErr)
os.Exit(1)
}
<-done
server.Log.Warn("caught signal, stopping...")
_ = server.Close()
server.Log.Info("main.go finished")
}
我还修改了 Paho MQTT 客户端示例以连接到 MQTT 服务器。这一切都是在同一台 Windows 11 计算机上完成的。
import time
import paho.mqtt.client as paho
import ssl
import pathlib
#define callbacks
def on_message(client, userdata, message):
print("received message =",str(message.payload.decode("utf-8")))
def on_log(client, userdata, level, buf):
print("log: ",buf)
def on_connect(client, userdata, flags, rc):
print("publishing ")
client.publish("muthu","muthupavithran",)
client=paho.Client()
client.on_message=on_message
client.on_log=on_log
client.on_connect=on_connect
print("connecting to broker")
client.tls_set(str(pathlib.Path().cwd() / pathlib.Path("Python-PKI/certs/Dev Cert 1.cert.pem")), tls_version=ssl.PROTOCOL_TLSv1_2)
client.tls_insecure_set(True)
client.connect("localhost", 1883, 60)
##start loop to process received messages
client.loop_start()
#wait to allow publish and logging and exit
time.sleep(1)
当 GoLang 服务器运行并且 Paho 测试客户端连接时,服务器打印以下错误:
time=2024-05-04T13:11:03.354-05:00 level=INFO msg="added hook" hook=allow-all-auth
time=2024-05-04T13:11:03.357-05:00 level=INFO msg="attached listener" id=t1 protocol=tcp address=[::]:1883
time=2024-05-04T13:11:03.357-05:00 level=INFO msg="attached listener" id=ws1 protocol=wss address=:1882
time=2024-05-04T13:11:03.357-05:00 level=INFO msg="attached listener" id=stats protocol=https address=:8080
time=2024-05-04T13:11:03.357-05:00 level=INFO msg="inline client received message from subscription" client=inline subscriptionId=1 topic=direct/publish payload="packet scheduled message"
time=2024-05-04T13:11:03.357-05:00 level=INFO msg="mochi mqtt starting" version=2.6.3
time=2024-05-04T13:11:03.359-05:00 level=INFO msg="mochi mqtt server started"
2024/05/04 13:11:03 Listening on :1883
2024/05/04 13:11:03 Listening on :1882
2024/05/04 13:11:03 Listening on :8080
2024/05/04 13:11:03 Running server
time=2024-05-04T13:11:20.865-05:00 level=WARN msg="" listener=t1 error="read connection: remote error: tls: unknown certificate authority"
此外,Paho 客户端打印以下内容:
C:\Users\me\go-mqtt-server\test\client.py:17: DeprecationWarning: Callback API version 1 is deprecated, update to latest version
client=paho.Client()
connecting to broker
Traceback (most recent call last):
File "C:\Users\me\go-mqtt-server\test\client.py", line 24, in <module>
client.connect("localhost", 1883, 60)
File "C:\Users\me\go-mqtt-server\test\Lib\site-packages\paho\mqtt\client.py", line 1435, in connect
return self.reconnect()
^^^^^^^^^^^^^^^^
File "C:\Users\me\go-mqtt-server\test\Lib\site-packages\paho\mqtt\client.py", line 1598, in reconnect
self._sock = self._create_socket()
^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\me\go-mqtt-server\test\Lib\site-packages\paho\mqtt\client.py", line 4612, in _create_socket
sock = self._ssl_wrap_socket(sock)
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\me\go-mqtt-server\test\Lib\site-packages\paho\mqtt\client.py", line 4671, in _ssl_wrap_socket
ssl_sock.do_handshake()
File "C:\Users\me\AppData\Local\Programs\Python\Python311\Lib\ssl.py", line 1346, in do_handshake
self._sslobj.do_handshake()
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1002)
我希望客户端只能使用本地 PKI CA 签名的证书进行连接。我做错了什么以及如何解决它?谢谢你。
基于“我想使用的身份验证类型是 TLS”这一说法,我相信您的意图是将自签名证书用于两个目的:
Dev Cert 2.cert
。Dev Cert 1
来验证客户端)。两个证书均由同一 CA 签名,这可能会让人有点困惑(在生产中,最好使用单独的 CA)。因此,在这种情况下,服务器(用于验证客户端证书)和客户端(用于验证服务器证书)需要信任 CA(您使用 Python-PKI 创建的)。
现在您说“我将客户端证书复制到了 Python Paho 测试客户端目录”。我假设这意味着您还复制了 Go 代码中引用的
ca.cert.pem
(如果没有,您将需要执行此操作),并且这是 Python-PKI 生成的 CA 证书(这意味着,在乍一看,服务器看起来不错)。
顺便说一句 - 一步一步地执行此操作可能会有所帮助。即
mochi-server
,使用MQTTX之类的东西进行测试,然后使用Python进行测试。mochi-server
中添加客户端证书的要求,测试如上。目前客户端和服务器很可能都存在问题,所以最好逐个测试。
无论如何,在你的Python代码中你调用:
client.tls_set(str(pathlib.Path().cwd() / pathlib.Path("Python-PKI/certs/Dev Cert 1.cert.pem")), tls_version=ssl.PROTOCOL_TLSv1_2)
client.tls_insecure_set(True)
这里有几个问题:
tls_set
仅被传递 ca_certs
(第一个参数)。这意味着不会将客户端证书发送到服务器(因此如果到达该部分将会失败)。ca_certs
未签署服务器证书(两个证书均由同一 CA 签署)所以,我想,你想要类似的东西(缩写):
client.tls_set("Python-PKI/certs/ca.cert.pem", "Python-PKI/certs/Dev Cert 1.cert.pem", "Python-PKI/certs/Dev Cert 1.cert.key", tls_version=ssl.PROTOCOL_TLSv1_2)
client.tls_insecure_set(True)
请注意,如果您自己颁发这些证书,则实际上不需要
tls_insecure_set(True)
(您可以将 CN 设置为 localhost
)。此设置仅控制(稍微简化)客户端是否确认服务器证书中的 CN 与主机名匹配(仍将检查证书以确保其有效并由受信任的 CA 颁发)。如果需要,您可以使用 cert_reqs
参数更改此设置(默认为 ssl.CERT_REQUIRED
)。
希望上述内容有所帮助,如果没有,请尝试简化您的问题(例如,专注于服务器并使用
MQTTX
进行测试),因为这里有很多变量。