Python Paho MQTT-Client selbstsignierte Zertifikate Websockets, howto

Die Verwendung von Paho in Python mit Websockets und selbstsignierten Zertifikaten für eine https:// Websocket-Verbindung birgt ein paar Fallstricke, die ich hier mit einem Beispielcode anspreche:

ssl importieren
Einfuhrzeit
paho.mqtt.client als mqtt importieren

Klasse PCPMQTTClient:
     def on_connect(self, client, userdata, flags, rc):
         self.connection_status = rc
         wenn rc == 0:
             self.connected_flag = True
             self.connection_error_flag = False
         sonst:
             self.connected_flag = False
             self.connection_error_flag = True

    # wird aufgerufen, wenn eine Nachricht, die mit publish() gesendet werden sollte
     # hat die Übermittlung an den Makler abgeschlossen
     # für qos=0 -> Nachricht hat den Client verlassen
     # für qos=1 & 2 -> Handshakes wurden abgeschlossen.
     Die mittlere Variable # entspricht der mittleren Variable, die von publish() zurückgegeben wird.
     def on_publish(self, client, userdata, mid):
         if self.verbose:
             print("Rückruf Mitte veröffentlichen: " + str(mid))

    def on_log(client, userdata, level, buf):
         print(buf)

    def testconnection(self):
         return self.connect(test=True)

    def connect(self, test=False):
         wenn test:
             print("Verbindung zu MQTT herstellen", end=")
         self.connection_status = -1 # noch keine Verbindungsversuche
         self.connection_error_flag = False
         self.mqttc.connect_async(
             host=self.connectiondata['host'],
             port=self.connectiondata['port'],
             keepalive=60,
             bind_address="")

        self.mqttc.loop_start() # laut Steve besser AFTER connect
         weiter_flag = False
         keep_connection_flag = not test
         timeout_counter = 0
         solange nicht continue_flag:
             time.sleep(1)
             timeout_counter += 1
             wenn test:
                 print(".", end=") # end=" unterdrückt Zeilenumbruch
             if self.connected_flag:
                 weiter_flag = True
             wenn timeout_counter > 6:
                 weiter_flag = True
                 keep_connection_flag = False
         code = self.connection_status
         Erfolg = Falsch
         wenn Code == 0:
             Erfolg = Wahr
             message = "Verbindung erfolgreich!"
             self.connected_flag = True
             if not keep_connection_flag: # muss dies sein, um zu erkennen, wenn wir nicht in der Lage sind, eine Verbindung herzustellen!
                 self.disconnect() # saubere Trennung der Verbindung vom Server
         elif code == -1:
             message = "Zeitüberschreitung der Verbindung - Server nicht online? Zertifikatsfehler (versuchen Sie -selfsigned)?"
         elif code == 1:
             message = "Verbindung abgelehnt - falsche Protokollversion"
         elif code == 2:
             message = "Verbindung verweigert - ungültiger Client-Bezeichner"
         elif code == 3:
             message = "Verbindung abgelehnt - Server nicht verfügbar"
         elif code == 4:
             message = "Verbindung abgelehnt - falscher Benutzername oder Passwort"
         elif code == 5:
             message = "Verbindung abgelehnt - nicht autorisiert"
         sonst:
             message = "Ein weiterer Fehler " + str(code) + " ist aufgetreten, bitte lesen Sie die Paho-Dokumentation"
         print(")
         return {"Erfolg": Erfolg, "Code": Code, "Nachricht": Nachricht}

    def publish(self, topic, message, qos=0, retain=False):
         Erfolg = Falsch
         code = 'PCP_WRONG_INPUTFORMAT'
         error_message = 'Falsches Eingabeformat der Daten (z.B. Thema kann nicht Null sein)'
         wenn Thema:
             pubresult = self.mqttc.publish(
                 topic=topic,
                 Nutzlast=Nachricht,
                 qos=qos,
                 beibehalten=beibehalten)
             # Prozess-Ergebnis
             if pubresult.rc == mqtt.MQTT_ERR_SUCCESS:
                 code = 'MQTT_ERR_SUCCESS'
                 error_message = 'Nachricht erfolgreich an den Makler gesendet.'
                 Erfolg = Wahr
             elif pubresult.rc == mqtt.MQTT_ERR_NO_CONN:
                 code = 'MQTT_ERR_NO_CONN'
                 error_message = 'Keine Verbindung zum MQTT-Server, versuchen Sie, sich erneut zu verbinden'.
             elif pubresult.rc == mqtt.MQTT_ERR_QUEUE_SIZE:
                 code = 'MQTT_ERR_QUEUE_SIZE'
                 error_message = 'Die Nachricht steht weder in der Warteschlange noch wurde sie gesendet.

        return {"success": success, "code": code, "message": error_message, "mid": pubresult.mid}

    def disconnect(self):
         # wie von Steve http://www.steves-internet-guide.com/client-connections-python-mqtt/
         self.mqttc.loop_stop()
         self.mqttc.disconnect()
         self.connected_flag = False

    def __init__(self, connectiondata, verbose=False, insecure=False):
         self.verbose = verbose
         clean_session = True
         self.mqttc = mqtt.Client(
             client_id=connectiondata['client_id'],
             clean_session=clean_session,
             userdata=None,
             protocol=mqtt.MQTTv311,
             transport=connectiondata['transport'])
         self.connectiondata = connectiondata
         self.connected_flag = False
         self.mqttc.on_connect = self.on_connect
         self.mqttc.on_publish = self.on_publish
         if self.verbose:
             print("### aktiviert Protokollierung für MQTT-Client ###")
             self.mqttc.on_log = self.on_log
         self.mqttc.username_pw_set(
             username=connectiondata['username'],
             passwort=verbindungsdaten['passwort'])
         if connectiondata['transport'] == "websockets":
             self.mqttc.ws_set_options(path=self.connectiondata['path'])
         if self.verbose:
             print("### verbindet sich mit den folgenden Parametern ###")
             print("* client_id: " + connectiondata['client_id'])
             print("* clean_session: " + str(clean_session))
             print("* Transport: " + connectiondata['transport'])
             print("* Pfad: " + connectiondata['path'])
             print("* Nutzername: " + Verbindungsdaten['Nutzername'])
             print("* Passwort: (wird nicht angezeigt!)")
         # self.mqttc.tls_set_context()
         wenn sie unsicher sind:
             self.mqttc.tls_set(cert_reqs=ssl.CERT_NONE)
             if self.verbose:
                 print("### unsicherer Betriebsmodus (-selfsigned) eingestellt! ###")
             self.mqttc.tls_insecure_set(True)
         sonst:
             self.mqttc.tls_set()

Wie Sie sehen können, ist es wichtig, ZWEI Dinge für eine unsichere Verbindung zu tun:

  • tls_set(cert_reqs=ssl.CERT_NONE)
  • tls_insecure_set(True)

Mit dem Aufruf tls_insecure_set(True) können Sie den Abgleich der Zertifikatsnamen auslassen. Es wird immer noch versucht, sicherzustellen, dass das Zertifikat gültig gemäß der Zertifikatskette!

Daher ist der Begriff wohl ein wenig irreführend.

Der oben gezeigte Beispielcode ist Teil einer Entwicklung am picockpit-client, für den Plattform picockpit.com

Paho / Websockets

connectiondata['transport'] wird an anderer Stelle im Code festgelegt. Es kann entweder (ein String) "tcp" oder "websockets" sein.

Wenn Sie Websockets angeben, können Sie zusätzlich ws_set_options verwenden, um einen Pfad festzulegen, zum Beispiel "/mqtt".

            host=self.connectiondata['host'],
             port=self.connectiondata['port'],

der Host ist Ihr Webhost - ohne http / https Präfix. Für die lokale Entwicklung verwende ich z. B. "picockpit.local" (Proxy durch envoy, siehe meine anderen Artikel).

den Port, im Falle einer TLS/SSL-Verbindung über den Standard-HTTPS-Port 443

Vorschlag zur leichteren Fehlersuche:

Zur besseren Fehlersuche wechseln Sie vom asynchronen Verbindungscode zu einer synchronen Verbindung (ersetzen Sie connect_async):

        self.mqttc.connect(
            host=self.connectiondata['host'],
            port=self.connectiondata['port'],
            keepalive=60,
            bind_address="")

Referenzen: