If you are familiar with the Python Paho MQTT library, implementing TLS is simple.
Prerequisites
- Ensure you have properly enabled TLS and created and configured TLS certificates. See Secure MQTT with certificates.
You must create a separate server certificate for each computer that runs a FairCom server. Each FairCom server must be configured to use the appropriate server certificate for the computer on which it runs - Each user must have a unique client certificate. A client certificate allows a client program to identify itself to a FairCom server. Each program that wants to use a certificate must be configured to send the certificate to the server.
Do not use a client certificate in a client program to connect to a FairCom server that is not configured with a certificate.
Configure the FairCom server
-
Configure the
MQTTSlistener in theservices.jsonfile to use your CA certificate, server certificate, and private key. - Ensure the
"enabled"property is set totrue. - Update the
"tls"object:- Update the
"certificateFilename"property value to the certificate you obtained for this FairCom server. - Add a
"privateKeyFilename"property and set it to the private key for this FairCom server. - Add a
"certificateAuthoritiesFilename"property and set it to the path and filename containing CA certificate.
- Update the
This is the certificate from the CA that signed the server certificate.
- Save any changes.
- Restart the server for the changes to take effect.
Example 1. MQTTS listener
{
"serviceName": "mqtts8883",
"description": "Port 8883 using TLS-secured MQTTS protocol for the MQTT broker on all TCP/IP addresses bound to this server",
"port": 8883,
"protocol": "mqtts",
"enabled": true,
"tls": {
"certificateAuthoritiesFilename": "C:/Certificates/ca.crt",
"certificateFilename": "C:/Certificates/server.crt",
"privateKeyFilename": "C:/Certificates/server.key"
}
},
Example 2. MQTTWSS listener
{
"serviceName": "mqttwss9002",
"description": "Port 9002 using TLS-secured WebSocket protocol for the MQTT broker on all TCP/IP addresses bound to this server",
"port": 9002,
"protocol": "mqttwss",
"enabled": true,
"tls": {
"certificateAuthoritiesFilename": "C:/Certificates/ca.crt",
"certificateFilename": "C:/Certificates/server.crt",
"privateKeyFilename": "C:/Certificates/server.key"
}
}
Configure the Paho Python client
Make the following updates to your MQTT code:
- Update the
"port"property value to the TLS port used by your broker. - Call
tls_set()on yourpaho.mqtt.clientclass object with the following parameters (see Example program):ca_certscertfilekeyfile
Establish communication
Build an MQTT project:
- Create a
callbackfunction that will print any messages received using the following code:
def message_callback( message_client, userdata, message ):
print( message.payload.decode( 'utf8' ) )
- Define a client using the following code:
To use a secure WebSocket, this call only needs the additional parameter of transport = 'websockets'.
mqtts_client = mqtt.Client( client_id = "MQTTS Client ID" )
- Enable TLS by calling
tls_set()on the newly created client using the following code:
mqtts_client.tls_set( ca_certs = "/Certificates/ca.crt",
certfile = "/Certificates/client.crt",
keyfile = "/Certificates/client.key" )
- Assign the message callback to the client using the following code:
There are many callbacks in the Paho library, including on_connect, on_connect_fail, on_disconnect, on_message, on_publish, on_subscribe, on_unsubscribe, on_log, on_socket_open, on_socket_close, on_socket_register_write, on_socket_unregister_write.
mqtts_client.on_message = message_callback
- Call
loop_start()on the client to begin processing MQTT events using the following code:
mqtts_client.loop_start()
- Connect the client and server using the following code:
The port is set to 8883. This must match the port configured in services.json. The broker address must also match the address in its certificate.
mqtts_client.connect( "MyServer.local", port = 8883 )
- Wait a few seconds or use the
on_connectcallback as a guide to when the connection has been made.
The connection will appear instantaneously, but work is happening in the background.
- Subscribe to a topic of your choice using the following code:
mqtts_client.subscribe( "test/MqttsTopic" )
- Publish to your chosen topic using the following code:
mqtts_client.publish( "test/MqttsTopic", "Simple MQTTS message" )
- Implement a simple non-blocking delay using the following code:
Even though the message will usually arrive in a few milliseconds, it is useful to give the message plenty of time to arrive, so your script does not execute step 12 before the message arrives.
count = 0
while count < 5:
time.sleep( 1 )
count += 1
- Observe the message:
Receiving the message means your client was able to subscribe and publish to your broker using TLS for encryption and authentication.
Simple MQTTS message
- Close the connection using the following code:
mqtts_client.unsubscribe( "test/MqttsTopic" )
mqtts_client.disconnect()
mqtts_client.loop_stop()
Example program
import time
import paho.mqtt.client as mqtt # If this fails, do "pip install paho-mqtt"
import ssl
use_client_certificate = True # Change to False to log in to MQTT broker using a client username/password.
ca_certificate_file = "ca.crt" # File that holds the CA certificate which signed the server certificate used by the broker and the client certificate used by this script.
# These two are for logging into the broker with a client certificate.
client_certificate_file = "user1Client.pem" # If this also contains the client's private key, set 'client_key_file' to None.
client_key_file = None # If 'client_certificate_file' does not contain the private key, specify the private key file here. Otherwise, use None.
client_key_file_password = None # password to use if client's private key is encrypted. Otherwise, use None. If the key is encrypted and this is None, you will be asked for the password at runtime.
# These two are for logging into the broker with a username/password, instead of a client certificate.
client_username = "user1"
client_password = "pass1"
# Make some callback functions which set global status variables.
connected=subscribed=published=False
def on_connect( connect_client, userdata, flags, connect_reason_code, connect_properties = None ): # 'I connected to the MQTT broker' callback
global connected
print( "Connected!" )
connected = True
def on_disconnect( disconnect_client, userdata, rc ): # 'I lost my connection to the MQTT broker' callback
global connected
print( f"client disconnected with reason code {rc}" )
connected = False
def on_subscribe( sub_client, userdata, mid, granted_qos ): # 'I subscribed' callback
global subscribed
print( f"subscribed with qos={granted_qos}\n" )
subscribed = True
def on_unsubscribe( sub_client, userdata, mid ): # 'I unsubscribed' callback
global subscribed
print( "unsubscribed\n" )
subscribed = False
def on_message( message_client, userdata, message ): # 'message arrived' callback
print( "message received: ", str( message.payload.decode( "utf-8" ) ) )
def on_publish( pub_client, userdata, mid ): # 'I published message' callback
global published
print( f"data published mid={mid}\n" )
published = True
if __name__ == "__main__":
mqtts_client = mqtt.Client( client_id = "MQTTS Client ID" )
topic_name = "My Test Topic"
if use_client_certificate:
# Connect to the MQTT broker using a client certificate
mqtts_client.tls_set( ca_certs = ca_certificate_file,
certfile = client_certificate_file,
keyfile = client_key_file,
keyfile_password = client_key_file_password,
tls_version=ssl.PROTOCOL_TLSv1_2)
mqtts_client.tls_insecure_set(False)
else:
# Connect to the MQTT broker using a username/password
mqtts_client.tls_set( ca_certs = ca_certificate_file, tls_version=ssl.PROTOCOL_TLSv1_2)
mqtts_client.tls_insecure_set(False)
mqtts_client.username_pw_set(client_username, client_password) # specify the username and password
# Hook up the callback functions
mqtts_client.on_connect = on_connect
mqtts_client.on_disconnect = on_disconnect
mqtts_client.on_publish = on_publish
mqtts_client.on_subscribe = on_subscribe
mqtts_client.on_unsubscribe = on_unsubscribe
mqtts_client.on_message = on_message
# Connect to the MQTT broker and start Paho's event loop
print("Connecting to the MQTT broker...")
mqtts_client.connect( "localhost", port = 8883 )
mqtts_client.loop_start()
while not connected: # Wait until the connection completes
time.sleep( 0.5 )
print("Subscribing to topic...")
mqtts_client.subscribe( topic_name )
while not subscribed: # Wait until the subscription completes
time.sleep( 0.5 )
print("Publishing message to topic...")
mqtts_client.publish( topic_name, "Simple MQTT test message" )
while not published: # Wait until the publish completes
time.sleep( 0.1 )
for i in range(5): # Wait 5 seconds for the message to arrive
time.sleep( 1 )
print("Unsubscribing from the topic...")
mqtts_client.unsubscribe( topic_name )
while subscribed: # Wait until the un-subscription completes
time.sleep( 0.5 )
print("Disconnecting from the MQTT broker...")
mqtts_client.disconnect()
while connected: # Wait until the disconnect completes
time.sleep( 0.5 )
mqtts_client.loop_stop()
print("Done!")
Example Websocket program
import time
import paho.mqtt.client as mqtt
def message_callback( message_client, userdata, message ):
print( message.payload.decode( 'utf8' ) )
if __name__ == "__main__":
mqtts_client = mqtt.Client( client_id = "MQTTS Client ID", transport = 'websockets' )
mqtts_client.tls_set( ca_certs = "/Certificates/ca.crt",
certfile = "/Certificates/client.crt",
keyfile = "/Certificates/client.key" )
mqtts_client.on_message = message_callback
mqtts_client.loop_start()
mqtts_client.connect( "MyServer.local", port = 9002 )
time.sleep( 2 )
mqtts_client.subscribe( "test/MqttWssTopic" )
mqtts_client.publish( "test/MqttWssTopic", "Simple MQTT WSS message" )
count = 0
while count < 5:
time.sleep( 1 )
count += 1
mqtts_client.unsubscribe( "test/MqttWssTopic" )
mqtts_client.disconnect()
mqtts_client.loop_stop()