MQTT Broker (Mosquitto) con certificado servidor (self-signed) y certificado en los clientes

Reading time: 68 – 113 minutes

Serie

  1. Gestión de certificados (PKI) – easy-rsa
  2. MQTT Broker (Mosquitto) con certificado servidor (self-signed)
  3. MQTT Broker (Mosquitto) con certificado servidor (self-signed) y certificado en los clientes
  4. MQTT Broker (Mosquitto) con certificado servidor (Let’s Encrypt)
  5. Formatos de Certificados: PEM, DER y PKCS#12 y Manipulación de Claves Privadas

Descripción del escenario

En este post vamos a explorar la configuración de un bróker MQTT utilizando Mosquitto, implementando seguridad mutua a través de certificados: un certificado auto-firmado para el servidor y certificados específicos para los clientes. Analizaremos paso a paso cómo montar el entorno, partiendo de una configuración mínima que permita el uso tanto de conexiones TCP como de Websockets.

A lo largo del artículo se demostrará de manera detallada el proceso de negociación de la conexión cifrada utilizando OpenSSL como cliente TCP+SSL/TLS, y posteriormente se emplearán los clientes de Mosquitto para suscribirse y publicar mensajes en un tópico de demostración. Además, veremos cómo consumir el servicio mediante un sencillo script en Python y, para cerrar, utilizaremos el cliente EMQ MQTTX desde la consola, que nos permitirá interactuar tanto vía TCP como a través de Websockets.

Este enfoque práctico te ayudará a comprender en profundidad cómo garantizar la seguridad en la comunicación MQTT implementando certificados tanto en el servidor como en los clientes. ¡Comencemos!

Asumciones

Para el desarrollo de este post se asumen los siguientes puntos:

  • PKI y easy-rsa: Contamos con los conocimientos básicos sobre la gestión de certificados mediante una PKI, tal como se describe en el post Gestión de certificados (PKI) – easy-rsa. Se asume que la PKI ya está desplegada siguiendo las indicaciones de dicho post y que la configuración se encuentra en el fichero vars.
  • Resolución DNS: Se dispone de un servidor DNS que resuelve los nombres de host dentro de nuestra red privada. Aunque es posible modificar el fichero /etc/hosts para lograrlo, esta práctica se considera menos recomendable en entornos donde se puede optar por una solución DNS adecuada.
  • Instalación de Mosquitto: Se asume que Mosquitto y sus herramientas/clients están instalados en el sistema operativo que utilizaremos. En este post, todas las demostraciones se llevarán a cabo en Linux Ubuntu 22.04.

Estas asunciones nos permitirán centrarnos en la configuración y uso de certificados en el bróker MQTT sin tener que detallar cada uno de los pasos previos para la creación y administración de la infraestructura de certificados.

Demostración en vídeo

Obtención de los certificados

Partimos de lo descrito en el post: MQTT Broker (Mosquitto) con certificado servidor (self-signed)

Por lo tanto, solo tenemos que crear el certificado para los clientes que vamos a usar. Los del servidor MQTT ya están listos.

# desde la máquina que queramos con easy-rsa instalado
./easyrsa gen-req client.example.tld
# se genera fichero mqtt.example.tld.req

Firma de la petición de certificado

Si no se lanzó la petición de certificado (certificate request) desde el mismo PKI, hay que copiarlo a la máquina del PKI e importarlo. No lo olvidemos. Aunque en soluciones domésticas típicamente todo lo movemos dentro de la misma estructura de directorios de la PKI.

# en la PKI:
## importamos el certificado, si no se ha hecho la petición desde aquí
## se asume que se copió el fichero de certificate request en
## /the-path/client.example.tld.req
./easyrsa import-req /the-path/client.example.tld.req client.example.tld
## esto colocará una copia del fichero .req en la PKI y algunos enlaces.

## Se le pide a la PKI que firme el certificado
./easyrsa sign-req client client.example.tld
### esto generará el fichero .crt necesarios para armar el cliente.

Creación y firma de la petición de certificado

En un solo comando, en el caso de hacerlo todo desde la PKI (en un solo servidor) podemos obtener: clave privada, request de certificador y certificado firmado. Este certificado será de tipo cliente.

./easyrsa build-client-full client.example.tld

# sin cifrado de la clave privada:
./easyrsa build-client-full client.example.tld --no-pass

IMPORTANTE, no tiene sentido hacer este comando si previamente hemos hecho la creación de request, (importación) y firmado.

Si queremos emitir un certificado que sea útil tanto para cliente como para servidor podemos usar:

./easyrsa build-serverClient-full clientserver.example.tld

# sin cifrado de la clave privada:
./easyrsa build-serverClient-full clientserver.example.tld --no-pass

Configuración de Mosquitto con certificado servidor y requisito de certificado cliente

Esta configuración es muy sencilla y solo pondrá en marcha un Mosquitto en el puerto TCP/8883 además del puerto WSS/8884.

mosquitto.conf

# mqtt secure
listener 8883 0.0.0.0
cafile certs/ca.crt
keyfile certs/mqtt.example.tld.key
certfile certs/mqtt.example.tld.crt
crlfile certs/pki.example.tld.crl
allow_anonymous true
require_certificate true

# websockets secure
listener 8884 0.0.0.0
protocol websockets
cafile certs/ca.crt
keyfile certs/mqtt.example.tld.key
certfile certs/mqtt.example.tld.crt
crlfile certs/pki.example.tld.crl
require_certificate true

Para lanzar el servicio, simplemente debemos usar el comando:

mosquitto -c mosquitto.conf

Cuando lancemos el servicio se nos pedirá la passphrase de la clave privada dos veces, una para cada uno de los listerners que hemos puesto la configuración, el TCP y el WSS.

Por otro lado, si se configura use_subject_as_username en true, se empleará el campo completo del sujeto del certificado como nombre de usuario. En el caso de que use_identity_as_username o use_subject_as_username se encuentren desactivados, el cliente deberá autenticarse mediante los métodos habituales, como la verificación a través de password_file. Asimismo, cualquier certificado expedido por las autoridades indicadas en cafile o capath será considerado válido para la conexión, lo que ofrece una mayor flexibilidad en la emisión y comprobación de los certificados de cliente.

Cuando el parámetro require_certificate de Mosquitto está configurado a true, se exige que el cliente presente un certificado válido para poder conectarse satisfactoriamente al servicio MQTT. En este contexto, los parámetros adicionales use_identity_as_username y use_subject_as_username resultan relevantes: si use_identity_as_username se establece en true, se utilizará el Common Name (CN) extraído del certificado del cliente en lugar del nombre de usuario MQTT para el control de acceso, omitiéndose la contraseña, ya que se asume que únicamente los clientes autenticados disponen de certificados válidos.

Negociación de la conexión (OpenSSL s_client)

OpenSSL es la herramienta que hay por debajo de easy-rsa y la librería de referencia para SSL/TLS; además de disponer de herramientas que nos permitirán depurar enlaces de cifrados. Para este caso de uso usaremos openssl s_client que nos va a permitir ver todos los detalles de la negociación SSL/TLS con el servidor de MQTT.

# self-signed fail chechking
openssl s_client \
  -CAfile certs/ca.crt \
  mqtt.example.tld:8883
  
# using my CA, self-signed succesful checking
openssl s_client \
  -CAfile certs/ca.crt \
  --cert certs/client.example.tld.crt \
  --key certs/client.example.tld.key \
  mqtt.example.tld:8883

En el vídeo de demostración puedes ver más detalles de cómo se analiza la negociación entre el servidor y el cliente.

Clientes Mosquitto

Usando mosquitto_sub y mosquitto_pub se demuestra cómo se intercambia tráfico a través de un enlace cifrado. Nada del otro mundo, pero que en entornos cifrados y para evoluciones de este escenario veremos que tiene algunas limitaciones.

Me suscribo al topic llamado topic1 y esperando que se publiquen mensajes en él:

mosquitto_sub -d \
  --cafile pki/ca.crt \
  --cert certs/client.example.tld.crt \
  --key certs/client.example.tld.key \
  -h mqtt.example.tld -p 8883 \
  -t topic1 -v

Publico un mensaje en el topic con el contenido: value1

mosquitto_pub -d \
  --cafile pki/ca.crt \
  --cert certs/client.example.tld.crt \
  --key certs/client.example.tld.key \
  -h mqtt.example.tld -p 8883 \
  -t topic1 -m "message1"

Cuando lancemos los comandos, se nos pedirá la passphrase de la clave privada. Tanto para publicar como para suscribirnos, ya que ambos necesitan acceso al fichero “certs/client.example.tld.key” que esta cifrado con una clave simétrica.

Clientes desarrollados en Python

Se trata de un par de scripts muy sencillos que solo pretenden ilustrar lo sencillo que es especificar nuestra propia CA en la inicialización de la conexión segura con el servidor MQTT.

# se requiere uv, si no lo tienes instalado:
curl -LsSf https://astral.sh/uv/install.sh | sh
# lanzamos el suscriptor
uv run mqtt_sub.py
# lanzamos el publicador
uv run mqtt_pub.py

Clientes MQTTX-cli usando TCP y Websockets

Al lanzar el bróker de MQTT expusimos el endpoint en TCP y el de Websockets, pero no hemos usado el segundo para nada todavía. Así, pues, vamos a aprovechar la potencia del cliente de EMQ llamado MQTTX para repetir las mismas funciones que antes, pero ahora haciendo la suscripción con Websockets y la publicación con TCP.

Este cliente por el moment no soporta protección de cifrado simétrico en la clave privada, así pues primero debemos extraer la clave privada a un nuevo fichero:

# extraer clave privada de fichero cifrado:
openssl rsa -in python_client/certs/client.example.tld.key -out python_client/certs/client.example.tld.key.raw
# para completar el proceso se deberá introducir la clave simétrica


# mqttx-cli using docker
alias mqttx-cli 'docker run --rm \
  --entrypoint /usr/local/bin/mqttx \
  -v ./pki/ca.crt:/tmp/ca.crt \
  -v ./python_client/certs/client.example.tld.crt:/tmp/client.example.tld.crt \
  -v ./python_client/certs/client.example.tld.key.raw:/tmp/client.example.tld.key \
  emqx/mqttx-cli'
  
# suscripción
mqttx-cli sub \
    --ca /tmp/ca.crt \
    --cert /tmp/client.example.tld.crt \
    --key /tmp/client.example.tld.key \
    -h mqtt.example.tld \
    -p 8884 -l wss \
    -t topic1

# publicación
mqttx-cli pub \
    --ca /tmp/ca.crt \
    --cert /tmp/client.example.tld.crt \
    --key /tmp/client.example.tld.key \
    -h mqtt.example.tld \
    -p 8883 -l mqtts \
    -t topic1 -m message1

Referencias

Scroll to Top