Exercise 2

Adding DTLS to the CoAP connection

In this exercise, we will encrypt the communication between our board and the CoAP server using DTLS.

DTLS is based on the TLS protocol and is intended to provide the same security guarantees. The main difference is that DTLS uses UDP, which is the transport layer CoAP uses, and is why we are using it in this exercise.

In addition to encryption, which makes sure that the content of your communication can not be read by third parties along the network path, DTLS also makes sure that the content cannot be altered by third parties along the network path.

Exercise Steps

1. In the GitHub repository for this course, go to the base code for this exercise, found in lesson5/cellfund_less5_exer2.

2. In the Kconfig file, define two new configurations COAP_DEVICE_NAME and COAP_SERVER_PSK.

Both these configurations will be used when writing credentials to the modem.

2.1 COAP_DEVICE_NAME will be used as the PSK Identity. From the documentation for the server we are using, the PSK Identity can be on the form cali.*.*, so we are using cali.test.nrf9160.

2.2 COAP_SERVER_PSK is the PSK secret, which is the string .fornium which when converted into hexadecimal values becomes 2e666f726e69756d.

3. Change the configured server port to use the DTLS port for the CoAP server we are using.

In our case, eclipse.californium.io uses port 5684 for DTLS connections. This is the standard CoAP over DTLS port.

In prj.conf, change the value of COAP_SERVER_PORT to be 5684.

4. Enable the modem key management library and TLS credentials API.

4.1 Enable the configuration by adding the following line to the prj.conf file.

4.2 In main.c, include the header file for the modem key management library and the TLS credentials API from the BSD socket API.

#include <modem/modem_key_mgmt.h>
#include <zephyr/net/tls_credentials.h>

5. Define the macros for the security tag and RX check interval.

#define SEC_TAG 12
#define RX_CHECK_INTERVAL 6500

6. In client_init(), create a DTLS socket and use setsockopt() to write credentials to the socket.

6.1 Create a DTLS socket by changing the last parameter in the call to socket() to IPPROTO_DTLS_1_2.

sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_DTLS_1_2);

7. Set the DTLS relevant socket options for the socket using setsockopt(), which has the following signature

  • sock – File descriptor for the socket to use
  • level – Specifies at which protocol level the option resides
  • optname – A single option to set
  • optval – Value of the option to set
  • optlen – Length of the option value

We will be setting the options TLS_PEER_VERIFY, TLS_HOSTNAME and TLS_SEC_TAG_LIST, which resides at the protocol level SOL_TLS. Note that since DTLS is an implementation of TLS intended to work over datagram sockets, DTLS uses the same socket options as TLS.

7.1 Set the option TLS_PEER_VERIFY to be required.

enum {
	NONE = 0,
	OPTIONAL = 1,
	REQUIRED = 2,
};

int verify = REQUIRED;

err = setsockopt(sock, SOL_TLS, TLS_PEER_VERIFY, &verify, sizeof(verify));
if (err) {
	LOG_ERR("Failed to setup peer verification, errno %d\n", errno);
	return -errno;
}

7.2 Set the option TLS_HOSTNAME to be the hostname for the CoAP server.

err = setsockopt(sock, SOL_TLS, TLS_HOSTNAME, CONFIG_COAP_SERVER_HOSTNAME,
	 strlen(CONFIG_COAP_SERVER_HOSTNAME));
if (err) {
	LOG_ERR("Failed to setup TLS hostname (%s), errno %d\n",
		CONFIG_COAP_SERVER_HOSTNAME, errno);
	return -errno;
}

7.3 Set the option TLS_SEC_TAG_LIST to the value we defined earlier in SEC_TAG.

When writing the credentials to the modem, this is the security tag that the credentials will be referenced with. The security tag must be attached to the socket before connecting.

sec_tag_t sec_tag_list[] = { SEC_TAG };

err = setsockopt(sock, SOL_TLS, TLS_SEC_TAG_LIST, sec_tag_list,
		 sizeof(sec_tag_t) * ARRAY_SIZE(sec_tag_list));
if (err) {
	LOG_ERR("Failed to setup socket security tag, errno %d\n", errno);
	return -errno;
}

8. Before turning on the modem, we need to write credentials to the modem using modem_key_mgmt_write(), which has the following signature

The function takes the security tag associated with the credential as the first parameter. This is the same security tag we wrote to the socket using setsockopt().

8.1 First we write the PSK identity to the modem, by specifying the credential type MODEM_KEY_MGMT_CRED_TYPE_IDENTITY.

This is the value stored in the Kconfig CONFIG_COAP_DEVICE_NAME.

err = modem_key_mgmt_write(SEC_TAG, MODEM_KEY_MGMT_CRED_TYPE_IDENTITY, CONFIG_COAP_DEVICE_NAME, 
			strlen(CONFIG_COAP_DEVICE_NAME));
if (err) {
	LOG_ERR("Failed to write identity: %d\n", err);
	return;
}

8.2 Next we write the PSK to the modem, by specifying the credential type MODEM_KEY_MGMT_CRED_TYPE_PSK. Recall that the PSK is stored in the Kconfig CONFIG_COAP_SERVER_PSK.

err = modem_key_mgmt_write(SEC_TAG, MODEM_KEY_MGMT_CRED_TYPE_PSK, CONFIG_COAP_SERVER_PSK, 
			strlen(CONFIG_COAP_SERVER_PSK));
if (err) {
	LOG_ERR("Failed to write identity: %d\n", err);
	return;
}

We have now configured the application to connect over CoAP using DTLS. However, if we test the application in its current state the connection will disconnect after a certain amount of time.

This is because of something called the NAT session timeout, which is the length of time the network will keep an inactive connection alive. The NAT timeout value is given by the network and can be as low as 12 seconds.

9. To solve this problem, we will set up some logic that will regularly ping the server to keep the connection alive. This will be done using work items and the system workqueue thread.

9.1 Define the interval in which the device will ping the server, TX_KEEP_ALIVE_INTERVAL.

 #define TX_KEEP_ALIVE_INTERVAL 6500

9.2 Define the delayable work item rx_work as a structure of type k_work_delayable.

static struct k_work_delayable rx_work;

9.3 Define the handler rx_work_fn() for the work item rx_work.

Define the function rx_work_fn() to call client_get_send(), just to send a package to keep the connection alive.

static void rx_work_fn(struct k_work *work)
{
	client_get_send();
}

9.4 Initialize the work item rx_work with the handler function rx_work_fn().

Before the while-loop in main(), call k_work_init_delayable() to initialize the delayable work structure rx_work with the handler rx_work_fn().

k_work_init_delayable(&rx_work,rx_work_fn);

9.5 In the while-loop, reschedule the work item rx_work using k_work_reschedule() with a delay of TX_KEEP_ALIVE_INTERVAL.

k_work_reschedule(&rx_work,K_MSEC(TX_KEEP_ALIVE_INTERVAL));

So if we don’t receive anything within the TX_KEEP_ALIVE_INTERVAL, rx_work performs the work to keep the connection alive. And if we do receive something, this is handled with a higher priority than rx_work, and then at the next iteration, rx_work is rescheduled with a delay of TX_KEEP_ALIVE_INTERVAL, and the cycle repeats.

More on this

There is another way to handle the issue with the NAT timer, which is to let the network disconnect, and then the next time you want to send something, initiate the DTLS connection again. In this case, the socket option TLS_SESSION_CACHE_ENABLED is recommended as it will reduce the amount of data, time and power that is required for the DTLS handshake.

Which solution to choose is a tradeoff between latency and power consumption in your application.

10. Build the exercise and flash it to your board.

Testing

11. Let’s first set up a CoAP Client to communicate with our board. This is pretty much similar to the previous exercise. Except you need to specify the server address with coaps .

We will be testing on the PC using cf-browser. You will need Java Runtime Environment installed on your machine.

More on this

We have many options here. If you are using a PC, you could either download the desktop application cf-browser or you could use a chrome extension Copper for Chrome (Cu4Cr) CoAP. If you are using a tablet or smartphone, several Android and iOS apps are available that act as a CoAP client (for example CoAP Client).

11.1 Enter the CoAP server URL (make sure it starts with coaps – it is available in the dropdown menu ) and discover its resources as shown below.

11.2 Send a message from the CoAP client to the board.

Locate the CONFIG_COAP_RX_RESOURCE resource used by the board to receive data. In other words, this is the CoAP resource that you will use to send to the board. This was set in step 3.2 to validate . Type the message you want to send to the board and send it as a PUT request. You should see a response of ACK 2.04/CHANGED which means that the client has successfully modified the content of the resource.

On your board, the board is configured to periodically check this resource and print it on the terminal. Also, pressing button 1 will print the received message on the terminal.

The payload “Hi From my PC!” is the value stored in the CONFIG_COAP_RX_RESOURCE resource.

11.2 Send a message from your board to the CoAP client.

Press button 2 on your nRF9160 DK (or button 1 twice on the Thingy:91). This will send a PUT request from your board to the CoAP server. The message sent is set in the macron MESSAGE_TO_SEND in STEP 4.1

Notice that when sending a PUT request, the CoAP packet received back from the server has no payload.

On the CoAP client side, locate the CONFIG_COAP_TX_RESOURCE resource used by the board to send data. In other words, this is the CoAP resource that you will use to receive from the board. This was set in step 3.2 to large-update. Then issue a GET request. You should see the message in the response payload as shown below.

The payload “Hello from nRF9160 SiP” is the value stored in the CONFIG_COAP_TX_RESOURCE resource.

The solution for this exercise can be found in lesson5/cellfund_less5_exer2_solution.

Register an account
Already have an account? Log in
(All fields are required unless specified optional)

Forgot your password?
Enter your email address, and we will send a link to reset your password.