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 mqttclasse 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 = Falsodef __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:
- https://github.com/eclipse/paho.mqtt.python/issues/85
- https://github.com/eclipse/paho.mqtt.python/issues/148
- https://aws.amazon.com/blogs/iot/how-to-implement-mqtt-with-tls-client-authentication-on-port-443-from-client-devices-python/
- https://www.eclipse.org/paho/clients/python/docs/ - a documentação oficial do Paho Python