Search code examples
pythonsslmosquittopaho

Can send an SSL message via mosquitto but getting CERTIFICATE_VERIFY_FAILED from python paho script


Using mosquitto:

mosquitto_pub -h <public ip address> -t testssl/topic -m "hello  world" --cafile ./ca_certificate.pem -p 8883 --tls-version tlsv1.2 -d --id client2

Client client2 sending CONNECT
Client client2 received CONNACK (0)
Client client2 sending PUBLISH (d0, q0, r0, m1, 'testssl/topic', ... (12 bytes))
Client client2 sending DISCONNECT

Using python paho script:

# python 3.11
import datetime
import json
import random
import time
import uuid

from paho.mqtt import client as mqtt_client

from config import broker, port, topic

# Generate a Client ID with the publish prefix.
client_id = f'publish-{random.randint(0, 100)}'

def connect_mqtt():
    def on_connect(client, userdata, flags, rc):
        if rc == 0:
            print("Connected to MQTT Broker!")
        else:
            print("Failed to connect, return code %d\n", rc)

    client = mqtt_client.Client(client_id)
    client.tls_set(
        ca_certs="ca_certificate.pem",
        tls_version=mqtt_client.ssl.PROTOCOL_TLSv1_2,
    )
    # client.username_pw_set("rw", "readwrite")
    client.on_connect = on_connect
    client.connect(broker, port)
    return client

def fake_sensor_data():
    """Generate a fake sensor data event."""
    list_of_sensor_ids = [ uuid.uuid4() for _ in range(10) ]
    values = [ 0, 0, 0, 0, 0, 0, 1, 0, 0 ]
    return {
        "timestamp": time.time(),
        "device_id": str(list_of_sensor_ids[random.randint(0, 9)]),
        "event": {
            "payload": values[random.randint(0, 8)],
        }
    }



def publish(client):
    while True:
        time.sleep(3)
        msg = json.dumps(fake_sensor_data())
        result = client.publish(topic, msg)
        status = result[0]
        if status == 0:
            print()
            print(datetime.datetime.now(), msg)


def run():
    print("Connecting to MQTT Broker")
    print(f"Broker: {broker}")
    print(f"Port: {port}")
    print(f"Topic: {topic}")
    client = connect_mqtt()
    print("Connected!")
    print("Ready to publish messages!")
    client.loop_start()
    publish(client)
    client.loop_stop()


if __name__ == '__main__':
    run()

Error:

ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: IP address mismatch, certificate is not valid for '<public ip address>'. (_ssl.c:1006)

I generated the ca_certificate using tls-gen on my server in a script. Elsewhere I read that if I am providing a public ip address instead of a DNS name, I should be using a SAN, however tls-gen does not allow for me to specify one easily - would be good to not have to modify the server initiation script too much.

I would like to understand why mosquitto would work and python/paho needs a SAN for the same certificate file.


Solution

  • The reason mosquitto verifies the certificate can be found in the mosquitto__verify_certificate_hostname function in the file lib/tls_mosq.c:

    https://github.com/eclipse/mosquitto/blob/15292b20b0894ec7c5c3d47e4b22ee9d89f91132/lib/tls_mosq.c#L128

    This can be seen to check the SAN entries and only checks the CN if there is no SAN section in the certificate.

            if(have_san_dns){
                /* Only check CN if subjectAltName DNS entry does not exist. */
                return 0;
            }`
    
    

    So without explicitly asking Roger (mosquitto maintainer) I expect this is just mosquitto still supporting the long deprecated option to use the CN where as Python has dropped that option.

    Either way the best option is to produce a valid certificate with the correct SAN options (especially if using raw IP addresses)