Tutorial: Secure TLS Communication with MQTT using mbedTLS on top of lwip

One of the most important aspects of the ‘IoT’ world is having a secure communication. Running MQTT on lwip (see “MQTT with lwip and NXP FRDM-K64F Board“) is no exception. Despite of the popularity of MQTT and lwip, I have not been able to find an example using a secure TLS connection over raw/native lwip TCP :-(. Could it be that such an example exists, and I have not found it? Or that someone implemented it, but has not published it? Only what I have found on the internet are many others asking for the same kind of thing “running MQTT on lwip with TLS”, but there was no answer? So I have to answer my question, which seems to be a good thing anyway: I can learn new things the hard way :-).

Blockdiagram MQTT Application with TLS using lwip

Block diagram MQTT Application with TLS using lwip

Outline

This article describes how to enable a bare-metal (no RTOS)  in RAW/native (no sockets, TCP only) lwip application running the MQTT protocol with TLS.

The project used in this article is available on GitHub: https://github.com/ErichStyger/mcuoneclipse/tree/master/Examples/MCUXpresso/FRDM-K64F/FRDM-K64F_lwip_lwip_mqtt_bm

The project runs a MQTT client application which initiates TLS handshaking and then communicates securely with a Mosquitto broker.

Prerequisites: Software/Tools

In this article I have used the following software and tools:

But any other software/tool combination should do it too :-).

mbed TLS

As outlined in “Introduction to Security and TLS (Transport Layer Security)“, I have selected mbed TLS because its licensing terms are very permissive (Apache).

Get mbed TLS from https://tls.mbed.org/download (I recommend the Apache version as it is permissive).

Another way is to get it from the NXP MCUXpresso SDK for the FRDM-K64F. Use the ‘import SDK examples’ function from the quickstart panel and import the mbedtls_selftest example. The advantage of this method is that it comes with the random number generator drivers (RNG):

mbed tls in MCUXpresso SDK

mbed tls in MCUXpresso SDK

Adding mbedTLS

From the mbed TLS distribution, add the ‘mbedtls’ folder to the project. You need

  • mbedtls\include\mbedtls
  • mbedtls\library

The mbed TLS implementation uses a ‘port’ which takes advantage of the hardware encryption unit of the on the NXP Kinetis K64F device. That ‘port’ is part of the MCUXpresso SDK, place it inside mbedtls\port.

And finally I need the driver for the mmCAU (Memory-Mapped Cryptographic Acceleration Unit) of the NXP Kinetis device:

  • mmcau_common: common mmCAU files and interface
  • \libs\lib_mmcau.a: library with cryptographic routines
mbedtls and MCUXpresso SDK Files

mbedtls and MCUXpresso SDK Files

The mbed configuration file is included with a preprocessor symbol. Add the following to compiler Preprocessor defined symbols:

MBEDTLS_CONFIG_FILE='"ksdk_mbedtls_config.h"'
MBEDTLS_CONFIG_FILE Macro

MBEDTLS_CONFIG_FILE Macro

Next, add the following to compiler include path settings so it can find all the needed header files:

"${workspace_loc:/${ProjName}/mbedtls/port/ksdk}"
"${workspace_loc:/${ProjName}/mbedtls/include/mbedtls}"
"${workspace_loc:/${ProjName}/mbedtls/include}"
"${workspace_loc:/${ProjName}/mmcau_common}"
mbedtls includes

mbedtls includes

And add the mmCAU library to the linker options so it gets linked with the application (see “Creating and using Libraries with ARM gcc and Eclipse“):

mmCAU Library Linker Options

mmCAU Library Linker Options

Last but not least, make sure that the random number generator (RNG) source files of the MCUXpresso SDK are part of the project:

  • drivers/fsl_rnga.c
  • drivers/fsl_rnga.h
Random Number Generator Sources

Random Number Generator Sources

This completes the files and settings to add mbed TLS to the project :-).

MQTT without TLS

In an application with MQTT, the MQTT communication protocol is handled between the application and the stack:

Application stack with MQTT

Application stack with MQTT

The block diagram below shows the general flow of the MQTT application interacting with lwip in RAW (tcp) mode. With lwip the application has basically two call backs:

  • recv_cb(): callback called when we receive a packet from the network
  • sent_cb(): callback called *after* a packet has been sent

💡 There is yet another call back, the error callback. To keep things simple, I ignore that callback here.s

MQTT Application with lwip

MQTT Application with lwip

In raw/bare metal mode, the application calls ethernet_input() which calls the ‘received’ callback. With using MQTT, the MQTT parses the incoming data and passes it to the application (e.g. CONNACK message).

If the application is e.g. sending a PUBLISH request, that TCP message is constructed by the MQTT layer and put into a buffer (actually a ring buffer). That data is only sent if the mqtt_output_send() is called (which is not available to the application). mqtt_output_send() is called for ‘sending’ functions like mqtt_publish() or as a side effect of the mqtt_tcp_sent_cb() callback which is called after a successful tcp_write(). The MQTT sent_cb() is forwarded to the application sent_cb() callback.

MQTT with TLS

The TLS encryption is happening between the application/MQTT part and the TCP/IP (lwip) layers. That way it is transparent between the protocol and the physical layer:

MQTT Application with Encryption

MQTT Application with Encryption

While things look easy from the above block diagram, it is much more complex to get the cryptographic library working between MQTT and lwip:

  1. mbed TLS needs to be initialized properly
  2. The application needs to first start the TLS handshaking, adding an extra state to the application state handling
  3. All calls to the lwip TCP layer needs to be replaced with calls to the mbedtls_ssl layer
  4. The communication flow is not any more ‘send one message, receive one sent_cb() callback). Because the TLS layer is doing the handshaking, multiple messages will be transmitted and received, so the call backs need to be separated too.
  5. Without the TLS layer, the communication flow is more synchronous, and received messages are directly passed up to the application layer. With the TLS between, there is the need for an extra buffering of the incoming messages.
  6. Messages to be sent from the MQTT layer are already buffered in the non-TLS version. To ensure that they are sent, an extra function mqtt_output.send() has been added.
Blockdiagram MQTT Application with TLS using lwip

Block diagram of MQTT Application with TLS on top of lwip

💡 I’m not very happy with that mqtt_output_send() method,  but that’s the best I was able to come up to get things working. I might need to refactor this.

To make the above working, I had the tweak the existing MQTT implementation with comes with lwip. Several things should be considered for a general refactoring or done with extra callbacks. I might be able to implement and improve it over the next weeks.

All the needed changes in the application to support TLS are enabled with the following macro inside mqtt_opts.h:

#ifndef MQTT_USE_TLS
  #define MQTT_USE_TLS    1  /*!< 1: enable TLS/SLL support; 0: do not use TLS/SSL */
#endif

The following sections explain the implementation in more details.

Random Number Generator

Before using the random number generator, it needs to be initialized:

#if MQTT_USE_TLS
  /* initialize random number generator */
  RNGA_Init(RNG); /* init random number generator */
  RNGA_Seed(RNG, SIM->UIDL); /* use device unique ID as seed for the RNG */
  if (TLS_Init()!=0) { /* failed? */
    printf("ERROR: failed to initialize for TLS!\r\n");
    for(;;) {} /* stay here in case of error */
  }
#endif

TLS Initialization

To use the mbed TLS library, several objects have to be initialized at application startup:

static mbedtls_entropy_context entropy;
static mbedtls_ctr_drbg_context ctr_drbg;
static mbedtls_ssl_context ssl;
static mbedtls_ssl_config conf;
static mbedtls_x509_crt cacert;
static mbedtls_ctr_drbg_context ctr_drbg;

static int TLS_Init(void) {
  /* inspired by https://tls.mbed.org/kb/how-to/mbedtls-tutorial */
  int ret;
  const char *pers = "ErichStyger-PC";

  /* initialize the different descriptors */
  mbedtls_ssl_init( &ssl );
  mbedtls_ssl_config_init( &conf );
  mbedtls_x509_crt_init( &cacert );
  mbedtls_ctr_drbg_init( &ctr_drbg );
  mbedtls_entropy_init( &entropy );

  if( ( ret = mbedtls_ctr_drbg_seed( &ctr_drbg, mbedtls_entropy_func, &entropy,
                             (const unsigned char *) pers,
                             strlen(pers ) ) ) != 0 )
  {
      printf( " failed\n  ! mbedtls_ctr_drbg_seed returned %d\n", ret );
      return -1;
  }
  /*
   * First prepare the SSL configuration by setting the endpoint and transport type, and loading reasonable
   * defaults for security parameters. The endpoint determines if the SSL/TLS layer will act as a server (MBEDTLS_SSL_IS_SERVER)
   * or a client (MBEDTLS_SSL_IS_CLIENT). The transport type determines if we are using TLS (MBEDTLS_SSL_TRANSPORT_STREAM)
   * or DTLS (MBEDTLS_SSL_TRANSPORT_DATAGRAM).
   */
  if( ( ret = mbedtls_ssl_config_defaults( &conf,
                  MBEDTLS_SSL_IS_CLIENT,
                  MBEDTLS_SSL_TRANSPORT_STREAM,
                  MBEDTLS_SSL_PRESET_DEFAULT ) ) != 0 )
  {
      printf( " failed\n  ! mbedtls_ssl_config_defaults returned %d\n\n", ret );
      return -1;
  }
  /* The authentication mode determines how strict the certificates that are presented are checked.  */
  mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_NONE ); /* \todo change verification mode! */

  /* The library needs to know which random engine to use and which debug function to use as callback. */
  mbedtls_ssl_conf_rng( &conf, mbedtls_ctr_drbg_random, &ctr_drbg );
  mbedtls_ssl_conf_dbg( &conf, my_debug, stdout );

  mbedtls_ssl_setup(&ssl, &conf);

  if( ( ret = mbedtls_ssl_set_hostname( &ssl, "ErichStyger-PC" ) ) != 0 )
  {
      printf( " failed\n  ! mbedtls_ssl_set_hostname returned %d\n\n", ret );
      return -1;
  }
  /* the SSL context needs to know the input and output functions it needs to use for sending out network traffic. */
  mbedtls_ssl_set_bio(&ssl, &mqtt_client, mbedtls_net_send, mbedtls_net_recv, NULL);

  return 0; /* no error */
}

💡 Notice that with I’m using MBEDTLS_SSL_VERIFY_NONE. I need to change this in a next iteration, see “Tuturial: mbedTLS SLL Certificate Verification with Mosquitto, lwip and MQTT“.

Application Main Loop

The application runs in an endless loop. To keep things simple, it uses a timer/timestamp to connect and then periodically publish MQTT data:

static void DoMQTT(struct netif *netifp) {
  uint32_t timeStampMs, diffTimeMs;
  #define CONNECT_DELAY_MS   1000 /* delay in seconds for connect */
  #define PUBLISH_PERIOD_MS  10000 /* publish period in seconds */

  MQTT_state = MQTT_STATE_IDLE;
  timeStampMs = sys_now(); /* get time in milli seconds */
  for(;;) {
    LED1_On();
    diffTimeMs = sys_now()-timeStampMs;
    if (MQTT_state==MQTT_STATE_IDLE && diffTimeMs>CONNECT_DELAY_MS) {
      MQTT_state = MQTT_STATE_DO_CONNECT; /* connect after 1 second */
      timeStampMs = sys_now(); /* get time in milli seconds */
    }
    if (MQTT_state==MQTT_STATE_CONNECTED && diffTimeMs>=PUBLISH_PERIOD_MS) {
      MQTT_state = MQTT_STATE_DO_PUBLISH; /* publish */
      timeStampMs = sys_now(); /* get time in milli seconds */
    }
    MqttDoStateMachine(&mqtt_client); /* process state machine */
    /* Poll the driver, get any outstanding frames */
    LED1_Off();
    ethernetif_input(netifp);
    sys_check_timeouts(); /* Handle all system timeouts for all core protocols */
  }
}

With ethernetif_input() it polls for any incoming TCP packets. With sys_check_timeouts() it checks for any timeout and for example sends periodic PINGREQ messages to the MQTT broker.

Application State Machine

The application state machine goes through init, connect and TLS handshake sequence, follwed by a periodic PUBLISH:

static void MqttDoStateMachine(mqtt_client_t *mqtt_client) {
  switch(MQTT_state) {
    case MQTT_STATE_INIT:
    case MQTT_STATE_IDLE:
      break;
    case MQTT_STATE_DO_CONNECT:
      printf("Connecting to Mosquito broker\r\n");
      if (mqtt_do_connect(mqtt_client)==0) {
#if MQTT_USE_TLS
        MQTT_state = MQTT_STATE_DO_TLS_HANDSHAKE;
#else
        MQTT_state = MQTT_STATE_WAIT_FOR_CONNECTION;
#endif
      } else {
        printf("Failed to connect to broker\r\n");
      }
      break;
#if MQTT_USE_TLS
    case MQTT_STATE_DO_TLS_HANDSHAKE:
      if (mqtt_do_tls_handshake(mqtt_client)==0) {
        printf("TLS handshake completed\r\n");
        mqtt_start_mqtt(mqtt_client);
        MQTT_state = MQTT_STATE_WAIT_FOR_CONNECTION;
      }
      break;
#endif
    case MQTT_STATE_WAIT_FOR_CONNECTION:
      if (mqtt_client_is_connected(mqtt_client)) {
        printf("Client is connected\r\n");
        MQTT_state = MQTT_STATE_CONNECTED;
      } else {
#if MQTT_USE_TLS
        mqtt_recv_from_tls(mqtt_client);
#endif
      }
      break;
    case MQTT_STATE_CONNECTED:
      if (!mqtt_client_is_connected(mqtt_client)) {
        printf("Client got disconnected?!?\r\n");
        MQTT_state = MQTT_STATE_DO_CONNECT;
      }
#if MQTT_USE_TLS
      else {
        mqtt_tls_output_send(mqtt_client); /* send (if any) */
        mqtt_recv_from_tls(mqtt_client); /* poll if we have incoming packets */
      }
#endif
      break;
    case MQTT_STATE_DO_PUBLISH:
      printf("Publish to broker\r\n");
      my_mqtt_publish(mqtt_client, NULL);
      MQTT_state = MQTT_STATE_CONNECTED;
      break;
    case MQTT_STATE_DO_DISCONNECT:
      printf("Disconnect from broker\r\n");
      mqtt_disconnect(mqtt_client);
      MQTT_state = MQTT_STATE_IDLE;
      break;
    default:
      break;
  }
}

In the MQTT_STATE_CONNECTED it calls mqtt_tls_output_send() to send any outstanding MQTT packets. It uses mqqt_recv_from_tls() to poll any incoming TCP packets.

Connecting to the Broker

The following is the connection code to the broker:

static int mqtt_do_connect(mqtt_client_t *client) {
  ip4_addr_t broker_ipaddr;
  struct mqtt_connect_client_info_t ci;
  err_t err;

  IP4_ADDR(&broker_ipaddr, configBroker_ADDR0, configBroker_ADDR1, configBroker_ADDR2, configBroker_ADDR3);
  memset(client, 0, sizeof(mqtt_client_t)); /* initialize all fields */

  /* Setup an empty client info structure */
  memset(&ci, 0, sizeof(ci));

  /* Minimal amount of information required is client identifier, so set it here */
  ci.client_id = configMQTT_CLIENT_NAME;
  ci.client_user = configMQTT_CLIENT_USER;
  ci.client_pass = configMQTT_CLIENT_PWD;
  ci.keep_alive = 60; /* timeout */

  /* Initiate client and connect to server, if this fails immediately an error code is returned
     otherwise mqtt_connection_cb will be called with connection result after attempting
     to establish a connection with the server.
     For now MQTT version 3.1.1 is always used */
#if MQTT_USE_TLS
  client->ssl_context = &ssl;
  err = mqtt_client_connect(client, &broker_ipaddr, MQTT_PORT_TLS, mqtt_connection_cb, 0, &ci);
#else
  err = mqtt_client_connect(client, &broker_ipaddr, MQTT_PORT, mqtt_connection_cb, 0, &ci);
#endif
  /* For now just print the result code if something goes wrong */
  if(err != ERR_OK) {
    printf("mqtt_connect return %d\n", err);
    return -1; /* error */
  }
  return 0; /* ok */
}

At this state, the only difference between TLS and unencrypted communication is that it uses uses a different port (8883 instead of 1883) and that it stores the SSL context in the client descriptor. Inside mqtt_client_connect(), it will directly call the tcp_connect() function.

Connection Callback

If the TCP connection succeeds, it calls the connection callback:

/**
 * TCP connect callback function. @see tcp_connected_fn
 * @param arg MQTT client
 * @param err Always ERR_OK, mqtt_tcp_err_cb is called in case of error
 * @return ERR_OK
 */
static err_t
mqtt_tcp_connect_cb(void *arg, struct tcp_pcb *tpcb, err_t err)
{
  mqtt_client_t* client = (mqtt_client_t *)arg;

  if (err != ERR_OK) {
    LWIP_DEBUGF(MQTT_DEBUG_WARN,("mqtt_tcp_connect_cb: TCP connect error %d\n", err));
    return err;
  }

  /* Initiate receiver state */
  client->msg_idx = 0;

#if MQTT_USE_TLS
  /* Setup TCP callbacks */
  tcp_recv(tpcb, tls_tcp_recv_cb);
  tcp_sent(tpcb, tls_tcp_sent_cb);
  tcp_poll(tpcb, NULL, 0);

  LWIP_DEBUGF(MQTT_DEBUG_TRACE,("mqtt_tcp_connect_cb: TCP connection established to server, starting TLS handshake\n"));
  /* Enter MQTT connect state */
  client->conn_state = TLS_HANDSHAKING;

  /* Start cyclic timer */
  sys_timeout(MQTT_CYCLIC_TIMER_INTERVAL*1000, mqtt_cyclic_timer, client);
  client->cyclic_tick = 0;
#else
  /* Setup TCP callbacks */
  tcp_recv(tpcb, mqtt_tcp_recv_cb);
  tcp_sent(tpcb, mqtt_tcp_sent_cb);
  tcp_poll(tpcb, mqtt_tcp_poll_cb, 2);

  LWIP_DEBUGF(MQTT_DEBUG_TRACE,("mqtt_tcp_connect_cb: TCP connection established to server\n"));
  /* Enter MQTT connect state */
  client->conn_state = MQTT_CONNECTING;

  /* Start cyclic timer */
  sys_timeout(MQTT_CYCLIC_TIMER_INTERVAL*1000, mqtt_cyclic_timer, client);
  client->cyclic_tick = 0;

  /* Start transmission from output queue, connect message is the first one out*/
  mqtt_output_send(client, &client->output, client->conn);
#endif
  return ERR_OK;
}

In TLS mode, it configures the special call backs for tls handling (tls_rcp_recv_cb() and tls_tcp_sent_cb()) and moves the connection state into TLS_HANDSHAKING mode.

Receiver Callback

Because the normal receiver callback mqtt_tcp_recv_cb() does not work with the TLS layer, I have implemented a function which reads from the TLS layer:

💡 Note that the error handling is not completed yet!

err_t mqtt_recv_from_tls(mqtt_client_t *client) {
  int nof;
  mqtt_connection_status_t status;
  struct pbuf p;

  /*! \todo check if can we really use rx_buffer here? */
  nof = mbedtls_ssl_read(client->ssl_context, client->rx_buffer, sizeof(client->rx_buffer));
  if (nof>0) {
    printf("mqtt_recv_from_tls: recv %d\r\n", nof);
    memset(&p, 0, sizeof(struct pbuf)); /* initialize */
    p.len = nof;
    p.tot_len = p.len;
    p.payload = client->rx_buffer;
    status = mqtt_parse_incoming(client, &p);
    if (status!=MQTT_CONNECT_ACCEPTED) {
      return ERR_CONN; /* connection error */ /*! \todo In case of communication error, have to close connection! */
    }
  }
  return ERR_OK;
}

TLS Sent Callback

For every packet sent, the callback tsl_tcp_sent_cb() gets called:

#if MQTT_USE_TLS
/**
 * TCP data sent callback function. @see tcp_sent_fn
 * @param arg MQTT client
 * @param tpcb TCP connection handle
 * @param len Number of bytes sent
 * @return ERR_OK
 */
static err_t tls_tcp_sent_cb(void *arg, struct tcp_pcb *tpcb, u16_t len)
{
  printf("tls_tcp_sent_cb\r\n");
  return mqtt_tcp_sent_cb(arg, tpcb, 0); /* call normal (non-tls) callback */
}
#endif /*MQTT_USE_TLS */

It call the corresponding callback in the MQTT layer:

/**
 * TCP data sent callback function. @see tcp_sent_fn
 * @param arg MQTT client
 * @param tpcb TCP connection handle
 * @param len Number of bytes sent
 * @return ERR_OK
 */
static err_t
mqtt_tcp_sent_cb(void *arg, struct tcp_pcb *tpcb, u16_t len)
{
  mqtt_client_t *client = (mqtt_client_t *)arg;

  LWIP_UNUSED_ARG(tpcb);
  LWIP_UNUSED_ARG(len);

  if (client->conn_state == MQTT_CONNECTED) {
    struct mqtt_request_t *r;

    printf("mqtt_tcp_sent_cb: and MQTT_CONNECTED\r\n");
    /* Reset keep-alive send timer and server watchdog */
    client->cyclic_tick = 0;
    client->server_watchdog = 0;
    /* QoS 0 publish has no response from server, so call its callbacks here */
    while ((r = mqtt_take_request(&client->pend_req_queue, 0)) != NULL) {
      LWIP_DEBUGF(MQTT_DEBUG_TRACE,("mqtt_tcp_sent_cb: Calling QoS 0 publish complete callback\n"));
      if (r->cb != NULL) {
        r->cb(r->arg, ERR_OK);
      }
      mqtt_delete_request(r);
    }
    /* Try send any remaining buffers from output queue */
    mqtt_output_send(client, &client->output, client->conn);
  }
  return ERR_OK;
}

Net.c Functions

The interface to the network/lwip layer for mbed TLS is implemented in net.c.

The receiving function puts the incoming data into a ring buffer and returns the number of bytes received

/*
 * Read at most 'len' characters
 */
int mbedtls_net_recv( void *ctx, unsigned char *buf, size_t len )
{
    struct mqtt_client_t *context;

    context = (struct mqtt_client_t *)ctx;
    if(context->conn == NULL) {
      return( MBEDTLS_ERR_NET_INVALID_CONTEXT );
    }
    if (RNG1_NofElements()>=len) {
      printf("mbedtls_net_recv: requested nof: %d, available %d\r\n", len, (int)RNG1_NofElements());
      if (RNG1_Getn(buf, len)==ERR_OK) {
        return len; /* ok */
      }
    }
    return 0; /* nothing read */
}

The sending function writes the data with tcp_write():

/*
 * Write at most 'len' characters
 */
int mbedtls_net_send( void *ctx, const unsigned char *buf, size_t len )
{
  struct mqtt_client_t *context;

  context = (struct mqtt_client_t *)ctx;
  int err;

  if(context->conn == NULL) {
    return( MBEDTLS_ERR_NET_INVALID_CONTEXT );
  }
  printf("mbedtls_net_send: len: %d\r\n", len);
  err = tcp_write(context->conn, buf, len, TCP_WRITE_FLAG_COPY /*| (wrap ? TCP_WRITE_FLAG_MORE : 0)*/);
  if (err!=0) {
    return MBEDTLS_ERR_SSL_WANT_WRITE;
  }
  return len; /* >0: no error */
}

Sending MQTT Messages

With TLS, sending MQTT messages is using mbedtls_ssl_write() instead of tcp_write():

/**
 * Try send as many bytes as possible from output ring buffer
 * @param rb Output ring buffer
 * @param tpcb TCP connection handle
 */
static void
mqtt_output_send(mqtt_client_t *client, struct mqtt_ringbuf_t *rb, struct tcp_pcb *tpcb)
{
  err_t err;
  int nof;
  u8_t wrap = 0;
  u16_t ringbuf_lin_len = mqtt_ringbuf_linear_read_length(rb);
  u16_t send_len = tcp_sndbuf(tpcb);
  LWIP_ASSERT("mqtt_output_send: tpcb != NULL", tpcb != NULL);

  if (send_len == 0 || ringbuf_lin_len == 0) {
    return;
  }

  LWIP_DEBUGF(MQTT_DEBUG_TRACE,("mqtt_output_send: tcp_sndbuf: %d bytes, ringbuf_linear_available: %d, get %d, put %d\n",
                                send_len, ringbuf_lin_len, ((rb)->get & MQTT_RINGBUF_IDX_MASK), ((rb)->put & MQTT_RINGBUF_IDX_MASK)));

  if (send_len > ringbuf_lin_len) {
    /* Space in TCP output buffer is larger than available in ring buffer linear portion */
    send_len = ringbuf_lin_len;
    /* Wrap around if more data in ring buffer after linear portion */
    wrap = (mqtt_ringbuf_len(rb) > ringbuf_lin_len);
  }
#if MQTT_USE_TLS
  printf("mqtt_output_send: mbedtls_ssl_write: bytes %d\r\n", send_len);
  nof = mbedtls_ssl_write(client->ssl_context, mqtt_ringbuf_get_ptr(rb), send_len);
  if (nof==send_len) {
    err = ERR_OK;
  } else {
    err = ERR_BUF; /* just assign an error */
  }
#else
  err = tcp_write(tpcb, mqtt_ringbuf_get_ptr(rb), send_len, TCP_WRITE_FLAG_COPY | (wrap ? TCP_WRITE_FLAG_MORE : 0));
#endif
  if ((err == ERR_OK) && wrap) {
    mqtt_ringbuf_advance_get_idx(rb, send_len);
    /* Use the lesser one of ring buffer linear length and TCP send buffer size */
    send_len = LWIP_MIN(tcp_sndbuf(tpcb), mqtt_ringbuf_linear_read_length(rb));
#if MQTT_USE_TLS
    printf("mbedtls_ssl_write: bytes %d\r\n", send_len);
    nof = mbedtls_ssl_write(client->ssl_context, mqtt_ringbuf_get_ptr(rb), send_len);
    if (nof==send_len) {
      err = ERR_OK;
    } else {
      err = ERR_BUF; /* just assign an error */
    }
#else
    err = tcp_write(tpcb, mqtt_ringbuf_get_ptr(rb), send_len, TCP_WRITE_FLAG_COPY);
#endif
  }
  if (err == ERR_OK) {
    mqtt_ringbuf_advance_get_idx(rb, send_len);
    /* Flush */
    tcp_output(tpcb);
  } else {
    LWIP_DEBUGF(MQTT_DEBUG_WARN, ("mqtt_output_send: Send failed with err %d (\"%s\")\n", err, lwip_strerr(err)));
  }
}

Summary

In order to get MQTT working with TLS/SLL and lwip, I had to deep dive into how TLS and lwip works. I have to admit that things were not as easy as I thought as both MQTT and TLS are new to me, and I only had used lwip as a ‘black box’. The current implementation is very likely not ideal, not that clean and lacks some error handling. But it ‘works’ fine so far with a local Mosquitto broker. Plus I have learned a lot new things. I plan to clean it up more, add better error handling, plus to add FreeRTOS in a next step. Will see how it goes :-).

I hope this is useful for you. I have pushed the application for the NXP FRDM-K64F board on GitHub (https://github.com/ErichStyger/mcuoneclipse/tree/master/Examples/MCUXpresso/FRDM-K64F/FRDM-K64F_lwip_lwip_mqtt_bm). I plan to update/improve/extend the implementation, so make sure you check the latest version on GitHub.

I hope you find this useful to add TLS to your MQTT application with lwip.

💡 There is an alterative lwip API which should be easier to use with TLS. I have found that one after writing this article: http://git.savannah.nongnu.org/cgit/lwip.git/tree/src/include/lwip/altcp.h

How to add server certificate verification, see my next article: “Tuturial: mbedTLS SLL Certificate Verification with Mosquitto, lwip and MQTT“.

Happy Encrypting 🙂

Links

Advertisements

7 thoughts on “Tutorial: Secure TLS Communication with MQTT using mbedTLS on top of lwip

    • Hi Carl,
      yes, I’m aware of FNET. But as it (currently?) only supports NXP devices, I have not used it. lwip on the other side is much more broadly used and supported.
      Erich

      Like

  1. Hi Erich,

    Compliments for gettin it running !
    I expect that my implementation ( MQX based ) should be a little bit simpler since RTCS give socket support with asyncronous receive and sending of the TCP/IP packets.
    Anyway, thank very much for all these article about MQTT and SSL : they are really inspiring and offer also a list of useful links.

    Luca

    Like

    • Thanks :-). Yes, I could have used sockets as well with lwip, but I wanted to start on the very low level, as it is easier to add a sockets layer later then to go the other way round.

      Like

  2. Pingback: Tuturial: mbedTLS SLL Certificate Verification with Mosquitto, lwip and MQTT | MCU on Eclipse

  3. Hi Erich,
    Great introduction! It’s very useful.
    I have one question about the reduce RAM usage part.
    The size of MBEDTLS_MPI_MAX_SIZE is reduced from 1024(default) to 512. I dont know what is MPI. How can you decide the 512 is enough? Can you give me some clues? 🙂
    Thanks!

    Like

What do you think?

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s