Este artículo es una extensión de este otro; te recomiendo que lo veas primero.
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.
TLS-PSK
Existe una solución un poco más simple a la que vimos en el primer artículo; consiste en utilizar, en vez de certificados, una clave pre-compartida entre el broker y el dispositivo que se conecta.
El proceso de conexión es similar al empleado con certificados, sólo que en vez de validarse éstos y luego generarse una clave derivada, se utiliza como punto de partida la clave pre-compartida. Según el esquema utilizado, se evitan las costosas operaciones de clave pública/privada (Public Key Cryptography), y la logística de manejo de claves suele ser más simple, además del hecho de no requerir una CA (Certification Authority).
Si bien existen al momento tres formas de operar, una de las cuales permite validar al broker mediante certificados y al dispositivo mediante clave pre-compartida (PSK), sólo hemos probado la forma simple.
Configuración
Configuramos el ESP32 con Mongoose-OS para operar mediante TLS-PSK.
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_psk_identity", "bob"] # identidad para la clave elegida - ["mqtt.ssl_psk_key", "000000000000000000000000deadbeef"] # clave AES-128 (ó 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"] # claves utilizables
El puerto comúnmente utilizado es 8883. El broker puede además solicitar que utilicemos nombre de usuario y password, aunque lo más común es configurarlo para aprovechar la identidad que se envía, como hicimos para estas pruebas (ejemplo más abajo).
Respecto a las cipher suites, se trata de indicar un conjunto de esquemas criptográficos que el sistema soporta y entre ellos debemos acertar a elegir uno que esté soportado por el broker. Este parámetro es obligatorio, sin él el dispositivo no anuncia ningún esquema criptográfico compatible con TLS-PSK y la conexión TLS no se establece. Lo habitual es acordarlo con el administrador del broker.
Al iniciar la conexión TLS, el dispositivo incluye los esquemas soportados en el mensaje ClientHello; el broker elige uno compatible y lo indica en el mensaje ServerHello. Dado que esta lista la obtuvimos analizando el código fuente, decidimos publicarla aquí para mayor utilidad. En nuestro caso en particular sólo uno de los esquemas coincidió con los disponibles en el broker: TLS-PSK-WITH-AES-128-CCM-8. El handshake de inicialización conteniendo estos mensajes puede observarse en la captura Wireshark que figura más abajo.
Por último, la clave pre-compartida (PSK, parámetro ssl_psk_key) debe tener una longitud de 128-bits (16-bytes) para sets basados en AES-128 y de 256-bits (32-bytes) para sets basados en AES-256.
Operación
Al iniciar el procesador, observaremos en el log si todo funciona como debe, o los errores que se hayan producido. En este último caso, resulta bastante difícil determinar las causas sin un sniffer dado que las pistas de ambos lados no suelen ser muy precisas. Tanto el handshake como la operación MQTT dentro de TLS pueden observarse en un sniffer, ingresando dicha clave en el mismo (por ejemplo Wireshark, ejemplo más abajo).
[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
Detallamos, a modo instructivo y como ayuda rápida, la configuración mínima necesaria para operar en Mosquitto. El path está en formato GNU/Linux y se espera que pongamos nuestro archivo con las claves allí.
listener 8883 192.168.5.1 log_dest syslog use_identity_as_username true # evitamos que requiera usuario en el header MQTT psk_file /etc/mosquitto/pskfile psk_hint cualquiera # cualquier nombre
El parámetro psk_hint, en el caso que el cliente se conecte a varios lugares, le da un indicio (hint) de qué identidad utilizar para identificarse. No tiene utilidad en nuestro caso.
El archivo pskfile, por su parte, debe contener las identidades y claves pre-compartidas; por ejemplo, para el usuario bob:
bob:000000000000000000000000deadbeef
Ejemplo
El código de ejemplo se encuentra en Github.
Sniffers: Wireshark
En todo sniffer, veremos el tráfico TLS pero no lo que éste transporta
En Wireshark, poder decodificar el tráfico TLS-PSK es tan simple como ingresar la clave compartida en Edit->Preferences->Protocols->TLS->Pre-Shared-Key.
El sniffer es entonces capaz de descifrar el contenido y nos permite observar el tráfico MQTT.