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; }