Python Paho MQTT client zelf-ondertekende certificaten websockets, howto

Het gebruik van Paho in Python met websockets en zelf-ondertekende certificaten voor een https:// websocket connectie heeft een paar valkuilen, dus die pak ik hier aan met wat voorbeeld code:

ssl importeren
importtijd
importeer paho.mqtt.client als mqtt

klasse PCPMQTTClient:
     def on_connect(self, client, userdata, flags, rc):
         self.connection_status = rc
         als rc == 0:
             self.connected_flag = True
             self.connection_error_flag = False
         anders:
             self.connected_flag = False
             self.connection_error_flag = True

    # opgeroepen wanneer een bericht dat via publish() moest worden verzonden
     # heeft verzending naar de makelaar voltooid
     # voor qos=0 -> bericht heeft de client verlaten
     # voor qos=1 & 2 -> handshakes zijn voltooid.
     # mid variabele komt overeen met mid variabele die publish() teruggeeft
     def on_publish(self, client, userdata, mid):
         als self.verbose:
             print("callback midden publiceren: " + str(mid))

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

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

    def connect(self, test=False):
         als test:
             print("Verbinding maken met MQTT", end=")
         self.connection_status = -1 # nog geen verbindingspogingen
         self.connection_error_flag = False
         self.mqttc.connect_async(
             host=self.connectiondata['host'],
             port=self.connectiondata['port'],
             keepalive=60,
             bind_adres="")

        self.mqttc.loop_start() # volgens Steve beter AFTER connect
         doorgaan_vlag = Onwaar
         keep_connection_flag = geen test
         timeout_counter = 0
         terwijl niet doorgaan_vlag:
             time.sleep(1)
             timeout_counter += 1
             als test:
                 print(".", end=") # end=" onderdrukt newline
             als self.connected_flag:
                 doorgaan_vlag = Waar
             als timeout_counter > 6:
                 doorgaan_vlag = Waar
                 keep_connection_flag = False
         code = self.connection_status
         succes = Onwaar
         als code == 0:
             succes = Waar
             message = "Verbinding geslaagd!"
             self.connected_flag = True
             if not keep_connection_flag: # nodig om dit te zijn, om te detecteren of we niet in staat zijn om verbinding te maken!
                 self.disconnect() # verbreek de verbinding met de server
         elif code == -1:
             message = "Connection timed out - server niet online? Certificaatfout (probeer -selfsigned)?"
         elif code == 1:
             message = "Verbinding geweigerd - onjuiste protocolversie"
         elif code == 2:
             message = "Verbinding geweigerd - ongeldige client-identifier"
         elif code == 3:
             message = "Verbinding geweigerd - server niet beschikbaar"
         elif code == 4:
             message = "Verbinding geweigerd - slechte gebruikersnaam of wachtwoord"
         elif code == 5:
             message = "Verbinding geweigerd - niet gemachtigd"
         anders:
             message = "Nog een fout " + str(code) + " opgetreden, gelieve de Paho-documentatie te raadplegen"
         print(")
         keer terug {"succes": succes, "code": code, "bericht": bericht}

    def publish(self, topic, message, qos=0, retain=False):
         succes = Onwaar
         code = "PCP_WRONG_INPUTFORMAT
         error_message = "Verkeerd invoerformaat van gegevens (bv. onderwerp kan niet Null zijn)
         als onderwerp:
             pubresult = self.mqttc.publish(
                 onderwerp=onderwerp,
                 payload=bericht,
                 qos=qos,
                 behouden=behouden)
             # proces pubresult
             als pubresult.rc == mqtt.MQTT_ERR_SUCCESS:
                 code = "MQTT_ERR_SUCCESS
                 error_message = "Succesvol bericht naar makelaar verzonden.
                 succes = Waar
             elif pubresult.rc == mqtt.MQTT_ERR_NO_CONN:
                 code = "MQTT_ERR_NO_CONN
                 error_message = "Geen verbinding met MQTT-server, probeer opnieuw te verbinden
             elif pubresult.rc == mqtt.MQTT_ERR_QUEUE_SIZE:
                 code = "MQTT_ERR_QUEUE_SIZE
                 error_message = "Bericht is noch in de wachtrij geplaatst noch verzonden.

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

    def disconnect(self):
         # zoals per 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 = verbindingsgegevens
         self.connected_flag = False
         self.mqttc.on_connect = self.on_connect
         self.mqttc.on_publish = self.on_publish
         als self.verbose:
             print("### logging inschakelen voor MQTT-client ###")
             self.mqttc.on_log = self.on_log
         self.mqttc.username_pw_set(
             gebruikersnaam=connectiegegevens['gebruikersnaam'],
             password=connectiondata['password'])
         als connectiondata['transport'] == "websockets":
             self.mqttc.ws_set_options(path=self.connectiondata['path'])
         als self.verbose:
             print("### verbinding maken met de volgende parameters ###")
             print("* client_id: " + connectiondata['client_id'])
             print("* clean_session: " + str(clean_session))
             print("* transport: " + connectiondata['transport'])
             print("* pad: " + connectiondata['pad'])
             print("*gebruikersnaam: " + connectiondata['gebruikersnaam'])
             print("* wachtwoord: (wordt niet getoond!)")
         # self.mqttc.tls_set_context()
         als het niet veilig is:
             self.mqttc.tls_set(cert_reqs=ssl.CERT_NONE)
             als self.verbose:
                 print("### onveilige bedieningsmodus (-selfsigned) ingesteld! ###")
             self.mqttc.tls_insecure_set(True)
         anders:
             self.mqttc.tls_set()

Zoals u ziet, is het belangrijk om TWEE dingen te doen voor een onveilige verbinding:

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

Met de aanroep tls_insecure_set(True) kunt u het matchen van certificaatnamen achterwege laten. Het zal nog steeds proberen om ervoor te zorgen dat het certificaat geldig volgens de certificaatketen!

Dus het is een beetje een verkeerde benaming, denk ik.

De voorbeeldcode hierboven is onderdeel van een ontwikkeling aan picockpit-client, voor de picockpit.com platform

Paho / Websockets

connectiondata['transport'] wordt elders in de code ingesteld. Het kan ofwel (een string) "tcp" of "websockets" zijn.

Als je websockets specificeert, kun je ook ws_set_options gebruiken om een pad in te stellen, bijvoorbeeld "/mqtt".

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

de host is je webhost - zonder http / https voorvoegsel. Voor lokale ontwikkeling, bijvoorbeeld, gebruik ik "picockpit.local" (geproxied via envoy, zie mijn andere artikelen).

de poort, in het geval van een TLS / SSL verbinding over de standaard https poort is 443

Suggestie voor makkelijker debuggen:

Voor betere debugging schakel over van de async verbindingscode naar een synchrone verbinding (vervang connect_async):

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

Referenties: