GoLang Mochi MQTT 服务器与 Python Paho MQTT 客户端 TLS 身份验证验证失败

问题描述 投票:0回答:1

我正在尝试在 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 签名的证书进行连接。我做错了什么以及如何解决它?谢谢你。

go ssl mqtt tls1.2 paho
1个回答
0
投票

基于“我想使用的身份验证类型是 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
进行测试),因为这里有很多变量。

© www.soinside.com 2019 - 2024. All rights reserved.