Certificados Python Paho MQTT clientes autoassinados, howto

Usando Paho em Python com websockets e certificados autoassinados para uma conexão websocket https:// tem um par de armadilhas, então estou abordando isso com algum código de exemplo aqui:

ssl de importação
tempo de importação
importar paho.mqtt.client como mqtt

classe PCPMQTTCliente:
     def on_connect(self, client, userdata, flags, rc):
         self.connection_status = rc
         se rc == 0:
             self.connected_flag = Verdadeiro
             self.connection_error_flag = Falso
         senão..:
             self.connected_flag = Falso
             self.connection_error_flag = Verdadeiro

    # chamado quando uma mensagem que deveria ser enviada usando publish()
     # completou a transmissão para o corretor
     # para qos=0 -> a mensagem deixou o cliente
     # para qos=1 & 2 -> os apertos de mão foram concluídos.
     # variável média corresponde à variável média que publica() retorna
     def on_publish(self, client, userdata, mid):
         se for autobastante palavroso
             print("publish callback mid: " + str(mid))

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

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

    def connect(self, test=False):
         se testar:
             print("Connecting to MQTT", end=")
         self.connection_status = -1 # ainda sem tentativas de conexão
         self.connection_error_flag = Falso
         self.mqttc.connect_async(
             host=self.connectiondata['host'],
             port=self.connectiondata['port'],
             keepalive=60,
             bind_address="")

        self.mqttc.loop_start() # de acordo com Steve é melhor APÓS a conexão
         continue_flag = Falso
         keep_connection_flag = não testar
         timeout_counter = 0
         enquanto não continuar_flag:
             tempo.de sono(1)
             timeout_counter += 1
             se testar:
                 print(".", end=") # end=" irá suprimir a nova linha
             se auto.connected_flag:
                 continue_flag = Verdadeiro
             se timeout_counter > 6:
                 continue_flag = Verdadeiro
                 keep_connection_flag = Falso
         código = self.connection_status
         sucesso = Falso
         se código == 0:
             sucesso = Verdadeiro
             mensagem = "Conexão bem sucedida!"
             self.connected_flag = Verdadeiro
             se não manter a_flag_connection: # necessário para ser isto, para detectar se não somos capazes de nos conectar!
                 self.disconnect() # clean disconnect do servidor
         elif code == -1:
             mensagem = "Ligação temporizada - servidor não está online? Erro de certificado (try -selfsigned)"?
         elif code == 1:
             mensagem = "Ligação recusada - versão incorrecta do protocolo".
         elif code == 2:
             mensagem = "Ligação recusada - identificador de cliente inválido"
         elif code == 3:
             mensagem = "Ligação recusada - servidor indisponível"
         elif code == 4:
             mensagem = "Conexão recusada - mau nome de usuário ou senha".
         elif code == 5:
             mensagem = "Conexão recusada - não autorizada".
         senão..:
             mensagem = "Outro erro " + str(código) + " ocorreu, por favor verifique a documentação Paho".
         imprimir(")
         retornar {"sucesso": sucesso, "código": código, "mensagem": mensagem}

    def publish(self, topic, message, qos=0, retain=False):
         sucesso = Falso
         código = 'PCP_WRONG_INPUTFORMAT'.
         error_message = 'Wrong input format of data (e.g. topic cannot be Null)' (formato de entrada de dados errado (por exemplo, o tópico não pode ser Nulo)
         se tópico:
             pubresult = self.mqttc.publish(
                 topic=topic,
                 carga útil=mensagem,
                 qos=qos,
                 retain=retain)
             # processo pubresultado
             se pubresult.rc == mqtt.MQTT_ERR_SUCCESS:
                 código = 'MQTT_ERR_SUCCESS' (MQTT_ERR_SUCCESS)
                 error_message = 'Mensagem enviada com sucesso ao corretor'.
                 sucesso = Verdadeiro
             elif pubresult.rc == mqtt.MQTT_ERR_NO_CONN:
                 código = 'MQTT_ERR_NO_CONN'.
                 error_message = 'Sem conexão ao servidor MQTT, tente reconectar'.
             elif pubresult.rc == mqtt.MQTT_ERR_QUEUE_SIZE:
                 código = 'MQTT_ERR_QUEUE_SIZE'.
                 error_message = 'Mensagem não está enfileirada nem enviada'.

        retornar {"sucesso": sucesso, "código": código, "mensagem": mensagem_de_erro, "meio": pubresult.mid}

    def disconnect(self):
         # de acordo com steve http://www.steves-internet-guide.com/client-connections-python-mqtt/
         self.mqttc.loop_stop()
         self.mqttc.disconnect()
         self.connected_flag = Falso

    def __init__(self, connectiondata, verbose=Falso, inseguro=Falso):
         auto.verbose = verbose
         clean_session = Verdadeiro
         self.mqttc = mqtt.Cliente(
             client_id=connectiondata['client_id'],
             clean_session=clean_session,
             userdata=Nenhum,
             protocol=mqtt.MQTTv311,
             transport=connectiondata['transport'])
         self.connectiondata = dados de conexão
         self.connected_flag = Falso
         self.mqttc.on_connect = self.on_connect
         self.mqttc.on_publish = self.on_publish
         se for autobastante palavroso
             print("### permitindo o registo para MQTT Client ###")
             self.mqttc.on_log = self.on_log
         self.mqttc.username_pw_set(
             username=connectiondata['username'],
             password=connectiondata['password'])
         if connectiondata['transport'] == "websockets":
             self.mqttc.ws_set_options(path=self.connectiondata['path'])
         se for autobastante palavroso
             print("### conectando com os seguintes parâmetros ###")
             print("* client_id: " + connectiondata['client_id'])
             print("* clean_session: " + str(clean_session))
             print("* transport: " + connectiondata['transport'])
             print("* path: " + connectiondata['path'])
             print("* username: " + connectiondata['username'])
             print("* password: (não sendo mostrada!)")
         # self.mqttc.tls_set_context()
         se insegura:
             self.mqttc.tls_set(cert_reqs=ssl.CERT_NONE)
             se for autobastante palavroso
                 print("### inseguro modo de operação (-selfsigned) set! #%P3T#")
             self.mqttc.tls_insecure_set(Verdadeiro)
         senão..:
             self.mqttc.tls_set()

Como você pode ver, é importante fazer DUAS coisas para uma conexão insegura:

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

A chamada tls_insecure_set(True) irá permitir-lhe omitir a correspondência de nomes de certificados. Ele ainda tentará garantir que o certificado seja válido de acordo com a cadeia de certificados!

Portanto, é um nome um pouco errado, acho eu.

O código de amostra mostrado acima é parte de um desenvolvimento no picockpit-cliente, para a plataforma picockpit.com

Paho / Websockets

connectiondata['transporte'] é definido em outro lugar no código. Pode ser (uma string) "tcp" ou "websockets".

Se você especificar websockets, você pode usar ws_set_options para definir um caminho, por exemplo "/mqtt".

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

o anfitrião é o seu webhost - sem prefixo http / https. Para desenvolvimento local, por exemplo, eu uso "picockpit.local" (proxied through envoy, veja meus outros artigos).

a porta, no caso de uma conexão TLS / SSL sobre a porta https padrão é 443

Sugestão para facilitar a depuração:

Para melhor depuração do código de conexão async para uma conexão sincronizada (substitua connect_async):

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

Referências: