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: 328d3086c99d9354bcb84d8edbe47c00abc468c5 $
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: 328d3086c99d9354bcb84d8edbe47c00abc468c5 $")
26
27#include <freeradius-devel/server/base.h>
28#include <freeradius-devel/server/module_rlm.h>
29#include <freeradius-devel/unlang/call_env.h>
30
31#include <freeradius-devel/tls/strerror.h>
32#include <freeradius-devel/tls/utils.h>
33
34#include <openssl/x509.h>
35#include <openssl/x509v3.h>
36#include <openssl/pem.h>
37#include <openssl/asn1.h>
38#include <openssl/bn.h>
39
40/** Global tree of CRLs
41 *
42 * Separate from the instance data because that's protected.
43 */
44typedef struct {
45 fr_rb_tree_t *crls; //!< A tree of CRLs organised by CDP URL.
46 fr_timer_list_t *timer_list; //!< The timer list to use for CRL expiry.
47 ///< This gets serviced by the main loop.
50
51typedef struct {
52 CONF_SECTION *virtual_server; //!< Virtual server to use when retrieving CRLs
53 fr_time_delta_t force_expiry; //!< Force expiry of CRLs after this time
55 fr_time_delta_t force_delta_expiry; //!< Force expiry of delta CRLs after this time
57 fr_time_delta_t early_refresh; //!< Time interval before nextUpdate to refresh
58 char const *ca_file; //!< File containing certs for verifying CRL signatures.
59 char const *ca_path; //!< Directory containing certs for verifying CRL signatures.
60 X509_STORE *verify_store; //!< Store of certificates to verify CRL signatures.
61 rlm_crl_mutable_t *mutable; //!< Mutable data that's shared between all threads.
62} rlm_crl_t;
63
64/** A single CRL in the global list of CRLs */
65typedef struct {
66 X509_CRL *crl; //!< The CRL.
67 char const *cdp_url; //!< The URL of the CRL.
68 ASN1_INTEGER *crl_num; //!< The CRL number.
69 fr_timer_t *ev; //!< When to expire the CRL
70 fr_rb_node_t node; //!< The node in the tree
71 fr_value_box_list_t delta_urls; //!< URLs from which a delta CRL can be retrieved.
72 rlm_crl_t const *inst; //!< The instance of the CRL module.
74
75/** A status used to track which CRL is being checked */
76typedef enum {
77 CRL_CHECK_BASE = 0, //!< The base CRL is being checked
78 CRL_CHECK_FETCH_DELTA, //!< The delta CRL is being fetched
79 CRL_CHECK_DELTA //!< The delta CRL exists and is being checked
81
82typedef struct {
83 fr_value_box_t *cdp_url; //!< The URL we're currently attempting to load.
84 crl_entry_t *base_crl; //!< The base CRL relating to the delta currently being fetched.
85 fr_value_box_list_t crl_data; //!< Data from CRL expansion.
86 fr_value_box_list_t missing_crls; //!< CRLs missing from the tree
87 crl_check_status_t status; //!< Status of the current CRL check.
89
91 { FR_CONF_OFFSET_IS_SET("force_expiry", FR_TYPE_TIME_DELTA, 0, rlm_crl_t, force_expiry) },
92 { FR_CONF_OFFSET_IS_SET("force_delta_expiry", FR_TYPE_TIME_DELTA, 0, rlm_crl_t, force_delta_expiry) },
93 { FR_CONF_OFFSET("early_refresh", rlm_crl_t, early_refresh) },
94 { FR_CONF_OFFSET("ca_file", rlm_crl_t, ca_file) },
95 { FR_CONF_OFFSET("ca_path", rlm_crl_t, ca_path) },
97};
98
100
103 { .out = &dict_freeradius, .proto = "freeradius" },
104 { NULL }
105};
106
109
112 { .out = &attr_crl_data, .name = "CRL.Data", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
113 { .out = &attr_crl_cdp_url, .name = "CRL.CDP-URL", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
114 { NULL }
115};
116
117typedef struct {
118 tmpl_t *http_exp; //!< The xlat expansion used to retrieve the CRL via http://
119 tmpl_t *ldap_exp; //!< The xlat expansion used to retrieve the CRL via ldap://
120 tmpl_t *ftp_exp; //!< The xlat expansion used to retrieve the CRL via ftp://
121 fr_value_box_t serial; //!< The serial to check
122 fr_value_box_list_head_t *cdp; //!< The CRL distribution points
124
125typedef enum {
126 CRL_ERROR = -1, //!< Unspecified error ocurred.
127 CRL_ENTRY_NOT_FOUND = 0, //!< Serial not found in this CRL.
128 CRL_ENTRY_FOUND = 1, //!< Serial was found in this CRL.
129 CRL_ENTRY_REMOVED = 2, //!< Serial was "un-revoked" in this delta CRL.
130 CRL_NOT_FOUND = 3, //!< No CRL found, need to load it from the CDP URL
131 CRL_MISSING_DELTA = 4, //!< Need to load a delta CRL to supplement this CRL.
132} crl_ret_t;
133
134#ifdef WITH_TLS
135static const call_env_method_t crl_env = {
137 .env = (call_env_parser_t[]){
139 ((call_env_parser_t[]) {
141 ((call_env_parser_t[]) {
146 }))},
148 }))},
150 .pair.dflt = "session-state.TLS-Certificate.Serial", .pair.dflt_quote = T_BARE_WORD },
152 .pair.dflt = "session-state.TLS-Certificate.X509v3-CRL-Distribution-Points[*]", .pair.dflt_quote = T_BARE_WORD },
154 },
155};
156
157static int8_t crl_cmp(void const *a, void const *b)
158{
159 crl_entry_t const *crl_a = (crl_entry_t const *)a;
160 crl_entry_t const *crl_b = (crl_entry_t const *)b;
161
162 return CMP(strcmp(crl_a->cdp_url, crl_b->cdp_url), 0);
163}
164
165static void crl_free(void *data)
166{
168}
169
170static void crl_expire(UNUSED fr_timer_list_t *tl, UNUSED fr_time_t now, UNUSED void *uctx)
171{
172 crl_entry_t *crl = talloc_get_type_abort(uctx, crl_entry_t);
173
174 DEBUG2("CRL associated with CDP %s expired", crl->cdp_url);
175 pthread_mutex_lock(&crl->inst->mutable->mutex);
176 fr_rb_remove(crl->inst->mutable->crls, crl);
177 pthread_mutex_unlock(&crl->inst->mutable->mutex);
178 talloc_free(crl);
179}
180
181/** Make sure we don't lock up the server if a request is cancelled
182 */
183static void crl_signal(module_ctx_t const *mctx, UNUSED request_t *request, fr_signal_t action)
184{
186
187 if (action == FR_SIGNAL_CANCEL) {
188 pthread_mutex_unlock(&inst->mutable->mutex);
190 }
191}
192
193/** See if a particular serial is present in a CRL list
194 *
195 */
196static crl_ret_t crl_check_entry(crl_entry_t *crl_entry, request_t *request, uint8_t const *serial)
197{
198 X509_REVOKED *revoked;
199 ASN1_INTEGER *asn1_serial = NULL;
200 int ret;
201
202 asn1_serial = d2i_ASN1_INTEGER(NULL, (unsigned char const **)&serial, talloc_array_length(serial));
203 ret = X509_CRL_get0_by_serial(crl_entry->crl, &revoked, asn1_serial);
204 ASN1_INTEGER_free(asn1_serial);
205 switch (ret) {
206 /* The docs describe 0 as "failure" - but that means "failed to find"*/
207 case 0:
208 RDEBUG3("Certificate not in CRL");
209 return CRL_ENTRY_NOT_FOUND;
210
211 case 1:
212 REDEBUG2("Certificate revoked by %s", crl_entry->cdp_url);
213 return CRL_ENTRY_FOUND;
214
215 case 2:
216 RDEBUG3("Certificate un-revoked by %s", crl_entry->cdp_url);
217 return CRL_ENTRY_REMOVED;
218 }
219
220 return CRL_ERROR;
221}
222
223/** Resolve a cdp_url to a CRL entry, and check serial against it, if it exists
224 *
225 */
226static crl_ret_t crl_check_serial(fr_rb_tree_t *crls, request_t *request, char const *cdp_url, uint8_t const *serial,
227 crl_entry_t **found)
228{
229 crl_entry_t *delta, find = { .cdp_url = cdp_url};
230 fr_value_box_t *vb = NULL;
232
233 *found = fr_rb_find(crls, &find);
234 if (*found == NULL) return CRL_NOT_FOUND;
235
236 /*
237 * First check the delta if it should exist
238 */
239 while ((vb = fr_value_box_list_next(&(*found)->delta_urls, vb))) {
240 find.cdp_url = vb->vb_strvalue;
241 delta = fr_rb_find(crls, &find);
242 if (delta) {
243 ret = crl_check_entry(delta, request, serial);
244
245 /*
246 * An entry found in a delta overrides the base CRL
247 */
248 if (ret != CRL_ENTRY_NOT_FOUND) return ret;
249 break;
250 } else {
251 ret = CRL_MISSING_DELTA;
252 }
253 }
254
255 if (ret == CRL_MISSING_DELTA) return ret;
256
257 return crl_check_entry(*found, request, serial);
258}
259
260static int _crl_entry_free(crl_entry_t *crl_entry)
261{
262 X509_CRL_free(crl_entry->crl);
263 if (crl_entry->crl_num) ASN1_INTEGER_free(crl_entry->crl_num);
264 return 0;
265}
266
267/** Add an entry to the cdp_url -> crl tree
268 *
269 * @note Must be called with the mutex held.
270 */
271static crl_entry_t *crl_entry_create(rlm_crl_t const *inst, fr_timer_list_t *tl, char const *url, uint8_t const *data,
272 crl_entry_t *base_crl)
273{
274 uint8_t const *our_data = data;
275 crl_entry_t *crl;
276 time_t next_update;
277 fr_time_t now = fr_time();
278 fr_time_delta_t expiry_time;
279 int i;
280 STACK_OF(DIST_POINT) *dps;
281 X509_STORE_CTX *verify_ctx = NULL;
282 X509_OBJECT *xobj;
283 EVP_PKEY *pkey;
284
285 MEM(crl = talloc_zero(inst->mutable->crls, crl_entry_t));
286 crl->cdp_url = talloc_bstrdup(crl, url);
287 crl->crl = d2i_X509_CRL(NULL, (const unsigned char **)&our_data, talloc_array_length(our_data));
288 if (crl->crl == NULL) {
289 fr_tls_strerror_printf("Failed to parse CRL from %s", url);
290 error:
291 talloc_free(crl);
292 if (verify_ctx) X509_STORE_CTX_free(verify_ctx);
293 return NULL;
294 }
295 talloc_set_destructor(crl, _crl_entry_free);
296
297 verify_ctx = X509_STORE_CTX_new();
298 if (!verify_ctx || !X509_STORE_CTX_init(verify_ctx, inst->verify_store, NULL, NULL)) {
299 fr_tls_strerror_printf("Error initialising X509 store");
300 goto error;
301 }
302
303 xobj = X509_STORE_CTX_get_obj_by_subject(verify_ctx, X509_LU_X509,
304 X509_CRL_get_issuer(crl->crl));
305 if (!xobj) {
306 fr_tls_strerror_printf("CRL issuer certificate not in trusted store");
307 goto error;
308 }
309 pkey = X509_get_pubkey(X509_OBJECT_get0_X509(xobj));
310 X509_OBJECT_free(xobj);
311 if (!pkey) {
312 fr_tls_strerror_printf("Error getting CRL issuer public key");
313 goto error;
314 }
315 i = X509_CRL_verify(crl->crl, pkey);
316 EVP_PKEY_free(pkey);
317
318 if (i < 0) {
319 fr_tls_strerror_printf("Could not verify CRL signature");
320 goto error;
321 }
322 if (i == 0) {
323 fr_tls_strerror_printf("CRL certificate signature failed");
324 goto error;
325 }
326
327 crl->crl_num = X509_CRL_get_ext_d2i(crl->crl, NID_crl_number, &i, NULL);
328
329 /*
330 * If we're passed a base_crl, then this is a delta - check the delta
331 * relates to the correct base.
332 */
333 if (base_crl) {
334 ASN1_INTEGER *base_num = X509_CRL_get_ext_d2i(crl->crl, NID_delta_crl, &i, NULL);
335 if (!base_num) {
336 fr_tls_strerror_printf("Delta CRL missing Delta CRL Indicator extension");
337 goto error;
338 }
339 if (ASN1_INTEGER_cmp(base_num, base_crl->crl_num) > 0) {
340 uint64_t delta_base, crl_num;
341 ASN1_INTEGER_get_uint64(&delta_base, base_num);
342 ASN1_INTEGER_get_uint64(&crl_num, base_crl->crl_num);
343 fr_tls_strerror_printf("Delta CRL referrs to base CRL number %"PRIu64", current base is %"PRIu64,
344 delta_base, crl_num);
345 ASN1_INTEGER_free(base_num);
346 goto error;
347 }
348 ASN1_INTEGER_free(base_num);
349 if (ASN1_INTEGER_cmp(crl->crl_num, base_crl->crl_num) < 0) {
350 uint64_t delta_num, crl_num;
351 ASN1_INTEGER_get_uint64(&delta_num, crl->crl_num);
352 ASN1_INTEGER_get_uint64(&crl_num, base_crl->crl_num);
353 fr_tls_strerror_printf("Delta CRL number %"PRIu64" is less than base CRL number %"PRIu64,
354 delta_num, crl_num);
355 goto error;
356 }
357 }
358
359 if (fr_tls_utils_asn1time_to_epoch(&next_update, X509_CRL_get0_nextUpdate(crl->crl)) < 0) {
360 fr_tls_strerror_printf("Failed to parse nextUpdate from CRL");
361 goto error;
362 }
363
364 if (!fr_rb_insert(inst->mutable->crls, crl)) {
365 ERROR("Failed to insert CRL into tree of CRLs");
366 goto error;
367 }
368 crl->inst = inst;
369
370 /*
371 * Check if this CRL has a Freshest CRL extension - the list of URIs to get deltas from
372 */
373 fr_value_box_list_init(&crl->delta_urls);
374 if (!base_crl && (dps = X509_CRL_get_ext_d2i(crl->crl, NID_freshest_crl, NULL, NULL))) {
375 DIST_POINT *dp;
376 STACK_OF(GENERAL_NAME) *names;
377 GENERAL_NAME *name;
378 int j;
379 fr_value_box_t *vb;
380
381 for (i = 0; i < sk_DIST_POINT_num(dps); i++) {
382 dp = sk_DIST_POINT_value(dps, i);
383 names = dp->distpoint->name.fullname;
384 for (j = 0; j < sk_GENERAL_NAME_num(names); j++) {
385 name = sk_GENERAL_NAME_value(names, j);
386 if (name->type != GEN_URI) continue;
387 MEM(vb = fr_value_box_alloc_null(crl));
388 fr_value_box_bstrndup(vb, vb, NULL,
389 (char const *)ASN1_STRING_get0_data(name->d.uniformResourceIdentifier),
390 ASN1_STRING_length(name->d.uniformResourceIdentifier), true);
391 DEBUG3("CRL references delta URI %pV", vb);
392 fr_value_box_list_insert_tail(&crl->delta_urls, vb);
393 }
394 }
395 CRL_DIST_POINTS_free(dps);
396 }
397
398 expiry_time = fr_time_delta_sub(fr_time_sub(fr_time_from_sec(next_update), now), inst->early_refresh);
399 if (base_crl && inst->force_delta_expiry_is_set) {
400 if (fr_time_delta_cmp(expiry_time, inst->force_delta_expiry)) expiry_time = inst->force_delta_expiry;
401 } else {
402 if (inst->force_expiry_is_set &&
403 (fr_time_delta_cmp(expiry_time, inst->force_expiry) > 0)) expiry_time = inst->force_expiry;
404 }
405
406 DEBUG3("CRL from %s will expire in %pVs", url, fr_box_time_delta(expiry_time));
407 if (fr_timer_in(crl, tl, &crl->ev, expiry_time, false, crl_expire, crl) <0) {
408 ERROR("Failed to set timer to expire CRL");
409 }
410
411 X509_STORE_CTX_free(verify_ctx);
412 return crl;
413}
414
415static unlang_action_t CC_HINT(nonnull) crl_process_cdp_data(unlang_result_t *p_result, module_ctx_t const *mctx,
416 request_t *request);
417
418/** Yield to a tmpl to retrieve CRL data
419 *
420 * @param request the current request.
421 * @param env the call_env for this module call.
422 * @param rctx the resume ctx for this module call.
423 *
424 * @returns
425 * - 1 - new tmpl pushed.
426 * - 0 - no tmpl pushed, soft fail.
427 * - -1 - no tmpl pushed, hard fail
428 */
429static int crl_tmpl_yield(request_t *request, rlm_crl_env_t *env, rlm_crl_rctx_t *rctx)
430{
431 fr_pair_t *vp;
432 tmpl_t *vpt;
433
435 MEM(fr_value_box_copy(vp, &vp->data, rctx->cdp_url) == 0);
436
437 if (strncmp(rctx->cdp_url->vb_strvalue, "http", 4) == 0) {
438 vpt = env->http_exp;
439 } else if (strncmp(rctx->cdp_url->vb_strvalue, "ldap", 4) == 0) {
440 if (!env->ldap_exp) {
441 RWARN("CRL URI %pV requires LDAP, but the crl module ldap expansion is not configured", rctx->cdp_url);
442 return 0;
443 }
444 vpt = env->ldap_exp;
445 } else if (strncmp(rctx->cdp_url->vb_strvalue, "ftp", 3) == 0) {
446 if (!env->ftp_exp) {
447 RWARN("CRL URI %pV requires FTP, but the crl module ftp expansion is not configured", rctx->cdp_url);
448 return 0;
449 }
450 vpt = env->ftp_exp;
451 } else {
452 RERROR("Unsupported URI scheme in CRL URI %pV", rctx->cdp_url);
453 return -1;
454 }
455
456 if (unlang_module_yield_to_tmpl(rctx, &rctx->crl_data, request, vpt,
457 NULL, crl_process_cdp_data, crl_signal, 0, rctx) < 0) return -1;
458 return 1;
459}
460
461static unlang_action_t crl_by_url(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request);
462
463/** Process the response from evaluating the cdp_url -> crl_data expansion
464 *
465 * This is the resumption function when we yield to get CRL data associated with a URL
466 */
467static unlang_action_t CC_HINT(nonnull) crl_process_cdp_data(unlang_result_t *p_result, module_ctx_t const *mctx,
468 request_t *request)
469{
471 rlm_crl_env_t *env = talloc_get_type_abort(mctx->env_data, rlm_crl_env_t);
472 rlm_crl_rctx_t *rctx = talloc_get_type_abort(mctx->rctx, rlm_crl_rctx_t);
474
475 switch (fr_value_box_list_num_elements(&rctx->crl_data)) {
476 case 0:
477 REDEBUG("No CRL data returned from %pV, failing", rctx->cdp_url);
478 again:
479 talloc_free(rctx->cdp_url);
480
481 /*
482 * If there are more URIs to try, push a new tmpl to expand.
483 */
484 rctx->cdp_url = fr_value_box_list_pop_head(&rctx->missing_crls);
485 if (rctx->cdp_url) {
486 switch (crl_tmpl_yield(request, env, rctx)) {
487 case 0:
488 goto again;
489 case 1:
491 default:
492 break;
493 }
494 }
495 fail:
496 pthread_mutex_unlock(&inst->mutable->mutex);
497 fr_value_box_list_talloc_free(&rctx->crl_data);
500
501 case 1:
502 {
503 crl_entry_t *crl_entry;
504 fr_value_box_t *crl_data = fr_value_box_list_pop_head(&rctx->crl_data);
505
506 crl_entry = crl_entry_create(inst, unlang_interpret_event_list(request)->tl,
507 rctx->cdp_url->vb_strvalue,
508 crl_data->vb_octets, rctx->base_crl);
509 talloc_free(crl_data);
510 if (!crl_entry) {
511 RPERROR("Failed to process returned CRL data");
512 goto again;
513 }
514
515 /*
516 * We've successfully loaded a URI - so we can clear the list of missing crls
517 * This can then be re-used to hold missing delta CRLs if needed.
518 */
519 fr_value_box_list_talloc_free(&rctx->missing_crls);
520
521 if (fr_value_box_list_num_elements(&crl_entry->delta_urls) > 0) {
522 crl_entry_t *delta, find;
523 fr_value_box_t *vb = NULL, *delta_uri;
524
525 rctx->status = CRL_CHECK_DELTA;
526 while ((vb = fr_value_box_list_next(&crl_entry->delta_urls, vb))) {
527 find.cdp_url = vb->vb_strvalue;
528 delta = fr_rb_find(inst->mutable->crls, &find);
529 if (delta) {
530 ret = crl_check_entry(delta, request, env->serial.vb_octets);
531 /*
532 * The delta contained an entry for this serial - so this
533 * is the return status.
534 */
535 if (ret != CRL_ENTRY_NOT_FOUND) break;
536 } else {
537 delta_uri = fr_value_box_acopy(rctx, vb);
538 fr_value_box_list_insert_tail(&rctx->missing_crls, delta_uri);
539 }
540 }
541
542 /*
543 * None of the delta CRL URIs were found, so go and get one.
544 * The list of URIs to fetch will now be in rctx->missing_crls
545 */
546 if (ret == CRL_NOT_FOUND) {
548 rctx->base_crl = crl_entry;
549 goto again;
550 }
551 }
552
553 if (rctx->status != CRL_CHECK_DELTA) ret = crl_check_entry(crl_entry, request, env->serial.vb_octets);
554 check_return:
555 switch (ret) {
556 case CRL_ENTRY_FOUND:
557 pthread_mutex_unlock(&inst->mutable->mutex);
559
561 /*
562 * We have a CRL, but the serial is not in it.
563 *
564 * If this was after fetching a delta, go check the base
565 */
566 if (rctx->status == CRL_CHECK_FETCH_DELTA) {
567 RDEBUG3("Certificate not in delta CRL, checking base CRL");
568 rctx->status = CRL_CHECK_BASE;
569 ret = crl_check_entry(rctx->base_crl, request, env->serial.vb_octets);
570 goto check_return;
571 }
573
575 pthread_mutex_unlock(&inst->mutable->mutex);
578
579 case CRL_ERROR:
580 goto fail;
581
582 /*
583 * This should never be returned by crl_check_entry because we provided the entry!
584 */
586 case CRL_NOT_FOUND:
587 fr_assert(0);
588 goto fail;
589 }
590
591 }
592 break;
593
594 default:
595 REDEBUG("Too many CRL values returned, failing");
596 break;
597 }
598
599 goto fail;
600}
601
602static unlang_action_t CC_HINT(nonnull) crl_by_url(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
603{
605 rlm_crl_env_t *env = talloc_get_type_abort(mctx->env_data, rlm_crl_env_t);
606 rlm_crl_rctx_t *rctx = mctx->rctx;
608 crl_entry_t *found;
609
610 if (fr_value_box_list_num_elements(env->cdp) == 0) RETURN_UNLANG_NOOP;
611
612 if (!rctx) rctx = talloc_zero(unlang_interpret_frame_talloc_ctx(request), rlm_crl_rctx_t);
613 fr_value_box_list_init(&rctx->missing_crls);
614
615 pthread_mutex_lock(&inst->mutable->mutex);
616
617 /*
618 * Fast path when we have a CRL.
619 * All distribution points are considered equivalent, so check if
620 * if we have any of them before attempting to fetch missing ones.
621 */
622 while ((rctx->cdp_url = fr_value_box_list_pop_head(env->cdp))) {
623 switch (crl_check_serial(inst->mutable->crls, request, rctx->cdp_url->vb_strvalue,
624 env->serial.vb_octets, &found)) {
625 case CRL_ENTRY_FOUND:
626 rcode = RLM_MODULE_REJECT;
627 break;
628
631 rcode = RLM_MODULE_OK;
632 break;
633
634 case CRL_ERROR:
635 continue;
636
637 case CRL_NOT_FOUND:
638 fr_value_box_list_insert_tail(&rctx->missing_crls, rctx->cdp_url);
639 rcode = RLM_MODULE_NOTFOUND;
640 continue;
641
643 {
644 /*
645 * We found a base CRL, but it has a delta which
646 * was not found. Populate the "missing" list with
647 * the CDP for the delta and go get it.
648 */
649 fr_value_box_t *vb = NULL, *delta_uri;
650 rctx->base_crl = found;
652 fr_value_box_list_talloc_free(&rctx->missing_crls);
653 while ((vb = fr_value_box_list_next(&found->delta_urls, vb))) {
654 delta_uri = fr_value_box_acopy(rctx, vb);
655 fr_value_box_list_insert_tail(&rctx->missing_crls, delta_uri);
656 }
657 goto fetch_missing;
658 }
659 }
660 }
661
662 if (rcode != RLM_MODULE_NOTFOUND) {
663 pthread_mutex_unlock(&inst->mutable->mutex);
664 RETURN_UNLANG_RCODE(rcode);
665 }
666
667 /*
668 * Need to convert a missing cdp_url to a CRL entry
669 *
670 * We yield to an expansion to allow this to happen, then parse the CRL data
671 * and check if the serial has an entry in the CRL.
672 */
673fetch_missing:
674 fr_value_box_list_init(&rctx->crl_data);
675
676again:
677 rctx->cdp_url = fr_value_box_list_pop_head(&rctx->missing_crls);
678
679 switch (crl_tmpl_yield(request, env, rctx)) {
680 case 0:
681 goto again;
682 case 1:
683 /*
684 * The lock is released after the pushed tmpl result is handled
685 */
686 /* coverity[missing_unlock] */
688 default:
689 pthread_mutex_unlock(&inst->mutable->mutex);
691 }
692}
693
694static int mod_mutable_free(rlm_crl_mutable_t *mutable)
695{
696 pthread_mutex_destroy(&mutable->mutex);
697 return 0;
698}
699#endif
700
701/** Instantiate the module
702 *
703 */
704static int mod_instantiate(module_inst_ctx_t const *mctx)
705{
706#ifdef WITH_TLS
707 rlm_crl_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_crl_t);
708
709 MEM(inst->mutable = talloc_zero(NULL, rlm_crl_mutable_t));
710 MEM(inst->mutable->crls = fr_rb_inline_talloc_alloc(inst->mutable, crl_entry_t, node, crl_cmp, crl_free));
711 pthread_mutex_init(&inst->mutable->mutex, NULL);
712 talloc_set_destructor(inst->mutable, mod_mutable_free);
713
714 if (!inst->ca_file && !inst->ca_path) {
715 cf_log_err(mctx->mi->conf, "Missing ca_file / ca_path option. One or other (or both) must be specified.");
716 return -1;
717 }
718
719 inst->verify_store = X509_STORE_new();
720 if (!X509_STORE_load_locations(inst->verify_store, inst->ca_file, inst->ca_path)) {
721 cf_log_err(mctx->mi->conf, "Failed reading Trusted root CA file \"%s\" and path \"%s\"",
722 inst->ca_file, inst->ca_path);
723 return -1;
724 }
725
726 X509_STORE_set_purpose(inst->verify_store, X509_PURPOSE_SSL_CLIENT);
727
728 return 0;
729#else
730 cf_log_err(mctx->mi->conf, "rlm_crl requires OpenSSL");
731 return -1;
732#endif
733}
734
735static int mod_detach(
736#ifndef WITH_TLS
737 UNUSED
738#endif
739 module_detach_ctx_t const *mctx)
740{
741#ifdef WITH_TLS
742 rlm_crl_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_crl_t);
743
744 if (inst->verify_store) X509_STORE_free(inst->verify_store);
745 talloc_free(inst->mutable);
746#endif
747 return 0;
748}
749
750extern module_rlm_t rlm_crl;
752 .common = {
753 .magic = MODULE_MAGIC_INIT,
754 .inst_size = sizeof(rlm_crl_t),
756 .detach = mod_detach,
757 .name = "crl",
759 },
760#ifdef WITH_TLS
761 .method_group = {
762 .bindings = (module_method_binding_t[]){
763 { .section = SECTION_NAME(CF_IDENT_ANY, CF_IDENT_ANY), .method = crl_by_url, .method_env = &crl_env },
765 }
766 }
767#endif
768};
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
@ CALL_ENV_FLAG_NULLABLE
Tmpl expansions are allowed to produce no output.
Definition call_env.h:80
#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:274
fr_dict_t const ** out
Where to write a pointer to the loaded/resolved fr_dict_t.
Definition dict.h:287
Specifies an attribute which must be present for the module to function.
Definition dict.h:273
Specifies a dictionary which must be loaded/loadable for the module to function.
Definition dict.h:286
#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:1661
fr_event_list_t * unlang_interpret_event_list(request_t *request)
Get the event list for the current interpreter.
Definition interpret.c:2013
#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
#define REDEBUG2(fmt,...)
Definition log.h:372
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:186
#define fr_assert(_expr)
Definition rad_assert.h:38
#define pair_update_request(_attr, _da)
#define REDEBUG(fmt,...)
Definition radclient.h:52
#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_UNLANG_RCODE(_rcode)
Definition rcode.h:66
#define RETURN_UNLANG_FAIL
Definition rcode.h:57
#define RETURN_UNLANG_REJECT
Definition rcode.h:56
#define RETURN_UNLANG_OK
Definition rcode.h:58
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
#define RETURN_UNLANG_NOOP
Definition rcode.h:63
pthread_mutex_t mutex
Definition rlm_crl.c:48
rlm_crl_mutable_t * mutable
Mutable data that's shared between all threads.
Definition rlm_crl.c:61
fr_value_box_list_t crl_data
Data from CRL expansion.
Definition rlm_crl.c:85
tmpl_t * ldap_exp
The xlat expansion used to retrieve the CRL via ldap://.
Definition rlm_crl.c:119
fr_value_box_list_t delta_urls
URLs from which a delta CRL can be retrieved.
Definition rlm_crl.c:71
bool force_delta_expiry_is_set
Definition rlm_crl.c:56
char const * ca_file
File containing certs for verifying CRL signatures.
Definition rlm_crl.c:58
crl_check_status_t
A status used to track which CRL is being checked.
Definition rlm_crl.c:76
@ CRL_CHECK_BASE
The base CRL is being checked.
Definition rlm_crl.c:77
@ CRL_CHECK_DELTA
The delta CRL exists and is being checked.
Definition rlm_crl.c:79
@ CRL_CHECK_FETCH_DELTA
The delta CRL is being fetched.
Definition rlm_crl.c:78
bool force_expiry_is_set
Definition rlm_crl.c:54
fr_rb_tree_t * crls
A tree of CRLs organised by CDP URL.
Definition rlm_crl.c:45
fr_value_box_t serial
The serial to check.
Definition rlm_crl.c:121
crl_ret_t
Definition rlm_crl.c:125
@ CRL_MISSING_DELTA
Need to load a delta CRL to supplement this CRL.
Definition rlm_crl.c:131
@ CRL_ENTRY_FOUND
Serial was found in this CRL.
Definition rlm_crl.c:128
@ CRL_ENTRY_REMOVED
Serial was "un-revoked" in this delta CRL.
Definition rlm_crl.c:129
@ CRL_ERROR
Unspecified error ocurred.
Definition rlm_crl.c:126
@ CRL_NOT_FOUND
No CRL found, need to load it from the CDP URL.
Definition rlm_crl.c:130
@ CRL_ENTRY_NOT_FOUND
Serial not found in this CRL.
Definition rlm_crl.c:127
fr_value_box_t * cdp_url
The URL we're currently attempting to load.
Definition rlm_crl.c:83
fr_dict_attr_autoload_t rlm_crl_dict_attr[]
Definition rlm_crl.c:111
static fr_dict_t const * dict_freeradius
Definition rlm_crl.c:99
rlm_crl_t const * inst
The instance of the CRL module.
Definition rlm_crl.c:72
fr_rb_node_t node
The node in the tree.
Definition rlm_crl.c:70
char const * cdp_url
The URL of the CRL.
Definition rlm_crl.c:67
X509_STORE * verify_store
Store of certificates to verify CRL signatures.
Definition rlm_crl.c:60
fr_time_delta_t force_delta_expiry
Force expiry of delta CRLs after this time.
Definition rlm_crl.c:55
X509_CRL * crl
The CRL.
Definition rlm_crl.c:66
static int mod_detach(UNUSED module_detach_ctx_t const *mctx)
Definition rlm_crl.c:735
fr_value_box_list_t missing_crls
CRLs missing from the tree.
Definition rlm_crl.c:86
fr_timer_list_t * timer_list
The timer list to use for CRL expiry.
Definition rlm_crl.c:46
static fr_dict_attr_t const * attr_crl_cdp_url
Definition rlm_crl.c:108
crl_entry_t * base_crl
The base CRL relating to the delta currently being fetched.
Definition rlm_crl.c:84
fr_time_delta_t force_expiry
Force expiry of CRLs after this time.
Definition rlm_crl.c:53
fr_dict_autoload_t rlm_crl_dict[]
Definition rlm_crl.c:102
fr_timer_t * ev
When to expire the CRL.
Definition rlm_crl.c:69
module_rlm_t rlm_crl
Definition rlm_crl.c:751
tmpl_t * ftp_exp
The xlat expansion used to retrieve the CRL via ftp://.
Definition rlm_crl.c:120
char const * ca_path
Directory containing certs for verifying CRL signatures.
Definition rlm_crl.c:59
fr_value_box_list_head_t * cdp
The CRL distribution points.
Definition rlm_crl.c:122
tmpl_t * http_exp
The xlat expansion used to retrieve the CRL via http://.
Definition rlm_crl.c:118
static int mod_instantiate(module_inst_ctx_t const *mctx)
Instantiate the module.
Definition rlm_crl.c:704
static fr_dict_attr_t const * attr_crl_data
Definition rlm_crl.c:107
CONF_SECTION * virtual_server
Virtual server to use when retrieving CRLs.
Definition rlm_crl.c:52
static conf_parser_t module_config[]
Definition rlm_crl.c:90
crl_check_status_t status
Status of the current CRL check.
Definition rlm_crl.c:87
ASN1_INTEGER * crl_num
The CRL number.
Definition rlm_crl.c:68
fr_time_delta_t early_refresh
Time interval before nextUpdate to refresh.
Definition rlm_crl.c:57
A single CRL in the global list of CRLs.
Definition rlm_crl.c:65
Global tree of CRLs.
Definition rlm_crl.c:44
static char const * name
static int instantiate(module_inst_ctx_t const *mctx)
Definition rlm_rest.c:1297
#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:349
size_t inst_size
Size of the module's instance data.
Definition module.h:212
void * data
Module's instance data.
Definition module.h:291
#define MODULE_BINDING_TERMINATOR
Terminate a module binding list.
Definition module.h:152
Named methods exported by a module.
Definition module.h:174
#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:229
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
int nonnull(2, 5))
#define fr_value_box_alloc_null(_ctx)
Allocate a value box for later use with a value assignment function.
Definition value.h:651