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/pairs.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: 2ea970c1d43ec8944e213f31d653d3fc676a1306 $
19
 *
20
 * @file tls/pairs.c
21
 * @brief Functions to convert certificate OIDs to attribute pairs
22
 *
23
 * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
24
 */
25
RCSID("$Id: 2ea970c1d43ec8944e213f31d653d3fc676a1306 $")
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 <freeradius-devel/tls/openssl_user_macros.h>
32
#include <freeradius-devel/util/pair.h>
33
#include <freeradius-devel/server/request.h>
34
#include <freeradius-devel/server/pair.h>
35
#include <freeradius-devel/der/der.h>
36
37
#include "attrs.h"
38
#include "bio.h"
39
#include "log.h"
40
#include "session.h"
41
#include "utils.h"
42
43
#include <openssl/x509v3.h>
44
#include <openssl/ssl.h>
45
#include <openssl/ocsp.h>
46
47
DIAG_OFF_OPTIONAL(used-but-marked-unused) /* fix spurious warnings for sk macros */
48
/** Extract session pairs from the Subject Alternate Name extension
49
 *
50
 */
51
static bool tls_session_pairs_from_san(fr_pair_list_t *pair_list, TALLOC_CTX *ctx, request_t *request,
52
               FR_OPENSSL_X509_CONST X509_EXTENSION *ext)
53
0
{
54
0
  GENERAL_NAMES *names = NULL;
55
0
  int   i;
56
0
  fr_pair_t *vp;
57
58
0
  if (!(names = X509V3_EXT_d2i(ext))) return false;
59
60
0
  for (i = 0; i < sk_GENERAL_NAME_num(names); i++) {
61
0
    GENERAL_NAME *name = sk_GENERAL_NAME_value(names, i);
62
63
0
    switch (name->type) {
64
0
#ifdef GEN_EMAIL
65
0
    case GEN_EMAIL:
66
0
      MEM(fr_pair_append_by_da(ctx, &vp, pair_list,
67
0
             attr_tls_certificate_subject_alt_name_email) == 0);
68
0
      MEM(fr_pair_value_bstrndup(vp,
69
0
               (char const *)ASN1_STRING_get0_data(name->d.rfc822Name),
70
0
               fr_tls_asn1_string_length(name->d.rfc822Name), true) == 0);
71
0
      break;
72
0
#endif  /* GEN_EMAIL */
73
0
#ifdef GEN_DNS
74
0
    case GEN_DNS:
75
0
      MEM(fr_pair_append_by_da(ctx, &vp, pair_list,
76
0
             attr_tls_certificate_subject_alt_name_dns) == 0);
77
0
      MEM(fr_pair_value_bstrndup(vp,
78
0
               (char const *)ASN1_STRING_get0_data(name->d.dNSName),
79
0
               fr_tls_asn1_string_length(name->d.dNSName), true) == 0);
80
0
      break;
81
0
#endif  /* GEN_DNS */
82
0
#ifdef GEN_OTHERNAME
83
0
    case GEN_OTHERNAME:
84
      /* look for a MS UPN */
85
0
      if (NID_ms_upn != OBJ_obj2nid(name->d.otherName->type_id)) break;
86
87
      /* we've got a UPN - Must be ASN1-encoded UTF8 string */
88
0
      if (name->d.otherName->value->type == V_ASN1_UTF8STRING) {
89
0
        MEM(fr_pair_append_by_da(ctx, &vp, pair_list,
90
0
               attr_tls_certificate_subject_alt_name_upn) == 0);
91
0
        MEM(fr_pair_value_bstrndup(vp,
92
0
                 (char const *)ASN1_STRING_get0_data(name->d.otherName->value->value.utf8string),
93
0
                 fr_tls_asn1_string_length(name->d.otherName->value->value.utf8string),
94
0
                 true) == 0);
95
0
        break;
96
0
      }
97
0
      RWARN("Invalid UPN in Subject Alt Name (should be UTF-8)");
98
0
        break;
99
0
#endif  /* GEN_OTHERNAME */
100
0
      default:
101
        /* XXX TODO handle other SAN types */
102
0
        break;
103
0
    }
104
0
  }
105
0
  if (names != NULL) GENERAL_NAMES_free(names);
106
107
0
  return true;
108
0
}
109
110
/** Extract session pairs from the X509v3-CRL-Distribution-Points extension
111
 *
112
 */
113
static bool tls_session_pairs_from_crl(fr_pair_list_t *pair_list, TALLOC_CTX *ctx, UNUSED request_t *request,
114
               FR_OPENSSL_X509_CONST X509_EXTENSION *ext)
115
{
116
  ASN1_STRING const *s = X509_EXTENSION_get_data(ext);
117
  char unsigned const *data = ASN1_STRING_get0_data(s);
118
  STACK_OF(DIST_POINT)  *dps;
119
  DIST_POINT    *dp;
120
  STACK_OF(GENERAL_NAME)  *names;
121
  GENERAL_NAME    *name;
122
  fr_pair_t   *vp;
123
  int     i, j;
124
125
  if (!(dps = d2i_CRL_DIST_POINTS(NULL, &data, fr_tls_asn1_string_length(s)))) return false;
126
127
  for (i = 0; i < sk_DIST_POINT_num(dps); i++) {
128
    dp = sk_DIST_POINT_value(dps, i);
129
130
    /*
131
     *  RFC 5280 Section 4.2.1.13 says that the distpoint is optional.
132
     */
133
    if (!dp->distpoint) {
134
      CRL_DIST_POINTS_free(dps);
135
      return false;
136
    }
137
138
    names = dp->distpoint->name.fullname;
139
140
    /*
141
     *  We only want CRL distribution points that cover all reasons,
142
     *  so ignore any where reasons are set.
143
     */
144
    if (dp->reasons) continue;
145
146
    for (j = 0; j < sk_GENERAL_NAME_num(names); j++) {
147
      name = sk_GENERAL_NAME_value(names, j);
148
149
      if (name->type != GEN_URI) continue;
150
      MEM(fr_pair_append_by_da(ctx, &vp, pair_list,
151
             attr_tls_certificate_x509v3_crl_distribution_points) == 0);
152
      MEM(fr_pair_value_bstrndup(vp,
153
               (char const *)ASN1_STRING_get0_data(name->d.uniformResourceIdentifier),
154
               fr_tls_asn1_string_length(name->d.uniformResourceIdentifier),
155
               true) == 0);
156
    }
157
  }
158
159
  CRL_DIST_POINTS_free(dps);
160
161
  return true;
162
}
163
164
/** Extract session pairs from the Authority Key Identifier extension - specifically OCSP URI
165
 *
166
 */
167
static bool tls_session_pairs_from_aia(fr_pair_list_t *pair_list, TALLOC_CTX *ctx, UNUSED request_t *request,
168
               FR_OPENSSL_X509_CONST X509_EXTENSION *ext)
169
{
170
  ASN1_STRING const *s = X509_EXTENSION_get_data(ext);
171
  char unsigned const *data = ASN1_STRING_get0_data(s);
172
  fr_pair_t   *vp;
173
  AUTHORITY_INFO_ACCESS *aia;
174
  ACCESS_DESCRIPTION  *ad;
175
  char      *host = NULL, *path = NULL;
176
  int     i, port, is_https;
177
178
  if (!(aia = d2i_AUTHORITY_INFO_ACCESS(NULL, &data, fr_tls_asn1_string_length(s)))) return false;
179
180
  for (i = 0; i < sk_ACCESS_DESCRIPTION_num(aia); i++) {
181
    char  *uri;
182
    size_t  len;
183
184
    ad = sk_ACCESS_DESCRIPTION_value(aia, i);
185
    if (OBJ_obj2nid(ad->method) != NID_ad_OCSP) continue;
186
    if (ad->location->type != GEN_URI) continue;
187
188
    len = fr_tls_asn1_string_length(ad->location->d.uniformResourceIdentifier);
189
    MEM(uri = talloc_bstrndup(NULL,
190
            (char const *)ASN1_STRING_get0_data(ad->location->d.uniformResourceIdentifier),
191
            len));
192
193
    /*
194
     *  Use OpenSSL URL parsing to check that the URL is valid.  It only takes a
195
     *  C string, so it needs the terminated copy.
196
     */
197
    if (OSSL_HTTP_parse_url(uri, &is_https, NULL, &host, NULL, &port, &path, NULL, NULL)) {
198
      OPENSSL_free(host);
199
      OPENSSL_free(path);
200
      MEM(fr_pair_append_by_da(ctx, &vp, pair_list,
201
             attr_tls_certificate_ocsp_uri) == 0);
202
      MEM(fr_pair_value_bstrndup(vp, uri, len, true) == 0);
203
    }
204
    talloc_free(uri);
205
  }
206
  AUTHORITY_INFO_ACCESS_free(aia);
207
208
  return true;
209
}
210
211
/** Extract attributes from an X509 certificate
212
 *
213
 * @param[out] pair_list  to copy attributes to.
214
 * @param[in] ctx   to allocate attributes in.
215
 * @param[in] request   the current request.
216
 * @param[in] cert    to validate.
217
 * @param[in] issuer    of the certificate.
218
 * @param[in] der_decode  should the certificate be parsed with the DER decoder.
219
 * @return
220
 *  - 1 already exists.
221
 *  - 0 on success.
222
 *  - < 0 on failure.
223
 */
224
int fr_tls_session_pairs_from_x509_cert(fr_pair_list_t *pair_list, TALLOC_CTX *ctx, request_t *request, X509 *cert,
225
          X509 *issuer,
226
#if OPENSSL_VERSION_NUMBER >= 0x30400000L
227
          bool der_decode
228
#else
229
          UNUSED bool der_decode
230
#endif
231
        )
232
0
{
233
0
  int   loc;
234
0
  char    buff[1024];
235
236
0
  ASN1_TIME const *asn_time;
237
0
  time_t    time;
238
239
0
  STACK_OF(X509_EXTENSION) const *ext_list = NULL;
240
241
0
  fr_pair_t *vp = NULL;
242
0
  bool    san_found = false, crl_found = false, aia_found = false;
243
244
  /*
245
   *  We require OpenSSL >= 3.4 to call the DER decoder due to the stack size
246
   *  needed to handle the recursive calls involved in certificate decoding.
247
   */
248
#if OPENSSL_VERSION_NUMBER >= 0x30400000L
249
  if (der_decode) {
250
    uint8_t     *cert_der;
251
    uint8_t     *cd;
252
    int     der_len;
253
    fr_der_decode_ctx_t der_ctx;
254
    fr_pair_list_t    tmp_list;
255
    ssize_t     slen;
256
257
    der_len = i2d_X509(cert, NULL);
258
    if (der_len < 0) {
259
      fr_tls_log_perror(request, "Failed retrieving certificate");
260
      return -1;
261
    }
262
    der_ctx = (fr_der_decode_ctx_t) {
263
      .tmp_ctx = talloc_new(ctx),
264
      .root = attr_der_certificate,
265
    };
266
    cert_der = cd = talloc_array(der_ctx.tmp_ctx, uint8_t, der_len);
267
    i2d_X509(cert, &cd);
268
    fr_pair_list_init(&tmp_list);
269
    slen = fr_der_decode_pair_dbuff(request->session_state_ctx, &tmp_list,
270
            attr_der_certificate, &FR_DBUFF_TMP(cert_der, (size_t)der_len), &der_ctx);
271
    talloc_free(der_ctx.tmp_ctx);
272
    if (slen < 0) {
273
      fr_tls_log_perror(request, "Failed decoding certificate");
274
      fr_pair_list_free(&tmp_list);
275
      return -1;
276
    }
277
    /*
278
     *  Certificates are decoded in CA .. intermediate .. client sequence
279
     *  so, prepend each decoded one to get the client cert first.
280
     */
281
    fr_pair_list_prepend(&request->session_state_pairs, &tmp_list);
282
  }
283
#endif
284
285
  /*
286
   *  Subject
287
   */
288
0
  MEM(fr_pair_append_by_da(ctx, &vp, pair_list, attr_tls_certificate_subject) == 0);
289
0
  if (unlikely(X509_NAME_print_ex(fr_tls_bio_dbuff_thread_local(vp, 256, 0),
290
0
          X509_get_subject_name(cert), 0, XN_FLAG_ONELINE) < 0)) {
291
0
    fr_tls_bio_dbuff_thread_local_clear();
292
0
    fr_tls_log_perror(request, "Failed retrieving certificate subject");
293
0
  error:
294
0
    fr_pair_list_free(pair_list);
295
0
    return -1;
296
0
  }
297
0
  fr_pair_value_bstrdup_buffer_shallow(vp, fr_tls_bio_dbuff_thread_local_finalise_bstr(), true);
298
299
0
  RDEBUG3("(TLS) Creating attributes for \"%pV\":", fr_box_strvalue_buffer(vp->vp_strvalue));
300
301
  /*
302
   *  Common name
303
   */
304
0
  loc = X509_NAME_get_index_by_NID(X509_get_subject_name(cert), NID_commonName, -1);
305
0
  if (loc >= 0) {
306
0
    ASN1_STRING const *cn;
307
308
0
    cn = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(X509_get_subject_name(cert), loc));
309
0
    if (cn && (fr_tls_asn1_string_length(cn) > 0)) {
310
0
      MEM(fr_pair_append_by_da(ctx, &vp, pair_list, attr_tls_certificate_common_name) == 0);
311
0
      MEM(fr_pair_value_bstrndup(vp, (char const *)ASN1_STRING_get0_data(cn),
312
0
               fr_tls_asn1_string_length(cn), true) == 0);
313
0
    }
314
0
  }
315
316
  /*
317
   *  Signature
318
   */
319
0
  {
320
0
    ASN1_BIT_STRING const *sig;
321
0
    X509_ALGOR const *alg;
322
323
0
    X509_get0_signature(&sig, &alg, cert);
324
325
0
    MEM(fr_pair_append_by_da(ctx, &vp, pair_list, attr_tls_certificate_signature) == 0);
326
0
    MEM(fr_pair_value_memdup(vp,
327
0
           (uint8_t const *)ASN1_STRING_get0_data(sig),
328
0
           fr_tls_asn1_string_length(sig), true) == 0);
329
330
0
    OBJ_obj2txt(buff, sizeof(buff), alg->algorithm, 0);
331
0
    MEM(fr_pair_append_by_da(ctx, &vp, pair_list, attr_tls_certificate_signature_algorithm) == 0);
332
0
    fr_pair_value_strdup(vp, buff, false);
333
0
  }
334
335
  /*
336
   *  Issuer
337
   */
338
0
  MEM(fr_pair_append_by_da(ctx, &vp, pair_list, attr_tls_certificate_issuer) == 0);
339
0
  if (unlikely(X509_NAME_print_ex(fr_tls_bio_dbuff_thread_local(vp, 256, 0),
340
0
          X509_get_issuer_name(cert), 0, XN_FLAG_ONELINE) < 0)) {
341
0
    fr_tls_bio_dbuff_thread_local_clear();
342
0
    fr_tls_log_perror(request, "Failed retrieving certificate issuer");
343
0
    goto error;
344
0
  }
345
0
  fr_pair_value_bstrdup_buffer_shallow(vp, fr_tls_bio_dbuff_thread_local_finalise_bstr(), true);
346
347
  /*
348
   *  Serial number
349
   */
350
0
  {
351
0
    ASN1_INTEGER const *serial = NULL;
352
0
    unsigned char *der;
353
0
    int len;
354
355
0
    serial = X509_get0_serialNumber(cert);
356
0
    if (!serial) {
357
0
      fr_tls_log_perror(request, "Failed retrieving certificate serial");
358
0
      goto error;
359
0
    }
360
361
0
    len = i2d_ASN1_INTEGER(serial, NULL);  /* get length */
362
0
    MEM(fr_pair_append_by_da(ctx, &vp, pair_list, attr_tls_certificate_serial) == 0);
363
0
    MEM(fr_pair_value_mem_alloc(vp, &der, len, false) == 0);
364
0
    i2d_ASN1_INTEGER(serial, &der);
365
0
  }
366
367
  /*
368
   *  Not valid before
369
   */
370
0
  asn_time = X509_get0_notBefore(cert);
371
372
0
  if (fr_tls_utils_asn1time_to_epoch(&time, asn_time) < 0) {
373
0
    RPWDEBUG("(TLS) Failed parsing certificate not-before");
374
0
    goto error;
375
0
  }
376
377
0
  MEM(fr_pair_append_by_da(ctx, &vp, pair_list, attr_tls_certificate_not_before) == 0);
378
0
  vp->vp_date = fr_unix_time_from_time(time);
379
380
  /*
381
   *  Not valid after
382
   */
383
0
  asn_time = X509_get0_notAfter(cert);
384
385
0
  if (fr_tls_utils_asn1time_to_epoch(&time, asn_time) < 0) {
386
0
    RPWDEBUG("(TLS) Failed parsing certificate not-after");
387
0
    goto error;
388
0
  }
389
390
0
  MEM(fr_pair_append_by_da(ctx, &vp, pair_list, attr_tls_certificate_not_after) == 0);
391
0
  vp->vp_date = fr_unix_time_from_time(time);
392
393
  /*
394
   *  Get the RFC822 Subject Alternative Name
395
   */
396
0
  loc = X509_get_ext_by_NID(cert, NID_subject_alt_name, 0);
397
0
  if (loc >= 0) {
398
0
    FR_OPENSSL_X509_CONST X509_EXTENSION *ext = X509_get_ext(cert, loc);
399
0
    if (ext) san_found = tls_session_pairs_from_san(pair_list, ctx, request, ext);
400
0
  }
401
402
0
  loc = X509_get_ext_by_NID(cert, NID_crl_distribution_points, 0);
403
0
  if (loc >= 0) {
404
0
    FR_OPENSSL_X509_CONST X509_EXTENSION *ext = X509_get_ext(cert, loc);
405
0
    if (ext) crl_found = tls_session_pairs_from_crl(pair_list, ctx, request, ext);
406
0
  }
407
408
0
  if (issuer) {
409
0
    OCSP_CERTID *certid;
410
0
    int   len;
411
0
    uint8_t   *ocsp_certid = NULL;
412
413
0
    certid = OCSP_cert_to_id(NULL, cert, issuer);
414
0
    if (unlikely(!certid)) goto error;
415
0
    len = i2d_OCSP_CERTID(certid, &ocsp_certid);
416
0
    if (len < 0) goto error;
417
0
    if (ocsp_certid != NULL) {
418
0
      MEM(fr_pair_append_by_da(ctx, &vp, pair_list, attr_tls_certificate_ocsp_cert_id) == 0);
419
0
      fr_value_box_memdup(vp, &vp->data, NULL, ocsp_certid, len, false);
420
0
      OPENSSL_free(ocsp_certid);
421
0
    }
422
0
    OCSP_CERTID_free(certid);
423
0
  }
424
425
  /*
426
   *  Only add extensions for the actual client certificate
427
   */
428
0
  ext_list = X509_get0_extensions(cert);
429
0
  if (unlikely(!ext_list)) {
430
0
    RWDEBUG("(TLS) Failed retrieving extensions");
431
0
    goto done;
432
0
  }
433
434
  /*
435
   *  Grab the X509 extensions, and create attributes out of them.
436
   *  For laziness, we reuse the OpenSSL names
437
   */
438
0
  if (sk_X509_EXTENSION_num(ext_list) > 0) {
439
0
    int     i;
440
0
    BIO     *bio;
441
0
    fr_tls_bio_dbuff_t  *bd;
442
0
    fr_dbuff_t    *in, *out;
443
444
0
    bio = fr_tls_bio_dbuff_alloc(&bd, NULL, NULL, 257, 4097, true);
445
0
    in = fr_tls_bio_dbuff_in(bd);
446
0
    out = fr_tls_bio_dbuff_out(bd);
447
448
0
    for (i = 0; i < sk_X509_EXTENSION_num(ext_list); i++) {
449
0
      ASN1_OBJECT const *obj;
450
0
      X509_EXTENSION    *ext;
451
0
      fr_dict_attr_t const  *da;
452
0
      char      *p;
453
454
0
      ext = sk_X509_EXTENSION_value(ext_list, i);
455
456
0
      obj = X509_EXTENSION_get_object(ext);
457
458
      /*
459
       *  If this is extension is Subject Alternate Name have we already
460
       *  handled it?  If not, do that now.
461
       *
462
       *  Some certificate encodings have been observed where the SAN extension
463
       *  was not found by X509_get_ext_by_NID() but then seen when the list
464
       *  of extensions is handled here.
465
       */
466
0
      if (OBJ_obj2nid(obj) == NID_subject_alt_name) {
467
0
        if (!san_found) san_found = tls_session_pairs_from_san(pair_list, ctx, request, ext);
468
0
        goto again;
469
0
      }
470
471
0
      if (OBJ_obj2nid(obj) == NID_crl_distribution_points) {
472
0
        if (!crl_found) crl_found = tls_session_pairs_from_crl(pair_list, ctx, request, ext);
473
0
        goto again;
474
0
      }
475
476
0
      if (OBJ_obj2nid(obj) == NID_info_access) {
477
0
        if (!aia_found) aia_found = tls_session_pairs_from_aia(pair_list, ctx, request, ext);
478
0
        goto again;
479
0
      }
480
481
0
      if (i2a_ASN1_OBJECT(bio, obj) <= 0) {
482
0
        RPWDEBUG("(TLS) Skipping X509 Extension (%i) conversion to attribute. "
483
0
           "Conversion from ASN1 failed...", i);
484
0
      again:
485
0
        fr_tls_bio_dbuff_reset(bd);
486
0
        continue;
487
0
      }
488
489
0
      if (fr_dbuff_remaining(out) == 0) goto again; /* Nothing written ? */
490
491
      /*
492
       *  All disallowed chars get mashed to '-'
493
       */
494
0
      for (p = (char *)fr_dbuff_current(out);
495
0
           p < (char *)fr_dbuff_end(out);
496
0
           p++) if (!fr_dict_attr_allowed_chars[(uint8_t)*p]) *p = '-';
497
498
      /*
499
       *  Terminate the buffer (after char replacement,
500
       *  so we do don't replace the \0)
501
       */
502
0
      if (unlikely(fr_dbuff_in_bytes(in, (uint8_t)'\0') <= 0)) {
503
0
        RWDEBUG("(TLS) Attribute name too long");
504
0
        goto again;
505
0
      }
506
507
0
      da = fr_dict_attr_by_name(NULL, attr_tls_certificate, (char *)fr_dbuff_current(out));
508
509
0
      fr_dbuff_set(in, fr_dbuff_current(in) - 1); /* Ensure the \0 isn't counted in remaining */
510
511
0
      if (!da) {
512
0
        RWDEBUG3("(TLS) Skipping attribute \"%pV\": "
513
0
           "Add a dictionary definition if you want to access it",
514
0
           fr_box_strvalue_len((char *)fr_dbuff_current(out),
515
0
                     fr_dbuff_remaining(out)));
516
0
        fr_strerror_clear();  /* Don't leave spurious errors from failed resolution */
517
0
        goto again;
518
0
      }
519
520
0
      fr_tls_bio_dbuff_reset(bd); /* 'free' any data used */
521
522
0
      if (X509V3_EXT_print(bio, ext, X509V3_EXT_PARSE_UNKNOWN, 0) != 1) {
523
0
        REDEBUG("(TLS) Failed extracting data for \"%s\"", da->name);
524
0
        goto again;
525
0
      }
526
527
0
      MEM(vp = fr_pair_afrom_da(ctx, da));
528
0
      if (fr_pair_value_from_str(vp, (char *)fr_dbuff_current(out), fr_dbuff_remaining(out),
529
0
               NULL, true) < 0) {
530
0
        RPWDEBUG3("Skipping: %s += \"%pV\"",
531
0
            da->name, fr_box_strvalue_len((char *)fr_dbuff_current(out),
532
0
                    fr_dbuff_remaining(out)));
533
0
        talloc_free(vp);
534
0
        goto again;
535
0
      }
536
0
      fr_tls_bio_dbuff_reset(bd); /* 'free' any data used */
537
538
0
      fr_pair_append(pair_list, vp);
539
0
    }
540
0
    talloc_free(bd);
541
0
  }
542
543
0
done:
544
0
  return 0;
545
0
}
546
547
/** Extract values from ClientHello extensions into pairs
548
 *
549
 * @param request being processed.
550
 * @param container List to put pairs in.
551
 * @param extension data to parse.
552
 * @param hlen    Length of "length" header in extension.
553
 * @param dlen    Length of each data entry in extension.
554
 * @param total_len of extension.
555
 * @param da    Attribute to create using extension values.
556
 * @return
557
 *   - 0 on success
558
 *   - -1 on failure
559
 */
560
static int fr_tls_extension_decode(request_t *request, fr_pair_t *container, uint8_t const *extension,
561
           size_t hlen, size_t dlen, size_t total_len, fr_dict_attr_t const *da)
562
0
{
563
0
  size_t i,extension_len;
564
0
  uint8_t const *data;
565
566
0
  fr_assert((hlen == 1) || (hlen == 2));
567
0
  fr_assert((dlen == 1) || (dlen == 2));
568
569
0
  if (total_len < hlen) {
570
0
    REDEBUG("(TLS) Missing length in %s extension", da->name);
571
0
    return -1;
572
0
  }
573
574
0
  if (hlen == 1) {
575
0
    extension_len = extension[0];
576
0
  } else {
577
0
    extension_len = (extension[0] << 8) + extension[1];
578
0
  }
579
0
  fr_assert(da->type == ((dlen == 1) ? FR_TYPE_UINT8 : FR_TYPE_UINT16));
580
581
0
  if (extension_len + hlen > total_len) {
582
0
    REDEBUG("(TLS) Invalid length %zu in %s extension", extension_len, da->name);
583
0
    return -1;
584
0
  }
585
586
0
  data = extension + hlen;
587
588
0
  for (i = 0; i < extension_len; i += dlen) {
589
0
    fr_pair_t *vp;
590
591
0
    MEM(fr_pair_append_by_da(container, &vp, &container->vp_group, da) >= 0);
592
593
0
    switch (dlen) {
594
0
    case 1:
595
0
      vp->vp_uint8= data[0];
596
0
      break;
597
598
0
    case 2:
599
0
      vp->vp_uint16 = (data[0] << 8) + data[1];
600
0
      break;
601
0
    }
602
603
0
    data += dlen;
604
0
  }
605
606
0
  return 0;
607
0
}
608
609
/** Callback to extract pairs from a Client Hello
610
 *
611
 */
612
int fr_tls_session_client_hello_pairs(SSL *ssl, request_t *request)
613
0
{
614
0
  request_t   *parent = request->parent;
615
0
  uint8_t const   *ciphers, *extension;
616
0
  int     i, *extensions = NULL;
617
0
  size_t      data_size, j;
618
0
  STACK_OF(SSL_CIPHER)  *sk;
619
0
  SSL_CIPHER const  *cipher;
620
0
  STACK_OF(SSL_CIPHER)  *scsvs;
621
0
  fr_pair_t   *container, *vp;
622
623
0
  fr_assert(parent);
624
625
0
  container = fr_pair_find_by_da(&parent->session_state_pairs, NULL, attr_tls_client_hello);
626
0
  if (container) return SSL_CLIENT_HELLO_SUCCESS;
627
628
0
  MEM(container = fr_pair_afrom_da(parent->session_state_ctx, attr_tls_client_hello));
629
630
0
  data_size = SSL_client_hello_get0_ciphers(ssl, &ciphers);
631
0
  if (SSL_bytes_to_cipher_list(ssl, ciphers, data_size,
632
#if OPENSSL_VERSION_NUMBER >= 0x40000000L
633
             0, /* OpenSSL 4.0 removed SSLv2 ClientHello support */
634
#else
635
0
             SSL_client_hello_isv2(ssl),
636
0
#endif
637
0
             &sk, &scsvs) == 0) {
638
0
    RPEDEBUG("(TLS) Failed to decode cipher list");
639
0
  fail:
640
0
    if (extensions) OPENSSL_free(extensions);
641
0
    talloc_free(container);
642
0
    return SSL_CLIENT_HELLO_ERROR;
643
0
  }
644
645
0
  for (i = 0; i < sk_SSL_CIPHER_num(sk); i++) {
646
0
    fr_pair_append_by_da(container, &vp, &container->vp_group, attr_tls_client_hello_cipher);
647
0
    cipher = sk_SSL_CIPHER_value(sk, i);
648
0
    fr_value_box_strdup(vp, &vp->data, NULL, SSL_CIPHER_get_name(cipher), false);
649
0
  }
650
651
0
  sk_SSL_CIPHER_free(sk);
652
0
  sk_SSL_CIPHER_free(scsvs);
653
654
  /*
655
   *  Fetch the extensions and decode the ones we know about
656
   *  These are the ones which are returned as simple lists
657
   *  of values of known length which map to enum attributes.
658
   */
659
0
  if (SSL_client_hello_get1_extensions_present(ssl, &extensions, &data_size) == 0) {
660
0
    RPEDEBUG("(TLS) Failed to fetch client hello extensions");
661
0
    goto fail;
662
0
  }
663
664
0
  for (j = 0; j < data_size; j++) {
665
0
    size_t total_len;
666
667
0
    if (SSL_client_hello_get0_ext(ssl, extensions[j], &extension, &total_len) == 0) {
668
0
      RPDEBUG("Failed getting client hello extension %d", extensions[j]);
669
0
      goto fail;
670
0
    }
671
672
0
    switch (extensions[j]) {
673
0
    case TLSEXT_TYPE_supported_groups: /* length[2] + 2*data */
674
0
      if (fr_tls_extension_decode(request, container, extension, 2, 2, total_len,
675
0
                attr_tls_client_hello_supported_group) < 0) goto fail;
676
0
      break;
677
678
0
    case TLSEXT_TYPE_ec_point_formats: /* length[1] + data */
679
0
      if (fr_tls_extension_decode(request, container, extension, 1, 1, total_len,
680
0
                attr_tls_client_hello_ec_point_format) < 0) goto fail;
681
0
      break;
682
683
0
    case TLSEXT_TYPE_signature_algorithms: /* length[2] + 2*data */
684
0
      if (fr_tls_extension_decode(request, container, extension, 2, 2, total_len,
685
0
                attr_tls_client_hello_sig_algo) < 0) goto fail;
686
0
      break;
687
688
0
    case TLSEXT_TYPE_supported_versions: /* length[1] + 2*data */
689
0
      if (fr_tls_extension_decode(request, container, extension, 1, 2, total_len,
690
0
                attr_tls_client_hello_tls_version) < 0) goto fail;
691
0
      break;
692
693
0
    case TLSEXT_TYPE_psk_kex_modes: /* length[1] + data */
694
0
      if (fr_tls_extension_decode(request, container, extension, 1, 1, total_len,
695
0
                attr_tls_client_hello_psk_key_mode) < 0) goto fail;
696
0
      break;
697
0
    }
698
0
  }
699
700
0
  if (extensions) OPENSSL_free(extensions);
701
702
  /*
703
   *  This is the version being negotiated by the client, but tops at 1.2.
704
   *  After that the supported_versions extension is where the real value is.
705
   */
706
0
  fr_pair_append_by_da(container, &vp, &container->vp_group, attr_tls_client_hello_tls_version);
707
0
  vp->vp_uint16 = SSL_client_hello_get0_legacy_version(ssl);
708
709
0
  fr_pair_append(&parent->session_state_pairs, container);
710
711
  return SSL_CLIENT_HELLO_SUCCESS;
712
0
}
713
DIAG_ON_OPTIONAL(used-but-marked-unused)
714
#endif