Coverage Report

Created: 2026-11-12 06:42

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/lib/tls/dtls.c
Line
Count
Source
1
/*
2
 *   This program is free software; you can redistribute it and/or modify
3
 *   it under the terms of the GNU General Public License as published by
4
 *   the Free Software Foundation; either version 2 of the License, or
5
 *   (at your option) any later version.
6
 *
7
 *   This program is distributed in the hope that it will be useful,
8
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 *   GNU General Public License for more details.
11
 *
12
 *   You should have received a copy of the GNU General Public License
13
 *   along with this program; if not, write to the Free Software
14
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15
 */
16
17
/**
18
 * $Id: 2024e63690676be1101c2b4bc5e3dfcca370311c $
19
 *
20
 * @file lib/tls/dtls.c
21
 * @brief Datagram Transport Layer Security (DTLS)
22
 *
23
 * @copyright 2026 Network RADIUS SAS (legal@networkradius.com)
24
 */
25
RCSID("$Id: 2024e63690676be1101c2b4bc5e3dfcca370311c $")
26
USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */
27
28
#ifdef WITH_TLS
29
#define LOG_PREFIX "tls"
30
31
#include <openssl/hmac.h>
32
#include <openssl/rand.h>
33
#include <freeradius-devel/protocol/tls/freeradius.h>
34
35
#include "base.h"
36
#include "dtls.h"
37
#include "log.h"
38
#include "strerror.h"
39
40
/** The key the DTLS cookies are generated with
41
 *
42
 * One per process, generated at startup.  Rotating it would invalidate
43
 * outstanding cookies, which costs a client one extra round trip, and nothing
44
 * yet needs that.
45
 */
46
static uint8_t    dtls_cookie_key[32];
47
48
/** Build the cookie from #fr_socket_t in the #fr_tls_connection_t
49
 *
50
 * RFC 6347 section 4.2.1 suggests this construction:
51
 *
52
 *  Cookie = HMAC(Secret, Client-IP, Client-Parameters)
53
 *
54
 * The application hands DTLS a #fr_socket_t, which identifies a particular network connection.  This should
55
 * be the same for all DTLS calls, and should point to something which has a lifetime shared with the DTLS
56
 * connection.
57
 *
58
 * OpenSSL does not do this work itself.  It's two pairs of cookie callbacks differ.  For the stateless pair,
59
 * used by SSL_stateless(), "the integrity of the entire cookie ... is automatically verified by HMAC", while
60
 * for this pair (used for the DTLS HelloVerifyRequest), "the integrity of the cookie is not verified by
61
 * OpenSSL.  This is an application responsibility."
62
 *
63
 * @param[in] ssl the cookie is for.
64
 * @param[out] out  buffer of at least EVP_MAX_MD_SIZE octets.
65
 * @param[out] out_len  how much of `out` was written.
66
 * @return
67
 *  - 0 on success.
68
 *  - -1 on failure.
69
 */
70
static int dtls_cookie_hmac(SSL *ssl, uint8_t *out, unsigned int *out_len)
71
0
{
72
0
  fr_tls_session_t  *tls_session = fr_tls_session(ssl);
73
74
  /*
75
   *  Nothing identifies the peer, so there is nothing to bind the
76
   *  cookie to, and a cookie which proves nothing is worse than
77
   *  refusing to make one.
78
   */
79
0
  if (!tls_session->cookie) {
80
0
    fr_strerror_const("No cookie value set on the session");
81
0
    return -1;
82
0
  }
83
84
#ifdef __COVERITY__
85
  /*
86
   *  Coverity doesn't see HMAC() write the digest.  Without the
87
   *  memset, Coverity reports out as uninitialised in the callers.
88
   */
89
  memset(out, 0, EVP_MAX_MD_SIZE);
90
#endif
91
92
  /*
93
   *  Hash the entire #fr_socket_t, as the application should set this once, and then never change
94
   *  it.  This method means that we do exactly the same work for IPv4, Ipv6, and (potentially) Unix
95
   *  sockets with datagrams.
96
   */
97
0
  if (!HMAC(EVP_sha256(), dtls_cookie_key, sizeof(dtls_cookie_key),
98
0
      (uint8_t const *) tls_session->cookie, sizeof(*tls_session->cookie),
99
0
      out, out_len)) {
100
0
    fr_tls_strerror_printf(NULL);
101
0
    return -1;
102
0
  }
103
104
0
  return 0;
105
0
}
106
107
/** Generate the cookie which goes into a HelloVerifyRequest
108
 *
109
 * @param[in] ssl   the cookie is for.
110
 * @param[out] cookie   to write.  At most DTLS1_COOKIE_LENGTH octets.
111
 * @param[out] cookie_len how much was written.
112
 * @return
113
 *  - 1 on success, which is what OpenSSL wants.
114
 *  - 0 on failure, which aborts the handshake.
115
 */
116
int fr_dtls_cookie_generate_cb(SSL *ssl, unsigned char *cookie, unsigned int *cookie_len)
117
0
{
118
0
  uint8_t   hmac[EVP_MAX_MD_SIZE];
119
0
  unsigned int  hmac_len;
120
121
0
  static_assert(EVP_MAX_MD_SIZE <= DTLS1_COOKIE_LENGTH,
122
0
          "A SHA256 digest has to fit in a DTLS cookie");
123
124
0
  if (dtls_cookie_hmac(ssl, hmac, &hmac_len) < 0) return 0;
125
126
0
  memcpy(cookie, hmac, hmac_len);
127
0
  *cookie_len = hmac_len;
128
129
0
  return 1;
130
0
}
131
132
/** Check the cookie which came back in the second ClientHello
133
 *
134
 * @param[in] ssl   the cookie is for.
135
 * @param[in] cookie    the client echoed back.
136
 * @param[in] cookie_len  its length.
137
 * @return
138
 *  - 1 when the cookie is ours and is for this peer.
139
 *  - 0 otherwise, which makes OpenSSL ask for another one.
140
 */
141
int fr_dtls_cookie_verify_cb(SSL *ssl, unsigned char const *cookie, unsigned int cookie_len)
142
0
{
143
0
  uint8_t   hmac[EVP_MAX_MD_SIZE];
144
0
  unsigned int  hmac_len;
145
146
0
  if (dtls_cookie_hmac(ssl, hmac, &hmac_len) < 0) return 0;
147
148
0
  if (cookie_len != hmac_len) return 0;
149
150
  /*
151
   *  A constant time compare, so that a wrong cookie says only
152
   *  that it was wrong.
153
   */
154
0
  return CRYPTO_memcmp(cookie, hmac, hmac_len) == 0;
155
0
}
156
157
/** Generate the per-process cookie key
158
 *
159
 * Called once from fr_openssl_init().
160
 *
161
 * @return
162
 *  - 0 on success.
163
 *  - -1 on failure.
164
 */
165
int fr_dtls_cookie_init(void)
166
0
{
167
0
  if (RAND_bytes(dtls_cookie_key, sizeof(dtls_cookie_key)) != 1) {
168
0
    fr_tls_strerror_printf(NULL);
169
0
    return -1;
170
0
  }
171
172
0
  return 0;
173
0
}
174
175
/** Set up the parts of a session which only a datagram transport needs
176
 *
177
 * Called once per session, after the session knows its MTU, and only when the
178
 * context was built with DTLS_method().
179
 *
180
 * @param[in] tls_session to configure.
181
 * @param[in] conf    the configuration
182
 * @return
183
 *  - 0 on success.
184
 *  - -1 on failure.
185
 */
186
int fr_dtls_session_init(fr_tls_session_t *tls_session, fr_tls_conf_t const *conf)
187
0
{
188
0
  fr_assert(tls_session->socket_type == SOCK_DGRAM);
189
190
  /*
191
   *  OpenSSL asks the BIO for the MTU of the socket underneath
192
   *  it.  There is no socket under a memory BIO, so the question
193
   *  has no answer, and OpenSSL has to be told not to ask and
194
   *  given the figure instead.
195
   *
196
   <* Without both of these the handshake does not complete.
197
   */
198
0
  SSL_set_options(tls_session->ssl, SSL_OP_NO_QUERY_MTU);
199
200
  /*
201
   *  @todo - we should have a way for the application to update the MTU based on expected headers.
202
   *  i.e. the MTU here should be the "raw" full-packet MTU, not the MTU of the application-layer
203
   *  contents.
204
   *
205
   *  That's because the admin often can find out the raw MTU, or even guess, but knowing how large
206
   *  the application MTU is depends on IP version, etc.
207
   */
208
0
  tls_session->mtu = conf->fragment_size;
209
0
  SSL_set_mtu(tls_session->ssl, tls_session->mtu);
210
211
  /*
212
   *  OpenSSL writes one datagram per BIO_write(), sized to the
213
   *  MTU above.  The buffer those writes land in is a byte
214
   *  stream, so without this the boundaries between them are
215
   *  lost, and the application would send a whole flight as one
216
   *  oversized datagram.
217
   */
218
0
  if (fr_tls_bio_dbuff_datagram_init(tls_session->from_ssl) < 0) {
219
0
    fr_strerror_const("Failed initialising the datagram queue");
220
0
    return -1;
221
0
  }
222
223
0
  return 0;
224
0
}
225
226
/** The retransmission timer has fired
227
 *
228
 * OpenSSL holds the flight it last sent, and DTLSv1_handle_timeout() puts it
229
 * back into the outgoing buffer with fresh record sequence numbers.  Writing
230
 * it is just like any other write.
231
 *
232
 * @param[in] tl  the timer fired on.  Unused.
233
 * @param[in] now Unused.
234
 * @param[in] uctx  the fr_tls_connection_t.
235
 */
236
static void _fr_dtls_timer_expired(UNUSED fr_timer_list_t *tl, UNUSED fr_time_t now, void *uctx)
237
0
{
238
0
  fr_tls_connection_t *conn = talloc_get_type_abort(uctx, fr_tls_connection_t);
239
0
  request_t   *request = conn->request;
240
0
  int     ret;
241
242
0
  fr_assert(request != NULL);
243
244
0
  RDEBUG2("(TLS) %s - DTLS retransmission timer fired", conn->name);
245
246
  /*
247
   *  DTLSv1_handle_timeout() asks OpenSSL to retransmit the pending data, and writes those records
248
   *  via fr_tls_session_msg_cb().  That callback reads the request back off the SSL *, which means
249
   *  that the request has to be bound across the call.  Without the bind, the callback logs the
250
   *  retransmitted flight with no request.
251
   *
252
   *  A return of < 0 means the handshake has given up, which
253
   *  OpenSSL reports rather than this code deciding it.
254
   */
255
0
  fr_tls_session_request_bind(conn->tls_session->ssl, request);
256
0
  ret = DTLSv1_handle_timeout(conn->tls_session->ssl);
257
0
  fr_tls_session_request_unbind(conn->tls_session->ssl);
258
259
0
  if (ret < 0) {
260
0
    fr_tls_log_perror(request, "DTLS handshake timed out");
261
0
    fr_tls_session_error_add(request, FR_ERROR_VALUE_DTLS_HANDSHAKE_TIMEOUT);
262
0
    fr_tls_connection_failed(conn, TLS_CONNECTION_FAIL_TLS);
263
0
    return;
264
0
  }
265
266
0
  if (fr_tls_connection_write(conn) < 0) {
267
0
    fr_tls_connection_failed(conn, TLS_CONNECTION_FAIL_APPLICATION);
268
0
    return;
269
0
  }
270
271
  /*
272
   *  The interval doubles on each retransmission, so the next one
273
   *  is not the same as the last one.
274
   */
275
0
  fr_dtls_timer_update(conn);
276
0
}
277
278
/** Arm or disarm the retransmission timer
279
 *
280
 * OpenSSL changes the timer value on every round.  When there's
281
 * nothing outstanding, the timer is disarmed.
282
 *
283
 * @param[in] conn  to update the timer for.
284
 */
285
void fr_dtls_timer_update(fr_tls_connection_t *conn)
286
0
{
287
0
  request_t *request = conn->request;
288
0
  struct timeval  tv;
289
290
  /*
291
   *  A stream connection has no timer list, and nothing to time.
292
   */
293
0
  if (!conn->tl) return;
294
295
  /*
296
   *  Anything other than 1 means no timer is wanted.
297
   */
298
0
  if (DTLSv1_get_timeout(conn->tls_session->ssl, &tv) != 1) {
299
0
    FR_TIMER_DELETE(&conn->timer_ev);
300
0
    return;
301
0
  }
302
303
0
  FR_TIMER_DELETE(&conn->timer_ev);
304
305
0
  if (fr_timer_in(conn, conn->tl, &conn->timer_ev, fr_time_delta_from_timeval(&tv),
306
0
      false, _fr_dtls_timer_expired, conn) < 0) {
307
0
    REDEBUG("(TLS) %s - Failed arming the DTLS retransmission timer", conn->name);
308
0
    fr_tls_connection_failed(conn, TLS_CONNECTION_FAIL_APPLICATION);
309
0
  }
310
0
}
311
312
#endif /* WITH_TLS */