Coverage Report

Created: 2026-06-30 07:16

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/lib/tls/verify.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: f028b9c59d8bff5438bc19d23ad3d4067952a811 $
19
 *
20
 * @file tls/verify.c
21
 * @brief Expose certificate OIDs as attributes, and call validation virtual
22
 *  server to check cert is valid.
23
 *
24
 * @copyright 2001 hereUare Communications, Inc. (raghud@hereuare.com)
25
 * @copyright 2003 Alan DeKok (aland@freeradius.org)
26
 * @copyright 2006-2016 The FreeRADIUS server project
27
 */
28
#ifdef WITH_TLS
29
0
#define LOG_PREFIX "tls"
30
31
#include <freeradius-devel/server/exec.h>
32
#include <freeradius-devel/server/pair.h>
33
#include <freeradius-devel/tls/log.h>
34
#include <freeradius-devel/unlang/function.h>
35
#include <freeradius-devel/unlang/subrequest.h>
36
#include <freeradius-devel/util/debug.h>
37
#include <freeradius-devel/util/strerror.h>
38
#include <freeradius-devel/util/syserror.h>
39
40
#include "attrs.h"
41
#include "base.h"
42
43
/** Check to see if a verification operation should apply to a certificate
44
 *
45
 * @param[in] depth starting at 0.
46
 *      Certificate 0 is the leaf cert (i.e. the client or server cert);
47
 * @param[in] untrusted The number of untrusted certificates.
48
 * @param[in] mode  to check
49
 * @return
50
 *  - true if a given validation check should apply.
51
 **     - false if a validation check should not apply.
52
 */
53
static inline CC_HINT(always_inline)
54
bool verify_applies(fr_tls_verify_mode_t mode, int depth, int untrusted)
55
0
{
56
0
  if (mode == FR_TLS_VERIFY_MODE_ALL) return true;
57
0
  if (mode == FR_TLS_VERIFY_MODE_DISABLED) return false;
58
59
0
  if ((mode & FR_TLS_VERIFY_MODE_LEAF) && (depth == 0)) return true;
60
0
  if ((mode & FR_TLS_VERIFY_MODE_ISSUER) && (depth == 1)) return true;
61
0
  if ((mode & FR_TLS_VERIFY_MODE_UNTRUSTED) && (depth < untrusted)) return true;
62
63
0
  return false;
64
0
}
65
66
DIAG_OFF(DIAG_UNKNOWN_PRAGMAS)
67
DIAG_OFF(used-but-marked-unused)  /* fix spurious warnings for sk macros */
68
69
/** Print verbose humanly readable messages about why certificate validation failed
70
 *
71
 */
72
static void tls_verify_error_detail(request_t *request, SSL_CTX *ctx, int err)
73
0
{
74
0
  X509_STORE  *store = SSL_CTX_get_ex_data(ctx, FR_TLS_EX_CTX_INDEX_VERIFY_STORE);
75
76
0
  switch (err) {
77
  /*
78
   *  We linked the provided cert to at least one
79
   *  other in a chain, but the chain doesn't terminate
80
   *  in a root CA.
81
   */
82
0
  case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
83
0
    FALL_THROUGH;
84
85
  /*
86
   *  We failed to link the provided cert to any
87
   *  other local certificates in the chain.
88
   */
89
0
  case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
90
0
    RDEBUG2("Static certificates in verification store are");
91
0
    if (RDEBUG_ENABLED2) {
92
0
      RINDENT();
93
0
      fr_tls_x509_objects_log(request, L_DBG, X509_STORE_get0_objects(store));
94
0
      REXDENT();
95
0
    }
96
0
    break;
97
98
0
  default:
99
0
    break;
100
0
  }
101
0
}
102
103
/** Validates a certificate using custom logic
104
 *
105
 * Before trusting a certificate, we make sure that the certificate is
106
 * 'valid'. There are several checks we perform to verify its validity.
107
 *
108
 *   1. Verify the certificate's signature, and verifying that the certificate has
109
 *      been issued by a trusted Certificate Authority (this is done for us by OpenSSL).
110
 *
111
 *   2. Verify that the certificate is valid for the present date (i.e. it is being
112
 *      presented within its validity dates).
113
 *
114
 *   3. Verify that the certificate has not been revoked by its issuing Certificate
115
 *      Authority, by checking with respect to a Certificate Revocation List (CRL).
116
 *
117
 * @note This callback will be called multiple times based on the depth of the root
118
 *  certificate chain.
119
 *
120
 * @note As a byproduct of validation, various OIDs will be extracted from the
121
 *  certificates, and inserted into the session-state list as fr_pair_t.
122
 *
123
 * @param ok    preverify ok.  1 if true, 0 if false.
124
 * @param x509_ctx  containing certs to verify.
125
 * @return
126
 *  - 0 if not valid.
127
 *  - 1 if valid.
128
 */
129
int fr_tls_verify_cert_cb(int ok, X509_STORE_CTX *x509_ctx)
130
0
{
131
0
  X509      *cert;
132
133
0
  SSL_CTX     *ssl_ctx;
134
0
  SSL     *ssl;
135
0
  fr_tls_session_t  *tls_session;
136
0
  int     err, depth;
137
0
  fr_tls_conf_t   *conf;
138
0
  int     my_ok = ok;
139
0
  int     untrusted;
140
141
0
  request_t   *request;
142
0
  fr_pair_t   *container = NULL;
143
0
  fr_pair_t   *depth_pair;
144
145
0
  cert = X509_STORE_CTX_get_current_cert(x509_ctx);
146
0
  err = X509_STORE_CTX_get_error(x509_ctx);
147
0
  depth = X509_STORE_CTX_get_error_depth(x509_ctx);
148
0
  untrusted = X509_STORE_CTX_get_num_untrusted(x509_ctx);
149
150
  /*
151
   *  Retrieve the pointer to the SSL of the connection currently treated
152
   *  and the application specific data stored into the SSL object.
153
   */
154
0
  ssl = X509_STORE_CTX_get_ex_data(x509_ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
155
0
  ssl_ctx = SSL_get_SSL_CTX(ssl);
156
0
  conf = fr_tls_session_conf(ssl);
157
0
  tls_session = talloc_get_type_abort(SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_TLS_SESSION), fr_tls_session_t);
158
0
  request = fr_tls_session_request(tls_session->ssl);
159
160
  /*
161
   *  If this error appears it suggests
162
   *  that OpenSSL is trying to perform post-handshake
163
   *  certificate validation which we don't support.
164
   */
165
0
  if (!tls_session->can_pause) {
166
0
    fr_assert_msg("Unexpected call to %s. "
167
0
            "tls_session_async_handshake_cont must be in call stack", __FUNCTION__);
168
0
    return 0;
169
0
  }
170
171
  /*
172
   *  Bail out as quickly as possible, producing
173
   *  as few errors as possible.
174
   */
175
0
  if (unlang_request_is_cancelled(request)) {
176
0
    X509_STORE_CTX_set_error(x509_ctx, 0);
177
0
    return 1;
178
0
  }
179
180
  /*
181
   *  Find or add the chain depth attribute and record the greatest depth we see + 1,
182
   *  as depth is zero based.
183
   */
184
0
  if (unlikely(fr_pair_find_or_append_by_da(request->session_state_ctx, &depth_pair, &request->session_state_pairs,
185
0
           attr_tls_certificate_chain_depth) < 0)) {
186
0
    RERROR("Failed to add certificate chain depth pair");
187
0
    return 0;
188
0
  }
189
0
  if (depth_pair->vp_uint32 < ((uint32_t)depth) + 1) depth_pair->vp_uint32 = (uint32_t)depth + 1;
190
191
0
  if (RDEBUG_ENABLED3) {
192
0
    char    subject[2048];
193
0
    STACK_OF(X509)  *our_chain;
194
0
    int   i;
195
196
0
    our_chain = X509_STORE_CTX_get0_chain(x509_ctx);
197
0
    RDEBUG3("Certificate chain - %i cert(s) untrusted", untrusted);
198
0
    for (i = sk_X509_num(our_chain); i > 0 ; i--) {
199
0
      X509 *this_cert = sk_X509_value(our_chain, i - 1);
200
201
0
      X509_NAME_oneline(X509_get_subject_name(this_cert), subject, sizeof(subject));
202
0
      subject[sizeof(subject) - 1] = '\0';
203
204
0
      RDEBUG3("%s [%i] %s", this_cert == cert ? ">" : " ", i - 1, subject);
205
0
    }
206
0
  }
207
208
  /*
209
   *  See if the user has disabled verification for
210
   *      this certificate.  If they have, force verification
211
   *  to succeed.
212
   */
213
0
  if (!my_ok) {
214
0
    char const *p = X509_verify_cert_error_string(err);
215
0
    if (!verify_applies(conf->verify.mode, depth, untrusted)) {
216
0
      RDEBUG2("Ignoring verification error - %s (%i)", p, err);
217
0
      tls_verify_error_detail(request, ssl_ctx, err);
218
219
0
      my_ok = 1;
220
0
      X509_STORE_CTX_set_error(x509_ctx, 0);
221
0
    } else {
222
0
      RERROR("Verification error - %s (%i)", p, err);
223
0
      tls_verify_error_detail(request, ssl_ctx, err);
224
0
      goto done;
225
0
    }
226
0
  }
227
228
0
  if (verify_applies(conf->verify.attribute_mode, depth, untrusted) &&
229
0
      (!(container = fr_pair_find_by_da_idx(&request->session_state_pairs, attr_tls_certificate, depth)) ||
230
0
       fr_pair_list_empty(&container->vp_group))) {
231
0
        if (!container) {
232
0
              unsigned int i;
233
234
      /*
235
       *  Build a stack of container attributes.
236
       *
237
       *  OpenSSL passes us the deepest certificate
238
       *      first, so we need to build out sufficient
239
       *      TLS-Certificate container TLVs so the TLS-Certificate
240
       *  indexes match the attribute depth.
241
       */
242
0
      for (i = fr_pair_count_by_da(&request->session_state_pairs, attr_tls_certificate);
243
0
           i <= (unsigned int)depth;
244
0
           i++) {
245
0
        MEM(container = fr_pair_afrom_da(request->session_state_ctx, attr_tls_certificate));
246
0
        fr_pair_append(&request->session_state_pairs, container);
247
0
      }
248
0
        }
249
250
#ifdef STATIC_ANALYZER
251
    /*
252
     *  Container can never be NULL, because if container
253
     *  was previously NULL, i will be <= depth.
254
     */
255
    if (!fr_cond_assert(container)) {
256
      my_ok = 0;
257
      goto done;
258
    }
259
#endif
260
    /*
261
     *  If we fail to populate the cert attributes,
262
     *  trash all instances in the session-state list
263
     *  and cause validation to fail.
264
     */
265
0
    if (fr_tls_session_pairs_from_x509_cert(&container->vp_group, container,
266
0
              request, cert, conf->verify.der_decode) < 0) {
267
0
      fr_pair_delete_by_da(&request->session_state_pairs, attr_tls_certificate);
268
0
      if (conf->verify.der_decode) {
269
0
        fr_pair_delete_by_da(&request->session_state_pairs, attr_der_certificate);
270
0
      }
271
0
      my_ok = 0;
272
0
      goto done;
273
0
    }
274
275
0
    log_request_pair(L_DBG_LVL_2, request, NULL, container, "session-state.");
276
0
  }
277
0
done:
278
  /*
279
   *  If verification hasn't already failed
280
   *  and we're meant to verify this cert
281
   *  then call the virtual server.
282
   *
283
   *  We only call the virtual server for
284
   *      the certificate at depth 0 as all
285
   *      other certificate attributes should
286
   *  have been added by this point.
287
   */
288
0
  if (my_ok && (depth == 0)) {
289
0
    if (conf->verify_certificate && tls_session->verify_client_cert) {
290
0
      RDEBUG2("Requesting certificate validation");
291
292
      /*
293
       *  This sets the validation state of the tls_session
294
       *  so that when we call ASYNC_pause_job(), and execution
295
       *  jumps back to tls_session_async_handshake_cont
296
       *  (just under SSL_read())
297
       *  the code there knows what job it needs to push onto
298
       *  the unlang stack.
299
       */
300
0
      fr_tls_verify_cert_request(tls_session, SSL_session_reused(tls_session->ssl));
301
302
      /*
303
       *  Jumps back to SSL_read() in session.c
304
       *
305
       *  Be aware that if the request is cancelled
306
       *  whatever was meant to be done during the
307
       *  time we yielded may not have been completed.
308
       */
309
0
      ASYNC_pause_job();
310
311
      /*
312
       *  Just try and bail out as quickly as possible.
313
       */
314
0
      if (unlang_request_is_cancelled(request)) {
315
0
        X509_STORE_CTX_set_error(x509_ctx, 0);
316
0
        fr_tls_verify_cert_reset(tls_session);
317
0
        return 1;
318
0
      }
319
320
321
      /*
322
       *  If we couldn't validate the certificate
323
       *  then validation overall fails.
324
       */
325
0
      if (!fr_tls_verify_cert_result(tls_session)) {
326
0
        REDEBUG("Certificate validation failed");
327
0
        my_ok = 0;
328
0
        X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_APPLICATION_VERIFICATION);
329
0
      }
330
0
    }
331
0
  }
332
333
0
  tls_session->client_cert_ok = (my_ok > 0);
334
0
  RDEBUG2("[verify] = %s", my_ok ? "ok" : "invalid");
335
336
0
  return my_ok;
337
0
}
338
DIAG_ON(used-but-marked-unused)
339
DIAG_ON(DIAG_UNKNOWN_PRAGMAS)
340
341
/** Revalidates the client's certificate chain
342
 *
343
 * Wraps the fr_tls_verify_cert_cb callback, allowing us to use the same
344
 * validation logic whenever we need to.
345
 *
346
 * @note Only use so far is forcing the chain to be re-validated on session
347
 *  resumption.
348
 *
349
 * @return
350
 *  - 1 if the chain could be validated.
351
 *  - 0 if the chain failed validation.
352
 */
353
int fr_tls_verify_cert_chain(request_t *request, SSL *ssl)
354
0
{
355
0
  int   err;
356
0
  int   verify;
357
0
  int   ret = 1;
358
359
0
  SSL_CTX   *ssl_ctx;
360
0
  STACK_OF(X509)  *chain;
361
0
  X509    *cert;
362
0
  X509_STORE  *store;
363
0
  X509_STORE_CTX  *store_ctx;
364
365
  /*
366
   *  If there's no client certificate, we just return OK.
367
   */
368
0
  cert = SSL_get0_peer_certificate(ssl);      /* Does not increase ref count */
369
0
  if (!cert) return 1;
370
371
0
  ssl_ctx = SSL_get_SSL_CTX(ssl);
372
0
  store_ctx = X509_STORE_CTX_new();
373
0
  chain = SSL_get_peer_cert_chain(ssl);     /* Does not increase ref count */
374
0
  store = SSL_CTX_get_ex_data(ssl_ctx, FR_TLS_EX_CTX_INDEX_VERIFY_STORE);  /* Gets the verification store */
375
376
  /*
377
   *  This sets up a store_ctx for doing peer certificate verification.
378
   *
379
   *  store_ctx - Is the ctx to initialise
380
   *  store   - Is an X509_STORE of implicitly
381
   *        trusted certificates.  Here we're using
382
   *        the verify store that was created when we
383
   *        allocated the SSL_CTX.
384
   *  cert    - Is the certificate to validate.
385
   *  chain   - Is any other certificates the peer provided
386
   *        us in order to build a chain from a trusted
387
   *            root or intermediary to its leaf (cert).
388
   *
389
   *  Note: SSL_CTX_get_cert_store() returns the ctx->cert_store, which
390
   *      is not the same as the verification cert store.
391
   */
392
0
  X509_STORE_CTX_init(store_ctx, store, cert, chain);
393
0
  X509_STORE_CTX_set_ex_data(store_ctx, SSL_get_ex_data_X509_STORE_CTX_idx(), ssl);
394
0
  X509_STORE_CTX_set_verify_cb(store_ctx, fr_tls_verify_cert_cb);
395
396
0
  verify = X509_verify_cert(store_ctx);
397
0
  if (verify != 1) {
398
0
    err = X509_STORE_CTX_get_error(store_ctx);
399
400
0
    if (err != X509_V_OK) {
401
0
      REDEBUG("Failed re-validating resumed session: %s", X509_verify_cert_error_string(err));
402
0
      ret = 0;
403
0
    }
404
0
  }
405
406
0
  X509_STORE_CTX_free(store_ctx);
407
408
0
  return ret;
409
0
}
410
411
/** Process the result of `verify certificate { ... }`
412
 *
413
 */
414
static unlang_action_t tls_verify_client_cert_result(request_t *request, void *uctx)
415
0
{
416
0
  fr_tls_session_t  *tls_session = talloc_get_type_abort(uctx, fr_tls_session_t);
417
0
  fr_pair_t   *vp, *next;
418
419
0
  fr_assert(tls_session->validate.state == FR_TLS_VALIDATION_REQUESTED);
420
421
0
  vp = fr_pair_find_by_da(&request->reply_pairs, NULL, attr_tls_packet_type);
422
0
  if (!vp || (vp->vp_uint32 != enum_tls_packet_type_success->vb_uint32)) {
423
0
    REDEBUG("Failed (re-)validating certificates");
424
425
    /*
426
     *  Hoist any instances of Module-Failure-Message from the subrequest
427
     *  so they can be used for logging failures.
428
     */
429
0
    vp = fr_pair_find_by_da(&request->request_pairs, NULL, attr_module_failure_message);
430
0
    while (vp && request->parent) {
431
0
      next = fr_pair_find_by_da(&request->request_pairs, vp, attr_module_failure_message);
432
0
      fr_pair_remove(&request->request_pairs, vp);
433
0
      fr_pair_steal_append(request->parent->request_ctx, &request->parent->request_pairs, vp);
434
0
      vp = next;
435
0
    }
436
437
0
    tls_session->validate.state = FR_TLS_VALIDATION_FAILED;
438
0
    return UNLANG_ACTION_CALCULATE_RESULT;
439
0
  }
440
441
0
  tls_session->validate.state = FR_TLS_VALIDATION_SUCCESS;
442
443
0
  RDEBUG2("Certificates (re-)validated");
444
445
0
  return UNLANG_ACTION_CALCULATE_RESULT;
446
0
}
447
448
/** Push a `verify certificate { ... }` call into the current request, using a subrequest
449
 *
450
 * @param[in] request   The current request.
451
 * @param[in] tls_session The current TLS session.
452
 * @return
453
 *      - UNLANG_ACTION_CALCULATE_RESULT on noop.
454
 *  - UNLANG_ACTION_PUSHED_CHILD on success.
455
 *      - UNLANG_ACTION_FAIL on failure.
456
 */
457
static unlang_action_t tls_verify_client_cert_push(request_t *request, fr_tls_session_t *tls_session)
458
0
{
459
0
  fr_tls_conf_t   *conf = fr_tls_session_conf(tls_session->ssl);
460
0
  request_t   *child;
461
0
  fr_pair_t   *vp;
462
0
  unlang_action_t   ua;
463
464
0
  MEM(child = unlang_subrequest_alloc(request, dict_tls));
465
0
  request = child;
466
467
  /*
468
   *  Add extra pairs to the subrequest
469
   */
470
0
  fr_tls_session_extra_pairs_copy_to_child(child, tls_session);
471
472
  /*
473
   *  Setup the child request for loading
474
   *  session resumption data.
475
   */
476
0
  MEM(pair_prepend_request(&vp, attr_tls_packet_type) >= 0);
477
0
  vp->vp_uint32 = enum_tls_packet_type_verify_certificate->vb_uint32;
478
479
  /*
480
   *  Copy certificate pairs to the child session state
481
   */
482
0
  vp = NULL;
483
0
  while ((vp = fr_pair_find_by_da(&request->parent->session_state_pairs, vp, attr_tls_certificate))) {
484
0
    fr_pair_append(&request->session_state_pairs, fr_pair_copy(request->session_state_ctx, vp));
485
0
  }
486
0
  if (conf->verify.der_decode) {
487
0
    while ((vp = fr_pair_find_by_da(&request->parent->session_state_pairs, vp, attr_der_certificate))) {
488
0
      fr_pair_append(&request->session_state_pairs, fr_pair_copy(request->session_state_ctx, vp));
489
0
    }
490
0
  }
491
492
0
  MEM(pair_append_request(&vp, attr_tls_session_resumed) >= 0);
493
0
  vp->vp_bool = tls_session->validate.resumed;
494
495
  /*
496
   *  Allocate a child, and set it up to call
497
   *      the TLS virtual server.
498
   */
499
0
  ua = fr_tls_call_push(child, tls_verify_client_cert_result, conf, tls_session, false);
500
0
  if (ua < 0) {
501
0
          PERROR("Failed calling TLS virtual server");
502
0
    talloc_free(child);
503
0
    return UNLANG_ACTION_FAIL;
504
0
  }
505
506
0
  return ua;
507
0
}
508
509
/** Clear any previous validation result
510
 *
511
 * Should be called by the validation requestor to get the result and reset
512
 * the validation state.
513
 *
514
 * @return
515
 *  - true if the certificate chain was validated.
516
 *  - false if the certificate chain failed validation.
517
 */
518
bool fr_tls_verify_cert_result(fr_tls_session_t *tls_session)
519
0
{
520
0
  bool result;
521
522
0
  fr_assert(tls_session->validate.state != FR_TLS_VALIDATION_INIT);
523
524
0
  result = tls_session->validate.state == FR_TLS_VALIDATION_SUCCESS;
525
526
0
  tls_session->validate.state = FR_TLS_VALIDATION_INIT;
527
0
  tls_session->validate.resumed = false;
528
529
0
  return result;
530
0
}
531
532
/** Reset the verification state
533
 *
534
 */
535
void fr_tls_verify_cert_reset(fr_tls_session_t *tls_session)
536
0
{
537
0
  tls_session->validate.state = FR_TLS_VALIDATION_INIT;
538
0
  tls_session->validate.resumed  = false;
539
0
}
540
541
/** Setup a verification request
542
 *
543
 */
544
void fr_tls_verify_cert_request(fr_tls_session_t *tls_session, bool session_resumed)
545
0
{
546
0
  fr_assert(tls_session->validate.state == FR_TLS_VALIDATION_INIT);
547
548
0
  tls_session->validate.state = FR_TLS_VALIDATION_REQUESTED;
549
0
  tls_session->validate.resumed = session_resumed;
550
0
}
551
552
/** Push a `verify certificate { ... }` section
553
 *
554
 * @param[in] request   The current request.
555
 * @param[in] tls_session The current TLS session.
556
 * @return
557
 *  - UNLANG_ACTION_CALCULATE_RESULT  - No pending actions
558
 *  - UNLANG_ACTION_PUSHED_CHILD    - Pending operations to evaluate.
559
 */
560
unlang_action_t fr_tls_verify_cert_pending_push(request_t *request, fr_tls_session_t *tls_session)
561
0
{
562
0
  if (tls_session->validate.state == FR_TLS_VALIDATION_REQUESTED) {
563
0
    return tls_verify_client_cert_push(request, tls_session);
564
0
  }
565
566
0
  return UNLANG_ACTION_CALCULATE_RESULT;
567
0
}
568
#endif /* WITH_TLS */