How to add DTLS to nRF coap client

Hello,

i ran  nRF Coap Client and it worked pretty well.

I am working with nRF9160 DK.

Now, I want to add DTLS to this example to secure the data.

Can someone please help me or is there any tutorials on how to do that?

Best regards,

Cedric

  • Hello Cedric,

    please be aware that DTLS only is experimental and not officially supported in NCS. Except from enabling the mentioned configuration parameter, you have to reconfigure the socket using a DTLS protocol type.

    I recommend you to have a look at the Echo Client sample, which demonstrates the usage of the corresponding API.

    Regards,

    Markus

  • Hi Markus,

    thanks very much for your reply.

    I used nRF Connect Trace Collector to capture the trace files of the modem, so i could see if the encrytion has done, but i had some issues with it.

    Here is the trace as attachment, you can open it with wireshark and have a look at it and please help me find a solution for the issue.

    trace-LTE-M-mit Verschlüsselung.pcapng

    Best regards,

    Cedric

  • ced27 said:
    Here is the trace as attachment, you can open it with wireshark and have a look at it and please help me find a solution for the issue.

    Honestly, I’m not quite sure what issue you have. Can you elaborate?

    Regards,

    Markus

  • Honestly, I’m not quite sure what issue you have. Can you elaborate?

    Actually i am having problems with DTLS connection and my aim is to be able to encrypt all data with a Pre Shared Key (PSK) before sending it to the server.

    Here is a sample of my code:

    /*
     * Copyright (c) 2020 Nordic Semiconductor ASA
     *
     * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
     */
    
    #include <zephyr.h>
    #include <stdio.h>
    #include <modem/lte_lc.h>
    #include <net/socket.h>
    #include <random/rand32.h>
    #include <net/coap.h>
    
    #include <modem/nrf_modem_lib.h>
    #include <logging/log.h>
    #include <sys/printk.h>
    #include <modem/modem_key_mgmt.h>
    #include <net/tls_credentials.h>
    #include <nrfx.h>
    #include <nrf_socket.h>
    
    #include "ui.h"
    #include "config.h"
    
    #define PAYLOAD_BYTES 100
    #define PSK_TAG 2
    
    static int sock; // BSD socket API
    static struct sockaddr_storage server; // structure für CoAP-Server- Addressinformation.
    
    static uint16_t token; // request ID
    static struct pollfd fds;
    
    // Benutzeroberflächenmodul.
    static void ui_evt_handler(struct ui_evt *evt) // Event-Handler für LEDS
    {
    	if (!evt) {
    		return;
    	}
    }
    
    static int dtls_init(void){
    
    int err;
    
    #if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) && defined(CONFIG_MODEM_KEY_MGMT)
    	err = modem_key_mgmt_write(PSK_TAG,
    				MODEM_KEY_MGMT_CRED_TYPE_PSK,
    				client_psk,
    				strlen(client_psk));
    	if (err < 0) {
    		printk("Bereitstellung des PSK fehlgeschlagen: %d", err);
                    return err;
    	}
    
           err = modem_key_mgmt_write(PSK_TAG,
    				MODEM_KEY_MGMT_CRED_TYPE_IDENTITY,
    				psk_id,
    				strlen(psk_id) - 1);
    	if (err < 0) {
    		printk("Bereitstellung der PSK-ID fehlgeschlagen: %d", err);
                    return err;
    	}
    #endif
    
    
    #if defined(CONFIG_MBEDTLS_KEY_EXCHANGE_PSK_ENABLED)
    
            // Provision Client PSK.
    	err = tls_credential_add(PSK_TAG,
    				TLS_CREDENTIAL_PSK,
    				client_psk,
    				sizeof(client_psk));
    	if (err < 0) {
    		printk("Fehler beim Registrieren von PSK: %d", err);
                    return err;
    	}
    
            // Provision Client Identity.
    	err = tls_credential_add(PSK_TAG,
    				TLS_CREDENTIAL_PSK_ID,
    				psk_id,
    				sizeof(psk_id) - 1);
    	if (err < 0) {
    		printk("Fehler beim Registrieren von PSK ID: %d", err);
                    return err;
    	}
    
    #endif
            return 0;
    }
    
    static int server_initialisieren(void)
    {
    
    	struct sockaddr_in *server4 = ((struct sockaddr_in *)&server); // Socket-Adressstruktur für IPv4
    
    	server4->sin_family = AF_INET; // AF_INET  = IP Protokoll Version 4 (IPv4)
    	server4->sin_port = htons(CONFIG_SERVER_PORT); //Schreibe Port in Network-Byte-Order in das Feld sin_port
    
    	inet_pton(AF_INET, CONFIG_SERVER_ADDRESS,
    		  &server4->sin_addr); // Schreibe IP-Adresse des Servers in die sockaddr_in-Struktur
      
    
    	return 0;
    }
    
    static int server_verbinden(void)
    {
    	int err;
    
    	sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_DTLS_1_2); // Erzeuge Socket. SOCK_DGRAM = Datagram socket type
                                                                  // IPPROTO_DTLS_1_2 = DTLS 1.2 protocol
    
            sec_tag_t sec_tag_opt[] = {TLS_CREDENTIAL_PSK};
    
            err = nrf_setsockopt(sock, NRF_SOL_SECURE, TLS_SEC_TAG_LIST,
                     sec_tag_opt, sizeof(sec_tag_opt) * ARRAY_SIZE(sec_tag_opt));
    
            // sock=0 => success, sock<0 => fehler
    	if (sock < 0) {
    		printk("Fehler beim Erstellen des CoAP/UDP-Sockets: error %d\n", errno);
    		err = -errno;
    		goto error;
    	}
    
    	err = connect(sock, (struct sockaddr *)&server,
    		      sizeof(struct sockaddr_in)); // Verbindung zum Server herstellen
    
            // err=0 => success, err<0 => fehler
    	if (err < 0) {
    		printk("Verbindung fehlgeschlagen: error %d\n", errno);
                    err = -errno;
    		goto error;
    	}
    
            /* Initialize FDS, for poll. */
    	fds.fd = sock;
    	fds.events = POLLIN;
    
    	/* Randomize token. */
    	token = sys_rand32_get();
    
    	return 0;
    
    error:
    	(void)close(sock); // beende die Verbindung zum Socket 
    
    	return err;
    }
    
    static int uebertragung_zu_server_fn(void)
    {
    	int err;
    
            struct coap_packet request; // Anfragepaket
            uint8_t data[CONFIG_DATA_UPLOAD_SIZE_BYTES]; // Maximale Nachrichtgröße
            const char payload[PAYLOAD_BYTES]="AAAAAAAAAAAAAAAAAAAAAAAAAAAA"; // Payload, der übertragen wird
    
            char uri_path[] = "test"; //Coap Ressource: Californium test Ressouce (URI)
    
            // Initialisiere CoAP Anfrage
            err = coap_packet_init(&request, data, sizeof(data),
    			       1, COAP_TYPE_NON_CON,
    			       0, NULL,COAP_METHOD_PUT, coap_next_id());
    
    	if (err < 0) {
    		printk("CoAP-Anfrage konnte nicht erstellt werden, error %d\n", err);
    		return err;
    	}
            // Fügt eine Option an das Paket an
    	coap_packet_append_option(&request, COAP_OPTION_URI_PATH,
    					uri_path, strlen(uri_path));
    
    
            // Payload-Marker an CoAP-Paket anhängen
            coap_packet_append_payload_marker(&request);
    
            // Payload an CoAP-Paket anhängen
            err=coap_packet_append_payload(&request, (uint8_t *)payload, sizeof(payload) - 1);
            if (err < 0) {
    		printk("Payload konnte nicht angehängt werden, %d\n", err);
    		return err;
    	}
    
    	err = send(sock, request.data, request.offset, 0); // Daten werden an den Port 5683 gesendet
    	if (err < 0) {
    		printk("Paket konnte nicht übertragen werden,error %d\n", errno);
    		return -errno;
    	}
    
            printk("CoAP Anfrage gesendet: token 0x%04x\n", token);
    	printk("Payload von %d bytes wird an die ", sizeof(payload));
             // CONFIG_SERVER_ADDRESS = californium.eclipseproject.io
             // CONFIG_SERVER_PORT = 5683
            printk("IP Adresse %s, Port Nummer %d übertragen\n",
    	       CONFIG_SERVER_ADDRESS,
    	       CONFIG_SERVER_PORT);
    
            return 0;
    
    }
    
    
    // Ein Semaphore ist ein Kernel-Objekt, das ein traditionelles Zählsemaphor implementiert.
    // Ein Semaphor wird verwendet, um den Zugriff auf eine Reihe von Ressourcen durch mehrere
    // Threads zu steuern und um die Verarbeitung zwischen einem produzierenden und einem 
    // verbrauchenden Thread oder Interrupt Service Routine (ISRs) zu synchronisieren.
    
    static K_SEM_DEFINE(lte_verbunden, 0, 1); // Semaphore statisch definiert und initialisiert. 
                                              // lte_verbunden = Name des Semaphores. 0 = anfängliche Semaphorezählung 
                                              // 1 = maximal zulässige Semaphoranzahl.
    
    static void lte_handler(const struct lte_lc_evt *const evt)
    {
    	switch (evt->type) {
    	case LTE_LC_EVT_NW_REG_STATUS: // emfangenes Ereignis mit Informationen zum Netwerkregistrierungsstatus
                                           // des Modems
    		if ((evt->nw_reg_status != LTE_LC_NW_REG_REGISTERED_HOME) &&
    		     (evt->nw_reg_status != LTE_LC_NW_REG_REGISTERED_ROAMING)) {
    			break;
    		}
                      
    		printk("Netwerkregistrierungsstatus: %s",
    			evt->nw_reg_status == LTE_LC_NW_REG_REGISTERED_HOME ?
    			"verbunden mit Heimnetwerk\n" : "verbunden mit Roaming\n");
    
    		k_sem_give(&lte_verbunden); // Gib ein Semaphor an. Setzte die Zählung von lte_verbunden auf Null 
    		break;                      // &lte_verbunden= Adresse des Semapores
    
    	case LTE_LC_EVT_PSM_UPDATE: // empfangenes Ereignis mit Informationen zu PSM-Updates. Die Updates enthalten 
                                        //  PSM-Parameter, die vom Netwerk vorgeben wurden.
    
                    // Das zugehörige Payload ist das Mitglied psm_cfg vom Typ lte_lc_psm_cfg in dem Library lte.lc.h
                    // tau = Periodisches Aktualisierungsintervall des Tracking-Bereichs.
                    // active_time = Aktiv-Zeit (Zeit von RRC Leerlauf bis PSM)
    		printk("PSM-Parameter update: TAU: %d, Active time: %d\n",
    			evt->psm_cfg.tau, evt->psm_cfg.active_time);
    		break;
    
    	case LTE_LC_EVT_EDRX_UPDATE:{// empfangenes Ereignis mit Informationen zu eDRX-Updates. Die Updates enthalten 
                                        //  eDRX-Parameter, die vom Netwerk vorgeben wurden.
    
                    // Das zugehörige Payload ist das Mitglied edrx_cfg vom Typ lte_lc_edrx_cfg in dem Library lte.lc.h
                    // edrx = eDRX-Intervallwert [s]
                    // ptw = Paging-Zeitfenster [s]
    		char log_buf[60];
    		ssize_t len;
    
    		len = snprintf(log_buf, sizeof(log_buf),
    			       "eDRX parameter update: eDRX: %f, PTW: %f\n",
    			       evt->edrx_cfg.edrx, evt->edrx_cfg.ptw);
    		if (len > 0) {
    			printk("%s\n", log_buf);
    		}
    		break;
             }
    
    	case LTE_LC_EVT_RRC_UPDATE: // empfangenes Ereignis mit Informationen über den 
                                        // Radio Ressource Control(RRC)-Status des Modems
                    
                    // Das zugehörige Payload ist das Mitglied rrc_mode vom Typ lte_lc_rrc_mode in dem Library lte.lc.h
                    // RRC Modus ist entweder verbunden oder idle.
    		printk("RRC Modus: %s",
    			evt->rrc_mode == LTE_LC_RRC_MODE_CONNECTED ?
    			"verbunden\n" : "Idle\n");
    		break;
    
    	case LTE_LC_EVT_CELL_UPDATE: // empfangenes Ereignis mit Informationen über die aktuell verbundene Zelle
    
                    // Das zugehörige Payload ist das Mitglied Cell vom Typ lte_lc_cell in dem Library lte.lc.h
                    // id = Mobilfunkzellenidentifikation
                    // tac = Tracking area code
    		printk("LTE veränderte Zelle: Zelle ID: %d, Tracking area: %d\n",
    			evt->cell.id, evt->cell.tac);
    		break;
    
    	case LTE_LC_EVT_LTE_MODE_UPDATE: // Wenn ein Systemmodus konfiguiert ist,der entweder LTE-M oder NB-IoT
                                             // aktiviert, kann das Modem den aktuell aktiven LTE-Modus basierend auf
                                             // der Netwerkverfügbarkeit ändern. Dieses Ereignis zeigt dann an, welcher
                                             // LTE-Modus derzeit vom Modem verwendet wird.
    
    		printk("Active LTE Modus: %s",
    			evt->lte_mode == LTE_LC_LTE_MODE_NONE ? "None\n" :
    			evt->lte_mode == LTE_LC_LTE_MODE_LTEM ? "LTE-M\n" :
    			evt->lte_mode == LTE_LC_LTE_MODE_NBIOT ? "NB-IoT\n" :
    			"Unknown\n");
    		break;
    	default:
    		break;
    	}
    }
    
    static void modem_konfigurieren(void)
    {
    #if defined(CONFIG_NRF_MODEM_LIB) // CONFIG_NRF_MODEM_LIB = verwende Nordic Modem Library
    	if (IS_ENABLED(CONFIG_LTE_AUTO_INIT_AND_CONNECT)) { 
                      // CONFIG_LTE_AUTO_INIT_AND_CONNECT = automatische Registrierung bei LTE-M oder NB-IoT
    		  // passiert nichts, falls der Modem bereits konfiguriert und LTE verbunden ist.
    
    	} else {
    
    		int err; // integer für errors
    
    #if defined(CONFIG_PSM_ENABLE) // CONFIG_PSM_ENABLE fordert PSM vom Modbilfunknetz an.
    
    		/* Das Anfordern von PSM vor der Verbindung ermöglicht es dem Modem, das
    		 * Netwerk bereits während des Verbindungsvorgangs über unseren Wunsch
    		 * nach einer bestimmten PSM-Konfiguration zu informieren, anstatt in 
    		 * einer separaten Anfrage nach der Verbindungsherstellung, die in einigen
    		 * Netzwerken abgelehnt werden kann */
    		
    		err = lte_lc_psm_req(true); // Anfordern des Modems, das Power saving mode zu aktivieren
    		if (err < 0) {
                             // Falls err= negativ =>  Fehler aufgetreten
    
    			printk("Power Saving Mode wurde nicht aktiviert, error: %d\n",
    				err);
    		} else{
                          // falls err=0  => das Anfordern war erfolgreich
    
                          printk("PSM-Modus aktiviert\n\n");
                    }
    #endif
    
    #if defined(CONFIG_EDRX_ENABLE) // CONFIG_EDRX_ENABLE fordert eDRX vom Modbilfunknetz an.
    
    	/** Extended Discontinuous Reception (eDRX) ermöglicht es das Sleep mode
                in RRC idle mode auf Minuten oder Stunden zu verlängern, was noch deutlichere
                Einsparungen beim Energieverbrauch bietet*/
    
    	err = lte_lc_edrx_req(true); // eDRX wird aktiviert
    	if (err<0) {
    		printk("eDRX wurde nicht aktiviert, error: %d\n", err);
    	}else {
    		printk("eDRX aktiviert\n");
    	}
    #endif
    
    
                    ui_led_set_pattern(UI_LTE_CONNECTING); // LED3 blinkt, um der Verbindungsherstellungsvorgang 
                                                          // zu signalisieren.
    		printk("Verbindung zum LTE-Netz wird hergestellt\n");
    		printk("Das kann ein paar Minuten dauern..\n");
    		
                    // lte_lc_init_and_connect_async initialisiert das LTE-Modul, 
                    // konfiguriert das Modem und verbindet sich mit dem LTE-Netzwerk.
                    // lte_lc_init_and_connect_async überprüft zusätzlich die LTE-Ereignisse
                    // vom Typ LTE_LC_EVT_NW_REG_STATUS in lte_handler(), um festzustellen, wann
                    // die LTE-Verbindung besteht.
    
    		err = lte_lc_init_and_connect_async(lte_handler);
    		if (err) {
                    // err= negativ => Fehler aufgetreten
    
    			printk("Modem konnte nicht konfiguriert werden, error: %d\n",
    				err);
    			return;
    		}
                    // err=0 => besteht LTE-Verbindung 
    
                    printk("Mit LTE-Netz verbunden\n");
    		ui_led_set_pattern(UI_LTE_CONNECTED); // LED3 leuchtet, um das Bestehen der Verbindung vorzugeben.
    	}
    #endif
    }
    
    
    /* Gibt 0 zurück, wenn Daten verfügbar sind.
     * Gibt -EAGAIN zurück, wenn eine Zeitüberschreitung aufgetreten ist und keine Daten geschickt sind.
     * Gibt im Falle eines Poll-Fehlers einen anderen, negativen Fehlercode zurück.
     */
    static int wait(int timeout)
    {
    	int ret = poll(&fds, 1, timeout);
    
    	if (ret < 0) {
    		printk("poll error: %d\n", errno);
    		return -errno;
    	}
    
    	if (ret == 0) {
    		/* Timeout. */
    		return -EAGAIN;
    	}
    
    	if ((fds.revents & POLLERR) == POLLERR) {
    		printk("warte: POLLERR\n");
    		return -EIO;
    	}
    
    	if ((fds.revents & POLLNVAL) == POLLNVAL) {
    		printk("warte: POLLNVAL\n");
    		return -EBADF;
    	}
    
    	if ((fds.revents & POLLIN) != POLLIN) {
    		return -EAGAIN;
    	}
    
    	return 0;
    }
    
    void main(void)
    {
    
            int64_t next_msg_time = CONFIG_DATA_UPLOAD_FREQUENCY_MS;
    	int err;
    
    	printk("Startet.....\n");
    
            err = dtls_init();
              if (err<0) {
              printk("DTLS Init Error: %d\n", err);
            }
    
            // Initialisiere das Benutzeroberflächenmodul
            ui_init(ui_evt_handler);
    
            // Modem wird konfiguriert
    #if defined(CONFIG_NRF_MODEM_LIB)
    
    	modem_konfigurieren();
    
    	k_sem_take(&lte_verbunden, K_FOREVER);
    #endif
    
          next_msg_time = k_uptime_get();
    
            // Verbindungsaufbau des Clients zu dem Server
    	err = server_initialisieren();
    	if (err) {
    		printk("Serververbindung kann nicht initialisiert werden\n");
    		return;
    	}
            
            // Verbindung zum Server herstellen
    	err = server_verbinden();
    	if (err) {
    		printk("Keine Verbindung zum Server möglich\n");
    		return;
    	}
    
    while (1) {
    		if (k_uptime_get() >= next_msg_time) {
    			if (uebertragung_zu_server_fn() != 0) {
    				printk("Fehler beim Senden der Anfrage, exit...\n");
    				break;
    			}
    
    			next_msg_time += CONFIG_DATA_UPLOAD_FREQUENCY_MS;
    		}
    
    		int64_t remaining = next_msg_time - k_uptime_get();
    
    		if (remaining < 0) {
    			remaining = 0;
    		}
    
    		err = wait(remaining);
    		if (err < 0) {
    			if (err == -EAGAIN) {
    				continue;
    			}
    
    			printk("Poll error, exit...\n");
    			break;
    		}
    
    	}
    
    }
    

    Best regards,

    Cedric

  • ced27 said:
    Actually i am having problems with DTLS connection

    The device initiates the DTLS handshake with a "Client Hello", which is correct.

    The problem appears to be the destination, which is 0.0.0.0. Which server/IP address are you trying to connect to? What is CONFIG_SERVER_ADRESS defined as?

    static int server_initialisieren(void)
    {
    
    	struct sockaddr_in *server4 = ((struct sockaddr_in *)&server); // Socket-Adressstruktur für IPv4
    
    	server4->sin_family = AF_INET; // AF_INET  = IP Protokoll Version 4 (IPv4)
    	server4->sin_port = htons(CONFIG_SERVER_PORT); //Schreibe Port in Network-Byte-Order in das Feld sin_port
    
    	inet_pton(AF_INET, CONFIG_SERVER_ADDRESS,
    		  &server4->sin_addr); // Schreibe IP-Adresse des Servers in die sockaddr_in-Struktur
      
    
    	return 0;
    }

    Regards,

    Markus

Related