mbedTLS SSL Certificate Verification With Mosquitto, lwIP, and MQTT
In order to further secure our IoT communications, it's a good idea to set up server certificate verification to prevent man-in-the-middle attacks.
Join the DZone community and get the full member experience.
Join For FreeIn Secure TLS Communication with MQTT using mbedTLS on top of lwIP, I used TLS for secure communication, but I had not enabled server certificate verification. This article is about closing that gap.
In my earlier article, I used the following as the authentication mode in mbedTLS:
mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_NONE);
This is definitely not something I want in production code, as it does not verify the server certificate and would allow a ‘man in the middle’ attack (see Enable Secure Communication with TLS and the Mosquitto Broker). This article explains how to enable and use certificate verification with mbedTLS and Mosquitto as the MQTT broker.
Mosquitto Broker Setup
I’m running the Mosquitto broker on my local machine with the following configuration:
port 8883
cafile C:\Program Files (x86)\mosquitto\certs\m2mqtt_ca.crt
certfile C:\Program Files (x86)\mosquitto\certs\m2mqtt_srv.crt
keyfile C:\Program Files (x86)\mosquitto\certs\m2mqtt_srv.key
tls_version tlsv1.
And running it with:
mosquitto -c mosquitto.conf -v
Which starts it listening on port 8883 with TSL enabled:
C:\Program Files (x86)\mosquitto>mosquitto -c mosquitto.conf -v
1492938937: mosquitto version 1.4.11 (build date 20/02/2017 23:24:29.40) starting
1492938937: Config loaded from mosquitto.conf.
1492938937: Opening ipv6 listen socket on port 8883.
1492938937: Opening ipv4 listen socket on port 8883.
I’m running the broker with the following server certificate setting:
certfile C:\Program Files (x86)\mosquitto\certs\m2mqtt_srv.crt
Using the Mosquitto client to subscribe, I can subscribe to a topic with the following command line, using that same certificate:
mosquitto_sub -c -i MyMQTTclient -h localhost -p 8883 -q 0 -t HSLU/test -v --cafile c:\tmp\tls_ssl\client\m2mqtt_srv.crt --insecure
Notice that I have to specify the option –insecure. Without the –insecure it will give an error message like this:
1492934878: OpenSSL Error: error:14094438:SSL routines:ssl3_read_bytes:tlsv1 alert internal error
1492934878: OpenSSL Error: error:140940E5:SSL routines:ssl3_read_bytes:ssl handshake failure
1492934878: Socket error on client <unknown>, disconnecting.
It took me while to find out why it is failing. A good way to test the TLS handshaking and connection is to use
openssl s_client -connect localhost:8883
Which gives me the reason in the last line of the output:
-----END CERTIFICATE-----
subject=/C=CH/ST=Switzerland/L=Lucerne/O=HSLU/OU=T&A/CN=ErichStyger-PC/emailAddress=mail@hslu.ch
issuer=/C=CH/ST=Switzerland/L=Lucerne/O=HSLU/OU=T&A/CN=ErichStyger-PC/emailAddress=mail@hslu.ch
---
No client certificate CA names sent
Peer signing digest: SHA512
Server Temp Key: ECDH, P-256, 256 bits
---
SSL handshake has read 1605 bytes and written 434 bytes
---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES256-GCM-SHA384
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
Protocol : TLSv1.2
Cipher : ECDHE-RSA-AES256-GCM-SHA384
Session-ID: D241F3BFA8D26BDEE381353E2C517E46F8D04B48F307467A0E46FD7A2F3EB6BB
Session-ID-ctx:
Master-Key: <cut>
Key-Arg : None
PSK identity: None
PSK identity hint: None
SRP username: None
TLS session ticket lifetime hint: 7200 (seconds)
TLS session ticket:
0000 - a0 fa a5 f5 8d 54 78 1a-7c 4e 86 51 4c 24 45 30 .....Tx.|N.QL$E0
0010 - 97 44 de c1 fb c7 06 96-46 ed ef 27 67 c2 91 6f .D......F..'g..o
0020 - 40 38 ef 86 2a 12 59 cb-f0 60 0d 34 e6 be 2a ef @8..*.Y..`.4..*.
0030 - e5 7c c8 ee c3 ac cb 25-ef 63 49 3c 27 2e b0 3c .|.....%.cI<'..<
0040 - e3 a6 88 53 08 20 4b 53-2f 2b 6e 44 20 1a e7 24 ...S. KS/+nD ..$
0050 - 60 a3 1a b0 08 74 74 56-46 13 22 0a 76 df 32 53 `....ttVF.".v.2S
0060 - d7 b1 6b 82 63 34 fc c8-9c 2c a6 16 a2 73 75 9d ..k.c4...,...su.
0070 - 33 03 dc c7 db e0 c7 89-d0 49 ac fd 7d d3 33 0e 3........I..}.3.
0080 - 35 eb df fc 05 b3 d0 bb-b7 02 25 67 86 71 76 f4 5.........%g.qv.
0090 - 56 59 3b 39 2a dc 04 0e-e1 60 ae e4 17 1c 8f 62 VY;9*....`.....b
00a0 - b9 bf f1 99 5e c5 15 3c-ae 60 60 cb 8e 63 1a af ....^..<.``..c..
Start Time: 1492935504
Timeout : 300 (sec)
Verify return code: 18 (self signed certificate)
---
closed
Ah, that makes sense: I have used a self-signed certificate. Now I know why I have to use the option –insecure with mosquitto_sub. I guess I could get rid of this with a non-self-signed certificate, but that’s too much of an effort for me now, as I’m only testing the connection.
mbed TLS With SSL Verification
To enable SSL verification and certificate verification, I have to replace:
mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_NONE);
with:
ret = mbedtls_x509_crt_parse(&cacert, (const unsigned char *)mbedtls_m2mqtt_srv_crt, mbedtls_m2mqtt_srv_crt_len );
if(ret != 0) {
printf( " failed\n ! mbedtls_x509_crt_parse returned -0x%x\n\n", -ret );
}
mbedtls_ssl_conf_ca_chain( &conf, &cacert, NULL );
mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_REQUIRED);
For the call to mbedls_x509_crt_parse(), I have to pass a pointer to the certificate string with its length. I have created the files certificate.h and certificate.h, which holds my certificates. Certificate.h looks like this:
/*
* certificate.h
*
* Author: Erich Styger
*/
#ifndef CERTIFICATE_H_
#define CERTIFICATE_H_
#include <stddef.h> /* for size_t */
extern const char mbedtls_m2mqtt_srv_crt[];
extern const size_t mbedtls_m2mqtt_srv_crt_len;
#endif /* CERTIFICATE_H_ */
The certificate I have to use is the one from the broker/server (the m2mqtt_srv.crt that I created in Enable Secure Communication with TLS and the Mosquitto Broker), which is a file with the following content:
-----BEGIN CERTIFICATE-----
MIIDnjCCAoYCCQCUTXSPenGUCjANBgkqhkiG9w0BAQsFADCBkDELMAkGA1UEBhMC
Q0gxFDASBgNVBAgMC1N3aXR6ZXJsYW5kMRAwDgYDVQQHDAdMdWNlcm5lMQ0wCwYD
VQQKDARIU0xVMQwwCgYDVQQLDANUJkExFzAVBgNVBAMMDkVyaWNoU3R5Z2VyLVBD
MSMwIQYJKoZIhvcNAQkBFhRlcmljaC5zdHlnZXJAaHNsdS5jaDAeFw0xNzA0MjMw
ODAzNDdaFw0yNzA0MjEwODAzNDdaMIGQMQswCQYDVQQGEwJDSDEUMBIGA1UECAwL
U3dpdHplcmxhbmQxEDAOBgNVBAcMB0x1Y2VybmUxDTALBgNVBAoMBEhTTFUxDDAK
BgNVBAsMA1QmQTEXMBUGA1UEAwwORXJpY2hTdHlnZXItUEMxIzAhBgkqhkiG9w0B
CQEWFGVyaWNoLnN0eWdlckBoc2x1LmNoMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAxGPL7lmqeDkq9Ah3DTieY0xQqV2ZoxSHUUpQ10e+A7bhNbumREdR
lQP1mHgaBX0IIiflNMHQ7WYZtrfFzVa09tguzN2oPGhRp0aamKRYsfOa1rTZVy31
zzHupKv+NlCi0LXqO1k8Tvvf/BbTa7GYB1hNwxdLc53rRZtxqveHUt5vywA6jpdu
x6SL9wmzFcspHJ+WOjfp09SOrLFAc9S3HsBwnK9TeDn7hVrR4I/6ne8mRLcCTtL1
qLUcGHWc2lAep5hyZBKepSVxC5KaqoHlAOyyMVYJyDhQhJ0W4ovCKjurK2rpUDIg
pjlxnCzNr6xtGXvScRFklF94PiH4x7O4DQIDAQABMA0GCSqGSIb3DQEBCwUAA4IB
AQBjjwlnK+Ej644ENcVwQpSf+0m8PbShbLFTsMNcP0rPLOcAJIZahNTlyMzzzb1F
pb7DxRUtBbcP735aA3zh+p279woV9DZjGLw4t0WMeFRAeJryoeTkhxHTBBJ/BFti
DEdOZaYeBMdyxCmO4WV9CzUFDXW5qYHoeTjLqfYAaY/nJiLN11d/MQoIsVXjg7PB
NBgD7SVK5WlbFR2kHyQzG9b3hNdCoax7S7yfkN6wFcIDKye2abEOi7VQOTEbQ2uk
AYj9dUTpo2d6T2xHvnPI69TSkt7cPp6uv5IlDn7856fnTARoF1LEGeWJRFNTYM2x
lzkg0fgI8eSV30YVrAHJIDOa
-----END CERTIFICATE-----
That certificate text gets translated into an ASCII array in certifcate.c (copy-paste from the above file and ‘stringify’ it:
/*
* certificate.c
*
* Author: Erich Styger
*/
#include "certificate.h"
/* m2mqtt_srv.crt: see
* https://mcuoneclipse.com/2017/04/14/enable-secure-communication-with-tls-and-the-mosquitto-broker/
*/
#define M2MQTT_CA_CRT_RSA \
"-----BEGIN CERTIFICATE-----\r\n" \
"MIIDIDCCAggCCQCUTXSPenGUBzANBgkqhkiG9w0BAQsFADBSMQswCQYDVQQGEwJD\r\n" \
"SDEUMBIGA1UECAwLU3dpdHplcmxhbmQxEDAOBgNVBAcMB0x1Y2VybmUxDTALBgNV\r\n" \
"BAoMBEhTTFUxDDAKBgNVBAsMA1QmQTAeFw0xNzA0MjMwNjQxMjdaFw0yNzA0MjEw\r\n" \
"NjQxMjdaMFIxCzAJBgNVBAYTAkNIMRQwEgYDVQQIDAtTd2l0emVybGFuZDEQMA4G\r\n" \
"A1UEBwwHTHVjZXJuZTENMAsGA1UECgwESFNMVTEMMAoGA1UECwwDVCZBMIIBIjAN\r\n" \
"BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxGPL7lmqeDkq9Ah3DTieY0xQqV2Z\r\n" \
"oxSHUUpQ10e+A7bhNbumREdRlQP1mHgaBX0IIiflNMHQ7WYZtrfFzVa09tguzN2o\r\n" \
"PGhRp0aamKRYsfOa1rTZVy31zzHupKv+NlCi0LXqO1k8Tvvf/BbTa7GYB1hNwxdL\r\n" \
"c53rRZtxqveHUt5vywA6jpdux6SL9wmzFcspHJ+WOjfp09SOrLFAc9S3HsBwnK9T\r\n" \
"eDn7hVrR4I/6ne8mRLcCTtL1qLUcGHWc2lAep5hyZBKepSVxC5KaqoHlAOyyMVYJ\r\n" \
"yDhQhJ0W4ovCKjurK2rpUDIgpjlxnCzNr6xtGXvScRFklF94PiH4x7O4DQIDAQAB\r\n" \
"MA0GCSqGSIb3DQEBCwUAA4IBAQAFVuA5+QarBpT7U7w66JhrikxDTwfg/6Q7Guuo\r\n" \
"AeuxpU0ida2UDqy3M31d5e/g906DbxctyeVERPXeDbivRFvJixGj2ry2mbjqHaYe\r\n" \
"3OYGM2QEhfW3jSkd4IOzrxD5J3BEYLwtz7Yb/7w+CRO34f/5MlYQJWUZiM+IyFh4\r\n" \
"R94rS3ietTK9AUnFTforc9GPBSjtagQVCdWoYo43+D2z/jDDIigkVhXL1ozJgsx/\r\n" \
"DwoYYGjCqhommSthyzaQsIu4LyDZ0UZ4uUboTAl6yzmGjuO92TdmZl9wMi91dDfv\r\n" \
"piwtIz+WkK31VWQYVtY8jIwowTuK6X5IBgodFv04/GYE6sWh\r\n" \
"-----END CERTIFICATE-----\r\n"
const char mbedtls_m2mqtt_srv_crt[] = M2MQTT_CA_CRT_RSA;
const size_t mbedtls_m2mqtt_srv_crt_len = sizeof(mbedtls_m2mqtt_srv_crt);
With this, I’m able to connect to the broker and start the TLS handshake with server certificate verification. I have implemented in the project a command line interface:
--------------------------------------------------------------
MQTT with lwip
--------------------------------------------------------------
CLS1 ; Group of CLS1 commands
help|status ; Print help or status information
TmDt1 ; Group of TmDt1 commands
help|status ; Print help or status information
time [hh:mm:ss[,z]] ; Set the current time. Prints the current time if no argument
date [dd.mm.yyyy] ; Set the current date. Prints the current date if no argument
dateToSec <datetime> ; Convert date/time int UNIX timestamp (seconds after 1970)
secToDate <secs> ; Convert UNIX timestamp to date/time
mqtt ; Group of mqtt commands
help|status ; Print help or status information
connect ; Connect to the broker
publish ; Publish topic to broker
subscribe ; Subscribe to topic on broker
disconnect ; Disconnect from the broker
Below is the connection log:
CMD> mqtt connect
Initiating connection to broker...
CMD> Connecting to Mosquito broker
mqtt_client_connect: Connecting to host: 192.168.0.111 at port:8883
mqtt_tcp_connect_cb: TCP connection established to server, starting TLS handshake
mbedtls_net_send: len: 393
tls_tcp_sent_cb
mbedtls_net_incoming: put nof bytes: 1358, free: 2738
mbedtls_net_recv: requested nof: 5, available 1358
mbedtls_net_recv: requested nof: 61, available 1353
mbedtls_net_recv: requested nof: 5, available 1292
mbedtls_net_recv: requested nof: 940, available 1287
mbedtls_net_recv: requested nof: 5, available 347
mbedtls_net_recv: requested nof: 333, available 342
mbedtls_net_recv: requested nof: 5, available 9
mbedtls_net_recv: requested nof: 4, available 4
mbedtls_net_send: len: 75
mbedtls_net_send: len: 6
mbedtls_net_send: len: 45
tls_tcp_sent_cb
mbedtls_net_incoming: put nof bytes: 258, free: 3838
mbedtls_net_recv: requested nof: 5, available 258
mbedtls_net_recv: requested nof: 202, available 253
mbedtls_net_recv: requested nof: 5, available 51
mbedtls_net_recv: requested nof: 1, available 46
mbedtls_net_recv: requested nof: 5, available 45
mbedtls_net_recv: requested nof: 40, available 40
TLS handshake completed
mqtt_tcp_connect_cb: TCP connection established to server
mqtt_output_send: tcp_sndbuf: 8760 bytes, ringbuf_linear_available: 23, get 0, put 23
mqtt_output_send: mbedtls_ssl_write: bytes 23
mbedtls_net_send: len: 52
tls_tcp_sent_cb
mbedtls_net_incoming: put nof bytes: 33, free: 4063
mbedtls_net_recv: requested nof: 5, available 33
mbedtls_net_recv: requested nof: 28, available 28
mqtt_recv_from_tls: recv 4
mqtt_parse_incoming: Remaining length after fixed header: 2
mqtt_parse_incoming: msg_idx: 4, cpy_len: 2, remaining 0
mqtt_message_received: received CONNACK, Connect response code 0
mqtt_connection_cb: Successfully connected
Client is connected
mqtt_cyclic_timer: Sending keep-alive message to server
mqtt_output_send: tcp_sndbuf: 8760 bytes, ringbuf_linear_available: 2, get 23, put 25
mqtt_output_send: mbedtls_ssl_write: bytes 2
mbedtls_net_send: len: 31
tls_tcp_sent_cb
mbedtls_net_incoming: put nof bytes: 31, free: 4065
mbedtls_net_recv: requested nof: 5, available 31
mbedtls_net_recv: requested nof: 26, available 26
mqtt_recv_from_tls: recv 2
mqtt_parse_incoming: Remaining length after fixed header: 0
mqtt_message_received: Received PINGRESP from server
mqtt_cyclic_timer: Sending keep-alive message to server
mqtt_output_send: tcp_sndbuf: 8760 bytes, ringbuf_linear_available: 2, get 25, put 27
mqtt_output_send: mbedtls_ssl_write: bytes 2
mbedtls_net_send: len: 31
tls_tcp_sent_cb
mbedtls_net_incoming: put nof bytes: 31, free: 4065
mbedtls_net_recv: requested nof: 5, available 31
mbedtls_net_recv: requested nof: 26, available 26
mqtt_recv_from_tls: recv 2
mqtt_parse_incoming: Remaining length after fixed header: 0
mqtt_message_received: Received PINGRESP from server
mqtt_cyclic_timer: Sending keep-alive message to server
mqtt_output_send: tcp_sndbuf: 8760 bytes, ringbuf_linear_available: 2, get 27, put 29
mqtt_output_send: mbedtls_ssl_write: bytes 2
mbedtls_net_send: len: 31
tls_tcp_sent_cb
mbedtls_net_incoming: put nof bytes: 31, free: 4065
mbedtls_net_recv: requested nof: 5, available 31
mbedtls_net_recv: requested nof: 26, available 26
mqtt_recv_from_tls: recv 2
mqtt_parse_incoming: Remaining length after fixed header: 0
mqtt_message_received: Received PINGRESP from server
Below is a publish request:
CMD> mqtt publish
Initiating PUBLISH to broker...
Publish to broker
mqtt_publish: Publish with payload length 1 to topic "HSLU/test"
mqtt_output_send: tcp_sndbuf: 8760 bytes, ringbuf_linear_available: 14, get 31, put 45
mqtt_output_send: mbedtls_ssl_write: bytes 14
mbedtls_net_send: len: 43
Published to topic "HSLU/test", payload "3", res: 0
tls_tcp_sent_cb
mqtt_tcp_sent_cb: Calling QoS 0 publish complete callback
And a subscription request:
CMD> mqtt subscribe
Initiating SUBSCRIBE to broker...
Subscribe from broker
mqtt_sub_unsub: Client (un)subscribe to topic "HSLU/test", id: 2
mqtt_output_send: tcp_sndbuf: 8760 bytes, ringbuf_linear_available: 16, get 67, put 83
mqtt_output_send: mbedtls_ssl_write: bytes 16
mbedtls_net_send: len: 45
Suscribed to topic "HSLU/test", res: 0
tls_tcp_sent_cb
mbedtls_net_incoming: put nof bytes: 34, free: 4062
mbedtls_net_recv: requested nof: 5, available 34
mbedtls_net_recv: requested nof: 29, available 29
mqtt_recv_from_tls: recv 5
mqtt_parse_incoming: Remaining length after fixed header: 3
mqtt_parse_incoming: msg_idx: 5, cpy_len: 3, remaining 0
mqtt_message_received: SUBACK response with id 2
Subscribe result: 0
Code and Data Size
TLS and broker certificate handling do not come for free. Below is the code and data size of my application on the FRDM-K64F with lwIP (no optimizations turned on in GCC). This is the base size (lwIP, MQTT, and without TLS):
text data bss dec hex filename
104576 96 129776 234448 393d0 FRDM-K64F_lwip_mqtt_bm.axf
Adding the mbedTLS library adds around 150KB of code size:
text data bss dec hex filename
351240 1008 136212 488460 7740c FRDM-K64F_lwip_mqtt_bm.axf
Adding the certificate handling is after that:
text data bss dec hex filename
355768 1008 136212 492988 785bc FRDM-K64F_lwip_mqtt_bm.axf
For debugging purposes, it is fine to skip the server certificate verification. But for production code, the server certificate needs to be verified to prevent man in the middle attacks. Moving from ‘no certificate verification’ to the ‘verify the certificate’ requires parsing and using the server certificate. This can be accomplished with a few calls to the mbedTLS library and does not add that much code size compared to the overall mbed library, but greatly extends security.
The updated project (MCUXpresso IDE with GNU tools, NXP FRDM-K64F board with lwIP, MQTT and embedTLS) is available on GitHub.
Happy Certificating!
Published at DZone with permission of Erich Styger, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments