MQTT over TLS with the ESP32 and Mongoose-OS

Mongoose-OS has a built-in MQTT client supporting TLS. This allows us to authenticate the server, but also the server infrastructure is able to authenticate the identity of incoming connections, that is, to validate that they belong to authorized users.

For further information on the MQTT client and using it without TLS, read this article. For more information on TLS, read this one.

Configuration

We’ll configure the ESP32 running Mongoose-OS to use the MQTT client and connect to a WiFi network. Since we are going to connect to a server (the MQTT broker), this is perhaps the simplest and fastest way to run our tests. This action can be done manually using RPCs (Remote Procedural Calls), writing a JSON config file, or it can be defined in the YAML file that describes how the project will be built. We’ve chosen this last option for our tests.

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_ca_cert", "ca.crt"]   # Broker certificate, required
  - ["mqtt.ssl_cert", "sandboxclient.crt"]  # our certificate, for mutual authentication
  - ["mqtt.ssl_key", "sandboxclient.key"]   # our key, for mutual authentication

The most common port for MQTT over TLS is 8883. The broker can also ask us to send a username and password, full configuration details are available at the corresponding Mongoose-OS doc page. Finally, it is the broker who decides which type of authentication to use (one- or two-way), though we must provide our certificate when the broker asks for it.

Operation

Before we build our project and execute our code, it is perhaps convenient to have an MQTT client connected to the broker, so we can see the message the device will send at connect time. We’ve used a broker we installed in a server in our lab and connected to it with no authentication, so all we’ve shown in the MQTT article is valid here too.

After the code is compiled and linked (mos build) and the microcontroller’s flash is programmed (mos flash) using mos tool, we’ll watch the log and check if everything is going on as it should, or catch any possible errors. Remember we need to configure the proper access credentials to connect to our WiFi network of choice (SSID and password), and the address and port for the MQTT broker we are going to use. At the end of this note we show how to configure a Mosquitto broker.

One-way authentication

This way we can authenticate the server, that is, we can trust it is the one to whom we want to connect, but the server has no idea who can we be.

This is the simplest configuration and we only have to provide the CA certificate filename in the parameter ssl_ca_cert, the certificate has to be stored in the fs directory. This indicates the broker has to be validated.

[Mar 31 17:21:18.360] mgos_mqtt_conn.c:435    MQTT0 connecting to 192.168.5.3:8883
[Mar 31 17:21:18.585] mongoose.c:4906         0x3ffca41c ciphersuite: TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256
[Mar 31 17:21:20.404] mgos_mqtt_conn.c:188    MQTT0 TCP connect ok (0)
[Mar 31 17:21:20.414] mgos_mqtt_conn.c:235    MQTT0 CONNACK 0
[Mar 31 17:21:20.423] init.js:34              MQTT connected
[Mar 31 17:21:20.437] init.js:26              Published:OK topic:/this/test/esp32_807A98 msg:CONNECTED!

Two-way (mutual) authentication

This way we can authenticate the server and the client, that is, now the server knows we should be the one who our certificate says we are. This dual process requires lots of processing and we’ll notice that connection establishment takes longer. In this configuration we must provide our certificate and key in the fs directory, and configure parameters ssl_cert and ssl_key appropriately; otherwise the broker might give us a clue in its log, as Mosquitto does:

Mar 31 17:30:52 Server mosquitto[2694]: OpenSSL Error: error:140890C7:SSL routines:ssl3_get_client_certificate:peer did not return a certificate 

Brokers: Mosquitto

These are minimal configurations for Mosquitto, take them as a quick help and not as a reference. Paths are in GNU/Linux form and we should put our certificates there.

One-way authentication (broker authentication)

listener 8883 192.168.5.1
log_dest syslog
cafile /etc/mosquitto/certs/ca.crt
certfile /etc/mosquitto/certs/server.crt
keyfile /etc/mosquitto/certs/server.key

Mutual (two-way) authentication

listener 8883 192.168.5.1
log_dest syslog
cafile /etc/mosquitto/certs/ca.crt
certfile /etc/mosquitto/certs/server.crt
keyfile /etc/mosquitto/certs/server.key
require_certificate true

Example

Companion example code available in Github, here and here. We’ll also need server credentials, I left a set here so we can do this easily.

TLS, Transport Layer Security

Those who want to connect to a server often require having the possibility to verify it is the desired server and not an impersonator. Those who offer a service might require some tool to authenticate the identity of incoming connections, that is, to determine those really belong to authorized users.

TLS(Transport Layer Security) allows mutually authenticating two parties at a conversation, and keeping its integrity. Authentication is done by means of X.509 certificates, and after that, keys are derived that allow transferred information to be encrypted. This means that whatever is sent can’t be observed with a network sniffer, and the only way to impersonate one of those entities is by having its certificate and the private key from which it’s been derived. TLS is a complex authentication scheme, requiring some certifying authority who generates keys and certificates used in this process. Due to it being a follow up to SSL, it is common to find references to this name.

The simplest form of operation, and maybe the most used, is server authentication; this happens for example when our web browser requests a page in the Internet using HTTPS (HTTP Secure). Before proceeding with the HTTP session, a handshake takes place that allows for validating the authenticity and establishing a secure connection, in the sense that the other end is identified and information integrity is protected. Web browsers know several certification authorities (have their certificates) and can validate a site by checking the signature in their credentials.

The other form of operation takes place in many IoT networks, when a device connects to “the cloud”. This time not only the server certificate is validated, the device certificate is validated too, that is there is a mutual authentication.

The details of this validation process depend on the method both parties agree to use, though roughly it consists in sending some information encrypted with the receiver’s public key, the receiver can only be able to decrypt and answer if he has the corresponding private key.

Even though authentication is a slow process involving Public Key Cryptography operations, the data transfer phase uses symmetric key encryption based on keys derived from the former process.

As we suggested at the beginning of this article, the security in this scheme resides on the impossibility of impersonating the Certification Authority, whose private key must be safely stored and protected from prying eyes.

MQTT sobre TLS con ESP32 y Mongoose-OS

Mongoose-OS incorpora un cliente MQTT con soporte TLS. Esto nos permite por un lado autenticar al servidor, y por otro que la infraestructura del servidor pueda autenticar la identidad de esas conexiones, es decir, que realmente sean usuarios autorizados.

Para mayor información sobre el cliente MQTT y la operación sin TLS, existe este artículo. Para más información sobre TLS, éste.

Configuración

Configuramos el ESP32 con Mongoose-OS para tener un cliente MQTT y conectarse como cliente a una red WiFi. Dado que debemos conectarnos a un servidor, ésta nos resulta la forma tal vez más rápida y simple de realizar las pruebas y explicar la operación. La configuración puede realizarse manualmente mediante RPC, en un archivo de configuración JSON; o definirla en el archivo YAML que describe el proyecto. Para las pruebas elegimos esta última opción.

libs:
  - origin: https://github.com/mongoose-os-libs/mqtt  # incorpora el cliente MQTT
config_schema:
  - ["mqtt.enable", true]            # Habilita el cliente MQTT
  - ["mqtt.server", "address:port"]  # Dirección IP del broker a utilizar
  - ["mqtt.ssl_ca_cert", "ca.crt"]   # Certificado del broker, requerido
  - ["mqtt.ssl_cert", "sandboxclient.crt"]  # nuestro certificado, para autenticación mutua
  - ["mqtt.ssl_key", "sandboxclient.key"]   # nuestra clave, para autenticación mutua

El puerto comúnmente utilizado es 8883. El broker puede además solicitar que utilicemos nombre de usuario y password, los detalles de la configuración completa los podemos encontrar en la página de Mongoose-OS. Finalmente, quien decide qué tipo de autenticación (simple o mutua) utilizamos es el broker, aunque nosotros debemos proveer el certificado cuando es requerido.

Operación

Antes de compilar y ejecutar, es conveniente tener un cliente conectado al broker para poder observar el mensaje enviado al momento de conexión. Hemos utilizado un broker instalado en nuestro lab, y nos conectamos a él sin autenticación, por lo que lo mostrado en el artículo sobre MQTT vale para éste también

Luego de compilado el código (mos build) y grabado el microcontrolador (mos flash) mediante mos tool, observaremos en el log si todo funciona como debe, o los errores que se hayan producido. Recordemos que debemos configurar las credenciales para conectarnos por WiFi a nuestra red (SSID y clave) y la dirección y port del broker MQTT que vayamos a utilizar. Al final de la nota comentamos cómo utilizar Mosquitto.

Autenticación simple

De esta forma autenticamos al servidor, es decir, podemos confiar en que es el que esperamos que sea, pero el servidor no tiene idea de quién podemos llegar a ser nosotros.

Es la configuración más simple y no debemos tomar ningún recaudo más allá de informar el nombre del archivo que contiene el certificado de la CA almacenado en el directorio fs en el parámetro ssl_ca_cert, esto indica que se deberá validar al broker.

[Mar 31 17:21:18.360] mgos_mqtt_conn.c:435    MQTT0 connecting to 192.168.5.3:8883
[Mar 31 17:21:18.585] mongoose.c:4906         0x3ffca41c ciphersuite: TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256
[Mar 31 17:21:20.404] mgos_mqtt_conn.c:188    MQTT0 TCP connect ok (0)
[Mar 31 17:21:20.414] mgos_mqtt_conn.c:235    MQTT0 CONNACK 0
[Mar 31 17:21:20.423] init.js:34              MQTT connected
[Mar 31 17:21:20.437] init.js:26              Published:OK topic:/this/test/esp32_807A98 msg:CONNECTED!

Autenticación mutua

De esta forma autenticamos tanto al servidor como al cliente, es decir, ahora el servidor sabe que deberíamos ser quien nuestro certificado dice que somos. Este doble proceso requiere bastante procesamiento y lo notaremos en el tiempo de establecimiento de la conexión. En esta configuración deberemos proveer nuestro certificado y clave en el directorio fs, además de configurar los parámetros ssl_cert y ssl_key; caso contrario el broker puede que nos dé una pista en el log, como lo hace Mosquitto:

Mar 31 17:30:52 Server mosquitto[2694]: OpenSSL Error: error:140890C7:SSL routines:ssl3_get_client_certificate:peer did not return a certificate 

Brokers: Mosquitto

Detallamos, a modo instructivo y como ayuda rápida, las configuraciones mínimas necesarias para operar en Mosquitto. Los paths están en formato GNU/Linux y se espera que pongamos nuestros certificados allí.

Autenticación simple (del broker)

listener 8883 192.168.5.1
log_dest syslog
cafile /etc/mosquitto/certs/ca.crt
certfile /etc/mosquitto/certs/server.crt
keyfile /etc/mosquitto/certs/server.key

Autenticación mutua

listener 8883 192.168.5.1
log_dest syslog
cafile /etc/mosquitto/certs/ca.crt
certfile /etc/mosquitto/certs/server.crt
keyfile /etc/mosquitto/certs/server.key
require_certificate true

Ejemplo

El código de ejemplo se encuentra en Github, aquí y aquí. Vamos a necesitar además las credenciales del servidor, dejé unas aquí para simplificar la operatoria.

TLS, seguridad en la capa de transporte

Quienes desean conectarse a un servidor suelen requerir la posibilidad de verificar que se trata del servidor deseado y no de impostor. Quienes dan un servicio pueden requerir una herramienta para autenticar la identidad de las conexiones, es decir, que realmente sean usuarios autorizados.

TLS(Transport Layer Security) permite autenticar mutuamente dos entidades en una conversación y mantener la integridad de la misma. La autenticación se realiza por certificados X.509, y luego de realizada, se derivan claves que permiten encriptar la información que viaja. Esto significa que lo que se envía no puede ser observado con un sniffer y la única forma de impersonar a una entidad es mediante la posesión del certificado y la clave privada de la que se deriva. Se trata de un esquema de autenticación complejo, con un ente certificador, quien genera las claves y certificados que se utilizan en el proceso. Dado que TLS se deriva de SSL, el esquema original (Secure Sockets Layer), es común que la nomenclatura refiera a este nombre.

La operación más simple y tal vez la más utilizada es la autenticación del servidor, operación que sucede cuando por ejemplo nuestro navegador solicita en Internet una página mediante HTTPS (HTTP Secure). Antes de proceder a la sesión HTTP, se realiza el handshake que permite la validación de autenticidad y el establecimiento de la conexión segura (“segura” en el sentido de que se identifica a la contraparte y se cuida la integridad de la información que viaja). El navegador conoce una serie de entes certificadores (tiene sus certificados) y puede validar mediante la firma al sitio solicitado cuando éste “le presenta sus credenciales”.

La otra operación posible es realizada en muchas redes IoT, cuando un dispositivo se conecta “a la nube”. En esta oportunidad no sólo se valida el certificado del servidor sino que éste a su vez valida el certificado del dispositivo, es decir, se establece una doble validación.

El proceso de validación en detalle depende del método acordado entre las contrapartes, aunque a grandes rasgos consiste en enviar una cierta información basada en la encripción con la clave pública del receptor, quien sólo puede descifrarla y así contestar si posee la clave privada correspondiente.

Si bien la autenticación es un proceso lento que involucra clave pública/privada (Public Key Cryptography), la comunicación transcurre encriptada por una clave simétrica derivada.

Como insinuamos al inicio, toda la seguridad del esquema radica en la imposibilidad de impersonar al ente certificador (Certification Authority), cuya clave privada es celosamente guardada y protegida.

En castellano (Spanish support)

En este post voy a reunir los artículos al respecto que escribí para Cika Electrónica en mi último tiempo con ellos. De hecho se trata de links a su servidor…

No trabajo más en Cika y no creo que vaya a escribir otros artículos en castellano en el futuro, a menos que aparezca un mecenas (o conjunto de ellos) que incline el uso de mi tiempo en esa dirección mediante su aporte económico voluntario. Ergo, disfrutad de este contenido mientras aprendéis inglés o armáis una sociedad de fomento ad hoc.

Intro al ESP32, bastante desactualizada

“edGarDo”, una nota de aplicación con una idea de cómo hacer un control de portón de garage usando BLE e incorporándolo a openHAB para obtener control mediante Google Assistant y Alexa sin esfuerzo

Configuraciones de autenticación Enterprise en WiFi con el ESP32, y el software

Autenticación en servidor HTTP: el texto y el software

Cómo usar TLS en el servidor HTTP: texto y software

Cómo usar TLS con el cliente MQTT: texto, software

Mongoose-OS tiene un esquema del tipo RPC (Remote Procedure Call) que entre otras cosas podemos decir que cumple la función de los CGI en un servidor HTTP. Cómo manejar la autorización: texto de la nota, código asociado

Cómo usar las RPC por WebSocket: nota, código

Usando las RPC con MQTT, o cómo controlar un ESP32 atrás de un firewall: texto, código

Configuración de un cliente SNTP, o cómo poner nuestro ESP32 en hora: nota, código de la nota

Guardando datos en la flash mediante un log: nota, código

Conectándonos a la Google Cloud Platform con un ESP32 usando MQTT: texto, código

Aprovechando las RPC para controlar un dispositivo conectado a la red de Google: texto, código

Conectándonos a la red de Amazon (AWS IoT Core): texto, código