This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

DTLS Session Resumption on nRF9160, modem FW v1.1.0

I am using built-in DTLS for encrypting my UDP traffic. In order to save expensive data quota and reduce battery drain, I have to make use of session resumption, so that I do not have to perform the full handshake each time I connect to the server. The handshake including certificate verification works, but with session resumption I am running into problems:

I know I have to enable the session cache with setsockopt(), which I did (see below). It doesn't seem to work, because when I analyze the Client Hello packets the nRF9160 sends to the server, I can see that there is no session_ticket extension present, which suggests that session tickets are not supported. So i fall back to session IDs. My server provides a 32 byte session ID in the Server Hello packet. But after closing and reopening the session on the nRF9160, the session ID in the Client Hello packet is empty, suggesting that the nRF9160 did not cache the session data.

The server uses GNUTLS, and the session resumption mechanism was tested successfully with a separate Client (using openSSL).

I'm not sure about the correct way to enable the session cache with setsockopt(). The header net/socket.h does not provide that option. Whereas the header nrf_socket.h defines the option NRF_SO_SEC_SESSION_CACHE with numeric value 3, whis is the same as the numeric value of TLS_CIPHERSUITE_LIST in net/socket.h. So I certainly cannot use this option ID with setsockopt() from net/socket.h, as is will be interpreted as a ciphersuit list. Does this mean that I have to rewrite my application using all the corresponding nrf_socket functions defined in nrf_socket.h instead of the functions defined in net/socket.h? All the SDK samples use net/socket.h, not nrf_socket.h, btw, and in another thead I read that I should use the net/socket functions, which internally use the nrf_socket functions anyway, which makes me wonder why the option definitions are inconsistent.

Is this the right way to enable the session cache? Is there any documentation on the built-in TLS/DTLS support?

Here is the relevant code, which I am executing several times with variable pause in between:

...
if (!DTLSTestClient_Connect())
{
    LOG_ERR("DTLSTestClient_Connect failed");
}
if (dtlsSocket >= 0)
{
    k_sleep(5000);
    DTLSTestClient_SendTestData();
    nrf_close(dtlsSocket);
    dtlsSocket = -1;
}
...
      
bool DTLSTestClient_Connect()
{
  char buf[256];
  if (dtlsSocket < 0)
  {
    nrf_sec_tag_t sec_tag_list[] = {DTLS_TEST_SEC_TAG};

    struct tls_config_t {
      int peer_verify;      // preference for peer verification
      u32_t cipher_count;   // number of entries in the cipher list
      int *cipher_list;     // list of ciphers to be used for the session; may be NULL to use the default ciphers
      u32_t sec_tag_count;  // number of entries in the sec tag list
      sec_tag_t *sec_tag_list;  // list of security tags to be used for the session
      char *hostname;       // peer hostname for ceritificate verification; may be NULL to skip hostname verification
    };
    struct tls_config_t tls_config = {};
    tls_config.peer_verify = 2;
    tls_config.cipher_list = NULL;
    tls_config.cipher_count = 0;
    tls_config.sec_tag_count = ARRAY_SIZE(sec_tag_list);
    tls_config.sec_tag_list = sec_tag_list;
    tls_config.hostname = NULL;

    LOG_DBG("configuring socket ...");
    dtlsSocket = nrf_socket(NRF_AF_INET, NRF_SOCK_DGRAM, NRF_SPROTO_DTLS1v2);
    if (dtlsSocket < 0)
    {
      LOG_ERR("socket: %d", errno);
      return false;
    }
    if (nrf_setsockopt(dtlsSocket, NRF_SOL_SECURE, NRF_SO_SEC_PEER_VERIFY, &tls_config.peer_verify, sizeof(tls_config.peer_verify)) < 0)
    {
      LOG_ERR("setsockopt TLS_PEER_VERIFY: %d", errno);
      nrf_close(dtlsSocket);
      dtlsSocket = -1;
      return false;
    }
    if (tls_config.cipher_list != NULL && tls_config.cipher_count > 0)
    {
      if (nrf_setsockopt(dtlsSocket, NRF_SOL_SECURE, NRF_SO_CIPHERSUITE_LIST, tls_config.cipher_list, sizeof(int) * tls_config.cipher_count) < 0)
      {
        LOG_ERR("setsockopt TLS_CIPHERSUITE_LIST: %d", errno);
        nrf_close(dtlsSocket);
        dtlsSocket = -1;
        return false;
      }
    }
    if (tls_config.sec_tag_list != NULL && tls_config.sec_tag_count > 0)
    {
      if (nrf_setsockopt(dtlsSocket, NRF_SOL_SECURE, NRF_SO_SEC_TAG_LIST, tls_config.sec_tag_list, sizeof(sec_tag_t) * tls_config.sec_tag_count) < 0)
      {
        LOG_ERR("setsockopt TLS_SEC_TAG_LIST: %d", errno);
        nrf_close(dtlsSocket);
        dtlsSocket = -1;
        return false;
      }
    }
    if (tls_config.hostname)
    {
      if (nrf_setsockopt(dtlsSocket, NRF_SOL_SECURE, NRF_SO_HOSTNAME, tls_config.hostname, strlen(tls_config.hostname)) < 0)
      {
        LOG_ERR("setsockopt TLS_HOSTNAME: %d", errno);
        nrf_close(dtlsSocket);
        dtlsSocket = -1;
        return false;
      }
    }
  }
  if (dtlsSocket >= 0)
  {
    struct nrf_sockaddr_in serverAddr = {};
    serverAddr.sin_len = sizeof(serverAddr);
    serverAddr.sin_family = NRF_AF_INET;
    serverAddr.sin_addr.s_addr = ResolveIPv4Address(coapServerName, SOCK_DGRAM);
    serverAddr.sin_port = htons(5684);

    LOG_DBG("DTLS connecting...");
    if (nrf_connect(dtlsSocket, (struct sockaddr*) &serverAddr, sizeof(serverAddr)) < 0)
    {
      LOG_ERR("Cannot connect to UDP remote: %d", errno);
      nrf_close(dtlsSocket);
      dtlsSocket = -1;
      return false;
    }
    LOG_DBG("DTLS connected");
  }
  return true;
}


bool DTLSTestClient_SendTestData()
{
  char buf[256];
  u32_t t = k_uptime_get_32()/1000;
  int len = snprintf(buf, sizeof(buf), "test %02d:%02d:%02d", (t/3600)%24, (t/60)%60, t%60);
  LOG_DBG("data to send: %s", buf);
  // flush recv buffer
  while (nrf_recv(dtlsSocket, buf, sizeof(buf), NRF_MSG_DONTWAIT) > 0) {;}
  // send request  
  int ret = nrf_send(dtlsSocket, buf, len, 0);
  if (ret == len)
  {
    int bytesReceived = 0;
    int timeElapsed = 0;
    while (timeElapsed < 2000)
    {
      k_sleep(100);
      timeElapsed += 100;
      len = nrf_recv(dtlsSocket, buf, sizeof(buf) - 1, NRF_MSG_DONTWAIT);
      if (len > 0)
      {
        bytesReceived += len;
        buf[len] = '\0';
        LOG_DBG("%s", buf);
      }
      else if (len == 0)
      {
        LOG_DBG("server closed connection");
        nrf_close(dtlsSocket);
        dtlsSocket = -1;
        break;
      }
      else if (len < 0 && errno == EAGAIN)
      {
        if ( bytesReceived > 0 )
        {
          break;
        }
      }
      else
      {
        LOG_DBG("recv error: errno=%d", errno);
        return false;
      }
    }
    if (bytesReceived > 0)
    {
      LOG_DBG("%d bytes received after %d ms", bytesReceived, timeElapsed);
    }
    else
    {
      LOG_DBG("timeout after %d ms", timeElapsed);
      return false;
    }
  }
  else
  {
    LOG_DBG("send error: ret=%d errno=%d", ret, errno);
    if (errno == ENOTCONN || errno == ECONNRESET || errno == EPIPE || errno == ETIMEDOUT)
    {
      // connection closed or timed out
      nrf_close(dtlsSocket);
      dtlsSocket = -1;
    }
    return false;
  }
  return true;
}

Related