This tutorial introduces the specifics of using DTLS (as opposed to TLS) with Mbed TLS. It assumes you’re familiar with using TLS connections with Mbed TLS, otherwise, we recommend starting with the Mbed TLS tutorial.
Register timer callbacks and context with
Suitable callbacks for blocking I/O are provided in
For event-based I/O, you need to write your own callbacks based on your event framework.
Server-side, register cookie callbacks with
An implementation is provided in
ssl_cookie.cand requires context set up with
If you prefer to begin with code right away, you can skip to our
dtls_server.c examples in the
programs/ssl directory. However, this article provides more background information, so we recommend reading it in order to make more informed choices.
Protocol differences and additional settings
TLS usually runs on top of TCP and provides the same guarantees as TCP, in addition to authentication, integrity, and confidentiality.
Like TCP, it delivers a stream of bytes in order and does not preserve packet boundaries. DTLS usually runs on top of UDP, and once the handshake is finished, provides the same guarantees as UDP as well as authentication, integrity, and confidentiality.
Just like UDP, it delivers datagrams of bytes. Some datagrams may be lost or re-ordered, but unlike UDP, DTLS can detect and discard duplicated datagrams if needed. In Mbed TLS, this is controlled by the compile-time flag MBEDTLS_SSL_DTLS_ANTI_REPLAY and the run-time setting
mbedtls_ssl_conf_dtls_anti_replay(), both enabled by default.
With TLS, when a record is received that does not pass the integrity check, the connection is immediately terminated. This denies attackers an opportunity to do more than one guess at the message authentication key, without introducing any new DoS vectors (injecting bad records is just as hard as injecting a TCP RST to tear down the connection).
However, with DTLS over UDP, injecting bad records is very easy (an attacker only needs to know the source and destination IP and port), so the DTLS standard, section 18.104.22.168 recommends not to tear down the connection.
In Mbed TLS, it is possible to set a limit to the number of bad records before the connection is torn down; this is controlled by the compile-time flag MBEDTLS_SSL_DTLS_BADMAC_LIMIT (enabled by default) and the run-time setting
mbedtls_ssl_conf_dtls_badmac_limit() (unlimited by default).
Retransmission: timer callbacks
The (D)TLS handshake is a lock-step procedure: messages need to arrive in a certain order and cannot be skipped.
To achieve this on top of UDP, DTLS has its own retransmission mechanism, which needs timers. In Mbed TLS, the SSL module accepts a pair of callbacks for timer functions, which can be set using
mbedtls_ssl_set_timer_cb(). Example callbacks (for Unix and Windows) are provided in
mbedtls_timing_get_delay(), that are suitable for use with blocking I/O.
The callbacks have the following interface:
void mbedtls_timing_set_delay( void *data, uint32_t int_ms, uint32_t fin_ms ); int mbedtls_timing_get_delay( void *data );
In both cases,
data is a context shared by the callbacks. The setting function accepts two delays: an intermediate and a final one, and the getting function tells the caller which of these delays are expired, if any (see the documentation of
mbedtls_ssl_set_timer_cb() for details). The final delay is used to indicate when retransmission should happen, while the intermediate delay is an internal implementation detail whose semantic may evolve in future versions.
The interface was designed to allow a variety of implementation strategies, two of which two are:
The setting function records a timestamp and the values of the delay in the context, and the getting function compares the stored timestamp with the current time.
This is the strategy used by the example callbacks in
timing.c. It is suitable when you know the application will call
mbedtls_ssl_handshake() repeatedly until it returns
0 or a fatal error, which is usually the case when using blocking I/O.
Timers and events.
The setting function ensures (for example using a hardware timer or a system call) that a timeout handler will be called when one of the delays expires. This timeout handler needs to at least record the information about which delay expired so that the getting function can return the proper value. For the intermediate delay, this is all you need to do (the information may be used internally if another event, such as an incoming packet, causes
mbedtls_ssl_handshake()to be called again before the final delay expires).
For the final delay however, if you are using an event-driven style of programming, the timeout handler needs to generate an event that will cause
mbedtls_ssl_handshake() to be called again. Our DTLS handshake code will then internally call the
get_delay() function, notice the delays are expired, and take the appropriate action (either retransmit the last flight of messages or give up on the handshake and return a timeout error).
Note: You need to make sure that calling
set_delay() while a timer is already running cancels it (more precisely, that no event will be generated when the final delay expires). In particular, after a call like
set_delay(0, 0), no timer should be running any more. Said otherwise, there should be at most one running timer at any given time.
Note: If you have multiple concurrent connections, you need to make sure each has its own independent set of timers, and that, when a timeout event is generated for one connection,
mbedtls_ssl_handshake() is called with the appropriate
ssl_context for that connection (the
data argument for the callbacks can be used to store the required information). You also need to avoid making multiple calls to
mbedtls_ssl_handshake() with the same
ssl_context at the same time.
With event-based I/O, don’t use read timeouts by calling
mbedtls_ssl_conf_read_timeout() with a non-zero value, for two reasons:
It’s unnecessary, as you only call
mbedtls_ssl_read()when data is ready to be read.
It makes your timeout handler more complex, as it would have to know whether the timeout happened during handshake or read in order to schedule the appropriate function.
Retransmission: timeout values
The retransmission delay starts with a minimum value, then doubles on each retransmission until its maximum value is reached, in which case a handshake timeout is reported to the application. The minimum and maximum can be set using
mbedtls_ssl_conf_handshake_timeout() (default: 1 to 60 seconds).
See the documentation of this function for the meaning of those values if you need to tune them according to the characteristics of your network in order to achieve optimal performance/reliability. Even if your timeout values are perfectly tuned, your application should still be prepared to see failing handshakes and react appropriately.
Note:: There might seem to be a parallel between
set_delay() as they both accept two durations as arguments, but this is not the case. The final delay will take various values from
max, doubling every time, while the intermediate delay is an internal implementation detail.
The cookie callbacks that are registered by default always fail. The rationale is as follows:
You cannot register working callbacks by default since you cannot create and setup the cookie context in an automated way (it needs to be shared among SSL contexts).
You do not want to silently disable the feature by default, as that would mean insecure defaults.
Failing callbacks force you to notice something needs to be done.
You can, if you are sure that amplification attacks against third parties are not an issue in your particular deployment, disable
ClientHello verification at run-time:
Alternatively, at compilation: Undefine MBEDTLS_SSL_DTLS_HELLO_VERIFY in