Я пытаюсь использовать сервер GoLang Mochi MQTT в Windows 11 (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).
Я настраиваю локальную инфраструктуру PKI на компьютере с Windows 11 с помощью библиотеки Python PKI (https://github.com/BrixIT/Python-PKI).
Я создаю корневой центр сертификации с библиотекой PKI и двумя клиентскими сертификатами.
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.
Я немного изменил пример сервера Mochi MQTT для TLS, чтобы он выглядел следующим образом.
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. Что я делаю не так и как это исправить? Спасибо.
Основываясь на утверждении «Тип аутентификации, который я хотел бы использовать, — TLS», я считаю, что вы намерены использовать самозаверяющие сертификаты для двух целей:
Dev Cert 2.cert
.Dev Cert 1
для аутентификации клиента).Оба сертификата подписаны одним и тем же центром сертификации, что может немного запутать (в производственной среде, вероятно, лучше использовать отдельные центры сертификации). Таким образом, в этом случае сервер (для проверки сертификата клиента) и клиент (для проверки сертификата сервера) должны доверять ЦС (который вы создали с помощью Python-PKI).
Теперь вы говорите: «Я скопировал сертификаты клиента в каталог тестового клиента Python Paho». Я предполагаю, что это означает, что вы также скопировали ca.cert.pem
, на который есть ссылка в вашем коде Go (если нет, вам нужно будет это сделать), и что это сертификат CA, сгенерированный Python-PKI (это означает, что на первый взгляд , сервер выглядит нормально).
Кстати, вероятно, полезно делать это шаг за шагом. то есть
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
не подписывал сертификат сервера (оба сертификата были подписаны одним и тем же центром сертификации)Итак, я думаю, вам нужно что-то вроде (сокращенно):
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 в сертификате сервера соответствует имени хоста (сертификат по-прежнему будет проверяться на предмет его действительности и выдачи доверенным центром сертификации). При необходимости вы можете изменить это с помощью параметра cert_reqs
(по умолчанию — ssl.CERT_REQUIRED).
Надеюсь, вышеизложенное поможет, если нет, то попробуйте упростить свой вопрос (например, сосредоточьтесь на сервере и протестируйте с помощью MQTTX
), поскольку здесь много переменных.
Обновление: несколько комментариев о том, почему для сервера и клиента будет использоваться отдельный центр сертификации? Цель здесь состоит в том, чтобы создать закрытую систему, в которой только клиенты, использующие сертификат, который подписывает мой собственный центр сертификации, могут аутентифицироваться на сервере MQTT.
Сертификаты клиента и сервера выполняют разные роли, и, на мой взгляд, единственное преимущество использования одного и того же центра сертификации заключается в том, что это упрощает задачу (немного!). Вот несколько причин, почему их разделение может быть хорошей идеей:
server-ca.pem
, который дает понять, что он используется для проверки сертификата сервера).Это несколько мыслей, которые пришли мне в голову.
Не могли бы вы дать несколько комментариев о том, почему отдельный центр сертификации будет использоваться как для сервера, так и для клиента? Причина, по которой я спрашиваю, заключается в том, что цель здесь состоит в том, чтобы создать закрытую систему, в которой только клиенты, использующие сертификат, который подписывает мой собственный центр сертификации, могут аутентифицироваться на сервере MQTT. Спасибо.
Спасибо за этот очень полезный ответ. Мне удалось заставить все работать, предоставив сертификат и ключ ca.cert.pem, Dev Cert 1, как вы предложили.