Coverage Report

Created: 2026-10-18 06:56

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/lib/tls/alpn.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: 3835d73a21fe5bcd3b4a3f99c048af37145815f0 $
19
 *
20
 * @file lib/tls/alpn.c
21
 * @brief Set up Application Layer Protocol Negotiation (ALPN)
22
 *
23
 * ALPN allows TLS connections to signal what protocol they are carrying.  This lets applications use multiple
24
 * different protocols on the same UDP or TCP port.
25
 *
26
 * The client sends a list of ALPN strings that it accepts.  The server either chooses one and sends it back,
27
 * or else sends back nothing to indicate that none have been chosen.  Either end can allow no ALPN, or
28
 * require that some ALPN string was negotiated.
29
 *
30
 * @copyright 2026 Network RADIUS SAS (legal@networkradius.com)
31
 */
32
#ifdef WITH_TLS
33
0
#define LOG_PREFIX "tls"
34
35
#define _TLS_PRIVATE 1
36
37
#include <freeradius-devel/util/debug.h>
38
#include <freeradius-devel/server/pair.h>
39
40
#include <freeradius-devel/protocol/tls/freeradius.h>
41
42
#include "attrs.h"
43
#include "base.h"
44
#include "log.h"
45
46
/*
47
 *  @todo - for automatically configured ALPN.
48
 *
49
 *  1) The application should define an enumerated data type which contains the list of ALPNs which it
50
 *  knows about.
51
 *
52
 *  2) alpn.h should define a data structure of 3 elements: enum value (int), ALPN string (without leading
53
 *  length), and configuration item name (or a CONF_PAIR)
54
 *
55
 *  3) We likely have to pass that data structure to fr_tls_conf_parse_server() and
56
 *  fr_tls_conf_parse_client()
57
 *
58
 *  4) the TLS conf parser should allow an `alpn { ... }` section to be defined.  But its contents should
59
 *  be parsed by a callback. e.g. `radius_v1_1 = true`
60
 *
61
 *  5) the callback walks through the entries of the structure, and compares them to the CONF_PAIRs.  Any
62
 *  mismatch is an error.  Duplicates are an error.  If there's a match, we update tls_session->alpn with
63
 *  one octet of the length of the ALPN string, plus the string.  This allows the administrator to list
64
 *  the ALPN strings in preferred order.
65
 *
66
* 6) when ALPN is negotiated, the TLS code should use the cached stucture (above) to determine which
67
* enum value to place into a field in tls_session.  That way the application doesn't have to do any
68
* string comparisons itself.
69
 */
70
71
/** Validate the ALPN list.
72
 *
73
 * The client has not necessarily been authenticated, so we don't trust the data that they sent.
74
 *
75
 * The list is in wire format:
76
 * - one octet length (1..255)
77
 * - 'length' octets data.
78
 *
79
 * Bounded by 'size'.  There is no terminator.
80
 *
81
 * @param[in] alpn  list to check.
82
 * @param[in] len length of the list.
83
 * @return
84
 *  - true if the list is well formed.
85
 *  - false if the list is empty, holds an empty name, or does not end
86
 *    where `len` says it ends.
87
 */
88
static bool tls_alpn_valid(unsigned char const *alpn, size_t len)
89
0
{
90
0
  size_t i = 0;
91
92
0
  if (len == 0) return false;
93
94
0
  while (i < len) {
95
0
    if (alpn[i] == 0) return false;
96
97
0
    i += alpn[i] + 1;
98
0
  }
99
100
0
  return i == len;
101
0
}
102
103
/** Select a matching ALPN string.
104
 *
105
 * OpenSSL calls this only when the client offered a list, so a client which does not use ALPN never reaches
106
 * here.  That case is caught after the handshake instead, by fr_tls_session_alpn_check().
107
 *
108
 * The servers preference is set by the list order.  SSL_select_next_proto() walks the list, and stops on the
109
 * first matching name offered by the client.
110
 *
111
 * @param[in] ssl session being negotiated.
112
 * @param[out] out  the name chosen, pointing into one of the two lists.
113
 * @param[out] outlen length of that name.
114
 * @param[in] in  the client's list.
115
 * @param[in] inlen length of the client's list.
116
 * @param[in] arg unused, the configuration comes from the SSL *.
117
 * @return
118
 *  - SSL_TLSEXT_ERR_OK if a name was chosen.
119
 *  - SSL_TLSEXT_ERR_ALERT_FATAL if the two lists have nothing in common.
120
 */
121
static int tls_session_alpn_select_cb(SSL *ssl, unsigned char const **out, unsigned char *outlen,
122
              unsigned char const *in, unsigned int inlen, UNUSED void *arg)
123
0
{
124
0
  request_t   *request = fr_tls_session_request(ssl);
125
0
  fr_tls_conf_t const *conf = fr_tls_session_conf(ssl);
126
0
  unsigned int    i;
127
128
  /*
129
   *  OpenSSL runs this callback while it parses the ClientHello,
130
   *  which happens inside a handshake round, and the library binds
131
   *  the request across a round.
132
   */
133
0
  fr_assert(request != NULL);
134
135
  /*
136
   *  Check that the list wasn't mangled after the application set it.
137
   */
138
0
  fr_assert(tls_alpn_valid(conf->alpn, conf->sizeof_alpn));
139
140
0
  if (RDEBUG_ENABLED2) {
141
0
    for (i = 0; i < inlen; i += in[i] + 1) {
142
0
      RDEBUG2("(TLS) ALPN - peer offers \"%.*s\"", in[i], &in[i + 1]);
143
0
    }
144
0
  }
145
146
  /*
147
   *  SSL_select_next_proto() writes through a non-const pointer,
148
   *  while the callback is handed a const one.  The data it writes
149
   *  is a pointer into one of the two lists, not the list itself,
150
   *  so nothing const is written through.
151
   */
152
0
  if (SSL_select_next_proto(UNCONST(unsigned char **, out), outlen,
153
0
          conf->alpn, (unsigned int) conf->sizeof_alpn,
154
0
          in, inlen) != OPENSSL_NPN_NEGOTIATED) {
155
0
    REDEBUG("(TLS) ALPN - no protocol in common with the peer");
156
0
    return SSL_TLSEXT_ERR_ALERT_FATAL;
157
0
  }
158
159
0
  RDEBUG2("(TLS) ALPN - chose \"%.*s\"", (int) *outlen, *out);
160
161
0
  return SSL_TLSEXT_ERR_OK;
162
0
}
163
164
/** Set ALPN (or not) for a connection.
165
 *
166
 * This is called for both client and server.  A client sends it's list, and verifies that the reply sent back
167
 * from the server matches one of the ALPN strings it sent.  A server reads the client list, and compares the
168
 * ALPN strings to the ones that it accepts.
169
 *
170
 * @param[in] ctx to configure.
171
 * @param[in] conf  holding the protocol list.
172
 * @param[in] client  true for the client role.
173
 * @return
174
 *  - 0 on success, including the case where the application wants no ALPN.
175
 *  - -1 if the list is malformed, or OpenSSL refused it.
176
 */
177
int fr_tls_ctx_alpn_set(SSL_CTX *ctx, fr_tls_conf_t const *conf, bool client)
178
0
{
179
  /*
180
   *  No ALPN string.  If ALPN is required, that's a code error.
181
   */
182
0
  if (!conf->alpn) {
183
0
    if (conf->alpn_required) {
184
0
      ERROR("ALPN is required, but no protocols were set");
185
0
      return -1;
186
0
    }
187
188
0
    return 0;
189
0
  }
190
191
0
  if (!tls_alpn_valid(conf->alpn, conf->sizeof_alpn)) {
192
0
    ERROR("ALPN protocol list is malformed, or does not match sizeof_alpn (%zu)",
193
0
          conf->sizeof_alpn);
194
0
    return -1;
195
0
  }
196
197
0
  if (client) {
198
    /*
199
     *  Returns zero on success, which is the opposite of
200
     *  most of the SSL_CTX_set_* calls around it.
201
     */
202
0
    if (SSL_CTX_set_alpn_protos(ctx, conf->alpn, (unsigned int) conf->sizeof_alpn) != 0) {
203
0
      fr_tls_log_perror(NULL, "Failed setting the ALPN protocol list");
204
0
      return -1;
205
0
    }
206
0
  } else {
207
0
    SSL_CTX_set_alpn_select_cb(ctx, tls_session_alpn_select_cb, NULL);
208
0
  }
209
210
0
  return 0;
211
0
}
212
213
/** Get the ALPN string which was selected.
214
 *
215
 * @param[in] tls_session to check
216
 * @return
217
 *  - true if the two ends agreed on a protocol.
218
 *  - false if they did not, which includes the case where ALPN was not used.
219
 */
220
static bool tls_session_alpn(fr_tls_session_t *tls_session)
221
{
222
  unsigned char const *data;
223
  unsigned int    len = 0;
224
225
  SSL_get0_alpn_selected(tls_session->ssl, &data, &len);
226
  if (!data || (len == 0)) {
227
    tls_session->alpn = NULL;
228
    tls_session->sizeof_alpn = 0;
229
    return false;
230
  }
231
232
  MEM(tls_session->alpn = talloc_memdup(tls_session, data, len));
233
  tls_session->sizeof_alpn = len;
234
235
  return true;
236
}
237
238
/** Check that we agreed on an ALPN string (or not).
239
 *
240
 * Runs once the handshake is done, for both roles.
241
 *
242
 * A server catches most failures earlier, in tls_session_alpn_select_cb(), which sends a fatal alert when the
243
 * two lists have nothing in common.  What that callback cannot catch is a client which offered no list,
244
 * because it's not called.  A client has no callback at all, and learns what the server chose only from the
245
 * handshake.
246
 *
247
 * @param[in] request   to log through.
248
 * @param[in] tls_session to check.
249
 * @return
250
 *  - 0 if a protocol was agreed, or if none was needed.
251
 *  - -1 if one was needed and none was agreed.
252
 */
253
int fr_tls_session_alpn_check(request_t *request, fr_tls_session_t *tls_session)
254
0
{
255
0
  fr_tls_conf_t const *conf = tls_session->conf;
256
257
  /*
258
   *  The only caller is tls_session_async_handshake_done_round(),
259
   *  which the interpreter runs with a request.
260
   */
261
0
  fr_assert(request != NULL);
262
263
0
  if (!conf->alpn) return 0;
264
265
  /*
266
   *  SSL_get0_alpn_selected() hands back the name on its own, with
267
   *  no leading length octet, so the whole of what it gave us is
268
   *  the name.
269
   */
270
0
  if (tls_session_alpn(tls_session)) {
271
0
    fr_pair_t *vp;
272
273
0
    RDEBUG2("(TLS) ALPN - agreed on \"%.*s\"",
274
0
      (int) tls_session->sizeof_alpn, tls_session->alpn);
275
276
0
    MEM(pair_append_session_state(&vp, attr_tls_alpn) >= 0);
277
0
    MEM(fr_pair_value_bstrndup(vp, (char *) tls_session->alpn, tls_session->sizeof_alpn, false) == 0);
278
279
0
    RDEBUG2("(TLS) session-state.%pP", vp);
280
281
0
    return 0;
282
0
  }
283
284
0
  if (!conf->alpn_required) {
285
0
    RDEBUG2("(TLS) ALPN - No protocols in common, not using ALPN");
286
0
    return 0;
287
0
  }
288
289
0
  REDEBUG("(TLS) ALPN - Failure, no protocols in common");
290
0
  fr_tls_session_error_alert(request, tls_session,
291
0
           FR_ERROR_VALUE_ALPN_FAILED, SSL_AD_NO_APPLICATION_PROTOCOL);
292
293
0
  return -1;
294
0
}
295
#endif /* WITH_TLS */