DTLS handshake never ends successfully

Xavi92
Posts: 36
Joined: Thu Mar 28, 2019 2:26 pm

DTLS handshake never ends successfully

Postby Xavi92 » Sat Jun 13, 2020 10:56 pm

I want to connect an ESP32 to a DTLS server using mbedtls' dtls_server demo. The code I used for the client is very similar to the dtls_client example, but is unable to finish the handshake process for some reason. According to Wireshark, the client is not responding to the "Server hello done" frame, causing a timeout that makes the server to send the certificate again and again until it gives up the connection. The dtls_client demo works correctly on the computer, but not on the ESP32. Has anyone tested DTLS on the ESP32?

This is the code for the DTLS client:

Code: Select all

#include "dtls.h"
#include "static_task.h"
#include "wifi.h"
#include "freertos/FreeRTOS.h"
#include "freertos/FreeRTOSConfig.h"
#include "freertos/event_groups.h"
#include "freertos/timers.h"
#include "freertos/portmacro.h"
#include "mbedtls/net_sockets.h"
#include "mbedtls/ssl.h"
#include "mbedtls/entropy.h"
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/debug.h"
#include "mbedtls/error.h"
#include "mbedtls/certs.h"
#include "mbedtls/timing.h"
#include "mbedtls/platform.h"
#include "global_defs.h"
#include "sdkconfig.h"
#include <errno.h>
#include <stdio.h>
#include <string.h>

enum {TIMER_PERIOD_MS = 10 / portTICK_PERIOD_MS};

struct dtls_static_priv
{
    mbedtls_net_context server_fd;
    mbedtls_entropy_context entropy;
    mbedtls_ctr_drbg_context ctr_drbg;
    mbedtls_ssl_context ssl;
    mbedtls_ssl_config conf;
    mbedtls_x509_crt cacert;

    struct dtls_time
    {
        TimerHandle_t handle;
        StaticTimer_t static_data;
        struct delay_priv
        {
            uint32_t ms;
            uint8_t count;
            bool finished;
        } interm, fin;
    } timer;
};

_Static_assert (sizeof (dtls_static) == sizeof (struct dtls_static_priv),
                "dtls_static public size mismatch");

#undef mbedtls_printf
#define mbedtls_printf(...) printf(__VA_ARGS__)

static void update_delay_priv(struct delay_priv *const dp)
{
    if (dp->count >= dp->ms)
        dp->finished = true;
    else
        dp->count++;
}

static void dtls_timer_expired(const TimerHandle_t handle)
{
    if (handle)
    {
        struct dtls_time *const t = (struct dtls_time *)pvTimerGetTimerID(handle);

        if (t)
        {
            update_delay_priv(&t->interm);
            update_delay_priv(&t->fin);
        }
        else
            mbedtls_printf("Timer handle %p not associated to any dtls_time instance\n", handle);
    }
    else
        mbedtls_printf("dtls_timer_expired: invalid timer handle\n");
}

static void timing_set_delay(void *const data, const uint32_t int_ms, const uint32_t fin_ms)
{
    struct dtls_time *const dtlst = data;

    dtlst->interm.ms = int_ms / TIMER_PERIOD_MS;
    dtlst->fin.ms = fin_ms / TIMER_PERIOD_MS;

    if (fin_ms)
    {
        dtlst->interm.count = 0;
        dtlst->interm.finished = false;

        dtlst->fin.count = 0;
        dtlst->fin.finished = false;

        xTimerStart(dtlst->handle, 0);
    }
}

static int timing_get_delay(void *const data)
{
    struct dtls_time *const dtlst = (struct dtls_time *)data;

    if (!dtlst->fin.ms)
        return -1;
    else if (dtlst->fin.finished)
        return 2;
    else if (dtlst->interm.finished)
        return 1;

    return 0;
}

static void my_debug( void *ctx, int level, const char *file, int line, const char *str )
{
    ((void) level);

    fprintf((FILE *)ctx, "%s:%04d: %s", file, line, str);
    fflush((FILE *)ctx);
}

static void dtls_close_priv(struct dtls_static_priv *const dtls)
{
    mbedtls_net_free(&dtls->server_fd);
    mbedtls_x509_crt_free(&dtls->cacert);
    mbedtls_ssl_free(&dtls->ssl);
    mbedtls_ssl_config_free(&dtls->conf);
    mbedtls_ctr_drbg_free(&dtls->ctr_drbg);
    mbedtls_entropy_free(&dtls->entropy);
}

static int dtls_retry(struct dtls_static_priv *const dtls)
{
    enum {MAX_RETRIES = 5};
    int ret = 0;
    size_t retries = 0;

    mbedtls_printf("Operation failed. Retrying...\n");

    while (retries++ < MAX_RETRIES)
    {
        if ((ret = mbedtls_ssl_session_reset(&dtls->ssl)))
        {
            mbedtls_printf("\tRetry %d failed, code: %d", retries, ret);
        }
        else
            break;
    }

    if (retries > MAX_RETRIES)
    {
        mbedtls_printf("Connection lost after %d retries\n", MAX_RETRIES);
        dtls_close_priv(dtls);
        ret = 1;
    }

    return ret;
}

#include <mbedtls/esp_debug.h>
int dtls_connect(const char *hostname, const char *port, dtls_static *const dtls_public)
{
    struct dtls_static_priv *const dtls = (struct dtls_static_priv *)dtls_public;
    int ret;

    while (!wifi_is_connected());

    mbedtls_net_init(&dtls->server_fd);
    mbedtls_ssl_init(&dtls->ssl);
    mbedtls_ssl_config_init(&dtls->conf);
    mbedtls_x509_crt_init(&dtls->cacert);
    mbedtls_ctr_drbg_init(&dtls->ctr_drbg);
    mbedtls_entropy_init(&dtls->entropy);

    {
        uint8_t mac_addr[6];
        ESP_ERROR_CHECK(esp_efuse_mac_get_default(mac_addr));

        if ((ret = mbedtls_ctr_drbg_seed(&dtls->ctr_drbg, mbedtls_entropy_func,
                                         &dtls->entropy, mac_addr, sizeof mac_addr)))
        {
            mbedtls_printf( " failed\n  ! mbedtls_ctr_drbg_seed returned %d\n", ret );
            return ret;
        }
    }

    if ((ret = mbedtls_x509_crt_parse(&dtls->cacert,
                                      (const unsigned char *)mbedtls_test_cas_pem,
                                      mbedtls_test_cas_pem_len)) < 0)
    {
        mbedtls_printf( " failed\n  !  mbedtls_x509_crt_parse returned -0x%x\n\n", -ret );
        return ret;
    }

    {
        enum
        {
            MIN_TIMEOUT_MS = 1000,
            MAX_TIMEOUT_MS = 60000
        };

        mbedtls_ssl_conf_handshake_timeout(&dtls->conf, MIN_TIMEOUT_MS, MAX_TIMEOUT_MS);
    }

    ret = mbedtls_net_connect(&dtls->server_fd, hostname, port, MBEDTLS_NET_PROTO_UDP);

    switch (ret)
    {
        case 0:
            /* Connection successful. */
            break;

        case MBEDTLS_ERR_NET_UNKNOWN_HOST:
            mbedtls_printf("Unknown host %s\n", hostname);
            return ret;

        case MBEDTLS_ERR_NET_CONNECT_FAILED:
            mbedtls_printf("Connection to %s:%s failed\n", hostname, port);
            return ret;

        case MBEDTLS_ERR_NET_SOCKET_FAILED:
            /* Fall through. */
        default:
            mbedtls_printf("mbedtls_net_connect failed with error code -0x%x\n", -ret);
            return ret;
    }

    if ((ret = mbedtls_ssl_config_defaults(&dtls->conf,
                                            MBEDTLS_SSL_IS_CLIENT,
                                            MBEDTLS_SSL_TRANSPORT_DATAGRAM,
                                            MBEDTLS_SSL_PRESET_DEFAULT)))
    {
        mbedtls_printf( " failed\n ! mbedtls_ssl_config_defaults returned %d\n\n", ret );
        return ret;
    }

    mbedtls_esp_enable_debug_log(&dtls->conf, 4);

    /* Taken from mbedtls/programs/ssl/dtls_client.c:
     * OPTIONAL is usually a bad choice for security, but makes interop easier
     * in this simplified example, in which the ca chain is hardcoded.
     * Production code should set a proper ca chain and use REQUIRED. */
    mbedtls_ssl_conf_authmode(&dtls->conf, MBEDTLS_SSL_VERIFY_NONE);
    mbedtls_ssl_conf_ca_chain( &dtls->conf, &dtls->cacert, NULL );
    mbedtls_ssl_conf_rng(&dtls->conf, mbedtls_ctr_drbg_random, &dtls->ctr_drbg);
    mbedtls_ssl_conf_dbg(&dtls->conf, my_debug, stdout);

    if ((ret = mbedtls_ssl_setup(&dtls->ssl, &dtls->conf)))
    {
        mbedtls_printf("mbedtls_ssl_setup() failed with code %d\n\n", ret);
        return ret;
    }

    if ((ret = mbedtls_ssl_set_hostname(&dtls->ssl, hostname)))
    {
        mbedtls_printf( " failed\n ! mbedtls_ssl_set_hostname returned %d\n\n", ret);
        return ret;
    }

    mbedtls_ssl_set_bio(&dtls->ssl, &dtls->server_fd, mbedtls_net_send, NULL, mbedtls_net_recv_timeout);

    dtls->timer.handle = xTimerCreateStatic("DTLS", TIMER_PERIOD_MS, pdTRUE, &dtls->timer,
                                            dtls_timer_expired, &dtls->timer.static_data);

    if (!dtls->timer.handle)
    {
        mbedtls_printf("%s, %d: xTimerCreateStatic() failed", __FILE__, __LINE__);
        return -1;
    }

    mbedtls_ssl_set_timer_cb(&dtls->ssl, &dtls->timer, timing_set_delay, timing_get_delay);

    do {
        mbedtls_printf("ssl->state = %d\n", dtls->ssl.state);
        ret = mbedtls_ssl_handshake(&dtls->ssl);
    }
    while( ret == MBEDTLS_ERR_SSL_WANT_READ ||
           ret == MBEDTLS_ERR_SSL_WANT_WRITE ||
           ret == MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS);

    if (ret)
    {
        mbedtls_printf( " failed\n  ! mbedtls_ssl_handshake returned -0x%x\n\n", -ret );
        return ret;
    }

    if ((ret = mbedtls_ssl_get_verify_result(&dtls->ssl)))
    {
        mbedtls_printf("mbedtls_ssl_get_verify_result returned %d\n\n", ret);
        return ret;
    }

    return 0;
}

int dtls_write(dtls_static *const dtls_public, const void *const buf, const size_t len)
{
    struct dtls_static_priv *const dtls = (struct dtls_static_priv *)dtls_public;
    size_t bytes_written = 0;
    int ret;

    do
    {
        if ((ret = mbedtls_ssl_write(&dtls->ssl, (const unsigned char *)buf, len)) >= 0)
            bytes_written += ret;
        else
        {
            mbedtls_printf("mbedtls_ssl_write returned error code %d", ret);
            if ((ret = dtls_retry(dtls)))
                return ret;
        }
    } while (bytes_written < len);

    return 0;
}

int dtls_read(dtls_static *const dtls_public, void *const buf, const size_t len)
{
    int ret;
    size_t read_bytes = 0;
    struct dtls_static_priv *const dtls = (struct dtls_static_priv *)dtls_public;

    do
    {
        ret = mbedtls_ssl_read(&dtls->ssl, (unsigned char*)buf, len);

        if (ret > 0)
        {
            if (read_bytes += ret >= len)
                return 0;
        }
        else
        {
            mbedtls_printf("mbedtls_ssl_read returned error code %d", ret);
            if ((ret = dtls_retry(dtls)))
                return ret;
        }
    } while (1);

    return -1;
}

void dtls_close(dtls_static *const dtls_public)
{
    struct dtls_static_priv *const dtls = (struct dtls_static_priv *)dtls_public;

    dtls_close_priv(dtls);
}

The Wireshark file cannot be attached here for some reason, so please use the following download link:
https://xami18.privatedns.org/index.php ... gGPTRDNJMH

Below there is a (rather verbose) log of the DTLS frames exchanged between client and server.
Attachments
mbedtls.log
mbedtls debug log
(681.11 KiB) Downloaded 24 times

Xavi92
Posts: 36
Joined: Thu Mar 28, 2019 2:26 pm

Re: DTLS handshake never ends successfully

Postby Xavi92 » Wed Jun 17, 2020 6:49 am

Any ideas? :)

FYI, dlts_static is a byte array that allows allocating the resources needed by DTLS statically by the caller (in other words, without heap allocation) and, at the same time, hiding the internals of the actual structure underneath. Therefore, this opaque structure is only valid as long as:

Code: Select all

sizeof dtls_static == sizeof (struct dtls_static_priv)
So that's why there is a static assertion on the previous message. This means dtls_static is currently defined as:

Code: Select all

typedef struct
{
    uint8_t op[1560];
} dtls_static;

ESP_flying_raijin
Posts: 2
Joined: Tue Aug 13, 2019 2:03 pm

Re: DTLS handshake never ends successfully

Postby ESP_flying_raijin » Fri Jun 19, 2020 2:58 pm

Hi @Xavi_92
Have you tried the coap_client( https://github.com/espressif/esp-idf/tr ... oap_client) and the coap_server(https://github.com/espressif/esp-idf/tr ... oap_server) example. The CoAP library internally uses mbedTLS DTLS API to create DTLS client and server respectively. You may use it as a reference to create DTLS client with mbedTLS on ESP32 if you do not want the CoAP.

Who is online

Users browsing this forum: ESP_Roland and 13 guests