The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
rlm_crl.c
Go to the documentation of this file.
1/*
2 * This program is 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 (at
5 * 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: 67210b48a732b93063254846da8ee12469b97df3 $
19 * @file rlm_crl.c
20 * @brief Check a certificate's serial number against a CRL
21 *
22 * @author Arran Cudbard-Bell (a.cudbardb@freeradius.org)
23 * @copyright 2025 Network RADIUS SAS (legal@networkradius.com)
24 */
25RCSID("$Id: 67210b48a732b93063254846da8ee12469b97df3 $")
26
27#include <freeradius-devel/util/dlist.h>
28#include <freeradius-devel/util/pair.h>
29#include <freeradius-devel/util/value.h>
30#include <freeradius-devel/util/time.h>
31#include <freeradius-devel/util/rb.h>
32#include <freeradius-devel/util/timer.h>
33
34#include <freeradius-devel/server/module_rlm.h>
35#include <freeradius-devel/server/rcode.h>
36#include <freeradius-devel/server/signal.h>
37#include <freeradius-devel/server/pair.h>
38#include <freeradius-devel/server/log.h>
39
40#include <freeradius-devel/unlang/call_env.h>
41#include <freeradius-devel/unlang/action.h>
42#include <freeradius-devel/unlang/interpret.h>
43#include <freeradius-devel/unlang/module.h>
44
45#include <freeradius-devel/tls/strerror.h>
46#include <freeradius-devel/tls/utils.h>
47
48#include <openssl/x509.h>
49#include <openssl/x509v3.h>
50#include <openssl/pem.h>
51#include <openssl/asn1.h>
52#include <openssl/bn.h>
53
54/** Global tree of CRLs
55 *
56 * Separate from the instance data because that's protected.
57 */
58typedef struct {
59 fr_rb_tree_t *crls; //!< A tree of CRLs organised by CDP URL.
60 fr_timer_list_t *timer_list; //!< The timer list to use for CRL expiry.
61 ///< This gets serviced by the main loop.
64
65typedef struct {
66 CONF_SECTION *virtual_server; //!< Virtual server to use when retrieving CRLs
67 fr_time_delta_t force_expiry; //!< Force expiry of CRLs after this time
69 fr_time_delta_t force_delta_expiry; //!< Force expiry of delta CRLs after this time
71 fr_time_delta_t early_refresh; //!< Time interval before nextUpdate to refresh
72 char const *ca_file; //!< File containing certs for verifying CRL signatures.
73 char const *ca_path; //!< Directory containing certs for verifying CRL signatures.
74 X509_STORE *verify_store; //!< Store of certificates to verify CRL signatures.
75 rlm_crl_mutable_t *mutable; //!< Mutable data that's shared between all threads.
76} rlm_crl_t;
77
78/** A single CRL in the global list of CRLs */
79typedef struct {
80 X509_CRL *crl; //!< The CRL.
81 char const *cdp_url; //!< The URL of the CRL.
82 ASN1_INTEGER *crl_num; //!< The CRL number.
83 fr_timer_t *ev; //!< When to expire the CRL
84 fr_rb_node_t node; //!< The node in the tree
85 fr_value_box_list_t delta_urls; //!< URLs from which a delta CRL can be retrieved.
86 rlm_crl_t const *inst; //!< The instance of the CRL module.
88
89/** A status used to track which CRL is being checked */
90typedef enum {
91 CRL_CHECK_BASE = 0, //!< The base CRL is being checked
92 CRL_CHECK_FETCH_DELTA, //!< The delta CRL is being fetched
93 CRL_CHECK_DELTA //!< The delta CRL exists and is being checked
95
96typedef struct {
97 fr_value_box_t *cdp_url; //!< The URL we're currently attempting to load.
98 crl_entry_t *base_crl; //!< The base CRL relating to the delta currently being fetched.
99 fr_value_box_list_t crl_data; //!< Data from CRL expansion.
100 fr_value_box_list_t missing_crls; //!< CRLs missing from the tree
101 crl_check_status_t status; //!< Status of the current CRL check.
103
105 { FR_CONF_OFFSET_IS_SET("force_expiry", FR_TYPE_TIME_DELTA, 0, rlm_crl_t, force_expiry) },
106 { FR_CONF_OFFSET_IS_SET("force_delta_expiry", FR_TYPE_TIME_DELTA, 0, rlm_crl_t, force_delta_expiry) },
107 { FR_CONF_OFFSET("early_refresh", rlm_crl_t, early_refresh) },
108 { FR_CONF_OFFSET("ca_file", rlm_crl_t, ca_file) },
109 { FR_CONF_OFFSET("ca_path", rlm_crl_t, ca_path) },
111};
112
114
117 { .out = &dict_freeradius, .proto = "freeradius" },
118 { NULL }
119};
120
123
126 { .out = &attr_crl_data, .name = "CRL.Data", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
127 { .out = &attr_crl_cdp_url, .name = "CRL.CDP-URL", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
128 { NULL }
129};
130
131typedef struct {
132 tmpl_t *http_exp; //!< The xlat expansion used to retrieve the CRL via http://
133 tmpl_t *ldap_exp; //!< The xlat expansion used to retrieve the CRL via ldap://
134 fr_value_box_t serial; //!< The serial to check
135 fr_value_box_list_head_t *cdp; //!< The CRL distribution points
137
138typedef enum {
139 CRL_ERROR = -1, //!< Unspecified error ocurred.
140 CRL_ENTRY_NOT_FOUND = 0, //!< Serial not found in this CRL.
141 CRL_ENTRY_FOUND = 1, //!< Serial was found in this CRL.
142 CRL_ENTRY_REMOVED = 2, //!< Serial was "un-revoked" in this delta CRL.
143 CRL_NOT_FOUND = 3, //!< No CRL found, need to load it from the CDP URL
144 CRL_MISSING_DELTA = 4, //!< Need to load a delta CRL to supplement this CRL.
145} crl_ret_t;
146
147#ifdef WITH_TLS
148static const call_env_method_t crl_env = {
150 .env = (call_env_parser_t[]){
152 ((call_env_parser_t[]) {
154 ((call_env_parser_t[]) {
158 }))},
160 }))},
162 .pair.dflt = "session-state.TLS-Certificate.Serial", .pair.dflt_quote = T_BARE_WORD },
164 .pair.dflt = "session-state.TLS-Certificate.X509v3-CRL-Distribution-Points[*]", .pair.dflt_quote = T_BARE_WORD },
166 },
167};
168
169static int8_t crl_cmp(void const *a, void const *b)
170{
171 crl_entry_t const *crl_a = (crl_entry_t const *)a;
172 crl_entry_t const *crl_b = (crl_entry_t const *)b;
173
174 return CMP(strcmp(crl_a->cdp_url, crl_b->cdp_url), 0);
175}
176
177static void crl_free(void *data)
178{
180}
181
182static void crl_expire(UNUSED fr_timer_list_t *tl, UNUSED fr_time_t now, UNUSED void *uctx)
183{
184 crl_entry_t *crl = talloc_get_type_abort(uctx, crl_entry_t);
185 crl_entry_t *delta, find;
186 fr_value_box_t *vb = NULL;
187
188 DEBUG2("CRL associated with CDP %s expired", crl->cdp_url);
189 pthread_mutex_lock(&crl->inst->mutable->mutex);
190 fr_rb_remove(crl->inst->mutable->crls, crl);
191 while ((vb = fr_value_box_list_next(&crl->delta_urls, vb))) {
192 find.cdp_url = vb->vb_strvalue;
193 delta = fr_rb_find(crl->inst->mutable->crls, &find);
194 if (!delta) continue;
195 DEBUG2("Expiring delta CRL from %s due to expired base CRL", delta->cdp_url);
196 fr_rb_remove(crl->inst->mutable->crls, delta);
197 talloc_free(delta);
198 }
199 pthread_mutex_unlock(&crl->inst->mutable->mutex);
200 talloc_free(crl);
201}
202
203/** Make sure we don't lock up the server if a request is cancelled
204 */
205static void crl_signal(module_ctx_t const *mctx, UNUSED request_t *request, fr_signal_t action)
206{
208
209 if (action == FR_SIGNAL_CANCEL) {
210 pthread_mutex_unlock(&inst->mutable->mutex);
212 }
213}
214
215/** See if a particular serial is present in a CRL list
216 *
217 */
218static crl_ret_t crl_check_entry(crl_entry_t *crl_entry, request_t *request, uint8_t const *serial)
219{
220 X509_REVOKED *revoked;
221 ASN1_INTEGER *asn1_serial = NULL;
222 int ret;
223
224 asn1_serial = d2i_ASN1_INTEGER(NULL, (unsigned char const **)&serial, talloc_array_length(serial));
225 ret = X509_CRL_get0_by_serial(crl_entry->crl, &revoked, asn1_serial);
226 ASN1_INTEGER_free(asn1_serial);
227 switch (ret) {
228 /* The docs describe 0 as "failure" - but that means "failed to find"*/
229 case 0:
230 RDEBUG3("Certificate not in CRL");
231 return CRL_ENTRY_NOT_FOUND;
232
233 case 1:
234 RDEBUG2("Certificate revoked by %s", crl_entry->cdp_url);
235 return CRL_ENTRY_FOUND;
236
237 case 2:
238 RDEBUG3("Certificate un-revoked by %s", crl_entry->cdp_url);
239 return CRL_ENTRY_REMOVED;
240 }
241
242 return CRL_ERROR;
243}
244
245/** Resolve a cdp_url to a CRL entry, and check serial against it, if it exists
246 *
247 */
248static crl_ret_t crl_check_serial(fr_rb_tree_t *crls, request_t *request, char const *cdp_url, uint8_t const *serial,
249 crl_entry_t **found)
250{
251 crl_entry_t *delta, find = { .cdp_url = cdp_url};
252 fr_value_box_t *vb = NULL;
254
255 *found = fr_rb_find(crls, &find);
256 if (*found == NULL) return CRL_NOT_FOUND;
257
258 /*
259 * First check the delta if it should exist
260 */
261 while ((vb = fr_value_box_list_next(&(*found)->delta_urls, vb))) {
262 find.cdp_url = vb->vb_strvalue;
263 delta = fr_rb_find(crls, &find);
264 if (delta) {
265 ret = crl_check_entry(delta, request, serial);
266
267 /*
268 * An entry found in a delta overrides the base CRL
269 */
270 if (ret != CRL_ENTRY_NOT_FOUND) return ret;
271 break;
272 } else {
273 ret = CRL_MISSING_DELTA;
274 }
275 }
276
277 if (ret == CRL_MISSING_DELTA) return ret;
278
279 return crl_check_entry(*found, request, serial);
280}
281
282static int _crl_entry_free(crl_entry_t *crl_entry)
283{
284 X509_CRL_free(crl_entry->crl);
285 if (crl_entry->crl_num) ASN1_INTEGER_free(crl_entry->crl_num);
286 return 0;
287}
288
289/** Add an entry to the cdp_url -> crl tree
290 *
291 * @note Must be called with the mutex held.
292 */
293static crl_entry_t *crl_entry_create(rlm_crl_t const *inst, fr_timer_list_t *tl, char const *url, uint8_t const *data,
294 crl_entry_t *base_crl)
295{
296 uint8_t const *our_data = data;
297 crl_entry_t *crl;
298 time_t next_update;
299 fr_time_t now = fr_time();
300 fr_time_delta_t expiry_time;
301 int i;
302 STACK_OF(DIST_POINT) *dps;
303 X509_STORE_CTX *verify_ctx = NULL;
304 X509_OBJECT *xobj;
305 EVP_PKEY *pkey;
306
307 MEM(crl = talloc_zero(inst->mutable->crls, crl_entry_t));
308 crl->cdp_url = talloc_bstrdup(crl, url);
309 crl->crl = d2i_X509_CRL(NULL, (const unsigned char **)&our_data, talloc_array_length(our_data));
310 if (crl->crl == NULL) {
311 fr_tls_strerror_printf("Failed to parse CRL from %s", url);
312 error:
313 talloc_free(crl);
314 if (verify_ctx) X509_STORE_CTX_free(verify_ctx);
315 return NULL;
316 }
317 talloc_set_destructor(crl, _crl_entry_free);
318
319 verify_ctx = X509_STORE_CTX_new();
320 if (!verify_ctx || !X509_STORE_CTX_init(verify_ctx, inst->verify_store, NULL, NULL)) {
321 fr_tls_strerror_printf("Error initialising X509 store");
322 goto error;
323 }
324
325 xobj = X509_STORE_CTX_get_obj_by_subject(verify_ctx, X509_LU_X509,
326 X509_CRL_get_issuer(crl->crl));
327 if (!xobj) {
328 fr_tls_strerror_printf("CRL issuer certificate not in trusted store");
329 goto error;
330 }
331 pkey = X509_get_pubkey(X509_OBJECT_get0_X509(xobj));
332 X509_OBJECT_free(xobj);
333 if (!pkey) {
334 fr_tls_strerror_printf("Error getting CRL issuer public key");
335 goto error;
336 }
337 i = X509_CRL_verify(crl->crl, pkey);
338 EVP_PKEY_free(pkey);
339
340 if (i < 0) {
341 fr_tls_strerror_printf("Could not verify CRL signature");
342 goto error;
343 }
344 if (i == 0) {
345 fr_tls_strerror_printf("CRL certificate signature failed");
346 goto error;
347 }
348
349 crl->crl_num = X509_CRL_get_ext_d2i(crl->crl, NID_crl_number, &i, NULL);
350
351 /*
352 * If we're passed a base_crl, then this is a delta - check the delta
353 * relates to the correct base.
354 */
355 if (base_crl) {
356 ASN1_INTEGER *base_num = X509_CRL_get_ext_d2i(crl->crl, NID_delta_crl, &i, NULL);
357 if (!base_num) {
358 fr_tls_strerror_printf("Delta CRL missing Delta CRL Indicator extension");
359 goto error;
360 }
361 if (ASN1_INTEGER_cmp(base_num, base_crl->crl_num) != 0) {
362 fr_tls_strerror_printf("Delta CRL referrs to incorrect base CRL number");
363 ASN1_INTEGER_free(base_num);
364 goto error;
365 }
366 ASN1_INTEGER_free(base_num);
367 if (ASN1_INTEGER_cmp(crl->crl_num, base_crl->crl_num) < 0) {
368 fr_tls_strerror_printf("Delta CRL number is less than base CRL number");
369 goto error;
370 }
371 }
372
373 if (fr_tls_utils_asn1time_to_epoch(&next_update, X509_CRL_get0_nextUpdate(crl->crl)) < 0) {
374 fr_tls_strerror_printf("Failed to parse nextUpdate from CRL");
375 goto error;
376 }
377
378 if (!fr_rb_insert(inst->mutable->crls, crl)) {
379 ERROR("Failed to insert CRL into tree of CRLs");
380 goto error;
381 }
382 crl->inst = inst;
383
384 /*
385 * Check if this CRL has a Freshest CRL extension - the list of URIs to get deltas from
386 */
387 fr_value_box_list_init(&crl->delta_urls);
388 if (!base_crl && (dps = X509_CRL_get_ext_d2i(crl->crl, NID_freshest_crl, NULL, NULL))) {
389 DIST_POINT *dp;
390 STACK_OF(GENERAL_NAME) *names;
391 GENERAL_NAME *name;
392 int j;
393 fr_value_box_t *vb;
394
395 for (i = 0; i < sk_DIST_POINT_num(dps); i++) {
396 dp = sk_DIST_POINT_value(dps, i);
397 names = dp->distpoint->name.fullname;
398 for (j = 0; j < sk_GENERAL_NAME_num(names); j++) {
399 name = sk_GENERAL_NAME_value(names, j);
400 if (name->type != GEN_URI) continue;
401 MEM(vb = fr_value_box_alloc_null(crl));
402 fr_value_box_bstrndup(vb, vb, NULL,
403 (char const *)ASN1_STRING_get0_data(name->d.uniformResourceIdentifier),
404 ASN1_STRING_length(name->d.uniformResourceIdentifier), true);
405 DEBUG3("CRL references delta URI %pV", vb);
406 fr_value_box_list_insert_tail(&crl->delta_urls, vb);
407 }
408 }
409 CRL_DIST_POINTS_free(dps);
410 }
411
412 expiry_time = fr_time_delta_sub(fr_time_sub(fr_time_from_sec(next_update), now), inst->early_refresh);
413 if (base_crl && inst->force_delta_expiry_is_set) {
414 if (fr_time_delta_cmp(expiry_time, inst->force_delta_expiry)) expiry_time = inst->force_delta_expiry;
415 } else {
416 if (inst->force_expiry_is_set &&
417 (fr_time_delta_cmp(expiry_time, inst->force_expiry) > 0)) expiry_time = inst->force_expiry;
418 }
419
420 DEBUG3("CRL from %s will expire in %pVs", url, fr_box_time_delta(expiry_time));
421 if (fr_timer_in(crl, tl, &crl->ev, expiry_time, false, crl_expire, crl) <0) {
422 ERROR("Failed to set timer to expire CRL");
423 }
424
425 X509_STORE_CTX_free(verify_ctx);
426 return crl;
427}
428
429static unlang_action_t crl_process_cdp_data(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request);
430
431/** Yield to a tmpl to retrieve CRL data
432 *
433 * @param request the current request.
434 * @param env the call_env for this module call.
435 * @param rctx the resume ctx for this module call.
436 *
437 * @returns
438 * - 1 - new tmpl pushed.
439 * - 0 - no tmpl pushed, soft fail.
440 * - -1 - no tmpl pushed, hard fail
441 */
442static int crl_tmpl_yield(request_t *request, rlm_crl_env_t *env, rlm_crl_rctx_t *rctx)
443{
444 fr_pair_t *vp;
445 tmpl_t *vpt;
446
448 MEM(fr_value_box_copy(vp, &vp->data, rctx->cdp_url) == 0);
449
450 if (strncmp(rctx->cdp_url->vb_strvalue, "http", 4) == 0) {
451 vpt = env->http_exp;
452 } else if (strncmp(rctx->cdp_url->vb_strvalue, "ldap", 4) == 0) {
453 if (!env->ldap_exp) {
454 RWARN("CRL URI %pV requires LDAP, but the crl module ldap expansion is not configured", rctx->cdp_url);
455 return 0;
456 }
457 vpt = env->ldap_exp;
458 } else {
459 RERROR("Unsupported URI scheme in CRL URI %pV", rctx->cdp_url);
460 return -1;
461 }
462
463 if (unlang_module_yield_to_tmpl(rctx, &rctx->crl_data, request, vpt,
464 NULL, crl_process_cdp_data, crl_signal, 0, rctx) < 0) return -1;
465 return 1;
466}
467
468static unlang_action_t crl_by_url(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request);
469
470/** Process the response from evaluating the cdp_url -> crl_data expansion
471 *
472 * This is the resumption function when we yield to get CRL data associated with a URL
473 */
474static unlang_action_t crl_process_cdp_data(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
475{
477 rlm_crl_env_t *env = talloc_get_type_abort(mctx->env_data, rlm_crl_env_t);
478 rlm_crl_rctx_t *rctx = talloc_get_type_abort(mctx->rctx, rlm_crl_rctx_t);
480
481 switch (fr_value_box_list_num_elements(&rctx->crl_data)) {
482 case 0:
483 REDEBUG("No CRL data returned from %pV, failing", rctx->cdp_url);
484 again:
485 talloc_free(rctx->cdp_url);
486
487 /*
488 * If there are more URIs to try, push a new tmpl to expand.
489 */
490 rctx->cdp_url = fr_value_box_list_pop_head(&rctx->missing_crls);
491 if (rctx->cdp_url) {
492 switch (crl_tmpl_yield(request, env, rctx)) {
493 case 0:
494 goto again;
495 case 1:
497 default:
498 break;
499 }
500 }
501 fail:
502 pthread_mutex_unlock(&inst->mutable->mutex);
503 fr_value_box_list_talloc_free(&rctx->crl_data);
506
507 case 1:
508 {
509 crl_entry_t *crl_entry;
510 fr_value_box_t *crl_data = fr_value_box_list_pop_head(&rctx->crl_data);
511
512 crl_entry = crl_entry_create(inst, unlang_interpret_event_list(request)->tl,
513 rctx->cdp_url->vb_strvalue,
514 crl_data->vb_octets, rctx->base_crl);
515 talloc_free(crl_data);
516 if (!crl_entry) {
517 RPERROR("Failed to process returned CRL data");
518 goto again;
519 }
520
521 /*
522 * We've successfully loaded a URI - so we can clear the list of missing crls
523 * This can then be re-used to hold missing delta CRLs if needed.
524 */
525 fr_value_box_list_talloc_free(&rctx->missing_crls);
526
527 if (fr_value_box_list_num_elements(&crl_entry->delta_urls) > 0) {
528 crl_entry_t *delta, find;
529 fr_value_box_t *vb = NULL, *delta_uri;
530
531 rctx->status = CRL_CHECK_DELTA;
532 while ((vb = fr_value_box_list_next(&crl_entry->delta_urls, vb))) {
533 find.cdp_url = vb->vb_strvalue;
534 delta = fr_rb_find(inst->mutable->crls, &find);
535 if (delta) {
536 ret = crl_check_entry(delta, request, env->serial.vb_octets);
537 /*
538 * The delta contained an entry for this serial - so this
539 * is the return status.
540 */
541 if (ret != CRL_ENTRY_NOT_FOUND) break;
542 } else {
543 delta_uri = fr_value_box_acopy(rctx, vb);
544 fr_value_box_list_insert_tail(&rctx->missing_crls, delta_uri);
545 }
546 }
547
548 /*
549 * None of the delta CRL URIs were found, so go and get one.
550 * The list of URIs to fetch will now be in rctx->missing_crls
551 */
552 if (ret == CRL_NOT_FOUND) {
554 rctx->base_crl = crl_entry;
555 goto again;
556 }
557 }
558
559 if (rctx->status != CRL_CHECK_DELTA) ret = crl_check_entry(crl_entry, request, env->serial.vb_octets);
560 check_return:
561 switch (ret) {
562 case CRL_ENTRY_FOUND:
563 pthread_mutex_unlock(&inst->mutable->mutex);
565
567 /*
568 * We have a CRL, but the serial is not in it.
569 *
570 * If this was after fetching a delta, go check the base
571 */
572 if (rctx->status == CRL_CHECK_FETCH_DELTA) {
573 RDEBUG3("Certificate not in delta CRL, checking base CRL");
574 rctx->status = CRL_CHECK_BASE;
575 ret = crl_check_entry(rctx->base_crl, request, env->serial.vb_octets);
576 goto check_return;
577 }
579
581 pthread_mutex_unlock(&inst->mutable->mutex);
584
585 case CRL_ERROR:
586 goto fail;
587
588 /*
589 * This should never be returned by crl_check_entry because we provided the entry!
590 */
592 case CRL_NOT_FOUND:
593 fr_assert(0);
594 goto fail;
595 }
596
597 }
598 break;
599
600 default:
601 REDEBUG("Too many CRL values returned, failing");
602 break;
603 }
604
605 goto fail;
606}
607
608static unlang_action_t crl_by_url(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
609{
611 rlm_crl_env_t *env = talloc_get_type_abort(mctx->env_data, rlm_crl_env_t);
612 rlm_crl_rctx_t *rctx = mctx->rctx;
614 crl_entry_t *found;
615
616 if (!rctx) rctx = talloc_zero(unlang_interpret_frame_talloc_ctx(request), rlm_crl_rctx_t);
617 fr_value_box_list_init(&rctx->missing_crls);
618
619 pthread_mutex_lock(&inst->mutable->mutex);
620
621 /*
622 * Fast path when we have a CRL.
623 * All distribution points are considered equivalent, so check if
624 * if we have any of them before attempting to fetch missing ones.
625 */
626 while ((rctx->cdp_url = fr_value_box_list_pop_head(env->cdp))) {
627 switch (crl_check_serial(inst->mutable->crls, request, rctx->cdp_url->vb_strvalue,
628 env->serial.vb_octets, &found)) {
629 case CRL_ENTRY_FOUND:
630 rcode = RLM_MODULE_REJECT;
631 break;
632
635 rcode = RLM_MODULE_OK;
636 break;
637
638 case CRL_ERROR:
639 continue;
640
641 case CRL_NOT_FOUND:
642 fr_value_box_list_insert_tail(&rctx->missing_crls, rctx->cdp_url);
643 rcode = RLM_MODULE_NOTFOUND;
644 continue;
645
647 {
648 /*
649 * We found a base CRL, but it has a delta which
650 * was not found. Populate the "missing" list with
651 * the CDP for the delta and go get it.
652 */
653 fr_value_box_t *vb = NULL, *delta_uri;
654 rctx->base_crl = found;
656 fr_value_box_list_talloc_free(&rctx->missing_crls);
657 while ((vb = fr_value_box_list_next(&found->delta_urls, vb))) {
658 delta_uri = fr_value_box_acopy(rctx, vb);
659 fr_value_box_list_insert_tail(&rctx->missing_crls, delta_uri);
660 }
661 goto fetch_missing;
662 }
663 }
664 }
665
666 if (rcode != RLM_MODULE_NOTFOUND) {
667 pthread_mutex_unlock(&inst->mutable->mutex);
668 RETURN_MODULE_RCODE(rcode);
669 }
670
671 /*
672 * Need to convert a missing cdp_url to a CRL entry
673 *
674 * We yield to an expansion to allow this to happen, then parse the CRL data
675 * and check if the serial has an entry in the CRL.
676 */
677fetch_missing:
678 fr_value_box_list_init(&rctx->crl_data);
679
680again:
681 rctx->cdp_url = fr_value_box_list_pop_head(&rctx->missing_crls);
682
683 switch (crl_tmpl_yield(request, env, rctx)) {
684 case 0:
685 goto again;
686 case 1:
687 /*
688 * The lock is released after the pushed tmpl result is handled
689 */
690 /* coverity[missing_unlock] */
692 default:
693 pthread_mutex_unlock(&inst->mutable->mutex);
695 }
696}
697
698static int mod_mutable_free(rlm_crl_mutable_t *mutable)
699{
700 pthread_mutex_destroy(&mutable->mutex);
701 return 0;
702}
703#endif
704
705/** Instantiate the module
706 *
707 */
708static int mod_instantiate(module_inst_ctx_t const *mctx)
709{
710#ifdef WITH_TLS
711 rlm_crl_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_crl_t);
712
713 MEM(inst->mutable = talloc_zero(NULL, rlm_crl_mutable_t));
714 MEM(inst->mutable->crls = fr_rb_inline_talloc_alloc(inst->mutable, crl_entry_t, node, crl_cmp, crl_free));
715 pthread_mutex_init(&inst->mutable->mutex, NULL);
716 talloc_set_destructor(inst->mutable, mod_mutable_free);
717
718 if (!inst->ca_file && !inst->ca_path) {
719 cf_log_err(mctx->mi->conf, "Missing ca_file / ca_path option. One or other (or both) must be specified.");
720 return -1;
721 }
722
723 inst->verify_store = X509_STORE_new();
724 if (!X509_STORE_load_locations(inst->verify_store, inst->ca_file, inst->ca_path)) {
725 cf_log_err(mctx->mi->conf, "Failed reading Trusted root CA file \"%s\" and path \"%s\"",
726 inst->ca_file, inst->ca_path);
727 return -1;
728 }
729
730 X509_STORE_set_purpose(inst->verify_store, X509_PURPOSE_SSL_CLIENT);
731
732 return 0;
733#else
734 cf_log_err(mctx->mi->conf, "rlm_crl requires OpenSSL");
735 return -1;
736#endif
737}
738
739static int mod_detach(
740#ifndef WITH_TLS
741 UNUSED
742#endif
743 module_detach_ctx_t const *mctx)
744{
745#ifdef WITH_TLS
746 rlm_crl_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_crl_t);
747
748 if (inst->verify_store) X509_STORE_free(inst->verify_store);
749 talloc_free(inst->mutable);
750#endif
751 return 0;
752}
753
754extern module_rlm_t rlm_crl;
756 .common = {
757 .magic = MODULE_MAGIC_INIT,
758 .inst_size = sizeof(rlm_crl_t),
760 .detach = mod_detach,
761 .name = "crl",
763 },
764#ifdef WITH_TLS
765 .method_group = {
766 .bindings = (module_method_binding_t[]){
767 { .section = SECTION_NAME(CF_IDENT_ANY, CF_IDENT_ANY), .method = crl_by_url, .method_env = &crl_env },
769 }
770 }
771#endif
772};
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
Definition action.h:35
@ UNLANG_ACTION_PUSHED_CHILD
unlang_t pushed a new child onto the stack, execute it instead of continuing.
Definition action.h:39
#define RCSID(id)
Definition build.h:485
#define FALL_THROUGH
clang 10 doesn't recognised the FALL-THROUGH comment anymore
Definition build.h:324
#define CMP(_a, _b)
Same as CMP_PREFER_SMALLER use when you don't really care about ordering, you just want an ordering.
Definition build.h:112
#define UNUSED
Definition build.h:317
#define CALL_ENV_TERMINATOR
Definition call_env.h:236
#define FR_CALL_ENV_METHOD_OUT(_inst)
Helper macro for populating the size/type fields of a call_env_method_t from the output structure typ...
Definition call_env.h:240
call_env_parser_t const * env
Parsing rules for call method env.
Definition call_env.h:247
#define FR_CALL_ENV_SUBSECTION(_name, _name2, _flags, _subcs)
Specify a call_env_parser_t which defines a nested subsection.
Definition call_env.h:402
@ CALL_ENV_FLAG_SUBSECTION
This is a subsection.
Definition call_env.h:87
@ CALL_ENV_FLAG_SINGLE
If the tmpl produces more than one box this is an error.
Definition call_env.h:77
@ CALL_ENV_FLAG_ATTRIBUTE
Tmpl MUST contain an attribute reference.
Definition call_env.h:86
@ CALL_ENV_FLAG_NONE
Definition call_env.h:74
@ CALL_ENV_FLAG_MULTI
Multiple instances of the conf pairs are allowed.
Definition call_env.h:78
@ CALL_ENV_FLAG_REQUIRED
Associated conf pair or section is required.
Definition call_env.h:75
@ CALL_ENV_FLAG_PARSE_MISSING
If this subsection is missing, still parse it.
Definition call_env.h:88
@ CALL_ENV_FLAG_BARE_WORD_ATTRIBUTE
bare words are treated as an attribute, but strings may be xlats.
Definition call_env.h:92
#define FR_CALL_ENV_OFFSET(_name, _cast_type, _flags, _struct, _field)
Specify a call_env_parser_t which writes out runtime results to the specified field.
Definition call_env.h:340
#define FR_CALL_ENV_PARSE_ONLY_OFFSET(_name, _cast_type, _flags, _struct, _parse_field)
Specify a call_env_parser_t which writes out the result of the parsing phase to the field specified.
Definition call_env.h:389
Per method call config.
Definition call_env.h:180
#define CONF_PARSER_TERMINATOR
Definition cf_parse.h:658
#define FR_CONF_OFFSET(_name, _struct, _field)
conf_parser_t which parses a single CONF_PAIR, writing the result to a field in a struct
Definition cf_parse.h:284
#define FR_CONF_OFFSET_IS_SET(_name, _type, _flags, _struct, _field)
conf_parser_t which parses a single CONF_PAIR, writing the result to a field in a struct,...
Definition cf_parse.h:298
Defines a CONF_PAIR to C data type mapping.
Definition cf_parse.h:595
A section grouping multiple CONF_PAIR.
Definition cf_priv.h:101
#define cf_log_err(_cf, _fmt,...)
Definition cf_util.h:289
#define CF_IDENT_ANY
Definition cf_util.h:78
#define MEM(x)
Definition debug.h:36
#define ERROR(fmt,...)
Definition dhcpclient.c:41
fr_dict_attr_t const ** out
Where to write a pointer to the resolved fr_dict_attr_t.
Definition dict.h:273
fr_dict_t const ** out
Where to write a pointer to the loaded/resolved fr_dict_t.
Definition dict.h:286
Specifies an attribute which must be present for the module to function.
Definition dict.h:272
Specifies a dictionary which must be loaded/loadable for the module to function.
Definition dict.h:285
#define MODULE_MAGIC_INIT
Stop people using different module/library/server versions together.
Definition dl_module.h:63
TALLOC_CTX * unlang_interpret_frame_talloc_ctx(request_t *request)
Get a talloc_ctx which is valid only for this frame.
Definition interpret.c:1405
fr_event_list_t * unlang_interpret_event_list(request_t *request)
Get the event list for the current interpreter.
Definition interpret.c:1757
#define DEBUG3(_fmt,...)
Definition log.h:266
#define RDEBUG3(fmt,...)
Definition log.h:343
#define RWARN(fmt,...)
Definition log.h:297
#define RERROR(fmt,...)
Definition log.h:298
#define RPERROR(fmt,...)
Definition log.h:302
talloc_free(reap)
@ FR_TYPE_TIME_DELTA
A period of time measured in nanoseconds.
@ FR_TYPE_STRING
String of printable characters.
@ FR_TYPE_OCTETS
Raw octets.
unsigned char uint8_t
void * env_data
Per call environment data.
Definition module_ctx.h:44
module_instance_t const * mi
Instance of the module being instantiated.
Definition module_ctx.h:42
void * rctx
Resume ctx that a module previously set.
Definition module_ctx.h:45
module_instance_t * mi
Instance of the module being instantiated.
Definition module_ctx.h:51
Temporary structure to hold arguments for module calls.
Definition module_ctx.h:41
Temporary structure to hold arguments for detach calls.
Definition module_ctx.h:56
Temporary structure to hold arguments for instantiation calls.
Definition module_ctx.h:50
module_t common
Common fields presented by all modules.
Definition module_rlm.h:39
static const conf_parser_t config[]
Definition base.c:183
#define fr_assert(_expr)
Definition rad_assert.h:38
#define pair_update_request(_attr, _da)
#define REDEBUG(fmt,...)
Definition radclient.h:52
#define RDEBUG2(fmt,...)
Definition radclient.h:54
#define DEBUG2(fmt,...)
Definition radclient.h:43
void * fr_rb_remove(fr_rb_tree_t *tree, void const *data)
Remove an entry from the tree, without freeing the data.
Definition rb.c:695
void * fr_rb_find(fr_rb_tree_t const *tree, void const *data)
Find an element in the tree, returning the data, not the node.
Definition rb.c:577
bool fr_rb_insert(fr_rb_tree_t *tree, void const *data)
Insert data into a tree.
Definition rb.c:626
#define fr_rb_inline_talloc_alloc(_ctx, _type, _field, _data_cmp, _data_free)
Allocs a red black that verifies elements are of a specific talloc type.
Definition rb.h:246
The main red black tree structure.
Definition rb.h:73
#define RETURN_MODULE_REJECT
Definition rcode.h:56
#define RETURN_MODULE_RCODE(_rcode)
Definition rcode.h:66
#define RETURN_MODULE_OK
Definition rcode.h:58
#define RETURN_MODULE_FAIL
Definition rcode.h:57
rlm_rcode_t
Return codes indicating the result of the module call.
Definition rcode.h:40
@ RLM_MODULE_OK
The module is OK, continue.
Definition rcode.h:43
@ RLM_MODULE_REJECT
Immediately reject the request.
Definition rcode.h:41
@ RLM_MODULE_NOTFOUND
User not found.
Definition rcode.h:47
@ RLM_MODULE_NOOP
Module succeeded without doing anything.
Definition rcode.h:48
pthread_mutex_t mutex
Definition rlm_crl.c:62
rlm_crl_mutable_t * mutable
Mutable data that's shared between all threads.
Definition rlm_crl.c:75
fr_value_box_list_t crl_data
Data from CRL expansion.
Definition rlm_crl.c:99
tmpl_t * ldap_exp
The xlat expansion used to retrieve the CRL via ldap://.
Definition rlm_crl.c:133
fr_value_box_list_t delta_urls
URLs from which a delta CRL can be retrieved.
Definition rlm_crl.c:85
bool force_delta_expiry_is_set
Definition rlm_crl.c:70
char const * ca_file
File containing certs for verifying CRL signatures.
Definition rlm_crl.c:72
crl_check_status_t
A status used to track which CRL is being checked.
Definition rlm_crl.c:90
@ CRL_CHECK_BASE
The base CRL is being checked.
Definition rlm_crl.c:91
@ CRL_CHECK_DELTA
The delta CRL exists and is being checked.
Definition rlm_crl.c:93
@ CRL_CHECK_FETCH_DELTA
The delta CRL is being fetched.
Definition rlm_crl.c:92
bool force_expiry_is_set
Definition rlm_crl.c:68
fr_rb_tree_t * crls
A tree of CRLs organised by CDP URL.
Definition rlm_crl.c:59
fr_value_box_t serial
The serial to check.
Definition rlm_crl.c:134
crl_ret_t
Definition rlm_crl.c:138
@ CRL_MISSING_DELTA
Need to load a delta CRL to supplement this CRL.
Definition rlm_crl.c:144
@ CRL_ENTRY_FOUND
Serial was found in this CRL.
Definition rlm_crl.c:141
@ CRL_ENTRY_REMOVED
Serial was "un-revoked" in this delta CRL.
Definition rlm_crl.c:142
@ CRL_ERROR
Unspecified error ocurred.
Definition rlm_crl.c:139
@ CRL_NOT_FOUND
No CRL found, need to load it from the CDP URL.
Definition rlm_crl.c:143
@ CRL_ENTRY_NOT_FOUND
Serial not found in this CRL.
Definition rlm_crl.c:140
fr_value_box_t * cdp_url
The URL we're currently attempting to load.
Definition rlm_crl.c:97
fr_dict_attr_autoload_t rlm_crl_dict_attr[]
Definition rlm_crl.c:125
static fr_dict_t const * dict_freeradius
Definition rlm_crl.c:113
rlm_crl_t const * inst
The instance of the CRL module.
Definition rlm_crl.c:86
fr_rb_node_t node
The node in the tree.
Definition rlm_crl.c:84
char const * cdp_url
The URL of the CRL.
Definition rlm_crl.c:81
X509_STORE * verify_store
Store of certificates to verify CRL signatures.
Definition rlm_crl.c:74
fr_time_delta_t force_delta_expiry
Force expiry of delta CRLs after this time.
Definition rlm_crl.c:69
X509_CRL * crl
The CRL.
Definition rlm_crl.c:80
static int mod_detach(UNUSED module_detach_ctx_t const *mctx)
Definition rlm_crl.c:739
fr_value_box_list_t missing_crls
CRLs missing from the tree.
Definition rlm_crl.c:100
fr_timer_list_t * timer_list
The timer list to use for CRL expiry.
Definition rlm_crl.c:60
static fr_dict_attr_t const * attr_crl_cdp_url
Definition rlm_crl.c:122
crl_entry_t * base_crl
The base CRL relating to the delta currently being fetched.
Definition rlm_crl.c:98
fr_time_delta_t force_expiry
Force expiry of CRLs after this time.
Definition rlm_crl.c:67
fr_dict_autoload_t rlm_crl_dict[]
Definition rlm_crl.c:116
fr_timer_t * ev
When to expire the CRL.
Definition rlm_crl.c:83
module_rlm_t rlm_crl
Definition rlm_crl.c:755
char const * ca_path
Directory containing certs for verifying CRL signatures.
Definition rlm_crl.c:73
fr_value_box_list_head_t * cdp
The CRL distribution points.
Definition rlm_crl.c:135
tmpl_t * http_exp
The xlat expansion used to retrieve the CRL via http://.
Definition rlm_crl.c:132
static int mod_instantiate(module_inst_ctx_t const *mctx)
Instantiate the module.
Definition rlm_crl.c:708
static fr_dict_attr_t const * attr_crl_data
Definition rlm_crl.c:121
CONF_SECTION * virtual_server
Virtual server to use when retrieving CRLs.
Definition rlm_crl.c:66
static conf_parser_t module_config[]
Definition rlm_crl.c:104
crl_check_status_t status
Status of the current CRL check.
Definition rlm_crl.c:101
ASN1_INTEGER * crl_num
The CRL number.
Definition rlm_crl.c:82
fr_time_delta_t early_refresh
Time interval before nextUpdate to refresh.
Definition rlm_crl.c:71
A single CRL in the global list of CRLs.
Definition rlm_crl.c:79
Global tree of CRLs.
Definition rlm_crl.c:58
static char const * name
static int instantiate(module_inst_ctx_t const *mctx)
Definition rlm_rest.c:1313
#define SECTION_NAME(_name1, _name2)
Define a section name consisting of a verb and a noun.
Definition section.h:40
CONF_SECTION * conf
Module's instance configuration.
Definition module.h:330
size_t inst_size
Size of the module's instance data.
Definition module.h:204
void * data
Module's instance data.
Definition module.h:272
#define MODULE_BINDING_TERMINATOR
Terminate a module binding list.
Definition module.h:151
Named methods exported by a module.
Definition module.h:173
#define pair_delete_request(_pair_or_da)
Delete a fr_pair_t in the request list.
Definition pair.h:172
static fr_slen_t vpt
Definition tmpl.h:1269
fr_signal_t
Signals that can be generated/processed by request signal handlers.
Definition signal.h:38
@ FR_SIGNAL_CANCEL
Request has been cancelled.
Definition signal.h:40
unlang_action_t unlang_module_yield_to_tmpl(TALLOC_CTX *ctx, fr_value_box_list_t *out, request_t *request, tmpl_t const *vpt, unlang_tmpl_args_t *args, module_method_t resume, unlang_module_signal_t signal, fr_signal_t sigmask, void *rctx)
Push a pre-compiled tmpl and resumption state onto the stack for evaluation.
Definition module.c:227
eap_aka_sim_process_conf_t * inst
fr_pair_t * vp
#define fr_time()
Allow us to arbitrarily manipulate time.
Definition state_test.c:8
Stores an attribute, a value and various bits of other data.
Definition pair.h:68
char * talloc_bstrdup(TALLOC_CTX *ctx, char const *in)
Binary safe strdup function.
Definition talloc.c:558
#define talloc_get_type_abort_const
Definition talloc.h:287
static const char * names[8]
Definition time.c:584
static int8_t fr_time_delta_cmp(fr_time_delta_t a, fr_time_delta_t b)
Compare two fr_time_delta_t values.
Definition time.h:930
static fr_time_t fr_time_from_sec(time_t when)
Convert a time_t (wallclock time) to a fr_time_t (internal time)
Definition time.h:858
static fr_time_delta_t fr_time_delta_sub(fr_time_delta_t a, fr_time_delta_t b)
Definition time.h:261
#define fr_time_sub(_a, _b)
Subtract one time from another.
Definition time.h:229
A time delta, a difference in time measured in nanoseconds.
Definition time.h:80
"server local" time.
Definition time.h:69
An event timer list.
Definition timer.c:50
A timer event.
Definition timer.c:84
#define fr_timer_in(...)
Definition timer.h:87
@ T_BARE_WORD
Definition token.h:120
int fr_tls_utils_asn1time_to_epoch(time_t *out, ASN1_TIME const *asn1)
Convert OpenSSL's ASN1_TIME to an epoch time.
Definition utils.c:115
int fr_value_box_copy(TALLOC_CTX *ctx, fr_value_box_t *dst, const fr_value_box_t *src)
Copy value data verbatim duplicating any buffers.
Definition value.c:3962
int fr_value_box_bstrndup(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_dict_attr_t const *enumv, char const *src, size_t len, bool tainted)
Copy a string to to a fr_value_box_t.
Definition value.c:4379
static fr_slen_t data
Definition value.h:1288
static fr_value_box_t * fr_value_box_acopy(TALLOC_CTX *ctx, fr_value_box_t const *src)
Copy an existing box, allocating a new box to hold its contents.
Definition value.h:734
#define fr_box_time_delta(_val)
Definition value.h:362
#define fr_value_box_alloc_null(_ctx)
Allocate a value box for later use with a value assignment function.
Definition value.h:651