/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 */ |