The Internet of Things on AWS – Official Blog

How to Implement MQTT with TLS Client Authentication on Port 443 from Client Devices (Python)

Application Layer Protocol Negotiation (ALPN) is an extension to TLS that enables clients connecting to a TLS server to pass an extra parameter, known as a ProtocolNameList. The ProtocolNameList is a preference-ordered list of the application protocols that the client would like to use to communicate. AWS IoT Core now allows you to connect devices over MQTT with TLS client authentication on port 443 using the ALPN TLS extension. For background about why this is useful, see this blog post.

In this blog post, I will walk you through two ways to connect your devices to AWS IoT Core over MQTT on port 443.

Method 1: Using Paho-MQTT client and OpenSSL

Most common TLS implementations, including OpenSSL and mbedTLS support the ALPN TLS extension. In this example, we will use a Paho-mqtt client and the OpenSSL library to connect your devices to the AWS IoT endpoint.

Prerequisites

Before you connect your devices, check the software version of Python and OpenSSL to ensure they support ALPN extension.

To check your environment

1.     Check the OpenSSL version:

openssl version
OpenSSL 1.0.2k-fips  26 Jan 2017

2.     Check the Python version.

python --version
Python 2.7.13

3.     Check the version of OpenSSL that Python references.

python
>>> import ssl
>>> print ssl.OPENSSL_VERSION
OpenSSL 1.0.2k-fips  26 Jan 2017

*If the reference is to older version of OpenSSL, you have to update it.

This sample script uses Paho as the MQTT library to publish messages. The latest stable version of the Paho-MQTT client is available in Python Package Index (PyPi). Install it using pip:

pip install paho-mqtt

Each connected device must have a credential to access the message broker or the Device Shadow service. The sample script uses X.509 certificates as an authentication mechanism to connect to the AWS IoT endpoint. You can use the AWS IoT console or CLI to create an AWS IoT certificate. For more information, see Create and Register an AWS IoT Device Certificate in the AWS IoT Developer Guide and create-keys-and-certificate in the AWS CLI Command Reference.

The following very simple example creates a connection to the AWS IoT endpoint and publishes a message to it. Copy the following script into a file and save the file as alpn_mqtt.py.

from __future__ import print_function
import sys
import ssl
import time
import datetime
import logging, traceback
import paho.mqtt.client as mqtt

IoT_protocol_name = "x-amzn-mqtt-ca"
aws_iot_endpoint = "AWS_IoT_ENDPOINT_HERE" # <random>.iot.<region>.amazonaws.com
url = "https://{}".format(aws_iot_endpoint)

ca = "YOUR/ROOT/CA/PATH" 
cert = "YOUR/DEVICE/CERT/PATH"
private = "YOUR/DEVICE/KEY/PATH"

logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler(sys.stdout)
log_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(log_format)
logger.addHandler(handler)

def ssl_alpn():
    try:
        #debug print opnessl version
        logger.info("open ssl version:{}".format(ssl.OPENSSL_VERSION))
        ssl_context = ssl.create_default_context()
        ssl_context.set_alpn_protocols([IoT_protocol_name])
        ssl_context.load_verify_locations(cafile=ca)
        ssl_context.load_cert_chain(certfile=cert, keyfile=private)

        return  ssl_context
    except Exception as e:
        print("exception ssl_alpn()")
        raise e

if __name__ == '__main__':
    topic = "test/date"
    try:
        mqttc = mqtt.Client()
        ssl_context= ssl_alpn()
        mqttc.tls_set_context(context=ssl_context)
        logger.info("start connect")
        mqttc.connect(aws_iot_endpoint, port=443)
        logger.info("connect success")
        mqttc.loop_start()

        while True:
            now = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
            logger.info("try to publish:{}".format(now))
            mqttc.publish(topic, now)
            time.sleep(1)

    except Exception as e:
        logger.error("exception main()")
        logger.error("e obj:{}".format(vars(e)))
        logger.error("message:{}".format(e.message))
        traceback.print_exc(file=sys.stdout)

Run the Python script

Run the Python script you created by executing the following command:

python alpn_mqtt.py

2018-03-15 11:03:25,174 - root - INFO - start connect 
2018-03-15 11:03:25,254 - root - INFO - connect success 
2018-03-15 11:03:25,255 - root - INFO - published:<timestamp> 
2018-03-15 11:03:26,256 - root - INFO - published:<timestamp>

When you see the “connect success” and “published:< timestamp >” messages in the console, the connection to AWS IoT Core was successfully established  and the message was published.

If you see any errors in the execution of the script, check the AWS IoT endpoint or certificate information you provided.

Test whether AWS IoT received the client message

To confirm that AWS IoT receives the client message, sign in to the AWS IoT console. In the left navigation pane, choose Test, and then choose Subscribe. Subscribe to the test/date topic.

After you have subscribed, you will see published messages from the client device on the console every second, as shown here.

 

If your client device is running on Linux, you can use tcpdump to test.

 tcpdump port 443

Method 2: Using the AWS IoT Device SDK for Python

The AWS IoT Device SDK for Python allows developers to write a Python script to use their devices to access AWS IoT. Currently, you can choose either MQTT over TLS on port 8883 or MQTT over the WebSocket protocol on port 443. Support for MQTT on port 443 is not provided by default. You have to modify the Device SDK to enable the functionality. Because the OpenSSL library built with the Device SDK supports ALPN extension, to enable MQTT communication over port 443, you have to modify how the SSL library is configured. In this example, I show the changes you need to make in the Device SDK to connect to an AWS IoT endpoint over MQTT on port 443.

The AWS IoT Device SDK for Python is built on top of a modified Paho-MQTT Python client library. Modify the client.py file in the AWSIoTPythonSDK/core/protocol/paho/ folder.

Example:  /usr/local/lib/python2.7/site-packages/AWSIoTPythonSDK/core/protocol/paho/client.py

The changes that you need to make are shown here:

--- a/AWSIoTPythonSDK/core/protocol/paho/client.py
+++ b/AWSIoTPythonSDK/core/protocol/paho/client.py
@@ -787,15 +787,26 @@ class Client(object):
            self._ssl = SecuredWebSocketCore(rawSSL, self._host, self._port, self._AWSAccessKeyIDCustomConfig, self._AWSSecretAccessKeyCustomConfig, self._AWSSessionTokenCustomConfig)  # Overeride the _ssl socket
                 # self._ssl.enableDebug()
        else:
-           self._ssl = ssl.wrap_socket(
-               sock,
-               certfile=self._tls_certfile,
-               keyfile=self._tls_keyfile,
-               ca_certs=self._tls_ca_certs,
-               cert_reqs=self._tls_cert_reqs,
-               ssl_version=self._tls_version,
-               ciphers=self._tls_ciphers)
-
+           if self._port == 8883:
+               self._ssl = ssl.wrap_socket(
+                   sock,
+                   certfile=self._tls_certfile,
+                   keyfile=self._tls_keyfile,
+                   ca_certs=self._tls_ca_certs,
+                   cert_reqs=self._tls_cert_reqs,
+                   ssl_version=self._tls_version,
+                   ciphers=self._tls_ciphers)
+           else:
+               context = ssl.SSLContext(self._tls_version)
+               context.load_cert_chain(self._tls_certfile, self._tls_keyfile)
+               context.verify_mode = self._tls_cert_reqs
+               context.load_verify_locations(self._tls_ca_certs)
+               context.set_alpn_protocols(["x-amzn-mqtt-ca"])
+               
+               self._ssl = context.wrap_socket(sock, server_hostname=self._host, do_handshake_on_connect=False)
+                   
+               self._ssl.do_handshake()
+               
            if self._tls_insecure is False:
                if sys.version_info[0] < 3 or (sys.version_info[0] == 3 and sys.version_info[1] < 5):  # No IP host match before 3.5.x
                    self._tls_match_hostname()

After making the changes, create a simple Python script that creates a connection to the AWS IoT endpoint and publishes a message to it. For more information, see the AWS IoT Device SDK for Python.

# Import SDK packages
from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTClient

# For certificate based connection
myMQTTClient = AWSIoTMQTTClient("myClientID")

# Configure the MQTT Client
myMQTTClient.configureEndpoint(<YOUR_AWS_IOT_ENDPOIT>, 443)
myMQTTClient.configureCredentials("YOUR/ROOT/CA/PATH", YOUR/DEVICE/KEY/PATH ", " YOUR/DEVICE/CERT/PATH ")
myMQTTClient.configureOfflinePublishQueueing(-1)  # Infinite offline Publish queueing
myMQTTClient.configureDrainingFrequency(2)  # Draining: 2 Hz
myMQTTClient.configureConnectDisconnectTimeout(10)  # 10 sec
myMQTTClient.configureMQTTOperationTimeout(5)  # 5 sec

# Connect to AWS IoT endpoint and publish a message
myMQTTClient.connect()
print ("Connected to AWS IoT")
myMQTTClient.publish("alpn/devicesdk", "Hello over MQTT on port 443", 0)
myMQTTClient.disconnect()

Run the Python script

Run the Python script you created by executing the following command.

python aws-iot-with-alpn.py 
Connected to AWS IoT

When you see the “Connected” message in the console, the connection to AWS IoT Core was successfully established and the message was published.  If you see any errors in the execution of the script, check the device certificates and make sure that the attached policy allows AWS IoT Core access.

Summary

In this post, I’ve shown you two ways to connect your IoT devices to AWS IoT Core over MQTT on port 443. If you have had a constraint in the past to open port 8883 in your corporate firewalls, you can now use a standard port for HTTPS traffic (443) to send your messages over MQTT to AWS IoT Core endpoint.

For more information about AWS IoT Core, see the AWS IoT Core Developer Guide