MQTT over TLS-PSK with the ESP32 and Mongoose-OS
This article is an extension to this one; I suggest you read it first.
Should you need more information on the MQTT client and using it with no TLS, read this article. For a bit more on TLS, this one.
TLS-PSK
There is a simpler solution to using full-blown TLS as we’ve seen on ourfirst article; instead of using certificates, broker and connecting device can have a pre-shared key (PSK).
The connection process is similar, but in this case, instead of validating certificates and generating a derived key, the process starts with the pre-shared key. Depending on the scheme being used, demanding Public Key Cryptography operations can be avoided, and key management logistics can be a bit simpler, plus, we don’t need a CA (Certification Authority) anymore.
Even though there are currently three different ways to work, where one of these allows broker validation using certificates and device validation using a PSK, we’ve only tested the simplest form.
Configuration
Let’s configure the ESP32 running Mongoose-OS to use TLS-PSK.
libs:
- origin: https://github.com/mongoose-os-libs/mqtt # Include the MQTT client
config_schema:
- ["mqtt.enable", true] # Enable the MQTT client
- ["mqtt.server", "address:port"] # Broker IP address (and port)
- ["mqtt.ssl_psk_identity", "bob"] # identity to use for our key
- ["mqtt.ssl_psk_key", "000000000000000000000000deadbeef"] # key AES-128 (or 256)
- ["mqtt.ssl_cipher_suites", "TLS-PSK-WITH-AES-128-CCM-8:TLS-PSK-WITH-AES-128-GCM-SHA256:TLS-ECDHE-PSK-WITH-AES-128-CBC-SHA256:TLS-PSK-WITH-AES-128-CBC-SHA"] # cipher suites
The most common port for MQTT over TLS-PSK is 8883. The broker can also request we send a username and password, though the usual stuff is to configure it to take advantage of the identity we’re already sending, as we did on these tests (example below).
Regarding cipher suites, we need to provide a set of cryptographic schemes supported by our device, and at least one of them must also be supported by the broker. This is a mandatory parameter, otherwise our device won’t announce any TLS-PSK compatible cipher suite and TLS connection will not take place. Usually we negotiate this with the broker admins.
When starting the TLS connection, the device sends its supported cipher suites in its ClientHello message; the broker then chooses one that it supports and indicates this on its ServerHello message. As we’ve obtained this list by analyzing the source code, we decided to publish it here so it can be of use. In our particular case, only one of the device cipher suites was also supported by the broker: TLS-PSK-WITH-AES-128-CCM-8. The start handshake with these messages can be seen at the Wireshark snapshot below.
Finally, our pre-shared key (PSK, parameter ssl_psk_key) must be a 128-bit (16-byte) quantity for AES-128 suites and a 256-bit (32-byte) quantity for AES-256 suites.
Operation
At startup, we’ll watch the log and check if everything is going on as it should, or catch any possible errors. In this sad case, it is pretty hard to properly determine what is going on without a sniffer, as both sides are often not very verbose nor precise. The handshake, as well as the MQTT messages inside TLS, can be observed with a sniffer, by introducing the proper key (see the example using Wireshark below).
[Mar 31 17:28:32.349] mgos_mqtt_conn.c:435 MQTT0 connecting to 192.168.5.3:8883
[Mar 31 17:28:32.368] mongoose.c:4906 0x3ffc76ac ciphersuite: TLS-PSK-WITH-AES-128-CBC-SHA
[Mar 31 17:28:32.398] mgos_mqtt_conn.c:188 MQTT0 TCP connect ok (0)
[Mar 31 17:28:32.411] mgos_mqtt_conn.c:235 MQTT0 CONNACK 0
[Mar 31 17:28:32.420] init.js:34 MQTT connected
[Mar 31 17:28:32.434] init.js:26 Published:OK topic:/this/test/esp32_807A98 msg:CONNECTED!
Brokers: Mosquitto
This is a minimal configuration for Mosquitto, take it as a quick help and not as a reference. Paths are in GNU/Linux form and we should put our keys there.
listener 8883 192.168.5.1
log_dest syslog
**use\_identity\_as\_username true** # so the broker does not ask for username in the MQTT header
**psk\_file /etc/mosquitto/pskfile**
**psk\_hint** _cualquiera_ # any name
Parameter psk_hint serves as a hint for the client on which identity to use, in case it connects to several places; we don’t use it in this example.
The file pskfile must contain each identity and its corresponding pre-shared key; for example, for our user bob:
bob:000000000000000000000000deadbeef
Example
Companion example code available in Github.
Sniffers: Wireshark
With any sniffer we are able to see the TLS traffic, but not its payload
Standard capture
With Wireshark, we can decrypt TLS-PSK entering the desired pre-shared key in Edit->Preferences->Protocols->TLS->Pre-Shared-Key.
Entering the PSK
We are then able to see MQTT traffic as TLS payload.
Decrypted capture